初识DL在自然语言中的应用

原文地址:

作者:

一、自然语言序列标注

虽然之前自己对于HMM、CRF、Word2vec、Attention机制、Encode-Decode等,在自然语言领域的应用已经比较熟悉了,看过的文献、教程、算法等也很多,但是感觉都只是纸上谈兵,自己从没有好好写过代码。因为自己一直是搞计算机视觉、图像领域,最近看了一些深度学习计算机视觉领域相关最新的文献后,总是觉得学到的知识不是很多;故而决定从今日开始正式踏足自然语言领域,开始NLP学习征程……

目前我看了很多经典文献:《Natural Language Processing (almost) from Scratch》、《Learning Character-level Representations for Part-of-Speech Tagging》、《End-to-end Sequence Labeling via Bi-directional LSTM-CNNs-CRF》、《Bidirectional LSTM-CRF Models for Sequence Tagging》 等  ,利用深度学习RNN、CNN、LSTM等网络模型做序列标注,主要包含以下几个特点:

对于自然语言来说,序列标注定义:给定一个句子X={x1,x2,……xn},我们的目标是求解输出序列Y={y1,y2,……yn},其中x1,x2……表示句子每个字。

以中文分词为例

(1)中文分词:目标是要把一个句子的每个字,标注成S、B、M、E,以下面的句子为例:

X={“ 人们  常 说 生活 是 一 部 教科书 , 而 血 与 火 的 战争}”

Y={S BE   S    S   B E   S   S    S    BME   S    S    S   S   S   S   B E}

对于分词任务来说,我们的任务就是要给上面的每个字打上类别标签:S、B、M、E,也就是说属于4分类问题,神经网络在设计的时候,网络的最后一层就是4个神经元的softmax输出。

(2)命名实体识别:目标是要把句子的每个字标注成:BA、MA、EA、BO、MO、EO、BP、MP、EP、O等,

其中BA代表这个汉字是地址首字,MA代表这个汉字是地址中间字,EA代表这个汉字是地址的尾字;BO代表这个汉字是机构名的首字,MO代表这个汉字是机构名称的中间字,EO代表这个汉字是机构名的尾字;BP代表这个汉字是人名首字,MP代表这个汉字是人名中间字,EP代表这个汉字是人名尾字,而O代表这个汉字不属于命名实体。具体的类别根据需求而定,比如你还要识别时间、一些特定的名词类别等,那么就加入新的类别标签。

除此之外,词性标注、语义角色识别,甚至连关键词提取等都可以看成是序列标注问题,比如关键词提取的目标可以看成对每个词做一个二分类:关键词、非关键词。因为序列标注都可以用同样一个框架,搞定任务。所以后面为了简单起见,我将用上面的分词例子为例,进行讲解。

(1)最普通的方案:

既然序列标注可以看成是对每个文字进行分类,最简单的方法是:把每个文字表示成一个索引向量(专业名词又称之为One-hot Representation),要预测该文字的标签的时候,就用这个向量作为输入。比如我们要分类上面句子中的文字"常",假设"常"在字典中的索引号为1020,字典的字数共10000。那么我们可以把常用数值表示x=(0,0,……1,……0),x是一个10000维的向量,除了第1020维的数值为1之外,其它的全部为0。用x向量作为神经网络的输入层,网络设计成三层MLP结构。神经网络的输出为4个神经元softmax层,对应于"常"分别属于S、B、M、E标签的概率,

OK,这是对于我等,只专注于计算机机视觉与深度学习,然而却完全没有接触过NLP的解决方案。首先上面的方案,不是不可行,而是精度非常非常低罢了(达到精度30%应该还是有的,哈哈,毕竟四个类别没有任何信息,等概率也可以到达25%精度)

虽然上面讲的方案很低级,然而后面我将以这个方案作为基础思路,对上面的方案存在的问题进行讲解,然后对方案做一步一步的改进,直到最后我们设计、训练出来的网络,可以与现有的世界最牛逼的序列标注算法相匹敌.

(2)第一次改进-上下文窗口

一般来说,我们在设计神经网络的时候,网络的输入层,不仅仅是一个文字,而是采用上下文窗口.可能以前我们遇到深度学习一般是思路是:输入层为xi,输入就是yi。xi表示一个句子中的某个文字,图片示例,一一映射:

然而一般对于自然语言来说,我们一般很难通过一个字,判断这个字的标签,比如上面"常",如果是单独的一个文字,我们根本无法确定其标签,其具体标签还得看"常"在该句子中的上下文信息,对于上面的分词例子,"常"在该句子中的分类标签是S;.然而对于下面的句子:

"我经常独自一人吃饭"

中"常"的标签就应该是B.

因此让神经网络的输入层为单个文字:"常",进行预测类别标签,显然是不合理的.一般来说我们会采用上下文窗口(这个就像图像处理的卷积窗口感受野一样)神经网络的输入应该是:

(2)第二次改进-字向量

(3)第三次改进-序列标签约束MLP+CRF

(5)第五次改进-RNN、LSTM+CRF

(6)第六次改进双向LSTM+CRF

(7)第七次改进双向LSTM+CNN+CRF

(4)细节改进-字向量无监督预训练

二、采用上下文窗口+词向量-简单MLP中文分词尝试

作为自己学习NLP的第一站,当然是先用最简单的网络做最简单的任务,练练手再说。

神经网络的输入:

人们 生活教科书战争…… ”

S BE   S    S     B E    S   S    S    BME     S     S    S   S    S   S     B E

神经网络的输出:

下面是我用简单的MLP,进行中文分词的源码测试,因此记录一下自己的第一个自然语言程序:

