mecab-ipadic-NEologdが、Unicode絵文字に試験的に対応したと聞いて。
(Beer Mug)の読み方を考える(mecab-ipadic-NEologdのUnicode 絵文字対応)
http://www.slideshare.net/overlast/mecab-ipadicneologdpydatatokyo05pub-48560060
これをLucene Kuromojiの辞書に取り込んで、試してみようということで。
なお、最初に試した時、一部の絵文字のコストが高過ぎてLucene Kuromojiに取り込めなかったのですが、@overlastさんに対応していただけました。いつもありがとうございます…。
mecab-ipadic-NEologd in Lucene Kuromoji
まずは、Lucene Kuromojiにmecab-ipadic-NEologdを適用したJARファイルを作ります。
作り方は、すでに以前この作業を自動的に行うbashスクリプトを書いているので、こちらを使用します。
Lucene Kuromojiに対して、mecab-ipadic-neologdの辞書を適用してビルドするbashスクリプトを書きました
http://d.hatena.ne.jp/Kazuhira/20150317/1426606053
以下のコマンドを任意のディレクトリで実行して、スクリプトを取得します。
$ wget https://raw.githubusercontent.com/kazuhira-r/lucene-kuromoji-with-mecab-neologd-buildscript/master/build-lucene-kuromoji-with-mecab-ipadic-neologd.sh $ chmod a+x build-lucene-kuromoji-with-mecab-ipadic-neologd.sh
ビルド実行。
$ ./build-lucene-kuromoji-with-mecab-ipadic-neologd.sh ### [2015-05-28 21:35:32] [main] [INFO] START. #################################################################### applied build options. [MeCab Version] ... mecab-0.996 [MeCab IPA Dictionary Version] ... mecab-ipadic-2.7.0-20070801 [Dictionary CharacterSet] ... utf-8 [mecab-ipadic-NEologd Tag (-N)] ... master [Max BaseForm Length] ... 15 [Lucene Version Tag (-L)] ... lucene_solr_5_1_0 [Kuromoji Package Name (-p)] ... org.apache.lucene.analysis.ja ####################################################################
今のスクリプトでは、実行時の情報をいろいろ出すようになりました(笑)。
このエントリを書いている時点で使用したLuceneのバージョンは5.1.0、mecab-ipadic-NEologdの辞書の日付は20150526です。
時間がかかるので、しばらく待ちます…。
数分待つと、JARファイルがカレントディレクトリにできあがります。
BUILD SUCCESSFUL Total time: 7 seconds -rw-rw-r-- 1 xxxxx xxxxx 28053544 5月 28 21:41 lucene-analyzers-kuromoji-ipadic-neologd-5.1.0-20150526-SNAPSHOT.jar ### [2015-05-28 21:41:25] [main] [INFO] END.
それでは、このJARファイルを使って形態素解析をしてみましょう。
動作確認
動作確認には、Luceneのcoreとanayzers-commonが必要です。
val luceneVersion = "5.1.0" libraryDependencies ++= Seq( "org.apache.lucene" % "lucene-core" % luceneVersion, "org.apache.lucene" % "lucene-analyzers-common" % luceneVersion )
そして、先ほどビルドしたLucene KuromojiのJARファイルを、クラスパスが通った場所に配置します。sbtの場合は、libディレクトリ。
$ cp lucene-analyzers-kuromoji-ipadic-neologd-5.1.0-20150526-SNAPSHOT.jar /path/to/lib/lucene-analyzers-kuromoji-ipadic-neologd-5.1.0-20150526-SNAPSHOT.jar
ちょっと作為的ですが、このようなプログラムを用意。
src/main/scala/org/littlewings/lucene/kuromoji/KuromojiWithNeologd.scala
package org.littlewings.lucene.kuromoji import scala.collection.immutable.ListMap import java.io.StringReader import org.apache.lucene.analysis.TokenStream import org.apache.lucene.analysis.cjk.CJKWidthFilter import org.apache.lucene.analysis.core.{LowerCaseFilter, StopFilter} import org.apache.lucene.analysis.ja._ import org.apache.lucene.analysis.ja.dict.UserDictionary import org.apache.lucene.analysis.ja.tokenattributes.{BaseFormAttribute, PartOfSpeechAttribute, ReadingAttribute, InflectionAttribute} import org.apache.lucene.analysis.tokenattributes.CharTermAttribute object KuromojiWithNeologd { def main(args: Array[String]): Unit = { val texts = Array( "MySQLの🍣🍺問題", "今日は🍖と🐟どっちにしようかな。💊飲んでるし🍵にしよう😞" ) for (text <- texts) { val tokenizer = new JapaneseTokenizer(null, true, JapaneseTokenizer.Mode.NORMAL) tokenizer.setReader(new StringReader(text)) var tokenStream: TokenStream = tokenizer val charTermAttr = tokenStream.addAttribute(classOf[CharTermAttribute]) val baseForm = tokenStream.addAttribute(classOf[BaseFormAttribute]) val partOfSpeech = tokenStream.addAttribute(classOf[PartOfSpeechAttribute]) val reading = tokenStream.addAttribute(classOf[ReadingAttribute]) val inflection = tokenStream.addAttribute(classOf[InflectionAttribute]) tokenStream.reset() val tokenAndAttrs = Iterator .continually(tokenStream.incrementToken()) .takeWhile(identity) .map { _ => ListMap( "token" -> charTermAttr.toString, "原型" -> baseForm.getBaseForm, "品詞" -> partOfSpeech.getPartOfSpeech, "読み" -> reading.getReading, "活用形" -> s"${inflection.getInflectionForm}", "活用型" -> s"${inflection.getInflectionType}" ) } println(s"InputText = $text") println(" Tokenized:") tokenAndAttrs .foreach { taa => println(taa.map { case (k, v) => s"$k: $v" }.mkString(" ", System.lineSeparator + " ", System.lineSeparator)) } println() tokenStream.close() } } }
はてなに書いても、絵文字の部分が数値文字参照になってしまうので…絵文字の部分だけ抜き出すとこう書いています。
このプログラムを実行すると、このような結果になります。
※こちらも画像で
絵文字を理解している上に、読みまで付いてます。すごい!!
補足
先ほどのプログラムを出した時に、「ちょっと作為的」と書きましたが、これについて補足を。
実は、先ほどのプログラムはAnalyzerではなくTokenizerを使用しました。
ここで、Tokenizerではなく以下のようにAnalyzerを使用すると
val texts = Array( "MySQLの🍣🍺問題", "今日は🍖と🐟どっちにしようかな。💊飲んでるし🍵にしよう😞" ) val analyzer = new JapaneseAnalyzer for (text <- texts) { val tokenStream = analyzer.tokenStream("", text) /* val tokenizer = new JapaneseTokenizer(null, true, JapaneseTokenizer.Mode.NORMAL) tokenizer.setReader(new StringReader(text)) var tokenStream: TokenStream = tokenizer */
絵文字が全部なくなってしまいます。
InputText = MySQLの🍣🍺問題 Tokenized: token: mysql 原型: null 品詞: 名詞-固有名詞-一般 読み: マイエスキューエル 活用形: null 活用型: null token: 寿司 原型: 寿司 品詞: 名詞-一般 読み: スシ 活用形: null 活用型: null token: 問題 原型: null 品詞: 名詞-ナイ形容詞語幹 読み: モンダイ 活用形: null 活用型: null InputText = 今日は🍖と🐟どっちにしようかな。💊飲んでるし🍵にしよう😞 Tokenized: token: 今日 原型: null 品詞: 名詞-副詞可能 読み: キョウ 活用形: null 活用型: null token: どっち 原型: null 品詞: 名詞-代名詞-一般 読み: ドッチ 活用形: null 活用型: null token: どっちにしようかな 原型: null 品詞: 名詞-固有名詞-一般 読み: ドッチニシヨウカナ 活用形: null 活用型: null token: 飲む 原型: 飲む 品詞: 動詞-自立 読み: ノン 活用形: 連用タ接続 活用型: 五段・マ行 token: るし 原型: null 品詞: 名詞-固有名詞-一般 読み: ルシ 活用形: null 活用型: null
これについてですが、現在は絵文字が「記号-一般」に分類されるため、JapanesePartOfSpeechStopFilterで除去されてしまいます。
なお、絵文字自体は複数の読みで登録されています。
$ grep 肉 mecab-ipadic-neologd/build/mecab-ipadic-2.7.0-20070801-neologd-20150526/mecab-user-dict-seed.20150526.csv 〜省略〜 🍖,1285,1285,5484,名詞,一般,*,*,*,*,肉,ニク,ニク 🍖,5,5,2024,記号,一般,*,*,*,*,肉,ニク,ニク 🍗,1285,1285,5484,名詞,一般,*,*,*,*,肉,ニク,ニク 🍗,5,5,2024,記号,一般,*,*,*,*,肉,ニク,ニク
複数登録されていますが、記号、一般の方がコストが低いため、こちらが選ばれるようです。
もっと細かく確認されたい方は、Tokenizerの部分を以下の用に定義して(JapaneseAnalyzerと同じものです)、TokenFilterを外したりしてみるとよいでしょう。
val tokenizer = new JapaneseTokenizer(null, true, JapaneseTokenizer.Mode.NORMAL) tokenizer.setReader(new StringReader(text)) var tokenStream: TokenStream = tokenizer tokenStream = new JapaneseBaseFormFilter(tokenStream) tokenStream = new JapanesePartOfSpeechStopFilter(tokenStream, JapaneseAnalyzer.getDefaultStopTags) tokenStream = new CJKWidthFilter(tokenStream) tokenStream = new StopFilter(tokenStream, JapaneseAnalyzer.getDefaultStopSet) tokenStream = new JapaneseKatakanaStemFilter(tokenStream) tokenStream = new LowerCaseFilter(tokenStream)
で、これらについて、@overlastさんに質問してみました。
.@kazuhira_r 質問に複数の疑問が埋め込まれているので勝手ですが分解して回答します。 Q1「同一の絵文字が複数の品詞情報で登録されてるのは文脈によって解釈が変わるから?」A1「YES。画像をご参照ください」 #neologd URL
2015-05-28 00:22:14 via TweetDeck to @kazuhira_r
.@kazuhira_r Q3「例文を再現を試みたが再現できない。名詞,一般になっている部分が記号,一般になる場合がある」A3「例文については今後コスト調整をするので、その結果としてうまくいくようになる例や、いかなくなる例が出てくると思います。」 #neologd
2015-05-28 00:35:59 via TweetDeck to @overlast
.@kazuhira_r 現状では添付画像のようなとても単純な構造な文以外では、大体記号,一般になると思います。今後の結果の改善にご期待ください。回答は以上です。 #neologd URL
2015-05-28 00:37:45 via TweetDeck to @overlast
このエントリの最初の方で参照していた資料を見ていたり、絵文字のコストの調整をお願いしていたのでわかりそうなものですが、なんとも微妙な質問の仕方をしてしまいました…。が、よくわかりました。
難しいですね…。
それにしても、いろいろ勉強になりました。mecab-ipadic-NEologdを更新、そして質問に答えていただいた@overlastさん、ありがとうございました!!
蛇足
絵文字を含んだソースコードは、以下のサイトからペタペタ貼りながら作りました…。
ユニコード6.0以降で使用できる絵文字
http://seesaawiki.jp/w/qvarie/d/%A5%E6%A5%CB%A5%B3%A1%BC%A5%C96.0%B0%CA%B9%DF%A4%C7%BB%C8%CD%D1%A4%C7%A4%AD%A4%EB%B3%A8%CA%B8%BB%FA%28%BF%A9%CA%D4%29
http://seesaawiki.jp/w/qvarie/d/%A5%E6%A5%CB%A5%B3%A1%BC%A5%C9%A4%C7%BB%C8%CD%D1%B2%C4%C7%BD%A4%CA%B3%A8%CA%B8%BB%FA%A1%DA%BC%EA%A1%DB
http://seesaawiki.jp/w/qvarie/d/%A5%E6%A5%CB%A5%B3%A1%BC%A5%C96.0%B0%CA%B9%DF%A4%C7%BB%C8%CD%D1%A4%C7%A4%AD%A4%EB%B3%A8%CA%B8%BB%FA%28%B4%E9%CA%D4%29
http://seesaawiki.jp/w/qvarie/d/%A5%E6%A5%CB%A5%B3%A1%BC%A5%C96.0%B0%CA%B9%DF%A4%C7%BB%C8%CD%D1%A4%C7%A4%AD%A4%EB%B3%A8%CA%B8%BB%FA%28%B4%EF%CA%AA%CA%D4%29
http://seesaawiki.jp/w/qvarie/d/%A5%E6%A5%CB%A5%B3%A1%BC%A5%C96.0%B0%CA%B9%DF%A4%C7%BB%C8%CD%D1%A4%C7%A4%AD%A4%EB%B3%A8%CA%B8%BB%FA%28%C6%B0%CA%AA%CA%D4%29