【作者主页】Francek Chen
【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科,通过算法和模型让计算机从数据中学习,进行模型训练和优化,做出预测、分类和决策支持。Python成为机器学习的首选语言,依赖于强大的开源库如Scikit-learn、TensorFlow和PyTorch。本专栏介绍机器学习的相关算法以及基于Python的算法实现。
【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/Python_machine_learning。
文章目录
- 一、k均值聚类的原理
- 二、动手实现k均值算法
- 三、k-means++算法
- 四、Sklearn中的k-means算法
- 五、拓展:Sklearn中的层次聚类和密度聚类
- (一)层次聚类
- (二)密度聚类之DBSCAN算法
本文开始我们讲解无监督学习算法。在之前的文章中,我们给模型的任务通常是找到样本 x \boldsymbol x x与标签 y y y之间的对应关系。在训练时,模型对样本 x \boldsymbol x x给出预测 y ^ \hat y y^,再通过损失函数计算 y ^ \hat y y^与 y y y之间的偏差,从而优化模型。在这一过程中,真实标签 y y y保证我们可以根据模型的预测 y ^ \hat y y^来调整模型,起到了“监督”模型输出的作用。因此,这样的学习过程称为监督学习(supervised learning)。还有一类任务中,我们只有样本 x \boldsymbol x x,却没有其标签 y y y。这类任务也不再是通过 x \boldsymbol x x去预测 y y y,而是通过样本的特征找到样本之间的关联。由于没有标签作为监督信号,这一过程被称为无监督学习(unsupervised learning)。监督学习和无监督学习在某些情况下可以互相转化。例如在卷积神经网络一文中,我们通过训练集中的图像与其类别得到模型,在测试集上完成了图像分类任务,这是有监督学习的过程。我们最后给图像分类的依据是由模型提取出的图像的特征。然而,即使我们一开始就不知道图像的真实类别,也可以通过图像特征之间的相似程度判断出来哪些图像属于同一类。也就是说,我们可以在仅有图像的情况下把猫和狗的图像分为两类,而类别无非是告诉我们这两类分别叫“猫”和“狗”而已。
本文我们将要讲解的k均值(k-means)聚类算法就是一个无监督学习算法。它的目标是将数据集中的样本根据其特征分为几个类,使得每一类内部样本的特征都尽可能相近,这样的任务通常称为聚类任务。作为最简单的聚类算法,k均值算法在现实中有广泛的应用。下面,我们就来详细讲解k均值算法的原理,然后动手实现该算法,最后我们将介绍改进的k-means++算法。
一、k均值聚类的原理
假设空间中有一些点,聚类问题的目标就是将这些点按距离分成数类。设数据集 D = { x 1 , ⋯ , x M } \mathcal D=\{\boldsymbol x_1,\cdots,\boldsymbol x_M\} D={x1,⋯,xM},其中每个样本 x i ∈ R n \boldsymbol x_i\in \mathbb R^n xi∈Rn 的特征维数都是 n n n。最终聚簇的个数 K K K由我们提前指定。直观上来说,同一类的点之间距离应该比不同类的点之间的距离远。但是,由于我们没有任何点的真实标签,所以也无法在最开始确定每一类的中心(centroid),以其为基准计算距离并分类。针对这一问题,k均值算法提出了一个非常简单的解决方案:在初始时,随机选取数据集中的 K K K个样本 μ 1 , ⋯ , μ K \boldsymbol\mu_1,\cdots,\boldsymbol\mu_K μ1,⋯,μK,将 μ i \boldsymbol\mu_i μi作为第 i i i类的中心。选取中心后,我们用最简单的方式,把数据集中的点归到最近的中心点所代表的类中。记第 i i i类包含样本的集合为 C i \mathcal C_i Ci,两点之间的距离函数为 d d d,那么 C i \mathcal C_i Ci可以写为 C i = { x j ∈ D ∣ ∀ l ≠ i , d ( x j , μ i ) ≤ d ( x j , μ l ) } \mathcal C_i=\{\boldsymbol x_j\in\mathcal D|\forall l\ne i,d(\boldsymbol x_j,\boldsymbol\mu_i)\le d(\boldsymbol x_j,\boldsymbol\mu_l)\} Ci={xj∈D∣∀l=i,d(xj,μi)≤d(xj,μl)}
当然,仅仅随机选取中心点还不够,我们还要继续进行优化,尽可能减小类内的点到中心点距离。将数据集中所有点到其对应中心距离之和作为损失函数,得到 L ( C 1 , ⋯ , C K ) = ∑ i = 1 K ∑ x ∈ C i d ( x , μ i ) = ∑ i = 1 K ∑ j = 1 M I ( x j ∈ C i ) d ( x j , μ i ) \mathcal L(\mathcal C_1,\cdots,\mathcal C_K)=\sum_{i=1}^K\sum_{\boldsymbol x\in\mathcal C_i}d(\boldsymbol x,\boldsymbol\mu_i)=\sum_{i=1}^K\sum_{j=1}^M\mathbb I(\boldsymbol x_j\in\mathcal C_i)d(\boldsymbol x_j,\boldsymbol\mu_i) L(C1,⋯,CK)=i=1∑Kx∈Ci∑d(x,μi)=i=1∑Kj=1∑MI(xj∈Ci)d(xj,μi)
既然在初始时,各个类的中心点 μ i \boldsymbol\mu_i μi是随机选取的,那么我们应当再选取新的中心点,使得损失函数的值最小。将上式对 μ i \boldsymbol\mu_i μi求偏导,得 ∂ L ∂ μ i = ∑ x ∈ C i ∂ d ( x , μ i ) ∂ μ i \frac{\partial\mathcal L}{\partial\boldsymbol\mu_i}=\sum_{\boldsymbol x\in\mathcal C_i}\frac{\partial d(\boldsymbol x,\boldsymbol\mu_i)}{\partial\boldsymbol\mu_i} ∂μi∂L=x∈Ci∑∂μi∂d(x,μi)
如果我们用欧氏距离的平方作为度量标准,即 d ( x , μ ) = ∥ x − μ ∥ 2 d(\boldsymbol x,\boldsymbol\mu)=\Vert\boldsymbol x-\boldsymbol\mu\Vert^2 d(x,μ)=∥x−μ∥2,上式可以进一步计算为 ∂ L ∂ μ i = ∑ x ∈ C i ∂ ∥ x − μ i ∥ 2 ∂ μ i = 2 ∑ x ∈ C i ( x − μ i ) = 2 ∑ x ∈ C i x − 2 ∣ C i ∣ μ i \frac{\partial\mathcal L}{\partial\boldsymbol\mu_i}=\sum_{\boldsymbol x\in\mathcal C_i}\frac{\partial\Vert\boldsymbol x-\boldsymbol\mu_i\Vert^2}{\partial\boldsymbol\mu_i}=2\sum_{\boldsymbol x\in\mathcal C_i}(\boldsymbol x-\boldsymbol\mu_i)=2\sum_{\boldsymbol x\in\mathcal C_i}\boldsymbol x-2|\mathcal C_i|\boldsymbol\mu_i ∂μi∂L=x∈Ci∑∂μi∂∥x−μi∥2=2x∈Ci∑(x−μi)=2x∈Ci∑x−2∣Ci∣μi
令该偏导数为零,就得到最优的中心点为: μ i = 1 ∣ C i ∣ ∑ x ∈ C i x \boldsymbol\mu_i=\frac{1}{|\mathcal C_i|}\sum_{\boldsymbol x\in\mathcal C_i}\boldsymbol x μi=∣Ci∣1x∈Ci∑x
上式表明,最优中心点就是 C i \mathcal C_i Ci中所有点的质心。但是,当中心点更新后,每个样本距离最近的中心点可能也会发生变化。因此,我们重新计算每个样本点到中心点的距离,对它们重新分类,再计算新的质心。如此反复迭代,直到各个点的分类几乎不再变化或者达到预设的迭代次数位置。需要注意,如果采用其他的距离函数作为度量,那么最优的中心点就不再是集合的质心。
二、动手实现k均值算法
下面,我们用一个简单的平面点集kmeans_data.csv
来展示k均值聚类算法的效果。首先,我们加载数据集并可视化。数据集中每行包含两个值
x
1
x_1
x1和
x
2
x_2
x2,表示平面上坐标为
(
x
1
,
x
2
)
(x_1,x_2)
(x1,x2)的点。考虑到我们还希望绘制迭代的中间步骤,这里将绘图部分写成一个函数。
import numpy as np
import matplotlib.pyplot as plt
dataset = np.loadtxt('kmeans_data.csv', delimiter=',')
print('数据集大小:', len(dataset))
# 绘图函数
def show_cluster(dataset, cluster, centroids=None):
# dataset:数据
# centroids:聚类中心点的坐标
# cluster:每个样本所属聚类
# 不同种类的颜色,用以区分划分的数据的类别
colors = ['blue', 'red', 'green', 'purple']
markers = ['o', '^', 's', 'd']
# 画出所有样例
K = len(np.unique(cluster))
for i in range(K):
plt.scatter(dataset[cluster == i, 0], dataset[cluster == i, 1], color=colors[i], marker=markers[i])
# 画出中心点
if centroids is not None:
plt.scatter(centroids[:, 0], centroids[:, 1],
color=colors[:K], marker='+', s=150)
plt.show()
# 初始时不区分类别
show_cluster(dataset, np.zeros(len(dataset), dtype=int))
对于简单的k均值算法,初始的中心点是从现有样本中随机选取的,我们将其实现如下。
def random_init(dataset, K):
# 随机选取是不重复的
idx = np.random.choice(np.arange(len(dataset)), size=K, replace=False)
return dataset[idx]
接下来,我们用欧氏距离作为标准,实现上面描述的迭代过程。由于数据集比较简单,我们将迭代的终止条件设置为所有点的分类都不再变化。对于更复杂的数据集,这一条件很可能无法使迭代终止,从而需要我们控制最大迭代次数,或者设置允许类别变动的点的比例等等。
def Kmeans(dataset, K, init_cent):
# dataset:数据集
# K:目标聚类数
# init_cent:初始化中心点的函数
centroids = init_cent(dataset, K)
cluster = np.zeros(len(dataset), dtype=int)
changed = True
# 开始迭代
itr = 0
while changed:
changed = False
loss = 0
for i, data in enumerate(dataset):
# 寻找最近的中心点
dis = np.sum((centroids - data) ** 2, axis=-1)
k = np.argmin(dis)
# 更新当前样本所属的聚类
if cluster[i] != k:
cluster[i] = k
changed = True
# 计算损失函数
loss += np.sum((data - centroids[k]) ** 2)
# 绘图
print(f'Iteration {itr}, Loss {loss:.3f}')
show_cluster(dataset, cluster, centroids)
# 更新中心点
for i in range(K):
centroids[i] = np.mean(dataset[cluster == i], axis=0)
itr += 1
return centroids, cluster
最后,我们观察k均值算法在上面的数据集上聚类的过程。根据上面的可视化结果,我们大概可以看出有4个聚类,因此设定 K = 4 K=4 K=4。
np.random.seed(0)
cent, cluster = Kmeans(dataset, 4, random_init)
三、k-means++算法
上面的分类结果与我们的主观感受区别不大。但是,k均值算法对初始选择的聚类中心非常敏感,且极易收敛到局部最小值,因此不同的中心选择可能导致完全不同的划分。通常来说,我们可以用不同的随机种子选择多组初值,最终挑出划分最好的那一个。但是,当聚类个数和数据量较大时,k均值算法运行需要的时间很长,反复调整随机种子也很不方便。因此,改进的k均值算法——k-means++算法提出了一种新的初始中心选择方法,使算法整体对随机种子的依赖大大减小。
首先,k-means++算法从所有样本中随机选取一个点当作第一个聚类的中心点。直观上来讲,我们希望初始的中心点尽可能散开。因此在选择接下来的中心点时,该算法会将样本到当前中心点的距离也纳入考量。设目前已有 k k k个中心点,分别是 μ 1 , ⋯ , μ k \boldsymbol\mu_1,\cdots,\boldsymbol\mu_k μ1,⋯,μk,对于样本 x \boldsymbol x x,其他与最近的中心点的距离为 D ( x ) D(\boldsymbol x) D(x)。为了使各个中心点之间的距离尽可能大,令 x \boldsymbol x x被选为第 k + 1 k+1 k+1 个中心点的概率为 P ( μ k + 1 = x ) = D 2 ( x ) ∑ x D 2 ( x ) P(\boldsymbol\mu_{k+1}=\boldsymbol x)=\frac{D^2(\boldsymbol x)}{\sum\limits_{\boldsymbol x}D^2(\boldsymbol x)} P(μk+1=x)=x∑D2(x)D2(x)
上式的分母是在整个数据集上进行的求和,使所有样本被选为中心点的概率值和为1。上式的含义是,样本 x \boldsymbol x x被选为中心点的概率与其到当前中心点距离的平方成正比。我们重复这一过程,直到 K K K个聚类中心都被选出为止。
下面,我们来实现k-means++的初始化函数。
def kmeanspp_init(dataset, K):
# 随机第一个中心点
idx = np.random.choice(np.arange(len(dataset)))
centroids = dataset[idx][None]
for k in range(1, K):
d = []
# 计算每个点到当前中心点的距离
for data in dataset:
dis = np.sum((centroids - data) ** 2, axis=-1)
# 取最短距离的平方
d.append(np.min(dis) ** 2)
# 归一化
d = np.array(d)
d /= np.sum(d)
# 按概率选取下一个中心点
cent_id = np.random.choice(np.arange(len(dataset)), p=d)
cent = dataset[cent_id]
centroids = np.concatenate([centroids, cent[None]], axis=0)
return centroids
我们已经预留了初始化函数的接口,只需要将参数从random_init
替换为kmeanspp_init
就可以测试k-means++算法的表现了。从绘制的迭代中间结果可以明显看出,用k-means++算法选择的初始中心点互相之间的距离非常远,从而收敛速度也要快很多。大家可以修改随机种子,观察随机初始化和k-means++初始化对随机种子的敏感程度。
cent, cluster = Kmeans(dataset, 4, kmeanspp_init)
四、Sklearn中的k-means算法
使用scikit-learn库中cluster模块的KMeans类可以实现KMeans聚类算法对数据进行聚类,KMeans类的基本使用格式和常用参数说明如下:
class sklearn.cluster.KMeans(n_clusters=8, *, init=‘k-means++’, n_init=10, max_iter=300, tol=0.0001)
参数名称 | 参数说明 |
---|---|
n_clusters | 接收int。表示要形成的簇数以及生成的质心数。默认为8。 |
init | 接收方法名。表示所选择的初始化方法,可选’k-means ++‘,‘random’,ndarray,callable。默认为’k-means ++’。 |
n_init | 接收int。表示K均值算法将在不同质心下运行的次数。默认为10。 |
max_iter | 接收int。表达单次运行的K均值算法的最大迭代次数。默认为300。 |
tol | 接收float。表示两个连续迭代的聚类中心的差异,以声明收敛。默认为1e-4。 |
选择K个算法的流程如下:首先先选择K个聚类个数.直接生成K个中心作为均值定量,或者随机选择K个均值定量,然后作为聚类中心。对每个点确定其聚类中心点。再计算其聚类新中心。重复以上步骤直到满足收敛要求。(通常就是确定的中心点不再改变)。
Kmeans的流程可以用以下一组图表示。
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
blobs = make_blobs(random_state=7,centers=3)
X_blobs = blobs[0]
plt.scatter(X_blobs[:,0],X_blobs[:,1],c='r',edgecolors='k')
plt.show()
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=3)
kmeans.fit(X_blobs)
y_kmeans = kmeans.predict(X_blobs)
plt.scatter(X_blobs[:, 0], X_blobs[:, 1], c=y_kmeans, s=50)
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 0], centers[:, 1],marker='x',c='r', s=200, alpha=0.5)
plt.show()
我们先设置了一个K值为3,然后我们先随机从数据中找出三点作为三个均值定量,然后在计算所有数据各点到质心的距离,然后将是数据分配给距离最近的一类,用不同的颜色表示数据所属各类,然后经过第一轮的迭代后从各类中可以计算新的均值定量,然后计算每个数据点到个类之间的最近距离分到该类里面,重复迭代上述步骤。最终得到了一个合适的均值定量来表示我们的类别。当然在实际运用该算法时,一般会多以迭代,才能得到理想的结果。
通过下列代码观察Kmeans算法的聚点,并观察决策边界。
# 导入必要的库
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
import numpy as np
from sklearn.cluster import KMeans
# 生成带有3个中心的随机数据点
blobs = make_blobs(random_state=7, centers=3)
X_blobs = blobs[0]
# 绘制原始数据点
plt.scatter(X_blobs[:, 0], X_blobs[:, 1], c='r', edgecolors='k')
# 创建KMeans聚类模型,设置聚类数为3
kmeans = KMeans(n_clusters=3)
kmeans.fit(X_blobs)
# 定义图形的边界
X_min, X_max = X_blobs[:, 0].min() - 0.5, X_blobs[:, 0].max() + 0.5
y_min, y_max = X_blobs[:, 1].min() - 0.5, X_blobs[:, 1].max() + 0.5
# 创建网格用于绘制决策边界
xx, yy = np.meshgrid(np.arange(X_min, X_max, .02), np.arange(y_min, y_max, .02))
# 使用KMeans模型预测网格上的每个点的聚类
Z = kmeans.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 创建新的图形并清空之前的内容
plt.figure(1)
plt.clf()
# 绘制聚类决策边界
plt.imshow(Z, interpolation='nearest', extent=(xx.min(), xx.max(), yy.min(), yy.max()), cmap=plt.cm.summer, aspect='auto', origin='lower')
# 绘制原始数据点
plt.plot(X_blobs[:, 0], X_blobs[:, 1], 'r.', markersize=5)
# 绘制聚类中心
centroids = kmeans.cluster_centers_
plt.scatter(centroids[:, 0], centroids[:, 1], marker='x', s=150, linewidths=3, color='b', zorder=10)
# 设置图形的显示范围
plt.xlim(X_min, X_max)
plt.ylim(y_min, y_max)
# 隐藏坐标轴刻度
plt.xticks(())
plt.yticks(())
# 显示图形
plt.show()
看过此算法后,我们很容易与KNN算法搞混,两者都有一个相似的过程,就是都需要找到某一个点最近的点,二者都采用了最近邻的思想。其实两者的差别还是挺大的,Kmeans算法是无监督学习的聚类算法,而KNN算法是监督学习的分类算法。KNN算法基本不需要训练,只要从测试集里面根据距离计算公式找到K个点,用这K个点表示测试机集的类别即可,而Kmeans算法有明显的训练过程,需要反复迭代找到K个类别的最佳质心来决定数据的类别。
Kmeans算法的优点:
- 原理简单、实现容易,是解决聚类问题的一种经典算法,保持可伸缩性和高效率,当数据集是密集的,它的效果较好。
- 算法的可解释性较强,只要需要调参的的参数只有分类数K。
Kmeans算法的缺点:
- 必须事先给出K值,而且对初值敏感,对于不同的初始值,可能会导致不同结果。
- 对躁声和孤立点数据敏感。
- 采用迭代方法,得到的结果只是局部最优。
五、拓展:Sklearn中的层次聚类和密度聚类
(一)层次聚类
使用scikit-learn库中cluster模块的AgglomerativeClustering类可以实现层次聚类算法对数据进行聚类,AgglomerativeClustering类的基本使用格式和常用参数说明如下:
class sklearn.cluster.AgglomerativeClustering(n_clusters=2, *, affinity='euclidean', memory=None, connectivity=None, compute_full_tree='auto', linkage='ward', distance_threshold=None)
参数名称 | 参数说明 |
---|---|
n_clusters | 接收int or None。表示要查找的集群数。默认为2。 |
affinity | 接收str or callable。表示用于计算链接的度量。默认为’euclidean’。 |
memory | 接收具有joblib的内存str或对象。表示用于缓存树计算的输出。默认为None。 |
connectivity | 接收数组或可调用的连接对象。表示连接的矩阵。默认为None。 |
compute_full_tree | 是否计算完整的聚类树,默认是 ‘auto’。 |
linkage | 连接策略,默认是 ‘ward’,可选择 ‘average’, ‘complete’, ‘single’。 |
distance_threshold | 接收float。表示链接距离的阈值。默认为None。 |
层次聚类算法类,是许多基于相同原则构建的聚类算法,算法的原理如下:所谓层次聚类就是让算法在一开始的时候,将每一个点作为一个类别,每一次进行算法合并两个最近的两类,直到满足某个条件为止。sklearn中实现该算法的满足条件是类别的个数,当剩下指定个数的类时,算法自动停止。
这里的参数重要的是链接准则,他的意思是如何寻找最相近的点。sklearn库中包含三种链接方式:ward
:是默认选项,通过挑选两个类来合并,类中的方差增加最小通过这中链接方式通常得到大小差不多相等的簇。average
链接:将簇中所有点之间平均距离最小的两个类合并。complete
链接:也称为最大链接,将簇中点之间最大距离最小的两个类合并。ward适用于大多数数据集。如果类中的成员个数非常不同,那么average或complete可能效果更好。single
连接法(单链接):是指使用最小距离来合并簇。这意味着在两个簇之间,距离最近的两个点的距离被用作簇间距离。这种方法倾向于形成长而狭的簇,可能导致链状结构的形成。适用于处理形状不规则的数据集,但也可能对噪声和离群点敏感。
下面是对数据集采用三种链接准则得到的结果。程序如下:
# 导入必要的库
from sklearn.datasets import make_blobs # 生成随机数据的模块
from sklearn.cluster import AgglomerativeClustering # 导入层次聚类模型
import numpy as np # 数值计算库
import matplotlib.pyplot as plt # 绘图库
from itertools import cycle # 用于循环颜色的迭代器模块
# 定义数据中心
centers = [[1, 1], [-1, -1], [1, -1]]
# 生成随机数据,n_samples是样本数量,centers是中心位置,cluster_std是簇的标准差
X, labels_true = make_blobs(n_samples=2000, centers=centers, cluster_std=0.5, random_state=22)
# 创建层次聚类模型,使用ward链接方法,期望聚类数为4
ac = AgglomerativeClustering(linkage='ward', n_clusters=4)
# ac = AgglomerativeClustering(linkage='average', n_clusters=4)
# ac = AgglomerativeClustering(linkage='complete', n_clusters=4)
# 训练模型并对数据进行聚类
ac.fit(X)
# 获取每个数据点的分类标签
labels = ac.labels_
# 绘图
plt.figure(1) # 创建一个新图形
plt.clf() # 清空当前图形
# 定义颜色循环,使用'r','g','c','y'四种颜色
colors = cycle('rgcy')
# 遍历每个聚类标签(0到3),并绘制相应的数据点
for k, col in zip(range(4), colors):
# 创建一个布尔数组,标识哪些点属于当前聚类
my_members = labels == k
# 绘制属于当前聚类的点,X[my_members,0]为横坐标,X[my_members,1]为纵坐标
plt.plot(X[my_members, 0], X[my_members, 1], col + '.')
# 显示绘制的图形
plt.show()
层次聚类算法的优点:
- 距离和规则的相似度容易定义,限制少
- 不需要预先制定聚类数
- 可以发现类的层次关系
层次聚类算法的缺点:
- 计算复杂度太高
- 奇异值也能产生很大影响
- 算法很可能聚类成链状
(二)密度聚类之DBSCAN算法
使用scikit-learn库中cluster模块的DBSCAN类可以实现密度聚类算法对数据进行聚类,DBSCAN类的基本使用格式和常用参数说明如下:
class sklearn.cluster.DBSCAN(eps=0.5, *, min_samples=5, metric='euclidean', metric_params=None, algorithm='auto', leaf_size=30, p=None, n_jobs=None)
参数名称 | 参数说明 |
---|---|
eps | 接收float。表示邻域半径。默认为0.5。 |
min_samples | 接收int。表示一个点附近的样品数量(或总重量)被视为核心点。默认为5。 |
metric | 接收string or callable。表示计算要素阵列中实例之间的距离时使用的度量。默认为’euclidean’。 |
metric_params | 接收dict。表示度量功能的其他关键字参数。默认为None。 |
algorithm | 接收算法名称。表示NearestNeighbors模块将使用该算法来计算逐点距离并查找最近的邻居。默认为’auto’。 |
leaf_size | 接收int。影响ball_tree和kd_tree的构建和查询速度的参数。较大的值可能会导致更快的查询,但更慢的构建时间。默认为30。 |
p | 接收float。在使用Minkowski距离时的参数,可以设置为1(曼哈顿距离)或2(欧氏距离)。默认为None。 |
n_jobs | 接收int。表示要运行的并行作业数。默认为None。 |
DBSCAN聚类算法是一种基于密度的算法,这一类算法通常通过根据数据的紧密程度进行分类,如果一个数据属于该类,则在其附近一定存在属于该类的数据点。通过紧密分布的数据集分为一类,就得到了一个聚类类别。通过将所有数据划分为不同类别,就得到了最终的聚类类别结果。该算法将具有足够密度的区域划分为一类,并在具有噪声的数据集中能划分出不同形状的类别,它将类定义为密度相连的点的最大集合。那么它是怎么基于密度工作的呢?
在一个数据集中,我们的目标是把数据中密度相近的聚为一类。我们先从数据中选择一点,然后按照一定规则在这里寻找密度相近的点组成一类。其他的数据点也是如此。这个规则就是根据这个随即被选中的数据点画一个圆圈,规定这个圆的半径以及圆内最少包含的数据数,然后在包含在内的数据中转移中心点,那么这个圆圈的圆心就转移到这个内部样本点,继续去圈附近其它的数据点,然后一直重复上述过程,继续增加数据点直到没有符合条件的数据为止。
基于密度这点有什么好处呢,我们知道k-means聚类算法只能根据距离进行计算,而现实中还会有各种形状的图,比如环形图,这个时候,k-means算法就不适用。于是就想到根据数据的密度进行分类。
上述基本是DBSCAN算法的主要内容了,是不是很简单,但是我们还有三个问题没有考虑。第一个是一些数据远离于其他数据点,这些点不在任何一类的周围,在DBSCAN中,我们一般将这些样本点标记为噪音点。第二个是距离的度量问题,即如何计算某样本和核心对象样本的距离。在DBSCAN中,一般采用最近邻思想,采用某一种距离度量来衡量样本距离,比如欧式距离。这和KNN分类算法的最近邻思想完全相同。
DBSCAN这个算法包括了重要的两个参数,这两个参数比较难指定,公认的指定方法是以下两个:
- 半径:半径是比较重要的一个点,如果选择过大了,圈住点就多了,类别的个数就少了,反之圈住的点少,类别就增加,这对最后生成的结果非常重要。
- MinPts:这个参数就是圈住的点的个数,也相当于是一个密度,一般我们先让这个值尽可能地小,然后进行多次尝试算法。
下面我们对DBSCAN算法进行应用。首先我们生成一个环形数据来测试算法。
from sklearn.datasets.samples_generator import make_circles
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
X, y_true = make_circles(n_samples=2000, factor=0.5,noise=0.1) # 环状数据
# DBSCAN算法
dbscan = DBSCAN(eps=.1, min_samples=10)
dbscan.fit(X) # 该算法对应的两个参数
plt.scatter(X[:, 0], X[:, 1], c=dbscan.labels_)
plt.show()
从图中可以看出,算法划分的方法符合我们的思维,并且基本做到了完美划分。接下来试验一下对于双半月数据,算法能不能做到好的预测。
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
X, y_true = make_moons(n_samples=2000, noise=0.1) # 双半月数据
# DBSCAN算法
dbscan = DBSCAN(eps=.1, min_samples=10)
dbscan.fit(X) # 该算法对应的两个参数
plt.scatter(X[:, 0], X[:, 1], c=dbscan.labels_)
plt.show()
从图中看出,依旧能建立较好的模型。测试完之后大家可能感觉没有那么神奇,因为这种方法符合我们的思维,但是这是由计算机划分出来的。与Kmeans算法和层次聚类算法进行对比,我们就能知道这个算法的优越之处了。
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans, AgglomerativeClustering
# 创建数据集
X, y_true = make_moons(n_samples=2000, noise=0.1)
# KMeans聚类
kmeans = KMeans(n_clusters=2)
kmeans.fit(X)
# 层次聚类
ac = AgglomerativeClustering(n_clusters=2)
ac.fit(X)
# 创建子图
fig, axs = plt.subplots(1, 2, figsize=(12, 5))
# KMeans图
axs[0].scatter(X[:, 0], X[:, 1], c=kmeans.labels_, cmap='viridis')
axs[0].set_title('KMeans Clustering')
# Agglomerative Clustering图
axs[1].scatter(X[:, 0], X[:, 1], c=ac.labels_, cmap='viridis')
axs[1].set_title('Agglomerative Clustering')
# 显示图形
plt.tight_layout()
plt.show()
# 层次聚类
ac = AgglomerativeClustering(n_clusters=2)
ac.fit(X)
plt.scatter(X[:, 0], X[:, 1], c=ac.labels_)
plt.show()
可以看到如果分为两类,这种划分方式不符合我们的思维。所以对于不同的问题要选择合适的算法才能解决。
DBSCAN算法的优点:
- 速度快,不需要提前设定K值大小,DBSCAN不需要事先知道要形成种类的数量。
- 对噪声不敏感。可以在聚类的同时发现异常点,对数据集中的异常点不敏感,能发现任意形状的簇。这是因为该算法能够较好地判断离群点,并且即使错判离群点,对最终的聚类结果也没什么影响。
- 聚类生成的种类没有偏倚,而Kmeans之类的聚类算法初始值对聚类结果有很大影响。
DBSCAN算法的缺点:
- 对参数的设置敏感,当半径和密度变化时会很大程度影响结果。
- 当数据量较大时,要求的内存也大。
- 当空间密度不均匀、聚类间距差相差很大时,聚类质量较差。
附:以上文中的数据集及相关资源下载地址:
链接:https://pan.quark.cn/s/a5fa36f23a77
提取码:viFn