日本語テキストから Bag of Words を作製して頻出単語を表示する

####**目的**
日本語のテキスト文書を対象に、単語の出現回数を表示する。（＝ Bag-of-words の作成）

####**Bag-of-words 作製の手順**

1. テキストファイルの準備

2. 形態素分析の関数定義
（日本語は英語と違いスペースが無いため、まず文節ごとに区切る必要がある）

3. テキストファイル読み込みと形態素分析実施
4. 形態素を id へ変換
5. コーパスの作成　
（テキストファイルの文書を id の並びで表現）
6. Bag of Words 作成と csv ファイルへの書き出し

(ソースコード github, https://github.com/MomonekoView/Bag-of-words)

####**環境**
Python 3.4.5
Windows 10 or Linux 16.04

####**1. テキストファイルの準備**
今回例題として、青空文庫の夏目漱石の「心」を引用した。
https://www.aozora.gr.jp/cards/000148/files/773_14560.html

文章を .txt ファイルに転写し保存。（kokoro.txt）
下記はファイルの中身。
＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿
こころ
夏目漱石
+目次
上　先生と私
一
　私わたくしはその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚はばかる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執とっても心持は同じ事である。よそよそしい頭文字かしらもじなどはとても使う気にならない。
（以下略）
＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿

####**2. 形態素分析の関数定義**
解析ソフトとして、janome を使用。インストールは pip install janome で OK。
（Mecab は Windows での環境構築が上手く行かず、使用を断念）

下記ソースで実施。名称 Text_Data の関数として定義。
除去する文字列については、あまり賢いやり方ではないが、マニュアルで個別に指定。

```python:text.py
# coding: utf-8
import sys
import os
import pickle
import numpy as np
from janome.tokenizer import Tokenizer

def Text_Data(file):

    # ファイルの読み込み
    f = open(file)
    text = f.read()
    f.close()
    text = text.split('\n')

    # 文章で分割
    for i in range(len(text)):
        text[i] = text[i].split('。')

    # 以下形態素解析（形態素に分割）
    t = Tokenizer()
    new_text = []
    word_list = []

    for i in range(len(text)):
        new_text = new_text + text[i]
    print(new_text)

    # トークナイズしてリストに格納
    for j in range(len(new_text)):
        tokens = t.tokenize(new_text[j])
        for k in range(len(tokens)):
            word_list.append(tokens[k].surface)

    # 除去する文字列を指定
    rems = ['@', ',', '，', '。', ' ', 'http', 'https','//', '、', '×', '.',
            '+', '＋', 
            '/',', ', '(', ')', '（', '）', '://', 'RT', ':', ';','：', '；',
            '」','「', '＠', '-', '／',  '%', '％', '!', '！', '#', '＃',
            '$', '＄', '=', '＝', 'ー', '￥', '^', '{', '}', '~', '～', 
            '&', '＆', '・', '?', '??', '？', '？？',  '<', '＜', '>', '＞', '_', '＿', 
            'co', 'jp', '(@', '○', '『', '』', '”', '’', '"', '…', 
            ')」', '〇', '【', '】', '[', ']',
            'から', 'より', 'こそ', 'でも', 'しか','さえ', 'けれど', 'たり', 'つつ', 'とも',
            'たら', 'ある', 'なら', 'のに', 'です', 'ます', 'する', 'ほど', 'ない', 'くる',
            'なり', 'そう', 'まし', 'その', 'この', 'あの', 'せる', 'どう', 'ため', 'どこ',
            'いる', 'これ', 'それ', 'あれ', 'いい', 'など', 'あっ', 'もう', 'さん', 'じゃ',
            'から', 'あり', 'ので', 'とも', 'ませ', 'でし', 'とき', 'こと', 'なる', 'って',
            'ただ', 'まで', 'もの', 'つか', 'なっ', 'でき', 'もっ', 'けど', 'ほぼ', 'なー',
            'そこ', 'ここ', 'だろ', 'なん', 'だっ', 'なあ', 'っけ', 'せる', 'やっ', 'また',
            'どれ', 'なれ', 'かも', 'いく', 'いけ', 'いう', 'たい', 'あと', 'かも', 'しれ',  
            'こう', 'なく', 'よく', 'だけ', 'れる', 'よう', 'かけ', 'どの', 'てる', 'とか',
            'という', '思う', '思っ', 'として', 'ところ', 'しまう', 'なんか', 'そんな', 
            'でしょ', 'しまい', 'わかり', 'まま'
            ]

    # 除去リストに数字を追加 
    for number in range(10000):
        rems.append(str(number))

    # 除去リストに追加 ― 一文字のひらがなとカタカナ，全角数字，アルファベット大文字・小文字
    hirakana    = [chr(i) for i in range(12353, 12436)]
    katakana    = [chr(i) for i in range(12449, 12533)]
    zenkaku_num = [chr(i) for i in range(65296, 65296+10)]
    alph_L      = [chr(i) for i in range(97, 97+26)]
    alph_S      = [chr(i) for i in range(65, 65+26)]
    for a in range(len(hirakana)):
        rems.append(hirakana[a])
    for b in range(len(katakana)):
        rems.append(katakana[b])
    for c in range(len(zenkaku_num)):
        rems.append(zenkaku_num[c])
    for d in range(len(alph_L)):
        rems.append(alph_L[d])
    for e in range(len(alph_S)):
        rems.append(alph_S[e])

    words = [x for x in word_list if (x in rems) == False]

    return words
```



####**3. テキストファイル読み込みと形態素分析実施**
```python:NLP_run.py
# coding: utf-8
import os
import glob
import numpy as np
from text import Text_Data
from NLP_func import *
import pandas as pd

words = Text_Data("kokoro.txt")
```
```python:NLP_run.py
print(words)
```
['こころ', '夏目', '漱石', '目次', '上', '\u3000', '先生', '私', '一', '私', 'わたくし', '人', '常に', '先生', '呼ん', 'だから', '先生', '書く', '本名', '打ち明け', '世間', '憚', 'はばかる', '遠慮', '方', '私', 'にとって', '自然', '私', '人', '記憶', '呼び', '起す', 'ごと', 'すぐ', '先生', 'たく', '筆', ・・・・・・・，, '妻', '己', 'おのれ', '過去', 'に対して', 'もつ', '記憶', 'なるべく', '純白', '保存', 'おい', 'やり', '私', '唯一', 'ゆい', '希望', '私', '死ん', '後', '妻', '生き', '以上', 'あなた', '限り', '打ち明け', 'られ', '私', '秘密', 'すべて', '腹の中', 'しまっ', 'おい', '下さい']

全角空白（\u3000）が見られるが、形態素分割はOKとする。
ちなみに、len(words) は 45273 である。
記号や数字、助詞などを除くと、およそ 4 万 5 千個の単語からなる小説ということになる。

####**4. 形態素を id へ変換**
リスト (words) 内の各単語に id を振る。
（「ゼロから作る Deep Learning ②　自然言語処理編，斎藤 康毅 著，O'REILLY」　の第 2 章を参照した）

```python:NLP_func.py
# coding: utf-8
import numpy as np

def id_preprocess(words, word_to_id, id_to_word):
    for word in words:
        if word not in word_to_id:
            new_id = len(word_to_id)
            word_to_id[word] = new_id
            id_to_word[new_id] = word
    return word_to_id, id_to_word
```

```python:NLP_run.py
word_to_id = {}
id_to_word = {}
word_to_id, id_to_word = id_preprocess(words, word_to_id, id_to_word)
```
```python:NLP_run.py
print(word_to_id)
```
{'こころ': 0, '夏目': 1, '漱石': 2, '目次': 3, '上': 4, '\u3000': 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, ・・・・・・・・, '徒労': 7730, '渡辺': 7731, '華山': 7732, 'なべ': 7733, 'かざん': 7734, '邯鄲': 7735, '画': 7736, '死期': 7737, '繰り延べ': 7738, '先達': 7739, 'せんだって': 7740, '果たす': 7741, '果たし': 7742, '善悪': 7743, '供する': 7744, '保存': 7745, '秘密': 7746}

記号や数字、助詞などを除いて、合計 7746 個の単語からなる模様。

word_to_id を逆にした id_to_word は下記。

```python:NLP_run.py
print(id_to_word)
```

{0: 'こころ', 1: '夏目', 2: '漱石', 3: '目次', 4: '上', 5: '\u3000', 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: '筆', ・・・・・・・，7730: '徒労', 7731: '渡辺', 7732: '華山', 7733: 'なべ', 7734: 'かざん', 7735: '邯鄲', 7736: '画', 7737: '死期', 7738: '繰り延べ', 7739: '先達', 7740: 'せんだって', 7741: '果たす', 7742: '果たし', 7743: '善悪', 7744: '供する', 7745: '保存', 7746: '秘密'}



####**5. コーパスの作成**

```python:NLP_func.py
def corpus_preprocess(words, word_to_id, id_to_word):
    corpus = np.array([word_to_id[w] for w in words])
    return corpus
```

```python:NLP_run.py
corpus_dic = {}
corpus_dic = corpus_preprocess(words, word_to_id, id_to_word)
```
```python:NLP_run.py
print(corpus_dic)
```
[   0    1    2 ...  499  694 1260]
これは、元の文章に出現する各単語を id で置換したものに相当する。
len(corpus_dic) = len(words) = 45273 であり、元の小説 45273 語を単語 id で表現したことになる。

####**6. Bag of Words 作成と csv ファイルへの書き出し**
5 で作成した corpus_dic の id の頻度をカウントする。

```python:NLP_run.py
bag_of_words = {}
vocab_size = len(word_to_id) # = len(id_to_word) 

bag_of_words = {}
corpus_size = len(corpus_dic)
for i in range(vocab_size):
    count = 0
    for j in range(corpus_size):
        if corpus_dic[j] == i: # 元の文書を走査していき，一致していればカウントする。
            count += 1
    bag_of_words[id_to_word[i]] = count
    
# 頻出順にソート
bag_of_words = sorted(bag_of_words.items(), key = lambda x: x[1], reverse=True)

# 単語を ID 順に CSV ファイルへ保存  # BOW を CSV ファイルへ保存
Table = pd.DataFrame(bag_of_words)
Table.to_csv('Table.csv', index=False, encoding="shift_jis")
```



####**結果**
出現回数でソートした単語は以下の通り。（上位30単語を表示）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/287001/7e987978-ae7c-fafe-3489-19acbf7cf6d9.png)
やはり「先生」が多いのは納得。
親友の K について、text.py の中で、英文字を除去していたが、これは全角文字だから残ったと思われる。
大事な登場人物なので除去されなくて何故かほっとしている。