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

 지금까지 이미지 분류 모델을 만들고, 학습까지 시켜보았다. 지난 포스트에서는 학습 과정을 보며, 학습이 제대로 이루어졌는지를 평가하고, 최적의 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
반응형

 이전 포스트에서는 범주형 데이터들을 원-핫 벡터로 바꿔서 성능 향상을 이뤄봤다. 그러나 Accuracy 0.78 > 0.79667이라는 기대에 미치지 못하는 성능 향상이 일어났다.

 이번에는 하이퍼 파라미터 튜닝을 하여, 성능을 보다 올려보도록 하겠다.

 

 

하이퍼 파라미터 튜닝

  • 하이퍼 파라미터 튜닝이 무엇인지 이전 포스트(참고)에서 살짝 다뤄보았다.
  • 이전 포스트에서 원-핫 벡터를 사용한, 데이터 셋을 만들었으나, 그 성능이 생각보다 크지 않았다.
  • 데이터 셋의 상태는 실제로 더 좋아졌으나, 적절한 하이퍼 파라미터나, 적합한 모델을 만들지 못해서 발생한 문제일 수 있다.
  • 이번엔 하이퍼 파라미터를 하나하나 잡아보도록 하자.

 

 

0. 학습 이전까지 코드 정리

# Import Module
import pandas as pd
import numpy as np
import os
from tensorflow.keras.layers import Dense
from tensorflow import keras
from copy import copy
# 필요한 Data를 모두 가져온다.
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


# Rawdata 생성
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 remove_columns(DF, remove_list):
    
    # 원본 정보 유지를 위해 copy하여, 원본 Data와의 종속성을 끊었다.
    result = copy(Rawdata)

    # PassengerId를 Index로 하자.
    result.set_index("PassengerId", inplace = True)

    # 불필요한 column 제거
    for column in remove_list:

        del(result[column])
        
    return result


# 결측값 처리
def missing_value(DF):

    # Cabin 변수를 제거하자
    del(DF["Cabin"])
    
    # 결측값이 있는 모든 행은 제거한다.
    DF.dropna(inplace = True)
    
    
# 원-핫 벡터
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
# Data Handling
############ Global Parameter ############
file_path = "./Dataset"
remove_list = ["Name", "Ticket"]
##########################################


# 0. Rawdata 생성
Rawdata_dict = import_Data(file_path)
Rawdata = make_Rawdata(Rawdata_dict)


# 1. 필요 없는 column 제거
DF_Hand = remove_columns(Rawdata, remove_list)


# 2. 결측값 처리
missing_value(DF_Hand)


# 3. One-Hot encoding
one_hot_Encoding(DF_Hand, 'Pclass')
one_hot_Encoding(DF_Hand, 'Sex')
one_hot_Encoding(DF_Hand, 'Embarked')


# 4. 데이터 쪼개기
# Label 생성
y_test, y_train = DF_Hand["Survived"][:300].to_numpy(), DF_Hand["Survived"][300:].to_numpy()


# 5. Dataset 생성
del(DF_Hand["Survived"])
X_test, X_train = DF_Hand[:300].values, DF_Hand[300:].values


# 6. 특성 스케일 조정
X_test, X_train = scale_adjust(X_test, X_train, 0, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 3, key="min_max")
# 모델 생성
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(1, activation = "sigmoid"))

# 모델 Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt,
              loss = "binary_crossentropy",
              metrics=["binary_accuracy"])

 

 

 

 

1. 적절한 Epochs 잡기

  • 혹시 과적합(Overfitting)이 발생한 것일지도 모르니 손실 값의 추이를 보자.
  • 모델은 적합한 epochs를 넘어 학습하게 된다면, train Dataset에 지나치게 맞춰져서, Test set을 제대로 분류하지 못하는 문제가 발생할 수 있다.
>>> model.fit(X_train, y_train, epochs = 500)

