การทำ Regularization แบบสมัยใหม่ ด้วยเทคนิค Augmentation, Batch Normalization และ Dropout

PUtoEY
9 min readOct 7, 2020

--

การเพิ่มประสิทธิภาพ Machine Learning Model มีวิธีการหลัก 2 อย่าง คือ

  1. การลด Generalization Error ด้วยการทำ Regularization
  2. การลด Cost Value ด้วยการทำ Optimization หรือการหาค่าที่เหมาะสมที่สุด

และหน้าที่ของการทำ Regularization คือ การปรับแต่งให้ Model มีประสิทธิภาพในการทำนายที่ดี ลด Error จากข้อมูลที่มันไม่เคยเห็นมาก่อนหรือจะเรียกอีกอย่างหนึ่งว่า Regularization คือ วิธีการใดก็ตามที่ใช้เพื่อแก้ปัญหา Under Fitting หรือ Over Fitting ของ Machine Learning Model ก็ได้

การแก้ปัญหา Overfitting

อาจใช้เทคนิคอย่างเช่น การทำ Augmentation, Dropout, Batch Normalization, L1/L2 Regularization, Weight Decay, Weight Constraints และ Early Stopping ฯลฯ

การแก้ปัญหา Under Fitting

เพิ่มขีดความสามารถ (Capacity) ด้วยการเพิ่มจำนวน Layer และจำนวน Node ใน Layer ให้มากขึ้น

ซึ่งเราจะใช้ 3 เทคนิค ต่อไปนี้ ได้แก่ การทำ Augmentation, Batch Normalization และ Dropout เพื่อที่จะดูว่าสามารถเพิ่มประสิทธิภาพของ Model ได้มากน้อยเท่าไหร่

Fashion-MNIST Dataset

เป็น Dataset ที่จะใช้ Train ในวันนี้ Fashion-MNIST เป็น Dataset ที่เกี่ยวกับเครื่องแต่งกาย แบ่งเป็นข้อมูล Train 60,000 ภาพ และข้อมูล Test อีก 10,000 ภาพ รวมทั้งหมด 10 ประเภท โดยมีการกำหนด Label ตั้งแต่ 0–9 ดังนี้

0: T-shirt/top
1: Trouser
2: Pullover
3: Dress
4: Coat
5: Sandal
6: Shirt
7: Sneaker
8: Bag
9: Ankle boot

  • สำหรับผู้อ่านที่มี GPU จะแนะนำให้ใช้ เพราะว่าถ้า Train โดยไม่ใช้ GPU จะช้ามากๆ เราสามารถ Config การใช้งาน ด้วยคำสั่งต่อไปนี้
!nvidia-smi -Limport tensorflow as tf
tf.__version__
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.compat.v1.Session(config=config)

print( 'Tensorflow Version:', tf.__version__)
print("GPU Available::", tf.config.list_physical_devices('GPU'))
  • Import Library
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation, Conv2D, MaxPool2D, Flatten, Dropout, BatchNormalization
from tensorflow.keras.optimizers import RMSprop,Adam

from tensorflow.keras.utils import to_categorical
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.model_selection import train_test_split

from matplotlib import pyplot
from tensorflow.keras.datasets import fashion_mnist

import plotly.graph_objs as go
from plotly import tools
import plotly

import cv2
  • กำหนดค่า Parameter ที่จำเป็น
IMG_ROWS = 28
IMG_COLS = 28
NUM_CLASSES = 10
TEST_SIZE = 0.2
RANDOM_STATE = 99

NO_EPOCHS = 10
BATCH_SIZE = 128
  • Load Dataset
(train_data, y), (test_data, y_test) = fashion_mnist.load_data()

