基于双向BiLstm神经网络的中文分词详解及源码

  • 基于双向BiLstm神经网络的中文分词详解及源码

    • 1 标注序列
    • 2 训练网络
    • 3 Viterbi算法求解最优路径
    • 4 keras代码讲解
    • 最后
    • 源代码地址

在自然语言处理中(NLP,Natural Language ProcessingNLP,Natural Language Processing),分词是一个较为简单也基础的基本技术。常用的分词方法包括这两种:基于字典的机械分词基于统计序列标注的分词。对于基于字典的机械分词本文不再赘述,可看字典分词方法。在本文中主要讲解基于深度学习的分词方法及原理,包括一下几个步骤:1标注序列2双向LSTM网络预测标签3Viterbi算法求解最优路径

1 标注序列

中文分词的第一步便是标注字,字标注是通过给句子中每个字打上标签的思路来进行分词,比如之前提到过的,通过4标签来进行标注(single,单字成词;begin,多字词的开头;middle,三字以上词语的中间部分;end,多字词的结尾。均只取第一个字母。),这样,“为人民服务”就可以标注为“sbebe”了。4标注不是唯一的标注方式,类似地还有6标注,理论上来说,标注越多会越精细,理论上来说效果也越好,但标注太多也可能存在样本不足的问题,一般常用的就是4标注和6标注。前面已经提到过,字标注是通过给句子中每个字打上标签的思路来进行分词,比如之前提到过的,通过4标签来进行标注(single,单字成词;begin,多字词的开头;middle,三字以上词语的中间部分;end,多字词的结尾。均只取第一个字母。),这样,“为人民服务”就可以标注为“sbebe”了。4标注不是唯一的标注方式,类似地还有6标注,理论上来说,标注越多会越精细,理论上来说效果也越好,但标注太多也可能存在样本不足的问题,一般常用的就是4标注和6标注。标注实例如下:

人/b  们/e  常/s  说/s  生/b  活/e  是/s  一/s  部/s  教/b  科/m  书/e 

2 训练网络

这里所指的网络主要是指神经网络,再细化一点就是双向LSTM(长短时记忆网络),双向LSTM是LSTM的改进版,LSTM是RNN的改进版。因此,首先需要理解RNN。

RNN的意思是,为了预测最后的结果,我先用第一个词预测,当然,只用第一个预测的预测结果肯定不精确,我把这个结果作为特征,跟第二词一起,来预测结果;接着,我用这个新的预测结果结合第三词,来作新的预测;然后重复这个过程;直到最后一个词。这样,如果输入有n个词,那么我们事实上对结果作了n次预测,给出了n个预测序列。整个过程中,模型共享一组参数。因此,RNN降低了模型的参数数目,防止了过拟合,同时,它生来就是为处理序列问题而设计的,因此,特别适合处理序列问题。循环神经网络原理见下图:

LSTM对RNN做了改进,使得能够捕捉更长距离的信息。但是不管是LSTM还是RNN,都有一个问题,它是从左往右推进的,因此后面的词会比前面的词更重要,但是对于分词这个任务来说是不妥的,因为句子各个字应该是平权的。因此出现了双向LSTM,它从左到右做一次LSTM,然后从右到左做一次LSTM,然后把两次结果组合起来。

在分词中,LSTM可以根据输入序列输出一个序列,这个序列考虑了上下文的联系,因此,可以给每个输出序列接一个softmax分类器,来预测每个标签的概率。基于这个序列到序列的思路,我们就可以直接预测句子的标签。假设每次输入y1

-yn由下图所示每个输入所对应的标签为x1-xn。再抽象一点用$ x_{ij} 表示状态

x_i$的第j个可能值。

最终输出结果串联起来形成如下图所示的网络

图中从第一个可能标签到最后一个可能标签的任何一条路径都可能产生一个新的序列,每条路径可能性不一样,我们需要做的是找出可能的路径。由于路径非常多,因此采用穷举是非常耗时的,因此引入Viterbi算法。

3 Viterbi算法求解最优路径

维特比算法是一个特殊但应用最广的动态规划算法,利用动态规划,可以解决任何一个图中的最短路径问题。而维特比算法是针对一个特殊的图——篱笆网络的有向图(Lattice )的最短路径问题而提出的。

