C n n Image Classification

Xây dựng CNN model cho bài toán đa lớp

Xây dựng CNN model cho bài toán đa lớp

Những bài toán mà chỉ có 2 lớp cần phân biệt gọi là binary classification, còn những bài toán có nhiều hơn 2 lớp được gọi là multiple classification.

Có một vài sự khác biệt trong cách cài đặt CNN model giữa 2 loại bài toán này. Trong hôm nay chúng ta sẽ cùng tìm hiểu điều đó thông qua thực hiện phân loại 3 classes: Cat, Dog và Panda của bộ dữ liệu animals. Bộ dataset này gồm 3000 ảnh chia đều cho mỗi class.

Ta sẽ bắt tay vào thực hiện code luôn, những điểm khác biệt sẽ được đề cập trong quá trình viết code.

Sử dụng kiến thức đã học từ bài trước, ta sẽ thực hiện bài này theo 2 cách và so sánh kết quả:

  • Không sử dụng Transfer Learning
  • Sử dụng Transfer Learning

Đầu tiên, download dataset về thư mục làm việc và dùng thư viện split-folers để chia dữ liệu thành 2 tập train và validation.

Import các thư viện sẽ sử dụng:

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

Chuẩn bị dữ liệu training:

def data_gen():
    train_gen = ImageDataGenerator(
        rescale=1/255,
        rotation_range=40,
        width_shift_range=0.2,
        height_shift_range=0.2,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True,
        fill_mode='nearest'
    )
    validation_gen = ImageDataGenerator(
        rescale=1/255
    )
    
    train_datagen = train_gen.flow_from_directory(
        'Animals/training',
        target_size=(300,300),
        batch_size=32,
        class_mode='categorical'
    )
    validation_datagen = validation_gen.flow_from_directory(
        'Animals/validation',
        target_size=(300,300),
        batch_size=32,
        class_mode='categorical'
    )
    
    return train_datagen, validation_datagen

Model tự định nghĩa:

def create_own_model():
    model = keras.Sequential([
        # CONV => RELU => BN => POOL => DO
        layers.Conv2D(32, (3,3), activation='relu', padding='same', input_shape=(300, 300, 3)),
        layers.BatchNormalization(),
        layers.MaxPooling2D(3,3),
        layers.Dropout(0.25),
        
        # (CONV => RELU => BN)*2 => POOL => DO
        layers.Conv2D(64, (3,3), activation='relu', padding='same'),
        layers.BatchNormalization(),
        layers.Conv2D(64, (3,3), activation='relu', padding='same'),
        layers.BatchNormalization(),
        layers.MaxPooling2D(2,2),
        layers.Dropout(0.25),
        
        # (CONV => RELU => BN)*2 => POOL => DO
        layers.Conv2D(128, (3,3), activation='relu', padding='same'),
        layers.BatchNormalization(),
        layers.Conv2D(128, (3,3), activation='relu', padding='same'),
        layers.BatchNormalization(),
        layers.MaxPooling2D(2,2),
        layers.Dropout(0.25),
        
        # (FC => RELU => BN => DO)*2 => FC => SOFTMAX
        layers.Flatten(),
        layers.Dense(1024, activation='relu'),
        layers.BatchNormalization(),
        layers.Dropout(0.25),
        layers.Dense(512, activation='relu'),
        layers.BatchNormalization(),
        layers.Dropout(0.25),
        
        layers.Dense(3, activation='softmax')
    ])
    
    model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
    
    return model

Kiến trúc model:

CONV => RELU => BN => POOL => DO => (CONV => RELU => BN)*2 => POOL => DO => (CONV => RELU => BN)*2 => POOL => DO => (FC => RELU => BN => DO)*2 => FC => SOFTMAX

Model sử dụng Transfer Learning:

def create_transfer_learning_model():
    base_model = InceptionV3(
        input_shape=(300,300,3),
        include_top=False,
        weights='imagenet'
    )
    
    for layer in base_model.layers:
        layer.trainable = False
    
    head_model = base_model.output
    head_model = layers.Flatten()(head_model)
    head_model = layers.Dense(1024, activation='relu')(head_model)
    head_model = layers.BatchNormalization()(head_model)
    head_model = layers.Dropout(0.25)(head_model)
    head_model = layers.Dense(512, activation='relu')(head_model)
    head_model = layers.BatchNormalization()(head_model)
    head_model = layers.Dropout(0.5)(head_model)
    
    head_model = layers.Dense(3, activation='softmax')(head_model)
    
    model = Model(inputs=base_model.input, outputs=head_model)
    model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
    
    return model

