Pytorch
TensorFlow

#
การ Fine-tune โมเดลด้วย Keras

หลังจากที่คุณได้ทำการประมวลผลข้อมูลใน section ที่แล้ว ก็เหลืองานอีกไม่กี่ขั้นตอนเท่านั้นในการเทรนโมเดล ควรระวังไว้ว่าคำสั่ง `model.fit()` จะรันบน CPU ได้ช้ามาก ๆ ถ้าคุณไม่มีการติดตั้ง GPU คุณสามารถเข้าถึง free GPUs หรือ TPUs ได้บน Google Colab

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

    from datasets import load_dataset
    from transformers import AutoTokenizer, DataCollatorWithPadding
    import numpy as np

    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, return_tensors="tf")

    tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
        columns=["attention_mask", "input_ids", "token_type_ids"],
        label_cols=["labels"],
        shuffle=True,
        collate_fn=data_collator,
        batch_size=8,
    )

    tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
        columns=["attention_mask", "input_ids", "token_type_ids"],
        label_cols=["labels"],
        shuffle=False,
        collate_fn=data_collator,
        batch_size=8,
    )

###
การ Train โมเดล

โมเดล TensorFlow ที่ import มาจากไลบรารี่ 🤗 Transformers นั้นเป็นโมเดล Keras อยู่แล้ว นี่คือการแนะนำสั้น ๆ ให้รู้จักกับ Keras

นั่นหมายความว่า เมื่อเรามีข้อมูลแล้ว ก็เหลืองานแค่เล็กน้อยที่ต้องทำก่อนจะเริ่มเทรนโมเดลด้วยข้อมูลของเรา

เหมือนกับใน previous chapter เราจะใช้ `AutoModelForSequenceClassification` class โดยมี 2 labels:

    from transformers import TFAutoModelForSequenceClassification

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

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

เพื่อจะ fine-tune โมเดลด้วย dataset ของเรา เราแค่ต้องทำการ `compile()` โมเดลของเราแล้วส่งข้อมูลเข้าโดยใช้เมธอด `fit()` ซึ่งจะเป็นการเริ่ม fine-tuning (ซึ่งจะใช้เวลาไม่กี่นาทีบน GPU) และรายงาน training loss ระหว่างการเทรน รวมถึง validation loss เมื่อจบแต่ละ epoch To fine-tune the model on our dataset, we just have to `compile()` our model and then pass our data to the `fit()` method. This will start the fine-tuning process (which should take a couple of minutes on a GPU) and report training loss as it goes, plus the validation loss at the end of each epoch.

ควรสังเกตว่าโมเดล 🤗 Transformers มีความสามารถพิเศษที่โมเดล Keras ส่วนใหญ่ไม่มี นั่นก็คือ พวกมันสามารถเลือก loss ที่เหมาะสมได้เอง โดยมันจะใช้ค่า loss นี้เป็นค่าเริ่มต้นหากคุณไม่ได้ใส่อากิวเมนต์ loss ในเมธอด `compile()` นอกจากนี้ควรระวังว่า การจะใช้ internal loss คุณจะต้องส่ง labels ของคุณเข้าไปเป็นส่วนหนึ่งของ input ด้วย ห้ามส่งแยกกัน ซึ่งเป็นวิธีการปกติที่ใช้จัดการกับ labels กับโมเดล Keras คุณจะเป็นตัวอย่างนี้ใน Part 2 ของคอร์ส ซึ่งการจะกำหนด loss function ให้ถูกต้องนั้นจะยุ่งยากเล็กน้อย อย่างไรก็ตาม สำหรับงาน sequence classification สามารถใช้ standard Keras loss function ได้โดยไม่มีปัญหา ซึ่งเราก็จะใช้แบบนั้นในตัวอย่างนี้

    from tensorflow.keras.losses import SparseCategoricalCrossentropy

    model.compile(
        optimizer="adam",
        loss=SparseCategoricalCrossentropy(from_logits=True),
        metrics=["accuracy"],
    )
    model.fit(
        tf_train_dataset,
        validation_data=tf_validation_dataset,
    )

ให้ระวังข้อผิดพลาดที่เกิดขึ้นบ่อยตรงนี้ - คุณ สามารถ แค่ใส่ชื่อของ loss เป็น string เข้าไปใน Keras แต่โดยค่าเริ่มต้นแล้ว Keras จะสันนิษฐานว่าคุณได้ใช้ softmax กับ outputs ของคุณไปแล้ว อย่างไรก็ตามมีโมเดลจำนวนมากที่ให้ผลลัพธ์เป็นค่าก่อนที่จะใช้ softmax ซึ่งเรียกว่า logits เราจะต้องบอก loss function ว่าโมเดลของเราทำอะไร และวิธีการเดียวที่จะทำได้คือการ call โดยตรง ไม่ใช่การส่งชื่อที่เป็น string เข้าไป

### การปรับปรุงประสิทธิภาพในการเทรน

