본문 바로가기

딥러닝/밑바닥부터 시작하는 딥러닝 2

1장 : 신경망 복습

1.3 신경망의 학습

1.3.1 손실함수

  • 교차 엔트로피 오차(Cross Entropy error) 이용
  • 소프트맥스 함수의 출력(확률)이 교차 엔트로피에 입력 
  • 확률과 정답 레이블의 오차를 계산 : 손실함수
  • softmax with Loss 계층 하나로 구현

 

1.3.2 미분과 기울기

  • 각 원소에 대한 미분을 정리한 것이 기울기 gradient
  • 행렬과 기울기의 형상이 같음 
  • 수학에서의 기울기는 벡터에 대한 미분으로 한정된 것과 달리 딥러닝에서는 행렬이나 텐서에 대해서도 미분을 정의하고 기울기라 부름

 

1.3.3 연쇄 법칙 

: 합성함수에 대한 미분의 법칙

-> 많은 함수를 연결하더라도 그 미분은 개별 함수의 미분들을 이용해 구할 수 있음

 

1.3.4 계산 그래프 

  • 각 노드의 역전파 
    • 덧셈 노드 : 상류로부터의 기울기 그대로 흘림
    • 곱셈 노드 : 상류로부터 받은 기울기에 순전파 시의 입력을 서로 바꾼 값을 곱함
    • 분기 노드 : 상류에서 온 기울기들의 합

  • Repeat 노드 : N개의 기울기를 모두 합함

  • Sum 노드 : repeat 
    -> sum 노드와 repeat 노드는 서로 반대 관계
    (sum 노드의 순전파가 repeat노드의 역전파, sum 노드의 역전파가 repeat노드의 순전파)

  • MatMul 노드 (행렬 곱)

-> 행렬의 형상을 확인하여 역전파 식 유도

class MatMul:
    def __init__(self, W):
        self.params = [W]
        self.grads = [np.zeros_like(W)]
        self.x = None

    def forward(self,x):
        W, = self.params
        out = np.matmul(x, W)
        self.x = x
        return out

    def backward(self, dout):
        W, = self.params  #매개변수 
        dx = np.matmul(dout, W.T)  #매개변수에 대응하는 기울기 
        dW = np.matmul(self.x.T, dout)
        self.grads[0][...] = dW  #메모리 위치 고정
        return dx
  • 생략(ellipsis) 기호 ... 사용 
    • a = b -> a가 가리키는 메모리 위치가 b가 가리키는 위치와 같아짐(실제 데이터는 복사x) => 얕은 복사 
    • a[...] = b -> a의 메모리 위치는 변하지 않고 a가 가리키는 메모리에 b의 원소 복제(실제 데이터가 복제)=> 깊은 복사

-> 생략 기호을 이용함으로써 배열의 메모리 주소가 변하는 일 없이 항상 갚을 덮어씀 

=> 기울기를 그룹화하는 작업을 최초에 한 번만 하면 됨

 

1.3.5 기울기 도출과 역전파 구현

  • Sigmoid 계층

#sigmoid 계층
class Sigmoid:
    def __init__(self):
        self.params, self.grads  = [], []
        self.out = None

    def forward(self, x):
        out = 1/(1+np.exp(-x))
        self.out = out 
        return out 

    def backward(self, dout):
        dx = dout *(1.0 - self.out)*self.out
        return dx

 

  • Affine 계층

순전파 : y = np.matmul(x, W) +b

#Affilne 계층
class Affine:
    def __init__(self, W, b):
        self.params = [W, b]
        self.grads = [np.zeros_like(W), np.zeros_like(b)]
        self.x = None

    def forward(self,x):
        W, b = self.params
        out = np.matmul(x, W) +b
        self.x = x
        return out

    def backward(self,dout):
        W, b = self.params
        dx = np.matmul(dout, W.T)
        dW = np.matmul(self.x.T, dout)
        db = np.sum(dout, axis=0)  #(N,H)->(H), 열기준으로 합 구함 

        self.grads[0][...] = dW
        self.grads[1][...] = db
        return dx

 

  • Softmax with Loss 계층

 

1.3.6 가중치 갱신

  • 신경망 학습

1) 미니배치 : 훈련 데이터 중에서 무작위로 다수의 데이터를 골라낸다.

2) 기울기 계산 : 오차역전파법으로 각 가중치 매개변수에 대한 손실 함수의 기울기를 구한다.

     기울기 - 현재의 가중치 매개변수에서 손실을 가장 크게 하는 방향을 가리킴 
     -> 매개변수를 기울기와 반대 방향으로 갱신 : 경사하강법

3) 매개변수 갱신 : 기울기를 사용하여 가중치 매개변수를 갱신한다.

4) 반복 : 1~3단계를 필요한 만큼 반복한다.

 

  • SGD 확률적경사하강법 구현

#SGD 구현
class SGD:
    def __init__(self, lr=0.01):
        self.lr = lr  #학습률 

    def update(self, params, grads):  #매개변수 갱신 처리 
        for i in range(len(params)):
            params[i] -= self.lr*grads[i]

1.4 신경망으로 문제를 풀다

1.4.1 스파이럴 데이터셋

#스파이럴 데이터셋
from dataset import spiral
import matplotlib.pyplot as plt

x, t = spiral.load_data()
print('x', x.shape)  #(300,2) 2차원
print('t', t.shape)  #(300,3)  3차원 

# 데이터점 플롯
N = 100
CLS_NUM = 3
markers = ['o', 'x', '^']
for i in range(CLS_NUM):
    plt.scatter(x[i*N:(i+1)*N, 0], x[i*N:(i+1)*N, 1], s=40, marker=markers[i])
plt.show()

  • 입력은 2차원 데이터, 분류할 클래스 3개
  • 직선만으로는 클래스들을 분리할 수 없음 
  • 비선형 학습 해야함

 

1.4.2 신경망 구현

##신경망 구현
class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size):
        I, H, O = input_size, hidden_size, output_size

        #가중치와 편향 초기화
        W1 = 0.01 * np.random.randn(I, H)  #작은 무작위 값으로 초기화 
        b1 = np.zeros(H)  #편향 영벡터로 초기화 
        W2 = 0.01 * np.random.randn(H, O)
        b2 = np.zeros(0)

        #계층 생성
        self.layers = [
            Affine(W1, b1),
            Sigmoid(),
            Affine(W2, b2)
        ]
        self.loss_layer = SoftmaxWithLoss()

        #모든 가중치와 기울기를 리스트에 모은다.
        self.params, self.grads = [], []
        for layer in self.layers:
            self.params += layer.params
            self.grads +=layer.grads

    def predict(self, x):
        for layer in self.layers:
            x = layer.forward(x)

        return x
    
    def forward(self,x, t):
        score = self.predict(x)
        loss = self.loss_layer.forward(score, t)
        return loss

    def backward(self, dout=1):
        dout = self.loss_layer.backward(dout)
        for layer in reversed(self.layers):
            dout = layer.backward(dout)
        return dout

 

1.4.3 학습용 코드

  • 학습 데이터를 읽어 들여 신경망(모델)과 옵티마이저(최적화기) 생성
##학습용 코드
from common.optimizer import SGD
from dataset import spiral
import matplotlib.pyplot as plt
from two_layer_net import TwoLayerNet

#1. 하이퍼파라미터 설정
max_epoch = 300
batch_size = 30
hidden_size = 10
learning_rate = 1.0

#2. 데이터 읽기, 모델과 옵티마이저 생성
x, t = spiral.load_data()
model = TwoLayerNet(input_size=2, hidden_size = hidden_size, output_size = 3)
optimizer = SGD(lr=learning_rate)

#학습에 사용하는 변수
data_size = len(x)
max_iters = data_size //batch_size
total_loss = 0
loss_count = 0
loss_list = []

