Chẩn đoán bệnh viêm phổi từ ảnh chụp X-Quang bằng Deep learning

Ứng dụng của AI nói chung và Deep learning nói riêng ngày càng trở nên phổ biến trong cuộc sống con người. AI đã có thể làm thay con người trong nhiều lĩnh vực, với những dữ liệu đã được tổng hợp và lưu trữ bằng máy tính sẽ giúp AI ngày càng thông minh hơn. Một trong những lĩnh vực mà AI có thể áp dụng vào rất tốt đó là lĩnh vực y tế, đó cũng là tiềm năng rất lớn ở Việt Nam cũng như trên thế giới. Hôm nay mình sẽ giới thiệu tới các bạn 1 bài toán khá thú vị về medical image analysis, ứng dụng Chẩn đoán bệnh viêm phổi, sử dụng Deeplearning và các kỹ thuật liên quan đến dữ liệu và tối ưu hiệu quả mô hình.

Mô tả bài toán

Viêm phổi là một bệnh khá phổ biến, nguyên nhân của bệnh viêm phổi có thể là do:

  1. Viêm phổi do vi khuẩn
  2. Viêm phổi do virus
  3. Viêm phổi do nấm
  4. Viêm phổi do hóa chất

Vì các dạng bệnh được thể hiện trên film chụp X-Quang là khác nhau nên phạm vi bài toán này chỉ giải quyết việc chẩn đoán bệnh cho nguyên nhân 1 và 2.

Bước 1: Cách tiếp cận bài toán

Dựa vào các hình ảnh chụp X-Quang ta sẽ tiến hành phân loại thành 2 loại: mắc bệnh và không mắc bệnh. Đây là bài toán phân loại hình ảnh bình thường, tuy nhiên đây chỉ là một mô hình để các bạn học tập nên bộ dữ liệu mẫu rất ít, đòi hỏi chũng ta phải hiểu cách thức hoạt động của mô hình để xây dựng 1 mô hình chấp nhận được ngay cả với một lượng dữ liệu hạn chế. Để thực hiện việc này chúng ta sẽ sử dụng mạng CNN (VGG16), có sử dụng transfer learning và data augmentation để cải thiện hiệu quả huấn luyện mô hình

Bước 2: Chuẩn bị dữ liệu.

Dữ liệu được public trong y tế rất ít và chúng ta chỉ đang nghiên cứu nên bộ dataset có được cũng khá hạn chế ^^. Bộ dữ liệu được sử dụng mình lấy trên Kaggle với dung lượng ~1Gb

Reference: https://www.kaggle.com/paultimothymooney/chest-xray-pneumonia

Bộ dataset này gồm 3 thư mục (train, test, val) và trong mỗi thư mục sẽ là các loại tương ứng (Pneumonia/Normal). Có tất cả 5,863 ảnh JPEG đã được các bác sỹ chuyên khoa phân loại bằng tay và chia làm 2 loại.

Tải về tại đây

Import thư viện

import os
import glob
import h5py
import shutil
import imgaug as aug
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.image as mimg
import imgaug.augmenters as iaa
from os import listdir, makedirs, getcwd, remove
from os.path import isfile, join, abspath, exists, isdir, expanduser
from PIL import Image
from pathlib import Path
from skimage.io import imread
from skimage.transform import resize
from keras.models import Sequential, Model
from keras.applications.vgg16 import VGG16, preprocess_input
from keras.preprocessing.image import ImageDataGenerator,load_img, img_to_array
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Dense, Dropout, Input, Flatten, SeparableConv2D
from keras.layers import GlobalMaxPooling2D
from keras.layers.normalization import BatchNormalization
from keras.layers.merge import Concatenate
from keras.models import Model
from keras.optimizers import Adam, SGD, RMSprop
from keras.callbacks import ModelCheckpoint, Callback, EarlyStopping
from keras.utils import to_categorical
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from mlxtend.plotting import plot_confusion_matrix
from sklearn.metrics import confusion_matrix
import cv2
from keras import backend as K
color = sns.color_palette()
%matplotlib inline

# Input data files are available in the "../input/" directory.
# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory
print(os.listdir("../input")) 

Dữ liệu sẽ nằm trong thư mục ../input

Thiết lập một số tham số cho Keras và tensorflow

import tensorflow as tf 
# Set the seed for hash based operations in python
os.environ['PYTHONHASHSEED'] = '0' 
# Set the numpy seed
np.random.seed(111) 
# Disable multi-threading in tensorflow ops
session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1) 
# Set the random seed in tensorflow at graph level
tf.set_random_seed(111) 
# Define a tensorflow session with above session configs
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf) 
# Set the session in keras
K.set_session(sess)

# Make the augmentation sequence deterministic
aug.seed(111)

Tiến hành phân chia dữ liệu

# Define path to the data directory
data_dir = Path('../input/chest-xray-pneumonia/chest_xray/chest_xray')
# Path to train directory (Fancy pathlib...no more os.path!!)
train_dir = data_dir / 'train'
# Path to validation directory
val_dir = data_dir / 'val'
# Path to test directory
test_dir = data_dir / 'test'

Chúng ta đi thử vào 1 thư mục trong tập training, mỗi thư mục sẽ chứa 2 thư mục con:

NORMAL: Các ảnh được đánh nhãn là không bị bệnh PNEUMONIA: Thư mục gồm các ảnh đánh nhãn là bị mắc bệnh

# Get the path to the normal and pneumonia sub-directories
normal_cases_dir = train_dir / 'NORMAL'
pneumonia_cases_dir = train_dir / 'PNEUMONIA' 
# Get the list of all the images
normal_cases = normal_cases_dir.glob('*.jpeg')
pneumonia_cases = pneumonia_cases_dir.glob('*.jpeg') 
# An empty list. We will insert the data into this list in (img_path, label) format
train_data = [] 
# Go through all the normal cases. The label for these cases will be 0
for img in normal_cases:
    train_data.append((img,0))

# Go through all the pneumonia cases. The label for these cases will be 1
for img in pneumonia_cases:
    train_data.append((img, 1))

# Get a pandas dataframe from the data we have in our list 
train_data = pd.DataFrame(train_data, columns=['image', 'label'],index=None)

# Shuffle the data 
train_data = train_data.sample(frac=1.).reset_index(drop=True)
 

Chúng ta in thử kết quả

train_data.head()
image label
0 ../input/chest-xray-pneumonia/chest_xray/chest... 0
1 ../input/chest-xray-pneumonia/chest_xray/chest... 0
2 ../input/chest-xray-pneumonia/chest_xray/chest... 1
3 ../input/chest-xray-pneumonia/chest_xray/chest... 1
4 ../input/chest-xray-pneumonia/chest_xray/chest... 1

Thống kê về tập dữ liệu, số lượng các mẫu bị bênh / không bị bệnh

# Get the counts for each class
cases_count = train_data['label'].value_counts()
print(cases_count)

# Plot the results 
plt.figure(figsize=(10,8))
sns.barplot(x=cases_count.index, y= cases_count.values)
plt.title('Number of cases', fontsize=14)
plt.xlabel('Case type', fontsize=12)
plt.ylabel('Count', fontsize=12)
plt.xticks(range(len(cases_count.index)), ['Normal(0)', 'Pneumonia(1)'])
plt.show()

Chúng ta có nhận xét sơ bộ về dữ liệu: Các trường hợp bị viêm phổi gấp 3 lần các trường hợp bình thường. Điều này rất bình thường trong các dữ liệu về y tế, dữ liệu sẽ bị không đồng đều, có quá nhiều trường hợp bình thường hoặc có quá nhiều trường hợp bị bệnh. Chúng ta hiển thị thử xem một vài mẫu từ dữ liệu training xem sao:

# Get few samples for both the classes
pneumonia_samples = (train_data[train_data['label']==1]['image'].iloc[:5]).tolist()
normal_samples = (train_data[train_data['label']==0]['image'].iloc[:5]).tolist()

# Concat the data in a single list and del the above two list
samples = pneumonia_samples + normal_samples
del pneumonia_samples, normal_samples

