chapter_8_1_2

08-2. 합성곱 신경망을 이용한 이미지 분류

패션 MNIST 데이터 불러오기

  • 데이터 스케일을 0 ~ 255 사이 0 ~ 1 로 표준화
  • 훈련 데이터 / 검증 데이터 분류
  • 완전 연결 신경망 (Fully Connected Layer)

–> 2차원 배열 -> 1차원 배열 (최종 분류값 도출)
–> 완전 연결 신경망과 달리, 합성곱에서는 2차원 이미지를 그대로 사용한다.

1
2
3
4
5
6
7
8
9
10
from tensorflow import keras
from sklearn.model_selection import train_test_split

(train_input, train_target), (test_input, test_target) = \
keras.datasets.fashion_mnist.load_data()

train_scaled = train_input.reshape(-1, 28, 28, 1) / 255.0

train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step

합성곱 신경망 만들기

  • 446p
  • 437p 그림을 코드로 구현하는 내용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
model = keras.Sequential()
# 합성곱 층
model.add(keras.layers.Conv2D(32, kernel_size=3, activation = 'relu', # Conv2D() 는 합성곱 층을 만든다.
padding = 'same', input_shape = (28, 28, 1))) # 합성곱의 필터 32이므로 특성 맵의 깊이는 32

# 풀링층
model.add(keras.layers.MaxPooling2D(2)) # (2,2) 풀링을 적용하여 합성곱 층의 특성 맵의 크기가 절반이 된다.

# 합성곱 층
model.add(keras.layers.Conv2D(64, kernel_size=(3,3), activation = 'relu', # 합성곱의 필터 64이므로 특성 맵의 깊이는 64
padding = 'same'))

# 풀링층
model.add(keras.layers.MaxPooling2D(2)) # (2,2) 풀링을 적용하여 합성곱 층의 특성 맵의 크기가 절반이 된다.

# 완전연결층 (밀집층 = Fully Connected Layer)
# Chapter 7장 내용
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(100, activation='relu')) # 은닉층
model.add(keras.layers.Dropout(0.4)) # 드롭아웃 -> 과대 적합 방지
model.add(keras.layers.Dense(10, activation='softmax')) # 출력측

model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                /images/chapter_8_1_2/output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 28, 28, 32)        320       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 14, 14, 32)       0         
 )                                                               
                                                                 
 conv2d_1 (Conv2D)           (None, 14, 14, 64)        18496     
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 7, 7, 64)         0         
 2D)                                                             
                                                                 
 flatten (Flatten)           (None, 3136)              0         
                                                                 
 dense (Dense)               (None, 100)               313700    
                                                                 
 dropout (Dropout)           (None, 100)               0         
                                                                 
 dense_1 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 333,526
Trainable params: 333,526
Non-trainable params: 0
_________________________________________________________________
  • 필터의 개수에 따라 특성 맵의 크기는
    • 첫 번째 합성곱 층을 통과하면서 특성 맵의 크기가 32가 된다.
    • 두 번째 합성곱에서 특성 맵의 크기가 64로 늘어난다.
  • 반면 특성 맵의 가로세로 크기는
    • 첫 번째 풀링 층에서 절반으로 줄어든다.
    • 두 번째 풀링층에서 다시 절반으로 더 줄어든다.
  • Flatten 클래스에서 (7,7,64) 크기의 특성 맵을 1차원 배열로 펼친다.
    • (7,7,64) -> (3136,)

모델 파라미터 개수 계산

  • 첫 번째 합성곱 층

    • 32개 필터, 커널 크기(3,3), 깊이1, 필터마다 하나의 절편 -> 3x3x1x32 + 32 = 320개
  • 두 번째 합성곱 층

    • 64개 필터, 커널 크기(3,3), 깊이32, 필터마다 하나의 절편 -> 3x3x32x64 + 64 = 18,496개
  • Flatten 즉, 은닉층

    • (3136,) 개의 1차원 배열, 100개의 뉴런 -> 3136x100 + 100 = 313,700개
  • 텐서플로 : https://www.tensorflow.org/hub

  • 필요한 것을 찾아서 가져다 사용할 수 있다.

  • 층의 구성을 그림으로 표현해 본다.

  • keras.uitls 패키지의 plot_model() 함수 사용

1
keras.utils.plot_model(model)

png

  • 박스 안에서

    • 왼쪽 : 층의 이름
    • 오른쪽 : 클래스
  • inputLayer 클래스

    • 케라스가 자동으로 추가해주는 입력층의 역할.
    • Conv2D 클래스의 input_shape 매개변수를 사용.
  • 층의 구성을 그림으로 표현해 본다.

  • keras.uitls 패키지의 plot_model() 함수 사용

  • show_shapes 매개변수를 True로 설정하면 그림에 입력과 출력의 크기를 표시한다.

1
keras.utils.plot_model(model, show_shapes = True)

png

  • 지금까지 한 것은 모델 정의
  • 모델 컴파일 후, 훈련
    • 7장 내용
    • Adam 옵티마이저를 사용
    • 조기 종료 기법을 구현 : ModelCheckpoint 콜백과 EarlyStopping 콜백을 함께 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
import tensorflow as tf
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics='accuracy')

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-cnn-model.h5',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2,
restore_best_weights=True)

