1. 程式人生 > >斯坦福大學機器學習筆記——聚類(k-均值聚類演算法、損失函式、初始化、聚類數目的選擇)

斯坦福大學機器學習筆記——聚類(k-均值聚類演算法、損失函式、初始化、聚類數目的選擇)

上面的部落格的演算法都是有監督學習的演算法,即對於每個資料我們都有該資料對應的標籤,資料集的形式如下:
這裡寫圖片描述
而今天我們學習的演算法是一種無監督學習的演算法——聚類,該演算法中的每個資料沒有標籤,資料集的形式如下:
這裡寫圖片描述

K-均值聚類
k-均值聚類是一種最常見的聚類演算法,該演算法對沒有標籤的資料集進行訓練,然後將資料集聚類成不同的類別。
k-均值聚類是一種迭代演算法,該方法的過程如下:
1. 選擇K個隨機點,作為聚類中心;
2. 對於資料集中的每個資料計算與K個聚類中心的距離,將其與距離最近的聚類中心關聯起來,將屬於同一個聚類中心的樣本聚成一類(也叫作簇);
3. 計算每一個簇所有樣本的均值,將聚類中心移動到平均值的位置。
重複上述步驟2-3,直到聚類中心不再發生變化為止。
該演算法的虛擬碼如下:
這裡寫圖片描述


其中,c(i)代表x(i)被分配的聚類的索引值,c(i){1,2,...,K}μk是類別為k的聚類中心。
該演算法主要有兩個關鍵步驟:第一步為簇分類,即對於每一個樣本i,計算其應該屬於的類別,計算的公式為:

mink||xiμk||2

第二步為更新聚類中心(移動聚類中心),即:對於每一個類k,重新計算該類的質心。

在進行k均值的時候偶爾會出現一種問題,那就是如果沒有點分配給它的聚類中心怎麼辦?也就是說,你初始化的聚類中心,在進行簇分類之後,可能會出現該簇一個樣本也沒有。在這種情況下,我們一般移除那個聚類中心,這樣就會得到K-1個簇,如果必須分成K個簇,則要重新隨機找一個聚類中心,但是一般情況下我們都是將該聚類中心直接去掉(這種問題出現的情況很少)。

k-均值聚類的目標函式:
瞭解k-均值聚類代價函式有兩個目的:
1. 能夠幫助我們除錯學習演算法,確保k均值聚類演算法是在正確執行中;
2. 能夠運用代價函式幫助k-均值找到更好的簇,並且避免區域性最優解(在下文中會講述到)。
k-均值最小化問題是最小化每個資料點與其所關聯的聚類中心點之間的距離之和,所以k-均值的代價函式(也稱為畸變函式)為:
這裡寫圖片描述
其中,c(i)代表x(i)被分配的聚類的索引值,c(i){1,2,...,K}μk是類別為k的聚類中心。μc(i)代表x(i)被分配聚類索引值所對應的聚類中心。比如x(i)被聚類到第5類聚類,則c(i)=5,於是μc(i)=μ5
我們從上述的k-均值的虛擬碼中來簡單的分析一下每個步驟怎樣來最小化代價函式。第一個迴圈是用於對每個樣本進行簇分類,,它是用於減少c

(i)引起的代價,也就是說:

minJ(c(1),c(2),...,c(m))

上述過程μ1,μ2,...,μK保持不變。
第二個迴圈是用於移動聚類中心的位置,它是用來減小μi引起的代價,也就是說:

minJ(μ1,μ2,...,μK)

上述過程c(1),c(2),...,c(m)保持不變。
在迭代的過程中,每一次的代價函式應該都在減小或者保持不變,如果出現代價函式增大的情況,則說明實現的過程可能存在錯誤。

隨機初始化:
在進行k-均值的過程中,首先需要對聚類中心進行初始化,初始點選擇的不同,可能會得到不同的聚類結果,下面簡單介紹一下怎麼進行初始化:
1. 應該選擇K<m,即聚類中心點的個數要小於所有訓練集例項的個數。
2. 隨機選擇K個訓練例項,然後令K個聚類中心分別與這K個訓練例項相等。
K-均值聚類會存在一個問題,也就是最終聚類的結果會停留在一個區域性最小值處,這取決於初始化的情況。
為了解決這個問題,我們通常採用多次執行k-均值聚類演算法,每一次都重新進行隨機初始化,最後比較多次執行k-均值的結果,選擇代價函式最小的結果。但是上述這種方法,對於聚類中心數目K較小時(2-10)可行,但是當K較大時,這麼做也可能不會有明顯的改善。
上述解決方案的虛擬碼如下:
這裡寫圖片描述

聚類數如何選擇:
沒有最好的選擇聚類數的方法,人們一般都是根據不同的問題進行手動選擇。在進行選擇的時候我們可以從k-均值聚類演算法的動機是什麼出發,選擇出最好的適應於該動機的聚類數。
例如,我們蒐集了一些人的身高和體重,想借助身高和體重兩個特徵進行衣服尺碼的劃分,例如廠家想生產三種類型的尺碼(S、M、L),以此可以獲得更好的收益,這是我們的聚類數目會選擇K=3;例如廠家想生產五種型別的尺碼(XS、S、M、L、XL),以此可以獲得更好的收益,這是我們的聚類數目會選擇K=5;所以這時候的聚類數目的選擇是根據製造的衣服是否能較好的適應我們的客戶。
那麼有沒有一種可以選擇聚類數目的方法呢?有一種方法叫做“肘部法則”,也就是說我們分別計算在各種K值情況下,聚類演算法最終的損失函式,繪製出隨著K值變化損失函式變化的曲線:
這裡寫圖片描述
例如上圖中的左圖,我們觀察可以發現,在K=3時,損失函式急劇下降,然後在K=3之後損失函式逐漸趨於平穩,出出現曲線類似於肘的形狀,這時候我們就會選擇K=3作為我們聚類的數目,但是“肘部法則”並不是適應於所有的情況,很多情況下隨著K值的變化損失函式曲線變化的趨勢如上圖中的有圖,沒有明顯的肘部,這時候肘部法則則不再適用。

上述演算法實現的matlab程式碼如下:

主函式:

clear;
close all;

%% 資料歸一化處理以及資料的顯示
load('ex7data2.mat'); 

% 實現特徵歸一化
max_X = max(X); % 每個特徵的最大值
mean_X = mean(X);   % 每個特徵的均值
std_X = std(X); % 每個特徵的標準差
feature = feature_normalized(X, max_X, mean_X, std_X);

figure;
plot(feature(:,1),feature(:,2),'bo','MarkerSize', 3);
hold on;

%% 資料的初始化
feature = X;
[m, n] = size(feature);
K = 3; % 聚類數目
% 初始化聚類中心
r = 1 + (m-1).*rand([K 1]); % 隨機生成一個範圍在1~m的K*1的矩陣
r = floor(r);   % 對產生的隨機數求整
centroid = feature(r,:);    % 從原來的資料中獲得初始的聚類中心
max_iteration = 5; % 最大迭代次數
c = zeros(m,1); % 初始化類別索引
data = zeros(m,n+1);
J = zeros(max_iteration,1); % 儲存損失函式

%% K-means聚類實現的過程
for i=1:max_iteration
    for a=1:m
        c(a) = index_distance(feature(a,:), centroid, K);   % 計算每個樣本與聚類中心的距離,根據距離將樣本劃分為某一個聚類中心的類別索引
        data(a,:) = [feature(a,:) c(a)];
    end
    first_class = find(c==1);   % 找到聚類類別為1在資料中的索引值
    second_class = find(c==2);  % 找到聚類類別為2在資料中的索引值
    third_class = find(c==3);   % 找到聚類類別為3在資料中的索引值

    %用不同的顏色將不同的類別顯示
    figure;
    plot(centroid(:,1),centroid(:,2),'ro','MarkerSize', 10); hold on;
    plot(feature(first_class,1),feature(first_class,2),'ro','MarkerSize', 3); hold on;
    plot(feature(second_class,1),feature(second_class,2),'bo','MarkerSize', 3); hold on;
    plot(feature(third_class,1),feature(third_class,2),'go','MarkerSize', 3); hold off;

    % 計算損失函式
    sum = 0;
    for z=1:m
        sum = sum + norm(data(z,1:n)-centroid(data(z,3),:));
    end
    J(i) = sum;

    % 更新聚類中心
    for b=1:K
        centroid(b,:)=mean(feature(find(c==b),:));
    end

end

%% 繪製損失函式曲線
figure;
x = 1:max_iteration;
plot(x,J,'-');

特徵歸一化函式

function [normalized_feature, max_feature, mean_feature, std_feature] = feature_normalized(original_feature, max_feature, mean_feature, std_feature)

[num_sample,num_feature] = size(original_feature);
normalized_feature = zeros(num_sample, num_feature);

for i=1:num_feature
%     % 方式1
%     normalized_feature(:,i) = original_feature(:,i)/max_feature(i);
%     % 方式2
%     normalized_feature(:,i) = (original_feature(:,i)-mean_feature(i))/max_feature(i);
    % 方式3
    normalized_feature(:,i) = (original_feature(:,i)-mean_feature(i))/std_feature(i);
end

根據距離獲得索引的子函式:

function [c] = index_distance(feature, centroid, K)

distance = zeros(K,1);
for i=1:K
    distance(i) = norm(feature-centroid(i,:));
end

c = find(distance==min(distance));
end

程式執行的結果:
資料集的分佈:
這裡寫圖片描述
第一次迭代的結果:
這裡寫圖片描述
第二次迭代的結果:
這裡寫圖片描述
第三次迭代的結果:
這裡寫圖片描述
第四次迭代的結果:
這裡寫圖片描述
第五次迭代的結果:
這裡寫圖片描述
損失函式曲線:
這裡寫圖片描述
由於初始化是隨機的,所以產生的結果可能不會相同,對於上述的初始化,我們可以從損失函式的曲線中可以看出,迭代兩次之後就已經收斂。