Моя реализация Python из Дэвис-Bouldin индекс правильный?


Я пытаюсь вычислить Дэвис-Bouldin индекс в Python.

Вот шаги, которые пытается воспроизвести приведенный ниже код.

5 шаги :

  1. для каждого кластера вычислите евклидовы расстояния между каждой точкой и центроидом
  2. для каждого кластера вычислите среднее из этих расстояний
  3. для каждой пары кластеров вычислите евклидово расстояние между их центроидами

Затем,

  1. для для каждой пары кластеров составьте сумму средних расстояний до их соответствующих центроидов (вычисленных на Шаге 2) и разделите ее на расстояние, разделяющее их (вычисленное на Шаге 3).

Наконец,

    Вычислите среднее значение всех этих делений (= всех индексов), чтобы получить индекс Дэвиса-Булдена всей кластеризации

Код

def daviesbouldin(X, labels, centroids):

    import numpy as np
    from scipy.spatial.distance import pdist, euclidean

    nbre_of_clusters = len(centroids) #Get the number of clusters
    distances = [[] for e in range(nbre_of_clusters)] #Store intra-cluster distances by cluster
    distances_means = [] #Store the mean of these distances
    DB_indexes = [] #Store Davies_Boulin index of each pair of cluster
    second_cluster_idx = [] #Store index of the second cluster of each pair
    first_cluster_idx = 0 #Set index of first cluster of each pair to 0

    # Step 1: Compute euclidean distances between each point of a cluster to their centroid
    for cluster in range(nbre_of_clusters):
        for point in range(X[labels == cluster].shape[0]):
            distances[cluster].append(euclidean(X[labels == cluster][point], centroids[cluster]))

    # Step 2: Compute the mean of these distances
    for e in distances:
        distances_means.append(np.mean(e))

    # Step 3: Compute euclidean distances between each pair of centroid
    ctrds_distance = pdist(centroids) 

    # Tricky step 4: Compute Davies-Bouldin index of each pair of cluster   
    for i, e in enumerate(e for start in range(1, nbre_of_clusters) for e in range(start, nbre_of_clusters)):
        second_cluster_idx.append(e)
        if second_cluster_idx[i-1] == nbre_of_clusters - 1:
            first_cluster_idx += 1
        DB_indexes.append((distances_means[first_cluster_idx] + distances_means[e]) / ctrds_distance[i])

    # Step 5: Compute the mean of all DB_indexes   
    print("DAVIES-BOULDIN Index: %.5f" % np.mean(DB_indexes)) 

В аргументах:

  • X - это данные
  • labels, являются надписи рассчитывается путем алгоритм кластеризации (то есть: kmeans)
  • centroids - координаты центроида каждого кластера (т. е.: cluster_centers_)

Также обратите внимание, что я использую Python 3

Вопрос 1 : правильно ли вычисление евклидовых расстояний между каждой парой центроидов (Шаг 3)?

QUESTION2 : является ли моя реализация шага 4 правильной?

Вопрос 3 : нужно ли нормализовать внутрикластерные и межкластерные расстояния ?


дальнейшие пояснения на шаге 4

Предположим, у нас есть 10 кластеров. Цикл должен вычислить индекс БД каждой пары кластеров.

На первой итерации:

  • суммирует среднее значение внутренних расстояний кластера 1 (индекс 0 из distances_means) и среднее значение внутренних расстояний кластера 2 (индекс 1 из distances_means)
  • делит эту сумму на расстояние между двумя кластерами (индекс 0 из ctrds_distance)

На втором итерация:

  • суммирует среднее значение внутренних расстояний кластера 1 (индекс 0 из distances_means) и среднее значение внутренних расстояний кластера 3 (индекс 2 из distances_means)
  • делит эту сумму на расстояние между двумя кластерами (индекс 1 из ctrds_distance)

И так далее...

На примере 10 кластеров полный итерационный процесс должен выглядеть следующим образом:

