728x90
반응형

 꽤 많은 블로그나 책에서 머신러닝을 다룰 때, 회귀모형, 로지스틱 회귀모형을 꼭 다루고 넘어가는데, 이번 포스트에서는 우리가 통계학에서 흔히 다루는 회귀모형과 딥러닝이 대체 어떤 관계길래 다들 회귀모형부터 다루는지에 대해 알아보고자 한다.

 사실 우리는 이미 이전 포스트에서 회귀모형, 로지스틱 회귀모형을 만들어보았으며, 만약, 통계에 조금 익숙한 사람이라면, 분석된 결과나 그 과정을 보면서, 이게 회귀분석 아닌가? 하는 생각이 들었을지도 모른다(물론, Odd Ratio, $R^2$과 같은 익숙한 지표들이 그대로 등장하진 않았지만, 그것과 유사한 역할을 하는 지표를 이미 봤을 것이다).

 

 

회귀모형(Regression Model)

 통계에 익숙한 사람이라면, 통계의 꽃인 회귀 모형에 대해 이미 잘 알고 있을 것이다. 우리가 기존에 학습했던, "Tensorflow-1.4. 기초(5)-하이퍼 파라미터 튜닝", "Tensorflow-2.4. 타이타닉 생종자 분류 모델(3)-하이퍼 파라미터 튜닝"에서 만들었던 모델이 바로 회귀모형이다.

 

 

 

 

1. 통계학과 딥러닝의 회귀모형

 회귀모형에 대해 잘 모를 수 있으므로, 통계학에서의 회귀모형에 대해 아주 단순하게 설명해보도록 하겠다.

  • 회귀모형은 데이터 간에 어떠한 경향성이 있다는 생각에서 시작된다.
  • 회귀모형은 독립변수와 종속변수가 서로 인과 관계가 있다고 할 때, 독립변수가 변하면 종속변수가 변하게 된다.

회귀식: $ y = b_1x_1 + b_2x_2 + b_3x_3 + c $

  • 회귀모형은 데이터에 가장 적합한 계수($b_1, b_2, b_3, c$)를 구하는 것이 목적이다.
  • 회귀모형은 그 적합한 계수를 찾는 방법으로 평균 제곱 오차(MSE)를 사용한다.
    (실제값과 예측값의 편차 제곱의 평균, 참고: "딥러닝-5.1. 손실함수(2)-평균제곱오차(MSE)")

 회귀식은 퍼셉트론 공식과 아주 똑 닮았다("머신러닝-2.1. 퍼셉트론(2)-논리회로"). 그리고 딥러닝을 통해 우리는 각각의 파라미터(가중치와 편향)를 찾아낼 수 있다.

 독립변수(Dataset이자 상수)의 변화에 따른 종속변수(Label, 상수)의 변화를 가장 잘 설명할 수 있는 계수(weight과 bias)를 찾아내는 것은 회귀분석이며, 이 점이 다층 퍼셉트론을 이용하여, 데이터 자체를 가장 잘 설명할 수 있는 파라미터를 찾아내는 딥러닝(Deep Learning)과 같다고 할 수 있다.

1.1. 이 밖의 회귀모형의 특징

  • 회귀모형은 기본적으로 연속형 데이터를 기반으로 한다.
  • 독립변수, 종속변수가 모두 연속형 데이터여야 한다.
  • 연속형 데이터가 아닌 독립변수 존재 시, 이를 가변수(Dummy variable)로 만든다.

 

 

 

 

2. 데이터셋

 이번 학습에서는 R을 사용해본 사람이라면 아주 친숙한 데이터 중 하나인 자동차 연비 데이터(MPG)를 이용해서 회귀모형을 만들어보도록 하겠다.

# Import Module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split

import pandas as pd
import numpy as np
>>> dataset_path = keras.utils.get_file("auto-mpg.data", "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
Downloading data from http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data
32768/30286 [================================] - 0s 4us/step
  • tf.keras.utils.get_file()을 사용하면, 각종 데이터셋을 쉽게 가져올 수 있다.
  • 데이터셋을 가지고 오는 곳은 UCI 머신러닝 저장소로 인터넷이 안 되는 환경이라면 데이터를 다운로드할 수 없으니, 조심하자.
  • sklearn처럼 데이터셋을 Dictionary에 깔끔하게 저장하여, 데이터를 다운로드하면, 모든 데이터에 대한 정보가 있는 상태가 아니므로, 데이터에 대해 파악하기 위해선 UCI 머신러닝 저장소에서 데이터에 대해 검색하여, 데이터 정보를 찾아봐야 한다.
  • 해당 포스팅에서는 "Tensorflow 공식 홈페이지의 자동차 연비 예측하기: 회귀"를 참고하여 데이터셋을 준비하였다.
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
                'Acceleration', 'Model Year', 'Origin']
Rawdata = pd.read_csv(dataset_path, names=column_names, na_values = "?",
                      comment='\t', sep=" ", skipinitialspace=True)
Rawdata

 

 

 

 

3. 데이터 전처리

3.1. 결측 값 처리

>>> Rawdata.isna().sum()
MPG             0
Cylinders       0
Displacement    0
Horsepower      6
Weight          0
Acceleration    0
Model Year      0
Origin          0
dtype: int64

>>> print("전체 데이터에서 결측값 행의 비율:", Rawdata.isna().sum(1).sum()/len(Rawdata))
전체 데이터에서 결측값 행의 비율: 0.01507537688442211
  • 위 결과를 보니, Horsepower에서 결측 값이 6개 발생한 것을 알 수 있다.
  • 전체 데이터에서 결측 값 행의 비율을 보니 0.015로 매우 미미한 양이므로, 제거하도록 하자.
Rawdata.dropna(inplace = True)

 

3.2. 범주형 데이터의 원-핫 벡터화

  • 칼럼 Origin은 숫자로 표시되어 있지만, 실제론 문자인 범주형 데이터이므로, 원-핫 벡터화 해주자.
    (이는 회귀 모델에서의 가변수 처리와 매우 유사한 부분이라고 할 수 있다.)
