返回介绍

数学基础

统计学习

深度学习

工具

Scala

六、问答

发布于 2023-07-17 23:38:23 字数 21006 浏览 0 评论 0 收藏 0

  1. 翻译是另一个 seq-to-seq 任务,它非常类似于文本摘要任务。你可以将我们将在此处学习到的一些内容迁移到其他的 seq-to-seq 问题。

5.1 数据集和数据处理

  1. 加载数据集:我们将使用 KDE4 数据集,该数据集是 KDE 应用程序本地化文件的数据集。该数据集有 92 种语言可用,这里我们选择英语和法语。

    
    
    xxxxxxxxxx
    from datasets import load_dataset, load_metric raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") print(raw_datasets) # DatasetDict({ # train: Dataset({ # features: ['id', 'translation'], # num_rows: 210173 # }) # })

    我们有 210,173 对句子,但是我们需要创建自己的验证集:

    
    
    xxxxxxxxxx
    split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) split_datasets["validation"] = split_datasets.pop("test") # 将 test 重命名为 validation print(split_datasets) # DatasetDict({ # train: Dataset({ # features: ['id', 'translation'], # num_rows: 189155 # }) # validation: Dataset({ # features: ['id', 'translation'], # num_rows: 21018 # }) # })

    我们可以查看数据集的一个元素:

    
    
    xxxxxxxxxx
    print(split_datasets["train"][1]["translation"]) # {'en': 'Default to expanded threads', 'fr': 'Par défaut, développer les fils de discussion'}

    我们使用的预训练模型已经在一个更大的法语和英语句子语料库上进行了预训练。我们看看这个预训练模型的效果:

    
    
    xxxxxxxxxx
    from transformers import pipeline model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" # 大约 300MB translator = pipeline("translation", model=model_checkpoint) print(translator("Default to expanded threads")) # [{'translation_text': 'Par défaut pour les threads élargis'}]
  2. 数据预处理:所有文本都需要转换为 token ID

    
    
    xxxxxxxxxx
    from transformers import AutoTokenizer model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf")

    但是,对于 target ,需要将 tokenizer 包装在上下文管理器 "as_target_tokenizer()"中,因为不同的语言需要不同的 tokenization

    
    
    xxxxxxxxxx
    max_input_length = 128 max_target_length = 128 # 这里设置了 label 和 input 的最大长度相同(也可以不同) def preprocess_function(examples): inputs = [ex["en"] for ex in examples["translation"]] targets = [ex["fr"] for ex in examples["translation"]] model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True) with tokenizer.as_target_tokenizer(): labels = tokenizer(targets, max_length=max_target_length, truncation=True) model_inputs["labels"] = labels["input_ids"] return model_inputs tokenized_datasets = split_datasets.map( preprocess_function, batched=True, remove_columns=split_datasets["train"].column_names, )

