使用深度学习检测疟疾

人工智能结合开源硬件工具能够提升严重传染病疟疾的诊断。

人工智能(AI)和开源工具、技术和框架是促进社会进步的强有力的结合。“健康就是财富”可能有点陈词滥调,但它却是非常准确的!在本篇文章,我们将测试 AI 是如何与低成本、有效、精确的开源深度学习方法结合起来一起用来检测致死的传染病疟疾。

我既不是一个医生,也不是一个医疗保健研究者,我也绝不像他们那样合格,我只是对将 AI 应用到医疗保健研究感兴趣。在这片文章中我的想法是展示 AI 和开源解决方案如何帮助疟疾检测和减少人工劳动的方法。

Python 和 TensorFlow: 一个构建开源深度学习方法的很棒的结合

感谢 Python 的强大和像 TensorFlow 这样的深度学习框架,我们能够构建健壮的、大规模的、有效的深度学习方法。因为这些工具是自由和开源的,我们能够构建非常经济且易于被任何人采纳和使用的解决方案。让我们开始吧!

项目动机

疟疾是由疟原虫造成的致死的、有传染性的、蚊子传播的疾病,主要通过受感染的雌性按蚊叮咬传播。共有五种寄生虫能够引起疟疾,但是大多数病例是这两种类型造成的:恶性疟原虫和间日疟原虫。

疟疾热图

这个地图显示了疟疾在全球传播分布形势,尤其在热带地区,但疾病的性质和致命性是该项目的主要动机。

如果一只受感染雌性蚊子叮咬了你,蚊子携带的寄生虫进入你的血液,并且开始破坏携带氧气的红细胞(RBC)。通常,疟疾的最初症状类似于流感病毒,在蚊子叮咬后,他们通常在几天或几周内发作。然而,这些致死的寄生虫可以在你的身体里生存长达一年并且不会造成任何症状,延迟治疗可能造成并发症甚至死亡。因此,早期的检查能够挽救生命。

世界健康组织(WHO)的疟疾实情表明,世界近乎一半的人口面临疟疾的风险,有超过 2 亿的疟疾病例,每年由于疟疾造成的死亡将近 40 万。这是使疟疾检测和诊断快速、简单和有效的一个动机。

检测疟疾的方法

有几种方法能够用来检测和诊断疟疾。该文中的项目就是基于 Rajaraman, et al. 的论文:“预先训练的卷积神经网络作为特征提取器,用于改善薄血涂片图像中的疟疾寄生虫检测”介绍的一些方法,包含聚合酶链反应(PCR)和快速诊断测试(RDT)。这两种测试通常用于无法提供高质量显微镜服务的地方。

标准的疟疾诊断通常是基于血液涂片工作流程的,根据 Carlos Ariza 的文章“Malaria Hero:一个更快诊断疟原虫的网络应用”,我从中了解到 Adrian Rosebrock 的“使用 Keras 的深度学习和医学图像分析”。我感激这些优秀的资源的作者,让我在疟原虫预防、诊断和治疗方面有了更多的想法。

一个疟原虫检测的血涂片工作流程

根据 WHO 方案,诊断通常包括对放大 100 倍的血涂片的集中检测。受过训练的人们手工计算在 5000 个细胞中有多少红细胞中包含疟原虫。正如上述解释中引用的 Rajaraman, et al. 的论文:

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

因此,疟疾检测可能受益于使用机器学习的自动化。

疟疾检测的深度学习

人工诊断血涂片是一个繁重的手工过程,需要专业知识来分类和计数被寄生虫感染的和未感染的细胞。这个过程可能不能很好的规模化,尤其在那些专业人士不足的地区。在利用最先进的图像处理和分析技术提取人工选取特征和构建基于机器学习的分类模型方面取得了一些进展。然而,这些模型不能大规模推广,因为没有更多的数据用来训练,并且人工选取特征需要花费很长时间。

深度学习模型,或者更具体地讲,卷积神经网络(CNN),已经被证明在各种计算机视觉任务中非常有效。(如果你想更多的了解关于 CNN 的背景知识,我推荐你阅读视觉识别的 CS2331n 卷积神经网络。)简单地讲,CNN 模型的关键层包含卷积和池化层,正如下图所示。

一个典型的 CNN 架构

卷积层从数据中学习空间层级模式,它是平移不变的,因此它们能够学习图像的不同方面。例如,第一个卷积层将学习小的和局部图案,例如边缘和角落,第二个卷积层将基于第一层的特征学习更大的图案,等等。这允许 CNN 自动化提取特征并且学习对于新数据点通用的有效的特征。池化层有助于下采样和减少尺寸。

因此,CNN 有助于自动化和规模化的特征工程。同样,在模型末尾加上密集层允许我们执行像图像分类这样的任务。使用像 CNN 这样的深度学习模型自动的疟疾检测可能非常有效、便宜和具有规模性,尤其是迁移学习和预训练模型效果非常好,甚至在少量数据的约束下。

Rajaraman, et al. 的论文在一个数据集上利用六个预训练模型在检测疟疾对比无感染样本获取到令人吃惊的 95.9% 的准确率。我们的重点是从头开始尝试一些简单的 CNN 模型和用一个预训练的训练模型使用迁移学习来查看我们能够从相同的数据集中得到什么。我们将使用开源工具和框架,包括 Python 和 TensorFlow,来构建我们的模型。

数据集

我们分析的数据来自 Lister Hill 国家生物医学交流中心(LHNCBC)的研究人员,该中心是国家医学图书馆(NLM)的一部分,他们细心收集和标记了公开可用的健康和受感染的血涂片图像的数据集。这些研究者已经开发了一个运行在 Android 智能手机的疟疾检测手机应用,连接到一个传统的光学显微镜。它们使用吉姆萨染液将 150 个受恶性疟原虫感染的和 50 个健康病人的薄血涂片染色,这些薄血涂片是在孟加拉的吉大港医学院附属医院收集和照相的。使用智能手机的内置相机获取每个显微镜视窗内的图像。这些图片由在泰国曼谷的马希多-牛津热带医学研究所的一个专家使用幻灯片阅读器标记的。

让我们简要地查看一下数据集的结构。首先,我将安装一些基础的依赖(基于使用的操作系统)。

Installing dependencies

我使用的是云上的带有一个 GPU 的基于 Debian 的操作系统,这样我能更快的运行我的模型。为了查看目录结构,我们必须使用 sudo apt install tree 安装 tree 及其依赖(如果我们没有安装的话)。

Installing the tree dependency

我们有两个文件夹包含血细胞的图像,包括受感染的和健康的。我们通过输入可以获取关于图像总数更多的细节:

  1. import os
  2. import glob
  3.  
  4. base_dir = os.path.join('./cell_images')
  5. infected_dir = os.path.join(base_dir,'Parasitized')
  6. healthy_dir = os.path.join(base_dir,'Uninfected')
  7.  
  8. infected_files = glob.glob(infected_dir+'/*.png')
  9. healthy_files = glob.glob(healthy_dir+'/*.png')
  10. len(infected_files), len(healthy_files)
  11.  
  12. # Output
  13. (13779, 13779)

看起来我们有一个平衡的数据集,包含 13,779 张疟疾的和 13,779 张非疟疾的(健康的)血细胞图像。让我们根据这些构建数据帧,我们将用这些数据帧来构建我们的数据集。

  1. import numpy as np
  2. import pandas as pd
  3.  
  4. np.random.seed(42)
  5.  
  6. files_df = pd.DataFrame({
  7. 'filename': infected_files + healthy_files,
  8. 'label': ['malaria'] * len(infected_files) + ['healthy'] * len(healthy_files)
  9. }).sample(frac=1, random_state=42).reset_index(drop=True)
  10.  
  11. files_df.head()

Datasets

构建和了解图像数据集

为了构建深度学习模型,我们需要训练数据,但是我们还需要使用不可见的数据测试模型的性能。相应的,我们将使用 60:10:30 的比例来划分用于训练、验证和测试的数据集。我们将在训练期间应用训练和验证数据集,并用测试数据集来检查模型的性能。

  1. from sklearn.model_selection import train_test_split
  2. from collections import Counter
  3.  
  4. train_files, test_files, train_labels, test_labels = train_test_split(files_df['filename'].values,
  5. files_df['label'].values,
  6. test_size=0.3, random_state=42)
  7. train_files, val_files, train_labels, val_labels = train_test_split(train_files,
  8. train_labels,
  9. test_size=0.1, random_state=42)
  10.  
  11. print(train_files.shape, val_files.shape, test_files.shape)
  12. print('Train:', Counter(train_labels), '\nVal:', Counter(val_labels), '\nTest:', Counter(test_labels))
  13.  
  14. # Output
  15. (17361,) (1929,) (8268,)
  16. Train: Counter({'healthy': 8734, 'malaria': 8627})
  17. Val: Counter({'healthy': 970, 'malaria': 959})
  18. Test: Counter({'malaria': 4193, 'healthy': 4075})

这些图片尺寸并不相同,因为血涂片和细胞图像是基于人、测试方法、图片方向不同而不同的。让我们总结我们的训练数据集的统计信息来决定最佳的图像尺寸(牢记,我们根本不会碰测试数据集)。

  1. import cv2
  2. from concurrent import futures
  3. import threading
  4.  
  5. def get_img_shape_parallel(idx, img, total_imgs):
  6. if idx % 5000 == 0 or idx == (total_imgs - 1):
  7. print('{}: working on img num: {}'.format(threading.current_thread().name,
  8. idx))
  9. return cv2.imread(img).shape
  10. ex = futures.ThreadPoolExecutor(max_workers=None)
  11. data_inp = [(idx, img, len(train_files)) for idx, img in enumerate(train_files)]
  12. print('Starting Img shape computation:')
  13. train_img_dims_map = ex.map(get_img_shape_parallel,
  14. [record[0] for record in data_inp],
  15. [record[1] for record in data_inp],
  16. [record[2] for record in data_inp])
  17. train_img_dims = list(train_img_dims_map)
  18. print('Min Dimensions:', np.min(train_img_dims, axis=0))
  19. print('Avg Dimensions:', np.mean(train_img_dims, axis=0))
  20. print('Median Dimensions:', np.median(train_img_dims, axis=0))
  21. print('Max Dimensions:', np.max(train_img_dims, axis=0))
  22.  
  23.  
  24. # Output
  25. Starting Img shape computation:
  26. ThreadPoolExecutor-0_0: working on img num: 0
  27. ThreadPoolExecutor-0_17: working on img num: 5000
  28. ThreadPoolExecutor-0_15: working on img num: 10000
  29. ThreadPoolExecutor-0_1: working on img num: 15000
  30. ThreadPoolExecutor-0_7: working on img num: 17360
  31. Min Dimensions: [46 46 3]
  32. Avg Dimensions: [132.77311215 132.45757733 3.]
  33. Median Dimensions: [130. 130. 3.]
  34. Max Dimensions: [385 394 3]

我们应用并行处理来加速图像读取,并且基于汇总统计结果,我们将每幅图片的尺寸重新调整到 125×125 像素。让我们载入我们所有的图像并重新调整它们为这些固定尺寸。

  1. IMG_DIMS = (125, 125)
  2.  
  3. def get_img_data_parallel(idx, img, total_imgs):
  4. if idx % 5000 == 0 or idx == (total_imgs - 1):
  5. print('{}: working on img num: {}'.format(threading.current_thread().name,
  6. idx))
  7. img = cv2.imread(img)
  8. img = cv2.resize(img, dsize=IMG_DIMS,
  9. interpolation=cv2.INTER_CUBIC)
  10. img = np.array(img, dtype=np.float32)
  11. return img
  12.  
  13. ex = futures.ThreadPoolExecutor(max_workers=None)
  14. train_data_inp = [(idx, img, len(train_files)) for idx, img in enumerate(train_files)]
  15. val_data_inp = [(idx, img, len(val_files)) for idx, img in enumerate(val_files)]
  16. test_data_inp = [(idx, img, len(test_files)) for idx, img in enumerate(test_files)]
  17.  
  18. print('Loading Train Images:')
  19. train_data_map = ex.map(get_img_data_parallel,
  20. [record[0] for record in train_data_inp],
  21. [record[1] for record in train_data_inp],
  22. [record[2] for record in train_data_inp])
  23. train_data = np.array(list(train_data_map))
  24.  
  25. print('\nLoading Validation Images:')
  26. val_data_map = ex.map(get_img_data_parallel,
  27. [record[0] for record in val_data_inp],
  28. [record[1] for record in val_data_inp],
  29. [record[2] for record in val_data_inp])
  30. val_data = np.array(list(val_data_map))
  31.  
  32. print('\nLoading Test Images:')
  33. test_data_map = ex.map(get_img_data_parallel,
  34. [record[0] for record in test_data_inp],
  35. [record[1] for record in test_data_inp],
  36. [record[2] for record in test_data_inp])
  37. test_data = np.array(list(test_data_map))
  38.  
  39. train_data.shape, val_data.shape, test_data.shape
  40.  
  41.  
  42. # Output
  43. Loading Train Images:
  44. ThreadPoolExecutor-1_0: working on img num: 0
  45. ThreadPoolExecutor-1_12: working on img num: 5000
  46. ThreadPoolExecutor-1_6: working on img num: 10000
  47. ThreadPoolExecutor-1_10: working on img num: 15000
  48. ThreadPoolExecutor-1_3: working on img num: 17360
  49.  
  50. Loading Validation Images:
  51. ThreadPoolExecutor-1_13: working on img num: 0
  52. ThreadPoolExecutor-1_18: working on img num: 1928
  53.  
  54. Loading Test Images:
  55. ThreadPoolExecutor-1_5: working on img num: 0
  56. ThreadPoolExecutor-1_19: working on img num: 5000
  57. ThreadPoolExecutor-1_8: working on img num: 8267
  58. ((17361, 125, 125, 3), (1929, 125, 125, 3), (8268, 125, 125, 3))

我们再次应用并行处理来加速有关图像载入和重新调整大小的计算。最终,我们获得了所需尺寸的图片张量,正如前面的输出所示。我们现在查看一些血细胞图像样本,以对我们的数据有个印象。

  1. import matplotlib.pyplot as plt
  2. %matplotlib inline
  3.  
  4. plt.figure(1 , figsize = (8 , 8))
  5. n = 0
  6. for i in range(16):
  7. n += 1
  8. r = np.random.randint(0 , train_data.shape[0] , 1)
  9. plt.subplot(4 , 4 , n)
  10. plt.subplots_adjust(hspace = 0.5 , wspace = 0.5)
  11. plt.imshow(train_data[r[0]]/255.)
  12. plt.title('{}'.format(train_labels[r[0]]))
  13. plt.xticks([]) , plt.yticks([])

Malaria cell samples

基于这些样本图像,我们看到一些疟疾和健康细胞图像的细微不同。我们将使我们的深度学习模型试图在模型训练中学习这些模式。

开始我们的模型训练前,我们必须建立一些基础的配置设置。

  1. BATCH_SIZE = 64
  2. NUM_CLASSES = 2
  3. EPOCHS = 25
  4. INPUT_SHAPE = (125, 125, 3)
  5.  
  6. train_imgs_scaled = train_data / 255.
  7. val_imgs_scaled = val_data / 255.
  8.  
  9. # encode text category labels
  10. from sklearn.preprocessing import LabelEncoder
  11.  
  12. le = LabelEncoder()
  13. le.fit(train_labels)
  14. train_labels_enc = le.transform(train_labels)
  15. val_labels_enc = le.transform(val_labels)
  16.  
  17. print(train_labels[:6], train_labels_enc[:6])
  18.  
  19.  
  20. # Output
  21. ['malaria' 'malaria' 'malaria' 'healthy' 'healthy' 'malaria'] [1 1 1 0 0 1]

我们修复我们的图像尺寸、批量大小,和纪元,并编码我们的分类的类标签。TensorFlow 2.0 于 2019 年三月发布,这个练习是尝试它的完美理由。

  1. import tensorflow as tf
  2.  
  3. # Load the TensorBoard notebook extension (optional)
  4. %load_ext tensorboard.notebook
  5.  
  6. tf.random.set_seed(42)
  7. tf.__version__
  8.  
  9. # Output
  10. '2.0.0-alpha0'

深度学习训练

在模型训练阶段,我们将构建三个深度训练模型,使用我们的训练集训练,使用验证数据比较它们的性能。然后,我们保存这些模型并在之后的模型评估阶段使用它们。

模型 1:从头开始的 CNN

我们的第一个疟疾检测模型将从头开始构建和训练一个基础的 CNN。首先,让我们定义我们的模型架构,

  1. inp = tf.keras.layers.Input(shape=INPUT_SHAPE)
  2.  
  3. conv1 = tf.keras.layers.Conv2D(32, kernel_size=(3, 3),
  4. activation='relu', padding='same')(inp)
  5. pool1 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv1)
  6. conv2 = tf.keras.layers.Conv2D(64, kernel_size=(3, 3),
  7. activation='relu', padding='same')(pool1)
  8. pool2 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv2)
  9. conv3 = tf.keras.layers.Conv2D(128, kernel_size=(3, 3),作者:Dipanjan (dj) Sarkar_Linux中国
    原文链接:https://linux.cn/article-10891-1.html