AI 物見遊山 - 自然言語処理編 - 文章のベクトル化

自然言語処理編 - 文章のベクトル化

アイキャッチ画像

文章のベクトル化について、試してみる。 手頃なモデルが見当たらなかったので、 学習用の文章データを用意し、モデルを学習する事とする。



学習に利用するデータを準備する

学習用データ作成には、Pandas のデータフレームを利用する。 学習に利用する文書は、分かち書きする必要がある。 分かち書きには、GiNZA(spaCy) を利用する。

import logging

import pandas as pd
import spacy
from gensim.models.doc2vec import Doc2Vec, TaggedDocument

# ログ出力設定
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)


def tokenize(doc):
    """spaCy の戻り値を分かち書き配列に変換する"""
    return [token.orth_ for token in doc]


data = {
    'text': [
        '今日は晴れていて、気持ちの良い一日です。公園でピクニックをするのに最適です。',
        '最新のスマートフォンは、カメラの性能が格段に向上しています。特に夜景撮影が綺麗です。',
        'AI技術の進化は目覚ましく、様々な分野で活用されています。医療、金融、教育など、応用範囲は広いです。',
        '健康的な生活を送るためには、バランスの取れた食事と適度な運動が重要です。睡眠も大切です。',
        '旅行は、新しい発見や出会いを提供してくれます。異文化に触れることで、視野が広がります。',
    ],
    'tags': [
        ['id_0'],
        ['id_1'],
        ['id_2'],
        ['id_3'],
        ['id_4'],
    ]
}

# データフレームを作成する。
df = pd.DataFrame(data)
# spaCy に日本語モデルを読み込む。
nlp = spacy.load('ja_ginza_bert_large')
# 文章を分かち書きして上書きする。
df['text'] = [tokenize(doc) for doc in nlp.pipe(df['text'])]
# Doc2Vec モデル学習用のデータ形式に変換する。
docs = [TaggedDocument(words=words, tags=tags) for words, tags in zip(df['text'], df['tags'])]

GiNZA の分かち書き結果オブジェクトについて

原則は、spaCy の API リファレンス Token を参照。

気になるものを記載しておく。

属性 説明
i 単語のインデックス
text 原文そのままの単語
orth_ text とほぼ同じだが、より厳密に原文の表現をそのまま保持する。
lemma_ 語形変化の接尾辞のない単語の基本形。レンマタイゼーション(文法的な情報を考慮して単語を基本形にする)に利用するようだ。
norm_ 単語の標準形
morph.get(“Reading”) 読み(カタカナ)
pos_ 品詞(Universal POS tagsで定義されているようだ)(例:VERB)
morph.get(“Inflection”) 語形変化 (例:五段-ラ行;終止形-一般)
tag_ 詳細な品詞 (例:名詞-普通名詞-一般)
dep_ 構文上の依存関係 (例:fixed)
head.i 係り先の単語のインデックス

モデルを生成する

モデルを生成するための学習には、gensim を利用する。 dm で、学習アルゴリズムを PV-DM にする。 vector_size で、ベクター次元を 100 次元にする。 min_count で、合計出現数 1 でも無視しないようにする。

model = Doc2Vec(documents=docs, dm=1, vector_size=100, min_count=1)

モデルの保存と読み込み

必要に応じてモデルを保存や、読み込みを行える。

model.save('doc2vec.model')
model = Doc2Vec.load('doc2vec.model')

文章の類似文章を取得する。

# 文章を分かち書きリストに変換する。
doc1 = tokenize(nlp('今日は晴れていて、気持ちの良い一日です。'))
# 文章のベクトルを取得する。
vec1 = model.infer_vector(doc1)
# 類似文章を取得する。
print(model.dv.most_similar(vec1))

doc2 = tokenize(nlp('今日は晴れていて、気持ちの良い一日です。公園でピクニックをするのに最適です。'))
vec2 = model.infer_vector(doc2)
print(model.dv.most_similar(vec2))

以下のような結果となった。 一行目は、id_0 の前半の文章にした場合で、id_2 が最も近いと判定されている。 二行名は、id_0 の全文の場合で、id_2 が最も近いと判定されている。 期待した結果ではない理由は、学習用データが少なすぎるためだと思われる。

[('id_2', 0.04947032034397125), ('id_3', 0.03414669632911682), ('id_1', 0.008340449072420597), ('id_0', -0.018285389989614487), ('id_4', -0.05277436971664429)]
[('id_2', 0.20955169200897217), ('id_4', 0.08027120679616928), ('id_1', 0.007603914942592382), ('id_3', -0.012494317255914211), ('id_0', -0.08694116026163101)]

文章間の距離を算出する。

doc1 と doc2 の文章間の距離を取得してみる。

print(model.wv.wmdistance(doc1, doc2))

結果は、以下のようになった。 場合によっては、inf などになる場合もあり、これは、学習データが少ないためと思われる。

0.44331685040330293

まとめ

ここまで、文章ベクトルを使用してみたが、 それなりのモデルを用意しないと、期待した結果にはならないということがわかった。 なかなか理解が進まないが、今回はここまでとしておく。