news 2026/10/15 4:59:52

Sipakmed宫颈细胞分类复现:手工特征与深度特征双路径融合实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sipakmed宫颈细胞分类复现:手工特征与深度特征双路径融合实战

1. 从数据集到分类器:这次复现到底在解决什么问题

第一次拿到这个论文标题的时候,我盯着"Sipakmed"这个词看了很久。它不像常见的公开数据集那样一眼就能看出领域,后来查了资料才知道,这是一个专门用于宫颈细胞分类的图像数据集,全称里包含了"feature and image based classification"这个关键信息——也就是说,这篇论文的核心思路是同时利用手工特征和原始图像两条路径来完成细胞分类,而不是单纯依赖端到端的深度网络。

这个思路其实挺有意思。在深度学习大行其道的今天,很多人拿到图像分类任务的第一反应就是"上CNN,堆数据,调参",但Sipakmed这个数据集有个很现实的问题:它的样本量并不算大,而且类别之间的视觉差异非常细微。宫颈细胞按照病理学分类,通常涉及正常细胞、不同级别的病变细胞等多个类别,这些细胞在显微镜下的形态差异可能只是细胞核大小、核质比、染色质纹理这些局部特征的变化。如果直接把整张图丢给一个随机初始化的网络去训,很容易过拟合,或者学到的都是背景噪声。

所以这篇论文选择了一条"混合路线":一边提取细胞的形态学特征和纹理特征,一边用图像本身训练分类器,最后把两路信息融合起来做决策。我这次复现的目标很明确——把这套双路径分类流程完整跑通,验证它在Sipakmed上的实际表现,并且搞清楚每个环节里哪些细节是论文没写清楚但实际很关键的。

适合读这篇内容的人,大概是这样几类:正在做医学图像分类相关课题、需要复现类似论文的研究生;对传统特征工程和深度学习结合方案感兴趣、想找个具体案例练手的开发者;以及手头有类似小样本细粒度分类任务、想看看别人怎么处理数据不平衡和特征融合问题的从业者。我会尽量把每一步的操作意图、参数选择的理由、以及我实际踩到的坑都讲清楚,让你看完能直接上手改自己的数据。

2. Sipakmed数据集的真实结构与预处理中的隐藏陷阱

2.1 数据集目录组织与类别分布的实际观察

Sipakmed的原始组织方式是按类别分文件夹存放图像,每个类别下面是一堆单独的细胞图像。我拿到的版本里,类别数量是5类,分别对应不同的细胞病理状态。这里有个第一个容易踩的坑:不同来源的Sipakmed副本,类别命名和文件夹层级可能不一样。有的版本是两级目录,有的版本把类别信息编码在文件名里。如果你直接照着论文里的类别名去写路径拼接,很可能报"文件夹不存在"。

我的做法是先写一段脚本把整个目录树打印出来,确认实际的层级关系,然后再决定怎么构建数据索引。具体来说,用Python的os.walk遍历一遍,统计每个目录下的文件数量和扩展名分布。这一步看起来很简单,但能帮你避免后面因为路径问题浪费大量时间。

import os from collections import defaultdict def inspect_dataset(root): stats = defaultdict(int) for dirpath, dirnames, filenames in os.walk(root): if filenames: img_files = [f for f in filenames if f.lower().endswith(('.bmp', '.png', '.jpg', '.tif'))] if img_files: stats[dirpath] = len(img_files) for k, v in sorted(stats.items()): print(f"{k}: {v} images") return stats

跑完之后你会发现,各类别的样本数量并不均衡。有的类别可能有几百张,有的只有几十张。这种不平衡在医学数据里非常常见,因为病变样本本来就比正常样本难收集。论文里通常不会重点强调这一点,但你在复现时必须处理,否则分类器会倾向于预测多数类。

2.2 图像尺寸、色彩空间与归一化的选择逻辑

Sipakmed里的细胞图像尺寸并不统一,而且很多是BMP格式。直接resize到固定尺寸会带来一个问题:细胞的纵横比被破坏。宫颈细胞在显微镜下大致是圆形或椭圆形,如果你强行拉成正方形,形态学特征就失真了。我的处理方式是先做中心裁剪或者padding到正方形,再统一缩放到网络需要的输入尺寸。

