【机器学习】基于3D CNN通过CT图像分类预测肺炎

发布于:2024-06-18 ⋅ 阅读:(22) ⋅ 点赞:(0)

1. 引言

1.1. 研究背景

在医学诊断中,医生通过分析CT影像来预测疾病时,面临一些挑战和局限性:

  1. 图像信息的广度与复杂性
    CT扫描生成的大量图像对医生来说既是信息的宝库也是处理上的负担。每组CT数据可能包含数百张切片,医生必须迅速审阅这些图像,以便捕捉到病变的微小细节。这种庞大的信息量要求医生在有限的时间内做出精准诊断,但同时也增加了漏诊或误诊的风险。部分容积效应也可能模糊小病变的边界,使得准确诊断变得更加困难。

  2. 技术局限与诊断的主观性
    CT技术虽然在疾病检测方面具有显著优势,但仍有其固有的技术限制。一些复杂的生物结构或病变细节可能在CT图像中不够清晰,限制了疾病的准确诊断。此外,诊断过程在很大程度上依赖于医生的专业技能和经验。不同的医生对同一组CT影像的解读可能存在差异,这种主观性可能导致诊断结果的波动。

通过在CT片识别过程中,增加人工智能的模型判定,甚至是将2D的图片变换为3D结构,并进行主动的识别和判定,有利于提高CT的使用效果和效率,

1.2. 3D CNN简介

3D CNN(三维卷积神经网络)是一种特殊的卷积神经网络,主要用于处理三维数据,如医学影像中的CT扫描、MRI图像,或是视频流等。

1.2.1. 数据处理
  • 输入数据:与2D CNN主要处理二维图像(如RGB图片)不同,3D CNN的输入数据是三维的,例如,它可以是一个立方体形状的像素块,其中包含了深度(depth)、高度(height)和宽度(width)三个维度。
  • 通道数:与2D CNN相似,3D CNN也考虑通道数(c),这通常对应于数据的颜色通道(对于彩色图像)或其他特性。
1.2.2. 卷积操作
  • 3D卷积核:3D CNN的核心是其3D卷积操作。3D卷积核的尺寸可以表示为 k_h × k_w × k_d,其中k_h、k_w和k_d分别是卷积核在高度、宽度和深度方向上的大小。
  • 特征提取:通过滑动3D卷积核与输入数据进行卷积操作,可以提取出三维空间中的特征。这种操作对于处理3D图像或视频数据特别有效,因为它能够同时考虑空间和时间(或深度)维度上的信息。
1.2.3. 网络结构
  • 卷积层:3D CNN包含多个卷积层,每个卷积层使用多个3D卷积核对输入数据进行处理,生成一系列特征图(或称为激活图)。
  • 池化层:类似于2D CNN,3D CNN也包含池化层,用于减少数据的空间维度和计算量。常用的池化方法包括最大池化、平均池化等。
  • 全连接层:在卷积层和池化层之后,3D CNN通常包含全连接层,用于将提取的特征映射到输出层,以进行分类或回归等任务。
1.2.4. 特性与优势
  • 丰富的信息:由于能够同时处理三维空间中的信息,3D CNN能够提取出更丰富的特征,这对于处理3D图像或视频数据特别重要。
  • 更好的性能:在一些应用中,如医学影像分析、视频动作识别等,3D CNN的性能往往优于2D CNN,因为它能够更准确地捕捉三维空间中的结构和运动信息。
  • 计算成本高:然而,由于需要处理三维数据,3D CNN的计算成本通常比2D CNN更高,需要更多的计算资源和时间。
1.2.5. 应用场景
  • 医学影像分析:用于肿瘤检测、器官分割等任务。
  • 视频分析:用于动作识别、场景理解等任务。
  • 3D建模与识别:在虚拟现实、增强现实等领域有广泛应用。

1.3 3D CNN应用于CT识别现状

随着深度学习技术的快速发展,3D CNN在医学影像分析领域的应用越来越广泛,特别是在CT识别方面。CT图像作为一种三维的医学影像数据,具有丰富的空间信息,对于疾病的诊断和治疗具有重要意义。因此,利用3D CNN进行CT图像的识别和分析具有重要的应用价值。

1.3.1.应用现状
  1. 疾病诊断与检测
  • 肺部疾病:3D CNN在肺部CT图像识别中取得了显著成果,如肺结节、肺癌等疾病的检测。通过训练3D CNN模型,可以自动提取CT图像中的特征,实现疾病的准确诊断。
  • 其他器官疾病:除了肺部疾病外,3D CNN还可以应用于其他器官的CT图像识别,如肝脏、肾脏等。这些应用同样展示了3D CNN在医学影像分析领域的潜力。
  1. 器官分割与重建
  • 器官分割:3D CNN可以用于CT图像中的器官分割任务,如心脏、血管等。通过训练模型,可以自动识别并分割出CT图像中的目标器官,为医生提供准确的诊断依据。
  • 三维重建:基于CT图像的器官三维重建是医学影像分析的重要任务之一。3D CNN可以用于提取CT图像中的特征,生成高质量的三维重建模型,为医生提供更直观、更全面的诊断信息。
  1. 技术进展与优势
  • 性能提升:相比传统的二维图像处理技术,3D CNN能够更好地利用CT图像的三维空间信息,提高识别准确率。同时,随着计算能力的提升和深度学习框架的优化,3D CNN的性能也在不断提升。
  • 泛化能力:3D CNN模型具有较强的泛化能力,可以在不同的CT数据集上进行训练和测试,并取得较好的识别效果。这使得3D CNN在医学影像分析领域具有广泛的应用前景。

3D CNN在CT识别领域的应用已经取得了一定的成果,但仍面临一些挑战和问题,如数据标注困难、计算资源消耗大等。未来,随着技术的不断发展和优化,相信3D CNN在医学影像分析领域的应用将更加广泛和深入。同时,也需要加强跨学科合作,共同推动医学影像分析技术的发展和应用。

