LoginSignup
3
2

More than 1 year has passed since last update.

sentence-bertを使って映画レビューの類似度を比較してみた

Posted at

今回使用したモデル

こちらの日本語用Sentence-BERTモデル(バージョン2)です。

参考記事

主にこちらの記事を参考にさせていただきました。

内容

映画レビューを文ベクトル化し、特定の文章との類似度を計算できるようにしました。
今回は「ゴッドファーザー」「スパイダーマン」「映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲」の3本の映画を対象に、Yahoo映画から★5のレビューを3件ずつ取得しています(手動で)。
取得したレビューを文ベクトル化し、さらに3件のレビューの平均ベクトルを計算しています。この平均ベクトルが、それぞれの映画の平均的なレビューのベクトルである、と考えています。

環境

Google Colabを使用しています。

実際のコード

ライブラリインストール

まずはBERTとpytorch等をインストールします。

!pip install -q transformers==4.7.0 fugashi ipadic
from transformers import BertJapaneseTokenizer, BertModel
import torch

モデルの定義

SentenceBertJapaneseクラスを定義します。これはこちらで紹介されていたクラスほぼそのままですが、batch_encode_plusの部分を少し修正しています。

class SentenceBertJapanese:
    def __init__(self, model_name_or_path, device=None):
        self.tokenizer = BertJapaneseTokenizer.from_pretrained(model_name_or_path)
        self.model = BertModel.from_pretrained(model_name_or_path)
        self.model.eval()

        if device is None:
            device = "cuda" if torch.cuda.is_available() else "cpu"
        self.device = torch.device(device)
        self.model.to(device)

    def _mean_pooling(self, model_output, attention_mask):
        token_embeddings = model_output[0] #First element of model_output contains all token embeddings
        input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
        return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)

    @torch.no_grad()
    def encode(self, sentences, batch_size=8):
        all_embeddings = []
        iterator = range(0, len(sentences), batch_size)
        for batch_idx in iterator:
            batch = sentences[batch_idx:batch_idx + batch_size]

            # encoded_input = self.tokenizer.batch_encode_plus(batch, padding="longest", 
            #                                truncation=True, return_tensors="pt").to(self.device)
            encoded_input = self.tokenizer.batch_encode_plus(batch, padding="max_length", max_length=512,
                                           truncation=True, return_tensors="pt").to(self.device)
            model_output = self.model(**encoded_input)
            sentence_embeddings = self._mean_pooling(model_output, encoded_input["attention_mask"]).to('cpu')

            all_embeddings.extend(sentence_embeddings)

        # return torch.stack(all_embeddings).numpy()
        return torch.stack(all_embeddings)

事前学習モデルのロード

日本語事前学習モデルをロードします。

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

コサイン類似度を計算する関数を定義

後ほど使うので先に定義しておきます。

import numpy as np

def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

レビューの読み込み

Yahoo映画のレビューから、役立ち度順に★5のレビューを拝借してきます。みなさんかなり気合の入ったレビューで文章が非常に長いので、ここでは省略して記載します。

movie_a = "ゴッドファーザー"
movie_a_comment_1 = "在米イタリア人による,..........皮肉な話だ"

movie_a_comment_2 = "華やかな音楽も届かない書斎では.....感じるからだろう"
movie_a_comment_3 = "まずこういう映画を毛嫌いする方もいると思いますし.....と個人的に思っています"
movie_a_vectors = model.encode([movie_a_comment_1, movie_a_comment_2, movie_a_comment_3])
movie_a_avg_vector = (movie_a_vectors[0].numpy() + movie_a_vectors[1].numpy() + movie_a_vectors[2].numpy()) / 3
                                                    
movie_b = "スパイダーマン"
movie_b_comment_1 = "映画スパイダーマン他のアメコミ映画とは明らか....のは俺だけではあるまい"
movie_b_comment_2 = "この映画の魅力それは.....過ぎないのだから"
movie_b_comment_3 = "注意....いやなんとなく"
movie_b_vectors = model.encode([movie_b_comment_1, movie_b_comment_2, movie_b_comment_3])
movie_b_avg_vector = (movie_b_vectors[0].numpy() + movie_b_vectors[1].numpy() + movie_b_vectors[2].numpy()) / 3
                                                    
