RNN 和 CNN-RNN 不能正确训练,总是预测一类
Posted
技术标签:
【中文标题】RNN 和 CNN-RNN 不能正确训练,总是预测一类【英文标题】:RNN and CNN-RNN won't train correctly, always predict one class 【发布时间】:2020-07-05 05:36:41 【问题描述】:我目前正在开发一个模型,以使用深度学习算法从文本中检测情绪。我有一个相对较小的标记数据集(~7500),有 7 种不同的情绪作为类。我开发了一个 CNN 并达到了约 63% 的准确率,但是当我尝试应用一个使用 LSTM 的 RNN 和一个同样使用 LSTM 的 CNN-RNN 时,它们似乎根本没有正确训练并且总是最终预测同一个班级。我相信我的模型基本上是合理的,但参数有一些错误。我将数据集分成 85% 用于训练,另外 20% 用于验证,剩下的 15% 用于测试。我的嵌入矩阵是使用 Google News word2vec 中的单词表示开发的,单词索引是使用 keras Tokenizer 开发的。
数据集细分:
情绪
愤怒1086
厌恶 1074
恐惧 1086
内疚 1062
欢乐1089
悲伤1080
耻辱 1058
CNN 实现
def make_model(kernel_sizes, num_filters, dropout, hidden_units):
submodels = []
for kernel_size in kernel_sizes:
submodel = Sequential()
submodel.add(Embedding(input_dim = input_dim,
output_dim = output_dim,
weights = [embedding_matrix],
input_length = max_len,
trainable = True))
submodel.add(Conv1D(filters=num_filters, kernel_size=kernel_size, padding='same',activation='relu',strides=1))
submodel.add(GlobalMaxPooling1D())
submodels.append(submodel)
submodel_outputs = [model.output for model in submodels]
submodel_inputs = [model.input for model in submodels]
merged = Concatenate(axis=1)(submodel_outputs)
x = Dropout(dropout)(merged)
if(hidden_units > 0):
x = Dense(hidden_units, activation='relu')(x)
x = Dropout(dropout)(x)
x = Dense(7,activation='softmax', kernel_initializer="uniform")(x)
out = Activation('sigmoid')(x)
model = Model(submodel_inputs, out)
model.compile(loss='categorical_crossentropy',optimizer='rmsprop',metrics=['acc'])
return model
def fit_model(model, kernel_sizes, num_epochs, batch_size, x_train, y_train):
x_train = [x_train]*len(kernel_sizes)
history = model.fit(x_train, y_train, batch_size=batch_size, epochs=num_epochs, validation_split=0.2)
return history
kernel_sizes = [2,6]
num_filters = 100
dropout = 0.6
num_hidden = 270
callbacks = callbacks_list
num_epochs = 15
batch_size = 64
model = make_model(kernel_sizes, num_filters, dropout, num_hidden)
print(model.summary())
history = fit_model(model, kernel_sizes, num_epochs, batch_size, x_train, y_train)
型号:“model_1”
层(类型)输出形状参数#连接到
embedding_1_input (InputLayer) (无, 179) 0
embedding_2_input (InputLayer) (无, 179) 0
embedding_1 (嵌入) (None, 179, 300) 2729400 embedding_1_input[0][0]
embedding_2 (嵌入) (None, 179, 300) 2729400 embedding_2_input[0][0]
conv1d_1 (Conv1D) (None, 179, 100) 60100 embedding_1[0][0]
conv1d_2 (Conv1D) (None, 179, 100) 180100 embedding_2[0][0]
global_max_pooling1d_1 (GlobalM (None, 100) 0 conv1d_1[0][0]
global_max_pooling1d_2 (GlobalM (None, 100) 0 conv1d_2[0][0]
concatenate_1(连接)(无,200)0 global_max_pooling1d_1[0][0] global_max_pooling1d_2[0][0]
dropout_1(丢弃)(无,200)0 concatenate_1[0][0]
dense_1(密集)(无,270)54270 dropout_1[0][0]
dropout_2 (Dropout) (None, 270) 0 dense_1[0][0]
dense_2(密集)(无,7)1897 dropout_2[0][0]
activation_1(激活)(无,7)0 dense_2[0][0]
总参数:5,755,167 可训练参数:5,755,167 不可训练参数:0
Training and Validation results for CNN
CNN confusion matrix
RNN 实现
def make_model(lstm_units, dropout, hidden_units):
model = Sequential()
model.add(Embedding(input_dim = input_dim,
output_dim = output_dim,
weights = [embedding_matrix],
input_length = max_len,
trainable = False))
model.add(LSTM(lstm_units))
model.add(Dropout(dropout))
if(hidden_units > 0):
model.add(Dense(hidden_units, activation='elu'))
model.add(Dropout(dropout))
model.add(Dense(7,activation='softmax', kernel_initializer="uniform"))
model.add(Activation('sigmoid'))
model.compile(loss='categorical_crossentropy',optimizer='rmsprop',metrics=['acc'])
return model
lstm_units = 120
dropout = 0.5
hidden_units = 550
callbacks = [tensorboard, early]
num_epochs = 20
batch_size = 60
model = make_model(lstm_units, dropout, hidden_units)
print(model.summary())
history = fit_model(model, num_epochs, batch_size, x_train, y_train)
型号:“sequential_6”
层(类型)输出形状参数#
embedding_6 (嵌入) (None, 179, 300) 2729400
lstm_8 (LSTM) (无, 120) 202080
dropout_5(辍学)(无,120)0
dense_6(密集)(无,550)66550
dropout_6(丢弃)(无,550)0
dense_7(密集)(无,7)3857
activation_3(激活)(无,7)0
总参数:3,001,887 可训练参数:272,487 不可训练参数:2,729,400
RNN training and validation scores
RNN confusion matrix
CNN-RNN 实现
def make_model(kernel_sizes, num_filters, dropout, hidden_units, lstm_units):
submodels = []
for kernel_size in kernel_sizes:
submodel = Sequential()
submodel.add(Embedding(input_dim = input_dim,
output_dim = output_dim,
weights = [embedding_matrix],
input_length = max_len,
trainable = True))
submodel.add(Conv1D(filters=num_filters, kernel_size=kernel_size, padding='same',activation='relu',strides=1))
submodel.add(MaxPooling1D(pool_size=2, strides = 2))
submodel.add(Dropout(dropout))
submodel.add(LSTM(lstm_units))
submodels.append(submodel)
submodel_outputs = [model.output for model in submodels]
submodel_inputs = [model.input for model in submodels]
merged = Concatenate(axis=1)(submodel_outputs)
x = Dropout(dropout)(merged)
if(hidden_units > 0):
x = Dense(hidden_units, activation='relu')(x)
x = Dropout(dropout)(x)
x = Dense(7,activation='softmax', kernel_initializer="uniform")(x)
out = Activation('sigmoid')(x)
model = Model(submodel_inputs, out)
model.compile(loss='categorical_crossentropy',optimizer='rmsprop',metrics=['acc'])
return model
kernel_sizes = [2,3,6]
num_filters = 100
dropout = 0.6
num_hidden = 270
lstm_units = 80
callbacks = [tensorboard, early]
num_epochs = 20
batch_size = 64
model = make_model(kernel_sizes, num_filters, dropout, num_hidden, lstm_units)
print(model.summary())
history = fit_model(model, kernel_sizes, num_epochs, batch_size, x_train, y_train)
型号:“model_2”
层(类型)输出形状参数#连接到
embedding_8_input (InputLayer) (无, 179) 0
embedding_9_input (InputLayer) (无, 179) 0
embedding_10_input (InputLayer) (None, 179) 0
embedding_8 (嵌入) (None, 179, 300) 2729400 embedding_8_input[0][0]
embedding_9 (嵌入) (None, 179, 300) 2729400 embedding_9_input[0][0]
embedding_10 (嵌入) (None, 179, 300) 2729400 embedding_10_input[0][0]
conv1d_8 (Conv1D) (None, 179, 100) 60100 embedding_8[0][0]
conv1d_9 (Conv1D) (None, 179, 100) 90100 embedding_9[0][0]
conv1d_10 (Conv1D) (None, 179, 100) 180100 embedding_10[0][0]
max_pooling1d_7 (MaxPooling1D) (None, 89, 100) 0 conv1d_8[0][0]
max_pooling1d_8 (MaxPooling1D) (None, 89, 100) 0 conv1d_9[0][0]
max_pooling1d_9 (MaxPooling1D) (None, 89, 100) 0 conv1d_10[0][0]
dropout_9 (Dropout) (None, 89, 100) 0 max_pooling1d_7[0][0]
dropout_10 (Dropout) (None, 89, 100) 0 max_pooling1d_8[0][0]
dropout_11 (Dropout) (None, 89, 100) 0 max_pooling1d_9[0][0]
lstm_2 (LSTM) (None, 80) 57920 dropout_9[0][0]
lstm_3 (LSTM) (None, 80) 57920 dropout_10[0][0]
lstm_4 (LSTM) (无, 80) 57920 dropout_11[0][0]
concatenate_3(连接)(无,240)0 lstm_2[0][0] lstm_3[0][0] lstm_4[0][0]
dropout_12(丢弃)(无,240)0 concatenate_3[0][0]
dense_3(密集)(无,270)65070 dropout_12[0][0]
dropout_13 (Dropout) (None, 270) 0 dense_3[0][0]
dense_4(密集)(无,7)1897 dropout_13[0][0]
activation_2(激活)(无,7)0 dense_4[0][0]
总参数:8,759,227 可训练参数:8,759,227 不可训练参数:0
CNN-RNN training and validation scores CNN-RNN confusion matrix
我知道神经网络没有神奇的公式,也没有一刀切的方法,我只是在我在实施 CNN-RNN 和 RNN 时可能犯错误的领域寻找一些指导。
对于任何格式错误,请提前致歉,因为这是我提出的第一个问题。如果需要任何其他信息,请告诉我。
非常感谢。
【问题讨论】:
【参考方案1】:首先,您的 CNN 实现过于热情,您是通过尝试多种设计得出的架构还是只是选择了它?
Usually, when multiple heads are chosen they are fed a slightly variation of the input, not the exact same copy so maybe your multi-head design is not the most optimal choice, it introduces too many unnecessary parameters and can lead to overfitting并且从您的损失曲线中可以看出。
你使用了分类交叉熵,但在 softmax 之后使用了 sigmoid,这也不是事情的完成方式。只需使用 softmax 激活并摆脱 sigmoid。
是测试集的混淆矩阵吗?然后,您的测试拆分似乎太容易了,因为模型过度拟合它应该表现不佳。因此,请确保没有太多相似的数据同时出现在训练和测试中,从而尝试找到更好的测试分割。
在使用复杂模型之前,最好先对简单模型进行微调。由于您的 LSTM 模型表现不佳,因此尝试更复杂的模型 (CNN-LSTM) 是没有意义的。你的 LSTM 模型没有收敛,原因可能很多(很明显是激活层使用不当)。
def make_model(lstm_units, dropout, hidden_units):
model = Sequential()
model.add(Embedding(input_dim = input_dim,
output_dim = output_dim,
weights = [embedding_matrix],
input_length = max_len,
trainable = False))
model.add(LSTM(lstm_units, return_sequences = True, recurrent_dropout = 0.2))
model.add(Dropout(dropout))
model.add(LSTM(lstm_units, recurrent_dropout = 0.2))
model.add(Dropout(dropout))
model.add(Dense(7, activation='softmax'))
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['acc'])
return model
通过摆脱 FC 层使其成为一个完全基于 LSTM 的模型,也可以从较小的 LSTM 单元开始,例如 8、16、32、...
要获得更多改进,您可以执行以下操作。
0) 摆脱手套嵌入并使用您自己的可学习嵌入。
1) 通过网络进行超参数搜索以找到最佳模型。
有很多库,但我发现这个非常灵活。 https://github.com/keras-team/keras-tuner
只需使用 pip 安装即可。
这是一个演示代码。
from tensorflow import keras
from tensorflow.keras import layers
from kerastuner.tuners import RandomSearch
def build_model(hp):
model = keras.Sequential()
model.add(layers.Embedding(input_dim=hp.Int('input_dim',
min_value=5000,
max_value=10000,
step = 1000),
output_dim=hp.Int('output_dim',
min_value=200,
max_value=800,
step = 100),
input_length = 400))
model.add(layers.Convolution1D(
filters=hp.Int('filters',
min_value=32,
max_value=512,
step = 32),
kernel_size=hp.Int('kernel_size',
min_value=3,
max_value=11,
step = 2),
padding='same',
activation='relu')),
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling1D())
model.add(layers.Flatten())
model.add(layers.Dropout(0.4))
model.add(layers.Dense(units=hp.Int('units',
min_value=64,
max_value=256,
step=32),
activation='relu'))
model.add(layers.Dropout(0.4))
model.add(layers.Dense(7, activation='softmax'))
model.compile(
optimizer=keras.optimizers.Adam(
hp.Choice('learning_rate',
values=[1e-2, 1e-3, 1e-4])),
loss='categorical_crossentropy',
metrics=['accuracy'])
return model
tuner = RandomSearch(
build_model,
objective='val_accuracy',
max_trials=5,
executions_per_trial=3,
directory='my_dir',
project_name='helloworld')
tuner.search_space_summary()
## The following lines are based on your model
tuner.search(x, y,
epochs=5,
validation_data=(val_x, val_y))
models = tuner.get_best_models(num_models=2)
如果您想提取更有意义的特征,我发现一种很有前途的方法是提取预训练的 BERT 特征,然后使用 CNN/LSTM 进行训练。
这是一个很好的入门存储库 - https://github.com/UKPLab/sentence-transformers
一旦您从 BERT/XLNet 获得句子嵌入,您就可以使用这些功能来训练另一个与您正在使用的 CNN 类似的 CNN,但可能会摆脱嵌入层,因为它很昂贵。
【讨论】:
经过多次反复试验,我想出了 CNN,这是表现最好的模型。当您说多头设计时,您指的是子模型的使用吗?您是正确的,混淆矩阵来自测试集。非常感谢,一些非常有用的反馈。 是的,子模型。也许子模型只是让你的模型变得庞大而且它过拟合了。对于数据集的规模,应该选择更简单的模型。 你尝试过我提供的更简单的 LSTM 模型吗?如果我的回答对您有帮助,请点赞/接受 :) 更简单的 CNN 绝对是对之前实现的改进,但更简单的 LSTM 仍在努力正确训练,准确率从未超过 16%。 这是一个好消息,LSTM 总是更难训练,可能没有足够的时间特征让 LSTM 找到。简单的 CNN 表现更好也表明,你的任务对于应用 LSTM 等固有的深层结构来说太简单了。【参考方案2】:我不能说这会解决您的所有问题,但绝对错误的是您在 softmax 激活后重复使用 sigmoid 激活,而您的分类问题有 7 个类别。 sigmoid 激活只能分离两个类。
例如:
model.add(Dense(7,activation='softmax', kernel_initializer="uniform"))
model.add(Activation('sigmoid'))
您应该只删除 sigmoid 激活您执行此操作的 3 次。
【讨论】:
以上是关于RNN 和 CNN-RNN 不能正确训练,总是预测一类的主要内容,如果未能解决你的问题,请参考以下文章
是否有一些用于时间序列预测的预训练 LSTM、RNN 或 ANN 模型?
一种预测单个值的结果但在 RNN 中对更广泛的输入数据进行训练和输入的方法? [关闭]