목차
👀, 🤷♀️ , 📜
이 아이콘들을 누르시면 코드, 개념 부가 설명을 보실 수 있습니다:)
전체 딥러닝 학습과정
이번 포스트에선 어딜 배울까?
바로 신경망이다.
기계가 데이터를 받아서 어떻게 처리하고, output을 내는지를 볼 것이다.
이 신경망이 input 데이터에서 중요한 특징을 추출하기 위해 데이터를 걸러내는 역할을 하는데 핵심이 되는 구성이 활성화 함수이다.
자세한 내용을 알아보러가자
INTRO
신경망 성질: 가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력이다.
[위 신경망의 특징]
- ‘2층 신경망’: 가중치를 갖는 층은 2개뿐이기 때문
- 뉴런이 연결되는 방식은 앞 장의 퍼셉트론과 같다.
- 은닉층의 뉴런: (입력층이나 출력층과 달리) 사람 눈에 보이지 않는다.
⛔ 그렇다면 모든 경우에서 입력층의 데이터가 출력층까지 다 넘어갈까??
⭕ 아니다! 1) 모든 화살표에서 계산이 일어나고
2) 그 계산을 바탕으로 활성화할지 정한다
그러 그 계산을 더 알아보자
[퍼셉트론의 계산 식]
아래의 그림을 보면 알겠지만 은닉층에서 계산을 한다고 무조건 넘어가는 것(활성화 되는 것)은 아니다
이를 쉽게하기 위해 출력층 하나를 줄여보고, ⭕의 계산 식을 정의해보자
1) \(𝒚=𝒉(𝒃+𝒘,𝒙_𝟏+𝒘_𝟐 𝒙_𝟐 )\)
2) \(𝒉(𝒙)\)
- 이것이 활성화 할지정하는 함수이다(이제 이 함수의 종류를 알아볼 것이다.)
- 지금 이 활성화 함수는 임의로 정한 것이다
(활성화 되는 경우)
(활성화 되지 않는 경우)
즉 이 넘어가는 기준을 정해주는 함수가(\(𝒉(𝒙)\)) 활성화 함수이다.
활성화 함수(activation function)
[등장]
- 의의: 조금 전 h(x)라는 함수가 등장했는데, 이처럼 입력 신호의 총합을 출력 신호로 변환하는 함수
-
역할: 입력 신호의 총합이 활성화를 일으키는지를 정함
1단계) 가중치가 곱해진 입력 신호의 총합을 계산
2단계) 그 합을 활성화 함수에 입력해 결과를 냄
이해를 돕기 위해 기존 뉴런의 원을 키우고, 그 안에 활성화 함수의 처리 과정을 명시적으로 그려 넣어봤다
a 노드(가중치 신호를 조합한 결과)가 활성화 함수 h ( )를 통과하여 y라는 노드로 변환되는 과정이 분명히 드러난다
퍼셉트론에서 신경망으로 가기 위한 길잡이
- 단순 퍼셉트론
단층 네트워크에서 계단 함수(임계값을 경계로 출력이 바뀌는 함수)를 활성화 함수로 사용한 모델 -
다층 퍼셉트론
신경망(여러 층으로 구성되고 시그모이드 함수 등의 매끈한 활성화 함수를 사용하는 네트워크)을 뜻함 -
- 활성화 함수
- h(x)와 같이 임계값을 경계로 출력이 바뀌는 함수
퍼셉트론에서 이용하는 활성화 함수
그럼 이제 활성화 함수의 종류를 알아보자.
계단 함수 step function
: 활성화 함수로 쓸 수 있는 여러 후보 중에서 퍼셉트론은 계단 함수를 채용
: 활성화 함수를 계단 함수에서 다른 함수로 변경하는 것이 신경망의 세계로 나아가는 열쇠
👀코드 보기
def step_function(x):
if x > 0:
return 1
else:
return 0
장점
- 단순함, 구현하기 쉬움
단점
- 인수X는 실수만 받아들임
- 넘파이 배열을 인수로 받아들일 수 없음
step_function (np.array ([1.0, 2.0 ] ) )
불가
👀코드 보기
def step_function(x): #-> 넘파이 가능
y = x > 0 #-> bool형 출력
return y.astype(np.int) #-> bool에서 int형으로
파이썬에서 bool을 int로 변환하면 True는 1, False는 0으로
[시각화]
👀코드 보기
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) # y축의 범위 지정
plt.show()
-> np.arange (-5.0, 5.0, 0.1 )
: -5.0에서 5.0 전까지 0.1 간격의 넘파이 배열을 생성
신경망에서 이용하는 활성화 함수
비선형 함수
- 계단 함수와 시그모이드 함수의 공통점
직선 1개로는 그릴 수 없는 함수 - 신경망에서는 활성화 함수로 비선형 함수만 사용해야함 -> 선형 함수를 이용하면 신경망의 층을 깊게 하는 의미가 없어지기 때문
➕ 선형함수의 문제
층을 아무리 깊게 해도 ‘은닉층이 없는 네트워크’로도 똑같은 기능을 할 수 있다.
즉, 여러 층으로 구성하는 이점을 살릴 수 없다
sigmoid function(시그모이드 함수)
- \(exp (-x )= e^{-x}\).
- e는 자연상수로 2.7182…의 값을 갖는 실수
신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환, 그 변환된 신호를 다음 뉴런에 전달한다.
퍼셉트론과 신경망의 주된 차이는 이 활성화 함수뿐이다.
이 함수는 0~1사이의 결과만 내어놓으므로 확률을 표현하기에 적절하다.
[구현]
def sigmoid(x):
return 1 / (1 + np.exp(-x))
넘파이 배열이어도 올바른 결과가 나옴
[시각화]
👀코드 보기
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # y축 범위 지정
plt.show()
ReLU 함수
입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력하는 함수
시그모이드 함수는 신경망 분야에서 오래전부터 이용해왔으나,
최근에는 ReLU Rectified Linear Unit, 렐루 함수를 주로 이용한다.
[구현]
👀코드 보기
def relu(x):
return np.maximum(0, x)
x = np.arange(-7.0, 7.0, 0.1)
y = relu(x)
plt.plot(x, y)
plt.ylim(-2, 7.1) # y축의 범위 지정
plt.show()
-> 여기에서는 넘파이의 maximum
함수를 사용했습니다. Maximum
: 두 입력 중 큰 값을 선택해 반환하는 함수
3층 신경망 구현하기
그럼 이제 간단한 3층 신경망을 구현하면서 위의 개념들이 어떻게 쓰이는지 다시 한번 확인해보자.
아래 그림의 신경망을 구현해 볼 것이다.
그 전 다차원 배열의 계산의 개념을 잘 모르면 아래를 클릭하여 기본 지식을 쌓고 보는 것을 추천한다.
📜 다차원 배열의 계산 보기
[배열의 차원 수 확인]
👀코드 보기
#np.ndim ( ) -> 차원수 반환
#or
#함수이름.shape -> 튜플 반환
>>> 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)
[행렬의 곱]
👀코드 보기
#np.dot(A,B)
>>> 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]])
유의: 행렬의 곱에서는 대응하는 차원수를 일치시켜야 한다
[신경망에서의 행렬의 곱]
1️⃣ 변수 설명
📜 편향 ?
- 편향: 오른쪽 아래 인덱스가 하나밖에 없음
- 회색 노드가 편향
- 앞 층의 편향 뉴런이 하나뿐이기 때문
📜 행렬의 곱을 이용하여 단순화
구현
2️⃣ 활성화 함수를 시그모이드 함수로 사용
-> 은닉층에서의 가중치 합(가중 신호와 편향의 총합)을 a로 표기
-> 활성화 함수 h ( )로 변환된 신호를 z로 표기
-> 활성화 함수를 시그모이드 함수로 사용
👀코드 보기
Z1 = sigmoid(A1)
print(A1) # [0.3, 0.7, 1.1]
print(Z1) # [0.57444252, 0.66818777, 0.75026011]
3️⃣ 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)
4️⃣ 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
[구현 정리]
👀코드 보기
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'], 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.31682708 0.69627909]
- 함수정리
- init_network ( ) 함수 : 가중치와 편향을 초기화하고 이들을 딕셔너리 변수인 network에
- Network-> 각 층에 필요한 매개변수(가중치와 편향)를 저장
- forward ( ) 함수: 입력 신호를 출력으로 변환하는 처리 과정을 모두 구현하고 있음
- init_network ( ) 함수 : 가중치와 편향을 초기화하고 이들을 딕셔너리 변수인 network에
출력층 설계
위에서 활성화된 입력틍의 값들은 아래의 각 활성화 함수의 종류에 따라 변환된 값을 전달된다.
- 계단 함수: 그 a값 자체를 0또는 1로 변환한다
- 시그모이드 함수: 그 a값 자체를 0~1사이로 변환한다
- ReLU 함수: 그 a값 자체를 전달한다
그럼 활성화 되었다는 전제하에 마지막 출력을 할 때 이 값들을 그냥 그대로 전달을 할까??
물론 그대로 출력을 할 수도 있다.(항등함수 identity function)
하지만 각 결과(y)중 확률을 뽑는 등 출력 전 간단한 상관관계 연산을 해서 출력하면 더 보기 좋지 않을까?
즉 마지막 노드에서 y의 각 a값을 출력층에서 자체가 아닌 다른노드들과 계산하여 필요한 값으로 변환하여 출력할 수 있을까?
있다! 소프트맥스 함수 softmax function를 출력 직전에 넣어주면 된다.
그럼 노드들이 최종 출력 이전에 마지막 노드에서 처리하는 함수인 출력층 함수 2개(항등함수 identity function, 소프트맥스 함수 softmax function)를 알아보자
📜 기계학습의 문제는 분류와 회귀로 나뉨
분류 classification
- 데이터가 어느 클래스 class 에 속하느냐는 문제
- 사진 속 인물의 성별을 분류하는 문제
회귀 regression - 입력 데이터에서 (연속적인) 수치를 예측하는 문제
- 사진 속 인물의 몸무게(57.4kg?)를 예측
항등 함수 identity function
입력을 그대로 출력
소프트맥스 함수 softmax function
분류에 사용
그 노드가 나올 확률
변수 설명
- n: 출력층의 뉴런 수,
- \(y_k\): 그중 k번째 출력임을 뜻함
식 설명
- 내 클래스의 값/각 클래스의 합
- 즉, 모든값 합하면 1이 나옴
소프트맥스의 출력은 모든 입력 신호로부터 화살표를 받음
➡ 분모에서 보듯, 출력층의 각 뉴런이 모든 입력 신호에서 영향을 받기 때문(다합 해서 1이 되어야 하니까(확률))
👀 구현 코드 보기
>>> 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
시그모이드 함수와 차이점
소프트맥스 함수 구현 시 주의점
- 오버플로: 지수 함수란 것이 쉽게 아주 큰 값을 내뱉어 이런 큰 값끼리 나눗셈을 하면 결과 수치가 ‘불안정’해짐
[구현개선]
C: 어떤 값을 대입해도 상관없긴 하지만 오버플로를 막기 위해선 입력신호 중 최댓값을 넣음
👀 구현 코드 보기
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
[특징]
- 출력: 0에서 1.0 사이의 실수/출력의 총합은 1
-> 확률로 계산 가능 -
주의: 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지X
지수 함수 y = exp (x )가 단조 증가 함수이기 떄문
-> 신경망으로 분류할 때는 출력층의 소프트맥스 함수를 생략가능 - 기계학습의 2단계
학습: 소프트맥스 함수를 사용
추론: 소프트맥스 함수를 생략
출력층의 뉴런 수 정하기
예를 들어 입력 이미지를 숫자 0부터 9 중 하나로 분류하는 문제라면 아래 그림 처럼 출력층의 뉴런을 10개로 설정하면 된다.
즉 정답이 될 수 있는 경우의 수가 출력층의 뉴런수가 되는 것이다