TiiL Tutorials
@TinhocTiiL

AI-Thực hành #2 – Transfer Learning example

Bài toán nhận dạng tiền Việt Nam bằng CNN Classify.

[[Nguồn : Mì AI: https://miai.vn/2020/04/21/nhan-dang-tien-viet-nam-voi-transfer-learning-vgg16-cnn-classify/ ]]

Nhiệm vụ của bài toán là: khi chúng ta đưa tiền vào camera, hệ thống phải nhận diện mệnh giá của từng tờ tiền. Bài này có nhiều cách làm lắm, và khi mình đăng bài này lên thế nào cũng có bạn vào bảo :”Cái này đơn giản mà, OpenCV, tách màu HSV là xong cần gì VGG16 cho mệt”.

Đúng! Nhưng lại sai vì mục đích bài này để các bạn nắm được các món:

  • Tự tạo dữ liệu cho bài toán
  • Augment dữ liệu tránh Overfit cho model
  • Cách “thiết kế” mạng NN cho bài toán dựa trên khung nền của mạng thần thánh VGG16.

Và một lần nữa lại xin nhắc lại, bài toán này làm để học tập, các bạn khi sử dụng làm thương mại cần làm nhiều thứ khác nữa để tránh các bạn comment kiểu “Cái này đếm nhầm bỏ bố”, “Dùng được còn khướt :D”.

Okie! Let’s go!

Phần 1 – Phân tích bài toán

Dự kiến thực hiện sẽ gồm các bước như sau:

  • Tạo dữ liệu bằng cách đọc ảnh các tờ tiền từ camera
  • Thiết kế mạng NN với đầu vào là ảnh (128,128,3), đưa vào mạng VGG16 và đầu ra của VGG16 sẽ dùng để đưa vào 1 mạng NN nhỏ kết thúc bằng 1 lớp Dense và hàm softmax.
  • Đầu ra sẽ là 1 vector softmax chứa các probality p(i) ứng với mỗi class i, chúng ta sẽ in ra giá trị max trong vector đó và chọn đó làm class dự đoán.

Vấn đề về dữ liệu

Ở đây mình dự kiến làm sample với 3 class là 10000,20000 và 50000. Vậy theo suy nghĩ thông thường thì chỉ cần chuẩn bị dữ liệu cho 3 class và lớp Dense output cuối cùng sẽ là Dense(3).

Tuy nhiên do tổng các p(i) phải = 1 nên giả sử khi chúng ta không cầm đồng tiền nào thì máy sẽ buộc phải dự đoán vào 1 trong 3 class nói trên, dó là điều vô lý. Vì vậy chúng ta sẽ train với 4 class output là “Không tiền”,”10000″,”20000″ và “50000”, output sẽ là Dense(4).

Phần 2 – Tạo dữ liệu cho bài toán

Có bài toán thì có dữ liệu cho trước rất nhiều trên mạng, ví dụ face, ví dụ ảnh đồ vật, ảnh phong cảnh. Tuy nhiên các dữ liệu về tiền, đặc biệt tiền Việt thì tìm mãi chả ra nên phải tự tạo cho mình.

Cách tạo dữ liệu đơn giản là viết một đoạn python đọc liên tục từ camera và save lại vào các thư mục tương ứng ảnh các tờ tiền. Ví dụ trong bài này mình làm sample với 3 class là 10000,20000 và 50000 nhé (các bạn chú ý sửa cái dòng label = “00000” thành class mà mình muốn capture dữ liệu )

# Label: 00000 là ko cầm tiền, còn lại là các mệnh giá
label = "00000"

cap = cv2.VideoCapture(0)

# Biến đếm, để chỉ lưu dữ liệu sau khoảng 60 frame, tránh lúc đầu chưa kịp cầm tiền lên
i=0
while(True):
    # Capture frame-by-frame
    #
    i+=1
    ret, frame = cap.read()
    if not ret:
        continue
    frame = cv2.resize(frame, dsize=None,fx=0.3,fy=0.3)

    # Hiển thị
    cv2.imshow('frame',frame)

    # Lưu dữ liệu
    if i>=60:
        # Tạo thư mục nếu chưa có
        if not os.path.exists('data/' + str(label)):
            os.mkdir('data/' + str(label))
            
        cv2.imwrite('data/' + str(label) + "/" + str(i) + ".png",frame)

Sau khi chạy thành công 4 class, chúng ta sẽ có thư mục data với các subfolders như sau (chú ý tạo thư mục data trước khi chạy nếu không sẽ bị lỗi):

ác bạn hãy đảm bảo mỗi class có số ảnh tương đồng nhau nhé, tầm 1000-1500 là okie. Mình có in số ảnh capture được ra màn hình để các bạn theo dõi và stop khi cần thiết nhé ;).

Phần 3 – Xử lý dữ liệu ảnh

Các bạn để ý như sau, trong quá trình chúng ta train model, thử nghiệm model thì sẽ phải chạy chương trình rất nhiều lần để debug, sửa lỗi… Như vậy mà mỗi lần chạy lại phải mò hết đống file và thư mục trên thì sẽ khá lâu nên chúng ta sẽ đọc 1 lần và thực hiện:

  • Convert nhãn (là cái mớ 00000,10000,20000….) thành one-hot
  • Resize ảnh về 128×128

và lưu vào file pickle để lần sau load cho tiện. (bạn nào chưa rõ one-hot là gì có thể đọc link này)

def save_data(raw_folder=raw_folder):

    dest_size = (128, 128)
    print("Bắt đầu xử lý ảnh...")

    pixels = []
    labels = []

    # Lặp qua các folder con trong thư mục raw
    for folder in listdir(raw_folder):
        if folder!='.DS_Store':
            print("Folder=",folder)
            # Lặp qua các file trong từng thư mục chứa các em
            for file in listdir(raw_folder  + folder):
                if file!='.DS_Store':
                    print("File=", file)
                    pixels.append( cv2.resize(cv2.imread(raw_folder  + folder +"/" + file),dsize=(128,128)))
                    labels.append( folder)

    pixels = np.array(pixels)
    labels = np.array(labels)#.reshape(-1,1)

    from sklearn.preprocessing import LabelBinarizer
    encoder = LabelBinarizer()
    labels = encoder.fit_transform(labels)
    print(labels)

    file = open('pix.data', 'wb')
    # dump information to that file
    pickle.dump((pixels,labels), file)
    # close the file
    file.close()

    return

Sau khi lưu xong thì chúng ta không cần quan tâm đến cái mớ folder data kia nữa mà chỉ quan tâm đến file pix.data mới sinh ra. Nó chứa full cả ảnh và labels rồi.

Khi nào cần dùng thì load cái vèo trong nốt nhạc:

def load_data():
    file = open('pix.data', 'rb')

    # dump information to that file
    (pixels, labels) = pickle.load(file)

    # close the file
    file.close()

    print(pixels.shape)
    print(labels.shape)


    return pixels, labels

Phần 4 – Thiết kế mạng CNN Classify dùng để train

Chúng ta chuẩn bị dữ liệu xong, xử lý xong thì giờ là phần quan trọng nhất – thiết kế mạng để train. Làm deep mà ko có mạng thì chả có gì hết 😀

Chúng ta đã biết cấu trúc của một mạng CNN, cụ thể là VGG16 sẽ có dạng như sau:

Nhìn vào cấu trúc VGG16 sẽ gồm 2 phần, phần màu cam là trích đặt trưng của ảnh còn phần màu tím sẽ là các lớp FC để classify. Nhưng classify ở đây là dùng cho mục đích của người ta, ko phải cho bài toán nhận dạng tiền của chúng ta nên chúng ta sẽ là gì….?

Chúng ta sẽ chặt phăng cái phần tím đó vất đi bằng cách khai báo include_top=False khi implement cái mạng này:  

 model_vgg16_conv = VGG16(weights='imagenet', include_top=False)

Rồi bây giờ chúng ta thực hiện ghép nối cái FC của chúng ta vào bằng đoạn lệnh:

# Đóng băng các layers
    for layer in model_vgg16_conv.layers:
        layer.trainable = False

    # Tạo model với input là ảnh, lấy output của VGG16 và làm input của các layers FC thêm vào
    input = Input(shape=(128, 128, 3), name='image_input')
    output_vgg16_conv = model_vgg16_conv(input)

    # Thêm vào các FC layers 
    x = Flatten(name='flatten')(output_vgg16_conv)
    x = Dense(4096, activation='relu', name='fc1')(x)
    x = Dropout(0.5)(x)
    x = Dense(4096, activation='relu', name='fc2')(x)
    x = Dropout(0.5)(x)
    x = Dense(4, activation='softmax', name='predictions')(x)

Chú ý ở đây phải có đoạn lệnh # Đóng băng các layers nhé, đoạn này nghĩa là khi train ta chỉ điều chỉnh weights của phần FC thêm vào, ko sờ mó gì đến mạng VGG đã lấy vì họ train tốt lắm rồi.

Chú ý layers cuối cùng là Dense(4) với activation softmax như chúng ta phân tích ở trên. Mạng cuối cùng sẽ như sau, phần màu xanh lá cây là lớp FC mà chúng ta thêm vào, output sẽ là 1 vector chứ probality từng class.

Phần 5 – Sử dụng augmentation cho dữ liệu

Bây giờ nếu chúng ta sử dụng ngay ảnh nói trên để train cho model CNN Classify thì sẽ bị hiện tượng Overfit vì dữ liệu nhiều nhưng đa phần giống nhau. Dẫn đến train sẽ có chất lượng tốt nhưng khi test sẽ thấy không nhận chuẩn lắm.

Chúng ta sẽ thực hiện augment dữ liệu để làm phong phú hơn dữ liệu, tăng data variance , tăng tính tổng quát cho model bằng ImageDataGenerator của Keras.

Mình đã có một bài chi tiết về món này tại đây, bạn nào chưa biết có thể xem lại.

Trong bài này mình dựa vào thực tế bài toán nên chỉ sử dụng các phép augment như sau:

 

aug = ImageDataGenerator(rotation_range=20, zoom_range=0.1,
    rescale=1./255,
	width_shift_range=0.1,
    height_shift_range=0.1,
	horizontal_flip=True,
    brightness_range=[0.2,1.5], fill_mode="nearest")

Và chúng ta cũng không augment và lưu ra file mà sẽ gen ra ảnh trực tiếp trong quá trình train luôn nhé.

Phần 6 – Train model CNN Classify

Nếu như mọi lần chúng ta sử dụng lệnh model.fit để train thì lần này chúng ta sẽ sử dụng model.fit_generator . Lý do đơn giản chúng ta sinh ra dữ liệu trong quá trình train mà.

vgghist=vggmodel.fit_generator(aug.flow(X_train, y_train, batch_size=64),
                               epochs=50,
                               validation_data=aug.flow(X_test,y_test,
                               batch_size=len(X_test)),
                               callbacks=callbacks_list)

Các bạn để ý sẽ thấy mình augment cho cả train và val cho nó tăng độ khó cho model “chăm học” chút.

Mình cũng đã tạo checkpoint để lưu lại các weights tốt:

filepath="weights-{epoch:02d}-{val_accuracy:.2f}.hdf5"
checkpoint = ModelCheckpoint(filepath, monitor='val_accuracy', verbose=1, save_best_only=True, mode='max')
callbacks_list = [checkpoint]

Chắc do dữ liệu mình cũng đơn giản, giới hạn bài toán ở mức để học tập là chính nên model đang validation đạt 0.98. Cơ mà chả biết được có chuẩn không nên cứ phải test một chút thực tế cho lành 😀

Mình có để sẵn weights cho bạn nào không có điều kiện train.

Phần 7 – Thử nghiệm model CNN Classify

Bây giờ các bạn chạy file test.py để kiểm thử model. File này khá đơn giản:

  • Đọc ảnh từ camera
  • Thực hiện resize ảnh, normalize ảnh, chuyển thành tensor và đưa vào model để predict
  • Lấy kết quả đầu ra và hiển thị trên màn hình

Các bạn lưu ý mình sẽ load file weights đã được checkpoint lưu lại ở trên ở đoạn này:

# Load weights model da train
my_model = get_model()
my_model.load_weights("weights-19-1.00.hdf5")

Đồng thời, chúng ta chỉ xét các ảnh được nhận dạng có probality > 0.8 và hiển thị lên màn hình nhé (tiền bạc là cứ phải chắc chắn, kaka)

# Predict
    predict = my_model.predict(image)
    print("This picture is: ", class_name[np.argmax(predict[0])], (predict[0]))
    print(np.max(predict[0],axis=0))
    if (np.max(predict)>=0.8) and (np.argmax(predict[0])!=0):


        # Show image
        font = cv2.FONT_HERSHEY_SIMPLEX
        org = (50, 50)
        fontScale = 1.5
        color = (0, 255, 0)
        thickness = 2

        cv2.putText(image_org, class_name[np.argmax(predict)], org, font,
                    fontScale, color, thickness, cv2.LINE_AA)

Okie rồi, mình đã guide qua các bạn cách sử dụng CNN Classify, transfer learning cho model của mình, sử dụng ImageGenerator để làm giàu dữ liệu tránh OF…

Bài này đưa vào thực tế cần làm thêm nhiều việc như:

  • Train thêm các loại tiền khác
  • Predict trong vài frame liên tục và lấy bình quân có trọng số để tăng độ chính xác, tránh nhận nhầm.
  • Giới hạn vùng xử lý (ví dụ ở giữa khung hinh) để tăng tốc độ xử lý video…

 

Avatar
https://khoacntt.ntu.edu.vn/giang-vien/mai-cuong-tho

một GV Đại học. TiiL đã phụ trách một số môn học như: Lập trình Java, Phát triển web với Java, Lập trình thiết bị di động, Lập trình hệ thống nhúng và IoT.

Comments are closed.