Моя реализация Python из Дэвис-Bouldin индекс правильный?
Я пытаюсь вычислить Дэвис-Bouldin индекс в Python.
Вот шаги, которые пытается воспроизвести приведенный ниже код.
5 шаги :
- для каждого кластера вычислите евклидовы расстояния между каждой точкой и центроидом
- для каждого кластера вычислите среднее из этих расстояний
- для каждой пары кластеров вычислите евклидово расстояние между их центроидами
Затем,
- для для каждой пары кластеров составьте сумму средних расстояний до их соответствующих центроидов (вычисленных на Шаге 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 ответа:
Вот более короткая, более быстрая исправленная версия наивной реализации индекса Дэвиса-Булдена выше.
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