飞道的博客

『反思』K-Means聚类时可能存在的问题——薛定谔的最优解

362人阅读  评论(0)

  在利用K-Means对句子向量进行聚类的时候,发现了两种容易疏忽的错误。

  关于代码的实现请参考 用K-Means对未标注的数据聚类


Error 1:一直循环无最优解

  首先,在聚类的过程中,我发现整整迭代一个小时都没有结果。刚开始我以为是数据比较多,一共有 6000 多条数据,每个数据是 100 维的向量。后来,我减少数据量到10条,发现还是一直迭代,我渐渐开始慌了。

  要是不按下暂停键,它就一直循环到地老天荒。我开始寻找原因。

  刚开始我认为是判断循环的条件出了问题。在刚开始,需要创建一个标志变量clusterChanged,若为真,则继续迭代。

# 数据聚类
def kMeans(dataSet, k):
    m = np.shape(dataSet)[0]  # 一共有m行数据
    clusterAssment = np.mat(np.zeros((m, 2)))  # 生成m*2的0矩阵,第一列存放该点所在簇的索引,第二列存放该点与簇质心的距离
    centroids = randCent(dataSet, k)  # 调用函数,构建质心矩阵
    clusterChanged = True  # 创建一个标志变量,若为真,则继续迭代
    count = 0
    while clusterChanged:
        clusterChanged = False
        # 寻找最近的质心
        for i in range(m):  # 对于每一行的数据
            minIndex = -1  # 在未迭代之前,将簇的索引设为-1
            maxDist = -np.inf  # 在未迭代之前,将最大距离设为负无穷大
            for j in range(k):  # 对于当前每一个质心
                distJI = distCal(centroids[j, :], dataSet[i, :])  # 计算第i个数据与第j个质心之间的距离
                if distJI > maxDist:
                    minIndex = j  # 若i与j之间距离小于目前的最小值,则更新当前点的索引为j
                    maxDist = distJI  # 若i与j之间距离大于目前的最大值,则更新当前最大值
            if clusterAssment[i, 0] != minIndex:  # 如果第i行的第一个值(簇的索引)不为 minIndex,说明簇发生了改变
                clusterChanged = True  # 继续迭代
            clusterAssment[i, :] = int(minIndex), maxDist**2
        #print(centroids)
        # 更新质心的位置
        for cent in range(k):
            category = np.nonzero(clusterAssment[:, 0].A == cent)  # 得到簇索引为cent的值的位置
            ptsInClust = dataSet[category[0]]
            if len(ptsInClust) != 0:
                centroids[cent, :] = np.mean(ptsInClust, axis=0)  # axis=0表示沿着矩阵列的方向进行均值计算
        #print(centroids)
    #print(centroids)
    return centroids, clusterAssment

  但是反反复复看,逻辑应该是没有问题的。什么时候还需要继续迭代?如果第 i i 行的第一个值(簇的索引)不为minIndex,说明簇发生了改变,这个点已经被重新划分到了与它距离更近的一个簇。

  换个思路。我把每一次迭代前后的簇和簇中心打印了出来。发现一个很有意思的现象。

  先解释一下这个矩阵,一共有十行、两列,第一列存放距离它最近的簇的索引,第二列存放距离的平方。

第3次迭代前后
第4次迭代前后

  我们发现,第三次迭代前后和第四次迭代前后的簇的索引,竟然是0-1转换!之前索引为0所有的点,经过转换变为1;索引为1所有的点,经过转换变为0。这是什么???我将之命名为 薛定谔的最优解,又称 “ 李氏死循环 ” 。

  我们再打印一下簇中心,发现不管是经过几百次、几千次的迭代,簇中心的值是一直没变的。只不过在来回颠倒,一会这个簇索引为 1 ,一会为 0 ,就像在震荡一样。

第3次迭代簇中心
第300次迭代簇中心

  簇中心的位置没有变,索引却在来回交替变换,我左思右想,整整一天,搞不明白(留下了没有技术和头脑的泪水)。明明已经找到了最优解,一个点找到了最近的簇,下一次为什么又将它分到了另外的簇?

  今天早上,看着迭代后第二列的值,我突然灵机一动。距离!应该是距离的问题!

  我们理一下思路 :对于 正常 的点来说,两个点越近,距离越小(废话)。

  因此,K-Means在确定簇的个数 k k 之后,对于每一个点,要比较它于 k k 个簇中心的距离,将其归到 距离最小 的那一个簇。

  但是在 句子向量 计算的时候,情况发生了改变!!!

  对于 句子向量,我们用 余弦 表示距离,它在[-1, 1]之间,句子越相似,余弦值 越大!两个点越近,计算出的距离越远!

  如果你选择距离最小的簇,意味着你将这个点归到了一个完全不相似的簇中,因此下一次,它又会反过来,就这样,子子孙孙无穷尽也。

  破案了兄弟们!所以,给定 k k ,每一个句子计算与 k k 个簇中心的距离,应该将其归到 距离较大 的那个簇。因此,将初始的最大距离设为负无穷大, < 改为 > 即可。

        for i in range(m):  # 对于每一行的数据
            minIndex = -1  # 在未迭代之前,将簇的索引设为-1
            maxDist = -np.inf  # 在未迭代之前,将最大距离设为负无穷大
            for j in range(k):  # 对于当前每一个质心
                distJI = distCal(centroids[j, :], dataSet[i, :])  # 计算第i个数据与第j个质心之间的距离
                if distJI > maxDist:
                    minIndex = j  # 若i与j之间距离小于目前的最小值,则更新当前点的索引为j
                    maxDist = distJI  # 若i与j之间距离大于目前的最大值,则更新当前最大值

  同样,如果是用二分法进行计算,也要更改判断语句哦!应该选择使得SSE最大 的簇进行二分,而不是最小了!

  最后,经过两次迭代,求出了最终解。用此图,祭奠飘落的头发和后移的发际线。


Error 2:RuntimeWarning: Mean of empty slice

  运行的时候,还发现了一个错误:RuntimeWarning: Mean of empty slice. 出现了空白的平均值。

  我们看看这一段的代码。它是在每一个点找到最近的簇中心后,对于每一个簇进行平均,求出新的簇中心。当前给定了 k k 个簇,对于每一个簇,找到对应的点,然后求平均。

		# 更新质心的位置
        for cent in range(k):
            category = np.nonzero(clusterAssment[:, 0].A == cent)  # 得到簇索引为cent的值的位置
            ptsInClust = dataSet[category[0]]
            centroids[cent, :] = np.mean(ptsInClust, axis=0)  # axis=0表示沿着矩阵列的方向进行均值计算

  出现问题的原因是某一个簇ptsInClust可能为空,即没有点在其中。可能是k值选择的过大,因此可以适当减少k值,也可以加一个判断语句。修改后代码如下,就不会产生这样的错误。

        # 更新质心的位置
        for cent in range(k):
            category = np.nonzero(clusterAssment[:, 0].A == cent)  # 得到簇索引为cent的值的位置
            ptsInClust = dataSet[category[0]]
            if len(ptsInClust) != 0:
                centroids[cent, :] = np.mean(ptsInClust, axis=0)  # axis=0表示沿着矩阵列的方向进行均值计算

  最后,我想说,下面这句话,胜过了世界上的千言万语。比起不痛不痒的“我爱你”、“我们在一起吧”,这句话才是照亮前行之路的灯塔。万般皆下品,惟有读书高,祝你万事胜意,遍地飘零。


转载:https://blog.csdn.net/qq_42491242/article/details/105527224
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场