본문 바로가기

ComputerScience/Machine Learning

DeepLearning - 4. Overfitting and Underfitting

728x90

 

 

Overfitting and Underfitting

Explore and run machine learning code with Kaggle Notebooks | Using data from DL Course Data

www.kaggle.com

*Kaggle에서 제공하는 Deep Learning Tutorial을 학습하며 번역한 내용입니다.

Introduction

이전 tutorial을 회상해보면 epochs를 거치면서 training과 loss변화의 기록을 Keras가 유지하고 있었다.

이번 장에서는 이 learning curves를 어떻게 해석해야하고 해석을 바탕으로 model을 발전시키는 방법을 알아볼 것이다.

특히  underfitting과 overfitting을 기반으로 learning curves를 평가하여 correcting하는 전략까지 살펴볼 것이다.

Interpreting the Learning Curves

학습데이터에 있는 정보는 signal과 noise, 두가지로 생각해 볼 수 있다. 

signal은 generalizes의 한 부분으로 이는 모델이 새로운 데이터로 부터의 예측을 돕는다.

noise는 학습데이터에서만 만족하는 부분이다. 실 세계로부터의 데이터 혹은 사건에서 오는 무작위 파동이다. 즉

모델이 실제 예측을 하는데 별다른 정보를 갖지 않는 녀석이다. noise는 유용해보이지만 전혀 그렇지 않다.

 

비유를 통해 이해해보자면 고양이 사진 100개중에 고양이는 맞는데 유독 강아지 같이 생긴 고양이도 있을 수 있다. 실세계에서 존재하는 무작위 사례인 것이다. 이를 error라고 하고 다른 예를 signal이라고 볼 수 있겠다.

 

우리는 loss를 최소화 하기위해 weights 혹은 parameters를 선택해서 모델을 학습시킨다. 하지만 정확도가 모델의 성능을 결정하기 때문에 모델을 새로운 데이터에 대해서 평가해볼 필요가 있다. 이를 위한 데이터를 validation data라고 한다. 

 

epoch에 따른 loss를 plot하면서 모델을 할습시킬때 이번에는 validation data에 대한 loss도 화면에 그려볼 것이다. 이 두 그래프를 learning curves라고 한다. 효과적으로 deep learning model을 학습시키기 위해서는 이를 잘 해석해야할 필요가 있다.

https://www.kaggle.com/ryanholbrook/overfitting-and-underfitting

validation loss는 아직 보지 않은 데이터에 대한 예상에러를 추측하게 해준다.

training loss는 model이 signal 혹은 noise를 학습할때, 점점 줄어들 것이다.

하지만 validation loss는 model이 signal을 학습할 때만 줄어든다. model이 training set으로 부터 학습한 noise가 뭐건 간에 새로운 데이터를 generalize하는데는 사용하지 않는다는 것이다. (위에서 든 예시를 활용하자면 유독 강아지 닮은 고양이는 고양이를 예측하는데 도움이 되지 않는다.)

따라서 model이 singnal을 학습할 때는 두 곡선이 모두 아래로 내려가지만 noise를 학습할때는 두 곡선의 차이가 생긴다.

두 곡선의 차이의 크기는 model이 얼마나 많은 noise를 학습했는지 말해준다.

이상적으로 우리는 오직 signal만 학습하도록 model을 만들지만 이는 불가능하다. 대신 거래를 하는것이다. 모델이 noise를 학습하는 대가로 더 많은 signal을 학습하도록 하는 것이다. 

이 거래를 지속하면서 물론 validation loss가 계속 줄어들 수 있지만 어느 시점부터는 역전이 될 수 있다. 비용이 이익보다 더 커지는 순간에 말이다. 이럴때 validation loss는 상승하게 된다.

https://www.kaggle.com/ryanholbrook/overfitting-and-underfitting

이런 거래를 하는데 있어서 발생하는 두가지 문제가 있다. signal이 충분히 없거나 noise가 너무 많은 경우이다. 

training set에서 underfitting은 model이 학습할 충분한 signal이 없어서 loss가 더 이상 낮아지지 못할 때를 말한다.