而维特比算法的精髓就是,既然知道到第i列所有节点Xi{j=123…}的最短路径,那么到第i+1列节点的最短路径就等于到第i列j个节点的最短路径+第i列j个节点到第i+1列各个节点的距离的最小值,关于维特比算法的详细可以点击

4 keras代码讲解

使用Keras构建bilstm网络,在keras中已经预置了网络模型,只需要调用相应的函数就可以了。需要注意的是,对于每一句话会转换为词向量(Embedding)如下图所示:

embedded = Embedding(len(chars) + 1, word_size, input_length=maxlen, mask_zero=True)(sequence)并将不足的补零。

创建网络

from keras.layers import Dense, Embedding, LSTM, TimeDistributed, Input, Bidirectional
from keras.models import Modeldef create_model(maxlen, chars, word_size):""":param maxlen::param chars::param word_size::return:"""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)return model

训练数据

# -*- coding:utf-8 -*-import re
import numpy as np
import pandas as pd# 设计模型
word_size = 128
maxlen = 32with open('data/msr_train.txt', 'rb') as inp:texts = inp.read().decode('gbk')
s = texts.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 ss = 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)# 保存数据
import picklewith open('model/chars.pkl', 'wb') as outp:pickle.dump(chars, outp)
print('** Finished saving the data.')# 生成适合模型输入的格式
from keras.utils import np_utilsd['x'] = d['data'].apply(lambda x: np.array(list(chars[x]) + [0] * (maxlen - len(x))))def trans_one(x):_ = map(lambda y: np_utils.to_categorical(y, 5), tag[x].reshape((-1, 1)))_ = list(_)_.extend([np.array([[0, 0, 0, 0, 1]])] * (maxlen - len(x)))return np.array(_)d['y'] = d['label'].apply(trans_one)import lstm_modelmodel = lstm_model.create_model(maxlen, chars, word_size)
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=20, verbose=2)
model.save('model/model.h5')

1080显卡训练每次需要耗时44s,训练20个epoch后精度达到95%

测试

import pickle
import lstm_model
import pandas as pdwith open('model/chars.pkl', 'rb') as inp:chars = pickle.load(inp)
word_size = 128
maxlen = 32model = lstm_model.create_model(maxlen, chars, word_size)
model.load_weights('model/model.h5', by_name=True)import re
import numpy as np# 转移概率,单纯用了等概率
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 layer in range(1, len(nodes)):  # 后面的每一层paths_ = paths.copy()  # 先保存上一层的路径# node_now 为本层节点, node_last 为上层节点paths = {}  # 清空 pathfor node_now in nodes[layer].keys():# 对于本层的每个节点,找出最短路径sub_paths = {}# 上一层的每个节点到本层节点的连接for path_last in paths_.keys():if path_last[-1] + node_now in zy.keys():  # 若转移概率不为 0sub_paths[path_last + node_now] = paths_[path_last] + nodes[layer][node_now] + zy[path_last[-1] + node_now]# 最短路径,即概率最大的那个sr_subpaths = pd.Series(sub_paths)sr_subpaths = sr_subpaths.sort_values()  # 升序排序node_subpath = sr_subpaths.index[-1]  # 最短路径node_value = sr_subpaths[-1]  # 最短路径对应的值# 把 node_now 的最短路径添加到 paths 中paths[node_subpath] = node_value# 所有层求完后,找出最后一层中各个节点的路径最短的路径sr_paths = pd.Series(paths)sr_paths = sr_paths.sort_values()  # 按照升序排序return sr_paths.index[-1]  # 返回最短路径(概率值最大的路径)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)print(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 wordselse:return []not_cuts = re.compile(u'([\da-zA-Z ]+)|[。,、?!\.\?,!]')def cut_word(s):result = []j = 0for 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 resultprint(cut_word('深度学习主要是特征学习'))

结果:

['深度', '学习', '主要', '是', '特征', '学习']

最后

本例子使用 Bi-directional LSTM 来完成了序列标注的问题。本例中展示的是一个分词任务,但是还有其他的序列标注问题都是可以通过这样一个架构来实现的,比如 POS(词性标注)、NER(命名实体识别)等。在本例中,单从分类准确率来看的话差不多到 95% 了,还是可以的。可是最后的分词效果还不是非常好,但也勉强能达到实用的水平。

源代码地址

https://github.com/stephen-v/zh-segmentation-keras

