Elasticsearchで画像の特徴ベクトルから類似した画像を抽出する方法

顔が写った画像からVGGFaceで特徴ベクトルを抜き出し、データベースに格納する。

このデータベースの中から指定した画像に類似したものを検索したかったのでいろいろ調べてみました。

類似しているかどうかはユークリッド距離というもので判別します。

こんな感じで求めることができます。

squares = []
squares = [(list1[i] - list2[i]) ** 2 for i in range(len(list1))]
sum_of_sqrt = math.sqrt(sum(squares))
1 / (1 + sum_of_sqrt)

 

最初はMySQLにデータの格納をしていたのですが、ベクトルだけで抽出できなそうなので式で抽出できそうなelasticsearchを使ってみました。

 

格納するデータはこんな感じ

{
  "name": "ichiro",
  "vector": { 
    "0": 1 
    "2": 4, 
    "3": 2, 
    "4": 5,
    "5": 29, 
    "6": 7
  }
}

値は適当ですが、本当はfloat型です。

vectorはオブジェクト型にする必要がありました。

理由としては配列にしてしまうと、script内で呼び出した配列が格納した順にソートされてないからです。

そして類似度を求めるクエリはこんな感じ。

"query": {
  "function_score": {
    "script_score" : {
      "script" : {
        "source": """
          double sumVal = 0;
          for (int i = 0; i < params.vector.length; i++) {
            sumVal = sumVal + Math.pow(doc['vector.' + i].value - params['vector'][i], 2);
          }
          return 1 + (1 / Math.sqrt(sumVal));
      """,
     "params": {
       "vector": params_vector
     }
   }
 }
}
},
"sort": [
  { "_score": { "order": "desc" }}
]

 

こんな感じ、doc[‘vector.’ + i]ここで渡したparamsと同じ位置の値を取得できます。

型を配列にしてしまえばmdoc[‘vector’][i]こんな感じでできそうですが、先程書いた通り、格納した順にソートされてないので変な値が返ってきます(内部でごにょごにょしてるみたいです)

params_vectorは配列にして渡しました

例:[0, 3, 23, 21, 44, 2]

for文でループしたかったからです。

一応こんな感じで似てる画像を抽出できました。

少ないデータ数でしか試していないので、パフォーマンスについては不明。

次回の記事でそこら辺も書いていきたいと思います。

 

追記

3万件のドキュメント数で大体2秒くらいで取得できました。

パフォーマンスチューニングせずこの速度です

早くはないですが、許容範囲ですかね