overfitting은 model이 noise를 너무 많이 학습해서 loss가 더 이상 낮아지지 못할 때를 말한다.

이 두 상황간의 밸런스를 잘 맞추는 것이 deep learning model을 학습시키기 위한 최적의 방법이다.

training data에서 더 많은 signal을 얻고 noise는 줄이는 방법을 알아보자.

Capacity

model의 capacity는 모델이 복잡하고 규모가 큰 데이터를 학습할 수 있는 능력을 가리킨다.

capacity는 얼마나 많은 neurons을 가지고 있는지 혹은 어떻게 neuron들이 연결되어 있는지에 따라 결정된다.

만약 network가 data에 대해 underfitting한다면 network의 capacity를 향상시킬 필요가 있다.

모델의 capacity는 더 모델을 wider(layer에 더 많은 units을 추가)하게 혹은 더 깊게(layer추가)해서 향상시킬 수 있다. wider network는 더 linear한 relationships을 학습하는데 유용하고 deeper network는 nonlinear한 relationships를 학습하는데 유용하다.

어느 모델이 더 좋냐는 dataset에 따라 다르다.

 

model = keras.Sequential([
    layers.Dense(16, activation='relu'),
    layers.Dense(1),
])

wider = keras.Sequential([
    layers.Dense(32, activation='relu'),
    layers.Dense(1),
])

deeper = keras.Sequential([
    layers.Dense(16, activation='relu'),
    layers.Dense(16, activation='relu'),
    layers.Dense(1),
])

network의 capacity가 어떻게 수행 능력에 영향을 미치는지 알아보자.

Early Stopping

만약 모델이 너무 많은 noise를 학습하고자 할때 validation loss의 상승이 시작된다고 했다. 이를 예방하기 위해서 validation loss가 더 이상 줄 것 같지 않다면 멈출 필요가 있다. 이를 early stopping이라고 한다.

https://www.kaggle.com/ryanholbrook/overfitting-and-underfitting

validation loss가 최소인 지점까지만 model의 학습을 진행한다.

일단 validation loss가 상승하려는 모습을 포착하면 weights를 loss가 최소가 된 시점의 weights로 reset back한다. 이렇게 함으로써 model은 더이상 noise를 학습하지 않고 overfitting을 막을 수 있다.

물론 너무 일찍 멈추지 않는 것도 중요하다. 적절한 시점에서의 중단은 overfitting과 underfitting을 막을 수 있다.

따라서 epoch를 필요이상으로 크기 한 다음 early stopping이 나머지를 관리할 수 있도록 하자.

Adding Early Stopping

케라스에서는 callback을 통해서 early stopping을 training에 추가할 수 있다. early stopping callback은 매 epoch가 끝나는 순간 호출될 것이다. 케라스에서 이미 많은 종류의 callback을 가지고 있지만 물론 내 맘대로 정의할 수 있다.

from tensorflow.keras.callbacks import EarlyStopping

early_stopping = EarlyStopping(
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=20, # how many epochs to wait before stopping
    restore_best_weights=True,
)

"validation loss가 20 epochs동안 0.001보다 큰 향상이 없다면 학습을 멈추고 이제까지 찾은 최적의 model을 유지하라" 라는 뜻이다. validation loss가 overfitting때문에 상승하는지 혹은 random batch variation때문인지 파악하는건 쉽지 않다. 전달한 parameters들이 언제 stop할지 잘 판단해 줄 것이다

이렇게 정의한 callback을 fit 메서드로 loss, optimizer처럼 전달해 줄 것이다.

Example - Train a Model with Early Stopping

저번 tutorial에 이어서 이번에는 model을 향상시켜보자. network의 capacity를 향상시키고 overfitting을 막기 위해 early-stopping callback을 추가해주자.

일단 데이터를 준비하자.

import pandas as pd
from IPython.display import display

red_wine = pd.read_csv('../input/dl-course-data/red-wine.csv')

