[혼공딥] CHAPTER 07-1 인공신경망
패션 MNIST
텐서플로의 케라스(Keras) 패키지를 임포트하고 패션 MNIST 데이터를 다운로드한다.
from tensorflow import keras
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
keras.datasets.fashion_mnist 모듈 아래 load_data() 함수는 훈련 데이터의 입력과 타깃의 쌍, 테스트 데이터의 입력과 타깃 쌍을 반환해준다.
데이터의 크기를 확인해보면,
print(train_input.shape, train_target.shape)
훈련 데이터는 60000개의 이미지로 크기는 28 x 28으로 3차원배열이고, 타깃도 60000개의 원소가 있는 1차원 배열이다.
(60000, 28, 28) (60000,)
테스트 세트의 크기를 확인해보면,
print(test_input.shape, test_target.shape)
훈련 데이터의 이미지 크기와 같고 10000개로 이루어져 있다.
(10000, 28, 28) (10000,)
입력과 타깃 샘플
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 10, figsize=(10, 10))
for i in range(10) :
axs[i].imshow(train_input[i], cmap='gray_r')
axs[i].axis('off')
plt.show()
처음 10개 샘플의 타깃값을 리스트로 만들고 출력을 해본다.
print([train_target[i] for i in range(10)])
타깃 데이터는 0에서부터 9까지 있다.
[9, 0, 0, 3, 0, 2, 7, 2, 5, 5]
unique 함수로 레이블 당 샘플 개수를 확인해본다.
- unique() 함수 : 데이터에 고유값들이 어떠한 종류들이 있는지 알고 싶을 때 사용
import numpy as np
print(np.unique(train_target, return_counts=True))
0~9까지 레이블마다 6000개의 샘플이 들어있음을 알 수 있다.
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8), array([6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000]))
로지스틱 회귀
훈련샘플이 60000개나 되기 때문에 전체 데이터를 한꺼번에 사용하는 것보다 샘플을 하나씩 꺼내서 훈련시키는 방법이 더 효율적이다 -> 확률적 경사 하강법
* 훈련세트에서 랜덤하게 하나의 샘플을 골라 경사를 조금씩 내려가고 이런 식으로 전체 샘플을 모두 사용할 때까지 계속한다.
SGDClassifier를 사용할 때 표준화 전처리된 데이터를 사용한다
WHY? 확률적 경사 하강법은 여러 특성 중 기울기가 가장 가파른 방향을 따라 이동한다. 특성마다 값의 범위가 많이 다르면 올바르게 손실 함수의 경사를 내려올 수 없다.
-> 이미지 데이터를 다룰 때 표준화를 하지 않고 픽셀값 0~255를 255로 나누어서 0~1 사이의 값으로 한다!
train_scaled = train_input / 255.0
2차원 배열을 1차원 배열로 만들기 위해 reshape로 변환한다.
train_scaled = train_scaled.reshape(-1, 28*28)
첫번째 차원인 샘플 개수는 변하지 않고 원본 데이터의 높이와 너비를 곱해서 하나의 차원으로 만들어서 각 샘플이 하나의 배열이 되도록 변환한다.
변환된 train_scaled를 확인해보면
print(train_scaled.shape)
784개의 픽셀로 이루어진 60000개의 샘플로 되었음을 볼 수 있다.
(60000, 784)
SGDClassifer 클래스와 cross_validate 함수를 사용해 교차 검증으로 성능을 확인한다.
- loss : 손실함수의 종류를 지정 -> 'log'로 지정하여 로지스틱 손실 함수를 지정
- max_iter : 수행할 에포크 횟수를 지정 -> 전체 훈련 세트를 5회 반복(*반복 횟수를 늘려도 성능이 크게 향상되지 않는다*)
from sklearn.model_selection import cross_validate
from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss='log', max_iter=5, random_state=42)
scores = cross_validate(sc, train_scaled, train_target, n_jobs=-1)
print(np.mean(scores['test_score']))
약 82%의 정확도를 얻을 수 있다
0.8195666666666668
훈련 데이터만 사용, 검증 폴드와 훈련 폴드로 나누어서 5-폴드 교차 검증을 하였고, 이의 평균값을 낸 것이다.
이진 분류일 때는 로지스틱 손실함수를 사용해서 모델을 훈련
다중 분류일 경우는 10개의 이진분류를 사용한다.
ex) 부츠-양성클래스, 나머지 9개-음성클래스 / 티셔츠-양성클래스, 나머지 9개-음성클래스 이런 식으로 10개를 나누어서 10개의 함수가 나온다 -> 10개의 식이 나온다, 10개의 z값을 소프트맥스 함수를 사용해서 확률로 바꾼다.
z_티셔츠 = w1 x (픽셀1) + w2 x (픽셀2) + ... + w784 x (픽셀784) + b
. . .
z_바지 = w1' x (픽셀1) + w2' x (픽셀2) + ... + w784' x (픽셀784) + b'
입력 픽셀이 10개의 선형방정식과 곱해질 때 서로 다른 가중치와 절편이 사용된다. -> 같은 가중치를 사용하면 티셔츠와 바지를 구분할 수 없다.
인공 신경망
출력층(output layer) : 마지막 10개의 확률을 얻기 위한 마지막 층
입력층(input layer) : 입력 데이터가 놓인 층, 입력 데이터 그 자체
뉴런(neuron) or 유닛(unit) : z값을 계산하는 단위
ex) z1을 만들기 위해 픽셀1인 x1에 가중치 w1,1이 곱해지고, z2를 만들기 위해 픽셀1인 x1에 가중치 w1,2가 곱해지는 이런 식이다. -> z1과 z2를 계산할 때 x1인 첫번째 픽셀에 다른 가중치가 곱해진다!
절편 b은 항상 마지막 출력층에 있는 뉴런에 더해진다.
- 인공 뉴런은 생물학적 뉴런에서 영감을 얻어서 만들어졌다.
- 인공뉴런은 생물학적 뉴런과 다르다
>> 가장 기본적인 인공신경망은 확률적 경사 하강법을 사용한 로지스틱 회귀나 선형회귀와 동일
텐서플로와 케라스
텐서플로는 구글이 2015년 11월 오픈소스로 공개한 딥러닝 라이브러리이다.
텐서플로에는 저수준 API과 고수준 API가 있는데 케라스가 고수준 API이다. 2015년 3월 프랑소와 솔레가 만든 딥러닝 라이브러리이다.
딥러닝 라이브러리는 GPU를 사용하여 인공 신경망을 훈련시킨다. GPU는 벡터와 행렬 연산에 매우 최적화되어 있기 때문에 곱셈과 덧셈이 많이 수행되는 인공 신경망에 도움이 된다.
케라스 라이브러리는 직접 GPU연산을 하지 않고 다른 라이브러리를 백엔드로 사용한다.
텐서플로가 케라스의 백엔드 중 하나이다. 씨아노, CNTK와 같은 여러 딥러닝 라이브러리를 케라스 백엔드로 사용할 수 있다. 이런 케라스를 멀티-백엔드 케라스라고 부른다.
프랑소와가 구글에 합류한 뒤에 텐서플로 라이브러리에 케라스 API가 내장되었다. 텐서플로 2.0부터는 케라스 API만 남고 모두 정리되었다. 이제 케라스와 텐서플로가 거의 동의어가 된 셈!
import tensorflow as tf #텐서플로
from tensorflow import keras # 텐서플로에서 케라스
인공 신경망으로 모델 만들기
- 인공신경망에서는 교차 검증을 사용하지 않는다
-> 데이터가 많아서 검증 점수가 안정, 검증 세트가 전체 훈련 세트를 잘 대표할 수 있다
-> 데이터가 많아서 계산 비용이 많이 든다. 어떤 딥러닝 모델은 훈련하는데 엄청 오래 걸린다
from sklearn.model_selection import train_test_split
train_scaled, val_scaled, train_target, val_target = train_test_split(train_scaled, train_target, test_size=0.2, random_state = 42)
훈련 세트에서 20%를 검증 세트로 덜어내었다.
훈련 세트와 검증 세트의 크기를 알아보면,
print(train_scaled.shape, train_target.shape)
#훈련세트 크기 : (48000, 784) (48000,)
print(val_scaled.shape, val_target.shape)
#검증세트 크기 : (12000, 784) (12000,)
60000개 중에 12000개가 검증 세트로 분리되었음을 볼 수 있다.
입력값은 784개로 다른 작업을 하지 않아서 유지되었다.
10개의 뉴런인 출력층을 만든다.
dense = keras.layers.Dense(10, activation='softmax', input_shape=(784,))
- 첫 번째 매개변수로 뉴런 개수를 지정, 여기에서는 10개로 지정->10개의 패션 아이템을 분류
- activation : 활성화 함수 지정, 뉴런의 출력에 적용할 함수 지정 -> 다중 분류여서 softmax 함수를 지정
* 활성화 함수(activation function) : 뉴런의 선형 방정식 계산 결과에 적용되는 함수 *
- input_shape : 입력값의 크기, 샘플의 개수
밀집층(dense layer) : 가장 간단한 인공 신경망의 층
- 밀집층에서는 뉴런들이 모두 연결되어 있기 때문에 완전 연결 층(fully connected layer)라고도 한다.
- 784개의 픽셀과 10개의 뉴런이 모두 연결되면 784 x 10 = 7840개의 연결된 선들이 빽빽하게 나타나게 된다.
밀집층을 가진 신경망 모델을 만들기 위해 케라스의 Sequential 클래스를 사용한다.
model = keras.Sequential(dense)
Sequential 클래스의 객체를 만들 때 앞에서 만든 밀집층의 객체 dense를 전달했다.
model 객체가 바로 신경망 모델이다.
모델을 훈련하기 전에 설정 작업이 필요 -> 케라스 모델 객체의 compile() 메서드에서 수행
- loss : 손실함수 정하기
- metrics : 모델이 훈련을 할 때 기본적으로 손실값을 저장을 해놓는데 그것 외에도 정확도도 기록을 하고 싶을 때 추가적으로 측정지표를 지정
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
이진 분류: loss = 'binary_crossentropy' <- 이진 크로스 엔트로피 손실 함수
다중 분류: loss = 'categorical_crossentropy' <- 크로스 엔트로피 손실 함수
sparse가 붙는 이유?
print(train_target[:10])
#[7 3 5 8 6 9 3 3 9 9]
MNIST 데이터의 타깃값은 모두 정수로 되어 있다.
정수값을 그대로 사용할 수 없고, 출력층의 10개의 유닛에서 소프트맥스 함수를 거쳐서 10개의 확률값이 나온다.
크로스 엔트로피 공식에서 이 확률에 log를 취해서 타깃값과 곱해진다. 티셔츠만 1이고 나머지는 0인 원-핫 인코딩이 되어야 나머지 유닛에서의 출력값이 0으로 곱해져서 상쇄된다. 타깃에 해당되는 뉴런의 출력값만 살아남아서 손실에 반영된다.
원-핫 인코딩(one-hot encoding) : 타깃값을 해당 클래스만 1이고 나머지는 모두 0인 배열로 만드는 것
->이런 식으로 바꾸어서 타깃 데이터를 사용해야하는데 그냥 정수로 사용하고 싶으면 sparse를 붙이면 된다.
로지스틱 회귀에서 사용했던 train_scaled 그대로, train_target 정수 레이블 그대로 사용, 에포크를 5번 돌려보았다.
model.fit(train_scaled, train_target, epochs=5)
metrics로 'accuracy'를 지정했기 때문에 손실값과 함께 정확도도 같이 출력 되었음을 볼 수 있다.
Epoch 1/5
1500/1500 [==============================] - 6s 2ms/step - loss: 0.6065 - accuracy: 0.7952
Epoch 2/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4774 - accuracy: 0.8397
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4566 - accuracy: 0.8469
Epoch 4/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4441 - accuracy: 0.8525
Epoch 5/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4366 - accuracy: 0.8546
에포크가 증가할수록 손실값이 낮아지고 정확도는 증가하고 있음을 볼 수 있다.
evaluate 메서드로 검증 세트를 평가
model.evaluate(val_scaled, val_target)
84%의 검증 정확도를 보이고 있다.
375/375 [==============================] - 1s 2ms/step - loss: 0.4571 - accuracy: 0.8478
[0.45714762806892395, 0.8477500081062317]
훈련 세트의 점수보다 조금 낮은 83%의 정확도가 나왔다
#사이킷런 모델
sc = SGDClassifier(loss='log', max_iter=5)
sc.fit(train_scaled, train_target)
sc.score(val_scaled, val_target)
사이킷런은 클래스를 만들 때 가능하면 많은 매개변수를 지정, loss나 max_iter로 에포크 횟수 지정
모델을 만들 때 클래스 하나에 매개변수로 모두 처리
#케라스 모델
dense = keras.layers.Dense(10, activation='softmax', input_shape=(784,))
model = keras.Sequential(dense)
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
model.evaluate(val_scaled, val_target)
케라스는 층을 따로 만들고 층을 모델 클래스에 추가하고 따로 손실함수나 지표를 추가하기 위한 것을 compile 메서드를 통해 할수 있다.
층을 만들고 모델을 만들고 설정하는 부분이 나뉘어있다. 모델 설정이 분산되어 있는 이유는 다양한 모델을 조합하기 위함이다
케라스의 fit, evaluate는 사이킷런의 fit과 score와 동일하다고 볼 수 있다