print("Fashion MNIST train - rows:",train_data.shape[0]," columns:", train_data.shape[1], " rows:", train_data.shape[2])
print("Fashion MNIST test - rows:",test_data.shape[0]," columns:", test_data.shape[1], " rows:", train_data.shape[2])
for i in range(9):
pyplot.subplot(330 + 1 + i)
pyplot.imshow(train_data[i], cmap=pyplot.get_cmap('gray'))

pyplot.show()
  • ขยายมิติของ Dataset
  • ทำ Scaling
train_data = train_data / 255.0
test_data = test_data / 255.0
  • เข้ารหัสผลเฉลยแบบ One-hot Encoding
print(y.shape, y_test.shape)
print(y[:10])
y = to_categorical(y)
y_test = to_categorical(y_test)

print(y.shape, y_test.shape)
y[:10]
  • แบ่งข้อมูลสำหรับ Train และ Validate โดยการสุ่มในสัดส่วน 80:20
X_train, X_val, y_train, y_val = train_test_split(train_data, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)

X_train.shape, X_val.shape, y_train.shape, y_val.shape

Baseline Model

  • นิยาม Model
model = Sequential()

#1. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same', input_shape=(28, 28, 1)))
model.add(Activation("relu"))

#2. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same'))
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))

#3. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(Activation("relu"))

#4. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))

#FULLY CONNECTED LAYER
model.add(Flatten())
model.add(Dense(256))
model.add(Activation("relu"))

#OUTPUT LAYER
model.add(Dense(10, activation='softmax'))
  • Compile Model
optimizer = Adam()
model.compile(optimizer = optimizer, loss = "categorical_crossentropy", metrics=["accuracy"])

model.summary()
  • Train Model
train_model = model.fit(X_train, y_train,
batch_size=BATCH_SIZE,
epochs=NO_EPOCHS,
verbose=1,
validation_data=(X_val, y_val))
  • Evaluation
def create_trace(x,y,ylabel,color):
trace = go.Scatter(
x = x,y = y,
name=ylabel,
marker=dict(color=color),
mode = "markers+lines",
text=x
)
return trace

def plot_accuracy_and_loss(train_model):
hist = train_model.history
acc = hist['accuracy']
val_acc = hist['val_accuracy']
loss = hist['loss']
val_loss = hist['val_loss']
epochs = list(range(1,len(acc)+1))

trace_ta = create_trace(epochs,acc,"Training accuracy", "Green")
trace_va = create_trace(epochs,val_acc,"Validation accuracy", "Red")
trace_tl = create_trace(epochs,loss,"Training loss", "Blue")
trace_vl = create_trace(epochs,val_loss,"Validation loss", "Magenta")

fig = tools.make_subplots(rows=1,cols=2, subplot_titles=('Training and validation accuracy',
'Training and validation loss'))
fig.append_trace(trace_ta,1,1)
fig.append_trace(trace_va,1,1)
fig.append_trace(trace_tl,1,2)
fig.append_trace(trace_vl,1,2)
fig['layout']['xaxis'].update(title = 'Epoch')
fig['layout']['xaxis2'].update(title = 'Epoch')
fig['layout']['yaxis'].update(title = 'Accuracy', range=[0,1])
fig['layout']['yaxis2'].update(title = 'Loss', range=[0,1])

plotly.offline.iplot(fig, filename='accuracy-loss')
plot_accuracy_and_loss(train_model)

จากกราฟ Loss ด้านบน พบว่า Model มีปัญหา Over Fitting เพราะว่า ส่วน Training จะมีการลงเรื่อยๆ ส่วน validate มีแนวโน้มขึ้นเรื่อยๆ

Image Augmentation

ปัญหา Overfitting ของ Model สามารถแก้ได้ด้วยการเพิ่มจำนวน Data ในการ Train ในกรณีของ Data แบบ Image เราสามารถใช้เทคนิคอย่างเช่นการหมุนภาพ การเลื่อนภาพ และการกลับภาพ ฯลฯ

Image Augmentation ยังช่วยเพิ่มความหลากหลายของภาพที่จะนำไป Train อีกด้วย

ไปโหลดไฟล์รูปภาพที่อยากใช้ Train มาก่อน

  • อ่านไฟล์ภาพ
cat = cv2.imread('cat2.jpg')
cat.shape
  • แปลงระบบสีจาก BGR ซึ่งเป็นค่า Default ของ OpenCV Library เป็น RGB
cat = cv2.cvtColor(cat, cv2.COLOR_RGB2BGR)
  • Plot ภาพที่เราอ่านไฟล์มา
plt.figure(dpi=100)
plt.imshow(cat)
  • ขยายมิติของภาพจาก 3 มิติเป็น 4 มิติ เพื่อเตรียมนำเข้า Function ทำ Image Augmentation
print(cat.shape)
cat = cat.reshape(1,cat.shape[0],cat.shape[1],cat.shape[2])
print(cat.shape)
  • ทดลองทำ Vertical Shift ด้วยการเลื่อนภาพขึ้นลงแบบสุ่มไม่เกิน 20%
datagen = ImageDataGenerator(height_shift_range=0.2)

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')
  • ทดลองทำ Horizontal Shift ด้วยการเลื่อนภาพซ้ายขวาแบบสุ่มไม่เกิน 20%
datagen = ImageDataGenerator(width_shift_range=0.2)

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')
  • ทดลองบิดภาพ (Shear) แบบสุ่มไม่เกิน 20 องศา
datagen = ImageDataGenerator(shear_range=20)

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')
  • ทดลองขยายภาพ (Zoom) แบบสุ่มไม่เกิน 30%
datagen = ImageDataGenerator(zoom_range=0.3)

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')
  • ทดลองพลิกภาพแนวตั้ง (Vertical Flip) แบบสุ่ม
datagen = ImageDataGenerator(vertical_flip=True)

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')

fig.savefig('cat.jpeg', dpi=300)
  • ทดลองพลิกภาพแนวนอน (Horizontal Flip) แบบสุ่ม
  • ทดลองหมุนภาพ (Rotate) ไม่เกิน 30 องศา แบบสุ่ม

Fill Mode

เมื่อมีการเลื่อนภาพ บิดภาพ หมุนภาพ จะเกิดพื้นที่ว่างที่มุม ซึ่งจะมีการเติมภาพให้เต็มโดยใช้เทคนิคแบบ Nearest Neighbor ซึ่งเป็นการดึงสีบริเวณใกล้เคียงมาระบาย

  • เติมสีดำ (Constant Values)
datagen = ImageDataGenerator(rotation_range=30, fill_mode = 'constant')

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')
  • เติมสีข้างเคียง (Nearest Neighbor)
datagen = ImageDataGenerator(rotation_range=30, fill_mode = 'nearest')

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')
  • เติมสีแบบกระจกสะท้อน (Reflect Values)
datagen = ImageDataGenerator(rotation_range=50, fill_mode = 'reflect')

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')
  • เติมสีจากภาพแบบต่อกัน (Wrap Values)
datagen = ImageDataGenerator(rotation_range=30, fill_mode = 'wrap')

aug_iter = datagen.flow(cat, batch_size=1)

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15,15))

for i in range(3):
image = next(aug_iter)[0].astype('uint8')
ax[i].imshow(image)
ax[i].axis('off')

เราจะเพิ่มความหลากหลายของภาพเพื่อแก้ปัญหา Overfitting ตามขั้นตอนดังนี้

  • นิยามวิธีการทำ Image Augmentation
datagen = ImageDataGenerator(
rotation_range=0.05, #Randomly rotate images in the range
zoom_range = 0.2, # Randomly zoom image
width_shift_range=0.1, #Randomly shift images horizontally
height_shift_range=0.1, #Randomly shift images vertically
shear_range=0.05 #Randomly shear images
)

datagen.fit(X_train)
  • นิยาม Model
model = Sequential()

