くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

MeCab

Python3.6+CentOSでMeCabを使う

CentOS上でPythonからMeCabを使いたくなった。Pythonは3.6

で、一筋縄ではいかなかったので記録。

mecab本体のインストール

まず、git と Cのコンパイラ入ってない場合は入れる。

yum -y install git gcc-c++

そしたら、githubからソースを取得。作業用のディレクトリを決めてから。

cd sandbox
git clone https://github.com/taku910/mecab.git

ビルドする。

#本体のビルド
cd mecab/mecab
./configure  --enable-utf8-only
make
make check
sudo make install

#辞書のビルド
cd ../mecab-ipadic
./configure --with-charset=utf8
make
sudo make install

↑の内容はcloneした下記のhtmlファイルに記載されてるインストール手順。

firefox ~/sandbox/mecab/index.html &

そしたら動作確認。mecabでインタラクティブな実行になるので解析したい文字列を適当に入れてみる。

mecab
すもももももももものうち

ただ、この状態だと、「libmecab.so.2」っていうのがリンクしてもらえてなくて、リンクしてもらえるように下記の設定を行う。

#ファイル名は適当だけど拡張子だけは.conf
sudo vi /etc/ld.so.conf.d/mecab.conf
###############↓の一行だけ
/usr/local/lib
###############

sudo ldconfig

本体のインストールは以上。

Pythonから呼べるようにする

※ここから先は仮想環境に切り替えてからやる。

source ~/pyenv/bin/activate

swigのインストール

sudo yum install -y swig

Python用のビルドなんだけど、そのままやると動かないので、setup.pyの一部を書き換える。

cd ~/sandbox/mecab/mecab/python
vi setup.py
######################
#return string.split (cmd1(str))ってなっているところを
#return cmd1(str).split()にする。
######################

python setup.py build
python setup.py install

pip install mecab-python3

mecab-python3のPyPiのページ的にはUbuntuだとapt-getだけでイケルっぽいけど、CentOS用のは用意されてないんですかね。。。

しゃーなし。

ここまででPythonからimportして動くはず。

python
import MeCab
m = MeCab.Tagger ("-Ochasen")
print(m.parse("すもももももももものうち"))

長かった。。。

もうちょっと楽に行けるかと思ってたんだけども。。。

出来るまでに見たエラーは↓

pip install mecab-python3 で。

「unable to execute 'swig': No such file or directory」

import MeCab で。

「ImportError: libmecab.so.2: cannot open shared object file: No such file or directory」

C# NMecabでユーザ辞書を使う

最近、またMecab使ってるんですが、Mecabはそのまま使うと凄く細かく分解されちゃって結構使いにくい。形態素解析したい文章である程度の単語のまとまりのままにしたい場合はユーザー辞書作るのが手っ取り早い。

C#でNMecab使う場合を想定。NMecabはNugetでインストール。

辞書作るときは、本家のサイトからWindows用をダウンロードしてインスト。

そしたら↓の感じのCSVで登録した単語の一覧を作る。例えば「chacha.csv」っていうファイル名で保存した場合。

ジャスミン茶,,,10,名詞,一般,*,*,*,*,独自辞書,ヨミ,ハツオン
ウーロン茶,,,10,名詞,一般,*,*,*,*,独自辞書,ヨミ,ハツオン

フォーマットは↓の感じらしい。自分は原型のところに分かりやすいように自分の辞書っていうのがわかる文字を入れてる。

表層形,左文脈ID,右文脈ID,コスト,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音

そしたら、↓コマンドでコンパイルして辞書ファイルを作る。フォルダはインストールしたフォルダを適当に。

cd C:\Program Files (x86)\MeCab\bin
mecab-dict-index -d "C:\Program Files (x86)\MeCab\dic\ipadic" -u "C:\Program Files (x86)\MeCab\dic\userdic\chacha.dic" -f shift-jis -t utf-8 "C:\Program Files (x86)\MeCab\dic\userdic\chacha.csv"

オプションとかは「mecab-dict-index -h」でヘルプ見た方が早い。

で、読み込みのCSVはSJISでいいと思うんだけど、dicファイルの方はUTF8指定しておかないと、プログラム側で読み込むときにエラーになる。。。あと、CSVファイルの最後は改行入れない方がいい。

dicファイルが出来たらプログラム側の適当なフォルダに入れて、↓の感じでMeCabParamを作る。

MeCabParam mPara = new MeCabParam();
//まず、システム辞書があるフォルダの指定
mPara.DicDir = @"C:\*******\dic\ipadic";
//そしたら、ユーザ辞書ファイルを下の感じで指定
List<string> wkList = new List<string>();
//複数指定可
wkList.Add(@"C:\*******dic\userdic\chacha.dic");
//MeCabParamにセット
mPara.UserDic = wkList.ToArray();

↑はプログラム内でやってるけど、app.configとかで外だしもできるっぽ。

で、ほぼやりたい感じには上記でなるんだけど、コストの指定とかもう少し理解したいところ。まだ困ってないのは、大した解析やってないからでしょうし。。。

C#でMeCabを使う

NuGetにMeCabがあった。テンションあがったので実装を試してみる。

NuGetでインストールしたら、using。

using NMeCab;

そしたら↓の感じ。

public void ana()
{
    MeCabParam mPara = new MeCabParam();
    //辞書ファイルがあるフォルダを指定(NuGetで入れれば勝手に入る)
    mPara.DicDir = @"*****\dic\ipadic";

    MeCabTagger mTagger = MeCabTagger.Create(mPara);

    string sentence = "すもももももももものうち";//解析する文字列
    MeCabNode node = mTagger.ParseToNode(sentence);
    while (node != null)
    {
        if (node.CharType > 0)
        {
            Console.WriteLine("{0}\t{1}", node.Surface, node.Feature);
        }
        node = node.Next;
    }
}

実行結果

すもも	名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
うち	名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ

すばらしい。前はRubyとか使ってゴニョゴニョしてたけど、いろいろできそう。

性能的には、CPU:2.9GHz(4コア),RAM:8GのWindows7で、 とあるニュース記事一本の文章を10000回解析して、51.34秒。 一回あたり、0.0051秒ってところでしょうか。

これだけだとアレなので、少し手を加える。MeCabはすばらしいんだけど、 戻りのインターフェースがCSV形式の文字列なので、いろいろ扱いにくい。 なので、結果を扱いやすくするためのクラスを下の感じで作る。 ↓のクラスの位置のプロパティは文章中の何番目に出てきたかを保持しておくために 追加してます。N-Gram解析とかで使いやすくするため。

public class MecabResult
{
    //結果のリスト
    public List<MecabResultItem> nodes { get; set; }
    //コンストラクタ
    public MecabResult(MeCabNode pnode)
    {
        nodes = new List<MecabResultItem>();
        int itempos = 0;
        while (pnode != null)
        {
            MecabResultItem addItem = new MecabResultItem();
            if (pnode.CharType > 0)
            {
                addItem.表層形 = pnode.Surface;
                string[] tmpStrs = pnode.Feature.Split(',');
                if (tmpStrs.Length == 9)
                {
                    addItem.品詞 = tmpStrs[0];
                    addItem.品詞細分類1 = tmpStrs[1];
                    addItem.品詞細分類2 = tmpStrs[2];
                    addItem.品詞細分類3 = tmpStrs[3];
                    addItem.活用形 = tmpStrs[4];
                    addItem.活用型 = tmpStrs[5];
                    addItem.原形 = tmpStrs[6];
                    addItem.読み = tmpStrs[7];
                    addItem.発音 = tmpStrs[8];
                }
                addItem.位置 = itempos;
                nodes.Add(addItem);
            }
            itempos++;
            pnode = pnode.Next;
        }
    }
    public partial class MecabResultItem
    {
        public string 表層形 { get; set; }
        public string 品詞 { get; set; }
        public string 品詞細分類1 { get; set; }
        public string 品詞細分類2 { get; set; }
        public string 品詞細分類3 { get; set; }
        public string 活用形 { get; set; }
        public string 活用型 { get; set; }
        public string 原形 { get; set; }
        public string 読み { get; set; }
        public string 発音 { get; set; }
        public int 位置 { get; set; }
    }
}

こうしておくとどーなるかと言うと、ラムダ式で検索とか出来ちゃうわけです。↓の感じ。

public void ana()
{
    MeCabParam mPara = new MeCabParam();
    //辞書ファイルがあるフォルダを指定(NuGetで入れれば勝手に入る)
    mPara.DicDir = @"****\dic\ipadic";

    MeCabTagger mTagger = MeCabTagger.Create(mPara);

    string sentence = "すもももももももものうち";//解析する文字列
    MeCabNode node = mTagger.ParseToNode(sentence);
    
    MecabResult mr = new MecabResult(node);//ココで初期化
    //名詞に絞って表示する
    foreach(var tmp in mr.nodes.Where(n=>n.品詞 == "名詞"))
    {
        Console.WriteLine(tmp.表層形);
    }
}

Rでワードクラウド その2

ワードクラウドを作るおおまか手順。
  1. テキストを単語にバラし、単語の出現数(率)を集計する
  2. Rで集計結果を読み込んでwordcloudでプロット
単語の出現数を求められればあとはそれをwordcloudに喰わせるだけ。

単語をバラすのにMeCabを使う。MeCabをRから呼び出すRMeCabのライブラリを使ってもできる。ただ、簡単な文章はよいのだけれども、多少量があるとなんかの制限にかかって動かない。なので、Rubyとかテキトーなスクリプトなりプログラムなりで自分でカウントする方がてっとりばやい。

