『カラマーゾフの兄弟』で学ぶ自然言語処理（第2回: 形態素解析編）

今回は，[前回](https://qiita.com/Koshka/items/afd95c5c36531dd0dc6a)整形した『カラマーゾフの兄弟』のテキストを用いて，形態素解析を学ぶ．

## 形態素解析とは
多くの自然言語処理では，初手として「形態素解析」という「文を解析し，形態素に分解する作業」を必要とする．ここで「形態素」とは単語（語）と同一の概念ではないが，とりあえず初めのうちはザックリと単語のようなものだと考えておいてよい．

以下，Google Colabでの実行を想定している．
 [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](http://colab.research.google.com/github/ezoalbus/kara_nlp/blob/master/colab_notes/2_morph_analysis.ipynb)

## テキストのダウンロードと確認
前回，前処理したテキストをダウンロードする．

``` py
!wget https://github.com/ezoalbus/kara_nlp/blob/master/data/cleansed_kara.txt
```

ダウンロードしたテキストを読み込み，確認する．

``` py
with open('cleansed_kara.txt', 'r', encoding='utf-8') as f:
    text = f.read()
print(text)
```

## 形態素解析（janome）
まずは簡単のため，"janome"を形態素解析のためのライブラリとして用いる．

### 導入

``` python
!pip install janome  # janomeのインストール
```

### 分かち書き

まずは，「分かち書き」（文を形態素に分解すること）をしてみる．

``` python
from janome.tokenizer import Tokenizer
# 冒頭の一文
text = 'この物語の主人公アレクセイ・フョードロヴィッチ・カラマゾフの伝記にとりかかるに当たって、自分は一種の懐疑に陥っている。'
tokenizer = Tokenizer(wakati=True)  # wakati=Trueにすることで，分かち書きが可能
token_ls = list(tokenizer.tokenize(text))
print(token_ls)
```

出力：

```
['この', '物語', 'の', '主人公', 'アレクセイ・フョードロヴィッチ・カラマゾフ', 'の', 
'伝記', 'に', 'とりかかる', 'に当たって', '、', '自分', 'は', '一種', 'の', '懐疑',
 'に', '陥っ', 'て', 'いる', '。']
```

いい感じに分かれているが，この分かち書きで"アレクセイ・フョードロヴィッチ・カラマゾフ"を正しく単一の形態素としてとれているのは，この単語(人物名)が形態素解析の学習に用いた辞書内に登場しているからだと思われる．
一方で，例えば"ジョー・バイデン"を分かち書きすると，以下のようになる．

```
['ジョー', '・', 'バイデン']
```

### 品詞情報
次に，分かち書きのみでなく，品詞情報も出力してみる．

``` python
tokenizer = Tokenizer(wakati=False)  # defaultでFalseだが，あえて明示的に書く
for t in tokenizer.tokenize(text):
    print(t)
```

出力:

```
この	連体詞,*,*,*,*,*,この,コノ,コノ
物語	名詞,一般,*,*,*,*,物語,モノガタリ,モノガタリ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
主人公	名詞,一般,*,*,*,*,主人公,シュジンコウ,シュジンコー
アレクセイ・フョードロヴィッチ・カラマゾフ	名詞,一般,*,*,*,*,アレクセイ・フョードロヴィッチ・カラマゾフ,*,*
の	助詞,連体化,*,*,*,*,の,ノ,ノ
伝記	名詞,一般,*,*,*,*,伝記,デンキ,デンキ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
とりかかる	動詞,自立,*,*,五段・ラ行,基本形,とりかかる,トリカカル,トリカカル
に当たって	助詞,格助詞,連語,*,*,*,に当たって,ニアタッテ,ニアタッテ
、	記号,読点,*,*,*,*,、,、,、
自分	名詞,一般,*,*,*,*,自分,ジブン,ジブン
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
一種	名詞,一般,*,*,*,*,一種,イッシュ,イッシュ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
懐疑	名詞,サ変接続,*,*,*,*,懐疑,カイギ,カイギ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
陥っ	動詞,自立,*,*,五段・ラ行,連用タ接続,陥る,オチイッ,オチイッ
て	助詞,接続助詞,*,*,*,*,て,テ,テ
いる	動詞,非自立,*,*,一段,基本形,いる,イル,イル
。	記号,句点,*,*,*,*,。,。,。
```

ごちゃごちゃしているので，形態素の文字列と品詞だけを出力する．

``` python
tokenizer = Tokenizer(wakati=False)
for t in tokenizer.tokenize(text):
    word_class = t.part_of_speech.split(',')[0]
    print(f'{t.surface}\t{word_class}')
```

ここで，`t.surface`は表層系(形態素の文字列)，`t.part_of_speech`は品詞情報である．

出力:

```
この	連体詞
物語	名詞
の	助詞
主人公	名詞
アレクセイ・フョードロヴィッチ・カラマゾフ	名詞
の	助詞
伝記	名詞
に	助詞
とりかかる	動詞
に当たって	助詞
、	記号
自分	名詞
は	助詞
一種	名詞
の	助詞
懐疑	名詞
に	助詞
陥っ	動詞
て	助詞
いる	動詞
。	記号
```

## 形態素解析（GiNZA）
日本語の文章に対して形態素解析を行うことができるPythonライブラリはjanome以外にもいくつか存在する．

janomeはインストールが容易で，使い方も直観的でとてもよい．
一方，以下で扱うGiNZAは，自然言語処理ライブラリであるspaCyのフレームワークに則ることで，日本語での形態素解析，係り受け解析，固有表現抽出などを単一のライブラリ内で行うことを可能にした（一言でいうと，spaCyの日本語対応版）．

非常に便利なので，以下で導入法および基本的な使用法を見ていく．

### 導入
``` python
!pip install ginza
```

おわり．
spaCyやSudachiPyなどが一緒にインストールされ，GiNZAの形態素解析部分はSudachiPyが担うことになる．

``` python
import pkg_resources, imp
imp.reload(pkg_resources)
```

[GiNZAの公式ページ](https://megagonlabs.github.io/ginza/)の注意書きに則り，Google Colabで実行する場合は，パッケージ情報を再読み込みさせておく.

### 分かち書き
``` python
import spacy
nlp = spacy.load('ja_ginza')
text = 'この物語の主人公アレクセイ・フョードロヴィッチ・カラマゾフの伝記にとりかかるに当たって、自分は一種の懐疑に陥っている。'
doc = nlp(text)
token_ls = list(doc)
print(token_ls)
```

出力:

```
[この, 物語, の, 主人公, アレクセイ, ・, フョードロヴィッチ, ・, カラマゾフ, の, 伝記, に, 
とりかかる, に, 当たっ, て, 、, 自分, は, 一種, の, 懐疑, に, 陥っ, て, いる, 。]
```

janomeの結果より細かく分かち書きされている. 
なお，GiNZA(というよりSudachiPy)はsplitモードを [A, B, C] の三種類から選ぶことができ，一番粗く形態素を分けるCがデフォルトになっている．これを変更したい場合は，以下のようにする．

``` py
import ginza

ginza.set_split_mode(nlp, 'A')
doc = nlp(text)
token_ls = list(doc)
print(token_ls)
```

例．

``` py
ginza.set_split_mode(nlp, 'A')
doc = nlp('自然言語処理')
token_ls = list(doc)
print('mode A: ', token_ls)

ginza.set_split_mode(nlp, 'C')
doc = nlp('自然言語処理')
token_ls = list(doc)
print('mode C: ', token_ls)
```

```
# 結果: 
mode A:  [自然, 言語, 処理]
mode C:  [自然言語処理]
```

### 品詞情報
先の分かち書きのコードにある`token_ls`の中身は文字列ではなく，`spacy.tokens.token.Token`のオブジェクト．
以下のようにすると，品詞情報を取り出すことができる．

``` py
for t in token_ls:
     print(t.text, t.pos_, t.tag_, sep='\t')
```

出力:

``` 
この	DET	連体詞
物語	NOUN	名詞-普通名詞-一般
の	ADP	助詞-格助詞
主人公	NOUN	名詞-普通名詞-一般
アレクセイ	PROPN	名詞-固有名詞-人名-一般
・	SYM	補助記号-一般
フョードロヴィッチ	NOUN	名詞-普通名詞-一般
・	SYM	補助記号-一般
カラマゾフ	NOUN	名詞-普通名詞-一般
の	ADP	助詞-格助詞
伝記	NOUN	名詞-普通名詞-一般
に	ADP	助詞-格助詞
とりかかる	VERB	動詞-一般
に	ADP	助詞-格助詞
当たっ	VERB	動詞-一般
て	SCONJ	助詞-接続助詞
、	PUNCT	補助記号-読点
自分	NOUN	名詞-普通名詞-一般
は	ADP	助詞-係助詞
一種	NOUN	名詞-普通名詞-一般
の	ADP	助詞-格助詞
懐疑	NOUN	名詞-普通名詞-サ変可能
に	ADP	助詞-格助詞
陥っ	VERB	動詞-一般
て	SCONJ	助詞-接続助詞
いる	AUX	動詞-非自立可能
。	PUNCT	補助記号-句点
```

## 形態素解析（MeCab）
最後に一番有名なMeCab(mecab-python3)で実行してみる．
Pythonでの実行のために面倒な手順を踏むべきと書いてある記事もあるが，[公式の手順](https://pypi.org/project/mecab-python3/)に則れば導入は簡単．

### 導入
**mecab-python3のインストール:**

``` py
!pip install mecab-python3
```

**辞書のインストール**

``` py
!pip install unidic-lite
```

おわり．

### 分かち書き

``` py
import MeCab
wakati = MeCab.Tagger("-Owakati")
token_ls = wakati.parse(text).split()
print(token_ls)
```

出力:

```
['カラマゾフ', 'の', '兄弟', '上', 'ドストエーフスキイ', '中山', '省三郎', '訳', '誠', 'に', 'まこと', 'に', '汝', 'ら',
 'に', '告ぐ', '、', '一', '粒', 'の', '麦', '、', '地', 'に', '落ち', 'て', '死な', 'ず', 'ば', '、', '唯', '一', 'つ',
 'にて', '在り', 'なん', '、', 'もし', '死な', 'ば', '、', '多く', 'の', '果', 'を', '結ぶ', 'べし', '。', 'ヨハネ', '伝',
 '第', '十', '二', '章', '第', '二十', '四', '節', 'アンナ', '・', 'グリゴリエヴナ・ドストイエフスカヤ', 'に', 'おくる', 
'作者', 'より', 'この', '物語', 'の', '主人', '公', 'アレクセイ・フョードロヴィッチ・カラマゾフ', 'の', '伝記', 'に', 
'とりかかる', 'に', '当たっ', 'て', '、', '自分', 'は', '一種', 'の', '懐疑', 'に', '陥っ', 'て', 'いる', '。', 'すなわち', 
'、', '自分', 'は', '、', 'この', 'アレクセイ', '・', 'フョードロヴィッチ', 'を', '主人', '公', 'と', '呼ん', 'で', 'は', 
'いる', 'が', '、', 'しかし', '彼', 'が', 'けっして', '偉大', 'な', '人物', 'で', 'ない', 'こと', 'は', '、', '自分', 
'で', 'も', 'よく', '承知',...]
```

### 品詞情報

``` py
tagger = MeCab.Tagger()
token_ls = tagger.parse(text)
print(token_ls[:1000])
```

出力:

```
カラマゾフ	カラマゾフ	カラマゾフ	カラマゾフ	名詞-普通名詞-一般			0
の	ノ	ノ	の	助詞-格助詞			
兄弟	キョーダイ	キョウダイ	兄弟	名詞-普通名詞-一般			1
上	ウエ	ウエ	上	名詞-普通名詞-副詞可能			0,2
ドストエーフスキイ	ドストエーフスキー	ドストエフスキー	ドストエフスキー-Dostoevskii	名詞-固有名詞-人名-一般			7
中山	ナカヤマ	ナカヤマ	ナカヤマ	名詞-固有名詞-人名-姓			0
省三郎	ショーサブロー	ショウサブロウ	ショウサブロウ	名詞-固有名詞-人名-名			3
訳	ヤク	ヤク	訳	名詞-普通名詞-一般			1,2
誠	マコト	マコト	真	名詞-普通名詞-一般			0
に	ニ	ニ	に	助詞-格助詞			
まこと	マコト	マコト	真	名詞-普通名詞-一般			0
に	ニ	ニ	に	助詞-格助詞			
汝	ナンジ	ナンジ	汝	代名詞			1,0
ら	ラ	ラ	等	接尾辞-名詞的-一般			
に	ニ	ニ	に	助詞-格助詞			
告ぐ	ツグ	ツゲル	告げる	動詞-一般	文語下二段-ガ行	終止形-一般	0
...
```

## 時間比較（分かち書き）
最後に上で紹介した三つのツールの実行時間を比較する．

### janome
``` py
%%time
tokenizer = Tokenizer(wakati=True)
jtoken_ls = list(tokenizer.tokenize(text))
```

結果:

```
CPU times: user 47.3 s, sys: 152 ms, total: 47.4 s
Wall time: 48 s
```

### GiNZA
``` py
# GiNZA
%%time
doc = nlp(text)
gtoken_ls = list(doc)
```

結果:

```
CPU times: user 3min 28s, sys: 29.2 s, total: 3min 58s
Wall time: 3min 53s
```

### MeCab
``` py
%%time
wakati = MeCab.Tagger("-Owakati")
token_ls = wakati.parse(text).split()
```

結果:

```
CPU times: user 643 ms, sys: 82.8 ms, total: 726 ms
Wall time: 735 ms
```

MeCabがダントツで速い．なお，GiNZAは形態素解析以外の処理も行っているため，遅いのは仕方がない（本来はSudachiPyの速度で比較するべき）．

---

以上，各ツールごとに手軽さ，実行時間，他の解析との接続性などに関し一長一短があるため，目的に応じて使い分けるとよい．

おわり．