# One-Hot Vector 만들기
def make_One_Hot(data_DF, column):
    
    target_column = data_DF.pop(column)

    for i in sorted(target_column.unique()):

        new_column = column + "_" + str(i)
        data_DF[new_column] = (target_column == i) * 1.0
  • 이전 참고 포스트에서 만들었던, 원-핫 벡터 코드보다 판다스의 성격을 잘 활용하여 만든 코드다
  • DataFrame.pop(column): DataFrame에서 선택된 column을 말 그대로 뽑아낸다. 그로 인해 기존 DataFrame에서 해당 column은 사라지게 된다.
  • (target_column == i) * 1.0: Boolearn의 성질을 사용한 것으로 target_column의 원소를 갖는 위치는 True로, 원소가 없는 곳은 False가 된다. 이에 1.0을 곱하여 int로 바꿔줬다. Python은 동적 언어이므로, 1.0을 곱해주어도 int형으로 변하게 된다.

 

3.3. 데이터셋 분리

  • train set과 test set은 7:3으로 분리하도록 하겠다.
  • validation set은 이 단계에서 뽑지 않고, 학습 과정(fit)에서 뽑도록 하겠다.
  • MPG 변수는 연비를 의미하며 종속변수에 해당하므로, 이를 Label로 사용하겠다.
# Dataset 쪼개기
label = Rawdata.pop("MPG").to_numpy()
dataset = Rawdata.values
X_train, X_test, y_train, y_test = train_test_split(dataset, label, test_size = 0.3)

 

3.4. 특성 스케일 조정

  • 해당 데이터 셋의 각 칼럼의 데이터는 다른 변수에서 나온 것이므로, 각각 변수별 기준으로 정규화해주어야 한다.
  • 표준 정규 분포를 사용하여 특성 스케일 조정을 하도록 하겠다.
  • 원-핫 벡터가 사용된 7:9까지의 열을 제외한 나머지 열에 대해 각각 특성 스케일 조정을 하겠다.
# 특성 스케일 조절
mean_point = X_train[:, :6].mean(axis=0)
std_point = X_train[:, :6].std(axis=0)

X_train[:, :6] = ((X_train[:, :6] - mean_point)/std_point)
X_test[:, :6] = ((X_test[:, :6] - mean_point)/std_point)
  • Numpy의 특징 중 하나인 Broadcasting 덕에 쉽게 정규화할 수 있다.

 

 

 

 

4. 모델 학습 및 평가

  • 모델은 은닉층이 1개 있는 단층 퍼셉트론(Sigle-later Perceptron)으로 만들어보겠다.
    (은닉층을 2개 이상 넣어 다층 퍼셉트론으로 만들어도 상관없다)
  • model은 Sequential 함수 안에 layer를 넣어주는 방법으로 만들어보겠다.
    (추천하지는 않는 방법이지만 이런 방법도 있다는 것을 보여주고자 사용해보았다)
  • 출력층의 활성화 함수는 선형(Linear)으로 설정하였다.
    (activation의 Default는 linear이므로, 따로 설정하지 않아도 된다)
  • 조기 종료(Early stop)를 콜백 함수로 주도록 하겠다.
# model 생성
model = keras.Sequential([
    keras.layers.Dense(60, activation = "relu"),
    keras.layers.Dense(1, activation = "linear")
])

# model compile 설정
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss="mse", metrics = ["accuracy"])

# model 학습
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
model.fit(X_train, y_train, epochs=300, validation_split=0.2, callbacks=[early_stop])
Epoch 1/300
7/7 [==============================] - 2s 248ms/step - loss: 572.1581 - accuracy: 0.0000e+00 - val_loss: 497.8600 - val_accuracy: 0.0000e+00
Epoch 2/300
7/7 [==============================] - 0s 19ms/step - loss: 516.3237 - accuracy: 0.0000e+00 - val_loss: 443.4315 - val_accuracy: 0.0000e+00
Epoch 3/300
7/7 [==============================] - 0s 18ms/step - loss: 454.6065 - accuracy: 0.0000e+00 - val_loss: 385.6940 - val_accuracy: 0.0000e+00
Epoch 4/300
7/7 [==============================] - 0s 19ms/step - loss: 390.3198 - accuracy: 0.0000e+00 - val_loss: 321.7114 - val_accuracy: 0.0000e+00

...

Epoch 79/300
7/7 [==============================] - 0s 8ms/step - loss: 5.6932 - accuracy: 0.0000e+00 - val_loss: 7.2688 - val_accuracy: 0.0000e+00
Epoch 80/300
7/7 [==============================] - 0s 8ms/step - loss: 5.9354 - accuracy: 0.0000e+00 - val_loss: 7.3990 - val_accuracy: 0.0000e+00
Epoch 81/300
7/7 [==============================] - 0s 8ms/step - loss: 5.5025 - accuracy: 0.0000e+00 - val_loss: 7.3694 - val_accuracy: 0.0000e+00
Epoch 82/300
7/7 [==============================] - 0s 8ms/step - loss: 5.8313 - accuracy: 0.0000e+00 - val_loss: 7.2677 - val_accuracy: 0.0000e+00
<tensorflow.python.keras.callbacks.History at 0x1e00b62b1f0>
# model 평가
>>> model.evaluate(X_test, y_test)
4/4 [==============================] - 0s 2ms/step - loss: 10.2492 - accuracy: 0.0000e+00
[10.24919319152832, 0.0]

 

 

 

 

