【时序数据预测】序列数据预处理与特征工程

下面给出一个完整的序列数据预处理与特征工程指南,并分别提供 PyTorchTensorFlow 两个端到端的实战案例(带详细代码)演示如何从原始时间序列中进行数据清洗归一化时间窗口划分以及特征工程(例如添加时间特征、移动平均/差分等常见技巧),最终提升序列模型(RNN/LSTM/GRU/Transformer 等)的预测性能与泛化能力。


一、序列数据预处理与特征工程概述

在机器学习和深度学习的实践中,数据质量往往比模型设计更能决定项目成败。特别是对于时间序列/序列数据,预处理和特征工程更显重要。本节简要总结常见的方法和思路:

  1. 数据清洗(Cleaning)

    • 缺失值处理:包括插值(如前向填充、线性插值等),或直接剔除缺失行。
    • 异常值检测与修正:可用统计方法(箱线图、Z-score)或复杂检测算法(Isolation Forest)去除或修正异常点。
    • 重复数据处理:对于日志类数据可能出现重复记录,需要去重并保证时间戳唯一。
  2. 时间戳与顺序处理

    • 确保时间戳正确且连续,如果有不连续的日期或采样频率不一致,需要做时间对齐重采样
    • 对毫秒级或分钟级别数据,可进行降采样(如从每分钟汇总到每小时)。
  3. 归一化/标准化

    • 常见方法:MinMaxScaler(区间缩放到 [0,1])或 StandardScaler(均值 0,方差 1)。
    • 不同特征(如价格、温度、交易量)可能数量级差距大,归一化有助于加快训练并稳定模型。
    • 注意:一定只对训练集拟合缩放器,再应用到验证集/测试集,避免数据泄露。
  4. 时间窗口划分(Sliding Window)

    • 对于预测任务,多数 RNN/LSTM 模型需要将连续的时间片段作为样本输入。
    • input_lengthforecast_length 的设定(如用过去 7 天预测未来 1 天,或过去 30 天预测未来 7 天)。
    • 窗口可以是滑动的,每次移动 1 个时间步生成新的样本,或移动多步。
  5. 特征工程

    1. 时间特征
      • 提取星期几、月份、一天中的小时、是否节假日等。
      • sin/cos 将周期性特征(如一天 24 小时或一年 365 天)编码成连续空间,以避免边界突变。
    2. 统计特征
      • 移动平均、移动标准差、移动最大/最小,捕捉历史窗口的统计信息。
      • 一阶差分、二阶差分:捕捉增长率或变化趋势。
    3. 外部特征
      • 天气、汇率、宏观经济指标、节假日信息……只要对预测目标有影响都可考虑纳入。
    4. 交互特征
      • 对多个特征做乘积、比率或其他函数组合,帮助模型捕捉交互关系。
  6. 数据集划分策略

    • 通常保留最近一段时间做测试,避免未来数据泄露到过去。
    • 还可以划出一段时间做验证集,用于调参和早停。

以上方法在国际上许多时序预测竞赛(如 Kaggle、M4、M5 比赛)和商业落地场景(能源电力、金融交易、IoT 设备等)都被广泛采用;许多优秀书籍(如《Forecasting: Principles and Practice》、《Deep Learning for Time Series Forecasting》)中也反复强调这些关键预处理与特征工程手段。


二、PyTorch 案例:股票价格多特征序列预测

这里以股票价格预测作为示例,模拟一个现实场景:

  • 我们有某只股票一段时间的 收盘价(Close)成交量(Volume) 和一些衍生特征;
  • 目标是用过去若干天(例如 20 天)的历史数据来预测第 21 天的收盘价
  • 整个流程包括:数据加载与清洗、特征工程(移动平均线、差分等)、归一化、滑动窗口构建样本、构建 LSTM 模型、训练与评估。

说明:为了便于在任何环境下都能演示,我们示例中将使用一个模拟的股票数据。若需要真实数据,可参考 Yahoo Finance 或其他 API 获取 CSV 文件,然后进行相同操作。

2.1 数据准备与清洗

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import matplotlib.pyplot as plt
import random

# ---------- 1) 生成模拟的股票数据 ---------- #
def generate_fake_stock_data(num_days=500, seed=42):
    """
    生成模拟股价数据:
      - Open, High, Low, Close, Volume
      - 包含一些随机趋势和波动
    """
    np.random.seed(seed)
    
    dates = pd.date_range(start="2021-01-01", periods=num_days, freq='D')
    
    # 模拟收盘价: 从100开始随机震荡
    close = 100 + np.cumsum(np.random.randn(num_days)) * 2
    
    # 模拟最高价/最低价/开盘价
    high = close + np.random.rand(num_days) * 2
    low  = close - np.random.rand(num_days) * 2
    open_ = close + np.random.randn(num_days) * 0.5
    
    # 模拟成交量
    volume = np.random.randint(100000, 500000, num_days)
    
    df = pd.DataFrame({
        'Date': dates,
        'Open': open_,
        'High': high,
        'Low': low,
        'Close': close,
        'Volume': volume
    })
    
    return df

df = generate_fake_stock_data(num_days=500)
df.set_index('Date', inplace=True)
df.head()
  • 此时 df 形如:

    DateOpenHighLowCloseVolume
    2021-01-01100.7102.199.8101.0300000
    2021-01-02100.1101.999.5100.8250000
    ..................

2.1.1 异常值、缺失值处理

  • 如果有缺失值 NaN,可用 df.fillna(method='ffill') 或其他插值方式。
  • 如果有明显异常值(比如价格为负或极端大),也需剔除或修正。

本示例假设模拟数据质量较好,无缺失和极端异常,可省略清洗。

# 如果有缺失值可做简单的前向填充
df = df.fillna(method='ffill')

2.2 特征工程

2.2.1 时间特征

若想添加星期几等,可从时间索引中提取。股市可能对星期一与星期五或不同月份表现不同。

df['DayOfWeek'] = df.index.dayofweek  # 0=Monday
df['Month'] = df.index.month

# 如果想做 one-hot,可以用 pd.get_dummies 或直接留数值
# 也可以做 sin/cos 编码,但这里先简化为整数特征

2.2.2 技术指标(如移动平均/差分)

常用股票时间序列特征:

  • MA5:5 日移动平均
  • MA20:20 日移动平均
  • Price Diff:今日收盘价 - 昨日收盘价
  • Volume Diff:今日成交量 - 昨日成交量
df['MA5'] = df['Close'].rolling(window=5).mean()
df['MA20'] = df['Close'].rolling(window=20).mean()

# 一阶差分
df['PriceDiff'] = df['Close'].diff()
df['VolDiff'] = df['Volume'].diff()

# 由于rolling和diff会产生NaN,需要去掉前面几行
df = df.dropna()

df.head(10)

2.3 归一化

选择需要的特征列并进行归一化。这里我们准备用以下特征来预测第 21 天的收盘价:

  • Close, Volume, DayOfWeek, Month, MA5, MA20, PriceDiff, VolDiff
from sklearn.preprocessing import MinMaxScaler

feature_cols = ['Close','Volume','DayOfWeek','Month','MA5','MA20','PriceDiff','VolDiff']
df_features = df[feature_cols].copy()
print("Original feature shape:", df_features.shape)

scaler = MinMaxScaler()
data_scaled = scaler.fit_transform(df_features.values)

我们还需要保留一个目标列(这里是下一天的 Close),做多步可以改成下一天或未来多天的收盘价。为了简单,先做单步预测。

2.4 训练集、测试集划分

假设我们用前 400 条数据做训练,后面做测试(模拟真实情况:较新的数据留作测试)。

train_size = 400
train_data = data_scaled[:train_size]
test_data  = data_scaled[train_size:]

2.5 时间窗口划分

设定 sequence_length = 20,用过去 20 天的特征预测第 21 天的 收盘价(而非当前天的 Close)。要注意目标值要从 df['Close']df_features['Close'] 的位置获取。如果我们要预测的值也已经归一化了,就需要在同一个 scaler 下找到对应的列。

此处可用自定义 Dataset;也可先做 NumPy 处理,再封装成 PyTorch Dataset

class StockDataset(Dataset):
    def __init__(self, data_array, seq_len=20, target_col_index=0):
        """
        :param data_array: 形状 (N, num_features) 的归一化后数据
        :param seq_len: 过去多少天作为输入序列
        :param target_col_index: 目标在第几列 (这里Close是feature_cols[0])
        """
        self.data = data_array
        self.seq_len = seq_len
        self.target_col = target_col_index
    
    def __len__(self):
        return len(self.data) - self.seq_len
    
    def __getitem__(self, idx):
        # input seq
        x = self.data[idx: idx+self.seq_len, :]  # (seq_len, num_features)
        
        # target: 第 seq_len 的那一天(=第 idx+seq_len行)的 Close
        y = self.data[idx+self.seq_len, self.target_col]  # 标量
        return torch.tensor(x, dtype=torch.float32), torch.tensor(y, dtype=torch.float32)

sequence_length = 20
target_col_idx = feature_cols.index('Close')  # 0

train_dataset = StockDataset(train_data, seq_len=sequence_length, target_col_index=target_col_idx)
test_dataset  = StockDataset(test_data,  seq_len=sequence_length, target_col_index=target_col_idx)

print("Train samples:", len(train_dataset))
print("Test  samples:", len(test_dataset))

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader  = DataLoader(test_dataset, batch_size=32, shuffle=False)

2.6 模型构建与训练(PyTorch)

2.6.1 定义 LSTM 回归模型

class LSTMRegressor(nn.Module):
    def __init__(self, input_dim, hidden_dim=32, num_layers=1):
        super(LSTMRegressor, self).__init__()
        self.lstm = nn.LSTM(
            input_size=input_dim, 
            hidden_size=hidden_dim,
            num_layers=num_layers,
            batch_first=True
        )
        self.fc = nn.Linear(hidden_dim, 1)  # 输出单个值(预测Close)
    
    def forward(self, x):
        # x: (batch, seq_len, input_dim)
        out, (h_n, c_n) = self.lstm(x)
        # 取最后时间步
        last_out = out[:, -1, :]  # (batch, hidden_dim)
        y_pred = self.fc(last_out)  # (batch, 1)
        return y_pred

2.6.2 训练循环

model = LSTMRegressor(input_dim=len(feature_cols), hidden_dim=32)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

num_epochs = 10
train_losses = []

for epoch in range(num_epochs):
    model.train()
    epoch_loss = 0
    
    for batch_x, batch_y in train_loader:
        pred = model(batch_x).squeeze(-1)  # (batch,)
        loss = criterion(pred, batch_y)
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        epoch_loss += loss.item()
    
    avg_loss = epoch_loss / len(train_loader)
    train_losses.append(avg_loss)
    
    if (epoch+1) % 2 == 0:
        print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}")

2.6.3 测试集评估

我们可以计算在测试集上的 MSE、MAE,并对比真实值与预测值曲线。

model.eval()
preds = []
targets = []
with torch.no_grad():
    for batch_x, batch_y in test_loader:
        out = model(batch_x).squeeze(-1)
        preds.append(out.numpy())
        targets.append(batch_y.numpy())

import numpy as np
preds = np.concatenate(preds)
targets = np.concatenate(targets)

mse_test = np.mean((preds - targets)**2)
mae_test = np.mean(np.abs(preds - targets))

print(f"Test MSE: {mse_test:.4f}, MAE: {mae_test:.4f}")

2.6.4 反归一化并可视化

目前 predstargets 都是在 [0,1] 归一化尺度上。如果想看真实股价数值,需要反归一化

# 做反归一化时,需要把 (pred, target) 放回到 data_array 的同一个 feature 矩阵中
# 由于 length 不同,可简单用 for 循环/拼接 approach

def inverse_transform(value_list, scaler, feature_index=0):
    """
    :param value_list: shape=(N,) 归一化后的值
    :param scaler: 之前fit的MinMaxScaler
    :param feature_index: 对应列索引
    :return: 反归一化后的数组 (N,)
    """
    # 构造空的 (N, num_features),只填充feature_index
    dummy = np.zeros((len(value_list), len(feature_cols)))
    dummy[:, feature_index] = value_list
    inv = scaler.inverse_transform(dummy)
    return inv[:, feature_index]

preds_real = inverse_transform(preds, scaler, feature_index=0)
targets_real = inverse_transform(targets, scaler, feature_index=0)

plt.figure(figsize=(8,4))
plt.plot(targets_real, label='True')
plt.plot(preds_real, label='Predicted')
plt.title("Test Prediction (Close Price)")
plt.legend()
plt.show()

若特征工程、窗口大小合理,并且数据本身模式明显,你应该看到预测曲线与真实曲线相对接近;否则可继续调参或改进特征。

扩展:可尝试增加多层 LSTM、加入 Dropout、或改用 GRU/Transformer 等结构,并在特征工程上加更多技术指标和外部因素(宏观经济/行业指数等)。


三、TensorFlow 案例:多维传感器时间序列预测

本示例展示如何在 TensorFlow (Keras) 框架下,对多维度传感器数据进行预处理并做时间序列预测。我们将模拟一个 IoT 传感器场景:

  • 传感器记录温度、湿度、振动等信息;
  • 我们想用过去 24 小时(24 个时间步)来预测下一小时的温度。

3.1 数据模拟与清洗

import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt

def generate_sensor_data(num_points=2000, seed=42):
    """
    模拟多传感器数据:
      - temperature (25 +/- 5, 带一些日周期波动)
      - humidity (0~100%)
      - vibration (机器振动幅度)
    目标是预测下一时刻 temperature
    """
    np.random.seed(seed)
    time = np.arange(num_points)
    
    # 模拟温度:带周期 + 随机噪声
    temperature = 25 + 5*np.sin(time * 2*np.pi/144) + np.random.randn(num_points)*0.5
    
    # 模拟湿度:略带随机波动
    humidity = 50 + 10*np.random.randn(num_points)
    humidity = np.clip(humidity, 0, 100)
    
    # 模拟振动:范围 0~10, 受一些随机峰值影响
    vibration = np.abs(5 + np.random.randn(num_points)*2)
    
    df = pd.DataFrame({
        'temperature': temperature,
        'humidity': humidity,
        'vibration': vibration
    })
    
    return df

df_sensor = generate_sensor_data(num_points=2000)
df_sensor.head()

3.1.1 数据清洗

和之前类似,若有缺失值或异常值需要处理,此处略。

3.2 特征工程

假设要对温度做预测,可以额外构造一些统计特征或时间特征:

  • 日周期编码:sin(2π * hour_of_day/24), cos(2π * hour_of_day/24)
  • 传感器值的移动平均/差分。

为演示,这里只做简单周期编码(假设 1个点=1小时):

df_sensor['time_index'] = np.arange(len(df_sensor))
df_sensor['sin_time'] = np.sin(2 * np.pi * df_sensor['time_index'] / 24)
df_sensor['cos_time'] = np.cos(2 * np.pi * df_sensor['time_index'] / 24)

3.3 归一化与窗口划分

选取特征列:['temperature','humidity','vibration','sin_time','cos_time']
目标:下一时刻的 temperature.

feature_cols = ['temperature','humidity','vibration','sin_time','cos_time']
scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(df_sensor[feature_cols])

print("Scaled shape:", df_scaled.shape)  # (2000,5)

3.3.1 划分训练、验证、测试集

train_size = 1400
val_size = 300
test_size = 300

train_data = df_scaled[:train_size]
val_data   = df_scaled[train_size:train_size+val_size]
test_data  = df_scaled[train_size+val_size:]

3.3.2 构建滑动窗口 (tf.data)

设定 past_history=24,用过去 24 个小时的数据预测下 1 小时的温度。

def make_window_dataset(data, past_len=24, target_index=0):
    X = []
    Y = []
    for i in range(len(data) - past_len):
        x_seq = data[i:i+past_len]
        # 目标:第 i+past_len 时刻的 temperature
        y_val = data[i+past_len, target_index]
        X.append(x_seq)
        Y.append(y_val)
    return np.array(X), np.array(Y)

past_history = 24

train_X, train_Y = make_window_dataset(train_data, past_history, target_index=0)
val_X,   val_Y   = make_window_dataset(val_data,   past_history, target_index=0)
test_X,  test_Y  = make_window_dataset(test_data,  past_history, target_index=0)

print("Train X:", train_X.shape, "Train Y:", train_Y.shape)
print("Val   X:", val_X.shape,   "Val   Y:", val_Y.shape)
print("Test  X:", test_X.shape,  "Test  Y:", test_Y.shape)

然后转成 tf.data.Dataset 用于批量训练。

batch_size = 32

train_ds = tf.data.Dataset.from_tensor_slices((train_X, train_Y))
train_ds = train_ds.shuffle(1000).batch(batch_size).prefetch(tf.data.AUTOTUNE)

val_ds = tf.data.Dataset.from_tensor_slices((val_X, val_Y))
val_ds = val_ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)

