python图像分类代码_医学图像 | 使用深度学习实现乳腺癌分类(附python演练)
乳腺癌是全球第二常见的女性癌症,占所有新癌症病例的12%和所有女性癌症病例的25%。它分为良性肿瘤(约80%)和恶性肿瘤(约20%),后者通常由遗传因素或家族病史引起。报告中提到的数据集包含约1万张良性和恶性乳腺癌图像,并通过Keras等工具进行分类。使用迁移学习的方法,基于DenseNet201预训练模型,在Top-2图像分类任务中取得了较高的准确率。通过数据增强和技术手段(如Dropout和Batch Normalization)来防止过拟合,并使用学习率调整策略优化训练过程。最终模型在验证集上表现优异,达到了97%以上的准确率,并通过混淆矩阵展示了良好的分类性能。

乳腺癌在全球范围内是最常见的女性癌症之一。根据一项针对2012年的新研究数据显示,在所有新发癌症病例中约有12%为乳腺癌,在所有女性癌症病例中其占比达到25%。
当乳腺细胞发生异常增殖时,乳腺癌便开始发展。这些细胞通常会聚集成一个肿瘤,在X射线照片中可以直接观察到或通过触觉感知到肿块的存在。如果这些癌细胞能够侵袭相邻组织或蔓延至身体的其他部位,则表明该肿瘤具有恶性特征。
以下是报告:
- 约占美国女性的1/8(约12%)一生中将患上浸润性乳腺癌。
- 预计到2019年,美国将新增268,600例侵袭性乳腺癌病例和62,930例非侵袭性乳腺癌病例。
- 超过85%的乳腺癌病例发生在没有家族历史的女性身上。这些病例的发生与基因突变有关而非遗传突变。
- 如果一名女性的一级亲属(母亲、姐妹、女儿)被确诊患乳腺癌,则她患乳腺癌的风险将显著增加。在所有患乳腺癌的女性中不到15%的人其家族成员曾患此病。
挑战
开发一个算法基于活检图像自动识别乳腺癌患者的存在。该系统必须具有高度的准确性以确保患者的健康和生命安全至上的考虑。
数据
该数据集可通过以下链接获取:(https://web.inf.ufpr.br/vri/databases/breast-cancer-histopathological-database-breakhis/)。这是一个二分类任务的问题实例。为便于理解数据组织结构,请将数据集按照附图所示的方式进行分割并进行详细标注
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上的完整项目位于此处: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)
随后, 我将数据集划分为两个部分, 并赋予各自包含80%与20%来自不同类别样例的图像类型. 接下来, 让我们来看几组来自不同类别的良性与恶性样例图片.
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。在深度学习中,批量大小通常被视为一个关键参数设置之一。相比而言,在我的实验中发现较大的批量能够显著提升模型的训练效率。这是因为较大的批量能够充分利用GPU的并行计算能力。然而,在实践中选择一个合适的批量大小对于模型性能至关重要。具体而言,在极端情况下(即当批次规模达到数据集总规模时),该方法能够保证收敛至目标函数全局最优解这一特性是显而易见的优势所在。不过这种做法往往伴随着收敛速度变慢的问题。相比之下,在另一种极端情况下(即当批次规模极小时),虽然这种方法无法保证全局最优解收敛性这一缺点限制了其应用范围。但值得指出的是,在大多数实际应用中发现较小规模的批次反而更容易实现较快的学习效果(即快速收敛到较好的局部解)。这种现象可以从以下角度直观理解:较小规模的批次允许模型在无需遍历全部训练数据的情况下就开始学习任务核心特征信息。然而需要注意的是,在这种情况下模型可能无法达到全局最优解这一理论理想状态下的最佳表现(即理论上无法保证全局最优)。因此为了平衡效率与准确性之间的关系建议从小规模开始逐步增加批处理规模以此来加快整体优化过程的速度。
我还在进行一些数据扩充工作。从实践效果来看,数据扩充是一种能够有效提升训练集规模的有效方法。通过扩增训练实例的数量,在网络的训练过程中能够更好地观察到多样化的数据样本,并且这些样本依然保持了高度的代表性。
后, 我开发了一个数据生成器, 并被自动提取自文件夹中的数据. Keras为此提供了方便的Python生成器函数.
BATCH_SIZE = 16
train_generator = ImageDataGenerator(
zoom_range=2, # 设置范围为随机缩放
rotation_range = 90,
horizontal_flip=True, # 随机翻转图片
vertical_flip=True, # 随机翻转图片
)
下一步是构建模型。这可以通过以下3个步骤来描述:
基于Imagenet训练的DenseNet201网络作为预训练权重,在此基础上,通过引入global average pooling层和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这两种技术。
在频繁的迭代训练过程中(尤其是每次迭代耗时较长),为了使模型性能得到显著提升,在这一过程中应定期保存当前最优模型以备后续使用

该模型我训练了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达到最大效果时(即达到1),所形成的曲线下面积将构成一个直角三角形。通过分析ROC曲线的变化趋势,我们可以对模型性能进行更加深入的评估和优化。例如,在实际应用中:
- 若某条ROC曲线上游区域(靠近左下角)接近随机线,则表明该分类器在识别阴性样本(Y=0)时存在较多误判。
- 而若某条ROC曲线上游区域(靠近右上角)接近随机线,则说明其在识别阳性样本(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()

结果

结论
目前这个项目仍处于初期阶段。然而值得指出的是,在众多现实世界的问题领域中,深度学习已经取得了显著成果。在这个博客中,我展示了如何利用卷积神经网络结合迁移学习的方法来分析一组显微图像中的良性和恶性乳腺癌细胞。
