「retrieva-jp/bert-1.3b」を使ってとりあえずベクトル検索

# 概要

RAGの台頭でエンベディングモデルの需要が高まっていると感じているが、日本語対応していて、トークン数の制約が厳しくないモデルはほとんど存在しない。（特にオープンソースでは）

以下の記事で利用した「intfloat/multilingual-e5-large」は、性能こそ期待通りの結果を残しているが、トークン数の制約が512と、汎用性に欠ける状態となっている。

https://qiita.com/kongo-jun/items/d7ea50a02d14d76766cb


「retrieva-jp/bert-1.3b」は、日本語に対応しつつ、2048トークンを実現したエンベディングモデルということで、トークン数の制約を克服できるだけの性能が期待できるかを試す。（ベンチマークのようなもので検証するというよりは、モデルを動かしてみるという位置づけ）

https://huggingface.co/retrieva-jp/bert-1.3b

※このモデルはファインチューニングなどで調整することを想定して、学習されているらしいので、その前提で見ていただければと思います。

https://note.com/retrieva/n/n715bea2c2cd1


# 環境

OS:Windows 11
GPU:GeForce RTX 3060 laptop
CPU:i7-10750H
memory:16G
python:3.10.10
pytorch:2.0.1
CUDA:11.8
cuDNN:8.8

# 環境構築

以下のコマンドでライブラリをインストールする。

CUDAなしの場合

```powershell
python -m venv bert_cpu_venv
bert_cpu_venv\Scripts\activate
cd bert_cpu_venv
pip install scipy
pip install transformers
pip install torch
```

CUDAありの場合

```powershell
python -m venv bert_gpu_venv
bert_gpu_venv\Scripts\activate
cd bert_gpu_venv
pip install scipy
pip install transformers
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
```

以下のプログラムを実行し、動作確認を行う。

```python
from transformers import AutoModelForMaskedLM, AutoTokenizer, pipeline

model_id = "retrieva-jp/bert-1.3b"
model = AutoModelForMaskedLM.from_pretrained(model_id, trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained(model_id)
pipe = pipeline("fill-mask", model=model, tokenizer=tokenizer)

text = "こんにちは！私の名前は<MASK|LLM-jp>です！"
print(pipe(text))
```

以下のような結果が出力されたら、動作確認完了。

```
[{'score': 0.007387423887848854, 'token': 539, 'token_str': 'K', 'sequence': 'こんにちは！私の名前は K です！'}, {'score': 0.007120715454220772, 'token': 369, 'token_str': 'M', 'sequence': 'こんにちは！私の名前は M です！'}, {'score': 0.006657002493739128, 'token': 38908, 'token_str': 'Yuki', 'sequence': 'こんにちは！私の名前は Yuki です！'}, {'score': 0.006240764632821083, 'token': 40866, 'token_str': 'ゆう', 'sequence': 'こんにちは！私の名前は ゆう です！'}, {'score': 0.005459163337945938, 'token': 348, 'token_str': 'A', 'sequence': 'こんにちは！私の名前は A です！'}]
```

# プログラム

### ベクトル検索準備

以下のプログラムを実行すると、変数.text_list内のquestionテキストをエンベディング（ベクトル化）し、CSVに出力する。

```python
import csv
from transformers import AutoTokenizer, AutoModel
import torch
from torch import Tensor
import torch.nn.functional as F

# モデルID
model_id = "retrieva-jp/bert-1.3b"

# トークナイザーとモデルの読み込み
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id, trust_remote_code=True)

# デバイス設定 (CUDAが利用可能ならばCUDAを使用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
print(f"Using device: {device}")

# モデルをデバイスに移動
model = model.to(device)

def average_pool(last_hidden_states: Tensor, attention_mask: Tensor) -> Tensor:
    last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
    return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]

text_list = [
    {"question":"あなたの名前は？", "answer":"ネムル"},
    {"question":"好きなものは？", "answer":"寝ること"},
    {"question":"好き食べ物は？", "answer":"芋"},
    {"question":"元気がでるのは？", "answer":"寝ること"},
    {"question":"最近うれしかったことは？", "answer":"今日晴れてたこと"},
    {"question":"歳は？", "answer":"知らない"},
    {"question":"出身は？", "answer":"ヨネズのコロニー"},
    {"question":"好きな人は？", "answer":"みはる"},
    {"question":"嫌いなことは？", "answer":"頑張ること"},
    {"question":"得意な勉強は？", "answer":"勉強とかできない"},
    {"question":"朝型か夜型か？", "answer":"特にない"},
    {"question":"普段、何をしていますか？", "answer":"だいたい寝てるかも"},
    {"question":"趣味は？", "answer":"寝る以外とかにない"},
    {"question":"最近読んだ本や漫画は？", "answer":"本？"},
    {"question":"印象に残っている旅行は？", "answer":"シティより遠くに行ったことない？"},
    {"question":"最近挑戦したことは？", "answer":"しゃべること"},
    {"question":"最近感動したことは？", "answer":"最近暖かくて寝心地がいい"},
    {"question":"最近見た映画やドラマは？", "answer":"映画？"},
]

# CSVファイルを開き、書き込みます
with open('q_and_a_with_vectors.csv', mode='w', encoding='utf-8', newline='') as csv_file:
    fieldnames = ['id', 'question', 'answer', 'vector']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
    
    writer.writeheader()
    id = 1
    for text in text_list:
        inputs = tokenizer(text['question'], return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
        
        with torch.no_grad():  # 勾配計算を不要にする
            outputs = model(**inputs)
        
        embeddings = average_pool(outputs.last_hidden_state, inputs['attention_mask'])
        embeddings = F.normalize(embeddings, p=2, dim=1)
        vector_string = ",".join([f"{x:.20f}" for x in embeddings[0].cpu().numpy()])  # ベクトルを文字列に変換
        
        writer.writerow({'id': id, 'question': text['question'], 'answer': text['answer'], 'vector': vector_string})
        id += 1

```

CSVは、（一部省略しているが）以下のような出力結果となる。
ここで、ベクトル化されているのは、questionのテキストで、questionをベクトル検索することで、類似する質問とその答えを取得する。

```
id,question,answer,vector
1,あなたの名前は,ネムル,"0.02783655188977718353,0.01221382152289152145,0.02694155462086200714,..."
2,好きなものは,寝ること,"0.00373834278434514999,-0.03592252731323242188,-0.01256802957504987717,..."
3,好き食べ物は,芋,"0.01298194192349910736,-0.00997738633304834366,-0.02407748438417911530,..."
```

### ベクトル検索実行

今回は「出身地は？」という質問を投げて、この質問と類似度の高い質問をCSVから取得し、そのanswerと共に出力する。

```python
import csv
import numpy as np
from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn.functional as F
from torch import Tensor
from scipy.spatial.distance import cdist

model_name = "retrieva-jp/bert-1.3b"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name, trust_remote_code=True)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

def average_pool(last_hidden_states: Tensor, attention_mask: Tensor) -> Tensor:
    last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
    return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]

def cosine_similarity(v1, v2):
    return 1 - cdist([v1], [v2], 'cosine')[0][0]

# クエリテキストをエンベディング
query_text = "出身地は？"
inputs = tokenizer(query_text, return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
with torch.no_grad():
    outputs = model(**inputs)

query_embeddings = average_pool(outputs.last_hidden_state, inputs['attention_mask'])
query_embeddings = F.normalize(query_embeddings, p=2, dim=1).cpu().numpy()[0]

# CSVファイルを読み込み、各レコードとクエリの類似度を計算
similarities = []

with open('q_and_a_with_vectors.csv', mode='r', encoding='utf-8') as csv_file:
    reader = csv.DictReader(csv_file)
    for row in reader:
        vector = np.array([float(x) for x in row['vector'].split(',')])
        similarity = cosine_similarity(query_embeddings, vector)
        similarities.append((row, similarity))

# 類似度でソートし、上位3つの結果を取得
top_matches = sorted(similarities, key=lambda x: x[1], reverse=True)[:3]

for i, (row, similarity) in enumerate(top_matches, 1):
    print(f"Match #{i}:")
    print(f"  Question: {row['question']}")
    print(f"  Answer: {row['answer']}")
    print(f"  Similarity: {similarity:.20f}\n")
```

上記のベクトル検索を実行すると、結果は以下となる、（ベクトル検索の精度を考慮して、類似度が高い３つのテキストを出力する。）

