NLP Course documentation

การ Fine-tune โมเดลด้วย Trainer API

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

การ Fine-tune โมเดลด้วย Trainer API

Ask a Question Open In Colab Open In Studio Lab

🤗 Transformers มี Trainer class เพื่อช่วยให้คุณสามารถ fine-tune โมเดลที่ผ่านการเทรนมาแล้วด้วย dataset ของคุณเองได้ หลังจากที่คุณได้ทำการประมวลผลข้อมูลใน section ที่แล้ว ก็เหลืองานอีกไม่กี่ขั้นตอนเท่านั้นในการกำหนดตัว Trainer ซึ่งงานส่วนที่ยากที่สุดน่าจะเป็นการเตรียม environment ในการ run Trainer.train() เนื่องจากมันจะ run ได้ช้ามากบน CPU ถ้าคุณไม่มีการติดตั้ง GPU คุณก็สามารถเข้าถึง free GPUs หรือ TPUs ได้บน Google Colab

โค้ดตัวอย่างข้างล่างนี้สันนิษฐานไว้ว่าคุณได้ทำตัวอย่างใน section ที่แล้วมาเรียบร้อยแล้ว นี่คือการสรุปสั้น ๆ เพื่อทบทวนสิ่งที่คุณต้องเตรียมให้พร้อม:

from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

การ Train โมเดล

ขั้นตอนแรกก่อนที่เราจะกำหนด Trainer ของเราก็คือการกำหนด TrainingArguments class ที่จะมีข้อมูลของ hyperparameters ทุกตัวที่ Trainer จะใช้ในการ train และการ evaluate โดยอากิวเมนต์เดียวที่คุณต้องใส่คือ directory ที่จะเซฟข้อมูลโมเดลที่เทรนแล้ว รวมถึง checkpoints ระหว่างการเทรน ที่เหลือนั้นคุณสามารถปล่อยให้เป็นไปตามค่าเริ่มต้นได้ ซึ่งน่าจะทำงานได้ดีสำหรับการ fine-tune แบบพื้นฐาน

from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")

💡 ถ้าคุณต้องการจะอัพโหลดโมเดลของคุณขึ้น Hub ระหว่างที่ทำการเทรนโดยอัตโนมัติ ให้ใส่ push_to_hub=True เข้าไปใน TrainingArguments ด้วย โดยเราจะเรียนรู้เพิ่มเติมใน Chapter 4

ขั้นตอนที่สองคือการกำหนดโมเดลของพวกเรา เหมือนกับใน previous chapter เราจะใช้ AutoModelForSequenceClassification class โดยมี 2 labels:

from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

คุณจะสังเกตได้ว่าคุณจะได้รับคำเตือนหลังจากที่สร้างโมเดลขึ้นมา ไม่เหมือนกับใน Chapter 2 ที่เป็นเช่นนี้เนื่องจาก BERT ยังไม่ได้มีการ pretrained ให้สามารถจำแนกคู่ประโยค ดังนั้น head ของโมเดลที่เทรนไว้แล้วจะถูกตัดทิ้งไป และจะใส่ head ใหม่ที่เหมาะกับการจำแนกลำดับ (sequence classification) เข้ามาแทน คำเตือนนี้เป็นการแจ้งว่า weights บางส่วนจะไม่ถูกนำมาใช้ (weights ของ head ที่ถูกตัดทิ้งไป) และ weights บางส่วน (ของ head ใหม่) จะถูกสร้างขึ้นแบบสุ่ม (randomly initialized) และจบคำเตือนโดยการส่งเสริมให้เราเทรนโมเดล ซึ่งก็เป็นสิ่งที่เรากำลังจะทำตอนนี้

หลังจากที่เราได้โมเดลแล้ว เราสามารถกำหนด Trainer โดยการใส่ออพเจ็กต์ที่เราสร้างขึ้นมาทั้งหมด ได้แก่ model, training_args, ชุดข้อมูล training และ validation, data_collator ของเรา และ tokenizer ของเรา:

from transformers import Trainer

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)