Epoch 95/500
24/24 [==============================] - 0s 997us/step - loss: 0.2396 - binary_accuracy: 0.8955
Epoch 96/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2528 - binary_accuracy: 0.8892
Epoch 97/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1841 - binary_accuracy: 0.9220
Epoch 98/500
24/24 [==============================] - 0s 997us/step - loss: 0.2407 - binary_accuracy: 0.8902
Epoch 99/500
24/24 [==============================] - 0s 911us/step - loss: 0.2251 - binary_accuracy: 0.8925
Epoch 100/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2491 - binary_accuracy: 0.8909

...

Epoch 195/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1727 - binary_accuracy: 0.9196
Epoch 196/500
24/24 [==============================] - 0s 997us/step - loss: 0.1872 - binary_accuracy: 0.9266
Epoch 197/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1782 - binary_accuracy: 0.9232
Epoch 198/500
24/24 [==============================] - 0s 954us/step - loss: 0.2125 - binary_accuracy: 0.9080
Epoch 199/500
24/24 [==============================] - 0s 867us/step - loss: 0.1910 - binary_accuracy: 0.9235
Epoch 200/500
24/24 [==============================] - 0s 954us/step - loss: 0.2054 - binary_accuracy: 0.9145

...

Epoch 296/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1784 - binary_accuracy: 0.9128
Epoch 297/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2052 - binary_accuracy: 0.9170
Epoch 298/500
24/24 [==============================] - 0s 997us/step - loss: 0.2241 - binary_accuracy: 0.9151
Epoch 299/500
24/24 [==============================] - 0s 996us/step - loss: 0.2345 - binary_accuracy: 0.9055
Epoch 300/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2218 - binary_accuracy: 0.9008

...

Epoch 395/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1688 - binary_accuracy: 0.9271
Epoch 396/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1840 - binary_accuracy: 0.9213
Epoch 397/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1711 - binary_accuracy: 0.9204
Epoch 398/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1995 - binary_accuracy: 0.9167
Epoch 399/500
24/24 [==============================] - 0s 1ms/step - loss: 0.2236 - binary_accuracy: 0.9166
Epoch 400/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1880 - binary_accuracy: 0.9280

...

Epoch 495/500
24/24 [==============================] - 0s 954us/step - loss: 0.1608 - binary_accuracy: 0.9270
Epoch 496/500
24/24 [==============================] - 0s 997us/step - loss: 0.1601 - binary_accuracy: 0.9334
Epoch 497/500
24/24 [==============================] - 0s 954us/step - loss: 0.1428 - binary_accuracy: 0.9540
Epoch 498/500
24/24 [==============================] - 0s 998us/step - loss: 0.1522 - binary_accuracy: 0.9360
Epoch 499/500
24/24 [==============================] - 0s 1ms/step - loss: 0.1431 - binary_accuracy: 0.9410
Epoch 500/500
24/24 [==============================] - 0s 911us/step - loss: 0.1581 - binary_accuracy: 0.9440
<tensorflow.python.keras.callbacks.History at 0x1e9a8427790>
  • 손실 값의 추이를 보니, 거의 차이가 없는 것을 볼 수 있다.
  • 과적합이 의심되므로, epochs를 100으로 줄여서 다시 학습해보자.
>>> model.fit(X_train, y_train, epochs = 100)

Epoch 1/100
24/24 [==============================] - 1s 1ms/step - loss: 0.5505 - binary_accuracy: 0.7738
Epoch 2/100
24/24 [==============================] - 0s 1ms/step - loss: 0.3769 - binary_accuracy: 0.8455
Epoch 3/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3293 - binary_accuracy: 0.8878
Epoch 4/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3400 - binary_accuracy: 0.8485
Epoch 5/100
24/24 [==============================] - 0s 1ms/step - loss: 0.3340 - binary_accuracy: 0.8688

...

Epoch 96/100
24/24 [==============================] - 0s 963us/step - loss: 0.2319 - binary_accuracy: 0.9013
Epoch 97/100
24/24 [==============================] - 0s 1ms/step - loss: 0.2237 - binary_accuracy: 0.9102
Epoch 98/100
24/24 [==============================] - 0s 953us/step - loss: 0.2326 - binary_accuracy: 0.9151
Epoch 99/100
24/24 [==============================] - 0s 997us/step - loss: 0.2412 - binary_accuracy: 0.8963
Epoch 100/100
24/24 [==============================] - 0s 1ms/step - loss: 0.2346 - binary_accuracy: 0.9002
<tensorflow.python.keras.callbacks.History at 0x119d9265790>
>>> pred = model.predict(X_test).reshape(X_test.shape[0])
>>> pred = np.where(pred > 0.5, 1, 0)
>>> accuracy = 1 - (np.where((pred - y_test) == 0, 0, 1).sum()/len(y_test))
>>> print("Accuracy:", accuracy)

