如何使用多模态输入构建 RNN 以对时间序列进行分类

Posted

技术标签:

【中文标题】如何使用多模态输入构建 RNN 以对时间序列进行分类【英文标题】:How to build RNN with multimodal input to classify time series 【发布时间】:2021-10-29 21:04:54 【问题描述】:

我有每个时间序列 50 个样本的数据。 我想构建一个时间序列分类器。

每个样本都有三个输入 - 一个形状为 1X768 的向量、一个形状为 1X25 的向量、一个形状为 1X496 的向量。

每个输入都来自不同的模态,因此在连接所有输入之前需要经过一些特定于输入的层。

数据存储在数据框中:

df = time_series_id timestamp    input1     input2     input3     time_series_label 
           0         0          [x0..x768] [x0..x25] [x0..x496]     A  
           0         1          [x0..x768] [x0..x25] [x0..x496]     A
     ..
           0         50         [x0..x768] [x0..x25] [x0..x496]     A  
           1         0          [x0..x768] [x0..x25] [x0..x496]     B
           1         50         [x0..x768] [x0..x25] [x0..x496]     B

我是 DL 新手,我想构建一个网络,将每个 50 个时间戳长的时间序列分类为 2 个类之一,但我找不到任何教程来举例说明如何将多模态数据插入 Conv1dLSTM层。

如何构建这样的网络,最好使用 keras,并在我的数据帧上进行训练以对时间序列进行分类? (所以,当我给它一个包含 50 个时间戳的新时间序列时,我将获得整个时间序列的 A/B 预测)?

请注意,具有相同 id 的所有行的标签都是相同的。所以每次,我只需要为 RNN 提供具有相同 id 的样本。

【问题讨论】:

【参考方案1】:

我为你创建了一个很好的例子:

# Define mini-dataset  similar to yours example
df = pd.DataFrame('A':[np.zeros((768))]*100,'B':[np.ones((25))]*100)
# 100 rows, 2 columns (each value in column A is a list size 768, each value in column B is a list size 25)

预处理数据以匹配 50 个时间戳的滚动窗口

# Create windows of data:
list_of_indexes=[]
df.index.to_series().rolling(50).apply((lambda x: list_of_indexes.append(x.tolist()) or 0), raw=False)
d_A = df.A.apply(list)
d_B = df.B.apply(list)
a = [[d_A[ix] for ix in x] for x in list_of_indexes]
b = [[d_B[ix] for ix in x] for x in list_of_indexes]
a = np.array(a)
b = np.array(b)

print(f'a shape: a.shape')
print(f'b shape: b.shape')

预处理后的数据:

a shape: (51, 50, 768)
b shape: (51, 50, 25)

解释:

a:51 个样本,每个样本包含 50 个时间戳,每个时间戳包含 768 个值。 (b与25个值相同。)

创建一个有两个输入的模型,输入a和输入b,你可以分别处理它们,然后连接起来。

# define two sets of inputs
input_A = Input(shape=(50, 768))
input_B = Input(shape=(50, 25))

LSTM_A = Bidirectional(LSTM(32))(input_A)
LSTM_B = Bidirectional(LSTM(32))(input_B)
               
combined = concatenate([
                        LSTM_A,
                        LSTM_B
                       ])
dense1 = Dense(32, activation='relu')(combined)
output = Dense(1, activation='sigmoid')(dense1)
model = Model(inputs=[
                     input_A,
                     input_B
                     ], outputs=output)
model.summary()

模型总结:

拟合模型:

adam = Adam(lr=0.00001)
model.compile(loss='binary_crossentropy', optimizer=adam)
history = model.fit([a,b], y, batch_size=2, epochs=2)

当然你可以在 LSTM 之前进行连接:

# define two sets of inputs
input_A = Input(shape=(50, 768))
input_B = Input(shape=(50, 25))

combined = concatenate([
                        input_A,
                        input_B
                       ])
LSTM_layer = Bidirectional(LSTM(32))(combined)
dense1 = Dense(32, activation='relu')(LSTM_layer)
output = Dense(1, activation='sigmoid')(dense1)
model = Model(inputs=[
                     input_A,
                     input_B
                     ], outputs=output)
model.summary()

编辑:

df:

形状:(100, 4)

预处理代码:

def split_into_inputs(group):
    x_data_inp1.append(group.input1)
    x_data_inp2.append(group.input2)
    # supposing time_series_id have the same label for all of its rows (thats what i understood from the question details)
    y_data.append(group.time_series_label.unique()[0])


x_data_inp1 = []
x_data_inp2 = []
y_data = []

df.groupby('time_series_id').apply(lambda group: split_into_inputs(group))
# convert list into array with np.float dtype to match the nn.
x_data_inp1 = np.array(x_data_inp1, dtype=np.float)
x_data_inp2 = np.array(x_data_inp2, dtype=np.float)

# Convert labels from chars into digits
from sklearn.preprocessing import LabelEncoder
# creating instance of labelencoder
labelencoder = LabelEncoder()
# Assigning numerical values. Convert 'A','B' into 0, 1
y_data = labelencoder.fit_transform(y_data)
x_data_inp1.shape, x_data_inp2.shape, y_data.shape

输出:

((2, 50, 768), (2, 50, 25), (2,))

对我们的100个样本进行预处理后,根据“time_series_id”列有2个序列,每个50个样本,有2个标签,第一个序列的标签A为0,第二个序列的标签B为1顺序。 问题:每个50个样本的序列都有不同的“time_series_id”?

定义模式:

# define two sets of inputs
input_A = Input(shape=(50, 768))
input_B = Input(shape=(50, 25))

LSTM_A = Bidirectional(LSTM(32))(input_A)
LSTM_B = Bidirectional(LSTM(32))(input_B)

combined = concatenate([
                        LSTM_A,
                        LSTM_B
                       ])
dense1 = Dense(32, activation='relu')(combined)
output = Dense(1, activation='sigmoid')(dense1)
model = Model(inputs=[
                     input_A,
                     input_B
                     ], outputs=output)
model.summary()

拟合模型:

adam = Adam(lr=0.00001)
model.compile(loss='binary_crossentropy', optimizer=adam)
history = model.fit([x_data_inp1, x_data_inp2], y_data, batch_size=2, epochs=2)

【讨论】:

谢谢!你能解释一下预处理/滚动步骤吗?这背后的目的是什么?以及为什么要在它之后有 51 行(之前是 100 行? 是的,我明白了,但为什么要把它转换成 (51,50,X)?为什么是51?为什么这一步是必要的? 写完之前发评论,不好意思。 100 行仅用于示例。我创建了一个您提到的大小的滑动窗口(50)。滑动窗口是一种流行的技术(例如***.com/questions/8269916/…)。 上一条评论的继续:根据 (51,50,X) - 50 是每个时间序列的样本(回溯大小) | 51 是在具有 100 个时间戳的集合上创建滑动窗口后的样本数(正如我之前提到的,100 只是示例,我不知道您的真实数据的大小是多少)。例如,如果我们在这个数据上使用 3 的滑动窗口:[1,2,3,4,5,6],我们得到 [1,2,3], [2,3,4], [3, 4,5],[4,5,6]。形状:(4,3,X) 改成x_data_inp1.append(list(group.input1))后终于成功了,谢谢!【参考方案2】:

使用一些网络(线性、MLP 等)将它们嵌入到相同的维度,您可以使用加法、逐元素乘法、双(三)线性或任何您想要将这些组合到 RNN 的维度统一输入或CNN。 或者你可以只连接每个时间步,每个时间步一个数据,这对 CNN 来说没问题

【讨论】:

请看我的编辑 - 分类是在时间序列级别 最简单的,按照时间步连接它们,然后就可以了。 我想在连接之前通过一些层运行每个模式。 Input1 应该首先通过 Dense(25) 并且 input3 应该通过 Dense(30) 所以我将在连接后得到一个 1X80 的向量,它应该是 Conv1D 的输入。我不明白我如何让神经网络“理解”所有 50 个时间戳都属于同一个时间序列。您可能有代码示例吗? 您可以简单地使用像 RNN 这样的顺序模型,在您的情况下,双向模型(如 Bi-LSTM、Bi-GRU)似乎更好。在 RNN 的情况下,完整的 50 个输入被顺序组合并被视为一个输入,形状类似于 [T, F],其中 T 是时间长度,F 是每个时间步的特征大小 对不起,我从理论上理解它,但我只是不明白我如何实现一个可以同时满足这两个需求的网络:1)所有 50 个时间戳都是同一时间序列的一部分 2)我有 3 个输入并且每个都可以在连接之前经过不同的层。也许你有一个代码示例?

以上是关于如何使用多模态输入构建 RNN 以对时间序列进行分类的主要内容,如果未能解决你的问题,请参考以下文章

如何构建一个从输出到下一个输入的大额外循环的 RNN 模型?

使用 RNN 进行时间序列分析的生成器

如何使用 LSTM 单元训练 RNN 以进行时间序列预测

多模态特征融合:图像语音文本如何转为特征向量并进行分类

RNN经典案例实战使用RNNLSTMGRU模型构建姓名分类器

RNN经典案例实战使用RNNLSTMGRU模型构建姓名分类器