机器学习——K-Means++
K-Means
K-Means是一种常见的非监督学习算法。
实现K-Means算法的步骤如下:
- step1:首先将N个样本数据进行特征提取,获得N个特征向量;
- step2:假定你要将样本数据分为K类;
- step3:从N个特征向量中随机取出K个数据,这K个数据将是初始的聚类中心点;
- step4:将所有特征向量和K个数据计算距离(欧式距离,曼哈顿距离…),每个特征向量和K个聚类中心点能够得到K个距离(K个距离是一个列表),找到该距离列表中的最小值的索引i,就可以将该特征向量分配到第i类;
- step5:经过step4将所有特征向量都分配了类别,然后对每个聚类中的特征向量求平均值,用得到的均值替换原来该类的聚类中心点;
- step6:重复step4和step5步骤,直到停止。
- 停止条件:
- 数据分类不变化;
- K个聚类中心点不变化;
- 达到设定的迭代次数。
K-Means——Demo
以iris数据集为例,手撕k-means算法如下:
from sklearn.datasets import load_iris
import numpy as np
from collections import Counter
train_data = load_iris()['data']
#print(train_data.shape)
# 数据量
sample_nums = len(train_data)
#print(sample_nums)
# 计算类别
def get_classes(train_data,centres):
# distances
distances = []
for i in train_data:
distance = np.sqrt(np.sum((i-centres)**2,1))
distances.append(distance)
distances = np.array(distances)
# print(distances)
# 求每个样本特征与聚类中心距离中的最小距离的索引,相同的索引就是同一类
distances_index = np.argmin(distances,1)
return distances_index
# K-means
def K_means(train_data,centres,K=3,epochs=10):
# 分类结果
sample_classes = []
# 更新聚类中心
for epoch in range(epochs):
# 样本特征的聚类结果
distance_index = get_classes(train_data,centres)
# 新的聚类中心
centres = np.zeros((K, train_data.shape[1]))
# print(distance_index)
# 将各自类别中的样本特征求和
for i,j in enumerate(distance_index):
centres[j] += train_data[i]
# 各自类别中样本数量
class_sample_nums = Counter(distance_index)
for i in range(K):
centres[i] = centres[i]/class_sample_nums[i]
# 分类结果
sample_classes = distance_index
return sample_classes
if __name__ == '__main__':
import matplotlib.pyplot as plt
for num in range(4):
# 聚类中心
centres = []
# 随机获得k个聚类中心
random_index = np.random.rand(1, 3) * sample_nums
random_index = random_index.astype(np.uint8)
for i in random_index[0]:
centres.append(train_data[i])
centres = np.array(centres)
sample_classes = K_means(train_data,centres)
plt.subplot(2,2,num+1)
colors = ['red','blue','green']
for i,j in enumerate(sample_classes):
x,y = train_data[i][0],train_data[i][1]
plt.scatter(x,y,c=colors[j])
plt.show()
下图是4次的聚类结果。
可以看到每次的聚类结果都不一样,有时候会出现很大差别。这是因为,聚类时,初始化的聚类中心对之后的聚类影响很大;而K-Means采用随机选择样本点作为聚类中心,存在很大风险;所以,为了减小风险,出现了一种方法叫做K-Means++。
K-Means++
K-Means++算法事实上只改变了K-Means算法中的第三步,将随机选择K个初始聚类中心,变为按照一定规则选择K个初始聚类中心。
规则如下:
- step1:第1个初始聚类中心是从样本特征中随机选择的;
- step2:第i个初始聚类中心:
- 1.所有样本特征和已有聚类中心求距离,每个样本都会产生i-1个距离值;
- 2.从i-1个距离值中选择一个最小值;
- 3.所有样本产生的距离都按照2的方法选择,将这些选择出来的距离组合在一起,设为矩阵A;
- 4.产生第i个初始聚类中心:
- 方法一(找样本中距离已有聚类中心最远的特征点作为新的聚类中心):
- 找到A中最大值的索引,该索引对应的样本特征就为新的聚类中心。(因为A保存的数据就是各个样本到已有聚类中心的最小距离;在这样的情况下,A中的最大值所对应的样本特征基本可以认为是距离所有已有聚类中心最远的特征点)
- 方法二(轮盘法):
- 首先,将矩阵A归一化;
- 然后,随机生成一个(0,1)的数据P;
- 最后,计算P在A中的位置——索引,该索引对应的样本特征就为新的聚类中心。
- 方法一(找样本中距离已有聚类中心最远的特征点作为新的聚类中心):
# 轮盘法
P_distances = distances / np.sum(distances) # 计算每个距离值,在真个“距离轮盘”上的占比
# 随机产生一个轮盘值
random_num = np.random.rand() # 假设轮盘转动后的结果
for i, j in enumerate(P_distances):
if random_num > 0:
# 假设的结果应该落在那个距离占比上
random_num -= j
else:
centres.append(features[i])
break
- step3:一直循环step2,直到i==K停止。
K-Means++——Demo
代码中K_means方法是前面代码中已有的方法
from K_means import train_data,K_means,np
def get_distance(centre,features):
'''
:param centre: 样本中心点的列表
:param sample: 样本特征点列表
:return: 距离矩阵
'''
distances = []
for i in features:
distance = np.sqrt(np.sum((centre-i)**2,1)).T
distances.append(distance)
# 取每个样本对应多个距离的最小距离
distances = np.min(distances,1)
return distances
# 获取聚类中心
def get_centres(K,features,feature_num=150):
# 聚类中心点列表
centres = []
# 产生一个随机数
random_num = int(np.random.rand() * feature_num)
# 随机选择一个点
init_centre = features[random_num]
centres.append(init_centre)
while (1):
if len(centres) >= K:
break
distances = get_distance(centres, features)
# # 方法一:轮盘法
# P_distances = distances / np.sum(distances) # 计算每个距离值,在真个“距离轮盘”上的占比
# # 随机产生一个轮盘值
# random_num = np.random.rand() # 假设轮盘转动后的结果
# for i, j in enumerate(P_distances):
# if random_num > 0:
# # 假设的结果应该落在那个距离占比上
# random_num -= j
# else:
# centres.append(features[i])
# break
# 方法二:最值法
i = np.where(distances==np.max(distances))[0][0]
centres.append(features[i])
return np.array(centres)
if __name__ == '__main__':
import matplotlib.pyplot as plt
for i in range(4):
# 初始化K个聚类中心
centres = get_centres(3,train_data)
# 使用K-means训练的方法
sample_classes = K_means(train_data,centres)
plt.subplot(2,2,i+1)
colors = ['red','blue','green']
for i,j in enumerate(sample_classes):
x,y = train_data[i][0],train_data[i][1]
plt.scatter(x,y,c=colors[j])
plt.show()
结果如下图所示,可以看出聚类结果基本保持一致,所以K-Means++比K-Means聚类准确度高。