Python for NLPの連載は今回で13回目です。
前回は、コーパスに含まれる単語のTF-IDFベクトルとユーザー入力のコサイン類似度を用いて、応答を生成する簡単なルールベースのチャットボットの作り方を見ました。
TF-IDFモデルは、基本的に単語を数値に変換するために使用されました。
この記事では、テキストを数値に変換するもう一つの非常に有用なモデル、すなわちBag of Words (BOW)を研究する。
機械学習や深層学習などの統計的アルゴリズムのほとんどは数値データを扱うので、テキストを数値に変換する必要がある。
この点については、いくつかのアプローチが存在する。
しかし、最も有名なものは、Bag of Words、TF-IDF、word2vecである。
Scikit-LearnやNLTKなど、これらの手法を1行のコードで実装できるライブラリがいくつか存在するが、これらの単語埋め込み手法の背後にある動作原理を理解することが重要である。
そのための最良の方法は、Pythonでゼロからこれらの技術を実装することであり、これが今日私たちが行うことです。
この記事では、PythonでゼロからBag of Wordsのアプローチを実装する方法を説明します。
次回は、TF-IDFの手法をPythonでゼロから実装する方法を説明します。
コーディングの前に、まず、Bag of Wordsアプローチの背後にある理論を見てみましょう。
バッグ・オブ・ワーズ・アプローチの理論
まず、Bag of Words Approachを理解するために、例を挙げて説明する。
例えば、3つの文からなるコーパスがあるとする。
- 私はサッカーをするのが好きです。
- “テニスをするために外に出たのか?”
- “ジョンと私はテニスをします”
ここで、上記のデータに対して、統計的手法を使ってテキストの分類やその他のタスクを実行しなければならないとすると、統計的手法は数字しか扱えないので、それはできない。
そこで、これらの文章を数値に変換する必要がある。
ステップ1:文のトークン化
この点に関する最初のステップは、コーパスに含まれる文をトークンまたは個々の単語に変換することである。
以下の表を見てほしい。
| 文1|文2|文3|||||。
| — | — | — |
| 私|は|ジョン|を|しました|。
| あなたが好きです。
ステップ2:単語の出現頻度の辞書を作成する
次のステップは、コーパスに含まれるすべての単語をキーとし、その単語の出現頻度を値とする辞書を作成することである。
言い換えれば、コーパスに含まれる単語のヒストグラムを作成する必要があるのです。
次の表を見てください。
| 単語|出現頻度||||を表す。
上の表では、コーパスに含まれる各単語をその出現頻度とともに見ることができる。
例えば、「play」という単語はコーパスに3回(各文章に1回)出現しているので、その頻度は3であることが分かるだろう。
このコーパスには3つの文しかないので、すべての単語を含む辞書を作成するのは簡単である。
現実の世界では、辞書には何百万もの単語が含まれる。
その中には、頻度が非常に低い単語も含まれます。
頻度の低い単語は、あまり有用ではないので、削除される。
頻度の低い単語を削除する方法の1つは、単語頻度辞書を頻度の高い順に並べ替えて、ある閾値より高い頻度を持つ単語をフィルタリングすることです。
単語頻度辞書をソートしてみましょう。
| 単語|頻度|の順で並べ替えてみましょう。
ステップ3:Bag of Wordsモデルの作成
Bag of Words モデルを作成するには、列が辞書の最頻出単語に対応し、行がドキュメントまたはセンテンスに対応する行列を作成する必要があります。
例えば、辞書から頻出する8個の単語を抽出するとします。
そうすると、文書頻度行列は次のようになります。
| プレイ|テニス|へ|私|フットボール|した|あなた|行く|||||。
上記の行列がどのように作成されるかを理解することが重要です。
上記のマトリックスでは、最初の行が最初の文に対応します。
1では、”play “という単語が1回出てきますので、1列目に1を加えました。
2列目の単語は “Tennis “で、これは最初の文には出てこないので、1番目の文の2列目に0を加えました。
同様に、2番目の文では、「Play」と「Tennis」の両方の単語が1回ずつ出てくるので、最初の2列で1を加えました。
しかし、5列目では、2文目に「Football」という単語は出てこないので、0を足す。
このようにして、上記の行列のすべてのセルは、単語の出現によって0か1のどちらかで埋められる。
最終的な行列は、bag of wordsモデルに対応する。
各行には、対応する文の数値表現が表示されている。
例えば、最初の行は文1の数値表現を示している。
この数値表現は、統計モデルの入力として使用することができます。
理論的な説明はもう十分なので、私たち独自のBag of Wordモデルをゼロから実装してみよう。
PythonによるBag of Wordsモデル
まず、Bag of Wordsモデルを作成するために必要なのはデータセットである。
前節では、3つの文からなるBag of Wordsモデルを手動で作成しました。
しかし、実世界のデータセットは数百万の単語からなる巨大なものです。
ランダムなコーパスを探すのに最適なのはWikipediaである。
最初のステップでは、自然言語処理に関するWikipediaの記事をスクレイピングします。
その前に、必要なライブラリをインポートしましょう。
import nltk
import numpy as np
import random
import string
import bs4 as bs
import urllib.request
import re
前回の記事で行ったように、Wikipediaのデータを解析するためにBeautifulsoup4ライブラリを使用する予定です。
さらに、Pythonの正規表現ライブラリである re
を使って、テキストの前処理を行います。
次に、自然言語処理に関するWikipediaの記事をスクレイピングする必要があります。
raw_html = urllib.request.urlopen('https://en.wikipedia.org/wiki/Natural_language_processing')
raw_html = raw_html.read()
article_html = bs.BeautifulSoup(raw_html, 'lxml')
article_paragraphs = article_html.find_all('p')
article_text = ''
for para in article_paragraphs:
article_text += para.text
上のスクリプトでは、Wikipediaの記事の生のHTMLをインポートしています。
生のHTMLから、段落テキスト内のテキストをフィルタリングする。
最後に、すべての段落を連結して完全なコーパスを作成する。
次のステップは、コーパスを個々の文に分割することである。
そのために、NLTKライブラリの sent_tokenize
関数を利用する。
corpus = nltk.sent_tokenize(article_text)
このテキストには句読点が含まれています。
単語頻度辞書に句読点が含まれないようにしたい。
以下のスクリプトでは、まずテキストを小文字に変換し、その後、テキストから句読点を削除しています。
句読点を削除すると、複数の空白が生じることがあります。
そこで、正規表現を用いて空白を削除します。
次のスクリプトを見てください。
for i in range(len(corpus )):
corpus [i] = corpus [i].lower()
corpus [i] = re.sub(r'W',' ',corpus [i])
corpus [i] = re.sub(r's+',' ',corpus [i])
上のスクリプトでは、コーパスの各文を繰り返し処理し、文を小文字に変換し、テキストから句読点と空白を削除している。
コーパスに含まれる文の数を調べてみよう。
print(len(corpus))
出力は49と表示される。
コーパスから1文を出力してみよう。
print(corpus[30])
出力。
in the 2010s representation learning and deep neural network style machine learning methods became widespread in natural language processing due in part to a flurry of results showing that such techniques 4 5 can achieve state of the art results in many natural language tasks for example in language modeling 6 parsing 7 8 and many others
この文章には特殊文字や複数の空白文字が含まれていないことがわかるだろう。
これで私たちは自分たちのコーパスを手に入れた。
次のステップは、コーパス中の文をトークン化し、コーパス中の単語とその頻度を対応させた辞書を作ることである。
次のスクリプトを見てみよう。
wordfreq = {}
for sentence in corpus:
tokens = nltk.word_tokenize(sentence)
for token in tokens:
if token not in wordfreq.keys():
wordfreq[token] = 1
else:
wordfreq[token] += 1
上のスクリプトでは、wordfreq
という辞書を作成している。
次に、コーパスに含まれる各文章を繰り返し処理する。
文は単語にトークン化される。
次に、文中の各単語を繰り返し処理する。
もし、その単語が wordfreq
という辞書に存在しなければ、その単語をキーとして追加し、その単語の値を 1 とする。
もし、その単語がすでに辞書に存在すれば、単純にキーの数を 1 だけ増やす。
私のようにSpyderエディタで上記を実行している場合は、右側の変数エクスプローラーで wordfreq
変数をクリックします。
このような辞書が表示されるはずです。
Key “欄に単語が、”Value “欄にその出現頻度が表示されているのがわかると思います。
理論のセクションで述べたように、タスクによっては、すべての単語が有用とは限りません。
巨大なコーパスでは、数百万語の単語が存在することもある。
最も頻出する単語をフィルターにかけることができる。
私たちのコーパスには全部で535の単語があります。
最も頻出する200語まで絞り込んでみよう。
そのためには、Pythonの heap
ライブラリを利用することができる。
以下のスクリプトを見てほしい。
import heapq
most_freq = heapq.nlargest(200, wordfreq, key=wordfreq.get)
これで、most_freq
リストには、最も頻出する200の単語とその出現頻度が含まれるようになりました。
最後のステップは、コーパスの文を対応するベクトル表現に変換することである。
考え方は簡単で、most_freq
の各単語に対して、その単語が文中に存在すれば1が追加され、そうでなければ0が追加されます。
sentence_vectors = []
for sentence in corpus:
sentence_tokens = nltk.word_tokenize(sentence)
sent_vec = []
for token in most_freq:
if token in sentence_tokens:
sent_vec.append(1)
else:
sent_vec.append(0)
sentence_vectors.append(sent_vec)
上のスクリプトでは、空のリスト sentence_vectors
を作成し、コーパスに含まれる全ての文のベクトルを格納する。
次に、コーパスの各文に対して繰り返し処理を行い、個々の文に対して空のリスト sent_vec
を作成する。
同様に、文のトークン化も行う。
次に、most_freq
リストにある各単語を繰り返し、その単語が文のトークンに存在するかどうかを確認する。
もしその単語が文の一部であれば、個々の文のベクトル sent_vec
に 1 が追加され、そうでなければ 0 が追加される。
最後に、文のベクトルは全ての文のベクトルを含むリスト sentence_vectors
に追加される。
基本的には、この sentence_vectors
が単語袋モデルとなる。
しかし、理論編で見た単語袋モデルは行列の形式であった。
我々のモデルはリストのリストである。
このスクリプトを使って、モデルを行列形式に変換することができます。
sentence_vectors = np.asarray(sentence_vectors)
基本的に、以下のスクリプトでは、asarray
関数を使ってリストを2次元のnumpy配列に変換しています。
Spyder エディタの変数エクスプローラで sentence_vectors
変数を開くと、次のような行列が表示されるはずです。
0と1を含むBag of Wordsモデルが表示されていることがわかります。
結論
Bag of Wordsモデルは、TF-IDFとWord2Vecを加えた3つの最もよく使われる単語埋め込みアプローチの1つです。
この記事では、PythonでゼロからBag of Wordsのアプローチを実装する方法を見ました。
このアプローチの理論は、このアプローチを実装するためのハンズオンコードとともに説明されました。
次回は、TF-IDF法をPythonでゼロから実装する方法を説明します。