医学图像 | 使用深度学习实现乳腺癌分类(附python演练)
点击上方“小白学视觉 ”,选择加"星标 "或“置顶 ”
重磅干货,第一时间送达
代码解读
本文转自:磐创AI
乳腺癌以其在全球范围内作为主要女性癌症之一的身份而闻名。根据数据统计,在所有新发癌症病例中,其占比为12%,而在特定人群中——如女性群体中——其占比则达到了25%。
一旦乳腺细胞出现失控状态,则会发展成为乳腺癌。这些细胞一般会聚集形成一个肿瘤,在X射线影像中可能直接观察到或触摸到明显的肿块。若癌细胞能够侵袭相邻组织或向身体其他部位扩散,则该肿瘤将被归类为恶性病变。
以下是报告:
-
大约八分之一的美国女性(约12%)将在其一生中患上浸润性乳腺癌。
-
2019年,美国预计将有268,600例新的侵袭性乳腺癌病例,以及62,930例新的非侵袭性乳腺癌。
约85%的乳腺癌病例发生在未患有乳腺癌的家庭成员中。这些病例的发生是由基因突变导致的,并非遗传突变的结果。
如果一名女性的直系亲属(包括母亲、姐妹和女儿)确诊患上乳腺癌,则她的患病风险将显著提升。在所有乳腺癌患者中,仅约15%左右的患者其直系亲属也确诊患上乳腺癌。
挑战
开发一个基于分析技术的乳腺癌诊断系统。
该系统将能够通过活检图像的数据分析实现对患者的早期筛查。
为了确保准确性起见,
我们必须要设计出一种高度准确的算法用于乳腺癌早期筛查。
基于医学影像数据建立一个高效可靠的乳腺癌检测模型,
这样能够帮助医生做出更加科学合理的诊断决策。
数据
dataset train
benign
b1.jpg
b2.jpg
//
malignant
m1.jpg
m2.jpg
// validation
benign
b1.jpg
b2.jpg
//
malignant
m1.jpg
m2.jpg
//...
代码解读
训练文件夹中的每一个类别都包含1000幅图像,在此分类中验证文件夹仅包含250幅图像


以上两张图片是良性样本


