Faiss解説シリーズ(第二回)ハマりポイント集
Faissインデックス毎の制限について
FlatインデックスはID管理出来ない
func AddWithIDsDontWorkOnFlatIndex() { fmt.Println("*** AddWithIDsDontWorkOnFlatIndex ") index, _ := faiss.IndexFactory(2, "Flat", faiss.MetricL2) index.AddWithIDs([]float32{0,0,1,0,0,1,1,1}, []int64{0,1,2,3}) }
実行結果
*** AddWithIDsDontWorkOnFlatIndex Error in virtual void faiss::Index::add_with_ids(faiss::Index::idx_t, const float *, const faiss::Index::idx_t *) at /Users/crumbjp/git/faiss/faiss/Index.cpp:39: add_with_ids not implemented for this type of index
(ID管理出来ない)Flatインデックスでベクトルの削除を行うとどうなるか?
func RemoveIDsOnFlatIndex() { fmt.Println("*** RemoveIDsOnFlatIndex") index, _ := faiss.IndexFactory(2, "Flat", faiss.MetricL2) index.Add([]float32{0,0,1,0,0,1,1,1}) fmt.Printf("Ntotal: %v\n", index.Ntotal()) distances, labels, _ := index.Search([]float32{1, 0.1}, 4) fmt.Printf("Distances: %v, Labels: %v\n", distances, labels) selector, _ := faiss.NewIDSelectorBatch([]int64{0}) defer selector.Delete() index.RemoveIDs(selector) fmt.Printf("Ntotal: %v\n", index.Ntotal()) distances, labels, _ = index.Search([]float32{1, 0.1}, 4) fmt.Printf("Distances: %v, Labels: %v\n", distances, labels) }
0
番目の要素を削除する。
selector, _ := faiss.NewIDSelectorBatch([]int64{0}) defer selector.Delete() index.RemoveIDs(selector)
実行結果
*** RemoveIDsOnFlatIndex Ntotal: 4 Distances: [0.010000001 0.80999994 1.01 1.81], Labels: [1 3 0 2] Ntotal: 3 Distances: [0.010000001 0.80999994 1.81 3.4028235e+38], Labels: [0 2 1 -1]
3ベクトルしか無いインデックスに4個取得しようとしているので、最後のラベルは見つからなかった事を示す -1
が返却される。
注目すべきは、削除した 0
番目は切り詰められて、全ベクトルのインデックスが変わってしまっている。
ID管理出来ない上に、IDが変わってしまうので超管理し難い
事実上Flatインデックスでは『削除』は出来ないものと思って良い
HNSWインデックスでは削除出来ない
func RemoveIDsOnHNSW() { fmt.Println("*** RemoveIDsOnHNSW ") index, _ := faiss.IndexFactory(2, "HNSW32", faiss.MetricL2) index.Add([]float32{0,0,1,0,0,1,1,1}) fmt.Printf("Ntotal: %v\n", index.Ntotal()) selector, _ := faiss.NewIDSelectorBatch([]int64{0}) defer selector.Delete() index.RemoveIDs(selector) }
ちなみに、HNSWだけ指定した場合はFlatインデックスの一種となる。制限もFlatインデックスに準拠する。
index, _ := faiss.IndexFactory(2, "HNSW32", faiss.MetricL2)
実行結果
*** RemoveIDsOnHNSW Ntotal: 4 Error in virtual size_t faiss::Index::remove_ids(const faiss::IDSelector &) at /Users/crumbjp/git/faiss/faiss/Index.cpp:43: remove_ids not implemented for this type of index
IVFインデックスにはトレーニングが必須
func IVFRequiresTrain() { fmt.Println("*** IVFRequiresTrain") index, _ := faiss.IndexFactory(2, "IVF4,Flat", faiss.MetricL2) index.AddWithIDs([]float32{0,0,1,0,0,1,1,1}, []int64{0,1,2,3}) }
4
クラスターに分割する
index, _ := faiss.IndexFactory(2, "IVF4,Flat", faiss.MetricL2)
実行結果
*** IVFRequiresTrain Error in virtual void faiss::IndexIVFFlat::add_core(faiss::Index::idx_t, const float *, const int64_t *, const int64_t *) at /Users/crumbjp/git/faiss/faiss/IndexIVFFlat.cpp:44: Error: 'is_trained' failed
クラスタリングを行い小さなインデックスに分割するので、予めテストデータからクラスターを作っておかなければならない。
インデックスが分割されているので検索クエリーを 何個のクラスターに投げるか?が重要
func IVFWithNprobe() { fmt.Println("*** IVFWithNprobe") index, _ := faiss.IndexFactory(2, "IVF4,Flat", faiss.MetricL2) index.Train([]float32{0,0,1,0,0,1,1,1}) index.AddWithIDs([]float32{0,0,1,0,0,1,1,1}, []int64{0,1,2,3}) fmt.Printf("Ntotal: %v\n", index.Ntotal()) distances, labels, _ := index.Search([]float32{1, 0.1}, 4) fmt.Printf("Distances: %v, Labels: %v\n", distances, labels) parameterSpace, _ := faiss. NewParameterSpace() parameterSpace.SetIndexParameter(index, "nprobe", 2) distances, labels, _ = index.Search([]float32{1, 0.1}, 4) fmt.Printf("Distances: %v, Labels: %v\n", distances, labels) }
デフォルトは一番近い 1
クラスター にクエリーを投げる。nprobe
パラメータで指定
parameterSpace, _ := faiss. NewParameterSpace() parameterSpace.SetIndexParameter(index, "nprobe", 2)
実行結果
*** IVFWithNprobe WARNING clustering 4 points to 4 centroids: please provide at least 156 training points Ntotal: 4 Distances: [0.010000001 3.4028235e+38 3.4028235e+38 3.4028235e+38], Labels: [1 -1 -1 -1] Distances: [0.010000001 0.80999994 3.4028235e+38 3.4028235e+38], Labels: [1 3 -1 -1]
4つのクラスターに4つのvectorを入れている(かつトレーニングデータが実データと一緒)ので 1クラスターに1vectorが入っている状態。 (トレーニングにはもっとデータが必要だという警告が出ている)
最初のクエリーでは、 nprobe
のデフォルト値 1
が採用され、1クラスターにのみクエリーを投げて 1
vectorだけがヒット。
次のクエリーでは nprobe
を 2
に変更しているので、 2クラスターにクエリーを投げて 2
vectorがヒットした。
IVFでは nprobe
の調整により、パフォーマンスと精度のバランスを取る事が重要だ。