地図上のポイントを距離ごとに同じサイズのグループにグループ化するための最速のアルゴリズムを探しています。 k-meansクラスタリングアルゴリズム は簡単で有望に見えますが、同じサイズのグループを生成しません。
このアルゴリズムのバリエーションや、すべてのクラスターでメンバーの数を同じにする別のアルゴリズムがありますか?
これは、トリックを行う可能性があります: (ロイドのアルゴリズム を適用して、k重心を取得します。配列内の関連するクラスターのサイズを降順で重心を並べ替えます。 i= 1からk-1まで、データポイントをclusteri他の重心までの最小距離j(i<j≤k)offjおよび重心を再計算i(ただし、クラスターを再計算しないでください)クラスターサイズがn/k。
この後処理ステップの複雑さはO(k²nlgn)。
[〜#〜] elki [〜#〜] データマイニングフレームワークには、 等サイズk-meansのチュートリアル があります。
これはparticularyの良いアルゴリズムではありませんが、独自のクラスタリングアルゴリズムを実装する方法を教えるためのチュートリアルを書くのに十分な簡単なk-meansバリエーションです変化; SSQの品質は通常のk-meansよりも劣りますが、明らかに同じサイズのクラスターが必要な人もいます。
ELKI 0.7.5では、このアルゴリズムをtutorial.clustering.SameSizeKMeansAlgorithm
。
重み付きグラフの定義として距離を表示できます。かなり少数のグラフ分割アルゴリズムは、グラフの頂点を同じサイズの2つのセットに分割しようとすることに明示的に基づいています。これは、たとえば Kernighan-Linアルゴリズム および スペクトルグラフ分割 でラプラシアンを使用して表示されます。複数のクラスターを取得するには、パーティションアルゴリズムを再帰的に適用できます。これについては、スペクトルグラフの分割に関するリンクで素敵な議論があります。
このk-meansバリエーションを試してください。
初期化:
k
センターをランダムにデータセットから選択するか、kmeans ++戦略を使用してさらに良い最後に、クラスターごとに同じオブジェクトの+ -1個の要件を満たすパーティションを作成する必要があります(最後のいくつかのクラスターにも正しい数があることを確認してください。最初のm
クラスターにはceil
オブジェクト、残りは正確にfloor
オブジェクト。)
反復ステップ:
必要条件:「スワップ提案」(異なるクラスターにあることを好むオブジェクト)を持つ各クラスターのリスト。
[〜#〜] e [〜#〜]ステップ:通常のk-meansのように、更新されたクラスター中心を計算します
[〜#〜] m [〜#〜]ステップ:すべてのポイント(1つだけ、または1つのバッチ内のすべて)を反復処理します。
オブジェクトに最も近いクラスター中心/現在のクラスターより近いすべてのクラスター中心を計算します。別のクラスターの場合:
クラスターサイズは不変(+-天井/床の差)のままであり、オブジェクトは、推定の改善につながる限り、あるクラスターから別のクラスターにのみ移動されます。したがって、k-meansのようなある点で収束するはずです。しかし、少し遅いかもしれません(つまり、より多くの反復)。
これが以前に公開または実装されたかどうかはわかりません。それは私が試してみただけです(k-meansを試してみると、はるかに優れたクラスタリングアルゴリズムがあります。)
この質問といくつかの同様の質問を読んだ後、Elkiのチュートリアルで https://elki-project.github.io/tutorialを使用して、同じサイズのk-meansのpython実装を作成しました/ same-size_k_means これは、ほとんどの一般的なメソッドと使い慣れたAPIにscikit-learnのK-Means実装を利用します。
私の実装はここにあります: https://github.com/ndanielsen/Same-Size-K-Means
クラスタリングロジックはこの関数にあります:_labels_inertia_precompute_dense()
再帰的な貪欲なマージの形式を検討してください。各ポイントはシングルトンクラスタとして始まり、そのようなマージが最大を超えないように、最も近い2つを繰り返しマージします。サイズ。最大サイズを超えて選択する余地がない場合は、ローカルで再クラスタ化します。これは、階層型クラスタリングのバックトラッキングの形式です。 http://en.wikipedia.org/wiki/Hierarchical_clustering
クラスター重心を指定すると、よりクリーンな後処理が行われます。 N
をアイテムの数、K
をクラスターの数、S = ceil(N/K)
の最大クラスターサイズとします。
(item_id, cluster_id, distance)
(item_id, cluster_id, distance)
:cluster_id
の要素数がS
を超える場合、何もしないitem_id
をクラスターcluster_id
に追加します。これはO(NK lg(N))で実行され、@ larsmansソリューションに匹敵する結果が得られ、実装が簡単です。擬似Pythonで
dists = []
clusts = [None] * N
counts = [0] * K
for i, v in enumerate(items):
dist = map( lambda x: dist(x, v), centroids )
dd = map( lambda (k, v): (i, k, v), enumerate(dist) )
dists.extend(dd)
dists = sorted(dists, key = lambda (x,y,z): z)
for (item_id, cluster_id, d) in dists:
if counts[cluster_id] >= S:
continue
if clusts[item_id] == None:
clusts[item_id] = cluster_id
counts[cluster_id] = counts[cluster_id] + 1
一般に、マップ上のポイントを距離ごとに同じサイズのグループにグループ化することは、理論上不可能な使命です。ポイントを同じサイズのグループにグループ化するため競合するクラスター内のポイントを距離によってグループ化する。
このプロットを参照してください:
4つのポイントがあります。
A.[1,1]
B.[1,2]
C.[2,2]
D.[5,5]
これらのポイントを2つのクラスターにクラスター化する場合。明らかに、(A、B、C)はクラスター1、Dはクラスター2です。しかし、同じサイズのグループが必要な場合、(A、B)は1つのクラスターになり、(C、D)はもう1つのクラスターになります。 Cは(A、B)の中心に近いが、クラスター(C、D)に属するため、これはクラスタールールに違反します。そのため、クラスターと同じサイズのグループの要件を同時に満たすことはできません。
2012年1月追加:後処理よりも、クラスターのサイズを成長とほぼ同じに保つ方が良いでしょう。
たとえば、Xごとに3つの最も近い中心を見つけ、Xを最適な距離-λclustersizeの中心に追加します。
K-meansからのクラスターのサイズがほぼ等しい場合、k-meansの後の単純な貪欲な後処理で十分です。
(これはk-meansからのNpt x C距離行列の割り当てアルゴリズムに近似しています。)
繰り返すことができます
diffsizecentres = kmeans( X, centres, ... )
X_centre_distances = scipy.spatial.distance.cdist( X, diffsizecentres )
# or just the nearest few centres
xtoc = samesizeclusters( X_centre_distances )
samesizecentres = [X[xtoc[c]].mean(axis=0) for c in range(k)]
...
繰り返しによってセンターが大きく変わった場合は驚かされますが、™に依存します。
Npoint NclusterとNdimの大きさはどれくらいですか?
#!/usr/bin/env python
from __future__ import division
from operator import itemgetter
import numpy as np
__date__ = "2011-03-28 Mar denis"
def samesizecluster( D ):
""" in: point-to-cluster-centre distances D, Npt x C
e.g. from scipy.spatial.distance.cdist
out: xtoc, X -> C, equal-size clusters
method: sort all D, greedy
"""
# could take only the nearest few x-to-C distances
# add constraints to real assignment algorithm ?
Npt, C = D.shape
clustersize = (Npt + C - 1) // C
xcd = list( np.ndenumerate(D) ) # ((0,0), d00), ((0,1), d01) ...
xcd.sort( key=itemgetter(1) )
xtoc = np.ones( Npt, int ) * -1
nincluster = np.zeros( C, int )
nall = 0
for (x,c), d in xcd:
if xtoc[x] < 0 and nincluster[c] < clustersize:
xtoc[x] = c
nincluster[c] += 1
nall += 1
if nall >= Npt: break
return xtoc
#...............................................................................
if __== "__main__":
import random
import sys
from scipy.spatial import distance
# http://docs.scipy.org/doc/scipy/reference/spatial.distance.html
Npt = 100
C = 3
dim = 3
seed = 1
exec( "\n".join( sys.argv[1:] )) # run this.py N= ...
np.set_printoptions( 2, threshold=200, edgeitems=5, suppress=True ) # .2f
random.seed(seed)
np.random.seed(seed)
X = np.random.uniform( size=(Npt,dim) )
centres = random.sample( X, C )
D = distance.cdist( X, centres )
xtoc = samesizecluster( D )
print "samesizecluster sizes:", np.bincount(xtoc)
# Npt=100 C=3 -> 32 34 34
最近、あまり大きくないデータセットのためにこれを自分で必要としました。私の答えは、実行時間が比較的長いものの、ローカル最適に収束することが保証されています。
def eqsc(X, K=None, G=None):
"equal-size clustering based on data exchanges between pairs of clusters"
from scipy.spatial.distance import pdist, squareform
from matplotlib import pyplot as plt
from matplotlib import animation as ani
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
def error(K, m, D):
"""return average distances between data in one cluster, averaged over all clusters"""
E = 0
for k in range(K):
i = numpy.where(m == k)[0] # indeces of datapoints belonging to class k
E += numpy.mean(D[numpy.meshgrid(i,i)])
return E / K
numpy.random.seed(0) # repeatability
N, n = X.shape
if G is None and K is not None:
G = N // K # group size
Elif K is None and G is not None:
K = N // G # number of clusters
else:
raise Exception('must specify either K or G')
D = squareform(pdist(X)) # distance matrix
m = numpy.random.permutation(N) % K # initial membership
E = error(K, m, D)
# visualization
#FFMpegWriter = ani.writers['ffmpeg']
#writer = FFMpegWriter(fps=15)
#fig = plt.figure()
#with writer.saving(fig, "ec.mp4", 100):
t = 1
while True:
E_p = E
for a in range(N): # systematically
for b in range(a):
m[a], m[b] = m[b], m[a] # exchange membership
E_t = error(K, m, D)
if E_t < E:
E = E_t
print("{}: {}<->{} E={}".format(t, a, b, E))
#plt.clf()
#for i in range(N):
#plt.text(X[i,0], X[i,1], m[i])
#writer.grab_frame()
else:
m[a], m[b] = m[b], m[a] # put them back
if E_p == E:
break
t += 1
fig, ax = plt.subplots()
patches = []
for k in range(K):
i = numpy.where(m == k)[0] # indeces of datapoints belonging to class k
x = X[i]
patches.append(Polygon(x[:,:2], True)) # how to draw this clock-wise?
u = numpy.mean(x, 0)
plt.text(u[0], u[1], k)
p = PatchCollection(patches, alpha=0.5)
ax.add_collection(p)
plt.show()
if __== "__main__":
N, n = 100, 2
X = numpy.random.Rand(N, n)
eqsc(X, G=3)
私もこの問題を解決する方法に苦労してきました。ただし、この間ずっと間違ったキーワードを使用していたことに気付きました。ポイント結果メンバーの数を同じサイズにしたい場合は、クラスタリングではなくグループ化を行っています。単純なpythonスクリプトおよびpostgisクエリを使用して、問題を最終的に解決できました。
たとえば、tb_pointsというテーブルに4000の座標点があり、それを同じサイズの10個のグループに分割します。このグループにはそれぞれ400の座標点が含まれます。これがテーブル構造の例です
CREATE TABLE tb_points (
id SERIAL PRIMARY KEY,
outlet_id INTEGER,
longitude FLOAT,
latitide FLOAT,
group_id INTEGER
);
次に、あなたがする必要があるのは:
これは、Pythonでの実装です。
import psycopg2
dbhost = ''
dbuser = ''
dbpass = ''
dbname = ''
dbport = 5432
conn = psycopg2.connect(Host = dbhost,
user = dbuser,
password = dbpass,
database = dbname,
port = dbport)
def fetch(sql):
cursor = conn.cursor()
rs = None
try:
cursor.execute(sql)
rs = cursor.fetchall()
except psycopg2.Error as e:
print(e.pgerror)
rs = 'error'
cursor.close()
return rs
def execScalar(sql):
cursor = conn.cursor()
try:
cursor.execute(sql)
conn.commit()
rowsaffected = cursor.rowcount
except psycopg2.Error as e:
print(e.pgerror)
rowsaffected = -1
conn.rollback()
cursor.close()
return rowsaffected
def select_first_cluster_id():
sql = """ SELECT a.outlet_id as ori_id, a.longitude as ori_lon,
a.latitude as ori_lat, b.outlet_id as dest_id, b.longitude as
dest_lon, b.latitude as dest_lat,
ST_Distance(CAST(ST_SetSRID(ST_Point(a.longitude,a.latitude),4326)
AS geography),
CAST(ST_SetSRID(ST_Point(b.longitude,b.latitude),4326) AS geography))
AS air_distance FROM tb_points a CROSS JOIN tb_points b WHERE
a.outlet_id != b.outlet_id and a.group_id is NULL and b.group_id is
null order by air_distance desc limit 1 """
return sql
def update_group_id(group_id, ori_id, limit_constraint):
sql = """ UPDATE tb_points
set group_id = %s
where outlet_id in
(select b.outlet_id
from tb_points a,
tb_points b
where a.outlet_id = '%s'
and a.group_id is null
and b.group_id is null
order by ST_Distance(CAST(ST_SetSRID(ST_Point(a.longitude,a.latitude),4326) AS geography),
CAST(ST_SetSRID(ST_Point(b.longitude,b.latitude),4326) AS geography)) asc
limit %s)
""" % (group_id, ori_id, limit_constraint)
return sql
def clustering():
data_constraint = [100]
n = 1
while n <= 10:
sql = select_first_cluster_id()
res = fetch(sql)
ori_id = res[0][0]
sql = update_group_id(n, ori_id, data_constraint[0])
print(sql)
execScalar(sql)
n += 1
clustering()
それが役に立てば幸い
また、各パーティションのメンバーがアルゴリズムへの入力であるBUCKET_SIZE未満になるまでデータをパーティション化するK-dツリーも見てください。
これにより、バケット/パーティションはまったく同じサイズになりませんが、すべてBUCKET_SIZE未満になります。