Skip to content

第 12 回 単語と文書のベクトル表現

第 12 回では、Word2Vec、Doc2Vec の使い方を見ていきます。

2.1 Word2Vec

Word2Vec は、Google によって開発された単語ベクトル表現のモデルです。単語をベクトル空間に埋め込み、同義語や関連語をより近いベクトルとして配置することで、意味的な関係性を反映させることが主な目的です。これにより、自然言語処理(NLP)タスクのパフォーマンス向上のが期待されます。

2.1.1 Word2Vec の主なモデル

Word2Vec は以下の 2 つのモデルを内包します。

  • CBOW (Continuous Bag of Words):文脈(前後の単語)からターゲットの単語を予測
  • Skip-gram:ターゲットの単語をから文脈を予測

一般に Skip-gram のほうが多くの計算時間を必要としますが、性能が良いことで知られています。

2.1.2 Word2Vec の学習プロセス

Word2Vec の学習は以下のプロセスで行われます。

  1. 単語のエンコーディング: 各単語を one-hot ベクトルとしてエンコーディング
  2. ニューラルネットワークの訓練: 2 層のニューラルネットワークを用いて、単語のベクトルを学習
  3. ベクトルの抽出: 学習した重み(ベクトル)を取り出し、単語の埋め込みとして使用

2.1.3 Word2Vec の実装例

単語の類似度計算

Word2Vec を使用するには、自然言語処理のためのオープンソースライブラリである gensim から Word2Vec クラスをインポートします。gensim をインストールして利用するには、以下のコマンドを実行します。

!pip install gensim

Word2Vec クラスは以下のようにインポートを行います。

from gensim.models import Word2Vec

学習は Word2Vec() のコンストラクタに必要な引数を渡すことによって行います。

  • sentences: トークン化(形態素解析)された単語のリストを渡します。ここでは名詞のみを抽出した tokens を渡しています。
  • vector_size: 単語ベクトルの次元数を指定します。ここでは 50 次元のベクトルとしています。
  • window: コンテキストウィンドウ(ある単語を予測するために使用する前後の単語の範囲)のサイズを指定します。ここでは前後 5 単語までを予測に使用しています。
  • min_count: モデルが単語を考慮する最低出現頻度を指定します。ここでは出現回数が 3 回未満の単語を無視しています。
  • epoch: 学習の反復回数を指定します。ここでは 20 回繰り返すこととしています。

その他にも、以下のようなパラメータを指定することができます。

  • sg: デフォルトでは sg=0 で、このとき CBOW アルゴリズムを使用します。sg=1 とすると、skip-gram アルゴリズムを使用します。
  • alpha: 学習率を指定します(デフォルト: 0.025)。
  • workers: 学習時に使用するスレッド数を指定します(デフォルト: 3)。
Word2Vec の実用例
import pandas as pd
import MeCab
from gensim.models import Word2Vec

# データを読み込む関数
def load_data(file_path):
    with open(file_path, "r", encoding="utf-8") as file:
        data = file.read()
    texts = data.split("\n\n")
    return texts

# MeCab の形態素解析を使用して名詞のみを抽出する関数
def extract_nouns(text):
    mecab = MeCab.Tagger()
    node = mecab.parseToNode(text)
    nouns = []
    while node:
      pos = node.feature.split(",")[0] # 品詞を取得
      if pos == "名詞": # 品詞が「名詞」であった場合
        nouns.append(node.surface) #リストに追加
      node = node.next # 次のノードに移動
    return nouns

# データを読み込む(教育ニュースと科学ニュース)
education_data = load_data("education.txt")
science_data = load_data("science.txt")

# データを結合
texts = education_data + science_data

# 形態素解析を使用して名詞のみを抽出
tokens = [extract_nouns(text) for text in texts]

# Word2Vecモデルの定義とトレーニング
w2v_model = Word2Vec(sentences=tokens, vector_size=50, window=5, min_count=3, epochs=20)

# 「宇宙」の単語ベクトルを表示
print(w2v_model.wv["宇宙"])
実行結果
[ 0.1701663  -0.37423316 -0.52348745  0.92520636  0.3101778   0.25683644
  1.5354437   0.64428383 -1.7516887   0.44824734 -0.4344387  -1.2117307
  0.9056027   0.43749502 -0.5209942  -0.5578273   0.8996761   0.13406879
 -1.6022731  -0.8273262   1.5439807   1.5803875   1.4533844  -0.9167201
 -1.1835027  -0.7599991   0.64027715 -1.4557135  -0.16824563 -0.663547
 -0.29143435  0.3126602   0.37243366 -0.22667587 -0.69756854  0.5164693
  0.13278899  0.7713792   0.8854544  -1.4196368   0.4782612   1.2023426
  0.555639    0.6124044   1.1851376   1.155162   -0.1363147  -1.772229
  1.0027039   0.08510534]

「宇宙」に近い単語を取得して表示したい場合は、以下のように wv.most_similar() 関数を利用します。

類似度の高い単語の取得
# 「宇宙」に近い単語を取得
similar_words = w2v_model.wv.most_similar("宇宙")

# 結果を表示
for word, score in similar_words:
    print(f"{word}: {score:.4f}")
実行結果
探査: 0.9579
科学: 0.9369
ミッション: 0.9253
火星: 0.9171
生命: 0.8720
飛行: 0.8637
例: 0.8492
機: 0.8463
月面: 0.8455
ステーション: 0.8441

「宇宙」に関連するキーワードが確認できます。

単語のベクトル演算

Word2Vec のモデルを作成したら、単語のベクトル演算も可能になります。

wv.most_similar() 関数の引数に、positivenegative で単語を指定することで、加算や減算を行うことができます。例えば、「地球」と「太陽」の単語ベクトルを加算し、「光」の単語ベクトルを減算するには、以下のようにします。

単語のベクトル演算
# 「地球」+「太陽」―「光」
similar_words = w2v_model.wv.most_similar(positive=["地球", "太陽"], negative=["光"])

# 結果を表示
for word, score in similar_words:
    print(f"{word}: {score:.4f}")
実行結果
エネルギー: 0.9161
発電: 0.8306
規模: 0.8220
再生: 0.8163
存在: 0.8154
対策: 0.8024
気候: 0.7986
海洋: 0.7921
燃料: 0.7909
風力: 0.7848

Word2Vec は、単語の意味的関係を反映したベクトル表現を提供する非常に強力なツールです。Word2Vec を用いることで、単語のベクトルを利用したクラスタリングや、テキスト分類モデルの構築を行うことができます。ただし、十分な精度を得るためには、学習におけるパラメータの調整や、大規模な文書データセットが必要になります。

文書間の類似度計算

Word2Vec を使用して文書間の類似度を計算することも可能です。その場合、各文書内の単語ベクトルの平均を取ることで、文書全体のベクトルを算出します。文書ベクトルの計算を行う document_vector() は、以下のように定義することができます。

# 文書ベクトルの計算を行う関数
def document_vector(model, doc):
    # モデルに存在する単語のみを使用
    doc = [word for word in doc if word in model.wv.index_to_key]
    # 単語が存在しない場合はゼロベクトルを返す
    if len(doc) == 0:
        return np.zeros(model.vector_size)
    return np.mean(model.wv[doc], axis=0)

また、以下のようにすることで、各文書のベクトルを計算し、最も高い類似度の文書ペアを求めることができます。ここで token は、形態素解析によって得られた単語(名詞)のリストです。

Word2Vec による文書間の類似度計算
# 各文書のベクトルを計算
doc_vectors = [document_vector(w2v_model, doc) for doc in tokens]

# 文書間の類似度を計算
similarities = cosine_similarity(doc_vectors)

# 対角成分をゼロに
np.fill_diagonal(similarities, 0)

# 最も高い類似度のインデックスを取得
max_sim_index = np.unravel_index(np.argmax(similarities), similarities.shape)
doc1_index, doc2_index = max_sim_index

print(f"最も高い類似度のペア: 文書 {doc1_index}, 文書 {doc2_index}")
print(f"類似度スコア: {similarities[doc1_index, doc2_index]}")
実行結果
最も高い類似度のペア: 文書 123, 文書 127
類似度スコア: 0.9966177467110748

文書間の類似度計算は、テキストの分類タスクで用いることができますが、Word2Vec ではその性能に限界があります。

2.2 Doc2Vec

Doc2Vec(Document to Vector)は、Word2Vec を拡張したモデルであり、文書全体を分散表現(ベクトル)として表現します。単語単位での分散表現(Word2Vec)では、単語の意味をうまく捉えることができますが、文書全体の意味を捉えるには不十分でした。Doc2Vec を用いることで、文書の意味を数値ベクトルとして捉え、それをもとに文書同士の類似性を計算することができます。

2.2.1 Doc2Vec の主なモデル

Doc2Vec は以下の 2 つのモデルを内包します。

  • PV-DM (Distributed Memory): Word2Vec の CBOW モデルと類似。文脈内の単語と文書 ID を使用して次に出てくる単語を予測(文脈を考慮可)。
  • PV-DBOW (Distributed Bag of Words): Word2Vec の Skip-gram モデルと類似。文書 ID を使用して文書内の単語を予測(文脈を考慮不可、計算コスト小)。

Doc2Vec の実装例

Doc2Vec は Word2Vec と同様に、gensimライブラリを使用します。以下のように、Doc2Vecクラスと、タグ付けされた文書を扱うための TaggedDocumentクラスをインポートします。

from gensim.models.doc2vec import Doc2Vec, TaggedDocument

文書のタグ付は、TaggedDocumentクラスに必要な引数を渡すことで、以下のように実装できます(リスト内包表記)。

tagged_data = [TaggedDocument(words=token, tags=[str(i)]) for i, token in enumerate(tokens)]