Accuracy: 0.81
  • epochs만 100으로 줄였을 뿐인데, Accuracy가 0.79667에서 0.81로 상승하였다.

 

 

 

 

2. 적절한 모델 만들기

  • 연구자의 감에 의존하는 부분이라고도 할 수 있으나, 적절한 모델 작성은 전혀 다른 결과를 가져온다.
  • 이번엔 DropoutBatchnormalization을 추가하여 모델을 학습시켜보자.
  • 위 두 내용은 꽤 심도 깊은 내용이므로 추후 자세히 설명하겠으나, 이번엔 아주 간략하게 설명하고 넘어가 보겠다.

 

Dropout

  • Dropout은 Overfitting, model combination 문제를 해결하기 위해 등장한 개념으로, 신경망의 뉴런을 랜덤 하게 부분적으로 생략시킨다.
  • 간단하게 말하자면, 신경망을 망각시킨다고 생각하는 게 좋다.

 

Bachnormalization:

  • 배치 정규화라 불린다.
  • 활성화 함수의 활성화 값이나 출력 값을 정규분포로 만들어줘, Noise를 추가하는 개념으로, 학습을 할 때마다 활성화 값이나 출력 값을 정규화하므로, 초기화(가중치 초깃값) 문제의 영향을 덜 받게 해 준다.
  • 학습률(Learning Rate)을 높게 설정할 수 있으므로, 학습 속도가 개선된다.
  • Overfitting 위험을 줄일 수 있다.
  • 가중치 소실(Gradient Vanishing) 문제를 해결해준다.

 

# module 추가 Import
from tensorflow.keras.layers import (Dense, Dropout, BatchNormalization)
# 모델 생성
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(1, activation = 'sigmoid'))

# 모델 Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt,
              loss = "binary_crossentropy",
              metrics=["binary_accuracy"])
>>> model.fit(X_train, y_train, epochs = 100)

Epoch 1/100
24/24 [==============================] - 1s 2ms/step - loss: 0.6060 - binary_accuracy: 0.6829
Epoch 2/100
24/24 [==============================] - 0s 2ms/step - loss: 0.4085 - binary_accuracy: 0.8484
Epoch 3/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3800 - binary_accuracy: 0.8719
Epoch 4/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3713 - binary_accuracy: 0.8580
Epoch 5/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3626 - binary_accuracy: 0.8666
Epoch 6/100

...

Epoch 96/100
24/24 [==============================] - 0s 1ms/step - loss: 0.3144 - binary_accuracy: 0.8592
Epoch 97/100
24/24 [==============================] - 0s 2ms/step - loss: 0.3060 - binary_accuracy: 0.8716
Epoch 98/100
24/24 [==============================] - 0s 2ms/step - loss: 0.2887 - binary_accuracy: 0.8794
Epoch 99/100
24/24 [==============================] - 0s 1ms/step - loss: 0.2879 - binary_accuracy: 0.8695
Epoch 100/100
24/24 [==============================] - 0s 2ms/step - loss: 0.2805 - binary_accuracy: 0.8969
<tensorflow.python.keras.callbacks.History at 0x1fa4c5b0220>
>>> pred = model.predict(X_test).reshape(X_test.shape[0])
>>> pred = np.where(pred > 0.5, 1, 0)
>>> accuracy = 1 - (np.where((pred - y_test) == 0, 0, 1).sum()/len(y_test))
>>> print("Accuracy:", accuracy)

