web-dev-qa-db-ja.com

K最近傍の発見とその実装

ユークリッド距離のKNNを使用して単純なデータを分類する作業をしています。以下に示すように、MATLAB knnsearch関数を使用して実行したいことの例を確認しました。

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)

上記のコードは新しい点、つまり[5 1.45]そして、新しいポイントに最も近い10個の値を見つけます。だれでも、knnsearch関数の機能の詳細な説明を含むMATLABアルゴリズムを教えてもらえますか?これを行う他の方法はありますか?

17

K最近傍(KNN)アルゴリズムの基礎は、N行とM列で構成されるデータマトリックスがあることです。ここで、Nはデータポイントの数であり、Mは各データポイントの次元です。たとえば、データマトリックス内にデカルト座標を配置した場合、これは通常_N x 2_または_N x 3_マトリックスです。このデータマトリックスを使用してクエリポイントを指定し、このデータマトリックス内でこのクエリポイントに最も近いkポイントを検索します。

通常、クエリとデータマトリックス内の残りのポイントとの間のユークリッド距離を使用して、距離を計算します。ただし、L1やシティブロック/マンハッタン距離などの他の距離も使用されます。この操作の後、クエリとデータセット内の対応する各ポイントとの間の距離を表すNユークリッド距離またはマンハッタン距離が得られます。これらを見つけたら、距離を昇順に並べ替えて、データセットとクエリの間の距離が最小のkポイントを取得することにより、クエリに最も近いkポイントを検索するだけです。

データマトリックスがxに格納されており、newpointM列を持つサンプルポイントである場合(つまり、_1 x M_)は、ポイント形式で従う一般的な手順です。

  1. newpointxのすべての点の間のユークリッド距離またはマンハッタン距離を求めます。
  2. これらの距離を昇順で並べ替えます。
  3. kに最も近いx内のnewpointデータポイントを返します。

各ステップをゆっくり実行しましょう。


ステップ1

誰かがこれを行う方法の1つは、おそらく次のようなforループにあります。

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

マンハッタン距離を実装したい場合、これは単に次のようになります:

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

distsは、Nxの各データポイント間の距離を含むnewpoint要素ベクトルです。 newpointxのデータポイントの間で要素ごとの減算を行い、差を二乗してから、 sum それらをすべて一緒にします。この合計は平方根となり、ユークリッド距離が完成します。マンハッタン距離の場合、要素ごとに減算を実行し、絶対値を取得してから、すべてのコンポーネントを合計します。これはおそらく、理解する実装の中で最も単純ですが、おそらく最も効率が悪い可能性があります...特に、サイズの大きいデータセットとデータの次元が大きい場合。

別の可能な解決策は、newpointを複製し、この行列をxと同じサイズにしてから、この行列の要素ごとの減算を行い、各行のすべての列を合計して平方根を求めることです。したがって、次のようなことができます。

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

マンハッタンの距離については、次のようにします。

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

repmat は、行列またはベクトルを受け取り、それらを特定の方向に特定の回数繰り返します。私たちの場合、newpointベクトルを取得し、このNを積み重ねて_N x M_行列を作成します。ここで、各行はM要素の長さです。これら2つの行列を一緒に減算してから、各成分を二乗します。これを実行したら、各行のすべての列に対してsumを実行し、最後にすべての結果の平方根を取得します。マンハッタン距離の場合は、減算を実行し、絶対値を取得してから合計します。

しかし、私の意見ではこれを行う最も効率的な方法は bsxfun を使用することです。これは基本的に、内部で単一の関数呼び出しを使用して説明したレプリケーションを実行します。したがって、コードは次のようになります。

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

私にとっては、これははるかにすっきりとしていて、要点に見えます。マンハッタンの距離については、次のようにします。

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

ステップ2

これで距離がわかったので、並べ替えるだけです。 sort を使用して、距離を並べ替えることができます。

_[d,ind] = sort(dists);
_

dには、昇順で並べ替えられた距離が含まれますが、indは、unsorted配列の各値について、sorted結果。 indを使用し、このベクトルの最初のk要素を抽出してから、indを使用してxデータマトリックスにインデックスを付け、newpointに最も近いポイントを返す必要があります。

ステップ#3

最後のステップは、kに最も近いnewpointデータポイントを返すことです。これは、次のように非常に簡単に行うことができます。

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

