NLP學習記錄(六)最大熵模型MaxEnt

原理

在叧掌握關於未知分佈的部分信息的情況下,符合已知知識的概率分佈可能有夗個,但使熵值最大的概率分佈最真實地反映了事件的的分佈情況,因爲熵定義了隨機變量的不確定性,弼熵值最大時,隨機變量最不確定,最難預測其行爲。


最大熵模型介紹

我們通過一個簡單的例子來介紹最大熵概念。假設我們模擬一個翻譯專家的決策過程,關於英文單詞in到法語單詞的翻譯。我們的翻譯決策模型p給每一個單詞或短語分配一個估計值p(f),即專家選擇f作爲翻譯的概率。爲了幫助我們開發模型p,我們收集大量的專家翻譯的樣本。我們的目標有兩個,一是從樣本中抽取一組決策過程的事實(規則),二是基於這些事實構建這一翻譯過程的模型。

我們能從樣本中得到的一個明顯的線索是允許的翻譯候選列表。例如,我們可能發現翻譯專家總是選擇下面這5個法語詞彙:{dans, en, à, au cours de, pendant}。一旦有了這些信息,我們可以給模型p施加第一個約束條件:

p(dans)+p(en)+ p(à)+p(au cours de)+p(pendant) = 1

這個等式代表了這一翻譯過程的第一個統計信息,我們現在可以進行尋找滿足這一條件的模型了。顯然,有無數滿足這個條件的模型可供選擇。其中一個模型是p(dans)=1,換句話說這個模型總是預測dans。另外一個滿足這一約束的模型是p(pendant)=1/2 and p(à)=1/2。 這兩個模型都有違常理:只知道翻譯專家總是選擇這5個法語詞彙,我們哪知道哪個概率分佈是對的。兩個模型每個都在沒有經驗支持的情況下,做了大膽的假設。最符合直覺的模型是:

p(dans) = 1/5

p(en) = 1/5

p(à) = 1/5

p(au cours de) = 1/5

p(pendant) = 1/5

這個模型將概率均勻分配給5個可能的詞彙,是與我們已有知識最一致的模型。我們可能希望從樣本中收集更多的關於翻譯決策的線索。假設我們發現到有30%時間in被翻譯成dans 或者en. 我們可以運用這些知識更新我們的模型,讓其滿足兩個約束條件:

p(dans) + p(en) = 3/10

p(dans)+p(en)+ p(à)+p(au cours de)+p(pendant) = 1

同樣,還是有很多概率分佈滿足這兩個約束。在沒有其他知識的情況下,最合理的模型p是最均勻的模型,也就是在滿足約束的條件下,將概率儘可能均勻的分配。

p(dans) = 3/20

p(en) = 3/20

p(à) = 7/30

p(au cours de) = 7/30

p(pendant) = 7/30

假設我們又一次檢查數據,這次發現了另外一個有趣的事實:有一般的情況,專家會選擇翻譯成dans 或 à.我們可以將這一信息列爲第三個約束:

p(dans) + p(en) = 3/10

p(dans)+p(en)+ p(à)+p(au cours de)+p(pendant) = 1

p(dans)+ p(à)=1/2

我們可以再一次尋找滿足這些約束的最均勻分配的模型p,但這一次的結果沒有那麼明顯。由於我們增加了問題的複雜度,我們碰到了兩個問題:首先,”uniform(均勻)”究竟是什麼意思?我們如何度量一個模型的均勻度(uniformity)?第二,有了這些問題答案之後,我們如何找到滿足一組約束且最均勻的模型?就像前面我們看到的模型。

最大熵的方法回答了這兩個問題。直觀上講,很簡單,即:對已知的知識建模,對未知的不過任何假設(model all that is known and assume nothing about that which is unknown)。換句話說,在給定一組事實(features+output)的條件下,選擇符合所有事實,且在其他方面近可能均勻的模型,這恰恰是我們在上面例子每一步選擇模型p所採取的方法。

  • Maxent Modeling

我們考慮一個隨機過程,它產生一個輸出y,y屬於一個有窮集合。對於剛纔討論的翻譯的例子,該過程輸出單詞in的翻譯,輸出值y可以是集合{dans, en, à, au cours de, pendant}中任何一個單詞。在輸出y時,該過程可能會被上下文信息x影響,x屬於有窮的集合X。在目前的例子中,這信息可能包括英文句子中in周圍的單詞。

