日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

条件随机场 python_如何直观地理解条件随机场,并通过PyTorch简单地实现

發布時間:2024/1/23 python 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 条件随机场 python_如何直观地理解条件随机场,并通过PyTorch简单地实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

條件隨機場是一種無向圖模型,且相對于深度網絡有非常多的優勢,因此現在很多研究者結合條件隨機場(CRF)與深度網絡獲得更魯棒和可解釋的模型。本文結合 PyTorch 從基本的概率定義到模型實現直觀地介紹了 CRF 的基本概念,有助于讀者進一步理解完整理論。

假設我們有兩個相同的骰子,但是其中的一個是公平的,每個點數出現的概率相同;另一個骰子則被做了手腳,數字 6 出現的概率為 80%,而數字 1-5 出現的概率都為 4%。如果我給你一個 15 次投擲骰子的序列,你能預測出我每次投擲用的是哪一枚骰子嗎?

為了得到較高的準確率,一個簡單的模型是,每當「6」出現的時候,我們那就預測使用了有偏的骰子,而出現其他數字時則預測使用了公平的骰子。實際上,如果我們在每次投擲時等可能地使用任意一個骰子,那么這個簡單的規則就是你可以做到的最好預測。

但是,設想一種情況:如果在使用了公平的骰子后,我們下一次投擲時使用有偏的骰子的概率為 90%,結果會怎樣呢?如果下一次投擲出現了一個「3」,上述模型會預測我們使用了公平的骰子,但是實際上我們使用有偏的骰子是一個可能性更大的選項。我們可以通過貝葉斯定理來進行驗證這個說法:

其中隨機變量 y_i 是第 i 次投擲所用的骰子類型,x_i 是第 i 次投擲得到的點數。

我們的結論是,在每一步中作出可能性最大的選擇只是可行策略之一,因為我們同時可能選擇其它的骰子。更有可能的情況是,以前對骰子的選擇情況影響了我未來會做出怎樣的選擇。為了成功地進行預測,你將不得不考慮到每次投擲之間的相互依賴關系。

條件隨機場(CRF)是一個用于預測與輸入序列相對應標注序列的標準模型。目前有許多關于條件隨機場的教程,但是我所看到的教程都會陷入以下兩種情況其中之一:1)全都是理論,但沒有展示如何實現它們 2)為復雜的機器學習問題編寫的代碼缺少解釋,不能令讀者對代碼有直觀的理解。

之所以這些作者選擇寫出全是理論或者包含可讀性很差的代碼教程,是因為條件隨機場從屬于一個更廣更深的課題「概率圖模型」。所以要想深入涵蓋其理論和實現可能需要寫一本書,而不是一篇博文,這種情況也使得學習條件隨機場的知識比它原本所需要的更困難。

本教程的目標是涵蓋恰到好處的理論知識,以便你能對 CRF 有一個基本的印象。此外我們還會通過一個簡單的問題向你展示如何實現條件隨機場,你可以在自己的筆記本電腦上復現它。這很可能讓你具有將這個簡單的條件隨機場示例加以改造,用于更復雜問題所需要的直觀理解。

理論

我們對于理論的討論將分為三個部分:1)指定模型參數 2)如何估計這些參數 3)利用這些參數進行預測,這三大類適用于任何統計機器學習模型。因此從這個意義上說,條件隨機場并沒有什么特別的,但這并不意味著條件隨機場就和 logistic 回歸模型一樣簡單。我們會發現,一旦我們要面對一連串的預測而不是單一的預測,事情就會變得更加復雜。

指定模型參數

在這個簡單的問題中,我們需要擔心的唯一的參數就是與從一次投擲轉換到下一次投擲狀態的分布。我們有六種狀態需要考慮,因此我們將它們存儲在一個 2*3 的「轉移矩陣」中。

第一列對應于「從前一次投擲使用公平骰子的狀態,轉換到當前使用公平骰子狀態的概率或成本(第一行的值),或轉換到有偏骰子狀態的概率(第二行的值)」。因此,第一列中的第一個元素編碼了在給定我本次投擲使用了公平骰子的前提下,預測下一次投擲使用公平骰子的概率。如果數據顯示,我不太可能在連續使用公平骰子,模型會學習到這個概率應該很低,反之亦然。同樣的邏輯也適用于第二列。

矩陣的第一和第二列假設我們知道在前一次投擲中使用了哪個骰子,因此我們必須將第一次投擲作為一個特例來對待。我們將把相應的概率存儲在第三列中。

參數估計

假設給定一個投擲的集合 X* *以及它們相應的骰子標簽 Y。我們將會找到使整個訓練數據的負對數似然最小的轉移矩陣 T。我將會向你展示單個骰子投擲序列的似然和負對數似然是什么樣的。為了在整個數據集上得到它,你要對所有的序列取平均。

