
简介卷积神经网络CNN是当前图像识别与分类领域的主流深度学习算法。这份 PDF 论文聚焦于其在宫颈细胞图像自动分类中的应用面向深度学习、机器学习方向的科研人员、数据建模学习者及医学图像处理从业者。研究团队提出一套基于 CNN 的宫颈细胞自动分类方法借助迁移学习初始化分类网络通过 Softmax 归一化、交叉熵损失、批归一化及反向传播优化实现精准快速的异常细胞检测。资源共 1 个文件为 PDF 格式全文压缩包约 1.56MB内容完整包含摘要、方法、实验与结论。该资源的实验基于 Herlev 数据集采用 5 折交叉验证与常用基准方法相比特异性、调和平均数和准确率分别提升 19.46%、10.71% 和 5.09%适合希望系统了解宫颈细胞自动分类网络设计、迁移学习与批归一化实战应用的读者。文中还讨论了细胞学检查法在欠发达地区普及难的问题并指出深度学习方法可有效弥补专业医师紧缺与人工排查效果差的短板有助于理解计算机辅助细胞学检测的前沿方案并启发相关研究。目前已有 201 人学习下载该资料适合作为相关课题入门与进阶参考。1. 宫颈细胞图像分类把病理医生的“眼睛”搬进卷积神经网络医生说“高度病变建议活检”的背后是病理医生在显微镜下对着巴氏涂片看了十几年。卷积神经网络在这个场景里做的就是把筛查流程的最前一步自动化输入一张宫颈细胞病理图像自动判读它是正常上皮细胞、低度病变还是高度鳞状上皮内病变帮实验室把稀缺的病理资源集中到真正可疑的样本上。这套方案的原理不复杂甚至可以说“老土”——CNN加微调但落地时大多数人会在同一个地方翻车不是模型不够深而是染色差异、细胞重叠和标签不均衡让网络学到的根本不是医生眼里的“细胞形态”。这篇文章按数据预处理、网络选型、训练评估、避坑与进阶的顺序讲清楚一套能复现、能改参数、能评估效果的做法适合理工背景的算法工程师、医学图像方向的研究生以及想给检验科做辅助判读工具的从业者。2. 训练数据是命根子染色归一化与单细胞裁剪2.1 公开数据集选型Herlev、SIPAKMeD 和液基制片数据该怎么挑宫颈细胞图像分类绕不开两个经典公开数据集丹麦 Herlev 医院的单细胞数据集以及后来的 SIPAKMeD 数据集。Herlev 包含约九百张单细胞图像划分为正常的柱状细胞、浅表/中间鳞状上皮细胞、副基底鳞状细胞以及异常的低度病变CIN1和中度/重度病变CIN2/3总共七类。SIPAKMeD 后来把类别整理成五类图像数量更多类别分布更均衡训练时随机划分的可信度更高。还有一批基于液基薄层制片LBC扫描得到的公开数据图像更接近真实筛查场景但标注颗粒度参差不齐用之前要仔细看标签说明。选数据集时我的判断标准是三条第一有没有专业病理医生参与标注只有工程师标注的数据基本不能用第二类别分布是否和真实筛查场景接近异常类别占比太低会让模型天然的偏向阴性第三分辨率和染色方式是否和你最终部署的场景一致。公开数据集主要是用来验证流程和跑通训练的真正的可用模型最后几乎都要靠你自己采样的数据来微调。下表是三类数据源的直观对比数据源典型规模类别特点适合做什么Herlev约 900 张单细胞图7 类异常类偏少验证分类流程、写课程作业SIPAKMeD数千张单细胞图5 类分布相对均衡算法选型、预训练、消融实验自采液基制片数据依项目而定含背景、杂质、多细胞重叠最终部署与临床验证2.2 从整片视野到单细胞分割裁剪与尺寸对齐真实筛查中输入的是整张玻片扫描图一个视野里往往是几十个细胞堆叠在一起。如果直接把整张图丢给分类网络它学到的是“这个视野里细胞多不多、颜色深不深”而不是单个细胞的形态特征。常见做法是先做一步细胞定位把视野裁剪成以细胞核为中心的单细胞小图再喂给分类模型。这一步可以用基于阈值的经典图像处理也可以用 UNet 之类的分割模型取决于你的数据质量。下面这段代码是从细胞核掩膜出发裁剪单细胞的标准流程用轮廓检测找核中心再按核尺寸的固定倍数扩展边界import cv2 import numpy as np def crop_single_cells(image, mask, margin1.5, target_size(128, 128)): image: BGR 原图 mask: 细胞核二值掩膜, 背景为 0 margin: 以细胞核短边为基准向外扩多少倍 target_size: 统一缩放的目标尺寸 contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cells [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) cx, cy x w // 2, y h // 2 side max(w, h) * margin x1 max(0, int(cx - side / 2)) y1 max(0, int(cy - side / 2)) x2 min(image.shape[1], int(cx side / 2)) y2 min(image.shape[0], int(cy side / 2)) crop image[y1:y2, x1:x2] # 保持长宽比填充到正方形, 避免细胞被拉伸变形 crop pad_to_square(crop, pad_value0) crop cv2.resize(crop, target_size, interpolationcv2.INTER_LINEAR) cells.append(crop) return cells def pad_to_square(img, pad_value0): h, w img.shape[:2] side max(h, w) top (side - h) // 2 bottom side - h - top left (side - w) // 2 right side - w - left return cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, valuepad_value)margin 是最关键的参数。取 1.2裁剪区域紧贴细胞核分类时会丢失细胞浆的外围纹理信息异常细胞核浆比明显增大容易被漏掉取 3.0 以上裁剪区域又混入太多背景和其他细胞边缘。我一般从 1.5 起步用验证集上的敏感性做小范围网格搜索。pad_value 设为 0 对应黑色背景如果你的训练数据是液基制片背景本身是淡红色pad 成 0 反而引入了分布外区域这种情况下改成训练集背景像素的中值更稳。2.3 染色归一化Macenko 算法与不需要逐张 K 的轻量做法宫颈细胞图像在不同医院、不同制片机、不同扫描仪下染色浓度差异极大。同一个细胞在苏木精染色偏深的设备下和偏浅的设备下RGB 值可能差出两倍。如果不做染色归一化模型训练时会把“颜色深”和“病变”关联起来换个扫描仪就直接失灵。在宫颈细胞这类苏木精-伊红/巴氏染色图像上Macenko 染色分离是常见做法先把 RGB 转到光学密度域再用奇异值分解估计染色浓度矩阵最后把源图像的颜色分布映射到目标图像的染色空间。工程上可以直接用现成的 stain-normalization 库核心调用类似这样import staintools from staintools.utils.misc import read_image # 参考图: 选一张染色均衡、细胞形态清晰的典型图 target staintools.LuminosityStandardizer.standardize(read_image(ref.png)) normalizer staintools.MacenkoNormalizer() normalizer.fit(target) source read_image(input.png) normalized normalizer.transform(staintools.LuminosityStandardizer.standardize(source))如果不想引入额外依赖也有轻量做法在 HSV 空间里对色相H通道做直方图匹配再把饱和度S和明度V缩放到参考图的统计量范围内。这个做法扛不住极端染色偏差但对绝大多数同品牌试剂的数据够用而且速度快。要注意一点归一化只解决颜色分布的系统性偏差不解决个别切片的局部染色不均后者要配合数据增强才能覆盖到。2.4 数据增强清单哪些增强对细胞图像有效哪些会毁掉形态细胞图像的增强策略和自然图像完全不同。随机裁剪这种通用增强在细胞场景里是危险操作裁剪区域可能刚好把细胞核切掉一半生成一张“半个细胞”的训练样本模型会学会用残缺特征蒙答案。HSV 颜色扰动却非常好用因为染色偏差每天都有颜色增强等于模拟了制片误差。弹性形变也适合细胞这种柔性物体但幅度要克制。下面这套增强配置是我在宫颈单细胞分类上比较常用的底线方案import albumentations as A train_aug A.Compose([ A.RandomRotate90(p0.5), # 细胞旋转任意角度不影响类别 A.Flip(p0.5), A.RandomBrightnessContrast( brightness_limit0.15, contrast_limit0.15, p0.5 ), A.HueSaturationValue( hue_shift_limit5, sat_shift_limit10, val_shift_limit10, p0.3 ), A.ElasticTransform( alpha60, sigma120 / 3, alpha_affine30, p0.3 ), ])三个参数值得细说。hue_shift_limit 只给了 5因为宫颈细胞经过巴氏染色后核的紫蓝色和浆的粉红色本身是诊断依据色相偏太多就会改变类别语义。ElasticTransform 的 alpha 控制形变强度sigma 控制形变范围alpha 超过 120 后细胞边界会扭曲得不像细胞。还有一点容易被忽略验证集和测试集不要用任何几何增强只做 resize否则评估结果里混着增强带来的偏差模型上线后性能对不上。3. 网络选型与特征设计从通用 CNN 到病理场景的定制3.1 卷积神经网络结构图背后的选型逻辑ResNet 和 MobileNet 各管什么看卷积神经网络结构图不要被 ResNet 的五十层和 MobileNet 的深度可分离卷积吓住。这类网络的骨干部分做的事情完全一样——从边缘、纹理这些底层特征逐层组合成高层语义。区别在于 ResNet 借助残差连接把网络做得更深在数据量充足时能学到更细的形态特征MobileNet 用深度可分离卷积大幅砍掉参数量适合推理资源有限的场景。宫颈单细胞图像通常只有一百像素左右见方输入分辨率低网络容量并不需要多大。在小数据集上直接用最新的图像分类模型尤其 Vision Transformer 一类的结构不一定是好事。ViT 没有卷积的平移等偏置需要的数据量更大在两千张单细胞图这种规模下很容易过拟合。相比之下ResNet-50 的归纳偏置更契合小数据场景实现路径也成熟。如果是在 CPU 或边缘设备上做实时辅助判读MobileNetV3 或 EfficientNet-B0 是更现实的选择。3.2 核浆分离的双分支结构提升异常类别召回率的常用改法宫颈细胞分类的核心诊断依据是细胞核核的面积、核浆比、核染色质纹理。整图分类网络会平均地对待核和浆的特征当异常细胞的浆区所占像素较多时网络可能被浆区带偏。一个常见的改进是设计双分支结构一个分支吃整图另一个分支单独吃细胞核区域掩膜通过 mask 把浆区遮蔽最后把两个分支的特征拼起来分类。这个结构的代价是多一个 mask 输入但推理时 mask 已经在裁剪阶段生成好了并不增加额外计算量。用 PyTorch 示意一个轻量双分支头import torch.nn as nn class DualBranchHead(nn.Module): def __init__(self, in_features, num_classes5, hidden512): super().__init__() # 整图分支: 保留全局上下文 self.global_fc nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(in_features, hidden), ) # 核分支: 先用 1x1 卷积强化核区域响应, 核区域由 mask 加权 self.nucleus_conv nn.Conv2d(in_features, 256, 1, biasFalse) self.nucleus_fc nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(256, hidden), ) self.classifier nn.Sequential( nn.ReLU(), nn.Dropout(0.3), nn.Linear(hidden * 2, num_classes), ) def forward(self, features, nucleus_mask): g self.global_fc(features) n self.nucleus_conv(features * nucleus_mask) n self.nucleus_fc(n) return self.classifier(torch.cat([g, n], dim1))代码里 features 是骨干网络倒数第二层的输出特征图nucleus_mask 把 mask 下采样到同样的空间尺寸。关键在两处nucleus_conv 用 1x1 卷积在核区域上做通道重标定而 nucleus_fc 又单独走一次池化让核的信息不会被浆区淹没。加了这个头之后异常类别尤其 HSIL的召回率通常能提几个点代价是训练时多一个 mask 输入要处理。3.3 加载预训练权重的正确方式迁移学习中的冻结与解冻宫颈单细胞图像和 ImageNet 自然图像差距很大但这不代表随机初始化更好。用 ImageNet 预训练权重做初始化前几层学到的边缘、颜色、斑点纹理依然有效关键是微调策略要控制好。直接全解冻训练学习率稍大就会出现 loss 震荡而把全部层冻结只训练分类头又学不到细胞形态的独特特征。常见做法是分两阶段训练# 阶段 1: 冻结骨干, 只训练分类头 for name, param in model.named_parameters(): if not name.startswith(classifier): param.requires_grad False # 阶段 2: 解冻骨干的后半段, 用 1/10 学习率继续微调 for name, param in model.named_parameters(): if name.startswith(layer3) or name.startswith(layer4): param.requires_grad True阶段 2 中解冻 layer3 和 layer4 是经验值ResNet 的 layer1、layer2 学到的是通用边缘和纹理迁移价值高layer3、layer4 更偏抽象语义更应该在目标任务上微调。阶段 2 的学习率一般是阶段 1 的十分之一比如阶段 1 用 3e-4阶段 2 就降到 3e-5。加入批归一化层时要注意BN 层的均值和方差即使在被冻结的块里也需要更新否则输出分布和预训练时不一致实践上只需要关闭参数的梯度不需要锁定 BN 的 running stats。4. 训练与评估用对参数的 CNN 才叫图像分类算法4.1 训练参数学习率、优化器、batch size宫颈细胞图像分类的训练参数不能照抄自然图像任务。基础学习率通常落在 1e-4 到 3e-4优化器选 AdamW 而不是裸 Adam因为 AdamW 的权重衰减更干净对微调场景更友好。batch size 受限于单细胞图像很小128×128 甚至 64×64一般可以开到 64 甚至 128但要留意 BN 层在小 batch 下的表现如果显存允许尽量保持 batch size 在 32 以上。参数建议值原因优化器AdamW权重衰减解耦微调更稳基础学习率1e-4 ~ 3e-4预训练模型微调不宜过大微调学习率3e-5 ~ 1e-4解冻骨干后期望更细的脚步batch size32 ~ 64太小 BN 抖动大太大收敛偏慢epoch50 ~ 100配合早停避免过拟合训练循环里最值得留意的是学习率调度。我一般在前 5 个 epoch 用线性 warmup把学习率从 0 升到基础学习率之后用余弦退火降到接近 0。warmup 对医学小数据集特别有效——刚加载的预训练权重还没适应输入分布突然给大步长很容易把 loss 推到高位然后恢复不回来。import torch from torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR optimizer torch.optim.AdamW(model.parameters(), lr3e-4, weight_decay1e-4) warmup LinearLR(optimizer, start_factor0.01, end_factor1.0, total_iters5) anneal CosineAnnealingLR(optimizer, T_max45, eta_min1e-6) for epoch in range(50): warmup.step() if epoch 5 else anneal.step() train_one_epoch(...) val_one_epoch(...)4.2 类别不均衡的应对类权重与 Focal Loss宫颈细胞数据集天然不均衡正常类别占大头HSIL 等高度病变样本占比可能只有百分之几。直接用交叉熵模型会把所有不确定样本都推给占比大的类别因为它只要把所有样本都判成正常准确率就已经很高了。最常见也最稳的补救措施是类别权重按样本数量的倒数归一化后给每类的 loss 乘权重让少数类的错误产生更大的梯度信号。import torch.nn as nn def make_class_weight(label_counts): total sum(label_counts.values()) weights {k: total / (len(label_counts) * v) for k, v in label_counts.items()} weight_tensor torch.tensor([weights[i] for i in sorted(weights)]) return weight_tensor criterion nn.CrossEntropyLoss(weightweight_tensor)如果类别权重还不够下一档是 Focal Loss它的核心改动是在交叉熵上乘一个 (1 - p_t)^gamma 的调制因子让模型降低对高置信度样本的重视程度把训练焦点往难样本上偏移。Focal Loss 有两个参数要注意gamma 取 2 是常见起点取大了会让训练不稳定alpha 的作用是对类别做显式加权可以和类权重同时用。在异常类别极少的数据集上gamma2 的 Focal Loss 比普通加权交叉熵通常能再带来 1-2 个百分点的异常类召回率。4.3 评估看什么敏感性、特异性和混淆矩阵评估宫颈细胞分类模型看准确率几乎是无效的。类别比例悬殊时acc 是 95% 的模型可能在高度病变类别上的召回率只有 30%这正是筛查场景最危险的结果——漏诊。临床上更关心两个指标敏感性异常细胞被找出来的比例和特异性正常细胞被误报为异常的比例。前者低了会漏病人后者低了会让病理医生淹没在假阳性里二者必须同时看。from sklearn.metrics import classification_report, confusion_matrix, recall_score y_true [] # 所有验证样本的真实类别 y_pred [] # 模型预测类别 print(classification_report(y_true, y_pred, digits4)) cm confusion_matrix(y_true, y_pred) # 重点看异常类别所在行的召回率, 而不是宏平均我习惯在训练结束后额外输出一张按类别归属的召回率表并针对每一类分别设定“可接受最低召回率”的验收线。比如要求 HSIL 召回率大于 0.85、NILM正常阴性的特异性大于 0.95任何一个不达标就回去调整类别权重或阈值而不是直接看综合指标放行。4.4 把模型封装成可复用的分类流程训练完成后模型推理代码很容易变成一堆没人能维护的脚本。建议从一开始就封装成一个纯函数式的预测接口输入是原始图像路径输出是类别概率和输入图像的预处理元数据这样后续接 Web 服务、接实验室 LIS 系统都很方便。def predict_img(model, img_path, transform, device): img cv2.imread(img_path) if img is None: raise FileNotFoundError(img_path) tensor transform(img).unsqueeze(0).to(device) model.eval() with torch.no_grad(): logits model(tensor) prob torch.softmax(logits, dim1).cpu().numpy()[0] return probpredict_img 这个接口我一般要求满足三个条件一是不改模型结构也能换 backbone二是 transform 必须和训练时的推理 transform 完全一致不能训练时归一化、推理时不归一化三是概率输出必须保留下游做阈值调整或不确定性拒识都要用概率而不是硬标签。5. 宫颈细胞图像分类避坑记录我踩过的 5 个坑5.1 坑 1整片视野直接分类指标虚高但换数据就翻车现象把一张包含几十个细胞的视野图直接缩放到 512×512 丢给模型分类验证集准确率冲到 96%换成同一家医院的新一批切片后掉到 70%。原因网络学到的不是细胞形态而是视野里的背景纹理、细胞密度这类和制片方式强相关的琐碎特征。同一批数据集内部这些特征高度一致所以验证集表现良好换了制片批次这些特征全部漂移。解决回到单细胞路径先定位细胞核再裁剪到单细胞图。如果想保留视野级上下文用多示例学习MIL把视野看成多个单细胞示例的包而不是让网络直接读整张大图。5.2 坑 2染色差异让同一套权重在不同的玻片下失灵现象模型在训练集所在医院的切片上表现正常放到另一家使用不同扫描仪、染色方案略有差异的医院数据上异常类召回率直接跌破可接受线。原因输入图像的 RGB 分布已经整体偏移同一个细胞在训练集是偏紫红色在目标域变成了偏橙红色模型像是在看一种没见过的新颜色。解决训练管线里加入染色归一化 颜色增强双保险。全部训练样本先经参考图做颜色标准化再叠加 HSV 扰动推理时同样先做归一化。如果再不行采集目标域少量样本做有监督微调这比换模型结构更有效。5.3 坑 3类别不均衡下宏平均指标掩盖了异常类召回率现象训练日志里 acc 一直在涨验证集宏平均 F1 也过了 0.9分析模型在 HSIL 上的预测结果时发现这个类别几乎全被预测成了 LSIL 或正常。原因HSIL 样本在数据集中占比太少交叉熵的梯度贡献被正常类稀释。宏平均 F1 只是把各类 F1 做算术平均少数类的糟糕表现被多数类拉平了。解决使用类权重或 Focal Loss 重新训练。另外在验证策略上加一条硬性规则每个异常类别单独输出召回率低于阈值时整个训练流程报警不允许只看宏平均放行。5.4 坑 4微调时把预训练骨干一股脑全解冻现象阶段 1 训得好好的进入阶段 2 全解冻后 loss 开始震荡甚至训练集上的 loss 反而升高最终效果不如只训分类头。原因宫颈细胞图像和 ImageNet 图像的低层特征差异不小直接把所有层的参数一起放开学习率稍大就会破坏预训练已经学好的边缘和纹理特征产生灾难性遗忘。解决阶段 2 只解冻骨干的后半段学习率降到阶段 1 的十分之一并且给骨干层单独挂一个更小的权重衰减。用余弦退火让 loss 平稳降落等验证集指标连续两个 epoch 不涨再考虑进一步解冻前层。5.5 坑 5只看 accuracy 不看混淆矩阵现象模型的综合准确率一路飘高病理医生试用后反馈“为什么这么多 ASCUS 看不出来”翻看测试集结果发现模型把大量低度病变都归到了正常类别。原因准确率对类别比例敏感无法暴露具体错在哪。宫颈细胞类别本身有高低级别之分LSIL 和 HSIL 在形态上连续过渡模型把 HSIL 误判为 LSIL 和把正常误判为 HSIL 的后果完全不同。解决每轮验证都打印完整混淆矩阵重点检查相邻类别之间的错判方向。医学图像的评估必须按类别分别定阈值正常类别求特异性异常类别求敏感性每个类别的验收标准独立不合格就单独加数据或调阈。6. 进阶技巧TTA、病人级投票与“拒识”输出模型训完只是第一步真正部署时还需要处理推理阶段的不稳定问题。单张细胞图像的分类结果受到裁剪位置、染色残留、拍摄噪声的影响同一个细胞微弱的平移旋转可能让概率输出跳变。测试时增强TTA是性价比很高的稳定化手段推理时对同一张图做小幅旋转和翻转把多次结果的概率取平均代替一次前向的硬判决。def tta_predict(model, img_tensor, n8, devicecuda): probs [] for i in range(n): angle i * (360 // n) rotated torchvision.transforms.functional.rotate(img_tensor, angle) if i % 2: rotated torchvision.transforms.functional.hflip(rotated) with torch.no_grad(): probs.append(torch.softmax(model(rotated), dim1).cpu()) return torch.stack(probs).mean(dim0) # 类别概率求均值TTA 的 n 取 8 到 12 就够了再多边际收益递减推理耗时却线性上升。部署时把 TTA 放在最前先做整图的概率平均再做后续决策。病人级投票比单图 TTA 更重要。一个病人的玻片会采样出几十到上百张单细胞图如果只取单张图的结果做判断一张过染的样本就能产生一次假阳性。常见做法是把一个病人所有细胞图的预测概率按类别累加取平均值作为病人级概率或者更保守地取异常类别的最大概率保证不遗漏。结合这两个做法就可以加入“拒识”机制计算病人级概率的熵熵高于设定阈值时模型不输出硬标签而是返回“需人工复核”。这是把模型从自动判读降级为辅助筛查的正确姿势——人工复核的样本量大幅减少但模型没有把话说完把医生兜底的主动权还回去。回过头来看最值得投入的不是换更强的主干网络而是把数据清洗、染色归一化、多细胞聚合这些不见得性感的环节打磨明白。我在这类项目上一个很固执的习惯是每个版本模型上线前都用混淆矩阵做一次人工巡检眼见为实再谈部署。这套流程不一定是最时髦的却是在医疗数据上最不容易翻车的做法。希望这些从数据、结构与坑里挤出来的经验能帮你在自己的数据集上少走几步弯路。本文还有配套的精品资源点击获取