skimemo


Laravel-20181218

_ ateliee/mecabをmecabにパスを通さなくても良くするのとfieg/bayesのmecab対応

これは Laravel開発中に日々学んだこと Advent Calendar 2018 の17日目の記事です。

昨日はベイジアンフィルタのライブラリであるfieg/bayesをDB対応しました。そして次に必要なのは当然日本語対応(形態素分析による単語分解)です。
形態素分析エンジンは、Chasenよりも高速と謳うMecabにしてみました。

Mecabのインストールはググればすぐ分かるので適当に。。。
LaravelからMecabを使用するにあたっては、ateliee/mecabを使うことにしました。php-mecabの導入も検討しましたが、コンパイルして入れるのが標準的なようなので、現プロジェクトの環境構築ポリシーに反することから見送りました。
ateliee/mecab は、導入するだけでmecabの解析結果を容易に取得できるようにしてくれるライブラリです。

ただこのライブラリには一点使いづらい所があります。mecabを「mecab」で呼び出している所です。

  1
    $command = array('mecab'); 

つまり、パスが通っていないといけないのです。アプリの挙動が別の所で設定される環境変数に依存するというのも気持ち悪いので、.envファイルからフルパスを取れるようにしてみます。

というわけで、オリジナルを継承した以下のクラスを作ります。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
<?php
namespace App\Http\Bayes;
 
use meCab\meCabWord;
 
class MeCab extends \meCab\meCab {
 
    private $tmp_file;
    private $dictionary;
 
    static private $dictionary_dir;
 
    public function __construct() {
        parent::__construct();
        $this->tmp_file = tempnam(sys_get_temp_dir(),'mecab');
    }
 
    /**
     * @param $text
     * @return meCabWord[]|null
     * @throws \Exception
     */
    public function analysis($text){
        if(file_put_contents($this->tmp_file,$text)){
            $command = array(env('MECAB_CMD','mecab'));
            if($this->dictionary){
                $command[] = '-d '.self::$dictionary_dir.$this->dictionary;
            }
            $this->exec(implode(' ',$command).' '.$this->tmp_file,$res);
            logger()->info(__METHOD__ . ':' . print_r($res, true));
            if($res && (count($res) > 0)){
                $words = array();
                foreach($res as $k => $r){
                    if($r == 'EOS' && count($res) >= ($k + 1)){
                        break;
                    }
                    $words[] = new meCabWord($r);
                }
                return $words;
            }else{
                throw new \Exception(sprintf('Error text analysis.'));
            }
        }else{
            throw new \Exception(sprintf('Error write tmp file in %s',$this->tmp_file));
        }
    }
 
    /**
     * @param $command
     * @param $res
     * @return string
     */
    private function exec($command,&$res){
        if($text = exec($command,$res)){
        }
        return $text;
    }
}

本当は25行目だけをなんとかしたいのですが、仕方が無いので対象メソッドと関連メソッド及びプロパティをまるっと用意します。
あとは\meCab\meCabの代わりにこのApp\Http\Bayes\MeCabを使えばOKです。

次に、このMeCabを使った形態素分析を、ベイジアンフィルタで使えるようにします。
Fieg\Bayes\TokenizerInterfaceは、拡張される事を前提に設計されていますので、これのimplementを作ります。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
<?php
namespace App\Http\Bayes;
 
use Fieg\Bayes\TokenizerInterface;
 
class MecabTokenizer implements TokenizerInterface {
 
    public function tokenize($string) {
        
        $mecab = new MeCab();
        $result = $mecab->analysis($string);
        $retval = [];
        foreach($result as $object){
            if( $object->getSpeech()=="名詞" or $object->getSpeech()=="形容詞" or (mb_strlen($object->getText())>1 and $object->getSpeech()=="助詞" and $object->getSpeechInfo()[0]=="副助詞") ){
                $retval[] = $object->getText();
            }
        }
 
        return $retval;
    }
}

てにをはまで全部拾ってしまわないよう、品詞に制限をつけて拾うようにしました。

これを以下のように使えば、日本語をベイジアンフィルタに対して使用できます。

  1
  2
  3
  4
    $text = "今日の天気は日本晴れ?";
    $tokenizer = new MecabTokenizer();
    $classifier = new Classifier($tokenizer);
    $result = $classifier->classify($text); 

おわり


Last-modified: 2018-12-18 (火) 13:53:14 (215d)