본문 바로가기

케라스 창시자에게 배우는 딥러닝(DACOS)

2. 신경망의 수학적 구성 요소

 

 

 

[목차]

2-1) 신경망과의 첫 만남

2-2) 신경망을 위한 데이터 표현

2-3) 신경망의 톱니바퀴: 텐서 연산

2-4) 신경망의 엔진: 그레이디언트 기반 최적화

 

 

2-1) 신경망과의 첫 만남

 

1. 데이터셋 적재

from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

 

클래스(class) : 분류 문제의 범주

샘플(sample) : 각 데이터 포인트

레이블(label) : 특정 샘플의 클래스

 

(예제에서 0~9는 클래스, 샘플은 이미지 데이터 하나, 레이블은 각 이미지 데이터에 해당되는 숫자 값)

 

train_images.shape
(60000, 28, 28)

훈련 세트: 모델이 학습해야 할 데이터셋, 이미지와 레이블로 구성

test_images.shape
(10000, 28, 28)

테스트 세트: 학습된 모델을 테스트할 데이터셋, 마찬가지로 이미지와 레이블로 구성

 

 

2. 신경망 구조 살펴보기

from tensorflow import keras
from tensorflow.keras import layers
 
model = keras.Sequential([
    layers.Dense(512, activation="relu"),
    layers.Dense(10, activation="softmax")
])

 

dense() : 케라스에서 인공신경망의 각 층을 구성

인공신경망 : 입력층, 은닉층, 출력층 등 다양한 층으로 구성

층: 데이터를 유용한 형태로 출력하기 위한 필터

 

(6만여개의 입력 데이터를 두개의 층을 거쳐 10개의 출력값으로 분류함)

 

 

3. 컴파일 단계

model.compile(optimizer="rmsprop",
              loss="sparse_categorical_crossentropy",
              metrics=["accuracy"])

 

옵티마이저(optimizer): 성능을 향상시키기 위해 입력된 데이터를 기반으로 모델을 업데이트하는 메커니즘

손실 함수(loss) : 훈련 데이터에서 모델의 성능을 측정하는 방법

지표(metrics) > 정확도

 

 

4. 데이터 준비 (전처리)

train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

 

(28, 28) 형태로 구성되어 있는 이미지 데이터를 (28*28, 1) 형태로 변환 > 학습시킬 수 있음!

 

 

5. 모델 훈련

model.fit(train_images, train_labels, epochs=5, batch_size=128)

 

.fit() 메서드를 호출하여 훈련 데이터셋을 기반으로 모델을 학습시킴 (각 배치, 즉 작은 데이터셋의 크기는 128)

 

 

6. 모델 사용하여 예측 만들기

test_digits = test_images[0:10]
predictions = model.predict(test_digits)
predictions[0]
1/1 [==============================] - 0s 66ms/step
array([7.9244224e-08, 2.8882177e-08, 4.1038176e-05, 3.3128036e-03,
       8.2901352e-11, 6.6504566e-07, 3.2575769e-12, 9.9654561e-01,
       1.4622490e-06, 9.8360535e-05], dtype=float32)

 

이미지 데이터에서 10개의 이미지만 뽑아 각 클래스에 대한 예측 확률을 계산한 것을 predictions 에 저장함.

predictions 배열의 0번 인덱스, 즉 첫째 항목의 각 클래스로 분류될 확률 출력.

> 해당 항목은 7로 분류되었을 확률이 가장 높음

 

 

7. 테스트 데이터셋으로 모델 평가하기

test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f"테스트 정확도: {test_acc}")

 

모델을 테스트 데이터를 기반으로 그 성능을 평가함, 테스트 정확도를 출력

(정확도 : 실제 클래스 예측에 성공한 테스트 샘플 수/ 전체 테스트 샘플 수)

 

 

2-2) 신경망을 위한 데이터 표현

 

텐서란? 데이터를 위한 컨테이너 (수치형 데이터가 일반적) 

>> 다차원 넘파이 배열 형태

 

랭크: 텐서의 축(차원) 개수

 

1. 랭크 -0 텐서(스칼라)

