前面提过,我们根据图的边是否有向将概率图模型大致分为两类,有向的称为贝叶斯网络,无向的称为马尔可夫网络。马尔可夫网络是关于一组有马尔可夫性质随机变量X的全联合概率分布模型。
马尔可夫网络与贝叶斯网络类似,可用于表示依赖关系。但也有不同之处,一方面它可以表示贝叶斯网络无法表示的一些依赖关系,如循环依赖;另一方面,它不能表示贝叶斯网络能够表示的某些关系,如推导关系。
在5.2节我们介绍了马尔可夫网络基于团或最大团的因子分子分解,其分解公式为式(5.3),这个因子分解也称为Hammersley–Clifford定理。
Hammersley–Clifford定理:定义在随机变量集X上的马尔可夫网络,其联合概率分布P(X)可表示为式(5.3)。
在贝叶斯网络中,我们重点介绍了隐马尔可夫模型。在马尔可夫网络中我们也将重点介绍一种常见的模型,即马尔可夫随机场,简称为MRF。它是典型的马尔可夫网络,也是一种著名的无向图模型,马尔可夫随机场有一组势函数,也称因子,这是定义在变量子集上的非负函数,其联合概率的表达式就是式(5.3)。
在概率图模型中,一个很重要的任务就是分解概率联合函数,而函数分解通常利用随机变量的独立性或条件独立性。当然利用概率图,还可以把这种独立性可视化。
在马尔可夫随机场中如何得到这种独立性呢?这里我们可借助“分离”的概念,如图5-8所示。如果从节点集X中的节点到Y中的节点都必须经过节点集Z中的节点,则称节点集X和Y被节点集Z分离,其中Z又称为分离集。
图5-8 节点集Z分离节点集X和Y
对马尔可夫随机场,有:
给定两个变量子集的分离集,则这两个变量子集条件独立。
如在无向图5-8中,集合X=(X1,X2,X3)和集合Y=(Y1,Y2,Y3)被集合Z=(Z1,Z2)所分离,则X和Y在给定Z的条件下独立,记为:X⊥Y|Z,用概率表示为:
由全局马尔可夫独立性,可以推广到两个局部的独立性质。
如图5-9所示,X 1 是无向图中的一个节点,W={X 2 ,X 3 }是与X 1 相连的所有节点,O是X 1 、W外的所有节点,O={X 4 ,X 5 }。则有:
图5-9 局部马尔可夫独立性
图5-10 成对马尔可夫独立性
如图3-10所示,X 4 、X 5 是无向图中任意两个不相邻的结点,其他所有结点是O={X 1 ,X 2 ,X 3 },则有
实际上,成对马尔科夫性、局部马尔可夫性、全局马尔可夫性是等价的。
条件随机场(Conditional Random Field,CRF)是条件概率分布模型P(Y|X),表示的是在给定一组输入随机变量X的条件下另一组输出随机变量Y的马尔可夫随机场,也就是说CRF的特点是假设输出随机变量构成马尔可夫随机场。这时,在条件概率模型P(Y|X)中,Y是输出变量,表示标记序列;X是输入变量,表示需要标注的观测序列。也可以把标记序列称为状态序列(参见隐马尔可夫模型)。学习时,利用训练数据集通过极大似然估计或正则化的极大似然估计得到条件概率模型 ;预测时,对于给定的输入序列X求出条件概率 最大的输出序列Y。
设X与Y是随机变量,P(Y|X)是在给定X的条件下Y的条件概率分布。若随机变量Y构成一个由无向图G=(V,E)表示的马尔可夫随机场,即
对任意节点v成立,则称条件概率分布P(Y|X)为条件随机场。
式中w~v表示在图G=(V,E)中与节点v有边连接的所有节点w,w!=v表示节点v以外的所有节点,Yv、Yw为节点v、w对应的随机变量。
一般假设X和Y有相同的图结构。线性链条件随机场的情况为:
在此情况下,X=(X 1 ,X 2 ,…,X n ),Y=(Y 1 ,Y 2 ,…,Y n ),最大团是相邻两个节点的集合。如图5-11所示。
图5-11 线性链条件随机场
设X=(X 1 ,X 2 ,…,X n ),Y=(Y 1 ,Y 2 ,…,Y n )为线性链表示的随机变量序列,若在给定随机变量序列X的条件下,随机变量序列Y的条件概率分布P(Y|X)构成条件随机场,即满足马尔可夫性:
其中i=1或n时,只考虑单边。则称P(Y|X)为线性链条件随机场。
条件随机场在词性标注中发挥重要作用,首先我们简单介绍何为词性标注,然后通过一个实例来实现一个具体的词性标注。
为了更好地理解词性标注这个概念,我们还是通过实例来说明。词性标注就是给一个句子中的每个单词注明词性。比如这句话:“He drank coffee at Starbucks”,注明每个单词的词性后是这样的:“He(代词)drank(动词)coffee(名词)at(介词)Starbucks(名词)”。我们用图5-12表示词性标注过程。
图5-12 词性标注
如何用条件随机场来解决词性标注问题?
以上面的这句话为例,这句话共有5个单词,我们将:(代词,动词,名词,介词,名词)作为一个标注序列,称为Y。可选的标注序列有很多种,比如Y还可以是这样:(名词,动词,动词,介词,名词),我们要在这么多的可选标注序列中,挑选出一个最靠谱的作为我们对这句话的标注。
怎么判断一个标注序列靠谱不靠谱呢?
就我们上面展示的两个标注序列来说,第二个显然不如第一个靠谱,因为它把第二、第三个单词都标注成了动词,动词后面接动词,这在一个句子中通常是说不通的。
假如我们给每一个标注序列打分,打分越高代表这个标注序列越靠谱,我们至少可以说,凡是标注中出现了动词后面还是动词的标注序列,就要给它负分。
上面所说的动词后面还是动词就是一个特征函数,我们可以定义一个特征函数集合,用这个特征函数集合来为一个标注序列打分,并据此选出最靠谱的标注序列。也就是说,每一个特征函数都可以用来为一个标注序列评分,把集合中所有特征函数对同一个标注序列的评分综合起来,就是这个标注序列最终的评分值。
现在,我们正式地定义一下什么是CRF中的特征函数。所谓特征函数,就是这样的函数,它接收四个参数:
·句子X:就是我们要标注词性的句子。
·i:用来表示句子X中第i个单词。
·y i :表示要评分的标注序列给第i个单词标注的词性。
·y i-1 :表示要评分的标注序列给第i-1个单词标注的词性。
它的输出值是0或者1,0表示要评分的标注序列不符合这个特征,1表示要评分的标注序列符合这个特征。这里,我们的特征函数仅仅依靠当前单词的标签和它前面的单词的标签对标注序列进行评判,这样建立的CRF也叫作线性链CRF,这是CRF中的一种简单情况。
定义好一组特征函数后,我们要给每个特征函数f j 赋予一个权重λ j 。现在,只要有一个句子X,有一个标注序列Y,我们就可以利用前面定义的特征函数集来对l评分。
上式中有两个求和,外面的求和用来求每一个特征函数f j 评分值的和,里面的求和用来求句子中每个位置的单词的特征值的和。
对这个分数进行指数化和标准化,我们就可以得到标注序列l的概率值P(Y|X),如下所示:
CRF与LSM等模型不同,能够考虑长远的上下文信息,它更多考虑的是整个句子局部特征的线性加权组合(通过特征模板去扫描整个句子)。关键的一点是,CRF的模型为P(Y|X),注意这里Y和X都是序列,优化的是一个序列Y=(y 1 ,y 2 ,…,y n ),而不是某个时刻的y t ,即找到一个概率最高的序列Y=(y 1 ,y 2 ,…,y n )使得P(Y|X)最高,它计算的是一种联合概率,优化的是整个序列(最终目标),而不是将每个时刻的最优拼接起来,在这一点上CRF要优于LSTM。
以下是用Tensorflow来具体实现一个CRF,在代码前,先说明几个重要函数的功能:
·tf.contrib.crf.crf_log_likelihood 在一个条件随机场里面计算标签序列的log-likelihood,其格式为:
crf_log_likelihood(inputs,tag_indices,sequence_lengths,transition_params=None)
·tf.contrib.crf.viterbi_decode 其作用就是返回最好的标签序列。这个函数只能够在测试时使用,在Tensorflow外部解码。其格式为:
viterbi_decode(score,transition_params)
·tf.contrib.crf.crf_decode 在tensorflow内解码,其格式为:
crf_decode(potentials,transition_params,sequence_length)
用TensorFlow实现CRF的主要步骤包括参数设置、构建随机特征、随机场tag、训练及评估模型,以下为详细代码。
import numpy as np import tensorflow as tf # 参数设置 num_examples = 10 num_words = 20 num_features = 100 num_tags = 5 # 构建随机特征 x = np.random.rand(num_examples, num_words, num_features).astype(np.float32) # 构建随机tag y = np.random.randint( num_tags, size=[num_examples, num_words]).astype(np.int32) # 获取样本句长向量(因为每一个样本可能包含不一样多的词),在这里统一设为 num_words - 1,真实情况下根据需要设置 sequence_lengths = np.full(num_examples, num_words - 1, dtype=np.int32) # 训练,评估模型 with tf.Graph().as_default(): with tf.Session() as session: x_t = tf.constant(x) y_t = tf.constant(y) sequence_lengths_t = tf.constant(sequence_lengths) # 在这里设置一个无偏置的线性层 weights = tf.get_variable("weights", [num_features, num_tags]) matricized_x_t = tf.reshape(x_t, [-1, num_features]) matricized_unary_scores = tf.matmul(matricized_x_t, weights) unary_scores = tf.reshape(matricized_unary_scores, [num_examples, num_words, num_tags]) # 计算log-likelihood并获得transition_params log_likelihood, transition_params = tf.contrib.crf.crf_log_likelihood( unary_scores, y_t, sequence_lengths_t) # 进行解码(维特比算法),获得解码之后的序列viterbi_sequence和分数viterbi_score viterbi_sequence, viterbi_score = tf.contrib.crf.crf_decode( unary_scores, transition_params, sequence_lengths_t) loss = tf.reduce_mean(-log_likelihood) train_op = tf.train.GradientDescentOptimizer(0.01).minimize(loss) session.run(tf.global_variables_initializer()) mask = (np.expand_dims(np.arange(num_words), axis=0) < # np.arange()创建等差数组 np.expand_dims(sequence_lengths, axis=1)) # np.expand_dims()扩张维度 # 得到一个num_examples*num_words的二维数组,数据类型为布尔型,目的是对句长进行截断 # 将每个样本的sequence_lengths加起来,得到标签的总数 total_labels = np.sum(sequence_lengths) # 进行训练 for i in range(1000): tf_viterbi_sequence, _ = session.run([viterbi_sequence, train_op]) if i % 100 == 0: correct_labels = np.sum((y == tf_viterbi_sequence) * mask) accuracy = 100.0 * correct_labels / float(total_labels) print("Accuracy: %.2f%%" % accuracy)
运行结果:
Accuracy: 20.53% Accuracy: 56.84% Accuracy: 67.37% Accuracy: 73.68% Accuracy: 80.00% Accuracy: 82.63% Accuracy: 84.21% Accuracy: 88.42% Accuracy: 88.42% Accuracy: 90.00%