2015年11月15日

自動要約API「summpy」を使ってPostgreSQLに文章の要約機能を追加する

昨日、PostgreSQL勉強会で「PostgreSQLハッキング 最初の一歩」と題して、PostgreSQLの拡張開発の初歩についていくつかお話させていただきました。 本エントリでは、勉強会でPL/Pythonの例としてご紹介した「自動要約APIをPostgreSQLに組み込む」について、もう詳しく紹介させていただこうと思います。

(右のサムネイルの意味は本エントリの最後に分かります)

■「自動要約API」とは?


自動要約APIは、10月末にリクルートテクノロジーズさんがリリースされた自然言語処理のライブラリで、Pythonで書かれたものです。 アルゴリズムについてはあまり詳しくは理解していないのですが、LexRankと呼ばれるアルゴリズムとMCP(Maximum Coverage Problem)と呼ばれるアルゴリズムが実装されており、形態素解析で品詞を分解したものにこれらのアルゴリズムを適用することで、「文章の中で重要度の高いセンテンスを選択する」ということを実現するようです。

詳細については上記ブログおよびGithubを参照してください。

■なぜPostgreSQLと組み合わせるのか?


このライブラリを最初に見た時、PostgreSQLに組み込んでみると面白そうだな、と感じました。

というのは、
  • PostgreSQLにはPL/Pythonがあり、Pythonでストアドプロシージャ(ファンクション)を書ける。もちろん、外部のライブラリを使うことができる。
  • データベースに蓄積したデータに対していろいろな加工・分析処理をしたい、というニーズはある。
  • 自分自身はPL/Pythonでプロシージャを書いたことがなかったので、試してみるにはいいレベル。
と考えたからです。

というわけで、このPythonライブラリ「summpy」をPostgreSQLからSQLを使って実行できるような拡張モジュール「pg_summpy」を作ってみます。

■「summpy」の動作環境の準備


まず、summpyを動作させる環境を構築する必要があります。

summpyを動作させる環境は README.md を参照していただきたいのですが、以下のソフトウェアがインストールされている必要があります。
  • Python 2.7
  • MeCab, MeCab-ipadic
  • python-mecab
  • networkx
  • numpy
  • scipy
  • sklearn
  • pulp
  • cherrypy
なお、RHEL6/CentOS 6での環境構築方法はserverspecのspecファイルにまとめてありますので、以下のspecファイルを参照してください。(各ソフトウェアのインストール方法もコメントの形式で記載してあります) なお、Red Hat Enterprise Linux 6/CentOS 6にデフォルトでインストールされるPythonのバージョンは「2.6」ですので、上記ソフトウェアは動作しません。別途「Python 2.7」をインストールして、そちらを使う必要があります。(今回はソースからコンパイルして /usr/local 以下にインストールしました)

■PL/Pythonの準備


次にPL/Pythonの準備をします。 PL/Pythonそのものについては上記のマニュアルを参照していただきたいのですが、ここで今回のちょっとトリッキーな点を解説します。

もともと、PL/Pythonのモジュール(plpython2.so)はPythonのスクリプトを実行するためにPython本体の共有ライブラリをリンクしています。

RHEL6/CentOS6用のコミュニティ版のPostgreSQL RPMからインストールすると、PL/PythonのライブラリはOSデフォルトのPython 2.6のライブラリにリンクされてます(以下のlddの出力の2行目)。
[snaga@localhost pg_summpy]$ ldd /usr/pgsql-9.4/lib/plpython2.so
        linux-vdso.so.1 =>  (0x00007fff243ac000)
        libpython2.6.so.1.0 => /usr/lib64/libpython2.6.so.1.0 (0x00007f0c174be000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f0c1712a000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f0c16f0c000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f0c16d08000)
        libutil.so.1 => /lib64/libutil.so.1 (0x00007f0c16b05000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f0c16880000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f0c17a8d000)
[snaga@localhost pg_summpy]$ 
しかし、Python 2.6にリンクされたこのPL/Pythonモジュールだと前述した(summpy動作に必要な)Pythonライブラリ群が動作しませんので、Python 2.7にリンクされたPL/Pythonモジュールを用意する必要があります。

具体的には、PostgreSQLのソースコードから --with-python オプションを付けてビルドし、plpython2.so ファイルだけをRPMのものと入れ替えます。(入れ替えるのは plpython2.so だけで構いません。)

すると、以下のようにPython 2.7にリンクしたplpython.soモジュールを使うことができるようになります。
[snaga@localhost pg_summpy]$ ldd /usr/pgsql-9.4/lib/plpython2.so
        linux-vdso.so.1 =>  (0x00007fffda0fb000)
        libpython2.7.so.1.0 => /usr/local/lib/libpython2.7.so.1.0 (0x00007f1aa594c000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f1aa55ac000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1aa538f000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f1aa518b000)
        libutil.so.1 => /lib64/libutil.so.1 (0x00007f1aa4f87000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f1aa4d03000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f1aa5f4b000)
[snaga@localhost pg_summpy]$ 
今回私が作成して使用した RHEL6/CentOS6 の PostgreSQL 9.4(x86_64版)のRPMで動作するplpython2.soは以下に置いておきます。 あるいは、もちろんソースコードから全部ビルドしたPostgreSQLを使っても構いません。

■PL/Pythonからsummpyを呼び出す関数を作成する


ここまで環境ができたら、pg_summpy.sqlを実行してsummpyライブラリを呼び出す関数をPostgreSQLのデータベースに登録します。ソースコードは以下です。
[snaga@localhost pg_summpy]$ psql -f pg_summpy.sql mydb
CREATE FUNCTION
DROP FUNCTION
CREATE FUNCTION
DROP FUNCTION
CREATE FUNCTION
[snaga@localhost pg_summpy]$ 
すると、lexrank_summarize()という関数とmcp_summarize()という関数が登録されて使用できるようになります。

関数自体は非常にシンプルで、例えば lexrank_summarize() 関数は、
  • 第1引数: summpy ライブラリのあるディレクトリへのPATH(git cloneした時のトップディレクトリ)
  • 第2引数: 要約したい文章
  • 第3引数: 「いくつの文に要約するか」という上限値
となっています。第1引数のsummpyへのPATHは、PostgreSQLのバックエンドプロセスが参照できるディレクトリである必要があることに注意してください。
CREATE OR REPLACE FUNCTION lexrank_summarize(p text, t text, s_limit integer)
  RETURNS SETOF text
AS $$
   import sys
   sys.path.append(p)
   from summpy import lexrank

   res = lexrank.summarize(unicode(t, 'utf-8'), sent_limit=s_limit)
   for s in res:
      yield(s.encode('utf-8'))
$$ LANGUAGE plpythonu;
そして、この関数は要約した文章1つを1レコードとして返却します。

■実際に文章を要約してみる


ここまでできたら、実際に文章を要約してみましょう。

今回は、先日ネット上で話題になっていた「東京カレンダー 東京女子図鑑」の「綾の銀座での”上質な”暮らし」を要約してみます。

まず、本文が非常に長いので、以下のように第2引数にすべての文章をコピペしたSQLファイルを作成します。(ここでは5つの文章に要約することにします)
SELECT * FROM lexrank_summarize('/path/to/summpy', '20代後半頃から、同期が1人また1人と、会社を辞めていきました。辞める理由はいろいろ(・・・中略・・・)もうちょっとしたら、別の男性探さないととは思ってますが、今はもう少しだけこの生活楽しんでいたいなって思います。', 5);
著作権の関係上、サンプルSQLをファイルで配布するのは避けますが、作成するとこんな感じ(↓)になります。


そして、このSQLを実際に実行すると、以下のような出力を得ることができます。
[snaga@localhost pg_summpy]$ psql -f tokyo-calendar.sql mydb
                                                                         lexrank_summarize
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 まさか、この私が、あの外資系ブランドで働くことになるとは思いませんでしたね。
 その女性に住んでる場所を聞かれて「恵比寿です。」と言ったところ、あぁ、って苦笑いされました。
 最初は、上司のいう一流のものがわからなかったけど、その人の行動をお手本に真似するようになって、やっと最近30歳を超えた大人の女の流儀が分かるようになった気がします。
 レストランも詳しいし、私は、大分その人から大人にしてもらいました(笑)。
 「女性が30歳になったら、歌舞伎デビューくらいしていないと」と彼は私を桟敷席に連れて行きました。
(5 rows)

[snaga@localhost pg_summpy]$
上記の通り、綾の銀座での暮らしを5つの文章に要約することができました。

綾が銀座の暮らしを通して大人になっていった様子が、ありありと、かつ簡潔にまとめられています(?)。

なお、形態素解析を正確に行うために文章に句点「。」を追加しなければならない個所がいくつかありますので、その点はご注意ください。

■まとめ


以上、簡単ではありますが、綾が銀座で大人になっていった様子PL/Pythonを使ったPostgreSQLの拡張方法をご紹介しました。

Pythonはデータ処理を行うためのライブラリやノウハウなどのエコシステムがどんどん大きくなっています。

その結果として、データベースに蓄積したデータをPythonで処理したい、その時に極力シンプルにデータを扱いたい、データベース内部で処理したい(In-Database処理と言います)といったニーズも出てくるかと思います。

そんなニーズにPL/Pythonはうまくフィットするのではないかと思います。興味をもった方は、ぜひこれを機会にチャレンジしてみていただければと思います。

では。

0 件のコメント:

コメントを投稿