1) 입력층 (input layer)
: 데이터셋으로부터 입력 받음
2) 은닉층 (hidden layer)
: 사람 눈에 안 보임
3) 출력층 (output layer)
: 출력 데이터가 나오는 층
실제 연산은 화살표에서 일어남
가중치 갖는 층 2개 뿐 -> '2층 신경망'
몇 층인지 : (입력층 + 은닉층 + 출력층의 개수 - 1)개
=> 인공신경망의 구조 = 퍼셉트론의 구조
b(편향)이 네트워크에 안 보이므로 명시적으로 적어서 표현하기 위해
-> 입력이 1, 가중치가 b인 뉴런 추가 (주의 : 편향 입력 신호는 항상 1임)
전체 동작 과정
-- x1, x2, b 신호가 뉴런에 입력됨
-- 각 신호에 가중치가 곱해짐 : w1x1, w2x2, b1
-- 다음(층의) 뉴런에 전달
-- 다음 뉴런에선 이 신호들을 더함 : w1x1 + w2x2 + b1
-- 총합 > 0 => 1 출력
-- 총합 < 0 => 0 출력
간결한 수식 유도 : 조건 분기의 동장을 h(x)라는 함수로 표현하면
활성화 함수 (activation function)
: 입력신호의 총합 -> 출력 신호로 변환하는 함수
가중치 신호를 조합한 결과 : a라는 뉴런이 됨 -> 활성화함수 h()를 통과해 y라는 뉴련이 됨
(활성화 처리 과정을 명시하여 표현한 그림)
단층 퍼셉트론 : 단층 네트워크에서 계단 함수(임계값을 경계로 출력이 바뀌는 함수)를 활성화 함수로 사용한 모델
다층 퍼셉트론 = 신경망 : #층으로 구성, 시그모이드 함수 등 매끈한 활성화 함수를 사용
=> 단순 퍼셉트론 : 계단 함수 사용
=> 계단 함수를 치환할 함수를 찾자!
: 계단 함수를 스무딩 해줌
- astype() : 자료형 변환할 때 사용
- np.int : 정수형 뜻함
return y.astype(np.int) # y를 bool 배열 -> int 배열로 바꾼 걸 리턴하삼
- bool -> int : True = 1 / False = 0
def step_function(x):
return np.array(x > 0, dtype = np.int)
# np.array로 새로운 넘파이 배열 생성
# 매개변수로 들어온 x 넘파일 배열의 값이 0보다 클 경우 True = 1 / 작을 경우 False = 0이 돼서 새로운 넘파이 배열에 저장됨
# 이 새로운 넘파이 배열이 결과로 반환됨
x = np.arrange(-5.0, 5.0, 0.1) # 매개변수로 넣을 넘파이 배열을 -5.0에서 +5.0까지 증가폭 0.1로 생성함
# ylim으로 y축 범위 설정
💡 np.arange(시작점(생략 시 0), 끝점(미포함), step size(생략 시 1))
- 실수 단위도 표현 가능
- 실행 결과의 수열이 numpy array 형태의 자료형에 들어가 있음
def sigmoid(x):
return 1 / (1 + np.exp(-x))
💡 브로드캐스트 기능 (Broadcasting)
: 서로 다른 차원의 배열 연산하기
- 스칼라 값을 자동으로 행렬 크기를 맞춰 줘서 계산하는 기능
- numpy 는 스칼라는 () 배열로 취급해서 행렬 크기만큼 늘려줘서 스칼라 & 행렬을 연산함
(신경망의) 시그모이드 함수 | (퍼셉트론의) 계단 함수 |
---|---|
부드러운 곡선 & 입력에 따라 출력이 연속적으로 변화 | 급작스럽게 0 -> 1로 변화 |
출력으로 실수를 리턴 | 출력으로 0 & 1만 리턴 |
연속적인 실수의 신호가 흐름 | 0 & 1의 신호만 있음 |
💡 선형 vs. 비선형 함수
- 선형 함수 : 함수에 무언가 입력했을 때 출력이 입력의 상수배만큼 변하는 함수
- f(x) = ax + b
- 1개의 직선으로 나타남
- 비선형 함수(= 선형이 아님) : 직선 1개로 그릴 수 없는 함수
: 입력이 0 넘으면 입력 그대로 출력, 0 이하면 0 출력
2차원 배열(= 행렬)
: 가중치만 가진다고 가정하고 numpy 행렬 이용해서 구현해보자!
X : 입력값 행렬
-> X.shape : (2,) => 12 행렬
W : 가중치 행렬
np.array(X1의 화살표 3개 - 리스트 1개, X2의 화살표 3개 - 리스트 1개) => 2차원 행렬
-> W.shape : (2,3) => 23 행렬
Y : 출력값 행렬
Y = np.dot(X,W) # X x W 행렬곱
< 가중치 변수 표기법 >
< 가중치 행렬 크기 >
: (앞층 뉴런 개수) by (다음층 뉴런 개수) 행렬
< 3층 신경망 구현 최종 코드 >
import numpy as np
def sigmoid_func(x):
return 1 / 1 + np.exp(-x)
def identify_func(x):
return x
def init_network(): # init_network라는 함수 정의 : network 딕셔너리에 가중치&편향 값을 저장해서 초기화해주는 함수
network = dict() # 변수 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.2, 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): # foward 함수 정의 : 진짜로 신경망 구현해주는 부분, 매개변수로 network 딕셔너리, x 행렬 입력 받음
W1, W2, W3 = network['W1'], network['W2'], network['W3'] # 변수 W1, W2, W3에 딕셔너리 각 키의 값을 가리키게 함
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1 # 은닉 1층 행렬곱으로 구현 : a1 = x*W1 + b1
z1 = sigmoid_func(a1) # 은닉 1층 활성화함수 처리
a2 = np.dot(z1, W2) + b2 #은닉 2층 행렬곱으로 구현 : a2 = z*W2 + b2
z2 = sigmoid_func(a2) # 은닉 2층 활성화함수 처리
a3 = np.dot(z2, W3) + b3 # 출력층 구현
y = identify_func(a3) # 출력층의 활성화 함수로 항등함수 사용
return y # 출력값 y 리턴함
network = init_network() # 변수 network는 init_network를 이용해서 초기화해줌
x = np.array([1.0, 0.5]) # 입력변수 x 행렬 선언
y = forward(network, x) # foward 함수로 입력변수 & network로부터 출력변수 y 계산
print(y) # 출력값 보여줌
*) forward 함수 명명; 입력 -> 출력 방향으로 신호 전달되는 것 의미함
어떤 문제를 푸느냐에 따라 출력층에서의 활성화 함수가 달라짐
기계학습 문제의 종류 | 출력층의 활성화 함수 |
---|---|
회귀 | 항등함수 |
분류 | softmax 함수 |
n : 출력층의 뉴런 개수
y_k : 그 중 k번째 출력
구현 시 주의점
=> 오버플로우 방지 방법
: 입력신호 중 최댓값을 빼서 사용
C' : (- 최댓값)
def softmax(a):
c = np.max(a) # 입력 신호의 최댓값
exp_a = np.exp(a - c) # 오버플로 방지위해 최댓값 빼줌
sum_exp_a = np.sum(exp_a) # exp_a 배열의 요소들 총합
y = exp_a / sum_exp_a # 출력값 = 해당 입력 신호의 지수함수값 / 모든 입력 신호의 지수함수값의 총합
return y # 출력값 반환
=> 출력의 총합이 1이라는 성질로 인해서 softmax 함수의 출력을 '확률'로 해석 가능
=> 여기서 y[0]의 확률 = 1.8%, y[1] = 24.5%, y[2] = 73.7%로 해석 가능
=> 이 경우, "세 번째(인덱스 2) Class일 확률이 가장 높으니, 입력 데이터는 세 번째 클래스에 해당한다!"는 결론을 내릴 수 있다.
=> 신경망으로 분류할 때 빠른 결과처리 위해선 일반적으로 softmax 생략함
💡 기계학습 or 신경망의 문제 풀이 과정
1) 학습 : 훈련 데이터(학습 데이터) 사용해 가중치 매개변수를 학습
2) 추론 : 학습한 매개변수 사용해서 입력 데이터를 분류
📌 load_mnist 함수
: 읽은 MNIST 데이터를 "(훈련이미지, 훈련레이블), (테스트이미지, 테스트레이블)" 형식으로 변환
- bool 타입 인수 3가지 : normalize, flatten, one_hot_label 설정
- normalize : 입력값을 0~1.0 사이의 값으로 정규화 할지 여부 결정
- True : 정규화 O / False : 영상의 DN값을 유지- flatten : 입력 이미지를 평탄하게 = 1차원 배열로 만들지 여부 결정
- True : 1차원으로 변환 / False : 기존 배열의 차원 유지- one_hot_label : 레이블(true label)를 원핫인코딩(one-hot encoding) 형태로 저장할지 여부 결정
💡 원-핫 인코딩 (one-hot encoding)
: [0,0,1,0,0,0,0,0,0]처럼 정답을 뜻하는 원소만 1, 나머진 모두 0인 배열을 의미함
피클 : 프로그램 실행 중 특정 객체를 파일로 저장하는 기능
입력층 뉴런 : 784개 & 출력칭 뉴런 : 10개
💡 why 입력층 뉴런 784개?
: 영상(image) 크기가 28*28 = 784 라서
💡 why 출력층 뉴런 10개?
: 숫자가 0~9까지 10개라서
은닉층 : 2개
-- 1번째 은닉층 : 뉴런 50개
-- 2번째 은닉층 : 뉴런 100개
(뉴런 개수는 임의로 설정하는 거임)
import sys, os
sys.path.append(os.pardir)
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax
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
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)))
<각 구문의 역할>
1) get_data() : load_mnist() 함수를 호출해 MNIST 데이터셋(중 테스트 데이터셋)을 로드함
2) init_network() : 피클 파일인 sample_weight.pkl에 저장된 '이미 학습된 가중치 매개변수'를 읽어와서 매개변수(모델의 network)를 로딩함
3) predict(network, x)
4) for문 : predict 함수로부터 출력값 얻음
: 신경망의 입력 데이터에 특정 변환을 가하는 것
: 0~255 영상 DN(digital number)를 0~1의 값으로 정규화하는 과정, 전처리 중 하나임
1) 입력데이터 & 매개변수의 크기 확인
신경망 각층의 배열 크기 변화
(1) 영상(이미지) 1장 이용했을 때
X W1 W2 W3 => Y
(1,784) X (784, 50) X (50, 100) X (100, 10) = (1,10)
(2) 이미지 100장을 한 번에 신경망에 추론해라고 넘겼을 때
X W1 W2 W3 => Y
(100,784) X (784, 50) X (50, 100) X (100, 10) = (100,10)
=> 이렇게 되면 입력 데이터 크기 : 100x784, 출력 데이터 크기 : 100 x 10
=> 100장의 영상 동시 출력 가능
x[0], y[0] ; 각각 1번째 이미지의 flatten array / prediction 결과
...
x[84], y[84] ; 각각 85번째
for문으로 배치 사이즈만큼씩 입력데이터 range로 자르고,
아까 위의 함수에서 t[i] -> t[i:i+batch_size]로 바꾸면 됨
batch_size = 100 # 배치 사이즈
accuracy_cnt = 0
for i in range(0, len(x), batch_size): # 0 ~ len(x)까지 batch_size만큼씩 건너뛰어진 값을 내놓는 iterator 반환
y_batch = predict(network, x[i:i+batch_size]) # 입력 데이터 x를 배치 사이즈 만큼 덩어리로 잘라서 predict 함수에 넣음
p = np.argmax(y_batch, axis=1) # 100*10 행렬 중 1번째 차원을 구성하는 원소 중 최댓값의 인덱스 리턴 해서 p에 저장함
accuracy_cnt += np.sum(p == t[i:i+batch_size])
# ==연산자로 np.array인 p랑 t[i:i+batch_size] 끼리 비교해서 True / False로 구성된 bool 타입 배열 만들고
# 여기서 np.sum으로 True가 몇 개인지 계산(true가 1이니까 1들의 합이 결국 true 개수임)
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))