脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Python - Python自定义指标聚类实例代码

Python自定义指标聚类实例代码

2022-10-17 10:47荷碧·TZ Python

K-means算法是最为经典的基于划分的聚类方法,是十大经典数据挖掘算法之一,下面这篇文章主要给大家介绍了关于Python自定义指标聚类的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下

前言

最近在研究 Yolov2 论文的时候,发现作者在做先验框聚类使用的指标并非欧式距离,而是IOU。在找了很多资料之后,基本确定 Python 没有自定义指标聚类的函数,所以打算自己做一个

设训练集的 shape 是 [n_sample, n_feature],基本思路是:

  • 簇中心初始化:第 1 个簇中心取样本的特征均值,shape = [n_feature, ];从第 2 个簇中心开始,用距离函数 (自定义) 计算每个样本到最近中心点的距离,归一化后作为选取下一个簇中心的概率 —— 迭代到选取到足够的簇中心为止
  • 簇中心调整:训练多轮,每一轮以样本点到最近中心点的距离之和作为 loss,梯度下降法 + Adam 优化器逼近最优解,在 loss 浮动值小于阈值的次数达到一定值时停止训练

因为设计之初就打算使用自定义距离函数,所以求导是很大的难题。笔者不才,最终决定借助 PyTorch 自动求导的天然优势

先给出欧式距离的计算函数

?
1
2
3
4
5
6
7
8
def Eu_dist(data, center):
    """ 以 欧氏距离 为聚类准则的距离计算函数
        data: 形如 [n_sample, n_feature] 的 tensor
        center: 形如 [n_cluster, n_feature] 的 tensor"""
    data = data.unsqueeze(1)
    center = center.unsqueeze(0)
    dist = ((data - center) ** 2).sum(dim=2)
    return dist

然后就是聚类器的代码:使用时只需关注 __init__、fit、classify 函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import torch
import numpy as np
import matplotlib.pyplot as plt
Adam = torch.optim.Adam
 
def get_progress(current, target, bar_len=30):
    """ current: 当前完成任务数
        target: 任务总数
        bar_len: 进度条长度
        return: 进度条字符串"""
    assert current <= target
    percent = round(current / target * 100, 1)
    unit = 100 / bar_len
    solid = int(percent / unit)
    hollow = bar_len - solid
    return "■" * solid + "□" * hollow + f" {current}/{target}({percent}%)"
 
 
