引入

聚類

是一種無監督學習,將

相似

的樣本(物件/例項)歸到同一簇(cluster)中。通常用樣本的

相似度

距離

來衡量。eg:天空中的星星,靠得近的星星可以被歸為一個星團,而星團之間的星星距離比較遠。(

簇內的物件越相似,聚類的效果越好。

硬聚類:一個樣本只能屬於一個類。

軟聚類:一個樣本可以以機率屬於多個類。

聚類與分類的不同:分類為監督,是監督學習,目標事先已知;而聚類的“類”沒有預先定義,是從資料中自動發現的,是無監督學習。也就是說,聚類問題中,給我們的樣本只用x,沒有y。

k-means表示:該演算法可以發現k個不同的簇,且每個簇的中心採用簇內所含值的均值計算而成。屬於硬聚類。。常見的聚類演算法還有:層次聚類。

K-means演算法

1967年MacQueen提出

流程

聚類演算法——K-Means原理、k選擇、質心初始化、Python實現

K-means缺點

K的選擇需要事先預定。

K個初始質心的位置選擇對聚類結果和執行時間都有很大影響。

不能保證全域性最優,可能是區域性最優解。

K-means改進

如何確定K?

一、手肘法

思想:隨著聚類數K的增大,樣本劃分更加精細,那麼所有樣本的聚類誤差(SSE)會逐漸變小:

SSE=\sum_{i=1}^k\sum_{x\in D_i}|x-\mu_i|^2

——當K值小於真實聚類數時,K的增加會對聚類效果產生很大影響,故SSE下降幅度很大;

——當K值大於真實聚類數時,K的增加不會對聚類效果產生很大影響,故SSE下降幅度將會趨於平緩;整個SSE-K圖為一個手肘型。

聚類演算法——K-Means原理、k選擇、質心初始化、Python實現

圖片來源

二、輪廓係數法

思想:類中樣本距離越近,類間樣本距離越遠,聚類效果越好。用平均輪廓係數來衡量。

類中不相似度:ai的平均,體現凝聚度。ai表示樣本xi到同類中其他樣本的平均距離。ai越小,表明類中樣本不相似度越低,凝聚度越高,越應該聚為一類。(among)

類間不相似度:bi的最小值,體現分離度。bi表示樣本xi到其他類中所有樣本的平均距離。bi越大,表明內間不相似程度越高,分離度越高,越不應該聚為一個類。(between)。最近類:

C_j=\min\limits_{D_k}\frac{1}{n}\sum_{x\in D_k}|x-x_i|^2

Dk為要找的最近類,x是最近類裡的全部樣本,n是最近類裡的全部樣本的個數。

某一個樣本點xi的輪廓係數:

聚類演算法——K-Means原理、k選擇、質心初始化、Python實現

聚類演算法——K-Means原理、k選擇、質心初始化、Python實現

選SSE還是輪廓係數?

通常選用SSE:舉例和解釋可以參照這裡

如何初始化質心

K-means++

隨機初始化質心可能導致演算法迭代很慢,K-means++是對K-mean隨機初始化質心的一個最佳化,具體步驟如下:

隨機選取一個點作為第一個聚類中心。

計算所有樣本與第一個聚類中心的距離。

選擇出上一步中距離最大的點作為第二個聚類中心。

迭代:計算所有點到與之最近的聚類中心的距離,選取最大距離的點作為新的聚類中心。

終止條件:直到選出了這k箇中心。

只需要隨機取第一個聚類中心即可。

然後按照最遠優先原則來選新的聚類中心

如何克服區域性最優解

K-means例項應用——Python實現

import

numpy

as

np

def

loadDataSet

fileName

):

‘’‘

:param fileName: 檔名字

:return: 矩陣

’‘’

dataMat

=

[]

fr

=

open

fileName

for

line

in

fr

readlines

():

curLine

=

line

strip

()

split

\t

fltLine

=

list

map

float

curLine

))

dataMat

append

fltLine

return

dataMat

def

distEclud

vecA

vecB

):

return

np

sqrt

np

sum

np

power

vecA

-

vecB

2

)))

def

randCent

dataSet

k

):

‘’‘

構建一個包含k個隨機質心的集合(k行n列,n表示資料的維度/特徵個數),

只需要保證質心在資料邊界裡面就可以了

