Note: see the link below for the English version of this article.
https://duongnt.com/hdf5-with-h5py
Thông thường, bộ training data của ta càng lớn thì model của ta càng có độ chính xác cao. Nhưng nếu training data quá lớn thì ta không thể chứa trọn nó vào trong memory. Mặc dù ta có thể dùng generator để đọc từng phần dữ liệu từ thiết bị lưu trữ, quá trình truy xuất sẽ gây ảnh hưởng lớn tới hiệu năng.
Trong bài hôm nay, chúng ta sẽ sử dụng định dạng HDF5 để cải thiện tốc độ xử lý dữ liệu. Tất cả code trong bài sử dụng package h5py và được viết bằng Python. Các bạn có thể download phần code tại đường link dưới đây.
https://gist.github.com/duongntbk/8f5828f74b082d6c5136790498ab8023
Bộ dữ liệu dùng cho thử nghiệm
Lưu ý là nội dung của dữ liệu test không quan trọng, chúng ta chỉ quan tâm tới cách lưu và đọc nó bằng định dạng HDF5. Các bạn có thể sử dụng bất kỳ bộ dữ liệu nào sẵn có.
Trong trường hợp không có sẵn dữ liệu, các bạn có thể tải về bộ ảnh Pokemon từ đường link này. Bộ dữ liệu này bao gồm 819 ảnh với định dạng JPEG và PNG. Mỗi ảnh có độ phân giải 256×256 pixel với 3 kênh màu. Hôm nay, ta sẽ xử lý ảnh JPEG trong folder pokemon_jpg
.
Để mô phỏng một bộ dữ liệu training, chúng ta sẽ chia số ảnh này thành 2 nhãn khác nhau. Ta tạo 2 folder con trong pokemon_jpg
và chuyển một nửa số ảnh vào trong mỗi folder.
.
pokemon_jpg
└── 0 (chuyển khoảng 400 ảnh vào folder này)
└── 1.jpg
└── ...
└── x.png
└── 1 (chuyển số ảnh còn lại vào folder này)
└── y.png
└── ...
└── z.png
Đọc dữ liệu trực tiếp từ thiết bị lưu trữ
Keras có hỗ trợ sẵn việc đọc dữ liệu ảnh trực tiếp từ thiết bị lưu trữ thông qua hàm image_dataset_from_directory.
from tensorflow.keras.preprocessing import image_dataset_from_directory
dataset = image_dataset_from_directory(
directory='pokemon_jpg',
color_mode='rgb',
batch_size=32,
image_size=(256,256)
)
Ta có thể dùng dataset
để train model.
dataset = dataset.repeat() # Lặp lại ảnh đầu tiên khi ta đọc đến ảnh cuối cùng trong dataset
model.fit(dataset, batch_size=32, epochs=50)
Hoặc ta có thể tự mình duyệt qua tất cả các tensor và nhãn trong dataset
.
for sample, labels in dataset:
print(sample.shape, labels.shape)
# Code để dừng vòng lặp
Sử dụng định dạng HDF5 với package h5py
HDF5 là một định dạng được thiết kế để lưu trữ lượng lớn dữ liệu một cách có trật tự. Nó cho phép ta truy xuất dữ liệu đó nhanh nhất và hiệu quả nhất có thể. Các bạn có thể đọc thêm về định dạng HDF5 từ đường link này. Ta sẽ dùng package h5py để đọc và ghi file HDF5. Các bạn có thể cài h5py bằng lệnh sau.
pip install h5py
Chuyển training data sang định dạng HDF5
Đầu tiên, ta cần tạo một file .hdf5
, đồng thời ta cần tạo 2 dataset để lưu dữ liệu và lưu nhãn.
db = h5py.File('pokemon_jpeg.hdf5', 'w')
data = db.create_dataset('data', (819, 256, 256, 3), dtype='float32') # Chúng ta có 819 ảnh, mỗi ảnh có độ phân giải là 256x256 và 3 kênh màu
labels = db.create_dataset('labels', 819, dtype = 'int') # Mỗi ảnh trong 819 ảnh đó có một nhãn tương ứng
Sau đó, ta cần đọc từng ảnh và chuyển nội dung ảnh sang dạng tensor.
from tensorflow.keras.preprocessing.image import img_to_array, load_img
image_path_1 = '/pokemon_jpg/0/1.jpg' # The label for this image is 0
image_1 = load_img(image_path, target_size=(256,256), interpolation='bilinear')
image_1 = img_to_array(image, data_format='channels_last')
image_path_2 = '/pokemon_jpg/1/401.jpg' # The label for this image is 1
image_2 = load_img(image_path, target_size=(256,256), interpolation='bilinear')
image_2 = img_to_array(image, data_format='channels_last')
# ...
Rồi ta có thể lưu các tensor và nhãn tương ứng vào file HDF5.
data[0] = image_1
label[0] = 0
data[1] = image_2
label[1] = 1
# ...
Bước cuối cùng ta cần làm là đóng file HDF5.
db.close()
Lưu ý là file HDF5 lớn hơn nhiều so với file ảnh bản đầu. Folder pokemon_jpg
chỉ nặng 33MB nhưng file pokemon_jpeg.hdf5
nặng tới 614MB.
Chú ý: Tất nhiên là trong bài toán thực tế ta sẽ không ghi lần lượt từng file tensor vào file HDF5. Thay vào đó, ta sẽ dùng buffer để giảm số lần ghi. Xin tham khảo đoạn code này.
Đọc dữ liệu từ file HDF5
Việc đọc dữ liệu từ file HDF5 là tương đối đơn giản. Bước đầu tiên là kết nối tới file HDF5.
db = h5py.File('pokemon_jpeg.hdf5')
Sau đó, chúng ta có thể đọc tất cả dữ liệu và nhãn tương ứng thông qua object db
.
images = db['data'][0:10] # Đọc tensor của 10 ảnh đầu tiên
labels = db['labels'][0:10] # Đọc 10 nhãn tương ứng với 10 ảnh đó
Lưu ý là h5py không tải toàn bộ file HDF5 vào trong memory cùng lúc, nó chỉ tải từng phần dữ liệu cần thiết.
Tạo generator từ file HDF5
Để sử dụng dữ liệu từ file HDF5 với lệnh fit
của Keras, ta cần tạo một generator. Dưới đây là một giải pháp đơn giản.
def create_hdf5_generator(db_path, batch_size):
db = h5py.File(db_path)
db_size = db['data'].shape[0]
while True: # lặp lại từ tensor đầu tiên sau khi đến cuối file
for i in np.arange(0, db_size, batch_size):
images = db['data'][i:i+batch_size]
labels = db['labels'][i:i+batch_size]
yield images, labels
Ta có thể thử chạy generator như sau.
db_path = 'pokemon_jpeg.hdf5'
batch_size = 32
hdf5_gen = create_hdf5_generator(db_path, batch_size)
samples, labels = next(hdf5_gen)
print(samples.shape) # In ra (32, 256, 256, 3)
print(labels) # In ra 32
Ta cũng có thể dùng generator để train model.
model.fit(hdf5_gen, batch_size=32, epochs=50)
Test thử hiệu năng
Hãy thử so sánh hiệu năng của việc sử dụng file HDF5 với việc đọc ảnh trực tiếp từ thiết bị lưu trữ. Ta sẽ sử dụng hàm timeit để đo tốc độ xử lý.
import timeit
dataset = image_dataset_from_directory(
directory='pokemon_jpg',
color_mode='rgb',
batch_size=32,
image_size=(256,256)
)
normal_gen = iter(dataset.repeat())
db_path = 'pokemon_jpeg.hdf5'
batch_size = 32
hdf5_gen = create_hdf5_generator(db_path, batch_size)
rs_normal = timeit.timeit(lambda: next(normal_gen), number=1000)
rs_hdf5 = timeit.timeit(lambda: next(hdf5_gen), number=1000)
print(f'Đọc từ thiết bị lưu trữ: {rs_normal}')
print(f'Dùng định dạng HDF5: {rs_hdf5}')
Trên máy của tôi, đoạn code trên cho kết quả như dưới đây. Có thể thấy rằng giải pháp sử dụng định dạng HDF5 chạy nhanh hơn 50% so với giải pháp đọc ảnh trực tiếp từ thiết bị lưu trữ.
Đọc từ thiết bị lưu trữ: 19.117717
Dùng định dạng HDF5: 12.248213200000002
Kết thúc
Như mọi bài toán tối ưu khác, để cải thiện một mặt này thì ta thường phải hy sinh một mặt nào đó khác. Định dạng HDF5 cho phép ta nhanh chóng đọc tensor từ thiết bị lưu trữ, nhưng nó cũng chiếm nhiều dung lượng hơn bản gốc. Về phía cá nhân, tôi tin rằng thời gian ta tiết kiệm được nhờ HDF5 là đáng giá hơn nhiều so với giá trị của lượng thiết bị lưu trữ cần bổ sung.
One Thought on “Lưu dữ liệu với định dạng HDF5 bằng h5py”