딥러닝) 가중치 초기화

박경록·2021년 4월 4일
3

최근 앤드류 응 교수님의 머신러닝 강의와 '밑바닥부터 시작하는 딥러닝' 학습 중에 딥러닝 모델 설계시 가중치의 초깃값을 0으로 설정할 경우 학습이 제대로 이루어지지 않는다는 것을 알았다.

이러한 이유가 가중치가 0으로 설정된 이후의 층의 노드들에 모두 같은 값이 전파되어 가중치를 여러개 같는 의미를 사라지게 하기 때문이라는데... 직관적으로 잘 이해가 가지 않았다. 딥러닝 모델 설계시 초깃값을 설정할 수 있는 가중치는 W, b 두개로 W를 공행렬로 설정한다고 해도 b의 값을 다르게해 다음 노드에 다른 값들을 충분히 전파시킬 수 있다고 생각했기 때문이다. 물론 Affine 계층에서 오차역전파를 흘려보낼시 ∂L/∂x = ∂L/∂Y*W^T의 역전파가 흐르기 때문에 공행렬인 W로 인해 이전층에서 부터 흘려들어온 역전파들이 사라질 가능성은 있을 것 이라고는 생각했다.

따라서 초기 가중치의 값을 0으로 설정하면 어떠한 현상이 일어날지 직접 모델을 설계하고 관찰함으로서 알아보기로 했다.

기본 모델

