본문 바로가기
  • 책상 밖 세상을 경험할 수 있는 Playground를 제공하고, 수동적 학습에서 창조의 삶으로의 전환을 위한 새로운 라이프 스타일을 제시합니다.
Miscellaneous

[2025-1] 장인영 - 밑바닥부터 시작하는 딥러닝 리뷰, (CH 3.4) 3층 신경망 구현하기

by inyeong 2025. 3. 5.

 

넘파이의 다차원 배열을 사용하여, 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)를 적용하는 역할을 한다.