CaboCha(0.69)の解析結果がオウム返しになる【C++, 自然言語処理】

149, 2021-01-01

目次

CaboCha(0.69)の解析結果がオウム返しになる

自然言語処理の勉強でUbuntuにCaboChaをインストールしました。
インストールしてCaboChaは起動するようになったのですが、肝心の解析結果が出力されず、入力テキストのオウム返しになってしまいます。
↓のような状態になります。

$ echo "皿を放る" | cabocha
皿を放る

MeCabは↓のように正常に動作しているようです。

$ echo "皿を放る" | mecab
皿      名詞,一般,*,*,*,*,皿,サラ,サラ
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
放る    動詞,自立,*,*,五段・ラ行,基本形,放る,ホウル,ホール
EOS

結論から言うとこれはCaboChaの仕様かもしれません。

バージョン

↓が関連ソフトのバージョンです。

  • Ubuntu ... 16.04.6 LTS (Xenial Xerus)
  • CaboCha ... 0.69
  • MeCab ... 0.996
  • CRF++ ... 0.58

Ubuntuは次のLTSにしたら?

そうだね

CaboChaのMake設定

CaboChaのビルドは↓のようにconfigureしました。
CRF++が/usr/local/libにあるのでそれの設定と、文字コードをUTF-8にする設定をしています。

$ LDFLAGS="-Wl,-rpath=/usr/local/lib -L/usr/local/lib" ./configure --with-charset=utf8 --enable-utf8-only

リソースファイル

~/.cabocharcの内容は↓になっています。

$ cat ~/.cabocharc
mecabrc = /etc/mecabrc

/etc/mecabrcの内容は↓です。

$ cat /etc/mecabrc
;
; Configuration file of MeCab
;
; $Id: mecabrc.in,v 1.3 2006/05/29 15:36:08 taku-ku Exp $;
;
;dicdir = /var/lib/mecab/dic/debian
dicdir = /var/lib/mecab/dic/ipadic-utf8

; userdic = /home/foo/bar/user.dic

; output-format-type = wakati
; input-buffer-size = 8192

; node-format = %m\n
; bos-format = %S\n
; eos-format = EOS\n

Valgrindによるエラーチェック

Valgrindでエラーが発生していないかチェックしました。
特にエラーは発生していませんでした。

$ echo "皿を放る" | valgrind cabocha
==29263== Memcheck, a memory error detector
==29263== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==29263== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==29263== Command: cabocha
==29263==
皿を放る
==29263==
==29263== HEAP SUMMARY:
==29263==     in use at exit: 72,704 bytes in 1 blocks
==29263==   total heap usage: 30 allocs, 29 frees, 126,220 bytes allocated
==29263==
==29263== LEAK SUMMARY:
==29263==    definitely lost: 0 bytes in 0 blocks
==29263==    indirectly lost: 0 bytes in 0 blocks
==29263==      possibly lost: 0 bytes in 0 blocks
==29263==    still reachable: 72,704 bytes in 1 blocks
==29263==         suppressed: 0 bytes in 0 blocks
==29263== Rerun with --leak-check=full to see details of leaked memory
==29263==
==29263== For counts of detected and suppressed errors, rerun with: -v
==29263== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

-I, -Oオプションによる問題の切り分け

-Iオプションでインプットレイヤー、-Oオプションでアウトプットレイヤーを切り替えることが出来ると公式に書いてあったのでやってみました。
インプットをraw layer、アウトプットをPOS tagged layerにすると↓のように出力されました。

$ echo "皿を放る" | cabocha -I0 -O1
皿      名詞,一般,*,*,*,*,皿,サラ,サラ
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
放る    動詞,自立,*,*,五段・ラ行,基本形,放る,ホウル,ホール
EOS

しかしアウトプットを2にしてChunked layerにすると↓のようなエラーになりました。

$ echo "皿を放る" | cabocha -I0 -O2
chunker.cpp(32) [model_] feature_index.cpp(193) [mmap_.open(model_filename)] mmap.h(153) [(fd = ::open(filename, flag | O_BINARY)) >= 0] open failed:

chuncker.cpp32行目で起きているエラーです。
↓のCHECK_FALSE(model_)で起きてます。

スポンサーリンク

bool Chunker::open(const Param &param) {
  close();

  if (action_mode() == PARSING_MODE) {
    const std::string filename = param.get<std::string>("chunker-model");
    std::vector<const char *> argv;
    argv.push_back(param.program_name());
    argv.push_back("-m");
    argv.push_back(filename.c_str());
    model_ = crfpp_model_new(argv.size(),
                             const_cast<char **>(&argv[0]));
    CHECK_FALSE(model_) << crfpp_model_strerror(model_);
    //    CHECK_FALSE(crfpp_ysize(tagger_) == 2);
    //    CHECK_FALSE(crfpp_xsize(tagger_) == 2);
    //    CHECK_FALSE(std::strcmp("B", crfpp_yname(tagger_, 0)) == 0);
    //    CHECK_FALSE(std::strcmp("I", crfpp_yname(tagger_, 1)) == 0);
  }

  return true;
}

