前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深度学习检测疟疾

深度学习检测疟疾

作者头像
代码医生工作室
发布2019-06-21 17:43:49
9860
发布2019-06-21 17:43:49
举报
文章被收录于专栏:相约机器人相约机器人

作者 | Dipanjan(DJ)Sarkar

来源 | Medium

编辑 | 代码医生团队

介绍

欢迎来到AI for Social Good系列,将重点关注人工智能(AI)与流行的开源工具,技术和框架如何用于发展和改善社会的不同方面。“健康就是财富”也许是一个陈词滥调,但却非常真实!在这篇特别的文章中,将研究如何利用AI来检测疟疾,这是一种致命的疾病,并且有望建立一个低成本,有效和准确的开源解决方案。本文的目的是双重的 - 了解致命疾病疟疾的动机和重要性以及深度学习在检测疟疾方面的有效性。将在本文中介绍以下主要主题:

  • 这个项目的背景
  • 疟疾检测方法
  • 疟疾检测的深度学习
  • 卷入神经网络(CNN)从头开始训练
  • 使用预训练模型进行迁移学习

本文的目的是要展示人工智能如何有助于疟疾检测,诊断和减少手工劳动,以低成本有效和准确开源解决方案。

Python和TensorFlow - 构建开源深度学习解决方案的绝佳组合

由于Python的强大功能和TensorFlow等深度学习框架,可以构建强大,可扩展且有效的深度学习解决方案。这些工具的额外好处是开源和免费,使能够构建真正具有成本效益的解决方案,并且每个人都可以轻松采用和使用。

背景

疟疾是由疟原虫寄生虫引起的一种致命的传染性蚊子传播疾病。这些寄生虫通过被感染的雌性蚊子的叮咬传播。有五种主要类型的疟疾。看看这种疾病在以下情节中的致命性的重要性。

疟疾估计风险健康地图

很明显疟疾在全球普遍存在,特别是在热带地区。然而项目的动机是基于这种疾病的性质和致命性。最初如果被感染的蚊子叮咬你,蚊子携带的寄生虫会进入你的血液并开始摧毁携氧的红细胞(红细胞)。通常在蚊虫叮咬后几天或几周内感觉不适时,疟疾的第一个症状类似于流感或病毒。然而这些致命的寄生虫可以在你的身体中存活超过一年!因此延迟和错误治疗可能导致并发症甚至死亡。因此早期有效的疟疾检测可以挽救生命。

世界卫生组织已经发布了几个关于疟疾的重要事实:世界上将近一半的人口面临疟疾风险,每年有超过2亿疟疾病例和大约40万人死于疟疾。要尽力让疟疾检测和诊断快速,简便和有效。

疟疾检测方法

有几种方法和测试可用于疟疾检测和诊断。基于的原始论文的数据分析,预先训练的卷积神经网络作为特征提取器,用于改善薄血涂片图像中的疟疾寄生虫检测,S Rajaraman等。简要介绍一下这些方法。但不限于厚和薄的血涂片检查,聚合酶链反应(PCR)和快速诊断测试(RDT)。后两种测试是通常使用的替代方法,特别是在无法提供高质量显微镜服务的情况下。

疟疾检测的血涂片工作流程

根据WHO方案的指导原则,该程序包括在100倍放大倍数下对血涂片进行深入检查,人们手动计算含有5000个细胞中寄生虫的红细胞。引用文件中的以下内容以使事情更清楚。

厚血涂片有助于检测寄生虫的存在,而薄血涂片有助于识别引起感染的寄生虫种类(疾病控制和预防中心,2012)。诊断准确性在很大程度上取决于人类的专业知识,并且可能受到观察者间差异和疾病流行/资源受限区域大规模诊断所造成的负担的不利影响(Mitiku,Mengistu&Gelaw,2003)。使用替代技术,例如聚合酶链反应(PCR)和快速诊断测试(RDT); 然而,PCR分析的表现受到限制(Hommelsheim等,2014),而RDT在疾病流行地区的成本效益较低(Hawkes,Katsuva&Masumbuko,2009)。

疟疾检测绝对是一个密集的手动过程,所以可以使用深度学习自动化。

疟疾检测的深度学习

