Как создать цветовую карту доверительных оценок для классификации k-ближайшего соседа

#python #algorithm #numpy #matplotlib

#python #алгоритм #numpy #matplotlib

Вопрос:

Чего я хочу:

Для отображения результатов моего простого алгоритма классификации (см. Ниже) в виде цветовой карты в python (данные представлены в 2D), где каждому классу присваивается цвет, а достоверность прогноза в любом месте 2D-карты пропорциональна насыщенности цвета, связанного с предсказанием класса. Изображение ниже как бы иллюстрирует, что я хочу для двоичного файла (задача с двумя классами), в котором красные части могут указывать на сильную уверенность в классе 1, тогда как синие части будут говорить о классе 2. Промежуточные цвета предполагают неопределенность в отношении любого из них. Очевидно, я хочу, чтобы цветовая схема обобщалась на несколько классов, поэтому мне понадобилось бы много цветов, и тогда масштаб изменился бы с белого (неопределенность) на очень яркий цвет, связанный с классом.

иллюстрация http://www.nicolacarlon.it/out.png

Некоторый пример кода:

В моем примере кода используется простой алгоритм kNN, в котором ближайшим k точкам данных разрешено «голосовать» за класс новой точки на карте. Достоверность прогноза просто определяется относительной частотой класса-победителя из k, который проголосовал. Я не имел дела со связями, и я знаю, что есть лучшие вероятностные версии этого метода, но все, что я хочу, это визуализировать мои данные, чтобы показать зрителю вероятность того, что класс находится в определенной части 2D-плоскости.

 import numpy as np
import matplotlib.pyplot as plt


# Generate some training data from three classes
n = 100 # Number of covariates (sample points) for each class in training set. 
mean1, mean2, mean3 = [-1.5,0], [1.5, 0], [0,1.5]
cov1, cov2, cov3 = [[1,0],[0,1]], [[1,0],[0,1]], [[1,0],[0,1]]
X1 = np.asarray(np.random.multivariate_normal(mean1,cov1,n))
X2 = np.asarray(np.random.multivariate_normal(mean2,cov2,n))
X3 = np.asarray(np.random.multivariate_normal(mean3,cov3,n))


plt.plot(X1[:,0], X1[:,1], 'ro', X2[:,0], X2[:,1], 'bo', X3[:,0], X3[:,1], 'go' )

plt.axis('equal'); plt.show() #Display training data


# Prepare the data set as a 3n*3 array where each row is a data point and its associated class
D = np.zeros((3*n,3))
D[0:n,0:2] = X1; D[0:n,2] = 1
D[n:2*n,0:2] = X2; D[n:2*n,2] = 2
D[2*n:3*n,0:2] = X3; D[2*n:3*n,2] = 3

def kNN(x, D, k=3):
    x = np.asarray(x)
    dist = np.linalg.norm(x-D[:,0:2], axis=1)
    i = dist.argsort()[:k] #Return k indices of smallest to highest entries
    counts = np.bincount(D[i,2].astype(int))
    predicted_class = np.argmax(counts) 
    confidence = float(np.max(counts))/k
    return predicted_class, confidence 

print(kNN([-2,0], D, 20))
  

Ответ №1:

Итак, вы можете вычислить два числа для каждой точки в 2D плоскости

  • достоверность (0 .. 1)
  • класс (целое число)

Одна из возможностей — рассчитать собственную карту RGB и показать ее с imshow помощью . Вот так:

 import numpy as np
import matplotlib.pyplot as plt

# color vector with N x 3 colors, where N is the maximum number of classes and the colors are in RGB
mycolors = np.array([
  [ 0, 0, 1],
  [ 0, 1, 0],
  [ 1, 0, 1],
  [ 1, 1, 0],
  [ 0, 1, 1],
  [ 0, 0, 0],
  [ 0, .5, 1]])

# negate the colors
mycolors = 1 - mycolors 

# extents of the area
x0 = -2
x1 = 2
y0 = -2
y1 = 2

# grid over the area
X, Y = np.meshgrid(np.linspace(x0, x1, 1000), np.linspace(y0, y1, 1000))

# calculate the classification and probabilities
classes = classify_func(X, Y)
probabilities = prob_func(X, Y)

# create the basic color map by the class
img = mycolors[classes]

# fade the color by the probability (black for zero prob)
img *= probabilities[:,:,None]

# reverse the negative image back
img = 1 - img

# draw it
plt.imshow(img, extent=[x0,x1,y0,y1], origin='lower')
plt.axis('equal')

# save it
plt.savefig("mymap.png")
  

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

Я создал две очень простые функции для имитации классификации и вероятностей:

 def classify_func(X, Y):
    return np.round(abs(X Y)).astype('int')

