web-dev-qa-db-fra.com

Trouver les voisins K les plus proches et sa mise en œuvre

Je travaille sur la classification de données simples en utilisant KNN avec une distance euclidienne. J'ai vu un exemple sur ce que j'aimerais faire qui se fait avec la fonction MATLAB knnsearch comme indiqué ci-dessous:

load fisheriris 
x = meas(:,3:4);
gscatter(x(:,1),x(:,2),species)
newpoint = [5 1.45];
[n,d] = knnsearch(x,newpoint,'k',10);
line(x(n,1),x(n,2),'color',[.5 .5 .5],'marker','o','linestyle','none','markersize',10)

Le code ci-dessus prend un nouveau point, c'est-à-dire [5 1.45] et trouve les 10 valeurs les plus proches du nouveau point. Quelqu'un peut-il me montrer un algorithme MATLAB avec une explication détaillée de ce que fait la fonction knnsearch? Y a-t-un autre moyen de faire ça?

17
Young_DataAnalyst

La base de l'algorithme K-Nearest Neighbour (KNN) est que vous avez une matrice de données qui se compose de N lignes et M colonnes où N est le nombre de points de données que nous avons, tandis que M est la dimensionnalité de chaque point de données. Par exemple, si nous plaçons des coordonnées cartésiennes à l'intérieur d'une matrice de données, il s'agit généralement d'une matrice N x 2 Ou N x 3. Avec cette matrice de données, vous fournissez un point de requête et vous recherchez les points k les plus proches dans cette matrice de données qui sont les plus proches de ce point de requête.

Nous utilisons généralement la distance euclidienne entre la requête et le reste de vos points dans votre matrice de données pour calculer nos distances. Cependant, d'autres distances comme la L1 ou la distance City-Block/Manhattan sont également utilisées. Après cette opération, vous aurez N les distances euclidiennes ou Manhattan qui symbolisent les distances entre la requête avec chaque point correspondant dans l'ensemble de données. Une fois que vous les avez trouvées, vous recherchez simplement les k points les plus proches de la requête en triant les distances dans l'ordre croissant et en récupérant les k points qui ont la plus petite distance entre votre ensemble de données et la requête .

