Xây dựng mạng CNN với tensorflow và keras

python
classification
deep-learning
tutorial

#1

Trong bài này, mình xin giới thiệu một phương pháp xây dựng một mạng CNN sử dụng keras với backend là tensorflow. Mục tiêu của bài viết là hướng dẫn xây dựng một mạng CNN sử dụng keras và chuyển sang dạng mạng tensorflow để có thể sử dụng ở các ứng dụng khác.

1. Xây dựng mạng CNN

Chi tiết về một mạng CNN đã được nói trong một bài viết khác ở trong diễn đàn nên mình xin phép không trình bày thêm ở đây.

0-9

Ở đây mình sẽ xây dựng một mạng CNN để phân biệt ảnh chứa số từ 0-9 trên biển số xe. Ảnh đầu vào được cắt từ biển số xe. Sau đây là chi tiết các bước, do mình tham khảo từ các nguồn tiếng Anh nên phần comment mình sẽ để nguyên, vì nếu dịch ra tiếng Việt đôi khi có thể sẽ bị hiểu sai.

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

# import the necessary packages
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from keras.models import Sequential
from keras.layers.core import Dense, Dropout
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers import Flatten
from keras.optimizers import SGD
import keras
from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import pickle
import cv2
import os

Sau đó các arguments

# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=True,
    help="path to input dataset of images")
ap.add_argument("-m", "--model", required=True,
    help="path to output trained model")
ap.add_argument("-l", "--label-bin", required=True,
    help="path to output label binarizer")
ap.add_argument("-p", "--plot", required=True,
    help="path to output accuracy/loss plot")
args = vars(ap.parse_args())

Bước tiếp theo là load ảnh và phân chia thành tập train, test

# initialize the data and labels
print("[INFO] loading images...")
data = []
labels = []
num_classes = 10

# grab the image paths and randomly shuffle them
imagePaths = sorted(list(paths.list_images(args["dataset"])))
random.seed(42)
random.shuffle(imagePaths)

# loop over the input images
for imagePath in imagePaths:
    print(imagePath)
    # load the image, resize the image to be 32x32 pixels (ignoring
    # aspect ratio), flatten the image into 32x32x1=1024 pixel image
    # into a list, and store the image in the data list
    image = cv2.imread(imagePath,0)
    image = cv2.resize(image, (32, 32))
    image = np.reshape(image, (32, 32, 1))
    data.append(image)

    # extract the class label from the image path and update the
    # labels list
    label = imagePath.split(os.path.sep)[-2]
    labels.append(label)

# scale the raw pixel intensities to the range [0, 1]
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)

# partition the data into training and testing splits using 75% of
# the data for training and the remaining 20% for testing
(trainX, testX, trainY, testY) = train_test_split(data,
    labels, test_size=0.2, random_state=42)

# convert the labels from integers to vectors (for 2-class, binary
# classification you should use Keras' to_categorical function
# instead as the scikit-learn's LabelBinarizer will not return a
# vector)
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY)

Tiếp theo là phần chính, cấu trúc mạng CNN. Cấu trúc mạng mình tham khảo của mạng Cifar-10.

# Initialising the CNN
model = Sequential()

model.add(Conv2D(32, (3, 3), input_shape = (32,32,1), activation = 'relu'))