P(x_i | y_i) 是在給定當前的骰子標簽的前提條件下,觀測到一個給定骰子投擲點數的概率。舉例而言,如果 y_i 為公平骰子,則 P(x_i | y_i) = 1/6。另一項 T(y_i | y_{i-1}) 是從上一個骰子標簽轉換到當前投資標簽的概率,我們可以直接從轉移矩陣中讀取出這個概率。

請注意在分母中,我們是怎樣在所有可能標簽 y' 的序列上進行求和的。在傳統的二分類問題 logistic 回歸中,我們在分母中會有兩個項。但是現在,我們要處理的是標注序列,并且對于一個長度為 15 的序列來說,一共有 2^15 種可能的標簽序列,所以分母項是十分巨大的。條件隨機場的「秘密武器」是,它假定當前的骰子標簽僅僅只取決于之前的骰子標簽,來高效地計算這個大規模求和。

這個秘密武器被稱為「前向-后向算法」。對該算法的深入討論超出了這篇博文的范圍,因此這里不做詳細的解釋。

序列預測

一旦我們估計出了我們的轉移矩陣,我們可以使用它去找到在給定一個投擲序列的條件下,最有可能的骰子標注序列。要做到這一點,最簡單的方法就是計算出所有可能的序列的似然,但這即使對于中等長度的序列也是十分困難的。正如我們在參數估計中所做的那樣,我們將不得不用一種特殊的算法高效地搜索可能性最大的序列。這個算法與「向前-向后算法」很相近,它被稱為「維特比算法」。

代碼

PyTorch 是一個在 Python 語言環境下為訓練深度學習模型而編寫的深度學習庫。盡管我們在這里并不是要進行深度學習,但 PyTorch 的自動微分庫將幫助我們通過梯度下降算法訓練條件隨機場模型,而無需我們手動計算任何梯度。使用 PyTorch 會迫使我們實現「向前-向后算法」的前向部分以及「維特比算法」,這比我們直接使用專門的條件隨機場構建的 Python 包更有指導意義。

首先,讓我們先設想一下結果應該是什么樣的。我們需要一種方法去計算給定骰子標簽時,對于任意投擲序列的對數似然度,下面我們給出一種可能的方法:def neg_log_likelihood(self, rolls, states):

"""Compute neg log-likelihood for a given sequence.

Input:

rolls: numpy array, dim [1, n_rolls]. Integer 0-5 showing value on dice.

states: numpy array, dim [1, n_rolls]. Integer 0, 1. 0 if dice is fair.

"""

loglikelihoods = self._data_to_likelihood(rolls)

states = torch.LongTensor(states)

sequence_loglik = self._compute_likelihood_numerator(loglikelihoods, states)

denominator = self._compute_likelihood_denominator(loglikelihoods)

return denominator - sequence_loglik

這個方法做了三件主要的事情:1)將骰子的值映射到一個似然函數上 2)計算對數似然項的分子 3)計算對數似然項的分母。

讓我們首先來處理「_data_to_likelihood」方法,它幫助我們去執行上面提到的步驟 1。我們將要做的是,創建一個維度為 6*2 的矩陣,其中第一列是用公平骰子擲到 1-6 的似然度,第二列是用有偏骰子擲到 1-6 的似然度。這個問題中的矩陣的形式如下所示:array([[-1.79175947, -3.21887582],

[-1.79175947, -3.21887582],

[-1.79175947, -3.21887582],

[-1.79175947, -3.21887582],

[-1.79175947, -3.21887582],

[-1.79175947, -0.22314355]])

現在,如果我們看到一個投擲的點數為「4」,我們可以直接選擇矩陣中的第四行。這個向量中的第一個元素是用公平骰子得到「4」的對數似然 log(1/6),而第二個元素是用有偏骰子得到「4」的對數似然 log(0.04)。代碼如下:def _data_to_likelihood(self, rolls):

"""Converts a numpy array of rolls (integers) to log-likelihood.

self.loglikeligood is a matrix of 6 x 2 in our case.

Input is one [1, n_rolls]

"""

log_likelihoods = self.loglikelihood[rolls]

return Variable(torch.FloatTensor(log_likelihoods), requires_grad=False)

接下來,我們將編寫計算對數似然分子和分母的方法。def _compute_likelihood_numerator(self, loglikelihoods, states):

"""Computes numerator of likelihood function for a given sequence.

We'll iterate over the sequence of states and compute the sum

of the relevant transition cost with the log likelihood of the observed

roll.

Input:

loglikelihoods: torch Variable. Matrix of n_obs x n_states.

i,j entry is loglikelihood of observing roll i given state j

states: sequence of labels

Output:

score: torch Variable. Score of assignment.

"""

prev_state = self.n_states

score = Variable(torch.Tensor([0]))

for index, state in enumerate(states):

score += self.transition[state, prev_state] + loglikelihoods[index, state]

prev_state = state

return score

def _compute_likelihood_denominator(self, loglikelihoods):

