Slide 1

Slide 1 text

1 メルカリにおけるアルゴリズム ~写真検索機能を例に~ 2020/06/17 Kosuke Arase (@arase) Software Engineer at US@Tokyo (ML/DE)

Slide 2

Slide 2 text

2 荒瀬 晃介 (Kosuke Arase) ● US@Tokyo ML/DE Team エンジニア ○ 東京大学大学院 原田研究室卒 ○ 2017/08~2019/03: インターン ○ 2019/04: 新卒入社 ○ 2019/07~2020/03: 写真検索 (TechLead) ○ 2020/04~: US@Tokyo ML/DE Team ● 専門: 画像認識,3次元点群認識 ● 出品時の画像認識機能/写真検索機能 ● Twitter, GitHub: @KosukeArase $ whoami

Slide 3

Slide 3 text

3 ● 持ち帰って欲しいこと ○ アルゴリズムの強力さ ○ 計算量大事 ● 持ち帰らなくて良いこと ○ メルカリの写真検索や近似近傍探索の詳細な仕組み 今日の目的

Slide 4

Slide 4 text

4 ● いわゆる画像検索機能 (iOS のみ) ● 商品名を知らなくても画像から商品を検索可能 What is 写真検索 動画リンク: https://youtu.be/kTni8EvOCgI

Slide 5

Slide 5 text

5 ● 画像から Neural Network を用いて 特徴量 (ベクトル) を抽出 ● ベクトルを近傍探索インデックスに追加 データの流れ Neural Network 0.32 0.55 0.23 0.12 ︙ 0.33 ベクトル 近傍探索 インデックス

Slide 6

Slide 6 text

6 最近傍探索 クエリ画像とインデックス内の 各データについて 特徴ベクトルどうしの距離を計算し、 最も距離が近いものを探す 特徴ベクトル空間 クエリ画像

Slide 7

Slide 7 text

7 最近傍探索 クエリ画像とインデックス内の 各データについて 特徴ベクトルどうしの距離を計算し、 最も距離が近いものを探す 特徴ベクトル 特徴空間 クエリ画像

Slide 8

Slide 8 text

8 最近傍探索 クエリ画像とインデックス内の 各データについて 特徴ベクトルどうしの距離を計算し、 最も距離が近いものを探す 特徴ベクトル 特徴空間 クエリ画像

Slide 9

Slide 9 text

9 ベクトルの距離の計算 (2次元) の距離:

Slide 10

Slide 10 text

10 ベクトルの距離の計算 (N次元) の距離: の距離:

Slide 11

Slide 11 text

11 最近傍探索 クエリ画像とインデックス内の 各データについて 特徴ベクトルどうしの距離を計算し、 最も距離が近いものを探す 特徴ベクトル 特徴空間 クエリ画像

Slide 12

Slide 12 text

12 ある検索クエリについて、データベース内の 全ての特徴ベクトルとの距離を計算する際の計算量をO記法で答えよ ● 特徴ベクトルの次元数: d 次元 ● 特徴ベクトルの数: N 個 問題

Slide 13

Slide 13 text

13 ある検索クエリについて、データベース内の 全ての特徴ベクトルとの距離を計算する際の計算量をO記法で答えよ ● 特徴ベクトルの次元数: d 次元 ● 特徴ベクトルの数: N 個 1組の特徴ベクトル間の距離を計算: O(d) N個の特徴ベクトルそれぞれとの距離を計算: O(Nd) 答え: O(Nd) 問題

Slide 14

Slide 14 text

14 ● 特徴ベクトルの次元数: d = 1,840 次元 ● 特徴ベクトルの数: N = 数千万 O(Nd) なので 1,840 x 数千万 = 数百億回の計算を毎回する…? 実際には…

Slide 15

Slide 15 text

15 ● 特徴ベクトルの次元数: d = 1,840 次元 ● 特徴ベクトルの数: N = 数千万 O(Nd) なので 1,840 x 数千万 = 数百億回の計算を毎回する…? 2.9 GHz で動作する CPU が1クロックで1命令処理できるとすると、 秒間の計算数は数十億 → 検索に毎回数十秒かかってしまう 実際には…

Slide 16

Slide 16 text

16 ● 特徴ベクトルの次元数: d = 1,840 次元 ● 特徴ベクトルの数: N = 数千万 データベースのサイズも問題。 float32 (4 Bytes) のベクトルとすると、index のサイズは、、 4 x 1,840 x 数千万 = 数百 GB →メモリに載らない! 実際には…

