C n n Image Classification

Sử dụng kỹ thuật Transfer Learning khi huấn luyện CNN model

Sử dụng kỹ thuật Transfer Learning khi huấn luyện CNN model

Trong các bài toán thực tế, khi làm việc với bộ dataset lớn và kiến trúc model phức tạp, việc huấn luyện model sẽ mất rất nhiều thời gian. Vài ngày hoặc thậm chí cả tuần mới ra được kết quả. Nếu ta chỉ train một lần thì không có gì đáng nói, nhưng thường thì ta sẽ train đi train lại rất nhiều lần, mỗi lần điều chỉnh hyper-parameter lại phải chạy train lại. Việc này quả thực rât rất mất thời gian và chán nản.

Hoặc khi chỉ có một lượng nhỏ dữ liệu để train model, chắc chắn sẽ cho ra một model không tốt, vì nó không học được kết các khía cạnh của dữ liệu. Khi triển khai thực tế chắc chắn sẽ thất bại.

Kỹ thuật Transfer Learning ra đời để giải quyết khó khăn này. Ý tưởng chính của nó là tận dụng lại những model kinh diển (VGG, Resnet, InceptionNet, …), đã được train trên những tập dữ liệu lớn (pre-trained models), loại bỏ các layers classification ở gần cuối (thường là các lớp FC), chỉ giữ lại các layers ở đầu làm nhiệm vụ trích xuất đặc trưng của dữ liệu.

Có 2 loại transfer learning:

  • Feature extractor: Sử dụng pre-trained model như là bộ trích xuất đặc trưng của dữ liệu. Các đặc trưng này sau đó sẽ được phân loại sử dụng các thuật toán ML như kNN, SVM, Decision Tree, …
  • Fine tuning: Loại bỏ các layers cuối làm nhiệm vụ phân loại trong pre-trained model, thêm vào các layers mới dựa theo bộ dữ liệu mà chúng ta có. Sau đó, train lại model tại những layers mà ta mới thêm vào.

Vậy thì khi nào ta nên sử dụng Transfer Learning?

Dựa trên kích thước và độ tương quan giữa CSDL mới và CSDL gốc (chủ yếu là ImageNet) để train các mô hình có sẵn, CS231n đưa ra một vài lời khuyên:

  • CSDL mới là nhỏ và tương tự như CSDL gốc. Vì CSDL mới nhỏ, việc tiếp tục train model dễ dẫn đến hiện tượng overfitting. Cũng vì hai CSDL là tương tự nhau, ta dự đoán rằng các high-level features là tương tự nhau. Vậy nên ta không cần train lại model mà chỉ cần train một classifer dựa trên feature vectors ở đầu ra ở layer gần cuối.

  • CSDL mới là lớn và tương tự như CSDL gốc. Vì CSDL này lớn, overfitting ít có khả năng xảy ra hơn, ta có thể train mô hình thêm một chút nữa (toàn bộ hoặc chỉ một vài layers cuối).

  • CSDL mới là nhỏ và rất khác với CSDL gốc. Vì CSDL này nhỏ, tốt hơn hết là dùng các classifier đơn giản (các linear classifiers) để tránh overfitting). Nếu muốn train thêm, ta cũng chỉ nên train các layer cuối. Hoặc có một kỹ thuật khác là coi đầu ra của một layer xa layer cuối hơn làm các feature vectors.

  • CSDL mới là lớn và rất khác CSDL gốc. Trong trường hợp này, ta vẫn có thể sử dụng mô hình đã train như là điểm khởi tạo cho mô hình mới, không nên train lại từ đầu.

Có một điểm đáng chú ý nữa là khi tiếp tục train các mô hình này, ta chỉ nên chọn learning rate nhỏ để các weights mới không đi quá xa so với các weights đã được trained ở các mô hình trước.

OK, ta sẽ bắt tay vào thực hành kỹ thuật này (theo cách thứ 2) ngay bây giờ!

Yêu cầu bài toán là huấn luyện một CNN model để phân loại ảnh chứa ngựa và người trong bộ dataset horse-or-humand.

Download bộ dataset horse-or-humand về máy tính, giải nén và sử dụng thư viện split-folders để chia thành 2 phần train set và validation set.

Đầu tiên, import các thư viện cần thiết:

import os
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras import layers
from tensorflow.keras import Model
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.applications.inception_v3 import InceptionV3

config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
session = tf.compat.v1.InteractiveSession(config=config)

Các pre-trained model phổ biến đã được tích hợp sẵn trong tensorflow. Ở đây ta khai báo lớp InceptionV3 để sử dụng InceptionNet pre-trained model.

Tiếp theo là chuẩn bị dữ liệu huấn luyện:

def data_gen():
    training_datagen = ImageDataGenerator(
        rescale=1/255,
        rotation_range=40,
        width_shift_range=0.2,
        height_shift_range=0.2,
        shear_range=0.2,
        horizontal_flip=True,
        fill_mode='nearest'
    )
    validation_datagen = ImageDataGenerator(rescale=1/255)
    
    training_generator = training_datagen.flow_from_directory(
        'horse-and-humand/train',
        target_size=(224,224),
        batch_size=16,
        class_mode='binary'
    )
    
    validation_generator = validation_datagen.flow_from_directory(
        'horse-and-humand/validation',
        target_size=(224,224),
        batch_size=16,
        class_mode='binary'
    )
    
    return training_generator, validation_generator

Khai báo 2 hàm callback: EarlyStopping và ModelCheckpoint:

def create_callbacks():
    callback_1 = ModelCheckpoint(
        'horse-humand_model_checkpoint/weights-improvement-{epoch:02d}-{val_acc:.2f}.hdf5',
        monitor='val_acc',
        save_best_only=True,
        save_weights_only=True,
        save_freq='epoch',
        mode='auto',
        verbose=1
    )

    callback_2 = EarlyStopping(monitor='val_acc', patience=5)

    return [callback_1, callback_2]

