본문 바로가기
ML & DL/책 & 강의

[밑시딥2] CHAPTER 1 신경망 복습

by 공부하는 무니 2023. 7. 3.
반응형

1.1 수학과 파이썬 복습

1.1.1 벡터와 행렬

벡터와 행렬의 예
벡터의 표현법

수학과 딥러닝 등 많은 분야에서 '열벡터' 방식을 선호하지만, 이 책에서는 구현 편의를 고려해 '행벡터'로 다룬다.

1.1.2 행렬의 원소별 연산

다차원 배열들에서 서로 대응하는 원소끼리 (각 원소가 독립적으로) 연산이 이뤄지는 것을 넘파이 배열의 '원소별 연산'이라고 한다.

1.1.3 브로드캐스트

1.1.4 벡터의 내적과 행렬의 곱

1.1.5 행렬 형상 확인

1.2 신경망의 추론

1.2.1 신경망 추론 전체 그림

1.2.2 계층으로 클래스화 및 순전파 구현

- 모든 계층은 forward()와 backward() 메서드를 가진다.

- 모든 계층은 인스턴스 변수인 params 와 grads를 가진다.

class Sigmoid:
    def __init__(self):
        self.params, self.grads = [], []
        self.out = None

    def forward(self, x):
        out = 1 / (1 + np.exp(-x))
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out
        return dx

class Affine:
    def __init__(self, W, b):
        self.params = [W, b]
        self.grads = [np.zeros_like(W), np.zeros_like(b)]
        self.x = None

    def forward(self, x):
        W, b = self.params
        out = np.dot(x, W) + b
        self.x = x
        return out

    def backward(self, dout):
        W, b = self.params
        dx = np.dot(dout, W.T)
        dW = np.dot(self.x.T, dout)
        db = np.sum(dout, axis=0)

        self.grads[0][...] = dW
        self.grads[1][...] = db
        return dx

1.3 신경망의 학습

1.3.1 손실 함수

신경망의 손실은 손실 함수를 사용해 구한다. 다중 클래스 분류 신경망에서는 손실 함수로 흔히 교차 엔트로피 오차를 이용한다. 

소프트맥스는 아래 식과 같다.

교차 엔트로피 오차의 수식은 다음과 같다.

미니배치를 고려하면 다음과 같다.

이 책에서는 소프트맥스 함수와 교차 엔트로피 오차를 계산하는 계층을 Softax with Loss 계층 하나로 구현한다.

1.3.2 미분과 기울기

y=x^2라는 함수를 예로 들면 기울기는 아래와 같이 표현할 수 있다.

1.3.3 연쇄 법칙

연쇄 법칙이 중요한 이유는 우리가 다루는 함수가 아무리 복잡하다 하더라도, 그 미분은 개별 함수의 미분들을 이용해 구할 수 있기 때문이다.

1.3.4 계산 그래프

곱셈 노드

분기 노드

Repeat 노드

Sum 노드

MatMul 노드

class MatMul:
    def __init__(self, W):
        self.params = [W]
        self.grads = [np.zeros_like(W)]
        self.x = None

    def forward(self, x):
        W, = self.params
        out = np.dot(x, W)
        self.x = x
        return out

    def backward(self, dout):
        W, = self.params
        dx = np.dot(dout, W.T)
        dW = np.dot(self.x.T, dout)
        self.grads[0][...] = dW
        return dx

1.3.5 기울기 도출과 역전파 구현

sigmoid 계층

class Sigmoid:
    def __init__(self):
        self.params, self.grads = [], []
        self.out = None

    def forward(self, x):
        out = 1 / (1 + np.exp(-x))
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out
        return dx

Affine 계층

class Affine:
    def __init__(self, W, b):
        self.params = [W, b]
        self.grads = [np.zeros_like(W), np.zeros_like(b)]
        self.x = None

    def forward(self, x):
        W, b = self.params
        out = np.dot(x, W) + b
        self.x = x
        return out

    def backward(self, dout):
        W, b = self.params
        dx = np.dot(dout, W.T)
        dW = np.dot(self.x.T, dout)
        db = np.sum(dout, axis=0)

        self.grads[0][...] = dW
        self.grads[1][...] = db
        return dx