with tf.device('/device:GPU:0'): # GPU 잡는 법
history = model.fit(train_scaled, train_target, epochs=10,
validation_data=(val_scaled, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
Epoch 1/10
1500/1500 [==============================] - 59s 39ms/step - loss: 0.4968 - accuracy: 0.8231 - val_loss: 0.3245 - val_accuracy: 0.8799
Epoch 2/10
1500/1500 [==============================] - 55s 37ms/step - loss: 0.3304 - accuracy: 0.8809 - val_loss: 0.2726 - val_accuracy: 0.8967
Epoch 3/10
1500/1500 [==============================] - 55s 37ms/step - loss: 0.2833 - accuracy: 0.8987 - val_loss: 0.2461 - val_accuracy: 0.9072
Epoch 4/10
1500/1500 [==============================] - 55s 37ms/step - loss: 0.2534 - accuracy: 0.9069 - val_loss: 0.2360 - val_accuracy: 0.9119
Epoch 5/10
1500/1500 [==============================] - 55s 37ms/step - loss: 0.2311 - accuracy: 0.9165 - val_loss: 0.2258 - val_accuracy: 0.9170
Epoch 6/10
1500/1500 [==============================] - 55s 37ms/step - loss: 0.2104 - accuracy: 0.9224 - val_loss: 0.2346 - val_accuracy: 0.9157
Epoch 7/10
1500/1500 [==============================] - 55s 37ms/step - loss: 0.1916 - accuracy: 0.9275 - val_loss: 0.2132 - val_accuracy: 0.9234
Epoch 8/10
1500/1500 [==============================] - 55s 37ms/step - loss: 0.1757 - accuracy: 0.9343 - val_loss: 0.2152 - val_accuracy: 0.9220
Epoch 9/10
1500/1500 [==============================] - 56s 37ms/step - loss: 0.1619 - accuracy: 0.9393 - val_loss: 0.2172 - val_accuracy: 0.9247
  • 훈련 세트의 정확도가 이전에 비해 증가했다.
  • 손실 그래프를 그린다.
    • 조기 종료가 잘 이루어졌는지 확인하자.
1
2
3
4
5
6
7
import matplotlib.pyplot as plt
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.xlabel('loss')
plt.legend(['train', 'val'])
plt.show()

png

  • 그래프를 기반으로 9번째 에포크를 최적으로 생각할 수 잇다.
  • 세트에 대한 성능을 평가해본다.
1
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 5s 14ms/step - loss: 0.2132 - accuracy: 0.9234





[0.21322399377822876, 0.9234166741371155]
  • 좌측 파일 선택 -> best-cnn-model.h5 다운로드

08-3. 합성곱 신경망 시각화

  • 교재 465p
  • 사전 학습 = 이전에 만든 모델이 어떤 가중치를 학습했는지 확인하기 위해 체크포인트 파일을 읽는다.
  • model.layers
    • 케라스 모델에 추가한 층을 출력한다.
1
2
3
4
5
6
7
from tensorflow import keras

# 사전학습 진행
model2 = keras.models.load_model("best-cnn-model.h5")

#keras.utils.plot_model(model2, show_shapes = True)
model.layers
[<keras.layers.convolutional.Conv2D at 0x7fe1487e19d0>,
 <keras.layers.pooling.MaxPooling2D at 0x7fe1d0495b50>,
 <keras.layers.convolutional.Conv2D at 0x7fe148c92590>,
 <keras.layers.pooling.MaxPooling2D at 0x7fe1487fa9d0>,
 <keras.layers.core.flatten.Flatten at 0x7fe1446bad10>,
 <keras.layers.core.dense.Dense at 0x7fe1446ba210>,
 <keras.layers.core.dropout.Dropout at 0x7fe14465cf50>,
 <keras.layers.core.dense.Dense at 0x7fe14465dcd0>]
  • 합성곱 층의 가중치를 확인 가능
  • 우선 첫 번째 합성곱 층의 가중치를 조사한다.
1
2
conv = model.layers[0]
print(conv.weights[0].shape, conv.weights[1].shape) # 가중치, 절편
(3, 3, 1, 32) (32,)
1
2
conv_weights = conv.weights[0].numpy()
print(conv_weights.mean(), conv_weights.std()) # 가중치 배열의 평균, 표쥰편차
-0.038952995 0.26509935
  • 이 가중치가 어떤 분표를 가졌는지 보기 쉽게 히스토그램으로 그린다.
1
2
3
4
plt.hist(conv_weights.reshape(-1, 1))
plt.xlabel('weight')
plt.ylabel('count')
plt.show()

png

  • 이 가중치가 어떤 의미인지 시각화 해보자.
  • 468p
  • 32개의 커널을 16개씩 2줄로 출력한다.
1
2
3
4
5
6
7
8
ig, axs = plt.subplots(2, 16, figsize=(15,2))

for i in range(2):
for j in range(16):
axs[i, j].imshow(conv_weights[:,:,0,i*16 + j], vmin=-0.5, vmax=0.5) # vmin, vmax는 맷플롯립의 컬러맵으로 표현할 범위를 지정
axs[i, j].axis('off')

plt.show()

png

  • 색이 밝은지 어두운지를 통해 가중치를 판단할 수 있다.

이번에는 훈련하지 않은 빈 합성곱 신경망을 만든다.

  • 먼저 Conv2D 층을 하나 추가한다.
1
2
3
4
no_training_model = keras.Sequential()

no_training_model.add(keras.layers.Conv2D(32, kernel_size=3, activation='relu',
padding='same', input_shape=(28,28,1)))
  • 첫 번째 Conv2D층의 가중치를 no_training_conv 변수에 저장한다.
1
2
3
no_training_conv = no_training_model.layers[0]

print(no_training_conv.weights[0].shape)
(3, 3, 1, 32)
  • 가중치의 평균과 표준편차를 확인한다.
1
2
no_training_weights = no_training_conv.weights[0].numpy()
print(no_training_weights.mean(), no_training_weights.std())
0.011464282 0.08503365
  • 이 가중치 배열을 히스토그램으로 표현한다.
1
2
3
4
plt.hist(no_training_weights.reshape(-1, 1))
plt.xlabel('weight')
plt.ylabel('count')
plt.show()

png

  • 그래프가 이전과 확실히 다르다.
  • 이 가중치 값을 맷플롯립의 imshow() 함수를 사용해 이전처럼 그림으로 출력한다.
1
2
3
4
5
6
7
8
ig, axs = plt.subplots(2, 16, figsize=(15,2))

for i in range(2):
for j in range(16):
axs[i, j].imshow(no_training_weights[:,:,0,i*16 + j], vmin=-0.5, vmax=0.5) # vmin, vmax는 맷플롯립의 컬러맵으로 표현할 범위를 지정
axs[i, j].axis('off')

plt.show()

png

  • 전체적으로 가중치가 밋밋하게 초기화되었다.
  • 이 그림을 훈련이 끝난 이전 가중치와 비교해보자.
  • 합성곱 신경망이 패현MNIST 데이터셋의 부류 정확도를 높이기 위해 유용한 패턴을 학습했다는 사실을 눈치챌 수 있다.

함수형 API

  • 474p
  • 특성 맵 시각화
    • 케라스로 패현 MNIST 데이터셋을 읽은 후 훈련 세트에 있는 첫 번째 샘플을 그려본다.
1
2
3
4
5
print(model.input)
conv_acti = keras.Model(model.input, model.layers[0]./images/chapter_8_1_2/output)
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
plt.imshow(train_input[0], cmap='gray_r')
plt.show()
KerasTensor(type_spec=TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='conv2d_input'), name='conv2d_input', description="created by layer 'conv2d_input'")

png

  • 앵클 부트다.
  • 이 샘플을 conv_acti 모델에 주입하여 Conv2D 층이 만드는 특성 맵을 출력한다.
  • 08-2장에서 했던 것처럼 전처리를 진행한다.
  • feature_maps의 크기를 확인한다.
1
2
3
inputs = train_input[0:1].reshape(-1, 28, 28, 1)/255.0
feature_maps = conv_acti.predict(inputs)
print(feature_maps.shape)
(1, 28, 28, 32)
  • same 패딩과 32개의 필터를 사용한 합성곱 층의 출력이므로 (28,28,32)이다.
  • 샘플을 하나 입력했기에 1이다.
  • 앞에서와 같이 맷플롯립의 imshow함수로 특성 맵을 그린다.
    • 32개의 특성 맵을 4개의 행으로 나누어 그린다.
1
2
3
4
5
6
7
8
fig, axs = plt.subplots(4, 8, figsize=(15,8))

for i in range(4):
for j in range(8):
axs[i, j].imshow(feature_maps[0,:,:,i*8 + j])
axs[i, j].axis('off')

plt.show()

png

  • 두 번째 합성곱 층이 많든 특성 맵도 같은 방식으로 확인할 수 있다.
  • 먼저 model 객체의 입력과 두 번째 합성곱 층인 model.layers[2]의 출력을 연결한 conv2_acti 모델을 만든다.
  • 그 다음 첫 샘플을 conv2_acti 모델의 predict() 메서드에 전달한다.
  • 첫 번째 풀링 층에서 가로세로 크기가 줄반으로 줄고, 두 번째 합성곱 층의 필터 개수는 64개이므로 (14,14,64) 가 된다.
  • 64개의 특성 맵을 8개씩 나누어 imshow()함수로 그린다.
1
2
3
4
5
6
7
8
9
10
11
12
conv2_acti = keras.Model(model.input, model.layers[2]./images/chapter_8_1_2/output)
feature_maps = conv2_acti.predict(train_input[0:1].reshape(-1, 28, 28, 1)/255.0)
print(feature_maps.shape)

fig, axs = plt.subplots(8, 8, figsize=(12,12))

for i in range(8):
for j in range(8):
axs[i, j].imshow(feature_maps[0,:,:,i*8 + j])
axs[i, j].axis('off')

plt.show()
(1, 14, 14, 64)

png

  • 이번 특성 맵은 시각적으로 이해하기 어렵다.

  • 두 번째 합성곱 층의 필터 크기는 (3,3,32)인데 (14,14,32)인 특성 맵에서 어떤 부위를 감지하는지 직관적으로 이해하기 어렵다.

    • 478p 그림 참고
  • 이런 현상은 합성곱 층을 많이 쌓을수록 심해진다.

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_7_1

딥러닝

인공신경망 1943년 즈음 등장

1차 융성기 ~ 1960 후반

  • 로봇이 인간들과 함께 살 것이다 예언

  • XOR 문제 해결 못함

  • AI 연구 겨울 찾아옴

  • 대안 : 최근접이웃, 결정트리, 서포트벡터머신 등

  • 토론토 대학 AI 연구소 (역전파 알고리즘 개발)

  • CNN 알고리즘 (1980년대 후반)

-2차 융성시기 ~ 1990년대 후반

  • CNN, RNN 알고리즘 등장

  • 연산 속도 문제/정확도 문제

  • 산업계 즉시 활용 어려움

-3차 융성시기 2012 ~ 현재까지

  • GPU 세팅 (그래픽카드)

  • 연산속도문제가 해결

  • 세돌 vs 알파고 바둑 대회(2017년)

  • 정부에서도 본격적으로 투자

  • 교육쪽으로 먼저 투자 시작

  • 대학교육 + 국비교육

  • 데이터과학

2012년

  • CNN 알고리즘 논문 다수 출현

  • 이미지 기본데이터셋

    1. 기존대비 성능이 좋아야 함
    1. 개존대비 연산속도가 좋아야 함

→ 각자 딥러닝 관심 생김

→ 공부하는 패턴 : 최운선순위는 가장 최근 나온 알고리즘

분야가 정말 많음

  • 지도학습 : 분류/수치 예측(회귀)/비지도학습

  • 엑셀데이터(정형데이터)

  • 기초 통계가 중요(리포트 형태가 더 중요)

  • 개발의 상대적 중요성 떨어짐(성과 측면)

딥러닝:비정형데이터

  • 텍스트,음성,이미지,영상

  • 주로 쓰이는 알고리즘 탐색 (최신 알고리즘)

  • 계속 업그레이드 되고 있음

  • 이세돌 vs 알파고 바둑 대회 (2017년)

  • 데이터과학

지도 학습 vs 딥러닝

→ 개발자라면 딥러닝 알고리즘을 가져다가 빠르게 개발하는 기술을 습득.

딥러닝 라이브러리

1
2
import tensorflow
print(tensorflow.__version__)
2.8.0

데이터 불러오기

패션 MNIST

  • 10종류의 패션 아이템으로 구성된 데이터셋
  • 텐서프로를 사용해 이 데이터를 불러온다.
1
2
3
from tensorflow import keras
(train_input, train_target), (test_input, test_target)= keras.datasets.fashion_mnist.load_data()
# load.data()함수는 훈련 데이터와 테스트 데이터를 나누어 반환한다.
  • 데이터 확인

  • 훈련 데이터

    • 60,000개 이미지, 이미지 크기는 28x28
    • 타깃은 60,000개 원소가 있는 1차원 배열
1
print(train_input.shape, train_target.shape)
(60000, 28, 28) (60000,)
  • 테스트 세트
    • 10,000개의 이미지로 이루어짐
1
print(test_input.shape, test_target.shape)
(10000, 28, 28) (10000,)
  • 이미지 시각화
1
2
3
4
5
6
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()

png

  • 타겟 값 리스트
    • 패션 MNIST의 타깃은 0~9까지의 숫자 레이블로 구성된다.
    • 같은 숫자가 나온다면 타깃이 같은 두 샘플은 같은 종류의 옷이다.
1
print([train_target[i] for i in range(10)])
[9, 0, 0, 3, 0, 2, 7, 2, 5, 5]
  • 실제 타겟값의 값을 확인
  • 각 라벨당 6000개의 이미지 존재 60,000개
  • 즉, 각 의류마다 6,000개의 샘플이 들어있다.
1
2
import numpy as np
print(np.unique(train_target, return_counts = True))
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8), array([6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000]))

로지스틱 회귀로 패션 아이템 분류하기

  • 경사하강법 (기울기)
    • 샘플이 60,000개나 되기에 샘플을 하나씩 꺼내서 모델을 훈련하는 게 더 효율적
    • 해당 상황에 맞는 것이 강사하강법이다.
  • 전제 조건 : 각 컬럼의 데이터셋 동일 (표준화)
  • why 255? 각 픽셀의 값 0~255 사이의 정수값을 가진다.
  • 255로 나누어 0~1 사이의 값으로 정규화 시킴
    • 표준화는 아니지만 양수 값으로 이루어진 이미지를 전처리할 때 사용하는 방벙
1
2
3
4
5
train_scaled = train_input / 255.0

# 경사하강법 사용을 위해 1차원 배열로 만들기
train_scaled = train_scaled.reshape(-1, 28*28)
print(train_scaled.shape)
(60000, 784)

모델 만들기

  • 비정형데이터에 선형모델 또는 비선형모델을 적용시키는 것이 합리적인가?

    • 결론은 아니다!
    • 다른 대안이 있는냐? 인공신경망!
  • 정형데이터에 인공신경망 및 딥러닝 모델을 적용시키는 것이 합리적인가?

    • 결론은 아니다!
  • SGDClassifier 클래스와 cross_validate 함수로 이 데이터에서 교차 검증으로 성능을 확인한다.

1
2
3
4
5
6
7
from sklearn.model_selection import cross_validate
from sklearn.linear_model import SGDClassifier

sc = SGDClassifier(loss='log', max_iter=5, random_state=42) # 반복 횟수를 5번으로 지정

scores = cross_validate(sc, train_scaled, train_target, n_jobs=-1)
print(np.mean(scores['test_score']))
0.8195666666666668
  • 로지스틱 회귀 공식을 그림으로 나타내면 인공신경망의 그림과 같다.
  • 인동신경망을 만들어 패션 아이템 분류 문제의 성능을 높일 수 있는지 지켜보자.

참고

인공신경망 모델 적용

  • 이미지 분류에는 인공 신경망이 적합하다.
1
2
import tensorflow as tf
from tensorflow import keras
  • 텐서플로 = 케라스
  • 케라스 API를 사용해 패션 아이템을 분류하는 가장 간단한 인공 신경망을 만들어 보자.
  • train_test_split()
1
2
3
4
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)
  • test_size=0.2
    • 훈련 세트에서 20%를 검증 세트로 덜어 내었다.
  • 훈련 세트와 검증 세트의 크기를 알아보자.
1
print(train_scaled.shape, train_target.shape)
(48000, 784) (48000,)
1
print(val_scaled.shape, val_target.shape)
(12000, 784) (12000,)
  • 60,000개 중에 12,000개가 검증 세트로 분리되었다.

  • 먼저 훈련 세트로 모델을 만든다. 그 다음 검증 세트로 훈련한 모델을 평가해본다.

  • 이미지 하나에 있는 픽셀은 784개. 뉴런은 10개. 이것을 모두 연결.

  • 완전 연결층 = 밀집층 = 밀집하게 연결되어 있는 것

    • fully connected layer = dense layer
1
print(train_target[:10])
[7 3 5 8 6 9 3 3 9 9]
  • Dense 클래스를 통해 밀집층을 만들어보자
  • 활성화 함수
    • softmax와 같이 뉴런의 선형 방정직 계산 결과에 적용되는 함수.
1
2
# 매개변수의 의미는 차례대로 뉴런개수, 뉴런의 출력에 적용할 함수, 입력의 크기다.
dense = keras.layers.Dense(10, activation = 'softmax', input_shape=(784, ))
  • 방금 만든 밀집층을 가진 신경망 모델을 만들자.
  • Sequential 클래스를 사용한다.
1
model = keras.Sequential(dense)

인공 신경망으로 패션 아이템 분류하기

  • 훈련하기 전의 설정 단계
1
model.compile(loss = 'sparse_categorical_crossentropy', metrics = "accuracy")
  • 모델을 훈련한다.
    • 반복할 에포크 횟수를 epochs 매개변수로 지정
