Pythonで学ぶ自然言語処理:Bag of WordsとBERTの類似度計算

こんにちは。ゆうせいです。

自然言語処理において、文章の意味がどの程度似ているかを計算することは重要な課題です。文章の類似度を計算するためには、文字列を数値の配列であるベクトルに変換する必要があります。文字列をベクトル化する手法として、古典的なBag of Wordsと、深層学習を用いたBERTの2種類を比較します。

Bag of WordsとBERTの仕組み

文章をベクトル化する手法には、それぞれ異なる特徴があります。

Bag of Wordsの手法

Bag of Wordsは、文章に含まれる単語の種類と出現回数のみを数え上げる手法です。高校生でも想像しやすい具体的な比喩を用いて説明します。買い物かごの中身を比較する状況を想像してください。りんごが何個、みかんが何個入っているかという種類と数だけをリストアップし、どの順番でかごに入れたか、あるいは購入した食材で何の料理を作るつもりかといった文脈は一切考慮しません。

ご提示いただいたプログラムの例では、私、は、猫、といった単語が共通して出現するため、文章1と文章2の類似度が高く算出されます。動物の猫と、熱いものが苦手な体質を表す猫舌は全く意味が異なりますが、Bag of Wordsは文字の一致のみを見るため、意味の違いを区別できません。

BERTの手法

BERTは、Googleが開発した自然言語処理のモデルであり、文章の前後関係を読み取ることで単語の文脈を理解します。こちらの比喩としては、文脈を深く読み取る現代文の先生を想像してください。同じ猫という文字が含まれていても、前後の文脈から動物の話なのか、熱い食べ物の話なのかを推測し、文章全体の意味を数値化します。

そのため、文章2の私は猫舌である、と、文章3の熱い食べ物が苦手、は、表面上の文字は一致していなくても、意味が近いことを計算によって導き出すことが可能です。

ベクトルの類似度計算について

ベクトル化された文章同士がどの程度似ているかを測る指標として、コサイン類似度という手法を用います。コサイン類似度は、2つのベクトルのなす角に注目し、方向がどれだけ一致しているかを計算します。

コサイン類似度を求める計算式は以下の通りです。

\cos(\theta) = \frac{\sum_{i=1}^{n} A_{i}B_{i}}{\sqrt{\sum_{i=1}^{n} A_{i}^{2}} \sqrt{\sum_{i=1}^{n} B_{i}^{2}}}

式の値は-1から1の範囲を取り、1に近いほど2つの文章のベクトルが同じ方向を向いている、すなわち類似度が高いと判断します。

Bag of WordsとBERTのメリットとデメリット

両者の手法には、システム開発において明確なメリットとデメリットが存在します。

Bag of Wordsの特徴

メリット:

計算に必要な処理が非常に軽く、少ないメモリで高速に動作します。特殊な環境を構築する必要がなく、scikit-learnのような標準的な機械学習のライブラリで簡単に実装できます。

デメリット:

単語の並び順や、同義語を理解できません。そのため、複雑な文章の意味を比較する用途には適していません。

BERTの特徴

メリット:

文脈を理解するため、言葉の表現の揺らぎや同義語を吸収し、高い精度で文章の意味を比較できます。

デメリット:

計算量が非常に大きく、処理に時間がかかります。また、Transformersなどの専用プログラムや、学習済みの巨大なモデルデータをダウンロードするための環境が必要です。

比較用のPythonプログラム

ご提示いただいたプログラムを拡張し、両方の手法で類似度を出力するコードを記述します。BERTの実行には、PyTorchおよびTransformersというライブラリのインストールが必要です。

!pip install fugashi unidic-lite
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import torch
from transformers import AutoTokenizer, AutoModel


sentences_divided = [
    "私 は 猫 が 好き",
    "私 は 猫舌 で ある",
    "熱い 食べ物 が 苦手"
]

vectorizer = CountVectorizer(token_pattern=u"(?u)\\b\\w+\\b")
bow_vectors = vectorizer.fit_transform(sentences_divided).toarray()

bow_sim_1_2 = cosine_similarity([bow_vectors[0]], [bow_vectors[1]])[0][0]
bow_sim_2_3 = cosine_similarity([bow_vectors[1]], [bow_vectors[2]])[0][0]

print("Bag of Wordsの類似度")
print("文章1と文章2:", bow_sim_1_2)
print("文章2と文章3:", bow_sim_2_3)

sentences = [
    "私は猫が好き",
    "私は猫舌である",
    "熱い食べ物が苦手"
]

model_name = "cl-tohoku/bert-base-japanese-v3"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)

def calculate_bert_similarity(sentence_a, sentence_b):
    inputs_a = tokenizer(sentence_a, return_tensors="pt", padding=True, truncation=True)
    inputs_b = tokenizer(sentence_b, return_tensors="pt", padding=True, truncation=True)
    
    with torch.no_grad():
        outputs_a = model(**inputs_a)
        outputs_b = model(**inputs_b)
        
    vec_a = outputs_a.last_hidden_state.mean(dim=1)
    vec_b = outputs_b.last_hidden_state.mean(dim=1)
    
    similarity = torch.nn.functional.cosine_similarity(vec_a, vec_b).item()
    return similarity

bert_sim_1_2 = calculate_bert_similarity(sentences[0], sentences[1])
bert_sim_2_3 = calculate_bert_similarity(sentences[1], sentences[2])

print("\nBERTの類似度")
print("文章1と文章2:", bert_sim_1_2)
print("文章2と文章3:", bert_sim_2_3)

<実行結果>

Bag of Wordsの類似度
文章1と文章2: 0.39999999999999997
文章2と文章3: 0.0

BERTの類似度
文章1と文章2: 0.8912748098373413
文章2と文章3: 0.7826831936836243

今後の学習ステップ

自然言語処理の学習を進めるにあたり、以下の順序で技術を習得していくことを推奨します。

  1. Pythonの基礎構文と、配列計算ライブラリであるNumPyの操作方法を習得します。
  2. scikit-learnを用いて、Bag of WordsやTF-IDFなどの古典的な自然言語処理手法を実装し、文字列を数値化する基本概念を理解します。
  3. PyTorchなどの深層学習フレームワークの基礎を学習し、テンソルと呼ばれる多次元配列の扱いに慣れます。
  4. Transformersライブラリを導入し、BERTをはじめとする事前学習済みモデルを利用した高度な自然言語処理プログラムを作成します。

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。