오늘은 신경망을 구현해 보자.
단일 뉴런 구현
가장 첫 번째로 단일 뉴런을 구현해 보자.
입력값 x, y를 설정하자
x = np.arrange(-1.0, 1.0, 0.2)
y = np.arrange(-1.0, 1.0, 0.2)
x, y값 모두 numpy의 arrange 함수로 -1.0부터 1.0 직전까지 0.2 간격으로 10개의 값을 넘파이 배열로 저장할 것이다.
출력값을 저장하는 그리드맵을 만들어주자.
z = np.zeros((10, 10))
그리고 단일 뉴런을 처리하는 코드를 합치면 다음과 같은 코드를 만들 수 있다.
import numpy as np
import matplotlib.pyplot as plt
#원소수 10개 설정
x = np.arange(-1.0, 1.0, 0.08)
y = np.arange(-1.0, 1.0, 0.08)
#출력값을 저장할 10 by 10 그리드
z = np.zeros((25, 25))
#입력 가중치
w_x = 2.5
w_y = 3.0
#편향
bias = 0.1
#그리드맵의 각 그리드별 뉴런 연산
for i in range(25):
for j in range(25):
#(입력과 가중치 곱의 합) + 편향
u = (x[i] * w_x + y[j] * w_y) + bias
#그리드맵에 출력값 저장
Y = 1 / (1 + np.exp(-u)) #시그모이드 함수
z[j][i] = Y
#그리드맵 표시
plt.imshow(z, "gray", vmin = 0.0, vmax = 1.0)
plt.colorbar()
plt.show()
출력:
좌상단부의 검은색 영역, 즉 출력값이 0에 가까운 영역에서 우하단부의 흰색 영역, 즉 출력값이 1에 가까운 영역까지의 출력값은 연속적으로 변화하고 있다. 이것은 뉴런의 활성화 함수를 시그모이드 함수를 이용했기 때문.
이 코드에서 가중치를 변화시켜 보면 어떻게 될까?
이처럼 가중치는 대응하는 입력의 영향력 크기를 표시한다. 가중치가 0에 가까우면 영향력이 작게 되고, 0 보다 커지면 영향력이 확대되며, 음수가 되면 영향력은 반대가 된다.
이번엔 편향을 조작해 보자.
가운데 그리드맵은 편향이 0인 경우이고 왼쪽은 음수인 경우, 오른쪽은 양수인 경우이다. 왼쪽의 경우 검은색 영역이 넓어 뉴런이 흥분하기 어려운 상태임을 알 수 있다. 반면 오른쪽은 흰색 영역이 더 넓어 뉴런이 흥분하기 쉬운 상태임을 알 수 있다.
단일 뉴런의 출력을 시각화해보았다.
신경망 구현
이제부터 신경망, 즉 다수의 뉴런으로 형성된 네트워크를 구현해 보자.
우선 출력값이 연속적인 회귀 문제를 다루어 보자
초록색 하이라이트는 무시 바란다.
이 신경망을 구현하기 위해 은닉층 활성화 함수를 시그모이드 함수, 출력층의 활성화 함수를 회귀 문제에 적합한 항등 함수로 설정하겠다.
입력층, 은닉층, 출력층, 이 3개의 각 층을 구현해보자.
입력층은 입력값을 받기만 하므로 생략하고 은닉층을 구현해 보자.
def middle_layer(x, w, b):
u = np.dot(x, w) + b
return 1 / (1 + np.exp(-u)) #시그모이드 함수
다음은 출력층을 구현해 보자.
def output_layer(x, w, b):
u = np.dot(x, w) + b
return u #항등 함수
가중치는 다음과 같이 Numpy의 배열을 이용해 행렬로 구현한다.
w_im = np.array([[4.0, 4.0],
[4.0, 4.0]]) #은닉층 2 by 2 행렬
w_mo = np.array([[1.0],
[-1.0]]) #출력층 2 by 1 행렬
입력층의 뉴런 수는 2이고 중간층의 뉴런 수는 2이므로, 은닉층에는 2*2개의 가중치가 필요하고 은닉층에서 출력층은 2*1개의 가중치가 필요하다. 편향은 다음과 같이 벡터로 생성한다.
b_im = np.array([3.0, -3.0]) #은닉층
b_mo = np.array([0.1]) #출력층
편향 수는 뉴런의 수과 일치하므로 은닉층에 2개, 출력층에 1개의 편향이 필요하다. 이 과정을 통해 순전파를 구현하면 이 신경망의 전체 코드는 다음과 같다.
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
#원소수 10개 설정
x = np.arange(-1.0, 1.0, 0.2)
y = np.arange(-1.0, 1.0, 0.2)
#출력값을 저장할 10 by 10 그리드
z = np.zeros((10, 10))
#입력 가중치
w_im = np.array([[4.0, 4.0],
[4.0, 4.0]]) #은닉층 2 by 2 행렬
w_mo = np.array([[1.0],
[-1.0]]) #출력층 2 by 1 행렬
#편향
b_im = np.array([3.0, -3.0]) #은닉층
b_mo = np.array([0.1]) #출력층
#은닉층
def middle_layer(x, w, b):
u = np.dot(x, w) + b
return 1 / (1 + np.exp(-u)) #시그모이드 함수
#출력층
def output_layer(x, w, b):
u = np.dot(x, w) + b
return u #항등 함수
#그리드맵의 각 그리드별 신경망 연산
for i in range(10):
for j in range(10):
inp = np.array([x[i], y[j]]) #입력층
mid = middle_layer(inp, w_im, b_im) #은닉층
out = output_layer(mid, w_mo, b_mo) #출력층
z[j][i] = out[0]
#그리드맵 표시
plt.imshow(z, "gray", vmin = 0.0, vmax = 1.0)
plt.colorbar()
plt.show()
출력:
단일 뉴런이었을 때는 흰색 영역과 검은색 영역 두 개로 분할되었지만 다수의 뉴런으로 형성된 이 신경망이 다수 복잡해져서 한번 더 살펴보길 바란다.
또한 가중치와 편향을 변화시킬 때 출력분포가 다양한 형태로 변화하는데, 이것을 통해 여러 개의 뉴런을 네트워크로 연결한 신경망을 구축하면 단일 뉴런에 비해 표현력이 훨씬 향상된 것을 확인할 수 있다.
회귀 문제를 다루었으니, 이번엔 분류 문제를 다뤄보자.
초록색 하이라이트는 무시 바란다.
회귀 문제에서 만든 네트워크와 비슷하지만, 출력층의 뉴런이 2개라는 점이 다르다. 이번에는 소프트맥스 함수를 이용하여 출력층을 구현해 보겠다.
소프트맥스 함수는 출력값을 확률로 해석하므로 출력층에 있는 2개의 뉴런에서 나온 출력값을 비교해 더 큰 쪽으로 입력값을 분류한다. 분류 결과를 산포도로 표시하면서 코드를 구현해 보자.
def output_layer(x, w, b):
u = np.dot(x, w) + b
return np.exp(u) / np.sum(np.exp(u)) #소프트맥스 함수
위의 소프트맥스 함수로 신경망 분류 문제의 전체 코드를 구현해 보자.
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
#원소수 20개 설정
x = np.arange(-1.0, 1.0, 0.1)
y = np.arange(-1.0, 1.0, 0.1)
#입력 가중치
w_im = np.array([[1.0, 2.0],
[2.0, 3.0]]) #은닉층 2 by 2 행렬
w_mo = np.array([[-1.0, 1.0],
[1.0, -1.0]]) #출력층 2 by 2 행렬
#편향
b_im = np.array([0.3, -0.3]) #은닉층
b_mo = np.array([0.4, 0.1]) #출력층
#은닉층
def middle_layer(x, w, b):
u = np.dot(x, w) + b
return 1 / (1 + np.exp(-u)) #시그모이드 함수
#출력층
def output_layer(x, w, b):
u = np.dot(x, w) + b
return np.exp(u) / np.sum(np.exp(u)) #소프트맥스 함수
#분류 결과를 저장하는 리스트
x_1 = []
y_1 = []
x_2 = []
y_2 = []
#그리드맵의 각 그리드별 신경망 연산
for i in range(20):
for j in range(20):
#순전파
inp = np.array([x[i], y[j]]) #입력층
mid = middle_layer(inp, w_im, b_im) #은닉층
out = output_layer(mid, w_mo, b_mo) #출력층
#확률 크기를 비교해 분류
if out[0] > out[1]:
x_1.append(x[i])
y_1.append(y[j])
else:
x_2.append(x[i])
y_2.append(y[j])
#산포도 표시
plt.scatter(x_1, y_1, marker = "+")
plt.scatter(x_2, y_2, marker = "*")
plt.show()
출력:
다음은 각 다양한 편향, 가중치에 따른 산포도 모양이다.
회귀 문제뿐만 아니라 분류 문제에서도 신경망의 표현력을 확인할 수 있다.
오늘은 단일 뉴런과 회귀, 분류 문제에 따른 신경망을 구현해 보았다.
'Machine Learning' 카테고리의 다른 글
역전파(2) (1) | 2024.01.05 |
---|---|
역전파(1) (2) | 2024.01.05 |
신경망(2) (0) | 2023.12.31 |
신경망(1) Neural Network (1) | 2023.12.30 |
딥러닝을 위한 수학 훑어보기 (2) | 2023.12.26 |