概率图模型相关知识

概率图模型知识记录

基本概念

  • 概率图模型:在概率模型的基础上,使用基于图的方法来表示概率分布,是一种通用化的不确定性知识表示和处理方法。节点表示随机变量,边表示依赖关系。

  • 概率图模型分类:
    有向图模型:一般是生成式模型
    无向图模型:一般是判别式模型
    生成模型与判别模型的本质区别:模型中观测序列x与状态序列y的决定关系。
    【见《统计自然语言处理方法》105页】

  • 图模型的三个基本问题:
    (1)表示问题:对于一个概率模型,如何通过图结构来描述变量之间的依赖关
    系.
    (2)学习问题:图模型的学习包括图结构的学习和参数的学习.
    (3) 推断问题:在已知部分变量时,计算其他变量的条件概率分布.

    图来源:《神经网络与深度学习》

    用图模型只是换一种表示,真正重要的是其中的条件独立性假设,能够减少参数个数。

  • 自然语言处理中概率图模型的演变过程

    朴素贝叶斯与逻辑回归解决一般的分类问题;
    HMM与线性的CRF解决“线式”序列问题,比如序列标注问题。
    生成式有向图模型、通用的CRF解决一般性的图问题。

有向图模型

有向图模型(Directed Graphical model),也称为贝叶斯网络(Bayesian
Network),或信念网络(Belief Network,BN),是一类用有向图来描述随机向
量概率分布的模型.

  • 条件独立:贝叶斯网络所依赖的一个核心概念,两节点没连接表示 两个随机变量在某些情况下条件独立,两个节点连接表示两个随机变量在任何情况下都不条件独立。

隐马尔可夫模型

  • 模型抽象表示: λ = ( A , B , π ) \lambda = (A,B,\pi ) λ=(A,B,π)
    (1)HMM由初始状态概率向量 π \pi π、状态转移概率矩阵A和观测概率矩阵B决定。
    (2) π \pi π和A确定了隐藏的马尔卡夫链,生成不可观测的状态序列。
    (3)B确定了如何从状态生成观测,与状态序列综合确定了如何产生观测序列。
  • 基本假设:
    (1)齐次马尔可夫性:任意时刻的状态只依赖上一时刻的状态。
    P ( i t ∣ i t − 1 , . . . . . . . ) = P ( i t ∣ i t − 1 ) P(i_t|i_{t-1},.......) = P(i_t|i_{t-1}) P(itit1,.......)=P(itit1)【转移概率】
    (2)观测独立性假设:任意时刻的观测只依赖当前时刻状态。
    P ( o t ∣ i t . . . . . . . ) = P ( o t ∣ i t ) P(o_t|i_t.......) =P(o_t|i_t) P(otit.......)=P(otit) 【发射概率】
  • 三个基本问题:
    (1)概率计算: P ( O ∣ λ ) P(O|\lambda) P(Oλ)
    (2)学习问题:估计参数,使得$max P(O|\lambda) $
    (3)推断问题:给定观测序列O, m a x P ( I ∣ O ) max P(I|O) maxP(IO)
概率计算:

分为前向算法与后向算法

学习问题

参数估计可以分为有监督、无监督。
有监督方法基于极大似然估计的思想,用频率估计概率。
{ ( O 1 , I 1 ) , ( O 2 , I 2 ) . . . . . . . . . . . } \{(O_1,I_1),(O_2,I_2)...........\} {(O1,I1),(O2,I2)...........} ——> ( π , A , B ) (\pi,A,B) (π,A,B)

无监督方法基于EM算法,这边还没理解:意思是给了一堆观测序列,就可以得到参数?
【留个坑】

推断问题【解码】

维特比算法:动态规划的思想求概率最大的路径,每个路径对应一个状态序列。
输入:模型和观测序列(一个句子)
输出:状态序列(每个词的标签)
维护两个变量。

  • 时刻t(第t个词)状态为 i t i_t it时的最优路径。
    递推计算:上一时刻的所有状态对应的最优路径转移到状态 i t i_t it,再由状态 i t i_t it发射 到观测 o t o_t ot所有路径中概率最大的那个。
  • 时刻t状态 i t i_t it所有单个路径中概率最大路径(上面计算得到的)的前一个节点(即第t-1个词)
    目的:方便求得最后一个节点的最优路径后,进行回溯。

简单来说:计算时是前向逐步计算,直到最后一个节点(词)。每个节点(词)都有对应所有状态的单个最优路径值。到最后一个节点(词)求得最优路径后,开始回溯,返回这条路径上每个节点对应的状态。
在这里插入图片描述
图来源:隐马尔科夫模型HMM(四)维特比算法解码隐藏状态序列【都是统计学习方法书上的例子】

代码的实现也比较简单,自己简单罗列了个伪代码,看思路和别人一样就没写了。
具体可参考:
维特比算法:算法详解+示例讲解+Python实现

无向图

条件随机场

条件随机场的理论说明-《统计学习方法》
网上的帖子都很详细。
几个不错的学习链接

两个问题:

  • 再谈理论的时候,都是说crf的条件概率是两种特征函数,(t 与 s)。实际实现中,通常说法是转移矩阵与发射得分向量,应该是对应t与s。但在书上,并没有对t于s的数量做要求(转移矩阵与发射向量应该是固定的)。可能理论描述是更General的情况?
  • 在Bilstm+CRF中,发射得分是BiLSTM的输出(可以不用softmax,因为无向图得分只是一个权重,不是概率。用softmax反而对于降低那些比较小值得权重,不必要归一化)。那不用Bilstm,直接embedding+映射,作为Baseline。
  • 在进行参数更新时,实际中损失函数比较好形容,就是实际路径得分/所有路径得分之和。然后CRF部分的参数是转移矩阵,利用梯度下降进行参数更新。但没有谈论具体求解梯度的细节,因为是通过工具完成的。这点在电子版学习笔记中有涉及。

代码部分

先看看tensorflow中的代码【tf2.0以后移到了tfa模块】:
Module: tfa.text

  1. 训练部分

    损失函数:tfa.text.crf_log_likelihood

def crf_log_likelihood(
    inputs: TensorLike,
    tag_indices: TensorLike,
    sequence_lengths: TensorLike,
    transition_params: Optional[TensorLike] = None,
) -> tf.Tensor:
    num_tags = inputs.shape[2]

    # cast type to handle different types
    tag_indices = tf.cast(tag_indices, dtype=tf.int32)
    sequence_lengths = tf.cast(sequence_lengths, dtype=tf.int32)

    if transition_params is None:
        initializer = tf.keras.initializers.GlorotUniform()
        transition_params = tf.Variable(
            initializer([num_tags, num_tags]), "transitions"
        )

    sequence_scores = crf_sequence_score(
        inputs, tag_indices, sequence_lengths, transition_params
    )
    log_norm = crf_log_norm(inputs, sequence_lengths, transition_params)

    # Normalize the scores to get the log-likelihood per example.
    log_likelihood = sequence_scores - log_norm
    return log_likelihood, transition_params

简单来说,输入句子+标签+长度+转移矩阵(迭代的),输出似然值+新的转移矩阵
计算这个式子:log_likelihood = sequence_scores - log_norm

第一部分:sequence_scores的函数:

def crf_sequence_score(
    inputs: TensorLike,
    tag_indices: TensorLike,
    sequence_lengths: TensorLike,
    transition_params: TensorLike,
) -> tf.Tensor:
    tag_indices = tf.cast(tag_indices, dtype=tf.int32)
    sequence_lengths = tf.cast(sequence_lengths, dtype=tf.int32)
    def _single_seq_fn():
        batch_size = tf.shape(inputs, out_type=tf.int32)[0]
        batch_inds = tf.reshape(tf.range(batch_size), [-1, 1])
        indices = tf.concat([batch_inds, tf.zeros_like(batch_inds)], axis=1)

        tag_inds = tf.gather_nd(tag_indices, indices)
        tag_inds = tf.reshape(tag_inds, [-1, 1])
        indices = tf.concat([indices, tag_inds], axis=1)

        sequence_scores = tf.gather_nd(inputs, indices)

        sequence_scores = tf.where(
            tf.less_equal(sequence_lengths, 0),
            tf.zeros_like(sequence_scores),
            sequence_scores,
        )
        return sequence_scores

    def _multi_seq_fn():
        # Compute the scores of the given tag sequence.
        unary_scores = crf_unary_score(tag_indices, sequence_lengths, inputs)
        binary_scores = crf_binary_score(
            tag_indices, sequence_lengths, transition_params
        )
        sequence_scores = unary_scores + binary_scores
        return sequence_scores

    return tf.cond(tf.equal(tf.shape(inputs)[1], 1), _single_seq_fn, _multi_seq_fn)

如果是序列长度为1,则是_single_seq_fn,否则就是_multi_seq_fn。
一般肯定一个句子长度不为1。所以看看_multi_seq_fn中:
sequence_scores = unary_scores + binary_scores
unary_scores:对应的是句子每个单词选择这个标签的得分。【之和】
binary_scores:对应的是有转移矩阵计算出标签转移得分。【之和】
sequence_scores:对应的是正确的路径得分

第二部分: log_norm的函数
只看序列长度大于1的:

    def _multi_seq_fn():
        """Forward computation of alpha values."""
        rest_of_input = tf.slice(inputs, [0, 1, 0], [-1, -1, -1])
        # Compute the alpha values in the forward algorithm in order to get the
        # partition function.

        alphas = crf_forward(
            rest_of_input, first_input, transition_params, sequence_lengths
        )
        log_norm = tf.reduce_logsumexp(alphas, [1])
        # Mask `log_norm` of the sequences with length <= zero.
        log_norm = tf.where(
            tf.less_equal(sequence_lengths, 0), tf.zeros_like(log_norm), log_norm
        )
        return log_norm

序列长度正常的话,就是tf.reduce_logsumexp(alphas, [1])

再来看看计算alphas的函数crf_forward。
alphas就是根据转移矩阵,前向计算出所有路径的得分。(是一个N*1的向量)N是所有路径的数目。
计算方法:代码中给的参考链接是:The Forward-Backward Algorithm
应该是对应通俗易懂,理解BiLSTM+CRF中所阐述的。

  1. 解码部分:
    维特比算法。
def viterbi_decode(score: TensorLike, transition_params: TensorLike) -> tf.Tensor:
    trellis = np.zeros_like(score)
    backpointers = np.zeros_like(score, dtype=np.int32)
    trellis[0] = score[0]

    for t in range(1, score.shape[0]):
        v = np.expand_dims(trellis[t - 1], 1) + transition_params
        trellis[t] = score[t] + np.max(v, 0)
        backpointers[t] = np.argmax(v, 0)

    viterbi = [np.argmax(trellis[-1])]
    for bp in reversed(backpointers[1:]):
        viterbi.append(bp[viterbi[-1]])
    viterbi.reverse()

    viterbi_score = np.max(trellis[-1])
    return viterbi, viterbi_score

输入的是发射得分以及转移矩阵,输出维特比解码的路径以及得分。
动态规划的写法是简洁的,思想是优雅的。
主要搞清楚状态转移方程:第t步每个状态的最优是由上一步每个状态最优转移过来的。然后记录下节点。

总结来说:

  • 训练部分:
    • 对数似然–log_likelihood = sequence_scores - log_norm
    • sequence_scores包括了发射得分,以及转移得分。
    • log_norm是根据转移矩阵,利用前向-后向算法计算出所有路径的得分之和。
    • 利用梯度下降法更新参数。(见统计学习方法的笔记)
  • 预测部分:
    • 根据得到的发射得分以及转移矩阵
    • 利用维特比算法解码,得到最优得分以及最优路径。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值