概率語言模型及其變形系列(1)-PLSA及EM算法

本系列博文介紹常見概率語言模型及其變形模型,主要總結PLSA、LDA及LDA的變形模型及參數Inference方法。初步計劃內容如下

第一篇:PLSA及EM算法

第二篇:LDA及Gibbs Samping

第三篇:LDA變形模型-Twitter LDA,TimeUserLDA,ATM,Labeled-LDA,MaxEnt-LDA等

第四篇:基於變形LDA的paper分類總結

第五篇:LDA Gibbs Sampling的JAVA實現


第一篇 PLSA及EM算法

[本文PDF版本下載地址 PLSA及EM算法-yangliuy]

前言:本文主要介紹PLSA及EM算法,首先給出LSA(隱性語義分析)的早期方法SVD,然後引入基於概率的PLSA模型,其參數學習採用EM算法。接着我們分析如何運用EM算法估計一個簡單的mixture unigram 語言模型和混合高斯模型GMM的參數,最後總結EM算法的一般形式及運用關鍵點。對於改進PLSA,引入hyperparameter的LDA模型及其Gibbs Sampling參數估計方法放在本系列後面的文章LDA及Gibbs Samping介紹。


1 LSA and SVD

LSA(隱性語義分析)的目的是要從文本中發現隱含的語義維度-即“Topic”或者“Concept”。我們知道,在文檔的空間向量模型(VSM)中,文檔被表示成由特徵詞出現概率組成的多維向量,這種方法的好處是可以將query和文檔轉化成同一空間下的向量計算相似度,可以對不同詞項賦予不同的權重,在文本檢索、分類、聚類問題中都得到了廣泛應用,在基於貝葉斯算法及KNN算法的newsgroup18828文本分類器的JAVA實現基於Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚類器的JAVA實現系列文章中的分類聚類算法大多都是採用向量空間模型。然而,向量空間模型沒有能力處理一詞多義和一義多詞問題,例如同義詞也分別被表示成獨立的一維,計算向量的餘弦相似度時會低估用戶期望的相似度;而某個詞項有多個詞義時,始終對應同一維度,因此計算的結果會高估用戶期望的相似度。


LSA方法的引入就可以減輕類似的問題。基於SVD分解,我們可以構造一個原始向量矩陣的一個低秩逼近矩陣,具體的做法是將詞項文檔矩陣做SVD分解




  其中是以詞項(terms)爲行, 文檔(documents)爲列做一個大矩陣. 設一共有t行d列,  矩陣的元素爲詞項的tf-idf值。然後的r個對角元素的前k個保留(最大的k個保留), 後面最小的r-k個奇異值置0, 得到;最後計算一個近似的分解矩陣




在最小二乘意義下是的最佳逼近。由於最多包含k個非零元素,所以的秩不超過k。通過在SVD分解近似,我們將原始的向量轉化成一個低維隱含語義空間中,起到了特徵降維的作用。每個奇異值對應的是每個“語義”維度的權重,將不太重要的權重置爲0,只保留最重要的維度信息,去掉一些信息“nosie”,因而可以得到文檔的一種更優表示形式。將SVD分解降維應用到文檔聚類的JAVA實現可參見此文

IIR中給出的一個SVD降維的實例如下:


左邊是原始矩陣的SVD分解,右邊是只保留權重最大2維,將原始矩陣降到2維後的情況。


2 PLSA

儘管基於SVD的LSA取得了一定的成功,但是其缺乏嚴謹的數理統計基礎,而且SVD分解非常耗時。Hofmann在SIGIR'99上提出了基於概率統計的PLSA模型,並且用EM算法學習模型參數。PLSA的概率圖模型如下




其中D代表文檔,Z代表隱含類別或者主題,W爲觀察到的單詞,表示單詞出現在文檔的概率,表示文檔中出現主題下的單詞的概率,給定主題出現單詞的概率。並且每個主題在所有詞項上服從Multinomial 分佈,每個文檔在所有主題上服從Multinomial 分佈。整個文檔的生成過程是這樣的:

(1) 以的概率選中文檔

(2) 以的概率選中主題

(3) 以的概率產生一個單詞。

我們可以觀察到的數據就是對,而是隱含變量。的聯合分佈爲




