1. 수학과 파이썬 복습

벡터와 행렬

신경망에서는 '벡터'와 '행렬'(또는 텐서)이 등장한다.

 

벡터: 크기와 방향을 가지고 있는 양

행렬: 매트릭스라고도 하는데 행렬의 가로 줄을 행, 세로 줄을 열이라고 함

 

벡터와 행렬의 예

위 그림처럼 1차원 배열로, 행렬은 2차원 배열로 표현할 수있다. 행렬에서 가로줄은 행 세로줄은 열이라고 한다.

 

[Note]

파이선으로 구현할 때 벡터를 '행벡터'로 취급할 경우, 벡터를 가로 방향 '행렬'로 변환해 사용하면 명확해진다. 예컨대 원소 수가 N개인 벡터라면 1*N 형상의 행렬로 처리한다.

 

행렬의 원소별 연산

코드를 통해 간단하게 살펴보자

W=np.array([[1,2,3],[4,5,6]])
X=np.array([[0,1,2],[3,4,5]])
print(W+X)
print(W*X)

[[ 1  3  5]
 [ 7  9 11]]
[[ 0  2  6]
 [12 20 30]]

 

BroadCast

넘파이의 다차원 배열에서는 형상이 다른 배열끼리도 연산할 수 있다. 코드로 살펴보자

 

A=np.array([[1,2],[3,4]])
print(A*10)

[[10 20]
 [30 40]]
A=np.array([[1,2],[3,4]])
b=np.array([10,20])
A*b

array([[10, 40],
       [30, 80]])

위 2번째 계산에서는 1차원 배열인 b가 2차원 배열 A와 형상이 같아지도록 '영리하게' 확장된다.

 

브로드캐스트 예2

벡터의 내적과 행렬의 곱

2개의 벡터 X=(x1,x2,x3,....,xn) Y=(y1,y2,y3,...,yn)이 있다고 가정하자.

 

내적

X·Y=x1y1+x2y2+...+xnyn

벡터의 내적은 위와 같이 두 벡터에서 대응하는 원소들의 곱을 모두 더한 것이다.

 

행렬의 곱

 

헹렬의 곱셈 방법

위 그림처럼 행렬의 곱은 '왼쪽 행렬의 행벡터(가로방향)'와 '오른쪽 행렬의 열벡터(세로방향)'의 내적(원소별 곱의 합)으로 계산한다.

코드로 살펴보자

 

#벡터의 내적
a=np.array([1,2,3])
b=np.array([4,5,6])
np.dot(a,b)

32

 

#행렬의 곱
A=np.array([[1,2],[3,4]])
B=np.array([[5,6],[7,8]])
np.matmul(A,B)

array([[19, 22],
       [43, 50]])

*matmul은 matrix multiply의 약자

 

행렬 형상 확인

행렬이나 벡터를 사용해 계산할 때는 형상에 주의해야 한다. 

형상 확인: 행렬의 곱에서는 대응하는 차원의 원소 수를 일치시킨다.

[Note] 행렬의 곱 등 행렬을 계산할 때는 형상 확인이 중요하다. 그래야 신경망 구현을 부드럽게 진행할 수 있다.

 

신경망의 추론

신경망 추론 전체 그림

신경망의 예

이미 공부한 것이기에 간단하게 계산법과 그림만 보고 넘어가자. 위 그림은 fully-connected neural network 이다.

 

[첫번째 뉴런 계산(벡터)]

 

[첫번째 뉴련 계산(행렬)]

이를 간소하게 표현하면

*X: input h:hidden layer neuran W:weight b:bias

 

행렬의 곱에서는 대응하는 차원의 원소수가 같아야 한다고 했다. 

형상 확인: 대응하는 차원의 원소 수가 일치함(편향은 생략)

위 그림처럼 형상을 살펴보면 올바른 변환인지를 확인할 수 있다.

[Note]

행렬의 곱 계산에서는 행렬의 형상 확인이 중요하다. 형상을 보면 그 계산이 올바른 계산인지, 적어도 계산이 성립하는지를 확인할 수 있다.

 

형상 확인: 미니배치 버전의 행렬 곱(편향 생략)

위 그림처럼 형상확인을 통해 각 미니배치가 올바르게 변환되었는지를 알 수 있다.

코드로 실습해보면,

W1=np.random.randn(2,4) #가중치
b1=np.random.randn(4) #편향
x=np.random.randn(10,2) #입력
h=np.matmul(x,W1)+b1 
print(h)

