最近傍怜玢方法


デヌタサむ゚ンスの分野のプログラマや専門家は、よく䌌たナヌザヌプロファむルを芋぀けるか、䌌たような音楜を遞択するずいう課題に盎面するこずがよくありたす。 ゜リュヌションは、オブゞェクトをベクトル圢匏に倉換し、最も近いものを芋぀けるこずに還元できたす。


たた、顔認識問題で最近傍を怜玢する必芁性に盎面したした。 そこで、ニュヌラルネットワヌクを䜿甚しお顔のベクトル衚珟を圢成し、すでに有名な人々の最も近いベクトルを探したす。 最初に、怜玢のために、Spotifyを含むよく知られた実瞟のあるアルゎリズムずしおAnnoyを遞択したした。 しかし、圌らはすぐに、圌の蚘憶欲求で、私たちはRAMに適合しなかったか、倚くの粟床を倱ったこずに気付きたした。 これは少し研究に぀ながりたした。 その結果を以䞋で説明したす。


理論を実践で薄めるために、この蚘事には、単語に最も近いものを探す小さなコヌドがありたす。 人気のあるword2vecを䜿甚しおベクタヌ衚珟を取埗したす。 このアルゎリズムは、意味的に類䌌した単語に察しお近いベクトルを生成したす。 word2vec CBOWでは、環境によっお単語を予枬する小さなニュヌラルネットワヌクの孊習の副産物ずしおベクトル衚珟が取埗されたす。 ベクトルを䜿甚するず、king +woman-man= queenのような算術挔算ができるのは興味深いです。


単語の埋め蟌み


コヌドでこれを操䜜する方法を芋おみたしょう。


model = gensim.models.KeyedVectors.load_word2vec_format('./GoogleNews-vectors-negative300.bin', binary=True) start = time.time() pprint.pprint(model.wv.most_similar(positive=['king'])) print('time:', time.time() - start) print('king + (woman - man) = ', model.wv.most_similar(positive=['woman', 'king'], negative=['man'])[0]) print('Japan + (Moscow - Russia) = ', model.wv.most_similar(positive=['Moscow', 'Japan'], negative=['Russia'])[0]) 

取埗するもの


  [(u'kings', 0.7138045430183411), (u'queen', 0.6510956883430481), (u'monarch', 0.6413194537162781), (u'crown_prince', 0.6204219460487366), (u'prince', 0.6159993410110474), (u'sultan', 0.5864823460578918), (u'ruler', 0.5797567367553711), (u'princes', 0.5646552443504333), (u'Prince_Paras', 0.5432944297790527), (u'throne', 0.5422105193138123)] time: 0.236690998077 king + (woman - man) = (u'queen', 0.7118192911148071) Japan + (Moscow - Russia) = (u'Tokyo', 0.8696038722991943) 

䞊蚘の䟋では、ラむブラリを䜿甚しおgensimテキストず、Googleのword2vec モデル 1.5 GBを凊理したした。このモデルには、300䞇の単語ず短いフレヌズがありたす。 コヌド出力は、女王、君䞻、王子が王に近いこずを瀺しおいたす。 たた、ベクトルを䜿甚した挔算が機胜するこずを確認したした。 ただし、リク゚ストごずに1/4秒はあたり魅力的ではなく、結局のずころ、gensimはブルヌトフォヌス怜玢の比范的良い実装を持っおいたすすべおの既知のオブゞェクトたでの距離の蚈算を䜿甚。 次に、粟床のわずかな損倱のみで、この時間を数癟分の1に短瞮できる方法を怜蚎したす。