分佈對應了兩組Multinomial 分佈,我們需要估計這兩組分佈的參數。下面給出用EM算法估計PLSA參數的詳細推導過程。


3 Estimate parameters in PLSA  by EM

(注:本部分主要參考Tomas Hoffman, Unsupervised Learning by Probabilistic Latent Semantic Analysis.

文本語言模型的參數估計-最大似然估計、MAP及貝葉斯估計一文所述,常用的參數估計方法有MLE、MAP、貝葉斯估計等等。但是在PLSA中,如果我們試圖直接用MLE來估計參數,就會得到似然函數




其中是term 出現在文檔中的次數。注意這是一個關於的函數,一共有N*K + M*K個自變量(注意這裏M表示term的總數,一般文獻習慣用V表示),如果直接對這些自變量求偏導數,我們會發現由於自變量包含在對數和中,這個方程的求解很困難。因此對於這樣的包含“隱含變量”或者“缺失數據”的概率模型參數估計問題,我們採用EM算法。


EM算法的步驟是:

(1)E步驟:求隱含變量Given當前估計的參數條件下的後驗概率。

(2)M步驟:最大化Complete data對數似然函數的期望,此時我們使用E步驟裏計算的隱含變量的後驗概率,得到新的參數值。

兩步迭代進行直到收斂。


先解釋一下什麼是Incomplete data和complete data。Zhai老師在一篇經典的EM算法Notes中講到,當原始數據的似然函數很複雜時,我們通過增加一些隱含變量來增強我們的數據,得到“complete data”,而“complete data”的似然函數更加簡單,方便求極大值。於是,原始的數據就成了“incomplete data”。我們將會看到,我們可以通過最大化“complete data”似然函數的期望來最大化"incomplete data"的似然函數,以便得到求似然函數最大值更爲簡單的計算途徑。


針對我們PLSA參數估計問題,在E步驟中,直接使用貝葉斯公式計算隱含變量在當前參數取值條件下的後驗概率,有




在這個步驟中,我們假定所有的都是已知的,因爲初始時隨機賦值,後面迭代的過程中取前一輪M步驟中得到的參數值。


在M步驟中,我們最大化Complete data對數似然函數的期望。在PLSA中,Incomplete data 是觀察到的,隱含變量是主題,那麼complete data就是三元組,其期望是




注意這裏是已知的,取的是前面E步驟裏面的估計值。下面我們來最大化期望,這又是一個多元函數求極值的問題,可以用拉格朗日乘數法。拉格朗日乘數法可以把條件極值問題轉化爲無條件極值問題,在PLSA中目標函數就是,約束條件是




由此我們可以寫出拉格朗日函數




這是一個關於的函數,分別對其求偏導數,我們可以得到




注意這裏進行過方程兩邊同時乘以的變形,聯立上面4組方程,我們就可以解出M步驟中通過最大化期望估計出的新的參數值




解方程組的關鍵在於先求出,其實只需要做一個加和運算就可以把的係數都化成1,後面就好計算了。

然後使用更新後的參數值,我們又進入E步驟,計算隱含變量 Given當前估計的參數條件下的後驗概率。如此不斷迭代,直到滿足終止條件。

注意到我們在M步驟中還是使用對Complete Data的MLE,那麼如果我們想加入一些先驗知識進入我們的模型,我們可以在M步驟中使用MAP估計。正如文本語言模型的參數估計-最大似然估計、MAP及貝葉斯估計中投硬幣的二項分佈實驗中我們加入“硬幣一般是兩面均勻的”這個先驗一樣。而由此計算出的參數的估計值會在分子分母中多出關於先驗參數的preduo counts,其他步驟都是一樣的。具體可以參考Mei Qiaozhu 的Notes

PLSA的實現也不難,網上有很多實現code。

例如這個PLSA的EM算法實現 http://ezcodesample.com/plsaidiots/PLSAjava.txt

主要的類如下(作者Andrew Polar)

//The code is taken from:
//http://code.google.com/p/mltool4j/source/browse/trunk/src/edu/thu/mltool4j/topicmodel/plsa
//I noticed some difference with original Hofmann concept in computation of P(z). It is 
//always even and actually not involved, that makes this algorithm non-negative matrix 
//factoring and not PLSA.
//Found and tested by Andrew Polar. 
//My version can be found on semanticsearchart.com or ezcodesample.com

class ProbabilisticLSA
{
	private Dataset dataset = null;
    private Posting[][] invertedIndex = null;
    private int M = -1; // number of data
    private int V = -1; // number of words
    private int K = -1; // number of topics

    public ProbabilisticLSA()
    {
    }

    public boolean doPLSA(String datafileName, int ntopics, int iters)
    {
    	File datafile = new File(datafileName);
        if (datafile.exists())
        {
        	if ((this.dataset = new Dataset(datafile)) == null)
            {
        		System.out.println("doPLSA, dataset == null");
                return false;
            }
            this.M = this.dataset.size();
            this.V = this.dataset.getFeatureNum();
            this.K = ntopics;
            
             //build inverted index
            this.buildInvertedIndex(this.dataset);
            //run EM algorithm
            this.EM(iters);
            return true;
            
        }
        else
        {
        	System.out.println("ProbabilisticLSA(String datafileName), datafile: " + datafileName + " doesn't exist");
            return false;
        }
    }

    //Build the inverted index for M-step fast calculation. Format:
    //invertedIndex[w][]: a unsorted list of document and position which word w
    // occurs. 
    //@param ds the dataset which to be analysis
    @SuppressWarnings("unchecked")
    private boolean buildInvertedIndex(Dataset ds)
    {
    	ArrayList<Posting>[] list = new ArrayList[this.V];
        for (int k=0; k<this.V; ++k) {
        	list[k] = new ArrayList<Posting>();
        }
        	
        for (int m = 0; m < this.M; m++)
        {
        	Data d = ds.getDataAt(m);
         	for (int position = 0; position < d.size(); position++)
         	{
        		int w = d.getFeatureAt(position).dim;
         		// add posting
         		list[w].add(new Posting(m, position));
        	}
        }
        // convert to array
        this.invertedIndex = new Posting[this.V][];
        for (int w = 0; w < this.V; w++)
        {
        	this.invertedIndex[w] = list[w].toArray(new Posting[0]);
        }
        return true;
    }

    private boolean EM(int iters)
    {
    	// p(z), size: K
        double[] Pz = new double[this.K];

        // p(d|z), size: K x M
        double[][] Pd_z = new double[this.K][this.M];

        // p(w|z), size: K x V
        double[][] Pw_z = new double[this.K][this.V];

        // p(z|d,w), size: K x M x doc.size()
        double[][][] Pz_dw = new double[this.K][this.M][];

         // L: log-likelihood value
         double L = -1;

         // run EM algorithm
         this.init(Pz, Pd_z, Pw_z, Pz_dw);
         for (int it = 0; it < iters; it++)
         {
         	 // E-step
        	 if (!this.Estep(Pz, Pd_z, Pw_z, Pz_dw))
        	 {
        		 System.out.println("EM,  in E-step");
             }

             // M-step
             if (!this.Mstep(Pz_dw, Pw_z, Pd_z, Pz))
             {
            	 System.out.println("EM, in M-step");
             }

             L = calcLoglikelihood(Pz, Pd_z, Pw_z);
             System.out.println("[" + it + "]" + "\tlikelihood: " + L);
         }
                
         //print result
         for (int m = 0; m < this.M; m++)
         {
        	 double norm = 0.0;
        	 for (int z = 0; z < this.K; z++) {
        		 norm += Pd_z[z][m];
             }
        	 if (norm <= 0.0) norm = 1.0;
        	 for (int z = 0; z < this.K; z++) {
        		 System.out.format("%10.4f", Pd_z[z][m]/norm);
             }
             System.out.println();
        } 
        return false;
    }
   
    private boolean init(double[] Pz, double[][] Pd_z, double[][] Pw_z, double[][][] Pz_dw)
    {
    	// p(z), size: K
        double zvalue = (double) 1 / (double) this.K;
        for (int z = 0; z < this.K; z++)
        {
        	Pz[z] = zvalue;
        }

        // p(d|z), size: K x M
        for (int z = 0; z < this.K; z++)
        {
        	double norm = 0.0;
            for (int m = 0; m < this.M; m++)
            {
            	Pd_z[z][m] = Math.random();
                norm += Pd_z[z][m];
            }

            for (int m = 0; m < this.M; m++)
            {
            	Pd_z[z][m] /= norm;
            }
        }

        // p(w|z), size: K x V
        for (int z = 0; z < this.K; z++)
        {
        	double norm = 0.0;
            for (int w = 0; w < this.V; w++)
            {
            	Pw_z[z][w] = Math.random();
                norm += Pw_z[z][w];
            }

            for (int w = 0; w < this.V; w++)
            {
            	Pw_z[z][w] /= norm;
            }
        }

        // p(z|d,w), size: K x M x doc.size()
        for (int z = 0; z < this.K; z++)
        {
        	for (int m = 0; m < this.M; m++)
            {
         		Pz_dw[z][m] = new double[this.dataset.getDataAt(m).size()];
            }
        }
        return false;
    }

    private boolean Estep(double[] Pz, double[][] Pd_z, double[][] Pw_z, double[][][] Pz_dw)
    {
    	for (int m = 0; m < this.M; m++)
        {
    		Data data = this.dataset.getDataAt(m);
            for (int position = 0; position < data.size(); position++)
            {
            	// get word(dimension) at current position of document m
                int w = data.getFeatureAt(position).dim;

                double norm = 0.0;
                for (int z = 0; z < this.K; z++)
                {
                	double val = Pz[z] * Pd_z[z][m] * Pw_z[z][w];
                    Pz_dw[z][m][position] = val;
                    norm += val;
                }

                // normalization
                for (int z = 0; z < this.K; z++)
                {
                	Pz_dw[z][m][position] /= norm;
                }
            }
        }
        return true;
    }

    private boolean Mstep(double[][][] Pz_dw, double[][] Pw_z, double[][] Pd_z, double[] Pz)
    {
    	// p(w|z)
        for (int z = 0; z < this.K; z++)
        {
        	double norm = 0.0;
            for (int w = 0; w < this.V; w++)
            {
            	double sum = 0.0;

                Posting[] postings = this.invertedIndex[w];
                for (Posting posting : postings)
                {
                	int m = posting.docID;
                    int position = posting.pos;
                    double n = this.dataset.getDataAt(m).getFeatureAt(position).weight;
                    sum += n * Pz_dw[z][m][position];
                }
                Pw_z[z][w] = sum;
                norm += sum;
            }

            // normalization
            for (int w = 0; w < this.V; w++)
            {
            	Pw_z[z][w] /= norm;
            }
        }

        // p(d|z)
        for (int z = 0; z < this.K; z++)
        {
        	double norm = 0.0;
            for (int m = 0; m < this.M; m++)
            {
            	double sum = 0.0;
                Data d = this.dataset.getDataAt(m);
                for (int position = 0; position < d.size(); position++)
                {
                	double n = d.getFeatureAt(position).weight;
                    sum += n * Pz_dw[z][m][position];
                }
                Pd_z[z][m] = sum;
                norm += sum;
            }

            // normalization
            for (int m = 0; m < this.M; m++)
            {
            	Pd_z[z][m] /= norm;
            }
        }

        //This is definitely a bug
        //p(z) values are even, but they should not be even
        double norm = 0.0;
        for (int z = 0; z < this.K; z++)
        {
        	double sum = 0.0;
            for (int m = 0; m < this.M; m++)
            {
            	sum += Pd_z[z][m];
            }
            Pz[z] = sum;
            norm += sum;
       }

        // normalization
        for (int z = 0; z < this.K; z++)
        {
        	Pz[z] /= norm;
        	//System.out.format("%10.4f", Pz[z]);  //here you can print to see
        }
        //System.out.println();

        return true;
    }

    private double calcLoglikelihood(double[] Pz, double[][] Pd_z, double[][] Pw_z)
    {
    	double L = 0.0;
        for (int m = 0; m < this.M; m++)
        {
        	Data d = this.dataset.getDataAt(m);
            for (int position = 0; position < d.size(); position++)
            {
            	Feature f = d.getFeatureAt(position);
                int w = f.dim;
                double n = f.weight;

                double sum = 0.0;
                for (int z = 0; z < this.K; z++)
                {
                	sum += Pz[z] * Pd_z[z][m] * Pw_z[z][w];
                }
                L += n * Math.log10(sum);
            }
        }
        return L;
    }
}

public class PLSA {
	public static void main(String[] args) {
		
		ProbabilisticLSA plsa = new ProbabilisticLSA();
		//the file is not used, the hard coded data is used instead, but file name should be valid,
		//just replace the name by something valid.
		plsa.doPLSA("C:\\Users\\APolar\\workspace\\PLSA\\src\\data.txt", 2, 60);
        System.out.println("end PLSA");
    }
}

4 Estimate parameters in a simple mixture unigram language model by EM

在PLSA的參數估計中,我們使用了EM算法。EM算法經常用來估計包含“缺失數據”或者“隱含變量”模型的參數估計問題。這兩個概念是互相聯繫的,當我們的模型中有“隱含變量”時,我們會認爲原始數據是“不完全的數據”,因爲隱含變量的值無法觀察到;反過來,當我們的數據incomplete時,我們可以通過增加隱含變量來對“缺失數據”建模。


爲了加深對EM算法的理解,下面我們來看如何用EM算法來估計一個簡單混合unigram語言模型的參數。本部分主要參考Zhai老師的EM算法Notes


4.1 最大似然估計與隱含變量引入

所謂unigram語言模型,就是構建語言模型是拋棄所有上下文信息,認爲一個詞出現的概率與其所在位置無關,具體概率圖模型可以參見LDA及Gibbs Samping一文中的介紹。什麼是混合模型(mixture model)呢?通俗的說混合概率模型就是由最基本的概率分佈比如正態分佈、多元分佈等經過線性組合形成的新的概率模型,比如混合高斯模型就是由K個高斯分佈線性組合而得到。混合模型中產生數據的確切“component model”對我們是隱藏的。我們假設混合模型包含兩個multinomial component model,一個是背景詞生成模型,另一個是主題詞生成模型。注意這種模型組成方式在概率語言模型中很常見。爲了表示單詞是哪個模型生成的,我們會爲每個單詞增加一個布爾類型的控制變量。


文檔的對數似然函數爲



爲第i個文檔中的第j個詞,爲表示文檔中背景詞比例的參數,通常根據經驗給定。因此是已知的,我們只需要估計即可。

同樣的我們首先試圖用最大似然估計來估計參數。也就是去找最大化似然函數的參數值,有




這是一個關於的函數,同樣的,包含在了對數和中。因此很難求解極大值,用拉格朗日乘數法,你會發現偏導數等於0得到的方程很難求解。所以我們需要依賴數值算法,而EM算法就是其中常用的一種。


我們爲每個單詞引入一個布爾類型的變量z表示該單詞是background word 還是topic word.即




這裏我們假設"complete data"不僅包含可以觀察到F中的所有單詞,而且還包括隱含的變量z。那麼根據EM算法,在E步驟我們計算“complete data”的對數似然函數有




比較一下,求和運算在對數之外進行,因爲此時通過控制變量z的設置,我們明確知道了單詞是由背景詞分佈還是topic 詞分佈產生的。的關係是怎樣的呢?如果帶估計參數是,原始數據是X,對於每一個原始數據分配了一個隱含變量H,則有




4.2 似然函數的下界分析

EM算法的基本思想就是初始隨機給定待估計參數的值,然後通過E步驟和M步驟兩步迭代去不斷搜索更好的參數值。更好的參數值應該要滿足使得似然函數更大。我們假設一個潛在的更好參數值是,第n次迭代M步驟得到的參數估計值是,那麼兩個參數值對應的似然函數和"complete data"的似然函數的差滿足




我們尋找更好參數值的目標就是要最大化,也等價於最大化。我們來計算隱含變量在給定當前數據X和當前估計的參數值條件下的條件概率分佈即,有




其中右邊第三項是的相對熵,總爲非負值。因此我們有




於是我們得到了潛在更好參數值的incomplete data似然函數的下界。這裏我們尤其要注意右邊後兩項爲常數,因爲不包含。所以incomplete data似然函數的下界就是complete data似然函數的期望,也就是諸多EM算法講義中出現的Q函數,表達式爲




可以看出這個期望等於complete data似然函數乘以對應隱含變量條件概率再求和。對於我們要求解的問題,Q函數就是




這裏多解釋幾句Q函數。單詞相應的變量z爲0時,單詞爲topic word,從多元分佈中產生;當z爲1時,單詞爲background word,從多元分佈產生。同時我們也可以看到如何求Q函數即complete data似然函數的期望,也就是我們要最大化的那個期望(EM算法最大化期望指的就是這個期望),我們要特別關注隱含變量在觀察到數據X和前一輪估計出的參數值條件下取不同值的概率,而隱含變量不同的值對應complete data的不同的似然函數,我們要計算的所謂的期望就是指complete data的似然函數值在不同隱含變量取值情況下的期望值。


4.3 EM算法的一般步驟

通過4.2部分的分析,我們知道,如果我們在下一輪迭代中可以找到一個更好的參數值使得




那麼相應的也會有,因此EM算法的一般步驟如下

(1) 隨機初始化參數值,也可以根據任何關於最佳參數取值範圍的先驗知識來初始化

(2) 不斷兩步迭代尋找更優的參數值

     (a) E步驟(求期望) 計算Q函數 




     (b)M步驟(最大化)通過最大化Q函數來尋找更優的參數值




(3) 當似然函數收斂時算法停止。


這裏需要注意如何儘量保證EM算法可以找到全局最優解而不是局部最優解呢?第一種方法是嘗試許多不同的參數初始值,然後從得到的很多估計出的參數值中選取最優的;第二種方法是通過一個更簡單的模型比如只有唯一全局最大值的模型來決定複雜模型的初始值。


通過前面的分析可以知道,EM算法的優勢在於complete data的似然函數更容易最大化,因爲已經假定了隱含變量的取值,當然要乘以隱含變量取該值的條件概率,所以最終變成了最大化期望值。由於隱含變量變成了已知量,Q函數比原始incomplete data的似然函數更容易求最大值。因此對於“缺失數據”的情況,我們通過引入隱含變量使得complete data的似然函數容易最大化。


在E步驟中,主要的計算難點在於計算隱含變量的條件概率,在PLSA中就是




在我們這個簡單混合語言模型的例子中就是




我們假設z的取值只於當前那一個單詞有關,計算很容易,但是在LDA中用這種方法計算隱含變量的條件概率和最大化Q函數就比較複雜,可以參見原始LDA論文的參數推導部分。我們也可以用更簡單的Gibbs Sampling來估計參數,具體可以參見LDA及Gibbs Samping


繼續我們的問題,下面便是M步驟。使用拉格朗日乘數法來求Q函數的最大值,約束條件是




構造拉格朗日輔助函數




對自變量求偏導數




令偏導數爲0解出來唯一的極值點




容易知道這裏唯一的極值點就是最值點了。注意這裏Zhai老師變換了一下變量表示,把對文檔裏面詞的遍歷轉化成了對詞典裏面的term的遍歷,因爲z的取值至於對應的那一個單詞有關,與上下文無關。因此E步驟求隱含變量的條件概率公式也相應變成了




最後我們就得到了簡單混合Unigram語言模型的EM算法更新公式

即E步驟 求隱含變量條件概率和M步驟 最大化期望估計參數的公式




整個計算過程我們可以看到,我們不需要明確求出Q函數的表達式。取而代之的是我們計算隱含變量的條件概率,然後通過最大化Q函數來得到新的參數估計值。

因此EM算法兩步迭代的過程實質是在尋找更好的待估計參數的值使得原始數據即incomplete data似然函數的下界不斷提升,而這個“下界“就是引入隱含變量之後的complete data似然函數的期望,也就是諸多EM算法講義中出現的Q函數,通過最大化Q函數來尋找更優的參數值。同時,上一輪估計出的參數值會在下一輪E步驟中當成已知條件計算隱含變量的條件概率,而這個條件概率又是最大化Q函數求新的參數值是所必需的。


5 Estimate parameters in GMM by EM

經過第3部分和第4部分用EM算法求解PLSA和簡單unigram混合模型參數估計問題的詳細分析,相信大部分讀者已經對EM算法有了一定理解。關於EM算法的材料包括PRML會首先介紹用EM算法去求解混合高斯模型GMM的參數估計問題。下面就讓我們來看看如何用EM算法來求解混合高斯模型GMM。


混合高斯模型GMM由K個高斯模型的線性組合組成,高斯模型就是正態分佈模型,其中每個高斯模型我們成爲一個”Component“,GMM的概率密度函數就是這K個高斯模型概率密度函數的線性組合即



其中

就是高斯分佈即正態分佈的概率密度函數。這是x爲向量的情況,對於x爲標量的情況就是


大部分讀者應該對標量情形的概率分佈更熟悉。這裏囉嗦幾句,最近看機器學習的論文和書籍,裏面的隨機變量基本都是多維向量,向量的計算比如加減乘除和求導運算都和標量運算有一些區別,尤其是求導運算,向量和矩陣的求導運算會麻煩很多,看pluskid推薦的一本冊子Matrix Cookbook,裏面有很多矩陣求導公式,直接查閱應該會更方便。


下面繼續說GMM。根據上面給出的概率密度函數,如果我們要從 GMM 的分佈中Sample一個樣本,實際上可以分爲兩步:首先隨機地在這 K 個 Component 之中選一個,每個 Component 被選中的概率實際上就是它的係數 \pi_k ,選中了 Component 之後,再單獨地考慮從這個 Component 的分佈中選取一個樣本點就可以了。在PRML上,引入了一個K維二值隨機變量z,只有1維是1,其他維都是0。唯一那個非零的維對應的就是GMM參數樣本時被選中的那個高斯分佈,而某一維非零的概率就是\pi_k,即



下面我們開始估計GMM的參數,包括這K個高斯分佈的所有均值和方差以及線性組合的係數。我們給每個樣本數據增加一個隱含變量, 就是上面所說的K維向量,表明了是從哪個高斯分佈中sample出來的。對應的概率圖模型就是




觀察變量的對數似然函數爲




令對的偏導數等於0我們有




注意這裏我們定義了表示後驗概率,也就是第n個樣本是有第k個高斯分佈產生的概率。可以解出



就是由第K個高斯分佈產生的樣本點的總數;用聚類的觀點看,就是聚到cluster k的樣本點總數。然後我們將對數似然函數對求偏導數,令偏導數爲0,得到協方差矩陣




最後我們求係數\pi_k。注意到係數的和爲1,即




這就是約束條件,最大化對數似然函數又成爲了條件極值問題。我們仍然用拉格朗日乘數法,構造輔助函數如下




求導數,令導數爲0有



這樣我們就估計出來係數項。

因此用EM算法估計GMM參數的步驟如下


(1) E步驟:估計數據由每個 Component 生成的概率:對於每個數據  來說,它由第 k 個 Component 生成的概率爲


注意裏面 \mu_k 和 \Sigma_k 也是需要我們估計的值,在E步驟我們假定 \mu_k 和 \Sigma_k 均已知,我們使用上一次迭代所得的值(或者初始值)。


(2)M步驟:由最大估計求出高斯分佈的所有均值、方差和線性組合的係數,更新待估計的參數值,根據上面的推導,計算公式是



其中


(3)重複迭代E步驟和M步驟,直到似然函數


收斂時算法停止。

更多關於EM算法的深入分析,可以參考PRML第9章內容。

最後我們給出用EM算法估計GMM參數的Matlab實現,出自pluskid的博客

function varargout = gmm(X, K_or_centroids)
% ============================================================
% Expectation-Maximization iteration implementation of
% Gaussian Mixture Model.
%
% PX = GMM(X, K_OR_CENTROIDS)
% [PX MODEL] = GMM(X, K_OR_CENTROIDS)
%
%  - X: N-by-D data matrix.
%  - K_OR_CENTROIDS: either K indicating the number of
%       components or a K-by-D matrix indicating the
%       choosing of the initial K centroids.
%
%  - PX: N-by-K matrix indicating the probability of each
%       component generating each point.
%  - MODEL: a structure containing the parameters for a GMM:
%       MODEL.Miu: a K-by-D matrix.
%       MODEL.Sigma: a D-by-D-by-K matrix.
%       MODEL.Pi: a 1-by-K vector.
% ============================================================
 
    threshold = 1e-15;
    [N, D] = size(X);
 
    if isscalar(K_or_centroids)
        K = K_or_centroids;
        % randomly pick centroids
        rndp = randperm(N);
        centroids = X(rndp(1:K), :);
    else
        K = size(K_or_centroids, 1);
        centroids = K_or_centroids;
    end
 
    % initial values
    [pMiu pPi pSigma] = init_params();
 
    Lprev = -inf;
    while true
        Px = calc_prob();
 
        % new value for pGamma
        pGamma = Px .* repmat(pPi, N, 1);
        pGamma = pGamma ./ repmat(sum(pGamma, 2), 1, K);
 
        % new value for parameters of each Component
        Nk = sum(pGamma, 1);
        pMiu = diag(1./Nk) * pGamma' * X;
        pPi = Nk/N;
        for kk = 1:K
            Xshift = X-repmat(pMiu(kk, :), N, 1);
            pSigma(:, :, kk) = (Xshift' * ...
                (diag(pGamma(:, kk)) * Xshift)) / Nk(kk);
        end
 
        % check for convergence
        L = sum(log(Px*pPi'));
        if L-Lprev < threshold
            break;
        end
        Lprev = L;
    end
 
    if nargout == 1
        varargout = {Px};
    else
        model = [];
        model.Miu = pMiu;
        model.Sigma = pSigma;
        model.Pi = pPi;
        varargout = {Px, model};
    end
 
    function [pMiu pPi pSigma] = init_params()
        pMiu = centroids;
        pPi = zeros(1, K);
        pSigma = zeros(D, D, K);
 
        % hard assign x to each centroids
        distmat = repmat(sum(X.*X, 2), 1, K) + ...
            repmat(sum(pMiu.*pMiu, 2)', N, 1) - ...
            2*X*pMiu';
        [dummy labels] = min(distmat, [], 2);
 
        for k=1:K
            Xk = X(labels == k, :);
            pPi(k) = size(Xk, 1)/N;
            pSigma(:, :, k) = cov(Xk);
        end
    end
 
    function Px = calc_prob()
        Px = zeros(N, K);
        for k = 1:K
            Xshift = X-repmat(pMiu(k, :), N, 1);
            inv_pSigma = inv(pSigma(:, :, k));
            tmp = sum((Xshift*inv_pSigma) .* Xshift, 2);
            coef = (2*pi)^(-D/2) * sqrt(det(inv_pSigma));
            Px(:, k) = coef * exp(-0.5*tmp);
        end
    end
end

6 全文總結

本文主要介紹PLSA及EM算法,首先給出LSA(隱性語義分析)的早期方法SVD,然後引入基於概率的PLSA模型,接着我們詳細分析瞭如何用EM算法估計PLSA、混合unigram 語言模型及混合高斯模型的參數過程,並總結了EM算法的一般形式和運用關鍵點。關於EM算法收斂性的證明可以參考斯坦福機器學習課程CS229 Andrew Ng老師的課程notes和JerryLead的筆記。EM算法在”缺失數據“和包含”隱含變量“的概率模型參數估計問題中非常常用,是機器學習、數據挖掘及NLP研究必須掌握的算法。


 參考文獻及推薦Notes

本文主要參考了Hoffman的PLSA論文、Zhai老師的EM Notes以及PRML第9章內容。

[1] Christopher M. Bishop. Pattern Recognition and Machine Learning (Information Science and Statistics). Springer-Verlag New York, Inc., Secaucus, NJ, USA, 2006.
[2] Gregor Heinrich. Parameter estimation for text analysis. Technical report, 2004.

[3] Wayne Xin Zhao, Note for pLSA and LDA, Technical report, 2011.

[4] Freddy Chong Tat Chua. Dimensionality reduction and clustering of text documents.Technical report, 2009.

[5] CX Zhai, A note on the expectation-maximization (em) algorithm 2007

[6] Qiaozhu Mei, A Note on EM Algorithm for Probabilistic Latent Semantic Analysis 2008

[7] pluskid, 漫談Clustering, Gaussina Mixture Model

[8] Christopher D. ManningPrabhakar Raghavan and Hinrich Schütze, Introduction to Information Retrieval, Cambridge University Press. 2008.

[9] Tomas Hoffman, Unsupervised Learning by Probabilistic Latent Semantic Analysis. 2011



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