for epoch in range(max_epoch):
    #3. 데이터 뒤섞기
    idx = np.random.permutation(data_size)  
    #데이터 인덱스 뒤섞기 (0부터 data_size-1까지의 무작위 순서를 생성해 반환)
    x = x[idx]
    t = t[idx]

    for iters in range(max_iters):
        batch_x = x[iters*batch_size : (iters+1)*batch_size]  
        #현재반복횟수*배치사이즈 (시작 인덱스)부터 다음 시작인덱스까지 슬라이싱
        batch_t = t[iters*batch_size : (iters+1)*batch_size]

        #4. 기울기를 구해 매개변수 갱신
        loss = model.forward(batch_x, batch_t)
        model.backward()
        optimizer.update(model.params, model.grads)

        total_loss += loss
        loss_count +=1

        #5. 정기적으로 학습 경과 출력
        if (iters+1) % 10 ==0:  #10번째 반복마다 
            avg_loss = total_loss / loss_count
            print('|에폭 %d | 반복 %d / %d |손실 %.2f'
                % (epoch+1, iters+1, max_iters, avg_loss))
            loss_list.append(avg_loss)  #손실의 평균 구해 loss_list 변수에 추가 
            total_loss, loss_count = 0,0

손실그래프 : 가로축 - 학습의 반복, 세로축 - 10번 반복당 손실 평균  / 학습 후 신경망의 결정 경계 시각화 (클래스별 영역을 색으로 구분) 

  • 학습을 진행함에 따라 손실 줄어듦
  • 비선형 분리 영역 학습함

 

1.4.4 Trainer 클래스 

  • 학습을 수행하는 역할을 Trainer라는 클래스로 제공
  • Trainer클래스의 초기화 메서드는 신경망과 옵티마이저를 인수로 받음
  • 그리고 fit()메서드를 호출해 학습 시작

  • plot()메서드도 제공 -> 손실 그래프 그려줌
##Trainer 클래스 
from common.trainer import Trainer
from two_layer_net import TwoLayerNet

max_epoch = 300
batch_size = 30
hidden_size = 10
learning_rate = 1.0

x, t = spiral.load_data()
model= TwoLayerNet(input_size = 2, hidden_size=hidden_size, output_size=3)
optimizer = SGD(lr = learning_rate)

trainer = Trainer(model, optimizer)
trainer.fit(x, t, max_epoch, batch_size, eval_interval=10)
trainer.plot()

1.5 계산 고속화

1.5.1 비트 정밀도 

  • 넘파이의 부동소수점 수는 기본적으로 64비트 
  • 신경망에서는 작은 비트 수로도 문제없이 수행 가능 -> 32비트 부동소수점 수 사용
  • 학습된 가중치를 저장하는 경우에 한해 16비트 수로 변환 
  • CPU와 GPU는 연산 자체를 32비트로 수행

 

1.5.2 GPU(쿠파이)

  • 쿠파이는 GPU를 이용해 병렬 계산을 수행해주는 라이브러리
  • 엔비디아의 GPU에서만 동작
  • 넘파이와 호환되는 API 제공

1.6 정리

  • 신경망은 입력층, 은닉층, 출력층을 지난다.
  • 완전연결계층에 의해 선형 변환이 이뤄지고, 활성화 함수에 의해 비선형 변환이 이뤄진다.
  • 완전연결계층이나 미니배치 처리는 행렬로 모아 한꺼번에 계산할 수 있다.
  • 오차역전파법을 사용하여 신경망의 손실에 관한 기울기를 효율적으로 구할 수 있다.
  • 신경망이 수행하는 처리는 계산 그래프로 시각화할 수 있으며, 순전파와 역전파를 이해하는 데 도움이 된다.
  • 신경망의 구성요소들을 '계층'으로 모듈화해두면, 이를 조립하여 신경망을 쉽게 구성할 수 있다.
  • 신경망 고속화에는 GPU를 이용한 병렬 계산과 데이터의 비트 정밀도가 중요하다.

책 참고 : 밑바닥부터 시작하는 딥러닝 (한빛미디어)