第 11 回 自然言語処理の基礎
第 11 回は、自然言語処理の概要と基本について学びます。
1.1 形態素解析
形態素解析とは、文を意味を持つ最小単位の単語(形態素)に分割する操作のことです。日本語の文章に対して自然言語処理を適用する場合、文章を単語単位に分割する分かち書き(形態素解析)が必要になります。形態素解析器には、MeCab や Juman++、Janome などがよく使用されます。
例えば、「吾輩は猫である」という文は、「吾輩」「は」「猫」「で」「ある」に分割されます。
1.1.1 MeCab と辞書データのインストール
形態素解析を実際に実行するために、MeCab と辞書データをインストールしましょう。
Google Colaboratory で以下のコマンドを実行することで、インストールすることができます。
ここでは日本語の大規模な形態素解析用の辞書 UniDic
の軽量版である unidic-lite
をインストールしています。
1.1.2 MeCab で形態素解析
インストールが完了したら、MeCab を使って形態素解析を行ってみましょう。
import MeCab
# MeCab のインスタンス生成
mecab = MeCab.Tagger()
# 解析するテキスト
text = "すもももももももものうち"
# 形態素解析を実行し、結果を表示
result = mecab.parse(text)
print(result)
上記のコードを実行すると、以下のようになります。正しく形態素解析ができることを確認してください。
すもも スモモ スモモ 李 名詞-普通名詞-一般 0
も モ モ も 助詞-係助詞
もも モモ モモ 桃 名詞-普通名詞-一般 0
も モ モ も 助詞-係助詞
もも モモ モモ 桃 名詞-普通名詞-一般 0
の ノ ノ の 助詞-格助詞
うち ウチ ウチ 内 名詞-普通名詞-副詞可能 0
EOS
1.1.3 形態素解析のオプション
MeCab を用いて形態素解析を行うとき、いくつかのオプションを指定することができます。 例えば、オプションに -Owakati
を指定すると、結果を分かち書きの形式で出力することができます。
出力結果は以下のようになります。
ただし、辞書が unidic-lite
である場合、使用することができるオプションは大きく制限されます。さまざまなオプションを使用したい場合や、より高精度な形態素解析を実行したい場合は、各自で辞書の変更を検討してください。
1.1.4 特定の品詞の抽出
形態素解析ができたら、特定の品詞を抽出してみましょう。文書の解析でよく利用される重要な品詞は、「名詞」「形容詞」「動詞」などです。
まずは、以下のように parseToNode()
メソッドを用いて、形態素解析の結果をノードに変換し、単語リストを生成することを考えます。ノードの surface
属性には単語が、feature
属性には品詞の情報が入っています。
import MeCab
# MeCab のインスタンス生成
mecab = MeCab.Tagger()
# 解析するテキスト
text = "すもももももももものうち"
# 形態素解析を実行し、ノードに変換
node = mecab.parseToNode(text)
# 単語を格納するためのリスト
words = []
# ノードがなくなるまで解析結果を表示
while node:
if node.surface: # surface 属性(単語)が存在する場合
words.append(node.surface) #リストに追加
node = node.next # 次のノードに移動
print(words)
次に、各ノードの feature
属性を参照し、「名詞」のときのみ辞書に追加してみましょう。品詞情報にはカンマ区切りで「普通名詞」や「一般」なども含まれるので、以下のように split()
メソッドで文字を分割し、最初の要素の確認を行います。
import MeCab
# MeCab のインスタンス生成
mecab = MeCab.Tagger()
# 解析するテキスト
text = "すもももももももものうち"
# 形態素解析を実行し、ノードに変換
node = mecab.parseToNode(text)
# 形態素を格納するためのリスト
words = []
# ノードがなくなるまで解析結果を表示
while node:
pos = node.feature.split(",")[0] # 品詞を取得
if pos == "名詞": # 品詞が「名詞」であった場合
words.append(node.surface) #リストに追加
node = node.next # 次のノードに移動
print(words)
練習
「青空文庫」「livedoorニュース」「Wikipedia」などから文書データを取得し、MeCab を用いた形態素解析を行い、品詞が「名詞」「形容詞」「動詞」のものだけ抽出しなさい。
1.1.5 ファイルの読み込み
スクレイピングやダウンロード操作などで取得したテキストデータを利用したい場合は、以下のようにしてファイルの読み込みを行います。
# ファイルを読み込む
with open("wagahai.txt", "r", encoding="utf-8") as f:
text = f.read()
# ファイルの内容を表示
print(text[:100])
一
吾輩は猫である。名前はまだ無い。
どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあ
練習用データとして、仮想ニュースの文書データセットをアップロードしておきました。必要に応じて使用してください。
1.2 BoW (Bag of Words)
形態素解析の次は、BoW (Bag of Words) を作成してみましょう。
1.2.1 辞書の作成と頻度のカウント
辞書 word_counts
を作成し、抽出した単語の数をカウントしていきます。
word = node.surface # 単語を取得
# BoW を作成
if word in word_counts:
word_counts[word] += 1
else:
word_counts[word] = 1
# 頻度順にソート
sorted_word_counts = sorted(word_counts.items(), key=lambda x: x[1], reverse=True)
# 頻出語を表示(上位10語)
for word, count in sorted_word_counts[:10]:
print(f"{word}: {count}")
上記のソースコードを形態素解析を実行するコードに組み込み、仮想ニュースのうちの教育ニュース(education.txt)の BoW を作成すると、頻出語の結果は以下のようになります。
1.2.2 Counter クラスの利用
前節では辞書を使った BoW の作成方法を紹介しましたが、collections
モジュールの Counter
クラスを使うことで、より簡単に BoW を作成することもできます。
# 頻出語をカウント
word_counter = Counter(words)
# 頻出語を表示(上位10語)
for word, count in word_counter.most_common(10):
print(f"{word}: {count}")
1.2.3 CountVectorizer クラスによるベクトル化
テキストデータを効率よくベクトル化するには、scikit-learn
ライブラリの feature_extraction.text
モジュールから、CountVectorizer
クラスをインポートして使います。解析対象の単語の配列を、join()
メソッドによりスペースで連結させ、CountVectorizer
のインスタンスを生成し、fit_transform()
メソッドを呼び出します。以下はコード例です。words_wagahai
には、「吾輩は猫である」を形態素解析した結果(名詞の配列)が入っているものとします。
import MeCab
from sklearn.feature_extraction.text import CountVectorizer
with open("education.txt", "r") as f:
text = f.read()
# 形態素解析を実行
node = mecab.parseToNode(text)
# 名詞の抽出
nouns = []
while node:
pos = node.feature.split(",")[0]
if pos == "名詞":
nouns.append(node.surface)
node = node.next
# 名詞の配列をスペースで結合
nouns_text = ' '.join(nouns)
# 文書のリスト(ここでは単一の文書)
documents = [nouns_text]
# CountVectorizer のインスタンスを作成
vectorizer = CountVectorizer()
# 文書の BoW 行列を計算
bow_matrix = vectorizer.fit_transform(documents)
# BoW 行列を表示
print("BoW Matrix:")
print(bow_matrix.toarray())
# 単語リストを表示
print("\nFeature Names:")
print(vectorizer.get_feature_names_out())
1.3 TF-IDF
次は TF-IDF を計算してみましょう。
TF-IDF は、scikit-learn
ライブラリの feature_extraction.text
モジュールから、TfidfVectorizer
クラスをインポートして使うと簡単に実行することができます。
まず、解析対象の単語の配列を、join()
メソッドによりスペースで連結させます。そして、TfidfVectorizer()
クラスのインスタンスを生成し、fit_transform()
メソッドで TF-IDF の行列を計算します。また、get_feature_names_out()
メソッドによって、単語リスト(特徴語)を取得することもできます。
以上をまとめると、以下のようなコードになります。教育ニュース(education.txt)の文書データを段落ごとに分割し、TF-IDF の行列を計算しています。
import MeCab
from sklearn.feature_extraction.text import TfidfVectorizer
# 名詞を抽出する関数
def extract_nouns(text):
node = mecab.parseToNode(text)
nouns = []
while node:
pos = node.feature.split(",")[0]
if pos == "名詞":
nouns.append(node.surface)
node = node.next
return " ".join(nouns)
# ファイルを読み込む関数
def read_file(file_path):
with open(file_path, "r", encoding="utf-8") as f:
data = f.read()
texts = data.split("\n\n") # 段落ごとに分割
return texts
# ファイルを読み込み
texts_education = read_file("/content/drive/MyDrive/0_news/education.txt")
# 各段落から名詞を抽出
nouns_education = [extract_nouns(text) for text in texts_education]
# 文書のリスト
documents = nouns_education
# TfidfVectorizer のインスタンスを作成
vectorizer = TfidfVectorizer()
# 文書の TF-IDF 行列を計算
tfidf_matrix = vectorizer.fit_transform(documents)
# 単語リスト(特徴語)を取得
feature_names = vectorizer.get_feature_names_out()
# TF-IDF 行列を表示
print("TF-IDF Matrix:")
print(tfidf_matrix.toarray())
# 単語リストを表示
print("\nFeature Names:")
print(feature_names)
TF-IDF Matrix:
[[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. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]]
Feature Names:
['000' '10' '20' ... '魅力' '黒板' '50']
TF-IDF のスコアを降順にソートし、特定の文書(今回は文書 ID=0)の上位 10 語を表示したい場合、以下のようなコードで実現できます。
# TF-IDF スコアを名詞とともにリストに格納
tfidf_scores = [(feature_names[word_idx], tfidf_matrix[0, word_idx]) for word_idx in tfidf_matrix[0].nonzero()[1]]
# TF-IDF スコアでソート(降順)
tfidf_scores.sort(key=lambda x: x[1], reverse=True)
# ソートした TF-IDF スコアを表示(上位10語)
for word, score in tfidf_scores[:10]:
print(f"{word}: {score}")
もの: 0.5685636719428927
主人: 0.5446548406099094
ところ: 0.22392661541135464
迷亭: 0.20001778407837148
寒月: 0.1667786771032485
人間: 0.16269668150981237
先生: 0.15978097037164368
細君: 0.12362615225835205
自分: 0.10204988983590381
うち: 0.08980390305559535
頻度とはまた違った結果を確認することができます。
1.4 コサイン類似度
コサイン類似度は、scikit-learn
ライブラリの metrics.pairwise
モジュールから、cosine_similarity
クラスをインポートすることで計算することができます。以下のように、cosine_similarity()
メソッドを用い、引数に計算対象のベクトルを 2 つ、またはベクトルの行列を入力することで、文書間のコサイン類似度が計算され、類似度行列を得ることができます。
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# 文書のリスト
documents = ['人民 の 人民 に よる 人民 の ため の 政治', '人民 元 は 今 何 円 だ っけ']
# CountVectorizer のインスタンスを作成
vectorizer = CountVectorizer()
# 文書の BoW 行列を計算
bow_matrix = vectorizer.fit_transform(documents)
# コサイン類似度を計算
cosine_sim = cosine_similarity(bow_matrix)
# コサイン類似度行列を表示
print("Cosine similarity matrix:")
print(cosine_sim)
tfidf_matrix
が与えられているとき、コサイン類似度を計算し、最も類似度の高い要素とそのインデックスを取得したい場合、以下のように書くことができます。
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# コサイン類似度の計算
cosine_similarities = cosine_similarity(tfidf_matrix)
# 対角成分をゼロに
np.fill_diagonal(cosine_similarities, 0)
# 最も高い類似度のインデックスを取得
max_sim_index = np.unravel_index(np.argmax(cosine_similarities), cosine_similarities.shape)
doc1_index, doc2_index = max_sim_index
print(f"最も高い類似度のペア: 文書 {doc1_index}, 文書 {doc2_index}")
print(f"類似度スコア: {cosine_similarities[doc1_index, doc2_index]}")
さまざまな文書に対して BoW や TF-IDF を計算し、その類似度を観察してみましょう。
課題 11
課題 11
仮想ニュースデータ(または各自で用意したコーパス)の文書をベクトル化しなさい。また、文書間のコサイン類似度を計算し、同一カテゴリ間または異なるカテゴリ間の類似ニュースを特定しなさい。
- 提出ファイル名:学籍番号_11.ipynb
- 提出締切:2024年7月19日(金) 23:59