5. 정리

  • 출력층에 활성화 함수로 기본값인 선형 함수(Linear)가 사용되었다.
  • 활성화 함수로 선형 함수가 사용되는 경우, 단순하게 이전 층에서 출력된 값에 대하여 선형 함수의 계수 값만큼만 곱하므로, 은닉층에서의 값을 보존한 상태로 출력하게 된다.
  • 참고: "딥러닝-3.0. 활성화함수(1)-계단함수와 선형함수"
  • 손실 함수로 평균 제곱 오차(MSE)를 사용하였다.
  • 참고: "딥러닝-5.1. 손실함수(2)-평균제곱오차(MSE)"
  • 회귀모형은 주어진 데이터가 어떠한 경향성을 갖고 있다는 가정하에 변수들 사이의 경향성을 가장 잘 설명할 수 있는 계수들을 찾아내는 것이다.
  • 우리는 딥러닝 모델을 사용해서 Feature로 독립변수들을 넣었고, Label에 종속변수를 넣었다.
  • 우리는 딥러닝 모델을 이용해 Feature가 Label에 대한 어떠한 경향성, 즉 패턴을 갖고 있다고 보고, 그 패턴을 나타내는 파라미터(weight, bias)를 찾아냈다.
  • 회귀분석은 일반적으로 최소제곱법(OLS)을 통해 최적의 가중치를 찾아내지만, 우리는 최적의 가중치를 경사 하강법과 역전파를 통한 학습으로 찾아내었다.
  • 출력층의 활성화 함수를 Sigmoid 함수로 사용하여 이진 분류 하면, 로지스틱 회귀모형(Logistic Regression model)이 된다.

 

 

 

 지금까지 딥러닝(Deep Learning)을 사용하여, 회귀모델을 만들어보았다. 앞서 "딥러닝을 통해 이런 것도 되는구나!"하고 넘어갔던 부분들이 사실은 회귀모델이고, 회귀모델의 아이디어와 딥러닝의 아이디어의 유사한 부분에서 꽤 재미를 느꼈을 것이다.

 다음 포스트에서는 Keras의 장점인 함수형 API를 이용해 모델을 만들어, 얕은 신경망(Wide model)과 깊은 신경망(Deep model)을 합친 Wide & Deep Learning에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 TensorBoard의 Log Data가 쌓일 경로 설정과 TensorBoard 실행하기에 대해 알아보았다. 이번 포스트에서는 TensorBoard를 실제로 사용하는 방법에 대해 알아보도록 하겠다.

 

 

1. SCALARS

  • Tensorboard를 실행하면, 처음 뜨는 화면으로, 이전 포스트에서 생성하였던 Scalar 값들을 이용해서 손실 값과 정확도를 이용해 그래프를 그린 것이다.
  • 해당 그래프를 통해, 훈련 셋(Train set)과 검증 셋(Validation set)의 손실 값(Loss)과 정확도(Accuracy)의 추이를 볼 수 있다.
  • 이전에 학습과정을 확인했던 포스팅에서는 Matplotlib.pyplot을 사용해서, 그래프를 그렸다.

  • 이전 포스팅을 그대로 따라왔다면, 다음과 같은 화면이 뜰 것이다.

  • 그래프 아래에 빨간 부분 클릭 시, 그래프가 커지게 된다.
  • 위 그래프는 우리가 이전에 만들었던 matplotlib.pyplot으로 뽑았던 결과보다 자세한 결과를 보여준다. train_set과 validation_set이 다른 경향을 보여주는 부분을 크게 확대해서 보여주었다.
  • 그래프를 보면 연한 파란색과 빨간색 그래프가 희미하게 있는 것을 볼 수 있는데, 이 희미한 그래프는 실제 값이고, 이를 부드럽게 만든 것이 우리가 보고 있는 진한 파란색, 빨간색 그래프다. 이 부드러운 정도는 좌측의 "Smoothing" 바를 움직여서 조절할 수 있다.
  • 위 그래프에서 epoch_accuracy는 epoch별 accuracy의 변화를 보여준다. epoch_loss는 epoch별 loss의 변화를 보여준다.
  • 위 그래프를 보면, train set에 대한 학습 과정을 보여주는 파란색은 epoch가 지날수록 train set에 최적화되어 가므로, 지속적으로 증가하거나 감소하는 것을 볼 수 있으나, valiation set인 빨간색은 accuracy에서는 증가하다가 감소하는 부분이 생기고, loss에서는 accuracy보다 더 큰 폭으로 출렁이다가, epoch 11을 최솟값으로 지속적으로 증가하는 것을 볼 수 있다.
  • 해당 모델은 조기종료를 사용하였으며, 조기종료에서의 patience 파라미터를 10으로 주어, 최솟값이 등장한 이후 10만큼 추가 epoch를 실시해, 그 안에 보다 작은 최솟값이 등장하지 않는다면, 학습을 멈추게 하였다.

 

 

 

 

2. GPAPHS

  • 좌측 상단의 SCALARS 옆에 있는 GRAPHS를 클릭하면, 다음과 같은 화면이 뜬다.

  • 이는 노드와 엣지 등을 이용하여, 그래프로 학습이 일어난 과정을 시각화한 것이다.
  • 위 내용을 보면, 무언가 처음 보는 것이 엄청 많기 때문에 이해하기가 쉽지 않다.
  • 우리가 모델을 만들 때, 사용했었던 Sequential 쪽으로 마우스를 드래그하고, 휠을 스크롤하여, 큰 화면으로 만들어보면, sequential 우측 상단에 + 마크가 있는 것을 확인할 수 있다. 이를 더블 클릭해보자.

  • 이를 보면 우리가 모델을 만들 때 사용했던, Flatten, Hidden1, Hidden2, Hidden3, Output layer가 등장하는 것을 알 수 있다.
  • Hidden1, Hidden2, Hidden3는 모델을 생성할 때, name 파라미터를 이용해서 부여한 이름이다. 
  • 모서리가 둥근 직사각형을 클릭하면 그 안에 요약되어 있는 내용들을 볼 수 있다.
  • 이를 이용해서 모델의 각 부분에서 어떻게 학습이 이루어졌는지 확인할 수 있다.

 

 

 

 

3. TIME SERIES

  • TIME SERIES에서는 실시간으로 그래프의 변화를 볼 수 있다.

  • 먼저, Setting을 손봐서, 그래프 자동 Update 시간을 바꾸자.

  • Reload Period는 자동 Update 시간으로 TensorBoard로 볼 데이터의 크기 학습 시간 등을 고려하여 설정해야 한다. 최소 시간은 15초이므로, 15로 설정하도록 하겠다.
  • 모델은 조기 종료의 patience를 50으로 잡아, 학습이 오랫동안 이뤄지도록 하겠다.
  • 해당 학습 코드는 아래와 같다.
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 os
import datetime




# 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 ################################
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"])
              
              
              

dir_name = "Learning_log"

def make_Tensorboard_dir(dir_name):
    root_logdir = os.path.join(os.curdir, dir_name)
    sub_dir_name = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    return os.path.join(root_logdir, sub_dir_name)

# 콜백함수 - 텐서보드
TB_log_dir = make_Tensorboard_dir(dir_name)
TensorB = keras.callbacks.TensorBoard(log_dir = TB_log_dir)

# 콜백함수 - 조기 종료
early_stop = keras.callbacks.EarlyStopping(monitor="val_loss", min_delta=0, patience=50, restore_best_weights=True)


# 모델 학습
history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size=32,
                    validation_data=(valid_images, valid_labels),
                    callbacks=[early_stop, TensorB])
  • 이를 실행하고 TensorBoard를 보도록 하자.

728x90
반응형

'Machine Learning > TensorBoard' 카테고리의 다른 글

Tensorboard - 텐서보드 시작하기  (0) 2021.02.18
728x90
반응형

Tensorboard

 텐서플로우가 익숙하지 않은 사람은, 텐서플로우로 코드를 짜는 과정에서 시각적으로 보이는 것이 많지 않고, 보이는 부분도 이해하기 쉽지 않다 보니, 자신이 모델을 만들고, 학습을 시켰음에도 정확히 자신이 무엇을 했고, 문제가 발생한다 할지라도 왜 문제가 발생했는지 이해하기 어려울 수 있다.

 이 때 필요한 것이 시각화 도구인 Tensorboard로, 이전 포스팅에서 우리가 학습이 종료된 후, matplotbib.pyplot을 이용해 시각화를 했던 것과 달리, 텐서보드는 훈련하는 도중에도 실시간 시각화를 할 수 있다.

 Tensorboard는 텐서플로우뿐만 아니라 파이토치(Pytorch)에서도 사용 가능하므로, 그 사용법을 꼭 익히도록 하자.

 

 

 

 

1. Tensorboard의 기능

 텐서보드는 주로 시각화 기능을 제공하지만, 그 외에도 간단한 통계 분석, 자동 클러스터링 등 다양한 편의 기능을 제공한다. 다음 내용은 Tensorflow 홈페이지에서 이야기하는 텐서보드의 대표적인 기능이다(텐서플로우 공식 홈페이지).

  • 손실 및 정확도 같은 측정 도구 추적 및 시각화
  • 모델 네트워크 시각화
  • 시간의 경과에 따른 가중치, 편향, 기타 텐서의 히스토그램의 변화
  • 저차원 공간에 임베딩 투영
  • 이미지, 텍스트, 오디오 데이터 표시
  • TensorFlow 프로그램 프로파일링
  • TensorBoard.dev를 사용하여 실험 결과를 쉽게 호스팅, 추적 및 공유 가능

 위 내용을 보면, 텐서보드가 머신러닝을 사용하기 위해 반드시 필요한 존재인 것은 아니지만, 텐서보드가 제공하는 다양한 기능을 통해서 보다 정확하고, 편리하게 기계학습을 진행할 수 있다는 것을 알 수 있다.

 지금까지의 포스팅에서는 학습을 하기 위해 데이터가 어떻게 생겼고, 손실 값과 정확도를 추적하는 과정에서 새로운 함수를 만들거나, 다른 라이브러리의 기능을 가져와서 구현했으나, 텐서보드를 사용하면 이보다 쉽게 더 강력한 결과를 볼 수 있다.

 

 

 

 

2. Tensorboard 디렉터리 세팅하기

  • Tensorboard는 Tensorflow 설치 시, 자동으로 설치가 되고, 우리가 지금까지 사용했던 keras에서도 Tensorboard를 지원하기 때문에 따로 Tensorboard를 설치하지 않고도 사용할 수 있다.
  • Tensorboard는 시각화하고자 하는 Log 데이터를 모아야 한다.
  • 텐서보드는 Log 데이터가 쌓이는 디렉터리를 모니터링하고, 자동으로 변경된 부분을 읽어 그래프를 업데이트한다.
  • 위 기능으로 인해 Tensorboard는 실시간 데이터를 시각화할 수 있으나, 약간의 지연이 생기게 된다.

 

2.1. Log Data가 저장될 디렉터리 경로 만들기

import os

# Log data를 저장할 디렉터리 이름 설정
dir_name = "Learning_log"

# main 디렉터리와 sub 디렉터리 생성 함수
def make_Tensorboard_dir(dir_name):
    root_logdir = os.path.join(os.curdir, dir_name)
    sub_dir_name = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    return os.path.join(root_logdir, sub_dir_name)
  • os 모듈: 운영 체제와 상호 작용을 할 때, 사용하는 Python 기본 모듈로, Python 안에서 끝내는 것이 아닌 자신의 컴퓨터와 어떠한 작업을 하고자 한다면, 해당 모듈을 필수로 사용하게 된다.
  • os.curdir: 현재 디렉터리를 가지고 온다.
  • os.path.join('C:\Tmp', 'a', 'b'): 주어진 경로들을 합쳐서 하나로 만든다. 
  • dir_name은 Tensorboard가 바라 볼 디렉터리의 이름이다.
  • 코드 실행 시, 생성된 로그 데이터가 섞이지 않도록, dir_name 디렉터리 아래에 현재 날짜와 시간으로 하위 디렉터리의 경로를 만들어낸다.

 

 

 

 

3. Log를 생성해보자.

  • 이전 TensorFlow를 학습할 때, 만들었던 모델을 가져와서 실행해보도록 하자.
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 os
import datetime
# 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 ################################
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"])

 

  • Tensorboard 콜백 함수와 조기 종료 콜백 함수를 동시에 사용하도록 하겠다.
dir_name = "Learning_log"

def make_Tensorboard_dir(dir_name):
    root_logdir = os.path.join(os.curdir, dir_name)
    sub_dir_name = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
    return os.path.join(root_logdir, sub_dir_name)

# 콜백함수 - 텐서보드
TB_log_dir = make_Tensorboard_dir(dir_name)
TensorB = keras.callbacks.TensorBoard(log_dir = TB_log_dir)

# 콜백함수 - 조기 종료
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=32,
                    validation_data=(valid_images, valid_labels),
                    callbacks=[early_stop, TensorB])
Epoch 1/100
1719/1719 [==============================] - 6s 3ms/step - loss: 0.3810 - accuracy: 0.8795 - val_loss: 0.1590 - val_accuracy: 0.9504
Epoch 2/100
1719/1719 [==============================] - 4s 2ms/step - loss: 0.1460 - accuracy: 0.9599 - val_loss: 0.1781 - val_accuracy: 0.9490
Epoch 3/100
1719/1719 [==============================] - 4s 2ms/step - loss: 0.1113 - accuracy: 0.9695 - val_loss: 0.1440 - val_accuracy: 0.9600
Epoch 4/100
1719/1719 [==============================] - 4s 2ms/step - loss: 0.1018 - accuracy: 0.9704 - val_loss: 0.1030 - val_accuracy: 0.9692

