MATLABでMeCabの品詞分類を活用する

# はじめに
MATLABのText Analytics Toolboxでは日本語の形態素解析器としてMeCabが使われていますが，結果として得られる単語[^1]の品詞（POS，Part of Speech）は15種類しかありません．品詞情報を用いて単語の選別を行う際に，MeCabが提供するきめ細かい品詞情報を（69種）を使えないのはなんとも勿体ないなあと思い，あれこれ探っているうちにそれっぽいことができたので紹介します．

（2020年4月16日追記：　ここで紹介している方法よりも，もっと簡単で便利な方法が見つかりました．よろしかったら[こちら](https://qiita.com/h583/items/e223d9c75b653fda7d34)の記事をご覧ください．）

環境と必要なツールボックス
MATLAB R2020a
Text Analytics Toolbox

<details><summary>MATLABが出力するPOS</summary><div>

'adjective'
'adposition'
'adverb'
'auxiliary-verb'
'coord-conjunction'
'determiner'
'interjection'
'noun'
'numeral'
'pronoun'
'proper-noun'
'punctuation'
'symbol'
'verb'
'other'

</div></details>

<details><summary>MeCabが出力するPOS情報と品詞ID[^2]</summary><div>

|品詞|ID|
|:-----|:--:|
|その他,間投,\\*,\\*| 0|
|フィラー,\*,\*,\*| 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|
|名詞,代名詞,一般,\*| 59|
|名詞,代名詞,縮約,\*| 60|
|名詞,動詞非自立的,\*,\*| 61|
|名詞,特殊,助動詞語幹,\*| 62|
|名詞,非自立,一般,\*| 63|
|名詞,非自立,形容動詞語幹,\*| 64|
|名詞,非自立,助動詞語幹,\*| 65|
|名詞,非自立,副詞可能,\*| 66|
|名詞,副詞可能,\*,\*| 67|
|連体詞,\*,\*,\*| 68|
</div></details>



# MATLABが品詞情報（POS,Part of Speech）を出力する仕組み
形態素解析を行うときにMATLABはMeCabを走らせますが，この際にMeCabが吐き出す品詞ID（69種）を内部の変数に保持します．そしてその情報を関数``` textanalytics.ja.mecabToPOS.m``` に渡してMATLABの出力するPOS（15種）にマッピングしています．このマッピング関数を自前のものに置き換えてしまえば，MeCabの出力をもっと柔軟に扱えるようになります．

実際，MATLABにはこのマッピング関数を置き換えるためのオプションが用意されています．まず，```mecabOptions``` オブジェクトを生成します．このときに，```POSExtractor```プロパティに自前のマッピング関数（ここでは```myPOSExtractor```）のハンドルを指定します．あとは，形態素解析を行うときに```tokenizedDocument```のオプション```'TokenizeMethod'```として，先ほど生成した```mecabOptions``` オブジェクトを渡せば自前のマッピング関数を用いたPOSが出力されます．

```matlab
mecabOpt = mecabOptions('POSExtractor',@myPOSExtractor);
tokens   = tokenizedDocument(str,'TokenizeMethod',mecabOpt);
```
# マッピング関数の作成
それでは，自前のマッピング関数を作成しましょう．MATLABがデフォルトで使うマッピング関数は，```textanalytics.ja.mecabToPOS.m``` ですので，このファイルのありかを```which``` コマンドで探してローカルフォルダにコピーします[^3] （ここではファイル名はmyPOSExtractor.m としてあります．適宜変更してください．）

```matlab
arika = which('textanalytics.ja.mecabToPOS');
system(['copy "' arika '"' ' myPOSExtractor.m'])
```
コピーしたら，最初の行の関数名をファイル名に変更しておきます．

<details><summary>マッピング関数の中身（抜粋）</summary><div>

```matlab

function newpartOfSpeechTags = myPOSExtractor(~,info)
%
%　・・・中略・・・
%
partOfSpeechTags = info.PartOfSpeech;

newpartOfSpeechTags = categorical(partOfSpeechTags, 0:68, ...
    {'interjection',... % 0
     'other',...
     'interjection',...
     'symbol',...
     'symbol',...
     'punctuation',...
     'punctuation',...
%
% ・・・　中略　・・・
%
     'noun',...
     'noun',...
     'noun',...
     'noun',...
     'noun',...
     'noun',...
     'adjective'});
end
```
</div></details>
マッピング関数の中に，縦に長いセル配列があるのに気づかれると思いますが，この部分がMeCabのPOS出力とMATLABのPOSのマッピングを定義している部分になります．セル配列の最初の要素がMeCabが出力するPOS IDの0番「その他,間投,\*,\*」，最後がPOS ID68番「連体詞,\*,\*,\*」にそれぞれ対応しています．

ですので，このセル配列の中身を書き換えることで自前のマッピング関数を作ることができます．例えば，「名詞を抽出したいけれども，接頭辞・接尾辞は要らない」といった場合を考えます．「名詞,接頭,...」「名詞,接尾,...」に対応するMeCabの品詞IDは27～30，50～58になりますので，セル配列の対応する要素[^4]を```'other'```[^5]に書き換えます．こうすることで，これらの品詞IDに対応する単語のPOSが```'other'```として出力されるようになります．

<details><summary> セル配列の中身の置き換え</summary><div>

![Qiita_Mecab_ExtractPOS_fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/541000/7fdb5cfa-7460-1bb7-85be-c9048d297a71.png)

</div></details>

#サンプル
サンプルとして，「お宅のお坊ちゃまお元気？」を形態素解析し，その中から```PartOfSpeech```が```'noun'``` であるものを抽出します．まずは，デフォルト設定．

```matlab
str = "お宅のお坊ちゃまお元気？"
tkn0 = tokenDetails(tokenizedDocument(str));
tkn0(tkn0.PartOfSpeech=='noun',:)
```
|Token|DocumentNumber|LineNumber|Type|Language|PartOfSpeech|Lemma|Entity|  
|:---:|:------------:|---------:|:---|:------:|:-----------|:---:|:-----|
|"お"|1|1|letters|ja|noun|"お"|non-entity|
|"宅"|1|1|letters|ja|noun|"宅"|non-entity|
|"お"|1|1|letters|ja|noun|"お"|non-entity|
|"坊"|1|1|letters|ja|noun|"坊"|non-entity|
|"お"|1|1|letters|ja|noun|"お"|non-entity|
|"元気"|1|1|letters|ja|noun|"元気"|non-entity|

「お」が```noun```として入ってしまっていますね．普通に品詞でフィルターをかけようとするときにノイズとして紛れ込むやっかいなやつです．次に，上で作った自前のマッピング関数を使ってみます．

```matlab
mecabOpt = mecabOptions('POSExtractor',@myPOSExtractor);

tkn1 = tokenDetails(tokenizedDocument(str,'TokenizeMethod',mecabOpt));
tkn1(tkn1.PartOfSpeech=='noun',:)
```
|Token|DocumentNumber|LineNumber|Type|Language|PartOfSpeech|Lemma|Entity|  
|:---:|:------------:|---------:|:---|:------:|:-----------|:---:|:-----|
|"宅"|1|1|letters|ja|noun|"宅"|non-entity|
|"坊"|1|1|letters|ja|noun|"坊"|non-entity|
|"元気"|1|1|letters|ja|noun|"元気"|non-entity|

狙ったとおり，接頭辞の「お」を排除することができました．

# おわりに
```mecabOptions``` の ```POSExtractor``` オプションに自前のマッピング関数を設定することで，MeCabの持つ豊富な品詞情報を利用できました．ここで紹介した方法は，最も単純なやり方ですので，もっと複雑なやり方もできるかと思います．

難点は，MATLAB側でPOSとして受け付ける文字列の内容が固定されているて，どんなに頑張っても15種類の出力しか得られない点でしょうか．力業で，POS 抽出関数を複数（例えば名詞用・形容詞用など）用意しておくという手もありますが，その分形態素解析の回数が増えるのであまりうれしいやり方ではないですね．



[^1]: 正しくは形態素というべきなのでしょうが，実用上「単語」と呼んでしまうことにします．
[^2]: 出展：[MeCab作者のページ](https://taku910.github.io/mecab/posid.html)の一番下にある表（「もっと複雑な例」と書いてありますが，これらがMeCabのデフォルトのPOS IDになります．）
[^3]: もちろん，エディタで開いて別名で保存してもOKです．
[^4]: MeCabの品詞IDは0番から始まっていることに注意．
[^5]: 'other'でなくても良いですが，MATLABのPOSを表す文字列として定義されているもののいずれかである必要があります．