#coding=utf-8
from collections import OrderedDict
import os
import random
import numpy
import theano
from theano import tensor as T
from Preprocess.LoadData import  Segment,write
import numpy as np# 打乱样本数据
def shuffle(lol, seed):for l in lol:random.seed(seed)random.shuffle(l)#输入一个长句,我们根据窗口获取每个win内的数据,作为一个样本。或者也可以称之为作为RNN的某一时刻的输入
def contextwin(l, win):assert (win % 2) == 1assert win >= 1l = list(l)lpadded = win // 2 * [-1] + l + win // 2 * [-1]#在一个句子的末尾、开头,可能win size内不知,我们用-1 paddingout = [lpadded[i:(i + win)] for i in range(len(l))]assert len(out) == len(l)return out# 输出结果,用于脚本conlleval.pl的精度测试,该脚本需要自己下载,在windows下调用命令为:perl conlleval.pl < filename
def conlleval(p, g, w, filename):out = ''for sl, sp, sw in zip(g, p, w):out += 'BOS O O\n'for wl, wp, w in zip(sl, sp, sw):out += w + ' ' + wl + ' ' + wp + '\n'out += 'EOS O O\n\n'f = open(filename, 'w')f.writelines(out)f.close()class RNNSLU(object):def __init__(self, nh, nc, ne, de, cs):'''nh ::隐藏层神经元个数nc ::输出层标签分类类别ne :: 单词的个数de :: 词向量的维度cs :: 上下文窗口'''#词向量实际为(ne, de),外加1行,是为了边界标签-1而设定的self.emb = theano.shared(name='embeddings',value=0.2 * numpy.random.uniform(-1.0, 1.0,(ne+1, de)).astype(theano.config.floatX))#词向量空间self.wx = theano.shared(name='wx',value=0.2 * numpy.random.uniform(-1.0, 1.0,(de * cs, nh)).astype(theano.config.floatX))#输入数据到隐藏层的权重矩阵self.wh = theano.shared(name='wh', value=0.2 * numpy.random.uniform(-1.0, 1.0,(nh, nh)).astype(theano.config.floatX))#上一时刻隐藏到本时刻隐藏层循环递归的权值矩阵self.w = theano.shared(name='w',value=0.2 * numpy.random.uniform(-1.0, 1.0,(nh, nc)).astype(theano.config.floatX))#隐藏层到输出层的权值矩阵self.bh = theano.shared(name='bh', value=numpy.zeros(nh,dtype=theano.config.floatX))#隐藏层偏置参数self.b = theano.shared(name='b',value=numpy.zeros(nc,dtype=theano.config.floatX))#输出层偏置参数self.h0 = theano.shared(name='h0',value=numpy.zeros(nh,dtype=theano.config.floatX))self.params = [self.emb, self.wx, self.wh, self.w,self.bh, self.b]#所有待学习的参数idxs = T.itensor3()x = self.emb[idxs].reshape((idxs.shape[0],idxs.shape[1],de*idxs.shape[2]))y_sentence = T.imatrix('y_sentence')  # 训练样本标签,二维的(batch,sentence)s_temp=self.forward(x)p_y =T.nnet.softmax(T.reshape(s_temp,(s_temp.shape[0]*s_temp.shape[1],-1)))p_y=T.reshape(p_y,s_temp.shape)#h,p_y=step(x, self.h0)#p_y为三维矩阵,表示每个样本的值loss=self.nll_multiclass(p_y,y_sentence)+0.0*((self.wx**2).sum()+(self.wh**2).sum()+(self.w**2).sum())lr = T.scalar('lr')#学习率,一会儿作为输入参数#神经网络的输出sentence_gradients = T.grad(loss, self.params)sentence_updates = OrderedDict((p, p - lr*g) for p, g in zip(self.params, sentence_gradients))self.sentence_train = theano.function(inputs=[idxs,y_sentence,lr],outputs=loss,updates=sentence_updates)#词向量归一化,因为我们希望训练出来的向量是一个归一化向量self.normalize = theano.function(inputs=[],updates={self.emb:self.emb /T.sqrt((self.emb**2).sum(axis=1)).dimshuffle(0, 'x')})#构造预测函数、训练函数,输入数据idxs每一行是一个样本(也就是一个窗口内的序列索引)y_pred = T.argmax(p_y,axis=-1)self.classify = theano.function(inputs=[idxs,y_sentence], outputs=[loss,y_pred])#这边没有采用RNN,而是直接采用MLP进行中文分词def forward(self,x):x_t=x#.dimshuffle((1,0,2))def step(x_t, h_tm1):h_t = T.nnet.sigmoid(T.dot(x_t, self.wx) + self.bh)#通过ht-1、x计算隐藏层s_temp=T.dot(h_t, self.w) + self.b#由于softmax不支持三维矩阵操作,所以这边需要对其进行reshape成2D,计算完毕后再reshape成3Dreturn h_t, s_temp#[h,s_temp], _ = theano.scan(step,sequences=x_t,outputs_info=[T.ones(shape=(x_t.shape[1],self.h0.shape[0])) * self.h0, None])[h,s_temp]=step(x_t,self.h0)#s_temp#.dimshuffle((1,0,2))return  s_tempdef backward(self,x):x_t=x#.dimshuffle((1,0,2))def step(x_t, h_tm1):h_t = T.nnet.tanh(T.dot(x_t, self.wx) + T.dot(h_tm1, self.wh) + self.bh)#通过ht-1、x计算隐藏层s_temp=T.dot(h_t, self.w) + self.b#由于softmax不支持三维矩阵操作,所以这边需要对其进行reshape成2D,计算完毕后再reshape成3Dreturn h_t, s_temp[h,s_temp], _ = theano.scan(step,sequences=x_t,outputs_info=[T.ones(shape=(x_t.shape[1],self.h0.shape[0])) * self.h0, None],go_backwards = True)s_temp=s_temp#.dimshuffle((1,0,2))return  s_temp#训练def train(self, x, y,learning_rate):loss=self.sentence_train(x, y, learning_rate)self.normalize()return  lossdef nll_multiclass(self,p_y_given_x, y):p_y =p_y_given_xp_y_m = T.reshape(p_y, (p_y.shape[0] * p_y.shape[1], -1))y_f = y.flatten(ndim=1)return -T.mean(T.log(p_y_m)[T.arange(p_y_m.shape[0]), y_f])#保存、加载训练模型def save(self, folder):for param in self.params:numpy.save(os.path.join(folder,param.name + '.npy'), param.get_value())def load(self, folder):for param in self.params:param.set_value(numpy.load(os.path.join(folder,param.name + '.npy')))
#为了采用batch训练,需要保证每个句子长度相同,因此这里采用均匀切分,不过有一个缺陷那就是有可能某个词刚好被切开
def convert2batch(dic,filename_list,win,length=3):x=[]y=[]for f in filename_list:xt,yt=dic.encode_index(f)#创建训练数据的索引序列x=x+xty=y+yttrain_batchxs=[]train_batchys=[]train_seqx=[x[i:i+length] for i in range(len(x)) if i%length==0]train_seqy=[y[i:i+length] for i in range(len(y)) if i%length==0]for x,y in zip(train_seqx,train_seqy):if len(x)!=length or len(y)!=length:continues=contextwin(x,win)train_batchxs.append(s)train_batchys.append(y)#每个句子的长度不同,不能直接转换return  np.asarray(train_batchxs,dtype=np.int32),np.asarray(train_batchys,dtype=np.int32)#RNN分词
def segment_train(dic,filename):trainx,trainy=convert2batch(dic,filename,5,1000)#计算相关参数vocsize = len(dic.word2index)#计算词的个数print vocsizenclasses =len(dic.label2index)#标签数为B、M、E、Swinsize=5#窗口大小ndim=50#词向量维度nhidden=200#隐藏层的神经元个数learn_rate=0.5#梯度下降学习率#构建RNN,开始训练rnn = RNNSLU(nh=nhidden,nc=nclasses,ne=vocsize,de=ndim,cs=winsize)batch_size=64n_train_batch=trainx.shape[0]/batch_sizernn.load('model/')epoch=0while epoch<0:shuffle([trainx,trainy], 345)loss=0for i in range(n_train_batch):batx=trainx[i*batch_size:(i+1)*batch_size]baty=trainy[i*batch_size:(i+1)*batch_size]decay_lr=learn_rate*0.5**(epoch/50)loss+=rnn.train(batx,baty,decay_lr)print 'epoch:',epoch,'\tloss:',loss/n_train_batchepoch+=1if epoch%50==0:rnn.save('model/')#rnn.save('model/')return  rnn
def segment_test(model,dic,test_file):#model.load('model/')#加载训练参数x,y=dic.encode_index(test_file)#创建训练数据的索引序列xjieba,yjieba=dic.encode_index('Data/msr/msr_test_jieba_result.txt')#创建训练数据的索引序列test_batchxs=[]test_batchys=[]s=contextwin(x,5)test_batchxs.append(s)test_batchys.append(y)loss,pre=model.classify(np.asarray(test_batchxs),np.asarray(test_batchys).transpose((1,0)))print 'loss:',lossprint pre.shape#测试集的输出标签print pre.shapepredictions_label=dic.decode_index(pre[0],is_label=True)groudtrue_label=dic.decode_index(y,is_label=True)words_test=dic.decode_index(x)for k,(w,i,j) in enumerate(zip(words_test,groudtrue_label,predictions_label)):print w,i,jprint 'save'conlleval(predictions_label,groudtrue_label,words_test, 'current.test.txt')filelist=['Data/msr/msr_training.utf8','Data/msr/pku_training.utf8','Data/msr/1.txt','Data/msr/1998.txt']
#filelist=['Data/msr/msr_training.utf8']
dic=Segment(filelist)#创建词典
print len(dic.word2index)
model=segment_train(dic,filelist)
segment_test(model,dic,'Data/msr/msr_test_gold.utf8')
再看一下结巴分词的结果:
用最简单的可以简单的看成对一个句子的每个文字进行分类。需要注意的是序列标注问题与简单的文字分类问题,最大的区别在于:,因此采用简单的神经网络进行输入输出,精度很难达到state-of-art,
三\标签约束
具体原因如下:
1、为了快速构建网络,进行批量训练,我比较懒惰,直接把一个文本,按照一个sequence 长度进行强制切分,
2、没有对一个文本的符号进行处理,而是直接扔进网络中。把句号、逗号等作为分类类别标致"S",也一起扔进网络中
3、没有对输出序列做约束关系,导致
5、没有结合采用字向量进行网络的预训练
参考文献:
1、http://blog.csdn.net/malefactor/article/details/50725480

