1. 程式人生 > >MADlib——基於SQL的資料探勘解決方案(26)——聚類之k-means方法

MADlib——基於SQL的資料探勘解決方案(26)——聚類之k-means方法

        “物以類聚,人以群分”,其核心思想就是聚類。所謂聚類,就是將相似的事物聚集在一起,而將不相似的事物劃分到不同的類別的過程,是資料分析中十分重要的一種手段。比如古典生物學中,人們通過物種的形貌特徵將其分門別類,可以說就是一種樸素的人工聚類。如此,我們就可以將世界上紛繁複雜的資訊,簡化為少數方便人們理解的類別,因此聚類可以說是人類認知這個世界的最基本方式之一。通過聚類,人們能意識到密集和稀疏的區域,發現全域性的分佈模式,以及資料屬性之間有趣的相互關係。

        聚類起源於分類學,在古老的分類學中,人們主要依靠經驗和專業知識來實現分類,很少利用數學工具定量分類。隨著科學技術的發展,對分類的要求越來越高,以至有時僅憑經驗難以確切地分類,於是人們逐漸把數學工具引用到了分類學中,形成了數值分析學,之後又將多元分析技術引入進數值分類學,從而形成了聚類。在實踐中,聚類往往為分類服務,即先通過聚類來判斷事物的合適類別,然後在利用分類技術對新的樣本進行分類。

        聚類演算法大都是幾種最基本的方法,如k-means、層次聚類、SOM等,以及它們的許多改進變種。MADlib提供了一種k-means演算法的實現。本篇主要介紹MADlib的k-means演算法相關函式和應用案例。

一、聚類方法簡介

1.  聚類的概念

        將物理或抽象物件的集合分成由類似的物件組成的多個類或簇(Cluster)的過程被稱為聚類(Clustering)。由聚類所生成的簇是一組資料物件的集合,這些物件與同一個簇中的物件相似度較高,與其它簇中的物件相似度較低。相似度是根據描述物件的屬性值來度量的,距離是經常採用的度量方式。分析事物聚類的過程稱為聚類分析或群分析,是研究樣品或指標分類問題的一種統計分析方法。

        在資料分析的術語中,聚類和分類是兩種技術。分類是指已經知道了事物的類別,需要從樣品中學習分類規則,對新的、無標記的物件賦予類別,是一種有監督學習。而聚類則沒有事先預定的類別,而是依據人為給定的規則進行訓練,類別在聚類過程中自動生成,從而得到分類,是一種無監督學習。作為一個數據挖掘的功能,聚類可當做獨立的工具來獲得資料分佈情況,觀察每個簇的特點,集中對特定簇做進一步的分析。此外,聚類分析還可以作為其它演算法的預處理步驟,簡少計算量,提高分析效率。

2.  類的度量方法

        雖然類的形式各有不同,但總的來說,一般用距離作為類的度量方法。設x、y是兩個向量  和 ,聚類分析中常用的距離有以下幾種:

(1)  曼哈頓距離

        x、y的曼哈頓距離定義為:

(2)  歐氏距離

        x、y的歐氏距離定義為:

(3)  歐氏平方距離

        x、y的歐氏平方距離定義為:

(4)  角距離

        x、y的角距離定義為:,分母是x、y兩個向量的2範數乘積。

(5)  谷本距離

        x、y的谷本距離定義為:

二、k-means方法

        在資料探勘中,k-means演算法是一種廣泛使用的聚類分析演算法,也是MADlib 1.10.0官方文件中唯一提及的聚類演算法。

1. 基本思想 

        k-means聚類劃分方法的基本思想是:將一個給定的有N個數據記錄的集合,劃分到K個分組中,每一個分組就代表一個簇,K<N。而且這K個分組滿足下列條件:

  • 每一個分組至少包含一個數據記錄。
  • 每一個數據記錄屬於且僅屬於一個分組。

        演算法首先給出一個初始的分組,以後通過反覆迭代的方法改變分組,使得每一次改進之後的分組方案都較前一次好,而所謂好的標準就是:同一分組中物件的距離越近越好(已經收斂,反覆迭代至組內資料幾乎無差異),而不同分組中物件的距離越遠越好。