しかし、単玔なアむデアから始めたしょう平面で2぀の半分に分割するこずにより、探玢空間を狭めるようにしおください。 たた、怜玢䞭に、ク゚リず同じ平面の同じ偎にいる隣人たでの距離のみを考慮したす。


 nbrs = NearestNeighbors(algorithm='brute', metric='cosine') nbrs.fit(model.wv.syn0norm) king_vec = model.wv['king'][np.newaxis, :] #              start = time.time() idxs = nbrs.kneighbors(king_vec, return_distance=False, n_neighbors=10)[0] print('full search time:', time.time() - start) print([model.wv.index2word[idx] for idx in idxs]) #  2          vec1_idx = random.randint(0, model.wv.syn0norm.shape[0]) vec2_idx = random.randint(0, model.wv.syn0norm.shape[0]) plane = model.wv.syn0norm[vec1_idx] - model.wv.syn0norm[vec2_idx] #     -,   . #             scalar = model.wv.syn0norm.dot(np.transpose(plane)) #                   if king_vec.dot(plane) > 0: mask = scalar > 0 else: mask = scalar < 0 print('elements in mask:', mask.sum()) half_nbrs = NearestNeighbors(algorithm='brute', metric='cosine') half_nbrs.fit(model.wv.syn0norm[mask]) half_index2word = list(compress(model.wv.index2word, mask)) start = time.time() idxs = half_nbrs.kneighbors(king_vec, return_distance=False, n_neighbors=10)[0] print('half search time:', time.time() - start) print([half_index2word[idx] for idx in idxs]) 

取埗するもの


  full search time: 20.3163180351 [u'king', u'kings', u'queen', u'monarch', u'crown_prince', u'prince', u'sultan', u'ruler', u'princes', u'Prince_Paras'] elements in mask: 1961204 half search time: 9.15824007988 [u'king', u'kings', u'queen', u'monarch', u'crown_prince', u'prince', u'sultan', u'ruler', u'princes', u'Prince_Paras'] 

そのため、蚈算を半分にしお、飛行機のすぐ隣で正確に倱いたした。 埌で説明するアルゎリズムの䞭には、同様のトリックを䜿甚するものがありたす。



1. HNSW


2011幎に、最高のナビゲヌション可胜なスモヌルワヌルドNSWの最近傍怜玢アルゎリズムの1぀が公開されたした。 2016幎、圌の階局的可航小䞖界HNSWの埌継者が登堎したした。


NSW芪アルゎリズムから始めたしょう。 「䞖界は狭い」ずいうコラムに基づいおいたす。 これらのグラフには奇劙で䟿利な機胜がありたす。頂点のペアはほずんど隣接しおいない可胜性が高いですが、比范的少数のステップで到達可胜です \ログN平均しお。 このようなグラフは非垞に䞀般的です。 たずえば、脳のニュヌラルネットワヌク、゜ヌシャルメディアグルヌプ、およびWordNetセマンティックネットワヌクはSWグラフです。 この堎合、頂点はベクトルであり、゚ッゞはそれらを最も近い頂点に接続したす。 グラフには、遠距離のピヌクを結ぶ゚ッゞも衚瀺されたす。


近傍を怜玢するには、ク゚リたでの最短距離で頂点を怜玢しおグラフを巡回したす。 ランダムな頂点から開始し、「友人」の未蚪問の頂点珟圚の゚ッゞに接続されおいる頂点からク゚リたでの距離を考慮し、最短距離の頂点に移動したす。 長い゚ッゞは、グラフにworld屈な䞖界の特性を䞎え、リク゚ストに近いオブゞェクトの領域にすばやく移動できるようにし、短い゚ッゞは最も近い隣人を熱心に怜玢したす。



グラフを巡回する際に、最近傍の小さなリストを曎新し、次の反埩でリストが曎新されおいない堎合は停止したす。


擬䌌コヌド
 K-NNSearch(object q, integer: m, k) 1 TreeSet [object] tempRes, candidates, visitedSet, result 2 for (i←0; i < m; i++) do: 3 put random entry point in candidates 4 tempRes←null 5 repeat: 6 get element c closest from candidates to q 7 remove c from candidates 8 //check stop condition: 9 if c is further than k-th element from result 10 than break repeat 11 //update list of candidates: 12 for every element e from friends of c do: 13 if e is not in visitedSet than 14 add e to visitedSet, candidates, tempRes 15 16 end repeat 17 //aggregate the results: 18 add objects from tempRes to result 19 end for 20 return best k elements from result 

 index = nmslib.init(space='cosinesimil', method='sw-graph') nmslib.addDataPointBatch(index, np.arange(model.wv.syn0.shape[0], dtype=np.int32), model.wv.syn0) index.createIndex({}, print_progress=True) start = time.time() items = nmslib.knnQuery(index, 10, king_vec.tolist()) print(time.time() - start) print([model.wv.index2word[idx] for idx in items]) 

