在多维数据中查找集群

数据挖掘 聚类
2021-10-13 09:32:42

我有一组来自 3,000 条记录的数据。每个人有 5 个属性(标记为 A - E)。我可以使用 Kendall 的 W(一致性系数)来确定任意两条记录之间的一致性。

我需要的是一种方法来辨别存在于整个数据中的任何集群,以及每个集群中的哪些属性。例如,对于下面的数据,我会看到 2 个集群:

集群 1: C、D /集群 2: A、B

ID  | A   | B   | C   | D   | E   |
001 | 55  | 80  | 125 | 114 | 75  |
002 | 75  | 78  | 110 | 105 | 95  |
003 | 135 | 105 | 95  | 93  | 92  |
004 | 120 | 115 | 101 | 55  | 44  |

我可以使用哪些技术或方法来帮助解决这个问题?

1个回答

一般来说,对特征进行聚类没有多大意义。在一个理想的世界里,你的特性应该是最好的,它们实际上应该是独立的,因此它们之间不应该有任何关系。通常,当我们谈论集群时,它是对实例进行集群。根据特征值的相似性将一些关联标签归因于实例的子集。

存在许多聚类算法,我想说最流行的是 K-means,但也经常使用谱聚类和高斯混合。与往常一样,每种算法都最适合特定类型的数据集,您可以选择最适合的算法,或者您可以尝试所有算法,看看哪种算法最好。

在这里,您可以找到具有各自用例的聚类算法列表。当您想要实现标准算法时,请始终使用这些库,它们是高度优化的。但是为了教育起见,看看正在发生的事情是件好事。

我将描述 K-means 算法的自制版本,这样您就可以了解幕后发生的事情,也许您会明白为什么我们集群实例而不是特征。

K-means 算法

import numpy as np
import matplotlib.pyplot as plt

首先,我们将制作一些人工数据。这些将包括n具有给定均值和方差的二维高斯簇。这里n=5 每个高斯分布我们将有 300 个实例。

params = [[[ 0,1],  [ 0,1]], 
          [[ 5,1],  [ 5,1]], 
          [[-2,5],  [ 2,5]],
          [[ 2,1],  [ 2,1]],
          [[-5,1],  [-5,1]]]

n = 300
dims = len(params[0])

data = []
y = []
for ix, i in enumerate(params):
    inst = np.random.randn(n, dims)
    for dim in range(dims):
        inst[:,dim] = params[ix][dim][0]+params[ix][dim][1]*inst[:,dim]
        label = ix + np.zeros(n)

    if len(data) == 0: data = inst
    else: data = np.append( data, inst, axis= 0)
    if len(y) == 0: y = label
    else: y = np.append(y, label)

num_clusters = len(params)

print(y.shape)
print(data.shape)

(1500,)
(1500, 2)

所以现在我们在 2D 空间中有 1500 个实例(记录)。这可以扩展到任何数字维度。2 最容易绘制。

plt.scatter(data[:,0], data[:,1])
plt.show()

在此处输入图像描述

我将把整个算法粘贴到答案的底部以便快速复制和粘贴,但在这里我将介绍它的不同部分,以便您了解它是如何工作的。算法如下:首先我们在数据范围内初始化一些质心。这些是下图中的红点

def train(self, data, verbose=1):

        shape = data.shape

        ranges = np.zeros((shape[1], 2))
        centroids = np.zeros((shape[1], 2))

        for dim in range(shape[1]):
            ranges[dim, 0] = np.min(data[:,dim])
            ranges[dim, 1] = np.max(data[:,dim])

        if verbose == 1:
            print('Ranges: ')
            print(ranges)

        centroids = np.zeros((self.k, shape[1]))
        for i in range(self.k):
            for dim in range(shape[1]):
                centroids[i, dim] = np.random.uniform(ranges[dim, 0], ranges[dim, 1], 1)

        if verbose == 1:
            print('Centroids: ')
            print(centroids)

            plt.scatter(data[:,0], data[:,1])
            plt.scatter(centroids[:,0], centroids[:,1], c = 'r')
            plt.show()

在此处输入图像描述

然后我们将计算每个实例(记录)到每个质心的距离。

distances = np.zeros((shape[0],self.k))
for ix, i in enumerate(data):
    for ic, c in enumerate(centroids):
        distances[ix, ic] = np.sqrt(np.sum((i-c)**2))

