CoolFace
Apppublic

chendl/compositional_test

sourceHugging Faceupdated 3y agoView on Hugging Face
1likes
eval_rag.py321 linesDownload Raw Back to rag
1""" Evaluation script for RAG models."""2 3import argparse4import ast5import logging6import os7import sys8 9import pandas as pd10import torch11from tqdm import tqdm12 13from transformers import BartForConditionalGeneration, RagRetriever, RagSequenceForGeneration, RagTokenForGeneration14from transformers import logging as transformers_logging15 16 17sys.path.append(os.path.join(os.getcwd()))  # noqa: E402 # isort:skip18from utils_rag import exact_match_score, f1_score  # noqa: E402 # isort:skip19 20 21logger = logging.getLogger(__name__)22logging.basicConfig(level=logging.INFO)23 24transformers_logging.set_verbosity_info()25 26 27def infer_model_type(model_name_or_path):28    if "token" in model_name_or_path:29        return "rag_token"30    if "sequence" in model_name_or_path:31        return "rag_sequence"32    if "bart" in model_name_or_path:33        return "bart"34    return None35 36 37def metric_max_over_ground_truths(metric_fn, prediction, ground_truths):38    return max(metric_fn(prediction, gt) for gt in ground_truths)39 40 41def get_scores(args, preds_path, gold_data_path):42    hypos = [line.strip() for line in open(preds_path, "r").readlines()]43    answers = []44 45    if args.gold_data_mode == "qa":46        data = pd.read_csv(gold_data_path, sep="\t", header=None)47        for answer_list in data[1]:48            ground_truths = ast.literal_eval(answer_list)49            answers.append(ground_truths)50    else:51        references = [line.strip() for line in open(gold_data_path, "r").readlines()]52        answers = [[reference] for reference in references]53 54    f1 = em = total = 055    for prediction, ground_truths in zip(hypos, answers):56        total += 157        em += metric_max_over_ground_truths(exact_match_score, prediction, ground_truths)58        f1 += metric_max_over_ground_truths(f1_score, prediction, ground_truths)59 60    em = 100.0 * em / total61    f1 = 100.0 * f1 / total62 63    logger.info(f"F1: {f1:.2f}")64    logger.info(f"EM: {em:.2f}")65 66 67def get_precision_at_k(args, preds_path, gold_data_path):68    k = args.k69    hypos = [line.strip() for line in open(preds_path, "r").readlines()]70    references = [line.strip() for line in open(gold_data_path, "r").readlines()]71 72    em = total = 073    for hypo, reference in zip(hypos, references):74        hypo_provenance = set(hypo.split("\t")[:k])75        ref_provenance = set(reference.split("\t"))76        total += 177        em += len(hypo_provenance & ref_provenance) / k78 79    em = 100.0 * em / total80    logger.info(f"Precision@{k}: {em: .2f}")81 82 83def evaluate_batch_retrieval(args, rag_model, questions):84    def strip_title(title):85        if title.startswith('"'):86            title = title[1:]87        if title.endswith('"'):88            title = title[:-1]89        return title90 91    retriever_input_ids = rag_model.retriever.question_encoder_tokenizer.batch_encode_plus(92        questions,93        return_tensors="pt",94        padding=True,95        truncation=True,96    )["input_ids"].to(args.device)97 98    question_enc_outputs = rag_model.rag.question_encoder(retriever_input_ids)99    question_enc_pool_output = question_enc_outputs[0]100 101    result = rag_model.retriever(102        retriever_input_ids,103        question_enc_pool_output.cpu().detach().to(torch.float32).numpy(),104        prefix=rag_model.rag.generator.config.prefix,105        n_docs=rag_model.config.n_docs,106        return_tensors="pt",107    )108    all_docs = rag_model.retriever.index.get_doc_dicts(result.doc_ids)109    provenance_strings = []110    for docs in all_docs:111        provenance = [strip_title(title) for title in docs["title"]]112        provenance_strings.append("\t".join(provenance))113    return provenance_strings114 115 116def evaluate_batch_e2e(args, rag_model, questions):117    with torch.no_grad():118        inputs_dict = rag_model.retriever.question_encoder_tokenizer.batch_encode_plus(119            questions, return_tensors="pt", padding=True, truncation=True120        )121 122        input_ids = inputs_dict.input_ids.to(args.device)123        attention_mask = inputs_dict.attention_mask.to(args.device)124        outputs = rag_model.generate(  # rag_model overwrites generate125            input_ids,126            attention_mask=attention_mask,127            num_beams=args.num_beams,128            min_length=args.min_length,129            max_length=args.max_length,130            early_stopping=False,131            num_return_sequences=1,132            bad_words_ids=[[0, 0]],  # BART likes to repeat BOS tokens, dont allow it to generate more than one133        )134        answers = rag_model.retriever.generator_tokenizer.batch_decode(outputs, skip_special_tokens=True)135 136        if args.print_predictions:137            for q, a in zip(questions, answers):138                logger.info("Q: {} - A: {}".format(q, a))139 140        return answers141 142 143def get_args():144    parser = argparse.ArgumentParser()145    parser.add_argument(146        "--model_type",147        choices=["rag_sequence", "rag_token", "bart"],148        type=str,149        help=(150            "RAG model type: rag_sequence, rag_token or bart, if none specified, the type is inferred from the"151            " model_name_or_path"152        ),153    )154    parser.add_argument(155        "--index_name",156        default=None,157        choices=["exact", "compressed", "legacy"],158        type=str,159        help="RAG model retriever type",160    )161    parser.add_argument(162        "--index_path",163        default=None,164        type=str,165        help="Path to the retrieval index",166    )167    parser.add_argument("--n_docs", default=5, type=int, help="Number of retrieved docs")168    parser.add_argument(169        "--model_name_or_path",170        default=None,171        type=str,172        required=True,173        help="Path to pretrained checkpoints or model identifier from huggingface.co/models",174    )175    parser.add_argument(176        "--eval_mode",177        choices=["e2e", "retrieval"],178        default="e2e",179        type=str,180        help=(181            "Evaluation mode, e2e calculates exact match and F1 of the downstream task, retrieval calculates"182            " precision@k."183        ),184    )185    parser.add_argument("--k", default=1, type=int, help="k for the precision@k calculation")186    parser.add_argument(187        "--evaluation_set",188        default=None,189        type=str,190        required=True,191        help="Path to a file containing evaluation samples",192    )193    parser.add_argument(194        "--gold_data_path",195        default=None,196        type=str,197        required=True,198        help="Path to a tab-separated file with gold samples",199    )200    parser.add_argument(201        "--gold_data_mode",202        default="qa",203        type=str,204        choices=["qa", "ans"],205        help=(206            "Format of the gold data file"207            "qa - a single line in the following format: question [tab] answer_list"208            "ans - a single line of the gold file contains the expected answer string"209        ),210    )211    parser.add_argument(212        "--predictions_path",213        type=str,214        default="predictions.txt",215        help="Name of the predictions file, to be stored in the checkpoints directory",216    )217    parser.add_argument(218        "--eval_all_checkpoints",219        action="store_true",220        help="Evaluate all checkpoints starting with the same prefix as model_name ending and ending with step number",221    )222    parser.add_argument(223        "--eval_batch_size",224        default=8,225        type=int,226        help="Batch size per GPU/CPU for evaluation.",227    )228    parser.add_argument(229        "--recalculate",230        help="Recalculate predictions even if the prediction file exists",231        action="store_true",232    )233    parser.add_argument(234        "--num_beams",235        default=4,236        type=int,237        help="Number of beams to be used when generating answers",238    )239    parser.add_argument("--min_length", default=1, type=int, help="Min length of the generated answers")240    parser.add_argument("--max_length", default=50, type=int, help="Max length of the generated answers")241 242    parser.add_argument(243        "--print_predictions",244        action="store_true",245        help="If True, prints predictions while evaluating.",246    )247    parser.add_argument(248        "--print_docs",249        action="store_true",250        help="If True, prints docs retried while generating.",251    )252    args = parser.parse_args()253    args.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")254    return args255 256 257def main(args):258    model_kwargs = {}259    if args.model_type is None:260        args.model_type = infer_model_type(args.model_name_or_path)261        assert args.model_type is not None262    if args.model_type.startswith("rag"):263        model_class = RagTokenForGeneration if args.model_type == "rag_token" else RagSequenceForGeneration264        model_kwargs["n_docs"] = args.n_docs265        if args.index_name is not None:266            model_kwargs["index_name"] = args.index_name267        if args.index_path is not None:268            model_kwargs["index_path"] = args.index_path269    else:270        model_class = BartForConditionalGeneration271 272    checkpoints = (273        [f.path for f in os.scandir(args.model_name_or_path) if f.is_dir()]274        if args.eval_all_checkpoints275        else [args.model_name_or_path]276    )277 278    logger.info("Evaluate the following checkpoints: %s", checkpoints)279 280    score_fn = get_scores if args.eval_mode == "e2e" else get_precision_at_k281    evaluate_batch_fn = evaluate_batch_e2e if args.eval_mode == "e2e" else evaluate_batch_retrieval282 283    for checkpoint in checkpoints:284        if os.path.exists(args.predictions_path) and (not args.recalculate):285            logger.info("Calculating metrics based on an existing predictions file: {}".format(args.predictions_path))286            score_fn(args, args.predictions_path, args.gold_data_path)287            continue288 289        logger.info("***** Running evaluation for {} *****".format(checkpoint))290        logger.info("  Batch size = %d", args.eval_batch_size)291        logger.info("  Predictions will be stored under {}".format(args.predictions_path))292 293        if args.model_type.startswith("rag"):294            retriever = RagRetriever.from_pretrained(checkpoint, **model_kwargs)295            model = model_class.from_pretrained(checkpoint, retriever=retriever, **model_kwargs)296            model.retriever.init_retrieval()297        else:298            model = model_class.from_pretrained(checkpoint, **model_kwargs)299        model.to(args.device)300 301        with open(args.evaluation_set, "r") as eval_file, open(args.predictions_path, "w") as preds_file:302            questions = []303            for line in tqdm(eval_file):304                questions.append(line.strip())305                if len(questions) == args.eval_batch_size:306                    answers = evaluate_batch_fn(args, model, questions)307                    preds_file.write("\n".join(answers) + "\n")308                    preds_file.flush()309                    questions = []310            if len(questions) > 0:311                answers = evaluate_batch_fn(args, model, questions)312                preds_file.write("\n".join(answers))313                preds_file.flush()314 315            score_fn(args, args.predictions_path, args.gold_data_path)316 317 318if __name__ == "__main__":319    args = get_args()320    main(args)321