mjpost / sacrebleu

Reference BLEU implementation that auto-downloads test sets and reports a version string to facilitate cross-lab comparisons
Apache License 2.0
1.03k stars 162 forks source link

Compute BLEU from Python mishandles references #256

Closed OrianeN closed 7 months ago

OrianeN commented 7 months ago

I've tried to compute BLEU scores from the Python API as described in the README https://github.com/mjpost/sacrebleu#using-sacrebleu-from-python, but I get strange results that do not correspond to the score I get from the CLI.

Here's an example to reproduce: $ sacrebleu -t wmt14 -l en-fr --echo src > wmt14.en-fr.en $ sacrebleu -t wmt14 -l en-fr --echo src > wmt14.en-fr.fr

My predictions are in wmt14.en-fr.preds.txt (rename it to "wmt14.en-fr.preds")

$ !head {dataset_noext}.preds

s de l'article 5 de la loi sur les questions de l'environnement
Il a été le système de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne a été de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne a été de l'Union européenne.
Les membres de l'Union européenne, les s de l'Union européenne et les s, mais aussi des s de l'Union européenne, mais aussi des s, mais aussi des s de l'Union européenne, mais aussi des s, mais aussi des s, mais aussi des s, mais aussi des s, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, mais aussi des s, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, mais aussi des s, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, mais aussi des s et de l'Union européenne.
Il est de ce que l'Union européenne, mais aussi une nouvelle nouvelle nouvelle nouvelle nouvelle nouvelle nouvelle nouvelle nouvelle nouvelle de l'Union européenne, mais il faut que le fait de l'Union européenne, mais il faut que l'Union européenne, mais il faut une nouvelle nouvelle nouvelle nouvelle nouvelle nouvelle de l'Union européenne.
Le Groupe de travail a été le droit de la loi sur les personnes qui ont été de la situation des personnes qui ont été de la paix, en particulier à l'Union européenne.
L'Union européenne est un rôle de l'Union européenne pour l'Union européenne, qui est un rôle de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne ou de l'Union européenne.
Les membres de l'Union européenne ont été le droit de l'Union européenne et de l'environnement, en particulier dans le domaine de l'environnement.
Le Comité a été le rôle de l'article 2 de la loi sur le plan de la question de la paix de l'Union européenne.
Les membres de l'Union européenne ont été le droit de l'Union européenne.
Il est de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, la loi sur le droit de l'Union européenne, la loi, la loi, la loi, la loi sur le droit de l'Union européenne.

When running sacreBLEU in CLI, I get this: $ sacrebleu {dataset_noext}.fr -i {dataset_noext}.preds -m bleu -w 4

{
 "name": "BLEU",
 "score": 0.4024,
 "signature": "nrefs:1|case:mixed|eff:no|tok:13a|smooth:exp|version:2.4.0",
 "verbose_score": "10.9/1.1/0.1/0.0 (BP = 1.000 ratio = 2.011 hyp_len = 155468 ref_len = 77306)",
 "nrefs": "1",
 "case": "mixed",
 "eff": "no",
 "tok": "13a",
 "smooth": "exp",
 "version": "2.4.0"
}

In Python, I first load the references and predictions, both with imbricated references as described in the README, and also with flat references to compare:

refs_flat = []
refs_imbricated = []
with open(f"{dataset_noext}.fr", "r") as f:
    for line in f:
        line = line.strip()
        refs_flat.append(line)
        refs_imbricated.append([line])

print("First refs flat:", refs_flat[:3])
print("First refs imbricated:", refs_imbricated[:3])

with open(f"{dataset_noext}.preds", "r") as f:
    preds = [line.strip() for line in f]

print("First preds:", preds[:3])
First refs flat: ['Les avionneurs se querellent au sujet de la largeur des sièges alors que de grosses commandes sont en jeu', 'La dispute fait rage entre les grands constructeurs aéronautiques à propos de la largeur des sièges de la classe touriste sur les vols long-courriers, ouvrant la voie à une confrontation amère lors du salon aéronautique de Dubaï qui a lieu de mois-ci.', "Le conflit porte sur la largeur des sièges proposés sur les vols long-courriers aux passagers de la classe économique – qui ne sont pas toujours les plus courtisés par les compagnies aériennes, mais auxquels l'espace alloué est essentiel pour augmenter les gains d'efficacité dans les derniers appareils présentés par Airbus SAS et Boeing Co."]
First refs imbricated: [['Les avionneurs se querellent au sujet de la largeur des sièges alors que de grosses commandes sont en jeu'], ['La dispute fait rage entre les grands constructeurs aéronautiques à propos de la largeur des sièges de la classe touriste sur les vols long-courriers, ouvrant la voie à une confrontation amère lors du salon aéronautique de Dubaï qui a lieu de mois-ci.'], ["Le conflit porte sur la largeur des sièges proposés sur les vols long-courriers aux passagers de la classe économique – qui ne sont pas toujours les plus courtisés par les compagnies aériennes, mais auxquels l'espace alloué est essentiel pour augmenter les gains d'efficacité dans les derniers appareils présentés par Airbus SAS et Boeing Co."]]
First preds: ["s de l'article 5 de la loi sur les questions de l'environnement", "Il a été le système de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne a été de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne a été de l'Union européenne.", "Les membres de l'Union européenne, les s de l'Union européenne et les s, mais aussi des s de l'Union européenne, mais aussi des s, mais aussi des s de l'Union européenne, mais aussi des s, mais aussi des s, mais aussi des s, mais aussi des s, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, mais aussi des s, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, mais aussi des s, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, de l'Union européenne, mais aussi des s et de l'Union européenne."]

BLEU with imbricated refs:

# Compute BLEU with imbricated refs
bleu = BLEU()
bleu_score_imbricated = bleu.corpus_score(preds, refs_imbricated).score
print(bleu_score_imbricated)
print(bleu.get_signature())
32.94791210580041
nrefs:3003|case:mixed|eff:no|tok:13a|smooth:exp|version:2.4.0

BLEU with flat refs:

# Compute BLEU with flat refs
bleu = BLEU()
bleu_score_flat = bleu.corpus_score(preds, refs_flat).score
print(bleu_score_flat)
print(bleu.get_signature())
0.08081679962246334
nrefs:3003|case:mixed|eff:no|tok:13a|smooth:exp|version:2.4.0

The issue seems to be the number of recognized references, which is 3003 when using the Python API compared to the expected 1 with the CLI.

I don't get what I'm doing wrong, can someone help me ?

tofula commented 7 months ago

Hi Oriane,

I guess you should create the reference list as the following: [[ref1-segment1, ref1-segment2 ,..], [ref2-segment1, ref2-segment2 ,..], ... ]

For your existing structure, you can use following command to transform it to the desired structure: list(map(list, zip(*refs)))

Hope this helps.

Tomas

OrianeN commented 7 months ago

Now I get it, I thought the imbrication should be like [[ref1-segment1, ref2-segment1], [ref1-segment2, ref2-segment2], ...].

This code solves it then to have the same score as the CLI:

# Compute BLEU with flat refs imbricated once to get [[ref1, ref2, ...]]
bleu = BLEU()
bleu_score_flat = bleu.corpus_score(preds, [refs_flat]).score
print(bleu_score_flat)
print(bleu.get_signature())
0.40243871535386055
nrefs:1|case:mixed|eff:no|tok:13a|smooth:exp|version:2.4.0

Thank you very much @tofula !