딥러닝이란 무엇인가?
딥러닝은 머신러닝의 특정한 한 분야로서 연속된 층(layer) 에서 점진적으로 의미 있는 표현을 배우는 데 강점이 있으며, 데이터로부터 표현을 학습하는 새로운 방식
딥러닝의 특징
딥러닝은 머신러닝 중에서 가장 중요한 단계인 특성 공학(feature engineering)을 완전히 자동화하기 때문에 문제 해결을 더 쉽게 만들어 줌
딥러닝은 특성을 직접 찾는 대신 한 번에 모든 특성을 학습할 수 있음
- 층을 거치면서 점진적으로 더 복잡한 표현이 만들어 짐
- 점진적인 중간 표현이 공동으로 학습
딥러닝이 현재 발전할 수 있는 이유
- 하드웨어
- 데이터셋과 벤치마크
- 알고리즘 향상
- 활성화 함수(activation function)
- 가중치 초기화(weight initialization) 방법
- 최적화 방법(옵티마이저)
신경망의 수학적 구성 요소
머신러닝에서 분류 문제의 범주(category)를 클래스(class)라고 하고, 데이터 포인트는 샘플(sample), 특정 샘플의 클래스는 레이블(label)이라고 함
신경망 예시
1
2
3
4
| # MNSIT
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
|
훈련 세트(train set)와 테스트 세트(test set) 구성
이미지는 넘파이 배열로 인코딩되어 있고 레이블은 0부터 9까지의 숫자 배열
1
| train_images.shape, len(train_labels)
|
1
| ((60000, 28, 28), 60000)
|
1
| test_images.shape, len(test_labels)
|
1
| ((10000, 28, 28), 10000)
|
train_images
와 train_labels
를 네트워크에 입력 -> 이미지와 레이블을 연관시킬 수 있도록 학습 -> 예측
1
2
3
4
5
6
| from tensorflow.keras import models
from tensorflow.keras import layers
network = models.Sequential()
network.add(layers.Dense(512, activation="relu", input_shape=(28*28, )))
network.add(layers.Dense(10, activation="softmax"))
|
신경망의 핵심 구성 요소는 데이터 처리 필터라고 생각할 수 있는 층(layer)
-> 어떤 데이터가 들어가면 더 유용한 형태로 출력
-> 층은 주어진 문제에 더 의미 있는 표현(representation) 을 추출
1
2
3
4
5
| network.compile(
optimizer="rmsprop",
loss="categorical_crossentropy",
metrics=["accuracy"]
)
|
- 옵티마이저(optimizer): 입력된 데이터와 손실 함수를 기반으로 네트워크를 업데이트하는 메커니즘
- 손실함수(loss function): 훈련 데이터에서 신경망의 성능을 측정하는 방법으로, 네트워크가 옳은 방향으로 학습될 수 있도록 도와줌
1
2
3
4
5
| 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
|
1
2
3
4
| from tensorflow.keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
|
훈련 데이터에 모델을 학습시킴
1
| network.fit(train_images, train_labels, epochs=5, batch_size=128, verbose=1)
|
1
2
3
4
5
6
7
8
9
10
| Epoch 1/5
469/469 [==============================] - 1s 2ms/step - loss: 0.0286 - accuracy: 0.9914
Epoch 2/5
469/469 [==============================] - 1s 2ms/step - loss: 0.0221 - accuracy: 0.9933
Epoch 3/5
469/469 [==============================] - 1s 2ms/step - loss: 0.0163 - accuracy: 0.9948
Epoch 4/5
469/469 [==============================] - 1s 2ms/step - loss: 0.0132 - accuracy: 0.9962
Epoch 5/5
469/469 [==============================] - 1s 2ms/step - loss: 0.0103 - accuracy: 0.9971
|
예측
1
| test_loss, test_acc = network.evaluate(test_images, test_labels)
|
1
| 313/313 [==============================] - 0s 329us/step - loss: 0.0728 - accuracy: 0.9801
|
신경망을 위한 데이터 표현
최근 모든 머신러닝 시스템은 일반적으로 텐서(Tensor)를 기본 데이터 구조로 사용
텐서는 데이터를 위한 컨테이너(container)로, 거의 항상 수치형 데이터를 다루므로 숫자를 위한 컨테이너
스칼라 (OD 텐서)
하나의 숫자만 담고 있는 텐서를 스칼라(scalar)라고 함
넘파이에서는 float32
나 float64
타입의 숫자가 스칼라 텐서
ndim
속성을 사용하면 넘파이 배열의 축 개수를 확인할 수 있음 (스칼라 텐서의 축 개수는 0)
축 개수를 랭크(rank)라고도 함
1
2
3
4
5
6
| import numpy as np
x = np.array(12)
print(x)
print(x.ndim)
|
벡터 (1D 텐서)
숫자의 배열을 벡터(vector) 또는 1D 텐서라고 부름
1
2
3
4
| x = np.array([12, 3, 6, 14, 7])
print(x)
print(x.ndim)
|
행렬 (2D 텐서)
벡터의 배열이 행렬(matrix) 또는 2D 텐서
1
2
3
4
5
6
| x = np.array([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
print(x)
print(x.ndim)
|
1
2
3
4
| [[ 5 78 2 34 0]
[ 6 79 3 35 1]
[ 7 80 4 36 2]]
2
|
3D 텐서와 고차원 텐서
행렬들을 하나의 새로운 배열로 합치면 숫자가 채워진 직육면체 형태로 해석 가능한 3D 텐서가 만들어짐
1
2
3
4
5
6
7
8
9
10
11
12
| 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]]])
print(x)
print(x.ndim)
|
1
2
3
4
5
6
7
8
9
10
11
12
| [[[ 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
|
3D 텐서들을 하나의 배열로 합치면 4D 텐서, 4D 텐서를 하나의 배열로 합치면 5D 텐서 …
핵심 속성
- 축의 개수(랭크)
- 크기(shape)
- 데이터타입
1
2
3
4
5
6
7
| from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
print(f"축의 개수: {train_images.ndim}")
print(f"배열의 크기: {train_images.shape}")
print(f"데이터 타입: {train_images.dtype}")
|
1
2
3
| 축의 개수: 3
배열의 크기: (60000, 28, 28)
데이터 타입: uint8
|
배치 데이터
일반적으로 딥러닝에서 사용하는 모든 데이터 텐서의 첫 번째 축은 샘플 축(sample axis) 임
딥러닝 모델은 한 번에 전체 데이터셋을 처리하지 않음
-> 작은 배치(batch)로 나눠서 처리함
MNIST
에서 배치 크기가 128이었다면,
1
2
3
4
| batch = train_image[:128]
batch = train_image[128:256]
...
batch = train_image[128*n:128*(n+1)]
|
와 같이 나타 낼 수 있음
이런 배치 데이터를 다룰 때는 첫 번째 축을 배치 축(batch axis) 또는 배치 차원(batch dimension)이라고 부름
텐서 예시
- 벡터 데이터:
(samples, features)
크기의 2D 텐서 - 시계열 데이터/시퀀스(sequence) 데이터:
(samples, timesteps, features)
크기의 3D 텐서 - 이미지:
(samples, height, width, channels)
또는 (samples, channels, height, width)
크기의 4D 텐서 - 동영상:
(samples, frames, height, width, channels)
또는 (samples, frames, channels, height, width)
크기의 5D 텐서
채널 마지막(channel-last) 방식과 채널 우선(channel-first) 방식이 있고, Keras에서는 모두 지원함
텐서 연산(Tensor Operation)
원소별 연산(element-wise operation)
텐서에 있는 각 원소에 독립적으로 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # relu 연산 구현
def naive_relu(x):
# 2차원 텐서 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
# 덧셈 구현
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
|
넘파이는 시스템에 설치된 BLAS(Basic Linear Algebra Subprogram) 구현에 복잡한 연산들을 대신 처리해 줌
BLAS는 고도로 병렬화되고 효율적인 저수준의 텐서 조작 루틴으로, 포트란(Fortran)이나 C 언어로 구현되어 있음
1
2
3
4
5
| import numpy as np
# 원소별 덧셈
z = x + y
# 원소별 렐루 함수
z = np.maximum(z, 0.)
|
브로드캐스팅(Broadcasting)
작은 텐서가 큰 텐서의 크기에 맞추어 연산이 가능해지는 것
- 큰 텐서의
ndim
에 맞도록 작은 텐서에 축이 추가 - 작은 텐서가 새 축을 따라 큰 텐서의 크기에 맞도록 반복
X의 크기가 (32, 10)이고 y의 크기가 (10, )라고 하면, y에 비어 있는 첫 번째 축을 추가하면 (1, 10)의 크기로 만들고
y를 이 축에 32번 반복하면 텐서 Y의 크기는 (32, 10)이 됨
1
| Y[i, :] = y for i in range(0, 32)
|
1
2
3
4
| x = np.random.random((64, 3, 32, 10))
y = np.random.random((32, 10))
z = np.maximum(x, y)
|
점곱(Dot product)
텐서 곱셈(tensor product)라고도 불리는 점곱 연산은 가장 널리 사용되는 유용한 텐서 연산
원소별 연산과 반대로 입력 텐서의 원소들을 결합 (행렬 곱)
1
2
| x = np.random.random((2, 2))
y = np.random.random((2, 2))
|
1
2
| print(f"x:\n{x}")
print(f"y:\n{y}")
|
1
2
3
4
5
6
| x:
[[0.22661398 0.49272441]
[0.60668969 0.68153466]]
y:
[[0.16168992 0.06804161]
[0.52869742 0.54692636]]
|
1
| print(f"x+y(원소별 연산):\n{x + y}")
|
1
2
3
| x+y(원소별 연산):
[[0.3883039 0.56076601]
[1.13538712 1.22846102]]
|
1
| print(f"x*y(원소별 연산):\n{x*y}")
|
1
2
3
| x*y(원소별 연산):
[[0.0366412 0.03352576]
[0.32075528 0.37274927]]
|
1
| print(f"x@y(점곱 연산):\n{x@y}")
|
1
2
3
| x@y(점곱 연산):
[[0.29714332 0.28490315]
[0.45842122 0.41402941]]
|
텐서 크기 변환(Tensor Reshaping)
특정 크기에 맞게 열과 행을 재배열
1
2
3
| x = np.zeros((300, 20))
print(x.shape)
print(x.T.shape)
|
그래디언트 기반 최적화
신경망에서 출력은 다음과 같음 $output = active(dot(W, input) + b)$
W
와 b
는 층의 속성처럼 볼 수 있음 -> 가중치(weight) 또는 훈련되는 파라미터(trainable parameter)라고 부름
- 초기에는 가중치 행렬이 작은 난수로 채워져 있음(무작위 초기화(random initialization))
- 피드백 신호에 기초해 가중치가 점진적으로 조정 (훈련(training))
훈련 반복 루프(training loop)
- 훈련 샘플 x와 이에 상응하는 타깃 y의 배치 추출
- x를 사용해 네트워크를 실행하고(forward pass), 예측 y_pred 계산
- 예측 값인 y_pred와 실제값인 y의 차이를 측정해 네트워크 손실 계산
- 배치에 대한 손실이 감소되도록 네트워크의 모든 가중치를 업데이트
신경망에 사용된 모든 연산이 미분 가능(differntiable)하다는 장점을 이용해 가중치에 대한 그래디언트(gradient, 기울기)를 계산
-> 그래디언트의 반대 방향으로 가중치를 이동하면 손실이 감소됨
텐서 연산의 변화율(Gradient)
확률적 경사 하강법(SGD, Stochastic Gradient Descent)
미분 가능한 함수가 주어지면 이론적으로 이 함수의 최솟값을 해석적으로 구할 수 있음(함수의 최솟값은 변화율이 0인 지점)
GD는 전체 데이터를 이용해 변화도를 계산하고, SGD나 미니배치 GD는 데이터의 서브셋을 이용해 변화도를 계산(실험 정리)
신경망에 적용하면 가장 작은 손실 함수의 값을 만드는 가중치의 조합을 해석적으로 찾는 것을 의미
- 훈련 샘플 x와 이에 상응하는 타깃 y 추출
- x로 네트워크를 실행하고, 예측 y_pred 계산
- 해당 배치에서 y와 y_pred 사이의 오차를 측정해 네트워크의 손실 계산
- 네트워크의 파라미터에 대한 손실 함수의 그래디언트 계산(역방향 패스, backward pass)
- 그래디언트의 반대 방향으로 파라미터를 이동
- 손실 감소
업데이트할 다음 가중치를 계산할 때 현재 그래디언트 값만 보지 않고 이전에 업데이트된 가중치를 여러 가지 다른 방식으로 고려하는 SGD 변종이 많음
-> 최적화 방법(optimization method) 또는 옵티마이저(optimizer)라고 부름
-> 모멘텀(momentum) 개념을 사용해 지역 최솟값(local minimum)과 전역 최솟값(global minimum) 문제를 해결
역전파 알고리즘(Backpropagation)
연결된 함수는 연쇄 법칙(chain rule)이라 부르는 다음 항등식, $f(g(x))’ = f’(g(x)) * g’(x)$ 를 사용해 유도 가능
연쇄 법칙을 신경망의 그래디언트 계산에 적용해 역전파 알고리즘이 탄생
역전파는 최종 손실 값에서부터 시작해, 손실 값에 각 파라미터가 기여한 정도를 계산하기 위해 연쇄 법칙을 적용해 최상위 층에서 하위층까지 거꾸로 진행
Comments powered by Disqus.