是否可以使用 scikit-learn K-Means Clustering 指定您自己的距离函数?
Posted
技术标签:
【中文标题】是否可以使用 scikit-learn K-Means Clustering 指定您自己的距离函数?【英文标题】:Is it possible to specify your own distance function using scikit-learn K-Means Clustering? 【发布时间】:2011-07-28 15:04:55 【问题描述】:【问题讨论】:
请注意,k-means 是为欧几里得距离设计的。当 mean 不再是集群“中心”的最佳估计时,它可能会停止与其他距离收敛。 为什么 k-means 只适用于欧几里得距离? @Anony-Mousse 说 k-means 只针对欧几里得距离是不正确的。可以修改它以使用在观察空间上定义的任何有效距离度量。例如,看看the article on k-medoids。 @curious:mean 最小化平方差(= 平方欧几里得距离)。如果您想要不同的距离函数,则需要用适当的中心估计替换 mean。 K-medoids 就是这样一种算法,但寻找 medoid 的成本要高得多。 这里有点相关:目前有一个 open pull request 实现内核 K-Means。完成后,您将能够为计算指定自己的内核。 【参考方案1】:def distance_metrics(dist_metrics):
kmeans_instance = kmeans(trs_data, initial_centers, metric=dist_metrics)
label = np.zeros(210, dtype=int)
for i in range(0, len(clusters)):
for index, j in enumerate(clusters[i]):
label[j] = i
【讨论】:
请考虑添加一些单词评论 这甚至与sklearn.cluster.KMeans
或sklearn.cluster.k_means
的签名都不匹配?【参考方案2】:
很遗憾没有:scikit-learn 当前的 k-means 实现只使用欧几里得距离。
将 k-means 扩展到其他距离并非易事,而 Denis 的上述回答并不是为其他指标实现 k-means 的正确方法。
【讨论】:
为什么Denis给出的实现不正确? 例如,对于曼哈顿距离(P=1 的 Minkowski),您需要一个专用算法(K-Medoids en.wikipedia.org/wiki/K-medoids),它在内部与 K-Means 完全不同。跨度> @ogrisel 这完全是错误的。曼哈顿距离的 k 均值是 ... k 中位数,即中心更新为集群的中位数(分别为每个维度计算)。【参考方案3】:Sklearn Kmeans 使用 欧几里得距离。它没有度量参数。这就是说,如果您要对时间序列进行聚类,则可以使用tslearn
python 包,当您可以指定指标时(dtw
、softdtw
、euclidean
)。
【讨论】:
【参考方案4】:pyclustering 是 python/C++(所以它的速度很快!)并允许您指定自定义指标函数
from pyclustering.cluster.kmeans import kmeans
from pyclustering.utils.metric import type_metric, distance_metric
user_function = lambda point1, point2: point1[0] + point2[0] + 2
metric = distance_metric(type_metric.USER_DEFINED, func=user_function)
# create K-Means algorithm with specific distance metric
start_centers = [[4.7, 5.9], [5.7, 6.5]];
kmeans_instance = kmeans(sample, start_centers, metric=metric)
# run cluster analysis and obtain results
kmeans_instance.process()
clusters = kmeans_instance.get_clusters()
实际上,我没有测试过这段代码,而是从a ticket 和example code 拼凑起来的。
【讨论】:
需要安装 Matplotlib,它需要“Python 作为 Mac OS X 上的框架”:(【参考方案5】:这是一个小的 kmeans,它使用了 20 多个距离中的任何一个 scipy.spatial.distance,或用户函数。 欢迎评论(到目前为止只有一个用户,还不够); 特别是,你的 N、dim、k、metric 是多少?
#!/usr/bin/env python
# kmeans.py using any of the 20-odd metrics in scipy.spatial.distance
# kmeanssample 2 pass, first sample sqrt(N)
from __future__ import division
import random
import numpy as np
from scipy.spatial.distance import cdist # $scipy/spatial/distance.py
# http://docs.scipy.org/doc/scipy/reference/spatial.html
from scipy.sparse import issparse # $scipy/sparse/csr.py
__date__ = "2011-11-17 Nov denis"
# X sparse, any cdist metric: real app ?
# centres get dense rapidly, metrics in high dim hit distance whiteout
# vs unsupervised / semi-supervised svm
#...............................................................................
def kmeans( X, centres, delta=.001, maxiter=10, metric="euclidean", p=2, verbose=1 ):
""" centres, Xtocentre, distances = kmeans( X, initial centres ... )
in:
X N x dim may be sparse
centres k x dim: initial centres, e.g. random.sample( X, k )
delta: relative error, iterate until the average distance to centres
is within delta of the previous average distance
maxiter
metric: any of the 20-odd in scipy.spatial.distance
"chebyshev" = max, "cityblock" = L1, "minkowski" with p=
or a function( Xvec, centrevec ), e.g. Lqmetric below
p: for minkowski metric -- local mod cdist for 0 < p < 1 too
verbose: 0 silent, 2 prints running distances
out:
centres, k x dim
Xtocentre: each X -> its nearest centre, ints N -> k
distances, N
see also: kmeanssample below, class Kmeans below.
"""
if not issparse(X):
X = np.asanyarray(X) # ?
centres = centres.todense() if issparse(centres) \
else centres.copy()
N, dim = X.shape
k, cdim = centres.shape
if dim != cdim:
raise ValueError( "kmeans: X %s and centres %s must have the same number of columns" % (
X.shape, centres.shape ))
if verbose:
print "kmeans: X %s centres %s delta=%.2g maxiter=%d metric=%s" % (
X.shape, centres.shape, delta, maxiter, metric)
allx = np.arange(N)
prevdist = 0
for jiter in range( 1, maxiter+1 ):
D = cdist_sparse( X, centres, metric=metric, p=p ) # |X| x |centres|
xtoc = D.argmin(axis=1) # X -> nearest centre
distances = D[allx,xtoc]
avdist = distances.mean() # median ?
if verbose >= 2:
print "kmeans: av |X - nearest centre| = %.4g" % avdist
if (1 - delta) * prevdist <= avdist <= prevdist \
or jiter == maxiter:
break
prevdist = avdist
for jc in range(k): # (1 pass in C)
c = np.where( xtoc == jc )[0]
if len(c) > 0:
centres[jc] = X[c].mean( axis=0 )
if verbose:
print "kmeans: %d iterations cluster sizes:" % jiter, np.bincount(xtoc)
if verbose >= 2:
r50 = np.zeros(k)
r90 = np.zeros(k)
for j in range(k):
dist = distances[ xtoc == j ]
if len(dist) > 0:
r50[j], r90[j] = np.percentile( dist, (50, 90) )
print "kmeans: cluster 50 % radius", r50.astype(int)
print "kmeans: cluster 90 % radius", r90.astype(int)
# scale L1 / dim, L2 / sqrt(dim) ?
return centres, xtoc, distances
#...............................................................................
def kmeanssample( X, k, nsample=0, **kwargs ):
""" 2-pass kmeans, fast for large N:
1) kmeans a random sample of nsample ~ sqrt(N) from X
2) full kmeans, starting from those centres
"""
# merge w kmeans ? mttiw
# v large N: sample N^1/2, N^1/2 of that
# seed like sklearn ?
N, dim = X.shape
if nsample == 0:
nsample = max( 2*np.sqrt(N), 10*k )
Xsample = randomsample( X, int(nsample) )
pass1centres = randomsample( X, int(k) )
samplecentres = kmeans( Xsample, pass1centres, **kwargs )[0]
return kmeans( X, samplecentres, **kwargs )
def cdist_sparse( X, Y, **kwargs ):
""" -> |X| x |Y| cdist array, any cdist metric
X or Y may be sparse -- best csr
"""
# todense row at a time, v slow if both v sparse
sxy = 2*issparse(X) + issparse(Y)
if sxy == 0:
return cdist( X, Y, **kwargs )
d = np.empty( (X.shape[0], Y.shape[0]), np.float64 )
if sxy == 2:
for j, x in enumerate(X):
d[j] = cdist( x.todense(), Y, **kwargs ) [0]
elif sxy == 1:
for k, y in enumerate(Y):
d[:,k] = cdist( X, y.todense(), **kwargs ) [0]
else:
for j, x in enumerate(X):
for k, y in enumerate(Y):
d[j,k] = cdist( x.todense(), y.todense(), **kwargs ) [0]
return d
def randomsample( X, n ):
""" random.sample of the rows of X
X may be sparse -- best csr
"""
sampleix = random.sample( xrange( X.shape[0] ), int(n) )
return X[sampleix]
def nearestcentres( X, centres, metric="euclidean", p=2 ):
""" each X -> nearest centre, any metric
euclidean2 (~ withinss) is more sensitive to outliers,
cityblock (manhattan, L1) less sensitive
"""
D = cdist( X, centres, metric=metric, p=p ) # |X| x |centres|
return D.argmin(axis=1)
def Lqmetric( x, y=None, q=.5 ):
# yes a metric, may increase weight of near matches; see ...
return (np.abs(x - y) ** q) .mean() if y is not None \
else (np.abs(x) ** q) .mean()
#...............................................................................
class Kmeans:
""" km = Kmeans( X, k= or centres=, ... )
in: either initial centres= for kmeans
or k= [nsample=] for kmeanssample
out: km.centres, km.Xtocentre, km.distances
iterator:
for jcentre, J in km:
clustercentre = centres[jcentre]
J indexes e.g. X[J], classes[J]
"""
def __init__( self, X, k=0, centres=None, nsample=0, **kwargs ):
self.X = X
if centres is None:
self.centres, self.Xtocentre, self.distances = kmeanssample(
X, k=k, nsample=nsample, **kwargs )
else:
self.centres, self.Xtocentre, self.distances = kmeans(
X, centres, **kwargs )
def __iter__(self):
for jc in range(len(self.centres)):
yield jc, (self.Xtocentre == jc)
#...............................................................................
if __name__ == "__main__":
import random
import sys
from time import time
N = 10000
dim = 10
ncluster = 10
kmsample = 100 # 0: random centres, > 0: kmeanssample
kmdelta = .001
kmiter = 10
metric = "cityblock" # "chebyshev" = max, "cityblock" L1, Lqmetric
seed = 1
exec( "\n".join( sys.argv[1:] )) # run this.py N= ...
np.set_printoptions( 1, threshold=200, edgeitems=5, suppress=True )
np.random.seed(seed)
random.seed(seed)
print "N %d dim %d ncluster %d kmsample %d metric %s" % (
N, dim, ncluster, kmsample, metric)
X = np.random.exponential( size=(N,dim) )
# cf scikits-learn datasets/
t0 = time()
if kmsample > 0:
centres, xtoc, dist = kmeanssample( X, ncluster, nsample=kmsample,
delta=kmdelta, maxiter=kmiter, metric=metric, verbose=2 )
else:
randomcentres = randomsample( X, ncluster )
centres, xtoc, dist = kmeans( X, randomcentres,
delta=kmdelta, maxiter=kmiter, metric=metric, verbose=2 )
print "%.0f msec" % ((time() - t0) * 1000)
# also ~/py/np/kmeans/test-kmeans.py
2012 年 3 月 26 日添加了一些注释:
1) 对于余弦距离,首先将所有数据向量归一化为 |X| = 1;那么
cosinedistance( X, Y ) = 1 - X . Y = Euclidean distance |X - Y|^2 / 2
很快。对于位向量,将范数与向量分开 而不是扩展到浮动 (尽管有些程序可能会为您扩展)。 对于稀疏向量,例如 N, X 的 1 %。 Y 应该花费时间 O( 2 % N ), 空间 O(N);但我不知道哪些程序可以做到这一点。
2) Scikit-learn clustering 对 k-means、mini-batch-k-means 进行了很好的概述... 使用适用于 scipy.sparse 矩阵的代码。
3) 总是在 k-means 之后检查集群大小。
如果您期望的集群大小大致相同,但它们会出现
[44 37 9 5 5] %
...(挠头的声音)。
【讨论】:
+1 首先,感谢您分享您的实施。我只是想确认该算法适用于我在 700 维空间中包含 900 个向量的数据集。我只是想知道是否也可以评估生成的集群的质量。是否可以重用代码中的任何值来计算集群质量以帮助选择最佳集群的数量? 传奇,不客气。 (更新代码以打印簇 50 % / 90 % 半径)。 “集群质量”是一个很大的话题:你有多少个集群,你有没有已知集群的训练样本,例如来自专家?关于集群的数量,请参阅 SO how-do-i-determine-k-when-using-k-means-clustering-when-using-k-means-clustering 再次感谢您。实际上,我没有训练样本,但我试图在分类后手动验证集群(也试图扮演领域专家的角色)。在将 SVD 应用于一些原始文档并减小它们的维度后,我正在执行文档级分类。结果看起来不错,但我不确定如何验证它们。在初始阶段,在探索各种集群有效性指标时,我遇到了 Dunn's Index、Elbow 方法等。我不确定该使用哪一种,所以我想从 Elbow 方法开始。 我知道这是非常古老的东西,但我刚开始使用 kmeans 并偶然发现了这一点。对于想要使用此代码的未来读者:请先查看@Anony-Mousse cmets 上面的问题!据我所知,这种实现是错误的假设,即您仍然可以以某种方式使用“集群中点的平均值”来确定该集群的质心。除了欧几里得距离之外,这对其他任何事情都没有意义(除了在单位球体上的非常特殊的情况等......)。再一次,Anony-Mousse 在这个问题上的 cmets 就在鼻子上。 @Nevoris,是的,我同意,除了余弦距离:请参阅 here 了解原因,以及 why-does-k-means-clustering-algorithm-use-only-euclidean-distance-metric【参考方案6】:在你可以做到这一点的地方使用 nltk,例如
from nltk.cluster.kmeans import KMeansClusterer
NUM_CLUSTERS = <choose a value>
data = <sparse matrix that you would normally give to scikit>.toarray()
kclusterer = KMeansClusterer(NUM_CLUSTERS, distance=nltk.cluster.util.cosine_distance, repeats=25)
assigned_clusters = kclusterer.cluster(data, assign_clusters=True)
【讨论】:
这个实现的效率如何?集群低至 5k 个点(维度为 100)似乎需要很长时间。 在维度 100 中,聚类 1k 点每次运行需要 1 秒 (repeats
),1.5k 点需要 2 分钟,而 2k 需要......太长了。
确实;根据下面的@Anony-Mousse 评论,余弦距离似乎可能存在收敛问题。对我来说,这确实是一个垃圾进垃圾出的例子:你可以使用任何你想要的距离函数,但是如果这个函数违反了算法的假设,不要指望它会产生有意义的结果!【参考方案7】:
k-means of Spectral Python 允许使用 L1(曼哈顿)距离。
【讨论】:
【参考方案8】:是的,您可以使用差异度量函数;然而,根据定义,k-means 聚类算法依赖于每个聚类平均值的欧氏距离。
您可以使用不同的指标,因此即使您仍在计算平均值,您也可以使用马氏距离之类的指标。
【讨论】:
+1:让我强调一下取均值仅适用于某些距离函数,例如欧几里得距离。对于其他距离函数,您也需要替换聚类中心估计函数! @Anony-Mousse。例如,当我使用余弦距离时,我应该改变什么? 我不知道。我还没有看到与余弦收敛的证据。我相信如果你的数据是非负的并且归一化到单位球体,它会收敛,因为它本质上是不同向量空间中的 k-means。 我同意@Anony-Mousse。对我来说,这只是garbage-in-garbage-out的一个例子:你可以用你想要的任何距离函数运行K-means,但是如果该函数违反了算法的基本假设,不要指望它会产生有意义的结果! @Anony-Mousse 但是如何使用马氏距离实现 K-means?以上是关于是否可以使用 scikit-learn K-Means Clustering 指定您自己的距离函数?的主要内容,如果未能解决你的问题,请参考以下文章
是否可以使用 scikit-learn K-Means Clustering 指定您自己的距离函数?
是否可以使用 scikit-learn K-Means Clustering 指定您自己的距离函数?
是否可以将 TransformedTargetRegressor 添加到 scikit-learn 管道中?