聚类的几种算法简述

聚类

聚类是无监督学习中应用最广泛的算法。聚类会将数据集中的样本划分为若干个不同的子集,每个子集称为一个簇。

原型聚类

原型聚类假设聚类结构能通过一组原型刻画。通常情况下,该类算法先对原型进行初始化,然后对原型进行迭代更新求解。

k均值算法(k-means)

该算法的目的是最小化每个簇中每个样本到对应原型的距离。

但是直接求解的话会是一个NP难问题,较难求解,因此我们仍然采用迭代更新的贪心算法来求解可能的最近似解。值得注意的是,这样并不一定能求得最优解。

其算法大致思想如下:

  1. 初始化簇的数目K和一组原型向量(从样本中随机选择K个)
  2. 迭代每个样本和原型向量,为每个样本找到其距离最近的簇,并将其划为到对应的簇中。
  3. 重新计算每个簇对应的原型向量
  4. 检查新的原型向量和原来的原型向量是否相等,如果全部更新就停止迭代,即找到了最近似解。

python代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 10 15:17:10 2021

@author: mw530
"""
import numpy as np
import pandas as pd

class ProBasedClusterin:
clusterNum = 3
data = None
dataLen = 0
clusterVector = []
clusterSplit = []

def __init__(self, data, clusterNum = 3):
self.clusterNum = clusterNum
self.data = data
self.dataLen = data.shape[0]
randArray = self.uniqueRandInt(clusterNum, 0, self.data.shape[0])
for i in range(clusterNum):
self.clusterVector.append(data[randArray[i]])
self.clusterSplit.append([])

def uniqueRandInt(self, num, low, high):
randArray = []
currNum = 0
while currNum < num:
randInt = np.random.randint(low, high)
if randInt not in randArray:
randArray.append(randInt)
currNum += 1
return randArray

def eucDistance(self, x, y):
return np.sqrt(np.sum((x - y)**2))

def train(self):
vectorChangeNum = -1
cycle = 0
while vectorChangeNum != 0:
cycle += 1
vectorChangeNum = 0
for i in range(self.clusterNum):
self.clusterSplit[i] = []
# 将每个样本分到对应的簇
for i in range(self.dataLen):
minDis = 9999999
clusterIndex = -1
for j in range(self.clusterNum):
dis = self.eucDistance(self.data[i], self.clusterVector[j])
if(dis < minDis):
minDis = dis
clusterIndex = j
self.clusterSplit[clusterIndex].append(self.data[i])
# print(self.clusterSplit)
print("##############################")
#计算每个簇的新原型向量
for i in range(self.clusterNum):
vector = np.mean(self.clusterSplit[i], axis=0)
if not (vector == self.clusterVector[i]).all():
vectorChangeNum += 1
self.clusterVector[i] = vector
print("第", cycle, "次遍历", "有", vectorChangeNum, "个原型向量不同")
if vectorChangeNum == 0:
print("找到最佳原型向量,共循环", cycle, "次")
return self.clusterSplit

def loadData(name):
path = './data/' + name
file = open(path, 'r')
csv = pd.read_csv(file)
arr = np.array(csv)
return arr

data = loadData('cetics_game.csv')
# print(data[1:20, :])
clustering = ProBasedClusterin(data)
clusterSplit = clustering.train()
# print(clusterSplit)

学习向量量化(Learning Vector Quantization)

该算法仍然属于原型聚类,因此与K均值相似。也是试图使用一组原型向量来刻画聚类结构。但是该算法假设每个样本有一个标记,学习算法的过程会利用样本的这些监督信息来辅助聚类。

其算法大致思想如下:

  1. 初始化学习率 μ,原型向量的个数K,K个原型向量(从样本中随机选择)
  2. 从样本集随机选择一个样本x,迭代每个原型向量,找到距离x最近的原型向量p。
  3. 根据公式p = p ± μ * (x - p) 更新该原型向量。(如果x与p标记相同为+,否则为-)

注意该算法的迭代一般就人为确定循环次数。

python代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 10 18:34:25 2021

@author: mw530
"""

import numpy as np
import pandas as pd

class LearningVectorQuan:
protoVecNum = 0
eTa = 0.5
data = None
dataLen = 0
labels =None
protoVector = []
protoLabels = []
cycleNum = 1000

def __init__(self, data, labels, protoVecNum = 3, eTa = 0.5, cycleNum = 1000):
self.protoVecNum = protoVecNum
self.eTa = eTa
self.cycleNum = cycleNum
self.data = data
self.labels = labels
self.dataLen = self.data.shape[0]
randInt = self.uniqueRandInt(self.protoVecNum, 0, self.dataLen - 1)
for i in randInt:
self.protoVector.append(self.data[i])
self.protoLabels.append(self.labels[i])

def uniqueRandInt(self, num, low, high):
randArray = []
currNum = 0
while currNum < num:
randInt = np.random.randint(low, high)
if randInt not in randArray:
randArray.append(randInt)
currNum += 1
return randArray

def eucDistance(self, x, y):
return np.sqrt(np.sum((x - y)**2))

def train(self):
for i in range(self.cycleNum):
randIndex = np.random.randint(0, self.dataLen - 1)
randData = self.data[randIndex]
randLabel = self.labels[randIndex][0]
minIndex = -1
minDis = 9999999
for j in range(self.protoVecNum):
dis = self.eucDistance(randData, self.protoVector[j])
if dis < minDis:
minDis = dis
minIndex = j

if randLabel == self.labels[minIndex]:
self.protoVector[minIndex] = self.protoVector[minIndex] + self.eTa * (randData - self.protoVector[minIndex])
else:
self.protoVector[minIndex] = self.protoVector[minIndex] - self.eTa * (randData - self.protoVector[minIndex])
return self.protoVector

def loadData(name):
path = './data/' + name
file = open(path, 'r')
csv = pd.read_csv(file)
arr = np.array(csv)
return arr[:, :-1], arr[:, -1:]

data, labels = loadData('cetics_game.csv')

# print(data, labels)
clustering = LearningVectorQuan(data, labels)
clusterSplit = clustering.train()
print(np.array(clusterSplit).shape)

高斯混合聚类(高斯分布即正态分布)

与以上两种算法不同,该算法使用的概率模型来表达聚类原型。

密度聚类

该算法亦称为“基于密度的聚类”,此类算法假设聚类结构能够通过样本分布的紧密程度确定。并基于可连接样本不断扩展聚类簇以获得最终的聚类结果。

DBSCAN是一种著名的密度聚类算法,它基于一组“邻域”参数(ε,MinPts)来刻画样本分布的紧密程度。给定数据集D,有一下几个概念需要了解。

  • ε-邻域:对于xi∈D,其ε-邻域包括样本集D中与xi距离不大于ε的所有样本。
  • 核心对象:若xi的ε-邻域中至少包含MinPts个样本,则xi为一个核心对象。
  • 密度直达:若xi位于xj的ε-邻域中,且xj是一个核心对象,则称xi由xj密度直达。
  • 密度可达:对于xi和xj,若存在样本序列p1, p2, p3, … , pn,其中p1 = xi, pn = xj, 且pi+1由pi密度直达,则称xj由xi密度直达。
  • 密度相连:对xi与xj,若存在xk使得xi与xj均由xk密度可达,则称xi与xj密度相连。

该算法的核心思想如下:

  1. 算法先根据给定的邻域参数(ε, MinPts)找出所有的核心对象
  2. 以任一核心对象为出发点,找出由其密度可达的样本生成聚类簇,直到所有核心对象均被访问过。

层次聚类

层次聚类(hierarchical clustering)试图在不同层次上对数据集进行划分,从而形成树状的聚类结构。数据集的划分可以采用自底向上的聚类策略,也可以采用自顶而下的分拆策略。

AGNES是以这种采用自底向上聚类策略的层次聚类算法。

其算法思想如下:

  1. 它先将数据集中的每个样本看作一个初始聚类簇。
  2. 然后再算法运行的每一步中找出距离最近的两个聚类进行合并,该过程不断重复,直到达到预设的聚类簇个数。

注意:以上的代码都是自己手写的,可能会有不足。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :