时间序列:使用滑动窗口处理单变量/多变量数据

之前写过使用 Python yield 实现的滑动窗口,因为用TensorFlow比较多,并且 tf.data API 处理数据更加高效,对于大数据量的情况,选择 API 实现滑动窗口相比原生的Python方法更好。本文介绍了如何使用 tensorflow 的 tf.data API 实现滑动窗口。


代码环境:

Python 3.7.6 
TensorFlow 2.1.0

导入必要的包:

import tensorflow as tf


在时间序列建模问题中,通常需要时间序列片段,并且的多数情况下是多个维度特征的数据。因此,需要对原始的时间序列数据进行划分,实现截取类似图像的窗口数据,作为样本,构造样本数据集,然后喂给神经网络训练。

先用一个简单的例子演示所述问题:


1. batch 实现 单变量滑动窗口

tf.data.batch 方法说明:

batch(batch_size, drop_remainder=False)
  • batch_sizetf.int64 标量,表示单个批次中元素的数量。
  • drop_remainder:(可选)tf.bool 标量,表示在 batch_size 不足批大小的情况下是否删除该批次数据;默认不删除较小的批次。

构造单变量虚拟数据:

range_ds = tf.data.Dataset.range(100000)

batch 实现无重叠,窗口宽度为10的滑动窗口:

# 将数据生成batch_size=10的批数据。其中,drop_remainder 表示
# 在batch_size不足批大小的情况下是否删除该批次数据;默认不删除较小的批次。
batches = range_ds.batch(10, drop_remainder=True)

# 从批次数据中,取出五个批次并打印
for batch in batches.take(5):
    print(batch.numpy())

输出:

[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
[40 41 42 43 44 45 46 47 48 49]

1.1 无重叠采样 有偏移预测

def dense_1_step(batch):
    # 将单变量时间序列数据与预测标签数据匹配
    # 此处将前9个采样值作为输入,偏移一步的后9个采样值作为输出
    return batch[:-1], batch[1:]

# map方法将所有批次数据实现数据与标签的匹配
predict_dense_1_step = batches.map(dense_1_step) 

# 打印三个匹配好的样本
for features, label in predict_dense_1_step.take(3):
    print(features.numpy(), " => ", label.numpy())

输出:

[0 1 2 3 4 5 6 7 8]  =>  [1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18]  =>  [11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28]  =>  [21 22 23 24 25 26 27 28 29]

1.2 无重叠采样 无偏移预测

要预测整个窗口而不是固定的偏移量,,可以将批处理分为两部分:

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
    return (batch[:-5],   # 一个批次内前十个采样点作为输入
            batch[-5:])   # 一个批次内后五个采样点作为标签

predict_5_steps = batches.map(label_next_5_steps)

for features, label in predict_5_steps.take(3):
    print(features.numpy(), " => ", label.numpy())

则输出:

[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]  =>  [25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42 43 44]

1.3 有重叠采样 无偏移预测

如果想让样本包含的采样数据有重叠,可以使用 tf.data.Dataset.zip 实现:

feature_length = 10 # 窗口宽度
label_length = 5 # 预测输出的长度

features = range_ds.batch(feature_length, drop_remainder=True)
# skip() 方法表示取一个批次之后的数据
# labels[:-5] 表示截取该批次的前五个采样数据
labels = range_ds.batch(feature_length).skip(1).map(lambda labels: labels[:-5])

# zip 方法实现将样本数据与样本标签匹配
predict_5_steps = tf.data.Dataset.zip((features, labels))

for features, label in predict_5_steps.take(3):
    print(features.numpy(), " => ", label.numpy())

输出:

[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22 23 24]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32 33 34]

如果将 skip(1) 改为 skip(2) 则输出:

[0 1 2 3 4 5 6 7 8 9]  =>  [20 21 22 23 24]
[10 11 12 13 14 15 16 17 18 19]  =>  [30 31 32 33 34]
[20 21 22 23 24 25 26 27 28 29]  =>  [40 41 42 43 44]

