还在愁如何入手二分类项目?今天小编给大家介绍一篇AI Studio的精品医疗行业二分类项目,只需4步即可达成,准确度达到95.5%以上。
项目介绍
肺炎是常见病,影像检查在肺炎诊断中不仅要发现病灶,还要鉴别是否为其他疾病,特别是与肺癌鉴别需要做胸部CT检查。在治疗中观察病灶的动态变化,胸片一般是最佳的检查方法。
本项目使用广州市妇女儿童医疗中心提供的儿童肺炎X光片数据集,所有X光片均为1~5岁儿童患者常规临床护理时采集项目使用的是儿童肺炎X光片数据集,其中肺炎的:3632张和正常的:1345张,共计4977张。图片为原始分辨率不规则的JPEG图片,两种图片分别被保存在pneumonia_和normal_文件夹中。在模型训练、评估阶段和验证阶段,按照8:1:1的比例将全部样本数据划分为训练集(3980张)、测试集(498张)和验证集(499张)。
下面对部分图片进行展示:
本项目是基于飞桨框架完成的,使用ResNet50网络作为训练模型,top1准确率达到了0.955,top5准确率达到了1.0。训练完成后对测试集进行预测,并可视化模型效果。通过模型就可以完成辅助CT检查,帮助医生快速对肺炎病例进行筛查。
数据处理
解压数据集并将数据集移动至指定位置,通过代码生成训练集文件夹、测试集文件夹、标签文件以及相应的路径txt文件,创建Dataset并可视化数据。主要代码如下:
# 定义DataSet
class XChestDateset(Dataset):
def __init__(self, txt_path, transform=None,mode='train'):
# 初始化函数
super(XChestDateset, self).__init__()
self.mode = mode
self.data_list = []
self.transform = transform
if mode == 'train':
self.data_list = np.loadtxt(txt_path, dtype='str')
elif mode == 'valid':
self.data_list = np.loadtxt(txt_path, dtype='str')
elif mode == 'test':
self.data_list = np.loadtxt(txt_path, dtype='str')
def __getitem__(self, idx):
# 读取图片,对图片进行归一化处理,返回图片和 标签
img_path = self.data_list[idx][0]
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
if self.transform:
img = self.transform(img)
return img, int(self.data_list[idx][1])
def __len__(self):
# 获取样本总数
return self.data_list.shape[0]
可视化以后的数据
网络定义
本项目使用的是ResNet50 网络需要获取网络最后一个卷积层输出的特征图,然后计算它的梯度。所以网络设计的时候需要提供一个方法,用来获取最后一个卷积层的输出。主要代码如下:
# 定义ResNet模型
class ResNet(paddle.nn.Layer):
def __init__(self, layers=50, class_dim=2):
super(ResNet, self).__init__()
self.layers = layers
supported_layers = [50, 101, 152]
assert layers in supported_layers, \
"supported layers are {} but input layer is {}".format(supported_layers, layers)
if layers == 50:
#ResNet50包含多个模块,其中第2到第5个模块分别包含3、4、6、3个残差块
depth = [3, 4, 6, 3]
elif layers == 101:
#ResNet101包含多个模块,其中第2到第5个模块分别包含3、4、23、3个残差块
depth = [3, 4, 23, 3]
elif layers == 152:
#ResNet152包含多个模块,其中第2到第5个模块分别包含3、8、36、3个残差块
depth = [3, 8, 36, 3]
# 残差块中使用到的卷积的输出通道数
num_filters = [64, 128, 256, 512]
# ResNet的第一个模块,包含1个7x7卷积,后面跟着1个最大池化层
self.conv = ConvBNLayer(
num_channels=3,
num_filters=64,
filter_size=7,
stride=2,
act='relu')
self.pool2d_max = nn.MaxPool2D(
kernel_size=3,
stride=2,
padding=1)
# ResNet的第二到第五个模块c2、c3、c4、c5
self.bottleneck_block_list = []
num_channels = 64
………………………………………………………………………………………………
def forward(self, inputs):
conv = self.conv_layer(inputs)
y = self.last_layer(conv)
return y
网络的基础结构和参数信息:
moel = ResNet()
paddle.summary(moel, (16, 3, 224, 224))
使用summary 函数打印网络的基础结构和参数信息:
-------------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===============================================================================
Conv2D-1 [[16, 3, 224, 224]] [16, 64, 112, 112] 9,408
BatchNorm2D-1 [[16, 64, 112, 112]] [16, 64, 112, 112] 256
ConvBNLayer-1 [[16, 3, 224, 224]] [16, 64, 112, 112] 0
MaxPool2D-1 [[16, 64, 112, 112]] [16, 64, 56, 56] 0
Conv2D-2 [[16, 64, 56, 56]] [16, 64, 56, 56] 4,096
BatchNorm2D-2 [[16, 64, 56, 56]] [16, 64, 56, 56] 256
ConvBNLayer-2 [[16, 64, 56, 56]] [16, 64, 56, 56] 0
…………………………………………………………………………………………………………………………………………………………………………………………………………
BatchNorm2D-53 [[16, 2048, 7, 7]] [16, 2048, 7, 7] 8,192
ConvBNLayer-53 [[16, 512, 7, 7]] [16, 2048, 7, 7] 0
BottleneckBlock-16 [[16, 2048, 7, 7]] [16, 2048, 7, 7] 0
AdaptiveAvgPool2D-1 [[16, 2048, 7, 7]] [16, 2048, 1, 1] 0
Flatten-1 [[16, 2048, 1, 1]] [16, 2048] 0
Linear-1 [[16, 2048]] [16, 2] 4,098
===============================================================================
Total params: 23,565,250
Trainable params: 23,459,010
Non-trainable params: 106,240
-------------------------------------------------------------------------------
Input size (MB): 9.19
Forward/backward pass size (MB): 4768.81
Params size (MB): 89.89
Estimated Total Size (MB): 4867.89
-------------------------------------------------------------------------------
{'total_params': 23565250, 'trainable_params': 23459010
网络训练及验证
使用飞桨分布式训练API对网络进行训练,接着对网络进行验证,查看验证集得分,主要代码如下:
BATCH_SIZE = 16
EPOCHS = 30 #训练次数
decay_steps = int(len(trn_dateset)/BATCH_SIZE * EPOCHS)
train_loader = DataLoader(trn_dateset, shuffle=True, batch_size=BATCH_SIZE )
valid_loader = DataLoader(val_dateset, shuffle=False, batch_size=BATCH_SIZE)
model = paddle.Model(ResNet( class_dim=2))
base_lr = 0.0125
lr = paddle.optimizer.lr.PolynomialDecay(base_lr, power=0.9, decay_steps=decay_steps, end_lr=0.0)
# 定义优化器
optimizer = paddle.optimizer.Momentum(learning_rate=lr,
momentum=0.9,
weight_decay=L2Decay(1e-4),
parameters=model.parameters())
# 进行训练前准备
model.prepare(optimizer, CrossEntropyLoss(), Accuracy(topk=(1, 5)))
# 启动训练
model.fit(train_loader,
valid_loader,
epochs=EPOCHS,
batch_size=BATCH_SIZE,
eval_freq =5,#多少epoch 进行验证
save_freq = 5,#多少epoch 进行模型保存
log_freq =30,#多少steps 打印训练信息
save_dir='/home/aistudio/checkpoint')
#对验证集进行验证,查看验证集得分
model.evaluate(valid_loader, log_freq=30, verbose=2)
训练结果:
此处获得的效果比较好准确率在95.5%以上。
{'loss': [0.8956491], 'acc_top1': 0.9559118236472945, 'acc_top5': 1.0}
模型预测及
可视化模型效果
使用测试集进行预测,然后对准确率等指标进行处理获得混淆矩阵和ROC曲线等,通过绘制类激活图获得可视化模型效果图。我们从混淆矩阵中得知,通过ResNet50算法对3980张儿童X光胸片,其中肺炎有2905例,正常有1075例进行训练。在测试集上进行验证,其中肺炎有363例,正常有135例,ResNet50算法的准确率高达96.39%,真阳性率98.07%,真阴性率91.85%,AUC值为0.99。
一般肺炎在胸片上在两侧肺部表现出局部高密度影、肺纹理增粗、边缘模糊等征象,在类激活图中可见模型对肺炎类别的关注区域多在胸片上的两侧肺部,对于正常类别多在心脏或者非肺部区域。可见模型的关注区域符合诊断要求。
混淆矩阵:
confusion = confusion_matrix(true_label, pre_label)#计算混淆矩阵
plt.figure(figsize=(7,7))
sns.heatmap(confusion,cmap='Blues_r',annot=True,fmt='.20g',annot_kws={'size':20,'weight':'bold', })#绘制混淆矩阵
plt.xlabel('Predict')
plt.ylabel('True')
plt.show()
print("混淆矩阵为:\n{}".format(confusion))
print("\n计算各项指标:")
print(classification_report(true_label, pre_label,digits=4))
ROC曲线:
接受者操作特性曲线(ROC曲线)又称为感受性曲线曲线上各点反映着相同的感受性,它们都是对同一信号刺激的反应,只不过是在几种不同的判定标准下所得的结果而已。接受者操作特性曲线就是以FPR(假阳性概率)为横轴,TPR(真阳性概率)为纵所组成的坐标图,和被试在特定刺激条件下由于采用不同的判断标准得出的不同结果画出的曲线。
plt.figure(figsize=(8,8))
kind = {"normal":0,'pneumonia':1}
y_score = np.array(y_score)
fpr , tpr ,threshold = roc_curve(true_label, y_score[:,kind['normal']], pos_label=kind['normal'])
roc_auc = auc(fpr,tpr) ###计算auc的
fpr1 , tpr1 ,threshold = roc_curve(true_label, y_score[:,kind['pneumonia']], pos_label=kind['pneumonia'])
roc_auc1 = auc(fpr1,tpr1) ###计算auc的
plt.plot(fpr, tpr,marker='o', markersize=5,label='Normal')
plt.plot(fpr1, tpr1,marker='*', markersize=5,label='Pneumonia')
plt.title("Normal AUC:{:.4f}, Pneumonia AUC:{:.4f}".format(
roc_auc,roc_auc1))
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.legend(loc=4)
plt.show()
将特征图的响应大小,映射到了原图能让读者更直观的了解模型的效果。
# 获取 Grad-CAM 类激活热图
def get_gradcam(model, data, label, class_dim=2):
conv = model.conv_layer(data) # 得到模型最后一个卷积层的特征图
predict = model.last_layer(conv) # 得到前向计算的结果
label = paddle.reshape(label, [-1])
predict_one_hot = paddle.nn.functional.one_hot(label, class_dim) * predict # 将模型输出转化为one-hot向量
score = paddle.mean(predict_one_hot) # 得到预测结果中概率最高的那个分类的值
score.backward() # 反向传播计算梯度
grad_map = conv.grad # 得到目标类别的loss对最后一个卷积层输出的特征图的梯度
grad = paddle.mean(paddle.to_tensor(grad_map), (2, 3), keepdim=True) # 对特征图的梯度进行GAP(全局平局池化)
gradcam = paddle.sum(grad * conv, axis=1) # 将最后一个卷积层输出的特征图乘上从梯度求得权重进行各个通道的加和
gradcam = paddle.maximum(gradcam, paddle.to_tensor(0.)) # 进行ReLU操作,小于0的值设为0
for j in range(gradcam.shape[0]):
gradcam[j] = gradcam[j] / paddle.max(gradcam[j]) # 分别归一化至[0, 1]
return gradcam
# 将 Grad-CAM 叠加在原图片上显示激活热图的效果
def show_gradcam(model, data, label, class_dim=2, pic_size=224):
gradcams = get_gradcam(model, data, label,class_dim=class_dim)
for i in range(data.shape[0]):
img = (data[i].numpy() *127.5 +127.5).astype('uint8').transpose([1, 2, 0]) # 归一化至[0,255]区间,形状:[h,w,c]
heatmap = cv2.resize(gradcams[i].numpy() * 255., (data.shape[2], data.shape[3])).astype('uint8') # 调整热图尺寸与图片一致、归一化
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET) # 将热图转化为“伪彩热图”显示模式
superimposed_img = cv2.addWeighted(heatmap, .2, img, .8, 1.) # 将特图叠加到原图片上
return superimposed_img
model_path = 'checkpoint/final.pdparams'
model = ResNet( class_dim=2)
para_state_dict = paddle.load(model_path)
model.set_dict(para_state_dict)
model.eval()
test_txt = 'work/data/test_list.txt'
test_dataset = XChestDateset(test_txt,val_transform, 'test')
test_loader = DataLoader(test_dataset, shuffle=True, batch_size=16 )
dataiter = iter(test_loader)
images, labels = dataiter.next()
总结
本项目是经典的医学相关的二分类项目,使用飞桨框架完成,提供了一些常用的指标和图表例如ROC曲线、类激活图等。希望可以给大家踩平跨进深度学习实践的门槛。
·项目地址·
基于PaddlePaddle搭建儿童X光胸部肺炎分类项目:
https://aistudio.baidu.com/aistudio/projectdetail/2269806
·相关项目·
一文搞懂卷积网络之五:
https://aistudio.baidu.com/aistudio/projectdetail/1655497
END
本文分享自 PaddlePaddle 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!