728x90
반응형

 지금까지 tensorflow.keras를 사용해서 일어나는 전반적인 과정에 대해 천천히 살펴보았다. 모델 평가는 하이퍼 파라미터 최적화(Hyper Parameter optimization)의 개념으로 들어가게 되면, 그 양이 꽤 길어지므로, 거기까진 나중에 따로 들어가도록 하겠다.

 이번 포스트에서는 지금까지 학습했던 내용들을 정리하고, .evaluate() 함수로 지금까지 만들었던 모델의 성능을 평가한 후, 모델을 저장 및 불러오기를 해보도록 하겠다.

 

 

1. 학습 코드 최종 정리 및 모델 평가

# Import module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout, Flatten)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)

train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)





# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))





# 모델 컴파일
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt,
              loss = "sparse_categorical_crossentropy",
              metrics = ["accuracy"])
              
              
              
              
              
early_stop = keras.callbacks.EarlyStopping(monitor="val_loss", min_delta=0, patience=10, restore_best_weights=True)
history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size=5000,
                    validation_data=(valid_images, valid_labels),
                    callbacks=[early_stop])
Epoch 1/100
11/11 [==============================] - 2s 162ms/step - loss: 1.4978 - accuracy: 0.4962 - val_loss: 0.4193 - val_accuracy: 0.8836
Epoch 2/100
11/11 [==============================] - 1s 53ms/step - loss: 0.3403 - accuracy: 0.9037 - val_loss: 0.2377 - val_accuracy: 0.9270
Epoch 3/100
11/11 [==============================] - 1s 58ms/step - loss: 0.2092 - accuracy: 0.9373 - val_loss: 0.1695 - val_accuracy: 0.9480
Epoch 4/100
11/11 [==============================] - 1s 51ms/step - loss: 0.1490 - accuracy: 0.9554 - val_loss: 0.1303 - val_accuracy: 0.9590
Epoch 5/100
11/11 [==============================] - 1s 52ms/step - loss: 0.1113 - accuracy: 0.9664 - val_loss: 0.1108 - val_accuracy: 0.9632

...

Epoch 19/100
11/11 [==============================] - 1s 47ms/step - loss: 0.0034 - accuracy: 0.9996 - val_loss: 0.0777 - val_accuracy: 0.9800
Epoch 20/100
11/11 [==============================] - 1s 47ms/step - loss: 0.0024 - accuracy: 0.9998 - val_loss: 0.0782 - val_accuracy: 0.9790
Epoch 21/100
11/11 [==============================] - 1s 52ms/step - loss: 0.0016 - accuracy: 1.0000 - val_loss: 0.0784 - val_accuracy: 0.9786
Epoch 22/100
11/11 [==============================] - 1s 52ms/step - loss: 0.0012 - accuracy: 0.9999 - val_loss: 0.0816 - val_accuracy: 0.9794
Epoch 23/100
11/11 [==============================] - 1s 47ms/step - loss: 0.0012 - accuracy: 0.9999 - val_loss: 0.0813 - val_accuracy: 0.9796
# 모델 평가
>>> model.evaluate(test_images, test_labels)
313/313 [==============================] - 1s 2ms/step - loss: 0.0718 - accuracy: 0.9794
[0.07175429910421371, 0.9793999791145325]
  • model.evaluate(test_set, test_label): 시험 셋으로 생성된 모델을 최종 평가한다.
  • 위 모델은 정확도가 0.979로 굉장히 정확도가 높은 모델이 만들어졌다.
  • 이 evaluate의 accuracy를 기준으로 하이퍼 파라미터를 튜닝하는 경우가 꽤 많은데, 그렇게 되면 시험 셋에 최적화가 돼버릴 위험이 있다.
  • 이를 방지하기 위해, 시험 셋은 최종 평가의 수단으로만 쓰고, 검증 셋을 대신 사용하여, 최적화를 하여, 특정 데이터 셋에 최적화되는 현상은 피하도록 하자.
  • 이 하이퍼 파라미터 튜닝(Hyper Parameter Tuning)은 추후 자세히 다루도록 하겠다.

 

 

 

 

2. 모델 저장하기

  • 지금까지 힘들게 만든 모델은 지금 당장 사용할 수도 있지만, 나중에 사용할 수도 있기 때문에, 모델 저장과 불러오기를 할 수 있어야 한다.
model.save("MNIST_210217.h5")
  • model.save("저장하고자_하는_모델_이름.h5")
  • 위 방법 사용 시, 원하는 이름과 경로에 모델의 아키텍처와 파라미터가 저장되게 된다.

  • 저장되는 내용은 다음과 같다.
  1. 모델을 재구성하기 위한 모델의 구성 정보(모델 아키텍처)
  2. 재학습을 할 수 있도록 마지막 학습 상태
  3. 모델을 구성하는 각 뉴런의 파라미터

 

 

 

 

3. 모델 불러오기

MNIST_model = keras.models.load_model("MNIST_210217.h5")
  • 모델 불러오기는 keras.models.load_model("불러오고자_하는_파일_이름.h5") 로 쉽게 할 수 있다.
  • 모델에 데이터를 넣어 결과를 출력하는 방법은 다음과 같다.
>>> np.argmax(MNIST_model.predict(test_images), axis=-1)
array([7, 2, 1, ..., 4, 5, 6], dtype=int64)
  • 단순 예시를 위해 시험 셋을 넣은 것이며, 실제로는 만들어진 모델의 목적에 맞는 새로운 데이터 셋을 넣어야 한다.
  • 모델의 아키텍처는 summary를 이용해서 보도록 하자.
>>> MNIST_model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
Flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
Hidden1 (Dense)              (None, 300)               235500    
_________________________________________________________________
Hidden2 (Dense)              (None, 200)               60200     
_________________________________________________________________
Hidden3 (Dense)              (None, 100)               20100     
_________________________________________________________________
Output (Dense)               (None, 10)                1010      
=================================================================
Total params: 316,810
Trainable params: 316,810
Non-trainable params: 0
_________________________________________________________________

 

 

 

 

 지금까지 Tensorflow를 사용해서 모델을 학습하는 과정을 세세히 들여다보았다. 지금까지 했던 부분에서 빼먹은 부분이 없는 것은 아니지만, 이는 앞으로 쭉 학습을 해나가면서 채워가도록 하겠다.

728x90
반응형
728x90
반응형

 지금까지 이미지 분류 모델을 만들고, 학습까지 시켜보았다. 지난 포스트에서는 학습 과정을 보며, 학습이 제대로 이루어졌는지를 평가하고, 최적의 epochs를 결정하는 방법에 대해 공부해보았다.

 그러나, 지금 같이 데이터의 양이 작고, epochs가 상대적으로 적은 경우엔 학습이 완전히 끝난 후 그래프를 그려서 학습 과정을 살필 수 있었지만, 만약에 epochs가 1,000 이거나 데이터의 크기가 1,000만 개를 가뿐히 넘겨 학습 시간이 길어지는 경우라면, 이전에 했던 방법으로 접근해서는 안된다.

 이때, 등장하는 개념이 바로 조기 종료다.

 

 

조기 종료(Early Stopping)

0. 선행 코드

# Import module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout, Flatten)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)

train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)





# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))





# 모델 컴파일
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt,
              loss = "sparse_categorical_crossentropy",
              metrics = ["accuracy"])

 

 

 

 

1. 검증 손실 값과 과대 적합

  • 조기 종료를 이해하기 위해선 이전에 만들었던 그래프를 다시 한번 봐야 한다.

  • 위 그래프는 훈련 셋과 검증 셋의 손실 값(Loss)과 정확도(Accuracy)를 시각화한 것이다.
  • 검증 셋의 손실 값(val_loss, 녹색)은, 쭉 감소하다가 갑자기 손실 값이 증가하게 된다.
  • 이는 모델이 학습 셋에 지나치게 최적화되어, 학습 셋이 아닌 다른 데이터 셋을 이상하게 출력하는 과대 적합(Overfitting) 현상이 발생하여, 일어나는 현상이다.
  • 조기 종료는 검증 셋의 손실 값이 최소가 되는 순간(최적의 모델) 학습을 멈춤으로써, 이러한 과대 적합을 멈추는 아주 간단하면서도 강력한 규제 방법 중 하나다.
  • 참고: "Tensorflow-3.2. 이미지 분류 모델(2)-검증 셋(Validation set)"
  • 조기 종료는 이전 "Tensorflow-3.3. 이미지 분류 모델(3)-모델 생성"에서 잠깐 언급하고 넘어갔던, "스트레치 팬츠(Stretch pants) 방식"을 위한 도구 중 하나다.

 

 

 

 

2. 콜벡(callbacks)

  • 콜벡(callbacks)은 학습 과정에서 영향을 주거나, 발생한 통계 데이터를 출력해주는 함수들의 집합이다.
  • 대표적인 callbacks 함수는 이전 포스트에서 우리가 다뤘던 history로, 워낙 유용하다 보니 자동으로 적용되어 있다.
  • callbacks 함수는 Sequential이나 .fit() 메서드에 전달 가능하다.
  • 조기 종료는 이 callbacks 안에 포함되어 있다.

2.1. 조기 종료

  • 조기 종료는 다음과 같은 방법으로 사용할 수 있다.
early_stop = keras.callbacks.EarlyStopping(monitor="val_loss", min_delta=0, patience=10, restore_best_weights=True)
history = model.fit(train_images, train_labels,
                    epochs=1000,
                    batch_size=5000,
                    validation_data=(valid_images, valid_labels),
                    callbacks=[early_stop])
  • keras.callbacks.EarlyStopping()의 중요 파라미터는 다음과 같다.
  1. monitor: 관찰할 값 - 일반적으로 검증 손실 값인 var_loss를 사용하며, 간간히 var_acc가 사용되기도 한다.
  2. min_delta: 개선 기준 최소 변화량 - 개선되고 있다고 판단할 수 있는 최소 변화량으로 변화량이 min_delta보다 작다면 개선이 없다고 판단한다.
  3. patience: 정지까지 기다리는 epochs - 당장 최솟값이 나왔다 할지라도, 이 값이 학습을 하다 보면, 더 떨어질 수도 있다. 그러므로, patience에 정해진 epochs만큼 학습을 더 실시하고, 그동안 개선이 없다면, 학습을 멈춘다.
  4. restore_best_weights: 최선 값이 발생한 때로 모델 가중치 복원 여부 - False로 돼 있다면, 학습의 마지막 단계에서 얻어진 모델 가중치가 사용된다.
  • val_loss는 증감을 반복하므로, epochs를 조금 줘서 기다리도록 하자.
  • 위 코드를 실행하면 다음과 같은 결과가 나온다.
Epoch 1/1000
11/11 [==============================] - 2s 177ms/step - loss: 1.5362 - accuracy: 0.4834 - val_loss: 0.4362 - val_accuracy: 0.8714
Epoch 2/1000
11/11 [==============================] - 1s 55ms/step - loss: 0.3673 - accuracy: 0.8928 - val_loss: 0.2479 - val_accuracy: 0.9252
Epoch 3/1000
11/11 [==============================] - 1s 55ms/step - loss: 0.2225 - accuracy: 0.9336 - val_loss: 0.1759 - val_accuracy: 0.9436
Epoch 4/1000
11/11 [==============================] - 1s 61ms/step - loss: 0.1550 - accuracy: 0.9539 - val_loss: 0.1353 - val_accuracy: 0.9560
Epoch 5/1000
11/11 [==============================] - 1s 55ms/step - loss: 0.1185 - accuracy: 0.9649 - val_loss: 0.1108 - val_accuracy: 0.9640