Softmax with Loss 계층

1.3.6 가중치 갱신

신경망의 학습 순서

- 1단계: 미니배치

- 2단계: 기울기 계산

- 3단계: 매개변수 갱신

- 4단계: 반복

 

SGD 수식은 다음과 같다.

코드로 구현하면 다음과 같다.

class SGD:
    '''
    확률적 경사하강법(Stochastic Gradient Descent)
    '''
    def __init__(self, lr=0.01):
        self.lr = lr
        
    def update(self, params, grads):
        for i in range(len(params)):
            params[i] -= self.lr * grads[i]

1.4 신경망으로 문제를 풀다 

1.4.1 스파이럴 데이터셋

# coding: utf-8
import sys
sys.path.append('..')  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
from dataset import spiral
import matplotlib.pyplot as plt


x, t = spiral.load_data()
print('x', x.shape)  # (300, 2)
print('t', t.shape)  # (300, 3)

# 데이터점 플롯
N = 100
CLS_NUM = 3
markers = ['o', 'x', '^']
for i in range(CLS_NUM):
    plt.scatter(x[i*N:(i+1)*N, 0], x[i*N:(i+1)*N, 1], s=40, marker=markers[i])
plt.show()

1.4.2 신경망 구현

# coding: utf-8
import sys
sys.path.append('..')  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from common.layers import Affine, Sigmoid, SoftmaxWithLoss


class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size):
        I, H, O = input_size, hidden_size, output_size

        # 가중치와 편향 초기화
        W1 = 0.01 * np.random.randn(I, H)
        b1 = np.zeros(H)
        W2 = 0.01 * np.random.randn(H, O)
        b2 = np.zeros(O)

        # 계층 생성
        self.layers = [
            Affine(W1, b1),
            Sigmoid(),
            Affine(W2, b2)
        ]
        self.loss_layer = SoftmaxWithLoss()

        # 모든 가중치와 기울기를 리스트에 모은다.
        self.params, self.grads = [], []
        for layer in self.layers:
            self.params += layer.params
            self.grads += layer.grads

    def predict(self, x):
        for layer in self.layers:
            x = layer.forward(x)
        return x

    def forward(self, x, t):
        score = self.predict(x)
        loss = self.loss_layer.forward(score, t)
        return loss

    def backward(self, dout=1):
        dout = self.loss_layer.backward(dout)
        for layer in reversed(self.layers):
            dout = layer.backward(dout)
        return dout

1.4.3 학습용 코드

# coding: utf-8
import sys
sys.path.append('..')  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from common.optimizer import SGD
from dataset import spiral
import matplotlib.pyplot as plt
from two_layer_net import TwoLayerNet


# 하이퍼파라미터 설정
max_epoch = 300
batch_size = 30
hidden_size = 10
learning_rate = 1.0

# 데이터 읽기, 모델과 옵티마이저 생성
x, t = spiral.load_data()
model = TwoLayerNet(input_size=2, hidden_size=hidden_size, output_size=3)
optimizer = SGD(lr=learning_rate)

# 학습에 사용하는 변수
data_size = len(x)
max_iters = data_size // batch_size
total_loss = 0
loss_count = 0
loss_list = []

for epoch in range(max_epoch):
    # 데이터 뒤섞기
    idx = np.random.permutation(data_size)
    x = x[idx]
    t = t[idx]

    for iters in range(max_iters):
        batch_x = x[iters*batch_size:(iters+1)*batch_size]
        batch_t = t[iters*batch_size:(iters+1)*batch_size]

        # 기울기를 구해 매개변수 갱신
        loss = model.forward(batch_x, batch_t)
        model.backward()
        optimizer.update(model.params, model.grads)

        total_loss += loss
        loss_count += 1

        # 정기적으로 학습 경과 출력
        if (iters+1) % 10 == 0:
            avg_loss = total_loss / loss_count
            print('| 에폭 %d |  반복 %d / %d | 손실 %.2f'
                  % (epoch + 1, iters + 1, max_iters, avg_loss))
            loss_list.append(avg_loss)
            total_loss, loss_count = 0, 0


