Chuyển đến nội dung chính

Tái tạo ảnh MRI não bằng Học sâu (Convolutional Autoencoder)

Trong hướng dẫn này, bạn sẽ học và hiểu cách đọc ảnh cộng hưởng từ (MRI) não định dạng nifti, tái tạo chúng bằng convolutional autoencoder.
· 13 phút đọc

Khám phá cùng AI

Bạn sẽ dùng bộ dữ liệu MRI não 3T để huấn luyện mạng. Để quan sát hiệu quả của mô hình, bạn sẽ kiểm thử mô hình trên:

  • Ảnh MRI 3T chưa từng thấy,
  • Ảnh MRI 3T có nhiễu và
  • Dùng thước đo định tính: Tỉ số đỉnh tín hiệu trên nhiễu (PSNR) để đánh giá hiệu năng của ảnh tái tạo.

Hướng dẫn này sẽ không đi sâu vào chi tiết phức tạp của ảnh y khoa mà tập trung vào khía cạnh học sâu! Lưu ý: Bài hướng dẫn này chủ yếu bao quát hiện thực hóa thực tiễn của convolutional autoencoders. Vì vậy, nếu bạn chưa nắm về mạng nơ-ron tích chập (CNN) và autoencoder, bạn có thể xem qua hướng dẫn về CNN và Autoencoder. Điểm hay nhất của hướng dẫn này là bạn sẽ nạp các thể tích 3D dưới dạng ảnh 2D và đưa thẳng vào mô hình. Tóm lại, bạn sẽ đề cập các chủ đề sau trong bài hôm nay:

  • Mở đầu là phần giới thiệu ngắn về Cộng hưởng từ (MRI),
  • Sau đó bạn sẽ tìm hiểu về bộ dữ liệu MRI não: gồm những loại ảnh nào, nhập các mô-đun, cách đọc ảnh, tạo mảng ảnh, tiền xử lý ảnh MRI não để đưa vào mô hình và cuối cùng là khám phá ảnh MRI não.
  • Trong phần hiện thực autoencoder tích chập: bạn sẽ huấn luyện mô hình với dữ liệu đã tiền xử lý, trực quan hóa đồ thị mất mát huấn luyện và xác thực, lưu mô hình đã huấn luyện và cuối cùng dự đoán trên tập kiểm thử.
  • Tiếp theo, bạn sẽ kiểm tra độ vững của mô hình đã huấn luyện sẵn bằng cách thêm nhiễu vào ảnh kiểm thử và xem mô hình thể hiện ra sao về mặt định lượng.
  • Cuối cùng, bạn sẽ kiểm tra dự đoán bằng thước đo định lượng tỉ số đỉnh tín hiệu trên nhiễu (PSNR) và đo lường hiệu năng của mô hình.

Giới thiệu ngắn về ảnh MR

Nhiều hệ thống được sử dụng trong chẩn đoán hình ảnh, từ máy MRI mở có cường độ từ trường 0,3 Tesla (T) đến hệ MRI chi với cường độ lên tới 1,0 T và máy quét toàn thân với cường độ đến 3,0 T (trong lâm sàng). Tesla là đơn vị đo cường độ từ trường của ảnh MR. Máy quét MR trường cao (7T, 11,5T) cho tỉ số tín hiệu trên nhiễu (SNR) cao hơn ngay cả với kích thước voxel nhỏ hơn (một mảng/ô lưới 3 chiều) và do đó được ưa chuộng cho chẩn đoán chính xác hơn.

Voxel nhỏ hơn dẫn đến độ phân giải tốt hơn, từ đó hỗ trợ chẩn đoán lâm sàng. Tuy nhiên, cường độ từ trường sử dụng trong máy quét MR đặt ra ngưỡng dưới cho kích thước voxel để duy trì SNR tốt, nhằm bảo toàn chi tiết ảnh MR.
Mặc dù chất lượng ảnh của 7T và 11,5T vượt trội, chúng hiếm khi được triển khai rộng rãi do hạn chế chi phí.

Theo các bài báo gần đây, số lượng máy quét 3T báo cáo vào khoảng ~20.000 so với chỉ ~40 máy 7T.

Tìm hiểu bộ dữ liệu MRI não 3T