ควรสังเกตว่าเมื่อคุณใส่ tokenizer แบบที่เราทำตอนนี้ ตัว data_collator ที่เป็นค่าเริ่มต้นที่ถูกใช้โดย Trainer จะเป็น DataCollatorWithPadding ที่ได้กำหนดไว้ก่อนหน้านี้ ดังนั้นคุณสามารถข้ามบรรทัด data_collator=data_collator ในการ call นี้ไปเลยก็ได้ ซึ่งคุณได้เรียนรู้กระบวนการนี้มาแล้วใน section 2!

เพื่อจะทำการ fine-tune โมเดลด้วย dataset ของเรา เราก็แค่ต้องเรียกเมธอด train() จาก Trainer ของเรา:

trainer.train()

การรันโค้ดนี้จะเป็นการเริ่มต้น fine-tune โมเดล (ซึ่งจะใช้เวลาไม่กี่นาที ถ้าทำบน GPU) และจะรายงาน training loss ทุก ๆ 500 steps แต่มันจะไม่บอกว่าโมเดลของคุณทำงานได้ดีหรือแย่แค่ไหน เนื่องจาก:

  1. เราไม่ได้บอก Trainer ให้ evaluate ระหว่างการเทรนโดยตั้งค่า evaluation_strategy ให้เป็น "steps" (เพื่อ evaluate ทุก ๆ eval_steps) หรือ "epoch" (เพื่อ evaluate เมื่อจบแต่ละ epoch)
  2. เราไม่ได้ใส่ฟังก์ชั่น compute_metrics() เข้าไปใน Trainer ของเรา โดยฟังก์ชั่นนี้จะคำนวณ metric เมื่อมีการ evaluate เกิดขึ้น (ไม่เช่นนั้นเมื่อมีการ evaluate เกิดขึ้น ก็จะรายงานแค่ค่า loss ซึ่งเป็นตัวเลขที่ทำความเข้าใจได้ยาก)

Evaluation (การประเมินผลโมเดล)

มาดูกันว่าเราจะสามารถสร้างฟังก์ชั่น compute_metrics() และนำไปใช้งานในการเทรนครั้งหน้าได้อย่างไร ฟังก์ชั่นนี้จะต้องรับออพเจกต์ EvalPrediction (ซึ่งเป็น named tuple ที่มี filed เป็น predictions และ label_ids) และจะให้ผลลัพธ์เป็น dictionary ที่มีการ map strings เข้ากับ floats (โดย strings เป็นชื่อของ metrics ผลลัพธ์ และ floats เป็นค่าของ metrics เหล่านั้น) เพื่อจะดูผลการทำนายของโมเดลของเรา เราสามารถใช้คำสั่ง Trainer.predict():

predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
(408, 2) (408,)

ผลลัพธ์จากเมธอด predict() จะเป็น named tuple อีกตัวหนึ่งซึ่งประกอบด้วย 3 fields ได้แก่ predictions, label_ids และ metrics โดย field metrics จะเก็บข้อมูล loss บน dataset ที่ป้อนเข้ามา รวมถึง metrics ที่เกี่ยวข้องกับเวลาบางตัว (ใช้เวลาในการทำนายเท่าไร โดยคิดทั้งเวลาทั้งหมดและเวลาโดยเฉลี่ย) เมื่อเราสร้างฟังก์ชั่น compute_metrics() เสร็จแล้วและใส่เข้าไปใน Trainer ตัว field metric ก็จะมีข้อมูล metrics ต่าง ๆ ที่ได้จากฟังก์ชั่น compute_metrics() ด้วย

ดังที่คุณเห็น predictions เป็น array 2 มิติ ที่มี shape 408 x 2 (408 เป็นจำนวนของ element ใน dataset ที่เราใช้) ซึ่งข้อมูลเหล่านี้คือ logits สำหรับแต่ละ element ของ dataset ที่เราส่งเข้าไปให้เมธอด predict() (เหมือนที่คุณเห็นมาแล้วใน previous chapter ว่าโมเดล Transformers ทุกตัวจะให้ผลลัพธ์เป็น logits) เพื่อจะแปลง logits ให้เป็นการทำนายที่เราสามารถเปรียบเทียบกับ label ของเราได้ เราจะต้องหา index ของค่าที่มีค่าสูงสุดใน axis ที่สอง:

import numpy as np

