web-dev-qa-db-ja.com

クラスターサイズが等しいK平均アルゴリズムのバリエーション

地図上のポイントを距離ごとに同じサイズのグループにグループ化するための最速のアルゴリズムを探しています。 k-meansクラスタリングアルゴリズム は簡単で有望に見えますが、同じサイズのグループを生成しません。

このアルゴリズムのバリエーションや、すべてのクラスターでメンバーの数を同じにする別のアルゴリズムがありますか?

参照: サイズが等しいk個のクラスター内のグループnポイント

45
pixelistik

これは、トリックを行う可能性があります: (ロイドのアルゴリズム を適用して、k重心を取得します。配列内の関連するクラスターのサイズを降順で重心を並べ替えます。 i= 1からk-1まで、データポイントをclusteri他の重心までの最小距離ji<jk)offjおよび重心を再計算i(ただし、クラスターを再計算しないでください)クラスターサイズがn/k

この後処理ステップの複雑さはO(k²nlgn)。

12
Fred Foo

[〜#〜] elki [〜#〜] データマイニングフレームワークには、 等サイズk-meansのチュートリアル があります。

これはparticularyの良いアルゴリズムではありませんが、独自のクラスタリングアルゴリズムを実装する方法を教えるためのチュートリアルを書くのに十分な簡単なk-meansバリエーションです変化; SSQの品質は通常のk-meansよりも劣りますが、明らかに同じサイズのクラスターが必要な人もいます。

ELKI 0.7.5では、このアルゴリズムをtutorial.clustering.SameSizeKMeansAlgorithm

5
Erich Schubert

重み付きグラフの定義として距離を表示できます。かなり少数のグラフ分割アルゴリズムは、グラフの頂点を同じサイズの2つのセットに分割しようとすることに明示的に基づいています。これは、たとえば Kernighan-Linアルゴリズム および スペクトルグラフ分割 でラプラシアンを使用して表示されます。複数のクラスターを取得するには、パーティションアルゴリズムを再帰的に適用できます。これについては、スペクトルグラフの分割に関するリンクで素敵な議論があります。

5

このk-meansバリエーションを試してください。

初期化

  • kセンターをランダムにデータセットから選択するか、kmeans ++戦略を使用してさらに良い
  • 各ポイントについて、最も近いクラスター中心までの距離を計算し、このためのヒープを構築します
  • クラスタが既にいっぱいになっていない限り、ヒープからポイントを描画し、最も近いクラスターに割り当てます。その場合、次に最も近いクラスター中心を計算し、ヒープに再挿入します

最後に、クラスターごとに同じオブジェクトの+ -1個の要件を満たすパーティションを作成する必要があります(最後のいくつかのクラスターにも正しい数があることを確認してください。最初のmクラスターにはceilオブジェクト、残りは正確にfloorオブジェクト。)

反復ステップ

必要条件:「スワップ提案」(異なるクラスターにあることを好むオブジェクト)を持つ各クラスターのリスト。

[〜#〜] e [〜#〜]ステップ:通常のk-meansのように、更新されたクラスター中心を計算します

[〜#〜] m [〜#〜]ステップ:すべてのポイント(1つだけ、または1つのバッチ内のすべて)を反復処理します。

オブジェクトに最も近いクラスター中心/現在のクラスターより近いすべてのクラスター中心を計算します。別のクラスターの場合:

  • 他のクラスターが現在のクラスターよりも小さい場合は、新しいクラスターに移動します
  • 他のクラスター(または距離の短いクラスター)からスワップの提案がある場合、2つの要素クラスターの割り当てをスワップします(複数のオファーがある場合、改善度が最も高いものを選択します)
  • そうでない場合は、他のクラスターのスワップ提案を示します

クラスターサイズは不変(+-天井/床の差)のままであり、オブジェクトは、推定の改善につながる限り、あるクラスターから別のクラスターにのみ移動されます。したがって、k-meansのようなある点で収束するはずです。しかし、少し遅いかもしれません(つまり、より多くの反復)。

これが以前に公開または実装されたかどうかはわかりません。それは私が試してみただけです(k-meansを試してみると、はるかに優れたクラスタリングアルゴリズムがあります。)

3
Anony-Mousse

この質問といくつかの同様の質問を読んだ後、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
Nate

再帰的な貪欲なマージの形式を検討してください。各ポイントはシングルトンクラスタとして始まり、そのようなマージが最大を超えないように、最も近い2つを繰り返しマージします。サイズ。最大サイズを超えて選択する余地がない場合は、ローカルで再クラスタ化します。これは、階層型クラスタリングのバックトラッキングの形式です。 http://en.wikipedia.org/wiki/Hierarchical_clustering

2
sclv

クラスター重心を指定すると、よりクリーンな後処理が行われます。 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
2

一般に、マップ上のポイントを距離ごとに同じサイズのグループにグループ化することは、理論上不可能な使命です。ポイントを同じサイズのグループにグループ化するため競合するクラスター内のポイントを距離によってグループ化する。

このプロットを参照してください: enter image description here

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)に属するため、これはクラスタールールに違反します。そのため、クラスターと同じサイズのグループの要件を同時に満たすことはできません。

2
BooksE

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
1
denis

最近、あまり大きくないデータセットのためにこれを自分で必要としました。私の答えは、実行時間が比較的長いものの、ローカル最適に収束することが保証されています。

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)
1
user2341646

このプロジェクトを試してみることを謙虚にお勧めします ekmeans

A Java K-means Clustering実装は、可能な限り空間的に凝集したままクラスターに等しいカーディナリティー制約を適用するオプションの特別な同等オプションを備えています。

それはまだ実験的ですので、 既知のバグ に注意してください。

私もこの問題を解決する方法に苦労してきました。ただし、この間ずっと間違ったキーワードを使用していたことに気付きました。ポイント結果メンバーの数を同じサイズにしたい場合は、クラスタリングではなくグループ化を行っています。単純な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
);

次に、あなたがする必要があるのは:

  1. 出発点となる最初の座標を見つけます
  2. 出発点から最も近い座標を見つけ、距離の昇順で並べ替え、優先メンバーの数で結果を制限します(この場合は400)
  3. Group_id列を更新して結果を更新します
  4. Group_id列がまだNULLである残りのデータに対して、10回を超える3ステップを実行します

これは、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()

それが役に立てば幸い

0
techamateur

また、各パーティションのメンバーがアルゴリズムへの入力であるBUCKET_SIZE未満になるまでデータをパーティション化するK-dツリーも見てください。

これにより、バケット/パーティションはまったく同じサイズになりませんが、すべてBUCKET_SIZE未満になります。

0
Ash