
简介这份PDF面向模式识别、机器学习课程学习及课程设计人群围绕基于贝叶斯定理的糖尿病检测系统展开重点解决如何用朴素贝叶斯与高斯概率完成患病预测、模型评估和结果展示的问题。内容结合皮马印第安人糖尿病数据集梳理数据载入与拆分、按类别汇总特征、均值方差标准差计算、条件概率连乘、准确率评估及图表分析等环节。资源包共1个PDF文件大小约771KB篇幅集中便于快速查阅与对照复现。文中给出程序流程图、主要函数说明如loadCsv、SplitDataset、calculateProbability等和核心代码片段也呈现发病人数与部分参数关系的可视化结果。已有237人学习下载适合需要完成相关大作业、实验报告或入门朴素贝叶斯分类实践的读者参考。1. 从一份体检指标表说起这套糖尿病检测系统到底解决什么问题拿到一份基于贝叶斯定理的糖尿病检测系统的设计与实现题目多数人第一反应是翻教材抄贝叶斯公式但真正卡住进度的是三个工程判断九个数值型生理指标该用什么分布去拟合、训练测试怎么切分才不会被随机性骗过去、手上的两个数据集谁负责分类谁负责解释。课堂上写的是 P(C|X) ∝ P(C)·∏P(xᵢ|C)落到代码里就变成一串要自己兜底的细节——标准差为零怎么处理、标签列什么时候从均值计算里剔掉、概率连乘到第几位开始下溢。这套方案给出的是一份 Python PyCharm 的单机实现高斯朴素贝叶斯完成患病二分类线性回归配 5×2 子图把单个指标与病情进展的关系摊开给人看。它适合要交模式识别、机器学习课程设计的学生照着搭也适合想拿一个能跑通的最小闭环去改造成其他二分类筛查场景的从业者。2. 需求与技术选型两类任务、两个数据集、一条可复现主流程先把题目里混在一起的两件事拆开后面所有的代码结构才有依据。这个项目表面上叫检测系统实际交付物包含两个目标不同的子任务它们在数据、算法、评价指标上都不一样硬塞进一个函数里就会变成一锅粥。2.1 需求拆解分类预测与因素分析是两件不同的事第一个子任务是二分类预测。输入是就诊者的若干项生理指标输出是 0 或 1——0 表示五年内未发病1 表示五年内发病。这是一个典型的监督学习分类问题评价指标是准确率也就是预测正确的样本数除以测试集样本总数再乘 100%。它关心的是这一次判对了没有模型内部的概率大小本身不重要只要最大概率对应的类别正确即可。第二个子任务是因素分析。它不预测任何东西只回答哪个指标和病情进展关系更紧。做法是对每个特征单独建一个一元线性回归看拟合直线的斜率方向、看决定系数 score 的高低再用散点图叠加拟合线的方式呈现出来。评价指标是拟合优度不是准确率。这两个任务共用一份数据来源但用的是不同视角。分类任务需要离散标签回归任务需要连续目标值。如果不先把这个边界划清楚后面就会出现拿回归的 target 去算分类准确率这种事一跑就报错或者更糟——不报错但结果全是错的。我一般会在需求文档第一页就写出这两行分类主任务负责判不判病可视化辅任务负责为什么这么判。2.2 技术选型为什么是高斯朴素贝叶斯而不是逻辑回归或决策树朴素贝叶斯在这个场景里有三个别人替代不了的优势。第一是样本量友好。皮马印第安人数据集只有几百条记录逻辑回归要拟合九个系数在这么小的样本上很容易过拟合交叉验证的方差会大得让你怀疑人生。朴素贝叶斯要估的参数是每个类别下每个特征的均值和标准差总共 2×9×2 36 个标量样本再少也撑得住。第二是可解释性。分类结果能直接拆成这个人的 BMI 在发病组的高斯分布下似然是多少、在未发病组下似然是多少答辩时被问为什么判他发病你能指着概率逐项讲而不是只能说模型输出 0.83。决策树虽然也好解释但树的结构对数据扰动很敏感换个随机种子可能就从七层变成十二层。第三是特征独立假设在这份数据上没那么致命。朴素贝叶斯的核心假设是给定类别后各特征相互独立现实中 BMI 和皮褶厚度肯定相关违反假设会让概率估计有偏。但分类只取最大概率的类别不要求概率值本身校准所以偏差常常被取最大这一步吸收掉。这也是为什么一个假设明显不成立的模型在中小规模表格数据上依然能打出可用的准确率。代价要说清楚高斯朴素贝叶斯假设每个特征在类别内服从正态分布。年龄、血压这类指标大致对称还能接受胰岛素、血糖这类明显右偏的指标就勉强了。如果要往上做正确的方向是对右偏特征做对数变换而不是换模型。选 Python 是因为整个链路——读 CSV、算统计量、训练模型、画图——都能在一个语言里完成选 PyCharm 社区版是因为断点调试比命令行直观尤其是在逐条检查 dataset 内容的时候。2.3 数据集对齐皮马印第安人数据与 442 条回归数据的边界原始资料里有个很容易被忽略的矛盾一处说数据集是442 个包含皮马印第安人医学细节的观察对象包括年龄、性别、BMI 和血清化验的九项指数以及一年后的病情发展情况另一处的代码又明确调用了sklearn.datasets.load_diabetes()。这两句话描述的不是同一份数据。load_diabetes()返回的是 442 条、10 个特征age、sex、bmi、bp、s1~s6、目标值是基线后一年疾病进展的连续定量指标。它是一个回归数据集没有 0/1 类别列八个特征也不含皮褶厚度胰岛素这些皮马数据的典型字段。而真正的皮马印第安人糖尿病数据集是 768 条、8 个特征加 1 个 0/1 标签。按这个项目场景下合格负责人的最可能做法是把两套数据各归其位用途数据集样本量特征标签/目标算法患病二分类皮马印第安人糖尿病数据数百条8 项数值指标0/1 类别高斯朴素贝叶斯指标-进展关系可视化sklearn 内置回归数据442 条10 项数值特征连续进展值一元线性回归这么分的好处是两边都不别扭分类任务拿到真正的离散标签准确率才有意义可视化任务拿到连续目标拟合直线才画得出来。做方案时把这张表放进设计文档答辩时被问到你到底用了几个数据集一句话就能答清楚不至于当场翻车。3. 总体设计与数据结构从 CSV 到概率字典的六步链路整体流程在原始资料的程序流程图里已经定了形载入数据 → 拆分训练集与测试集 → 按类别计算均值方差标准差 → 计算每类概率并预测 → 图表显示与准确率评估。这五步画在图上很清爽但每一步之间传的是什么形状的数据、字段顺序怎么约定才是决定代码能不能一次跑通的关键。3.1 六步链路与模块职责划分把流程图细化成可落地的模块得到下面这条链路load_csv()读文件把字符串转成 float返回二维列表dataset每行形如[x1, x2, ..., x8, label]。split_dataset()按比例随机切成trainingSet和testSet两者结构完全一样。separate_by_class()把训练集按标签值分组返回{0.0: [[...], ...], 1.0: [[...], ...]}。summarize_by_class()对每组算每个特征的均值和标准差返回{0.0: [(mean1, std1), ...], 1.0: [...]}也就是后面反复用到的 summaries。predict()对一条测试样本用 summaries 算两类的高斯似然连乘取大的那个。get_accuracy()批量预测测试集对比真实标签算百分比。这条链路有个隐藏约束第 3 步到第 4 步之间标签列必须被剔掉。因为特征只有 8 个如果 summaries 里多算了第 9 列标签本身的均值方差第 5 步按索引取参数时就会把标签当成特征参与概率计算准确率会莫名其妙地虚高——这就是典型的测试集泄漏。模块职责要守住一条加载层只负责格式转换不做任何业务判断统计层只输出数值不做预测预测层不碰文件。这样任何一层出问题都能单独拿一份小样本喂进去验证。3.2 数据在内存里的形态列表、字典与元组的三次变形原始实现用的是最朴素的 Python 容器没有 NumPy 数组这在小数据量下反而是好事——结构一眼看得穿。数据在三个阶段的形态分别是阶段一二维列表dataset[i][j]表示第 i 条记录的第 j 个字段最后一列是标签。行数等于样本数。阶段二字典套列表separated[classValue]是一个列表里面装的是属于该类的所有完整记录含标签。字典的 key 是浮点数0.0和1.0因为 CSV 读进来统一转成了 float。阶段三字典套元组列表summaries[classValue][j]是一个(mean, stdev)二元组长度等于特征数。这三个形态的字段顺序必须始终一致。最容易出问题的是阶段二到阶段三的转换zip(*dataset)会把每一列打散成元组最后一组就是标签列必须显式删除。原始实现用del summaries[-1]干这件事它依赖标签永远在最后一列这个约定CSV 的列顺序一旦调整这行代码就静默出错——不报异常只是准确率掉下去。3.3 关键参数与输入输出约定把需要手工调的参数集中列出来避免散落在代码各处参数典型取值作用调整影响split_ratio0.67 ~ 0.9训练集占比越高训练样本越足测试样本越少准确率方差越大random_seed固定整数随机拆分种子不固定则每次结果不同答辩无法复现特征数8参与概率计算的特征列数必须与 CSV 列数减一相等标准差分母n−1样本标准差用 n 会让小样本类别低估离散度类别标签值0.0 / 1.0二分类取值必须是数值型字符串需先映射输入约定上明确一条CSV 无表头全部为数值最后一列为标签缺失值在预处理阶段处理掉不允许空字符串进入float()。输出约定是predict()返回单个标签值get_predictions()返回与测试集等长的标签列表get_accuracy()返回 0~100 的百分比数值。提示标准差用 n−1 还是 n在样本量几百的时候差别不到 1%但当某个类别下只有个位数样本时n 会让方差被系统性低估高斯曲线变尖尾部概率算出来偏小。稳妥做法是统一用 n−1。4. 核心实现高斯概率、连乘与预测的完整代码落地这一章是整份方案的主体把六个函数逐个写出来每个函数后面说清楚它为什么这么写、参数怎么改、跑不通的时候看哪里。4.1 数据加载与数值化把格式问题挡在入口# -*- coding: utf-8 -*- import csv import random import math FEATURE_NUM 8 # 皮马数据集的 8 个生理指标 LABEL_INDEX -1 # 约定标签永远在最后一列 def load_csv(path): 读取 CSV返回全部为 float 的二维列表。 每行格式: [x1..x8, label]label 取 0.0 或 1.0 with open(path, r, encodingutf-8, newline) as f: rows list(csv.reader(f)) dataset [] for row in rows: if not row: # 跳过文件末尾的空行 continue dataset.append([float(x) for x in row]) # 字符串统一转数值 return datasetwith语句保证了文件句柄被关闭这一点比原始实现里直接csv.reader(open(...))要稳后者在异常路径下会留下未关闭的句柄长时间的批量跑图容易出问题。newline是 csv 模块的官方建议能避免 Windows 下换行符被重复处理导致多出空行。float(x)那一行是唯一可能抛异常的地方遇到空字符串或中文表头就会在这里炸掉——所以文件里不能有表头缺失值必须在入库前补成合理的数值或整行剔除。路径建议写成相对路径./data/pima.csv不要写死盘符。原始资料里的绝对路径换一台机器就跑不动了这是课程设计里最常见的在我电脑上是好的事故。4.2 按类拆分与统计汇总zip 转置与标签剔除def split_dataset(dataset, split_ratio, seed42): 按比例随机划分为训练集与测试集 random.seed(seed) # 固定种子保证结果可复现 train_size int(len(dataset) * split_ratio) train_set, copy [], list(dataset) # copy 是待抽取的全集 while len(train_set) train_size: index random.randrange(len(copy)) train_set.append(copy.pop(index)) # 抽走一条就少一条不会重复 return train_set, copy # 剩下的就是测试集 def separate_by_class(dataset): 按标签把记录分组: {0.0: [...], 1.0: [...]} separated {} for row in dataset: label row[LABEL_INDEX] separated.setdefault(label, []).append(row) return separated def mean(numbers): return sum(numbers) / float(len(numbers)) def stdev(numbers): avg mean(numbers) variance sum([(x - avg) ** 2 for x in numbers]) / float(len(numbers) - 1) return math.sqrt(variance) # 分母用 n-1样本标准差 def summarize(dataset): 把同一类别的每一列压成 (均值, 标准差)剔除标签列 summaries [(mean(col), stdev(col)) for col in zip(*dataset)] del summaries[LABEL_INDEX] # 关键去掉最后一列标签 return summaries def summarize_by_class(dataset): separated separate_by_class(dataset) return {label: summarize(rows) for label, rows in separated.items()}zip(*dataset)是这段代码里最巧也最脆的一步。它把 N 行 M 列的列表转置成 M 个长度为 N 的元组每个元组就是某一列的全部取值于是mean和stdev可以直接对整列生效不需要双重循环。代价是列顺序被硬编码进逻辑del summaries[-1]之所以成立完全依赖标签在最后一列。我一般会在 summarize 里加一句断言兜底assert len(summaries) FEATURE_NUM, 特征数与预期不符检查 CSV 列顺序这句话看着啰嗦但它能在列顺序错位的瞬间把问题暴露出来而不是等到准确率算出来只有 51% 的时候再去逐个函数排查。split_dataset里的seed参数是必须的。不固定种子每次运行切出来的测试集都不一样准确率会在几个百分点之间来回跳答辩现场演示两次结果不同很难解释。固定之后同一个人在同一台机器上跑出来的数字完全一致这是可复现的最低要求。4.3 高斯概率密度与类别概率连乘def gaussian_probability(x, mu, sigma): 一维高斯概率密度: 1/(σ√(2π)) · exp(-(x-μ)²/(2σ²)) if sigma 0: # 防止除零该特征在类内是常数 return 1.0 if x mu else 0.0 exponent math.exp(-((x - mu) ** 2) / (2 * sigma ** 2)) return (1.0 / (math.sqrt(2 * math.pi) * sigma)) * exponent def class_probabilities(summaries, input_vector): 返回 {类别: 该样本属于此类的似然} probabilities {} for label, class_summaries in summaries.items(): prob 1.0 # 连乘从 1 起手不能是 0 for i, (mu, sigma) in enumerate(class_summaries): prob * gaussian_probability(input_vector[i], mu, sigma) probabilities[label] prob return probabilities def predict(summaries, input_vector): 取似然最大的类别 probs class_probabilities(summaries, input_vector) best_label, best_prob None, -1.0 for label, prob in probs.items(): if best_label is None or prob best_prob: best_label, best_prob label, prob return best_labelgaussian_probability里那个sigma 0的分支不是防御性编程的装饰是真会遇上的情况。如果某个类别下某个特征的所有取值完全相同比如清洗后某批数据的某个字段被统一置零标准差就是 0公式里的除法直接抛ZeroDivisionError。当时的处理是退化成一个指示函数值相等给概率 1不等给 0。这在数学上不完全严谨但能保证程序不中断同时提示你该去看数据了。关于贝叶斯公式的分母probabilities里存的只是似然连乘没有乘先验概率 P(C)也没有除以证据 P(X)。这在实现上是可接受的简化因为 P(X) 对两个类别是同一个常数比较大小的时候会被约掉先验不乘则隐含假设两类先验相等。如果数据集正负样本严重失衡比如发病的只有 15%正确做法是把先验乘回去# 训练阶段记录先验 priors {label: len(rows) / len(training_set) for label, rows in separated.items()} # 预测阶段乘上 prob * priors[label]这一段在原始实现里没有但在样本不均衡的数据上加上它和不加上它准确率的含义完全不同。还有一个隐患是连乘下溢。8 个概率密度相乘单个密度值通常在 0.01~0.1 量级连乘后可能小到 1e-20 以下超过 float 的表示下限就变成 0两个类别都变成 0 时predict会退化成返回第一个标签。扛得住 8 个特征扛不住 50 个。真要扩特征正确姿势是取对数log_prob math.log(gaussian_probability(...) 1e-300) # 加法代替乘法答辩时如果被问到朴素贝叶斯有什么工程缺陷把下溢和零概率这两个点讲出来比背公式有说服力。4.4 批量预测与准确率评估def get_predictions(summaries, test_set): return [predict(summaries, row) for row in test_set] def get_accuracy(test_set, predictions): correct sum(1 for i in range(len(test_set)) if test_set[i][LABEL_INDEX] predictions[i]) return correct / float(len(test_set)) * 100.0 def confusion_matrix(test_set, predictions): 返回 (TP, TN, FP, FN)用于补充准确率之外的指标 tp tn fp fn 0 for i, row in enumerate(test_set): real, pred row[LABEL_INDEX], predictions[i] if real 1.0 and pred 1.0: tp 1 elif real 0.0 and pred 0.0: tn 1 elif real 0.0 and pred 1.0: fp 1 else: fn 1 return tp, tn, fp, fn主流程串起来只有五行if __name__ __main__: dataset load_csv(./data/pima.csv) train_set, test_set split_dataset(dataset, 0.67, seed42) summaries summarize_by_class(train_set) preds get_predictions(summaries, test_set) print(Accuracy: %.2f%% % get_accuracy(test_set, preds))split_ratio取 0.67 是原始资料的取值训练集约三分之二。这个比例在几百条样本上偏保守模型见过的数据不够多准确率会低估。我在后续版本里改成 0.8测试集缩小到一百多条准确率的方差变大但训练更充分。两者的取舍写在文档里就行没有绝对对错。confusion_matrix是我额外加的因为只看准确率会被数据不均衡骗过去。如果数据里 65% 是未发病那么一个永远输出 0的傻瓜模型也有 65% 的准确率朴素贝叶斯如果只跑到 70%看起来比基线高不了多少。把 TP/TN/FP/FN 打出来再看召回率 FN/(TPFN)才知道模型是真的在识别病人还是主要在猜多数类。4.5 特征与标签关系可视化一元回归拟合与 5×2 子图分类负责判,可视化负责解释。第二个任务的代码独立成一个文件用 sklearn 内置的回归数据集跑import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import load_diabetes from sklearn.linear_model import LinearRegression raw load_diabetes() # 442 条10 特征目标为连续进展值 df pd.DataFrame(raw[data], columnsraw[feature_names]) target raw[target] plt.figure(figsize(12, 25)) # 2 列 5 行画布提前撑开 for i, col in enumerate(df.columns): x df.loc[:, col].values.reshape(-1, 1) # 一维 Series 必须 reshape 成二维 model LinearRegression() model.fit(x, target) score model.score(x, target) # 单特征拟合优度 R² ax plt.subplot(5, 2, i 1) ax.scatter(x, target, s8, alpha0.6) # 散点原始样本 k, b model.coef_[0], model.intercept_ # 斜率与截距 xs np.linspace(x.min(), x.max(), 100) ax.plot(xs, k * xs b, cred) # 拟合直线 ax.set_title(%s : R2%.3f % (col, score)) plt.tight_layout() plt.show()reshape(-1, 1)这一步是新手最容易漏的。df.loc[:, col]取出来的是一维 SeriesLinearRegression.fit要求输入是二维(n_samples, n_features)不 reshape 会直接抛ValueError: Expected 2D array。把 reshape 写成一列数据的标准化动作后面每画一个特征都照做一遍。plt.subplot(5, 2, i1)里的三个数字分别是行数、列数、序号10 个特征正好铺满 5 行 2 列。子图序号从 1 开始i1不能写成i。拟合结果的解读要谨慎。单特征回归的 score 是 R²表示这一个特征能解释目标值多少比例的方差。图上能看到 BMI 对应的 R² 明显高于其他特征散点围绕拟合线更紧性别特征的散点几乎是两条竖线取值只有两档拟合出来的直线斜率接近 0说明在这份数据里它对进展值几乎没有线性解释力。这就是原始资料里和性别无关、和 BMI 关系很大这个结论的代码依据。但要说清边界单特征 R² 低不代表该特征不重要只代表它和目标之间不存在强的线性关系也可能存在非线性关系或者它的作用被其他特征吸收了。方案的措辞应该是在该数据集上线性视角下 BMI 与进展值的关联最强而不是糖尿病只和 BMI 有关。5. 避坑与排查这个项目里最容易翻车的五个点下面五条都是这类小系统上真实会撞上的按现象 → 原因 → 解决写清楚遇到时对着查。5.1 准确率每次运行都不一样来回跳好几个百分点现象同一份数据、同一份代码连续跑三次输出分别是 72.4%、68.1%、75.3%文档里不知道该写哪个数。原因split_dataset用random.randrange抽取训练集没有固定随机种子。每次进程启动Python 的随机数生成器用系统时间初始化切出来的训练集和测试集完全不同。小样本下测试集只有一百多条换一条记录就可能让准确率变动 1%。解决在拆分函数入口加random.seed(seed)把 seed 作为参数传进去默认值写一个固定整数。同时在结果输出里带上seed和split_ratio让任何人都能复现。如果要写模型准确率约 70%正确做法是跑 10 次不同 seed报告均值±标准差而不是挑一个最好看的写进去。5.2 程序在计算概率时抛 ZeroDivisionError现象gaussian_probability执行到1 / (math.sqrt(2*math.pi) * sigma)时崩溃或者抛出math domain error。原因某个类别下某个特征的所有取值完全一致样本标准差算出来是 0。这在真实数据里不罕见——某批记录里某个字段被统一填成 0 或统一填成同一个默认值或者切分后某个类别的样本只剩几条且刚好该字段相同。解决在gaussian_probability入口判断sigma 0退化成指示函数值相等返回 1否则返回 0同时在summarize里打印出标准差为 0 的特征索引提示回去查数据。根本性的修法是检查数据源把常量列从特征里剔除——它对分类没有任何信息量。5.3 准确率异常高接近 95% 以上现象模型在测试集上跑出 95% 甚至 99% 的准确率比文献里的结果高出一大截看起来很美好。原因标签列没有被正确剔除参与了均值方差计算和概率连乘。测试样本的标签值本身作为第 9 个特征被送进了模型等于把答案提前告诉它。另一种可能是数据在拆分前就被排序了训练集和测试集分布高度相似。解决在summarize里加assert len(summaries) FEATURE_NUM用特征数卡死。在class_probabilities里检查len(class_summaries)是否等于输入向量的特征维度不等就报错。另外在拆分前先random.shuffle一次全集或者依赖random.randrange抽取本身就带随机性——但绝不能读进来就按顺序切前后两段那份数据大概率是按标签排好序的。5.4 换一台机器就报 FileNotFoundError 或编码错误现象在自己电脑上跑得好好的拷给同学或者交到实验室机器上一运行就找不到文件或者路径里有中文报UnicodeDecodeError。原因文件路径写成了绝对路径绑定了具体的盘符和目录层级。另外读取时没指定编码Windows 默认用 GBK 打开 UTF-8 文件中文或特殊字符会解码失败。解决数据文件放在项目根目录下的data/里代码里统一用相对路径./data/pima.csv。打开文件时显式指定encodingutf-8和newline。项目提交时把data/目录一起带上在说明文档里写清楚运行时工作目录必须是项目根目录。这一条血泪经验值得单列因为答辩前十分钟发现文件读不进来基本就是灾难现场。5.5 把回归数据集的目标值当成分类标签用现象train_test_split之后训练朴素贝叶斯separate_by_class返回的字典里有几百个 keypredict找不到最大概率或者准确率算出来是 0%。原因load_diabetes()的 target 是连续值每个样本的目标值几乎都不同按类分组就变成几百个类别每组只有一条记录标准差算出来全是 0。这份数据根本不能直接做分类。解决分类任务只能用带 0/1 标签的数据回归数据集只用于因子分析和可视化。如果一定要在回归数据上做分类得先人工离散化目标值比如按中位数二分但这会引入阈值选择的额外假设方案文档里必须写明。最干净的做法就是第 2 章那张表——两套数据各干各的活。6. 验收与答辩怎么证明这套系统真的跑通了代码写完只是完成了一半剩下的一半是让别人相信它是对的。交付前必过的验收清单验收项通过标准检查方式环境可复现干净机器上按文档步骤能跑通删掉缓存重装依赖从头执行数据可定位相对路径读取成功换一个工作目录路径测试结果可复现同一 seed 连跑三次输出一致打印 seed 与准确率对照特征数正确summaries 长度等于 8断言不触发指标完整有准确率 混淆矩阵输出 TP/TN/FP/FN可视化完整10 个子图全部出图标题带 R²看图不重叠、不漏子图答辩现场最容易被追问的三个问题提前把答案写进讲稿第一个是为什么用高斯分布。答因为八个特征都是连续数值型朴素贝叶斯在连续特征上的标准做法是假设类内服从正态分布用训练集估出均值和标准差直接代入密度函数。这个假设在年龄、血压上比较贴合在胰岛素这类右偏指标上偏弱属于已知局限。第二个是准确率 70% 算好吗。答要看基线。如果数据里未发病样本占 65%那么全猜 0 就有 65%模型的 70% 只比基线高 5 个百分点这时要看召回率。如果未发病占 50%70% 就是一个明显高于随机的水平。所以报准确率必须带样本分布。第三个是朴素贝叶斯的独立性假设明显不成立为什么还能用。答因为分类只比较两类似然的大小不要求概率值精确校准。独立性假设被违反时概率估计会有偏但取最大这个操作对单调偏差有一定容忍度。这是它在中小规模表格数据上长期可用的原因不代表假设成立。一个小技巧答辩时先演示一次固定 seed 的运行把输出的准确率、混淆矩阵、四张关键子图一次性摆出来然后在讲稿里明确写若改变 seed准确率在 X% 到 Y% 之间波动。主动交代波动范围比被追问出来要体面得多。从那以后我每次交这类模型作业都会在文件头注释里先把三件事写死随机种子是多少、标签在第几列、数据文件从哪个相对路径读。这三行写不下去说明设计还没想清楚代码也不用急着敲。希望帮到你。本文还有配套的精品资源点击获取