表記ゆれ対策を力業で実装

# はじめに
表記ゆれとは、「どらねこ」「どら猫」「ドラネコ」のような、読み方も意味も同じながら、書き方が異なっているものを指します。文書の中にこの表記ゆれが混ざっていると、例えば単語の頻度を調べた際に値が分散してしまうので、どれか1つの表記に統一することが望ましいです。とはいえ、この処理が非常に難しい。[strrep](https://jp.mathworks.com/help/matlab/ref/strrep.html)という、文字列置換の関数はあるのですが、1単語1単語手入力なんてできないので、良い方法がないかなと考えておりました。

# 今回の表記ゆれ対策手法

今回[neologd-solr-elasticsearch-synonyms : Elasticsearch と Solr 用の日本語名詞のシノニムファイル](https://github.com/neologd/neologd-solr-elasticsearch-synonyms/blob/master/README.ja.md)なるものを利用させていただきました。

>このシノニムファイルは mecab-ipadic-NEologd と共通な大量の名詞の表記ゆれ文字列を含んでいます。
>もしも名詞や新語、固有表現のシノニム(同義語)を定義したいと思った場合は、まずこのシノニムファイルを試すのがベターな選択のひとつです。

今回、力業で実装したと書きました。本当は、Pythonが使えれば[neologdn](https://github.com/ikegami-yukino/neologdn)というテキストの初期化関数があります。しかしながら、私はPythonを使いこなせないので、MATLABベースで自力で表記ゆれ対策のコードを書き進めました。これが力業の所以です。次からのセクションで、実際の進め方やコードも公開します。たたき台になれば...というレベルなので、改善策あればコメント頂けたら幸いです。

# やったこと
「どら猫とどらねことドラネコ」→「どら猫とどら猫とどら猫」に変換できる関数を作る。

# 環境
MATLAB R2020a
Text Analytics Toolbox
neologd-solr-elasticsearch-synonyms

# 手順
## １．シノニムファイルをダウンロード
[neologd-solr-elasticsearch-synonyms : Elasticsearch と Solr 用の日本語名詞のシノニムファイル](https://github.com/neologd/neologd-solr-elasticsearch-synonyms/blob/master/README.ja.md)からダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521542/83d4d3b6-92f3-1399-b259-b3e0f790a78a.png)


## ２．シノニムファイルの解凍
archiveフォルダの中にneologd-synonyms.20160323.txt.xzというファイルがあるので、解凍しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521542/f93c58b6-a705-2b45-f7bd-6c442ead08e1.png)
ちなみにシノニムファイルを開くとこんな感じになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521542/a0afd708-c991-4e44-c9b4-97cabe6f15b2.png)

筆者の理解では、1列目が正規表現で、2列目以降がシノニムの認識です。ということで、2列目以降の表記を見つけたら、1列目の書き方にしてあげるスクリプトを書いていきたいと思います。

## ３．シノニムファイルをMATLABに読み込む


まず、シノニムファイルをMATLABに読み込みます。

```matlab
synonymtable = readtable('neologd-synonyms.20160323.txt');
```

最初の10行を表示させてみましょう。「,」で区切られた単語が各列に格納されていることがわかります。

```matlab
head(synonymtable,10)
```
| |x_____|x______1|Var3|Var4|Var5|Var6|Var7|Var8|Var9|Var10|Var11|Var12|Var13|Var14|Var15|Var16|Var17|Var18|Var19|Var20|Var21|Var22|Var23|Var24|Var25|Var26|Var27|Var28|Var29|Var30|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|1|'あいの子'|'アイノ子'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|2|'あいまい度'|'あいまいど'|'アイマイド'|'アイマイ度'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|3|'あい路'|'アイ路'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|4|'あおか'|'アオカ'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|5|'あお向け'|'あお向'|'アオ向'|'アオ向ケ'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|6|'あかまんま'|'アカマンマ'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|7|'あかんべえ'|'アカンベエ'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|8|'あきい'|'アキイ'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|9|'あきず'|'アキズ'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|
|10|'あきらめ'|'アキラメ'|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|''|



後々使えるように型を変換しておきます。


```matlab
synonymtable = table2array(synonymtable);
```

## ４．NEologdのユーザー辞書を使ってトークン化

次に、解析したい文書と、NEologdのユーザー辞書を用意します。MATLAB上でのNEologdの使い方は[前回の記事](https://qiita.com/aoimidori/items/85aa3a5791ca86a46217)でまとめました。


```matlab
str = ["どら猫とどらねことドラネコ"];
userdic = 'NEologd.20200315.dic';
mOptions = mecabOptions('UserModel', userdic);
```


ユーザー辞書を使ってトークン化を行います。トークン化された結果を表示させてみましょう。


```matlab
documents = tokenizedDocument(str,"TokenizeMethod",mOptions);
td = tokenDetails(documents)
```
| |Token|DocumentNumber|LineNumber|Type|Language|PartOfSpeech|Lemma|Entity|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|1|"どら猫"|1|1|letters|ja|noun|"どら猫"|non-entity|
|2|"と"|1|1|letters|ja|coord-conjunction|"と"|non-entity|
|3|"どらねこ"|1|1|letters|ja|<undefined>|"どらねこ"|<undefined>|
|4|"と"|1|1|letters|ja|coord-conjunction|"と"|non-entity|
|5|"ドラネコ"|1|1|letters|ja|proper-noun|"ドラネコ"|organization|


## ５．シノニムファイルを用いて表記ゆれを修正

続いて、トークン化されたテキストに対して、表記ゆれの修正を行っていきます。最初に読み込んだシノニムファイルを使って、2列目以降のトークンが出てきた場合に1列目のトークンに変換します。


```matlab
for ii = 1:height(td)
    idx = synonymtable == table2array(td(ii,1));
    if sum(idx(:)) == 1
        updateddoc{ii} = synonymtable(logical(sum(idx,2)),1);
    else
        updateddoc{ii} = cellstr(table2array(td(ii,1)));
    end
end
```


表記ゆれを修正した文書を表示させてみます。


```matlab
updateddocument = cell2table(updateddoc')
```
| |Var1|
|:--:|:--:|
|1|'どら猫'|
|2|'と'|
|3|'どら猫'|
|4|'と'|
|5|'どら猫'|



うまく表記ゆれが修正されていることがわかりますので、updateddocを使ってテキスト解析ができそうです。


```matlab
mOptions = mecabOptions('UserModel', userdic);
str = erase(strjoin(string(updateddoc)),' ')
```

updatedocをstring型に戻します。

```
str = "どら猫とどら猫とどら猫"
```

このstringを基に、いつも通りの解析を進めることができます。

```matlab
documents = tokenizedDocument(str,"TokenizeMethod",mOptions);
td = tokenDetails(documents)
```
| |Token|DocumentNumber|LineNumber|Type|Language|PartOfSpeech|Lemma|Entity|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|1|"どら猫"|1|1|letters|ja|noun|"どら猫"|non-entity|
|2|"と"|1|1|letters|ja|coord-conjunction|"と"|non-entity|
|3|"どら猫"|1|1|letters|ja|noun|"どら猫"|non-entity|
|4|"と"|1|1|letters|ja|coord-conjunction|"と"|non-entity|
|5|"どら猫"|1|1|letters|ja|noun|"どら猫"|non-entity|


# 考察
実はちょっと問題があります。
「どら猫とどらねことドラネコと**ドラ猫**」だとうまくいきません。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521542/13643e68-5260-841d-e08e-1d68e84bd078.png)


上記を見る限り、シノニムファイルには「ドラ猫」は存在します。しかしながら、ユーザー辞書に「ドラ猫」が登録されていない模様で（ちゃんと確認はしていませんが、下記の結果を見ると「ドラ猫」は「ドラ」と「猫」に分かれます）「ドラ猫」というトークンに分かれないのが原因だと思われます。


```matlab
str = ["どら猫とどらねことドラネコとドラ猫"];
userdic = 'NEologd.20200315.dic';
mOptions = mecabOptions('UserModel', userdic);
documents = tokenizedDocument(str,"TokenizeMethod",mOptions);
td = tokenDetails(documents)
```
| |Token|DocumentNumber|LineNumber|Type|Language|PartOfSpeech|Lemma|Entity|
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
|1|"どら猫"|1|1|letters|ja|noun|"どら猫"|non-entity|
|2|"と"|1|1|letters|ja|coord-conjunction|"と"|non-entity|
|3|"どらねこ"|1|1|letters|ja|<undefined>|"どらねこ"|<undefined>|
|4|"と"|1|1|letters|ja|coord-conjunction|"と"|non-entity|
|5|"ドラネコ"|1|1|letters|ja|noun|"ドラネコ"|non-entity|
|6|"と"|1|1|letters|ja|coord-conjunction|"と"|non-entity|
|7|"ドラ"|1|1|letters|ja|<undefined>|"DORA"|<undefined>|
|8|"猫"|1|1|letters|ja|noun|"猫"|non-entity|

結果だけ載せるとこんな感じです。

```matlab
str = 

    "どら猫とどら猫とどら猫とドラ猫"
```

ということで、ここがクリアされれば尚良いのでしょうが、今の所性能はもう一息という感じです。