딥러닝

밑바닥부터 시작하는 딥러닝 - 신경망(4)

Leesemo 2021. 10. 15. 21:29

저번 글까지 신경망의 대해 알아보았다.

이번 글에서는 준비된 데이터셋으로 신경망을 학습시켜 볼 것이다.

 


스파이럴 데이터셋

Spiral(나선형) 데이터를 불러오는 클래스를 구현한다.

import sys
sys.path.append('..')
from dataset import spiral
import matplotlib.pyplot as plt

x, t = spiral.load_data()
print('x', x.shape)
print('t', t.shape)

spiral.py 파일을 임포트 하여 이용한다.

그 후 spiral.load_data()로 데이터를 읽어 온다.

이때, x가 입력 데이터이고, t가 정답 레이블이다. x와 t의 형상을 출력해보면 각각 300개의 샘플 데이터를 담고 있으며, x는 2차원 데이터이고 t는 3차원 데이터임을 알 수 있다.

참고로 t는 원핫 벡터로, 정답에 해당하는 클래스에는 1이, 그 외에는 0이 레이블되어 있다.

spiral dataset

입력은 2차원 데이터이고, 분류할 클래스 수는 3개가 있다. 이 그래프를 보면 직선만으로는 클래스들을 분리할 수 없음을 알 수 있다. 따라서 비선형 분리를 학습해야 한다.

 


신경망 구현

import sys
sys.path.append('..')
import numpy as np
from common.layers import Affine, Sigmoid, SoftmaxWithLoss

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(O)
        
        # 계층 생성
        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

__init__ 메서드를 보면 초기화 메서드는 3개의 인수를 받는데, 차례대로 input_size는 입력층의 뉴런 수, hidden_size는 은닉층의 뉴런 수, output_size는 출력층의 뉴런 수이다. 메서드 안에서는 우선 편향을 영벡터로 초기화하고, 가중치는 작은 무작위 값으로 초기화한다.

 

이어서 TwoLayerNet에 3개의 메서드를 구현해 넣는다. 추론을 수행하는 predict() 메서드, 순전파를 담당하는 forward() 메서드, 역전파를 담당하는 backward() 메서드이다.

 

이번 구현은 이전보다 훨씬 깔끔해졌다.

 


학습용 코드

이어서 학습을 수행하는 코드를 구현할 것이다. 학습 데이터를 읽어 들여 신경망과 옵티마이저를 생성한다.

앞에서 본 학습의 네 단계의 절차대로 학습을 수행한다.

import sys
sys.path.append('..')
import numpy as np
from common.optimizer import SGD
from dataset import spiral
import matplotlib.pyplot as plt

# 하이퍼파라미터 설정
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)

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

for epoch in range(max_epoch):
    # 데이터 뒤섞기
    idx = np.random.permutation(data_size)
    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]
        
        # 기울기를 구해 매개변수 갱신
        loss = model.forward(batch_x, batch_t)
        model.backward()
        optimizer.update(model.params, model.grads)
        
        total_loss += loss
        loss_count += 1
        
        # 정기적으로 학습 경과 출력
        if (iters+1) % 10 == 0:
            avg_loss = total_loss / loss_count
            print('| 에폭 %d |  반복 %d / %d | 손실 %.2f'
                 % (epoch + 1, iters + 1, max_iters, avg_loss))
            loss_list.append(avg_loss)
            total_loss, loss_count = 0, 0
            
# 학습 결과 플롯
plt.plot(np.arange(len(loss_list)), loss_list, label='train')
plt.xlabel('반복 (x10)')
plt.ylabel('손실')
plt.show()

# 경계 영역 플롯
h = 0.001
x_min, x_max = x[:, 0].min() - .1, x[:, 0].max() + .1
y_min, y_max = x[:, 1].min() - .1, x[:, 1].max() + .1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
X = np.c_[xx.ravel(), yy.ravel()]
score = model.predict(X)
predict_cls = np.argmax(score, axis=1)
Z = predict_cls.reshape(xx.shape)
plt.contourf(xx, yy, Z)
plt.axis('off')

# 데이터점 플롯
x, t = spiral.load_data()
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()
  • 1. 하이퍼파라미터를 설정한다. 에포크 수, 미니 배치 크기, 은닉층의 뉴런 수, 학습률을 설정한다.
  • 2. 데이터를 읽어 들이고, 신경망과 옵티마이저를 생성한다. 학습은 미니 배치 방식으로 진행되며 데이터를 무작위로 선택한다.
  • 3. 에포크 단위로 데이터를 뒤섞고, 뒤섞은 데이터 중 앞에서부터 순서대로 뽑아내는 방식을 사용했다.
  • 4. 기울기를 구해 매개변수를 갱신한다.
  • 5. 학습 결과를 출력한다. 위의 코드에서는 10번째 반복마다 손실의 평균을 구해 loss_list 변수에 추가했다.

- 손실 그래프로 가로축은 학습의 반복 수, 세로축은 학습 10번 반복당 손실의 평균

- 위의 그림에서 보듯이 학습을 진행함에 따라 손실이 줄어들고 있다. 그 뜻은 신경망이 올바른 방향으로 학습되고 있다는 것을 의미한다.

 

다음으로 학습 후 신경망이 영역을 어떻게 분리했는지 시각화해보자.

학습된 신경망은 '나선형' 패턴을 올바르게 파악했음을 알 수 있다.

비선형 분리 영역을 학습한 것이다.

신경망에 은닉층을 추가하면 더 복잡한 표현이 가능해진다.