形態素解析で遊んだ話



## はじめに
僕は明治大学公認サークルのNCCに所属していて, 毎年夏と春に合宿を行なっています. 今年の夏合宿のテーマはペアプログラミングで, そこで行なった形態素解析について書きます.


## 環境
OS: High Sierra
言語: Python3.7


## 形態素解析とは
>文法的な情報の注記の無い自然言語のテキストデータ（文）から、対象言語の文法や、辞書と呼ばれる単語の品詞等の情報にもとづき、形態素（Morpheme, おおまかにいえば、言語で意味を持つ最小単位）の列に分割し、それぞれの形態素の品詞等を判別する作業である。(Wiki)

要は文章を最小単位の単語(形態素)で分解するということを形態素解析といいます.

__今回は一方の文の名詞をランダムに他方の文の名詞の部分に突っ込み, おもしろ文章を生成します.__


[GitHub(auto_statement_generator)](https://github.com/jabelic/auto-sentence-generator#auto_statement_generator)
## 使用したライブラリ
今回はJanomeを利用します. 
[リファレンス](https://mocobeta.github.io/janome/api/janome.html)


## 使用した主な関数

[class janome.tokenizer.Tokenizer]( https://mocobeta.github.io/janome/api/janome.html#janome.tokenizer.Tokenizer )


[class janome.analyzer.Analyzer]( https://mocobeta.github.io/janome/api/janome.html#janome.analyzer.Analyzer )


[class janome.tokenfilter.POSKeepFilter]( https://mocobeta.github.io/janome/api/janome.html#janome.tokenfilter.POSKeepFilter )




## 処理の流れ
- 文を他のファイルからimport
  - 文１：名詞を挿入される文
  - 文２：名詞を抽出される文
 とする
- 文１を品詞分解し, タプルリストに格納
  - タプルリスト１とする
- 文２から名詞のオブジェクトを抽出, 配列に格納
  - 配列２とする
- 配列２の要素のオブジェクトから名詞のみを抽出, 別の配列に格納
  - 配列３とする
- タプルリスト１の名詞の位置を取得. 
  - 配列４とする
- 配列４の要素数(取得すべき)分だけの名詞を別の配列に格納して用意しておく.
  - 配列５とする
- 配列５の要素を順次, タプルリスト１にタプルに直して突っ込む
- タプルリスト１の単語を結合して文にして完成



## スクリプト

```python

import artifact #文章を格納したスクリプト
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.tokenfilter import POSKeepFilter
import random

tokenizer = Tokenizer()

# artifact.pyからtextを呼び出す
# inserted_sentenceは名詞を挿入される文章
# extracted_only_nounは名詞を抽出される文章
inserted_sentence_text = artifact.inserted_sentence_text
extracted_only_noun_text = artifact.extracted_only_noun_text
```


`tokenizer.tokenize(sentence)`はオブジェクトを格納したリスト
`token.surface`で単語, `token.part_of_speech`は解析結果(e.g. 助詞,係助詞,*,*), この先頭の品詞の情報のみが欲しかったので, `token.part_of_speech.split(",")[0]`としました. 

```python

# ワードと品詞をタプルリストで返す
def decompose_pos(sentence):
    result = []
    for token in tokenizer.tokenize(sentence):
        result.append((token.surface, token.part_of_speech.split(",")[0]))
    return result


# inserted_sentence_textを品詞分解してタプルリストに格納
inserted_sentence_tuple_list = decompose_pos(inserted_sentence_text)
```


```python
# 名詞のみを抽出させるフィルター
filter_extract_noun = Analyzer(token_filters=[POSKeepFilter(['名詞'])])
#filter_extract_noun.analyze(sentence1)でsentence1の名詞を抽出
```


```python
# 名詞のみ抽出されたリストの個数 #多分, inserted_sentence_noun_numの名詞の数を数えているんだと思う
inserted_sentence_noun_num = len(
    list(filter_extract_noun.analyze(inserted_sentence_text)))
# len([<janome.tokenizer.Token object at 0x11c7e2c18>, <janome.tokenizer.Token object at 0x11c7e2eb8>, <janome.tokenizer.Token object at 0x11c7e2b00>, <janome.tokenizer.Token object at 0x11c7e2b38>, <janome.tokenizer.Token object at 0x11c7e2d68>, <janome.tokenizer.Token object at 0x11c7e2e10>, <janome.tokenizer.Token object at 0x11c7e2ef0>])
# print(list(filter_extract_noun.analyze(inserted_sentence_text))[0])
# > 下人    名詞,一般,*,*,*,*,下人,ゲニン,ゲニン


# extracted_only_noun_textの名詞を抽出し, リストに格納. 形式はオブジェクトである.
analyzed_extracted_only_noun_noun = list(
    filter_extract_noun.analyze(extracted_only_noun_text))
# [<janome.tokenizer.Token object at 0x11e2182b0>, <janome.tokenizer.Token object at 0x11e218320>, <janome.tokenizer.Token object at 0x11e218390>, <janome.tokenizer.Token object at 0x11e218470>, <janome.tokenizer.Token object at 0x11e2184a8>, <janome.tokenizer.Token object at 0x11e218518>, <janome.tokenizer.Token object at 0x11e218550>, <janome.tokenizer.Token object at 0x11e2185c0>, <janome.tokenizer.Token object at 0x11e218668>, <janome.tokenizer.Token object at 0x11e2186a0>, <janome.tokenizer.Token object at 0x11e218748>, <janome.tokenizer.Token object at 0x11e2187b8>, <janome.tokenizer.Token object at 0x11e218828>, <janome.tokenizer.Token object at 0x11e218860>]
# print(analyzed_extracted_only_noun_noun[0])
# > 日      名詞,非自立,副詞可能,*,*,*,日,ヒ,ヒ
```


t.surfaceでオブジェクトからワードのみを抽出できる

```python
# analyzed_extracted_only_noun_nounの情報をワードと品詞のみにする
extracted_only_noun_noun_list = []
for t in analyzed_extracted_only_noun_noun:
    extracted_only_noun_noun_list.append(t.surface)

# ['日', '暮方', '事', '一', '人', '下人', 'げ', 'ん', '羅生門', 'ら', 'もん', '下', '雨', 'やみ']
# t.surfaceでオブジェクトから"名詞"だけを取得.
```


今, 名詞を挿入される文は品詞とワードが格納されているタプルリストになっている.
そこから名詞の部分を探し, 位置を記録する.


```python
# 名詞の挿入位置を得る関数
def find_index(tuple_list):
    index_list = []
    for i, name in enumerate(tuple_list):
        if name[1] == "名詞":
            index_list.append(i)
    return index_list


# inserted_sentence_textの名詞のindexを取得
inserted_sentence_index_list = find_index(inserted_sentence_tuple_list)
# print(inserted_sentence_index_list)
# [0, 4, 6, 7, 14, 16, 18]
```


```python
# extracted_only_noun_textから取得する名詞の数をinserted_sentence_textの名詞の数だけ用意し, 名詞リストにする
def prepare_word_list_for_replacement(noun_list, limit_noun_num):
    if limit_noun_num >= len(noun_list):
        noun_list = noun_list * (limit_noun_num // len(noun_list) + 1)
    # print(noun_list)
    random.shuffle(noun_list)
    return noun_list[:limit_noun_num]

# 挿入する名詞のリスト
replace_word_list = prepare_word_list_for_replacement(
    extracted_only_noun_noun_list, inserted_sentence_noun_num)
# print(replace_word_list)
# ['下人', '羅生門', '人', 'やみ', '雨', '日', '下']
```


```python
# inserted_sentenceの名詞の部分にextracted_only_nounの名詞を挿入
def insert_noun(index_list, noun_list, tuple_list):
    count = 0
    for i in range(len(inserted_sentence_tuple_list)):
        if i in inserted_sentence_index_list:
            tuple_list[i] = (noun_list[count], "名詞")
            count += 1
    return tuple_list
```


```python
# 完成文のタプルリスト. 単語と品詞が入っている
complete_tuple_list = insert_noun(
    inserted_sentence_index_list,
    replace_word_list,
    inserted_sentence_tuple_list)
#[('下', '名詞'), ('は', '助詞'), ('、', '記号'), ('大きな', '連体詞'), ('一', '名詞'), ('く', '形容詞'), ('下人', '名詞'), ('事', '名詞'), ('を', '助詞'), ('し', '動詞'), ('て', '助詞'), ('、', '記号'), ('それから', '接続詞'), ('、', '記号'), ('げ', '名詞'), ('たい', '助動詞'), ('雨', '名詞'), ('に', '助詞'), ('ら', '名詞'), ('上っ', '動詞'), ('た', '助動詞'), ('。', '記号')]



# 完成文のタプルリストから完成文を生成
complete_setence = ""
for i in complete_tuple_list:
    complete_setence += str(i[0])
    

print(inserted_sentence_text)
print(extracted_only_noun_text)
print(complete_setence)
```



## 結果


```bash
下人は、大きな嚔くさめをして、それから、大儀たいぎそうに立上った。

ある日の暮方の事である。一人の下人げにんが、羅生門らしょうもんの下で雨やみを待っていた。

らは、大きな日くんやみをして、それから、げたい人に雨上った。
```


## 終わりに


マルコフ連鎖を使わなかったので大変おかしな文が生成されてしまいました. (確か)6時間で仕上げなければなかったので, チュートリアルからここまで漕ぎ着けられたのは大きな収穫となったのでは, と思います. @ auk_key_ikonさん, ありがとうございました.

急いで書いたのでごちゃごちゃです. すみません(汗
後ほど直すかも？


## 参考文献
[Janome v0.3 documentation (ja)](https://mocobeta.github.io/janome/)

[janome package](https://mocobeta.github.io/janome/api/janome.html#janome-package)

[Python, Janomeで日本語の形態素解析、分かち書き（単語分割）](https://note.nkmk.me/python-janome-tutorial/)