지시딥 3. 신경망

곽정은·2021년 1월 19일
1

스터디

목록 보기
3/19
  • 퍼셉트론은 이론상 컴퓨터를 표현할 수 있을 정도로 복잡한 함수도 표현할 수 있지만, 원하는 결과를 출력하도록 가중치를 적절히 설절하는 작업은 여전히 사람이 수동으로 해야 함.
  • 신경망은 가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력이 있어 사람이 수동으로 가중치를 설정해야 하는 문제를 해결해 줌.

3.1 퍼셉트론에서 신경망으로

3.1.1 신경망의 예

  • 은닉층(맨 왼쪽)의 뉴런은 입력층니아 출력층과는 달리 사람 눈에 보이지 않음.
  • 책에서는 입력층, 은닉층, 출력층을 각각 0층, 1층, 2층으로 부르겠음.
  • 위 그림처럼 층이 3개인 신경망의 경우, 실제 가중치를 가진 층은 2개 뿐임을 알 수 있음.
    = 따라서 실제로 가중치를 갖는 층의 개수는 입력층, 은닉층, 출력층의 합계 - 1로 함.

3.1.2 퍼셉트론 복습

  • 앞 장에서 퍼셉트론의 네트워크를 봤을 때는 편향이 없었음.

  • 편향을 구조에 명시하면 다음과 같음.

  • 위 그림에서는 가중치가 b이고 입력이 1인 뉴런이 추가.

  • 편향의 입력 신호는 항상 1이기에 회색으로 칠해 다른 뉴런과 구분함.

  • 해당 퍼셉트론의 동작은 다음과 같다.

    x1, x2, 1이라는 신호가 뉴런이 입력 - 각 신호에 가중치를 곱함 - 다음 뉴런에 전달 - 그 다음 뉴런에서 신호들의 값을 합함 - 그 합이 0을 넘으면 1을 출력/ 0을 넘지 않으면 0을 출력

  • 조건 분기 동작(0을 넘으면 1 출력, 아니면 0 출력)을 따로 빼서 식 작성 가능.

