일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 코드업
- 이진탐색트리
- cURL
- 함수
- BST
- 미분 방정식
- 자바
- 자료형
- auto-encoder
- dictionary
- java
- 회로이론
- 선적분
- 파이썬
- 강화학습
- 소행성
- Asteroid RL
- 백트래킹
- 딥러닝
- 계단 오르기
- Class
- Python
- 벡터해석
- 2P1L
- 델
- 최단 경로
- 벡터 해석
- 피보나치 수열
- 신경망
- 딕셔너리
- Today
- Total
Zeta Oph's Study
신경망의 구현 - 딥러닝 공부 (2) 본문
이 글에서는 저번 글에 이어 신경망을 구현해보도록 하겠습니다.
저번 글에서 퍼셉트론이 무엇인지, 신경망이 무엇인지, 그리고 이를 이해하기 위해 필요한 기초 지식들을 다루었습니다.
https://crane206265.tistory.com/17
퍼셉트론과 신경망 - 딥러닝 공부 (1)
앞으로 여러 글에 걸쳐 딥러닝 기초를 공부해보려고 합니다. 이 글에서는 신경망이란 무엇인지 알아보도록 하겠습니다. 신경망이란? 신경망(Neural Network)이란 인간의 뉴런 구조를 본떠 만든 기계
crane206265.tistory.com
신경망에 대해 공부를 해보았으니, 이번엔 직접 신경망을 구현해봅시다.
다시 3층 신경망 그림을 가져와봅시다.
노드에서 다음 노드로 넘어갈 때 연산을 해주려고 보았더니, 서로 연결된 것이 너무 많습니다. 기존 방식으로 일일이 계산을 다 해주기에는 너무 많죠. 그래서 신경망에서 한 층에서 다음 층으로 넘어갈 때는 행렬곱을 사용합니다.
먼저 표기법을 정하고 갑시다. $a_{m}^{(n)},\;b_{m}^{(n)}$는 각각 $n$번째 층의 $m$번째 입력, 편향이라는 뜻입니다. 또한 $w_{ij}^{(n)}$는 앞 층의 $j$번째 뉴런에서 다음 층의 $i$번째 뉴런으로 가는 $n$층의 가중치라는 뜻입니다. 이 글에서는 앞으로 이러한 표기를 사용하도록 하겠습니다.
위 그림에서 1층을 살펴봅시다. 아래와 같은 그림으로 표현될 것입니다.
위 그림에 주어진 대로 계산해보면,
$$a_1^{(1)}=w_{11}^{(1)}x_1+w_{12}^{(1)}x_2+b_1^{(1)}$$
이 값들을 일일이 계산하기는 조금 버겁겠죠. 그래서 행렬곱을 이용할 겁니다. 우선 아래와 같이 행렬을 정의합시다.
$$A^{(1)}=\left[ \begin{matrix} a_1^{(1)} \; a_2^{(1)} \; a_3^{(1)} \end{matrix} \right], \; X=\left[ \begin{matrix} x_1 \; x_2 \end{matrix} \right], \; B^{(1)}=\left[ \begin{matrix} b_1^{(1)} \; b_2^{(1)} \; b_3^{(1)} \end{matrix} \right]$$
$$W^{(1)}=\begin{bmatrix} w_{11}^{(1)} & w_{21}^{(1)} & w_{31}^{(1)} \\ w_{12}^{(1)} & w_{22}^{(1)} & w_{32}^{(1)} \\ \end{bmatrix}$$
이를 이용하여, 입력값 총합 계산식을 아래처럼 단순화 할 수 있습니다.
$$A^{(1)}=XW^{(1)}+B^{(1)}$$
이러한 행렬 연산을 층마다 반복해주면, 그것이 신경망 연산이 되는 것입니다.
그러면 이를 코드로 짜봅시다. 보통은 라이브러리를 사용하여 신경망을 구현하지만, 이 글에서는 라이브러리 없이 신경망을 구현해보도록 하겠습니다. 코드가 복잡할 것으로 생각되어서, 순서도를 대략적으로 그려보았습니다.
이를 바탕으로 코드를 짜면 아래와 같습니다. 가중치와 편향 값은 임의로 정해주었습니다.
import numpy as np
def sigmoid(x): #시그모이드 함수
return 1/(1+np.exp(-x))
def identity_function(x): #항등 함수
return x
def init_network(): #각 노드의 가중치, 편향 값 설정
network = {}
network['W1'] = np.array([[0.1, 0.3, 0.5], [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])
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(y)
우선 처음에 시그모이드 함수와 항등 함수를 정의해줍니다. 그리고 가중치와 편향 값을 설정하는 init_network 함수를 정의하고, 행렬곱과 활성화 함수를 반복 실행하는 신경망 연산을 담당하는 forward 함수를 정의해줍니다. 필요한 함수들을 모두 정의했으면, 순서대로 작동시키고 forward 함수에 입력값 x를 넣어 출력값 y를 계산해줍니다.
그런데, 코드를 짜다보니 이러한 생각이 들었습니다. "만약 노드와 층의 개수가 많아진다면, 가중치/편향 값을 담고 있는 행렬을 불러올 때 network['W1']와 같이 인덱스를 문자로 하여 불러오기 힘들 것 같고, 각 층별 행렬곱과 활성화 함수 연산을 일일이 적어주는 것이 힘들지 않을까?" 그래서 저는 층과 노드의 수가 늘어나더라도 불편하지 않도록 반복문을 이용하고, 가중치와 편향 행렬을 새로 선언해주어 코드를 개선해보았습니다. 개선한 코드는 아래와 같습니다.
#3층 신경망 구현
import numpy as np
def sigmoid(x): #시그모이드 함수
return 1/(1+np.exp(-x))
def identity_function(x): #항등 함수
return x
def init_network(): #각 노드의 가중치, 편향 설정
networkW = [] #가중치 배열 저장할 리스트
networkB = [] #편향 배열 저장할 리스트
#가중치, 편향 값 설정
w1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
networkW.append(w1)
b1 = np.array([0.1, 0.2, 0.3])
networkB.append(b1)
w2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
networkW.append(w2)
b2 = np.array([0.1, 0.2])
networkB.append(b2)
w3 = np.array([[0.1, 0.3], [0.2, 0.4]])
networkW.append(w3)
b3 = np.array([0.1, 0.2])
networkB.append(b3)
return networkW, networkB
n = 3 #층 수 설정
def forward(networkW, networkB, x):
#신경망 연산
input = x
for i in range(n-1):
a = np.dot(input, networkW[i]) + networkB[i] #행렬곱 이용 각 층 연산
z = sigmoid(a) #활성화 함수
input = z
an = np.dot(input, networkW[n-1]) + networkB[n-1]
y = identity_function(an) #분류 문제 : 소프트맥스 함수 / 회귀 문제 : 항등 함수
return y
#작동 코드
networkW, networkB = init_network() #가중치, 편향 설정
x = np.array([1.0, 0.5]) #입력값
y = forward(networkW, networkB, x) #출력값 = 신경망 연산값
print(y) #결과 출력
networkW, networkB 어레이로 가중치 행렬과 편향 행렬을 분리해주었고, 인덱스를 숫자로 바꾸어 반복문으로 접근할 수 있도록 하였습니다. 또한 행렬곱과 활성화 함수를 반복 실행하는 과정을 반복문을 이용하여 처리하였습니다.
이와 같이 신호가 입력에서 출력 방향으로만 진행하는 것을 순전파(순방향 전파)라고 합니다.
이번에는 배치(batch)에 대해 알아봅시다. 쉽게 말하자면 데이터의 묶음입니다. 기존에는 데이터를 입력으로 하나하나 보냈다면, 배치 처리를 할 경우 데이터를 모아두었다가 묶음으로 한 번에 입력으로 넘겨주는 것이죠. 이러한 배치 처리를 하는 이유는 컴퓨터에서 큰 배열을 효율적으로 처리할 수 있기 때문이고, 데이터 전송 시 병목 현상을 방지하기 위해서입니다. 배치 처리를 하는 방법은, 데이터를 배치 사이즈만큼으로 쪼개어 각각을 array로 묶어주고, 순서대로 묶음씩 입력으로 넘겨주는 것이 전부입니다.
이렇게 딥러닝 공부 2번째 글에서는 신경망의 순전파를 구현해보았습니다. 다음 글에서는 구현한 신경망을 학습하는 방법을 알아보겠습니다.
참고 : 밑바닥부터 시작하는 딥러닝
'프로그래밍' 카테고리의 다른 글
다익스트라 알고리즘(Dijkstra Algorithm) (1) | 2023.07.11 |
---|---|
신경망의 학습(이론편) - 딥러닝 공부 (3) (1) | 2023.06.05 |
퍼셉트론과 신경망 - 딥러닝 공부 (1) (1) | 2023.05.31 |
다항 회귀 (Polynomial Regression) (2) | 2023.05.16 |
(연립) 미분 방정식의 수치적 해법 (2) : 룽게-쿠타 방법(Runge-Kutta Method)과 로트카-볼테라 방정식(Lotka-Volterra Equation) (1) | 2023.04.01 |