Це захоплююча проблема! Дві речі роблять це особливо складним:
- Як нам порівнювати два точкові множини? Класичні проблеми машинного навчання мають фіксовану кількість атрибутів, і ці атрибути не є взаємозамінними: Наприклад, у мене можуть бути дані про різних осіб з атрибутами
age
та height
(у сантиметрах). Кожен зразок має один запис для кожного, і, звичайно (age, height) = (22, 180)
, не такий, як (age, height) = (180, 22)
. Це не відповідає дійсності вашої проблеми. Набір точок має від 3 до 10 балів, і порядок, в який ми вводимо точки, не повинен змінювати при порівнянні двох наборів точок.
- Як ми можемо зробити прогноз? Скажіть, що ми знайшли спосіб підібрати набір точок з нашого навчального набору, подібний до вашого набору вище. Ми стикаємося з проблемою, що наше передбачення має бути однією з 7 точок на вашій картині; але жодна з цих точок не може міститись у подібних наборах точок.
Дозвольте мені окреслити алгоритм, який вирішує обидва завдання. Точність прогнозування не дуже хороша; але, можливо, ви бачите спосіб, як це можна вдосконалити. І принаймні щось пророкує , правда?
1. Моделювання зразків
Щоб мати можливість перевірити алгоритм, я написав функції, які генерують зразки та мітки.
Генерування зразків:
кожен зразок містить від 3 до 10 балів. Кількість балів випадкова, виведена з рівномірного розподілу. Кожна точка має форму (x_coordinate, y_coordinate)
. Координати знову випадкові, виведені з нормального розподілу.
import numpy as np
from random import randint
def create_samples(number_samples, min_points, max_points):
def create_single_sample(min_points, max_points):
n = randint(min_points, max_points)
return np.array([np.random.normal(size=2) for _ in range(n)])
return np.array([create_single_sample(min_points, max_points) for _ in range(number_samples)])
Генерування етикеток. Як приклад іграшки, припустимо, що правило вибору точки: Завжди вибирайте точку, яка є найближчою до того (0, 0)
, де «найближчі» слід розуміти з точки зору евклідової норми.
def decision_function_minnorm(sample):
norms = np.apply_along_axis(np.linalg.norm, axis=1, arr=sample)
return sample[norms.argmin()]
def create_labels(samples, decision_function):
return np.array([decision_function(sample) for sample in samples])
Тепер ми можемо створити набір поїздів і тестів:
n_train, n_test = 1000, 100
dec_fun = decision_function_minnorm
X_train = create_samples(number_samples=n_train, min_points=3, max_points=10)
X_test = create_samples(number_samples=n_test, min_points=3, max_points=10)
y_train = create_labels(X_train, dec_fun)
y_test = create_labels(X_test, dec_fun)
2. Порівняння наборів точок через відстань Хаусдорфа
Давайте вирішимо першу проблему: як нам порівнювати різні набори точок? Кількість балів у наборах точок різна. Також пам’ятайте, що порядок, в який ми записуємо бали, не повинен мати значення: Порівняння з набором точок [(0,0), (1,1), (2,2)]
повинно дати такий же результат, як і порівняння з набором точок [(2,2), (0,0), (1,1)]
. Мій підхід полягає в порівнянні наборів точок через відстань Хаусдорфа :
def hausdorff(A, B):
def dist_point_to_set(x, A):
return min(np.linalg.norm(x - a) for a in A)
def dist_set_to_set(A, B):
return max(dist_point_set(a, B) for a in A)
return max(dist_set_to_set(A, B), dist_set_to_set(B, A))
3. Прогнозування через k-найближчих сусідів та усереднення
Тепер у нас є поняття відстані між множинами точок. Це дає можливість використовувати k-класифікацію найближчих сусідів: Враховуючи набір тестових точок, k
у нашому навчальному зразку ми знаходимо набори точок, які мають найменшу відстань Хаусдорфа відносно набору тестових точок, та отримуємо їх мітки. Тепер виникає друга проблема: як ми можемо перетворити ці k
мітки в передбачення для встановленої точки тесту? Я взяв найпростіший підхід: середній рівень міток і прогнозування точки в наборі тестових точок, найбільш близької до середньої.
def predict(x, num_neighbors):
# Find num_neighbors closest points in X_train.
distances_to_train = np.array([hausdorff(x, x_train) for x_train in X_train])
neighbors_idx = np.argpartition(distances_to_train, -num_neighbors)[-num_neighbors:]
# Get labels of the neighbors and calculate the average.
targets_neighbors = y_train[neighbors_idx]
targets_mean = sum(targets_neighbors) / num_neighbors
# Find point in x that is closest to targets_mean and use it as prediction.
distances_to_mean = np.array([np.linalg.norm(p - targets_mean) for p in x])
closest_point = x[distances_to_mean.argmin()]
return closest_point
4. Тестування
Все на місці, щоб перевірити працездатність нашого алгоритму.
num_neighbors = 70
successes = 0
for i, x in enumerate(X_test):
print('%d/%d' % (i+1, n_test))
prediction = predict(x, num_neighbors)
successes += np.array_equal(prediction, y_test[i])
Для даної функції рішення і num_neighbors = 70
ми отримуємо точність прогнозування 84%. Це не дуже добре, і це, звичайно, специфічно для нашої функції прийняття рішень, яку здається передбачити досить просто.
Щоб побачити це, визначте іншу функцію рішення:
decision_function_maxaverage(sample):
avgs = (sample[:, 0] + sample[:, 1]) / 2
return sample[norms.argmin()]
Використання цієї функції за допомогою dec_fun = decision_function_maxaverage
знижує точність прогнозування до 45%. Це показує, як важливо продумати правила прийняття рішень, які генерують ваші мітки. Якщо у вас є ідея, чому люди обирають певні моменти, це допоможе вам знайти найкращий алгоритм.
Деякі способи вдосконалення цього алгоритму: (1) Використовувати іншу функцію дистанції замість відстані Хаусдорфа, (2) використовувати щось більш досконале, ніж k-найближчі сусіди, (3) покращити, як обрані навчальні мітки перетворюються на прогнозування.