之前学习过了,MLP,CNN,所以RNN也不能落下。下面以经典数据集IMDB来训练一个RNN模型。IMDB是25,000条影评数据,被标记为正面/负面两种评价。影评已被预处理为词下标构成的序列。关于词下标构成的序列,即是将词从词典中查找对应的索引构成的序列。
eg:
上图即表示了一个序列样本,序列中的数字代表词在词典中的索引,若以该索引去词典中查找对应的词,那么该序列就表示了一段文本,以该序列作为模型的输入,会经过模型的embed层来获取词向量的作为RNN层的输入。
library(keras)
vocab_size <- 10000 # 设置词典大小
vec_len <- 32 # 词向量长度
max_word <- 300 # 最大序列长度
hidden_num <- 32 # 隐藏结点数
n_class <- 2 # 类别数目
# 加载获取数据
imdb_data <- dataset_imdb(num_words = vocab_size, maxlen = max_word)
# 训练数据
x_train <- pad_sequences(imdb_data$train$x, maxlen = max_word)
y_train <- to_categorical(imdb_data$train$y)
# 测试数据
x_test <- pad_sequences(imdb_data$test$x, maxlen = max_word)
y_test <- to_categorical(imdb_data$test$y)
keras自带了IMDB数据集,通过dataset_imdb函数调用加载获取数据,通过pad_sequences函数将不定长序列,补齐为统一长度,这里设置最大序列长度为300。
# 定义模型
model <- keras_model_sequential()
model %>%
layer_embedding(input_dim = vocab_size, output_dim = vec_len, input_length = max_word) %>%
layer_simple_rnn(units = hidden_num, return_sequences = TRUE) %>%
layer_simple_rnn(units = hidden_num, return_sequences = FALSE) %>%
layer_dense(units = n_class, activation = "softmax")
模型定义很简单,通过%>%连通符,一行代码就搞定了。这里设置了两层simple_rnn,第一层全序列输出,第二层只输出序列的最后一个,后面接softmax层来进行文本分类任务。(softmax层只是以softmax作为神经元的激活函数,即本质还是常规层)
通过model %>% summary()来查看模型结构:
# 设置模型损失函数和优化器
model %>% compile(
loss = 'categorical_crossentropy',
optimizer = optimizer_adam(lr = 0.001),
metrics = c('accuracy')
)
# 训练模型
history <- model %>% fit(
x_train, y_train,
epochs = 30,
batch_size = 100,
validation_split = 0.2,
verbose = 1
)
模型以为交叉熵作为损失函数,选择Adam进行优化,设置epochs=30,batch_size=100。
上图所示,损失函数在验证集上的从第三次迭代就开始上升了,准确率在第七次迭代后就趋向稳定。最终在测试集合上的准确率为 85% 。事实上simple_rnn 在处理序列问题时候会遇到梯度爆炸等问题,因此可以选用lstm来代替它,以layer_lstm函数代替layer_simple_rnn函数即可。同时因为经过几次迭代后,在验证集上模型效果没有再提升,因此模型并不需要那么多轮迭代,可以通过在fit函数当中设置callbacks = callback_early_stopping(patience = 2),让模型后续训练在准确率没有提升时终止训练。
如图所示,模型在没有提升的情况下停止迭代,采用lstm作为循环层在测试集上的准确率达到了86%,比之前采用simple_rnn作为循环层提升了1%。