如何降低LSTM时间序列数据集构建过程中的内存占用?
这确实是处理高维时间序列时很头疼的内存瓶颈问题——你的原数据已经是(8392, 41164)的大矩阵,滑动窗口后生成的三维数组吃掉50GB内存完全合理。我来分享几个实用的解决方案,从简单到进阶,帮你把内存占用砍下来:
1. 优先降低数据精度(最快见效)
默认情况下NumPy数组用float64存储,这对很多深度学习任务来说精度过剩。如果你的数据不需要这么高的精度,直接转换成float32甚至float16(TensorFlow/PyTorch都支持),内存占用直接减半或降到原来的1/4:
# 转换原数据精度 X = X.astype('float32') y = y.astype('float32') # 再用你原来的create_dataset函数 Xs, ys = create_dataset(X, y, time_steps=14)
这样处理后,你的数据集内存占用会从50GB降到25GB左右,如果用float16能压到12.5GB,几乎是零成本的优化。
2. 使用生成器/迭代器,避免一次性加载全部数据
你的原代码会把所有滑动窗口数据都存入列表再转成数组,相当于在内存里存了两份大数据(原数据+生成的三维数组)。改用生成器,每次只生成一个batch的数据,内存里只保留当前batch的内容:
自定义生成器(兼容Keras)
import numpy as np def lstm_data_generator(X, y, time_steps, batch_size): num_samples = len(X) - time_steps while True: # 按batch遍历数据 for start_idx in range(0, num_samples, batch_size): end_idx = min(start_idx + batch_size, num_samples) # 提取当前batch的滑动窗口 X_batch = X[start_idx : end_idx + time_steps].reshape( (end_idx - start_idx, time_steps, X.shape[1]) ) y_batch = y[start_idx + time_steps : end_idx + time_steps] yield X_batch, y_batch
训练模型时直接用这个生成器:
batch_size = 32 steps_per_epoch = (len(X) - 14) // batch_size model.fit( lstm_data_generator(X, y, 14, batch_size), steps_per_epoch=steps_per_epoch, epochs=10 )
如果用TensorFlow,更推荐用tf.data.Dataset来构建管道,它还支持并行加载、预取等优化:
import tensorflow as tf def create_tf_dataset(X, y, time_steps, batch_size): dataset = tf.data.Dataset.from_tensor_slices((X, y)) # 滑动窗口转换 dataset = dataset.window(time_steps + 1, shift=1, drop_remainder=True) dataset = dataset.flat_map(lambda x, y: tf.data.Dataset.zip((x.batch(time_steps), y.skip(time_steps).take(1)))) dataset = dataset.batch(batch_size).prefetch(tf.data.AUTOTUNE) return dataset train_dataset = create_tf_dataset(X, y, 14, 32) model.fit(train_dataset, epochs=10)
这种方式下,内存占用只会是一个batch的大小,几十MB到几百MB不等,完全解决内存溢出问题。
3. 用NumPy视图创建滑动窗口,避免数据复制
如果你一定要用数组形式而不是生成器,可以用np.lib.stride_tricks.as_strided创建滑动窗口的视图——它不会复制原数据,只是改变数组的步长信息,内存占用和原数据几乎一样:
def create_dataset_view(X, y, time_steps): num_samples = len(X) - time_steps # 创建滑动窗口视图,无数据复制 Xs = np.lib.stride_tricks.as_strided( X, shape=(num_samples, time_steps, X.shape[1]), strides=(X.strides[0], X.strides[0], X.strides[1]), writeable=False # 设为不可写,防止意外修改原数据 ) ys = y[time_steps:] return Xs, ys
⚠️ 注意:这个方法是“视图”而非复制,所以如果原数据被修改,Xs也会跟着变。建议使用前把原数据设为只读,或者确保不会修改原数据。
4. 特征降维(从根源减少数据量)
你的数据有41164个特征,这可能是内存占用高的核心原因——哪怕每个特征只占4字节(float32),一个样本就要占411644≈160KB,8378个样本就是8378160KB≈1.3GB,再乘以14个时间步就是18GB左右(加上y的话接近20GB,再算上原数据的话确实会到50GB)。
如果这些特征有冗余,用降维方法把特征数砍下来:
- PCA:保留大部分方差,把特征数降到几百甚至几十:
from sklearn.decomposition import PCA # 保留95%的方差,自动计算需要的特征数 pca = PCA(n_components=0.95) X_reduced = pca.fit_transform(X) # 再用降维后的数据创建LSTM数据集 Xs, ys = create_dataset(X_reduced, y, time_steps=14)
- Autoencoder:用深度学习模型学习特征的低维表示,适合非线性的特征冗余。
- 特征选择:用互信息、方差阈值等方法筛选出和y相关的特征,去掉无用特征。
降维不仅能大幅降低内存,还能减少模型的训练时间,降低过拟合风险,一举多得。
内容的提问来源于stack exchange,提问作者BAGUS SATYA MAS

