Machine Learning/[Kaggle Course] ML (+ 딥러닝, 컴퓨터비전)

[Kaggle Course] Overfitting, Underfitting → early_stopping + capacity↑

WakaraNai 2020. 12. 24. 00:27
728x90
반응형

이번 튜토리얼에서는 아래의 두 가지에 대해 배우게 됩니다.

- learning curve 해석

- 그 해석을 바탕으로 model development

특히, learning curve의 underfitting 또는 overfitting의 요소를 찾고 

이를 개선하기 위한 방책을 세우는 것이죠.

 

Interperting the Learning Curves

train data에는 두 가지의 정보가 있습니다.

- signal: 일반적으로 생기며, 새로운 data에서도 모델이 좋은 예측을 하도록 해줍니다

- noise: 오직 train data에서만 참값을 보입니다ㅠ (그렇다고 noise가 도움되지 않는 건 아닙니다...!)

     noise는 실세계의 데이터 또는 model에 부수적이고 도움되지 않는 패턴으로 인해 발생한 무작위적인 변동성입니다

train set에서 loss가 최소화될 수 있는 weight와 parameter를 선택하여 model을 훈련시킵니다.

그러나 모델의 성능을 측정할 때는 new set of data(validation data)에 대해서 하죠.

 

 train set에 epochs 횟수만큼 훈련한 loss의 결과치를 그래프로 나타내려 합니다. 여기에 validation data도 넣으려 하고요. 이 때의 그래프를 바로 learning curves라고 부릅니다.

 

옆의 사진을 보면 validation에서 실패했음을 알 수 있죠.

 

 

loss의 감소를 위한 훈련을 위해서는 model은 singal 뿐만 아니라 noise도 학습해야합니다.

그러나 validation loss는 오직 model이 singal을 학습할 때만 하향세를 보입니다.

(model이 train set의 noise를 아무리 학습해도 new data에선 무용지물)

그래서 model이 signal을 학습하여 curve가 하향세를 보여도, noise를 학습하면 curves 사이에 "gap"이 생깁니다.

틈(gap)의 크기는 model의 noise를 얼마나 학습했는지 보여줍니다.

 

 

 

Overfitting , Underfitting _ trade between signal and noise

이상적으로, 모든 signal을 학습하고 noise는 전부 제외한 model을 만들고 싶지요.

그러나 이는 절대로 일어날 수 없습니다. 대신 'trade'(거래)를 합니다

더 많은 noise를 학습하는 대신 더 많은 signal을 학습하는 model을 만들 수 있지요.

그 거래가 우리에게 유리하다면, validation loss는 하향세를 보일 것입니다.

그러나 특정 지점 이후부터 예상한 바와 반대로 흘러가며, cost 즉 비용이 이익을 넘어서게 되어

validation loss는 다시 오르게 됩니다.

 

이러한 거래교환은 2가지 문제점을 일으킵니다.

-> signal data가 부족하거나, -> Underfitting

-> 너무 많은 noise data가 들어오거나, -> Overfitting

 

 

Underfitting = model이 signal data를 충분히 학습하지 못하여,  loss가 더 이상 감소하지 않음을 의미

Overfitting = model이 너무 많은 noise를 학습하여,  loss가 더 이상 감소하지 않음을 의미

 

 

 

이제부터는, noise를 줄이고 signal data를 충분히 얻는 몇가지 방법에 대해서 보려합니다

 

 

Capacity

model의 capacity는 그 모델이 학습할 수 있는 크기와 복잡도를 가리킵니다.

neural networks에서 이는 얼마나 많은 neurons를 보유했는지 그리고 서로 어떻게 연결되었는지에 따라 결정됩니다.

만약 여러분의 network에 underfitting이 일어나면 그 network의 capacity를 증가시켜야 합니다.

 

network의 capacity를 증가시키는 방법 

dataset에 따라 두 방법 중 하나를 선택하자

1. network를 더 크게 확장시키기(layer에 units를 더 추가) -> linear relationships 간 학습에 도움이 됨

2. network를 깊게 만들기(layer 추가) -> nonlinear relationships 간 학습에 도움이 됨

 

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),
])

 

 

Early Stopping

noise로 인해 validation loss가 어느 지점부터 증가하기 시작할 때 학습을 중단하는 것

 

validation loss가 또다시 증가하는 것을 감지하면, 가중치를 이전의 최소치로 되돌리는 식으로 reset할 수 있습니다. 이는 model이 더 이상 noise를 학습하지 않게 방지하며 data가 overfit되는 것 또한 방지해줍니다. 

 

그런데 Early stopping을 이용한 training은 너무 이른 시기에, 즉 signal data 학습을 끝내기 전에, training을 멈출 수 있는 위험이 있습니다.

 

overfitting이 지속되는 것을 방지하는 것 외에도 early stopping은 충분히 학습되지 않은 상태로부터 방지하는 것 또한 예방할 수 있습니다.

그 방법은 early stopping 사용 시, training epochs에 큰 숫자(필요한 횟수보다 더 크게)를 넣어,