以上两张图片是恶性样本
环境和工具
-
scikit-learn
-
keras
-
numpy
-
pandas
-
matplotlib
图像分类
完整的图像分类流程可以形式化如下:
我们的输入是一个由N个图像组成的训练数据集集合,在每个图像中都存在对应的标签信息。
然后,我们使用这个训练集来训练分类器,来学习每个类。
为了评估分类器的质量,我们让其对一组未曾见过的新图像进行标签预测;接着我们将这些图像的真实标签与其分类器预测的相应标签进行对比。
为了评估分类器的质量, 我们让其对一组未曾见过的新图像进行标签预测; 接着我们将这些图像的真实标签与其分类器预测的相应标签进行对比.
代码实现
让我们开启代码开发。GitHub.apsara云数据库实践平台提供了丰富的资源链接(https://github.com/abhinavsagar/Breast-cancer-classification)
让我们从加载所有库和依赖项开始。
import json
import math
import os
import cv2
from PIL import Image
import numpy as np
from keras import layers
from keras.applications import DenseNet201
from keras.callbacks import Callback, ModelCheckpoint, ReduceLROnPlateau, TensorBoard
from keras.preprocessing.image import ImageDataGenerator
from keras.utils.np_utils import to_categorical
from keras.models import Sequential
from keras.optimizers import Adam
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import cohen_kappa_score, accuracy_score
import scipy
from tqdm import tqdm
import tensorflow as tf
from keras import backend as K
import gc
from functools import partial
from sklearn import metrics
from collections import Counter
import json
import itertools
代码解读
接下来,我将图像加载到相应的文件夹中。
def Dataset_loader(DIR, RESIZE, sigmaX=10):
IMG = []
read = lambda imname: np.asarray(Image.open(imname).convert("RGB"))
for IMAGE_NAME in tqdm(os.listdir(DIR)):
PATH = os.path.join(DIR,IMAGE_NAME)
_, ftype = os.path.splitext(PATH)
if ftype == ".png":
img = read(PATH)
img = cv2.resize(img, (RESIZE,RESIZE))
IMG.append(np.array(img))
return IMG
benign_train = np.array(Dataset_loader('data/train/benign',224))
malign_train = np.array(Dataset_loader('data/train/malignant',224))
benign_test = np.array(Dataset_loader('data/validation/benign',224))
malign_test = np.array(Dataset_loader('data/validation/malignant',224))
代码解读
随后, 我生成了一个全零值的numpy数组, 并将其用于对良性图像进行标注; 同时生成了一个全一值的numpy数组, 并将其用于对恶性图像进行标注. 此外, 在完成数据预处理后, 我还完成了标签格式化处理.
benign_train_label = np.zeros(len(benign_train))
malign_train_label = np.ones(len(malign_train))
benign_test_label = np.zeros(len(benign_test))
malign_test_label = np.ones(len(malign_test))
X_train = np.concatenate((benign_train, malign_train), axis = 0)
Y_train = np.concatenate((benign_train_label, malign_train_label), axis = 0)
X_test = np.concatenate((benign_test, malign_test), axis = 0)
Y_test = np.concatenate((benign_test_label, malign_test_label), axis = 0)
s = np.arange(X_train.shape[0])
np.random.shuffle(s)
X_train = X_train[s]
Y_train = Y_train[s]
s = np.arange(X_test.shape[0])
np.random.shuffle(s)
X_test = X_test[s]
Y_test = Y_test[s]
Y_train = to_categorical(Y_train, num_classes= 2)
Y_test = to_categorical(Y_test, num_classes= 2)
代码解读
随后, 我将数据集划分为两部分: 其中一部分占主要比例作为训练集, 另一部分占次要比例作为测试集. 让我们观察一些良性和恶性图像样本.
x_train, x_val, y_train, y_val = train_test_split(
X_train, Y_train,
test_size=0.2,
random_state=11
)
w=60
h=40
fig=plt.figure(figsize=(15, 15))
columns = 4
rows = 3
for i in range(1, columns*rows +1):
ax = fig.add_subplot(rows, columns, i)
if np.argmax(Y_train[i]) == 0:
ax.title.set_text('Benign')
else:
ax.title.set_text('Malignant')
plt.imshow(x_train[i], interpolation='nearest')
plt.show()
代码解读

本研究中采用的批量大小设定为16。批量大小(batch size)是深度学习训练过程中一个关键参数。相对于较小的批量大小而言,本研究采用较大的批量大小以提升训练效率。然而,在某些情况下过大的批量会导致模型泛化能力下降。研究表明,在某些情况下过大的批量会导致模型泛化能力下降。在一个极端情况下,在这种极端情况下将整个数据集作为单个批次处理将能够确保算法收敛至目标函数全局最小值的位置。然而,在这种极端情况下将整个数据集作为单个批次处理将能够确保算法收敛至目标函数全局最小值的位置这一策略虽然能够保证找到全局最优解,但其收敛速度相对较慢。另一方面,在这种情况下适当减少批量大小已被证明能够加速模型收敛至良好结果区域的学习过程。这可以从以下角度来看:较小规模的批次允许模型在尚未遍历所有训练样本的情况下就开始学习任务特征。然而,在这种情况下适当减少批量大小已被证明能够加速模型收敛至良好结果区域的学习过程这一缺点在于无法保证算法必然收敛至全局最优解的位置。因此,在实际应用中通常建议从小规模批次开始训练,并逐步增加其规模以便于更快地达到较好的收敛效果
我在进行一些数据扩展工作。数据扩展作为提升训练集规模的方法是一种有效手段。通过扩展训练实例的数量与质量使网络能够更好地学习并处理更多的不同类别与复杂情况下的样本。
接着, 我搭建了一个数据生成器, 能够自动生成来自文件夹的数据样本. Keras为此提供了一款高效的Python生成器接口.
BATCH_SIZE = 16
train_generator = ImageDataGenerator(
zoom_range=2, # 设置范围为随机缩放
rotation_range = 90,
horizontal_flip=True, # 随机翻转图片
vertical_flip=True, # 随机翻转图片
)
代码解读
下一步是构建模型。这可以通过以下3个步骤来描述:
在训练前的权重中,我采用了DenseNet201。该模型在Imagenet比赛中已获得过训练,并设置了学习率参数为...。
在此基础上,我采用了全局平均池化机制和50% dropout率来降低模型过拟合程度。
在模型中采用批次归一化技术和Softmax函数作为激活函数,并引入包含两个神经元的全连接层分别用于区分良性与恶性两种类别。
- 我使用Adam作为优化器,使用二元交叉熵作为损失函数。
def build_model(backbone, lr=1e-4):
model = Sequential()
model.add(backbone)
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dropout(0.5))
model.add(layers.BatchNormalization())
model.add(layers.Dense(2, activation='softmax'))
model.compile(
loss='binary_crossentropy',
optimizer=Adam(lr=lr),
metrics=['accuracy']
)
return model
resnet = DenseNet201(
weights='imagenet',
include_top=False,
input_shape=(224,224,3)
)
model = build_model(resnet ,lr = 1e-4)
model.summary()
代码解读
让我们看看每个层中的输出形状和参数。

