CabochaとComainuをDockerで動かす

下の記事を見て、研究とかで使われるツールでちょっとインストールとかが複雑なものはもうDockerで配布したほうがいいのかなと思った。
専門用語を自動抽出するTermExtractをDockerで簡単に使えるようにしました - CreateField Blog

なので、試しにCabochaとComainuをDockerで動かせるようにしてみた。

Cabocha(日本語構文解析器)

Cabochaのインストールはそんなに複雑じゃないけど、--enable-utf8-only(CabochaというかMeCabだけど) みたいなの毎回気にしなくて良くなくなる。
今回は辞書にはUnidicを利用

docker pullして

$ docker pull skozawa/cabocha-unidic

docker runすればCabochaが使えるようになる。

$ echo "太郎は花子が読んでいる本を次郎に渡した" | docker run -i skozawa/cabocha-unidic cabocha
    太郎は---------D
      花子が-D     |
    読んでいる-D   |
            本を---D
            次郎に-D
              渡した
EOS

MeCabも入ってるからMeCabも使える

$ echo "太郎は花子が読んでいる本を次郎に渡した" | sudo docker run -i skozawa/cabocha-unidic mecab   
太郎	タロー	タロウ	タロウ	名詞-固有名詞-人名-名		
は	ワ	ハ	は	助詞-係助詞		
花子	ハナコ	ハナコ	ハナコ	名詞-固有名詞-人名-名		
が	ガ	ガ	が	助詞-格助詞		
読ん	ヨン	ヨム	読む	動詞-一般	五段-マ行	連用形-撥音便
で	デ	テ	て	助詞-接続助詞		
いる	イル	イル	居る	動詞-非自立可能	上一段-ア行	連体形-一般
本	ホン	ホン	本	名詞-普通名詞-一般		
を	オ	ヲ	を	助詞-格助詞		
次郎	ジロー	ジロウ	ジロウ	名詞-固有名詞-人名-名		
に	ニ	ニ	に	助詞-格助詞		
渡し	ワタシ	ワタス	渡す	動詞-一般	五段-サ行	連用形-一般
た	タ	タ	た	助動詞	助動詞-タ	終止形-一般
EOS

https://registry.hub.docker.com/u/skozawa/cabocha-unidic/

Comainu(中・長単位解析器)

Comainuは依存ツールが結構あってインストールするの面倒なので、Dockerで使えると便利な気がする。

こっちもdocker pullしてrunすれば使える。

$ docker pull skozawa/comainu

XML版のUnidic2がまだ公開されてない影響で標準エラーにメッセージだしてるので、-a stdin -a stdout だけ指定して、stderrは出力しないようにしてる。

$ echo "固有名詞に関する論文を執筆した" | docker run -i -a stdin -a stdout skozawa/comainu comainu plain2longout
B    固有    コユー     コユウ  固有    名詞-普通名詞-形状詞可能                        名詞-普通名詞-一般      *       *       コユウメイシ    固有名詞        固有名詞
        名詞    メーシ  メイシ  名詞    名詞-普通名詞-一般                      *       *       *       *       *       *
        に      ニ      ニ      に      助詞-格助詞                     助詞-格助詞     *       *       ニカンスル      に関する        に関する
        関する  カンスル        カンスル        関する  動詞-一般       サ行変格        連体形-一般     *       *       *       *       *       *
        論文    ロンブン        ロンブン        論文    名詞-普通名詞-一般                      名詞-普通名詞-一般      *       *       ロンブン        論文    論文
        を      オ      ヲ      を      助詞-格助詞                     助詞-格助詞     *       *       ヲ      を      を
        執筆    シッピツ        シッピツ        執筆    名詞-普通名詞-サ変可能                  動詞-一般       サ行変格        連用形-一般     シッピツスル    執筆する        執筆し
        し      シ      スル    為る    動詞-非自立可能 サ行変格        連用形-一般     *       *       *       *       *       *
        た      タ      タ      た      助動詞  助動詞-タ       終止形-一般     助動詞  助動詞-タ       終止形-一般     タ      た      た
EOS

https://registry.hub.docker.com/u/skozawa/comainu/


ちょっとインストールとか設定が面倒なツールとかはDockerにして配ればちょっと試したいくらいのときにも気軽に使えるしよさそう。ツール使いたいだけなのに、インストールに手間取って時間とられるのは不毛だし、どんどんDockerでも配布されるようになればいいんじゃないかと思った。まあ、ディスクサイズとられるのがちょっと辛いけど。

「ITビジネスの原理」を読んだ

「ITビジネスの原理」を読んだけど、個人的にはそんなに面白くなかった。


章構成は以下の通り。

  1. ITビジネスは何で稼いできたのか
  2. ネットが世界を細分化する
  3. ネットワークとコミュニケーション
  4. 消費されるコミュニケーション
  5. ITの目指すもの、向かう場所


1~4章は基本的なことが書いてある印象で、これまでのWebの流れやその中でどういう風にビジネスが行われてきたかが書かれてある。
Web業界あまり知らない人には面白いのかもしれない。

4章の終わりから5章ではハイコンテキストというキーワードを使って著者が一番言いたかったと思われることが書かれている。
商品やサービスの売り方として、アメリカ的なフォーマットが決まっていて必要な情報がきっちり書かれているローコンテキストなモノの売り方ではなく、商品とその商品にまつわる物語や売っている人との関係性を売るようなハイコンテキストなモノの売り方が重要となるというもので、前者がAmazon、後者が楽天らしい。あと、非言語コミュニケーションの話やウェアラブルデバイスの話も少し書いてあった。

5章のなかで、

  • コミュニケーションは共通語から多言語へ
  • 多言語から、さらに非言語へ

のような節があって、多言語化も非言語化もどっちも今後起きると思うので、それぞれはいいんだけど、多言語化はよりローカルへ、非言語化はよりグローバルへと方向性としては逆な気がするのでこの2つが連続して起きるような構成だったのはちょっと気になった。

主張としてはハイコンテキストなコミュニケーションが今後重要となるという話で、その点はそうだと思った。


ITビジネスの原理

ITビジネスの原理

文節境界解析のラベルと性能

文節境界解析で使うラベルで、BIとBILUの2種類でどちらが性能がよいかを検証してみた。結果的にはBIだけのほうが性能が高かった。


前回の輪読会で紹介した固有表現抽出に関する論文の中で印象に残ったことのひとつとして、系列ラベリングに使うラベルで最近はBIOよりもBILOUを使ったほう性能が高いというものがあった。
BIO(Begin, Inside, Outside)とBILOU(Begin, Inside, Last, Outside, Unit(Begin and Last))の違いは固有表現の末尾を考慮するかどうかで、末尾を考慮したほうが性能がよいというもの。
社内輪読会で「Joint Inference of Named Entity Recognition and Normalization for Tweets」を紹介した - skozawa's blog


Comainuでは、同じ系列ラベリングの問題である文節境界解析を行っていて、そこではラベルとしてBIを利用している。文節境界解析の場合、Oはないので、BかIを付与することになる。それで、文節境界解析でも固有表現抽出と同様にラベルとしてBILUを利用することで性能が向上するかを試してみた。


データはBCCWJの一部を学習データのサイズを変えながら試してみる。
学習のモデルはSVMで、素性は形態素情報(書字形、語彙素読み、語彙素、品詞、活用型、活用形、語種情報)と形態素が括弧内かどうかを表すラベル(BIO)。

テストデータ 学習データ1 学習データ2 学習データ3 学習データ4
文数 4534 4534 13602 22670 31738


結果としては、データサイズに関わらずBIの方がよかった。

- 学習データ1 学習データ2 学習データ3 学習データ4
BI 95.96 96.87 97.29 97.57
BILU 95.81 96.81 97.28 97.52


BIとBILUでの誤りを軽く調べてみたけど(「/」が文節境界)、BIのモデルでは形態素間をくっつける傾向にあって、BILUのモデルだと分割する傾向があった。BILUのほうは「ガッカリ/する」とか、それを分割してしまうのかというのもちらほらあって、分割しすぎて性能が下がっていそうだった。

モデル
BI 反論して / きています。 反論してきています。
BI 滞在費 / 全て 滞在費全て
BILU つくっていった。 つくって / いった。
BILU ガッカリする。 ガッカリ / する。

