news 2026/10/11 11:36:01

用LSTM预测虚拟机实例数量:时序数据预处理与滚动多步预测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用LSTM预测虚拟机实例数量:时序数据预处理与滚动多步预测

简介:面向云计算资源管理与机器学习初学者,这是一份基于长短期记忆网络实现虚拟机实例个数预测的完整工程包,针对云数据中心资源利用率优化与弹性伸缩场景提供了一套可运行的参考实现。压缩包内合计六十七个文件,以十七个Java源码和十八个class编译文件为主体,同时配有文本格式的数据说明、Excel格式的训练样本、XML格式的工程配置和README说明文档;整体体积仅一百六十六KB,保持Eclipse工程结构,源码目录与编译输出目录分离,便于直接导入开发环境进行学习与复现。目前共有一百四十一人学习下载。资源不仅演示了长短期记忆网络的输入门、遗忘门、输出门和记忆单元等核心机制,还完整覆盖了虚拟机历史数据准备、模型构建与训练、实例数量预测的整个流程,能够帮助读者理解LSTM处理时序预测和云资源弹性伸缩问题的方法;配合清晰注释,既适合新手快速上手,也能为进阶者提供调参优化和工程化部署的参考,是课程设计与毕业设计的实用素材。

1. 虚拟机实例个数预测:为什么LSTM适合这份容量规划需求

预测虚拟机实例个数这件事,本质上是在预测一条时间序列的下一个值——而这条序列在大多数业务场景里并不平稳,它带着明显的日周期、周周期,偶尔还有突刺。LSTM能记住长周期依赖,比ARIMA这种线性模型更适合抓这种“昨天这个时段就涨过”的规律。我拿到这个项目包时,第一反应是看它数据怎么组织、窗口怎么切,因为时序预测的成败往往不在网络有多深,而在样本构造对不对。如果你正处于需要提前规划资源、控制授权成本、避免半夜弹扩容告警的状态,这套流程可以完整复用到你的场景里。适合所有做云资源管理、容量预估或基础设施监控的开发者和运维人员。

2. 从原始序列到训练样本:数据清洗、滑动窗口与归一化的三个关键选择

2.1 原始数据长什么样

这类项目包里的数据一般就是一个CSV,两列:一列是时间戳,一列是虚拟机实例个数。时间粒度常见的有两种:按小时采样,一天24条;按半小时采样,一天48条。采样粒度决定了你能预测的最小时间单位,也决定了序列的长度规模。

拿到数据后先别急着建模,我会先看一眼时间范围和缺失情况。虚拟机实例个数来自云平台接口或监控系统,通常存在两类脏数据:一是某几小时数据没采到,出现NaN;二是业务高峰期偶发超大值,比如自动扩容瞬间从几十跳到几百,下一小时又回落。这两种情况如果直接喂给LSTM,会让模型学到很离谱的“突刺记忆”。

常见做法是:缺失值用前向填充兜底,连续缺失超过6小时再考虑线性插值;异常尖峰用滚动中位数加阈值识别,超过3倍滚动标准差就做截断处理,而不是直接删掉——因为突刺本身也有业务含义,完全删除会导致序列不连续。

提示:处理时间序列的缺失值时尽量不要用全局均值填充,那会抹掉周期性,模型会学出一个每天24点准时误报的坏习惯。

2.2 滑动窗口构造样本的核心逻辑

LSTM监督训练需要把原始序列切成“x看过去,y看未来”的样本对。这个包的核心代码一般在preprocess.py里,思路是用一个固定长度的窗口在序列上滑动。窗口长度是历史步数,预测目标可以是下一步的个数,也可以是未来24步的曲线。

我一般会做两步切法:第一步,生成“一步预测”样本用于训练模型;第二步,验证时用滚动方式看多步效果。以小时粒度为例,窗口长度选24,含义是让模型看到过去整整一天的变化规律,再去预测下一小时。如果粒度是半小时,窗口长度选48,同样是看一天。

下面这段是构造滑动窗口的参考实现,逻辑上可以直接套用:

import pandas as pd import numpy as np def make_sequences(data, seq_len=24, pred_len=1): """ 将一维时间序列切分为 (X, y) 监督样本 data: 排序后的 vm_count 数值序列 seq_len: 历史窗口长度,即用过去多少个时刻预测未来 pred_len: 预测未来几个时刻 """ X, y = [], [] for i in range(len(data) - seq_len - pred_len + 1): X.append(data[i : i + seq_len]) y.append(data[i + seq_len : i + seq_len + pred_len]) return np.array(X), np.array(y) # 读取原始数据 df = pd.read_csv("data/raw_vm_count.csv", parse_dates=["time"]) values = df["vm_count"].values.astype(float) X, y = make_sequences(values, seq_len=24, pred_len=1) print("样本形状:", X.shape, y.shape) # 输出示例: 样本形状: (训练样本数, 24, 1) 或 (样本数, 24)

这段代码的关键在于seq_len和pred_len两个参数:seq_len=24表示用过去24小时预测未来,pred_len=1表示只预测下一小时。构造完成后,X的形状是(样本数, 24),后面还需要reshape成LSTM要求的(样本数, 24, 1),第三维是特征通道数。如果资源包代码里还有额外特征,比如星期几、是否节假日,这个第三维会大于1,但在这个包的基础版本里通常只有数量本身。

注意:切片循环结束后,序列末尾会有seq_len + pred_len - 1个点无法生成样本,这是正常现象,不要因此去填充数据。

2.3 归一化选MinMax还是Standard

LSTM对输入尺度极其敏感,不归一化直接训练会出现梯度爆炸或收敛极慢。对于虚拟机实例个数这种数据,波动范围往往在0到几百之间,而且没有太多长尾,我一般优先选MinMaxScaler,把数据压缩到[0, 1]区间。StandardScaler适合大部分分布接近正态的数据,但对尖峰数据不太友好——一旦序列尾部有个超大值,标准化后的正常点位会全部挤在一团。

归一化要注意一个容易被忽略的细节:统计量必须只用训练集计算。如果把全量数据的min和max拿来用,等于让训练集“偷看”了未来,验证集和测试集的分布信息被提前泄漏,指标会虚高。这也是包里preprocess.py里最常见的隐患。

from sklearn.preprocessing import MinMaxScaler # 必须先切分,再归一化,避免信息泄漏 train_size = int(len(values) * 0.8) val_size = int(len(values) * 0.1) train_raw = values[:train_size] val_raw = values[train_size:train_size + val_size] test_raw = values[train_size + val_size:] scaler = MinMaxScaler(feature_range=(0, 1)) # 用训练集统计量进行 fit,再对验证集和测试集只做 transform train_norm = scaler.fit_transform(train_raw.reshape(-1, 1)).flatten() val_norm = scaler.transform(val_raw.reshape(-1, 1)).flatten() test_norm = scaler.transform(test_raw.reshape(-1, 1)).flatten()

另外要区分训练集和验证集是否也参与窗口构造。这里切分发生在窗口构造之前,保证验证集样本不会包含训练集后面点的信息。时序数据切分不需要打乱顺序,因为它本质上就是时间先后关系;随机打乱只会破坏序列结构,让模型学到错误的“记忆”。

3. 模型搭建与训练:LSTM层数、隐藏单元与早停之间的平衡

3.1 网络结构不是越深越好

虚拟机实例个数这种数据,单变量输入,周期性明显,深度不需要太大。常见的两套配置:单层LSTM配64个隐藏单元;或者两层LSTM,第一层128、第二层64。单层模型训练快、不易过拟合,适合数据量只有几百上千条的情况;双层模型能捕获更复杂的周期组合,但需要更多数据支撑,否则验证集Loss容易反弹。

我一般会先跑单层64单元作为基准,如果验证集MAE明显偏高再升级到双层。这个包的模型文件核心结构大致是:

import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=1, output_size=1, dropout=0.2): super().__init__() self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=dropout if num_layers > 1 else 0.0 ) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): # x: (batch, seq_len, input_size) out, _ = self.lstm(x) # 取最后一个时间步的输出做预测 out = out[:, -1, :] return self.fc(out)

batch_first=True让输入形状变成(batch, seq_len, input_size),这是PyTorch里更直观的写法,不容易搞混维度。output_size=1对应一步预测的输出。dropout只在层数大于1时生效,单层LSTM加dropout反而可能破坏记忆通道。

3.2 损失函数与优化器的选择

训练回归任务,最常用的损失函数是MSE(均方误差)和MAE(平均绝对误差)。MSE对大误差惩罚更重,会让模型更谨慎地避开极端值,但如果数据里有少量异常尖峰,MSE会被这些尖峰带偏。MAE对异常值更稳健,但梯度在误差较小时恒定,收敛速度略慢。折中方案是用HuberLoss,它结合了两者优势,在误差较小时按平方增长,超过阈值后按线性增长。

