1. 程式人生 > >王小草【機器學習】筆記--EM演算法

王小草【機器學習】筆記--EM演算法

標籤(空格分隔): 王小草機器學習筆記

EM演算法的英文全稱是Expectation Maximization Algorithm,也就是求期望最大化,也就是我們常說的目標函式求最大值的演算法。EM演算法,直觀的說,就是有一堆未知的資料(比如一些特徵值),這些資料可能來自於不同的類別,而你想知道的是每一個數據都來自於哪個類別,並且知道來自於這個類別的概率是多少。而在EM演算法看來,每一個類別中的資料必然服從了某個固有的分佈(如二項分佈,正態分佈等),只要尋找它密度函式的引數,就能知道資料的分佈,所以EM使用了極大似然估計的方法去估計了各個分佈的引數,從而進行了無監督地聚類。

本文首先會回顧和複習一下EM演算法中要涉及到的知識:Jensen不等式和最大似然估計;
然後介紹EM演算法,並且會用混合高斯GMM來作為例子用EM演算法求解。
最後,會給出一些實現EM演算法的python例項。

1. 回顧

1.1 回顧Jensen不等式

Jensen不等式這個名字有點陌生,但是如果眼睜睜地看到這個不等式,你肯定會覺得特別特別熟悉,並且鼻尖傳來陣陣數學考試卷的味道,它會出現在選擇題填空題或者後面的大分推導題中。萬萬沒想到,今天還會在那麼重要的演算法中遇到。

假設有兩個變數x1,x2,有一個函式f(),並且函式f是凸函式,那麼就肯定有以下不等式成立:

f(θx1 + (1-θ)x2) <= θf(x1) + (1-θ)f(x2)

在二維座標中表示如下
QQ截圖20160921122004.png-12kB

同理,若有多個x,多個θ,只要滿足f是凸函式,並且
θ1,θ2,…θk >= 0;且θ1+θ2+…+θk = 1
那麼下面不等式肯定成立:
QQ截圖20160921122255.png-5.8kB

上面講的是離散型的變數,針對連續型的變數,Jesen不等式也是成立的。
QQ截圖20160921122421.png-5.9kB
QQ截圖20160921122451.png-4.8kB

QQ截圖20160921122803.png-2.3kB中θ是隨機變數,分別與xi相乘後相加,其實就是在求x的期望值,那麼以上任何形式的不等式,都可以表示成如下:
QQ截圖20160921123009.png-3.3kB

1.2 回顧K-means演算法

之所以要在EM演算法中回顧K-means,是因為在迭代k-means中其實就是不斷取均值點求期望的過程,於EM迭代求最大化期望類似。

先初始選擇k個簇中心,然後根據各個樣本點到中心的距離來分割,再對分割後的類找到這個類中所有樣本點的中心點(均值點)作為新的簇中心,然後再進行聚類,再找新的中心,如此迴圈,直到滿足終止條件而停止。

這裡,通過各個樣本的均值來確定新的中心點其實就是在求期望,其過程的大致思路其實與我們接下去要講的EM演算法是類似的。

k-means能夠非常方便得將樣本分成若干簇,但是由一個巨大的問題是,我們無法知道這個樣本點屬於這個簇的概率,我們只知道屬於或不屬於的布林值,如此一來,就沒那麼好玩了。

那麼如果想知道概率的話,就得去找到與樣本分佈最接近的概率分佈模型,要得到概率分佈模型,就得先將模型中的引數估計出來。EM演算法所實現的就是這個功能。

2.最大似然估計求引數

2.1 小引子

先來看一個簡單的例子。
簡單的例子一般都離不開拋硬幣來。

假設我們現在不知道拋硬幣出現正反面的概率,然後設每拋一次出現正面的概率是p,這個p是我們想求得的。
現在我做一個實驗,將硬幣拋是10次,然後記錄結果為:正正反正正正反反正正。
最大似然估計就是去求出現以上這10次結果的概率最大時候,去估計p的概率。
因為每次拋硬幣是一個獨立事件,所以每一次拋硬幣的概率是可以相乘的,於是以上10次結果發生的概率可以寫成:
QQ截圖20160922094939.png-4.3kB
要求的P最大時p的值,其實就是對以上等式求目標函式最優化的過程,最後可以求出p=0.7。

當然,你肯定會義正言辭地說,不對!拋硬幣的概率誰不知道呢,正反兩面出現的概率都是0.5呀!
這是因為我們這個實驗中只拋了10次,樣本量太小存在的誤差會偏大,如果拋100次,1000次,10000次,樣本中正反兩面出現的次數會越來越趨於1:1,那麼求出來的p值肯定也會更接近與0.5了。

當然,你肯定還會義正言辭得說,可是這個極大似然估計有什麼用嗎,拋硬幣的概率我本來就知道。嗯,對,拋硬幣只是一個例子。假如我收集了上海9月份30天的天氣資料,想知道9月份的上海下雨的概率p有多大;再假如我記錄了今年我從東昌路上2號線有沒有座位的資料,想知道上車後有座位的概率p是多少;再假如我有所有進入購物網站的行為資料樣本,想知道首次進來的人會購買下單的概率;再假如二號店若推出新品促銷的廣告,使用者看到會點選進入的概率…等等。這些概率我想你應該不知道,但作為商家也許會非常渴望知道。

2.2 二項分佈的最大似然估計

拋硬幣其實是一個二項分佈,它有兩個值,概率分別為p和1-p。
假設投擲N次硬幣,出現正面朝上次數是n,反面朝上的次數是N-n。
並且設正面朝上的概率是p。
現在使用似然函式來求目標函式的最優化,為了計算方便,我們將函式取對數來求最大值,成為“對數似然函式”。
目標函式如下:
QQ截圖20160922111612.png-4.9kB

對目標函式中的p求導數,最後得到p = n/N
QQ截圖20160922111620.png-5.2kB

以上就是用最大似然函式估計二項分佈引數的過程。

2.3 高斯分佈的最大似然估計

現在我們來看一看高斯分佈。
若給定一組樣本X1,X2,X3…Xn,已知他們是來自於高斯分佈N(μ,σ),即符合均值為μ,標準差為σ的正太分佈。要根據這些樣本點的分佈去估計這個正態分佈的引數μ,σ。

1.首先,要知道高斯分佈的概率密度函式:
QQ截圖20160922112432.png-3.6kB
裡面有兩個引數,分別就是μ,σ。也是我們要求的引數。

2.要得到樣本點那樣分佈的概率,假設每個樣本都是獨立的,所有總的概率就是每個樣本的概率的乘積:
QQ截圖20160922112604.png-4.7kB

3.對上面的等式進行對數似然函式的化簡:
QQ截圖20160922112713.png-18.3kB

4.得到化簡後的目標函式:
QQ截圖20160922112745.png-5kB
現在就是對這個目標函式求最大值時的引數μ,σ的值

5.將目標函式分別對引數μ,σ進行求導,就能求出μ,σ的公式:
QQ截圖20160922112752.png-5.2kB

以上就是用最大似然函式估計高斯分佈引數的過程。

3.EM演算法

經過了冗長的鋪墊終於到了本文的重點了–EM演算法。在講EM演算法乏味難懂的定理前,我們先用高斯混合模型來走一遍它的引數估計

3.1 直觀理解猜測GMM的引數估計

已知一個學校的所有學生的身高樣本(X1,X2,X3…Xn),並且男生和女生都分別服從N(μ男,σ男)和N(μ女,σ女)的高斯分佈。目的是要求出μ男,σ男,μ女,σ女這四個引數。

來來來理一下這個題目,與上文中的例子不同了,現在同時有兩個高斯分佈混合在一起,我們要去求出兩個高斯分佈各自的均值與標準差引數。那麼問題來了…我怎麼知道某個樣本屬於男高斯還是女高斯啊,現在我們只知道所有的身高值,並不知道這些身高背後的性別呀。恩恩,這是一個混合高斯模型,簡稱GMM(隨機變數是由2個高斯分佈混合而成)。在GMM中,有一個隱藏的隨機變數我們沒法看到,那就是性別,因為無法知道性別的概率,也就無法知道某個樣本屬於哪個高斯分佈的了。所以這個隱藏的概率π男,π女也是我們需要估計的,它表示某個樣本屬於某個高斯分佈的概率。

