(GAEで日本語の形態素解析を試してみる)第3回 Kuromojiを使ってみる

GoogleAppEngineの勉強中です。
3回に分けて、GAEで日本語の形態素解析を試してみたことを書いています。
第1回 lucene-gosenを使ってみる
第2回 lucene-gosenのTokenFilterを試す
第3回 Kuromojiを使ってみる

今回は、第3回です。

使っている環境
Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1

Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

kuromoji-0.7.7


(1)はじめに
もともと、kuromoji に関しては、GoogleAppEngineで動かせないと思っていました。
なぜかというと、GoogleAppEngineにアップロードするファイルサイズには、10MBまでという制限があるから。
kuromojiの場合、Jarファイルのサイズが、11MBなので、これだと制限にひっかかるな~、と思っていました。

だけど、Eclipse上でアプリのwarフォルダの中を見ていたら、libフォルダの中に入っているライブラリ(jarファイル)の中に、サイズが20MB超えてるものがあった。
あれ、もしかしてjarファイルの場合、10MB超えていても大丈夫なのかな、って思って、kuromojiを試してみたら、動いた!


(2)kuromojiダウンロード
こちらのサイトに行くと、
kuromoji – japanese morphological analyzer

「Downloading」というところに、GitHubへのリンクがあるので、それをクリック。
kuromoji-0.7.7.tar.gzをダウンロードし、展開します。中にlibフォルダがあり、その中にkuromoji-0.7.7.jarがあります。

このjarファイルをクラスパスに通しておけば、準備完了。


(3)コーディングの流れ
第2回にもかいたんだけど、まずはじめに、注意点です。
僕はGoogleAppEngine上で動かしています。
で、今回テスト用に作成するものは、HTTPのGETリクエストに「q=解析したい文章」といったパラメータを付けることで、パラメータqに設定した文章を解析して、ブラウザに結果を返すように作っています。

そのため、以下のようにして、getリクエストのパラメータqを取得して、変数contentに格納するようにしています。

String content = req.getParameter("q");

また、結果をブラウザに返却するため、以下のメソッドで出力しています。

resp.getWriter().println("出力内容をここに書く");

このあたりはGAEならではの作法になります。

それを踏まえて、kuromojiの使い方をみていきます。

まず、一番シンプルなコーディングのサンプルは、kuromojiのサイトに載っています。
kuromoji – japanese morphological analyzer

また、その他もろもろの情報は以下のサイトを参考にしました。
(参考)Java製形態素解析器「Kuromoji」を試してみる
(参考)sample/kuromoji-sample/src/main/java/jp/mwsoft/sample/kuromoji/SimpleTokenizerSample.java at master · mwsoft/sample · GitHub
(参考)sample/kuromoji-sample/src/main/java/jp/mwsoft/sample/kuromoji/TokenizerModeSample.java at master · mwsoft/sample · GitHub

以下のような形で、Tokenizerクラスのtokenizeメソッド実行時に、形態素解析されます。(ここでは、contentに解析したい文章を入れています)

        Builder builder = Tokenizer.builder();
        Tokenizer tokenizer = builder.build();
        List<Token> tokensNormal = tokenizer.tokenize(content);

上記では、解析結果がtokensNormalの中に入っていて、取り出すときは以下のような感じ。

        String words="";

        for (Token token : tokensNormal){
            words = words +
                    token.getSurfaceForm() + " | "; 
        }

        words = words + "\n";

        resp.getWriter().println(words);

TokenクラスのgetSurfaceFormメソッドで、解析された単語を取出せます。

上記の場合、たとえば「日本経済新聞でモバゲーの記事を読んだ。」という文章を解析した結果が、
「日本経済新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 |」

といった感じで表示される。

Tokenクラスには、ほかにも情報取得用のメソッドが用意されていて、以下のようにすると、いろいろ取得できる。

String words="";

for (Token token : tokens){
    words = words +
            "--------------------------------------------------" + "\n" + 
            "allFeatures : " + token.getAllFeatures() + "\n" + 
            "partOfSpeech : " + token.getPartOfSpeech() + "\n" + 
            "position : " + token.getPosition() + "\n" + 
            "reading : " + token.getReading() + "\n" + 
            "SurfaceForm : " + token.getSurfaceForm() + "\n" + 
            "allFeaturesArray : " + Arrays.asList(token.getAllFeaturesArray()) + "\n" + 
            "辞書にある言葉? : " + token.isKnown() + "\n" + 
            "未知語? : " + token.isUnknown() + "\n" + 
            "ユーザ定義? : " + token.isUser() + "\n"; 
}
resp.getWriter().println(words);

