专栏首页人工智能LeadAITF使用例子-LSTM实现序列标注

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)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • TensorFlow从0到1丨第十六篇 L2正则化对抗“过拟合”

    前面的第十四篇 交叉熵损失函数——防止学习缓慢和第十五篇 重新思考神经网络初始化从学习缓慢问题入手,尝试改进神经网络的学习。本篇讨论过拟合问题,并引入与之相对的...

    用户1332428
  • 构建并用 TensorFlow Serving 部署 Wide & Deep 模型

    用户1332428
  • 简单的TensorFlow分类教程

    本篇文章有2个topic,简单的分类器和TensorFlow。首先,我们会编写函数生成三种类别的模拟数据。第一组数据是线性可分的,第二种是数据是月牙形数据咬合在...

    用户1332428
  • Tensorflow ActiveFunction激活函数解析

    输入参数:● features: 一个Tensor。数据类型必须是:float32,float64,int32,int64,uint8,int16,int8。●...

    DrawSky
  • tensorflow入门:Logistic Regression

    g(z)=11+e−zg(z) = \frac{1}{1+e^{-z}}g(z)=1+e−z1​

    Steve Wang
  • tf12: 判断男声女声

    本帖训练一个简单的神经网络模型,用来判断声音是男是女。 本帖数据集取自voice-gender项目,这个项目使用了n种分类模型,并比较了准确率,但是它没有使...

    MachineLP
  • 《机器学习实战:基于Scikit-Learn、Keras和TensorFlow》第12章 使用TensorFlow自定义模型并训练

    目前为止,我们只是使用了TensorFlow的高级API —— tf.keras,它的功能很强大:搭建了各种神经网络架构,包括回归、分类网络、Wide & De...

    SeanCheney
  • tf.decode_raw

    原链接: https://tensorflow.google.cn/versions/r1.8/api_docs/python/tf/decode_raw?h...

    于小勇
  • Task 3 使用TensorFlow

    对于一个被研究的物体,它有多个属性(x1, x2, ... xn)和一个值y。线性回归假设y与(x1, x2, ... xn)有线性关系,也就是我们可以把y表示...

    平凡的学生族
  • 基于tensorflow的手写数字分类预测kaggle实战

    kaggle网站手写数字分类的比赛链接:https://www.kaggle.com/c/digit-recognizer 注册账号后才能参加kaggle比赛...

    潇洒坤

扫码关注云+社区

领取腾讯云代金券