# 학습 결과 플롯
plt.plot(np.arange(len(loss_list)), loss_list, label='train')
plt.xlabel('반복 (x10)')
plt.ylabel('손실')
plt.show()

# 경계 영역 플롯
h = 0.001
x_min, x_max = x[:, 0].min() - .1, x[:, 0].max() + .1
y_min, y_max = x[:, 1].min() - .1, x[:, 1].max() + .1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
X = np.c_[xx.ravel(), yy.ravel()]
score = model.predict(X)
predict_cls = np.argmax(score, axis=1)
Z = predict_cls.reshape(xx.shape)
plt.contourf(xx, yy, Z)
plt.axis('off')

# 데이터점 플롯
x, t = spiral.load_data()
N = 100
CLS_NUM = 3
markers = ['o', 'x', '^']
for i in range(CLS_NUM):
    plt.scatter(x[i*N:(i+1)*N, 0], x[i*N:(i+1)*N, 1], s=40, marker=markers[i])
plt.show()

1.4.4 Trainer 클래스

학습을 수행하는 역할을 Trainer라고 하는 클래스로 제공한다. 

1.5 계산 고속화

1.5.1 비트 정밀도

넘파이의 부동소수점 수는 기본적으로 64비트 데이터 타입을 사용한다.

신경망의 추론과 학습은 32비트 부동소수점 수로도 문제없이 수행할 수 있다. 따라서 책에서는 32비트 부동소수점 수를 우선으로 사용한다. 32비트 부동소수점을 사용하려면 아래와 같이 데이터타입을 np.float32나 'f'로 지정하면 된다.

또한 신경망 추론으로 한정하면 16비트 부동소수점 수를 사용해도 인식률이 거의 떨어지지 않는다. 넘파이에도 16비트 부동소수점 수가 준비되어 있다. 다만, CPU와 GPU는 연산 자체를 32비트로 수행하므로 16비트 부동소수점으로 변환하더라도 계산 자체는 32비트로 이루어져서 처리 속도 면에서는 혜택이 없을 수 있다. 다만 학습된 가중치를 16비트 부동소수점 으로 저장하면 절반의 용량만 사용하므로 메모리를 아낄 수 있다. 또한 최근 GPU들은 '저장'과 '연산' 모두 16비트 반정밀도 부동소수점 수를 지원한다. 심지어 TPU 칩은 8비트 계산도 지원한다. 

1.5.2 GPU(쿠파이)

이 책의 예제 중에는 쿠파이라는 파이썬 라이브러리를 사용하는 예제가 있다. 쿠파이는 GPU를 활용해 병렬 계산을 수행해주는 라이브러리이다. 또한 CUDA라는 GPU 전용 범용 병렬 컴퓨팅, 플랫폼을 설치해야 한다. 

뒤에 나올 예제에서 config.GPU = True라는 주석을 풀면 넘파이 대신 쿠파이가 사용되고 쿠파이가 계산을 GPU에게 위임하여 학습이 빠르게 이루어진다. 

1.6 정리

  • 신경망은 입력층, 은닉층(중간층), 출력층을 지닌다.
  • 완전연결계층에 의해 선형 변환이 이뤄지고, 활성화 함수에 의해 비선형 변환이 이뤄진다.
  • 완전연결계층이나, 미니배치 처리는 행렬로 모아 한꺼번에 계산할 수 있다.
  • 오차역전파법을 사용하여 신경망의 손실에 관한 기울기를 효율적으로 구할 수 있다.
  • 신경망이 수행하는 처리는 계산 그래프로 시각화할 수 있으며, 순전파와 역전파를 이해하는데 도움이 된다.
  • 신경망의 구성요소들을 계층으로 모듈화해두면, 이를 조립하여 신경망을 쉽게 구성할 수 있다.
  • 신경망 고속화에는 GPU를 이용한 병렬 계산과 데이터의 비트 정밀도가 중요하다.
반응형

댓글