本篇文章大部分代码延续了上一篇(Kmeans算法实现(只调用数学运算的库)-优快云博客)
但在自动生成数据函数generate上做了一些改进,代码如下:
def generate_data(size,dimension,centers,cluster_std):
"""
:param size: 表示生成数据的总个数
:param dimension: 表示生成数据的维度
:param centers: 随机生成数据围绕的点
:param cluster_std: 浮点数,表示簇内数据的标准差
:return: 返回生成的数据
"""
all_data = []
for center in centers:
data = np.random.randn(size,dimension)*cluster_std+np.array(center)
all_data.append(data)
all_points = np.vstack(all_data)
return all_points
尝试一:自定义三个点,在这三个点的周围生成随机数据,不断地把这三个点靠拢,观察Kmeans聚类算法的效果
第一次:定义三点为(-5, -4), (-2, 0), (2, 4),簇中心为3,每个点周围数据量为20个,聚类结果如下图所示:
第二次:定义三点为(-4, -1), (-2, 0), (1, 2),簇中心为3,每个点周围数据量为20个,聚类结果如下图所示:
第三次:定义三点为(-2, -1), (0, 0), (3, 4),簇中心为3,每个点周围数据量为20个,聚类结果如下图所示:
我们可以观察到,在(-2,-1)和(0,0)两点距离很小的情况下,数据本可以聚类为两簇,而不是三簇。这是由于Kmeans算法必须要提前定义簇的数量,按照簇的数量进行聚类。那么如何解决确定簇数量的问题呢?
按照吴恩达老师的想法,我们可以将损失函数的值与K(簇的个数)关联成表格,并选取损失函数的“肘部”,这样就可以得到一个数据上相对容易判断的K个数。但并不是所有的数据集在聚类数与总内平方和的关系图中都有一个明显的“肘点”。有时这个曲线可能是平滑的,没有明显的转折点。其次,数据集本身可能具有极其复杂的结构,如多层次的聚类或不同形状的聚类,这使得“肘点”的识别更加困难。再者,噪声和异常值可能会影响聚类的总内平方和,使得肘点的位置不够稳定,或者在某些情况下使肘点看起来不显著。
这里我希望使用相似于戴维森堡丁指数的方式对算法进行改进:通过引入一个额外的约束条件(min_distance)来改进K-means算法,使得聚类结果更加符合实际应用的需求。min_distance用来表示允许簇中心之间的最小距离。
改进后的算法步骤为:
1、初始化:使用随机样本点初始化聚类中心,以减少初始中心选择对最终结果的影响。
2、聚类:执行标准的K-means聚类算法,直到收敛。
3、计算簇间距离:在第一轮聚类结束后,计算所有聚类中心之间的距离,并记录这些距离
4、检查min_distance:遍历所有聚类中心对,检查它们之间的距离是否小于min_distance。如果是,将这两个聚类中心合并为一个新的聚类中心。
5、重新分配样本:对于被合并的聚类中心,将属于这两个簇的所有样本重新分配到新的聚类中心。这可能需要重新计算每个样本到新聚类中心的距离,并重新分配样本。
6、重新聚类:使用更新后的聚类中心和重新分配的样本,再次执行K-means聚类算法。
6、重复步骤3-6:重复上述步骤,直到所有聚类中心之间的距离都大于min_distance。
以下代码使用了scikit-learn库中的KMeans算法作为基础实现刚刚提出的算法:
def new_kmeans(data, min_distance, max_iters=100):
n_samples, n_features = data.shape
centroids = KMeans(n_clusters=2, n_init=10, init='k-means++', max_iter=100, random_state=42).fit(data).cluster_centers_
cluster_labels = KMeans(n_clusters=2, n_init=10, init=centroids, max_iter=100, random_state=42).fit_predict(data)
for _ in range(max_iters):
# 计算簇中心
new_centroids = np.array([data[cluster_labels == i].mean(axis=0) for i in range(len(centroids))])
# 计算簇间距离
distances = euclidean_distances(new_centroids)
min_dist = np.min(distances[np.triu_indices(len(centroids), k=1)])
# 如果簇间最小距离小于min_distance,则合并距离最近的两个簇
if min_dist < min_distance:
# 找到距离最近的两个簇
idx = np.unravel_index(np.argmin(distances, axis=None), distances.shape)
cluster1, cluster2 = idx
# 合并簇中心
new_centroids[cluster1] = (new_centroids[cluster1] + new_centroids[cluster2]) / 2
# 重新分配样本
cluster_labels = np.array([new_centroids[cluster1] for _ in range(n_samples)])
cluster_labels[cluster_labels == cluster2] = cluster1
# 更新簇数
centroids = new_centroids
else:
break
return cluster_labels, centroids
改进的算法通过限制簇之间的最小距离,可以防止聚类过于细化,从而避免过拟合现象。这有助于在保持数据特征的同时,减少模型复杂度。其次,算法通过合并距离过近的簇,可以减少聚类结果对初始中心选择的敏感性,从而提高聚类的稳定性。与Kmeans算法相比,改进算法允许根据数据的实际分布调整聚类数,而不是预先设定一个固定的K值。这使得算法更加灵活,能够适应不同类型和分布的数据集。最后,从输入的数据集角度来看,这种方法不依赖于特定的数据类型或特征,因此具有较好的通用性,可以应用于多种不同的数据集和应用场景。
当然,改进的算法也具有欠缺,这种方法可能需要调整到一个合适的min_distance,以找到最佳的聚类效果。这可以通过交叉验证或实验不同的min_distance值来实现。最终的目标是找到一个既能保证聚类效果,又能控制计算成本的平衡点。