人工知能に言語を理解させる!?自然言語処理に重要なデータの前処理をPythonで徹底解説

今回は、自然言語処理に重要なデータの前処理について解説していきます。

形態素解析

まず、自然言語処理において大事な、形態素解析から説明していきます。

例えば「すもももももももものうち」といった言葉を、形態素解析すると、「すもも」「も」「もも」「も」「もも」「の」「うち」のような分かち書きとなり、「意味が分かるような、最小単位に分ける」ことを形態素解析と言います。

何故このような形にするかというと、英語は単語ごとに区切られているので単語同士の関係性がわかりやすいですが、日本語の場合だと、どこでどの単語が区切られているかがぱっと見ではわかりません。

自然言語処理などでは単語をベクトル表現することが多く、そのベクトル表現のためにどうしても単語ごとの区切りが必要になってきます。


単語のベクトル表現

それではどのようにして、単語をベクトル表現していくのか説明します。

例えば上の「すもも」「も」「もも」「も」「もも」「の」「うち」は、全部で7つの区切りです。
そこで最初に7つ全てに単語IDをつけます。

「すもも」ー>ID1
「も」ー>ID2
「もも」ー>ID3
「も」ー>ID2
「もも」ー>ID3
「の」ー>ID4
「うち」ー>ID5

重複している言葉、つまり、「も」や「もも」は同じIDになります。

One Hot Vector

続いて、このIDがついた単語からOne Hot Vectorといった、単語をベクトルで表現したものを作っていきます。
例えば、上の単語は全部で5つのIDで構成された辞書と見ることができます。

そこで、単語IDの数だけゼロで埋めたベクトルを用意します。

「0、0、0、0、0」

続いて、単語ID1の「すもも」は、「1、0、0、0、0」という「固有」のベクトルにします。
そうやって、各IDごとにベクトルを作っていくと、

単語ID単語One Hot Vector
ID1すもも[1,0,0,0,0]
ID2[0,1,0,0,0]
ID3もも[0,0,1,0,0]
ID4[0,0,0,1,0]
ID5うち[0,0,0,0,1]

表のように、各単語が固有のベクトルで表現できるようになります。

Pythonで試す

上の説明の流れを、Pythonでやってみたいと思います。
尚、今回形態素解析に使用するライブラリは「janome」を使います。
理由は「pip」で簡単にインストールできて、お手軽に試せるからです。

ローカル環境になければインストールします。

pip install janome

続いて、分かち書きを行います。

# janomeをインポート
from janome.tokenizer import Tokenizer# 形態素解析をするインスタンスを作成。
t = Tokenizer()# 例文は「すもももももももものうち」。
s = ‘すもももももももものうち’# 解析します。
tokens = t.tokenize(s)# トークンオブジェクトのリストから一つずつ解析結果をプリントします。
for token in t.tokenize(s):
print(token)”””
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
“””

各トークンオブジェクトには、形態素や品詞、読みといった情報が入っています。

品詞ごとに分けたい場合はこの品詞情報を使います。
とりあえず、オブジェクトのsurface属性から、分かち書きの結果だけを取り出します。

import numpy as np

# 分かち書きの結果の単語をリストにします。
word_list=[token.surface for token in tokens]

# IDから単語へ、逆に単語からIDへアクセスできるよう2つの辞書を作ります。
i2w=dict((i,w) for i,w in enumerate(sorted(list(set(word_list)))))
w2i=dict((w,i) for i,w in enumerate(sorted(list(set(word_list)))))

# 確認してみましょう。

print(i2w)
“””
{0: ‘うち’, 1: ‘すもも’, 2: ‘の’, 3: ‘も’, 4: ‘もも’}
“””

print(w2i)
“””
{‘うち’: 0, ‘すもも’: 1, ‘の’: 2, ‘も’: 3, ‘もも’: 4}
“””

# 語彙数を辞書の長さから取得します。
vocab_size=len(w2i)

# 単語リストをNumpy配列にします。
N=np.array(list(w2i.values()))

# 単語数 x 語彙数 のゼロ埋め行列を作ります。
one_hot = np.zeros((N.shape[0], vocab_size), dtype=np.int32)

print(one_hot)
“””
[[0 0 0 0 0][0 0 0 0 0][0 0 0 0 0][0 0 0 0 0][0 0 0 0 0]]“””

# 行には単語が割り振られてる考え、
# 列に一意の数字を割り振ると考えます。
for i, word_id in enumerate(list(w2i.values())):
one_hot[i, word_id] = 1

print(one_hot)
“””
[[1 0 0 0 0][0 1 0 0 0][0 0 1 0 0][0 0 0 1 0][0 0 0 0 1]]“””

列には一つずつズレて、1がつけられています。
各行は、同じ値をもたない固有のベクトル表現とみることができます。
このように、単語の関係性を数値化することによって、 ニューラルネットワークなどで学習ができるようなります。

細かな前処理

本来ならこの章の細かな前処理をおこなってから、 単語や文のベクトル表現をしますが、 学習の種類、目的によって使わないことも含まれているので後回しにしました。

正規化

日本語には「2」と「2」、「ネコ」と「ネコ」など 半角文字と全角文字があります。
これらが混ざっていると、同じ意味なのに違う単語IDに振り分けられてしまうことが起こります。
それを防ぐために単語辞書を作るのと同時に、または分かち書きする前に正規化してしまいましょう。
neologdn上の、PyPiリンクのライブラリは、半角や全角文字を統一してくれる便利な機能をもっています。
これを使って例を示します。

