LDA (Latent Dirichlet Allocation) を使って英文記事をクラスタリングする（前処理編）

自然言語処理の研究を始めるにあたって LDA に触れておこうと思ったので，ついでに簡単にまとめてみます．
この分野はまだ学び始めたばかりなので，もし間違いがあれば指摘していただけると嬉しいです．
実行環境には [Google Colaboratory](https://colab.research.google.com/?hl=ja) を使用しました．

# LDA について
トピックモデルは，テキストデータの集合から得られる抽象的なトピックを見つけ出すための，確率モデルの一種です．LDA (Latent Dirichlet Allocation) はそのようなトピックモデルの一つで，あるドキュメントを適当なトピックに分類するために使われます．ドキュメント毎のトピックが占める割合，及びトピック毎の単語が占める割合をそれぞれ算出します．

LDA でできることを簡単に述べると，

- 文書の集合はどんなトピックに分類できるのか
- ある文書にはどんなトピックがどの程度含まれているか
- あるトピックはどの単語がどの程度構成しているのか

のようなことがわかります．


LDA についてはこのあたりの記事で紹介されています．

[トピックモデル（LDA）で初学者に分かりづらいポイントについての解説](http://acro-engineer.hatenablog.com/entry/2017/12/11/120000)

[【Python】トピックモデル（LDA）](https://qiita.com/kenta1984/items/b08d5caeed6ed9c8abf1)

### LDA の入力データについて
テキストデータを LDA で使用するためのデータの加工方法として，BOW (Bag of Words) や tf-idf があります．これらは以下の記事で紹介されています．

[Bag of Wordsについて書いてみる](https://www.pytry3g.com/entry/2018/03/21/181514#1-%E6%95%B0%E5%80%A4%E5%A4%89%E6%8F%9B)

[tf-idfについてざっくりまとめ_理論編](https://dev.classmethod.jp/machine-learning/yoshim_2017ad_tfidf_1-2/)

# 実際に使ってみる
ここでは以下の記事を参考にしながら，英語のニュース見出しのクラスタリングをやっていこうと思います．
https://towardsdatascience.com/topic-modeling-and-latent-dirichlet-allocation-in-python-9bf156893c24

### データの準備
今回使用するデータはここからダウンロードします（要：kaggle へのサインイン）
https://www.kaggle.com/therohk/million-headlines/data

データを Python で利用できるように読み込みます．

```python:
import pandas as pd

data = pd.read_csv('abcnews-date-text.csv', error_bad_lines=False);
data_text = data[['headline_text']]
data_text['index'] = data_text.index
documents = data_text
```

データの中身がどうなっているのか少し見てみると，およそ110万件のドキュメント（ニュース見出し）があって，そのうち上から5件が上の表のようになっていることがわかります．

```python:
print(len(documents))
print(documents[:5])
```

**1103663**

|   |                                     headline_text  |index|
|--:|---------------------------------------------------:|----:|
| 0 | aba decides against community broadcasting lic...  |    0|
| 1 |    act fire witnesses must be aware of defamation  |    1|
| 2 |    a g calls for infrastructure protection summit  |    2|
| 3 |          air nz staff in aust strike for pay rise  |    3|
| 4 |     air nz strike to affect australian travellers  |    4|



### 前処理
次の手順でデータを処理します．

- ドキュメントのトークン化：具体的には，テキストを文単位に，文を単語単位にそれぞれ分割します．大文字の単語を小文字に修正し，句読点を取り除きます．
- 3文字未満の単語の除去
- ストップワードの除去：ストップワードとは，頻出あるいは一般的すぎて，利用価値が低いと考えられる単語のことをいいます．ex. the, a, for
- 単語を見出し語化 (lemmatize) ：具体的には，三人称の単語は一人称に，過去時制及び未来時制の動詞は現在時制に変更されます．
- 単語の語幹抽出 (stemming)

処理には gensim と nltk を使います．

```python:
import gensim
from gensim.utils import simple_preprocess
from gensim.parsing.preprocessing import STOPWORDS
from nltk.stem import WordNetLemmatizer, SnowballStemmer
from nltk.stem.porter import *
import numpy as np
np.random.seed(2018)

import nltk
nltk.download('wordnet')
```

lemmatize の例を確認してみます．

```python:
print(WordNetLemmatizer().lemmatize('went', pos='v'))
```
**go**

stemming の例を確認してみます（結果は省略）．

```python:
stemmer = SnowballStemmer('english')
original_words = ['caresses', 'flies', 'dies', 'mules', 'denied','died', 'agreed', 'owned', 
           'humbled', 'sized','meeting', 'stating', 'siezing', 'itemization','sensational', 
           'traditional', 'reference', 'colonizer','plotted']
singles = [stemmer.stem(plural) for plural in original_words]
pd.DataFrame(data = {'original word': original_words, 'stemmed': singles})
```

lemmatizeとstemming をする関数を定義し，前に述べた手順で前処理をするための関数を作成します．

```python:
def lemmatize_stemming(text):
  return stemmer.stem(WordNetLemmatizer().lemmatize(text, pos='v'))

def preprocess(text):
  result = []
  for token in gensim.utils.simple_preprocess(text):
    if token not in gensim.parsing.preprocessing.STOPWORDS and len(token) > 3:
      result.append(lemmatize_stemming(token))
  return result
```

ドキュメントを一つ選択して前処理の前後比較をしてみましょう．

```python:
doc_sample = documents[documents['index'] == 4310].values[0][0]

print('original document: ')
words = []
for word in doc_sample.split(' '):
  words.append(word)
print(words)
print('\n\ntokenized and lemmatized document: ')
print(preprocess(doc_sample))
```

**original document:**
**['rain', 'helps', 'dampen', 'bushfires']**


**tokenized and lemmatized document:**
**['rain', 'help', 'dampen', 'bushfir']**

きちんと動作していることが確認できたので，全ドキュメントの前処理をします．完了には少し時間がかかると思います．

```python:
processed_docs = documents['headline_text'].map(preprocess)
```

長くなってしまうので，今回はここまでとします．
次回は，LDA の入力データ (BOW, TF-IDF) の作成と，LDA モデルの作成について書こうと思います．

次回 -> [LDA (Latent Dirichlet Allocation) を使って英文記事をクラスタリングする（分類編）](https://qiita.com/benyamerumo/items/394d5db0d596ce46b983)