优点:
无需输入类别数,可适用于任意形状的聚类簇;
对噪声、异常点不敏感,能够识别出噪声点,不依赖于初始类中心的选取。
缺点:样本集密度不均、聚类间距差较大时,聚类结果较差;样本集较大时,收敛时间较长; 对于参数和,调参难度较大,不同参数组合对聚类结果影响较大;对于高维数据,点之间极为稀疏,密度很难定义
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)
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
更新的过程以一个例子来讲,假如有1,2,3,4,5这五个点,然后我们发现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聚类是简单无监督学习算法,用于已知类数。
给定数据集 { 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
值在一定程度上刻画了簇内样本围绕簇均值向量的紧密程度, 值越小,则簇内样本相似度越高。
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
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)