中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

Faiss解説シリーズ(第二回)ハマりポイント集

crumbjp.hateblo.jp

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だけがヒット。

次のクエリーでは nprobe2 に変更しているので、 2クラスターにクエリーを投げて 2 vectorがヒットした。

IVFでは nprobe の調整により、パフォーマンスと精度のバランスを取る事が重要だ。

次回

crumbjp.hateblo.jp