# Create training and validation splits
df_train = red_wine.sample(frac=0.7, random_state=0)
df_valid = red_wine.drop(df_train.index)
display(df_train.head(4))

# Scale to [0, 1]
max_ = df_train.max(axis=0)
min_ = df_train.min(axis=0)
df_train = (df_train - min_) / (max_ - min_)
df_valid = (df_valid - min_) / (max_ - min_)

# Split features and target
X_train = df_train.drop('quality', axis=1)
X_valid = df_valid.drop('quality', axis=1)
y_train = df_train['quality']
y_valid = df_valid['quality']

 

https://www.kaggle.com/ryanholbrook/overfitting-and-underfitting

이제 network의 capacity를 향상시키자. 더 큰 network를 만들 것이다. 하지만 callback이 training중 validation loss의 상승을 막도록 의존해야한다.

from tensorflow import keras
from tensorflow.keras import layers, callbacks

early_stopping = callbacks.EarlyStopping(
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=20, # how many epochs to wait before stopping
    restore_best_weights=True,
)

model = keras.Sequential([
    layers.Dense(512, activation='relu', input_shape=[11]),
    layers.Dense(512, activation='relu'),
    layers.Dense(512, activation='relu'),
    layers.Dense(1),
])
model.compile(
    optimizer='adam',
    loss='mae',
)

callback을 정의한 후에 fit메서드로 인자로 넘겨주자. 이때 콜백을 list로 넘겨주자.

early stopping을 쓸때는 필요이상으로 큰 epochs를 선택하자.

history = model.fit(
    X_train, y_train,
    validation_data=(X_valid, y_valid),
    batch_size=256,
    epochs=500,
    callbacks=[early_stopping], # put your callbacks in a list
    verbose=0,  # turn off training log
)

history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot();
print("Minimum validation loss: {}".format(history_df['val_loss'].min()))

https://www.kaggle.com/ryanholbrook/overfitting-and-underfitting
https://www.kaggle.com/ryanholbrook/overfitting-and-underfitting

500 epochs를 다 학습하기 전에 training은 성공적으로 멈췄다.

Exercise

overfitting을 막기 위한 callback을 추가해서 training outcomes를 향상시켜보자. 일단 그림그릴 준비를 먼저하자.

# Setup plotting
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
# Set Matplotlib defaults
plt.rc('figure', autolayout=True)
plt.rc('axes', labelweight='bold', labelsize='large',
       titleweight='bold', titlesize=18, titlepad=10)
plt.rc('animation', html='html5')

# Setup feedback system
from learntools.core import binder
binder.bind(globals())
from learntools.deep_learning_intro.ex4 import *

Spotify dataset를 load하자. audio features(tempo, danceability)를 통해 곡의 popularity를 예측해보자. 

import pandas as pd
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import make_column_transformer
from sklearn.model_selection import GroupShuffleSplit

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import callbacks

spotify = pd.read_csv('../input/dl-course-data/spotify.csv')

X = spotify.copy().dropna()
y = X.pop('track_popularity')
artists = X['track_artist']

features_num = ['danceability', 'energy', 'key', 'loudness', 'mode',
                'speechiness', 'acousticness', 'instrumentalness',
                'liveness', 'valence', 'tempo', 'duration_ms']
features_cat = ['playlist_genre']

preprocessor = make_column_transformer(
    (StandardScaler(), features_num),
    (OneHotEncoder(), features_cat),
)

# We'll do a "grouped" split to keep all of an artist's songs in one
# split or the other. This is to help prevent signal leakage.
def group_split(X, y, group, train_size=0.75):
    splitter = GroupShuffleSplit(train_size=train_size)
    train, test = next(splitter.split(X, y, groups=group))
    return (X.iloc[train], X.iloc[test], y.iloc[train], y.iloc[test])

X_train, X_valid, y_train, y_valid = group_split(X, y, artists)

X_train = preprocessor.fit_transform(X_train)
X_valid = preprocessor.transform(X_valid)
y_train = y_train / 100 # popularity is on a scale 0-100, so this rescales to 0-1.
y_valid = y_valid / 100

