【时间序列预测/分类】 全系列60篇由浅入深的博文汇总:传送门
在处理时间序列预测或者时间序列分类任务的时候,经常会遇到“滑动窗口(sliding window)”的概念,包括在之前写的二十几篇有关时间序列预测或者分类的文章中或多或少也反复提及这个概念。与卷积神经网络处理图像的原理类似,在序列数据中“滑动窗口”用来截取序列片段,从而将原始数据重塑为指定长度的样本,以便模型的建模。
滑动窗口的实现方法比较多,可以使用for循环,但是在处理大量数据就显得捉襟见肘了,这时候Python yield就派上用场了。关于Python yield的原理与使用,需要一定基础,之前专门写过一篇文章介绍,感兴趣的可以参考:深入浅出解析 Python yield。
在日常的业务需求中,滑动窗口函数一般是跟训练集测试集的划分是分开定义的。如果是比较简单的情况,可以定义在一个函数中。
方法1
下边的例子是家庭用电量预测的例子中的滑动窗口实现方法。
先看一下数据集的情况:
由上图可知,使用的训练集(下面代码中的train)的shape为(1442,8),采样点为1442个,特征数(features)为8个(8列)。通过滑动窗口和切片相结合的方式来将以上采样数据划分成样本(samples),其中滑动窗口的宽度和滑动步长(sw_width) 可以设置为参数,方便后期调参的时候,找到适合自己业务需求的窗口宽度和滑动步长。代码实现:
def sliding_window(train, sw_width=7, n_out=7, in_start=0):
'''
该函数实现窗口宽度为7、滑动步长为1的滑动窗口截取序列数据
'''
data = train.reshape((train.shape[0] * train.shape[1], train.shape[2])) # 将以周为单位的样本展平为以天为单位的序列
X, y = [], []
for _ in range(len(data)):
in_end = in_start + sw_width
out_end = in_end + n_out
# 保证截取样本完整,最大元素索引不超过原序列索引,则截取数据;否则丢弃该样本
if out_end < len(data):
# 训练数据以滑动步长1截取
train_seq = data[in_start:in_end, 0]
train_seq = train_seq.reshape((len(train_seq), 1))
X.append(train_seq)
y.append(data[in_end:out_end, 0])
in_start += 1
return np.array(X), np.array(y)
有了以上说明,稍微有点基础应该不难理解。感兴趣的可参考这篇文章:时间序列预测15:Multi-input / Multi-head CNN 实现用电量/发电量预测
方法2
该方法是单列截取数据,可以结合for循环和np.vstack方法将多列数据堆叠起来,该部分是项目中的一个点,等项目完成,会在以后的文章中介绍。 该方法可以指定滑动窗口的宽度和滑动步长。先看一下数据集的情况,方便理解实现的功能。
由上图可知,数据集(下面代码中的train)的shape为(875,10),采样点为875个,特征数(features)为10个(10列)。代码实现:
def _slide_window(rows, sw_width, sw_steps):
'''
函数功能:
按指定窗口宽度和滑动步长实现单列数据截取
--------------------------------------------------
参数说明:
rows:单个文件中的行数;
sw_width:滑动窗口的窗口宽度;
sw_steps:滑动窗口的滑动步长;
'''
start = 0
s_num = (rows - sw_width) // sw_steps # 计算滑动次数
new_rows = sw_width + (sw_steps * s_num) # 完整窗口包含的行数,丢弃少于窗口宽度的采样数据;
while True:
if (start + sw_width) > new_rows: # 如果窗口结束索引超出最大索引,结束截取;
return
yield start, start + sw_width
start += sw_steps
使用上边的数据集测试一下:
_test_list = []
for start,end in _slide_window(test_concat_file.shape[0], 100, 40):
'''
此处可以添加for循环或者其他方式,以实现处理多列数据
'''
_test_list.append(test_concat_file['ax'][start:end])
'''
此处可添加判断条件,以实现数组堆叠
'''
查看生成样本列表的长度:
len(_test_list)
输出:
20
查看最后一个样本所包含的采样数据信息:
_test_list[19]
输出:
760 0.1245
761 0.124742
762 0.124991
763 0.125238
764 0.125482
...
855 0.185577
856 0.185849
857 0.186137
858 0.186466
859 0.186857
Name: ax, Length: 100, dtype: object
方法3
完整代码及更多源码将在以下github仓库更新,欢迎star,fork,issue!
https://github.com/datamonday/TimeSeriesMoonlightBox
def slide_window(self, rows):
'''
函数功能:
生成切片列表截取数据,按指定窗口宽度的50%重叠生成;
--------------------------------------------------
参数说明:
rows:excel文件中的行数;
size:窗口宽度;
'''
start = 0
s_num = (rows - self.sw_width) // self.sw_steps # 计算滑动次数
new_rows = self.sw_width + (self.sw_steps * s_num) # 为保证窗口数据完整,丢弃不足窗口宽度的采样数据
while True:
if (start + self.sw_width) > new_rows: # 丢弃不是完整窗口的数据
return
yield start, start + self.sw_width
start += self.sw_steps
def segment_sensor_signal(self, data_file, label_index='state'):
'''
参数说明:
self.sw_width:滑动窗口宽度;
n_features:特征数量;
label_index:用于计数的列索引;添加编码后的标签列
'''
# 计算特征数
n_features = data_file.shape[1] - 1
# 添加编码后的列名
label_index = 'state_encode'
# scikit-learn 类,实现将真实标签转化为整型数字;
le = preprocessing.LabelEncoder()
# 添加新的标签列;ravel 返回包含输入元素的一维数组。
data_file[label_index] = le.fit_transform(data_file['state'].values.ravel())
print(data_file.columns[:-2])
# -----------------------------------------------#
# label_index = 'state'
# 构造一个切片,方便填充数据
segments = np.empty((0, self.sw_width, n_features), dtype=np.float64)
# 单列标签数据
# labels = np.empty((0), dtype=np.float64)
labels = []
labels_true = np.empty((0))
for start, end in self.slide_window(data_file.shape[0]): # 调用滑动窗口函数,通过yield实现滑动效果;
temporary = [] # 每次存放各个特征的序列片段
for feature in data_file.columns[:-2]: # 遍历文件所有特征列
temporary.append(data_file[feature][start:end])
if (len(data_file[label_index][start:end]) == self.sw_width): # 如果达到窗口宽度,则截取样本
# 将数据通过stack方法堆叠成样本 shape为(none, sw_width, features);
segments = np.vstack([segments, np.dstack(temporary)]) # 堆叠为三维数组
# scipy.stats.mode函数寻找数组每行/每列中最常出现成员以及出现的次数;实现将一个窗口内采样数据的标签出现次数最多的作为样本标签
labels = np.append(labels, stats.mode(data_file[label_index][start:end])[0][0]) # 出现次数最多的标签作为样本标签
labels_true = np.append(labels_true, stats.mode(data_file['state'][start:end])[0][0])
# labels_true = pd.DataFrame(labels)
# labels_onehot = np.asarray(pd.get_dummies(labels), dtype = np.int8)
labels = np.asarray(labels, dtype=np.float64)
labels_onehot = to_categorical(labels, num_classes=len(np.unique(data_file[label_index].values)))
return segments, labels_onehot, labels_true