_ind_closest_には、xに最も近い、元のデータマトリックスnewpointのインデックスを含める必要があります。具体的には、_ind_closest_には、xに最も近いポイントを取得するためにnewpointでサンプリングする必要があるが含まれています。 _x_closest_には、実際のデータポイントが含まれます。


コピーと貼り付けを行うために、コードは次のようになります。

_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,:);
_

例を実行して、実際のコードを見てみましょう。

_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,:);
_

_ind_closest_と_x_closest_を検査すると、次のようになります。

_>> 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
_

knnsearchを実行した場合、変数nが_ind_closest_と一致することがわかります。ただし、変数dは、newpointから各ポイントxまでのdistancesを返します。実際のデータポイント自体ではありません。実際の距離が必要な場合は、私が書いたコードの後に​​次のことを行うだけです。

_dist_sorted = d(1:k);
_

上記の回答では、Nの例のバッチで1つのクエリポイントのみを使用しています。非常に頻繁にKNNは複数の例で同時に使用されます。 KNNでテストするQクエリポイントがあるとします。これにより、_k x M x Q_マトリックスが生成され、各例または各スライスについて、次元がkMの最も近い点が返されます。または、kの最も近いポイントのIDsを返すことで、_Q x k_マトリックスを生成できます。両方を計算してみましょう。

これを行う素朴な方法は、上記のコードをループに適用し、すべての例にループすることです。

このようなものは、_Q x k_マトリックスを割り当て、bsxfunベースのアプローチを適用して、出力マトリックスの各行をデータセット内のkの最も近いポイントに設定する場合に機能します。ここでは、Fisher Irisデータセットを使用します。以前あった。また、前の例と同じ次元を維持し、4つの例を使用するので、_Q = 4_および_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
_

これはとてもいいことですが、もっとうまくやることができます。 2つのベクトルセット間の2乗ユークリッド距離を効率的に計算する方法があります。マンハッタンでこれを実行したい場合は、演習として残します。コンサルティング このブログ 、ただし、Aは_Q1 x M_マトリックスであり、各行は次元のポイントMと_Q1_ポイントであり、Bは_Q2 x M_マトリックスであり、行は_Q2_ポイントを持つ次元Mでもあり、距離行列D(i, j)を効率的に計算できます。ここで、行iおよび列jの要素は、iの行Aと行jの間の距離を示します次の行列式を使用したBの例:

_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
_

したがって、Aをクエリポイントの行列、Bを元のデータで構成されるデータセットとすると、各行を個別に並べ替え、最小であった各行のkの場所を特定することで、kの最も近いポイントを特定できます。これをさらに使用して、実際のポイント自体を取得することもできます。

したがって:

_%// 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]);
_

距離行列の計算に使用したロジックは同じですが、一部の変数は例に合わせて変更されています。また、sortの2つの入力バージョンを使用して各行を個別にソートするため、indには行ごとのIDが含まれ、dには対応する距離が含まれます。次に、このマトリックスをk列に切り捨てるだけで、各クエリポイントに最も近いインデックスを特定します。次に permutereshape を使用して、関連付けられている最も近い点を特定します。最初に最も近いインデックスをすべて使用し、すべてのIDを積み重ねるポイントマトリックスを作成して、_Q * k x M_マトリックスを取得します。 reshapepermuteを使用すると、3Dマトリックスを作成して、指定したような_k x M x Q_マトリックスになるようにすることができます。実際の距離を取得したい場合は、dにインデックスを付けて、必要なものを取得できます。これを行うには、線形インデックスを取得するために _sub2ind_ を使用して、dに一度にインデックスを付けることができるようにする必要があります。 _ind_closest_の値は、アクセスする必要のある列をすでに提供しています。アクセスする必要がある行は、単純に1、k回、2、k回など、Qまでです。 kは、返したいポイントの数です。

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

上記のクエリポイントに対して上記のコードを実行すると、これらは取得するインデックス、ポイント、距離です。

_>> 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
_

これをknnsearchと比較するには、代わりに、各行がクエリポイントである2番目のパラメーターにポイントの行列を指定します。この実装とknnsearchの間でインデックスと並べ替えられた距離が一致することがわかります。


これがお役に立てば幸いです。幸運を!