intra-cluster distance intra-cluster distance       distance between their
      of cluster:             of cluster:           centroids(storage num):
         0           +             1            /             0
         0           +             2            /             1
         0           +             3            /             2
         0           +             4            /             3
         0           +             5            /             4
         0           +             6            /             5
         0           +             7            /             6
         0           +             8            /             7
         0           +             9            /             8
         1           +             2            /             9
         1           +             3            /             10
         1           +             4            /             11
         1           +             5            /             12
         1           +             6            /             13
         1           +             7            /             14
         1           +             8            /             15
         1           +             9            /             16
         2           +             3            /             17
         2           +             4            /             18
         2           +             5            /             19
         2           +             6            /             20
         2           +             7            /             21
         2           +             8            /             22
         2           +             9            /             23
         3           +             4            /             24
         3           +             5            /             25
         3           +             6            /             26
         3           +             7            /             27
         3           +             8            /             28
         3           +             9            /             29
         4           +             5            /             30
         4           +             6            /             31
         4           +             7            /             32
         4           +             8            /             33
         4           +             9            /             34
         5           +             6            /             35
         5           +             7            /             36
         5           +             8            /             37
         5           +             9            /             38
         6           +             7            /             39
         6           +             8            /             40
         6           +             9            /             41
         7           +             8            /             42
         7           +             9            /             43
         8           +             9            /             44
Проблема здесь в том, что я не совсем уверен, что индекс distances_means соответствует индексу ctrds_distance.

В другом другими словами, Я не уверен, что первое вычисленное межкластерное расстояние соответствует расстоянию между кластером 1 и кластером 2. И что второе вычисленное межкластерное расстояние соответствует расстоянию между кластером 3 и кластером 1... и так далее, следуя описанной выше схеме.

Короче говоря: боюсь, что я делю пары внутрикластерных расстояний на межкластерные расстояния, которые не соответствуют друг другу.

Любая помощь более чем приветствуется !

4 4

4 ответа:

Вот более короткая, более быстрая исправленная версия наивной реализации индекса Дэвиса-Булдена выше.

def DaviesBouldin(X, labels):
    n_cluster = len(np.bincount(labels))
    cluster_k = [X[labels == k] for k in range(n_cluster)]
    centroids = [np.mean(k, axis = 0) for k in cluster_k]
    variances = [np.mean([euclidean(p, centroids[i]) for p in k]) for i, k in enumerate(cluster_k)]
    db = []

    for i in range(n_cluster):
        for j in range(n_cluster):
            if j != i:
                db.append((variances[i] + variances[j]) / euclidean(centroids[i], centroids[j]))

    return(np.max(db) / n_cluster)

Отвечая на мои собственные вопросы:

  • счетчик на первом черновом варианте (Шаг 4) был правильным, но не относящимся к делу
  • нет необходимости нормализовать внутрикластерные и межкластерные расстояния
  • произошла ошибка при вычислении евклидовых расстояний

Примечание Вы можете найти инновационные подходы, которые пытаются улучшить этот индекс, в частности " новая версия Дэвиса-Bouldin индекс", который заменяет Евклидову расстоянию цилиндрической расстояние.

Спасибо за код и доработку-действительно помогли мне начать работу. Более короткая и быстрая версия не совсем верна. Я изменил его, чтобы правильно усреднить оценки дисперсии наиболее сходного кластера для каждого кластера.

См. https://www.researchgate.net/publication/224377470_A_Cluster_Separation_Measure для оригинального алгоритма и объяснения:

DBI-среднее значение мер сходства каждого кластера с его наиболее похожий кластер.

def DaviesBouldin(X, labels):
    n_cluster = len(np.bincount(labels))
    cluster_k = [X[labels == k] for k in range(n_cluster)]
    centroids = [np.mean(k, axis = 0) for k in cluster_k]

    # calculate cluster dispersion
    S = [np.mean([euclidean(p, centroids[i]) for p in k]) for i, k in enumerate(cluster_k)]
    Ri = []

    for i in range(n_cluster):
        Rij = []
        # establish similarity between each cluster and all other clusters
        for j in range(n_cluster):
            if j != i:
                r = (S[i] + S[j]) / euclidean(centroids[i], centroids[j])
                Rij.append(r)
         # select Ri value of most similar cluster
         Ri.append(max(Rij)) 

    # get mean of all Ri values    
    dbi = np.mean(Ri)

    return dbi

Спасибо за вашу реализацию. У меня только один вопрос: не пропало ли деление в последнем ряду. На последнем шаге значение max (db) должно быть разделено на реализованное число кластеров.