ถ้าคุณลองโค้ดข้างต้น มันก็จะรันได้ แต่คุณจะพบว่า loss จะลดลงได้ช้ามาก หรือลดลงเพียงบางช่วง ซึ่งสาเหตุหลักมาจาก learning rate ซึ่งก็เหมือนกับ loss ถ้าเราส่งชื่อของ optimizer เป็น string เข้าไป Keras จะ initialize ตัว optimizer นั้นด้วยค่าเริ่มต้นสำหรับทุก ๆ parameters รวมถึง learning rate ด้วย แต่จากประสบการณ์ที่ยาวนาน เรารู้ว่าโมเดล transformer จะได้ประโยชน์จาก learning rate ที่มีค่าต่ำมากกว่าค่าเริ่มต้นของ Adam (ซึ่งมีค่าเริ่มต้นคือ 1e-3 หรือ 10 ยกกำลัง -3 หรือ 0.001.) ค่า learning rate ที่ 5e-5 (0.00005) ซึ่งน้อยกว่าค่าเริ่มต้นของ Adam ถึง 20 เท่า เป็นค่าที่เหมาะกับการเริ่มต้นมากกว่า

นอกเหนือไปจากการลด learning rate เรายังมี trick อีกอย่างหนึ่งคือ เราสามารถลด learning rate ลงอย่างช้า ๆ ได้ ซึ่งในงานวิจัยคุณมักจะเห็นการทำเช่นนี้ถูกเรียกว่า decaying หรือ annealing the learning rate ซึ่งใน Keras วิธีการที่ดีที่สุดก็คือการใช้ learning rate scheduler โดยตัวที่น่าใช้คือ `PolynomialDecay` โดยมีการตั้งค่าเริ่มต้นให้ลด learning rate ลงแบบ linearly จากค่าเริ่มต้นไปจนถึงค่าสุดท้ายเมื่อจบการเทรน ซึ่งเป็นสิ่งที่เราต้องการ เพื่อที่จะใช้งาน scheduler ได้อย่างถูกต้อง เราจะต้องระบุว่าจะเทรนนานเท่าไร เราจะคำนวณเวลาในการเทรนเป็น `num_train_steps` ดังโค้ดข้างล่างนี้

    from tensorflow.keras.optimizers.schedules import PolynomialDecay

    batch_size = 8
    num_epochs = 3
    # The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied
    # by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset,
    # not the original Hugging Face Dataset, so its len() is already num_samples // batch_size.
    num_train_steps = len(tf_train_dataset) * num_epochs
    lr_scheduler = PolynomialDecay(
        initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps
    )
    from tensorflow.keras.optimizers import Adam

    opt = Adam(learning_rate=lr_scheduler)

ไลบรารี่ 🤗 Transformers ก็มีฟังก์ชั่น `create_optimizer()` ซึ่งจะสร้าง `AdamW` optimizer โดยใช้ learning rate decay ซึ่งเป็นทางลัดที่สะดวก และคุณจะได้เห็นรายละเอียดใน section ต่อ ๆ ไปในคอร์ส

ตอนนี้เราก็มี optimizer ตัวใหม่เอี่ยม และสามารถนำไปลองเทรนได้เลย ขั้นแรก เรามาโหลดโมเดลขึ้นมากใหม่ เพื่อ reset weights จากการเทรนก่อนหน้านี้ จากนั้นเราก็จะ compile โมเดลโดยใช้ optimizer ตัวใหม่

    import tensorflow as tf

    model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
    loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
    model.compile(optimizer=opt, loss=loss, metrics=["accuracy"])

ตอนนี้เราก็ fit อีกรอบได้เลย:

    model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3)

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

### การทำนายผลของโมเดล

การเทรน และการมองดู loss ค่อย ๆ ลดลงนั้นเยี่ยมไปเลย แต่ถ้าสิ่งที่เราต้องการจริง ๆ คือการเอาผลลัพธ์จากโมเดลไปประเมินผล หรือนำไปใช้งานจริงใน production ล่ะ? เพื่อจะทำอย่างนั้น เราก็แค่ใช้เมธอด `predict()` ก็จะได้ผลลัพธ์เป็น logits จาก output head ของโมเดล (หนึ่งตัวต่อหนึ่งคลาส)

    preds = model.predict(tf_validation_dataset)["logits"]

เราสามารถแปลง logits เหล่านี้เป็นการทำนาย class ได้โดยการใช้ `argmax` เพื่อหา logit ที่มีค่าสูงสุด ซึ่งจะตรงกับ class ที่มีความน่าจะเป็นมากที่สุด:

    class_preds = np.argmax(preds, axis=1)
    print(preds.shape, class_preds.shape)

    (408, 2) (408,)

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

    import evaluate

    metric = evaluate.load("glue", "mrpc")
    metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"])

    {'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` จึงเป็นเหตุให้มีผลลัพธ์ที่ดีกว่า

ก็เป็นอันเสร็จสิ้นวิธีการ fine-tune โดยใช้ `Trainer` API ซึ่งตัวอย่างการ fine-tune กับ NLP tasks ส่วนใหญ่ที่ใช้บ่อยจะอยู่ใน Chapter 7 ถ้าคุณอยากฝึกทักษะการใช้ Keras API เพิ่มเติม ให้ลอง fine-tune โมเดลโดยใช้ GLUE SST-2 dataset โดยใช้การประมวลผลข้อมูลแบบที่คุณทำไว้ใน section 2