...

Epoch 19/100
1719/1719 [==============================] - 4s 2ms/step - loss: 0.0462 - accuracy: 0.9891 - val_loss: 0.1291 - val_accuracy: 0.9744
Epoch 20/100
1719/1719 [==============================] - 4s 2ms/step - loss: 0.0472 - accuracy: 0.9901 - val_loss: 0.1196 - val_accuracy: 0.9762
Epoch 21/100
1719/1719 [==============================] - 4s 2ms/step - loss: 0.0398 - accuracy: 0.9909 - val_loss: 0.1714 - val_accuracy: 0.9698
Epoch 22/100
1719/1719 [==============================] - 4s 2ms/step - loss: 0.0483 - accuracy: 0.9899 - val_loss: 0.1745 - val_accuracy: 0.9714

  • 위 코드를 실행하면, 위 사진과 같이 자신의 워킹 디렉터리 내에 Tensorboard용 디렉터리와 자기가 코드를 실행한 횟수만큼 서브 디렉터리가 생성된 것을 볼 수 있다.
  • 생성된 서브 디렉터리 안에는 train과 validation이라는 디렉터리가 생성되며, 그 아래에는 event라는 파일이 새로 생성된 것을 볼 수 있다.

 

 

 

 

 

4. Tensorboard 실행하기

  • 텐서보드 실행방법은 크게 두 가지다.

4.1. Terminal을 이용해서 실행하기

  • 머신러닝을 사용한다면, 서버에 붙어서 사용할 가능성이 높으므로, 주피터 노트북을 기준으로 터미널에 접속하는 방법을 설명하도록 하겠다.

  • New > Terminal을 클릭하면 검은 화면이 뜨게 된다.

  • 이 화면에서 방금 만들었던 텐서보드용 디렉터리를 찾아가면 된다.
  • 위 과정대로 따라왔다면, 자신의 워킹 디렉터리에 만들었을 것이므로, 워킹 디렉터리를 찾아가자
>>> os.getcwd()
'C:\\Jupyter\\Blog\\ML'
  • os.getcwd(): 해당 주피터 파일의 워킹 디렉터리 주소를 반환한다.

  • cd 'C:\\Jupyter\\Blog\\ML'를 위 검은 창에 적으면, 해당 디렉터리로 이동한다.
  • dir 입력 시, 그 디렉터리에 있는 파일들을 모두 가져온다.
  • 디렉터리 안에 텐서보드를 위해 만들었던 Learning_lig 디렉터리가 존재하는 것을 볼 수 있다.
  • 터미널 안에 아래 코드를 입력하자.
tensorboard --logdir=./Learning_log --port=6006

  • 위 사진 같이 글이 뜰 것인데 여기서 http://localhost:6006/ 을 주소창에 쓰거나 클릭하면, 텐서보드로 이동한다.

  • 터미널을 종료해도 텐서보드 서버가 켜졌기 때문에 접속 가능하다.
  • 물론, 컴퓨터를 종료하는 등 텐서보드 서버를 끄는 행동을 한다면, 위 과정을 다시 반복해야 한다.

 

4.2. 주피터 노트북 안에서 텐서보드 실행하기

  • 조금 더 쉽지만, 개인적으로는 선호하지 않는 방법이다(가시성이 좀 떨어진다.)
  • 지금까지 코드를 작성했던, 주피터 노트북 안에 다음 코드를 실행해보자.
%load_ext tensorboard
%tensorboard --logdir=./Learning_log --port=6006

 

 

 지금까지 텐서보드를 사용하기 위해, Log 디렉터리를 생성하고, 텐서보드를 실행하는 방법에 대해 알아보았다. 다음 포스트에서는 텐서보드를 사용해서 훈련 셋(Train set)과 검증 셋(Validation set)의 손실 값(loss)과 정확도(accuracy)를 평가하는 방법에 대해 알아보도록 하자.

728x90
반응형
728x90
반응형

 이전 포스트에서 DataFrame의 생김새와 부위별 이름에 대해 알아보았다. DataFrame의 가장 큰 특징은 index가 존재한다는 것이고, 이 index를 얼마나 잘 가지고 노느냐에 따라 DataFrame을 얼마나 효율적으로 사용할 수 있는지가 나뉘게 된다.

 

 

데이터 프레임과 인덱스

 Pandas에 대해 흔히들 가지고 있는 오해가 Pandas는 순차적으로 데이터를 조회하기 때문에, 데이터 전처리 속도가 매우 느리다는 것이다.

 이 말은 반은 맞고, 반은 틀리다고 할 수 있는데, DataFrame에서 특정 데이터를 단순하게 조회하면, 순서대로 하나하나 조회하기 때문에 속도가 매우 느려지고, 도리어 이 특징을 이용해서, 데이터 전처리 속도를 줄일 수도 있다.

 그러나, 인덱스를 사용하여 조회를 하게 된다면, 순차적 조회가 아닌 한 번에 index에 해당하는 값을 가지고 오기 때문에 조회 속도가 엄청 빨라진다.

 그러므로, Pandas를 사용해야 하는 데이터 분석가라면 반드시 이 인덱스를 잘 가지고 놀 수 있어야 한다.

 

 

 

 

1. DataFrame 생성 시, 원하는 Index 부여하기

  • index는 DataFrame을 생성하면서 부여할 수도 있고, DataFrame을 생성한 후에도 바꿔줄 수 있다.
  • 다음과 같은 데이터 셋을 만들어보자.
import pandas as pd
name_list = ["박명수", "유재석", "노홍철", "길", "정준하", "정형돈", "하하"]
math_list = [65, 95, 70, 80, 100, 85, 60]
english_list = [75, 80, 85, 90, 65, 75, 100]
class_list = [1, 2, 1, 2, 2, 2, 1]
index_list = list(range(10, 24, 2))

exam = pd.DataFrame({"name":name_list, "math":math_list, "english":english_list,
                     "class":class_list}, index=index_list)
exam

  • range(start, end, by): start부터 end까지 값을 by 간격으로 만들 준비를 한다.
    (list 함수 같은 것으로 가동해주지 않으면, 대기 상태를 유지한다)
  • 생성된 데이터 프레임을 보듯, 데이터 프레임 생성 시, index 파라미터에 넣어준 list를 index로 부여한다.

 

 

 

 

