sacreBLEUによる翻訳モデルの評価

# 概要
日々、新しい言語モデルがHugging Faceなどに投稿されています。それらの翻訳性能を比較したいと思い、評価方法について調査しました。
その結果、[sacreBLEU](https://github.com/mjpost/sacrebleu)というツールが便利そうだったので、その使用方法をまとめます。

# sacreBLEUのインストール
sacreBLEUはPythonで書かれており、`pip`コマンドでインストールできます。
日本語を扱う場合は[`mecab-python3`](https://pypi.org/project/mecab-python3/) が必要らしく、`ja` オプションをつけてインストールします。

```shell-session
pip install "sacrebleu[ja]"
```

なお、`ja`オプションを付けているのにトークナイザー関連のエラーが出る場合、`mecab-python3`のアップデートが必要かもしれません。

```shell-session
pip install -U mecab-python3
```

# テストデータの取得
まず、テストデータを選んで出力します。利用可能なデータセットの一覧は次のコマンドで確認できます。

```shell-session
sacrebleu --list
```

この例では、[WMT23](https://www2.statmt.org/wmt23/)データセットを使用して英日翻訳の性能を評価します。
以下のコマンドでテストデータをファイル`wmt23.en-ja.en`に出力します。


```shell-session
sacrebleu -t wmt23 -l en-ja --echo src > wmt23.en-ja.en
```

評価対象の言語モデルを使ってこのファイルの各行を翻訳し、別のファイルに保存します。

# テストデータの翻訳

実験として、軽量かつ高性能な[fuguMT](https://huggingface.co/staka/fugumt-en-ja)を使用して翻訳を行います。
この実験では、[CTranslate2](https://github.com/OpenNMT/CTranslate2)を[Rustバインディング](https://github.com/jkawamoto/ctranslate2-rs)を介して使用しました。
具体的なソースコードは以下のとおりです。

```toml:Cargo.toml
[package]
name = "translate"
edition = "2021"

[dependencies]
anyhow = "1.0.86"
ct2rs = "0.8.5"
hf-hub = "0.3.2"
```

```rust:src/main.rs
use std::env;
use std::fmt::Display;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;

use anyhow::{anyhow, Result};
use ct2rs::{TranslationOptions, Translator};

fn main() -> Result<()> {
    let translator = Translator::new(find_model_dir("fugumt-en-ja")?, &Default::default())?;
    let opts = TranslationOptions {
        beam_size: 5,
        ..Default::default()
    };

    let n = 8; // batch size
    let mut iter = BufReader::new(File::open(
        env::args()
            .skip(1)
            .next()
            .ok_or_else(|| anyhow!("input file is required"))?,
    )?)
    .lines()
    .peekable();
    while iter.peek().is_some() {
        let res = translator.translate_batch(
            &iter
                .by_ref()
                .take(n)
                .collect::<std::result::Result<Vec<String>, std::io::Error>>()?,
            &opts,
            None,
        )?;
        res.into_iter().for_each(|(r, _)| println!("{}", r));
    }

    Ok(())
}

fn find_model_dir<T: Display>(model: T) -> Result<PathBuf> {
    let api = hf_hub::api::sync::Api::new()?;
    let repo = api.model(format!("jkawamoto/{}-ct2", model));

    let mut res = None;
    for f in repo.info()?.siblings {
        let path = repo.get(&f.rfilename)?;
        if res.is_none() {
            res = path.parent().map(PathBuf::from);
        }
    }

    res.ok_or_else(|| anyhow!("no model files are found"))
}
```

以下のコマンドを実行すると、`fugumt.out`ファイルに翻訳結果が保存されます。

```shell-session
cargo run -- wmt23.en-ja.en > fugumt.out
```

# 翻訳結果の評価

翻訳結果からBLEUスコアを計算するには、次のコマンドを実行します。
```shell-session
sacrebleu -t wmt23 -l en-ja -i fugumt.out
```

`beam_size`を`5`に設定したfuguMTによるBLEUスコアは、`18.0`という結果でした。

```json
{
 "name": "BLEU",
 "score": 18.0,
 "signature": "nrefs:1|case:mixed|eff:no|tok:ja-mecab-0.996-IPA|smooth:exp|version:2.4.2",
 "verbose_score": "53.8/25.1/13.4/7.5 (BP = 0.940 ratio = 0.942 hyp_len = 49459 ref_len = 52516)",
 "nrefs": "1",
 "case": "mixed",
 "eff": "no",
 "tok": "ja-mecab-0.996-IPA",
 "smooth": "exp",
 "version": "2.4.2"
}
```