我們的任務是構造一個統計模型,該模型能夠準確表示隨機過程的行爲。該模型任務是預測在給定上下文x的情況下,輸出y的概率:p(y|x).
Training Data

爲了研究這一過程,我們觀察一段時間隨機過程的行爲,收集大量的樣本:這裏寫圖片描述。在我們討論的例子中,每一個樣本由包含in周圍單詞的詞彙x,和in的翻譯y組成。現在,我們可以假設這些訓練樣本已經由一個專家搞定了,我們提供大量包含in的隨機的短語要求她選擇一個合適的翻譯。

我們可以通過它的經驗分佈來總結訓練樣本的特性:

這裏寫圖片描述

通常,對於一個特定的pair (x, y),它要麼不出現在樣本中,要麼最多出現幾次。

  • Features and constraints

我們的目標是構造一個產生訓練樣本這一隨機過程的統計模型。組成這個模型的模塊將是一組訓練樣本的統計值。在目前的例子中,我們已經採用了幾個統計數據:(1)in被翻譯成dans 或者en的頻率是3/10;(2) in被翻譯成dans 或 à的概率是1/2 ;…等。這些統計數據是上下文獨立的,但我們也可以考慮依賴上下文信息x的統計數據。例如,我們可能注意到,在訓練樣本中,如果 April 是一個出現在in之後,那麼in翻譯成en的頻率有9/10.

爲了表示這個事件(event),即當Aprial出現在in之後,in被翻譯成en,我們引入了指示函數:

這裏寫圖片描述

特徵f 關於經驗分佈的期望值,正是我們感興趣的統計數據。我們將這個期望值表示爲:

這裏寫圖片描述 (1)

我們可以將任何樣本的統計表示成一個適當的二值指示函數的期望值,我們把這個函數叫做特徵函數(feature function)或簡稱特徵(feature)。

當我們發現一個統計量,我們覺得有用時,我們讓模型去符合它(擬合),來利用這一重要性。擬合過程通過約束模型p分配給相應特徵函數的期望值來實現。特徵f關於模型p(y|x)的期望值是:

這裏寫圖片描述 (2)

這裏,是x在訓練樣本中的經驗分佈。我們約束這一期望值和訓練樣本中f的期望值相同。那就要求:

這裏寫圖片描述 (3)

組合(1),(2) 和(3),我們得到等式:

這裏寫圖片描述

我們稱(3)爲約束等式(constraint equation)或者簡稱約束(constraint)。我們只關注那麼滿足(3)的模型這裏寫圖片描述,不再考慮那些和訓練樣本中特徵f頻率不一致的模型。

目前總結來看,我們現在有辦法表示樣本數據中內在的統計現象(叫做這裏寫圖片描述),也有辦法使我們的模型繼承這一現象(叫做這裏寫圖片描述)。

最後,仍我關於特徵和約束再羅嗦兩句:單詞 feature'' andconstraint”在討論最大熵時經常被混用,我們希望讀者注意區分這兩者的概念:特徵(feature)是(x,y)的二值函數;約束是一個等式:即模型的特徵函數期望值等於訓練樣本中特徵函數的期望值。

  • The maxent prinple

假設給我們n個特徵函數fi,它們的期望值決定了在建模過程中重要的統計數據。我們想要我們的模型符合這些統計,就是說,我們想要模型p屬於的子集C。

這裏寫圖片描述

圖1是這一限制的幾何解釋。這裏,P是三點上所有可能的概率分佈空間。如果我們不施加任何約束(圖a),所有概率模型都是允許的。施加一個線性約束C1後,模型被限制在C1定義的區域,如圖b示。如果兩個約束是可滿足的, 施加第二個線性約束後可以準確確定p,如圖c所示。另一種情形是,第二個線性約束與第一個不一致,例如,第一個約束可能需要第一個點的概率是1/3,第二個約束需要第三個點的概率是3/4,圖d所示。在目前的設置中,線性約束是從訓練數據集中抽取的,不可能手工構造,因此不可能不一致。進一步來說,在我們應用中的線性約束甚至不會接近唯一確定的p,就象圖c那樣。相反,集合C=C1∩C2∩C3∩…∩Cn中的模型是無窮的。

這裏寫圖片描述

屬於集合C的所有模型p中,最大熵的理念決定我們選擇最均勻的分佈。但現在,我們面臨一個前面遺留的問題:什麼是”uniform(均勻)”?

數學上,條件分佈p(y|x)的均勻度就是條件熵定義:
這裏寫圖片描述

熵的下界是0, 這時模型沒有任何不確定性;熵的上界是log|Y|,即在所有可能(|Y|個)的y上均勻分佈。有了這個定義,我們準備提出最大熵原則。

當從允許的概率分佈集合C中選擇一個模型時,選擇模型這裏寫圖片描述,使得熵H(p)最大。

這裏寫圖片描述

可以證明這裏寫圖片描述是well-defined的,就是說,在任何的約束集合C中,總是存在唯一的模型這裏寫圖片描述取得最大熵。

  • Exponential form

最大熵原理呈現給我們的是一個約束優化問題:find the 這裏寫圖片描述 which maximizes H(p)。簡單的情況,我們可以分析求出問題的解。如翻譯模型中我們施加前兩個約束時,容易求得p的分佈。不幸的是,通常最大熵模型的解無法明顯得出,我們需要一個相對間接的方法。

爲了解決這個問題,我們採用約束優化理論中Lagrange multipliers的方法。這裏僅概述相關步驟,請參考進一步閱讀以更深入瞭解約束優化理論如何應用到最大熵模型中的。

我們的約束優化問題是:
這裏寫圖片描述

我們將這個稱爲原始問題(primal)。簡單的講,我們目標是在滿足以下約束的情況下,最大化H(p)。

這裏寫圖片描述

爲了解決這個優化問題,引入Lagrangian 乘子。

這裏寫圖片描述

實值參數這裏寫圖片描述這裏寫圖片描述對應施加在解上的n+1個約束。

下面的策略可以求出p的最優解(這裏寫圖片描述),但我們不打算證明它。

首先,將這裏寫圖片描述這裏寫圖片描述看成常量,尋找p最大化公式(8)。這會產生以這裏寫圖片描述這裏寫圖片描述爲參數的表示式p,(參數沒有解決)。接着,將該表達式代回(8)中,這次求這裏寫圖片描述這裏寫圖片描述的最優解( 這裏寫圖片描述and這裏寫圖片描述 ,respectively)。

按照這一方式,我們保證這裏寫圖片描述這裏寫圖片描述不變,計算在所有這裏寫圖片描述空間下,計算這裏寫圖片描述的無約束的最大值。

這裏寫圖片描述

令該式等於0, 求解 p(y|x):

這裏寫圖片描述

可以看出公式(10)的第二個因子對應第二個約束:

這裏寫圖片描述

將上式帶入公式(10)得到:

這裏寫圖片描述

將公式(11)帶入(10),我們得到:
這裏寫圖片描述

因此,

這裏寫圖片描述

Z(x)是正則化因子。

現在我們要求解最優值這裏寫圖片描述,這裏寫圖片描述 。顯然,我們已經知道了這裏寫圖片描述 ,還不知道這裏寫圖片描述

爲此,我們介紹新的符號,定義對偶函數這裏寫圖片描述

這裏寫圖片描述

對偶優化問題是:
這裏寫圖片描述

因爲p*和這裏寫圖片描述是固定的,公式(15)的右邊只有自由變量這裏寫圖片描述

參數值等於這裏寫圖片描述的p*就是一開始約束優化問題的最優解。這辦法不明顯看出爲什麼,但這的確是Lagrange multipliers理論的基本原理,通常叫做Kuhn-Tucker theorem(KTT)。詳細的解釋已經超出本文討論的範圍。我們簡單地陳述最後結論:

滿足約束C最大熵模型具有(13)參數化形式,最優參數這裏寫圖片描述可以通過最小化對偶函數這裏寫圖片描述求得。

補充說明:

這裏寫圖片描述究竟是什麼樣呢? (記住我們要求這裏寫圖片描述的最小值, 這是Lagrange multipliers理論的基本原理)

這裏寫圖片描述

  • Maximum likelihood

最大似然率:找出與樣本的分佈最接近的概率分佈模型。

比如:10次拋硬幣的結果是:

畫畫字畫畫畫字字畫畫

假設p是每次拋硬幣結果爲”畫”的概率。

得到這樣試驗結果的概率是:

P = pp(1-p)ppp(1-p)(1-p)pp=p7(1-p)3

最大化似然率的方法就是:
這裏寫圖片描述

最優解是:p=0.7

似然率的一般定義爲:這裏寫圖片描述

p(x)是模型估計的概率分佈,這裏寫圖片描述是結果樣本的概率分佈。

在我們的問題裏,要估計的是p(y|x),最大似然率爲:

這裏寫圖片描述

因此,有:

這裏寫圖片描述

這裏的p具有公式(13)的形式,我們的結果進一步可以表述爲:

最大熵模型這裏寫圖片描述是具有公式(13)形式,且最大化樣本似然率的模型。最大熵模型與最大似然率模型一致。

  • Computing the Parameters

要算λ,解析解肯定是行不通的。對於最大熵模型對應的最優化問題,GIS,lbfgs,sgd等等最優化算法都能解。相比之下,GIS大概是最好實現的。這裏只介紹GIS算法。

具體步驟如下:

(1)set這裏寫圖片描述 等於任意值, 比如等於0.

這裏寫圖片描述

(2) 重複直到收斂:
這裏寫圖片描述

這裏,(t)是迭代下標,常數C定義爲:
這裏寫圖片描述, 實踐中C是訓練樣本里最大的特徵個數。儘管C再大些也沒關係,但是它決定了收斂速度,還是取最小的好。

實際上,GIS算法用第N次迭代的模型來估算每個特徵在訓練數據中的分佈。如果超過了實際的,就把相應參數這裏寫圖片描述變小。否則,將它們變大。當訓練樣本的特徵分佈和模型的特徵分佈相同時,就求得了最優參數。

  • 最大熵實現

下面是GIS訓練算法的python實現,代碼不到100行。

from collections import defaultdict

import math


class MaxEnt(object):

    def __init__(self):

        self.feats = defaultdict(int)

        self.trainset = []

        self.labels = set()  



    def load_data(self,file):

        for line in open(file):

            fields = line.strip().split()

            # at least two columns

            if len(fields) < 2: continue

            # the first column is label

            label = fields[0]

            self.labels.add(label)

            for f in set(fields[1:]):

                # (label,f) tuple is feature 

                self.feats[(label,f)] += 1

            self.trainset.append(fields)



    def _initparams(self):

        self.size = len(self.trainset)

        # M param for GIS training algorithm

        self.M = max([len(record)-1 for record in self.trainset])

        self.ep_ = [0.0]*len(self.feats)

        for i,f in enumerate(self.feats):

            # calculate feature expectation on empirical distribution

            self.ep_[i] = float(self.feats[f])/float(self.size)

            # each feature function correspond to id

            self.feats[f] = i

        # init weight for each feature

        self.w = [0.0]*len(self.feats)

        self.lastw = self.w



    def probwgt(self,features,label):

        wgt = 0.0

        for f in features:

            if (label,f) in self.feats:

                wgt += self.w[self.feats[(label,f)]]

        return math.exp(wgt)



    """

    calculate feature expectation on model distribution

    """        

    def Ep(self):

        ep = [0.0]*len(self.feats)

        for record in self.trainset:

            features = record[1:]

            # calculate p(y|x)

            prob = self.calprob(features)

            for f in features:

                for w,l in prob:

                    # only focus on features from training data.

                    if (l,f) in self.feats:

                        # get feature id

                        idx = self.feats[(l,f)]

                        # sum(1/N * f(y,x)*p(y|x)), p(x) = 1/N

                        ep[idx] += w * (1.0/self.size)

        return ep



    def _convergence(self,lastw,w):

        for w1,w2 in zip(lastw,w):

            if abs(w1-w2) >= 0.01:

                return False

        return True



    def train(self, max_iter =1000):

        self._initparams()

        for i in range(max_iter):

            print 'iter %d ...'%(i+1)

            # calculate feature expectation on model distribution

            self.ep = self.Ep()           

            self.lastw = self.w[:]  

            for i,win enumerate(self.w):

                delta = 1.0/self.M * math.log(self.ep_[i]/self.ep[i])

                # update w

                self.w[i] += delta

            print self.w

            # test if the algorithm is convergence

            if self._convergence(self.lastw,self.w):

                break



    def calprob(self,features):

        wgts = [(self.probwgt(features, l),l) for l in self.labels]

        Z = sum([ w for w,l in wgts])

        prob = [ (w/Z,l) for w,l in wgts]

        return prob 



    def predict(self,input):

        features = input.strip().split()

        prob = self.calprob(features)

        prob.sort(reverse=True)

        return prob   

原文參考
https://blog.csdn.net/erli11/article/details/24718655

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章