2021. 9. 27. 13:23ㆍAI/Deep learning
- 목차
CHAPTER 3 신경망
Perceptron은 weight의 설정을 수동으로 해야 함
신경망은 자동으로 설정함
입력층 은닉층 출력층
x1 --------> s1-\
\ /+---> \+--> y
\ +-->
/ \+----> /
x2 --------> s2 -/
그림 3-1 (p64)
입력층 은닉층 출력층
()
() ()
()
() ()
()
각 층들의 node들은 다음층의 모든 node로 출력을 내보내는 구조
은닉층은 사람 눈에는 보이지 않기에 '은닉'
신경망은 모두 3층으로 구성됨
가중치를 갖는 층은 2개뿐이라 2층 신경망이라고 함
(3층 신경망으로 부르기도 함)
Perceptron에서와 특별히 다른 것이 없음
Perceptron 복습
w1
(x1)-------->
w2 (y)
(x2)-------->
x1과 x2라는 두 신호를 입력받아 y를 출력하는 perceptron
y = 0 if b + w1x1 + w2x2 <= 0
1 if b + w1x1 + w2x2 > 0
b는 bias (뉴런의 활성화 정도를 제어)
w1과 w2는 가중치 - 각 신호의 영향력을 제어
그런데 위 그림에서는 b가 없음
b를 명시한다면 다음과 같은 그림이 되어야 함
b
(1) -------->
w1
(x1)-------->
w2 (y)
(x2)-------->
y = h(b + w1x1 + w2x2)
h(x) = 0 if x <= 0
= 1 if x > 0
활성화 함수
h(x)
활성화 함수 (activation function)
입력신호의 총합이 활성화를 일으키는지를 정하는 역할 수행
위 식을 다시 써보면,
a = b + w1x1 + w2x2
y = h(a)
b
(1) -------->
w1 +---------------+
(x1)--------> | h() |
w2 | (a)-----> (y) |
(x2)--------> +---------------+
가중치 신호를 조합한 결과가 a라는 node
활성화 함수 h()를 통해 y라는 node로 변환되는 과정
하나의 node를 자세하게 표현하기도 함(활성화 처리 과정 명시)
단순 perceptron
단층 network에서 계산 함수(임계값을 경계로 출력이 바뀌는 함수)를 활성화 함수로 사용한 모델
다층 perceptron
신경망(여러 층으로 구성되고 sigmoid 함수 등의 매끈한 활성화 함수를 사용하는 network)
활성화 함수
Perceptron에서는 setp function을 활성화 함수로 이용함
이 활성화 함수를 계산 함수가 아닌 다양한 것을 사용하는 것이 신경망의 세계로 나아가는 열쇠
sigmoid function
h(x) = 1/(1 + exp(-x))
exp(-x)는 e의 -x승
e는 자연상수 2.7182... (실수)
h(1) = 0.731
h(2) = 0.880
...
ex. step function implementation
def step_function(x):
if x > 0:
return 1
else:
return 0
or
np.array 처리를 위해 다음과 같이 처리 가능
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])
>>> x
array([-1., 1., 2.])
>>> y = x > 0
>>> y
array([False, True, True], dtype=boool)
numpy 배열에 부등호 연산을 수행하면 배열의 원소 각각에 부등호 연산을 수행한 bool 배열이 생성됨
즉, 위 y는 bool 배열임
>>> y = y.astype(np.int)
>>> y
array([0, 1, 1])
>>>
astype()을 사용해서 자료형을 변환할 수 있음
bool을 int로 변환하면 False는 0으로 변환됨
계단 함수의 graph
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, np.float)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
1.0 | +------
| |
| |
| |
| |
0.0 | ------------------+
+-----------------------------
0
0을 경계로 출력이 0에서 1로 혹은 1에서 0으로 바뀜
값을 바꾸는 형태가 계단 형태이기 때문에 계단함수임
sigmoid 함수 구현
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])
broadcast 기능
배열과 scalar의 연산을 넘파이 배열의 원소 각각과 scalar의 연산으로 바꿔 수행하는 것
>>> t = np.array([1.0, 2.0, 3.0])
>>> 1.0 + t
array([ 2., 3., 4.])
>>> 1.0 / t
array([ 1. , 0.5 , 0.33333333])
결과적으로 scalar값과 넘파이 배열의 각 원소 사이에서 연산이 이뤄짐
연산 결과가 넘파이 배열로 출력
>>> x = np.arange(-5.0, 5.0, 0.1)
>>>
>>> y = sigmoid(x)
>>> plt.plot(x, y)
[<matplotlib.lines.Line2D object at 0x000002157C59E198>]
>>> plt.ylim(-0.1, 1.1)
(-0.1, 1.1)
>>> plt.show()
1.0 | . ..
| .
| .
| .
0.0 | ..-
+----------------
그림 3-7 (p73)
sigmoid란 'S자 모양'이라는 뜻
sigmoid 함수와 step function의 비교
매끄러움
둘 다 비선형
선형함수
출력이 입력의 상수배만큼 변하는 함수
신경망에서는 선형으로 하게되면 다양한 동작을 수행할 수 없기에 사용하지 않음
ex.
h(x) = cx
y(x) = h(h(h(x)))여도 y(x) = c * c* c* x이니 즉 c^3 * x가 됨
이는 은닉층 없이 가중치의 변경으로 동일하게 표현 가능하므로 신경망이 필요 없음
ReLU (Rectified Linear Unit) function
최근에는 sigmoid 보다 ReLU 함수를 주로 이용함
5 |
| /
4 | /
| /
3 | /
| /
2 | /
| /
1 | /
| /
0 |------------/
|
-1+-------------------------------
0 5
h(x) = x if x > 0
0 if x <= 0
def relu(x):
return np.maximum(0, x)
maximu은 두 입력 중 큰 값을 반환하는 함수
다차원 배열의 계산
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
2차원 배열
>>> 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)
3x2 배열인 B
처음 차원에는 원소가 3개
다음 차원에는 원소가 2개
1, 2 <-- 행
3 4
5 6
^
|
열
행렬의 내적 (행렬 곱)
---> |
|
v
>>> 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와 B는 2x2 행렬
이들 두 행렬의 내적은 넘파이 함수 np.dot으로 계산 가능
행렬에서 AxB와 BxA는 다름
2x3 행렬과 3x2 행렬 곱
>>> 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]])
A의 열수와 B의 행수가 같아야 계산 됨 (맞지 않으면 오류 출력)
3 x 2 2 x 4 = 3 x 4
3행 2열 2행 4열 3행 4열
() () () () () ()
() () () () () ()
() ()
신경망의 내적
편향과 활성화 함수를 생략하고 간단한 신경망의 내적 수행하기
(x1) (y1)
(x2) (y2)
(y3)
x들에서 y들로 연결되는 edge들은 6개이며 각각 위부터 edge에는 1, 2, 3, 4, 5, 6와 같은 weight가 있음
>>> X = np.array([1, 2])
>>> X.shape
(2,)
>>> W = np.array([[1, 3, 5], [2, 4, 6]])
>>> W.shape
(2, 3)
>>> print(W)
[[1 3 5]
[2 4 6]]
>>> Y = np.dot(X, W)
>>> print(Y)
[ 5 11 17]
np.dot을 사용하면 이처럼 행렬 내적곱으로 간단하게 각 입력에 대한 가중치 별 output을 구할 수 있음
3층 신경망 구현하기
입력층 2개 은닉층 (1층) 3개 두 번째 은닉층 (2개) 출력층 (3층) 2개의 뉴런으로 구성
() () () ()
() () () ()
()
w11 (1)
(x1)-------(a1) ---
\
\ w12
\
\+--(a2)
(1)
w12의 의미 앞층의 2번 뉴런에서 다음층의 1번 뉴런 (안의 수는 가중치)
(1)+- (1)
\ b1 (1)
\+-------> (a1)
+------->
/ (1) /
(x1)+- w11 /
+--/ (1)
/ w12
(x2)----+
(1)
a1 = w(1)11*x1 + x(1)12*x2 + b(1)1
A(1) = XW(1) + B(1)
A(1) = (a(1)1, a(1)2, a(1)3)
X = (x1, x2)
B(1) = (b(1)1, b(1)2, b(1)3)
W(1) = (w(1)11, w(1)21, W(1)31)
(w(1)12, w(1)22, W(1)32)
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)
print(X.shape)
print(B1.shape)
A1 = np.dot(X, W1) + B1
1층의 활성화 함수
b(1)1 h()
(1) -----------> [ a(1)1 ---> z(1)z ]
w(1)11
(x1)----------->
Z1 = sigmoid(A1)
print(A1) #[0.3, 0.7, 1.1]
print(Z1) #[0.57444252, 0.66818777, 0.75026011]
(1)---------+ (1)----------+
\ b(1)1 \ b(3)1
b(1)1 h() \ h() \
\ \+-> sigmoid()
(1) -----------> [ a(1)1 ---> z(1)z] +--------> [ a(2)1 -----> z(2)1 ] ----> [ a(3)1 ------> y1 ]
w(1)11 w(2)11 w(3)11
(x1)----------->
1층에서 2층으로의 신호전달
- weight구하고
- Bias 적용하고
- A를 구한 후, Z를 구함
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) # 혹은 Y = A3
활성함수 identity_function을 굳이 정의할 필요는 없으나, 그동안의 흐름과 통일하기 위해서 위와 같이 구현
구현정리
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.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):
W1, w2, W3 = network['W1'], netwrok['W2'], netwrok['W3']
b1, b2, b3 = network['b1'], netwrok['b2'], netwrok['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.31682708 0.69627909]
3.5 출력층 설계하기
신경망은 분류, 회기 모두에 사용 가능
분류, 회기에 따라 활성화 함수가 달라짐
회기 (regression) : 항등함수
분류 (classification): 소프트맥스 함수
분류:
데이터가 어느 class에 속하는지에 대한 문제
회기:
입력 데이터에서 (연속적인) 수치를 예측하는 문제
ex. 사진 속 인물의 몸무게를 예측하는 문제
항등함수와 소프트맥스 함수 구현
identity function: 입력을 그대로 출력
입력과 출력이 항상 같음
그림 3-2
sigmoid()
(a1) -------------> (y1)
sigmoid()
(a2) -------------> (y2)
sigmoid()
(a3) -------------> (y3)
소프트맥스 함수 (softmax function)
n
yk = exp(ak) / sigma(exp(ai))
i=1
e는 자연상수
n은 출력층의 뉴런 수
yk는 그중 k번째 출력
분자: 입력 신호 ak의 지수 함수
분모: 모든 입력 신호의 지수 함수의 합
그림 3-22 (softmax function)
softmax function의 출력은 모든 입력 신호로부터 화살표를 받음
출력층의 각 뉴런이 모든 입력 신호에서 영향을 받기 때문 (위 식에서 볼 수 있듯이)
각 ai에서 나가는 edge는 모든 yi를 향함 (아래 그림은 생략)
sigmoid()
(a1)---------------> (y1)
(a2)---------------> (y2)
(a3)---------------> (y3)
>>> import numpy as np
>>>
>>> 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.1221542102
>>>
>>> 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
softmax function 구현 시 주의점
overflow 문제
지수 사용 시 큰 값이 나올 수 있음 ex. e^10은 20,000이 넘고, e^100은 0이 40개가 넘는 큰 값
e^1000은 무한대를 뜻하는 inf가 출력됨
이를 해결하기 위해서 softmax function을 다음과 같이 개선
식 3-11 (p93)
yk = exp(ak) / sigma[i=1, n](exp(ai))
= C exp(ak) / C sigma[i=1, n](exp(ai))
= exp(ak + logC) / sigma[i=1, n](exp(ai + logC))
= exp(ak + C') / sigma[i=1, n](exp(ai + C'))
C라는 임의의 정수를 분자와 분모 양쪽에 곱
그리고 C를 지수 함수로 옮겨 logC로 만듦
마지막으로 logC를 C'라는 생로운 기호로 변경
위 식의 의미는
softmax function의 지수 함수 계산 시 어떤 정수를 더해도 (/빼도) 결과는 동일함을 의미
C'에 어떤 값을 대입해도 상관 없음, overflow를 막을 목적으로 입력 신호 중 최댓값을 이용하는 것이 일반적
아래와 같이 계산이 제대로 되지 않음
nan (not a number)를 출력
>>> a = np.array([1010, 1000, 990])
>>> np.exp(a) / np.sum(np.exp(a))
__main__:1: RuntimeWarning: overflow encountered in exp
__main__:1: RuntimeWarning: invalid value encountered in true_divide
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])
입력 신호 중 최댓값(이 경우 c)를 빼주면 정상 동작함
def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c) # overflow 대책
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
softmax function features
softmax() 함수 사용 시 신경망의 출력은 다음과 같이 계산 가능
>>> 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
중요특징
softmax 함수의 출력은 0에서 1.0 사이의 실수임
또 softmax 함수 출력의 총합은 1
위 성질로 인해 softmax 함수의 출력을 '확률'로 해석할 수 있음
y[0]의 확률은 0.018 (1.8%)
y[1]의 확률은 0.245 (24.5%)
y[2]의 확률은 0.737 (73.7%)
2번째 원소의 확률이 가장 높으니 답은 2번째 클래스다라고 할 수 있음
혹은 74%의 확률로 2번째 클래스
25%의 확률로 1번째 클래스
1%의 확률로 0번째 클래스
와 같이 확률적 결론 도출 가능 (통계적으로 대응 가능)
softmax function을 적용해도 각 원소의 대소 관계는 변하지 않음
지수함수가 단조 증가 함수임 (a, b,가 a <= b일때, f(a) <= f(b) 성립)
a의 원소들 사이의 대소관계는 y의 원소들 사이의 대소관계에 그대로 반영 됨
신경망을 사용한 분류
보통 가장 큰 출력을 내는 neuran에 해당하는 class로만 인식함
softmax function을 적용해도 출력이 가장 큰 뉴런의 위치는 달라지지 않음
즉, 신경망으로 분류 시 출력층의 softmax function을 생략해도 됨
기계학습 문제 풀이 단계
1) 학습 : 모델을 학습 (훈련)
2) 추론 : 모델로 데이터 추론(분류)
추론 단계에서는 출력층의 softmax function을 생략하는 것이 일반적
출력층의 뉴런 수 정하기
문제에 맞게 적절히 정해야 함
분류하고 싶은 class의 수로 설정하는 것이 일반적
ex. 숫자 0에서 9중 하나로 분류하는 문제라면, 출력층의 뉴런을 10개로 설정함
-------------> y0 = 0
-------------> y1 = 1
image 2 ------> 임의의 계산 -------------> y2 = 2
-------------> y3 = 3
...
-------------> y9 = 9
출력층
위 그림에서 y2 node의 색상이 가장 진함
색이 가장 짙은 y2 뉴런이 가장 큰 값을 출력
이 신경망이 선택한 class는 y2, 즉 입력 이미지를 숫자 '2'로 판단했음을 의미
3.6 손글씨 숫자 인식
손글씨 숫자 분류 ex.
학습 과정은 생략하고 추론 과정만 구현
이 추론 과정: 신경망의 순전파 (forward progagation)
기계학습처럼 신경망도
훈련을 통해 가중치 매개변수를 학습
이후 추론 단계에서 학습한 매개변수를 사용해 입력 데이터를 분류
MNIST data set
MNIST라는 손글씨 숫자 이미지 집합
MNIST는 기계학습에서 아주 유명한 data set
간단한 실험부터 논문으로 발표되는 연구까지 다양한 곳에서 사용됨
0~9까지의 숫자 이미지로 구성 (그림 3-24)
훈련 이미지가 60,000장
시험 이미지가 10,000장
이들 훈련 이미지를 사용해 '학습'
학습한 모델로 시험 이미지들을 얼마나 정확하게 분류하는지 평가
그림 3-24
7 2 1 ... 이미지들
각 28x28, luminance image, 각 8bits per pixel
이 책에서는 MNIST data set을 내려받아 이미지르 ㄹ넘파이 배열로 변환해주는 python script를 제공
(dataset/mnist.py file)
mnist.py를 import해 사용하려면 작업 directory ch01, ch02, ch03, ...ch08 중 하나로 옮겨야 함
'AI > Deep learning' 카테고리의 다른 글
seq2seq (0) | 2022.03.06 |
---|---|
Loss 최소화, SGD, 가중치 갱신 방법 (0) | 2022.03.06 |
텐서(Tensor) (0) | 2021.08.16 |
data partitioning, hyper-parameter tuning (0) | 2021.08.16 |
data partitioning, hyper-parameter tuning (0) | 2021.08.16 |