728x90
이번에 소개할 내용은 단층 퍼셉트론의 한계를 해결한 행렬곱연산과 신경망구현에 대한 이야기입니다.
저번 포스팅 내용은 신경망과 활성화함수에 대해서였습니다.
2024.01.31 - [Deep Learning] - [딥러닝] 밑바닥부터 시작하는 딥러닝 공부 3-1 : 신경망과 활성화 함수
1. 다차원 배열의 계산
- 넘파이 패키지의 다차원 배열을 사용한 계산법을 활용하면 신경망을 구현할 때 효율적인 구현이 가능합니다.
- 그래서 이번 포스팅에서는 넘파이의 다차원 배열 계산, 즉 행렬곱연산에 대해서 설명한 뒤 신경망 구현까지 해보겠습니다.
1-1. 다차원 배열
- 다차원 배열이란?
- 다차원 배열이란 '숫자의 집합'입니다.
- 숫자가 한 줄로 늘어선 형태나 직사각형으로 늘어놓은 형태, 3차원 등 N차원으로 나열하는 형태 모두를 통틀어 다차원 배열이라고 합니다.
- 1차원 배열 구현
- Numpy 배열의 차원수는 np.ndim() 함수로 확인할 수 있습니다.
- 배열의 크기는 인스턴스 변수인 shape로 알 수 있습니다.
import numpy as np
A = np.array([1, 2, 3, 4])
print('A :', A)
print('A의 차원 :', np.ndim(A))
print('A의 크기 :', A.shape)
print('A의 행 :', A.shape[0])
- 2차원 배열 구현
- 3x2 배열인 B를 작성했습니다.
- 2차원 배열은 '행렬'이라고 부르고 가로 방향을 행, 세로 방향을 열이라고 합니다.
B = np.array([[1, 2], [3, 4], [5, 6]])
print(B)
print('B의 차원 :', np.ndim(B))
print('B의 크기 :', B.shape)
1-2. 행렬 곱연산
- 아래 그림은 행렬의 곱 연산을 이미지화시킨 형태입니다.
- 그림에서처럼 행렬 곱은 왼쪽 행렬의 행과 오른쪽 행렬의 열을 원소별로 곱하고 그 값을 더해서 계산합니다.
- 선형대수를 배웠다면 이 개념이 쉽게 와 닿을 수 있지만 전공생이 아니라면 헷갈릴 수 있으니 행렬곱연산을 이해하는데 시간을 많이 쏟는 걸 추천드립니다.
- 행렬 곱연산 코드 구현
- 파이썬에서 행렬 곱을 구현할 때에는 np.dot() 함수를 이용하면 구현할 수 있습니다.
- np.dot() 함수는 입력이 1차원 배열이면 벡터를, 2차원 배열이면 행렬 곱을 계산합니다.
- 여기서 주의할 점은 A와 B의 순서를 바꾼다면 다른 결과가 나올 수 있다는 점입니다.
A = np.array([[1, 2], [3, 4]])
print('A의 크기 :', A.shape)
B = np.array([[5, 6], [7, 8]])
print('B의 크기 :', B.shape)
# 행렬 곱연산
np.dot(A, B)
- 행렬의 형상
- 행렬 곱연산 시에 주의할 점은 바로 행렬의 형상입니다.
- 즉, 행렬 A의 열과 행렬 B의 행이 같아야 합니다.
- 이 값이 다르다면 행렬의 곱을 계산할 수 없습니다.
- 아래 예시처럼 2X3 행렬 A와 2X2 행렬 C를 곱하면 다음과 같은 오류를 출력합니다.
- 즉, 다차원 배열을 곱하려면 두 행렬의 대응하는 차원의 원소 수를 일치시켜야 합니다.
A = np.array([[1, 2, 3], [4, 5, 6]])
print('A의 크기 :', A.shape)
B = np.array([[1, 2], [3, 4], [5, 6]])
print('B의 크기 :', B.shape)
np.dot(A, B)
C = np.array([[1, 2], [3, 4]])
print('C의 크기 :', C.shape)
print(np.dot(B, C))
print(np.dot(A, C))
2. 행렬곱과 함께 신경망 구현하기
- 이번에는 입력부터 출력까지의 처리(순방향 처리)를 구현해보겠습니다.
- 넘파이 배열을 잘 활용하면 아주 적은 코드만으로 신경망의 순방향 처리를 완성할 수 있습니다.
- 활성화함수 처리가 추가된 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) # [0.3, 0.7, 1,1]
print(Z1) # [0.5744... / 0.66818... / 0.7502...]
- 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) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
- 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) # 혹은 Y = A3
print(Y) # [0.3168... / 0.6963...]
3. 전체 구현 정리
# 전체 코드 정리
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.3, 0.5])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.3, 0.2])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.4, 0.2])
return network
def sigmoid(x):
return 1 / (1 + np.exp(-x))
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) # [0.6228... / 0.7132...]
이렇게 오늘은 행렬곱연산과 신경망구현에 대해서 알아보았습니다.
다음 포스팅은 손글씨 숫자 인식에 대해서 다룰 예정입니다.
즐거운 설 보내세요.
728x90
'Deep Learning' 카테고리의 다른 글
[딥러닝] CNN, 합성곱 신경망 (0) | 2024.07.08 |
---|---|
[딥러닝] 밑바닥부터 시작하는 딥러닝 공부 3-2 : MNIST 손글씨 숫자 인식 및 구현 (0) | 2024.02.17 |
[딥러닝] 밑바닥부터 시작하는 딥러닝 공부 3-1 : 신경망과 활성화 함수 (1) | 2024.01.31 |
[딥러닝] 밑바닥부터 시작하는 딥러닝 공부 2-2 : 퍼셉트론의 한계 및 다중 퍼셉트론 (0) | 2024.01.24 |
[딥러닝] 밑바닥부터 시작하는 딥러닝 공부 2-1 : 퍼셉트론이란? (0) | 2024.01.19 |