我正在进行一个项目,需要在神经网络中结合使用数值和文本数据,以预测系统在接下来一小时的可用性。我决定不使用单独的神经网络并在最后进行一些奇怪或不明确(对我来说)的操作来生成所需的输出,而是使用Keras的合并层与两个网络(一个用于数值数据,一个用于文本数据)。我的想法是,我为模型提供前6小时性能指标的序列,形状为(batch_size, 6hrs, num_features)。除了提供给处理数值数据的网络的输入外,我还向第二个网络提供另一个大小为(batch_size, max_alerts_per_sequence, max_sentence_length)的序列。
在某个时间范围内的任何数值数据序列都可能有可变数量的事件(文本数据)与之相关。为了简化起见,我只允许每个性能数据序列最多伴随50个事件。每个事件都是按单词进行哈希编码并填充的。我尝试使用一个展平层将输入形状从(50, 30)减少到(1500),这样模型就可以训练这些“序列”中的每一个事件(为了澄清:我为每个性能数据序列传递给模型50个句子,每个句子包含30个编码元素)。
我的问题是:由于我需要神经网络查看给定性能指标序列的所有事件,我如何使基于文本数据的神经网络训练句子序列?
我的模型:
#LSTM Module for performance metricsinput = Input(shape=(shape[1], shape[2]))lstm1 = Bidirectional(LSTM(units=lstm_layer_count, activation='tanh', return_sequences=True, input_shape=shape))(input)dropout1 = Dropout(rate=0.2)(lstm1)lstm2 = Bidirectional(LSTM(units=lstm_layer_count, activation='tanh', return_sequences=False))(dropout1)dropout2 = Dropout(rate=0.2)(lstm2)#LSTM Module for text based datatInput = Input(shape=(50, 30))flatten = Flatten()(tInput)embed = Embedding(input_dim=vocabsize + 1, output_dim= 50 * 30, input_length=30*50)(flatten)magic = Bidirectional(LSTM(100))(embed)tOut = Dense(1, activation='relu')(magic)#Merge the layersconcat = Concatenate()([dropout2, tOut])output = Dense(units=1, activation='sigmoid')(concat)nn = keras.models.Model(inputs=[input, tInput], outputs = output)opt = keras.optimizers.SGD(lr=0.1, momentum=0.8, nesterov=True, decay=0.001)nn.compile(optimizer=opt, loss='mse', metrics=['accuracy', coeff_determination])
回答:
据我所知,你有一个最多50个事件的序列,你想要为这些事件进行预测。这些事件附带文本数据,这些文本数据可以被视为另一个单词嵌入序列。这里有一篇关于类似架构的文章。
我建议的解决方案包括针对文本部分使用LSTM,以及针对“真实”序列部分使用一维卷积。每层LSTM都与数值数据连接。这涉及到50个LSTM层,即使使用共享权重,训练也可能非常耗时。也可以只为文本部分使用卷积层,这样更快,但不能建模长期依赖关系。(我有经验,这些长期依赖关系在文本挖掘中往往不是那么重要)。
文本 -> LSTM或1DConv -> 与数值数据连接 -> 1DConv -> 输出下面是一些示例代码,展示如何使用共享权重:
numeric_input = Input(shape=(x_numeric_train.values.shape[1],), name='numeric_input')nlp_seq = Input(shape=(number_of_messages ,seq_length,), name='nlp_input'+str(i))# shared layersemb = TimeDistributed(Embedding(input_dim=num_features, output_dim=embedding_size, input_length=seq_length, mask_zero=True, input_shape=(seq_length, )))(nlp_seq) x = TimeDistributed(Bidirectional(LSTM(32, dropout=0.3, recurrent_dropout=0.3, kernel_regularizer=regularizers.l2(0.01))))(emb) c1 = Conv1D(filter_size, kernel1, padding='valid', activation='relu', strides=1, kernel_regularizer=regularizers.l2(kernel_reg))(x)p1 = GlobalMaxPooling1D()(c1)c2 = Conv1D(filter_size, kernel2, padding='valid', activation='relu', strides=1, kernel_regularizer=regularizers.l2(kernel_reg))(x)p2 = GlobalMaxPooling1D()(c2)c3 = Conv1D(filter_size, kernel3, padding='valid', activation='relu', strides=1, kernel_regularizer=regularizers.l2(kernel_reg))(x)p3 = GlobalMaxPooling1D()(c3)x = concatenate([p1, p2, p3, numeric_input]) x = Dense(1, activation='sigmoid')(x) model = Model(inputs=[nlp_seq, meta_input] , outputs=[x])model.compile('adam', 'binary_crossentropy', metrics=['accuracy'])
以及训练:
model.fit([x_train, x_numeric_train], y_train)# where x_train is a a array of num_samples * num_messages * seq_length
像这样的复杂模型需要大量数据才能收敛。对于较少的数据,可以通过聚合事件来实现更简单的解决方案。例如,所有事件的文本数据可以被视为一个单一文本(使用分隔符),而不是多个文本,同时数值数据可以被求和、平均或甚至组合成一个固定长度的列表。但这取决于你的数据。
由于我也在做类似的工作,我稍后会更新这个回答并提供代码。