Accuracy: 0.8200000000000001
  • 성능이 소폭 상승하긴 하였으나, 손실 값이 이전보다 떨어진 폭이 작다.
  • 혹시나, 손실 값을 더 떨어뜨릴 수 있을지도 모르니, epochs를 올려보자.
  • Dropout과 Batchnormalization 둘 모두 overfitting 문제를 해결해주므로, 현재 필요한 epochs보다 낮은 상태일 가능성이 있다.
>>> model.fit(X_train, y_train, epochs = 200)

Epoch 1/200
24/24 [==============================] - 1s 2ms/step - loss: 0.6264 - binary_accuracy: 0.6648
Epoch 2/200
24/24 [==============================] - 0s 2ms/step - loss: 0.4572 - binary_accuracy: 0.8413
Epoch 3/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3927 - binary_accuracy: 0.8649
Epoch 4/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3488 - binary_accuracy: 0.8736
Epoch 5/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3500 - binary_accuracy: 0.8776

...

Epoch 196/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2589 - binary_accuracy: 0.8917
Epoch 197/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3108 - binary_accuracy: 0.8813
Epoch 198/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2486 - binary_accuracy: 0.9010
Epoch 199/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2808 - binary_accuracy: 0.9001
Epoch 200/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2638 - binary_accuracy: 0.9069
<tensorflow.python.keras.callbacks.History at 0x1b772b801f0>
>>> pred = model.predict(X_test).reshape(X_test.shape[0])
>>> pred = np.where(pred > 0.5, 1, 0)
>>> accuracy = 1 - (np.where((pred - y_test) == 0, 0, 1).sum()/len(y_test))
>>> print("Accuracy:", accuracy)
Accuracy: 0.8266666666666667
  • 정확도가 0.82667로 소폭 성능이 상승한 것을 볼 수 있다.

 

 

 

 

3. 최종 코드

  • 결측 값이 가장 많은 연령에 대하여, 평균 대체, 중윗값 대체, 사용하지 않음(Default)이 가능하게 코드를 수정하였다.
  • 변수 SibSp와 Parch는 등간 척도이므로, 연속형 척도이나, 표준화를 하지 않았다.
  • 그 값의 편차가 매우 작으므로, 미치는 영향은 작다고 판단되지만, 통일성을 위해 스케일 조정을 해주었다.
# 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 ######################################
# 필요한 Data를 모두 가져온다.
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


# Rawdata 생성
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 remove_columns(DF, remove_list):
    
    # 원본 정보 유지를 위해 copy하여, 원본 Data와의 종속성을 끊었다.
    result = copy(Rawdata)

    # PassengerId를 Index로 하자.
    result.set_index("PassengerId", inplace = True)

    # 불필요한 column 제거
    for column in remove_list:

        del(result[column])
        
    return result


# 결측값 처리
def missing_value(DF, key=None):

    # Cabin 변수를 제거하자
    del(DF["Cabin"])
    
    if key == "mean":
        DF["Age"] = DF["Age"].fillna(np.mean(DF["Age"]))
        
    elif key == "median":
        DF["Age"] = DF["Age"].fillna(np.median((DF["Age"].dropna())))
    
    # 결측값이 있는 모든 행은 제거한다.
    DF.dropna(inplace = True)
    
    
# 원-핫 벡터
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
######################################################################################






################################## Global Variable ###################################
file_path = "./Dataset"
remove_list = ["Name", "Ticket"]
######################################################################################
# Data Handling
# 0. Rawdata 생성
Rawdata_dict = import_Data(file_path)
Rawdata = make_Rawdata(Rawdata_dict)


# 1. 필요 없는 column 제거
DF_Hand = remove_columns(Rawdata, remove_list)


# 2. 결측값 처리
missing_value(DF_Hand)


# 3. One-Hot encoding
one_hot_Encoding(DF_Hand, 'Pclass')
one_hot_Encoding(DF_Hand, 'Sex')
one_hot_Encoding(DF_Hand, 'Embarked')


# 4. 데이터 쪼개기
# Label 생성
y_test, y_train = DF_Hand["Survived"][:300].to_numpy(), DF_Hand["Survived"][300:].to_numpy()


# 5. Dataset 생성
del(DF_Hand["Survived"])
X_test, X_train = DF_Hand[:300].values, DF_Hand[300:].values


# 6. 특성 스케일 조정
X_test, X_train = scale_adjust(X_test, X_train, 0, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 1, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 2, key="min_max")
X_test, X_train = scale_adjust(X_test, X_train, 3, key="min_max")
######################################################################################






######################################## 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(1, activation = 'sigmoid'))

# 모델 Compile
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt,
              loss = "binary_crossentropy",
              metrics=["binary_accuracy"])
######################################################################################
>>> model.fit(X_train, y_train, epochs = 200)

Epoch 1/200
24/24 [==============================] - 1s 2ms/step - loss: 0.6264 - binary_accuracy: 0.6648
Epoch 2/200
24/24 [==============================] - 0s 2ms/step - loss: 0.4572 - binary_accuracy: 0.8413
Epoch 3/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3927 - binary_accuracy: 0.8649
Epoch 4/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3488 - binary_accuracy: 0.8736
Epoch 5/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3500 - binary_accuracy: 0.8776

...

Epoch 196/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2589 - binary_accuracy: 0.8917
Epoch 197/200
24/24 [==============================] - 0s 2ms/step - loss: 0.3108 - binary_accuracy: 0.8813
Epoch 198/200
24/24 [==============================] - 0s 2ms/step - loss: 0.2486 - binary_accuracy: 0.9010
Epoch 199/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2808 - binary_accuracy: 0.9001
Epoch 200/200
24/24 [==============================] - 0s 1ms/step - loss: 0.2638 - binary_accuracy: 0.9069
<tensorflow.python.keras.callbacks.History at 0x1b772b801f0>
>>> test_loss, test_acc = model.evaluate(X_test, y_test, verbose = 2)
>>> print("Accuracy:", np.round(test_acc, 5))
10/10 - 0s - loss: 0.7177 - binary_accuracy: 0.8200
Accuracy: 0.82
  • model.evaluate(test_set, test_label, verbose=2): 위에서 직접 만들었던, Accuracy를 한 줄의 코드로 추출 가능하다.
  • test_loss는 손실 값(loss)을 의미한다.
  • test_acc는 정확도(accuracy)를 의미한다.

 

 

 지금까지 가장 기본적인 방법을 사용해서 타이타닉 데이터를 이진 분류해보았다. 최초 정확도가 0.78이 나왔으나, 최종적으로는 0.82가 나왔다.

 타이타닉 데이터를 사용한 생존자 분류 모델의 정확도는 기본적으로 Accuracy = 0.8을 기준으로 하며, 이를 얼마나 더 잘 모델링하느냐에 따라 그 결과가 Accuracy = 1.0까지 나오기도 한다.

 이보다 더 좋은 해결 방법을 참고하고자 한다면, 캐글에서 다른 사람들의 코드를 참고해보는 것도 좋은 생각이다. 그러나, 개인적으로는 이 내용을 기반으로, 더 발전시켜보기를 바란다.

 다음 포스팅에서는 타이타닉 데이터를 사용해서 더 많은 작업을 해보도록 하겠다.

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

인공신경망(ANN)과 다층 퍼셉트론(MLP)

 이전 포스트에서 단층 퍼셉트론이 행렬 연산을 통해 다음 노드로 정보를 전달하는 과정을 살펴보았다. 이전에 학습했었던 퍼셉트론과의 차이점은 활성화 함수로 계단 함수가 아닌 시그노이드 함수를 사용한 것이다.

 이렇게 활성화 함수에 정보를 전달하느냐 마느냐인 계단 함수를 넣는 것이 아니라, 시그노이드, 소프트맥스, 하이퍼볼릭 탄젠트, 렐루 등 다양한 활성화 함수를 넣고, 단층 퍼셉트론이 아닌, 다층 퍼셉트론을 만들고, 가중치를 인간이 수동으로 만드는 것이 아닌, 자동으로 가장 적합한 값을 찾아내는 것이 바로 인공 신경망이다.

 

 

 

 

1. 신경망의 구조

  • "입력층 - 출력층"만 존재하는 단층 퍼셉트론(Single Layer Perceptron, SLP)과 달리 "입력층 - m개의 은닉층 - 출력층"이 존재하는 다층 퍼셉트론(Multi Layer Perceptron) "n-층 신경망"이라고도 부르며, 일반적으로 단층 퍼셉트론처럼 입력층을 제외하고 부르거나, 입력층을 0층으로 생각하고 "n = m + 1"로 은닉층의 개수 + 출력층 1개로 명명한다.
  • 즉, 아래 다층 퍼셉트론은 3-층 신경망이라고 부른다.
  • 이렇게 은닉층이 2개 이상인 신경망을 심층 신경망(Deep Neural Network)라 한다.
  • 그러나, 간혹 다른 책에선 입력층까지 포함하여 m+2 층 신경망, 아래 예시에선 4-층 신경망이라 부르는 경우도 있다. 본, 블로그에서는 "은닉층의 수(m) + 출력층 1개"인 m+1층 신경망이라 부르도록 하겠다.

  • 입력층(Input Layer): 
    파란색 노드로, 학습 데이터셋(Train dataset)이 입력되는 곳이다. 학습 데이터의 Feature의 차원 수만큼의 뉴런 개수를 가진다. 입력층은 단 한층만 존재한다.

  • 은닉층(Hidden Layer):
    연두색 노드로, 입력층과 출력층 사이의 모든 층이다. 은닉층이라 불리는 이유는 입력층과 출력층은 Input 되는 Dataset과 Output 된 Dataset을 눈으로 확인할 수 있지만, 은닉층은 보이지 않기 때문이다.

  • 출력층(Output Layer):
    주황색 노드로, 출력하고자 하는 데이터의 형태에 따라 노드의 수가 바뀐다. 예를 들어, 0부터 9까지 10 종류의 숫자가 있다고 할 때, 이를 분류하고자 한다면, 출력층의 노드 수는 10개가 되며, 시그모이드 함수를 사용하여, 이진 분류를 하고자 하는 경우엔 노드의 수가 1개가 된다.

 

 

 

 

2. 다층 퍼셉트론의 연산

  • 다층 퍼셉트론의 연산 방식은 앞서 다뤘던 단층 퍼셉트론의 연산 방식과 동일하나, 더 많이 실시하게 된다.
  • 가중치를 알아서 찾아내는 방식은 뒤에서 다루도록 하고, 이번에는 가중치를 임의로 만들어보자.

$$ X = (x_1, x_2, x_3) = (10, 2) $$

