- 수치 미분을 통하여 기울기를 구하는데, 이는 단순하고 구현하기는 쉽지만 계산 시간이 오래 걸린다는 단점이 있습니다. 따라서, 가중치 매개변수의 기울기를 가장 효율적으로 계산할 수 있는 오차역전파법(backpropagation)을 사용합니다.
순전파 : 왼쪽 -> 오른쪽
역전파 : 오른쪽 > 왼쪽
계산 그래프
: 계산 그래프는 계산 과정을 그래프로 나타낸 자료구조로, 복수의 노드와 에지로 표현합니다.
예제1)
현빈 군은 슈퍼에서 1개에 100원인 사과를 2개 샀습니다. 이때 지불금액을 구하시오, 단 소비세가 10% 부과됩니다.
최초에 사과를 두 개 사고, 소비세를 곱한 값이 답이 됩니다.(220원). 위의 그래프에서는 x2와 x1.1을 각각 하나의 연산으로 취급했지만, 곱하기만을 연산으로 취급할수도 있습니다. 이렇게 하면 곱한 값 또한 변수가 되어 원 밖에 위치하게 됩니다.
계산 그래프를 구성
그래프에서 계산을 왼쪽에서 오른쪽으로 진행
여기서 2번, ‘계산을 왼쪽에서 오른쪽으로 진행하는 것’을 순전파이고, 역전파는 반대로(오른쪽에서 왼쪽으로) 계산을 진행합니다.
예제2)
현빈군은 슈퍼에서 사과를 2개, 귤을 3개 샀습니다. 사과는 1개에 100원, 귤은 1개 150원 입니다. 소비세가 10%일 때 지불 금액을 구하세요.
계산 그래프의 특징은 '국소적 계산'을 통해 최종 결과를 얻는 것으로 '자신과 직접 관계된' 범위 내에서만 계산이 이루어 집니다.
- 계산그래프의 장점
국소적 계산을 통해 각 노드의 계산에 집중하여 문제를 단순화
: 아무리 복잡한 최종결과가 이루어진다고 하더라도 계산 그래프로 표현하면 각각의 항목별로 독립적으로 표현하고, 독립적으로 연산합니다. 연관지어서 대상을 표현하는 것이 아니라 독립적으로 표현합니다. 독립적으로 수행한 결과만 합쳐주면 됩니다.
역전파를 통해 '미분'을 효율적으로 계산
사과 가격이 오르면 최종 금액에 어떠한 영향을 주는가'에 대해서 사과 가격에 대한 지불 금액의 미분을 구해 계산할 수 있습니다. 사과의 값을 x, 지불 금액을 L라 했을 때, 𝜕L/𝜕x(분수)를 구하는 것으로, 이러한 미분 값은 사과 값(x)가 '아주 조금'올랐을 때 지불 금액(L)이 얼마나 증가하는지를 나타냅니다.
계산 그래프의 역전파
y = f(x)
역전파의 계산 절차
- 신호 E에 노드의 국소적 미분( 𝜕𝑦/𝜕𝑥 (분수))을 곱한 후 다음 노드로 전달. - 국소적 미분이란 : 순전파 때의 y = f(x) 계산의 미분을 구한다는 것이며, 이는 x에 대한 y의 미분( 𝜕𝑦/𝜕𝑥(분수))을 구함.
* 𝜕𝑦/𝜕𝑥(분수) 의 값을 구하기 위해서는 x, y 의 값을 알아야 하는데, 계산 그래프를 그려서 확인하면 결과 값들을 보관함으로 빠르게 확인할 수 있습니다.
- 입력에 대한 변화를 미분으로 정의한 것이라고 볼 수 있습니다.
연쇠법칙
: 연쇄법칙은 합성 함수의 미분에 대한 성질로, 합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있습니다.
* 합성함수 : 여러 함수로 구성된 함수
예시)
t^2을 미분하면 2t이고, t를 미분하면 1입니다. 그래서, 최종적으로 z를 미분한 값은 2t * 1 = 2t = 2(x+y)입니다.
연쇄법칙과 계산 그래프
x의 변화량 분의 z의 변화량의 편미분값
덧셈 노드의 역전파
z = x + y 라는 식을 대상으로, 역전파를 살펴보겠습니다. 그래프를 그리면 다음과 같은 모양이 될 것입니다.
x + y 의 식에 대해, 위의 임의의 계산으로 가는 노드는 x를 기준으로 편미분을 하므로, y가 사라지면서 1이 남게 됩니다. 밑의 임의의 계산으로 가는 노드는 y 를 기준으로 편미분을 하므로, x가 사라지면서 1이 남게 됩니다. 따라서, + 노드를 대상으로 하는 역전파는 결과적으로 입력받은 데이터를 그대로 흘려주게 됩니다. 별도의 부정을 수행할 필요 없다는 것이 함축 되어 있습니다.
: 퍼셉트론에서 출발하여 여러 입력신호를 입력받아서 출력을 내보내주는 표현이 퍼셉트론이었는데, 퍼셉트론과의 차이는 활성화 함수를 어떤 것을 사용하느냐에 차이를 가집니다. 퍼셉트론은 스텝 함수를 사용하고, 신경망은 시그모이드 함수를 사용합니다. 스텝 함수는 선형성이고, 시그모이드 비선형입니다. 게이트를 통해서 결과를 확인할 때, XOR는 비선형의 형태를 통해서 코드상으로 구현할 수 있습니다. 코드의 형태를 봤을 때, 입력과 출력의 형태로만 되어있던 것을 은닉층을 통해 다층의 구조를 가져갈 때, 활성화 함수를 시그모이드 함수로 활용하여 선형적으로 절대 분류 할 수 없었던 특징을 비선형으로 구현할 수 있게 되었습니다.
신경망 학습 : 데이터로부터 매개변수의 값을 정하는 방법
- ex) y = ax + b / a(기울기) , b(절편) 을 획득하라.
신경망의 특징
- 데이터를 보고 학습할 수 있다. - 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻. - 사람의 개입을 최소화하고, 수집한 데이터로부터 답을 찾고, 패턴을 찾으려는 시도 - 특히, 신경망과 딥러닝은 기존 기계학습에서 사용하던 방법보다 사람의 개입을 더욱 배제할 수 있게 해주는 중요한
특성을 지님
훈련 데이터와 시험 데이터
훈련 데이터 (training data)
: 훈련 데이터만 사용하여 학습하면서 최적의 매개변수를 찾음
시험 데이터 (test data) : 앞서 훈련한 모델의 실력을 평가하는 것
훈련 / 시험 데이터 분리 이유 : 우리가 원하는 것은 범용적으로 사용할 수 있는 모델 구현 : 범용 능력을 제대로 평가하기 위해 모델을 찾아내는 것이 기계 학습의 최종 목표
오버피팅 (overfitting) : 한 데이터 셋에만 지나치게 최적화된 상태
- 학습용 데이터를 넣을 때는 정확하지만, 테스트 데이터를 넣었을 때는 결과가 정확하지 않은 것.
손실 함수(loss function)
: 손실함수는 신경망을 학습할 때 학습 상태에 대해 측정할 수 있도록 해주는 지표입니다. 신경망의 가중치 매개변수들이 스스로 특징을 찾아 가기에 이 가중치 값의 최적이 될 수 있도록 해야 하며 잘 찾아가고 있는지 볼 때 손실 함수를 보는 것입니다 = 손실 함수의 결과값을 가장 작게 만드는 가중치 매개 변수를 찾는 것이 학습의 목표.
- 데이터와 그래프와의 거리의 오차가 가장 작은 범위를 찾아서 머신러닝/딥러닝을 통해서 찾도록 하는 것입니다.
1) 평균 제곱 오차(mean squared error, MSE) - 회귀분석
: 평균제곱오차는 손실 함수로 가장 많이 쓰이며 간단하게 설명하면 예측하는 값이랑 실제 값의 차이(error)를 제곱하여 평균을 낸 것이 평균제곱오차입니다. 예측 값과 실제 값의 차이가 클수록 평균제곱오차의 값도 커진다는 것은 이 값이
작을 수록 예측력이 좋다고 할 수 있습니다.
pi = 실제값 , yi - 예측값
MSE(Mean Squared Error)
def mean_squared_error(y, t): # p와 y 자리가 바뀌어도 제곱을 할것이기 때문에 문제 없다.
return np.sum((y-t) ** 2) # 제곱
# 2 : 정답
t = [0,0,1,0,0,0,0,0,0,0] # 답 2 : 원핫인코딩(One-Hot Encording)
# 예측 결과 : 2
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
msq = mean_squared_error(np.array(t),np.array(y)) # 리스트 형태 -> 배열로 변환
print(msq)
출력값 : 0.19500000000000006
# 예측 결과 : 7
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
msq = mean_squared_error(np.array(t),np.array(y)) # 리스트 형태 -> 배열로 변환
print(msq)
출력값 : 1.195
- 제곱을 한다는 것은 면적을 구하는 것으로, 그 면적의 제곱이 더 작은 기울기를 찾는 것입니다. 왼쪽 보다 오른쪽이 더잘 찾은 그래프 입니다.
2) 교차 엔트로피 오차(cross entropy error, CEE) - 분류분석 : 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 됩니다. 데이터 하나에 대한 손실 함수에서 N개의 데이터로 확장. 다만, N으로 나누어 정규화. 평균 손실 함수, 교차엔트로피는 로그의 밑이 e인 자연로그를 예측값에 씌워서 실제 값과 곱한 후 전체 값을 합한 후 음수로 변환합니다. 실제 값이 원핫인코딩(one-hot encoding; 더미변수처럼 1~9까지 범주로 했을 때 정답이 2일 경우 2에는 '1'을 나머지 범주에는 '0'으로) 방식 일경우에는 2를 제외한 나머지는 무조건 0이 나오므로 실제값일 때의 예측값에 대한 자연로그를 계산하는 식이 됩니다. 실제 값이 2인데 0.6으로 예측했다면 교차 엔트로피 오차는 -log(1*0.6) = -log0.6 이 된다. = 0.51
ti : 정답 레이블 , yi : 예측값
CEE(Cross Entropy Error)
def cross_entropy_error(t, y):
delta = 1e-7 # NaN 값이 나오지 않기 위함
return -np.sum(t * np.log(y+delta))
# 2 : 정답
t = [0,0,1,0,0,0,0,0,0,0] # 답 2 : 원핫인코딩(One-Hot Encording)
# 예측 결과 : 2
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
cee = cross_entropy_error(np.array(t),np.array(y)) # 리스트 형태 -> 배열로 변환
print(cee)
출력값 : 0.510825457099338
# 예측 결과 : 7
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
cee = cross_entropy_error(np.array(t),np.array(y)) # 리스트 형태 -> 배열로 변환
print(cee)
출력값 : 2.302584092994546
자연로그y=log𝑥의 그래프
: 교차 엔트로피는 출력이 1일 때 0이 되며 x가 커질수록 0에 가까워지고 x가 작아질수록(0에 가까워질수록) 값이 작아집니다(음의방향).
미니배치 학습
: 한번에 하나만 계산하는게 아니라 일부를 조금씩 가져와서 전체의 '근사치'로 이용하여 일분만 계속 사용하여 학습을 수행하는 방법을 이용합니다. 그 일부를 미니배치 mini-batch 라고 합니다. 훈련 데이터에서 일부를 무작위로 뽑아 학습하는 것은 미니배치 학습입니다. 미니배치는 무작위로 추출하는 것으로 표본을 무작위로 샘플링하는 것과 개념적으로 유사합니다.
import numpy as np
from dataset.mnist import load_mnist
# 60000개 데이터 10000개 데이터
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
# normalize= True : 정규화 , one_hot_label= True : 원핫인코딩 방식으로 처리
print(x_train.shape) # (60000, 784) 60000개 데이터, 784 입력 개수
print(t_train.shape) # (60000, 10) one_hot_label=True - 1이 아닌 10으로 불러온다.
print(t_train.shape[0]) # 60000 : 전체 데이터의 개수를 출력한다.
print(t_train.shape[1]) # 10 : one_hot_label=True 에 의해서 10으로 출력.
batch_size = 100 # 60000개 중에서 batch 100
# 지정한 범위의 수 중에서 무작위로 원하는 개수만 선택(batch_size = 100).
batch_mask = np.random.choice(t_train.shape[0], batch_size)
# (범위,출력개수) - 60000개 중에서 100개 index를 출력
print(batch_mask)
출력값:
[10995 25334 55558 55950 43378 42591 59185 42772 37410 15106 19732 25420
36632 2856 19635 32958 44348 1958 2423 29217 32011 32203 6246 3725
36512 9584 5884 39660 55198 54392 10831 17019 29137 33830 34056 48376
40238 45480 37854 1991 26662 4218 24518 2198 3433 58183 49781 32639
49820 58634 22093 3984 58591 54007 44081 49513 34769 27648 2329 24193
7408 56742 55155 14269 59064 45151 54338 42582 7501 36434 15837 49116
26964 21467 29748 3811 55781 1606 16729 15604 3548 5645 8241 5217
26850 29007 50469 26448 41770 59517 28124 45990 40545 12044 27309 29817
35131 34838 14644 35117]
미분 - 접선의 기울기를 구하는 개념
: 경사법에서는 기울기 값을 기준으로 방향을 정합니다.
- 미분은 한 순간의 변화량을 계산한 것(x 의 작은 변화 가 함수 f(x) 를 얼마나 변화시키느냐를 의미)
수치 미분
h는 시간을 뜻하고 이를 한 없이 0에 가깝게 한다는 의미로 lim를 주고, 분자는 a에 대한 변화량을 나타냅니다.
이와 같은 방식으로 미분을 구하는 것은 수치 미분이라고 합니다. 차분(임의의 두 점에서 함수 값들의 차이)으로 미분을 구하기 때문입니다. 수치 미분은 오차가 포함될 수 밖에 없습니다. 오차를 줄이기 위해서는 x를 중심으로 h 만큼의 함수 f의 차분을 계산하여 구하기도 하며 이를 중심 차분 또는 중앙 차분이라고 합니다.
- 진정한 미분 접선 과 수치 미분 근사로 구한 접선 의 값
- 미분의 나쁜 구현 예(문제점2가지)
: 0.0이 답이 아닌데 출력되는 값이 0.0으로 출력됩니다.
import numpy as np
import matplotlib.pyplot as plt # 시각화 관련 패키지
# 미분의 나쁜 구현 예(문제점 2가지)
def numerical_diff(f, x):
h = 10e-50 # 1번째 문제점) 0에 최대한 가깝게 구현.
# But python의 경우 np.float32(1e-50)는 0.0으로 처리(반올림 오차)
return (f(x+h)-f(x)) / h # 2번째 문제점) 1e-50)보다 크게 하면 h에 의한 오차 발생.
def function_1(x):
return 0.01 * x ** 2 +0.1 * x
x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x,y)
# plt.show()
print(numerical_diff(function_1,5))
출력값 : 0.0
- 문제점 해결 수치 미분의 예
: 중앙차분 , 오차를 줄일 수 있도록 합니다(반드시 오차가 줄어드는 것은 아닙니다).
def numerical_diff(f,x):
h = 1e-4 # 0.0001
return (f(x+h) -f(x-h))/2*h
# x를 중심으로 그 전후의 차분을 계산한다. : 중심차분 혹은 중앙차분
# 오차는 존재하지만 접선의 기울기를 그리는 것과 같은 효과를 낼 수 있다.
def function_1(x):
return 0.01 * x ** 2 +0.1 * x
x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x,y)
# plt.show()
print(numerical_diff(function_1,5))
# 1.9999999999908982e-09
편미분
: 변수가 여러 개인 함수에 대한 미분을 편미분이라고 합니다. 편미분 역시 변수가 하나인 미분과 동일하게 특정 장소에 대한 기울기를 구합니다. 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정합니다.
: 모든 변수의 편미분을 벡터로 정리한 것을 기울기 gradient라고 합니다. 기울기는 가장 낮은 장소를 가리키지만 각 지점에서 낮아지는 방향을 의미합니다. 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 줄이는 방향이라고 할 수 있습니다.
모든 변수의 편미분을 동시에 계산하고 싶다을 때
양쪽의 편미분을 묶어서 계산 합니다.
경사하강법
: 손실함수를 기준으로 오차값을 보고 음수쪽으로 이동할지 양수쪽으로 이동할지를 판단합니다. 손실합수의 접선의 기울기를 구하여 양수가 나오면 음수를 넣어주고, 음수가 나오면 양수를 넣어줘서 접선의 기울기가 0이 나오도록 합니다. 손실을 최소화 하는 값을 추적해가는 알고리즘입니다.
- 기울기를 잘 이용해 함수의 최소값 또는 가능한 한 작은 값 을 찾으려는 것 이 경사 하강법
- 접선의 기울기가 0이 되는 지점이 최소값이 됩니다(음수가 나오면 양수로 이동 , 양수가 나오면 음수가 나오도록
값을 조정합니다).
학습률 = 얼마 만큼 이동(간격)할 것인가? 값을 넣어주는 것은 분석하는 사람의 역할입니다.
import numpy as np
import matplotlib.pyplot as plt
# 경사하강법 (손실을 최소화)
def numerical_gradient_no_batch(f,x):
# 배치단위가 아닌 미분 값을 구현되어지게 만듦. - 중앙차분(미분)
h = 1e-4
grad = np.zeros_like(x)
# _like를 가진 함수의 공통점 : _like(배열) 지정한 배열과 동일한 shape의 행렬을 만듦
# 입력으로 전달되는 shape 과 같은 shape를 초기값을 0으로 생성
for idx in range(x.size):
tmp_val = x[idx] # x값을 꺼내와서 변수에 담아주었다.
# f(x+h) 계산
x[idx] = float(tmp_val) + h
fxh1 = f(x)
# f(x-h) 계산
x[idx] = float(tmp_val) - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2 * h) # 중앙차분
x[idx] = tmp_val
return grad
def numerical_gradient(f,x): # 편미분 정의 함수 (함수,입력값(단항,다항))
if x.ndim == 1 : # 단항
return numerical_gradient_no_batch(f,x)
else:
grade = np.zeros_like(x)
# _like를 가진 함수의 공통점 : _like(배열) 지정한 배열과 동일한 shape의 행렬을 만듦
# 입력으로 전달되는 shape 과 같은 shape를 초기값을 0으로 생성
for idx, z in enumerate(x):
# enumerate() : 입력으로 전달 받은 값을 자돟으로 인덱스 값을 0부터 증가하면서 x의 담긴 값을 반환
grade[idx] = numerical_gradient_no_batch(f, x)
return grade
def gradient_descent(f, init_x, lr, step_num): # 함수, 초기값, learning rate, 횟수 - 경사하강법
x = init_x # 초기값
x_history = [] # 학습을 통해서 나온 값들을 저장하여, 시각화 할 때 사용
for i in range(step_num): # 20번 반복
x_history.append(x.copy()) # 초기값을 가져와 저장한 이후 입력으로 전달되는 데이터 추가.
grad = numerical_gradient(f,x) # 편미분
x -= lr * grad
return x, np.array(x_history) # 반복이 끝난 이후!!!!
# 튜플의 가로 생략 된것이지 2개가 return이 되는 것이 아니다.
def function_2(x): # f(x0, x1) = x0^2 + x1^2
return x[0]**2 + x[1]**2
if __name__ =="__main__":
init_x = np.array([-3.0, 4.0])
# 초기값 셋팅(내부에서 처리 될 때, float으로 처리 되기 때문에 float형(실수)로 넣어줘야한다.
lr = 0.1 # learning rate(학습률)
step_num = 20 # 경사하강법의 학습을 시킬 때, 학습시킬 횟수의 값(20회)
x, x_history = gradient_descent(function_2, init_x, lr, step_num)
# 함수, 초기값, learning rate, 회수 / 튜플로 반환해서 튜플로 받은 것(변수 2개x)
plt.plot([-5,5],[0,0],'--b')
plt.plot([0, 0], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')
plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()
f(x0, x1) = x0^2 + x1^2
경사하강법을 이용하여 (0.0)으로 조정
신경망에서의 기울기
- 가중치 매개변수에 대한 손실 함수의 기울기 - 예 ) 형상이 2 x 3, 가중치 W, 손실 함수 L 인 신경망의 경우
: 인공신경망(artificial neural network,ANN)은기계학습과인지과학에서 생물학의 신경망(동물의중추신경계중 특히뇌)에서 영감을 얻은 통계학적 학습 알고리즘입니다.
신경망은 아래와 같이 왼쪽부터 Input(입력층), Hidden(은닉층), Output(출력층)으로 표현할 수 있습니다.
은닉층은 양쪽의 입력층과 출력층과는 달리 우리 눈에는 보이지 않기 때문에 'Hidden(은닉)'이라고 합니다.
퍼셉트론 복습
- y는 출력 신호 - x1 과 x2는 입력 신호 - w1 과 w2는 가중치(weight) : 각 신호가 결과에
주는 영향력을 조절하는 요소로 작용 - b(편향) : 뉴런이 얼마나 쉽게 활성화 제어 -뉴런(혹은 노드) : 그림의 원
Bias(편향)를 명시한 퍼셉트론
y = h(b + w1x1 + w2+x2)
h(x) = - > 0 ( x <= 0 )
- > 1 ( x > 0 )
퍼셉트론이 하나의 뉴런 단위로 다루어진다면 각 뉴런이 모여 하나의 뇌가 되는 것과 같은 신경망은 퍼셉트론의 하위요소로 볼 수 있습니다. 단층 퍼셉트론이 하나의 나무와 같이 신경망은 나무가 모여 숲을 이룬 것과 같은 느낌으로 이해하면 됩니다.
활성화 함수 (Activation Function)
: 입력 신호의 총합을 출력 신호로 변환하는 함수 , 입력 신호의 총합이 활성화를 일으키는지를 정하는 역할합니다.
- 활성화 함수를 통해서 비선형성을 가지게 됩니다.( 경계면의 부근의 모양을 결정 )
a = b + w1x1 + w2x2
y = h(a)
(a는 입력 신호의 총합, h()는 활성화 함수, y는 출력)
계단함수 (step function)
: 퍼셉트론은 활성화 함수로 step function(계단 함수)를 이용합니다. 특정 임계값을 넘기면 활성화되는 함수입니다. 아래 왼쪽(a)가 계단 함수이다. 0에서 멈추어있다가 어느 기점에서 1로 바뀝니다. 임계값을 경계로 출력이 바뀌는 활성화되며, 활성화 함수를 계단함수에서 다른 함수로 변경하는 것이 신경망의 세계로 나아가는 열쇠가 됩니다.
h(x) = 0 ( x <= 0 )
1 ( x > 0 )
# 계단 함수(Step Function)
import numpy as np
def step_function(x):
if x > 0:
return 1
else:
return 0
def step_func_ndarray(x):
y = x > 0
if __name__ =="__main__":
x = step_function(3)
print(x) # 1
x = step_function(-3)
print(x) # 0
주의 ) error : if 을 통해서 하나의 값이 비교가 되어야 하는데 배열로 다수의 값이 오기 때문에 처리 되지 않습니다.
# 계단 함수(Step Function)
import numpy as np
def step_function(x):
if x > 0:
return 1
else:
return 0
if __name__ =="__main__":
x = step_function(3)
print(x) # 1
x = step_function(-3)
print(x) # 0
z = np.array([-1,1,2])
x = step_function(z)
print(x)
해결점 1) 함수를 정의해서 boolean 값으로 출력.
# 계단 함수(Step Function)
import numpy as np
def step_function(x):
if x > 0:
return 1
else:
return 0
def step_func_ndarray(x):
y = x > 0
print(y)
if __name__ =="__main__":
x = step_function(3)
print(x) # 1
x = step_function(-3)
print(x) # 0
z = np.array([-1,1,2])
step_func_ndarray(z)
해결점 2) int로 변환하여 0, 1 출력.
# 계단 함수(Step Function)
import numpy as np
def step_function(x):
if x > 0:
return 1
else:
return 0
def step_func_ndarray(x):
y = x > 0
return y.astype(np.int)
if __name__ =="__main__":
x = step_function(3)
print(x) # 1
x = step_function(-3)
print(x) # 0
z = np.array([-1,1,2])
print(step_func_ndarray(z))
- 시각화
x = np.arange(-5,5,0.1) # -5 ~ 4.9 까지.
y = step_func_ndarray(x
plt.plot(x,y)
plt.ylim(-0.1, 1.1)
plt.show()
시그모이드 함수 (sigmoid fuction)
: 신경망에서 자주 이용하는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달합니다. 변화와 간격이 완만하게 하는데 이것이 비선형성을띄게 되는 것입니다.
영역을 구분하는 일차 함수의 선형성이 완만하게 가게 되면서 굴곡이 가해질 수 있게 되었습니다. - 비선형성
import numpy as np
# - sigmoid(활성화) 함수의 특징
# 1) 음수 입력 값의 경우 양수로 변환
# 2) 0 < y < 1 사이의 연속적인 실수 값으로 변환
def sigmoid(x):
return 1 / (1 + np.exp(-x)) # np.exp(-x) : 배열로 전달 받아도 전처리 과정 없이 한번에 수행된다
if __name__ == "__main__":
x = np.array([-1, 0, 1, 2])
y = sigmoid(x)
print(y) 출력값 : [0.26894142 0.5 0.73105858 0.88079708] - 실수 값으로 출력.
계단 함수 (Step Function) vs 시그모이드 함수 (Sigmoid Function)
퍼셉트론과 신경망의 주된 차이는
활성화 함수의 차이
- 계단 함수가 0과 1 중 하나의 값만 돌려주는 반면 시그모이드 함수는 실수를 돌려준줍니다.
- 즉, 퍼셉트론에서는 뉴런 사이에 0 혹은 1이 흘렀다면, 신경망에서는 연속적인 실수가 흐릅니다.
- 두 함수 모두 비선형 함수입니다.
- 신경망에서는 활성화 함수로 비선형
함수를 사용해야 합니다.
시그모이드 함수는 값을 실수형으로 가지는 것을 볼 수 있습니다. 시그모이드 함수의 매끄러움은 가중치 값을 전달할 때 좀 더 부드럽게 양을 조절해서 전달할 수 있습니다.
- 비선형 함수를 사용해야 하는 이유?
: 선형함수를 사용했을 때는 은닉층을 사용하는 이점이 없기 때문입니다. 즉, 선형함수를 여러층으로 구성한다 하더라도 이는 선형함수를 세번 연속 반복한 것에 지나지 않는다는 의미와 같기 때문입니다. y = ax라는 선형함수가 있다고 한다면 이 것을 3층으로 구성하면 y = a(a(a(x))) 와 동일한 것으로 이는 y = a3(x)와 같습니다. 굳이 은닉층 없이 선형함수로 네트워크를 구성하는 것은 의미가 없어집니다.
ReLU 함수(Rectified Linear Unit Function)
: 최근 신경망 분야에서 이용하는 함수로 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력하는 함수입니다.
h(x) = x ( x > 0 )
0 ( x <= 0 )
import numpy as np
import matplotlib.pyplot as plt
def relu(x): # 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력
return np.maximum(0, x) # np.maximum : 두 입력 중 큰 값을 선택해 반환하는 함수
if __name__=="__main__":
print(relu(5)) #5
print(relu(-5)) #0
x = np.arange(-5,5,0.1) # 배열형
y = relu(x)
print(y)
- 시각화
x = np.arange(-5,5,0.1) # 배열형
y = relu(x)
print(y)
plt.plot(x,y)
plt.ylim(-1.0,5.5)
plt.show()
다차원 배열
① Vector(1차원 배열)
X = np.array([1,2,3,4,5])
print(X.shape) 출력값 : (5,) 1차원의 배열 - 열 개수 카운팅 / ndarray
항등 함수의 정의 - 출력단
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)
print(Y) # [0.31682708 0.69627909]
입력 부터 출력까지의 모듈
import numpy as np
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
# 1층에서의 활성화 함수 처리
def sigmoid(x): # 시그모이드 함수 정의
return 1/ (1 + np.exp(-x))
# 항등 함수의 정의 - 출력단
def identity_function(x):
return x
# 데이터 전달
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
if __name__ == "__main__":
network = init_network()
x = np.array([1.0,0.5])
y = forward(network, x)
print(y) 출력값 : [0.31682708 0.69627909]
- 소프트맥스 함수
: 비율적으로 빠르게 변환할 수 있도록 도와주는 함수입니다.
소프트맥스 함수의 특징
- 소프트맥스 함수는 0에서 1사이의 실수로 출력합니다. - 출력의 총합은 1 입니다. - 출력을 확률로 해석할 수 있습니다. - 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용합니다. - 추론 단계에서는 출력층의 소프트맥스 함수를 생략하는 것이 일반적 입니다. 각 원소의 대소 관계는 변하지 않습니다. 지수 함수 y = exp(x)가 단조 증가 함수이기 때문입니다.
x = np.arange(0,6,0.1) # start 0 ~ last 5 , step = 0.1 (0.1~5.9)
y1 = np.sin(x) # 사인함수 를 이용하여 0.1단위로 계산해서 y1에 저장
y2 = np.cos(x) # 코사인함수 를 이용하여 0.1단위로 계산해서 y2에 저장
- 그래프 그리기
plt.plot(x, y1, label = "sin")
plt.plot(x, y2, linestyle = "--", label = "cos") # cos 함수는 점선으로 그리기
plt.xlabel("x") # x축 이름
plt.ylabel("y") # y축 이름
plt.title("sin & cos") # 제목
plt.legend() # 범례
plt.show() # 출력
- 꺽은선 그래프
x = np.arange(10)
y = np.random.rand(10)
plt.plot(x,y) # 꺽은선 그래프를 등록 / 10개의 난수를 plot 함수를 이용하여 꺽은석 그래프로 피드백.
plt.show() # 그래프 그리기
- 3차 함수 f(x) = (x-2) x (x+2)
def f(x):
return (x-2) * x * (x+2) # 3차 함수 정의
print(f(0)) 출력값 : 0
print(f(2)) 출력값 : 0
print(f(-2)) 출력값 : 0
# x값에 대해 ndarray 배열이며 각각에 대한 f를 한꺼번에 ndarray로 돌려줍니다.
print(f(np.array([1,2,3]))) 출력값 : [-3 0 15]
print(type(f(np.array([1,2,3])))) 출력값 : <class 'numpy.ndarray'>
# 그래프를 그리는 x의 범위를 -3 ~ 3까지로 하고 , 간격 0.5
x = np.arange(-3,3.5,0.5)
plt.plot(x,f(x))
plt.show()
- 그래프를 장식
x = np.linspace(-3,3,100) # x를 100 분할하기
# 차트 묘사
plt.plot(x, f2(x,2), color = "black")
plt.plot(x, f2(x,1), color = "blue")
plt.legend(loc="upper left") # 범례의 위치도 정해줄 수 있다.
plt.ylim(-15,15) # y축 범위
plt.title("f2(x, w) 함수")
plt.xlabel("x축")
plt.ylabel("y축")
plt.grid(True) # 그리드(눈금)
plt.show()
- 그래프를 여러 개 보여주기
plt.figure(figsize=(10,3)) # 전체 영역의 크기를 지정
plt.subplots_adjust(wspace=0.5, hspace=0.5) # 그래프의 간격을 지정
for i in range(6): # 0 ~ 5
plt.subplot(2, 3, i+1) # 그래프 위치를 지정 # 2행 3열
plt.title(i+1)
plt.plot(x,f2(x,i))
plt.ylim(-20,20)
plt.grid(True)
plt.show()
- 이미지 표시하기
from matplotlib.image import imread
img = imread('image/2.png') # 이미지 읽어오기
plt.imshow(img)
plt.show()
- 다수의 신호를 입력으로 받아 하나의 신호를 출력 - 신호 : 전류나 강물처럼 흐름이 있는 것을 상상 - 퍼셉트론 신호도 흐름을 만들고 정보를 앞으로 전달 - 퍼셉트론 신호는 ‘흐른다(1로 표현) / 안 흐른다(0으로 표현)의 두 가지 값으로 표현
퍼셉트론의 동작 방식
: 각 노드의 가중치와 입력치를 곱한 것을 모두 합한 값이 활성함수에 의해 판단되는데, 그 값이 임계치(보통 0)보다 크면 뉴런이 활성화되고 결과값으로 1을 출력합니다. 뉴런이 활성화되지 않으면 결과값으로 -1을 출력합니다.
- 입력으로 2개의 신호를 받은 퍼셉트론의 예
- x1 과 x2는 입력 신호 - y는 출력 신호 - w1 과 w2는 가중치(weight) : 각 신호가 결과에 주는 영향력을
조절하는 요소로 작용
- 각 입력신호에는 고유한 weight가 부여되며 weight가 클수록
해당 신호가 중요 - 뉴런(혹은 노드) : 그림의 원 - θ(theta) : 임계값(뉴런의 활성화 기준값)
θ : 흐르는지 흐르지 않는지에 대한 기준이 되는 값
θ < y 신호가 흐른다 / θ >= y신호가 흐르지 않는다.
- 신호가 흐르지 않는다.
- 신호가 흐른다.
퍼셉트론의 출력 값은 앞에서 말했듯이 1 또는 0(or -1)이기 때문에 선형 분류(linear classifier) 모형이라고도 볼 수 있습니다. 보통 실수형의 입력 벡터를 받아 이들의 선형조합을 계산하는 것이며, 선형 분류 평면 상에 선을 쫙 그어서 여기 넘으면 A, 못 넘으면 B 이런식으로 분류합니다.
y = ax + b 의 기원이 되는 것으로 a(기울기) = w 값, b(절편) = x 값 입니다.
y = w1x1 + w2x2 , 2개의 feature(colum) 를 통해서 계산 하는 것입니다.
- 컴퓨터에서 가장 많이 존재합니다. 변화가 일어날때만 출력하기 때문에 일정한 상태가 지속되어 질때, 이벤트를 감지합니다.
컴퓨터의 cpu가 위의 모습으로 되어 있기 때문에 이러한 모습을 컴퓨터의 언어로 만들어준 시초가 퍼셉트론으로 빠른 명령을 내려서 피드백 받을 수 있도록 하기 위한 것이었다.
perceptron -> 신경망 : 20년이라는 과도기가 있었습니다.
gate - AND , NAND , OR , NOR
1차 암흑기)
- ○ (동그라미) 와 △ (세모)는 일차 함수로 나눌 수 없었기 때문에 과도기가 존재했습니다.
AND , NAND , OR 를 구성하여 XOR를 구현
문제 해결
: 단층으로 선형으로만 가능할 것이라는 고정관념에서 다층으로 가져감으로 비선형성을 가지는 것을 발견되었습니다.
노드를 다층으로 가져가므로 물리적으로 gate를 조합함으로 분류가 가능하도록 되었다. 비선형의 모형으로 구분할 수 있도록 합니다.
- 다층(선형) -> 다층(비선형)
- 다층 구조
XOR gate : 다층 퍼셉트론
다층 퍼셉트론의 동작 설명
1. 0층의 두 뉴런이 입력 신호를 받아 1층의 뉴런으로 신호를 보냅니다. 2. 1층의 뉴런이 2층의 뉴런으로 신호를 보내고, 2층의 뉴런은이 입력신호를 바탕으로 y를 출력합니다. 3. 단층 퍼셉트론으로는 표현하지 못한 것을 층을 하나 늘려 구현할 수 있었습니다. - 퍼셉트론은 층을 쌓아(깊게하여) 더 다양한 것을 표현할 수 있습니다.
1) 단순한 논리 회로 – AND 게이트
- 퍼셉트론을 활용한 간단한 문제 적용 - AND 게이트를 퍼셉트론으로 표현하기 - 즉, w1, w2, θ 의 값 구하기
- 사람이 매개변수 값을 정함
def AND(x1, x2):
w1, w2, theta = 0.5, 0.5, 0.7 # 사람이 매개변수의 값을 정함.
tmp = w1 * x1 + w2 * x2
if tmp <= theta:
return 0
else:
return 1
if __name__ == "__main__":
for xs in [(0,0),(0,1),(1,0),(1,1)]:
y = AND(xs[0],xs[1])
print(str(xs)+" : "+ str(y))
앞의 퍼셉트론 수식에서 나오는 세타θ를 -b로 치환하여 좌변으로 넘기면
b + w1x1 + w2x2 <0 => 0
b + w1x1 + w2x2 >=0 => 1
과 같이 되며 여기에서 b를 편향(bias)라고 할 수 있습니다.
import numpy as np
def AND(x1, x2):
x = np.array([x1,x2]) # 배열형으로 형변환 해서 x에 담다아줌
w = np.array([0.5,0.5])
b = -0.7
tmp = np.sum(w * x) + b # b(bias : 편향/절편)
if tmp <=0:
return 0
else:
return 1
if __name__ == "__main__":
result = AND(0,0)
print(result) # 0
result = AND(0,1)
print(result) # 0
result = AND(1,0)
print(result) # 0
result = AND(1,1)
print(result) # 1
2) 단순한 논리 회로 – NAND 게이트
- 퍼셉트론을 활용한 간단한 문제 적용 - NAND(Not AND) 게이트를 퍼셉트론으로 표현하기 - 즉, w1, w2, θ 의 값 구하기 – 사람이 매개변수 값을 정함.
- 실제 하드웨어의 동작(부하를 최소화 하기 위해 반대로 동작)
의미를 가질 때 : 0
의미를 가지지 않을 때 : 1
import numpy as np
# NAND Gate - AND의 반대
def NAND(x1, x2):
x = np.array([x1,x2])
w = np.array([-0.5,-0.5])
b = 0.7 # bias
tmp = np.sum(w * x) + b
if tmp <= 0:
return 0
else:
return 1
if __name__ == "__main__":
result = NAND(0,0)
print(result)
result = NAND(1,0)
print(result)
result = NAND(0,1)
print(result)
result = NAND(1,1)
print(result)
3) 단순한 논리 회로 – OR 게이트
-퍼셉트론을 활용한 간단한 문제 적용 - OR 게이트를 퍼셉트론으로 표현하기 - 즉, w1, w2, θ 의 값 구하기
– 사람이 매개변수 값을 정함
def OR(x1, x2):
x = np.array([x1,x2])
w = np.array([0.5,0.5])
b = -0.2
tmp = np.sum(w * x) + b
if tmp <=0:
return 0
else:
return 1
if __name__ == "__main__":
result = OR(0,0)
print(result) # 0
result = OR(1, 0)
print(result) # 1
result = OR(0, 1)
print(result) # 1
result = OR(1, 1)
print(result) # 1
4) XOR gate - 다층 퍼셉트론
import numpy as np
# AND gate
def AND(x1, x2):
x = np.array([x1,x2]) # 배열형으로 형변환 해서 x에 담다아줌
w = np.array([0.5,0.5])
b = -0.7
tmp = np.sum(w * x) + b # b(bias : 편향/절편)
if tmp <=0:
return 0
else:
return 1
# NAND gate
def NAND(x1, x2):
x = np.array([x1,x2])
w = np.array([-0.5,-0.5])
b = 0.7 # bias
tmp = np.sum(w * x) + b
if tmp <= 0:
return 0
else:
return 1
# OR gate
def OR(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.2
tmp = np.sum(w * x) + b
if tmp <= 0:
return 0
else:
return 1
def XOR(x1, x2):
s1 = NAND(x1,x2)
s2 = OR(x1,x2)
y = AND(s1,s2)
return y
if __name__ == "__main__":
print(XOR(0,0)) # 0
print(XOR(0,1)) # 1
print(XOR(1,0)) # 1
print(XOR(1,1)) # 0