def DaviesBouldin(Daten, DatenLabels):
n_cluster = len(np.bincount(DatenLabels)) 
cluster_k = [Daten[DatenLabels == k] for k in range(n_cluster)] 
centroids = [np.mean(k, axis = 0) for k in cluster_k] 
variances = [np.mean([euclidean(p, centroids[i]) for p in k]) for i, k in enumerate(cluster_k)] # mittlere Entfernung zum jeweiligen Clusterzentrum
db = []

for i in range(n_cluster):
    for j in range(n_cluster):
        if j != i:
            db.append((variances[i] + variances[j]) / euclidean(centroids[i], centroids[j]) / n_cluster)
return(np.max(db))

Может быть, я наблюдаю за этим подразделением, потому что я новичок в Python. Но в моей графике (я перебираю диапазон кластеров) значение DB.Макс очень низок в начале и увеличивается позже. После масштабирования по количеству кластеров график выглядит лучше (высокий DB.максимум значение в начале и постоянно падает с увеличением числа кластеров).

С наилучшими пожеланиями

Это в 20 раз быстрее, чем приведенный ниже код, все вычисления выполняются в numpy.

import numpy as np
from scipy.spatial.distance import euclidean, cdist, pdist, squareform

def db_index(X, y):
    """
    Davies-Bouldin index is an internal evaluation method for
    clustering algorithms. Lower values indicate tighter clusters that 
    are better separated.
    """
    # get unique labels
    if y.ndim == 2:
        y = np.argmax(axis=1)
    uniqlbls = np.unique(y)
    n = len(uniqlbls)
    # pre-calculate centroid and sigma
    centroid_arr = np.empty((n, X.shape[1]))
    sigma_arr = np.empty((n,1))
    dbi_arr = np.empty((n,n))
    mask_arr = np.invert(np.eye(n, dtype='bool'))
    for i,k in enumerate(uniqlbls):
        Xk = X[np.where(y==k)[0],...]
        Ak = np.mean(Xk, axis=0)
        centroid_arr[i,...] = Ak
        sigma_arr[i,...] = np.mean(cdist(Xk, Ak.reshape(1,-1)))
    # compute pairwise centroid distances, make diagonal elements non-zero
    centroid_pdist_arr = squareform(pdist(centroid_arr)) + np.eye(n)
    # compute pairwise sigma sums
    sigma_psum_arr = squareform(pdist(sigma_arr, lambda u,v: u+v))
    # divide 
    dbi_arr = np.divide(sigma_psum_arr, centroid_pdist_arr)
    # get mean of max of off-diagonal elements
    dbi_arr = np.where(mask_arr, dbi_arr, 0)
    dbi = np.mean(np.max(dbi_arr, axis=1))
    return dbi


Вот реализация, использующая numpy 1.14, scipy 1.1.0 и python 3. Не сильно улучшается скорость вычислений,но должен быть немного меньше объем памяти.

import numpy as np
from scipy.spatial.distance import euclidean, cdist, pdist, squareform

def db_index(X, y):
    """
    Davies-Bouldin index is an internal evaluation method for
    clustering algorithms. Lower values indicate tighter clusters that 
    are better separated.

    Arguments
    ----------
    X : 2D array (n_samples, embed_dim)
    Vector for each example.

    y : 1D array (n_samples,) or 2D binary array (n_samples, n_classes)
    True labels for each example.

    Returns
    ----------
    dbi : float
        Calculated Davies-Bouldin index.
    """
    # get unique labels
    if y.ndim == 2:
        y = np.argmax(axis=1)
    uniqlbls = np.unique(y)
    n = len(uniqlbls)
    # pre-calculate centroid and sigma
    centroid_arr = np.empty((n, X.shape[1]))
    sigma_arr = np.empty(n)
    for i,k in enumerate(uniqlbls):
        Xk = X[np.where(y==k)[0],...]
        Ak = np.mean(Xk, axis=0)
        centroid_arr[i,...] = Ak
        sigma_arr[i,...] = np.mean(cdist(Xk, Ak.reshape(1,-1)))
    # loop over non-duplicate cluster pairs
    dbi = 0
    for i in range(n):
        max_Rij = 0
        for j in range(n):
            if j != i:
                Rij = np.divide(sigma_arr[i] + sigma_arr[j], 
                                euclidean(centroid_arr[i,...], centroid_arr[j,...]))
                if Rij > max_Rij:
                    max_Rij = Rij
        dbi += max_Rij
    return dbi/n