Bộ dữ liệu MRI não gồm các thể tích 3D, mỗi thể tích có tổng cộng 207 lát/ảnh MRI chụp ở các lát cắt khác nhau của não. Mỗi lát có kích thước 173 x 173. Ảnh là ảnh xám một kênh. Có tổng cộng 30 đối tượng, mỗi đối tượng chứa ảnh quét MRI của một bệnh nhân. Định dạng ảnh không phải jpeg, png... mà là định dạng nifti. Bạn sẽ thấy ở phần sau cách đọc ảnh định dạng nifti.

Bộ dữ liệu gồm ảnh MR chế độ T1, các chuỗi T1 truyền thống được coi là tốt để đánh giá cấu trúc giải phẫu. Bộ dữ liệu hôm nay bạn sẽ làm việc là MRI não 3T.

Bộ dữ liệu là công khai và có thể tải về tại nguồn này.

Mẹo: nếu bạn muốn học cách hiện thực Multi-Layer Perceptron (MLP) cho bài toán phân loại với bộ MNIST, hãy xem hướng dẫn này.

Lưu ý : Trước khi bắt đầu, xin lưu ý mô hình sẽ được huấn luyện trên hệ thống dùng GPU Nvidia 1080 Ti, CPU Xeon e5 GeForce với RAM 32GB. Nếu bạn dùng Jupyter Notebook, bạn sẽ cần thêm ba dòng mã để chỉ định thứ tự thiết bị CUDA và thiết bị CUDA hiển thị bằng mô-đun os.

Trong đoạn mã dưới đây, về cơ bản bạn đặt biến môi trường trong notebook bằng os.environ. Nên làm điều này trước khi khởi tạo Keras để giới hạn backend TensorFlow của Keras dùng GPU đầu tiên. Nếu máy bạn huấn luyện có GPU ở 0, hãy dùng 0 thay vì 1. Bạn có thể kiểm tra bằng lệnh đơn giản trên terminal: ví dụ, nvidia-smi

import os
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="1" #model will be trained on GPU 1

Nhập các mô-đun

Đầu tiên, bạn nhập tất cả mô-đun cần thiết như cv2, numpy, matplotlib và quan trọng nhất là keras, vì hôm nay bạn sẽ dùng framework này!
Để đọc ảnh định dạng nifti, bạn cũng cần nhập mô-đun tên là nibabel.

import os
import cv2
from keras.layers import Input,Dense,Flatten,Dropout,merge,Reshape,Conv2D,MaxPooling2D,UpSampling2D,Conv2DTranspose
from keras.layers.normalization import BatchNormalization
from keras.models import Model,Sequential
from keras.callbacks import ModelCheckpoint
from keras.optimizers import Adadelta, RMSprop,SGD,Adam
from keras import regularizers
from keras import backend as K
Using TensorFlow backend.
import numpy as np
import scipy.misc
import numpy.random as rng
from PIL import Image, ImageDraw, ImageFont
from sklearn.utils import shuffle
import nibabel as nib #reading MR images
from sklearn.cross_validation import train_test_split
import math
import glob
from matplotlib import pyplot as plt
%matplotlib inline
/usr/local/lib/python3.5/dist-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)

Nạp dữ liệu

Bạn sẽ dùng mô-đun glob để trả về danh sách gồm tất cả các thể tích trong thư mục bạn chỉ định!

ff = glob.glob('ground3T/*')

Hãy in phần tử đầu tiên của danh sách và kiểm tra độ dài danh sách: trong trường hợp này sẽ là 30.

ff[0]
'ground3T/181232.nii.gz'
len(ff)
30

Bây giờ bạn đã sẵn sàng nạp các thể tích 3D bằng nibabel. Lưu ý khi nạp một thể tích định dạng Nifti, Nibabel chưa nạp mảng ảnh ngay. Nó đợi cho đến khi bạn yêu cầu dữ liệu mảng. Cách thông thường để lấy dữ liệu mảng là gọi phương thức get_data().

Vì bạn muốn các lát 2D thay vì 3D, bạn sẽ khởi tạo một danh sách; mỗi lần đọc một thể tích, bạn sẽ lặp qua toàn bộ 207 lát của thể tích 3D và thêm từng lát một vào danh sách.

images = []

Hãy in thử kích thước của một thể tích 3D, nó sẽ có dạng 173 x 207 x 173 (tọa độ x, y, z).

