[2025-1] 장인영 - 밑바닥부터 시작하는 딥러닝 리뷰, (CH 3.4) 3층 신경망 구현하기
넘파이의 다차원 배열을 사용하여, 3층 신경망에서 수행되는 입력부터 출력까지의 처리를 구현한다.
3층 신경망의 입력층은 2개, 첫 번째 은닉층은 3개, 두 번째 은닉층은 2개, 출력층은 2개의 뉴런으로 구성되어 있다.
3.4.1. 표기법 설명
먼저, 신경망 처리를 설명하기 위한 표기법을 알아본다.
입력층의 뉴런에서 다음 층의 뉴런으로 향하는 선 위에 가중치를 표시한다.
가중치와 은닉층 뉴런의 오른쪽 위에는 (1)이 붙어 있고, 이는 1층의 가중치임을 뜻한다.
가중치의 오른쪽 아래의 두 숫자는 차례로 다음 층 뉴런과 앞 층 뉴런의 인덱스 번호이다.
3.4.2. 각 층의 신호 전달 구현하기
이제 입력층에서 1층의 첫 번째 뉴런으로 가는 신호를 살펴본다.
그림을 살펴보면, 편향을 뜻하는 뉴런이 추가되었다.
이때 편향은 앞 층의 편향 뉴런이 하나뿐이기 때문에 오른쪽 아래 인덱스가 하나 밖에 없다.
a1(1)은 가중치를 곱한 신호 두개와 편향을 합해서 다음과 같이 계산한다.
여기에서 행렬의 곱을 이용하면 1층의 가중치 부분을 다음 식처럼 간소화할 수 있다.
이때 행렬 A, X, B, W는 각각 다음과 같다.
넘파이의 다차원 배열을 사용해 식을 구현해본다.
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) # (2,3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X, W1) + B1
다음으로, 1층의 활성화 함수에서의 처리를 살펴본다.
은닉층에서의 가중치 합을 a로 표기하고 활성화 함수 h( )로 변환된 신호를 z로 표기한다.
활성화 함수로 시그모이드 함수를 사용하며, 파이썬으로 구현하면 다음과 같다.
Z1 = sigmoid(A1)
print(A1) # [0.3, 0.7, 1.1]
print(Z1) # [0.57444252, 0.66818777, 0.750262011]
다음으로, 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)
1층의 출력 Z1이 2층의 입력이 된다는 점을 제외하면 이전의 구현과 같다.
마지막으로 2층에서 출력층으로 신호가 전달되는 과정이다.
def identity_function(x):
return x
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 = identity_function(A3)
출력층의 구현도 거의 비슷하지만, 활성화함수에 차이가 있다.
입력을 그대로 출력하는 항등 함수인 identity_function()을 정의하고, 이를 출력층의 활성화 함수로 이용했다.
3.4.3. 구현 정리
def init_network():
network = {}
network['W1'] = np.array([[0.1, 0.3, 05], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2, 0.3])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
def forward(network, x) :
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)
return y
network = init_network()
x = np.array([1.0, 0,5])
y = forward(network, x)
print(t)
init_network() 함수는 가중치와 편향을 초기화하고 이들을 딕셔너리 변수인 network에 저장한다.
딕셔너리 변수 network에는 각 층에 필요한 매개변수를 저장한다.
forward() 함수는 입력 신호를 출력으로 변환하는 처리 과정을 모두 구현하고 있다.
퀴즈 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])
A1 = np.dot(X, W1) + B1
print(A1)
퀴즈 2. 다음 코드에서 init_network() 함수의 역할로 옳은 것은?
def init_network():
network = {}
network['W1'] = np.array([[0.1, 0.3, 05], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2, 0.3])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
def forward(network, x) :
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)
return y
network = init_network()
x = np.array([1.0, 0,5])
y = forward(network, x)
print(t)
A) 신경망을 학습시켜 가중치를 조정하는 역할을 한다.
B) 신경망의 가중치와 편향을 초기화하고 저장하는 역할을 한다.
C) 입력 신호를 출력으로 변환하는 처리 과정을 구현한다.
D) 활성화 함수(sigmoid)를 적용하는 역할을 한다.