import numpy as np
x = np.array(12)
x

array(12)


x.ndim

0

 

2. 랭크-1 텐서 (벡터)

x = np.array([12, 3, 6, 14, 7])
x
array([12,  3,  6, 14,  7])

 

> 5D 벡터, 1D 텐서

(축을 따라 놓인 원소의 수가 벡터의 차원 결정, 축의 개수가 텐서의 차원 결정)

 

3. 랭크-2 텐서 (행렬) > 차원:2

x = np.array([[5, 78, 2, 34, 0],
              [6, 79, 3, 35, 1],
              [7, 80, 4, 36, 2]])

 

4. 랭크-n 텐서 > 차원: n

x = np.array([[[5, 78, 2, 34, 0],
               [6, 79, 3, 35, 1],
               [7, 80, 4, 36, 2]],
              [[5, 78, 2, 34, 0],
               [6, 79, 3, 35, 1],
               [7, 80, 4, 36, 2]],
              [[5, 78, 2, 34, 0],
               [6, 79, 3, 35, 1],
               [7, 80, 4, 36, 2]]])

 

다음은 랭크-3 텐서를 표현한 것

>> 이러한 형태로 랭크-n 에서 n의 값이 커져도 여러 행렬을 합친 꼴로 표현 가능하다.

 

핵심 정리 (텐서의 핵심적인 3가지 속성)

1) 축의 개수: 랭크

2) 각 축을 따라 있는 차원의 수 : 크기 

    = 벡터의 차원의 수 = 파이썬 튜플

3) 데이터 타입

 

 

[배치 데이터]

 

샘플 축: 0번째 축  = 배치 축

배치(batch) : 샘플 축을 기준으로 데이터를 작게 나눈 것

 

[데이터의 종류]

 

벡터 데이터 

구성: (samples, features)

랭크-2 텐서 : 첫 번째 축이 샘플 축, 두 번째 축이 특성 축

 

시계열 데이터 / 시퀀스 데이터

(samples, timesteps, features) 크기의 랭크-3 텐서. 각 샘플은 특성 벡터의(길이가 timestpes인) 시퀀스

- 관례적으로 시간 축은 인덱스가 1인 축(두번째 축)

- ex) 주식 가격 데이터셋 (날짜 수 , 390, 3), 트윗 데이터셋 (샘플 수, 총 문자 수, 알파벳 수)

 

이미지

(samples, channels, height, width) 크기의 랭크-4 텐서. 각 샘플은 픽셀의 2D 격자고 각 픽셀은 수치 값(채널)의 벡터

- 채널 우선 방식: (samples, color_depth, height, width)

- 채널 마지막 방식: (samples, height, width, color_depth)

> 컬러 채널의 위치에 따라 구분!

 

동영상

(samples, frames, height, width, channels) 또는 (samples, frames, channels, height, width) 크기의 랭크-5 텐서. 각 샘플은 이미지의 (길이가 frames인) 시퀀스

 

 

2-3) 신경망의 톱니바퀴: 텐서 연산

 

층: 입력을 받아서 입력 텐서를 또 다른 행렬로 표현하여 반환하는 함수처럼도 해석 가능

 

예시 : keras.layers.Dense(512, activation="relu")

 

output = relu (dot (W , input) + b)

 

3개의 텐서 연산

1) 입력 텐서와 텐서 W 사이의 점곱(dot)

2) 점곱으로 만들어진 행렬과 벡터 b 사이의 덧셈(+)

3) relu(렐루) 연산. relu(x)는 max(x, 0) 

 

 

원소별 연산

 

1) relu()

def naive_relu(x):
    assert len(x.shape) == 2
    x = x.copy()
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            x[i, j] = max(x[i, j], 0)
    return x

 

2) add()

def naive_add(x, y):
    assert len(x.shape) == 2
    assert x.shape == y.shape
    x = x.copy()
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            x[i, j] += y[i, j]
    return x

 

> 비슷한 원리로 다른 원소에도 적용 가능 

 

import time

x = np.random.random((20, 100))
y = np.random.random((20, 100))

