私はここに同様の問題を議論するスレッドがあることを理解しています:
最も近い一致であるベクトルのベクトルのセットを効率的に検索する方法
しかし、私の問題は少し異なります-うまくいけばもっと簡単です。
同じ次元の一連のベクトルが与えられた場合、たとえば、
[2、3、5、9]
[12、9、2、8]
[45、1、0、1]
また、クエリベクトル[1、4、7、2]は、[2、3、5、9]を要素ごとの差の合計が最も小さいため、最も一致するベクトルとして[2、3、5、9]を返すアルゴリズム、つまり(1- 2)+(4-3)+(7-5)+(2-9)= -5
1つの方法は、ペアごとの「類似性」を事前に計算することですが、5000を超えるベクトルがあるため、250,000の比較になるため、計算は非常に重くなります。
あなたはあなたを再配置することができます:
(1-2) + (4-3) + (7-5) + (2-9)
...になる:
(1 + 4 + 7 + 2) - (2 + 3 + 5 + 9)
...そして(モジュロ丸め誤差、オーバーフローなど)答えは同じままです。
つまり、各ベクトルの要素の合計を格納し、クエリを取得したら、そのベクトルの要素を合計してから、指定された要素の中で最も近い値を検索します。
これにより、ほとんどの計算を事前に合理的に行うことができます。
ただし、これらすべてのベクトルを検索するよりもさらに良い方法があります。たとえば、std::multimap
(またはstd::unordered_multimap
)を使用して、各合計に関連付けられたベクトルを使用するように指示します。 unordered_multimap
、ルックアップではほぼ一定の複雑さを期待できます。
ただし、コメントには、N個の最も類似したベクトルを見つけるための要件が追加されています。この場合、おそらくstd::multimap
順不同の代わりに。これにより、初期ルックアップは(予想される)一定の複雑さではなく対数になります-ただし、次の最も類似したアイテムの検索は、順序付けされているため、一定の複雑さで実行できます。
状況によっては、代わりに合計のソートされたベクトルを使用することも検討する価値があります。これは、データを連続して格納するため、キャッシュの使用を大幅に改善する傾向があります。トレードオフは、要素の挿入/削除をうまくサポートしないことです。多くの検索を実行し、これらの5000ベクトルをめったに変更しない(またはまったく変更しない)場合は、合計のソートされたベクトルがおそらく最適です。ただし、それらに多くの変更が加えられる場合(つまり、常に古いベクターを削除して新しいベクターを挿入する場合)、std::map
の方が良いかもしれません。
もう1つのポイント:multimap
は、バイナリ検索を使用して最も近いアイテムを見つけます。差異が合理的に均等に分布している場合は、代わりに補間検索を使用できます。そのためには、最小合計と最大合計の差を見つけることから始めます。たとえば、最小値が4で最大値が7912038であり、32750に最も近い合計を探しているとします。合計の差は7912034なので、32750/7912034 =約0.004を計算することから始めます。次に(正確に5000ベクトルであると仮定して)5000を掛けて約20にします。したがって、合計の配列の中央から開始する代わりに、20から開始できます。番目 配列内の項目(結果がどれだけ間違っているかに応じて、目的のポイントに到達するまで同じプロセスを繰り返すことができます)。
合計が合理的に均等に分散されていると仮定すると、これにより、バイナリ検索のO(log N)複雑度ではなく、約O(log log N)複雑度を持つ最も類似したアイテムを見つけることができます。もちろん、ここでは線形補間を使用しました。合計に(たとえば)対数または指数分布があることがわかっている場合は、対数または指数補間を使用できます。言い換えれば、合計が実際に機能するためにevenlyが実際に分配される必要はありません-それらは合理的に予測可能に分配されなければなりません。
log log N
はしばしば「疑似定数」と呼ばれます。調べられる項目の数とともに増加しますが、非常にゆっくりと増加するため、ほとんどの実用的な目的では、5000のような少数の項目では、定数として扱うことができます。それは1つです。アイテム数が多い場合は2に増えます。3に達する前に、地球全体で生成されたすべてのストレージに保存できるデータよりも多くのデータについて話していることになります。それが4になる前に、宇宙の原子の数よりも大きい数について話しています。
要素ごとの違いについては、ジェリーは優れたソリューションを提供しました。それが本当にあなたが望むことなら、これは行くのに最適な方法です。
ただし、要素ごとの違いとベクトル間の距離の間で混乱が生じている可能性があることを指摘しておきます。ベクトルの距離を計算する場合(=クエリベクトルに対して最も近いであるベクトルを検索する場合)、計算を再定義する必要があります。
次の2つのベクトルについて考えます。
クエリベクトルq [2 4]が与えられた場合、差の合計は同じですが、v1がv2より近いことは明らかです。
diff(v1, q) = (1-2) + (4-4) = -1+0 = -1
diff(v2, q) = (4-2) + (1-4) = 2-3 = -1
Diff関数の問題は、ベクトルの距離を知りたい場合は、負の差を正に処理する必要があることです。したがって、距離関数(=差の絶対値)を使用すると、方程式は次のようになります。
dist(v1, q) = abs(1-2) + abs(4-4) = 1+0 = 1
dist(v2, q) = abs(4-2) + abs(1-4) = 2+3 = 5
これで、v1がqに近いことがわかります。
繰り返しますが、これは最初の質問に対する回答ではない可能性があります-いくつかの追加:)
GPGPUを使用して、クエリベクトルと一連のベクトル(m)の間のマンハッタン距離を計算するための非正統的なアプローチを提案します。
ベクトルのセットがS(m、n)として定義されていると仮定しましょう
S (m,n) =
[s_1,1, s_1,2, .... s_1,n
s_2,1, s_1,2, .... s_2,n
.... ....
s_m,1, s_m,2, .... s_m,n]
最初に、N次元ベクトル(V1、.. Vn)を(V1、... Vn、1)としてN + 1次元ベクトルに変換する必要があります。これは、O(m)時間。
S'(m,n+1)=
[s_1,1, s_1,2, .... s_1,n ,1
s_2,1, s_1,2, .... s_2,n, 1
.... ....
s_m,1, s_m,2, .... s_m,n ,1]
次に、クエリベクトルQ '= [Q1、...、Qn、1]を使用してセットを変換できます。
TQ(n+1,n+1)=
[1, 0, .... 0, -Q1
0, 1, .... 0, -Q2
.... ....
0, 0, .... 1, -Qn
0, 0, .... 0, 1]
S''(m,n+1)= S'(m,n+1) x TQ(n+1,n+1);
マンハッタン距離は、次の行列乗算を使用して計算できます。
MD(n+1,1)=
[1,
1,
...
1,
0]
S'''(m,1) = S''(m,n+1) x MD(n+1,1)
すべての行列乗算は、CUDAを使用して処理を最適化することにより、 GPUでの行列計算 のセクション3.4で説明されているように、GPUで実行できます。
さて、あなたがしなければならない唯一のことは、O(m)時間で実行できるmin(S '' ')とそれに対応するベクトルを見つけることです。