5.2 使用 Trainer API 微调模型

  1. 这里我们使用 Seq2SeqTrainer,它是 Trainer 的子类,它可以正确处理这种 seq-to-seq 的评估,并使用 generate() 方法来预测输出。

    • 首先我们加载一个 AutoModelForSeq2SeqLM 模型:

      
      
      xxxxxxxxxx
      from transformers import AutoModelForSeq2SeqLM model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
    • 然后我们使用 DataCollatorForSeq2Seq 来创建一个 data_collator ,它不仅预处理输入,也同时预处理 label

      
      
      xxxxxxxxxx
      from transformers import DataCollatorForSeq2Seq data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)

      现在我们来测试这个 data_collator

      
      
      xxxxxxxxxx
      batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) print(batch) # { # 'input_ids': # tensor([[47591,12,9842,19634,9, 0,59513,59513,59513,59513,59513, 59513, 59513, 59513, 59513], # [ 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 28149, 139, 33712, 25218, 0]]), # 'attention_mask': # tensor([[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], # [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), # 'labels': # tensor([[577, 5891, 2, 3184, 6,2542, 5,1710, 0, -100, -100, -100, -100, -100, -100, -100], # [1211,3,49,9409,1211, 3, 29140,817, 3124,817, 550,7032,5821,7907, 12649, 0]]), # 'decoder_input_ids': # tensor([[59513,577,5891,2,3184,16,2542,5,1710, 0,59513, 59513, 59513, 59513, 59513, 59513], # [59513,1211,3,49,9409,1211,3,29140,817,3124,817,550,7032,5821,7907,12649]])}

      可以看到,label 已经用 -100 填充到 batch 的最大长度。

  2. 评估指标:相比较于父类 TrainerSeq2SeqTrainer 的一个额外功能是,在评估或预测期间调用 generate() 方法从而进行评估。

    用于翻译任务的传统指标是 BLUE 分数,它评估翻译与 label 的接近程度。BLUE 分数不衡量翻译结果的语法正确性或可读性,而是使用统计规则来确保生成输出中的所有单词也出现在 label 中。BLUE 的一个缺点是,它需要确保文本已被 tokenization ,这使得比较使用不同 tokenizer 的模型之间的 BLUE 分数变得困难。因此,目前用于翻译任务的常用指标是 SacreBLUE,它通过标准化 tokenization 步骤解决这个缺点(以及其他的一些缺点)。

    
    
    xxxxxxxxxx
    # pip install sacrebleu # 首先安装 from datasets import load_metric metric = load_metric("sacrebleu")

    该指标接受多个 acceptable labels,因为同一个句子通常有多个可接受的翻译。在我们的这个例子中,每个句子只有一个 label 。因此,预测结果是关于句子的一个列表,而 reference 也是关于句子的一个列表。

    
    
    xxxxxxxxxx
    predictions = [ "This plugin lets you translate web pages between several languages automatically."] references = [ [ "This plugin allows you to automatically translate web pages between several languages." ] ] print(metric.compute(predictions=predictions, references=references)) # { # 'score': 46.750469682990165, # 'counts': [11, 6, 4, 3], # 'totals': [12, 11, 10, 9], # 'precisions': [91.66666666666667, 54.54545454545455, 40.0, 33.333333333333336], # 'bp': 0.9200444146293233, # 'sys_len': 12, # 'ref_len': 13 # }

    这得到了 46.75BLUE 得分,看起来相当不错。如果我们尝试使用翻译模型中经常出现的两种糟糕的预测类型(大量重复、或者太短),我们将得到相当糟糕的BLEU 分数:

    
    
    xxxxxxxxxx
    predictions = ["This This This This"] # 大量重复 predictions = ["This plugin"] # 翻译太短

    为了从模型输出转换为文本从而计算 BLUE 得分,我们需要使用 tokenizer.batch_decode() 方法来解码。注意,我们需要清理 label 中的所有 -100

    
    
    xxxxxxxxxx
    import numpy as np def compute_metrics(eval_preds): preds, labels = eval_preds # In case the model returns more than the prediction logits if isinstance(preds, tuple): preds = preds[0] decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) # 解码 output labels = np.where(labels != -100, labels, tokenizer.pad_token_id) # 替换 -100 为 pad_token_id decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) # 解码 label decoded_preds = [pred.strip() for pred in decoded_preds] # 格式清理 decoded_labels = [[label.strip()] for label in decoded_labels] # 格式清理 result = metric.compute(predictions=decoded_preds, references=decoded_labels) return {"bleu": result["score"]}
  3. 执行微调:

    • 首先我们创建 Seq2SeqTrainingArguments 的实例,其中 Seq2SeqTrainingArgumentsTrainingArguments 的子类。

      
      
      xxxxxxxxxx
      from transformers import Seq2SeqTrainingArguments args = Seq2SeqTrainingArguments( f"marian-finetuned-kde4-en-to-fr", evaluation_strategy="no", # 训练过程中不评估验证集(因为我们会在训练之前和训练结束后分别手动评估验证集) save_strategy="epoch", # 每个 epoch 保存一次 learning_rate=2e-5, per_device_train_batch_size=32, per_device_eval_batch_size=64, weight_decay=0.01, save_total_limit=3, num_train_epochs=3, predict_with_generate=True, # 通过 model.generate() 来执行预测 fp16=True, # FP16 混合精度训练 push_to_hub=False, # 不上传到 HuggingFace Hub )
    • 然后我们创建 Seq2SeqTrainer

      
      
      xxxxxxxxxx
      from transformers import Seq2SeqTrainer trainer = Seq2SeqTrainer( model, args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], data_collator=data_collator, tokenizer=tokenizer, compute_metrics=compute_metrics, )
    • 训练之前,我们首先评估模型在验证集上的 BLUE 分数:

      
      
      xxxxxxxxxx
      print(trainer.evaluate(max_length=max_target_length)) # 大约10分钟(AMD epyc 7543 CPU, 128G内存, 4090显卡) # { # 'eval_loss': 1.6964517831802368, # 'eval_bleu': 25.572039589826357, # 'eval_runtime': 698.3895, # 'eval_samples_per_second': 30.095, # 'eval_steps_per_second': 0.471 # }
    • 然后开始训练(模型大小 300M,训练显存消耗 15.7G):

      
      
      xxxxxxxxxx
      trainer.train() # 每个 epoch 都会保存 # trainer.push_to_hub(tags="translation", commit_message="Training complete") # 可选:推送模型到 HuggingFace Hub
    • 训练完成之后,我们再次评估模型:

      
      
      xxxxxxxxxx
      print(trainer.evaluate(max_length=max_target_length)) # { # 'eval_loss': 0.8559923768043518, # 'eval_bleu': 44.69236449595659, # 'eval_runtime': 669.7527, # 'eval_samples_per_second': 31.382, # 'eval_steps_per_second': 0.491, # 'epoch': 3.0 # }

      可以看到 BLUE 分有 19.12 分的提高。