1
model.fit(train_scaled, train_target, epochs = 5)
Epoch 1/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4782 - accuracy: 0.8383
Epoch 2/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4574 - accuracy: 0.8484
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4450 - accuracy: 0.8525
Epoch 4/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.4372 - accuracy: 0.8549
Epoch 5/5
1500/1500 [==============================] - 2s 2ms/step - loss: 0.4318 - accuracy: 0.8575





<keras.callbacks.History at 0x7fb1c0b7f450>
  • 갈수록 정확도가 증가함을 알 수 있다.
  • 검증 세트(val_scaled, val_target)에서 모델의 성능을 확인한다.
1
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 1ms/step - loss: 0.4530 - accuracy: 0.8463





[0.4530307352542877, 0.8463333249092102]
  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_7_2

심층 신경망

  • 인공신경망에 층을 여러 개 추가하여 패션 MNIST 데이터셋을 분류한다.

  • 동시에 케라스로 심층 신경망을 만들어본다.

  • 368p 그림 참고

  • 케라스로 API를 사용해 패션 MNIST 데이터셋을 불러온다.

1
2
3
from tensorflow import keras

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
  • 이미지의 픽셀값을 0 ~ 255 범위에서 0 ~ 1로 변환
  • 28x28 크기의 2차원 배열을 784 크기인 1차원 배열로 펼친다.
  • train_test_split() 함수로 훈련 세트와 검증 세트로 나눈다.
1
2
3
4
5
6
7
from sklearn.model_selection import train_test_split

train_scaled = train_input / 255.0
train_scaled = train_scaled.reshape(-1, 28*28)

train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
  • 입력층과 출력층 사이에 밀집층을 만들 예정이다.

  • 은닉층 : 입력층과 출력층 사이에 있는 모든 층

  • 케라스의 Dense 클래스로 다음 내용을 만든다.

    • sigmoid 활성화 함수를 사용한 은닉층
    • softmax 함수를 사용한 출력층
  • 층을 추가하는 방법

    • Dense 클래스의 객체 dense1, 2를 만들어 Sequential 클래스에 전달한다.
1
2
dense1 = keras.layers.Dense(100, activation='sigmoid', input_shape=(784,))
dense2 = keras.layers.Dense(10, activation='softmax')
  • dense1이 은닉층이고 100개의 뉴런을 가진 밀집층이다.
    • 활성화 함수를 ‘sigmoid’로 지정했고 매개변수로 입력의 크기를 (784,)로 지정했다.
  • dense2는 출력층이다.
    • 10개의 클래스를 분류하므로 10개의 뉴런을 두었고 활성화 함수는 softmax로 지정했다.

심층 신경망

  • 컨셉만 이해하라!

  • 직접 신경망 만들 일은 없고 가져다 쓰기만 하면 된다.

  • 앞의 dense1과 dense2 객체를 Sequential 클래스에 추가하여 심층 신경망을 만들 예정이다.

1
2
model = keras.Sequential([dense1, dense2])
model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 100)               78500     
                                                                 
 dense_1 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________
  • 위와 같이 Sequential 클래스의 객체를 만들 때 여러 개의 층을 추가하려면 층을 리스트로 만들어 전달해야 한다.
  • model.summary()로 층에 대한 정보를 얻을 수 있다.
    • 첫 줄에 모델의 이름이 나온다.
    • 이 모델에 들어 있는 층이 순서대로 나열된다.
      • 이 순서는 맨 처음 추가한 은닉층에서 출력층의 순서로 나열된다.
    • 층마다 층 이름, 클래스, 출력 크기, 모델 파라미터 개수가 출력된다.
    • name 매개변수로 이름을 지정하지 않으면 디폴트인 ‘dense’로 네이밍된다.
    • 출력 크기는 (None,100)인데, 첫 번째 차원은 샘플 개수를 나타낸다.
      • None인 이유는 어떤 배치 크기에도 잘 대응하기 위함이다.
      • 두 번째 차원인 100은 뉴런 개수가 100이며, 따라서 100개의 출력이 나옴을 나타낸다.

층을 추가하는 다른 방법

  • Sequential 클래스의 생성자 안에서 바로 Dense 클래스의 객체를 만든다.
1
2
3
4
5
model = keras.Sequential([
keras.layers.Dense(100, activation='sigmoid', input_shape=(784,), name='hidden'), # 층을 쌓아간다
keras.layers.Dense(10, activation='softmax', name='output') # 층을 쌓아간다
], name='패션 MNIST 모델')
model.summary()
Model: "패션 MNIST 모델"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 hidden (Dense)              (None, 100)               78500     
                                                                 
 output (Dense)              (None, 10)                1010      
                                                                 
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________

층을 추가하는 다른 방법 2

  • Sequential 클래스의 객체를 만들고 이 객체의 add() 메서드를 호출하여 층을 추가한다.
1
2
3
4
5
model = keras.Sequential()
model.add(keras.layers.Dense(100, activation='sigmoid', input_shape=(784,))) # 층을 쌓아간다
model.add(keras.layers.Dense(10, activation='softmax')) # 층을 쌓아간다

model.summary()
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_2 (Dense)             (None, 100)               78500     
                                                                 
 dense_3 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________
  • 이제 모델을 훈련한다.
    • 반복할 에포크 횟수를 epochs 매개변수로 지정
1
2
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
Epoch 1/5
1500/1500 [==============================] - 6s 3ms/step - loss: 0.5628 - accuracy: 0.8069
Epoch 2/5
1500/1500 [==============================] - 4s 3ms/step - loss: 0.4087 - accuracy: 0.8522
Epoch 3/5
1500/1500 [==============================] - 5s 3ms/step - loss: 0.3747 - accuracy: 0.8645
Epoch 4/5
1500/1500 [==============================] - 4s 3ms/step - loss: 0.3506 - accuracy: 0.8737
Epoch 5/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3344 - accuracy: 0.8784





<keras.callbacks.History at 0x7f5bcb861b50>
  • 렐루 함수

    • 층이 많은 신경망일수록 그 효과가 누적되어 학습이 어려워진다.
    • 이를 개선하기 위한 활성화 함수이다.
    • relu() 함수는 입력이 양수일 그냥 통과시키고, 입력이 음수라면 0으로 만든다.
  • Flatten 클래스

    • 배치 차원을 제외하고 나머지 입력 차원을 모두 일렬로 펼친다.
    • Flatten 클래스를 층처럼 입렬층과 은닉층 사잉에 추가하기 때문에 이를 층이라 부른다.
    • 다음 코드처럼 입력층 바로 뒤에 추가한다.
1
2
3
4
5
6
model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28,28))) # 기존 코드 비교
model.add(keras.layers.Dense(100, activation='relu')) # relu 로 변경
model.add(keras.layers.Dense(10, activation='softmax'))

model.summary()
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten (Flatten)           (None, 784)               0         
                                                                 
 dense_4 (Dense)             (None, 100)               78500     
                                                                 
 dense_5 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________
  • 훈련 데이터를 다시 준비해서 모델을 훈련한다.
1
2
3
4
5
6
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()

train_scaled = train_input / 255.0

train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
  • 모델을 컴파일하고 훈련한다.
1
2
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
Epoch 1/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.5283 - accuracy: 0.8151
Epoch 2/5
1500/1500 [==============================] - 4s 3ms/step - loss: 0.3926 - accuracy: 0.8602
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3562 - accuracy: 0.8713
Epoch 4/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3336 - accuracy: 0.8809
Epoch 5/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3203 - accuracy: 0.8853





<keras.callbacks.History at 0x7f5bcb762a10>
  • 시그모이드 함수를 사용했을 때와 비교하면 성능이 조금 향상되었다.
  • 검증 세트에서의 성능도 확인하자.
1
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 3ms/step - loss: 0.3713 - accuracy: 0.8717





[0.3712655007839203, 0.871749997138977]
  • 검증 성능도 향상되었다.
1
2
3
4
5
6
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()

train_scaled = train_input / 255.0

train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
1
2
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
Epoch 1/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3094 - accuracy: 0.8890
Epoch 2/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.2989 - accuracy: 0.8951
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.2902 - accuracy: 0.8974
Epoch 4/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.2825 - accuracy: 0.9018
Epoch 5/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.2781 - accuracy: 0.9024





<keras.callbacks.History at 0x7f5bcb835d10>
1
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 1ms/step - loss: 0.4110 - accuracy: 0.8792





[0.41104814410209656, 0.8791666626930237]

옵티마이저의 개념

–> Adam 사용하라
–> why Adam? 최고점을 찾기 위해서

  • 스텝방향 & 스템사이즈를 모두 고려한 옵티마이저
  • 스텝방향 : GD, SGD, Momentum, NAG
  • 스텝사이즈 : GD, SGD, Adagrad, RMSProp
  • 하이퍼 파라미터는 사람이 지정해야 하는 파라미터
  • 신경망에는 특히 하이퍼 파라미터가 많다.
  • 은닉층의 뉴런 개수도 하이퍼 파라미터이다.
  • compile() 에서는 케라스의 기본 하강법 알고리즘인 RMSprop을 사용했다.
    • 케라스는 다양한 종류의 경사 하강법 알고리즘을 제공한다.
    • 이들을 ‘옵티마이저’라고 부른다.

옵티마이저

  • 381p
  • SGD 옵티마이저를 사용하려면 compile() 메서드의 optimizer 매개변수를 ‘sgd’로 지정
1
model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy', metrics='accuracy')
  • ‘sgd’ 문자열은 이 클래스의 기본 설정 매개변수로 생성한 객체와 동일하다.
  • 다음 코드는 위의 코드와 정확히 동일하다.
1
2
sgd = keras.optimizers.SGD()
model.compile(optimizer=sgd, loss='sparse_categorical_crossentropy', metrics='accuracy')
  • 382p
  • learning_rate = 0.1
    • 만약 SGD 클래스의 학습률 기본값이 0.01일 때 이를 바꾸고 싶다면 다음와 같이 지정한다.
  • 랜덤서치, 그리드서치
  • 딥러닝에서도 하이퍼파라미터 튜닝
1
sgd = keras.optimizers.SGD(learning_rate = 0.1)
  • 기본 경사 하강법 옵티마이저는 모두 SGD 클래스에서 제공한다.
  • SGD 클래서의 momentum 매개변수의 기본값은 0이다. 보통 0.9이상을 지정한다.
  • 다음처럼 SGD 클래스의 nesterov 매개변수를 기본값 False 에서 True로 바꾸면 네스테로프 모멘텀 최적화를 사용한다.
    • 테스테로프 모멘텀은 모멘텀 최적화를 2번 반복하여 구현한다.
    • 대부분의 경우 네스테로프 모멘텀 최적화가 기본 확률적 경사 하강법보다 더 나은 성능을 제공한다.
1
sgd = keras.optimizers.SGD(momentum = 0.9, nesterov = True)
  • 적응적 학습률

    • 모델이 최적점에 가까이 갈수록 학습률을 낮출 수 있다.
    • 이렇게 하면 안정적으로 최적점에 수렴할 가능성이 높다.
    • 이런 학습률을 적응적 학습률이라고 한다.
  • Adagrad() 클래스

    • 적응적 학습률을 사용하는 대표적인 옵티마이저이다.
    • optimizer 매개변수에서 지정할 수 있다.
    • optimizer 매개변수의 기본값이 바로 rmsprop이다.
1
2
adagrad = keras.optimizers.Adagrad()
model.compile(optimizer=adagrad, loss='sparse_categorical_crossentropy', metrics='accuracy')
  • RMSprop() 클래스
    • 적응적 학습률을 사용하는 대표적인 옵티마이저이다.
    • optimizer 매개변수에서 지정할 수 있다.
    • optimizer 매개변수의 기본값이 바로 rmsprop이다.