Slide 17

Slide 17 text

17 import faiss, timeit import numpy as np d = 1024 # ベクトルの次元数 N = 10**5 # データ数 index_flat = faiss.IndexFlatL2(d) # 近傍探索 index index_flat.add(np.random.random((N, d)).astype('float32')) # N本のd次元ベクトルを作成しindexへ追加 query = np.random.random((1, d)).astype('float32') # クエリの作成 def search(_index, _query): _index.search(_query, 10) # 検索 timeit.timeit("search(index_flat, query)", setup="from __main__ import search, index_flat, query", number=1) Facebook AI Research (FAIR) が開発した近傍探索ライブラリ Faiss Demo

Slide 18

Slide 18 text

18 ● PCA などによりベクトルの次元を削減 (e.g. d = 1840 -> 128) ● 近似最近傍探索 高速化

Slide 19

Slide 19 text

19 ● PCA などによりベクトルの次元を削減 (e.g. d = 1840 -> 128) ● 近似最近傍探索 高速化

Slide 20

Slide 20 text

20 最近傍探索 (再掲) クエリ画像とインデックス内の 各データについて 特徴ベクトルどうしの距離を計算し、 最も距離が近いものを探す 特徴ベクトル 特徴空間 クエリ画像

Slide 21

Slide 21 text

21 近似最近傍探索 クエリ画像とインデックス内の 各データについて 特徴ベクトルどうしの距離を計算し、 厳密に最近傍でなくても良いので 距離が近いものを高速に探す 特徴ベクトル 特徴空間 クエリ画像

Slide 22

Slide 22 text

22 ● Locality Sensitive Hashing (LSH) ○ Falconn, etc. ● Tree based ○ FLANN, Annoy*1, etc. ● Graph based ○ NMSLIB ● データ圧縮 ○ ハミング系、ベクトル量子化 (VQ)、直積量子化 (PQ) ● 転置ファイルインデックス (IVF) 近似近傍探索アルゴリズム/ライブラリ *1 Approximate Nearest Neighbors Oh Yeah

Slide 23

Slide 23 text

23 ● Locality Sensitive Hashing (LSH) ○ Falconn, etc. ● Tree based ○ FLANN, Annoy*1, etc. ● Graph based ○ NMSLIB ● データ圧縮 ○ ハミング系、ベクトル量子化 (VQ)、直積量子化 (PQ) ● 転置ファイルインデックス (IVF) 近似近傍探索アルゴリズム/ライブラリ *1 Approximate Nearest Neighbors Oh Yeah

Slide 24

Slide 24 text

24 ● 近似最近傍探索の最前線 ○ 松井勇佑 (相澤・山崎・松井研究室講師) ○ 以下ではこちらの資料の画像を引用 ● メルカリUSの @kumon とともに CVPR@2020 で Tutorial 開催 ○ Image Retrieval in the Wild ○ 08:30-, June 19th (AM), 2020 ○ (日本時間6/20の午前0:30) ○ メルカリの写真検索がトップに! 参考資料

Slide 25

Slide 25 text

25 (コードブックは事前に 訓練データにk-meansを施し獲得)

Slide 26

Slide 26 text

26 (コードブックは事前に 訓練データにk-meansを施し獲得)

Slide 27

Slide 27 text

27 ● 近似精度 (Kの大きさ) と量子化の計算量 (O(DK)) のトレードオフ ● データ圧縮のために使うのは難しい

Slide 28

Slide 28 text

28

Slide 29

Slide 29 text

29 ● メモリ効率が良い ● 入力とコードの距離を近似計算可能

Slide 30

Slide 30 text

30 1本のベクトル (D=1,024次元、32bit float) を M=64分割してK=256個 (8bit) のコードブックを保持する場合のメモリは? 直積量子化 (メモリ効率)

Slide 31

Slide 31 text

31 1本のベクトル (D=1,024次元、32bit float) を M=64分割してK=256個 (8bit) のコードブックを保持する場合のメモリは? ● Before: 32D [bit] = 4D [Byte] = 4,096 [Byte] 直積量子化 (メモリ効率)

Slide 32

Slide 32 text

32 1本のベクトル (D=1,024次元、32bit float) を M=64分割してK=256個 (8bit) のコードブックを保持する場合のメモリは? ● Before: 32D [bit] = 4D [Byte] = 4,096 [Byte] ● After: 8M [bit] = M [byte] = 64 [Byte] 直積量子化 (メモリ効率)