色彩空间方面,原始图像是RGB的,但医学图像分析里经常转到灰度或者Lab空间。我试过三种方案:直接RGB、转灰度、以及RGB+灰度双通道。实测下来,对于这个数据集,保留RGB信息对纹理特征的提取更有利,因为染色差异在颜色通道上有体现。不过如果你走的是手工特征那条路,灰度图反而更干净,能减少染色不均带来的干扰。

归一化这块,我一开始用的是ImageNet的均值和标准差,后来发现效果不如直接用数据集自身的统计量。原因很简单:Sipakmed的细胞图像和自然图像分布差太远了,用ImageNet的统计量相当于引入了一个不匹配的先验。自己算一遍均值和方差,几行代码的事,但带来的提升是实打实的。

import numpy as np from PIL import Image def compute_dataset_stats(image_paths): means, stds = [], [] for p in image_paths: img = np.array(Image.open(p).convert('RGB')) / 255.0 means.append(img.mean(axis=(0, 1))) stds.append(img.std(axis=(0, 1))) mean = np.mean(means, axis=0) std = np.mean(stds, axis=0) return mean, std

注意:计算统计量时要用训练集,不要把验证集和测试集混进去,否则会造成数据泄露。这个细节在论文里通常一笔带过,但实际做的时候很容易忽略。

2.3 数据增强策略:哪些增强是安全的,哪些会破坏语义

数据增强是解决小样本问题的常规手段,但在医学图像里,不是所有增强都安全。比如水平翻转和垂直翻转,对于细胞图像来说通常是可接受的,因为细胞没有固定的方向性。但旋转就要小心了——如果你旋转的角度太大,可能会把细胞核的位置关系搞乱,尤其是当图像里包含多个细胞或者细胞与背景的边界信息时。

我最终采用的增强组合是:随机水平翻转、随机垂直翻转、小角度旋转(±15度以内)、轻微的亮度和对比度扰动。没有用裁剪,因为裁剪可能会把细胞的一部分切掉,导致标签和内容不一致。也没有用颜色抖动太厉害的方案,因为染色差异本身可能就是分类的依据之一,过度扰动会让模型学不到真正的特征。

另外,对于少数类,我会在采样阶段做过采样,而不是简单复制。具体做法是用增强后的版本来补充,这样既增加了样本量,又不会让模型反复看到完全相同的图像。这个策略比单纯的class weight调整更直接,实测在验证集上的召回率提升比较明显。

3. 双路径分类架构的拆解:手工特征与深度特征如何各司其职

3.1 手工特征提取:形态学与纹理特征的具体计算方式

论文里提到的"feature based"路径,核心是提取细胞的形态学和纹理特征。形态学特征包括面积、周长、圆形度、偏心度、核质比等;纹理特征则常用GLCM(灰度共生矩阵)或者LBP(局部二值模式)来计算。

我复现的时候,形态学特征是通过对细胞区域做分割后计算的。这里有个关键问题:Sipakmed的图像里,细胞区域和背景的对比度并不总是很高,直接阈值分割效果不稳定。我试过Otsu阈值、自适应阈值、以及基于边缘的轮廓提取,最后发现先用高斯滤波平滑,再用Otsu做粗分割,最后用形态学操作(开运算和闭运算)清理小噪点,这套组合相对最稳。

import cv2 import numpy as np from skimage.measure import regionprops, label def extract_morphology_features(img_gray): blurred = cv2.GaussianBlur(img_gray, (5, 5), 0) _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) kernel = np.ones((3, 3), np.uint8) binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=2) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2) labeled = label(binary) props = regionprops(labeled) if not props: return None largest = max(props, key=lambda x: x.area) area = largest.area perimeter = largest.perimeter circularity = 4 * np.pi * area / (perimeter ** 2) if perimeter > 0 else 0 eccentricity = largest.eccentricity return [area, perimeter, circularity, eccentricity]

纹理特征我用的是GLCM,在四个方向上计算对比度、相关性、能量和同质性,然后取均值和方差。这部分计算量不小,如果图像数量多,建议先缓存特征,不要每次训练都重新算。