```
Match #1:
  Question: 出身は？
  Answer: ヨネズのコロニー
  Similarity: 0.91511445332937779717

Match #2:
  Question: 趣味は？
  Answer: 寝る以外とかにない
  Similarity: 0.83537017771875232697

Match #3:
  Question: 歳は？
  Answer: 知らない
  Similarity: 0.82222227846813955576
```

今回のテキストは「出身地は？」と「出身は？」で、明らかに近いテキストということもあるが、類似度が「0.91511445332937779717」となり、想定通りの結果となった。

### データ追加

検索対象のデータを追加し、更に検索の精度を確認する。

```python
import csv
from transformers import AutoTokenizer, AutoModel
import torch
from torch import Tensor
import torch.nn.functional as F

# モデルID
model_id = "retrieva-jp/bert-1.3b"

# トークナイザーとモデルの読み込み
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id, trust_remote_code=True)

# デバイス設定 (CUDAが利用可能ならばCUDAを使用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
print(f"Using device: {device}")

# モデルをデバイスに移動
model = model.to(device)

def average_pool(last_hidden_states: Tensor, attention_mask: Tensor) -> Tensor:
    last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
    return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]

text_list = [
    {"question":"あなたの名前は", "answer":"ネムル"},
    {"question":"好きなものは", "answer":"寝ること"},
    {"question":"好き食べ物は", "answer":"芋"},
    {"question":"元気がでるのは", "answer":"寝ること"},
    {"question":"最近うれしかったことは", "answer":"今日晴れてたこと"},
    {"question":"歳は", "answer":"知らない"},
    {"question":"出身は", "answer":"ヨネズのコロニー"},
    {"question":"好きな人は", "answer":"みはる"},
    {"question":"嫌いなことは", "answer":"頑張ること"},
    {"question":"得意な勉強は", "answer":"勉強とかできない"},
    {"question":"朝型か夜型か", "answer":"特にない"},
    {"question":"普段、何をしていますか", "answer":"だいたい寝てるかも"},
    {"question":"趣味は", "answer":"寝る以外特にない"},
    {"question":"最近読んだ本や漫画は", "answer":"本？"},
    {"question":"印象に残っている旅行は", "answer":"シティより遠くに行ったことない？"},
    {"question":"最近挑戦したことは", "answer":"しゃべること"},
    {"question":"最近感動したことは", "answer":"最近暖かくて寝心地がいい"},
    {"question":"最近見たドラマや映画は", "answer":"映画？"},
    {"question":"運動は何が得意", "answer":"運動は苦手"},
    {"question":"好き飲み物は", "answer":"雨水は嫌いだけど、水"},
    {"question":"季節の中で好きなのは", "answer":"春が好き"},
    {"question":"お気に入りの音楽は", "answer":"あんまり知らない"},
    {"question":"誰と一緒に住んでいますか", "answer":"最近はみはる"},
    {"question":"テレビ番組はよく見ますか", "answer":"テレビって何？"},
    {"question":"異性の好みは", "answer":"異性って誰の事？"},
    {"question":"お気に入りの映画ジャンルは", "answer":"映画って何？"},
    {"question":"空いた時間はどう過ごしますか", "answer":"寝てる"},
    {"question":"今の生活で満足していますか", "answer":"特に不満はない"},
    {"question":"将来の夢は", "answer":"ずっと寝ること"},
    {"question":"自分を色で表すと", "answer":"茶色"},
    {"question":"一番長く話したことは何ですか", "answer":"あんまり話せない"},
    {"question":"友達は多いですか", "answer":"少ない"},
    {"question":"どんな映画が嫌い", "answer":"映画が何かわからない"},
    {"question":"絵を描くことは好きですか", "answer":"絵はあまり得意じゃない"},
    {"question":"好きな季節のイベントは", "answer":"晴れてる春"},
    {"question":"休日の過ごし方は", "answer":"寝てる"},
    {"question":"何色の服をよく着ますか", "answer":"茶色しかない"},
    {"question":"最後に笑ったことは", "answer":"みはるが川でひっくり返ったとき"},
    {"question":"携帯電話はよく使いますか", "answer":"携帯って何かわからない"},
    {"question":"自分の長所と短所は", "answer":"一人じゃ何もできない"},
    {"question":"自分を動物に例えると", "answer":"ダンゴムシ"},
    {"question":"好きなスポーツは", "answer":"スポーツが何かわからないよ"},
    {"question":"自炊はしますか", "answer":"まったくしない"},
    {"question":"好きなお菓子は", "answer":"虫の幼虫"},
    {"question":"最近買ったものは", "answer":"買うことなんてない"},
    {"question":"好きなアニメはありますか", "answer":"アニメ？"},
    {"question":"パソコンは使いますか", "answer":"みはるがよく使ってるけど、ネムルはよくわからない"},
    {"question":"お気に入りのレストランは", "answer":"レストランとかはない"},
    {"question":"自分の性格を一言で表すと", "answer":"寝坊助"},
    {"question":"最近感じた小さな幸せは", "answer":"昼まで寝れた時"},
    {"question":"目標は何ですか", "answer":"特にない"},
    {"question":"好きな花は何ですか", "answer":"食べれる花か臭くない花"},
    {"question":"最近行った場所は", "answer":"近くの川"},
    {"question":"好きな言葉は", "answer":"おやすみ"},
    {"question":"今一番欲しいものは", "answer":"やわらかい布団"},
    {"question":"好きな動物は", "answer":"魚"},
    {"question":"尊敬する人は誰ですか", "answer":"ヨネズ"},
    {"question":"一番幸せな瞬間は", "answer":"寝る瞬間"},
    {"question":"最近驚いたことは", "answer":"急に雨が降ったこと"},
    {"question":"朝食は何を食べますか", "answer":"芋"},
    {"question":"夜更かしはしますか", "answer":"しない"},
    {"question":"人混みは好きですか", "answer":"苦手"},
    {"question":"好きな色は", "answer":"特にない"},
    {"question":"一番リラックスできる場所は", "answer":"布団"},
    {"question":"好きなアーティストは", "answer":"知らない"},
    {"question":"旅行先で必ず持っていくものは", "answer":"旅行とか行かない"},
    {"question":"最近会った友人は誰ですか", "answer":"みはる"},
    {"question":"好きな場所はどこですか", "answer":"布団の中"},
    {"question":"最近始めたことはありますか", "answer":"新しい寝方"},
    {"question":"子供の頃の思い出は", "answer":"覚えていない"},
    {"question":"好きな遊びは", "answer":"寝れるところを探す"},
    {"question":"最近見た夢は", "answer":"覚えてない"},
    {"question":"よく使うアプリは", "answer":"アプリって何？"},
    {"question":"今の心境は", "answer":"眠い"},
    {"question":"好きなタイプの天気は", "answer":"曇り"},
    {"question":"最近の悩みは", "answer":"眠い"},
    {"question":"お気に入りの場所は", "answer":"布団"},
    {"question":"最近習得したスキルは", "answer":"特にない"},
    {"question":"好きな香りは", "answer":"芋を焼いた香り"},
    {"question":"何か習い事をしていますか", "answer":"してない"},
    {"question":"休日の過ごし方は", "answer":"寝てる"},
    {"question":"最近のマイブームは", "answer":"昼寝"},
    {"question":"最近感謝したことは", "answer":"みはるが食べ物をくれたこと"},
    {"question":"一番のストレス解消法は", "answer":"寝ること"},
    {"question":"旅行に行くならどこに行きたいですか", "answer":"特に行きたいところはない"},
    {"question":"好きな果物は", "answer":"ブドウ"},
    {"question":"好きなアイスクリームの味は", "answer":"食べたことない"},
    {"question":"一番の宝物は", "answer":"布団"},
    {"question":"最近作った料理は", "answer":"料理はしない"},
    {"question":"今一番行きたい場所は", "answer":"特にない"},
    {"question":"一番の憧れの人は", "answer":"みはる"},
    {"question":"好きな映画は", "answer":"映画は見たことない"},
    {"question":"最近覚えた言葉は", "answer":"おやすみ"},
    {"question":"一番の思い出の場所は", "answer":"ヨネズのコロニー"},
]

# CSVファイルを開き、書き込みます
with open('q_and_a_with_vectors.csv', mode='w', encoding='utf-8', newline='') as csv_file:
    fieldnames = ['id', 'question', 'answer', 'vector']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
    
    writer.writeheader()
    id = 1
    for text in text_list:
        inputs = tokenizer(text['question'], return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
        
        with torch.no_grad():  # 勾配計算を不要にする
            outputs = model(**inputs)
        
        embeddings = average_pool(outputs.last_hidden_state, inputs['attention_mask'])
        embeddings = F.normalize(embeddings, p=2, dim=1)
        vector_string = ",".join([f"{x:.20f}" for x in embeddings[0].cpu().numpy()])  # ベクトルを文字列に変換
        
        writer.writerow({'id': id, 'question': text['question'], 'answer': text['answer'], 'vector': vector_string})
        id += 1
```

