iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0

今天我們講怎麼 find-tuned 摘要任務,今天會很吃 GPU ,不一定每個人都能跑,不過也有比較節省 GPU 的寫法。

  1. 我們來用這個 dataset ,是 BBC 的新聞摘要。
dataset_url = "https://huggingface.co/datasets/gopalkalpande/bbc-news-summary/raw/main/bbc-news-summary.csv"
  1. 載入 dataset 。
from datasets import load_dataset
remote_dataset = load_dataset("csv", data_files=dataset_url)
  1. 我們來用 pandas 看一下 dataset 長什麼樣。
import pandas as pd

remote_dataset.set_format(type="pandas")

df = remote_dataset["train"][:]

df.head(10)

會得到如下圖的結果:
koko hugging face azure machine learning

  1. 記得再把 dataset 還原。
remote_dataset.reset_format()
  1. 再把 dataset 洗牌。
train_dataset = remote_dataset.shuffle(seed=5566)
  1. 我們得知這個 dataset 只有 train 的類別,所以要把它切成 train-test-valid
from datasets import DatasetDict

train_test_dataset = train_dataset['train'].train_test_split(test_size=0.1)

test_valid = train_test_dataset['test'].train_test_split(test_size=0.5)

train_test_valid_dataset = DatasetDict({
    'train': train_test_dataset['train'],
    'test': test_valid['test'],
    'valid': test_valid['train']})

train_test_valid_dataset

會得到:

DatasetDict({
    train: Dataset({
        features: ['File_path', 'Articles', 'Summaries'],
        num_rows: 2001
    })
    test: Dataset({
        features: ['File_path', 'Articles', 'Summaries'],
        num_rows: 112
    })
    valid: Dataset({
        features: ['File_path', 'Articles', 'Summaries'],
        num_rows: 111
    })
})
  1. 接著把模型和 tokenizer 都叫進來。
from transformers import AutoModelForSeq2SeqLM,AutoTokenizer
import torch

model_name = "google/pegasus-cnn_dailymail"
device = "cuda" if torch.cuda.is_available() else "cpu"

model = AutoModelForSeq2SeqLM.from_pretrained(model_name).to(device)
tokenizer = AutoTokenizer.from_pretrained(model_name)

  1. 然後把 dataset 做 tokenize ,並轉成我們要格式與 pt。
def convert_dataset(dataset):
    input_encodings = tokenizer(dataset["Articles"], max_length=1024,
                                truncation=True)

    with tokenizer.as_target_tokenizer():
        target_encodings = tokenizer(dataset["Summaries"], max_length=128,
                                     truncation=True)

    return {"input_ids": input_encodings["input_ids"],
            "attention_mask": input_encodings["attention_mask"],
            "labels": target_encodings["input_ids"]}

dataset_pt = train_test_valid_dataset.map(convert_dataset,
                                       batched=True)
columns = ["input_ids", "labels", "attention_mask"]
dataset_pt.set_format(type="torch", columns=columns)
  1. 接著來設定超參數,但是不要懷疑你沒看錯,batch size 就是設定成 1。因為這個模型的參數量太了,如果 batch size 設定大,一般人的顯卡就會 out of memory,所以我們設定為 1。然後我們利用 gradient_accumulation_steps 這個參數,gradient accumulation 顧名思義就是計算梯度,然後再慢慢的累積起。我們用這個技巧會彌補 batch size 不足的問題。
from transformers import Seq2SeqTrainingArguments, trainer

model_saved_name = model_name.split("/")[-1] 

args = Seq2SeqTrainingArguments( 
    output_dir=f"{model_name}-finetuned", 
    num_train_epochs=1, 
    warmup_steps=100,
    per_device_train_batch_size=1, 
    per_device_eval_batch_size=1,
    weight_decay=0.01, 
    logging_steps=10,
    evaluation_strategy='steps',
    eval_steps=100, 
    save_steps=1e6,
    gradient_accumulation_steps=64,
    report_to="azure_ml"
)
  1. 接著我們用 DataCollatorForSeq2Seq,這個的目的是在 decoding 的過程中,我們需要將標籤向右移動一格,以確保 decoder 只看到之前的真實標籤,而不是當前或未來的標籤。透過 DataCollatorForSeq2Seq,會用 -100 來動態填充輸入和標籤。
from transformers import DataCollatorForSeq2Seq

seq2seq_data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)

  1. 接著叫出 NLTK。
import nltk
from nltk.tokenize import sent_tokenize

nltk.download("punkt")
  1. 再叫出來 ROUGE。
from datasets import load_metric

rouge_metric = load_metric("rouge")
  1. 再來一樣設定 compute_metrics,這一段真的很麻煩,我們參考 hugging face 的範例。
import numpy as np

def compute_metrics(eval_pred):
    predictions, labels = eval_pred

    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    # 這裡把 DataCollatorForSeq2Seq 會填入的 -100 排除掉
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)

    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

    decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds]
    decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels]

    result = rouge_metric.compute(
        predictions=decoded_preds, references=decoded_labels, use_stemmer=True
    )
    # Extract the median scores
    result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
    return {k: round(v, 4) for k, v in result.items()}
  1. 接著我們呼叫 Seq2SeqTrainer 來做 trainer。
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    model,
    args,
    train_dataset= dataset_pt["train"],
    eval_dataset = dataset_pt["valid"],
    data_collator=seq2seq_data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics
)
trainer.train()
  1. 如果你的 GPU 不夠強,會出現 memory 不足的錯誤,就再調調看參數。如果你夠幸運,那麼我們來看結果。
trainer.evaluate()

會得到類似下面結果:

{ 'eval_loss' : 3.028524398803711 ,
  'eval_rouge1' : 16.9728 ,
  'eval_rouge2' : 8.2969 ,
  'eval_rougeL' : 16.8366 ,
  'eval_rougeLsum' : 16.851 ,
  'eval_gen_len' : 10.1597 ,
  'eval_runtime' : 6.1054 ,
  ' eval_samples_per_second' : 38.982 ,
  ' eval_steps_per_second':4.914 }

看起來真的有比昨天的結果還要好了呢!


上一篇
# Day22-評價摘要好壞的演算法
下一篇
# Day24- Hugging Face Named Entity Recognition
系列文
變形金剛與抱臉怪---NLP 應用開發之實戰30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言