ヤフー株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。LINEヤフー Tech Blog

テクノロジー

高次元ベクトルデータ検索技術「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というように状況に応じて適したツール、言語を選択できるので開発に幅が広がると思います。密ベクトル検索でアプリケーションを構築する際にはぜひご検討いただければと思います。

検索技術 森本

こちらの記事のご感想を聞かせください。

  • 学びがある
  • わかりやすい
  • 新しい視点

ご感想ありがとうございました

このページの先頭へ