$$ W_1=
\begin{pmatrix}
w_{11}^{(1)} & w_{21}^{(1)} & w_{31}^{(1)}\\ 
w_{12}^{(1)} & w_{22}^{(1)} & w_{32}^{(1)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 & 0.3 & 0.5\\ 
0.6 & 0.4 & 0.2 
\end{pmatrix} $$

$$W_2=
\begin{pmatrix}
w_{11}^{(2)} & w_{21}^{(2)}\\ 
w_{12}^{(2)} & w_{22}^{(2)}\\ 
w_{13}^{(2)} & w_{23}^{(2)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 & 0.2\\ 
0.2 & 0.4\\
0.3 & 0.6
\end{pmatrix}$$

$$W_3=
\begin{pmatrix}
w_{11}^{(3)} & w_{21}^{(3)}\\ 
w_{12}^{(3)} & w_{22}^{(3)}\\ 
\end{pmatrix}
=
\begin{pmatrix}
0.2 & 0.4\\ 
0.4 & 0.8
\end{pmatrix}$$

$$B_1 = (b_{1}^{(1)}, b_{2}^{(1)}, b_{3}^{(1)})=(0.7, 0.6, 0.5)$$

$$B_2 = (b_{1}^{(2)}, b_{2}^{(2)})=(0.6, 0.4)$$

$$B_3 = (b_{1}^{(3)}, b_{2}^{(3)})=(0.5, 0.6)$$

  • 활성화 함수는 은닉층에서는 렐루 함수를 사용하고, 출력층에서는 시그모이드 함수를 사용해보자.
  • 즉, $f()$는 렐루 함수, $h()$는 시그모이드 함수이다.

 

 

 

 

3. 구현해보자.

  • 먼저 데이터를 생성하고, 데이터의 모양을 봐야 한다.
  • 기계학습에선 행렬곱이 주를 이르므로, 행렬의 모양을 파악하는 것이 최우선이다.
>>> X = np.array([10, 2])

>>> W1 = np.array([[0.1, 0.3, 0.5], [0.6, 0.4, 0.2]])
>>> W2 = np.array([[0.1, 0.2],[0.2, 0.4],[0.3, 0.6]])
>>> W3 = np.array([[0.2, 0.4],[0.4, 0.8]])

>>> B1 = np.array([0.7, 0.6, 0.5])
>>> B2 = np.array([0.6, 0.4])
>>> B3 = np.array([0.5, 0.6])

>>> print("X Shape:", X.shape)
>>> print("----"*20)
>>> print("W1 Shape:", W1.shape)
>>> print("W2 Shape:", W2.shape)
>>> print("W3 Shape:", W3.shape)
>>> print("----"*20)
>>> print("B1 Shape:", B1.shape)
>>> print("B2 Shape:", B2.shape)
>>> print("B3 Shape:", B3.shape)

X Shape: (2,)
--------------------------------------------------------------------------------
W1 Shape: (2, 3)
W2 Shape: (3, 2)
W3 Shape: (2, 2)
--------------------------------------------------------------------------------
B1 Shape: (3,)
B2 Shape: (2,)
B3 Shape: (2,)

  • 중요한 것은 노드와 가중치 엣지의 모양이다.
  • 편향은 위 노드와 가중치 엣지의 모양만 제대로 맞게 이루어져 있다면, 벡터 합이 당연히 되므로, 신경 쓰지 않아도 된다.
>>> def ReLU(x):
>>>     return np.maximum(0, x)

>>> def sigmoid(x):
>>>     return 1 / (1 + np.exp(-x))


# 입력층에서 출력층 방향으로 계산된다.
>>> A1 =  np.dot(X, W1) + B1
>>> Z1 = ReLU(A1)

>>> A2 = np.dot(Z1, W2) + B2
>>> Z2 = ReLU(A2)

>>> A3 = np.dot(Z2, W3) + B3
>>> Y = sigmoid(A2)

>>> Y

array([0.97180471, 0.9981301 ])
  • 위 코드를 보면, 입력층 X에서 출발한 데이터가 "행렬곱 > 활성화 함수 > 행렬곱 > 활성화 함수 > 행렬곱 > 활성화 함수 > 출력"의 형태로 진행된 것을 알 수 있다.
  • 이를 보면, 단층 퍼셉트론을 활성화 함수만 바꾸면서 층을 쌓듯 여러 번 수행된 것을 알 수 있다.

 

 

 

 

4. 순전파(Forward Propagation)

  • 위 신경망에서 데이터의 흐름은 입력층에서 출력층의 방향으로 전달되었는데, 이를 순전파라고 한다.
  • 위에서 인공 신경망은 스스로 가장 적합한 가중치를 찾아간다고 하였는데, 이를 우리는 학습이라고 하며, 이 학습은 역전파(Back Propagation) 과정을 통해 이루어진다.
  • 만약 내가 신경망의 틀을 만들고, 그 신경망에서 가장 적합한 가중치를 찾는 것을, 이는 학습을 한다고 하며, 모델을 만든다라고 한다. 지금 같이 이미 가중치를 알고 있는 상태는 학습이 끝난, 모델이 완성된 상태이며, 이 모델에 데이터를 집어넣어, 그 결과를 확인하는 것은 순전파 되어 실행된다.

 

 

 

 지금까지 이미 가중치가 얻어진 다층 퍼셉트론(MLP)을 이용해, 신경망이 어떻게 연산되는지를 알아보았다. 신경망의 연산은 행렬곱과 활성화 함수 이 두 가지를 통해서 구해지는, 생각보다 단순한 알고리즘인 것을 알 수 있다. 

 다음 포스트에서는 그렇다면 대체 그 학습이라는 과정은 어떻게 이루어지는 지에 대해 알아보도록 하겠다.

728x90
반응형

+ Recent posts