简介:资源内含一份基于数据挖掘的应用系统负载分析与磁盘容量预测Python源码包,面向系统运维、数据分析与机器学习初学者,解决如何在历史负载数据基础上建立容量预测模型的问题。压缩包共6个文件,包含4个xls格式的数据文件(涵盖原始数据与预处理后的数据)、1个py主程序脚本和1个txt模块导入说明,整体仅20KB,轻量便于快速下载和学习。已有561人学习下载,适合作为数据挖掘入门实战参考。通过源码与配套数据文件,读者可了解数据清洗、特征分析、模型训练与预测输出的完整流程,并结合实际磁盘使用数据观察算法参数如何影响预测效果;代码结构简洁、注释清晰,便于二次修改,为后续扩展其他系统指标预测提供可复用的框架。
1. 应用系统负载分析与磁盘容量预测:这套 Python 源码不是玄学
周五下午 4 点,监控告警弹出:/data 分区使用率已到 85%,按这个增长速率,再过两天日志文件就会把磁盘写满,应用直接进入只读状态。这类故障我处理过不止一次,问题从来都不是突然发生的——磁盘使用量曲线早就给出了信号,只是没人把它当成一个可预测的指标去看。
这套应用系统负载分析与磁盘容量预测 Python 源码,核心就是把“负载怎么看、容量怎么预测”串成一条完整流程:采集系统指标和应用日志、做特征工程、训练模型、输出每个分区的剩余可用天数。它不解决容量规划里的所有问题,但能让你在磁盘写满之前收到一份明确的预测报告,而不是等故障发生了再扩容、再找数据、再复盘。
如果你是运维工程师、后端开发,或者团队里负责服务器容量的人,这套源码正好卡在你们最痛的位置。它不要求你有很强的时序预测理论背景,代码按采集、特征、模型、服务四层拆开,照着流程跑就能拿到预测结果,也适合直接接进现有监控体系。下面我从原理到实现,把每一步拆开讲清楚。
2. 负载分析与容量预测的原理:指标口径、建模选型与源码结构
拆代码之前,先统一口径。“应用系统负载分析”这个说法,在不同团队手里含义差别很大:有人把它等同于 CPU 使用率平均值,也有人把它理解为请求量、线程数、GC 停顿时间这些应用层指标。这套源码的做法是两者都接,但归到两条线上:系统资源线(CPU、内存、磁盘 IO、网络)和应用负载线(请求量、错误率、平均响应时间)。
真正影响磁盘容量的,主要落在系统资源线里的磁盘使用量,以及应用负载线里会引发日志增长的写流量指标。两条线先定义清楚,后面做特征和预测时才不会被口径带偏。下面从指标采集开始讲,再落到建模选型和源码目录结构。
2.1 指标拆解:CPU、内存、磁盘 IO 与应用层负载的关系
预测磁盘容量,不能只采集一项 disk_used 就完事。磁盘增长往往由应用行为驱动:日志轮转策略、数据库归档频率、临时文件清理机制,少了任何一项,曲线都会在你意想不到的时间点拐头。所以我拿到这套源码,第一件事是看它的采集脚本到底覆盖了哪些指标。
下面是这类工程里最常见的采集端写法,用 psutil 把系统指标打成一行一行的 CSV,保留原始时间戳,不做任何聚合:
import time import csv import psutil def collect_once(interval=60): # 按挂载点遍历磁盘分区,避免把系统盘和数据盘混在一起 parts = [] for part in psutil.disk_partitions(all=False): usage = psutil.disk_usage(part.mountpoint) parts.append({ "mount": part.mountpoint, "total_gb": round(usage.total / (1024 ** 3), 2), "used_gb": round(usage.used / (1024 ** 3), 2), "used_percent": usage.percent, }) return { "ts": int(time.time()), "cpu_percent": psutil.cpu_percent(interval=1), "mem_used_percent": psutil.virtual_memory().percent, "disk_read_mb_s": psutil.disk_io_counters().read_bytes / 1024 / 1024, "disk_write_mb_s": psutil.disk_io_counters().write_bytes / 1024 / 1024, "parts": parts, } def write_csv(path, interval=60): with open(path, "a", newline="") as f: writer = csv.DictWriter(f, fieldnames=["ts", "cpu_percent", "mem_used_percent", "disk_read_mb_s", "disk_write_mb_s", "parts"]) writer.writeheader() while True: row = collect_once(interval) writer.writerow(row) time.sleep(interval)这段代码里有几个设计细节值得注意。disk_partitions 遍历的是分区而不是整机磁盘,同一个服务器上如果 /data 是独立数据盘,/home 是系统盘,两个分区的增长曲线可能完全不同,混在一起算一个容量池会把预测结果拉偏。代码把 mount 点单独留在各部分里,就是给后续按分区建模留了接口。
磁盘容量预测真正关心的指标按优先级排,大致是这个顺序:
| 指标 | 采集建议 | 用途 |
|---|---|---|
| 分区使用量 used_gb | 每 5-15 分钟一次 | 核心预测目标 |
| 分区总量 total_gb | 每 5-15 分钟一次 | 计算剩余比例 |
| 写入字节数 write_bytes | 每 5 分钟一次 | 判断增长速率来源 |
| 文件数 / inode 使用率 | 每 30 分钟一次 | 防止容量未满但 inode 耗尽 |
我用几个项目验证过,采集间隔的选择影响很大。间隔太密(5 秒一次)数据量大,而且磁盘增长是慢变量,短间隔里全是噪声,模型学到的是抖动而不是趋势;间隔太疏(一天一次)会丢失夜间清理脚本造成的下降拐点。这套源码默认 60 秒采集一次,我一般会把它改成 300 秒,跑一周拿到的数据量也足够训练了。
2.2 预测方法选型:趋势外推优先,机器学习打补丁
源码包的模型层分两类,一类是统计方法,移动平均、指数平滑、线性回归;另一类是监督学习,随机森林和梯度提升树。同一个预测场景维护两类模型,不是冗余,而是因为磁盘使用量这个序列的性质决定了模型选择空间。
磁盘使用量是一个带强趋势、弱周期、偶发阶跃的序列。强趋势意味着线性模型往往已经能解释大部分变化;弱周期意味着周维度上存在规律——周一早上日志量暴涨,周末流量低谷——但这个规律不稳定;偶发阶跃意味着清理脚本、数据迁移会让序列突然掉头,统计模型对这种突变反应迟钝,需要人工标记或残差监控补齐。
不同方法的适用边界可以用一张表大致说明:
| 方法 | 对趋势的适应 | 对周期的处理 | 实现成本 | 适合场景 |
|---|---|---|---|---|
| 移动平均 | 滞后明显 | 不处理 | 极低 | 看短期趋势,不宜单独预测 |
| 指数平滑 | 能跟随趋势 | 需加季节分量 | 低 | 日志量稳定的应用 |
| 线性回归 | 直接拟合斜率 | 需加周期特征 | 低 | 长期线性增长的数据库 |
| 随机森林 | 能拟合非线性 | 弱 | 中 | 存在突变和特征交互 |
| Prophet 式分解 | 好 | 显式建模 | 中高 | 周期明显的日活型业务 |
这套源码的主链路是线性回归做基线、随机森林做残差修正。从工程角度看这个选型是合理的:基线模型保证不会太离谱,树模型弥补非线性,两个结果一对比,还能判断预测稳定度。如果两个模型给出的剩余天数差出 20 天以上,说明历史数据里存在明显异常段,这时候更该怀疑数据而不是模型。
2.3 源码目录结构:四个模块的职责与替换边界
打开这套源码,先认四个目录:collect、features、models、service,分别对应采集、特征、训练和对外输出。文件布局大概是这样:
app_system_load_analysis/ ├── collect/ │ ├── system_metrics.py # psutil 系统指标采集 │ └── app_log_parser.py # 应用日志解析,生成请求量序列 ├── features/ │ ├── build_dataset.py # 特征工程:滞后、差分、滑动窗口 │ └── split.py # 按时间切分训练集/验证集 ├── models/ │ ├── linear_baseline.py # 线性回归基线 │ ├── tree_model.py # 随机森林 / GBDT 模型 │ └── evaluate.py # MAE / RMSE / MAPE 评估 ├── service/ │ ├── train.py # 训练入口 │ ├── predict.py # 加载模型预测未来 N 天 │ └── capacity_report.py # 输出剩余可用天数报告 └── config/ └── disk_predict.yaml # 分区、阈值、预测天数配置每个文件都尽量独立可运行,这是这类 Python 工程源码的通用组织方式。我拿到手习惯先跑 predict.py,因为它只依赖一个已经训练好的模型和一份历史数据,能最快看到输出长什么样;train.py 才是重头戏,但它依赖前面特征工程的数据格式,所以我会在读完 model 目录之后再跑。
配置项里最有价值的是三个参数:history_days、predict_days、capacity_threshold。history_days 决定训练窗口,一般 30 天起步;predict_days 是预测窗口,磁盘容量预测通常设 30 到 90 天;capacity_threshold 是告警线,0.8 表示使用率到 80% 就开始预警。这三个参数直接决定报告里的剩余天数含义,改之前先想清楚自己的业务响应周期。
3. 把源码跑通:环境准备、数据接入与一次完整训练
原理讲完,接下来是最容易卡住新手的部分:环境。环境问题看似琐碎,但我在帮别人复现这套源码时,至少一半的时间花在解决 Python 版本和 numpy 安装上。下面按我自己操作的顺序来写。
3.1 Python 环境与依赖安装:版本选择与 numpy 安装的常见坑
第一步先确认 Python 版本。这套源码面向 Python 3.8 以上,我在 CentOS 7 机器上踩过坑,系统自带的 Python 是 3.6,直接 pip install 一堆依赖会报语法错误。打开终端先看一眼:
python3 --version如果版本低于 3.8,不要硬适配代码,用 pyenv 或系统包管理器装一个新版本再继续。版本确认后创建虚拟环境,把依赖装进去。工程里 requirements.txt 通常长这样:
pandas==1.5.3 numpy==1.23.5 scikit-learn==1.2.2 psutil==5.9.4 PyYAML==6.0 matplotlib==3.7.1安装最容易出问题的两个库是 numpy 和 scikit-learn。Python 3.11 以上环境里直接 pip install numpy,有时会看到 “Could not find a version that satisfies the requirement” 的提示,多半是默认源没有对应版本的预编译 wheel。解决方法是先升级 pip,再指定兼容的 numpy 版本:
python3 -m pip install --upgrade pip pip install numpy==1.23.5 pandas==1.5.3 scikit-learn==1.2.2装完用一条命令验证关键依赖是否就绪,避免训练跑到一半才发现缺库:
python3 -c "import pandas, numpy, sklearn, psutil; print(pandas.__version__, numpy.__version__, sklearn.__version__)"能正常打印版本号,说明环境这关过了。如果导入 sklearn 报缺少 libgomp 之类的底层库,那是系统级依赖问题,在 CentOS 上执行 yum install libgomp,在 Ubuntu 上执行 apt install libgomp1 就能解决。
3.2 数据接入三种方式:日志、数据库与监控平台导出
源码默认从 CSV 读数据,但实际环境里数据源往往分散在日志、数据库和监控系统中。源码提供了三个采集入口,我逐个说。
第一种是解析应用日志。比如 nginx access_log,统计每小时请求量,生成应用负载序列。这里用 awk 就能很快出一版:
# 统计 access_log 每小时的请求数,输出为 request_cnt_hourly.csv awk '{print substr($4, 2, 13)}' /var/log/nginx/access.log | sort | uniq -c > request_cnt_hourly.csv这个命令把日志里形如 [10/Oct/2024:13:56:12 的时间字段截取到小时,然后排序去重计数。生成的文件第一列是请求次数,第二列是小时,后续 build_dataset.py 只需要把这个文件和系统指标按时间合并即可。
第二种是从数据库取容量数据,常用于 MySQL、PostgreSQL 这类有系统表的数据库。以 PostgreSQL 为例,查各表占用空间:
SELECT schemaname, tablename, pg_total_relation_size(schemaname || '.' || tablename) AS size_bytes, now() AS ts FROM pg_tables ORDER BY size_bytes DESC;把查询结果定时导出成 CSV,就形成了数据库容量的历史序列。这里有个关键点:这条 SQL 里的 now() 是采集时刻的时间戳,必须定期执行才能积累出时间序列。只在磁盘告警时手工查一次,解决不了预测问题。
第三种是监控平台导出,我在生产环境里最常用。从 Prometheus 查 node_filesystem_avail_bytes 这类指标,用 API 拉下来存成 JSON:
curl -G 'http://prometheus.example.com/api/v1/query_range' \ --data-urlencode 'query=node_filesystem_avail_bytes{mountpoint="/data"}' \ --data-urlencode 'start=2024-10-01T00:00:00Z' \ --data-urlencode 'end=2024-10-08T00:00:00Z' \ --data-urlencode 'step=300' | python3 -m json.tool > disk_usage.json注意把监控地址、挂载点、时间范围换成自己的。拿到 JSON 后写个小脚本转成 CSV,字段格式要和 collect/system_metrics.py 的输出保持一致。这一步是整个接入过程最容易翻车的地方——字段名对不上,后面的 build_dataset.py 直接报 KeyError,而且错误信息只会告诉你缺字段,不会告诉你去哪补。
3.3 训练与评估:两条命令跑出预测报告
数据准备好之后,训练入口在 service/train.py。参数通过 config/disk_predict.yaml 控制,我摘一段典型配置:
disk: mountpoint: "/data" total_gb: 2000 capacity_threshold: 0.8 data: csv_path: "output/system_metrics.csv" history_days: 45 model: algorithm: "ensemble" # linear / ensemble forecast_days: 60 retrain_on_missing_data: true然后运行训练:
cd app_system_load_analysis python3 service/train.py --config config/disk_predict.yaml训练脚本会先读 CSV,按 mountpoint 过滤出 /data 分区数据;features/build_dataset.py 生成训练样本;models/linear_baseline.py 和 tree_model.py 各自训练,最后把评估指标和预测结果写进 output/。正常跑完,output/ 下会多出三个文件:evaluation.json、forecast.csv、capacity_report.csv。
evaluation.json 里记录 MAE、RMSE、MAPE 三个值。我建议先看 MAPE,它表示平均百分比误差,5% 以内说明预测曲线贴近真实使用量,超过 15% 就要怀疑数据质量或特征工程有问题。forecast.csv 是未来 60 天的逐日预测使用量,capacity_report.csv 直接给出每个分区预计耗尽的天数。排错时先看 capacity_report.csv 的输出行数对不对,再反推前面环节,这是最快的排查路径。
4. 核心代码拆解:特征工程、容量预测建模与剩余天数计算
训练跑通了,接下来要知道模型到底在做什么。这一章我把特征构建、模型融合、剩余天数计算三段代码拆开,逐行说明。
4.1 特征工程:把时间序列整理成模型能吃的训练样本
直接拿磁盘使用量的逐日序列去训练模型,效果通常很差,因为模型只看得到“今天”,看不到“昨天”和“上周同一时间”。特征工程要做的,就是把时间序列转换成监督学习样本:用过去 K 天的值,预测未来某一天的值。
下面是 build_dataset.py 里最核心的一段,构造滞后特征、滑动统计、差分特征:
import pandas as pd def build_features(df, target_col="used_gb", lag_steps=7, forecast_horizon=7): # 按时间排序,避免 CSV 乱序影响滞后特征 df = df.sort_values("ts").reset_index(drop=True) df["ts"] = pd.to_datetime(df["ts"]) sample = pd.DataFrame() sample["y"] = df[target_col].shift(-forecast_horizon) # 未来第 7 天作为标签 # 滞后特征:过去 7 天每天的磁盘使用量 for lag in range(1, lag_steps + 1): sample[f"lag_{lag}"] = df[target_col].shift(lag) # 滑动统计:过去 30 天的均值、标准差,捕捉趋势和波动 sample["mean_30d"] = df[target_col].rolling(30).mean() sample["std_30d"] = df[target_col].rolling(30).std() # 一阶差分:当天的增量,用于识别增长是在加速还是减速 sample["diff_1d"] = df[target_col].diff() # 剩余可用空间百分比 sample["avail_percent"] = 1 - df[target_col] / df["total_gb"] # 去掉尾部没有标签的行 sample = sample.dropna().reset_index(drop=True) return sample这里有几个设计需要说明。滞后特征 lag_1 到 lag_7 让模型知道“昨天、前天、一周前”分别用了多少,这比只用当天值稳定得多;mean_30d 和 std_30d 是滑动窗口统计,mean 描述长期水平,std 描述波动幅度,两者组合能帮模型区分“稳定增长”和“波动剧烈”两种状态;diff_1d 是今天和前一天的差值,模型可以用它判断增长是在加速还是减速。
注意 forecast_horizon 这个参数。上面代码里设成 7,表示样本的标签是未来第 7 天的使用量,而不是未来第 1 天。预测 60 天容量时,一般会循环调用这个函数,以 7 天为步长滚动生成多组样本。真实项目里,horizon 越大,可用的样本越少,训练难度越高,所以这个值不能拍脑袋定,要看业务期望提前多少天收到告警。
4.2 容量预测:线性回归打底,树模型吸收非线性
特征工程之后,模型层就比较标准了。线性回归在这种强趋势序列上表现并不差,它能把“平均每天增长几 GB”学得比较准;随机森林则能捕捉“周六日志量大导致增速变化”这类非线性关系。
源码模型层的关键代码如下:
import numpy as np from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error def train_models(X_train, y_train, X_val, y_val): lr = LinearRegression() lr.fit(X_train, y_train) lr_pred = lr.predict(X_val) rf = RandomForestRegressor( n_estimators=200, max_depth=8, min_samples_leaf=5, random_state=42, n_jobs=-1 ) rf.fit(X_train, y_train) rf_pred = rf.predict(X_val) result = {} for name, pred in [("linear", lr_pred), ("random_forest", rf_pred)]: mae = mean_absolute_error(y_val, pred) rmse = mean_squared_error(y_val, pred, squared=False) mape = np.mean(np.abs((y_val - pred) / (y_val + 1e-6))) * 100 result[name] = {"mae": mae, "rmse": rmse, "mape": mape} # 按比例融合两个模型,降低单一模型误判的风险 blend = 0.6 * lr_pred + 0.4 * rf_pred result["blend"] = { "mae": mean_absolute_error(y_val, blend), "rmse": mean_squared_error(y_val, blend, squared=False), } return lr, rf, result重点看 blend 的计算。线性回归预测乘 0.6,随机森林预测乘 0.4,加起来作为最终输出。这个比例不是玄学,是我在类似场景里的经验值:磁盘使用量数据中线性趋势占六成,非线性波动占四成。实际使用中你可以改成 0.5/0.5,然后观察验证集 MAPE 的变化;如果随机森林的 MAPE 明显更低,就调大它的权重。
评估指标里 mape 加了 1e-6,是为了防止 y_val 为 0 时除零崩溃,这是容量数据的常见保护手段。训练集和验证集的划分必须是按时间切分而不是随机切分,这一点在避坑章节会专门展开,因为它是我见过最多人踩的坑。
4.3 剩余可用天数:容量阈值与提前量怎么算
模型输出的是未来每一天的预测使用量,但业务人员更关心一个问题:磁盘还有几天会满。源码里用 capacity_report.py 把这个计算固定下来,核心逻辑是找到预测曲线第一次越过阈值的日期:
import pandas as pd def calculate_capacity_exhaustion(forecast_df, capacity_threshold=0.8): # forecast_df 需要包含 total_gb 和 predicted_used_gb 两列 forecast_df["used_percent"] = forecast_df["predicted_used_gb"] / forecast_df["total_gb"] # 找到预测使用率第一次超过阈值的日期 over_idx = forecast_df.index[forecast_df["used_percent"] >= capacity_threshold] if len(over_idx) == 0: return None, None # 预测窗口内不会触达阈值 first_over = over_idx[0] exceed_date = forecast_df.loc[first_over, "ds"] days_left = (exceed_date - forecast_df.loc[forecast_df.index[0], "ds"]).days return exceed_date, days_left这个函数的逻辑很直白:遍历预测结果,找到第一个 used_percent 大于等于 0.8 的日期,把它和基准日期相减,得到剩余天数。如果整个预测窗口都没超阈值,返回 None,意思是安全期内不必处理。
这里我一般会把容量阈值从 0.85 改到 0.8,因为生产环境要预留运维操作时间。当报告显示剩余 15 天时,实际上从收到告警到扩容、迁移数据、重启服务,至少需要一周,0.8 阈值相当于给团队留出缓冲。源码把阈值放在配置文件的意图就在这里——每个团队的响应速度不同,阈值自然应该不同。
5. 应用系统负载分析与磁盘容量预测避坑指南:五个翻车现场
源码本身不复杂,真正坑人的永远是数据细节和操作习惯。下面五条是我在真实环境里踩过的,每条按「现象 → 原因 → 解决」说明。
5.1 采样间隔不一致导致预测曲线漂移
现象:训练结束后,预测曲线在某些时段比真实值偏高 20%,有些时段又偏低,整体走势呈锯齿状。
原因:采集脚本中途被重启过,或者定时任务只在工作时间运行,深夜数据缺失,时间序列的步长不再均匀。模型把每个样本当成等间隔处理,时间间隔不一致导致趋势斜率计算错误,预测自然漂移。
解决:在 build_dataset.py 里强制按固定周期重采样,缺失的小时用前一周同时刻的值填充,再检查相邻时间戳的差值:
import pandas as pd df = pd.read_csv("output/system_metrics.csv", parse_dates=["ts"]).set_index("ts") df = df.resample("1H").mean().interpolate(method="time") print(df.index.to_series().diff().dropna().value_counts()) # 应该只有 1H 一个值如果输出里出现多个不同的时间差,说明数据没有完全对齐,把异常区间直接丢弃,不要勉强保留。
5.2 用平均值代替峰值,漏掉磁盘水位毛刺
现象:预测报告显示剩余 200GB,结果三天后磁盘被一个大文件写满,应用直接变成只读。
原因:训练时用的是按天聚合的平均使用量,忽略了某几个小时内的高写入峰值。磁盘增长不是匀速的,大促、定时任务、日志刷盘都会在短时间内把水位抬上去,均值预测对这类毛刺完全无感。
解决:采集端同时记录每小时的最大使用量和 95 分位使用量,预测时取 p95 作为基准,额外再加 10% 安全余量。容量管理里只靠平均值做预测,是风险最高的做法。
5.3 只盯着磁盘空间,没看 inode 使用率
现象:df -h 显示 /data 用了 30%,但应用创建文件时报 “No space left on device”。
原因:磁盘空间没满,inode 耗尽了。小文件特别多的应用,比如临时文件、缓存目录,会先把 inode 吃光。如果采集脚本只记录磁盘使用量,预测结果完全体现不出这个风险。
解决:把 inode 使用率加进采集指标,并按同样流程单独做预测。生产环境里 inode 的耗尽速度往往比磁盘空间更快,尤其在生成大量小文件的服务上。两个指标同时监控,任何一个超过阈值都触发告警。
5.4 随机切分数据集,导致预测模型数据泄漏
现象:模型在训练集上 MAPE 只有 2%,一到线上预测就偏了 20% 以上。
原因:用了 train_test_split 的默认随机切分。时间序列里前一天的样本和后一天的样本高度相关,随机切分等于让模型在训练时就偷看了未来的数据,训练分数虚高,真实场景里自然打回原形。
解决:改成按时间顺序切分,前 80% 训练、后 20% 验证,并在中间留出 7 天间隔:
from sklearn.model_selection import train_test_split # 错误做法:随机切分导致数据泄漏 # X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2) # 正确做法:保持时间顺序,训练集验证集之间留出空白间隔 cut = int(len(X) * 0.8) X_train, X_val = X.iloc[:cut], X.iloc[cut:] y_train, y_val = y.iloc[:cut], y.iloc[cut:]留出的 7 天空白间隔,能验证模型是否真的学会了预测未来,而不是单纯拟合相邻样本。
5.5 缺失数据直接补零,预测曲线出现锯齿
现象:预测曲线中间突然掉到谷底,然后又反弹,像锯齿一样。
原因:采集停了一段时间,数据缺失后直接 fillna(0),序列里出现假的“清零”。模型把这一天当成真实的容量下降,趋势斜率被拉低,后面只能强行反弹,预测结果彻底失真。
解决:用时间插值代替补零,缺失较长的时间段直接剔除:
df["used_gb"] = df["used_gb"].interpolate(method="time")如果缺失超过 6 小时,我更倾向直接删掉这一段。宁可样本少一点,也不要给模型输入假信号,这是容量预测里最重要的数据伦理。
6. 进阶:滚动回测与监控面板对接,别让模型自说自话
模型训练完,不能只看训练集上的分数就上线。我自己在这类容量预测项目里,最后一道工序永远是跑一遍滚动回测,把结果接到监控面板,观察一个完整的预测周期。
滚动回测的做法是:用历史某一天之前的数据训练模型,预测后面 30 天,然后对比真实值;把时间窗口往后移 7 天,重新训练、预测、对比,如此反复。这样验证的是模型在真实未来上的表现,而不是在同一个数据集上的拟合度。
import pandas as pd def walk_forward_validation(df, model_fn, train_days=45, forecast_days=30, step_days=7): results = [] df = df.sort_values("ts").reset_index(drop=True) start = 0 while start + train_days + forecast_days <= len(df): train = df.iloc[start:start + train_days] test = df.iloc[start + train_days:start + train_days + forecast_days] model = model_fn(train) pred = model.predict(test) # 只比较预测窗口最后一天的误差,更贴近业务真正关心的剩余天数 results.append(abs(pred.iloc[-1] - test["used_gb"].iloc[-1]) / test["used_gb"].iloc[-1]) start += step_days return pd.Series(results).describe()这段代码会在数据上滑动多次训练,输出的 describe 里,50% 和 75% 分位数就是模型在多个未来窗口上的稳定性表现。我一般盯 75% 分位:小于 10%,说明模型在多数时间窗口上预测靠谱;如果 max 很大,说明总有几个窗口预测崩了,要去检查那一段对应的数据是否存在日志清理、容量调整等事件。
回测通过之后,还有一步落地操作:源码默认把预测结果写 CSV,但团队监控系统通常要的是接口。我会在 service/predict.py 里加一个输出 JSON 的函数,把剩余天数、预测使用率、触发阈值直接暴露给监控,由监控平台负责推送告警:
import json def output_prediction_json(forecast_df, capacity_report): payload = { "predict_date": forecast_df["ds"].max().strftime("%Y-%m-%d"), "days_left": capacity_report["days_left"], "threshold": capacity_report["threshold"], "series": [ {"date": row["ds"].strftime("%Y-%m-%d"), "predicted_gb": row["predicted_used_gb"]} for _, row in forecast_df.iterrows() ] } with open("output/prediction.json", "w") as f: json.dump(payload, f, ensure_ascii=False, indent=2)监控平台拿到这份 JSON 后,只需要读 days_left 字段做判断。比如 days_left 小于 14 天触发预警,小于 7 天呼叫值班人员。这比让各家监控系统各自解析 CSV 靠谱得多,也避免了字段命名不一致带来的维护成本。
从实践来看,滚动回测对容量预测的效果远超单次验证,尤其是多次滚动之后,你会对自己数据的“脾气”有清晰的体感。从那以后,我每次跑这套源码,都会强制走一遍回测流程,确认预测没有明显漂移,再决定要不要发出告警,而不是看一眼训练集分数就草率上线。希望帮到你。
本文还有配套的精品资源,点击获取