自然语言处理(二)统计分词(隐马尔可夫模型)

一、统计分词简介

随着大规模语料库的建立,统计机器学习方法的研究和发展,基于统计的中文分词算法渐渐成为主流,其主要思想是把每个词看作是词的最小单位的各个字组成的,如果相连的字在不同的文本中出现的而次数越多,就证明这相连的字很可能是一个词。因此我们就可以利用字与字相邻出现的频率来反应成词的可靠度,统计语料中相邻共现的各个字的组合的频度,当组合频度高于某一个临界值时,我们便可认为此字组可能会构成一个词语。

基于统计的分词,一般要做如下两步操作:

1)建立统计语言模型。

2)对句子进行单词的划分,然后对划分结果进行概率计算,获得概率最大的分词方式。其中用于分词的模型可以是隐含马尔可夫(HMM)或者是条件随机场(CRF)等统计学习的算法。

二、语言模型介绍

对于一个句子来说,用概率论的专业术语描述语言模型就是:为长度m的字符串确定其概率分布为p(w1, w2,......,wm),其中w1到wm依次表示文本中各个词语。一般采用链式法则计算其概率值

p(w1, w2, ..,wm) = p(w1)p(w2|w1)p(w3|w1,w2)....p(wm|w1, w2,...,wm-1)

当文本过长的时候,公式右部从第三项起的每一项计算难度都很大。为解决问题,有人提出了n元模型(n-gram)降低该计算难度。所谓的n元模型就是在估算条件概率时,忽略距离大于等于n的上文词的影响,因此p(w1,w2,...wi-1)的计算可以简化成

p(w1,w2,...wi-1) = p(wi|wi-(n-1),...,wi-1))

对于这个公式的优化目的是保证一定的分词正确性的同时尽最大可能简化公式的计算代价。很明显对于n越大那么这个模型的分词正确率应该是越高的,相对应的是极大的计算代价,这样的计算代价很明显是无法接受的,因此在缩短n的长度的同时保证一定的分词准确性是模型最应该体现的一点。

三、隐含马尔可夫模型

 隐含马尔可夫模型(HMM)是将分词作为字在字符串中的序列标注任务来实现的。其基本思路是:每个字在构造一个特定的词语时都占据着一个确定的构词位置(即词位),现规定每一个字最多只有四个构词位置:即B(词首)、M(词中)、E(词尾)、S(单独成词),对于下面句子1)的分词结果就可以直接表示成如2)所示的逐字标注形式:

1)中文/分词/是/文本处理/不可或缺/的/一步!

2)中/B文/E分/B词/E是/S文/B本/M处/M理/E不/B可/M或/M缺/E的/S一/B步/E!/S

class HMM_Cell(object):
    def __init__(self, config):
        self.n_classes = config.n_classes
        self.n_feature = config.n_feature

        # init define three probability
        self.init_probability = np.zeros((self.n_classes, 1))
        self.transition = np.zeros((self.n_classes, self.n_classes))
        self.emission = np.zeros((self.n_feature, self.n_classes))

    def fit(self, x, y):
        """
        using train data and train target to fit three probabilities
        :param x: example x is like [[12, 33, 44], [22, 332], [231]]
        :param y: example y is like [[1, 2, 3], [0, 2], [3]]
        :return: None
        """
        for i in range(len(x)):
            for j in range(len(x[i])):

                # init probability
                if j == 0:
                    self.init_probability[y[i][j]] += 1

                # transition probability
                else:
                    self.transition[y[i][j], y[i][j - 1]] += 1

                # emission probability
                self.emission[x[i][j]][y[i][j]] += 1

        # laplace smoothing
        self.init_probability = (self.init_probability + 1) / np.sum(self.init_probability)
        self.transition = (self.transition + 1) / (np.sum(self.transition) + self.n_classes)
        self.emission = (self.emission + 1) / (np.sum(self.emission) + self.n_feature)

    # viterbi algorithm(without start node and stop node)
    def predict_label(self, x):
        """
        using Viterbi algorithm and probabilities to calculate max score path
        :param x: example x is like [[12, 33, 44], [22, 332], [231]]
        :return: predicted label, the shape is same as x
        """
        best_path_ids, label_col = [], []
        for i in range(len(x)):
            # forward_var denote end this step each node will get max score
            forward_var = np.log(self.init_probability)

            for j in range(len(x[i])):
                if j == 0:
                    forward_var += np.log(self.emission[x[i][j]].reshape(-1, 1))

                else:
                    current_score, current_ids = np.zeros((self.n_classes, 1)), np.zeros(self.n_classes, dtype=int)
                    for next_tag in range(self.n_classes):
                        next_score = forward_var + np.log(self.transition[next_tag].reshape(-1, 1))
                        max_score_id = np.argmax(next_score.reshape(-1))

                        current_score[next_tag] = np.max(next_score)
                        current_ids[next_tag] = max_score_id

                    # update forward_var with each step
                    forward_var = current_score + np.log(self.emission[x[i][j]].reshape(-1, 1))
                    best_path_ids.append(current_ids.tolist())

            start_node = np.argmax(forward_var)
            label_col.append(self.find_path(start_node, best_path_ids))
            best_path_ids = []

        return label_col

    def find_path(self, start_node, best_path_ids):
        label = [start_node]
        for i in range(len(best_path_ids) - 1, -1, -1):
            label.append(best_path_ids[i][label[-1]])
        label.reverse()
        return label

详细代码见:https://github.com/3030curry30/HMM

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值