-
5. 신경망 학습 관련 기술(2)AI 모델(딥러닝 기초)/5. 신경망 학습 관련 기술 2023. 2. 3. 16:36728x90
※ 가중치의 초깃값
- 초깃값의 중요성을 간과하는 경우가 매우 많은데 가중치의 초깃값을 무엇으로 설정하느냐에 따라 신경망 학습의 성패가 달라지는 아주 중요한 단계이다.
■ 초깃값을 0으로 하면 안 된다!!!
- 가중치 감소 기법을 통해 오버피팅을 방지하기 위해 초깃값을 최대한 작게 하는 것이 바람직한 방법이나 0으로 설정할 경우 오차역전파법에서 모든 가중치의 값이 똑같이 갱신되어 버리기 때문에 신경망 학습이 올바로 이루어 질 수 없다. 신경망 학습은 은닉층의 깊이를 깊게 만들고 가중치를 여러 개 가짐으로써 더 깊은 학습을 해 나간다. 그런데 초깃값이 0이 된다면 가중치 자체가 모두 고르게 되기 때문에 가중치를 여러개 가지는 의미가 사라진다. 이것을 방지하기 위해 초깃값을 무작위로 설정하는 것이 바람직하다.
이제 여러개의 히스토그램과 활성화 함수, 각각의 활성화 함수에 필요한 초깃값을 실험을 통해 비교해 보자.
1) Sigmoid 함수 & 초깃값 : 가중치 표준편차가 1인 정규분포
# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000개의 데이터 node_num = 100 # 각 은닉층의 노드(뉴런) 수 hidden_layer_size = 5 # 은닉층이 5개 activations = {} # 이곳에 활성화 결과를 저장 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 초깃값을 다양하게 바꿔가며 실험해보자! w = np.random.randn(node_num, node_num) * 1 # w = np.random.randn(node_num, node_num) * 0.01 # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 활성화 함수도 바꿔가며 실험해보자! z = sigmoid(a) # z = ReLU(a) # z = tanh(a) activations[i] = z # 히스토그램 그리기 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()
결과는 활성화값이 0과 1에 치우쳐 분포된다. 이 데이터는 0과 1에 데이터가 치우쳐 분포하게 되어 역전파의 기울기 값이 점점 작아지다가 사라지게 된다. 말그대로 기울기 소실이라고 하는 심각한 문제를 불러온다.
2) Sigmoid & 초깃값 : 가중치 표준편차가 0.01인 정규분포
# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000개의 데이터 node_num = 100 # 각 은닉층의 노드(뉴런) 수 hidden_layer_size = 5 # 은닉층이 5개 activations = {} # 이곳에 활성화 결과를 저장 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 초깃값을 다양하게 바꿔가며 실험해보자! # w = np.random.randn(node_num, node_num) * 1 w = np.random.randn(node_num, node_num) * 0.01 # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 활성화 함수도 바꿔가며 실험해보자! z = sigmoid(a) # z = ReLU(a) # z = tanh(a) activations[i] = z # 히스토그램 그리기 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()
기울기 소실 문제는 일어나지 않았으나, 모든 값이 0.5 근처에 분포하는 것을 볼 수 있다. 다수의 뉴런들이 거의 동일한 값을 출력하고 있기 때문에 뉴런을 여러 개 둔 의미가 없어진다는 뜻이다. 활성화 값들이 0.5에 치우쳐 표현력을 제한하는 문제를 불러 온다.
3) Sigmoid & Xavier 초깃값
# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000개의 데이터 node_num = 100 # 각 은닉층의 노드(뉴런) 수 hidden_layer_size = 5 # 은닉층이 5개 activations = {} # 이곳에 활성화 결과를 저장 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 초깃값을 다양하게 바꿔가며 실험해보자! # w = np.random.randn(node_num, node_num) * 1 # w = np.random.randn(node_num, node_num) * 0.01 w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 활성화 함수도 바꿔가며 실험해보자! z = sigmoid(a) # z = ReLU(a) # z = tanh(a) activations[i] = z # 히스토그램 그리기 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()
Xavier 초깃값이란 초깃값의 표준편차가 1/(n^(1/2))이고 앞 계층의 노드가 n개인 분포를 의미한다. 시각화를 해보면 각 층에 골고루 값들이 퍼져있는 것을 확인할 수 있다. 이를 통해서 Sigmoid함수에는 일반적으로 Xavier 초깃값을 사용하는 것이 좋다는 것을 알 수 있다.
4) tan & Xavier 초깃값
# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000개의 데이터 node_num = 100 # 각 은닉층의 노드(뉴런) 수 hidden_layer_size = 5 # 은닉층이 5개 activations = {} # 이곳에 활성화 결과를 저장 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 초깃값을 다양하게 바꿔가며 실험해보자! # w = np.random.randn(node_num, node_num) * 1 # w = np.random.randn(node_num, node_num) * 0.01 w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 활성화 함수도 바꿔가며 실험해보자! # z = sigmoid(a) # z = ReLU(a) z = tanh(a) activations[i] = z # 히스토그램 그리기 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()
Sigmoid함수가 (0, 0.05)에서 대칭인 점 때문에 (0, 0) 원점을 대칭으로 사용하는 tan함수를 활성화 함수로 사용해 보았다. 조금 더 종모양으로 부드럽게 시각화가 이루어진 것을 확인할 수 있다.
5) ReLU & He 초깃값
# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000개의 데이터 node_num = 100 # 각 은닉층의 노드(뉴런) 수 hidden_layer_size = 5 # 은닉층이 5개 activations = {} # 이곳에 활성화 결과를 저장 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 초깃값을 다양하게 바꿔가며 실험해보자! # w = np.random.randn(node_num, node_num) * 1 # w = np.random.randn(node_num, node_num) * 0.01 # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 활성화 함수도 바꿔가며 실험해보자! # z = sigmoid(a) z = ReLU(a) # z = tanh(a) activations[i] = z # 히스토그램 그리기 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()
Sigmoid와 tan 함수는 좌우 대칭으로 중앙 부근이 선형인 함수라 활성화 함수가 선형인 것을 전제로 이끄는 Xavier 초깃값이 적절했지만, 음의 영역이 0이 되어버리는 ReLU의 경우는 더 넓은 분포를 위해 He 초깃값((2/n)^(1/2))을 사용한다.
He 초깃값을 사용하게 될 경우 위와 같이 모든 층에서 균일한 분포를 띄고 층이 깊어져도 분포가 균일하게 유지되어 역전파 때도 적절한 값이 나올 것으로 예측할 수 있다. 이를 통해 ReLU 함수를 사용할 경우 He 초깃값이 적절하다는 사실을 알 수 있다.
※ MNIST 데이터셋을 통한 가중치의 초깃값 비교
# coding: utf-8 import os import sys sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정 import numpy as np import matplotlib.pyplot as plt from dataset.mnist import load_mnist from common.util import smooth_curve from common.multi_layer_net import MultiLayerNet from common.optimizer import SGD # 0. MNIST 데이터 읽기========== (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True) train_size = x_train.shape[0] batch_size = 128 max_iterations = 2000 # 1. 실험용 설정========== weight_init_types = {'std=0.01': 0.01, 'Xavier': 'sigmoid', 'He': 'relu'} optimizer = SGD(lr=0.01) networks = {} train_loss = {} for key, weight_type in weight_init_types.items(): networks[key] = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100], output_size=10, weight_init_std=weight_type) train_loss[key] = [] # 2. 훈련 시작========== for i in range(max_iterations): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] for key in weight_init_types.keys(): grads = networks[key].gradient(x_batch, t_batch) optimizer.update(networks[key].params, grads) loss = networks[key].loss(x_batch, t_batch) train_loss[key].append(loss) if i % 100 == 0: print("===========" + "iteration:" + str(i) + "===========") for key in weight_init_types.keys(): loss = networks[key].loss(x_batch, t_batch) print(key + ":" + str(loss)) # 3. 그래프 그리기========== markers = {'std=0.01': 'o', 'Xavier': 's', 'He': 'D'} x = np.arange(max_iterations) for key in weight_init_types.keys(): plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key) plt.xlabel("iterations") plt.ylabel("loss") plt.ylim(0, 2.5) plt.legend() plt.show()
mnist 데이터셋을 통해서 지금까지 살펴보았던 활성화 함수와 해당하는 초깃값들을 설정하고 시각화를 해서 성능을 비교해 보았다. 최적화 기법을 Adam을 쓰게 되니 자동으로 편향 보정이 되어 버려서 확실하게 성능을 시각화해보기 위해 SGD를 사용했다. batch_size = 128 만큼을 2000개의 반복횟수만큼 돌려주었고, 100으로 나눈 값 만큼씩 iteration을 보여주었다.
MNIST 데이터셋을 통해 비교를 해보면 std=0.01의 정규분포 초깃값의 경우 값이 일정하게 학습이 이루어지지 않는 것을 볼 수 있다. 나머지 경우는 학습은 순조롭게 이루어지지만, Sigmoid를 사용한 Xavier의 경우보다 ReLU를 사용한 He의 경우에 기울기가 조금 더 가파르게 떨어지면서 학습이 더 빠르게 진행되고 있는 것을 알 수 있다. 이렇게 초깃값을 무엇을 사용할 것인지는 신경망 학습에서 굉장히 중요한 요소이다.
728x90'AI 모델(딥러닝 기초) > 5. 신경망 학습 관련 기술' 카테고리의 다른 글
5. 신경망 학습 관련 기술(6) (0) 2023.02.13 5. 신경망 학습 관련 기술(5) (0) 2023.02.13 5. 신경망 학습 관련 기술(4) (0) 2023.02.06 5. 신경망 학습 관련 기술(3) (0) 2023.02.04 5. 신경망 학습 관련 기술(1) (0) 2023.02.01