分布式表示
用一个词附近的其他词来表示该词 。基于人的语言表达,认为一个词是由这个词的周边词汇一起来构成精确的语义信息。
共现矩阵
词文档的共现矩阵主要用于发现主题(topic),用于主题模型,如LSA。局域窗中的word-word共现矩阵可以挖掘语法和语义信息
- I like deep learning.
- I like NLP.
- I enjoy flying
有以上三句话,设置滑窗为2,可以得到一个词典:{“I like”,“like deep”,“deep learning”,“like NLP”,“I enjoy”,“enjoy flying”}。
counts | i | like | enjoy | deep | learning | nlp | flying | . |
---|---|---|---|---|---|---|---|---|
i | 0 | 2 | 1 | 0 | 0 | 0 | 0 | 0 |
like | 2 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
enjoy | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
deep | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
learning | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
nlp | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
flying | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
. | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
中间每个格子表示的是行和列组成的词组在词典中共同出现的次数即共现特性。
存在的问题:
- 向量维数随 着词典大小线性增长。
- 存储整个词典的空间消耗非常大。
- 一些模型如文本分类模型会面临稀疏性问题。
- 模型会欠稳定,每新增一份语料进来,稳定性就会变化。
NNLM(Neural Network Language model)
神经网络语言模型是03年提出来的,通过训练得到中间产物——词向量矩阵,即文本表示向量矩阵。
NNLM定义一个前向窗口大小,把这个窗口中最后一个词当作y把之前的词当作输入x,通俗来说就是预测这个窗口中最后一个词出现概率的模型。
NNLM网络 结构图如下:
-
input层是一个前向词的输入,是经过one-hot编码的词向量表示形式,具有V*1的矩阵。
-
C矩阵是投影矩阵,也就是稠密词向量表示,在神经网络中是w参数矩阵,该矩阵的大小为D*V,正好与input层进行全连接(相乘)得到
D*1的矩阵,采用线性映射将one-hot表示投影到稠密D维表示。
C = ( w 1 , w 2 , . . . , w v ) = ( ( w 1 ) 1 ( w 2 ) 1 ⋯ ( w V ) 1 ( w 1 ) 2 ( w 2 ) 2 ⋯ ( w V ) 2 ⋮ ⋮ ⋱ ⋮ ( w 1 ) D ( w 2 ) D ⋯ ( w V ) D ) \text{C}=({{w}_{1}},{{w}_{2}},...,{{w}_{v}})=\left( \begin{matrix} {{({{w}_{1}})}_{1}} & {{({{w}_{2}})}_{1}} & \cdots & {{({{w}_{V}})}_{1}} \\ {{({{w}_{1}})}_{2}} & {{({{w}_{2}})}_{2}} & \cdots & {{({{w}_{V}})}_{2}} \\ \vdots & \vdots & \ddots & \vdots \\ {{({{w}_{1}})}_{D}} & {{({{w}_{2}})}_{D}} & \cdots & {{({{w}_{V}})}_{D}} \\ \end{matrix} \right) C=(w1,w2,...,wv)=⎝⎜⎜⎜⎛(w1)1(w1)2⋮(w1)D(w2)1(w2)2⋮(w2)D⋯⋯⋱⋯(wV)1(wV)2⋮(wV)D⎠⎟⎟⎟⎞
-
output层(softmax)自然是前向窗口中需要预测的词。
-
通过BP+SGD得到最优的C投影矩阵,这就是NNLM的中间产物,也是我们所求的文本表示矩阵。通过NNLM将稀疏矩阵投影到稠密向量矩阵中。
Word2Vec
Word2Vec实际是一种浅层的神经网络模型,它有两种网络结构,分别是CBOW(Continues Bag of Words)连续词袋和Skip-gram
CBOW:
CBOW获得中间词两边的上下文,然后用周围的词去预测中间的词,把中间的词当作y,把窗口中的其他词当作x输入,x输入是经过one-hot编码过的,然后通过一个隐藏层进行求和操作,最后通过激活函数softmax,可以计算出每个单词的生成概率,接下来的任务就是训练神经网络的权重,使得语料库中所有单词的整体生成概率最大化,而求得的权重矩阵就是文本表示词向量的结果。
Skip-gram:
通过当前词来预测窗口中上下文词出现的概率模型,把窗口中其它词当作y,依然是通过一个隐藏层接一个Softmax激活函数来预测其它词的频率。如下图所示:
优化方法:
-
层次Softmax:如果单只是接一个softmax激活函数,计算量还是很大的,有多少词就会有多少维的权重矩阵,所以此处提出层次Softmax(Hierarchical Softmax),使用Huffman Tree来编码输出层的词典,相当于平铺到各个叶子节点上,瞬间把维度降低到了树的深度,这棵树把出现频率高的词放到靠近根节点的叶子节点初,每一次只要做二分类计算,计算路径上所有非叶子节点词向量的贡献即可。
Huffman Tree:给定N个权值作为N个叶子节点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树,它是带权路径长度最短的树,权值较大的节点离根较近。
-
负例采样(Negative Sampling):此种优化方式做的事情是在正确单词以外的负样本中进行采样,最终目的是为了减少负样本的数量,达到减少计算量的效果。将词典中的每一个词对应一条线段,,所有词组成了[0,1]间的剖分,然后每次随机生成一个[1,M-1]间的整数,看落在哪个词对应的剖分上就选择哪个词,最后会得到一个负样本集合。
Word2Vec存在的问题:
- 对每个local context window单独训练,没有利用包含在global co-currence矩阵中的统计信息。
- 对多词义无法很好地表示和处理,因此使用了唯一的词向量。
import logging
import os.path
import sys
import multiprocessing
from gensim.corpora import WikiCorpus
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
# 定义输入输出
basename = "F:/temp/DL/"
inp = basename+'wiki.zh.text'
outp1 = basename+'wiki.zh.text.model'
outp2 = basename+'wiki.zh.text.vector'
program = os.path.basename(basename)
logger = logging.getLogger(program)
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s')
logging.root.setLevel(level=logging.INFO)
logger.info("running %s" % ' '.join(sys.argv))
# check and process input arguments
if len(sys.argv) < 4:
print(globals()['__doc__'] % locals())
model = Word2Vec(LineSentence(inp), size=400, window=5, min_count=5,
workers=multiprocessing.cpu_count())
# trim unneeded model memory = use(much) less RAM
#model.init_sims(replace=True)
model.save(outp1)
model.save_word2vec_format(outp2, binary=False)
Aho-Corasick Automaton(AC自动机)
是一种基于前缀的,使用了确定有限自动机(DFA)原理的字符串多模匹配算法。其核心功能是:实现字符串多模匹配。
DFA也就是确定有限自动机Deterministic Finite Automation.
自动机是接受输入值和状态值,输出同为状态值的结果的代码块;有限是指自动机接受的状态种类是有限的;确定的是指自动机的输出状态是单一的一个状态。
def automata(value, state): if state == 0: if value =='a': return 0 elif value =='b': return 1 elif state == 1: if value == 'a': return 0 elif value == 'b': return 1
Aho-Corasic算法基本原理:
Tire Tree的由来:构建一个DAF状态转化图,使得输入下面三条字符串以后,都能得到终点态:aababbaba\aaabbaba\aabababa.得到如下的状态转换图
状态转化图过程:状态0为初始态,状态9为终点态。结构与逻辑如下:
-
首先数据结构应该是使用链表来表示每个节点
-
代码逻辑大体如下:
- 接收输入的 字符,判断是否和当前状态所在节点的字符内容相同;
- 若不同,或当前状态节点为初始态节点,则本次输入字符新建节点。然后将当前状态所在节点的next指针指向新建的节点;
- 若相同,则略过本次输入,跳转回a.接收下一字符
但所得的结果跟用纸笔画出的状态转化图不一样。区别在于变成了树状的分叉结构,针对这种树状结构,,只要把状态节点换成对应的输入字符内容就是Tire Tree了,也叫单词查找树。
- 每条边对应一个字母。
- 每个节点对应一项前缀。叶节点对应最长前缀,即单词本身
- 共有前缀即共有前项分枝。
- 查询操作非常简单
相较于Tire Tree而言,Aho-Corasick算法引入Fail路径进行算法优化。
这些曲线表Fail路径,就是我们输入的字符内容,无法使状态值从当前状态节点,跳转到下一个预定的状态节点时,我们就通过Fail路径回溯到前面的某一个状态节点。
如何构建Fail路径?
Fail路径就是指向另一个可以作为某一范式前缀的节点。构建逻辑:
-
如果自己是根节点,则其Fail路径指向自己;
-
如果自己的父节点是根节点,则其fail路径扔指向根节点;
-
找到自己父节点Fail路径指向的节点,执行如下操作:
1)如果父节点的fail路径指向的节点可以正常接收自己的输入字符,那么就指向这个节点接受输入字符后所指向的那个节点。
2)如果父节点的fail路径指向的节点不能正常接受自己的输入字符,就按照第三步的判断,检查自己父节点的父节点的Fail路径指向的节点;
-
依次递推,如果直到回溯至根节点还没找到的话,那么其Fail路径就指向根节点;
如何进行状态跳转?
- 如果当前这个节点,可以正常接收输入值,那么就跳转到输入值对应的下一个节点,本轮跳转结束,接收下一个输入值以进入下一轮跳转;
- 如果当前这个节点,不能正常接受输入值,那么就先跳转到自己Fail路径指向的节点,然后在尝试执行第一步;
- 如果现在已经跳转回到了根节点,那么先尝试第一步,若失败,则不再执行第二步,而是停留在根节点了,本轮跳转结束,接受下一个输入值已进入下一轮跳转。
如何得到匹配结果?
- 每轮跳转结束后,所停留在的节点,如果是终点态,则该节点对应的模式串匹配成功;
- 从停留的结点开始,一路沿着Fail路径递归至根节点,期间路过的所有节点,只要是终点态节点,则该节点对应的模式串也就匹配成功。
"""
给定一个wordlist列表
"""
import ahocorasick
actree = ahocorasick.Automaton()
for index, word in enumerate(wordlist):
actree.add_word(word, (index, word))
actree.make_automaton()
for i in actree.iter('昨天发烧,服用了阿司匹林。。。。'):
print(i)
region_wds = []
for i in actree.iter('昨天发烧,服用了阿司匹林,并且吃了牛黄清胃丸,饭吃了瓜烧白菜,大便颜色浅'):
wd = i[1][1] #i形如(31, (13, '白菜')),通过i[1][1]切片出‘白菜’
print("wd:", wd)
region_wds.append(wd) # 将匹配到的word添加到列表region_wds中
print("region_wds:", region_wds)
stop_wds = []
for wd1 in range_wds:
for wd2 in range_wds:
if wd1 in wd2 and wd1 != wd2:
print("w1:{}, w2:{}".format(wd1,wd2))
stop_wds.append(wd1)
print("stop_wds:", stop_wds)
final_wds = [i for i in region_wds if i not in stop_wds]
#当欲匹配的wordlist中包含string和它的截断字符串组成的sub_string时,剔除sub_string部分的匹配项。
print("final_wds:", final_wds)
根据Automaton.iter()方法的返回结果为一个二元组,它由两部分构成:
- input字符串所匹配到的key的结束索引;
- 所匹配到的key的关联value【(insertion index, original string)】
ord()
python中ord()函数主要用于返回对应字符的ascii码,用于从给定字符值中获取数字值,它接收一个字符并返回一个整数,即用于将字符转换为整数。chr()主要用来表示ascii码对应的字符。
val = 'A'
print("ASCII code of ", val, "is = ", ord(val))
val = 'x'
print("ASCII code of ", val, "is = ", ord(val))
val = '@'
print("ASCII code of ", val, "is = ", ord(val))
val = '8'
print("ASCII code of ", val, "is = ", ord(val))
>>>
ASCII code of A is = 65
ASCII code of x is = 120
ASCII code of @ is = 64
ASCII code of 8 is = 56