代价函数:
J ( c , u ) = ∑ i = 1 k ∣ ∣ x ( i ) − u c ( i ) ∣ ∣ 2 J(c,u)=\sum_{i=1}^k||x(i)-u_{c^{(i)}}||^2 J(c,u)=i=1∑k∣∣x(i)−uc(i)∣∣2
u c ( i ) u_{c^{(i)}} uc(i)表示第i个类的均值。 我们希望代价函数最小,直观的来说,各类内的样本越相似,其与该类均值间的误差平方越小,对所有类所得到的误差平方求和,即可验证分为k类时,各聚类是否是最优的
算法流程
输入:待聚类的样本X、预先给定的聚类数K 输出:样本X中每个样本被分到的类、最终的所有聚类中心 流程:
- 初始化K个聚类中心作为最初的中心
- 循环每个样本,计算其与K个聚类中心的距离,将该样本分到距离最小的那个聚类中心
- 将每个聚类中的样本均值作为新的聚类中心
- 重复步骤2和3直到聚类中心不再变化
性能分析
-
优点
- 是解决聚类问题的一种经典算法,简单、快速
- 对处理大数据集,该算法是相对可伸缩和高效率的。它的复杂度是 O ( n k t ) O(nkt) O(nkt),其中, n n n是所有对象的数目, k k k 是簇的数目, t t t 是迭代的次数。通常 k < < n k<<n k<<n 且 t < < n t<<n t<<n
- 当结果簇是密集的,而簇与簇之间区别明显时, 它的效果较好
-
缺点
- 在簇的平均值被定义的情况下才能使用,这对于处理符号属性的数据不适用
- 必须事先给出K
- 对初值敏感,对于不同的初始值,可能会导致不同结果
- 何初始化聚类中心
-
随机选择K个样本点
-
KMeans++
- 从输入的数据点集合中随机选择一个点作为第一个聚类中心𝜇1
- 对于数据集中的每一个点𝑥𝑖,计算它与已选择的聚类中心中最近聚类中心的距离
D ( x i ) = a r g m i n ∣ ∣ x i − μ r ∣ ∣ 2 2 D(x_i)=argmin||x_i-\mu_r||_2^2 D(xi)=argmin∣∣xi−μr∣∣22, r = 1 , 2 , . . . k s e l e c t e d r=1,2,...kselected r=1,2,...kselected - 选择一个新的数据点作为新的聚类中心,选择的原则是:𝐷(𝑥)较大的点,被选取作为聚类中心的概率较大
- 重复2和3直到选择出k个聚类质心
- 利用这k个质心来作为初始化质心去运行标准的K-Means算法
Python实现
import numpy as np
from base import ClusterMixin, UnsupervisedModel
from utils.distances import euclidean_distance
from exceptions import NotFittedError
def euclidean_distance(x1, x2):
"""欧氏距离 d(x,y)=( ∑(xi-yi)^2 )^0.5
Parameters
----------
x1, x2: n维空间的两个样本点
Return
------
返回点x1和x2的欧氏距离
"""
return np.sqrt(np.sum((x1 - x2) ** 2))
class KMeans(object):
def fit(self, X, k=8, max_iter=100000,
distance=euclidean_distance):
"""
:param X: 训练集, (n_samples, n_features)
:param k: 将X聚成k类
:param max_iter: 最大迭代次数,到达max_iter则停止迭代
:param distance: 距离函数,默认欧氏距离,支持多种距离函数
:return: self
self.centeriods: 质心, (k)
self.labels: 存放每个点对应的类
"""
# 初始化质心
self._init_centriods(X, k, method=init_centriods_method)
centriods_changed = True
self.labels = np.zeros(len(X)) # 用于存放每个样本点对应的类
count = 0
# 当 质心变化时进入循环
while centriods_changed:
# 对每个样本,计算其属于哪个类
for i, x in enumerate(X):
# distances = 当前点x 和 所有质心 的距离
distances = np.array(
[distance(x, self.centriods[i]) for i in range(k)])
self.labels[i] = distances.argmin()
# 对每个类,更新其质心
updated_centriods = np.array(
[np.mean(X[self.labels == i], axis=0) for i in range(k)])
if updated_centriods.tolist() == self.centriods.tolist():
print('已收敛,停止迭代')
centriods_changed = False
else:
self.centriods = updated_centriods
count += 1
if count == max_iter:
print('达到最大迭代次数,停止迭代')
return self
return self
def _init_centriods(self, X: np.ndarray, k: int):
"""初始化聚类中心"""
indices = np.random.choice(len(X), k, replace=False)
self.centriods = X[indices]
return self
@staticmethod
def _get_nearest_class(sample, centers):
"""点sample离centers中哪个质心更近,返回哪个质心的索引"""
return np.argmin(np.sqrt(np.sum((centers - sample) ** 2, axis=1)))
def predict(self, X):
"""预测
:param X: 需要预测的数据集,(n_samples, n_features)
:return: 数据集每个点分到的类, (n_samples)
"""
# fit之后会添加属性labels和centriods,遂可根据这个判断是否模型已经训练
if not hasattr(self, 'labels') or not hasattr(self, 'centriods'):
raise NotFittedError('fit first, then predict')
return np.array([self._get_nearest_class(x, self.centriods) for x in X])
if __name__ == "__main__":
X = np.array([[1, 1], [1, 2], [2, 1], [1, 10],
[2, 10], [2, 9], [9, 9], [9, 10]])
model = KMeans()
model.fit(X, k=3)
print(model.centriods) # 最终的质心
print(model.labels) # 训练集每个样本点对应的类
print(model.predict(np.array([[1, 0], [11, 12]]))) # 预测两个点
结果如下:
已收敛,停止迭代
[[4.6 9.6]
[2. 1. ]
[1. 1.5]]
[0. 0. 0. 1. 1. 1. 2. 2.]
[1 0]