ここで各文書は、インデックス i を用いて 0 から順番にタグ付けされています。また、Doc2Vecクラスにはタグ付けされた文書データを渡す必要がありますが、それ以外の引数は基本的に Word2Vecのものと同様です。ただし、Doc2Vec 固有の引数として、以下のようなものがあります。

  • dm: Distributed Memory (DM)を使用するかどうか。1 なら DM、0 なら DBOW(デフォルト: 0)
  • dbow_words: DBOW モデルで単語のベクトルを訓練するかどうか(デフォルト: 0)

Doc2Vec の実際の使用例は以下のようになります。

Doc2Vecの使用例
import pandas as pd
import MeCab
from gensim.models.doc2vec import Doc2Vec, TaggedDocument

# データを読み込む関数
def load_data(file_path):
    with open(file_path, "r", encoding="utf-8") as file:
        data = file.read()
    texts = data.split("\n\n")
    return texts

# MeCab の形態素解析を使用して名詞のみを抽出する関数
def extract_nouns(text):
    mecab = MeCab.Tagger()
    node = mecab.parseToNode(text)
    nouns = []
    while node:
      pos = node.feature.split(",")[0] # 品詞を取得
      if pos == "名詞": # 品詞が「名詞」であった場合
        nouns.append(node.surface) #リストに追加
      node = node.next # 次のノードに移動
    return nouns

# データを読み込む
education_data = load_data("education.txt")
science_data = load_data("science.txt")

# データを結合する
data = education_data + science_data

# 形態素解析を使用して名詞のみを抽出
tokens = [extract_nouns(text) for text in texts]

# タグ付けされたデータを作成
tagged_data = [TaggedDocument(words=token, tags=[str(i)]) for i, token in enumerate(tokens)]

# Doc2Vecモデルの定義とトレーニング
d2v_model = Doc2Vec(tagged_data, vector_size=50, window=5, min_count=3, epochs=20)

# 1つ目の文書ベクトルを表示
print(d2v_model.dv["0"])
実行結果
[-0.29112646 -0.01795701 -0.1313675  -0.13224296  0.02310067 -0.42390206
  0.21799323  0.46834004 -0.21038814 -0.2704192   0.11004175  0.0096407
  0.02546708  0.14091727 -0.01171341  0.30446252  0.06569391 -0.00851064
 -0.09175506 -0.05761463  0.00418857  0.07184632  0.00400528  0.02747167
  0.29131833  0.08358882 -0.2507545  -0.05060206  0.10045283 -0.08582567
  0.4631889   0.02311449 -0.20549521  0.04195794 -0.0409352   0.08375671
  0.18615241 -0.04826397  0.07233746 -0.00879903  0.24982297  0.0056202
 -0.10204502 -0.36341774  0.3018667   0.00339435 -0.10210954 -0.01514526
  0.144878   -0.07303411]

文書間の類似度計算をし、類似度の高いものを抽出するコードは以下のようになります。

Doc2Vec による文書間の類似度計算
# 文書ベクトルを取得
doc_vectors = [d2v_model.dv[str(i)] for i in range(len(tagged_data))]

# 文書間の類似度を計算
similarities = cosine_similarity(doc_vectors)

# 対角成分をゼロに
np.fill_diagonal(similarities, 0)

# 最も高い類似度のインデックスを取得
max_sim_index = np.unravel_index(np.argmax(similarities), similarities.shape)
doc1_index, doc2_index = max_sim_index

print(f"最も高い類似度のペア: 文書 {doc1_index}, 文書 {doc2_index}")
print(f"類似度スコア: {similarities[doc1_index, doc2_index]}")
実行結果
最も高い類似度のペア: 文書 320, 文書 348
類似度スコア: 0.992438554763794

Word2Vec とはまた異なる結果になっていることが確認できます。パラメータの変更によって結果がどう変わるかについても確認してみてください。

2.3 モデルデータの保存・読込

Word2Vec や Doc2Vec による学習は、データサイズや条件によっては膨大な時間がかかります。また、膨大なコーパスによって学習済みのモデルを使用することができれば、効率的に自然言語処理のタスクを実現することができます。

2.3.1 モデルデータの保存

モデルデータを保存するには、save()関数を用います。引数にはファイルのパスを指定します。方法は Word2Vec でも Doc2Vec でも同様です。

モデルデータの保存
model.save("model.model")

2.3.2 モデルデータの読込

モデルデータを読み込むには、load()関数を用います。引数にモデルデータのパスを指定します。

モデルデータの読込(Word2Vec)
from gensim.models import Word2Vec

w2v_model = Word2Vec.load("w2v_model.model")
モデルデータの読込(Doc2Vec)
from gensim.models.doc2vec import Doc2Vec

d2v_model = Doc2Vec.load("d2v_model.model")

課題 12

課題 12

仮想ニュースデータ(または各自で用意したコーパス)を用いて Word2Vec / Doc2Vec のモデルをトレーニングしなさい。また、特定の単語や文書に対して類似度の高いものを確認し、結果が期待通りかどうかを確認しなさい。

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