この場合、取得結果はこんな感じで表示されます。

--------------------------------------------------
allFeatures : 名詞,固有名詞,組織,*,*,*,日本経済新聞,ニホンケイザイシンブン,ニホンケイザイシンブン
partOfSpeech : 名詞,固有名詞,組織,*
position : 0
reading : ニホンケイザイシンブン
SurfaceForm : 日本経済新聞
allFeaturesArray : [名詞, 固有名詞, 組織, *, *, *, 日本経済新聞, ニホンケイザイシンブン, ニホンケイザイシンブン]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
allFeatures : 助詞,格助詞,一般,*,*,*,で,デ,デ
partOfSpeech : 助詞,格助詞,一般,*
position : 6
reading : デ
SurfaceForm : で
allFeaturesArray : [助詞, 格助詞, 一般, *, *, *, で, デ, デ]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
    :   :   :
    ( 中略 )

getAllFeaturesメソッドで取得できる情報ですが、
kuromojiでは、MeCab-IPADIC辞書を使っているようで、MeCabの出力フォーマットになっているようです。

品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音

MeCabのフォーマットは、以下のサイトの「使い方」-「とりあえず解析してみる」のところに載っています。
MeCab: Yet Another Part-of-Speech and Morphological Analyzer

さて、kuromojiには3つのモードが用意されています。
kuromojiのホームページによると、

・Normal – regular segmentation
・Search – use a heuristic to do additional segmentation useful for search
・Extended – similar to search mode, but also unigram unknown words (experimental)

Searchモードでは、「日本経済新聞」といった単語が、「日本, 経済, 新聞」といった感じに細かく分割されます。
Lucene/Solrで全文検索するときに、「 ”日本”という検索ワードで検索したときに、”日本経済新聞”も引っかかる」 ようになるみたいです。

Extendedモードでは、「辞書に登録されていない単語については、unigramで解析されて、1文字ずつ分割されます。
たとえば、「Lucene」という単語は、「L, u, c, e, n, e」と分割されます。

で、モードの切り替えは、builder.build()する前に、builder.mode()でモードをセットします。(以下では、tokenizerを使いまわしています)

        Builder builder = Tokenizer.builder();

        // Normalモードで解析
        builder.mode(Mode.NORMAL);
        Tokenizer tokenizer = builder.build();
        List<Token> tokensNormal = tokenizer.tokenize(content);

        // Searchモードで解析
        builder.mode(Mode.SEARCH);
        tokenizer = builder.build();
        List<Token> tokensSearch = tokenizer.tokenize(content);

        // Extendsモードで解析
        builder.mode(Mode.EXTENDED);
        tokenizer = builder.build();
        List<Token> tokensExtended = tokenizer.tokenize(content);

ここまでの内容を踏まえて、次に、作成したサンプルのソースを書いておきます。


(4)ソースコード
実際に作成したサンプルアプリのソースコードと、動かした出力結果を書いておきます。
まずは、ソースコードから。

package sample;

import java.io.*;
import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.atilika.kuromoji.Token;
import org.atilika.kuromoji.Tokenizer;
import org.atilika.kuromoji.Tokenizer.Builder;
import org.atilika.kuromoji.Tokenizer.Mode;

@SuppressWarnings("serial")
public class TestKuromoji_Servlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {

        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/plain; charset=UTF-8");
        resp.getWriter().println("TestKuromoji テスト");

        // パラメータ取得
        String content = req.getParameter("q");
        if (content == null || content.length() == 0){
            content = "";
        }

        resp.getWriter().printf("content= %s%n", content);

        Builder builder = Tokenizer.builder();

        // Normalモードで解析
        builder.mode(Mode.NORMAL);
        Tokenizer tokenizer = builder.build();
        List<Token> tokensNormal = tokenizer.tokenize(content);

        // Searchモードで解析
        builder.mode(Mode.SEARCH);
        tokenizer = builder.build();
        List<Token> tokensSearch = tokenizer.tokenize(content);

