본문 바로가기
Deep Learning

[딥러닝] 밑바닥부터 시작하는 딥러닝 공부 3-2 : 행렬 곱연산과 신경망 구현

by 단깅수 2024. 2. 11.
728x90

 이번에 소개할 내용은 단층 퍼셉트론의 한계를 해결한 행렬곱연산과 신경망구현에 대한 이야기입니다.

 

저번 포스팅 내용은 신경망과 활성화함수에 대해서였습니다.

2024.01.31 - [Deep Learning] - [딥러닝] 밑바닥부터 시작하는 딥러닝 공부 3-1 : 신경망과 활성화 함수

 

[딥러닝] 밑바닥부터 시작하는 딥러닝 공부 3-1 : 신경망과 활성화 함수

이번에 소개할 내용은 단층 퍼셉트론의 한계를 해결한 인공신경망과 활성화함수에 대한 이야기입니다. 저번 포스팅 내용은 퍼셉트론의 한계 및 다중 퍼셉트론에 대해서였습니다. 2024.01.24 - [Deep

dangingsu.tistory.com

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