然后我们将每个实例归入它最接近的质心。

labels = np.argmin(distances, axis = 1)

在此处输入图像描述

现在我们将通过查找每个维度中最接近给定质心的所有实例的平均位置来更新新质心的位置。

new_centroids = np.zeros((self.k, shape[1]))
for centroid in range(self.k):
    temp = data[labels == centroid]
    if len(temp) == 0:
        return 0
    for dim in range(shape[1]): 
        new_centroids[centroid, dim] = np.mean(temp[:,dim])

在此处输入图像描述

然后我们重复这个过程,直到质心不再显着移动。通常,如果所有质心的位置差异小于机器 epsilon,我们认为算法已经收敛。

if np.linalg.norm(new_centroids - centroids) < np.finfo(float).eps:
    print("DONE!")
    break

对于这个数据集,它需要 16 次迭代才能收敛,这是最终结果。

在此处输入图像描述

我们在这里看到的是,每个实例都被分组到一个具有与其自身相似属性的集群中。例如,如果我们的数据维度表示身高(x 轴)和体重(y 轴),那么我们可以将人们分为 5 个不同的 BMI 指数。

  • 紫色:短而瘦
  • 绿色:矮胖
  • 蓝色:中等身高和中等体重
  • 黄色:中等身高和肥胖
  • 绿松石:高且中等重量。

我们人群中的每个成员都属于一个特定的 BMI 指数,该指数基于他拥有的两个可测量的属性(特征),即身高和体重进行聚类。

完整的 K-means 算法

这是算法

class Kmeans(object):

    def __init__(self, k=1):
        self.k = k

    def train(self, data, verbose=1):

        shape = data.shape

        ranges = np.zeros((shape[1], 2))
        centroids = np.zeros((shape[1], 2))

        for dim in range(shape[1]):
            ranges[dim, 0] = np.min(data[:,dim])
            ranges[dim, 1] = np.max(data[:,dim])

        if verbose == 1:
            print('Ranges: ')
            print(ranges)

        centroids = np.zeros((self.k, shape[1]))
        for i in range(self.k):
            for dim in range(shape[1]):
                centroids[i, dim] = np.random.uniform(ranges[dim, 0], ranges[dim, 1], 1)

        if verbose == 1:
            print('Centroids: ')
            print(centroids)

            plt.scatter(data[:,0], data[:,1])
            plt.scatter(centroids[:,0], centroids[:,1], c = 'r')
            plt.show()

        count = 0
        while count < 100:
            count += 1
            if verbose == 1:
                print('-----------------------------------------------')
                print('Iteration: ', count)

            distances = np.zeros((shape[0],self.k))
            for ix, i in enumerate(data):
                for ic, c in enumerate(centroids):
                    distances[ix, ic] = np.sqrt(np.sum((i-c)**2))

            labels = np.argmin(distances, axis = 1)

            new_centroids = np.zeros((self.k, shape[1]))
            for centroid in range(self.k):
                temp = data[labels == centroid]
                if len(temp) == 0:
                    return 0
                for dim in range(shape[1]): 
                    new_centroids[centroid, dim] = np.mean(temp[:,dim])

            if verbose == 1:
                plt.scatter(data[:,0], data[:,1], c = labels)
                plt.scatter(new_centroids[:,0], new_centroids[:,1], c = 'r')
                plt.show()

            if np.linalg.norm(new_centroids - centroids) < np.finfo(float).eps:
                print("DONE!")
                break

            centroids = new_centroids
        self.centroids = centroids
        self.labels = labels
        if verbose == 1:
            print(labels)
            print(centroids)
        return 1

    def getAverageDistance(self, data):

        dists = np.zeros((len(self.centroids),))
        for ix, centroid in enumerate(self.centroids):
            temp = data[self.labels == ix]
            dist = 0
            for i in temp:
                dist += np.linalg.norm(i - centroid)
            dists[ix] = dist/len(temp)
        return dists

    def getLabels(self):
        return self.labels

要使用该算法,请使用以下内容,其中数据是我们在上面制作的人工数据,但也可以是任何 numpy 矩阵,其中记录是行,特征是列。

kmeans = Kmeans(5)
kmeans.train(data)