#1. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same', input_shape=(28, 28, 1)))
model.add(Activation("relu"))

#2. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same'))
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))

#3. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(Activation("relu"))

#4. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))

#FULLY CONNECTED LAYER
model.add(Flatten())
model.add(Dense(256))
model.add(Activation("relu"))

#OUTPUT LAYER
model.add(Dense(10, activation='softmax'))
  • Compile Model
optimizer = Adam()
model.compile(optimizer = optimizer, loss = "categorical_crossentropy", metrics=["accuracy"])

model.summary()
  • Train Model
NO_EPOCHS = 30

history = model.fit_generator(datagen.flow(X_train, y_train, batch_size=BATCH_SIZE),
shuffle=True,
epochs=NO_EPOCHS, validation_data = (X_val, y_val),
verbose = 1, steps_per_epoch=X_train.shape[0] // BATCH_SIZE)
  • Plot กราฟ
plot_accuracy_and_loss(history)
  • วัดค่า Accuracy จาก Test Dataset
score = model.evaluate(test_data, y_test,verbose=0)
print("Test Loss:",score[0])
print("Test Accuracy:",score[1])

Batch Normalization

การทำ Normalization เพื่อปรับค่าข้อมูลให้อยู่ในขอบเขตที่กำหนด

นอกจากนี้การทำ Data Normalization กับ Feature อย่างเช่น อายุ และเงินเดือน จะทำให้ทั้ง 2 Feature มีน้ำหนักเท่ากัน มีการกระจายตัวเหมือนกัน ไม่มีตัวหนึ่งตัวใดมีอิทธิพลมากกว่า ทั้งยังเป็นการเพิ่มความเร็วในการ Train Model และทำให้ค่า Loss ลดลงเมื่อเทียบกันตอนที่ยังไม่ได้ทำ Data Normalization เพราะมีค่าข้อมูลที่เล็กกว่า

ในการทำ Data Normalization เราสามารถเลือกวิธีการได้หลายวิธี เช่น การทำ Min-Max Normalization หรือการทำ Standardization เป็นต้น

Min-Max Normalization = 𝑥′ = [𝑥–min(𝑥)]/[max(𝑥) — min(𝑥)]

Standardization = 𝑥′ = (𝑥–𝑥¯)/𝜎2

โดยที่ 𝑥¯ คือ Mean และ 𝜎2 คือ Variance

Batch Normalization จะใช้วิธีการแบบ Standardization ซึ่งจะมีการกำหนดค่า Mean และ Variance

เราจะทดลองใช้ Batch Normalization ร่วมกับเทคนิค Image Augmentation เพื่อแก้ปัญหา Overfitting ตามขั้นตอนดังต่อไปนี้

  • นิยามวิธีการทำ Image Augmentation
datagen = ImageDataGenerator(
rotation_range=0.05, # Randomly rotate images in the range
zoom_range = 0.2, # Randomly zoom image
width_shift_range=0.1, # Randomly shift images horizontally
height_shift_range=0.1, # Randomly shift images vertically
shear_range=0.05
)
datagen.fit(X_train)
  • นิยาม Model
model = Sequential()

#1. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same', input_shape=(28, 28, 1)))
model.add(BatchNormalization())
model.add(Activation("relu"))

#2. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same'))
model.add(BatchNormalization())
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))

#3. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(BatchNormalization())
model.add(Activation("relu"))

#4. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(BatchNormalization())
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))

#FULLY CONNECTED LAYER
model.add(Flatten())
model.add(Dense(256))
model.add(BatchNormalization())
model.add(Activation("relu"))

#OUTPUT LAYER
model.add(Dense(10, activation='softmax'))
  • Compile Model
optimizer = Adam()
model.compile(optimizer = optimizer, loss = "categorical_crossentropy", metrics=["accuracy"])

model.summary()
  • Train Model
NO_EPOCHS = 50
history = model.fit_generator(datagen.flow(X_train, y_train, batch_size=BATCH_SIZE),
shuffle=True,
epochs=NO_EPOCHS, validation_data = (X_val, y_val),
verbose = 1, steps_per_epoch=X_train.shape[0] // BATCH_SIZE)
  • Plot กราฟ
plot_accuracy_and_loss(history)
  • วัดค่า Accuracy จาก Test Dataset
score = model.evaluate(test_data, y_test,verbose=0)
print("Test Loss:",score[0])
print("Test Accuracy:",score[1])
score = model.evaluate(test_data, y_test,verbose=0)
print("Test Loss:",score[0])
print("Test Accuracy:",score[1])

จากกราฟ Loss พบว่า Training Loss มีแนวโน้มที่จะลดลง แต่ Validation Loss ค่อนข้างแกว่ง จึงอาจต้องใช้เทคนิคอื่นร่วมแก้ปัญหา Overfitting

Dropout

Dropout เป็นเทคนิคในการทำ Regularization ที่เรียบง่าย แต่มีประสิทธิภาพอย่างมาก โดยเมื่อมีการใช้งาน Dropout ภายใน Layer ที่กำหนดแล้ว Node ใน Layer นั้นจะถูกสุ่มเพื่อปิดการทำงานชั่วคราวในแต่ละรอบของการทำ Forward Propagation และ Back-propagation ตามอัตราที่กำหนด ในขณะที่มีการ Train Model ทำให้ไม่มีการ Update Weight ใดๆ ที่ถูกเชื่อมต่อกับ Neuron Node ที่กำลังถูกปิด

  • นิยามวิธีการทำ Image Augmentation
datagen = ImageDataGenerator(
rotation_range=0.05, #Randomly rotate images in the range
zoom_range = 0.2, # Randomly zoom image
width_shift_range=0.1, #Randomly shift images horizontally
height_shift_range=0.1, #Randomly shift images vertically
shear_range=0.05 #Randomly shear images
)

datagen.fit(X_train)
  • นิยาม Model
model = Sequential()

#1. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same', input_shape=(28, 28, 1)))
model.add(BatchNormalization())
model.add(Activation("relu"))
model.add(Dropout(0.3))

#2. CNN LAYER
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'Same'))
model.add(BatchNormalization())
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Dropout(0.3))

#3. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(BatchNormalization())
model.add(Activation("relu"))
model.add(Dropout(0.3))

#4. CNN LAYER
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = 'Same'))
model.add(BatchNormalization())
model.add(Activation("relu"))

model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Dropout(0.3))


#FULLY CONNECTED LAYER
model.add(Flatten())
model.add(Dense(256))
model.add(BatchNormalization())
model.add(Activation("relu"))
model.add(Dropout(0.30))

#OUTPUT LAYER
model.add(Dense(10, activation='softmax'))
  • Compile Model
optimizer = Adam()
model.compile(optimizer = optimizer, loss = "categorical_crossentropy", metrics=["accuracy"])

model.summary()
  • Train Model
NO_EPOCHS = 180
history = model.fit_generator(datagen.flow(X_train, y_train, batch_size=BATCH_SIZE),
shuffle=True,
epochs=NO_EPOCHS, validation_data = (X_val, y_val),
verbose = 1, steps_per_epoch=X_train.shape[0] // BATCH_SIZE)

เพิ่ม Epoch ในการ Train เป็น 180

  • Plot กราฟ
plot_accuracy_and_loss(history)
  • วัดค่า Accuracy จาก Test Dataset
score = model.evaluate(test_data, y_test,verbose=0)
print("Test Loss:",score[0])
print("Test Accuracy:",score[1])

เมื่อวัดประสิทธิภาพการ Predict ด้วย Test Dataset ได้ค่า Accuracy 93.30%

ขอบคุณค่ะ >-<

--

--

PUtoEY
PUtoEY

No responses yet