深度学习(三十八)初识DL在自然语言序列标注中的应用-未完待续相关推荐

  1. 推荐系统遇上深度学习(三十九)-推荐系统中召回策略演进!

    推荐系统中的核心是从海量的商品库挑选合适商品最终展示给用户.由于商品库数量巨大,因此常见的推荐系统一般分为两个阶段,即召回阶段和排序阶段.召回阶段主要是从全量的商品库中得到用户可能感兴趣的一小部分候选 ...

  2. 花书+吴恩达深度学习(十八)迁移学习和多任务学习

    目录 0. 前言 1. 迁移学习 2. 多任务学习 如果这篇文章对你有一点小小的帮助,请给个关注,点个赞喔~我会非常开心的~ 花书+吴恩达深度学习(十八)迁移学习和多任务学习 花书+吴恩达深度学习(十 ...

  3. 深度学习三十年创新路

    深度学习三十年创新路 编者注:深度学习火了,从任何意义上,大家谈论它的热衷程度,都超乎想象.但是,似乎很少有人提出不同的声音,说深度学习的火热,有可能是过度的繁荣,乃至不理性的盲从.而这次,有不同的想 ...

  4. 深度学习(十八)基于R-CNN的物体检测-CVPR 2014-未完待续

    基于R-CNN的物体检测 原文地址:http://blog.csdn.net/hjimce/article/details/50187029 作者:hjimce 一.相关理论 本篇博文主要讲解2014 ...

  5. 深度学习(十八)——YOLOv2(2), 语义分割

    YOLOv2 Stronger(续) Hierarchical classification(层次式分类) ImageNet的标签参考WordNet(一种结构化概念及概念之间关系的语言数据库).例如: ...

  6. 【深度学习】深度学习三十问!一位算法工程师经历30+场CV面试后总结的常见问题合集(含答案)...

    作者丨灯会 来源丨极市平台 编辑丨极市平台 导读 作者灯会为21届中部985研究生,凭借自己整理的面经,去年在腾讯优图暑期实习,七月份将入职百度cv算法工程师.在去年灰飞烟灭的算法求职季中,经过30+ ...

  7. 深度学习三十问!一位算法工程师经历30+场CV面试后总结的常见问题合集(含答案)...

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨灯会 来源丨极市平台 编辑丨极市平台 极市导读 作者灯会为21届中部985研究生,凭借自己整理的 ...

  8. Tensorflow实战学习(三十八)【实现估值网络】

    Q-Learning,学习Action对应期望值(Expected Utility).1989年,Watkins提出.收敛性,1992年,Watkins和Dayan共同证明.学习期望价值,从当前一步到 ...

  9. 深度学习(十八):人脸验证(face verification)和人脸识别(face recognition)

    这是一系列深度学习的介绍,本文不会涉及公式推导,主要是一些算法思想的随笔记录. 适用人群:深度学习初学者,转AI的开发人员. 编程语言:Python 参考资料:吴恩达老师的深度学习系列视频 吴恩达老师 ...

最新文章

  1. Spring过滤器组件自动扫描
  2. ArcGIS实验教程——实验四十一:ArcGIS区域分析统计直方图(土地利用--坡度分级柱状统计图的制作)
  3. java前台线程(普通线程) 和 后台线程
  4. win2008 mysql_mysql5.7.17在win2008R2的64位系统安装与配置实例
  5. SpringMVC配置没问题却却找不到页面,页面显示404
  6. python 线性回归 统计检验 p值_PAST:最简便易用的统计学分析软件教程(一)软件基本信息介绍...
  7. Android应用开发(1)---Android五大UI布局的特有属性
  8. GoPro 8及旗下首款360全景运动摄像机HERO Max将发售
  9. Redis学习笔记~Redis并发锁机制
  10. Microsoft edge兼容性问题
  11. 深圳房价三连跌,国内的房地产价格或将持续下跌,该持现金过冬了
  12. 如何修复dns服务器超时,DNS服务器安全及解析超时问题的解决
  13. 关于性能测试的这点事,值得收藏~
  14. html项目符号正方形,HTML无序列表| HTML项目符号列表
  15. html:简易制作拼多多登录页面
  16. 【JAVA】Java 内存模型中的 happen-before
  17. 为什么需要一部21世纪的全球通史?
  18. 数据结构实验-稀疏一元多项式计算
  19. 什么是IOC和什么是AOP
  20. inline-block中居中元素

热门文章

  1. java面试……面霸养成记
  2. 阿里云服务器内网/私网IP地址段说明
  3. C++控制台程序判断输入的数字
  4. 深度学习-YOLOV3在百度点石数据集测试
  5. 前端 cdn 方式使用 Vue + element-ui
  6. 人工智能与区块链技术的结合: 金融与商业世界内的一场革命
  7. 保护您的企业数据免受.mkp勒索病毒:恢复加密数据库的关键策略
  8. 用于表检测和结构识别的深度学习:综述
  9. 商务部:2017中国国际货运代理行业发展报告 (附下载)
  10. Characters_of_the_Three_Kingdoms - 三国人物结构化数据 1