# Plot the data 
f, ax = plt.subplots(2,5, figsize=(30,10))
for i in range(10):
    img = imread(samples[i])
    ax[i//5, i%5].imshow(img, cmap='gray')
    if i<5:
        ax[i//5, i%5].set_title("Pneumonia")
    else:
        ax[i//5, i%5].set_title("Normal")
    ax[i//5, i%5].axis('off')
    ax[i//5, i%5].set_aspect('auto')
plt.show()

Bằng mắt thường chúng ta khó có thể phân biệt được đâu là trường hợp bị bệnh, đâu là trường hợp không bị bệnh. Vì vậy nếu có thể xây dựng được một mô hình mạnh mẽ thì hẳn sẽ cực kì có ích với bác sỹ 😄

Chuẩn bị dữ liệu kiểm thử

# Get the path to the sub-directories
normal_cases_dir = val_dir / 'NORMAL'
pneumonia_cases_dir = val_dir / 'PNEUMONIA'

# Get the list of all the images
normal_cases = normal_cases_dir.glob('*.jpeg')
pneumonia_cases = pneumonia_cases_dir.glob('*.jpeg')

# List that are going to contain validation images data and the corresponding labels
valid_data = []
valid_labels = []


# Some images are in grayscale while majority of them contains 3 channels. So, if the image is grayscale, we will convert into a image with 3 channels.
# We will normalize the pixel values and resizing all the images to 224x224 

# Normal cases
for img in normal_cases:
    img = cv2.imread(str(img))
    img = cv2.resize(img, (224,224))
    if img.shape[2] ==1:
        img = np.dstack([img, img, img])
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = img.astype(np.float32)/255.
    label = to_categorical(0, num_classes=2)
    valid_data.append(img)
    valid_labels.append(label)
                      
# Pneumonia cases        
for img in pneumonia_cases:
    img = cv2.imread(str(img))
    img = cv2.resize(img, (224,224))
    if img.shape[2] ==1:
        img = np.dstack([img, img, img])
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = img.astype(np.float32)/255.
    label = to_categorical(1, num_classes=2)
    valid_data.append(img)
    valid_labels.append(label)
    
# Convert the list into numpy arrays
valid_data = np.array(valid_data)
valid_labels = np.array(valid_labels)

print("Total number of validation examples: ", valid_data.shape)
print("Total number of labels:", valid_labels.shape)
Total number of validation examples:  (16, 224, 224, 3)
Total number of labels: (16, 2)

Bước 3: Tiền xử lý dữ liệu

Augmentation

Một kỹ thuật nữa giúp tiếp tục cải thiện mô hình là tạo ra nhiều dữ liệu training từ dữ liệu training ban đầu. Thoạt nghe có vẻ vô lý, nhưng kỹ thuật này rất hiệu quả. Kỹ thuật này có tên là Image Augmentation. Nghĩa là tạo ra các bức ảnh từ 1 ảnh ban đầu, ví dụ như xoay trái, xoay phải, thêm bộ lọc...

Trong bộ data này chúng ta sẽ tạo ra các dữ liệu mới bằng cách lật theo chiều ngang, xoay ảnh và thêm các điều kiện sáng tối khác nhau

# Augmentation sequence 
seq = iaa.OneOf([
    iaa.Fliplr(), # horizontal flips
    iaa.Affine(rotate=20), # roatation
    iaa.Multiply((1.2, 1.5))]) #random brightness

Bước cuối cùng của việc chuẩn bị dữ liệu là khởi tạo dữ liệu training

def data_gen(data, batch_size):
    # Get total number of samples in the data
    n = len(data)
    steps = n//batch_size
    
    # Define two numpy arrays for containing batch data and labels
    batch_data = np.zeros((batch_size, 224, 224, 3), dtype=np.float32)
    batch_labels = np.zeros((batch_size,2), dtype=np.float32)

    # Get a numpy array of all the indices of the input data
    indices = np.arange(n)
    
    # Initialize a counter
    i =0
    while True:
        np.random.shuffle(indices)
        # Get the next batch 
        count = 0
        next_batch = indices[(i*batch_size):(i+1)*batch_size]
        for j, idx in enumerate(next_batch):
            img_name = data.iloc[idx]['image']
            label = data.iloc[idx]['label']
            
            # one hot encoding
            encoded_label = to_categorical(label, num_classes=2)
            # read the image and resize
            img = cv2.imread(str(img_name))
            img = cv2.resize(img, (224,224))
            
            # check if it's grayscale
            if img.shape[2]==1:
                img = np.dstack([img, img, img])
            
            # cv2 reads in BGR mode by default
            orig_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            # normalize the image pixels
            orig_img = img.astype(np.float32)/255.
            
            batch_data[count] = orig_img
            batch_labels[count] = encoded_label
            
            # generating more samples of the undersampled class
            if label==0 and count < batch_size-2:
                aug_img1 = seq.augment_image(img)
                aug_img2 = seq.augment_image(img)
                aug_img1 = cv2.cvtColor(aug_img1, cv2.COLOR_BGR2RGB)
                aug_img2 = cv2.cvtColor(aug_img2, cv2.COLOR_BGR2RGB)
                aug_img1 = aug_img1.astype(np.float32)/255.
                aug_img2 = aug_img2.astype(np.float32)/255.

                batch_data[count+1] = aug_img1
                batch_labels[count+1] = encoded_label
                batch_data[count+2] = aug_img2
                batch_labels[count+2] = encoded_label
                count +=2
            
            else:
                count+=1
            
            if count==batch_size-1:
                break
            
        i+=1
        yield batch_data, batch_labels
            
        if i>=steps:
            i=0

Bước 4: Định nghĩa mô hình

Đây dường như là phần quan trọng nhất và cần nhiều kỹ thuật để nâng cao hiệu quả huấn luyên. Nhiều người sẽ chọn cách sử dụng transfer learning và fine-tuning từ các mạng đã được học sẵn. Đây là cách làm hiệu quả với hầu hết các bài toán mà tập dữ liệu huấn luyện ít. Chúng ta sẽ thực hiện việc này nhưng với một vài thay đổi để mô hình hiệu quả hơn:

  • Sử dụng mạng đơn giản, ít tham số
  • Thay vì việc transfer cả mạng, ta sẽ sử dụng các tham số đã được huấn luyện cho mô hình với các layer gốc và thêm vào 1 số layer đầu tiên để trích xuất một số đặc điểm như đốm màu, vùng ảnh, cạnh, v.v. Các layer gốc thay vì khởi tạo tham số ngẫu nhiên, chúng ta sẽ lấy tham số từ mạng đã được học từ trước.
  • Chọn các Layer ít tham số hơn, ví dụ như Depthwise separableConv là sự thay thế tốt cho lớp Conv, số lượng tham số ít hơn và các thông tin trích xuất được nhiều hơn. Bạn có thể đọc thêm tại paper
  • Tiến hành training với learning rate nhỏ với weight decay

Nói thế chắc cũng đủ rồi, chúng ta đi vào code:

def build_model():
    input_img = Input(shape=(224,224,3), name='ImageInput')
    x = Conv2D(64, (3,3), activation='relu', padding='same', name='Conv1_1')(input_img)
    x = Conv2D(64, (3,3), activation='relu', padding='same', name='Conv1_2')(x)
    x = MaxPooling2D((2,2), name='pool1')(x)
    
    x = SeparableConv2D(128, (3,3), activation='relu', padding='same', name='Conv2_1')(x)
    x = SeparableConv2D(128, (3,3), activation='relu', padding='same', name='Conv2_2')(x)
    x = MaxPooling2D((2,2), name='pool2')(x)
    
    x = SeparableConv2D(256, (3,3), activation='relu', padding='same', name='Conv3_1')(x)
    x = BatchNormalization(name='bn1')(x)
    x = SeparableConv2D(256, (3,3), activation='relu', padding='same', name='Conv3_2')(x)
    x = BatchNormalization(name='bn2')(x)
    x = SeparableConv2D(256, (3,3), activation='relu', padding='same', name='Conv3_3')(x)
    x = MaxPooling2D((2,2), name='pool3')(x)
    
    x = SeparableConv2D(512, (3,3), activation='relu', padding='same', name='Conv4_1')(x)
    x = BatchNormalization(name='bn3')(x)
    x = SeparableConv2D(512, (3,3), activation='relu', padding='same', name='Conv4_2')(x)
    x = BatchNormalization(name='bn4')(x)
    x = SeparableConv2D(512, (3,3), activation='relu', padding='same', name='Conv4_3')(x)
    x = MaxPooling2D((2,2), name='pool4')(x)
    
    x = Flatten(name='flatten')(x)
    x = Dense(1024, activation='relu', name='fc1')(x)
    x = Dropout(0.7, name='dropout1')(x)
    x = Dense(512, activation='relu', name='fc2')(x)
    x = Dropout(0.5, name='dropout2')(x)
    x = Dense(2, activation='softmax', name='fc3')(x)
    
    model = Model(inputs=input_img, outputs=x)
    return model
model =  build_model()
model.summary()

Thông tin của mô hình:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
ImageInput (InputLayer)      (None, 224, 224, 3)       0         
_________________________________________________________________
Conv1_1 (Conv2D)             (None, 224, 224, 64)      1792      
_________________________________________________________________
Conv1_2 (Conv2D)             (None, 224, 224, 64)      36928     
_________________________________________________________________
pool1 (MaxPooling2D)         (None, 112, 112, 64)      0         
_________________________________________________________________
Conv2_1 (SeparableConv2D)    (None, 112, 112, 128)     8896      
_________________________________________________________________
Conv2_2 (SeparableConv2D)    (None, 112, 112, 128)     17664     
_________________________________________________________________
pool2 (MaxPooling2D)         (None, 56, 56, 128)       0         
_________________________________________________________________
Conv3_1 (SeparableConv2D)    (None, 56, 56, 256)       34176     
_________________________________________________________________
bn1 (BatchNormalization)     (None, 56, 56, 256)       1024      
_________________________________________________________________
Conv3_2 (SeparableConv2D)    (None, 56, 56, 256)       68096     
_________________________________________________________________
bn2 (BatchNormalization)     (None, 56, 56, 256)       1024      
_________________________________________________________________
Conv3_3 (SeparableConv2D)    (None, 56, 56, 256)       68096     
_________________________________________________________________
pool3 (MaxPooling2D)         (None, 28, 28, 256)       0         
_________________________________________________________________
Conv4_1 (SeparableConv2D)    (None, 28, 28, 512)       133888    
_________________________________________________________________
bn3 (BatchNormalization)     (None, 28, 28, 512)       2048      
_________________________________________________________________
Conv4_2 (SeparableConv2D)    (None, 28, 28, 512)       267264    
_________________________________________________________________
bn4 (BatchNormalization)     (None, 28, 28, 512)       2048      
_________________________________________________________________
Conv4_3 (SeparableConv2D)    (None, 28, 28, 512)       267264    
_________________________________________________________________
pool4 (MaxPooling2D)         (None, 14, 14, 512)       0         
_________________________________________________________________
flatten (Flatten)            (None, 100352)            0         
_________________________________________________________________
fc1 (Dense)                  (None, 1024)              102761472 
_________________________________________________________________
dropout1 (Dropout)           (None, 1024)              0         
_________________________________________________________________
fc2 (Dense)                  (None, 512)               524800    
_________________________________________________________________
dropout2 (Dropout)           (None, 512)               0         
_________________________________________________________________
fc3 (Dense)                  (None, 2)                 1026      
=================================================================
Total params: 104,197,506
Trainable params: 104,194,434
Non-trainable params: 3,072
_________________________________________________________________

Load weight từ pre-train của VGG16 và gán cho các layer:

# Open the VGG16 weight file
f = h5py.File('../input/vgg16/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5', 'r')

# Select the layers for which you want to set weight.

w,b = f['block1_conv1']['block1_conv1_W_1:0'], f['block1_conv1']['block1_conv1_b_1:0']
model.layers[1].set_weights = [w,b]

w,b = f['block1_conv2']['block1_conv2_W_1:0'], f['block1_conv2']['block1_conv2_b_1:0']
model.layers[2].set_weights = [w,b]

w,b = f['block2_conv1']['block2_conv1_W_1:0'], f['block2_conv1']['block2_conv1_b_1:0']
model.layers[4].set_weights = [w,b]

w,b = f['block2_conv2']['block2_conv2_W_1:0'], f['block2_conv2']['block2_conv2_b_1:0']
model.layers[5].set_weights = [w,b]

f.close()
model.summary()    

Bước 5: Training thuật toán

Complie mô hình

opt = Adam(lr=0.0001, decay=1e-5)
es = EarlyStopping(patience=5)
chkpt = ModelCheckpoint(filepath='best_model_todate', save_best_only=True, save_weights_only=True)
model.compile(loss='binary_crossentropy', metrics=['accuracy'],optimizer=opt)

lấy dữ liệu

batch_size = 16
nb_epochs = 20

# Get a train data generator
train_data_gen = data_gen(data=train_data, batch_size=batch_size)

# Define the number of training steps
nb_train_steps = train_data.shape[0]//batch_size

print("Number of training and validation steps: {} and {}".format(nb_train_steps, len(valid_data)))

Training

# # Fit the model
history = model.fit_generator(train_data_gen, epochs=nb_epochs, steps_per_epoch=nb_train_steps,
                               validation_data=(valid_data, valid_labels),callbacks=[es, chkpt],
                               class_weight={0:1.0, 1:0.4})

Việc training này khá lâu, có thể mất một vài tiếng phụ thuộc vào cấu hình máy bạn. Nếu bạn không muốn chạy qua bước này thì mình đã thực hiện rồi, bạn chỉ cần load file weight do mình đã training trước rồi. Bạn tải file ở đây

# Load the model weights
model.load_weights("../input/xray-best-model/best_model/best_model.hdf5")

Bước 6: Kiểm thử mô hình

# Preparing test data
normal_cases_dir = test_dir / 'NORMAL'
pneumonia_cases_dir = test_dir / 'PNEUMONIA'

normal_cases = normal_cases_dir.glob('*.jpeg')
pneumonia_cases = pneumonia_cases_dir.glob('*.jpeg')

test_data = []
test_labels = []

for img in normal_cases:
    img = cv2.imread(str(img))
    img = cv2.resize(img, (224,224))
    if img.shape[2] ==1:
        img = np.dstack([img, img, img])
    else:
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = img.astype(np.float32)/255.
    label = to_categorical(0, num_classes=2)
    test_data.append(img)
    test_labels.append(label)
                      
for img in pneumonia_cases:
    img = cv2.imread(str(img))
    img = cv2.resize(img, (224,224))
    if img.shape[2] ==1:
        img = np.dstack([img, img, img])
    else:
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = img.astype(np.float32)/255.
    label = to_categorical(1, num_classes=2)
    test_data.append(img)
    test_labels.append(label)
    

test_data = np.array(test_data)
test_labels = np.array(test_labels)

print("Total number of test examples: ", test_data.shape)
print("Total number of labels:", test_labels.shape)
Total number of test examples:  (624, 224, 224, 3)
Total number of labels: (624, 2)

Đánh giá tập test

# Evaluation on test dataset
test_loss, test_score = model.evaluate(test_data, test_labels, batch_size=16)
print("Loss on test set: ", test_loss)
print("Accuracy on test set: ", test_score)
624/624 [==============================] - 7s 11ms/step
Loss on test set:  0.905697194154084
Accuracy on test set:  0.8269230769230769

Thử dự đoán tập test

# Get predictions
preds = model.predict(test_data, batch_size=16)
preds = np.argmax(preds, axis=-1)

# Original labels
orig_test_labels = np.argmax(test_labels, axis=-1)

print(orig_test_labels.shape)
print(preds.shape)

Bước 7: Đánh giá mô hình

Để đánh giá mô hình, chúng ta sử dụng ma trận confusion

# Get the confusion matrix
cm  = confusion_matrix(orig_test_labels, preds)
plt.figure()
plot_confusion_matrix(cm,figsize=(12,8), hide_ticks=True, alpha=0.7,cmap=plt.cm.Blues)
plt.xticks(range(2), ['Normal', 'Pneumonia'], fontsize=16)
plt.yticks(range(2), ['Normal', 'Pneumonia'], fontsize=16)
plt.show()

Kết quả

Đánh giá bằng chỉ số Precision and Recall

tn, fp, fn, tp = cm.ravel()

precision = tp/(tp+fp)
recall = tp/(tp+fn)

print("Recall of the model is {:.2f}".format(recall))
print("Precision of the model is {:.2f}".format(precision))
Recall of the model is 0.98
Precision of the model is 0.79

Source code

Bạn có thể tải về source code tại đây

Các Weight sử dụng để training bạn có thể tải về ở đây

Cảm ơn các bạn đã theo dõi bài viết hẹn gặp lại trong những bài viết tiếp theo.