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?
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:
newpoint
et chaque point dans x
.k
dans x
les plus proches de newpoint
.Faisons chaque étape lentement.
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);
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
.
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!