尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

最小二乘法实战:用线性回归提前预测磁盘容量告警

最小二乘法实战:用线性回归提前预测磁盘容量告警 凌晨三点被磁盘容量告警吵醒爬起来一看使用率曲线沿着一条很规整的直线往上爬。当时脑子里冒出一个很朴素的想法如果能把这条趋势线的表达式算出来提前几天就知道磁盘大概什么时候会满比每天被动等着告警强多了。顺着这个想法查资料我第一次认真地搜索“机器学习”而几乎所有入门资料的前几页都会出现同一个名字最小二乘法。那时候我就是标题里写的“彩笔运维”代码能抄脚本能写服务器能摆弄但让一个运维把数学公式从头推一遍本能反应是发怵。可当我真正开始啃的时候发现最小二乘法并没有想象中那么“数学”。它本质上只是把“预测得准不准”这件事翻译成一个可以求最小值的函数。这篇就把我从零推导、写代码、用真实监控数据做预测的完整过程记录下来想转机器学习或者做智能运维的兄弟可以直接照着走一遍。1. 运维为什么绕不过最小二乘法先说结论运维的日常工作中有一大类问题本质上都是“根据历史数据推测未来趋势”。磁盘使用率会涨日志量会涨带宽占用会涨这些量在大多数情况下都带有明显的线性增长趋势。而最小二乘法就是处理这类趋势问题最经典、最基础的工具。1.1 一个让我决定搞懂它的场景记得那次磁盘告警我从监控系统里拉出最近三十天的使用率数据画在 Grafana 里看基本就是一条斜率很稳定的上升直线。如果单纯拍脑袋我也可以说“按这个速度大概一周后到阈值得扩容”但问题在于这个“大概”没法量化没法写进自动告警逻辑也没法跟领导汇报时说清楚依据。最小二乘法解决的就是这件事给出一组历史数据 (x, y)它能把 y 和 x 之间最匹配的线性关系 y ax b 算出来。这里的 a 就是每天的增长率b 是起始水平。有了这两个数预测未来某一天的使用率就是一个简单的乘法和加法。对运维来说这是再典型不过的容量规划问题。很多人觉得运维学机器学习门槛高其实我理解的门槛不在数学而在“把问题翻译成数学模型”。最小二乘法就是第一次翻译它把“曲线看起来像直线”变成“求一个让误差最小的直线参数”。这个翻译过程一旦打通后面学梯度下降、逻辑回归、甚至简单神经网络都会顺畅很多。1.2 最小二乘法在机器学习体系里的位置在各种机器学习教材里线性回归几乎永远是第一个模型。吴恩达的课程前几节讲的就是线性回归和最小二乘思路周志华《机器学习》西瓜书第三章也专门花了很大篇幅讲线性模型。这不是巧合而是因为线性回归足够简单、可解释性强又是很多高阶模型的地基。所谓“最小二乘法”体现在线性回归里就是目标函数的求解方式找到一组参数让所有样本点的预测误差残差平方和最小。很多入门资料直接甩出公式就完了但我觉得如果不把推导过程吃透后面看到“正规方程”“梯度下降”这些词会一直觉得是黑盒。还有一个运维相关的点机器学习应用的完整流程包括数据采集、清洗、特征工程、模型训练、评估、部署和监控而线性回归因为计算量小、可解释性强非常适合作为运维场景里的第一个模型。智能运维、AIOps 里很多容量预测和异常检测的实现底层其实就是这类回归模型只是套了更花哨的外衣。2. 先搞懂损失函数残差平方和到底在算什么2.1 残差与损失函数假设我们有 n 个样本点第 i 个样本是 (x_i, y_i)。我们想找一条直线 y ax b 来描述它们的关系。对每个样本模型给出的预测值是 \hat{y}_i a x_i b真实值和预测值的差 e_i y_i - \hat{y}_i 就是残差。问题是有无数条直线可以画怎么判断哪条最好最小二乘法的回答是把所有样本的残差平方加起来让这个总和最小的那条直线就是最优直线。写成公式L(a, b) \sum_{i1}^{n} (y_i - a x_i - b)^2这个 L 就是我们说的损失函数也叫目标函数。机器学习里“损失”这个词很直白——预测得越差损失越大。求最优参数就是求让损失最小的参数。2.2 为什么是平方而不是绝对值刚接触时我也有这个疑问残差有正有负直接求和会互相抵消那用绝对值 \sum |y_i - a x_i - b| 不也能衡量误差总量吗平方的好处主要有三个。第一平方函数处处可导绝对值在零点不可导而接下来我们要用“求导并令导数为零”来解最小值可导性非常关键。第二平方会放大误差真实值和预测值差 1 的时候平方贡献 1差 2 的时候平方贡献 4。这符合直觉误差越大的样本越像是“事故”应该被重点惩罚。第三平方对应欧氏距离如果把 n 个残差看成 n 维空间中的一个点残差平方和就是这个点到原点的距离平方。从几何上看最小二乘是在找一个离所有样本点都尽量近的解。2.3 损失函数前面的系数为什么无伤大雅很多资料里的损失函数会写成 L \frac{1}{2n}\sum_{i1}^{n}(y_i - a x_i - b)^2。多加的 \frac{1}{2} 和 \frac{1}{n} 有什么用其实它们不改变最优参数的位置。求导后乘一个正常数零点还是零点。\frac{1}{n} 的好处是让损失值变成“平均每个样本的误差”方便跨数据集比较\frac{1}{2} 纯粹是为了求导后把 2 约掉公式写起来清爽。后面推导我会用不带系数的形式因为更直观也省得约分绕来绕去。注意损失函数加不加系数影响的是损失值的大小不影响最优解。但如果用梯度下降这类迭代算法学习率就得配合这些系数调否则收敛速度会有差异。3. 一元线性回归推导从求导到闭式解这是整篇的核心把每一步写透。我们只处理一个特征 x目标变量 y模型 y ax b。3.1 建模与求导思路模型是 y ax b损失函数是 L \sum_{i1}^{n}(y_i - a x_i - b)^2。L 是关于 a 和 b 的二元函数。在高数里求二元函数的最小值常见做法是先对每个变量求偏导令偏导数为零解方程组。因为 L 是凸函数平方和开口向上所以偏导为零的点就是全局最小值点。这一点很重要线性回归的损失函数不会像神经网络那样有局部最优的困扰。3.2 对 b 求偏导先把 L 看成 b 的函数求 \frac{\partial L}{\partial b}\frac{\partial L}{\partial b} -2 \sum_{i1}^{n}(y_i - a x_i - b)令它等于零\sum_{i1}^{n}(y_i - a x_i - b) 0把求和拆开\sum y_i - a \sum x_i - n b 0于是b \frac{\sum y_i - a \sum x_i}{n} \bar{y} - a \bar{x}这个结果其实很优美不管 a 最终取多少最优直线一定经过所有样本点的重心 (\bar{x}, \bar{y})。就像你在一堆点里找一条“中间”的线线的中点要落在数据云团的中心。3.3 对 a 求偏导再对 a 求偏导\frac{\partial L}{\partial a} -2 \sum_{i1}^{n} x_i (y_i - a x_i - b)令它等于零\sum x_i y_i - a \sum x_i^2 - b \sum x_i 0把刚才解出的 b \bar{y} - a \bar{x} 代进去\sum x_i y_i - a \sum x_i^2 - (\bar{y} - a \bar{x}) \sum x_i 0整理一下注意 \bar{x} \sum x_i \frac{(\sum x_i)^2}{n}于是a \frac{\sum x_i y_i - \frac{1}{n}\sum x_i \sum y_i}{\sum x_i^2 - \frac{1}{n}(\sum x_i)^2}实际操作中更常用的是它的等价形式a \frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{\sum (x_i - \bar{x})^2}分子就是 x 和 y 的协方差差一个常数分母是 x 的方差也差一个常数。所以斜率的本质是“x 变一个单位时y 平均跟着变多少”完全符合直觉数据本身相关性越强斜率估计越可信。3.4 求导结果的几何直觉得到 a 和 b 之后回归直线 y ax b 有两个显著特征一是经过样本均值点 (\bar{x}, \bar{y})二是斜率等于协方差除以方差。从这个角度看最小二乘法并不需要死记公式你只需要记住三个词重心、协方差、方差。做题遇到的时候按这三个量去套基本不会出错。不过有个前提要强调分母 \sum (x_i - \bar{x})^2 不能为零也就是 x 的数据必须有波动。如果所有 x 都是同一个值那么斜率在数学上没有定义——你连“x 变大”这件事都观察不到自然没法估计 x 对 y 的影响。这在我后面踩坑章节会细说。4. 矩阵化推导正规方程是怎么来的只有一个特征时手动算 a 和 b 还撑得住。如果特征变成两个、三个甚至几十个手动公式就完全没法写了。这个时候需要用矩阵把所有逻辑统一起来。4.1 设计矩阵 X 与参数向量把模型 y ax b 改写成向量形式。构造一个 n 行 m 列的矩阵 X每一行是一个样本的所有特征列数 m 是特征数量。为了把截距 b 也塞进向量里通常在矩阵最左边加一列全为 1 的列叫做偏置列对应的参数就是 b。于是线性回归变成 y X\theta。其中 X 是 n×m 矩阵θ 是 m 维列向量y 是 n 维列向量。在只有一个特征时θ [b, a]^TX 的第一列全是 1第二列是 x 的取值。这和前面的一元模型完全一一对应。4.2 损失函数的矩阵展开误差向量 e y - Xθ残差平方和用矩阵写法就是L(\theta) (y - X\theta)^T (y - X\theta)展开L(\theta) y^T y - y^T X\theta - \theta^T X^T y \theta^T X^T X\theta注意 y^T Xθ 是一个标量1×1 矩阵它的转置就是 θ^T X^T y同样也是标量两者相等。所以中间两项可以合并成 -2θ^T X^T yL(\theta) y^T y - 2\theta^T X^T y \theta^T X^T X\theta4.3 求梯度并令其为零对 θ 求梯度。这里需要两个矩阵求导的基本结论\frac{d(\theta^T X^T y)}{d\theta} X^T y\frac{d(\theta^T X^T X\theta)}{d\theta} 2 X^T X\theta第二个式子成立是因为 X^T X 是对称矩阵。如果你不想深究可以直接当作“矩阵版的二次函数求导”来记形式上跟标量表达式 \frac{d(a\theta^2)}{d\theta} 2a\theta 是对应的。于是\nabla L -2X^T y 2X^T X\theta 0移项X^T X\theta X^T y如果 X^T X 可逆两边左乘它的逆矩阵\theta (X^T X)^{-1} X^T y这就是传说中的正规方程。整个式子看起来高大上拆开看就是“先做投影再解方程”的过程。4.4 可逆性与数值稳定性X^T X 什么时候不可逆一是样本数小于特征数二是特征之间完全线性相关比如一个特征是另一个特征的 2 倍。这时候 (\cdot)^{-1} 在数学上不存在正规方程直接失效。运维场景里特征可能很多比如把一周每天的 CPU 使用率都作为特征相互之间就高度相关很容易踩到这个坑。即便 X^T X 可逆当特征高度相关时它的条件数会变得很大直接求逆在浮点数环境下会放大误差算出来的参数可能没有意义。所以工程上很少真去调用 inv()而是用基于 SVD 分解的求解器后面写代码的时候我会给出对比。大家在实践中千万别看到公式就无脑 inv() 一把梭。5. 用 Python 把数学公式变成能跑的代码理论说完上代码。我用的是最简单的 numpy不需要引入任何深度学习框架。5.1 手动实现一元线性回归先造一份模拟数据然后用前面推导的公式手写一遍import numpy as np x np.array([1, 2, 3, 4, 5], dtypefloat) y np.array([2.1, 3.8, 6.2, 7.5, 10.1], dtypefloat) x_mean x.mean() y_mean y.mean() # a Σ(x-x̄)(y-ȳ) / Σ(x-x̄)² a np.sum((x - x_mean) * (y - y_mean)) / np.sum((x - x_mean) ** 2) b y_mean - a * x_mean print(f斜率 a {a:.4f}) print(f截距 b {b:.4f})跑出来大概是 a ≈ 2.0、b ≈ 0.0因为数据本来就是按 y 2x 加噪声生成的。理解流程比结果重要先算均值再算协方差和方差最后求斜率、算截距。5.2 用矩阵和正规方程再算一遍把同样的数据变成设计矩阵用正规方程求解# 构造设计矩阵第一列全1对应截距 X np.column_stack([np.ones_like(x), x]) # 正规方程理解用 theta np.linalg.inv(X.T X) X.T y print(正规方程结果:, theta) # 更稳的方式推荐 theta_lstsq, residuals, rank, singular np.linalg.lstsq(X, y, rcondNone) print(lstsq 结果:, theta_lstsq)第一列对应截距 b第二列对应斜率 a。正常情况两个结果完全一致。但这里我要特别强调以后在工程里不要直接写 inv()。np.linalg.lstsq 内部走 SVD 分解遇到不可逆或接近不可逆的矩阵也能给出一个最小范数解不会直接崩。可以在注释里保留正规方程的写法用于理解但真正部署到监控脚本里时请用它。5.3 和 sklearn 的结果对比写代码的人总喜欢验证一下自己的轮子有没有装歪。用 sklearn 的标准 LinearRegression 对同一份数据跑一遍from sklearn.linear_model import LinearRegression model LinearRegression() model.fit(x.reshape(-1, 1), y) print(sklearn 斜率:, model.coef_[0]) print(sklearn 截距:, model.intercept_)不出意外三个结果高度一致差在小数点后面几位。这证明手工推导和实现没有出错。对运维同学来说这个对照实验很重要它能让你在面对更复杂模型时坚信底层原理和现成库是互通的。6. 实战用最小二乘法预测磁盘什么时候告警讲完原理回到开头的场景。假设监控系统每天 0 点记录一次根分区使用率我手上有最近 30 天的数据想预测未来 7 天会不会撞上告警线。6.1 数据准备与可视化从监控系统导出的数据一般是表格一列是日期、一列是使用率。建模前要把日期换成数值特征days np.arange(30, dtypefloat) usage np.array([ 55.0, 55.8, 56.5, 57.1, 58.0, 58.7, 59.4, 60.2, 60.9, 61.5, 62.3, 63.0, 63.8, 64.4, 65.2, 66.0, 66.7, 67.3, 68.1, 68.9, 69.6, 70.3, 71.0, 71.8, 72.5, 73.2, 74.0, 74.7, 75.4, 76.1 ], dtypefloat)先画散点图确认趋势这一步永远值得做。如果数据里明显有清理磁盘、扩容造成的“断崖式”拐点就该先清洗不能让那些异常点参与回归。别一上来就 fit。6.2 拟合、评估与参数解读X np.column_stack([np.ones_like(days), days]) theta, _, _, _ np.linalg.lstsq(X, usage, rcondNone) b, a theta print(f截距 b {b:.3f}, 斜率 a {a:.3f}) usage_pred X theta ss_res np.sum((usage - usage_pred) ** 2) ss_tot np.sum((usage - usage.mean()) ** 2) r2 1 - ss_res / ss_tot rmse np.sqrt(ss_res / len(usage)) print(fR² {r2:.4f}, RMSE {rmse:.4f})斜率 a 的含义就是“磁盘使用率每天大约上涨多少个百分点”这比盯着曲线拍脑袋靠谱得多。一次拟合之后你可以把“每天涨幅”和“预计哪一天到告警线”直接写进周报。6.3 预测未来 7 天并设置预警阈值预测就是拿未来日期继续代入表达式future_days np.arange(30, 37, dtypefloat) X_future np.column_stack([np.ones_like(future_days), future_days]) future_pred X_future theta for d, p in zip(future_days, future_pred): print(f第 {d:.0f} 天: {p:.2f}%)如果输出显示第六、七天后会超过 80%那在还没到系统告警阈值前就可以先发黄色预警。对运维团队来说这带来的变化不只是少接几个电话而是把被动救火变成了主动排障。注意预测的可靠性依赖趋势在未来保持线性。真实场景里磁盘可能被大文件写入、日志清理、快照备份干扰所以预测值要当成参考不能当成精确数字建议加 5%~10% 的余量再设阈值。7. 运维转机器学习最容易踩的 5 个坑最后这部分是我最想聊的都是自己实打实踩过坑以后总结出来的。整理成一张速查表放在最后方便收藏。7.1 把时间序列直接当线性回归数据用严格来说时间序列数据存在自相关性直接用普通最小二乘会违背残差独立的假设。但在容量预测这个场景里只要趋势稳定、样本都来自同一个环境把“第几天”当特征做线性回归往往够用。如果你要更严谨可以把滞后特征、移动平均特征加进来再对比效果。我的建议是先画图再决定用简单线性回归还是更复杂的特征工程。7.2 归一化操作泄漏到测试集数据处理时很容易犯一个错先对全体数据做标准化再切训练集和测试集。这样一来测试集的信息已经通过均值、方差混进训练过程评估结果会虚高。正确做法是先切分再单独计算训练集的均值方差用同一套参数去处理训练集和测试集。这个问题在用 sklearn 的 StandardScaler 时尤其常见。7.3 异常值会把斜率带偏最小二乘法对异常值非常敏感因为误差被平方放大了。运维数据里恰恰不缺少“异常”一次扩容、一次日志清空、一次临时拉高都会在曲线上留下异常点。如果把它们全部喂给模型拟合出的斜率可能被拽到完全没意义的位置。处理方式可以先基于业务规则剔除明显异常或者用可视化人眼过一遍再或者用 RANSAC、Huber 回归这类更抗异常的方法。这个教训我是在磁盘预测的二次复盘里才印象深刻的。7.4 特征线性相关导致正规方程失效特征多了以后如果两个特征完全线性相关X^T X 就不可逆inv() 会直接报错即使不完全相关只要相关性很高逆矩阵也会数值不稳定。解决办法是删除冗余特征或者考虑用岭回归等高容错方案。遇到“特征很多但样本很少”的场景时尤其要小心正规方程会把参数估计得非常离谱。7.5 只看拟合曲线漂亮不看指标拟合曲线上几乎总能贴得很好真正的问题是样本外表现。评估回归模型至少要看 R² 和 RMSERMSE 告诉你预测平均偏差多少个百分点R² 告诉你模型解释了多大比例的数据波动。对于磁盘使用率这类数据如果 RMSE 是 0.3那预测非常可用如果 RMSE 是 3那预警可靠性就要打个问号。运维同学普遍擅长看图和靠经验但转机器学习之后要学会用数字说话。症状可能的坑解决办法拟合曲线完美测试集一塌糊涂数据泄漏或过拟合检查预处理是否泄漏增加验证集简化模型斜率明显不合理异常值污染先画图剔除异常点或用鲁棒回归X^T X 不可逆或报错特征共线、样本太少删冗余特征加正则化趋势预测对但偏差很大特征不够或模型不匹配增加滞后特征尝试非线性回归最后说一点个人感受我从凌晨三点被磁盘告警吵醒到把最小二乘法推导吃透、写进容量预测脚本整个过程最难的不是公式而是肯坐下来把“预测趋势”这个目标一步一步翻译成数学语言。如果你也是运维出身建议别一上来就啃支持向量机、深度学习先从这篇最小二乘法入手用自家监控数据跑一个预测。那种“数学公式变成了凌晨三点能救我的工具”的感觉才是坚持下去的真正动力。
返回列表