使用神经网络进行医学影像识别分析(代码片段)

格格巫MMQ!! 格格巫MMQ!!     2022-12-01     779

关键词:

近年高速发展的人工智能技术应用到了各个垂直领域,比如把深度学习应用于各种医学诊断,效果显著甚至在某些方面甚至超过了人类专家。典型的 CV 最新技术已经应用于阿尔茨海默病的分类、肺癌检测、视网膜疾病检测等医学成像任务中。
图像分割
图像分割是将图像按照内容物切分为不同组的过程,它定位出了图像中的对象和边界。语义分割是像素级别的识别,我们在很多领域的典型应用,背后的技术支撑都是图像分割算法,比如:医学影像、无人驾驶可行驶区域检测、背景虚化等。


语义分割典型网络 U-Net
U-Net 是一种卷积网络架构,用于快速、精确地分割生物医学图像。

关于语义分割的各类算法原理及优缺点对比(包括U-Net),ShowMeAI 在过往文章 📘 深度学习与CV教程(14) | 图像分割 (FCN,SegNet,U-Net,PSPNet,DeepLab,RefineNet) 中有详细详解。

U-Net 的结构如下图所示:

在 U-Net 中,与其他所有卷积神经网络一样,它由卷积和最大池化等层次组成。

U-Net 简单地将编码器的特征图拼接至每个阶段解码器的上采样特征图,从而形成一个梯形结构。该网络非常类似于 Ladder Network 类型的架构。
通过跳跃 拼接 连接的架构,在每个阶段都允许解码器学习在编码器池化中丢失的相关特征。
上采样采用转置卷积。

使用 U-Net 进行肺部影像分割

我们这里使用到的数据集是 🏆 蒙哥马利县 X 射线医学数据集。 该数据集由肺部的各种 X 射线图像以及每个 X 射线的左肺和右肺的分段图像的图像组成。大家也可以直接通过ShowMeAI的百度网盘链接下载此数据集。

工具库导入&环境设置
首先导入我们本次使用到的工具库。

导入工具库

import os
import numpy as np
import cv2
from glob import glob
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.metrics import Recall, Precision
② 数据读取
接下来我们完成数据读取部分,这里读取的内容包括图像和蒙版(mask,即和图片同样大小的标签)。我们会调整维度大小,以便可以作为 U-Net 的输入。

读取X射线图像

def imageread(path,width=512,height=512):
x = cv2.imread(path, cv2.IMREAD_COLOR)
x = cv2.resize(x, (width, height))
x = x/255.0
x = x.astype(np.float32)
return x

读取标签蒙版

def maskread(path_l, path_r,width=512,height=512):
x_l = cv2.imread(path_l, cv2.IMREAD_GRAYSCALE)
x_r = cv2.imread(path_r, cv2.IMREAD_GRAYSCALE)
x = x_l + x_r
x = cv2.resize(x, (width, height))
x = x/np.max(x)
x = x > 0.5
x = x.astype(np.float32)
x = np.expand_dims(x, axis=-1)
return x
③ 数据切分
我们要对模型的效果进行有效评估,所以接下来我们进行数据划分,我们把全部数据分为训练集、验证集和测试集。具体代码如下:

“”“加载与切分数据”“”
def load_data(path, split=0.1):
images = sorted(glob(os.path.join(path, “CXR_png”, “.png")))
masks_l = sorted(glob(os.path.join(path, “ManualMask”, “leftMask”, "
.png”)))
masks_r = sorted(glob(os.path.join(path, “ManualMask”, “rightMask”, “*.png”)))
split_size = int(len(images) * split) # 9:1的比例切分
train_x, val_x = train_test_split(images, test_size=split_size, random_state=42)
train_y_l, val_y_l = train_test_split(masks_l, test_size=split_size, random_state=42)
train_y_r, val_y_r = train_test_split(masks_r, test_size=split_size, random_state=42)
train_x, test_x = train_test_split(train_x, test_size=split_size, random_state=42)
train_y_l, test_y_l = train_test_split(train_y_l, test_size=split_size, random_state=42)
train_y_r, test_y_r = train_test_split(train_y_r, test_size=split_size, random_state=42)

return (train_x, train_y_l, train_y_r), (val_x, val_y_l, val_y_r), (test_x, test_y_l, test_y_r)

④ TensorFlow IO准备
我们会使用到 TensorFlow 进行训练和预估,我们用 TensorFlow 读取 numpy array 格式的数据,转为 TensorFlow 的 tensor 形式,并构建方便以 batch 形态读取和训练的 dataset 格式。

tensor格式转换

def tf_parse(x, y_l, y_r):
def _parse(x, y_l, y_r):
x = x.decode()
y_l = y_l.decode()
y_r = y_r.decode()
x = imageread(x)
y = maskread(y_l, y_r)
return x, y
x, y = tf.numpy_function(_parse, [x, y_l, y_r], [tf.float32, tf.float32])
x.set_shape([512, 512, 3])
y.set_shape([512, 512, 1])
return x, y

构建tensorflow dataset

def tf_dataset(X, Y_l, Y_r, batch=8):
dataset = tf.data.Dataset.from_tensor_slices((X, Y_l, Y_r))
dataset = dataset.shuffle(buffer_size=200)
dataset = dataset.map(tf_parse)
dataset = dataset.batch(batch)
dataset = dataset.prefetch(4)
return dataset
⑤ U-Net 网络构建
下面我们构建 U-Net 网络。

from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Conv2DTranspose, Concatenate, Input
from tensorflow.keras.models import Model

一个卷积块结构

def conv_block(input, num_filters):
x = Conv2D(num_filters, 3, padding=“same”)(input)
x = BatchNormalization()(x)
x = Activation(“relu”)(x)

x = Conv2D(num_filters, 3, padding="same")(x)
x = BatchNormalization()(x)
x = Activation("relu")(x)

return x

编码器模块

def encoder_block(input, num_filters):
x = conv_block(input, num_filters)
p = MaxPool2D((2, 2))(x)
return x, p

解码器模块

def decoder_block(input, skip_features, num_filters):
x = Conv2DTranspose(num_filters, (2, 2), strides=2, padding=“same”)(input)
x = Concatenate()([x, skip_features])
x = conv_block(x, num_filters)
return x

完整的U-Net

def build_unet(input_shape):
inputs = Input(input_shape)

# 编码器部分
s1, p1 = encoder_block(inputs, 64)
s2, p2 = encoder_block(p1, 128)
s3, p3 = encoder_block(p2, 256)
s4, p4 = encoder_block(p3, 512)

b1 = conv_block(p4, 1024)

# 解码器部分
d1 = decoder_block(b1, s4, 512)
d2 = decoder_block(d1, s3, 256)
d3 = decoder_block(d2, s2, 128)
d4 = decoder_block(d3, s1, 64)

# 输出
outputs = Conv2D(1, 1, padding="same", activation="sigmoid")(d4)

model = Model(inputs, outputs, name="U-Net")
return model

⑥ 评估准则与损失函数
我们针对语义分割场景,编写评估准则 IoU 的计算方式,并构建 Dice Loss 损失函数以便在医疗场景语义分割下更针对性地训练学习。

关于IoU、mIoU等评估准则可以查看ShowMeAI的文章 📘 深度学习与CV教程(14) | 图像分割 (FCN,SegNet,U-Net,PSPNet,DeepLab,RefineNet) 做更多了解。

关于Dice Loss损失函数的解释如下:

📌 Dice 系数
根据 Lee Raymond Dice 命名,是一种集合相似度度量函数,通常用于计算两个样本的相似度(值范围为 [0,1]):

s=2|X∩Y||X|+|Y|
|X∩Y|表示 X 和 Y 之间的交集;|X| 和 |Y| 分别表示 X 和 Y 的元素个数。其中,分子中的系数 2,是因为分母存在重复计算 X 和 Y 之间的共同元素的原因。

针对,语义分割问题而言,X 为分割图像标准答案 GT,Y 为分割图像预测标签 Pred。

📌 Dice 系数差异函数(Dice loss)
s=1−2|X∩Y||X|+|Y|
评估准则与损失函数的代码实现如下:

IoU计算

def iou(y_true, y_pred):
def f(y_true, y_pred):
intersection = (y_true * y_pred).sum()
union = y_true.sum() + y_pred.sum() - intersection
x = (intersection + 1e-15) / (union + 1e-15)
x = x.astype(np.float32)
return x
return tf.numpy_function(f, [y_true, y_pred], tf.float32)

Dice Loss定义

smooth = 1e-15
def dice_coef(y_true, y_pred):
y_true = tf.keras.layers.Flatten()(y_true)
y_pred = tf.keras.layers.Flatten()(y_pred)
intersection = tf.reduce_sum(y_true * y_pred)
return (2. * intersection + smooth) / (tf.reduce_sum(y_true) + tf.reduce_sum(y_pred) + smooth)

def dice_loss(y_true, y_pred):
return 1.0 - dice_coef(y_true, y_pred)
⑦ 超参数设置与模型编译
接下来在开始模型训练之前,我们先敲定一些超参数,如下:

批次大型 batch size = 2
学习率 learning rate= 1e-5
迭代轮次 epoch = 30
我们使用 Adam 优化器进行训练,使用的评估指标包括 Dice 系数、IoU、召回率和精度。

超参数

batch_size = 2
lr = 1e-5
epochs = 30
model_path = “models/model.h5”

读取数据

dataset_path = ‘./NLM-MontgomeryCXRSet/MontgomerySet’
(train_x, train_y_l, train_y_r), (val_x, val_y_l, val_y_r), (test_x, test_y_l, test_y_r) = load_data(dataset_path)

训练集与验证集

train_dataset = tf_dataset(train_x, train_y_l, train_y_r, batch=batch_size)
val_dataset = tf_dataset(val_x, val_y_l, val_y_r, batch=batch_size)

构建模型

model = build_unet((512, 512, 3))

评估准则

metrics = [dice_coef, iou, Recall(), Precision()]

编译模型

model.compile(loss=dice_loss, optimizer=Adam(lr), metrics=metrics)
可以使用model.summary查看模型结构信息与参数量:

model . summary()
结果如下图所示(部分内容截图,全部模型信息较长):

⑧ 回调函数&模型训练
我们在回调函数中设置模型存储相关设置,学习率调整策略等,之后在数据集上进行训练。

回调函数

callbacks = [
ModelCheckpoint(model_path, verbose=1, save_best_only=True),
ReduceLROnPlateau(monitor=‘val_loss’, factor=0.1, patience=5, min_lr=1e-8, verbose=1)
]

模型训练

history = model.fit(
train_dataset,
epochs=epochs,
validation_data=val_dataset,
callbacks=callbacks
)
训练部分中间信息如下图所示。

在训练模型超过 30 个 epoch 后,保存的模型(验证损失为 0.10216)相关的评估指标结果如下:

dice coef:0.9148
iou:0.8441
recall:0.9865
precision:0.9781
val_loss:0.1022
val_dice_coef: 0.9002
val_iou:0.8198
val_recall:0.9629
val_precision:0.9577
⑨ 模型加载与新数据预估
我们可以把刚才保存好的模型重新加载入内存,并对没有见过的测试数据集进行预估,代码如下:

重新载入模型

from tensorflow.keras.utils import CustomObjectScope
with CustomObjectScope(‘iou’: iou, ‘dice_coef’: dice_coef, ‘dice_loss’: dice_loss):
model = tf.keras.models.load_model(“/content/model.h5”)

测试集预估

from tqdm import tqdm
import matplotlib.pyplot as plt
ct=0

遍历测试集

for x, y_l, y_r in tqdm(zip(test_x, test_y_l, test_y_r), total=len(test_x)):
“”" Extracing the image name. “”"
image_name = x.split(“/”)[-1]

# 读取测试图片集
ori_x = cv2.imread(x, cv2.IMREAD_COLOR)
ori_x = cv2.resize(ori_x, (512, 512))
x = ori_x/255.0
x = x.astype(np.float32)
x = np.expand_dims(x, axis=0)

# 读取标签信息
ori_y_l = cv2.imread(y_l, cv2.IMREAD_GRAYSCALE)
ori_y_r = cv2.imread(y_r, cv2.IMREAD_GRAYSCALE)
ori_y = ori_y_l + ori_y_r
ori_y = cv2.resize(ori_y, (512, 512))
ori_y = np.expand_dims(ori_y, axis=-1)  # (512, 512, 1)
ori_y = np.concatenate([ori_y, ori_y, ori_y], axis=-1)  # (512, 512, 3)

# 预估
y_pred = model.predict(x)[0] > 0.5
y_pred = y_pred.astype(np.int32)
#plt.imshow(y_pred)

# 存储预估结果mask
save_image_path = "./"+str(ct)+".png"
ct+=1
y_pred = np.concatenate([y_pred, y_pred, y_pred], axis=-1)
sep_line = np.ones((512, 10, 3)) * 255
cat_image = np.concatenate([ori_x, sep_line, ori_y, sep_line, y_pred*255], axis=1)
cv2.imwrite(save_image_path, cat_image)

部分结果可视化:

下面为2个测试样本的原始图像、原始掩码(标准答案)和预测掩码的组合图像:

测试用例的输入图像(左侧)、原始掩码标签(中间)、预测掩码(右侧)

使用keras训练lenet网络来进行手写数字识别(代码片段)

...构是深度学习中的一项开创性工作,演示了如何训练神经网络以端到端的方式识别图像中的对象(即不必进行特征提取,网络能够从图像本身学习模式)。首先由LeCun等人介绍。在他们1998年的论文中,基于梯... 查看详情

使用循环神经网络做手写数字识别(代码片段)

文章目录使用循环神经网络做手写数字识别思路分析PyTorch实现版权声明:本文为博主原创文章,转载请注明原文出处!写作时间:2019-03-0221:36:12使用循环神经网络做手写数字识别思路分析做图像识别的使用卷积神... 查看详情

[bpnet识别mnist04]神经网络的变量和公式分析(代码片段)

写在前面今天正式进入前馈神经网络的公式推导环节,综合数据处理难度和公式推导难度以及神经网络的易理解性,我将之前放出的神经网络做了一些调整,之后给出。那么这篇文章主要进行神经网络的变量讲解࿰... 查看详情

卷积神经网络(cnn)使用自己的数据集进行天气识别(代码片段)

记录|深度学习100例-卷积神经网络(CNN)天气识别|第5天这篇博客将从构建自己的天气数据集开始,到定义模型,编译模型,训练模型及验证模型。并进行一些升级,以使得模型更好。如ImageDateGenerator进行... 查看详情

深度学习100例-卷积神经网络(cnn)3d医疗影像识别|第23天(代码片段)

大家好,我是「K同学啊」!好像有一段时间没有更新了,这段事情真的太多了,自己也有一点点小偷懒,但是我还在坚持哈,等开学了更新频率可能就会稳定下来。唠嗑结束,进入正题,前段时间... 查看详情

使用卷积神经网络cnn训练识别mnist(代码片段)

算的的上是自己搭建的第一个卷积神经网络。网络结构比较简单。输入为单通道的mnist数据集。它是一张28*28,包含784个特征值的图片我们第一层输入,使用5*5的卷积核进行卷积,输出32张特征图,然后使用2*2的池化核进行池化输... 查看详情

人工智能--使用神经网络分析电影评论(代码片段)

...面评价还是负面评价。参考以下代码,建立基于LSTM的神经网络,对影评数据进行分类。并调整网络参数,提高分类的效果,和原始的神经网络方法的结果进行比较。提示:可参考BidirectionalLSTMonIMDB BidirectionalLS... 查看详情

python数据分析与挖掘学习笔记-交通路标自动识别实战与神经网络算法(代码片段)

这一节主要涉及神经网络算法,由此展开交通路标自动识别的应用。交通路标的自动识别其实就是一个分类问题。对于分类问题,我们有很多的方法来实现,比如KNN,贝叶斯等。关键点在于图片转文本。本节采用... 查看详情

交通标志识别基于matlabhog特征结合bp神经网络交通标志识别含matlab源码2331期(代码片段)

⛄一、BP神经网络交通标志识别简介道路交通标志用以禁止、警告、指示和限制道路使用者有秩序地使用道路,保障出行安全.若能自动识别道路交通标志,则将极大减少道路交通事故的发生.但是由于道路交通错综复杂,且智能识别... 查看详情

keras深度学习实战——使用长短时记忆网络构建情感分析模型(代码片段)

...行情感分类相关链接0.前言我们已经学习了如何使用循环神经网络(Recurrentneuralnetworks,RNN)构建情感分析模型,为了将循环神经网络与长短时记忆网络(LongShortTermMemory,LSTM)的性能进行对 查看详情

keras深度学习实战——使用循环神经网络构建情感分析模型(代码片段)

Keras深度学习实战——使用循环神经网络构建情感分析模型0.前言1.使用循环神经网络构建情感分析模型1.1数据集分析1.2构建RNN模型进行情感分析相关链接0.前言在《循环神经详解与实现》一节中,我们已经了解循环神经网络(Recurr... 查看详情

[bpnet识别mnist05]神经网络梯度下降公式分析(代码片段)

写在前面我终于把神经网络的梯度下降公式推导完毕了!先看看为了伟大工程牺牲的草稿纸吧~接下来我就分享出我推导的公式以及我总结出来的规律。正文主要内容在我的古月居博客:[BPnet识别MNIST05]神经网络梯度下降... 查看详情

arcgis微课1000例0057:将多波段栅格(影像.tif)背景设置为无数据nodata的方法(代码片段)

...tif)背景设置为无数据nodata的方法。文章目录一、背景值识别二、背景值去除【推荐阅读】:【ArcGIS微课1000例】0056:将单波段栅格背景设置为无数据NoData的方法一、背景值识别可以用【识别】工具来获取影像数据的背景值。在... 查看详情

矩池云|神经网络图像分割:气胸x光片识别案例(代码片段)

在上一次肺炎X光片的预测中,我们通过神经网络来识别患者胸部的X光片,用于检测患者是否患有肺炎。这是一个典型的神经网络图像分类在医学领域中的运用。另外,神经网络的图像分割在医学领域中也有着很重要的用作。接下... 查看详情

分别使用bp神经网络和svm支持向量机进行乳腺癌识别matlab仿真,输出识别率,召回率以及f1(代码片段)

目录一、理论基础二、核心程序三、测试结果一、理论基础    乳腺癌是全球第二常见的女性癌症。2012年,它占所有新癌症病例的12%,占所有女性癌症病例的25%。当乳腺细胞生长失控时,乳腺癌就开始了。这些细胞... 查看详情

tensorflow-神经网络识别验证码(数字+小写字母)(代码片段)

...样式如下:识别分析识别流程分析准备数据数据处理神经网络输入经过神经网络隐层全连接层输出soft 查看详情

(网络安全)主动信息收集操作系统识别(代码片段)

...别操作系统的目的通过识别目标主机的操作系统如果对方使用未打补丁或存在已知漏洞的版本系统便可以通过漏洞实施入侵0X02:根据TTL起始值进行判断WindowsTTL值:128(65----128)Linux/UnixTTL值:64(1-64)... 查看详情

使用pytorch实现手写数字识别(代码片段)

...DataLoader构建模型,这里可以使用torch构造一个深层的神经网络模型的训练模型的保存,保存模型,后续持续使用模型的评估,使用测试集,观察模型的好坏准备训练集和测试集准备数据集的方法前面已经讲过&#... 查看详情