모델에서 가중치가 0일 경우 어떠한 현상이 일어나는지 알아보기 위해 일단은 간단한 모델을 설계하였다. 모델의 특성은 다음과 같다.

  • 가중치의 초기값은 pytorch에서 기본으로 설정되어있는 것을 사용(U(-k\sqrt{k}, -k\sqrt{k})의 분포를 따르며 k = number of input features 이다.

  • 활성화 함수로는 마지막 출력층을 제외하고 모두 Sigmoid 함수를 사용하였다.

  • 데이타는 sklearn의 손글씨 digits 데이타를 사용하였다.

  • 가중치를 0으로 만든 전후층의 가중치 변화를 관찰하기위해서 층의 수는 4로 설정하였다.

  • 정확도는 88.6%

import torch
import torch.nn as nn
import torch.utils.data as Data
import matplotlib.pyplot as plt
import sklearn.datasets as Dataset

torch.manual_seed(3)
digits= Dataset.load_digits()
x = torch.FloatTensor(digits.data)
y = torch.FloatTensor(digits.target).long()


train_data_set = Data.TensorDataset(x[:1500], y[:1500])

data_loader = Data.DataLoader(dataset= train_data_set, batch_size = 100, shuffle = True,
                              drop_last=True)

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        
        self.linear1 = nn.Linear(64, 64)
        self.linear2 = nn.Linear(64, 64)
        self.linear3 = nn.Linear(64, 32)
        self.linear4 = nn.Linear(32, 10)
        
        self.layer1 = nn.Sequential(self.linear1, nn.Sigmoid())
        self.layer2 = nn.Sequential(self.linear2, nn.Sigmoid())
        self.layer3 = nn.Sequential(self.linear3, nn.Sigmoid())
        self.layer4 = nn.Sequential(self.linear4)

model = Model()
epochs = 300
optimizer = torch.optim.SGD(model.parameters(), lr = 0.3)
cost_function = nn.CrossEntropyLoss()

for epoch in range(epochs):
    for train_x, train_y in data_loader:

        hypothesis = model(train_x)
        optimizer.zero_grad()
        cost = cost_function(hypothesis, train_y)
        cost.backward()
        optimizer.step()
    print('epoch: %s, cost: %s' %(epoch+1, cost.item()))        

with torch.no_grad():
    x_test = x[1500:]
    y_test = y[1500:]
    
    test_hypothesis = model(x_test)
    
  
    prediction = torch.argmax(test_hypothesis, dim = 1) == y_test.long()
    
    accuracy = prediction.float().mean()
    print('acc: %s' %(accuracy.item()*100))

코드는 위와 같으며 epoch 수, Learning Rate, 트레이닝, 테스트 데이타의 비율 등 하이퍼파라미터는 사실 별의미가 없이 인의적으로 설정하였다. 우리는 가중치가 0일때 신경망의 성능이 어떻게 변화하고 가중치가 어떻게 갱신되는지만 알면 되기 때문이다.

2번째 층의 가중치를 0으로 설정

  • 2번째 층의 bias, weight 모두 0으로 설정

  • 정확도는 89.56으로 상승....

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        
        self.linear1 = nn.Linear(64, 64)
        self.linear2 = nn.Linear(64, 64)
        self.linear3 = nn.Linear(64, 32)
        self.linear4 = nn.Linear(32, 10)
        
        nn.init.zeors_(self.linear2.weight)
        nn.init.zeors_(self.linear2.bia)
        
        self.layer1 = nn.Sequential(self.linear1, nn.Sigmoid())
        self.layer2 = nn.Sequential(self.linear2, nn.Sigmoid())
        self.layer3 = nn.Sequential(self.linear3, nn.Sigmoid())
        self.layer4 = nn.Sequential(self.linear4)
        

비교적 중간에 있는 층인 2층의 weight와 bias를 모두 0으로 초기화 시켰는데 놀랍게도 정확도가 증가했다... 이유를 찾기위해 2층과 전후층인 1,3 층의 가중치의 변화를 관찰하기로 했다.

이때 가중치 변화를 쉽게 관찰하기 위해 model에 다음과 같은 코드를 추가하였다. 또한 시각적으로 관찰을 용이하게 하기 위해서 30 Epoch 마다의 가중치 변화를 관찰하였다.

class Model(nn.Module):

	def Print(self):
        	print('weight1:%s, bias1:%s' %(self.linear1.weight, self.linear1.bias))
        	print('weight2:%s, bias2:%s' %(self.linear2.weight, self.linear2.bias))
        	print('weight3:%s, bias3:%s' %(self.linear3.weight, self.linear3.bias))

1층

1층의 경우 가중치 변화는 다음과 같았다.(가중치를 전부 삽입하다 보니 가독성이 좀 떨어진다...)

weight1(60epoch)

tensor([[-0.1239, -0.1180, -0.2048,  ...,  0.2765,  0.0497,  0.0327],
        [-0.0936, -0.0833,  0.0367,  ...,  0.0210,  0.0510, -0.0513],
        [-0.0233, -0.0121,  0.0142,  ..., -0.0606,  0.0593, -0.1567],
        ...,
        [ 0.0097, -0.0383, -0.0039,  ...,  0.0804, -0.0339, -0.0385],
        [-0.0789, -0.0244,  0.2051,  ..., -0.3040, -0.1175, -0.2189],
        [ 0.0911,  0.1109,  0.1433,  ..., -0.1573, -0.2226, -0.1488]],
       requires_grad=True)
       
bias1(60epoch)

tensor([-0.0859,  0.0021,  0.0862,  0.0863, -0.0406, -0.0700,  0.0850,  0.0086,
        -0.0685, -0.0677,  0.1027,  0.1201,  0.0447, -0.0491,  0.0549,  0.0402,
        -0.0546,  0.0188, -0.0595,  0.1076,  0.1133, -0.0089, -0.0440,  0.0398,
        -0.0931, -0.0997, -0.0276, -0.0641, -0.1149, -0.0492, -0.0049,  0.0349,
        -0.0517,  0.0074,  0.0765,  0.1121, -0.0590,  0.1102,  0.1060, -0.0337,
         0.1018, -0.0235,  0.0882,  0.0474,  0.0083,  0.1098, -0.0511, -0.1147,
        -0.0287, -0.0915,  0.0907,  0.0911, -0.1085,  0.1036, -0.1022, -0.0426,
        -0.0186,  0.0703,  0.0944, -0.0755, -0.0319, -0.0201,  0.0987, -0.1111],
       requires_grad=True)
       
weight1(120epoch)

tensor([[-0.1239, -0.1125, -0.2664,  ...,  0.3777,  0.0895,  0.1191],
        [-0.0936, -0.0866, -0.0393,  ..., -0.0053,  0.1135, -0.0727],
        [-0.0233, -0.0326, -0.1083,  ..., -0.0311,  0.1411, -0.2079],
        ...,
        [ 0.0097, -0.0382,  0.0342,  ...,  0.0497, -0.0222, -0.0254],
        [-0.0789, -0.0257,  0.2463,  ..., -0.3380, -0.1485, -0.2777],
        [ 0.0911,  0.1152,  0.1940,  ..., -0.1755, -0.3034, -0.1866]],
       requires_grad=True)
 
 bias1(120epoch)
 
tensor([-0.0846,  0.0049,  0.0786,  0.0851, -0.0405, -0.0730,  0.0967,  0.0054,
        -0.0714, -0.0560,  0.1012,  0.1193,  0.0490, -0.0486,  0.0632,  0.0357,
        -0.0588,  0.0151, -0.0597,  0.1088,  0.1098, -0.0020, -0.0468,  0.0383,
        -0.0984, -0.1030, -0.0287, -0.0644, -0.1204, -0.0504, -0.0037,  0.0441,
        -0.0543,  0.0093,  0.0748,  0.1102, -0.0713,  0.1066,  0.1048, -0.0394,
         0.1026, -0.0224,  0.0948,  0.0427,  0.0080,  0.1020, -0.0544, -0.1153,
        -0.0318, -0.0950,  0.0910,  0.0904, -0.1095,  0.1006, -0.1044, -0.0387,
        -0.0134,  0.0713,  0.0941, -0.0759, -0.0312, -0.0198,  0.0944, -0.1113],
       requires_grad=True)

놀랍게도 epoch의 변화가 전혀 없다... 사실 이는 XW, 행렬의 곱 연산시 X쪽에 흘려들어오는 역전파와 WTW^T의 행렬곱의 역전파가 흘러나가기 때문이다. 2층의 W가 공행렬 이기 때문에 1층에 흘러들어오는 역전파 또한 공행렬이고 1층의 W 값을 전혀 갱신하지 못하게 된다. 굳이 역전파로 생각을 하지 않더라도 순전파시 2층의 가중치가 공행렬이기 때문에 1층의 변화가 2층에 전혀 영향을 주지 못하게된다. 이때문에 1층의 가중치가 갱신되지 못한다고 생각해도 타당하다.

그러나 b의 값은 갱신된다... 큭폭으로 갱신된다고 말할 수는 없지만말이다. 참으로 궁금하다.

또한 2층의 가중치 초깃값을 0으로 설정하였는데 도대체 왜 정확도가 상승했을까... 이는 다른 층의 가중치들도 관찰한 후 생각해보아야겠다.

2층

2층의 분석 결과는 다음과 같다.


weight2(0epoch)
tensor([[ 0.0708,  0.0685, -0.0658,  ..., -0.0898,  0.0252,  0.0837],
        [-0.0351,  0.0005, -0.0547,  ...,  0.0990,  0.0947,  0.0368],
        [-0.0450,  0.0123,  0.0075,  ..., -0.1023, -0.0697,  0.0832],
        ...,
        [ 0.0032,  0.0836,  0.0125,  ..., -0.1168, -0.0605,  0.0127],
        [ 0.0843,  0.0048, -0.0301,  ..., -0.0934,  0.0191,  0.1092],
        [-0.1173,  0.0350, -0.0849,  ..., -0.0775,  0.0501,  0.0732]],
       requires_grad=True)
       
bias2(0epoch)

tensor([ 0.0833, -0.0775,  0.0472,  0.0982, -0.0665,  0.0074,  0.0062,  0.1056,
        -0.0144,  0.0850, -0.0800, -0.0099, -0.0292,  0.0187,  0.1190,  0.0342,
        -0.0261, -0.0014, -0.0657,  0.0274,  0.1006, -0.0759, -0.0669,  0.0214,
         0.1202,  0.1034,  0.1104, -0.0856, -0.0248,  0.0194,  0.0478, -0.0603,
         0.0612, -0.0292, -0.0238,  0.0084,  0.0966,  0.0969,  0.0749, -0.1119,
        -0.0940, -0.0401, -0.0672,  0.0674, -0.0737,  0.0650,  0.0678,  0.0836,
         0.1076, -0.0758,  0.0095, -0.0583, -0.0749,  0.0049,  0.0989,  0.1223,
         0.0864,  0.0313, -0.1155,  0.0389,  0.0506,  0.0342,  0.0406, -0.0842],
       requires_grad=True)
       
weight2(120epoch)

tensor([[-0.1372,  0.1642, -0.0382,  ..., -0.1024,  0.2196,  0.0174],
        [-0.0485,  0.0435,  0.0681,  ...,  0.0407,  0.1547,  0.1000],
        [ 0.2130, -0.1269,  0.1489,  ..., -0.2693, -0.2537,  0.2359],
        ...,
        [-0.1177,  0.1392,  0.0787,  ..., -0.1995,  0.0249,  0.0318],
        [ 0.1130, -0.2200, -0.1059,  ..., -0.1693, -0.1562,  0.0133],
        [-0.1836, -0.0515, -0.2443,  ...,  0.0147,  0.0276, -0.1128]],
       requires_grad=True)

bias2(120epoch)

tensor([ 0.0698, -0.0737,  0.0765,  0.1240, -0.1335,  0.0309,  0.0415,  0.0326,
        -0.0292,  0.0528, -0.1464,  0.0361, -0.0581,  0.0554,  0.0950,  0.0417,
         0.0009, -0.0275, -0.0262,  0.0829,  0.0657, -0.0557, -0.0210, -0.0085,
         0.1106,  0.1616,  0.0840, -0.1597, -0.0323,  0.0739,  0.0677, -0.0125,
         0.1012, -0.0423, -0.0008,  0.0124,  0.1097,  0.1038,  0.1056, -0.1443,
        -0.1112, -0.0286, -0.0415,  0.0916, -0.0510,  0.0820,  0.0288,  0.1124,
         0.0418, -0.0497,  0.0190, -0.0904, -0.1351, -0.0105,  0.1533,  0.0633,
         0.0736,  0.0430, -0.1300,  0.0874, -0.0145,  0.0266,  0.0329, -0.0727],
       requires_grad=True)       	

솔직히 어떠한 현상을 발견하지는 못했다. 가중치가 딱히 일정한 값을 기준으로 갱신되는 것 같지도 않고 계속 같은 비율이 곱해지는 것 같지도 않다.

3층

가중치 갱신 값을 올리는 것이 무색할 정도로 어떠한 현상을 관찰하기는 힘들었다.. 2층의 weight와 bias가 모두 공행렬 이긴 했지만 3층에서 계산되는 weight와 bias로 인해 새로운 값을 갱신할 수 있기 때문이다. 다만 이로인해 순전파시 3층으로 들어가는 값들이 모두 동일하게 되어서 사실상 그전의 층들이 나타내었던 정보는 모두 소실된다고 봐야한다고는 생각했지만 막상 결과는 정확도가 올라가서...

그래서 3층 이전의 층들이 모든 정보를 손실했는지 증명하기 위해 모델에서 3층을 제외한 층은 삭제한 뒤 다시 분류를 진행했는데... 그 결과도 89.56이 었다... 즉 나는 여태껏 한층으로만 분류를 진행했던 것이다.

아마 2층의 가중치를 0으로 초기화 한뒤에 모델의 성능이 향상된건 순전히 내가 모델을 잘 못 설계한 탓인 것 같다..

쨋건 모델에서 초기 가중치를 모두 0으로 설정하는 것은

  • 이전층의 가중치 갱신을 실패하게 만든다.

  • 이후층에 똑같은 값들을 출력하여 사실상 모든 정보가 손실된다.

의 결과를 초래한다는 것은 깨닫기는 했다. 그러나 bias만 0 이거나 weight만 0일 경우에는 어떠한 현상이 일어나는지는 알지 못했다. 이는 다음번에 진행하겠다. 또한 무엇보다 근본적으로 모델을 잘 설계하여야 한다는 것을 깨달았다.

마지막으로 애초에 왜 모델이 제대로된 성능을 보이지 못했는지는 다음과 같은 이유로 추론했다.

  • 데이터의 개수가 총 1800개로 상당히 적다. 이중 1500개를 선정하여 모델에 반복 학습하였기 때문에 과적합의 가능성이 존재한다.

  • 파라미터들 또한 매우 크게 학습되었다. 아직 많은 모델 설계 경험은 없어 어느정도가 큰 정도인지는 제대로 알지 못하지만 다른 많은 모델의 파라미터들은 표준편차가 1보다 작은 것들을 감안하였을 때 내가 얻은 가중치들은 그 크기가 매우크다고 볼 수 있다. 따라서 과적합의 가능성이 있다.

  • learning Rate도 0.3으로 큰 값이었다고 생각한다. 이는 정확도를 일정 수준 이상으로 올리는 데에는 한계가 있었을 것이다.

분석해보니 모델 설계를 참으로 못했다. 쨋건 오늘의 포스트는 이만 마치고 오늘을 경험삼아 다음번엔 더욱 좋은 모델의 설계를 위해 노력해야겠다.

profile
정말 잘 하고싶다!!!

0개의 댓글

관련 채용 정보