티스토리 뷰
[딥러닝 express]의 연습문제 6장 풀이입니다. (개인적인 풀이기에 오답이 있을 수 있습니다!)
01. 신경망에서는 다양한 활성화 함수가 사용된다. 어떤 활성화 함수들이 사용되는가? 각 활성화 함수들의 특징은 무엇인가?
계단함수 (step function)
입력 신호의 총합이 0을 넘으면 1을, 아니라면 0을 출력합니다. 0에서 미분되지 않습니다.
시그모이드 함수 (sigmoid function)
1980년대부터 사용되어온 전통적인 활성화 함수로 s자 형태의 곡선 형태를 가지며, 출력이 0.0에서 1.0의 연속적인 실수기 때문에 정밀한 출력이 가능하다는 장점이 있습니다. 하지만 입력값이 작아지거나, 커질수록 기울기가 0에 가까워지며 그래디언트 소실 문제가 발생합니다.
ReLU 함수 (Rectifed Linear Unit function)
0을 넘으면 값을 그대로 출력하고 아니라면 0을 출력하는 함수입니다. 다른 함수들에 비해 학습 속도가 빠르고, 연산과 구현이 간단합니다. 그래디언트 소멸을 방지하여 널리 사용되고 있습니다.
tanh 함수 (tangent function)
시그모이드 함수와 유사하나, 출력 값이 -1에서 1까지로 음수 출력을 지원합니다. 순환 신경망에서 많이 사용됩니다.
02. 본문에서 오차함수가 \(y = x^2 - 6x + 4 \) 일 때 경사 하강법을 사용하여 오차함수의 y의 최저값을 계산하는 절차를 설명한 바 있다. 이것을 다음과 같이 그래프로 그릴수 있는가? 맵플롯립을 사용해보자.
import numpy as np
import matplotlib.pyplot as plt
x = 10 # 시작 x좌표
learning_rate = 0.2 # 학습률
max_iterations = 100 # 최대 반복회수
# precision = 0.00001
# 손실함수를 람다식으로 정의
loss_func = lambda x: (x-3)**2 + 10
# 그래디언트를 람다식으로 정의 (손실함수의 1차 미분)
gradient = lambda x: 2*x-6
# 손실함수 그래프 X, Y값
X_ = np.linspace(0,10,50)
Y_ = (X_-3)**2 + 10
# 그래디언트 그래프 X, Y값
X = np.array([x])
Y = np.array([loss_func(x)])
# 그래디언트 강하법
for i in range(max_iterations):
x = x - learning_rate * gradient(x)
# 결과 저장
X = np.append(X, x)
Y = np.append(Y, loss_func(x))
# 그래프 그리기
plt.plot(X_, Y_, zorder = 0)
plt.scatter(X, Y, marker='*', color = 'y', zorder = 1)
plt.show()
학습률을 변화 시키면서 점을 그려보자. 어떻게 점들이 그려지는가? 학습률을 아주 크거나 작게 설정해서 점들을 그려보자.
학습률이 너무 크니 최솟값을 찾지 못하고 발산하는 모습을 볼 수 있었다. 반대로 학습률이 작을때는 수렴 할 때까지 오랜 시간 학습을 진행해야 했다.
03. 구글의 플레이그라운드를 사용하여 다음과 같은 MLP를 생성한다. 활성화 함수를 “linear”로 설정하였을 때, 입력을 올바르게 분류하는가? 활성화 함수를 “ReLU”로 변경하면 어떻게 되는가? 활성화 함수가 “Sigmoid”와 “ReLU”일 때도 성능을 비교해 보자.
활성화 함수를 Linear 함수로 선택했을 때에는 오랜 시간이 지나도 분류가 되지 않는 것을 볼 수 있었다. 이와 다르게 Sigmoid 함수와 ReLU 함수는 제대로 입력을 분류하며 ReLU가 더 적은 epoch로 좋은 성능을 보여주었다.
04. 어떤 경우에는 구체적으로 계산을 해보는 것이 이해하는데 도움이 된다. 다음과 같이 2개의 입력과 하나의 출력을 가지는 MLP에서 원하는 출력값이 0이라고 할 때, 각 뉴론의 출력값과 오차를 계산해보자. 활성화 함수는 계산을 간단히 하기 위하여 ReLU함수라고 가정한다. 순방향 패스와 오차값만을 계산해보자.
05. 4번의 신경망에서 오차가 역전파되는 과정을 한단계만 계산해보자. 활성화 함수는 계산을 간단히 하기위하여 ReLU함수라고 가정한다.
06. 본문의 MLP 파이썬 프로그램을 참조하여 4번의 신경망을 구현해보자. 활성화 함수는 ReLU함수라고 가정한다.
import numpy as np
# ReLU 함수
def relu(x):
return np.maximum(x, 0)
# 렐루 함수의 미분치
def relu_deriv(x):
x[x <= 0] = 0
x[x > 0] = 1
return x
# 입력유닛의 개수, 은닉유닛의 개수, 출력유닛의 개수
inputs, hiddens, outputs = 2, 3, 1
learning_rate = 0.2 # 학습률
# 훈련 샘플과 정답
X = np.array([[1, 1]])
T = np.array([[0]])
# 가중치
W1 = np.array([[0.8, 0.4, 0.3], [0.2, 0.9, 0.5]])
W2 = np.array([[0.3], [0.5], [0.9]])
# 순방향 전파
def predict(x):
layer0 = x # 입력을 layer0에 대입한다.
Z1 = np.dot(layer0, W1)
layer1 = relu(Z1)
Z2 = np.dot(layer1, W2)
layer2 = relu(Z2)
return layer0, layer1, layer2
# 역방향 전파
def fit():
global W1, W2
for i in range(1): # 1번 반복
for x, y in zip(X, T): # 학습 샘플 하나씩 가져오기 (여기서는 원래 한개)
# 2차원 행렬로 변환
x = np.reshape(x, (1, -1))
y = np.reshape(y, (1, -1))
layer0, layer1, layer2 = predict(x) # 순방향 계산
layer2_error = layer2 - y # 오차 계산
layer2_delta = layer2_error * relu_deriv(layer2) # 출력층의 델타 계산
layer1_error = np.dot(layer2_delta, W2.T) # 은닉층의 오차 계산
layer1_delta = layer1_error * relu_deriv(layer1) # 은닉층의 델타 계산
# 가중치 갱신
W2 += -learning_rate * np.dot(layer1.T, layer2_delta)
W1 += -learning_rate * np.dot(layer0.T, layer1_delta)
# print("W1 : ", W1)
print("W2 : ", W2.reshape(1, -1), "\n")
def test():
for x, y in zip(X, T):
x = np.reshape(x, (1, -1)) # 샘플 2차원 행렬로 변환
layer0, layer1, layer2 = predict(x)
print(x, y, layer2, "\n") # 출력층의 값 출력
print("학습하기 전 결과")
test()
fit()
print("학습한 후 결과")
test()
학습하기 전 결과
[[1 1]] [0] [[1.67]]
W2 : [[-0.034 0.166 0.566]]
학습한 후 결과
[[1 1]] [0] [[0.2456904]]
위에서 계산한 결과가 맞는지 1번 학습시킨 후 가중치를 확인해 보았습니다. 계산한대로 w7의 가중치가 -0.034으로 변경된 것을 확인할 수 있습니다. 가중치 갱신으로 출력 결과 또한 정답에 가까워진 것으로 보아 역전파 알고리즘이 제대로 작동하고 있음을 알 수 있습니다.
07. 본문의 MLP파이썬 프로그램에서 다양한 활성화 함수를 사용하여서 학습이 어떻게 달라지는 지를 살펴보자. ReLU함수와 Tanh함수를 사용해보자
주석처리를 바꾸어 가며 테스트 해보았습니다.
import numpy as np
# 시그모이드 함수
def actf(x):
return 1 / (1 + np.exp(-x))
# 시그모이드 함수의 미분치
def actf_deriv(x):
return x * (1 - x)
# ReLU 함수
def relu(x):
return np.maximum(x, 0)
# 렐루 함수의 미분치
def relu_deriv(x):
x[x <= 0] = 0
x[x > 0] = 1
return x
# tanh 함수
def tanh(x):
return np.tanh(x)
# tanh 함수의 미분치
def tanh_deriv(x):
return (1 + tanh(x)) * (1 - tanh(x))
# 입력유닛의 개수, 은닉유닛의 개수, 출력유닛의 개수
inputs, hiddens, outputs = 2, 2, 1
learning_rate = 0.2
# 훈련 샘플과 정답
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
T = np.array([[0], [1], [1], [0]])
W1 = np.array([[0.10, 0.20], [0.30, 0.40]])
W2 = np.array([[0.50], [0.60]])
B1 = np.array([0.1, 0.2])
B2 = np.array([0.3])
# 순방향 전파 계산
def predict(x):
layer0 = x #
Z1 = np.dot(layer0, W1) + B1
layer1 = actf(Z1)
# layer1 = relu(Z1)
# layer1 = tanh(Z1)
Z2 = np.dot(layer1, W2) + B2
layer2 = actf(Z2)
# layer2 = relu(Z2)
# layer2 = tanh(Z2)
return layer0, layer1, layer2
# 역방향 전파 계산
def fit():
global W1, W2, B1, B2
for i in range(90000):
for x, y in zip(X, T):
x = np.reshape(x, (1, -1))
y = np.reshape(y, (1, -1))
layer0, layer1, layer2 = predict(x)
layer2_error = layer2 - y
layer2_delta = layer2_error * actf_deriv(layer2)
# layer2_delta = layer2_error*relu_deriv(layer2)
# layer2_delta = layer2_error*tanh_deriv(layer2)
layer1_error = np.dot(layer2_delta, W2.T)
layer1_delta = layer1_error * actf_deriv(layer1)
# layer1_delta = layer1_error*relu_deriv(layer1)
# layer1_delta = layer1_error*tanh_deriv(layer1)
W2 += -learning_rate * np.dot(layer1.T, layer2_delta)
W1 += -learning_rate * np.dot(layer0.T, layer1_delta)
B2 += -learning_rate * np.sum(layer2_delta, axis=0)
B1 += -learning_rate * np.sum(layer1_delta, axis=0)
def test():
for x, y in zip(X, T):
x = np.reshape(x, (1, -1))
layer0, layer1, layer2 = predict(x)
print(x, y, layer2)
print("sigmoid")
# print("ReLU")
# print("Tanh")
fit()
test()
ReLU의 경우 모든 층에서 Relu를 사용했더니 제대로 학습이 되지 않는 것을 확인했다. 출력단에서는 0과 1사이의 값을 구하기 위해 렐루가 아닌 시그모이드를 사용하여야 하기 때문이다.
sigmoid
[[0 0]] [0] [[0.00814407]]
[[0 1]] [1] [[0.99154105]]
[[1 0]] [1] [[0.99152258]]
[[1 1]] [0] [[0.01038517]]
ReLU
[[0 0]] [0] [[0.32786885]]
[[0 1]] [1] [[1.06557377]]
[[1 0]] [1] [[0.32786885]]
[[1 1]] [0] [[0.32786885]]
Tanh
[[0 0]] [0] [[-0.00412996]]
[[0 1]] [1] [[0.99996947]]
[[1 0]] [1] [[0.99996947]]
[[1 1]] [0] [[-0.00633601]]
08. 본문의 MLP파이썬 프로그램에서 XOR를 학습시킬 때, 가중치가 어떻게 변화하는지 조사해보자. 즉 한문장씩 실행하면서 가중치를 추적해 보자. 또 바이어스 값을 어떻게 변경되는가?
import numpy as np
# 시그모이드 함수
def actf(x):
return 1 / (1 + np.exp(-x))
# 시그모이드 함수의 미분치
def actf_deriv(x):
return x * (1 - x)
# 입력유닛의 개수, 은닉유닛의 개수, 출력유닛의 개수
inputs, hiddens, outputs = 2, 2, 1
learning_rate = 0.2
# 훈련 샘플과 정답
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
T = np.array([[0], [1], [1], [0]])
W1 = np.array([[0.10, 0.20], [0.30, 0.40]])
W2 = np.array([[0.50], [0.60]])
B1 = np.array([0.1, 0.2])
B2 = np.array([0.3])
# 순방향 전파 계산
def predict(x):
layer0 = x
Z1 = np.dot(layer0, W1) + B1
layer1 = actf(Z1)
Z2 = np.dot(layer1, W2) + B2
layer2 = actf(Z2)
return layer0, layer1, layer2
# 역방향 전파 계산
def fit():
global W1, W2, B1, B2
for i in range(1):
for x, y in zip(X, T):
x = np.reshape(x, (1, -1))
y = np.reshape(y, (1, -1))
layer0, layer1, layer2 = predict(x)
print("입력 : ", layer0, " 은닉층 출력 : ", layer1, " 출력층 출력 : ", layer2)
layer2_error = layer2 - y
print("오차 : ", layer2_error)
layer2_delta = layer2_error * actf_deriv(layer2)
print("출력층-은닉층 델타 :", layer2_delta.reshape(1, -1))
layer1_error = np.dot(layer2_delta, W2.T)
layer1_delta = layer1_error * actf_deriv(layer1)
print("은닉층-입력층 델타 :", layer1_delta.reshape(1, -1))
W2 += -learning_rate * np.dot(layer1.T, layer2_delta)
W1 += -learning_rate * np.dot(layer0.T, layer1_delta)
B2 += -learning_rate * np.sum(layer2_delta, axis=0)
B1 += -learning_rate * np.sum(layer1_delta, axis=0)
print("\n==가중치 갱신==")
print("W2 : ", W2.reshape(1, -1))
print("W1 : ", W1.reshape(1, -1))
print("B2 : ", B2.reshape(1, -1))
print("B1 : ", B1.reshape(1, -1), "\n")
def test():
for x, y in zip(X, T):
x = np.reshape(x, (1, -1))
layer0, layer1, layer2 = predict(x)
print(x, y, layer2)
fit()
# test()
1epoch를 진행하면서 가중치들의 변화를 추적해보았다.
입력 : [[0 0]] 은닉층 출력 : [[0.52497919 0.549834 ]] 출력층 출력 : [[0.70938314]]
오차 : [[0.70938314]]
출력층-은닉층 델타 : [[0.14624551]]
은닉층-입력층 델타 : [[0.01823506 0.02171891]]
==가중치 갱신==
W2 : [[0.48464483 0.58391785]]
W1 : [[0.1 0.2 0.3 0.4]]
B2 : [[0.2707509]]
B1 : [[0.09635299 0.19565622]]
입력 : [[0 1]] 은닉층 출력 : [[0.59781111 0.64466189]] 출력층 출력 : [[0.71847437]]
오차 : [[-0.28152563]]
출력층-은닉층 델타 : [[-0.05694389]]
은닉층-입력층 델타 : [[-0.00663536 -0.0076168 ]]
==가중치 갱신==
W2 : [[0.49145317 0.59125976]]
W1 : [[0.1 0.2 0.30132707 0.40152336]]
B2 : [[0.28213968]]
B1 : [[0.09768006 0.19717958]]
입력 : [[1 0]] 은닉층 출력 : [[0.54925971 0.59800984]] 출력층 출력 : [[0.71211007]]
오차 : [[-0.28788993]]
출력층-은닉층 델타 : [[-0.05902012]]
은닉층-입력층 델타 : [[-0.00718102 -0.00838884]]
==가중치 갱신==
W2 : [[0.49793664 0.59831868]]
W1 : [[0.1014362 0.20167777 0.30132707 0.40152336]]
B2 : [[0.2939437]]
B1 : [[0.09911627 0.19885735]]
입력 : [[1 1]] 은닉층 출력 : [[0.62290093 0.69041464]] 출력층 출력 : [[0.73442623]]
오차 : [[0.73442623]]
출력층-은닉층 델타 : [[0.14324568]]
은닉층-입력층 델타 : [[0.01675445 0.01831912]]
==가중치 갱신==
W2 : [[0.48009107 0.5785389 ]]
W1 : [[0.09808532 0.19801395 0.29797618 0.39785954]]
B2 : [[0.26529456]]
[0,0]
[0,1]
[1,0]
[1, 1]
09. 우리는 앞장에서 퍼셉트론을 이용하여 아이리스 데이터를 분류해 본 바 있다. MLP을 사용하여 이것을 다시 시도해보자. 다음의 코드를 참조한다. Sklearn이 제공하는 MLP를 사용해도 좋다.
import numpy as np
from sklearn.datasets import load_iris
from sklearn.neural_network import MLPClassifier
from sklearn.linear_model import Perceptron
iris = load_iris()
X = iris.data[:,(0,1)]
y = (iris.target == 0).astype(np.int64)
# 퍼셉트론 생성, 학습
percept = Perceptron(random_state=32, max_iter=1000)
percept.fit(X,y)
print("Perceptron Score : ", percept.score(X,y))
# MLP생성, 학습
mlp = MLPClassifier(random_state=32, max_iter=1000)
mlp.fit(X,y)
print("MLP Score : ", mlp.score(X,y))
같은 회수로 학습시켰을 때 MLP의 정확률이 더 올라간 것을 볼 수 있다.
Perceptron Score : 0.8533333333333334
MLP Score : 0.9933333333333333
감사합니다.
공부한 내용을 복습/기록하기 위해 작성한 글이므로 내용에 오류가 있을 수 있습니다.
'인공지능' 카테고리의 다른 글
[인공지능] 딥러닝 express 연습문제 7장 (0) | 2022.09.16 |
---|---|
[인공지능] Keras로 MNIST 숫자 학습하기 (feat. Sequential 모델) (0) | 2022.09.16 |
[인공지능] Numpy로 역방향전파 구현하기 (1) | 2022.09.14 |
[인공지능] 딥러닝 express 연습문제 4장 (1) | 2022.09.08 |
[인공지능] Sklearn + Numpy만으로 퍼셉트론 구현하기 (1) | 2022.09.07 |