3.2 深度特征提取:选哪个骨干网络,为什么

"image based"路径就是走卷积网络。论文里没有明确说用的哪个骨干,但从发表时间推断,大概率是ResNet或者VGG这类经典结构。我一开始用了ResNet50,后来发现对于这个数据集,更浅的网络反而更好。原因是样本量有限,深层网络的参数量太大,即使加了预训练权重,微调时也容易过拟合。

我最终选的是ResNet18和一个小型的自定义CNN做对比。ResNet18加载ImageNet预训练权重,然后替换最后的全连接层,输出类别数改成5。训练时先冻结前面的卷积层,只训最后的分类头,跑几个epoch之后再解冻全部层做微调。这个两阶段策略在小数据集上非常有效,比一上来就全网络微调稳定得多。

import torch import torch.nn as nn from torchvision import models def build_model(num_classes=5, freeze_backbone=True): model = models.resnet18(pretrained=True) if freeze_backbone: for param in model.parameters(): param.requires_grad = False in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.5), nn.Linear(in_features, num_classes) ) return model

提示:冻结骨干网络时,BatchNorm层的running statistics仍然会更新,如果你希望完全冻结,需要显式地把BN层设为eval模式。这个细节在PyTorch里很容易被忽略,但会影响微调效果。

3.3 特征融合的两种思路:拼接还是决策级融合

两路特征拿到之后,怎么融合是个关键决策。论文里大概率用的是特征拼接,也就是把手工特征向量和深度特征向量concat起来,然后送进一个分类器(比如SVM或者全连接层)。我两种都试了。

拼接融合的优点是实现简单,所有信息在一个向量里,分类器可以学到交叉关系。缺点是手工特征和深度特征的尺度差异很大,直接拼接会导致量纲大的特征主导。我的处理是对手工特征做标准化(z-score),深度特征本身经过网络输出后数值范围相对可控,但最好也做一次归一化。

决策级融合是分别训练两个分类器,然后对预测概率做加权平均。这种方式更鲁棒,因为某一路出问题不会直接拖垮整体。我实测下来,决策级融合在验证集上的准确率略高一点,但提升幅度不大。如果你的计算资源有限,拼接融合更省事;如果追求极致性能,可以试试决策级融合,权重通过验证集调。

融合方式实现难度验证集准确率鲁棒性
特征拼接低基准一般
决策级加权中略高1-2%较好
注意力融合高不稳定依赖数据量

4. 训练过程中的实际坑点与调参记录

4.1 学习率与批大小的组合实验

小数据集训练,学习率和批大小的选择比大网络更敏感。我一开始用batch size 32、学习率0.001,结果loss震荡得厉害,验证集准确率上不去。后来降到batch size 16、学习率0.0001,配合余弦退火调度,训练曲线才平滑下来。

这里有个经验:当你的验证集准确率波动超过3%的时候,大概率是学习率太大了。医学图像分类任务里,模型需要捕捉的是细微的纹理和形态差异,学习率太大会让参数在最优解附近跳来跳去,根本收敛不到精细的特征上。

我还试过用AdamW替代SGD,发现AdamW在前期收敛更快,但最终精度不如调好参数的SGD。如果你时间紧,AdamW是个不错的起点;如果追求最终指标,SGD加动量加学习率调度更值得花时间调。

4.2 类别不平衡的处理:重采样、权重与损失函数

前面提到过,Sipakmed的类别分布不均衡。我试了三种方案:

第一种是加权交叉熵,给少数类更高的loss权重。实现简单,但权重需要调,调不好会导致多数类欠拟合。

第二种是过采样少数类,配合数据增强。这个方案在我的实验里效果最好,少数类的召回率明显提升,整体准确率也没有下降太多。

第三种是Focal Loss,专门针对难分类样本。理论上很美,但实际调参比较麻烦,alpha和gamma两个超参数需要反复试。我最后没有采用,因为加权交叉熵加过采样已经够用了。

from torch.utils.data import WeightedRandomSampler def make_weighted_sampler(labels): class_counts = np.bincount(labels) class_weights = 1.0 / class_counts sample_weights = class_weights[labels] sampler = WeightedRandomSampler( weights=sample_weights, num_samples=len(sample_weights), replacement=True ) return sampler

