python EM算法2

1硬幣問題

先看一個拋硬幣問題,如果我們有A和B兩個不均勻硬幣,選擇任意一個硬幣拋10次(這裏我們知道選擇是的哪一個硬幣),共計選擇5次。正面記爲H,背面記爲T。記錄實驗結果,求A和B再拋正面向上的概率?

使用極大似然估計(Maximum likelihood)來算:

  • 統計出每次實驗,正反面的次數
  • 多次實驗結果相加
  • 相除得到結果,P(A)=0.8,P(B)=0.45

但是在實際過程中,很有可能我們只知道有兩個硬幣,不知道每次選擇的哪一個硬幣,問是否能求出每個硬幣拋出正面的概率?

是不是第一感覺這個問題很無解,我都不知道選擇是哪個硬幣,我怎麼求解啊?
事實上是可以求出的。這裏使用的時EM算法。

使用期望最大值(Expectation maximization,EM)算法來算:

  • 假設$\hat{\theta}_A^{(0)}=0.6,\hat{\theta}_B^{(0)}=0.5$
  • 統計每次的實驗結果,記錄正反面
  • 通過貝葉斯公式,估計每次實驗選擇的A硬幣或是B硬幣的概率
  • 依據計算出的選擇硬幣概率得到該概率下的正反面結果
  • 5次平均得到$\hat{\theta}_A^{(1)}\approx 0.71,\hat{\theta}_B^{(1)}\approx 0.58$
  • 重複上面的過程,例如迭代10次後,得到$\hat{\theta}_A^{(2)}\approx 0.8,\hat{\theta}_B^{(2)}\approx 0.52$
  • $θ^A(10),θ^B(10)\hat{\theta}_A^{(10)},\hat{\theta}_B^{(10)}`就是使用EM算法計算出的概率值

這裏比較難理解的是:如何利用貝葉斯公式計算每次實驗選擇的A硬幣或是B硬幣的概率?


那麼先看下面這個例子:

假如現在有射擊運動員甲和乙,甲乙射擊靶心的概率爲$\hat{\theta}_{\text{甲}}=0.9$,$θ^=0.2\hat{\theta}_{\text{乙}}=0.2`,如果現在有一組實驗結果爲

中,不中,中,中,中
問這次是誰射擊的?

**直觀上的來看,非常大的概率是甲射擊的,但是也有可能是乙走狗屎運了。那麼該如何從概率的角度計算是誰射擊的? **

首先我們知道選擇甲和乙的概率爲p(甲)=p(乙)=0.5(先驗概率),本次實驗記爲E。通過貝葉斯公式

 p(擊中)=p(擊中)p()p(擊中)\quad\ p(\text{甲}|\text{擊中})=\frac{p(\text{擊中}|\text{甲})p(\text{甲})}{p(\text{擊中})}

=p(擊中)p()p(擊中)p()+p(擊中)p()\qquad\qquad\qquad=\frac{p(\text{擊中}|\text{甲})p(\text{甲})}{p(\text{擊中}|\text{甲})p(\text{甲})+p(\text{擊中}|\text{乙})p(\text{乙})}

$0.98\qquad\qquad\qquad\approx 0.98`

故本次實驗有98%可能是A射擊的,2%的可能是B射擊的。


有了上面的案例,我們再回到拋硬幣的問題上,由貝葉斯公式:

p(A\vert E)=\frac{p(E|A)P(A)}{p(E)}

A 爲選用硬幣A,E爲本次實驗。而選擇兩個硬幣的概率是相同的:$p(A)=p(B)=0.5$,且$p(E|A)=0.6^5\times 0.4^5\approx 0.0008,P(E|B)=0.5^5\times 0.5^5\approx 0.001$

\begin{array}{lll}
p(A|E)&=&\frac{p(E|A)p(A)}{p(E)}\\
&=&\frac{p(E|A)p(A)}{p(E|A)p(A)+p(E|B)p(B)}\\
&\approx & 0.45
\end{array}

$0.45\times 10\times 0.5\approx 2.2$,故第一次實驗結果平均下來,有2.22.2個A硬幣正面的可能。

最後將5次結果平均得到新的A,B拋硬幣正面估計值$\hat{\theta}_A^{(1)}\approx 0.71,\hat{\theta}_B^{(1)}\approx 0.58$,,這是我們第一次迭代的值(這就是一次學習過程),照着這個流程迭代多次,得到最後的估測值。

上面我們計算出每次實驗中是拋A或拋B的概率值就是隱變量.這個過程就是EM算法的簡單案例。

2形式化EM算法

2.1 算法理論

`p(x)=j=1kϕjN(xμj,Σj)p(x)=\sum\limits_{j=1}^k \phi_j N(x|\mu_j,\Sigma_j)$

`N(xμj,Σj)=1(2π)(n/2)Σj(1/2)exp(12(xμj)TΣj1(xμj))N(x|\mu_j,\Sigma_j)=\frac{1}{(2\pi)^(n/2)|\Sigma_j|^(1/2)}\exp(-\frac{1}{2}(x-\mu_j)^T\Sigma_j^{-1}(x-\mu_j))$

$j=1kϕj=1\sum\limits_{j=1}^k \phi_j=1`

這是一個非凸問題,只能求局部極值,一般採用EM算法。

步驟如下:

(1).隨機化`{(ϕj,μj,Σj)1jk}\{(\phi_j,\mu_j,\Sigma_j)\vert 1\leq j \leq k\}$

(2).E步:計算

 ωj(i)=Qi(z(i)=j)=P(z(i)=jx(i);ϕ,μ,Σ)\qquad\qquad\quad\, \omega_j^{(i)}=Q_i(z^{(i)}=j)=P(z^{(i)}=j\vert x^{(i)};\phi,\mu,\Sigma)

=ϕjN(x(i)μj,Σj)j=1kN(x(i)μj,Σj)\qquad\qquad\qquad\quad=\frac{\phi_j N(x^{(i)}|\mu_j,\Sigma_j)}{\sum\limits_{j=1}^kN(x^{(i)}|\mu_j,\Sigma_j)}

上式表示:第i個樣本落在第j個高斯的概率.

(3). M步:

μj:=i=1mω(i)x(i)i=1mω(i)\qquad\mu_{j}:=\frac{\sum_{i=1}^m\omega_{\ell}^{(i)}x^{(i)}}{\sum_{i=1}^m\omega_{\ell}^{(i)}}

Σj=i=1mωj(i)(x(i)μj)(x(i)μj)Ti=1mωj(i)\qquad\Sigma_j=\frac{\sum\limits_{i=1}^m\omega_j^{(i)}(x^{(i)}-\mu_j)(x^{(i)}-\mu_j)^T}{\sum\limits_{i=1}^m\omega_j^{(i)}}

ϕj:=1mi=1mωj(i)\qquad\phi_j:=\frac{1}{m}\sum\limits_{i=1}^m \omega_j^{(i)}

(4).回到(2)直至收斂.

2.2 python實現
以西瓜數據集4.0爲例:

編號 密度 含糖率
1 0.697 0.46
2 0.774 0.376
3 0.634 0.264
4 0.608 0.318
5 0.556 0.215
6 0.403 0.237
7 0.481 0.149
8 0.437 0.211
9 0.666 0.091
10 0.243 0.267
11 0.245 0.057
12 0.343 0.099
13 0.639 0.161
14 0.657 0.198
15 0.36 0.37
16 0.593 0.042
17 0.719 0.103
18 0.359 0.188
19 0.339 0.241
20 0.282 0.257
21 0.748 0.232
22 0.714 0.346
23 0.483 0.312
24 0.478 0.437
25 0.525 0.369
26 0.751 0.489
27 0.532 0.472
28 0.473 0.376
29 0.725 0.445
30 0.446 0.459
import numpy as np
# 預處理數據
def loadData(filename):
     dataSet = []
     fr = open(filename)
     for line in fr.readlines():
         curLine = line.strip().split(' ')
         fltLine = list(map(float, curLine))
         dataSet.append(fltLine)
     return dataSet
# 計算高斯函數
def Gaussian(data,mean,cov):
    dim = np.shape(cov)[0]   # 計算維度
    covdet = np.linalg.det(cov) # 計算|cov|
    covinv = np.linalg.inv(cov) # 計算cov的逆
    if covdet==0:              # 以防行列式爲0
        covdet = np.linalg.det(cov+np.eye(dim)*0.01)
        covinv = np.linalg.inv(cov+np.eye(dim)*0.01)
    m = data - mean
    z = -0.5 * np.dot(np.dot(m, covinv),m)    # 計算exp()裏的值
    return 1.0/(np.power(np.power(2*np.pi,dim)*abs(covdet),0.5))*np.exp(z)  # 返回概率密度值
# 獲取最初的聚類中心
def GetInitialMeans(data,K,criterion):
    dim = data.shape[1]  # 數據的維度
    means = [[] for k in range(K)] # 存儲均值
    minmax=[]
    for i in range(dim):
        minmax.append(np.array([min(data[:,i]),max(data[:,i])]))  # 存儲每一維的最大最小值
    minmax=np.array(minmax)
    while True:
        for i in range(K):
            means[i]=[]
            for j in range(dim):
                 means[i].append(np.random.random()*(minmax[j][1]-minmax[j][0])+minmax[j][0] ) #隨機產生means
            means[i]=np.array(means[i])

        if isdistance(means,criterion):
            break
    return means
# 用於判斷初始聚類簇中的means是否距離離得比較近
def isdistance(means,criterion=0.03):
     K=len(means)
     for i in range(K):
         for j in range(i+1,K):
             if criterion>np.linalg.norm(means[i]-means[j]):
                 return False
     return True

dataSet=loadData('d:/watermelon4.txt')
means=GetInitialMeans(np.array(dataSet),3,0.03)
# K均值算法,估計大約幾個樣本屬於一個GMM
def Kmeans(data,K):
    N = data.shape[0]  # 樣本數量
    dim = data.shape[1]  # 樣本維度
    means = GetInitialMeans(data,K,15)
    means_old = [np.zeros(dim) for k in range(K)]
    # 收斂條件
    while np.sum([np.linalg.norm(means_old[k] - means[k]) for k in range(K)]) > 0.01:
        means_old = cp.deepcopy(means)
        numlog = [0] * K  # 存儲屬於某類的個數
        sumlog = [np.zeros(dim) for k in range(K)]
        # E步
        for i in range(N):
            dislog = [np.linalg.norm(data[i]-means[k]) for k in range(K)]
            tok = dislog.index(np.min(dislog))
            numlog[tok]+=1         # 屬於該類的樣本數量加1
            sumlog[tok]+=data[i]   # 存儲屬於該類的樣本取值

        # M步
        for k in range(K):
            means[k]=1.0 / numlog[k] * sumlog[k]
    return means

def GMM(data,K):
    N = data.shape[0]
    dim = data.shape[1]
    means= Kmeans(data,K)
    convs=[0]*K
    # 初始方差等於整體data的方差
    for i in range(K):
        convs[i]=np.cov(data.T)
    pis = [1.0/K] * K
    gammas = [np.zeros(K) for i in range(N)]
    loglikelyhood = 0
    oldloglikelyhood = 1

    while np.abs(loglikelyhood - oldloglikelyhood) > 0.0001:
        oldloglikelyhood = loglikelyhood

        # E步
        for i in range(N):
            res = [pis[k] * Gaussian(data[i],means[k],convs[k]) for k in range(K)]
            sumres = np.sum(res)
            for k in range(K):           # gamma表示第n個樣本屬於第k個混合高斯的概率
                gammas[i][k] = res[k] / sumres
        # M步
        for k in range(K):
            Nk = np.sum([gammas[n][k] for n in range(N)])  # N[k] 表示N個樣本中有多少屬於第k個高斯
            pis[k] = 1.0 * Nk/N
            means[k] = (1.0/Nk)*np.sum([gammas[n][k] * data[n] for n in range(N)],axis=0)
            xdiffs = data - means[k]
            convs[k] = (1.0/ Nk)*np.sum([gammas[n][k]* xdiffs[n].reshape(dim,1) * xdiffs[n] for  n in range(N)],axis=0)
        # 計算最大似然函數
        loglikelyhood = np.sum(
            [np.log(np.sum([pis[k] * Gaussian(data[n], means[k], convs[k]) for k in range(K)])) for n in range(N)])
        print(means)
        print(loglikelyhood)
GMM(np.array(dataSet),3)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章