2021. 10. 1. 04:35ㆍDeep Learning
본 게시글은 한빛미디어 『밑바닥부터 시작하는 딥러닝, 사이토 고키, 2020』의 내용을 참조하였음을 밝힙니다.
이번 장에서는 지금까지 살펴본 신경망의 출력, 손실 함수, 그레디언트, 가중치 업데이트,
미니 배치 학습을 모두 이용하여 2층 신경망을 구현하고, 실제로 신경망을 학습시켜보도록 하겠다.
2층 신경망 구현
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
### 가중치들을 초기화
def predict(self, x):
### 입력값 x와 가중치들을 바탕으로 출력값을 반환
def loss(self, x, t):
### 입력값 x를 통해 예측값을 계산하고
### 타깃값 t와의 오차를 계산함
def accuracy(self, x, t):
### 입력값 x로 예측값을 계산하고
### 타깃값 t와 일치하는 케이스들의 비율을 계산해
### 정확도를 산출
def numerical_gradient(self, x, t):
### 손실함수를 정하고
### 각 가중치에 대해 손실함수를 편미분하여
### 그레디언트(gradient)를 계산 후 반환
구현하고자 하는 2층 신경망은 위와 같다. 하나하나 구현해보도록 하자.
def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
먼저 인스턴스를 초기화하는 __init__부터 살펴보자.
TwoLayerNet 클래스에서는 각 층의 모든 가중치(및 편향)을 params 변수에 딕셔너리 형태로 저장한다.
신경망에 입력으로 들어오는 입력층 크기(input_size)를 행의 크기로 갖고 은닉층 크기(hidden_size)를 열의 크기로 갖는
가중치 행렬 W1, (은닉층 크기(hidden_size), 출력층 크기(output_size)) 형태의 가중치 행렬 W2을 만들어준다.
가중치 W는 최초에는 넘파이의 random 모듈을 통해 난수로 초기화된다.
편향 행렬(b1, b2)도 전달하고자 하는 층의 크기에 맞게 영행렬로 만들어진다.
def predict(self, x):
a1 = np.dot(x, self.params['W1']) + self.params['b1']
z1 = sigmoid(a1)
a2 = np.dot(z1, self.params['W2']) + self.params['b2']
y = softmax(a2)
return y
predict 함수는 가중치와 입력값을 계산하고 이를 시그모이드 함수에 통과시킨 것을 은닉층에 전달하고,
다음 가중치를 이용하여 계산된 결과를 소프트맥스 함수에 통과시킨 후 반환한다.
def loss(self, x, t):
y = self.predict(x)
loss = cross_entropy_error(y, t)
return loss
loss 함수는 입력값 x(와 가중치)를 통해 예측을 수행하고 타깃값과의 오차를 계산하고 이를 반환한다.
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
accuracy 함수는 예측을 수행하고 타깃값과의 대조를 통해 옳게 예측한 비율을 측정하여 이를 반환한다.
def numerical_gradient(self, x, t):
loss_w = lambda w : self.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_w, self.params['W1'])
grads['b1'] = numerical_gradient(loss_w, self.params['b1'])
grads['W2'] = numerical_gradient(loss_w, self.params['W2'])
grads['b2'] = numerical_gradient(loss_w, self.params['b2'])
return grads
numerical_gradient 함수는 클래스 내의 함수인 loss함수(교차 엔트로피 오차를 반환)를 가져와서
이 손실 함수에서 각 가중치(W1, b1, W2, b2)에 대해 편미분을 수행하고, 그레디언트를 반환한다.
신경망을 학습시키기 위한 모든 준비가 완료되었다.
이제 미니배치 학습을 수행하도록 하자.
미니배치 학습
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=True)
먼저 mnist 데이터셋을 가져와서 훈련 세트와 테스트 세트로 나눈다. 우리는 이 중에서 훈련 데이터를 사용한다.
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
훈련 데이터 하나당 입력 이미지 크기는 784(28 * 28)이고, 0~9까지를 예측해야하므로 출력 크기는 10으로 설정한다.
은닉층 크기는 하이퍼 파라미터로, 50으로 설정하였다.
iter_num = 300
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
학습의 총 반복 횟수는 200번, 미니 배치 크기는 20, 학습률은 0.1로 설정하였다.
train_loss_list = []
train_acc_list = []
test_acc_list = []
각 반복마다 훈련 데이터에서의 손실값 리스트와 오버피팅 유무를 확인하기 위해 (에포크마다) 훈련 세트에서의 정확도,
테스트 세트에서의 정확도를 비교하기 위한 리스트들을 선언한다.
iter_per_epoch = max(train_size/batch_size, 1)
반복의 특정 시점마다 그래프를 그리기 위해 epoch(배치 데이터를 모두 소진한 단위)의 총 크기를 계산한다.
for i in range(iter_num):
print(f'epoch {i} processing...')
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
grad = network.numerical_gradient(x_batch, t_batch)
넘파이의 random모듈의 choice 함수를 통해 batch_mask를 생성하고 이를 훈련 데이터에 적용한다.
이렇게 각 반복마다 랜덤하게 배치 데이터를 추출한 뒤, 경사 하강법으로 학습시키는 방식을
확률적 경사하강법(SGD)라고 한다. 배치 데이터에서 gradient를 계산한다.
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
계산된 grad에 학습률을 곱한 뒤 이를 기존 가중치들에서 차감해준다.
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
train_loss_list에 배치 데이터에서의 손실값을 추가한다.
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("train acc, test acc |" + str(train_acc)+", "+str(test_acc))
한 에포크에 도달할 때마다 훈련 세트와 테스트 세트에서의 정확도를 계산하고 이를 각각의 리스트에 추가한다.
plot = plt.plot(train_loss_list)
plt.xlabel('epoch')
plt.ylabel('train loss')
plt.title('Changes in training losses according to epochs.')
plt.show()
이제 for문을 빠져나와 train_loss_list를 plot 그래프로 출력한다.
가중치 갱신이 매회 진행됨에 따라서 훈련 데이터의 손실도 조금씩 낮아지는 것을 확인할 수 있다.
plt.plot(train_acc_list, 'g--', label = 'train_accuracy')
plt.plot(test_acc_list, 'b-', label = 'test_accuracy')
plt.legend(loc='best')
plt.grid(linestyle='dotted')
plt.xlabel('every 10 iter')
plt.ylabel('accuracy')
plt.show()
반복문 안에서 10회 단위로 훈련 세트와 테스트 세트에서의 정확도를 그래프로 나타낸 값이다.
마지막에 15% 정도의 정확도를 갖는 것으로 보아 완전히 랜덤한 예측 모델의 10% 정확도(0~9까지의 값이므로)보다는
좋은 성능을 지닌다.
학습을 200회 반복했음에도 성능이 그리 좋지 못하는데 이는 배치 사이즈를 20, 반복횟수도 200으로 작게 설정했기
때문이다. 상용 프레임워크가 아닌 하나하나 직접 구현했다보니 계산 속도가 많이 따라오지 못해 내린 선택이다.
실제로 반복 횟수(iter_num)도 10000 이상으로 크게 늘리고, 배치 사이즈도 100으로 실행하면
훨씬 더 좋은 성능을 낼 수 있을 것이다.
'Deep Learning' 카테고리의 다른 글
[밑바닥딥러닝] 10. 오차역전파법(backpropagation) 구현(1) (0) | 2021.10.07 |
---|---|
[밑바닥딥러닝] 9. 오차역전파법(backpropagation) - 계산그래프 (0) | 2021.10.03 |
[밑바닥딥러닝] 7. 경사하강법(gradient descent) (0) | 2021.10.01 |
[밑바닥딥러닝] 6. 수치 미분, 편미분 (0) | 2021.10.01 |
[밑바닥딥러닝] 5. 신경망 학습 - 손실 함수, 미니 배치 학습 (0) | 2021.09.30 |