Bài 28 - Thực hành training Facenet
21 Mar 2020 - phamdinhkhanhTiếp nối bài 27 model facenet. Trong bài này mình sẽ hướng dẫn các bạn cách thức xây dựng và huấn luyện model facenet cho bộ dữ liệu của mình. Bài thực hành được viết trên google colab. Các bạn mở trực tiếp link hướng dẫn facenet để bắt đầu các bước nhé.
Ngoài ra để tạo thuận lợi cho việc thực hành, mọi git repository của blog được lưu dữ tại khanhBlogTurtorial
Trước tiên để làm việc được với notebook bạn cần mount folder google drive:
1
2
3
4
5
6
from google.colab import drive
import os
drive.mount("/content/gdrive")
path = "/content/gdrive/My Drive/[thay folder của bạn vào đây]"
os.chdir(path)
Mục đích chính của package face_recognition là để phát hiện vị trí các khuôn mặt trong ảnh.
Có khá nhiều phương pháp để bắt vị trí khuôn mặt trong ảnh. Chúng ta có thể sử dụng:
Face Haar Cascade: Đây là phương pháp base trên machine learning classification. Mô hình được huấn luyện trên các bộ dữ liệu lớn gồm nhiều ảnh có hoặc không xuất hiện vật thể. Phương pháp này có tốc độ khá cao, tuy nhiên độ chính xác thì không được tốt như áp dụng deep learning. Bạn cũng có thể sử dụng haar cascade để huấn luyện nhận diện các object của mình như xe cộ, người, động vật, …. Trên opencv đã có sẵn rất nhiều các model pretrain Cascade cho các vật thể này.
module face_recognition: Đây là Một pretrain model được xây dựng từ một mạng CNN trên bộ dữ liệu kích thước lớn. Mô hình này có độ chính xác cao. Có khả năng bắt được vị trí khuôn mặt ở những vị trí cường độ sáng và góc nghiêng mà haar cascade phát hiện kém hơn. Tuy nhiên tốc độ thì có thể chậm hơn so với haar cascade.
Cả 2 phương pháp trên đều cho phép xác định vị nhiều khuôn mặt trên cùng 1 bức ảnh.
Hình 1: So sánh giữa 2 phương pháp Face haar cascade (khung mà đỏ) và face_recognition (khung màu xanh) cho thấy face_recognition phát hiện được hầu hết các khuôn mặt trong khi haar cascade bỏ sót nhiều khuôn mặt ở vị trí đầu tiên.
Để cài package bạn gõ lệnh
1
!pip install face_recognition
1
2
Collecting face_recognition
Successfully installed face-recognition-1.3.0 face-recognition-models-0.3.0
Ngoài ra bạn cũng cần cài đặt thêm các packages khác như opencv2, tensorflow version 2.x. Đối với các bạn thực hành trên google colab, tất cả đã sẵn có.
Bộ dữ liệu của chúng ta sẽ là một bộ dữ liệu gồm 150 ảnh của 5 người, mỗi người 30 ảnh.
Tôi đã chuẩn bị sẵn một bộ dữ liệu này. Bạn run lệnh bên dưới để clone data.
1
!git clone https://github.com/phamdinhkhanh/FacenetDataset.git ./Dataset
1
2
Cloning into './Dataset2'...
Checking out files: 100% (150/150), done.
Mỗi folder là ảnh của một người. Chúng ta có tổng cộng 5 folder
Như bài 27 chúng ta đã biết, một khuôn mặt cần được nhúng dưới một véc tơ 128 chiều để mã hóa nó. Trong bài này chúng ta sẽ thử nghiệm 2 phương pháp khác nhau và so sánh hiệu quả.
Chúng ta sẽ sử dụng pretrain model có tác dụng embedding các khuôn mặt có trong bức ảnh thành những véc tơ embedding 128 chiều. File pretrain chính là nn4.small2.v1.t7
trong git project.
Do model được huấn luyện từ pytorch nên sẽ cần các hàm để load model từ lên opencv như bên dưới.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Hàm load model
## Load model từ Caffe
import cv2
import os
import numpy as np
EMBEDDING_FL = os.path.join(path, "nn4.small2.v1.t7")
DATASET_PATH = os.path.join(path, "Dataset")
def _load_torch(model_path_fl):
"""
model_path_fl: Link file chứa weigth của model
"""
model = cv2.dnn.readNetFromTorch(model_path_fl)
return model
1
encoder = _load_torch(EMBEDDING_FL)
Model pretrain sẽ sử dụng input là blob images. Mục đích của blob images là để giảm nhiễu cho ảnh do chiếu sáng (illumination). Đây là bước tiền xử lý dữ liệu cần thiết khi xây dựng các model xử lý ảnh nói chung.
Hình 1: Ảnh gốc và ảnh đã được blob. Ta có thể nhận thấy ảnh đã được segment thành các vùng ảnh có chung cường độ màu sắc. Do đó ảnh hưởng của thay đổi màu sắc do ánh sáng đã được giảm thiểu.
Ngoài ra các bạn cũng có thể áp dụng các phương pháp khác như canny để tiền xử lý dữ liệu và so sánh hiệu quả.
Hàm _blogImage()
được sử dụng để convert hình ảnh RGB thành ảnh blob.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cv2
def _blobImage(image, out_size = (300, 300), scaleFactor = 1.0, mean = (104.0, 177.0, 123.0)):
"""
input:
image: ma trận RGB của ảnh input
out_size: kích thước ảnh blob
return:
imageBlob: ảnh blob
"""
# Chuyển sang blobImage để tránh ảnh bị nhiễu sáng
imageBlob = cv2.dnn.blobFromImage(image,
scalefactor=scaleFactor, # Scale image
size=out_size, # Output shape
mean=mean, # Trung bình kênh theo RGB
swapRB=False, # Trường hợp ảnh là BGR thì set bằng True để chuyển qua RGB
crop=False)
return imageBlob
Hàm _extract_bbox()
có tác dụng trích xuất các vị trí khuôn mặt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from face_recognition import face_locations
import matplotlib.pyplot as plt
IMAGE_TEST = os.path.join(path, "Dataset/khanh/001.jpg")
def _image_read(image_path):
"""
input:
image_path: link file ảnh
return:
image: numpy array của ảnh
"""
image = cv2.imread(image_path)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
return image
image = _image_read(IMAGE_TEST)
def _extract_bbox(image, single = True):
"""
Trích xuất ra tọa độ của face từ ảnh input
input:
image: ảnh input theo kênh RGB.
single: Lấy ra 1 face trên 1 bức ảnh nếu True hoặc nhiều faces nếu False. Mặc định True.
return:
bbox: Tọa độ của bbox: <start_Y>, <start_X>, <end_Y>, <end_X>
"""
bboxs = face_locations(image)
if len(bboxs)==0:
return None
if single:
bbox = bboxs[0]
return bbox
else:
return bboxs
Hàm _extract_face()
sẽ trích suất ra ra ảnh RGB của face.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def _extract_face(image, bbox, face_scale_thres = (20, 20)):
"""
input:
image: ma trận RGB ảnh đầu vào
bbox: tọa độ của ảnh input
face_scale_thres: ngưỡng kích thước (h, w) của face. Nếu nhỏ hơn ngưỡng này thì loại bỏ face
return:
face: ma trận RGB ảnh khuôn mặt được trích xuất từ image input.
"""
h, w = image.shape[:2]
try:
(startY, startX, endY, endX) = bbox
except:
return None
minX, maxX = min(startX, endX), max(startX, endX)
minY, maxY = min(startY, endY), max(startY, endY)
face = image[minY:maxY, minX:maxX].copy()
# extract the face ROI and grab the ROI dimensions
(fH, fW) = face.shape[:2]
# ensure the face width and height are sufficiently large
if fW < face_scale_thres[0] or fH < face_scale_thres[1]:
return None
else:
return face
bbox = _extract_bbox(image)
face = _extract_face(image, bbox)
plt.axis("off")
plt.imshow(face)
1
<matplotlib.image.AxesImage at 0x7fbae455fd68>
Tiếp theo ta sẽ tạo vòng lặp trích suất khuông mặt từ các bức ảnh và lưu trữ vào một pickle file. Do giả định mỗi bức ảnh chỉ bao gồm 1 người nên _extract_bbox()
được thiết lập single=True
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from imutils import paths
DATASET_PATH = "./Dataset"
def _model_processing(face_scale_thres = (20, 20)):
"""
face_scale_thres: Ngưỡng (W, H) để chấp nhận một khuôn mặt.
"""
image_links = list(paths.list_images(DATASET_PATH))
images_file = []
y_labels = []
faces = []
total = 0
for image_link in image_links:
split_img_links = image_link.split("/")
# Lấy nhãn của ảnh
name = split_img_links[-2]
# Đọc ảnh
image = _image_read(image_link)
(h, w) = image.shape[:2]
# Detect vị trí các khuôn mặt trên ảnh. Gỉa định rằng mỗi bức ảnh chỉ có duy nhất 1 khuôn mặt của chủ nhân classes.
bbox =_extract_bbox(image, single=True)
# print(bbox_ratio)
if bbox is not None:
# Lấy ra face
face = _extract_face(image, bbox, face_scale_thres = (20, 20))
if face is not None:
faces.append(face)
y_labels.append(name)
images_file.append(image_links)
total += 1
else:
next
print("Total bbox face extracted: {}".format(total))
return faces, y_labels, images_file
faces, y_labels, images_file = _model_processing()
1
Total bbox face extracted: 142
Nhớ lưu dữ liệu vào các file pickle để load lên dùng lại khi cần.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pickle
def _save_pickle(obj, file_path):
with open(file_path, 'wb') as f:
pickle.dump(obj, f)
def _load_pickle(file_path):
with open(file_path, 'rb') as f:
obj = pickle.load(f)
return obj
_save_pickle(faces, "./faces.pkl")
_save_pickle(y_labels, "./y_labels.pkl")
_save_pickle(images_file, "./images_file.pkl")
Ở bước 3.1 chúng ta đã load model encoder. Tiếp theo chúng ta sẽ sử dụng các ảnh khuôn mặt đã được trích xuất từ bước 3.2 để tạo embedding véc tơ. Đầu vào của model sẽ là các ảnh blob kích thước 96x96
nên ta sẽ convert dữ liệu về ảnh blob và sau đó truyền qua encoder.
1
2
3
4
5
6
7
8
9
10
11
12
13
def _embedding_faces(encoder, faces):
emb_vecs = []
for face in faces:
faceBlob = _blobImage(face, out_size = (96, 96), scaleFactor=1/255.0, mean=(0, 0, 0))
# Embedding face
encoder.setInput(faceBlob)
vec = encoder.forward()
emb_vecs.append(vec)
return emb_vecs
embed_faces = _embedding_faces(encoder, faces)
# Nhớ save embed_faces vào Dataset.
_save_pickle(embed_faces, "./embed_blob_faces.pkl")
Để thuận tiện cho so sánh hiệu quả giữa model pretrain và model self-training chúng ta sẽ phân chia tập dữ liệu thành tập train và test với tỷ lệ 80:20
và so sánh độ chính xác chỉ trên tập test.
1
2
embed_faces = _load_pickle("./embed_blob_faces.pkl")
y_labels = _load_pickle("./y_labels.pkl")
1
2
3
4
5
6
7
8
from sklearn.model_selection import train_test_split
ids = np.arange(len(y_labels))
X_train, X_test, y_train, y_test, id_train, id_test = train_test_split(np.stack(embed_faces), y_labels, ids, test_size = 0.2, stratify = y_labels)
X_train = np.squeeze(X_train, axis = 1)
X_test = np.squeeze(X_test, axis = 1)
print(X_train.shape, X_test.shape)
print(len(y_train), len(y_test))
1
2
(113, 128) (29, 128)
113 29
1
2
_save_pickle(id_train, "./id_train.pkl")
_save_pickle(id_test, "./id_test.pkl")
Sau khi lấy được dữ liệu các khuôn mặt, chúng ta sẽ sử dụng phương pháp learning similarity ở bài 27 để tìm kiếm các ảnh tương đồng nhất làm nhãn cho ảnh dự báo. Theo phương pháp này chúng ta sẽ không bị phụ thuộc vào số lượng classes khi output thay đổi. Để tính toán similarity ta dùng hàm cosine_similarity của sklearn, khá đơn giản.
1
2
3
4
5
6
7
8
9
10
11
12
13
from sklearn.metrics.pairwise import cosine_similarity
def _most_similarity(embed_vecs, vec, labels):
sim = cosine_similarity(embed_vecs, vec)
sim = np.squeeze(sim, axis = 1)
argmax = np.argsort(sim)[::-1][:1]
label = [labels[idx] for idx in argmax][0]
return label
# Lấy ngẫu nhiên một bức ảnh trong test
vec = X_test[1].reshape(1, -1)
# Tìm kiếm ảnh gần nhất
_most_similarity(X_train, vec, y_train)
1
'baejun'
Kiểm tra độ chính xác trên tập test
1
2
3
4
5
6
7
8
9
10
# def _acc_test(test_set, y_test):
from sklearn.metrics import accuracy_score
y_preds = []
for vec in X_test:
vec = vec.reshape(1, -1)
y_pred = _most_similarity(X_train, vec, y_train)
y_preds.append(y_pred)
print(accuracy_score(y_preds, y_test))
1
0.6206896551724138
Như vậy với phương pháp sử dụng lại pretrain model thì chúng ta chỉ đạt độ chính xác là 62%
trên tập test. Kết quả của các bạn cũng sẽ khác mình một chút. Đây là một tỷ lệ khá thấp vậy có cách nào để cải thiện độ chính xác của model không? Chúng ta cùng tìm hiểu ở mục tiếp theo. Huấn luyện model bằng triplot loss function.
Model pretrain đã không phát huy tác dụng. Nguyên nhân chính mình nghĩ rằng pretrain model đã được training trên tập ảnh của người châu âu, trong khi khuôn mặt của người châu âu khá khác biệt so với người châu á. Do đó chúng ta sẽ cần tự huấn luyện lại model facenet cho riêng bộ dữ liệu của mình. Chúng ta kì vọng mô hình sẽ tìm ra biểu diễn cho tập ảnh trên không gian 128 chiều tốt hơn.
Khó khăn nhất của việc tự huấn luyện đó là phải tùy biến lại hàm loss function. Điều này khá khó đối với các beginners. Mình thì muốn simple is the best
nên sử dụng luôn TripletSemiHardLoss của tensorflow
bản 2.x.x
.
base_netwok
model dự kiến của mình là VGG19. Chúng ta có thể dễ dạng load kiến trúc này nhừ keras
1
%tensorflow_version 2.x
1
TensorFlow 2.x selected.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import tensorflow as tf
from tensorflow.keras.layers import Dense, Lambda, Flatten
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.applications import VGG16
def _base_network():
model = VGG16(include_top = True, weights = None)
dense = Dense(128)(model.layers[-4].output)
norm2 = Lambda(lambda x: tf.math.l2_normalize(x, axis = 1))(dense)
model = Model(inputs = [model.input], outputs = [norm2])
return model
model = _base_network()
model.summary()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Model: "model_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_5 (InputLayer) [(None, 224, 224, 3)] 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 112, 112, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 56, 56, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 28, 28, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 14, 14, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
_________________________________________________________________
flatten (Flatten) (None, 25088) 0
_________________________________________________________________
dense_4 (Dense) (None, 128) 3211392
_________________________________________________________________
lambda_3 (Lambda) (None, 128) 0
=================================================================
Total params: 17,926,080
Trainable params: 17,926,080
Non-trainable params: 0
_________________________________________________________________
Ta thấy dữ liệu ảnh các khuôn mặt hiện tại đang không cùng shape. Do đó cần thực hiện các preprocessing image.
Chúng ta sẽ resize lại các ảnh thông qua hàm resize của opencv.
1
faces = _load_pickle("./faces.pkl")
1
2
3
4
5
6
7
8
9
import cv2
faceResizes = []
for face in faces:
face_rz = cv2.resize(face, (224, 224))
faceResizes.append(face_rz)
X = np.stack(faceResizes)
X.shape
1
(142, 224, 224, 3)
Phân chia tập train/test
1
2
3
4
5
6
7
id_train = _load_pickle("./id_train.pkl")
id_test = _load_pickle("./id_test.pkl")
X_train, X_test = X[id_train], X[id_test]
print(X_train.shape)
print(X_test.shape)
1
2
(113, 224, 224, 3)
(29, 224, 224, 3)
Chúng ta sẽ sử dụng hàm triplet semi hard loss của tensorflow cho nhanh. Không phải code lại từ đầu và rất tiết kiệm thời gian. Về cách sử dụng hàm loss function này bạn xem thêm tại Triplet-semi-hard loss.
Để sử dụng loss function triplet ta cần complile model như sau:
1
2
3
4
5
import tensorflow_addons as tfa
model.compile(
optimizer=tf.keras.optimizers.Adam(0.001),
loss=tfa.losses.TripletSemiHardLoss())
Để huấn luyện model, ta khởi tạo một tensorflow dataset với batch_size = 32 và shuffle sau mỗi 1024 steps.
1
2
3
4
print(X_train.shape, len(y_train))
gen_train = tf.data.Dataset.from_tensor_slices((X_train, y_train)).repeat().shuffle(1024).batch(32)
gen_train
1
2
3
4
5
6
7
(113, 224, 224, 3) 113
<BatchDataset shapes: ((None, 224, 224, 3), (None,)), types: (tf.uint8, tf.string)>
1
2
3
4
history = model.fit(
gen_train,
steps_per_epoch = 50,
epochs=10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Train for 50 steps
Epoch 1/10
50/50 [==============================] - 21s 421ms/step - loss: 0.9579
Epoch 2/10
50/50 [==============================] - 20s 405ms/step - loss: 0.9127
Epoch 3/10
50/50 [==============================] - 20s 408ms/step - loss: 0.8601
Epoch 4/10
50/50 [==============================] - 21s 412ms/step - loss: 0.7297
Epoch 5/10
50/50 [==============================] - 21s 414ms/step - loss: 0.5813
Epoch 6/10
50/50 [==============================] - 21s 414ms/step - loss: 0.2593
Epoch 7/10
50/50 [==============================] - 20s 407ms/step - loss: 0.0092
Epoch 8/10
50/50 [==============================] - 20s 405ms/step - loss: 1.2906e-04
Epoch 9/10
50/50 [==============================] - 20s 406ms/step - loss: 1.5807e-05
Epoch 10/10
50/50 [==============================] - 20s 406ms/step - loss: 0.0000e+00
1
model.save("model/model_triplot.h5")
Sau khi huấn luyện xong mô hình ta cùng kiểm tra accuracy trên tập test
1
2
X_train_vec = model.predict(X_train)
X_test_vec = model.predict(X_test)
1
2
3
4
5
6
7
y_preds = []
for vec in X_test_vec:
vec = vec.reshape(1, -1)
y_pred = _most_similarity(X_train_vec, vec, y_train)
y_preds.append(y_pred)
print(accuracy_score(y_preds, y_test))
1
0.6551724137931034
Như vậy việc huấn luyện lại model trên chính bộ dữ liệu gốc theo Triplot loss function đã giúp chúng ta cải thiện độ chính xác trên tập test lên 3%. Nhưng tỷ lệ này vẫn còn khá thấp. Để hiểu rõ hơn nguyên nhân lỗi là gì, hãy cùng đi phân tích lỗi.
Ta sẽ biểu diễn các ảnh bị dự báo sai và xem chúng có đặc điểm gì?
1
2
3
4
5
6
7
8
9
10
idx_diff = np.flatnonzero(np.array(y_preds) != np.array(y_test))
fg, ax = plt.subplots(2, 5, figsize=(15, 8))
fg.suptitle('Wrong predict images')
for i in np.arange(2):
for j in np.arange(5):
ax[i, j].imshow(X_test[idx_diff[i + j + j*i]])
ax[i, j].set_xlabel('Transform '+str(i+j+j*i))
ax[i, j].axis('off')
Ta nhận thấy các bức ảnh dự đoán sai đa phần là rơi vào các trạng thái:
Nhân vật đang cười.
Chụp ở một góc nghiêng.
Đội nón.
Đeo kính
Tiếp theo ta sẽ xem các ảnh này bị dự báo sai với ảnh nào gần nhất.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def _most_similarity_idx(embed_vecs, vec, labels):
sim = cosine_similarity(embed_vecs, vec)
sim = np.squeeze(sim, axis = 1)
argmax = np.argsort(sim)[::-1][:1][0]
# label = [labels[idx] for idx in argmax][0]
return argmax
# Lấy ra các bức ảnh gần nhất với các ảnh dự báo.
nearest_idx = []
for vec in X_test_vec:
vec = vec.reshape(1, -1)
argmax = _most_similarity_idx(X_train_vec, vec, y_train)
nearest_idx.append(argmax)
# Lọc ra tiếp các ảnh bị dự báo sai.
nearest_idx = [nearest_idx[idx] for idx in idx_diff]
1
2
3
4
5
6
7
8
fg, ax = plt.subplots(2, 5, figsize=(15, 8))
fg.suptitle('Nearest predict images')
for i in np.arange(2):
for j in np.arange(5):
ax[i, j].imshow(X_train[nearest_idx[i + j + j*i]])
ax[i, j].set_xlabel('Transform '+str(i+j+j*i))
ax[i, j].axis('off')
Nhận xét:
Hầu hết các ảnh cười của bae yong yoon đều bị nhận nhầm thành tôi. Cả 2 đều đeo kính và đang cười.
Hầu hết các ảnh nhận nhầm có màu da như nhau.
Như vậy để tăng cường hiệu quả nhận dạng thì:
Chúng ta thử nghiệm một số phương pháp data augumentation để xem kết quả có cải thiện không.
Tiếp theo ta sẽ khởi tạo một ImageDataGenerator để thực hiện một loạt các bến đổi cho hình ảnh. Trong đó bao gồm:
Chuẩn hóa theo phân phối chuẩn các pixels của ảnh: Trung bình các pixels bằng 0, phương sai bằng 1.
Tạo các ảnh với các góc nghiêng là 20 độ.
Dịch chuyển ảnh theo width, height.
Lật ảnh theo chiều ngang.
Sau khi thực hiện các biến đổi, các biến thể của ảnh sẽ trông như sau:
1
2
3
4
5
6
7
8
9
10
11
from tensorflow.keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
featurewise_center=True,
featurewise_std_normalization=True,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True)
datagen.fit(X_train)
Với mỗi bức ảnh trên tập train sẽ lấy ra 5 ảnh biến thể. Như vậy ta có khoảng gần 550 ảnh.
1
2
3
4
5
6
7
8
9
10
11
no_batch = 0
X_au = []
y_au = []
for i in np.arange(len(X_train)):
no_img = 0
for x in datagen.flow(np.expand_dims(X_train[i], axis = 0), batch_size = 1):
X_au.append(x[0])
y_au.append(y_train[i])
no_img += 1
if no_img == 5:
break
1
2
3
4
5
6
import tensorflow_addons as tfa
model2 = _base_network()
model2.compile(
optimizer=tf.keras.optimizers.Adam(0.001),
loss=tfa.losses.TripletSemiHardLoss())
1
2
3
# Điều chỉnh tăng batch_size = 64
gen_train2 = tf.data.Dataset.from_tensor_slices((X_au, y_au)).repeat().shuffle(1024).batch(64)
gen_train2
1
<BatchDataset shapes: ((None, 224, 224, 3), (None,)), types: (tf.float32, tf.string)>
1
print(len(X_au), len(y_au))
1
565 565
1
2
3
4
history = model2.fit(
gen_train2,
steps_per_epoch = 50,
epochs=20)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Train for 15 steps
Epoch 1/10
15/15 [==============================] - 13s 874ms/step - loss: 0.2821
Epoch 2/10
15/15 [==============================] - 13s 857ms/step - loss: 0.1791
Epoch 3/10
15/15 [==============================] - 13s 870ms/step - loss: 0.1471
Epoch 4/10
15/15 [==============================] - 13s 861ms/step - loss: 0.0672
Epoch 5/10
15/15 [==============================] - 13s 846ms/step - loss: 0.0361
Epoch 6/10
15/15 [==============================] - 13s 834ms/step - loss: 0.0134
Epoch 7/10
15/15 [==============================] - 12s 826ms/step - loss: 0.0018
Epoch 8/10
15/15 [==============================] - 12s 828ms/step - loss: 9.6324e-04
Epoch 9/10
15/15 [==============================] - 12s 825ms/step - loss: 1.8847e-04
Epoch 10/10
15/15 [==============================] - 12s 825ms/step - loss: 1.1150e-04
1
model2.save("model/model_triplot_au.h5")
Để dự báo trên tập test thì chúng ta sẽ cần phải transform ảnh trên X theo đúng như nguyên tắc tranform trên tập train.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data_tf = ImageDataGenerator(
featurewise_center=True,
featurewise_std_normalization=True,
rotation_range=20,
# width_shift_range=0.2,
# height_shift_range=0.2,
horizontal_flip=True
)
data_tf.fit(X_test)
no_batch = 0
X_test_tf = []
for i in np.arange(len(X_test)):
no_img = 0
for x in data_tf.flow(np.expand_dims(X_test[i], axis = 0), batch_size = 1):
X_test_tf.append(x[0])
no_img += 1
if no_img == 1:
break
1
2
X_train_vec = model2.predict(np.stack(X_au))
X_test_vec = model2.predict(np.stack(X_test_tf))
1
2
3
4
5
6
7
y_preds = []
for vec in X_test_vec:
vec = vec.reshape(1, -1)
y_pred = _most_similarity(X_train_vec, vec, y_au)
y_preds.append(y_pred)
print(accuracy_score(y_preds, y_test))
1
0.6896551724137931
Như vậy bạn đã thấy hiệu quả của model rồi chứ? Sau khi thực hiện augumentation thì accuracy của chúng ta đã tăng lên từ 65%-68%. Tôi nghĩ rằng trong điều kiện bộ dữ liệu là không quá tốt thì đây là một kết quả chấp nhận được.
Trước tiên chúng ta cần tạo một hàm normalize để tiền xử lý ảnh trước khi đưa vào dự báo mô hình.
1
2
3
4
5
6
def _normalize_image(image, epsilon=0.000001):
means = np.mean(image.reshape(-1, 3), axis=0)
stds = np.std(image.reshape(-1, 3), axis=0)
image_norm = image - means
image_norm = image_norm/(stds + epsilon)
return image_norm
Sau đó ta thực hiện một vòng lặp để trích suất toàn bộ các faces trên ảnh và dự báo kết quả trên từng face.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
IMAGE_OUTPUT = "./predictions.jpg"
IMAGE_PREDICT = "./test1.jpg"
# Trích xuất bbox image
image = _image_read(IMAGE_PREDICT)
# imageBlob = _blobImage(image)
bboxs = _extract_bbox(image, single=False)
# print(len(bboxs))
faces = []
for bbox in bboxs:
face = _extract_face(image, bbox, face_scale_thres = (20, 20))
# face = face.copy()
faces.append(face)
try:
face_rz = cv2.resize(face, (224, 224))
# Chuẩn hóa ảnh bằng hàm _normalize_image
face_tf = _normalize_image(face_rz)
face_tf = np.expand_dims(face_tf, axis = 0)
# Embedding face
vec = model2.predict(face_tf)
# Tìm kiếm ảnh gần nhất
name = _most_similarity(X_train_vec, vec, y_au)
# Tìm kiếm các bbox
(startY, startX, endY, endX) = bbox
minX, maxX = min(startX, endX), max(startX, endX)
minY, maxY = min(startY, endY), max(startY, endY)
pred_proba=0.891
text = "{}: {:.2f}%".format(name, pred_proba * 100)
y = startY - 10 if startY - 10 > 10 else startY + 10
cv2.rectangle(image, (minX, minY), (maxX, maxY), (0, 0, 255), 2)
cv2.putText(image, text, (minX, y),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
except:
print("Not found face")
cv2.imwrite(IMAGE_OUTPUT, image)
# import matplotlib.pyplot as plt
plt.figure(figsize = (16, 8))
img = plt.imread(IMAGE_OUTPUT)
plt.imshow(img)
Như vậy qua bài thực hành này tôi đã hướng dẫn cho các bạn cách thức:
Ngoài ra khi xây dựng một ứng dụng nhận diện khuôn mặt, mô hình của các bạn có thể dự báo thiếu chính xác. Nguyên nhân xuất phát chủ yếu từ dữ liệu dự báo và dữ liệu huẫn luyện của chúng ta có phân phối quá khác biệt về màu sắc, cường độ các điểm ảnh, hình dạng khuôn mặt. Khi đó một số biện pháp khắc phục đó là:
Nên đồng nhất điều kiện chụp ảnh cho các bức ảnh huấn luyện. Không lấy các ảnh bị nhiễu sáng, các ảnh có phân phối cường độ sáng khác biệt với phần còn lại. Trong bài này, bộ dữ liệu của chúng ta được thu thập trên mạng nên các ảnh rất khác biệt về cường độ sáng. Tôi đã huấn luyện lại model với bộ dữ liệu YALE thì kết quả đạt được 100% độ chính xác. Bạn đọc có thể thử nghiệm theo hướng dẫn: facenet training YALE dataset.
Nên lấy ảnh với nhiều góc độ và trạng thái khác nhau.