Lưu ý: Bạn sẽ chỉ dùng 51 lát giữa của não chứ không phải toàn bộ 207 lát. Vậy hãy xem cách chỉ dùng các lát trung tâm và nạp chúng.

for f in range(len(ff)):
    a = nib.load(ff[f])
    a = a.get_data()
    a = a[:,78:129,:]
    for i in range(a.shape[1]):
        images.append((a[:,i,:]))
print (a.shape)
(173, 51, 173)

Hãy phân tích kích thước của một lát trong số 207 lát.

a[:,0,:].shape
(173, 173)

Tiền xử lý dữ liệu

Vì images là một danh sách nên bạn sẽ dùng mô-đun numpy để chuyển danh sách thành mảng numpy.

images = np.asarray(images)

Đến lúc kiểm tra kích thước mảng numpy, chiều đầu tiên của mảng sẽ lần lượt là 207 x 30 = 6210 và hai chiều còn lại là 173 x 173.

images.shape
(1530, 173, 173)

Các ảnh trong bộ dữ liệu đúng là ảnh xám với kích thước 173 x 173 nên trước khi đưa dữ liệu vào mô hình, tiền xử lý là rất quan trọng. Trước hết bạn sẽ chuyển mỗi ảnh 173 x 173 thành ma trận kích thước 173 x 173 x 1 để có thể đưa vào mạng:

images = images.reshape(-1, 173,173,1)
images.shape
(1530, 173, 173, 1)

Tiếp theo, chuẩn hóa lại dữ liệu bằng kỹ thuật chuẩn hóa max-min:

m = np.max(images)
mi = np.min(images)
m, mi
(3599.0959, -341.83853)
images = (images - mi) / (m - mi)

Hãy kiểm tra lại giá trị nhỏ nhất và lớn nhất của dữ liệu, sau khi chuẩn hóa chúng phải là 0.0 và 1.0!

np.min(images), np.max(images)
(0.0, 1.0)

Đây là bước quan trọng: bạn sẽ đệm ảnh bằng các số 0 ở viền để kích thước ảnh là số chẵn và dễ giảm mẫu đi hai lần khi truyền qua mô hình. Hãy thêm ba hàng và ba cột số 0 để đưa kích thước thành 176 x 176

temp = np.zeros([1530,176,176,1])
temp[:,3:,3:,:] = images
images = temp

Sau tất cả, việc phân chia dữ liệu là quan trọng. Để mô hình tổng quát hóa tốt, bạn chia dữ liệu thành hai phần: tập huấn luyện và tập xác thực. Bạn sẽ huấn luyện mô hình trên 80% dữ liệu và xác thực trên 20% còn lại.

Điều này cũng giúp giảm khả năng quá khớp, vì bạn sẽ xác thực mô hình trên dữ liệu mà nó chưa thấy trong giai đoạn huấn luyện.

Bạn có thể dùng mô-đun train_test_split của scikit-learn để chia dữ liệu đúng cách:

from sklearn.model_selection import train_test_split
train_X,valid_X,train_ground,valid_ground = train_test_split(images,
                                                             images,
                                                             test_size=0.2,
                                                             random_state=13)

Lưu ý rằng với tác vụ này, bạn không cần nhãn huấn luyện và kiểm thử. Đó là lý do bạn truyền ảnh huấn luyện hai lần. Ảnh huấn luyện vừa đóng vai trò đầu vào vừa là ground truth tương tự như nhãn trong bài toán phân loại.

Khám phá dữ liệu

Giờ hãy xem ảnh trong bộ dữ liệu trông như thế nào và kiểm tra lại kích thước ảnh một lần nữa vì bạn đã thêm ba hàng và cột bằng thuộc tính .shape của mảng NumPy:

# Shapes of training set
print("Dataset (images) shape: {shape}".format(shape=images.shape))
Dataset (images) shape: (1530, 176, 176, 1)

Từ kết quả trên, bạn thấy dữ liệu có kích thước 6210 x 176 x 176 vì có 6210 mẫu, mỗi mẫu là ma trận kích thước 176 x 176 x 1.

Bây giờ, hãy xem một vài ảnh huấn luyện và xác thực trong bộ dữ liệu của bạn:

