前回はFessをWebスクレイピングサーバとして利用して、サイト内の情報を収集しました。今回はFessで収集した情報を利用して、Pythonによる自然言語処理について紹介します。

Python環境が必要になるので、事前にPython 3を利用できる環境を準備してください。

自然言語処理とは?

自然言語処理とは、コンピュータを使って人が話したり書いたりする言語を処理するための一連の技術のことです。構文解析や機械翻訳などさまざまな分野があります。今回はそれらの分野の中でも、形態素解析を含む単語分割と文書分類を中心に説明していきます。

Fessの検索エンジンとして利用されているElasticsearchでは、Apache Luceneが提供するAnalyzerによる単語分割機能を使用しています。

Analyzerはさまざまなカスタマイズが可能であり、この機能を利用することで、日本語の形態素解析や他の言語にも対応する単語分割を実現できます。Analyzerを使いこなすことができれば、テキスト分析や機械学習などで活用できるでしょう。

Elasticsearchでは、AnalyzerをAPI経由でアクセスするAnalyze APIを提供しています。しかし、Analyze APIを使うためだけにElasticsearch環境を構築するのは大変です。

今回は、その手間をラップして手軽に利用できるようにしたPythonパッケージEsanpyを利用します。

Esanpyでは、内部的に最小構成でElasticsearchを立ち上げて、データを保存している既存のElasticsearchなどとは競合しない形で利用することができます。

Esanpy

EsanpyはElasticsearchベースのテキスト解析パッケージです。Esanpyでは、ElasticsearchのAnalyzer機能がそのまま利用可能です。

以下のコマンドでインストールができます。

$ pip install esanpy

利用方法はesanpyをimportして、esanpy.analyzer()にAnalyzer名とテキストを渡すだけで単語配列が返却されます。

$ python
>>> import esanpy
>>> esanpy.start_server()
>>> esanpy.analyzer("今日の天気は晴れです。", analyzer="kuromoji")
['今日', '天気', '晴れ']

esanpy.start_server()はバックグラウンドでテキスト解析用Elasticsearchを立ち上げるので、esanpy.analyzer()を利用する前に1度実行する必要があります。

一連の処理が終わり、単語分割処理が不要になったら、esanpy.stop_server()を実行して、起動したElasticsearchを停止します。

検索用データを格納するElasticsearchを運用している環境であれば、そのElasticsearchに対して、Analyze APIで単語分割に利用することもできます。

しかし、筆者の経験上、テキスト解析用APIとして利用するのであれば、Esanpyのように、データ格納用Elasticsearchとは別に利用した方が運用面では楽でしょう。

以前、データ格納用とテキスト解析用の兼用Elasticsearchとして運用していたことがありますが、Elasticsearchのバージョンアップ作業などで苦労しました。その点、Esanpyは容易に導入や運用を実現できます。

文書分類の方法

前回取得したIT Search+の解説/事例記事を利用してテキスト分類モデルを作成します。

このモデルに記事の本文(article_body)を渡すと、その内容を判断してカテゴリ情報(article_category)を予測するものを考えます。

モデルを作成する流れは以下の通りです。

  • Fessに格納されたデータを取得する
  • 取得したデータのテキストを単語分割する
  • 文字列データをベクトル化する
  • テキスト分類モデルを作成する
  • テキスト分類モデルで任意のテキストを予測する

本稿では予測結果の評価やパラメータのチューニングなどについては触れませんので、必要に応じて、scikit-learnのドキュメントなどを参照してください。

まず、必要なPythonパッケージをインストールします。

$ pip install elasticsearch
$ pip install numpy
$ pip install scipy
$ pip install scikit-learn

以下のコードからは.pyファイルにまとめるなどして、順に実行してください。

始めに、今回利用するPythonモジュールをimportします。

from elasticsearch.client import Elasticsearch
import esanpy
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import LabelEncoder
from sklearn.ensemble import RandomForestClassifier

まずはelasticsearchのPythonモジュールを使用して、Fessに格納されたデータを取得します。

今回のデータはarticle_categoryとarticle_bodyに格納しているので、そのデータだけ抽出してDicitonaryの配列を生成します。

Fessのデータを別の用途で利用したい場合は、この部分のコードだけを参考にして、各用途で利用することもできると思います。

def load_docs(doc_fields,
              es_host = 'localhost:9201',
              fess_index = 'fess.search',
              search_query = {"query":{"match_all":{}}}):
    es = Elasticsearch(es_host)
    response = None
    running = True
    docs = []
    # スクロール検索でsearch_queryの条件にマッチする全件を取得
    while(running):
        if response is None:
            response = es.search(index=fess_index,
                                       scroll='5m',
                                       size=100,
                                       body=search_query)
        else:
            response = es.scroll(scroll_id=scroll_id,
                                 scroll='5m',
                                 params={"request_timeout":60})
        if len(response['hits']['hits']) == 0:
            running = False
            break
        scroll_id = response['_scroll_id']
        for hit in response['hits']['hits']:
            if '_source' in hit:
                docs.append({f:hit.get('_source').get(f) for f in doc_fields})
    return docs

dataset = load_docs(['article_category', 'article_body'])
# dataset = [{'article_category':'...', 'article_body':'...'},...]

次に、テキストを単語分割して、分類モデルを作成するためにベクトル化します。日本語テキストの単語分割はEsanpyを利用して、kuromojiで形態素解析を行います。

ベクトル化はscikit-learnのTfidfVectorizerでartcile_bodyの文書群をTFIDFの行列Xに変換して、予測するカテゴリ情報article_categoryはLabelEncoderで数値化され、整数配列yに変換されます。

Xが説明変数、yが目的変数として、分類モデルを作成する際に利用します。

# Esanpyの起動
esanpy.start_server()

# TfidfVectorizerで利用するAnalyzerを関数にする
def ja_analyzer(t):
    return esanpy.analyzer(t, analyzer='kuromoji')

vectorizer = TfidfVectorizer(analyzer=ja_analyzer)
corpus = [x.get('article_body') for x in dataset]
X = vectorizer.fit_transform(corpus) # 説明変数の行列

encoder = LabelEncoder()
y = encoder.fit_transform([x.get('article_category') for x in dataset]) # 目的変数の配列

Xとyを用いて分類モデルを作成します。

今回は分類モデルを作成する方法としてscikit-learnのランダムフォレストを利用します。scikit-learnにはさまざまな手法での実装があり、インタフェースも統一されていてます。

学習する場合にはfit、予測する場合はpredictです。fitで学習後、ランダムフォレストの学習器clfに任意のテキストを渡すことで、そのテキストのカテゴリを予測することができます。

clf = RandomForestClassifier()
clf.fit(X, y) # 学習

text = 'マウスコンピューターは6月20日、AMDのハイエンドCPU「AMD Ryzen 7 1700X」を搭載したデスクトップPC「LM-AG350XN1-SH5」を発売した。'
preds = clf.predict(vectorizer.transform([text])) # 予測
print('category: %s' % encoder.inverse_transform(preds))

このようなコードを実行すると、以下のようにtextで渡した文書のカテゴリを予測することができます。

category: ['ソリューション']

なお、クロールした時期により学習対象となるドキュメントも変わるので、予測結果が「ソリューション」以外にもなる場合もあります。

*  *  *

今回はFessで収集したデータを用いた自然言語処理として、文書分類を紹介しました。

今回は紹介できませんでしたが、gensimを利用すれば、Fessで収集したデータからWord2VecやDoc2Vecなども生成することができます。

Pythonには多くのパッケージがあるので、それらを組み合わせて利用することで、Fessを自然言語処理や機械学習での活用できると思います。

次回は検索システムで重要な機能であるAnalyzerについて詳しくみていく予定です。

著者紹介

菅谷 信介 (Shinsuke Sugaya)

Apache PredictionIOにて、コミッター兼PMCとして活動。また、自身でもCodeLibs Projectを立ち上げ、オープンソースの全文検索サーバFessなどの開発に従事。