1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > HAN文本分类与tensorflow实现

HAN文本分类与tensorflow实现

时间:2022-10-10 02:47:16

相关推荐

HAN文本分类与tensorflow实现

1. 引言

前面介绍了LSTM_CNN、RCNN等文本分类方法,这几种方法都是将整个文本进行encoder,但是我们知道,RNN对于长句子或长文档其实编码能力是有限的,会丢失掉句子中的信息,因此,为了克服这个问题,Nikolaos等人在提出了一个新的文本分类模型——HAN,该模型采用层叠的RNN,对文本的句子级别进行encoder,并且引入了神经机器翻译中的注意力机制,可以有效地解决长句子的分类难问题,本文将对该模型进行具体介绍,并用tensorflow来实现它。

论文地址:《Multilingual Hierarchical Attention Networks for Document Classification》

2. HAN模型结构介绍

2.1 HAN模型结构

HAN模型总共包含三层,分别是词汇层、句子层和输出层,其中词汇层和句子层都包含一层encoder和一层attention层。其结构如图1所示。

图1 HAN模型结构

2.1.1 词汇层

在词汇层,HAN首先将文本分为个句子,每个句子的长度为,然后在词汇层,每个句子分别会进入一层encoder层和一层attention层,最终得到该句子的一个向量表示。记数据集为,其中表示数据集的大小,表示文本的词汇序列,表示文本的标签。

在encoder时,记为encoder层的变换函数,则对于一个文本中的第个句子,经过encoder后得到每个时间步的隐藏状态为:

这里的encoder可以是GRU或LSTM,也可以是双向的GRU或LSTM,如果是选择双向的GRU或LSTM时,则需要对前向和后向的隐藏状态进行拼接:

接着,对于每个句子得到隐藏状态,会经过一层attention层,attention层会对各个时间步的隐藏状态进行加权平均,从而得到该句子的向量表示,其计算公式如下:

其中,是一个全连接层,其权重矩阵为,也是一个权重向量,其权重的计算方式其实优点类似Luong Attention中的general方式。最终,词汇层将得到个句子向量,其向量维度与encoder的隐藏层维度一致或者是隐藏层维度的2倍(双向RNN)。

2.1.2 句子层

当词汇层结束后,原文本的序列长度由变为了,其实相当于对文本的长度起到了一个压缩的过程,在句子层,也同样包含一层encoder层和一层attention层,其计算的逻辑与词汇层的完全一样,这里不再赘述,其计算公式如下:

其中,也是一个全连接层,其权重矩阵为,也是一个权重向量。

2.1.2 输出层

最后,将得到的文本向量传入一个带有sigmoid的全连接层,即可得到文本最终的类别概率分布:

其中,分别为全连接层的权重矩阵和偏置项。

3. HAN的tensorflow实现

本文利用tensorflow对HAN模型进行了复现,并在情感分析的数据集上进行了测试。模型的代码如下:

