机器学习笔记(2)—K-means
K-means作为一种聚类方法,是一种比较典型的无监督学习(unsupervised learning),即没有给定事先标记过的训练示例(期望),按照给定的训练模式,自动对输入的数据进行分类或分群。而监督式学习(supervised learning)不同之处在于预先给出了训练示例(期望),并通过训练示例(期望)来推测一个训练模式,再对训练数据进行操作。
两者并不难区分,以我的理解,简单的来说,无监督学习没有预先给出训练期望,而监督学习预先给出了训练期望。
K-平均聚类的目的是:把n个点划分到k个聚类中,其中每个聚类有一个聚类中心,使得每个点都属于离他最近聚类中心对应的聚类(比方说n1点距离聚类中心k1比它距离其他所有聚类中心近,那么它就归属于聚类k1),以之作为聚类的标准。
大致流程如下:
1.随机选取k个点作为聚类中心,每个点代表一个类的中心点;
2.计算每个数据点距离所有聚类中心的距离,再把每个数据点归到距离它最近的类中,划分出k个类;
3.将同一个类的所有数据点计算均值,得到一个此类的新中心点;
4.迭代2-3,直到新中心点变换的距离越来越小,趋紧不变;
方法简单易懂,但很遗憾的是,K-means是属于NP-hard的,这里并不多概述,可以理解为,算起来非常慢,同时验证其正确性也很难
这篇文章简单写一下K-means方法的对数据集的算法实现
(1)
首先生成数据集,4*100个二维向量,分别给出四个均值点与协方差矩阵来生成数据集
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D #This is for 3d scatter plots.
import math
np.random.seed(1)
def generate_data():
X = np.zeros((400, 2))
cov=1/30*np.eye(2)
mean_1=np.array([0.5,0.5])
mean_2=np.array([-0.5,0.5])
mean_3=np.array([0.75,-0.5])
mean_4=np.array([-0.25,-0.5])
x1=np.random.multivariate_normal(mean_1,cov,100)
x2=np.random.multivariate_normal(mean_2,cov,100)
x3=np.random.multivariate_normal(mean_3,cov,100)
x4=np.random.multivariate_normal(mean_4,cov,100)
X=np.vstack((x1,x2,x3,x4))
return X
X = generate_data()
plt.scatter(X[:,0], X[:,1])
plt.show()
数据集生成结果如下:
(2)
K-means的两个难点:
1.
我们从图中可以明显看出数据应划分为四个聚类,这也是由我们生成数据的方式决定的,但在更广泛的实际的机器学习中,这样一开始就决定好准确聚类数量的好运气非常少见
举个例子,如果数据集是在多维空间中分布,而不像我们现在简单的在二维空间分布的话,观察出聚类数量十分困难
再举个例子,如果数据集的聚类高达数百万个,那我们又如何找到这个k呢?
这也是造成K-means属于NP困难的原因之一
2.
K-means算法需要给出初始聚类的中心点,那么中心点如何选取呢?
有这样两个思路(当然也不止两个):
随机取数据集中k个数据,将它们定为初始的k个聚类中心;
将数据集中的所有数据随机划分到k个聚类中,再计算出每个聚类中数据的均值作为聚类中心
从大量的训练结果来看,后者的效果明显好于前者,但博主为了偷懒,用了第一种方法,第一种方法写起来简单,同时对于我们这个不大的数据集来说,也不怎么会影响最终效果
更为详细的划分思路:https://en.wikipedia.org/wiki/K-means_clustering#Initialization_methods
def initialise_parameters(m, n, X):
C = np.zeros((m, n))
for i in range(C.shape[0]):
C[i]=X[np.random.randint(X.shape[0])]
return C
C = initialise_parameters(4, 2, X)
print(C)
运行结果产生了四个初始聚类中心如下:
(3)
然后进行文章开头所述的step2,将数据集划分到距离最近的聚类中
def E_step(C, X):
L = np.zeros(X.shape)
def nearest(x,C):
i=0
m=np.linalg.norm(x - C[0], axis=0)
for j in range(C.shape[0]):
d=np.linalg.norm(x - C[j], axis=0)
if d<m:
m=d
i=j
return C[i]
for i in range(X.shape[0]):
L[i]=nearest(X[i],C)
return L
L = E_step(C, X)
plt.scatter(L[:, 0], L[:, 1])
plt.show()
(4)
接下来是step3,重新计算聚类中心
def M_step(C, X, L):
new_C = np.zeros(C.shape)
for i in range(C.shape[0]):
acc=np.zeros(C.shape[1])
n=0
for j in range(L.shape[0]):
if np.array_equal(L[j, :], C[i,:]):
acc=acc+X[j]
n=n+1
if n!= 0: new_C[i]=acc/n
else: new_C[i]=X[np.random.randint(low=0, high=X.shape[0])]
return new_C
print('Before:')
print(C)
print('\nAfter:')
print(M_step(C, X, L))
(5)
接下来是step4,迭代step2与step3,不断重新划分聚类,得到更理想的训练结果,这里我们设置迭代次数为10次
def kmeans(X, m, i):
L = np.zeros(X.shape)
C=initialise_parameters(m, X.shape[1], X)
oldC=None
it=0
while not (oldC == C).all() and it<i:
oldC=C
L=E_step(C, X)
C=M_step(C, X, L)
it=it+1
return C, L
C_final, L_final = kmeans(X, 4, 10)
print('Initial Parameters:')
print(C)
print('\nFinal Parameters:')
print(C_final)
def allocator(X, L, c):
cluster = []
for i in range(L.shape[0]):
if np.array_equal(L[i, :], c):
cluster.append(X[i, :])
return np.asarray(cluster)
colours = ['r', 'g', 'b', 'y']
for i in range(4):
cluster = allocator(X, L_final, C_final[i, :])
plt.scatter(cluster[:,0], cluster[:,1], c=colours[i])
plt.show()
运行结果如下,可以看出,原数据集被我们划分成了四个清晰可见的类:
(5)
拓展一下,假设我们生成的数据集如下
def gen_z():
Z = np.random.randn(800, 2)
for r in range(Z.shape[0]):
s = np.linalg.norm(Z[r, :])
Z[r, :] = Z[r, :] * (s < 0.3 or s > 1.45)
return Z
Z = gen_z()
plt.scatter(Z[:, 0], Z[:, 1])
plt.show()
对于这样的数据集,像刚刚那样在二维空间内划分聚类是不太行得通的
因此,我们观察数据集的特性,其挖空了参数s属于(0.3,1.45)区间内的点,我们不妨将其扩增到三维空间,其三维空间的坐标z,采用<x,y>取内积的方式
在这里,我们运用到的方法是kernel method,对于低维空间难以划分的数据集,我们常常将其扩增至更高维的空间中去,利用函数f将n维空间数据映射至m维空间,而kernel method可以帮我们省去高维空间中数据难以计算的麻烦,甚至解决无限维空间无法计算的问题
更多链接:https://en.wikipedia.org/wiki/Kernel_method
Z = gen_z()
Z = np.array(list(map(lambda x : [x[0],x[1],10*math.sqrt(x[0]**2+x[1]**2)],Z)))
#YOUR CODE HERE.
C,L=kmeans(Z,2,10)
cluster0 = allocator(Z, L, C[0, :])
cluster1 = allocator(Z, L, C[1, :])
if cluster0.shape[0]>cluster1.shape[0]:
clump=cluster0
ring=cluster1
else:
ring=cluster0
clump=cluster1
colours = ['r', 'b']
if Z.shape[1] == 2:
Z = np.hstack((Z, np.zeros((800, 1))))
cluster = Z
fig = plt.figure(figsize=(16, 10))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(ring[:, 0], ring[:, 1], ring[:, 2], alpha=0.45)
ax.scatter(clump[:, 0], clump[:, 1], clump[:, 2], alpha=0.45)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
fig = plt.figure(figsize=(16, 10))
plt.scatter(ring[:,0], ring[:,1])
plt.scatter(clump[:,0], clump[:,1])
plt.show()
对于新增广三维数据集,我们再使用K-means划分,得到了很好的解决
再将聚类转化至原二维空间中,结果如下: