You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用kneed寻找L曲线肘点时遭遇TypeError错误的技术求助

问题描述

我需要寻找L曲线的肘点(elbow point),将肘点的y轴值作为阈值,删除y轴值低于该阈值的点。使用Python的kneed库实现时,大部分数据正常运行,但处理少量样本数据(比如仅3个点)时抛出错误,且无法找到肘点。

我的实现函数:

def findThresholdLCurve(data:dict): 
    if len(data) > 0:
        # 统计所有值并按降序排序
        Occ = sorted(list(data.values()), reverse=True)

        x = list(range(1, len(data) + 1))
        y = Occ
        kneedle = KneeLocator(x, y, curve="convex", direction="decreasing", online=True,interp_method="polynomial")
        th = kneedle.knee_y
        kneedle.plot_knee() 
        return th

测试出错的数据:

data = {'apple': 0.5966618322834488, 'banana': 0.32877029629550597, 'peach': 0.5540431299774883}

错误栈:

Traceback (most recent call last):
  File "D:\my_study\AUDESOME_py_chatgpt\main.py", line 35, in <module>
    S = cal_S(O1, L)
  File "D:\my_study\AUDESOME_py_chatgpt\cal_S.py", line 43, in cal_S
    th2 = findThresholdLCurve(S[ci])
  File "D:\my_study\AUDESOME_py_chatgpt\findThresholdLCurve.py", line 23, in findThresholdLCurve
    kneedle.plot_knee()  
  File "D:\Anaconda\lib\site-packages\kneed\knee_locator.py", line 386, in plot_knee
    plt.vlines(
  File "D:\Anaconda\lib\site-packages\matplotlib\pyplot.py", line 3012, in vlines
    return gca().vlines(
  File "D:\Anaconda\lib\site-packages\matplotlib\__init__.py", line 1412, in inner
    return func(ax, *map(sanitize_sequence, args), **kwargs)
  File "D:\Anaconda\lib\site-packages\matplotlib\axes\_axes.py", line 1148, in vlines
    self.update_datalim(corners)
  File "D:\Anaconda\lib\site-packages\matplotlib\axes\_base.py", line 2481, in update_datalim
    if not np.any(np.isfinite(xys)):
TypeError: ufunc 'isfinite' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
解决方案

1. 修复kneed库的报错问题

报错核心原因:当数据量过小时,kneed无法检测到有效肘点,导致kneedle.knee_y为None,调用plot_knee()时,matplotlib尝试对None值绘制竖线,触发类型错误。

修改函数,增加肘点存在性判断,同时处理无肘点的边界情况:

def findThresholdLCurve(data:dict): 
    if len(data) > 0:
        Occ = sorted(list(data.values()), reverse=True)
        x = list(range(1, len(data) + 1))
        y = Occ
        kneedle = KneeLocator(x, y, curve="convex", direction="decreasing", online=True, interp_method="polynomial")
        th = kneedle.knee_y
        
        # 仅找到肘点时才执行绘图
        if th is not None:
            kneedle.plot_knee() 
        # 无肘点时自定义阈值逻辑,示例取次小值或最小值
        else:
            th = Occ[-2] if len(Occ)>=2 else Occ[0] if Occ else None
        return th

2. 优化kneed检测参数适配小数据集

小数据集下默认参数灵敏度不足,可调整以下参数提升检测能力:

  • 降低S参数(灵敏度):默认1.0,调至0.5可让算法更容易识别拐点
  • 更换插值方法:将interp_method="polynomial"改为interp_method="linear",线性插值对小数据更稳定

修改后的初始化代码:

kneedle = KneeLocator(x, y, curve="convex", direction="decreasing", online=True, interp_method="linear", S=0.5)

3. 替代方法:手动计算肘点

如果kneed库仍不适用于场景,可采用以下两种手动计算方式:

方法一:最大距离法(点到直线的最大垂直距离)

计算曲线上每个点到首尾点连线的垂直距离,取距离最大的点作为肘点:

import numpy as np

def find_elbow_max_distance(y):
    y = np.array(y)
    # 归一化数据消除量纲影响
    y_norm = (y - y.min()) / (y.max() - y.min())
    x_norm = np.arange(len(y_norm)) / (len(y_norm)-1)
    
    # 计算首尾点连线的向量
    start_vec = np.array([x_norm[0], y_norm[0]])
    end_vec = np.array([x_norm[-1], y_norm[-1]])
    line_vec = end_vec - start_vec
    
    # 计算每个点到直线的垂直距离
    distances = []
    for x, y_val in zip(x_norm, y_norm):
        point_vec = np.array([x, y_val]) - start_vec
        cross_prod = np.cross(line_vec, point_vec)
        distance = np.abs(cross_prod) / np.linalg.norm(line_vec)
        distances.append(distance)
    
    # 距离最大的点即为肘点
    elbow_idx = np.argmax(distances)
    return y[elbow_idx]

方法二:二阶导数法

通过二阶导数的突变点识别肘点:

def find_elbow_second_deriv(y):
    y = np.array(y)
    # 计算一阶、二阶导数
    first_deriv = np.diff(y)
    second_deriv = np.diff(first_deriv)
    # 取二阶导数绝对值最大的点(补回diff导致的索引偏移)
    elbow_idx = np.argmax(np.abs(second_deriv)) + 1
    return y[elbow_idx]

内容的提问来源于stack exchange,提问作者Emma_J

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:32:51