1.4 研究内容

本文旨在探讨利用3D卷积神经网络(3D CNN)对CT图像进行肺炎分类预测的方法。文章首先介绍了肺炎的诊断重要性、传统方法的局限,以及3D CNN在医学图像分析中的优势。接着,详细阐述了3D CNN的基本原理、数据预处理流程、模型架构设计,以及训练和评估策略。

文章的核心部分展示了3D CNN模型在CT图像肺炎检测中的性能,通过准确率、召回率等指标对模型进行了全面评估。此外,通过案例分析,讨论了模型的有效性和潜在的误诊情况,并探讨了提高模型泛化能力和减少误诊的途径。

最后,文章总结了3D CNN在肺炎检测中的潜力,指出了当前方法的局限性,并对未来的研究方向提出了建议。这包括进一步优化模型结构、探索更大的数据集,以及推动3D CNN在临床实践中的应用。通过这些研究,有望提高肺炎的早期诊断率,为患者提供更及时的治疗。

2. 3D CNN模型识别CT图像过程

2.1. 设置

# 导入os模块,用于操作文件和目录
import os
# 导入zipfile模块,用于读取和写入ZIP压缩文件
import zipfile
# 导入numpy库,一个用于科学计算的Python库
import numpy as np
# 导入tensorflow库,用于深度学习模型的构建和训练
import tensorflow as tf

# 从tensorflow.keras子模块导入keras库,用于构建深度学习模型
from tensorflow.keras import layers

# 注意:在TensorFlow 2.x版本中,可以直接使用tensorflow.keras代替旧版本的keras

代码主要是导入Python编程中常用的一些库,用于后续的数据处理、模型构建和训练等操作:

  • os:用于操作文件系统,例如文件路径的拼接、目录的遍历等。
  • zipfile:用于创建、读取或写入ZIP文件。
  • numpy:一个强大的科学计算库,用于处理多维数组和矩阵。
  • tensorflow:一个开源机器学习库,用于构建和训练深度学习模型。
  • keras:一个高层神经网络API,可以在TensorFlow之上使用,简化了模型的构建过程。

2.2. 数据预处理

在深入探讨深度学习在医学图像分析中的应用时,MosMedData: Chest CT Scans with COVID-19 Related Findings数据集为我们提供了一个宝贵的机会。这个数据集包含了大量具有COVID-19相关肺部异常的CT扫描图像,同时也包括了一些没有此类异常的图像。通过对这些数据的利用,我们可以训练出能够准确识别病毒性肺炎的深度学习模型。

在本案例中,我们将重点关注该数据集的子集,利用这些CT扫描图像及其相应的放射学诊断标签来构建一个3D卷积神经网络(3D CNN)分类器。该分类器的目标是对新的CT扫描图像进行预测,判断其是否显示出病毒性肺炎的迹象。这是一个典型的二元分类问题,其中标签是二进制的:存在病毒性肺炎(阳性)或不存在(阴性)。

为了实现这一目标,我们首先需要下载并预处理MosMedData的子集。这通常包括将CT扫描图像转换为模型可以处理的格式(如NumPy数组),并对其进行必要的归一化或标准化处理。此外,我们还需要将放射学诊断标签转换为模型可以理解的数值形式(如0和1)。

一旦数据准备就绪,我们就可以开始构建和训练3D CNN模型了。这个过程通常涉及选择适当的网络架构(如ResNet、VGG等),并调整其参数以适应我们的任务。在训练过程中,我们将使用带有标签的CT扫描图像来优化模型的参数,使其能够尽可能准确地预测新的、未见过的CT扫描图像中是否存在病毒性肺炎。

通过精心设计和训练,我们的3D CNN模型将能够成为医生的有力工具,帮助他们更快速、更准确地诊断病毒性肺炎。这不仅有助于提高诊断效率,还可以减少因误诊或漏诊而给患者带来的潜在风险。因此,利用深度学习技术来处理和分析医学图像数据具有重要的现实意义和应用前景。

2.2.1. 下载数据集
import os
import zipfile
from tensorflow import keras

# 定义正常CT扫描图像的下载链接
url_normal = "https://github.com/hasibzunair/3D-image-classification-tutorial/releases/download/v0.2/CT-0.zip"
# 定义下载后文件的存储路径
filename_normal = os.path.join(os.getcwd(), "CT-0.zip")
# 使用keras的get_file函数下载文件
keras.utils.get_file(filename_normal, url_normal)

# 定义异常CT扫描图像的下载链接
url_abnormal = "https://github.com/hasibzunair/3D-image-classification-tutorial/releases/download/v0.2/CT-23.zip"
# 定义下载后文件的存储路径
filename_abnormal = os.path.join(os.getcwd(), "CT-23.zip")
# 使用keras的get_file函数下载文件
keras.utils.get_file(filename_abnormal, url_abnormal)

# 创建一个目录用于存储下载的数据
os.makedirs("MosMedData")

# 解压"CT-0.zip"文件到"MosMedData"目录
with zipfile.ZipFile(filename_normal, "r") as z_fp:
    z_fp.extractall("./MosMedData/")

# 解压"CT-23.zip"文件到"MosMedData"目录
with zipfile.ZipFile(filename_abnormal, "r") as z_fp:
    z_fp.extractall("./MosMedData/")

代码的主要功能是下载和解压CT扫描图像数据集,用于后续的3D图像分类任务。关键步骤包括:

  1. 定义正常和异常CT扫描图像数据集的下载链接。
  2. 使用keras.utils.get_file函数下载数据集到当前工作目录。
  3. 创建一个名为"MosMedData"的新目录,用于存储解压后的CT扫描图像数据。
  4. 使用zipfile.ZipFile对象打开下载的ZIP文件,并调用extractall方法将文件解压到指定目录。