class Cluster:
    """ 聚类器
        n_cluster: 簇中心数
        dist_fun: 距离计算函数
            kwargs:
                data: 形如 [n_sample, n_feather] 的 tensor
                center: 形如 [n_cluster, n_feature] 的 tensor
            return: 形如 [n_sample, n_cluster] 的 tensor
        init: 初始簇中心
        max_iter: 最大迭代轮数
        lr: 中心点坐标学习率
        stop_thresh: 停止训练的loss浮动阈值
        cluster_centers_: 聚类中心
        labels_: 聚类结果"""
 
    def __init__(self, n_cluster, dist_fun, init=None, max_iter=300, lr=0.08, stop_thresh=1e-4):
        self._n_cluster = n_cluster
        self._dist_fun = dist_fun
        self._max_iter = max_iter
        self._lr = lr
        self._stop_thresh = stop_thresh
        # 初始化参数
        self.cluster_centers_ = None if init is None else torch.FloatTensor(init)
        self.labels_ = None
        self._bar_len = 20
 
    def fit(self, data):
        """ data: 形如 [n_sample, n_feature] 的 tensor
            return: loss浮动日志"""
        if self.cluster_centers_ is None:
            self._init_cluster(data, self._max_iter // 5)
        log = self._train(data, self._max_iter, self._lr)
        # 开始若干轮次的训练,得到loss浮动日志
        return log
 
    def classify(self, data, show=False):
        """ data: 形如 [n_sample, n_feature] 的 tensor
            show: 绘制分类结果
            return: 分类标签"""
        dist = self._dist_fun(data, self.cluster_centers_)
        self.labels_ = dist.argmin(axis=1)
        # 将标签加载到实例属性
        if show:
            for idx in range(self._n_cluster):
                container = data[self.labels_ == idx]
                plt.scatter(container[:, 0], container[:, 1], alpha=0.7)
            plt.scatter(self.cluster_centers_[:, 0], self.cluster_centers_[:, 1], c="gold", marker="p", s=50)
            plt.show()
        return self.labels_
 
    def _init_cluster(self, data, epochs):
        self.cluster_centers_ = data.mean(dim=0).reshape(1, -1)
        for idx in range(1, self._n_cluster):
            dist = np.array(self._dist_fun(data, self.cluster_centers_).min(dim=1)[0])
            new_cluster = data[np.random.choice(range(data.shape[0]), p=dist / dist.sum())].reshape(1, -1)
            # 取新的中心点
            self.cluster_centers_ = torch.cat([self.cluster_centers_, new_cluster], dim=0)
            progress = get_progress(idx, self._n_cluster, bar_len=self._n_cluster if self._n_cluster <= self._bar_len else self._bar_len)
            print(f"\rCluster Init: {progress}", end="")
            self._train(data, epochs, self._lr * 2.5, init=True)
            # 初始化簇中心时使用较大的lr
 
    def _train(self, data, epochs, lr, init=False):
        center = self.cluster_centers_.cuda()
        center.requires_grad = True
        data = data.cuda()
        optimizer = Adam([center], lr=lr)
        # 将中心数据加载到 GPU 上
        init_patience = int(epochs ** 0.5)
        patience = init_patience
        update_log = []
        min_loss = np.inf
        for epoch in range(epochs):
            # 对样本分类并更新中心点
            sample_dist = self._dist_fun(data, center).min(dim=1)
            self.labels_ = sample_dist[1]
            loss = sum([sample_dist[0][self.labels_ == idx].mean() for idx in range(len(center))])
            # loss 函数: 所有样本到中心点的最小距离和 - 中心点间的最小间隔
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
            # 反向传播梯度更新中心点
            loss = loss.item()
            progress = min_loss - loss
            update_log.append(progress)
            if progress > 0:
                self.cluster_centers_ = center.cpu().detach()
                min_loss = loss
                # 脱离计算图后记录中心点
            if progress < self._stop_thresh:
                patience -= 1
                # 耐心值减少
                if patience < 0:
                    break
                    # 耐心值归零时退出
            else:
                patience = init_patience
                # 恢复耐心值
            progress = get_progress(init_patience - patience, init_patience, bar_len=self._bar_len)
            if not init:
                print(f"\rCluster: {progress}\titer: {epoch + 1}", end="")
        if not init:
            print("")
        return torch.FloatTensor(update_log)

与KMeans++比较

KMeans++ 是以欧式距离为聚类准则的经典聚类算法。在 iris 数据集上,KMeans++ 远远快于我的聚类器。但在我反复对比测试的几轮里,我的聚类器精度也是不差的 —— 可以看到下图里的聚类结果完全一致

Python自定义指标聚类实例代码

  KMeans++ My Cluster
Cost 145 ms 1597 ms
Center

[[5.9016, 2.7484, 4.3935, 1.4339],

[5.0060, 3.4280, 1.4620, 0.2460],
[6.8500, 3.0737, 5.7421, 2.0711]]

[[5.9016, 2.7485, 4.3934, 1.4338],
[5.0063, 3.4284, 1.4617, 0.2463],
[6.8500, 3.0741, 5.7420, 2.0714]]

虽然速度方面与老牌算法对比的确不行,但是我的这个聚类器最大的亮点还是自定义距离函数

Yolo 检测框聚类

本来想用 Yolov4 检测框聚类引入的 CIoU 做聚类,但是没法解决梯度弥散的问题,所以退其次用了 DIoU

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def DIoU_dist(boxes, anchor):
    """ 以 DIoU 为聚类准则的距离计算函数
        boxes: 形如 [n_sample, 2] 的 tensor
        anchor: 形如 [n_cluster, 2] 的 tensor"""
    n_sample = boxes.shape[0]
    n_cluster = anchor.shape[0]
    dist = Eu_dist(boxes, anchor)
    # 计算欧式距离
    union_inter = torch.prod(boxes, dim=1).reshape(-1, 1) + torch.prod(anchor, dim=1).reshape(1, -1)
    boxes = boxes.unsqueeze(1).repeat(1, n_cluster, 1)
    anchor = anchor.unsqueeze(0).repeat(n_sample, 1, 1)
    compare = torch.stack([boxes, anchor], dim=2)
    # 组合检测框与 anchor 的信息
    diag = torch.sum(compare.max(dim=2)[0] ** 2, dim=2)
    dist /= diag
    # 计算外接矩形的对角线长度
    inter = torch.prod(compare.min(dim=2)[0], dim=2)
    iou = inter / (union_inter - inter)
    # 计算 IoU
    dist += 1 - iou
    return dist

我提取了 DroneVehicle 数据集的 650156 个预测框的尺寸做聚类,在这个过程中发现因为小尺寸的预测框过多,导致聚类中心聚集在原点附近。所以对 loss 函数做了改进:先分类,再计算每个分类下的最大距离之和

Python自定义指标聚类实例代码

横轴表示检测框的宽度,纵轴表示检测框的高度,其数值都是相对于原图尺寸的比例。若原图尺寸为 608 * 608,则得到的 9 个先验框为:

[ 2,  3 ] [ 9,  13 ] [ 19,  35 ]
[ 10,  76 ] [ 60,  14 ] [ 25,  134 ]
[ 167,  25 ] [ 115,  54 ] [ 70, 176 ]

总结

到此这篇关于Python自定义指标聚类的文章就介绍到这了,更多相关Python自定义指标聚类内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/qq_55745968/article/details/122889804

延伸 · 阅读

精彩推荐
  • Pythonpython实现sm2和sm4国密(国家商用密码)算法的示例

    python实现sm2和sm4国密(国家商用密码)算法的示例

    这篇文章主要介绍了python实现sm2和sm4国密(国家商用密码)算法的示例,帮助大家使用python加密文件,感兴趣的朋友可以了解下 ...

    小小咸鱼YwY5232020-09-26
  • Pythonpytorch锁死在dataloader(训练时卡死)

    pytorch锁死在dataloader(训练时卡死)

    这篇文章主要介绍了pytorch锁死在dataloader(训练时卡死),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    Totoro-wen7712021-11-16
  • Python基于numpy中数组元素的切片复制方法

    基于numpy中数组元素的切片复制方法

    今天小编就为大家分享一篇基于numpy中数组元素的切片复制方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    grey_csdn7742021-04-20
  • Python解决python大批量读写.doc文件的问题

    解决python大批量读写.doc文件的问题

    今天小编就为大家分享一篇解决python大批量读写.doc文件的问题。具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    X212140546652021-02-19
  • Pythonpython修改操作系统时间的方法

    python修改操作系统时间的方法

    这篇文章主要介绍了python修改操作系统时间的方法,涉及Python同步网络时间与本机时间的相关技巧,需要的朋友可以参考下 ...

    lele11912020-06-30
  • PythonPython检测网站链接是否已存在

    Python检测网站链接是否已存在

    Python是一种解释型、面向对象、动态数据类型的高级程序设计语言。通过本文给大家介绍Python检测网站链接是否已存在的相关内容,需要的朋友一起学习吧...

    jerrylsxu10562020-08-18
  • Pythonpython中xml格式的转换方法

    python中xml格式的转换方法

    这篇文章主要为大家详细介绍了python中xml格式的转换方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    开心+16742022-08-10
  • PythonPython常用小技巧总结

    Python常用小技巧总结

    这篇文章主要介绍了Python常用小技巧,实例总结了Python关于字典、字符串、随机数等操作技巧,非常简单实用,需要的朋友可以参考下 ...

    shichen20144932020-07-10