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

相关文章

来自专栏WOLFRAM

用 Mathematica 玩转环面

2365
来自专栏人工智能

利用神经网络算法的C#手写数字识别

尽管在过去几年已经有许多系统和分类算法被提出,但是手写识别任然是模式识别中的一项挑战。

1.2K11
来自专栏专知

【2018最新版】 200个最好的与机器学习、自然语言处理相关教程

【导读】近年来,机器学习等新最新技术层出不穷,如何跟踪最新的热点以及最新资源,作者Robbie Allen列出了一系列相关资源教程列表,包含四个主题:机器学习,...

1210
来自专栏郭耀华‘s Blog

Batch Normalization&Dropout浅析

一. Batch Normalization 对于深度神经网络,训练起来有时很难拟合,可以使用更先进的优化算法,例如:SGD+momentum、RMSProp、...

2816
来自专栏量化投资与机器学习

【Python机器学习】系列之特征提取与处理篇(深度详细附源码)

第1章 机器学习基础 将机器学习定义成一种通过学习经验改善工作效果的程序研究与设计过程。其他章节都以这个定义为基础,后面每一章里介绍的机器学习模型都是按照这个...

1.1K7
来自专栏我的python

char-rnn 语言模型

项目GitHub地址:https://github.com/ClownW/Char-RNN-Pytorch

4136
来自专栏小鹏的专栏

tf25: 使用深度学习做阅读理解+完形填空

记的在学生时代,英语考试有这么一种类型的题,叫:阅读理解。首先让你读一段洋文材料,然后回答一些基于这个洋文材料提的问题。 我先给你出一道阅读理解 Big ...

5475
来自专栏深度学习自然语言处理

【干货】GRU神经网络

前用的最多的三种神经网络是CNN,LSTM,GRU。其中,后两者都是RNN的变种,去年又给RNN发明了个SRU(优点是train RNN as fast as ...

44511
来自专栏量化投资与机器学习

【世界读书日】2018版十大引用数最高的深度学习论文集合

1313
来自专栏人工智能头条

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

2704

扫码关注云+社区