简介:面向数据分析课程学习者与需要完成大作业的高校学生,这份红酒数据集分析资料围绕评分影响因素与简单分类展开,适合用作统计建模、回归分析与分类实践的参考案例。压缩包仅含1个docx文档,约202KB,篇幅紧凑,便于直接查阅与二次整理。内容覆盖变量相关性分析、多元线性回归拟合、主成分回归及KNN分类思路,并对挥发性酸度、硫酸盐、酒精等属性与评分的关系作出解释,同时保留模型检验与预测结果,可帮助读者理解从数据清洗、降维到建模评估的完整流程。已有4271人学习下载,适合需要快速搭建分析框架、补充实验报告或对照检查建模步骤的读者参考。
1. 一个“预测差 0.5 分”的开场:红酒数据集分析到底要交付什么
导师验收数据分析大作业时,很少问“你用了什么模型”,更常见的动作是从表里随手挑一条样本,让你把 11 项理化指标代进去算出评分,再跟实际值对一下。红酒数据集分析这类选题翻车往往不在算法新旧,而在链路断掉:数据怎么来的、变量凭什么删、系数怎么解释、分类结果为什么全是 5 分和 6 分,任何一环答不上来,前面做得再花也白搭。这份方案把 11 项理化指标、0 到 10 的感官评分、相关性分析、多元线性回归、主成分回归和 KNN 分类串成一条能复现的线,适合做数据分析大作业、统计学课程设计、机器学习入门项目的同学照着搭结构,也适合要交一份完整建模报告的人拿去做模板。下面从数据准备一路讲到答辩,每一步都给到能直接跑的代码和参数。
2. 数据底座与变量体检:11 项理化指标怎么读、怎么转、怎么删
第 1 章说的那条链路,起点不是模型而是数据。红酒数据集通常一千五百条左右的样本、12 列字段,看起来很干净,实际动手就会发现一堆格式问题:csv 用分号分隔、列名带空格、评分是整数但读进来变成浮点、xlsx 存的时候多出一列无名索引。这些问题不处理,后面 R 里写lm()只会得到一堆莫名其妙的报错。
2.1 从 csv 到 xlsx:先把输入格式固定下来
原始数据一般是 csv,字段之间有的用逗号、有的用分号。R 的 readxl 读 xlsx 比读 csv 稳,尤其是路径里带中文、或者文件编码不确定的时候,所以第一步统一转成 xlsx,顺便把列名改成x1到x11加q这种短名,后面写回归公式能短一大截。
import pandas as pd # 原始文件用分号分隔,不显式指定 sep 的话整行会被读成一列 raw = pd.read_csv("winequality-red.csv", sep=";") # 11 个理化属性 + 1 个感官评分,统一成短列名,R 里写公式更省事 raw.columns = ["x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "q"] # 评分为空的样本直接剔除,评分是标签,用均值填等于伪造数据 raw = raw.dropna(subset=["q"]).copy() raw["q"] = raw["q"].astype(int) # index=False 很关键,否则 xlsx 多出一列无名索引,R 读进来变量数对不上 raw.to_excel("redwine.xlsx", sheet_name="Sheet1", index=False) # 打印形状和评分分布,第一时间确认数据没读歪 print(raw.shape) print(raw["q"].value_counts().sort_index())这段代码里真正容易出问题的两个参数是sep和index=False。前者错了会把 12 列读成 1 列,后者错了会在 xlsx 里凭空多一列Unnamed: 0,等到 R 里read_excel读出来是 13 列,cor()一算就多出一个跟谁都无关的变量,相关性表直接看不了。dropna(subset=["q"])只针对评分列,其他属性缺失要不要处理是另一个话题——在红酒数据里属性缺失通常很少,真有就按行删,别搞均值填补,那会让回归残差看起来漂亮但没意义。
2.2 相关性分析:为什么这里该用 Spearman 而不是 Pearson
评分只有 3 到 8 这 6 个取值,是离散有序变量,Pearson 系数假设两个变量连续且近似正态,套在评分上会把“6 分到 7 分”和“3 分到 4 分”当成等距变化,实际上人对分数的感知不是等距的。Spearman 只看秩序,对单调非线性关系也吃得下,第一步筛变量用它更稳。
install.packages("readxl") library(readxl) # 只读第一个 sheet,col_names = TRUE 表示第 1 行是表头 df <- read_excel("redwine.xlsx", sheet = 1, col_names = TRUE) # 12 列一起算,得到一个 12x12 的相关系数矩阵 cor_mat <- cor(df, method = "spearman") # 点名要最后一列,也就是 q 与各变量的相关系数,保留三位小数 round(cor_mat[, "q"], 3)跑出来的结果大致是这样一条分界线:x4 残糖、x6 游离二氧化硫、x9 pH 值与评分的相关系数接近 0,属于可以先请出模型的变量;x2 挥发性酸度、x10 硫酸盐、x11 酒精的绝对值明显更高,是影响评分的主线。这里要提醒一句,相关系数多少算“低”没有国家标准,我一般按 |r| < 0.1 当作基本无关,0.1 到 0.3 算弱相关,0.3 到 0.5 算中等,但删变量不能只盯这一个数——pH 与评分弱相关,可它和一批酸度指标高度绑定,删它本质上是删一组共线变量里的一员,得放到下一节一起看。
2.3 变量之间也互相牵连:共线性从哪来
只看评分那一列会漏掉一半信息,相关矩阵的非对角线部分才是回归翻车的根源。下面这张表是红酒数据里几乎必然出现的几组关系,换数据集也八九不离十。
| 变量对 | 关系方向 | 强度 | 对建模的影响 |
|---|---|---|---|
| x1 非挥发酸 vs x9 pH | 负相关 | 强 | 同时入模会互相抵消,系数符号容易反 |
| x11 酒精 vs x8 密度 | 负相关 | 强 | 密度被酒精“背着走”,系数不稳定 |
| x7 总二氧化硫 vs x6 游离二氧化硫 | 正相关 | 强 | 前者包含后者,等于一个变量进两次 |
| x2 挥发性酸 vs q | 负相关 | 中 | 评分的主要负向因素 |
| x10 硫酸盐 vs q | 正相关 | 中 | 评分的主要正向因素 |
| x11 酒精 vs q | 正相关 | 中 | 评分的主要正向因素 |
共线性带来的直接后果不是预测变差,而是参数检验失效:模型整体 F 检验很显著,单个变量的 p 值却一大堆不通过,系数估计的方差被放大,样本稍微动一动系数就翻号。这也是为什么后面第 3 章的全变量回归会出现“部分参数没通过检验”——不是变量没用,是它们彼此抢解释权。
2.4 评分分布:后面所有麻烦的源头
把q的频数拉出来看,会发现一个很扎眼的事实:3 分和 8 分都是零星几条,5 分和 6 分加起来占了绝大多数,7 分有一部分,4 分不多。这个分布决定了三件事,做方案的时候必须提前想到。
| 评分 | 大致占比 | 对建模的影响 |
|---|---|---|
| 3 | 极少 | 样本量不足,任何按类评估都不稳 |
| 4 | 少量 | 与 5 分边界模糊,容易被误判 |
| 5 | 约四成 | 多数类,分类器默认往这里靠 |
| 6 | 约四成 | 多数类,与 5 分最难分开 |
| 7 | 一成多 | 少数类,召回率天然低 |
| 8 | 极少 | 基本无法单独评估 |
第一,回归的 R² 天花板不高。因变量本身方差就窄,绝大多数样本挤在中间两档,模型能解释的部分自然有限,0.65 左右的 R² 在感官评分建模里属于正常水平,不该被当成“模型不行”。第二,分类的多数表决会被多数类吸走,KNN 无论输入什么,输出十有八九是 5 或 6。第三,“准确率 70%”这个数字很有欺骗性——全部猜 6 分也能接近这个数,所以后面必须用混淆矩阵和宏平均 F1 补上。把这张分布表放在报告第二节,答辩时被问到“为什么准确率上不去”,翻到这一页就够用了。
3. 多元线性回归:九变量、三变量与两种区间的取舍
相关性筛完,剩下的九到十个变量一股脑塞进lm(),是大多数人写大作业的第一步,也是第一次看到“部分参数未通过检验”的地方。这一章要做的事情很明确:先跑全变量,看清共线性把哪些系数搞坏了;再砍到三个核心变量,得到一个每个参数都显著、方程能写进报告的模型;最后拿一条留出样本做外推,把置信区间和预测区间的区别讲清楚。
3.1 全变量回归:先看清哪些参数是“假的”
去掉 x4、x6、x9 之后,剩下 x1、x2、x3、x5、x7、x8、x10、x11 八个自变量。为什么是这八个而不是九个,取决于第 2 章删了几个,我按最常见的结果写。
# 全变量回归,q 是因变量,后面八个是筛完留下的自变量 lm.sol <- lm(q ~ x1 + x2 + x3 + x5 + x7 + x8 + x10 + x11, data = df) # summary 里重点看四块:系数表、R-squared、F-statistic、残差分布 summary(lm.sol)summary()的输出要看四块。系数表里的Estimate是系数,Pr(>|t|)是单个变量的显著性,很多变量这一列会大于 0.05;Multiple R-squared是整体拟合度;F-statistic的 p 值通常极小,说明模型整体是显著的;最下面的残差五分位数用来粗判正态性。出现“整体显著、单个不显著”这种组合,基本可以判定是共线性在作祟,而不是变量真的没用——这正好是第 2.3 节那张表预言的结果。
这一步在报告里的价值不是给出最终模型,而是留下一个对照:全变量模型 R² 可能略高一点,但参数一堆不显著,没法解释;后面的精简模型 R² 掉一点,但每个系数都站得住。答辩时把这两个模型并排放,比只贴一个模型有说服力得多。
3.2 砍到三个变量:方程怎么写、系数怎么讲
把范围收窄到相关性最高的三个变量 x2 挥发性酸度、x10 硫酸盐、x11 酒精,重新拟合。
# 只保留与评分相关性最高的三个变量 lm.sol2 <- lm(q ~ x2 + x10 + x11, data = df) summary(lm.sol2) # 取出系数,方便写进报告 coef(lm.sol2)结果三个参数全部通过检验,R² 落在 0.6589 附近。方程写成这样:
q = -1.22130 * x2 + 0.67898 * x10 + 0.30920 * x11 + 2.61104系数不能只报数字,得翻译成人话。挥发性酸度的系数是 -1.22130,意思是其他条件不变时,挥发性酸度每上升一个单位,评分平均下降约 1.22 分,这个量级相当大,和酿酒常识对得上——挥发酸高了酒会有醋味甚至洗甲水味,品鉴师一定压分。硫酸盐系数 0.67898,正向,它在酒里承担抗氧化和护色的角色,含量合适时口感更稳定。酒精系数 0.30920,正向但幅度温和,酒精度高一点会让酒体更饱满,但过高会盖住香气,所以系数不该太大。
R² = 0.6589 这个数字要主动解释,别等被问。它说的是:这三个理化指标能解释评分方差的约 66%,剩下 34% 来自品鉴师主观差异、年份、产区、橡木桶、储存条件这些没进模型的变量。感官评分天生带主观噪声,同一个样本给两个品鉴师打分都可能差一分,硬追高 R² 反而是过拟合。把这句话写进报告,比硬凑一个 0.9 的模型更让人信服。
3.3 置信区间和预测区间:两个最容易讲混的东西
留一条样本不参与建模,专门用来验证,是最简单的“外推测试”。取 x2 = 0.31、x10 = 0.66、x11 = 11 这一组,实际评分是 6。
# 构造一条新样本,列名必须和模型里的自变量名完全一致 new <- data.frame(x2 = 0.31, x10 = 0.66, x11 = 11) # 置信区间:响应变量“均值”的区间,回答的是这类酒平均能打几分 lm.conf <- predict(lm.sol2, new, interval = "confidence") # 预测区间:单瓶酒的区间,回答的是这一瓶具体能打几分 lm.pred <- predict(lm.sol2, new, interval = "prediction") lm.conf lm.pred两个函数返回的都是三列:fit是点预测,lwr和upr是下界和上界。区别在于confidence估的是条件均值,区间窄;prediction估的是单个新观测,要把残差方差也算进去,区间明显更宽。下面这张表可以直接放进报告。
| 区间类型 | 参数 | 回答的问题 | 区间宽度 | 典型误用 |
|---|---|---|---|---|
| 置信区间 | interval = "confidence" | 这类酒的平均评分落在哪 | 窄 | 说成“单瓶酒有 95% 概率落在此区间” |
| 预测区间 | interval = "prediction" | 下一瓶酒的评分落在哪 | 宽 | 拿它当模型精度指标 |
实际结果里fit落在 5 分多,与真实值 6 差不到一分,这个偏差在感官评分场景里完全可以接受。报告里要写清楚:点预测 5.x、预测区间覆盖了 6,说明模型没有系统性高估或低估,而不是只写一句“预测准确”。
3.4 回归诊断:四个图各看什么
模型跑完不画诊断图,等于开车不看后视镜。
# 2x2 布局,一次性输出四张诊断图 par(mfrow = c(2, 2)) plot(lm.sol2)第一张残差对拟合值,看有没有喇叭口或者明显的曲线,喇叭口说明异方差,曲线说明该加二次项。第二张正态 QQ 图,点大致贴着直线就还行,两头翘说明残差厚尾。第三张 Scale-Location,看残差是否随拟合值扩散。第四张残差对杠杆值,重点找那些越过库克距离虚线的点,它们对系数影响极大,删掉一个可能系数就变。红酒数据里通常会有几条杠杆值偏高的样本,报告里点出“存在若干强影响点,但剔除后结论方向不变”,比装作没看见强。
4. 主成分回归:五个主成分怎么压住共线性再还原成原变量
第 3 章的做法是“删变量”,代价是丢掉信息。主成分回归走另一条路:不删变量,把 11 个高度相关的指标压缩成几个互不相关的综合变量,用它们做回归,最后再把系数换算回原来的量纲。这条路在报告里显得更“完整”,也更考验推导功底——很多人卡在最后一步还原上,系数算出来对不上,查半天才发现少写了一项。
4.1 princomp 提主成分:cor = TRUE 到底改变了什么
# 读取全部 11 个属性,range 限定数据区域,避免把表头算进去 wine <- read_xlsx("redwine.xlsx", sheet = 1, col_names = TRUE, range = "A1:K1559") # cor = TRUE 表示用相关阵而不是协方差阵,等价于先标准化 wine.pr <- princomp(wine, cor = TRUE) # loadings = TRUE 把载荷一起打出来 summary(wine.pr, loadings = TRUE)cor = TRUE是这一步最关键的参数。红酒数据里密度在 0.99 附近,总二氧化硫在 46 附近,如果直接对协方差阵做分解,方差大的变量会独吞第一主成分,密度这种量纲小的变量几乎不贡献信息。用相关阵等于把每个变量先标准化,量纲问题一次性消掉。这个坑和第 5 章 KNN 里不做标准化是同一个病根,只是换了个位置犯。
看载荷矩阵能读出每个主成分在说什么。第一主成分的表达式大致是z1 = 0.49*x1 - 0.239*x2 + 0.466*x3 + ... - 0.437*x9 + ...,非挥发酸和柠檬酸权重为正、pH 权重为负,这就是一条“酸度轴”:酸越高、pH 越低,z1 越大。前五个主成分一般能覆盖八成以上的方差,用它们做回归既不丢太多信息,又把共线性彻底解决了。
| 主成分 | 主要载荷变量 | 业务含义 | 累积方差贡献 |
|---|---|---|---|
| z1 | x1、x3 正,x9 负 | 酸度与 pH 的综合轴 | 约三成 |
| z2 | x10、x7 正 | 硫化物与抗氧化水平 | 约五成 |
| z3 | x11 正,x8 负 | 酒精与酒体浓度 | 约六成半 |
| z4 | x4、x5 | 残糖与氯化物 | 约七成半 |
| z5 | x2 负 | 挥发酸方向 | 约八成 |
具体数值以实际跑出来的为准,报告里贴表时把载荷大于 0.3 的变量加粗,读者一眼就能看出每个主成分代表什么。
4.2 主成分回归:五个得分当自变量
# 计算每条样本在主成分上的得分 pre <- predict(wine.pr) # 取前五列,也就是前五个主成分得分 z1 <- pre[, 1]; z2 <- pre[, 2]; z3 <- pre[, 3]; z4 <- pre[, 4]; z5 <- pre[, 5] # 注意 q 的取法:必须和 pre 的行数、顺序完全一致 redd <- data.frame(q = df$q, z1, z2, z3, z4, z5) lm.solp <- lm(q ~ z1 + z2 + z3 + z4 + z5, data = redd) summary(lm.solp)这里有一个必须提前确认的点:wine和df必须是同一批样本、同一顺序。wine用了range限定区域,df是整表读入,只要行数或排序有一点差异,q和主成分得分就会错位,回归结果看着正常但其实全乱。我的习惯是先nrow(wine) == nrow(df)断言一次,再往下走。
拟合结果里五个主成分的系数基本都通过检验,原因很直接——主成分之间正交,没有共线性可抢。用一组给定的 z 值代入能算出预测分,和实际值有偏差,这是正常的,主成分回归牺牲了一点解释精度换稳定性。
4.3 还原成原变量系数:特征向量乘系数再除标准差
这一步是整份作业里最容易翻车的地方,公式不长但项数容易漏。
# 相关阵的特征向量,A 的每一列对应一个主成分的方向 XX <- cor(wine) A <- eigen(XX)$vectors # 主成分回归的系数,beta[1] 是截距,beta[2] 到 beta[6] 是五个主成分的系数 beta <- coef(lm.solp) # 各变量的均值与标准差,从数据里算,别手抄 X.bar <- colMeans(wine) X.sd <- apply(wine, 2, sd) # 五组都要加进去,少一组系数就系统性偏小 coef <- (beta[2] * A[, 1] + beta[3] * A[, 2] + beta[4] * A[, 3] + beta[5] * A[, 4] + beta[6] * A[, 5]) / X.sd # 截距还原:原截距 = 标准化截距 - sum(均值 * 原系数) beta0 <- beta[1] - sum(X.bar * coef) c(beta0, coef)推导逻辑写清楚:主成分得分是标准化变量的线性组合,z_i = sum_j A[j, i] * Z_j,其中Z_j = (x_j - mean_j) / sd_j。把它代回回归方程,合并同类项,原变量系数就是“各主成分系数乘对应特征向量分量再求和,最后除以该变量的标准差”,截距再做一次中心化修正。写代码时最容易漏掉第五项beta[6] * A[, 5],只写四项的话,还原出来的系数会整体偏小,而且因为漏的是最后一组,前几个变量偏得还不一样,很难靠肉眼发现。我一般是先手算一个变量的系数对一遍,确认无误再批量跑。
还有一个细节:X.bar和X.sd一定要从数据里现算,别抄别人报告里的常数。不同版本的数据集行数不同,均值和标准差会有小数位差异,抄来的常数值会让还原系数对不上,答辩被追问时说不清。
4.4 两条路线怎么选:报告里该放哪个
| 对比项 | 多元线性回归(三变量) | 主成分回归(五主成分) |
|---|---|---|
| 可解释性 | 强,系数直接对应理化指标 | 弱,主成分是综合轴,要额外解释 |
| 共线性处理 | 靠删变量回避 | 从根上消除 |
| R² | 约 0.66 | 与前者接近或略高 |
| 计算复杂度 | 一行lm() | 需要还原系数,容易出错 |
| 答辩友好度 | 高,方程好讲 | 中,追问多集中在还原步骤 |
我的做法是两个都放:主成分回归证明“我处理了共线性”,三变量回归作为最终解释模型。报告结构上先讲主成分、再讲精简回归,读者会认为你是权衡之后的取舍,而不是不会做才删变量。
5. KNN 分类实现与结果排查:五项属性的距离怎么算、投票为什么总偏向 5 分和 6 分
回归回答“打几分”,分类回答“属于哪一档”。很多人写完回归顺手加个 KNN,觉得算法简单不会出错,实际跑起来准确率低得离谱,改来改去也不知道问题在哪。这一章把 KNN 从头搭一遍,重点放在后半段的排查——准确率上不去的锅,八成不在算法,而在数据预处理和评估方式。
5.1 距离度量选哪五项,标准化为什么不能省
分类用的属性集和回归不完全一样。回归挑了与评分线性相关的 x2、x10、x11,分类则更看整体风味轮廓,通常会再加上 x5 氯化物和 x7 总二氧化硫,凑成五项做距离度量。选五个而不是十一个,理由和降维一样:无关属性会给距离贡献噪声,维度越高,样本间距离越趋同,近邻就失去区分度。
真正要命的是量纲。氯化物在 0.08 上下波动,总二氧化硫在 46 上下波动,两者平方之后再相加,总二氧化硫一项就能把距离完全决定,其他四个属性等于没参与计算。
def standardize(objects): """对前 5 列做 z-score,最后一列 q 是标签,不能动""" n = len(objects) dim = 5 means = [sum(o[j] for o in objects) / n for j in range(dim)] sds = [] for j in range(dim): var = sum((o[j] - means[j]) ** 2 for o in objects) / (n - 1) sds.append(var ** 0.5) # 标准差为 0 的列(常数属性)直接跳过,避免除零 scaled = [[(o[j] - means[j]) / sds[j] if sds[j] else 0.0 for j in range(dim)] + [o[5]] for o in objects] return scaled这段代码做的事情就是让五项属性处在同一个数量级上,距离才有意义。用 numpy 的话一行StandardScaler就够,但手写一遍能看清均值、标准差是从训练集算的——注意是从训练集算,不能拿全体数据算完再切分,那是数据泄漏,评估结果会虚高。
5.2 读取、距离、投票三段代码
读取部分用 openpyxl,列号按 1 基写清楚,避免偏移一位。
import openpyxl def read_excel(input_file_name, objects): """从 xlsx 读取样本,只保留 5 个距离属性和评分 q""" workbook = openpyxl.load_workbook(input_file_name) table = workbook["Sheet1"] rows, cols = table.max_row, table.max_column # 从第 2 行开始,第 1 行是表头;列号 1 基:x2=2, x5=5, x7=7, x10=10, x11=11, q=12 for row in range(2, rows + 1): obj = [] for col in (2, 5, 7, 10, 11, 12): obj.append(table.cell(row=row, column=col).value) if None not in obj: # 有空值的样本直接跳过 objects.append(obj) return objects列号这块是血泪经验。原代码写的是col == 1 or col == 4 or ...配合cell(row + 1, col + 1),0 基索引和 1 基偏移混在一起,只要中间有一列被跳过,取到的就是错位的数据,而程序不会报错,只会安安静静算出一堆错的准确率。改成显式写出 1 基列号(2, 5, 7, 10, 11, 12),一眼能对照表头核验。
距离和投票分开写,逻辑清楚也方便调参。
def knn_distance(now, objects): """欧氏距离:五项属性已标准化,直接算平方和再开方""" return [sum((o[j] - now[j]) ** 2 for j in range(5)) ** 0.5 for o in objects] def classify(distances, objects, k=20): """取最近的 k 个邻居做多数表决,票数相同看谁更近""" order = sorted(range(len(distances)), key=lambda i: distances[i])[:k] votes, nearest = {}, {} for i in order: label = objects[i][5] votes[label] = votes.get(label, 0) + 1 if label not in nearest: nearest[label] = distances[i] # 记录该类别里最近的邻居距离 # 先比票数,票数相同比距离,近的赢 label = max(votes, key=lambda c: (votes[c], -nearest[c])) return label, votesk=20写成参数而不是硬编码在循环里,是这份代码和我见过的多数版本最大的差别。原来那段for i in range(30)配文字里的“取最近 20 个点”,参数和实现根本对不上,改 k 值要动两处,很容易忘。投票部分补了票数相同时的裁决规则,否则max在字典上遇到平票会按插入顺序返回,结果不可复现。
5.3 k 值怎么扫、混淆矩阵怎么看
k 从 1 扫到 30,每个值都跑一次留一法,把准确率画出来,能直观看到“k 太小容易受噪声影响、k 太大退化成多数类”的那条曲线。
def evaluate(objects, k, sample=200): """留一法评估,sample 控制参与评估的样本数,全量跑太慢""" hit = 0 for idx, obj in enumerate(objects[:sample]): train = objects[:idx] + objects[idx + 1:] d = knn_distance(obj[:5], train) pred, _ = classify(d, train, k) hit += (pred == obj[5]) return hit / min(sample, len(objects))只报准确率不够。全部猜 6 分的基线也能到四成左右,所以必须把基线一起报出来,再配一张混淆矩阵看 5 分和 6 分互相误判的比例。如果 7 分样本的召回率接近 0,说明模型根本没有能力区分高分档,这时候要么把标签重新分箱成“低/中/高”三档,要么直接说明“高分段样本不足,模型不适用”,都比硬报一个好看的准确率强。
5.4 五条踩坑记录:现象、原因与解决
一、输入什么样本,输出都是 5 或 6。现象是改十组测试数据,预测结果始终落在两档。原因是评分分布严重倾斜,5 分和 6 分占了七成以上,20 个邻居里多数票必然被这两类拿走。解决办法是分层抽样保证每类都有足够邻居,或者把任务改成三档分类并用宏平均 F1 代替准确率,同时在报告里明说“原始标签分布不均,逐类评估更合理”。
二、换个数据文件,结果全变。现象是同一份代码在别人电脑上准确率相差二十个百分点。原因是列索引硬编码加上偏移叠加,数据文件的列顺序稍有不同就取错列,而且程序不报错。解决办法是用表头名定位列,或者先断言table.cell(1, 12).value == "q"再取数。
三、距离几乎完全由总二氧化硫决定。现象是只改酒精含量,预测结果纹丝不动。原因是没有标准化,总二氧化硫的方差比其他属性大两个数量级。解决办法是 z-score,均值标准差只从训练集算。
四、训练集上准确率 90%,换一批样本掉到 40%。现象是自评结果和交叉验证差得离谱。原因是被评估的样本自己也在邻居集合里,距离为 0,永远投自己一票,等于把答案提前告诉了模型。解决办法是留一法里先把自己剔掉,或者正规做分层五折。
五、回归结论和分类结论对不上。现象是回归说酒精最重要,分类说硫酸盐最重要,被追问就答不上来。原因是两个模型用的属性集不同,回归用了三项、分类用了五项,衡量标准也不一样。解决办法是在报告里明确写出两次建模的输入差异,或者统一属性集,让两条线的结论能互相对照。
6. 验收与答辩:把 R²=0.6589 和“分类不准”讲成加分项
验收环节真正被考的不是模型多先进,而是你对每个数字的来历是否清楚。带过几轮课设之后我发现,分数高的那份报告通常不是 R² 最高的,而是每一项指标都能说清“为什么是这个值、边界在哪”。答辩前按下面这张表自查一遍,能挡掉
本文还有配套的精品资源,点击获取