可以看到样本数据与样本标签隔了一个批次。这样做没什么实际意义,只是为了方便理解 skip() 方法。


2. window 实现 单变量滑动窗口

tf.data.window() 方法

window(size, shift=None, stride=1, drop_remainder=False)

参数说明:

  • size:表示拆分后每个窗口包含多少个采样点,即窗口宽度
  • shift:表示滑动窗口中输入元素的跨度,即滑动步长
  • stride:表示采样点之间的跨度;可选参数,默认为 None

为了方便理解该方法的用法,请看下例:

dataset = tf.data.Dataset.range(7).window(3, None, 1, True) 
for window in dataset: 
    print(list(window.as_numpy_iterator())) 

输出:

[0, 1, 2]
[3, 4, 5]

可以看到该示例是无重叠采样,drop_remainder=True 表示丢弃不足窗口宽度的数据。

为了增加可读性,方便比较,仅保留关键代码:

range(7).window(3, 1, 1, True) 
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
-------------------------------
range(7).window(3, 2, 1, True) 
[0, 1, 2]
[2, 3, 4]
[4, 5, 6]
-------------------------------
range(7).window(3, 3, 1, True)
[0, 1, 2]
[3, 4, 5]
-------------------------------
range(7).window(3, None, 1, True) 
[0, 1, 2]
[3, 4, 5]
-------------------------------
range(7).window(3, None, 2, True) 
[0, 2, 4]
-------------------------------
range(7).window(3, None, 3, True) 
[0, 3, 6]
-------------------------------
range(7).window(3, 1, 1, True) 
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
-------------------------------
range(7).window(3, 1, 2, True) 
[0, 2, 4]
[1, 3, 5]
[2, 4, 6]
-------------------------------
range(7).window(3, 1, 3, True)
[0, 3, 6]

Dataset.flat_map 方法可以获取数据集的数据集并将其展平为单个数据集:

window_size = 5
windows = range_ds.window(window_size, shift=1)

for x in windows.flat_map(lambda x: x).take(30):
    print(x.numpy(), end=' ')

输出(为了方便说明该方法的用法,警告信息就不粘过来了):

0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9 

通过函数封装:

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
    windows = ds.window(window_size, shift=shift, stride=stride)

    def sub_to_batch(sub):
        return sub.batch(window_size, drop_remainder=True)

    windows = windows.flat_map(sub_to_batch)
    return windows

测试

ds = make_window_dataset(range_ds, window_size=10, shift=1, stride=2)

for example in ds.take(10):
    print(example.numpy())

输出:

[ 0  2  4  6  8 10 12 14 16 18]
[ 1  3  5  7  9 11 13 15 17 19]
[ 2  4  6  8 10 12 14 16 18 20]
[ 3  5  7  9 11 13 15 17 19 21]
[ 4  6  8 10 12 14 16 18 20 22]
[ 5  7  9 11 13 15 17 19 21 23]
[ 6  8 10 12 14 16 18 20 22 24]
[ 7  9 11 13 15 17 19 21 23 25]
[ 8 10 12 14 16 18 20 22 24 26]
[ 9 11 13 15 17 19 21 23 25 27]

3.多变量滑动窗口

更多源码将在以下github仓库更新,欢迎star,fork,issue!
https://github.com/datamonday/TimeSeriesMoonlightBox

import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
from scipy import stats
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import sys


