英語の部分一致・単語の変化を無視した検索をする方法を考える

曖昧さを極力排除するために、最初に定義を書きます。

  • この記事内では曖昧検索は「This book」と検索して「This is a pen.」がヒットすることを指すものとします。
  • 単語の変化を無視した検索とは以下とします。
    • 単数形、複数形による単語の変化、「cars」で検索した時に「car」がヒットすること
    • 時制による単語の変化、「do」で検索して「did」がヒットすること

今回は検索した英文・英単語に対する訳を返すことを想定しています。

環境構築

この環境を構築します。

  • Ubuntu 14.04
  • Python 3.4.2
  • Elasticsearch 1.4.2
  • NLTK 3.0.1

まずはapt-getでインストールできるものをまとめてインストールします。lib系はPythonのインストールのために、openjdkはElasticsearchのためにインストールします。

sudo apt-get install -y python-pip libssl-dev zlib1g-dev libbz2-dev openjdk-7-jre

Python 3.4.2をインストールするためにPythonzをインストールします。

curl -kL https://raw.github.com/saghul/pythonz/master/pythonz-install | bash

Pythonのインストールを行います。Pythonのビルドが行われるため、時間がかかります。

source ~/.pythonz/etc/bashrc
pythonz install 3.4.2

Virtualenvwrapperを使い、Python 3.4.2の環境に移行します。

sudo pip install virtualenvwrapper
export WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
mkvirtualenv english_search -p ~/.pythonz/pythons/CPython-3.4.2/bin/python
workon english_search

今回使用するPythonのライブラリをインストールします。

pip install nltk elasticsearch

次にElasticsearchのインストールを行います。

cd /tmp
wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.4.2.deb
sudo dpkg -i elasticsearch-1.4.2.deb

最後にElasticsearchを起動します。

sudo service elasticsearch start

Elasticsearchのインストールに成功していれば、curl経由のリクエストでこのような結果を得られます。

curl http://localhost:9200
{
  "status" : 200,
  "name" : "Michael Nowman",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "1.4.2",
    "build_hash" : "927caff6f05403e936c20bf4529f144f0c89fd8c",
    "build_timestamp" : "2014-12-16T14:11:12Z",
    "build_snapshot" : false,
    "lucene_version" : "4.10.2"
  },
  "tagline" : "You Know, for Search"
}

曖昧検索

曖昧検索についてはElasticsearchに任せることができます。まずは検索対象をElasticsearchに挿入します。

import elasticsearch


index = 'translation'


def connect() -> elasticsearch.client.Elasticsearch:
    return elasticsearch.Elasticsearch(host='localhost')


def insert(doc_type: str, source: str, target: str) -> None:
    connect().index(index=index, doc_type=doc_type, body={
        'source': source,
        'target': target,
    })

例えばこのように呼び出します。

insert('en_ja', "This is a pen", "これはペンです。")

doc_typeに指定した'en_ja'としたのは、このドキュメントが英語から日本語への翻訳データを持っているからです。doc_typeはRDBでいうテーブルに近い概念です。

挿入する関数が完成したので、次に検索する関数を定義します。

def search(doc_type: str, text: str) -> dict:
    return connect().search(index=index, doc_type=doc_type, body={
        'query': {
            'fuzzy_like_this_field': {
                'source': {
                    'like_text': text,
                },
            },
        },
    })

検索してみましょう。

print(search('en_ja', 'this is'))
{'took': 17, 'timed_out': False, '_shards': {'total': 5, 'failed': 0, 'successful': 5}, 'hits': {'max_score': 0.2169777, 'total': 1, 'hits': [{'_id': 'AUuHC_fm5NVipubJJyjk', '_source': {'target': 'これはペンです。', 'source': 'This is a pen'}, '_type': 'en_ja', '_score': 0.2169777, '_index': 'translation_memory'}]}}

単語の変化を無視した検索については後日。

コメント

2015 - 2017 (c) 成瀬基樹