请注意,代码中的os.getcwd()函数获取当前工作目录的路径,os.makedirs用于创建新目录,如果目录已存在,则不会抛出错误。keras.utils.get_file函数会自动处理文件的下载和保存,如果文件已存在则不会重新下载。

2.2.2. 数据加载

数据加载与预处理是机器学习项目中的一个重要环节,特别是对于医学影像数据如CT扫描。在这个案例中,我们将处理以Nifti格式(.nii扩展名)提供的CT扫描文件。为了读取这些文件,我们将使用nibabel库,这是一个专门用于处理医学图像格式的Python库。

首先,我们需要确保安装了nibabel库,这可以通过在Python环境中运行pip install nibabel命令来实现。

CT扫描存储的原始数据是体素的强度,通常用Hounsfield单位(HU)表示。在这个数据集中,HU值的范围从-1024(代表空气)到超过2000(代表致密组织如骨骼)。由于我们主要关注肺部区域,而骨骼的HU值通常超过400,因此我们将400作为上限来排除骨骼等无关组织。

为了进行有效的模型训练,我们需要对数据进行预处理。以下是预处理的步骤:

  1. 方向校正:首先,我们将所有CT扫描的体积旋转90度,以确保它们具有一致的方向。这有助于模型在训练时更容易地识别特征。

  2. HU值缩放:接下来,我们将HU值缩放到0到1的范围内。这是因为大多数深度学习模型都期望输入数据在相同的尺度上,缩放可以确保模型能够更有效地学习特征。

  3. 尺寸调整:由于CT扫描的体积可能具有不同的尺寸(宽度、高度和深度),我们需要将它们调整到相同的尺寸,以便能够批量处理。这通常涉及插值技术,如最近邻插值或双线性插值。

为了实现这些预处理步骤,我们将定义一些辅助函数。这些函数将处理单个CT扫描文件,并将它们转换为模型可以接受的格式。当构建训练和验证数据集时,我们将使用这些函数来批量处理数据。

通过预处理步骤,我们可以确保模型能够充分利用CT扫描中的信息,并有效地学习区分具有COVID-19相关肺部异常的图像和正常图像的特征。

import nibabel as nib  # 用于读取和处理NIfTI格式的医学图像文件
from scipy import ndimage  # 用于图像的变换和滤波

def read_nifti_file(filepath):
    """读取并加载NIfTI文件"""
    # 使用nibabel加载NIfTI文件
    scan = nib.load(filepath)
    # 获取文件的原始数据
    scan = scan.get_fdata()
    return scan

def normalize(volume):
    """对图像数据进行归一化处理"""
    # 设置最小和最大阈值
    min_val = -1000
    max_val = 400
    # 将低于最小阈值的数据设置为最小阈值
    volume[volume < min_val] = min_val
    # 将高于最大阈值的数据设置为最大阈值
    volume[volume > max_val] = max_val
    # 进行归一化
    volume = (volume - min_val) / (max_val - min_val)
    # 转换数据类型为float32
    volume = volume.astype("float32")
    return volume

def resize_volume(img):
    """对图像数据的Z轴进行重新采样"""
    # 设置期望的尺寸
    desired_depth = 64
    desired_width = 128
    desired_height = 128
    # 获取当前图像的尺寸
    current_depth = img.shape[-1]
    current_width = img.shape[0]
    current_height = img.shape[1]
    # 计算缩放比例
    depth_factor = current_depth / desired_depth
    width_factor = current_width / desired_width
    height_factor = current_height / desired_height
    # 旋转图像90度,以匹配期望的方向
    img = ndimage.rotate(img, 90, reshape=False)
    # 使用线性插值对图像进行缩放
    img = ndimage.zoom(img, (width_factor, height_factor, depth_factor), order=1)
    return img

def process_scan(path):
    """读取、归一化和重新采样图像数据"""
    # 读取NIfTI文件
    volume = read_nifti_file(path)
    # 对数据进行归一化
    volume = normalize(volume)
    # 调整图像的尺寸
    volume = resize_volume(volume)
    return volume

这段代码定义了四个函数,用于处理医学图像数据:

  1. read_nifti_file(filepath):读取NIfTI格式的医学图像文件,并返回其原始数据。
  2. normalize(volume):对图像数据进行归一化处理,确保数据值位于指定的范围内,并转换为浮点型数据。
  3. resize_volume(img):调整图像数据的尺寸,特别是Z轴的深度,以符合深度学习模型的输入要求。
  4. process_scan(path):整合上述步骤,完成对图像的读取、归一化和尺寸调整。

从类别目录中读取CT扫描的路径。

import os

# "CT-0"文件夹包含正常肺组织的CT扫描图像,没有病毒性肺炎的CT表现。
# 获取"MosMedData/CT-0"目录下所有CT扫描文件的路径列表
normal_scan_paths = [
    os.path.join(os.getcwd(), "MosMedData/CT-0", x)  # 拼接完整路径
    for x in os.listdir("MosMedData/CT-0")  # 列出目录中的所有文件名
]

# "CT-23"文件夹包含有多个磨玻璃样混浊和肺实质受累的CT扫描图像。
# 获取"MosMedData/CT-23"目录下所有CT扫描文件的路径列表
abnormal_scan_paths = [
    os.path.join(os.getcwd(), "MosMedData/CT-23", x)  # 拼接完整路径
    for x in os.listdir("MosMedData/CT-23")  # 列出目录中的所有文件名
]

# 打印正常肺组织的CT扫描数量
print("正常肺组织的CT扫描数量: " + str(len(normal_scan_paths)))
# 打印异常肺组织的CT扫描数量
print("异常肺组织的CT扫描数量: " + str(len(abnormal_scan_paths)))

这段代码的主要功能是:

  1. 通过列表推导式和os.path.join函数,构造包含特定目录下所有CT扫描文件完整路径的列表。
  2. 使用os.listdir函数列出指定目录中的所有文件名。
  3. 打印出正常和异常肺组织CT扫描文件的数量。