文節境界解析だと、Lを求めるのはほぼBを求めることと同じなので、Lを導入してもあまり性能はよくならなかったのかな。それに、BIだけのOがないタスクだと2値分類なので、BILUで多値分類にすることで問題が複雑になったのかもしれない。

関係ないけど、実験中にComainuのバグを見つけてしまったので、とりあえずやってよかった。
モデルの再学習しないといけないので、今度やってアップデートしよう...

社内輪読会で「Joint Inference of Named Entity Recognition and Normalization for Tweets」を紹介した

社内輪読会で論文を紹介した。
今回はACL2012からJoint Inference of Named Entity Recognition and Normalization for Tweetsを紹介。

内容

ツイートから固有表現抽出(NER)、及び、固有表現の正規化(NEN)を行っている。提案のポイントは、これまで、NERとNENを別タスクとして、NERを行った後にNENを行っていたものを、同時に解いている点。これにより、NENで得られる知見をNERにフィードバックできて、性能が向上するというもの。


感想

  • NENの効果

人名と地名、組織名などの区別は難しそうなので、それをNENの知見を取り入れて解決できるのであればよさそうに感じた。

  • 評価実験

ベースラインとして従来研究の手法を利用して比較してるけど、提案手法をNERとNENに分割して直列に適用した手法との比較もして欲しいと感じた。ベースラインのNENの手法はルールベースなので、ルールベースを学習ベースにしたのが効いたのか、NERとNENを同時に解いていることが効いているのかちょっと分かりづらい。

  • 外部辞書

素性としてWikipediaなどの外部辞書に含まれているかを利用していて、それがだいぶ効いているのだけど、外部辞書に含まれていない固有表現がどの程度存在してどの程度抽出できるのか気になった。

  • NERのラベル

固有表現抽出のラベルといえば、BIOかと思ってたけど、最近はBILOUの方が使われていて、性能が高いというのは知らなかった。
Comainuでは文節境界解析も少しやっていて、そこでのラベルはBI(Oはない)を使っているけど、BILUを使ったほうがいいのかな、今度試してみよう。

大阪PRML読書会#7に参加した

少し前になるけど、大阪PRML読書会#7 - 大阪PRML読書会 | Doorkeeper に参加した。
前回に続いて2回目の参加。

大阪PRML読書会、良いんだけど、月1なので前回の話を結構忘れてしまう問題がある。あと、1回で進むのが15ページくらいなので、読み終わるのが3年後とかになりそうで、もう少し短いスパンで読みたいんだけど、みんなで集まるとなると月1が限界だし難しいなーと感じた。

PRML、家で一人でだらだら読んでるときは全然理解できなかったけど、勉強会にいって他の人と真面目に読むとある程度理解できる。まぁ、そもそもだらだら読んで理解できる類の本ではないから、家でも真剣に読めということなんだけど、家だとなかなか集中できないことも結構あって、家と会社以外で作業できる場所が欲しい。

それで、ちょうど次回は他の予定と被ってて参加できそうにないから、ここ3週間くらい週1回仕事終わりにカフェにいってPRMLを読み進めてみてる。今のところそれなりにうまくいってるけど、たまにわからないところがあっても、誰にも聞けず困るだけなのが悩ましい。

週1回カフェに行って読んでるけど、それでも上巻読み終わるのにあと3,4ヶ月はかかりそうで長い道程だ。けど、なんで今仕事に役立つ予定がないPRML勉強してるんだろという気がするし、たぶん1ヵ月後にでも仕事に活きる勉強をしたほういいんだと思うけど、最近そっち方面への興味が薄れてる。興味なんてコントロールできないので諦めて、半年、1年後にでもPRMLが役に立つようにしたいなと考えてる。

そんなことより、カフェで勉強とかしたことなかったけど、思っていたよりも集中できるし、結構気に入ってる。場所を変えることで気分転換にもなるし、週に1、2回、仕事が終わった後にカフェに行って、2時間くらい本を読むのはいいなと思う。いいカフェあったら教えて欲しい。

Comainu for 中古和文

中古和文版の長単位解析Comainuを作って、リリースした。

https://sourceforge.jp/projects/comainu-emj/