...

Epoch 19/1000
11/11 [==============================] - 1s 54ms/step - loss: 0.0032 - accuracy: 0.9997 - val_loss: 0.0786 - val_accuracy: 0.9806
Epoch 20/1000
11/11 [==============================] - 1s 51ms/step - loss: 0.0026 - accuracy: 0.9999 - val_loss: 0.0841 - val_accuracy: 0.9794
Epoch 21/1000
11/11 [==============================] - 1s 52ms/step - loss: 0.0024 - accuracy: 0.9998 - val_loss: 0.0831 - val_accuracy: 0.9794
Epoch 22/1000
11/11 [==============================] - 1s 60ms/step - loss: 0.0017 - accuracy: 1.0000 - val_loss: 0.0800 - val_accuracy: 0.9798
Epoch 23/1000
11/11 [==============================] - 1s 51ms/step - loss: 0.0013 - accuracy: 1.0000 - val_loss: 0.0845 - val_accuracy: 0.9792
  • epochs를 1000으로 지정하였으나, val_loss가 최솟값을 찍었기 때문에 epoch 23에서 학습을 정지하였다.
  • 조기 종료를 사용하면, 별도의 과정 없이, 손쉽게 최적의 epochs를 찾아낼 수 있으므로, 오차 역전파법을 찾아내 지금의 딥 러닝을 있게 한 1등 공신 중 한 명인 제프리 힌턴은 조기 종료를 "훌륭한 공짜 점심(beautiful free lunch)"이라고 불렀다고 한다.
  • 이외에도 콜벡에는 학습 중간에 자동 저장을 하는 ModelCheckPoint나 학습률을 스케쥴링하는 LearningRateSchedule 등 유용한 기능이 많다. 관심 있는 사람은 다음 아래 사이트를 참고하기 바란다.
    (keras.io/ko/callbacks/)
 

Callbacks - Keras Documentation

Usage of callbacks 콜백은 학습 과정의 특정 단계에서 적용할 함수의 세트입니다. 학습 과정 중 콜백을 사용해서 모델의 내적 상태와 통계자료를 확인 할 수 있습니다. 콜백의 리스트는 (키워드 인수

keras.io

 

 

[참고 서적]

 

 

  지금까지 조기 종료(Early stopping)와 콜백에 대해 알아보았다. 다음 포스트에서는 최종 과정인 모델 평가와 모델 저장 및 불러오기에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 모델을 학습시키는 것에 대해 알아보았다. 기존 포스팅에서는 학습을 시키고, 이를 가만히 기다리기만 했었는데, 이 학습 과정에서 발생하는 Log를 분석할 수 있다면, 언제 학습을 멈춰야 할지, 과적합이 발생하였는지 등을 정보다 정확히 알 수 있다. 이번 포스팅에서는 학습과정을 확인하는 방법에 대해 알아보도록 하겠다.

 

 

학습과정 확인(History)

0. 이전 코드 정리

# Import module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout, Flatten)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)

train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)
# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))
# 모델 컴파일
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt,
              loss = "sparse_categorical_crossentropy",
              metrics = ["accuracy"])
# 모델 학습하기
history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size = 5000,
                    validation_data=(valid_images, valid_labels))
  • 이전 포스팅에서는 model.fit()을 따로 다른 변수에 담지 않았으나, 이번 포스팅에서는 이들을 history라는 변수에 담고, 이를 분석해보도록 하겠다.

 

 

 

 

1. History

  • model.fit()을 실시하여, 학습을 시작하게 되면, 다음과 같은 Log가 출력되게 된다.
history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size=5000,
                    validation_data=(valid_images, valid_labels))
Epoch 1/100
11/11 [==============================] - 2s 182ms/step - loss: 1.5596 - accuracy: 0.4747 - val_loss: 0.4669 - val_accuracy: 0.8640
Epoch 2/100
11/11 [==============================] - 1s 57ms/step - loss: 0.3706 - accuracy: 0.8902 - val_loss: 0.2654 - val_accuracy: 0.9182
Epoch 3/100
11/11 [==============================] - 1s 55ms/step - loss: 0.2260 - accuracy: 0.9337 - val_loss: 0.1824 - val_accuracy: 0.9416
Epoch 4/100
11/11 [==============================] - 1s 51ms/step - loss: 0.1626 - accuracy: 0.9514 - val_loss: 0.1369 - val_accuracy: 0.9562
Epoch 5/100
11/11 [==============================] - 1s 52ms/step - loss: 0.1198 - accuracy: 0.9642 - val_loss: 0.1176 - val_accuracy: 0.9624

...

Epoch 96/100
11/11 [==============================] - 1s 52ms/step - loss: 2.8271e-05 - accuracy: 1.0000 - val_loss: 0.1096 - val_accuracy: 0.9788
Epoch 97/100
11/11 [==============================] - 1s 57ms/step - loss: 2.7180e-05 - accuracy: 1.0000 - val_loss: 0.1100 - val_accuracy: 0.9790
Epoch 98/100
11/11 [==============================] - 1s 56ms/step - loss: 2.6393e-05 - accuracy: 1.0000 - val_loss: 0.1101 - val_accuracy: 0.9790
Epoch 99/100
11/11 [==============================] - 1s 50ms/step - loss: 2.5213e-05 - accuracy: 1.0000 - val_loss: 0.1103 - val_accuracy: 0.9792
Epoch 100/100
11/11 [==============================] - 1s 51ms/step - loss: 2.4920e-05 - accuracy: 1.0000 - val_loss: 0.1106 - val_accuracy: 0.9792
  • model.fit()는 History 객체를 반환하며, 이 것이 바로 fit()을 실행하면 반환되는 출력 Log이다.
  • 출력되는 값은 model.compile을 할 때, metrics를 어떻게 지정하느냐에 따라 달라진다.
  • 가장 일반적으로 사용되는 accuracy를 넣고, validation data를 넣으면, loss, accuracy, val_loss, val_acc가 출력되게 된다.
  • 이는 매 에포크마다의 모델을 평가하는 점수라고 할 수 있으며, 그 뜻은 다음과 같다.
  1. loss: 훈련 셋 손실 값
  2. accuracy: 훈련 셋 정확도
  3. val_loss: 검증 셋 손실 값
  4. val_acc: 검증 셋 정확도
  • 만약 검증 셋을 추가하지 않는다면 val_loss, val_acc가 출력되지 않으며, metrics에서 accuracy를 지정하지 않는다면, accuracy도 출력되지 않는다.

 

 

 

 

2. History 데이터를 다뤄보자.

# history를 출력시켜보자.
>>> history
<tensorflow.python.keras.callbacks.History at 0x20f001a1d00>
  • history를 출력시켜보면, <tensorflow.python.keras.callbacks.History at ~~~~~>와 같은 이상한 문자가 출력된다. Python에서는 이를 객체라 부르며, 해당 객체에 다양한 함수를 이용하여 학습과정에서 발생한 다양한 정보를 볼 수 있다.
  • 예를 들어, history.epoch를 입력하면, 전체 epoch를 list로 출력하며, history.params 입력 시, 훈련 파라미터가 반환된다.
  • 우리에게 필요한 것은 history.history로, 이 데이터는 한 에포크가 끝날 때마다 훈련 셋과 검증 셋을 평가한 지표들이 모여 있는 것이다.
# history.history는 각 지표들이 담겨있는 dictionary다
>>> type(history.history)
dict

# history.history의 생김새는 다음과 같다.
>>> history.history
{'loss': [1.1046106815338135,
  0.32885095477104187,
  0.2113472819328308,
  0.1513378769159317,
  0.11605359613895416,
  0.09276161342859268,
  
...

  0.9787999987602234,
  0.9789999723434448,
  0.9789999723434448,
  0.979200005531311,
  0.979200005531311]}
  • 데이터를 파악하기 쉽도록 DataFrame의 형태로 바꿔서 보자.
history_DF = pd.DataFrame(history.history)
history_DF

  • 적합한 epoch를 알기 위해 해당 데이터를 시각화해보자.
  • index는 epoch와 동일하다.
# 꺾은선 그래프를 그리자.
# 그래프의 크기와 선의 굵기를 설정해주었다.
history_DF.plot(figsize=(12, 8), linewidth=3)

# 교차선을 그린다.
plt.grid(True)

# 그래프를 꾸며주는 요소들
plt.legend(loc = "upper right", fontsize =15)
plt.title("Learning Curve", fontsize=30, pad = 30)
plt.xlabel('Epoch', fontsize = 20, loc = 'center', labelpad = 20)
plt.ylabel('Variable', fontsize = 20, rotation = 0, loc='center', labelpad = 40)

# 위 테두리 제거
ax=plt.gca()
ax.spines["right"].set_visible(False) # 오른쪽 테두리 제거
ax.spines["top"].set_visible(False) # 위 테두리 제거

  • 시각화를 해서 4개 지표의 변화 추이를 보았다.
  • 위 그래프의 결과를 해석해보면 다음과 같다.
  1. 훈련 셋과 검증 셋의 그래프가 비슷한 형태를 보였다. 이는 과적합(Overfitting)이 되지 않았다는 것을 의미한다.
  2. 훈련 셋, 검증 셋의 Accuracy는 1을 향해 상승하였다. loss는 0을 향해 하강하는 형태를 보였다. 이는 안정적으로 학습을 하고 있는 것을 의미한다.
  3. 훈련 셋, 검증 셋의 Accuracy와 loss는 epoch 20부터 수렴하기 시작했다.
  4. 검증 셋의 loss는 epoch가 20을 넘는 순간부터 소폭 증가하는 형태를 보였다.
  5. 검증 셋의 손실 값이 최저값을 약 epoch 20에서 달성하였으므로, 모델이 완전히 수렴하였다고 할 수 있다.
  • 기존에 epochs을 100으로 설정하였으나, loss와 accuracy가 수렴한 지점을 볼 때, 이는 과하게 큰 값임을 알 수 있다. epochs를 30으로 줄여서 다시 학습을 해보도록 하자.
  • epoch가 불필요하게 큰 경우, 리소스의 낭비가 발생하기도 하지만, 과적합(Overfitting)이 될 위험이 있으므로, 적합한 epoch에서 학습을 해주는 것이 좋다.
  • epoch가 커지면 커질수록 훈련 셋의 성능은 검증 셋의 성능보다 높게 나온다. 검증 셋의 손실(var_loss)의 감소가 아직 이루어지고 있는 상태라면, 모델이 완전히 수렴되지 않은 상태라 할 수 있으므로, 검증 셋 손실의 감소가 더 이상 이루어지지 않을 때 까진 학습을 해야 한다.

 

epochs를 30으로 하여 다시 학습해보자.

history = model.fit(train_images, train_labels,
                    epochs=30,
                    batch_size=5000,
                    validation_data=(valid_images, valid_labels))
history_DF = pd.DataFrame(history.history)
# 꺾은선 그래프를 그리자.
# 그래프의 크기와 선의 굵기를 설정해주었다.
history_DF.plot(figsize=(12, 8), linewidth=3)

# 교차선을 그린다.
plt.grid(True)

plt.legend(loc = "upper right", fontsize =15)