Phần quan trọng nhất trong bài này là định nghĩa model, sử dụng kỹ thuật Transfer Learning:

def create_model():
    # Load pre-trained model
    base_model = InceptionV3(
        input_shape=(224,224,3), # Kích thước ảnh đầu vào
        include_top=False, # Loại bỏ các FC layers ở cuối
        weights='imagenet' # Sử dụng các weights được train trên tập imagenet
    )
    # Đóng băng các layers của pre-trained model, không cho chúng update
    for layer in base_model.layers:
        layer.trainable = False
        
    # Tạo head_model
    head_model = base_model.output
    head_model = layers.Flatten()(head_model)
    head_model = layers.Dense(1024, activation='relu')(head_model)
    head_model = layers.Dropout(0.2)(head_model)
    head_model = layers.Dense(1, activation='sigmoid')(head_model)

    model = Model(inputs=base_model.input, outputs=head_model)
    model.compile(optimizer=RMSprop(lr=0.001), loss='binary_crossentropy', metrics=['acc'])
    
    return model

Model được tạo thành gồm 2 phần:

  • base_model: chính là pre-trained model (đã loại bỏ các FC layers ở cuối).
  • head_model: là các FC layers được thêm vào làm nhiệm vụ phân loại dựa theo tập dữ liệu mới.

Trong bài toán này, ta sử dụng pre-trained model của mạng InceptionNetV3 trên tập dữ liệu imagenet. Head_model bao gồm 2 FC layers xen kẽ DO layer ở giữa.

Hàm vẽ đồ thị:

def plot_chart(history):
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']

    epochs = range(len(acc))
    plt.figure(figsize=(10, 6))

    plt.plot(epochs, acc, 'r', label='Training Accuracy')
    plt.plot(epochs, val_acc, 'b', label='Validation Accuracy')
    plt.plot(epochs, loss, 'g', label='Training Loss')
    plt.plot(epochs, val_loss, 'y', label='Validation Loss')

    plt.title('Traing and Validation, Accuracy and Loss')
    plt.legend(loc=0)
    plt.show()

Gộp tất cả lại và tiến hành train model:

model = create_model()
training_generator, validation_generator = data_gen()
callbacks = create_callbacks()

history = model.fit(
    training_generator,
    epochs=30,
    callbacks=[callback_1, callback_2],
    validation_data=validation_generator,
    verbose=1
)

Training output:

Epoch 1/30
65/65 [==============================] - ETA: 0s - loss: 0.1342 - acc: 0.9786
Epoch 00001: val_acc improved from -inf to 0.91797, saving model to horse-humand_model_checkpoint/weights-improvement-01-0.92.hdf5
65/65 [==============================] - 12s 180ms/step - loss: 0.1342 - acc: 0.9786 - val_loss: 0.4456 - val_acc: 0.9180
Epoch 2/30
65/65 [==============================] - ETA: 0s - loss: 0.0992 - acc: 0.9708
Epoch 00002: val_acc did not improve from 0.91797
65/65 [==============================] - 12s 189ms/step - loss: 0.0992 - acc: 0.9708 - val_loss: 0.9824 - val_acc: 0.8594
Epoch 3/30
65/65 [==============================] - ETA: 0s - loss: 0.0501 - acc: 0.9903
Epoch 00003: val_acc improved from 0.91797 to 0.95312, saving model to horse-humand_model_checkpoint/weights-improvement-03-0.95.hdf5
............
Epoch 8/30
65/65 [==============================] - ETA: 0s - loss: 0.1031 - acc: 0.9834
Epoch 00008: val_acc did not improve from 0.99219
65/65 [==============================] - 13s 201ms/step - loss: 0.1031 - acc: 0.9834 - val_loss: 0.5211 - val_acc: 0.9219
Epoch 9/30
65/65 [==============================] - ETA: 0s - loss: 0.0893 - acc: 0.9786
Epoch 00009: val_acc did not improve from 0.99219
65/65 [==============================] - 13s 203ms/step - loss: 0.0893 - acc: 0.9786 - val_loss: 0.5921 - val_acc: 0.9180
Epoch 10/30
65/65 [==============================] - ETA: 0s - loss: 0.1015 - acc: 0.9815
Epoch 00010: val_acc did not improve from 0.99219
65/65 [==============================] - 13s 204ms/step - loss: 0.1015 - acc: 0.9815 - val_loss: 0.4264 - val_acc: 0.9453

Model dừng train sau 10 epochs, độ chính xác cao nhất đạt được trên tập validation là 94.53%, một kết quả khá cao với số lượng epochs “khiêm tốn” như vậy.

Quan sát thư mục horse-humand_model_checkpoint ta cũng thấy model được lưu tại một số điểm checkpoint. Model có độ chính xác cao nhất tại epoch thứ 3.

├── weights-improvement-01-0.92.hdf5
├── weights-improvement-03-0.95.hdf5

Kiểm tra quá trình huấn luyện bằng cách thể hiện giá trị loss và accuracy lên đồ thị:

plot_chart(history)

Mặc dù giá trị của val_loss dao động 1 chút nhưng acc, val_acc và loss đều khá lý tưởng, chứng tỏ sự hiệu quả của kỹ thuật Transfer Learning trong bài toàn này.

Source code của bài này, các bạn có thể tham khảo trên github cá nhân của mình tại đây.

Các bài viết từ trước đến giờ đều chỉ phân loại 2 lớp. Đối với bài toán phân loại nhiều lớp thì sẽ như thế nào? Câu trả lời sẽ có ở bài tiếp theo . Mời các bạn đón đọc!

Tham khảo