上一次我們談到了用 k-means 進行聚類的方法,這次我們來說一下另一個很流行的算法:Gaussian Mixture Model (GMM)。事實上,GMM 和 k-means 很像,不過 GMM 是學習出一些概率密度函數來(所以 GMM 除了用在 clustering 上之外,還經常被用於 density estimation ),簡單地說,k-means 的結果是每個數據點被 assign 到其中某一個 cluster 了,而 GMM 則給出這些數據點被 assign 到每個 cluster 的概率,又稱作 soft assignment 。
得出一個概率有很多好處,因爲它的信息量比簡單的一個結果要多,比如,我可以把這個概率轉換爲一個 score ,表示算法對自己得出的這個結果的把握。也許我可以對同一個任務,用多個方法得到結果,最後選取“把握”最大的那個結果;另一個很常見的方法是在諸如疾病診斷之類的場所,機器對於那些很容易分辨的情況(患病或者不患病的概率很高)可以自動區分,而對於那種很難分辨的情況,比如,49% 的概率患病,51% 的概率正常,如果僅僅簡單地使用 50% 的閾值將患者診斷爲“正常”的話,風險是非常大的,因此,在機器對自己的結果把握很小的情況下,會“拒絕發表評論”,而把這個任務留給有經驗的醫生去解決。
廢話說了一堆,不過,在回到 GMM 之前,我們再稍微扯幾句。我們知道,不管是機器還是人,學習的過程都可以看作是一種“歸納”的過程,在歸納的時候你需要有一些假設的前提條件,例如,當你被告知水裏遊的那個傢伙是魚之後,你使用“在同樣的地方生活的是同一種東西”這類似的假設,歸納出“在水裏遊的都是魚”這樣一個結論。當然這個過程是完全“本能”的,如果不仔細去想,你也不會了解自己是怎樣“認識魚”的。另一個值得注意的地方是這樣的假設並不總是完全正確的,甚至可以說總是會有這樣那樣的缺陷的,因此你有可能會把蝦、龜、甚至是潛水員當做魚。也許你覺得可以通過修改前提假設來解決這個問題,例如,基於“生活在同樣的地方並且穿着同樣衣服的是同一種東西”這個假設,你得出結論:在水裏有並且身上長有鱗片的是魚。可是這樣還是有問題,因爲有些沒有長鱗片的魚現在又被你排除在外了。
在這個問題上,機器學習面臨着和人一樣的問題,在機器學習中,一個學習算法也會有一個前提假設,這裏被稱作“歸納偏執 (bias)”(bias 這個英文詞在機器學習和統計裏還有其他許多的意思)。例如線性迴歸,目的是要找一個函數儘可能好地擬合給定的數據點,它的歸納偏執就是“滿足要求的函數必須是線性函數”。一個沒有歸納偏執的學習算法從某種意義上來說毫無用處,就像一個完全沒有歸納能力的人一樣,在第一次看到魚的時候有人告訴他那是魚,下次看到另一條魚了,他並不知道那也是魚,因爲兩條魚總有一些地方不一樣的,或者就算是同一條魚,在河裏不同的地方看到,或者只是看到的時間不一樣,也會被他認爲是不同的,因爲他無法歸納,無法提取主要矛盾、忽略次要因素,只好要求所有的條件都完全一樣──然而哲學家已經告訴過我們了:世界上不會有任何樣東西是完全一樣的,所以這個人即使是有無比強悍的記憶力,也絕學不到任何一點知識。
這個問題在機器學習中稱作“過擬合 (Overfitting)”,例如前面的迴歸的問題,如果去掉“線性函數”這個歸納偏執,因爲對於 N 個點,我們總是可以構造一個 N-1 次多項式函數,讓它完美地穿過所有的這 N 個點,或者如果我用任何大於 N-1 次的多項式函數的話,我甚至可以構造出無窮多個滿足條件的函數出來。如果假定特定領域裏的問題所給定的數據個數總是有個上限的話,我可以取一個足夠大的 N ,從而得到一個(或者無窮多個)“超級函數”,能夠 fit 這個領域內所有的問題。然而這個(或者這無窮多個)“超級函數”有用嗎?只要我們注意到學習的目的(通常)不是解釋現有的事物,而是從中歸納出知識,並能應用到新的事物上,結果就顯而易見了。
沒有歸納偏執或者歸納偏執太寬泛會導致 Overfitting ,然而另一個極端──限制過大的歸納偏執也是有問題的:如果數據本身並不是線性的,強行用線性函數去做迴歸通常並不能得到好結果。難點正在於在這之間尋找一個平衡點。不過人在這裏相對於(現在的)機器來說有一個很大的優勢:人通常不會孤立地用某一個獨立的系統和模型去處理問題,一個人每天都會從各個來源獲取大量的信息,並且通過各種手段進行整合處理,歸納所得的所有知識最終得以統一地存儲起來,並能有機地組合起來去解決特定的問題。這裏的“有機”這個詞很有意思,搞理論的人總能提出各種各樣的模型,並且這些模型都有嚴格的理論基礎保證能達到期望的目的,然而絕大多數模型都會有那麼一些“參數”(例如 K-means 中的 k ),通常沒有理論來說明參數取哪個值更好,而模型實際的效果卻通常和參數是否取到最優值有很大的關係,我覺得,在這裏“有機”不妨看作是所有模型的參數已經自動地取到了最優值。另外,雖然進展不大,但是人們也一直都期望在計算機領域也建立起一個統一的知識系統(例如語意網就是這樣一個嘗試)。
廢話終於說完了,回到 GMM 。按照我們前面的討論,作爲一個流行的算法,GMM 肯定有它自己的一個相當體面的歸納偏執了。其實它的假設非常簡單,顧名思義,Gaussian Mixture Model ,就是假設數據服從 Mixture Gaussian Distribution ,換句話說,數據可以看作是從數個 Gaussian Distribution 中生成出來的。實際上,我們在 K-means 和 K-medoids 兩篇文章中用到的那個例子就是由三個 Gaussian 分佈從隨機選取出來的。實際上,從中心極限定理可以看出,Gaussian 分佈(也叫做正態 (Normal) 分佈)這個假設其實是比較合理的,除此之外,Gaussian 分佈在計算上也有一些很好的性質,所以,雖然我們可以用不同的分佈來隨意地構造 XX Mixture Model ,但是還是 GMM 最爲流行。另外,Mixture Model 本身其實也是可以變得任意複雜的,通過增加 Model 的個數,我們可以任意地逼近任何連續的概率密分佈。
每個 GMM 由 個 Gaussian 分佈組成,每個 Gaussian 稱爲一個“Component”,這些 Component 線性加成在一起就組成了 GMM 的概率密度函數:
由於在對數函數裏面又有加和,我們沒法直接用求導解方程的辦法直接求得最大值。爲了解決這個問題,我們採取之前從 GMM 中隨機選點的辦法:分成兩步,實際上也就類似於 K-means 的兩步。
- 估計數據由每個 Component 生成的概率(並不是每個 Component 被選中的概率):對於每個數據 來說,它由第 個
Component 生成的概率爲
其中 ,並且 也順理成章地可以估計爲 。
- 重複迭代前面兩步,直到似然函數的值收斂爲止。
當然,上面給出的只是比較“直觀”的解釋,想看嚴格的推到過程的話,可以參考 Pattern Recognition and Machine Learning 這本書的第九章。有了實際的步驟,再實現起來就很簡單了。Matlab 代碼如下:
(Update 2012.07.03:如果你直接把下面的代碼拿去運行了,碰到 covariance 矩陣 singular 的情況,可以參見這篇文章。)
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
函數返回的 Px
是一個 的矩陣,對於每一個 ,我們只要取該矩陣第 行中最大的那個概率值所對應的那個
Component 爲 所屬的
cluster 就可以實現一個完整的聚類方法了。
轉載自:http://blog.pluskid.org/?p=39