Trong phương pháp Transfer Learning, ta sử dụng pre-trained InceptionV3 làm base_model.

Như chúng ta thấy, có 2 điểm khác biệt ở đây:

  • Hàm activation ở layer cuối Nếu như bài toán binary classification sử dụng hàm sigmoid chỉ có 1 output thì bài toán multiple classification sử dụng hàm softmax, số lượng output bằng số classes cần phân loại. Mình sẽ có bài phân tích chi tiết về các loại hàm này sau.
  • Hàm loss Binary classification sử dụng hàm binary_crossentropy, còn multiple classification sử dụng categorical_crossentropy hoặc sparse_categorical_crossentropy. Mình cũng sẽ viết một bài về các dạng hàm loss trong tương lai.

Định nghĩa callback functions:

def create_callbacks():
    callback_1 = EarlyStopping(monitor='val_acc', patience=4)
    callback_2 = ModelCheckpoint(
        'Animals_ModelCheckpoints/model-{epoch:02d}-{val_acc:.2f}.hdf5',
        save_best_only=True,
        save_weights_only=True,
        monitor='val_acc',
        save_freq='epoch',
        mode='auto',
        verbose=1
    )

    return [callback_1, callback_2]

Hàm vẽ đồ thị:

def plot_graph(history):
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    epoch = range(len(acc))
    
    plt.plot(epoch, acc, 'r', label='Training Accuracy')
    plt.plot(epoch, val_acc, 'b', label='Validation Accuracy')
    plt.plot(epoch, loss, 'g', label='Training Loss')
    plt.plot(epoch, val_loss, 'y', label='Validation Loss')
    
    plt.title('Training & Validation, Accuracy & Loss')
    plt.legend(loc=0)
    plt.show()

Train model với kiến trúc tự định nghĩa:

training_datagen, validation_datagen = data_gen()
model = create_own_model()

history = model.fit(
    training_datagen,
    epochs=30,
    validation_data=validation_datagen,
    callbacks=create_callbacks(),
    verbose=1
)

Training output:

Found 2400 images belonging to 3 classes.
Found 600 images belonging to 3 classes.
Epoch 1/30
 2/75 [..............................] - ETA: 4s - loss: 1.8446 - acc: 0.3906WARNING:tensorflow:Callbacks method `on_train_batch_end` is slow compared to the batch time (batch time: 0.0442s vs `on_train_batch_end` time: 0.0907s). Check your callbacks.
75/75 [==============================] - ETA: 0s - loss: 1.2381 - acc: 0.5292
Epoch 00001: val_acc improved from -inf to 0.33333, saving model to Animals_ModelCheckpoints/model-01-0.33.hdf5
75/75 [==============================] - 40s 533ms/step - loss: 1.2381 - acc: 0.5292 - val_loss: 4.0135 - val_acc: 0.3333
Epoch 2/30
75/75 [==============================] - ETA: 0s - loss: 0.8709 - acc: 0.6042
Epoch 00002: val_acc did not improve from 0.33333
75/75 [==============================] - 40s 533ms/step - loss: 0.8709 - acc: 0.6042 - val_loss: 3.9076 - val_acc: 0.3333
Epoch 3/30
75/75 [==============================] - ETA: 0s - loss: 0.8323 - acc: 0.6008
Epoch 00003: val_acc improved from 0.33333 to 0.35000, saving model to Animals_ModelCheckpoints/model-03-0.35.hdf5
75/75 [==============================] - 40s 535ms/step - loss: 0.8323 - acc: 0.6008 - val_loss: 3.0383 - val_acc: 0.3500
........
Epoch 11/30
75/75 [==============================] - ETA: 0s - loss: 0.6230 - acc: 0.6958
Epoch 00011: val_acc did not improve from 0.72667
75/75 [==============================] - 40s 536ms/step - loss: 0.6230 - acc: 0.6958 - val_loss: 0.9326 - val_acc: 0.5933
Epoch 12/30
75/75 [==============================] - ETA: 0s - loss: 0.5867 - acc: 0.7133
Epoch 00012: val_acc did not improve from 0.72667
75/75 [==============================] - 40s 539ms/step - loss: 0.5867 - acc: 0.7133 - val_loss: 0.6075 - val_acc: 0.7067
Epoch 13/30
75/75 [==============================] - ETA: 0s - loss: 0.5752 - acc: 0.7262
Epoch 00013: val_acc did not improve from 0.72667
75/75 [==============================] - 41s 540ms/step - loss: 0.5752 - acc: 0.7262 - val_loss: 0.5461 - val_acc: 0.7250

Model dừng train sau 13 epochs do giá trị của val_acc không tăng sau 5 epochs liên tiếp từ epoch 9 đến epoch 13. Độ chính xác cao nhất đạt được trên tập validation 72.67% tại epoch thứ 9.

Đồ thị quá trình training:

Bây giờ, thử sử dụng pre-trained model:

model = create_transfer_learning_model()
training_datagen, validation_datagen = data_gen()

history = model.fit(
    training_datagen,
    validation_data=validation_datagen,
    epochs=30,
    callbacks=create_callbacks(),
    verbose=1
)

Traning output:

Found 2400 images belonging to 3 classes.
Found 600 images belonging to 3 classes.
Epoch 1/30
75/75 [==============================] - ETA: 0s - loss: 0.1724 - acc: 0.9525
Epoch 00001: val_acc improved from -inf to 0.95667, saving model to Animals_ModelCheckpoints/model-01-0.96.hdf5
75/75 [==============================] - 41s 548ms/step - loss: 0.1724 - acc: 0.9525 - val_loss: 0.2077 - val_acc: 0.9567
Epoch 2/30
75/75 [==============================] - ETA: 0s - loss: 0.1153 - acc: 0.9679
Epoch 00002: val_acc improved from 0.95667 to 0.99167, saving model to Animals_ModelCheckpoints/model-02-0.99.hdf5
75/75 [==============================] - 42s 559ms/step - loss: 0.1153 - acc: 0.9679 - val_loss: 0.0331 - val_acc: 0.9917
Epoch 3/30
75/75 [==============================] - ETA: 0s - loss: 0.0719 - acc: 0.9771
Epoch 00003: val_acc improved from 0.99167 to 0.99333, saving model to Animals_ModelCheckpoints/model-03-0.99.hdf5
75/75 [==============================] - 42s 557ms/step - loss: 0.0719 - acc: 0.9771 - val_loss: 0.0082 - val_acc: 0.9933
........
Epoch 8/30
75/75 [==============================] - ETA: 0s - loss: 0.0410 - acc: 0.9833
Epoch 00008: val_acc did not improve from 0.99500
75/75 [==============================] - 43s 569ms/step - loss: 0.0410 - acc: 0.9833 - val_loss: 0.0288 - val_acc: 0.9933
Epoch 9/30
75/75 [==============================] - ETA: 0s - loss: 0.0554 - acc: 0.9842
Epoch 00009: val_acc did not improve from 0.99500
75/75 [==============================] - 44s 588ms/step - loss: 0.0554 - acc: 0.9842 - val_loss: 0.0140 - val_acc: 0.9917
Epoch 10/30
75/75 [==============================] - ETA: 0s - loss: 0.0632 - acc: 0.9804
Epoch 00010: val_acc did not improve from 0.99500
75/75 [==============================] - 43s 572ms/step - loss: 0.0632 - acc: 0.9804 - val_loss: 0.0219 - val_acc: 0.9933

Model dừng train sớm hơn, tại epoch thứ 10 sau 5 epochs liên tiếp không cải thiện về giá trị của val_acc (từ epoch 6 đến epoch 10). Độ chính xác trên tập validation cao nhất đạt được là 99,5% tại epoch thứ 5. Kết quả tốt hơn so với sử dụng model tự định nghĩa rất nhiều.

Đồ thị quá trình training:

Như vậy, qua bài này ta đã biết được cách thức xây dựng kiến trúc CNN model cho bài toán multiple classification. Đồng thời ta cũng nhận thấy rõ ràng ưu điểm của kỹ thuật Transfer Learning so với cách tự xây dựng CNN model. Có thể nói rằng, sử dụng pre-trained model luôn cho kết quả tốt hơn, trừ khi chúng ta có lý do cụ thể để không sử dụng chúng.

Cuối cùng, ta lưu lại model để sử dụng cho việc dự đoán về sau khi triển khai model vào sản phẩm thực tế:

model.save('animal_classification_model.h5')

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.

Trong các bài viết tiếp theo, mình sẽ viết về một số bài toán NLP và các kỹ thuật cần dùng để giải quyết chúng. Mời các bạn đón đọc!

Tham khảo