Data generator với Keras

Tram Ho

Ở bài viết này mình sẽ viết về cách tạo Data Generator với Keras như thế nào. (nhạt quá =)) )

Mình sẽ viết gì ở bài này?

  • Tại sao lại là Data Generator
  • Thực Hành
  • Kết Luận
  • Reference

Tại sao lại là Data Generator

Trên thực tế không phải ai cũng có đủ tiền mua máy khủng và dữ liệu mình cần train chiếm nhiều Ram hơn dung lượng RAM thực tế mà máy chúng ta đang có. Vấn đề ở đây là khi chúng ta có một tập dữ liệu lớn và RAM không đủ để load vào cùng một lúc rồi sau đó chia tập train và test sau đó train model. Để giải quyết vấn đề này chúng ta cần chia nhỏ tập dữ liệu thành từng thư mục nhỏ sau đó load dữ liệu từng phần trong quá trình train model. Chúng ta có thể lựa chọn ăn mì bằng cách sử dụng ImageDatagenerator có sẵn của Keras. Hay chúng ta có thể tự chế biến món ăn theo cách mình mong muốn bằng cách custom Data Generator.

Ở bài viết này mình sẽ hướng dẫn bằng cách thực hành với tập Mnist.

Thực hành

Để custom Data Generator Keras có cung cấp cho chúng ta lớp Sequence (Sequence class) và cho phép chúng ta tạo các lớp có thể kế thừa từ nó.

Đầu tiên cần load tập dataset mnist.

import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras.utils import Sequence, to_categorical
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten, MaxPooling2D, Dropout
#load data mnist
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

Tập Mnist của chúng ta bao gồm 60000 ảnh cho tập train và 10000 ảnh cho tập test. Mỗi ảnh có kích thước là 28×28. Voi mỗi ảnh có type là float32 thì dung lượng mỗi ảnh khoảng 4 byte.
chúng ta sẽ cần 4 * (28*28) *70000 + (70000 * 10) ~ 220Mb RAM đó là theo tính toán nhưng trên thực tế có thể chúng ta sẽ mất nhiều hơn. Vì vậy việc lựa chọn Data Generator là hợp lý.

Data Generator

Hàm khởi tạo init()

    def __init__(self,
                 img_paths,
                 labels, 
                 batch_size=32,
                 dim=(224, 224),
                 n_channels=3,
                 n_classes=4,
                 shuffle=True):
        self.dim = dim
        self.batch_size = batch_size
        self.labels = labels
        self.img_paths = img_paths
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.shuffle = shuffle
        self.img_indexes = np.arange(len(self.img_paths))
        self.on_epoch_end()

img_paths: list toàn bộ ảnh
labels: nhãn của toàn bộ ảnh
batch_size: kích thước của 1 batch
img_indexes: index của các class
input_dim: (width, height) đầu vào của ảnh
n_channels: số lượng channels của ảnh
n_classes: số lượng các class
shuffle: có shuffle dữ liệu sau mỗi epoch hay không

on_epoch_end()

Mỗi khi end hoặc start một epoch hàm này sẽ quyết định có shuffle dữ liệu hay không

    def on_epoch_end(self):
        'Updates indexes after each epoch'
        self.indexes = np.arange(len(self.img_paths))
        if self.shuffle == True:
            np.random.shuffle(self.indexes)

len()

    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.img_indexes) / self.batch_size))

Trả về số lượng batch trên 1 epoch. Hàm len() là một built in function trong python. Chúng ta set giá trị thành:

Chính là số step trên một epoch chúng ta sẽ nhìn thấy khi train model.

get_item()

    def __getitem__(self, index):
        'Generate one batch of data'
        # tạo ra index cho từng batch
        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]

        #lấy list IDs trong 1 batch
        list_IDs_temps = [self.img_indexes[k] for k in indexes]

        # Generate data
        X, y = self.__data_generation(list_IDs_temps)
        return X, y

Hàm này sẽ tạo từng batch cho data theo thứ tự được truyền vào.

__data_generation()

    def __data_generation(self, list_IDs_temps):
        X = np.empty((self.batch_size, *self.dim))
        y = []
        for i, ID in enumerate(list_IDs_temps):
            X[i,] = self.img_paths[ID]
            X = (X/255).astype('float32')
            y.append(self.labels[ID])
        X = X[:,:,:, np.newaxis]
        return X, keras.utils.to_categorical(y, num_classes=10)

__data_generation() sẽ được gọi trực tiếp từ hàm get_item() để thực hiện các nhiệm vụ chính như đọc ảnh, xử lý dữ liệu và trả về dữ liệu theo ý mình mong muốn trước khi đưa vào train model.

DataGenerator class

Sau khi hiểu và định nghĩa được các hàm ở trên chúng ta sẽ được đoạn code hoàn chỉnh dưới đây.

class DataGenerator(Sequence):
    def __init__(self,
                 img_paths,
                 labels, 
                 batch_size=32,
                 dim=(224, 224),
                 n_channels=3,
                 n_classes=4,
                 shuffle=True):
        self.dim = dim
        self.batch_size = batch_size
        self.labels = labels
        self.img_paths = img_paths
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.shuffle = shuffle
        self.img_indexes = np.arange(len(self.img_paths))
        self.on_epoch_end()
        
    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.img_indexes) / self.batch_size))

    def __getitem__(self, index):
        'Generate one batch of data'
        # Generate indexes of the batch
        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]

        # Find list of IDs
        list_IDs_temps = [self.img_indexes[k] for k in indexes]

        # Generate data
        X, y = self.__data_generation(list_IDs_temps)
        return X, y
    def on_epoch_end(self):
        'Updates indexes after each epoch'
        self.indexes = np.arange(len(self.img_paths))
        if self.shuffle == True:
            np.random.shuffle(self.indexes)
    def __data_generation(self, list_IDs_temps):
        X = np.empty((self.batch_size, *self.dim))
        y = []
        for i, ID in enumerate(list_IDs_temps):
            X[i,] = self.img_paths[ID]
            X = (X/255).astype('float32')
            y.append(self.labels[ID])
        X = X[:,:,:, np.newaxis]
        return X, keras.utils.to_categorical(y, num_classes=10)

Khởi tạo data và Training model

Ở đây mình chỉ dùng mô hình phân loại đơn giản dưới đây:
Khởi tạo model

n_classes = 10
input_shape = (28, 28)
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=(28, 28 , 1)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(n_classes, activation='softmax'))
model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adam(),
              metrics=['accuracy'])

Khởi tạo dữ liệu train_generator và val_generator

train_generator = DataGenerator(x_train, y_train, batch_size = 32, dim = input_shape,
 n_classes=10, shuffle=True)
val_generator = DataGenerator(x_test, y_test, batch_size=32, dim = input_shape, 
n_classes= n_classes, shuffle=True)

Tiếp theo là train model.

model.fit_generator(
 train_generator,
 steps_per_epoch=len(train_generator),
 epochs=10,
 validation_data=val_generator,
 validation_steps=len(val_generator))

Kết Luận

Cảm ơn mọi người đã đọc bài viết của mình, nếu có gì chưa đúng mong nhận được sự góp ý từ mọi người!

Reference

https://stanford.edu/~shervine/blog/keras-how-to-generate-data-on-the-fly

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo