ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

葡萄酒质量数据分析:Python二分类建模与避坑指南

葡萄酒质量数据分析:Python二分类建模与避坑指南 简介面向数据挖掘课程的葡萄酒质量分析项目是一套完整的课程设计与期末大作业方案围绕真实葡萄酒评分数据展开涵盖数据预处理、探索性分析、特征工程与模型构建评估全流程并配有超详细注释适合计算机相关专业学生和项目实战初学者参考学习。资源包共16个文件包括10个CSV数据集、3个Python源码和3个文本说明其中CSV为葡萄酒理化指标数据txt为项目说明文件py为带注释的可执行脚本压缩后约为597KB目录结构清晰便于按需查阅。目前已有55人学习其完整覆盖数据挖掘全流程学习者可在此基础上快速复现并拓展自己的项目。借助该项目使用者可掌握真实葡萄酒评分数据的处理方法理解回归、决策树、随机森林等算法的实际应用并能借鉴整体思路高效完成同类数据挖掘任务获得课程设计或大作业的直接参考。1. 先从“获95分以上”说起葡萄酒质量数据分析到底在评什么第一次看到这个项目标题大多数人的第一反应是Python 实现的葡萄酒质量数据分析有源码、有详尽注释、有数据集还拿了95分以上那直接照着复现不就行了。先别急这类项目的评分口径我拆开看过“95分以上”通常是课程作业总分、答辩分或代码规范分不是模型准确率。如果你拿着 accuracy0.95 去投模型多数时候会被打回来。这个方向本身很值得做。UCI 的 Wine Quality 数据集规模小、字段少、目标列清晰特别适合把 pandas 清洗、Python 数据分析与可视化、特征工程、交叉验证完整串一遍。下面按一条能落地、能解释清楚的路径走先读红酒和白酒两份 csv处理分号分隔符做分布体检和相关性分析再做二分类建模最后用分类报告和注释把所有环节固定成可交付状态。拿高分的关键不是把数字调高而是每一步都有依据。2. 先用 pandas 把原始数据读成一张干净的建模表收到一份“源码加数据集”的压缩包第一件要做的事不是建模而是确认数据长什么样。UCI 的葡萄酒质量数据集分红酒和白酒两份红酒约 1500 多行白酒约 4900 行字段结构相同。字段不多但每一个都直接影响后续的分布判断和特征选择所以我习惯先做一张字段表再往下写代码。2.1 数据集字段和单位读不懂字段就别谈后续分析下面是红酒和白酒共用的 12 个字段。quality 是品酒师打出的总分范围在 3 到 9 之间不是百分比也不是质量等级它就是我们要预测的目标列。字段含义常用单位/范围fixed acidity固定酸度g/Lvolatile acidity挥发酸度g/Lcitric acid柠檬酸g/Lresidual sugar残糖g/Lchlorides氯化物g/Lfree sulfur dioxide游离二氧化硫mg/Ltotal sulfur dioxide总二氧化硫mg/Ldensity密度g/cm³pHpH 值0-14sulphates硫酸盐g/Lalcohol酒精体积分数% volquality品酒师评分整数 3-9十几个字段里最容易误导的是二氧化硫。游离二氧化硫是未结合的那部分总二氧化硫包含结合态的二者高度相关建模时如果同时放进去某些线性模型会给出不稳定的系数。另一个常见误解是 pH 越小酸度越高所以后面做相关性分析时看到 pH 和 fixed acidity 呈负相关不要觉得奇怪。2.2 读取 csv这个数据集的分隔符是分号而不是逗号拿到数据集压缩包后先解压找到 winequality-red.csv 和 winequality-white.csv。我一般不会盲拆压缩包而是先放进一个干净的 data 目录再写统一的读取脚本。这里有一个所有第一次接触该数据集的人都会踩的坑文件后缀是 .csv但分隔符是英文分号。import pandas as pd # UCI 的葡萄酒质量数据集使用分号分隔直接用默认逗号会读成单列 columns [ fixed acidity, volatile acidity, citric acid, residual sugar, chlorides, free sulfur dioxide, total sulfur dioxide, density, pH, sulphates, alcohol, quality ] red pd.read_csv(winequality-red.csv, sep;, namescolumns, header0) white pd.read_csv(winequality-white.csv, sep;, namescolumns, header0) print(red.shape) # (1599, 12) print(white.shape) # (4898, 12) print(red.head())这里namescolumns是给自己加的一层保险即使原始表头改了列名也保持一致header0表示第一行是表头不要读成数据。如果你打开 csv 发现第一行不是字段名就把header0改成headerNone让 names 完全接管列名。读取之后的red.shape能立刻暴露问题如果打印出来是(1599, 1)说明分号没有被拆开sep 参数没写对。很多项目源码第一段就错在这里但后面代码照样能跑只是所有列名都变成一个长字符串等到red[quality]报 KeyError 时才发现。2.3 用 info 和 describe 做第一次体检缺失值、重复行和取值分布建模前的体检不是随便跑一下df.describe()就结束。我要看四样东西列类型是否正常、有没有缺失值、是否存在重复行、quality 的取值分布是否均匀。def quick_report(df, name): print(f {name} ) print(df.info()) print(df.describe().T[[mean, std, min, max]]) print(缺失值:, df.isna().sum().sum()) print(重复行:, df.duplicated().sum()) print(quality取值:, sorted(df[quality].unique())) quick_report(red, red wine) quick_report(white, white wine)info()会打印列名、非空数量和数据类型如果出现 object 类型的数值列说明读入时可能有脏值。isna().sum().sum()是快速扫一遍全部缺失。duplicated().sum()统计完整重复的行这里要特别小心重复行不一定都要删白酒数据里重复行很多但有些是同一批酒在不同品酒师手里的自然结果我会先留着等到做交叉验证时再评估。体检后你会看到 quality 取值主要是 4 到 8两头稀少。以红酒为例评分 3 和 9 都只有几十行甚至个位数。如果直接做多分类预测模型会偏向样本多的中间分数少数类几乎学不到。所以这个项目最常见的做法不是预测具体几分而是把 quality 转成“普通/优质”二分类。2.4 切标签quality 怎么变成二分类阈值选 6 还是 7二分类阈值没有标准答案取决于你想要什么样的业务含义。有人把 6 分及以上算好酒有人把 7 分及以上算优质。我一般先用 7 作为主阈值因为 7 分以上的样本更稀疏模型能学到的东西更接近品酒师的“好评”概念但如果课程作业要求准确率 95 分以上6 分阈值会更容易达到因为多数类占比更高。def make_label(df, threshold7): df df.copy() df[good] (df[quality] threshold).astype(int) return df red make_label(red, threshold7) white make_label(white, threshold7) print(red wine 优质比例:) print(red[good].value_counts(normalizeTrue)) print(white wine 优质比例:) print(white[good].value_counts(normalizeTrue))good是在 quality 基础上生成的目标列构造完后要始终记住建模时特征里不能留 quality也不能留 good只能留 11 个理化指标。很多写着“获 95 分”的项目拿到数据后没有做任何标签切分直接跑一个准确率那个数字基本没有参考价值。做完这一步数据侧的准备就齐了。下一章进入分析流水线先可视化再标准化再建模。3. 用 Python 数据分析与可视化跑通建模流水线拿到干净数据后的下一个任务不是马上训练模型而是先做一轮探索性数据分析也就是 EDA。EDA 的目的不是画图好看而是回答三个问题字段分布长什么样、字段之间是否相关、有没有极端值会在建模时把损失函数带偏。回答完这三个问题再进特征工程和模型选择。3.1 先把分布和相关矩阵画出来四个图解决大多数疑问红酒特征里我最先看 alcohol、volatile acidity、residual sugar、sulphates 四个字段。alcohol 和 sulphates 与质量通常正相关volatile acidity 偏高通常会拉低质量residual sugar 的分布很偏直接进模型会带来长尾问题。import matplotlib.pyplot as plt import seaborn as sns # Windows 下显示中文macOS 可改成 Arial Unicode MS plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(2, 2, figsize(12, 10)) for idx, col in enumerate([alcohol, volatile acidity, residual sugar, sulphates]): ax axes[idx // 2][idx % 2] sns.histplot(red[col], bins40, kdeTrue, axax) ax.set_title(col) plt.tight_layout() plt.show()bins40让分布轮廓更细kdeTrue叠加密度曲线方便看偏度。residual sugar 和 chlorides 这类字段往往是右偏分布直方图会拖一条长尾巴后续可以考虑做 log1p 变换但不要在第一轮就固定带进模板。相关性矩阵我会用 Spearman而不是默认的 Pearson。因为 quality 是整数评分不是连续正态变量Spearman 衡量的是秩相关性对离群值不敏感更能反映“某个指标升高时评分是否跟着升高”。corr red.select_dtypes(number).corr(methodspearman) plt.figure(figsize(10, 8)) sns.heatmap(corr, annotTrue, fmt.2f, cmapcoolwarm, linewidths0.5) plt.title(Spearman Correlation - red wine) plt.show()看相关矩阵时先找与 quality 正负相关最强的字段再处理特征之间的高相关。比如 density 和 residual sugar 经常高度正相关total sulfur dioxide 和 free sulfur dioxide 也高度相关。这类字段放进线性模型会产生共线性问题放进树模型没那么敏感但解释性会变差。3.2 只对训练集做标准化切分和缩放顺序不能反标准化这一步最容易翻车而且翻得很隐蔽。常见错误是先用fit_transform处理整份数据再切训练测试集。这样做会把测试集的信息提前泄露给 scaler交叉验证分数虚高部署后分数突然掉下来。正确的顺序是先切分再在训练集上 fit再对测试集只做 transform。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler df_model red.copy() # quality 和 good 都不能进特征一个是原始目标一个是衍生目标 X df_model.drop(columns[quality, good]) y df_model[good] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)stratifyy是为了让训练集和测试集里的优质酒比例保持一致。红酒数据里 good1 的比例不高如果不分层切分某个 random_state 可能把少数类全切进测试集导致训练集学不到正样本。random_state42不是必须的数值但固定种子能让别人复现你的结果这个动作在交付项目时很重要。StandardScaler 的默认行为是每个字段减均值、除以标准差。fit_transform 在训练集上计算均值方差transform 在测试集上使用同一组参数所以测试集的分布是否与训练集一致直接决定模型上线后的表现。3.3 四个模型做五折交叉验证95 分应该出现在这里第一轮建模不要追求复杂模型先用逻辑回归做基线再对比随机森林、SVM 和梯度提升。树模型不要求标准化但统一用标准化后的特征可以保证对比条件一致也方便后续换线性模型。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.svm import SVC from sklearn.model_selection import cross_val_score models { LogisticRegression: LogisticRegression(max_iter1000, random_state42), RandomForest: RandomForestClassifier( n_estimators300, max_depth8, min_samples_leaf2, random_state42 ), SVM: SVC(kernelrbf, C1.0, gammascale, random_state42), GradientBoosting: GradientBoostingClassifier( n_estimators300, learning_rate0.1, max_depth3, random_state42 ), } for name, model in models.items(): scores cross_val_score(model, X_train_scaled, y_train, cv5, scoringaccuracy) print(f{name}: {scores.mean():.4f} ± {scores.std():.4f})cv5表示五折交叉验证每折轮流当验证集最后取平均值和标准差。准确率均值反映模型整体水平标准差反映稳定性如果某个模型均值高但标准差也高说明它依赖特定数据划分不能直接信任。RandomForest 的n_estimators300是树的数量max_depth8限制单棵树深度min_samples_leaf2保证叶子节点至少两个样本这三个参数组合起来能明显抑制过拟合。GradientBoosting 的learning_rate0.1控制每棵树贡献权重调小会提高精度但增加训练时间。SVM 的gammascale是 sklearn 根据特征数自动计算的默认值比手动给 gamma 更稳。如果你的目标是“获 95 分以上”五折交叉验证的准确率是一个可以被答辩老师认可的数字。如果这里只能跑到 0.86但代码注释写着“准确率 95”说明数据划分或标签构造有问题回到上一章检查。3.4 看完 accuracy 再看分类报告多数类陷阱在这里二分类准确率经常骗人。假设优质酒只占 10%即使模型把所有样本都预测成普通酒准确率也有 90%。所以我每次跑完准确率都会强制自己看一眼精确率、召回率和 F1。from sklearn.metrics import classification_report model RandomForestClassifier( n_estimators300, max_depth8, min_samples_leaf2, random_state42 ) model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) print(classification_report( y_test, y_pred, target_names[普通, 优质] ))target_names只是让报告可读不影响计算。分类报告里最需要注意的是优质酒那一行的 recall它表示真实优质酒中有多少被正确找出来。有些项目为了刷准确率把阈值往多数类偏移召回率掉到 0.3 还声称 95 分这在答辩时一问就会露馅。到这里模型流水线已经完整跑通。下一章专门说这个项目里最高频的翻车点每一条我都按“现象、原因、解决”的顺序写你可以把它当排查手册用。4. 葡萄酒质量项目避坑指南五个高频翻车点的现象与修复为什么同样一份数据集有人能交出干净可复现的项目有人却卡在 60 分徘徊问题通常不在模型而在数据口径、特征泄漏和代码注释。这一章把我在葡萄酒质量项目里见过最多的五个问题列出来全部围绕“获 95 分以上”这个目标展开。4.1 把“获95分以上”当成模型准确率先核对评分口径现象项目 README 第一行写着“准确率 95%”但复现时五折交叉验证最高只有 0.87。去问作者他说 95 是课程总分包含代码规范、可视化、答辩表现和实验报告不是模型准确率。原因标题里的“获 95 分以上”是评分体系的分数不是 accuracy 指标。有人为了自我加分把总分直接写在模型结果旁造成误导。解决拿到任何项目先把评价口径拆开。我习惯在 results.md 里写四行准确率是多少、评分口径是什么、报告版本是哪一份、复现命令是什么。如果题目要求准确率就用交叉验证结果如果题目要求项目分就要把代码结构、注释、可视化图表都做成可评分的交付物而不是只调参。4.2 数据泄漏quality 列留在特征里现象模型准确率突然到 0.98特征重要性排名第一的是 quality相关性热图里 quality 和 good 完全正相关。原因创建 good 标签时原始 quality 列没有被删除。建模阶段有人习惯写X df.drop(good, axis1)但忘了 quality 还在 df 里。模型直接看到目标变量衍生出的字段等于开卷考试。解决特征列必须显式剔除质量相关列。X df_model.drop(columns[quality, good])这行代码应当放在所有建模逻辑之前并且用一个断言兜底assert quality not in X.columns, quality 泄漏进特征了 assert good not in X.columns, good 泄漏进特征了养成这个习惯后即使后面加了新字段也能第一时间发现问题。4.3 重复行和离群值被无差别删除现象数据里本来有几百行重复项代码一句df.drop_duplicates(inplaceTrue)全部删掉模型准确率不掉但答辩时老师问“为什么删重复行”答不上来。原因重复行可能是采集重复也可能是真实存在的相似酒款。直接删意味着丢失真实分布尤其在白酒数据里重复样本会让模型高估多数类。解决先看重复行的标签分布再决定是否删除。如果 good 两类里都有重复删除后类别比例变化不大可以删如果只删了某一类就会造出虚假平衡。离群值同理不要按 3 倍标准差一刀切先画箱线图确认离群值是否是物理上不可能出现的值比如负值的二氧化硫再单独处理。4.4 预处理泄漏先全量标准化再切分现象本地交叉验证 0.93换到新数据只有 0.80。排查后发现在建模前对整个数据集做了scaler.fit_transform(df)模型已经看过测试集的均值和方差。原因标准化属于数据学习的一部分必须在 train 集上学习参数再应用到 test 集。全量标准化会让测试集信息混入训练阶段交叉验证虚高。解决严格按训练集/测试集分离后的管道执行X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)更进一步用 sklearn 的 Pipeline 把缩放和模型打包避免手工维护顺序from sklearn.pipeline import Pipeline pipe Pipeline([ (scaler, StandardScaler()), (model, LogisticRegression(max_iter1000)) ])Pipeline 在交叉验证时对每一折只 fit 当前训练折的数据从结构上杜绝预处理泄漏。4.5 注释与代码不一致最后换模型忘了改说明现象源码文件头的注释写“使用逻辑回归max_iter1000”但训练代码已经是随机森林参数完全对不上。别人复现时照注释找模型怎么都找不到。原因项目后期常试多个模型定稿时只改了代码没改注释。注释变成和代码无关的装饰品。解决注释要跟着实验迭代更新。我一般会在每个模型块前写三行模型名、关键参数、选择理由。例如# 最终模型RandomForest # 关键参数max_depth8, min_samples_leaf2 # 选择理由五折交叉验证均值最高且标准差低于 SVM这种注释不需要长篇大论但能保证别人照着代码流程走一遍不会产生歧义。答辩问“为什么用随机森林”时也可以直接指着注释里的交叉验证结论回答。5. 把项目交付成能拿高分的样子验证、导出和注释习惯模型跑通不代表项目能拿 95 分以上。高分项目最后交付的东西通常包含三类可以复现的代码、可以解释的指标、可以追溯的数据。最后一章把这三件事一次性讲完。5.1 用一张混淆矩阵交付模型结论分类报告是数字混淆矩阵是图形。答辩时一张矩阵图比一段 print 输出更能说明问题。用 sklearn 的 ConfusionMatrixDisplay 可以一行生成from sklearn.metrics import ConfusionMatrixDisplay ConfusionMatrixDisplay.from_estimator( model, X_test_scaled, y_test, cmapBlues, display_labels[普通, 优质] ) plt.title(Confusion Matrix - RandomForest) plt.show()from_estimator会重新调用 model.predict所以模型必须已经 fit 过。图里四个格子分别表示真正、假正、真负、假负。重点看左下角那块也就是真实优质但被预测成普通的样本数如果它特别高说明模型在业务上不可接受。5.2 把预测结果和模型参数一并导出可复现项目要有“后悔药”任何时间重跑都还能看到上次的预测文件。我会把测试集预测结果导成 csv同时把模型参数序列化保存。result pd.DataFrame({ actual: y_test.values, pred: y_pred }) result.to_csv(wine_predictions.csv, indexFalse) import joblib joblib.dump(model, wine_rf_model.pkl) joblib.dump(scaler, wine_scaler.pkl)joblib.dump比 pickle 更适合 sklearn 模型对大对象压缩更好。下次加载时先用joblib.load载入 scaler 和 model再对新数据依次执行 transform 和 predict。导出的 csv 不要只存预测标签把真实标签也存上这样后续画图、复盘、对比版本都不用来回翻原始数据。5.3 给源码留一套“能活三个月”的注释最后说注释。x项目源码里最该写注释的不是每一行而是文件头、模型选择和关键参数。我给每个项目模板都放这样一段# 项目葡萄酒质量数据分析 # 数据UCI Wine Qualityred1599 行white4898 行 # 目标quality 7 的优质酒二分类 # 版本v1.3 # 运行python wine_quality_analysis.py这套注释让我三个月后再打开代码不需要从零回忆。实际上我现在做数据分析项目的第一习惯确实如此先写数据来源和运行方式再写功能代码。遇到评分虚高问题就回到字段表重新核对注释对不上代码时先改注释再改代码。希望这些路径能帮你把同一个方向做成一个能交出去、能解释、能复现的高分项目。本文还有配套的精品资源点击获取
返回列表