2. 原理與步驟 

        k-means演算法的工作原理是:首先隨機從資料集中選取K個點,每個點初始地代表每個簇的中心,然後計算剩餘各個樣本到中心點的距離,將它賦給最近的簇,接著重新計算每一簇的平均值作為新的中心點,整個過程不斷重複,如果相鄰兩次調整沒有明顯變化,說明資料聚類形成的簇已經收斂。本演算法的一個特點是在每次迭代中都要考察每個樣本的分類是否正確。若不正確,就要調整,在全部樣本調整完後,再修改中心點,進入下一次迭代。這個過程將不斷重複直到滿足某個終止條件,終止條件可以是以下任何一個:

  • 沒有物件被重新分配給不同的聚類。
  • 聚類中心不再發生變化。
  • 誤差平方和區域性最小。

        k-means演算法是很典型的基於距離的聚類演算法,採用距離作為相似性的評價指標,即認為兩個物件的距離越近,其相似度就越大。該演算法認為簇是由距離靠近的物件組成,因此把得到緊湊且獨立的簇作為最終目標。

        k-means演算法的輸入是聚類個數k,以及n個數據物件,輸出是滿足誤差最小標準的k個聚簇。其處理流程為:

  1. 從n個數據物件中任意選擇k個物件作為初始中心。
  2. 計算每個物件與這些中心物件的距離,並根據最小距離對相應的物件進行劃分。
  3. 重新計算每個有變化聚類的均值作為新的中心。
  4. 迴圈2、3直到每個聚類不再發生變化為止。終止條件一般為最小化物件到其聚類中心的距離的平方和:

3. k-means演算法

        k-means演算法接受輸入量k,然後將n個數據物件劃分為k個簇以便使得所獲得的簇滿足:同一簇中的物件相似度較高,而不同簇中的物件相似度較低。簇相似度是利用各簇中物件的均值所獲得的中心物件來進行計算的。為了便於理解k-means演算法,可以參考圖1所示的二維向量的例子。

圖1 k-means聚類演算法

        從圖中我們可以看到A、B、C、D、E五個點。而灰色的點是初始中心點,也就是用來找簇的點。有兩個中心點,所以K=2。

        k-means的演算法如下:

  1. 隨機在圖中取K(這裡K=2)個初始中心點。
  2. 對圖中的所有點求到這K箇中心點的距離,假如點Pi離種子點Si最近,那麼Pi屬於Si聚類。圖1中,我們可以看到A、B屬於上面的中心點,C、D、E屬於下面中部的中心點。
  3. 移動中心點到屬於它的簇的中心,作為新的中心點,見圖1上的第三步。
  4. 重複第2和第3步,直到中心點沒有移動,可以看到圖1中的第四步上面的中心點聚合了A、B、C,下面的中心點聚合了D、E。

        二維座標中兩點之間距離公式如下:

        公式中(x1,y1),(x2,y2)分別為A、B兩個點的座標。求聚類中心點的演算法可以簡單使用各個點的X/Y座標的平均值。

        k-means主要有兩個重大缺陷,並且都和初始值有關:

  • K是事先給定的,這個K值的選定是非常難以估計的。很多時候,事先並不知道給定的資料集應該分成多少個類別才最合適。(ISODATA演算法通過類的自動合併和分裂,得到較為合理的型別數目K)
  • k-means演算法以初始隨機中心點為基礎,這個隨機中心點非常重要,不同的隨機中心點會有得到完全不同的結果。k-means++演算法就是用來解決這個問題,它可以有效地選擇初始點。

        k-means++演算法步驟:

  1. 先從輸入資料物件中隨機挑一個作為中心點。
  2. 對於每個資料物件x,計算其和最近的一箇中心點的距離D(x)並儲存在一個數組裡,然後把這些距離加起來得到Sum(D(x))。
  3. 再取一個隨機值,用取權重的方式來計算下一個中心點。這個演算法的實現是,先取一個能落在Sum(D(x))中的隨機值Random,然後用Random -= D(x),直到其<=0,此時的x就是下一個中心點。
  4. 重複第2和第3步直到所有的K箇中心點都被選出來。
  5. 進行k-means演算法。

