2、k-means聚类算法sklearn与手动实现

news/2024/5/20 10:37:41 标签: 算法, kmeans, 聚类

本文将对k-means聚类算法原理和实现过程进行简述

算法原理

k-means算法原理较简单,基本步骤如下:

1、假定我们要对N个样本观测做聚类,要求聚为K类,首先选择K个点作为初始中心点;
2、接下来,按照距离初始中心点最小的原则,把所有观测分到各中心点所在的类中;
3、每类中有若干个观测,计算K个类中所有样本点的均值,作为第二次迭代的K个中心点;
4、然后根据这个中心重复第2、3步,直到收敛(中心点不再改变或达到指定的迭代次数),聚类过程结束。

聚类过程示意图:
在这里插入图片描述

算法实践

下面对一个具体场景做聚类分析:500x500px的地图上,随机生成60个城市,要求生成10个聚类中心。

Sklearn实现

下面是调取sklearn相关的函数进行实现:

import matplotlib.pyplot as plt
import numpy as np
import warnings

from sklearn.cluster import KMeans

Num_dots = 60  # 城市总数
Num_gas = 10  # 聚类中心总数
Size_map = 500  # 500x500地图


colors = ['#FF3838', '#FF9D97', '#FF701F', '#FFB21D', '#CFD231', '#48F90A', '#92CC17', '#3DDB86', '#1A9334', '#00D4BB',
          '#2C99A8', '#00C2FF', '#344593', '#6473FF', '#0018EC', '8438FF', '#520085', '#CB38FF', '#FF95C8', '#FF37C7']

warnings.filterwarnings("ignore")

# 生成随机点
def generate():
    dots = []
    for i in range(Num_dots):
        dots.append(np.random.uniform([Size_map, Size_map]))
    # dots_sorted_x = sorted(dots, key=lambda dot: dot[0])
    return dots

