第 12 章 演習解答¶
第 12 章「自然言語処理の基礎」の演習問題の解答例です。問題は 第 12 章 演習問題 を参照してください。
演習 12-1: テキストクリーニング¶
import re
text = """
明日のセミナー【締切間近!】の案内です★
詳しくは https://example.com まで!
質問は contact@example.jp までお願いします。
電話番号: 03-1234-5678
"""
# 1. URL の削除
text = re.sub(r"https?://[\w/:%#\$&\?\(\)~\.=\+\-]+", "", text)
# 2. メールアドレスの削除
text = re.sub(r"\S+@\S+", "", text)
# 3. 電話番号の削除
text = re.sub(r"\d{2,4}-\d{2,4}-\d{4}", "", text)
# 4. 装飾記号の削除
text = re.sub(r"[【】★]", "", text)
# 5. 余分な空白の整理 + strip
text = re.sub(r"\s+", " ", text).strip()
print(text)
考察:正規表現で消えるのは、指定したパターンに一致する部分だけです。URL、メールアドレス、電話番号、装飾記号は消えましたが、「!」や「:」、句点は残っています。これらも正規表現で消せますし(書籍本文の preprocess() 関数は記号類をまとめて除去しています)、演習 12-3 のように形態素解析の後で品詞を絞って落とすこともできます。なお、メールアドレスに使った \S+@\S+ は「空白以外の文字列+@+空白以外の文字列」という緩いパターンで、メールアドレス以外の文字列に一致することもあります。もっと厳密にするなら、演習 12-3 で使った [\w\.-]+@[\w\.-]+\.\w+ のようなパターンにします。
演習 12-2: 名詞(内容語)の抽出¶
import MeCab
tagger = MeCab.Tagger()
def extract_by_pos(text, target_pos):
"""指定した品詞の単語のみを抽出する"""
words = []
node = tagger.parseToNode(text)
while node:
if node.surface != "":
pos = node.feature.split(",")[0]
if pos in target_pos:
words.append(node.surface)
node = node.next
return words
text = "機械学習の技術は急速に進歩している"
print("名詞のみ:", extract_by_pos(text, ["名詞"]))
print("名詞・動詞・形容詞:",
extract_by_pos(text, ["名詞", "動詞", "形容詞"]))
# 自分で考えた文でも確認
my_text = "昨日は友達と新宿で美味しいラーメンを食べました"
print("自作文の名詞:", extract_by_pos(my_text, ["名詞"]))
名詞のみ: ['機械', '学習', '技術', '進歩']
名詞・動詞・形容詞: ['機械', '学習', '技術', '進歩', 'し', 'いる']
自作文の名詞: ['昨日', '友達', '新宿', 'ラーメン']
考察:名詞だけに絞ると、「し」「いる」(動詞)が除かれ、内容を表す単語が残ります。「進歩している」の「進歩」はサ変接続の名詞なので、名詞だけに絞っても残ります。文書分類やキーワード抽出では、「する」「いる」のような意味の薄い動詞がじゃまになりやすいため、名詞だけを使うことがよくあります。一方、感情分析では「良い」「悪い」のような形容詞が大事なので、目的に合わせて品詞を選びます。自作文でも、「昨日・友達・新宿・ラーメン」の名詞だけで、文の話題がおおよそわかります。
演習 12-3: 前処理パイプラインの構築¶
import re
import MeCab
texts = [
"AI技術の発展は素晴らしいです!詳細は https://example.jp へ。",
"機械学習を勉強することで、新しいことができるようになります。",
"本日のテーマ:ディープラーニング入門。質問がある方は contact@ml.example.com まで★",
"価格は1980円、送料は300円です。",
]
stopwords = {"する", "ある", "いる", "なる", "こと", "もの", "ため", "とき"}
tagger = MeCab.Tagger()
def preprocess(text):
"""クリーニング→形態素解析→品詞フィルタ→ストップワード除去"""
# 1. テキストクリーニング
text = re.sub(r"https?://[\w/:%#\$&\?\(\)~\.=\+\-]+", "", text) # URL除去
text = re.sub(r"[\w\.-]+@[\w\.-]+\.\w+", "", text) # メールアドレス除去
text = re.sub(r"\d+", "", text) # 数字の除去
text = re.sub(r"[【】★:]", "", text) # 装飾記号の除去
text = re.sub(r"\s+", " ", text).strip() # 空白の正規化
# 2. 形態素解析と品詞フィルタリング + ストップワード除去
words = []
node = tagger.parseToNode(text)
while node:
if node.surface != "":
pos = node.feature.split(",")[0]
if pos in ["名詞", "動詞", "形容詞"] and node.surface not in stopwords:
words.append(node.surface)
node = node.next
return words
for i, text in enumerate(texts, 1):
print(f"テキスト{i}: {preprocess(text)}")
テキスト1: ['AI', '技術', '発展', '素晴らしい', '詳細']
テキスト2: ['機械', '学習', '勉強', '新しい', 'できる', 'なり']
テキスト3: ['本日', 'テーマ', 'ディープ', 'ラーニング', '入門', '質問', '方']
テキスト4: ['価格', '円', '送料', '円']
考察:数字を除去したので、テキスト4では「1980」「300」が消え、「価格」「円」「送料」が残りました。数字の除去は、URL とメールアドレスの除去より後に置きます。先に数字を消すと、数字を含む URL やメールアドレスがパターンに一致しなくなり、一部の文字列が残ることがあります。また、テキスト2では「なり」が残っています。ストップワードには基本形の「なる」しか登録しておらず、表層形(node.surface)で照合しているため、活用した形は除けません。node.feature から基本形を取り出して照合すれば除けます。
演習 12-4: BoW と TF-IDF の比較¶
import MeCab
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
# 本章のサンプル文書コーパス(5文)
documents = [
"機械学習はデータからパターンを学習する技術です",
"深層学習はニューラルネットワークを用いた機械学習の手法です",
"自然言語処理はテキストデータを分析する技術です",
"画像認識は写真や動画から物体を検出する技術です",
"機械学習を使ってテキストを分類することができます",
]
# MeCabで分かち書き
tagger = MeCab.Tagger("-Owakati")
wakati_docs = [tagger.parse(doc).strip() for doc in documents]
# BoWとTF-IDFの両方でベクトル化
count_vec = CountVectorizer()
bow = count_vec.fit_transform(wakati_docs)
tfidf_vec = TfidfVectorizer()
tfidf = tfidf_vec.fit_transform(wakati_docs)
# 特徴的な単語について、文書ごとの値を比較
vocab = list(count_vec.get_feature_names_out())
print("単語 / BoW(各文書の出現回数) / TF-IDF(各文書の値)")
for w in ["技術", "学習", "画像", "分類"]:
i = vocab.index(w)
bow_col = bow.toarray()[:, i]
tfidf_col = tfidf.toarray()[:, i].round(2)
print(f"{w}: {bow_col} {tfidf_col}")
単語 / BoW(各文書の出現回数) / TF-IDF(各文書の値)
技術: [1 0 1 1 0] [0.28 0. 0.27 0.24 0. ]
学習: [2 2 0 0 1] [0.56 0.49 0. 0. 0.26]
画像: [0 0 0 1 0] [0. 0. 0. 0.36 0. ]
分類: [0 0 0 0 1] [0. 0. 0. 0. 0.38]
考察:BoW は出現回数なので、「技術」も「画像」も 1 回出れば同じ 1 です。TF-IDF では、5 文書中 3 文書に出る「技術」は 0.24〜0.28 に下がり、文書4だけの「画像」(0.36)や文書5だけの「分類」(0.38)のほうが高くなります。多くの文書に出る単語は文書を見分ける手がかりにならないので、TF-IDF はその重みを小さくします。そのため文書分類や検索では、TF-IDF のほうが BoW より役に立つことが多くなります。