plt.title("Learning Curve", fontsize=30, pad = 30)
plt.xlabel('Epoch', fontsize = 20, loc = 'center', labelpad = 20)
plt.ylabel('Variable', fontsize = 20, rotation = 0, loc='center', labelpad = 40)

# 위 테두리 제거
ax=plt.gca()
ax.spines["right"].set_visible(False) # 오른쪽 테두리 제거
ax.spines["top"].set_visible(False) # 위 테두리 제거

  • 위 그래프를 보면 검증 셋이 연습 셋 보다 더 빠르게 0과 1로 다가가는 것으로 오해할 수 있으나, 훈련 셋의 지표는 epoch 도중에 계산되고, 검증 셋의 지표는 epoch 종료 후 계산되므로, 훈련 곡선을 왼쪽으로 소폭(에포크의 절반만큼) 이동시켜야 한다.
  • 혹시나 해서 하는 말이지만, 모델이 생성된 후에 다시 학습을 하려면, 커널을 재시작해서 모델의 파라미터를 초기화시키고 학습해야 한다. Tensorflow는 이미 학습이 되어 파라미터가 생긴 모델을 재학습 시키면, 기존의 파라미터를 감안하여 학습을 하게 된다.

 

 

[참고 서적]

 

 

 지금까지 모델 학습 과정에서 발생한 history 객체를 이용해서, 학습 셋과 검증 셋의 지표들을 시각화하고, 과적합이 발생하였는지, 적합한 epochs이 얼마인지 탐색해보았다.

 간단하게 검증 셋과 학습 셋의 그래프가 비슷한 경향을 보이면 과적합이 일어나지 않고 있다는 것을 의미하며, 0과 1에 수렴하지 못한다면, 하이퍼 파라미터가 주어진 데이터셋에 맞지 않다는 것을 의미한다. 또한 epochs가 수렴 이후에도 지속되는 경우, 연습 셋에 과적합 될 수 있으므로, 적당한 epochs를 설정하는 것이 중요하다.

 그러나, 꼭 위 방법처럼 epoch를 갱신해서 다시 학습을 해줘야 하는 것은 아니다. 다음 포스트에서는 epoch가 아직 남았더라도, 조건을 만족하면 알아서 학습을 멈추는 조기 종료에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 판다스(Pandas)가 무엇인지 간단한 소개와 판다스의 대표적인 데이터 타입인 데이터 프레임(DataFrame)과 시리즈(Series)를 살펴보았다.

 이번 포스트는 판다스의 상징인 데이터 프레임을 만드는 방법에 대해 알아보도록 하겠다.

 

 

데이터 프레임 만들기

1. 판다스 가져오기

  • 판다스를 사용하기 앞서 먼저 판다스를 설치하고 판다스를 사용하겠다고 선언해보자.
  • Python 모듈 설치는 크게 Anaconda를 사용하여 일반적으로 사용하는 Pandas를 포함한 각종 라이브러리를 한 번에 다운로드하는 방법이 있고, pip를 이용해서 다운로드하는 방법이 있다.
  • 해당 방법을 서술하기엔 그 내용이 꽤 길고 헷갈리기 쉽기 때문에 Pandas를 비롯한 각종 라이브러리를 설치하는 방법이 적힌 이전 포스트로 가는 참조를 걸어놓겠다.
  • 참고 1 - 라이브러리 설치 방법: "Python 필요한 모듈들을 설치해보자"
  • 참고 2 - Anaconda 설치 방법: "Python 파이썬과 아나콘다"
  • 참고 3 - 오프라인 환경에서 라이브러리 설치 방법: "Python 오프라인 환경에서 파이썬 패키지를 설치해보자"
  • 위 방법대로 Pandas를 정상적으로 설치하였다는 전제하에 Pandas에 대해 학습해보도록 하겠다.
# Import module
import pandas as pd
  • Python에서 #은 주석을 만드는 특수 문자로 코드(Syntax) 앞에 입력 시, 해당 행의 명령어들을 주석 처리한다.
  • import pandas as pd는 문자 그대로 "import(수입하다) pandas as(처럼) pd"로, pandas를 가지고 오는데 pd라는 문자로 가지고 온다는 의미이다.
  • Python은 특정 라이브러리 내 함수를 사용하려면 "라이브러리.함수()" 이런 식으로 코드를 짜게 되는데, pandas는 제법 긴 단어이므로 pd라는 간결한 단어로 라이브러리를 의미하겠다는 소리다.
  • 일반적으로 pandas는 pd라 사용하니 pd로 쓰도록 하자.

 

 

 

 

2. 데이터 프레임 함수 설명

  • pandas에서 제공하는 DataFrame API에서 DataFrame이 가지고 있는 파라미터(Parameter)는 다음과 같다.
  • pd.DataFrame(data=None, index=None, columns=None, dtype=None, copy=False)
  • data: list나 numpy에서 제공하는 array, dictionary 등을 받는다. 간단하게 생각해서 행렬이나 벡터를 Data로 사용할 수 있다고 생각하면 된다.
  • index: 이전 포스트에서 말했듯, pandas의 데이터 프레임이나 시리즈의 특징은 index를 갖는다는 것이다. 그리고 사용자가 원하는 index를 부여할 수 있다. 지정하지 않은 경우, 자동으로 range(0, len(data))에 해당하는 index가 생성된다.
  • columns: 열, 즉 변수의 이름이다.
  • dtype: DataFrame 안에 들어갈 데이터의 type을 결정한다. DataFrame의 장점은 변수 별로 다른 dtype이 들어갈 수 있는 것이므로, 특수한 경우가 아닌 이상 지정 안 한다.
  • copy: DataFrame의 특징 중 하나인, Data의 종속성에 대한 부분인데, 이는 나중에 다루도록 하겠다. 크게 신경 쓰지 않아도 된다.

 

 

 

 

3. 행렬(2-d array)을 사용해서 데이터 프레임 만들기

  • 2-d array는 2차원 배열을 의미하며, 우리에게 친숙한 행렬이 2-d array다.
  • 머신러닝 라이브러리인 sklearn에서 제공하는 붓꽃 데이터를 이용해서 행렬을 보도록 하자.
import numpy as np
from sklearn.datasets import load_iris
  • numpy는 선형 대수학에 특화된 라이브러리로 다른 포스팅에서 자세히 다룰 테니 일단 넘어가자(당장 쓰지 않더라도 Pandas를 쓸 때는 꼭 numpy도 import 해놓는 습관을 들여놓자. 나중에 아주 많이 쓰게 될 것이다.)
  • from sklearn.datasets import load_iris는 sklearn의 dataset이라는 모듈에서 load_iris라는 함수만 가져오겠다는 뜻이다. 특정 라이브러리에서 한, 두 개의 함수만 필요한 경우에 사용하는 방법이다.
>>> iris = load_iris()
>>> iris
{'data': array([[5.1, 3.5, 1.4, 0.2],
        [4.9, 3. , 1.4, 0.2],
        [4.7, 3.2, 1.3, 0.2],
        [4.6, 3.1, 1.5, 0.2],
        [5. , 3.6, 1.4, 0.2],
        [5.4, 3.9, 1.7, 0.4],
        
...

lustering system finds 3 classes in the data.\n   - Many, many more ...',
 'feature_names': ['sepal length (cm)',
  'sepal width (cm)',
  'petal length (cm)',
  'petal width (cm)'],
 'filename': 'C:\\Users\\gooop\\anaconda3\\lib\\site-packages\\sklearn\\datasets\\data\\iris.csv'}
  • 해당 블로그에서 >>> 는 코드 실행을 의미하며, 앞에 >>>가 없는 것은 출력된 결과를 의미한다. 한 셀 안에 >>>가 없는 경우, 출력된 결과가 없기 때문에 >>>를 쓰지 않은 것이다.
  • load_iris()를 실행 시, dictionary가 출력된다. dictionary는 여러 종류의 데이터를 key:value로 담을 수 있는 사전이다.
  • dictionary를 key로 조회 시, 원하는 value만 볼 수 있다.
# iris data에 있는 key를 보자
>>> iris.keys()
dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename'])
  • iris 데이터가 가지고 있는 key를 보면, data, target, frame 등 여러 key가 존재하는 것을 알 수 있다.
  • 여기서 "data"는 iris data를 의미한다.
  • "feature_names"는 변수의 이름이다.
>>> iris["data"]
array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
       [4.7, 3.2, 1.3, 0.2],
       [4.6, 3.1, 1.5, 0.2],
       [5. , 3.6, 1.4, 0.2],
       
...

       [6.7, 3. , 5.2, 2.3],
       [6.3, 2.5, 5. , 1.9],
       [6.5, 3. , 5.2, 2. ],
       [6.2, 3.4, 5.4, 2.3],
       [5.9, 3. , 5.1, 1.8]])
  • 데이터가 매우 크므로 중간을 생략하였다. 위 데이터 같이 행과 열로 2개의 차원(dimension)이 존재하는 데이터를 2차원 배열 2-d array라고 한다.
>>> iris["feature_names"]
['sepal length (cm)',
 'sepal width (cm)',
 'petal length (cm)',
 'petal width (cm)']
  • 칼럼 이름은 list로 출력되었다.
  • 위 두 데이터를 사용해서 데이터 프레임을 생성해보자.
>>> iris_DF = pd.DataFrame(data=iris["data"], columns=iris["feature_names"])

  • DataFrame은 주피터 노트북 기준 한 번에 60개의 행이 조회되며, 아래에 있는 150 rows x 4 columns는 150개의 행과 4개의 열을 가진 데이터라는 것을 의미한다.
>>> iris_DF.values
array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
       [4.7, 3.2, 1.3, 0.2],
       [4.6, 3.1, 1.5, 0.2],
       [5. , 3.6, 1.4, 0.2],
       
...

       [6.7, 3. , 5.2, 2.3],
       [6.3, 2.5, 5. , 1.9],
       [6.5, 3. , 5.2, 2. ],
       [6.2, 3.4, 5.4, 2.3],
       [5.9, 3. , 5.1, 1.8]])
  • DataFrame.values 함수를 사용하면, DataFrame을 2-d array로 돌릴 수 있다. 꽤 중요한 기능이니 꼭 숙지하도록 하자.

 

 

 

 

4. list를 사용해서 데이터 프레임 만들기

  • 이번엔 list를 이용해서 데이터 프레임을 만들어보자.
  • list는 Python에서 대표적인 Data를 담는 그릇이다.
>>> name_list = ["박명수", "유재석", "노홍철", "길", "정준하", "정형돈", "하하"]
>>> math_list = [65, 95, 70, 80, 100, 85, 60]
>>> english_list = [75, 80, 85, 90, 65, 75, 100]
>>> exam = pd.DataFrame({"name":name_list, "math":math_list, "english":english_list})
>>> exam

  • 데이터 프레임은 위 결과처럼 각 변수(열) 별로 다른 dtype이 들어갈 수 있다.

 

 

 

 

5. Dictionary를 사용해서 데이터 프레임 만들기

  • Dictionary는 모든 형태의 데이터를 Key:value의 형태로 담을 수 있는 데이터 타입이다.
  • Dictionary의 형태는 조금만 편집해서 바로 Json으로 사용할 수 있을 정도로 Json과 굉장히 유사하다.
  • Dictionary의 value들이 모두 list 또는 array이고, 그 길이가 동일하다면, DataFrame으로 만들 수 있다.
    (길이가 다르다면, 오류가 뜬다)
