| """Fine-tune BART/T5/Pegasus on CNN/DailyMail or XSum.""" |
|
|
| import argparse |
| import json |
| import os |
|
|
| import evaluate |
| import numpy as np |
| from datasets import load_dataset |
| from transformers import ( |
| AutoModelForSeq2SeqLM, |
| AutoTokenizer, |
| DataCollatorForSeq2Seq, |
| Seq2SeqTrainer, |
| Seq2SeqTrainingArguments, |
| set_seed, |
| ) |
|
|
|
|
| DATASETS = { |
| "cnn_dailymail": { |
| "path": "cnn_dailymail", |
| "name": "3.0.0", |
| "text": "article", |
| "summary": "highlights", |
| }, |
| "xsum": { |
| "path": "EdinburghNLP/xsum", |
| "name": None, |
| "text": "document", |
| "summary": "summary", |
| }, |
| } |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--dataset", choices=DATASETS, default="cnn_dailymail") |
| parser.add_argument("--model", default="facebook/bart-base") |
| parser.add_argument("--output-dir", default="outputs/summarization-model") |
| parser.add_argument("--epochs", type=float, default=3) |
| parser.add_argument("--batch-size", type=int, default=4) |
| parser.add_argument("--learning-rate", type=float, default=3e-5) |
| parser.add_argument("--max-input-length", type=int, default=1024) |
| parser.add_argument("--max-target-length", type=int, default=128) |
| parser.add_argument("--train-samples", type=int) |
| parser.add_argument("--eval-samples", type=int) |
| parser.add_argument("--gradient-accumulation-steps", type=int, default=4) |
| parser.add_argument("--seed", type=int, default=42) |
| parser.add_argument("--fp16", action="store_true") |
| parser.add_argument("--push-to-hub", action="store_true") |
| parser.add_argument("--hub-model-id") |
| return parser.parse_args() |
|
|
|
|
| def main(): |
| args = parse_args() |
| set_seed(args.seed) |
| config = DATASETS[args.dataset] |
| dataset = load_dataset(config["path"], config["name"]) |
| tokenizer = AutoTokenizer.from_pretrained(args.model) |
| model = AutoModelForSeq2SeqLM.from_pretrained(args.model) |
|
|
| train_data = dataset["train"] |
| eval_split = "validation" if "validation" in dataset else "test" |
| eval_data = dataset[eval_split] |
| if args.train_samples: |
| train_data = train_data.select(range(min(args.train_samples, len(train_data)))) |
| if args.eval_samples: |
| eval_data = eval_data.select(range(min(args.eval_samples, len(eval_data)))) |
|
|
| def preprocess(batch): |
| model_inputs = tokenizer( |
| batch[config["text"]], |
| max_length=args.max_input_length, |
| truncation=True, |
| ) |
| labels = tokenizer( |
| text_target=batch[config["summary"]], |
| max_length=args.max_target_length, |
| truncation=True, |
| ) |
| model_inputs["labels"] = labels["input_ids"] |
| return model_inputs |
|
|
| remove_columns = train_data.column_names |
| train_data = train_data.map(preprocess, batched=True, remove_columns=remove_columns) |
| eval_data = eval_data.map(preprocess, batched=True, remove_columns=remove_columns) |
| rouge = evaluate.load("rouge") |
|
|
| def compute_metrics(prediction): |
| predictions, labels = prediction |
| if isinstance(predictions, tuple): |
| predictions = predictions[0] |
| labels = np.where(labels != -100, labels, tokenizer.pad_token_id) |
| decoded_predictions = tokenizer.batch_decode(predictions, skip_special_tokens=True) |
| decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) |
| scores = rouge.compute( |
| predictions=[text.strip() for text in decoded_predictions], |
| references=[text.strip() for text in decoded_labels], |
| use_stemmer=True, |
| ) |
| return {key: round(value * 100, 4) for key, value in scores.items()} |
|
|
| training_args = Seq2SeqTrainingArguments( |
| output_dir=args.output_dir, |
| num_train_epochs=args.epochs, |
| learning_rate=args.learning_rate, |
| per_device_train_batch_size=args.batch_size, |
| per_device_eval_batch_size=args.batch_size, |
| gradient_accumulation_steps=args.gradient_accumulation_steps, |
| eval_strategy="epoch", |
| save_strategy="epoch", |
| logging_steps=50, |
| predict_with_generate=True, |
| generation_max_length=args.max_target_length, |
| load_best_model_at_end=True, |
| metric_for_best_model="rougeL", |
| greater_is_better=True, |
| fp16=args.fp16, |
| report_to="none", |
| push_to_hub=args.push_to_hub, |
| hub_model_id=args.hub_model_id, |
| ) |
|
|
| trainer = Seq2SeqTrainer( |
| model=model, |
| args=training_args, |
| train_dataset=train_data, |
| eval_dataset=eval_data, |
| processing_class=tokenizer, |
| data_collator=DataCollatorForSeq2Seq(tokenizer, model=model), |
| compute_metrics=compute_metrics, |
| ) |
| trainer.train() |
| metrics = trainer.evaluate() |
| trainer.save_model(args.output_dir) |
| tokenizer.save_pretrained(args.output_dir) |
| os.makedirs(args.output_dir, exist_ok=True) |
| with open(os.path.join(args.output_dir, "evaluation_metrics.json"), "w", encoding="utf-8") as file: |
| json.dump(metrics, file, indent=2) |
| print(json.dumps(metrics, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|