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개의 벡터 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)를 구할 수 있었다. 이처럼 계층을 클래스로 만들어두면 신경망을 쉽게 구현할 수 있다.