>>> Raw_dict = {
>>>     "name":["박명수", "유재석", "노홍철", "길", "정준하", "정형돈", "하하"],
>>>             "math":[65, 95, 70, 80, 100, 85, 60],
>>>             "english":[75, 80, 85, 90, 65, 75, 100],
>>>             "class":[1, 2, 1, 2, 2, 2, 1]
>>> }

>>> Raw_dict

{'name': ['박명수', '유재석', '노홍철', '길', '정준하', '정형돈', '하하'],
 'math': [65, 95, 70, 80, 100, 85, 60],
 'english': [75, 80, 85, 90, 65, 75, 100],
 'class': [1, 2, 1, 2, 2, 2, 1]}
DF = pd.DataFrame(Raw_dict)
DF

  • Dictionary는 key와 value로 이루어져 있으므로, 별도의 컬럼 이름을 지정해주지 않아도 DataFrame으로 쉽게 전환이 가능하다.
  • 반대로 DataFrame을 Dictionary로 변환하는 것 역시 굉장히 간단하다.
>>> DF.to_dict()
{'name': {0: '박명수', 1: '유재석', 2: '노홍철', 3: '길', 4: '정준하', 5: '정형돈', 6: '하하'},
 'math': {0: 65, 1: 95, 2: 70, 3: 80, 4: 100, 5: 85, 6: 60},
 'english': {0: 75, 1: 80, 2: 85, 3: 90, 4: 65, 5: 75, 6: 100},
 'class': {0: 1, 1: 2, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1}}
  • DataFrame.to_dict(): 해당 함수를 사용하면 DataFrame을 dictionary로 바로 전환할 수 있다.

 

 

 

 지금까지 DataFrame을 만드는 방법에 대해 알아보았다. 다음 포스트에서는 데이터 프레임의 부위별 이름에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

 지난 포스트에서 MNIST 데이터 셋을 사용하여, 검증 셋(Validation set)을 생성해보았다. 이번 포스트에서는 Keras의 중심인 모델을 만들어보도록 하겠다.

 

 

모델 생성

0. 이전 코드 정리

# Import Module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout)
from tensorflow.keras.datasets.mnist import load_data

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
################################### Function ###################################

# Data 시각화
def show_images(dataset, label, nrow, ncol):

    # 캔버스 설정
    fig, axes = plt.subplots(nrows=nrow, ncols=ncol, figsize=(2*ncol,2*nrow))
    ax = axes.ravel()

    xlabels = label[0:nrow*ncol]

    for i in range(nrow*ncol):

        image = dataset[i]
        ax[i].imshow(image, cmap='gray')
        ax[i].set_xticks([])
        ax[i].set_yticks([])
        ax[i].set_xlabel(xlabels[i])

    # 빈 칸 없이 꽉 채우기
    plt.tight_layout()
    plt.show()
    
################################################################################
# Dataset 준비
(train_images, train_labels), (test_images, test_labels)= load_data()

# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]

# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)
train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)

 

 

 

 

1. 모델 생성

  • 이전에 만들었던 모델들에 들어간 데이터들은 1차원 배열이 n개의 row로 구성된 형태였다.
>>> train_images.shape
(60000, 28, 28)
  • 그러나, 이번 데이터셋은 28*28인 행렬이 60000개 row로 쌓인 형태다.
  • 이때 평활(Flatten)이라는 개념이 추가로 등장한다.
  • 모델을 먼저 만들어보자.
# 모델 생성
model = keras.models.Sequential()
model.add(keras.layers.Flatten(input_shape=[28, 28], name="Flatten"))
model.add(Dense(300, activation="relu", name="Hidden1"))
model.add(Dense(200, activation="relu", name="Hidden2"))
model.add(Dense(100, activation="relu", name="Hidden3"))
model.add(Dense(10, activation="softmax", name="Output"))
  • 각 Layer를 구분하기 쉽도록 name이라는 parameter를 추가해주었다.
  • 이전과 달리 Flatten이라는 Layer가 새로 추가되었다.
  • Flatten Layer는 입력된 2차원 배열을 1차원 배열로 만들어주는 전처리용 Layer다.
  • 한 Row가 X = (1, 28, 28)인 데이터셋을 X.reshape(-1, 28*28)으로 형 변환해준다고 생각하면 된다. 
>>> X = train_images[0]
>>> X.reshape(-1, 28*28).shape
(1, 784)
  • 평활 Layer 통과는 각 Row에 적용되므로, 전체 데이터 셋의 형태가 (60000, 28, 28)에서 (60000, 784)로 바뀐다고 생각해도 좋다.

 

 

 

 

2. 은닉층 설정하기

  • 지금까지의 포스팅에선 은닉층의 수와 은닉층에 있는 노드의 수를 정할 때, 어째서 이렇게 구성하였는지 설명하지 않았다. 이번에는 은닉층을 설정할 때, 무엇을 인지한 상태로 은닉층을 만들어야 하는지에 대해 학습해보도록 하겠다.

 

2.1. 은닉층의 개수

  • 은닉층의 개수가 1개인 경우를 얕은 신경망이라고 하며, 2개 이상인 경우를 심층 신경망이라고 한다.
  • 이론적으로 입력층, 은닉층, 출력층으로 3개의 층만 있는 경우에도 뉴런수만 충분하다면 아주 복잡한 함수도 모델링할 수 있다.
    George Cybenko(1989), Approximation by superpositions of a sigmoidal function - 조지 시벤코의 시벤코 정리: 뉴런 수만 무한하다면 은닉층 하나로 어떤 함수도 근사할 수 있다.
  • 그러나, 심층 신경망은 얕은 신경망보다 적은 노드를 사용하여, 과적합 문제에서 비교적 자유롭고, 얕은 신경망보다 적은 epochs로 최적해에 수렴하게 된다.
  • 이는 은닉층의 수가 늘어날수록, 저수준에서 고수준으로 체계적으로 구조를 모델링할 수 있기 때문이다.
  • 예를 들어, 3개의 은닉층을 사용해 사람을 구분하는 모델을 만들고자 한다면, Hidden1에서는 가장 저수준의 구조인 사람과 배경을 구분하는 일을, Hidden2에서는 사람의 머리, 몸, 키 등을 구분하는 일을, Hidden3에서는 가장 고수준인 사람의 얼굴과 머리스타일을 구분하도록 모델링하게 된다.
  • 이러한 계층 구조는 심층 신경망이 새로운 데이터에 대해 일반화하는 능력도 향상하게 해 준다.
  • 계층 구조는 기존 모델에 추가 기능이 생긴 업그레이드된 모델을 만들 때, 기존 모델의 파라미터를 하위 은닉층에서 재사용해 훈련을 진행할 수 있다. 이를 전이 학습(Transfer Learning)이라 한다.

 

2.2. 은닉층의 뉴런 개수

  • 데이터 셋에 따라 다르긴 하지만 모델에서 은닉층의 뉴런 수를 정할 땐, 다음과 같은 경향이 있다.
  • 일반적으로 첫 번째 은닉층을 제일 크게 하는 것이 도움된다.
  • 만약 한 층의 뉴런 수가 지나치게 적다면, 전달되는 정보 중 일부가 사라져 버릴 수 있다. 
  • 깔때기 형태: 은닉층의 구성은 일반적으로 각 층의 뉴런의 수를 점차 줄여가며, 깔때기처럼 구성한다. 이는 저수준의 많은 특성이 고수준의 적은 특성으로 합쳐질 수 있기 때문이다.
  • 직사각형 형태: 모든 은닉층의 뉴런 수를 같게 함. 이렇게 모델링을 하는 경우, 깔때기 형태와 동일하거나 더 나은 성능을 내는 경우도 있다고 한다.
  • 일반적으로 은닉층의 뉴런 수를 늘리는 것보다 은닉층의 수를 늘리는 쪽이 유리하다.

 

2.3. 스트레치 팬츠(Stretch pants) 방식

  • 실제 필요한 것보다 은닉층의 수와 뉴런의 수를 크게 하고, 과대 적합이 발생하지 않도록, 조기 종료를 하거나 규제 기법을 사용하는 방법
  • 말 그대로 "자기에게 맞는 바지를 찾는 것이 아닌, 큰 스트레치 팬츠를 사고 나중에 나에 맞게 줄이는 기법"이라 할 수 있다.
  • 해당 방식 사용 시, 모델에서 문제를 일으키는 병목층을 피할 수 있다.

 

 

 

 

3. 생성된 모델 정보

  • 이전 포스트까지는 단순하게 모델을 생성하고 바로 학습으로 뛰어들었지만, 이번엔 모델의 요약 정보를 보고 진행해보자.
>>> model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
Flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
Hidden1 (Dense)              (None, 300)               235500    
_________________________________________________________________
Hidden2 (Dense)              (None, 200)               60200     
_________________________________________________________________
Hidden3 (Dense)              (None, 100)               20100     
_________________________________________________________________
Output (Dense)               (None, 10)                1010      
=================================================================
Total params: 316,810
Trainable params: 316,810
Non-trainable params: 0
_________________________________________________________________
  • model.summary(): 모델의 요약 정보를 보여준다.
  • Layer (type)을 보면, 앞서 설정한 Layer의 이름과 어떤 Layer인지가 나온다.
  • Output shape에서 None은 아직 모르는 값으로, Input 될 데이터의 양을 의미한다.
  • Input Data는 2-d 배열인 행렬이지만, 1-d 배열로 학습이 진행되므로 shape은 1-d 배열인 것을 알 수 있다.
  • Param은 각 Layer별 파라미터의 수로, Dense Layer는 Input layer와 Output layer의 모든 노드를 연결하는 완전 연결 계층이기 때문에 연결된 선(Param)이 많다.
  • Hidden1은 이전 층의 Node가 784개이고, 자신의 Node가 300개이므로, 가중치(Weight)의 엣지가 784*300=235,200개 생성된다. 여기에 편향(Bias)의 엣지가 자신의 Node 수만큼 존재하므로, +300을 하여, 235,500개의 Param이 존재하게 된다.
  • 위 모델 같이 파라미터의 수가 많은 경우, 과대 적합(Overfitting)의 위험이 올라갈 수 있으며, 특히 훈련 데이터의 양이 많지 않은 경우 이 위험이 증가하게 된다.

 

 

[참고 서적]

 

 

 지금까지 모델을 생성하고, 그 정보를 보는 방법에 대해 학습해보았다. 다음 포스팅에서는 모델 컴파일과 학습을 진행해보도록 하겠다.

 

728x90
반응형
728x90
반응형

 지금까지의 포스팅에서는 검증 셋을 따로 생성하지 않았으나, 이번엔 검증 셋을 데이터에서 추출해보도록 하겠다. 검증 셋 추출 시, 주의할 점은 검증 셋에서 편향이 발생해서는 안된다는 것이다.

 

 

검증 셋(Validation set)