取埗するもの


  0.000545024871826 [u'king', u'kings', u'queen', u'monarch', u'crown_prince', u'prince', u'sultan', u'ruler', u'princes', u'royal'] 

Hierarchical Navigable Small WorldHNSWアルゎリズムでの䞊蚘のアむデアの開発を怜蚎しおください。 これは倚くの点でNSWに非垞に䌌おいたすが、珟圚はグラフの階局を扱っおいたす。すべおのオブゞェクトはれロレむダヌで衚され、レむダヌが倧きくなったり小さくなったりするに぀れお倧きくなりたす。 この堎合、レむダヌ䞊のすべおのオブゞェクト n+1レむダヌ䞊にありたす n。



怜玢するずき、開始は䞊䜍局のグラフのランダムな頂点から発生し、そこで芁求に近い頂点候補をすばやく芋぀け、前の局でそれらから怜玢を再開したす。


擬䌌コヌド
 SearchAtLayer (object q, Set[object] enterPoints, integer: M, ef, layer) 1 Set [object] visitedSet 2 priority_queue [object] candidates (closer - first), result (further - first) 3 candidates, visitedSet, result ← enterPoints 7 4 repeat: 5 object c =candidates.top() 6 candidates.pop() 7 //check stop condition: 8 if d(c,q)>d(result.top(),q) do: 9 break 10 //update list of candidates: 11 for_each object e from c.friends(layer) do: 12 if e is not in visitedSet do: 13 add e to visitedSet 14 if d(e, q)< d(result.top(),q) or result.size()<ef do: 15 add e to candidates, result 16 if result.size()>ef do: 17 result.pop() 18 return best k elements from result K-NNSearch (object query, integer: ef) 1 Set [object] tempRes, enterPoints=[enterpoint] 2 for i=maxLayer downto 1 do: 3 tempRes=SearchAtLayer (query, enterPoints, M, 1, i) 4 enterPoints =closest elements from tempRes 5 tempRes=SearchAtLayer (query, enterPoints, M, ef, 0) 6 return best K of tempRes 

1.1。 長所ず短所


+アルゎリズムは理解しやすい


+最先端の結果を衚瀺したす


+ nmslibラむブラリに効果的な実装がありたす


+グラプッゞを保存するための远加メモリコストが少ない


-このアルゎリズムはベクトル衚珟の圧瞮をサポヌトしおいたせん。これに぀いおはさらに怜蚎したす


 index = nmslib.init(space='cosinesimil', method='hnsw') nmslib.addDataPointBatch(index, np.arange(model.wv.syn0.shape[0], dtype=np.int32), model.wv.syn0) index.createIndex({}, print_progress=True) start = time.time() items = nmslib.knnQuery(index, 10, king_vec.tolist()) print(time.time() - start) print([model.wv.index2word[idx] for idx in items]) 

取埗するもの


  0.000469923019409 [u'king', u'kings', u'queen', u'monarch', u'crown_prince', u'prince', u'sultan', u'ruler', u'princes', u'Prince_Paras'] 

2. FAISS


2017幎3月、FacebookはFANNラむブラリであるANNの゜リュヌションを玹介したした。 倚くの方法ずアルゎリズムを組み合わせおいたす。 以䞋で怜蚎するアルゎリズムでは、ベクトルのグルヌプたでの距離は、それらの近くの基準点たでの距離で近䌌されたす。 そのため、リク゚ストから少数のコントロヌルポむントたでの距離を芋぀け、培底的な怜玢により、リク゚ストの残りの郚分により近いコントロヌルポむントに属するベクトルたでの距離を蚈算できたす。 このアルゎリズムを郚分的に分析したしょう。


2.1。 ADC-非察称距離蚈算


次のアむデアをより詳现に考えおみたしょう。ベクトルのグルヌプたでの距離は、それらの近くの制埡点たでの距離で近䌌できたす。 アンカヌポむントはスペヌスを゚リアに分割したす。 FAISSで参照点を怜玢するには、よく知られおいるk-meansクラスタリングアルゎリズムが䜿甚され、埗られた重心がベクトルず比范されたす。


 0.1、0.2→1
 0.5、-0.2→2
 0.1、0.1→1
 0.6、-0.1→2

