TF使用例子-LSTM实现序列标注

本文主要改写了一下"Sequence Tagging with Tensorflow"(https://link.jianshu.com?t=https://guillaumegenthial.github.io/sequence-tagging-with-tensorflow.html)程序。

原文是基于英文的命名实体识别(named entity recognition)问题,由于博主找不到相应的中文数据集(其实是没备份数据丢了,如果有同学提供,万分感谢)。因此,本文用了msra的分词数据(https://link.jianshu.com?t=http://sighan.cs.uchicago.edu/bakeoff2005/)。

另外,由于用到了词向量,所以用了搜狗实验室发布的2008新闻数据(https://link.jianshu.com?t=https://www.sogou.com/labs/resource/ca.php),提前训练了300维度的字向量(用的gensim包训练word2vector,另外后续可以尝试Glove)。

1、序列标注

序列标注就是给定一串序列,对序列中的每个元素做一个标记。比如我们希望识别一句话里面的人名,地名,组织机构名(命名实体识别)。有如下的句子:

琪斯美是日本的“东方project”系列弹幕游戏及其衍生作品的登场角色之一。

为每个字做标注之后的结果就是:

琪(B-PER)斯(I-PER)美(E-PER)是(O)日(B-LOC)本(E-LOC)的(O)“(O)东(B-ORG)方(I-ORG)project(E-ORG)”(O)系(O)列(O)弹(O)幕(O)游(O)戏(O)及(O)其(O)衍(O)生(O)作(O)品(O)的(O)登(O)场(O)角(O)色(O)之(O)一(O)。(O)*

这里标注采用的是BIEO,即Begin, Intermediate, End, Other(?我也不知道O是什么)

琪(B-PER)斯(I-PER)美(E-PER) 表示的含义就是 “琪”是人名开始,“斯”是人名中间的字,“美”是人名的末尾的字。其它符号同理。

这里可以看到,实际上就是用一串符号来标注出你感兴趣的部分。那么对于分词问题也是同理:

琪斯美是日本的“东方project”系列弹幕游戏及其衍生作品的登场角色之一。 琪斯美 是 日本 的 “ 东方project ” 系列 弹幕 游戏 及 其 衍生 作品 的 登场 角色 之一。 琪(B)斯(I)美 (E)是(S) 日(B)本(E) 的(S) “(S) 东(B)方(I)project(E) ”(S) 系(B)列(E) 弹(B)幕(E) 游(B)戏(E) 及(S) 其(S) 衍(B)生(E) 作(B)品(E) 的(S) 登(B)场(E) 角(B)色(E) 之(B)一(E)。(S)

当然,你可能想把“弹幕游戏”作为一个词,这取决于你如何标注这个数据,但是标注的时候要统一和规范。比如网上有PKU的数据标注规范(http://sighan.cs.uchicago.edu/bakeoff2005/data/pku_spec.pdf)。

其它比如像词性的标注都属于同一类问题。

2、常用方法

常用方法有MEMM (Maximum Entropy Markov Model)【1】,CRF (Conditional Random Field)【2】与 LSTM+CRF【3】。

【1】【2】原理待补充。 【3】类型的模型大致如图:

这是一个双向的LSTM,这里的英文单词可以类比成中文的字,在输出结果的时候再用crf对输出结果进行调整(排除不太可能的标注顺序)。

本文简单的用tensorflow实现了双向LSTM+CRF在中文文本分词上标注问题结果。

3、TF实现简单的序列标注

预处理

首先,我们需要为每个字建立一个id,另外可以设置一个阈值把出现次数小于该阈值的字用UNK(unknown)来统一表示。另外数字可以同义用NUM来代替。

然后我们把训练集按照6:3:1的比例分成训练集,验证集,测试集。并把格式整理成一列句子,一列标注。这里用的是BIEs标注方案。

李 B 元 E 与 s

卞 B 德 I 培 E 初 s 识 s 于 s 1 B 9 I 4 I 7 I 年 E 。 s

建模

这部分主要是翻译了原文。

由于tensorflow是batch处理数据样本的,所以我们需要对句子做padding,让它们一样长,所以我们需要先对其定义2个placeholders,一个表示句子,一个表示每个句子除去padding的实际长度:

#shape = (batch size, max length of sentence in batch)
word_ids = tf.placeholder(tf.int32, shape=[None, None])
#shape = (batch size)`
sequence_lengths = tf.placeholder(tf.int32, shape=[None])

假设embeddings是我们预先训练好的词向量,那么我么可以这样load词向量。

L = tf.Variable(embeddings, dtype=tf.float32, trainable=False)
# shape = (batch, sentence, word_vector_size)
pretrained_embeddings = tf.nn.embedding_lookup(L, word_ids)

这里trainable设置成False而不是tf.constant,否则会有内存问题。(另外如果不需要训练embedding层的话也没必要设置成True)

原文把每个英文单词作为一个词,并考虑了这个词当中的字母的特征,而我们这里直接只考虑每个字,所以省略了字母特征这一块。

一旦我们有了词的表示之后,我们只用跑一个LSTM或者bi-LSTM,得到另一串向量(LSTM的隐藏层,或者bi-LSTM的前向后向的隐藏层的组合)。

对于序列标注问题,前后字对于当前字的标注结果都会有影响,所以用双向的LSTM是很有意义的。这次我们用每个time step的隐藏层状态,代码如下:

word_embeddings = pretrained_embeddings
lstm_cell = tf.contrib.rnn.LSTMCell(hidden_size)

(output_fw, output_bw), _ = tf.nn.bidirectional_dynamic_rnn(lstm_cell, 
    lstm_cell, word_embeddings, sequence_length=sequence_lengths, 
    dtype=tf.float32)

context_rep = tf.concat([output_fw, output_bw], axis=-1)

解码

这一步,我们可以用两种方式来为每个tag打分:

方法一: 用softmax,然后argmax选择score值最大的那个tag,这种方法是基于字级别的。

方法二: 用条件随机场(Conditional Random Field, CRF)在句子层面做预测。

两种方法的目的都是为了让最后的序列标注结果的概率最大。先来计算scores:

W = tf.get_variable("W", shape=[2*self.config.hidden_size, self.config.ntags], 
                dtype=tf.float32)

b = tf.get_variable("b", shape=[self.config.ntags], dtype=tf.float32, 
                initializer=tf.zeros_initializer())

ntime_steps = tf.shape(context_rep)[1]
context_rep_flat = tf.reshape(context_rep, [-1, 2*hidden_size])
pred = tf.matmul(context_rep_flat, W) + b
scores = tf.reshape(pred, [-1, ntime_steps, ntags])

对于softmax, 实际上是使得每个字属于某个tag的概率最大,最后一串序列的结果就是序列中每个字的标注概率相乘得到的。这种结果都是局部的,也就是说某个字在标注的时候并没有考虑前后面字的标注结果的影响。

对于linear-chain CRF: 定义了一个全局的score,考虑了标注结果之间的转移。如下图:

如果我们不考虑转移情况,都选取局部最大的值,我们就会标注为PER-PER-LOC了。

训练

用CRF得到loss, 另外tf.contrib.crf.crf_log_likelihood还会返回转移矩阵T,方便我们在做预测的时候用它:

# shape = (batch, sentence)
labels = tf.placeholder(tf.int32, shape=[None, None], name="labels")

log_likelihood, transition_params = tf.contrib.crf.crf_log_likelihood(
scores, labels, sequence_lengths)

loss = tf.reduce_mean(-log_likelihood)

用local softmax的到的loss, 这里用mask过滤掉pad上去的token带来的loss:

losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=scores, labels=labels)
# shape = (batch, sentence, nclasses)
mask = tf.sequence_mask(sequence_lengths)
# apply mask
losses = tf.boolean_mask(losses, mask)

loss = tf.reduce_mean(losses)

最后定义我们的train_op:

optimizer = tf.train.AdamOptimizer(self.lr) train_op = optimizer.minimize(self.loss)

预测

对于local softmax直接选择每个time step最高的值就可以:

labels_pred = tf.cast(tf.argmax(self.logits, axis=-1), tf.int32)

对于CRF,传递一下训练时候得到的转移矩阵T,用viterbi的方法搜索到最优解即可:

# shape = (sentence, nclasses) score = ... viterbi_sequence, viterbi_score = tf.contrib.crf.viterbi_decode( score, transition_params

结果

楼主按照上述方法对msra的分词数据跑了60个epoch后在(验证集和测试集)上的准确率是96%左右,f1大概也在95%的样子。以下是分出来的结果:

琪斯美是日本的“东方project”系列弹幕游戏及其衍生作品的登场角色之一。 ['B', 'I', 'E', 's', 'B', 'E', 's', 's', 'B', 'E', 'B', 'I', 'I', 'I', 'I', 'I', 'E', 's', 'B', 'E', 'B', 'E', 'B', 'E', 'B', 'E', 'B', 'E', 'B', 'E', 's', 'B', 'E', 'B', 'E', 'B', 'E', 's']

附分词实例代码

戳这里(https://link.jianshu.com?t=https://github.com/Slyne/tf_tagging.git) 数据见README

附keras实现的简易版本代码

keras官方版本目前还木有实现crf层,但是网上有同学自己实现了,戳这里(https://link.jianshu.com?t=https://github.com/phipleg/keras/blob/crf/keras/layers/crf.py)

例子:

n_words = 10000
maxlen = 32
(X_train, y_train), (X_test, y_test) = load_treebank(nb_words=n_words, maxlen=maxlen)

n_samples, n_steps, n_classes = y_train.shape

model = Sequential()
model.add(Embedding(n_words, 128, input_length=maxlen, dropout=0.2))
model.addBidirectional(LSTM(64, dropout_W=0.2, dropout_U=0.2, return_sequences=True),merge_mode='concat'))
model.add(Dropout(0.2))
model.add(TimeDistributed(Dense(n_classes)))
model.add(Dropout(0.2))
crf = ChainCRF()
model.add(crf)
model.compile(loss=crf.loss, optimizer='rmsprop', metrics=['accuracy'])

local softmax的代码如下:

model = Sequential()
# keras.layers.embeddings.Embedding(input_dim, output_dim, init='uniform', input_length=None, W_regularizer=None, activity_regularizer=None, W_constraint=None, mask_zero=False, weights=None, dropout=0.0)
model.add(Embedding(max_features, 128, dropout=0.2))
model.add(Bidirectional(LSTM(64, dropout_W=0.2, dropout_U=0.2, return_sequences=True),merge_mode='concat'))  # try using a GRU instead, for fun
model.add(TimeDistributed(Dense(num_class)))
model.add(Activation('softmax'))


# try using different optimizers and different optimizer configs
model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
model.fit(X_train, y_train, batch_size=batch_size, nb_epoch=50,
          validation_data=(X_test, y_test))
score, acc = model.evaluate(X_test, y_test,
                            batch_size=batch_size)

相关文献

【1】McCallum, Andrew, Dayne Freitag, and Fernando CN Pereira. "Maximum Entropy Markov Models for Information Extraction and Segmentation."Icml. Vol. 17. 2000.

【2】Lafferty, John, Andrew McCallum, and Fernando Pereira. "Conditional random fields: Probabilistic models for segmenting and labeling sequence data."Proceedings of the eighteenth international conference on machine learning, ICML. Vol. 1. 2001.

【3】Huang, Zhiheng, Wei Xu, and Kai Yu. "Bidirectional LSTM-CRF models for sequence tagging."arXiv preprint arXiv:1508.01991(2015).

原文发布于微信公众号 - 人工智能LeadAI(atleadai)

原文发表时间:2017-12-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AI研习社

用 OpenCV 检测图像中各物体大小

在图像中测量物体的大小与计算从相机到物体之间的距离是相似的,在这两种情况下,我们需要定义一个比值,它测量每个给定指标的像素个数。

1731
来自专栏大数据文摘

利用 Scikit Learn的Python数据预处理实战指南

2586
来自专栏机器学习算法全栈工程师

TensorFlow模拟简单线性模型小栗子

作者:赵 慧 编辑:李文臣 TensorFlow是什么 官方英文介绍:TensorFlow™ is an open source software libra...

2707
来自专栏计算机视觉战队

神经网络介绍—利用反向传播算法的模式学习

神经网络也许是计算机计算的将来,一个了解它的好方法是用一个它可以解决的难题来说明。假设给出 500 个字符的代码段,您知道它们是C,C++,JAVA或Pytho...

2528
来自专栏Python数据科学

数据分析实战—北京二手房房价分析(建模篇)

本篇将继续上一篇数据分析之后进行数据挖掘建模预测,这两部分构成了一个简单的完整项目。结合两篇文章通过数据分析和挖掘的方法可以达到二手房屋价格预测的效果。

872
来自专栏CaiRui

基于MATLAB的多项式数据拟合方法研究-毕业论文

1633
来自专栏人工智能LeadAI

时间序列异常检测 EGADS Surus iForest

时间序列异常检测 (原文链接:http://wurui.cc/tech/time-series-anomaly-detection/) 本文总结了我在时间序列异...

8434
来自专栏企鹅号快讯

使用RNN预测股票价格系列一

正文共11490个字,16张图,预计阅读时间:29分钟。 01 概述 我们将解释如何建立一个有LSTM单元的RNN模型来预测S&P500指数的价格。 数据集可以...

2349
来自专栏专知

【专知-Java Deeplearning4j深度学习教程04】使用CNN进行文本分类:图文+代码

【导读】主题链路知识是我们专知的核心功能之一,为用户提供AI领域系统性的知识学习服务,一站式学习人工智能的知识,包含人工智能( 机器学习、自然语言处理、计算机视...

1.5K6
来自专栏新智元

中国团队夺得MegaFace百万人脸识别冠军,精度98%再创记录,论文代码+数据全开源

MegaFace数据集 网络结构 首先,我们尝试在人脸识别的任务上找到一个优秀的网络结构。 3.1 网络输入设定 在我们所有的实验当中,都根据人脸的 5 个...

77510

扫码关注云+社区