1
2
rmsprop = keras.optimizers.RMSprop()
model.compile(optimizer=rmsprop, loss='sparse_categorical_crossentropy', metrics='accuracy')
  • 다만, Adam을 사용하는 것이 더 좋다.

  • Adam

    • 모멘텀 최적화와 RMSprop의 장점을 접목한 것이 Adam이다.
    • 적응적 학습률을 사용하는 이 3개의 클래스는 learning_rate 매개변수의 기본값을 0.001로 두고 사용한다.
  • Adam 클래스의 매개변수 기본값을 사용해 패션 MNIST 모델을 훈련해본다.

  • 일단 모델을 다시 생성한다.

1
2
3
4
model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28,28))) # 기존 코드 비교
model.add(keras.layers.Dense(100, activation='relu')) # relu 로 변경
model.add(keras.layers.Dense(10, activation='softmax'))
  • compile() 메서드의 optimizer를 ‘adam’으로 설정하고 5번의 에포크 동안 훈련한다.
1
2
3
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')
model.fit(train_scaled, train_target, epochs=5)
model.evaluate(val_scaled, val_target)
Epoch 1/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.5293 - accuracy: 0.8155
Epoch 2/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3980 - accuracy: 0.8571
Epoch 3/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3542 - accuracy: 0.8713
Epoch 4/5
1500/1500 [==============================] - 4s 3ms/step - loss: 0.3287 - accuracy: 0.8798
Epoch 5/5
1500/1500 [==============================] - 3s 2ms/step - loss: 0.3081 - accuracy: 0.8867
375/375 [==============================] - 1s 2ms/step - loss: 0.3296 - accuracy: 0.8806





[0.32961416244506836, 0.8805833458900452]
  • 결과를 보면 기본 RMSprop을 사용했을 때와 거의 같은 성능을 보인다.
  • 검증 세트에서의 성능도 확인한다.
1
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 3ms/step - loss: 0.3296 - accuracy: 0.8806





[0.32961416244506836, 0.8805833458900452]
  • 환경마다 차이가 있을 수 있지만 여기서는 기본 RMSprop보다 조금 더 나은 성능을 보인다.

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_7_3

7-3. 신경망 모델 훈련

  • 케라스 API를 사용해 모델을 훈련하는데 필요한 다양한 도구들을 알아본다.

손실곡선

  • 패션 MNIST 데이터셋을 적재하고 훈련 세트와 검증 세트로 나눈다.
1
2
3
4
5
6
7
8
9
10
from tensorflow import keras
from sklearn.model_selection import train_test_split

(train_input, train_target), (test_input, test_target) = \
keras.datasets.fashion_mnist.load_data()

train_scaled = train_input / 255.0

train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 1s 0us/step
26435584/26421880 [==============================] - 1s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
  • 모델을 만든다.
    • 사용자 정의함수를 작성함
    • if 구문을 제외하면 7-2의 코드와 동일하다.
    • if 구문의 역할은 model_fn() 함수에 케라스 층을 추가하면 은닉층 뒤어 또 하나의 층을 추가하는 것이다.
  • 모델 구조를 출력해본다.
1
2
3
4
5
6
7
8
9
10
def model_fn(a_layer=None):
model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28,28)))
model.add(keras.layers.Dense(100, activation='relu'))
if a_layer:
model.add(a_layer)
model.add(keras.layers.Dense(100, activation='softmax'))
return model
model = model_fn()
model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                /images/chapter_7_3/output Shape              Param #   
=================================================================
 flatten (Flatten)           (None, 784)               0         
                                                                 
 dense (Dense)               (None, 100)               78500     
                                                                 
 dense_1 (Dense)             (None, 100)               10100     
                                                                 
=================================================================
Total params: 88,600
Trainable params: 88,600
Non-trainable params: 0
_________________________________________________________________
  • 모델 정의 후, 학습
  • fit() 메서드의 결과를 history 변수에 담아본다.
1
2
model.compile(loss='sparse_categorical_crossentropy', metrics = 'accuracy')
history = model.fit(train_scaled, train_target, epochs = 5, verbose = 1)
Epoch 1/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.5574 - accuracy: 0.8081
Epoch 2/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3972 - accuracy: 0.8574
Epoch 3/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3572 - accuracy: 0.8710
Epoch 4/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3353 - accuracy: 0.8805
Epoch 5/5
1500/1500 [==============================] - 4s 2ms/step - loss: 0.3187 - accuracy: 0.8855
  • history 객체 값은 무슨 값이 있냐?
    • history 객체에는 훈련 측정값이 담겨 있는 history 딕셔너리가 들어 있다.
    • dictionary 값으로 출력되기 때문에 다음과 같이 작성
1
print(history.history.keys())
dict_keys(['loss', 'accuracy'])
  • 결과 : 손실과 정확도가 포함되어 있다.

  • 손실 곡선

    • history 속성에 포함된 손실과 정확도는 에포크마다 계산한 값이 순서대로 나열된 단순한 리스트이다.
    • 멧플롯립으로 간단히 그릴 수 있다.
1
2
3
4
5
6
import matplotlib.pyplot as plt

plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

png

  • 정확도 출력
    • 이번에는 정확도를 출력해본다.
1
2
3
4
plt.plot(history.history['accuracy'])
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

png

  • 확실히 에포크마다 손실이 감소하고 정확도가 향상됨을 알 수 있다.
  • 계속 손실이 감소하는지 확인해보자.
    • 에포크를 20으로 늘려서 모델을 훈련하고 손실을 그려본다.
1
2
3
4
5
6
7
8
model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')

history = model.fit(train_scaled, train_target, epochs=20, verbose=0) # 수치 조정
plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

png

  • 예상대로 손실이 잘 감소한다.

  • 검증손실

    • 다음과 같이 loss, accuracy, val_loss, val_accuracy 가 출력되도록 하는 것이 정석이다.
    • 에포크마다 검증 손실을 계산하기 위해 케라스 모델의 fit()메서드에 검증 데이터를 전달할 수 있다.
1
2
3
4
5
model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')

history = model.fit(train_scaled, train_target, epochs=10, verbose=1,
validation_data=(val_scaled, val_target))
Epoch 1/10
1500/1500 [==============================] - 9s 6ms/step - loss: 0.5619 - accuracy: 0.8060 - val_loss: 0.4507 - val_accuracy: 0.8375
Epoch 2/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.3984 - accuracy: 0.8571 - val_loss: 0.3923 - val_accuracy: 0.8600
Epoch 3/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.3603 - accuracy: 0.8704 - val_loss: 0.3582 - val_accuracy: 0.8761
Epoch 4/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.3351 - accuracy: 0.8792 - val_loss: 0.3619 - val_accuracy: 0.8770
Epoch 5/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.3207 - accuracy: 0.8860 - val_loss: 0.3707 - val_accuracy: 0.8754
Epoch 6/10
1500/1500 [==============================] - 4s 3ms/step - loss: 0.3084 - accuracy: 0.8907 - val_loss: 0.3775 - val_accuracy: 0.8703
Epoch 7/10
1500/1500 [==============================] - 4s 3ms/step - loss: 0.2998 - accuracy: 0.8948 - val_loss: 0.3707 - val_accuracy: 0.8787
Epoch 8/10
1500/1500 [==============================] - 4s 3ms/step - loss: 0.2901 - accuracy: 0.8981 - val_loss: 0.3494 - val_accuracy: 0.8805
Epoch 9/10
1500/1500 [==============================] - 4s 3ms/step - loss: 0.2815 - accuracy: 0.9015 - val_loss: 0.3691 - val_accuracy: 0.8823
Epoch 10/10
1500/1500 [==============================] - 4s 3ms/step - loss: 0.2756 - accuracy: 0.9034 - val_loss: 0.4148 - val_accuracy: 0.8700
  • 과대 / 과소적합 문제를 조사하기 위해 훈련 손실과 검증 손실을 한 그래프에 그려서 비교해본다.
1
2
3
4
5
6
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

png

  • 검증 데이터 val이 갈수록 손실이 증가한다.
  • 더 나은 그래프를 위해 조정해본다.
  • 위 내용에서 optimizer = adam을 추가
1
2
3
4
5
6
7
8
9
10
11
12
model = model_fn()
model.compile(optimizer = 'adam', loss='sparse_categorical_crossentropy', metrics='accuracy') # adam 추가

history = model.fit(train_scaled, train_target, epochs=10, verbose=1,
validation_data=(val_scaled, val_target))

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
Epoch 1/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.5635 - accuracy: 0.8080 - val_loss: 0.5627 - val_accuracy: 0.7847
Epoch 2/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.4053 - accuracy: 0.8535 - val_loss: 0.3899 - val_accuracy: 0.8593
Epoch 3/10
1500/1500 [==============================] - 5s 3ms/step - loss: 0.3595 - accuracy: 0.8705 - val_loss: 0.3780 - val_accuracy: 0.8627
Epoch 4/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.3311 - accuracy: 0.8785 - val_loss: 0.3409 - val_accuracy: 0.8767
Epoch 5/10
1500/1500 [==============================] - 5s 3ms/step - loss: 0.3130 - accuracy: 0.8855 - val_loss: 0.3361 - val_accuracy: 0.8784
Epoch 6/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.2950 - accuracy: 0.8899 - val_loss: 0.3473 - val_accuracy: 0.8775
Epoch 7/10
1500/1500 [==============================] - 5s 4ms/step - loss: 0.2818 - accuracy: 0.8961 - val_loss: 0.3380 - val_accuracy: 0.8781
Epoch 8/10
1500/1500 [==============================] - 5s 3ms/step - loss: 0.2707 - accuracy: 0.9003 - val_loss: 0.3430 - val_accuracy: 0.8823
Epoch 9/10
1500/1500 [==============================] - 6s 4ms/step - loss: 0.2623 - accuracy: 0.9024 - val_loss: 0.3381 - val_accuracy: 0.8830
Epoch 10/10
1500/1500 [==============================] - 5s 3ms/step - loss: 0.2520 - accuracy: 0.9064 - val_loss: 0.3427 - val_accuracy: 0.8813

png

  • val의 손실이 성공적으로 줄어들었다.
  • 구글링 : image classification django -> 개발자라면 공부해봐라
  • 구글링 : image classification tensorflow -> 이것도

드롭아웃

  • 훈련 과정에서 층에 있는 일부 뉴런을 랜덤하게 꺼서(뉴런의 출력을 0으로 만들어) 과대적합을 막는다.

  • 기본적으로는 모든 파라미터를 연산하는 것이 원칙

    • 그런데, 일부 뉴런에서 출력이 없는 뉴런 발생
    • 기존 일부 뉴런은 계산에서 제외 시킴
  • 인공신경망(뇌과학)

    • 값이 쏠림 현상 = 뇌에 피가 고인 현상
      = 뇌출혈
  • 앞서 정의한 model_fn() 함수에 드롭아웃 객체를 전달하여 층을 추가해본다.

  • 여기에서 30% 정도를 드롭아웃한다.

1
2
model = model_fn(keras.layers.Dropout(0.3)) # 30% 드롭아웃
model.summary()
Model: "sequential_4"
_________________________________________________________________
 Layer (type)                /images/chapter_7_3/output Shape              Param #   
=================================================================
 flatten_4 (Flatten)         (None, 784)               0         
                                                                 
 dense_8 (Dense)             (None, 100)               78500     
                                                                 
 dropout (Dropout)           (None, 100)               0         
                                                                 
 dense_9 (Dense)             (None, 100)               10100     
                                                                 
=================================================================
Total params: 88,600
Trainable params: 88,600
Non-trainable params: 0
_________________________________________________________________
  • 결과. 은닉층 뒤에 추가된 드롭아웃 층(Dropout)은 훈련되는 모델 파라미터가 없다.
  • 일부 뉴런의 출력을 0으로 만들지만 전체 출력 배열의 크기를 바꾸지는 않는다.
  • 그래서 마음 편하게 검증 점수를 계산할 수 있다.
  • 드롭아웃한 상태에서 이전과 마찬가지로 훈련 손실과 검증 손실의 그래프를 그려 비교해본다.
