☰
DCE-MRI结合CNN与迁移学习的乳腺癌分子分型二分类实战
2026/10/18 10:26:02 网站建设 项目流程

简介:《基于卷积神经网络的乳腺癌分子分型预测研究》是发表于《杭州电子科技大学学报(自然科学版)》的学术论文PDF,内容面向医学影像分析、深度学习及乳腺癌智能诊断方向的研究者与相关专业学生。资源共1个文件,PDF格式,包体大小576KB,正文包含摘要、引言、实验数据、方法原理、结果分析和讨论等完整章节。研究回顾性分析了171例乳腺癌患者的DCE-MRI影像,采用卷积神经网络(CNN)自动提取病灶区域特征,开展Luminal B型与非Luminal B型的二分类预测,受试者工作特征曲线下面积(AUC)最高达到0.697,验证了CNN结合动态增强磁共振成像预测乳腺癌分子分型的可行性。读者可从中获取具体实验数据统计、ROI图像获取与扩充流程、网络结构设计思路及结果评估方法,也能了解该方向当前进展与未来优化方向。目前已有241人学习,可作为医学影像与深度学习交叉领域研究、课程设计或开题报告的参考资料。

1. 把乳腺癌分子分型预测做成二分类:DCE-MRI 影像与 CNN 的首次结合实验

医学影像做深度学习,最难的不是搭网络,而是拿到一份能用的数据和一套合理的预处理流程。这篇基于卷积神经网络的乳腺癌分子分型预测研究,给我最大的启发是它把四分类问题主动降级成二分类,用 171 例 DCE-MRI(动态对比增强磁共振)影像去区分 Luminal B 型与非 Luminal B 型,最终在病例级别拿到最高 AUC 0.697。坦白说,这个数字不算惊艳,甚至不如同一批数据上传统手工特征的 0.712,但它证明了 CNN 可以自动从增强 MRI 里学到与分子分型相关的表征,而且用的是迁移学习思路——对样本量捉襟见肘的医学影像课题来说,这个方向比一味堆网络深度更值得复现。

2. 数据准备与 ROI 提取:从原始四维 DCE-MRI 到 227×227 训练样本

2.1 分子分型的标签构造:为什么只做 Luminal B 与非 Luminal B 二分类

研究纳入了 171 例术前、化疗前行免疫组化病理检查及 DCE-MRI 检查的乳腺癌患者。免疫组化结果按 ER、PR、Her-2、Ki-67 的阴阳性组合,把样本划成 Luminal A、Luminal B、HER-2 过表达、Basal-like 四类,分布是 35、93、17、26 例。HER-2 型只有 17 例,Basal-like 型 26 例,这类小类别单独建模时别说训练 CNN,连交叉验证的每个折里保证类别均衡都困难。

所以我特别认同作者的处理方式:不硬做四分类,而是把问题改成 Luminal B(93 例)与非 Luminal B(78 例)的二分类。表面上看损失了细粒度信息,但这其实是医学影像小样本课题里很务实的策略——类别不均衡是比网络结构更致命的坑,与其用类别权重硬拉,不如先保证正负样本量级相近。

2.2 DCE-MRI 预处理链路:T0 蒙片、T3 增强序列与 TER 增强率图的计算逻辑

原文用的是西门子 3.0T 磁共振扫描仪,T1 加权 DCE-MRI 序列,造影剂 Gd-DTPA 按 0.2 mmol/kg 前臂静脉注射。从蒙片开始扫 5 个增强序列,每个序列 144 张断层图,分辨率 448×448,成像间隔 60 秒。数据本质是四维的:三个空间维度加一个时间维度,记录造影剂在组织里的动态分布。

预处理的关键一步是从 5 个时间序列里挑出 T3 序列和 T0 序列。T0 是注射造影剂之前的蒙片,T3 是增强后某个时间点。之所以选 T3 不选后面的序列,是因为肿瘤内新生血管丰富,造影剂在 T3 时间点的组织差异性表现最突出。预处理流程如下:

import numpy as np from scipy.ndimage import wiener_filter def compute_ter(t0_volume, t3_volume): # t0_volume, t3_volume: (depth, height, width) 对应单个病例的某个序列 t0_denoised = wiener_filter(t0_volume.astype(np.float32), size=3) t3_denoised = wiener_filter(t3_volume.astype(np.float32), size=3) # 减影序列 subtracted = t3_denoised - t0_denoised # 增强率序列 TER = (T3 - T0) / T0,加 epsilon 防止除零 epsilon = 1e-6 ter = subtracted / (t0_denoised + epsilon) # 归一化到 0~1 ter_min = ter.min() ter_max = ter.max() ter_norm = (ter - ter_min) / (ter_max - ter_min + epsilon) return ter_norm