# 计算两点之间欧式距离
def cal_dist(x, y):
    return ((x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2) ** 0.5

# 统计数组中各种相同元素个数
def num_same(dots_labels):
    num_labels = []
    key = np.unique(dots_labels)
    for k in key:
        mask = (dots_labels == k)
        y_new = dots_labels[mask]
        v = y_new.size
        num_labels.append(v)
    return num_labels

def cal_center_dist(center, dots):
    distance = 0
    for i in range(len(dots)):
        distance += cal_dist(center, dots[i])
    return distance

# K-Means聚类
def k_means(dots):
    cluster = KMeans(n_clusters=Num_gas)
    dots_labels = cluster.fit_predict(dots)
    centers = cluster.cluster_centers_
    return dots_labels, centers


# 绘制图像
def plot_dots(dots, dots_labels, centers):
    # 绘制点
    for i in range(len(dots_labels)):
        plt.scatter(dots[i][0], dots[i][1], color=colors[dots_labels[i]])
    # 绘制聚类中心
    for i in range(len(centers)):
        plt.scatter(centers[i][0], centers[i][1], marker='x', color="#000000", s=50)
    plt.show()


if __name__ == '__main__':
    np.random.seed(250)
    dots = generate()
    dots_labels, centers = k_means(dots)
    num_labels = num_same(dots_labels)
    # 输出结果
    distance = 0
    for i in range(len(centers)):
        print("聚类中心", i+1, "坐标为", np.round(centers[i], 2))
        index = np.argwhere(dots_labels == i)
        print("属于该聚类中心的城市标号为", [int(x)+1 for x in index])
        mark = [int(x) for x in index]
        distance += cal_center_dist(centers[i], [dots[i] for i in mark])
        print("所有聚类中心和所辖城市的距离之和为", np.round(distance,2))

    # 绘图
    plot_dots(dots, dots_labels, centers)

在这里插入图片描述

输出总距离:所有聚类中心和所辖城市的距离之和为 2860.48.

手动实现

下面根据算法的理解,进行手动实现:

import numpy as np
from matplotlib import pyplot as plt

Num_dots = 60  # 城市总数
Num_gas = 10  # 聚类中心总数
Size_map = 500  # 500x500地图

colors = ['#FF3838', '#FF9D97', '#FF701F', '#FFB21D', '#CFD231', '#48F90A', '#92CC17', '#3DDB86', '#1A9334', '#00D4BB',
          '#2C99A8', '#00C2FF', '#344593', '#6473FF', '#0018EC', '8438FF', '#520085', '#CB38FF', '#FF95C8', '#FF37C7']

# 生成随机点
def generate():
    dots = []
    for i in range(Num_dots):
        dots.append(np.random.uniform([Size_map, Size_map]))
    # dots_sorted_x = sorted(dots, key=lambda dot: dot[0])
    return dots

# 计算两点之间欧式距离
def cal_dist(x, y):
    return ((x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2) ** 0.5

# 计算中心点距离它所负责的所有点之和
def cal_center_dist(center, dots):
    distance = 0
    for i in range(len(dots)):
        distance += cal_dist(center, dots[i])
    return distance

# 根据城市坐标搜索城市序号
def search_city(value, dots):
    for i, item in enumerate(dots):
        if (item == value).any():
            return i


class K_Means:
    # k是分组数;tolerance‘中心点误差’;max_iter是迭代次数
    def __init__(self, k=2, tolerance=0.0001, max_iter=300):
        self.k_ = k
        self.tolerance_ = tolerance
        self.max_iter_ = max_iter
        self.distance = 0

    def fit(self, data):
        self.centers_ = {}
        for i in range(self.k_):
            self.centers_[i] = data[i]
            # print(self.centers_[i])  # {0: array([256.5, 542. ]), 1: array([586.5, 261.5]), 2: array([869. , 449.5])}

        for iter in range(self.max_iter_):
            self.clf_ = {}
            for i in range(self.k_):
                self.clf_[i] = []
            for feature in data:
                distances = []
                for center in self.centers_:
                    distances.append(cal_dist(feature, self.centers_[center]))

                classification = distances.index(min(distances))
                self.clf_[classification].append(feature)

            # 记录总路程
            self.distance = np.sum(distances)

            # 记录上一阶段中心点位置
            prev_centers = dict(self.centers_)

            # 移动每一个center到所辖城市的中心位置
            for c in self.clf_:
                self.centers_[c] = np.average(self.clf_[c], axis=0)

            # 若center的移动空间在误差范围内,跳出循环得到结果
            optimized = True
            for center in self.centers_:
                org_centers = prev_centers[center]
                cur_centers = self.centers_[center]
                if np.sum((cur_centers - org_centers) / org_centers * 100.0) > self.tolerance_:
                    optimized = False
            if optimized:
                break


if __name__ == '__main__':
    np.random.seed(250)
    dots = generate()
    k_means = K_Means(Num_gas)
    k_means.fit(dots)

    # 输出结果
    for i in range(Num_gas):
        print("聚类中心", i + 1, "坐标为", np.round(k_means.centers_[i], 2))
        city_index = []
        for x in k_means.clf_[i]:
            city_index.append(search_city(x, dots))
        print("属于该聚类中心的城市标号为", city_index)

    print("所有聚类中心和所辖城市的距离之和为", np.round(k_means.distance, 2))

    # 绘制中心点
    for center in k_means.centers_:
        plt.scatter(k_means.centers_[center][0], k_means.centers_[center][1], marker='x', color="#000000", s=50)

    # 绘制城市点
    for cat in k_means.clf_:
        for point in k_means.clf_[cat]:
            plt.scatter(point[0], point[1], c=colors[cat])

    plt.show()

在这里插入图片描述

输出总距离:所有聚类中心和所辖城市的距离之和为 2816.76

结论

聚类的常规标准是让聚类中心和所辖城市的距离之和,在本实验中,手动实现的k-means算法的结果要优于sklearn的结果。

这主要是由于k-means算法本身并不是非常稳定,容易受到初始点、离群点的影响,因此,所求解不一定是最优解。

附录:sklearn K-means参数/属性/接口

下面是sklearn中K-means算法的常用接口参数,数据来自菜菜的机器学习sklearn

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


http://www.niftyadmin.cn/n/5016454.html

相关文章

.netcore对传输类设置区分大小

.Net Core中内置了对Json的转化与解析 可将PropertyNameCaseInsensitive false 设置为区分大小写。

艺术与AI:科技与艺术的完美融合

文章目录 艺术创作的新工具生成艺术艺术与数据 AI与互动艺术虚拟现实(VR)与增强现实(AR)机器学习与互动性 艺术与AI的伦理问题结语 🎉欢迎来到AIGC人工智能专栏~艺术与AI:科技与艺术的完美融合 ☆* o(≧▽≦…

SpringMVC实现增删改查

文章目录 一、配置文件1.1 导入相关pom依赖1.2 jdbc.properties:配置文件1.3 generatorConfig.xml:代码生成器1.4 spring-mybatis.xml :spring与mybatis整合的配置文件1.5 spring-context.xml :上下文配置文件1.6 spring-mvc-xml:…

利用transform和border 创造简易图标,以适应uniapp中多字体大小情况下的符号问题

heml: <text class"icon-check"></text> css: .icon-check {border: 2px solid black;border-left: 0;border-top: 0;height: 12px;width: 6px;transform-origin: center;transform: rotate(45deg);} 实际上就是声明一个带边框的div 将其中相邻的两边去…

Java基础(二十一)十点半游戏

十点半游戏 十点半是一种流行的纸牌游戏&#xff0c;可以说是一种变体的二十一点游戏。游戏的规则是&#xff0c;每个玩家根据所拿到的牌点数的总和来决定是否继续要牌。目标是尽量接近但不超过十点半的点数&#xff0c;超过十点半即为爆牌。如果两名玩家都未爆牌&#xff0c;…

Python入门教程35:使用email模块发送HTML和图片邮件

smtplib模块实现邮件的发送功能&#xff0c;模拟一个stmp客户端&#xff0c;通过与smtp服务器交互来实现邮件发送的功能&#xff0c;可以理解成Foxmail的发邮件功能&#xff0c;在使用之前我们需要准备smtp服务器主机地址、邮箱账号以及密码信息。 #我的Python教程 #官方微信公…

enum类型变量强制转换为uint_16类型,是向上转换,还是向下转换?

1. 重新认识enum类型 C语言中&#xff0c;enum类型可以用来定义常量&#xff0c;且可以自动累加变量值&#xff0c;在方便程度上&#xff0c;易于使用宏定义。因此&#xff0c;enum类型在较多场景中使用。 使用方式如下&#xff1a; typedef enum {UART_MSG_CMD_OPEN 0, UAR…

MATLAB遗传算法求解生鲜货损制冷时间窗碳排放多成本车辆路径规划问题

MATLAB遗传算法求解生鲜货损制冷时间窗碳排放多成本车辆路径规划问题实例 1、问题描述 已知配送中心和需求门店的地理位置,并且已经获得各个门店的需求量。关于送货时间的要求,门店都有规定的时间窗,对于超过规定时间窗外的配送时间会产生相应的惩罚成本。为保持生鲜农产品的…