上記のプログラムで作成したリストに対して、検索を行う。

質問①：つらいことを教えて

```
Match #1:
  Question: 嫌いなことは
  Answer: 頑張ること
  Similarity: 0.74523602201310901183

Match #2:
  Question: どんな映画が嫌い
  Answer: 映画が何かわからない
  Similarity: 0.74142241982644108145

Match #3:
  Question: 最近感じた小さな幸せは
  Answer: 昼まで寝れた時
  Similarity: 0.73691367820296527213
```


質問②：趣味はなんですか？

```
Match #1:
  Question: 目標は何ですか
  Answer: 特にない
  Similarity: 0.83571777426124516097

Match #2:
  Question: 友達は多いですか
  Answer: 少ない
  Similarity: 0.82980705459516856948

Match #3:
  Question: 好きなアニメはありますか
  Answer: アニメ？
  Similarity: 0.82446367304603718207
```

質問➂：楽しかったのとは？

```
Match #1:
  Question: お気に入りの音楽は
  Answer: あんまり知らない
  Similarity: 0.73919344420386101646

Match #2:
  Question: 子供の頃の思い出は
  Answer: 覚えていない
  Similarity: 0.73540338952105210879

Match #3:
  Question: お気に入りの場所は
  Answer: 布団
  Similarity: 0.73485175636783450770
```