# まずインストール
pip install neologdn
# neologdnをインポート
import neologdn# 半角のカナを全角に直します。
neologdn.normalize(“ネコカワイイ”)
# => ‘ネコカワイイ’# 全角の記号を半角に直します。
neologdn.normalize(“・()「」!?@#”)
# => ‘・()「」!?@#’# 長音短縮
# 様々な長さのウェーイを統一します。
weei=[“ウェーーーーイ”,”ウェーーイ”,”ウェーーーーーーーーイ”]for w in weei:
print(neologdn.normalize(w))
# => ウェーイ
# ウェーイ
# ウェーイ# チルダを削除できます。
neologdn.normalize(“ウェ~∼∾〜〰~イ”)
# => ‘ウェイ’# ハイフンの統一
neologdn.normalize(“˗֊‐‑‒–⁃⁻₋−”)
# => ‘-‘# 全角英数字を半角に直し、スペースを詰めます。
neologdn.normalize(“   Python   必 読 書   ”)
# => ‘Python必読書’# 同じ文字の繰り返しを何回にするかを指定できます。
neologdn.normalize(“勇者あああああああよ”, repeat=4)
# => ‘勇者ああああよ’# 色々組み合わせることができます。
# ‘勇者「ああああ」よ。にしたい。’
text=’勇者「ぁぁぁあああ~~~ぁああぁああ」よ。’text_normalized=neologdn.normalize(text)
print(text_normalized)
# => 勇者「ぁぁぁあああぁああぁああ」よ。text_normalized=text_normalized.replace(‘ぁ’,”)
print(text_normalized)
# => 勇者「あああああああ」よ。text_normalized=neologdn.normalize(text_normalized,repeat=4)
print(text_normalized)
# => 勇者「ああああ」よ。# 全角「」が変換されないので、
# Replaceメソッドを使う
text_normalized=text_normalized.replace(‘「’,’「’).replace(‘」’,’」’)
print(text_normalized)
# => 勇者「ああああ」よ。# ReplaceメソッドやReライブラリ等で補っていけば良いと思います。

絵文字

絵文字も除去する必要がある場合があります。

emoji

上のPyPiリンクのライブラリは、絵文字を取り扱えるようにしてくれる便利な機能をもっています。
これを使って例を示します。

# まずインストール
pip install emoji –upgrade
codes=[emoji.demojize(c) for c in chars]print(codes)
# => [‘:grinning_face:’,’:grinning_face_with_big_eyes:’,’:grinning_face_with_smiling_eyes:’]# 絵文字の除去
with_emoji=[‘グリーティングフェイス😀ビッグアイズ😃スマイリングフェイス😄’]remove_emoji = ”.join(list(filter(lambda x: x not in emoji.UNICODE_EMOJI, test)))
print(remove_emoji)
# => ‘グリーティングフェイスビッグアイズスマイリングフェイス’

Stop Word

Stop Wordとは、全文検索などで一般的すぎて検索の邪魔になる単語をいいます。
英語なら「The」や「a」など、日本語なら「て」「に」「を」「は」などです。
これらを取り除くことで、計算量の節約、学習精度を上げることができます。

ただ、個人の意見ですが、あくまで全文検索、つまり検索エンジンのアルゴリズムからきているので、100%自然言語処理の学習において正しいとは言い切れません。
なぜなら、「私は今日朝10時に起きた。」という文があり、「私に今日を朝10時に起きた。」や「私を今日は朝10時に起きた。」になると不自然な文になります。
色々考え方ややり方があると思いますが、とりあえずStop Wordの除去をPythonで実装してみましょう。
文章の題材は、ここまでの説明文にします。

import urllib
from janome.tokenizer import Tokenizer# ストップワードをダウンロード
url = ‘http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt’
urllib.request.urlretrieve(url, ‘stop_word.txt’)with open(‘stop_word.txt’, ‘r’, encoding=’utf-8′) as file:
stop_word_list = [word.replace(‘\n’, ”) for word in file.readlines() if word.replace(‘\n’, ”)!=”]print(stop_word_list)
# => [‘あそこ’, ‘あたり’,………,’同じ’, ‘感じ’]# 上の説明文をそのまま文字列として格納する。
text_data=’Stop Wordとは、…….省略……. Pythonで実装してみましょう。’# 分かち書きを行う。# 形態素解析をするインスタンスを作成。
t = Tokenizer()# 解析します。
tokens = t.tokenize(text_data)

# 分かち書きの結果の単語をリストにします。
word_list=[token.surface for token in tokens]

split_stop_word=[]for w in word_list:
if w not in stop_word_list and w != ‘ ‘:
split_stop_word.append(w)

print(split_stop_word)
# => [‘Stop’, ‘Word’, ‘、’, ‘全文’, ‘検索’,………,’Python’, ‘実装’, ‘み’, ‘ましょ’, ‘。’]

品詞ごとに分類

続いて品詞、例えば「名詞」「形容詞」「動詞」に絞って分類してみます。
感情分析や、レコメンドシステムを作る際に便利です。

# pandasをインポートします。
import pandas as pd# 先程と同様、janomeによる形態素解析を行う。
tokens = t.tokenize(text_data)# 名詞、形容詞、動詞に分けてデータフレームに直す関数を作る。
def split_part_of_speech(tokens):
word_list=[]for token in tokens:
pos=token.part_of_speech.split(‘,’)
if ‘名詞’ == pos[0] or ‘形容詞’ == pos[0] or ‘動詞’ == pos[0]:
word_list.append([token.surface,pos[0]])
return pd.DataFrame(word_list,columns=[‘単語’,’品詞’])# 使ってみる。
df=split_part_of_speech(tokens)print(df)
“””
単語 品詞
0 Stop 名詞
1 Word 名詞
……………
75 除去 名詞
76 Python 名詞
77 実装 名詞
78 し 動詞
79 み 動詞
“””# さらにStop Wordも併用してみます。
remove_stop_word_df=df[df[‘単語’].apply(lambda x: x not in stop_word_list)==True]

print(df.shape)
# => (80, 2)

# 12単語減りました。
print(remove_stop_word_df.shape)
# => (68, 2)

# indexが飛び飛びなので、修正します。
remove_stop_word_df=remove_stop_word_df.reset_index(drop=True)

print(remove_stop_word_df)
“””
単語 品詞
0 Stop 名詞
1 Word 名詞
2 全文 名詞
……………….
64 Python 名詞
65 実装 名詞
66 し 動詞
67 み 動詞
“””

おわりに

今回は以上ですが、その他にもTF-IDFによる単語頻出度による振り分けなど、様々な前処理の方法があります。

文章や言語、目的によって前処理の方法も違ってきます。
様々な文章にチャレンジして、適切な前処理を身につけてください。

もっと深く理解されたい方は、ビジネス向けAI完全攻略セミナーを受講してみてください。
最後までお読み頂きありがとうございました。

最新情報をチェックしよう!