Это увлекательная проблема! Две вещи делают это особенно сложным:
- Как мы должны сравнивать два набора точек? Классические проблемы в машинном обучении имеют фиксированное количество атрибутов, и эти атрибуты не являются взаимозаменяемыми: например, у меня могут быть данные о разных людях с атрибутами
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) улучшить то, как выбранные метки обучения превращаются в предсказание.