这里有两个细节值得新手注意。第一,维纳滤波在原文里是「自适应维纳滤波」,在 MATLAB 里大概是wiener2,我在 Python 里习惯用scipy.ndimage.wiener_filter做近似,size 设 3 或 5 都行,主要是去高斯噪声,但别设太大,否则会把病灶边缘磨掉。第二,增强率序列 TER 相当于是对血流灌注信号的归一化表达,它比直接用原始灰度的减影图更抗机器间差异。

2.3 ROI 截取与图像扩增:1 倍与 1.5 倍病灶框、多角度旋转和滑窗策略

ROI 截取依赖影像科医生标注的病灶信息:肿瘤出现的序列位置、消失位置、最大面积序列,以及肿瘤最大面积的中心坐标。拿到这些标注后,在 TER 序列的对应切片上以病灶中心为中点,截取 1 倍和 1.5 倍病灶直径的方形区域。同时为了保持每个病例的图像数量一致,选取最大截面切片及其前后各 2 张,共 5 张切片各截一次。

光截取肯定不够,171 例病例拆成训练集后也就一百多例,直接喂 CNN 会严重过拟合。原文的扩增方案很系统:

from PIL import Image import numpy as np def augment_roi(roi_img, crop_sizes=(1.0, 1.5), angles=(0, 30, 60, 90), flip=True): augmented = [] base_size = roi_img.size[0] # 假设正方形 for scale in crop_sizes: target = int(base_size * scale) cropped = roi_img.resize((target, target), Image.BICUBIC) for angle in angles: rotated = cropped.rotate(angle, resample=Image.BICUBIC) if flip: augmented.append(rotated) augmented.append(rotated.transpose(Image.FLIP_LEFT_RIGHT)) augmented.append(rotated.transpose(Image.FLIP_TOP_BOTTOM)) else: augmented.append(rotated) # 双线性插值统一采样到 227x227 resized = [im.resize((227, 227), Image.BILINEAR) for im in augmented] return resized

这套扩增的逻辑是把「病灶内部特征的空间分布模式」尽可能多地暴露给网络:旋转 30、60、90 度相当于在教 CNN 对肿瘤方向不敏感,水平加竖直镜像进一步增加样本多样性,双线性插值重采样到 227×227 则保证所有样本进入网络时的尺寸一致。原文里训练组扩增到了原始数据的 120 倍,测试组扩到 15 倍,训练组每类取 3.5 万张、共 7 万张,测试组每类取 1000 张、共 2000 张。这个比例很讲究,扩增倍数太大会让相邻样本高度相关,反而造成数据泄露式的虚高精度。

3. CNN 结构设计与训练策略:参考经典模型改出来的八层卷积实测配置

3.1 网络结构拆解:卷积核尺寸、步长、填充与参数量对 227×227 输入的适配

原文直接参考了 AlexNet 的骨架,但做了适配小数据集和 227×227 输入的调整。整个网络 8 层卷积加 3 层全连接,我做了参数梳理:

层操作输入核步长填充输出特征图
C1卷积+ReLU227×227×111×11×964055×55×96
P1最大池化55×55×963×32027×27×96
C2卷积+ReLU27×27×965×5×2561227×27×256
P2最大池化27×27×2563×32013×13×256
C3卷积+ReLU13×13×2563×3×3841113×13×384
C4卷积+ReLU13×13×3843×3×3841113×13×384
C5卷积+ReLU13×13×3843×3×2561113×13×256
P5最大池化13×13×2563×3206×6×256
FC6全连接+ReLU6×6×256———1024
FC7全连接+ReLU1024———512
FC8全连接512———2

注意几个关键设计点。第一,C1 用了 11×11 的大卷积核加步长 4,目的是在浅层就快速缩小空间尺寸、扩大感受野,对 227×227 的输入,一步就能从 227 压到 55,省掉一层池化。第二,C3、C4、C5 都是 3×3 卷积加填充 1,输出尺寸不变、只改通道数,这种「用连续小卷积堆深度」的玩法比单层大卷积更能拟合非线性。第三,全连接前接 Dropout,原文点到为止,但这是控制 1024 维全连接过拟合的关键手段。我从经验上补一句:Dropout 概率在 0.5 附近效果最好,太大欠拟合。