代码中的os.getcwd()函数获取当前工作目录的路径,os.path.join用于拼接路径,确保不同操作系统下路径的正确性。os.listdir用于列出目录中的文件和子目录名。通过这两个列表,可以进一步处理或分析对应的CT扫描图像数据。

2.2.3 划分数据集

构建训练和验证数据集是机器学习项目中至关重要的步骤,特别是当我们处理医学图像数据如CT扫描时。首先,我们需要从表示不同类别的目录中读取CT扫描文件,并为每个扫描分配相应的标签。

为了训练深度学习模型,我们通常需要将图像数据调整为统一的尺寸,以适应模型的输入层。在这个例子中,我们将对CT扫描进行下采样,使其具有128x128x64的尺寸。这样做可以确保所有输入数据都具有相同的维度,同时也有助于减少计算资源的需求。

此外,CT扫描中的原始体素强度值(以Hounsfield单位表示)需要进行缩放,以便在模型训练过程中更好地处理。我们将原始HU值缩放到0到1的范围内,这样可以确保所有输入数据都在相同的尺度上,从而提高模型训练的效率和准确性。

完成上述预处理步骤后,我们需要将数据集分割为训练集和验证集。训练集用于训练模型,而验证集则用于评估模型的性能并调整超参数。通过将数据划分为这两个子集,我们可以确保模型在未见过的数据上也能表现良好,从而增加其在实际应用中的泛化能力。

构建训练和验证数据集是一个涉及多个步骤的过程,包括读取扫描、分配标签、下采样、缩放和分割数据集。这些步骤对于训练出高性能的深度学习模型至关重要。

import numpy as np
import os

# 读取并处理扫描图像。
# 每个扫描图像在高度、宽度和深度上进行重新采样和尺寸调整。
abnormal_scans = np.array([process_scan(path) for path in abnormal_scan_paths])
normal_scans = np.array([process_scan(path) for path in normal_scan_paths])

# 对于存在病毒性肺炎的CT扫描,标记为1;对于正常的CT扫描,标记为0。
abnormal_labels = np.array([1 for _ in range(len(abnormal_scans))])
normal_labels = np.array([0 for _ in range(len(normal_scans))])

# 将数据按照70-30的比例分割为训练集和验证集。
# 假设异常和正常扫描的数量相同,各取前70个作为训练数据
x_train = np.concatenate((abnormal_scans[:70], normal_scans[:70]), axis=0)
y_train = np.concatenate((abnormal_labels[:70], normal_labels[:70]), axis=0)
# 剩余的数据用作验证数据
x_val = np.concatenate((abnormal_scans[70:], normal_scans[70:]), axis=0)
y_val = np.concatenate((abnormal_labels[70:], normal_labels[70:]), axis=0)

# 打印训练集和验证集的样本数量
print(
    "训练集和验证集的样本数量分别是 %d 和 %d。"
    % (x_train.shape[0], x_val.shape[0])
)

代码的主要功能是:

  1. 使用之前定义的process_scan函数处理异常和正常CT扫描图像列表,并将处理后的图像数据转换为NumPy数组。
  2. 为异常和正常CT扫描分配标签,其中异常(存在病毒性肺炎)的标签为1,正常的标签为0。
  3. 根据指定比例(70%训练,30%验证)分割数据集,创建训练集和验证集的图像数组和标签数组。
  4. 使用np.concatenate函数将异常和正常的图像及标签数组沿第一个轴(axis=0)合并,形成完整的训练集和验证集。
  5. 打印出训练集和验证集中的样本数量,以确认数据分割是否正确。

请注意,代码中假设abnormal_scan_pathsnormal_scan_paths中的扫描图像数量是相同的,且每个类别至少有70个样本以满足70-30分割比例的要求。如果实际数据不满足这些条件,需要调整代码以适应实际数据。

2.2.4. 数据增强

数据增强是深度学习训练过程中提升模型泛化能力的重要步骤,它通过对原始数据进行各种变换来生成新的训练样本。在处理CT扫描这类3D医学图像数据时,数据增强尤为关键,因为它能够模拟不同的扫描条件和视角,帮助模型学习更加鲁棒的特征。

在训练过程中,除了常规的数据预处理步骤(如标准化、归一化等),我们还可以通过随机角度旋转来增强CT扫描数据。具体来说,我们可以在训练阶段对每个CT扫描进行随机角度的旋转,以模拟实际扫描过程中可能存在的不同角度和方向。这种变换能够增加模型的视角多样性,使其更好地适应各种实际情况。

由于CT扫描数据通常以形状为(样本,高度,宽度,深度)的4D张量形式存储,我们需要在数据上增加一个大小为1的维度,以符合大多数深度学习模型对输入数据的要求。这个新增的维度通常代表通道数(对于灰度图像,通道数为1),因此,经过扩展后的数据形状变为(样本,高度,宽度,深度,1)。

除了随机旋转外,还有许多其他的数据增强技术可以用于CT扫描数据。例如,我们可以进行随机平移、缩放、翻转等操作,以进一步增加训练样本的多样性。这些变换可以单独使用,也可以组合使用,以达到最佳的数据增强效果。

在应用数据增强技术时,我们需要注意以下几点:

  1. 保持数据的原始结构和特征:在进行数据增强时,要确保变换后的数据仍然保持其原始的结构和特征,避免引入过多的噪声或失真。
  2. 选择合适的增强策略:根据具体任务和数据集的特点,选择合适的增强策略。例如,对于肺部CT扫描,我们可能更关注于旋转和缩放等变换,因为它们能够模拟不同的扫描角度和层厚。
  3. 控制增强的程度:数据增强的程度需要适中,过度的增强可能会导致模型过拟合或学习到无关的特征。因此,我们需要根据实验结果和验证集的性能来调整增强的程度。

数据增强是提升深度学习模型性能的重要手段之一。在处理CT扫描这类3D医学图像数据时,通过应用各种数据增强技术,我们可以有效地增加训练样本的多样性和数量,提高模型的泛化能力和鲁棒性。

