聚类算法之——K-Means算法
聚类算法属于无监督学习,它将相似的对象归到同一个簇中。K-Means算法是聚类算法中最常用到算法;
1. 预备知识点
距离计算
闵可夫斯基距离
点
x
=
(
x
1
,
x
2
,
…
,
x
n
)
和
y
=
(
y
1
,
y
2
,
…
,
y
n
)
x=(x_1,x_2,\dots,x_n)和y=(y_1,y_2,\dots,y_n)
x=(x1,x2,…,xn)和y=(y1,y2,…,yn)之间的闵可夫斯基距离为
d
(
x
,
y
)
=
(
∑
i
=
1
n
∣
x
i
−
y
i
∣
p
)
1
p
其
中
p
≥
1
d(x,y)=(\sum_{i=1}^{n}{|x_i-y_i|^p})^\frac{1}{p} \quad 其中p\geq1
d(x,y)=(i=1∑n∣xi−yi∣p)p1其中p≥1
欧式距离
点
x
=
(
x
1
,
x
2
,
…
,
x
n
)
和
y
=
(
y
1
,
y
2
,
…
,
y
n
)
x=(x_1,x_2,\dots,x_n)和y=(y_1,y_2,\dots,y_n)
x=(x1,x2,…,xn)和y=(y1,y2,…,yn)之间的欧氏距离为:
d
(
x
,
y
)
=
(
x
1
−
y
1
)
2
+
(
x
2
−
y
2
)
2
+
⋯
+
(
x
n
−
y
n
)
2
=
∑
i
=
1
n
(
x
i
−
y
i
)
2
\begin{aligned}d(x,y)&=\sqrt{(x_1-y_1)^2+(x_2-y_2)^2+\dots+(x_n-y_n)^2}\\&=\sqrt{\sum_{i=1}^{n}{(x_i-y_i)^2}} \end{aligned}
d(x,y)=(x1−y1)2+(x2−y2)2+⋯+(xn−yn)2=i=1∑n(xi−yi)2
曼哈顿距离(Manhattan Distance )
图中红线代表曼哈顿距离,绿线代表欧式距离,也就是直线距离,而蓝色和黄色代表等价的曼哈顿距离。
曼哈顿距离:两点在南北方向上的距离加上在东西方向上的距离。
点
x
=
(
x
1
,
x
2
,
…
,
x
n
)
和
y
=
(
y
1
,
y
2
,
…
,
y
n
)
x=(x_1,x_2,\dots,x_n)和y=(y_1,y_2,\dots,y_n)
x=(x1,x2,…,xn)和y=(y1,y2,…,yn)之间的曼哈顿距离为:
d
(
x
,
y
)
=
∑
i
=
1
n
∣
x
i
−
y
i
∣
d(x,y)=\sum_{i=1}^{n}{|x_i-y_i|}
d(x,y)=i=1∑n∣xi−yi∣
余弦距离
两个向量 A 和 B,其余弦距离(即两向量夹角的余弦)由点积和向量长度给出,计算公式如下:
c
o
s
θ
=
A
⋅
B
∣
∣
A
∣
∣
⋅
∣
∣
B
∣
∣
=
∑
i
=
1
n
A
i
B
i
∑
i
=
1
n
(
A
i
)
2
∑
i
=
1
n
(
B
i
)
2
cos\theta=\frac{A \cdot B}{||A|| \cdot ||B||}=\frac{\sum_{i=1}^{n}{A_iB_i}}{\sqrt{\sum_{i=1}^{n}{(A_i)^2}}\sqrt{\sum_{i=1}^{n}{(B_i)^2}}}
cosθ=∣∣A∣∣⋅∣∣B∣∣A⋅B=∑i=1n(Ai)2∑i=1n(Bi)2∑i=1nAiBi
其中,
A
i
和
B
i
A_i和B_i
Ai和Bi分别代表向量
A
A
A和
B
B
B的各分量 。
2. K-Means算法步骤
输入:样本集{ x 1 , x 2 , … , x m x_1,x_2,\dots,x_m x1,x2,…,xm}
聚类簇数k
输出:簇划分 C = C= C={ C 1 , C 2 , … , C k C_1,C_2,\dots,C_k C1,C2,…,Ck}
-
随机初始化k个点作为簇质心;
-
将样本集中的每个点分配到一个簇中;
计算每个点与质心之间的距离(常用欧式距离和余弦距离),并将其分配给距离最近的质心所对应的簇中;
-
更新簇的质心;
每个簇的质心更新为该簇所有点的平均值;
-
反复迭代2 - 3 步骤,直到达到某个终止条件;
常用的终止条件有:1)达到指定的迭代次数;2)簇心不再发生明显的变化,即收敛;3)最小误差平方和SSE;
3. 聚类效果的评价指标SSE
SSE(Sum of Square Error, 误差平方和),SSE值越小表示数据点越接近于它们的质心,聚类效果也越好。
S S E = ∑ i = 1 k ∑ x ϵ C i ( x − μ i ) 2 μ i = 1 ∣ C i ∣ ∑ x ϵ C i x \begin{aligned}SSE&=\sum_{i=1}^{k}{\sum_{x\epsilon C_i}{(x-\mu_i)^2}}\\\mu_i&=\frac{1}{|C_i|}\sum_{x\epsilon C_i}{x}\end{aligned} SSEμi=i=1∑kxϵCi∑(x−μi)2=∣Ci∣1xϵCi∑x
4. python脚本
调用sklearn包里的K-Means算法来实现
完整版脚本,直接运行即可
import time
import matplotlib.pyplot as plt
import matplotlib
from sklearn.cluster import KMeans
from sklearn.datasets import load_iris
matplotlib.rcParams['font.sans-serif'] = [u'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 获取鸢尾花数据集,特征分别是sepal length、sepal width、petal length、petal width
iris = load_iris()
X = iris.data[:,2:] # 通过花瓣的两个特征来聚类
k=3 # 假设聚类为3类
# 构建模型
s=time.time()
km = KMeans(n_clusters=k)
km.fit(X)
print("用sklearn内置的K-Means算法聚类耗时:",time.time()-s)
label_pred = km.labels_ # 获取聚类后的样本所属簇对应值
centroids = km.cluster_centers_ # 获取簇心
#绘制K-Means结果
# 未聚类前的数据分布
plt.subplot(121)
plt.scatter(X[:, 0], X[:, 1], s=50)
plt.xlabel('petal length')
plt.ylabel('petal width')
plt.title("未聚类前的数据分布")
plt.subplots_adjust(wspace=0.5)
plt.subplot(122)
plt.scatter(X[:, 0], X[:, 1], c=label_pred, s=50, cmap='viridis')
plt.scatter(centroids[:,0],centroids[:,1],c='red',marker='o',s=100)
plt.xlabel('petal length')
plt.ylabel('petal width')
plt.title("用sklearn内置的K-Means算法聚类结果")
plt.show()
运行结果
按原理来实现K-Means算法
import time
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = [u'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
import numpy as np
def distEclud(vecA,vecB):
"""
计算两个向量的欧式距离
"""
return np.sqrt(np.sum(np.power(vecA-vecB,2)))
def randCent(dataSet,k):
"""
随机生成k个点作为质心,其中质心均在整个数据数据的边界之内
"""
n=dataSet.shape[1] # 获取数据的维度
centroids = np.mat(np.zeros((k,n)))
for j in range(n):
minJ = np.min(dataSet[:,j])
rangeJ = np.float(np.max(dataSet[:,j])-minJ)
centroids[:,j] = minJ+rangeJ*np.random.rand(k,1)
return centroids
def kMeans(dataSet,k,distMeas=distEclud, createCent=randCent):
"""
k-Means聚类算法,返回最终的k各质心和点的分配结果
"""
m = dataSet.shape[0] #获取样本数量
# 构建一个簇分配结果矩阵,共两列,第一列为样本所属的簇类值,第二列为样本到簇质心的误差
clusterAssment = np.mat(np.zeros((m,2)))
# 1. 初始化k个质心
centroids = createCent(dataSet,k)
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m):
minDist = np.inf
minIndex = -1
# 2. 找出最近的质心
for j in range(k):
distJI = distMeas(centroids[j,:],dataSet[i,:])
if distJI < minDist:
minDist = distJI
minIndex = j
# 3. 更新每一行样本所属的簇
if clusterAssment[i,0] != minIndex:
clusterChanged = True
clusterAssment[i,:]=minIndex,minDist**2
print(centroids) # 打印质心
# 4. 更新质心
for cent in range(k):
ptsClust = dataSet[np.nonzero(clusterAssment[:,0].A==cent)[0]] # 获取给定簇的所有点
centroids[cent,:] = np.mean(ptsClust,axis=0) # 沿矩阵列的方向求均值
return centroids,clusterAssment
s=time.time()
myCentroids,clustAssing=kMeans(X,3) # myCentroids为簇质心
print("用K-Means算法原理聚类耗时:",time.time()-s)
centroids=myCentroids.A # 将matrix转换为ndarray类型
# 获取聚类后的样本所属的簇值,将matrix转换为ndarray
y_kmeans=clustAssing[:,0].A[:,0]
# 未聚类前的数据分布
plt.subplot(121)
plt.scatter(X[:, 0], X[:, 1], s=50)
plt.xlabel('petal length')
plt.ylabel('petal width')
plt.title("未聚类前的数据分布")
plt.subplots_adjust(wspace=0.5)
plt.subplot(122)
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis')
plt.scatter(centroids[:, 0], centroids[:, 1], c='red', s=100, alpha=0.5)
plt.xlabel('petal length')
plt.ylabel('petal width')
plt.title("用K-Means算法原理聚类的效果")
plt.show()
运行结果
算法优缺点和适用场景
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢
适用数据类型:数值型数据
如何避免陷入局部最优
-
当k=2~10之间时,可运行多次,一般次数在50到1000之间,在得到的多个损失函数中选取最小的;
例如:循环100次,选取损失函数最小的那个聚类结果作为最终的聚类结果;
for i =1 to 100:
随机初始化中心点;
运行k-means算法步骤;
计算损失函数J(即第3部分的误差平方和SSE);
选取损失函数最小的那个聚类结果作为最终的聚类结果;
- 当k比较大时,不建议采取(1)的方法。
如何选择K的数量
-
用“Elbow Method”(肘部法则)来选择K的数量;
需要画图(横轴聚类的数量,纵轴损失函数),选择曲线明显的拐点作为K;
但也会存在没有清晰的拐点,无法选出K的数;
-
根据后续目标来选择聚类数目;
比如哪种衬衫尺码会更好的满足我的顾客。
衬衫的尺码数k=3(S M L)还是k=5(XS S M L XL),选哪个会让顾客更满意(这是目标);
补充
如果没有数据,也可以通过python现有库模拟产生数据,代码如下
from sklearn.datasets import make_blobs # 导入产生模拟数据的方法
# 生成模拟数据
k = 5 # 给定聚类数量
X, Y = make_blobs(n_samples=1000, n_features=2, centers=k, random_state=1)
用模拟生成的数据替换上面代码中的x和k,则调算法包和用原理实现的运行效果分别如下
总结
用封装好的算法和用原理实现的算法相比,从时间上来看,封装好的算法要比按原理实现的速度快,后续探索到其他方面再补充。