Mục tiêu của bài thực hành này nhằm trình bày các bước cơ bản trong một quy trình xây dựng mô hình mạng neuron tích chập (Convolutional Neural network), để giải quyết vấn đề nhận dạng/phân loại vật thể trong ảnh chụp. Thí dụ minh họa trong bài có nội dung là phân biệt 4 loại tế bào bạch cầu bao gồm Lympho bào, bạch cầu đa nhân trung tính (neutrophils), bạch cầu ái toan (eosinophil) và Monocyte từ ảnh chụp của chúng.
Bài toán mà chúng ta cần giải quyết có bản chất rất phức tạp, vì khác với những vật thể trong tự nhiên như quần áo, mèo, chó, hoa quả, cả 4 loại bạch cầu đều có hình dạng, màu sắc, kích thước tương tự nên rất dễ nhầm lẫn. Con người làm công việc nhận dạng này dựa vào kiến thức về tế bào học và kinh nghiệm trong khi máy tính chỉ nhìn mọi thứ bằng con số. Tuy nhiên, ta có thể thử giải quyết nó theo cách đơn giản, bằng công cụ có sẵn là package keras trong R.
Đầu tiên Nhi tải bộ dữ liệu blood-cells từ kaggle: Các bạn chỉ cần tạo 1 account để có thể tải data:
https://www.kaggle.com/paultimothymooney/blood-cells
Đây là 1 file ZIP với dung lượng khoảng 100 MB, sau khi giải nén file ZIP này trong một thư mục tùy chọn, thí dụ “/DeeplearningDATA/blood-cells/”, bạn sẽ thấy nó có 2 folders là dataset-master và dataset2-master, ta chỉ dùng folder dataset2-master.
Bên trong folder “/DeeplearningDATA/blood-cells/dataset2-master/images/”, có 3 folders khác tên là TRAIN, TEST, TEST_SIMPLE, sẽ lần lượt được sử dụng như nguồn dữ liệu Training, Validation (trong quá trình huấn luyện) và Test (kiểm định).
Bên trong mỗi folder, bạn sẽ thấy có 4 folders khác có tên là EOSINOPHIL, LYMPHOCYTE, MONOCYTE và NEUTROPHIL; cho thấy tác giả của bộ dữ liệu đã phân loại sẵn những hình ảnh cho chúng ta, theo từng folder riêng biệt.
Khi mở 4 folders này, ta có thể thấy bên trong chứa hàng trăm file ảnh với kích thước 320x240 pixels , nội dung của những tấm ảnh này cho thấy chúng là kết quả của quy trình xử lý “tăng cường”(augmente) nhằm tạo ra rất nhiều phiên bản từ một số ít ảnh gốc, và áp dụng các thủ thuật như kéo dãn, phóng đại, thu nhỏ, xoay một cách ngẫu nhiên.
Với bài toán này, mục tiêu nhận diện là tế bào máu - một vật thể hình cầu có thể xoay tự do và bị ép dẹt nên các thủ thuật nêu trên rất quan trọng để có thể bao quát toàn bộ những hình dạng khả dĩ của bạch cầu.
Bước chuẩn bị dữ liệu đã xong, bây giờ chúng ta bắt đầu thí nghiệm nhé.
Trước hết, bạn cần install Python và sau đó là R package keras, bạn có thể làm theo hướng dẫn tại: https://keras.rstudio.com/index.html
keras là R package chính ta sẽ dùng trong thí nghiệm.
Đầu tiên, Nhi sẽ xác định trước cấu trúc của tensor (định dạng dữ liệu đầu vào của mô hình).Vì dữ liệu gốc là ảnh màu, ta cần 3 kênh để mã hóa cho mỗi màu R,G,B; nên channels=3. Chiều rộng và cao của ảnh sẽ được thu gọn và chuẩn hóa thành 1 hình vuông 150 x 150. Như vậy bất kể ảnh gốc có kích thước bao nhiêu, cũng sẽ được chuyển về kích thước chuẩn này trước khi đi vào mô hình.
img_width <- 150L
img_height <- 150L
target_size <- c(img_width, img_height)
channels <- 3
rbind(img_width,img_height,channels)
## [,1]
## img_width 150
## img_height 150
## channels 3
Tiếp theo, Nhi chuẩn bị danh sách các labels và số label cho mô hình, trong 2 object cell_list và output_n
cell_list<-c("EOSINOPHIL","LYMPHOCYTE","MONOCYTE","NEUTROPHIL")
output_n <- length(cell_list)
cell_list
## [1] "EOSINOPHIL" "LYMPHOCYTE" "MONOCYTE" "NEUTROPHIL"
Dữ liệu gốc trong thí nghiệm này là ảnh chụp, do đó chắc các bạn cũng có thể hình dung là keras phải có một quy trình để chuyển file ảnh thành một dòng chảy dữ liệu dưới dạng array (tensor), tự động và liên tục trong quá trình huấn luyện và kiểm định mô hình.
Bước đầu tiên trong quy trình này, ta cần 1 con đường để keras biết địa chỉ của những tấm ảnh sẽ được khai thác:
train_image_files_path <- "/DeeplearningDATA/blood-cells/dataset2-master/images/TRAIN"
valid_image_files_path <- "/DeeplearningDATA/blood-cells/dataset2-master/images/TEST"
rbind(train_image_files_path,valid_image_files_path)
## [,1]
## train_image_files_path "/DeeplearningDATA/blood-cells/dataset2-master/images/TRAIN"
## valid_image_files_path "/DeeplearningDATA/blood-cells/dataset2-master/images/TEST"
Bước tiếp theo của quy trình, đó là viết hàm để chuyển ảnh thành array (tensor), bằng hàm flow_images_from_directory, thứ bậc của quy trình này bao gồm: nhận diện địa chỉ (folder chứa ảnh), tải toàn bộ ảnh trong folder đó, thu nhỏ kích thước còn 150x150, chuyển kết quả thành 2D Tensor,chuyển thang đo của mỗi kênh từ 0:255 thành 0:1; dán label (classes); tạo batch với kích thước 30
train_datagen <- image_data_generator(rescale = 1/255)
validation_datagen <- image_data_generator(rescale = 1/255)
train_generator <- flow_images_from_directory(
train_image_files_path,
train_datagen,
target_size = c(150, 150),
batch_size = 30,
class_mode = "categorical",
classes=cell_list,
seed=123
)
validation_generator <- flow_images_from_directory(
valid_image_files_path,
validation_datagen,
target_size = c(150, 150),
batch_size = 30,
class_mode = "categorical",
classes=cell_list,
seed=123
)
batch <- generator_next(train_generator)
str(batch)
## List of 2
## $ : num [1:30, 1:150, 1:150, 1:3] 0 0 0 0.0157 0.0392 ...
## $ : num [1:30, 1:4] 1 0 0 0 1 0 1 0 0 0 ...
Đến đây, chuyện dữ liệu đã tạm ổn, Nhi bắt đầu thiết kế cấu trúc của mạng neuron sẽ được dùng cho mô hình; các bạn có thể tạm dừng để tìm hiểu về một số khái niệm của Convolutional neural network , đặc biệt là cơ chế của 3 layers: convolution2D, max_pooling, flatten. Một số bài viết dễ hiểu có thể đọc tại đây:
https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/
http://colah.github.io/posts/2014-07-Conv-Nets-Modular/
https://medium.freecodecamp.org/an-intuitive-guide-to-convolutional-neural-networks-260c2de0a050
Sau khi đọc những bài này, ta có thể hình dung về cơ chế của một mạng neuron CNN, và nhận ra rằng có thể ghép nối tiếp nhiều lớp convolution+pooling với nhau trước khi dẫn đến flatten layer để tổng hợp giá trị của các features, và tiếp theo là một hay nhiều layer_dense để xuất kết quả
Trong keras, còn một thủ thuật khác cần sử dụng đó là dropout layer ,có công dụng giảm nguy cơ overfitting cho mỗi layer, ta đặt dropout sau mỗi tổ hợp conv2d và pooling, hoặc dense.
Nhi dựng một CNN có cấu trúc khá sâu, với 3 lớp convolution2D+pooling, kích thước tăng dần từ 32-64-128, với kernel 3x3, hàm kích hoạt là RELU; mỗi lớp kèm theo 1 lớp dropout 0.25. Lớp cuối cùng xuất kết quả gồm 4 neurons, dùng hàm softmax.
Sau đó Model được compile với hàm loss là categorical_crossentropy cho bài toán multiclass, hàm optimizer là rmsprop với tùy chỉnh lr=1e-4, decay=1e-6; tiêu chí huấn luyện là Accuracy.
model <- keras_model_sequential() %>%
layer_conv_2d(filters = 32, kernel_size = c(3, 3), activation = "relu",
input_shape = c(150, 150, 3)) %>%
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
layer_dropout(0.25) %>%
layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = "relu") %>%
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
layer_dropout(0.25) %>%
layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = "relu") %>%
layer_max_pooling_2d(pool_size = c(2, 2)) %>%
layer_dropout(0.25) %>%
layer_flatten() %>%
layer_dense(units = 128, activation = "relu") %>%
layer_dropout(0.5) %>%
layer_dense(output_n,activation = "softmax")
model %>% compile(
loss = "categorical_crossentropy",
optimizer = optimizer_rmsprop(lr = 0.0001, decay = 1e-6),
metrics = "accuracy"
)
model
## Model
## ___________________________________________________________________________
## Layer (type) Output Shape Param #
## ===========================================================================
## conv2d_1 (Conv2D) (None, 148, 148, 32) 896
## ___________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D) (None, 74, 74, 32) 0
## ___________________________________________________________________________
## dropout_1 (Dropout) (None, 74, 74, 32) 0
## ___________________________________________________________________________
## conv2d_2 (Conv2D) (None, 72, 72, 64) 18496
## ___________________________________________________________________________
## max_pooling2d_2 (MaxPooling2D) (None, 36, 36, 64) 0
## ___________________________________________________________________________
## dropout_2 (Dropout) (None, 36, 36, 64) 0
## ___________________________________________________________________________
## conv2d_3 (Conv2D) (None, 34, 34, 128) 73856
## ___________________________________________________________________________
## max_pooling2d_3 (MaxPooling2D) (None, 17, 17, 128) 0
## ___________________________________________________________________________
## dropout_3 (Dropout) (None, 17, 17, 128) 0
## ___________________________________________________________________________
## flatten_1 (Flatten) (None, 36992) 0
## ___________________________________________________________________________
## dense_1 (Dense) (None, 128) 4735104
## ___________________________________________________________________________
## dropout_4 (Dropout) (None, 128) 0
## ___________________________________________________________________________
## dense_2 (Dense) (None, 4) 516
## ===========================================================================
## Total params: 4,828,868
## Trainable params: 4,828,868
## Non-trainable params: 0
## ___________________________________________________________________________
Nhi bắt đầu huấn luyện cho mô hình trên đây bằng hàm fit, với tùy chỉnh epochs=30, khai báo dữ liệu train= train_generator ; 200 steps cho mỗi epoch, và validation_data=validation_generator; validation steps=50,
Việc huấn luyện này rất chậm, vì máy tính của Nhi không có GPU, mà chỉ dùng CPU (Core i7). Nhi mất hơn 1h để chạy 30 epochs. Kết quả cuối cùng là Accuracy đạt khoảng 72%, một kết quả khiêm tốn.
Nhi save lại mô hình cuối cùng dưới định dạng hdf5
bestmodel <- load_model_hdf5(filepath = "/DeeplearningDATA/blood-cells/dataset2-master/bloodcells.h5")
bestmodel
## Model
## ___________________________________________________________________________
## Layer (type) Output Shape Param #
## ===========================================================================
## conv2d_12 (Conv2D) (None, 148, 148, 32) 896
## ___________________________________________________________________________
## max_pooling2d_12 (MaxPooling2D) (None, 74, 74, 32) 0
## ___________________________________________________________________________
## dropout_16 (Dropout) (None, 74, 74, 32) 0
## ___________________________________________________________________________
## conv2d_13 (Conv2D) (None, 72, 72, 64) 18496
## ___________________________________________________________________________
## max_pooling2d_13 (MaxPooling2D) (None, 36, 36, 64) 0
## ___________________________________________________________________________
## dropout_17 (Dropout) (None, 36, 36, 64) 0
## ___________________________________________________________________________
## conv2d_14 (Conv2D) (None, 34, 34, 128) 73856
## ___________________________________________________________________________
## max_pooling2d_14 (MaxPooling2D) (None, 17, 17, 128) 0
## ___________________________________________________________________________
## dropout_18 (Dropout) (None, 17, 17, 128) 0
## ___________________________________________________________________________
## flatten_5 (Flatten) (None, 36992) 0
## ___________________________________________________________________________
## dense_9 (Dense) (None, 128) 4735104
## ___________________________________________________________________________
## dropout_19 (Dropout) (None, 128) 0
## ___________________________________________________________________________
## dense_10 (Dense) (None, 4) 516
## ===========================================================================
## Total params: 4,828,868
## Trainable params: 4,828,868
## Non-trainable params: 0
## ___________________________________________________________________________
Chúng ta có thể thử trích xuất một số features hay kết quả mà mỗi layer trong mạng neuron, để biết mô hình đã nhìn thấy gì từ ảnh chụp.
img_path <- "/DeeplearningDATA/blood-cells/dataset2-master/images/TEST_SIMPLE/EOSINOPHIL/_7_2284.jpeg"
img <- image_load(img_path, target_size = c(150, 150))
img_tensor <- image_to_array(img)
img_tensor <- array_reshape(img_tensor, c(1, 150, 150, 3))
img_tensor <- img_tensor / 255
layer_outputs <- lapply(bestmodel$layers[1:8], function(layer) layer$output)
activation_model <- keras_model(inputs = bestmodel$input, outputs = layer_outputs)
activations <- activation_model %>% predict(img_tensor)
plot_channel <- function(channel) {
rotate <- function(x) t(apply(x, 2, rev))
image(rotate(channel), axes = FALSE, asp = 1,
col = terrain.colors(12))
}
Thí dụ với 1 ảnh chụp bạch cầu ái toan, mỗi layer cho ra kết quả như sau
image_size <- 50
images_per_row <- 10
for (i in 1:8) {
layer_activation <- activations[[i]]
layer_name <- bestmodel$layers[[i]]$name
n_features <- dim(layer_activation)[[4]]
n_cols <- n_features %/% images_per_row
png(paste0("cell_activations_", i, "_", layer_name, ".png"),
width = image_size * images_per_row,
height = image_size * n_cols)
op <- par(mfrow = c(n_cols, images_per_row), mai = rep_len(0.02, 4))
for (col in 0:(n_cols-1)) {
for (row in 0:(images_per_row-1)) {
channel_image <- layer_activation[1,,,(col*images_per_row) + row + 1]
plot_channel(channel_image)
}
}
par(op)
dev.off()
}
Sau lớp pooling thứ nhất:
Sau lớp conv2D và maxpooling thứ 2
Và lớp cov2D + maxpooling thứ 3:
Sau khi huấn luyện xong mô hình, Nhi thử áp dụng nó để phân loại cho 4 ảnh chụp ngẫu nhiên từ folder TEST_SIMPLE.
eos<-image_load("/DeeplearningDATA/blood-cells/dataset2-master/images/TEST_SIMPLE/EOSINOPHIL/_7_2284.jpeg",
target_size = c(150, 150))%>%
image_to_array()%>%
array_reshape(c(1, 150, 150, 3))
plot(as.raster(eos[1,,,]/255))
bestmodel %>% predict(eos/255)
## [,1] [,2] [,3] [,4]
## [1,] 0.5379255 0.02376844 0.1272769 0.3110292
lymp<-image_load("/DeeplearningDATA/blood-cells/dataset2-master/images/TEST_SIMPLE/LYMPHOCYTE/_2_6981.jpeg",
target_size = c(150, 150))%>%
image_to_array()%>%
array_reshape(c(1, 150, 150, 3))
plot(as.raster(lymp[1,,,]/255))
bestmodel %>% predict(lymp/255)
## [,1] [,2] [,3] [,4]
## [1,] 0.02128963 0.6865467 0.2906503 0.001513446
neut<-image_load("/DeeplearningDATA/blood-cells/dataset2-master/images/TEST_SIMPLE/NEUTROPHIL/_0_1966.jpeg",
target_size = c(150, 150))%>%
image_to_array()%>%
array_reshape(c(1, 150, 150, 3))
plot(as.raster(neut[1,,,]/255))
bestmodel %>% predict(neut/255)
## [,1] [,2] [,3] [,4]
## [1,] 0.5322058 0.1002449 0.1154476 0.2521017
mono<-image_load("/DeeplearningDATA/blood-cells/dataset2-master/images/TEST_SIMPLE/MONOCYTE/_3_9457.jpeg",
target_size = c(150, 150))%>%
image_to_array()%>%
array_reshape(c(1, 150, 150, 3))
plot(as.raster(mono[1,,,]/255))
bestmodel %>% predict(mono/255)
## [,1] [,2] [,3] [,4]
## [1,] 0.002869422 1.983579e-05 0.9971063 4.480792e-06
Bài thực hành đến đây là hết, chúng ta vừa sử dụng keras để thực hiện một mô hình CNN cho phép nhận diện 4 loại tế bào bạch cầu trong ảnh chụp.
Do giới hạn về kỹ thuật, Nhi chỉ có thể trình bày những bước cơ bản nhất, nhưng chúng cũng là những điểm mấu chốt quan trọng giúp cho các bạn làm quen với quy trình Deep learning với dữ liệu là ảnh chụp. Trên thực tế, khi bắt đầu nghiên cứu từ số 0, nhiều công sức phải bỏ ra và cần sự hợp tác của cả một tập thể - để có thể chuẩn bị, xử lý và phân loại được ảnh chụp cho dữ liệu gốc, với số lượng từ vài trăm đến vài ngàn ảnh.
Hy vọng bài thực hành tạo được cảm hứng và sự khích lệ cho các bạn để tìm hiểu sâu hơn về Deep learning.