Кластеризация данных путем усреднения там, где градиент мал

#python #numpy

#python #numpy

Вопрос:

Мои данные, которые являются довольно непрерывными, но имеют разные области. Я пытаюсь определить центр каждого такого кластера (приблизительно).

По сути, данные представляют собой список 2D векторов в (N,2) массиве numpy. Данные имеют характерную структуру, которая выглядит следующим образом:

структура данных

Вы можете видеть, что два кластера появляются (частично) в списке, и я хотел бы определить их центры (скажем, путем усреднения). Я вычислил норму градиента L2, и, похоже, она очень хорошо коррелирует с границами между кластерами (конечно).

Далее я хотел бы усреднить все точки данных, которые принадлежат каждому кластеру, как определено величиной градиента (если, конечно, у кого-то нет лучшей идеи). Я бы хотел избежать зацикливания на данных и вместо этого использовать векторизованные операции, чтобы они выполнялись быстро. Это трудность для меня.

Спасибо!

Ограничения:

  1. Я не могу использовать какую-либо библиотеку, кроме numpy и opencv.

Ответ №1:

Я нашел способ, который использует цикл for , но он повторяется по кластерам, а не по значениям, поэтому у вас все еще есть преимущества векторизованных операций.

Я настроил свой тестовый пример в соответствии с вашим описанием проблемы следующим образом:

 data = np.ndarray((30,2))
data[:10] = np.random.random((10,2)) * 4   60
data[10:20] = np.random.random((10,2)) * 4   70
data[20:30] = np.random.random((10,2)) * 4   80
norms = np.linalg.norm(np.gradient(data,axis=0), axis=1)
 

И я нашел центры, подобные этому:

 threshold = 5 # Specify how much greater the gradient is at the threshold

# get cluster boundaries by finding the indexes at which 
# the next gradient norm is significantly smaller
boundaries = np.where(norms[:-1] > norms[1:]   threshold)
# add start and end boundaries
boundaries = np.hstack((0,*boundaries,None))

centers = np.ndarray((boundaries.size-1,2)) # empty array for centers
# calulate the mean between each set of boundaries
for i in range(boundaries.size-1):
  centers[i] = np.mean(data[boundaries[i]:boundaries[i 1]],axis=0)

print(centers)
 

Что для моего тестового примера дало мне эти центры:

 [[62.34829355 62.46226224]
 [71.70243459 72.23661627]
 [81.54729804 82.25504445]]
 

Комментарии:

1. Очень приятно! Кажется эффективным!

Ответ №2:

Если вам разрешено использовать OpenCV, я думаю, что самый простой способ выполнить работу — использовать cv2.kmeans :

 import numpy as np
import cv2
import matplotlib.pyplot as plt

vectors = np.array([[64, 20],
                    [64, 21],
                    [65, 19],
                    [65, 20],
                    [66, 20],
                    [68, 19],
                    [74, 4],
                    [74, 7],
                    [74, 8],
                    [75, 3],
                    [75, 4]], dtype=np.float32)

criteria = (cv2.TERM_CRITERIA_EPS   cv2.TERM_CRITERIA_MAX_ITER, 200, .1)
flags = cv2.KMEANS_RANDOM_CENTERS
_, labels, centers = cv2.kmeans(data=vectors, 
                                K=2, 
                                bestLabels=None, 
                                criteria=criteria,
                                attempts=100,
                                flags=flags)

plt.scatter(vectors[:, 0], vectors[:, 1], s=30)
for x, y in centers:
    plt.scatter(x, y, s=300, marker='s')
 

диаграмма рассеяния

Ваши данные представлены в виде маленьких точек, в то время как центры кластеров отображаются в виде больших квадратов.

Комментарии:

1. K — это то, что я пытаюсь найти (вместе с центром для каждого кластера)

2. Если количество кластеров неизвестно априори , вы должны оценить его, например, с помощью метода elbow