1
2
3
4
5
6
7
8
9
10
11
model.compile(optimizer = 'adam', loss='sparse_categorical_crossentropy', metrics='accuracy') # adam 추가

history = model.fit(train_scaled, train_target, epochs=20, verbose=0, # 수치 조정
validation_data=(val_scaled, val_target))

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

png

  • 과대적합이 확실히 줄었다.
  • 다만, 20번의 에포크 동안 훈련했기에 결국 다소 과대적합이 되었다.
  • 여기서 더 과대적합 하지 않게 하려면 에포크 횟수를 10으로 하고 다시 훈련하면 된다.

모델 저장과 복원

  • 개발자 : 정확도는 중요하지 않음

    • 딥러닝 모델 활용해서 웹앱을 개발
  • 분석가 & 머신러닝 엔지니어 : 캐글대회(정확도 검증 필수)

  • 에포크 횟수를 10으로 하고 다시 훈련한다.

  • 그리고 나중에 사용하려면 이 모델을 저장해야 한다.

1
2
3
4
5
model = model_fn(keras.layers.Dropout(0.3))                                                    # 30% 드롭아웃
model.compile(optimizer = 'adam', loss='sparse_categorical_crossentropy', metrics='accuracy') # adam 추가

history = model.fit(train_scaled, train_target, epochs=20, verbose=0, # 수치 조정
validation_data=(val_scaled, val_target))
  • save_weights()
    • 훈련된 모델의 파라미터를 저장한다.
  • save()
    • 모델 구조와 모델 파라미터를 함께 저장한다.
1
2
model.save_weights('model-weights.h5')
model.save('model-whole.h5')
  • 두 가지 실험을 해본다.

    • 첫 번째는 훈련을 하지 않은 새로운 모델을 만들고 model-weights.h5 파일에서 훈련된 모델 파라미터를 읽어서 사용한다.
    • 두 번째는 아예 model-whole.h5 파일에서 새로운 모델을 만들어 바로 사용한다.
  • 첫 번째 실험

    • 모델 불러오기
1
2
model = model_fn(keras.layers.Dropout(0.3))
model.load_weights('model-weights.h5')
  • 406p
  • 10개 확률 중에 가장 큰 값의 인덱스를 골라 타깃 레이블과 비교하여 정확도를 계산해 본다.
1
2
3
4
import numpy as np

val_labels = np.argmax(model.predict(val_scaled), axis=-1)
print(np.mean(val_labels == val_target))
0.8840833333333333
  • 모델 전체를 파일에서 읽은 다음 검증 세트의 정확도를 출력해 본다.
  • load_model()을 이용하여 파일을 읽으면 된다.
1
2
model = keras.models.load_model('model-whole.h5')
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 3ms/step - loss: 0.3263 - accuracy: 0.8841





[0.326292484998703, 0.8840833306312561]
  • 같은 모델을 저장하고 다시 불렀기에 이전 코드와 동일한 정확도를 얻었다.

콜백

  • 408p
  • 지금까지 20번의 에포크 동안 모델을 훈련하여 검증 점수가 상승하는 지점을 확인했다.
  • 이전처럼 모델을 두 번씩 훈련하지 않고 한 번에 끝내기 위해 콜백을 사용할 수 있다.
  • 콜백 = 훈련 과정 중간에 어떤 작업을 수행할 수 있게 하는 객체이다.
1
2
3
4
5
6
7
8
9
10
model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics='accuracy')

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.h5',
save_best_only=True)

model.fit(train_scaled, train_target, epochs=20, verbose=0,
validation_data=(val_scaled, val_target),
callbacks=[checkpoint_cb])
<keras.callbacks.History at 0x7f3da939e310>
  • model_fn()함수로 모델을 만들고 compile()을 호출한다.
  • 모델이 훈련한 후에 best-model.h5에 최상의 검증 점수를 낸 모델이 저장된다.
  • 이 모델을 load_model()함수로 다시 읽어서 예측을 수행한다.
1
2
model = keras.models.load_model('best-model.h5')
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 1s 2ms/step - loss: 0.3197 - accuracy: 0.8858





[0.31966158747673035, 0.8858333230018616]
  • EarlyStopping

    • 조기 종료
    • 에포크를 많이 주면 줄수록 성능(가중치 업데이트 / 기울기가 계속 미분)이 좋아야 하는 것이 원리
    • 에포크 100 / 50 에포크 시점과 90 에포크 시점 성능 차이 없음
    • 즉, 계속 진행해도 좋아질지 안 좋아질지 모르기에 조기 종료하는 것.
  • EarlyStopping 콜백을 ModelCheckpoint 콜백과 함께 사용하면 가장 낮은 검증 손실의 모델을 파일에 저장한다.

  • 그리고 검증 손실이 다시 상승할 때 훈련을 중지할 수 있다.

  • 훈련을 중지한 다음 현재 모델의 파라미터를 최상의 파라미터로 되돌린다.

  • 두 콜백을 함께 사용해보자.

1
2
3
4
5
6
7
8
9
10
11
12
model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics='accuracy')

checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.h5',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2, # patience는 몇 개의 콜백을 리스트로 전달할지 결정한다.
restore_best_weights=True)

history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
validation_data=(val_scaled, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
  • 몇 번째 훈련에서 중지되는지 다음 코드로 확인할 수 있다.
1
print(early_stopping_cb.stopped_epoch)
10
  • epoch 값이 10에 다다랐을 때, ‘조기종료’한다.
1
2
3
4
5
6
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

png

  • 이런 식으로 조기 종료 기법을 사용하면 안심하고 에포크 횟수를 크게 지정해도 괜찮다.

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_6_2

k-평균

  • 각각의 픽셀값 (3차원 -> 1차원 배열) 평균 구함

    • 픽셀의 평균값은 활용해서 사과, 바나나, 파인애플에 근사한 이미지를 추출한 것
  • 어떻게 평균값을 구할 수 있을까?

    • k-평균 알고리즘 (k-Means) 알고리즘
    • 평균값 = Cluster Center = Centroid

데이터 불러오기

다음을 참고하라 : http://bit.ly/hg-06-2

1
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2022-03-31 02:17:17--  https://bit.ly/fruits_300_data
Resolving bit.ly (bit.ly)... 67.199.248.11, 67.199.248.10
Connecting to bit.ly (bit.ly)|67.199.248.11|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following]
--2022-03-31 02:17:17--  https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy
Resolving github.com (github.com)... 192.30.255.112
Connecting to github.com (github.com)|192.30.255.112|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following]
--2022-03-31 02:17:17--  https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.109.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3000128 (2.9M) [application/octet-stream]
Saving to: ‘fruits_300.npy’

fruits_300.npy      100%[===================>]   2.86M  --.-KB/s    in 0.05s   

2022-03-31 02:17:17 (56.9 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
  • 넘파이 파일을 불러옴
1
2
3
4
5
6
import numpy as np
import matplotlib.pyplot as plt

fruits = np.load('fruits_300.npy')
print(fruits.shape)
print(fruits.ndim)
(300, 100, 100)
3
  • 3차원 (샘플개수, 너비, 높이)
  • 2차원 (샘플개수, 너비 x 높이)
1
2
fruits_2d = fruits.reshape(-1, 100*100)
fruits_2d.shape
(300, 10000)
  • k-평균 알고리즘 활용
1
2
3
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state = 42)
km.fit(fruits_2d)
KMeans(n_clusters=3, random_state=42)
  • 모형학습 후, labels
1
print(km.labels_)
[2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 0 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 0 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1]
  • 직접 샘플의 개수 확인
1
print(np.unique(km.labels_, return_counts=True))
(array([0, 1, 2], dtype=int32), array([111,  98,  91]))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt

def draw_fruits(arr, ratio=1):
n = len(arr) # n은 샘플 개수입니다
# 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 10으로 나누어 전체 행 개수를 계산합니다.
rows = int(np.ceil(n/10))
# 행이 1개 이면 열 개수는 샘플 개수입니다. 그렇지 않으면 10개입니다.
cols = n if rows < 2 else 10
fig, axs = plt.subplots(rows, cols,
figsize=(cols*ratio, rows*ratio), squeeze=False)
for i in range(rows):
for j in range(cols):
if i*10 + j < n: # n 개까지만 그립니다.
axs[i, j].imshow(arr[i*10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
1
draw_fruits(fruits[km.labels_==0])

png

클러스터 중심

1
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)

png

1
print(km.transform(fruits_2d[100:101]))
[[3393.8136117  8837.37750892 5267.70439881]]
1
print(km.predict(fruits_2d[100:101]))
[0]
1
draw_fruits(fruits[100:101])

png

최적의 k-평균 찾기

1
2
3
4
5
6
7
inertia = []
for k in range(2, 7):
km = KMeans(n_clusters = k, random_state=42)
km.fit(fruits_2d)
inertia.append(km.inertia_)
plt.plot(range(2, 7), inertia)
plt.show()

png

  • 위 결과 최적의 k-평균은 3.0 정도 된다.

  • chapter6. 비지도학습은 잘 안 쓰인다. 시각화 문법만 유의해서 살펴보자.

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_6_1

비지도 학습

  • vs 지도 학습
    • 종속 변수가 있다 = 타겟이 있다
  • 비지도 학습은 종속변수 및 타겟이 없다.
  • 분류
    • 다중분류
    • 전제조건 : (다양한 유형) 데이터가 많아야 함
    • 딥러닝과 연관이 됨(자연어 처리, 이미지)

데이터 불러오기

  • 과일가게 문제 : 많은 과일 사진을 각 과일 별로 분류해야 한다.
  • 다음을 참고하라 : http://bit.ly/hg-06-1
1
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2022-03-31 03:09:03--  https://bit.ly/fruits_300_data
Resolving bit.ly (bit.ly)... 67.199.248.11, 67.199.248.10
Connecting to bit.ly (bit.ly)|67.199.248.11|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following]
--2022-03-31 03:09:03--  https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy
Resolving github.com (github.com)... 140.82.121.4
Connecting to github.com (github.com)|140.82.121.4|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following]
--2022-03-31 03:09:03--  https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.111.133, 185.199.108.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.111.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3000128 (2.9M) [application/octet-stream]
Saving to: ‘fruits_300.npy’

fruits_300.npy      100%[===================>]   2.86M  --.-KB/s    in 0.03s   

