以前写过一篇kmeans算法的博客,没有用numpy,可视化也不完美,今天来对比一下numpy对kmeans的影响
k均值聚类算法是一种迭代求解的聚类分析算法,其步骤是随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
1.随机取1000个点,其中随机5个为中心点
2.求其余995个点与每个中心点的距离
3.距离最小的,将此点和对应的中心点归为一类
4.类中求平均值作为新的中心点
5.如果新中心点和旧中心点的误差小于某个阈值,停止,否则重复以上操作
#引入需要的模块
import numpy as np
import matplotlib.pyplot as plt
import random
import time #检验效率,时间少的效率高
figure = plt.figure()
#用numpy的方法
subplot1 = figure.add_subplot(1,2,1)
class KMeans():
def __init__(self, k=1):
'''
:param k: k代表分类数
'''
self.__k = k
self.__data = None # 存放原始数据
self.__pointCenter = None # 存放中心点,第一次获得的中心点通过随机方式在__data里随机出来
self.__result = [] # 存放分类结果
for i in range(k):
self.__result.append([]) # [[],[],[],[],[]]
pass
pass
def fit(self, data, threshold, times=50000):
'''
进行模型训练
:param data: 训练数据
:param threshold: 阈值,退出条件
:return:
'''
self.__data = data
self.randomCenter()
# print(self.__pointCenter)
centerDistance = self.calPointCenterDistance(self.__pointCenter, self.__data)
# 对原始数据进行分类,将每个点分到离它最近的中心点
i = 0
for temp in centerDistance:
index = np.argmin(temp)
self.__result[index].append(self.__data[i])
i += 1
pass
# 打印分类结果
# print(self.__result)
oldCenterPoint = self.__pointCenter
newCenterPoint = self.calNewPointCenter(self.__result)
while np.sum(np.sum((oldCenterPoint - newCenterPoint)**2, axis=1)**0.5)/self.__k > threshold:
times -= 1
result = []
for i in range(self.__k):
result.append([])
pass
# 保存上次的中心点
oldCenterPoint = newCenterPoint
centerDistance = self.calPointCenterDistance(newCenterPoint, self.__data)
# 对原始数据进行分类,将每个点分到离它最近的中心点
i = 0
for temp in centerDistance:
index = np.argmin(temp)
result[index].append(self.__data[i]) # result = [[[10,20]]]
i += 1
pass
newCenterPoint = self.calNewPointCenter(result)
self.__result = result
pass
self.__pointCenter = newCenterPoint
return newCenterPoint, self.__result
pass
def calPointCenterDistance(self, center, data):
'''
计算每个点和每个中心点之间的距离
:return:
'''
centerDistance = []
flag = False
for temp in data:
centerDistance.append([np.sum((center - temp) ** 2, axis=1) ** 0.5])
pass
# print(centerDistance)
return np.array(centerDistance)
pass
def calNewPointCenter(self, result):
'''
计算新的中心点
:param result:
:return:
'''
newCenterPoint = None
flag = False
for temp in result:
# 转置
temps = np.array(temp)
point = np.mean(temps, axis=0)
if not flag:
newCenterPoint = np.array([point])
flag = True
pass
else:
newCenterPoint = np.vstack((newCenterPoint, point))
pass
# print(newCenterPoint)
return newCenterPoint
pass
def randomCenter(self):
'''
从原始的__data里随机出最开始进行计算的k个中心点
:return:
'''
if not self.__pointCenter:
index = random.randint(0, len(self.__data) - 1)
self.__pointCenter = np.array([self.__data[index]])
pass
while len(self.__pointCenter) < self.__k:
# 随机一个索引
index = random.randint(0, len(self.__data) - 1)
# 判断中心点是否重复,如果不重复,加入中心点列表
if self.__data[index] not in self.__pointCenter:
self.__pointCenter = np.vstack((self.__pointCenter, self.__data[index]))
pass
pass
pass
pass
if __name__ == "__main__":
# 原始数据改为nunmpy结构
data = np.random.randint(0, 100, 20000).reshape(10000, 2)
# print(data)
startTime = time.time()
kmeans = KMeans(k=5)
centerPoint, result = kmeans.fit(data, 0.0001)
print('numpy方法的kmeans算法的时间',time.time() - startTime)
# print(centerPoint)
i = 0
tempx = []
tempy = []
color = []
for temp in result:
temps = [[temp[x][i] for x in range(len(temp))] for i in range(len(temp[0]))]
color += [i] * len(temps[0])
tempx += temps[0]
tempy += temps[1]
i += 2
pass
subplot1.scatter(tempx, tempy, c=color, s=30)
#不用numpy的方法
subplot2 = figure.add_subplot(1,2,2)
class KMeans():
def __init__(self, k=1): #k代表分类数,默认为1,可以改变
self.__k = k
self.__data = [] # 存放原始数据
self.__pointCenter = [] # 存放中心点,第一次获得的中心点通过随机方式在__data里随机出来
self.__result = [] #类里的数据
for i in range(k):
self.__result.append([]) # [[],[],[],[],[]] #有几个类添加几个列表
pass
pass
#进行模型训练
def fit(self, data, threshold, times=500): #data: 训练数据 threshold: 阈值,退出条件
self.__data = data
self.randomCenter() #调用此方法随机出最开始的中心点
# print(self.__pointCenter) #输出中心点列表,无实意
centerDistance = self.calPointCenterDistance(self.__pointCenter, self.__data) #计算每个点和每个中心点的距离
# 对原始数据进行分类,将每个点分到离它最近的中心点
i = 0 #从第一个点开始
for temp in centerDistance:
index = temp.index(min(temp))
self.__result[index].append(self.__data[i])
i += 1
pass
# print(self.__result) # 打印分类结果
oldCenterPoint = self.__pointCenter #将中心点赋值给它
newCenterPoint = self.calNewPointCenter(self.__result) #调用此方发,计算新的中心点
#判断:如果前后两次中心点之间的距离是否小于某个阈值
while self.calCenterToCenterDistance(oldCenterPoint, newCenterPoint) > threshold: #如果大于,重复while语句的操作,次数在times次内
times -= 1
result = []
for i in range(self.__k):
result.append([])
pass
oldCenterPoint = newCenterPoint # 保存上次的中心点
centerDistance = self.calPointCenterDistance(newCenterPoint, self.__data) #计算新中心点和个点之间的距离
# 对原始数据进行分类,将每个点分到离它最近的中心点
i = 0
for temp in centerDistance:
index = temp.index(min(temp))
result[index].append(self.__data[i]) # result = [[[10,20]]]
i += 1
pass
newCenterPoint = self.calNewPointCenter(result)
# print(self.calCenterToCenterDistance(oldCenterPoint, newCenterPoint))
self.__result = result
pass
self.__pointCenter = newCenterPoint
return newCenterPoint, self.__result #将新中心点和所有类中的点返回
pass
#计算两次中心点之间的距离,求和求均值
def calCenterToCenterDistance(self, old, new): #old:上次的中心点 new:新计算的中心点
total = 0
for point1, point2 in zip (old, new):
total += self.distance(point1, point2) #有几个中心点求几次
pass
return total / len(old) #返回前后两次中心点距离之和的平均值
pass
#计算每个点和每个中心点之间的距离
def calPointCenterDistance(self, center, data):
centerDistance = []
for temp in data:
centerDistance.append([self.distance(temp, point) for point in center]) #调用distance方法求各点到各中心点的距离
pass
# print(centerDistance)
return centerDistance #返回距离
pass
#计算新的中心点
def calNewPointCenter(self, result):
newCenterPoint = []
for temp in result:
# 转置:将每个点与所有中心点的距离列表 转置成 每个中心点与同一类的所有点的列表
temps = [[temp[x][i] for x in range(len(temp)) ] for i in range(len(temp[0]))]
point = []
for t in temps:
# 对每个维度(类)求和,取平均即为新的中心点
point.append(sum(t)/len(t)) # mean
pass
newCenterPoint.append(point)
pass
# print(newCenterPoint)
return newCenterPoint #返回新中心点的列表
pass
#计算两个点之间的距离,支持任意维度,欧式距离
def distance(self, pointer1, pointer2):
distance = (sum([(x1 - x2)**2 for x1, x2 in zip(pointer1, pointer2)]))**0.5 #公式,几个未知数和集合都可以,支持任意维度,这里是二维的
return distance #返回距离
pass
#从原始的__data里随机出最开始进行计算的k个中心点
def randomCenter(self):
while len(self.__pointCenter) < self.__k:
# 随机一个索引 所有点中随机取几个中心点
index = random.randint(0, len(self.__data) - 1)
# 判断中心点是否重复,如果不重复,加入中心点列表 避免因重复导致的中心点个数不够
if self.__data[index] not in self.__pointCenter:
self.__pointCenter.append(self.__data[index])
pass
pass
pass
pass
if __name__ == "__main__":
data = [[random.randint(1, 100), random.randint(1, 100)] for i in range(10000)] #随机10000个点
start = time.time()
kmeans = KMeans(k=5) #几个中心点(几个类)
centerPoint, result = kmeans.fit(data, 0.0001) #调用fit方法,参数中的0.0001为阈值
print('普通方法的kmeans算法的时间',time.time()-start)
# print(centerPoint)
#界面可视化,可以清晰的看到散点图,需引入 matplotlib模块
i = 0
tempx = []
tempy = []
color = []
for temp in result: #遍历类中的点,用同一种颜色,需转置
temps = [[temp[x][i] for x in range(len(temp))] for i in range(len(temp[0]))]
color += [i] * len(temps[0])
tempx += temps[0]
tempy += temps[1]
i += 2
pass
subplot2.scatter(tempx, tempy, c=color, s=30) #x轴,y轴,点的颜色,点的大小
plt.show()
一定要把程序中除时间外的print语句删掉,因为numpy中转码花费的时间很多,影响结果
第一种情况(绝大数情况):普通版本效率高
2.numpy版本效率高
用的同一个画布两张图,都是获取10000个点,结果存在偶然性,因为随机数的存在,两种方法获取的随机数不同效率也不同,有的时候用numpy的效率高,有的时候相反。