BCCWJ(現代日本語書き言葉均衡コーパス)を作るときに、長単位解析をしていて、現代文に関しては終わったのだけど、そのあと中古和文でもという話があって少し手伝いをしていた。
実際に手伝ってたのはもう半年以上前で、気づいたらコーパス日本語歴史コーパス(CHJ)として公開されていた。

Unidicの中古和文版も公開されてることなので、Comainuの中古和文版も作った。
中古和文のUnidicとComainuを使うと中古和文の長単位解析ができる。

$ echo "いづれの御時にか、女御、更衣あまたさぶらひたまひける中に、いとやむごとなき際にはあらぬが、すぐれて時めきたまふありけり。" | ./script/comainu.pl plain2longout
B       いづれ  イズレ  イズレ  何れ    代名詞                  代名詞  *       *       イズレ  何れ    いづれ
        の      ノ      ノ      の      助詞-格助詞                     助詞-格助詞     *       *       ノ      の      の
        御      オオン  オオン  御      接頭辞                  名詞-普通名詞-一般      *       *       オオントキ      御時    御時
        時      トキ    トキ    時      名詞-普通名詞-副詞可能                  *       *       *       *       *       *
        に      ニ      ニ      に      助詞-格助詞                     助詞-格助詞     *       *       ニ      に      に
        か      カ      カ      か      助詞-係助詞                     助詞-係助詞     *       *       カ      か      か
        、                      、      補助記号-読点                   補助記号-読点   *       *       、      、      、
        女御    ニョーゴ        ニョウゴ        女御    名詞-普通名詞-一般                      名詞-普通名詞-一般      *       *       ニョウゴ        女御    女御
        、                      、      補助記号-読点                   補助記号-読点   *       *       、      、      、
        更衣    コーイ  コウイ  更衣    名詞-普通名詞-サ変可能                  名詞-普通名詞-一般      *       *       コウイ  更衣    更衣
        あまた  アマタ  アマタ  数多    名詞-普通名詞-一般                      名詞-普通名詞-一般      *       *       アマタ  数多    あまた
        さぶらひ        サブライ        サブラウ        侍う    動詞-一般       文語四段-ハ行   連用形-一般     動詞-一般       文語四段-ハ行   連用形-一般     サブラウタマウ  侍う給う-尊敬   さぶらひたまひ
        たまひ  タマイ  タマウ  給う-尊敬       動詞-非自立可能 文語四段-ハ行   連用形-一般     *       *       *       *       *       *
        ける    ケル    ケリ    けり    助動詞  文語助動詞-ケリ 連体形-一般     助動詞  文語助動詞-ケリ 連体形-一般     ケリ    けり    ける
        中      ナカ    ナカ    中      名詞-普通名詞-副詞可能                  名詞-普通名詞-一般      *       *       ナカ    中      中
        に      ニ      ニ      に      助詞-格助詞                     助詞-格助詞     *       *       ニ      に      に
        、                      、      補助記号-読点                   補助記号-読点   *       *       、      、      、
        いと    イト    イト    いと    副詞                    副詞    *       *       イト    いと    いと
        やむごとなき    ヤンゴトナキ    ヤンゴトナイ    やんごとない    形容詞-一般     文語形容詞-ク   連体形-一般     形容詞-一般     文語形容詞-ク   連体形-一般     ヤンゴトナイ    やんごとない    やむごとなき
        際      キワ    キワ    際      名詞-普通名詞-一般                      名詞-普通名詞-一般      *       *       キワ    際      際
        に      ニ      ニ      に      助詞-格助詞                     助詞-格助詞     *       *       ニ      に      に
        は      ワ      ハ      は      助詞-係助詞                     助詞-係助詞     *       *       ハ      は      は
        あら    アラ    アル    有る    動詞-非自立可能 文語ラ行変格    未然形-一般     動詞-一般       文語ラ行変格    未然形-一般     アル    有る    あら
        ぬ      ヌ      ズ      ず      助動詞  文語助動詞-ズ   連体形-一般     助動詞  文語助動詞-ズ   連体形-一般     ズ      ず      ぬ
        が      ガ      ガ      が      助詞-格助詞                     助詞-格助詞     *       *       ガ      が      が
        、                      、      補助記号-読点                   補助記号-読点   *       *       、      、      、
        すぐれ  スグレ  スグレル        優れる  動詞-一般       文語下二段-ラ行 連用形-一般     動詞-一般       文語下二段-ラ行 連用形-一般     スグレル        優れる  すぐれ
        て      テ      テ      て      助詞-接続助詞                   助詞-接続助詞   *       *       テ      て      て
        時      トキ    トキ    時      名詞-普通名詞-副詞可能                  動詞-一般       文語四段-ハ行   連体形-一般     トキメクタマウ  時めく給う-尊敬 時めきたまふ
        めき    メキ    メク    めく    接尾辞-動詞的   文語四段-カ行   連用形-一般     *       *       *       *       *       *
        たまふ  タマウ  タマウ  給う-尊敬       動詞-非自立可能 文語四段-ハ行   連体形-一般     *       *       *       *       *       *
        あり    アリ    アル    有る    動詞-非自立可能 文語ラ行変格    連用形-一般     動詞-一般       文語ラ行変格    連用形-一般     アル    有る    あり
        けり    ケリ    ケリ    けり    助動詞  文語助動詞-ケリ 終止形-一般     助動詞  文語助動詞-ケリ 終止形-一般     ケリ    けり    けり
        。                      。      補助記号-句点                   補助記号-句点   *       *       。      。      。
EOS

Finish


作ったけど、たぶん需要は5人くらい。僕自身はそんなに中古和文には詳しくない。
あと、関係ないけど、中古和文Unidicって東ロボプロジェクトで古文の問題解くのに使われてたりするのかーと不思議なところに需要があるものだなと思った。

日本語WordNet-Affectの構築

日本語版WordNet-Affectが欲しかったので構築した。

WordNet-Affect

WordNet-AffectWordNetに対して感情情報を付与したもので、感情分析などに利用されている言語資源。
WordNet-Affect: an Affective Extension of WordNet

日本語のWordNet-Affectを構築する論文はでているのだけど、構築された言語資源は特に公開はされていないようだったので、この論文に書かれている作り方とは少し違うけど、作ってみた。
Developing Japanese WordNet Affect for Analyzing Emotions

日本語WordNet-Affectの作り方

以下の言語資源を利用して日本語WordNet-Affectを構築する

WordNet1.6とWordNet3.0の2つのバージョンを使うのは、WordNet-AffectがWordNet1.6をベースに構築しているのに対して、日本語WordNetはWordNet3.0をベースに構築されているから。

なので、流れとしては

  1. WordNet3.0版のWordNet-Affectを構築
    • WordNet-Affect、WordNet1.6、WordNet3.0を利用
    • WordNet1.6とWordNet3.0のマッチング:WordNet1.6とWordNet3.0のsynsetを比較し、最も似たsynsetを取得(類似度の計算はnltkのwup_similarityを利用)
  2. 日本語WordNet-Affectを構築
    • WordNet3.0版のWordNet-Affectと日本語WordNetを利用
    • WordNet-Affectのsynsetを利用するだけだと語彙数が少ないので、少し拡張
      • WordNet3.0からsynsetのhyponyms、similar、verb groups、entailment、pertainymを利用してsynsetを拡張
      • derive formsも利用しようかと思ったけど、ノイズが多そうだったのでやめた

コード

以下のコードで日本語WordNet-Affectを作れるのだけど、問題としてはWordNet-AffectのダウンロードがWordNet Domainsからの申請式で少し面倒だということ。

https://github.com/skozawa/japanese-wordnet-affect

# coding: utf-8
import os
os.environ["NLTK_DATA"] = os.getcwd()
import xml.etree.ElementTree as ET
from xml.etree.ElementTree import *
import nltk
from nltk.corpus import WordNetCorpusReader
from sqlalchemy import *
from xml.dom import minidom

WN16 = WordNetCorpusReader(nltk.data.find('resources/wordnet-1.6/dict'))
WN = WordNetCorpusReader(nltk.data.find('resources/WordNet-3.0/dict'))
DB = create_engine('sqlite:///resources/wnjpn.db')

# load Wordnet-Affect synsets
# corpus: a-synset.xml
# return: {
#   'noun': {
#     '05586574': { 'categ': 'electricity', 'pos': 'noun', 'offset16': '05586574' }
#   }, ...
# }
def load_asynsets(corpus):
    tree = ET.parse(corpus)
    root = tree.getroot()

    asynsets = {}
    for pos in ["noun", "adj", "verb", "adv"]:
        asynsets[pos] = {}
        for elem in root.findall(".//%s-syn-list//%s-syn" % (pos, pos)):
            # n#05588321 -> (n, 05588321)
            (p, offset) = elem.get("id").split("#")
            if not offset: continue

            asynsets[pos][offset] = { "offset16": offset, "pos": pos };
            if elem.get("categ"):
                asynsets[pos][offset]["categ"] = elem.get("categ")
            if elem.get("noun-id"):
                # n#05588321 -> 05588321
                noun_offset = elem.get("noun-id").replace("n#", "", 1)
                asynsets[pos][offset]["noun-offset"] = noun_offset
                asynsets[pos][offset]["categ"] = asynsets["noun"][noun_offset]["categ"]
            if elem.get("caus-stat"):
                asynsets[pos][offset]["caus-stat"] = elem.get("caus-stat")

    return asynsets

# Merge WordNet-Affect synsets with WordNet-3.0 synsets
def merge_asynset_with_wn(asynsets):
    pos_map = { "noun": "n", "adj": "a", "verb": "v", "adv": "r" }
    # start from "noun" because other pos use noun-synset
    for pos in ["noun", "adj", "verb", "adv"]:
        for offset in asynsets[pos].keys():
            # Get WordNet-1.6 synset
            synset_16 = WN16._synset_from_pos_and_offset(pos_map[pos], int(offset))
            if not synset_16: continue

            synset_30 = _wn30_synsets_from_wn16_synset(synset_16)
            if not synset_30:
                asynsets[pos][offset]["missing"] = 1
            else:
                (word, p, index) = synset_30.name.split(".")
                asynsets[pos][offset]["word"] = word
                asynsets[pos][offset]["synset"] = synset_30.name
                # db-synset is used to query the japanese wordnet (sqlite)
                asynsets[pos][offset]["db-synset"] = str("%08d-%s" % (synset_30.offset, p))
                asynsets[pos][offset]["offset"] = str("%08d" % (synset_30.offset))
                if "noun-offset" in asynsets[pos][offset]:
                    noffset = asynsets[pos][offset]["noun-offset"]
                    asynsets[pos][offset]["noun-synset"] = asynsets["noun"][noffset]["synset"]

    return asynsets

# Get WordNet-3.0 synset
# Similarity is calculated by wup_similarity
def _wn30_synsets_from_wn16_synset(synset):
    (word, p, index) = synset.name.split(".")
    # ADJ_SAT -> ADJ: DO NOT EXIST ADJ_SAT in wordnet.POS_LIST
    if p == 's': p = 'a'
    synsets = WN.synsets(word, p)
    if len(synsets) == 0: return

    synset_sims = {}
    for i in range(len(synsets)):
        try:
            synset_sims[i] = synset.wup_similarity(synsets[i])
        except (RuntimeError, TypeError, NameError):
            # Set similarity to 0 in case of RuntimeError
            synset_sims[i] = 0
    # Most similar synset index
    index = sorted(synset_sims.items(), key=lambda x:x[1], reverse=True)[0][0]

    return synsets[index]

# Merge asynsets with Japanese WordNet
def merge_asynset_with_wnjpn(asynsets):
    for pos in asynsets.keys():
        for offset in asynsets[pos].keys():
            if not "db-synset" in asynsets[pos][offset]: continue
            db_synsets = _retrieve_similar_synset(WN.synset(asynsets[pos][offset]["synset"]))
            asynsets[pos][offset]["jpnwords"] = _get_jpnword_from_synsets(db_synsets)

    return asynsets

# Retrieve similar synsets from WordNet
def _retrieve_similar_synset(synset):
    if not synset: return []
    similar_db_synsets = [str("%08d-%s" % (synset.offset, synset.pos))]
    searched_words = {}

    synsets = [synset]
    while synsets:
        for synset in synsets:
            searched_words[synset.name] = 1

        nexts = []
        for synset in synsets:
            for syn in _get_similar_synsets(synset):
                if not syn.name in searched_words:
                    similar_db_synsets.append(str("%08d-%s" % (syn.offset, syn.pos)))
                    nexts.append(syn)
        synsets = nexts

    return similar_db_synsets