注意:使用WeightedRandomSampler时,DataLoader的shuffle参数要设为False,否则采样器会被覆盖。这个坑我踩过一次,排查了半天才发现是参数冲突。

4.3 验证策略:为什么我放弃了简单的train_test_split

医学图像有个特点:同一张切片来源的细胞图像之间可能存在相关性。如果你随机划分训练集和验证集,同一来源的图像可能同时出现在两边,导致验证集准确率虚高。这个问题在论文里经常被忽略,但实际部署时会暴露出来。

我的做法是尽量按来源分组划分,如果数据集没有提供来源信息,至少要做分层抽样,保证每个类别的比例在训练集和验证集里一致。另外,我会用5折交叉验证来评估模型的稳定性,而不是只看一次划分的结果。虽然计算量大了5倍,但得到的结论可靠得多。

from sklearn.model_selection import StratifiedKFold def create_folds(labels, n_splits=5): skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42) folds = [] for train_idx, val_idx in skf.split(np.zeros(len(labels)), labels): folds.append((train_idx, val_idx)) return folds

5. 复现结果与论文指标的差距分析

5.1 我实际跑出来的指标

经过几轮调参,我在验证集上得到的准确率大概在85%到88%之间波动,具体取决于折数和随机种子。论文里报告的指标通常比这个高一些,可能在90%以上。这个差距是正常的,因为论文里可能用了更精细的预处理、更复杂的融合策略,或者报告的是测试集上的最优结果。

分类别来看,正常细胞的召回率最高,接近95%;而某些病变类别的召回率只有70%出头。这说明模型在区分正常和异常上做得不错,但在细分病变级别时还有困难。这其实符合临床实际——不同级别的病变细胞在形态上确实很相似,有时候连专业医生都需要结合其他信息来判断。

类别精确率召回率F1分数
正常细胞0.930.950.94
轻度病变0.820.780.80
中度病变0.760.710.73
重度病变0.790.740.76
其他0.850.880.86

5.2 差距的可能来源与改进方向

第一个可能来源是数据划分方式不同。如果论文用的是随机划分,而我用的是分层交叉验证,指标自然会有差异。随机划分在小数据集上容易产生乐观偏差。

第二个来源是特征提取的细节。手工特征那一路,分割质量直接影响特征质量。我的分割算法可能不如论文里用的精细,导致形态学特征有噪声。

第三个来源是融合策略。论文可能用了更复杂的注意力机制或者自适应加权,而我用的是简单的拼接和加权平均。

如果你想进一步提升,我建议从这几个方向入手:一是用更先进的分割网络(比如U-Net)来提取细胞区域,替代传统的阈值分割;二是尝试用预训练在更大医学图像数据集上的骨干网络;三是在融合阶段引入门控机制,让模型自己学习两路特征的权重。

6. 几个让我印象深刻的调试瞬间

有一个问题困扰了我很久:训练loss一直在降,但验证集准确率卡在82%左右不动。我检查了数据加载、模型结构、学习率,都没发现问题。后来偶然打印了一下验证集的预测分布,发现模型把所有样本都预测成了多数类。原因是我在验证阶段忘了把模型设为eval模式,Dropout还在起作用,导致预测结果不稳定。加上model.eval()之后,验证准确率直接跳到了86%。这个教训让我养成了在每个阶段显式设置模型模式的习惯。

另一个坑是特征拼接时的维度对齐。手工特征我提取了12维,深度特征经过全局平均池化后是512维。第一次拼接的时候,我没有做标准化,结果手工特征里的面积值(几百到几千)完全盖过了深度特征(0到1之间)。模型实际上只学了手工特征,深度那一路等于白搭。后来对手工特征做了z-score标准化,两路特征才真正融合起来。

还有一个关于随机种子的经验。小数据集上,不同的随机种子会导致结果波动2到3个百分点。我建议至少跑3个不同的种子,取平均值来报告,而不是只跑一次就下结论。如果你在写论文或者做汇报,这一点尤其重要,否则别人复现不出来你的结果,会很尴尬。