:param dataSet: 輸入資料集

:param k: 質心個數

:return:

’‘’

# 得到資料樣本的維度

n

=

np

shape

dataSet

)[

1

# 初始化為一個(k,n)的全零矩陣

centroids

=

np

mat

np

zeros

((

k

n

)))

# 遍歷資料集的每一個維度

for

j

in

range

n

):

# 得到該列資料的最小值,最大值

minJ

=

np

min

dataSet

[:,

j

])

maxJ

=

np

max

dataSet

[:,

j

])

# 得到該列資料的範圍(最大值-最小值)

rangeJ

=

float

maxJ

-

minJ

# k個質心向量的第j維資料值隨機為位於(最小值,最大值)內的某一值

centroids

[:,

j

=

minJ

+

rangeJ

*

np

random

rand

k

1

# random。rand(行,列)產生這個形狀的矩陣,且每個元素in [0,1)

# 返回初始化得到的k個質心向量

return

centroids

def

kMeans

dataSet

k

distMeas

=

distEclud

createCent

=

randCent

):

‘’‘

:param dataSet: 輸入的資料集

:param k: 聚類的個數,可調

:param distMeas: 計算距離的方法,可調

:param createCent: 初始化質心的位置的方法,可調

:return: k個類質心的位置座標,樣本所處的類&到該類質心的距離

’‘’

# 獲取資料集樣本數

m

=

np

shape

dataSet

)[

0

# 初始化一個(m,2)全零矩陣,用來記錄沒一個樣本所屬類,距離類中心的距離

clusterAssment

=

np

mat

np

zeros

((

m

2

)))

# 建立初始的k個質心向量

centroids

=

createCent

dataSet

k

# 聚類結果是否發生變化的布林型別

clusterChanged

=

True

# 終止條件:所有資料點聚類結果不發生變化

while

clusterChanged

# 聚類結果變化布林型別置為False

clusterChanged

=

False

# 遍歷資料集每一個樣本向量

for

i

in

range

m

):

# 初始化最小距離為正無窮,最小距離對應的索引為-1

minDist

=

float

‘inf’

minIndex

=

-

1

# 迴圈k個類的質心

for

j

in

range

k

):

# 計算資料點到質心的歐氏距離

distJI

=

distMeas

centroids

j

:],

dataSet

i

:])

# 如果距離小於當前最小距離

if

distJI

<

minDist

# 當前距離為最小距離,最小距離對應索引應為j(第j個類)

minDist

=

distJI

minIndex

=

j

# 當前聚類結果中第i個樣本的聚類結果發生變化:布林值置為True,繼續聚類演算法

if

clusterAssment

i

0

!=

minIndex

clusterChanged

=

True

# 更新當前變化樣本的聚類結果和平方誤差

clusterAssment

i

:]

=

minIndex

minDist

**

2

# 列印k-means聚類的質心

# print(centroids)

# 遍歷每一個質心

for

cent

in

range

k

):

# 將資料集中所有屬於當前質心類的樣本透過條件過濾篩選出來

ptsInClust

=

dataSet

np

nonzero

clusterAssment

[:,

0

A

==

cent

)[

0

]]

# 計算這些資料的均值(axis=0:求列均值),作為該類質心向量

centroids

cent

:]

=

np

mean

ptsInClust

axis

=

0

# 返回k個聚類,聚類結果及誤差

return

centroids

clusterAssment

聚類演算法——K-Means原理、k選擇、質心初始化、Python實現

import matplotlib。pyplot as plt

def plotDataSet(filename):

# 匯入資料

datMat = np。mat(loadDataSet(filename))

# 進行k-means演算法其中k為4

myCentroids, clustAssing = kMeans(datMat, 4)

clustAssing = clustAssing。tolist()

myCentroids = myCentroids。tolist()

xcord = [[], [], [], []]

ycord = [[], [], [], []]

datMat = datMat。tolist()

m = len(clustAssing)

for i in range(m):

if int(clustAssing[i][0]) == 0:

xcord[0]。append(datMat[i][0])

ycord[0]。append(datMat[i][1])

elif int(clustAssing[i][0]) == 1:

xcord[1]。append(datMat[i][0])

ycord[1]。append(datMat[i][1])

elif int(clustAssing[i][0]) == 2:

xcord[2]。append(datMat[i][0])

ycord[2]。append(datMat[i][1])

elif int(clustAssing[i][0]) == 3:

xcord[3]。append(datMat[i][0])

ycord[3]。append(datMat[i][1])

fig = plt。figure()

ax = fig。add_subplot(111)

# 繪製樣本點

ax。scatter(xcord[0], ycord[0], s=20, c=‘b’, marker=‘*’, alpha=。5)

ax。scatter(xcord[1], ycord[1], s=20, c=‘r’, marker=‘D’, alpha=。5)

ax。scatter(xcord[2], ycord[2], s=20, c=‘c’, marker=‘>’, alpha=。5)

ax。scatter(xcord[3], ycord[3], s=20, c=‘k’, marker=‘o’, alpha=。5)

# 繪製質心

ax。scatter(myCentroids[0][0], myCentroids[0][1], s=100, c=‘k’, marker=‘+’, alpha=。5)

ax。scatter(myCentroids[1][0], myCentroids[1][1], s=100, c=‘k’, marker=‘+’, alpha=。5)

ax。scatter(myCentroids[2][0], myCentroids[2][1], s=100, c=‘k’, marker=‘+’, alpha=。5)

ax。scatter(myCentroids[3][0], myCentroids[3][1], s=100, c=‘k’, marker=‘+’, alpha=。5)

plt。title(‘DataSet’)

plt。xlabel(‘X’)

plt。show()

————————————————-【補充】呼叫程式碼和資料集————————————————-

# 測試一下函式

dataMat

=

np

mat

loadDataSet

‘testSet。txt’

))

print

‘min x:

%f

%

min

dataMat

[:,

0

]))

print

‘min y:

%f

%

min

dataMat

[:,

1

]))

print

‘max x:

%f

%

max

dataMat

[:,

0

]))

print

‘man y:

%f

%

max

dataMat

[:,

1

]))

for

i

in

range

10

):

testRandCent

=

randCent

dataMat

2

print

‘No

%d

random center is ’

%

i

print

testRandCent

# 測試歐幾里得距離函式

print

distEclud

dataMat

0

],

dataMat

1

]))

myCentroids

clustAssing

=

kMeans

dataMat

4

print

myCentroids

print

clustAssing

dataMat2

=

np

mat

loadDataSet

‘testSet2。txt’

))

myCentroids2

clustAssing2

=

kMeans

dataMat2

4

print

myCentroids2

print

clustAssing2

print

‘SSE of simple k-means:

%f

%

sum

clustAssing2

[:,

1

]))

sumSSE

=

0

for

i

in

range

40

):

myCentroids2

clustAssing2

=

kMeans

dataMat2

4

sumSSE

+=

sum

clustAssing2

[:,

1

])

avgSSE

=

sumSSE

/

40

print

‘Avg(40) SSE of simple k-means:

%f

%

avgSSE

plotDataSet

‘testSet。txt’

plotDataSet

‘testSet2。txt’

testSet。txt

1。658985 4。285136

-3。453687 3。424321

4。838138 -1。151539

-5。379713 -3。362104

0。972564 2。924086

-3。567919 1。531611

0。450614 -3。302219

-3。487105 -1。724432

2。668759 1。594842

-3。156485 3。191137

3。165506 -3。999838

-2。786837 -3。099354

4。208187 2。984927

-2。123337 2。943366

0。704199 -0。479481

-0。392370 -3。963704

2。831667 1。574018

-0。790153 3。343144

2。943496 -3。357075

-3。195883 -2。283926

2。336445 2。875106

-1。786345 2。554248

2。190101 -1。906020

-3。403367 -2。778288

1。778124 3。880832

-1。688346 2。230267

2。592976 -2。054368

-4。007257 -3。207066

2。257734 3。387564

-2。679011 0。785119

0。939512 -4。023563

-3。674424 -2。261084

2。046259 2。735279

-3。189470 1。780269

4。372646 -0。822248

-2。579316 -3。497576

1。889034 5。190400

-0。798747 2。185588

2。836520 -2。658556

-3。837877 -3。253815

2。096701 3。886007

-2。709034 2。923887

3。367037 -3。184789

-2。121479 -4。232586

2。329546 3。179764

-3。284816 3。273099

3。091414 -3。815232

-3。762093 -2。432191

