基于双向LSTM的seq2seq字标注

25 篇文章 0 订阅

事不宜迟,动手最重要。词向量维度用了128,句子长度截断为32(抛弃了多于32字的样本,这部分样本很少,事实上,用逗号、句号等天然分隔符分开后,句子很少有多于32字的。)。这次我用了5tag,在原来的4tag的基础上,加上了一个x标签,用来表示不够32字的部分,比如句子是20字的,那么第21~32个标签均为x。

在数据方面,我用了Bakeoff 2005的语料中微软亚洲研究院(Microsoft Research)提供的部分。代码如下,如果有什么不清晰的地方,欢迎留言。

-- coding:utf-8 --

import re
import numpy as np
import pandas as pd

s = open(‘msr_train.txt’).read().decode(‘gbk’)
s = s.split(‘\r\n’)

def clean(s): #整理一下数据,有些不规范的地方
if u’“/s’ not in s:
return s.replace(u’ ”/s’, ”)
elif u’”/s’ not in s:
return s.replace(u’“/s ‘, ”)
elif u’‘/s’ not in s:
return s.replace(u’ ’/s’, ”)
elif u’’/s’ not in s:
return s.replace(u’‘/s ‘, ”)
else:
return s

s = u”.join(map(clean, s))
s = re.split(u’[,。!?、]/[bems]’, s)

data = [] #生成训练样本
label = []
def get_xy(s):
s = re.findall(‘(.)/(.)’, s)
if s:
s = np.array(s)
return list(s[:,0]), list(s[:,1])

for i in s:
x = get_xy(i)
if x:
data.append(x[0])
label.append(x[1])

d = pd.DataFrame(index=range(len(data)))
d[‘data’] = data
d[‘label’] = label
d = d[d[‘data’].apply(len) <= maxlen]
d.index = range(len(d))
tag = pd.Series({’s’:0, ‘b’:1, ‘m’:2, ‘e’:3, ‘x’:4})

chars = [] #统计所有字,跟每个字编号
for i in data:
chars.extend(i)

chars = pd.Series(chars).value_counts()
chars[:] = range(1, len(chars)+1)

生成适合模型输入的格式

from keras.utils import np_utils
d[‘x’] = d[‘data’].apply(lambda x: np.array(list(chars[x])+[0]*(maxlen-len(x))))
d[‘y’] = d[‘label’].apply(lambda x: np.array(map(lambda y:np_utils.to_categorical(y,5), tag[x].reshape((-1,1)))+[np.array([[0,0,0,0,1]])]*(maxlen-len(x))))

设计模型

word_size = 128
maxlen = 32
from keras.layers import Dense, Embedding, LSTM, TimeDistributed, Input, Bidirectional
from keras.models import Model

sequence = Input(shape=(maxlen,), dtype=’int32’)
embedded = Embedding(len(chars)+1, word_size, input_length=maxlen, mask_zero=True)(sequence)
blstm = Bidirectional(LSTM(64, return_sequences=True), merge_mode=’sum’)(embedded)
output = TimeDistributed(Dense(5, activation=’softmax’))(blstm)
model = Model(input=sequence, output=output)
model.compile(loss=’categorical_crossentropy’, optimizer=’adam’, metrics=[‘accuracy’])

batch_size = 1024
history = model.fit(np.array(list(d[‘x’])), np.array(list(d[‘y’])).reshape((-1,maxlen,5)), batch_size=batch_size, nb_epoch=50)

转移概率,单纯用了等概率

zy = {‘be’:0.5,
‘bm’:0.5,
‘eb’:0.5,
‘es’:0.5,
‘me’:0.5,
‘mm’:0.5,
‘sb’:0.5,
‘ss’:0.5
}

zy = {i:np.log(zy[i]) for i in zy.keys()}

def viterbi(nodes):
paths = {‘b’:nodes[0][‘b’], ‘s’:nodes[0][’s’]}
for l in range(1,len(nodes)):
paths_ = paths.copy()
paths = {}
for i in nodes[l].keys():
nows = {}
for j in paths_.keys():
if j[-1]+i in zy.keys():
nows[j+i]= paths_[j]+nodes[l][i]+zy[j[-1]+i]
k = np.argmax(nows.values())
paths[nows.keys()[k]] = nows.values()[k]
return paths.keys()[np.argmax(paths.values())]

def simple_cut(s):
if s:
r = model.predict(np.array([list(chars[list(s)].fillna(0).astype(int))+[0]*(maxlen-len(s))]), verbose=False)[0][:len(s)]
r = np.log(r)
nodes = [dict(zip([’s’,’b’,’m’,’e’], i[:4])) for i in r]
t = viterbi(nodes)
words = []
for i in range(len(s)):
if t[i] in [’s’, ‘b’]:
words.append(s[i])
else:
words[-1] += s[i]
return words
else:
return []

not_cuts = re.compile(u’([\da-zA-Z ]+)|[。,、?!.\?,!]’)
def cut_word(s):
result = []
j = 0
for i in not_cuts.finditer(s):
result.extend(simple_cut(s[j:i.start()]))
result.append(s[i.start():i.end()])
j = i.end()
result.extend(simple_cut(s[j:]))
return result
我们可以用model.summary()看一下模型的结构。

model.summary()


Layer (type) Output Shape Param # Connected to

input_2 (InputLayer) (None, 32) 0


embedding_2 (Embedding) (None, 32, 128) 660864 input_2[0][0]


bidirectional_1 (Bidirectional) (None, 32, 64) 98816 embedding_2[0][0]


timedistributed_2 (TimeDistribute) (None, 32, 5) 325 bidirectional_1[0][0]

Total params: 760005


最终的模型结果如何?我不打算去对比那些评测结果了,现在的模型在测试上达到90%以上的准确率不是什么难事。我关心的是对新词的识别和对歧义的处理。下面是一些测试结果(随便选的):

RNN 的 意思 是 , 为了 预测 最后 的 结果 , 我 先 用 第一个 词 预测 , 当然 , 只 用 第一个 预测 的 预测 结果 肯定 不 精确 , 我 把 这个 结果 作为 特征 , 跟 第二词 一起 , 来 预测 结果 ; 接着 , 我 用 这个 新 的 预测 结果 结合 第三词 , 来 作 新 的 预测 ; 然后 重复 这个 过程 。

结婚 的 和 尚未 结婚 的

苏剑林 是 科学 空间 的 博主 。

广东省 云浮市 新兴县

魏则西 是 一 名 大学生

这 真是 不堪入目 的 环境

列夫·托尔斯泰 是 俄罗斯 一 位 著名 的 作家

保加利亚 首都 索非亚 是 全国 政治 、 经济 、 文化中心 , 位于 保加利亚 中 西部

罗斯福 是 第二次世界大战 期间 同 盟国 阵营 的 重要 领导人 之一 。 1941 年 珍珠港 事件发生 后 , 罗斯 福力 主对 日本 宣战 , 并 引进 了 价格 管制 和 配给 。 罗斯福 以 租 借 法案 使 美国 转变 为 “ 民主 国家 的 兵工厂 ” , 使 美国 成为 同 盟国 主要 的 军火 供应商 和 融资 者 , 也 使得 美国 国内 产业 大幅 扩张 , 实现 充分 就业 。 二战 后期 同 盟国 逐渐 扭转 形势 后 , 罗斯福 对 塑造 战后 世界 秩序 发挥 了 关键 作用 , 其 影响 力 在 雅尔塔 会议 及 联合国 的 成立 中 尤其 明显 。 后来 , 在 美国 协助 下 , 盟军 击败 德国 、 意大利 和 日本 。

可以发现,测试结果是很乐观的。不论是人名(中国人名或外国人名)还是地名,识别效果都很好。关于这个模型,目前就说到这里,以后会继续深入的。

