k-means算法
K-均值聚类算法(k-means clustering algorithm)是一种无监督聚类算法。本文前部分介绍算法原理及优缺点,后面通过Python代码实现一个简版的k-means算法。
优缺点
- 优点:简洁快速,算法的关键在于初始中心的选择和距离度量。
- 缺点:
- K值(聚类的数目)需要事先确定。
- 聚类结果对初始类中心的选取较为敏感。
- 容易陷入局部最优。
- 只能发现球型簇
- 时间复杂度:,其中,t为迭代次数,K为簇的数目,m为记录数,n为维数。
- 空间复杂度:,其中,K为簇的数目,m为记录数,n为维数。
算法流程
-
1.随机给定各簇中心 $ c_1, c_2,…, c_n $
-
2.计算各样本点到簇中心的距离(一般采用欧式距离),将样本点归类到距离最小的簇中,公式如下:
其中,是使目标函数取最小值时的变量值
-
3.更新各簇中心$ c_1, c_2,…, c_n $
上式中,为簇y的样本总数
-
4.重复步骤2、3,直到达到收敛精度即可
停止条件
- 达到预先设置的最大迭代次数。
- 聚类中心不再发生变化。
- 相邻两次聚类结果中差值变化小于某一个阈值。
K值的确定
-
经验法: 在实际工作中,结合业务的场景和需求,来决定几类确定K值
-
肘部法则: 肘部法则通过成本函数来刻画的,其是通过将不同K值的成本函数刻画出来,随着K值的增大,平均畸变程度的改善效果会不断降低。因此。在找出K值增大的过程中,畸变程度下降幅度最大的位置所对应的K较为合理。(注:成本函数为各类的畸变程度之和与其内部成员位置距离的平方和,最优解是成本函数最小化为目标。公式表示为
其中,是第k个质心的位置。 -
规则法:
K-means算法Python实现
下面代码将k-means封装成类,实例化对象后调用fit()和transform()方法实现对数据的训练和转化,talk is checp, show you the code!
import numpy as np
from collections import Counter
import matplotlib.pyplot as plt
class Kmeans():
def __init__(self, n_clusters=8, max_iter=10, tol=1e-4):
self.__n_cluster = n_clusters
self.__max_iter = max_iter
self.__tol = tol
self.__cluster_centers_ = None
self.__n_iter_ = 0
self.__label = None
self.info = ''
@property
def cluster_centers_(self):
return self.__cluster_centers_
@property
def n_iter(self):
return self.__n_iter_
@property
def label(self):
return self.__label
def __generate_cluster_centers(self, data):
"""随机生成类中心"""
m, n = data.shape
col_max_value = data.max(axis=0)
col_min_value = data.min(axis=0)
cluster_centers = np.empty(shape=[self.__n_cluster, n])
for col_index in range(n):
col_value = np.random.uniform(col_min_value[col_index],
col_max_value[col_index],
self.__n_cluster)
cluster_centers[:, col_index] = col_value
return cluster_centers
def __calu_distance(self, sample, cluster_center):
"""
计算一个样本sample到一个聚类中心的距离(欧式距离)
:param sample: 输入的一个样本
:return: 距离
"""
return np.sqrt(np.sum(np.square(cluster_center - sample)))
def __category(self, sample):
"""
计算一个sample所属的簇
:param sample:
:return: 簇索引
"""
distance_list = []
for cluster_center_index in range(self.__n_cluster):
cluster_center = self.__cluster_centers_[cluster_center_index, :]
distance_list.append(self.__calu_distance(sample=sample, cluster_center=cluster_center))
return distance_list.index(min(distance_list))
def __update_cluster_centers(self, data, label):
"""
计算类的中心
:param label:
:return:
"""
cluster_centers = np.empty(shape=[self.__n_cluster, data.shape[1]])
for cluster_index in range(self.__n_cluster):
sample_index_array = np.where(label == cluster_index)
samples = data[sample_index_array]
center = samples.mean(axis=0)
cluster_centers[cluster_index] = center
return cluster_centers
def __is_terminate(self, cluster_centers, label):
"""
是否满足终止条件(1. 达到最大迭代次数; 2. 簇中心不再变化; 3. 两次聚类结果差值小于某一个阈值
:param label:
:return:
"""
diff = self.__calu_diff(label)
if self.__n_iter_ >= self.__max_iter:
self.info = '达到最大迭代次数: ' + str(self.__max_iter) + ',聚类结束。'
return True
if (self.__cluster_centers_ == cluster_centers).all():
self.info = '两次聚类簇中心未发生变化,聚类结束。'
return True
if diff <= self.__tol:
self.info = '聚类结果差值为 {},小于 {},聚类结束。'.format(diff, self.__tol)
return True
self.__label = label
return False
def __calu_diff(self, label):
"""
计算两次聚类结果的差值
:param label: 最近的一次聚类结果
:return: 差值占比
"""
ans = label == self.__label
length = len(ans)
ans_dict = Counter(ans)
diff_num = ans_dict.get(False)
if diff_num == None:
return 0
else:
return diff_num / length
def fit(self, data):
"""
将数据分簇,并计算簇的中心
:param data: 数据
:return:
"""
label = np.array([np.nan] * data.shape[0])
self.__cluster_centers_ = self.__generate_cluster_centers(data=data)
while True:
# 将每个样本划分到最近的簇中
for sample_index in range(data.shape[0]):
category = self.__category(sample=data[sample_index])
label[sample_index] = category
# 更新簇中心
cluster_centers = self.__update_cluster_centers(data=data, label=label)
self.__n_iter_ += 1
# 检查是否满足终止条件
if self.__is_terminate(cluster_centers=cluster_centers, label=label):
self.__cluster_centers_ = cluster_centers
self.print_info()
break
self.__cluster_centers_ = cluster_centers
self.__label = label.copy()
def transform(self, data):
"""
计算每个数据到各聚类中心的距离
:param data:
:return:
"""
ans = np.empty([data.shape[0], self.__n_cluster])
for index in range(data.shape[0]):
tmp = []
for center in self.__cluster_centers_:
tmp.append(self.__calu_distance(sample=data[index], cluster_center=center))
center_array = np.array(tmp)
ans[index] = center_array
return ans
def predict(self, data):
"""
预测数据所属的簇
:param data: 待预测数据
:return: 数据所属类别
"""
distiance = self.transform(data)
cluster_labels = np.argmax(distiance, axis=1)
return cluster_labels
def print_info(self):
print('clustering is terminate!!!')
print('结束原因:{}'.format(self.info))
print('迭代次数 {}'.format(self.__n_iter_))
print("簇中心:\n", self.__cluster_centers_)
随机生成正太分布的数据
def generate_test_data(size=(120, 2), clusters=3, loc=10, scale=10):
cluster_center = np.array([[10, 10], [50, 100], [100, 50]])
data = np.empty(shape=[0, size[1]])
for cluster_index in range(clusters):
new_data_x = np.random.normal(loc=cluster_center[cluster_index][0],
scale=scale, size=[int(size[0] / clusters),1])
new_data_y = np.random.normal(loc=cluster_center[cluster_index][1],
scale=scale, size=[int(size[0] / clusters), 1])
new_data = np.concatenate((new_data_x, new_data_y), axis=1)
data = np.concatenate([data, new_data], axis=0)
# print(data.shape)
return data
聚类结果可视化函数
def show_data(data, label, cluster_centers=[]):
filled_markers = ('o', 'v', '^', '<', '>', '8', 's', 'p', 'h', 'H', 'D', 'd', 'P', 'X')
filled_colors = ('black', 'green', 'darkorchid', 'darkred', 'darksalmon', 'darkslategray')
plt.figure()
for sample, sample_label in zip(data, label):
plt.scatter(sample[0], sample[1], marker=filled_markers[sample_label],
color=filled_colors[sample_label], s=40, label='cluster_' + str(sample_label))
if len(cluster_centers) > 0:
for center in cluster_centers:
plt.scatter(center[0], center[1], marker='*', color='red', s=80)
# plt.legend(scatterpoints=1)
plt.show()
主函数调用
if __name__ == '__main__':
model = Kmeans(n_clusters=3, max_iter=10000)
data = generate_test_data(scale=15)
model.fit(data)
label = model.label.astype('int')
centers = model.cluster_centers_
show_data(data, label, centers)
运行上面代码段,输出下面结果
clustering is terminate!!!
结束原因:两次聚类簇中心未发生变化,聚类结束。
迭代次数 5
簇中心:
[[100.46756833 48.52330939]
[ 49.11200803 101.26172269]
[ 9.27754808 9.65780098]]