將上面的例子擴充套件地表示出來:隨機變數X是由K個高斯分佈混合而成,取各個高斯分佈的概率為π1,π2,π3…πk;第i個高斯分佈的均值為μi,方差為Σi。若觀測到隨機變數X的一系列樣本X1,X2,X3…Xn,試估計引數π,μ,Σ。

1.建立目標函式
同樣的,我們使用對數似然函式來建立目標函式
QQ截圖20160922141447.png-6.7kB
由於對數函式裡面又有加和,無法直接用求導解方程的方法來求最大值。為了解決這個問題,接下來分兩步走。

2.第一步:估計資料來自哪個組(也就是說來自哪個高斯分佈)
首先我們根據經驗隨便給定π,μ,Σ的先驗值。
然後求r(i,k),表示,樣本i 由高斯分佈k生成的概率。公式如下:
QQ截圖20160922141751.png-5.8kB
根據這個公式,我們可以求得樣本X1分別屬於K1,K2,K3..的概率

3.第二步:估計每個高斯分佈中的引數
對於高斯分佈k來說,它說生成的點可看成是{r(i,k)*xi|i-=,1,2,3..N},就是所有原來的樣本點乘以它屬於高斯分佈k的概率,從而得到了新的樣本點,這些樣本點應是服從高斯分佈k的。因此高斯分佈k的樣本個數不再是原來的N,而是所有樣本屬於它的概率的加和:QQ截圖20160922144831.png-2.1kB
同理,π,μ,Σ都可以因此重新計算:
QQ截圖20160922144952.png-13.1kB

4.重複第一步,第二步
我們用先驗的π,μ,Σ計算出來新的π,μ,Σ,於是又可以利用新的π,μ,Σ去計算新的r(i,k),再得出又一輪新的π,μ,Σ,如此迴圈往復,這些引數會慢慢收斂,直到前後兩次的差值小於預先設定的閥值,就停止迭代,最後一次算出的π,μ,Σ就可以當成最終的估計值啦。

3.2 EM演算法的提出

問題的提出:
假設有訓練樣本X1,X2,X3…Xm,包含m個獨立樣本,希望從中找出p(x,z)的引數。

通過似然估計建立目標函式:
QQ截圖20160922161544.png-6.7kB
x是樣本點,z是隱藏引數(就是上文中的π),θ是顯引數(就是上文高斯分佈中的μ,σ)

z是隱藏隨機變數,不方便直接找到引數估計。所以使用策略:計算l(θ)的下界,求出該下界的最大值,重複該過程知道收斂,下圖中綠線所經過的點是下界與l(θ)相等的點,求下界的最大值,往往會更接近目標函式的最大值。

設Qi是z的某一個分佈,那麼目標函式可以轉換:
QQ截圖20160922161854.png-21.8kB
log函式是一個凹函式,要求的一個點與它相等,只能使得QQ截圖20160922162705.png-2.7kB是一個常數,也就是:
QQ截圖20160922162728.png-3.8kB

進一步分析:
QQ截圖20160922162817.png-17.2kB
到後來,居然發現Q(z)其實就是z關於樣本xi,引數θ的條件概率

由此,可以歸納出EM演算法的整體框架
QQ截圖20160922163005.png-21.7kB

3.3 理論公式推導GMM

還是3.1中的例子:
已知一個學校的所有學生的身高樣本(X1,X2,X3…Xn),並且男生和女生都分別服從N(μ男,σ男)和N(μ女,σ女)的高斯分佈。目的是要求出μ男,σ男,μ女,σ女這四個引數。

E-STEP:
QQ截圖20160922163212.png-7.8kB

M-STEP:
將多項式和高斯分佈等引數帶入
QQ截圖20160922163240.png-30.5kB

對均值求偏導,求均值:
QQ截圖20160922163357.png-44.1kB
上式等於0解均值為:
QQ截圖20160922163410.png-5.8kB

對方差求偏導,求方差:
QQ截圖20160922163533.png-9.6kB

