by 나다경
가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력이 신경망의 중요한 성질임.
가장 왼쪽 줄을 입력층, 맨 오른쪽 줄을 출력층, 중간 줄을 은닉층이라고 함
$$ y = 0 (w1x1 + w2x2) <= theta $$ $$ y = 1 (w1x1 + w2x2) > theta $$ 을 간결한 형태로 표현하면
$$ y = h(b + w1x1 + w2x2) $$ $$ h(x) = 0 (x <= 0 ) $$ $$ h(x) = 1 (x > 0) $$
여기서 $h(x)$라는 함수는 입력 신호의 총합을 출력 신호로 변환하는 함수이며 활성화 함수라고 함.
위의 활성화 함수는 임계값을 경계로 출력이 바뀌는데, 이를 계단 함수라고 함.
따라서 퍼셉트론에서는 활성화 함수로 계단 함수를 이용한다. 라 할 수 있음.
시그모이드 함수는 신경망에서 자주 이용하는 활성화 함수임.
$S(x) = \frac{1}{1 + e^{-x}}$
신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달함.
퍼셉트론과 신경망의 주된 차이는 활성화 함수 뿐임.
계단 함수 구현하기
def step_function(x):
if x > 0 :
return 1
else:
return 0
넘파이 배열 지원하기 위해 아래와 같이 수정함
def step_function(x):
y = x > 0
return y.astype(np.int)
import numpy as np
x = np.array([-1.0, 1.0, 2.0])
print(x) # [-1. 1. 2.]
y = x > 0
print(y) # [False True True]
y = y.astype(np.int)
print(y) # [0 1 1]
import numpy as np
import matplotlib.pylab as plt
def step_function(x):
return np.array(x > 0, dtype = np.int)
x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x,y)
plt.ylim(-0.1, 1.1)
plt.show()
시그모이드 함수 구현하기
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.array([-1.0, 1.0, 2.0])
sigmoid(x) # array([0.26894142, 0.73105858, 0.88079708])
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x,y)
plt.ylim(-0.1, 1.1)
plt.show()
시그모이드 함수와 계단 함수 비교
시그모이드 함수는 부드러운 곡선이며 입력에 따라 출력이 연속적으로 변화함.
계단 함수는 0을 경계로 출력이 갑자기 바뀜.
퍼셉트론에서는 뉴런 사이에 0 혹은 1이 흘렀다면, 신경망에서는 연속적인 실수가 흐름.
계단 함수와 시그모이드 함수는 입력이 중요하지만 큰 값을 출력하고 입력이 중요하지 않으면 작은 값을 출력함.
입력이 아무리 작거나 커도 출력은 0에서 1사이임.
비선형 함수임.
ReLU 함수
ReLU는 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력하는 함수임.
$$ h(x) = x (x > 0) $$ $$ h(x) = 0 (x <= 0) $$
def relu(x):
return np.maximum(0,x)
다차원 배열
import numpy as np
A = np.array([1, 2, 3, 4])
print(A) # [1 2 3 4]
# 차원 수
np.ndim(A) # 1
# 배열의 형상
A.shape # (4,)
A.shape[0] # 4
B = np.array([[1,2], [3,4], [5,6]])
print(B)
#[[1 2]
[3 4]
[5 6]]
np.ndim(B) # 2
B.shape # (3, 2)
2차원 배열은 특히 행렬이라고 함.
배열의 가로 방향을 행, 세로 방향을 열이라고 함.
행렬의 곱
np.dot()으로 계산함
A = np.array([[1,2],[3,4]])
A.shape # (2,2)
B = np.array([[5,6],[7,8]])
B.shape # (2,2)
np.dot(A, B)
# array([[19, 22],
[43, 50]])
A = np.array([[1,2,3],[4,5,6]])
A.shape # (2, 3)
B = np.array([[1,2],[3,4],[5,6]])
B.shape # (3, 2)
np.dot(A,B)
# array([[22, 28],
[49, 64]])
C = np.array([[1,2],[3,4]])
C.shape
# (2, 2)
A.shape # (2, 3)
np.dot(A,C) # 에러 shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)
행렬 A의 1번째 차원의 원소 수(열 수)와 행렬 B의 0번째 차원의 원소 수(행 수)가 같아야만 함.
A = np.array([[1,2],[3,4],[5,6]])
A.shape # (3, 2)
B = np.array([7,8])
B.shape # (2,)
np.dot(A,B) # array([23, 53, 83])
신경망에서의 행렬 곱
X = np.array([1,2])
X.shape # (2,)
W = np.array([[1,3,5],[2,4,6]])
print(W)
# [[1 3 5]
[2 4 6]]
W.shape # (2, 3)
Y = np.dot(X,W)
print(Y) # [ 5 11 17]
# 입력층 -> 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) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X,W1) + B1
활성화 함수로 시그모이드 함수 사용
Z1 = sigmoid(A1)
print(A1) # [0.3 0.7 1.1]
print(Z1) # [0.57444252 0.66818777 0.75026011]
# 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)
구현 정리
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.2,0.3])
network['W2'] = np.array([[0.1,0.4],[0.5,0.5],[0.3,0.6]])
network['b2'] = np.array([0.1,0.2])
network['W3'] = np.array([[0.1,0.3],[0.2,0.4]])
network['b3'] = np.array([0.1,0.2])
return network
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.32138828 0.70996271]
항등 함수와 소프트맥스 함수 구현하기
항등 함수 : 입력을 그대로 출력함, 출력층에서 항등함수를 사용하면 입력 신호가 그대로 출력 신호가 됨.
소프트맥스 함수 : 소프트맥스의 출력은 모든 입력 신호로부터 화살표를 받음. 출력층의 각 뉴런이 모든 입력 신호에서 영향을 받음.
a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a) # 지수 함수
print(exp_a) # [ 1.34985881 18.17414537 54.59815003]
sum_exp_a = np.sum(exp_a)
print(sum_exp_a) # 74.1221542101633
y = exp_a / sum_exp_a
print(y) # [0.01821127 0.24519181 0.73659691]
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
소프트맥스 함수 구현 시 주의점
오버플로 문제가 발생 -> $C$라는 임의의 정수를 분자와 분모 양쪽에 곱하고 지수함수 안으로 옮겨 $log C$로 만든 후 $C'$이라는 새로운 기호로 만듦
a = np.array([1010, 1000, 990])
np.exp(a) / np.sum(np.exp(a)) # 소프트맥스 함수의 계산 # array([nan, nan, nan])
제대로 계산되지 않음
c = np.max(a)
a - c # array([ 0, -10, -20])
np.exp(a-c)/np.sum(np.exp(a-c)) # array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09])
def softmax(a):
c = np.max(a)
exp_a = np.exp(a-c)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
소프트맥스 함수의 특징
a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y) # [0.01821127 0.24519181 0.73659691]
np.sum(y) # 1.0
소프트맥스 함수 출력의 총합은 1임
소프트맥스 함수의 출력을 확률로 해석할 수 있음. 즉, 소프트맥스 함수를 이용함으로써 문제를 확률적(통계적)으로 대응할 수 있게 됨.
주의점
소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않음.
신경망을 이용한 분류에서는 일반적으로 가장 큰 출력을 내는 뉴런에 해당하는 클래스로만 인식함.
소프트맥스 함수를 적용해도 출력이 가장 큰 뉴런의 위치는 달라지지 않음
결과적으로 신경망으로 분류할 때는 출력층의 소프트맥스 함수를 생략해도 됨
import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
# 각 데이터의 형상 출력
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)
load_mnist 함수는 읽은 MNIST 데이터를 (훈련 이미지, 훈련 레이블), (시험 이미지, 시험 레이블) 형식으로 반환함.
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image # 5
print(img.shape) # (784,)
img = img.reshape(28,28)
flatten=True로 설정해 읽어 들인 이미지는 1차원 넘파이 배열로 저장되어 있기 때문에 이미지를 표시할 때는 원래 형상인 28x28 크기로 다시 변형해야 함
신경망의 추론 처리
입력층 뉴런을 784개(이미지 크기가 28x28)로 구성하고 출력층 뉴런을 10개(0~9까지의 숫자를 구분)로 구성함
import pickle
def get_data():
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
return x_test, t_test
def init_network():
with open('sample_weight.pkl', 'rb') as f:
network = pickle.load(f)
return network
def predict(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 = softmax(a3)
return y
init_network()에서는 pickle 파일인 sample_weight.pkl에 저장된 학습된 가중치 매개변수 를 읽음
가중치와 편향 매개변수가 딕셔너리 변수로 저장되어 있음
x, t = get_data()
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
y = predict(network, x[i])
p = np.argmax(y) # 확률이 가장 높은 원소의 인덱스를 얻음
if p == t[i]:
accuracy_cnt += 1
print('Accuracy:' + str(float(accuracy_cnt) / len(x))) # Accuracy:0.9352
predict()
함수는 각 레이블의 확률을 넘파이 배열로 반환함
np.argmax()
함수로 이 배열에서 값이 가장 큰(확률이 가장 높은) 원소의 인덱스를 구함
신경망이 예측한 답변과 정답 레이블을 비교하여 맞힌 숫자(accuracy_cnt
)를 세고, 전체 이미지 숫자로 나눠 정확도를 구함
정규화 : 데이터를 특정 범위로 변환하는 처리
전처리 : 신경망의 입력 데이터에 특정 변환을 가하는 것
배치 처리
# 가중치 형상
x, _ = get_data()
network = init_network()
W1, W2, W3 = network['W1'], network['W2'], network['W3']
x.shape # (10000, 784)
W1.shape # (784, 50)
W2.shape # (50, 100)
W3.shape # (100, 10)
다차원 배열의 대응하는 차원의 원소 수가 일치함을 확인할 수 있음.
원소 784개로 구성된 1차원 배열이 입력되어 마지막에는 원소가 10개인 1차원 배열이 출력되는 흐름임.
이미지 100장을 한꺼번에 입력하는 경우 입력 데이터의 형상은 100x784, 출력 데이터의 형상은 100x10임.
배치 : 하나로 묶은 입력 데이터
x, t = get_data()
network = init_network()
batch_size = 100 # 배치 크기
accuracy_cnt = 0
for i in range(0, len(x), batch_size):
x_batch = x[i:i+batch_size]
y_batch = predict(network, x_batch)
p = np.argmax(y_batch, axis=1)
accuracy_cnt += np.sum(p == t[i:i+batch_size])
print('Accuracy:', str(float(accuracy_cnt) / len(x))) # Accuracy: 0.9352
x[i:i+batch_size]은 입력 데이터의 i번째부터 i+batch_size번째까지의 데이터를 묶는다는 의미임 e.g)x[0:100], x[100:200]
argmax()에 axis=1 인수를 추가한 것은 100x10 배열 중 1번째 차원을 구성하는 각 원소에서 최댓값의 인덱스를 찾도록 한 것임
데이터를 배치로 처리함으로써 효율적이고 빠르게 처리 가능