En supposant que votre matrice de données a été stockée dans x et que newpoint est un exemple de point où elle a des colonnes M (c'est-à-dire 1 x M), Voici la procédure générale vous suivriez sous forme de points:

  1. Trouvez la distance euclidienne ou Manhattan entre newpoint et chaque point dans x.
  2. Triez ces distances par ordre croissant.
  3. Renvoyez les points de données k dans x les plus proches de newpoint.

Faisons chaque étape lentement.


Étape 1

Une façon pour quelqu'un de faire cela est peut-être dans une boucle for comme ceci:

N = size(x,1);
dists = zeros(N,1);
for idx = 1 : N
    dists(idx) = sqrt(sum((x(idx,:) - newpoint).^2));
end

Si vous vouliez implémenter la distance de Manhattan, ce serait simplement:

N = size(x,1);
dists = zeros(N,1);
for idx = 1 : N
    dists(idx) = sum(abs(x(idx,:) - newpoint));
end

dists serait un vecteur d'élément N qui contient les distances entre chaque point de données dans x et newpoint. Nous faisons une soustraction élément par élément entre newpoint et un point de données dans x, quadrillons les différences, puis sum tous ensemble. Cette somme est alors de racine carrée, ce qui complète la distance euclidienne. Pour la distance de Manhattan, vous devez effectuer une soustraction élément par élément, prendre les valeurs absolues, puis additionner tous les composants ensemble. C'est probablement la plus simple des implémentations à comprendre, mais elle pourrait être la plus inefficace ... en particulier pour les ensembles de données de plus grande taille et la plus grande dimensionnalité de vos données.

Une autre solution possible serait de répliquer newpoint et de rendre cette matrice de la même taille que x, puis de faire une soustraction élément par élément de cette matrice, puis de sommer toutes les colonnes pour chacune ligne et faire la racine carrée. Par conséquent, nous pouvons faire quelque chose comme ceci:

N = size(x, 1);
dists = sqrt(sum((x - repmat(newpoint, N, 1)).^2, 2));

Pour la distance de Manhattan, vous feriez:

N = size(x, 1);
dists = sum(abs(x - repmat(newpoint, N, 1)), 2);

repmat prend une matrice ou un vecteur et les répète un certain nombre de fois dans une direction donnée. Dans notre cas, nous voulons prendre notre vecteur newpoint et empiler ces N fois les uns sur les autres pour créer une matrice N x M, Où chaque ligne est M longs. Nous soustrayons ces deux matrices ensemble, puis quadrillons chaque composant. Une fois que nous faisons cela, nous sum sur toutes les colonnes de chaque ligne et prenons enfin la racine carrée de tous les résultats. Pour la distance de Manhattan, nous faisons la soustraction, prenons la valeur absolue puis additionnons.

Cependant, la façon la plus efficace de le faire à mon avis serait d'utiliser bsxfun . Cela fait essentiellement la réplication dont nous avons parlé sous le capot avec un seul appel de fonction. Par conséquent, le code serait simplement le suivant:

dists = sqrt(sum(bsxfun(@minus, x, newpoint).^2, 2));

Pour moi, cela semble beaucoup plus propre et au point. Pour la distance de Manhattan, vous feriez:

dists = sum(abs(bsxfun(@minus, x, newpoint)), 2);

Étape 2

Maintenant que nous avons nos distances, nous les trions simplement. Nous pouvons utiliser sort pour trier nos distances:

[d,ind] = sort(dists);

d contiendrait les distances triées par ordre croissant, tandis que ind vous indiquerait pour chaque valeur du tableau non trié où il apparaît dans le résultat trié . Nous devons utiliser ind, extraire les premiers k éléments de ce vecteur, puis utiliser ind pour indexer dans notre matrice de données x pour renvoyer les points qui étaient les plus proches de newpoint.

Étape 3

La dernière étape consiste à renvoyer les points de données k les plus proches de newpoint. Nous pouvons le faire très simplement en:

ind_closest = ind(1:k);
x_closest = x(ind_closest,:);

ind_closest Doit contenir dans la matrice de données d'origine x les indices les plus proches de newpoint. Plus précisément, ind_closest Contient les lignes à partir desquelles vous devez échantillonner dans x pour obtenir les points les plus proches de newpoint. x_closest Contiendra ces points de données réels.


Pour votre plaisir à copier et coller, voici à quoi ressemble le code:

dists = sqrt(sum(bsxfun(@minus, x, newpoint).^2, 2));
%// Or do this for Manhattan
% dists = sum(abs(bsxfun(@minus, x, newpoint)), 2);
[d,ind] = sort(dists);
ind_closest = ind(1:k);
x_closest = x(ind_closest,:);

En parcourant votre exemple, voyons notre code en action:

load fisheriris 
x = meas(:,3:4);
newpoint = [5 1.45];
k = 10;

%// Use Euclidean
dists = sqrt(sum(bsxfun(@minus, x, newpoint).^2, 2));
[d,ind] = sort(dists);
ind_closest = ind(1:k);
x_closest = x(ind_closest,:);

En inspectant ind_closest Et x_closest, Voici ce que nous obtenons:

>> ind_closest

ind_closest =

   120
    53
    73
   134
    84
    77
    78
    51
    64
    87

>> x_closest

x_closest =

    5.0000    1.5000
    4.9000    1.5000
    4.9000    1.5000
    5.1000    1.5000
    5.1000    1.6000
    4.8000    1.4000
    5.0000    1.7000
    4.7000    1.4000
    4.7000    1.4000
    4.7000    1.5000

Si vous avez exécuté knnsearch, vous verrez que votre variable n correspond à ind_closest. Cependant, la variable d renvoie les distances de newpoint à chaque point x, pas le les données réelles se pointent elles-mêmes. Si vous voulez les distances réelles, faites simplement ce qui suit après le code que j'ai écrit:

dist_sorted = d(1:k);

Notez que la réponse ci-dessus utilise un seul point de requête dans un lot d'exemples N. Très fréquemment, KNN est utilisé simultanément sur plusieurs exemples. Supposons que nous ayons Q points de requête que nous voulons tester dans le KNN. Il en résulterait une matrice k x M x Q Où, pour chaque exemple ou chaque tranche, nous renvoyons les k points les plus proches avec une dimensionnalité de M. Alternativement, nous pouvons renvoyer les ID des points les plus proches k résultant ainsi en une matrice Q x k. Calculons les deux.

Une façon naïve de le faire serait d'appliquer le code ci-dessus en boucle et en boucle sur chaque exemple.

Quelque chose comme cela fonctionnerait où nous allouons une matrice Q x k Et appliquons l'approche basée sur bsxfun pour définir chaque ligne de la matrice de sortie sur les points k les plus proches de l'ensemble de données, où nous utiliserons le jeu de données Fisher Iris comme ce que nous avions auparavant. Nous conserverons également la même dimensionnalité que dans l'exemple précédent et j'utiliserai quatre exemples, donc Q = 4 Et M = 2:

%// Load the data and create the query points
load fisheriris;
x = meas(:,3:4);
newpoints = [5 1.45; 7 2; 4 2.5; 2 3.5];

%// Define k and the output matrices
Q = size(newpoints, 1);
M = size(x, 2);
k = 10;
x_closest = zeros(k, M, Q);
ind_closest = zeros(Q, k);

%// Loop through each point and do logic as seen above:
for ii = 1 : Q
    %// Get the point
    newpoint = newpoints(ii, :);

    %// Use Euclidean
    dists = sqrt(sum(bsxfun(@minus, x, newpoint).^2, 2));
    [d,ind] = sort(dists);

    %// New - Output the IDs of the match as well as the points themselves
    ind_closest(ii, :) = ind(1 : k).';
    x_closest(:, :, ii) = x(ind_closest(ii, :), :);
end

Bien que ce soit très agréable, nous pouvons faire encore mieux. Il existe un moyen de calculer efficacement la distance euclidienne au carré entre deux ensembles de vecteurs. Je vais le laisser comme un exercice si vous voulez le faire avec le Manhattan. Consulting ce blog , étant donné que A est une matrice Q1 x M Où chaque ligne est un point de dimensionnalité M avec Q1 Points et B est une matrice Q2 x M où chaque ligne est également un point de dimensionnalité M avec Q2 points, nous pouvons calculer efficacement une matrice de distance D(i, j) où l'élément de la ligne i et de la colonne j indique la distance entre la ligne i de A et la ligne j de B en utilisant la formulation de matrice suivante:

nA = sum(A.^2, 2); %// Sum of squares for each row of A
nB = sum(B.^2, 2); %// Sum of squares for each row of B
D = bsxfun(@plus, nA, nB.') - 2*A*B.'; %// Compute distance matrix
D = sqrt(D); %// Compute square root to complete calculation

Par conséquent, si nous laissons A une matrice de points de requête et B l'ensemble de données composé de vos données d'origine, nous pouvons déterminer les k points les plus proches en triant chaque ligne individuellement et déterminer les emplacements k de chaque ligne qui étaient les plus petits. Nous pouvons également l'utiliser pour récupérer les points réels eux-mêmes.

Par conséquent:

%// Load the data and create the query points
load fisheriris;
x = meas(:,3:4);
newpoints = [5 1.45; 7 2; 4 2.5; 2 3.5];

%// Define k and other variables
k = 10;
Q = size(newpoints, 1);
M = size(x, 2);

nA = sum(newpoints.^2, 2); %// Sum of squares for each row of A
nB = sum(x.^2, 2); %// Sum of squares for each row of B
D = bsxfun(@plus, nA, nB.') - 2*newpoints*x.'; %// Compute distance matrix
D = sqrt(D); %// Compute square root to complete calculation 

%// Sort the distances 
[d, ind] = sort(D, 2);

%// Get the indices of the closest distances
ind_closest = ind(:, 1:k);

%// Also get the nearest points
x_closest = permute(reshape(x(ind_closest(:), :).', M, k, []), [2 1 3]);

Nous voyons que nous avons utilisé la logique pour calculer la matrice de distance est la même, mais certaines variables ont changé pour convenir à l'exemple. Nous trions également chaque ligne indépendamment en utilisant les deux versions d'entrée de sort et ainsi ind contiendra les ID par ligne et d contiendra les distances correspondantes. Nous déterminons ensuite quels indices sont les plus proches de chaque point de requête en tronquant simplement cette matrice en colonnes k. Nous utilisons ensuite permute et reshape pour déterminer quels sont les points les plus proches associés. Nous utilisons d'abord tous les indices les plus proches et créons une matrice de points qui empile tous les ID les uns sur les autres pour obtenir une matrice Q * k x M. L'utilisation de reshape et permute nous permet de créer notre matrice 3D afin qu'elle devienne une matrice k x M x Q Comme nous l'avons spécifié. Si vous vouliez obtenir les distances réelles elles-mêmes, nous pouvons indexer dans d et récupérer ce dont nous avons besoin. Pour ce faire, vous devrez utiliser sub2ind pour obtenir les indices linéaires afin que nous puissions indexer dans d en une seule fois. Les valeurs de ind_closest Nous donnent déjà les colonnes auxquelles nous devons accéder. Les lignes auxquelles nous devons accéder sont simplement 1, k fois, 2, k fois, etc. jusqu'à Q. k est pour le nombre de points que nous voulions retourner:

row_indices = repmat((1:Q).', 1, k);
linear_ind = sub2ind(size(d), row_indices, ind_closest);
dist_sorted = D(linear_ind);

Lorsque nous exécutons le code ci-dessus pour les points de requête ci-dessus, ce sont les indices, les points et les distances que nous obtenons:

>> ind_closest

ind_closest =

   120   134    53    73    84    77    78    51    64    87
   123   119   118   106   132   108   131   136   126   110
   107    62    86   122    71   127   139   115    60    52
    99    65    58    94    60    61    80    44    54    72

>> x_closest

x_closest(:,:,1) =

    5.0000    1.5000
    6.7000    2.0000
    4.5000    1.7000
    3.0000    1.1000
    5.1000    1.5000
    6.9000    2.3000
    4.2000    1.5000
    3.6000    1.3000
    4.9000    1.5000
    6.7000    2.2000


x_closest(:,:,2) =

    4.5000    1.6000
    3.3000    1.0000
    4.9000    1.5000
    6.6000    2.1000
    4.9000    2.0000
    3.3000    1.0000
    5.1000    1.6000
    6.4000    2.0000
    4.8000    1.8000
    3.9000    1.4000


x_closest(:,:,3) =

    4.8000    1.4000
    6.3000    1.8000
    4.8000    1.8000
    3.5000    1.0000
    5.0000    1.7000
    6.1000    1.9000
    4.8000    1.8000
    3.5000    1.0000
    4.7000    1.4000
    6.1000    2.3000


x_closest(:,:,4) =

    5.1000    2.4000
    1.6000    0.6000
    4.7000    1.4000
    6.0000    1.8000
    3.9000    1.4000
    4.0000    1.3000
    4.7000    1.5000
    6.1000    2.5000
    4.5000    1.5000
    4.0000    1.3000

>> dist_sorted

dist_sorted =

    0.0500    0.1118    0.1118    0.1118    0.1803    0.2062    0.2500    0.3041    0.3041    0.3041
    0.3000    0.3162    0.3606    0.4123    0.6000    0.7280    0.9055    0.9487    1.0198    1.0296
    0.9434    1.0198    1.0296    1.0296    1.0630    1.0630    1.0630    1.1045    1.1045    1.1180
    2.6000    2.7203    2.8178    2.8178    2.8320    2.9155    2.9155    2.9275    2.9732    2.9732

Pour comparer cela avec knnsearch, vous devez plutôt spécifier une matrice de points pour le deuxième paramètre où chaque ligne est un point de requête et vous verrez que les indices et les distances triées correspondent entre cette implémentation et knnsearch.


J'espère que cela vous aidera. Bonne chance!