        // Extendsモードで解析
        builder.mode(Mode.EXTENDED);
        tokenizer = builder.build();
        List<Token> tokensExtended = tokenizer.tokenize(content);

        // 解析結果の表示(SurfaceFormのみ)
        resp.getWriter().println("NORMALモード(要約表示)=====================================");
        resp.getWriter().println(disp_SurfaceForm(tokensNormal));
        resp.getWriter().println("SEARCHモード(要約表示)=====================================");
        resp.getWriter().println(disp_SurfaceForm(tokensSearch));
        resp.getWriter().println("Extendsモード(要約表示)====================================");
        resp.getWriter().println(disp_SurfaceForm(tokensExtended));

        // 解析結果の表示(全パラメータ)
        resp.getWriter().println("NORMALモード(全パラメータ表示)================================");
        resp.getWriter().println(disp_all(tokensNormal));

    }

    public static String disp_SurfaceForm(List<Token> tokens) {
        String words="";

        for (Token token : tokens){
            words = words +
                    token.getSurfaceForm() + " | "; 
        }

        words = words + "\n";
        return words;
    }

    public static String disp_all(List<Token> tokens) {
        String words="";

        for (Token token : tokens){
            words = words +
                    "--------------------------------------------------" + "\n" + 
                    "allFeatures : " + token.getAllFeatures() + "\n" + 
                    "partOfSpeech : " + token.getPartOfSpeech() + "\n" + 
                    "position : " + token.getPosition() + "\n" + 
                    "reading : " + token.getReading() + "\n" + 
                    "SurfaceForm : " + token.getSurfaceForm() + "\n" + 
                    "allFeaturesArray : " + Arrays.asList(token.getAllFeaturesArray()) + "\n" + 
                    "辞書にある言葉? : " + token.isKnown() + "\n" + 
                    "未知語? : " + token.isUnknown() + "\n" + 
                    "ユーザ定義? : " + token.isUser() + "\n"; 
        }

        return words;
    }

}

次に、動かしてみた結果を載せておきます。

「日本経済新聞でモバゲーの記事を読んだ。2013年のLucene/Solrは検索エンジンです。」という文章を、形態素解析させてみました。以下のような感じでgetリクエストを送った後に帰ってきた結果になります。

http://URL/?q=日本経済新聞でモバゲーの記事を読んだ。2013年のLucene/Solrは検索エンジンです。

量が多いので、途中を省略してあります。

TestKuromoji テスト
content= 日本経済新聞でモバゲーの記事を読んだ。2013年のLucene/Solrは検索エンジンです。
NORMALモード(要約表示)======================================
日本経済新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 | 2013 | 年 | の | Lucene | / | Solr | は | 検索 | エンジン | です | 。 | 

SEARCHモード(要約表示)======================================
日本 | 経済 | 新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 | 2013 | 年 | の | Lucene | / | Solr | は | 検索 | エンジン | です | 。 | 

Extendsモード(要約表示)======================================
日本 | 経済 | 新聞 | で |  |  |  | ー | の | 記事 | を | 読ん | だ | 。 | 2 | 0 | 1 | 3 | 年 | の | L | u | c | e | n | e | / | S | o | l | r | は | 検索 | エ | ン | シ | ゙ | ン | です | 。 | 

NORMALモード(全パラメータ表示)======================================
--------------------------------------------------
allFeatures : 名詞,固有名詞,組織,*,*,*,日本経済新聞,ニホンケイザイシンブン,ニホンケイザイシンブン
partOfSpeech : 名詞,固有名詞,組織,*
position : 0
reading : ニホンケイザイシンブン
SurfaceForm : 日本経済新聞
allFeaturesArray : [名詞, 固有名詞, 組織, *, *, *, 日本経済新聞, ニホンケイザイシンブン, ニホンケイザイシンブン]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
allFeatures : 助詞,格助詞,一般,*,*,*,で,デ,デ
partOfSpeech : 助詞,格助詞,一般,*
position : 6
reading : デ
SurfaceForm : で
allFeaturesArray : [助詞, 格助詞, 一般, *, *, *, で, デ, デ]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
--------------------------------------------------
 :  :
 (省略)
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s