import osimport numpy as npimport tensorflow as tffrom eval.evaluate import accuracy,Macro_F1from tensorflow.contrib import slimfrom loss.loss import cross_entropy_lossclass HAN(object):def __init__(self,num_classes,seq_length,vocab_size,embedding_dim,learning_rate,learning_decay_rate,learning_decay_steps,epoch,dropout_keep_prob,rnn_type,hidden_dim,num_sentences):self.num_classes = num_classesself.seq_length = seq_lengthself.vocab_size = vocab_sizeself.embedding_dim = embedding_dimself.learning_rate = learning_rateself.learning_decay_rate = learning_decay_rateself.learning_decay_steps = learning_decay_stepsself.epoch = epochself.dropout_keep_prob = dropout_keep_probself.rnn_type = rnn_typeself.hidden_dim = hidden_dimself.num_sentences = num_sentencesself.input_x = tf.placeholder(tf.int32, [None, self.seq_length], name='input_x')self.input_y = tf.placeholder(tf.float32, [None, self.num_classes], name='input_y')self.model()def model(self):# 词向量映射with tf.name_scope("embedding"):input_x = tf.split(self.input_x, self.num_sentences, axis=1)# shape:[None,self.num_sentences,self.sequence_length/num_sentences]input_x = tf.stack(input_x, axis=1)embedding = tf.get_variable("embedding", [self.vocab_size, self.embedding_dim])# [None,num_sentences,sentence_length,embed_size]embedding_inputs = tf.nn.embedding_lookup(embedding, input_x)# [batch_size*num_sentences,sentence_length,embed_size]sentence_len = int(self.seq_length / self.num_sentences)embedding_inputs_reshaped = tf.reshape(embedding_inputs,shape=[-1, sentence_len, self.embedding_dim])# 词汇层with tf.name_scope("word_encoder"):(output_fw, output_bw) = self.bidirectional_rnn(embedding_inputs_reshaped, "word_encoder")# [batch_size*num_sentences,sentence_length,hidden_size * 2]word_hidden_state = tf.concat((output_fw, output_bw), 2)with tf.name_scope("word_attention"):# [batch_size*num_sentences, hidden_size * 2]sentence_vec = self.attention(word_hidden_state, "word_attention")# 句子层with tf.name_scope("sentence_encoder"):# [batch_size,num_sentences,hidden_size*2]sentence_vec = tf.reshape(sentence_vec, shape=[-1, self.num_sentences,self.hidden_dim * 2])output_fw, output_bw = self.bidirectional_rnn(sentence_vec, "sentence_encoder")# [batch_size*num_sentences,sentence_length,hidden_size * 2]sentence_hidden_state = tf.concat((output_fw, output_bw), 2)with tf.name_scope("sentence_attention"):# [batch_size, hidden_size * 2]doc_vec = self.attention(sentence_hidden_state, "sentence_attention")# Add dropoutwith tf.name_scope("dropout"):h_drop = tf.nn.dropout(doc_vec, self.dropout_keep_prob)# 输出层with tf.name_scope("output"):# 分类器self.logits = tf.layers.dense(h_drop, self.num_classes, name='fc2')self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits), 1, name="pred") # 预测类别# 损失函数self.loss = cross_entropy_loss(logits=self.logits, labels=self.input_y)# 优化函数self.global_step = tf.train.get_or_create_global_step()learning_rate = tf.train.exponential_decay(self.learning_rate, self.global_step,self.learning_decay_steps, self.learning_decay_rate,staircase=True)optimizer = tf.train.AdamOptimizer(learning_rate)update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)self.optim = slim.learning.create_train_op(total_loss=self.loss, optimizer=optimizer, update_ops=update_ops)# 准确率self.acc = accuracy(logits=self.logits, labels=self.input_y)# self.acc = Macro_F1(logits=self.logits,labels=self.input_y)def rnn_cell(self):"""获取rnn的cell,可选RNN、LSTM、GRU"""if self.rnn_type == "vanilla":return tf.nn.rnn_cell.BasicRNNCell(self.hidden_dim)elif self.rnn_type == "lstm":return tf.nn.rnn_cell.BasicLSTMCell(self.hidden_dim)elif self.rnn_type == "gru":return tf.nn.rnn_cell.GRUCell(self.hidden_dim)else:raise Exception("rnn_type must be vanilla、lstm or gru!")def bidirectional_rnn(self,inputs, name):with tf.variable_scope(name):fw_cell = self.rnn_cell()fw_cell = tf.nn.rnn_cell.DropoutWrapper(fw_cell, output_keep_prob=self.dropout_keep_prob)bw_cell = self.rnn_cell()bw_cell = tf.nn.rnn_cell.DropoutWrapper(bw_cell, output_keep_prob=self.dropout_keep_prob)(output_fw, output_bw), states = tf.nn.bidirectional_dynamic_rnn(cell_fw=fw_cell,cell_bw=bw_cell,inputs=inputs,dtype=tf.float32)return output_fw, output_bwdef attention(self,inputs, name):with tf.variable_scope(name):# 采用general形式计算权重hidden_vec = tf.layers.dense(inputs, self.hidden_dim * 2, activation=tf.nn.tanh, name='w_hidden')u_context = tf.Variable(tf.truncated_normal([self.hidden_dim * 2]), name='u_context')alpha = tf.nn.softmax(tf.reduce_sum(tf.multiply(hidden_vec, u_context),axis=2, keep_dims=True), dim=1)# 对隐藏状态进行加权attention_output = tf.reduce_sum(tf.multiply(inputs, alpha), axis=1)return attention_outputdef fit(self, train_x, train_y, val_x, val_y, batch_size):# 创建模型保存路径if not os.path.exists('./saves/han'): os.makedirs('./saves/han')if not os.path.exists('./train_logs/han'): os.makedirs('./train_logs/han')# 开始训练train_steps = 0best_val_acc = 0# summarytf.summary.scalar('val_loss', self.loss)tf.summary.scalar('val_acc', self.acc)merged = tf.summary.merge_all()# 初始化变量sess = tf.Session()writer = tf.summary.FileWriter('./train_logs/han', sess.graph)saver = tf.train.Saver(max_to_keep=10)sess.run(tf.global_variables_initializer())for i in range(self.epoch):batch_train = self.batch_iter(train_x, train_y, batch_size)for batch_x, batch_y in batch_train:train_steps += 1feed_dict = {self.input_x: batch_x, self.input_y: batch_y}_, train_loss, train_acc = sess.run([self.optim, self.loss, self.acc], feed_dict=feed_dict)if train_steps % 1000 == 0:feed_dict = {self.input_x: val_x, self.input_y: val_y}val_loss, val_acc = sess.run([self.loss, self.acc], feed_dict=feed_dict)summary = sess.run(merged, feed_dict=feed_dict)writer.add_summary(summary, global_step=train_steps)if val_acc >= best_val_acc:best_val_acc = val_accsaver.save(sess, "./saves/han/", global_step=train_steps)msg = 'epoch:%d/%d,train_steps:%d,train_loss:%.4f,train_acc:%.4f,val_loss:%.4f,val_acc:%.4f'print(msg % (i, self.epoch, train_steps, train_loss, train_acc, val_loss, val_acc))sess.close()def batch_iter(self, x, y, batch_size=32, shuffle=True):"""生成batch数据:param x: 训练集特征变量:param y: 训练集标签:param batch_size: 每个batch的大小:param shuffle: 是否在每个epoch时打乱数据:return:"""data_len = len(x)num_batch = int((data_len - 1) / batch_size) + 1if shuffle:shuffle_indices = np.random.permutation(np.arange(data_len))x_shuffle = x[shuffle_indices]y_shuffle = y[shuffle_indices]else:x_shuffle = xy_shuffle = yfor i in range(num_batch):start_index = i * batch_sizeend_index = min((i + 1) * batch_size, data_len)yield (x_shuffle[start_index:end_index], y_shuffle[start_index:end_index])def predict(self, x):sess = tf.Session()sess.run(tf.global_variables_initializer())saver = tf.train.Saver(tf.global_variables())ckpt = tf.train.get_checkpoint_state('./saves/han/')saver.restore(sess, ckpt.model_checkpoint_path)feed_dict = {self.input_x: x}logits = sess.run(self.logits, feed_dict=feed_dict)y_pred = np.argmax(logits, 1)return y_pred

在训练时,文本的最大长度设置为165,每个句子的长度设置为15,即总共有11个句子,RNN的维度设置为200,其他参数与之前FastText的设置一样,最终模型在验证集上的效果如图2所示,在经过230000次迭代后,模型在验证集上的准确率达到最高,为94.85%,在3000个测试集上的准确率是97.57%,效果没有RCNN好。

图2 HAN模型在验证集的效果

4. 总结

以上就是HAN的模型介绍,其实整体上模型没有什么特别的地方,只是采用了层叠的结构,另外,原论文中的公式和符号我觉得多处写的有错误(也可能是笔者的水平有限,理解错了),因此,本文的公式是笔者按照自己的理解写的,如有不对之处还请见谅。最终,对HAN模型也做个总结吧:

HAN模型引入了层叠RNN和注意力机制,在一定程度上可以缓解长文本的编码问题。将文本转化为句子级别,对句子的长度起到一个压缩的作用。HAN按照固定的长度对句子进行分割,对一些关联性比较强的词汇可能会被拆解成两个句子,虽然句子层可以考虑到前后句子的序列信息,但是笔者觉得对于一些词汇的序列信息还是会有所丢失。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。