DBSCAN,K-means,系统聚类法实现
DBSCAN
DBSCAN基本介绍
DBSCAN伪代码
DBSCAN评价
优点:
无需输入类别数𝑘,可适用于任意形状的聚类簇;
对噪声、异常点不敏感,能够识别出噪声点,不依赖于初始类中心的选取。
缺点:样本集密度不均、聚类间距差较大时,聚类结果较差;样本集较大时,收敛时间较长; 对于参数𝜀和𝑀𝑖𝑛𝑃𝑡𝑠,调参难度较大,不同参数组合对聚类结果影响较大;对于高维数据,点之间极为稀疏,密度很难定义
DBSCAN Matlab实现
function [ result ] = DBSCAN( sample, neighbor_range, MinPts )
% 每一行为一个向量
result = [];
% 存放核心对象集
core = [];
amount = size(sample, 1);
% 记录每两个节点是否邻近
record = zeros(amount);
for i = 1:amount
temp = 0;
for j = 1:amount
if( j~=i && pdist(sample([i,j],:)) <= neighbor_range)
temp = temp + 1;
record(i, j) = 1;
end
end
if( temp >= MinPts)
core = [core i];
end
end
core
% 聚类簇数
cluster = 0;
% notaccess记录该节点是否被访问过
notaccess = ones(amount, 1);
while(~isempty(core))
index = randi(size(core, 1));
% 初始化队列
queue = core(index);
% 从核心对象集中删除该对象
core(index) = [];
notaccess(queue) = 0;
temp_cluster = [];
% queue
fprintf('\n');
while(~isempty(queue))
temp = queue(1);
temp_cluster = [temp_cluster temp];
queue(1) = [];
if(sum(record(temp, :)) >= MinPts)
for i = 1:amount
if(record(temp, i) && notaccess(i))
queue = [queue i];
notaccess(i) = 0;
if(find(core, i))
core(core == i) = [];
end
end
end
end
% queue
% fprintf('\n');
% temp_cluster
% fprintf('\n');
end
cluster = cluster + 1;
result = [result; temp_cluster];
end
% cluster
% result
end
测试用例
A=[0 2;1 1;1 2;1 3;2 2;5 5;5 4;5 6;4 5;6 5];
DBSCAN(A,1,4)
DBSCAN Python实现
import numpy as np
import queue
import random as rd
def DBSCAN(sample, neighbor_range, Minpts):
amount = sample.shape[0]
record = np.mat(np.zeros([amount, amount], dtype=int))
core = []
for i in range(0, amount):
temp = 0
for j in range(0, amount):
if j == i or np.sqrt(np.sum(np.square(sample[i] - sample[j]))) > neighbor_range:
# print('fail')
# print(str(i) + " " + str(j))
# print(np.sqrt(np.sum(np.square(sample[i] - sample[j]))))
continue
temp = temp + 1
# print('success')
# print(np.sqrt(np.sum(np.square(sample[i] - sample[j]))))
record[i, j] = 1
if temp >= Minpts:
core.append(i)
print(core)
print(record)
cluster = 0
notaccess = [1] * amount
while core:
index = rd.randint(0, len(core) - 1)
dateque = queue.Queue()
dateque .put(core[index])
del core[index]
notaccess[index] = 0
temp_cluster = []
while not dateque .empty():
temp = dateque .get()
temp_cluster.append(temp)
if np.sum(record[temp]) >= Minpts:
for i in range(0, amount):
if record[temp, i] and notaccess[temp] == 1:
dateque.put(i)
notaccess[i] = 0
if i in core:
core.remove(i)
cluster = cluster + 1
print("The cluster" + str(cluster) + ":")
print(temp_cluster)
A = [[0, 2], [1, 1], [1, 2], [1, 3], [2, 2], [5, 5], [5, 4], [5, 6], [4, 5], [6, 5]]
B = np.mat(A)
DBSCAN(B, 1, 3)
层次聚类法
方法介绍
系统聚类法又常被称为谱系聚类法或分层聚类法。在聚类之前,先将每一个样本或变量都各自看成一类,计算样本之间距离,并以样本之间距离定义类之间的距离。先选择距离最近的一对合并成一个新类,计算新类与其他类之间的距离,再将距离最近的两类合并,如此持续做下去,这样就每次减少一类,直至所有的样本或变量都归为一大类为止,最后可以根据聚类的结果画出一张聚类的树形图,可以直观的反映整个聚类过程。
层次聚类法思路
基于最长距离法:
(1)置 t=0,规定样本之间的距离,此时各样本自成一类,即
c
p
c_p
cp 类与
c
q
c_q
cq 类的距离为
D
p
q
=
d
p
q
D_{pq} = d_{pq}
Dpq=dpq,计算类之间距离的对称阵,记为
R
(
t
)
R^{(t)}
R(t)
(2)选择矩阵
R
(
t
)
R^{(t)}
R(t) 中的最小值元素,假设为
d
p
q
d_{pq}
dpq ,将对应的类
c
p
c_p
cp 类与
c
q
c_q
cq 合并成一类,记为
c
m
=
{
x
∣
x
∈
c
p
,
o
r
,
x
∈
c
q
}
c_m=\{x|x\in c_p,or,x\in c_q\}
cm={x∣x∈cp,or,x∈cq} 。
(3)计算新类
c
m
c_m
cm 与其他类之间的距离
D
m
k
=
min
i
∈
c
m
,
j
∈
c
k
d
i
j
=
m
i
n
(
D
p
k
,
D
q
k
)
D_{mk}=\min_{i\in c_m,j\in c_k}d_{ij}=min(D_{pk},D_{qk})
Dmk=i∈cm,j∈ckmindij=min(Dpk,Dqk)
将
R
(
t
)
R^{(t)}
R(t)中 p,q 行, p,q列分别合并为一个新行新列,新行新列对应为类
c
m
c_m
cm ,所得到的矩阵记为
R
(
t
+
1
)
R^{(t+1)}
R(t+1) 。
(4)若全部样本已聚集成一个类,则停止算法;否则 t=t+1 ,转(2)。
算法优缺点
优点:适用于任意形状和任意属性的数据集,不需要预先设定聚类数,可以发现类的层次关系。
缺点:计算复杂度太高, 𝑂(𝑛^3),一步错步步错。
一种实现方式
为了减少所需要的空间,这里并不使用一个距离矩阵来记录两两距离,而且即使使用矩阵,矩阵在更新时所需要做的变换也不简单。所以我干脆用字典 distance: dict来保存两两距离,字典的键为数值对
(
i
,
j
)
(i,j)
(i,j),并且有
i
<
j
i<j
i<j。 另外用remain_cluster 记录可用的点,删除合并的簇,添加新的簇,都需要用到这个集合。
更新的过程以一个例子来讲,假如有1,2,3,4,5这五个点,然后我们发现1和4最近这时候我们分四步进行:
- 在 remain_cluster 中去除1和4
- 利用 remain_cluster计算新的距离(2,6),(3,6),(5,6)不用计算(1,6)和(4,6)因为在第一步我们就将他们删去了
- 将 distance 中键值含有1或4的键值对删去
- 在 remain_cluster 中添加簇6(6为簇1和簇4合并)
代码实现
# Hierarchical clustering method
import numpy as np
def Hc_method(data: list):
data = np.array(data)
amount = len(data)
distance = {}
remain_cluster = set(range(amount))
cluster_value = [0] * (amount - 1)
for i in range(amount - 1):
for j in range(i + 1, amount):
# Euclidean Distance
distance[(i, j)] = np.sqrt(np.sum(np.square(data[i] - data[j])))
for i in range(amount - 1):
cluster_value[i] = min(distance.values())
index = [k for k, v in distance.items() if v == cluster_value[i]][0]
print("Merge " + str(i + 1) + ":")
print(index)
print("Distance:" + str(cluster_value[i]))
del distance[index]
remain_cluster.remove(index[0])
remain_cluster.remove(index[1])
# COMplete method(最长距离法)
for items in remain_cluster:
if items <= index[0] and items <= index[1]:
distance[(items, amount + i)] = max(distance[(items, index[0])], distance[(items, index[1])])
del distance[(items, index[0])]
del distance[(items, index[1])]
elif index[0] < items <= index[1]:
distance[(items, amount + i)] = max(distance[(index[0], items)], distance[(items, index[1])])
del distance[(index[0], items)]
del distance[(items, index[1])]
else:
distance[(items, amount + i)] = max(distance[(index[0], items)], distance[(index[1], items)])
del distance[(index[0], items)]
del distance[(index[1], items)]
remain_cluster.add(amount + i)
record = set()
for items in distance:
if items[0] == index[0] or items[0] == index[1] or items[1] == index[0] or items[1] == index[1]:
record.add(items)
for elements in record:
del distance[elements]
print("cluster {0}: {1} + {2}".format(str(amount), str(index[0]), str(index[1])))
test_data = [[1, 2], [2, 3], [3, 4], [4, 3], [2, 5], [6, 1], [5, 5], [4, 7]]
Hc_method(test_data)
K-means法
基本原理
从直观上:离得越近的数据越相似,理应被划分为同一簇 ——数据间的相似性与它们之间的欧式距离成反比
K-means聚类是简单无监督学习算法,用于已知类数𝒌。
给定数据集
{
x
l
}
l
=
1
n
\{x_l\}^n_{l=1}
{xl}l=1n,要将其划分为𝑘个簇,𝑘个簇中心记为
c
i
c_i
ci (“簇均值向量”),𝑖=1,2,…,𝑘。希望数据到它所属类中心的距离越小越好,定义如下的损失函数:
J
=
min
{
c
i
,
i
=
1
,
2
,
⋯
,
k
}
∑
i
=
1
k
∑
x
∈
c
i
∥
x
−
c
i
∥
2
2
J=\min_{\{c_i,i=1,2,\cdots,k\}}\sum_{i=1}^k\sum_{x\in c_i}\|x-c_i\|^2_2
J={ci,i=1,2,⋯,k}mini=1∑kx∈ci∑∥x−ci∥22
𝐽值在一定程度上刻画了簇内样本围绕簇均值向量的紧密程度, 𝐽 值越小,则簇内样本相似度越高。
K−means算法步骤(给定聚类数𝑘 )
𝑠𝑡𝑒𝑝1:初始化,给定类数 𝑘,置 𝑗=0,从样本向量中选取任意𝑘个向量 𝒄 1 𝑗 , 𝒄 2 𝑗 … 𝒄 𝑘 𝑗 𝒄_1^𝑗,𝒄_2^𝑗…𝒄_𝑘^𝑗 c1j,c2j…ckj作为聚类中心,并记中心为 𝒄 𝑖 𝑗 𝒄_𝑖^𝑗 cij 的聚类块为 𝐶 𝑖 𝑗 𝐶_𝑖^𝑗 Cij。
𝑠𝑡𝑒𝑝2:将向量样本按最短欧式距离归入𝑘聚类块。例
𝑥
𝑙
𝑥_𝑙
xl 到
c
𝑖
𝑗
c_𝑖^𝑗
cij:
‖
𝒙
𝑙
−
𝒄
𝑖
𝑗
‖
2
=
min
1
≤
𝑡
≤
𝑘
∥
𝒙
𝑙
−
𝒄
𝑡
𝑗
∥
2
‖𝒙_𝑙−𝒄_𝑖^𝑗 ‖_2=\min_{1≤𝑡≤𝑘}\|𝒙_𝑙−𝒄_𝑡^𝑗 \|_2
‖xl−cij‖2=1≤t≤kmin∥xl−ctj∥2
𝑠𝑡𝑒𝑝3:调整聚类中心。
c
i
j
+
1
=
∑
x
t
∈
c
i
j
x
t
N
i
c_i^{j+1}=\frac{\sum_{x_t\in c_i^j }x_t}{N_i}
cij+1=Ni∑xt∈cijxt
,
N
i
N_i
Ni 为聚类块
𝐶
𝑖
𝑗
𝐶_𝑖^𝑗
Cij 的向量数。
𝑠𝑡𝑒𝑝4:若𝑠𝑡𝑒𝑝2聚类中心不再明显变换则终止,否则𝑗=𝑗+1,转𝑠𝑡𝑒𝑝2。
终止迭代目标函数: J = min { c i , i = 1 , 2 , ⋯ , k } ∑ i = 1 k ∑ x ∈ c i ∥ x − c i ∥ 2 2 J=\min_{\{c_i,i=1,2,\cdots,k\}}\sum_{i=1}^k\sum_{x\in c_i}\|x-c_i\|^2_2 J=min{ci,i=1,2,⋯,k}∑i=1k∑x∈ci∥x−ci∥22
python实现
import numpy as np
def my_kmeans(data: list, k: int):
amount = len(data)
data = np.array(data)
index = [0] * amount
iter = 0
if k > amount:
print("Error")
return
cluster_center = data[0:k]
shutdown = False
target = 1000000
while not shutdown and iter < 10000:
iter += 1
print(iter)
internal_amount = [0] * k
new_target = 0
for i in range(amount):
temp = [0] * k
for j in range(k):
temp[j] = np.sum(np.square(data[i] - cluster_center[j]))
index[i] = temp.index(min(temp))
internal_amount[index[i]] += 1
new_cluster = np.zeros([k, len(data[0])])
for i in range(amount):
new_cluster[index[i]] += data[i]
for i in range(k):
cluster_center[i] = new_cluster[i] / internal_amount[i]
print(cluster_center)
for i in range(amount):
new_target += np.sum(np.square(data[i] - cluster_center[index[i]]))
print(new_target)
print(index)
if abs(target - new_target)/target < 0.001:
shutdown = True
else:
target = new_target
A = [[0, 2], [5, 5], [1, 2], [1, 3], [2, 2], [1, 1], [5, 4], [5, 6], [4, 5], [6, 5]]
my_kmeans(A, 2)