y=h(b+w1x1+w2x2)y = h(b+w_1x_1+w_2x_2)
h(x)={0(x0)1(x>0)h(x)=\begin{cases}0(x\leq0)\\1(x>0)\end{cases}

  • 위 식은 입력 신호의 총합이 h(x)라는 함수를 거쳐 변환되어, 그 변환된 값이 y의 출력이 됨을 보여줌.
  • 위 식은 y={0(b+w1x1+w2x20)1(b+w1x1+w2x2>0)y =\begin{cases} 0 (b+w_1x_1+w_2x_2\leq0)\\ 1 (b+w_1x_1+w_2x_2>0) \end{cases}와 같음.

3.1.3 활성화 함수의 등장

  • h(x) 함수처럼 입력 신호의 총합을 출력 신호로 변환하는 함수를 일반적으로 활성화 함수(activation function)라고 정의.

  • 활성화라는 이름답게 입력 신호의 총합이 활겅화를 일으키는지응 정하는 역할을 함.

  • y=h(b+w1x1+w2x2)y = h(b+w_1x_1+w_2x_2)라는 식은 (1)가중치가 곱해진 입력 신호의 총합을 계산, (2) 이 합을 활성화 함수의 입력하는 2단계를 가짐. 따라서 다음과 같이 나타낼 수 있음.

a=b+w1x1+w2x2a=b+w_1x_1+w_2x_2
y=h(a)y=h(a)

  • 가중치가 곱해진 입력 신호와 편향의 합을 a로 두고, a를 h()에 넣어 y를 출력하는 흐름은 구조로 다음과 같이 나타낼 수 있음.

  • 가중치 신호를 조합한 결과가 a라는 노드가 되고, 활성화 함수 h()를 통과하여 y라는 노드로 변환되는 과정이 명시됨.

  • 뉴런과 노드는 같은 것.

  • 뉴런을 그릴 때는 보통 뉴런을 하나의 원으로 그리지만 신경망의 동작을 명확히 드러내고자 할 때는 오른쪽과 같이 활성화 과정을 명시하기도 함.


노트
일반적으로 단순 퍼셉트론은 단층 네트워크에서 계단 함수(임계값을 경계로 출력이 바뀌는 함수)를 활성화 함수로 사용한 함수를 나타내고, 다층 퍼셉트론은 신경망(여러 층으로 구성되고 시그모이드 함수 등을 사용하는 네트워크)를 나타냄.


3.2 활성화 함수

  • 활성화 함수는 임계값을 경계로 출력이 바뀌는데, 이런 함수를 계단함수라고 함.
    = '퍼셉트론에서는 활성화 함수로 계단 함수를 이용함.

  • 계단 함수 이외의 함수를 이용하면 퍼셉트론에서 신경망으로 나아갈 수 있음.

3.2.1 시그모이드 함수

h(x)=11+exp(x)h(x) = \frac{1}{1+exp(-x)}

  • exp(-x)는 exe^{-x}을 뜻하며, e는 자연상수로 2.7182...의 값을 갖는 실수.

  • 신경망에서는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달함.

  • 퍼셉트론과의 차이는 활성화 함수 뿐임. 뉴런이 다층으로 이어진 구조와 신호 전달 방법은 같음.

3.2.2 계단 함수 구현하기

  • 계단 함수는 입력이 0을 넘으면 1을 출력하고, 그 외에는 0을 출력하는 함수.
def step_function(x):
	if x > 0:
    	return 1
    else:
    	return 0
  • 단순한 구현으로 인수 x는 실수(부동소수점)만 수용함. (Ex. step_function(3.0))
  • 넘파이 배열을 인수로 넣을 수 없음. (Ex. step_function(np.array([1.0, 2.0]))
def step_function(x):
	y = x > 0
    return y.astype(np.int)
  • 넘파이의 기능을 사용해서 넘파이 배열을 인수로 받게 만들 수 있음.
  • 위 코드는 x라는 넘파이 배열을 준비해서 그 배열로 부등호 연산을 수행.
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])
  • 넘파이 배열의 자료형을 변환 시에는 astype(원하는 자료형)메서드 사용.
  • bool을 int로 변환하면 True는 1로, False는 0으로 변환됨.

3.2.3 계단 함수의 그래프

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
  • np.arrange(-5.0, 5.0, 0.1)은 -5.0에서 5.0까지 0.1 간격으로 넘파이 배열을 생성.
  • step_function은 인수로 받은 넘파이 배열의 원소 각각을 인수로 계단 함수를 실행하고, 그 결과를 다시 배열로 만들어 돌려줌.
  • 다음과 같은 그래프로 나타남.
  • 보다시피 계단 함수를 0을 기점으로 출력이 0에서 1로 바뀜.

3.2.4 시그모이드 함수 구현하기

def sigmoid(x):
	return 1 (1+np.exp(-x))
  • np.exp(-x)는 exp(-x) 수식에 해당.
  • 인수 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()

  • 시그모이드 함수는 S자 모양.

3.2.5 시그모이드 함수와 계단 함수 비교

  • 두 함수는 그래프에서와 같이 '매끄러움'에 차이가 있음. 시그모이드 함수는 입력에 따라 출력이 연속적으로 변하는 곡선 모양이지만 계단 함수는 0을 기점으로 출력이 갑자기 바뀜.
    = 시그모이드 함수의 곡선은 신경망 학습에 중요.

  • 두 함수는 돌려주는 값에 차이가 있음. 계단 함수는 1, 0 중 하나만 돌려주지만 시그모이드 함수는 실수를 돌려줌.
    = 퍼셉트론의 뉴런에서는 0과 1이 흘렸다면 신경망에서는 연속적인 실수가 흐름.

  • 매끄러움에 차이가 있지만 두 함수 모두 입력이 작을 때 출력은 0에 가깝고, 입력이 커지면 출력이 1에 가까워지는 구조임.
    = 두 함수는 입력이 중요하면 큰 값을 출력, 중요하지 않으면 작은 값을 출력.

  • 두 함수의 출력은 항상 0 ~ 1 사이.

3.2.6 비선형 함수

  • 계단 함수(구부러진 직선)와 시그모이드 함수(곡선) 모두 비선형 함수임.

노트

  • 선형 함수(직선): 입력 시 출력이 입력의 상수배만큼 변하는 함수. (f(x)=ax+bf(x)=ax+b(a, b는 상수))
  • 비선형 함수(직선X): 직선 1개로는 그릴 수 없는 함수.

  • 신경망에선 활성화 함수로 비선형 함수를 사용해야 함. = 선형 함수는 사용 금물.

  • 왜? 선형 함수를 사용하면 신경망의 층을 깊게 하는 의미가 없어짐.

  • 선형 함수 사용 시 문제: 층을 아무리 깊게 해도 '은익층이 없는 네트워크'로도 똑같은 기능을 할 수 있음.

    선형 함수인 h(x)=cxh(x)=cx를 활성화 함수로 사용한 3층 네트워크가 있다고 하자.
    식으로 나타내면 y(x)=h(h(h(x)))y(x)=h(h(h(x)))가 됨.
    이 계산은 y(x)=cccxy(x)=c*c*c*x처럼 곱셈을 3번 함.
    그러면 a=c3a=c^3이라고 하면 y(x)=axy(x)=ax의 모양과 같게 됨.
    = 따라서 은닉층이 없는 네트워크로 표현이 가능함.

  • 선형 함수로는 층을 쌓는 이점을 살릴 수 없어 활성화 함수로는 비선형 함수를 사용해야 함.

3.2.7 ReLU

  • 최근 신경망 분야에서 사용하는 ReLU(Rectified Linear Unit, 렐루)함수.

  • ReLU는 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하면 0을 출력.

    h(x)={x(x>0)0(x0)h(x)=\begin{cases}x(x>0)\\0(x\leq0)\end{cases}

  • 그래프와 수식처럼 ReLu함수는 간단한 함수.

ReLu함수 구현

def relu(x):
	return np.maximum(0, x)
  • 넘파이의 maximum 함수는 두 입력 중 큰 값을 선택해 반환하는 함수.

3.3 다차원 배열의 계산

3.3.1 다차원 배열

  • 1차원, 2차원, 3차원, n차원 배열을 모두 다차원 배열이라 함.

1차원 배열

import numpy as np
A = np.array([1, 2, 3, 4])
print(A)
결과: [1 2 3 4]
print(np.ndim(A)) 
결과:1
  • np.ndim()함수는 배열의 차원의 갯수를 알려줌.
print(A.shape) 
결과: (4,)
  • 배열의 형상은 인스턴스 변수인 shape로 알 수 있음. 또한 이는 튜플을 반환함.
    = 1차원 배열이라고 다차원 배열일 때와 통일된 결과를 반환하기 위함.
print(A.shape[0])
결과:4

2차원 배열

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)
  • 3x2 배열은 처음 차원(0차원)에 원소 3개, 다음 차원(1차원) 원소 2개가 있다는 말.

3.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]])
  • 두 행렬의 곱은 np.dot()으로 계산. np.dot()은 입력이 1차원이면 벡터를, 2차원이면 행렬 곱을 계산함.
  • 주의점은 np.dot(A, B)와 np.dot(B, A)는 다른 값이 될 수 있다는 것. 피연산자의 순서가 다르면 결과도 다름.