基于双向BiLstm神经网络的中文分词详解及源码相关推荐

  1. 中文分词算法python_Python FMM算法的中文分词器实现方法源码

    这是一篇基于Python代码使用FMM算法达到中文分词效果实现方法的文章.中文语句分词因为编码的关系在Python语言中并不是很好处理,关于中文乱码与编码的问题解决方法,可以参考玩蛇网的Python中 ...

  2. 简单有效的多标准中文分词详解

    简单有效的多标准中文分词详解 本文介绍一种简洁优雅的多标准中文分词方案,可联合多个不同标准的语料库训练单个模型,同时输出多标准的分词结果.通过不同语料库之间的迁移学习提升模型的性能,在10个语料库上的 ...

  3. 【电力负荷预测】基于matlab BP神经网络电力负荷预测【含Matlab源码 278期】

    ⛄一.获取代码方式 获取代码方式1: 完整代码已上传我的资源: [电力负荷预测]基于matlab BP神经网络电力负荷预测[含Matlab源码 278期] 获取代码方式2: 付费专栏Matlab智能算 ...

  4. 详解 Python 源码之对象机制

    在Python中,对象就是在堆上申请的结构体,对象不能是被静态初始化的,并且也不能是在栈空间上生存的.唯一的例外就是类型对象(type object),Python中所有的类型对象都是被静态初始化的. ...

  5. FPGA学习之路—接口(2)—I2C协议详解+Verilog源码分析

    FPGA学习之路--I2C协议详解+Verilog源码分析 定义 I2C Bus(Inter-Integrated Circuit Bus) 最早是由Philips半导体(现被NXP收购)开发的两线时 ...

  6. okhttp的应用详解与源码解析--http的发展史

    乘5G之势,借物联网之风,Android未来亦可期,Android优势在于开放,手机.平板.车载设备.智能家居等都是Android的舞台,Google不倒,Android不灭,本专栏的同步视频教程已经 ...

  7. RN FlatList使用详解及源码解析

    FlatList使用详解及源码解析 前言 长列表或者无限下拉列表是最常见的应用场景之一.RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧.而在最新的 0.43 版本中,提供 ...

  8. Android应用Context详解及源码解析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 今天突然想起之前在上家公司(做TV与BOX盒子)时有好几个人问过我关于Android ...

  9. hadoop作业初始化过程详解(源码分析第三篇)

    (一)概述 我们在上一篇blog已经详细的分析了一个作业从用户输入提交命令到到达JobTracker之前的各个过程.在作业到达JobTracker之后初始化之前,JobTracker会通过submit ...

最新文章

  1. 华为鸿蒙系统新机发布时间,华为鸿蒙OS发布会定档,或将有多款新机亮相
  2. Java里的数组介绍
  3. Elasticsearch 快速入门
  4. http请求之get和post的区别
  5. 百年诺奖的那些争议与放弃
  6. sql 查询超时已过期_监视来自SQL Server代理作业的查询超时过期消息
  7. cruise软件模型,混动仿真模型,IMMD架构混联混动仿真模型
  8. DOSBOX的安装及ASM文件的编译
  9. 邮件签名——html模板
  10. 网络冗余计算机,基于冗余计算机的冗余网络IP智能切换方法及系统与流程
  11. cissp证书(cissp证书)
  12. java实现查询Word是否包含批注和修订内容
  13. matlab 多个向量的余弦角_matlab中怎么求一个行向量的反余弦值
  14. linux下 scp 和 ssh 命令
  15. Deep Projective 3D Semantic Segmentation
  16. Ordinary least squares是什么意思?
  17. APP通用异常测试场景
  18. 参加计算机社团的英语作文,欢迎参加我们俱乐部
  19. IC617如何绘制反相器和反相器的仿真
  20. 基于阿里云IoT平台OTA进行APP确认升级的方案——业务架构类

热门文章

  1. 《Visual C++ 开发从入门到精通》——2.5 标识符
  2. python电梯题_课内资源 - 基于PyQt5实现的python电梯调度程序
  3. python预测模型案例_案例2:精准营销的两阶段预测模型1
  4. 甲骨文软件测试自学,【甲骨文(Oracle)软件测试面试】自己太菜鸟,感觉好丢人-看准网...
  5. yolov6论文翻译
  6. java 三种工厂模式
  7. 土壤墒情监测解决方案
  8. 程序员:三行情书|告白不只有我爱你
  9. android系统广播 定向广播,定向广播的几种方式
  10. 2020年大学毕业生就业前景如何?职业规划定位准确,并不是难事