# Get hyponyms, similar, verb groups, entailment, pertainym
def _get_similar_synsets(synset):
    synsets = []
    synsets.append(synset.hyponyms())
    synsets.append(synset.similar_tos())
    synsets.append(synset.verb_groups())
    synsets.append(synset.entailments())
    for lemma in synset.lemmas:
        synsets.append(map(lambda x: x.synset, lemma.pertainyms()))

    return list(set(reduce(lambda x,y: x+y, synsets)))


# Get japanese word from japanese wordnet
def _get_jpnword_from_synsets(synsets):
    metadata = MetaData(DB, reflect=True)

    jpnwords = []
    sense = Table('sense', metadata)
    sense_rows = DB.execute(sense.select(and_(
        sense.c.lang == 'jpn',
        sense.c.synset.in_(synsets)
    ))).fetchall()
    if len(sense_rows) == 0: return []

    word = Table('word', metadata)
    word_rows = DB.execute(word.select(and_(
        word.c.wordid.in_([ row.wordid for row in sense_rows ])
    ))).fetchall()

    return word_rows

# Output japanese wordnet affect
def output_jpn_asynset(asynsets):
    root = Element('syn-list')
    for pos in asynsets.keys():
        pos_node = SubElement(root, "%s-syn-list" % (pos))
        for offset, asynset in asynsets[pos].items():
            node = SubElement(pos_node, "%s-syn" % (pos))
            for attr in ["offset", "synset", "categ", "caus-stat", "noun-synset", "jpnword", "jpnwordid"]:
                if attr in asynset:
                    node.set(attr, asynset[attr])
            if "jpnwords" in asynset:
                for word in asynset["jpnwords"]:
                    word_node = SubElement(node, "jpn-word", {
                        "wordid": str(word.wordid),
                        "lemma": word.lemma,
                        "pos": word.pos,
                    })

    file = open("jpn-asynset.xml", "w")
    file.write(minidom.parseString(tostring(root)).toprettyxml(encoding='utf-8'))
    file.close()


if __name__ == '__main__':
    asynsets_16 = load_asynsets("resources/wn-affect-1.1/a-synsets.xml")
    asynsets_30 = merge_asynset_with_wn(asynsets_16)
    asynsets_with_jpn = merge_asynset_with_wnjpn(asynsets_30)
    output_jpn_asynset(asynsets_with_jpn)

日本語WordNet-Affect

WordNet-Affectと同じようにXMLで出力してみた、以下のようなものが作成される。
カテゴリはWordNet-Affectのもので、以下のような階層になっていると思う。
https://gist.github.com/skozawa/24051d3bf0ea45dd79d1

<noun-syn-list>
  <noun-syn categ="loyalty" offset="07546389" synset="loyalty.n.02">
    <jpn-word lemma="忠魂" pos="n" wordid="211296"/>
    <jpn-word lemma="忠誠" pos="n" wordid="226847"/>
    <jpn-word lemma="忠心" pos="n" wordid="238518"/>
  </noun-syn>
  ...
</noun-syn-list>
<adv-syn-list>
  <adv-syn categ="favor" caus-stat="stat" noun-synset="favor.n.04" offset="00230444" synset="favorably.r.01">
    <jpn-word lemma="好意的に" pos="r" wordid="164889"/>
    <jpn-word lemma="好ましい" pos="a" wordid="166551"/>
    <jpn-word lemma="色良い" pos="a" wordid="167291"/>
    ...
  </adv-syn>
  ...
</adv-syn-list>


これで日本語で感情分析っぽいことができるようになるはず。
語彙数がまだ少ないので、類似語などで拡張する必要はありそう。
あと、結構日本語が重複して出現するのでもう少し整理しないといけないのかもしれない。


nltkで初めてWordNetを扱ったけど、結構簡単に使えるので便利だった。
WordNetの場所の指定方法が最初わからなかったので、ソース見てみたら、NLTK_DATAという環境変数をみていたので、os.environ["NLTK_DATA"] = os.getcwd() という感じで指定している。