【NLP傻瓜式教程】手把手带你RNN文本分类(附代码)
文章來源于NewBeeNLP,作者kaiyuan
寫在前面
這是NLP傻瓜式教程的第二篇----基于RNN的文本分類實現(Text RNN)
參考的的論文是來自2016年復旦大學IJCAI上的發表的關于循環神經網絡在多任務文本分類上的應用:Recurrent Neural Network for Text Classification with Multi-Task Learning[1]
論文概覽
在先前的許多工作中,模型的學習都是基于單任務,對于復雜的問題,也可以分解為簡單且相互獨立的子問題來單獨解決,然后再合并結果,得到最初復雜問題的結果。這樣做看似合理,其實是不正確的,因為現實世界中很多問題不能分解為一個一個獨立的子問題,即使可以分解,各個子問題之間也是相互關聯的,通過一些共享因素或「共享表示(share representation)」 聯系在一起。把現實問題當做一個個獨立的單任務處理,往往會忽略了問題之間所富含的豐富的關聯信息。
上面的問題引出了本文的重點——「多任務學習(Multi-task learning)」,把多個相關(related)的任務(task)放在一起學習。多個任務之間共享一些因素,它們可以在學習過程中,共享它們所學到的信息,這是單任務學習沒有具備的。相關聯的多任務學習比單任務學習能去的更好的泛化(generalization)效果。本文基于 RNN 循環神經網絡,提出三種不同的信息共享機制,整體網絡是基于所有的任務共同學習得到。
下圖展示的是單任務學習和多任務學習的流程圖,可以對比一下區別。
下面具體介紹一下文章中的三個模型。
Model I: Uniform-Layer Architecture
在他們提出的第一個模型中,不同的任務共享一個LSTM網絡層和一個embedding layer,此外每個任務還有其自己的embedding layer。所以對于上圖中的任務m,輸入x包括了兩個部分:
其中等號右側第一項和第二項分別表示該任務「特有」的word embedding和該模型中「共享」的word embedding,兩者做一個concatenation。
LSTM網絡層是所有任務所共享的,對于任務m的最后sequence representation為LSTM的輸出:
Model II: Coupled-Layer Architecture
在第二個模型中,為每個任務都指定了「特定」的LSTM layer,但是不同任務間的LSTM layer可以共享信息。
為了更好地控制在不同LSTM layer之間的信息流動,作者提出了一個global gating unit,使得模型具有決定信息流動程度的能力。
為此,他們改寫了LSTM中的表達式:
其中,
Model III: Shared-Layer Architecture
與模型二相似,作者也為每個單獨的任務指派了特定的LSTM層,但是對于整體的模型使用了雙向的LSTM,這樣可以使得信息共享更為準確。
模型表現
論文作者在4個數據集上對上述模型做了評價,并和其他state-of-the-art的網絡模型進行了對比,均顯示最好的效果。
代碼實現
RNN的代碼框架和上一篇介紹的CNN類似,首先定義一個RNN類來實現論文中的模型
class RNN(BaseModel):"""A RNN class for sentence classificationWith an embedding layer + Bi-LSTM layer + FC layer + softmax"""def __init__(self, sequence_length, num_classes, vocab_size,embed_size, learning_rate, decay_steps, decay_rate,hidden_size, is_training, l2_lambda, grad_clip,initializer=tf.random_normal_initializer(stddev=0.1)):這里的模型包括了一層embedding,一層雙向LSTM,一層全連接層最后接上一個softmax分類函數。
然后依次定義模型,訓練,損失等函數在后續調用。
def inference(self):"""1. embedding layer2. Bi-LSTM layer3. concat Bi-LSTM output4. FC(full connected) layer5. softmax layer"""# embedding layerwith tf.name_scope('embedding'):self.embedded_words = tf.nn.embedding_lookup(self.Embedding, self.input_x)# Bi-LSTM layerwith tf.name_scope('Bi-LSTM'):lstm_fw_cell = rnn.BasicLSTMCell(self.hidden_size)lstm_bw_cell = rnn.BasicLSTMCell(self.hidden_size)if self.dropout_keep_prob is not None:lstm_fw_cell = rnn.DropoutWrapper(lstm_fw_cell, output_keep_prob=self.dropout_keep_prob)lstm_bw_cell = rnn.DropoutWrapper(lstm_bw_cell, output_keep_prob=self.dropout_keep_prob)outputs, output_states = tf.nn.bidirectional_dynamic_rnn(lstm_fw_cell, lstm_bw_cell,self.embedded_words,dtype=tf.float32)output = tf.concat(outputs, axis=2)output_last = tf.reduce_mean(output, axis=1)# FC layerwith tf.name_scope('output'):self.score = tf.matmul(output_last, self.W_projection) + self.b_projectionreturn self.scoredef loss(self):# losswith tf.name_scope('loss'):losses = tf.nn.softmax_cross_entropy_with_logits(labels=self.input_y, logits=self.score)data_loss = tf.reduce_mean(losses)l2_loss = tf.add_n([tf.nn.l2_loss(cand_v) for cand_v in tf.trainable_variables()if 'bias' not in cand_v.name]) * self.l2_lambdadata_loss += l2_lossreturn data_lossdef train(self):learning_rate = tf.train.exponential_decay(self.learning_rate, self.global_step,self.decay_steps, self.decay_rate, staircase=True)optimizer = tf.train.AdamOptimizer(learning_rate)grads_and_vars = optimizer.compute_gradients(self.loss_val)grads_and_vars = [(tf.clip_by_norm(grad, self.grad_clip), val) for grad, val in grads_and_vars]train_op = optimizer.apply_gradients(grads_and_vars, global_step=self.global_step)return train_op訓練部分的數據集這里就直接采用CNN那篇文章相同的數據集(懶...),預處理的方式與函數等都是一樣的,,,
def train(x_train, y_train, vocab_processor, x_dev, y_dev):with tf.Graph().as_default():session_conf = tf.ConfigProto(# allows TensorFlow to fall back on a device with a certain operation implementedallow_soft_placement= FLAGS.allow_soft_placement,# allows TensorFlow log on which devices (CPU or GPU) it places operationslog_device_placement=FLAGS.log_device_placement)sess = tf.Session(config=session_conf)with sess.as_default():# initialize cnnrnn = RNN(sequence_length=x_train.shape[1],num_classes=y_train.shape[1],vocab_size=len(vocab_processor.vocabulary_),embed_size=FLAGS.embed_size,l2_lambda=FLAGS.l2_reg_lambda,is_training=True,grad_clip=FLAGS.grad_clip,learning_rate=FLAGS.learning_rate,decay_steps=FLAGS.decay_steps,decay_rate=FLAGS.decay_rate,hidden_size=FLAGS.hidden_size)# output dir for models and summariestimestamp = str(time.time())out_dir = os.path.abspath(os.path.join(os.path.curdir, 'run', timestamp))if not os.path.exists(out_dir):os.makedirs(out_dir)print('Writing to {} \n'.format(out_dir))# checkpoint dir. checkpointing – saving the parameters of your model to restore them later on.checkpoint_dir = os.path.abspath(os.path.join(out_dir, FLAGS.ckpt_dir))checkpoint_prefix = os.path.join(checkpoint_dir, 'model')if not os.path.exists(checkpoint_dir):os.makedirs(checkpoint_dir)saver = tf.train.Saver(tf.global_variables(), max_to_keep=FLAGS.num_checkpoints)# Write vocabularyvocab_processor.save(os.path.join(out_dir, 'vocab'))# Initialize allsess.run(tf.global_variables_initializer())def train_step(x_batch, y_batch):"""A single training step:param x_batch::param y_batch::return:"""feed_dict = {rnn.input_x: x_batch,rnn.input_y: y_batch,rnn.dropout_keep_prob: FLAGS.dropout_keep_prob}_, step, loss, accuracy = sess.run([rnn.train_op, rnn.global_step, rnn.loss_val, rnn.accuracy],feed_dict=feed_dict)time_str = datetime.datetime.now().isoformat()print("{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))def dev_step(x_batch, y_batch):"""Evaluate model on a dev setDisable dropout:param x_batch::param y_batch::param writer::return:"""feed_dict = {rnn.input_x: x_batch,rnn.input_y: y_batch,rnn.dropout_keep_prob: 1.0}step, loss, accuracy = sess.run([rnn.global_step, rnn.loss_val, rnn.accuracy],feed_dict=feed_dict)time_str = datetime.datetime.now().isoformat()print("dev results:{}: step {}, loss {:g}, acc {:g}".format(time_str, step, loss, accuracy))# generate batchesbatches = data_process.batch_iter(list(zip(x_train, y_train)), FLAGS.batch_size, FLAGS.num_epochs)# training loopfor batch in batches:x_batch, y_batch = zip(*batch)train_step(x_batch, y_batch)current_step = tf.train.global_step(sess, rnn.global_step)if current_step % FLAGS.validate_every == 0:print('\n Evaluation:')dev_step(x_dev, y_dev)print('')path = saver.save(sess, checkpoint_prefix, global_step=current_step)print('Save model checkpoint to {} \n'.format(path))def main(argv=None):x_train, y_train, vocab_processor, x_dev, y_dev = prepocess()train(x_train, y_train, vocab_processor, x_dev, y_dev)if __name__ == '__main__':tf.app.run()「完整代碼可以在公眾號后臺回復"RNN2016"獲取。」
本文參考資料
[1]
Recurrent Neural Network for Text Classification with Multi-Task Learning: https://arxiv.org/abs/1605.05101
-?END?-
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習在線手冊深度學習在線手冊AI基礎下載(pdf更新到25集)備注:加入本站微信群或者qq群,請回復“加群”獲取一折本站知識星球優惠券,請回復“知識星球”喜歡文章,點個在看
總結
以上是生活随笔為你收集整理的【NLP傻瓜式教程】手把手带你RNN文本分类(附代码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【NLP傻瓜式教程】手把手带你CNN文本
- 下一篇: 疫情之下,将业务迁移至云端会是一个正确的