input_shape = [X_train.shape[1]]
print("Input shape: {}".format(input_shape))

https://www.kaggle.com/scratchpad/notebook5baf196fe2/edit

일단은 linear model부터 시작해보자. low capacity를 갖지만 Spotify dataset에서 linear model을 먼저 학습시켜 보자.

model = keras.Sequential([
    layers.Dense(1, input_shape=input_shape),
])
model.compile(
    optimizer='adam',
    loss='mae',
)
history = model.fit(
    X_train, y_train,
    validation_data=(X_valid, y_valid),
    batch_size=512,
    epochs=50,
    verbose=0, # suppress output since we'll plot the curves
)
history_df = pd.DataFrame(history.history)
history_df.loc[0:, ['loss', 'val_loss']].plot()
print("Minimum Validation Loss: {:0.4f}".format(history_df['val_loss'].min()));

https://www.kaggle.com/scratchpad/notebook5baf196fe2/edit

이렇게 hockey stick 패턴을 따르는 모습이 흔하지 않은건 아니다. 단 마지막 부분을 살펴보기 어려우니까 epoch를 10에서부터 그림을 다시 그려보자.

# Start the plot at epoch 10
history_df.loc[10:, ['loss', 'val_loss']].plot()
print("Minimum Validation Loss: {:0.4f}".format(history_df['val_loss'].min()));

https://www.kaggle.com/scratchpad/notebook5baf196fe2/edit

자 이 모델은 underfitting일까? overfitting일까? 아니면 적당한 결과일까?

두 그래프의 차이는 꽤나 작으면서 validation loss가 더이상 상승하지 않기 때문에 overfitting이라기 보다는 underfitting에 가깝다. 이 상태라면 capacity를 늘려서 실험을 계속해보자.

model = keras.Sequential([
    layers.Dense(128, activation='relu', input_shape=input_shape),
    layers.Dense(64, activation='relu'),
    layers.Dense(1)
])
model.compile(
    optimizer='adam',
    loss='mae',
)
history = model.fit(
    X_train, y_train,
    validation_data=(X_valid, y_valid),
    batch_size=512,
    epochs=50,
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot()
print("Minimum Validation Loss: {:0.4f}".format(history_df['val_loss'].min()));

https://www.kaggle.com/scratchpad/notebook5baf196fe2/edit

지금은 validation loss가 시작부터 상승한다. 반면 training loss는 계속 줄어들고 있다. 이 말은 network가 overfit하기 시작했다는 것을 나타낸다. 이 지점에서 이를 예방하기 위해서 units를 줄이거나 early stopping 메서드를 사용해야 한다.

early stopping callback을 정의하자. 5 epochs동안 validation loss가 적어도 0.001은 있어야 하고 best loss를 위한 weights를 유지하도록 하자.

from tensorflow.keras import callbacks

# YOUR CODE HERE: define an early stopping callback
early_stopping = callbacks.EarlyStopping(
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=5, # how many epochs to wait before stopping
    restore_best_weights=True,
)

이제 learning curves를 보기 위해 model을 학습시켜보자. model.fit으로 callbacks를 인자로 넘겨주는 것을 잊지말자.

model = keras.Sequential([
    layers.Dense(128, activation='relu', input_shape=input_shape),
    layers.Dense(64, activation='relu'),    
    layers.Dense(1)
])
model.compile(
    optimizer='adam',
    loss='mae',
)
history = model.fit(
    X_train, y_train,
    validation_data=(X_valid, y_valid),
    batch_size=512,
    epochs=50,
    callbacks=[early_stopping]
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot()
print("Minimum Validation Loss: {:0.4f}".format(history_df['val_loss'].min()));

https://www.kaggle.com/scratchpad/notebook5baf196fe2/edit

early stopping을 추가했을 때 결과가 어떤것 같은가?

overfitting이 시작되는 지점에서 callback이 학습을 멈춰주었다. restore_best_weights를 포함시켜주어서 model의 validation loss를 최소로 유지하도록 하였다.

728x90
반응형