使用 LSTM 进行多元二进制序列预测

Posted

技术标签:

【中文标题】使用 LSTM 进行多元二进制序列预测【英文标题】:Multivariate binary sequence prediction with LSTM 【发布时间】:2019-05-27 09:50:52 【问题描述】:

我正在研究一个序列预测问题,我在这方面没有太多经验,所以下面的一些问题可能很幼稚。

仅供参考:我创建了一个关注 CRF 的后续问题here

我有以下问题:

我想预测多个非独立变量的二进制序列。

输入:

我有一个包含以下变量的数据集:

    时间戳 A 组和 B 组 在特定时间戳对应每个组的二进制信号

另外,假设如下:

    我们可以从时间戳(例如一天中的小时)中提取其他属性,这些属性可以用作外部预测器 我们认为 A 组和 B 组不是独立的,因此联合建模它们的行为可能是最佳的

binary_signal_group_Abinary_signal_group_B 是我想使用 (1) 它们过去的行为和 (2) 从每个时间戳中提取的附加信息来预测的 2 个非独立变量。

到目前为止我做了什么:

# required libraries
import re
import numpy as np
import pandas as pd
from keras import Sequential
from keras.layers import LSTM

data_length = 18  # how long our data series will be
shift_length = 3  # how long of a sequence do we want

df = (pd.DataFrame  # create a sample dataframe
    .from_records(np.random.randint(2, size=[data_length, 3]))
    .rename(columns=0:'a', 1:'b', 2:'extra'))
# NOTE: the 'extra' variable refers to a generic predictor such as for example 'is_weekend' indicator, it doesn't really matter what it is

# shift so that our sequences are in rows (assuming data is sorted already)
colrange = df.columns
shift_range = [_ for _ in range(-shift_length, shift_length+1) if _ != 0]
for c in colrange:
    for s in shift_range:
        if not (c == 'extra' and s > 0):
            charge = 'next' if s > 0 else 'last'  # 'next' variables is what we want to predict
            formatted_s = '0:02d'.format(abs(s))
            new_var = 'var_charge_n'.format(var=c, charge=charge, n=formatted_s)
            df[new_var] = df[c].shift(s)

# drop unnecessary variables and trim missings generated by the shift operation
df.dropna(axis=0, inplace=True)
df.drop(colrange, axis=1, inplace=True)
df = df.astype(int)
df.head()  # check it out

#   a_last_03  a_last_02      ...        extra_last_02  extra_last_01
# 3          0          1      ...                    0              1
# 4          1          0      ...                    0              0
# 5          0          1      ...                    1              0
# 6          0          0      ...                    0              1
# 7          0          0      ...                    1              0
# [5 rows x 15 columns]

# separate predictors and response
response_df_dict = 
for g in ['a','b']:
    response_df_dict[g] = df[[c for c in df.columns if 'next' in c and g in c]]

# reformat for LSTM
# the response for every row is a matrix with depth of 2 (the number of groups) and width = shift_length
# the predictors are of the same dimensions except the depth is not 2 but the number of predictors that we have

response_array_list = []
col_prefix = set([re.sub('_\d+$','',c) for c in df.columns if 'next' not in c])
for c in col_prefix:
    current_array = df[[z for z in df.columns if z.startswith(c)]].values
    response_array_list.append(current_array)

# reshape into samples (1), time stamps (2) and channels/variables (0)
response_array = np.array([response_df_dict['a'].values,response_df_dict['b'].values])
response_array = np.reshape(response_array, (response_array.shape[1], response_array.shape[2], response_array.shape[0]))
predictor_array = np.array(response_array_list)
predictor_array = np.reshape(predictor_array, (predictor_array.shape[1], predictor_array.shape[2], predictor_array.shape[0]))

# feed into the model
model = Sequential()
model.add(LSTM(8, input_shape=(predictor_array.shape[1],predictor_array.shape[2]), return_sequences=True))  # the number of neurons here can be anything
model.add(LSTM(2, return_sequences=True))  # should I use an activation function here? the number of neurons here must be equal to the # of groups we are predicting
model.summary()

# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# lstm_62 (LSTM)               (None, 3, 8)              384       
# _________________________________________________________________
# lstm_63 (LSTM)               (None, 3, 2)              88        
# =================================================================
# Total params: 472
# Trainable params: 472
# Non-trainable params: 0

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])  # is it valid to use crossentropy and accuracy as metric?
model.fit(predictor_array, response_array, epochs=10, batch_size=1)
model_preds = model.predict_classes(predictor_array)  # not gonna worry about train/test split here
model_preds.shape  # should return (12, 3, 2) or (# of records, # of timestamps, # of groups which are a and b)

# (12, 3)

model_preds
# array([[1, 0, 0],
#        [0, 0, 0],
#        [1, 0, 0],
#        [0, 0, 0],
#        [1, 0, 0],
#        [0, 0, 0],
#        [0, 0, 0],
#        [0, 0, 0],
#        [0, 0, 0],
#        [0, 0, 0],
#        [1, 0, 0],
#        [0, 0, 0]])

问题:

这里的主要问题是:我如何让它工作,以便模型预测两个组的下 N 个序列?