model.add(Conv2D(32, (3, 3), activation = 'relu'))
model.add(MaxPooling2D(pool_size = (2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), activation = 'relu'))
model.add(Conv2D(64, (3, 3), activation = 'relu'))
model.add(MaxPooling2D(pool_size = (2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())

model.add(Dense(units = 128, activation = 'relu'))
model.add(Dropout(0.5))
model.add(Dense(units = num_classes, activation = 'softmax'))

Tiếp theo là train mạng

# initialize our initial learning rate and # of epochs to train for
INIT_LR = 0.0001
EPOCHS = 50

# Compiling the CNN
opt = keras.optimizers.rmsprop(lr=INIT_LR, decay=1e-6)
model.compile(optimizer = opt, loss = 'categorical_crossentropy', metrics = ['accuracy'])

# Fitting the CNN to the images
H = model.fit(trainX, trainY, validation_data=(testX, testY), epochs=EPOCHS, batch_size=32)

Đánh giá mạng

# evaluate the network
print("[INFO] evaluating network...")
predictions = model.predict(testX, batch_size=32)
print(classification_report(testY.argmax(axis=1),
    predictions.argmax(axis=1), target_names=lb.classes_))

Lưu mạng, label binarizer và đồ thị

# plot the training loss and accuracy
N = np.arange(0, EPOCHS)
plt.style.use("ggplot")
plt.figure()
plt.plot(N, H.history["loss"], label="train_loss")
plt.plot(N, H.history["val_loss"], label="val_loss")
plt.plot(N, H.history["acc"], label="train_acc")
plt.plot(N, H.history["val_acc"], label="val_acc")
plt.title("Training Loss and Accuracy (Simple NN)")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend()
plt.savefig(args["plot"])

# save the model and label binarizer to disk
print("[INFO] serializing network and label binarizer...")
model.save(args["model"])
f = open(args["label_bin"], "wb")
f.write(pickle.dumps(lb))
f.close()

File code có thể down ở đây

Vậy là xong phần code. Để train, đầu tiên các bạn chuẩn bị folder project cấu trúc như sau:

├───train_data
│   ├───0
│   ├───1
│   ├───2
│   ├───3
│   ├───4
│   ├───5
│   ├───6
│   ├───7
│   ├───8
│   ├───9
├───train.py

Tập ảnh train data có thể down tại đây

sau đó chạy:

python train.py --dataset train_data --model model.model --label-bin bin --plot plot

và đợi training

>Epoch 1/50
>16875/16875 [==============================] - 68s 4ms/step - loss: 1.2134 - acc: 0.6775 - val_loss: 0.3016 - val_acc: 0.9370
>Epoch 2/50 
>4096/16875 [======>.......................] - ETA: 31s - loss: 0.4872 - acc: 0.8777

2. Chuyển model sang dạng tensorflow .pb

Để có thể đưa mạng CNN này vào các ứng dụng khác, ví dụ như mình là để sử dụng trong C#, chuyển đổi về dạng .pb của tensorflow sẽ tiện lợi hơn.

Các bạn down file code Convert_keras_to_tf.py và đặt vào trong folder project. Sau đó chạy:

python Convert_keras_to_tf.py --keras_model model.model --tf_model tf_model.pb

Để kiểm tra xem mạng có hoạt động bình thường không, các bạn down file code Prediction_tf_pb.py và chạy:

python Prediction_tf_pb.py

Kết quả (mình chạy thử lại trên các ảnh số 6 trong tập train):

>6
>prediction time 0.0025361785888671875

Kết quả thực tế trên phần mềm C#:

1

Nếu so sánh với kết quả khi sử dụng Mobilenet ở bài viết này thì tốc độ đạt được đã nhanh hơn 5 lần mà vẫn đạt được độ chính xác cần thiết.

Bài hướng dẫn tuy hơi dài nhưng mình mong là có thể giúp các bạn tự train được một mạng CNN cho ứng dụng của mình.

Tài liệu tham khảo


#2

Mình nghĩ nên thêm augmentation vào thì mới đạt được chất lượng tốt vì ảnh input thực tế không phải luôn được như data


#3

Cảm ơn bạn đã chia sẻ. Mình đã chạy được toàn bộ các file của bạn để có thể tham khảo. Nhưng mình chạy trên python shell chứ chưa chạy được trên command prompt. Khi chạy trên command prompt xảy ra lỗi data type float64 mà chưa tìm đc nguyên nhân, do mình mới tìm hiểu nên sẽ debug sau. Mình muốn làm một ứng dụng nhỏ để có thể hình dung ra được việc áp dụng 1 model vào thực tế như thế nào nên muốn xây dựng project trên C#. Ý tưởng là sẽ mở 1 ảnh trong dữ liệu ra sau đó sẽ gọi model đã train để nhận diện chữ đó. Mình đã tìm hiểu nhưng vẫn chưa làm được. Bạn có thể chia sẻ làm thế nào để gọi được model đã train từ C# không?


#4

Hi, anh. Em có chia sẻ một bài về sử dụng model .pb của tensorflow trong C# sử dụng Emgucv. Trong bài đó, em sử dụng Mobilenet, nếu anh sử dụng model của mình thì cần thay đổi một số thông số. Lấy ví dụ mạng CNN trong bài này.

Đầu tiên là thay đổi đầu vào, đầu vào ở đây là ảnh xám 32x32:

Mat m = new Mat("1.jpg", ImreadModes.Grayscale);
Mat blob = DnnInvoke.BlobFromImage(m, 1, new Size(32, 32));

Sau đó tên của input và output node:

tensor_net.SetInput(blob, "conv2d_1_input");
Mat detection = tensor_net.Forward("dense_2/Softmax"); 

Kết quả lúc này là 10 class x 4 byte = 40 byte:

byte[] data = new byte[40];
detection.CopyTo(data);

Các bước còn lại thì mình làm giống như trong bài đó là được.


#5

Cảm ơn bài chia sẻ của bạn. Mình không thấy bạn train chữ cái, sao phần dự đoán kết quả thực tế kia lại predict được chữ cái A vậy nhỉ :slight_smile:


#6

Thực tế em đã train được một số kí tự chữ cái khác, tuy nhiên do chưa có đủ mẫu nên em không đưa hết vào trong bài này ạ.


#7

Chào bạn, mình hỏi chút. Trong code mình không thấy bạn làm cắt ký tự, vậy bạn có code phần cắt ký tự không hay nhét cả cái ảnh chứa nhiều ký tự vào? Nếu là chuỗi ký tự thì sao bạn giải quyết được. Mình đang nghĩ là mình đọc sót, nhìn cái vd của bạn mà hơi hoang mang :-?


#8

Ở bài này em chỉ đề cập đến việc nhận diện ký tự thôi, còn tất nhiên ta phải có phần cắt từng ký tự trong ảnh. Chính các ảnh trong tập dùng để train đã được cắt bằng phần cắt ký tự. Về phần cắt ký tự thì có 3 phần chính: phát hiện biển số có trong ảnh -> xác định ký tự trong biển -> cắt ký tự. Tuy nhiên, để chi tiết hơn thì có thể em sẽ viết chi tiết một bài khác trong thời gian tới.

Hiện tại code phần cắt từng ký tự đã được up lên github, anh có thể tham khảo thêm.


#9

À ok, mình tưởng bạn có kèm luôn nên hơi bất ngờ hehe. Cá nhân mình thì quan tâm bài đó hơn là bài đọc chữ số này (bài này thông dụng mà), hehe chờ bài viết của bạn về bài toán đó vậy.


#10

chào bạn!!! cảm ơn bạn vì bài viết này!!! dùng mạng CNN này kết hợp với 1 model detect có được không bạn nhỉ!!! để nó detect và nhận dạng vùng có chữ số


#11

Hoàn toàn được ạ. Nhưng kết hợp ở đây là gộp chung vào một model hay ntn ạ? Nếu tách thành 2 model, 1 model detect biển số, 1 model nhận dạng biển số thì có lẽ sẽ hợp lý hơn.