Это дополнение к серии Давайте понятно объясним, в котором я демонстрирую, как кодировать упомянутые модели на питоне с нуля. Если вы не знакомы с КНН, прочитайте мой рассказ по теме:



Данные:

Сначала нам нужно получить наши данные. Хотя я не использую какие-либо расширенные библиотеки для построения модели, такие как sklearn, я собираюсь использовать ее для создания набора данных Iris в целях тестирования. (На практике набор данных будет импортирован из вашего фактического источника)

from sklearn import datasets
import numpy as np

iris = datasets.load_iris()
sepalLength = iris.data[:,0]
sepalWidth = iris.data[:,1]
petalLength = iris.data[:,2]
petalWidth = iris.data[:,3]
## Reducing 4 dimensional data to 2: x = width*height
## Also keeping in a (n, 2) shape of numpy array
X = np.hstack([(petalLength*petalWidth).reshape(iris.data.shape[0],1), \
               (sepalLength*sepalWidth).reshape(iris.data.shape[0],1)])
y = iris.target # Labels

## Generate an unknown labeled datapoint
## Assuring same shape as X: (m, 2)
newData = np.array([8,19]).reshape(1,2)

«X» содержит наш набор данных 150x2, включающий 50 из каждых 3 категорий меток. Каждая точка данных содержит информацию о двух функциях: PetalWidth*PetalHeight и SepalWidth*SepalHeight. «y» содержит 3 метки 150 точек данных, и мы сгенерировали «newData» неизвестной точки данных с меткой. Наша неизвестная радужка имеет значения PetalWidth*PetalHeight = 8 и SepalWidth*SepalHeight = 19.

Модель:

Наша цель — найти ближайшие точки данных Iris относительно наших «новых данных» и подсчитать, какая метка появляется чаще. Сначала мы должны рассчитать каждое расстояние до каждой из 150 точек данных.

def getDistances(X, newData, p=2):
    distances = []
    for x in X:
        ## Minkowsky method with variable parameter 
           ## (defaulting to p=2 -> Eucledian)
        distance = sum(abs(x-newData)**p)**(1/p)
        distances.append(distance)
    return np.array(distances) ## Converting to numpy array

## Calculate a numpy array of each distance
distances = getDistances(X, newData[0])

Теперь, когда мы знаем, как далеко наши «новые данные» расположены относительно нашего набора данных, мы должны выбрать ближайших «К» соседей.

 def getNeighbors(distances, k):
    return np.argsort(distances)[:k] ## Indexes of the neighbors

print(X.shape[0]**0.5) ## -> 12.2475
neighbors = getNeighbors(distances, 13)

Как я отметил в своей истории KNN, мы определяем K как нечетное число, близкое к квадратному корню из размера нашей выборки: 13. Наш результат соседи содержит индексы 13 ближайших соседей. Остается только последний шаг — проверить метки этих соседей и сделать прогноз.

def predict(y, neighbors):
    labels = y[neighbors] ## Slice for neighbors
    ## Create a Dict of each label and calculate proportions
    probs = {label:(labels==label).sum()/labels.shape[0] for label in np.unique(y)} 
    return probs

pred = predict(y, neighbors)
## -> {0: 0, 1: 0.6154, 2: 0.3846}

Мы видим, что 61,54% соседей помечены как «1», а остальные 38,46% помечены как «2», поэтому мы классифицируем наши «новые данные» как «1». Ниже вы можете увидеть весь программный код вместе:

from sklearn import datasets
import numpy as np

def getDistances(X, newData, p=2):
    distances = []
    for x in X:
        ## Minkowsky method with variable parameter (defaulting to p=2 -> Eucledian)
        distance = sum(abs(x-newData)**p)**(1/p)
        distances.append(distance)
    return np.array(distances) ## Converting to numpy array

def getNeighbors(distances, k):
    return np.argsort(distances)[:k]

def predict(y, neighbors):
    labels = y[neighbors] ## Slice for neighbors
    ## Create a Dict of each label and calculate proportions
    probs = {label:(labels==label).sum()/labels.shape[0] for label in np.unique(y)} 
    return probs


### Load Data
iris = datasets.load_iris()
sepalLength = iris.data[:,0]
sepalWidth = iris.data[:,1]
petalLength = iris.data[:,2]
petalWidth = iris.data[:,3]
## Reducing 4 dimensional data to 2: x = width*height
## Also keeping in a (n, 2) shape of numpy array
X = np.hstack([(petalLength*petalWidth).reshape(iris.data.shape[0],1), \
               (sepalLength*sepalWidth).reshape(iris.data.shape[0],1)])
y = iris.target # Labels

newData = np.array([8,19]).reshape(1,2)
distances = getDistances(X, newData[0])
print(X.shape[0]**0.5) ## -> 12.2475
neighbors = getNeighbors(distances, 13)
pred = predict(y, neighbors)
## -> {0: 0, 1: 0.6153, 2: 0.3846}

В поисках лучшего К

В качестве дополнительной темы я расскажу о методе поиска оптимизированного K для нашего набора данных. Идея, лежащая в основе этого, состоит в том, чтобы итеративно выбирать каждую из 150 точек данных в качестве наших «новых данных» (таким образом, мы будем знать их истинную метку) и прогнозировать их на основе разных значений «К». После этого мы можем измерить точность модели в каждом сценарии.

k_accuracy = {}
for K in range(1,100): ## Iterating through K = 1 to 100
    accuracy = 0
    for i, newData in enumerate(X): ## Iterating through each datapoint
        tempX = np.delete(X, i, 0) ## Remove our "newData" from the dataset
        distances = getDistances(tempX, newData)
        neighbors = getNeighbors(distances, K)
        probs = predict(y, neighbors)
        pred = max(probs, key=probs.get) ## Get the label with max probability
        accuracy += pred == y[i] ## Increment accuracy if managed to predict successfully
    k_accuracy[K] = accuracy / tempX.shape[0]
plt.scatter(x=k_accuracy.keys(), y=k_accuracy.values())
best_K = max(k_accuracy, key=k_accuracy.get) ## Get Best K
print(best_K, k_accuracy[best_K])
## -> 9, 0.9732

Наилучший (но не единственный) K оказался равным 9 с точностью 97,32%. График хорошо показывает, что слишком большое увеличение K может снизить точность. Кроме того, K = 12 и 13 также имеют точность 97,32%, поэтому кажется, что использование эмпирического правила (квадратный корень из n) является эффективным методом.