通过定期手动诊断血涂片,这是一个密集的手动过程,需要适当的专业知识来分类和计数寄生和未感染的细胞。通常情况下,在世界各地的特定地区没有合适的专业知识,这可能无法很好地扩展并可能导致出现问题。在利用最先进的(SOTA)图像处理和分析技术提取手工设计的特征和构建基于机器学习的分类模型方面取得了一些进展。然而由于手工设计的功能需要花费大量时间,因此这些模型无法扩展,可提供更多数据用于训练。

深度学习模型,或更具体地说,卷积神经网络(CNN)已被证明在各种计算机视觉任务中非常有效。CNN模型中的关键层包括卷积和池化层,如下图所示。

典型的CNN架构

卷积层从数据中学习空间分层模式,这些模式也是平移不变的。因此能够学习图像的不同方面。例如第一卷积层将学习诸如边缘和角落的小和局部图案,第二卷积层将基于来自第一层的特征来学习更大的图案。这允许CNN自动化特征工程并学习有效的特征,这些特征很好地概括了新的数据点。池化层有助于下采样和降维。

因此CNN帮助实现自动化和可扩展的功能工程。此外在模型末端插入密集层能够执行图像分类等任务。使用像CNN这样的深度学习模型进行自动疟疾检测可能非常有效,便宜且可扩展,特别是随着迁移学习和预训练模型的出现,这些模型即使在数据量较少等限制条件下也能很好地工作。

Rajaraman等人的论文。预先训练过的卷积神经网络作为特征提取器,用于改善薄血涂片图像中的寄生虫检测,利用论文中提到的数据共计6个预训练模型,在检测疟疾时获得95.9%的令人印象深刻的准确度。未感染的样本。重点是从头开始尝试一些简单的CNN模型和一些预先训练的模型,使用传递学习来查看我们在同一数据集上得到的结果!将使用包含Python和TensorFlow的开源工具和框架来构建模型。

数据集详细信息

将在分析中使用的数据集。可以从官方网站下载这些图像。

https://ceb.nlm.nih.gov/repositories/malaria-datasets/

实际上开发了一种移动应用程序,该应用程序运行在连接到传统光学显微镜的标准Android智能手机上(Poostchi等,2018)。收集来自150只恶性疟原虫感染的和50名健康患者的Giemsa染色的薄血涂片载玻片并在孟加拉国吉大港医学院医院拍照。智能手机的内置摄像头为每个微观视野获取了幻灯片图像。图像由泰国曼谷Mahidol-Oxford热带医学研究单位的专家幻灯片阅读器手动注释。简要地看一下数据集结构。首先根据使用的操作系统安装一些基本依赖项。

在云上使用基于Debian的系统,有GPU所以可以更快地运行模型!如果没有树依赖项,请安装树依赖项,以便可以查看目录结构(sudo apt install tree)。

看起来有两个文件夹,其中包含受感染和健康的细胞图像。可以使用以下代码进一步了解图像总数。

代码语言:javascript
复制
import os
import glob
 
base_dir = os.path.join('./cell_images')
infected_dir = os.path.join(base_dir,'Parasitized')
healthy_dir = os.path.join(base_dir,'Uninfected')
 
infected_files = glob.glob(infected_dir+'/*.png')
healthy_files = glob.glob(healthy_dir+'/*.png')
len(infected_files), len(healthy_files)
 
# Output
(13779, 13779)

有一个13779 疟疾和非疟疾(未感染)细胞图像的平衡数据集。从中构建一个数据帧,这将开始构建数据集时很有用。

代码语言:javascript
复制
import numpy as np
import pandas as pd
 
np.random.seed(42)
 
files_df = pd.DataFrame({
    'filename': infected_files + healthy_files,
    'label': ['malaria'] * len(infected_files) + ['healthy'] * len(healthy_files)
}).sample(frac=1, random_state=42).reset_index(drop=True)
 
files_df.head()

构建和探索图像数据集

要构建深度学习模型,需要训练数据,但还需要测试模型在看不见的数据上的性能。将分别使用60:10:30分割训练,验证和测试数据集。将在训练期间利用训练和验证数据集,并检查模型在测试数据集上的性能。

代码语言:javascript
复制
from sklearn.model_selection import train_test_split
from collections import Counter
 
train_files, test_files, train_labels, test_labels = train_test_split(files_df['filename'].values,
                                                                      files_df['label'].values,
                                                                      test_size=0.3, random_state=42)
train_files, val_files, train_labels, val_labels = train_test_split(train_files,
                                                                    train_labels,
                                                                    test_size=0.1, random_state=42)
 
print(train_files.shape, val_files.shape, test_files.shape)
print('Train:', Counter(train_labels), '\nVal:', Counter(val_labels), '\nTest:', Counter(test_labels))
 
# Output
(17361,) (1929,) (8268,)
Train: Counter({'healthy': 8734, 'malaria': 8627})
Val: Counter({'healthy': 970, 'malaria': 959})
Test: Counter({'malaria': 4193, 'healthy': 4075})

现在显然由于血涂片和细胞图像将根据人,测试方法和拍摄照片的方向而变化,因此图像的尺寸不同。得到一些训练数据集的摘要统计数据,以确定最佳图像尺寸。

代码语言:javascript
复制
import cv2
from concurrent import futures
import threading
 
def get_img_shape_parallel(idx, img, total_imgs):
    if idx % 5000 == 0 or idx == (total_imgs - 1):
        print('{}: working on img num: {}'.format(threading.current_thread().name,
                                                  idx))
    return cv2.imread(img).shape
  
ex = futures.ThreadPoolExecutor(max_workers=None)
data_inp = [(idx, img, len(train_files)) for idx, img in enumerate(train_files)]
print('Starting Img shape computation:')
train_img_dims_map = ex.map(get_img_shape_parallel,
                            [record[0] for record in data_inp],
                            [record[1] for record in data_inp],
                            [record[2] for record in data_inp])
train_img_dims = list(train_img_dims_map)
print('Min Dimensions:', np.min(train_img_dims, axis=0))
print('Avg Dimensions:', np.mean(train_img_dims, axis=0))
print('Median Dimensions:', np.median(train_img_dims, axis=0))
print('Max Dimensions:', np.max(train_img_dims, axis=0))
 
 
# Output
Starting Img shape computation:
ThreadPoolExecutor-0_0: working on img num: 0
ThreadPoolExecutor-0_17: working on img num: 5000
ThreadPoolExecutor-0_15: working on img num: 10000
ThreadPoolExecutor-0_1: working on img num: 15000
ThreadPoolExecutor-0_7: working on img num: 17360
Min Dimensions: [46 46  3]
Avg Dimensions: [132.77311215 132.45757733   3.]
Median Dimensions: [130. 130.   3.]
Max Dimensions: [385 394   3]

应用并行处理来加速图像读取操作,并且基于汇总统计,决定将每个图像调整为125x125像素。加载所有图像并将它们调整为这些固定尺寸。

代码语言:javascript
复制
IMG_DIMS = (125, 125)
 
def get_img_data_parallel(idx, img, total_imgs):
    if idx % 5000 == 0 or idx == (total_imgs - 1):
        print('{}: working on img num: {}'.format(threading.current_thread().name,
                                                  idx))
    img = cv2.imread(img)
    img = cv2.resize(img, dsize=IMG_DIMS,
                     interpolation=cv2.INTER_CUBIC)
    img = np.array(img, dtype=np.float32)
    return img
 
ex = futures.ThreadPoolExecutor(max_workers=None)
train_data_inp = [(idx, img, len(train_files)) for idx, img in enumerate(train_files)]
val_data_inp = [(idx, img, len(val_files)) for idx, img in enumerate(val_files)]
test_data_inp = [(idx, img, len(test_files)) for idx, img in enumerate(test_files)]
 
print('Loading Train Images:')
train_data_map = ex.map(get_img_data_parallel,
                        [record[0] for record in train_data_inp],
                        [record[1] for record in train_data_inp],
                        [record[2] for record in train_data_inp])
train_data = np.array(list(train_data_map))
 
print('\nLoading Validation Images:')
val_data_map = ex.map(get_img_data_parallel,
                        [record[0] for record in val_data_inp],
                        [record[1] for record in val_data_inp],
                        [record[2] for record in val_data_inp])
val_data = np.array(list(val_data_map))
 
print('\nLoading Test Images:')
test_data_map = ex.map(get_img_data_parallel,
                        [record[0] for record in test_data_inp],
                        [record[1] for record in test_data_inp],
                        [record[2] for record in test_data_inp])