另外,我想问以下问题:

    预计 A 组和 B 组是互相关的,但是,尝试通过单个模型同时输出 A 和 B 序列是否有效,或者我应该拟合 2 个单独的模型,一个预测 A,另一个预测 B但都使用历史 A 和 B 数据作为输入? 虽然我在模型中的最后一层是形状为 (None, 3, 2) 的 LSTM,但预测输出的形状为 (12, 3),而我原本预计它是 (12, 2) - 我我在这里做错了,如果是,我将如何解决这个问题? 就输出 LSTM 层而言,在这里使用激活函数(例如 sigmoid)会不会是个好主意?为什么/为什么不? 使用分类类型损失(二元交叉熵)和指标(准确度)来优化序列是否有效? LSTM 模型在这里是最佳选择吗?有人认为 CRF 或某些 HMM 类型的模型在这里会更好吗?

非常感谢!

【问题讨论】:

【参考方案1】:

我会依次回答所有问题

我如何让这个工作,以便模型预测下一个 N 两组的序列?

我建议对您的模型进行两项修改。first 在最后一层使用 sigmoid 激活。

为什么??考虑二元交叉熵损失函数(我借用了here的方程) 其中L 是计算损失,p 是网络预测,y 是目标值。

损失是为 定义的。 如果 p 超出此开区间范围,则损失未定义。 keras is tanh 默认激活 lstm 层,输出范围为 (-1, 1)。这意味着模型的输出不适合二元交叉熵损失。如果您尝试训练模型,您最终可能会因为损失而得到nan

第二次修改(是第一次修改的一部分)要么在最后一层之前添加 sigmoid 激活。为此,您有三个选择。

    在输出和最后一个 lstm 层之间添加带 sigmoid 激活的密集层。 或者将lstm层的激活改为sigmoid。 或者在输出层之后添加带有sigmoid激活的激活层。

即使所有情况都有效,我还是建议使用带 sigmoid 激活的密集层,因为它几乎总是效果更好。 现在具有建议更改的模型将是

model = Sequential()
model.add(LSTM(8, input_shape=(predictor_array.shape[1],predictor_array.shape[2]), return_sequences=True))  
model.add(LSTM(2, return_sequences=True)) 
model.add(TimeDistributed(Dense(2, activation="sigmoid")))
model.summary()

... 尝试通过单个输出 A 和 B 序列是否有效 模型还是我应该安装 2 个单独的模型...?

理想情况下,这两种情况都可以。但是最新的研究,如this one 表明,前一种情况(你对两组都使用一个模型)往往表现更好。该方法通常称为Multi Task Learning。 多任务学习背后的思想非常广泛,为简单起见,可以将其视为通过强制模型学习多个任务常见的隐藏表示来增加归纳偏差。

...预测输出的形状为 (12, 3) 应该是 (12, 2) - 我在这里做错了吗...?

你得到这个是因为你正在使用predict_classes 方法。与 predict 方法不同, predict_classes 方法返回通道轴的最大索引(在您的情况下为第三索引)。正如我在上面所解释的,如果您对最后一层使用 sigmoid 激活并将 predict_classes 替换为 predict,您将得到您所期望的。

就输出 LSTM 层而言,这会是一个好主意吗 在这里使用激活函数,例如 sigmoid?为什么/为什么不?

我希望我已经在上面解释了这一点。答案是肯定的。

使用分类类型损失是否有效(二元交叉熵) 和用于优化序列的指标(准确性)?

由于您的目标是二进制信号(分布为Bernoulli distribution),是的,使用二进制损失和准确度指标是有效的。 This answer gives 更多关于为什么二进制交叉熵对这种类型的目标变量有效的详细信息。

LSTM 模型在这里是最佳选择吗?有没有人认为 CRF 或者一些 HMM 类型的模型在这里会更好用?

这取决于可用数据和您选择的网络的复杂性。如果可用数据很小,CRF 和 HMM 网络很简单并且工作得更好。但是如果可用的数据集很大,LSTM 几乎总是优于 CRF 和 HMM。我的建议是,如果您有大量数据,请使用 LSTM。但是,如果您有少量数据或正在寻找简单的模型,您可以使用 CRF 或 HMM。

【讨论】:

感谢您的回复,希望得到更详细的答复!一个小的修正:激活函数应该是一个密集的参数而不是 TimeDistributed。关于 CRF 的后续问题,如果您不介意的话:我已经尝试在 keras contrib 包中使用它的实现,它可以工作,但不适用于密集层(诚然,我需要阅读更多内容)但还有其他尽管大多数其他实现可能不允许我将其作为多任务学习问题来解决,但您会推荐没有密集层的 keras contrib 实现吗? 是的,关于激活函数,你是对的。我已经更新了代码。 仅供参考,我创建了一个关于 CRF 的后续问题here

以上是关于使用 LSTM 进行多元二进制序列预测的主要内容,如果未能解决你的问题,请参考以下文章

如何处理keras中多元LSTM中的多步时间序列预测

用于罕见事件时间序列预测的LSTM模型体系结构

在 keras 中使用 CNN-LSTM 模型进行序列到序列分类

Python对商店数据进行lstm和xgboost销售量时间序列建模预测分析|附代码数据

Python对商店数据进行lstm和xgboost销售量时间序列建模预测分析|附代码数据

LSTM时序预测基于matlab LSTM时间序列神经网络预测含Matlab源码 2267期