對多項式中的引數:
考察m-step中的目標函式,對於φ,刪除常數項
QQ截圖20160922163753.png-11.2kB
得到:
QQ截圖20160922163759.png-3.5kB
由於多項式的概率和為1,建立拉格朗日方程:
QQ截圖20160922163846.png-8.4kB
求偏導
QQ截圖20160922163910.png-16.3kB

於是我們仍然可以得到引數的估計方程:
QQ截圖20160922164028.png-10.4kB

與3.1相比,得到了相同的結果!

5. Python實現

5.1 EM演算法估算GMM均值

這個例子是我網上找的,註釋不多,所以我將每句程式碼都寫了註釋以方便理解。

第一個方法ini_data是生成了一組由兩個高斯分佈產生的樣本資料,他們的方差相同,均值不同。
第二個方法e_step是EM演算法的第1步,先初始化引數,然後根據已知的引數去估計概率π
第三個方法M-step是EM演算法的第2步,根據e-step中計算出來的π去估計新的μ和σ
第四個方法run是去不斷迭代迴圈e-step和m-step直至收斂求得最終的三個引數的估計值

#!/usr/bin/python
# -*- coding:utf-8 -*-

import math
import copy
import numpy as np
import matplotlib.pyplot as plt

is_debug = False


# # 建立兩個正態分佈的資料,方差已知並同,均值不同,k是高斯分佈的個數,n是樣本數
def ini_data(sigma, mu1, mu2, k, n):
    global x
    global mu
    global expectations
    # 建立1*n維的0向量
    x = np.zeros((1, n))
    # 隨機生成兩個0-1間的隨機數
    mu = np.random.random(2)
    # 生成n*k維的0向量
    expectations = np.zeros((n, k))
    # 遍歷
    for i in xrange(0, n):
        # 如果生成一個隨機數大於0.5
        if np.random.random(1) > 0.5:
            # 那麼x中第0行第i列由第1個高斯分佈生成
            x[0,i] = np.random.normal()*sigma + mu1
        # 如果小於0.5
        else:
            # 則由第2個高斯分佈生成隨機數
            x[0,i] = np.random.normal()*sigma + mu2
    if is_debug:
        print "***"
        print u"初始觀測值資料x:"
        print x


# # 計算E[Zij]隱藏引數
def e_step(sigma, k, n):
    global expectations
    global mu
    global x
    # 對每個樣本做遍歷
    for i in xrange(0, n):
        # 初始化新的樣本數
        denom = 0
        # 對該樣本在每個分佈中做遍歷
        for j in xrange(0, k):
            # 累加計算所有樣本點在該分佈中的數
            denom += math.exp((-1/(2 * float(sigma ** 2))))*(float(x[0,i] - mu[j]) ** 2)
        for j in xrange(0, k):
            # 計算該樣本點在該分佈中的數
            numer = math.exp((-1/(2 * float(sigma ** 2))))*(float(x[0,i] - mu[j]) ** 2)
            # 計算該樣本點來自該分佈的概率r(i,k)
            expectations[i,j] = numer / denom
    if is_debug:
        print "***"
        print u"隱藏變數E(Z)"
        print expectations


# # M-step:求最大化E(Zij)時的引數mu
def m_step(k, N):
    global expectations
    global x
    # 遍歷每個分佈
    for j in xrange(0, k):
        # 初始化新的樣本點的值
        numer = 0
        # 初始化新的樣本數量
        denom = 0
        # 對每個新樣本做遍歷
        for i in xrange(0, N):
            # 累加所有樣本的值
            numer += expectations[i, j] * x[0, i]
            # 累加所有樣本的數量
            denom += expectations[i, j]
        # 計算新的均值
        mu[j] = numer / denom


# # 迭代收斂
def run(sigma, mu1, mu2, k, n, iter_num, epsilon):
    # 生成由兩個正態分佈組成的資料
    ini_data(sigma, mu1, mu2, k, n)
    print u"初始化均值:", mu
    # 根據預先設定的迭代次數迴圈
    for i in range(iter_num):
        # 將現有的均值賦值給老的均值變數
        old_mu = copy.deepcopy(mu)
        # 計算隱變數的期望
        e_step(sigma, k, n)
        # 求目標函式最大值時的均值
        m_step(k, n)
        print i, mu
        # 如果新舊均值的差值小於設定的閥值則終止迴圈
        if sum(abs(mu - old_mu)) < epsilon:
            break


