BERTScoreの基本的な使い方

## はじめに
本記事は私自身の活動記録、および備忘録を目的としているので、間違っている内容が含まれているかもしれません。もし誤解を招くような内容や、間違っている内容があった場合はコメントをいただけると幸いです。

## 動作環境
```console
pip3: 23.1.2
python3: 3.9.6
numpy: 1.23.5
SentencePiece: 0.1.99
```
BERTScoreのより詳しい情報は下記のリンク先などをご確認ください。
- https://www.nogawanogawa.com/entry/bertscore
- https://github.com/Tiiiger/bert_score

## 基本的なスコアの出し方
まずはインストールをする。SentencePieceについては、計算に用いるモデルによっては必要になる。
```python
pip3 install bert_score
pip3 install SentencePiece
```
次にスコアを計算する教師データと検査データを用意する。ここでは例として以下のデータを用意する。
```python
refs = [
    "夕食には寿司を食べるのが好きです。", 
    "今日はいい天気ですね", 
    "今日は本当にいい天気",
    "暇な時間にはビデオゲームをするのが好きです。", 
    "太陽が空で輝いています。", 
    "今週末、海に旅行に行くつもりです。",
]
cands = [
    "夕食に食べるのは寿司が一番好きな食べ物です。", 
    "今日は良くない天気ですね",
    "今日は本当にいい天気", 
    "暇な時間にはビデオゲームをするのは楽しいです。", 
    "外では今、激しい雨が降っています。", 
    "週末は仕事で、楽しいことをすることができません。",
]
```
あとは下記の1行でスコアを取得できる。P,R,F1は、Precision, Recall, F1を表している。単純に類似度だけ求めたい場合はF1のみの結果を得れば問題ない（気がする）。
```python
P, R, F1 = score(cands, refs, lang="ja")
```
配列の形のスコアを取得する場合は、以下のようにする。
```python
P_score = P.numpy().tolist()
R_score = R.numpy().tolist()
F1_score = F1.numpy().tolist()
```
※refsとcandsがそれぞれ1つしかデータがない、すなわち以下のようになる場合、
```python
refs = [
    "夕食には寿司を食べるのが好きです。",
]
cands = [
    "夕食に食べるのは寿司が一番好きな食べ物です。",
]
```
配列の形のスコアを取得する場合は、以下のようにする。
```python
P_score = P.item()
R_score = R.item()
F1_score = F1.item()
```
注意点としては、データが1つしかない場合でも、必ずリストとしてbert_scoreに渡す必要がある。また、refsとcandsはデータ数を揃えなければならない。

## 実装例

以下は出力例である。score関数にverbose=Trueを追加しておけば、途中経過を随時確認することができる。
```python
from bert_score import score

def calc_bert_score(cands, refs):
    P, R, F1 = score(cands, refs, lang="ja", verbose=True)
    return F1.numpy().tolist() # 複数のデータを一気に計算する場合はこちら
    # return F1.item() # データを1つずつ計算する場合はこちら


refs = [
    "夕食には寿司を食べるのが好きです。", 
    "今日はいい天気ですね", 
    "今日は本当にいい天気",
    "暇な時間にはビデオゲームをするのが好きです。", 
    "太陽が空で輝いています。", 
    "今週末、海に旅行に行くつもりです。",
]
cands = [
    "夕食に食べるのは寿司が一番好きな食べ物です。", 
    "今日は良くない天気ですね",
    "今日は本当にいい天気", 
    "暇な時間にはビデオゲームをするのは楽しいです。", 
    "外では今、激しい雨が降っています。", 
    "週末は仕事で、楽しいことをすることができません。",
]

# 複数データを一気に計算するプログラム
f1_score = calc_bert_score(refs, cands)
for r, c, f1 in zip(refs, cands, f1_score):
    print(f"refs: {r}, cands: {c}, f1_score: {f1}")


# データを一つずつ計算するプログラム
# for i in range(len(refs)):
#     f1_score = calc_bert_score([refs[i]], [cands[i]])  
#     print(f"refs: {refs[i]}, cands: {cands[i]}, f1_score: {f1_score}")
```
以下が出力。
```console
refs: 夕食には寿司を食べるのが好きです。, cands: 夕食に食べるのは寿司が一番好きな食べ物です。, f1_score: 0.8283668160438538
refs: 今日はいい天気ですね, cands: 今日は良くない天気ですね, f1_score: 0.9224663376808167
refs: 今日は本当にいい天気, cands: 今日は本当にいい天気, f1_score: 1.0000001192092896
refs: 暇な時間にはビデオゲームをするのが好きです。, cands: 暇な時間にはビデオゲームをするのは楽しいです。, f1_score: 0.9004584550857544
refs: 太陽が空で輝いています。, cands: 外では今、激しい雨が降っています。, f1_score: 0.7608160972595215
refs: 今週末、海に旅行に行くつもりです。, cands: 週末は仕事で、楽しいことをすることができません。, f1_score: 0.7464361190795898
```
似ている文章同士は、そうでない文章同士に比べて高い点数が出力されている。概ね正しい使い方ができているのではないだろうか。

## モデルの指定
モデルの指定は、以下のようにhuggingfaceに挙げてあるモデルを指定すればそのまま用いることができる（中には使用できないものもある）。自分で再学習などをして作成したモデルを使用したい場合は、ローカルの相対パスを指定すれば使用できる。何も指定しない場合、「bert-base-multilingual-cased」というモデルが使用されるが、「lang」か「model_type」のどちらかはオプションに指定しなければならない。また、モデルによっては「num_layers」というオプションが必要な場合がある（これはどのくらい深い層の値を計算に用いるかを表す）。例えば「"ku-nlp/deberta-v2-tiny-japanese"」はnum_layersを0から3に指定しなければならない。なるべく深い層の値を用いた方が良いということであれば、num_layersを3にすれば良い。
```python
model = [
    "distilbert-base-multilingual-cased",
    "bandainamco-mirai/distilbert-base-japanese",
    "ku-nlp/deberta-v2-tiny-japanese",
    "microsoft/deberta-v3-xsmall",
    "line-corporation/line-distilbert-base-japanese",
]

def calc_bert_score(cands, refs):
    P, R, F1 = score(cands, refs, model_type=model[2], num_layers=3)
    return F1.numpy().tolist() # 複数のデータを一気に計算する場合はこちら
    # return F1.item() # データを1つずつ計算する場合はこちら
```

## 終わりに
ニューラルネットワークや機械学習についての基礎を勉強している最中であるため、まだまだ細かいオプションの設定についてはよく分かっていない。今後こういったモデルの再学習も行ってスコアを出したいと思っているため、仕組みから確実に理解していきたい。