简介:面向MATLAB数据分析与机器学习初学者的工具库使用说明文档,系统梳理Statistics and Machine Learning Toolbox的安装检查与核心功能模块,涵盖描述性统计、假设检验、方差分析、回归分析、聚类分析以及数据预处理等环节。文档重点演示两个典型实战案例:一是基于fitlm函数的房屋价格线性回归建模,从数据准备、模型拟合到回归线可视化完整呈现;二是通过kmeans函数对随机二维数据执行K均值聚类,包含聚类数设置、中心点提取与分组结果展示,帮助读者直观理解监督学习与无监督学习的核心操作。资源仅含1个doc文件,约33KB,内容集中,适合作为课堂辅助、科研入门或工程速查手册。目前已有1412人学习下载,是一份轻量而实用的MATLAB统计与机器学习参考资料。
1. 为什么 Statistics and Machine Learning Toolbox 是 MATLAB 里被低估最多的工具箱
你在 MATLAB 里做数据分析和建模时,很大概率已经装了 Statistics and Machine Learning Toolbox,但八成只用到其中一两个函数——比如mean、std或者histogram。实际上这个工具箱把描述统计、假设检验、分布拟合、回归、分类、聚类、降维和模型部署全部收在了同一套架构里,最大的价值不是单个函数,而是让你从原始表格数据一路走到可部署模型,全程不用切语言。这篇笔记面向两类人:一类是刚接手数据分析任务、想知道该从哪个函数入手的工程师;另一类是已经在用其他语言做过机器学习、想评估 MATLAB 这套方案值不值得投入的人。我按自己实际做项目的顺序来写:先讲统计部分怎么落地,再讲机器学习建模流程,最后把模型导出到千代田外,顺便把最容易翻车的几个坑摊开讲。
2. 用 fitlm、fitdist 和 ttest 把统计部分跑起来:回归、分布与假设检验的最小闭环
2.1 fitlm 做回归建模:表格数据进,诊断图出
回归是统计工具箱里最常用的入口。我一般用内置的carbig数据来演示,因为它自带缺失值和混合类型变量,比较接近真实工程数据。
% 加载汽车数据,包含重量、马力、加速度和油耗 load carbig % 先处理缺失值:删除任一字段为 NaN 的行,避免拟合时报错 data = table(Weight, Horsepower, Acceleration, MPG); data = rmmissing(data); % 拟合线性模型:MPG 作为响应,其余三个变量作为预测变量 mdl = fitlm(data, 'MPG ~ Weight + Horsepower + Acceleration'); % 输出模型摘要:系数、p 值和 R 方都在里面 disp(mdl) % 画四张诊断图:残差 vs 拟合值、正态概率图等 plot(mdl)这里fitlm第一参数可以传 table 也可以传矩阵,但传 table 有一个实际好处:模型摘要里会直接显示变量名,而不是x1、x2。公式'MPG ~ Weight + Horsepower + Acceleration'的写法来自 Wilkinson 符号语法,~左边是响应变量,右边是预测项。如果你不做特征筛选、想用全部列,可以直接写成fitlm(data, 'MPG ~ Weight + Horsepower + Acceleration')。
disp(mdl)输出的表格里,最值得先看的是pValue和R-squared。p 值大于 0.05 的预测变量在统计意义上不显著,可以考虑剔除后重新拟合。我习惯用step(mdl)做逐步回归,它会基于 AIC 自动加项或去项,省去手动来回试的时间。另一个容易忽略的是plot(mdl)生成的诊断图:如果残差随拟合值呈喇叭口发散,说明方差非恒定,此时要先考虑对响应做对数变换,或者换用稳健回归fitlm(..., 'RobustOpts', 'on'),而不是急着加高阶项。
提示:
fitlm默认把字符型变量识别为分类变量,但如果你读入的是数值编码的类别列,它不会自动知道这是类别。这种情况要么用categorical()显式转换,要么在拟合时用'CategoricalPredictors'参数指定列号。
2.2 fitdist 做分布拟合:参数估计和置信区间一次拿全
很多做可靠性分析或者金融风险建模的读者,真正需要的不是回归,而是先搞清楚数据服从什么分布。fitdist配合Distribution Fitter应用可以完成这件事,但命令行方式更适合批量处理。
% 生成一组近似正态的样本,演示拟合流程 rng(42); % 固定随机种子,保证结果可复现 x = randn(1000, 1) * 1.5 + 10; % 均值10,标准差1.5 % 拟合正态分布,返回对象里带 mu 和 sigma 的估计值 pd = fitdist(x, 'Normal'); % 查看估计参数和置信区间 ci = paramci(pd); fprintf('mu = %.3f, 95%% CI = [%.3f, %.3f]\n', pd.mu, ci(1,1), ci(1,2)); % 用 KS 检验判断拟合是否合理 [h, p] = kstest((x - pd.mu) / pd.sigma);fitdist支持的分布很多,'Normal'、'Logistic'、'Weibull'、'Gamma'是常见选项。输出对象pd是一个ProbabilisticDistribution对象,可以直接传给cdf、pdf、icdf做后续计算,不用重新读参数。paramci输出的置信区间基于最大似然估计的渐近正态性,样本量小于 50 时区间会偏窄,这时候我更建议用bootstrap方法计算区间,工具箱里的bootci可以直接配合使用。
做金融建模的读者可能会问 Heston 模型这类随机波动率模型怎么和这个工具箱结合。我的做法是用fitdist先拟合标的收益率的边缘分布,拿到参数作为 Heston 模型校准的初始值,再丢给优化器继续迭代。这样至少保证起点合理,大幅减少优化不收敛的概率。kstest这个是基础版检验,它会用你传入的分布参数重新标准化数据,如果 p 值小于 0.05 说明数据和假设分布差异显著,要考虑换分布族,而不是强行继续。
2.3 ttest 假设检验:搞清楚 p 值怎么用,少走一半弯路
假设检验在工程上最常见的场景是工艺改进前后的对比。工具箱里的ttest、ttest2、anova1分别对应单样本、双样本和单因素方差分析,够覆盖绝大多数需求。
% 模拟两组数据:一组是原始工艺,一组是改进后工艺 before = [5.1 4.9 5.3 5.0 4.8 5.2]; after = [5.6 5.4 5.7 5.5 5.8 5.6]; % 双样本 t 检验,不假设方差相等 [h, p, ci, stats] = ttest2(before, after, 'Vartype', 'unequal'); if h == 1 fprintf('差异显著,p = %.4f,均值差95%%置信区间 [%.3f, %.3f]\n', p, ci(1), ci(2)); else fprintf('差异不显著\n'); endttest2返回的h是布尔值,h = 1表示在 5% 显著性水平下拒绝原假设。这里的'Vartype', 'unequal'对应 Welch 检验,比默认的方差相等假设更稳健,我建议默认就加上。ci是均值差的置信区间,实际报告里这个比 p 值更有信息量——区间不包含 0 才说明差异可靠。
一个常被忽略的点是:ttest2是参数检验,对正态性有一定要求。如果样本量小且分布明显偏态,换成 Wilcoxon 秩和检验ranksum更合适。工具箱里这两个函数都很容易替换,不需要改数据格式。另一种坑是三重循环里每次调用ttest2都打印结果,日志文件巨大。我一般把 p 值先存到数组里,最后统一做多重比较校正,用fdr_bh(需要自己实现)或者bonferroni手动校正。工具箱没有内置 FDR,但在新版里anova相关的多重比较可以用multcompare结合tukey-kramer做,这个对方差分析后的两两对比特别有用。
2.4 数组还是表格:数据组织形式决定后面省不省事
这个小节单独拿出来,是因为我见过太多人在这上面吃亏。fitlm、fitcsvm、fitctree这些函数同时支持数值矩阵和table输入,但用矩阵时你会丢失变量名和数据类型信息,一旦数据里有类别变量,就很容易翻车。
% 数组方式:读入 CSV 后直接转矩阵 raw = readmatrix('data.csv'); % 如果某一列是文本,readmatrix 直接报错 % 表格方式:保留变量名和类型 tbl = readtable('data.csv'); % 混合类型也能读 % 表格取列子集,等价于 A(:, [1 3 5]) 的风格 sub = tbl(:, {'feature1', 'feature3', 'label'});readmatrix遇到文本列会整体报错,而readtable会把文本列识别为string或cell,保留了原始语义。我现在的习惯是:只要数据来源是 CSV 或 Excel,一律先readtable,再在建模函数里直接用 table 形式训练。这样做有两个好处:一是和fitcsvm这类函数配合时,PredictorNames自动填好,画图时直接显示变量名;二是后面做特征选择或者可解释性分析时,不需要再维护一份列名映射表。
如果你已经有一个矩阵A和对应的列名元胞数组,可以用array2table(A, 'VariableNames', names)快速转换。反过来,table2array可以提取数值部分,但会丢掉类别列,使用前要确认不会影响后续计算。数据处理流程上,我通常把清洗逻辑集中在脚本前部:加载、去空、类型转换、拆分训练测试集,后面所有建模代码都只依赖这个统一格式的 table,避免每个模型单独处理一遍数据。
3. 机器学习建模实操:用分类学习器快速摸方案,用命令行落地生产代码
3.1 classificationLearner 应用:拖拽式建模的正确打开方式
如果你的项目还在方案探索阶段,没有定最终模型类型,直接在命令行里写fitcsvm、fitctree逐个试是浪费时间的。工具箱自带的classificationLearner应用可以让你在几分钟内对比十几种分类器,关键输出是每种模型的验证准确率和混淆矩阵。
% 打开分类学习器,导入已准备好的 table classificationLearner应用打开后,左侧选择数据源,指定预测变量和响应变量,然后勾选候选模型。我建议第一次跑的时候把“所有快速模型”勾上,先看整体准确率分布,再点开表现最好的两三个看混淆矩阵。这里有个操作细节:应用默认使用交叉验证,验证折数可以在“高级”里调整,数据量小的时候从 5 折改成 10 折会略微提升可信度,但训练时间也线性增加。
classificationLearner一方面是探索工具,另一方面是代码生成器。等你在界面上调好超参数、确认模型效果后,右侧“导出模型”可以生成训练代码,这样后续流程就变成了:界面探索 → 导出代码 → 微调 → 集成到你的脚本里。这个流程特别适合新手,因为你不用记住每个分类器的参数名,先靠界面建立直觉,再回到命令行深入。但要注意,界面导出的代码通常包含整个训练过程的完整复现,如果你只是想在预测脚本里加载模型,导出类型要选“导出紧凑模型”,或者直接用exportCompactModel。
3.2 fitcTree 和 fitcsvm 命令行训练:最小可运行的训练与预测闭环
界面探索完方案后,最终要落成可维护的脚本。这里给一个最小闭环:训练决策树和 SVM,交叉验证,评估,保存模型。
% 用 fisheriris 鸢尾花数据做演示,这是工具箱内置的标准数据集 load fisheriris X = meas; % 150x4 的数值特征 Y = species; % 150x1 的类别标签 % 划分训练集和测试集,保持类别比例 cv = cvpartition(Y, 'HoldOut', 0.2); Xtrain = X(training(cv), :); Ytrain = Y(training(cv), :); Xtest = X(test(cv), :); Ytest = Y(test(cv), :); % 训练决策树:限制叶子大小为 5,防止过拟合 treeMdl = fitctree(Xtrain, Ytrain, 'MaxNumSplits', 20, 'MinLeafSize', 5); % 训练 SVM:RBF 核 + 自动标准化,这两个参数对结果影响巨大 svmMdl = fitcsvm(Xtrain, Ytrain, 'KernelFunction', 'rbf', 'Standardize', true); % 预测并评估 labelsTree = predict(treeMdl, Xtest); labelsSvm = predict(svmMdl, Xtest); % 查看准确率 fprintf('决策树准确率: %.2f%%\n', mean(labelsTree == Ytest) * 100); fprintf('SVM 准确率: %.2f%%\n', mean(labelsSvm == Ytest) * 100);cvpartition(Y, 'HoldOut', 0.2)按响应变量的类别比例分层划分,比randperm手动拆更稳,尤其类别不平衡时。fitctree里我固定了MinLeafSize,这是决策树最重要的正则化参数——值越大树越浅,抗过拟合能力越强。fitcsvm里Standardize这个参数是新手最容易漏掉的:SVM 对特征尺度极其敏感,如果特征量级差十倍,量级大的特征会直接主导决策边界,结果就是小尺度特征完全失效。
这段代码里没有对训练集做交叉验证,直接用了固定划分。数据量较小时,单次划分的评估结果方差很大,我建议配合后面的交叉验证一起看。
3.3 特征选择与交叉验证:不要把所有特征一股脑丢进模型
特征多不见得是好事,尤其线性模型和距离类模型。工具箱里内置了fscmrmr(最小冗余最大相关)和fsrftest(基于 F 检验),可以帮你排序特征重要性。
% 对训练集做 MRMR 特征选择 idx = fscmrmr(Xtrain, Ytrain); % 输出特征重要性排序 disp(idx); % 取前 2 个最重要的特征重新训练 XtrainSel = Xtrain(:, idx(1:2)); XtestSel = Xtest(:, idx(1:2)); svmMdlSel = fitcsvm(XtrainSel, Ytrain, 'KernelFunction', 'rbf', 'Standardize', true); predSel = predict(svmMdlSel, XtestSel); fprintf('选择特征后的 SVM 准确率: %.2f%%\n', mean(predSel == Ytest) * 100);fscmrmr返回的是特征索引,按重要性降序排列。它不直接告诉你要取前几个,所以我会写一个简单循环:依次取前 1、2、3…k 个特征训练模型,画准确率随特征数的曲线,观察在哪里达到平台期。这个曲线的解读很直观:准确率上升后趋平,说明多出来的特征是冗余的;如果准确率下降,说明后面几个特征在引入噪声。
交叉验证方面,crossval函数可以复用已经训练好的模型模板:
rng(42) % 对 SVM 模型做 5 折交叉验证 cvMdl = crossval(svmMdl, 'KFold', 5); % 查看每一折的错误率,再求平均 err = kfoldLoss(cvMdl); fprintf('5 折交叉验证平均错误率: %.2f%%\n', err * 100);crossval对fitcsvm的模型对象直接可用,返回一个ClassificationPartitionedModel对象。kfoldLoss的默认损失是误分类率,如果你想看每个类的召回率,可以改用kfoldLoss(cvMdl, 'Mode', 'individual')或直接kfoldPredict手动算混淆矩阵。这个步骤的价值在于:单次 HoldOut 的结果可能运气成分大,交叉验证的误差均值更接近模型真实水平。
提示:
fitcsvm的OptimizeHyperparameters参数可以自动搜参,但自动搜参的时间成本不小。我通常先用fscmrmr降维,再在降维后的特征上搜参,搜索空间小很多,收敛也快。
4. 模型效果好不好的评判标准:混淆矩阵、ROC 曲线和自动调参
4.1 混淆矩阵与 ROC 曲线:这两个图不看等于白做
很多人报模型效果只说“准确率 95%”,但这个数字在类别不平衡时是骗人的。工具箱里confusionchart和rocmetrics可以把这个真相直接画出来。
% 沿用上一章训练好的 SVM 模型 % 生成测试集预测和得分(得分用于 ROC 曲线绘制) [labelsPred, score] = predict(svmMdlSel, XtestSel); % 画混淆矩阵:行是真实类别,列是预测类别 figure; confusionchart(Ytest, labelsPred); % 计算 ROC 曲线和 AUC,用 rocmetrics 统一处理多分类 rocObj = rocmetrics(Ytest, score, classnames(svmMdlSel)); figure; plot(rocObj);confusionchart会显示每个格子里的计数,对角线越亮越好。多分类时,它还能自动汇总真正例率、假正例率这些指标。rocmetrics是 R2020a 之后引入的类,传入真实标签、得分矩阵和类名列表,它会自动为每个类别画一条 ROC 曲线并计算 AUC。AUC 的解释有一个实际用途:当 AUC 接近 0.5,说明模型基本在瞎猜,此时哪怕准确率 90% 也是因为类别不平衡造成的假象。
4.2 超参数搜索的两个层次:网格搜索到贝叶斯优化
模型中这些超参数试起来很花时间:SVM 的BoxConstraint和核尺度、决策树的MinLeafSize、随机森林的NumLearners。工具箱提供了从手动到自动的两档方案。
% 第一档:手动网格搜索,对决策树的 MinLeafSize 做循环 leafSizes = [1 5 10 20]; for i = 1:length(leafSizes) mdl = fitctree(Xtrain, Ytrain, 'MinLeafSize', leafSizes(i), 'CrossVal', 'on'); err(i) = kfoldLoss(mdl); end [minErr, bestIdx] = min(err); fprintf('最优 MinLeafSize = %d,错误率 = %.3f\n', leafSizes(bestIdx), minErr); % 第二档:贝叶斯自动优化,让工具箱帮你搜 SVM 参数 svmOpt = fitcsvm(Xtrain, Ytrain, 'KernelFunction', 'rbf', 'Standardize', true, ... 'OptimizeHyperparameters', {'BoxConstraint', 'KernelScale'}, ... 'HyperparameterOptimizationOptions', struct('AcquisitionFunctionName', 'expected-improvement-plus', 'MaxObjectiveEvaluations', 30));网格搜索的价值在于可解释:你知道每个超参数档位对应什么结果,适合参数少的情况。自动优化的价值在于省时间,适合参数多、手工调不过来的情况。fitcsvm的自动优化默认支持BoxConstraint(误分类惩罚权重)和KernelScale(RBF 核宽度),这两者共同决定了 SVM 的决策边界复杂度。30 次目标函数评估在数据量几千行的场景大约几分钟到十几分钟,可以接受。
自动优化跑完后,用svmOpt.HyperparameterOptimizationResults查看搜索过程,重点看BestPoint对应的参数值,以及每个参数在搜索区间内的敏感度。如果你发现某个参数范围内目标函数几乎没变化,说明模型对这个参数不敏感,后续可以固定中间值,减少搜索维度。]]注意:优化过程使用交叉验证评估,所以训练样本越少,评估噪声越大,最优结果可能不稳定。数据量小时,我会把MaxObjectiveEvaluations调小到 15,避免模型过拟合调参集。
5. 新手最容易踩的 5 个坑:现象、原因和解决办法
5.1 类别变量被当成连续数值,模型结果面目全非
现象:模型训练完成,准确率看着还行,但画出决策边界后发现某个类别被完全忽略,或者变量重要性排名里一个明显是类别的列排最前。
原因:readtable读入的数据,数值编码的类别列(比如 0/1/2 代表三个等级)被当成数值特征。决策树即使能处理数值和类别混合,底层分裂逻辑也不同,SVM 这种距离模型更是把 0、1、2 当成了真的数值大小,强行计算欧氏距离。
解决:建模前显式声明。用categorical()转换列,再传给模型。对于table输入,也可以用fitctree(tbl, 'CategoricalPredictors', [2 5])按列号指定。我自己的习惯是在数据清洗阶段把所有类别列的 dtype 一次性规范化,统一存成categorical,这样后续不管用哪个模型都不会再踩这个坑。
5.2 归一化泄漏:训练测试混合归一化,准确率虚高
现象:测试集上准确率高得离谱,但部署到真实数据上效果崩盘。
原因:典型的归一化泄漏。你先把整个数据集zscore归一化,再划分训练集和测试集。模型训练时,测试集的均值和方差已经被模型“见过”,等于开卷考试。真实场景里新数据是陆续到的,它的统计量不可能和训练集完全一样。
解决:先划分,后归一化。划分完成后,只用训练集计算均值和标准差,用同一组统计量去变换测试集。工具箱里可以这样:
mu = mean(Xtrain); sigma = std(Xtrain); XtrainNorm = (Xtrain - mu) ./ sigma; XtestNorm = (Xtest - mu) ./ sigma;也可以用fitcsvm的Standardize参数,它内部只用训练数据计算标准化参数,不存在泄漏问题。手动做归一化时,记住一个原则:任何从测试集计算出来的东西都是泄漏,包括均值、方差、缺失值填充用的中位数。
5.3 类别不平衡不处理:准确率 95%,少数类全部误判
现象:测试集准确率 95%,但打开混淆矩阵发现占比小的类别几乎全部被预测成大类。
原因:模型目标是整体准确率,少数类样本太少,错分它们对整体损失贡献小,所以模型干脆全预测大类。
解决:三选一。最简单的是在fitcsvm中设置'Prior', 'balanced',让模型按类别逆频率加权。第二步是用'Cost'矩阵,手动指定错分少数类的惩罚倍率。第三步才是采样法,比如用datasample对少数类做 bootstrap 复制,或者用unbalanced相关的算法(SMOTE 需要自己实现)。我测试过的项目里,'Prior', 'balanced'通常能带来最大的提升,且改动最小;调 Cost 矩阵需要不断试倍率,见效慢但更精细。
5.4 fitcsvm 默认不做标准化:隐式假设害死距离类模型
现象:数据里某个特征单位是千米、另一个是毫米,训练出来的 SVM 决策面几乎只垂直于千米那个特征。
原因:SVM 的 RBF 核通过欧氏距离计算样本相似度,量级大的特征会支配距离值。默认情况下fitcsvm不做标准化,完全依赖用户自己处理。
解决:忘掉手动 zscore 的繁琐,直接在fitcsvm里加'Standardize', true,或者用'KernelFunction', 'Polynomial'时配合'PolynomialOrder'自己控制。除了 SVM,kmeans聚类和pca也都对尺度敏感,前者可以在训练前zscore,后者用'VariableWeights', 'variance'让 PCA 自动均衡特征方差。记住一句话:距离和方差相关的模型,先标准化;树模型,无所谓。
5.5 直接 save 模型对象,部署时才发现带不动
现象:模型训练结束,你敲了一句save('model.mat', 'mdl'),模型文件几个 GB,同事打不开,或者部署到嵌入式环境报错说找不到类。
原因:save保存了整个模型对象,包括训练数据、超参数搜索过程的中间信息、类定义依赖。fitcsvm返回的对象体积远大于真正做预测所需的最小信息。
解决:训练完成后先压缩再保存。正确姿势是两步:
% 压缩模型:丢掉训练数据,只保留预测所需的信息 compactMdl = compact(svmMdl); % 导出为单文件格式,便于部署和分享 exportCompactModel(compactMdl, 'trainedSVM');exportCompactModel(R2019a 之后版本)会把模型打包成一个文件,同时生成配套的加载函数。新版本里还有saveCompactModel和loadCompactModel,直接保存到.mat文件更省事。部署到 Simulink 或 C 代码生成时,使用 compact 格式是硬性要求,完整模型对象不受支持。如果踩了“找不到类”的报错,绝大多数情况是你直接 save 了非 compact 对象,尤其是 tree 或者 ensemble 类的模型。
6. 模型导出到 Simulink 与 C 代码:把 MATLAB 里的原型变成能跑的东西
建模做到最后一步,往往不是桌面上的准确率图表,而是把模型交到别人手里运行。工具箱支持把训练好的模型直接导出为 Simulink 模块,或者通过 MATLAB Coder 生成 C 代码。这里给一个最常用的 C 代码生成流程:
% 已有 compact 模型文件 trainedSVM.mat load('trainedSVM.mat'); % 恢复变量 trainedSVM(实际上是 CompactClassificationSVM 对象) % 定义一个只包含预测逻辑的入口函数 % 该函数输入特征向量,输出类别标签和分数 type('predictSVM.m'); % 假设 predictSVM.m 已写好,内部调用 predict(trainedSVM, X) % 生成可执行 MEX 文件,验证算法一致性 codegen -config:mex predictSVM.m -args {zeros(1, 4)}codegen的-args必须给定输入类型模板,这里zeros(1, 4)表示输入是 1x4 的 double 向量。生成的 MEX 文件可以在 MATLAB 里直接调用,验证数值结果和原始predict一致。如果代码中包含table格式的数据处理,codegen通常不支持,需要在入口函数里先把table转成数值矩阵。我的经验是:入口函数越薄越好,只保留“输入 -> 调用 predict -> 输出”,所有预处理都放模型外部。这样既满足代码生成要求,也便于后续用 C++ 或其他语言重写。实际项目里,模型部署往往不是技术难点,而是边界条件处理,比如输入缺失值怎么办、输出置信度阈值怎么定。这些逻辑要写在入口函数里压测,而不是等到对方集成时才暴雷。
用这个工具箱这几年,我最深的教训是:参数只给结果,不给业务的解释框架。SVM 调出了一组漂亮指标,但如果说不清每个特征对决策的影响,项目验收时还是会被挑战。后来我在每次训练完都会额外输出特征排序和 SHAP 风格的重要性图(用fitcensemble自带的predictorImportance),把“模型为什么这么判”挂在嘴边,反而省掉了大量沟通成本。这套流程如果你能照着走一遍,从readtable到codegen的链路就通了。希望帮到你。
本文还有配套的精品资源,点击获取