Windowsで形態素解析（MeCab & Janome）

## はじめに
先日Kaggleの言語モデルのコンペに参加し、日本語の前処理の方法も少しメモを残しておこうと思った。（結構前にやったきりあまり触れていなかったので。。。）
まずは基本的な形態素解析の実装方法くらいを今回の記事にまとめる。


## 前提条件

【PC環境】
　　Windows 10 Pro　
【ローカル環境のpython.Ver】
　　python 3.9.5
【仮想環境のpython.Ver】
　　python 3.9.5

仮想環境を作成することはマストではないが、一旦テストが終わったら丸っと削除するつもりなので、今回は仮想環境で作業している。


## 手順

１．**MeCab**、**Janome** の準備/インストール
２．各ライブラリで形態素解析

## １．**MeCab**、**Janome** の準備/インストール

### **MeCab** の準備/インストール

**・WindowsにMeCabのダウンロード**
　32bit版：MeCabの[公式サイト](http://taku910.github.io/mecab/#download)からダウンロードする
　64bit版：有志がビルドしたもの。[こちらのサイト](https://github.com/ikegami-yukino/mecab/releases/tag/v0.996.2)からダウンロード。

　※今回は64bit版をダウンロード。文字コードは`UTF-8`を選択する。

**・パスを設定**
　Windowsの環境変数`Path`に `C:\Program Files\MeCab\bin`を追加する。

　試しにコマンドプロンプトを立ち上げ、以下の様に実行して何かしらレスポンスが返ってくればOK。

```:
> mecab
庭には二羽鶏がいます。


庭には二        險伜捷,荳闊ｬ,*,*,*,*,*
H       蜷崎ｩ・蝗ｺ譛牙錐隧・邨・ｹ・*,*,*,*
        險伜捷,荳闊ｬ,*,*,*,*,*
{       蜷崎ｩ・繧ｵ螟画磁邯・*,*,*,*,*
がいます        險伜捷,荳闊ｬ,*,*,*,*,*
B       蜷崎ｩ・蝗ｺ譛牙錐隧・邨・ｹ・*,*,*,*
EOS
```

**・Pythonパッケージのインストール**

```:venvの仮想環境上
pip install ipykernel
pip install mecab-python3
```

`import MeCab` で問題なくインポートできれば、一旦はOK！


### **Janome** の準備/インストール
　PurePythonで書かれている形態素解析ツールのため、`pip` によるインストールのみで完結。

```:venvの仮想環境上
pip install janome
```

同じく`import janome` で問題なくインポートできればOK！


**仮想環境をjupyter notebookで利用するため以下を実行。**

```:venvの仮想環境上
ipython kernel install --user --name=【任意の名称】
```

## ２．各ライブラリで形態素解析

そもそも形態素解析の機能についてだが、最低限以下の３つを有している事が一般的らしい。

・分かち書き（文章を形態素で分ける）
・品詞わけ（名詞や動詞などに分類する）
・原型付与（単語の基本形をだす）

今回は上記の処理を使い、以下の①～③の文章から、**[名詞]**、**[動詞]**、**[形容詞]** の辞書を作り、各文章のそれぞれの単語の出現度数を計算する
処理を**MeCab** と　**janome** を使い実装してみる。


---
① 私はりんごは好きだけど、バナナは好きではない。なぜなら、バナナには水分が多く含まれていないため、果物特有の瑞々しさを感じないからだ。同じような理由で、パンもそんなに好きではない。どちらかと言えば、ご飯やうどんの方が好きだ。

---
② 『寒いと言うほど余計に寒くなるから、寒い時に寒いと言わない方がいい！』と主張する奴が時々いるが、何を根拠にその様な主張をしているのだろう？実際のところ、寒い寒いと連呼している方が、多少なりとも筋肉が動くため、黙ってじっとしているよりはマシなのではないか。

---
③ 花より団子、あなたは花と団子どちらが好きですか？花を見て楽しむのいいですが、美味しいリンゴやバナナを食べるのもまた至福のひと時ですよね。

---

### **MeCab** の利用
以下のコードをnotebookにコピペすれば動く。

```py:
# ライブラリインポート
import numpy as np
import pandas as pd
import MeCab
import collections

# pandasのdisplayによる表示行を調整
pd.options.display.max_rows = 200

# MeCabpのインスタンスを作成
m = MeCab.Tagger ("-Ochasen")

# テスト文章の準備
sentence1 = '私はりんごは好きだけど、バナナは好きではない。なぜなら、バナナには水分が多く含まれていないため、果物特有の瑞々しさを感じないからだ。同じような理由で、パンもそんなに好きではない。どちらかと言えば、ご飯やうどんの方が好きだ。'
sentence2 = '『寒いと言うほど余計に寒くなるから、寒い時に寒いと言わない方がいい！』と主張する奴が時々いるが、何を根拠にその様な主張をしているのだろう？実際のところ、寒い寒いと連呼している方が、多少なりとも筋肉が動くため、黙ってじっとしているよりはマシなのではないか。'
sentence3 = '花より団子、あなたは花と団子どちらが好きですか？花を見て楽しむのいいですが、美味しいリンゴやバナナを食べるのもまた至福のひと時ですよね。'
sentence_list = [sentence1, sentence2, sentence3]
```

```py:
# テキストに出現した単語（名詞、形容詞、動詞）の単語リストを作成
text_parse_list = []

for text in sentence_list:
    text_parse_split1 = m.parse(text).split("\n")

    for text_parse in text_parse_split1:

        text_parse_split2 = text_parse.split("\t")
        text_parse_list.append(text_parse_split2)

# DataFrameに入れる
alltext_words_df = pd.DataFrame(text_parse_list)
alltext_words_df.columns = ['keitaiso', 'yomi', 'gokan', 'hinshi', 'col5', 'col6']
alltext_words_df["hinshi"] = alltext_words_df["hinshi"].str.split('-').str[0]
alltext_words_df = alltext_words_df[(alltext_words_df["hinshi"] == '名詞') | (alltext_words_df["hinshi"] == '形容詞') | (alltext_words_df["hinshi"] == '動詞')]
alltext_words_df = alltext_words_df[alltext_words_df["keitaiso"].duplicated() == False]

#print(len(alltext_words_df))
#display(alltext_words_df)
```

```py:
# 各テキストの出現単語のカウント
words = [[], [], []]

for i, text in enumerate(sentence_list):
    
    node = m.parseToNode(text)
    words[i] = []

    while node:
        words[i].append(node.surface)
        node = node.next

c0 = collections.Counter(words[0])
print(c0, "\n")

c1 = collections.Counter(words[1])
print(c1, "\n")

c2 = collections.Counter(words[2])
print(c2, "\n")
```


```py:
# 各テキストの出現単語のカウント（名詞、動詞、形容詞に絞り、上位50個の単語のみ）
words = [[], [], []]

for i, text in enumerate(sentence_list):
    
    node = m.parseToNode(text)
    words[i]=[]

    while node:
        hinshi = node.feature.split(",")[0]
        
        if hinshi in ["名詞","動詞","形容詞"]:
            words[i].append(node.surface)
        node = node.next

c0 = collections.Counter(words[0])
print(c0.most_common(50), "\n")

c1 = collections.Counter(words[1])
print(c1.most_common(50), "\n")

c2 = collections.Counter(words[2])
print(c2.most_common(50), "\n")


# 単語リストとJOINするために、dfに変換。
sentence1_df = pd.DataFrame(c0.most_common(50))
sentence1_df.columns = ['keitaiso', 'sen1_freq']
# display(sentence1_df)

sentence2_df = pd.DataFrame(c1.most_common(50))
sentence2_df.columns = ['keitaiso', 'sen2_freq']
# display(sentence1_df)

sentence3_df = pd.DataFrame(c2.most_common(50))
sentence3_df.columns = ['keitaiso', 'sen3_freq']
# display(sentence3_df)
```

```py:
# 各テキストの出現単語のカウントテーブルと　最初に作成した単語リストを紐づけ
temp_df = pd.merge(alltext_words_df, sentence1_df, how="left", on="keitaiso")
temp_df = pd.merge(temp_df, sentence2_df, how="left", on="keitaiso")
temp_df = pd.merge(temp_df, sentence3_df, how="left", on="keitaiso")

display(temp_df)
```
最後のコマンドの実行により、以下の様な表が表示される。
![Qiita-no046_img01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654767/53de7204-4736-fe57-c62f-32983784887c.jpeg)

※集計したい内容によっては、この後さらに "語幹" でgroup by をかけてもいいかもしれない。


### **janome** の利用

上記と同じ様な処理を janome でも試しに作って見た。

```py:
from janome.tokenizer import Tokenizer

t = Tokenizer()

# テスト文章の準備
sentence1 = '私はりんごは好きだけど、バナナは好きではない。なぜなら、バナナには水分が多く含まれていないため、果物特有の瑞々しさを感じないからだ。同じような理由で、パンもそんなに好きではない。どちらかと言えば、ご飯やうどんの方が好きだ。'
sentence2 = '『寒いと言うほど余計に寒くなるから、寒い時に寒いと言わない方がいい！』と主張する奴が時々いるが、何を根拠にその様な主張をしているのだろう？実際のところ、寒い寒いと連呼している方が、多少なりとも筋肉が動くため、黙ってじっとしているよりはマシなのではないか。'
sentence3 = '花より団子、あなたは花と団子どちらが好きですか？花を見て楽しむのいいですが、美味しいリンゴやバナナを食べるのもまた至福のひと時ですよね。'
sentence_list = [sentence1, sentence2, sentence3]

# 試しにどの様に取り出すのか確認
for i, token in enumerate(t.tokenize(sentence1)):
    print(i, "\n")
    print(token)
    print('■表層形：', token.surface, '　■品詞：', token.part_of_speech, '　■活用形：', token.infl_type, '■基本形：', token.base_form, '■読み：', token.reading)
```

```py:
# テキストに出現した単語（名詞、形容詞、動詞）の単語リストを作成
text_parse_list = []
token_len = 0

for text in sentence_list:
    
    for i, token in enumerate(t.tokenize(text)):
        
        text_parse_list.append(token.surface)
        text_parse_list.append(token.reading)
        text_parse_list.append(token.base_form)
        text_parse_list.append(token.part_of_speech.split(',')[0])
        token_len += 1

        
# DataFrameに入れる
alltext_words_df = pd.DataFrame(np.array(text_parse_list).reshape(token_len, 4))
alltext_words_df.columns = ['keitaiso', 'yomi', 'gokan', 'hinshi']
alltext_words_df = alltext_words_df[(alltext_words_df["hinshi"] == '名詞') | (alltext_words_df["hinshi"] == '形容詞') | (alltext_words_df["hinshi"] == '動詞')]
alltext_words_df = alltext_words_df[alltext_words_df["keitaiso"].duplicated() == False]

#print(len(alltext_words_df))
#display(alltext_words_df)
```

```py:
# 各テキストの出現単語のカウント
c = [[], [], []]

for i, text in enumerate(sentence_list):

    c[i] = collections.Counter(t.tokenize(text, wakati=True))
    print(c[i], "\n")
    print(c[i].most_common(50), "\n")
    
    
# 単語リストとJOINするために、dfに変換。
sentence1_df = pd.DataFrame(c[0].most_common(100))
sentence1_df.columns = ['keitaiso', 'sen1_freq']
# display(sentence1_df)

sentence2_df = pd.DataFrame(c[1].most_common(100))
sentence2_df.columns = ['keitaiso', 'sen2_freq']
# display(sentence1_df)

sentence3_df = pd.DataFrame(c[2].most_common(100))
sentence3_df.columns = ['keitaiso', 'sen3_freq']
# display(sentence3_df)
```

```py:
# 各テキストの出現単語のカウントテーブルと　最初に作成した単語リストを紐づけ
temp_df = pd.merge(alltext_words_df, sentence1_df, how="left", on="keitaiso")
temp_df = pd.merge(temp_df, sentence2_df, how="left", on="keitaiso")
temp_df = pd.merge(temp_df, sentence3_df, how="left", on="keitaiso")

display(temp_df)
```
最後のコマンドの実行後に表示される表。

![Qiita-no046_img02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654767/359f140a-f296-da38-1fc8-614777818285.jpeg)





## まとめ
今回は日本語の自然言語解析でまず最初に使われる形態素解析について、初歩的な実装の流れをまとめてみた。
また時間がある時に、ワードマップや、Bertなどのモデルを使った、文章分類、文章生成(要約)、感情分析なども試してみたいと思います。