三、MADlib的k-means相關函式

        形式上,我們希望最小化以下目標函式:

        其中 是n個數據物件, 是k箇中心點,常見的情況下,距離使用歐氏平方距離。這個問題在計算上很困難(NP-hard問題),但由於區域性啟發式搜尋演算法在實踐中表現的相當好,如今被普遍採用,其中之一就是前面討論的k-means演算法。MADlib提供了三組k-means演算法相關函式,分別是訓練函式、簇分配函式和輪廓係數函式。

1.  訓練函式

(1)語法

        MADlib提供了以下四個k-means演算法訓練函式。使用隨機中心點方法,語法如下:

kmeans_random (rel_source,  
               expr_point,  
               k,  
               fn_dist,  
               agg_centroid,  
               max_num_iterations,  
               min_frac_reassigned
 )

        使用kmeans++中心點方法,語法如下:

kmeanspp( rel_source,  
          expr_point,  
          k,  
          fn_dist,  
          agg_centroid,  
          max_num_iterations,  
          min_frac_reassigned,  
          seeding_sample_ratio  
        ) 

        由rel_initial_centroids引數提供一個包含初始中心點的表名,語法如下:

kmeans( rel_source,  
        expr_point,  
        rel_initial_centroids,  
        expr_centroid,  
        fn_dist,  
        agg_centroid,  
        max_num_iterations,  
        min_frac_reassigned  
      )

        由initial_centroids引數提供的陣列表示式,指定一個初始中心點集合,語法如下:

kmeans( rel_source,  
        expr_point,  
        initial_centroids,  
        fn_dist,  
        agg_centroid,  
        max_num_iterations,  
        min_frac_reassigned  
      )

(2)引數

引數名稱

資料型別

描述

rel_source

TEXT

含有輸入資料物件的表名。資料物件和預定義中心點(如果使用的話)應該使用一個數組型別的列儲存,如FLOAT[]或INTEGER[]。呼叫任何以上四種函式進行資料分析時,都會跳過具有non-finite值的資料物件,non-finite值包括NULL、NaN、infinity等。

expr_point

TEXT

包含資料物件的列名。

k

INTEGER

指定要計算的中心點的個數。

fn_dist(可選)

TEXT

預設值為‘squared_dist_norm2’,指定計算資料物件與中心點距離的函式名稱。可以使用以下距離函式,括號內為均值計算方法:

  • dist_norm1:1範數/曼哈頓距離(元素中位數)。
  • dist_norm2: 2正規化/歐氏距離(元素平均數)。
  • squared_dist_norm2:歐氏平方距離(元素平均數)。
  • dist_angle:角距離(歸一化資料的元素平均數)。
  • dist_tanimoto:谷本距離(歸一化資料的元素平均數)。
  • 具有DOUBLE PRECISION[] x, DOUBLE PRECISION[] y -> DOUBLE PRECISION引數形式的使用者自定義函式。

agg_centroid(可選)

TEXT

預設值為‘avg’。確定中心點使用的聚合函式名,可以使用以下聚合函式:

  • avg:平均值(預設)。
  • normalized_avg:歸一化平均值。

max_num_iterations(可選)

INTEGER

預設值為20,指定執行的最大迭代次數。

min_frac_reassigned(可選)

DOUBLE PRECISION

預設值為0.001。相鄰兩次迭代所有中心點相差小於該值時計算完成。

seeding_sample_ratio(可選)

DOUBLE PRECISION

預設值為1.0。kmeans++將掃描資料‘k’次,對大資料集會很慢。此引數指定用於確定初始中心點所使用的原始資料集樣本比例。當此引數大於0時(最大值為1.0),初始中心點在資料均勻分佈的隨機樣本上。注意,k-means演算法最終會在全部資料集上執行。此引數只是為確定初始中心點建立一個子樣本,並且只對kmeans++有效。

rel_initial_centroids

TEXT

包含初始中心點的表名。

expr_centroid

TEXT

rel_initial_centroids指定的表中包含中心點的列名。

initial_centroids

TEXT

包含初始中心點的DOUBLE PRECISION陣列表示式的字串。

表1 kmeans相關函式引數說明

(3)輸出格式

        k-means模型的輸出具有表2所示列的複合資料型別。

列名

資料型別

描述

centroids

DOUBLE PRECISION[][]

最終的中心點。

cluster_variance

DOUBLE PRECISION[]

每個簇的方差。

objective_fn

DOUBLE PRECISION

方差合計。

frac_reassigned

DOUBLE PRECISION

最後一次迭代的誤差。

num_iterations

INTEGER

迭代執行的次數。

表2 k-means模型輸出列說明

2.  簇分配函式

(1)語法

        得到中心點後,可以呼叫以下函式為每個資料物件進行簇分配:

closest_column( m, x )

(2)引數

        m:DOUBLEPRECISION[][]型別,訓練函式返回的中心點。

        x:DOUBLEPRECISION[]型別,輸入資料。

(3)輸出格式

        column_id:INTEGER型別,簇ID,從0開始。

        distance:DOUBLEPRECISION型別,資料物件與簇中心點的距離。

3.  輪廓係數函式

        輪廓係數(Silhouette Coefficient),是聚類效果好壞的一種評價方法。作為 k-means模型的一部分,MADlib提供了一個輪廓係數方法的簡化版本函式,該函式結果值處於-1~1之間,值越大,表示聚類效果越好。注意,對於大資料集,該函式的計算代價很高。

(1)語法

simple_silhouette( rel_source,  
                   expr_point,  
                   centroids,  
                   fn_dist  
                 )  

(2)引數

引數名稱

資料型別

描述

rel_source

TEXT

含有輸入資料物件的表名。

expr_point

TEXT

資料物件列名。

centroids

TEXT

中心點表示式。

fn_dist(可選)

TEXT

計算資料點到中心點距離的函式名,預設值為‘dist_norm2’。

表3 simple_silhouette函式引數說明

四、k-means應用示例

1.  問題提出

        RFM模型是在做使用者價值細分時常用的方法,主要涵蓋的指標有最近一次消費時間R(Recency)、消費頻率(Frequency),消費金額(Monetary)。我們用R、F、M三個指標作為資料物件屬性,應用MADlib的k-means模型相關函式對使用者進行聚類分析,並得出具有實用性和解釋性的結論。

2.  建立測試資料表並裝載原始資料 

-- 建立原始資料表  
drop table if exists t_source;  
create table t_source  
(cust_id int,  
 amount decimal(10 , 2 ),  
 quantity int,  
 dt date);  
  
-- 新增100條資料  
insert into t_source (cust_id,amount,quantity,dt) values   
(567,1100.51,2,'2017-07-20'),(568,2003.47,2,'2017-07-20'),
(569,297.91,2,'2017-07-14'),(570,300.02,2,'2017-07-12'),
…
(663,954.77,2,'2017-06-27'),(664,6006.78,3,'2017-06-22'),
(665,25755.7,2,'2017-06-06'),(666,60201.48,2,'2017-07-11'); 

3.  資料預處理

(1)將最近一次訪問日期處理成最近一次訪問日期到當前日期的間隔天數,代表該使用者是否最近有購買記錄(即目前是否活躍)。

