對於條件隨機場的學習,我覺得應該結合HMM模型一起進行對比學習。首先瀏覽HMM模型:https://www.cnblogs.com/pinking/p/8531405.html
一、定義
條件隨機場(crf):是給定一組輸入隨機變量條件下,另一組輸出隨機變量的條件概率的分布模型,其特點是假設輸出隨機變量構成馬爾科夫隨機場。本文所指線性鏈條件隨機場。
隱馬爾科夫模型(HMM):描述由隱藏的馬爾科夫鏈隨機生成觀測序列的過程,屬於生成模型。
當然,作為初學者,從概念上直觀感受不到兩者的區別與聯系,甚至感覺兩個概念都理解不了了,不過這沒啥問題,繼續學下去吧。
二、學習CRF包含的知識點
參考李航的統計學習方法,將該部分內容的主要知識點梳理如圖,可以看到,CRF和HMM由很多共同點,譬如,都和馬爾科夫有關、都有三個問題要解決,解決的方法也有相同的地方。
三、概率無向圖
概率無向圖,又稱馬爾科夫隨機場,也就是定義中假設輸出隨機變量構成的。關於模型的構建,其實就是一個由節點(node,記作V)和節點鏈接關系的邊(edge,記作E)組成的圖G = (V,E),所謂無向圖,就是邊沒有方向。
隨機變量存在的關系包括:成對馬爾科夫性、局部馬爾科夫性和全局馬爾科夫性。假設隨機變量的聯合概率分布P(Y)和表示它的無向圖G,若P(Y)滿足上述三種關系,則此聯合概率分布為概率無向圖或稱為馬爾科夫隨機場。
提出該定義事實上是為了求聯合概率分布做鋪墊,為了求聯合概率,給出無向圖中的團與最大團的定義。
團:{Y1,Y2},{Y1,Y3},{Y2,Y3},{Y2,Y4},{Y3,Y4}
最大團:{Y1,Y2,Y3},{Y4,Y2,Y3}
概率無向圖模型的聯合概率分布表示為其最大團上的隨機變量的函數的乘積的形式的操作。概率無向圖模型的鏈和概率分布P(Y)可以表示為如下形式:
C為無向圖的最大團,Yc是C的結點對應的隨機變量,Ψ就是一個函數,暫且不用管是什么,就是一個轉換關系。
看到這里,其實對於CRF的結果的圖的理解已經有了鋪墊,實際上,CRF就是將輸入X,經過變換,獲得輸出Y的過程,當然這個Y就滿足了上面所畫的無向圖。但是這個概率是干什么的呢?
繼續看CRF的內容,等看完再第四章返回來看該處內容,我相信會有進一步了解,知道概率是怎么求的了吧。
四、條件隨機場的定義
突然來的一點感悟,和內容無關:雖然這一章和第一章內容有點重合,但是我覺得作為初學者,最應該的是一個循序漸進的過程,有很多書都是為了內容的連續性,而忽略初學者的接受能力,事實上很多東西需要不斷學習,不斷深入的過程,這個在很多教程並不能體現出來,而且有時候,網上查問題找資料,總是一搜一大堆,一打開都是一樣的,可能是很多人看到別人的博客,學習完了,理解了然后就復制粘貼上了,也懶得再改改或者加點自己的東西。我覺得是可以理解的,最好百度能做一個機制,相同的東西別都索引上了。
條件概率模型:P(Y|X),Y為輸出變量,表示標記的序列,X為輸入變量,表示需要標注的觀測序列(再HMM中也稱為狀態序列)。
- 學習問題中,利用極大似然估計,估計P^(Y|X)
- 預測問題中,利用給定的序列x,求出條件概率P^(Y|X)最大的輸出序列y^
一般的線性鏈條件隨機場表示如圖,通常假設X和Y有相同的結構,那么表示圖就如下所示:
而此時,最大團,就是相鄰兩個結點的集合。可以引出公式:
當然,后續會有CRF的參數化形式、簡化形式等表示形式,但實際上都是第三章求概率的表達。
五、CRF的三個需要解決的問題
5.1 概率計算問題
條件隨機場的概率計算問題,就是給定x,y,求它的P(Yi = yi | x),P(Yi-1 = yi-1 ,Yi = yi | x)以及相應的數學期望的問題。其解決手段用的是HMM那樣的前向-后向算法。
前向-后向算法:
定義前向向量:ai(x):
遞推公式表示為:
ai(yi|x)表示在位置i的標記為yi並且到位置i的前部分標記序列的非規范化概率,yi可取m個,所以ai(x)是m維列向量。同理也可以定義后向算法。其中M的定義是在上述條件隨機場的矩陣形式中定義的,本文中未介紹,直接給出定義:
Mi表示的是隨機變量Y取值為yi的非規范化的條件概率,這個概率依賴於當前和前一個位置。
對於此處的理解,我覺得如果非要和HMM中類比的化,a類似於前向概率,只不過此處叫做:前向向量,兩者的不同就是在CRF中,a的求法沒有狀態轉移矩陣;而M類似於HMM中狀態轉移概率位置。並且,CRF的這個式子中,沒有觀測矩陣。總之雖然都叫前向求法,但是里面參數意義是不一樣的,不好對比,CRF中,並不是依賴狀態轉移矩陣和觀測矩陣的過程,而是一個依賴於前一時刻生成結果的概率的預測概率。因為我們要計算的是一個已知yi排列的概率嘛,所以是一個連乘的關系,按序列順序將概率相乘(i時刻的概率依賴於i-1時刻的概率)。
后向向量,表示在位置i的標記為yi並且從i+1到n的后部分標記序列的非規范化概率。
當然,前向向量是從前往后掃描,掃到頭的化,就和后向向量第一個值相同了。。。
概率計算:
按照前向-后向向量的定義,可知,αi表示位置i處標記為yi,從1到i-1處為某一排列的概率,βi表示位置i處標記為yi,從i+1到n處為某一排列的概率。因此,得到條件概率:
對於下面一個式子的理解:事實上,這兩個概率都是根據定義直接列出來的,αi-1表示i-1標記為yi-1時,以及之前排序為某一序列的概率,因為yi-1與yi並不是獨立的,所以聯合概率就表示成上面的式子了。
期望值的計算:
利用前向-后向算法,可以求出特征函數fk關於P(X,Y)和P(Y|X)的數學期望。不過要求P(X , Y)的話,需要假設經驗分布P^(X)。該期望值的計算公式此處略過,就是一個求期望的公式嘛。
5.2 條件隨機場的學習算法
該節研究的是給定訓練數據集估計CRF模型參數的問題。參數估計通常用極大似然估計,HMM中,如果隱層狀態未知的話,也是用極大似然估計。
具體的優化實現算法有改進的迭代尺度法IIS、梯度下降法以及擬牛頓法。直接給出優化函數吧:
其實就是一個EM算法,道理和HMM中的一樣,先對權值w進行優化,優化完求狀態特征和轉移特征的期望,然后再根據期望再迭代優化w,最后滿足概率最大就可以了。
5.3 條件隨機場的預測算法
條件隨機場的預測問題,是給定CRF和輸入x,求輸出y的問題。這個求法就是使用viterbi算法。求法同HMM一樣,只不過HMM中反推的時候,利用的是上一時刻某一狀態轉移到當前時刻狀態概率最大的那個上一時刻的狀態。
此處,結合序列標記問題,定義為δi(l),表示在位置i標記l各個可能取值(1,2...m),遞推公式:
其中,就是非規范化的P(yi|x),因此,這里采用了相加的方法。反推的時候,選擇上一時刻某一Ψ,在李航書中那個例子的觀察方法如下,其中畫黃框的是取值大的那一項。
六、CRF與HMM的區別聯系
來自:Sutton, Charles, and Andrew McCallum. "An introduction to conditional random fields." Machine Learning 4.4 (2011): 267-373.
雖然網上這個圖都在抄,不過我感覺都解釋的不夠詳細啊。首先從HMM看,白色圓代表狀態,黑色代表觀測值,可以看到,他們之間有一個順序的依賴關系;而采用CRF的話,狀態和觀測值(或者說用詞性和詞語表示),可以看到沒有這種順序的依賴關系。HMM中的狀態只與前一個有關,而CRF綜合考慮了前后的依賴關系。
判別式模型和生成式模型
HMM是生成式模型,CRF是判別式模型,首先介紹兩種模型。判別模型是給定輸入序列 X,直接評估對應的輸出Y ;生成模型是評估給定輸出 Y,如何從概率分布上生成輸入序列 X。其實兩者的評估目標都是要得到最終的類別標簽Y, 即Y=argmax p(y|x)。判別式模型直接通過解在滿足訓練樣本分布下的最優化問題得到模型參數,主要用到拉格朗日乘算法、梯度下降法,常見的判別式模型如最大熵模型、CRF、LR、SVM等;而生成式模型先經過貝葉斯轉換成Y = argmax p(y|x) = argmax p(x|y)*p(y),然后分別學習p(y)和p(x|y)的概率分布,常見的如n-Gram、HMM、Naive Bayes。
判別模型和生成模型只是描述一種問題的兩種方式,在理論上,它們是可以互相轉換的。 對於上述HMM和CRF的區別,最主要的就是對於HMM,需要加入狀態概率分布的先驗知識,即:
在HMM中有這樣一個過程,但是CRF就不需要了。
最大后驗估計 vs最大似然估計
- 頻率學派:最大似然估計(MLE):在進行推論時,我們只關心似然度,並選擇給出所最大化 p(data|hypo) 的假設作為預測。
- 貝葉斯學派:最大后驗估計(MAP):我們也需要把先驗 p(hypo) 納入計算,不僅是似然度,還要選擇給出最大 p(data|hypo) * p(hypo) 的假設作為預測。
- 如果我們認為所有假設都服從均勻分布,那么MAP = MLE 。
可以看到,HMM和CRF是兩種思路,CRF是頻率學派,而HMM是貝葉斯學派的。
七、CRF與Softmax
對於序列標注問題,可以簡單的理解為分類問題,既然是分類,為什么NLP中通常不直接用softmax等分類器,而使用CRF\HMM呢?這是因為目標輸出序列本身會帶有一些上下文關聯,而softmax等不能體現出這種聯系。當然,CRF體現的不僅僅是上下文的聯系,更重要的是利用viterbi算法,體現的是一種路徑規划的概率。
另外,通常在NLP中,輸入每個batch的語句長度是不一樣的(單個batch語句長度可以通過padding補齊),如果用CNN做特征提取的話,batch之間的結果的維度是不同的。而采用CRF的話,就不用考慮這個維度不同的問題了。
softmax在tf中的接口:
@tf_export("nn.sampled_softmax_loss") def sampled_softmax_loss(weights, biases, labels, inputs, num_sampled, num_classes, num_true=1, sampled_values=None, remove_accidental_hits=True, partition_strategy="mod", name="sampled_softmax_loss", seed=None): #num_sampled則是Sample Softmax時候用到的一個超參數,確定選幾個詞來對比優化 ''' weights: A `Tensor` of shape `[num_classes, dim]`, or a list of `Tensor` objects whose concatenation along dimension 0 has shape [num_classes, dim]. The (possibly-sharded) class embeddings. biases: A `Tensor` of shape `[num_classes]`. The class biases. labels: A `Tensor` of type `int64` and shape `[batch_size, num_true]`. The target classes. Note that this format differs from the `labels` argument of `nn.softmax_cross_entropy_with_logits`. inputs: A `Tensor` of shape `[batch_size, dim]`. The forward activations of the input network. num_sampled: An `int`. The number of classes to randomly sample per batch. num_classes: An `int`. The number of possible classes. '''
而crf的接口:
def crf_log_likelihood(inputs, tag_indices, sequence_lengths, transition_params=None): """Computes the log-likelihood of tag sequences in a CRF. Args: inputs: A [batch_size, max_seq_len, num_tags] tensor of unary potentials to use as input to the CRF layer. tag_indices: A [batch_size, max_seq_len] matrix of tag indices for which we compute the log-likelihood. sequence_lengths: A [batch_size] vector of true sequence lengths. transition_params: A [num_tags, num_tags] transition matrix, if available. """
可以看到,雖然兩者都是實現分類的功能,但是實際上是不一樣的,基於seq2seq的時候用softmax判斷字典中是哪一個字,其softmax的輸入是一個固定維度的向量,因為整個seq2seq是基於序列的。而CRF輸入就需要句子的長度做內部處理,它本身是基於序列的。
八、條件隨機場的tensorflow代碼實現
參考:https://mp.weixin.qq.com/s/1KAbFAWC3jgJTE-zp5Qu6g
作者以骰子為例,假設了每次擲骰子之間會有一個相互依賴的關系,參考代碼:https://www.cnblogs.com/pinking/p/9362966.html 的基礎上進行修改即可:
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt TIME_STEPS = 15#20 # backpropagation through time 的time_steps BATCH_SIZE = 1#50 INPUT_SIZE = 1 # x數據輸入size LR = 0.05 # learning rate num_tags = 2 # 定義一個生成數據的 get_batch function: def get_batch(): xs = np.array([[2, 3, 4, 5, 5, 5, 1, 5, 3, 2, 5, 5, 5, 3, 5]]) res = np.array([[0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1]]) return [xs[:, :, np.newaxis], res] # 定義 CRF 的主體結構 class CRF(object): def __init__(self, n_steps, input_size, num_tags, batch_size): self.n_steps = n_steps self.input_size = input_size self.num_tags = num_tags self.batch_size = batch_size self.xs = tf.placeholder(tf.float32, [None, self.n_steps, self.input_size], name='xs') self.ys = tf.placeholder(tf.int32, [self.batch_size, self.n_steps], name='ys') #將輸入 batch_size x seq_length x input_size 映射到 batch_size x seq_length x num_tags weights = tf.get_variable("weights", [self.input_size, self.num_tags]) matricized_x_t = tf.reshape(self.xs, [-1, self.input_size]) matricized_unary_scores = tf.matmul(matricized_x_t, weights) unary_scores = tf.reshape(matricized_unary_scores, [self.batch_size, self.n_steps, self.num_tags]) sequence_lengths = np.full(self.batch_size,self.n_steps,dtype=np.int32) log_likelihood,transition_params = tf.contrib.crf.crf_log_likelihood(unary_scores,self.ys,sequence_lengths) self.pred, viterbi_score = tf.contrib.crf.crf_decode(unary_scores, transition_params, sequence_lengths) # add a training op to tune the parameters. self.cost = tf.reduce_mean(-log_likelihood) self.train_op = tf.train.AdamOptimizer(LR).minimize(self.cost) # 訓練 CRF if __name__ == '__main__': # 搭建 CRF 模型 model = CRF(TIME_STEPS, INPUT_SIZE, num_tags, BATCH_SIZE) sess = tf.Session() sess.run(tf.global_variables_initializer()) # matplotlib可視化 plt.ion() # 設置連續 plot plt.show() # 訓練多次 for i in range(150): xs, res = get_batch() # 提取 batch data #print(res.shape) # 初始化 data feed_dict = { model.xs: xs, model.ys: res, } # 訓練 _, cost,pred = sess.run( [model.train_op, model.cost, model.pred], feed_dict=feed_dict) # plotting x = xs.reshape(-1,1) r = res.reshape(-1, 1) p = pred.reshape(-1, 1) x = range(len(x)) plt.clf() plt.plot(x, r, 'r', x, p, 'b--') plt.ylim((-1.2, 1.2)) plt.draw() plt.pause(0.3) # 每 0.3 s 刷新一次 # 打印 cost 結果 if i % 20 == 0: print('cost: ', round(cost, 4))
得到的結果: