python手写聚类算法:Kmeans&DBscan
算法思路以及步骤介绍
首先,我们分别介绍一下Kmeans算法以及DBSCAN算法。
Kmeans算法步骤:首先先随机的选择K个点(这里的K是超参数),这K个点作为中心点,对于剩下的所有的点,计算剩下的点和这三个点的距离,距离中最小的,认为属于这个类。在更新完一遍之后,计算类中的均值向量作为新的中心,再次重复上面的步骤,直到类的中心不变。主要的思路就是:每一类一定有一个中心,这个类中的对象一定离自己所属的类的中心最近,在和所有的中心的距离中。
DBSCAN的思路是通过定义了核心对象和密度直达来进行聚类(优点像感染式的聚类),其中核心对象指的是,此对象的较近的距离内有足够多的样本的点,这个距离和点的个数都是我们来决定的。这个域里面的对象都叫做密度直达的点,对于这些点,如果也是一个核心对象的话,就把它们的领域也加入到这个类里面,以此类推,直到所有的核心对象都已经考虑过了,停止算法。主要的思路就是,如果我们是一类,我们一定离的比较足够近,而如果你和其他的一个足够近,那么说明可能大家都是一类。
手写代码
Kmeans
class Kmeans:
def __init__(self):
pass
def distance(self, Xi):
import numpy as np
dist = []
for k in self.centroid:
dist.append(np.linalg.norm(k - Xi, ord=self.p))
dist0 = list(enumerate(dist))
near = sorted(dist0, key=lambda x: x[1])[0][0]
return near
def predict(self, Xi):
import numpy as np
num = Xi.shape[0]
dist = []
for i in range(num):
dist.append([])
for k in self.centroid:
dist[i].append(np.linalg.norm(k - Xi, ord=self.p))
dist0 = list(enumerate(dist[i]))
near = sorted(dist0, key=lambda x: x[1])[0][0]
dist[i] = near
return dist
def fit(self, X, k=3, p=1):
import random
import numpy as np
import copy
self.k = k
self.p = p
self.centroid = []
self.data = X
num, col = X.shape
pick = random.sample(range(0, num), self.k)
for i in pick:
self.centroid.append(X[i, :])
flag = True
while flag:
classlist = []
for i in range(self.k):
classlist.append([])
centroid = self.centroid.copy()
for i in range(num):
near = self.distance(self.data[i, :])
classlist[near].append(self.data[i, :])
for i in range(self.k):
self.centroid[i] = np.average(classlist[i], axis=0)
if (np.array(self.centroid) - np.array(centroid)).all() == 0:
flag = False
return self.centroid, classlist
手写DBscan
import numpy as np
def fit(X,eta,num):
coreindex = {}
for i in range(X.shape[0]):
dist = []
for k in range(X.shape[0]):
if i != k:
distance = np.linalg.norm(X[i,:]-X[k,:], ord=2)
if distance <= eta:
dist.append(k)
if len(dist)>=num:
coreindex[i]=dist
return coreindex
def train(coreindex):
import random
classindex = 0
classdict = {}
index = list(coreindex.keys())
while len(index)!=0:
sample = random.randint(0,len(index)-1)
sample = index[sample]
classdict[classindex] = coreindex[sample]
classdict[classindex].append(sample)
index.remove(sample)
for i in classdict[classindex]:
if i in index:
index.remove(i)
for j in coreindex[i]:
if j in classdict[classindex]:
pass
else:
classdict[classindex].append(j)
classindex += 1
return classdict
关于手写程序的说明
自己在设计程序的时候,读懂几个地方,就很好读懂全部了。首先在Kmeans里面,centroid表示的是中心的点,每一次都会留一下,并且更新一遍,停止的条件就是这个更新前后不变了,classlist = []这个是一个二维的列表,第一个维度表示的是第几类,第二个维度表示的是样本(我直接把横纵坐标都放进去了),其实更好的方式是只放样本的索引进去。这也是我们最后会返回的结果。
在DBSCAN算法中,我们设计了coreindex = {},一个字典,键用来放所有的核心对象,值用来放核心对象的邻域,classdict,这个也是一个字典,键表示类别的编号,值是样本的索引。其实设计好这些之后,其余的按照程序的顺序,就很好很自然的可以写出来和读懂。感觉了解程序最需要知道的就是输入输出,知道这些设计之后,基本就是编写或者使用就很舒服了。
|