优化器直接用Adam,学习率0.001起步,配合ReduceLROnPlateau做动态衰减。如果训练到后期损失一直在0.02左右抖动,那就是学习率大了,把学习率降到0.0003继续跑几轮。

import torch.optim as optim from torch.optim.lr_scheduler import ReduceLROnPlateau model = LSTMPredictor(input_size=1, hidden_size=64, num_layers=1, output_size=1) optimizer = optim.Adam(model.parameters(), lr=0.001) criterion = nn.HuberLoss(delta=1.0) scheduler = ReduceLROnPlateau(optimizer, mode="min", factor=0.5, patience=5)

这里delta=1.0表示误差超过1.0后从平方损失切换为线性损失。在归一化后的数据里,误差一般小于1,这个阈值能覆盖大多数情况。如果数据方差很小,可以把delta调小到0.5。

3.3 训练流程与早停策略

循环训练时,batch_size选32或64都可以,小批量能让训练更稳定,但太小的batch(比如8)会导致损失震荡。训练轮次设一个比较大的值比如200,配合早停机制:验证集损失连续15轮不下降就停止,然后加载历史最优的模型参数。这相当于给训练过程留了“后悔药”,不用担心最后一轮恰好过拟合。

best_val_loss = float("inf") patience = 15 no_improve = 0 for epoch in range(200): model.train() train_loss = 0 for x_batch, y_batch in train_loader: optimizer.zero_grad() pred = model(x_batch) loss = criterion(pred, y_batch) loss.backward() optimizer.step() train_loss += loss.item() # 验证 model.eval() val_loss = 0 with torch.no_grad(): for x_val, y_val in val_loader: pred = model(x_val) val_loss += criterion(pred, y_val).item() if val_loss < best_val_loss: best_val_loss = val_loss torch.save(model.state_dict(), "models/lstm_vm.pt") no_improve = 0 else: no_improve += 1 if no_improve >= patience: print(f"early stop at epoch {epoch}") break scheduler.step(val_loss)

训练完成后,模型文件保存在models/lstm_vm.pt,后续推理环节直接加载这个最优权重。这里train_loader和val_loader用的是PyTorch的DataLoader,注意不要在加载时对窗口内数据进行随机打乱,时间顺序的语义必须保持。

4. 推理与容量预警:从模型到调度系统的落地路径

4.1 加载模型并做真实推理

模型训练好之后,真正业务场景里需要做的是:拿到当前时刻前seq_len个实际观测值,预测未来一个或多个时刻的实例个数。推理代码的要点和训练不同——推理时没有验证集Loss,更关键的是归一化和反归一化的对称性。

import torch import numpy as np def predict_next(model, recent_values, scaler, seq_len=24): """ recent_values: 最近 seq_len 个时刻的原始 vm_count 数值,需要严格按时间正序 """ model.eval() # 归一化最近窗口 recent_norm = scaler.transform(np.array(recent_values).reshape(-1, 1)).flatten() # 构造输入形状: (1, seq_len, 1) x = torch.tensor(recent_norm, dtype=torch.float32).view(1, seq_len, 1) with torch.no_grad(): pred_norm = model(x).item() # 反归一化得到真实数量 pred_raw = scaler.inverse_transform([[pred_norm]])[0][0] return round(pred_raw)

这里有个常见的坑:scaler.transform的输入必须是二维数组,直接传一维会报错;inverse_transform同样如此。round的目的是让预测值回到合法的整数范围,因为虚拟机实例个数不可能是1.7台。不过如果要做更精细的容量规划,保留一位小数也没问题。

4.2 评估指标与误差阈值

模型预测得准不准,不在训练Loss,而在反归一化后的绝对误差。我在这个项目里会看三个指标:MAE用于日常误差感知,RMSE用于放大偶发性大误差的影响,MAPE用于衡量相对偏差。以一个规模在100~300台之间波动的集群为例,MAE为4.5意味着平均偏差约4.5台实例,对于提前一天做资源准备来说完全够用;如果MAE超过了15台,那预测基本没有指导价值,要回去检查窗口长度或数据清洗环节。

评估代码可以直接对比验证集预测和真实值:

from sklearn.metrics import mean_absolute_error, mean_squared_error mae = mean_absolute_error(y_true, y_pred) rmse = mean_squared_error(y_true, y_pred, squared=False) mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100 print(f"MAE: {mae:.2f}") print(f"RMSE: {rmse:.2f}") print(f"MAPE: {mape:.2f}%")

