自然言語処理編 - 文章のベクトル化
文章のベクトル化について、試してみる。 手頃なモデルが見当たらなかったので、 学習用の文章データを用意し、モデルを学習する事とする。
学習に利用するデータを準備する
学習用データ作成には、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
まとめ
ここまで、文章ベクトルを使用してみたが、 それなりのモデルを用意しないと、期待した結果にはならないということがわかった。 なかなか理解が進まないが、今回はここまでとしておく。