使用Python找到优化曲线上的“拐点”

22

我有一个点列表,这些点是 kmeans 算法的惯性值。


为了确定最优群集的数量,我需要找到曲线开始变平的点。

数据示例

以下是我的值列表如何创建和填充的:

sum_squared_dist = []
K = range(1,50)
for k in K:
    km = KMeans(n_clusters=k, random_state=0)
    km = km.fit(normalized_modeling_data)
    sum_squared_dist.append(km.inertia_)

print(sum_squared_dist)

如何找到一个点,使得这条曲线的斜率增大(曲线下降,所以一阶导数为负)?

我的方法

derivates = []
for i in range(len(sum_squared_dist)):
    derivates.append(sum_squared_dist[i] - sum_squared_dist[i-1])

我希望使用“肘部法”找到任何给定数据的最佳聚类数量。有人能帮助我找到惯性值列表开始变平的点吗?

编辑
数据点:

[7342.1301373073857, 6881.7109460930769, 6531.1657905495022,  
6356.2255554679778, 6209.8382535595829, 6094.9052166741121, 
5980.0191582610196, 5880.1869867848218, 5779.8957906367368, 
5691.1879324562778, 5617.5153566271356, 5532.2613232619951, 
5467.352265375117, 5395.4493783888756, 5345.3459908298091, 
5290.6769823693812, 5243.5271656371888, 5207.2501206569532, 
5164.9617535255456]

图表: graph


请看这个问题:https://dev59.com/wHI-5IYBdhLWcg3wF0Qc,但似乎有很多不同的方法和解决方法。您能否包含一个典型曲线或15-20(x,y)数据点的图形? - xdze2
添加了前20个数据点以及图像和链接(如果图像无法显示)。 - ItFreak
请查看以下答案 https://dev59.com/WGUp5IYBdhLWcg3wS2LP#15376462 - user2653663
这不是重复的,因为这里没有真正的“弯曲点”,而且三点并不是最优的聚类数。 - ItFreak
我会做这个,但需要一些时间... 当肘部法则因被聚类的底层数据失败时,是否有其他度量标准来确定最佳聚类? - ItFreak
显示剩余2条评论
2个回答

47

我曾经参与制作了一个Python包,基于Kneedle算法。它可以找到x=5这个点,在此处曲线开始变平。有关如何选择拐点的算法,文档和论文中有更详细的讨论。

y = [7342.1301373073857, 6881.7109460930769, 6531.1657905495022,  
6356.2255554679778, 6209.8382535595829, 6094.9052166741121, 
5980.0191582610196, 5880.1869867848218, 5779.8957906367368, 
5691.1879324562778, 5617.5153566271356, 5532.2613232619951, 
5467.352265375117, 5395.4493783888756, 5345.3459908298091, 
5290.6769823693812, 5243.5271656371888, 5207.2501206569532, 
5164.9617535255456]

x = range(1, len(y)+1)

from kneed import KneeLocator
kn = KneeLocator(x, y, curve='convex', direction='decreasing')
print(kn.knee)
5

import matplotlib.pyplot as plt
plt.xlabel('number of clusters k')
plt.ylabel('Sum of squared distances')
plt.plot(x, y, 'bx-')
plt.vlines(kn.knee, plt.ylim()[0], plt.ylim()[1], linestyles='dashed')

膝点


1
非常感谢您,我发布了自己的答案,以提供如何实现此功能的印象:) - ItFreak
嗨 @Kevin,谢谢你的分享!我正在使用你的代码。但是,我需要膝盖的y或垂直坐标。请问如何使用你的代码实现? - atjw94
@atjw94,源代码中没有任何东西,但你可以使用这个:y[x.index(kn.knee)]--如果你认为这是一个有用的功能,请随时在GitHub上打开一个问题或拉取请求 :) - Kevin
@Kevin,我们可以在图像上使用kneed来进行kmeans吗?那么y值会是什么? - ASingh

1

对于所有想要自行实现的人,这里提供一个简单的基本实现。它非常适合我的用例(200个集群作为计算边界),距离计算非常基础,基于2D空间中点到点的计算,但可以适应任何其他数字量。
我认为Kevin的库在技术上更加更新和更好地实现了。

import KMeansClusterer
from math import sqrt, fabs
from matplotlib import pyplot as plp
import multiprocessing as mp
import numpy as np

class ClusterCalculator:
    m = 0
    b = 0
    sum_squared_dist = []
    derivates = []
    distances = []
    line_coordinates = []

    def __init__(self, calc_border, data):
        self.calc_border = calc_border
        self.data = data

    def calculate_optimum_clusters(self, option_parser):
        if(option_parser.multiProcessing):
            self.calc_mp()
        else:
            self.calculate_squared_dist()

        self.init_opt_line()
        self.calc_distances()
        self.calc_line_coordinates()
        opt_clusters = self.get_optimum_clusters()
        print("Evaluated", opt_clusters, "as optimum number of clusters")
        self.plot_results()
        return opt_clusters


    def calculate_squared_dist(self):
        for k in range(1, self.calc_border):
            print("Calculating",k, "of", self.calc_border, "\n", (self.calc_border - k), "to go!")
            kmeans = KMeansClusterer.KMeansClusterer(k, self.data)
            ine = kmeans.calc_custom_params(self.data, k).inertia_
            print("inertia in round", k, ": ", ine)
            self.sum_squared_dist.append(ine)

    def init_opt_line(self):
        self. m = (self.sum_squared_dist[0] - self.sum_squared_dist[-1]) / (1 - self.calc_border)
        self.b = (1 * self.sum_squared_dist[0] - self.calc_border*self.sum_squared_dist[0]) / (1 - self.calc_border)

    def calc_y_value(self, x_calc):
        return self.m * x_calc + self.b

    def calc_line_coordinates(self):
        for i in range(0, len(self.sum_squared_dist)):
            self.line_coordinates.append(self.calc_y_value(i))

    def calc_distances(self):
        for i in range(0, self.calc_border):
            y_value = self.calc_y_value(i)
            d = sqrt(fabs(self.sum_squared_dist[i] - self.calc_y_value(i)))
            length_list = len(self.sum_squared_dist)
            self.distances.append(sqrt(fabs(self.sum_squared_dist[i] - self.calc_y_value(i))))
        print("For border", self.calc_border, ", calculated the following distances: \n", self.distances)

    def get_optimum_clusters(self):
        return self.distances.index((max(self.distances)))

    def plot_results(self):
        plp.plot(range(0, self.calc_border), self.sum_squared_dist, "bx-")
        plp.plot(range(0, self.calc_border), self.line_coordinates, "bx-")
        plp.xlabel("Number of clusters")
        plp.ylabel("Sum of squared distances")
        plp.show()

    def calculate_squared_dist_sliced_data(self,output, proc_numb, start, end):
        temp = []
        for k in range(start, end + 1):
            kmeans = KMeansClusterer.KMeansClusterer(k, self.data)
            ine = kmeans.calc_custom_params(self.data, k).inertia_
            print("Process", proc_numb,"had the CPU,", "calculated", ine, "in round", k)
            temp.append(ine)
        output.put((proc_numb, temp))

    def sort_result_queue(self, result):
        result.sort()
        result = [r[1] for r in result]
        flat_list= [item for sl in result for item in sl]
        return flat_list

    def calc_mp(self):
        output = mp.Queue()
        processes = []
        processes.append(mp.Process(target=self.calculate_squared_dist_sliced_data, args=(output, 1, 1, 50)))
        processes.append(mp.Process(target=self.calculate_squared_dist_sliced_data, args=(output, 2, 51, 100)))
        processes.append(mp.Process(target=self.calculate_squared_dist_sliced_data, args=(output, 3, 101, 150)))
        processes.append(mp.Process(target=self.calculate_squared_dist_sliced_data, args=(output, 4, 151, 200)))

        for p in processes:
            p.start()


        #lock code and wait for all processes to finsish
        for p in processes:
            p.join()
        results = [output.get() for p in processes]
        self.sum_squared_dist = self.sort_result_queue(results)

你如何使用这个?我需要安装什么来获取KMeansClusterer? - RaduS
嗨,KMeansClusterer是我自己编写的一个类,它包装了k-means算法。 - ItFreak
但如果您不发布这个类,那么这里的整个代码就毫无用处。 - RaduS
2
不,它只是一个k-means的包装类。检查传递的参数 ;) 顺便说一下,这个问题是关于肘部计算的,而这个特定的类完全涵盖了它。 - ItFreak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接