5.3 自定义训练过程

  1. 自定义训练过程如下:

    
    
    xxxxxxxxxx
    from datasets import load_dataset, load_metric from transformers import AutoTokenizer from transformers import AutoModelForSeq2SeqLM from transformers import DataCollatorForSeq2Seq from torch.utils.data import DataLoader from transformers import AdamW from accelerate import Accelerator from transformers import get_scheduler import numpy as np from datasets import load_metric ##******************** 创建 DataLoader ******************** ##***** 加载数据集 raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) split_datasets["validation"] = split_datasets.pop("test") # 将 test 重命名为 validation ##***** tokenization model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") max_input_length = 128 max_target_length = 128 # 这里设置了 label 和 input 的最大长度相同(也可以不同) def preprocess_function(examples): inputs = [ex["en"] for ex in examples["translation"]] targets = [ex["fr"] for ex in examples["translation"]] model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True) with tokenizer.as_target_tokenizer(): labels = tokenizer(targets, max_length=max_target_length, truncation=True) model_inputs["labels"] = labels["input_ids"] return model_inputs tokenized_datasets = split_datasets.map( preprocess_function, batched=True, remove_columns=split_datasets["train"].column_names, ) tokenized_datasets.set_format("torch") ##******* 创建 data_collator model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) ##****** 创建 dataloader train_dataloader = DataLoader( tokenized_datasets["train"], shuffle=True, collate_fn=data_collator, batch_size=8, ) eval_dataloader = DataLoader( tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 ) ##*************** 创建训练组件 ********************* ##******* 优化器 optimizer = AdamW(model.parameters(), lr=2e-5) ##****** accelerator accelerator = Accelerator() model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( model, optimizer, train_dataloader, eval_dataloader ) ##***** 学习率调度器 num_train_epochs = 3 num_update_steps_per_epoch = len(train_dataloader) num_training_steps = num_train_epochs * num_update_steps_per_epoch lr_scheduler = get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps, ) ##***** 创建 Repository (可选) # from huggingface_hub import Repository, get_full_repo_name # model_name = "marian-finetuned-kde4-en-to-fr-accelerate" # repo_name = get_full_repo_name(model_name) output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" # repo = Repository(output_dir, clone_from=repo_name) ##******* 评估方法 metric = load_metric("sacrebleu") def postprocess(predictions, labels): predictions = predictions.cpu().numpy() labels = labels.cpu().numpy() decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) labels = np.where(labels != -100, labels, tokenizer.pad_token_id) # 替换 label 中的 -100 decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) decoded_preds = [pred.strip() for pred in decoded_preds] decoded_labels = [[label.strip()] for label in decoded_labels] return decoded_preds, decoded_labels ##*************** 训练 ********************* from tqdm.auto import tqdm import torch progress_bar = tqdm(range(num_training_steps)) for epoch in range(num_train_epochs): # Training model.train() for batch in train_dataloader: outputs = model(**batch) loss = outputs.loss accelerator.backward(loss) optimizer.step() lr_scheduler.step() optimizer.zero_grad() progress_bar.update(1) # Evaluation model.eval() for batch in tqdm(eval_dataloader): with torch.no_grad(): # 注意:我们需要调用底层模型的 generate() 方法,因此这里需要调用 accelerator.unwrap_model() generated_tokens = accelerator.unwrap_model(model).generate( batch["input_ids"], attention_mask=batch["attention_mask"], max_length=128, # 最大生成序列的长度为 128 ) labels = batch["labels"] # 需要在 accelerator.gather() 调用之前,首先跨所有进程把 generated_tokens/labels 填充到相同的长度 generated_tokens = accelerator.pad_across_processes( generated_tokens, dim=1, pad_index=tokenizer.pad_token_id ) labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) # 跨进程收集 generated_tokens/labels,收集的结果拼接到 batch 维 predictions_gathered = accelerator.gather(generated_tokens) labels_gathered = accelerator.gather(labels) decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered) metric.add_batch(predictions=decoded_preds, references=decoded_labels) results = metric.compute() print(f"epoch {epoch}, BLEU score: {results['score']:.2f}") # 每个 epoch 结束时保存模型 accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) if accelerator.is_main_process: tokenizer.save_pretrained(output_dir) # repo.push_to_hub( # commit_message=f"Training in progress epoch {epoch}", blocking=False # ) # 训练输出: # epoch 0, BLEU score: 50.54 # epoch 1, BLEU score: 53.21 # epoch 2, BLEU score: 53.83
  2. 调用微调好的模型:

    
    
    xxxxxxxxxx
    from transformers import pipeline translator = pipeline("translation", model="./marian-finetuned-kde4-en-to-fr-accelerate") print(translator("Default to expanded threads")) # [{'translation_text': 'Par défaut, développer les fils de discussion'}]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文