在信号处理、传感器数据分析和工业检测里,我们经常碰到这种场景:手上攒了一批采样点,每个点只有一个数值特征——可能是温度、电压、振动幅值或者光强,现在要根据这个数值把样本分成两个类别。比如判断电机当前是正常还是异常,区分钢管焊缝合格还是开裂。这类问题听起来简单,真做起来却也有不少讲究:阈值定在哪里最合理?样本重叠区怎么处理?分类器该选谁?
这篇博文就围绕“基于 MATLAB 的一维数据二分类”这个项目,把从数据准备、可视化诊断、分类器选型到模型评估的完整流程拆开讲。内容适合正在做课程设计、毕业设计的学生,也适合刚入门机器学习、想在 MATLAB 里快速跑通一个分类任务的开发者。我不光会贴出能直接跑的代码,还会解释每一步为什么这么做、什么时候该换一种做法,以及我实际踩过的坑。
1. 项目整体设计与思路拆解
1.1 一维二分类问题的本质是什么
一维数据二分类,指的是输入特征只有一个维度,也就是每个样本用标量 x 来表示,目标输出 y 是二值标签,比如 0 和 1、正品和次品、正常和故障。很多资料喜欢拿二维平面上的点云分类做演示,那是在二维特征空间里画一条直线或曲线分界。一维问题其实是它的退化版:特征空间退化成一条数轴,决策边界不再是曲线,而是一个或多个阈值点。
如果把一维数据点在数轴上画出来,两组数据天然存在三种关系。第一类是完全可分,左边全是 A 类、右边全是 B 类,中间有明显空档。第二类是存在少量交叉,大部分样本能分开,但在交界处有几个“刺头”混在对方的阵营里。第三类是严重重叠,两组数据的分布范围大面积重合,单靠一个阈值永远无法完美分开。理解这三种情况很重要,因为它决定了后面选什么分类器、该不该做特征工程,以及最终模型的准确率上限在哪里。
这个项目里还有个容易被忽视的点:一维数据虽然“简单”,但它同样能暴露出机器学习流程中的所有关键环节。维度低不代表问题 trivial,数据的质量、标签的可靠性、验证集划分合理性,这些因素对一维问题的影响和对高维问题一样致命。把一维问题吃透了,再去做高维特征分类,思路完全是相通的。
1.2 为什么选 MATLAB 而不是 Python
做一维二分类,可选工具很多。Python 生态有 scikit-learn,R 有 caret,MATLAB 也有 Statistics and Machine Learning Toolbox。我在实践中更偏向用 MATLAB 做这类小规模分类任务,原因有几个。
MATLAB 的交互式环境对“边做边看”非常友好。一维数据本身没有什么隐藏结构,可视化是最重要的诊断手段,而 MATLAB 的绘图交互、数据光标标注和图形窗口管理非常顺手。写代码的时候,工作区里每个变量一目了然,新手不容易迷路。
MATLAB 的机器学习接口高度统一。逻辑回归、SVM、决策树、KNN 这些分类器都通过 fit 开头的一系列函数实现,调用的逻辑几乎一样:传进去特征矩阵 X、标签 Y,返回一个模型对象,再统一用 predict 函数做预测。不需要像 Python 那样在不同库之间来回切换,对于学习阶段来说,这种一致性可以大幅降低认知负担。
还有一点是文档和示例代码齐全。MATLAB 官方文档里每个分类器都有针对性的一维或多维示例,遇到问题直接 help 一下就能找到参考。对不知道“该选哪个模型”的新手来说,这种内置的引导体验是很重要的。
当然,Python 也有不可替代的优势,比如深度学习框架支持、社区库丰富、部署灵活。但就这个项目而言——一维、二分类、数据量不大、重点是理解原理和流程——MATLAB 的学习曲线更平缓,出结果更快。工具选择本身就是一种权衡,没有绝对的优劣。
1.3 技术路线总览
整个项目我按下面这条线推进,后面的章节也顺着这个顺序展开:
- 数据准备:构造或加载一维特征向量和标签,做必要的清洗和归一化。
- 可视化诊断:画出直方图、箱线图、散点分布,用肉眼判断类别可分性。
- 数据划分:用交叉验证的思路划分训练集和测试集,确保评估结果可信。
- 分类器实现:分别用逻辑回归、SVM、决策树、KNN 训练模型,对比效果。
- 模型评估:用准确率、混淆矩阵、ROC 曲线等指标综合评判。
- 常见问题排查:梳理边界重叠、样本不平衡、过拟合、数据泄漏等坑。
这条路线是分类任务的通用骨架,一维问题只是在这个骨架上填充了更简单的几何直觉。每一步都有充分的理由,后面我会在对应位置把“为什么”讲透。
2. 核心细节解析与实操要点
2.1 数据从哪里来:构造与加载的常见做法
这个项目的数据有两种常见来源。第一种是已有的真实数据,比如某公司提供了一批传感器采样值,对应着两种运行状态。这种情况下数据通常是 Excel、CSV 或 TXT 文件,列结构是“特征值 + 标签”。读取方式很简单,用 readtable 或 readmatrix 就能搞定。
% 从 CSV 读取:假设第一列是特征 x,第二列是标签 y data = readtable('sensor_data.csv'); x = data.Feature; y = data.Label;第二种来源是自己构造仿真数据,这也是我强烈建议初学者先做的。自构造数据的最大好处是“标准答案已知”,你能清楚地知道两组数据的真实分布参数,这样验证模型效果时心里有底。最常用的构造方法是高斯混合模型:从两个不同均值、不同标准差的正态分布中分别采样,再打上不同标签。
rng(42); % 固定随机种子,保证可复现 % 类别 0:均值 0,标准差 1 x0 = randn(200, 1) * 1.0 + 0; % 类别 1:均值 3,标准差 1.2 x1 = randn(200, 1) * 1.2 + 3; % 合并数据 X = [x0; x1]; Y = [zeros(200, 1); ones(200, 1)]; % 打乱顺序 idx = randperm(length(Y)); X = X(idx); Y = Y(idx);把均值间距设为 3,标准差设为 1 和 1.2,两组数据会存在一定程度的交叉,但绝大部分样本还是可分的。这个参数组合模拟的是“有噪声但工程上可处理”的真实场景,而不是一个理想化的完美分离问题。想观察过拟合或更困难的情况,可以缩小均值差到 1.5,那会儿两组数据会大面积重叠,分类器怎么调都吃力。
2.2 可视化:直方图、箱线图和散点图怎么用
一维数据的可视化是我认为整个项目中最关键的一步,它决定了你对“这个问题到底有多难”的感知。很多新手拿到数据就急着训练模型,这是完全错误的路径。一维数据的分布通过图表一眼就能看明白,没必要盲目跑模型。
直方图是最直观的方式。横轴是特征值 x,纵轴是频数,用不同颜色画两组数据的直方图并叠加,马上就能看出分类的大致规律。
figure; histogram(X(Y==0), 'FaceColor', [0.2 0.6 1], 'FaceAlpha', 0.6); hold on; histogram(X(Y==1), 'FaceColor', [0.9 0.3 0.3], 'FaceAlpha', 0.6); xlabel('特征值 x'); ylabel('频数'); legend('类别 0', '类别 1'); title('两类样本的特征分布直方图'); grid on;运行这段代码,你会看到两个山峰形状的分布,蓝色峰在左、红色峰在右,中间有一段“谷地”。这个谷地的宽度就是分类器发挥作用的余地:谷地越宽,分类越容易;谷地越窄甚至出现负谷地(蓝峰右尾压在红峰左尾上),分类难度就上来了。
箱线图适合看离群点和分布的四分位间距。它把每组数据的中位数、上下四分位数、最大最小值和离群点画成一种“带须的盒子”。如果两个箱体在纵向位置上错开明显,说明特征有区分度;如果箱体高度重叠,说明这个特征本身对分类贡献有限。
figure; boxplot(X, Y, 'Labels', {'类别 0', '类别 1'}); ylabel('特征值 x'); title('两类样本的箱线图对比');散点图也值得画一下,不过一维数据的散点图需要做点处理:横轴放特征值,纵轴可以放样本序号或一个随机的抖动值,避免所有点叠在一条线上什么都看不清。
figure; y_jitter = Y + (rand(size(Y)) - 0.5) * 0.2; scatter(X(Y==0), y_jitter(Y==0), 20, 'filled', 'MarkerFaceColor', [0.2 0.6 1]); hold on; scatter(X(Y==1), y_jitter(Y==1), 20, 'filled', 'MarkerFaceColor', [0.9 0.3 0.3]); xlabel('特征值 x'); ylabel('标签(抖动)'); legend('类别 0', '类别 1'); title('两类样本散点分布'); grid on;我个人的经验是:可视化花的时间和训练模型的时间一样多都值得。一维数据的分布形态直接决定了后续模型的性能上限。如果直方图显示两组数据几乎完全重叠,那么再高级的分类器也不可能做到 90% 以上的准确率,这个认知能帮你避免在错误的方向上死磕。
2.3 训练集与测试集划分:必须认真对待的一步
划分训练集和测试集是机器学习的基本功,但在简单的项目里容易被敷衍过去。有些同学图省事直接拿全部数据训练,再用同一批数据评估,得到的准确率高得惊人,但完全没有意义——那是“考试开卷还看答案”的假成绩。正确的做法是把数据切成互不重叠的两份,训练只用训练集,测试只用测试集。
在 MATLAB 里,cvpartition 是划分数据集的推荐方式。
cv = cvpartition(length(Y), 'HoldOut', 0.3); idxTrain = training(cv); idxTest = test(cv); XTrain = X(idxTrain); YTrain = Y(idxTrain); XTest = X(idxTest); YTest = Y(idxTest);HoldOut 的含义是保留 30% 的数据作为测试集,剩下 70% 用于训练。这个比例不是死规定,数据量大的时候可以留 20%,数据量小的时候建议留 40%,核心原则是测试集要足够大,大到能反映模型在未见数据上的真实表现。
还有一个隐蔽但非常重要的问题:数据打乱顺序。如果原始数据在文件里就是按类别排好序的——前 200 行是类别 0,后 200 行是类别 1——而你直接用前 70% 训练、后 30% 测试,那么训练集里可能只有类别 0,测试集里只有类别 1,模型直接崩溃。解决方案就是在划分之前做一次随机打乱。前面构造数据时我已经用 randperm 打乱了一次,但读真实 CSV 文件时尤其需要检查这一点。cvpartition 的 HoldOut 选项默认不做打乱,它按顺序抽取,所以一般建议先打乱数据再划分,或者用 cvpartition 自带的 'Stratify' 选项来按类别比例分层抽样。
% 分层抽样:保证训练集和测试集中各类别比例与原数据一致 cv = cvpartition(Y, 'HoldOut', 0.3, 'Stratify', true);这个细节对不平衡数据尤其重要。如果原始数据中类别 1 只占 5%,普通随机划分有可能把测试集里的类别 1 全部划走,导致测试结果完全失真。分层抽样可以在很大程度上缓解这个问题。
3. 实操过程与核心环节实现
3.1 逐步实现一个完整的二分类流程
现在我们把前面几个环节串起来,实现一个完整的 MATLAB 脚本。假设我们手头有一份名为 process_data.csv 的文件,里面包含特征列 feature 和标签列 label,标签取值为 0 和 1。
第一步,读取并检查数据。
data = readtable('process_data.csv'); X = data.feature; Y = data.label; % 检查是否有缺失值 fprintf('缺失值数量:%d\n', sum(ismissing(X))); fprintf('类别分布:\n'); disp(categorical(Y));缺失值在真实数据中非常常见,一维特征只要有一个缺失,这个样本的模型预测就没法进行。最简单的处理方式是删除缺失样本,如果缺失占比很高,就需要考虑填充策略,比如用均值填充或用 KNN 插补。不过对这个项目来说,删除是最稳妥的方案。
第二步,绘制分布图观察可分性。
这一步就是前面 2.2 小节里那些直方图代码,不再重复。这里要补充一个关键经验:直方图的 bin 数量会影响视觉判断。bin 太多会看到大量毛刺,bin 太少会掩盖分布细节。MATLAB 默认的 bin 数量通常是 10~20 之间,对于 400 个样本的一维数据,我习惯手动指定 bin 数为 20~30。
figure; histogram(X(Y==0), 25, 'FaceColor', [0.2 0.6 1], 'FaceAlpha', 0.6); hold on; histogram(X(Y==1), 25, 'FaceColor', [0.9 0.3 0.3], 'FaceAlpha', 0.6);第三步,划分数据。
cv = cvpartition(Y, 'HoldOut', 0.3, 'Stratify', true); XTrain = X(training(cv)); YTrain = Y(training(cv)); XTest = X(test(cv)); YTest = Y(test(cv));第四步,训练逻辑回归分类器。
逻辑回归是二分类任务的首选入门模型。它输出的是一个概率值,表示样本属于类别 1 的可能性,决策规则就是概率是否超过 0.5。在 MATLAB 中,训练逻辑回归用 fitclinear 函数,但注意它是针对高维稀疏数据优化的;对于一维数据,更常用的是 fitglm,因为它直接支持广义线性模型。
% 使用 fitglm 拟合逻辑回归 mdl_lr = fitglm(XTrain, YTrain, 'Distribution', 'binomial', 'Link', 'logit'); % 预测测试集概率 prob_lr = predict(mdl_lr, XTest); % 根据概率得到类别标签 pred_lr = double(prob_lr >= 0.5);这里有个细节:predict 返回的是概率值,不是标签。必须自己设定阈值 0.5。这个阈值默认是 0.5,但在类别不平衡的时候可能需要调整。后面我会专门讲。
第五步,训练 SVM。
SVM 在一维空间上的表现很有意思。因为特征只有一维,SVM 的核函数选择其实被“压缩”了。线性核就是把空间内的点映射到一条数轴上找最大间隔阈值点;高斯核(RBF 核)在这里几乎退化成一种局部密度比较。不过 SVM 依然值得尝试,因为它的间隔最大化思想在噪声环境下比逻辑回归更稳健。
mdl_svm = fitcsvm(XTrain, YTrain, 'KernelFunction', 'linear', 'Standardize', true); pred_svm = predict(mdl_svm, XTest);Standardize 参数值得注意。它会对特征做标准化处理,让均值为 0、方差为 1。一维数据虽然只有一个特征,但如果这个特征本身数值范围很大,比如电压在几百伏量级,标准化就能让 SVM 的优化过程更稳定。
第六步,训练决策树。
决策树在一维场景下非常直观:它只会做一件事,就是寻找最佳切分阈值,使得切分后两个子节点的纯度最高。MATLAB 中使用 fitctree。
mdl_tree = fitctree(XTrain, YTrain, 'MaxNumSplits', 4); pred_tree = predict(mdl_tree, XTest);MaxNumSplits 控制树的深度。默认情况下决策树可能长得很深,把训练数据完完整整地背下来,这就是过拟合的典型表现。一维数据上树深度超过 4 基本就是在记样本了。限制分裂次数是防止过拟合最直接的手段。
第七步,训练 KNN。
KNN 的思路更朴素:对于一个新样本,看看它在训练集里最近的 K 个邻居都是什么标签,按多数投票决定自己的类别。一维数据上“距离”就是数轴上两个数的绝对差。
mdl_knn = fitcknn(XTrain, YTrain, 'NumNeighbors', 5); pred_knn = predict(mdl_knn, XTest);K 值的选取很关键。K 太小,模型对单个样本的噪声敏感;K 太大,模型会被远处的多数类“绑架”。对于 280 个训练样本,K 取 3 到 10 都算合理区间,具体可以用交叉验证来选。我通常习惯先试 K=5,再观察错误分布微调。
3.2 分类器性能对比与可视化
训练完四个模型后,我们需要一个统一的标准来评估谁的效果好。最基础的是准确率:预测正确的样本数除以总样本数。
accuracy_lr = sum(pred_lr == YTest) / length(YTest); accuracy_svm = sum(pred_svm == YTest) / length(YTest); accuracy_tree = sum(pred_tree == YTest) / length(YTest); accuracy_knn = sum(pred_knn == YTest) / length(YTest); fprintf('逻辑回归准确率:%.2f%%\n', accuracy_lr * 100); fprintf('SVM 准确率:%.2f%%\n', accuracy_svm * 100); fprintf('决策树准确率:%.2f%%\n', accuracy_tree * 100); fprintf('KNN 准确率:%.2f%%\n', accuracy_knn * 100);准确率只是最粗的指标。当两类样本数量不均衡时,准确率会产生很大的误导。比如 95% 的样本是类别 0,那么一个“永远预测类别 0”的垃圾模型也能拥有 95% 的准确率。所以必须看混淆矩阵。
figure; confusionchart(YTest, pred_lr); title('逻辑回归混淆矩阵');confusionchart 会画出一个 2x2 的热力图矩阵,四个格子分别代表真负例(TN)、假正例(FP)、假负例(FN)和真正例(TP)。理想情况是反对角线元素(TN 和 TP)占绝对优势,对角线以外的 FP 和 FN 越小越好。
混淆矩阵还能告诉我们另一件事:模型的错误是偏向哪一侧。如果 FP 多,说明模型容易把类别 0 错判为类别 1;如果 FN 多,说明模型容易漏掉真正的类别 1。这在工程上往往有完全不同的代价,比如在故障检测里,漏掉一次故障(FN)的代价可能远大于一次误报(FP)。所以选用分类器,不能只看准确率,还要结合实际的错误代价。
ROC 曲线是另一个重要工具。它刻画的是不同概率阈值下真正例率(TPR)和假正例率(FPR)的关系。曲线下的面积 AUC 越大,模型区分两类的能力越强。MATLAB 中用 perfcurve 函数可以方便地绘制 ROC 曲线。
% 以逻辑回归概率为例绘制 ROC [Xroc, Yroc, ~, AUC] = perfcurve(YTest, prob_lr, 1); figure; plot(Xroc, Yroc, 'LineWidth', 2); xlabel('假正例率 FPR'); ylabel('真正例率 TPR'); title(['逻辑回归 ROC 曲线,AUC = ', num2str(AUC)]); grid on;AUC 达到 0.9 以上说明区分能力强,0.8~0.9 是优秀,0.7~0.8 是一般。一维可分性较好的数据,AUC 通常在 0.9 以上;如果 AUC 低于 0.75,就该回头检查是数据本身重叠太严重,还是预处理环节出了问题。
3.3 阈值调整与不平衡数据处理
如果我们发现模型在类别 1 上的召回率偏低,而类别 1 又是我们更关心的对象,可以在逻辑回归输出的概率上做文章。默认阈值是 0.5,把它降低到 0.3,意味着只要样本有 30% 的概率属于类别 1 就判定为类别 1。这样会提高召回率,但代价是误报增多。阈值的选择本质上是在“宁可不漏”和“宁可不错”之间寻找平衡。
% 调整阈值提高类别 1 的召回率 threshold = 0.3; pred_lr_adjusted = double(prob_lr >= threshold); recall_original = sum(pred_lr == 1 & YTest == 1) / sum(YTest == 1); recall_adjusted = sum(pred_lr_adjusted == 1 & YTest == 1) / sum(YTest == 1); fprintf('原始阈值召回率:%.2f%%\n', recall_original * 100); fprintf('调整阈值召回率:%.2f%%\n', recall_adjusted * 100);数据不平衡的另一个常见处理思路是调整样本权重或重采样。在 MATLAB 里,fitcsvm 和 fitctree 都支持 'Prior' 或 'Weights' 参数。给少数类样本更高的权重,模型会更“重视”它们,代价是整体准确率可能略降,但对少数类的识别能力会显著增强。
% 给类别 1 分配更高权重 w = ones(length(YTrain), 1); w(YTrain == 1) = 2; % 类别1权重加倍 mdl_svm_w = fitcsvm(XTrain, YTrain, 'KernelFunction', 'linear', ... 'Standardize', true, 'Weights', w);我的经验是:权重调整要克制,从 1.5 倍开始试,不要一上来就调到 10 倍,否则模型会从“忽略少数类”跳到“把大量多数类误判为少数类”,两个极端都不好。
4. 常见问题与排查技巧实录
4.1 两类数据严重重叠时怎么办
这是做一维分类最常遇到的难题。直方图一画出来,两个峰基本重叠,中间没有明显的谷。此时无论用哪种分类器,准确率天花板都差不多。我们能做的有几件事。
第一,能不能加特征。如果手头有原始信号数据,而不是只有一张最终特征表,可以提取额外特征,比如均值、峰值、方差、过零率、频域能量等,把一维特征扩展成多维特征。这是治本的办法,从根本上改变问题的复杂度。
第二,可不可以换一个时间尺度上的特征。传感器数据里,单点值可能重叠严重,但相邻两个点的差值或短时间窗口内的趋势往往包含更多区分信息。比如两根温度曲线的瞬时值可能接近,但变化速率明显不同。差值和斜率就是这个项目里值得试的一维特征。
第三,接受现实,调整预期。如果特征本身信息量不足,再好的分类器也分不开。此时可以诚实地报告模型的 AUC 和混淆矩阵,而不是反复调参追求不切实际的准确率。工程上我们经常要面对“能做到什么程度”的问题,把结论交代清楚,本身就很有价值。
4.2 模型在训练集上表现好但测试集上拉胯
这是典型的过拟合。在一维数据的场景下,过拟合常出现在两个场合。决策树深度过深是第一个:树可以拆到每个叶节点只有一个样本,训练准确率 100% 没问题,但测试集完全失去泛化能力。解决办法是限制 MaxNumSplits,或者用交叉验证自动选出合适深度。
第二个是 KNN 中 K 值取太小。K=1 时,模型把每个训练样本都当成了自己的“私密邻居”,测试样本只要和某一个训练样本接近就会继承它的标签,噪声被完整保留下来。增大 K 值,或者用交叉验证选最优 K。
% 用交叉验证自动选择 K 值范围 for k = 1:2:15 mdl_tmp = fitcknn(XTrain, YTrain, 'NumNeighbors', k); cvmodel = crossval(mdl_tmp, 'KFold', 5); loss = kfoldLoss(cvmodel); fprintf('K=%d 交叉验证损失:%.4f\n', k, loss); end还有一种隐蔽的过拟合来源:模型选择过程本身。如果我们在测试集上反复试不同模型,看谁准确率高就选谁,那测试集的信息就“泄漏”进了模型选择过程,最终选出来的模型其实是对测试集过拟合。正确做法是用交叉验证来选择模型和参数,测试集只在最后评估一次。
4.3 预测结果为什么总偏向多数类
这是类别不平衡的直接表现。当类别 1 只占 5% 时,逻辑回归的默认决策边界会整体偏向类别 1,导致几乎所有的预测结果都是类别 0。处理思路有三个层面:数据层面做重采样或合成少数类样本;算法层面给少数类加权重;决策层面降低阈值或调整代价矩阵。这三个层面可以组合使用,但要注意不要同时用力过猛,否则会从“全都预测 0”翻转到“全都预测 1”。
我个人在工程实践中的体会是:先用混淆矩阵和召回率精确率定位问题在哪一侧,再决定用哪种手段。大多数情况下,简单调整阈值就能满足需求,重采样和加权是杀手锏,留在最后用。
4.4 随机种子与结果可复现性
MATLAB 的随机数生成器如果不固定,每次运行脚本都可能产生不同的数据划分和不同的模型表现。同一个模型跑两次,准确率相差几个百分点,会让人对自己的代码产生怀疑。解决办法是在脚本开头固定随机种子。
rng(2024); % 固定随机数生成器固定种子之后,rand、randn、randperm 等函数生成的随机序列就完全确定了,整个流程从数据采样、打乱顺序到交叉验证都能复现。做实验和写报告的时候,这个习惯能在你排查问题时省下大量时间——如果每次结果都不一样,你根本分不清一个修改到底是改善了模型还是纯粹由随机波动引起的。
4.5 代码调试中的三个实操技巧
第一,定期打印中间变量的尺寸。用 size() 检查 X 和 Y 的行数是否一致。特征向量和标签长度不匹配是最常见的低级错误,一旦 mismatch,fit 函数就会报错,而且错误信息往往不够直观。
第二,用 isscalar 或 isnumeric 确认特征类型。从 CSV 读入的数据可能是字符串格式,直接参与运算会报错或产生隐式转换。用 class() 和 whos 检查变量类型,发现不对就及时处理。
第三,理解 predict 和 fit 的输入格式。MATLAB 的 fit 系列函数统一要求特征矩阵是 n×m,其中 n 是样本数,m 是特征维度。一维数据要写成 XTrain 是列向量而不是行向量,否则模型会认为只有一个样本、280 个特征,训练结果完全错误。
5. 从一维到多维的扩展思路
一维二分类虽然简单,但它的方法论可以平移到更复杂的问题上。我建议做完这个项目之后,可以尝试沿着以下两个方向做扩展练习。
第一个方向是特征扩展。把原来的一维特征扩展成二维或三维特征,比如同时记录传感器读数的均值、峰值和方差。你会发现分类边界从“点”变成了“线”再到“面”,SVM 的核函数选择开始有发挥余地,决策树的形状也从“阈值列表”变成“多维切分树”。这个过程能帮你直观理解维度对人类视觉和模型能力的影响。
第二个方向是引入时间结构。如果数据是按时间顺序采集的,可以构造滑动窗口特征。比如用过去 10 个采样点的平均值、斜率、标准差作为新特征。这类特征在语音识别、心电信号分类、机械故障诊断中非常常见。理解了窗口特征的意义,就理解了时序分类的本质。
还有一个值得动手的实验:把各类分类器的决策边界画出来。在二维特征空间里,决策边界是一条曲线,比较逻辑回归的线性边界和 SVM 的 RBF 边界在视觉上的差异,比读任何教科书都更直观。反过来再看一维问题,你会发现一维空间里所有分类器的边界本质上都落在少数几个阈值点上,这恰恰说明了一维问题的“信息容量”是有限的。
6. 完整可运行的参考代码
最后给出一个整合了全部环节的完整 MATLAB 脚本。你可以直接复制保存为 classify_1d.m,换掉数据文件名和相应列名就能用到自己的数据上。
%% 基于 MATLAB 的一维数据二分类完整流程 clear; clc; close all; rng(2024); %% 1. 加载数据 data = readtable('process_data.csv'); % 替换为自己的数据文件 X = data.feature; Y = data.label; %% 2. 查看基本信息和缺失值 fprintf('样本总数:%d\n', length(Y)); fprintf('类别0数量:%d,类别1数量:%d\n', sum(Y==0), sum(Y==1)); fprintf('缺失值数量:%d\n', sum(ismissing(X))); % 剔除缺失样本 validIdx = ~ismissing(X) & ~ismissing(Y); X = X(validIdx); Y = Y(validIdx); %% 3. 可视化 figure; histogram(X(Y==0), 25, 'FaceColor', [0.2 0.6 1], 'FaceAlpha', 0.6); hold on; histogram(X(Y==1), 25, 'FaceColor', [0.9 0.3 0.3], 'FaceAlpha', 0.6); xlabel('特征值 x'); ylabel('频数'); legend('类别0', '类别1'); title('两类样本的分布直方图'); grid on; figure; boxplot(X, Y, 'Labels', {'类别0', '类别1'}); ylabel('特征值 x'); title('两类样本的箱线图对比'); %% 4. 划分训练集和测试集(分层抽样) cv = cvpartition(Y, 'HoldOut', 0.3, 'Stratify', true); XTrain = X(training(cv)); YTrain = Y(training(cv)); XTest = X(test(cv)); YTest = Y(test(cv)); fprintf('训练集样本数:%d\n', length(YTrain)); fprintf('测试集样本数:%d\n', length(YTest)); %% 5. 训练逻辑回归 mdl_lr = fitglm(XTrain, YTrain, 'Distribution', 'binomial', 'Link', 'logit'); prob_lr = predict(mdl_lr, XTest); pred_lr = double(prob_lr >= 0.5); %% 6. 训练SVM mdl_svm = fitcsvm(XTrain, YTrain, 'KernelFunction', 'linear', 'Standardize', true); pred_svm = predict(mdl_svm, XTest); %% 7. 训练决策树 mdl_tree = fitctree(XTrain, YTrain, 'MaxNumSplits', 4); pred_tree = predict(mdl_tree, XTest); %% 8. 训练KNN mdl_knn = fitcknn(XTrain, YTrain, 'NumNeighbors', 5); pred_knn = predict(mdl_knn, XTest); %% 9. 评估 accuracy = @(p) sum(p == YTest) / length(YTest); fprintf('\n======= 测试集准确率 =======\n'); fprintf('逻辑回归:%.2f%%\n', accuracy(pred_lr) * 100); fprintf('SVM:%.2f%%\n', accuracy(pred_svm) * 100); fprintf('决策树:%.2f%%\n', accuracy(pred_tree) * 100); fprintf('KNN:%.2f%%\n', accuracy(pred_knn) * 100); %% 10. 混淆矩阵 figure; confusionchart(YTest, pred_lr); title('逻辑回归混淆矩阵'); %% 11. ROC曲线 [Xroc, Yroc, ~, AUC] = perfcurve(YTest, prob_lr, 1); figure; plot(Xroc, Yroc, 'LineWidth', 2); xlabel('假正例率 FPR'); ylabel('真正例率 TPR'); title(['逻辑回归 ROC 曲线,AUC = ', num2str(AUC)]); grid on;这个脚本可以直接跑通整个流程。但记住,它只是一个起点。真实项目中你会遇到数据噪声更大、样本更少、类别更不平衡的情况,处理起来需要灵活应变。多做几个不同的数据模拟,调整参数观察结果变化,你对分类模型的理解会远比“跑通一个脚本”要深得多。
我在做这个项目时,最有感触的一点是:一维数据把机器学习里几乎所有核心概念都压缩在一个极简的几何图景里,反而比那些花哨的高维案例更容易让人看清本质。明白分类边界本质上就是在数轴上选位置,你就能明白所有复杂模型不过是在高维空间里做同样的事。把基础想透了,后面无论做多复杂的项目都不会发怵。