argvの中身を見て見ることにしました。↓のようにコードを改造します。

  if (action_mode() == PARSING_MODE) {
    const std::string filename = param.get<std::string>("chunker-model");
    std::vector<const char *> argv;
    argv.push_back(param.program_name());
    argv.push_back("-m");
    argv.push_back(filename.c_str());

    // ↓これ
    for (auto arg : argv) {
      std::cout << "arg:" << arg << std::endl;
    }

    model_ = crfpp_model_new(argv.size(),
                             const_cast<char **>(&argv[0]));
    CHECK_FALSE(model_) << crfpp_model_strerror(model_);
    //    CHECK_FALSE(crfpp_ysize(tagger_) == 2);
    //    CHECK_FALSE(crfpp_xsize(tagger_) == 2);
    //    CHECK_FALSE(std::strcmp("B", crfpp_yname(tagger_, 0)) == 0);
    //    CHECK_FALSE(std::strcmp("I", crfpp_yname(tagger_, 1)) == 0);
  }

このCaboChaをビルドして先ほどのコマンドを実行すると↓のように表示されます。

$ echo "皿を放る" | cabocha -I0 -O2
arg:cabocha
arg:-m
arg:
chunker.cpp(36) [model_] feature_index.cpp(193) [mmap_.open(model_filename)] mmap.h(153) [(fd = ::open(filename, flag | O_BINARY)) >= 0] open failed:

オプション-mが空になってます。これは空のオプションでしょうか? --helpを見ると↓のようになっています。

 -m, --parser-model=FILE   use FILE as parser model file

引数有りのオプションみたいです。
つまりオプション-mに空文字列(filename.c_str())が指定されてエラーになっているということですね。
なんでfilenameが空になっているんでしょうか?

公式のドキュメントで--parser-modelオプションを調べてみます。

すると↓のような~/.cabocharcの設定例が見つかりました。

# Parser model file name
parser-model  = /usr/local/lib/cabocha/model/dep.ipa.model

# Chunker model file name
chunker-model = /usr/local/lib/cabocha/model/chunk.ipa.model

# NE model file name
ne-model = /usr/local/lib/cabocha/model/ne.ipa.model

どうやらCaboChaは.cabocharcを参照して各種ファイルを使うようになっているみたいです。
そこで↑の設定を~/.cabocharcに追記しました。
再び↓のコマンドを実行します。

$ echo "皿を放る" | cabocha -I0 -O2
arg:cabocha
arg:-m
arg:/usr/local/lib/cabocha/model/chunk.ipa.model
* 0 -1D
皿      名詞,一般,*,*,*,*,皿,サラ,サラ
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
* 1 -1D
放る    動詞,自立,*,*,五段・ラ行,基本形,放る,ホウル,ホール
EOS

↑を見ると今度はしっかり-mオプションにファイルが指定されているのがわかります。
出力も正常にされました。

オウム返しは仕様?

エラーを解決できたので再びCaboChaを実行してみます。
しかし相変わらず↓のようなコマンドはオウム返しになります。

$ echo "皿を放る" | cabocha
皿を放る

ひょっとしてこれは仕様なんでしょうか。
オプションを指定すると↓のように正常に期待した出力が得られます。

$ echo "皿を放る" | cabocha -f0 -I0 -O4
皿を-D
  放る
EOS

$ echo "皿を放る" | cabocha -f1 -I0 -O4
* 0 1D 0/1 0.000000
皿      名詞,一般,*,*,*,*,皿,サラ,サラ
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
* 1 -1D 0/0 0.000000
放る    動詞,自立,*,*,五段・ラ行,基本形,放る,ホウル,ホール
EOS

おそらく仕様でしょうか? オプションを指定するのが正しい使い方みたいです。
ネットの記事ではそのまま入力すれば解析されるという解説の記事が多いみたいです。
公式のドキュメントにもデフォルトでは解析結果がツリーとして表示されると今のところは書かれています。

デフォルトの出力形式を調べてみる

CaboChaのパーサーはparser.cppに定義されています。
ParserImplというクラスがそうです。
このクラスのメンバ変数を見ると↓のようになっています。

  ParserImpl() : tree_(0),
                 output_format_(FORMAT_TREE),
                 input_layer_(INPUT_RAW_SENTENCE),
                 output_layer_(OUTPUT_DEP),
                 charset_(EUC_JP), posset_(IPA) {}

output_format_というのが出力形式を表す整数です。
↑をみると初期値はFORMAT_TREEになってます。ドキュメント通りですね。
この整数はcabocha.hで↓のように定義されています。

enum FormatType {
  FORMAT_TREE         = CABOCHA_FORMAT_TREE,
  FORMAT_LATTICE      = CABOCHA_FORMAT_LATTICE,
  FORMAT_TREE_LATTICE = CABOCHA_FORMAT_TREE_LATTICE,
  FORMAT_XML          = CABOCHA_FORMAT_XML,
  FORMAT_CONLL        = CABOCHA_FORMAT_CONLL,
  FORMAT_NONE         = CABOCHA_FORMAT_NONE
};

このoutput_formatの初期値がどこで変更されているのかと言うと、ParserImpl::open()関数の↓の部分です。

  if (output_layer_ != OUTPUT_DEP) {
    output_format_ = FORMAT_LATTICE;
  }

echo "皿を放る" | cabochaというコマンドでは↑のif文がtrueになり、フォーマットがFORMAT_LATTICEに変更されます。
ここら辺で追いかけるのをやめました。

おいおい

おわりに

今回はなんだかよくわかりませんが、解決できました。
Q&Aサイトで最初聞こうと思ってましたが、ブログのネタになってよかったです。

謎が多かった

気分は眠りの小五郎

スポンサーリンク

投稿者名です。64字以内で入力してください。

必要な場合はEメールアドレスを入力してください(全体に公開されます)。

投稿する内容です。

スポンサーリンク

スポンサーリンク