t0 = time.time()
for _ in range(1000):
    z = x + y
    z = np.maximum(z, 0.)
print("걸린 시간: {0:.2f} s".format(time.time() - t0))

 

넘파이를 이용해 처리 시 코드

- 해당 코드와 위의 함수들의 적용 결과 런타임 비교 시 1/174로 러닝타임 단축됨

 

 

브로드캐스팅

크기가 다른 두 텐서 연산에 사용.

>> 작은 텐서를 큰 텐서에 크기에 맞추는 것

 

1. 큰 텐서의 ndim에 맞도록 작은 텐서에(브로드캐스팅 축이라고 부르는) 축이 추가됨

2. 작은 텐서가 새 축을 따라서 큰 텐서의 크기에 맞도록 반복됨.

 

import numpy as np
X = np.random.random((32, 10))
y = np.random.random((10,))                  // y는 벡터
y = np.expand_dims(y, axis=0)                // y를 행렬꼴로 변환
Y = np.concatenate([y] * 32, axis=0)         // 축에 따라 32번 반복해서 (32, 10) 얻음

 

 

텐서 크기 변환

 

이동: 도형(점들의 집합)을 고정된 양만큼 고정된 방향으로 이동

회전: 2*2 행렬 [[cos(theta), -sin(theta)], [sin(theta), cos(theta)]] 와 점곱 (반시계방향 회전)

선형 변환: 임의의 행렬과 점곱

 

아핀 변환: 어떤 행렬과 점곱하여 얻은 선형 변환과 벡터를 더해 얻는 이동의 조합이다.

 y= dot(W, x)+ b    >> dense 층에서 수행하는 연산!!

 

> 성질: 여러 번 아핀 변환을 반복해서 얻은 결과도 아핀 변환이 됨

(여러 층의 신경망을 하나의 dense 층으로 표현 가능!!)

 

 

 

2-4) 신경망의 엔진: 그레이디언트 기반 최적화

 

 

output = relu (dot (W , input) + b)

x: 입력

W, b : 가중치 / 훈련되는 파라미터

 

훈련 반복 루프

1. 훈련 샘플 x와 이에 상응하는 타깃 y_true의 배치를 추출

2. x를 사용하여 모델을 실행하고, 예측 y_pred를 구함

3. y_pred와 y_true의 차이를 측정하여 배치에 대한 이 모델의 손실을 계산

4. 배치에 대한 손실이 조금 감소되도록 네트워크의 모든 가중치를 업데이트함

 

(예측 > 예측과 실제 사이 차이 구함(손실) > 가중치 변화 (손실 줄어듬)) 반복

 

그레이디언트: 파라미터를 조금씩 변경하여 손실 값을 예측 가능한 방향으로 바뀌게 하는 것

>> 텐서 함수의 도함수

>> 해당 함수가 설명하는 다차원 표면의 곡률을 나타냄

 

loss_value() = loss( y_pred, y_true) = f(W)           : 손실 값에 대한 함수 표현

 

f(x) 에서 x 값을 도함수 반대 방향으로 조금씩 이동하면 f(x) 값을 감소시킬 수 있음

 

확률적 경사 하강법 : 가장 작은 가중치의 조합을 해석적으로 찾음

 

> grad ( f(W) , W) = 0 (모델 가중치 수를 변수로의 개수로 가짐)

 

1. 훈련 배치 샘플과 y_true 추출

2. x 로 모델을 실행하고 예측 y_pred를 구함

3. 이 배치에서 y_pred와 y_true 사이 오차를 측정하여 모델의 손실 계산

 

역전파 알고리즘: 2개 이상의 층(복잡한 식) 을 가진 모델의 가중치에 대한 손실의 그레이디언트

 

loss_value = loss(y_true, softmax(dot(relu(dot(inputs, W1) + b1), W2) + b2)) >> 연결된 도함수

 

 

역전파 알고리즘을 거치면 최종적으로,

grad(loss_val, w) = grad( loss_val, x2) * grad(x2, x1) * grad(x1, w) 라는 식이 나옴

 

> 텐서플로: 자동 미분 가능한 최신 프레임워크 사용