movie_c = "映画クレヨンしんちゃん 嵐を呼ぶモーレツオトナ帝国の逆襲"
movie_c_comment_1 = "子供向けじゃあない....育った大人なんてゾっとする"
movie_c_comment_2 = "今まで観なくて....大事なのかもしれませんね"
movie_c_comment_3 = "ノスタルジーって....笑えて泣けた心から"
movie_c_vectors = model.encode([movie_c_comment_1, movie_c_comment_2, movie_c_comment_3])
movie_c_avg_vector = (movie_c_vectors[0].numpy() + movie_c_vectors[1].numpy() + movie_c_vectors[2].numpy()) / 3

類似度を比較する

適当に文章を作って、その文章との類似度を比較してみます。まずは「笑って泣ける映画です」という文章に対して、どの映画のレビューが最も類似度が高いか?を比較します。

comment = model.encode(["笑って泣ける映画です"])

print(f'vs{movie_a}_平均:{cos_sim(movie_a_avg_vector, comment[0])}')
print(f'vs{movie_a}_コメント1:{cos_sim(movie_a_vectors[0].numpy(), comment[0])}')
print(f'vs{movie_a}_コメント2:{cos_sim(movie_a_vectors[1].numpy(), comment[0])}')
print(f'vs{movie_a}_コメント3:{cos_sim(movie_a_vectors[2].numpy(), comment[0])}')

print(f'vs{movie_b}_平均:{cos_sim(movie_b_avg_vector, comment[0])}')
print(f'vs{movie_b}_コメント1:{cos_sim(movie_b_vectors[0].numpy(), comment[0])}')
print(f'vs{movie_b}_コメント2:{cos_sim(movie_b_vectors[1].numpy(), comment[0])}')
print(f'vs{movie_b}_コメント3:{cos_sim(movie_b_vectors[2].numpy(), comment[0])}')

print(f'vs{movie_c}_平均:{cos_sim(movie_c_avg_vector, comment[0])}')
print(f'vs{movie_c}_コメント1:{cos_sim(movie_c_vectors[0].numpy(), comment[0])}')
print(f'vs{movie_c}_コメント2:{cos_sim(movie_c_vectors[1].numpy(), comment[0])}')
print(f'vs{movie_c}_コメント3:{cos_sim(movie_c_vectors[2].numpy(), comment[0])}')

このようになりました。_平均というのが各映画平均ベクトルです。笑って泣ける映画として名高い「映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲」が最も高い数値が出ていますね。感覚的にも十分あっています。

vsゴッドファーザー_平均:0.445037305355072
vsゴッドファーザー_コメント1:0.33053019642829895
vsゴッドファーザー_コメント2:0.4115988314151764
vsゴッドファーザー_コメント3:0.4690192937850952
vsスパイダーマン_平均:0.49370622634887695
vsスパイダーマン_コメント1:0.4734647274017334
vsスパイダーマン_コメント2:0.42756545543670654
vsスパイダーマン_コメント3:0.49704039096832275
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_平均:0.582426905632019
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_コメント1:0.4933982193470001
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_コメント2:0.61888587474823
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_コメント3:0.5201992988586426

他の文章も試してみます。
「スーパーヒーローが出てきて敵を退治する姿がかっこいい。」という文章で類似度を計算してみます。

comment = model.encode(["スーパーヒーローが出てきて敵を退治する姿がかっこいい。"])

やはりスパイダーマンのスコアが高いですね!

vsゴッドファーザー_平均:0.4695703089237213
vsゴッドファーザー_コメント1:0.31775474548339844
vsゴッドファーザー_コメント2:0.5198161602020264
vsゴッドファーザー_コメント3:0.4389423429965973
vsスパイダーマン_平均:0.5631412267684937
vsスパイダーマン_コメント1:0.5113078355789185
vsスパイダーマン_コメント2:0.5612474083900452
vsスパイダーマン_コメント3:0.5188660621643066
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_平均:0.3811109960079193
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_コメント1:0.3106297552585602
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_コメント2:0.4306870698928833
vs映画クレヨンしんちゃん 嵐を呼ぶモーレツ!オトナ帝国の逆襲_コメント3:0.3270125687122345

感想

類似度自体に大きな差はついていませんが、大小は精度よく計算ができているようです。
ランキング作成などに使えそうです。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2