Skip to content

第 11 回 自然言語処理の基礎

第 11 回は、自然言語処理の概要と基本について学びます。

1.1 形態素解析

形態素解析とは、文を意味を持つ最小単位の単語(形態素)に分割する操作のことです。日本語の文章に対して自然言語処理を適用する場合、文章を単語単位に分割する分かち書き(形態素解析)が必要になります。形態素解析器には、MeCab や Juman++、Janome などがよく使用されます。

例えば、「吾輩は猫である」という文は、「吾輩」「は」「猫」「で」「ある」に分割されます。

1.1.1 MeCab と辞書データのインストール

形態素解析を実際に実行するために、MeCab と辞書データをインストールしましょう。 Google Colaboratory で以下のコマンドを実行することで、インストールすることができます。 ここでは日本語の大規模な形態素解析用の辞書 UniDic の軽量版である unidic-lite をインストールしています。

!pip install mecab-python3 unidic-lite

1.1.2 MeCab で形態素解析

インストールが完了したら、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 を指定すると、結果を分かち書きの形式で出力することができます。

MeCab による分かち書き
# MeCab のインスタンス生成(分かち書きを指定)
mecab = MeCab.Tagger('-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_counts = {}
BoWの作成(node.surface に単語が入っている場合)
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 を作成すると、頻出語の結果は以下のようになります。

頻出語の表示結果(education.txt:名詞)
こと 861
学習 433
生徒 381
学校 333
授業 330
プログラム 320
教育 309
期待 293
技術 265
学び 241

1.2.2 Counter クラスの利用

前節では辞書を使った BoW の作成方法を紹介しましたが、collections モジュールの Counter クラスを使うことで、より簡単に BoW を作成することもできます。

Counter クラスのインポート
from collections import 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 には、「吾輩は猫である」を形態素解析した結果(名詞の配列)が入っているものとします。

BoW ベクトルの作成
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())
実行結果
BoW Matrix:
[[ 1  2  1 ... 12  1  1]]

Feature Names:
['000' '10' '20' ... '魅力' '黒板' '50']

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 の行列を計算しています。

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 の結果をソートして表示
# 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)
実行結果
Cosine similarity matrix:
[[1.         0.61237244]
 [0.61237244 1.        ]]

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]}")
実行結果
最も高い類似度を持つ文書のペア: 文書 128, 文書 149
類似度スコア: 0.5921665610499245

さまざまな文書に対して BoW や TF-IDF を計算し、その類似度を観察してみましょう。

課題 11

課題 11

仮想ニュースデータ(または各自で用意したコーパス)の文書をベクトル化しなさい。また、文書間のコサイン類似度を計算し、同一カテゴリ間または異なるカテゴリ間の類似ニュースを特定しなさい。

  • 提出ファイル名:学籍番号_11.ipynb
  • 提出締切:2024年7月19日(金) 23:59