はっきり言って、「intfloat/multilingual-e5-large」には及ばない性能に感じる。

### データにプレフィックス（query:）を追加して精度向上可能か確認

「intfloat/multilingual-e5-large」では、プレフィックス（query:）を追加する前提となっている。
「retrieva-jp/bert-1.3b」でも同様の精度向上が期待できるか確認する。

```python
import csv
from transformers import AutoTokenizer, AutoModel
import torch
from torch import Tensor
import torch.nn.functional as F

# モデルID
model_id = "retrieva-jp/bert-1.3b"

# トークナイザーとモデルの読み込み
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id, trust_remote_code=True)

# デバイス設定 (CUDAが利用可能ならばCUDAを使用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
print(f"Using device: {device}")

# モデルをデバイスに移動
model = model.to(device)

def average_pool(last_hidden_states: Tensor, attention_mask: Tensor) -> Tensor:
    last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
    return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]

text_list = [
    {"question":"query: あなたの名前は", "answer":"ネムル"},
    {"question":"query: 好きなものは", "answer":"寝ること"},
    {"question":"query: 好き食べ物は", "answer":"芋"},
    {"question":"query: 元気がでるのは", "answer":"寝ること"},
    {"question":"query: 最近うれしかったことは", "answer":"今日晴れてたこと"},
    {"question":"query: 歳は", "answer":"知らない"},
    {"question":"query: 出身は", "answer":"ヨネズのコロニー"},
    {"question":"query: 好きな人は", "answer":"みはる"},
    {"question":"query: 嫌いなことは", "answer":"頑張ること"},
    {"question":"query: 得意な勉強は", "answer":"勉強とかできない"},
    {"question":"query: 朝型か夜型か", "answer":"特にない"},
    {"question":"query: 普段、何をしていますか", "answer":"だいたい寝てるかも"},
    {"question":"query: 趣味は", "answer":"寝る以外特にない"},
    {"question":"query: 最近読んだ本や漫画は", "answer":"本？"},
    {"question":"query: 印象に残っている旅行は", "answer":"シティより遠くに行ったことない？"},
    {"question":"query: 最近挑戦したことは", "answer":"しゃべること"},
    {"question":"query: 最近感動したことは", "answer":"最近暖かくて寝心地がいい"},
    {"question":"query: 最近見たドラマや映画は", "answer":"映画？"},
    {"question":"query: 運動は何が得意", "answer":"運動は苦手"},
    {"question":"query: 好き飲み物は", "answer":"雨水は嫌いだけど、水"},
    {"question":"query: 季節の中で好きなのは", "answer":"春が好き"},
    {"question":"query: お気に入りの音楽は", "answer":"あんまり知らない"},
    {"question":"query: 誰と一緒に住んでいますか", "answer":"最近はみはる"},
    {"question":"query: テレビ番組はよく見ますか", "answer":"テレビって何？"},
    {"question":"query: 異性の好みは", "answer":"異性って誰の事？"},
    {"question":"query: お気に入りの映画ジャンルは", "answer":"映画って何？"},
    {"question":"query: 空いた時間はどう過ごしますか", "answer":"寝てる"},
    {"question":"query: 今の生活で満足していますか", "answer":"特に不満はない"},
    {"question":"query: 将来の夢は", "answer":"ずっと寝ること"},
    {"question":"query: 自分を色で表すと", "answer":"茶色"},
    {"question":"query: 一番長く話したことは何ですか", "answer":"あんまり話せない"},
    {"question":"query: 友達は多いですか", "answer":"少ない"},
    {"question":"query: どんな映画が嫌い", "answer":"映画が何かわからない"},
    {"question":"query: 絵を描くことは好きですか", "answer":"絵はあまり得意じゃない"},
    {"question":"query: 好きな季節のイベントは", "answer":"晴れてる春"},
    {"question":"query: 休日の過ごし方は", "answer":"寝てる"},
    {"question":"query: 何色の服をよく着ますか", "answer":"茶色しかない"},
    {"question":"query: 最後に笑ったことは", "answer":"みはるが川でひっくり返ったとき"},
    {"question":"query: 携帯電話はよく使いますか", "answer":"携帯って何かわからない"},
    {"question":"query: 自分の長所と短所は", "answer":"一人じゃ何もできない"},
    {"question":"query: 自分を動物に例えると", "answer":"ダンゴムシ"},
    {"question":"query: 好きなスポーツは", "answer":"スポーツが何かわからないよ"},
    {"question":"query: 自炊はしますか", "answer":"まったくしない"},
    {"question":"query: 好きなお菓子は", "answer":"虫の幼虫"},
    {"question":"query: 最近買ったものは", "answer":"買うことなんてない"},
    {"question":"query: 好きなアニメはありますか", "answer":"アニメ？"},
    {"question":"query: パソコンは使いますか", "answer":"みはるがよく使ってるけど、ネムルはよくわからない"},
    {"question":"query: お気に入りのレストランは", "answer":"レストランとかはない"},
    {"question":"query: 自分の性格を一言で表すと", "answer":"寝坊助"},
    {"question":"query: 最近感じた小さな幸せは", "answer":"昼まで寝れた時"},
    {"question":"query: 目標は何ですか", "answer":"特にない"},
    {"question":"query: 好きな花は何ですか", "answer":"食べれる花か臭くない花"},
    {"question":"query: 最近行った場所は", "answer":"近くの川"},
    {"question":"query: 好きな言葉は", "answer":"おやすみ"},
    {"question":"query: 今一番欲しいものは", "answer":"やわらかい布団"},
    {"question":"query: 好きな動物は", "answer":"魚"},
    {"question":"query: 尊敬する人は誰ですか", "answer":"ヨネズ"},
    {"question":"query: 一番幸せな瞬間は", "answer":"寝る瞬間"},
    {"question":"query: 最近驚いたことは", "answer":"急に雨が降ったこと"},
    {"question":"query: 朝食は何を食べますか", "answer":"芋"},
    {"question":"query: 夜更かしはしますか", "answer":"しない"},
    {"question":"query: 人混みは好きですか", "answer":"苦手"},
    {"question":"query: 好きな色は", "answer":"特にない"},
    {"question":"query: 一番リラックスできる場所は", "answer":"布団"},
    {"question":"query: 好きなアーティストは", "answer":"知らない"},
    {"question":"query: 旅行先で必ず持っていくものは", "answer":"旅行とか行かない"},
    {"question":"query: 最近会った友人は誰ですか", "answer":"みはる"},
    {"question":"query: 好きな場所はどこですか", "answer":"布団の中"},
    {"question":"query: 最近始めたことはありますか", "answer":"新しい寝方"},
    {"question":"query: 子供の頃の思い出は", "answer":"覚えていない"},
    {"question":"query: 好きな遊びは", "answer":"寝れるところを探す"},
    {"question":"query: 最近見た夢は", "answer":"覚えてない"},
    {"question":"query: よく使うアプリは", "answer":"アプリって何？"},
    {"question":"query: 今の心境は", "answer":"眠い"},
    {"question":"query: 好きなタイプの天気は", "answer":"曇り"},
    {"question":"query: 最近の悩みは", "answer":"眠い"},
    {"question":"query: お気に入りの場所は", "answer":"布団"},
    {"question":"query: 最近習得したスキルは", "answer":"特にない"},
    {"question":"query: 好きな香りは", "answer":"芋を焼いた香り"},
    {"question":"query: 何か習い事をしていますか", "answer":"してない"},
    {"question":"query: 休日の過ごし方は", "answer":"寝てる"},
    {"question":"query: 最近のマイブームは", "answer":"昼寝"},
    {"question":"query: 最近感謝したことは", "answer":"みはるが食べ物をくれたこと"},
    {"question":"query: 一番のストレス解消法は", "answer":"寝ること"},
    {"question":"query: 旅行に行くならどこに行きたいですか", "answer":"特に行きたいところはない"},
    {"question":"query: 好きな果物は", "answer":"ブドウ"},
    {"question":"query: 好きなアイスクリームの味は", "answer":"食べたことない"},
    {"question":"query: 一番の宝物は", "answer":"布団"},
    {"question":"query: 最近作った料理は", "answer":"料理はしない"},
    {"question":"query: 今一番行きたい場所は", "answer":"特にない"},
    {"question":"query: 一番の憧れの人は", "answer":"みはる"},
    {"question":"query: 好きな映画は", "answer":"映画は見たことない"},
    {"question":"query: 最近覚えた言葉は", "answer":"おやすみ"},
    {"question":"query: 一番の思い出の場所は", "answer":"ヨネズのコロニー"},
]

# CSVファイルを開き、書き込みます
with open('q_and_a_with_vectors.csv', mode='w', encoding='utf-8', newline='') as csv_file:
    fieldnames = ['id', 'question', 'answer', 'vector']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
    
    writer.writeheader()
    id = 1
    for text in text_list:
        inputs = tokenizer(text['question'], return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
        
        with torch.no_grad():  # 勾配計算を不要にする
            outputs = model(**inputs)
        
        embeddings = average_pool(outputs.last_hidden_state, inputs['attention_mask'])
        embeddings = F.normalize(embeddings, p=2, dim=1)
        vector_string = ",".join([f"{x:.20f}" for x in embeddings[0].cpu().numpy()])  # ベクトルを文字列に変換
        
        writer.writerow({'id': id, 'question': text['question'], 'answer': text['answer'], 'vector': vector_string})
        id += 1
```