test_ds = tf.data.Dataset.from_tensor_slices((test_X, test_Y))
test_ds = test_ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)

3.4 模型构建(TensorFlow)

3.4.1 LSTM 模型

from tensorflow.keras import layers, models

model = models.Sequential([
    layers.Input(shape=(past_history, len(feature_cols))),
    layers.LSTM(64, activation='tanh'),
    layers.Dense(1)  # 输出标量 -> 预测下一时刻temperature
])

model.compile(
    optimizer='adam',
    loss='mse',
    metrics=['mae']
)

model.summary()

3.4.2 训练与可视化

epochs = 10
history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=epochs
)

plt.figure(figsize=(8,4))
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Val Loss')
plt.title("Training/Validation Loss")
plt.legend()
plt.show()

3.4.3 测试集评估

test_loss, test_mae = model.evaluate(test_ds, verbose=0)
print(f"Test MSE: {test_loss:.4f}, Test MAE: {test_mae:.4f}")

3.4.4 反归一化并可视化预测

model.predict(test_X) 获得归一化的温度值,再使用 scaler.inverse_transform 复原。这里同样需要将预测值放入一个dummy array,只填对应列。

y_pred_norm = model.predict(test_X).flatten()  # shape=(N,)
y_true_norm = test_Y

def inverse_temp(values):
    dummy = np.zeros((len(values), len(feature_cols)))
    dummy[:, 0] = values  # 0列是temperature
    inv = scaler.inverse_transform(dummy)
    return inv[:, 0]

y_pred = inverse_temp(y_pred_norm)
y_true = inverse_temp(y_true_norm)

plt.figure(figsize=(8,4))
plt.plot(y_true, label='True Temp')
plt.plot(y_pred, label='Pred Temp')
plt.title("Sensor Temperature Prediction")
plt.legend()
plt.show()

如果特征工程、模型结构等较好,两个曲线应具备一定拟合度;若偏差较大,需要继续调参或引入更多特征(如长序列、天气影响、故障事件等)。


四、总结与实践要点

  1. 数据清洗

    • 检查并处理缺失值、异常值、重复记录,保证数据完整且质量可靠。
  2. 时间戳处理与分段

    • 在序列数据中维持好时间顺序,切忌在时序上出现“未来信息泄露”。
  3. 特征工程

    • 时间特征(weekday, hour, month, sin/cos周期编码)、统计特征(移动平均/标准差/差分/最大最小)、外部特征(天气、事件、经济指标)常能显著提升模型精度。
    • 参考国际优秀案例/书籍时,常会看到对于长期季节性、突发性事件等的处理手段,这些都在真实项目非常关键。
  4. 归一化或标准化

    • 有助于训练稳定;注意仅对训练集拟合 scaler,再应用到验证/测试集。
  5. 时间窗口划分

    • 依据任务需求(预测未来 1 天 or 7 天 or 多步),灵活设定输入窗口与输出窗口;可用自定义 Dataset 或 tf.datawindow API。
  6. 模型选择

    • 本文仅演示 LSTM;在实际中,也可尝试 GRU、1D CNN、或基于注意力机制的 Transformer(如 Informer、Autoformer)等在时序上的最新进展。
  7. 调参与评估

    • 常见指标:MSE, MAE, RMSE, MAPE, SMAPE。
    • 可视化预测曲线、残差分布、未来场景做 Stress Testing。
  8. 部署与维护

    • 序列预测往往需要滚动预测(Rolling Forecast),需周期性更新模型或在在线学习模式下对新数据做增量训练。

参考国际优秀案例和书籍

这些竞赛和文献中都有大量真实落地案例和实战技巧,值得深入研读与对照实践。


五、结语

数据清洗、归一化、时间窗口构建、特征工程等预处理步骤,能够显著提升序列模型对数据模式的捕捉能力,提高预测准确度泛化能力。不管是 PyTorch 还是 TensorFlow,其核心流程都是 (1)获取并清洗数据 → (2)构造特征与归一化 → (3)将序列构造成 (X, Y) 样本 → (4)定义模型并训练 → (5)测试与可视化

希望以上两个端到端示例能帮助你理清在实际项目中如何落地实现序列预测。如需更高的准确率或在国际竞赛取得好名次,还可在此基础上做更深层网络Attention 机制高级特征工程并结合外部数据等进一步强化。祝学习和项目开发顺利!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值