if __name__ == '__main__':
    run(6, 40, 20, 2, 10000, 10, 0.01)
    plt.hist(x[0, :], 50)
    plt.show()

最後一個方法將這組資料在二維座標上畫出來了,影象如下,確實可以很清晰地看出有兩個正態分佈,他們的均值不同,但方差類似。
QQ截圖20160922180247.png-11.6kB

打印出迭代了10次的均值結果:
QQ截圖20160922180257.png-8.4kB
初始化的均值是隨機的,非常小,與我們實際的均值差別非常大,在經過第一次迭代後,就已經有了明顯的改善了,接下來每次迭代的結果都越來越接近真實的均值40,20了,可以看到如果迭代次數增加到100,或1000,EM估計出來的結果應該會非常接近40和20的。

# !/usr/bin/python
# -*- coding:utf-8 -*-

import numpy as np
from scipy.stats import multivariate_normal
import matplotlib as mpl
import matplotlib.pyplot as plt


mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False


# # 生成來自兩個高斯分佈的資料
if __name__ == '__main__':
    # 第一個分佈的均值
    mu1 = (0, 0, 0)
    # 建立一個維度為3的單位矩陣,作為方差
    cov1 = np.identity(3)
    # 根據均值與方差隨機生成多元的正太分佈的資料
    data1 = np.random.multivariate_normal(mu1, cov1, 100)
    # 第二個分佈的均值
    mu2 = (2, 2, 1)
    # 設定方差
    cov2 = np.identity(3)
    # 根據均值與方差隨機生成第二個多元的正太分佈的資料
    data2= np.random.multivariate_normal(mu2, cov2, 100)
    # 將兩組資料union
    data = np.vstack((data1, data2))

    # 設定迭代次數
    num_iter = 100
    # 樣本資料的大小,n是樣本數的個數,d是維度,這裡是3維
    n, d = data.shape
    # 初始化引數,隨機設定即可
    mu1 = np.random.standard_normal(d)  # 從三維的標準正態分佈中隨機取一個點作為初始均值
    mu2 = np.random.standard_normal(d)
    sigma1 = np.identity(d)  # 建立d維的單位矩陣作為初始化的方差
    sigma2 = np.identity(d)
    pi = 0.5 # 再拍腦門決定一下隱引數的值

    # # 進行EM演算法
    for i in range(num_iter):
        # E-step
        # 根據均值方差建立兩個正太分佈
        norm1 = multivariate_normal(mu1, cov1)
        norm2 = multivariate_normal(mu2, cov2)
        # 分佈計算每個樣本點由兩個分佈產生的概率
        tau1 = pi*norm1.pdf(data)
        tau2 = (1 - pi)*norm2.pdf(data)
        # 計算由第一個分佈產生的概率
        gamma = tau1/(tau1 + tau2)

        # M-step
        mu1 = np.dot(gamma, data)/sum(gamma)
        mu2 = np.dot((1-gamma), data)/sum(1-gamma)
        sigma1 = np.dot(gamma * (data - mu1).T, (data - mu1))/sum(gamma)
        sigma2 = np.dot((1-gamma) * (data - mu2).T, (data - mu2))/sum(1-gamma)
        pi = sum(gamma)/n
        # if i % 2 == 0:
        #     print i, ":\t",mu1, mu2

    print '類別概率:\t', pi
    print '均值:\t', mu1, mu2
    print '方差:\t', sigma1, sigma2

    g = GMM(n_components=2, covariance_type="full", n_iter=100)
    g.fit(data)
    print '類別概率:\t', g.weights_[0]
    print '均值:\n', g.means_, '\n'
    print '方差:\n', g.covars_, '\n'

    # 預測分類
    norm1 = multivariate_normal(mu1, sigma1)
    norm2 = multivariate_normal(mu2, sigma2)
    tau1 = norm1.pdf(data)
    tau2 = norm2.pdf(data)

    # figsize設定畫布的大小,facecolor設定畫布背景色為白色
    fig = plt.figure(figsize=(14, 7), facecolor='w')
    # 121表示畫1行2列中的第1個圖:原始資料的分佈圖
    ax = fig.add_subplot(121, projection='3d')
    # x,y,z座標是原始資料的第1,2,3列資料,c是顏色為藍色,s是三點的大小,marker是三點的形狀為圓,depthshade為顏色需不需要分深淺度
    ax.scatter(data[:, 0], data[:, 1], data[:, 2], c='b', s=30, marker='o', depthshade=True)
    # 設定座標的標記
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    # 設定影象的名稱與此題大小
    ax.set_title(u'origin data', fontsize=18)

    # 畫1行2列中的第2個圖:預測資料的分類圖
    ax = fig.add_subplot(122, projection='3d')
    # 提取出概率在分佈1中比分佈2中大的樣本點
    c1 = tau1 > tau2
    # 將這點畫在第2張圖中,顏色為紅色,形狀是小圓
    ax.scatter(data[c1, 0], data[c1, 1], data[c1, 2], c='r', s=30, marker='o', depthshade=True)
    # 同理,拿出在分佈2中概率大的樣本點
    c2 = tau1 < tau2
    # 用綠色的小三角表示
    ax.scatter(data[c2, 0], data[c2, 1], data[c2, 2], c='g', s=30, marker='^', depthshade=True)
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title(u'EM演算法分類', fontsize=18)

    # 自動調整影象大小,使兩個圖形比較緊湊
    plt.tight_layout()
    # 畫出來吧,少年
    plt.show()

QQ截圖20160923181241.png-138.6kB

QQ截圖20160923182837.png-24.5kB

QQ截圖20160923183024.png-23.7kB

# !/usr/bin/python
# -*- coding:utf-8 -*-

import numpy as np
from scipy.stats import multivariate_normal
from sklearn.cross_validation import train_test_split
from sklearn.mixture import GMM
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.colors


mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False


def expand(a, b):
    d = (b - a) * 0.05
    return a-d, b+d


if __name__ == '__main__':
    # 讀入資料(性別,身高,體重)
    data = np.loadtxt('D:\Documents\cc\python\data\HeightWeight.csv',
                      dtype = np.float, delimiter=',', skiprows=1)
    # 將資料分成兩部分y:性別標籤列,x:身高與體重2列,[1, ]表示第一部分取[0,1)列,第二部分取剩下的列
    y, x = np.split(data, [1, ], axis=1)
    # 將資料分成訓練集與測試集,random_state保證了隨機池相同
    x, x_test, y, y_test = train_test_split(x, y, train_size=0.6, random_state=0)
    # 用生成一個GMM,由兩個分佈組成,tol是閥值
    gmm = GMM(n_components=2, covariance_type='full', tol=0.0001, n_iter=100, random_state=0)
    # 分別取出身高與體重的最大值與最小值
    x_min = np.min(x, axis=0)
    x_max = np.max(x, axis=0)
    # 使身高與體重的訓練資料去擬合剛剛建立的gmm
    gmm.fit(x)
    # 列印均值,方差看一看
    print '均值 = \n', gmm.means_
    print '方差 = \n', gmm.covars_
    # 對訓練資料與測試資料都帶入gmm做預測
    y_hat = gmm.predict(x)
    y_test_hat = gmm.predict(x_test)
    # 以為gmm做預測是不會分兩個分佈的先後順序的,所以要保證是順序顛倒的時候女性仍然表示為0
    change = (gmm.means_[0][0] > gmm.means_[1][0])
    if change:
        z = y_hat == 0
        y_hat[z] = 1
        y_hat[~z] = 0
        z = y_test_hat == 0
        y_test_hat[z] = 0
        y_test_hat[~z] = 0
    # 求訓練與測試集的準確率
    acc = np.mean(y_hat.ravel() == y.ravel())
    acc_test = np.mean(y_test_hat.ravel() == y_test.ravel())
    acc_str = u'訓練集準確率:%.2f%%' % (acc * 100)
    acc_test_str = u'測試集準確率:%.2f%%' % (acc_test * 100)
    print acc_str
    print acc_test_str

    # 建立淺色的顏色(用來做背景)和深色的顏色(用來表示點)
    cm_light = mpl.colors.ListedColormap(['#FF8080', '#77E0A0'])
    cm_dark = mpl.colors.ListedColormap(['r', 'g'])
    # 分別取兩個特徵的最大值與最小值
    x1_min, x1_max = x[:, 0].min(), x[:, 0].max()
    x2_min, x2_max = x[:, 1].min(), x[:, 1].max()
    x1_min, x1_max = expand(x1_min, x1_max)
    x2_min, x2_max = expand(x2_min, x2_max)
    # 在畫布上畫上橫縱座標網格各500
    x1, x2 = np.mgrid[x1_min:x1_max:500j, x2_min:x2_max:500j]
    # 將兩個特徵的數鋪平成一列
    grid_test = np.stack((x1.flat, x2.flat), axis=1)
    # 用gmm做預測y值
    grid_hat = gmm.predict(grid_test)
    # 將預測出來的y值調整大小成與x1一致
    grid_hat = grid_hat.reshape(x1.shape)
    if change:
        z = grid_hat == 0
        grid_hat[z] = 1
        grid_hat[~z] = 0
    # 畫一個9*7的畫布,背景色為白色
    plt.figure(figsize=(9, 7), facecolor='w')
    # 用之前設定的淺顏色來做兩個分佈的區分割槽域
    plt.pcolormesh(x1, x2, grid_hat,cmap=cm_light)
    # 畫訓練樣本點,用圓表示,並使用之前設定的深色表示
    plt.scatter(x[:, 0], x[:, 1], s=50, c=y, marker='o', cmap=cm_dark, edgecolors='k')
    # 畫訓練樣本點,用三角形表示,並使用之前設定的深色表示
    plt.scatter(x_test[:, 0], x_test[:, 1], s=60, c=y_test, marker='^', cmap=cm_dark, edgecolors='k')
    # 奔跑吧,少年
    plt.show()

QQ截圖20160923194057.png-30.8kB

QQ截圖20160923203144.png-7.6kB

# !/usr/bin/python
# -*- coding:utf-8 -*-

import numpy as np
from sklearn.mixture import GMM, DPGMM
from sklearn.cross_validation import train_test_split
import matplotlib as mpl
import matplotlib.colors
import matplotlib.pyplot as plt

mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False


def expand(a, b, rate = 0.05):
    d = (b - a) * rate
    return a-d, b+d


def accuracy_rate(y1, y2):
    acc1 = np.mean(y1 == y2)
    acc = acc1 if acc1 > 0.5 else 1-acc1
    return acc


if __name__ == '__main__':
    # 設定隨機池
    np.random.seed(0)
    # 設定協方差
    cov1 = np.diag((1, 2))
    # 設定生成隨機數的個數
    N1 = 500
    N2 = 300
    N = N1 + N2
    # 生成正態分佈的隨機數
    x1 = np.random.multivariate_normal(mean=(3, 2), cov=cov1, size=N1)
    # 建立一個數組
    m = np.array(((1, 1), (1, 3)))
    # 將x1與新陣列做點乘,目的是做一個轉換
    x1 = x1.dot(m)
    x2 = np.random.multivariate_normal(mean=(-1, 10), cov=cov1, size=N2)
    # union兩組隨機數
    x = np.vstack((x1, x2))
    # 設定它們的y值
    y = np.array([0]*N1 + [1]*N2)

    # 型別
    types = ('spherical', 'diag', 'tied', 'full')
    err = np.empty(4)
    bic = np.empty(4)
    # 對型別進行列舉,i是索引
    for i, type in enumerate(types):
        gmm = GMM(n_components=2, covariance_type=type, random_state=0)
        gmm.fit(x)
        err[i] = 1 - accuracy_rate(gmm.predict(x), y)
        bic[i] = gmm.bic(x)
    print '錯誤率:', err.ravel()
    print 'BIC:', bic.ravel()

錯誤率: [ 0.44625  0.3625   0.30625  0.     ]
BIC: [ 8291.31989604  8189.04587246  8290.20374814  6850.90774844]
均值 = 
[[ -0.98545235  10.07568825]
 [  4.88245339   8.69755024]]
方差 = 
[[[  0.89173153  -0.02570634]
  [ -0.02570634   1.95207306]]

 [[  2.86753624   6.6289323 ]
  [  6.6289323   17.97477745]]]