自然语言处理中的序列生成是不什么是伪命题题

自然语言处理中的Attention Model:是什么及为什么
要是关注深度学习在自然语言处理方面的研究进展,我相信你一定听说过Attention Model(后文有时会简称AM模型)这个词。AM模型应该说是过去一年来NLP领域中的重要进展之一,在很多场景被证明有效。听起来AM很高大上,其实它的基本思想是相当直观简洁的。本文作者可以对灯发誓:在你读完这篇啰里啰嗦的文章及其后续文章后,一定可以透彻了解AM到底是什么,以及轻易看懂任何有关论文看上去复杂的数学公式部分。怎么样,这广告打的挺有吸引力吧,尤其是对那些患有数学公式帕金森病的患者。
在正戏开演前,我们先来点题外话。
|引言及废话
你应该常常听到被捉奸在床的男性经常感叹地说一句话:女性的第六感通常都很准,当然这里的女性一般是特指这位男性的老婆或者女友,当然也可能是他的某位具有女性气质的男友。要我说,男人的第六感其实也不差(这里的“男人”特指本文作者本人,当然非上文所引用的“男性”,为避免混淆特做声明)。当我第一次看到机器学习领域中的Attention Model这个名字的时候,我的第一直觉就是:这是从认知心理学里面的人脑注意力模型引入的概念。若干年前,也就是在我年轻不懂事的花样年华里,曾有一阵子沉迷于人脑的工作机制,大量阅读了认知心理学方面的书籍和论文,而一般注意力模型会作为书籍的单独一章来讲。下面请允许我显摆一下鄙人渊博的知识。
注意力这东西其实挺有意思,但是很容易被人忽略。让我们来直观地体会一下什么是人脑中的注意力模型。首先,请您睁开眼并确认自己处于意识清醒状态;第二步,请找到本文最近出现的一个“Attention Model”字眼(就是“字眼”前面的两个英文单词,…^@@^)并盯住看三秒钟。好,假设此刻时间停止,在这三秒钟你眼中和脑中看到的是什么?对了,就是“Attention Model”这两个词,但是你应该意识到,其实你眼中是有除了这两个单词外的整个一副画面的,但是在你盯着看的这三秒钟,时间静止,万物无息,仿佛这个世界只有我和你…..对不起,串景了,仿佛这个世界只有“Attention Model”这两个单词。这是什么?这就是人脑的注意力模型,就是说你看到了整幅画面,但在特定的时刻t,你的意识和注意力的焦点是集中在画面中的某一个部分上,其它部分虽然还在你的眼中,但是你分配给它们的注意力资源是很少的。其实,只要你睁着眼,注意力模型就无时不刻在你身上发挥作用,比如你过马路,其实你的注意力会被更多地分配给红绿灯和来往的车辆上,虽然此时你看到了整个世界;比如你很精心地偶遇到了你心仪的异性,此刻你的注意力会更多的分配在此时神光四射的异性身上,虽然此刻你看到了整个世界,但是它们对你来说跟不存在是一样的…..
这就是人脑的注意力模型,说到底是一种资源分配模型,在某个特定时刻,你的注意力总是集中在画面中的某个焦点部分,而对其它部分视而不见。
其实吧,深度学习里面的注意力模型工作机制啊,它跟你看见心动异性时荷尔蒙驱动的注意力分配机制是一样一样的。
好,前戏结束,正戏开场。
|Encoder-Decoder框架
本文只谈谈文本处理领域的AM模型,在图片处理或者(图片-图片标题)生成等任务中也有很多场景会应用AM模型,但是我们此处只谈文本领域的AM模型,其实图片领域AM的机制也是相同的。
要提文本处理领域的AM模型,就不得不先谈Encoder-Decoder框架,因为目前绝大多数文献中出现的AM模型是附着在Encoder-Decoder框架下的,当然,其实AM模型可以看作一种通用的思想,本身并不依赖于Encoder-Decoder模型,这点需要注意。
Encoder-Decoder框架可以看作是一种文本处理领域的研究模式,应用场景异常广泛,本身就值得非常细致地谈一下,但是因为本文的注意力焦点在AM模型,所以此处我们就只谈一些不得不谈的内容,详细的Encoder-Decoder模型以后考虑专文介绍。下图是文本处理领域里常用的Encoder-Decoder框架最抽象的一种表示:
图1. 抽象的Encoder-Decoder框架
Encoder-Decoder框架可以这么直观地去理解:可以把它看作适合处理由一个句子(或篇章)生成另外一个句子(或篇章)的通用处理模型。对于句子对&X,Y&,我们的目标是给定输入句子X,期待通过Encoder-Decoder框架来生成目标句子Y。X和Y可以是同一种语言,也可以是两种不同的语言。而X和Y分别由各自的单词序列构成:
Encoder顾名思义就是对输入句子X进行编码,将输入句子通过非线性变换转化为中间语义表示C:
对于解码器Decoder来说,其任务是根据句子X的中间语义表示C和之前已经生成的历史信息y1,y2….yi-1来生成i时刻要生成的单词yi
每个yi都依次这么产生,那么看起来就是整个系统根据输入句子X生成了目标句子Y。
Encoder-Decoder是个非常通用的计算框架,至于Encoder和Decoder具体使用什么模型都是由研究者自己定的,常见的比如CNN/RNN/BiRNN/GRU/LSTM/Deep LSTM等,这里的变化组合非常多,而很可能一种新的组合就能攒篇论文,所以有时候科研里的创新就是这么简单。比如我用CNN作为Encoder,用RNN作为Decoder,你用BiRNN做为Encoder,用深层LSTM作为Decoder,那么就是一个创新。所以正准备跳楼的憋着劲想攒论文毕业的同学可以从天台下来了,当然是走下来,不是让你跳下来,你可以好好琢磨一下这个模型,把各种排列组合都试试,只要你能提出一种新的组合并被证明有效,那恭喜你:施主,你可以毕业了。
扯远了,再拉回来。
Encoder-Decoder是个创新游戏大杀器,一方面如上所述,可以搞各种不同的模型组合,另外一方面它的应用场景多得不得了,比如对于机器翻译来说,&X,Y&就是对应不同语言的句子,比如X是英语句子,Y是对应的中文句子翻译。再比如对于文本摘要来说,X就是一篇文章,Y就是对应的摘要;再比如对于对话机器人来说,X就是某人的一句话,Y就是对话机器人的应答;再比如……总之,太多了。哎,那位施主,听老衲的话,赶紧从天台下来吧,无数创新在等着你发掘呢。
|Attention Model
图1中展示的Encoder-Decoder模型是没有体现出“注意力模型”的,所以可以把它看作是注意力不集中的分心模型。为什么说它注意力不集中呢?请观察下目标句子Y中每个单词的生成过程如下:
其中f是decoder的非线性变换函数。从这里可以看出,在生成目标句子的单词时,不论生成哪个单词,是y1,y2也好,还是y3也好,他们使用的句子X的语义编码C都是一样的,没有任何区别。而语义编码C是由句子X的每个单词经过Encoder 编码产生的,这意味着不论是生成哪个单词,y1,y2还是y3,其实句子X中任意单词对生成某个目标单词yi来说影响力都是相同的,没有任何区别(其实如果Encoder是RNN的话,理论上越是后输入的单词影响越大,并非等权的,估计这也是为何Google提出Sequence to Sequence模型时发现把输入句子逆序输入做翻译效果会更好的小Trick的原因)。这就是为何说这个模型没有体现出注意力的缘由。这类似于你看到眼前的画面,但是没有注意焦点一样。如果拿机器翻译来解释这个分心模型的Encoder-Decoder框架更好理解,比如输入的是英文句子:Tom chase Jerry,Encoder-Decoder框架逐步生成中文单词:“汤姆”,“追逐”,“杰瑞”。在翻译“杰瑞”这个中文单词的时候,分心模型里面的每个英文单词对于翻译目标单词“杰瑞”贡献是相同的,很明显这里不太合理,显然“Jerry”对于翻译成“杰瑞”更重要,但是分心模型是无法体现这一点的,这就是为何说它没有引入注意力的原因。没有引入注意力的模型在输入句子比较短的时候估计问题不大,但是如果输入句子比较长,此时所有语义完全通过一个中间语义向量来表示,单词自身的信息已经消失,可想而知会丢失很多细节信息,这也是为何要引入注意力模型的重要原因。
上面的例子中,如果引入AM模型的话,应该在翻译“杰瑞”的时候,体现出英文单词对于翻译当前中文单词不同的影响程度,比如给出类似下面一个概率分布值:
(Tom,0.3)(Chase,0.2)(Jerry,0.5)
每个英文单词的概率代表了翻译当前单词“杰瑞”时,注意力分配模型分配给不同英文单词的注意力大小。这对于正确翻译目标语单词肯定是有帮助的,因为引入了新的信息。同理,目标句子中的每个单词都应该学会其对应的源语句子中单词的注意力分配概率信息。这意味着在生成每个单词Yi的时候,原先都是相同的中间语义表示C会替换成根据当前生成单词而不断变化的Ci。理解AM模型的关键就是这里,即由固定的中间语义表示C换成了根据当前输出单词来调整成加入注意力模型的变化的Ci。增加了AM模型的Encoder-Decoder框架理解起来如图2所示。
图2 引入AM模型的Encoder-Decoder框架
即生成目标句子单词的过程成了下面的形式:
而每个Ci可能对应着不同的源语句子单词的注意力分配概率分布,比如对于上面的英汉翻译来说,其对应的信息可能如下:
其中,f2函数代表Encoder对输入英文单词的某种变换函数,比如如果Encoder是用的RNN模型的话,这个f2函数的结果往往是某个时刻输入xi后隐层节点的状态值;g代表Encoder根据单词的中间表示合成整个句子中间语义表示的变换函数,一般的做法中,g函数就是对构成元素加权求和,也就是常常在论文里看到的下列公式:
假设Ci中那个i就是上面的“汤姆”,那么Tx就是3,代表输入句子的长度,h1=f(“Tom”),h2=f(“Chase”),h3=f(“Jerry”),对应的注意力模型权值分别是0.6,0.2,0.2,所以g函数就是个加权求和函数。如果形象表示的话,翻译中文单词“汤姆”的时候,数学公式对应的中间语义表示Ci的形成过程类似下图:
图3 Ci的形成过程
这里还有一个问题:生成目标句子某个单词,比如“汤姆”的时候,你怎么知道AM模型所需要的输入句子单词注意力分配概率分布值呢?就是说“汤姆”对应的概率分布:
(Tom,0.6)(Chase,0.2)(Jerry,0.2)
是如何得到的呢?
为了便于说明,我们假设对图1的非AM模型的Encoder-Decoder框架进行细化,Encoder采用RNN模型,Decoder也采用RNN模型,这是比较常见的一种模型配置,则图1的图转换为下图:
图4 RNN作为具体模型的Encoder-Decoder框架
那么用下图可以较为便捷地说明注意力分配概率分布值的通用计算过程:
图5 AM注意力分配概率计算
对于采用RNN的Decoder来说,如果要生成yi单词,在时刻i,我们是可以知道在生成Yi之前的隐层节点i时刻的输出值Hi的,而我们的目的是要计算生成Yi时的输入句子单词“Tom”、“Chase”、“Jerry”对Yi来说的注意力分配概率分布,那么可以用i时刻的隐层节点状态Hi去一一和输入句子中每个单词对应的RNN隐层节点状态hj进行对比,即通过函数F(hj,Hi)来获得目标单词Yi和每个输入单词对应的对齐可能性,这个F函数在不同论文里可能会采取不同的方法,然后函数F的输出经过Softmax进行归一化就得到了符合概率分布取值区间的注意力分配概率分布数值。图5显示的是当输出单词为“汤姆”时刻对应的输入句子单词的对齐概率。绝大多数AM模型都是采取上述的计算框架来计算注意力分配概率分布信息,区别只是在F的定义上可能有所不同。
上述内容就是论文里面常常提到的Soft Attention Model的基本思想,你能在文献里面看到的大多数AM模型基本就是这个模型,区别很可能只是把这个模型用来解决不同的应用问题。那么怎么理解AM模型的物理含义呢?一般文献里会把AM模型看作是单词对齐模型,这是非常有道理的。目标句子生成的每个单词对应输入句子单词的概率分布可以理解为输入句子单词和这个目标生成单词的对齐概率,这在机器翻译语境下是非常直观的:传统的统计机器翻译一般在做的过程中会专门有一个短语对齐的步骤,而注意力模型其实起的是相同的作用。在其他应用里面把AM模型理解成输入句子和目标句子单词之间的对齐概率也是很顺畅的想法。
当然,我觉得从概念上理解的话,把AM模型理解成影响力模型也是合理的,就是说生成目标单词的时候,输入句子每个单词对于生成这个单词有多大的影响程度。这种想法也是比较好理解AM模型物理意义的一种思维方式。
图6是论文“A Neural Attention Model for Sentence Summarization”中,Rush用AM模型来做生成式摘要给出的一个AM的一个非常直观的例子。
图6 句子生成式摘要例子
这个例子中,Encoder-Decoder框架的输入句子是:“russian defense minister ivanov called sunday for the creation of a joint front for combating global terrorism”。对应图中纵坐标的句子。系统生成的摘要句子是:“russia calls for joint front against terrorism”,对应图中横坐标的句子。可以看出模型已经把句子主体部分正确地抽出来了。矩阵中每一列代表生成的目标单词对应输入句子每个单词的AM分配概率,颜色越深代表分配到的概率越大。这个例子对于直观理解AM是很有帮助作用的。
最后是广告:关于AM,我们除了本文,下周还会有续集:从AM来谈谈两种科研创新模式,请不要转台,继续关注,谢谢。
责任编辑:
声明:本文由入驻搜狐号的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。
今日搜狐热点使用深度学习进行中文自然语言处理之序列标注 - 简书
使用深度学习进行中文自然语言处理之序列标注
深度学习简介
深度学习的资料很多,这里就不展开了讲,本文就介绍中文NLP的序列标注工作的一般方法。
机器学习与深度学习
简单来说,机器学习就是根据样本(即数据)学习得到一个模型,再根据这个模型预测的一种方法。
ML算法很多,Naive Bayes朴素贝叶斯、Decision Tree决策树、Support Vector Machine支持向量机、Logistic Regression逻辑回归、Conditional Random Field条件随机场等。
而深度学习,简单来说是一种有多层隐层的感知机。
DL也分很多模型,但一般了解Convolution Neural Network卷积神经网络、Recurrent Neural Network循环神经网络就够了(当然都要学,这里是指前期学习阶段可以侧重这两个)。
异同:ML是一种浅层学习,一般来说都由人工设计特征,而DL则用pre-training或者无监督学习来抽取特征表示,再使用监督学习来训练预测模型(当然不全都是这样)。
本文主要用于介绍DL在中文NLP的应用,所以采用了使用最为简单、方便的DL框架keras来开发,它是构建于两个非常受欢迎的DL框架theano和tensorflow之上的上层应用框架。
Natural Language Process自然语言处理又分为NLU自然语言理解和NLG自然语言生成。而分词、词性标注、实体识别、依存分析则是NLP的基础工作,它们都可以理解为一种序列标注工作。
序列标注工作简介
词向量简介
Word Embedding词向量方法,用实数向量来表示一个词的方法,是对One-hot Representation的一种优化。优点是低维,而且可以方便的用数学距离衡量词的词义相似度,缺点是词一多,模型就有点大,所以又有工作提出了Char Embedding方法,这种方法训练出来的模型很小,但丢失了很多的语义信息,所以又有基于分词信息的字向量的研究工作。
中文NLP序列标注之CWS
Chinese Word Segmentation中文分词是中文NLP的基础,一般来说中文分词有两种方法,一种是基于词典的方法,一种是基于ML或者DL的方法。CWS的发展可以参考,简单来说基于词典的方法实现简单、速度快,但是对歧义和未登录词没有什么好的办法,而基于ML和DL的方法实现复杂、速度较慢,但是可以较好地应对歧义和OOV(Out-Of-Vocabulary)。
基于词典的方法应用最广的应该是正向最大匹配,而基于ML的CWS效果比较好的算法是CRF,本文主要介绍基于DL的方法,但在实际应用中应该合理的结合两种方法。
标注集与评估方法
这里采用B(Begin字为词的起始)、M(Middle字为词的中间)、E(End字为词的结束)、S(Single单字词)标注集,训练预料和评估工具采用SIGHAN中的方法,具体可以参考我的另一篇文章。
原理是采用bi-directional LSTM模型训练后对句子进行预测得到一个标注的概率,再使用Viterbi算法寻找最优的标注序列。在分词的工作中不需要加入词向量,提升效果不明显。
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#2016年 03月 03日 星期四 11:01:05 CST by Demobin
import json
import h5py
import string
import codecs
corpus_tags = ['S', 'B', 'M', 'E']
def saveCwsInfo(path, cwsInfo):
'''保存分词训练数据字典和概率'''
print('save cws info to %s'%path)
fd = open(path, 'w')
(initProb, tranProb), (vocab, indexVocab) = cwsInfo
j = json.dumps((initProb, tranProb))
fd.write(j + '\n')
for char in vocab:
fd.write(char.encode('utf-8') + '\t' + str(vocab[char]) + '\n')
fd.close()
def loadCwsInfo(path):
'''载入分词训练数据字典和概率'''
print('load cws info from %s'%path)
fd = open(path, 'r')
line = fd.readline()
j = json.loads(line.strip())
initProb, tranProb = j[0], j[1]
lines = fd.readlines()
fd.close()
vocab = {}
indexVocab = [0 for i in range(len(lines))]
for line in lines:
rst = line.strip().split('\t')
if len(rst) & 2: continue
char, index = rst[0].decode('utf-8'), int(rst[1])
vocab[char] = index
indexVocab[index] = char
return (initProb, tranProb), (vocab, indexVocab)
def saveCwsData(path, cwsData):
'''保存分词训练输入样本'''
print('save cws data to %s'%path)
#采用hdf5保存大矩阵效率最高
fd = h5py.File(path,'w')
(X, y) = cwsData
fd.create_dataset('X', data = X)
fd.create_dataset('y', data = y)
fd.close()
def loadCwsData(path):
'''载入分词训练输入样本'''
print('load cws data from %s'%path)
fd = h5py.File(path,'r')
X = fd['X'][:]
y = fd['y'][:]
fd.close()
return (X, y)
def sent2vec2(sent, vocab, ctxWindows = 5):
charVec = []
for char in sent:
if char in vocab:
charVec.append(vocab[char])
charVec.append(vocab['retain-unknown'])
#首尾padding
num = len(charVec)
pad = int((ctxWindows - 1)/2)
for i in range(pad):
charVec.insert(0, vocab['retain-padding'] )
charVec.append(vocab['retain-padding'] )
for i in range(num):
X.append(charVec[i:i + ctxWindows])
def sent2vec(sent, vocab, ctxWindows = 5):
chars = []
for char in sent:
chars.append(char)
return sent2vec2(chars, vocab, ctxWindows = ctxWindows)
def doc2vec(fname, vocab):
'''文档转向量'''
#一次性读入文件,注意内存
fd = codecs.open(fname, 'r', 'utf-8')
lines = fd.readlines()
fd.close()
#标注统计信息
tagSize = len(corpus_tags)
tagCnt = [0 for i in range(tagSize)]
tagTranCnt = [[0 for i in range(tagSize)] for j in range(tagSize)]
for line in lines:
#按空格分割
words = line.strip('\n').split()
#每行的分词信息
chars = []
for word in words:
#包含两个字及以上的词
if len(word) & 1:
chars.append(word[0])
tags.append(corpus_tags.index('B'))
#词中间的字
for char in word[1:(len(word) - 1)]:
chars.append(char)
tags.append(corpus_tags.index('M'))
chars.append(word[-1])
tags.append(corpus_tags.index('E'))
chars.append(word)
tags.append(corpus_tags.index('S'))
#字向量表示
lineVecX = sent2vec2(chars, vocab, ctxWindows = 7)
#统计标注信息
lineVecY = []
lastTag = -1
for tag in tags:
lineVecY.append(tag)
#lineVecY.append(corpus_tags[tag])
#统计tag频次
tagCnt[tag] += 1
#统计tag转移频次
if lastTag != -1:
tagTranCnt[lastTag][tag] += 1
#暂存上一次的tag
lastTag = tag
X.extend(lineVecX)
y.extend(lineVecY)
charCnt = sum(tagCnt)
#转移总频次
tranCnt = sum([sum(tag) for tag in tagTranCnt])
#tag初始概率
initProb = []
for i in range(tagSize):
initProb.append(tagCnt[i]/float(charCnt))
#tag转移概率
tranProb = []
for i in range(tagSize):
for j in range(tagSize):
p.append(tagTranCnt[i][j]/float(tranCnt))
tranProb.append(p)
return X, y, initProb, tranProb
def genVocab(fname, delimiters = [' ', '\n']):
#一次性读入文件,注意内存
fd = codecs.open(fname, 'r', 'utf-8')
data = fd.read()
fd.close()
vocab = {}
indexVocab = []
for char in data:
#如果为分隔符则无需加入字典
if char not in delimiters and char not in vocab:
vocab[char] = index
indexVocab.append(char)
index += 1
#加入未登陆新词和填充词
vocab['retain-unknown'] = len(vocab)
vocab['retain-padding'] = len(vocab)
indexVocab.append('retain-unknown')
indexVocab.append('retain-padding')
#返回字典与索引
return vocab, indexVocab
def load(fname):
print 'train from file', fname
delims = [' ', '\n']
vocab, indexVocab = genVocab(fname)
X, y, initProb, tranProb = doc2vec(fname, vocab)
print len(X), len(y), len(vocab), len(indexVocab)
print initProb
print tranProb
return (X, y), (initProb, tranProb), (vocab, indexVocab)
if __name__ == '__main__':
load('~/work/corpus/icwb2/training/msr_training.utf8')
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#2016年 03月 03日 星期四 11:01:05 CST by Demobin
import numpy as np
import json
import h5py
import codecs
from dataset import cws
from util import viterbi
from sklearn.model_selection import train_test_split
from keras.preprocessing import sequence
from keras.optimizers import SGD, RMSprop, Adagrad
from keras.utils import np_utils
from keras.models import Sequential,Graph, model_from_json
from keras.layers.core import Dense, Dropout, Activation, TimeDistributedDense
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM, GRU, SimpleRNN
from gensim.models import Word2Vec
def train(cwsInfo, cwsData, modelPath, weightPath):
(initProb, tranProb), (vocab, indexVocab) = cwsInfo
(X, y) = cwsData
train_X, test_X, train_y, test_y = train_test_split(X, y , train_size=0.9, random_state=1)
train_X = np.array(train_X)
train_y = np.array(train_y)
test_X = np.array(test_X)
test_y = np.array(test_y)
outputDims = len(cws.corpus_tags)
Y_train = np_utils.to_categorical(train_y, outputDims)
Y_test = np_utils.to_categorical(test_y, outputDims)
batchSize = 128
vocabSize = len(vocab) + 1
wordDims = 100
maxlen = 7
hiddenDims = 100
w2vModel = Word2Vec.load('model/sougou.char.model')
embeddingDim = w2vModel.vector_size
embeddingUnknown = [0 for i in range(embeddingDim)]
embeddingWeights = np.zeros((vocabSize + 1, embeddingDim))
for word, index in vocab.items():
if word in w2vModel:
e = w2vModel[word]
e = embeddingUnknown
embeddingWeights[index, :] = e
model = Sequential()
model.add(Embedding(output_dim = embeddingDim, input_dim = vocabSize + 1,
input_length = maxlen, mask_zero = True, weights = [embeddingWeights]))
model.add(LSTM(output_dim = hiddenDims, return_sequences = True))
model.add(LSTM(output_dim = hiddenDims, return_sequences = False))
model.add(Dropout(0.5))
model.add(Dense(outputDims))
model.add(Activation('softmax'))
model.compile(loss = 'categorical_crossentropy', optimizer = 'adam')
result = model.fit(train_X, Y_train, batch_size = batchSize,
nb_epoch = 20, validation_data = (test_X,Y_test), show_accuracy=True)
j = model.to_json()
fd = open(modelPath, 'w')
fd.write(j)
fd.close()
model.save_weights(weightPath)
return model
def loadModel(modelPath, weightPath):
fd = open(modelPath, 'r')
j = fd.read()
fd.close()
model = model_from_json(j)
model.load_weights(weightPath)
return model
# 根据输入得到标注推断
def cwsSent(sent, model, cwsInfo):
(initProb, tranProb), (vocab, indexVocab) = cwsInfo
vec = cws.sent2vec(sent, vocab, ctxWindows = 7)
vec = np.array(vec)
probs = model.predict_proba(vec)
#classes = model.predict_classes(vec)
prob, path = viterbi.viterbi(vec, cws.corpus_tags, initProb, tranProb, probs.transpose())
for i, t in enumerate(path):
ss += '%s/%s '%(sent[i], cws.corpus_tags[t])
for i, t in enumerate(path):
if cws.corpus_tags[t] == 'S':
ss += sent[i] + ' '
elif cws.corpus_tags[t] == 'B':
word += sent[i]
elif cws.corpus_tags[t] == 'E':
word += sent[i]
ss += word + ' '
elif cws.corpus_tags[t] == 'M':
word += sent[i]
def cwsFile(fname, dstname, model, cwsInfo):
fd = codecs.open(fname, 'r', 'utf-8')
lines = fd.readlines()
fd.close()
fd = open(dstname, 'w')
for line in lines:
rst = cwsSent(line.strip(), model, cwsInfo)
fd.write(rst.encode('utf-8') + '\n')
fd.close()
def test():
print 'Loading vocab...'
cwsInfo = cws.loadCwsInfo('./model/cws.info')
cwsData = cws.loadCwsData('./model/cws.data')
print 'Done!'
print 'Loading model...'
#model = train(cwsInfo, cwsData, './model/cws.w2v.model', './model/cws.w2v.model.weights')
#model = loadModel('./model/cws.w2v.model', './model/cws.w2v.model.weights')
model = loadModel('./model/cws.model', './model/cws.model.weights')
print 'Done!'
print '-------------start predict----------------'
#s = u'为寂寞的夜空画上一个月亮'
#print cwsSent(s, model, cwsInfo)
cwsFile('~/work/corpus/icwb2/testing/msr_test.utf8', './msr_test.utf8.cws', model, cwsInfo)
if __name__ == '__main__':
viterbi算法
#!/usr/bin/python
# -*- coding: utf-8 -*-
#2016年 01月 28日 星期四 17:14:03 CST by Demobin
def _print(hiddenstates, V):
" + " ".join(("%7d" % i) for i in range(len(V))) + "\n"
for i, state in enumerate(hiddenstates):
s += "%.5s: " % state
s += " ".join("%.7s" % ("%f" % v[i]) for v in V)
#标准viterbi算法,参数为观察状态、隐藏状态、概率三元组(初始概率、转移概率、观察概率)
def viterbi(obs, states, start_p, trans_p, emit_p):
lenObs = len(obs)
lenStates = len(states)
V = [[0.0 for col in range(lenStates)] for row in range(lenObs)]
path = [[0 for col in range(lenObs)] for row in range(lenStates)]
#t = 0时刻
for y in range(lenStates):
#V[0][y] = start_p[y] * emit_p[y][obs[0]]
V[0][y] = start_p[y] * emit_p[y][0]
path[y][0] = y
for t in range(1, lenObs):
newpath = [[0.0 for col in range(lenObs)] for row in range(lenStates)]
for y in range(lenStates):
for y0 in range(lenStates):
#nprob = V[t - 1][y0] * trans_p[y0][y] * emit_p[y][obs[t]]
nprob = V[t - 1][y0] * trans_p[y0][y] * emit_p[y][t]
if nprob & prob:
prob = nprob
state = y0
#记录最大概率
V[t][y] = prob
newpath[y][:t] = path[state][:t]
newpath[y][t] = y
path = newpath
for y in range(lenStates):
if V[lenObs - 1][y] & prob:
prob = V[lenObs - 1][y]
#_print(states, V)
return prob, path[state]
def example():
hiddenstates = ('Healthy', 'Fever')
observations = ('normal', 'cold', 'dizzy')
Healthy': 0.6, 'Fever': 0.4
start_p = [0.6, 0.4]
Healthy' : {'Healthy': 0.7, 'Fever': 0.3},
Fever' : {'Healthy': 0.4, 'Fever': 0.6}
trans_p = [[0.7, 0.3], [0.4, 0.6]]
#发射概率/输出概率/观察概率
Healthy' : {'normal': 0.5, 'cold': 0.4, 'dizzy': 0.1},
Fever' : {'normal': 0.1, 'cold': 0.3, 'dizzy': 0.6}
emit_p = [[0.5, 0.4, 0.1], [0.1, 0.3, 0.6]]
return viterbi(observations,
hiddenstates,
if __name__ == '__main__':
print(example())
中文NLP序列标注之POS
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#2016年 03月 03日 星期四 11:01:05 CST by Demobin
import h5py
import json
import codecs
mappings = {
#人民日报标注集:863标注集
tags_863 = {
[0, '形容词'],
[1, '区别词'],
[2, '连词'],
[3, '副词'],
[4, '叹词'],
[5, '语素字'],
[6, '前接成分'],
[7, '习用语'],
[8, '简称'],
[9, '后接成分'],
[10, '数词'],
[11, '名词'],
[12, '方位名词'],
[13, '人名'],
[14, '团体、机构、组织的专名'],
[15, '处所名词'],
[16, '地名'],
[17, '时间名词'],
[18, '其它专名'],
[19, '拟声词'],
[20, '介词'],
[21, '量词'],
[22, '代词'],
[23, '助词'],
[24, '动词'],
[25, '标点'],
[26, '字符串'],
[27, '非语素字'],
def genCorpusTags():
features = ['b', 'm', 'e', 's']
for tag in tags:
for f in features:
s += '\'' + tag + '-' + f + '\'' + ','
corpus_tags = [
'nh-b','nh-m','nh-e','nh-s','ni-b','ni-m','ni-e','ni-s','nl-b','nl-m','nl-e','nl-s','nd-b','nd-m','nd-e','nd-s','nz-b','nz-m','nz-e','nz-s','ns-b','ns-m','ns-e','ns-s','nt-b','nt-m','nt-e','nt-s','ws-b','ws-m','ws-e','ws-s','wp-b','wp-m','wp-e','wp-s','a-b','a-m','a-e','a-s','c-b','c-m','c-e','c-s','b-b','b-m','b-e','b-s','e-b','e-m','e-e','e-s','d-b','d-m','d-e','d-s','g-b','g-m','g-e','g-s','i-b','i-m','i-e','i-s','h-b','h-m','h-e','h-s','k-b','k-m','k-e','k-s','j-b','j-m','j-e','j-s','m-b','m-m','m-e','m-s','o-b','o-m','o-e','o-s','n-b','n-m','n-e','n-s','q-b','q-m','q-e','q-s','p-b','p-m','p-e','p-s','r-b','r-m','r-e','r-s','u-b','u-m','u-e','u-s','v-b','v-m','v-e','v-s','x-b','x-m','x-e','x-s'
def savePosInfo(path, posInfo):
'''保存分词训练数据字典和概率'''
print('save pos info to %s'%path)
fd = open(path, 'w')
(initProb, tranProb), (vocab, indexVocab) = posInfo
j = json.dumps((initProb, tranProb))
fd.write(j + '\n')
for char in vocab:
fd.write(char.encode('utf-8') + '\t' + str(vocab[char]) + '\n')
fd.close()
def loadPosInfo(path):
'''载入分词训练数据字典和概率'''
print('load pos info from %s'%path)
fd = open(path, 'r')
line = fd.readline()
j = json.loads(line.strip())
initProb, tranProb = j[0], j[1]
lines = fd.readlines()
fd.close()
vocab = {}
indexVocab = [0 for i in range(len(lines))]
for line in lines:
rst = line.strip().split('\t')
if len(rst) & 2: continue
char, index = rst[0].decode('utf-8'), int(rst[1])
vocab[char] = index
indexVocab[index] = char
return (initProb, tranProb), (vocab, indexVocab)
def savePosData(path, posData):
'''保存分词训练输入样本'''
print('save pos data to %s'%path)
#采用hdf5保存大矩阵效率最高
fd = h5py.File(path,'w')
(X, y) = posData
fd.create_dataset('X', data = X)
fd.create_dataset('y', data = y)
fd.close()
def loadPosData(path):
'''载入分词训练输入样本'''
print('load pos data from %s'%path)
fd = h5py.File(path,'r')
X = fd['X'][:]
y = fd['y'][:]
fd.close()
return (X, y)
def sent2vec2(sent, vocab, ctxWindows = 5):
charVec = []
for char in sent:
if char in vocab:
charVec.append(vocab[char])
charVec.append(vocab['retain-unknown'])
#首尾padding
num = len(charVec)
pad = int((ctxWindows - 1)/2)
for i in range(pad):
charVec.insert(0, vocab['retain-padding'] )
charVec.append(vocab['retain-padding'] )
for i in range(num):
X.append(charVec[i:i + ctxWindows])
def sent2vec(sent, vocab, ctxWindows = 5):
chars = []
words = sent.split()
for word in words:
#包含两个字及以上的词
if len(word) & 1:
chars.append(word[0] + '_b')
#词中间的字
for char in word[1:(len(word) - 1)]:
chars.append(char + '_m')
chars.append(word[-1] + '_e')
chars.append(word + '_s')
return sent2vec2(chars, vocab, ctxWindows = ctxWindows)
def doc2vec(fname, vocab):
'''文档转向量'''
#一次性读入文件,注意内存
fd = codecs.open(fname, 'r', 'utf-8')
lines = fd.readlines()
fd.close()
#标注统计信息
tagSize = len(corpus_tags)
tagCnt = [0 for i in range(tagSize)]
tagTranCnt = [[0 for i in range(tagSize)] for j in range(tagSize)]
for line in lines:
#按空格分割
words = line.strip('\n').split()
#每行的分词信息
chars = []
for word in words:
rst = word.split('/')
if len(rst) &= 0:
print word
word, tag = rst[0], rst[1].decode('utf-8')
if tag not in tags_863:
tag = mappings[tag]
#包含两个字及以上的词
if len(word) & 1:
chars.append(word[0] + '_b')
tags.append(corpus_tags.index(tag + '-' + 'b'))
#词中间的字
for char in word[1:(len(word) - 1)]:
chars.append(char + '_m')
tags.append(corpus_tags.index(tag + '-' + 'm'))
chars.append(word[-1] + '_e')
tags.append(corpus_tags.index(tag + '-' + 'e'))
chars.append(word + '_s')
tags.append(corpus_tags.index(tag + '-' + 's'))
#字向量表示
lineVecX = sent2vec2(chars, vocab, ctxWindows = 7)
#统计标注信息
lineVecY = []
lastTag = -1
for tag in tags:
lineVecY.append(tag)
#lineVecY.append(corpus_tags[tag])
#统计tag频次
tagCnt[tag] += 1
#统计tag转移频次
if lastTag != -1:
tagTranCnt[lastTag][tag] += 1
#暂存上一次的tag
lastTag = tag
X.extend(lineVecX)
y.extend(lineVecY)
charCnt = sum(tagCnt)
#转移总频次
tranCnt = sum([sum(tag) for tag in tagTranCnt])
#tag初始概率
initProb = []
for i in range(tagSize):
initProb.append(tagCnt[i]/float(charCnt))
#tag转移概率
tranProb = []
for i in range(tagSize):
for j in range(tagSize):
p.append(tagTranCnt[i][j]/float(tranCnt))
tranProb.append(p)
return X, y, initProb, tranProb
def vocabAddChar(vocab, indexVocab, index, char):
if char not in vocab:
vocab[char] = index
indexVocab.append(char)
index += 1
return index
def genVocab(fname, delimiters = [' ', '\n']):
#一次性读入文件,注意内存
fd = codecs.open(fname, 'r', 'utf-8')
lines = fd.readlines()
fd.close()
vocab = {}
indexVocab = []
#遍历所有行
for line in lines:
words = line.strip().split()
if words &= 0: continue
#遍历所有词
for word in words:
word, tag = word.split('/')
#包含两个字及以上的词
if len(word) & 1:
char = word[0] + '_b'
index = vocabAddChar(vocab, indexVocab, index, char)
#词中间的字
for char in word[1:(len(word) - 1)]:
char = char + '_m'
index = vocabAddChar(vocab, indexVocab, index, char)
char = word[-1] + '_e'
index = vocabAddChar(vocab, indexVocab, index, char)
char = word + '_s'
index = vocabAddChar(vocab, indexVocab, index, char)
#加入未登陆新词和填充词
vocab['retain-unknown'] = len(vocab)
vocab['retain-padding'] = len(vocab)
indexVocab.append('retain-unknown')
indexVocab.append('retain-padding')
#返回字典与索引
return vocab, indexVocab
def load(fname):
print 'train from file', fname
delims = [' ', '\n']
vocab, indexVocab = genVocab(fname)
X, y, initProb, tranProb = doc2vec(fname, vocab)
print len(X), len(y), len(vocab), len(indexVocab)
print initProb
print tranProb
return (X, y), (initProb, tranProb), (vocab, indexVocab)
def test():
load('../data/pos.train')
if __name__ == '__main__':
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#2016年 03月 03日 星期四 11:01:05 CST by Demobin
import numpy as np
import json
import h5py
import codecs
from dataset import pos
from util import viterbi
from sklearn.model_selection import train_test_split
from keras.preprocessing import sequence
from keras.optimizers import SGD, RMSprop, Adagrad
from keras.utils import np_utils
from keras.models import Sequential,Graph, model_from_json
from keras.layers.core import Dense, Dropout, Activation, TimeDistributedDense
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM, GRU, SimpleRNN
from util import pChar
def train(posInfo, posData, modelPath, weightPath):
(initProb, tranProb), (vocab, indexVocab) = posInfo
(X, y) = posData
train_X, test_X, train_y, test_y = train_test_split(X, y , train_size=0.9, random_state=1)
train_X = np.array(train_X)
train_y = np.array(train_y)
test_X = np.array(test_X)
test_y = np.array(test_y)
outputDims = len(pos.corpus_tags)
Y_train = np_utils.to_categorical(train_y, outputDims)
Y_test = np_utils.to_categorical(test_y, outputDims)
batchSize = 128
vocabSize = len(vocab) + 1
wordDims = 100
maxlen = 7
hiddenDims = 100
w2vModel, vectorSize = pChar.load('model/pChar.model')
embeddingDim = int(vectorSize)
embeddingUnknown = [0 for i in range(embeddingDim)]
embeddingWeights = np.zeros((vocabSize + 1, embeddingDim))
for word, index in vocab.items():
if word in w2vModel:
e = w2vModel[word]
print word
e = embeddingUnknown
embeddingWeights[index, :] = e
model = Sequential()
model.add(Embedding(output_dim = embeddingDim, input_dim = vocabSize + 1,
input_length = maxlen, mask_zero = True, weights = [embeddingWeights]))
model.add(LSTM(output_dim = hiddenDims, return_sequences = True))
model.add(LSTM(output_dim = hiddenDims, return_sequences = False))
model.add(Dropout(0.5))
model.add(Dense(outputDims))
model.add(Activation('softmax'))
model.compile(loss = 'categorical_crossentropy', optimizer = 'adam')
result = model.fit(train_X, Y_train, batch_size = batchSize,
nb_epoch = 20, validation_data = (test_X,Y_test), show_accuracy=True)
j = model.to_json()
fd = open(modelPath, 'w')
fd.write(j)
fd.close()
model.save_weights(weightPath)
return model
#Bi-directional LSTM
def loadModel(modelPath, weightPath):
fd = open(modelPath, 'r')
j = fd.read()
fd.close()
model = model_from_json(j)
model.load_weights(weightPath)
return model
# 根据输入得到标注推断
def posSent(sent, model, posInfo):
(initProb, tranProb), (vocab, indexVocab) = posInfo
vec = pos.sent2vec(sent, vocab, ctxWindows = 7)
vec = np.array(vec)
probs = model.predict_proba(vec)
#classes = model.predict_classes(vec)
prob, path = viterbi.viterbi(vec, pos.corpus_tags, initProb, tranProb, probs.transpose())
words = sent.split()
index = -1
for word in words:
for char in word:
index += 1
ss += word + '/' + pos.tags_863[pos.corpus_tags[path[index]][:-2]][1].decode('utf-8') + ' '
#ss += word + '/' + pos.corpus_tags[path[index]][:-2] + ' '
return ss[:-1]
def posFile(fname, dstname, model, posInfo):
fd = codecs.open(fname, 'r', 'utf-8')
lines = fd.readlines()
fd.close()
fd = open(dstname, 'w')
for line in lines:
rst = posSent(line.strip(), model, posInfo)
fd.write(rst.encode('utf-8') + '\n')
fd.close()
def test():
print 'Loading vocab...'
#(X, y), (initProb, tranProb), (vocab, indexVocab) = pos.load('data/pos.train')
#posInfo = ((initProb, tranProb), (vocab, indexVocab))
#posData = (X, y)
#pos.savePosInfo('./model/pos.info', posInfo)
#pos.savePosData('./model/pos.data', posData)
posInfo = pos.loadPosInfo('./model/pos.info')
posData = pos.loadPosData('./model/pos.data')
print 'Done!'
print 'Loading model...'
#model = train(posInfo, posData, './model/pos.w2v.model', './model/pos.w2v.model.weights')
model = loadModel('./model/pos.w2v.model', './model/pos.w2v.model.weights')
#model = loadModel('./model/pos.model', './model/pos.model.weights')
print 'Done!'
print '-------------start predict----------------'
s = u'为 寂寞 的 夜空 画 上 一个 月亮'
print posSent(s, model, posInfo)
#posFile('~/work/corpus/icwb2/testing/msr_test.utf8', './msr_test.utf8.pos', model, posInfo)
if __name__ == '__main__':
中文NLP序列标注之NER
中文NLP序列标注之DP
To be continue...
PS:全贴代码有点长,等我找时间再整理一下。
机器学习(Machine Learning)&深度学习(Deep Learning)资料(Chapter 1) 注:机器学习资料篇目一共500条,篇目二开始更新 希望转载的朋友,你可以不用联系我.但是一定要保留原文链接,因为这个项目还在继续也在不定期更新.希望看到文章的朋友...
References: 《speech and language processing 》2nd & 3rd 《统计自然语言处理》第二版
补充最近有一个观点愈发明晰: 深度学习模型只能作为统计学的模型, 用于建模所给的数据的分布. 无论是辨别式任务还是生成...
常用概念: 自然语言处理(NLP) 数据挖掘 推荐算法 用户画像 知识图谱 信息检索 文本分类 常用技术: 词级别:分词(Seg),词性标注(POS),命名实体识别(NER),未登录词识别,词向量(word2vec),词义消歧 句子级别:情感分析,关系提取,意图识别,依存句...
机器学习(Machine Learning)&深度学习(Deep Learning)资料(Chapter 1) 廖君机器学习资料 注:机器学习资料篇目一共500条,篇目二开始更新 希望转载的朋友,你可以不用联系我.但是一定要保留原文链接,因为这个项目还在继续也在不定期更新....
《Brief History of Machine Learning》 介绍:这是一篇介绍机器学习历史的文章,介绍很全面,从感知机、神经网络、决策树、SVM、Adaboost到随机森林、Deep Learning. 《Deep Learning in Neural Netw...
关在象牙塔里的孩子永远不知道外面的世界是什么样子的,想象中或是温柔的,美好的。 所以一有机会可以走出这座塔,内心中的雀跃急速涌来,跳跃着欢呼着满满都是好奇的迎接它们。 可以遇见许多比我优秀的人,或是不如我的人;可以遇见和善的人,或是险恶的人;亦可以遇见温柔有礼的人,或是我行...
一时书 为智库捐款一元 为血癌儿童捐款两元 路上小心开车,礼让路人 二时书 纸箱厂涨价,有点焦虑。耐心用友善的语言沟通 图纸出错,需要沟通。 三时书 种子开花啦,分三万元 获得祺予智慧的分享。看到他人做到的部分 四时书 支持同事多拿荣誉证书,为未来发展做好准备 坏种子爆,货...
有人会问,我们同在一个公司,凭什么别人可以拿30000,我只能拿3000? 拿3000工资与30000工资,究竟有什么区别? 5张聊天截图告诉你人与人之间的差别! 情景一 情景二 ▼ 这就是差距! 一、刚入职时 普通员工 看重工资的高低,在一无所长的前提下,没有想过学习丰富...
(一) 明月当空,如水般泄下,万物皆被披上了一层银白色的外衣,天空格外干净,没有一点乌云,有的是星光灿烂和划破夜空的颗颗流星。这里是峒圣山崖头,山崖一面是万丈深渊,深不见底,长年云气笼罩,一面是欲与天空试比高的孤峰,异常陡峭,只在峰脚和悬崖边的间隔处有一片平坦的空地,可供人...
人到中年草木悲,苍凉心境有谁知,蒹葭白露魂销夜,山鬼女萝肠断时。几缕斜阳新旧梦,满天飞雪死生痴。欲将头上斑斑发,系上明春杨柳枝。 雪压霜枝暮色重,幽怀应许诉流虹。少年孤傲云中鹤,陈迹飘零雪里鸿。空有诗名传塞北,恨无宝剑馈关东。行人莫问欲何去,惆怅西山落日红。 小楼昨夜立深宵...

我要回帖

更多关于 区块链 伪命题 的文章

 

随机推荐