2. 내가 원하는 칼럼을 index로 부여하기

  • 이미 index가 정해졌다 할지라도, index는 내가 원하는 칼럼을 index로 만들어줄 수 있다.
exam.set_index("name", inplace=True)
exam

  • DataFrame.set_index(column, drop=True, append=False, inplace=False): set_index()는 말 그대로, 내가 원하는 칼럼을 index로 설정해주는 함수다.
  • drop 파라미터는 기존 칼럼을 index로 넣을 때, 기존 칼럼을 말 그대로 버려버릴지를 묻는 것이다.
  • append 파라미터는 기존 인덱스에 내가 원하는 칼럼까지 추가해서 인덱스를 만들지를 묻는 것이다.
  • inplace 파라미터는 원본 데이터에 덮어씌울지를 물어보는 것이다. 여기서는 inplace = True를 하였으므로, index가 바뀐 것을 바로 원본에 적용하였다.
  • 중복되는 값이라 할지라도 index로 만들 수 있다.
exam.set_index("class", inplace=True)
exam

  • 데이터 프레임의 인덱스에 대해 "중복이 돼서는 안 되는 유니크한 값이어야 한다."라 오해를 하는 경우가 종종 있는데, 위에서 보시다시피 중복이 가능하다. 이는, 데이터 프레임의 인덱스의 특징이며, 이 특징을 잘 이용하는 것이 중요하다.
  • 데이터 프레임의 인덱스는 중복 가능하므로, 조회 시, 해당 인덱스에 해당하는 값을 모두 가지고 온다.
exam.loc[2]

  • DataFrame.loc[index]: index에 해당하는 행을 조회하는 방법으로 추후 자세히 다루도록 하겠다.

 

 

 

 

3. 인덱스 초기화

  • 무한도전 멤버들의 이름을 인덱스로 했었다가 class로 인덱스를 설정하니, 무한도전 멤버들의 이름이 사라져 버렸다.
  • 이는 DataFrame.set_index()는 기본적으로 기존 index를 없애는 것을 전재로 하기 때문이다.
  • 이러한 일을 막기 위해선 set_index()를 하기 전에 인덱스를 초기화해줘야 한다.
exam.reset_index(drop = False, inplace = True)
exam

  • DataFrame.reset_index(drop=False, inplace=False): reset_index는 인덱스를 말 그대로 초기화시켜버린다.
  • drop 파라미터는 기존의 index를 버릴지 말지를 결정하는 파라미터다
  • 무한도전 멤버들의 이름이 index인 상태에서 reset_index()하고 set_index()를 하였다면, 이번처럼 기존 인덱스가 날아가버리는 일을 막을 수 있다.

 

 

 

 

4. 새로운 list를 인덱스로 부여하고 인덱스를 조작해보자

  • 처음에 만들었던, name_list를 index로 부여하여, 무한도전 멤버의 이름이 index인 상태로 돌아가 보자.
exam.index = name_list
exam

  • DataFrame.index: DataFrame의 index를 조작할 수 있다.
  • 이전에는 index의 이름이 name으로 나왔으나, index는 본래 이름이 따로 없기 때문에 index의 이름이 없는 상태로 출력된 것을 볼 수 있다.

4.1. 인덱스 이름 부여

exam.index.name = "name"
exam

  • DataFrame.index는 뒤에 다른 함수들을 붙여 추가적인 기능들을 실행할 수 있다.
  • 이번에는 DataFrame.index.name으로 하여 index의 이름을 부여해보았다.

4.2. 인덱스 데이터 추출

>>> exam.index
Index(['박명수', '유재석', '노홍철', '길', '정준하', '정형돈', '하하'], dtype='object', name='name')
  • list를 index에 넣었듯, array로 index를 뽑아낼 수도 있다.
  • 이러한 index 조작은 데이터 프레임을 다룰 때, 상당히 쓸모 있는 기술이므로 꼭 숙지하도록 하자.

 

 

 

 

5. 멀티 인덱스(Multi index)와 인덱스를 기준으로 정렬

  • 멀티 인덱스는 말 그대로 index를 한 번에 여러 개 사용하는 방법이다.
  • 무한도전 멤버의 이름을 칼럼으로 되돌리고, class, name으로 멀티 인덱싱을 해보자.
exam.reset_index(drop=False, inplace=True)
exam.set_index(["class", "name"], inplace = True)
exam

  • 멀티 인덱스는 set_index(column)에서 칼럼을 여러 개 넣어서 실시할 수 있다.
  • 멀티 인덱스를 하고 나니, class 2의 길, 정준하, 정형돈의 class가 하나로 붙어 깔끔하게 나온 것을 볼 수 있다.
  • 그러나, 썩 깔끔해 보이지 않으므로 인덱스를 기준으로 정렬시켜보자.

5.1. 인덱스 기준 정렬

exam.sort_index(inplace=True)
exam

  • DataFrame.sort_index(ascending=True, inplace=False): 말 그대로 index순으로 정렬(sort)한다.
  • ascending 파라미터는 오름차순, 내림차순을 의미하며, 기본적으로 오름차순 정렬이 실행된다.

5.2. 멀티 인덱스의 실제 모습

  • 멀티 인덱스를 실행하게 되면, 데이터의 형태를 보다 쉽게 파악할 수 있다는 장점이 있다. 때문에 멀티 인덱스는 group_by를 하여 두 집단 이상의 기술 통계량을 볼 때, 멀티 인덱스로 출력되게 된다.
  • 멀티 인덱스를 하면, class는 하나의 index로 뭉친 것처럼 보이는데, 눈에만 저렇게 보이지 실제로는 따로 떨어져 있다.
>>> exam.index
MultiIndex([(1, '노홍철'),
            (1, '박명수'),
            (1,  '하하'),
            (2,   '길'),
            (2, '유재석'),
            (2, '정준하'),
            (2, '정형돈')],
           names=['class', 'name'])
  • exam의 index를 보면, 튜플로 묶여 있는 것을 볼 수 있다.

5.3. 멀티 인덱스 해제

