이번 글에서는 내가 cs231n A1 Q4를 해결하면서 생겼던 문제들이나 중요하다고 생각했던 부분들을 정리해보려고 한다.
layers.py의 affine_backward 함수에서 dx,dw,db 계산하는 것에서 조금 시간이 걸렸다. local*global gradient를 해줘야 하는 것을 까먹어 계속 error rate가 크게 나왔다. 아래 코드는 내가 헷갈렸던 부분들만 적은 것이다. 실제로 data들은 matrix 형태이기 때문에 dot을 이용해줘야 한다. 이때 dout는 upstream gradient로, 주어졌기 때문에 우리는 local gradient 값만 계산해서 각각 곱해주면 된다. 이 경우에 그냥 two layer net이므로, y=wx+b 라고 생각하고 dx,dw,db를 구하면 된다. 따라서 각각 편미분하면, dx=w, dw=x, db=1 이 나온다.
def affine_backward(dout,cache):
...
...
x_new= x.reshape(x.shape[0],-1)
dx=dout.dot(w.T).reshape(x.shape)
dw=x_new.T.dot(dout)
db=np.sum(dout,axis=0)
같은 layers.py의 relu_forward 함수에서도 짚고 넘어가야 할 부분이 있다. ReLU의 정의상, ReLU의 output은 여야 한다. 이게 intuition이고, 실제로 벡터나 행렬에 대해서 코딩할 때는 max와 관련된 함수를 이용해elementwise 연산을 해야 한다. 그래봐야 max 관련된 함수는 np.max() 와 np.maximum() 두 가지인데, 나는 이 둘의 차이를 정확히 알지 못했다.
따라서 np.max(0,x)와 np.maximum(0,x)의 차이를 정확히 짚고 넘어갈 필요가 있다.
1) np.maximum(a,b)는 두 개의 numpy array a,b를 elementwise로 비교한다. 내가 몰랐던 것은 a,b 둘 중에 하나는 스칼라값이어도 된다는 것이다. 하나가 스칼라 값이라면 나머지 배열의 모든 원소와 비교해서 최댓값을 뱉는다. 따라서 우리가 하려는 연산에는 np.maximum이 더 적합하다.
2) np.max(a)는 하나의 배열 a내의 특정 axis에 대해서 최댓값을 알려주는 함수이다. 처음에 내가 np.max(0,x,axis=1)와 같은 식으로 코드를 썼는데, 애초에 하나의 배열만 비교할 수 있으므로 잘못된 선택이었다.
나는 np.maximum을 사용했지만, mask를 씌워서 계산해도 된다.
def relu_forward(x):
"""
Computes the forward pass for a layer of rectified linear units (ReLUs).
Input:
- x: Inputs, of any shape
Returns a tuple of:
- out: Output, of the same shape as x
- cache: x
"""
out = None
###########################################################################
# TODO: Implement the ReLU forward pass. #
###########################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
out=np.maximum(0,x) # 0 is a scalar.
zero_mask = x > 0 # shape=(3,4) (x와 같은 크기)
out = x * zero_mask
zero_mask=x>0는 (3,4) 크기의 boolean matrix를 만들어준다. (0,1로 구성되어 있다는 소리이다.) 이를 x와 곱해주면 우리가 원하는 행렬을 얻을 수 있는 것이다. (x<0인 원소들의 자리에는 0이 있기 때문에, 우리가 x와 곱할 때 영향을 미치지 못한다.)
fc_net.py 함수에서 문제가 생겼다. 코드의 초반에 layers.py와 layer_utils.py를 import 하는 과정을 거쳤다.
from ..layers import *
from ..layer_utils import *
def loss(self, X, y=None):
scores = None
############################################################################
# TODO: Implement the forward pass for the two-layer net, computing the #
# class scores for X and storing them in the scores variable. #
############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
W1, b1, W2, b2 = self.params.values()
out1,cache1=affine_forward(X,W1,b1)
out2, cache2=relu_forward(out1)
scores,cache3=affine_forward(out2,W2,b2)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
############################################################################
# END OF YOUR CODE #
############################################################################
# If y is None then we are in test mode so just return scores
if y is None:
return scores
loss, grads = 0, {}
############################################################################
# TODO: Implement the backward pass for the two-layer net. Store the loss #
# in the loss variable and gradients in the grads dictionary. Compute data #
# loss using softmax, and make sure that grads[k] holds the gradients for #
# self.params[k]. Don't forget to add L2 regularization! #
# #
# NOTE: To ensure that your implementation matches ours and you pass the #
# automated tests, make sure that your L2 regularization includes a factor #
# of 0.5 to simplify the expression for the gradient. #
############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
loss,dloss=softmax_loss(scores,y)
loss += 0.5 * self.reg * (np.sum(W1**2) + np.sum(W2**2))
dout3, dW2, db2 = affine_backward(dloss, cache3)
dout2 = relu_backward(dout3, cache2)
dout1, dW1, db1 = affine_backward(dout2, cache1)
dW1 += self.reg * W1
dW2 += self.reg * W2
grads = {'W1': dW1, 'b1': db1, 'W2': dW2, 'b2': db2}
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
############################################################################
# END OF YOUR CODE #
############################################################################
return loss, grads
위와 같이 코드를 적었으나, 에러 문구는 layers.py와 layer_utils.py 의 함수들을 import 하지 못한다고 하길래 뭔가 파일이 저장되어 있는 경로의 문제인가 싶었다. 그러나 scores 배열에 none이 들어가 있다는 에러 문구와 번갈아 나오는 것을 보고 scores에 대한 코드를 조금 수정하니 해결되었다. 한마디로 말하자면 삽질한거다..ㅋㅋㅋㅋ
그리고 dictionary에서 값들에 접근하기 위해 사용할 수 있는 것들을 좀 정리해보았다.
위의 링크에 dictionary 파트를 보면 된다.
또 하나 내가 몰랐던 것은 Weight matrix가 여러 개 있다면 regularization에 다 반영해줘야 한다는 것이다.
이 부분은 함수는 아니지만, two_layer_net.ipynb 에서 solver 부분을 해결할 때 생겼던 문제를 다루겠다. hyperparameter를 바꾸면서 모델의 성능을 일정 정확도 이상으로 만드는 문제였는데, 해보니 너무 많은 것들을 바꾸면 시간이 너무 오래 걸려서 lr, batchsize를 여러 개 정의해놓고 for문을 통해 최적의 hyperparameter 조합을 찾으려고 했다. 그러나 훈련 도중 loss 값이 nan이 나오고, 정확도도 0.0087 정도로 낮게 나오는 문제가 있었다. 보통 loss가 nan이 나오면 overfitting을 의심해볼 수 있다. 그리고 나서 training accuracy와 validation accuracy를 비교해보니 training accuracy가 더 높은 경향이 있었다. 그래서 overfitting이라고 생각하고 batchsize 대신 regularization을 바꾸면서 문제를 해결하려고 했다. 그러니까 lr,reg를 바꿔가며 최적의 조합을 찾는 것이다.
learning_rate=[1e-4,1e-3]
regularization_strength=[1e-5,1e-4,1e-3]
results={}
for lr in learning_rate: # code from previous execise
for reg in regularization_strength:
model=TwoLayerNet(input_size, hidden_size, num_classes,reg=reg)
solver=Solver(model,data, optim_config={'learning_rate': lr},num_epochs=10,batch_size=200,print_every=1000)
solver.train()
accuracy = solver.check_accuracy(data['X_val'],data['y_val'])
if accuracy > best_accuracy:
best_model = model
best_accuracy = accuracy
results[(lr,reg)] = accuracy
# Print out results.
for lr, reg in sorted(results):
val_accuracy = results[(lr, reg)]
print('lr %e reg %e val accuracy: %f' % (lr, reg, val_accuracy))
print('best validation accuracy achieved during cross-validation: %f' % best_accuracy)
일단 내가 실행한 방법은 위의 코드와 같다. best_accuracy는 0.5, test_accuracy는 0.481 정도였다.
아직 pytorch는 들어가지도 않았다. 그러나 과제를 하면 할수록 차원을 맞추는 것, 그리고 hyperparameter와 같은 경우는 상황별로 어떻게 바꾸고 뭘 추가해야 되는지에 대한 insight를 갖추는 것이 중요하다는 것을 느낀다. 그래도 처음에 할 때보단 속도가 좀 붙는다.