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 条评论
登录 后参与评论

相关文章

来自专栏人工智能LeadAI

文本数据处理的终极指南-[NLP入门]

简介 实现任何程度或者级别的人工智能所必需的最大突破之一就是拥有可以处理文本数据的机器。值得庆幸的是,全世界文本数据的数量在最近几年已经实现指数级增长。这也迫切...

3596
来自专栏小鹏的专栏

TensorFlow 聊天机器人

上一次提到了不错的学习聊天机器人的资源,不知道小伙伴们有没有去学习呢。 自己动手做聊天机器人教程 我最近每天都会学一点,拿出解读来和大家分享一下。 本文结...

2646
来自专栏PaddlePaddle

转载|PaddleFluid和TensorFlow基本使用概念对比

介绍:Paddle Fluid 是用来让用户像 PyTorch 和 Tensorflow Eager Execution 一样执行程序。在这些系统中,不再有模型...

1042

用 Python 进行时间序列数据可视化

随时间推移的观测数据的线图可视化是很受欢迎,但还有一些其他类型的图像您可以用来了解关于您的问题的更多信息。

1.7K8
来自专栏Tencentcloud

交互式数字作品教程——专为艺术家的你!

这篇文章不是对循环神经网络的综合概述。它适用于没有任何机器学习背景的读者。其目的是向艺术家和设计师展示如何使用预先训练的神经网络——使用简单的Javascrip...

2076
来自专栏大数据挖掘DT机器学习

基于Xgboost + LR + Keras 建模评估用户信用状态

项目背景 拍拍贷“魔镜风控系统”基于400多个数据维度来对当前用户的信用状态进行评估,通过历史数据每个借款人的性别、年龄、籍贯、学历信息、通讯方式、网站登录...

3094
来自专栏CSDN技术头条

递归神经网络不可思议的有效性

递归神经网络(RNNs)有一些不可思议的地方。我仍然记得我训练的第一个用于图片字幕的递归网络。从花几十分钟训练我的第一个婴儿模型(相当随意挑选的超参数)开始,到...

1889
来自专栏段石石的专栏

自然语言处理基础技术之分词、向量化、词性标注

前段时间,因为项目需求, 开始接触了 NLP,中文分词、词向量、词性标注, 这三块是前段时间项目上有用到过,所以稍做总结与大家分享下,只有更极致地深入了解才能学...

8154
来自专栏racaljk

A星寻路算法(A* Search Algorithm)

你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢?

993
来自专栏PPV课数据科学社区

用 TensorFlow 做个聊天机器人

本文结构: 聊天机器人的架构简图 用 TensorFlow 实现 Chatbot 的模型 如何准备 chatbot 的训练数据 Chatbot 源码解读 1. ...

2613

扫描关注云+社区