高次元ベクトルデータ検索技術「NGT」のGo APIとサーバ機能のOSS公開

  • このエントリーをはてなブックマークに追加

はじめに

検索技術の森本と申します。

先日、高速な近傍探索を実現するソフトウエアであるNGT(Neighborhood Graph and Tree)とそのPython用インターフェースを公開しております。それに引き続いてgongtngtdを公開しました。

アプリケーション開発を想定して開発にGo言語、サーバプロトコルとしてHTTPとgRPCを採用しました。Go APIとしてgongtとHTTP/gRPCサーバ機能を提供するngtdを公開しましたので、近似ベクトル検索エンジンのアプリケーション開発が容易になることを期待しております。

前回のPython用インターフェースの時に使ったgensimを使い、gRPCでPythonから登録して、node.jsから検索する方法をご紹介します。gensimのインストール方法やモデルの入手についてはこちらをご参照ください。

また、今回はngtdのDocker imageを用意したので、是非お使いください。

Dockerを使わない場合や詳細な利用方法は省略しますので、それぞれのリポジトリのREADME.md(gongt)README.md(ngtd)をご参照ください。

Docker imageの取得と確認

Docker Hubで公開していますので、以下のコマンドで取得・確認ができます。

$ sudo docker pull yahoojapan/ngtd:latest
$ sudo docker run --rm yahoojapan/ngtd:latest
NAME:
   ngtd - NGT Daemonize

USAGE:
   ngtd [global options] command [command options] [arguments...]

VERSION:
   0.0.1-first

COMMANDS:
     http, H   serve ngtd index by http
     grpc, g   serve ngtd index by grpc
     build, b  build ngtd index
     help, h   Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version

gRPCサーバの起動

$ sudo docker run -it -v $(pwd)/w2v:/w2v -p 8200:8200 ngtd g -i /w2v/index -d 200 -t bolt -p /w2v/kvs.bdb -P 8200
2018-04-17 08:30:23     [INFO]: NGTD GRPC Server Starting ...

gRPCを用いたインデックス作成

ngtdにはビルドコマンドを用意してありますが、ここではgRPC経由でインデックスを作成します。

gRPCのインストールとPythonインターフェースの生成

pipでライブラリとツールをインストールし、ngtd_pb2.pyとngtd_pb2_grpc.pyを生成します。

$ pip install grpcio grpcio-tools
$ curl -O https://raw.githubusercontent.com/yahoojapan/ngtd/master/proto/ngtd.proto
$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ngtd.proto

インデックス作成サンプルプログラム

以下のプログラムを実行すると、インデックスが作成されます。

今回のデータの場合は筆者の実行した環境で1500秒ほどかかりました。

データの挿入時にDBロックを取得しなければならないため、Python版と比較すると1.7倍程度遅くなっています。

# -*- coding: utf-8 -*-

import argparse

from gensim.models.keyedvectors import KeyedVectors
import grpc
import numpy as np

from ngtd_pb2 import CreateIndexRequest, Empty, InsertRequest
import ngtd_pb2_grpc


def generator(model):
    # generate insert word2vec data
    word_vectors = KeyedVectors.load_word2vec_format(model, binary=True)
    for word in word_vectors.index2word:
        vector = word_vectors[word]
        normalized_vector = vector / np.linalg.norm(vector)
        word = word.encode('utf8')
        yield InsertRequest(vector=normalized_vector.tolist(), id=word)


def insert(host, port, model):
    # insert vectors into ngtd
    channel = grpc.insecure_channel('{}:{}'.format(host, port))
    stub = ngtd_pb2_grpc.NGTDStub(channel)
    for _ in stub.StreamInsert(generator(model)): pass
    stub.CreateIndex(CreateIndexRequest(pool_size=8))
    stub.SaveIndex(Empty())


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-H', '--host', type=str, help='ngtd hostname', default='localhost')
    parser.add_argument('-p', '--port', type=int, help='ngtd port', default=8200)
    parser.add_argument('-m', '--model', type=str, help='/path/to/model', default='entity_vector/entity_vector.model.bin')
    args = parser.parse_args()
    insert(args.host, args.port, args.model)

gRPCを用いた検索

gRPCを採用しており言語を問わず利用できるので、今回はnode.jsを使って検索します。

node.jsのインストールは省略します。

gRPCライブラリのインストール

npmでgrpcをインストールします。node.jsはprotoファイルを直接読み込めるので、生成は省略します。

$ npm install grpc

検索サンプルプログラム

const grpc = require('grpc');
const rl = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: 'search> '
});
const ngtd = grpc.load('/path/to/ngtd.proto').ngtd;
const label = 'search time';

var client = new ngtd.NGTD(process.argv[2], grpc.credentials.createInsecure());

rl.prompt();

rl.on('line', (line) => {
  var query = Buffer.from(line.trim());
  console.time(label);
  client.SearchByID({id: query, size: 11, epsilon: 0.01}, (err, res) => {
    console.timeEnd(label);
    if (err) {
      console.log('Error: %s', err);
    } else {
      res.result.forEach((r) => {
        console.log('%s\t%f', r.id.toString(), r.distance);
      });
    }
    rl.prompt();
  });
}).on('close', () => {
  console.log("close.");
});

実行結果

同一サーバー間で約2msで検索できました。通信やデータのシリアライズ処理を含んでいることを考えると、高速に検索できていると言えると思います。

$ node search.js localhost:8200
search> [ヤマハ]
search time: 2.224ms
[ヤマハ]        0
[ローランド]    0.588580846786499
[コルグ]        0.6159689426422119
[河合楽器製作所]        0.6263557076454163
ヤマハ  0.6312673687934875
[電子オルガン]  0.6925672292709351
[イーエスピー]  0.7000870108604431
[フェンダー_(楽器メーカー)]     0.733221709728241
[ギブソン_(楽器メーカー)]       0.7335250377655029
[電子ピアノ]    0.7376455068588257
[シンセサイザー]        0.7419003844261169
search>

おわりに

NGTをサーバ化し、インデックスの構築と検索をgRPCで行ってみましたがいかがでしたでしょうか。gRPC以外にHTTPプロトコルもサポートしていますので、ぜひそちらも試してみてください。機械学習のモデリングはPython製のツール、アプリケーションはnode.jsというように状況に応じて適したツール、言語を選択できるので開発に幅が広がると思います。密ベクトル検索でアプリケーションを構築する際にはぜひご検討いただければと思います。

検索技術 森本

Yahoo! JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。

  • このエントリーをはてなブックマークに追加