plt.figure(figsize=[5,5])

# Display the first image in training data
plt.subplot(121)
curr_img = np.reshape(train_X[0], (176,176))
plt.imshow(curr_img, cmap='gray')

# Display the first image in testing data
plt.subplot(122)
curr_img = np.reshape(valid_X[0], (176,176))
plt.imshow(curr_img, cmap='gray')
<matplotlib.image.AxesImage at 0x7ff67d5d59b0>
images

Kết quả của hai biểu đồ trên là từ tập huấn luyện và tập xác thực. Bạn có thể thấy ảnh huấn luyện và xác thực đều khác nhau, sẽ rất thú vị khi xem autoencoder tích chập có học được đặc trưng và tái tạo các ảnh này tốt không.

Giờ bạn đã sẵn sàng định nghĩa mạng và đưa dữ liệu vào. Không dài dòng nữa, sang bước tiếp theo!

Convolutional Autoencoder!

Các ảnh có kích thước 176 x 176 x 1 hay một vector 30.976 chiều. Bạn chuyển ma trận ảnh thành mảng, chuẩn hóa về 0 đến 1, reshape về kích thước 176 x 176 x 1 và đưa làm đầu vào cho mạng.

Ngoài ra, bạn sẽ dùng kích thước lô 128; dùng lô lớn hơn 256 hoặc 512 cũng tốt, tùy hệ thống bạn huấn luyện. Nó ảnh hưởng mạnh đến tham số học và độ chính xác dự đoán. Bạn sẽ huấn luyện mạng trong 50 epoch.

batch_size = 128
epochs = 300
inChannel = 1
x, y = 176, 176
input_img = Input(shape = (x, y, inChannel))

Như bạn đã biết, autoencoder gồm hai phần: bộ mã hóa (encoder) và giải mã (decoder).

Encoder: Gồm 3 khối tích chập, mỗi khối có một lớp tích chập theo sau là lớp chuẩn hóa theo lô. Lớp gộp cực đại dùng sau khối tích chập thứ nhất và thứ hai.

  • Khối tích chập đầu có 32 bộ lọc kích thước 3 x 3, theo sau là lớp giảm mẫu (max-pooling),
  • Khối thứ hai có 64 bộ lọc kích thước 3 x 3, theo sau là lớp giảm mẫu khác,
  • Khối cuối của encoder có 128 bộ lọc kích thước 3 x 3.

Decoder: Gồm 2 khối tích chập, mỗi khối có một lớp tích chập theo sau là lớp chuẩn hóa theo lô. Lớp tăng mẫu dùng sau khối tích chập thứ nhất và thứ hai.

  • Khối đầu có 128 bộ lọc kích thước 3 x 3 theo sau là lớp tăng mẫu,
  • Khối thứ hai có 64 bộ lọc kích thước 3 x 3 theo sau là lớp tăng mẫu khác,
  • Lớp cuối cùng của encoder có 1 bộ lọc kích thước 3 x 3 để tái tạo lại đầu vào với một kênh.

Lớp gộp cực đại sẽ giảm mẫu đầu vào xuống hai lần mỗi khi dùng, trong khi lớp tăng mẫu sẽ tăng mẫu đầu vào lên hai lần mỗi khi dùng.

Lưu ý: Số lượng bộ lọc, kích thước bộ lọc, số lớp, số epoch bạn huấn luyện, tất cả đều là siêu tham số và nên quyết định dựa trên trực giác của bạn; bạn tự do thử nghiệm bằng cách chỉnh các siêu tham số này và đo lường hiệu năng mô hình. Và đó là cách bạn dần học được nghệ thuật của học sâu!

