MATLAB葡萄酒数据集分类实战:从90%到99%准确率的全流程指南 说实话MATLAB里拿葡萄酒数据集做分类基本是每个刚接触模式识别的人都会撞上的经典项目。但正因为它太经典了反而很少有人把它讲透数据集怎么读、用什么分类器、为什么用那个分类器、准确率卡在90%上不去时该查哪里。这篇不打算复读教科书就按我自己从下载数据到跑通全流程再到把分类准确率从90%提到99%的完整经历来写。该填的坑、该避的雷、该有的代码和参数一次说清楚。1. 先认识UCI Wine数据集178瓶葡萄酒和3个品种1.1 数据集的身世与结构葡萄酒分类数据集来自UCI机器学习库UCI Machine Learning Repository最早由意大利的科研人员对同一地区种植的三种葡萄酒进行化学分析后整理出来的。整个数据集一共178个样本每个样本有13个化学特征标签是3个葡萄酒品种。这里有个细节值得多说一句数据里的178个样本并不是随机抽取的三种酒的样本数分别是59、71、48数量并不均衡。做分类时如果忽略这一点直接用准确率当唯一指标很可能会被“表面高分”骗过去——比如某个模型把所有样本都预测成数量最多的那类准确率也能有40%上下。后面我会专门讲这个事。13个化学特征分别是Alcohol酒精含量Malic acid苹果酸含量Ash灰分Alcalinity of ash灰分碱度Magnesium镁含量Total phenols总酚含量Flavanoids黄酮类化合物含量Nonflavanoid phenols非黄酮类酚含量Proanthocyanins原花青素含量Color intensity颜色强度Hue色调OD280/OD315 of diluted wines稀释葡萄酒的OD280/OD315吸光度比值Proline脯氨酸含量从特征类型看这批数据分两类。一类是浓度、含量类的数值特征比如酒精、镁、脯氨酸一类是光学测量值比如颜色强度、吸光度比值。这两类特征在数值量纲和分布形态上差异很大后面做z-score标准化时就特别容易踩坑。1.2 为什么这个数据集适合入门对比同样出名的鸢尾花数据集葡萄酒数据集有几个特别适合练手的特点。第一类别数是3比二分类复杂一点但又不会像图像分类动辄上百类那样劝退新手多分类的流程可以完整跑一遍。第二特征维度13维比鸢尾花的4维高很多足够体验特征选择、降维这些操作的价值又远没到需要深度网络来兜底的程度。第三数据本身质量很高几乎没有什么缺失值和异常值这意味着你能把主要精力放在分类方法本身而不是花大量时间做数据清洗——对初学阶段的人来说这是最友好的地方。我在很多场合提过一个观点入门分类项目的最佳难度应该恰好是“用朴素方法能跑通但想得到更好结果又必须动脑子调优”。葡萄酒数据集恰好就处在这个位置。线性判别分析LDA直接跑就能有不错的准确率但每次交叉验证的方差却不小真要稳定地拿到98%、99%的成绩就必须考虑特征缩放、核函数选择甚至集成学习。这样一个数据集足够让人体会到“分类不是把数据喂给模型就完事”。2. 数据集进MATLAB的三条路径内置、CSV与在线直读2.1 路径一MATLAB内置方式先辟个谣MATLAB内置的经典分类数据集中只有鸢尾花fisheriris和费米数据fisheriris、pima等并没有直接叫“wine”的数据集。很多人一上来就load wine结果报错“未找到文件或目录”其实是因为惯性思维把R语言或者Python的经验搬过来了。如果只是想先验证一下代码流程可以用鸢尾花数据集替代命令是load fisheriris % 读出来后meas是150x4的特征矩阵species是150x1的类别元胞数组但注意鸢尾花只有4维特征而且类别是完全线性可分的用它调通流程后再切回葡萄酒数据集你会发现同样的分类器代码表现差异很大。我的建议是直接把葡萄酒数据集的csv下载下来按接下来讲的方式读取这样整个流程才不会在数据集切换时出现意外。2.2 路径二从UCI官网下载CSV并用readtable导入UCI官网上的葡萄酒数据集下载页面可以直接找到wine.data这个文件。需要注意一个问题官网原始文件的列之间用逗号分隔但第一列是类别标签不是特征。下载后用MATLAB的readtable读取代码很简洁% 假设wine.data和脚本在同一文件夹下 wineTable readtable(wine.data, FileType, text, ReadVariableNames, false); % 因为原始文件没有表头所以要明确告诉MATLAB第一行不是变量名读进来之后表格默认的列名是Var1到Var14。其中Var1是标签Var2到Var14是特征。为了方便后面写代码可以把它们分成两个变量labels wineTable.Var1; features table2array(wineTable(:, 2:end));这里我强烈建议把labels转成分类变量类型也就是categorical后面调用分类器、画混淆矩阵都不容易出类型错误labels categorical(wineTable.Var1);一个小教训有些教程喜欢直接用Var1这个列索引但如果你后续要做特征工程比如删除某些特征列或者给特征重命名索引位置就会变。最稳妥的方式是在数据读取完成之后立刻把标签和特征拆成两个变量这是吃过亏之后的经验。2.3 路径三用webread在线读取不想手动下载文件的话可以直接用webread把数据从UCI的URL拉下来。前提是网络能访问到UCI网站代码也比较简单url https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data; dataStr webread(url); % 把拿到的字符串数据按行拆分再按逗号解析 lines splitlines(strtrim(dataStr)); dataNumeric zeros(numel(lines), 14); for i 1:numel(lines) row str2double(strsplit(lines{i}, ,)); dataNumeric(i, :) row; end labels categorical(dataNumeric(:, 1)); features dataNumeric(:, 2:end);这条路比较适合只想快速跑通Demo的人。但我在实际使用中不太推荐在课程项目里用webread原因有两个一是UCI的服务器在国外上课环境网络状态不稳定等数据拉下来可能就要吃掉几分钟二是webread拿回来的数据一旦有编码问题排查起来非常麻烦。更稳妥的做法是上课前手动下载好wine.data让你把精力留在分类环节。提示如果读取csv后发现所有列都挤在一列里大概率是文件的中文路径问题。把数据文件挪到纯英文路径下再试一次。3. 建模前的数据体检分布、相关性与降维观察3.1 分类别统计特征分布很多教程上来就切分训练集测试集然后直接上分类器。但对葡萄酒数据集我建议大家先花10分钟做一次数据体检要不然你连分类器为什么在这个数据集上表现好都说不清楚。第一步是看各类别下特征的均值和标准差。用splitapply或者循环都可以我习惯用这样的方式classNames categories(labels); for c 1:numel(classNames) idx labels classNames(c); classMean mean(features(idx, :)); classStd std(features(idx, :)); fprintf(类别 %s: 均值 [%s]\n, classNames(c), num2str(classMean, %.2f )); end算完之后你会发现一些非常有意思的规律。比如类别1的脯氨酸Proline含量明显高于类别2和类别3类别3的总酚和黄酮类化合物含量偏低而颜色强度偏高。这些规律其实就是后面分类器建立决策边界的依据。第二步是画箱线图直观看出每个特征的类别重叠程度。我通常用boxplot函数按类别分组画比如看颜色强度在三个类别上的分布figure; boxplot(features(:, 10), labels); % 第10列是颜色强度 xlabel(类别); ylabel(Color intensity); title(不同葡萄酒类别的颜色强度分布);箱线图的重叠程度能一眼告诉你这个特征的区分能力。如果三个箱体几乎不重叠说明这个特征单独就能把类别分开如果箱体重叠得一塌糊涂那它单用肯定不行。3.2 相关性热力图是特征选择的地图13个特征之间不是独立的。比如黄酮类化合物和总酚含量往往正相关颜色强度和色调常常负相关。做一个相关矩阵热力图能帮你理解为什么有些特征单独用区分度一般但组合起来却很好用。figure; corrMatrix corr(features); imagesc(corrMatrix); colorbar; title(13维特征的相关性矩阵); set(gca, XTick, 1:13, YTick, 1:13);从相关矩阵里你能看到明显的特征聚类。通常总酚、黄酮类、原花青素这些酚类指标会聚在一起形成一团高相关的特征群。当你后面做判别分析时这团特征群等价于一个“综合酚含量指标”。理解这一点你就明白为什么PCA降维后只用两三个主成分就能保留大部分分类信息。3.3 PCA降维之后画散点图把13维数据降到2维用散点图观察类别分布这是数据体检的临门一脚。MATLAB里做PCA一行命令就能完成[coeff, score, latent] pca(features); figure; gscatter(score(:,1), score(:,2), labels); xlabel(第一主成分); ylabel(第二主成分); title(PCA降维后的样本分布);我第一次跑这个散点图时就发现类别1和类别2在主成分空间里基本是分开的但类别2和类别3有一部分点比较靠近有轻微重叠。这说明什么问题说明独立的两类特征并不能完美切分三个类别分类器必须借助更多维度信息做判断。这也直接解释了为什么线性分类器能达到高准确率但偶尔会有几个样本被误判——因为边界区域就是有天然的重叠。提示PCA之前一定要先把特征标准化。否则脯氨酸这类量纲大的特征会主导主成分方向画出来的散点图看着很干净实际上主成分被单一特征绑架了没有真实的分类价值。4. 三个分类器的横向实测LDA、决策树与SVM4.1 通用实验框架先划分后评估为了避免每个分类器写一遍重复代码我习惯先定义一个统一的实验框架。核心步骤是用cvpartition做分层划分保证训练集和测试集的类别比例与原始数据集一致。这一步很重要因为前面说了样本数量并不均衡随机划分极度偏向多数的类别会带来误导性的高准确率。rng(42); % 固定随机种子保证实验可复现 cv cvpartition(labels, HoldOut, 0.3); trainIdx training(cv); testIdx test(cv); X_train features(trainIdx, :); y_train labels(trainIdx); X_test features(testIdx, :); y_test labels(testIdx);这里固定随机种子是我特别想强调的操作。如果没有rng(42)你每次运行结果的准确率都会有波动可能上一次还是98%下一次就成了94%。固定种子后实验结论才可重复写完报告也好解释。评估环节我用混淆矩阵和准确率两个指标然后写一个简单函数统一输出function acc evaluateModel(predictions, y_test) cm confusionchart(y_test, predictions); acc sum(predictions y_test) / numel(y_test); fprintf(测试集准确率: %.2f%%\n, acc * 100); end4.2 LDA分类器为什么它在葡萄酒数据集上表现优异第一个上的分类器是线性判别分析LDAMATLAB里对应函数是fitcdiscr。ldaModel fitcdiscr(X_train, y_train); pred_lda predict(ldaModel, X_test); evaluateModel(pred_lda, y_test);实测下来直接用原始13维特征跑LDA在测试集上的准确率通常在95%到98%之间。这个成绩相当能打背后原因也很清晰LDA的核心假设是“各类别样本服从多元正态分布且协方差矩阵相同”它投影的方向是让类别间距离最大、类别内离散度最小。而葡萄酒数据集恰好满足协方差结构差异不大、类别可分性强的特点所以LDA几乎是把数据集的“底牌”吃透了。有一个值得留意的点LDA对异常值敏感。虽然葡萄酒数据集质量高但如果你自己采集的数据里有离群样本LDA的均值和协方差估计都会被带偏准确率会明显下滑。所以使用LDA前画一下各个特征的箱线图找找离群点是必要的。4.3 决策树可解释性拉满但精度看运气第二个分类器是决策树函数是fitctree。treeModel fitctree(X_train, y_train); pred_tree predict(treeModel, X_test); evaluateModel(pred_tree, y_test);决策树在葡萄酒数据集上的表现波动比较大常见在90%到95%之间。原因是葡萄酒数据集的类别边界更多依赖特征的线性组合而决策树用单个特征的阈值切分天然吃亏。但它有一个巨大的优势你能直接查看树的分支看到模型用哪些特征、在什么阈值上做判断。view(treeModel, Mode, graph);这个可视化对展示项目、写实验报告特别有用。比如我跑出来的树经常第一层就选了脯氨酸或颜色强度作为切分特征这其实和数据体检时的观察——脯氨酸含量在各类别间差异大——是一致的。每一个节点都像在说“我为什么这么判断”这是决策树独有的解释力。4.4 多分类SVM特征缩放前后天差地别第三个是支持向量机。MATLAB里多分类SVM用fitcecoc也就是一对一编码的纠错输出码。svmModel fitcecoc(X_train, y_train); pred_svm predict(svmModel, X_test); evaluateModel(pred_svm, y_test);这里我必须直说一个我踩过的坑不缩放特征直接跑SVM测试集准确率可能连80%都不到。我在一次实验中直接拿原始特征跑SVM结果准确率只有87%远不如LDA。原因在于SVM是基于距离和间隔的算法酒精含量和镁含量这类特征的数值范围差异太大数值大的特征会主导核函数计算而那些区分度其实高的微量特征反而被淹没了。处理方法是先做标准化。具体怎么做下一章专门讲顺序问题。这里先给出效果对比方法特征处理方式测试集准确率LDA原始特征约96%决策树原始特征约92%SVM线性核原始特征约88%SVM线性核z-score标准化约98%从表格能看出LDA和决策树对量纲相对不敏感因为它们不直接依赖特征距离但SVM这类算法特征缩放几乎是必需的。5. 极易翻车的特征缩放顺序先划分再标准化5.1 为什么顺序错误等于数据泄漏特征缩放看着简单但在分类任务里顺序错了会让你的评估结果虚高。常见错误做法是先对全体特征做z-score标准化再划分训练集和测试集。假设你用了全部178个样本的均值和标准差来做标准化测试集的信息就已经通过均值、标准差“泄漏”到训练阶段了。这不是严格意义上的标签泄漏但确实会让模型在实际应用中表现打折因为你部署模型时面对的新数据不可能用到整个测试集合的统计量。5.2 正确的缩放流程正确的流程分三步% 第一步在训练集上计算均值mu和标准差sigma mu mean(X_train); sigma std(X_train); % 第二步用训练集的mu和sigma去标准化训练集 X_train_scaled (X_train - mu) ./ sigma; % 第三步用同样的mu和sigma去标准化测试集千万不能重新计算 X_test_scaled (X_test - mu) ./ sigma;如果你的代码写成了(X_test - mean(X_test)) ./ std(X_test)那就错了。测试集的均值和标准差必须来自训练集这是所有标准化类操作的铁律。MATLAB 2023a之后的版本提供了更简洁的normalize函数但底层逻辑完全相同。我自己在实际课程作业里就吃过这个亏当时为了图省事在把所有数据拼接成一个大矩阵之后统一做了zscore再划分数据集。结果测试集准确率高达98.5%我还挺高兴。后来拿着模型到一个新批次的数据上验证准确率掉到了91%。一查才发现就是标准化顺序错了。5.3 标准化之后还要不要加回原始特征另一个新手常见的问题是标准化之后原来的特征含义是不是就丢了还能不能做PCA、画箱线图我的答案是标准化后的特征只是调整了尺度和原点它和原始特征的分布形状完全一致只是数值范围变了。你完全可以拿标准化后的特征画图、做PCA、分析相关性因为线性变换不会改变样本之间的相对关系。不过有一个例外就是决策树。决策树通过阈值切分来做决策它对特征数值的绝对大小并不敏感。你用原始特征跑决策树和用标准化特征跑决策树理论上得到的是同一棵树或者等价的切分。所以我在上面的实验里没有对决策树做标准化这也是合理的你可以看到标准化的核心意义在不同算法那里并不相同。提示用fitcecoc跑多分类SVM时最好也顺手设置Standardize, true这个参数这样MATLAB会在内部自动做标准化但依旧要小心它内部的实现是否用了整个数据集。安全起见我还是建议自己先划分再手工缩放。6. 从90%到99%调参、集成与决策面可视化6.1 支持向量机的核函数选择和网格调参完成基础实验后真正拉开差距的是调参环节。以SVM为例线性核在标准化后的数据集上已经能到98%左右但如果换成RBF核并调整两个关键参数——BoxConstraint框约束控制误分类惩罚和KernelScale核尺度准确率还有进一步的提升空间。svmRbf fitcecoc(X_train_scaled, y_train, ... Learners, templateSVM(KernelFunction, rbf, ... BoxConstraint, 1, KernelScale, 1.5));网格搜索是找参数最朴素但也最稳妥的办法boxGrid [0.5, 1, 5, 10]; scaleGrid [0.5, 1, 2, 5]; bestAcc 0; for b boxGrid for s scaleGrid mdl fitcecoc(X_train_scaled, y_train, ... Learners, templateSVM(KernelFunction, rbf, ... BoxConstraint, b, KernelScale, s)); pred predict(mdl, X_test_scaled); acc sum(pred y_test) / numel(y_test); if acc bestAcc bestAcc acc; bestParams [b, s]; end end end fprintf(最优参数: BoxConstraint%g, KernelScale%g, 准确率%.2f%%\n, ... bestParams(1), bestParams(2), bestAcc * 100);在RBF核的加持下测试集准确率经常能到99%甚至100%。但这里要提醒一句测试集只有53个样本左右准确率从98%到100%可能只差一个样本的预测结果并不代表模型有了质的飞跃。报告里要把混淆矩阵和交叉验证结果一起展示别只给一个孤零零的准确率百分比。6.2 集成学习Bagging和AdaBoostM2的实际体验如果还想往上推试试集成学习。MATLAB的fitcensemble封装了Bagging、AdaBoostM2等主流方法bagModel fitcensemble(X_train_scaled, y_train, ... Method, Bag); pred_bag predict(bagModel, X_test_scaled); evaluateModel(pred_bag, y_test); adaModel fitcensemble(X_train_scaled, y_train, ... Method, AdaBoostM2); pred_ada predict(adaModel, X_test_scaled); evaluateModel(pred_ada, y_test);实测中Bagging对决策树的提升很明显准确率大约能从92%提到96%以上AdaBoostM2通常也能稳定在97%到99%的区间。但代价是训练时间比单分类器长而且模型的可解释性大幅下降。我觉得对葡萄酒数据集而言集成方法更像是一个“锦上添花”的验证步骤验证你理解了集成思想而不见得是解决问题的必经之路。6.3 把决策边界画出来报告顿时有说服力最后分享一个让项目报告脱胎换骨的小技巧把决策边界可视化。虽然完整特征空间是13维的没法直接画但只要选取两个最有区分度的特征通常选脯氨酸和颜色强度或者PCA的前两个主成分就可以在二维平面上把分类器的决策区域画出来。% 选两个特征 pairIdx [7, 10]; % 第7列是黄酮类化合物第10列是颜色强度 X_pair X_train_scaled(:, pairIdx); svmPair fitcecoc(X_pair, y_train, ... Learners, templateSVM(KernelFunction, rbf)); % 生成网格点 [x1Grid, x2Grid] meshgrid(linspace(min(X_pair(:,1)), max(X_pair(:,1)), 200), ... linspace(min(X_pair(:,2)), max(X_pair(:,2)), 200)); gridPoints [x1Grid(:), x2Grid(:)]; predGrid predict(svmPair, gridPoints); % 画决策区域和样本点 figure; gscatter(x1Grid(:), x2Grid(:), predGrid, [0.8 0.8 0.8; 0.9 0.7 0.7; 0.7 0.9 0.7]); hold on; gscatter(X_pair(:,1), X_pair(:,2), y_train, krb); xlabel(黄酮类化合物标准化); ylabel(颜色强度标准化); title(SVM在二维特征空间的决策边界);这张图一出来整个项目的完成度就能提升一个档次。因为它直观解释了“为什么有些样本会误分类”比如类别2和类别3在边界区域的点混在一起画出来之后你能一眼看到误分类样本基本都集中在两个类别区域的交界处。7. 进阶方向与我的几点实操体会7.1 换个思路把13维特征当作深度学习网络的输入如果你做完前面的步骤还有余力可以尝试搭建一个简单的神经网络分类器。MATLAB里用fitcnet可以直接训练一个前馈神经网络netModel fitcnet(X_train_scaled, y_train); pred_net predict(netModel, X_test_scaled); evaluateModel(pred_net, y_test);神经网络的好处是不需要手动设计特征组合它能自动学习特征之间的非线性交互。但在葡萄酒数据集这样的小样本任务里神经网络的优势不明显很容易过拟合所以我更推荐把它作为“感受不同模型风格”的选修项目而不是主推方案。7.2 一点真实的经验教训最后说几个我在实际跑这个项目时留下的真实体会。第一不要把准确率当成唯一指标。我刚接触这个数据集时一度追着100%跑后来发现只要把RBF核的参数调得极端一点测试集可以做到100%但换一个随机种子重新划分数据立刻掉回95%。真正稳的模型是那种在多次不同随机划分下准确率波动小、平均成绩高的模型而不是某一次运气好冲上100%的模型。第二建议每次实验换不同的随机种子多跑几次。rng(42)保证可复现但如果只跑一次也可能被局部运气欺骗。我的习惯是分别用rng(42)、rng(7)和rng(2024)各跑一遍然后报告平均准确率和波动范围。这样的实验设计在答辩时基本挑不出毛病。第三MATLAB的交互式工具值得用起来。在命令行输入classificationLearner能打开分类学习器App把特征和标签导入后它能一次性跑十几个分类器并自动评估。我当时就是先在这个App里快速筛出了LDA和SVM两个最有潜力的算法然后才回到脚本里精调参数的。先用App缩小范围再用脚本精雕细琢效率最高。第四如果以后想把这个数据集应用到真实酒类鉴定场景要注意UCI Wine数据的采集条件和现代酒类样本的差异。实验室数据和实际生产数据的分布往往不完全一致模型在历史数据上再准拿到新设备、新产区、新工艺下的样本时也可能失灵。这就是为什么特征工程和模型验证永远比追求测试集100%更重要。