남은 data에 대한 훈련을 너무 일찍 끝내버리게 되는 상황을 모면하는 것입니다.

 

 

Add Early Stopping

Keras의 callback 함수를 이용하여 early stopping을 훈련에 추가할 수 있습니다.

early stopping callback은 epoch이 끝날 때마다 실행됩니다.

(Keras에는 callback에 여러 가지 기능이 포함되어 있으며,

lamda 함수로 내 방식대로 기능을 적용할 수 있습니다.)

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,
)

+) parameter 설명

- min_delta : validation loss가 최소 0.001이라도 변화하면,

- patience : 20 epochs만큼 기다리게 하고

- restore_best_weights : 최적의 loss값을 내놓는 가중치들을 보관해줍니다. 

 

 

validation loss의 증가가 overfitting 때문인지 random batch variation 때문인지 구분하기 어렵습니다.

그렇기에 parameter에 언제쯤 멈추어도 되는지 epoch에 허용치, 범위를 넣어주는 것입니다.

하나의 기능이 보완책으로도 작동된 거죠.

 

 

 

Ex. Train a Model with Early Stopping

저번 튜토리얼의 예제에 나온 model을 개선하려 합니다.

network의 capacity를 증가시키기고 early-stopping callback을 추가하여 overfitting을 방지할 것입니다.

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']

 

network의 capacity 증가시키기

네트워크 아주 커지겠지만, validation loss가 증가하는 추세가 보이면 callback으로 훈련을 중지시킬 수 있습니다.

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',
)

 

Add early stopping - with 'fit' method

fit method에 callbacks parameter에다가 early_stopping을 리스트 형식으로 적어줍니다.

early_stopping을 적용했으니 epochs도 크게 해봅시다.

확신이 들면, Keras는 epochs를 500번하기 전에 훈련을 멈출 것입니다.

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()))


#  Minimum validation loss: 0.09167633205652237

 

 


Excercise

 

Setup

# 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')


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))

# Input shape: [18]

 

'Spotify' dataset을 사용해봅시다.

'tempo', 'daceability', 'mode' 등의 음악 관련 다양한 features를 기반으로 노래의 인기도를 예측해봅시다.

 

a linear model로 간단한 network부터 시작해봅시다. 간단한 만큼, model의 capacity는 낮습니다.

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()));


# Minimum Validation Loss: 0.1951

옆의 사진처럼 '하키스틱' 패턴의 곡선은 흔합니다.

이는 훈련의 막바지에 어려움을 겪게 될 것을 예상할 수 있습니다. 

그러니 0 대신 10 epoch부터 시작해봅시다.

 

# 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()));

#  Minimum Validation Loss: 0.1951

 

 

1. Evaluate Baseline & Add Capacity

그래프를 보시면 underfitting인지, overfitting인지 감이 오시나요?

 

두 곡선의 gap이 작고, validation loss가 증가하지 않는 것으로 보아, overfitting 보단

underfitting인 것 같네요. 

그러니 이 dataset에는 capacity를 증가시켜야 합니다.

 

이를 위해 각 hidden layer마다 unit을 추가적으로 넣어 총 192개로 되도록 해봅시다

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()));

 

 

 

 

 

 

 

 

 

 

 

 

 

capacity 적용 후 그래프를 다시보니 

validation loss가 조금씩 증가하는 동안, training loss가 감소하는 것으로 보아 overfitting이 되었습니다.

이를 방지하기 위해  unit의 수를 줄이거나, early_stopping을 적용해야 합니다.

 

 

2. Define Early Stopping Callback & Train and Interpret

- min_delta : validation loss가 최소 0.001이라도 변화하면,

- patience : 5 epochs만큼 기다리게 하고

- restore_best_weights : 최적의 loss값을 내놓는 가중치들을 보관해줍니다. 

from tensorflow.keras import callbacks

# YOUR CODE HERE: define an early stopping callback
early_stopping = callbacks.EarlyStopping(
    min_delta = 0.001,
    patience=5,
    restore_best_weights = True,
)

 

early_stopping을 fit method의 'callbacks' argument에 리스트 형식으로 넣어줍니다.

아래의 코드블럭은 모델을 훈련하여 learning curves를 출력합니다.

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()));

 

early stopping을 적용하여 epochs를 크게 주었고,

총 50번 중 10번까지 하고 멈췄네요.

 

early stopping을 적용한 것과 아닌 것 중

어느 것이 더 나아 보이나요?

 

>>> 6번째 epochs에서 0.001 이상의 증가 추세를 보였고 이후 5 epochs를 거친 후 멈췄습니다.

overfitting을 감지하고 더 이상 엇나가지 않도록 멈춘 거죠. 게다가 'restore_best_weights' 덕분에 validation loss가 가장 낮은 model이 누구인지 알 수 있게 되었습니다.

마지막에 출력한 Minimum Validation Loss와 같은 val_loss 값을 가진 9번째 epoch이네요

728x90
반응형