강명호 강사님
import numpy as np
from dataset.mnist import load_mnist
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
load_mnist
: MNIST 데이터셋을 로드하는 함수. 학습용과 테스트용 데이터셋을 반환한다.normalize=True
: 데이터를 0~1 범위로 정규화.one_hot_label=True
: 레이블을 원핫 인코딩으로 변환.train_loss_list = []
train_acc_list = []
test_acc_list = []
train_loss_list
: 학습 과정에서의 손실 값을 저장하는 리스트.train_acc_list
: 각 에포크마다 기록되는 훈련 데이터 정확도를 저장하는 리스트.test_acc_list
: 각 에포크마다 기록되는 테스트 데이터 정확도를 저장하는 리스트.# 하이퍼파라미터
iters_num = 5 # 반복 횟수를 적절히 설정
train_size = x_train.shape[0]
batch_size = 100 # 미니배치 크기
learning_rate = 0.1
iters_num
: 경사하강법의 반복 횟수.train_size
: 학습 데이터의 총 크기.batch_size
: 한 번의 학습에 사용할 미니배치 데이터의 크기.learning_rate
: 매개변수를 업데이트할 때 사용하는 학습률.network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
TwoLayerNet
: 2층 신경망 클래스를 초기화.iter_per_epoch = max(train_size / batch_size, 1)
iter_per_epoch
: 한 에포크는 전체 학습 데이터를 한 번 모두 사용하는 것을 의미한다. 한 에포크가 끝날 때마다 훈련 정확도와 테스트 정확도를 계산할 수 있다.train_size / batch_size
: 전체 데이터를 미니배치로 나눈 횟수.for i in range(iters_num):
# 미니배치 획득
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)
# 매개변수 갱신
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
# 학습 경과 기록
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
# 1에폭당 정확도 계산
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))
이 부분은 도저히 강사님이 보여주신 사과 그림들을 말로 설명할 자신이 없어서 팻스. 기억이 안나면 강의자료 찾아보기.
여러 개의 함수가 합성된 경우 그 함수들의 미분을 계산할 때 사용하는 방법. 각 함수의 미분을 순차적으로 곱하는 방식이다. 계산 그래프의 역전파는 이 연쇄법칙을 사용해 각 노드의 미분을 차례로 계산한다.
class MulLayer:
def __init__(self):
self.x = None
self.y = None
클래스가 생성될 때 x
와 y
변수를 초기화한다. 이 변수들은 곱셈에 사용할 두 입력 값을 저장하는 데 사용된다. None
으로 초기화하는 이유는 아직 값을 받지 않았기 때문.
def forward(self, x, y):
self.x = x
self.y = y
out = x * y
return out
순전파 과정.
x
, y
두 입력값을 받아서 이들을 곱한 out
을 계산한다.x
, y
를 각각 클래스 내부 변수 self.x
, self.y
에 저장한다. def backward(self, dout):
dx = dout * self.y # x와 y를 바꾼다.
dy = dout * self.x
return dx, dy
역전파 과정.
dout
: 출력 값에 대한 미분값. 이 값은 이전 계층으로부터 전달된다.dx
는 dout * y
, dy
는 dout * x
가 된다.dx
, dy
: x
, y
에 대한 미분값.class AddLayer:
def __init__(self):
pass
이 클래스는 덧셈 연산만 다루므로 따로 변수를 저장할 필요가 없다. 그래서 __init__
메서드 안에 아무것도 없고 pass로 넘어간다. 특별히 초기화할 것이 없을 때 이렇게 사용한다.
def forward(self, x, y):
out = x + y
return out
순전파에서 덧셈 연산 처리.
x
,y
를 받아서 더한 결과를 out
에 저장하고 반환.x + y
로 간단히 계산할 수 있다. def backward(self, dout):
dx = dout * 1
dy = dout * 1
return dx, dy
역전파.
dout
: 출력에 대한 미분값.dx
, dy
: dout
에 1을 곱한 값dx = dout
, dy = dout
이 된다.class Relu :
def __init__(self) :
self.mask = None
def forward(self, x) :
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
self.mask = (x <= 0)
: 입력 값 x
가 0 이하인 위치를 True
, 그 외에는 False
로 저장한다.out
에서 mask
가 True
인 위치에 해당하는 값을 0으로 만든다. 즉 0 이하인 값을 모두 0으로 만들어준다.out
을 반환. def backward(self, dout) :
dout[self.mask] = 0
dx = dout
return dx
mask
를 사용해서 0 이하였던 입력값들에 해당하는 미분값을 0으로 만든다.self.mask
가 True
였던 위치(입력값이 0 이하였던 위치)의 dout
을 0으로 바꾼다. (ReLU 함수의 특징 때문에 0 이하였던 입력값들이 기울기가 0이 되기 때문)dout
을 dx
로 반환.주로 신경망의 완전연결층에서 사용된다. Affine 계층은 입력 데이터를 가중치와 곱하고 편향을 더해서 출력하는 역할을 한다.
class Affine:
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None
x
: 입력 데이터를 저장하기 위한 변수.dW
, db
: 가중치와 편향에 대한 미분값(기울기). def forward(self, x):
self.x = x
out = np.dot(x, self.W) + self.b
return out
x
를 가중치 W
와 곱하고 편향 b
를 더해 out
을 계산한다.np.dot(x, self.W)
: 입력 x
와 가중치 W
를 행렬 곱셈으로 연산한다.+ self.b
: 계산된 결과에 편향 더해주기.x
는 self.x
에 저장해둔다. def backward(self, dout):
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
return dx
dout
을 받아 입력 x
, 가중치W
, 편향 b
에 대한 미분값을 각각 계산한다.dx = np.dot(dout, self.W.T)
: dx
는 입력 x
에 대한 미분. dout
에 W
의 전치행렬(W.T
)을 곱해 dx
를 계산한다.self.dW = np.dot(self.x.T, dout)
: dW
는 가중치 W
에 대한 미분. self.x
의 전치행렬(self.x.T
)과 dout
을 곱해 계산한다.self.db = np.sum(dout, axis=0)
: db
는 편향 b
에 대한 미분으로 dout
의 각 원소를 행 방향으로 합산해서 구한다. 이는 편향이 모든 출력에 고르게 영향을 미치기 때문에 가능하다.dx
를 반환한다.class SoftmaxWithLoss :
def __init__(self) :
self.loss = None # 손실함수
self.y = None # softmax의 출력
self.t = None # 정답브레이블(원-핫 인코딩 형태)
loss
: 손실 함수 값(예측과 실제 정답 간의 오차)을 저장.y
: 순전파에서 소프트맥스의 출력을 저장하는 변수.t
: 정답 레이블을 저장하는 변수. def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
x
를 받아서 손실 값을 계산한다.self.y = softmax(x)
: 입력 x
를 소프트맥스 함수에 통과시켜 각 클래스에 대한 확률로 변환한다. (소프트맥스: 신경망의 출력 값을 0과 1 사이의 확률 값으로 정규화)self.loss = cross_entropy_error(self.y, self.t)
: 소프트맥스 결과 y
와 정답 레이블 t
를 사용해 교차 엔트로피 오차를 계산한다. 이 오차 값은 예측한 확률 분포가 정답 분포와 얼마나 다른지를 나타낸다.self.loss
를 반환한다. def backward(self, dout=1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
return dx
batch_size = self.t.shape[0]
: 입력 데이터의 배치 크기를 구한다.dx = (self.y - self.t) / batch_size
: 소프트맥스의 출력 y
와 정답 레이블 t
의 차이를 구한 후 배치 크기로 나누어 손실에 대한 입력 x
에 대한 미분 dx
를 구한다.dx
를 반환하여 손실에 대한 입력 x
의 미분값을 전달한다.from collections import OrderedDict
class TwoLayerNet:
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)
# 계층 생성
self.layers = OrderedDict()
self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
self.layers['Relu1'] = Relu()
self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
self.lastLayer = SoftmaxWithLoss()
self.params
: 신경망의 가중치와 편향을 저장.W1
, b1
, W2
, b2
: 각 계층의 가중치와 편향.OrderedDict
에 저장해서 순서대로 순전파 및 역전파가 가능하도록 한다.Affine1
, Affine2
: Affine 계층Relu
: ReLU 활성화 함수 계층lastLayer
: SoftmaxWithLoss 계층으로, 손실을 계산하는 마지막 계층이다. def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
self.layers
에 저장된 계층을 순서대로 통과하여 x
는 다음 계층으로 전달된다. # x: 입력 데이터, t: 정답 레이블
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
predict
메서드를 통해 예측값 y
를 얻는다.y
와 정답 레이블 t
를 lastLayer
인 SoftmaxWithLoss
계층의 forward
메서드에 전달해 손실을 계산한다. def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
if t.ndim != 1 : t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
np.argmax(y, axis=1)
: 예측값 중에서 가장 높은 확률을 가진 클래스의 인덱스를 가져온다. # x: 입력 데이터, t: 정답 레이블
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_W
를 정의한 후 numerical_gradient
함수를 이용해 W1
, b1
, W2
, b2
에 대한 기울기를 계산하여 저장한다. def gradient(self, x, t):
# 순전파
self.loss(x, t)
# 역전파
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 결과 저장
grads = {}
grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
return grads
gradient
: 역전파를 통해 각 가중치와 편향에 대한 기울기를 효율적으로 계산한다.self.loss(x, t)
를 호출하여 순전파를 수행해 손실을 계산한다.SoftmaxWithLoss
계층부터 역전파를 시작하여 dout
값을 거꾸로 전달하면서 각 계층의 backward
메서드를 호출해 기울기를 계산한다.Affine
계층의 dW
, db
값을 grads
딕셔너리에 저장하여 반환한다.from dataset.mnist import load_mnist
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
load_mnist()
함수로 MNIST 데이터를 불러오며 normalize=True
옵션으로 입력 데이터를 0~1 사이 값으로 정규화하고 one_hot_label=True
옵션으로 정답 레이블을 원-핫 인코딩 형태로 변환한다.
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
TwoLayerNet
클래스 객체 network
를 생성한다. MNIST 데이터는 각 이미지가 28*28 크기이므로 입력 크기는 784, 은닉층 노드수는 50, 출력층 노드 수는 10으로 설정.
x_batch = x_train[:3]
t_batch = t_train[:3]
학습 데이터 중 처음 3개의 데이터(x_batch
)와 정답 레이블(t_batch
)을 미니 배치로 선택해 기울기 계산에 사용한다.
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
grad_numerical
: numerical_gradient
메서드를 통해 수치 미분으로 계산한 기울기 값.grad_backprop
: gradient
메서드를 통해 역전파로 계산한 기울기 값.# 각 가중치의 차이의 절대값을 구한 후 그 절대값들의 평균을 산출함
for key in grad_numerical.keys():
diff = np.average(np.abs(grad_backprop[key] - grad_numerical[key]))
print(key + ': ' + str(diff))
각 가중치에 대해 두 기울기의 차이(절대값)를 평균해서 출력한다. 차이가 작을수록 역전파가 제대로 구현되었다는 의미.
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
load_mnist()
: 학습 데이터와 테스트 데이터를 불러온다.normalize=True
: 입력 데이터를 0~1 사이로 정규화.one_hot_label=True
: 레이블을 원-핫 인코딩 형식으로 변환.iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
iters_num
: 전체 반복 횟수. 총 10,000번 반복.batch_size
: 미니배치 크기. 한 번에 100개의 데이터를 신경망에 입력.learning_rate
: 학습률. 기울기를 얼마나 크게 적용할지 조절.train_loss_list = []
train_acc_list = []
test_acc_list = []
train_loss_list
: 각 반복마다 손실 함수 값을 기록.train_acc_list
, test_acc_list
: 각 epoch 마다 학습 정확도와 테스트 정확도를 기록.iter_per_epoch = max(train_size / batch_size, 1)
한 epoch에 몇 번의 반복이 필요한지를 계산해 매 epoch 마다 정확도를 측정한다.
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
grad = network.gradient(x_batch, t_batch) # 오차역전파법
iters_num
만큼 반복된다.batch_mask
를 사용해 학습 데이터에서 무작위로 batch_size
만큼의 인덱스를 선택해 미니배치를 만든다.network.gradient()
를 호출해 미니배치에 대한 기울기를 계산한다. # 갱신
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
grad[key]
를 학습률만큼 곱해 기존 값에서 뺀다. 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)
iter_per_epoch
횟수마다 학습 데이터와 테스트 데이터에 대한 정확도를 계산한다.train_acc_list
와 test_acc_list
에 저장하고, 학습 정확도와 테스트 정확도를 출력하여 모델 성능 변화를 확인한다.