def prob_func(X,Y):
    return 1 - 2*abs(abs(X Y)-classify_func(X,Y))
  

Первый дает для данной области целочисленные значения от 0 до 4, а последний дает плавно изменяющиеся вероятности.

Результат:

введите описание изображения здесь

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


Здесь функциям classify_func и prob_func в качестве аргументов задаются два массива, первый из которых представляет собой координаты X, в которых должны быть вычислены значения, а второй — координаты Y. Это хорошо работает, если базовые вычисления полностью векторизованы. С кодом в вопросе это не так, поскольку он вычисляет только отдельные значения.

В этом случае код немного меняется:

 x = np.linspace(x0, x1, 1000)
y = np.linspace(y0, y1, 1000)
classes = np.empty((len(y), len(x)), dtype='int')
probabilities = np.empty((len(y), len(x)))
for yi, yv in enumerate(y):
    for xi, xv in enumerate(x):
    classes[yi, xi], probabilities[yi, xi] = kNN((xv, yv), D)
  

Кроме того, поскольку ваши оценки достоверности не равны 0 .. 1, их необходимо масштабировать:

 probabilities -= np.amin(probabilities)
probabilities /= np.amax(probabilities)
  

После того, как это будет сделано, ваша карта должна выглядеть следующим образом с экстентами -4, -4 ..4,4 (согласно цветовой карте: зеленый = 1, пурпурный = 2, желтый = 3):

Карта kNN


Векторизировать или не векторизировать — вот в чем вопрос

Этот вопрос всплывает время от времени. В Интернете много информации о векторизации, но поскольку быстрый поиск не выявил никаких кратких резюме, я приведу здесь несколько мыслей. Это довольно субъективный вопрос, поэтому все просто отражает мое скромное мнение. У других людей могут быть разные мнения.

Необходимо учитывать три фактора:

  • Производительность
  • разборчивость
  • использование памяти

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

Помимо тривиальных случаев (элементарные простые операции, простые матричные операции), мой подход:

  • напишите код без векторизации и проверьте, что он работает
  • профилируйте код
  • векторизируйте внутренние циклы, если это необходимо и возможно (1D векторизация)
  • создайте 2D-векторизацию, если она простая

Например, операция попиксельной обработки изображений может привести к ситуации, когда я получаю одномерную векторизацию (для каждой строки). Тогда внутренний цикл (для каждого пикселя) выполняется быстро, а внешний цикл (для каждой строки) на самом деле не имеет значения. Код может выглядеть намного проще, если он не пытается использоваться со всеми возможными входными измерениями.

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

Иногда векторизация не дает никакого преимущества в производительности. Например, удобная функция numpy.vectorize может использоваться для векторизации практически любой функции, но в ее документации указано:

Функция векторизации предоставляется в первую очередь для удобства, а не для производительности. Реализация по сути представляет собой цикл for .

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

Векторизация дает большую производительность, только если базовые векторизованные функции выполняются быстрее. Иногда они есть, иногда нет. Только профилирование и опыт подскажут. Кроме того, не всегда необходимо векторизировать все. У вас может быть алгоритм обработки изображений, который имеет как векторизованные, так и попиксельные операции. Это numpy.vectorize очень полезно.

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

И может случиться так, что по пути вы заметите, что n-мерное обобщение не намного сложнее. Затем сделайте это, если позволяет память.

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

1. Большое спасибо! Это выглядит многообещающе. Не могли бы вы, возможно, посоветовать мне, как заставить мою текущую функцию kNN , которая возвращает ваши probabilties и classes значения, работать с входными X данными и Y из meshgrid ? В том виде , в каком я имею его сейчас, моя функция принимает только одну координату x

2. Теперь он там есть. Кроме того, пожалуйста, обратите внимание, что я добавил недостающее origin='lower' в imshow . В противном случае изображение будет перевернуто вверх ногами. Также обратите внимание, что масштабирование изображения немного смещено (масштабный коэффициент n / ( n 1), где n — количество пикселей. Это можно исправить, но это привело бы к излишнему запутыванию кода, и обычно никто все равно не замечает, что 1/1000.

3. Это здорово, я могу взять это отсюда, большое спасибо!

4. Просто продолжение: о чем бы вы посоветовали мне подумать при создании подобного кода? Должен ли я с самого начала думать, что код должен быть векторизован для скорости, поскольку мы вычисляем алгоритм kNN для многих точек?

5. @Jack: Кратко: Да, вы, вероятно, должны в этом случае, но смотрите Мой Отредактированный ответ для более подробного обсуждения. Если вам нужны дополнительные инструкции о том, как это сделать, попробуйте сами, а затем создайте новый вопрос на SO, когда вы застряли. Удачи!