본문 바로가기

딥러닝/밑바닥부터 시작하는 딥러닝

3장 : 신경망

  • 퍼셉트론의 가중치 - 사람이 수동적으로 설정해야 함
  • 신경망 : 가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력
  • 신경망이 입력 데이터가 무엇인지 식별하는 처리 과정 : 활성화 함수

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

3.1.3 활성화 함수 

: 입력 신호의 총합을 출력 신호로 변환하는 함수 

  • 입력 신호의 총합이 활성화를 일으키는 지 정하는 역할 
  • 단순 퍼셉트론 : 단층 네트워크에서 계단 함수를 활성화 함수로 사용한 모델
  • 다층 퍼셉트론 : 신경망 (여러 층으로 구성, 시그모이드 함수 등의 매끈한 활성화 함수 사용하는 네트워크)

3.2 활성화 함수

3.2.1 시그모이드 함수 

참고: https://gooopy.tistory.com/52

 

3.2.2 계단 함수 구현

import numpy as np
import matplotlib.pylab as plt

#계단 함수 정의
def step_function(x):
  y = x > 0
  return y.astype(np.int)

x = np.arange(-5.0, 5.0, 0.1)  #-5에서 5 사이 0.1간격의 넘파이 배열
y = step_function(x)

#그래프
plt.plot(x,y)
plt.ylim(-0.1, 1.1)  #y축의 범위 지정
plt.show()
  • 계단 함수
    • y : 0보다 크면 True, 작으면 False -> int로 변환 (true는 1, false는 0)

 

3.2.4 시그모이드 함수 구현

#시그모이드 함수 정의
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)
plt.show
  • 시그모이드 함수
    • np.exp(-x) : exp(-x) 수식
    • x가 넘파이 배열이어도 처리 가능
    • S자 모양

 

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

  • 차이점
    • 계단 : 0과 1 중 하나의 값만 출력
    • 시그모이드 : 연속적인 실수(0.731...0.880)를 출력
  • 공통점
    • 입력이 작을 때의 출력은 0에 가깝거나 0
    • 입력이 클 때의 출력은 1에 가깝거나 1

 

3.2.6 비선형 함수

  • 신경망에서는 활성화 함수로 비선형 함수를 사용해야 함
  • 선형 함수를 이용해서는 여러 층으로 구성하는 이점을 살릴 수 없기 때문

 

3.2.7 ReLU 함수

: 입력이 0을 넘으면 그 입력을 그대로 출력, 0 이하이면 0을 출력하는 함수

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

3.3 다차원 배열의 계산

3.3.1 다차원 배열

#1차원
A = np.array([1,2,3,4])
print(A)
print(np.ndim(A))  #차원 수 확인
print(A.shape)
print(A.shape[0])

[1 2 3 4]

1

(4,)

4

#2차원 (행렬)
B = np.array([[1,2], [3,4], [5,6]])
print(B)
print(np.ndim(B))
print(B.shape)

[[1 2]

[3 4]

[5 6]]

2

(3, 2)  #행/열

 

3.3.2 행렬의 곱

#행렬의 곱
A = np.array([[1,2], [3,4]])
B = np.array([[5,6],[7,8]])

np.dot(A,B)

array([[19, 22],

            [43, 50]])

#형상이 다른 행렬의 곱
A = np.array([[1,2,3], [4,5,6]])  #2*3
B = np.array([[1,2],[3,4], [5,6]])  #3*2

np.dot(A,B)

array([[22, 28],

           [49, 64]])

  • 행렬 A의 1번째 차원의 원소 수 (열 수)와 행렬 B의 0번째 차원의 원소 수(행 수)가 같아야 함( 2*3 / 3*2)
  • 계산 결과 : 행렬 A의 행 수와 행렬 B의 열수 (2*3 / 3*2)

 

3.3.3 신경망에서의 행렬 곱

X = np.array([1,2])  #입력값
W = np.array([[1,3,5], [2,4,6]])  #가중치

Y = np.dot(X,W)  #출력값
print(Y)

[ 5 11 17]

  • 편향과 활성화 함수는 생략

 

3.4 3층 신경망 구현하기

def init_network():
  network = {}
  network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])  #1층 가중치
  network['b1'] = np.array([0.1, 0.2, 0.3])  #1층 편향
  network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])  #2층 가중치
  network['b2'] = np.array([0.1,0.2])  #2층 편향
  network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])  #3층 가중치
  network['b3'] = np.array([0.1, 0.2])  #3층 편향
  
  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)  #1층 활성화함수 출력값
  a2 = np.dot(z1, W2) +b2  #2층
  z2 = sigmoid(a2)  #2층 활성화함수 출력값
  a3 = np.dot(z2, W3) +b3  #3층
  y = 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 출력층 설계하기 

기계학습 

  • 분류 -> 소프트맥스 함수
  • 회귀 -> 항등 함수 

소프트맥스 함수 : https://gooopy.tistory.com/53

 

3.5.2 소프트맥스 함수 구현 시 주의점

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
  • 지수함수 exp()는 큰 값을 출력 -> 큰 값끼리 나눗셈 하면 결과 수치 불안정 : 오버플로 문제
  • 오버플로 : 컴퓨터는 표현할 수 있는 수의 범위 한정, 너무 큰 값 표현할 수 없음 
  • 소프트맥스 함수의 지수 함수는 어떤 정수를 빼거나 더해도 결과 바뀌지 않음 
  • 입력 신호 중 최댓값을 빼는 것이 일반적 (a-c)

 

3.5.3 소프트맥스 함수의 특징

a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y)
np.sum(y)
[0.01821127 0.24519181 0.73659691]
1.0
  • 0에서 1.0 사이의 실수 출력
  • 출력의 총합 1 -> 확률로 해석 가능
  • 원소의 대소 관계는 변하지 않음 (입력값이 크면 출력값도 큼)
    -> 추론 단계
    에서는 출력층의 소프트맥스 함수 생략
  • 학습시킬 때는 사용
     

3.5.4 출력층의 뉴런 수 정하기

  • 분류 : 분류하고 싶은 클래스 수로 설정하는 것이 일반적

 


3.6 손글씨 숫자 인식

3.6.1 MNIST 데이터셋

  • 데이터셋 py 다운 후 구글드라이브에 업로드
  • 구글 드라이브 마운트
import sys, os
sys.path.append("/content/drive/MyDrive/Colab Notebooks/밑바닥딥러닝/dataset")  #파일 경로
from mnist import load_mnist  ##mnist.py파일에 정의된 load_mnist()함수 이용
#다운로드
(X_train, t_train), (X_test, t_test) = \  #(훈련이미지, 훈련레이블), (시험이미지, 시험레이블)
  load_mnist(flatten=True, normalize=False)

load_mnist의 인수

  • nomalize(정규화) : 입력 이미지의 픽셀 값을 0.0~1.0 사이의 값으로 정규화 (false : 원래 값인 0~255) -> 전처리 
  • flatten : 입력 이미지를 1차원 배열로 만듦 (True: 784,  false : 1*28*28)
  • one_hot_label : 원-핫 인코딩 = 정답을 뜻하는 원소만 1, 나머지는 모두 0
img = X_train[0]  #첫번째 훈련 이미지
label = t_train[0]
print(label)  #5
print(img.shape)  #flatten 결과 (784,)
img = img.reshape(28,28)
print(img.shape)

 

3.6.2 신경망의 추론 처리

import numpy as np
import pickle  #프로그램 실행 중에 특정 객체를 파일로 저장, 데이터 빠르게 준비 가능

def get_data():
  (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
  return x_test, t_test

def init_network():
  with open("/content/drive/MyDrive/Colab Notebooks/밑바닥딥러닝/dataset/sample_weight.pkl", 'rb') as f:
        network = pickle.load(f) #학습된 가중치 매개변수: 가중치, 편향 매개변수가 딕셔너리 변수로 되어 있음

  return network

def predict(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)  #1층 활성화함수 출력값
  a2 = np.dot(z1, W2) +b2  #2층
  z2 = sigmoid(a2)  #2층 활성화함수 출력값
  a3 = np.dot(z2, W3) +b3  #3층
  y = softmax(a3)

  return y
#정확도 평가
x, t = get_data()
network = init_network()

accuracy_cnt = 0
for i in range(len(x)):
  y = predict(network, x[i])
  p = np.argmax(y)  #확률이 가장 높은 원소의 인덱스 
  if p == t[i]:
    accuracy_cnt +=1

print("Accuracy:" +str(float(accuracy_cnt / len(x))))  #정확도 : 맞힌 횟수 / 전체 이미지 숫자

Accuracy:0.9352

 

3.6.3 배치 처리

  • 배치 : 하나로 묶은 입력 데이터, 배치가 곧 묶음
  • 컴퓨터에서는 큰 배열을 한꺼번에 계산하는 것이 분할된 작은 배열을 여러 번 계산하는 것보다 빠름
    -> 배치 처리로 큰 배열로 이뤄진 계산 
#배치 처리 
x, t = get_data()
network = init_network()

batch_size = 100  #배치 크기
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
  x_batch = x[i:i+batch_size]
  y_batch = predict(network, x_batch)
  p = np.argmax(y_batch, axis=1)  #axis=1 : 행을 따라 최대값, axis=0 :열을 따라 최대값
  accuracy_cnt +=np.sum(p==t[i:i+batch_size])  #True인, p와 t가 같은 개수 셈

print("Accuray:" + str(float(accuracy_cnt)/len(x)))

3.7 정리

  • 신경망에서는 활성화 함수로 Sigmoid와 ReLU 같은 매끄럽게 변화하는 함수를 이용한다.
  • Numpy의 다차원 배열을 잘 사용하면, 신경망을 효율적으로 구현할 수 있다.
  • 기계학습 문제는 크게 회귀와 분류로 나눌 수 있다.
  • 출력층의 활성화 함수로는 회귀에서 주로 항등함수를, 분류에서는 주로 소프트맥스 함수를 이용한다.
  • 분류에서는 출력층의 뉴런 수를 분류하려는 클래스 수와 같게 설정한다.
  • 입력 데이터를 묶은 것을 배치라 하며, 추론 처리를 이 배치 단위로 진행하면 결과를 훨씬 빠르게 얻을 수 있다.

책 참고 : 밑바닥부터 시작하는 딥러닝 (한빛미디어)

 

'딥러닝 > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글

6장 : 학습 관련 기술들  (0) 2023.09.15
5장 : 오차역전파법  (0) 2023.09.13
4장 : 신경망 학습  (0) 2023.09.06
2장 : 퍼셉트론  (0) 2023.09.04
1장 : 헬로 파이썬  (0) 2023.09.02