🗂

自然言語処理 ベクトル化の手法

2022/11/16に公開

はじめに

文章を固定長ベクトルに変換する方法はないかなあと調べていたら、SentenseBertというものを見つけました.それ以外にもベクトル化にはいろいろな方法があるようで調べた内容を少しまとめてみようと思います.

ベクトル化の用途

ベクトル化することによる応用の仕方はいろいろですが、文書ベクトルの応用としては以下のようなことが可能です.

  • 類似文書の検索
  • 文章間の類似度を測っておすすめの記事をレコメンド
  • 画像検索エンジン(文章ベクトルと画像ベクトルの類似度を測る)

さまざま手法

word2vecに始まり、自然言語の機械学習によるベクトル化のアルゴリズムは常に改良されてきました.
いくつか有名なものをピックアップして紹介します.

word2vec(2013)

米グーグルの研究者、トマス、ミニコフらが開発しました。
周辺語から中心単語(CBOW), あるいは中心単語から周辺語(Skip-gram)を予測するニューラルネットワークを学習し、学習後の中間層の重みを単語ベクトルとして用いています。

(CBOWのイメージ)

Distributed Representations of Sentences and Documents

GloVe(2014)

スタンフォード大学のmanning氏の研究質で開発されました。
名前はgloval vectorの略です。
word2vecは局所的な情報だけで学習していましたが、gloveは名前の通り大局的な情報も考慮します。

doc2vec(2014)

単語ではなく文章をベクトル化する手法です。word2vecにおける文章全体にも適応し、文章をベクトル化できるようにしたものです。word2vecと同様、周辺語から中心語(PV-DBoW)、中心語から周辺語を予測するモデルを学習しますが、doc2vecでは単語に加えて文書(文章それぞれのIDをワンホットベクトルにしたもの)を入力値に与えます.こうすることで、

(PV-DMのイメージ)

Distributed Representations of Sentences and Documents

elmo(2018)

allen aiおよびワシントン大学が開発しました。word2vecではあくまで1単語1ベクトルでしたが、実際のケースでは、文脈によって単語の意味が変わることがあります。例えば、ご飯という単語は、米なのか、食事なのか、献立なのかは使い方によりますね。こうした文章による文脈の違いを考慮しています。

BERT: Pre-training of Deep Bidirectional Transformers for
Language Understanding

Sentence Bert(2019)

通常のBertでも文章のベクトルを得ることは可能ですが、実はあまり精度が良くなく、論文によるとタスクによってはGloVeで単純に単語ベクトルを平均したものより悪いそうです.そのため、より質の高いベクトルを得る手法としてSentence Bertが開発されました.


Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks

Sentence Bertで文章間の類似度を測る

実際にSentence Bertで文章をベクトル化し類似度を計測してみました.

環境とライブラリ

colab上で実装しました.ライブラリはtransformersとそのほか諸々入れてます.

!pip install transformers sentencepiece fugashi ipadic gensim
from transformers import BertJapaneseTokenizer, BertModel
import torch
import torch.nn.functional as F
import pandas as pd

ベクトル化

今回はこちらの日本語での学習済モデルを使用してます.
https://huggingface.co/sonoisa/sentence-bert-base-ja-mean-tokens

モデルを定義します.
SentenceBertJapaneseのクラスの定義はリンク先のものをそのまま使用してるので割愛

model = SentenceBertJapanese("sonoisa/sentence-bert-base-ja-mean-tokens-v2")

適当な文章でベクトル化してみます.

input_docs = [
    'あなたは犬が好きです',
    'あなたは犬が大好きです',
    'あなたは猫が好きです',
    'あなたは猫が大好きです',
    '私は犬が好きです',
    '私は犬が大好きです',
    '私は猫が好きです',
    '私は猫が大好きです',
    '彼は犬が好きです',
    '彼は犬が大好きです',
    '彼は猫が好きです',
    '彼は猫が大好きです',
]
vecs = model.encode(input_docs, batch_size=12)

ベクトル化された文章が返ってきましたね.(12x768次元)

tensor([[-0.4879,  0.1216, -0.3270,  ..., -0.2512, -0.7447, -0.2589],
        [-0.1567,  0.1844, -0.5140,  ..., -0.1664, -0.8702, -0.3971],
        [-0.6319, -0.0753, -0.0927,  ..., -0.1216, -0.8620,  1.3580],
        ...,
        [-0.1157,  0.7892, -0.2568,  ..., -0.1601, -0.3302, -0.1772],
        [ 0.1865,  0.5694, -0.0136,  ..., -0.5572, -1.0538,  1.4919],
        [ 0.2857,  0.6080, -0.2015,  ..., -0.3121, -1.1592,  1.2562]],
       grad_fn=<StackBackward0>)

類似度を計測

1つ目の文章に対してコサイン類似度を測ってみます.

sim = F.cosine_similarity(vecs[0], vecs).tolist()
pd.DataFrame({'文章': input_docs, '類似度': sim})

類似度の変化を見るか、主語が変わると0.3くらい類似度が下がり、目的語が変わると0.5以上下がっているようですね.この辺りの癖は学習データに依存しそうですね.

おわりに

自然言語処理におけるベクトル化についてざっくりとまとめてみました.
今回はSentenseBertのみ試したのですが、他の手法と比較してどう活用できるかをまた考察したいなと思います.

最後に、私の所属するsweeepではエンジニアを募集しています.お気軽に覗いてみてください.
https://www.wantedly.com/companies/sweeep/projects

Discussion