Questo è un problema affascinante! Due cose lo rendono particolarmente impegnativo:
- Come dovremmo confrontare due set di punti? I problemi classici nell'apprendimento automatico hanno un numero fisso di attributi e questi attributi non sono intercambiabili: ad esempio, potrei avere dati su diverse persone con attributi
age
e height
(in centimetri). Ogni campione ha una voce per ciascuno, e ovviamente (age, height) = (22, 180)
non è la stessa (age, height) = (180, 22)
. Né è vero nel tuo problema. Una serie di punti ha tra 3 e 10 punti e l'ordine in cui inseriamo i punti non dovrebbe fare la differenza quando si confrontano due serie di punti.
- Come facciamo una previsione? Supponiamo di aver trovato un modo per selezionare i set di punti dal nostro set di allenamento simili al set di punti sopra riportato. Affrontiamo il problema che la nostra previsione deve essere uno dei 7 punti nella tua foto; ma nessuno di questi punti potrebbe essere contenuto in insiemi di punti simili.
Vorrei delineare un algoritmo che affronti entrambe le sfide. L'accuratezza della previsione non è molto buona; ma forse vedi un modo per migliorarlo. E almeno prevede qualcosa , giusto?
1. Simulazione di campioni
Per poter testare l'algoritmo, ho scritto funzioni che generano campioni ed etichette.
Generazione di campioni:
ogni campione contiene tra 3 e 10 punti. Il numero di punti è casuale, tratto da una distribuzione uniforme. Ogni punto è della forma (x_coordinate, y_coordinate)
. Le coordinate sono di nuovo casuali, tratte da una distribuzione normale.
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)])
Generazione di etichette: come esempio di un giocattolo, supponiamo che la regola per scegliere un punto sia: scegli sempre il punto più vicino a (0, 0)
, dove "più vicino" dovrebbe essere compreso in termini di norma euclidea.
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])
Ora possiamo creare i nostri set di treni e test:
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. Confronto dei set di punti tramite la distanza di Hausdorff
Affrontiamo il primo problema: come dobbiamo confrontare diversi set di punti? Il numero di punti nelle serie di punti è diverso. Ricorda anche che l'ordine in cui annotiamo i punti non dovrebbe avere importanza: il confronto con il set di punti [(0,0), (1,1), (2,2)]
dovrebbe produrre lo stesso risultato del confronto con il set di punti [(2,2), (0,0), (1,1)]
. Il mio approccio è quello di confrontare i set di punti tramite la loro distanza Hausdorff :
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. Previsione tramite k-vicini più vicini e media
Ora abbiamo una nozione di distanza tra set di punti. Ciò consente di utilizzare la classificazione k-vicini più vicini: dato un set di punti di test, troviamo i k
set di punti nel nostro campione di addestramento che hanno la distanza più piccola di Hausdorff rispetto al set di punti di test e ottengono le loro etichette. Ora arriva il secondo problema: come trasformiamo queste k
etichette in una previsione per il set di punti test? Ho adottato l'approccio più semplice: fare la media delle etichette e prevedere il punto nel set di punti di test più vicino alla media.
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. Test
Tutto è a posto per testare le prestazioni del nostro algoritmo.
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])
Per la data funzione decisionale e num_neighbors = 70
otteniamo una precisione di previsione dell'84%. Questo non è terribilmente buono, ed è ovviamente specifico per la nostra funzione decisionale, che sembra abbastanza facile da prevedere.
Per vedere questo, definire una diversa funzione decisionale:
decision_function_maxaverage(sample):
avgs = (sample[:, 0] + sample[:, 1]) / 2
return sample[norms.argmin()]
L'uso di questa funzione dec_fun = decision_function_maxaverage
riduce la precisione delle previsioni al 45%. Questo dimostra quanto sia importante pensare alle regole di decisione che generano le tue etichette. Se hai un'idea del perché le persone scelgono determinati punti, questo ti aiuterà a trovare l'algoritmo migliore.
Alcuni modi per migliorare questo algoritmo: (1) Usa una funzione di distanza diversa invece della distanza di Hausdorff, (2) usa qualcosa di più sofisticato dei vicini k-più vicini, (3) migliora il modo in cui le etichette di allenamento selezionate vengono trasformate in una previsione.