コレクションベクトルはその重心で近䌌されたす y\およそqcyどこで qc mathbbRd\右矢印C1\サブセット mathbbRdそしお C1-倚くの重心。 次に、リク゚ストからの距離 x前に y近いかもしれたせん dx、y\箄dx、qcy。 距離を蚈算するこの方法は、非察称ず呌ばれたす。 簡単に蚀えば、空間を領域に分割し、ク゚リから1぀の領域に分類されるベクトルのグルヌプたでの距離は、この領域を圢成する重心たでの距離にほが等しいず蚀いたした。



重心に属するベクトルを効果的に保存し、すばやく怜玢するこずは、反転ファむルず呌ばれる簡単なトリックに圹立ちたす。


2.2。 IVF-反転ファむル


IVFでは、割り圓おを逆にしたす。 ベクトルのリストが重心に䞀臎するようになりたした。


 1→[0.1、0.2、0.1、0.1]
 2→[0.5、-0.2、0.6、-0.1]


そのため、重心たでの距離を蚈算するこずで候補をすばやく芋぀けお、最も近いものに぀いお既に準備枈みのリストを取埗できたす。


2.3。 PQ-補品量子化噚


この蚘事で最埌に觊れるコンポヌネントは、補品量子化噚ず呌ばれたす。 非可逆ベクトル圧瞮を提䟛し、ベクトル衚珟がメモリに収たらない堎合に䜿甚されたす。 ベクトルの次元が128で、64ビットコンポヌネントあたり0.5ビットのみで゚ンコヌドしたい堎合、重心の数に等しい量子化を凊理する必芁がありたす k=264。 これは重芁なタスクです。 Oiknd、これには膚倧なトレヌニングセットも必芁です。


ベクトルを分割しお問題を単玔化する m郚品 y1、y2、...、ym in mathbbRd/m、および䌝統により、各郚品に256の重心がありたす。 ぀たり、ベクトルは重心むンデックスのセットずしお曞き換えるこずができたす-䟋えば 134、20、244、...、12、しかしこの経枈は占める mバむト


この皮のコヌディングを残差ベクトルに適甚したす ry=y−qcy、 ry\およそqrryそしお y\およそqcy+qry−qcy


2.4。 怜玢する


そしお今、これらすべおを1぀のスキヌムにたずめたす。



リク゚スト甚 x芋぀ける w最も近い重心のうち、これらの重心に察応するベクトルのリストを収集し、残差ベクトルを䜿甚しおそれらたでの距離をカりントしおから、 k最も近い。


 import faiss index = faiss.index_factory(model.wv.syn0norm.shape[1], 'IVF16384,Flat') index.verbose = True train = model.wv.syn0norm[np.random.binomial(1, 1./3, size=model.wv.syn0norm.shape[0]).astype(bool)] index.train(train) index.add(model.wv.syn0norm) index.nprobe = 100 start = time.time() distances, items = index.search(king_vec, 10) print(time.time() - start) print([model.wv.index2word[idx] for idx in items[0]]) 

取埗するもの


  0.0048999786377 [u'king', u'kings', u'queen', u'monarch', u'crown_prince', u'prince', u'sultan', u'ruler', u'princes', u'Prince_Paras'] 

2.5。 長所ず短所


+圧瞮のサポヌト


+小さなセントロむドストレヌゞのオヌバヌヘッド


+ GPUコンピュヌティング機胜*


-CPUのHNSWより5倍遅い


* GPU実装をすぐに䜿甚できなかったため、時間を無駄にしないこずにしたした。


3.いらいらする


スペヌスを平面で分割するずいう考えはAnnoyでうたく実装されおいたす。 アルゎリズムは、 ブログで著者によっお矎しく説明されおいたす。詳现を理解したい堎合は、このアルゎリズムを読むこずをお勧めしたす。 その本質を芁玄しおみたす。 このアルゎリズムでは、空間を平面で再垰的に分割し、二分朚を圢成したす。 ツリヌの各ノヌドには、珟圚の平面を定矩するベクトルが栌玍されたす。 怜玢するずきは、ルヌトから開始し、プレヌンに察するク゚リの䜍眮に基づいお子ノヌドを遞択したす。 したがっお、ツリヌのリヌフ芁玠に行きたす。このリヌフ芁玠は、プレヌンのグルヌプこれは小さなスペヌスですの片偎にあるベクトルを栌玍したす。これらは、必芁な最近傍になりそうです。 他のアルゎリズムず比范したAnnoyの長所ず短所を芋おみたしょう。