(2)因為k-means受異常值影響很大,並且金額變異比較大,所以去除該維度的異常值。

(3)使用PCA方法消除維度之間的相關性。

(4)0-1歸一化處理。

-- 去掉異常值  
drop table if exists t_source_change;  
create table t_source_change   
(row_id serial,  
 cust_id int,  
 amount decimal(10 , 2 ),  
 quantity int,  
 dt int);  
  
insert into t_source_change (cust_id,amount,quantity,dt)   
select cust_id,   
       amount,  
       quantity,  
       current_date-dt dt   
  from t_source   
 where amount < (select percentile_cont (0.99) within group (order by amount)  
                   from t_source);  
  
select * from t_source_change order by cust_id;

        查詢結果為: 

     …  
     94 |     660 | 11594.24 |       10 |  2  
     95 |     661 | 12039.49 |        2 | 30  
     96 |     662 |  1494.97 |        2 | 39  
     97 |     663 |   954.77 |        2 | 25  
     98 |     664 |  6006.78 |        3 | 30  
     99 |     665 | 25755.70 |        2 | 46  
(99 rows) 

        可以看到,因為cust_id=666使用者的金額不在99%的範圍內,所以t_source_change表中去掉了該條記錄。在此去除異常並非這個使用者異常,而是為了改善聚類結果。最後需要給這些“異常使用者”做業務解釋。

-- PCA去掉相關性  
drop table if exists mat;    
create table mat (id integer,    
                  row_vec double precision[] );
    
insert into mat  
select row_id,  
       string_to_array(amount||','||quantity||','||dt,',')::double precision[] row_vec  
  from t_source_change;  

-- PCA培訓  
drop table if exists result_table, result_table_mean;    
select madlib.pca_train('mat',              -- source table    
                        'result_table',     -- output table    
                        'id',               -- row id of source table    
                        3                   -- number of principal components    
                       );    

-- PCA投影  
drop table if exists residual_table, result_summary_table, out_table;    
select madlib.pca_project( 'mat',    
                           'result_table',    
                           'out_table',    
                           'id',    
                           'residual_table',    
                           'result_summary_table'    
                           );  
-- 0-1歸一化  
drop table if exists t_source_change_nor;  
create table t_source_change_nor  
as   
select row_id,  
       string_to_array(amount_nor||','||quantity_nor||','||dt_nor,',')::double precision[] row_vec  
  from  
(  
select row_id,   
       (row_vec[1] - min_amount)/(max_amount - min_amount) amount_nor,  
       (row_vec[2] - min_quantity)/(max_quantity - min_quantity) quantity_nor,  
       (max_dt - row_vec[3])/(max_dt - min_dt) dt_nor  
  from out_table,  
       (select max(row_vec[1]) max_amount,  
               min(row_vec[1]) min_amount,  
               max(row_vec[2]) max_quantity,  
               min(row_vec[2]) min_quantity,  
               max(row_vec[3]) max_dt,  
               min(row_vec[3]) min_dt  
          from out_table) t) t;  
  
select * from t_source_change_nor order by row_id;  

        查詢結果為:

     …  
     94 | {0.558470357737996,0.954872666162949,0.296935710714377}  
     95 | {0.54122257689463,0.482977156688704,0.81244230552888}  
     96 | {0.949697477408967,0.385844448834949,0.65901807391295}  
     97 | {0.970623648952883,0.62014760223173,0.704941708880569}  
     98 | {0.774918367989914,0.513405499602443,0.666993533505089}  
     99 | {0.00988267286683593,0.150872332720288,0.908966781310526}  
(99 rows)

4.  k-means聚類

(1)呼叫kmeanspp函式執行聚類

drop table if exists km_result;  
create table km_result as  
select * from madlib.kmeanspp
( 't_source_change_nor',         -- 源資料表名  
  'row_vec',                     -- 包含資料點的列名   
  3,                             -- 中心點個數  
  'madlib.squared_dist_norm2',   -- 距離函式  
  'madlib.avg',                  -- 聚合函式  
  20,                            -- 迭代次數  
0.001                          -- 停止迭代條件 );  

