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