带约束的K-means聚类算法

带约束的K-means 聚类算法

1. 前言

上一期学习了K-means聚类算法,聚类是不受到限制的,单纯的无监督学习,但是当存在一些约束时,比如对每一簇的聚类样本点数量有限制,或者每个样本点带需求,每一簇存在满足约束的最大能力,这时候应该怎么添加约束,并且不破坏聚类的效果。

2. 场景构建

假设场景:有一定数量的区域需要分配邮递员配送信件,怎么将区域划分给邮递员?每个区域有一定的需求量,一个邮递员一天的工作量只能满足一定的需求,那么应该怎么将区域划分给邮递员?考虑到成本问题,这里设置一个目标——在满足需求的情况下最小化邮递员数量。

这样的场景在快递行业应该很常见,不太清楚实际业务中是怎么分配的,但我觉得带约束的聚类算法应该能求解一个可行解,为了给K-means加约束构想的场景,场景简化了很多,可能不太符合实际,欢迎提建议。

3. 带约束的K-means 聚类设计

在这里插入图片描述

K-means聚类算法流程

算法说明:由于存在需求约束,那么就存在最小的K(总需求/簇的能力),可以从最小值K开始遍历。而约束就加在(3)将对象分配到最相似的簇中,具体修改为:在满足约束的情况下,将对象分配到最相似的簇中,如果最相似的簇无法容纳,就找第二相似的簇,依次类推。

遍历改进:对遍历对象进行一个小的修改,通常K-means是随机/按列表顺序遍历对象,将对象分配到最相似的簇,这里改进为:取所有对象中距离某个簇最近的点,优先分配(这算是随机/按列表顺序遍历对象的一个特殊情况)。

初始解敏感:为了减小K-means对初始解敏感的问题,这里采用多次求解的方式保留最优的方式降低对初始解的依赖。

效果评价:聚类效果使用轮廓系数进行评价。

3. python实现

# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import random
import math
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei']  # 添加这条可以让图形显示中文


def calDistance(centers,nodes):
    ''' 
    计算节点间距离
    输入:centers-中心,nodes-节点;
    输出:距离矩阵-dis_matrix
    '''
    dis_matrix = pd.DataFrame(data=None,columns=range(len(centers)),index=range(len(nodes)))
    for i in range(len(nodes)):
        xi,yi = nodes[i][0],nodes[i][1]
        for j in range(len(centers)):
            xj,yj = centers[j][0],centers[j][1]
            dis_matrix.iloc[i,j] = round(math.sqrt((xi-xj)**2+(yi-yj)**2),2)
    
    return dis_matrix


def scatter_diagram(clusters,nodes):
    '''
    #画路径图
    输入:nodes-节点坐标;
    输出:散点图
    '''
    for cluster in clusters:
        x,y= [],[]
        for Coordinate in cluster:
            x.append(Coordinate[0])
            y.append(Coordinate[1])
        plt.scatter(x, y,alpha=0.8,)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()


def distribute(center,nodes,K,demand,d_limit):
    '''
    将节节点分给最近的中心
    输入:center-中心,nodes-节点,K-类数量,demand-需求,d_limit-一个簇的满足需求的能力
    输出:新的簇-clusters,簇的需求-clusters_d
    '''
    clusters = [[] for i in range(K)]#簇
    label = [None for i in range(len(nodes))]
    clusters_d = [0 for i in range(K)]#簇需求
    
    dis_matrix = calDistance(center,nodes).astype('float64')
    
    for i in range(len(dis_matrix)):
        row, col = dis_matrix.stack().idxmin()
        node = nodes[row]
        j = 1
        while clusters_d[col] + demand[row] > d_limit:#检验约束
            if j < K: 
                j += 1
                dis_matrix.loc[row,col] = math.pow(10,10)
                col = dis_matrix.loc[row,:].idxmin()
            else:#所有类都不满足需求量约束
                print("K较小")
                scatter_diagram(clusters,nodes)
                return None
            
        #满足约束正常分配
        clusters[col].append(node)
        label[row] = col
        clusters_d[col] += demand[row]
        dis_matrix.loc[row,:] = math.pow(10,10)
        
    return clusters,clusters_d,label


def cal_center(clusters):
    '''
    计算簇的中心
    输入:clusters-类坐标;
    输出:簇中心-new_center
    '''
    new_center = []
    for cluster in clusters:
        x,y= [],[]
        for Coordinate in cluster:
            x.append(Coordinate[0])
            y.append(Coordinate[1])
        x_mean = np.mean(x)
        y_mean = np.mean(y)
        new_center.append((round(x_mean,2),round(y_mean,2)))
    
    return new_center
    

if __name__ == "__main__":
    #输入数据
    nodes = [(11, 36),(13, 35),(14, 12),(25, 22),(15, 11),(16, 24),(14, 30),(16, 32),(26, 10),(27, 31),(22, 29),(1, 29),(20, 29),(17, 15),(5, 38),
             (3, 15),(22, 20),(4, 13),(4, 22),(12, 18),(23, 12),(18, 15),(5, 13),(9, 27),(17, 36),(2, 14),(1, 16),(7, 15),(25, 15),(19, 26),(25, 40),
             (26, 34),(25, 35),(2, 36),(29, 24),(17, 17),(8, 26),(4, 14),(5, 25),(6, 37),(1, 14),(6, 39),(11, 13),(10, 20),(21, 11),(5, 19),(5, 35),(1, 34),
             (16, 39),(19, 24),(39, 31),(49, 31),(41, 50),(31, 33),(32, 40),(35, 30),(31, 39),(34, 48),(42, 32),(32, 35),(35, 33),(35, 34),(43, 41),(35, 47),
             (49, 36),(37, 41),(43, 46),(41, 41),(45, 50),(41, 35),(45, 44),(41, 30),(43, 33),(31, 45),(48, 32),(39, 49),(38, 42),(33, 39),(49, 33),(43, 44),
             (32, 30),(40, 47),(36, 46),(47, 47),(37, 33),(35, 31),(42, 38),(43, 47),(30, 47),(30, 30),(37, 34),(41, 45),(27, 33),(42, 39),(43, 43),(50, 43),
             (28, 40),(35, 41),(32, 41),(31, 30)]
    demand = [3,3,5,9,6,1,4,8,8,3,6,5,6,4,1,4,7,1,3,6,2,5,7,2,2,1,9,3,7,8,4,3,1,2,5,1,3,4,8,5,2,9,3,10,6,1,9,3,5,3,3,3,9,9,6,2,5,2,4,
              10,4,10,10,6,3,7,9,4,2,9,7,4,5,3,3,8,2,3,2,9,1,3,3,3,9,5,7,8,8,5,2,8,3,4,2,4,6,3,7,4]
    
    scatter_diagram([list(nodes)],nodes)#初始位置分布图
    
    #参数
    d_limit = 150 # 一个区的约束
    K = math.ceil(sum(demand)/d_limit)  #簇的最小边界
    
    #历史最优参数
    best_s = -1
    best_center = 0
    best_clusters = 0
    best_labels = 0
    best_clusters_d = 0
    
    for n in range(20):#多次遍历,kmeans对初始解敏感
        #初始化随机生成簇中心
        index = random.sample(list(range(len(nodes))),K)
        new_center = [nodes[i] for i in index]
        i = 1
        center_list = []
        while True:
        	#节点——> 簇
            center = new_center.copy()
            center_list.append(center)#保留中心,避免出现A生成B,B生成C,C有生成A的情况
            clusters,cluster_d,label = distribute(center,nodes,K,demand,d_limit)
            new_center = cal_center(clusters)
            if (center == new_center) | (new_center in center_list):
                break
            i += 1
            
        s = silhouette_score(nodes,label, metric='euclidean') # 计算轮廓系数,聚类的评价指标
        # scatter_diagram(clusters,nodes)#当前最优图
        
        if best_s < s:
            best_s = s
            best_clusters_d = cluster_d
            best_center = center.copy()
            best_clusters = clusters.copy()
            best_labels = label.copy()
    
    scatter_diagram(best_clusters,nodes)#历史最优图
    # print("各类需求量:",best_clusters_d)
    

【结果展示】

在这里插入图片描述

图1 需求点位置

在这里插入图片描述

图2 聚类效果

4 谈论

从结果上看,带约束的聚类效果还是可以的,但是求解过程中会出现图3的聚类效果,蓝色的点距离黄色的点反而更近一点。而且还存在另外一个问题,假设需求量是500,而每簇满足需求的能力是100,理论上最少需要5个簇就可以,但是由于约束的存在和需求不可拆分,可能会导致无法刚好满足五个簇的容量,遇到这种情况我写的算法中是直接报错,虽然这种情况下大概率是出现如图3蓝色点的情况存在,但是也会减小求解最优解的可能性,程序的健壮性较差。

在这里插入图片描述

图3 聚类效果
约束种子k-means聚类算法是一种改进的k-means聚类算法,它在传统的k-means算法基础上引入了约束条件,以提高聚类的准确性和效率。其基本原理如下: 1. **种子选择**:与传统的k-means算法不同,约束种子k-means算法首先选择一组种子点作为初始聚类中心。这些种子点可以是用户指定的,也可以是通过某种启发式方法自动选择的。 2. **约束条件**:在选择种子点时,可以引入一些约束条件,例如种子点之间的最小距离、种子点的分布均匀性等。这些约束条件有助于避免初始聚类中心过于集中,从而提高聚类的效果。 3. **聚类过程**:在选择好种子点后,约束种子k-means算法与传统的k-means算法类似,通过迭代的方式将数据点分配到最近的聚类中心,并重新计算聚类中心。具体步骤如下: - **分配步骤**:将每个数据点分配到最近的聚类中心。 - **更新步骤**:重新计算每个聚类的中心(即均值)。 4. **迭代收敛**:重复分配步骤和更新步骤,直到聚类中心不再显著变化或达到预定的迭代次数。 5. **约束应用**:在每次迭代中,约束条件会被重新应用,以确保聚类结果满足预定的约束。例如,可以强制某些数据点始终属于同一个聚类,或者限制某些聚类的大小。 约束种子k-means聚类算法通过引入约束条件,能够更好地处理复杂的数据分布情况,提高聚类的准确性和鲁棒性。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值