\x on;  
select centroids[1][1]||', '||centroids[1][2]||', '||centroids[1][3] cent1,
       centroids[2][1]||', '||centroids[2][2]||', '||centroids[2][3] cent2,           
       centroids[3][1]||', '||centroids[3][2]||', '||centroids[3][3] cent3,
       cluster_variance,
       objective_fn,
       frac_reassigned,
       num_iterations	   
  from km_result;

        查詢結果如下:

-[ RECORD 1 ]----+------------------------------------------------
cent1            | 0.872433445942, 0.0724942318135, 0.318094096598
cent2            | 0.890144445443, 0.546835465582, 0.333554735766
cent3            | 0.238390106949, 0.449997152636, 0.267439867941
cluster_variance | {1.33448519773,2.05461238207,1.83212942768}
objective_fn     | 5.22122700748
frac_reassigned  | 0
num_iterations   | 8

(2)呼叫simple_silhouette函式評價聚類質量

select * from madlib.simple_silhouette
( 't_source_change_nor',  
  'row_vec',  
  (select centroids 
     from madlib.kmeanspp('t_source_change_nor',  
                          'row_vec',  
                          3,  
                          'madlib.squared_dist_norm2',  
                          'madlib.avg',  
                          20,  
                          0.001)),  
                          
  'madlib.dist_norm2' );

        結果如下:

-[ RECORD 1 ]-----+------------------  
simple_silhouette | 0.640471849127657 

(3)呼叫closest_column函式執行簇分配

\x off;  
  
select cluster_id,  
       round(count(cust_id)/99.0,4) pct,  
       round(avg(amount),4) avg_amount,    
       round(avg(quantity),4) avg_quantity,   
       round(avg(dt),2) avg_dt  
  from   
(  
select t2.*,      
    (madlib.closest_column(centroids, row_vec)).column_id as cluster_id  
  from t_source_change_nor as t1, km_result, t_source_change t2  
 where t1.row_id = t2.row_id) t  
 group by cluster_id;

        查詢結果為:

 cluster_id |  pct   | avg_amount | avg_quantity | avg_dt   
------------+--------+------------+--------------+--------  
          2 | 0.1919 |  5439.9795 |       2.0526 |  48.79  
          1 | 0.4848 |  3447.5631 |       2.4375 |  29.56  
          0 | 0.3232 |  5586.0203 |       4.0313 |   5.56  
(3 rows) 

5.  解釋聚類結果

        表4對聚類結果分成的三類使用者進行了說明。

類別

佔比

描述

第一類:高價值使用者

32.3%

購買頻率高(平均4次);消費金額較高(平均5586元);最近一週有過購買行為,這部分使用者需要大力發展。

第二類:中價值使用者

48.5%

購買頻率中等(平均2.4次);消費金額不高(平均3447);最近一個月有個購買行為,這部分使用者可以適當誘導購買。

第三類:高價值挽留使用者

19.2

購買頻率一般(平均2次);消費金額較高(平均5439元);較長時間沒有購買行為,這部分客戶需要儘量挽留。

表4 聚類形成的三類使用者

五、小節

        聚類方法是根據給定的規則進行訓練,自動生成類別的資料探勘方法,屬於無監督學習範疇。聚類已經被應用在模式識別、資料分析、影象處理、市場研究等多個領域。雖然類的形式各不相同,但一般都用距離作為類的度量方法。聚類演算法有很多種,其中k-means是應用最廣泛、適應性最強的聚類演算法,也是MADlib唯一支援的聚類演算法。MADlib提供了4個k-means訓練函式、一個簇分配函式、一個輪廓係數函式。我們利用MADlib提供的這些函式,實現了一個按照RFM模型對使用者進行細分的示例需求。