3。542056 2。778832

-1。736822 4。241041

2。127073 -2。983680

-4。323818 -3。938116

3。792121 5。135768

-4。786473 3。358547

2。624081 -3。260715

-4。009299 -2。978115

2。493525 1。963710

-2。513661 2。642162

1。864375 -3。176309

-3。171184 -3。572452

2。894220 2。489128

-2。562539 2。884438

3。491078 -3。947487

-2。565729 -2。012114

3。332948 3。983102

-1。616805 3。573188

2。280615 -2。559444

-2。651229 -3。103198

2。321395 3。154987

-1。685703 2。939697

3。031012 -3。620252

-4。599622 -2。185829

4。196223 1。126677

-2。133863 3。093686

4。668892 -2。562705

-2。793241 -2。149706

2。884105 3。043438

-2。967647 2。848696

4。479332 -1。764772

-4。905566 -2。911070

testSet2。txt

3。275154 2。957587

-3。344465 2。603513

0。355083 -3。376585

1。852435 3。547351

-2。078973 2。552013

-0。993756 -0。884433

2。682252 4。007573

-3。087776 2。878713

-1。565978 -1。256985

2。441611 0。444826

-0。659487 3。111284

-0。459601 -2。618005

2。177680 2。387793

-2。920969 2。917485

-0。028814 -4。168078

3。625746 2。119041

-3。912363 1。325108

-0。551694 -2。814223

2。855808 3。483301

-3。594448 2。856651

0。421993 -2。372646

1。650821 3。407572

-2。082902 3。384412

-0。718809 -2。492514

4。513623 3。841029

-4。822011 4。607049

-0。656297 -1。449872

1。919901 4。439368

-3。287749 3。918836

-1。576936 -2。977622

3。598143 1。975970

-3。977329 4。900932

-1。791080 -2。184517

3。914654 3。559303

-1。910108 4。166946

-1。226597 -3。317889

1。148946 3。345138

-2。113864 3。548172

0。845762 -3。589788

2。629062 3。535831

-1。640717 2。990517

-1。881012 -2。485405

4。606999 3。510312

-4。366462 4。023316

0。765015 -3。001270

3。121904 2。173988

-4。025139 4。652310

-0。559558 -3。840539

4。376754 4。863579

-1。874308 4。032237

-0。089337 -3。026809

3。997787 2。518662

-3。082978 2。884822

0。845235 -3。454465

1。327224 3。358778

-2。889949 3。596178

-0。966018 -2。839827

2。960769 3。079555

-3。275518 1。577068

0。639276 -3。412840

聚類演算法——K-Means原理、k選擇、質心初始化、Python實現

這個資料集比較理想,聚類情況比較好。但是對於有些資料集,由於K值選擇、初始點隨機選擇這些很有可能導致演算法產生區域性最優解。

聚類演算法——K-Means原理、k選擇、質心初始化、Python實現

這一副圖透過同一個資料集,多次呼叫上述K-means演算法,但是產生了不同的效果。

一些問題

初始設定k=4,為什麼到了後面怎麼只聚成了3個類?

為什麼有些大一點的cluster分成了兩類?有些是一類?

都是因為隨機初始點造成的!!

接下來需要解決的問題(見下一篇文章)

怎麼避免由於隨機化初始點而造成的區域性最優解問題?上面有提到K-means++是一種解決辦法。

如何實現選取最佳K的程式碼?

講解影片請移步:

參考文獻

1。 Peter Harrington。 機器學習實戰[M]。 河北: 人民郵電出版社, 2019。

2。 李航。 統計學習方法第二版。 北京: 清華大學出版社, 2019。

2。

https://www。

cnblogs。com/pinard/p/61

64214。html

3。

https://www。

cnblogs。com/jerrylead/a

rchive/2011/04/06/2006910。html

4。 如何選擇k:

https://

blog。csdn。net/qq_157385

01/article/details/79036255

5。 輪廓係數:

https://

blog。csdn。net/wangxiaop

eng0329/article/details/53542606

6。 Iris實現輪廓係數選k(R):

http://

studio。galaxystatistics。com

/report/cluster_analysis/article4/

7。 Python3程式碼參考:

https://

github。com/wzy6642/Mach

ine-Learning-in-Action-Python3/blob/master/K_Means_Project1/K_Means。py