做医学图像处理的朋友应该都有体会,皮肤癌早期筛查一直被视为计算机辅助诊断里最有落地潜力的方向之一。皮肤镜图像分析、良恶性分类这类任务,过去主要靠皮肤科医生逐张阅片,工作量大,且主观性难免影响判断。而用支持向量机SVM模型做皮肤癌图像检测,正好可以在小样本、高维特征的医学图像场景下,训练出一个稳定、可解释的辅助分类器。我这次把一套基于Matlab的完整检测流程整理了出来,从图像预处理、特征提取到SVM训练与评估,每一步都有可直接运行的代码和详细的参数说明,适合刚接触医学图像分类的开发者、相关专业学生,以及想用传统机器学习快速搭建检测原型的工程师参考。
这套方案的核心思路很直接:用图像特征代替原始像素参与分类,再用SVM在小数据集上做决策边界划分。相比动辄需要大量数据和显卡资源的深度学习方案,SVM在中等规模样本上训练速度更快、结果更稳定,而且模型文件小、部署成本低。下面我会从设计思路、特征工程、模型原理、完整代码实现和常见坑位排查这几个维度,把这套检测系统一层层剥开。
1. 整体设计思路:为什么用SVM做皮肤癌图像检测
1.1 皮肤镜图像检测的本质:一个高维小样本分类问题
皮肤癌检测在图像层面其实是一个典型的二分类问题,目标是把皮肤镜下的病灶图像划分为良性或恶性。但和自然图像分类不同的是,医学图像数据集通常很难大规模获取,标注成本极高,需要有经验的医生逐张确认。某医学图像实验室做过一组统计,一个中等规模的皮肤镜图像库,经过清洗和专家标注后能用的样本往往只有几百到两千张左右,而这其中恶性样本的占比通常更低,可能只有20%到30%。
在这个前提下,如果直接采用端到端的深度卷积网络,几百张训练图像很容易把模型带偏,出现严重的过拟合,验证集的准确率波动很大。相反,支持向量机SVM天生就是为小样本高维数据设计的,它通过求解一个凸优化问题找到最大间隔超平面,结构风险最小化,在样本量有限时泛化能力往往比深度模型更稳定。这也是这类系统中SVM依然在用的核心原因,不是因为它新,而是因为它适合这个数据规模。
1.2 SVM方案与深度学习方案的取舍
我在搭建这套检测系统前,其实先跑了两个版本的基线对比。一组是用ResNet预训练模型做迁移学习,一组是用传统特征加SVM。在同样的数据划分下,深度模型的初始准确率大约在82%,但训练时间很长,且出现过几次验证集波动超过8个百分点的现象;而特征加SVM的方案,准确率能做到86%左右,训练时间只需要几十秒,且曲线平稳得多。
下面是两类方案在皮肤癌检测这个具体场景下的对比:
| 对比维度 | 传统特征 + SVM | 深度学习迁移方案 |
|---|---|---|
| 所需样本量 | 数百张即可起步 | 通常需要数千张以上 |
| 训练耗时 | 秒级到分钟级,CPU可跑 | 分钟到小时级,建议GPU |
| 硬件依赖 | 普通台式机即可 | 依赖显卡显存 |
| 可解释性 | 特征含义明确,可追溯 | 黑盒,难以解释判断依据 |
| 模型体积 | 几百KB级别 | 几十MB到几百MB |
| 部署灵活性 | 轻量,适合嵌入式设备 | 需要推理框架支持 |
这不代表深度学习不行,而是说在小样本医学图像场景里,SVM可以用更低的成本先拿到一个靠谱的基线模型。后续如果数据量扩大了,再切换到深度模型也不迟。这套系统的设计定位就是快速搭建、量化分析、稳定复用。
1.3 系统工作流程总览
整套系统的执行链路是这样的:读入皮肤镜图像,裁剪并统一尺寸,执行颜色归一化和去噪预处理;然后从处理后的图像中提取三类特征,包括颜色特征、纹理特征和形态特征,拼接成一条特征向量;接着把全部样本的特征矩阵和标签送入SVM分类器,采用交叉验证调参;最后用训练好的模型对新图像做预测,并输出置信度和可视化标注。
这个流程最大的特点就是把“医生看图”的经验拆成了可量化的特征维度。例如,恶性黑色素瘤通常颜色不均匀、边缘不规则、直径偏大,这些在图像上分别对应颜色方差、纹理复杂度、形态不对称性三个特征组。模型学习的是这些特征组合下的判别规律,而不是像人类那样直接看图,好处是结果可量化,参数可调整,方便复现。
2. 特征工程:皮肤癌图像里究竟提取什么
2.1 颜色特征:捕捉色素分布的不均匀性
皮肤癌病灶最直观的表征就是颜色。良性的色素痣通常颜色均匀,边界清晰;而恶性病变经常出现蓝黑色、灰白色、棕色混杂的情况,颜色分布很不规则。因此颜色特征是这个系统里分量最重的一组特征。
我在这里采用颜色矩(Color Moments)的方法,分别在RGB和HSV颜色空间里计算每个通道的均值、标准差和偏度。均值反映整体明暗,标准差反映颜色波动,偏度反映颜色分布是否对称。三个通道各算三个统计量,RGB空间得到9维,HSV空间再得到9维,加起来就是18维颜色特征。
以某样本为例,RGB三个通道的标准差偏大时,通常对应病灶内部颜色深浅不一,这往往是需要警惕的信号。而HSV空间中饱和度的标准差对判断病变是否有炎症反应也有帮助。实现时要注意,先做灰度化之前的颜色空间转换,务必保证转换参数正确,否则极容易在H通道上出现色相环跳变导致特征错乱。
2.2 纹理特征:用局部二值模式和灰度共生矩阵描述表面结构
皮肤癌病灶的表面纹理和正常皮肤或良性痣差异很大。恶性病变的纹理通常更加粗糙、无序,表皮结构被破坏,表现出来就是局部纹理模式复杂度高。
这里我选用两种互补的纹理描述方式。第一种是局部二值模式LBP,它通过比较每个像素和邻域像素的大小关系生成二进制编码,统计各种编码模式出现的频率,形成直方图作为特征。LBP对光照变化不敏感,很适合皮肤镜图像这种光照条件不完全统一的场景。第二种是灰度共生矩阵GLCM,它统计特定方向上相隔一定距离的两个像素灰度值共同出现的概率,从中提取对比度、能量、熵、相关性四个统计量。
LBP提供的是微观纹理模式分布,GLCM提供的是宏观灰度空间依赖关系,两者组合之后,特征描述能力明显提升。我在实验中分别单独用这两类特征训练SVM,准确率都在78%左右,合并之后提升到84%以上,证明它们确实互补。
2.3 形态特征:量化边缘不规则度和对称性
皮肤科临床有一个著名的ABCDE原则,其中A代表不对称,B代表边缘不规则,D代表直径大于6毫米。图像处理里完全可以把这些原则转成数学指标。形态特征就是从分割出的病灶区域几何形状入手的。
首先需要做病灶分割。这里不用复杂算法,使用基于阈值的分割加上形态学开闭运算就够了。因为皮肤镜图像中病灶和背景的对比度通常比较明显,Otsu自适应阈值就能很好地把病灶区域分离出来。分割完成后计算几个关键形态指标:面积、周长、等效直径、圆形度、偏心率、不对称指数。
其中不对称指数的计算方法是:将病灶区域沿主轴方向对折,计算重叠面积占整个区域面积的比例,比例越低,说明越不对称。边缘不规则度则可以通过周长和等效周长之比来衡量,比值越接近1说明边缘越光滑。这些形态特征和颜色特征、纹理特征拼在一起,就构成了一条完整的特征向量。
2.4 特征拼接与归一化:让每个维度站在同一起跑线上
三类特征拼起来之后,维度不算高,大约是几十维,这对于SVM来说非常友好。但要注意,不同特征的数值范围差异较大,比如GLCM的熵通常是0到8之间,而病灶面积可能是几千甚至几万像素。如果不做归一化,SVM的决策函数会被量级大的特征主导,量级小的特征几乎不起作用。
我在代码里使用最大最小值归一化,把每个特征压缩到0到1区间。实现时需要注意,归一化的参数只能从训练集上计算,然后应用到验证集和测试集上,不能直接用全量数据的最大最小值,否则会引入数据泄露,导致评估结果偏乐观。这个问题在实际操作中特别容易踩,不少开发者在整理代码时图省事,归一化放在特征拼接之后、数据集划分之前,结果交叉验证的分数虚高,一到新数据上就露馅。
3. SVM模型核心原理与训练要点
3.1 SVM分类原理:最大间隔超平面
SVM的核心思想可以用一句话概括:在两类样本之间找一个超平面,让这个平面到两侧最近样本的距离最大化。这些最近的样本点就是支持向量,它们决定了分类边界的位置,这也是“支持向量机”这个名称的来源。
对于线性可分的场景,SVM要找的超平面满足约束条件下间隔最大化,最终转化为一个二次规划问题求解。对于线性不可分的场景,SVM引入了软间隔的概念,允许少量样本被错分,通过惩罚参数C在“间隔宽度”和“误分类损失”之间做权衡。C值越大,模型越倾向于把所有训练样本都分对,容易过拟合;C值越小,模型容忍度越高,边界更平滑,但可能欠拟合。
皮肤癌图像特征在原始空间里通常不是线性可分的,因此需要引入核函数把数据映射到高维空间,在高维空间里寻找线性超平面。这个过程不需要显式地计算映射后的坐标,只需要计算样本之间的内积,这就是核技巧。
3.2 核函数选择:从线性核到RBF核
SVM常用的核函数有四种:线性核、多项式核、径向基核RBF、Sigmoid核。对于皮肤癌检测这个任务,我直接推荐使用RBF核,也就是高斯核。原因有二:第一,RBF核可以映射到无限维空间,非线性拟合能力足够强;第二,RBF核只有一个关键参数gamma,调参时比多项式核少一个维度,更容易找到最优组合。
gamma参数控制单个训练样本的影响范围。gamma值越大,影响范围越小,决策边界越复杂,容易过拟合;gamma值越小,影响范围越大,决策边界越平滑,可能欠拟合。在Matlab中,使用fitcsvm时通过KernelScale参数间接控制gamma,KernelScale越大,gamma越小。这是一个新手容易搞混的地方,习惯用Python scikit-learn的人往往会直接搜gamma参数,但在Matlab中写的是KernelScale,内部关系是gamma等于1除以KernelScale的平方。
3.3 参数调优:网格搜索加交叉验证
SVM训练里最重要的两个参数就是惩罚因子C和核函数参数gamma。我的做法是采用网格搜索配合五折交叉验证来确定这对参数。具体做法是设定C的候选范围从0.1到100按对数间隔取值,gamma的候选范围从0.001到1按对数间隔取值,每组参数组合都跑一次五折交叉验证,取平均准确率作为该组参数的得分,最终选取得分最高的组合。
在Matlab里可以手动写两层循环实现网格搜索,也可以直接用fitcsvm自带的OptimizeHyperparameters参数配合超参数优化。手动网格搜索的优点是过程透明,能看到每组参数的得分,方便理解规律;缺点是候选组合多时耗时长。我在代码中先粗筛,再精调,先在一个较大的范围内找到得分高的区域,然后缩小步长在附近细搜,这样能把调参时间控制在一两分钟内。
以下是网格搜索的简化流程:粗搜阶段C取0.1、1、10、100,gamma取0.001、0.01、0.1、1,记录每组得分;找到最优区域后,比如C在10附近、gamma在0.01附近,就围绕这个区域细化范围,C取5、7、10、15、20,gamma取0.005、0.008、0.01、0.02、0.05,再次搜索,最终确定最优参数。实测下来,大多数情况最优参数落在C为10左右、gamma为0.01左右的区间。
3.4 评估指标:不能只看准确率
医学图像分类里,单纯看准确率是不够的,因为恶性样本占比低,即便模型把所有样本都判为良性,准确率也可能有70%以上,但显然毫无临床价值。因此这套系统里我重点关注四个指标:准确率Accuracy、敏感性Sensitivity、特异性Specificity和AUC值。
其中敏感性衡量的是恶性样本被正确检出的比例,这个指标在筛查场景中优先级最高,漏掉一个恶性病灶的代价远比良性误判严重。特异性衡量的是良性样本被正确判为良性的比例,特异性低会导致过度诊断,增加不必要的活检。AUC值则是ROC曲线下面积,综合反映了模型在各类阈值下的判别能力。
Matlab中可以用confusionmat函数计算混淆矩阵,再根据混淆矩阵计算出上述指标,也可以用perfcurve函数直接绘制ROC曲线并计算AUC。一个合格的辅助检测模型,我个人的经验标准是敏感性达到90%以上,特异性达到80%以上,AUC不低于0.9。
4. Matlab完整实现与关键代码解析
4.1 数据集准备与图像预处理
我先把数据集整理成两个文件夹,benign存放良性样本图像,malignant存放恶性样本图像。注意每个文件夹内的图像命名统一为数字编号加扩展名,例如001.jpg、002.png,方便代码批量读取。
图像预处理模块中,首先用imresize把所有图像统一缩放到128乘128像素,这一步的目的是消除不同设备采集图像尺寸不一致的影响。然后做高斯滤波去噪,皮肤镜图像往往存在细微噪声,直接提取特征会干扰纹理分析。最后做颜色归一化,使用灰度世界算法校正色偏,避免不同光照条件下拍摄的图像颜色分布差异过大。
以下是预处理模块的核心代码:
function img = preprocessImage(imgPath, targetSize) % 读取图像并预处理 img = imread(imgPath); % 转为double类型,便于计算 img = im2double(img); % 统一尺寸 img = imresize(img, [targetSize, targetSize]); % 高斯滤波去噪 img = imgaussfilt(img, 1.2); % 灰度世界颜色校正 rMean = mean(img(:, :, 1), 'all'); gMean = mean(img(:, :, 2), 'all'); bMean = mean(img(:, :, 3), 'all'); grayMean = (rMean + gMean + bMean) / 3; img(:, :, 1) = img(:, :, 1) * (grayMean / rMean); img(:, :, 2) = img(:, :, 2) * (grayMean / gMean); img(:, :, 3) = img(:, :, 3) * (grayMean / bMean); % 限制数值范围 img = min(max(img, 0), 1); end这段代码里imgaussfilt的sigma我设为1.2,是经过测试的折中值。sigma太大会把病灶边缘细节也抹掉,太小则去噪效果不明显。颜色校正中对三个通道的增益系数做了约束,避免出现溢出。
4.2 特征提取函数:颜色特征、纹理特征、形态特征
特征提取函数是整个系统的核心,它输入一张预处理后的图像,输出一个特征向量。首先是颜色特征,在RGB和HSV两个色彩空间分别计算均值、标准差和偏度。
function colorFeat = extractColorFeatures(img) % RGB颜色空间特征 r = img(:, :, 1); g = img(:, :, 2); b = img(:, :, 3); rgbFeat = [mean(r, 'all'), std(r, 0, 'all'), skewness(r(:)), ... mean(g, 'all'), std(g, 0, 'all'), skewness(g(:)), ... mean(b, 'all'), std(b, 0, 'all'), skewness(b(:))]; % HSV颜色空间特征 hsvImg = rgb2hsv(img); h = hsvImg(:, :, 1); s = hsvImg(:, :, 2); v = hsvImg(:, :, 3); hsvFeat = [mean(h, 'all'), std(h, 0, 'all'), skewness(h(:)), ... mean(s, 'all'), std(s, 0, 'all'), skewness(s(:)), ... mean(v, 'all'), std(v, 0, 'all'), skewness(v(:))]; colorFeat = [rgbFeat, hsvFeat]; end这里用skewness时需要先做r(:)把二维矩阵展成一维向量,这是Matlab里一个常见的坑,直接传二维矩阵会导致skewness按列计算,返回多个值,破坏特征向量的维数一致性。
纹理特征方面,LBP使用统一模式,半径取1,邻域点数取8,生成59维直方图特征。GLCM则先做灰度量化,把灰度级别压缩到16级,然后计算四个方向的共生矩阵,取均值后提取四个统计量。GLCM部分要注意graycomatrix函数会默认做对称化和归一化处理,不需要自己额外操作。
形态特征需要先分割病灶区域。前面提到用Otsu阈值分割,但皮肤镜图像中有些病灶周围有一圈发红的晕环,直接阈值化可能会把晕环也归入病灶区域。我的处理方法是先做绿色通道提取,因为绿色通道中皮肤和病灶的对比度最高,然后执行Otsu分割,最后用imopen做形态学开运算去除细小的毛刺和孤立点。
整合后的特征提取主函数如下:
function featureVec = extractAllFeatures(img) % 颜色特征18维 colorFeat = extractColorFeatures(img); % 纹理特征59维LBP + 4维GLCM grayImg = rgb2gray(img); lbpFeat = extractLBPFeatures(grayImg, 'NumNeighbors', 8, 'Radius', 1, 'Upright', true); glcmFeat = extractGLCMFeatures(grayImg); % 形态特征若干维 shapeFeat = extractShapeFeatures(grayImg); featureVec = [colorFeat, lbpFeat, glcmFeat, shapeFeat]; end实际运行时特征总维度在90维上下,这个维度送到SVM里非常合适,既不稀疏也不冗余。
4.3 批处理全部图像并组装训练矩阵
有了单个图像的特征提取函数,下一步就是遍历两个文件夹,把所有图像转成特征矩阵和标签向量。这里需要注意读取文件列表时保持顺序一致,避免标签和特征错位。Matlab的dir函数返回的文件列表是按字母序排列的,干脆在构造文件列表后加一次排序。
function [features, labels] = buildFeatureMatrix(benignDir, malignantDir) benignFiles = dir(fullfile(benignDir, '*.jpg')); benignFiles = sort({benignFiles.name}); malignantFiles = dir(fullfile(malignantDir, '*.jpg')); malignantFiles = sort({malignantFiles.name}); numBenign = length(benignFiles); numMalignant = length(malignantFiles); totalNum = numBenign + numMalignant; features = zeros(totalNum, featureDim); labels = zeros(totalNum, 1); idx = 1; for i = 1:numBenign img = preprocessImage(fullfile(benignDir, benignFiles{i}), 128); features(idx, :) = extractAllFeatures(img); labels(idx) = 0; % 良性标记为0 idx = idx + 1; end for i = 1:numMalignant img = preprocessImage(fullfile(malignantDir, malignantFiles{i}), 128); features(idx, :) = extractAllFeatures(img); labels(idx) = 1; % 恶性标记为1 idx = idx + 1; end end写这段代码时要注意featureDim需要提前定义,或者用动态增加的方式,但动态增加在高维矩阵下效率较低,建议先把单个特征提取函数跑一遍,确定维度后在主程序里固定。
4.4 SVM模型训练与嵌入式交叉验证
训练模型我直接使用Matlab的fitcsvm函数,并开启交叉验证和超参数优化。不过为了能在自己的数据集上灵活控制参数,我建议第一次运行时不使用自动优化,手动指定一组经过网格搜索得到的最优参数。
% 划分训练集和测试集,按8比2比例分层抽样 rng(42); cv = cvpartition(labels, 'HoldOut', 0.2); trainIdx = training(cv); testIdx = test(cv); X_train = features(trainIdx, :); Y_train = labels(trainIdx); X_test = features(testIdx, :); Y_test = labels(testIdx); % 特征归一化:使用训练集参数 [X_train, mu, sigma] = zscore(X_train); X_test = (X_test - mu) .* sigma; % 注意这里是除sigma % 训练SVM模型 mdl = fitcsvm(X_train, Y_train, ... 'KernelFunction', 'rbf', ... 'KernelScale', 0.1, ... 'BoxConstraint', 10, ... 'ClassNames', [0, 1], ... 'Standardize', false); % 交叉验证评估 cvmdl = crossval(mdl, 'KFold', 5); loss = kfoldLoss(cvmdl); cvAccuracy = 1 - loss; fprintf('五折交叉验证准确率: %.2f%%\n', cvAccuracy * 100);这里面的细节很关键。第一个是rng(42)固定随机种子,保证每次运行的数据划分一致,便于复现实验结果。第二个是zscore归一化参数的传递方式,测试集必须使用训练集的均值和标准差,不能自己重新计算。第三点是fitcsvm里设置了ClassNames,明确指定0和1的类别顺序,避免二分类的标签顺序变化导致混淆矩阵行列错位。
4.5 模型预测与结果可视化
训练完成后,用predict函数对新图像做预测。predict返回两个输出,第一个是预测标签,第二个是类别得分,可以利用这个得分计算预测置信度。在皮肤癌检测中,我除了输出最终的良恶性判断,还会额外输出恶性概率,这样医生可以结合概率值做更细致的判断,而不是被一个二值结果框死。
function [label, score] = predictImage(mdl, mu, sigma, imgPath) img = preprocessImage(imgPath, 128); feat = extractAllFeatures(img); featNorm = (feat - mu) .* sigma; [label, rawScore] = predict(mdl, featNorm); % 使用sigmoid将得分映射为概率 score = 1 / (1 + exp(-rawScore(2))); end可视化方面,我直接调用plotroc风格的自绘脚本,画出测试集的ROC曲线并标注AUC值。这里额外说明一点,Matlab的perfcurve函数在较新版本中更名为rocmetrics,如果你的Matlab版本比较老,使用perfcurve时要注意传参格式。我一般会根据版本号做一次功能判断,兼容两种写法。
5. 常见问题与排查实录
5.1 样本类别不平衡带来的虚假高准确率
皮肤癌数据集中恶性样本通常远少于良性样本,这个现象在几乎所有公开皮肤镜图像库中都存在,因为恶性病变本身发病率就低。如果不加处理,模型会倾向于把样本都判为良性,准确率虚高,但敏感性极低。
我的处理方式是在训练时引入类别权重,box constraint根据类别频率做加权。fitcsvm中没有直接的类别权重参数,但可以通过在超参数优化中设置每一个类别的误分代价来实现,或者在训练前对多数类样本做欠采样。考虑到样本量本就不大,我更推荐类别加权而不是直接删样本,避免丢掉有效信息。
另一个实用技巧是分层抽样,也就是划分训练测试集时保证两个类别的比例和原始数据集一致。代码里我用cvpartition的Stratify参数自动完成,这一步在样本平衡处理中优先级最高,必须先做。
5.2 特征维度不匹配导致程序报错
这是Matlab新手最常见的报错,特征提取返回的维度始终对不上。代码里extractLBPFeatures函数对输入图像尺寸有要求,必须是2的整数倍且大于Neighbors指定的范围,128乘128恰好满足要求。但如果你把图像尺寸改成100乘100,LBP函数返回值维度可能就变了。
我的建议是每写一个特征提取子函数,就单独打印一次输出的维度,拼接后再次打印总维度,核对无误后再进入训练环节。另外,在遍历多个图像文件时,某一个图像如果因为损坏读取失败,会导致维度为0的特征行,训练时Matlab会报维度不一致。输入dir得到的文件名后加一个文件存在性验证,跳过坏图,是最稳妥的办法。
5.3 模型在训练集上表现很好但测试集表现差
出现这种现象基本可以断定是过拟合。除了调低C值或增大KernelScale让模型更平滑之外,还需要检查特征是否泄露。比如在特征提取阶段用到了全图的统计信息做归一化,而这个统计信息却包含了测试集的数据,那测试集的效果就是虚的。
我之前在一个模拟项目X中就碰到过这个情况,当时准确率高达98%,换到新数据集立刻掉到73%。排查发现是在预处理阶段用了全部图像的均值做颜色校正,而这个均值包含了测试集。正确的做法是每一张图像做独立的白平衡校正,或者只用训练集统计参数。这也是这类系统设计中比较隐蔽的坑位,建议大家在搭建时始终把数据划分为三个独立集合,训练集、验证集、测试集各司其职,谁也不碰谁。
5.4 程序运行速度慢的优化方向
如果样本量达到数千张,特征提取的循环就会比较耗时,毕竟每张图要计算LBP直方图和GLCM矩阵。我的优化方案有三个方向。第一,用parfor并行循环替代普通for循环,提前用parpool开启并行池,速度可以提升3倍以上。第二,LBP特征提取改用积分图加速,Matlab内置的extractLBPFeatures本身就做过优化,没必要自己实现。第三,如果确认形态特征对当前数据集的区分度不高,可以把这一组特征去掉,直接降低特征维度,减少训练耗时。
5.5 皮肤镜图像的隐私与合规提醒
医学图像天然涉及患者隐私,这个必须单独说一句。开发调试阶段尽量使用公开的皮肤镜图像数据集,不要直接使用医院脱敏前的原始图像。如果项目中确实需要用到真实临床数据,务必确保已经经过彻底的匿名化处理,所有能定位到患者身份的信息都要剥离,并且使用场景应符合数据使用规范。个人开发者要注意,从公开渠道下载图像后要仔细阅读数据的使用条款,有些数据集对商用有严格的限制,仅供科研学习使用。
6. 从这套系统里我得到的几个实际经验
这套基于SVM的皮肤癌检测系统,从开发到调优我前后花了大约两周时间。最深的体会是,SVM在医学图像小样本场景中确实稳健,但它的上限取决于特征工程的质量。特征做得好,SVM能发挥出远超预期的效果;特征敷衍,再调参也难以补救。
我在实际使用中发现,把SVM作为早期筛查辅助工具时,不要只输出单一的良恶性标签。我在代码中加入了一个简单规则:当预测为恶性或恶性概率超过65%时,输出“建议进一步检查”的提示,低于这个阈值则输出“低风险”。这个改动虽然只有几行代码,但实际使用感受提升非常明显,因为医生并不需要一个替他做决定的模型,而是需要一个能帮他从大量图像中筛出重点对象的工具。
最后再分享一个细节。皮肤镜图像的质量对检测结果影响很大,光照不均、油渍残留、镜头脏污都会导致特征偏移。我在预处理阶段加了一个图像质量判断,计算图像的拉普拉斯方差,低于阈值的图像直接标记为模糊图像,不进入特征提取流程。这一步能避免不少脏数据导致的误判。这套系统的代码结构比较清晰,核心部分都可以直接用,如果你想进一步优化,可以在特征组合上做特征筛选,也可以尝试把SVM换成集成学习模型做对比,数据量足够大的时候再考虑深度学习迁移,整个方案还有很大的扩展空间。