当MAE占均值比例低于5%时,说明模型已经能抓住主要规律。如果MAPE异常高,大概率是深夜低值时段(比如0点数量只有个位数),绝对值很小的偏差被放大了。这种情况不用过度紧张,可以看分时段的MAE来决定模型是否合格。

4.3 接入告警系统

模型产出的预测值本身不是终点,它要变成一个可执行动作。常见做法是让推理脚本每小时跑一次,算出来来小时预测值,然后和容量阈值比较。比如某公司定的规则是:预测值超过集群容量上限的85%时触发预警,连续三次预测超过90%就发扩容工单。这相当于给容量水位安装一个前视镜头,而不是等系统真实用满后才报警。

这个资源包里如果带了调度接入的示例代码,一般也是薄薄一层:读模型、拉最新数据、预测、判断阈值、推送消息。整个流程跑通后,预测环节才真正融合进运维链路。

5. 避坑指南:六个常见错误与排查方法

5.1 预测结果全是同一个数

现象:模型训练完,预测出来每个时刻的值都差不多,曲线几乎是平的,完全看不出周期。

原因:目标变量做了MinMax归一化后,序列被压缩到了[0, 1]的极小区间内波动。如果原始数值分布在极小范围,模型拟合出的“最优解”是输出平均值附近的一个常数。

解决:先检查原始序列的方差。如果方差偏小,可以放宽归一化区间,或改用StandardScaler重新做;另一个更有效的做法是增加窗口长度,让模型看到更多周期结构。

5.2 验证集误差远大于训练集

现象:训练集MAE只有2,验证集MAE达到20,误差曲线明显分叉。

原因:大概率是切分时没有按时间顺序,或者把数据随机打乱后再切分。时序预测必须按顺序切分,否则验证集里的时间点混入了训练集之前的数据。

解决:严格使用train → val → test的顺序切分,并用训练集统计量归一化验证集和测试集。如果包里的代码用了随机分割函数,直接改成按索引切分即可。

5.3 一跑训练显存就溢出

现象:笔记本电脑训练,batch_size设成128,seq_len取144,还没跑完一个epoch就OOM。

原因:LSTM在长序列上需要展开很多时间步,模型内部缓存了很多中间变量,序列越长显存占用越大。虚拟机实例个数预测通常不需要144小时的历史窗口,24或48完全足够。

解决:把seq_len降到24再试。如果确实需要更长历史,可以减小batch_size到16或32,或者在LSTM层前加一个Conv1D做降采样,把序列先压缩再进LSTM。

5.4 预测值出现负数

现象:反归一化后,某些时刻的预测值变成了-8这种负数,看着明显不合理。

原因:模型输出层是线性层,训练数据里没有负样本,但模型外推时可能在某些组合下产生负输出;尤其Sequence归一化到[0, 1]区间时、输入值接近0而LSTM对短记忆不敏感时,输出可能低于0。

解决:代码里对pred_raw做一次max(0, pred_raw)处理。更优雅的方案是在训练时把输出层激活函数改为ReLU,强制输出非负,代价是会微幅增加训练难度。两者可以同时使用。

5.5 训练正常但推理时shape总报错

现象:训练完保存模型,写推理脚本一执行就报Expected 3D input之类的维度错误。

原因:训练时输入是(batch, seq_len, 1),而推理时只传入了一个样本,如果直接把一维数组喂给模型,形状变成了(seq_len,),模型无法识别。

解决:推理前先重塑成(1, seq_len, 1)。调试方法很简单:打印x.shape确认三维。这类问题几乎每个写LSTM推理的人都会遇到一次,不是模型问题,是PyTorch维度约定问题。

5.6 安装了依赖却发现模型文件和代码版本对不上

现象:加载训练好的.pt文件时提示找不到某个key或维度和模型定义不匹配。

原因:训练时的模型结构和推理脚本里的模型结构不一致。最常见的是修改了隐藏单元数,比如训练时用128,推理时却用了64。

解决:模型定义必须和保存时一致。如果改了网络结构,旧模型权重就不能用,只能重新训练。建议在推理脚本里把模型定义完整复制过来,不要“凭印象”重写,这是最稳妥的方式。

6. 从单步到滚动多步:一个更稳的预测技巧

只预测下一步的价值有限,实际容量规划通常需要看未来6小时、12小时甚至24小时的趋势。常见做法有两种:第一种是训练时把pred_len直接设为目标步长,让模型一次输出多条未来曲线;第二种是在单步模型上做滚动预测,把上一步的预测值当作窗口的新输入,重复调用。

第二种方法的风险是误差累积:第一步预测偏差0.5,第二步在这个偏差基础上继续预测,偏差可能放大。我一般会混合使用:预测未来24小时时,先用事件预测模式算出未来24个点,再把每个预测点的输出当作输入继续推演,同时每天早上或整点用真实监测值来重置起点,避免误差无限累积。

滚动推理代码:

def rolling_predict(model, recent_values, scaler, steps=24): """ 基于最近窗口滚动预测未来 steps 个时刻 """ model.eval() seq = recent_values[-seq_len:].copy() preds = [] for _ in range(steps): seq_norm = scaler.transform(np.array(seq).reshape(-1, 1)).flatten() x = torch.tensor(seq_norm, dtype=torch.float32).view(1, seq_len, 1) with torch.no_grad(): next_norm = model(x).item() next_raw = scaler.inverse_transform([[next_norm]])[0][0] preds.append(round(next_raw)) # 滚动窗口:移除最早的一个点,加入最新的预测点 seq.append(next_raw) seq.pop(0) return preds

这里有个性能优化点:如果预测步数较长且调用频繁,每次都用inverse_transform其实没必要,可以全部在归一化空间累加,最后统一反归一化,能省掉几十次转换开销。seq.append(next_raw)时注意recent_values最后一个点要保留,避免窗口起点错误。

从那以后我每次上线这种时序预测模型,都强制自己走一遍“清洗→窗口切分→训练→滚动推演→反归一化取整→与真实值对比”的完整链路,确认每一步的形状和参数都对得上,才敢把预测值接进告警系统。时序模型像是个黑匣子,但黑匣子外面的数据管道才是真正决定成败的部分,希望这里记下的这些细节能帮到你少踩几个坑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 11:35:53

电力数据采集核心板选型实战指南:精度、EMC与可靠性三维决策

1. 项目概述&#xff1a;为什么一块“不起眼”的核心板&#xff0c;能决定整套电力数据采集系统的生死&#xff1f;在某高校智能电网实验室做设备联调时&#xff0c;我亲眼见过一套刚交付的配电房监测系统&#xff0c;在现场连续运行72小时后突然失联——不是通信中断&#xff…

作者头像 李华
网站建设 2026/10/11 11:34:47

Java性能优化底层原则:量化、定位、优先级与验证闭环

Java 程序员做性能优化&#xff0c;最常犯的错不是技术不够&#xff0c;而是上来就动手改代码。我见过太多团队&#xff0c;花了一周把某个"看起来很慢"的接口改成了异步&#xff0c;结果压测一打&#xff0c;慢的还是慢&#xff0c;甚至更慢了——因为真正的问题出在…

作者头像 李华
网站建设 2026/10/11 11:33:58

微信小程序名片管理系统实战:数据库设计与高频踩坑排查

简介&#xff1a;这是一份微信小程序名片管理系统的完整源码与数据库工程&#xff0c;定位为毕业设计模板与业务型小程序开发参考&#xff0c;适合学生、个人开发者以及需要快速搭建员工名片库的企业技术团队。项目覆盖普通用户与管理员两类角色&#xff0c;包含员工名片、联系…

作者头像 李华
网站建设 2026/10/11 11:32:52

YOLOv8摔倒检测全链路实战:从标注规范到RK3588边缘部署

简介&#xff1a;本资源是一套基于YOLOv8实现的摔倒检测完整代码工程&#xff0c;面向具备Python与PyTorch基础的计算机视觉开发者及智能安防、老年监护、运动分析等领域的算法实践者&#xff0c;解决真实场景中人物摔倒事件的自动识别与实时预警问题。压缩包为ZIP格式&#xf…

作者头像 李华
网站建设 2026/10/11 11:30:22

内核报错 kernel paging request 排查:分清驱动还是内存故障

服务器半夜报警&#xff0c;拉到控制台一看&#xff0c;屏幕上滚动着一行刺眼的BUG: unable to handle kernel paging request at ffff8800...。这行字对不少运维人来说既熟悉又头疼&#xff1a;熟悉是因为它一出现通常意味着系统已经离宕机不远了&#xff1b;头疼是因为紧接着…

作者头像 李华