3.2 从头学习与迁移学习:Caffe 1.0 下两种训练的工程实现差异

训练分了两条路线:从头学习(CNN 初始参数随机赋值)和迁移学习(把预训练模型的卷积层参数拿来初始化)。原文用的是 Caffe 1.0,Ubuntu 14.04 环境,Dell T5610 工作站加 Quadro K5000,4GB 显存。这种配置在当年跑一个模型约 14 小时,批大小不敢设大,我按经验估计在 32 到 64 之间。

迁移学习的实现核心是冻结或微调卷积层的学习率:

layer { name: "conv1" type: "Convolution" bottom: "data" top: "conv1" param { lr_mult: 1 name: "conv1_w" } param { lr_mult: 2 name: "conv1_b" } convolution_param { num_output: 96 kernel_size: 11 stride: 4 weight_filler { type: "gaussian" std: 0.01 } } }

迁移学习时把conv1到conv5的lr_mult调低到 0.1 倍或 0.01 倍,FC6 到 FC8 保持正常学习率甚至调高。原因是预训练模型已经把通用视觉特征(边缘、纹理基元、形状组件)学到了,浅层参数基本不用动,主要任务是让深层分类器重新拟合我们的二分类边界。如果所有层用同一个学习率,预训练权重很快就被梯度冲坏,结果比从头学还差——这个现象在做医学影像迁移学习时非常普遍。

3.3 训练集与测试集的病例级划分:防数据泄露的关键一步

原文在划训练测试集时特别强调了按病例划分:随机选 80% 病例做训练、20% 做测试,做了 5 折交叉验证。这两个细节值得展开。

如果按图片而不是按病例划分,同一个病人的 120 张扩增图会同时出现在训练集和测试集里,CNN 会学到「记住这张图」而不是「学会判别分子分型」,测试精度虚高得离谱。按病例划分后,测试集里出现的每一个病例的所有图片,模型在训练时都没见过,这种验证结果才真实。

交叉验证 5 折的划分各有微调,Luminal B 训练组在 74 到 75 例之间波动、测试组 18 到 19 例,非 Luminal B 训练组 62 到 63 例、测试组 15 到 16 例。整体比例保持 8:2 不动。

4. 模型评估与结果分析:ACC、SEN、SPE、AUC 四指标怎么算、怎么看

4.1 评估指标计算:从混淆矩阵出发复现实验结果

原文按临床惯例定义 Luminal B 为阳性、非 Luminal B 为阴性。TP 是真阳性(Luminal B 预测正确),FN 是假阴性(Luminal B 被判成非 Luminal B),TN 是真阴性,FP 是假阳性。四个指标公式是 ACC = (TP+TN)/(TP+FP+TN+FN),SEN = TP/(TP+FN),SPE = TN/(TN+FP)。注意原文给出的灵敏度公式在排版上有歧义,按标准定义应该是 TP/(TP+FN),不是分母写成 TP+FP。

跑完交叉验证后,评价分两个粒度:以单张图片为评价单元,和以病例为评价单元。病例级的结果由图片级预测加权得到——具体加权方式原文没有展开,但常见做法是:一个病例的 5 张切片分别预测,投票或平均概率取最终类别。我一般用平均概率,因为投票在概率接近 0.5 时容易抖动。

迁移学习在病例维度上的表现是 ACC 0.691、SEN 0.696、SPE 0.685、AUC 0.697。对比从头学习的病例级 ACC 0.600、AUC 0.589,迁移学习每个指标都高出约 0.09 到 0.11,相当可观。

4.2 结果对比表:深度学习与传统手工特征方法的真实差距

原文把深度学习和传统方法放上了对比台面,传统方法走的是病灶分割、人工特征提取、特征选择到随机森林分类的路,最终拿到 91 维特征:

方法ACCSENSPEAUC
传统提取特征 + 随机森林0.6600.7280.5920.712
CNN 从头学习(病例级)0.6000.5790.6250.589
CNN 迁移学习(病例级)0.6910.6960.6850.697

注意传统方法的 AUC 0.712 略高于迁移学习的 0.697,但灵敏度只有 0.592 的特异性搭配出现了明显的偏斜:传统方法 SEN 0.728 高、SPE 0.592 低,说明它更倾向于把样本判成正类(Luminal B),在牺牲特异性的情况下换来了整体 AUC 微涨。深度学习方法 SEN 和 SPE 都压在 0.68 到 0.70 区间,分布均匀得多。

这种「平均精度高但类别严重偏斜」的对比,在医学影像领域很常见,不能只看 AUC 就下结论。

4.3 图片级与病例级预测的差异:为什么病例级结果普遍优于图片级

从实验数据看,迁移学习图片级 ACC 0.653、病例级 0.691,病例级全面占优。原因在于单个病例的多张切片信息在聚合时做了噪声平均:5 张切片各自预测概率,即使有一两张切片模糊或囊变区域干扰,其余正常切片的置信度足以把平均概率拉回正确侧。

我一般在图片级概率出来后会做一个 Soft Voting 而不是 Hard Voting:

import numpy as np def case_level_predict(patch_probs, threshold=0.5): # patch_probs: 单个病例所有切片的阳性概率列表 mean_prob = np.mean(patch_probs) return 1 if mean_prob >= threshold else 0

如果不做概率平均而是逐张判类再投票,5 张里 3 张阴性 2 张阳性,边缘病例很容易翻车。概率平均保留住了预测的不确定性强度,这个细节在正式复现原文结果时值得保留。

5. 落地复现中的避坑与常见问题:从标签泄露到显存不足的九个实测记录

5.1 标签泄露:按病例划分数据集是底线中的底线

现象:模型训练集 ACC 超过 0.98,测试集却只有 0.61。

原因:没有按病例划分,而是按图片随机拆分。同一个病例的 120 张扩增图在训练集和测试集里各占一半,网络直接背下了该病例的纹理特征,测试集里同一病例的图被样本外推理「沾光」。

解决:一律以病例 ID 为最小单位切分数据。先拿病例列表做train_test_split,再按病例 ID 去取图片进训练集或测试集。代码上建议一开始就把图片名带病例前缀,后续所有切分都基于这个前缀来筛。

5.2 扩增过度:120 倍的花式操作引入样本间强相关性

现象:训练损失降得漂亮,验证集指标震荡不止。

原因:旋转 30、60、90 度加镜像,每张原图变出几十张高度相似样本,训练集中的「有效信息量」远低于图片数量暗示的量,网络在视觉上仍在拟合同一病灶的不同角度投影。

解决:扩增倍数控制在合理范围。原文用 120 倍是因为基础样本量只有一百多例,逼不得已。如果数据量能到 500 例以上,扩增 20 倍足矣,再往上边际收益几乎为零。

5.3 迁移学习学习率没分层调:预训练权重被冲刷

现象:迁移学习效果和从头学习一模一样,甚至更差。

原因:所有层用了同一个学习率,反向传播时预训练卷积核被微调得面目全非。

解决:把前五层卷积的lr_mult设成 0.1 或 0.01,全连接层学习率保持 1.0 或 2.0。另外建议前 5 个 epoch 全冻结卷积层只训全连接,之后解冻微调。这套「先分类器后特征」的习惯,在 Caffe 和 PyTorch 里都适用。

5.4 样本类别不均衡:四分类崩塌后降级成二分类

现象:HER-2 型只有 17 例,预测结果把大部分样本判成了大类。

原因:直接上四分类,网络被类别数量带偏,少数类梯度贡献被淹没。

解决:先看类别分布再定任务粒度。原文把四类合并成 Luminal B 与非 Luminal B,是数据驱动的最优选择。如果将来数据量真的大起来,可以按 ER/PR 阳阴、Her-2 阳阴这种组合重新拆分标签。

5.5 显存溢出:Quadro K5000 上的 4GB 显存限制

现象:Caffe 训练到第二个 epoch 时报out of memory。

原因:227×227 输入按测试阶段 batch size 32 推,中间层特征图累积把显存撑爆。

解决:先降 batch size 到 16 再试,如果还爆就检查是不是测试阶段开了average_loss统计窗口太大。再不行,把数据改成单通道灰度图输入,输入通道从 3 降为 1,显存占用直接砍半。

5.6 图像尺寸不一致:双线性插值后病灶比例被压缩

现象:同一病灶在 1 倍框和 1.5 倍框下,重采样到 227×227 后形态完全不同。

原因:1.5 倍框里的病灶占整个图像面积更小,网络看到的有效特征尺度不一致。

解决:不要只截 1 倍框,而是像原文一样把 1 倍和 1.5 倍都纳入训练,让 CNN 学到尺度不变性。从结果看 ACC 0.653 证明混合尺度确实有效。