형상이 다른 행렬의 곱

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의 1번째 차원의 원소 수(열 수) = 행렬 B의 0번째 차원의 원소 수(행 수)여야 함.
  • 이 값이 다르면 행렬의 곱을 계산할 수 없음. 다차원 배열을 곱하려면 두 행렬의 대응하는 차원의 원소 수를 일치시켜야 함.
  • 계산 결과인 행렬의 형상은 행렬 A의 행 수와 행렬 B의 열 수가 됨.
  • 행렬 A가 2차원이고 행렬 B가 1차원이어도 '대응하는 차원의 원소 수를 일치시켜라'라는 원칙은 같이 적용됨.

2차원과 1차원의 곱

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])

3.3.3 신경망에서의 행렬의 곱

  • 위 신경망은 편향과 활성화 함수를 생략하고 가중치마 갖음.

넘파이 행렬로 신경망 구현

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]
  • 다차원 배여릐 스칼라 곱을 구해주는 np.dot() 함수를 사용하면 단번에 결과를 구할 수 있음.
  • np.dot() 함수를 사용하지 않으면 결과의 원소를 하나씩 찾아봐야 함.
    = 행렬의 곱으로 한꺼번에 계산해주는 기능은 신경망 구현에 필수적임.

3.4 3층 신경망 구현하기

  • 3층 신경망에서 수앤되는 입력부터 출력까지의 처리(순방향 처리)를 구현할 것.
  • 3층 신경망은 입력층 2개, 첫번째 은닉층 3개, 두번째 은닉층 2개, 출력층 2개로 이루어짐.

3.4.1 표기법 설명

  • 위 그림은 입력층의 뉴런 x2x_2에서 다음 층의 뉴런 a1(1)a_1^{(1)}으로 향하는 선 위에 가중치를 표시.
  • 가중치와 은닉층 뉴런의 오른쪽 위에 있는 (1)^{(1)}는 1층의 가중치, 1층의 뉴런임을 뜻함.
  • W12(1)W_{12}^{(1)}에서처럼 가중치의 오른쪽 아래의 두 숫자는 차례로 다음 층 뉴런과 앞 층 뉴런의 인덱스 번호임.
  • W12(1)W_{12}^{(1)}은 앞 층의 2번째 뉴런(x2x_2)에서 다음 층의 1번째 뉴런(a1(1)a_1^{(1)})으로 향할 때의 가중치라는 뜻.
  • 가중치 오른쪽 아래의 인덱스 번호는 '다음 층 번호, 앞 층 번호' 순으로 적음.

3.4.2 각 층의 신호 전달 구현하기

  • 입력층에서 '1층의 첫 번째 뉴런'으로 가는 신호 살펴보기

  • 해당 그림에서는 편향을 뜻하는 뉴런인 b1(1)b_1^{(1)}이 추가됨.

  • 편향은 오른쪽 아래 인덱스가 1개 뿐임. 앞 층의 편향 뉴런이 1개이기 때문.

  • a1(1)a_1^{(1)}를 수식으로 나타내면 다음과 같음.

a1(1)=W11(1)x1+W12(1)x2+b1(1)a_1^{(1)}=W_{11}^{(1)}x_1+W_{12}^{(1)}x_2+b_1^{(1)}

  • 행렬 곱을 이용하면 1층의 가중치 부분을 다음 식처럼 간소화 가능.

A(1)=XW(1)+B(1)A^{(1)}=XW^{(1)}+B^{(1)}

  • 이때 각 행렬은 다음과 같음.

A(1)=(a1(1),a2(1),a3(1))A^{(1)}=(a_1^{(1)}, a_2^{(1)}, a_3^{(1)})

X=(x1,x1,x2)X=(x_1, x_1,x_2)

B(1)=(b1(1),b2(1),b3(1))B^{(1)}=(b_1^{(1)}, b_2^{(1)}, b_3^{(1)})

W(1)=(w11(1)w21(1)w31(1)w12(1)w22(1)w32(1)))W^{(1)}=\left(\begin{array}{ccc}w_{11}^{(1)}&w_{21}^{(1)}&w_{31}^{(1)}\\w_{12}^{(1)}&w_{22}^{(1)}&w_{32}^{(1)})\end{array}\right)

넘파이 다차원 배열로 A(1)=XW(1)+B(1)A^{(1)}=XW^{(1)}+B^{(1)} 구현

X = np.array([1.0, 0.5]) # 12열
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) # 23열
B1 = np.array([0.1, 0.2, 0.3])  # 13print(W1.shape)
>>> (2, 3)
print(X.shape)
>>> (2, )
print(B1.shape)
>>> (3, )

A1 = np.dot(X, W1) + B1 #(1,3) # 식을 구현

-이제 1층의 활성화 함수에서의 처리를 살펴보겠음.

  • 은닉층에서의 가중치 합(가중 신호와 편향의 종합) = a
  • 활성화 함수 h()h()로 변환된 신호 = z (여기선 시그모이드)
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
  • 입출력을 그대로 출력하는 항등 함수인 identity_function()을 정의하고, 출력층의 활성화 함수로 사용.
  • 그림에서는 출력층의 활성화 함수를 σ()\sigma()로 표시하여 은닉층의 활성화 함수(h()h())와 다름을 명시.

노트
출력층의 활성화 함수는 풀고자하는 성질에 맞게 정해야 함. 회귀에는 항등 함수/ 2클래스 분류에는 시그모이드 함수/ 다중 클래스 분류에는 소프트맥스 함수를 사용.


출처

profile
인공지능 냉각시스템 개발기업 전략기획

0개의 댓글