[[ 0.35116155  0.94789119 -0.67386652 -0.65909502]
 [ 0.384877   -0.72901409  0.18267204  0.7529613 ]
 [ 0.20165288  0.13624162 -0.09941832  0.00564759]
 [ 1.32167646 -0.20576129 -0.99788172  0.41926761]
 [ 1.26638845 -0.1371531  -0.98095405  0.35541107]
 [ 1.58613619 -0.25255841 -1.2280294   0.48841506]
 [-0.50280274  2.07146982 -0.44627022 -1.69904766]
 [ 0.50183445  0.11793651 -0.37910997  0.05489936]
 [ 4.47713058 -3.74912125 -2.16138615  3.7510847 ]
 [-2.8574282   2.54573098  1.5723183  -2.36307749]]

*위 코드에서 마지막 줄에 편향 b1의 덧셈은 broadcast된다. b1의 형상은 (4,)이지만 자동으로 (10,4)로 복제되는 것.

위 코드는 fully-connected layer이며 변환은 선형변환이다. 

여기서 비선형 효과를 부여하는 것이 바로 활성화 함수이다. 

대표적인 예로 시그모이드함수를 사용하자.

 

시그모이드 함수 그래프

활성화함수에 대해서는 이미 포스팅했으니 넘어가겠다.

undeadkwandoll.tistory.com/16?category=909256

 

Chapter 3-1 신경망

가중치 매개 변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력이 이제부터 살펴볼 신경망의 중요한 성질이다. 3.1 퍼셉트론에서 신경망으로 3.1.1 신경망의 예 신경망을 그림으로 나타내

undeadkwandoll.tistory.com

지금까지 한 것을 종합하여 코딩으로 구현해보자.

 

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

print(s)

[[ 1.37255171  3.33303451  0.51454931]
 [ 1.45573303  3.28327178  0.59098017]
 [ 1.66378388  2.76835385  0.05013013]
 [-0.01466047  2.41627885  0.00654713]
 [ 1.44857064  2.97941464  0.12383537]
 [ 1.07105157  3.19234836  0.20873783]
 [ 1.03450019  2.88402671  0.72500931]
 [ 0.95191827  3.17697541  0.52755519]
 [-0.14788331  2.33162532 -0.11776118]
 [ 1.16358013  3.10202918  0.73084865]]

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

이제 신경망에서 하는 처리를 계층으로 구현해보자. 

완전연결계층: Affine계층으로 변환 활성화함수: Sigmoid 기본변환 메서드: forward()

구현 규칙:

-모든 계층은 forward()와 backward() 메서드를 가진다.

-모든 계층은 인스턴스 변수인 params와 grads를 가진다.

 

forward()와 backward()메서드는 각각 순전파와 역전파를 수행한다. params는 가중치와 편향 같은 매개변수를 담는 리스트이다. grads는 params에 저장된 각 매개변수에 대응하여, 해당 매개변수의 기울기를 보관하는 리스트이다.

 

이번에는 순전파만 구현할 것이므로 앞의 구현 규칙 중 다음 두 사항만 적용한다.

1. 각 계층은 forward() 메서드만 가진다.

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

 

코드를 통해 살펴보자.

import numpy as np

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

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

위 두 클래스의 주 변환처리는 forward(x)가 담당한다.

Sigmoid 계층에는 학습하는 매개변수가 따로 없으므로 인스턴스 변수인 params는 빈 리스트로 초기화한다.

Affine 계층은 초기화될 때 가중치와 편향을 받는다. 즉, 가중치와 편향은 Affine 계층의 매개변수이며, 리스트인 params인스턴스 변수에 보관한다.

 

다음은 TwoLayerNet을 구현해보자.

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
    

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

[[-1.63600236 -0.58829572  2.12459972]
 [-1.29363917 -0.62995628  2.00383472]
 [-2.18231262 -0.51745636  2.29597345]
 [-0.45702306 -0.56597897  1.78600372]
 [-1.63980911 -0.71859781  2.13101938]
 [-1.95776705 -0.89778093  2.23616852]
 [-1.31212998 -1.19684139  2.04171904]
 [-0.38606698 -0.6532667   1.78814915]
 [-0.96887503 -0.71086643  1.90366492]
 [-1.4403579  -0.96416926  2.07110086]]

이상으로 입력 데이터 x에 대한 점수(s)를 구할 수 있었다. 이처럼 계층을 클래스로 만들어두면 신경망을 쉽게 구현할 수 있다. 

+ Recent posts