# 实现多变量的滑动窗口截取数据(滑动步长为1)
def create_samples(self, dataset, target, start_index, end_index):
    '''
    参数说明:
    dataset:DataFrame.values 格式;
    target:要预测的特征列,格式同上;
    start_index:数据中开始截取的索引;
    end_index:数据中结束截取的索引;
    '''
    # 定义用于存放样本数据的列表
    sample = []
    # 定义用于存放样本期望预测值的列表
    labels = []

    # 令开始索引为start_index参数+预测的时间步长度,后续程序段会将其减去
    # 该函数返回样本之前的预测时间步长度,目的是为了防止数据开始0之前没有数据而无法截取。
    start_index = start_index + self.n_history

    if end_index is None:
        # 结束截取索引为数据集行数减去预测的时间步长度;
        # 此举是为了保证截取的样本完整,如果最后一个样本不满足预测的时间步长度则丢弃。
        end_index = len(dataset) - self.n_outputs

    for i in range(start_index, end_index):
        # 防止最后一个样本溢出
        if i + self.n_outputs >= end_index:
            pass

        else:
            # 按照步长返回n_history长度大小的切片
            indices = range(i - self.n_history, i, self.interval) # step表示滑动步长

            # 截取数据并添加到列表中
            sample.append(dataset[indices])

            # 如果单步预测标志位为True,则期望预测值为之后的一个单值
            if self.single_step:
                labels.append(target[i+ self.n_outputs])

            # 否则为设置的目标长度值
            else:
                labels.append(target[i:i+ self.n_outputs])

    sample = np.array(sample)
    labels = np.array(labels)

    return sample, labels


# 实现多变量的滑动窗口截取数据(滑动步长为step)
def create_samples2(dataset, target, start_index, end_index, history_size,
                    target_size, step, single_step=False):
    data = []
    labels = []

    start_index = start_index + history_size
    
    if end_index is None:
        end_index = len(dataset) - target_size

    for i in range(start_index, end_index):
        indices = range(i-history_size, i, step) # step表示滑动步长
        data.append(dataset[indices])

        if single_step:
            labels.append(target[i+target_size])
        else:
            labels.append(target[i:i+target_size])

    return np.array(data), np.array(labels)
你可以按照以下步骤调用 class MultiHeadAttention(tf.keras.layers.Layer): 1. 首先,导入 tensorflow 库: ```python import tensorflow as tf ``` 2. 创建一个 MultiHeadAttention 类的实例,并传入所需的参数: ```python mha = MultiHeadAttention(heads=8, d_model=512, dropout=0.2) ``` 这里的 heads 表示头的数量,d_model 表示模型的维度,dropout 表示 dropout 的概率。 3. 将输入数据传递给 MultiHeadAttention 实例: ```python output = mha(inputs) ``` 这里的 inputs 是一个形状为 (batch_size, seq_len, d_model) 的张量,表示输入数据的形状。 完整的调用代码示例: ```python import tensorflow as tf class MultiHeadAttention(tf.keras.layers.Layer): def __init__(self, heads, d_model, dropout): super(MultiHeadAttention, self).__init__() self.heads = heads self.d_model = d_model self.dropout = dropout self.depth = d_model // heads self.Wq = tf.keras.layers.Dense(d_model) self.Wk = tf.keras.layers.Dense(d_model) self.Wv = tf.keras.layers.Dense(d_model) self.dense = tf.keras.layers.Dense(d_model) def split_heads(self, x, batch_size): x = tf.reshape(x, (batch_size, -1, self.heads, self.depth)) return tf.transpose(x, perm=[0, 2, 1, 3]) def call(self, inputs): q = self.Wq(inputs) k = self.Wk(inputs) v = self.Wv(inputs) batch_size = tf.shape(q)[0] q = self.split_heads(q, batch_size) k = self.split_heads(k, batch_size) v = self.split_heads(v, batch_size) scaled_attention, attention_weights = scaled_dot_product_attention(q, k, v) scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3]) concat_attention = tf.reshape(scaled_attention, (batch_size, -1, self.d_model)) output = self.dense(concat_attention) return output mha = MultiHeadAttention(heads=8, d_model=512, dropout=0.2) inputs = tf.random.uniform((64, 10, 512)) output = mha(inputs) print(output.shape) ``` 这里的输入数据 inputs 的形状是 (64, 10, 512),表示有 64 个序列,每个序列的长度为 10,每个词的向量维度为 512。输出的形状也是 (64, 10, 512)。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

datamonday

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值