앞 장에서 퍼셉트론의 네트워크를 봤을 때는 편향이 없었음.
편향을 구조에 명시하면 다음과 같음.
위 그림에서는 가중치가 b이고 입력이 1인 뉴런이 추가.
편향의 입력 신호는 항상 1이기에 회색으로 칠해 다른 뉴런과 구분함.
해당 퍼셉트론의 동작은 다음과 같다.
x1, x2, 1이라는 신호가 뉴런이 입력 - 각 신호에 가중치를 곱함 - 다음 뉴런에 전달 - 그 다음 뉴런에서 신호들의 값을 합함 - 그 합이 0을 넘으면 1을 출력/ 0을 넘지 않으면 0을 출력
조건 분기 동작(0을 넘으면 1 출력, 아니면 0 출력)을 따로 빼서 식 작성 가능.
3.1.3 활성화 함수의 등장
h(x) 함수처럼 입력 신호의 총합을 출력 신호로 변환하는 함수를 일반적으로 활성화 함수(activation function)라고 정의.
활성화라는 이름답게 입력 신호의 총합이 활겅화를 일으키는지응 정하는 역할을 함.
라는 식은 (1)가중치가 곱해진 입력 신호의 총합을 계산, (2) 이 합을 활성화 함수의 입력하는 2단계를 가짐. 따라서 다음과 같이 나타낼 수 있음.
가중치가 곱해진 입력 신호와 편향의 합을 a로 두고, a를 h()에 넣어 y를 출력하는 흐름은 구조로 다음과 같이 나타낼 수 있음.
가중치 신호를 조합한 결과가 a라는 노드가 되고, 활성화 함수 h()를 통과하여 y라는 노드로 변환되는 과정이 명시됨.
뉴런과 노드는 같은 것.
뉴런을 그릴 때는 보통 뉴런을 하나의 원으로 그리지만 신경망의 동작을 명확히 드러내고자 할 때는 오른쪽과 같이 활성화 과정을 명시하기도 함.
노트
일반적으로 단순 퍼셉트론은 단층 네트워크에서 계단 함수(임계값을 경계로 출력이 바뀌는 함수)를 활성화 함수로 사용한 함수를 나타내고, 다층 퍼셉트론은 신경망(여러 층으로 구성되고 시그모이드 함수 등을 사용하는 네트워크)를 나타냄.
활성화 함수는 임계값을 경계로 출력이 바뀌는데, 이런 함수를 계단함수라고 함.
= '퍼셉트론에서는 활성화 함수로 계단 함수를 이용함.
계단 함수 이외의 함수를 이용하면 퍼셉트론에서 신경망으로 나아갈 수 있음.
exp(-x)는 을 뜻하며, e는 자연상수로 2.7182...의 값을 갖는 실수.
신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달함.
퍼셉트론과의 차이는 활성화 함수 뿐임. 뉴런이 다층으로 이어진 구조와 신호 전달 방법은 같음.
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])
x
결과: array([-1., 1., 2.])
y = x > 0
y
결과: array([False, True, True], dtype=bool)
넘파이 배열에 부등호 연산을 수행하면 배열의 원소 각각에 부등호 연산을 수행한 bool 배열이 생성됨. (배열 x의 원소가 0보다 크면 True, 작으면 False로 반환한 새로운 배열 y 생성.)
우리가 원하는 건 int를 출력하는 함수라서 배열 y의 원소를 bool에서 int로 바꿔줌.
y = y.astype(np.int)
y
결과: array([0, 1, 1])
import numpy as np
import marplotlib.pylab as plt
def step_function(x):
return np.array(x > 0, dtype=np.int
x = np.arrange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # y축 범위 지정
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) # y축의 범위 지정
plt.show()
3.2.5 시그모이드 함수와 계단 함수 비교
두 함수는 그래프에서와 같이 '매끄러움'에 차이가 있음. 시그모이드 함수는 입력에 따라 출력이 연속적으로 변하는 곡선 모양이지만 계단 함수는 0을 기점으로 출력이 갑자기 바뀜.
= 시그모이드 함수의 곡선은 신경망 학습에 중요.
두 함수는 돌려주는 값에 차이가 있음. 계단 함수는 1, 0 중 하나만 돌려주지만 시그모이드 함수는 실수를 돌려줌.
= 퍼셉트론의 뉴런에서는 0과 1이 흘렸다면 신경망에서는 연속적인 실수가 흐름.
매끄러움에 차이가 있지만 두 함수 모두 입력이 작을 때 출력은 0에 가깝고, 입력이 커지면 출력이 1에 가까워지는 구조임.
= 두 함수는 입력이 중요하면 큰 값을 출력, 중요하지 않으면 작은 값을 출력.
두 함수의 출력은 항상 0 ~ 1 사이.
노트
신경망에선 활성화 함수로 비선형 함수를 사용해야 함. = 선형 함수는 사용 금물.
왜? 선형 함수를 사용하면 신경망의 층을 깊게 하는 의미가 없어짐.
선형 함수 사용 시 문제: 층을 아무리 깊게 해도 '은익층이 없는 네트워크'로도 똑같은 기능을 할 수 있음.
선형 함수인 를 활성화 함수로 사용한 3층 네트워크가 있다고 하자.
식으로 나타내면 가 됨.
이 계산은 처럼 곱셈을 3번 함.
그러면 이라고 하면 의 모양과 같게 됨.
= 따라서 은닉층이 없는 네트워크로 표현이 가능함.
선형 함수로는 층을 쌓는 이점을 살릴 수 없어 활성화 함수로는 비선형 함수를 사용해야 함.
최근 신경망 분야에서 사용하는 ReLU(Rectified Linear Unit, 렐루)함수.
ReLU는 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하면 0을 출력.
그래프와 수식처럼 ReLu함수는 간단한 함수.
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]
print(np.ndim(A))
결과:1
print(A.shape)
결과: (4,)
print(A.shape[0])
결과:4
B = np.array([[1, 2], [3, 4], [5, 6]])
print(B)
결과:
[[1 2]
[3 4]
[5 6]]
print(np.ndim(B))
결과: 2
print(B.shape)
결과: (3, 2)
A = np.array([[1, 2], [3, 4]])
print(A.shape)
결과: (2,2)
B = np.array([[5, 6], [7, 8]])
print(B.shape)
결과: (2,2)
np.dot(A, B) # A*B와는 다름
결과:
array([[19, 22],
[43, 50]])
A = np.array([[1, 2, 3], [4, 5, 6]])
print(A.shape)
결과: (2,3)
B = np.array([[1, 2], [3, 4], [5, 6]])
print(B.shape)
결과: (3,2)
np.dot(A, B)
결과:
array([[22, 28],
[49, 64]])
A = np.array([[1, 2], [3, 4], [5, 6]])
print(A.shape)
결과: (3, 2)
B= np.array([7, 8])
print(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층의 첫 번째 뉴런'으로 가는 신호 살펴보기
해당 그림에서는 편향을 뜻하는 뉴런인 이 추가됨.
편향은 오른쪽 아래 인덱스가 1개 뿐임. 앞 층의 편향 뉴런이 1개이기 때문.
를 수식으로 나타내면 다음과 같음.
X = np.array([1.0, 0.5]) # 1행 2열
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) # 2행 3열
B1 = np.array([0.1, 0.2, 0.3]) # 1행 3열
print(W1.shape)
>>> (2, 3)
print(X.shape)
>>> (2, )
print(B1.shape)
>>> (3, )
A1 = np.dot(X, W1) + B1 #(1,3) # 식을 구현
-이제 1층의 활성화 함수에서의 처리를 살펴보겠음.
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)
본 구현은 1층의 출력 Z1이 2층에 입력된다는 점만 제외하면 이전 구현과 같음.
이제 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
노트
출력층의 활성화 함수는 풀고자하는 성질에 맞게 정해야 함. 회귀에는 항등 함수/ 2클래스 분류에는 시그모이드 함수/ 다중 클래스 분류에는 소프트맥스 함수를 사용.