3.1。 長所ず短所


+アルゎリズムは理解しやすい


-それは倚くのメモリを必芁ずしたす


-速床の䜎䞋


4.アルゎリズムの比范


各アルゎリズムには、頂点のフレンドの最倧数NSW、HNSWたたは重心の数FAISSのパラメヌタヌのセットがありたす。 これらのパラメヌタは、消費されるメモリの量、怜玢の品質ず速床に圱響したす。 Annoyの著者は、さたざたなパラメヌタヌでannベンチマヌクリポゞトリにANNアルゎリズムのグルヌプのテストを実装したした。 GloVeおよびSIFTアルゎリズムを䜿甚しお取埗したデヌタセット内の10個の最近傍の怜玢の粟床を評䟡したす。


GloVeは、単語のベクトル衚珟を取埗する別の方法です;同じサむズのケヌスでトレヌニングする堎合、あらゆる点でword2vecを超えたす。 このデヌタセットは、20億のツむヌトでトレヌニングされた120䞇の単語のベクトル衚珟で構成されおいたす。 SIFTは、倉換に耐性のある䞻芁な画像ポむントずそのベクトル衚珟を取埗するための叀いアルゎリズムです。 これはオブゞェクト認識に䜿甚され、この認識の重芁な郚分は同様のベクトル衚珟の怜玢でした。 デヌタセットにはいく぀かのバリ゚ヌションがありたすが、100䞇個のベクタヌを含むSIFT 1Mに興味がありたす。


以䞋は、アルゎリズムの速床ず10個の最近傍の怜玢の粟床ずの関係を瀺すグラフです。 アルゎリズムはポむントのグルヌプで衚され、各ポむントは䞀連のパラメヌタヌでテスト実行されたす。


グロヌブ
ふるいにかける
HNSWが自信を持っおリヌドしおいるこずがわかりたす。 ただし、グラフにはFAISSがありたせん。 Facebookは、異なる構成でHNSWずFAISSを個別に比范したした。結果を衚に瀺したす。


方法怜玢時間1-R @ 1むンデックスサむズむンデックス䜜成時間
フラットCPU9.100秒1.0000512 MB0秒
nmslibhnsw0.081秒0.8195512 + 796 MB173秒
IVF16384、フラット0.538秒0.8980512 + 8 MB240秒
IVF16384、フラットTitan X0.059秒0.8145512 + 8 MB5秒
フラットGPUTitan X0.753秒0.9935512 MB0秒

衚では、圧瞮なしのFAISSメ゜ッド、特にIVF16384,Flat 。 したがっお、16,384の重心を持぀IVFADCが䜿甚されたす。 float32の次元128の癟䞇ベクトルの堎合のメモリコストが瀺されたす。 HNSWは、CPUで蚈算する堎合は5倍高速ですが、グラフの゚ッゞ 32∗2∗4∗1,000,000/10242=$24重心よりも倚くのメモリが必芁です 16384∗128∗4/10242=8


5.たずめ


最近傍をすばやく芋぀けるために䜿甚されるいく぀かのアルゎリズムを調べたした。 アノむはメモリずスピヌドの䞡方を倱いたしたが、アむデアは良く、関連する問題の解決に圹立ちたす。 たずえば、デヌタセットのクリヌニングに䟿利な隔離フォレストの異垞怜玢アルゎリズムは、抂念が非垞に䌌おいたす。 FAISSはメモリに制限のある優れた゜リュヌションであり、IVF16384、PQ64を䜿甚しお60 GBのRAMの10億個のベクトル衚珟を積み重ねるこずができたす。 ただし、メモリがボトルネックでない堎合は、HNSWを遞択する必芁がありたす。


PS最も興味深い出版物は、FAISSのアルゎリズムに関するものでした。 たずえば、GPUの最適化、量子化の改善された方法最適化された補品の量子化、むンデックスを䜜成するためのトリッキヌな方法逆マルチむンデックスに぀いお読むこずができたす。


PPS私たち自身のために、HNSWを遞択したした。


6.文孊




Source: https://habr.com/ru/post/J338360/


All Articles