1. 검증 셋이란?

 검증 셋은 앞선 "Tensorflow-1.0. 기초(1)-데이터 셋 만들기"에서 한 번 언급하긴 하였으나, 이번엔 좀 더 자세히 설명해보도록 하겠다.

  • 검증 셋은 학습 도중에 학습된 내용을 평가하는 "가짜 최종 시험"이다.
  • 예를 들어, 수능을 준비하는 고3 학생에게 모의고사 문제가 5개가 있다면, 4개는 공부를 할 때 사용하고, 나머지 1개는 수능 전에 자신이 얼마나 잘 공부를 했는지 평가하는 용도로 사용하는 것이라 생각하면 된다.
  • 최종 목표인 수능(Test set)을 보기 전에 자신의 실력을 평가하는 용도(학습된 파라미터 평가)로 사용되기 때문에 검증 셋을 얼마나 잘 추출하느냐는 꽤 중요한 문제다.
  • 검증 셋은 파라미터 갱신에 영향을 주는 것이 아니라, 학습 과정에서 생성된 여러 모델 중 어느 모델이 가장 좋은지를 평가하는 용도로 사용된다.
  • 검증 셋(Validation set)과 학습 셋(Train set)이 중복되면, 편향이 발생하여, 제대로 된 평가가 이루어지지 않을 수 있다. 이렇게 검증 셋과 학습 셋이 중복된 현상을 Leakage라고 한다.

 

 

 

2. 검증 셋의 효과

  • 검증 셋을 사용한 학습 데이터의 평가는, 학습 과정에서 생긴 여러 모델들이 만들어낸 수많은 파라미터 중 최적의 파라미터를 선택하므로, 파라미터를 튜닝하는 효과가 있다고 할 수 있다.
  • 만약 학습 셋(Train set)으로만 학습하고, 시험 셋(Test set)으로만 모델을 검증한다면, 모델은 시험 셋(Test set)에 과적합(Overfitting)된 모델이 될 수 있다.
  • 시험 셋(Test set)은 모델의 성능을 평가하기 위해 사용되는 것이긴 하지만, 모델 성능 향상의 기준이 시험 셋이 돼버린다면, 시험 셋의 "모델 성능 평가"라는 목적이 "모델 성능을 맞추는 기준"으로 변질되게 된다. 이 경우, 검증 셋을 사용한다면, 이 문제를 해결할 수 있다.

 

 

 

3. 검증 셋 추출 방법

  • 검증 셋 추출 방법에서 핵심은 "어떻게 검증 셋의 편향을 피하는가"이다.
  • 예를 들어, 총 10개의 시험 단원이 있고, 여기서 랜덤 하게 문제를 뽑아, 수능 시험을 보러 가기 전 최종 평가를 하려고 한다. 그런데, 우연히 1~6단원 문제가 90% 가까이 나왔고, 7~10단원 문제가 10%밖에 나오지 않았다고 가정해보자. 이 시험 문제를 최종 기준으로 사용하는 것은 꼭 피해야 할 문제다.
  • 즉, 최대한 검증 셋의 편향을 없애는 것이 다양한 검증 셋 추출 방법들이 생기게 된 이유라고 할 수 있다.

3.1. Hold-out

  • 단순하게 일정 비율의 데이터 셋을 분리해내는 방법이다.
  • 데이터의 양이 적을수록, 전체 데이터를 대표하지 못할 가능성이 높으며, 편향된 결과를 얻을 가능성이 있다.

3.2. Random subsampling

  • Hold-out을 완전 무작위 표본 추출로 반복 시행하고, 정확도의 평균으로 성능을 평가한다.

3.3. K-fold cross validation

  • 데이터 셋을 중복되지 않는 K개의 집단으로 나눈다.
  • K개의 집단에서 1개의 집단을 검증 셋으로 사용하며, K번 검증 셋을 집단이 중복되지 않게 바꿔가며 정확도를 계산하고, 그 평균으로 성능을 평가한다.
  • 데이터 셋이 많으면 많을수록 지나치게 시간을 많이 소모하므로, 빅데이터를 사용하는 현 트렌드에는 맞지 않다.
  • 물론, 데이터의 양이 매우 적다면, 가지고 있는 모든 데이터를 학습과 평가에 사용할 수 있다는 장점이 있다.

3.4. Leave p-out cross validation

  • 중복되지 않은 전체 데이터 n개에서 p개의 샘플을 검증 셋으로 사용하여 정확도를 계산하고, 그 결과의 평균으로 성능을 평가한다.
  • 전체 경우의 수가 ${n}C{p}$개이기 때문에 k-fold cross validation보다 소모되는 시간이 K-fold cross validation보다 많다.
  • 데이터 셋의 양이 매우 적은 경우에나 사용할만하다.
  • 여기서 p=1로 하면 Leave one-out cross validation(LOOCV)라 하며, 소모 시간과 성능 모두 Leave p-out cross validation보다 우수하다고 한다.

3.5. Stratified Sampling

  • 전체 데이터 셋을 구성하는 클래스별로 데이터를 일부 추출한다.
  • 전체 데이터셋에서 클래스의 비율이 불균형할수록 편향을 줄여준다는 장점이 있다.
  • 완전 무작위 표본 추출 시, 우연히 특정 클래스에 표본이 편중되는 현상을 피할 수 있다.
  • Stratified Sampling 역시 k-fold 방식처럼 k개의 집단을 생성하여 그 평균을 낼 수도 있다(Stratified k-fold cross validation).

3.6. Bootstrap

  • 전체 데이터 셋에서 중복을 허용한 샘플링을 반복 실시해, 모집단으로부터 새로운 데이터 셋을 만들어 냄
  • 크기가 n인 데이터셋에 대하여 부트스트랩을 b번 한다고 가정할 때, 공식은 다음과 같다.

$$ACC_{boot} = \frac{1}{b}\sum_{j=1}^{b}\frac{1}{n}\sum_{i=1}^{n}(1-L(\hat{y_i},y_i))$$

  1. 전체 데이터 셋에서 하나의 샘플을 뽑고, $j$번째 부트스트랩에 할당
  2. 1번 과정을 부트스트랩 샘플 크기가 원본 데이터 셋 크기인 n이 될 때까지 반복한다.
    (중복을 허용하여 완전 무작위 추출하므로 부트스트랩 샘플에 한번 이상 포함되었거나, 아예 없을 수도 있다.)
  3. b개의 부트스트랩 샘플 하나하나를 모델에 학습시키고 훈련에 사용된 데이터를 이용하여 성능을 평가한다(재치환 정확도).
  4. b개의 부트스트랩 샘플의 정확도의 평균으로 모델 정확도를 구한다.
  • 부트스트랩은 데이터의 분포를 알 수 없고, 추가적인 데이터를 구할 수 없는 경우 추정량의 통계적 속성을 결정하기 위해 사용한다.
  • 보다 자세한 설명을 보고 싶은 사람은 다음 출처를 확인하기 바란다.
    출처: 텐서 플로우 블로그 (Tensor ≈ Blog)
 

머신 러닝의 모델 평가와 모델 선택, 알고리즘 선택 – 2장. 부트스트래핑과 불확실성

이 글은 파이썬 머신 러닝의 저자 세바스찬 라쉬카(Setabstian Raschka)가 쓴 ‘Model evaluation, model selection, and algorithm selection in machine learning Part II – Bootstrapping and uncertainties‘를 원저자의 동의하에 번

tensorflow.blog

 

 

 

 

4. 검증 셋 추출

  • 검증 셋 추출 방법은 데이터 셋의 크기가 크면 클수록 교차 검증 방식을 사용하지 않는 것이 좋다.
  • k-fold 교차 검증이나 LOOCV은 데이터의 양이 늘어나면 늘어날수록 연산량이 지나치게 늘어나게 되는데, 현대 같이 빅데이터를 사용하여 딥러닝을 실시하는 경우에는 개인적으로 추천하지 않는다.
  • 검증 셋을 추출하기 전에 Label의 빈도를 보도록 하자.
>>> pd.Series(train_labels).value_counts()
1    6742
7    6265
3    6131
2    5958
9    5949
0    5923
6    5918
8    5851
4    5842
5    5421
dtype: int64
# 히스토그램을 보자
plt.hist(train_labels)
plt.show()

  • 데이터셋의 분포는 특정 클래스에 치우치지 않은 평탄한 상태인 것을 알 수 있다.
  • 그러므로, Hold-Out을 사용하되 완전 무작위 표본 추출로 검증 셋을 생성하도록 하겠다.
# 무작위로 샘플 추출
np.random.seed(1234)
index_list = np.arange(0, len(train_labels))
valid_index = np.random.choice(index_list, size = 5000, replace = False)

# 검증셋 추출
valid_images = train_images[valid_index]
valid_labels = train_labels[valid_index]

# 학습셋에서 검증셋 제외
train_index = set(index_list) - set(valid_index)
train_images = train_images[list(train_index)]
train_labels = train_labels[list(train_index)]
  • 완전 무작위 표본 추출을 위해 index를 무작위로 추출하였다.
  • 차집합을 이용해서 간단하게 index의 차를 구하였다.
>>> pd.Series(valid_labels).value_counts()
1    616
9    518
3    514
7    497
4    491
8    488
6    482
2    481
0    480
5    433
dtype: int64
  • valid set의 빈도 표를 통해 데이터가 어느 정도 이쁘게 뽑힌 것을 볼 수 있다.
  • 혹시 모르니 표준편차도 뽑아보도록 하자.
>>> np.std(train_labels)
2.887480385250541

>>> np.std(valid_labels)
2.9085488065356584
  • 검증 셋과 학습 셋의 표준편차가 거의 유사하게 나온 것으로 볼 때, 검증 셋이 대표성을 갖고 있다고 할 수 있다.

 

 

 

 

5. 스케일 조정

  • 앞서 이야기 하긴 했으나, 다시 한번 이야기하자면, 데이터의 범위 차이를 줄여 최적해에 보다 쉽게 수렴하게 하는 방법이다.
  • 스케일 조정에서 사용되는 값의 기준은 Train set이 되어야 한다.
  • 보다 자세한 내용은 "Tensorflow-1.3. 기초(4)-특성 스케일 조정"을 참고하기 바란다.
  • min-max scaling을 이용해 표준화시키도록 하겠다.
# min-max scaling
min_key = np.min(train_images)
max_key = np.max(train_images)

train_images = (train_images - min_key)/(max_key - min_key)
valid_images = (valid_images - min_key)/(max_key - min_key)
test_images = (test_images - min_key)/(max_key - min_key)

 

 

 

 지금까지 MNIST 데이터셋에서 검증 셋을 추출해보았다. 다음 포스트에서는 모델을 만들어보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 기본적인 딥러닝을 통해 타이타닉 데이터의 생존자 분류 모델을 생성해보았다. 이번에는 이전에 했던 이진 분류가 아닌, 3가지 이상의 군으로 나누는 다중 분류 모델을 만들어보도록 하겠다.

 

 

다중 분류 모델

  • 다중 분류 모델과 이진 분류 모델의 가장 큰 차이는 출력층에서 사용하는 활성화 함수와 손실 함수가 다르다는 것이다.
  • 통계학에 익숙한 사람이라면, 이진 분류를 할 때는 일반적인 로지스틱 회귀 모형을 사용하지만, 다중 분류 시, 다중 로지스틱 회귀 모형을 사용한다는 것을 알 수 있다.
  • 손실 함수는 큰 차이가 없으니 넘어가더라도, 활성화 함수는 Sigmoid에서 Softmax로 바뀌게 되는데, 이 Softmax 함수는 Sigmoid 함수에서 발전한 함수다.
  • Softmax 함수에 대한 추가 설명은 다음 포스팅을 참고하기 바란다(참고).
  • 이번 포스팅에서는 타이타닉 데이터의 Name 변수에 있는 Mr, Mrs, Ms를 추출해 Class라는 변수를 생성하고, 이를 Label로 사용하여 분류기를 만들어보도록 하겠다.

 

 

 

 

1. Class 추출.

  • 이전에 만들었던 함수들을 사용해서 쉽게 데이터셋을 만들어보자.
  • Name 변수의 내용은 다음과 같다.
# Name 데이터의 생김새
>>> Rawdata.Name.head(20)
0                               Braund, Mr. Owen Harris
1     Cumings, Mrs. John Bradley (Florence Briggs Th...
2                                Heikkinen, Miss. Laina
3          Futrelle, Mrs. Jacques Heath (Lily May Peel)
4                              Allen, Mr. William Henry
5                                      Moran, Mr. James
6                               McCarthy, Mr. Timothy J
7                        Palsson, Master. Gosta Leonard
8     Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)
9                   Nasser, Mrs. Nicholas (Adele Achem)
10                      Sandstrom, Miss. Marguerite Rut
11                             Bonnell, Miss. Elizabeth
12                       Saundercock, Mr. William Henry
13                          Andersson, Mr. Anders Johan
14                 Vestrom, Miss. Hulda Amanda Adolfina
15                     Hewlett, Mrs. (Mary D Kingcome) 
16                                 Rice, Master. Eugene
17                         Williams, Mr. Charles Eugene
18    Vander Planke, Mrs. Julius (Emelia Maria Vande...
19                              Masselmani, Mrs. Fatima
Name: Name, dtype: object
  • 데이터를 보면, 처음 등장하는 ", "와 ". " 사이에 해당 인물이 속하는 Class가 나온다.
  • 이를 뽑아내 보자.
# Inport Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import (Dense, Dropout, BatchNormalization)
from tensorflow import keras
from copy import copy



################################## Function ##################################
def import_Data(file_path):

    result = dict()
    for file in os.listdir(file_path):

        file_name = file[:-4]
        result[file_name] = pd.read_csv(file_path + "/" + file)

    return result

def make_Rawdata(dict_data):

    dict_key = list(dict_data.keys())
    test_Dataset = pd.merge(dict_data["gender_submission"], dict_data["test"], how='outer', on="PassengerId")
    Rawdata = pd.concat([dict_data["train"], test_Dataset])
    Rawdata.reset_index(drop=True, inplace=True)
    
    return Rawdata
##############################################################################




# Rawdata Import
file_path = "./Dataset"
Rawdata_dict = import_Data(file_path)

# Rawdata 생성
Rawdata = make_Rawdata(Rawdata_dict)

# Name에서 Class 추출
Class1 = Rawdata["Name"].str.partition(", ")[2]
Rawdata["Class"] = Class1.str.partition(". ")[0]
  • 판다스의 str 모듈에 있는 partition 함수를 사용하여, 원하는 문자를 가지고 왔다.
  • Series.str.partition(sep): 함수는 맨 처음 등장하는 sep의 단어로 해당 열의 데이터를 분리하여, 3개의 열을 생성한다.
  • Class에 어떤 데이터들이 존재하는지 빈도 표를 출력하여 확인해보자.
# Class 데이터 빈도분석 결과
>>> Rawdata.Class.value_counts()

Mr              757
Miss            260
Mrs             197
Master           61
Rev               8
Dr                8
Col               4
Ms                2
Major             2
Mlle              2
Jonkheer          1
Capt              1
Don               1
Sir               1
the Countess      1
Mme               1
Dona              1
Lady              1
Name: Class, dtype: int64
  • 해당 데이터는 Mr, Miss, Mrs뿐만 아니라 95개 데이터가 15개의 분류에 속하는 것을 볼 수 있다.
  • 확실하게 Miss에 속하는 Ms, Mlle, Lady를 하나로, Mrs에 속하는 것이 확실한 the Countess, Dona, Jonkheer, Mme를 하나로 묶고, Mr를 제외한 나머지는 버리도록 하자.
# Class를 숫자로 치환하자.
Class_a = Rawdata["Class"].to_numpy()

Class_b = np.where(Class_a == "Mr", 0,
                   np.where(np.isin(Class_a, ['Miss','Mlle','Ms','Lady']), 1,
                            np.where(np.isin(Class_a, ["Mrs", 'the Countess', 'Dona', 'Mme']), 2, 9)))

Rawdata["Class"] = Class_b
# 변환된 결과 확인
>>> Rawdata["Class"].value_counts()
0    757
1    265
2    200
9     87
Name: Class, dtype: int64
  • 쓸모없는 변수들을 제거하겠다.
  • Index와 PassengerId는 거의 일치하므로 제거하자.
  • Name, Ticket, Cabin은 사용하지 않으므로 제거하자.
  • Class에서 9로 지정된 경우는 결측 값이므로 제거하자.

 

 

 

 

2. 전체 코드

# Inport Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import (Dense, Dropout, BatchNormalization)
from tensorflow import keras
from copy import copy




###################################### Function ######################################
def import_Data(file_path):

    result = dict()
    for file in os.listdir(file_path):

        file_name = file[:-4]
        result[file_name] = pd.read_csv(file_path + "/" + file)

    return result


def make_Rawdata(dict_data):

    dict_key = list(dict_data.keys())
    test_Dataset = pd.merge(dict_data["gender_submission"], dict_data["test"], how='outer', on="PassengerId")
    Rawdata = pd.concat([dict_data["train"], test_Dataset])
    Rawdata.reset_index(drop=True, inplace=True)
    
    return Rawdata


# 원-핫 벡터
def one_hot_Encoding(data, column):

    # 한 변수 내 빈도
    freq = data[column].value_counts()

    # 빈도가 큰 순서로 용어 사전 생성
    vocabulary = freq.sort_values(ascending = False).index

    # DataFrame에 용어 사전 크기의 column 생성
    for word in vocabulary:

        new_column = column + "_" + str(word)
        data[new_column] = 0

    # 생성된 column에 해당하는 row에 1을 넣음
    for word in vocabulary:

        target_index = data[data[column] == word].index
        new_column = column + "_" + str(word)
        data.loc[target_index, new_column] = 1

    # 기존 컬럼 제거
    del(data[column])

    
# 스케일 조정
def scale_adjust(X_test, X_train, C_number, key="min_max"):
    
    if key == "min_max":
        
        min_key = np.min(X_train[:,C_number])
        max_key = np.max(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - min_key)/(max_key - min_key)
        X_test[:,C_number] = (X_test[:,C_number] - min_key)/(max_key - min_key)
        
    elif key =="norm":
        
        mean_key = np.mean(X_train[:,C_number])
        std_key = np.std(X_train[:,C_number])
        
        X_train[:,C_number] = (X_train[:,C_number] - mean_key)/std_key
        X_test[:,C_number] = (X_test[:,C_number] - mean_key)/std_key
        
    return X_test, X_train
######################################################################################
######################################## Model #######################################
# 모델 생성
model = keras.Sequential()
model.add(BatchNormalization())
model.add(Dense(128, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(64, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(32, activation = 'relu'))
model.add(Dropout(0.10))
model.add(Dense(16, activation = 'relu'))
# 마지막 Dropout은 좀 크게 주자
model.add(Dropout(0.50))
model.add(Dense(3, activation = 'softmax'))

# 모델 Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt,
              loss = "sparse_categorical_crossentropy",
              metrics=["accuracy"])
######################################################################################
  • 이진 분류와 다중 분류는 크게 3가지 부분에서 다르다.
  1. 출력층 활성화 함수를 sigmoid에서 softmax 함수 사용
    (Softmax의 출력층은 Label의 분류 수와 동일하므로, Node size를 거기에 맞게 맞춰줘야 한다.)
  2. 손실 함수(loss)를 binary_crossentropy에서 sparse_categorical_crossentropy를 사용
  3. metrics를 binary_accuracy에서 accuracy를 사용해서 비교
>>> model.fit(X_train, y_train, epochs = 200)

Epoch 1/200
23/23 [==============================] - 1s 2ms/step - loss: 0.8308 - accuracy: 0.5861
Epoch 2/200
23/23 [==============================] - 0s 2ms/step - loss: 0.4274 - accuracy: 0.7672
Epoch 3/200
23/23 [==============================] - 0s 2ms/step - loss: 0.3213 - accuracy: 0.8454
Epoch 4/200
23/23 [==============================] - 0s 2ms/step - loss: 0.2921 - accuracy: 0.8486
Epoch 5/200
23/23 [==============================] - 0s 2ms/step - loss: 0.2337 - accuracy: 0.8824

...

Epoch 196/200
23/23 [==============================] - 0s 1ms/step - loss: 0.1437 - accuracy: 0.9250
Epoch 197/200
23/23 [==============================] - 0s 1ms/step - loss: 0.1127 - accuracy: 0.9434
Epoch 198/200
23/23 [==============================] - 0s 2ms/step - loss: 0.0937 - accuracy: 0.9581
Epoch 199/200
23/23 [==============================] - 0s 2ms/step - loss: 0.1252 - accuracy: 0.9274
Epoch 200/200
23/23 [==============================] - 0s 1ms/step - loss: 0.1411 - accuracy: 0.9296
<tensorflow.python.keras.callbacks.History at 0x27862b0be20>
>>> test_loss, test_acc = model.evaluate(X_test, y_test, verbose = 2)
>>> print("Accuracy:", np.round(test_acc, 5))
8/8 - 0s - loss: 0.2408 - accuracy: 0.9400
Accuracy: 0.94
  • 결과를 보면 정확도 Accuracy가 0.94로 매우 높은 것을 볼 수 있다.
  • 이는 거의 같은 변수인 성별(Sex)이 존재해 그러는 것으로 보인다.
  • 만약 성별을 제외하고 모델을 학습시킨다면, Accuracy가 0.8 이하로 크게 감소하는 것을 볼 수 있다.
# 데이터에서 Sex를 제외하고 학습
>>> test_loss, test_acc = model.evaluate(X_test, y_test, verbose = 2)
>>> print("Accuracy:", np.round(test_acc, 5))
8/8 - 0s - loss: 1.0323 - accuracy: 0.8000
Accuracy: 0.8

 

 

 

 

3. Softmax의 결과

  • Softmax의 결과는 Sigmoid와 달리, 분류하고자 하는 집합의 수와 형태가 같다.
  • 10개 Dataset의 결과를 보자
>>> model.predict(X_test)[:10]
array([[7.8218585e-01, 1.8440728e-01, 3.3406798e-02],
       [2.3066834e-01, 1.6199030e-01, 6.0734141e-01],
       [2.6888084e-01, 6.2357849e-01, 1.0754070e-01],
       [4.0409532e-01, 1.8306581e-02, 5.7759809e-01],
       [9.4835693e-01, 5.1634710e-02, 8.4140474e-06],
       [9.9992132e-01, 7.8680758e-05, 2.3134878e-11],
       [2.3000217e-04, 2.4639613e-07, 9.9976975e-01],
       [5.7762786e-04, 3.2117957e-01, 6.7824280e-01],
       [2.4147890e-13, 9.9999237e-01, 7.5892681e-06],
       [6.8085140e-01, 3.1174924e-02, 2.8797367e-01]], dtype=float32)
  • 각 행에서 가장 큰 값의 위치를 반환하면, 가장 확률이 높은 값의 위치를 반환한다.
>>> np.argmax(model.predict(X_test), axis = 1)[:10]
array([0, 2, 1, 2, 0, 0, 2, 2, 1, 0], dtype=int64)
  • np.argmax(array, axis=0): array에서 가장 큰 값의 위치를 반환한다.

 

 

 

 

 지금까지 타이타닉 데이터를 이용한 기초적인 분류 모델을 만들고, 그 성능을 평가하는 부분에 대해 학습해보았다. 지금까지는 기계 학습의 대략적인 흐름을 보는 것과 흥미를 끌기 위해 빠르게 넘어갔다면, 다음 포스트부터는 조금 천천히 그리고 자세히 알아보자.

728x90
반응형
728x90
반응형

판다스(Pandas) 소개

 파이썬을 처음 사용하는 데이터 분석가가 제일 먼저 공부해야 할 라이브러리를 한 가지 꼽으라면, 많은 사람들이 판다스(Pandas)를 선택할 것이다.

 판다스는 R과 마찬가지로 데이터 프레임(DataFrame)을 사용해서, 데이터를 시각화, 분석을 할 수 있는데, R의 데이터 프레임이 그렇듯 매우 직관적이고, 데이터를 가지고 놀기 좋은 R의 기능을 대부분 사용할 수 있기 때문에 데이터 분석가에게 있어 필수 라이브러리라고 할 수 있다.

 

 사족으로 판다스라고 하면, 동물인 판다가 먼저 떠오를 텐데, 판다스는 동물에서 따온 이름이 아닌, 계량 경제학에서 사용하는 "패널 데이터(Panner Data)"에서 따온 이름이다.

 사회 과학에서 자주 다뤄지는 패널 데이터를 간략히 설명하자면, 횡단 데이터인 한 시점에서의 데이터 셋이 종단 데이터로 규칙적인 기간을 간격으로 여러 개 존재하는 데이터를 말한다. 즉, 종단 + 횡단의 성격을 갖는 데이터가 패널 데이터다. 이는 판다스가 한 시점에서 뿐만이 아닌 시계열 데이터에도 강한 면모를 가진다는 뜻이기도 하다.

 

 판다스는 대용량 데이터를 다룰 때나, 서비스를 위해 0.5초, 1초 내의 빠른 연산이 필요한 상황에선 취약한 모습을 보이기 때문에 만능이라고 할 수는 없으나, 판다스는 데이터의 흐름이나 데이터의 특징 파악이 매우 쉬우므로, 먼저 판다스로 코드를 짜고, 속도가 매우 빠른 Numpy로 코드를 수정하면, 이를 쉽게 해결할 수 있다.

 

 

 

 

1. 판다스의 데이터 타입

  • 판다스는 크게 2개의 고유 데이터 타입을 가지고 있다.
  • 하나는 데이터 프레임(DataFrame)이고, 다른 하나는 시리즈(Series)이다.
  • 어떻게 생겼는지만 간략히 봐보자.
import pandas as pd
from sklearn.datasets import load_iris

# 붓꽃(iris) 데이터를 가져와보자.
iris_dict = load_iris()

DF = pd.DataFrame(iris_dict["data"], columns=iris_dict["feature_names"])
DF

  • 위 표가 데이터 프레임이다.
  • 데이터 프레임은 마치 액셀처럼 이쁘게 표로 나눠져 있는 형태다.
  • 엑셀과 차이라면, 데이터 프레임은 모든 데이터를 한 번에 보여주지 않는다는 것이다.
  • 데이터 프레임은 한 번에 최대 60개 행까지 보여준다.
>>> DF[:60]

>>> DF["sepal length (cm)"]
0      5.1
1      4.9
2      4.7
3      4.6
4      5.0
      ... 
145    6.7
146    6.3
147    6.5
148    6.2
149    5.9
Name: sepal length (cm), Length: 150, dtype: float64
  • 시리즈는 array와 굉장히 비슷하며, 데이터 프레임은 길이가 동일한 시리즈가 열의 숫자만큼 붙어있다고 생각해도 좋다.
  • array와의 차이점은 array의 index는 무조건 0부터 시작하지만, 시리즈는 그렇지 않다는 것이다. 또한 데이터 프레임이나 시리즈는 멀티 인덱스라 하여, 2개의 칼럼을 인덱스로 사용할 수 있다.

 

 

 

 다음 포스트에서는 본격적으로 데이터 프레임을 다뤄보도록 하겠다.

728x90
반응형
728x90
반응형

 이전에 학습했던 모델은 Input 되는 Node의 수가 2개인 모델이었다. Input Node의 수를 3개 이상으로 하는 방법도 크게 다르지 않다. 이번 포스트에서는 Output Node의 수를 2개로 해보도록 하겠다.

 

 

학습 목표

  • 연속형 데이터를 이용하여 Input Node가 3개이고, Output Node가 2개인 데이터를 컴퓨터가 맞추도록 해보자.
  • 패턴:

$$h(x)=\begin{cases}y_1=0.3x_1+0.2x_2-0.4x_3+0.1x_4+2 \\  y_2=0.5x_1-0.1x_2+0.3x_3+0x_4-2\end{cases}$$

 

 

 

구현해보자.

1. 데이터셋 생성

# import mudule
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense
# Dataset 생성
def f1(x1, x2, x3, x4):
    return 0.3*x1 + 0.2*x2 - 0.4*x3 + 0.1*x4 + 2


def f2(x1, x2, x3, x4):
    return 0.5*x1 - 0.1*x2 + 0.3*x3 - 2


def make_dataset(start_N, end_N):
    
    x1 = np.arange(start_N, end_N)
    x2 = x1 + 1
    x3 = x1 + 2
    x4 = x1 + 3
    
    y1 = f1(x1, x2, x3, x4)
    y2 = f2(x1, x2, x3, x4)
    
    append_for_shuffle = np.c_[x1, x2, x3, x4, y1, y2]
    np.random.shuffle(append_for_shuffle)
    
    X = append_for_shuffle[:,[0,1,2,3]]
    y = append_for_shuffle[:,[4,5]]
    
    return X, y
X, y = make_dataset(0, 1000)
X_train, X_test = X[:800], X[800:]
y_train, y_test = y[:800], y[800:]
  • 생성된 Dataset을 보자.
>>> X_train
array([[491., 492., 493., 494.],
       [ 47.,  48.,  49.,  50.],
       [755., 756., 757., 758.],
       ...,
       [445., 446., 447., 448.],
       [429., 430., 431., 432.],
       [881., 882., 883., 884.]])
       
>>> y_train
array([[ 99.9, 342.2],
       [ 11.1,  31.4],
       [152.7, 527. ],
       ...,
       [ 90.7, 310. ],
       [ 87.5, 298.8],
       [177.9, 615.2]])

 

 

 

2. 모델 생성 및 학습

# 모델 생성 및 Compile 실시
model = keras.Sequential()
model.add(Dense(128, activation = "relu"))
model.add(Dense(64, activation = "relu"))
model.add(Dense(32, activation = "relu"))
model.add(Dense(16, activation = "relu"))
model.add(Dense(2, activation = "linear"))

opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer = opt, loss = "mse")

# 표준화
min_key = np.min(X_train)
max_key = np.max(X_train)

X_std_train = (X_train - min_key)/(max_key - min_key)
y_std_train = (y_train - min_key)/(max_key - min_key)
X_std_test = (X_test - min_key)/(max_key - min_key)
>>> model.fit(X_std_train, y_std_train, epochs = 100)

Epoch 1/100
25/25 [==============================] - 1s 1ms/step - loss: 0.0181
Epoch 2/100
25/25 [==============================] - 0s 1ms/step - loss: 1.9474e-04
Epoch 3/100
25/25 [==============================] - 0s 1ms/step - loss: 2.2225e-05
Epoch 4/100
25/25 [==============================] - 0s 1ms/step - loss: 3.8012e-06
Epoch 5/100
25/25 [==============================] - 0s 1ms/step - loss: 7.8100e-07
Epoch 6/100
25/25 [==============================] - 0s 1ms/step - loss: 3.6994e-07

...

Epoch 96/100
25/25 [==============================] - 0s 1ms/step - loss: 1.1601e-07
Epoch 97/100
25/25 [==============================] - 0s 1ms/step - loss: 1.8677e-07
Epoch 98/100
25/25 [==============================] - 0s 956us/step - loss: 3.6037e-07
Epoch 99/100
25/25 [==============================] - 0s 878us/step - loss: 3.3609e-07
Epoch 100/100
25/25 [==============================] - 0s 873us/step - loss: 1.9744e-07
<tensorflow.python.keras.callbacks.History at 0x1d068dd0670>
>>> def MAE(x, y):
>>>     return np.mean(np.abs(x - y))

>>> pred = model.predict(X_std_test) * (max_key - min_key) + min_key
>>> print("Accuracy:", MAE(pred, y_test))
Accuracy: 0.1379637644290885
  • 정확도(Accuracy)는 0.137로 만족스러울 정도는 아니지만 그리 나쁘진 않게 나왔다.
  • 정확도는 평균 절댓값 오차(MAE)로 구하였으므로, RMSE보다 실제 편차에 더 가깝다고 할 수 있다.
  • 실제 데이터의 생김새를 보자.
DF = pd.DataFrame(pred, columns=["y1_pred", "y2_pred"])
DF[["y1_label", "y2_label"]] = y_test
DF["y1_gap"] = DF["y1_label"]-DF["y1_pred"]
DF["y2_gap"] = DF["y2_label"]-DF["y2_pred"]
DF[["y1_pred", "y1_label", "y1_gap", "y2_pred", "y2_label", "y2_gap"]]

  • 만족스러운 수준은 아니지만, 실제 데이터와 예측 데이터가 꽤 유사하게 나왔다.

 

 

 

 

 지금까지 가볍게 연속형 데이터를 이용해서 숨겨진 패턴을 찾아보았다. 다음 포스트에서는 머신러닝을 할 때, 가장 처음 사용하게 되는 실제 데이터인 타이타닉 데이터를 이용해서 생존 여부를 분류해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서 변수가 1개인 Input이 들어가 Output이 1개인 모델을 만들어보았다. 이번 포스트에서는 Input이 2개고, Output이 1개인 모델을 만들어보도록 하겠다.

 

 

학습 목표

  • 이전 패턴보다 컴퓨터가 인지하기 어려운 패턴을 컴퓨터가 찾아내도록 해보자.
  • 패턴: $ f(x)=\frac{1}{2}x_1^2-3x_2+5 $

 

 

 

 

1. 이전 방식대로 모델을 만들고 평가해보자.

  • 이전 모델을 생성했던 방법대로 데이터셋을 생성하고 학습을 시켜서 패턴을 찾는지 확인해보자.
# Import Module
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense
# Dataset 만들기
np.random.seed(1234)

def f2(x1, x2):
    
    return 0.5*x1**2 - 3*x2 + 5

X0_1 = np.random.randint(0, 100, (1000))
X0_2 = np.random.randint(0, 100, (1000))
X_train = np.c_[X0_1, X0_2]
y_train = f2(X0_1, X0_2)

X1_1 = np.random.randint(100, 200, (300))
X1_2 = np.random.randint(100, 200, (300))
X_test = np.c_[X1_1, X1_2]
y_test = f2(X1_1, X1_2)
# make model
model = keras.Sequential()
model.add(Dense(16, activation = 'relu'))
model.add(Dense(32, activation = 'relu'))
model.add(Dense(16, activation = 'relu'))
model.add(Dense(1, activation = 'linear'))


# Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss='mse')


# Standardization
mean_key = np.mean(X_train)
std_key = np.std(X_train)

X_train_std = (X_train - mean_key)/std_key
y_train_std = (y_train - mean_key)/std_key

X_test_std = (X_test - mean_key)/std_key
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
32/32 [==============================] - 1s 972us/step - loss: 4486.5587
Epoch 2/100
32/32 [==============================] - 0s 1ms/step - loss: 2577.3394
Epoch 3/100
32/32 [==============================] - 0s 974us/step - loss: 135.0658
Epoch 4/100
32/32 [==============================] - 0s 1ms/step - loss: 39.6805
Epoch 5/100
32/32 [==============================] - 0s 1ms/step - loss: 26.0182
Epoch 6/100
32/32 [==============================] - 0s 1ms/step - loss: 23.2357

...

Epoch 96/100
32/32 [==============================] - ETA: 0s - loss: 0.870 - 0s 730us/step - loss: 0.9306
Epoch 97/100
32/32 [==============================] - 0s 835us/step - loss: 0.4291
Epoch 98/100
32/32 [==============================] - 0s 792us/step - loss: 0.5671
Epoch 99/100
32/32 [==============================] - 0s 856us/step - loss: 0.3809
Epoch 100/100
32/32 [==============================] - 0s 708us/step - loss: 0.4041
<tensorflow.python.keras.callbacks.History at 0x21cdb6c0b80>
>>> pred = (model.predict(X_test_std) * std_key) + mean_key
>>> pred = pred.reshape(pred.shape[0])
>>> print("Accuracy:", np.sqrt(np.sum((y_test - pred)**2))/len(y_test))
Accuracy: 209.2436541220142
  • 이전 포스트처럼 시험 데이터 셋과 학습 데이터 셋을 전혀 겹치지 않는 영역으로 만들어보았다.
  • 손실 값은 0에 가깝게 줄어들었으나, 정확도(Accuracy)가 209.243으로 매우 낮은 것을 알 수 있다.
  • 예측값과 라벨의 차이가 어느 정도인지 확인해보자.
result_DF = pd.DataFrame({"predict":pred, "label":y_test})
result_DF["gap"] = result_DF["label"] - result_DF["predict"]
result_DF

  • 위 데이터를 보면, 실제(label)와 예측값(predict)의 차이가 매우 크게 나는 것을 볼 수 있다.
  • 대체 왜 이런 현상이 발생한 것일까?

 

 

 

 

2. 학습에 맞는 데이터셋 만들기

  • 이전 학습에서 숨겨져 있던 패턴은 다음과 같다.
  • $h(x) = x + 10 $
  • 위 패턴은 아주 단순한 선형 함수이므로, 학습 데이터 셋과 거리가 있는 데이터라 할지라도, 쉽게 예측할 수 있다.
  • 그러나, 이번에 숨겨진 패턴인 $f(x)=\frac{1}{2}x_1^2-3x_2+5$은 $x^2$의 존재로 인해 선형 함수가 아니며, 해가 2개이므로, 이전에 비해 꽤 복잡해졌다.
  • 이번엔 train Dataset에서 test Dataset을 분리해서 학습해보자.
  • 단, train Dataset과 test Dataset은 절대 중복되선 안 된다.
# Dataset 만들기
np.random.seed(1234)

def f2(x1, x2):
    
    return 0.5*x1**2 - 3*x2 + 5

X1 = np.random.randint(0, 100, (1000))
X2 = np.random.randint(0, 100, (1000))
X = np.c_[X1, X2]
y = f2(X1, X2)

# 데이터셋을 중복되지 않게 만든다.
Xy = np.c_[X, y]
Xy = np.unique(Xy, axis = 0)
np.random.shuffle(Xy)
test_len = int(np.ceil(len(Xy)*0.3))
X = Xy[:, [0,1]]
y = Xy[:, 2]

# test Dataset과 train Dataset으로 나누기
X_test = X[:test_len]
y_test = y[:test_len]

X_train = X[test_len:]
y_train = y[test_len:]
  • np.c_[array1, array2]: 두 array를 열 기준으로 붙인다.
  • np.unique(array, axis = 0): array에서 unique 한 값만 추출한다(axis를 어떻게 잡느냐에 따라 다른 결과를 가지고 올 수 있다).
  • np.random.shuffle(array): array를 랜덤 하게 섞는다
  • np.ceil(float): float을 올림 한다.
  • 데이터셋을 중복되지 않게 만들어, test set과 train set이 중복되어 Accuracy가 낮게 나오는 현상을 피한다.
# make model
model = keras.Sequential()
model.add(Dense(16, activation = 'relu'))
model.add(Dense(32, activation = 'relu'))
model.add(Dense(16, activation = 'relu'))
model.add(Dense(1, activation = 'linear'))


# Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss='mse')


# Standardization
mean_key = np.mean(X_train)
std_key = np.std(X_train)

X_train_std = (X_train - mean_key)/std_key
y_train_std = (y_train - mean_key)/std_key

X_test_std = (X_test - mean_key)/std_key
# Model Learning
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
139/139 [==============================] - 1s 912us/step - loss: 2999.6784
Epoch 2/100
139/139 [==============================] - 0s 943us/step - loss: 26.4051
Epoch 3/100
139/139 [==============================] - 0s 1ms/step - loss: 14.5395
Epoch 4/100
139/139 [==============================] - 0s 1ms/step - loss: 9.9778
Epoch 5/100
139/139 [==============================] - 0s 814us/step - loss: 7.2809
Epoch 6/100
139/139 [==============================] - 0s 777us/step - loss: 5.1137
Epoch 7/100

...

Epoch 96/100
139/139 [==============================] - 0s 1ms/step - loss: 0.0378
Epoch 97/100
139/139 [==============================] - 0s 931us/step - loss: 0.0468
Epoch 98/100
139/139 [==============================] - 0s 821us/step - loss: 0.0808
Epoch 99/100
139/139 [==============================] - 0s 745us/step - loss: 0.1535
Epoch 100/100
139/139 [==============================] - 0s 793us/step - loss: 0.0493
<tensorflow.python.keras.callbacks.History at 0x260b7b33c70>
>>> pred = (model.predict(X_test_std) * std_key) + mean_key
>>> pred = pred.reshape(pred.shape[0])
>>> print("Accuracy:", np.sqrt(np.sum((y_test - pred)**2))/len(y_test))
Accuracy: 0.9916198414587479
  • 데이터 셋만 바꿨는데, 이전 데이터 셋의 정확도(Accuracy)가 209.243에서 0.9916으로 큰 폭으로 떨어진 것을 볼 수 있다.
  • 실제 예측 결과가 어떻게 생겼는지 확인해보자.
result_DF = pd.DataFrame({"predict":pred, "label":y_test})
result_DF["gap"] = result_DF["label"] - result_DF["predict"]
result_DF

  • 차이가 있긴 하지만, 실제 데이터와 상당히 가까워졌다.
  • 이번엔 데이터의 양을 늘려서 학습시켜보자.

 

 

 

 

3. 데이터의 양을 늘려보자.

# Import Module
import pandas as pd
import numpy as np
from tensorflow import keras
from tensorflow.keras.layers import Dense




# Dataset 만들기
np.random.seed(1234)

def f2(x1, x2):
    
    return 0.5*x1**2 - 3*x2 + 5

X1 = np.random.randint(0, 100, (30000))
X2 = np.random.randint(0, 100, (30000))
X = np.c_[X1, X2]
y = f2(X1, X2)

# 데이터셋을 중복되지 않게 만든다.
Xy = np.c_[X, y]
Xy = np.unique(Xy, axis = 0)
np.random.shuffle(Xy)
test_len = int(np.ceil(len(Xy)*0.2))
X = Xy[:, [0,1]]
y = Xy[:, 2]

# test Dataset과 train Dataset으로 나누기
X_test = X[:test_len]
y_test = y[:test_len]

X_train = X[test_len:]
y_train = y[test_len:]




# make model
model = keras.Sequential()
model.add(Dense(32, activation = 'elu'))
model.add(Dense(32, activation = 'elu'))
model.add(Dense(1, activation = 'linear'))


# Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss='mse')


# min-max scaling
min_key = np.min(X_train)
max_key = np.max(X_train)

X_train_std = (X_train - min_key)/(max_key - min_key)
y_train_std = (y_train - min_key)/(max_key - min_key)

X_test_std = (X_test - min_key)/(max_key - min_key)
>>> model.fit(X_train_std, y_train_std, epochs = 100)

Epoch 1/100
238/238 [==============================] - 1s 970us/step - loss: 168.8257
Epoch 2/100
238/238 [==============================] - 0s 1ms/step - loss: 4.6773A: 0s - loss: 5.8
Epoch 3/100
238/238 [==============================] - 0s 821us/step - loss: 1.2054
Epoch 4/100
238/238 [==============================] - 0s 842us/step - loss: 0.4222
Epoch 5/100
238/238 [==============================] - 0s 781us/step - loss: 0.1056
Epoch 6/100
238/238 [==============================] - 0s 851us/step - loss: 0.0459

...

Epoch 96/100
238/238 [==============================] - 0s 736us/step - loss: 4.2894e-04
Epoch 97/100
238/238 [==============================] - 0s 741us/step - loss: 5.0023e-04
Epoch 98/100
238/238 [==============================] - 0s 720us/step - loss: 0.0046
Epoch 99/100
238/238 [==============================] - 0s 749us/step - loss: 0.0036
Epoch 100/100
238/238 [==============================] - 0s 812us/step - loss: 0.0189
<tensorflow.python.keras.callbacks.History at 0x24611ae5910>
>>> pred = (model.predict(X_test_std) * (max_key - min_key)) + min_key
>>> pred = pred.reshape(pred.shape[0])
>>> print("Accuracy:", np.sqrt(np.sum((y_test - pred)**2))/len(y_test))
Accuracy: 0.03539701825569002
result_DF = pd.DataFrame({"predict":pred, "label":y_test})
result_DF["gap"] = result_DF["label"] - result_DF["predict"]
result_DF

  • 중복을 제거하여 데이터의 양을 953개에서 9,493개로 늘렸다.
  • 그로 인해 Accuracy가 0.9916에서 0.0353으로 감소하여, 정확도가 보다 올라갔다.
  • 이상치가 존재하지 않는 데이터이므로, 최소-최대 스케일 변환(min-max scaling)을 이용해 표준화를 시켰다. 그로 인해, Accuracy가 크게 변하지는 않았으나, 이전에 비해 손실 값이 빠르게 0에 수렴하는 것을 볼 수 있다.
  • 활성화 함수를 relu가 아닌 elu를 사용하였다. 성능 차이가 그리 크지는 않으나, 손실 값과 Accuracy에 긍정적인 영향을 미쳤다.
  • 네트워크의 노드 수와 Layer의 수를 바꿨다.

 

 

 

 지금까지 변수가 2개인 데이터 셋을 학습시키는 과정을 해보았다. 숨어있는 패턴이 복잡하고 변수의 수가 늘어났더니, 처음 보는 영역에 있는 데이터를 제대로 분류하지 못하는 현상이 발생하였다.

 이 때는 학습 데이터셋에 시험 데이터셋과 유사한 데이터 셋을 포함시키는 것이 가장 좋은 해결 방법이다. 위처럼 시험 데이터 셋과 학습 데이터 셋이 중복되지 않는다 할지라도, 유사한 영역에 있는 경우 제대로 예측하는 것을 볼 수 있다.

728x90
반응형

+ Recent posts