Slide 33

Slide 33 text

33 直積量子化 (距離近似) クエリベクトルとデータベース内のベクトルの距離は クエリベクトルとPQコードとの距離で近似され高速に計算可能

Slide 34

Slide 34 text

34 直積量子化 (距離近似) ルックアップテーブル クエリベクトルとデータベース内のベクトルの距離は クエリベクトルとPQコードとの距離で近似され高速に計算可能 ● D次元のクエリをM分割し、各コード(K個)との距離を 予め計算してルックアップテーブルに保存 (O(DK))

Slide 35

Slide 35 text

35 クエリベクトルとデータベース内のベクトルの距離は クエリベクトルとPQコードとの距離で近似され高速に計算可能 ● D次元のクエリをM分割し、各コード(K個)との距離を 予め計算してルックアップテーブルに保存 (O(DK)) ● データベース内のN個のPQコードとの距離は ルックアップテーブルを参照して足し合わせる (O(MN)) 直積量子化 (距離近似) ルックアップテーブル O(DK + MN)

Slide 36

Slide 36 text

36 データベース中の各データベクトル Xi に対し以下の操作を行う ● K-meansなどで粗い量子化器を用いて、空間を c 個に分割する 転置ファイルインデックス (IVF, indexing)

Slide 37

Slide 37 text

37 データベース中の各データベクトル Xi に対し以下の操作を行う ● K-meansなどで粗い量子化器を用いて、空間を c 個に分割する ● 入力と最も近い代表点 (Cj) との距離の差 (残差、ri = Xi - Cj) を計算 転置ファイルインデックス (IVF, indexing)

Slide 38

Slide 38 text

38 データベース中の各データベクトル Xi に対し以下の操作を行う ● K-meansなどで粗い量子化器を用いて、空間を c 個に分割する ● 入力と最も近い代表点 (Cj) との距離の差 (残差、ri = Xi - Cj) を計算 ● 粗い量子化の結果 (j) と残差 ri のPQコード (ri’) を保存 転置ファイルインデックス (IVF, indexing) j = 1 j = 2 j = c

Slide 39

Slide 39 text

39 クエリ q に対し以下の操作を行う ● クエリ q に最も近い代表点 (Cj) との距離の差 (残差、r = q - Cj) を計算 転置ファイルインデックス (IVF, search)

Slide 40

Slide 40 text

40 クエリ q に対し以下の操作を行う ● クエリ q に最も近い代表点 (Cj) との距離の差 (残差、r = q - Cj) を計算 ● j に登録されている全てのコード (N個) との近似距離を計算 (O(DK+MN)) ● 近似距離が最も近いものを選ぶ 転置ファイルインデックス (IVF, search) j = c j = 2 j = 1

Slide 41

Slide 41 text

41 結局どれくらい早くなるの? Brute force ● O(ND) = 1,024 x 3,000万 = 300億回の計算 IVF+PQ ● 粗い量子化: O(DC) = 1,024 x 8,192 = 1,000万回の計算 ● PQ: O(DK+M(N/C)) = 1,024 x 256 + 64 x (3,000万/8,192) = 50 万の計算 数千倍の高速化! ● 特徴ベクトルの次元数 : D = 1,024 次元 ● 特徴ベクトルの数: N = 3,000万 ● 粗い量子化: c = 8,192 ● PQの分割数: M = 64 ● コードサイズ: K = 256

Slide 42

Slide 42 text

42 import faiss, timeit import numpy as np d = 1024 # ベクトルの次元数 N = 10**6 # データ数 index_pq = faiss.index_factory(d, 'IVF512,PQ64') # 近傍探索 index (IVF + PQ) index_pq.train(np.random.random((10**5, d)).astype('float32')) # 事前学習 index_pq.add(np.random.random((N, d)).astype('float32')) # N本のd次元ベクトルを作成しindexへ追加 query = np.random.random((1, d)).astype('float32') # クエリの作成 def search(_index, _query): _index.search(_query, 10) # 検索 timeit.timeit("search(index_pq, query)", setup="from __main__ import search, index_pq, query", number=1) Brute force では 10**6 で xxx 秒、10**7 で OOM したが、、 Demo (IVF+PQ)

Slide 43

Slide 43 text

43 ● メルカリで使用されているアルゴリズムの一例を示した ● 持ち帰って欲しいこと ○ アルゴリズムの強力さ ○ 計算量大事 ○ Faiss すごい まとめ