目录
概述
聚类系列算法是无监督学习算法。对于大量未标注的数据集,按照内在的相似性来分为多个类别(簇),类别内的相似度大,类别间的相似度小。常用的相似性表达有欧式距离、余弦距离、Jaccard相似系数。本文将介绍k-means算法及其一些变种、层次聚类和密度聚类。
k-means
简介
k-means算法是最常用的基于欧式距离的聚类算法,该算法模型在训练之前需要提供k值,即需要将数据分为几个类或簇,后文会介绍如何选择k值,及其优化的算法
算法步骤
- 初始化k个点作为初始聚类中心
- 计算所有样本到聚类中心的距离,距离哪个聚类中心越近,归属于哪一个簇
- 经过2步骤,所有样本都划分于某一个簇,簇内重新计算均值,更新簇中心
- 经过2和3迭代,直到簇中心不再变化或者达到迭代次数
k值的选择
肘部法
肘部法通过观察损失函数随k值变化的曲线来确定k值,损失函数的定义为簇内所有样本点到簇中心距离平方之和,就是每个簇的MSE的加和,公式定义如下:
随着k值的增大,误差减小量很小的拐点即可作为最佳的k值,如下图像当k=4时最佳:
实现代码:
from sklearn.cluster import KMeans
from sklearn import datasets
import matplotlib.pyplot as plt
import numpy as np
# 生成数据集
n_samples = 400
n_classes = 4
X, y = datasets.make_blobs(n_samples=n_samples, n_features=2, centers=n_classes, random_state=6)
# losses
losses = []
k_values = [i for i in range(1, 11)]
fig = plt.figure(figsize=(25, 10), dpi=80)
for k in k_values:
# 模型训练
kmeans = KMeans(n_clusters=k, random_state=6)
y_predict = kmeans.fit_predict(X)
losses.append(kmeans.inertia_)
# 绘图
plt.subplot(2, 5, k)
plt.title('k={}'.format(k))
for i in range(k):
plt.scatter(X[y_predict == i, 0], X[y_predict == i, 1])
plt.tight_layout(pad=0.4, h_pad=1)
plt.show()
# plot loss
plt.figure(figsize=(8, 6), dpi=80)
plt.plot(k_values, losses)
plt.xlabel('k')
plt.ylabel('loss')
plt.show()
轮廓系数
轮廓系数需要计算如下两个值:
- 计算同簇内每个样本到其他样本的距离,加和求平均得
- 计算同簇内每个样本到其他簇样本的平均距离,若干个簇中最小的距离
任意一点的轮廓系数如下:
聚类结果的轮廓系数就是每个点轮廓系数求平均,值越大越好,取轮廓系数最优的k值,从如下图像可以k=4时,轮廓系数最大
实现代码:
from sklearn.metrics import silhouette_score
from sklearn import datasets
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
# 生成数据集
n_samples = 400
n_classes = 4
X, y = datasets.make_blobs(n_samples=n_samples, n_features=2, centers=n_classes, random_state=6)
silhouette_scores = []
k_values = [i for i in range(2, 11)]
for k in k_values:
kmeans = KMeans(n_clusters=k, random_state=6)
y_predict = kmeans.fit_predict(X)
silhouette_scores.append(silhouette_score(X, kmeans.labels_, metric='euclidean'))
plt.plot(k_values,silhouette_scores)
plt.xlabel('k')
plt.ylabel('silhouette score')
plt.show()
算法优缺点
优点:简单且聚类效果不错
缺点:
- 对异常值敏感:所有样本归为簇之后需要重新计算簇中心的位置,如果存在异常点,簇中心位置偏差大
- 对初始值敏感:损失函数是非凸的,簇中心点的初始化有可能得到局部最优解
- 对于一些分布聚类效果不好
算法改进
k-mediods
改进点:计算簇中心点不再使用均值,而是中位数,抗噪能力加强
k-means++
改进点:解决了k-means算法对初始簇中心敏感的问题
步骤:
- 随机选择一个点作为第一个簇中心点
- 计算每个样本点到的距离,并将距离概率化(距离越远的点越有可能成为)
- 重复迭代2步骤,直到找到k个簇中心点
说明:sklearn和spark mllib默认采用的就是这种方式
mini-batch k-means
改进点:相比于k-means使用簇内所有样本参与计算簇中心点,该算法只使用部分的样本点计算簇中心点,有可能能规避异常点的影响
Canopy
改进点:是一种粗聚类的算法,执行速度快,但是精度低,一般不单独使用,结合k-means使用,作为k-means的簇中心的初始化算法,能够解决k-means对初始点敏感问题
步骤:
- 指点超参数T1和T2,T1>T2
- 随机产生d作为D的簇中心
- 计算所有样本到d的距离,距离小于T1的点归属于d
- 从D中删除d和小于T1的样本点
- 步骤2,3,4迭代,直到D为空
说明:就好比一个家族,有一个族长,在族长中期选举时,族长所在的家庭成员(<T2)不能够取代族长的地位(一般是拥护他),除了族长家庭成员的其他成员(>T2,<T1)属于本族但是有可能另立山头(成为其他簇中心点),非本族成员(>T2)有自己的族长
层次聚类
简介
层次聚类可以不用指定k值,层次聚类又分为自上而下的分裂法(divisive)和自下而上的凝聚法(agglomerative)
分裂法
步骤:
- 所有样本归为一个簇C
- 簇中计算样本与样本间的距离,选取最远的两个样本a和b,作为簇C1和C2的簇中心点
- 簇中样本离谁进就划入谁
- 迭代,直到满足了预设k值或者小于距离阈值
凝聚法
步骤:
- 数据集中的每个样本都是一个簇
- 计算两个簇之间的距离,找到距离最小的两个簇合并
- 迭代2,直到满足预设k值
簇之间的距离计算方式:
- single:两个簇之间距离最近的两个样本点的距离作为两个簇之间的距离,易受异常值影响,对应sklearn AgglomerativeClustering中linkage='ward'
- complete:两个簇之间距离最远的两个样本点的距离作为两个簇之间的距离,易受异常值影响,对应sklearn AgglomerativeClustering中linkage='complete'
- average:两个簇之间的所有样本到其他样本的距离的均值作为两个簇之间的距离,对应sklearn AgglomerativeClustering中linkage='average'
代码:
"""
层次聚类,凝聚法
"""
from sklearn.cluster import AgglomerativeClustering
from sklearn import datasets
import matplotlib.pyplot as plt
# 数据集
n_samples = 400
n_classes = 4
X, y = datasets.make_blobs(n_samples=n_samples, n_features=2, centers=n_classes, random_state=6)
# 模型训练预测
model = AgglomerativeClustering(n_clusters=4, affinity='euclidean', linkage='ward')
y_predict = model.fit_predict(X)
# 数据可视化
plt.scatter(X[y == 0, 0], X[y == 0, 1])
plt.scatter(X[y == 1, 0], X[y == 1, 1])
plt.scatter(X[y == 2, 0], X[y == 2, 1])
plt.scatter(X[y == 3, 0], X[y == 3, 1])
plt.tight_layout(pad=0.4)
plt.show()
密度聚类
简介
与层次聚类不同,密度聚类将簇定义为密度相连的点最大集合,能够把具有高密度的区域划分为簇,并可以有效对抗噪声。有些样本点不能被划分为某一类簇
概念
直接密度可达:对象的e领域是给定对象在半径e内的区域,如果一个对象的e领域有至少m个样本,则这个对象是核心对象,如果p在q的e领域内,且q是核心对象,那么p从q出发时直接密度可达的(俗语:我周围指定半径的人超过了m个,那么我就是核心,我周围指定半径的人从我这出发是直接密度可达)
密度可达:存在样本序列P1, P2, , ... , Pn,其中P1的核心对象为xi,Pn的核心对象为xj,且Pi+1由Pi密度直达,则xi由xj密度可达
密度相连:xi和xj均由xk密度可达,则xi和xj密度相连
- x2由x1直接密度可达
- x3由x1密度可达
- x3和x4密度相连
代码实现
import numpy as np
import matplotlib.pyplot as plt
import sklearn.datasets as ds
import matplotlib.colors
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
if __name__ == "__main__":
N = 1000
centers = [[1, 2], [-1, -1], [1, -1], [-1, 1]]
data, y = ds.make_blobs(N, n_features=2, centers=centers, cluster_std=[0.5, 0.25, 0.7, 0.5], random_state=0)
data = StandardScaler().fit_transform(data)
# 数据的参数:(epsilon, min_sample)
params = ((0.2, 5), (0.2, 10), (0.2, 15), (0.3, 5), (0.3, 10), (0.3, 15))
matplotlib.rcParams['font.sans-serif'] = [u'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(12, 8), facecolor='w')
plt.suptitle(u'DBSCAN聚类', fontsize=20)
for i in range(6):
eps, min_samples = params[i]
model = DBSCAN(eps=eps, min_samples=min_samples)
model.fit(data)
y_hat = model.labels_
core_indices = np.zeros_like(y_hat, dtype=bool)
core_indices[model.core_sample_indices_] = True
y_unique = np.unique(y_hat)
n_clusters = y_unique.size - (1 if -1 in y_hat else 0)
print(y_unique, '聚类簇的个数为:', n_clusters)
plt.subplot(2, 3, i+1)
plt.scatter(data[y_hat == -1, 0], data[y_hat == -1, 1], s=20)
plt.scatter(data[y_hat == 0, 0], data[y_hat == 0, 1])
plt.scatter(data[y_hat == 1, 0], data[y_hat == 1, 1])
plt.scatter(data[y_hat == 2, 0], data[y_hat == 2, 1])
plt.scatter(data[y_hat == 3, 0], data[y_hat == 3, 1])
plt.title("eps={},min_samples={}".format(eps, min_samples))
plt.tight_layout()
plt.subplots_adjust(top=0.9)
plt.show()
聚类评价指标
给定标签(外部评估)
同质性/完整性/v-measure
v-measure是结合同质性和完整性的调和平均,v-measure越接近1越好
from sklearn import metrics
y_true = [0, 0, 0, 1, 1, 1]
y_hat = [0, 0, 1, 1, 2, 2]
homogeneity = metrics.homogeneity_score(y_true, y_hat)
completeness = metrics.completeness_score(y_true, y_hat)
v_measure = metrics.v_measure_score(y_true, y_hat)
print('同质性:{}'.format(homogeneity))
print('完整性:{}'.format(completeness))
print('v_measure:{}'.format(v_measure))
调整兰德系数(ARI)
调整兰德系数取值在(-1,1)之间,负值代表簇内差异大,正值代表预测值和真实值差别较小,ARI越接近1越好
from sklearn import metrics
y_true = [0, 0, 0, 1, 1, 1]
y_hat = [0, 0, 1, 1, 2, 2]
adjusted_rand = metrics.adjusted_rand_score(y_true, y_hat)
print('调整兰德系数:{}'.format(adjusted_rand))
未给定标签(内部评估)
轮廓系数
详见上K值选择轮廓系数