test_data = np.array(list(test_data_map))
 
train_data.shape, val_data.shape, test_data.shape  
 
 
# Output
Loading Train Images:
ThreadPoolExecutor-1_0: working on img num: 0
ThreadPoolExecutor-1_12: working on img num: 5000
ThreadPoolExecutor-1_6: working on img num: 10000
ThreadPoolExecutor-1_10: working on img num: 15000
ThreadPoolExecutor-1_3: working on img num: 17360
 
Loading Validation Images:
ThreadPoolExecutor-1_13: working on img num: 0
ThreadPoolExecutor-1_18: working on img num: 1928
 
Loading Test Images:
ThreadPoolExecutor-1_5: working on img num: 0
ThreadPoolExecutor-1_19: working on img num: 5000
ThreadPoolExecutor-1_8: working on img num: 8267
((17361, 125, 125, 3), (1929, 125, 125, 3), (8268, 125, 125, 3))

再次利用并行处理来加速与图像加载和调整大小相关的计算。最后得到了所需尺寸的图像张量,如前面的输出所示。现在可以查看一些样本单元格图像,以了解数据是如何形成的。

代码语言:javascript
复制
import matplotlib.pyplot as plt
%matplotlib inline
 
plt.figure(1 , figsize = (8 , 8))
n = 0
for i in range(16):
    n += 1
    r = np.random.randint(0 , train_data.shape[0] , 1)
    plt.subplot(4 , 4 , n)
    plt.subplots_adjust(hspace = 0.5 , wspace = 0.5)
    plt.imshow(train_data[r[0]]/255.)
    plt.title('{}'.format(train_labels[r[0]]))
    plt.xticks([]) , plt.yticks([])

基于上面的示例图像,可以注意到疟疾和健康细胞图像之间的一些细微差别。将基本上使深度学习模型在模型训练期间尝试并学习这些模式。在开始训练模型之前,设置了一些基本配置设置。

代码语言:javascript
复制
BATCH_SIZE = 64
NUM_CLASSES = 2
EPOCHS = 25
INPUT_SHAPE = (125, 125, 3)
 
train_imgs_scaled = train_data / 255.
val_imgs_scaled = val_data / 255.
 
# encode text category labels
from sklearn.preprocessing import LabelEncoder
 
le = LabelEncoder()
le.fit(train_labels)
train_labels_enc = le.transform(train_labels)
val_labels_enc = le.transform(val_labels)
 
print(train_labels[:6], train_labels_enc[:6])
 
 
# Output
['malaria' 'malaria' 'malaria' 'healthy' 'healthy' 'malaria'] [1 1 1 0 0 1]

修复图像尺寸,批量大小,时期并编码分类标签。TensorFlow 2.0的alpha版本于2019年3月发布,可以试用它!

代码语言:javascript
复制
import tensorflow as tf
 
# Load the TensorBoard notebook extension (optional)
%load_ext tensorboard.notebook
 
tf.random.set_seed(42)
tf.__version__
 
# Output
'2.0.0-alpha0'

深度学习模式训练阶段

在模型训练阶段,将构建几个深度学习模型,并根据训练数据进行训练,并比较它们在验证数据上的表现。然后将保存这些模型,并在模型评估阶段再次使用它们。

模型1:来自Scratch的CNN

第一个疟疾检测模型将从头开始构建和训练基本的卷积神经网络(CNN)。首先定义模型架构。

代码语言:javascript
复制
inp = tf.keras.layers.Input(shape=INPUT_SHAPE)
 
conv1 = tf.keras.layers.Conv2D(32, kernel_size=(3, 3),
                               activation='relu', padding='same')(inp)
pool1 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = tf.keras.layers.Conv2D(64, kernel_size=(3, 3),
                               activation='relu', padding='same')(pool1)
pool2 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = tf.keras.layers.Conv2D(128, kernel_size=(3, 3),
                               activation='relu', padding='same')(pool2)
pool3 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv3)
 
flat = tf.keras.layers.Flatten()(pool3)
 
hidden1 = tf.keras.layers.Dense(512, activation='relu')(flat)
drop1 = tf.keras.layers.Dropout(rate=0.3)(hidden1)
hidden2 = tf.keras.layers.Dense(512, activation='relu')(drop1)
drop2 = tf.keras.layers.Dropout(rate=0.3)(hidden2)
 