preds = np.argmax(predictions.predictions, axis=-1)

ตอนนี้เราก็สามารถเปรียบเทียบ preds เหล่านี้กับ labels ของเราได้แล้ว เพื่อจะสร้างฟังก์ชั่น compute_metric() ของเรา เราจะยืม metrics จากไลบรารี่ 🤗 Evaluate มาใช้ เราสามารถโหลด metrics ที่เกี่ยวข้องกับ MRPC dataset ได้อย่างง่ายดายเหมือนกับที่เราโหลดชุดข้อมูล โดยการใช้ฟังก์ชั่น evaluate.load() โดยจะได้ผลลัพธ์เป็นออพเจ็กต์ที่มีเมธอด compute() ที่เราสามารถนำไปใช้ในการคำนวณ metric ได้:

import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}

ผลลัพธ์ที่ได้อาจแตกต่างไปเล็กน้อย เนื่องจากมีการกำหนดค่า weight ของ model head ขึ้นมาแบบสุ่ม และอาจเปลี่ยนผลลัพธ์ใน metrics ได้ ซึ่งตรงนี้เราจะเห็นได้ว่าโมเดลของเราได้ accuracy ที่ 85.78% เมื่อทดสอบด้วย validation set และได้ค่า F1 score ที่ 89.97 ซึ่ง metrics ทั้งสองตัวนี้เป็น metrics ที่ใช้วัดผล MRPC dataset สำหรับ GLUE benchmark โดยตารางในรายงาน BERT paper ได้รายงานค่า F1 score ไว้ที่ 88.9 สำหรับ base model ซึ่งเป็นโมเดล uncased ในขณะที่โมเดลของเราเป็นโมเดล cased จึงเป็นเหตุให้มีผลลัพธ์ที่ดีกว่า

เมื่อรวมทุกอย่างเข้าด้วยกัน เราก็จะได้ฟังก์ชั่น compute_metrics() ของเราดังนี้:

def compute_metrics(eval_preds):
    metric = evaluate.load("glue", "mrpc")
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)

และเพื่อให้มันรายงาน metrics เมื่อจบ epoch แต่ละ epoch เราสามารกำหนด Trainer ตัวใหม่ โดยใช้ฟังก์ชั่น compute_metrics() ได้ดังนี้:

training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)

ควรสังเกตว่าเราได้สร้าง TrainingArguments ชุดใหม่ โดยกำหนนด evaluation_strategy ให้เป็น "epoch" และสร้างโมเดลขึ้นใหม่ด้วย มิฉะนั้นเราก็จะเทรนโมเดลตัวเดิมของเราต่อ เพื่อจะเริ่มการ training รอบใหม่ เราจะใช้คำสั่ง:

trainer.train()

คราวนี้มันจะรายงาน validation loss และ metrics ต่าง ๆ ทุก ๆ ครั้งที่จบแต่ละ epoch นอกจาก training loss ซึ่ง accuracy และ F1 score ที่คุณได้อาจจะต่างจากนี้ไปเล็กน้อยเนื่องจากการสุ่ม แต่มันก็ควรจะได้ค่าที่ใกล้เคียงกัน

Trainer จะสามารถทำงานได้ดีกับการเทรนด้วย GPUs หรือ TPUs หลายตัวโดยไม่ต้องปรับแต่งอะไรมาก และยังมี options มากมายให้เลือกใช้ เช่น mixed-precision training (เลือกได้โดยใส่ fp16 = True เข้าไปใน training arguments ของคุณ) เราจะอธิบายทุกอย่างที่มันทำได้ใน Chapter 10

ก็เป็นอันเสร็จสิ้นวิธีการ fine-tune โดยใช้ Trainer API ซึ่งตัวอย่างการ fine-tune กับ NLP tasks ส่วนใหญ่ที่ใช้บ่อยจะอยู่ใน Chapter 7 แต่ตอนนี้เรามาดูการทำแบบเดียวกันนี้โดยใช้ PyTorch เพียงอย่างเดียวกัน

✏️ ลองเลย! Fine-tune โมเดลโดยใช้ GLUE SST-2 dataset โดยใช้การประมวลผลข้อมูลแบบที่คุณทำไว้ใน section 2