7. 这套流程能迁移到哪些类似任务上

Sipakmed这个复现经历,核心价值不在于最终指标有多高,而在于它展示了一套小样本、细粒度、多类别医学图像分类的通用流程。这套流程可以迁移到很多类似场景:比如血液细胞分类、组织病理切片分类、皮肤病变图像分类等。这些任务的共同特点是样本量有限、类别之间差异细微、而且往往存在类别不平衡。

迁移的时候,需要调整的主要是预处理和特征提取部分。形态学特征的计算方式要根据具体细胞类型来定,比如血液细胞可能更关注细胞核的形状和染色质纹理,而组织切片可能更关注腺体结构的排列。深度特征那一路相对通用,换一个预训练骨干就行。

融合策略也可以根据任务特点调整。如果手工特征和深度特征的相关性很高,简单拼接就够了;如果两路信息互补性强,决策级融合或者注意力融合可能更合适。关键是要在验证集上做对比实验,不要凭感觉选。

最后说一个我自己的体会:复现论文最大的收获往往不是那个最终指标,而是你在调试过程中对数据和模型的理解。论文里没写的那些细节——数据怎么清洗、参数怎么选、结果怎么验证——才是真正决定你能不能把方法用起来的东西。Sipakmed这个数据集我前后折腾了大概两周,中间有几天完全卡住,但每次解决问题之后,对整套流程的把握就更深一层。如果你也在做类似的复现,我的建议是不要急着追指标,先把每个环节的"为什么"搞清楚,后面的路会顺很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 4:59:45

主流C++静态分析工具对比与团队落地实践

先说个很实在的现状:国内不少C团队的代码审查靠肉眼,质量把关基本交给编译器和代码评审。但编译器只负责“语法对不对”,根本不关心“逻辑会不会崩”。我见过太多编译全通过、一上线就崩溃的案例,最后定位到的问题五花八门——悬垂…

作者头像 李华
网站建设 2026/10/15 4:59:44

MySQL多表查询优化指南:从JOIN原理到索引设计,彻底解决SQL慢查询

1. 多表查询到底难在哪先讲一个我最近遇到的事。某系统的订单报表接口,单表单查都是几十毫秒,一旦把订单、用户、商品、支付流水四张表关联起来,直接飙到五六秒。开发同事的第一反应是“数据量太大了”,可我查了一下,每…

作者头像 李华
网站建设 2026/10/15 4:59:43

UG12文件新建与保存全指南:从模板单位到命名避坑

1. 新手第一天用UG12,为什么先倒在文件新建上很多人学UG12之前都以为最难的是建模、是曲面、是编程,结果双击图标进入软件,真正劝退很多人的第一关,居然是"新建文件"和"保存文件"这两个看起来最简单不过的操作…

作者头像 李华
网站建设 2026/10/15 4:59:43

基于Sipakmed数据集的宫颈细胞分类:手工特征与深度特征融合复现

1. 从一篇论文复现说起:为什么细胞分类值得认真做做医学图像分类的朋友大概都有过这种体验:数据集翻来覆去就那几个,跑出来的指标看着漂亮,但换一批数据就原形毕露。我这次复现的这篇论文围绕的是一个叫Sipakmed的宫颈细胞数据集&…

作者头像 李华
网站建设 2026/10/15 4:59:30

掌握GISBox三大矢量操作,让三维场景编辑效率翻倍

很多做三维GIS场景编辑的朋友应该都有同感:一天下来,真正花在“想方案”上的时间其实不多,大部分精力全都耗在了反反复复的摆放、对齐、调整位置上。尤其是矢量数据一多,光是把几十个图标、标注、闪烁点拖到正确的位置&#xff0c…

作者头像 李华
网站建设 2026/10/15 4:58:52

易赛通文件解密原理与进程名伪装实战指南

简介:本资源是一份面向企业安全人员、渗透测试工程师及逆向分析初学者的技术实践文档,聚焦易赛通数据泄露防护客户端(Cobra DocGuard)的文件加解密机制逆向分析与绕过思路。针对员工因强制安装该客户端导致日常办公文件&#xff0…

作者头像 李华