开源项目kcws代码分析--基于深度学习的分词技术
http://blog.csdn.net/pirage/article/details/53424544
分词原理
本小节内容参考待字闺中的两篇博文:
简单的说,kcws的分词原理就是:
- 对语料进行处理,使用word2vec对语料的字进行嵌入,每个字特征为50维。
- 得到字嵌入后,用字嵌入特征喂给双向LSTM, 对输出的隐层加一个线性层,然后加一个CRF就得到本文实现的模型。
- 于最优化方法,文本语言模型类的貌似Adam效果更好, 对于分类之类的,貌似AdaDelta效果更好。
另外,字符嵌入的表示可以是纯预训练的,但也可以在训练模型的时候再fine-tune,一般而言后者效果更好。对于fine-tune的情形,可以在字符嵌入后,输入双向LSTM之前加入dropout进一步提升模型效果。
具体的解决方案,基于双向LSTM与CRF的神经网络结构:
如上图所示,单层圆圈代表的word embedding输入层,菱形代表学习输入的决定性方程,双层圆圈代表随机变量。信息流将输入层的word embedding送到双向LSTM, l(i)代表word(i)和从左边传入的历史信号,r(i)代表word(i)以及从右边传入的未来的信号,用c(i)连接这两个向量的信息,代表词word(i)。
先说Bi-LSTM这个双向模型,LSTM的变种有很多,基本流程都是一样的,文献中同样采用上一节说明的三个门,控制送入memory cell的输入信息,以及遗忘之前阶段信息的比例,再对细胞状态进行更新,最后用tanh方程对更新后的细胞状态再做处理,与sigmoid叠加相乘作为最终输出。具体的模型公式见下图,经过上一节的解释,这些符号应该不太陌生了。
双向LSTM说白了,就是先从左至右,顺序学习输入词序列的历史信息,再从右至左,学习输入词序列未来影响现在的信息,结合这两种方式的最终表示有效地描述了词的内容,在多种标注应用上取得了好效果。
如果说双向LSTM并不特殊,这个结构中另一个新的尝试,就是将深度神经网络最后学出来的结果,作为特征,用CRF模型连接起来,用P来表示双向LSTM神经网络学习出来的打分输出矩阵,它是一个 nxk 的矩阵,n是输入词序列个数,k是标记类型的数目, P(ij)指的是在一个输入句子中,第i个词在第j个tag标记上的可能性(打分)。另外一个特征函数是状态转移矩阵 A,A(ij) 代表从tag i转移到tag j的可能性(打分),但这个转移矩阵实际上有k+2维,其中包括句子的开始和结束两个状态,用公式表示如下图:
在给定输入序列X,最终定义的输出y序列的概率,则使用softmax函数表示如下:
而在训练学习目标函数的时候,要优化的就是下面这个预测输出标记序列的log概率,其中 Y(X)代表的是所有可能的tag标记序列集合,那么最后学习得到的输出,就是概率最大的那个标记序列。如果只是模拟输出的bigram交互影响方式,采用动态规划即可求解下列方程。
至此,基于双向LSTM与CRF的神经网络结构已经介绍完毕,文献中介绍的是在命名实体识别方面的一个实践应用,这个思路同样可以用在分词上。具体的实践和调参,也得应场景而异,Koth在上一篇博客中已经给出了自己的践行,读者们可以借鉴参考。
代码结构与实践
koth大神开源的项目地址为:https://github.com/koth/kcws
主要的代码在目录kcws/kcws/train路径下。(写这篇文章的时候发现K大神做了更新,我主要还是分析之前的代码)
- process_anno_file.py 将人民网2014训练语料进行单字切割,包括标点符号
- generate_training.py 生成单字的vector之后,处理每篇训练语料,以“。”划分为句子,对每个句子,如果长度大于MAX_LEN(默认设置为80,在代码里改),不处理,Long lines加一,如果长度小于MAX_LEN,则生成长度为160的vector,前80为单字在字典中的index,不足80的补0,后80为每个字对应的SBME标记(S表示单字,B表示开始,M表示中间,E表示结尾)。
- filter_sentence.py 将语料切分为训练集和测试集,作者将含有两个字以下的句子过滤掉,剩下的按照二八分,测试集最多8000篇。
- train_cws_lstm.py 主要训练代码。
作者在项目主页上很详细的写了构建和训练的步骤,按照这些步骤来实践一下不算难,我主要遇到了以下几个问题:
- 之前安装tensorflow的时候没有用bazel,不了解bazel的工作方式和原理,但是这个项目必须要用,因为需要用到third_party中word2vec的类。(可以将word2vec的某些类构建为python可以import的类?)
- 已安装的0.8.0版本的tensorflow没有实现crf,需要升级。
- 安装tensorflow 0.11.0版本后运行,出现PyUnicodeUCS4_AsUTF8String的错误,查找后发现是当前安装的python默认是unicode=ucs2,需要重新编译安装python。编译的时候设置./configure –enable-unicode=ucs4 。
- numpy,scipy都需要重新build,setup。
主要代码分析
def main(unused_argv):
curdir = os.path.dirname(os.path.realpath(__file__))
trainDataPath = tf.app.flags.FLAGS.train_data_path
if not trainDataPath.startswith("/"):
trainDataPath = curdir + "/" + trainDataPath
graph = tf.Graph()
with graph.as_default():
model = Model(FLAGS.embedding_size, FLAGS.num_tags,
FLAGS.word2vec_path, FLAGS.num_hidden)
print("train data path:", trainDataPath)
# 读取训练集batch大小的feature和label,各为80大小的数组
X, Y = inputs(trainDataPath)
# 读取测试集所有数据的feature和label,各为80大小的数组
tX, tY = do_load_data(tf.app.flags.FLAGS.test_data_path)
# 计算训练集的损失
total_loss = model.loss(X, Y)
# 使用AdamOptimizer优化方法
train_op = train(total_loss)
# 在测试集上做评测
test_unary_score, test_sequence_length = model.test_unary_score()
# 创建Supervisor管理模型的分布式训练
sv = tf.train.Supervisor(graph=graph, logdir=FLAGS.log_dir)
with sv.managed_session(master='') as sess:
# actual training loop
training_steps = FLAGS.train_steps
for step in range(training_steps):
if sv.should_stop():
break
try:
_, trainsMatrix = sess.run(
[train_op, model.transition_params])
# for debugging and learning purposes, see how the loss gets decremented thru training steps
if step % 100 == 0:
print("[%d] loss: [%r]" % (step, sess.run(total_loss)))
if step % 1000 == 0:
test_evaluate(sess, test_unary_score,
test_sequence_length, trainsMatrix,
model.inp, tX, tY)
except KeyboardInterrupt, e:
sv.saver.save(sess,
FLAGS.log_dir + '/model',
global_step=step + 1)
raise e
sv.saver.save(sess, FLAGS.log_dir + '/finnal-model')
sess.close()
1
Class Model:
def __init__(self, embeddingSize, distinctTagNum, c2vPath, numHidden):
self.embeddingSize = embeddingSize
self.distinctTagNum = distinctTagNum
self.numHidden = numHidden
self.c2v = self.load_w2v(c2vPath)
self.words = tf.Variable(self.c2v, name="words")
with tf.variable_scope('Softmax') as scope:
self.W = tf.get_variable(
shape=[numHidden * 2, distinctTagNum],
initializer=tf.truncated_normal_initializer(stddev=0.01),
name="weights",
regularizer=tf.contrib.layers.l2_regularizer(0.001))
self.b = tf.Variable(tf.zeros([distinctTagNum], name="bias"))
self.trains_params = None
self.inp = tf.placeholder(tf.int32,
shape=[None, FLAGS.max_sentence_len],
name="input_placeholder")
pass
def length(self, data):
used = tf.sign(tf.reduce_max(tf.abs(data), reduction_indices=2))
length = tf.reduce_sum(used, reduction_indices=1)
length = tf.cast(length, tf.int32)
return length
def inference(self, X, reuse=None, trainMode=True):
word_vectors = tf.nn.embedding_lookup(self.words, X) # 按照X顺序返回self.words中的第X行,返回的结果组成tensor。
length = self.length(word_vectors)
# length是shape为[batch_size]大小值为句子长度的vector
length_64 = tf.cast(length, tf.int64)
if trainMode: # 训练的时候启用dropout,测试的时候关键dropout
word_vectors = tf.nn.dropout(word_vectors, 0.5) # 将word_vectors按照50%的概率丢弃某些词,tf增加的一个处理是将其余的词scale 1/0.5
with tf.variable_scope("rnn_fwbw", reuse=reuse) as scope:
forward_output, _ = tf.nn.dynamic_rnn(
tf.nn.rnn_cell.LSTMCell(self.numHidden),
word_vectors,
dtype=tf.float32,
sequence_length=length,
scope="RNN_forward")
backward_output_, _ = tf.nn.dynamic_rnn(
tf.nn.rnn_cell.LSTMCell(self.numHidden),
inputs=tf.reverse_sequence(word_vectors,
length_64,
seq_dim=1),
# 训练和测试的时候,inputs的格式不同。训练时,tensor shape是[batch_size, max_time,input_size]
# 测试时,tensor shape是[max_time,batch_size,input_size].
# tf.reverse_sequence作用就是指定在列上操作(batch_dim表示按行操作)
dtype=tf.float32,
sequence_length=length,
scope="RNN_backword")
# tf.nn.dynamic_rnn(cell, inputs, sequence_length,time_major,...)主要参数:
# cell:搭建好的网络,这里用LSTMCell(num_cell),num_cell表示一个lstm单元输出的维数(100)
# inputs:word_vectors,它的shape由time_major决定,默认是false,即[batch_size,max_time,input_size],如果是测试
# 过程,time_major设置为True,shape为[max_time,batch_size,input_size],这里直接做了reverse,省去了time_major设置。
# 其中,batch_size=100, max_time=80句子最大长度,input_size字的向量的长度。
# sequence_length:shape[batch_size]大小的值为句子最大长度的tensor。
# 输出:
# outputs:[batch_size, max_time, cell.output_size]
# state: shape取决于LSTMCell中state_size的设置,返回Tensor或者tuple。
backward_output = tf.reverse_sequence(backward_output_,
length_64,
seq_dim=1)
# 这里的reverse_sequence同上。
output = tf.concat(2, [forward_output, backward_output])
# 连接两个三维tensor,2表示按照列连接(0表示纵向,1表示行)
# 连接后,output的shape:[batch_size, max_time, 2*cell.output_size],即[100, 80, 2*50]
output = tf.reshape(output, [-1, self.numHidden * 2])
# reshape后,output的shape:[batch_size, self.numHidden * 2],即[100, 200]
matricized_unary_scores = tf.batch_matmul(output, self.W)
# 得到未归一化的CRF输出
# 点乘W的shape[ 100*2, 4],生成[batch_size, 4]大小的matricized_unary_scores
unary_scores = tf.reshape(
matricized_unary_scores,
[-1, FLAGS.max_sentence_len, self.distinctTagNum])
# reshape后,unary_scores大小为[batch_size,80, 4]
return unary_scores, length
def loss(self, X, Y):
P, sequence_length = self.inference(X)
# CRF损失计算,训练的时候使用,测试的时候用viterbi解码
log_likelihood, self.transition_params = tf.contrib.crf.crf_log_likelihood(
P, Y, sequence_length)
# crf_log_likelihood参数(inputs,tag_indices, sequence_lengths)
# inputs:大小为[100, 80, 4]的tensor,CRF层的输入
# tag_indices:大小为[100, 80]的矩阵
# sequence_length:大小 [100]值为80的向量。
# 输出:
# log_likelihood:[batch_size]大小的vector,log-likelihood值
# transition_params:[4,4]大小的矩阵
loss = tf.reduce_mean(-log_likelihood)
return loss
def load_w2v(self, path): #返回(num+2)*50大小的二维矩阵,其中第一行全是0,最后一行是每个词向量维度的平均值。
fp = open(path, "r")
print("load data from:", path)
line = fp.readline().strip()
ss = line.split(" ")
total = int(ss[0])
dim = int(ss[1])
assert (dim == (FLAGS.embedding_size))
ws = []
mv = [0 for i in range(dim)]
# The first for 0
ws.append([0 for i in range(dim)])
for t in range(total):
line = fp.readline().strip()
ss = line.split(" ")
assert (len(ss) == (dim + 1))
vals = []
for i in range(1, dim + 1):
fv = float(ss[i])
mv[i - 1] += fv
vals.append(fv)
ws.append(vals)
for i in range(dim):
mv[i] = mv[i] / total
ws.append(mv)
fp.close()
return np.asarray(ws, dtype=np.float32)
def test_unary_score(self):
P, sequence_length = self.inference(self.inp,
reuse=True,
trainMode=False)
return P, sequence_length
其他的代码比较简单,个人觉得不必要做深入分析。
总结
近一两个月开始学习TensorFlow,代码看了一些,但是总感觉临门差那么一脚。革命尚未完成,同志们仍需努力
开源项目kcws代码分析--基于深度学习的分词技术的更多相关文章
- 开源项目(9-0)综述--基于深度学习的目标跟踪sort与deep-sort
基于深度学习的目标跟踪sort与deep-sort https://github.com/Ewenwan/MVision/tree/master/3D_Object_Detection/Object_ ...
- 基于深度学习的人脸性别识别系统(含UI界面,Python代码)
摘要:人脸性别识别是人脸识别领域的一个热门方向,本文详细介绍基于深度学习的人脸性别识别系统,在介绍算法原理的同时,给出Python的实现代码以及PyQt的UI界面.在界面中可以选择人脸图片.视频进行检 ...
- 一个基于深度学习回环检测模块的简单双目 SLAM 系统
转载请注明出处,谢谢 原创作者:Mingrui 原创链接:https://www.cnblogs.com/MingruiYu/p/12634631.html 写在前面 最近在搞本科毕设,关于基于深度学 ...
- 【课程学习】课程2:十行代码高效完成深度学习POC
本文用户记录黄埔学院学习的心得,并补充一些内容. 课程2:十行代码高效完成深度学习POC,主讲人为百度深度学习技术平台部:陈泽裕老师. 因为我是CV方向的,所以内容会往CV方向调整一下,有所筛检. 课 ...
- 基于深度学习的回声消除系统与Pytorch实现
文章作者:凌逆战 文章代码(pytorch实现):https://github.com/LXP-Never/AEC_DeepModel 文章地址(转载请指明出处):https://www.cnblog ...
- 基于深度学习的建筑能耗预测01——Anaconda3-4.4.0+Tensorflow1.7+Python3.6+Pycharm安装
基于深度学习的建筑能耗预测-2021WS-02W 一,安装python及其环境的设置 (写python代码前,在电脑上安装相关必备的软件的过程称为环境搭建) · 完全可以先安装anaconda(会自带 ...
- 【OCR技术系列之四】基于深度学习的文字识别(3755个汉字)
上一篇提到文字数据集的合成,现在我们手头上已经得到了3755个汉字(一级字库)的印刷体图像数据集,我们可以利用它们进行接下来的3755个汉字的识别系统的搭建.用深度学习做文字识别,用的网络当然是CNN ...
- 深度学习与计算机视觉(12)_tensorflow实现基于深度学习的图像补全
深度学习与计算机视觉(12)_tensorflow实现基于深度学习的图像补全 原文地址:Image Completion with Deep Learning in TensorFlow by Bra ...
- 基于深度学习的中文语音识别系统框架(pluse)
目录 声学模型 GRU-CTC DFCNN DFSMN 语言模型 n-gram CBHG 数据集 本文搭建一个完整的中文语音识别系统,包括声学模型和语言模型,能够将输入的音频信号识别为汉字. 声学模型 ...
随机推荐
- Codeforces Round #394 (Div. 2) C. Dasha and Password 暴力
C. Dasha and Password 题目连接: http://codeforces.com/contest/761/problem/C Description After overcoming ...
- 使用 IntraWeb (7) - 主模板
TIWLayoutMgrHTML.TIWTemplateProcessorHTML 属于页面级的模板, 如果要全站统一模板, 当然要用主模板. TIWTemplateProcessorHTML 通过其 ...
- Digital adjustment of LM317
- SWD 接口电路
- Visio中如何绘制黑白图像
- TStream实现多表提交
TStream实现多表提交 function TynFiredac.SaveDatas(const ATableName, ATableName2: string; ADeltas: TStream; ...
- Java Deadlock Example and How to analyze deadlock situation
source:http://www.journaldev.com/1058/java-deadlock-example-and-how-to-analyze-deadlock-situation De ...
- Xcode 5中非常期待的6个功能
这里是新特征汇总博文链接:iOS7新特征汇总 小引: 自从北京时间2013年06月11日苹果发布Xcode 5 Developer Preview 1,到现在(2013年7约15日)已经过去一个月,苹 ...
- C#编程小结----集合的小小总结
集合的小结 以上文章介绍了如何处理不同类型的集合,数组的大小是固定的,但可以使用列表作为动态增长的集合.队列以先进先出的方式访问元素.栈以后进先出的方式访问元素.链表可以快速的插入和删除元素,但搜索操 ...
- SharePoint 内容编辑器部件介绍
前言 在SharePoint的使用过程中,我们经常会往页面中插入一些东西,这时候很可能就需要内容编辑器部件了.比如:插HTML.插样式.插脚本.插图片,统统都拿来,用内容编辑器部件. 正文 使用内容编 ...