1. Chuẩn bị môi trường và tải mô hình mẫu MobileNetV2
1.1. Cài đặt TensorFlow và TensorFlow Lite
Trước tiên, bạn cần cài đặt các thư viện Python cần thiết để xử lý việc huấn luyện, tải model và chuyển đổi định dạng. Chúng ta sẽ sử dụng phiên bản TensorFlow ổn định nhất cho server Linux.
Để đảm bảo tính tương thích với WebAssembly sau này, ta cần cài đặt `tensorflow` (core) và `tensorflow-lite` (runtime và converter).
pip install --upgrade pip
pip install tensorflow tensorflow-lite numpy
Kết quả mong đợi: Không có lỗi cài đặt, các thư viện được install thành công và sẵn sàng để import trong Python.
1.2. Tải mô hình MobileNetV2 từ TensorFlow Hub
Thay vì huấn luyện từ đầu tốn nhiều tài nguyên, ta sẽ tải một mô hình MobileNetV2 đã được huấn luyện sẵn trên tập dữ liệu ImageNet. Đây là mô hình cân bằng tốt giữa kích thước và độ chính xác, phù hợp cho việc deploy trên browser.
Tạo script Python `download_model.py` để tải model về dạng `SavedModel` (định dạng chuẩn của TensorFlow trước khi convert).
import tensorflow as tf
from tensorflow import hub
from tensorflow import keras
# Đường dẫn lưu model dưới dạng SavedModel
MODEL_PATH = "./mobilenet_v2_savedmodel"
# URL model MobileNetV2 từ TensorFlow Hub
MODEL_URL = "https://tfhub.dev/tensorflow/mobilenet_v2/feature_vector/1"
print("Đang tải mô hình MobileNetV2 từ TensorFlow Hub...")
hub.load(MODEL_URL)
model = keras.models.load_model(MODEL_URL, compile=False)
# Lưu model dưới dạng SavedModel
model.save(MODEL_PATH)
print(f"Model đã được lưu vào: {MODEL_PATH}")
Kết quả mong đợi: Script chạy xong, xuất hiện thư mục `mobilenet_v2_savedmodel` chứa các file `saved_model.pb` và thư mục `variables`.
2. Chuyển đổi mô hình từ SavedModel sang TensorFlow Lite (.tflite)
2.1. Cấu hình TFLiteConverter
Bước này sử dụng `TFLiteConverter` để biến đổi graph tính toán từ định dạng SavedModel sang định dạng `.tflite` nhẹ hơn, tối ưu cho edge device.
Ta cần chỉ định input/output của model để converter hiểu đúng luồng dữ liệu. Với MobileNetV2 từ TF Hub, input thường là tensor ảnh 224x224x3.
import tensorflow as tf
import numpy as np
MODEL_PATH = "./mobilenet_v2_savedmodel"
TFLITE_PATH = "./mobilenet_v2_float32.tflite"
# Load model từ SavedModel
model = tf.saved_model.load(MODEL_PATH)
# Khởi tạo Converter
converter = tf.lite.TFLiteConverter.from_saved_model(MODEL_PATH)
# Cấu hình Input Shape (Batch size = 1, Height = 224, Width = 224, Channels = 3)
# Đây là bước quan trọng để cố định shape, giúp Wasm biên dịch tốt hơn
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS]
# Convert
tflite_model = converter.convert()
# Lưu file .tflite
with open(TFLITE_PATH, 'wb') as f:
f.write(tflite_model)
print(f"File .tflite đã được tạo: {TFLITE_PATH}")
print(f"Kích thước file: {len(tflite_model)} bytes")
Kết quả mong đợi: File `mobilenet_v2_float32.tflite` được tạo ra, kích thước khoảng 14-15MB (chưa quantization).
2.2. Kiểm tra cú pháp file .tflite
Sử dụng công cụ dòng lệnh `tflite_convert` hoặc script Python để verify file vừa tạo có thể load được mà không bị lỗi cấu trúc.
import tensorflow as tf
TFLITE_PATH = "./mobilenet_v2_float32.tflite"
interpreter = tf.lite.Interpreter(model_path=TFLITE_PATH)
interpreter.allocate_tensors()
print("Kiểm tra Input Details:")
input_details = interpreter.get_input_details()
print(f"Shape: {input_details[0]['shape']}")
print(f"Type: {input_details[0]['dtype']}")
print("Kiểm tra Output Details:")
output_details = interpreter.get_output_details()
print(f"Shape: {output_details[0]['shape']}")
print(f"Type: {output_details[0]['dtype']}")
Kết quả mong đợi: In ra thông tin Input có shape `[1, 224, 224, 3]` và dtype `float32`, Output có shape `[1, 1280]`.
3. Tối ưu hóa mô hình bằng Quantization
3.1. Thực hiện Post-training Quantization (Float to Int8)
Để mô hình chạy nhanh trên WebAssembly, ta cần giảm độ chính xác từ 32-bit float (float32) xuống 8-bit integer (int8). Kỹ thuật này gọi là Post-training Quantization.
Phương pháp này không cần lại huấn luyện (retraining), chỉ cần một tập dữ liệu nhỏ để calibrate (điều chỉnh) các giá trị min/max. Nó giúp giảm kích thước file xuống 4 lần và tăng tốc độ suy luận đáng kể.
import tensorflow as tf
import numpy as np
MODEL_PATH = "./mobilenet_v2_savedmodel"
QUANTIZED_TFLITE_PATH = "./mobilenet_v2_int8.tflite"
# Load model
converter = tf.lite.TFLiteConverter.from_saved_model(MODEL_PATH)
# Cấu hình Quantization
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS]
# Bật chế độ quantization động hoặc tĩnh (Static Quantization)
# Để có hiệu năng tốt nhất cho Wasm, ta dùng Static Quantization
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
# Hàm calibrate_data: Tạo dữ liệu mẫu để converter học giá trị min/max
# Trong thực tế, bạn nên dùng tập validation thật. Ở đây dùng dummy data ngẫu nhiên.
def representative_data_gen():
for _ in range(100):
# Tạo ảnh ngẫu nhiên 224x224x3 với giá trị 0-255 (uint8)
input_data = np.random.randint(0, 255, (1, 224, 224, 3), dtype=np.uint8)
yield [input_data]
converter.representative_dataset = representative_data_gen
# Chuyển đổi
quantized_model = converter.convert()
# Lưu file
with open(QUANTIZED_TFLITE_PATH, 'wb') as f:
f.write(quantized_model)
print(f"Model quantized đã được lưu: {QUANTIZED_TFLITE_PATH}")
print(f"Kích thước file: {len(quantized_model)} bytes")
Kết quả mong đợi: File `mobilenet_v2_int8.tflite` được tạo, kích thước giảm xuống khoảng 3.5MB - 4MB.
3.2. Xử lý lỗi khi Quantization (Fallback)
Đôi khi model sử dụng các op không hỗ trợ Int8. Nếu script trên bị lỗi, ta cần thêm `converter.target_spec.supported_ops` hoặc dùng `converter.allow_custom_ops = True` (tùy vào op cụ thể).
Tuy nhiên, với MobileNetV2 chuẩn, bước trên thường chạy mượt. Nếu gặp lỗi "Op not supported", hãy thử thêm dòng sau vào trước `converter.convert()`:
converter._experimental_lower_tensor_list_ops = False
Kết quả mong đợi: Script chạy thành công hoặc báo lỗi rõ ràng để debug op không hỗ trợ.
4. Kiểm tra độ chính xác sau khi chuyển đổi và tối ưu
4.1. So sánh kích thước file
Thực hiện lệnh `ls -lh` để so sánh trực quan kích thước giữa file float32 và int8.
ls -lh *.tflite
Kết quả mong đợi: File `.int8.tflite` nhỏ hơn khoảng 4 lần so với file `.float32.tflite`.
4.2. So sánh độ chính xác (Accuracy Verification)
Độ chính xác sau khi quantization có thể giảm nhẹ (thường < 1-2%). Ta cần viết script chạy inference trên cùng một tập ảnh mẫu cho cả hai model để so sánh kết quả.
Tạo script `verify_accuracy.py` để tải ảnh mẫu, chạy qua cả 2 model và so sánh dự đoán (prediction).
import tensorflow as tf
import numpy as np
from PIL import Image
# Load các model
model_float = tf.lite.Interpreter(model_path="./mobilenet_v2_float32.tflite")
model_int8 = tf.lite.Interpreter(model_path="./mobilenet_v2_int8.tflite")
model_float.allocate_tensors()
model_int8.allocate_tensors()
# Lấy input/output details
float_input = model_float.get_input_details()[0]['index']
float_output = model_float.get_output_details()[0]['index']
int8_input = model_int8.get_input_details()[0]['index']
int8_output = model_int8.get_output_details()[0]['index']
# Hàm chuẩn hóa ảnh
def preprocess_image(image_path):
img = Image.open(image_path).resize((224, 224))
img_array = np.array(img, dtype=np.float32)
# Chuẩn hóa về [-1, 1] cho MobileNetV2
img_array = img_array / 127.5 - 1.0
return np.expand_dims(img_array, axis=0)
# Tạo ảnh test giả lập (nếu chưa có ảnh thật)
test_img = preprocess_image("./test_sample.jpg") # Cần có file ảnh này
# Chạy inference Float32
model_float.set_tensor(float_input, test_img)
model_float.invoke()
pred_float = model_float.get_tensor(float_output)
# Chạy inference Int8 (Cần dequantize đầu vào và đầu ra)
# Input của Int8 model thường đã được scale sẵn, nhưng ta cần cung cấp dữ liệu đúng dạng uint8
# Ở đây ta giả định model đã được quantize với input uint8 [0, 255]
# Cần convert lại ảnh từ [-1, 1] về [0, 255] để feed vào model int8
img_uint8 = (test_img * 127.5 + 127.5).astype(np.uint8)
model_int8.set_tensor(int8_input, img_uint8)
model_int8.invoke()
pred_int8 = model_int8.get_tensor(int8_output)
# So sánh kết quả (Top-1 prediction)
# Lấy chỉ số của giá trị lớn nhất
top1_float = np.argmax(pred_float)
top1_int8 = np.argmax(pred_int8)
print(f"Prediction Float32: {top1_float}")
print(f"Prediction Int8: {top1_int8}")
print(f"Độ chính xác khớp nhau: {top1_float == top1_int8}")
Kết quả mong đợi: Kết quả `top1_float` và `top1_int8` giống nhau hoặc rất gần nhau. Nếu khác nhau quá nhiều, cần xem lại hàm `representative_data_gen` ở bước 3.1.
4.3. Đo thời gian suy luận (Latency Check)
Dùng `time` module để đo thời gian chạy `invoke()` trên CPU của server.
import time
import tensorflow as tf
model = tf.lite.Interpreter(model_path="./mobilenet_v2_int8.tflite")
model.allocate_tensors()
input_details = model.get_input_details()[0]['index']
output_details = model.get_output_details()[0]['index']
# Tạo input mẫu
input_data = np.random.randint(0, 255, (1, 224, 224, 3), dtype=np.uint8)
# Warmup (chạy 1 lần để cache)
model.set_tensor(input_details, input_data)
model.invoke()
# Benchmark (chạy 100 lần)
start_time = time.time()
for _ in range(100):
model.set_tensor(input_details, input_data)
model.invoke()
end_time = time.time()
avg_latency = (end_time - start_time) / 100 * 1000 # ms
print(f"Average Latency (Int8): {avg_latency:.2f} ms")
Kết quả mong đợi: Latency trung bình thấp hơn đáng kể so với model Float32 (thường giảm 30-50% thời gian).
Điều hướng series:
Mục lục: Series: Xây dựng nền tảng Real-time AI với WebAssembly, TensorFlow Lite và Kubernetes
« Phần 2: Giới thiệu kiến trúc Real-time AI với WebAssembly
Phần 4: Xây dựng engine suy luận WebAssembly từ C++ »