Note: see the link below for the English version of this article.

https://duongnt.com/hdf5-with-h5py

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.

A software developer from Vietnam and is currently living in Japan.

One Thought on “Lưu dữ liệu với định dạng HDF5 bằng h5py”

Leave a Reply