私はMeCabに解析させたいテキストファイルを食わせて結果を取得⇒カウントしてCSVに保存⇒Rで読み込んでプロットしてる。あとで色々使い回したいから、MySqlにMeCabの解析結果をつっこんで、Rubyで拾って集計してる。集計部分はまた今度載せるとして、Rで読み込みプロットする部分。

↓の感じで「単語,出現数」の集計結果のCSVを準備
・・・
項,2
通り,3
否定,2
対,61
懐疑,3
ECB,17
国債,11
購入,5
・・・
そしたらRでCSVを読み込む
data1 <- read.csv("C:\\R\\wordcount.csv",header=FALSE,row.names=1)
このまま使ってもいいけど、出現数が10回以上の単語だけ出すみたいな事をしたい場合は↓の感じで絞る
data1sub <- subset(data1, data1[, 1] >= 10)
プロットする際の色を決める
Color <- brewer.pal(9, "Blues") #文字色
Color <- Color[-(1:3)] #薄い色を消す
プロット
wordcloud(row.names(data1sub), data1sub[, 1], scale=c(6,.2),random.order = T, rot.per = .15, colors = Color)
ライブラリの読み込みからまとめて書くと↓の感じ
library("RColorBrewer")
library("wordcloud")
data1 <- read.csv("C:\\R\\wordcount.csv",header=FALSE,row.names=1)
data1sub <- subset(data1, data1[, 1] >= 10)
Color <- brewer.pal(9, "Blues") #文字色
Color <- Color[-(1:3)] #薄い色を消す
wordcloud(row.names(data1sub), data1sub[, 1], scale=c(6,.2),random.order = T, rot.per = .15, colors = Color)

Rでワードクラウド作るのはいい感じなんだけど画像なのがまれに傷。FlashとかJQueryのプラグインとかでなんかかっこいいのないかなーって探してみたけど、グッとくるものがない。さらにJSONとかでデータ渡せると胸が熱くなると思う。じゃ作れとか言われそうですが。 見た感じはRのこのwordcloudっぽく。ホイールクリクリしたら中まで入っていけたり、文字をクリックでリンクしてみたりとかを夢見てみる。

MecabをRubyから呼び出すお手軽方法

Rubyから呼び出す方法はいくつかあるみたいだけど、設定とかハマりまくった覚えがあるので、お手軽版。ただし、一番遅いと思う。まぁ、膨大なデータを解析するわけじゃなければ気にならない遅さなので十分かと。Mecabをコマンドプロンプトで実行するのと同じ事をRubyで書くだけです。コマンドの文字列作ってopen3で投げる感じ。後はその結果を受け取ってるだけです。

require "open3"

mpath       = "\"C:\\Program Files\\MeCab\\bin\\mecab.exe\"" # MeCabへのパス
arg         = ""#Mecabにオプション渡したい場合
target_file = "hoge.txt"#解析対象のファイル
cmd_string = [mpath, arg, target_file].join(" ")

out, err, status = Open3.capture3(cmd_string)
out.split("\n").each  do |wkStr|
  p wkStr.split("\t")
end

↓実行結果サンプル

["この", "連体詞,*,*,*,*,*,この,コノ,コノ"]
["サイト", "名詞,一般,*,*,*,*,サイト,サイト,サイト"]
["について", "助詞,格助詞,連語,*,*,*,について,ニツイテ,ニツイテ"]
["EOS"]
["個人", "名詞,一般,*,*,*,*,個人,コジン,コジン"]
["情報", "名詞,一般,*,*,*,*,情報,ジョウホウ,ジョーホー"]
["保護", "名詞,サ変接続,*,*,*,*,保護,ホゴ,ホゴ"]

わかち書きにしたい場合はオプションに「-O wakati」をセットしてあげる。

・・・
arg         = "-O wakati"#Mecabにオプション渡したい場合
・・・

実行結果サンプル↓

["この サイト について "]
["個人 情報 保護 "]

わかち書きをさらに単語にばらしたい場合はwkStr.split(' ')みたいにして空白でsplitしてあげる。

ちなみに、参考にしたサイトではopen3じゃなくて、「IO.popen」を使ってた。↓の感じ。

io = IO.popen(cmd_string, "r")
until io.eof?
  word_list.concat io.gets.split(' ')
end

これで問題なく動くように見えるけど、数百ファイル以上を処理しようとすると、何故かたまに処理が止まる。理由は不明。ふらっと調べた感じではバグ?らしい。なので今は「open3」使えという事らしいので、素直に従って書いてます。

あとは、上記の結果の戻りをHashなり配列なりで戻すClassにしておけば、色々使い回せます。

問合せ