2022-03-31 03:09:03 (107 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
  • numpy 파일을 불러옴
1
2
3
4
5
6
import numpy as np
import matplotlib.pyplot as plt

fruits = np.load('fruits_300.npy')
print(fruits.shape)
print(fruits.ndim) # 차원 수 확인
(300, 100, 100)
3
  • 첫 번째 차원(300) = 샘플의 개수
  • 두 번째 차원(100) = 이미지 높이
  • 세 번째 차원(100) = 이미지 너비
  • 이미지 크기 100 x 100
1
2
# 첫 번째 행에 있는 픽셀 100개에 들어있는 값을 출력
fruits[0, 0, :]
array([  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   2,   1,   2,   2,   2,   2,   2,   2,   1,   1,
         1,   1,   1,   1,   1,   1,   2,   3,   2,   1,   2,   1,   1,
         1,   1,   2,   1,   3,   2,   1,   3,   1,   4,   1,   2,   5,
         5,   5,  19, 148, 192, 117,  28,   1,   1,   2,   1,   4,   1,
         1,   3,   1,   1,   1,   1,   1,   2,   2,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
         1,   1,   1,   1,   1,   1,   1,   1,   1], dtype=uint8)
  • 이미지 시각화
    • 흑백 사진을 담고 있다.
    • 0~255까지의 정숫값을 가진다.
1
2
plt.imshow(fruits[0], cmap='gray')  # cmap 은 옵션
plt.show()

png

1
2
3
4
plt.imshow(fruits[0], cmap='gray_r')  # cmap 은 옵션
plt.show()
# 밝은 부분은 0에 가깝다
# 어두운 부분은 255에 가깝다

png

  • 여러 이미지 시각화
1
2
3
4
5
fig, axs = plt.subplots(1, 2)
axs[0].imshow(fruits[100], cmap='gray_r')
axs[1].imshow(fruits[200], cmap='gray_r')

plt.show()

png

픽셀값 분석

1
2
3
4
5
6
7
apple = fruits[0:100].reshape(-1, 100 * 100)  # 두 번째와 세 번째 차원 크기가 100이므로.
pineapple = fruits[100:200].reshape(-1, 100 * 100)
banana = fruits[200:300].reshape(-1, 100 * 100)

print(apple.shape)
print(pineapple.shape)
print(banana.shape)
(100, 10000)
(100, 10000)
(100, 10000)
  • axis = 0 vs axis = 1 차이 확인 (p.293)

  • 각 이미지에 대한 픽셀 평균값 비교

1
2
# axis = 1 = 열
print(apple.mean(axis = 1))
[ 88.3346  97.9249  87.3709  98.3703  92.8705  82.6439  94.4244  95.5999
  90.681   81.6226  87.0578  95.0745  93.8416  87.017   97.5078  87.2019
  88.9827 100.9158  92.7823 100.9184 104.9854  88.674   99.5643  97.2495
  94.1179  92.1935  95.1671  93.3322 102.8967  94.6695  90.5285  89.0744
  97.7641  97.2938 100.7564  90.5236 100.2542  85.8452  96.4615  97.1492
  90.711  102.3193  87.1629  89.8751  86.7327  86.3991  95.2865  89.1709
  96.8163  91.6604  96.1065  99.6829  94.9718  87.4812  89.2596  89.5268
  93.799   97.3983  87.151   97.825  103.22    94.4239  83.6657  83.5159
 102.8453  87.0379  91.2742 100.4848  93.8388  90.8568  97.4616  97.5022
  82.446   87.1789  96.9206  90.3135  90.565   97.6538  98.0919  93.6252
  87.3867  84.7073  89.1135  86.7646  88.7301  86.643   96.7323  97.2604
  81.9424  87.1687  97.2066  83.4712  95.9781  91.8096  98.4086 100.7823
 101.556  100.7027  91.6098  88.8976]
  • 각 과일에 대한 히스토그램 작성
    • 히스토그램은 값이 발생하는 빈도를 그래프로 표시한 것.
    • 보통 x축은 값의 구간이고, y축은 발생 빈도이다.
1
2
3
4
5
6
7
plt.hist(np.mean(apple, axis = 1), alpha = 0.8)     # alpha 는 투명도 조절하는 매개변수
plt.hist(np.mean(pineapple, axis = 1), alpha = 0.8)
plt.hist(np.mean(banana, axis = 1), alpha = 0.8)
plt.legend(['apple', 'pineapple', 'banana'])
plt.xlabel('pixel average')
plt.ylabel('prequency')
plt.show()

png

  • 위 결과에서 banana는 픽셀 평균값이 다른 두 과일과 확연히 다르다.

    • banana는 픽셀 평균값으로 구분하기 쉽다.
  • 이번에는 샘플의 평균값이 아니라 픽셀별 평균값을 비교해 본다.

  • 즉, 전체 샘플에 대해 각 픽셀의 평균을 조사한다.

  • axis=0으로 지정하여 픽셀의 평균을 계산하면 된다.

1
2
3
4
5
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].bar(range(10000), np.mean(apple, axis=0))
axs[1].bar(range(10000), np.mean(pineapple, axis=0))
axs[2].bar(range(10000), np.mean(banana, axis=0))
plt.show()

png

  • 대표 이미지
1
2
3
4
5
6
7
8
9
apple_mean = np.mean(apple, axis=0).reshape(100, 100)
pineapple_mean = np.mean(pineapple, axis=0).reshape(100, 100)
banana_mean = np.mean(banana, axis=0).reshape(100, 100)

fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].imshow(apple_mean, cmap='gray_r')
axs[1].imshow(pineapple_mean, cmap='gray_r')
axs[2].imshow(banana_mean, cmap='gray_r')
plt.show()

png

평균값과 가까운 사진 고르기

1
2
3
abs_diff = np.abs(fruits - apple_mean)
abs_mean = np.mean(abs_diff, axis=(1,2))
print(abs_mean.shape)
(300,)
  • 오차의 값이 가장 작은 순서대로 100개를 골라본다.
1
2
3
apple_index = np.argsort(abs_mean)[:100]
fig, axs = plt.subplots(10, 10, figsize=(10,10))
plt.show()

png

1
2
3
4
5
6
7
apple_index = np.argsort(abs_mean)[:100]
fig, axs = plt.subplots(10, 10, figsize=(10,10))
for i in range(10): # 2중 for문
for j in range(10):
axs[i, j].imshow(fruits[apple_index[i*10 + j]], cmap='gray_r')
axs[i, j].axis('off')
plt.show()

png

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_6_3

주성분 분석 (PCA)

PCA (주성분 분석)

  • 차원축소의 개념

  • PCA 개념

  • 과일 사진의 겨우, 10,000개의 픽셀 (높이 x 너비)

  • 10,000개의 특성이 있는 셈(차원)

  • 정형데이터에서도 활용 가능

    • 문자열 데이터, 수치형 데이터 (연속형 데이터, 비연속형 데이터)
    • 캐글 대회 : 수치형 컬럼 304개
      • 연산은 RAM에서 처리
      • 라면을 5개 끓여야 함 / 냄비 크기는 3개 용량
  • 차원축소 = 일부 특성을 선택하여 데이터 크기를 줄임

    • 머신러닝 측면 : 과대적합 방지 & 성능 향상
    • 데이터가 너무 많으니까 RAM에 부하가 걸린다.
    • 따라서 데이터를 줄이고 과대적합을 방지하기 위한 것
  • 양적 데이터 사이의 분산-공분산 관계를 이용해서 선형결합으로 표시되는 주성분을 찾음

  • 2~3개의 주성분으로 전체 변동을 찾는 것이 PCA

p326

  • 그래프를 보면, 처음 10개의 주성분이 (10,000개의 픽셀)

  • 굳이 10,000개의 픽셀을 전부 쓸 필요가 없다.

  • 알고리즘 구성할 때, 필요한 데이터 픽셀 수, 300 x 10,000개 픽셀

  • 원래는 300 x 10,000개 픽셀 필요

  • 그런데, 300 x pc 10 주성분으로 줄임

  • 기존 1시간 걸림 / 이제 10분 걸림

  • 그럼에도 불구하고, 분류가 더 잘되더라.

PCA 클래스

데이터 불러오기

1
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
--2022-03-31 06:16:36--  https://bit.ly/fruits_300_data
Resolving bit.ly (bit.ly)... 67.199.248.11, 67.199.248.10
Connecting to bit.ly (bit.ly)|67.199.248.11|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy [following]
--2022-03-31 06:16:36--  https://github.com/rickiepark/hg-mldl/raw/master/fruits_300.npy
Resolving github.com (github.com)... 192.30.255.113
Connecting to github.com (github.com)|192.30.255.113|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy [following]
--2022-03-31 06:16:36--  https://raw.githubusercontent.com/rickiepark/hg-mldl/master/fruits_300.npy
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3000128 (2.9M) [application/octet-stream]
Saving to: ‘fruits_300.npy’

fruits_300.npy      100%[===================>]   2.86M  --.-KB/s    in 0.04s   

2022-03-31 06:16:36 (64.8 MB/s) - ‘fruits_300.npy’ saved [3000128/3000128]
  • 배열로 업로드
1
2
3
4
import numpy as np
fruits = np.load("fruits_300.npy")
fruits_2d = fruits.reshape(-1, 100*100)
fruits_2d.shape
(300, 10000)
  • sklearn.decomposition 모듈
    • 사이킷런은 이 모듈 아래 PCA 클래스로 주성분 분석 알고리즘을 제공한다.
    • k-평균과 마찬가지로 비지도 학습이기 때문에 fit()메서드에 타깃값을 제공하지 않는다.
1
2
3
4
5
from sklearn.decomposition import PCA
pca = PCA(n_components = 50)

# PCA 50개 성분으로 300 x 10000 픽셀값을 압축
pca.fit(fruits_2d)
PCA(n_components=50)
  • PCA 클래스가 찾은 주성분은 components_ 속성에 저장되어 있다.
1
print(pca.components_.shape)
(50, 10000)
  • 그래프 그리기
    • draw_fuits()함수를 사용해서 이 주성분을 그림으로 그려보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import matplotlib.pyplot as plt

def draw_fruits(arr, ratio=1):
n = len(arr) # n은 샘플 개수입니다
# 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 10으로 나누어 전체 행 개수를 계산합니다.
rows = int(np.ceil(n/10))
# 행이 1개 이면 열 개수는 샘플 개수입니다. 그렇지 않으면 10개입니다.
cols = n if rows < 2 else 10
fig, axs = plt.subplots(rows, cols,
figsize=(cols*ratio, rows*ratio), squeeze=False)
for i in range(rows):
for j in range(cols):
if i*10 + j < n: # n 개까지만 그립니다.
axs[i, j].imshow(arr[i*10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()

draw_fruits(pca.components_.reshape(-1, 100, 100))

png

1
2
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
(300, 50)
  • 데이터의 원래 크기 대비해서 1/200 줄임
  • 용량이 줄었다는 것과 똑같음

원본 데이터 재구성

  • 10,000개의 특성을 50개로 줄임
  • 100% 재구성은 어렵지만, 그래도 쓸만하다.
1
2
fruits_inverse = pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape)
(300, 10000)
  • 그래프 작성
    • 10000개의 데이터가 복원되었다.
    • 이 데이터를 100 x 100 크기로 바꾸어 100개씩 나누어 출력한다.
1
2
fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100)
print(fruits_reconstruct.shape)
(300, 100, 100)
1
2
3
4
# 압축을 풀고 사용하는 연쇄적인 과정
for start in [0, 100, 200]:
draw_fruits(fruits_reconstruct[start:start + 100])
print("\n")

png

png

png

설명된 분산

1
2
plt.plot(pca.explained_variance_ratio_)
plt.show()

png

  • 처음 10개의 주성분이 대부분의 분산을 표현한다.
  • 11개 주성분부터 ~50개까지는 잘 설명이 안됨
1
print(np.sum(pca.explained_variance_ratio_))
0.9215782334086065

다른 알고리즘과 함께 사용하기

  • 3개의 과일 사진 분류 위해 로지스틱 회귀
1
2
3
4
5
6
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()

target = np.array([0]*100 + [1]*100 + [2]*100)
print(target)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2]
  • 교차검증 진행
1
2
3
4
from sklearn.model_selection import cross_validate
scores = cross_validate(lr, fruits_2d, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
0.9966666666666667
1.5795912265777587
  • PCA 수행 후, 학습 시간 비교
1
2
3
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
1.0
0.12322616577148438
  • PCA 수행 후, fit_time이 짧게 단축되었다.

    • 1.57 -> 0.12 로 시간이 짧아졌다.
    • 그러니 특성이 너무 많으면 PCA를 사용하자.
  • 주 성분의 매개변수 개수 지정, 분산비율 지정

1
2
3
pca = PCA(n_components = 0.5)
pca.fit(fruits_2d)
print(pca.n_components_)
2
  • 주성분을 2개로 압축시킴.
1
2
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
(300, 2)
1
2
3
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
0.9933333333333334
0.051814031600952146


/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
  • 차원 축소된 데이터를 k-평균 알고리즘에 추가한다.
1
2
3
4
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_pca)
print(np.unique(km.labels_, return_counts = True))
(array([0, 1, 2], dtype=int32), array([110,  99,  91]))
1
2
3
for label in range(0,3):
draw_fruits(fruits[km.labels_ == label])
print("\n")

png

png

png

  • 시각화로 뿌려주기
1
2
3
4
5
for label in range(0,3):
data = fruits_pca[km.labels_ == label]
plt.scatter(data[:, 0], data[:, 1])
plt.legend(['apple', 'banana', 'pineapple'])
plt.show()

png

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_5_3

트리의 앙상블

  • lightGBM 기억!
    • GBM –> XGBoost –> LightGBM
    • 참고 1. 모델개발속도가 빨라졌나?
    • 참고 2. 모델의 성능이 좋아졌나?
  • TabNet(2019)
    • 딥러닝 컨셉!

랜덤 포레스트(Forest)

  • 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만든다.

  • 결정 트리 나무를 500개 심기

  • 최종적인 결정은 투표 방식

    • 나무-1 : 양성
    • 나무_2 : 음성
    • 나무_3 : 양성
      ..
    • 나무-500 : 양성
  • 데이터 불러오기

  • 넘파이 배열로 변환

  • 데이터 세트 나누기

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

wine = pd.read_csv('https://bit.ly/wine_csv_data')

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

train_input, test_input, train_target, test_target = train_test_split(data,
target,
test_size=0.2,
random_state=42)
  • 267p
    • cross_validate()함수 : 교차 검증 수행
    • RandomForestClassifier는 기본적으로 100개의 트리를 사용하므로 n_jops=-1로 지정하여 모든 CPU 코어를 사용한다.
1
2
3
4
5
6
7
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs=-1, random_state=42) # n_jobs = -1은 pc의 모든 코어를 사용하겠다는 뜻
scores = cross_validate(rf, train_input, train_target,
return_train_score = True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9973541965122431 0.8905151032797809
  • 랜덤 포레스트는 결정 트리의 앙상블이기 때문에 DecisionTreeClassifier가 제공하는 매개변수를 모두 제공한다.
  • 또한 결정 트리의 큰 장점 중 하나인 특성 중요도를 계산한다.
  • 랜덤 포레스트 모델을 훈련 세트에 훈련한 후 특성 중요도를 출력해 본다.
1
2
rf.fit(train_input, train_target)
print(rf.feature_importances_)
[0.23167441 0.50039841 0.26792718]
  • 두 번째 특성인 sugar가 가장 중요도가 높다는 것을 알 수 있다.

  • RandomForestClassifier는 자체적으로 모델을 평가하는 점수를 얻을 수도 있다.

  • 이 점수를 얻으려면 RandomForestClassifier 클래스의 oob_score 매개변수를 True로 지정해야 한다.

  • oob_score = True로 지정하고 모델을 훈련하여 OOB 점수를 출력해보자.

1
2
3
rf = RandomForestClassifier(oob_score = True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)
0.8934000384837406
  • 교차 검즈에서 얻은 점수와 매우 비슷한 결과를 얻었다.

그래이디언트 부스팅

  • 그 이전 트리의 오차를 보완하는 방식으로 사용
  • 깊이가 얕은 트리를 사용.
  • 학습률 매개변수로 속도를 조절.
  • 단점 : 속도가 느림.
1
2
3
4
5
6
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=42)
scores = cross_validate(gb, train_input, train_target,
return_train_score = True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.8881086892152563 0.8720430147331015
  • 거의 과대적합이 되지 않았다.
  • 그래디언트 부스팅은 결정 트리의 개수를 늘려도 과대적합에 매우 강하다.
  • 학습률을 증가시키고 트리의 개수를 늘리면 조금 더 성능이 향상될 수 있다.
1
2
3
4
5
gb = GradientBoostingClassifier(n_estimators = 500, learning_rate = 0.2, random_state = 42)
scores = cross_validate(gb, train_input, train_target,
return_train_score = True, n_jobs=-1)

print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.9464595437171814 0.8780082549788999
  • 결정 트리 개수를 500개로 늘렸다. 5배로 늘렸지만 과대적합을 잘 억제하고 있다.
  • 학습률 learning_rate의 기본값은 0.1이다.
  • 그레이디언트 부스팅도 특성 중요도를 제공한다.
  • 결과에서 볼 수 있듯이 그레이디언트 부스팅이 랜덤 포레스트보다 일부 특성(당도)에 더 집중한다.
1
2
gb.fit(train_input, train_target)
print(gb.feature_importances_)
[0.15872278 0.68010884 0.16116839]
  • 흐름

      1. 데이터 전처리 / 시각화
      1. 기본 모형으로 전체 흐름을 설계
      1. 여러 모형으로 비교 대조
      1. 교차 검증, 하이퍼 파라미터 성능 비교
    • 1등 하는 그날까지
  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_5_1

결정 트리

  • 결정 트리로 다음 문제를 해결해 보자
    • 와인 캔에 인쇄된 알코올 도수, 당도, pH값으로 와인 종류를 구별해야 한다.

로지스틱 회귀로 와인 분류하기

  • 우선 로지스틱 회귀로 문제 해결을 시도해본다.

데이터 불러오기

  • 와인데이터
    • alcohol(알고올 도수), sugar(당도), pH(산도)
    • 클래스 0 = 레드 와인
    • 클래스 1 = 화이트 와인
1
2
3
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head(5) # 데이터 잘 들어왔는지 확인

alcohol sugar pH class
0 9.4 1.9 3.51 0.0
1 9.8 2.6 3.20 0.0
2 9.8 2.3 3.26 0.0
3 9.8 1.9 3.16 0.0
4 9.4 1.9 3.51 0.0

  <script>
    const buttonEl =
      document.querySelector('#df-ed50d596-f5ff-4ae2-a52f-cd3b0b3d75ad button.colab-df-convert');
    buttonEl.style.display =
      google.colab.kernel.accessAllowed ? 'block' : 'none';

    async function convertToInteractive(key) {
      const element = document.querySelector('#df-ed50d596-f5ff-4ae2-a52f-cd3b0b3d75ad');
      const dataTable =
        await google.colab.kernel.invokeFunction('convertToInteractive',
                                                 [key], {});
      if (!dataTable) return;

      const docLinkHtml = 'Like what you see? Visit the ' +
        '<a target="_blank" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'
        + ' to learn more about interactive tables.';
      element.innerHTML = '';
      dataTable['/images/chapter_5_1/output_type'] = 'display_data';
      await google.colab./images/chapter_5_1/output.render/images/chapter_5_1/output(dataTable, element);
      const docLink = document.createElement('div');
      docLink.innerHTML = docLinkHtml;
      element.appendChild(docLink);
    }
  </script>
</div>
  • info()
    • 결측치 확인 / 변수 타입
1
wine.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6497 entries, 0 to 6496
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   alcohol  6497 non-null   float64
 1   sugar    6497 non-null   float64
 2   pH       6497 non-null   float64
 3   class    6497 non-null   float64
dtypes: float64(4)
memory usage: 203.2 KB
1
2
wine.describe()
# 표준화가 안되어 있음을 알 수 있다.

alcohol sugar pH class
count 6497.000000 6497.000000 6497.000000 6497.000000
mean 10.491801 5.443235 3.218501 0.753886
std 1.192712 4.757804 0.160787 0.430779
min 8.000000 0.600000 2.720000 0.000000
25% 9.500000 1.800000 3.110000 1.000000
50% 10.300000 3.000000 3.210000 1.000000
75% 11.300000 8.100000 3.320000 1.000000
max 14.900000 65.800000 4.010000 1.000000

  <script>
    const buttonEl =
      document.querySelector('#df-decc42d0-d914-4603-8cd7-d87f4c9e480a button.colab-df-convert');
    buttonEl.style.display =
      google.colab.kernel.accessAllowed ? 'block' : 'none';

    async function convertToInteractive(key) {
      const element = document.querySelector('#df-decc42d0-d914-4603-8cd7-d87f4c9e480a');
      const dataTable =
        await google.colab.kernel.invokeFunction('convertToInteractive',
                                                 [key], {});
      if (!dataTable) return;

      const docLinkHtml = 'Like what you see? Visit the ' +
        '<a target="_blank" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'
        + ' to learn more about interactive tables.';
      element.innerHTML = '';
      dataTable['/images/chapter_5_1/output_type'] = 'display_data';
      await google.colab./images/chapter_5_1/output.render/images/chapter_5_1/output(dataTable, element);
      const docLink = document.createElement('div');
      docLink.innerHTML = docLinkHtml;
      element.appendChild(docLink);
    }
  </script>
</div>

표준화 작업

  • 배열로 바꿔서 진행
1
2
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()  # 참고할 데이터
target = wine['class'].to_numpy() # class = 타깃값 = 어떤 와인인지 구분하는 것이 목표

훈련데이터와 테스트데이터로 분리

  • train_test_split() 함수는 설정값을 지정하지 않으면 25%를 테스트 세트로 지정한다.
  • 이번엔 샘플 개수가 충분히 많으므로 20% 정도만 테스트 세트로 나눈다.
  • test = 0.2 에는 이러한 의도가 담겨 있다.
1
2
3
4
5
6
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
data, target, test_size=0.2, random_state=42 # test_size=0.2 는 20%를 테스트 세트로 한다는 뜻.
)

print(train_input.shape, test_input.shape)
(5197, 3) (1300, 3)
  • 이제 표준화 진행하자
1
2
3
4
5
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

모델 만들기

로지스틱 회귀

  • 표준변환된 train_scaled와 test_scaled를 사용해 로지스틱 회귀 모델을 훈련한다.
1
2
3
4
5
6
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
print(lr.coef_, lr.intercept_)
0.7808350971714451
0.7776923076923077
[[ 0.51270274  1.6733911  -0.68767781]] [1.81777902]
  • 점수가 높게 나오지 않았다.
  • 결정 트리를 이용하여 좀 더 쉽게 문제를 해결해보자

로지스틱 회귀

  • 수식

의사결정트리의 기본 알고리즘을 활용해서, MS, 구글 등 이런 회사들이 신규 알고리즘을 만듬

  • XGBoost, lightGBM, CatBoost
  • 캐글 정형데이터
  • lightGBM (지금 현재 실무에서 많이 쓰임)
    • 4월 말까지는 코드에 집중. 대회 나감
    • PPT (알고리즘 소개)

결정 트리 (Decision Tree)

  • 스무 고개와 같다.
  • 질문을 하나씩 던져서 정답과 맞춰가는 것이다.
  • 표준화된 훈련 세트를 이용하여 결정트리를 사용해 본다.
1
2
3
4
5
6
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target)

print(dt.score(train_scaled, train_target)) # 훈련 세트
print(dt.score(test_scaled, test_target)) # 테스트 세트
0.996921300750433
0.8592307692307692
  • 위 코드의 두 결과는 차이가 있다.
  • 두 결과가 유사하게 나와야 한다.
  • 앞으로 ‘가지치기’에서 차이를 좁히는 과정을 진행한다.
1
2
3
4
5
6
# 현재 트리의 형태를 출력해본다.
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()

png

  • 과대적합이 나오는 이유 : 조건식을 걸기 때문
1
2
3
4
# plot_tree()함수에서 트리의 깊이를 제한하여 출력해 본다.
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

png

  • 불순도 : 운동회 ox 퀴즈에서 정답을 맞힌 사람만 살아남는 것과 같은 원리

가지치기

  • 과대적합을 방지하기 위한 것
  • 가지치기를 통해 두 결과가 유사하게 출력된다.
1
2
3
4
dt = DecisionTreeClassifier(max_depth = 3, random_state=42) # max_depth 매개변수 조절을 통해 가지치기 한다.
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) # 훈련 세트
print(dt.score(test_scaled, test_target)) # 데이터 세트
0.8454877814123533
0.8415384615384616
  • 훈련 세트와 테스트 성능이 유사하게 출력되었다.
  • 이런 모델을 트리 그래프로 그린다면 훨씬 이해햐기 쉬울 것이다.
1
2
3
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

png

  • 훨씬 보기 좋게 출력되었다.

  • 루트 노트

    • 당도(sugar)를 기준으로 훈련세트를 나눈다.
  • 깊이 1의 노드

    • 모두 당도(sugar)를 기준으로 훈련 세트를 나눈다.
  • 깊이 2의 노드

    • 맨 왼쪽의 노드만 당도를 기준으로 나눈다.
    • 왼쪽에서 두 번째 노드는 알고올 도수(alcohol)를 기준으로 나눈다.
    • 오른쪽 두 노드는 pH를 기준으로 나눈다.
  • 리프 노드

    • 왼쪽에서 3번째에 있는 노드만 음성 클래스가 더 많다.
      • 이 노드에 도착해야만 레드 와인으로 예측한다.
      • 이 노드에 도달하려면 -0.802 < sugar < -0.239, alcohol < -0.454 라는 조건을 만족해야 한다.
      • 즉, -0.802 < sugar < -0.239, alcohol < -0.454 이면 레드와인이다
  • 그런데 -0.802라는 음수로 된 당도를 어떻게 설명해야할까?

    • 좀 더 설명하기 쉽게 바꿔보자.
  • 특성값의 스케일은 결정 트리 알고리즘에 아무런 영향을 미치지 않는다.

  • 따라서 표준화 전처리를 할 필요가 없다.

  • 전처리하기 전의 훈련 세트(train_input)와 테스트 세트(test_input)로 결정 트리 모델을 다시 훈련해 본다.

1
2
3
4
5
6
dt = DecisionTreeClassifier(max_depth=3, random_state=42)

# 가지치기 때와 달리 train_scaled를 사용하지 않았다. 표준화 전처리 할 필요가 없기 때문인 듯.
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(train_input, train_target))
0.8454877814123533
0.8454877814123533
  • 정확히 같은 결과가 나왔다.
  • 트리도 그려보자.
1
2
3
plt.figure(figsize=(20, 15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

png

  • 같은 트리지만 특성값을 표준점수로 바꾸지 않았기에 이해하기 훨씬 쉽다.

    • 적어도 당도를 음수로 표기하는 것보단 보기 좋다.
  • 다음 조건을 만족하는 것이 레드 와인이다.

    • (1.625 < sugar < 4.325) AND (alcohol < 11.025) = 레드 와인
  • 특성 중요도

    • 결정 트리는 어떤 특성이 가장 유용한지 나타내는 특성 중요도를 계산해준다.
    • 이 트리의 루트 노드와 깊이 1에서 sugar를 사용했기 때문에 아마 sugar가 가장 유용한 특성 중 하나일 것이다.
    • 특성 중요도는 결정 트리 모델의 feature_importances_ 속성에 저장되어 있다.
1
print(dt.feature_importances_)
[0.12345626 0.86862934 0.0079144 ]
  • alcohol ,sugar, ph 순서이기 때문에 두 번째인 sugar의 중요도가 가장 높은 것을 알 수 있다.

  • 번외

1
2
3
4
5
6
7
8
9
10
11
12
import graphviz
from sklearn import tree

# DOT data
dot_data = tree.export_graphviz(dt, out_file=None,
feature_names = ['alcohol', 'sugar', 'pH'],
filled=True)

# Draw graph
graph = graphviz.Source(dot_data, format="png")
graph
graph.render("decision_tree_graphivz")
'decision_tree_graphivz.png'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from matplotlib.colors import ListedColormap, to_rgb
import numpy as np

plt.figure(figsize=(20, 15))
artists = plot_tree(dt, filled = True,
feature_names = ['alcohol', 'sugar', 'pH'])

colors = ['blue', 'red']
for artist, impurity, value in zip(artists, dt.tree_.impurity, dt.tree_.value):
r, g, b = to_rgb(colors[np.argmax(value)])
f = impurity * 2
artist.get_bbox_patch().set_facecolor((f + (1-f)*r, f + (1-f)*g, f + (1-f)*b))
artist.get_bbox_patch().set_edgecolor('black')

plt.show()

png

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝

chapter_5_2

교차 검증과 그리드 서치

  • 키워드 : 하이퍼 파라미터
  • 데이터가 작을 때, 주로 사용
  • 하이퍼 파라미터
    • max_depth : 3, 정확도가 84%
  • 결론
    • 모르면 디폴트만 쓰자!
    • 가성비 (시간 대비 성능 보장 안됨!)

검증 세트

  • 테스트 세트(1회성)
  • 훈련 데이터를 훈련 데이터 + 검증 데이터로 재 분할

현실

  • 테스트 데이터가 별도로 존재하지 않음!

  • 전체 데이터 = 훈련 (6) : 검증 (2) : 테스트 (2)

    • 테스트 데이터는 모르는 데이터로 생각!
  • 캐글

    • 캐글에서는 훈련, 테스트 데이터가 제공된다. 훈련 데이터만 한 번 쪼개서 사용하면 된다.

참고

1
2
3
4
5
import pandas as pd
wine = pd.read_csv("https://bit.ly/wine_csv_data")

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
1
2
3
4
5
from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(
data, target, test_size=0.2, random_state=42
)
1
2
3
sub_input, val_input, sub_target, val_target = train_test_split(
train_input, train_target, test_size=0.2, random_state=42
)

모델 만든 후 평가

1
2
3
4
5
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))
0.9971133028626413
0.864423076923077

교차 검증

  • 많이 하면 많이 할수록 좋다.
  • 교차 검증의 목적 : 좋은 모델이 만들어진다!
    • 좋은 모델 != 성능 좋은 모델
    • 좋은 모델 = 과대 적합이 아닌 모델 = 모형의 오차가 적은 모델 = 안정적인 모델
  • 교재 245p
    • 모델 평가 1 : 90%
    • 모델 평가 2 : 85%
    • 모델 평가 3 : 80%
  • 단점 : 시간이 오래 걸림

교차 검증 함수

1
2
3
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
{'fit_time': array([0.01164412, 0.00772762, 0.00744891, 0.00796771, 0.00716805]), 'score_time': array([0.00128865, 0.00070405, 0.0007143 , 0.00097823, 0.00069904]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
  • 최종점수 평균 구하기
1
2
import numpy as np
print(np.mean(scores['test_score']))
0.855300214703487
  • 훈련 세트 섞은 후, 10-폴드 교차검증
1
2
3
4
5
from sklearn.model_selection import StratifiedKFold
splitter = StratifiedKFold(n_splits = 10, shuffle = True, random_state=42)
scores = cross_validate(dt, train_input, train_target, cv = splitter)

print(np.mean(scores['test_score']))
0.8574181117533719

하이퍼 파라미터 튜닝 꼭 하고 싶다!

  • 랜덤 서치 사용하자!
    • 그리드 서치보다 편리하다
  • 자동으로 잡아주는 라이브러리들이 등장하기 시작함
    • hyperopt
1
2
3
4
5
6
7
8
9
%%time

from sklearn.model_selection import GridSearchCV
params = {
'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005],
}
# dt = DecisionTreeClassifier(random_state=42)
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)
CPU times: user 83.8 ms, sys: 1.66 ms, total: 85.5 ms
Wall time: 264 ms
  • pamas에 2줄을 쓰면 시간이 2배 이상 더 걸린다.
1
2
3
4
5
6
7
8
9
10
%%time

from sklearn.model_selection import GridSearchCV
params = {
'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005],
'max_depth' : [3, 4, 5, 6, 7]
}
# dt = DecisionTreeClassifier(random_state=42)
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)
CPU times: user 191 ms, sys: 1.13 ms, total: 192 ms
Wall time: 674 ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%%time

from sklearn.model_selection import GridSearchCV
params = {
'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005],
'max_depth' : [3, 4, 5, 6, 7]
}
# dt = DecisionTreeClassifier(random_state=42)
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)

dt = gs.best_estimator_
print(dt)
print(dt.score(train_input, train_target))
print(gs.best_params_)
DecisionTreeClassifier(max_depth=7, min_impurity_decrease=0.0005,
                       random_state=42)
0.8830094285164518
{'max_depth': 7, 'min_impurity_decrease': 0.0005}
CPU times: user 284 ms, sys: 38.7 ms, total: 323 ms
Wall time: 2.15 s
  • 이 부분에 의해 결과가 (5x5=)25개 출력된다.
    • ‘min_impurity_decrease’ : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]
    • ‘max_depth’ : [3, 4, 5, 6, 7]
1
print(gs.cv_results_['mean_test_score'])
[0.84125583 0.84125583 0.84125583 0.84125583 0.84125583 0.85337806
 0.85337806 0.85337806 0.85337806 0.85318557 0.85780355 0.85799604
 0.85857352 0.85857352 0.85838102 0.85645721 0.85799678 0.85876675
 0.85972866 0.86088306 0.85607093 0.85761031 0.85799511 0.85991893
 0.86280466]

랜덤 서치

  • p252. 매개변수 값의목록을 전달하는 것이 아니라 매개변수를 샘플링할 수 있도록 확률 분포 객체를 전달.
1
2
3
from scipy.stats import uniform, randint
rgen = randint(0, 10)
rgen.rvs(10)
array([7, 9, 7, 4, 2, 0, 4, 8, 6, 3])
1
np.unique(rgen.rvs(1000), return_counts = True)
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
 array([103,  90,  88, 110,  84, 115, 105, 102, 104,  99]))
  • 254p
1
2
3
4
5
6
7
8
9
10
11
from sklearn.model_selection import RandomizedSearchCV

params = {
'min_impurity_decrease' : uniform(0.0001, 0.001),
'max_depth' : randint(20,50)
}

gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42),
params, n_iter = 100, n_jobs = -1, random_state=42)

gs.fit(train_input, train_target)
RandomizedSearchCV(estimator=DecisionTreeClassifier(random_state=42),
                   n_iter=100, n_jobs=-1,
                   param_distributions={'max_depth': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fa6910bdd90>,
                                        'min_impurity_decrease': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7fa6907f3810>},
                   random_state=42)
  • 위 parmas에 정의된 매개변수 범위에서 총 100번(n_iter 매개변수)을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다.
  • 앞서 그리드 서치보다 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다.
  • 결과를 확인해보자. 먼저 최적의 매개변수 조합을 출력한다.
1
print(gs.best_params_)
{'max_depth': 29, 'min_impurity_decrease': 0.000437615171403628}
  • 최고의 교차 검증 점수도 확인한다.
1
print(np.max(gs.cv_results_['mean_test_score']))
0.8689635004071962
  • 최적의 모델은 이미 전체 훈련 세트(train_input, train_target)로 훈련되어 best_estimator_속성에 저장되어 있다.
  • 이 모델을 최종 모델로 결정하고 테스트 세트의 성능을 확인해 보자
1
2
dt = gs.best_estimator_
print(dt.score(test_input, test_target))
0.8638461538461538
  • 테스트 세트 점수는 검증 세트에 대한 점수보다 조금 작은 것이 일반적이다.

  • 여기까지 두 서치를 사용해 본 결과, 랜덤 서치가 더 사용하기 용이하였다.

  • Reference : 혼자 공부하는 머신러닝 + 딥러닝