机器学习红酒产地预测实战:数据预处理与模型调参全流程

发布时间:2026/10/6 22:40:07
机器学习红酒产地预测实战:数据预处理与模型调参全流程
简介面向机器学习初学者与数据挖掘爱好者这份压缩包围绕“红酒产地预测”分类任务给出了一套可直接运行的代码与配套实验报告。内容覆盖数据预处理、特征选择、逻辑回归/softmax回归等模型实现、交叉验证与评估指标计算适合用于课程作业、竞赛入门或算法对比练习。包内共18个文件以9个Python脚本为主体另有7个编译后的pyc文件、1份Word实验报告及1个图标文件整体仅258KB。Python脚本分别实现梯度下降、随机梯度下降、正则化等不同训练方式并附分类评估工具实验报告则记录了数据说明、建模思路与结果分析便于对照学习。目前已有672人浏览学习。通过该资源读者可快速复用完整代码框架理解从数据清洗到模型调参的每一步还可参考报告中的排版与分析方法提升自身项目撰写能力。1. 拿到「机器学习红酒产地预测问题代码资料.zip」先想清楚这是一道什么题如果你刚下载完这份「机器学习红酒产地预测问题代码资料.zip」先别急着双击解压。这类名字的压缩包八成来自课程期末项目、平台实训或个人练手整理里面的东西逃不出「数据 训练脚本 说明文档」这三样。它要做的是一件很朴素的事拿一瓶红酒的 13 项化学成分检测结果——酒精度、苹果酸、颜色强度、脯氨酸这些——去预测它产自哪个产区。数据集不大标签只有三类是典型的小样本多分类任务。这个方向适合刚学完 scikit-learn、想跑通一次完整建模流程的人也适合准备面试时拿来找回手感的人。我的建议是先不要急着一行行读代码而是先搞清楚数据长什么样、标签怎么分布、zip 里到底给了什么再动手训练。看完本文你会知道这个 zip 该怎么解压、怎么跑通最小流程、参数怎么从默认值调到合理水平以及哪几步最容易翻车。2. 看懂红酒数据13 个化学特征凭什么能区分产地2.1 数据集结构与标签分布这个任务背后用的数据集在 UCI 上有个经典名字——Wine Recognition Dataset178 条样本13 个连续型化学特征3 个类别。很多课程引用它机器学习西瓜书和国内高校的期末项目里也常拿它当练手题。你手里的 zip 里这份数据通常被存成wine.csv或wine.data列名可能是英文也可能被加工成中文。先看一张特征表特征含义大致取值区间alcohol酒精度11.014.8malic acid苹果酸含量0.75.8ash灰分1.43.2alcalinity of ash灰分碱度10.030.0magnesium镁含量70160total phenols总酚含量0.93.9flavanoids类黄酮含量0.35.1nonflavanoid phenols非类黄酮酚含量0.10.7proanthocyanins原花青素含量0.43.6color intensity颜色强度1.313.0hue色调0.41.8OD280/OD315蛋白质含量稀释酒1.34.0proline脯氨酸含量2801680注意最后几列的取值区间脯氨酸能从几百到上千灰色调只在小数点附近波动颜色强度横跨 1 到 13。这意味着特征之间的量纲差异极大后面做特征缩放不是可选项而是必选项。标签部分三个产区的样本数大约是 59、71、48总量 178。不算极度不平衡但也谈不上均衡。做交叉验证时要考虑按类别比例分层切分不然某一次验证折里可能恰好缺了一个类别。这种小数据集上划分方式对结果的影响非常直观。2.2 为什么化学指标能当产地的「指纹」产地不同意味着气候、土壤、日照、葡萄品种都不同。这些差异会直接写进葡萄的代谢产物里最后反映在酒精度、酚类物质含量和脯氨酸这类指标上。逻辑上讲来自同一产区的红酒化学指标应该更接近来自不同产区的指标分布会有系统性偏移。机器学习模型做的就是把这个偏移捕捉出来。我拿到数据后第一件事不是建模而是做一次朴素的分组均值对比看看每个特征在不同类别上有没有肉眼可见的差异import pandas as pd df pd.read_csv(data/wine.csv) # 实际文件名和列名以压缩包内为准 print(df.shape) print(df[target].value_counts()) # 按类别分组看每个特征的平均值找区分度高的列 grouped df.groupby(target).mean().T print(grouped)这段代码的逻辑很简单groupby(target)把样本按类别分开.mean()计算每类的特征均值.T转置后方便竖着看。如果某一列在三类之间数值差距明显比如类 0 的脯氨酸均值 800、类 1 只有 500那这个特征就是天然的分类信号。你解压 zip 后可以自己跑一遍看看哪几个特征区分度最高。2.3 现在先想清楚目标分类还是回归标题叫「产地预测」但实际落到代码里就是多分类给定 13 个特征输出 0、1、2 三个类别中的一个。不要把这个任务当成回归去写虽然产区可以编号但类别之间没有远近关系类 1 和类 2 的距离不等于 1 和 2 的编号差。少数练习代码会犯这个错把LogisticRegression默认当成回归器来用输出连续值最后算出来一团糟。另外有一点值得提UCI 原始文档里这个数据集的标签描述是「三种不同来源的葡萄酒」很多教材和题目就把它改写成「产地预测」。当作产区分类来做逻辑是一样的不用纠结措辞。关键是记住输入是 13 维连续特征输出是 3 类离散标签评估用准确率和混淆矩阵不用回归指标。3. 把 zip 里的代码跑起来解压、配环境与最小训练命令3.1 先看 zip 里有什么别急着跑脚本解压之前建议你先用压缩软件预览一下目录结构。我见过的同类「代码资料.zip」通常包含下面几类东西路径/文件作用README.md运行说明、目录解释、环境要求requirements.txtPython 依赖清单data/wine.csv模型输入数据train.py训练入口脚本model/保存训练好的模型文件report/分类报告、混淆矩阵图常见做法是把README.md和requirements.txt放在顶层数据单独放data/目录。解压后先读README搞清楚它要求的 Python 版本是 3.7 还是 3.10、依赖是不是已经列全。最忌讳的事是跳过 README 直接双击train.py结果报一堆缺失模块再一个个手动pip install浪费一下午。3.2 解压、建虚拟环境、装依赖解压这一步看似简单坑不少。最大的坑是中文路径和目录嵌套。如果你把 zip 解压到「下载」下面路径里带了中文或空格Python 的某些旧库在读写中文路径时会出现UnicodeDecodeError或FileNotFoundError。我一般会把它挪到纯英文路径下比如D:\ml\wine。终端里操作是这样的mkdir D:\ml\wine unzip 机器学习红酒产地预测问题代码资料.zip -d D:\ml\wine cd D:\ml\wine python -m venv venv venv\Scripts\activate # Linux/macOS 用 source venv/bin/activate pip install -r requirements.txt第一行建目录第二行把 zip 解压到目标目录第三行进入项目目录第四行建虚拟环境第六行安装依赖。虚拟环境这一步很多人跳过其实在小项目里更应该做——这个 zip 里如果用了旧版 scikit-learn直接装到全局环境里大概率会把别项目的包版本冲掉。处理完环境问题后跑python train.py --help看看入口脚本支持哪些参数。3.3 跑通一次最小训练先把目标定为「不报错」第一次训练不要追求准确率追求的是流程完整走通。一个标准的train.py入口脚本长得差不多是这样import argparse import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report parser argparse.ArgumentParser() parser.add_argument(--data, defaultdata/wine.csv) parser.add_argument(--test-size, typefloat, default0.2) parser.add_argument(--seed, typeint, default42) args parser.parse_args() df pd.read_csv(args.data) X df.drop(columns[target]) y df[target] X_train, X_test, y_train, y_test train_test_split( X, y, test_sizeargs.test_size, random_stateargs.seed, stratifyy ) model LogisticRegression(max_iter1000) model.fit(X_train, y_train) print(classification_report(y_test, model.predict(X_test)))几个参数值得说明--test-size控制测试集比例小数据集用 0.2 比较稳妥留出 36 个样本做测试--seed固定随机种子跑出来的结果可复现stratifyy是分层抽样保证测试集里三个产区的比例和全量一致。max_iter1000是给逻辑回归的迭代上限不设的话数据没标准化时经常收敛不了报警告。运行命令就是python train.py --data data/wine.csv。如果你拿到的 zip 里列名不叫target比如叫class或label把脚本里的drop(columns[target])改成对应列名即可。这一刻你不用管准确率是多少先确认数据读进来了、模型训练不报错、分类报告能打印出来。4. 从基线到调参用逻辑回归和 xgboost 把准确率稳定在 90% 以上4.1 先做标准化机器学习应用流程里最容易被跳过的一步拿到了能跑通的基线接下来就要认真提升精度。红酒数据集中 13 个特征的量纲差异大得离谱脯氨酸动辄上千色调值还在个位数徘徊。如果不做标准化逻辑回归的正则化项会偏爱数值大的特征KNN 的距离计算直接被脯氨酸主导结果就是模型学到的「规律」全是假的。正确的顺序是先标准化再训练。但要注意标准化必须在交叉验证内部做不能先对全量数据做StandardScaler再切分训练集和测试集否则会把测试集的信息泄漏进训练过程。用 Pipeline 是标准解法import pandas as pd from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score df pd.read_csv(data/wine.csv) X df.drop(columns[target]) y df[target] pipe Pipeline([ (scale, StandardScaler()), (lr, LogisticRegression(max_iter1000, C1.0)) ]) scores cross_val_score(pipe, X, y, cv5, scoringaccuracy) print(faccuracy: {scores.mean():.3f} ± {scores.std():.3f})cross_val_score默认会把X、y切成 5 折每一折内部先运行StandardScaler().fit再transform训练折和验证折之间互不干扰。这是标准做法也是避免数据泄漏的关键。跑完之后你会得到一个均值准确率以这个数据集的经验值逻辑回归 标准化后准确率通常落在 0.90 到 0.95 之间。如果低于 0.85回头检查标签列是不是字符串或者有没有漏掉标准化。C1.0是逻辑回归的正则化强度的倒数越小表示惩罚越重。在这个 178 样本的小数据集上C从 1.0 降到 0.1 有时反而更稳但提升幅度不大。我的习惯是先跑C [0.1, 1, 10] 三档看交叉验证均值变化选最稳的而不是选最高的。4.2 上 xgboost小样本上调参才不容易翻车逻辑回归的优势是可解释、不容易过拟合但遇到特征和类别之间的非线性关系线性模型的表达力上限就在那里。xgboost 是这类小表格数据上最常见的增强模型。不过在小样本上直接用默认参数几乎一定会过拟合。默认的max_depth6、n_estimators100在这 178 条数据上会把训练集准确率刷到 1.0测试集反而掉到 0.85。我的做法是手动把树的复杂度压下去参数这么设参数推荐范围说明learning_rate0.010.1学习率小样本用小学习率配合更多轮数max_depth24树深度小样本不建议超过 4n_estimators100300树的数量配合学习率调整subsample0.71.0每棵树随机行采样比例colsample_bytree0.71.0每棵树随机列采样比例网格搜索的范围不要拉太大。178 条样本撑不起几十组参数的大搜索网格每多一个维度交叉验证就更容易在验证折上「碰运气」。下面的代码把搜索空间控制在 12 组以内from xgboost import XGBClassifier from sklearn.model_selection import StratifiedKFold, GridSearchCV from sklearn.preprocessing import LabelEncoder df pd.read_csv(data/wine.csv) X df.drop(columns[target]) y LabelEncoder().fit_transform(df[target]) # 字符串标签转 0/1/2 xgb XGBClassifier( learning_rate0.05, max_depth3, n_estimators200, subsample0.8, colsample_bytree0.8, random_state42, eval_metricmlogloss, ) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) grid { learning_rate: [0.03, 0.05], max_depth: [2, 3], subsample: [0.7, 0.9], } gs GridSearchCV(xgb, grid, cvskf, scoringaccuracy, n_jobs-1) gs.fit(X, y) print(gs.best_params_, gs.best_score_)LabelEncoder()这一步经常被忽略。如果你的标签列是class_1、class_2这种字符串xgboost 可能报错或输出类别错乱。GridSearchCV里的cvskf指定分层交叉验证n_jobs-1让所有 CPU 核心并行跑12 组参数 × 5 折一共 60 次训练一两分钟就跑完了。跑完之后你会看到xgboost 的分数往往和逻辑回归差不多甚至略低。这不是坏事反而说明数据本身线性可分的成分高非线性模型没有占到便宜。不要因为 xgboost 名气大就非得用它小数据集上简单模型 正确预处理才是性价比最高的组合。4.3 对比模型时不要只看准确率准确率在小样本上非常敏感。测试集只有 36 个样本一个样本预测错了准确率就掉 2.7 个百分点。所以只看测试集单次准确率没有意义。要报告就报告多次交叉验证的均值 ± 标准差比如「0.93 ± 0.04」。如果发现标准差超过 0.05说明模型对数据划分方式很敏感。这时候试RepeatedStratifiedKFold把 5 折交叉验证重复 10 次取 50 个分数的平均比一次 5 折更有说服力。另外可以拉一个最简单的 KNN 基线进来对比。KNN 不需要训练只需要标准化准确率在这个数据集上也能到 0.9 左右。如果一个复杂模型的分数还不如 KNN那就不是数据的问题而是建模流程出了问题。5. 避坑红酒分类任务的五个翻车现场与排查思路5.1 解压运行就报错模块缺失与中文路径现象解压后运行python train.py终端直接报ModuleNotFoundError: No module named sklearn或者FileNotFoundError: data/wine.csv。原因第一类报错是没有安装依赖最常见是只装了 pandas 没装 scikit-learn第二类往往不是文件真的不存在而是路径里有中文或空格Python 在某些 Windows 环境下读取失败。还有一种情况是压缩包解压后多了一层嵌套目录比如某项目/某项目/train.py而你当前目录还停留在外层。解决先pip install -r requirements.txt一条命令省掉所有模块缺失问题。路径问题直接物理移动到纯英文路径比如D:\ml\wine再解压一次。然后ls或dir看一下当前目录底下到底有没有train.py路径层级不对就cd进内层。5.2 标签列预处理不当分类报告和混淆矩阵全部错位现象模型训练不报错但分类报告里三个类别的精确率、召回率全是 0或者混淆矩阵维度明显不对比如 3 分类却出现 4×4 的矩阵。原因读入的标签列是字符串比如class_1、class_2而代码里直接用了classification_reportsklearn 会把字符串的排序顺序当作标签顺序。如果测试集里某个字符串值没出现在训练集或者顺序和你预期的 0、1、2 不一致评估就乱了。解决先跑print(df[target].unique())看标签到底有哪几类。然后用LabelEncoder统一转成 0、1、2。注意LabelEncoder只适合标签列不要拿着它对特征列做编码。转完后再打印一次y[:10]确认转换没有错位。5.3 不标准化直接跑模型准确率卡在 70% 上不去现象逻辑回归测试集准确率徘徊在 0.70 左右KNN 更惨只有 0.60 上下而且换参数怎么都提不上去。原因数据集里脯氨酸的取值范围是 2801680而色调不到 2。逻辑回归的损失函数里大数值特征天然获得更大的权重正则化项也会被这类特征牵着走。KNN 的距离计算里大数值特征直接吞掉了其他特征的影响。解决把StandardScaler()放进Pipeline跟模型一起做交叉验证。不要手动先算均值方差再切分数据。标准化的目的是让每个特征均值为 0、方差为 1而不是把数值压缩到 01注意区分标准化和归一化。5.4 调参越调越差小样本上的过拟合陷阱现象GridSearchCV选出来的最优参数在测试集上的准确率比默认参数还低。比如网格搜索选到max_depth6训练集 100% 正确测试集只有 0.85。原因样本总量只有 178 条。网格搜索本质上是在多个参数组合里挑验证集分数最高的那一组搜索空间越大选到「恰好对验证集友好」的组合概率就越高。这个现象在小数据集上特别明显不是模型不行是参数搜索本身就过拟合了。解决第一网格大小控制在 20 组以内max_depth不要超过 4第二用RepeatedStratifiedKFold替代普通KFold多跑几次取平均第三接受简单模型。如果逻辑回归 0.93、xgboost 0.94选逻辑回归因为它的方差更小、解释起来更容易。5.5 随机种子不固定同一个脚本跑出三个结果现象同一个脚本跑三次准确率分别是 0.92、0.88、0.90同事还跑出了 0.94。你怀疑代码有问题其实很可能只是随机性在作祟。原因train_test_split、模型初始化、交叉验证折的划分每一步默认都有随机性。不固定随机种子等于每次拿不同的一份数据做测试。解决所有带随机性的地方统一设置random_state42。train_test_split、KFold、XGBClassifier、GridSearchCV这些接口全都支持这个参数。设置完之后再跑三次结果应该完全一致。如果还抖就考虑是不是代码里漏掉了某个随机源。6. 进阶用置换重要性和 SHAP 验证模型别只信测试集数字6.1 置换重要性比内置 feature_importance 更靠谱树模型的feature_importance反映的是「这个特征被用来分裂的频次和增益」它偏向数值范围大的特征容易给你一个「脯氨酸最重要」的结论但没法告诉你这个特征对预测的边际贡献到底有多大。置换重要性思路不同把某个特征的取值随机打乱再重新预测看准确率下降多少。下降越多说明模型越依赖这个特征。from sklearn.inspection import permutation_importance result permutation_importance( model, X_test, y_test, n_repeats10, random_state42, scoringaccuracy ) for i in result.importances_mean.argsort()[::-1]: print(X.columns[i], round(result.importances_mean[i], 4))n_repeats10表示每个特征打乱多少次取平均避免单次打乱的偶然波动。如果某个特征的置换重要性是负数说明打乱它之后准确率反而上升——这个特征八成是噪声或者和另一个特征有强相关性导致模型把权重分给了它。我在写报告之前总会跑一遍这个用来决定要不要删特征。6.2 SHAP 看单个样本的决策过程而不是只看平均置换重要性给的是全局视角SHAP 则能告诉你「对某一个具体样本哪个特征把它推向了类 0哪个特征把它推向类 1」。这个小数据集上我一般只看预测错误的那几个样本对比它们的 SHAP 值很快能发现训练集里没有覆盖到的特征组合。import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test, feature_namesX.columns)summary_plot画出来的图中横轴是 SHAP 值纵轴是特征颜色表示特征值高低。特征点分布在 0 右侧说明推高预测结果左侧说明压低。这个图比内置的 feature importance 信息量多一截。这些年我养成的习惯是任何小样本分类任务在交付结论之前都跑一遍置换重要性。如果发现删掉某个特征后交叉验证分数不降反升就果断删掉再重新训练一遍。这个习惯帮我避开了好几次「测试集分数虚高、换数据就崩」的尴尬。希望帮到你。本文还有配套的精品资源点击获取