out = tf.keras.layers.Dense(1, activation='sigmoid')(drop2)
 
model = tf.keras.Model(inputs=inp, outputs=out)
model.compile(optimizer='adam',
                loss='binary_crossentropy',
                metrics=['accuracy'])
model.summary()
 
 
# Output
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 125, 125, 3)]     0         
_________________________________________________________________
conv2d (Conv2D)              (None, 125, 125, 32)      896       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 62, 62, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 62, 62, 64)        18496     
_________________________________________________________________
...
...
_________________________________________________________________
dense_1 (Dense)              (None, 512)               262656    
_________________________________________________________________
dropout_1 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 513       
=================================================================
Total params: 15,102,529
Trainable params: 15,102,529
Non-trainable params: 0
_________________________________________________________________

基于前面代码中的体系结构,CNN模型有三个卷积和池化层,后面是两个密集层和丢失用于正则化。现在训练模型吧!

代码语言:javascript
复制
import datetime
 
logdir = os.path.join('/home/dipanzan_sarkar/projects/tensorboard_logs',
                      datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5,
                              patience=2, min_lr=0.000001)
callbacks = [reduce_lr, tensorboard_callback]
 
history = model.fit(x=train_imgs_scaled, y=train_labels_enc,
                    batch_size=BATCH_SIZE,
                    epochs=EPOCHS,
                    validation_data=(val_imgs_scaled, val_labels_enc),
                    callbacks=callbacks,
                    verbose=1)
                    
 
# Output
Train on 17361 samples, validate on 1929 samples
Epoch 1/25
17361/17361 [====] - 32s 2ms/sample - loss: 0.4373 - accuracy: 0.7814 - val_loss: 0.1834 - val_accuracy: 0.9393
Epoch 2/25
17361/17361 [====] - 30s 2ms/sample - loss: 0.1725 - accuracy: 0.9434 - val_loss: 0.1567 - val_accuracy: 0.9513
...
...
Epoch 24/25
17361/17361 [====] - 30s 2ms/sample - loss: 0.0036 - accuracy: 0.9993 - val_loss: 0.3693 - val_accuracy: 0.9565
Epoch 25/25
17361/17361 [====] - 30s 2ms/sample - loss: 0.0034 - accuracy: 0.9994 - val_loss: 0.3699 - val_accuracy: 0.9559

得到的验证准确率为95.6%,这是非常好的,尽管模型看起来过于拟合。通过绘制训练和验证准确度和损失曲线,可以对此有清晰的认识。

代码语言:javascript
复制
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
t = f.suptitle('Basic CNN Performance', fontsize=12)
f.subplots_adjust(top=0.85, wspace=0.3)
 
max_epoch = len(history.history['accuracy'])+1
epoch_list = list(range(1,max_epoch))
ax1.plot(epoch_list, history.history['accuracy'], label='Train Accuracy')
ax1.plot(epoch_list, history.history['val_accuracy'], label='Validation Accuracy')
ax1.set_xticks(np.arange(1, max_epoch, 5))
ax1.set_ylabel('Accuracy Value')
ax1.set_xlabel('Epoch')
ax1.set_title('Accuracy')
l1 = ax1.legend(loc="best")
 
ax2.plot(epoch_list, history.history['loss'], label='Train Loss')
ax2.plot(epoch_list, history.history['val_loss'], label='Validation Loss')
ax2.set_xticks(np.arange(1, max_epoch, 5))
ax2.set_ylabel('Loss Value')
ax2.set_xlabel('Epoch')
ax2.set_title('Loss')
l2 = ax2.legend(loc="best")

基本CNN的学习曲线

可以看到在第五个时期之后,事情似乎并没有改善整体。保存这个模型以备将来评估。

代码语言:javascript
复制
model.save('basic_cnn.h5')

深度转移学习

就像人类具有能够跨任务传递知识的固有能力一样,转移学习能够利用先前学习的任务中的知识,并将其应用于更新的相关任务,即使在机器学习或深度学习的环境中也是如此。

深度转学的想法

本文目的是否可以利用预先训练的深度学习模型通过在上下文中应用和迁移其知识来解决疟疾检测问题。

将应用两种最受欢迎的深度转移学习策略。

  • 预训练模型作为特征提取器
  • 具有微调的预训练模型

将使用由牛津大学视觉几何组(VGG)开发的预训练VGG-19深度学习模型进行实验。像VGG-19这样的预训练模型是一个已经预先训练过的模型,它位于具有大量不同图像类别的大型数据集(ImageNet)上。该模型应该已经学习了一个强大的特征层次结构,这些特征是关于CNN模型学习的特征的空间,旋转和平移不变量。因此该模型已经学习了超过一百万个图像的特征的良好表示,可以作为适合计算机视觉问题的新图像的良好特征提取器,就像疟疾检测一样!在释放问题的迁移学习的力量之前,简要讨论一下VGG-19模型架构。

了解VGG-19模型

VGG-19模型是一个19层(卷积和完全连接)深度学习网络,建立在ImageNet数据库上,该数据库是为图像识别和分类而构建的。这个模型由Karen Simonyan和Andrew Zisserman建造。VGG-19型号的架构如下图所示。

VGG-19模型架构

清楚地看到总共有16卷积层使用3 x 3卷积滤波器以及用于下采样的最大池层,并且每层中总共有两个完全连接的隐藏层4096单元,后面是密集的1000单元层,其中每个单元代表一个ImageNet数据库中的图像类别。不需要最后三层,因为将使用自己的完全连接的密集层来预测疟疾。更关注前五个块,以便可以利用VGG模型作为有效的特征提取器。

对于其中一个模型,将它作为一个简单的特征提取器,通过冻结所有五个卷积块来确保它们的权重在每个时期之后不会更新。对于最后一个模型,将对VGG模型应用微调,将解冻最后两个块(块4和块5),以便训练每个时期(每批数据)时更新它们的权重自己的模型。

模型2:预训练模型作为特征提取器

为了构建这个模型,将利用TensorFlow加载VGG-19模型,并冻结卷积块以便可以将其用作图像特征提取器。将在末尾插入自己的密集层以执行分类任务。

代码语言:javascript
复制
vgg = tf.keras.applications.vgg19.VGG19(include_top=False, weights='imagenet',
                                        input_shape=INPUT_SHAPE)
vgg.trainable = False
# Freeze the layers
for layer in vgg.layers:
    layer.trainable = False
    
base_vgg = vgg
base_out = base_vgg.output
pool_out = tf.keras.layers.Flatten()(base_out)
hidden1 = tf.keras.layers.Dense(512, activation='relu')(pool_out)
drop1 = tf.keras.layers.Dropout(rate=0.3)(hidden1)
hidden2 = tf.keras.layers.Dense(512, activation='relu')(drop1)
drop2 = tf.keras.layers.Dropout(rate=0.3)(hidden2)
 
out = tf.keras.layers.Dense(1, activation='sigmoid')(drop2)
 
model = tf.keras.Model(inputs=base_vgg.input, outputs=out)
model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=1e-4),
                loss='binary_crossentropy',
                metrics=['accuracy'])
model.summary()
 
 
# Output
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         [(None, 125, 125, 3)]     0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 125, 125, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 125, 125, 64)      36928     
_________________________________________________________________
...
...
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 3, 3, 512)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 4608)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 512)               2359808   
_________________________________________________________________
dropout_2 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 512)               262656    
_________________________________________________________________
dropout_3 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_5 (Dense)              (None, 1)                 513       
=================================================================
Total params: 22,647,361
Trainable params: 2,622,977
Non-trainable params: 20,024,384
_________________________________________________________________

因此从前面的输出可以看出,模型中有很多层,将仅使用VGG-19模型的冻结层作为特征提取器。可以使用以下代码来验证模型中有多少层确实可以训练,以及网络中存在多少层。

代码语言:javascript
复制
print("Total Layers:", len(model.layers))
print("Total trainable layers:",
      sum([1 for l in model.layers if l.trainable]))
 
# Output
Total Layers: 28
Total trainable layers: 6

现在将使用之前模型中使用的类似配置和回调来训练模型。请参阅GitHub存储库以获取训练模型的完整代码。观察以下图表,显示模型的准确性和损失。

https://nbviewer.jupyter.org/github/dipanjanS/data_science_for_all/tree/master/os_malaria_detection/

用于冷冻预训练CNN的学习曲线

这展示了模型并不像基本CNN模型那样过度拟合,但性能并不是真的更好,实际上比基本CNN模型更小。现在保存此模型以供将来评估。

代码语言:javascript
复制
model.save('vgg_frozen.h5')

模型3:具有图像增强的微调预训练模型

在最终模型中,将微调预训练的VGG-19模型的最后两个区块中存在的层的权重。除此之外还将介绍图像增强的概念。图像增强背后的想法与名称听起来完全一样。从训练数据集加载现有图像并对它们应用一些图像变换操作,例如旋转,剪切,平移,缩放等,以生成现有图像的新的,更改的版本。由于这些随机变换,每次都不会得到相同的图像。将利用工具ImageDataGenerator中tf.keras。

代码语言:javascript
复制
train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255,
                                                                zoom_range=0.05,
                                                                rotation_range=25,
                                                                width_shift_range=0.05,
                                                                height_shift_range=0.05,
                                                                shear_range=0.05, horizontal_flip=True,
                                                                fill_mode='nearest')
 
val_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255)
 
# build image augmentation generators
train_generator = train_datagen.flow(train_data, train_labels_enc, batch_size=BATCH_SIZE, shuffle=True)
val_generator = val_datagen.flow(val_data, val_labels_enc, batch_size=BATCH_SIZE, shuffle=False)

除了缩放图像(这是必需的)之外,不对验证数据集应用任何转换,因为将使用它来评估每个时期的模型性能。来看一批图像增强变换的一些样本结果。

代码语言:javascript
复制
img_id = 0
sample_generator = train_datagen.flow(train_data[img_id:img_id+1], train_labels[img_id:img_id+1],
                                      batch_size=1)
sample = [next(sample_generator) for i in range(0,5)]
fig, ax = plt.subplots(1,5, figsize=(16, 6))
print('Labels:', [item[1][0] for item in sample])
l = [ax[i].imshow(sample[i][0][0]) for i in range(0,5)]

样本增强图像

可以清楚地看到前面输出中图像的轻微变化。现在将构建深度学习模型,确保VGG-19模型的最后两个块可以训练。

代码语言:javascript
复制
vgg = tf.keras.applications.vgg19.VGG19(include_top=False, weights='imagenet',
                                        input_shape=INPUT_SHAPE)
# Freeze the layers
vgg.trainable = True
 
set_trainable = False
for layer in vgg.layers:
    if layer.name in ['block5_conv1', 'block4_conv1']:
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False
    
base_vgg = vgg
base_out = base_vgg.output
pool_out = tf.keras.layers.Flatten()(base_out)
hidden1 = tf.keras.layers.Dense(512, activation='relu')(pool_out)
drop1 = tf.keras.layers.Dropout(rate=0.3)(hidden1)
hidden2 = tf.keras.layers.Dense(512, activation='relu')(drop1)
drop2 = tf.keras.layers.Dropout(rate=0.3)(hidden2)
 
out = tf.keras.layers.Dense(1, activation='sigmoid')(drop2)
 
model = tf.keras.Model(inputs=base_vgg.input, outputs=out)
model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=1e-5),
                loss='binary_crossentropy',
                metrics=['accuracy'])
 
print("Total Layers:", len(model.layers))
print("Total trainable layers:", sum([1 for l in model.layers if l.trainable]))
 
 
# Output
Total Layers: 28
Total trainable layers: 16

降低了模型中的学习率,因为不希望在微调时对预训练的层进行大的权重更新。由于使用的是数据生成器,因此该模型的训练过程略有不同,因此将利用该fit_generator(…) 功能。

代码语言:javascript
复制
tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5,
                              patience=2, min_lr=0.000001)
 
callbacks = [reduce_lr, tensorboard_callback]
train_steps_per_epoch = train_generator.n // train_generator.batch_size
val_steps_per_epoch = val_generator.n // val_generator.batch_size
history = model.fit_generator(train_generator, steps_per_epoch=train_steps_per_epoch, epochs=EPOCHS,
                              validation_data=val_generator, validation_steps=val_steps_per_epoch,
                              verbose=1)
 
 
# Output
Epoch 1/25
271/271 [====] - 133s 489ms/step - loss: 0.2267 - accuracy: 0.9117 - val_loss: 0.1414 - val_accuracy: 0.9531
Epoch 2/25
271/271 [====] - 129s 475ms/step - loss: 0.1399 - accuracy: 0.9552 - val_loss: 0.1292 - val_accuracy: 0.9589
...
...
Epoch 24/25
271/271 [====] - 128s 473ms/step - loss: 0.0815 - accuracy: 0.9727 - val_loss: 0.1466 - val_accuracy: 0.9682
Epoch 25/25
271/271 [====] - 128s 473ms/step - loss: 0.0792 - accuracy: 0.9729 - val_loss: 0.1127 - val_accuracy: 0.9641

这看起来是最好的模型,但验证精度几乎达到了96.5%,并且基于训练精度,模型看起来并不像第一个模型那样过度拟合。这可以通过以下学习曲线进行验证。

学习曲线用于经过微调的预训练CNN

保存这个模型,以便可以很快将它用于测试数据集的模型评估。

代码语言:javascript
复制
model.save('vgg_finetuned.h5')

完成了模型训练阶段,现在已准备好在实际测试数据集上测试模型的性能!

深度学习模型绩效评估阶段

现在将通过对测试数据集中的数据进行预测来评估刚刚在训练阶段构建的三种不同模型,构建了一个漂亮的实用程序模块model_evaluation_utils,将使用它来评估具有相关分类指标的深度学习模型的性能。

代码语言:javascript
复制
test_imgs_scaled = test_data / 255.
test_imgs_scaled.shape, test_labels.shape
 
# Output
((8268, 125, 125, 3), (8268,))

下一步是加载保存的深度学习模型并对测试数据进行预测。

代码语言:javascript
复制
# Load Saved Deep Learning Models
basic_cnn = tf.keras.models.load_model('./basic_cnn.h5')
vgg_frz = tf.keras.models.load_model('./vgg_frozen.h5')
vgg_ft = tf.keras.models.load_model('./vgg_finetuned.h5')
 
# Make Predictions on Test Data
basic_cnn_preds = basic_cnn.predict(test_imgs_scaled, batch_size=512)
vgg_frz_preds = vgg_frz.predict(test_imgs_scaled, batch_size=512)
vgg_ft_preds = vgg_ft.predict(test_imgs_scaled, batch_size=512)
 
basic_cnn_pred_labels = le.inverse_transform([1 if pred > 0.5 else 0
                                                  for pred in basic_cnn_preds.ravel()])
vgg_frz_pred_labels = le.inverse_transform([1 if pred > 0.5 else 0
                                                  for pred in vgg_frz_preds.ravel()])
vgg_ft_pred_labels = le.inverse_transform([1 if pred > 0.5 else 0
                                                  for pred in vgg_ft_preds.ravel()])

最后一步是利用model_evaluation_utils模块,并使用相关的分类指标检查每个模型的性能。

代码语言:javascript
复制
import model_evaluation_utils as meu
import pandas as pd
 
basic_cnn_metrics = meu.get_metrics(true_labels=test_labels, predicted_labels=basic_cnn_pred_labels)
vgg_frz_metrics = meu.get_metrics(true_labels=test_labels, predicted_labels=vgg_frz_pred_labels)
vgg_ft_metrics = meu.get_metrics(true_labels=test_labels, predicted_labels=vgg_ft_pred_labels)
 
pd.DataFrame([basic_cnn_metrics, vgg_frz_metrics, vgg_ft_metrics],
             index=['Basic CNN', 'VGG-19 Frozen', 'VGG-19 Fine-tuned'])

看起来第三个模型在测试数据集上的所有三个模型中表现最好,给出了模型精度以及96%的f1分数!

结论

在本文中研究了一个有趣的疟疾检测实际医学成像案例研究。疟疾检测本身并不是一个简单的程序,全球合适人员的可用性也是一个严重问题。研究了易于构建利用AI的开源技术,这种技术可以提供最先进的检测疟疾的准确性,从而使AI具有社会效益。希望在医疗保健领域更多地采用开源人工智能功能,让全世界的每个人都能更便宜地使用它!

代码在GitHub存储库中

https://nbviewer.jupyter.org/github/dipanjanS/data_science_for_all/tree/master/os_malaria_detection/

官方网站数据下载链接

https://ceb.nlm.nih.gov/repositories/malaria-datasets/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 相约机器人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档