5.7 灰度分布漂移:维纳滤波后图像均值不一致

现象:同一病例的 T0、T3 序列在预处理后灰度范围差别明显。

原因:每个序列独立做维纳滤波和归一化,不同序列的均值和方差没有对齐。

解决:计算 TER 后做全局归一化,而不是对 T0 和 T3 分别归一化再相除。原文的 TER 公式里减影除蒙片本身就带归一化效果,算是规避了这个坑。

5.8 评价粒度混淆:图片级 AUC 和病例级 AUC 不能混用

现象:论文里图片级 0.671 和病例级 0.697 同时出现,复现者误以为取高值即可。

原因:两张指标代表的信息粒度不同,图片级是单张切片的判别能力,病例级是多张切片聚合后的判别能力。

解决:临床落地看病例级,模型调参阶段看图片级。图片级低说明网络对病灶本身的特征提取力不足,病例级高说明多切片冗余信息在弥补单张的不足。

5.9 分类阈值与工作点选择:AUC 高于 0.6 不代表临床可用

现象:迁移学习病例级 AUC 0.697,但 ACC 只有 0.691,误诊率仍偏高。

原因:AUC 是阈值无关的排序指标,默认阈值 0.5 不一定是这个模型的最优工作点。

解决:画出 ROC 曲线找约登指数 J = SEN + SPE - 1 最大的点作为实际分类阈值。如果临床更看重不漏诊,就降低阈值以换取灵敏度;如果更怕误诊,就提高阈值换特异性。

6. 迁移学习在医学影像小样本场景的进阶用法:批量归一化、数据扩增边界与消融实验设计

迁移学习从 0.589 抬到 0.697 的幅度,在医疗影像数据集里已经是很大的收益了。但这个收益还能继续放大,我沿着原文的思路补充三个高频有效的进阶手段。

第一个是批量归一化(Batch Normalization)。原文网络结构里没有 BN,这在 2018 年不算罕见,但现在做迁移学习微调,BN 几乎是必需品。BN 放在激活函数之前或之后都可以,我习惯放在卷积之后、ReLU 之前,它可以压制不同病例之间因扫描参数不同导致的灰度分布漂移,让梯度更新更平滑。加了 BN 后原来需要 14 小时的训练,往往 8 到 10 小时就收敛了,因为 BN 允许用更大的学习率而不会发散。

第二个是数据扩增的边界控制。原文的扩增是在预处理阶段完成的,先扩增再喂网络。如果你用 PyTorch 或 TensorFlow,更推荐在 DataLoader 里做在线扩增:

import torch from torchvision import transforms train_transform = transforms.Compose([ transforms.RandomRotation(degrees=(0, 90)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomVerticalFlip(p=0.5), transforms.Resize((227, 227), interpolation=2), transforms.ToTensor(), transforms.Normalize(mean=[0.485], std=[0.229]) ])

在线扩增有一个隐藏优势:每个 epoch 生成的扩增样本都不同,相当于无限数据。离线扩增把 120 倍的样本固定在硬盘上,样本间的强相关性反而会成为过拟合的帮凶。在线扩增配合 Dropout,是对抗小样本过拟合的双保险。

第三个是消融实验设计。原文用 5 折交叉验证对比了从头学习和迁移学习,这个框架可以直接扩展成消融实验来定位「哪些改动在起作用」。我的习惯是固定 Cross-Validation 的折数和随机种子,在下面四个配置上各跑一遍:基础 CNN 不做任何扩增、加基础扩增、加扩增和迁移学习、加扩增加迁移学习加 BN。每跑完一组记录病例级 AUC,四组成对对比,就能拆出每个因子对最终指标的边际贡献。

有一点必须承认:AUC 0.697 距离临床决策还有距离。传统手工特征在这个数据集上 0.712 的 AUC,也说明 DCE-MRI 影像中分子分型的信息边界可能就卡在 0.7 到 0.75 之间。原文明说了后续要靠增加样本量和改网络结构继续优化,如果未来能拉入更多病例,把 HER-2 和 Basal-like 的样本量补到 50 例以上,四分类也不是没机会。这类医学影像深度学习课题的常态就是第一版效果有限、天花板偏低,真正能抬头的反而是后续数据迭代。

从那以后我每次做医学影像深度学习,都会先强制走一遍「病例级划分 → 类别分布检查 → 预处理链路可视化 → 基线模型跑通」这四步,然后再考虑网络结构够不够深、迁移学习要不要加。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询