最后
事实上本文是提供了一个框架,能够直接通过双向LSTM对序列进行标注,给出完整的标注序列。这种标注的思路,可以用于很多任务,如词性标注、实体识别,因此,基于双向LSTM的seq2seq标注思路,有很广的应用,值得研究。甚至最近热门的深度学习的机器翻译,都是用这种序列到序列的模型实现的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用TensorFlow实现基于LSTM-CRF的序列标注的模型代码示例: ``` import tensorflow as tf from tensorflow.contrib.crf import crf_log_likelihood, viterbi_decode class BiLSTM_CRF(object): def __init__(self, num_chars, num_tags, embedding_dim, hidden_dim, batch_size=64, learning_rate=0.001): self.num_chars = num_chars self.num_tags = num_tags self.embedding_dim = embedding_dim self.hidden_dim = hidden_dim self.batch_size = batch_size self.learning_rate = learning_rate self.inputs = tf.placeholder(tf.int32, shape=[None, None], name='inputs') self.targets = tf.placeholder(tf.int32, shape=[None, None], name='targets') self.seq_length = tf.placeholder(tf.int32, shape=[None], name='seq_length') self._build_model() self._build_loss() self._build_optimizer() self.sess = tf.Session() self.sess.run(tf.global_variables_initializer()) def _build_model(self): # 定义词嵌入层 self.word_embeddings = tf.Variable(tf.random_uniform([self.num_chars, self.embedding_dim], -1.0, 1.0), name='word_embeddings') embeddings = tf.nn.embedding_lookup(self.word_embeddings, self.inputs) # 定义双向LSTM层 cell_fw = tf.contrib.rnn.LSTMCell(self.hidden_dim) cell_bw = tf.contrib.rnn.LSTMCell(self.hidden_dim) (output_fw, output_bw), _ = tf.nn.bidirectional_dynamic_rnn(cell_fw, cell_bw, embeddings, dtype=tf.float32, sequence_length=self.seq_length) output = tf.concat([output_fw, output_bw], axis=-1) # 定义全连接层 W = tf.get_variable('W', shape=[2*self.hidden_dim, self.num_tags], initializer=tf.contrib.layers.xavier_initializer()) b = tf.get_variable('b', shape=[self.num_tags], initializer=tf.zeros_initializer()) output = tf.reshape(output, [-1, 2*self.hidden_dim]) logits = tf.matmul(output, W) + b self.logits = tf.reshape(logits, [-1, tf.shape(self.inputs)[1], self.num_tags]) # 定义CRF层 self.transition_params = tf.get_variable('transition_params', shape=[self.num_tags, self.num_tags], initializer=tf.contrib.layers.xavier_initializer()) def _build_loss(self): log_likelihood, self.transition_params = crf_log_likelihood(inputs=self.logits, tag_indices=self.targets, sequence_lengths=self.seq_length, transition_params=self.transition_params) self.loss = tf.reduce_mean(-log_likelihood) def _build_optimizer(self): self.optimizer = tf.train.AdamOptimizer(self.learning_rate).minimize(self.loss) def train(self, inputs, targets, seq_length): feed_dict = {self.inputs: inputs, self.targets: targets, self.seq_length: seq_length} _, loss = self.sess.run([self.optimizer, self.loss], feed_dict=feed_dict) return loss def predict(self, inputs, seq_length): feed_dict = {self.inputs: inputs, self.seq_length: seq_length} logits, transition_params = self.sess.run([self.logits, self.transition_params], feed_dict=feed_dict) viterbi_sequences = [] for logit, length in zip(logits, seq_length): logit = logit[:length] viterbi_seq, _ = viterbi_decode(logit, transition_params) viterbi_sequences.append(viterbi_seq) return viterbi_sequences ``` 这里实现了一个包含词嵌入、双向LSTM、全连接和CRF层的模型,并使用Adam优化器进行训练。在训练过程中,需要传入输入序列、目标序列和序列长度;在预测过程中,只需要传入输入序列和序列长度即可得到预测的标注序列。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值