
简介一套面向机器学习初学者的红酒产地预测完整项目代码与实验报告资料包聚焦多分类预测任务完整覆盖数据预处理、特征选择、模型训练、交叉验证、评估指标与超参数调优等核心环节适合正在学习分类算法或需要完成课程设计的学习者。压缩包共18个文件含9个脚本逻辑回归、Softmax回归、随机梯度下降、分类评估指标与ROC曲线绘制等、7个编译缓存文件、1份实验报告文档及1个图标资源整体仅258KB目录结构清晰便于快速定位与对照学习。目前已有672人学习下载。实验报告详细记录了数据来源、预处理方式、模型选择依据、训练细节与结果分析配套代码注释清晰演示了从数据清洗到模型评估的完整流程可作为课程作业、毕业设计或入门实战的参考模板帮助读者系统掌握机器学习项目的一般方法论。1. 红酒产地预测这份 zip 里装的是一条完整的机器学习入门基线拿到“机器学习红酒产地预测问题代码资料.zip”大多数人是冲着机器学习期末复习或者第一次完整跑通一个分类任务来的。它的内核是 UCI 经典 Wine 数据集178 个样本、13 个化学成分特征预测意大利三个不同产区的红酒归属一上来就是一个标准的三分类监督学习问题。zip 解压之后代码通常是两种形态——Jupyter Notebook 逐格讲解或者一个 train.py / classify.ipynb 配一份 data.csv。这篇文章就按实际落地顺序把这份压缩包拆开数据该怎么诊断、预处理写到哪一步才算对、KNN 到 XGBoost 的参数怎么设、哪些位置最容易翻车以及用什么方法验证结果真的可靠。新手照着一路能复现熟手可以直接对照参数表和避坑清单自查。2. 拆开 zip 看数据红酒产地预测的特征语义、数据诊断与读取方式2.1 三个产区的 13 个化学指标特征不是越多越好Wine 数据集源于意大利同一地区三个不同品种的红酒每个样本由 13 个化学成分测定值和一个类别标签构成。这些特征包括酒精含量alcohol、苹果酸malic_acid、灰分ash、灰分碱度alcalinity_of_ash、镁magnesium、总酚total_phenols、黄酮类化合物flavanoids、非黄酮类酚nonflavonoid_phenols、原花青素proanthocyanins、颜色强度color_intensity、色调hue、OD280/OD315 稀释酒吸光度od280_od315_of_diluted_wines和脯氨酸proline。产地差异不是拍脑袋定的不同产区的土壤、日照和酿酒工艺会影响酚类物质和酸度分布这些特征组合起来模型才能学到类别边界。读取数据的代码一般长这样注意编码和列名引号这两个老坑import pandas as pd df pd.read_csv(data/wine.csv, sep,, encodingutf-8) print(shape:, df.shape) # 期望输出 (178, 14)13 个特征 1 个标签 print(df[class].value_counts().sort_index())逻辑说明wine.csv是这份 zip 里最常用的数据文件名如果读取时报UnicodeDecodeError把编码改成encodinglatin1即可。shape为什么要核对到(178, 14)数据集的 178 个样本是公开常识如果你的 csv 多了一行表头外的空行或者少了一列后面所有代码都会带着错误跑早点发现能省半小时。value_counts()打印三类样本数常见分布是 59 / 71 / 48这个比例属于接近均衡不需要做重采样但你得先知道它均衡才能决定不做。特征这里多说一句很多初学者看到 13 个特征觉得少想自己加交互项比如alcohol * proline。在我的经验里Wine 数据集本身线性可分性就很好标准化之后 KNN 都能拿到 0.95 以上的交叉验证分数加特征收益不大反而会把模型的可解释性搞脏。先跑通基线再考虑特征工程顺序别反。2.2 训练集/测试集拆分stratify 与 random_state 是硬要求不是习惯178 个样本是个很小的规模。如果不分层随机拆一次测试集里可能某个类只剩四五个样本评估值波动会非常大。train_test_split里的stratifyy保证三个类在训练集和测试集中的比例与全量数据一致random_state固定下来则保证任何时刻重新跑脚本都得到同样的划分。这两件事是“可复现”的最低门槛也是代码评审时最先看的两行。from sklearn.model_selection import train_test_split X df.drop(columns[class]) y df[class] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, # 分层抽样类别比例在划分前后保持一致 random_state42 # 固定随机种子保证结果可复现 ) print(X_train.shape, X_test.shape) # 期望 (124, 13) / (54, 13) print(y_test.value_counts().sort_index() / len(y_test))参数说明test_size0.3是常见初始值178 条数据切下来训练集约 124 条、测试集约 54 条。测试集比例不建议再大训练集太小模型学不到稳定边界也不建议小于 0.2测试集本身的评估方差会大得让调参失去意义。stratify传入的是完整标签序列y不是y_train这是新手容易看错的地方。拆分之后还有一个容易被忽略的动作确认y_test里三个类都还在。上面第三行打印的就是归一化后的类别占比比如 0.333 / 0.407 / 0.259。如果某一类在测试集里是 0说明分层没生效回去检查y是不是被读成了 float 类型——这种问题在原始数据里很常见因为 UCI 导出的 CSV 类别字段可能带引号pd.read_csv会把1识别成字符串或者直接 parse 成 floatstratify就会报错或者失效。2.3 五条数据诊断清单先看分布再喂模型在跑任何模型之前我一般做五个检查顺序固定缺失值df.isna().sum().sum()有缺失就用列均值填充或SimpleImputer。标签分布三个类 59 / 71 / 48属于均衡数据不需要重采样。量纲alcohol在 11~15proline在 278~1680color_intensity在 1.3~13差异巨大KNN 和逻辑回归必须标准化才能收敛树模型无所谓但统一处理没坏处。特征相关性flavanoids和total_phenols相关性通常很高说明有冗余不影响树模型精度但会影响逻辑回归系数解释。异常值用describe()看 min/maxWine 数据集没有特别离谱的极端值但如果发现color_intensity出现 0 或proline出现负值大概率是原始数据读入或合并时出了问题。print(缺失值总数:, df.isna().sum().sum()) print(df.describe().T[[min, max, mean, std]].head(5))逻辑说明这两行代码回答两个问题——这份数据能不能直接用以及哪些特征在数值范围上差异最大。head(5)只看前 5 个特征但实际使用时我会把 13 个特征全部打出来过一眼重点看mean和std。标准差的量级差异就是后续要不要做 StandardScaler 的直接依据。3. 把代码跑起来KNN、逻辑回归与树模型的参数设置3.1 StandardScaler 的位置决定测试集干不干净先拆分再 fit这是整个红酒产地预测代码里最容易翻车的一步。常见错误写法是# 错误的顺序先标准化再拆分 scaler StandardScaler() X scaler.fit_transform(X) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3)这样写的问题在于scaler在计算均值和方差时已经看过了全部 178 条数据测试集的信息提前混进了后续所有模型的输入。模型的测试分数会偏乐观而且这个乐观程度没法估计。正确做法是先拆分再在训练集上fit测试集只做transform。为了把这道保护写死在代码里我推荐直接用Pipelinefrom sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier pipe Pipeline([ (scaler, StandardScaler()), # 每个特征独立做 z-score 标准化 (knn, KNeighborsClassifier()) # 默认欧氏距离 ])逻辑说明Pipeline不只是少写两行代码那么简单。它保证在fit、cross_val_score、GridSearchCV这些操作里数据的标准化都在同一个 fold 内完成——训练折 fit scaler验证折 transform永远不会把验证集信息带进训练。这是 sklearn 推荐的用法也是我拿到任何 zip 代码时最先看的地方。StandardScaler默认对每个特征减均值、除以标准差生成均值为 0、方差为 1 的分布。参数说明如果原始代码里用的不是StandardScaler而是MinMaxScaler把数据缩放到 0~1对 KNN 也能用但 KNN 的距离度量对异常值更敏感StandardScaler通常更稳。对逻辑回归来说标准化之后解路径更快正则化参数C的作用也更可解释。3.2 KNN 与逻辑回归两个基线模型的超参数范围KNN 在这个数据集上表现出色因为 13 维特征经过标准化后三个类在欧氏距离下确实聚成三团。真正要调的就一个n_neighbors取值范围我一般从 3 开始到 15 结束。n_neighbors1在训练集上准确率永远是 100%但泛化很差基本等于把边界背下来了不要放进搜索空间。from sklearn.linear_model import LogisticRegression lr Pipeline([ (scaler, StandardScaler()), (lr, LogisticRegression(max_iter1000, random_state42)) ])逻辑说明max_iter1000是为了避免小数据集上逻辑回归的收敛警告。C是正则化强度的倒数默认 1.0搜索范围我会用10**np.linspace(-2, 2, 5)也就是 0.01 到 100 之间取对数均匀分布的点。这个数据集特征维数低、类间区分度高C的取值对最终分数影响不大但对系数可解释性有影响——C越小正则越强系数会被压得更小更适合在报告里解读“哪个特征对产地判断贡献大”。KNN 的调参代码通常长这样from sklearn.model_selection import GridSearchCV import numpy as np param_grid { knn__n_neighbors: [3, 5, 7, 9, 11, 13, 15] } grid GridSearchCV(pipe, param_grid, cv5, scoringaccuracy) grid.fit(X_train, y_train) print(best k:, grid.best_params_) print(cv accuracy:, grid.best_score_)参数说明注意参数名字是knn__n_neighbors双下划线连接 pipeline 中的 step 名和真实参数名漏掉这一步 GridSearchCV 会报错或者压根不调这个参数。cv5是 5 折交叉验证124 条训练数据每折约 25 条够用了。最终模型用grid.best_estimator_去预测测试集而不是用grid.predict直接预测——后者用的是最后一次重拟合的模型实际上效果一样但前者语义更清晰。3.3 随机森林与 XGBoost树模型在小样本上的表现与坑树模型不需要特征标准化这是随机森林在这个任务里最省心的地方。但小样本也有小样本的坑树模型容量太大时会把训练集背下来。n_estimators没必要用 1000小数据集 200~300 足够max_depth限制在 3~5让每棵树只看有限几次分裂避免把样本点单独切出去。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators200, max_depth5, min_samples_leaf2, random_state42, n_jobs-1 )逻辑说明min_samples_leaf2是另一个抑制过拟合的参数要求每个叶节点至少两个样本比单独限制max_depth更平滑。n_jobs-1用满多核这个数据集训练本来就快但写成-1是好习惯。随机森林在这个任务上通常能拿到和 KNN 相当的分数它的附加价值是可以输出feature_importances_这在报告里比准确率更有说服力。如果你的 zip 里给了 XGBoost 代码那多半是在做模型对比xgb 代码的常见形态如下from xgboost import XGBClassifier xgb XGBClassifier( n_estimators100, max_depth3, learning_rate0.1, subsample0.8, colsample_bytree0.8, random_state42, eval_metricmlogloss, use_label_encoderFalse )逻辑说明eval_metricmlogloss是多分类的评估指标对应交叉熵损失use_label_encoderFalse是 XGBoost 新版本的必填项旧版代码不写会警告或者报错如果你的 sklearn 版本大于 1.1 而 xgboost 版本大于 1.6这个参数一定要保留。subsample0.8表示每棵树随机抽 80% 样本训练colsample_bytree0.8表示每棵树只用 80% 特征列这两个参数在小数据上能明显降低方差。XGBoost 在这个数据集上不一定打得过随机森林它的优势在更大规模的数据上才体现得出来。但作为期末报告或者面试项目多跑一个 xgb 基线会显得你对梯度提升有实操认识。3.4 横向对比准确率范围、训练耗时与调参成本跑完三个模型之后我习惯做一张表格放进报告比贴一堆交叉验证输出更直观。Wine 数据集上标准化后的 KNN、逻辑回归、随机森林和 XGBoost 的 5 折交叉验证准确率都在 0.93 到 0.99 的范围浮动具体数值取决于随机种子和超参数不必纠结于某个精确值。模型5 折交叉验证分数常见范围调参重点适用场景KNN0.93~0.97n_neighbors最快跑通、理解距离度量逻辑回归0.94~0.98C、max_iter系数可解释、报告好写随机森林0.95~0.99max_depth、min_samples_leaf特征重要性输出XGBoost0.94~0.99max_depth、learning_rate展示梯度提升能力表格里的范围是这类小数据集上多年公开经验值会随随机种子波动如果你的分数明显低于 0.9先回头检查数据诊断和预处理顺序而不是继续调参。训练耗时在这个数据集上全部小于一秒耗时不是决策变量模型的可解释性和代码可维护性才是。4. 红酒产地预测的避坑指南五个最容易翻车的细节4.1 现象交叉验证分数很高测试集分数却大幅下降原因StandardScaler在train_test_split之前做了整体fit_transform测试集的均值和方差提前暴露给训练流程交叉验证分数虚高。等你真拿去预测未知数据预处理参数对不上分数就现出原形。解决把所有预处理写进Pipeline确保fit只在训练折上执行。如果用的是 notebook 逐步执行检查是不是在某格提前fit_transform了再重新跑后续单元格。4.2 现象classification_report里三个类别的分数对不上原因y_test里的标签类型不一致比如class列被读成 float1.0、2.0模型预测输出是整数1或字符串1sklearn 在计算混淆矩阵时就会把类别对不上号报告里出现异常值或者某些类别空缺。解决读取数据之后立刻做类型统一常见做法是df[class] df[class].astype(int)。打印一次y_train.dtype和y_test.dtype确认一致再建模。4.3 现象修改random_state之后调好的参数突然不灵了原因178 条样本的单次划分方差很大。上次用random_state42调出的k5换到 0 之后这个 k 不一定还是最优这是数据本身的波动不是代码变坏了。解决不要用单次划分的测试集分数来选超参数。把网格搜索包在cross_val_score外面用 5 折均值来决定参数最后再用固定random_state的划分做一次最终评估。均值稳定的参数换种子也不会崩。4.4 现象GridSearchCV 把最优参数选成了n_neighbors1原因k1 时模型在训练集上零误差交叉验证的某些折里因为样本太少k1 恰好显得很好于是被搜索算法选中。问题出在参数空间设置太宽没有把不合理区域排除掉。解决参数范围从 3 开始不要包含 1 和 2。如果搜索完最优值仍落在边界上说明搜索范围设窄了往外扩一档再搜。另外看交叉验证的std如果标准差大于 0.03这个参数本身就不可靠增加n_neighbors通常能降低方差。4.5 现象pd.read_csv直接报UnicodeDecodeError或者列名带引号原因这份 zip 里的 CSV 不一定是 UTF-8 编码。UCI 仓库早期的数据导出经常是 ISO-8859-1 或 latin1列名也可能带双引号直接默认编码读必然出错。解决读取时显式指定encodinglatin1或者encoding_errorsignore。如果列名带引号读取后做一次清洗df.columns [c.strip() for c in df.columns]。这一步放在数据诊断最前面属于拿到 zip 之后的五分钟例行检查。5. 从准确率到可解释性把产地预测从分数变成能用的结论5.1 混淆矩阵与归一化错在哪个类别比总分更能说明问题三分类任务只看准确率会漏掉关键信息——可能是某一类特别好、另一类几乎全错平均下来数字还行。把测试集预测结果画成混淆矩阵错分模式一眼就能看出来。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm confusion_matrix(y_test, y_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(Predicted) plt.ylabel(True) plt.show()逻辑说明对角线上是预测正确的样本数非对角线是错误。如果第 2 类和第 3 类之间的格子数字偏大说明这两个产区的红酒化学成分在特征空间里有重叠这时可以考虑可视化特征对比如proline和color_intensity的散点分布进一步确认模型学到的边界是否合理。归一化版代码可以叠加在原始矩阵上cm_norm cm.astype(float) / cm.sum(axis1, keepdimsTrue) sns.heatmap(cm_norm, annotTrue, fmt.2f, cmapBlues)逻辑说明cm.sum(axis1)按行求和再相除得到每一行归一化的比例。它回答的问题是“真实类别里有多少比例被分到了每一类”比单纯看绝对数量更公平。三组类别数量本来就不同表示成比例之后类别间错误率可以直接横向比较。5.2 交叉验证用cross_val_score代替“跑一次就交作业”期末报告里如果只写一次train_test_split的测试集准确率老师一眼就能看出没有做验证。用交叉验证把多个划分的平均分和标准差一起输出数据的说服力完全不同。from sklearn.model_selection import cross_val_score scores cross_val_score(pipe, X_train, y_train, cv5, scoringaccuracy) print(facc: {scores.mean():.3f} ± {scores.std():.3f})逻辑说明pipe是整个 Pipeline 对象交叉验证内部会自动对每个折做标准化和模型训练这一步天然避免了预处理数据泄露也是一种对前面代码的保护。cv5表示把训练集切成 5 份轮流拿一份做验证、其余 4 份训练最终得到 5 个分数。mean看平均水平std看稳定性标准差大于 0.02 就应该先检查数据或参数范围而不是继续往下跑。在交叉验证结果稳定之后再用最初固定的X_test做一次最终评估这次评估只是为了报告“模型在没见过的数据上的表现”不要再拿它去调任何参数。5.3 特征重要性随机森林的feature_importances_怎么读Wine 数据集上随机森林给出的特征重要性通常指向脯氨酸proline、黄酮类化合物flavanoids和颜色强度color_intensity这几个特征。这个结果和葡萄酒化学知识对得上脯氨酸含量受葡萄品种和成熟度影响黄酮类化合物决定单宁结构颜色强度反映酚类氧化程度。import numpy as np rf_model rf.named_steps[rf] if isinstance(rf, Pipeline) else rf importance rf_model.feature_importances_ idx np.argsort(importance)[::-1] for i in idx[:5]: print(f{X.columns[i]}: {importance[i]:.3f})逻辑说明named_steps[rf]是从 Pipeline 里把训练好的随机森林模型取出来如果没包 Pipeline 就跳过这一步直接取rf。argsort(...)[::-1]得到从大到小的特征下标idx[:5]取前五个最重要的特征。这里的数字是训练集上的启发式重要性不要当成因果证据但它能帮你回答“模型是靠哪些化学成分做判断的”这个问题。如果 zip 代码里没有随机森林只有 KNN 或逻辑回归可以用 sklearn 自带的permutation_importance替代它的原理是把某一列特征随机打乱观察预测分数下降多少下降越多说明该特征越重要这套逻辑在任何模型上都适用比feature_importances_更接近真实预测依赖关系。6. 收尾的验证技巧这份 zip 值不值得留三个快速检验方法拿到任何一份机器学习代码 zip我先做的不是跑通而是按三个方法验证它值不值得花时间投入。第一个是复现性检验把整个 notebook 从头到尾重跑两遍固定好random_state两次输出必须完全一致。如果有一丝浮动说明代码里还有没固定的随机源比如某些模型内部的数据打乱没有设种子这类代码在期末答辩时很容易被追问到哑口无言。第二个是留一法验证178 个样本用LeaveOneOut交叉验证就是每次拿 1 个样本做测试、其余 177 个做训练重复 178 次。这里的模型训练都在毫秒级完全跑得起得到的是对这个模型最稳定的误差估计比单次 0.3 划分的 54 个测试样本扎实得多。第三个是学习曲线画训练集分数和交叉验证分数随训练样本量变化的曲线。训练分数高、验证分数低是过拟合两条线都低是欠拟合两条线靠近且偏高才说明模型容量和数据量匹配。from sklearn.model_selection import learning_curve train_sizes, train_scores, test_scores learning_curve( pipe, X_train, y_train, cv5, train_sizesnp.linspace(0.2, 1.0, 5) )我自己第一次跑这个数据集时就是栽在StandardScaler提前 fit 上测试分数虚高到接近满分还兴奋了半个下午直到换成 Pipeline 才看清真实水平。后来凡是拿到类似的代码资料我都会先看三样东西有没有固定random_state、有没有把预处理写进 Pipeline、有没有用交叉验证选参而不是拿单次划分硬调。这份红酒产地预测代码按这个顺序读、改、验至少能帮你省掉一整轮翻车。希望帮到你。本文还有配套的精品资源点击获取