exam.reset_index(drop=False)

  • 멀티 인덱스 해제는 reset_index()를 하여 쉽게 할 수 있다.

 

 

 

 지금까지 판다스에서 인덱스를 가지고 노는 법에 대해 학습해보았다. 판다스에서 인덱스를 얼마나 잘 활용하느냐에 따라 판다스의 속도는 크게 변하므로, 인덱스를 잘 활용하길 바란다.

728x90
반응형
728x90
반응형

 이전 포스트에서 데이터 프레임을 만들어보았으니, 이번에는 데이터 프레임의 부위별 이름을 알아보자.

 

 

데이터 프레임의 구조와 용어 정리

  • 위 표는 우리가 지금까지 봐온 데이터 프레임이다. 데이터 프레임의 생김새는 우리가 자주 봐왔던 표(Table)처럼 생겼다.
  • Data를 사용하는 분야에서 기본적으로 사용하는 관계형 데이터베이스(Relational Database, RDM)의 테이블(Table)도 데이터 프레임과 동일하게 생겼다.
  • RDB를 여기저기서 많이 사용하다 보니, 그 용어를 데이터 프레임에도 사용하는 경우가 많기 때문에, RDB에서 사용하는 용어도 함께 정리하도록 하겠다.

 

 

 

 

1. 칼럼(Column)

  • 칼럼(Column)은 단어 그대로 을 의미하며, 행과 열을 가진 데이터에서 열은 모두 칼럼이라고 불린다.
  • 변수(Variable), 속성(Attribute), 필드(Field)라고도 불린다.
  • 데이터에서 객체(대상)가 가지고 있는 특징이 들어간다.
  • 예를 들어, 이름, 성별, 나이와 같은 대상 집단이 가지고 있는 특징이다.
  • 차수(Degree): RDB에서는 칼럼의 수를 가리킨다.
    예) 위 테이블의 Degree는 11이다.
  • 도메인(Domain): RDB에서 한 칼럼에서 가질 수 있는 값의 집합
    예) 성별의 도메인은 "남", "여" 둘 뿐이며, 그 외의 값은 들어갈 수 없다.

 

 

 

 

2. 로우(Row)

  • 말 그대로 행을 가리키며, 데이터 하나하나를 의미한다. 그러다 보니, 모양이 데이터 프레임과 조금 다르더라도, 데이터 하나하나를 지칭할 때, 단순하게 Row라고 부르기도 한다.
  • 튜플(Tuple), 레코드(Record)라고도 불린다.
  • 데이터의 대상이 되는 객체 하나하나를 의미한다.
  • 예를 들어, 위 데이터 프레임에서 index가 417번인 사람은, 고객번호(PassengerId)가 1309번이고, 이름이 Peter, Master. Michael J이며, 성별이 male인 사람이다.
  • 카디널리티(Cardinality): 행의 총 개수이다.

 

 

 

 

3. 인덱스(Index)

  • 데이터 프레임만의 특징으로, index는 중복될 수도 있고, 동일한 index에 해당하는 row의 값이 다를 수도 있다.
  • index를 어떻게 조작하느냐에 따라, 데이터 프레임에서의 데이터 조회 속도 차이가 엄청 커진다.
  • RDB의 key와 유사해 보이지만, RDB의 key는 고유 개체를 식별할 수 있는 유니크한 값임에 반해, 데이터 프레임의 index는 중복이 가능하고, index가 동일하나, row에 있는 값은 다를 수 있으므로, key와 굉장히 다르다.

 

 

 

 지금까지 데이터 프레임의 행, 열의 다른 용어와 그것이 의미하는 바에 대해 간단히 다뤄봤다. 위 단어 외에도 인스턴스(Instance), 엔티티(Entity)와 같은 다양한 단어가 있으나, 데이터 프레임이던 테이블이던 기본적으로 행과 열이 중심이기 때문에 위 용어만 알면, 데이터에 대한 기본적인 의사소통은 할 수 있을 것이다.

 다음 포스트에서는 index를 가지고 노는 방법에 대해 학습해보도록 하겠다.

728x90
반응형
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
반응형

 이전 포스트까지 모델 컴파일에 대해 알아보았다. 이번 포스트에서는 모델을 학습에 대해 자세히 알아보도록 하자.

 

 

모델 학습과 학습 과정 확인

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. 모델 학습

# 모델 학습하기
history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size = 5000,
                    validation_data=(valid_images, valid_labels))
Epoch 1/100
11/11 [==============================] - 2s 185ms/step - loss: 1.5537 - accuracy: 0.4795 - val_loss: 0.4806 - val_accuracy: 0.8638
Epoch 2/100
11/11 [==============================] - 1s 52ms/step - loss: 0.3852 - accuracy: 0.8864 - val_loss: 0.2727 - val_accuracy: 0.9204
Epoch 3/100
11/11 [==============================] - 1s 53ms/step - loss: 0.2276 - accuracy: 0.9327 - val_loss: 0.1879 - val_accuracy: 0.9406
Epoch 4/100
11/11 [==============================] - 1s 53ms/step - loss: 0.1617 - accuracy: 0.9522 - val_loss: 0.1460 - val_accuracy: 0.9558
Epoch 5/100
11/11 [==============================] - 1s 52ms/step - loss: 0.1213 - accuracy: 0.9650 - val_loss: 0.1222 - val_accuracy: 0.9618

...

Epoch 96/100
11/11 [==============================] - 1s 54ms/step - loss: 2.8528e-05 - accuracy: 1.0000 - val_loss: 0.1078 - val_accuracy: 0.9804
Epoch 97/100
11/11 [==============================] - 1s 59ms/step - loss: 2.9069e-05 - accuracy: 1.0000 - val_loss: 0.1080 - val_accuracy: 0.9806
Epoch 98/100
11/11 [==============================] - 1s 56ms/step - loss: 2.7108e-05 - accuracy: 1.0000 - val_loss: 0.1082 - val_accuracy: 0.9806
Epoch 99/100
11/11 [==============================] - 1s 51ms/step - loss: 2.8243e-05 - accuracy: 1.0000 - val_loss: 0.1086 - val_accuracy: 0.9806
Epoch 100/100
11/11 [==============================] - 1s 50ms/step - loss: 2.6565e-05 - accuracy: 1.0000 - val_loss: 0.1086 - val_accuracy: 0.9804
  • 이전 포스팅까지는 model.fit(train_set, train_label, epochs)만 설정하였었으나, 이번 포스팅에서는 model.fit(train_set, train_label, epochs, batch_size, validation_data)로 처음 보는 인자들이 여럿 등장한 것을 볼 수 있다.
  • train_set, train_label, epochs는 여러 번 본 인자이므로 넘어가고, batch_size와 validation_data를 위주로 설명해보겠다.
  • 아직 epochs에 대한 개념이 헷갈린다면 다음 포스팅을 참고하기 바란다.
    참고: "머신러닝-6.2. 최적화(3)-학습단위(Epoch, Batch size, Iteration)

 

 

 

 

2. Batch size

  • model.fit() 함수 안에 batch_size라는 인자가 추가된 것을 볼 수 있다.
  • batch_size는 전체 데이터셋을 한 번에 학습시키자니, 데이터가 너무 크기 때문에 메모리 과부하와 속도 저하 문제가 발생하므로, 데이터를 Batch size만큼 쪼개서 학습을 시키는 것을 의미한다.
  • 학습 단위에 대한 보다 자세한 설명은 다음 포스팅을 참고하기 바란다.
  • Batch size는 전체 데이터셋을 Batch size로 나눴을 때, 나머지가 생기지 않은 크기로 만드는 것이 좋다.
  • Batch size를 너무 크게 하면, 메모리 과부하가 발생하기 쉬우나, 더 많은 데이터를 보고 파라미터를 결정하므로 학습이 안정적으로 진행된다.
  • Batch size를 너무 작게 하면, 자주 파라미터 갱신이 발생하므로 학습이 불안정해진다.
  • Batch size를 얼마나 잡느냐에 대해선 정답이 없다고 할 수 있는데, 어떤 사람들은 자신의 머신이 가진 메모리를 넘지 않는 선에서 Batch size를 최대로 잡는 것이 좋다고 하고, 또 다른 사람은 32보다 큰 미니배치를 사용해선 절대 안된다고도 했다.
  • 양쪽 다 주장이 꽤 탄탄하므로, 미니 배치를 크게도 해보고 작게도 해보며, 결과를 비교해보도록 하자.

 

2.1. Batch size의 효과

  • Batch size는 학습에 걸리는 시간과 학습에 소모되는 메모리에 큰 영향을 미친다.
  • 얼마나 차이가 나는지 확인하기 위해 Batch size를 설정하지 않은 학습과 설정하지 않은 학습을 비교해보도록 하자.

 A. Batch size를 지정하지 않는 경우

from time import time
start = time()

history = model.fit(train_images, train_labels,
                    epochs=100,
                    validation_data=(valid_images, valid_labels))
...
>>> print("코드 동작 시간: {} minutes".format(round((time() - start)/60, 2)))
코드 동작 시간: 6.39 minutes
  • time 모듈의 time() 함수는 1970년 1월 1일 0시 0분 0초 이후 경과한 시간을 초 단위로 반환하는 함수로, time함수의 차를 이용해서, 특정 구간에서 소모된 시간을 알 수 있다.
  • Batch size를 사용하지 않고, 모든 데이터를 한 번에 학습시키는 경우, 6.39분이 소모된 것을 볼 수 있다.

B. Batch size를 지정한 경우

start = time()

history = model.fit(train_images, train_labels,
                    epochs=100,
                    batch_size=5000,
                    validation_data=(valid_images, valid_labels))
...
>>> print("코드 동작 시간: {} minutes".format(round((time.time() - start)/60, 2)))
코드 동작 시간: 1.0 minutes
  • Batch size를 사용하자, 학습에 소모된 시간이 1.0분으로 6배 이상 감소한 것을 볼 수 있다.
  • 이번에 학습에 사용한 MNIST는 그다지 큰 데이터도 아님에도 소모 시간 차이가 이렇게 크게 발생한 것을 볼 때, 이보다 더 큰 데이터를 다루게 되는 실제 상황에서 Batch size 지정은 필수임을 알 수 있다.

 

 

 

 

3. validation data

  • 이전 학습에서 validation_data를 지정하지 않았듯, 검증 셋을 지정하지 않아도 학습을 하는 데는 문제가 없지만, 검증 셋이 존재한다면, 매 학습 때마다 모델을 평가하여, 최적의 모델을 만들어내는데 큰 도움이 된다.
  • 검증 셋에 대해 헷갈리는 분을 위해 이전 포스트 링크를 걸어놓도록 하겠다.
    (참고: "Tensorflow-3.2. 이미지 분류 모델(2)-검증 셋(Validation set)")
  • validation data는 위 방법처럼 검증 셋을 미리 뽑고, 학습을 진행하는 방법도 있지만 자동으로 검증 셋을 뽑아놓는 방법도 존재한다.
  • model.fit() 함수의 파라미터에 validation_split이라는 인자가 존재하는데, 이는 float으로 지정할 수 있으며, 데이터를 섞기(Shuffle) 전에 지정한 비율만큼의 데이터를 검증 셋으로 사용한다.
  • 그러나, 참고 포스팅에서 보듯 검증 셋을 대표성 있게 추출하는 것은 매우 중요하므로, 사전에 검증 셋을 미리 추출하는 것을 추천한다.
  • 이밖에도 validation_steps, validation_batch_size, validation_freq와 같은 검증 셋 관련 파라미터들이 더 존재하지만, 이들에 대해서는 추후 다루도록 하겠다.

 

 

 

 

 지금까지 모델의 학습(Fit)에 대해 알아보았다. 이전에 fit()에 사용했던 파라미터들은 학습을 위해 필요한 최소한의 파라미터들이었다면, 이번에 사용한 파라미터들은 가장 일반적으로 사용되는 파라미터들이다. 

 fit() 함수는 이밖에도 샘플별 가중치 조정(sample_weight)이나, 특정 클래스에 대한 가중치 조정(class_weight)과 같은 다양한 기능들을 더 가지고 있다. 그러나, 이들까지 모두 다루기는 쉽지 않고, 이번에 다룬 내용만 알더라도 Tensorflow를 적당히 다루는데 지장이 없으므로, 여기까지 학습을 하고, 나중에 필요하다면 더 자세히 다뤄보도록 하겠다.

 다음 포스트에선 학습 과정에서 나온 Log들을 분석하는 History 객체의 사용법에 대해 학습해보도록 하겠다.

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
반응형

+ Recent posts