找到平均值时出现 ZeroDivisionError

ZeroDivisionError when finding average

过去几天我一直在尝试编写自己的 k-mean 算法,但遇到了障碍。当我试图找到簇中点的平均位置以移动质心时,我得到一个零除法错误(注意:当 k = 2 时不会发生这种情况,只有当 k = 3 时才会发生,但总是当 k >= 4 时发生)。我试图通过确保每个质心从数据集中的一个点开始来解决这个问题,这样它在它的集群中总是至少有一个点,但它没有奏效。我也重新安排了计数器等,但同样,它没有用。我有 运行 的想法,我不确定为什么这个错误仍然发生。我很确定问题出在这些函数之一(编辑:添加了所有代码和完整的错误消息):

import random
import math
import matplotlib.pyplot as plt


class Kmeans:
    def __init__(self, K, dataset, centroids, sorting):
        self.K = K
        self.dataset = dataset
        self.centroids = centroids
        self.sorting = sorting

    def initializeCentroids(self):
        usedPoints = [random.choice(data_set)]
        self.centroids = []
        for q in range(self.K):
            pointSelected = False
            while not pointSelected:
                m = random.choice(data_set)
                print(m)
                print(usedPoints)
                distance = math.sqrt(abs(((m[0] - usedPoints[len(usedPoints) - 1][0]) ** 2) + (m[1] - usedPoints[len(usedPoints) - 1][1]) ** 2))
                if usedPoints.count(m) == 0 and distance > 50:
                    self.centroids.append(list(m))
                    usedPoints.append(m)
                    pointSelected = True
        return self.centroids

    def calcDistance(self):
        self.sorting = []
        for w in self.dataset:
            distances = []
            counter = -1
            for centr in self.centroids:
                counter += 1
                distances.append(math.sqrt(abs((((w[0] - centr[0]) ** 2) + (w[1] - centr[1]) ** 2))))
                for x in range(len(distances)):
                    if len(distances) > 1:
                        print(distances)
                        if distances[0] > distances[1]:
                            distances.pop(0)
                        else:
                            distances.pop(1)
                            counter -= 1
            print(counter)
            self.sorting.insert(0, [w, counter, distances[0]])
        return self.sorting
    # not done

    def find_ME(self):
        counter2 = 0
        for r in self.centroids:
            for t in self.sorting:
                nums = []
                if t[1] == counter2:
                    nums.append(t[2])
                    population = len(nums)
                    error = sum(nums) / population

    def reassignCentroids(self):
        counter3 = 0
        for r in self.centroids:

            positionsX = []
            positionsY = []
            for t in self.sorting:
                if t[1] == counter3:
                    positionsX.append(t[0][0])
                    positionsY.append(t[0][1])
            population = len(positionsY)
            print(population)
            print(self.sorting)
            r[0] = sum(positionsX) / population
            r[1] = sum(positionsY) / population
            counter3 += 1
        return self.centroids

    def checkSimilar(self, prevList):
        list1 = []
        list2 = []
        for u in prevList:
            list1.append(u[1])
        for i in self.sorting:
            list2.append(i[1])
            print(i)
        if list2 == list1:

            return True
        else:
            return False


k = 3
data_set = [(1, 1), (1, 2), (1, 3), (2, 3), (50, 52), (48, 50), (47, 60), (112, 90), (120, 100), (108, 130), (102, 121), (43, 51), (0, 1)]
attempt = Kmeans(k, data_set, [], [])

attempt.initializeCentroids()

xvals = []
yvals = []
sortCompare = []
maxIterations = 100000
# plots

for p in data_set:
    xvals.append(p[0])
    yvals.append(p[1])


running = True
zeroError = True

while running:
    attempt.calcDistance()
    sortCompare = attempt.sorting
    print(sortCompare, "thisss")
    attempt.reassignCentroids()
    attempt.calcDistance()
    attempt.reassignCentroids()
    boolVal = attempt.checkSimilar(sortCompare)
    if boolVal or maxIterations <= 0:
        xs = []
        ys = []
        for y in attempt.centroids:
            xs.append(y[0])
            ys.append(y[1])
        plt.scatter(xs, ys)
        running = False
    else:
        sortCompare = []

    maxIterations -= 1
    print(attempt.sorting)
print(attempt.centroids)
plt.scatter(xvals, yvals)
plt.show()

完整错误:回溯(最后一次调用): 文件“C:/Users/Jack Cramer/PycharmProjects/kmeans/main.py”,第 117 行,位于 attempt.reassignCentroids() 文件“C:/Users/Jack Cramer/PycharmProjects/kmeans/main.py”,第 73 行,在 reassignCentroids 中 r[0] = sum(positionsX) / population ZeroDivisionError:除以零

如果你知道为什么会这样,请告诉我,谢谢你的建议。

正如@thierry-lathuille 指出的那样,当您除以 population 时,错误发生在 reassignCentroids 中,即零。 population设置为positionsY的长度,所以我们需要看看哪些场景导致positionsY没有元素

positionsY 将其值附加到值 t in self.sorting 的循环内。仅当 counter3(范围从 0 到 K-1)匹配 t[1] 时才附加值。因此,如果 counter3 的值与 t[1] 值中的 none 相等,我们将得到错误。为了帮助调试,我在循环中添加了一些打印语句

            print(f"{counter3=}")
            for t in self.sorting:
                print(f"{t[1]=}")
                if t[1] == counter3:
                    positionsX.append(t[0][0])
                    positionsY.append(t[0][1])

运行 多次使用 k=2,我看到 t[1] 是 0 或 1,如您所见,它不会崩溃。然而,上升到 k=3,有时我得到 t[1] 等于 0、1 或 2,但其他时候它只等于 0 或 1。这意味着没有匹配,你会除以零。上升到 k=4,我们应该得到 t[1] 等于 0、1、2 或 3,但在多次重新启动后我没有看到任何 3。

根据上下文,我猜测 t[1] 代表给定数据点最接近的集群。根据您的输入,肉眼看来存在三组点 所以你的代码自然会有三个预测的集群趋向于它们应该在的位置,所以如果你试图预测 4 个集群,你总是会崩溃。如果您只想预测 3 个集群,您可能会倒霉,并且您预测的集群之一不是最接近任何点的。即使 k=2 对某些初始化出错,我也不会感到惊讶,但在实践中一定很少见。

我想你需要先问问自己,当 k 高于实际簇数时,你希望看到什么行为。其次,您需要确定当 none 个数据点最接近集群时如何更新集群的位置。如果 population == 0,一个快速的解决方法是什么都不做,尽管这可能不会产生您想要的行为。