2. 신경망(3)
신경망 3번째 시간에는 신경망을 직접 한 번 구현해 보자.
신경망 구현에 있어 가장 중요한 개념은 행렬의 연산이다.
행렬의 연산 : A(m×n) * B(n×r) = C(m×r)
우리는 행렬의 연산을 위와 같이 정의한다. m×n 행렬과 n×r 행렬이 곱해질 경우 새로운 m×r 행렬이 나타나게 된다. 여기서 앞 행렬의 열과 뒤 행렬의 행의 차원이 일치하지 않을 경우 행렬은 곱셈 연산을 할 수 없다.
이처럼 1×2 행렬과 2×3 행렬을 계산하여 1×3 행렬이 나오는 것을 확인할 수 있다. 이것을 똑같이 신경망에 적용시켜 볼 수 있다.
맨 처음 퍼셉트론 시간에 보았던 단순 신경망이다. 이 신경망을 위 행렬 연산을 적용시켜 보면, input 1×2 행렬과 weight 2×1 행렬이 만나서 output 1×1 y 행렬을 만드는 원리이다.
※ 3층 신경망 구현
1.
위 신경망을 구현하기 위하여 우리는 앞에서 다룬 행렬식 연산을 떠올려 볼 수 있다.
A = (a1, a2, a3), X = (x1, x2), B = (b1, b2, b3), W = [[w11,w21, w31], [w12, w22, w32]] 와 같은 행렬식을 통해 최종적으로
A = XW + B
라는 공식을 얻어내어 볼 수 있다. 이를 간단한 행렬 연산식으로 나타내어보자.
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape)
print(X.shape)
print(B1.shape)
A1 = np.dot(X, W1) + B1
print(A1)
이렇게 행렬식을 numpy 배열을 이용하여 구할 수 있다. 그렇다면 여기서 조금 더 나아가서 활성화 함수 처리를 하여 A 값을 구해보자.
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape)
print(X.shape)
print(B1.shape)
A1 = np.dot(X, W1) + B1
print(A1)
def sigmoid(x):
return 1 / (1 + np.exp(-x))
Z1 = sigmoid(A1)
print(Z1)
활성화 함수로 sigmoid 함수를 사용했다. 활성화 함수를 통해서 구해 보면
이처럼 조금 더 구체화된 값이 나오게 됨을 알 수 있다. 이렇게 활성화 함수를 사용하여 단순한 퍼셉트론을 복잡한 신경망 형태로 구현했다.
2.
이제 다음 과정으로 나아가 보자.
W2 = np.array([[0.1,0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape)
print(W2.shape)
print(B2.shape)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
print(A2)
print(Z2)
이렇게 구현할 수 있었다. 과정1과 과정2의 차이를 보면 배열의 원소 갯수가 다르다는 것을 확인할 수 있을 것이다. 이는 은닉층의 복잡도에 따라 달라지는 것으로 은닉층의 복잡도가 행렬의 갯수 차이를 불러오는 것을 확인할 수 있다.
3.
마지막 과정이다. 여기서는 항등함수를 사용했지만 나는 그대로 sigmoid 함수를 사용하여 마지막 과정에도 조금 더 좋은 값을 불러오도록 하겠다.
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = sigmoid(A3)
print(A3)
print(Y)
이렇게 3층 신경망을 직접적으로 구현해보았다. 행렬식과 활성화 함수를 사용하여 신경망 하나를 구현할 수 있었고 이런 과정을 반복하게 되면 더 고층 신경망 역시 구현할 수 있을 것이다.
<코드 정리>
import numpy as np
def sigmoid(x): # sigmoid 함수 구현
return 1 / (1 + np.exp(-x))
# 0->1층 신경망
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape)
print(X.shape)
print(B1.shape)
A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)
print(A1)
print(Z1)
# 1->2층 신경망
W2 = np.array([[0.1,0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape)
print(W2.shape)
print(B2.shape)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
print(A2)
print(Z2)
# 2->3층 신경망
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = sigmoid(A3)
print(A3)
print(Y) # 최종 결과값
이렇게 신경망을 구현했고, 신경망의 순방향 순전파를 구현할 수 있었다. 기본 propogation 뿐만 아니라 추후 back propogation 역시 다루어 볼 예정이고 back propogation이 인공지능에서 차지하는 비중이 꽤 크기 때문에 back propogation에서는 조금 더 집중을 해보려 한다.