質問①：query: つらいことを教えて

```
Match #1:
  Question: query: 嫌いなことは
  Answer: 頑張ること
  Similarity: 0.84877390310301736864

Match #2:
  Question: query: 最近驚いたことは
  Answer: 急に雨が降ったこと
  Similarity: 0.83997002919338481508

Match #3:
  Question: query: パソコンは使いますか
  Answer: みはるがよく使ってるけど、ネムルはよくわからない
  Similarity: 0.83852142180097732904
```

質問②：query: 趣味はなんですか？

```
Match #1:
  Question: query: 目標は何ですか
  Answer: 特にない
  Similarity: 0.90452070846932797199

Match #2:
  Question: query: 好きな花は何ですか
  Answer: 食べれる花か臭くない花
  Similarity: 0.90241369690186490349

Match #3:
  Question: query: 友達は多いですか
  Answer: 少ない
  Similarity: 0.89114118790260821168
```

質問➂：query: 楽しかったのとは？

```
Match #1:
  Question: query: 最後に笑ったことは
  Answer: みはるが川でひっくり返ったとき
  Similarity: 0.85992820461276175958

Match #2:
  Question: query: お気に入りの場所は
  Answer: 布団
  Similarity: 0.85216680207674377900

Match #3:
  Question: query: 好きな場所はどこですか
  Answer: 布団の中
  Similarity: 0.84628918594805235820
```

気のせいかもしれないが、プレフィックスをつけないよりは、精度が上がっているように感じる。

# 感想

トークン数をどうしても増やす必要がある場合は利用を検討してもよいと感じる。
ただ、トークン数の制限が気にならないケースであれば、「intfloat/multilingual-e5-large」を利用することをお勧めする。
そもそもファインチューニングなどで後処理をする前提のモデルのため、チューニング次第で、活躍の幅は広がるかもしれない