直接上干货用Matlab手撕一个基于逻辑回归的多变量预测模型先说点实在的。逻辑回归这个名字确实容易把人带偏名字里带个“回归”很多人第一反应是拿它去拟合连续数值比如预测房价、预测销量。但逻辑回归骨子里是个分类模型做的事情是估算“某件事发生的概率”然后根据概率大小去决定类别。不过因为它的输出天然是0到1之间的概率值所以直接拿来做概率预测也非常顺手——比如预测用户的购买意愿、设备故障的概率、客户流失的风险这些都是逻辑回归的高频应用场景。这篇文章不打算讲那些教科书里泛泛的原理推导我直接带你用Matlab建一个多变量逻辑回归预测模型从数据处理到训练迭代再到预测和评估完整走一遍。整个过程会手写核心算法不依赖现成的fitglm之类的黑盒子这样你能真正看清每一步在算什么。适合有一定Matlab基础、想搞明白机器学习模型内部逻辑的同学也适合做数据分析、做课题研究时需要自己建模的工程师和研究生。1. 逻辑回归为什么既能分类又能预测概率逻辑回归的本质是在线性回归外面套了一层“压缩”函数。线性回归算出的是一个实数范围是负无穷到正无穷这没法直接当概率用。逻辑回归做的事情就是把线性回归的输出丢进sigmoid函数把这个实数压到0到1之间变成一个有意义的概率值。sigmoid函数的数学形式很简单[ p \frac{1}{1 e^{-z}}, \quad z \theta_0 \theta_1 x_1 \theta_2 x_2 ... \theta_n x_n ]这里的x就是你的多个特征变量也就是“多变量”这个说法的来源。θ是需要训练求解的模型参数每一个特征对应一个权重系数。p就是模型预测的正样本概率。这个公式很有生活化的类比想象你给一个学生评估能否通过考试z是所有因素复习时间、平时成绩、出勤率的加权打分分数越高越容易过。但打分本身是“裸分”没有上限。sigmoid的作用就是把这个裸分映射成一个通过概率裸分很高时概率趋近于1裸分很低时概率趋近于0。那为什么不用线性回归直接预测概率呢因为线性回归可能预测出大于1或者小于0的数这在物理意义上说不通。更关键的是线性回归假设误差服从正态分布而分类任务的误差分布根本不是这么回事。逻辑回归用交叉熵损失函数替代均方误差能更好地惩罚“自信的错误”——就是那种模型非常确定地说“是正类”结果实际是负类的情况。从参数求解的角度看逻辑回归没有解析解必须用迭代优化算法去逼近最优参数。最常见的就是梯度下降法。梯度下降的思想也特别朴素你站在一座山上要找到山谷的最低点最简单的办法就是每一步都朝着下坡最陡的方向走。这个“最陡方向”就是梯度的负方向走的步长就是学习率。对于逻辑回归交叉熵损失函数对参数θ求梯度得到的更新公式非常简洁[ \theta_j : \theta_j - \alpha \frac{1}{m} \sum_{i1}^{m} (h(x^{(i)}) - y^{(i)}) x_j^{(i)} ]这个公式的意思是计算每个样本的预测概率和真实标签的差值乘以该样本的特征值求和取平均再乘以学习率α最后用它更新参数。整个过程重复迭代很多次直到损失函数收敛。也就是说逻辑回归训练的核心就是反复执行这一条更新公式直到找到一组让损失函数足够小的参数。这就是后面所有代码实现的数学基础。理解了这层原理你就知道为什么逻辑回归的输出可以用来做概率预测因为模型的输出本身就是经过sigmoid函数映射的概率值只是我们在做分类决策时默认拿0.5作为阈值来划分正负类。实际上这个阈值完全可以调整比如宁可错杀也不放过的场景你可以把阈值调低到0.3让更多的样本被判为正类。2. 多变量场景下的数据处理和关键准备模型真正的难点不在算法本身而在数据处理。多变量意味着你的输入特征不止一个特征之间可能存在量纲差异、相关性甚至冗余这些都会直接影响模型的收敛速度和预测效果。我这次用的是一个模拟的两类分类数据集包含五个特征变量分别记为x1到x5。其中x1和x2对标签的影响占主导x3是一个中等影响变量x4和x5则偏向噪声。这样设计数据集是刻意为之——真实的业务数据不会那么干净总有一些特征作用微弱甚至纯粹是干扰项模型要学会在噪声中找出真正有用的模式。拿到数据之后第一件事不是直接扔进模型训练而是做特征标准化。这点特别重要我也经常看到初学者在这上面翻车。如果你的特征x1取值范围是0~100而特征x2取值范围是0~1那么梯度下降时x1对应的参数会更新得很快x2对应的参数会更新得很慢导致收敛路径七扭八歪。标准化就是把每个特征缩放到均值0、标准差1的范围内让所有特征站在同一个起点上。Matlab里做标准化很简单用zscore这个函数就行X_train_std zscore(X_train); X_test_std zscore(X_test);这里有个注意点标准化只按训练集的均值和标准差来做然后用训练集的这两个统计量去标准化测试集不能把测试集混进来重新算。否则就是数据泄露会让测试集上的评估结果虚高。数据准备的第二步是检查正负样本的分布。如果正样本占比90%负样本占比10%模型只需要把所有的样本都预测为正类准确率就已经是90%了但这显然没有意义。逻辑回归在类别极度不均衡时决策边界会被多数类主导导致少数类样本几乎全被忽略。处理类别不均衡的策略有很多例如对少数类样本进行过采样、对多数类进行欠采样或者调整分类阈值。我在这次案例里用的数据集基本均衡所以不做特殊处理但你需要知道这个问题存在。还有一个容易被忽视的点是多重共线性。如果两个特征之间存在强相关关系比如特征x1是“销售额”特征x2是“销售额的对数”这两个变量高度相关逻辑回归的参数估计会变得不稳定参数方差会很大。排查方法也很简单画个相关系数矩阵热力图看看哪些变量之间的相关系数接近±1如果有建议去掉一个或者做PCA降维。数据处理阶段我还习惯做一件事随机划分训练集和测试集并且固定随机种子。固定随机种子不是小题大做是为了保证结果可复现。你在调试模型时需要反复调整参数如果每次跑出来的结果都不一样你就分不清某个改动到底起了正面还是负面作用。在Matlab中可以这样固定随机种子rng(42);设置完随机种子之后从原始数据集中随机抽出70%作为训练集剩下30%作为测试集。idx randperm(size(X, 1)); train_idx idx(1:round(0.7 * length(idx))); test_idx idx(round(0.7 * length(idx)) 1:end); X_train X(train_idx, :); y_train y(train_idx); X_test X(test_idx, :); y_test y(test_idx);这几行代码做的事情是先把样本索引打乱再按比例切分。注意这里的y是原始的二分类标签取值为0或1这个二值特性决定了我们使用二元逻辑回归模型。接下来就是核心环节把梯度下降训练过程完整地写出来然后用这个训练好的模型去做预测和评估。3. 手写Matlab代码实现逻辑回归训练我在这部分会把完整的训练流程代码拆开来讲。先定义一个函数trainLogReg它接收训练数据、学习率、迭代次数这些参数然后返回训练好的参数。function [theta, loss_history] trainLogReg(X, y, alpha, num_iters) % 在X前加一列全1对应截距项theta0 X [ones(size(X, 1), 1), X]; m length(y); [n_features] size(X, 2); theta zeros(n_features, 1); loss_history zeros(num_iters, 1); for iter 1:num_iters z X * theta; h 1 ./ (1 exp(-z)); loss -1 / m * sum(y .* log(h 1e-8) (1 - y) .* log(1 - h 1e-8)); loss_history(iter) loss; gradient 1 / m * (X * (h - y)); theta theta - alpha * gradient; end end这段代码有几个关键细节值得展开。第一截距项的处理。逻辑回归的z θ₀ θ₁x₁ ... θₙxₙ中θ₀是截距它不乘以任何特征。一种做法是把它单独拿出来更新但更简洁的做法是在特征矩阵的最前面加一列常数1这样θ₀就变成这个常数列对应的权重系数整个参数更新过程可以统一用矩阵运算处理不需要特殊对待截距。第二损失函数计算里的1e-8这个微小常数。这是为了防止log(0)导致结果为负无穷。如果某个样本的预测概率恰好是1那么log(1-h)就是log(0)这是数学上未定义的量Matlab会直接给出Inf。加上一个极小的常数可以避免这种数值不稳定问题。这个细节看起来无关紧要但实际跑数据的时候遇到预测概率饱和没加保护的话整条训练流程就崩溃了。第三梯度计算的矩阵形式。X * (h - y)是一次性完成所有特征参数的梯度计算利用了矩阵运算的向量化特性比for循环遍历每个样本再累加要快得多。这也是Matlab的强项——尽量避免显式循环多写矩阵运算。h - y是一个m维列向量X是(n1)×m的矩阵两者相乘得到一个(n1)维的列向量正好对应每个参数的梯度值。训练完成之后预测阶段同样需要写一个函数。这个函数接收测试集特征和训练好的参数输出每个样本的正类概率。function pred_prob predictLogReg(X_test, theta) X_test [ones(size(X_test, 1), 1), X_test]; z X_test * theta; pred_prob 1 ./ (1 exp(-z)); end有了预测概率之后你可以自行决定分类阈值。默认情况下概率大于等于0.5判定为正类否则判定为负类。但我要提醒一句阈值的选择应当服务于业务场景而不是机械地固定在0.5。在欺诈检测这类场景里把阈值降低到0.3可以召回更多可疑样本即使牺牲一些精确率也是值得的在邮件分类这类场景里垃圾邮件误判进收件箱的代价比漏判更大所以会把阈值调高到0.6甚至0.7。下面是一段完整的训练和评估脚本把这个案例的整个流程串起来rng(42); % 模拟数据集生成5个特征其中x1、x2影响显著x3中等x4、x5近似噪声 n_samples 1000; x1 randn(n_samples, 1) .* 1.2 0.5; x2 randn(n_samples, 1) .* 0.8 - 0.3; x3 randn(n_samples, 1) .* 0.6 0.2; x4 randn(n_samples, 1); x5 randn(n_samples, 1); z_lin 0.8 * x1 - 1.2 * x2 0.5 * x3 0.05 * x4 - 0.03 * x5; prob 1 ./ (1 exp(-z_lin)); y double(rand(n_samples, 1) prob); X [x1, x2, x3, x4, x5]; % 随机划分训练集和测试集 idx randperm(n_samples); train_idx idx(1:700); test_idx idx(701:end); X_train X(train_idx, :); y_train y(train_idx); X_test X(test_idx, :); y_test y(test_idx); % 标准化 [X_train_std, mu, sigma] zscore(X_train); X_test_std (X_test - mu) ./ sigma; % 训练 alpha 0.1; num_iters 3000; [theta, loss_history] trainLogReg(X_train_std, y_train, alpha, num_iters); % 预测 pred_prob_test predictLogReg(X_test_std, theta); pred_label double(pred_prob_test 0.5); % 评估 accuracy mean(pred_label y_test); fprintf(测试集准确率: %.4f\n, accuracy);这里再次强调标准化测试集时要用训练集的mean和sigma不能自己对测试集重新计算。上面代码中那行X_test_std (X_test - mu) ./ sigma;而不是zscore(X_test)就是在刻意避开数据泄露。跑完之后可以画一下损失函数的收敛曲线这是检查训练过程是否正常最直观的方式figure; plot(1:num_iters, loss_history, b); xlabel(迭代次数); ylabel(损失值); title(损失函数收敛曲线); grid on;正常情况下损失值应该随着迭代次数增加而持续下降最后趋近一个平稳值。如果损失曲线上下波动说明学习率设大了需要调小如果下降非常缓慢说明学习率设小了可以适当调大或者尝试更大的迭代次数。我在实际调试中还发现损失曲线在前几百轮下降很快之后会进入一个缓慢下降的平台期。这不是模型出问题了而是前期参数离最优解远梯度大所以更新幅度大后期参数接近最优解梯度变小更新幅度也随之变小。这很正常。4. 模型评估——准确率之外还要看什么跑完上面的代码你会得到一个测试集准确率一般会落在0.75到0.85之间。这个成绩看起来还行但光看准确率是不够的尤其是在正负样本不够均衡的情况下。准确率有一个致命缺陷它对多数类更友好。假如测试集里90%是负样本模型全预测成负类准确率是90%看起来很高实际上这个模型什么都没学到。所以必须引入更全面的评估维度。Precision精确率衡量的是模型预测为正类的样本中有多少是真正的正类Recall召回率衡量的是真正的正类中有多少被成功识别出来。F1分数是精确率和召回率的调和平均它更注重两者的平衡。这三个指标在Matlab里都可以用confusionmat函数计算不需要额外装工具包confMat confusionmat(y_test, pred_label); TP confMat(2,2); TN confMat(1,1); FP confMat(1,2); FN confMat(2,1); precision TP / (TP FP); recall TP / (TP FN); F1 2 * precision * recall / (precision recall); fprintf(精确率: %.4f\n, precision); fprintf(召回率: %.4f\n, recall); fprintf(F1分数: %.4f\n, F1);confusionmat返回的矩阵是2×2的行表示真实类别列表示预测类别。第1行第1列是True Negative第2行第2列是True Positive。注意别把行列的方向搞反我第一次用这个函数的时候就在这上面栽过跟头算出来的精确率怎么都对不上后来打印confMat才发现是索引位置搞错了。除了这些基于混淆矩阵的指标之外评估概率预测模型还有两个更核心的指标。第一个是LogLoss也叫对数损失。这个指标直接衡量预测概率和真实标签之间的契合程度数值越小说明预测概率越准确。计算公式就是训练时使用的交叉熵损失函数只是把它用在测试集上[ \text{LogLoss} -\frac{1}{m} \sum_{i1}^{m} [y_i \log(p_i) (1-y_i) \log(1-p_i)] \quad ]如果一个样本的真实标签是1模型给出了0.9的概率这个预测的LogLoss贡献是-log(0.9)约等于0.105如果模型给出了0.1的概率那么LogLoss贡献是-log(0.1)约2.303惩罚力度大得多。这正是LogLoss的价值所在——它不仅在区分对错更在衡量模型的“自信程度”是否合理。第二个是AUC也就是ROC曲线下的面积。ROC曲线的横轴是False Positive Rate纵轴是True Positive Rate曲线上的每一个点对应一个不同的分类阈值。AUC的取值在0.5到1之间0.5意味着模型没有区分能力相当于抛硬币1意味着完美预测。Matlab中计算AUC可以用perfcurve函数这个函数在Statistics and Machine Learning Toolbox里如果你的Matlab没有装这个工具箱还可以用rank-sum法手写一个近似AUC% 如果装了工具箱 [X_roc, Y_roc, ~, auc] perfcurve(y_test, pred_prob_test, 1);AUC的一个好处是它跟分类阈值无关直接度量模型把正类排到负类前面的能力。也就是说AUC评估的是模型的内在排序能力不管你怎么调阈值它都不变。我在实际项目中通常同时关注LogLoss和AUC这两个指标。LogLoss衡量概率校准度AUC衡量排序能力两者结合才能全面评价一个概率预测模型的好坏。5. 从二分类到多分类的扩展——Softmax回归很多实际问题不止两个类别。比如预测客户属于哪个消费档位这是低中高三分类识别图片里的物体类别可能是几十个类别。逻辑回归从二分类扩展到多分类本质上是把sigmoid函数换成softmax函数。sigmoid输出两个类别的概率且概率之和为1softmax则输出多个类别的概率分布所有类别的概率之和也为1。softmax的计算公式是[ P(y k | x) \frac{e^{z_k}}{\sum_{j1}^{K} e^{z_j}} \quad ]其中K是类别总数z_k是第k个类别的线性打分。每个类别都有一组自己的参数向量。训练时用softmax交叉熵损失函数仍然可以用梯度下降来求解。在Matlab里实现多分类逻辑回归你可以为每个类别单独训练一个二元逻辑回归模型然后用softmax把所有模型的得分归一化成概率分布。这种One-vs-All策略实现简单而且和二元逻辑回归共享同一套损失函数和梯度更新逻辑是初学多分类时很合适的跳板。从二分类走向多分类的意义不仅是模型能力的提升更是理解机器学习统一框架的一步特征表示、线性打分、激活函数、概率归一化、交叉熵损失、梯度下降这套模式在神经网络里也完全成立。可以说学会逻辑回归之后你已经摸到了深度学习中多层神经网络的第一层。写到这里整个基于逻辑回归的多变量预测模型的构建过程就算完整走完了。从sigmoid函数的原理到梯度下降的推导再到Matlab代码实现和评估指标的分析每一步都清楚明了。说实话逻辑回归至今仍然是许多工业级系统的首选模型原因就在于它训练快、结果可解释、对输入数据的分布要求相对宽松而且不容易过拟合。即使现在深度学习框架满天飞逻辑回归依然在实际业务里大量存在你学会了它遇到的很多分类和概率预测问题就有了一个可靠的基础解法。