利用距离远近的思想将目标数据剧为指定的k个簇,簇类样本越相似,表明簇类效果越好。
子图1,从原始样本中随机挑选两个数据点作为初始的簇中心,即子图中的两个五角星;子图2,将其余样本点与这两个五角星分别计算距离(距离的度量可选择欧氏距离、曼哈顿距离等),然后将每个样本点划分到离五角星最近的簇,即子图中按虚线隔开的两部分;子图3,计算两个簇内样本点的均值,得到新的簇中心,即子图中的五角星;子图4,根据新的簇中心,继续计算各样本与五角星之间的距离,得到子图5的划分结果和子图6中新的簇内样本均值;以此类推,最终得到理想的聚类效果,如子图9所示,图中的五角星即最终的簇中心点。
KMeans(n_clusters=8, init='k-means++', n_init=10, max_iter=300, tol=0.0001,
precompute_distances='auto', verbose=0, random_state=None,
copy_x=True, n_jobs=1, algorithm='auto')
n_clusters
:用于指定聚类的簇数。init
:用于指定初始的簇中心设置方法,如果为’k-means++’,则表示设置的初始簇中心之间相距较远;如果为’random’,则表示从数据集中随机挑选k个样本作为初始簇中心;如果为数组,则表示用户指定具体的簇中心。n_init
:用于指定Kmeans算法运行的次数,每次运行时都会选择不同的初始簇中心,目的是防止算法收敛于局部最优,默认为10。max_iter
:用于指定单次运行的迭代次数,默认为300。tol
:用于指定算法收敛的阈值,默认为0.0001。precompute_distances
:bool类型的参数,是否在算法运行之前计算样本之间的距离,默认为’auto’,表示当样本量与变量个数的乘积大于1200万时不计算样本间距离。verbose
:通过该参数设置算法返回日志信息的频度,默认为0,表示不输出日志信息;如果为1,就表示每隔一段时间返回一次日志信息。random_state
:用于指定随机数生成器的种子。copy_x
:bool类型参数,当参数precompute_distances为True时有效,如果该参数为True,就表示提前计算距离时不改变原始数据,否则会修改原始数据。n_jobs
:用于指定算法运算时使用的CPU数量,默认为1,如果为-1,就表示使用所有可用的CPU。algorithm
:用于指定Kmeans的实现算法,可以选择’auto’‘full’和’elkan’,默认为’auto’,表示自动根据数据特征选择运算的算法。对于每个簇而言,就是保证这些簇的**离差平方和(方差)**的总和最小。
当簇的个数与样本个数一致时(每个样本代表一个类),就可以得到最小值0。确实不假,簇被划分得越细,总和肯定会越小,但这样的簇不一定是合理的。所谓合理,就是随着簇的增加,离差平方和之和趋于稳定(波动小于某个给定的阈值)。
该函数的表达式为:
cj表示第j个簇的簇中心,xi属于第j个簇的样本i,nj表示第j个簇的样本总量。对于该目标函数而言,cj是未知的参数。由于J为凸函数,可以通过求导获得cj。
只有当簇中心cj为簇内的样本均值时,目标函数才会达到最小,获得稳定的簇。推导出来的簇中心正好与Kmeans聚类思想中的样本均值相吻合。
对于Kmeans算法来说,如何确定簇数k值是一个至关重要的问题。
Kmeans聚类算法的目标函数J,随着簇数量的增加,簇中的样本量会越来越少,进而导致目标函数J的值也会越来越小。通过可视化方法,重点关注的是斜率的变化,当斜率由大突然变小时,并且之后的斜率变化缓慢,则认为突然变化的点就是寻找的目标点,因为继续随着簇数k的增加,聚类效果不再有大的变化。
绘制三组1000个点的散点图。
# 导入第三方包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn import metrics
# 随机生成三组二元正态分布随机数
"""
函数可使得随机数具有预见性,
即当参数相同时使得每次生成的随机数相同;
"""
np.random.seed(1234)
# 均值矩阵
mean1 = [0.5, 0.5]
# 协方差矩阵
cov1 = [[0.3, 0], [0, 0.3]]
#生成1000个点,满足mean1和cov1
x1, y1 = np.random.multivariate_normal(mean1, cov1, 1000).T
mean2 = [0, 8]
cov2 = [[1.5, 0], [0, 1]]
x2, y2 = np.random.multivariate_normal(mean2, cov2, 1000).T
mean3 = [8, 4]
cov3 = [[1.5, 0], [0, 1]]
x3, y3 = np.random.multivariate_normal(mean3, cov3, 1000).T
# 绘制三组数据的散点图
plt.scatter(x1,y1)
plt.scatter(x2,y2)
plt.scatter(x3,y3)
# 显示图形
plt.show()
虚拟的数据呈现三个簇,接下来基于这个虚拟数据,使用拐点法,绘制簇的个数与总的簇内离差平方和之间的折线图,确定该聚为几类比较合适。
# 构造自定义函数,用于绘制不同k值和对应总的簇内离差平方和的折线图
def k_SSE(X, clusters):
# 选择连续的K种不同的值
K = range(1,clusters+1)
# print(K)
# 构建空列表用于存储总的簇内离差平方和
TSSE = []
for k in K:
# 用于存储各个簇内离差平方和
SSE = []
# 传入簇数
kmeans = KMeans(n_clusters=k)
# 传入数据
kmeans.fit(X)
# 返回簇标签(返回array,即表示每个ID所属的簇)
labels = kmeans.labels_
# print(k,"==",labels)
# 返回簇中心(返回k*2的array)
centers = kmeans.cluster_centers_
# print(k,"==",centers)
# 计算各簇样本的离差平方和,并保存到列表中
for label in set(labels):
# append 将所有的参数视为一个列表元素插入到列表尾部
SSE.append(np.sum((X.loc[labels == label,]-centers[label,:])**2))
# 计算总的簇内离差平方和
TSSE.append(np.sum(SSE))
# 中文和负号的正常显示
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 设置绘图风格
plt.style.use('ggplot')
# 绘制K的个数与GSSE的关系
plt.plot(K, TSSE, 'b*-')
plt.xlabel('簇的个数')
plt.ylabel('簇内离差平方和之和')
# 显示图形
plt.show()
# 将三组数据集汇总到数据框中
# np.concatenate 按轴axis连接array组成一个新的array
# pd.DataFrame Pandas库中的一种数据结构,它类似excel,是一种二维表。
X = pd.DataFrame(np.concatenate([np.array([x1,y1]),np.array([x2,y2]),np.array([x3,y3])], axis = 1).T)
# 自定义函数的调用
k_SSE(X, 15)
当簇的个数为3时形成了一个明显的拐点,因为k值从1到3时,折线的斜率都比较大,但是k值为4时斜率突然就降低了很多,并且之后的簇对应的斜率都变动很小。所以,合理的k值应该为3。
考虑了簇的密集性与分散性两个信息,如果数据集被分割为理想的k个簇,那么对应的簇内样本会很密集,而簇间样本会很分散。
轮廓系数的计算公式可以表示为:
a(i)体现了簇内的密集性,代表样本i与同簇内其他样本点距离的平均值;b(i)反映了簇间的分散性,它的计算过程是,样本i与其他非同簇样本点距离的平均值,然后从平均值中挑选出最小值。当S(i)接近于-1时,说明样本i分配的不合理,需要将其分配到其他簇中;当S(i)近似为0时,说明样本i落在了模糊地带,即簇的边界处;当S(i)近似为1时,说明样本i的分配是合理的。
需要对所有点的轮廓系数求平均值,得到总轮廓系数。当总轮廓系数小于0时,说明聚类效果不佳;当总轮廓系数接近于1时,说明簇内样本的平均距离a非常小,而簇间的最近距离b非常大,进而表示聚类效果非常理想。
有关轮廓系数的计算,可以直接调用sklearn
子模块metrics
中的函数,即silhouette_score
。需要注意的是,该函数接受的聚类簇数必须大于等于2。
# 构造自定义函数,用于绘制不同k值和对应轮廓系数的折线图
def k_silhouette(X, clusters):
K = range(2,clusters+1)
# 构建空列表,用于存储个中簇数下的轮廓系数
S = []
for k in K:
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
labels = kmeans.labels_
# 调用字模块metrics中的silhouette_score函数,计算轮廓系数
# sklearn.metrics.silhouette_score(X, labels, *, metric='euclidean', sample_size=None, random_state=None, **kwds)
# 适用于二维
S.append(metrics.silhouette_score(X, labels, metric='euclidean'))
# 中文和负号的正常显示
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 设置绘图风格
plt.style.use('ggplot')
# 绘制K的个数与轮廓系数的关系
plt.plot(K, S, 'b*-')
plt.xlabel('簇的个数')
plt.ylabel('轮廓系数')
# 显示图形
plt.show()
# 自定义函数的调用
k_silhouette(X, 15)
学习中…
聚类前必须指定具体的簇数k值,如果k值是已知的,可以直接调用cluster子模块中的Kmeans类,对数据集进行分割;如果k值是未知的,可以根据行业经验或前面介绍的三种方法确定合理的k值。
对原始数据集做必要的标准化处理,由于Kmeans的思想是基于点之间的距离实现“物以聚类”的,所以,如果原始数据集存在量纲上的差异,就必须对其进行标准化的预处理,否则可以不用标准化。数据集的标准化处理可以借助于sklearn
子模块preprocessing
中的scale
函数或minmax_scale
实现。