
torch.nn 모듈에 익숙해져 있는 나에게 nn 없이 numpy만으로 2-layer-MLP를 구현해보는 task가 주어지게 되었다. forward는 구현이 매우 쉽지만, backward 구현이 매우 어렵게 느껴졌다..
Linear(1) -> Sigmoid(activation) -> Linear(2) -> Softmax(Final layer) -> Cross Entropy(Loss)
첫 스텝은 weight를 초기화이다. 초기화 방법으로는 Xaiver Initialization을 사용하였으며, 이에 대한 설명은 해당 포스트에서는 생략하도록 하겠다! (코드 구현이 목적이므로)
https://paperswithcode.com/method/xavier-initialization
def initialize_parameters(self, input_dim, num_hiddens, num_classes):
"""
[Inputs]
- input_dim
- num_hiddens: hidden units의 수
- num_classes: 클래스의 수
[Returns]
- params: 초기화된 파라미터들의 딕셔너리
"""
# weight init -> xavier
W1_num = np.sqrt(6 / (input_dim + num_hiddens))
W1 = np.random.uniform(low=-W1_num, high=W1_num, size=(num_hiddens, input_dim))
W2_num = np.sqrt(6 / (num_hiddens + num_classes))
W2 = np.random.uniform(low=-W2_num, high=W2_num, size=(num_classes, num_hiddens))
# bias init
b1 = np.zeros(num_hiddens)
b2 = np.zeros(num_classes)
return {
'W1': W1,
'b1': b1,
'W2': W2,
'b2': b2
}
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def softmax(X):
logit = np.exp(X - np.amax(X, axis=1, keepdims=True)) # for numerical stability
return logit / np.sum(logit, axis=1, keepdims=True)
def forward(self, X):
"""
[Inputs]
- X: input 행렬 (N, D)
[Returns]
- y: 모델 output
- ff_dict: 각 layer에서의 output
- type: dict
"""
fc1 = X @ self.params['W1'].T + self.params['b1'] # (N, H)
sig = sigmoid(fc1)
fc2 = sig @ self.params['W2'].T + self.params['b2'] # (N, C)
y = softmax(fc2)
ff_dict = {
'fc1': fc1,
'sig': sig,
'fc2': fc2,
'y': y
}
return y, ff_dict
def loss(self, Y, Y_pred):
"""
[Inputs]
Y: 실제 라벨
Y_pred: 예측된 라벨
[Returns]
loss
"""
loss = -(1 / Y.shape[0]) * np.sum(Y * np.log(Y_pred))
return loss


def backward(self, X, Y, ff_dict):
"""
[Inputs]
- X: input 행렬 (B, D)
- Y: label (B, C)
- ff_dict: outputs in forward step
- type: dict
[Returns]
- grads: W2, b2, W1, b1
- type: dict
"""
N = Y.shape[0]
dy = (1/N) * (ff_dict['y'] - Y) # (N, C)
grad_W2 = dy.T @ ff_dict['sig'] # (C, N) X (N, H) -> (C, H)
grad_b2 = np.sum(dy, axis=0) # (C,)
grad_a1 = dy @ self.params['W2'] # (N, H)
grad_z1 = grad_a1 * ff_dict['sig'] * (1 - ff_dict['sig']) # (N, H)
grad_W1 = grad_z1.T @ X # (H, N) X (N, D) -> (H, input_dim)
grad_b1 = np.sum(grad_z1, axis=0) # (H,)
return {
'W2': grad_W2,
'b2': grad_b2,
'W1': grad_W1,
'b1': grad_b1
}

torch module을 잘 쓰자..! MLP 구현을 torch없이 직접 해보니, torch의 Autograd가 얼마나 강력한 프레임워크인지, 인공지능에서 수학(확률론, 선형대수 등..)이 얼마나 큰 비중을 차지하는지 다시 느낄 수 있었습니다. 직접 학습 과정을 구현해보면서 머신러닝에 대한 전체적인 이해도도 높아졌던 시간이었습니다!