基于压缩感知机的中文分词学习笔记

来源:互联网 发布:宁波最新房价走势知乎 编辑:程序博客网 时间:2024/05/22 13:43

中文分词学过CRFRNN等,基于压缩感知机的中文分词还没接触过,发现了一篇很好的压缩感知机的中文分词博客,http://www.hankcs.com/nlp/segment/implementation-of-word-segmentation-device-java-based-on-structured-average-perceptron.html,作者用的是java的实现,由于本人对python比较熟悉,在此简单记录一下基于压缩感知机的中文分词及其的python代码的学习笔记,代码参考地址:https://github.com/zhangkaixu/minitools/blob/master/cws.py

原理为对于每个字ci,取其7个特征,分别为ci,ci-1,ci+1,ci-2ci-1,ci-1ci,cici+1,ci+1ci+2,特征生成代码如下:

def update(self, x, y, delta):  # 更新权重    for i, features in zip(range(len(x)), self.gen_features(x)):        for feature in features:            self.weights.update_weights(str(y[i]) + feature, delta)#每个特征为tag+word    for i in range(len(x) - 1):        self.weights.update_weights(str(y[i]) + ':' + str(y[i + 1]), delta)#更新相邻两个tag的特征的权重

因此,每个字可以生成7个特征,这7个特征与该字的前后两个字相关,同时在分词的时候,我们需要对每个词分类,例如BMES,分别对于0,1,2,3数字,我们生成每两个标签的特征,例如01,00,11,13,...。这样总共生成了N组个特征,我们对每个特征分配一个权重,在训练的时候,若分类结果正确,则对于的特征权重+1,分类错误,则特征权重-1

在训练的时候,对于输入句子x,和分类结果y,我们首先通过genfeaturex)函数生成句子中每个字的特征向量,之后根据每个字的每个特征向量(7个),更新每个字的权重,代码如下:

def update(self, x, y, delta):  # 更新权重    for i, features in zip(range(len(x)), self.gen_features(x)):        for feature in features:            self.weights.update_weights(str(y[i]) + feature, delta)#每个特征为tag+word    for i in range(len(x) - 1):        self.weights.update_weights(str(y[i]) + ':' + str(y[i + 1]), delta)#更新相邻两个tag的特征的权重

上面的代码可知,对于每个字,生成了7个特征,每个字可能有4个分类结果,因此将7个特征与4个分类结果组合,需要28个特征权重与其对应,同时对于分类结果tag,每相邻两个字的tag组合为一个特征,有16中组合结果,即转移特征transitions,也需要16个特征权重。根据分类结果我们可以根系每个特征对于的权重。特征权重更新代码如下:

def update_weights(self, key, delta):  # 更新权重    if key not in self._values:        self._values[key] = 0        self._acc[key] = 0        self._last_step[key] = self._step        self.updateF[key]=1    else:        self._new_value(key)        self.updateF[key]+=1    self._values[key] += delta#特征更新权重

代码中key为特征,delta为更新不长,若分类正确,delta=1,错误则delta=-1

训练:训练过程为首先对于输入句子x,采用viterbi 算法进行解码:

def decode(self, x):  # 类似隐马模型的动态规划解码算法    # 类似隐马模型中的转移概率    transitions = [[self.weights.get_value(str(i) + ':' + str(j), 0) for j in range(4)]                   for i in range(4)]    # 类似隐马模型中的发射概率    emissions = [[sum(self.weights.get_value(str(tag) + feature, 0) for feature in features)                  for tag in range(4)] for features in self.gen_features(x)]    # 类似隐马模型中的前向概率    # if len(emissions)<1:    #     print("x is ")    #     print(x)    alphas = [[[e, None] for e in emissions[0]]]    for i in range(len(x) - 1):        alphas.append([max([alphas[i][j][0] + transitions[j][k] + emissions[i + 1][k], j]                           for j in range(4))                       for k in range(4)])    # 根据alphas中的“指针”得到最优序列    alpha = max([alphas[-1][j], j] for j in range(4))    i = len(x)    tags = []    while i:        tags.append(alpha[1])#先计算最后一个状态,再往前推        i -= 1        alpha = alphas[i][alpha[1]]    return list(reversed(tags))

首先初始化tag16中转移特征的权重即transition矩阵为0,生成句子x的特征,我们用self.values保存特征权重,刚开使时self.values={}为空,对于每个特征f,若其不在self.values中,则将其添加进self.values中,且初始化self.values[f]=0。解码阶段,生成x的特征之后,便可以查找self.values中特征的权重,得到转移矩阵trainsiton,发射矩阵emissions,再根据viterbi算法,解码得到预测值z.

得到y_pred后便可以更新self.values,代码如下:

if z != y:    cws.update(x, y, 1)    cws.update(x, z, -1)

代码中,z为预测值,y为输入正确值,若预测值语正确值不想等,则更新两次权重,对y,更新权重delta=1,对于错误分类值z,更新权重delta=-1

在预测阶段,我们便可以直接对输入句子x,生成特征,得到t转移矩阵trainsiton,发射矩阵emissions,再采用viterbi算法解码得到预测值y

总结,在训练的时候,由于对于句子中的每个字会产生7个特征,对于同一个字,若其前后2个字不同,则产生的特征又不同,这样会产生很多特征,而很多时候特征分词的时候是不需要用到的,保留这些无用的特征会需要很大的空间,因此通常会想到对特征进行压缩。

邓知龙 《基于感知器算法的高效中文分词与词性标注系统设计与实现》中有提到对模型进行压缩,具体方法是在更新每个特征权重的时候,记录每个特征权重更新的次数,对于分词结果影响重要的特征,我们在训练的时候对其的更新当然比较频繁,其更新次数会较大,而另一些影响较小的特征其更新次数较小,因此我们可以将更新次数较小的特征去除,从而压缩模型。

0 0
原创粉丝点击