"""Implements the forward pass of the forward-backward algorithm.

We loop over all possible states efficiently using the recursive

relationship: alpha_t(j) = \sum_i alpha_{t-1}(i) * L(x_t | y_t) * C(y_t | y{t-1} = i)

Input:

loglikelihoods: torch Variable. Same input as _compute_likelihood_numerator.

This algorithm efficiently loops over all possible state sequences

so no other imput is needed.

Output:

torch Variable.

"""

# Stores the current value of alpha at timestep t

prev_alpha = self.transition[:, self.n_states] + loglikelihoods[0].view(1, -1)

for roll in loglikelihoods[1:]:

alpha_t = []

# Loop over all possible states

for next_state in range(self.n_states):

# Compute all possible costs of transitioning to next_state

feature_function = self.transition[next_state,:self.n_states].view(1, -1) +\

roll[next_state].view(1, -1).expand(1, self.n_states)

alpha_t_next_state = prev_alpha + feature_function

alpha_t.append(self.log_sum_exp(alpha_t_next_state))

prev_alpha = torch.cat(alpha_t).view(1, -1)

return self.log_sum_exp(prev_alpha)

就是這樣!我們現在已經擁有了學習轉移矩陣所需的全部代碼。但是,如果我們想要在訓練完模型之后作出預測,我們就必須編寫維特比算法:def _viterbi_algorithm(self, loglikelihoods):

"""Implements Viterbi algorithm for finding most likely sequence of labels.

Very similar to _compute_likelihood_denominator but now we take the maximum

over the previous states as opposed to the sum.

Input:

loglikelihoods: torch Variable. Same input as _compute_likelihood_denominator.

Output:

tuple. First entry is the most likely sequence of labels. Second is

the loglikelihood of this sequence.

"""

argmaxes = []

# prev_delta will store the current score of the sequence for each state

prev_delta = self.transition[:, self.n_states].view(1, -1) +\

loglikelihoods[0].view(1, -1)

for roll in loglikelihoods[1:]:

local_argmaxes = []

next_delta = []

for next_state in range(self.n_states):

feature_function = self.transition[next_state,:self.n_states].view(1, -1) +\

roll.view(1, -1) +\

prev_delta

most_likely_state = self.argmax(feature_function)

score = feature_function[0][most_likely_state]

next_delta.append(score)

local_argmaxes.append(most_likely_state)

prev_delta = torch.cat(next_delta).view(1, -1)

argmaxes.append(local_argmaxes)

final_state = self.argmax(prev_delta)

final_score = prev_delta[0][final_state]

path_list = [final_state]

# Backtrack through the argmaxes to find most likely state

for states in reversed(argmaxes):

final_state = states[final_state]

path_list.append(final_state)

return np.array(path_list), final_score

我們實現的代碼還有很多,但我在這里僅僅只包含了我們在理論部分討論過的核心功能。

利用數據進行模型評價

我使用下面概率模擬得到的數據,并對模型進行評價:

1.P(序列中的第一個骰子為公平骰子)=0.5

2.P(當前為公平骰子|上一次為公平骰子)=0.8

3.P(當前為有偏骰子|上一次為有偏骰子)=0.35

請查看我編寫的 Notebook 去看看我是如何生成條件隨機場模型并且訓練它的。

我們要做的第一件事就是看看估計出的轉移矩陣是怎樣的。模型會學習如果在前一次使用了公平骰子,那么這一次更有可能使用公平骰子進行投擲(-1.38 < -0.87)。模型還學到在使用了有偏骰子后,我們更有可能使用公平骰子,但這和使用有偏投擲的可能性差別并不是很大(-1.38 < -0.87)。該模型在第一次投擲時給兩種骰子分配相同的代價(0.51 ~ -0.54)。array([[-0.86563134, -0.40748784, -0.54984874],

[-1.3820231 , -0.59524935, -0.516026 ]], dtype=float32)

接下來,我們來看看對于一個特定的投擲序列的預測結果如何:# observed dice rolls

array([2, 3, 4, 5, 5, 5, 1, 5, 3, 2, 5, 5, 5, 3, 5])

# corresponding labels. 0 means fair

array([0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1])

# predictions

array([0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0])

模型識別出「6」的長序列(在上面的代碼中實際顯示為「5」,因為我們是從「0」開始的)來自于有偏的骰子,這是有意義的。請注意,模型并沒有將所有的「6」都分配給有偏的骰子,就像對第八次投擲的預測那樣。這是因為在這個「6」之前,我們很確信使用了公平骰子(我們擲出了一個「2」)并且從公平骰子狀態轉換到有偏骰子狀態的可能性較小。我認為這種錯誤是可以接受的,因此模型是很成功的。

結論

我向你展示了條件隨機場背后的一小部分理論知識,同時也展示了你如何才能實現一個用于簡單問題的條件隨機場。當然,相關的知識遠遠比我在這里所能夠涵蓋到的要多。所以我建議各位讀者查看更多相關的資源。

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的条件随机场 python_如何直观地理解条件随机场,并通过PyTorch简单地实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。