def autoencoder(input_img):
    #encoder
    #input = 28 x 28 x 1 (wide and thin)
    conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img) #28 x 28 x 32
    conv1 = BatchNormalization()(conv1)
    conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv1)
    conv1 = BatchNormalization()(conv1)
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1) #14 x 14 x 32
    conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1) #14 x 14 x 64
    conv2 = BatchNormalization()(conv2)
    conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv2)
    conv2 = BatchNormalization()(conv2)
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2) #7 x 7 x 64
    conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2) #7 x 7 x 128 (small and thick)
    conv3 = BatchNormalization()(conv3)
    conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3)
    conv3 = BatchNormalization()(conv3)


    #decoder
    conv4 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv3) #7 x 7 x 128
    conv4 = BatchNormalization()(conv4)
    conv4 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv4)
    conv4 = BatchNormalization()(conv4)
    up1 = UpSampling2D((2,2))(conv4) # 14 x 14 x 128
    conv5 = Conv2D(32, (3, 3), activation='relu', padding='same')(up1) # 14 x 14 x 64
    conv5 = BatchNormalization()(conv5)
    conv5 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv5)
    conv5 = BatchNormalization()(conv5)
    up2 = UpSampling2D((2,2))(conv5) # 28 x 28 x 64
    decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2) # 28 x 28 x 1
    return decoded

Sau khi tạo mô hình, bạn cần biên dịch nó với bộ tối ưu RMSProp.

Lưu ý bạn cũng cần chỉ định loại hàm mất mát qua tham số loss. Ở đây là mean squared error, vì mất mát sau mỗi lô sẽ được tính giữa lô đầu ra dự đoán và ground truth theo lỗi bình phương trung bình trên từng điểm ảnh:

autoencoder = Model(input_img, autoencoder(input_img))
autoencoder.compile(loss='mean_squared_error', optimizer = RMSprop())

Hãy trực quan hóa các lớp bạn đã tạo bằng hàm summary, hàm này sẽ hiển thị số lượng tham số (trọng số và độ lệch) ở mỗi lớp và tổng số tham số trong mô hình.

autoencoder.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         (None, 176, 176, 1)       0         
_________________________________________________________________
conv2d_18 (Conv2D)           (None, 176, 176, 32)      320       
_________________________________________________________________
batch_normalization_11 (Batc (None, 176, 176, 32)      128       
_________________________________________________________________
conv2d_19 (Conv2D)           (None, 176, 176, 32)      9248      
_________________________________________________________________
batch_normalization_12 (Batc (None, 176, 176, 32)      128       
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 88, 88, 32)        0         
_________________________________________________________________
conv2d_20 (Conv2D)           (None, 88, 88, 64)        18496     
_________________________________________________________________
batch_normalization_13 (Batc (None, 88, 88, 64)        256       
_________________________________________________________________
conv2d_21 (Conv2D)           (None, 88, 88, 64)        36928     
_________________________________________________________________
batch_normalization_14 (Batc (None, 88, 88, 64)        256       
_________________________________________________________________
max_pooling2d_6 (MaxPooling2 (None, 44, 44, 64)        0         
_________________________________________________________________
conv2d_22 (Conv2D)           (None, 44, 44, 128)       73856     
_________________________________________________________________
batch_normalization_15 (Batc (None, 44, 44, 128)       512       
_________________________________________________________________
conv2d_23 (Conv2D)           (None, 44, 44, 128)       147584    
_________________________________________________________________
batch_normalization_16 (Batc (None, 44, 44, 128)       512       
_________________________________________________________________
conv2d_24 (Conv2D)           (None, 44, 44, 64)        73792     
_________________________________________________________________
batch_normalization_17 (Batc (None, 44, 44, 64)        256       
_________________________________________________________________
conv2d_25 (Conv2D)           (None, 44, 44, 64)        36928     
_________________________________________________________________
batch_normalization_18 (Batc (None, 44, 44, 64)        256       
_________________________________________________________________
up_sampling2d_5 (UpSampling2 (None, 88, 88, 64)        0         
_________________________________________________________________
conv2d_26 (Conv2D)           (None, 88, 88, 32)        18464     
_________________________________________________________________
batch_normalization_19 (Batc (None, 88, 88, 32)        128       
_________________________________________________________________
conv2d_27 (Conv2D)           (None, 88, 88, 32)        9248      
_________________________________________________________________
batch_normalization_20 (Batc (None, 88, 88, 32)        128       
_________________________________________________________________
up_sampling2d_6 (UpSampling2 (None, 176, 176, 32)      0         
_________________________________________________________________
conv2d_28 (Conv2D)           (None, 176, 176, 1)       289       
=================================================================
Total params: 427,713
Trainable params: 426,433
Non-trainable params: 1,280
_________________________________________________________________

Đến lúc huấn luyện mô hình bằng hàm fit() của Keras! Mô hình huấn luyện 50 epoch. Hàm fit() sẽ trả về một đối tượng lịch sử; bằng cách lưu kết quả này vào fashion_train, bạn có thể dùng sau để vẽ đồ thị mất mát giữa huấn luyện và xác thực, giúp phân tích hiệu năng mô hình trực quan.

Huấn luyện mô hình

autoencoder_train = autoencoder.fit(train_X, train_ground, batch_size=batch_size,epochs=epochs,verbose=1,validation_data=(valid_X, valid_ground))
Train on 1224 samples, validate on 306 samples
Epoch 1/300
1224/1224 [==============================] - 7s - loss: 0.1201 - val_loss: 0.0838
Epoch 2/300
1224/1224 [==============================] - 7s - loss: 0.0492 - val_loss: 0.0534
...
Epoch 299/300
1224/1224 [==============================] - 7s - loss: 1.3101e-04 - val_loss: 6.1086e-04
Epoch 300/300
1224/1224 [==============================] - 7s - loss: 1.0711e-04 - val_loss: 3.9641e-04

Cuối cùng! Bạn đã huấn luyện mô hình trên bộ dữ liệu vân tay trong 200 epoch. Bây giờ, hãy vẽ đồ thị mất mát giữa dữ liệu huấn luyện và xác thực để trực quan hóa hiệu năng mô hình.

loss = autoencoder_train.history['loss']
val_loss = autoencoder_train.history['val_loss']
epochs = range(300)
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
training and validation loss

Cuối cùng, bạn có thể thấy cả mất mát xác thực và huấn luyện đều đồng bộ. Điều này cho thấy mô hình không bị quá khớp: mất mát xác thực đang giảm chứ không tăng, và hầu như không có khoảng cách giữa mất mát huấn luyện và xác thực trong suốt quá trình huấn luyện.

Vì vậy có thể nói khả năng tổng quát hóa của mô hình là tốt.

Cuối cùng, đã đến lúc tái tạo ảnh kiểm thử bằng hàm predict() của Keras và xem mô hình tái tạo trên dữ liệu kiểm thử tốt đến đâu.

Lưu mô hình

Giờ hãy lưu mô hình đã huấn luyện. Đây là bước quan trọng khi làm việc với Học sâu. Bởi vì trọng số là “trái tim” của lời giải cho vấn đề bạn đang xử lý!

Bạn có thể nạp lại trọng số đã lưu vào cùng mô hình bất cứ lúc nào và tiếp tục huấn luyện từ nơi dừng lại. Ví dụ: nếu huấn luyện lại mô hình trên, các tham số như trọng số, độ lệch, hàm mất mát... sẽ không khởi đầu từ đầu và sẽ không còn là quá trình huấn luyện mới.

Chỉ với một dòng mã, bạn có thể lưu và nạp lại trọng số vào mô hình.

autoencoder = autoencoder.save_weights('autoencoder_mri.h5')
autoencoder = Model(input_img, autoencoder(input_img))
autoencoder.load_weights('autoencoder_mri.h5')

Dự đoán trên dữ liệu xác thực

Ở đây bạn không có dữ liệu kiểm thử riêng. Hãy dùng dữ liệu xác thực để dự đoán với mô hình vừa huấn luyện.

Bạn sẽ dự đoán trên 306 ảnh xác thực còn lại và vẽ một vài ảnh tái tạo để trực quan hóa mô hình tái tạo ảnh xác thực tốt đến đâu.

pred = autoencoder.predict(valid_X)
plt.figure(figsize=(20, 4))
print("Test Images")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(valid_ground[i, ..., 0], cmap='gray')
plt.show()    
plt.figure(figsize=(20, 4))
print("Reconstruction of Test Images")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(pred[i, ..., 0], cmap='gray')  
plt.show()
Test Images
Test Images
Reconstruction of Test Images
Reconstruction of Test Images

Từ các hình trên, bạn có thể thấy mô hình làm rất tốt việc tái tạo ảnh kiểm thử được dự đoán bằng mô hình. Ít nhất là về mặt định tính, ảnh gốc và ảnh tái tạo gần như giống hệt nhau.

Có thể mô hình còn làm tốt hơn nữa ở một số chi tiết cục bộ trong ảnh 3T gốc.

Dự đoán trên ảnh 3T có nhiễu

Đầu tiên hãy thêm một ít nhiễu vào ảnh xác thực với trung bình bằng 0 và độ lệch chuẩn 0,03.

[a,b,c,d]= np.shape(valid_X)
mean = 0
sigma = 0.03
gauss = np.random.normal(mean,sigma,(a,b,c,d))
noisy_images = valid_X + gauss

Đến lúc dự đoán trên các ảnh xác thực có nhiễu. Hãy xem mô hình của bạn thể hiện ra sao trên ảnh nhiễu dù không được huấn luyện với chúng.

pred_noisy = autoencoder.predict(noisy_images)
plt.figure(figsize=(20, 4))
print("Noisy Test Images")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(noisy_images[i, ..., 0], cmap='gray')
plt.show()    
plt.figure(figsize=(20, 4))
print("Reconstruction of Noisy Test Images")
for i in range(5):
    plt.subplot(1, 5, i+1)
    plt.imshow(pred_noisy[i, ..., 0], cmap='gray')  
plt.show()
Noisy Test Images
noisy test images
Reconstruction of Noisy Test Images
Reconstruction of Noisy Test Images

Có vẻ mô hình làm khá tốt. Đúng không? Sau cùng thì ảnh tái tạo trông tốt hơn ảnh có nhiễu. Hơn nữa, mô hình chưa từng thấy ảnh có nhiễu trong lúc huấn luyện.

Thước đo định lượng: Tỉ số đỉnh tín hiệu trên nhiễu (PSNR)

Khối PSNR tính tỉ số đỉnh tín hiệu trên nhiễu, theo decibel (dB), giữa hai ảnh. Tỉ số này thường dùng như một thước đo chất lượng giữa ảnh gốc và ảnh tái tạo. PSNR càng cao, chất lượng ảnh tái tạo càng tốt.

Vậy trước hết hãy tính hiệu năng giữa ảnh xác thực và ảnh xác thực đã tái tạo.

valid_pred = autoencoder.predict(valid_X)
mse =  np.mean((valid_X - valid_pred) ** 2)
psnr = 20 * math.log10( 1.0 / math.sqrt(mse))
print('PSNR of reconstructed validation images: {psnr}dB'.format(psnr=np.round(psnr,2)))
PSNR of reconstructed validation images: 34.02dB

Tiếp theo, hãy tính PSNR giữa ảnh xác thực gốc và ảnh nhiễu đã được mô hình dự đoán

noisy_pred = autoencoder.predict(noisy_images)
mse =  np.mean((valid_X - noisy_pred) ** 2)
psnr_noisy = 20 * math.log10( 1.0 / math.sqrt(mse))
print('PSNR of reconstructed validation images: {psnr}dB'.format(psnr=np.round(psnr_noisy,2)))
PSNR of reconstructed validation images: 32.48dB

Có vẻ về mặt định lượng chỉ chênh khoảng 1,54 dB giữa PSNR của tái tạo không nhiễu và có nhiễu. Hơn nữa, bạn chưa huấn luyện mô hình để xử lý nhiễu. Thật ấn tượng phải không?

Đi xa hơn!

Bài hướng dẫn này là khởi đầu tốt để hiểu cách đọc ảnh MRI định dạng nifti, phân tích, tiền xử lý và đưa chúng vào mô hình bằng bộ dữ liệu MRI não 3T. Nó cho bạn thấy một ứng dụng hay của autoencoder trong thực tế. Nếu bạn theo kịp dễ dàng hoặc dù phải nỗ lực thêm chút, rất tốt!

Bạn có thể thử thay đổi kiến trúc và cải thiện dự đoán cả về định lượng lẫn định tính. Có thể thêm nhiều lớp hơn, có thể huấn luyện lâu hơn? Hãy thử các kết hợp và xem liệu chúng có giúp ích không.

Vẫn còn rất nhiều điều để học, vậy tại sao không tham gia khóa Deep Learning in Python của DataCamp? Nếu bạn chưa làm. Bạn sẽ học từ căn bản và dần tiến tới làm chủ lĩnh vực học sâu, chắc chắn sẽ là nguồn tài nguyên không thể thiếu khi bạn học cách làm việc với mạng nơ-ron tích chập trong Python, cách phát hiện khuôn mặt, đối tượng, v.v.

Chủ đề

Liên quan