밑바닥부터 시작하는 딥러닝 - 신경망(2)

지난 글에 이어 신경망에 대해 알아볼 것이다. 신경망에서 수행하는 작업은 크게 두 단계로 나눌 수 있다.

'학습'과 '추론'이다. 이번 글에서는 '추론'에 대해 알아볼 것이다.

신경망의 추론

신경망 추론 그림

입력층에는 뉴런 2개, 출력층에는 뉴런 3개를 각각 준비한다. 그리고 은닉층에도 적당한 수의 뉴런을 배치한다. 여기서는 4개의 뉴런을 두기로 한다. 위의 그림의 신경망은 인접하는 층의 모든 뉴런과 연결되어 있다는 뜻에서 완전연결계층이라고 한다.

x는 입력, h는 은닉층의 뉴런, W는 가중치, b는 편향을 뜻한다. 이 기호 각각은 모두 행렬이다.

# 완전연결계층에 의한 변환의 미니배치 버전 구현

import numpy as np
W1 = np.random.randn(2, 4)
b1 = np.random.randn(4)
x = np.random.randn(10, 2)
h = np.matmul(x, W1) + b1

위 예에서는 10개의 샘플 데이터 각각을 완전연결계층으로 변환시켰다. 이때 x의 첫 번째 차원이 각 샘플 데이터에 해당한다.

 

하지만 완전연결계층에 의한 변환은 '선형' 변환이다. 여기에 '비선형' 효과를 부여하는 것이 바로 활성화 함수이다.

여기에서는 활성화 함수로 시그모이드 함수를 사용할 것이다.

 

시그모이드 함수는 임의의 실수를 입력받아 0에서 1 사이의 실수를 출력한다. 이 시그모이드 함수를 파이썬으로 구현해보자.

# 시그모이드 함수 구현
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
    
a = sigmoid(h)

시그모이드 함수를 사용하여 은닉층 뉴런을 변환하였다.

이로써 비선형 변환이 가능해졌다.

계속해서 이 활성화 함수의 출력인 a를 또 다른 완전연결계층에 통과시켜 변환한다.

지금 예에서는 은닉층의 뉴런은 4개, 출력층의 뉴런은 3개이므로 완전연결계층에 사용되는 가중치 행렬은 4 X 3 형상으로 설정해야 한다. 이것으로 출력층의 뉴런을 얻을 수 있다.

이것을 종합하여 코드로 작성해보자.

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

x = np.random.randn(10, 2)
W1 = np.random.randn(2, 4)
b1 = np.random.randn(4)
W2 = np.random.randn(4, 3)
b2 = np.random.randn(3)

h = np.matmul(x, W1) + b1
a = sigmoid(h)
s = np.matmul(a, W2) + b2

여기에서 x의 형상은 (10, 2)이다. 즉, 2차원 데이터 10개가 미니배치로 처리된다는 뜻이다.

그리고 최종 출력인 s의 형상은 (10, 3)이 된다. 다시 말하면, 10개의 데이터가 한꺼번에 처리되었고, 각 데이터는 3차원 데이터로 변환되었다.

이 신경망은 3차원 데이터를 출력한다. 따라서 각 차원의 값을 이용하여 3 클래스 분류를 할 수 있다. 이 경우, 출력된 3차원 벡터의 각 차원은 각 클래스에 대응하는 점수가 된다.

실제로 분류를 한다면 출력층에서 가장 큰 값을 내뱉는 뉴런에 해당하는 클래스가 예측 결과가 된다.

 


계층으로 클래스화 및 순전파 구현

지금까지 신경망 추론을 구현해봤다. 지금부터는 수행한 처리를 하나의 '계층'으로 추상화한 파이썬 클래스를 구현해 볼 것이다.

  • 완전연결계층에 의한 변환을 Affine 계층으로, 시그모이드 함수에 의한 변환을 Sigmoid 계층으로 구현할 것이다.
  • 순전파 : 입력층에서 출력층으로 향하는 전파
  • 역전파 : 순전파와는 반대 방향으로 전파

신경망에는 다양한 계층이 등장한다. 이 글에서는 이 계층들을 모두 파이썬 클래스로 구현할 것이다. 이렇게 모듈화를 해두면 신경망을 구축하기 쉬워진다. 또한 '구현 규칙'을 따를 것이다.

  • 모든 계층은 forward()와 backward() 메서드를 가진다.
  • 모든 계층은 인스턴스 변수인 params와 grads를 가진다.

위의 2가지는 구현 규칙이다. forward()와 backward() 메서드는 각각 순전파와 역전파를 수행한다.

params는 가중치와 편향 같은 매개변수를 담는 리스트이다. 마지막으로 grads는 params에 저장된 각 매개변수에 대응하여, 해당 매개변수의 기울기를 보관하는 리스트이다.

 

import numpy as np

class Sigmoid:
    def __init__(self):
        self.params = []
    
    def forward(self, x):
        return 1 / (1 + np.exp(-x))

순전파만 구현한 것이다.

첫째, 각 계층은 forward() 메서드만 가진다.

둘째, 매개변수들은 params 인스턴스 변수에 보관한다.

 

그럼 계속해서 완전연결계층인 Affine 계층을 구현해보자.

class Affine:
    def __init__(self, W, b):
        self.params = [W, b]
    
    def forward(self, x):
        W, b = self.params
        out = np.matmul(x, W) + b
        return out

Affine 계층은 초기화될 때 가중치와 편향을 받는다. 가중치와 편향은 Affine 계층의 매개변수이며, 리스트인 params 인스턴스 변수에 보관한다. 다음으로 forward(x)는 순전파 처리를 구현한다.

아래 그림처럼 구성된 신경망을 구현할 것이다.

이번 예에서는 입력 x가 Affine 계층, Sigmoid 계층, Affine 계층을 차례로 거쳐 점수인 s를 출력하게 된다.

이 신경망을 TwoLayerNet이라는 클래스로 추상화하고 주 추론 처리는 predict(x) 메서드로 구현한다.

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size):
        I, H, O = input_size, hidden_size, output_size
        
        # 가중치와 편향 초기화
        W1 = np.random.randn(I, H)
        b1 = np.random.randn(H)
        W2 = np.random.randn(H, O)
        b2 = np.random.randn(O)
        
        # 계층 생성
        self.layers = [
            Affine(W1, b1),
            Sigmoid(),
            Affine(W2, b2)
        ]
        
        # 모든 가중치를 리스트에 모음
        self.params = []
        for layer in self.layers:
            self.params += layer.params
            
    def predict(self, x):
        for layer in self.layers:
            x = layer.forward(x)
        return x

이 클래스의 초기화 메서드(__init__)는 먼저 가중치를 초기화하고 3개의 계층을 생성한다.

마지막으로 학습해야 할 가중치 매개변수들을 params 리스트에 저장한다. 모든 계층은 자신의 학습 매개변수들을 인스턴스 변수인 params에 보관하고 있으므로, 이 변수들을 더해주기만 하면 된다.

 

이로써 TwoLayerNet의 params 변수에는 모든 학습 매개변수가 담기게 되었다.

간단한 예를 보자.

x = np.random.randn(10, 2)
model = TwoLayerNet(2, 4, 3)
s = model.predict(x)
s

이상으로 입력 데이터 x에 대한 점수 s를 구할 수 있었다. 이처럼 계층을 클래스로 만들어두면 신경망을 쉽게 구현할 수 있다. 또한 학습해야 할 모든 매개변수가 model, params라는 하나의 리스트에 모여 있으므로 이어서 진행할 신경망 학습이 수월해질 것이다.

 

지금까지 신경망 추론을 알아봤다.

학습되지 않은 신경망은 '좋은 추론'을 해낼 수 없다. 그래서 학습을 먼저 수행하고, 그 학습된 매개변수를 이용해 추론을 수행하는 흐름이 일반적이다. 추론이란 지금까지 했던 다중 클래스 분류 등의 문제에 답을 구하는 작업이다. 한편, 신경망의 학습은 최적의 매개변수 값을 찾는 작업이다.

 

다음 글에서는 신경망의 학습에 대해 알아볼 것이다.

TAGS.

Comments