※ 본 내용은 stanford에서 제공하는 cs231n 강의, 강의자료를 바탕으로 작성하였습니다.
Q3에서는 Softmax loss의 구현과 SGD에 대해 다루고 있다.
Q2와 비슷한 구조로, naive하게 loss와 미분을 계산하는 것에서 fully vectorized한 코드까지 작성하는 것을 다룬다.
(SGD를 이용한 train의 경우 Q2와 동일하므로 생략)
<Softmax>
- Setup
마찬가지로 CIFAR10 dataset을 load하여 사용하고, shape는 위와 같다.
- Naive하게 loss와 미분(dW) 계산하기
def softmax_loss_naive(W, X, y, reg):
"""
Softmax loss function, naive implementation (with loops)
Inputs have dimension D, there are C classes, and we operate on minibatches
of N examples.
Inputs:
- W: A numpy array of shape (D, C) containing weights.
- X: A numpy array of shape (N, D) containing a minibatch of data.
- y: A numpy array of shape (N,) containing training labels; y[i] = c means
that X[i] has label c, where 0 <= c < C.
- reg: (float) regularization strength
Returns a tuple of:
- loss as single float
- gradient with respect to weights W; an array of same shape as W
"""
# Initialize the loss and gradient to zero.
loss = 0.0
dW = np.zeros_like(W)
#############################################################################
# TODO: Compute the softmax loss and its gradient using explicit loops. #
# Store the loss in loss and the gradient in dW. If you are not careful #
# here, it is easy to run into numeric instability. Don't forget the #
# regularization! #
#############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
# -- loss, dW --
num_train = X.shape[0]
for i in range(num_train) :
score = np.dot(X[i], W)
score = np.exp(score)
score = score / np.sum(score)
X_temp = np.repeat(X[i].reshape(-1, 1), 10, axis = 1)
dW += X_temp*score / num_train # 3072x10
dW[:, y[i]] += -X[i] / num_train
loss += -np.log(score[y[i]])
dW += 2*reg*W
loss /= num_train
loss += reg*np.sum(W*W)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss, dW
loss와 미분을 naive하게 구현하는 코드는 위와 같다.
● Softmax Loss
for문을 사용하여 loss는 직관적으로 구현되어 있다. 계산 방법을 순서대로 코드화한 것 뿐이라 크게 어렵지 않다.
● 미분(dW)
처음엔 Softmax에서 Weight(W)에 대한 미분을 어떻게 계산해야 할지 감이 안왔지만,
SVM에서 했던 것과 유사하게 loss에 더해지는 값을 풀어쓴 후 미분할 수 있었다.
각 데이터마다 loss에 더해지는 값은 다음과 같다.
(s_x는 x-th class에 대한 prediction score, y_i는 정답 label을 의미한다.)
이를 좀 더 풀어보면,
위와 같이 정리할 수 있다.
따라서 각 데이터마다 $ dW^{(i)}$에 대한 미분은 위식을 $ W^{i}$에 대해 미분한 결과와 같다.
score가 데이터와 Weight의 내적인 점을 고려하여 미분을 진행하면 최종적으로 다음과 같은 식이나온다.
$ dW^{(i)} += x^{(n)}*probability(i) $ (i가 정답 label이 아닌 경우)
추가적으로 data의 수로 나눠주고, regularization term에 대한 미분을 더해주면 미분 계산이 완료된다.
코드는 단순히 위 내용을 구현한 내용이라 자세히 설명하진 않겠다.
- Inline Question 1
위 결과가 -log(0.1)과 비슷한지 검사하는 내용이 있었는데, 왜 해당 값과 비교하는지 설명하라는 질문이 있었다.
- Fully vectorize하여 loss와 미분 계산하기
def softmax_loss_vectorized(W, X, y, reg):
"""
Softmax loss function, vectorized version.
Inputs and outputs are the same as softmax_loss_naive.
"""
# Initialize the loss and gradient to zero.
loss = 0.0
dW = np.zeros_like(W)
#############################################################################
# TODO: Compute the softmax loss and its gradient using no explicit loops. #
# Store the loss in loss and the gradient in dW. If you are not careful #
# here, it is easy to run into numeric instability. Don't forget the #
# regularization! #
#############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
#loss
score = np.dot(X, W) # 500 x 10
score = np.exp(score)
score = score / np.sum(score, axis = 1).reshape(-1, 1)
loss = np.sum(-np.log(score[np.arange(X.shape[0]), y])) / X.shape[0] + reg*np.sum(W*W)
#dW
score[np.arange(X.shape[0]), y] -= 1 #not valid from here
dW += np.dot(np.transpose(X), score) / X.shape[0] # 3072x10
dW += 2*reg*W
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss, dW
for문을 사용하지 않고, vectorization을 통해 구현한 코드는 위와 같다.
● Loss
loss의 경우 사실 처음부터 vectorized version으로 작성하는 것이 편했다.
score의 shape가 어떻게 결정되는지만 이해했다면 쉽게 이해할 수 있을 것이다.
앞서 각 데이터별로 for문을 거치며 계산한 과정을 행렬에 저장된 결과를 보고 계산한 것 뿐이다.
● 미분
앞서
위와 같은 수식을 확인할 수 있었다.
i번째 W에 대한 미분을 구하려면 n번째 데이터와 해당 데이터의 i-th class에 대한 score의 곱을 모든 데이터에 대해 더해주면 된다. i가 정답인 경우는 probability에 -1을 더해줘서 곱하면 된다.
앞서 Q2에서 모든 데이터에 각각 어떤 값을 곱하고, 그 합을 구하는 것을 for문을 사용하지 않고 구현하는 코드가 있었고, 해당 코드와 동일한 원리로 행렬곱을 이용해 계산할 수 있었다.
X의 전치행렬과 확률이 계산된 행렬을 곱해주면 되었다. (정답 label인 경우 확률에 -1을 해줘야한다.)
위 그림은 $ dW^{(0)}$를 계산하는 예시로, 그림을 통해 확인하면 보다 쉽게 이해할 수 있을 것이다.
마지막으로 regularization term에 대한 미분을 더해주면 fully vectorized된 미분 계산 코드를 완성할 수 있다.
※ 본 내용은 stanford에서 제공하는 cs231n 강의, 강의자료를 바탕으로 작성하였습니다.
Q2에서는 SVM loss의 계산과 미분 그리고 SGD를 통한 optimization에 대해 다루고 있다.
<SVM>
- setup
X_train, X_val, X_test, X_dev 의 shape는 위와 같다.
- Naive하게 svm loss와 dW 계산하기
def svm_loss_naive(W, X, y, reg):
"""
Structured SVM loss function, naive implementation (with loops).
Inputs have dimension D, there are C classes, and we operate on minibatches
of N examples.
Inputs:
- W: A numpy array of shape (D, C) containing weights.
- X: A numpy array of shape (N, D) containing a minibatch of data.
- y: A numpy array of shape (N,) containing training labels; y[i] = c means
that X[i] has label c, where 0 <= c < C.
- reg: (float) regularization strength
Returns a tuple of:
- loss as single float
- gradient with respect to weights W; an array of same shape as W
"""
dW = np.zeros(W.shape) # initialize the gradient as zero
# compute the loss and the gradient
num_classes = W.shape[1]
num_train = X.shape[0]
loss = 0.0
for i in range(num_train):
scores = X[i].dot(W)
correct_class_score = scores[y[i]]
for j in range(num_classes):
if j == y[i]:
continue
margin = scores[j] - correct_class_score + 1 # note delta = 1
if margin > 0:
loss += margin
dW[:, y[i]] += -X[i] / num_train
dW[:, j] += X[i] / num_train
# Right now the loss is a sum over all training examples, but we want it
# to be an average instead so we divide by num_train.
loss /= num_train
# Add regularization to the loss.
loss += reg * np.sum(W * W)
#############################################################################
# TODO: #
# Compute the gradient of the loss function and store it dW. #
# Rather that first computing the loss and then computing the derivative, #
# it may be simpler to compute the derivative at the same time that the #
# loss is being computed. As a result you may need to modify some of the #
# code above to compute the gradient. #
#############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
#pass
dW += W*2*reg
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss, dW
2중 for문을 이용해서 loss와 미분(dW)를 계산하는 코드이다.
loss의 경우 2중 for문을 통한 구현은 직관적이고 어렵지 않기 때문에 생략한다.
미분의 경우 loss 의 식을 천천히 살펴보면 된다.
loss에 값이 추가되는 경우는 margin > 0인 경우이고, 그때 더해지는 값, margin은 다음과 같다.
$ loss += (x^{(n)}W^{(j)} - x^{(n)}W^{(y_i)} + 1) $
(j : 잘못 예측한 class, $ y_i$ : 정답 class)
따라서 $ W^{(j)}$에 대한 미분값으로는 $ +x^{(n)}$, $ W^{(y_i)}$에 대한 미분값으로는 $ -x^{(n)}$이 더해질 것이다.
(어차피 선형수식으로 계산이 되기 때문에 각 연산마다 개별적으로 dW += ~ 를 연산해도 된다.)
마지막으로 data의 수로 나눠주고, 마지막에 regularization에 대한 미분을 더해주면 된다.
- fully vectorize하여 loss와 미분(dW) 계산하기
def svm_loss_vectorized(W, X, y, reg):
"""
Structured SVM loss function, vectorized implementation.
Inputs and outputs are the same as svm_loss_naive.
"""
loss = 0.0
dW = np.zeros(W.shape) # initialize the gradient as zero
#############################################################################
# TODO: #
# Implement a vectorized version of the structured SVM loss, storing the #
# result in loss. #
#############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
mul = np.dot(X, W) #500x10
S_correct = mul[np.arange(X.shape[0]), y].reshape(-1, 1) # 500x1, 맞는 label 들의 score
S_gap = (mul - (S_correct - 1)).clip(0)
S_gap[np.arange(X.shape[0]), y ] =0
loss = (np.sum(S_gap) / X.shape[0]) + reg*np.sum(W*W)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
#############################################################################
# TODO: #
# Implement a vectorized version of the gradient for the structured SVM #
# loss, storing the result in dW. #
# #
# Hint: Instead of computing the gradient from scratch, it may be easier #
# to reuse some of the intermediate values that you used to compute the #
# loss. #
#############################################################################
# *****START OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
#pass
Z = np.where(S_gap > 0, 1, 0)
Z[np.arange(X.shape[0]), y] = -np.sum(Z, axis = 1) # 정답보다 score가 높은 lable의 수
dW += np.dot(X.T, Z) / X.shape[0]
dW += (2*reg*W)
# *****END OF YOUR CODE (DO NOT DELETE/MODIFY THIS LINE)*****
return loss, dW
● loss
1. $ XW $의 결과로 score가 계산되고(code상으로는 mul이라고 표시)
2. 맞는 label의 score와 margin을 고려해서 각 행에 빼준다.
3. clip을 통해 0 이하의 값은 0으로 바꾸고, 맞는 label의 값도 0으로 바꿔준다.
위 3과정만 거치면 margin값만 남게되고, np.sum()을 통해 margin을 전부 더해주면 loss가 계산된다.
● 미분(dW)
미분을 fully vectorize하는 것은 꽤 복잡하다.
처음엔 구현을 하지 못했고, 코드를 참고한 후 이해하는 방향으로 학습했다.
덕분에 다음 과제인 softmax classifier에서 비슷한 내용은 스스로 구현할 수 있었다.