大資料聚類演算法效能比較及實驗報告
在大資料領域這個聚類演算法真是起到了十分重要的作用,只有通過有效地聚類才能得到非常直觀的結果。
有一個實驗要求對比兩種大資料聚類演算法的效能,具體的程式碼也不是由我實現的,我只是改了一部分,主要還是部落格大佬們的程式碼,我這裡借用了一下~~ 具體的實驗報告和python原始碼檔案在最後位置,提供百度雲下載,本文使用的是K-means演算法和層次聚類演算法AGNES,原理介紹和實驗結果詳見百度雲提供的報告等
如今大資料的時代,大量的可以獲得的資料被儲存,用來分析以獲得有用的資訊,然而資料紛繁雜亂,需要研究者對齊進行一定的劃分,將相似的資料放在一起進行分析,這就是所謂的聚類。
聚類就是按照某個特定標準(如距離準則,即資料點之間的距離)把一個數據集分割成不同的類或簇,使得同一個簇內的資料物件的相似性儘可能大,同時不在同一個簇中的資料物件的差異性也儘可能地大,常用的基本的距離計算方式就是歐氏距離。我們可以具體地理解為,聚類後同一類的資料儘可能聚集到一起,不同類資料儘量分離。聚類技術正在蓬勃發展,應用到的領域包括資料探勘、統計學、機器學習、空間資料庫技術、生物學以及市場營銷等。各種聚類方法也被不斷提出和改進,而不同的方法適合於不同型別的資料,因此對各種聚類方法、聚類效果的比較是一個值得研究的問題。
目前,有大量的聚類演算法。而對於具體應用,聚類演算法的選擇取決於資料的型別、聚類的目的。如果聚類分析被用作描述或探查的工具,可以對同樣的資料嘗試多種演算法,以發現數據可能揭示的結果。主要的聚類演算法可以劃分為如下幾類:劃分方法、層次方法、基於密度的方法、基於網格的方法以及基於模型的方法。
K-means聚類演算法
K-means是劃分方法中較經典的聚類演算法之一。由於該演算法的效率高,所以在對大規模資料進行聚類時被廣泛應用。目前,許多演算法均圍繞著該演算法進行擴充套件和改進。K-means演算法目標是,以k為引數,把n個物件分成k個簇,使簇內具有較高的相似度,而簇間的相似度較低。 K-means演算法的處理過程如下:首先,隨機地 選擇k個物件,每個物件初始地代表了一個簇的平均值或中心;對剩餘的每個物件,根據其與各簇中心的距離,將它賦給最近的簇;然後重新計算每個簇的平均值。 這個過程不斷重複,直到準則函式收斂。 通常,採用平方誤差準則。平方誤差準則採用的是資料庫中所有物件的平方誤差的總和,與是空間中的點和每個簇的的平均值有關[9]。目標是使生成的簇儘可能緊湊獨立,使用的距離度量是歐幾里得距離,當然也可以用其他距離度量。K-means聚類演算法的演算法流程如下: 輸入:包含n個物件的資料庫和簇的數目k; 輸出:k個簇,使平方誤差準則最小。 步驟: (1) 任意選擇k個物件作為初始的簇中心; (2) repeat; (3) 根據簇中物件的平均值,將每個物件(重新)賦予最類似的簇; (4) 更新簇的平均值,即計算每個簇中物件的平均值; (5) until不再發生變化。 優點:簡單直接(體現在邏輯思路以及實現難度上),易於理解,在低維資料集上有不錯的效果(簡單的演算法不見得就不能得到優秀的效果)。 缺點:對於高維資料(如成百上千維,現實中還不止這麼多),其計算速度十分慢,主要是慢在計算距離上(參考歐幾里得距離,當然並行化處理是可以的,這是演算法實現層面的問題),它的另外一個缺點就是它需要我們設定希望得到的聚類數k,若我們對於資料沒有很好的理解,那麼設定k值就成了一種估計性的工作,並且會對實驗結果造成很大的偏差。
層次聚類演算法
根據層次分解的順序是自底向上的還是自上向下的,層次聚類演算法分為凝聚的層次聚類演算法和分裂的層次聚類演算法。 凝聚型層次聚類的策略是先將每個物件作為一個簇,然後合併這些原子簇為越來越大的簇,直到所有物件都在一個簇中,或者某個終結條件被滿足。絕大多數層次聚類屬於凝聚型層次聚類,它們只是在簇間相似度的定義上有所不同。四種廣泛採用的簇間距離度量方法為:最小距離,最大距離,平均值距離,平均距離。 這裡以最小距離的凝聚層次聚類演算法流程為例: (1) 將每個物件看作一類,計算兩兩之間的最小距離; (2) 將距離最小的兩個類合併成一個新類; (3) 重新計算新類與所有類之間的距離; (4) 重複(2)、(3),直到所有類最後合併成一類。 優點: 1. 距離和規則的相似度容易定義,限制少; 2. 不需要預先制定聚類數; 3. 可以發現類的層次關係(在一些特定領域如生物有很大作用); 缺點: 1. 計算複雜度太高(考慮並行化); 2. 奇異值也能產生很大影響; 3. 演算法很可能聚類成鏈狀(一層包含著一層); 4. 演算法不需要預定聚類數,但是我們選擇哪個層次的聚類作為我們需要的聚類效果,這需要我們按照實際客觀情況以及經驗來完成,畢竟就凝聚聚類來說,從最底層的每個個體作為一個個體,到最頂層所有個體合併為一個個體,其中的聚類結果可能有許許多多種。當然針對這個問題也有許多解決方案,其中一個常用的就是凝聚到某個程度其聚類之間的距離都大於某個閾值k,就停止凝聚。
具體程式碼
在實驗中,本人先是下載的點集,然後為了測試效率寫了一個簡單的產生大量點的指令碼,具體的實現程式碼如下,注意需要的庫檔案,沒有的安裝一下,還有路徑也需要改: AGNES
#-*- coding:utf-8 -*-
import math
import pylab as pl
import codecs
import re
import datetime
pl.rcParams['axes.unicode_minus'] = False
# 為了顯示漢字座標的
#計算歐式距離,a,b代表兩個元組
def calcudistance(a, b):
return math.sqrt(math.pow(a[0]-b[0], 2)+math.pow(a[1]-b[1], 2))
# 求出最小距離
def dist_min(Ci, Cj):
return min(calcudistance(i, j) for i in Ci for j in Cj)
# 求出最大距離
def dist_max(Ci, Cj):
return max(calcudistance(i, j) for i in Ci for j in Cj)
#求出平均距離
def dist_avg(Ci, Cj):
return sum(calcudistance(i, j) for i in Ci for j in Cj)/(len(Ci)*len(Cj))
#找到距離最小的下標
def find_Min(M):
min = 1000
x = 0; y = 0
for i in range(len(M)):
for j in range(len(M[i])):
if i != j and M[i][j] < min:
min = M[i][j];x = i; y = j
return (x, y, min)
#演算法核心
def AGNES(dataset, dist, k):
#初始化C和M
C = [];M = []
for i in dataset:
Ci = []
Ci.append(i)
C.append(Ci)
for i in C:
Mi = []
for j in C:
Mi.append(dist(i, j))
M.append(Mi)
q = len(dataset)
#合併更新
while q > k:
x, y, min = find_Min(M)
C[x].extend(C[y])
C.remove(C[y])
M = []
for i in C:
Mi = []
for j in C:
Mi.append(dist(i, j))
M.append(Mi)
q -= 1
return C
# 畫出結果圖
def drawfig(C):
colValue = ['r', 'y', 'g', 'b', 'c', 'k', 'm'] # 顏色陣列
for i in range(len(C)):
coo_X = [] # x座標
coo_Y = [] # y座標
for j in range(len(C[i])):
coo_X.append(C[i][j][0])
coo_Y.append(C[i][j][1])
pl.scatter(coo_X, coo_Y, marker='o', color=colValue[i%len(colValue)], label=i)
pl.legend(loc='upper right')
pl.title("聚類結果圖")
pl.savefig(savepath + '2.png')
pl.show()
def draworigian(dataset):
x_list = list()
y_list = list()
for i in range(len(dataSet)):
temp = dataSet[i]
x_list.append(temp[0])
y_list.append(temp[1])
pl.scatter(x_list, y_list, marker='o', color="b")
pl.legend(loc='upper right')
pl.title("資料原始分佈")
pl.savefig(savepath + '1.png')
pl.show()
def loadtxt(Filepath):
# 讀取文字 儲存為二維點集
inDate = codecs.open(Filepath, 'r', 'utf-8').readlines()
dataSet = list()
for line in inDate: # 段落的處理
line = line.strip()
strList = re.split('[ ]+', line)
numList = list()
for item in strList:
num = float(item)
numList.append(num)
# print numList
dataSet.append(numList)
return dataSet # dataSet = [[], [], [], ...]
savepath='D:/學習/研1/模式識別/AGNES/'
Filepath = "D:/學習/研1/模式識別/testSet.txt" # 資料集檔案
dataSet = loadtxt(Filepath) # 載入資料集
draworigian(dataSet)
start = datetime.datetime.now()
result = AGNES(dataSet, dist_avg, 4)
end = datetime.datetime.now()
timeused = end - start
print(timeused)
drawfig(result)
# 100 1.203133, 01.140652, 1.156260, 1.203152, 1.453138
# 200點 9.359476, 09.367193, 09.312600, 09.325362, 09.356845
# 500點 147.946446, 147:351248, 147.153595,147.946446, 145.493638
# 500 無需 145.429797 146.016936 147.240645 146.563253 147.534587
k-MEANS
# -*- coding: UTF-8 -*-
import numpy
import random
import codecs
import re
import matplotlib.pyplot as plt
import datetime
plt.rcParams['axes.unicode_minus'] = False # 顯示負號
# 計算歐式距離,a,b代表兩個向量
def calcudistance(a,b):
return numpy.sqrt(numpy.sum(numpy.square(a - b)))
# 初始化k個質心,資料集中隨機選擇
def initcentroids(dataSet, k):
return random.sample(dataSet, k) # 從dataSet中隨機獲取k個數據項返回
def mindistance(dataSet, centroidList):
# 對每個屬於dataSet的item,計算item與centroidList中k個質心的歐式距離,找出距離最小的,
# 並將item加入相應的簇類中
clusterDict = dict() # 用dict來儲存簇類結果
for item in dataSet:
vec1 = numpy.array(item) # 轉換成array形式
flag = 0 # 簇分類標記,記錄與相應簇距離最近的那個簇
minDis = float("inf") # 初始化為最大值
for i in range(len(centroidList)):
vec2 = numpy.array(centroidList[i])
distance = calcudistance(vec1, vec2) # 計算相應的歐式距離
if distance < minDis:
minDis = distance
flag = i # 迴圈結束時,flag儲存的是與當前item距離最近的那個簇標記
if flag not in clusterDict.keys(): # 簇標記不存在,進行初始化
clusterDict[flag] = list()
# print flag, item
clusterDict[flag].append(item) # 加入相應的類別中
return clusterDict # 返回新的聚類結果
def getCentroids(clusterDict):
# 得到k個質心
centroidList = list()
for key in clusterDict.keys():
centroid = numpy.mean(numpy.array(clusterDict[key]), axis=0) # 計算每列的均值,即找到質心
# print key, centroid
centroidList.append(centroid)
return numpy.array(centroidList).tolist()
def getVar(clusterDict, centroidList):
# 計算簇集合間的均方誤差
# 將簇類中各個向量與質心的距離進行累加求和
sum = 0.0
for key in clusterDict.keys():
vec1 = numpy.array(centroidList[key])
distance = 0.0
for item in clusterDict[key]:
vec2 = numpy.array(item)
distance += calcudistance(vec1, vec2)
sum += distance
return sum
# 畫出結果他圖
def drawfig(centroidList, clusterDict):
# 展示聚類結果
global imgcount
imgcount += 1
colorMark = ['or', 'ob', 'og', 'ok', 'oy', 'ow'] # 不同簇類的標記 'or' --> 'o'代表圓,'r'代表red,'b':blue
centroidMark = ['dr', 'db', 'dg', 'dk', 'dy', 'dw'] # 質心標記 同上'd'代表稜形
for key in clusterDict.keys():
plt.plot(centroidList[key][0], centroidList[key][1], centroidMark[key], markersize=12) # 畫質心點
for item in clusterDict[key]:
plt.plot(item[0], item[1], colorMark[key]) # 畫簇類下的點
plt.title("聚類分佈圖")
plt.savefig(savepath + str(imgcount) + '.png')
plt.show()
# 關鍵部分
def k_means(dataset,kindnum):
centroidList = initcentroids(dataSet, 4) # 初始化質心,設定k=4
clusterDict = mindistance(dataSet, centroidList) # 第一次聚類迭代
newVar = getVar(clusterDict, centroidList) # 獲得均方誤差值,通過新舊均方誤差來獲得迭代終止條件
oldVar = -0.0001 # 舊均方誤差值初始化為-1
print("----- 第1次迭代 -----")
print('k個均值向量: ')
print(centroidList)
print('平均均方誤差: %d' % (newVar))
# drawfig(centroidList, clusterDict) # 展示聚類結果
k = 2
while abs(newVar - oldVar) >= 0.0001: # 當連續兩次聚類結果小於0.0001時,迭代結束
centroidList = getCentroids(clusterDict) # 獲得新的質心
clusterDict = mindistance(dataSet, centroidList) # 新的聚類結果
oldVar = newVar
newVar = getVar(clusterDict, centroidList)
print('----- 第%d次迭代 -----\n簇類' %(k))
print('k個均值向量: ')
print(centroidList)
print('平均均方誤差: %d' %(newVar))
k += 1 # 迭代次數
return (centroidList,clusterDict)
def loadtxt(Filepath):
# 讀取文字 儲存為二維點集
inDate = codecs.open(Filepath, 'r', 'utf-8').readlines()
dataSet = list()
for line in inDate:
line = line.strip()
strList = re.split('[ ]+', line) # 去除多餘的空格
# print strList[0], strList[1]
numList = list()
for item in strList:
num = float(item)
numList.append(num)
# print numList
dataSet.append(numList)
return dataSet # dataSet = [[], [], [], ...]
savepath = 'D:/學習/研1/模式識別/K-means/'
imgcount = 0
Filepath = "D:/學習/研1/模式識別/testSet.txt" # 資料集檔案
dataSet = loadtxt(Filepath) # 載入資料集
start = datetime.datetime.now()
centroidList, clusterDict = k_means(dataSet, 4)
end = datetime.datetime.now()
timeused = end - start
print(timeused)
drawfig(centroidList, clusterDict) # 展示聚類結果
# 100 0.031245, 0.015623, 0.031249, 0.015609, 0.015624
# 200點 0.031232, 0.031253, 0.046892, 0.031234, 0.046875
# 500點 0.156265, 0.093733, 0.078108,0.062499, 0.187502
# 10000 2.000017
# 無順序 500 00.218750 00.547491 00.421866 00.281266 00.281266
最後這是是一個產生點集的指令碼,分兩種,一個是無序的隨機,另一個是大概扎堆的,一個是為了檢測效率,一個是為了測試效果。
import numpy as np
import copy
choosetype = 2 # 1 表示有序點 其他表示隨機點
data = [[3.5, -3.5], [3.5, 3.5], [-3.5, 3.5], [-3.5, -3.5]] # 四類點的中心
totalnum =500 #產生點的個數
file_path = "D:\學習\研1\模式識別\\testSet2.txt" # 儲存路徑
fopen = open(file_path, 'w') # 追加的方式讀寫開啟
for i in range(totalnum):
if choosetype == 1:
datatemp = copy.deepcopy(data)
choose = datatemp[i % 4]
n1 = 2*np.random.random(1) - 1
n2 = 2*np.random.random(1) - 1
choose[0] = choose[0] + n1
choose[1] = choose[1] + n2
fopen.writelines(str(round(choose[0][0], 6)) + " " + str(round(choose[1][0], 6)) + "\n")
else:
n1 = 4 * np.random.random(1) - 2
n2 = 4 * np.random.random(1) - 2
fopen.writelines(str(round(n1[0], 6)) + " " + str(round(n2[0], 6)) + "\n")
fopen.close()
實驗結果
k-means測試結果,該演算法有效
AGNES的結果,演算法有效
為了比較效能,對大量的點進行實驗,表格如下: 具體的演算法原理和實驗分析見報告內容,最後面提供百度雲原始碼和報告的連線
心得體會
大資料可以說是現在研究十分火熱的一個課題,在眾多的研究室和科技公司等都是屬於一個較為核心的專案。尤其是在當今網路迅速發展的時代,資訊就意味著資源,如果能掌握資訊就能把握機會。作為大資料分析的關鍵環節,聚類為分析者提供了效果超凡的資料預處理,使得我們可以發現在資料之下隱藏的邏輯關係與形勢。
在眾多的聚類演算法之中,我們對兩個基本的演算法K-means和AGNES進行了瞭解和學習,並實用python語言來實現和對比兩者的效果。結果表明,從大資料量的運算來講,K-means快很多,但AGNES對資料的適用性更高。
在模式識別課程上,學習到了很多有關的人工智慧和大資料分析等的知識,可以說獲益匪淺。經過動手做實驗,更加加深了我對聚類方法的理解,有助於以後更加深入的學習。首先,我要感謝老師耐心細緻的授課,對基本概念的講解十分簡明易懂,採用的方式也讓人容易理解。其次,感謝我的組員同學們對我實驗期間的幫助,共同進步,共同提高。