0%

聚类算法

聚类算法

聚类算法又叫做“无监督分类”,其目的是将数据划分成有意义或有用的组(或簇)。这种划分可以基于我们的业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。

无监督学习:在进行模型训练的时候,只需要特征矩阵 X,不需要真实标签 Y

K-Means算法

算法过程

  • 定义: 簇中所有数据的均值μ(j),通常被称为这个簇的质心(centroids),j表示第j个簇 在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值

  • 算法过程: K-Means算法中,,簇的个数K是一个需要我们人为确定的参数 K-Means的核心任务就是根据我们设定好的K,找出K个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去 其中,距离的度量:欧氏距离或余弦相似度(需要标准化)

    1
    2
    3
    4
    5
    1. 随机添加K个样本作为最初的质心
    2. 开始循环:
    2.1 计算每个样本点到每个质心的距离,将他们分配到最近的质心,生成K个簇
    2.2 在每个簇中,计算所有被分配到该簇的样本点的平均值并作为新的质心
    3. 当质心的位置不再发生变化,循环结束,聚类完成

程序实现

详细文档:https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html#sklearn.cluster.KMeans

创建数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt

# 导入数据集
x, y = make_blobs(
n_samples=1000, # 1000个数据
n_features=2, # 二维平面图
centers=4, # 4个中心点
random_state=1) # 随机数种子,保证每次创建的数据集一样

plt.scatter(
x[:, 0], # 横坐标
x[:, 1], # 纵坐标
s=8) # 点的大小
plt.show()

make_blobs方法有两个返回值,第一个值表示生成的数据点,第二个值表示每个数据点的标签(或类别)(但是这在无监督学习中并没有用处)

K-Means聚类

  1. 重要参数n_clusters:要生成的簇数和要生成的质心数,即上文的K,默认值为8
    1
    2
    3
    4
    from sklearn.cluster import KMeans

    # 训练x之后,完成循环,聚类结束
    cluster = KMeans(n_clusters=4, random_state=0).fit(x)

注意:另外两个APIfit_predictpredict常用于大数据集的形式,假设1000万条数据,全部用KMeans().fit()去一轮一轮的查找,效率极低。此时可以对数据集进行切片,切取小部分数据进行fit得到准确的质心,再将整个数据集放到predict中进行预测

  1. 重要属性labels_:指聚类完成后,每个样本所对应的簇数

    1
    2
    labels = cluster.labels_
    print(labels)

  2. 重要属性cluster_centers_:查看质心

    1
    2
    centroid = cluster.cluster_centers_
    print(centroid)

  3. 重要属性inertia_:查看总距离平方和,聚类结果的一个评判依据,越小越好

    1
    2
    inertia = cluster.inertia_
    print(inertia)

通过inertia_查看总距离平方和(total inertia)。对于一个簇来说,所有的样本点到质心的距离之和越小,那么簇中的样本越相似,簇内的差异就越小

但是当更改n_clusters的时候,inertia会越来越小。实际上K-Means算法过程就是求解能够让 inertia 最小化的质心,而这样一来,改变了n_clusters,同时会对聚类结果有影响。所以综上,inertia不是对聚类结果很好的一个评判依据

  1. APItransform:计算每个样本点到质心的距离。它将会返回一个矩阵,矩阵的每一行代表一个样本点到每个质心的距离。
    1
    distances = kmeans.transform(x)
    这个距离信息可以用来进行各种任务,例如降维、特征工程等。通常,在聚类任务中,transform方法的输出可以作为新的特征,用于构建监督学习模型,或者用于其他机器学习任务。
  • 数据可视化:
    1
    2
    3
    4
    plt.figure()
    plt.scatter(x[:, 0], x[:, 1], c=labels)
    plt.scatter(centroid[:, 0], centroid[:, 1], marker='x', s=100, c='black')
    plt.show()

轮廓系数

由于是无监督学习,所以我们是对没有真实标签的数据进行探索,需要完全依赖于评价簇内的稠密程度(簇内差异小)和簇间的离散程度(簇外差异大)来评估聚类的效果,所以引入了轮廓系数这个评判依据

某样本的轮廓系数计算公式:

其中,a是该样本点与其簇内其他所有样本点的平均距离,b是该样本点与另一个最近的簇内所有样本点的平均距离。轮廓系数的计算结果是(-1,1),其中: - 值越接近1,表示样本与自己所在的簇中的样本很相似,并且与其他簇中的样本不相似。 - 当轮廓系数为0时,则代表两个簇中的样本相似度一致,两个簇本应该是一个簇。 - 如果许多样本点具有低轮廓系数甚至负值,则聚类是不合适的聚类的超参数K可能设定得太大或者太小。

在sklearn中,使用silhouette_score计算所有样本的轮廓系数的均值,使用silhouette_sample计算每个样本自己的轮廓系数

1
2
3
4
5
6
7
8
9
10
from sklearn.metrics import silhouette_score
from sklearn.metrics import silhouette_samples

# silhouette_score
result_score = silhouette_score(x,labels)
print(result_score)

# silhouette_samples
result_samples = silhouette_samples(x,labels)
print(result_samples)

  • 因此我们可以通过循环K值的方式找到最合适的簇数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 利用轮廓系数检验簇数(或质心数)

    # 方式一:使用silhouette_score
    inertia_list = []
    for i in range(3,10):
    cluster = KMeans(n_clusters=i,random_state=0).fit(x)
    y_pred = cluster.labels_
    result_score = silhouette_score(x, y_pred)
    inertia_list.append(result_score)
    print(inertia_list)

    # 方式二:使用silhouette_samples
    for i in range(3,10):
    cluster = KMeans(n_clusters=i,random_state=0).fit(x)
    y_pred = cluster.labels_
    result_samples = silhouette_samples(x, y_pred)
    print(result_samples)

  • 使用可视化工具可以更直观地找到拐点

1
2
3
4
plt.figure()
axis_x = list(range(3, 10))
plt.plot(axis_x, inertia_list)
plt.show()

可见,当K=4的时候,轮廓系数接近1,聚类结果最好

DBSCAN算法

算法过程

  • 定义: DBSCAN 是一种基于密度的聚类算法,这类密度聚类算法一般假定类别可以通过样本分布的紧密程度决定。通过将紧密相连的样本划为一类,这样就得到了一个聚类类别。通过将所有各组紧密相连的样本划为各个不同的类别,则我们就得到了最终的所有聚类类别结果。

以下是需要了解的基本概念,在DBSCAN中需要人为指定的参数有半径ϵ和阈值MinPts: 1. ϵ-邻域:样本x以ϵ为半径的范围内包含的所有样本的集合,这个集合称为x的ϵ-邻域。 2. 核心对象:样本x的ϵ-领域内样本数大于阈值MinPts,则称x为核心对象。

对于样本点,我们可以分为3类: 1. 核心点:核心对象内部的点 2. 边缘点:核心对象内部但没有后继的点 3. 噪音点:不在任何核心对象内部的点

  • 算法过程:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    将所有的样本标记为未访问状态
    loop
    随机选择一个未访问对象p,将其标记为已访问
    如果p为核心对象,创建一个新簇C,将p的ϵ-邻域添加到C中
    对于C中的每一个样本q,如果q为核心对象,遍历其ϵ-邻域内的所有样本x
    loop
    if x未被访问,则标记为已访问,将x添加到C中
    else x已被访问但是不属于任何一个簇,将x添加到C中
    end
    end
    输出所有簇

程序实现

详细文档:https://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html#sklearn.cluster.DBSCAN

创建数据集

我们可以通过make_circles或者make_moons生成有特殊形状的数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.datasets import make_circles
from sklearn.datasets import make_moons
import matplotlib.pylab as plt

x1, y1 = make_circles(
n_samples=3000, # 样本数
factor=0.3, # 内环和外环的比例因子,越小越分离
noise=0.05, # 噪声值,越小越向心
random_state=666) # 随机数种子,保证每次创建的数据集一样
x2, y2 = make_moons(
n_samples=3000,
noise=0.05, # 噪声值,越小则两条线越清晰
random_state=666)
plt.subplot(1, 2, 1)
plt.title('make_circles')
plt.scatter(x1[:, 0], x1[:, 1], s=8)
plt.subplot(1, 2, 2)
plt.title('make_moons')
plt.scatter(x2[:, 0], x2[:, 1], s=8)
plt.show()

DBSCAN聚类

  1. DBSCAN有两个重要参数:eps:邻域半径;min_samples:一个核心对象应该拥有的最少样本数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    from sklearn.cluster import DBSCAN
    from sklearn.cluster import KMeans

    # 使用DBSCAN聚类
    y_pred1 = DBSCAN(eps=0.2, min_samples=5).fit_predict(x1)

    # 使用KMeans聚类
    cluster = KMeans(n_clusters=2)
    y_pred2 = cluster.fit_predict(x1)
    centroid = cluster.cluster_centers_

    # 数据可视化
    plt.figure()
    plt.subplot(1, 2, 1)
    plt.title('DBSCAN')
    plt.scatter(x1[:, 0], x1[:, 1], c=y_pred1)
    plt.subplot(1, 2, 2)
    plt.title('K-Means')
    plt.scatter(x1[:, 0], x1[:, 1], c=y_pred2)
    plt.scatter(centroid[:, 0], centroid[:, 1], marker='x', s=100, c='black')
    plt.show()

其余参数详见文档

可见,K-Means与DBSCAN的区别:K-Means只能用于具有明确定义的质心(比如均值或中位数)的数据;DBSCAN要求密度定义(基于传统的欧几里得密度概念)对于数据是有意义的。

  1. 重要属性labels_:指聚类完成后,每个样本所对应的簇数
    1
    2
    db_labels_ = DBSCAN(eps=0.3, min_samples=10).fit(x).labels_
    print(db_labels_)
    其中,-1代表该点属于噪音点

聚类练习:鸢尾花

  • 数据集探索
  1. 导包
    1
    2
    3
    4
    5
    6
    7
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt

    from sklearn.datasets import load_iris
    from sklearn.cluster import KMeans
    from sklearn.preprocessing import MinMaxScaler
  2. 加载数据集

iris也称鸢尾花卉数据集,是一类多重变量分析的数据集,常用于分类实验。数据集包含150个数据样本,分为3类,每类50个数据,每个数据包含4个属性。可通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 加载数据集
iris_data = load_iris()

# load_iris()返回的是一个 Bunch 对象,与字典非常相似,里面包含键和值
print(iris_data.keys())

# 鸢尾花的三个品种:setosa,versicolor,virginica
target_names = iris_data.target_names
print(target_names)

# 鸢尾花的四个属性:花萼长度,花萼宽度,花瓣长度,花瓣宽度
feature_names = iris_data.feature_names
print(feature_names)
3. 获取数据集中的数据
1
2
3
# 获取每朵花的属性数据
x = iris_data.data
print(x)
- 数据预处理

不同特征之间往往具有不同的量纲,由此所造成的数值间的差异可能很大,在涉及空间距离计算或梯度下降法等情况的时候不对其进行处理会影响到数据分析结果的准确性。为了消除特征之间的量纲和取值范围差异可能会造成的影响,需对数据进行标准化处理,也可以称为规范化处理。

1
2
3
# 数据预处理:标准差标准化
MMS = MinMaxScaler().fit(x)
data = MMS.transform(x)
- 使用K-Means聚类算法
1
2
3
4
5
6
7
8
# 3.构建KMeans模型训练数据
cluster = KMeans(n_clusters=3,random_state=10,n_init=10).fit(data)
# 3.1 获取聚类结果
y_pred = cluster.labels_
# 3.2 获取质心
centers = cluster.cluster_centers_
# 3.3 查看簇内平方和
inertia = cluster.inertia_
- 数据可视化

由于数据集是四维的(即四个特征),我们需要对其进行降维处理,目标是降到二维平面使用散点图进行数据可视化。这里我们使用TSNE降维法:

1
2
3
4
5
6
# 4.聚类结果可视化
from sklearn.manifold import TSNE
# 进行数据降维处理
tsne = TSNE(n_components=2,init='random',random_state=0).fit(data)
df = pd.DataFrame(tsne.embedding_)
print(df)
1
2
3
4
# 绘制散点图
plt.figure(figsize=(8,6))
plt.scatter(df[0],df[1],c=y_pred,s=8)
plt.show()

  • 聚类模型评估
  1. 轮廓系数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 5.聚类模型评估
    # 5.1轮廓系数
    from sklearn.metrics import silhouette_score
    silhouetteScore = []
    for i in range(2,10):
    # 构建并训练模型
    kmeans = KMeans(n_clusters=i,random_state=0,n_init=10).fit(data)
    score = silhouette_score(data,kmeans.labels_)
    silhouetteScore.append(score)
    plt.figure(figsize=(8,6))
    plt.plot(range(2,10),silhouetteScore,linewidth=1.5,linestyle='-')
    plt.show()

  1. 卡林斯基 - 哈拉巴斯指数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 5.2 卡林斯基-哈拉巴斯指数
    from sklearn.metrics import calinski_harabasz_score
    chs = []
    for i in range(2,10):
    # 构建聚类模型
    kmeans = KMeans(n_clusters=i,random_state=0,n_init=10).fit(data)
    chsScore = calinski_harabasz_score(data,kmeans.labels_)
    chs.append(chsScore)
    plt.figure(figsize=(10, 8))
    plt.plot(range(2, 10), chs, linewidth=1.5, linestyle='-')
    plt.show()

  1. FMI评价法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 5.3 FMI评价法
    # 注意:需要有真实标签
    y = iris_data.target

    from sklearn.metrics import fowlkes_mallows_score
    fms = []
    for i in range(2,10):
    # 构建聚类模型
    kmeans = KMeans(n_clusters=i,random_state=0,n_init=10).fit(data)
    fmsScore = fowlkes_mallows_score(y,kmeans.labels_)
    fms.append(fmsScore)
    plt.figure(figsize=(10, 8))
    plt.plot(range(2, 10), fms, linewidth=1.5, linestyle='-')
    plt.show()
  • 完整代码