在模型训练开始前,设置至少一个或多个回调函数是很有益的。特别方便的是ModelCheckpoint及其 ReduceLROnPlateau。
ModelCheckpoint:在频繁的迭代过程中(尤其是涉及长时间计算的需求),该检查点机制负责存储经过优化的模型参数。
ReduceLROnPlateau:一种有效的学习率衰减技术,在监测到目标指标停滞不进步时触发并执行学习率衰减操作。该回调机制会持续监控训练过程中的表现指标,并根据预设的'patience'(耐心)阈值决定是否继续训练或采取其他措施以防止过拟合。如果在指定的'patience'次数内未能提升模型性能,则会自动触发学习率衰减策略以帮助模型收敛于最优解。该回调机制通过动态调整学习速率来优化训练效果,在实现模型稳定训练的同时有效防止了早停现象的发生。

该模型我训练了20个epoch。
learn_control = ReduceLROnPlateau(monitor='val_acc', patience=5,
verbose=1,factor=0.2, min_lr=1e-7)
filepath="weights.best.hdf5"
checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max')
history = model.fit_generator(
train_generator.flow(x_train, y_train, batch_size=BATCH_SIZE),
steps_per_epoch=x_train.shape[0] / BATCH_SIZE,
epochs=20,
validation_data=(x_val, y_val),
callbacks=[learn_control, checkpoint]
)
代码解读
性能指标
通常用于衡量模型性能的主要标准是精度。然而,在您的数据集中仅存在2%(恶性)和98%(良性)两个类别时,在这种情况下错误分类的分数无从衡量。即使达到98%的准确率,在这种情况下你仍然无法识别出恶性病例。换句话说,在预测结果中所有样本都被标记为良性的情况下也是一种糟糕的表现。
history_df = pd.DataFrame(history.history)
history_df[['loss', 'val_loss']].plot()
history_df = pd.DataFrame(history.history)
history_df[['acc', 'val_acc']].plot()
代码解读


精度,召回率和F1度量
为了更深入地了解分类错误的分布情况,在数据分析过程中我们通常会采用以下指标来系统性地评估真正例(TP)、真负例(TN)、假正例(FP)以及假负例(FN),以此为基础展开进一步的分析与探讨。
精度 反映了被分类器判定的正例中真正的正例样本的比重。
召回率指标衡量了全部真实正样本中被分类器识别出来的正样本数量占总真实正样本的比例。
F1度量 是准确率和召回率的调和平均值。

F1度量越高,模型越好。对于所有三个度量,0值表示最差,而1表示最好。
混淆矩阵
混肴矩阵是一个评估分类模型误判性能的关键工具。该表格中每一行对应预测类别中的实例,而每一列对应真实类别中的实例。正确分类的实例位于主对角线位置上,从中我们能获取有价值的信息:了解哪些类别被错误识别为其他类别,以及识别出这些误判的原因。
from sklearn.metrics import classification_report
classification_report( np.argmax(Y_test, axis=1), np.argmax(Y_pred_tta, axis=1))
from sklearn.metrics import confusion_matrix
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
print("Normalized confusion matrix")
else:
print('Confusion matrix, without normalization')
print(cm)
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=55)
plt.yticks(tick_marks, classes)
fmt = '.2f' if normalize else 'd'
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, format(cm[i, j], fmt),
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.tight_layout()
cm = confusion_matrix(np.argmax(Y_test, axis=1), np.argmax(Y_pred, axis=1))
cm_plot_label =['benign', 'malignant']
plot_confusion_matrix(cm, cm_plot_label, title ='Confusion Metrix for Skin Cancer')
代码解读

ROC曲线
45度的斜率对应的是一条随机基准线,在这种情况下曲线下面积或AUC值为0.5。这条基准线与曲线下面积或AUC值的关系表明:当该曲线距离对角线越远,则AUC值越大,模型性能越好。当模型达到完美分类时(即曲线下面积或AUC值达到最大),其曲边三角形将形成一个直角三角形。通过ROC曲线不仅可以直观评估模型性能还可以辅助模型优化过程。例如,在实际应用中若曲线左下端点的位置接近随机基准线,则表明模型在预测Y=0类样本时存在较多误判情况;相反地如果右上端点位于对角线上则说明模型在预测Y=1类样本时出现了较多误判。
from sklearn.metrics import roc_auc_score, auc
from sklearn.metrics import roc_curve
roc_log = roc_auc_score(np.argmax(Y_test, axis=1), np.argmax(Y_pred_tta, axis=1))
false_positive_rate, true_positive_rate, threshold = roc_curve(np.argmax(Y_test, axis=1), np.argmax(Y_pred_tta, axis=1))
area_under_curve = auc(false_positive_rate, true_positive_rate)
plt.plot([0, 1], [0, 1], 'r--')
plt.plot(false_positive_rate, true_positive_rate, label='AUC = {:.3f}'.format(area_under_curve))
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC curve')
plt.legend(loc='best')
plt.show()
#plt.savefig(ROC_PLOT_FILE, bbox_inches='tight')
plt.close()
代码解读

结果

结论
尽管该项目尚未完成, 但注意到深度学习在众多现实世界的问题上取得了显著的成功是值得重视的. 在本博客中展示了如何利用卷积神经网络和迁移学习从一组显微图像中区分良性和恶性乳腺癌细胞.
下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台处键入:扩展模块中文教程**, 即可获取这份全面的OpenCV扩展模块中文版教程。该教程完整涵盖了以下主要内容:包括但不限于扩展模块安装操作指南、基于SFM算法的深度学习应用实践、立体视觉技术实现方法等二十多个章节的内容。
下载2:Python视觉实战项目52讲
访问「小白学视觉」公众号并在其后台提交 Python视觉实战项目 ,通过提交即可获取包含图像分割;口罩检测;车道线检测;车辆计数;添加眼线;车牌识别;字符识别;情绪检测;文本内容提取;面部识别 等31个实战项目的包(共31个项目),助力掌握前沿技术并提升实践能力。
下载3:OpenCV实战项目20讲
在「小白学视觉」公众号的后台提交请求:发布于《OpenCV实战项目20讲》的内容包中包含20个基于OpenCV实现的实战项目课程资源,并支持逐一完成学习目标以达到进阶掌握效果
交流群
诚邀您加入我们的公众号读者群以便与同行交流。目前我们已开设以下微信群(后续将逐步细分):
如(SLAM, 三维视觉, 传感器, 自动驾驶, 计算摄影, 检测, 分割, 识别, 医学影像, GAN, 算法竞赛等)。请您扫描下方微信二维码加入我们的读者群,并在入群时注明您的个人信息:昵称+学校/公司+研究领域(如张三(上海交大)视觉SLAM团队)。我们会根据您的研究方向为您分配合适的微信群。
特别提醒各位成员,请不要在群里发送与学习无关的广告信息。