import random
import tensorflow as tf
from scipy import ndimage

def rotate(volume):
    ""“对体积数据进行随机角度旋转以增强数据”""
    
    def scipy_rotate(volume):
        # 定义一组旋转角度
        angles = [-20, -10, -5, 5, 10, 20]
        # 从角度列表中随机选择一个角度
        angle = random.choice(angles)
        # 执行旋转操作,reshape=False表示不改变体积数据的尺寸
        volume = ndimage.rotate(volume, angle, reshape=False)
        # 将旋转后的数据归一化到[0, 1]区间
        volume[volume < 0] = 0
        volume[volume > 1] = 1
        return volume

    # 使用TensorFlow的numpy_function来调用Scipy的rotate函数
    augmented_volume = tf.numpy_function(scipy_rotate, [volume], tf.float32)
    return augmented_volume

def train_preprocessing(volume, label):
    ""“训练数据的预处理,包括旋转和添加通道”""
    # 对体积数据进行旋转增强
    volume = rotate(volume)
    # 添加一个维度作为通道维度,以符合深度学习模型的输入要求
    volume = tf.expand_dims(volume, axis=3)
    return volume, label

def validation_preprocessing(volume, label):
    ""“验证数据的预处理,仅添加通道”""
    # 验证数据不需要增强,直接添加通道维度
    volume = tf.expand_dims(volume, axis=3)
    return volume, label

代码的主要功能是:

  1. rotate 函数:定义了一个内部函数 scipy_rotate,用于随机选择一个角度并使用 ndimage.rotate 对体积数据进行旋转。旋转后,将数据归一化到 [0, 1] 区间,并使用 tf.numpy_function 来在TensorFlow中调用该函数。

  2. train_preprocessing 函数:对训练数据进行预处理,包括调用 rotate 函数进行数据增强和使用 tf.expand_dims 添加通道维度。

  3. validation_preprocessing 函数:对验证数据进行预处理,由于验证集不需要数据增强,因此只调用 tf.expand_dims 来添加通道维度。

在构建深度学习模型时,为了确保模型具备良好的泛化能力,我们通常会定义一个数据加载器来加载训练和验证数据集。特别地,对于训练数据,我们通常会引入一些数据增强策略,以增加模型的鲁棒性。

当定义训练和验证数据加载器时,一个关键的步骤是将训练数据通过数据增强函数进行处理。在这个特定的例子中,数据增强函数会随机旋转CT扫描的体积数据,以模拟不同的扫描角度。这种随机旋转有助于模型学习在不同视角下的特征,从而增强其在真实世界中的泛化能力。

值得注意的是,尽管训练数据会经过这样的增强处理,但验证数据通常保持原样,以便我们能够准确地评估模型在未增强数据上的性能。这是因为在验证和测试阶段,我们希望模型能够对原始数据进行准确的预测,而不是依赖于任何形式的增强。

import tensorflow as tf

# 定义训练数据加载器
train_loader = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# 定义验证数据加载器
validation_loader = tf.data.Dataset.from_tensor_slices((x_val, y_val))

# 设置批次大小
batch_size = 2

# 对训练数据进行增强处理
train_dataset = (
    train_loader.shuffle(len(x_train))  # 打乱数据
    .map(train_preprocessing)  # 应用训练数据的预处理
    .batch(batch_size)  # 将数据分批
    .prefetch(2)  # 预取数据以优化性能
)

# 对验证数据进行处理,但不做增强
validation_dataset = (
    validation_loader.shuffle(len(x_val))  # 打乱数据
    .map(validation_preprocessing)  # 应用验证数据的预处理
    .batch(batch_size)  # 将数据分批
    .prefetch(2)  # 预取数据以优化性能
)

代码的主要功能是:

  1. 使用 tf.data.Dataset.from_tensor_slices 创建训练集和验证集的数据加载器,这些加载器可以迭代处理图像和标签。

  2. 设置训练和验证数据的批次大小 batch_size

  3. 为训练数据集定义了一个处理流程,包括:
    - 使用 shuffle 函数打乱数据,有助于模型训练时的泛化能力。
    - 使用 map 函数应用 train_preprocessing 函数,进行数据增强和添加通道维度。
    - 使用 batch 函数将数据分批处理。
    - 使用 prefetch 函数预取数据,以提高数据加载的效率。

  4. 为验证数据集定义了一个处理流程,包括:
    - 与训练数据集类似,首先打乱数据。
    - 使用 map 函数应用 validation_preprocessing 函数,只进行添加通道维度的预处理。
    - 同样使用 batchprefetch 函数进行分批和预取操作。

增强后的数据可以参照以下代码进行可视化。

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

# 从训练数据集中取出一个批次的数据
data = train_dataset.take(1)
# 获取图像数据和标签
images, labels = list(data)[0]
# 将图像数据转换为NumPy数组
images = images.numpy()
# 选择第一个图像进行展示
image = images[0]
# 打印CT扫描的维度
print("CT scan的维度是:", image.shape)
# 展示CT扫描的第31个切片(索引从0开始)
plt.imshow(np.squeeze(image[:, :, 30]), cmap="gray")
# 显示图像
plt.show()

代码的主要功能是:

  1. 使用 train_dataset.take(1) 从训练数据集中取出一个批次的数据,这里假设每个批次包含多个图像。

  2. 将取出的数据解包,获取图像 images 和对应的标签 labels

  3. 调用 images.numpy() 将图像数据从 TensorFlow 的张量格式转换为 NumPy 数组,以便于进行进一步处理和可视化。

  4. 选择数组中的第一个图像 image = images[0]

  5. 打印出 CT 扫描图像的维度,这有助于了解数据的形状。

  6. 使用 plt.imshow 展示第 31 个切片(因为索引是从 0 开始的),使用 np.squeeze 去除单维度的轴,并指定 cmap="gray" 以灰度模式显示图像。

  7. 调用 plt.show() 显示图像。
    在这里插入图片描述
    当处理CT扫描时,由于它是一个三维的数据集,包含了多个连续的二维切片(通常称为“层”或“切片”),直接查看单一的切片可能不足以理解整个扫描的结构和细节。因此,为了更好地理解和分析CT扫描,我们通常会将多个连续的切片组合成一个整体的视图,这个过程被称为“切片组合”或“montage”。

在可视化增强后的CT扫描时,我们可以选择一系列连续的切片,并将它们按照其在三维空间中的顺序排列,形成一个二维的组合图像。这个组合图像能够展示CT扫描的整体结构,包括不同组织和器官的位置、形状和大小等信息。

通过查看增强后的CT扫描的切片组合图,我们可以更直观地了解扫描中的病变区域、器官异常或其他感兴趣的结构。这种可视化方法有助于医生或研究人员进行更准确的诊断和分析,提高医疗服务的质量和效率。

因此,让我们可视化一个增强后的CT扫描的切片组合图,以便更好地理解和分析扫描数据。

import matplotlib.pyplot as plt
import numpy as np

def plot_slices(num_rows, num_columns, width, height, data):
    """
    绘制20个CT切片的拼贴图。
    
    参数:
    - num_rows: 拼贴图中的行数。
    - num_columns: 拼贴图中的列数。
    - width: 每个切片的宽度。
    - height: 每个切片的高度。
    - data: 要绘制的CT切片数据。
    """
    # 将数据旋转90度,并转置
    data = np.rot90(np.array(data))
    data = np.transpose(data)
    # 调整数据形状以匹配行列数和每个切片的尺寸
    data = np.reshape(data, (num_rows, num_columns, width, height))
    # 获取拼贴图中的行和列的数量
    rows_data, columns_data = data.shape[0], data.shape[1]
    # 计算每个切片的高度和每行切片的宽度
    heights = [slc[0].shape[0] for slc in data]
    widths = [slc.shape[1] for slc in data[0]]
    # 计算图形的宽度和高度
    fig_width = 12.0
    fig_height = fig_width * sum(heights) / sum(widths)
    # 创建子图
    f, axarr = plt.subplots(
        num_rows,
        num_columns,
        figsize=(fig_width, fig_height),
        gridspec_kw={"height_ratios": heights},
    )
    # 对每个切片进行绘制
    for i in range(rows_data):
        for j in range(columns_data):
            axarr[i, j].imshow(data[i][j], cmap="gray")  # 使用灰度颜色映射
            axarr[i, j].axis("off")  # 关闭坐标轴
    # 调整子图间的空白
    plt.subplots_adjust(wspace=0, hspace=0, left=0, right=1, bottom=0, top=1)
    plt.show()  # 显示图形

# 可视化CT扫描的切片拼贴图。
# 设置为4行10列,展示CT扫描的前100个切片。
plot_slices(4, 10, 128, 128, image[:, :, :40])

代码的主要功能如下:

  1. 定义了一个函数 plot_slices,用于绘制CT扫描切片的拼贴图。
  2. 将输入的CT扫描数据进行旋转和转置,以适应拼贴图的布局。
  3. 调整数据形状,以符合指定的行数和列数,以及每个切片的宽度和高度。
  4. 计算每个切片的高度和每行切片的宽度,用于确定子图的尺寸。
  5. 创建一个子图网格,每个子图显示一个CT切片。
  6. 对每个子图使用 imshow 函数显示CT切片,设置灰度颜色映射,并关闭坐标轴。
  7. 调整子图之间的空白,确保没有多余的空间。
  8. 调用 plt.show() 显示最终的拼贴图。

在这里插入图片描述

2.3. 构建3D CNN模型

2.3.1.定义3D卷积神经网络

为了使模型更易于理解,我们将其结构化为模块(或称为块)。本例中使用的3D CNN架构是基于这篇论文的。

接下来,我们可以简要概述一个典型的3D CNN架构可能包含的主要组件:

  1. 输入层:接受3D图像或体积数据作为输入,其形状为(样本数,高度,宽度,深度,通道数)。

  2. 卷积层:这些层使用3D卷积核对输入体积进行卷积运算,以提取空间特征。通常,这些层后面会跟着激活函数(如ReLU)来增加模型的非线性。

  3. 池化层:池化层用于减少空间维度(高度、宽度和深度),从而减少计算量和参数数量,同时保持重要的特征。常见的池化操作包括最大池化和平均池化。

  4. 批量归一化层:这些层用于规范化激活值,以加速训练并改善模型的泛化能力。

  5. 全连接层(或称为密集层):在网络的最后部分,通常使用全连接层来将卷积层提取的特征映射到输出空间。这些层中的神经元与前一层的所有神经元连接。

  6. 输出层:根据任务的不同,输出层可以是单个神经元(用于二分类任务)、多个神经元(用于多分类任务)或具有特定形状的张量(用于回归任务或图像分割任务)。

  7. 跳跃连接(可选):在某些架构中,如U-Net或ResNet,跳跃连接被用于跳过某些层,以便在网络的不同层次之间传递信息,这有助于保持空间信息和梯度流。

  8. 损失函数:用于衡量模型预测与真实标签之间的差距,并在训练过程中指导模型进行优化。

  9. 优化器:用于根据损失函数的梯度更新模型的权重和偏置。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

def get_model(width=128, height=128, depth=64):
    ""“构建一个3D卷积神经网络模型”""
    
    # 定义模型输入,假设输入数据包含一个通道(例如,CT扫描)
    inputs = keras.Input((width, height, depth, 1))
    
    # 第一个卷积块:卷积层 + 激活函数 + 池化层 + 批量归一化
    x = layers.Conv3D(filters=64, kernel_size=3, activation="relu")(inputs)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)
    
    # 第二个卷积块,结构与第一个相同
    x = layers.Conv3D(filters=64, kernel_size=3, activation="relu")(x)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)
    
    # 第三个卷积块,增加过滤器数量
    x = layers.Conv3D(filters=128, kernel_size=3, activation="relu")(x)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)
    
    # 第四个卷积块,进一步增加过滤器数量
    x = layers.Conv3D(filters=256, kernel_size=3, activation="relu")(x)
    x = layers.MaxPool3D(pool_size=2)(x)
    x = layers.BatchNormalization()(x)
    
    # 全局平均池化层
    x = layers.GlobalAveragePooling3D()(x)
    # 全连接层
    x = layers.Dense(units=512, activation="relu")(x)
    # dropout层,用于正则化,防止过拟合
    x = layers.Dropout(0.3)(x)
    
    # 定义模型输出,使用sigmoid激活函数进行二分类
    outputs = layers.Dense(units=1, activation="sigmoid")(x)
    
    # 定义模型
    model = keras.Model(inputs, outputs, name="3dcnn")
    return model

# 构建模型
model = get_model(width=128, height=128, depth=64)
# 打印模型的概览信息
model.summary()

上述代码主要实现以下功能:

  1. 定义了一个函数 get_model,用于构建一个3D卷积神经网络模型,该模型适用于处理3D图像数据,如CT扫描。

  2. 使用 keras.Input 定义模型的输入层,输入数据的维度为 (width, height, depth, 1),其中最后一个维度是通道数。

  3. 构建多个卷积块,每个卷积块包含卷积层、ReLU激活函数、最大池化层和批量归一化层。

  4. 使用 GlobalAveragePooling3D 进行全局平均池化,以减少模型参数并提取特征。

  5. 添加一个全连接层 Dense 和一个Dropout层,Dropout层有助于减少过拟合。

  6. 最后一个全连接层定义了模型的输出,使用sigmoid激活函数进行二分类。

  7. 使用 keras.Model 定义和编译模型。

  8. 调用 get_model 函数构建模型实例,并使用 model.summary() 打印模型的层级结构和参数信息。

2.3.2. 训练模型
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.optimizers.schedules import ExponentialDecay

# 设置初始学习率
initial_learning_rate = 0.0001

# 定义学习率衰减策略,使用指数衰减方式
# initial_learning_rate: 初始学习率
# decay_steps: 衰减步数,每过这么多步数学习率会进行一次衰减
# decay_rate: 衰减率,每次衰减时学习率会乘以这个系数
# staircase: 是否将decay_steps看作阶梯状,True表示在每个decay_steps的倍数步数衰减
lr_schedule = ExponentialDecay(
    initial_learning_rate,
    decay_steps=100000,
    decay_rate=0.96,
    staircase=True
)

# 编译模型
# 使用二元交叉熵作为损失函数
# 使用带有学习率衰减策略的Adam优化器
# 监控准确率作为评价指标
# run_eagerly=True 表示模型将在eager模式下运行,便于调试但可能降低运行效率
model.compile(
    loss="binary_crossentropy",
    optimizer=keras.optimizers.Adam(learning_rate=lr_schedule),
    metrics=["accuracy"],  # 监控准确率
    run_eagerly=True,  # 启用eager模式
)

# 定义回调函数
# ModelCheckpoint用于在每个epoch结束时保存模型,如果模型表现更好则更新保存
checkpoint_cb = keras.callbacks.ModelCheckpoint(
    "3d_image_classification.keras", save_best_only=True
)

# EarlyStopping用于提前停止训练,如果验证准确率在连续15个epoch内没有提升则停止
early_stopping_cb = keras.callbacks.EarlyStopping(
    monitor="val_accuracy",  # 监控验证集的准确率
    patience=15  # 没有提升的epoch数
)

# 训练模型
# epochs表示训练的轮数
# shuffle=True表示在每个epoch开始时打乱训练数据
# verbose=2表示输出每个epoch的训练信息
# callbacks指定了训练过程中使用的回调函数列表
epochs = 100
model.fit(
    train_dataset,  # 训练数据集
    validation_data=validation_dataset,  # 验证数据集
    epochs=epochs,  # 训练轮数
    shuffle=True,  # 打乱数据
    verbose=2,  # 详细输出训练过程
    callbacks=[checkpoint_cb, early_stopping_cb],  # 指定回调函数
)

代码的主要功能是:

  1. 设置初始学习率,并定义了一个指数衰减的学习率调度策略。

  2. 编译模型,指定了损失函数、优化器、评价指标,并启用了eager模式以便于调试。

  3. 定义了两个回调函数:ModelCheckpoint 用于保存模型,EarlyStopping 用于在验证集准确率长时间不提升时提前终止训练。

  4. 使用 fit 方法训练模型,指定了训练数据集、验证数据集、训练轮数、是否打乱数据、输出级别以及回调函数。

值得注意的是,样本数量非常有限(仅200个),并且我们没有指定随机种子。因此,你可以预期结果会有显著的差异。完整的数据集包含超过1000个CT扫描图像,可以在此处找到。当使用完整数据集进行训练时,模型的分类准确率达到了83%。无论是在使用这200个样本还是完整数据集的情况下,分类性能的波动范围都在6-7%之间。这表明,尽管使用更大的数据集可以提高模型的稳定性和准确性,但由于医学图像的复杂性和多样性,仍会存在一定的变异性。因此,在设计和评估医学图像分类模型时,需要充分考虑这些因素。

2.3.3. 模型性能的可视化

在此,我们绘制了训练集和验证集的模型准确率和损失图。由于验证集是类别平衡的,因此准确率提供了一个无偏的模型性能表示。

具体来说,当我们评估一个机器学习模型的性能时,一个常见的做法是在训练集上训练模型,并在一个独立的验证集上评估其性能。验证集的作用是模拟模型在未见过的数据上的表现,从而更准确地评估模型的泛化能力。

在这个场景中,验证集是类别平衡的,这意味着各类别的样本数量是相等的。这是一个理想的条件,因为它避免了由于类别不平衡而导致的性能偏差。在类别不平衡的情况下,模型可能会偏向于数量较多的类别,导致对数量较少的类别的预测性能较差。

因此,当我们说验证集的准确率提供了一个无偏的模型性能表示时,我们指的是这个准确率是基于一个类别平衡的验证集计算得出的,从而更准确地反映了模型在所有类别上的整体性能。通过绘制训练集和验证集的准确率和损失图,我们可以更直观地了解模型在训练过程中的性能变化,并据此调整模型参数或结构以优化其性能。
以下是对您提供的代码片段进行的改写,并添加了中文注释:

import matplotlib.pyplot as plt

# 创建一个图形和两个子图,子图排列为1行2列
fig, ax = plt.subplots(1, 2, figsize=(20, 3))
# 将子图数组展平成一维数组方便迭代
ax = ax.ravel()

# 遍历性能指标列表,这里包括"acc"(准确率)和"loss"(损失)
for i, metric in enumerate(["acc", "loss"]):
    # 在对应子图上绘制训练和验证的性能指标
    ax[i].plot(model.history.history[metric])
    ax[i].plot(model.history.history["val_" + metric])
    # 设置子图标题,显示性能指标名称
    ax[i].set_title("Model {}".format(metric))
    # 设置x轴标签为"epochs"(轮数)
    ax[i].set_xlabel("Epochs")
    # 设置y轴标签为性能指标名称
    ax[i].set_ylabel(metric)
    # 添加图例,区分训练和验证结果
    ax[i].legend(["train", "val"])

# 显示整个图形
plt.show()

代码的主要功能是:

  1. 使用 plt.subplots 创建一个包含两个子图的图形,子图排列为1行2列,并且设置整个图形的大小为宽20英寸、高3英寸。

  2. 将子图数组 ax 展平成一维数组,以便于按顺序访问每个子图。

  3. 遍历性能指标列表(准确率和损失),对于每个指标:

    • 在对应子图上绘制模型训练过程中记录的训练准确率/损失和验证准确率/损失。
    • 设置子图的标题,表明当前绘制的性能指标。
    • 设置x轴标签为“Epochs”,表示训练的轮数。
    • 设置y轴标签为性能指标的名称。
    • 添加图例,以区分训练和验证的性能指标曲线。
  4. 使用 plt.show() 显示整个图形,包括两个子图,每个子图分别展示了训练和验证过程中的准确率和损失变化。

在这里插入图片描述

2.4 预测CT

以下是对您提供的代码片段进行的改写,并添加了中文注释:

import numpy as np
import tensorflow as tf

# 加载最佳权重
# 假设之前训练过程中已经保存了最佳模型权重
model.load_weights("3d_image_classification.keras")

# 对验证集的第一个样本进行预测
# np.expand_dims增加一个维度,以符合模型输入的批次维度要求
prediction = model.predict(np.expand_dims(x_val[0], axis=0))[0]
# 计算预测为正常和异常的得分
scores = [1 - prediction[0], prediction[0]]

# 定义类别名称
class_names = ["normal", "abnormal"]
# 遍历得分和类别名称
for score, name in zip(scores, class_names):
    # 打印模型对CT扫描的预测结果和置信度
    print(
        "This model is %.2f percent confident that CT scan is %s"
        % ((100 * score), name)
    )

这段代码的主要功能是:

  1. 使用 model.load_weights 加载之前训练并保存的最佳模型权重。

  2. 从验证集 x_val 中取出第一个样本,并使用 np.expand_dims 增加一个批次维度,以符合模型输入的要求。

  3. 使用加载了权重的模型对这个样本进行预测,model.predict 返回的是一个包含预测结果的NumPy数组。

  4. 根据预测结果计算出模型预测为“正常”和“异常”的得分,其中 prediction[0] 是模型输出的原始预测概率,1 - prediction[0] 是预测为非事件(正常)的概率。

  5. 定义两个类别的名称:“normal”(正常)和“abnormal”(异常)。

  6. 遍历得分和类别名称,使用 zip 函数将它们组合在一起,并打印出模型对CT扫描的预测结果和置信度百分比。

预测的结论如下:

This model is 32.99 percent confident that CT scan is normal
This model is 67.01 percent confident that CT scan is abnormal

3. 总结和展望

3.1 项目总结

本文详细介绍了3D卷积神经网络(3D CNN)在CT图像肺炎分类预测任务中的应用。从医学影像分析的挑战出发,探讨了3D CNN如何有效地处理三维数据,并提取用于疾病诊断的特征。通过构建和训练一个3D CNN模型,我们展示了该模型在识别和分类肺炎CT图像中的潜力。研究过程中,我们特别关注了数据预处理、增强、模型设计、训练策略和性能评估等关键步骤。

3.2 技术成就与挑战

本文的实验结果表明,3D CNN在CT图像分类任务中表现出了较高的准确率,证明了其在医学影像诊断领域的应用价值。然而,我们也遇到了一些挑战,包括有限的数据集规模、计算资源的限制以及模型泛化能力的提高。未来的工作将需要解决这些问题,并通过优化模型结构、增强数据集的多样性和探索更高效的训练方法来进一步提升模型性能。

3.3 未来研究方向

对于未来的研究,我们提出了几个可能的方向。首先,扩大和多样化训练数据集,以提高模型的泛化能力和鲁棒性。其次,研究和开发更高效的模型结构和训练算法,以减少计算资源的需求。再次,加强与医学专家的合作,提高模型的临床应用价值。最后,提高模型的可解释性,帮助医生更好地理解和信任AI辅助诊断的结果。随着技术的不断进步,我们期待3D CNN在医学影像分析领域发挥更大的作用,为提高疾病诊断的准确性和效率做出贡献。

参考文献

[1] Keras官方示例. “3D图像分类”. Keras官方文档. : [2024-1-11].https://keras.io/examples/vision/3D_image_classification/