☰
从课程资料到医疗机器学习系统设计:样本量、交叉验证与避坑指南
2026/11/3 3:03:16 网站建设 项目流程

简介:面向高校机器学习课程期末大作业与系统设计报告写作需求,这份文档以医疗保健智能系统为主题,围绕膀胱癌诊断场景给出可参考的完整范例。内容从机器学习在医学诊断中的基础切入,梳理影像学、尿细胞学、卡介苗治疗预后及术后预测等应用,并落到数据收集、特征提取与模型选择、实验评估设计三个环节,配有系统示意图与章节化论述,便于读者理解如何把课程知识组织成合理可行的设计报告。压缩包仅含1个docx文件,约29KB,体量轻便,适合查阅报告结构、摘要关键词写法与案例论证思路。文档正文按章节展开,覆盖应用概述、诊断案例与系统设计逻辑,可帮助读者快速搭建不少于1500字报告的写作框架。已有1024人学习,可作为课程作业参考与医疗机器学习选题的入门样本。

1. 一份被当成“作业答案”的机器学习资料:真正能复用的是那五组样本量

翻课程资料的时候,十有八九会碰到这样一份东西:封面写着某高校某学院《机器学习》期末大作业,正文从膀胱癌的影像诊断一路写到尿脱落细胞,中段夹了一份与主题无关的安防系统方案,末尾又拐去农作物产量预测。多数人第一反应是“拼凑货”,翻两页就关掉。但如果手头的任务是“面向医疗保健领域设计一个数据驱动的机器学习系统,覆盖数据收集、数据处理、实验评估三步”,这份资料反而是少数把三步都落到具体病例数上的文档。

它值钱的地方不在那些 95%、97% 的准确率,而在每个案例旁边如实写着的样本量:40 名患者的 160 张 MRI、115 例尿液细胞标本、112 例癌组织的全基因组测序、117 例根治性切除术的临床数据、2111 例术后随访。这几个数字决定了模型能不能照搬、交叉验证该怎么切、报出来的指标能不能站住。下面按“资料里有什么、怎么改成自己的系统设计、哪里会翻车”拆一遍。

2. 拆包:七个研究方向的数据规模与建模路线对照

这份资料的主体是一篇综述性质的长文,把机器学习在泌尿系肿瘤上的应用按“影像、细胞学、灌注治疗预后、术后复发、术后生存”分了若干段。每段的写法很像:先讲临床痛点,再讲研究者收了什么数据、用了什么模型、最后报一个准确率。这种写法在课程报告里常见,但要拿它当自己系统设计的底稿,得先把散落在段落里的关键参数抠出来,重新排成一张能横向比的表。否则你只会记住“准确率 95%”这种孤零零的数字,完全不知道它是在 40 例样本上算出来的,还是 4000 例。

抠参数的时候有个小技巧:先找病例数,再找划分方式,最后才看指标。顺序反过来最容易踩坑,因为指标最醒目,会诱导你以为这套流程可以直接复制。实际上小样本研究的模型结构往往非常朴素,一旦你的数据规模差一个量级,照搬结构只会得到一个过拟合的黑匣子。

2.1 影像学两条线:40 例 160 张 MRI 与 84 例 CT 的分组方式

MRI 那条线的做法是:收集经病理确诊患者的盆腔磁共振图像,取同一角度不同层面的 160 张,先做影像增强,再把图像边界去掉,只保留膀胱区域,然后提取特征,最后送进多层感知机做识别,报出的准确率是 95%。这条流程里最容易被忽略的是“去边界”这一步——膀胱在 MRI 上跟周围脂肪、肠管的灰度接近,不裁 ROI 直接整图入模,模型学到的很可能是扫描设备的视野特征,而不是病灶本身。

CT 那条线更讲究一点:84 个膀胱 CT 影像随机分成两组,每组 42 例,而且两组的肿瘤分级比例相当,用一组训练、另一组测试,然后把两组对调再做一次,用这种交叉验证保证分类器的稳健性。最终任务是把病例分到“小于 T2”和“大于等于 T2”两类,准确度 97%。注意这里的标签不是“有没有癌”,而是“肌层有没有被侵犯”,这是决定要不要做根治性切除的关键分界,临床价值比二分类的“有病/没病”高得多,但样本标注难度也高得多。

这两条线放在一起看,能得出一件事:影像方向的可复现门槛不在模型,而在 ROI 怎么划、灰度怎么归一化。原始资料没有写具体的归一化方式,常见做法是把 ROI 内的灰度线性拉伸到 0-255,或者按同一次扫描的皮下脂肪做参考做相对归一化。我一般会用后者,因为不同设备、不同序列的绝对灰度差异太大,用脂肪做内参能消掉一部分设备差异。

研究方向数据源样本量关键预处理模型报告指标
影像-MRI盆腔磁共振40 例 / 160 张影像增强、去边界、特征提取多层感知机准确率 95%
影像-CT膀胱 CT84 例(42/42)分级比例配平、两组对调分类器 + 交叉验证分级准确率 97%
尿细胞学尿液细胞标本115 例(59 恶性 / 56 良性)形态、密度、染色质计量ANN验证 100%、测试 94.4%
基因表达尿脱落细胞微阵列46 例 / 56000 探针分层聚类 + 有监督筛选14 基因分类器准确率 76%
灌注治疗预后临床 + CD34 表达资料未给总数70/30 反复随机切割ANN(8 个输入)准确率 79%
术后复发癌组织测序112 例遗传规划 + 基因组合投票3 基因 / 5 基因规则敏感性 80.4% / 71%
术后生存临床随访数据117 例、2111 例七种算法横向对比正则化极限学习机 / ANN平均精度 0.8 / 70% 以上

这张表就是你写系统设计报告时最该先画出来的东西。它不需要任何实验结果,纯粹是把已有研究的“输入规模—处理路线—输出形式”摆平,评审看一眼就知道你有没有读懂这些数字的分量。

2.2 尿细胞学与基因表达:115 例的 79/18/18 划分与 56000 个探针怎么降到 14

尿细胞学这条线是整份资料里流程写得最完整的一段。115 例人体尿液细胞标本,其中 59 例经病理诊断为膀胱癌,56 例为良性,从细胞的形态、密度和染色质形态结构里提取计量学特征,然后按 79 例训练、18 例验证、18 例测试切分。训练出的 ANN 在验证组上准确率 100%,在测试组上错 1 例,准确率 94.4%。

这里有两个细节值得抄进自己的报告。第一,它把验证集和测试集分开了,而且两个集合都是 18 例,规模一致。很多课程设计只切训练/测试两份,然后用测试集反复调参,最后报出来的数字是“调参调出来的最优值”,不是泛化性能。第二,它明确写了“随机分为训练组、验证组和测试组”,说明划分是随机的,但没写是否分层。115 例里恶性 59、良性 56,接近 1:1,随机划分出偏斜的概率不高,但如果你的数据是 9:1 的不平衡比例,就必须分层,否则某一折里可能一个正样本都没有。

基因表达那条线的数字更能说明问题:46 例患者,56000 个目标微阵列做统计分析,先按肿瘤负担做分层聚类,再用有监督学习分类,找出 319 个与膀胱癌发生发展相关的基因,交叉验证后筛出 14 个能准确预测的基因,用这 14 个基因构建的分类器准确率 76%。从 56000 降到 14,降维比接近 4000:1,而最终准确率只有 76%,比影像那条线的 95% 低不少。

这不是模型不行,是任务更难:基因表达谱的噪声大、批次效应强,而且 46 例样本撑不起太复杂的决策边界。选 14 个基因而不是用主成分分析压到十几个主成分,理由也很实在——临床要的是“哪几个基因”,不是一个无法命名的线性组合。可解释性在这里不是加分项,是准入条件。

把这条线搬到自己项目里的步骤大致是:先对表达矩阵做标准化(每行基因跨样本 z-score 是常见做法),再用方差过滤去掉低表达基因,然后做单变量检验粗筛,最后用递归特征消除或 L1 正则做精筛,每一步都在训练折内完成。这个“在折内完成”是重点,下一章会专门讲。

2.3 预后与生存预测:8 个输入特征的 ANN 与 3 基因优于 5 基因的反直觉结论

灌注治疗预后那条线,输入是 8 个变量:年龄、性别、肿瘤分期、肿瘤分级、原位癌、肿瘤大小、肿瘤多样性、吸烟,外加 CD34 表达情况,输出是肿瘤是否复发。随机抽 70% 作训练、30% 作验证,对每一个新模型做反复随机切割,每次训练得出一个结论,把预测结果与实际确诊结果对比,统计误差,然后调整神经元和权重进入下一轮,迭代到分类错误最小,最终准确率 79%。

这段描述里“反复随机切割”是个含糊说法,落到代码上一般就是重复分层 K 折交叉验证。它的价值在于:单次 70/30 划分的结果方差极大,换一个随机种子准确率可能掉 10 个百分点,重复多次取均值才能看出模型的真实水平。资料没写重复了多少次,这是它的短板,你自己做的时候至少要写清楚 n_splits 和 n_repeats。

术后复发那条线给出了一个很值得写进报告的反直觉结论:研究者用遗传规划算法对 112 例癌组织的全基因组测序数据训练,识别出 21 个预测复发的基因,然后做组合。5 基因组合的投票算法在训练组和测试组的敏感性分别是 77% 和 69%,特异性 85% 和 62%;3 基因组合的敏感性是 80.4% 和 71%,特异性 90% 和 67%。3 个基因的组合全面优于 5 个基因。

原因不难推:基因越多,组合规则空间越大,在 112 例这种规模上极易过拟合训练集,到了测试集就露馅。这条结论直接对应一个工程常识——特征数量要和样本量匹配,经验法则是每个特征至少对应 10 到 15 个样本,112 例撑 3 到 5 个基因刚好,撑 21 个必然虚高。

术后生存那条线则做了横向对比:117 例临床数据,用七种机器学习方法训练建模,正则化的极限学习机以最快速度达到 0.8 的平均预测精度;另一项研究用 2111 例根治性手术后的患者数据,输入年龄、性别、肿瘤分级分期、淋巴结情况及浸润情况,ANN 预测术后生存率的准确性达到 70% 以上。样本量从 117 涨到 2111,指标反而从 0.8 掉到 0.7,这不是模型退步,是大样本把早期研究里的乐观偏差挤掉了。写报告时把这两个数字并列,本身就是一段有说服力的分析。

3. 数据收集与特征工程:把“年龄、性别、基因、影像”落成一张能跑的表

系统设计报告的第一部分要求写“数据收集”,很多人在这里写成一堆数据来源的名词罗列,读起来像采购清单。合格的写法是把每个字段的来源、类型、缺失情况、入模方式都定下来,让读者相信你真的见过脏数据。这一章按“字段设计—特征提取—标签划分”三段走,每段给一段能直接改改就用的代码。

3.1 三类数据源的字段设计与缺失值处理

医疗数据的三类来源在这里很清晰:结构化临床记录(年龄、性别、分期、分级)、影像资料(MRI/CT)、实验室与分子数据(尿细胞学参数、基因表达量)。三类数据的缺失机制完全不同。临床字段的缺失往往和诊疗路径有关,比如某项检查没做,通常意味着医生判断病情不需要做,这个“没做”本身就是信息;影像的缺失多半是设备或存档问题,属于随机缺失;基因表达量的缺失则可能是探针信号低于检测下限,属于结构性缺失。

把这三类混在一起用同一个均值填充,是最常见的错误。我一般会按下面的方式处理:

import numpy as np import pandas as pd from sklearn.model_selection import train_test_split # 一行一个病例,索引使用脱敏编号,禁止出现姓名、住院号 df = pd.read_csv("bladder_clinical.csv") label_col = "recurrence_5y" # 标签:5 年内是否复发,0/1 drop_cols = ["case_id", "exam_date"] # 标识列与时间戳一律不进特征矩阵 num_cols = ["age", "tumor_size_cm", "cd34_ihc"] cat_cols = ["sex", "stage", "grade", "cis", "multifocal", "smoking"] # 1) 类别列先补缺失指示列,再把缺失填成独立类别 UNK for c in cat_cols: df[c + "_isna"] = df[c].isna().astype(int) df[c] = df[c].fillna("UNK") # 2) 数值列用中位数填充,同样保留缺失指示列 for c in num_cols: df[c + "_isna"] = df[c].isna().astype(int) df[c] = df[c].fillna(df[c].median()) X = df.drop(columns=drop_cols + [label_col]) y = df[label_col].astype(int)

逻辑说明:缺失指示列的作用是把“有没有做这项检查”变成模型可用的信号,而不是让填充值把这个信号抹平。数值列选填充中位数而不是均值,是因为肿瘤大小、CD34 免疫组化评分这类变量通常右偏,均值会被少数极大值拉走。类别列填成独立的“UNK”而不是众数,是为了避免把“未知”强行归到最常见的分级里,那等于凭空造标签。

参数上唯一需要按数据改的是 num_cols 和 cat_cols 两个列表,其余逻辑可以原样复用。如果某一列的缺失率超过 40%,更稳妥的做法是直接整列删掉而不是填充,因为超过这个比例后,填充值和真实值的分布差异会大到影响模型。

3.2 特征提取:影像纹理与细胞形态计量怎么落地

影像特征分三类。一阶统计量描述 ROI 内的灰度分布,包括均值、标准差、偏度和峰度,偏度和峰度对强化程度和噪声特别敏感,是区分良恶性时最常用的一批。纹理特征用灰度共生矩阵,取对比度、能量、同质性、相关性四个量,分别在 0 度、45 度、90 度、135 度四个方向上算,再取均值和标准差做旋转不变的聚合。形状特征包括面积、周长、圆形度、等效直径,对判断肿瘤是否呈浸润性生长有帮助。

细胞形态计量的思路类似,只是把灰度换成光密度:细胞面积、核面积、核浆比、染色质密度、积分光密度。资料里提到的“形态、密度和染色质形态结构的计量学数据”基本就是这几项。

import numpy as np from skimage.feature import graycomatrix, graycoprops def roi_features(img, mask, levels=32): """img: 归一化到 0-255 的灰度图;mask: 勾画的 ROI 二值图""" vals = img[mask > 0] # 只在 ROI 内量化灰度,避免大片背景把灰度直方图拉偏 q = np.clip((vals / 256 * levels).astype(np.uint8), 0, levels - 1) roi = np.zeros_like(img, dtype=np.uint8) roi[mask > 0] = q # distances 取 1 和 2 个像素,兼顾细纹理和稍粗的纹理 glcm = graycomatrix(roi, distances=[1, 2], angles=[0, np.pi/4, np.pi/2, 3*np.pi/4], levels=levels, symmetric=True, normed=True) feats = {} for prop in ("contrast", "energy", "homogeneity", "correlation"): v = graycoprops(glcm, prop) feats[f"glcm_{prop}_mean"] = float(v.mean()) # 四方向均值 = 旋转不变 feats[f"glcm_{prop}_std"] = float(v.std()) # 方向间差异 = 各向异性 feats["int_mean"] = float(vals.mean()) feats["int_std"] = float(vals.std()) feats["int_skew"] = float(((vals - vals.mean()) ** 3).mean() / (vals.std() ** 3 + 1e-8)) return feats

参数说明:levels 控制灰度量化级数,32 级是常用折中,级数越高越能反映细微纹理,但共生矩阵会变稀疏,统计量噪声变大;distances 控制像素对间距,间距越大反映的纹理尺度越粗;angles 取四个方向是为了之后能对方向做聚合,如果你的病灶有明显方向性(比如沿膀胱壁生长),也可以把四个方向的值分别保留而不聚合。

注意:这段函数必须在训练折内对每张图调用,不能先对全数据集提完特征再划分。特征提取本身不含标签信息,看起来不会泄漏,但如果提取过程用到全数据集的灰度统计量(比如按全数据集均值做归一化),泄漏就发生了。

3.3 标签构造与双次切分:70/15/15 的分层抽样怎么写

资料里 115 例分成 79/18/18,比例大致是 69%/15.5%/15.5%。这个比例对小样本是合理的:测试集太小(比如 5%)会导致指标方差过大,太大又会浪费本就稀缺的训练数据。落到代码上,两次 train_test_split 就够,但一定要带 stratify。

from sklearn.model_selection import train_test_split # 第一次切:留出 15% 做最终测试,标签分布必须和总体一致 X_tr, X_te, y_tr, y_te = train_test_split( X, y, test_size=0.15, stratify=y, random_state=42) # 第二次切:从剩下的 85% 里再切出占总体约 15% 的验证集 # 0.15 / 0.85 ≈ 0.176 X_tr, X_va, y_tr, y_va = train_test_split( X_tr, y_tr, test_size=0.176, stratify=y_tr, random_state=42)

逻辑说明:stratify 保证每一份里的正负比例与总体一致,在 115 例这种规模上,不分层很可能出现某一折正样本只有个位数的情况,指标会剧烈抖动。random_state 固定下来是为了让结果可复现,写报告时把这个种子写进去,别人才能验算。第二次切分的 test_size 要用 0.176 而不是 0.15,因为它是相对剩余 85% 计算的比例,这点经常被写错。

缩放、填充、特征选择这些会用到数据分布的操作,全部只能 fit 在 X_tr 上,然后 transform 到验证集和测试集。把 StandardScaler 放在划分之前,是最隐蔽也最常见的一处泄漏,很多报告的指标虚高就是这么来的。

4. 模型选型:为什么这份资料里 ANN 出现四次,而我只建议你在两个位置用它

资料里 ANN 的出场频率极高:影像诊断用它、尿细胞学用它、灌注治疗预后用它、术后生存预测还是用它。这在当年的研究语境下合理,因为多层感知机是当时最容易拿到、也最容易讲清楚的 nonlinear 工具。但把这份资料当模板照抄,很容易得出“医疗 ML 就该用神经网络”的错误结论。模型选型要看的是样本量、特征维度和可解释性要求三者的组合,不是看哪个名词更时髦。

4.1 小样本高维下的取舍:逻辑回归、SVM、随机森林、ANN 的边界

在 100 到 200 例、特征 10 到 30 个这个区间,逻辑回归往往不输任何复杂模型,而且系数直接对应优势比,临床医生看得懂。资料里提到的 8 输入特征预测灌注治疗后复发,就是典型的逻辑回归适用场景,用 ANN 也能跑,但多出来的那点非线性拟合能力很难在 100 多例上体现出来,反而增加了调参负担。

SVM 的优势在于核函数加惩罚因子这套机制对过拟合有一定抵抗力,资料末尾也提到“核函数机制以及惩罚因子能较好地处理模型的过拟合问题”,但同时指出高维数据分析时表现力受限。这句话是准确的:SVM 在特征维度远大于样本量时(比如 56000 个探针、46 例样本),如果不做特征筛选直接上,核矩阵的计算和泛化都会出问题。

随机森林的实用价值在于给出特征重要性排序,这在写报告时很好用,可以直接回答“哪些临床指标最关键”。但在高维稀疏数据上,重要性排序会偏向连续型变量和多类别变量,需要额外做置换重要性校验。

模型建议样本量特征维度容忍度可解释性主要调参项典型问题
逻辑回归100 例以上低维优先高,系数即效应正则强度 C共线性导致系数符号反转
SVM100 至 1000 例中高维中核函数、C、gamma不做标准化直接训练
随机森林200 例以上高维中,靠特征重要性树数量、最大深度高维稀疏时重要性偏斜
ANN500 例以上较稳低维结构化数据低,接近黑匣子层数、节点数、正则、早停小样本必然过拟合

我自己的习惯是:先跑逻辑回归拿一个基线,再跑随机森林看特征重要性,最后才决定要不要上 ANN。如果 ANN 相对逻辑回归的提升在交叉验证下不到 2 个百分点,就不值得为它多写一节调参说明。

4.2 ANN 的超参数:从输入层节点到早停,资料里“调整神经元和权重”到底指什么

资料里描述灌注治疗那段写了“为尽量减少 ANN 误差,神经元和权重需进行重新调整后再进入下一个训练周期;迭代训练重复过程直到分类错误达到最小值”。这句话翻译成工程语言就是:用反向传播更新权重,用验证集监控损失,损失不再下降就停,同时把隐藏层节点数作为超参数在验证集上搜索。

落到 sklearn 上,这段流程一个 Pipeline 就能表达:

from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.neural_network import MLPClassifier # num_aug / cat_aug 是 3.1 节处理后的列名列表(含 _isna 指示列) pre = ColumnTransformer([ ("num", StandardScaler(), num_aug), ("cat", OneHotEncoder(handle_unknown="ignore"), cat_aug), ]) clf = Pipeline([ ("pre", pre), ("ann", MLPClassifier( hidden_layer_sizes=(16, 8), # 两层,节点数约为输入维度的 1/2 与 1/4 activation="relu", alpha=1e-3, # L2 正则,小样本下这是主要的过拟合闸门 learning_rate_init=1e-3, batch_size=16, # 小样本不要用默认的 200 max_iter=2000, early_stopping=True, # 从训练集内部再切一部分做早停监控 validation_fraction=0.1, n_iter_no_change=30, random_state=42, )), ])

参数说明:hidden_layer_sizes 是最关键的一项,两层 16/8 对应输入维度大约 30 的场景,节点数再多就会开始记忆训练集;alpha 是 L2 惩罚系数,1e-3 到 1e-1 之间通常要试三四个值,样本越少这个值应该越大;batch_size 设成 16 是因为默认的 200 会让小样本每轮只更新一两次,收敛极不稳定;early_stopping 打开后会从训练集内部再切 10% 出来监控验证损失,连续 30 轮没改善就停,这比固定 max_iter 靠谱得多。

需要提醒的是,early_stopping 内部切出来的那份数据不能和你自己的验证集混用,否则等于用验证集调了早停轮数,验证集就不再是“没见过的数据”了。三层结构(训练/验证/测试)的意义就在这里。

4.3 交叉验证与重采样:资料里“反复随机切割”怎么实现才算数

资料在灌注治疗那段提到“对每一个新的 ANN 模型进行随机的反复切割,每一个被训练的数据集都得出一个结论”,这个描述对应的标准实现是重复分层 K 折。单次 5 折给出的 5 个分数方差很大,重复 10 次取 50 个分数的均值,标准误能降到原来的三分之一左右。

from sklearn.model_selection import RepeatedStratifiedKFold, cross_validate from sklearn.metrics import make_scorer, confusion_matrix def specificity(y_true, y_pred): tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel() return tn / (tn + fp) if (tn + fp) else 0.0 spec_scorer = make_scorer(specificity) cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=42) scores = cross_validate( clf, X, y, cv=cv, scoring={"acc": "accuracy", "sens": "recall", "spec": spec_scorer}, return_train_score=True, )

逻辑说明:cross_validate 的每一个折都会重新 fit 一遍 Pipeline,包括里面的标准化和编码,所以不存在跨折泄漏。return_train_score 打开后能同时看到训练分和验证分,两者差距过大就是过拟合的直接证据,这比只看验证分有用得多。

参数上,n_splits 在 100 到 200 例的规模上建议取 5,取 10 会让每折只剩十几例,方差反而变大;n_repeats 取 10 已经足够稳定,再往上收益递减而耗时线性增长。留一法在 115 例上要训练 115 次,耗时是 5 折的 23 倍,而方差的改善有限,一般不推荐。

5. 排查与避坑:五处照着资料抄会翻车的地方

这份资料的可读性不错,但它是文献综述,不是实验手册。综述的写法天然会省略掉那些“当时踩过的坑”,而这些坑恰恰是复现时最要命的。下面五条是我照着类似资料做实验时真实翻过车的记录,每条按现象、原因、解决三段写。

5.1 数据泄漏的三个隐蔽入口

现象:交叉验证准确率 92%,换一个随机种子重跑掉到 78%,换一份外部数据直接掉到 60% 附近。

原因一,先对全量数据做了标准化或归一化,再划分训练测试。标准化用到了全体样本的均值和方差,测试集的分布信息提前泄漏进了训练过程。这个错误在影像数据上尤其普遍,因为很多人习惯先把整个数据集预处理成 npz 存下来。

解决:把 StandardScaler、SimpleImputer 全部塞进 Pipeline,让 cross_validate 在每一折内部重新 fit。影像的灰度归一化如果必须用全局参数,参数只能从训练折估计,再应用到其余折。

原因二,把测试集当验证集用。资料里 115 例分成 79/18/18,如果你只有两份,然后用测试集反复挑模型、挑超参数,最后报的那个“测试准确率”其实是验证准确率,乐观偏差可能有 5 到 10 个百分点。

解决:严格三分。超参数搜索在训练集和验证集之间做,测试集只在最后跑一次,跑完不许回头改模型。这一条说起来简单,做起来需要一点自律。

原因三,特征选择做在划分之前。56000 个探针先在全样本上做单变量检验筛出 319 个,再划分训练测试,这时筛选过程已经看过测试集的标签。

解决:把特征选择放进 Pipeline,作为一个 Transformer 步骤,在每一折的训练部分 fit。用 SelectKBest 或 RFE 都可以,关键是位置要对。

5.2 指标误读:94.4% 和 100% 之间差的是一个病例

现象:验证组准确率 100%,测试组 94.4%,看起来是“泛化略有下降”。实际算一下,18 例里错 1 例就是 94.4%,错 0 例就是 100%,两个结果之间只差一个病例。

原因:小样本测试集上,准确率的分辨率极低。18 例的测试集,每次预测变化都对应 5.6 个百分点。这种量级的指标差异完全在随机波动范围内,不能用来比较模型优劣。

解决:小样本必须报混淆矩阵,同时报敏感性和特异性,再给一个置信区间。18 例、17 例正确的情况下,准确率的 95% 置信区间大约从 74% 到 99%,这个区间宽度本身就说明了很多问题。如果两个模型的置信区间大幅重叠,就不要在报告里写“模型 A 优于模型 B”。

另外,类别不平衡时准确率会骗人。假设阳性只占 10%,一个把所有样本都判成阴性的模型准确率是 90%,但它一个阳性都找不出来。医疗筛查场景里漏诊的代价远高于误诊,这时候该看的是敏感性,或者 F2 分数这种偏向召回率的指标。

5.3 文档层面的坑:引用错位与主题拼凑

现象:把资料里的参考文献列表直接搬到自己的报告里,答辩时被问“这篇文献和你的方法有什么关系”,答不上来。

原因:这份资料的参考文献列表和正文论点存在明显错位,多条文献的主题与正文讨论的机器学习方法并不对应,看起来像是从别处整段搬过来的。同时正文中段插入了一份与医疗主题完全无关的安防系统方案,末尾又出现了农作物产量预测的段落,这是典型的拼凑痕迹。

解决:只取它的骨架——三段式系统设计结构、七个研究方向的数据规模对照、每类任务的指标口径——文献自己按主题重新检索。具体做法是拿“bladder cancer machine learning diagnosis”这类关键词去检索近五年的综述,优先选那些明确写了样本量和验证方式的,把样本量抄进自己的对照表里。

注意:资料里出现的具体准确率数字可以作为“已有研究报告值”引用,但绝不能写成“本系统达到”。两者的区别在答辩时一问就清楚。

5.4 复现环境的坑:随机种子与依赖版本

现象:代码在自己的机器上跑出 0.85,换台机器重跑变成 0.79,怎么都复现不出来。

原因:随机种子只设了 train_test_split 那一处,MLPClassifier 的权重初始化、RepeatedStratifiedKFold 的折划分、甚至 OneHotEncoder 的列顺序都可能引入随机性。另外 sklearn 不同版本间 MLPClassifier 的默认行为有变化,比如 early_stopping 的默认值和 n_iter_no_change 的默认值都调整过。

解决:在脚本入口统一设一次全局种子,并在报告里固定写明依赖版本。这两件事花不了五分钟,但能省掉后面所有的扯皮。

6. 从报告到可交付:把 95% 说成一句站得住的话

写到最后一步,很多报告卡在同一个地方:指标算出来了,但不知道怎么表述才不会被追问。我的做法是给自己定三个动作,每次交报告前强制走一遍。

第一个动作,把混淆矩阵贴出来,不要只写准确率。四个格子里的数字比任何百分比都有说服力,读者能自己算出敏感性和特异性。第二个动作,给关键指标配一个自助法置信区间,代码只有几行:

import numpy as np from sklearn.metrics import accuracy_score def bootstrap_ci(y_true, y_pred, n_boot=2000, alpha=0.05, seed=42): rng = np.random.default_rng(seed) y_true, y_pred = np.asarray(y_true), np.asarray(y_pred) n = len(y_true) accs = [] for _ in range(n_boot): idx = rng.integers(0, n, n) # 有放回重采样,样本量与原始一致 accs.append(accuracy_score(y_true[idx], y_pred[idx])) return np.percentile(accs, [100 * alpha / 2, 100 * (1 - alpha / 2)]) # 18 例测试集、错 1 例的情形 y_true = np.r_[np.ones(17), np.zeros(1)] y_pred = np.ones(18) print(bootstrap_ci(y_true, y_pred))

参数说明:n_boot 取 2000 已经足够稳定,再往上精度提升有限;alpha 取 0.05 对应 95% 区间,如果报告要写“敏感性不低于某值”,可以改成单侧区间。这段代码的价值不在算法,而在逼你意识到 18 例样本上的 94.4% 到底有多不确定。

第三个动作,指标选择写成一张对照表,让读者一眼看出你为什么选这个指标:

场景主指标辅助指标为什么不看准确率
恶性/良性二分类,比例接近 1:1准确率 + 敏感性特异性、AUC可以看,但仍需报混淆矩阵
筛查场景,阳性占比低于 20%敏感性、F2特异性、召回率曲线全判阴性也有高准确率
分级/分期多分类宏平均 F1各类混淆矩阵少数类被多数类淹没
生存/复发预测C 指数、校准曲线时间依赖 AUC准确率忽略时间维度

这张表不需要实验结果就能写,但它决定了你后面所有数字的解释框架。

最后是一个习惯问题。我早期的报告里写过“模型准确率达 97%”这种句子,被问了一句“97% 是在多少例上算的”就卡住了。从那以后我每次写指标都强制走一遍:先写样本量,再写划分方式,再写指标定义,最后才是数字。顺序反了,数字就是孤证。

提示:报告里凡出现百分比,前面必须能找到对应的样本量。找不到的,删掉或者补上。

这份资料的用法也就清楚了:把它当一张已发表研究的参数速查表,而不是当一份可以直接交的答案。七个方向的数据规模、每类任务的建模路线、那些看起来漂亮但要打折看的指标,构成了你设计自己系统时最省时间的一块地基。希望帮到你。

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

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

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

立即咨询