# Course

## Docs

- [จบพาร์ทที่ 1!](https://huggingface.co/learn/course/th/chapter4/5.md)
- [The Hugging Face Hub](https://huggingface.co/learn/course/th/chapter4/1.md)
- [การแบ่งปันโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models)](https://huggingface.co/learn/course/th/chapter4/3.md)
- [คำถามท้ายบท](https://huggingface.co/learn/course/th/chapter4/6.md)
- [การสร้างการ์ดโมเดล (model card)](https://huggingface.co/learn/course/th/chapter4/4.md)
- [การใช้งานโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models)](https://huggingface.co/learn/course/th/chapter4/2.md)
- [Fine-tune โมเดลสำเร็จแล้ว!](https://huggingface.co/learn/course/th/chapter3/5.md)
- [บทนำ](https://huggingface.co/learn/course/th/chapter3/1.md)
- [การ Fine-tune โมเดลด้วย Trainer API](https://huggingface.co/learn/course/th/chapter3/3.md)
- [คำถามท้ายบท](https://huggingface.co/learn/course/th/chapter3/6.md)
- [การ Fine-tune โมเดลด้วย Keras](https://huggingface.co/learn/course/th/chapter3/3_tf.md)
- [การเทรนโมเดลฉบับสมบูรณ์](https://huggingface.co/learn/course/th/chapter3/4.md)
- [การประมวลผลข้อมูล](https://huggingface.co/learn/course/th/chapter3/2.md)
- [เรียนจบเรื่อง tokenizer แล้ว!](https://huggingface.co/learn/course/th/chapter6/9.md)
- [Byte-Pair Encoding tokenization](https://huggingface.co/learn/course/th/chapter6/5.md)
- [บทนำ](https://huggingface.co/learn/course/th/chapter6/1.md)
- [ความสามารถพิเศษของตัวตัดคำแบบเร็ว (fast tokenizers)](https://huggingface.co/learn/course/th/chapter6/3.md)
- [WordPiece tokenization](https://huggingface.co/learn/course/th/chapter6/6.md)
- [คำถามท้ายบท](https://huggingface.co/learn/course/th/chapter6/10.md)
- [การสร้าง tokenizer ทีละขั้นตอน](https://huggingface.co/learn/course/th/chapter6/8.md)
- [Normalization และ pre-tokenization](https://huggingface.co/learn/course/th/chapter6/4.md)
- [Unigram tokenization](https://huggingface.co/learn/course/th/chapter6/7.md)
- [การใช้งานตัวตัดคำแบบเร็ว (Fast tokenizers) ใน QA pipeline](https://huggingface.co/learn/course/th/chapter6/3b.md)
- [การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว](https://huggingface.co/learn/course/th/chapter6/2.md)
- [การจัดการกับหลายๆประโยค(multiple sequences)](https://huggingface.co/learn/course/th/chapter2/5.md)
- [บทนำ](https://huggingface.co/learn/course/th/chapter2/1.md)
- [โมเดล](https://huggingface.co/learn/course/th/chapter2/3.md)
- [ประกอบทุกอย่างเข้าด้วยกัน](https://huggingface.co/learn/course/th/chapter2/6.md)
- [แบบทดสอบท้ายบท](https://huggingface.co/learn/course/th/chapter2/8.md)
- [Tokenizers](https://huggingface.co/learn/course/th/chapter2/4.md)
- [การใช้งานเบื้องต้นสำเร็จแล้ว!](https://huggingface.co/learn/course/th/chapter2/7.md)
- [เบื้องหลังของ pipeline](https://huggingface.co/learn/course/th/chapter2/2.md)
- [บทนำ](https://huggingface.co/learn/course/th/chapter0/1.md)
- [สรุป](https://huggingface.co/learn/course/th/chapter1/9.md)
- [โมเดล Encoder](https://huggingface.co/learn/course/th/chapter1/5.md)
- [บทนำ](https://huggingface.co/learn/course/th/chapter1/1.md)
- [Transformers ชื่อนี้มีดียังไง?](https://huggingface.co/learn/course/th/chapter1/3.md)
- [โมเดล Decoder](https://huggingface.co/learn/course/th/chapter1/6.md)
- [คำถามท้ายบท](https://huggingface.co/learn/course/th/chapter1/10.md)
- [ข้อจำกัดจากอคติของข้อมูล](https://huggingface.co/learn/course/th/chapter1/8.md)
- [Transformer ทำงานยังไง?](https://huggingface.co/learn/course/th/chapter1/4.md)
- [โมเดล sequence-to-sequence](https://huggingface.co/learn/course/th/chapter1/7.md)
- [การประมวลผลภาษาธรรมชาติ(หรือเรียกว่า NLP)](https://huggingface.co/learn/course/th/chapter1/2.md)

### จบพาร์ทที่ 1!
https://huggingface.co/learn/course/th/chapter4/5.md

# จบพาร์ทที่ 1!

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

นี่คือจุดจบของพาร์ทแรกในคอร์สนี้! พาร์ทที่ 2 จะถูกปล่อยในวันที่ 15 พฤศจิกายน พร้อมกับงานอีเว้นท์ชุมชนใหญ่ ดูรายละเอียดเพิ่มเติม [ที่นี่](https://huggingface.co/blog/course-launch-event)

ตอนนี้คุณควรจะสามารถทำ fine-tune โมเดลที่ผ่านการเทรนมาแล้ว (pretrained model) กับปัญหาการจำแนกประเภทตัวหนังสือ (text classification) (แบบประโยคเดี่ยวหรือคู่ประโยค) และอัพโหลดผลลัพธ์ขึ้นสู่ Model Hub ได้ เพื่อที่จะทำให้มั่นใจว่าคุณเชี่ยวชาญส่วนแรกนี้แล้วจริงๆ คุณควรจะฝึกทำแบบนี้เป๊ะๆกับปัญหาที่คุณสนใจ (และไม่จำเป็นจะต้องเป็นภาษาอังกฤษถ้าคุณพูดภาษาอื่น)! คุณสามารถหาความช่วยเหลือได้ใน [Hugging Face forums](https://discuss.huggingface.co/) และแบ่งปันโปรเจคของคุณได้ใน [หัวข้อนี้](https://discuss.huggingface.co/t/share-your-projects/6803) เมื่อคุณทำเสร็จ

เรารอไม่ไหวแล้วที่จะได้เห็นว่าคุณจะสร้างอะไรได้บ้างจากความรู้นี้!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter4/5.mdx" />

### The Hugging Face Hub
https://huggingface.co/learn/course/th/chapter4/1.md

# The Hugging Face Hub

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

[Hugging Face Hub](https://huggingface.co/) –- เว็บไซต์หลักของเรา –- เป็นแพลตฟอร์มกลางที่ทุกคนสามารถค้นหาโมเดลและชุดข้อมูลที่ล้ำสมัยที่สุด (state-of-the-art) และสามารถนำไปใช้งาน รวมถึงมีส่วนร่วมได้ เรามีโมเดลที่เปิดให้ใช้ได้อย่างเป็นสาธารณะที่หลากหลายมากกว่า 10,000 โมเดลให้เลือกใช้ ซึ่งในบทนี้เราจะมาเจาะลึกลงในเรื่องของโมเดล และเราจะพูดถึงชุดข้อมูลในบทที่ 5

โมเดลใน hub ของเราไม่ได้มีแค่ 🤗 Transformers หรือ NLP เท่านั้น ยังมีโมเดลจาก [Flair](https://github.com/flairNLP/flair) และ [AllenNLP](https://github.com/allenai/allennlp) สำหรับงาน NLP, [Asteroid](https://github.com/asteroid-team/asteroid) และ [pyannote](https://github.com/pyannote/pyannote-audio) สำหรับงานเสียง และ [timm](https://github.com/rwightman/pytorch-image-models) สำหรับงานภาพ และอื่นๆอีกมากมาย 

โมเดลเหล่านี้ถูกเก็บไว้ในรูปแบบของ Git repository ซึ่งนั่นสามารถทำให้เกิดการกำหนดเวอร์ชั่น (versioning) และการทำซ้ำได้ (reproducibility) การแบ่งปันโมเดลบน hub นั้นหมายถึงการปล่อยโมเดลสู่ชุมชน และทำให้ผู้คนสามารถเข้าถึงโมเดลได้อย่างง่ายดาย รวมถึงช่วยกำจัดความจำเป็นในการเทรนโมเดลด้วยตัวเอง และทำให้สามารถแบ่งปันและใช้งานได้ง่ายอีกด้วย

มากไปกว่านั้นการแบ่งปันโมเดลบน hub ยังเป็นการปล่อย (deploy) API สำหรับใช้โมเดลนั้นในการทำนายผลด้วย ซึ่งทุกคนสามารถนำไปทดสอบใช้งานกับข้อมูลอินพุตที่กำหนดได้เอง (custom inputs) และใช้คู่กับเครื่องมือที่เหมาะสมได้โดยตรงจากหน้าเว็บไซต์ของโมเดลนั้นโดยไม่มีค่าใช้จ่าย

ส่วนที่ดีที่สุดคือ การแบ่งปันและใช้โมเดลสาธารณะบน hub นั้นไม่มีค่าใช้จ่ายโดยสิ้นเชิง! ซึ่งเรามี [แผนแบบจ่าย](https://huggingface.co/pricing) ถ้าหากคุณต้องการจะแบ่งปันโมเดลอย่างเป็นส่วนตัว

วีดีโอข้างล่างนี้แสดงวิธีการนำทางไปหน้าต่างๆใน hub

<Youtube id="XvSGPZFEjDY"/>

ในส่วนนี้คุณจำเป็นที่จะต้องมีบัญชี huggingface.co เพื่อที่จะทำตาม เพราะเราจะมีการสร้างและจัดการ repositories บน Hugging Face Hub: [สร้างบัญชี](https://huggingface.co/join)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter4/1.mdx" />

### การแบ่งปันโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models)
https://huggingface.co/learn/course/th/chapter4/3.md

# การแบ่งปันโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models)

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter4/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter4/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter4/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter4/section3_tf.ipynb"},
]} />

{/if}

ในขั้นตอนข้างล่างต่อไปนี้ เราจะมาดูวิธีที่ง่ายที่สุดในการแบ่งปันโมเดลที่ผ่านการเทรนมาแล้วบน 🤗 Hub มีเครื่องมือและสิ่งอำนวยความสะดวกมากมายที่ทำให้การแบ่งปันและอัพเดตโมเดลโดยตรงบน Hub เป็นเรื่องง่าย ซึ่งนั่นคือสิ่งที่เราจะมาสำรวจกันต่อจากนี้

<Youtube id="9yY3RB_GSPM"/>

เราส่งเสริมให้ผู้ใช้ทุกคนที่เทรนโมเดลช่วยมีส่วนร่วมโดยการแบ่งปันโมเดลเหล่านั้นให้กับชุมชน การแบ่งปันโมเดลนั้นสามารถช่วยเหลือผู้อื่นได้ ช่วยลดเวลาและทรัพยากรในการประมวลผล และช่วยให้เข้าถึงไฟล์ผลลัพธ์จากการเทรน (artifacts) ที่เป็นประโยชน์ได้ แม้ว่าจะเป็นโมเดลที่ถูกเทรนมากับชุดข้อมูลที่เฉพาะเจาะจงมากก็ตาม ในทางเดียวกันนั้นคุณก็ได้รับประโยชน์จากงานที่คนอื่นได้ทำเช่นกัน!

มี 3 วิธีที่สามารถใช้สร้าง model repositories ใหม่ได้:

- ใช้ `push_to_hub` API
- ใช้ `huggingface_hub` Python library
- ใช้ web interface

เมื่อคุณได้สร้าง repository แล้ว คุณสามารถอัพโหลดไฟล์ต่างๆเข้าไปได้ผ่าน git และ git-lfs ในส่วนถัดไปเราจะพาคุณมาดูขั้นตอนการสร้าง model repositories และการอัพโหลดไฟล์ต่างๆเข้าไป


## ใช้ `push_to_hub` API

{#if fw === 'pt'}

<Youtube id="Zh0FfmVrKX0"/>

{:else}

<Youtube id="pUh5cGmNV8Y"/>

{/if}

วิธีที่ง่ายที่สุดในการอัพโหลดไฟล์ขึ้นไปสู่ Hub คือการใช้ `push_to_hub` API

ก่อนจะไปกันต่อ คุณจะต้องสร้างโทเค็นสำหรับรับรองความน่าเชื่อถือ (authentication token) เพื่อให้ `huggingface_hub` API รู้ว่าคุณคือใครและคุณมีสิทธิ์ในการเขียน (write access) ใน namespaces อะไรบ้าง ในตอนนี้ให้คุณทำให้มั่นใจว่าคุณอยู่ใน environment ที่คุณได้ทำการติดตั้ง `transformers` เอาไว้แล้ว (ดู [ติดตั้งโปรแกรม](/course/chapter0)) ถ้าคุณกำลังอยู่ใน notebook คุณสามารถใช้งานคำสั่งต่อไปนี้ในการเข้าสู่ระบบได้เลย:

```python
from huggingface_hub import notebook_login

notebook_login()
```

สำหรับใน terminal คนสามารถรัน:

```bash
huggingface-cli login
```

ในทั้งสองกรณี คุณควรจะถูกให้กรอก username และ password ซึ่งเป็นชุดเดียวกันกับที่คุณใช้ในการ login เข้าสู่ Hub ถ้าหากคุณยังไม่มีโปรไฟล์ Hub คุณสามารถสร้างได้ [ที่นี่](https://huggingface.co/join)

เยี่ยม! ตอนนี้คุณมี authentication token เก็บเอาไว้ใน cache folder ของคุณแล้ว มาเริ่มสร้าง repositories กันเถอะ!

{#if fw === 'pt'}

ถ้าคุณเคยได้ลองใช้งาน `Trainer` API ในการเทรนโมเดลมาบ้างแล้ว วิธีที่ง่ายที่สุดที่จะอัพโหลดขึ้นไปสู่ Hub คือการตั้ง `push_to_hub=True` ตอนที่คุณกำหนด `TrainingArguments`:

```py
from transformers import TrainingArguments

training_args = TrainingArguments(
    "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True
)
```

เมื่อคุณเรียกใช้ `trainer.train()` ตัว `Trainer` จะทำการอัพโหลดโมเดลของคุณขึ้นสู่ Hub ในทุกๆครั้งที่มันถูกบันทึก (ในที่นี้คือ ทุกๆรอบการเทรน (epoch)) ใน repository และ namespace ของคุณ repository นั้นจะถูกตั้งชื่อให้เหมือนกับโฟลเดอร์ output ที่คุณเลือก (ในที่นี้คือ `bert-finetuned-mrpc`) แต่คุณสามารถเลือกชื่ออื่นได้โดยการใช้ `hub_model_id = "ชื่ออื่น"` 

สำหรับการอัพโหลดโมเดลของคุณเข้าสู่องค์กรที่คุณเป็นสมาชิกนั้น คุณสามารถทำได้โดย เพียงแค่ผ่านตัวแปล `hub_model_id = "องค์กร_ของคุณ/ชื่อ_repo_ของคุณ"` เข้าไป

เมื่อการเทรนของคุณเสร็จสิ้น คุณควรที่จะใช้ `trainer.push_to_hub()` อีกครั้งเป็นครั้งสุดท้าย เพื่ออัพโหลดโมเดลเวอร์ชั่นล่าสุดของคุณ มันจะสร้างการ์ดโมเดล (model card) ที่มีข้อมูลเมตา (metadata) ที่เกี่ยวข้อง เช่น รายงาน hyperparameters ที่ใช้ และผลลัพธ์ของการประเมินผล (evaluation result) ให้คุณด้วย! ต่อไปนี้เป็นตัวอย่างของเนื้อหาที่คุณเจอได้ในการ์ดโมเดล:  

<div class="flex justify-center">
  <img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/model_card.png" alt="An example of an auto-generated model card." width="100%"/>
</div>

{:else}

ถ้าคุณใช้ Keras ในเทรนโมเดล วิธีที่ง่ายที่สุดที่จะอัพโหลดมันขึ้นสู่ Hub คือการผ่านค่าตัวแปร `PushToHubCallback` เข้าไป เมื่อคุณเรียกใช้ `model.fit()`:

```py
from transformers import PushToHubCallback

callback = PushToHubCallback(
    "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer
)
```

ต่อจากนั้นคุณควรเพิ่ม `callbacks=[callback]` ไปในตอนที่คุณเรียก `model.fit()` ตัว callback นี้จะทำการอัพโหลดโมเดลของคุณขึ้นสู่ Hub ในทุกๆครั้งที่มันถูกบันทึก (ในที่นี้คือ ทุกๆรอบการเทรน (epoch)) ใน repository และ namespace ของคุณ repository นั้นจะถูกตั้งชื่อให้เหมือนกับโฟลเดอร์ output ที่คุณเลือก (ในที่นี้คือ `bert-finetuned-mrpc`) แต่คุณสามารถเลือกชื่ออื่นได้โดยการใช้ `hub_model_id = "ชื่ออื่น"`

สำหรับการอัพโหลดโมเดลของคุณเข้าสู่องค์กรที่คุณเป็นสมาชิกนั้น คุณสามารถทำได้โดย เพียงแค่ผ่านตัวแปล `hub_model_id = "องค์กร_ของคุณ/ชื่อ_repo_ของคุณ"` เข้าไป

{/if}

ในระดับล่างลงไปนั้น การเข้าถึง Model Hub สามารถทำได้โดยตรงจาก objects ประเภท models, tokenizers และconfiguration ผ่านคำสั่ง `push_to_hub()` คำสั่งนี้จะจัดการทั้งการสร้าง repository และการดันไฟล์โมเดลและ tokenizer ขึ้นสู่ repository โดยตรง ไม่มีความจำเป็นที่จะต้องทำอะไรด้วยตนเองเลย ไม่เหมือนกับ API ที่เราจะได้เห็นข้างล่างนี้ 

เพื่อให้เห็นภาพมากขึ้นว่ามันทำงานอย่างไร เรามาเริ่มสร้างโมเดลและ tokenizer ตัวแรกกันเถอะ:

{#if fw === 'pt'}
```py
from transformers import AutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = AutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```
{:else}
```py
from transformers import TFAutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = TFAutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```
{/if}

ถึงจุดนี้คุณสามารถทำอะไรก็ได้กับมันตามที่คุณต้องการ ไม่ว่าจะเป็นการเพิ่ม tokens ไปใน tokenizer, การเทรนโมเดล, การ fine-tune โมเดล หลังจากที่คุณพอใจกับผลลัพธ์ของโมเดล, weights และ tokenizer แล้ว คุณสามารถเรียกใช้คำสั่ง `push_to_hub()` ที่มีอยู่ในตัว `model` object ได้โดยตรง

```py
model.push_to_hub("dummy-model")
```

ขั้นตอนนี้จะสร้าง repository ใหม่ชื่อ `dummy-model` ในโปรไฟล์ของคุณ และเติมมันด้วยไฟล์โมเดลต่างๆของคุณ
ทำแบบเดียวกันนี้กับ tokenizer นั่นจะทำให้ไฟล์ทั้งหมดอยู่บน repository นี้:

```py
tokenizer.push_to_hub("dummy-model")
```

ถ้าคุณสังกัดองค์กร คุณสามารถระบุชื่อขององค์กรใน `organization` argument เพื่ออัพโหลดเข้าไปใน namespace ขององค์กร:

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface")
```

ถ้าคุณต้องการที่จะใช้งานโทเค็นของ Hugging Face โดยเฉพาะเจาะจง คุณสามารถระบุมันไปในคำสั่ง `push_to_hub()` ได้เช่นกัน:

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="<TOKEN>")
```

ในตอนนี้ให้คุณมุ่งหน้าไปที่ Model Hub เพื่อหาโมเดลที่คุณเพิ่งได้อัพโหลดไป: *https://huggingface.co/ชื่อ-user-หรือ-organization/dummy-model*

คลิกไปที่แท็บ "Files and versions" และคุณควรจะเห็นไฟล์แบบในภาพข้างล่างนี้:

{#if fw === 'pt'}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{:else}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model_tf.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{/if}

<Tip>

✏️ **ทดลองใช้ได้เลย!** นำโมเดลและ tokenizer ที่เกี่ยวข้องกับ `bert-base-cased` checkpoint และอัพโหลดขึ้นไปบน repo ใน namespace ของคุณด้วยคำสั่ง `push_to_hub()` จากนั้นลองตรวจสอบดูว่า repo ปรากฏออกมาในรูปแบบที่สมควรจะเป็นบนหน้าของคุณ ก่อนที่จะลบมันออกไป

</Tip>

อย่างที่คุณได้เห็น คำสั่ง `push_to_hub()` สามารถรับ arguments ได้มากมาย ทำให้สามารถอัพไฟล์ไปยัง repository หรือ namespace ขององค์กรแบบเจาะจง หรือใช้งานโทเค็น API ที่ต่างกันได้ เราแนะนำให้คุณลองเปิดดูรายละเอียดของคำสั่งนี้ได้โดยตรงที่ [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html) เพื่อจะได้เข้าใจมากขึ้นว่าคำสั่งนี้สามารถทำอะไรได้บ้าง

คำสั่ง `push_to_hub()` ได้รับการหนุนหลังโดย [`huggingface_hub`](https://github.com/huggingface/huggingface_hub) Python package ซึ่งให้ API โดยตรงไปสู่ Hugging Face Hub มันถูกบูรณาการเข้ากับ 🤗 Transformers และ machine learning libraries อื่นๆอีกหลายอันด้วย เช่น [`allenlp`](https://github.com/allenai/allennlp) แม้ว่าเราเจาะจงแค่การบูรณาการกับ 🤗 Transformers ในบทนี้ แต่การบูรณาการกับการใช้โค้ดของคุณเองหรือ library อื่นนั้นเรียบง่ายมาก

ข้ามไปส่วนสุดท้ายเพื่อดูวิธีการอัพโหลดไฟล์ขึ้นไปบน repository ที่คุณสร้างขึ้นมาใหม่!

## ใช้ `huggingface_hub` Python library

`huggingface_hub` Python library คือ package หนึ่งที่บรรจุชุดเครื่องมือสำหรับ hub ของโมเดลและชุดข้อมูล มันมีคำสั่งและคลาสที่เรียบง่ายไว้สำหรับงานทั่วไปอย่าง
การดึงข้อมูลเกี่ยวกับ repositories บน hub และการจัดการมัน มันมี API ง่ายๆที่สามารถทำงานบน git เพื่อที่จะจัดการเนื้อหาของ repositories เหล่านั้นและบูรณาการเข้ากับ Hub
ในงานโปรเจคและ libraries ของคุณ

คล้ายคลึงกับการใช้ `push_to_hub` API คุณจำเป็นจะต้องมีโทเค็น API เก็บไว้ใน cache ของคุณ เพื่อที่จะมีมันได้ คุณจะต้องใช้คำสั่ง `login` จาก CLI อย่างที่เราเคยกล่าวถึงไปแล้วในส่วนก่อนหน้านี้ (ย้ำอีกครั้ง ทำให้มั่นใจว่าคุณใส่เครื่องหมาย `!` ไว้ข้างหน้าคำสั่งถ้าคุณกำลังรันใน Google Colab):

```bash
huggingface-cli login
```

`huggingface_hub` package บรรจุคำสั่งและคลาสซึ่งเป็นประโยชน์สำหรับจุดประสงค์ของเราเอาไว้หลายอย่าง อย่างแรก มีคำสั่งจำนวนหนึ่งที่ใช้จัดการกับการสร้าง, การลบ และการจัดการอื่นๆของ repository 

```python no-format
from huggingface_hub import (
    # User management
    login,
    logout,
    whoami,

    # Repository creation and management
    create_repo,
    delete_repo,
    update_repo_visibility,

    # And some methods to retrieve/change information about the content
    list_models,
    list_datasets,
    list_metrics,
    list_repo_files,
    upload_file,
    delete_file,
)
```


มากไปกว่านั้น มันยังมี `Repository` class ที่ทรงพลังมากๆไว้จัดการกับ local repository เราจะมาสำรวจคำสั่งเหล่านี้และคลาสนั้นกันในอีกส่วนไม่ไกลข้างหน้า เพื่อที่จะได้เข้าใจวิธีการนำมันมาใช้

คำสั่ง `create_repo` สามารถใช้ในการสร้าง repository ใหม่บน hub ได้

```py
from huggingface_hub import create_repo

create_repo("dummy-model")
```

โค้ดนี้จะสร้าง repository ชื่อ `dummy-model` ใน namespace ของคุณ ถ้าคนชอบ คุณสามารถระบุอย่างเจาะจงไปได้ว่า repository นี้เป็นขององค์กรไหน โดยการใช้ `organization` argument:

```py
from huggingface_hub import create_repo

create_repo("dummy-model", organization="huggingface")
```

โค้ดนี้จะสร้าง repository ชื่อ `dummy-model` ใน namespace ของ `huggingface`  สมมุติว่าคุณเป็นคนขององค์กรนั้น
Arguments อื่นที่อาจจะเป็นประโยชน์:

- `private` ใช้ในการระบุว่าคนอื่นควรเห็น repository นี้ได้หรือไม่
- `token` ใช้ในกรณีที่คุณอยากจะแทนที่โทเค็นที่ถูกเก็บเอาไว้ใน cache ของคุณด้วยอีกโทเค็นที่ให้ไว้
- `repo_type` ใช้ในกรณีที่คุณอยากจะสร้าง `dataset` หรือ `space` แทนที่จะเป็นโมเดล ค่าที่รับได้คือ `"dataset"` และ `"space"`

เมื่อ repository ถูกสร้างขึ้นมาแล้ว เราสามารถเพิ่มไฟล์ไปในนั้นได้! ข้ามไปส่วนถัดไปเพื่อดูสามวิธีในการทำสิ่งนี้


## ใช้ web interface

web interface บรรจุเครื่องมือมากมายที่ใช้จัดการ repositories โดยตรงใน Hub ด้วยการใช้งาน web interface คุณสามารถสร้าง repositories, เพิ่มไฟล์ (รวมถึงไฟล์ขนาดใหญ่ด้วย!), สำรวจโมเดลต่างๆ, แสดงผลความแตกต่าง และอื่นๆอีกมากมายได้อย่างง่ายดาย

สร้าง repository ใหม่ได้โดยการไปที่ [huggingface.co/new](https://huggingface.co/new):

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/new_model.png" alt="Page showcasing the model used for the creation of a new model repository." width="80%"/>
</div>

ขั้นแรก ระบุเจ้าของของ repository ซึ่งสามารถเป็นคุณเองหรือองค์กรใดๆที่คุณเกี่ยวข้องด้วย ถ้าคุณเลือกองค์กร โมเดลจะถูกนำไปขึ้นบนหน้าขององค์กรด้วย และสมาชิกทุกคนขององค์กรนั้นจะมีสิทธิ์ในการมีส่วนร่วมกับ repository นี้

ถัดไป ใส่ชื่อโมเดลของคุณ ซึ่งจะเหมือนกับชื่อของ repository ก็ได้ และสุดท้าย คุณสามารถระบุได้ว่าคุณต้องการให้โมเดลของคุณเป็นสาธารณะหรือส่วนบุคคล โมเดลส่วนบุคคลนั้นจะถูกซ่อนจากมุมมองสาธารณะ

หลังจากที่คุณสร้าง model repository ของคุณแล้ว คุณควรจะเห็นหน้าแบบนี้:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/empty_model.png" alt="An empty model page after creating a new repository." width="80%"/>
</div>

นี่คือที่ที่โมเดลของคุณจะถูก host เอาไว้ สำหรับการเริ่มปรับแต่งเพิ่มเติมนั้น คุณสามารถเพิ่มไฟล์ README ได้โดยตรงจาก web interface

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/dummy_model.png" alt="The README file showing the Markdown capabilities." width="80%"/>
</div>

ไฟล์ README อยู่ในรูปแบบของ Markdown — สามารถปรับแต่งมันแบบจัดเต็มได้เลย! ในส่วนที่สามของบทนี้ถูกอุทิศให้กับการสร้างการ์ดโมเดล (model card) ซึ่งนี่เป็นส่วนที่สำคัญเป็นอันดับหนึ่งในการสร้างคุณค่าให้โมเดลของคุณ เพราะว่ามันจะเป็นสิ่งที่ใช้บอกคนอื่นว่าโมเดลของคุณทำอะไรได้บ้าง

ถ้าคุณดูที่แท็บ "Files and versions" คุณจะเห็นว่ามันยังไม่มีไฟล์อะไรมากมายเลย — แค่ไฟล์ *README.md* ที่คุณเพิ่งได้สร้างและไฟล์ *.gitattributes* สำหรับไว้ติดตามไฟล์ขนาดใหญ่

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/files.png" alt="The 'Files and versions' tab only shows the .gitattributes and README.md files." width="80%"/>
</div>

ต่อไปเราจะมาดูวิธีการเพิ่มไฟล์ใหม่กัน

## การอัพโหลดไฟล์โมเดลต่างๆ

ระบบที่ใช้จัดการไฟล์บน Hugging Face Hub มีพื้นฐานมาจาก git สำหรับไฟล์ทั่วไป และ git-lfs (ย่อมาจาก [Git Large File Storage](https://git-lfs.github.com/)) สำหรับไฟล์ที่ใหญ่ขึ้น

ในส่วนถัดไปนี้ เราจะพามาดูสามวิธีในการอัพโหลดไฟล์ต่างๆไปบน Hub: ผ่าน `huggingface_hub` และ ผ่านคำสั่ง git ต่างๆ

### แนวทางแบบใช้ `upload_file`

การใช้ `upload_file` ไม่จำเป็นต้องมี git และ git-lfs ติดตั้งอยู่บนระบบของคุณ มันดันไฟล์โดยตรงไปสู่ 🤗 Hub โดยใช้ HTTP POST requests ข้อจำกัดของแนวทางนี้คือมันไม่สามารถรองรับไฟล์ที่มีขนาดมากกว่า 5GB ได้
ถ้าไฟล์ของคุณมีขนาดใหญ่กว่า 5GB โปรดทำตามอีกสองขั้นตอนที่ระบุรายละเอียดไว้ด้านล่าง

API สามารถถูกใช้แบบนี้ได้:

```py
from huggingface_hub import upload_file

upload_file(
    "<path_to_file>/config.json",
    path_in_repo="config.json",
    repo_id="<namespace>/dummy-model",
)
```

โค้ดนี้จะทำการอัพโหลดไฟล์ `config.json` ที่อยู่ใน `<path_to_file>` ไปสู่รากของ repository โดยใช้ชื่อ `config.json` ไปสู่ `dummy-model` repository
Arguments อื่นที่อาจจะเป็นประโยชน์:

- `token` ใช้ในกรณีที่คุณอยากจะแทนที่โทเค็นที่ถูกเก็บเอาไว้ใน cache ของคุณด้วยอีกโทเค็นที่ให้ไว้
- `repo_type` ใช้ในกรณีที่คุณอยากจะสร้าง `dataset` หรือ `space` แทนที่จะเป็นโมเดล ค่าที่รับได้คือ `"dataset"` และ `"space"`


### คลาส `Repository`

คลาส `Repository` จัดการ local repository ในลักษณะคล้าย git มันเอาปัญหาส่วนมากที่ผู้คนมักเจอเวลาใช้ git ออกเพื่อจัดสรรทุกฟีเจอร์ทุกอย่างที่เราจำเป็นต้องใช้

การใช้คลาสนี้จำเป็นต้องติดตั้ง git และ git-lfs ดังนั้นคุณควรทำให้มั่นใจว่าคุณได้ติดตั้ง git-lfs แล้ว (ดู [ที่นี่](https://git-lfs.github.com/) สำหรับขั้นตอนการติดตั้ง) และตั้งค่าให้เสร็จก่อนที่คุณจะเริ่มไปต่อ

เพื่อที่จะเริ่มเล่นกับ repository ที่เราเพิ่งจะได้สร้างกัน เราสามารถเริ่มตั้งต้นมันไปใน local folder ได้โดยการโคลน (clone) remote repository:

```py
from huggingface_hub import Repository

repo = Repository("<path_to_dummy_folder>", clone_from="<namespace>/dummy-model")
```

โค้ดนี้จะสร้างโฟลเดอร์ `<path_to_dummy_folder>` ในไดเรกทอรีที่คุณทำงานอยู่ (working directory) โฟลเดอร์นี้เก็บไฟล์ `.gitattributes` เอาไว้เพียงไฟล์เดียวเพราะว่ามันเป็นไฟล์เดียวที่จะถูกสร้างเมื่อมีการสร้าง repository ตั้งต้นด้วยคำสั่ง `create_repo`

จากจุดนี้เป็นต้นไป เราสามารถใช้คำสั่ง git แบบดั่งเดิมต่างๆได้:

```py
repo.git_pull()
repo.git_add()
repo.git_commit()
repo.git_push()
repo.git_tag()
```

และอื่นๆอีก! เราแนะนำให้ลองไปดูที่หนังสืออ้างอิง (documentation) ของ `Repository` ซึ่งเข้าถึงได้จาก [ที่นี่](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) สำหรับภาพรวมของคำสั่งทั้งหมดที่ใช้ได้

ณ ปัจจุบัน เรามีโมเดลและ tokenizer ซึ่งเราต้องการที่จะดันขึ้นสู่ hub แล้ว เราได้โคลน repository มาอย่างเสร็จสมบูรณ์ ดังนั้นเราสามารถเก็บไฟล์ต่างๆไว้ใน repository นั้นได้แล้ว

อันดับแรกเราต้องทำให้มั่นใจว่า local clone ของเรานั้นเป็นปัจจุบัน ซึ่งทำได้โดยการดึง (pull) การเปลี่ยนแปลงใหม่ล่าสุดเข้ามา:

```py
repo.git_pull()
```

เมื่อโค้ดทำงานเสร็จแล้ว เราเก็บไฟล์โมเดลและ tokenizer:

```py
model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```

ที่ `<path_to_dummy_folder>` ตอนนี้มีไฟล์โมเดลและ tokenizer ทั้งหมดแล้ว เราจะทำตาม workflow ของ git ทั่วไปโดยการ เพิ่มไฟล์ไปยัง staging area, ทำการ commit และดัน (push) ไฟล์เหล่านั้นสู่ hub:

```py
repo.git_add()
repo.git_commit("Add model and tokenizer files")
repo.git_push()
```

ขอแสดงความยินดีด้วย! คุณเพิ่งได้ทำการดันไฟล์ชุดแรกของคุณสู่ hub

### แนวทางแบบใช้ git

แนวทางนี้เป็นแนวทางที่ตรงๆมากสำหรับทำการอัพโหลดไฟล์ นั่นคือเราจะใช้ git และ git-lfs โดยตรง ส่วนที่ยากส่วนใหญ่ถูกในออกไปในแนวทางก่อนๆ แต่มันมีข้อแม้บางอย่างกับขั้นตอนแต่ไปนี้ ดังนั้นเราจะทำตามการใช้งานแบบที่ซับซ้อนขึ้นกว่าปกติ

การใช้งานต่อจากนี้จำเป็นต้องติดตั้ง git และ git-lfs ดังนั้นคุณควรทำให้มั่นใจว่าคุณได้ติดตั้ง git-lfs แล้ว (ดู [ที่นี่](https://git-lfs.github.com/) สำหรับขั้นตอนการติดตั้ง) และตั้งค่าให้เสร็จก่อนที่คุณจะเริ่มไปต่อ

เริ่มแรกโดยการเริ่มต้นสร้าง git-lfs:

```bash
git lfs install
```

```bash
Updated git hooks.
Git LFS initialized.
```

เมื่อโค้ดทำงานเสร็จแล้ว ขั้นตอนแรกคือการโคลน model repository ของคุณ:

```bash
git clone https://huggingface.co/<namespace>/<your-model-id>
```

username ของฉันคือ `lysandre` และฉันใช้ `dummy` เป็นชื่อโมเดล ดังนั้นสำหรับฉันแล้ว คำสั่งจะมีหน้าตาประมาณแบบนี้:

```
git clone https://huggingface.co/lysandre/dummy
```

ตอนนี้ฉันมีโฟลเดอร์ชื่อ *dummy* ในไดเรกทอรีที่ฉันทำงานอยู่ (working directory) ฉันสามารถ `cd` เข้าไปในโฟลเดอร์และดูว่ามีอะไรข้างในบ้าง:

```bash
cd dummy && ls
```

```bash
README.md
```

ถ้าคุณสร้าง repository ด้วยการใช้คำสั่ง `create_repo` ของ Hugging Face Hub โฟลเดอร์นี้ควรจะมีไฟล์ `.gitattributes` ถูกซ่อนอยู่ด้วย ถ้าคุณทำตามขั้นตอนในส่วนก่อนหน้านี้ในการสร้าง repository โดยใช้ web interface ในโฟลเดอร์นี้ควรจะมี *README.md* หนึ่งไฟล์มาพร้อมกับไฟล์ `.gitattributes` ที่ถูกซ่อนอยู่ อย่างที่แสดงให้เห็นนี้

การเพิ่มไฟล์หนึ่งไฟล์ที่มีขนาดปกติ เช่น ไฟล์สำหรับกำหนดค่า (configuration file), ไฟล์สำหรับเก็บคำศัพท์ (vocabulary file) หรือโดยง่ายๆแล้วไฟล์อะไรก็ตามที่มีขนาดน้อยกว่าสองสาม megabytes สามารถทำได้ด้วยการทำแบบที่ทำปกติกับระบบที่พึ่งพา git อย่างไรก็ตามไฟล์ที่ใหญ่กว่านั้นจะต้องถูกลงทะเบียนผ่าน git-lfs เพื่อที่จะดันไฟล์เหล่านั้นขึ้นสู่ *huggingface.co* ได้

กลับไปหา Python กันสักหน่อยเพื่อจะสร้างโมเดลและ tokenizer ที่เราต้องการจะ commit ไปสู่ repository จำลองของเรา

{#if fw === 'pt'}
```py
from transformers import AutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = AutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Do whatever with the model, train it, fine-tune it...

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{:else}
```py
from transformers import TFAutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = TFAutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Do whatever with the model, train it, fine-tune it...

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{/if}

ถึงตอนนี้เราได้ทำการบันทึกไฟล์จากโมเดลและ tokenizer บางส่วนแล้ว ลองไปดูโฟลเดอร์ *dummy* อีกรอบกันเถอะ:

```bash
ls
```

{#if fw === 'pt'}
```bash
config.json  pytorch_model.bin  README.md  sentencepiece.bpe.model  special_tokens_map.json tokenizer_config.json  tokenizer.json
```

ถ้าคุณดูที่ขนาดของไฟล์ (อย่างเช่น ใช้คำสั่ง `ls -lh`) คุณควรจะเห็นได้ว่าไฟล์ model state dict (*pytorch_model.bin*) มีขนาดที่โดดมาก นั่นคือมากกว่า 400 MB

{:else}
```bash
config.json  README.md  sentencepiece.bpe.model  special_tokens_map.json  tf_model.h5  tokenizer_config.json  tokenizer.json
```

ถ้าคุณดูที่ขนาดของไฟล์ (อย่างเช่น ใช้คำสั่ง `ls -lh`) คุณควรจะเห็นได้ว่าไฟล์ model state dict (*t5_model.h5*) มีขนาดที่โดดมาก นั่นคือมากกว่า 400 MB

{/if}

<Tip>
✏️ ตอนที่สร้าง repository จาก web interface ไฟล์ *.gitattributes* ถูกตั้งค่าอย่างอัตโนมัติเพื่อให้พิจารณาไฟล์บางประเภท เช่น *.bin* และ *.h5* ว่าเป็นไฟล์ขนาดใหญ่ และ git-lfs จะติดตามไฟล์เหล่านั้นโดยคุณไม่จำเป็นต้องตั้งค่าอะไรเลย
</Tip> 

ต่อไปเราจะทำต่อโดยการทำแบบที่เราทำกับ Git repositories ดั้งเดิม เราสามารถเพิ่มไฟล์ทั้งหมดไปยัง staging environment ของ Git ได้ด้วยการใช้คำสั่ง `git add`:

```bash
git add .
```

เราสามารถดูได้ว่าไฟล์ที่อยู่ในสถานะ staged มีอะไรบ้าง:

```bash
git status
```

{#if fw === 'pt'}
```bash
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   .gitattributes
	new file:   config.json
	new file:   pytorch_model.bin
	new file:   sentencepiece.bpe.model
	new file:   special_tokens_map.json
	new file:   tokenizer.json
	new file:   tokenizer_config.json
```
{:else}
```bash
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   .gitattributes
  	new file:   config.json
	new file:   sentencepiece.bpe.model
	new file:   special_tokens_map.json
	new file:   tf_model.h5
	new file:   tokenizer.json
	new file:   tokenizer_config.json
```
{/if}

ในแบบเดียวกัน เราสามารถทำให้มั่นใจได้ว่า git-lfs กำลังติดตามไฟล์ที่ถูกต้องอยู่ ได้โดยการใช้คำสั่ง status:

```bash
git lfs status
```

{#if fw === 'pt'}
```bash
On branch main
Objects to be pushed to origin/main:


Objects to be committed:

	config.json (Git: bc20ff2)
	pytorch_model.bin (LFS: 35686c2)
	sentencepiece.bpe.model (LFS: 988bc5a)
	special_tokens_map.json (Git: cb23931)
	tokenizer.json (Git: 851ff3e)
	tokenizer_config.json (Git: f0f7783)

Objects not staged for commit:


```

เราสามารถเห็นได้ว่าทุกไฟล์มี `Git` เป็นตัวจัดการ (handler) ยกเว้น *pytorch_model.bin* และ *sentencepiece.bpe.model* ซึ่งเป็น `LFS` เยี่ยม!

{:else}
```bash
On branch main
Objects to be pushed to origin/main:


Objects to be committed:

	config.json (Git: bc20ff2)
	sentencepiece.bpe.model (LFS: 988bc5a)
	special_tokens_map.json (Git: cb23931)
	tf_model.h5 (LFS: 86fce29)
	tokenizer.json (Git: 851ff3e)
	tokenizer_config.json (Git: f0f7783)

Objects not staged for commit:


```

เราสามารถเห็นได้ว่าทุกไฟล์มี `Git` เป็นตัวจัดการ (handler) ยกเว้น *t5_model.h5* ซึ่งเป็น `LFS` เยี่ยม!

{/if}

มาทำขั้นตอนสุดท้ายต่อกันเถอะ ทำการ commit และดัน (push) ขึ้นสู่ *huggingface.co* remote repository:

```bash
git commit -m "First model version"
```

{#if fw === 'pt'}
```bash
[main b08aab1] First model version
 7 files changed, 29027 insertions(+)
  6 files changed, 36 insertions(+)
 create mode 100644 config.json
 create mode 100644 pytorch_model.bin
 create mode 100644 sentencepiece.bpe.model
 create mode 100644 special_tokens_map.json
 create mode 100644 tokenizer.json
 create mode 100644 tokenizer_config.json
```
{:else}
```bash
[main b08aab1] First model version
 6 files changed, 36 insertions(+)
 create mode 100644 config.json
 create mode 100644 sentencepiece.bpe.model
 create mode 100644 special_tokens_map.json
 create mode 100644 tf_model.h5
 create mode 100644 tokenizer.json
 create mode 100644 tokenizer_config.json
```
{/if}

การดันไฟล์นั้นอาจจะใช้เวลาสักหน่อย ขึ้นอยู่กับความเร็วของการเชื่อมต่ออินเทอร์เน็ตและขนาดไฟล์ของคุณ:

```bash
git push
```

```bash
Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done.
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 12 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done.
Total 9 (delta 1), reused 0 (delta 0), pack-reused 0
To https://huggingface.co/lysandre/dummy
   891b41d..b08aab1  main -> main
```

{#if fw === 'pt'}
ถ้าเราดูที่ model repository เมื่อโค้ดทำงานเสร็จแล้ว เราจะเห็นทุกไฟล์ที่เพิ่งถูกเพิ่มเข้าไปอยู่ในนั้น:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

UI ช่วยให้คุณสามารถสำรวจไฟล์โมเดลและ commits และดูความแตกต่างของแต่ละ commit ได้:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffs.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>
{:else}
ถ้าเราดูที่ model repository เมื่อโค้ดทำงานเสร็จแล้ว เราจะเห็นทุกไฟล์ที่เพิ่งถูกเพิ่มเข้าไปอยู่ในนั้น:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model_tf.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

UI ช่วยให้คุณสามารถสำรวจไฟล์โมเดลและ commits และดูความแตกต่างของแต่ละ commit ได้:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffstf.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>
{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter4/3.mdx" />

### คำถามท้ายบท
https://huggingface.co/learn/course/th/chapter4/6.md

# คำถามท้ายบท

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

มาทดสอบความรู้ที่คุณได้เรียนในบทนี้กันเถอะ!

### 1. อะไรคือข้อจำกัดของโมเดลบน Hub?

<Question
	choices={[
		{
			text: "โมเดลจาก 🤗 Transformers library",
			explain: "แม้ว่า Hugging Face Hub รับรองโมเดลจาก 🤗 Transformers library แต่นั่นก็ไม่ใช่ทั้งหมดที่มี!"
		},
		{
			text: "ทุกโมเดลที่มีอินเตอร์เฟซเหมือนกับ 🤗 Transformers",
			explain: "ไม่มีข้อบังคับเกี่ยวกับอินเตอร์เฟซเมื่อทำการอัพโหลดโมเดลขึ้นสู่ Hugging Face Hub"
		},
		{
			text: "ไม่มีข้อจำกัดใดๆ",
			explain: "ถูกต้องแล้ว! ไม่มีข้อจำกัดใดๆในการอัพโหลดโมเดลขึ้นสู่ Hub",
			explain: "Right! There are no limits when uploading models to the Hub.",
            correct: true
		},
        {
			text: "โมเดลที่ใช้ได้กับงานประเภท NLP",
			explain: "ไม่มีข้อบังคับเกี่ยวกับประเภทของการใช้งาน!"
		}
	]}
/>

### 2. คุณสามารถจัดการโมเดลบน Hub ได้อย่างไร?

<Question
	choices={[
		{
			text: "ผ่านบัญชี GCP",
			explain: "ไม่ถูกต้อง!"
		},
		{
			text: "ผ่านการกระจายแบบ peer-to-peer",
			explain: "ไม่ถูกต้อง!"
		},
		{
			text: "ผ่าน git และ git-lfs",
			explain: "ถูกต้องแล้ว! โมเดลบน Hub เป็น Git repositories แบบเรียบง่าย มีการใช้งาน <code>git-lfs</code> สำหรับไฟล์ขนาดใหญ่",
            correct: true
		}
	]}
/>

### 3. คุณสามารถใช้ web interface ของ Hugging Face Hub ทำอะไรได้บ้าง? 

<Question
	choices={[
		{
			text: "Fork repository ที่มีอยู่แล้ว",
			explain: "การ Fork repository ไม่สามารถทำได้บน Hugging Face Hub"
		},
		{
			text: "สร้าง model repository ใหม่",
			explain: "ถูกต้องแล้ว! แต่นั้นก็ไม่ใช่ทั้งหมดที่คุณทำได้",
            correct: true
		},
		{
			text: "จัดการและแก้ไขไฟล์",
			explain: "ถูกต้องแล้ว! แต่นั้นก็ไม่ใช่คำตอบที่ถูกเพียงข้อเดียว",
            correct: true
		},
        {
			text: "อัพโหลดไฟล์",
			explain: "ถูกต้องแล้ว! แต่ยังไม่ใช่ทั้งหมด",
            correct: true
		},
        {
			text: "ดูความแตกต่างระหว่างเวอร์ชั่นต่างๆ",
			explain: "ถูกต้องแล้ว! แต่นั้นก็ไม่ใช่ทั้งหมดที่คุณทำได้",
            correct: true
		}
	]}
/>

### 4. การ์ดโมเดลคืออะไร?

<Question
	choices={[
		{
			text: "คำอธิบายโมเดลแบบหยาบๆ ดังนั้นจึงสำคัญน้อยกว่าไฟล์โมเดลและ tokenizer",
			explain: "มันคือคำอธิบายของโมเดล แต่มันเป็นชิ้นที่สำคัญมาก: ถ้ามันไม่สมบูรณ์หรือขาดตกบกพร่องบางอย่างไป คุณประโยชน์ของโมเดลจะลงลดอย่างมาก"
		},
		{
			text: "วิธีที่ใช้ในการรับประกันความสามารถในการทำซ้ำ (reproducibility), ความสามารถในการนำกลับมาใช้ (reusability) และความเท่าเทียม (fairness)",
			explain: "ถูกต้องแล้ว! การแบ่งปันข้อมูลที่ถูกต้องในการ์ดโมเดลจะช่วยให้ผู้ใช้สามารถนำโมเดลของคุณไปใช้และคำนึงถึงข้อจำกัดและความลำเอียงของมัน",
            correct: true
		},
		{
			text: "ไฟล์ Python ที่สามารถรันเพื่อดึงข้อมูลเกี่ยวกับโมเดลได้",
			explain: "การ์ดโมเดลเป็นไฟล์ Markdown แบบเรียบง่าย"
		}
	]}
/>

### 5. วัตถุ (objects) ใดต่อไปนี้ของ 🤗 Transformers library ที่สามารถถูกแบ่งปันได้โดยตรงบน Hub ผ่านคำสั่ง `push_to_hub()`?

{#if fw === 'pt'}
<Question
	choices={[
		{
			text: "tokenizer",
			explain: "ถูกต้องแล้ว! tokenizer ทั้งหมดมีคำสั่ง <code>push_to_hub</code> และใช้มันในการดันไฟล์ทั้งหมดของ tokenizer (คำศัพท์ (vocabulary), สถาปัตยกรรมของ tokenizer และอื่นๆ) ไปยัง repo ที่กำหนดได้ แต่นี่ก็ไม่ใช่คำตอบที่ถูกเพียงข้อเดียว!",
            correct: true
		},
		{
			text: "model configuration",
			explain: "ถูกต้องแล้ว! ไฟล์สำหรับกำหนดค่าของโมเดล (model configurations) ทั้งหมดมีคำสั่ง <code>push_to_hub</code> และใช้มันในการดันพวกมันขึ้นไปยัง repo ที่กำหนดได้ มีอะไรอีกที่คุณสามารถแบ่งปันได้?",
            correct: true
		},
		{
			text: "model",
			explain: "ถูกต้องแล้ว! โมเดลทั้งหมดมีคำสั่ง <code>push_to_hub</code> และใช้มันในการดันพวกมันและไฟล์กำหนดค่าของพวกมันขึ้นไปยัง repo ที่กำหนดได้ แต่นั่นก็ไม่ใช่ทั้งหมดที่คุณแบ่งปันได้",
            correct: true
		},
        {
			text: "Trainer",
			explain: "ถูกต้อง — <code>Trainer</code> ก็มีคำสั่ง <code>push_to_hub</code> และใช้มันในการอัพโหลดโมเดล, ไฟล์กำหนดค่า, tokenizer และดราฟของการ์ดโมเดลไปยัง repo ที่กำหนดได้ ลองตอบข้ออื่นดู!",
            correct: true
		}
	]}
/>
{:else}
<Question
	choices={[
		{
			text: "tokenizer",
			explain: "ถูกต้องแล้ว! tokenizer ทั้งหมดมีคำสั่ง <code>push_to_hub</code> และใช้มันในการดันไฟล์ทั้งหมดของ tokenizer (คำศัพท์ (vocabulary), สถาปัตยกรรมของ tokenizer และอื่นๆ) ไปยัง repo ที่กำหนดได้ แต่นี่ก็ไม่ใช่คำตอบที่ถูกเพียงข้อเดียว!",
            correct: true
		},
		{
			text: "model configuration",
			explain: "ถูกต้องแล้ว! ไฟล์สำหรับกำหนดค่าของโมเดล (model configurations) ทั้งหมดมีคำสั่ง <code>push_to_hub</code> และใช้มันในการดันพวกมันขึ้นไปยัง repo ที่กำหนดได้ มีอะไรอีกที่คุณสามารถแบ่งปันได้?",
            correct: true
		},
		{
			text: "model",
			explain: "ถูกต้องแล้ว! โมเดลทั้งหมดมีคำสั่ง <code>push_to_hub</code> และใช้มันในการดันพวกมันและไฟล์กำหนดค่าของพวกมันขึ้นไปยัง repo ที่กำหนดได้ แต่นั่นก็ไม่ใช่ทั้งหมดที่คุณแบ่งปันได้",
            correct: true
		},
		{
			text: "ทั้งหมดที่กล่าวมารวมกับ callback ที่อุทิศให้",
			explain: "ถูกต้อง — โดยปกติ <code>PushToHubCallback</code> จะส่งวัตถุทั้งหมดเหล่านั้นไปยัง repo ระหว่างการเทรน",
            correct: true
		}
	]}
/>
{/if}

### 6. อะไรคือขั้นตอนแรกในการใช้คำสั่ง `push_to_hub()` หรือเครื่องมือ CLI?

<Question
	choices={[
		{
			text: "Log in เข้าสู่หน้าเว็บไซต์",
			explain: "การทำแบบนี้ไม่ได้ช่วยอะไรกับ local machine ของคุณเลย"
		},
		{
			text: "รันคำสั่ง 'huggingface-cli login' ใน terminal",
			explain: "ถูกต้อง — การทำแบบนี้จะดาวน์โหลดและเก็บโทเค็นส่วนตัวของคุณไว้ใน cache",
            correct: true
		},
		{
			text: "รันคำสั่ง 'notebook_login()' ใน notebook",
			explain: "ถูกต้อง — การทำแบบนี้จะแสดงเครื่องมือ (widget) ให้คุณรับรองความถูกต้อง (authentication)",
            correct: true
		},
	]}
/>

### 7. คุณกำลังใช้งานโมเดลและ tokenizer อยู่ — คุณจะสามารถอัพโหลดพวกมันขึ้นไปบน Hub ได้อย่างไร?

<Question
	choices={[
		{
			text: "โดยการเรียกคำสั่ง push_to_hub โดยตรงจากโมเดลและ tokenizer",
			explain: "ถูกต้องแล้ว!",
            correct: true
		},
		{
			text: "ภายใน Python runtime คลุมพวกมันเอาไว้ด้วย <code>huggingface_hub</code> utility",
			explain: "โมเดลและ tokenizers ถ้ารับประโยชน์จาก <code>huggingface_hub</code> utilities อยู่แล้ว: ไม่จำเป็นจะต้องมีการคลุมเพิ่ม!"
		},
		{
			text: "โดยการบันทึกพวกมันลงบนเครื่องและเรียกใช้คำสั่ง <code>transformers-cli upload-model</code>",
			explain: "ไม่มีคำสั่ง <code>upload-model</code> นี้อยู่"
		}
	]}
/>

### 8. มีการดำเนินการ git (git operations) ใดบ้างที่คุณสามารถทำได้กับคลาส `Repository`?

<Question
	choices={[
		{
			text: "commit",
			explain: "ถูกต้อง คำสั่ง <code>git_commit()</code> มีไว้สำหรับสิ่งนี้",
            correct: true
		},
		{
			text: "pull",
			explain: "นั่นคือวัตถุประสงค์ของคำสั่ง <code>git_pull()</code>",
            correct: true
		},
		{
			text: "push",
			explain: "คำสั่ง <code>git_push()</code> มีไว้ทำสิ่งนี้",
            correct: true
		},
		{
			text: "merge",
			explain: "ไม่ การดำเนินการนี้ไม่สามารถทำได้กับ API นี้"
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter4/6.mdx" />

### การสร้างการ์ดโมเดล (model card)
https://huggingface.co/learn/course/th/chapter4/4.md

# การสร้างการ์ดโมเดล (model card)

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

การ์ดโมเดลเป็นไฟล์ที่เรียกได้ว่าสำคัญพอๆกับไฟล์โมเดลและ tokenizer ใน model repository มันคือคำนิยามส่วนกลางของโมเดล เป็นสิ่งที่ทำให้มั่นใจว่าสมาชิกของชุมชนสามารถนำไปใช้ได้ (reusability) และสามารถทำซ้ำและได้ผลลัพธ์เหมือนเดิมได้ (reproducibility) และมันยังจัดเตรียมแพลตฟอร์มให้สมาชิกคนอื่นๆอาจใช้สร้างสิ่งประดิษฐ์ (artifacts) ของเขาเองได้ 

การสร้างหนังสืออ้างอิง (documenting) ถึงกระบวนการเทรนและประเมินผลช่วยให้ผู้อื่นเข้าใจถึงสิ่งที่คาดหวังได้จากโมเดลๆหนึ่ง และการให้ข้อมูลที่เพียงพอเกี่ยวกับข้อมูลที่ถูกใช้, กระบวนการเตรียมข้อมูล (preprocessing) และกระบวนการหลังจากใช้โมเดล (postprocessing) ที่ถูกทำมาทำให้มั่นใจถึงขีดจำกัด (limitations), ความลำเอียง (biases) และบริบท (contexts) ที่โมเดลเป็นและไม่เป็น ซึ่งเป็นสิ่งที่เป็นประโยชน์มากถ้าหากสามารถระบุและเข้าใจได้

ดังนั้นการสร้างการ์ดโมเดลที่อธิบายถึงโมเดลของคุณอย่างละเอียดถือเป็นขั้นตอนหนึ่งที่สำคัญมาก ในส่วนนี้เรามีเคล็ดลับบางอย่างที่จะช่วยให้คุณทำสิ่งนี้ได้ การสร้างการ์ดโมเดลนั้นถูกทำผ่านไฟล์ *README.md* ที่คุณได้เห็นก่อนหน้านี่ ซึ่งมันก็คือไฟล์ Markdown ไฟล์หนึ่ง

แนวคิด “การ์ดโมเดล” เกิดขึ้นมาจากทิศทางของงานวิจัยชิ้นหนึ่งจาก Google ซึ่งถูกแบ่งปันครั้งแรกใน ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) โดย Margaret Mitchell et al. ข้อมูลส่วนใหญ่ที่เราจะพูดถึงในที่นี้มีฐานมาจากงานวิจัยชิ้นนี้ และเราแนะนำให้คุณลองดูงานวิจัยนี้เพื่อจะได้เข้าใจถึงว่าทำไมการ์ดโมเดลถึงมีความสำคัญต่อโลกที่ให้ค่าแก่ความสามารถในการทำซ้ำ (reproducibility), ความสามารถในการนำกลับมาใช้ (reusability) และความเท่าเทียม (fairness) อย่างมาก

โดยทั่วไปแล้วการ์ดโมเดลมักเริ่มต้นด้วยภาพรวมแบบสรุปย่อของโมเดลว่าโมเดลนี้มีไว้ทำอะไร ตามมาด้วยข้อมูลเพิ่มเติมในส่วนต่างๆต่อไปนี้:

- คำอธิบายโมเดล
- การนำไปใช้งานที่ควรจะเป็นและข้อจำกัดต่างๆ
- วิธีใช้
- ข้อจำกัดและความลำเอียง
- ข้อมูลที่ใช้เทรน
- ขั้นตอนการเทรน
- ผลจากการประเมิน

มาดูกันดีกว่าว่าแต่ละส่วนนี้ควรมีอะไรบ้าง

### คำอธิบายโมเดล

คำอธิบายโมเดลควรบอกรายละเอียดพื้นฐานเกี่ยวกับโมเดล ซึ่งหมายถึงสถาปัตยกรรม (architecture), เวอร์ชั่น, มาจากงานวิจัยหรือไม่, มี implementation จากแหล่งต้นทางหรือไม่, ผู้เขียน และข้อมูลทั่วไปเกี่ยวกับโมเดล ลิขสิทธิ์ทุกอย่างควรจะถูกระบุอยู่ในส่วนนี้ ข้อมูลโดยทั่วไปเกี่ยวกับกระบวนการเทรน, พารามิเตอร์ และข้อจำกัดความรับผิดชอบ (disclaimers) ที่สำคัญสามารถถูกพูดถึงในส่วนนี้ได้เช่นกัน

### การนำไปใช้งานที่ควรจะเป็นและข้อจำกัดต่างๆ

ในส่วนนี้คุณควรอธิบายกรณีการใช้งาน (use cases) ที่โมเดลถูกตั้งใจสร้างขึ้นมาเพื่อให้ใช้งาน รวมถึงภาษา, ประเภทงาน และโดเมนที่โมเดลสามารถประยุกต์ใช้ได้ ในส่วนนี้ของการ์ดโมเดลสามารถพูดถึงเกี่ยวกับสายงานที่ไม่อยู่ในขอบเขตของโมเดล และสายงานที่โมเดลไม่สามารถปฏิบัติการได้ดีที่สุด

### วิธีใช้

ส่วนนี้ควรมีตัวอย่างของการใช้งานโมเดล สามารถเป็นในรูปแบบของการใช้งานฟังก์ชั่น `pipeline()`, การใช้งานคลาสโมเดลและ tokenizer และโค้ดอื่นๆที่คุณคิดว่าน่าจะเป็นประโยชน์ได้

### ข้อมูลที่ใช้เทรน

ส่วนนี้ควรระบุถึงชุดข้อมูลที่โมเดลได้ถูกเทรนมา ถ้าหากมีคำอธิบายโดยสรุปของชุดข้อมูลก็จะดีมาก

### ขั้นตอนการเทรน

ในส่วนนี้คุณควรอธิบายทุกอย่างที่เกี่ยวข้องกับการเทรนที่เป็นประโยชน์ในมุมมองของการนำไปทำซ้ำ ซึ่งควรมีกระบวนการเตรียมข้อมูล (preprocessing) และกระบวนการหลังจากใช้โมเดล (postprocessing) ที่ถูกทำไว้กับชุดข้อมูลนี้ รวมถึงรายละเอียดเกี่ยวกับจำนวนรอบ (epochs) ที่ใช้ในการเทรนโมเดล, ขนาด batch (batch size), อัตราการเรียนรู้ (learning rate) และอื่นๆ

### ตัวแปรและเมตริก

ส่วนนี้คุณควรอธิบายเมตริก (metrics) ที่คุณใช้ในการประเมินผล และปัจจัยต่างๆที่คุณตรวจวัด การกล่าวถึงเมตริกต่างๆที่ถูกใช้ กับชุดข้อมูลไหนและแบ่งอย่างไร ช่วยทำให้การเปรียบเทียบประสิทธิภาพของโมเดลของคุณกับโมเดลอื่นๆเป็นไปได้ง่าย ซึ่งสิ่งเหล่านี้ควรจะถูกระบุอยู่ในส่วนที่ผ่านมา เช่น ผู้ใช้และการนำไปใช้งานที่ควรจะเป็น 

### ผลจากการประเมิน

ส่วนสุดท้าย คุณควรระบุข้อบ่งชี้ว่าโมเดลของคุณทำงานได้ดีแค่ไหนกับชุดข้อมูลสำหรับประเมินผล (evaluation dataset) ถ้าโมเดลมีการใช้เกณฑ์ในการตัดสินใจ (decision threshold) คุณควรระบุค่าที่ใช้เป็นเกณฑ์นั้นในการประเมินผล หรือระบุรายละเอียดของการประเมินผลที่เกณฑ์ต่างกันสำหรับการใช้งานที่ควรจะเป็น

## ตัวอย่าง

ลองเข้าไปดู links เหล่านี้เป็นตัวอย่างสำหรับการ์ดโมเดลที่ถูกขัดเกลามาอย่างดี:

- [`bert-base-cased`](https://huggingface.co/bert-base-cased)
- [`gpt2`](https://huggingface.co/gpt2)
- [`distilbert`](https://huggingface.co/distilbert-base-uncased)

ตัวอย่างเพิ่มเติมจากองค์กรและบริษัทต่างๆ สามารถดูได้จาก [ที่นี่](https://github.com/huggingface/model_card/blob/master/examples.md)

## หมายเหตุ

การ์ดโมเดลไม่ได้เป็นหนึ่งในสิ่งที่เรียกร้อง (requirement) เมื่อจะทำการเปิดเผยโมเดลสู่สาธารณะ คุณไม่จำเป็นต้องใส่ทุกส่วนที่เราได้พูดถึงไปข้างบนถ้าคุณต้องการจะทำมัน  อย่างไรก็ได้หนังสืออ้างอิงที่ชัดเจนของโมเดลสามารถสร้างประโยชน์กับผู้ใช้ในอนาคตได้ ดังนั้นเราจึงแนะนำให้คุณเติมส่วนต่างๆที่คุณรู้ให้ได้มากที่สุดเท่าที่จะเป็นไปได้
## ข้อมูลเมตาของการ์ดโมเดล (Model card metadata)

ถ้าคุณได้ทำการสำรวจ Hugging Face Hub มาบ้างแล้ว คุณน่าจะได้เห็นบางโมเดลในหมวดหมู่ต่างๆ ที่คุณสามารถคัดกรองพวกมันได้ด้วยประเภทงาน, ภาษา, libraries และอื่นๆ หมวดหมู่ที่โมเดลๆหนึ่งอยู่ถูกระบุตามข้อมูลเมตา (metadata) ที่คุณเพิ่มเข้าไปในหัวของการ์ดโมเดล

ตัวอย่างเช่น ถ้าคุณลองดูที่ [การ์ดโมเดล `camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md) คุณจะเห็นบรรทัดต่อไปนี้ในหัวการ์ดโมเดล:

```
---
language: fr
license: mit
datasets:
- oscar
---
```

ข้อมูลเมตานี้ถูกแยกวิเคราะห์โดย Hugging Face Hub ซึ่งต่อจากนั้นจะถูกใช้เป็นตัวระบุว่าโมเดลนี้เป็นโมเดลภาษาฝรั่งเศส มีใบอนุญาต MIT และถูกเทรนมากับชุดข้อมูล Oscar

[รายละเอียดการ์ดโมเดลแบบเต็ม](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) ช่วยให้สามารถระบุภาษา, ใบอนุญาต, แท็ก, ชุดข้อมูล, เมตริก รวมไปถึงผลลัพธ์จากการประเมินผลที่โมเดลได้จากการเทรน

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter4/4.mdx" />

### การใช้งานโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models)
https://huggingface.co/learn/course/th/chapter4/2.md

# การใช้งานโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models)

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter4/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter4/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter4/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter4/section2_tf.ipynb"},
]} />

{/if}

Model Hub ทำให้การเลือกใช้โมเดลที่เหมาะสมเป็นเรื่องง่ายขนาดที่ว่า การใช้งานมันคู่กับ library ปลายน้ำสามารถเสร็จได้ในการใช้โค้ดเพียงไม่กี่บรรทัดเท่านั้น มาดูวิธีใช้โมเดลพวกนี้และการให้ความช่วยเหลือกับชุมชนกันดีกว่า

สมมุติว่าเรากำลังมองหาโมเดลภาษาฝรั่งเศสที่สามารถเติมคำที่หายไปได้ (mask filling)

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/camembert.gif" alt="Selecting the Camembert model." width="80%"/>
</div>

เราเลือก `camembert-base` checkpoint มาลองใช้ ตัวระบุ `camembert-base` คือทั้งหมดที่เราต้องการในการเริ่มใช้งาน! อย่างที่คุณได้เห็นไปแล้วในบทก่อนหน้านี้ เราสามารถเรียกใช้งานมันได้ด้วยคำสั่ง `pipeline()`:

```py
from transformers import pipeline

camembert_fill_mask = pipeline("fill-mask", model="camembert-base")
results = camembert_fill_mask("Le camembert est <mask> :)")
```

```python out
[
  {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, 
  {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, 
  {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, 
  {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, 
  {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'}
]
```

อย่างที่คุณเห็น การโหลดโมเดลใน pipeline นั้นง่ายมากๆ สิ่งเดียวที่ควรระวังคือ checkpoint ที่คุณเลือกนั้นควรเหมาะสมกับประเภทของงานที่คุณจะทำ อย่างเช่น ในงานนี้เราโหลด `camembert-base` checkpoint ใน `fill-mask` pipeline ซึ่งเหมาะกับงานที่เราจะใช้อย่างแน่นอน แต่ถ้าเราโหลด checkpoint นี้ใน `text-classification` pipeline ผลลัพธ์จะไม่สมเหตุสมผล เพราะหัวข้อของ `camembert-base` ไม่เหมาะสมกับงานประเภทนี้! เราแนะนำให้ใช้ตัวเลือกประเภทงาน (task selector) ในอินเตอร์เฟซของ Hugging Face Hub เพื่อเลือก checkpoints ที่เหมาะสม

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/tasks.png" alt="The task selector on the web interface." width="80%"/>
</div>

คุณสามารถเรียกใช้ checkpoint โดยการใช้สถาปัตยกรรมโมเดล (model architecture) ได้โดยตรงด้วย:

{#if fw === 'pt'}
```py
from transformers import CamembertTokenizer, CamembertForMaskedLM

tokenizer = CamembertTokenizer.from_pretrained("camembert-base")
model = CamembertForMaskedLM.from_pretrained("camembert-base")
```

อย่างไรก็ตาม เราแนะนำให้ใช้ [คลาส `Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) แทน เพราะว่ามันเป็นคลาสที่สามารถใช้ได้กับสถาปัตยกรรมหลายประเภท (design architecture-agnostic) ในขณะที่โค้ดก่อนหน้านี้จำกัดผู้ใช้อยู่กับ checkpoints ที่สามารถโหลดได้เฉพาะกับสถาปัตยกรรมแบบ CamemBERT การใช้คลาส `Auto*` นั้นทำให้การเปลี่ยน checkpoints เป็นเรื่องง่าย:

```py
from transformers import AutoTokenizer, AutoModelForMaskedLM

tokenizer = AutoTokenizer.from_pretrained("camembert-base")
model = AutoModelForMaskedLM.from_pretrained("camembert-base")
```
{:else}
```py
from transformers import CamembertTokenizer, TFCamembertForMaskedLM

tokenizer = CamembertTokenizer.from_pretrained("camembert-base")
model = TFCamembertForMaskedLM.from_pretrained("camembert-base")
```

อย่างไรก็ตาม เราแนะนำให้ใช้ [คลาส `TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) แทน เพราะว่ามันเป็นคลาสที่สามารถใช้ได้กับสถาปัตยกรรมหลายประเภท (design architecture-agnostic) ในขณะที่โค้ดก่อนหน้านี้จำกัดผู้ใช้อยู่กับ checkpoints ที่สามารถโหลดได้เฉพาะกับสถาปัตยกรรมแบบ CamemBERT การใช้คลาส `TFAuto*` นั้นทำให้การเปลี่ยน checkpoints เป็นเรื่องง่าย:

```py
from transformers import AutoTokenizer, TFAutoModelForMaskedLM

tokenizer = AutoTokenizer.from_pretrained("camembert-base")
model = TFAutoModelForMaskedLM.from_pretrained("camembert-base")
```
{/if}

<Tip>
เมื่อมีการใช้งานโมเดลที่ผ่านการเทรนมาแล้ว (pretrained model) คุณควรตรวจสอบให้มั่นใจว่ามันถูกเทรนมาอย่างไร กับชุดข้อมูลไหน ขีดจำกัด (limits) และความลำเอียง (biases) คืออะไร ซึ่งข้อมูลทั้งหมดนี้ควรถูกระบุอยู่ในการ์ดโมเดล (model card)
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter4/2.mdx" />

### Fine-tune โมเดลสำเร็จแล้ว!
https://huggingface.co/learn/course/th/chapter3/5.md

# Fine-tune โมเดลสำเร็จแล้ว!

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

สนุกจังเลย! ในสองบทแรกคุณได้เรียนรู้เกี่ยวกับโมเดลและ tokenizers และตอนนี้คุณก็รู้วิธีการ fine-tune โมเดลด้วยข้อมูลของคุณเองแล้ว มาทบทวนกันว่าคุณได้ทำอะไรไปบ้างในบทนี้:

{#if fw === 'pt'}
* เรียนรู้การใช้งาน datasets ผ่าน [Hub](https://huggingface.co/datasets)
* เรียนรู้วิธีการโหลดและประมวลผล datasets รวมถึงการใช้งาน dynamic padding และ collators
* เขียนโค้ดการ fine-tuning และการประเมินประสิทธิภาพของโมเดลในแบบของคุณเอง
* เขียนโค้ดลูปการเทรนโดยไม่ใช้ class Trainer
* ใช้ไลบรารี่ 🤗 Accelerate เพื่อปรับลูปการเทรนของคุณให้ใช้การได้กับการเทรนโดยใช้ GPUs หรือ TPUs หลายตัวได้อย่างง่ายดาย

{:else}
* เรียนรู้การใช้งาน datasets ผ่าน [Hub](https://huggingface.co/datasets)
* เรียนรู้วิธีการโหลดและประมวลผล datasets
* เรียนรู้วิธีการใช้ Keras ในการ fine-tune และประเมินประสิทธิภาพของโมเดล
* เขียนโค้ดสร้าง metric ในแบบของคุณเอง

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter3/5.mdx" />

### บทนำ
https://huggingface.co/learn/course/th/chapter3/1.md

# บทนำ

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

ใน [Chapter 2](/course/chapter2) เราได้เรียนรู้วิธีการใช้ tokenizers และโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models) ในการทำนาย แต่ถ้าเราต้องการจะใช้ dataset ของเราเองในการ fine-tune โมเดลล่ะ? นั่นคือหัวข้อของบทนี้เลย! คุณจะได้เรียนรู้:

{#if fw === 'pt'}
* วิธีการเตรียม dataset ขนาดใหญ่จาก Hub
* วิธีการใช้ high-level `Trainer` API ในการ fine-tune โมเดล
* วิธีการใช้ training loop ที่เขียนขึ้นเอง (custom training loop)
* วิธีการใช้ประโยชน์จาก 🤗 Accelerate library ในการรัน custom training loop บน distributed setup แบบต่าง ๆ

{:else}
* วิธีการเตรียม dataset ขนาดใหญ่จาก Hub
* วิธีการใช้ Keras ในการ fine-tune โมเดล
* วิธีการใช้ Keras ในการทำนายผล
* วิธีการใช้ metric ที่เขียนขึ้นเอง (custom metric)

{/if}

ถ้าคุณต้องการจะ upload trained checkpoints ของคุณขึ้น Hugging Face Hub คุณจะต้องมีบัญชี huggingface.co ก่อน : [create an account](https://huggingface.co/join)

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter3/1.mdx" />

### การ Fine-tune โมเดลด้วย Trainer API
https://huggingface.co/learn/course/th/chapter3/3.md

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

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter3/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter3/section3.ipynb"},
]} />

<Youtube id="nvBXf7s7vTI"/>

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

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

```py
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 แบบพื้นฐาน

```py
from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")
```

<Tip>

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

</Tip>

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

```py
from transformers import AutoModelForSequenceClassification

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

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

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

```py
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` ของเรา:

```py
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()`:

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

```python out
(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](/course/chapter2) ว่าโมเดล Transformers ทุกตัวจะให้ผลลัพธ์เป็น logits) เพื่อจะแปลง logits ให้เป็นการทำนายที่เราสามารถเปรียบเทียบกับ label ของเราได้ เราจะต้องหา index ของค่าที่มีค่าสูงสุดใน axis ที่สอง:

```py
import numpy as np

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

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

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
```

```python out
{'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](https://arxiv.org/pdf/1810.04805.pdf) ได้รายงานค่า F1 score ไว้ที่ 88.9 สำหรับ base model ซึ่งเป็นโมเดล `uncased` ในขณะที่โมเดลของเราเป็นโมเดล `cased` จึงเป็นเหตุให้มีผลลัพธ์ที่ดีกว่า

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

```py
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()` ได้ดังนี้:

```py
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 เพียงอย่างเดียวกัน

<Tip>

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

</Tip>



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter3/3.mdx" />

### คำถามท้ายบท
https://huggingface.co/learn/course/th/chapter3/6.md

# คำถามท้ายบท

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

ทดสอบความรู้ที่คุณได้เรียนมาจากบทนี้กัน!

### 1. ใน `emotion` dataset ซึ่งได้รวบรวมข้อความ Twitter ที่มีการ labeled ว่าแต่ละข้อความนั้นเป็นข้อความที่มีอารมณ์แบบใด ลองค้นข้อมูลดูจาก [Hub](https://huggingface.co/datasets)และอ่าน dataset card ดูแล้วตอบว่า ข้อใดไม่ใช่หนึ่งในอารมณ์พื้นฐานของ dataset นี้?

<Question
	choices={[
		{
			text: "Joy",
			explain: "ลองอีกครั้ง — อารมณ์นี้มีอยู่ใน dataset นะ!"
		},
		{
			text: "Love",
			explain: "ลองอีกครั้ง — อารมณ์นี้มีอยู่ใน dataset นะ!"
		},
		{
			text: "Confusion",
			explain: "ถูกต้อง! Confusion ไม่ได้เป็นหนึ่งในหกอารมณ์พื้นฐาน",
            correct: true
		},
        {
			text: "Surprise",
			explain: "Surprise! ยังไม่ถูกนะ ลองใหม่อีกครั้ง!"
		}
	]}
/>

### 2. ลองหาข้อมูล `ar_sarcasm` dataset ใน [Hub](https://huggingface.co/datasets) ดูว่ามันสามารถทำ Task อะไรได้บ้าง?

<Question
	choices={[
		{
			text: "Sentiment classification (การจำแนกอารมณ์ของข้อความ)",
			explain: "ถูกต้องเลย! ดีนะที่อ่าน tags.",
            correct: true
		},
		{
			text: "Machine translation (การแปลภาษา)",
			explain: "ยังไม่ถูกนะ — ลองดูข้อมูลใหม่อีกครั้งที่ <a href='https://huggingface.co/datasets/ar_sarcasm'>dataset card</a>!"
		},
		{
			text: "Named entity recognition (การจำแนกหน่วยย่อยของประโยค)",
			explain: "ยังไม่ถูกนะ — ลองดูข้อมูลใหม่อีกครั้งที่ <a href='https://huggingface.co/datasets/ar_sarcasm'>dataset card</a>!"
		},
        {
			text: "Question answering (การตอบคำถาม)",
			explain: "ตอบคำถามได้ดีแต่ยังไม่ถูกนะ ลองใหม่อีกครั้ง!"
		}
	]}
/>

### 3. โมเดล BERT ต้องการข้อมูลนำเข้า เป็นคู่ประโยคในลักษณะใด?

<Question
	choices={[
		{
			text: "Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2",
			explain: "คุณต้องใส่ Token พิเศษชื่อ <code>[SEP]</code> เพื่อแยกระหว่างคู่ประโยคด้วย แต่แค่นี้ยังไม่ครบถ้วนนะ!"
		},
		{
			text: "[CLS] Tokens_of_sentence_1 Tokens_of_sentence_2",
			explain: "คุณต้องใส่ Token พิเศษชื่อ  <code>[CLS]</code> ไว้ที่ต้นประโยคแรกด้วย แต่แค่นี้ยังไม่ครบถ้วนนะ!"
		},
		{
			text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2 [SEP]",
			explain: "ถูกต้อง!",
            correct: true
		},
        {
			text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2",
			explain: "คุณต้องใส่ Token พิเศษชื่อ  <code>[CLS]</code> ไว้ที่ต้นประโยคแรก รวมถึง Token พิเศษชื่อ <code>[SEP]</code> เพื่อแยกระหว่างคู่ประโยค แต่แค่นี้ยังไม่ครบถ้วนนะ!"
		}
	]}
/>

{#if fw === 'pt'}
### 4. ข้อใดเป็นประโยชน์ที่ได้จากการใช้เมธอด `Dataset.map()`?

<Question
	choices={[
		{
			text: "มีการเก็บผลลัพธ์ของฟังก์ชั่นไว้ใน cache ทำให้คุณไม่ต้องเสียเวลาเพิ่มเติมหากมีการรันโค้ดใหม่",
			explain: "นั่นเป็นหนึ่งในประโยชน์ของเมธอดนี้จริง ๆ ด้วย! แต่มันไม่ได้มีประโยชน์แค่ข้อนี้ข้อเดียวนะ...",
            correct: true
		},
		{
			text: "มันสามารถใช้ multiprocessing ทำให้ประมวลผลได้เร็วกว่าการใช้ฟังก์ชั่นกับ element แต่ละตัวใน dataset",
			explain: "นั่นก็เป็นหนึ่งในประโยชน์ของเมธอดนี้ แต่มันไม่ได้มีประโยชน์แค่ข้อนี้ข้อเดียวนะ..",
            correct: true
		},
		{
			text: "มันไม่ได้โหลดเอา dataset ทั้งหมดเข้าไปใน memory แต่จะบันทึกผลลัพธ์เมื่อประมวลผล element แต่ละตัวเสร็จ",
			explain: "นั่นก็เป็นหนึ่งในประโยชน์ของเมธอดนี้ แต่มันยังมีประโยชน์ด้านอื่นด้วยนะ!",
            correct: true
		},
	]}
/>

### 5. dynamic padding หมายถึงอะไร?

<Question
	choices={[
		{
			text: "หมายถึงการเติมข้อมูลในแต่ละ batch ให้เท่ากับข้อมูลที่ยาวที่สุดใน dataset",
			explain: "มันมีการเติมข้อมูลในแต่ละ batch นะ แต่ไม่ได้เติมให้เท่ากับข้อมูลที่ยาวที่สุดใน dataset"
		},
		{
			text: "หมายถึงการเติมข้อมูลในแต่ละ batch ให้เท่ากับข้อมูลที่ยาวที่สุดใน batch นั้น",
			explain: "ถูกต้อง! คำว่า \"dynamic\" มีที่มาจากการที่ขนาดของแต่ละ batch นั้นมีความแตกต่างกันและถูกกำหนดเมื่อสร้างแต่ละ batch",
            correct: true
		},
		{
			text: "หมายถึงการเติมข้อมูลเพื่อให้แต่ละประโยคมีจำนวน token เท่ากับประโยคก่อนหน้าใน dataset.",
			explain: "นั่นไม่ถูกต้องนะ และมันก็ไม่สมเหตุสมผลที่จะไปดูข้อมูลของประโยคก่อนหน้า เพราะเราจะมีการ shuffle ข้อมูลระหว่างการเทรนโมเดลด้วย"
		},
	]}
/>

### 6. ข้อใดคือหน้าที่ของฟังก์ชั่น collate?

<Question
	choices={[
		{
			text: "เพื่อทำให้มั่นใจว่าทุก sequences ใน dataset มีความยาวเท่ากัน",
			explain: "ฟังก์ชั่น collate นั้นมีการจัดการเพียงในแต่ละ batch ไม่ได้จัดการทั้ง dataset นอกจากนี้เรากำลังพูดถึงฟังก์ชั่น collate ทั่ว ๆ ไป ไม่ได้เจาะจงถึง <code>DataCollatorWithPadding</code>"
		},
		{
			text: "เพื่อเก็บข้อมูลเข้ามาทำเป็น batch อย่างเหมาะสม",
			explain: "ถูกต้อง! คุณสามารถใส่ฟังก์ชั่น collate เป็นอากิวเมนต์ของ <code>DataLoader</code> เราได้ใช้ฟังก์ชั่น <code>DataCollatorWithPadding</code> ในการเติมข้อมูลทุกตัวใน batch ให้มีความยาวเท่ากัน",
            correct: true
		},
		{
			text: "เพื่อประมวลผลข้อมูลทั้ง dataset.",
			explain: "นั่นเป็นหน้าที่ของฟังก์ชั่นประมวลผล (preprocessing) ไม่ใช่หน้าที่ของฟังก์ชั่น collate"
		},
        {
			text: "เพื่อตัด sequences ทุกตัวใน dataset.",
			explain: "ฟังก์ชั่น collate นั้นมีการจัดการเพียงในแต่ละ batch ไม่ได้จัดการทั้ง dataset และถ้าคุณต้องการตัด (truncating) คุณสามารถใช้อากิวเมนต์ <code>truncate</code> ของ <code>tokenizer</code>."
		}
	]}
/>

### 7. จะเกิดอะไรขึ้นถ้าคุณสร้างออพเจ็กต์ของคลาส `AutoModelForXxx` ตัวหนึ่งซึ่งมี pretrained language model (เช่น `bert-base-uncased`) เพื่อนำไปทำ task ที่แตกต่างไปจาก task ที่เคยเทรนไว้?

<Question
	choices={[
		{
			text: "ไม่มีอะไรเกิดขึ้น แต่คุณจะได้รับข้อความ warning",
			explain: "คุณจะได้ข้อความ warning แต่มันไม่ใช่แค่นั้นนะ!"
		},
		{
			text: "head ของ pretrained model จะถูกตัดทิ้งไป และจะมีการสร้าง head ใหม่ให้เหมาะสมกับ task นั้นขึ้นมาแทนที่",
			explain: "ถูกต้อง ยกตัวอย่างเช่น เมื่อเราใช้ <code>AutoModelForSequenceClassification</code> กับ <code>bert-base-uncased</code> เราจะได้ข้อความ warnings เพราะ pretrained head นั้นไม่ใช้ในการทำ sequence classification มันจึงถูกตัดทิ้งและมีการสร้าง head ใหม่ขึ้นมาแทน โดยกำหนดค่า weights ขึ้นแบบสุ่ม",
            correct: true
		},
		{
			text: "head ของ pretrained model จะถูกตัดทิ้งไป",
			explain: "ยังมีอีกสิ่งหนึ่งที่จะต้องเกิดขึ้นด้วย ลองใหม่อีกครั้งนะ!"
		},
        {
			text: "ไม่มีอะไรเกิดขึ้น เนื่องจากเราสามารถ fine tune โมเดลให้ทำ task ที่ต่างออกไปได้",
			explain: "head ของ pretrained model ไม่ได้ถูกเทรนมาให้ทำ task นี้ เราจึงต้องตัดมันทิ้งไป!"
		}
	]}
/>

### 8. ข้อใดคือหน้าที่ของ `TrainingArguments`?

<Question
	choices={[
		{
			text: "เพื่อเก็บ hyperparameters ทุกตัวที่ใช้ในการเทรนและประเมินผลโมเดลด้วย <code>Trainer</code>",
			explain: "ถูกต้อง!",
            correct: true
		},
		{
			text: "เพื่อกำหนดขนาดของโมเดล",
			explain: "ขนาดของโมเดลนั้นจะถูกกำหนดโดย model configuration ไม่ใช่ <code>TrainingArguments</code>"
		},
		{
			text: "เพื่อเก็บ hyperparameters ที่ใช้ในการประเมินผลโมเดล",
			explain: "ในตัวอย่างเราได้กำหนดว่าจะเก็บโมเดลและ checkpoints ไว้ที่ไหนด้วย ลองใหม่อีกครั้ง!"
		},
        {
			text: "เพื่อเก็บ hyperparameters ที่ใช้ในการเทรนโมเดล",
			explain: "ในตัวอย่างเราได้ใช้ <code>evaluation_strategy</code> ด้วย มันจึงส่งผลต่อการประเมินผลโมเดลด้วย ลองใหม่อีกครั้ง!"
		}
	]}
/>

### 9. ทำไมคุณจึงควรใช้ไลบรารี่ 🤗 Accelerate?

<Question
	choices={[
		{
			text: "เพื่อเรียกใช้โมเดลที่ทำงานได้เร็วกว่า",
			explain: "ไม่ใช่นะ ไลบรารี่ 🤗 Accelerate ไม่มีข้อมูลเกี่ยวกับโมเดลเลย"
		},
		{
			text: "มันมี high-level API ให้ใช้ ฉันจึงไม่ต้องเขียนลูปในการเทรนเอง",
			explain: "ขั้นตอนนั้นเป็นการเรียกใช้ <code>Trainer</code> ไม่ใช่ไลบรารี่ 🤗 Accelerate ลองใหม่อีกครั้ง!"
		},
		{
			text: "ช่วยให้ลูปในการเทรนของคุณใช้การได้กับ distributed setup",
			explain: "ถูกต้อง! การใช้ 🤗 Accelerate จะช่วยให้ลูปในการเทรนของคุณใช้การได้เมื่อต้องเทรนด้วย GPUs หรือ TPUs หลาย ๆ ตัว",
            correct: true
		},
        {
			text: "มันมีฟังก์ชั่น optimization ให้เลือกใช้มากกว่า",
			explain: "ไม่ใช่นะ ไลบรารี่ 🤗 Accelerate ไม่มีฟังก์ชั่น optimization เลย"
		}
	]}
/>

{:else}
### 4. จะเกิดอะไรขึ้นถ้าคุณสร้างออพเจ็กต์ของคลาส `AutoModelForXxx` ตัวหนึ่งซึ่งมี pretrained language model (เช่น `bert-base-uncased`) เพื่อนำไปทำ task ที่แตกต่างไปจาก task ที่เคยเทรนไว้?

<Question
	choices={[
		{
			text: "ไม่มีอะไรเกิดขึ้น แต่คุณจะได้รับข้อความ warning",
			explain: "คุณจะได้ข้อความ warning แต่มันไม่ใช่แค่นั้นนะ!"
		},
		{
			text: "head ของ pretrained model จะถูกตัดทิ้งไป และจะมีการสร้าง head ใหม่ให้เหมาะสมกับ task นั้นขึ้นมาแทนที่",
			explain: "ถูกต้อง ยกตัวอย่างเช่น เมื่อเราใช้ <code>AutoModelForSequenceClassification</code> กับ <code>bert-base-uncased</code> เราจะได้ข้อความ warnings เพราะ pretrained head นั้นไม่ใช้ในการทำ sequence classification มันจึงถูกตัดทิ้งและมีการสร้าง head ใหม่ขึ้นมาแทน โดยกำหนดค่า weights ขึ้นแบบสุ่ม",
            correct: true
		},
		{
			text: "head ของ pretrained model จะถูกตัดทิ้งไป",
			explain: "ยังมีอีกสิ่งหนึ่งที่จะต้องเกิดขึ้นด้วย ลองใหม่อีกครั้งนะ!"
		},
        {
			text: "ไม่มีอะไรเกิดขึ้น เนื่องจากเราสามารถ fine tune โมเดลให้ทำ task ที่ต่างออกไปได้",
			explain: "head ของ pretrained model ไม่ได้ถูกเทรนมาให้ทำ task นี้ เราจึงต้องตัดมันทิ้งไป!"
		}
	]}
/>

### 5. โมเดล TensorFlow จาก `transformers` นั้นเป็นโมเดล Keras อยู่แล้ว การที่เป็นแบบนี้นั้นมีประโยชน์อะไรบ้าง?

<Question
	choices={[
		{
			text: "โมเดลจะทำงานกับ TPU ได้เลยทันที",
			explain: "เกือบแล้ว! มันยังต้องปรับแก้อีกเล็กน้อย ยกตัวอย่างเช่น คุณจะต้องรันทุกอย่างใน <code>TPUStrategy</code> scope รวมถึงการ initialize โมเดลด้วย"
		},
		{
			text: "คุณสามารถใช้ประโยชน์จากเมธอดที่มีอยู่แล้วอย่างเช่น <code>compile()</code>, <code>fit()</code> และ <code>predict()</code>",
			explain: "ถูกต้อง! เมื่อคุณมีข้อมูล การเทรนก็ไม่ยาก แค่ต้องทำงานเพิ่มอีกนิดเดียวเท่านั้น",
            correct: true
		},
		{
			text: "คุณจะได้เรียนวิธีใช้ Keras และ transformers.",
			explain: "ก็ถูกนะ แต่เราอยากได้คำตอบอื่น :)",
			correct: true
		},
        {
			text: "คุณจะสามารถคำนวณ metrics ที่เกี่ยวข้องกับ dataset ได้โดยง่าย",
			explain: "Keras นั้นช่วยในการเทรนและประเมินผลโมเดล แต่ไม่ได้ช่วยคำนวณ metrics ที่เกี่ยวข้องกับ dataset"
		}
	]}
/>

### 6. คุณสามารถสร้าง metric ของคุณเองได้อย่างไร?

<Question
	choices={[
		{
			text: "โดยการสร้างคลาสย่อยของ <code>tf.keras.metrics.Metric</code>.",
			explain: "เยี่ยมเลย!",
			correct: true
		},
		{
			text: "ใช้ Keras functional API.",
			explain: "ลองใหม่อีกครั้ง!"
		},
		{
			text: "โดยการใช้ callable ด้วย signature <code>metric_fn(y_true, y_pred)</code>.",
			explain: "ถูกต้อง!",
			correct: true
		},
        {
			text: "ใช้ google ค้นหาวิธี",
			explain: "นั่นไม่ใช่คำตอบที่เราต้องการนะ แต่มันก็น่าจะช่วยคุณหาวิธีได้จริง ๆ",
			correct: true
		}
	]}
/>

{/if}

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter3/6.mdx" />

### การ Fine-tune โมเดลด้วย Keras
https://huggingface.co/learn/course/th/chapter3/3_tf.md

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

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter3/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter3/section3_tf.ipynb"},
]} />

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

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

```py
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

<Youtube id="rnTGBy2ax1c"/>

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

<Youtube id="AUozVp78dhk"/>

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

```py
from transformers import TFAutoModelForSequenceClassification

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

คุณจะสังเกตได้ว่าคุณจะได้รับคำเตือนหลังจากที่สร้างโมเดลขึ้นมา ไม่เหมือนกับใน [Chapter 2](/course/chapter2) ที่เป็นเช่นนี้เนื่องจาก 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.

<Tip>

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

</Tip>

```py
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,
)
```

<Tip warning={true}>

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

</Tip>


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

<Youtube id="cpzq6ESSM5c"/>

ถ้าคุณลองโค้ดข้างต้น มันก็จะรันได้ แต่คุณจะพบว่า 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` ดังโค้ดข้างล่างนี้

```py
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)
```

<Tip>

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

</Tip>

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

```py
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 อีกรอบได้เลย:

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

<Tip>

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

</Tip>

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

<Youtube id="nx10eh4CoOs"/>


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

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

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

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

```python out
(408, 2) (408,)
```

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

```py
import evaluate

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

```python out
{'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](https://arxiv.org/pdf/1810.04805.pdf) ได้รายงานค่า 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


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter3/3_tf.mdx" />

### การเทรนโมเดลฉบับสมบูรณ์
https://huggingface.co/learn/course/th/chapter3/4.md

# การเทรนโมเดลฉบับสมบูรณ์

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter3/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter3/section4.ipynb"},
]} />

<Youtube id="Dh9CL8fyG80"/>

คราวนี้เราจะมาดูกันว่าถ้าเราอยากเขียนโค้ดให้ได้ผลลัพธ์แบบเดียวกันกับใน section ที่แล้วโดยไม่ต้องเรียกใช้ `Trainer` class จะต้องทำอย่างไร เราสันนิษฐานว่าคุณได้ทำกระบวนการประมวลผลข้อมูลใน section 2 มาแล้ว โค้ดข้างล่างนี้เป็นการสรุปอย่างย่อครอบคลุมถึงกระบวนการทุกอย่างที่คุณจำเป็นต้องทำ:

```py
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)
```

### เตรียมพร้อมก่อนเทรน

ก่อนที่เราจะเริ่มเขียนลูปในการเทรนโมเดล เราจะต้องกำหนดออพเจ็กต์บางตัวก่อน โดยออพเจ็กต์ชุดแรกที่เราต้องกำหนดก็คือ dataloaders (ออพเจ็กต์สำหรับโหลดข้อมูล) ที่เราจะใช้ในการโหลดข้อมูล โดยการทำซ้ำกับหลาย ๆ batch ของข้อมูล แต่ก่อนที่คุณจะกำหนด dataloaders เหล่านี้ เราจะต้องทำกระบวนการ postprocessing บางอย่างกับ `tokenized_datasets` ของเราก่อน เพื่อทำกระบวนการบางอย่างที่ `Trainer` ได้จัดการให้เราโดยอัตโนมัติ ซึ่งกระบวนการเหล่านั้นได้แก่:

- ลบคอลัมน์ที่มีข้อมูลที่โมเดลไม่ต้องการใช้ (เช่น คอลัมน์ `sentence1` และ  `sentence2`)
- เปลี่ยนชื่อคอลัมน์ `label` เป็น `labels` (เพราะว่าโมเดลคาดหวังอากิวเมนต์ชื่อว่า `labels`)
- กำหนดรูปแบบของ datasets ให้ส่งผลลัพธ์ออกมาเป็น PyTorach tensors แทนที่จะเป็น lists

`tokenized_datasets` ของเรามีเมธอดสำหรับการจัดการแต่ละขั้นตอนดังนี้:

```py
tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")
tokenized_datasets["train"].column_names
```

จากนั้นเราก็สามารถตรวจสอบผลลัพธ์ได้ว่ามีเฉพาะคอลัมน์ที่โมเดลต้องการใช้:

```python
["attention_mask", "input_ids", "labels", "token_type_ids"]
```

เมื่อเราทำขั้นตอนนี้เสร็จแล้ว เราก็สามารถกำหนด dataloaders ของเราได้อย่างง่ายดาย ดังนี้:

```py
from torch.utils.data import DataLoader

train_dataloader = DataLoader(
    tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator
)
```

เพื่อจะตรวจสอบอย่างรวดเร็วว่าไม่มีข้อผิดพลาดจากการประมวลผลข้อมูล เราสามารถลองเรียกข้อมูล batch หนึ่งมาดูได้ดังนี้:

```py
for batch in train_dataloader:
    break
{k: v.shape for k, v in batch.items()}
```

```python out
{'attention_mask': torch.Size([8, 65]),
 'input_ids': torch.Size([8, 65]),
 'labels': torch.Size([8]),
 'token_type_ids': torch.Size([8, 65])}
```

ควรระวังไว้ว่า shape ที่คุณได้อาจจะแตกต่างไปจากนี้เล็กน้อย เนื่องจากเราได้กำหนดค่าให้ training dataloader มีการทำ `shuffle=True` และเราได้เติมข้อมูลให้ยาวเท่ากับข้อมูลตัวที่ยาวที่สุดใน batch

ตอนนี้เราก็ทำกระบวนการประมวลผลข้อมูลเสร็จแล้ว (สำหรับนักปฏิบัติ ML แล้วนี่เป็นเป้าหมายที่น่าพึงพอใจทีเดียว แต่ยังไม่ได้ให้ผลลัพธ์อะไรออกมาเป็นรูปธรรมนะ) ลองกลับมาดูที่โมเดลกัน เราจะสร้างโมเดลขึ้นมาแบบเดียวกับที่เราทำใน section ที่แล้ว:

```py
from transformers import AutoModelForSequenceClassification

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

เพื่อให้แน่ใจว่าทุกอย่างจะทำงานได้ราบรื่นตลอดการเทรน เราจึงลองส่ง batch ของเราเข้าไปในโมเดลนี้ดู:

```py
outputs = model(**batch)
print(outputs.loss, outputs.logits.shape)
```

```python out
tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2])
```

โมเดล 🤗 Transformers ทุกตัวจะให้ผลลัพธ์ค่า loss ออกมาด้วยถ้าหากเราใส่ข้อมูล `labels` เข้าไปด้วย และเรายังได้ผลลัพธ์ออกมาเป็น logits (ได้ออกมาเป็น 2 ค่าสำหรับแต่ละ input ใน batch ของเรา ดังนั้น logits จะเป็น tensor ที่มีขนาด 8 x 2)

เราเกือบจะพร้อมสำหรับการเขียนลูปในการเทรนแล้ว! เราแค่ต้องการอีกสองสิ่งเท่านั้นเอง: optimizer (ตัวปรับปรุงให้การเทรนราบรื่นขึ้น) และ learning rate scheduler (ตัวกำหนดค่า learning rate ตามเวลา) เนื่องจากตอนนี้เราพยายามจะเลียนแบบสิ่งที่ `Trainer` ทำไว้ เราก็จะใช้ค่าเริ่มต้นที่เหมือนกัน โดยตัว optimizer ที่ `Trainer` ใช้คือ `AdamW` ซึ่งเป็นตัวเดียวกันกับ Adam แต่มีการพลิกแพลงในส่วนของ weight decay regularization (ดูเพิ่มเติมที่ ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) โดย Ilya Loshchilov and Frank Hutter):

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)
```

เราก็มาถึงขั้นตอนสุดท้าย เราจะต้องกำหนดตัว learning rate scheduler ซึ่งมีค่าเริ่มต้นให้ learningrate มีการ decay แบบเชิงเส้น โดยมีการลดค่าจากค่า learning rate ที่สูงที่สุด (5e-5) ไปเรื่อย ๆ จนมีค่าเป็น 0 เพื่อจะกำหนดค่าให้ถูกต้อง เราจะต้องรู้ว่าการเทรนครั้งนี้มีการเทรนจำนวนทั้งสิ้นกี่ step ซึ่งคำนวณได้จากจำนวน epochs ที่เราจะเทรน คูณด้วยจำนวน training batches (ซึ่งก็คือความยาวของ training dataloader ของเรา) โดยตัว `Trainer` มีค่าเริ่มต้นในการเทรนอยู่ที่ 3 epochs เราก็จะยึดตามค่านี้:

```py
from transformers import get_scheduler

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)
print(num_training_steps)
```

```python out
1377
```

### ลูปในการเทรน

ข้อควรคำนึงถึงข้อสุดท้าย: เราจะต้องการใช้ GPU ถ้าหากเรามีการติดตั้งไว้ (ถ้าเราเทรนบน CPU จะต้องใช้เวลาหลายชั่วโมงแทนที่จะเป็นเวลาไม่กี่นาที) เพื่อกำหนดให้มีการใช้ GPU ทุกครั้งที่เป็นไปได้ เราสามารถกำหนด `device` ที่เราจะใส่โมเดลและ batches ของข้อมูลของเราลงไปได้ดังนี้:

```py
import torch

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
device
```

```python out
device(type='cuda')
```

ตอนนี้เราก็พร้อมจะเทรนโมเดลแล้ว! เพื่อให้เราพอคาดการณ์ได้ว่าการเทรนจะใช้เวลานานเท่าไร เราจึงเพิ่มแถบแสดงสถานะความคืบหน้าตามจำนวน step ในการเทรน โดยใช้ไลบรารี่ `tqdm` ดังนี้:

```py
from tqdm.auto import tqdm

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

คุณจะสามารถเห็นได้ว่าแก่นของลูปในการเทรนนั้นก็เหมือนกับตัวที่เราแสดงให้ดูในบทนำ เรายังไม่ได้กำหนดให้มีการรายงานค่าใด ๆ ออกมา ดังนั้นลูปในการเทรนตัวนี้จึงไม่ได้บอกอะไรเราเลยว่าโมเดลมีประสิทธิภาพเป็นอย่างไร เราจึงจำเป็นต้องเขียนลูปในการประเมินผลโมเดล (evaluation loop) ด้วย


### ลูปในการประเมินผลโมเดล (evaluation loop)

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

```py
import evaluate

metric = evaluate.load("glue", "mrpc")
model.eval()
for batch in eval_dataloader:
    batch = {k: v.to(device) for k, v in batch.items()}
    with torch.no_grad():
        outputs = model(**batch)

    logits = outputs.logits
    predictions = torch.argmax(logits, dim=-1)
    metric.add_batch(predictions=predictions, references=batch["labels"])

metric.compute()
```

```python out
{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535}
```

ผลลัพธ์ที่ได้อาจแตกต่างไปเล็กน้อยเนื่องจากมีการสุ่มค่า weight ตอนสร้าง model head และมีการสลับข้อมูลแบบสุ่ม แต่ผลที่ได้ก็ควรจะใกล้เคียงกัน

<Tip>

✏️ **ลองเลย!** แก้ไขลูปในการเทรนก่อนหน้านี้เพื่อทำการ fine-tune โมเดลของคุณด้วย SST-2 dataset.

</Tip>

### เร่งความเร็วให้ลูปในการเทรนของคุณด้วย 🤗 Accelerate

<Youtube id="s7dy8QRgjJ0" />

ลูปในการเทรนที่เรากำหนดขึ้นก่อนหน้านี้ทำงานได้ดีบน CPU หรือ GPU ตัวเดียว แต่การใช้ไลบรารี่ [🤗 Accelerate](https://github.com/huggingface/accelerate) และเพิ่มการปรับค่าอีกเพียงเล็กน้อย จะช่วยให้เราสามารถเทรนบน distributed setup ที่มีการใช้ GPUs หรือ TPUs หลายตัวได้ โดยเริ่มต้นจากการสร้าง training และ validation dataloaders ลูปในการเทรนแบบ manual ของเรามีลักษณะดังนี้:

```py
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        batch = {k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

และต่อไปนี้คือสิ่งที่ต้องปรับแก้:

```diff
+ from accelerate import Accelerator
  from transformers import AutoModelForSequenceClassification, get_scheduler

+ accelerator = Accelerator()

  model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
  optimizer = AdamW(model.parameters(), lr=3e-5)

- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
- model.to(device)

+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+     train_dataloader, eval_dataloader, model, optimizer
+ )

  num_epochs = 3
  num_training_steps = num_epochs * len(train_dataloader)
  lr_scheduler = get_scheduler(
      "linear",
      optimizer=optimizer,
      num_warmup_steps=0,
      num_training_steps=num_training_steps
  )

  progress_bar = tqdm(range(num_training_steps))

  model.train()
  for epoch in range(num_epochs):
      for batch in train_dataloader:
-         batch = {k: v.to(device) for k, v in batch.items()}
          outputs = model(**batch)
          loss = outputs.loss
-         loss.backward()
+         accelerator.backward(loss)

          optimizer.step()
          lr_scheduler.step()
          optimizer.zero_grad()
          progress_bar.update(1)
```

โค้ดบรรทัดแรกที่ต้องเพิ่มเข้ามาเป็นส่วนของการ import โดยบรรทัดที่สองเป็นการสร้างออพเจ็กต์ `Accelerator` ที่จะตรวจสอบ environment ของคุณและสร้าง distributed setup ที่เหมาะสมขึ้นมาให้ โดย 🤗 Accelerate จะช่วยจัดการ device ให้คุณ คุณจึงสามารถเอาโค้ดส่วนที่คุณใส่โมเดลเข้าไปใน device ออกได้ (หรือถ้าคุณอยากคงไว้ ก็เปลี่ยนจาก `device` เป็น `accelerator.device`)

จากนัั้นก็มีการทำงานหลัก ๆ ในบรรทัดที่ส่ง dataloaders, โมเดล และ optimizer เข้าไปที่ `accelerator.prepare()` ซึ่งเป็นการ wrap ออพเจ็กต์เหล่านี้ให้อยู่ใน container ที่เหมาะสม และทำให้แน่ใจว่า distributed training ของคุณทำงานได้ตามที่ตั้งใจไว้ การเปลี่ยนแปลงส่วนที่เหลือคือการเอาโค้ดส่วนที่คุณใส่ batch เข้าไปใน `device` ออก (และอีกครั้ง ถ้าคุณอยากคงไว้ ก็เปลี่ยนจาก `device` เป็น `accelerator.device` และแก้จาก `loss.backward()` เป็น `accelerator.backward(loss)`)

<Tip>
⚠️ เพื่อที่จะได้ประโยชน์จากความเร็วที่เพิ่มขึ้นจากการใช้ Cloud TPUs เราแนะนำให้คุณเติมข้อมูลของคุณด้วยความยาวที่คงที่โดยการกำหนดอากิวเมนต์ `padding="max_length"` และ `max_length` ให้กับ tokenizer
</Tip>

ถ้าคุณอยากคัดลองและวางโค้ดเพื่อทดลองดู โค้ดข้างล่างนี้คือตัวอย่างของลูปในการเทรนโดยใช้ 🤗 Accelerate แบบสมบูรณ์:

```py
from accelerate import Accelerator
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler

accelerator = Accelerator()

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

train_dl, eval_dl, model, optimizer = accelerator.prepare(
    train_dataloader, eval_dataloader, model, optimizer
)

num_epochs = 3
num_training_steps = num_epochs * len(train_dl)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dl:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)
```

การใส่โค้ดข้างบนนี้เข้าไปในสคริปต์ `train.py` จะทำให้สคริปต์ของคุณรันได้ไม่ว่าจะมี distributed setup เป็นแบบใดก็ตาม เพื่อจะลองบน distributed setup ของคุณ ให้รันคำสั่งนี้:

```bash
accelerate config
```

ซึ่งจะให้คุณตอบคำถาม 2-3 ข้อ และใส่คำตอบของคุณลงไปในไฟล์ configuration ที่ใช้ในคำสั่งนี้:

```
accelerate launch train.py
```

ซึ่งจะเริ่มการเทรนโมเดลแบบ distributed 

ถ้าคุณอยากลองโค้ดนี้บน Notebook (เช่น ทดลองกับ TPUs บน Colab) แค่วางโค้ดนี้ลงไปใน `training_function()` และรัน cell สุดท้ายด้วยโค้ดนี้:

```python
from accelerate import notebook_launcher

notebook_launcher(training_function)
```

คุณสามารถศึกษาจากตัวอย่างอื่น ๆ เพิ่มเติม ได้ใน [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter3/4.mdx" />

### การประมวลผลข้อมูล
https://huggingface.co/learn/course/th/chapter3/2.md

# การประมวลผลข้อมูล

{#if fw === 'pt'}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter3/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter3/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter3/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter3/section2_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
เราจะยังคงใช้ตัวอย่างจากบทที่แล้ว [previous chapter](/course/chapter2) โค้ดข้างล่างนี้คือวิธีการเทรนโมเดลสำหรับจำแนกลำดับ (sequence classifier) โดยใช้ข้อมูล 1 batch ใน Pytorch:

```python
import torch
from torch.optim import AdamW
from transformers import AutoTokenizer, AutoModelForSequenceClassification

# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "This course is amazing!",
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")

# This is new
batch["labels"] = torch.tensor([1, 1])

optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()
```
{:else}
เราจะยังคงใช้ตัวอย่างจากบทที่แล้ว [previous chapter](/course/chapter2) โค้ดข้างล่างนี้คือวิธีการเทรนโมเดลสำหรับจำแนกลำดับ (sequence classifier) โดยใช้ข้อมูล 1 batch ใน TensorFlow:

```python
import tensorflow as tf
import numpy as np
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "This course is amazing!",
]
batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf"))

# This is new
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
labels = tf.convert_to_tensor([1, 1])
model.train_on_batch(batch, labels)
```
{/if}

เป็นที่แน่นอนว่า ถ้าเราเทรนโมเดลโดยใช้ข้อมูลเพียง 2 ประโยคก็คงไม่ได้ผลลัพธ์ที่ดีเท่าไรนัก ถ้าคุณต้องการผลลัพธ์ที่ดีขึ้น คุณจะต้องเตรียมชุดข้อมูล (dataset) ที่มีขนาดใหญ่ขึ้น

ใน section นี้ เราจะใช้ชุดข้อมูล MRPC (Microsoft Research Paraphrase Corpus) มารันให้ดูเป็นตัวอย่าง ชุดข้อมูลนี้มีการนำเสนอใน [paper](https://www.aclweb.org/anthology/I05-5002.pdf) โดย William B. Dolan and Chris Brockett โดยชุดข้อมูลนี้ประกอบด้วยคู่ของประโยคจำนวน 5,801 คู่ โดยมีข้อมูล label บ่งบอกว่าประโยคแต่ละคู่เกิดจากการถอความ (paraphrase) หรือไม่ (ประโยคคู่นี้มีความหมายเดียวกันหรือไม่) เหตุผลที่เราเลือกชุดข้อมูลนี้ เนื่องจากมันเป็นชุดข้อมูลที่มีขนาดเล็ก จึงง่ายต่อการนำไปทดลองเทรนโมเดล

### วิธีการโหลดชุดข้อมูลจาก Hub

{#if fw === 'pt'}
<Youtube id="_BZearw7f0w"/>
{:else}
<Youtube id="W_gMJF0xomE"/>
{/if}

Hub นั้นไม่ได้เก็บเพียงแค่โมเดล แต่ยังเก็บชุดข้อมูลในหลากหลายภาษาไว้เป็นจำนวนมาก คุณสามารถเลือกดูชุดข้อมูลต่าง ๆ ได้ที่ [here](https://huggingface.co/datasets) และเราขอแนะนำให้คุณลองโหลดและประมวลผลชุดข้อมูลชุดใหม่หลังจากที่คุณเรียน section นี้จบแล้ว (ดูเอกสารข้อมูลทั่วไปได้ที่ [here](https://huggingface.co/docs/datasets/loading)) แต่ตอนนี้เรามาสนใจกับชุดข้อมูล MRPC กันก่อนนะ! ชุดข้อมูลนี้เป็นหนึ่งในสิบของชุดข้อมูลที่ใช้วัดผลใน [GLUE benchmark](https://gluebenchmark.com/) ซึ่งเป็นตัววัดผลทางวิชาการ (academic benchmark) ที่ใช้วัดประสิทธิภาพของโมเดล ML โดยให้โมเดลทำงานจำแนกข้อความแบบต่าง ๆ กัน รวม 10 งาน

ไลบรารี่ 🤗 Datasets library มีคำสั่งที่ใช้งานได้ง่ายมากในการดาวโหลดและ cache ชุดข้อมูลที่อยู่บน Hub เราสามารถดาวโหลดชุดข้อมูล MRPC ได้ดังนี้:

<Tip>
⚠️ **คำเตือน** ตรวจสอบให้แน่ใจว่า `datasets` ได้ถูกติดตั้งโดยการรัน `pip install datasets` ก่อน จากนั้นโหลดชุดข้อมูล MRPC และพิมพ์เพื่อดูว่ามีอะไรบ้าง
</Tip> 

```py
from datasets import load_dataset

raw_datasets = load_dataset("glue", "mrpc")
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 408
    })
    test: Dataset({
        features: ['sentence1', 'sentence2', 'label', 'idx'],
        num_rows: 1725
    })
})
```

คุณจะเห็นว่า เราจะได้อ็อบเจกต์ `DatasetDict` ซึ่งเก็บข้อมูลของ training set (ชุดข้อมูลที่ใช้เทรน) validation set (ชุดข้อมูลที่ใช้ตรวจสอบ) และ test set (ชุดข้อมูลที่ใช้ทดสอบ) ซึ่งในแต่ละชุดก็ประกอบด้วยหลายคอลัมน์ (`sentence1`, `sentence2`, `label`, and `idx`) และมีตัวแปร num_rows เก็บจำนวนข้อมูลของแต่ละชุด (ใน training set มีคู่ประโยคจำนวน 3,668 คู่ ส่วนใน validation set มี 408 คู่ และใน test set มี 1,725 คู่)

คำสั่งนี้จะดาวโหลดและเก็บ cache ของชุดข้อมูลไว้ โดยค่าเริ่มต้น (by default) จะเก็บ cache ไว้ที่ *~/.cache/huggingface/dataset* โดยใน Chapter 2 เราได้บอกวิธีไว้แล้วว่า คุณสามารถเปลี่ยนโฟลเดอร์ที่จะเก็บ cache ได้โดยการตั้งค่าตัวแปร environment ที่ชื่อ `HF_HOME`

เราสามารถเข้าถึงข้อมูลประโยคแต่ละคู่ในอ็อบเจกต์ `raw_datasets` ของเราได้โดยการใช้ indexing แบบเดียวกับที่ใช้กับ dictionary:

```py
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
```

```python out
{'idx': 0,
 'label': 1,
 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}
```

เราจะเห็นได้ว่าข้อมูล labels นั้นอยู่ในรูป integers อยู่แล้ว จึงไม่ได้ต้องทำการประมวลผลใด ๆ เพิ่มเติมกับ label ถ้าอยากรู้ว่า integer ตัวไหนตรงกับ label ตัวไหน เราสามารถเข้าไปดูได้ที่ `features` ของอ็อพเจกต์ `raw_train_dataset` ของเรา ซึ่งจะบอกชนิดของข้อมูลในแต่ละคอลัมน์:

```py
raw_train_dataset.features
```

```python out
{'sentence1': Value(dtype='string', id=None),
 'sentence2': Value(dtype='string', id=None),
 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
 'idx': Value(dtype='int32', id=None)}
```

เราจะเห็นเบื้องหลังของ `label` ว่าเป็นข้อมูลชนิด `ClassLabel` โดยข้อมูลการ mapping integers เข้ากับชื่อ label นั้นเก็บอยู่ในโฟลเดอร์ *names* โดย `0` จะตรงกับ `not_equivalent` และ `1` ตรงกับ `equivalent`

<Tip>

✏️ **ลองเลย!** ลองดูที่ element 15 ของ training set และ element 87 ของ validation set ว่ามี label เป็นอะไร?

</Tip>

### การประมวลผลชุดข้อมูล

{#if fw === 'pt'}
<Youtube id="0u3ioSwev3s"/>
{:else}
<Youtube id="P-rZWqcB6CE"/>
{/if}

ในขั้นตอนการประมวลผลชุดข้อมูล เราจะต้องแปลงตัวอักษรให้กลายเป็นตัวเลข เพื่อให้โมเดลสามารถทำความเข้าใจได้ ดังที่คุณได้เห็นแล้วใน [previous chapter](/course/chapter2) ขั้นตอนการแปลงนี้สามารถทำได้โดยใช้ tokenizer โดยเราสามารถป้อนข้อมูลเข้า tokenizer เพียงแค่หนึ่งประโยค หรือจะป้อนข้อมูลเป็น list ของประโยคทั้งหมดเลยก็ได้ เราสามารถ tokenize ทั้งประโยคแรกและประโยคที่สองในแต่ละคู่ประโยคทุกคู่ได้ดังนี้:

```py
from transformers import AutoTokenizer

checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
```

อย่างไรก็ตาม การส่งเพียงข้อมูลสองลำดับ (sequences) ในลักษณะนี้เข้าไปยังไม่เพียงพอที่จะทำให้โมเดลสามารถเรียนรู้และทำนายว่าประโยคทั้งสองนี้เป็นประโยคที่เกิดจากการถอดความ (paraphrase) หรือไม่ เราจะต้องจัดการให้ประโยคทั้งสองเป็นคู่กันก่อนแล้วค่อยทำการประมวลผลให้เหมาะสม ซึ่งโชคดีมากที่ tokenizer สามารถรับข้อมูลคู่ของลำดับแล้วเตรียมข้อมูลให้อยู่ในรูปแบบที่เหมาะสมกับการป้อนเข้าโมเดล BERT ของเรา:

```py
inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
```

```python out
{ 
  'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
  'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
  'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
```

เราได้อธิบายเกี่ยวกับ keys ที่ชื่อ `input_ids` และ `attention_mask` ไปแล้วใน [Chapter 2](/course/chapter2) แต่เรายังไม่ได้พูดถึง `token_type_ids` ซึ่งในตัวอย่างนี้ ตัว token_type_ids นี่เองที่เป็นตัวบอกโมเดลว่าส่วนไหนของ input ที่เป็นประโยคแรก และส่วนไหนที่เป็นประโยคที่สอง

<Tip>

✏️ **ลองเลย!** ลองเลือก element 15 ของ training set มาลอง tokenize ประโยคทั้งสองแยกกันทีละประโยค และลอง tokenize เป็นคู่มาเทียบกันดู การ tokenize สองแบบนี้ให้ผลลัพธ์ที่ต่างกันอย่างไร?

</Tip>

ถ้าเรา decode ข้อมูล IDs ที่อยู่ใน `input_ids` กลับไปเป็นคำ:

```py
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
```

เราจะได้ผลลัพธ์:

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
```

เราจะเห็นได้ว่าถ้าเราจะป้อนข้อมูลเข้าไปทีละสองประโยค โมเดลจะต้องการรับข้อมูลในรูปของ `[CLS] ประโยคที่หนึ่ง [SEP] ประโยคที่สอง [SEP]` ซึ่งถ้าเราไปเรียงให้ตรงกับ `token_type_ids` เราจะได้: 

```python out
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
[      0,      0,    0,     0,       0,          0,   0,       0,      1,    1,     1,        1,     1,   1,       1]
```

คุณจะเห็นได้ว่า input ในส่วนที่ตรงกับ `[CLS] ประโยคที่หนึ่ง [SEP]` จะมี token type ID มีค่าเป็น 0 ทั้งหมด ในขณะที่ input ส่วนที่เหลือซึ่งตรงกับ `ประโยคที่สอง [SEP]` จะมี token type ID มีค่าเป็น 1 ทั้งหมด

ควรระวังไว้ว่า ถ้าคุณเลือก checkpoint อื่น ผลลัพธ์จากการ tokenize อาจจะไม่มี token_type_ids อยู่ด้วยก็ได้ (ยกตัวอย่างเช่น ถ้าคุณเลือกโมเดล DistilBERT ผลลัพธ์จากการ tokenize จะไม่มี token_type_ids) การ tokenize จะให้ token_type_ids ออกมาก็ต่อเมื่อโมเดลนั้นรู้ว่าต้องจัดการกับมันอย่างไร เพราะโมเดลเคยเห็นข้อมูลนี้มาแล้วในช่วง pretraining

ในตัวอย่างนี้ โมเดล BERT ผ่านการ pretrain มาด้วย token type IDs แล้ว และนอกเหนือไปจากเป้าหมายในการเทรนให้โมเดลสามารถเติมคำที่ถูกปิดไว้ (masked langauge modeling objective) ที่เราได้คุยกันใน [Chapter 1](/course/chapter1) โมเดล BERT ยังมีอีกเป้าหมายหนึ่งที่เรียกว่า _next sentence prediction_ (การทำนายประโยคถัดไป) โดยมีเป้าหมายในการทำแบบจำลองความสัมพันธ์ระหว่างคู่ของประโยคต่าง ๆ 

ในการทำให้โมเดลสามารถบรรลุเป้าหมายการทำนายประโยคถัดไป ได้มีการป้อนคู่ของประโยคที่ถูกปิดไว้อย่างสุ่มจำนวนมาก (pairs of sentences with randomly masked tokens) เข้าไปในโมเดล แล้วให้โมเดลทำนายว่าประโยคที่สองเป็นประโยคที่ตามหลังประโยคแรกหรือไม่ เพื่อไม่ให้โมเดลเรียนรู้เฉพาะประโยคที่เรียงตามกันเพียงอย่างเดียว จึงมีการแบ่งข้อมูลให้ครึ่งหนึ่งของคู่ประโยคทั้งหมด เป็นประโยคที่เรียงตามกันจริง ๆ เหมือนในเอกสารต้นฉบับ และอีกครึ่งหนึ่งเป็นคู่ประโยคที่เกิดจากสองประโยคที่มาจากเอกสารคนละชิ้นกัน

โดยทั่วไปแล้ว คุณไม่ต้องกังวลว่าจะมีข้อมูล `token_type_ids` ในผลลัพธ์จากการ toknize หรือไม่ ตราบเท่าที่คุณเลือกให้ tokenizer และโมเดลใช้ checkpoint ตัวเดียวกัน เพราะถ้า tokenizer รู้ว่าต้องป้อนข้อมูลอะไรเข้าโมเดล ก็จะไม่เกิดปัญหาใด ๆ 

ตอนนี้เราก็ได้เห็นแล้วว่า tokenizer ของเราสามารถรับข้อมูลคู่ประโยคเพียงคู่เดียวก็ได้ หรือสามารถ tokenize คู่ประโยคทั้งหมดที่มีอยู่ในชุดข้อมูลของเราเลยก็ได้: เหมือนกับที่เราทำใน [previous chapter](/course/chapter2) เราสามารถป้อนข้อมูลเป็น list ของคู่ประโยคต่าง ๆ เข้าไปใน tokenizer ได้ โดยป้อนข้อมูล list ของประโยคแรก แล้วตามด้วย list ของประโยคที่สอง และยังสามารถทำการเติมและตัด (padding and truncation) เหมือนกับที่เราทำใน [Chapter 2](/course/chapter2) ได้ เราอาจจะเขียนคำสั่งในการประมวลผลชุดข้อมูล training set ได้ดังนี้:

```py
tokenized_dataset = tokenizer(
    raw_datasets["train"]["sentence1"],
    raw_datasets["train"]["sentence2"],
    padding=True,
    truncation=True,
)
```

ซึ่งการเขียนคำสั่งแบบนี้ก็ได้ผลลัพธ์ที่ถูกต้อง แต่จะมีจุดด้อยคือการทำแบบนี้จะได้ผลลัพธ์ออกมาเป็น dictionary (โดยมี keys ต่าง ๆ คือ  `input_ids`, `attention_mask` และ `token_type_ids` และมี values เป็น lists ของ lists) วิธีการนี้จะใช้การได้ก็ต่อเมื่อคอมพิวเตอร์ของคุณมี RAM เพียงพอที่จะเก็บข้อมูลของทั้งชุดข้อมูลในตอนที่ทำการ tokenize ชุดข้อมูลทั้งหมด (ในขณะที่ dataset ที่ได้จากไลบรารี่ 🤗 Datasets จะเป็นไฟล์ [Apache Arrow](https://arrow.apache.org/) ซึ่งจะเก็บข้อมูลไว้ใน disk คุณจึงสามารถโหลดเฉพาะข้อมูลที่ต้องการมาเก็บไว้ใน memory ได้)

เพื่อให้ข้อมูลของเรายังเป็นข้อมูลชนิด dataset เราจะใช้เมธอด [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) ซึ่งจะช่วยให้เราเลือกได้ว่าเราจะทำการประมวลผลอื่น ๆ นอกเหนือจากการ tokenize หรือไม่ โดยเมธอด `map()` ทำงานโดยการเรียกใช้ฟังก์ชั่นกับแต่ละ element ของชุดข้อมูล เรามาสร้างฟังก์ชั่นสำหรับ tokenize ข้อมูลของเรากันก่อน:  

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

ฟังก์ชั่นนี้จะรับ dictionary (เหมือนกับแต่ละ item ของชุดข้อมูลของเรา) และให้ผลลัพธ์เป็น dictionary ตัวใหม่ที่มี keys เป็น `input_ids`, `attention_mask` และ `token_type_ids` ควรสังเกตว่าถึงแม้ `example` dictionary จะประกอบไปด้วยข้อมูลหลายชุด (แต่ละ key เป็น list ของประโยคต่าง ๆ ) ฟังก์ชั่นนี้ก็ยังทำงานได้ เนื่องจาก `tokenizer` สามารถรับข้อมูลเป็น list ของคู่ประโยคต่าง ๆ ได้ดังที่ได้เห็นแล้วข้างต้น และการเขียนฟังก์ชั่นแบบนี้ยังทำให้เราสามารถใช้ตัวเลือก `batched=True` ตอนที่เราเรียกใช้เมธอด `map()` ได้อีกด้วย ซึ่งจะช่วยให้การ tokenize เร็วขึ้นอย่างมาก เนื่องจาก tokenizer ในไลบรารี่ [🤗 Tokenizers](https://github.com/huggingface/tokenizers) นั้นเขียนโดยใช้ภาษา Rust ซึ่งจะทำงานได้รวดเร็วมากหากคุณป้อนข้อมูลเข้าไปจำนวนมากพร้อม ๆ กัน

ควรสังเกตว่าเรายังไม่ได้ใส่อากิวเมนต์ `padding` เข้ามาในฟังก์ชั่น tokenize ของเราตอนนี้ เนื่องจากการเติม (padding) ข้อมูลทุก ๆ ตัวอย่างให้มีความยาวเท่ากับประโยคที่มีความยาวมากสุดนั้นไม่ค่อยมีประสิทธิภาพเท่าไรนัก วิธีการที่ดีกว่าคือให้เราเติม (pad) ข้อมูลเมื่อเรากำลังสร้าง batch ขึ้นมา ซึ่งเราก็จะต้องเติมให้ข้อมูลมีความยาวเท่ากับประโยคที่ยาวที่สุดใน batch นั้น ๆ ก็พอ ไม่จำเป็นต้องเติมให้ยาวเท่ากับประโยคที่ยาวที่สุดในทั้งชุดข้อมูล การทำเช่นนี้จะช่วยประหยัดเวลาและพลังในการประมวลผลได้อย่างมาก แม้ input ของเราจะมีความยาวที่แตกต่างกันมากก็ตาม!  
 
ต่อไปนี้คือวิธีการใช้ฟังก์ชั่น tokenize ให้ทำงานกับข้อมูลใน dataset ทุกชุดของเราในคราวเดียว โดยเราจะใส่ `batched=True` ตอนที่ call เมธอด `map` เพื่อให้ฟังก์ชั่นทำงานกับ elements หลาย ๆ ตัวใน dataset ของเราในคราวเดียว (ไม่ได้ทำทีละ element แยกกัน) ซึ่งการทำเช่นนี้จะช่วยให้เราประมวลผลข้อมูลได้เร็วขึ้นมาก

```py
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets
```

ไลบรารี่ 🤗 Datasets จะทำการประมวลผลนี้โดยการเพิ่ม fields ใหม่เข้าไปยัง datasets ของเรา โดยเพิ่ม field ให้กับแต่ละ key ของ dictionary ที่ได้ออกมาจากฟังก์ชั่นประมวลผลของเรา

```python out
DatasetDict({
    train: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 3668
    })
    validation: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 408
    })
    test: Dataset({
        features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
        num_rows: 1725
    })
})
```

นอกจากนี้คุณยังสามารถใช้ multiprocessing ตอนที่คุณใช้ฟังก์ชั่น preprocess ของคุณกับ `map()` ได้โดยการใส่อากิวเมนต์ `num_proc` แต่ที่เราไม่ได้ทำให้ดูตรงนี้ เนื่องจากไลบรารี่ 🤗 Tokenizers นั้นมีการใช้ multiple threads เพื่อให้การ tokenize ตัวอย่างของเราเร็วขึ้นอยู่แล้ว แต่ถ้าคุณไม่ได้ใช้ fast tokenizer ที่เขียนไว้ในไลบรารี่นี้ การใช้ multiprocessing ก็อาจจะช่วยให้การประมวลผลชุดข้อมูลของคุณเร็วขึ้นได้

`tokenize_function` ของเราให้ผลลัพธ์เป็น dictionary โดยมี keys ต่าง ๆ ได้แก่ `input_ids`, `attention_mask` และ `token_type_ids` เพื่อให้ทั้งสาม field นี้ถูกเพิ่มเข้าไปใน dataset ทั้งสาม split คุณควรจำไว้ว่าเราอาจจะเปลี่ยน filed ที่มีอยู่แล้วก็ได้ ถ้าหากว่าคุณเลือกเขียนฟังก์ชั่นให้เปลี่ยนค่าใน key เดิมใน dataset ที่เราจะทำการ map และให้ฟังก์ชั่น return ค่าใหม่ออกมา

ขั้นตอนสุดท้ายที่ต้องทำก็คือการเติมชุดข้อมูลตัวอย่างของเราให้มีความยาวเท่ากับข้อมูลตัวที่มีความยาวมากที่สุดใน batch ซึ่งเทคนิคเราจะเรียกว่า *dynamic padding* (การเติมแบบพลวัต)

### Dynamic padding (การเติมแบบพลวัต)

<Youtube id="7q5NyFT8REg"/>

{#if fw === 'pt'}
ฟังก์ชั่นที่ทำหน้าที่เก็บข้อมูลตัวอย่างเข้ามาทำเป็น batch เรียกว่า *collate function* ซึ่งเป็นอากิวเมนต์ที่คุณสามารถใส่เพิ่มได้เมื่อคุณสร้าง `DataLoader` โดยการตั้งค่าเริ่มต้นจะเป็นฟังก์ชั่นที่ทำหน้าที่เพียงแปลงข้อมูลตัวอย่างของคุณให้เป็น Pytorch tensors และนำมา concatenate ต่อกัน (แบบ recursive ถ้าหากคุณป้อนข้อมูลเป็น lists, tuples หรือ dictionaries) ซึ่งในกรณีตัวอย่างของเรานี้จะทำแบบนั้นไม่ได้ เนื่องจากข้อมูลป้อนเข้าแต่ละตัวของเรามีขนาดไม่เท่ากัน ซึ่งเราก็ได้จงใจที่ยังไม่ทำการเติม (padding) มาจนถึงตอนนี้ เพื่อที่จะทำการเติมเท่าที่จำเป็นต้องทำในแต่ละ batch เพื่อหลีกเลี่ยงการเติมข้อมูลให้มีความยาวเกินจำเป็น ซึ่งการทำแบบนี้จะช่วยให้การ training เร็วขึ้นค่อนข้างมาก แต่ควรระวังไว้ว่าถ้าคุณ train บน TPU การทำแบบนี้อาจสร้างปัญหาได้ เนื่องจาก TPUs นั้นชอบข้อมูลที่มี shape คงที่มากกว่า แม้ว่าจะต้องเติมข้อมูลให้ยาวมากก็ตาม

{:else}

ฟังก์ชั่นที่ทำหน้าที่เก็บข้อมูลตัวอย่างเข้ามาทำเป็น batch เรียกว่า *collate function* ซึ่งมีการตั้งค่าเริ่มต้นเป็นฟังก์ชั่นที่ทำหน้าที่เพียงแปลงข้อมูลตัวอย่างของคุณให้เป็น tf.Tensor และนำมา concatenate ต่อกัน (แบบ recursive ถ้าหากคุณป้อนข้อมูลเป็น lists, tuples หรือ dictionaries) ซึ่งในกรณีตัวอย่างของเรานี้จะทำแบบนั้นไม่ได้ เนื่องจากข้อมูลป้อนเข้าแต่ละตัวของเรามีขนาดไม่เท่ากัน ซึ่งเราก็ได้จงใจที่ยังไม่ทำการเติม (padding) มาจนถึงตอนนี้ เพื่อที่จะทำการเติมเท่าที่จำเป็นต้องทำในแต่ละ batch เพื่อหลีกเลี่ยงการเติมข้อมูลให้มีความยาวเกินจำเป็น ซึ่งการทำแบบนี้จะช่วยให้การ training เร็วขึ้นค่อนข้างมาก แต่ควรระวังไว้ว่าถ้าคุณ train บน TPU การทำแบบนี้อาจสร้างปัญหาได้ เนื่องจาก TPUs นั้นชอบข้อมูลที่มี shape คงที่มากกว่า แม้ว่าจะต้องเติมข้อมูลให้ยาวมากก็ตาม

{/if}

ในทางปฏิบัติแล้ว เราจะต้องสร้างฟังก์ชั่น collate ที่จะทำการเติมข้อมูลในแต่ละ batch ของ dataset ด้วยจำนวนที่ถูกต้อง ซึ่งโชคดีที่ไลบรารี่ 🤗 Transformers ได้เตรียมฟังก์ชั่นนี้ไว้ให้แล้วในโมดูล `DataCollatorWithPadding` โดยจะรับข้อมูลเป็น tokenier (เพื่อให้รู้ว่าจะต้องเติมด้วย paddin token อะไร และเพื่อให้รู้ว่าโมเดลคาดหวังว่าจะต้องเติมไปทางซ้ายหรือทางขวามือของข้อมูล) และจะทำขั้นตอนทุกอย่างที่คุณต้องการ:

{#if fw === 'pt'}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```
{:else}
```py
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")
```
{/if}

เพื่อจะทดสอบของเล่นชิ้นใหม่นี้ ลองเลือกข้อมูลบางส่วนจากชุดข้อมูล training ของเรามาทดลองสร้างเป็น batch ซึ่งตรงนี้เราจะเอาคอลัมน์ idx, sentence1 และ sentence2 ออกไปเนื่องจากเราไม่จำเป็นต้องใช้ อีกทั้งคอลัมน์เหล่านี้ยังมี strings (ซึ่งเราไม่สามารถใช้ strings ในการสร้าง tensor ได้) แล้วลองดูความยาวของข้อมูลแต่ละตัวใน batch ของเรา:

```py
samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
[len(x) for x in samples["input_ids"]]
```

```python out
[50, 59, 47, 67, 59, 50, 62, 32]
```

เราเลือกได้ข้อมูลที่มีความยาวต่าง ๆ กัน ตั้งแต่ 32 ไปถึง 67 (ซึ่งก็ไม่น่าประหลาดใจอะไร) การทำ Dynamic padding ควรที่จะเติมข้อมูลทุกตัวใน batch นี้ให้มีความยาวเท่ากันเท่ากับ 67 (ซึ่งเป็นความยาวของข้อมูลที่ยาวที่สุดใน batch นี้) ถ้าไม่มีการทำ dynamic padding เราก็จะต้องเติมข้อมูลให้ยาวเท่ากับข้อมูลที่ยาวที่สุดใน dataset หรือไม่ก็เท่ากับความยาวสูงสุดที่โมเดลจะรับได้ ลองมาตรวจสอบกันดูว่า `data_collator` ของเรานั้นได้ทำการเติมแบบพลวัตให้กับข้อมูลใน batch ของเราอย่างถูกต้องเหมาะสม:  

```py
batch = data_collator(samples)
{k: v.shape for k, v in batch.items()}
```

{#if fw === 'tf'}

```python out
{'attention_mask': TensorShape([8, 67]),
 'input_ids': TensorShape([8, 67]),
 'token_type_ids': TensorShape([8, 67]),
 'labels': TensorShape([8])}
```

{:else}

```python out
{'attention_mask': torch.Size([8, 67]),
 'input_ids': torch.Size([8, 67]),
 'token_type_ids': torch.Size([8, 67]),
 'labels': torch.Size([8])}
```

ผลลัพธ์ออกมาดูดีเลย! ตอนนี้เราก็จัดการข้อมูลจาก raw text ให้เป็นชุดของ batch ที่โมเดลทำความเข้าใจได้แล้ว เราพร้อมที่จะ fine-tune แล้ว!

{/if}

<Tip>

✏️ **ลองเลย!** ลองทำการประมวลผลแบบนี้กับชุดข้อมูล GLUE SST-2 ดู มันจะต่างจากตัวอย่างนี้เล็กน้อย เนื่องจากชุดข้อมูลนั้นประกอบไปด้วยประโยคเดียวแทนที่จะเป็นคู่ประโยค แต่ส่วนที่เหลือก็เหมือนกัน ถ้าอยากลองความท้าทายที่ยากขึ้นไปอีก ให้ลองเขียนฟังก์ชั่นประมวลผลที่ใช้กับ GLUE tasks ได้ทุก task ดูสิ

</Tip>

{#if fw === 'tf'}

ตอนนี้เราก็ได้ dataset และ data collator แล้ว เราจะต้องนำมันมาต่อเข้าด้วยกัน โดยเราอาจจะโหลด batch และ collate มันแบบ manual ก็ได้ แต่นั่นเป็นงานที่หนักมากและไม่ค่อยมีประสิทธิภาพนัก เราอาจเลือกใช้เมธอด `to_tf_dataset()` ในการแก้ปัญหานี้อย่างมีประสิทธิภาพ โดยเมธอดนี้จะ wrap `tf.data.Dataset` เข้ากับ dataset ของคุณและคุณสามารถใส่ collation function เข้าไปด้วยได้ โดย `tf.data.Dataset` นั้นเป็น native TensorFlow format ซึ่ง Keras สามารถใช้ร่วมกับ `model.fit()` ได้ ดังนั้นเมธอดนี้จะแปลง 🤗 Dataset ให้เป็น format ที่พร้อมสำหรับการ training แล้ว ลองมาดูการเทรนโมเดลด้วย dataset ของเรากันเลย! 

```py
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,
)
```

เสร็จเรียบร้อย! เราสามารถนำ datasets พวกนี้ไปใช้ในบทเรียนต่อไปของเราได้เลย โดยการ training นั้นค่อนข้างตรงไปตรงมาไม่ซับซ้อนหลังจากที่เราทำงานอย่างหนักไปกกับการประมวลผลข้อมูลแล้ว

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter3/2.mdx" />

### เรียนจบเรื่อง tokenizer แล้ว!
https://huggingface.co/learn/course/th/chapter6/9.md

# เรียนจบเรื่อง tokenizer แล้ว!

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

เยี่ยมมาก คุณเรียนจบบทนี้แล้ว!

หลังจากที่ได้เรียนเกี่ยวกับ tokenizer อย่างละเอียดแล้ว คุณจะ :

- สามารถเทรน tokenizer ตัวใหม่ จาก tokenizer อีกตัวที่มีโครงสร้างอยู่แล้ว
- เข้าใจวิธีการใช้ค่า offsets เพื่อ map ตำแหน่งของ token ไปหาค่าช่วงตำแหน่งของมัน(span)ในข้อความหลัก
- รู้ความแตกต่างระหว่าง BPE, WordPiece, และ Unigram
- สามารถผสมผสานแต่ละเครื่องมือจาก 🤗 Tokenizers library เพื่อสร้าง tokenizer ของคุณเองได้
- สามารถนำ tokenizer นั้นไปใช้ใน 🤗 Transformers library ได้


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/9.mdx" />

### Byte-Pair Encoding tokenization
https://huggingface.co/learn/course/th/chapter6/5.md

# Byte-Pair Encoding tokenization

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section5.ipynb"},
]} />

ดั้งเดิมแล้ว Byte-Pair Encoding (BPE) เป็นอัลกอริทึมที่ถูกสร้างเพื่อใช้บีบอัดข้อความให้เล็กลง (compress texts) ภายหลัง OpenAI ได้นำอัลกอริทึมนี้มาใช้ในการตัดคำ ในขั้นตอนเตรียมข้อมูลเพื่อเทรน GPT อัลกอริทึมตัวนี้ยังถูกนำมาใช้อย่างกว้างขวางกับโมเดลประเภท Transformer เช่น GPT, GPT-2, RoBERTa, BART, และ DeBERTa

<Youtube id="HEikzVL-lZU"/>

<Tip>

💡 บทนี้จะพูดถึง BPE อย่างละเอียด เราจะเจาะลึกถึงไปถึงการ implement อัลกอริทึมนี้ คุณสามารถข้ามไปตอนท้ายได้ ถ้าคุณสนใจเพียงแค่ภาพรวมคร่าวๆเท่านั้น

</Tip>

## อัลกอริทึมที่ใช้ในการเทรน

BPE เริ่มการเทรนด้วยการคำนวณรายการของคำที่อยู่ในคลังข้อมูล (คลังข้อมูลจะต้องผ่านการ normalization และ pre-tokenization มาแล้ว) จากนั้นมันจะเริ่มสร้างชุดคำศัพท์ (vocabulary) จากตัวอักษรที่อยู่ในแต่ละคำ มาดูตัวอย่างง่ายๆกัน เราจะสมมติว่าคลังข้อมูลของเรามีเพียงห้าคำเท่านั้น :

```
"hug", "pug", "pun", "bun", "hugs"
```

vocabulary ตั้งต้นสำหรับชุดข้อมูลนี้คือ `["b", "g", "h", "n", "p", "s", "u"]`  ในการใช้งานจริง vocabulary ตั้งต้นจะประกอบด้วยตัวอักษร ASCII เป็นอย่างต่ำ หรืออาจจะมีตัวอักษร Unicode ได้ด้วย

ถ้าข้อความใหม่ที่คุณต้องการจะตัดคำ มีสัญลักษณ์ที่ไม่ได้อยู่ใน training corpus สัญลักษณ์พวกนี้จะถูกแปลงเป็น unknown token นี่เป็นเหตุผลว่าทำไมโมเดล NLP จึงประมวลผลข้อความที่มีอีโมจิได้ไม่ดีนัก

<Tip>

Tokenizer ของ GPT-2 และ RoBERTa (ซึ่งค่อนข้างคล้ายกัน) มีวิธีการจัดการกับปัญหานี้ได้อย่างประสิทธิภาพ มันจะไม่มองแต่ละคำเป็น Unicode แต่จะมองว่าเป็น byte การทำแบบนี้ทำให้ vocabulary ตั้งต้น มีขนาดที่เล็ก (256) แต่ยังสามารถบันทึกทุกๆสัญลักษณ์ได้ โดยไม่ต้องแปลงสัญลักษณ์พิเศษต่างๆเป็น unknown token เทคนิคนี้เรียกว่า *byte-level BPE*

</Tip>

หลังจากสร้าง vocabulary ตั้งต้นแล้ว เราจะเพิ่ม token ใหม่ๆ เข้าไปจนว่าจะได้ vocabulary ขนาดใหญ่พอกับที่เราต้องการ โดยเราจะเทรน BPE ให้เรียน กฎที่เรียกว่า *merges* ซึ่งเป็นกฎสำหรับการรวมสองหน่วยใน vocabulary เข้าด้วยกัน
ตอนช่วงเริ่มต้น กฎ merges พวกนี้จะสร้างคำย่อยที่ประกอบด้วยตัวอักษรสองตัว ระหว่างที่เราเทรนต่อไปเรื่อยๆ คำย่อยที่ได้ก็จะยาวขึ้น
ในแต่ละรอบของการเทรน BPE จะคำนวณหาคู่ของคำย่อยที่พบบ่อยที่สุด (คู่ของคำย่อย ในที่นี้เราหมายถึง token ที่อยู่ติดกัน)
คู่ที่มีความถี่มากที่สุดจะถูกรวมเข้าด้วยกัน จากนั้นโมเดลจะทำแบบเดิมอีกในการเทรนรอบต่อไป

กลับมาที่ตัวอย่างของเรา สมมติว่าแต่ละคำมีความถี่ดังต่อไปนี้ :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

ซึ่งแปลว่า `"hug"` พบ 10 ครั้งใน corpus, `"pug"` พบ 5 ครั้ง, `"pun"` พบ 12 ครั้ง, `"bun"` พบ 4 ครั้ง, และ `"hugs"` พบ 5 ครั้ง

เราจะเริ่มการเทรน โดยแยกแต่ละคำออกเป็นตัวอักษร (ตัวอักษรจะต้องมาจาก vocabulary ตั้งต้นที่เราสร้างมาก่อนหน้านี้แล้ว) ตอนนี้คุณจะเห็นว่าแต่ละคำถูกแปลงเป็น list ที่ประกอบไปด้วยหลายๆ token

```
("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)
```

จากนั้น เราจะดูทีละคู่ ตัวอย่างเช่น คู่ `("h", "u")` ซึ่งมาจากคำว่า `"hug"` และ `"hugs"` และพบ 15 ครั้งใน corpus
อย่างไรก็ตาม คู่นี้ไม่ใช่คู่ที่พบบ่อยที่สุด คู่ที่พบบ่อยที่สุดคือ `("u", "g")` ซึ่งพบใน คำว่า `"hug"`, `"pug"`, และ `"hugs"` ซึ่งความถี่รวมของมันคือ 20 ครั้ง
ดังนั้น กฎแรกของการ merge ที่ tokenizer เรียนคือ `("u", "g") -> "ug"` แปลว่ามันจะเพิ่ม `"ug"` เข้าไปใน vocabulary และใน corpus คู่นี้ก็จะถูกรวมเป็น token เดียวด้วย

หลังจากขั้นตอนนี้ vocabulary และ corpus จะมีค่าดังนี้ :

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)
```

ตอนนี้ จะเห็นว่าเรามีคู่ที่เมื่อรวมกันจะได้ token ที่ยาวกว่าสองตัวอักษร ตัวอย่างเช่น คู่ `("h", "ug")` ซึ่งพบ 15 ครั้งใน corpus
อย่างไรก็ตาม คู่ที่พบบ่อยที่สุดคือ `("u", "n")` ซึ่งพบ 16 ครั้ง ดังนั้นกฎที่สองก็คือ `("u", "n") -> "un"` หลังจากที่เราเพิ่มคู่นี้ไปใน vocabulary และ merge token ใน corpus เข้าด้วยกันแล้ว เราจะได้ :

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5)
```

ตอนนี้ คู่ที่พบบ่อยที่สุดคือ `("h", "ug")` ดังนั้นกฎที่ได้คือ `("h", "ug") -> "hug"` ซึ่งจะทำให้เราได้ token ที่มีสามตัวอักษร หลังจากการ merge เราจะได้ :

```
Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]
Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)
```

เราจะทำแบบนี้ต่อไปเรื่อยๆ จนกว่าจะได้ขนาดของ vocabulary ที่ต้องการ

<Tip>

✏️ **ตาคุณแล้ว!** คุณคิดว่ากฎ merge ต่อไปคืออะไร

</Tip>

## Tokenization algorithm

การ tokenization เป็นขั้นตอนหลังจากการเทรน โดย input ใหม่จะถูก tokenize ด้วยขั้นตอนดังต่อไปนี้

1. Normalization (การปรับข้อความให้เป็นมาตรฐาน)
2. Pre-tokenization (การเตรียมข้อความให้พร้อมสำหรับการ tokenize จริง)
3. แยกคำออกเป็นตัวอักษรเดี่ยว
4. ใช้กฎ merge ที่ได้จากการเทรนเพื่อรวมตัวอักษรที่เราได้จากขั้นตอนก่อนหน้า

มาดูกฎสามตัวที่เราได้จากการเทรนก่อนหน้านี้ :

```
("u", "g") -> "ug"
("u", "n") -> "un"
("h", "ug") -> "hug"
```

คำว่า`"bug"` จะถูกแยกเป็น `["b", "ug"]` ส่วนคำว่า `"mug"` จะถูกแยกเป็น `["[UNK]", "ug"]` เพราะว่า `"m"` ไม่ได้อยู่ใน vocabulary ของเรา
้เช่นเดียวกัน คำว่า `"thug"` จะถูกแยกเป็น `["[UNK]", "hug"]` เพราะว่า `"t"` ไม่ได้อยู่ใน vocabulary กฎแรกจะรวม `"u"` และ `"g"` เข้าด้วยกัน จากนั้น `"hu"` และ `"g"` ก็จะถูกรวมเข้าด้วยกัน

<Tip>

✏️ **ตาคุณแล้ว!** คุณคิดว่าคำว่า `"unhug"` จะถูกแยกอย่างไร

</Tip>

## การสร้าง BPE (Implementing BPE)

ตอนนี้เราจะมาดูกันว่า คุณจะสามารถ implement อัลกอริทึม BPE ได้อย่างไร สิ่งที่เราจะเรียนต่อไปนี้ไม่ใช่ implementation ที่ดีที่สุด เราเพียงต้องการให้คุณเข้าใจโค้ดและเข้าใจว่า BPE ทำงานอย่างไร

อันดับแรก เราต้องการ corpus ดังนั้น เราจะสร้าง corpus แบบง่ายๆขึ้นมา โดยประกอบไปด้วยไม่กี่ประโยค :

```python
corpus = [
    "This is the Hugging Face course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

จากนั้นเราจะทำการ pre-tokenize corpus นี้ เพื่อแยกข้อความออกเป็นคำๆ เนื่องจากเราจะสร้าง BPE tokenizer ตามตัวที่ใช้ใน GPT-2 เราจึงต้องใช้ `gpt2` tokenizer ในการ pre-tokenize

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

จากนั้นเราจะคำนวณความถี่ของแต่ละคำ:

```python
from collections import defaultdict

word_freqs = defaultdict(int)

for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

print(word_freqs)
```

```python out
defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1,
    'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1,
    'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1,
    'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1})
```

ขั้นตอนต่อไป คือการคำนวณ vocabulary ตั้งต้น ซึ่งสร้างจากแต่ละตัวอักษรใน corpus :

```python
alphabet = []

for word in word_freqs.keys():
    for letter in word:
        if letter not in alphabet:
            alphabet.append(letter)
alphabet.sort()

print(alphabet)
```

```python out
[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's',
  't', 'u', 'v', 'w', 'y', 'z', 'Ġ']
```

เราจะเพิ่ม token พิเศษเข้าไปในข้างหน้า list นี้ด้วย GPT-2 ใช้ token พิเศษคือ `"<|endoftext|>"` :

```python
vocab = ["<|endoftext|>"] + alphabet.copy()
```

จากนั้นเราจะแยกแต่ละคำใน corpus ให้เป็นตัวอักษร เพื่อที่เราจะได้เริ่มการเทรน :

```python
splits = {word: [c for c in word] for word in word_freqs.keys()}
```

ตอนนี้เราก็พร้อมที่จะเทรนแล้ว เราจะเริ่มด้วยการเขียนฟังก์ชันที่คำนวณความถี่ของแต่ละคู่ token :

```python
def compute_pair_freqs(splits):
    pair_freqs = defaultdict(int)
    for word, freq in word_freqs.items():
        split = splits[word]
        if len(split) == 1:
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            pair_freqs[pair] += freq
    return pair_freqs
```

มาดูส่วนผลลัพธ์ (ซึ่งเป็น dictionary) กัน :

```python
pair_freqs = compute_pair_freqs(splits)

for i, key in enumerate(pair_freqs.keys()):
    print(f"{key}: {pair_freqs[key]}")
    if i >= 5:
        break
```

```python out
('T', 'h'): 3
('h', 'i'): 3
('i', 's'): 5
('Ġ', 'i'): 2
('Ġ', 't'): 7
('t', 'h'): 3
```

จากนั้นเราจะหาคู่ที่พบบ่อยที่สุด ซึ่งทำได้ง่ายๆดังนี้ :

```python
best_pair = ""
max_freq = None

for pair, freq in pair_freqs.items():
    if max_freq is None or max_freq < freq:
        best_pair = pair
        max_freq = freq

print(best_pair, max_freq)
```

```python out
('Ġ', 't') 7
```

ดังนั้น กฎแรกที่เราได้ก็คือ `('Ġ', 't') -> 'Ġt'` และเราจะต้องเพิ่ม `'Ġt'` เข้าไปใน vocabulary :

```python
merges = {("Ġ", "t"): "Ġt"}
vocab.append("Ġt")
```

จากนั้น เราจะต้องทำการ merge คำย่อยที่อยู่ใน dictionary `splits` ด้วย โดยเราจะเขียนฟังก์ชันต่อไปนี้ :

```python
def merge_pair(a, b, splits):
    for word in word_freqs:
        split = splits[word]
        if len(split) == 1:
            continue

        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                split = split[:i] + [a + b] + split[i + 2 :]
            else:
                i += 1
        splits[word] = split
    return splits
```

และนี่ก็คือผลลัพธ์จากการ merge ครั้งแรก :

```py
splits = merge_pair("Ġ", "t", splits)
print(splits["Ġtrained"])
```

```python out
['Ġt', 'r', 'a', 'i', 'n', 'e', 'd']
```

ตอนนี้เราก็มีทุกอย่างพร้อมสำหรับการเทรนแล้ว เราจะเทรนจนกว่าขนาดของ vocabulary จะเท่ากับ 50 :

```python
vocab_size = 50

while len(vocab) < vocab_size:
    pair_freqs = compute_pair_freqs(splits)
    best_pair = ""
    max_freq = None
    for pair, freq in pair_freqs.items():
        if max_freq is None or max_freq < freq:
            best_pair = pair
            max_freq = freq
    splits = merge_pair(*best_pair, splits)
    merges[best_pair] = best_pair[0] + best_pair[1]
    vocab.append(best_pair[0] + best_pair[1])
```

ผลลัพธ์ที่ได้คือ tokenizer ของเราได้เรียน 19 กฎ (vocabulary ตั้งต้นมี 31 token ซึ่งมาจากตัวอักษรที่เรามี 30 ตัวและ token พิเศษอีกหนึ่งตัว) :

```py
print(merges)
```

```python out
{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en',
 ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok',
 ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe',
 ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'}
```

ส่วน vocabulary ที่ได้จะประกอบไปด้วย token พิเศษ, ตัวอักษรตั้งต้น, และผลลัพธ์จากการ merge แต่ละครั้ง :

```py
print(vocab)
```

```python out
['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o',
 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se',
 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni']
```

<Tip>

💡 ถ้าคุณใช้ `train_new_from_iterator()` กับ corpus เดียวกันนี้ คุณจะไม่ได้ vocabulary เดียวกัน เพราะว่าอาจจะมีหลายคู่ token ที่มีความถี่สูงสุดเท่ากัน ในตัวอย่างของเรา เราเลือกคู่แรกที่โค้ดของเราอ่านเจอ ส่วน 🤗 Tokenizers library เลือกคู่แรกโดยเรียงตาม ID

</Tip>

หากเราต้องการ tokenize ข้อความใดข้อความหนึ่ง สิ่งที่ต้องทำคือ pre-tokenize จากนั้นจึงทำการ tokenize และสุดท้าย apply กฎ merge :

```python
def tokenize(text):
    pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in pre_tokenize_result]
    splits = [[l for l in word] for word in pre_tokenized_text]
    for pair, merge in merges.items():
        for idx, split in enumerate(splits):
            i = 0
            while i < len(split) - 1:
                if split[i] == pair[0] and split[i + 1] == pair[1]:
                    split = split[:i] + [merge] + split[i + 2 :]
                else:
                    i += 1
            splits[idx] = split

    return sum(splits, [])
```

คุณสามารถทดลองโค้ดนี้ได้กับข้อความทุกข้อความ ที่ประกอบไปด้วยตัวอักษรเท่านั้น :

```py
tokenize("This is not a token.")
```

```python out
['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.']
```

<Tip warning={true}>

⚠️ การ implementation ในตัวอย่างของเราจะ return error ถ้าโปรแกรมอ่านเจอตัวอักษรที่ไม่มีใน vocabulary นั่นเพราะว่าเราไม่ได้เขียนโค้ดเพื่อจัดการกับกรณีแบบนี้
ใน GPT-2 ปกติจะไม่มี unknown token แบบนี้ เพราะว่า ถ้าเราใช้ byte-level BPE เราจะไม่มีทางได้ตัวอักษรที่ unknown อย่างไรก็ตามในตัวอย่างของเรา เราไม่ได้ใช้ทุกๆ byte เพื่อสร้าง vocabulary ตั้งต้น
อย่างไรก็ตาม หัวข้อนี้นั้นค่อนข้างลึก เราจึงจะไม่ขอพูดถึงรายละเอียดไปมากกว่านี้

</Tip>

นี่ก็คือ อัลกอริทึม BPE ในบทต่อไป เราจะมาดู WordPiece กัน

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/5.mdx" />

### บทนำ
https://huggingface.co/learn/course/th/chapter6/1.md

# บทนำ

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

ใน[บทที่ 3](/course/chapter3) คุณได้เรียนเกี่ยวกับการ fine-tune โมเดลเพื่อนำไปใช้ในงานที่คุณต้องการ ตอนนั้นเราใช้ตัวตัดคำ(tokenizer)แบบเดียวกับตัวที่มากับโมเดล แต่หากคุณอยากจะเทรนโมเดลตั้งแต่เริ่มต้นเลย คุณควรจะเลือกใช้ตัวตัดคำแบบไหนดี
ในกรณีนี้ถ้าคุณใช้ตัวตัดคำที่เทรนจากคลังข้อมูล(corpus)ที่ไม่ใช่ภาษาเดียวกับโมเดลหรือคลังข้อมูลที่มาจากโดเมนอื่น(แปลว่าเนื้อหาของข้อมูลที่ใช้เทรนตัวตัดคำและใช้เทรนโมเดลมีความแตกต่างกันมาก)ก็จะไม่เหมาะสมนัก
ตัวอย่างเช่น ตัวตัดคำที่เทรนมาสำหรับตัดคำภาษาอังกฤษ เมื่อนำมาใช้เพื่อตัดคำภาษาญี่ปุ่นก็จะได้ผลลัพธ์ที่ไม่ดี เพราะว่าทั้งสองภาษามีการใช้ช่องว่าง(space)และเครื่องหมายวรรคตอน(punctuation)ที่ต่างกันมาก


ในบทนี้คุณจะได้เรียนเกี่ยวกับการเทรนตัวตัดคำจากคลังข้อความ(corpus of texts) เพื่อให้ได้ตัวตัดคำที่เหมาะสมกับ language model ที่คุณต้องการจะเทรน
เราจะใช้ library ที่ชื่อว่า [🤗 Tokenizers](https://github.com/huggingface/tokenizers) ซึ่งมีตัวตัดคำแบบ "เร็ว" ให้ผู้ใช้เลือกได้ ใน [🤗 Transformers](https://github.com/huggingface/transformers) library
เราจะมาดู features ต่างๆของ library นี้กันและมาเรียนรู้ว่าตัวตัดคำแบบเร็วและแบบช้านั้นต่างกันอย่างไร


หัวข้อที่เราจะเรียนกันในบทนี้:

* การสร้างตัวตัดคำขึ้นมาใหม่ให้คล้ายกับตัวที่ใช้ใน checkpoint โดนใช้ชุดข้อมูลใหม่ในการเทรน
* feature พิเศษของตัวตัดคำแบบเร็ว
* ความแตกต่างระหว่างอัลกอริทึม 3 แบบที่ใช้ในการสร้างตัวตัดคำประเภท subword ที่ใช้ใน NLP ทุกวันนี้
* การสร้างและเทรนตัวตัดคำตั้งแต่เริ่มต้นด้วย 🤗 Tokenizers library

เทคนิคต่างๆที่คุณจะได้เรียนในบทนี้จะเป็นเตรียมให้คุณพร้อมสำหรับ[บทที่ 7](/course/chapter7/6) ซึ่งคุณจะได้เรียนเกี่ยวกับการสร้าง language model ด้วย Python
เรามาเริ่มกันที่ความหมายของการ "เทรน" ตัวตัดคำ

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/1.mdx" />

### ความสามารถพิเศษของตัวตัดคำแบบเร็ว (fast tokenizers)
https://huggingface.co/learn/course/th/chapter6/3.md

# ความสามารถพิเศษของตัวตัดคำแบบเร็ว (fast tokenizers)

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section3_tf.ipynb"},
]} />

{/if}

ในบทนี้ เราจะเรียนเกี่ยวกับความสามารถของ tokenizer ต่างๆ ใน 🤗 Transformers library กัน
ในบทก่อนๆ คุณได้ลองใช้ tokenizer เพื่อแยกข้อความให้เป็นคำๆ และเพื่อแปลง ID ของคำให้กลับไปเป็นข้อความแล้ว
จริงๆแล้ว tokenizer นั้นยังมีความสามารถอีกหลายอย่าง โดยเฉพาะ tokenizer จาก 🤗 Tokenizers library

เพื่อให้คุณเห็นภาพได้อย่างชัดเจน เราจะมาลองคำนวณผลลัพธ์ (reproduce) ของ `token-classification` (ซึ่งเราจะเรียกสั้นๆว่า `ner`) และสร้าง pipeline สำหรับ `question-answering` อย่างที่คุณได้เรียนมาแล้ว[บทที่ 1](/course/chapter1)กัน


<Youtube id="g8quOxoqhHQ"/>

เราจะแยก tokenizer เป็นแบบช้า (slow) และแบบเร็ว (fast) ซึ่งแบบช้าหมายถึง tokenizer ที่เขียนด้วย Python และมาจาก 🤗 Transformers library ส่วนแบบเร็วหมายถึง tokenizer ที่เขียนด้วย Rust และมาจาก 🤗 Tokenizers library
ถ้าคุณยังจำตารางจาก[บทที่ 5](/course/chapter5/3)ได้ ซึ่งเป็นตารางเปรียบเทียบเวลา ที่ tokenizer แบบเร็วและช้า ใช้ในการตัดคำชุดข้อมูล Drug Review คุณก็จะเห็นว่า ทำไมเราจึงเรียกพวกมันว่า แบบช้าและเร็ว

                | Fast tokenizer | Slow tokenizer
:--------------:|:--------------:|:-------------:
`batched=True`  | 10.8s          | 4min41s
`batched=False` | 59.2s          | 5min3s

<Tip warning={true}>

⚠️ ถ้าคุณเปรียบเทียบ tokenizer ทั้งสองแบบ โดยดูจากความเร็วในการตัดคำของประโยคเดียว คุณอาจจะไม่เห็นความแตกต่างมาก และบางที fast tokenizer อาจจะช้ากว่า slow tokenizer ด้วยซ้ำ คุณจะเห็นความแตกต่างที่แท้จริง ก็เมื่อลองรันกับ input ที่มีขนาดใหญ่ระดับหนึ่ง เพราะการทำแบบนี้ จะทำให้การประมวลผลแบบ parallel ถูกเรียกใช้งาน

</Tip>

## Batch encoding

<Youtube id="3umI3tm27Vw"/>

output ที่ได้จากการตัดคำนั้นไม่ใช่ dictionary แต่เป็น Python object ที่เรียกว่า `BatchEncoding` ซึ่งเป็น subclass ของ dictionary อีกที ทำให้เราสามารถ index ตัว output ได้ `BatchEncoding` ต่างจาก dictionary ทั่วไปตรงที่ มันมี method เพิ่มเติม ที่ส่วนมากจะถูกใช้โดย fast tokenizer

นอกจาก fast tokenizer จะสามารถประมวลผลแบบ parallel ได้แล้ว ความสามารถหลักของของมันก็คือ มันจะบันทึก span (ตำแหน่งเริ่มและจบ) ของแต่ละ token ไว้ด้วย ข้อมูลเกี่ยวกับ span นี้เราเรียกว่า *offset mapping*

*offset mapping* สามารถช่วยให้เราโยง "คำ" ไปหา token ของมันได้ (ในที่นี้ "คำ" หมายถึง กลุ่มของตัวอักษรที่ถูกแบ่งด้วย space ซึ่งหนึ่งคำอาจจะถูกแบ่งออกเป็นหลาย token ได้) และนอกจากนั้น ก็ยังช่วยให้เราสามารถโยงตัวอักษร ไปหา token ได้ด้วยเช่นกัน

มาดูตัวอย่างกัน:

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
encoding = tokenizer(example)
print(type(encoding))
```

จะเห็นได้ว่า ผลลัพธ์ที่ได้จากตัดคำ คือ `BatchEncoding` อย่างที่เราได้กล่าวไว้ข้างต้น:

```python out
<class 'transformers.tokenization_utils_base.BatchEncoding'>
```

เนื่องจาก class `AutoTokenizer` จะเรียกใช้ตัว fast tokenizer เป็นค่าเริ่มต้น (by default) ซึ่งแปลว่า output ที่ได้คือ `BatchEncoding` และเราก็จะสามารถใช้ method พิเศษของมันได้
การจะเช็คว่า ตัวตัดคำเป็นแบบเร็วหรือช้า ทำได้สองวิธี วิธีแรกคือเช็คโดยการใช้ attribute `is_fast` ของ `tokenizer` :

```python
tokenizer.is_fast
```

```python out
True
```

อีกวิธีคือเช็ค attribute `is_fast` ของ `encoding`:

```python
encoding.is_fast
```

```python out
True
```

มาดูกันว่า fast tokenizer ทำอะไรได้บ้าง อย่างแรกคือ เราสามารถเรียกดู token ได้ โดยไม่ต้องแปลงแต่ละ ID กลับไปเป็น token

```py
encoding.tokens()
```

```python out
['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in',
 'Brooklyn', '.', '[SEP]']
```

ในตัวอย่างนี้ token ในตำแหน่งที่ 5 คือ `##yl` ซึ่งเป็นส่วนหนึ่งของคำว่า "Sylvain"

นอกจากนั้น คุณยังสามารถใช้ method `word_ids()` เพื่อเรียกดูตำแหน่งของคำได้ด้วย

```py
encoding.word_ids()
```

```python out
[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None]
```

คุณจะเห็นว่า token พิเศษอย่าง `[CLS]` และ `[SEP]` จะถูกจับคู่กับค่า `None` ส่วน token อื่นๆก็จะถูกจับคู่กับคำต้นตอของมัน
ข้อมูลนี้ มีประโยชน์ในการเอาไว้เช็คว่า token ที่เราสนใจนั้น อยู่ในตำแหน่งที่เป็นจุดเริ่มต้นของคำหรือไม่ และเอาไว้เช็คว่า token สองตัว มาจากคำเดียวกันหรือไม่
คุณสามารถเช็คข้อมูลพวกนี้ได้จากการดูที่สัญลักษณ์ `##` ที่อยู่ข้างหน้า token (ซึ่งแปลว่า token ตัวนี้ ไม่ได้อยู่ตรงจุดเริ่มต้นคำ) แต่วิธีนี้ ใช้ได้แค่กับตัวตัดคำที่มีโครงสร้างแบบโมเดล BERT เท่านั้น อย่างไรก็ตาม วิธีนี้ใช้ได้กับตัวตัดคำทุกประเภทที่เป็นแบบเร็ว

ในบทหน้า เราจะมาดูกันว่า เราจะใช้ feature นี้เพื่อจับคู่ label กับ token ใน task เช่น entity recognition (NER) and part-of-speech (POS) tagging ได้อย่างไร
นอกจากนั้น คุณยังสามารถใช้ feature นี้ เพื่อทำการปกปิด(mask) token ทุกตัวที่มาจากคำเดียวกัน เวลาใช้ masked language modeling ได้อีกด้วย (การทำแบบนี้เราเรียกว่า _whole word masking_)

<Tip>

นิยามของ "คำ" นั้นค่อนข้างยากที่จะกำหนด ตัวอย่างเช่น  "I'll" (เป็นการเขียนแบบสั้นของ "I will" ) ควรนับเป็นหนึ่งหรือสองคำ ?
คำตอบของคำถามนี้นั้น ขึ้นกับว่า คุณใช้ตัวตัดคำแบบไหน และมีการปรับแต่งข้อความ input ก่อนที่จะทำการตัดคำหรือไม่
ตัวตัดคำบางตัว อาจจะแยกคำด้วย space บางตัวอาจจะแยกคำด้วยเครื่องหมายวรรคตอน (punctuation) ก่อนแล้วจึงแบ่งด้วย space ในกรณีหลังนี้ "I'll" ก็จะถูกแบ่งเป็นสองคำ

✏️ **ลองทำดู!** ให้คุณลองสร้างตัวตัดคำจาก checkpoint ของ `bert-base-cased` and `roberta-base` แล้วให้ลองตัดคำว่า "81s"  คุณสังเกตเห็นอะไรบ้าง และ ID ของคำที่ได้คืออะไร

</Tip>

นอกจากนั้นยังมี method คล้ายๆกัน ที่ชื่อ `sentence_ids()` ที่เอาไว้ใช้เพื่อโยง token ไปหาประโยคต้นตอ ในตัวอย่างของเรา คุณสามารถใช้ `token_type_ids` ซึ่งเป็นผลลัพธ์จากการรัน tokenizer แทน `sentence_ids()` ได้ เพราะทั้งสองให้ข้อมูลเดียวกัน

ความสามารถสุดท้าย คือโยง token ไปหาแต่ละตัวอักษร หรือกลับกัน โดยการใช้ method `word_to_chars()` หรือ `token_to_chars()` และ `char_to_word()` หรือ `char_to_token()`

ตัวอย่างเช่น method `word_ids()` สามารถบอกให้คุณรู้ว่า `##yl` เป็นส่วนหนึ่งของคำในตำแหน่งที่ 3 แต่ถ้าคุณอยากรู้ว่า เป็นคำไหนในประโยค คุณสามารถเช็คได้ดังนี้ :

```py
start, end = encoding.word_to_chars(3)
example[start:end]
```

```python out
Sylvain
```

อย่างที่เราได้บอกข้างต้นแล้ว fast tokenizer สามารถทำแบบนี้ได้ เพราะมันเก็บข้อมูลเกี่ยวกับ span ของแต่ละ token เอาไว้ และบันทึกไว้ใน list ของ *offsets*
เพื่อที่จะอธิบายการใช้งานของ feature นี้ เรามาลองคำนวณผลลัพธ์ของ pipeline `token-classification` กัน

<Tip>

✏️ **ลองทำดู!** ให้คุณลองคิดข้อความตัวอย่างขึ้นมา แล้วถามตัวเองว่า token ตัวไหนคู่กับ ID ของคำไหน และ คุณจะหา span ของแต่ละคำได้อย่างไร นอกจากนั้น ให้คุณลองสร้างสองประโยคเพื่อเป็น input ให้กับตัวตัดคำของคุณ แล้วดูว่า ID ของประโยคนั้นเหมาะสมหรือไม่


</Tip>

## โครงสร้างภายในของ pipeline `token-classification`

ใน[บทที่ 1](/course/chapter1) คุณได้เรียนเกี่ยวกับการสร้างระบบ NER ซึ่งเป้าหมายหลักของระบบนี้ คือการหาส่วนของข้อความที่เป็น entitiy เช่น ชื่อคน ชื่อสถานที่ หรือชื่อองค์กร โดยการใช้ฟังก์ชัน `pipeline()` จาก 🤗 Transformers
ส่วนใน[บทที่ 2](/course/chapter2) คุณได้เรียนรู้ว่า pipeline ประกอบไปด้วย 3 ขั้นตอนสำคัญ เริ่มจาก การตัดคำ จากนั้นผลลัพธ์ก็จะถูกส่งไปให้โมเดล และสุดท้ายคือ การการปรับแต่งผลลัพธ์ (post-processing)
สองขั้นตอนแรกใน pipeline `token-classification` นั้น จะเหมือนกันกับ pipeline อื่นๆ แต่ขั้นตอน post-processing จะค่อนข้างซับซ้อนกว่า เราจะมาดูรายละเอียดกัน


{#if fw === 'pt'}

<Youtube id="0E7ltQB7fM8"/>

{:else}

<Youtube id="PrX4CjrVnNc"/>

{/if}

### การคำนวณผลลัพธ์เบื้องต้นด้วยการใช้ pipeline

อันดับแรก เราจะใช้ token classification pipeline เพื่อเปรียบเทียบกับ pipeline
ของเรา โมเดลที่ถูกตั้งเป็นค่าเบื้องต้นคือ [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english) ซึ่งมันจะคำนวณ NER ของแต่ละ ข้อความ input:

```py
from transformers import pipeline

token_classifier = pipeline("token-classification")
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

คุณจะเห็นว่า โมเดลนี้สามารถบอกได้ว่า token ที่มาจาก คำว่า "Sylvain" นั้นเป็นชื่อคน และ token ที่มาจากคำว่า "Hugging Face" นั้นเป็นชื่อองค์กร และ "Brooklyn" เป็นชื่อสถานที่
เราสามารถใช้ pipeline นี้เพื่อรวมรวม token ที่มี entity ประเภทเดียวกันได้ด้วย

```py
from transformers import pipeline

token_classifier = pipeline("token-classification", aggregation_strategy="simple")
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

`aggregation_strategy` ที่เราเลือก จะเปลี่ยนคะแนนของแต่ละกลุ่ม entity ด้วย
ถ้าเราตั้งค่าให้เป็นแบบ `"simple"` มันจะคำนวณคะแนน โดยการเฉลี่ยคะแนนของแต่ละ token ที่นับเป็น entity เดียวกัน ตัวอย่างเช่น คะแนนของคำว่า "Sylvain" คือค่าเฉลี่ยของคะแนนจาก token ย่อยซึ่งก็คือ `S`, `##yl`, `##va`, and `##in`

วิธีคำนวณคะแนนรวมแบบอื่น :

- `"first"` จะใช้คะแนนของ token แรกเท่านั้น เป็นคะแนนรวม (เช่น คะแนนรวมของคำว่า "Sylvain" ก็จะเป็น 0.993828 ซึ่งมาจากคะแนนของ `S`)
- `"max"` จะใช้คะแนนของ token ที่มีคะแนนมากที่สุด (เช่น คะแนนรวมของคำว่า "Hugging Face" ก็จะเป็น 0.98879766 ซึ่งมาจากคะแนนของ "Face")
- `"average"` จะใช้ค่าเฉลี่ยของแต่ละ token ที่เป็นส่วนของ entity นั้น เป็นคะแนนรวมของ entity (สำหรับคำว่า "Sylvain" คะแนนรวมแบบเฉลี่ยจะไม่ต่างจากคะแนนรวมแบบ `"simple"` แต่คำว่า "Hugging Face" จำได้คะแนน 0.9819 ซึ่งเป็นค่าเฉลี่ย ของ "Hugging" 0.975 และ "Face" 0.98879)

มาดูกันว่า คุณจะสร้างผลลัพธ์แบบนี้ได้อย่างไร โดยไม่ใช้ฟังก์ชัน `pipeline()`!

### จาก input สู่ ผลลัพธ์

{#if fw === 'pt'}

อันดับแรก เราจะเอาข้อความ input มาตัดคำก่อน แล้วส่งต่อผลลัพธ์ที่ได้ไปให้กับโมเดล ขั้นตอนนี้จะเหมือนที่เราทำกันใน[บทที่ 2](/course/chapter2) โดยเริ่มจากสร้างตัวตัดคำและโมเดลขึ้นมา โดยใช้คลาส `AutoXxx`
```py
from transformers import AutoTokenizer, AutoModelForTokenClassification

model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModelForTokenClassification.from_pretrained(model_checkpoint)

example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
inputs = tokenizer(example, return_tensors="pt")
outputs = model(**inputs)
```

เนื่องจากเราใช้ `AutoModelForTokenClassification` หลังจากรัน เราจะได้ค่า logit (set of logits) สำหรับแต่ละ token ที่อยู่ในข้อความ input

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
torch.Size([1, 19])
torch.Size([1, 19, 9])
```

{:else}

อันดับแรก เราจะเอาข้อความ input มาตัดคำก่อน แล้วส่งต่อผลลัพธ์ที่ได้ไปให้กับโมเดล ขั้นตอนนี้จะเหมือนที่เราทำกันใน[บทที่ 2](/course/chapter2) โดยเริ่มจากสร้างตัวตัดคำและโมเดลขึ้นมา โดยใช้คลาส `TFAutoXxx`

```py
from transformers import AutoTokenizer, TFAutoModelForTokenClassification

model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint)

example = "My name is Sylvain and I work at Hugging Face in Brooklyn."
inputs = tokenizer(example, return_tensors="tf")
outputs = model(**inputs)
```

เนื่องจากเราใช้ `TFAutoModelForTokenClassification` หลังจากรัน เราจะได้ค่า logit (set of logits) สำหรับแต่ละ token ที่อยู่ในข้อความ input

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
(1, 19)
(1, 19, 9)
```

{/if}

แต่ละ batch ประกอบไปด้วย 1 ข้อความ ซึ่งมี token 19 ตัว และโมเดลที่เราใช้ สามารถทำนายได้ 9 หมวด (label) ดังนั้นขนาดของ output ที่ได้คือ 1 x 19 x 9
เช่นเดียวกับตอนที่เราใช้ text classification pipeline คือเราจะใช้ฟังก์ชัน softmax เพื่อที่จะแปลงค่า logits ไปเป็นค่าความเป็นไปได้ (probabilities) จากนั้นเราจะคำนวณค่า argmax เพื่อคำนวณคำทำนายสุดท้าย (เราใช้ argmax ของค่า logits ตรงนี้ได้ ก็เพราะการคำนวณ softmax จากคะแนนของแต่ละหมวด ไม่ได้ทำให้ลำดับของหมวดเปลี่ยน)

{#if fw === 'pt'}

```py
import torch

probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist()
predictions = outputs.logits.argmax(dim=-1)[0].tolist()
print(predictions)
```

{:else}

```py
import tensorflow as tf

probabilities = tf.math.softmax(outputs.logits, axis=-1)[0]
probabilities = probabilities.numpy().tolist()
predictions = tf.math.argmax(outputs.logits, axis=-1)[0]
predictions = predictions.numpy().tolist()
print(predictions)
```

{/if}

```python out
[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0]
```

attribute `model.config.id2label` คือตัวที่เก็บข้อมูลเกี่ยวกับ mapping ของ index ไปหา label ที่เราเอาไว้ใช้เพื่อดูว่า ผลลัพธ์นั้นถูกต้องหรือไม่

```py
model.config.id2label
```

```python out
{0: 'O',
 1: 'B-MISC',
 2: 'I-MISC',
 3: 'B-PER',
 4: 'I-PER',
 5: 'B-ORG',
 6: 'I-ORG',
 7: 'B-LOC',
 8: 'I-LOC'}
```

จาก output ข้างบน คุณจะเห็นว่า เรามีคำทำนายทั้งหมด 9 หมวด (label) โดยที่ label `O` หมายถึง token ที่ไม่ได้เป็น named entity (`O` มาจากคำว่า "outside") และ แต่ละประเภทของ entity จะมีอย่างละสอง label (เรามีทั้งหมด 4 entity: miscellaneous, person, organization, and location)

ถ้า token ถูก label ด้วย `B-XXX` นั่นแปลว่า token นี้อยู่ข้างหน้าของ entity ประเภท `XXX` ส่วน label `I-XXX` หมายถึง token นี้อยู่ข้างใน entity ประเภท `XXX`
ถ้าดูจากข้อความตัวอย่างที่เราใช้ข้างบน โมเดลของเราก็ควรจะจับคู่ token `S` กับหมวด `B-PER` (ซึ่งแปลว่า `S` เป็นส่วนข้างหน้าของ entity ประเภทชื่อคน) ส่วน token `##yl`, `##va` และ `##in` ก็ควรจะถูกทำนายให้เป็นหมวด `I-PER` (ซึ่งหมายถึง token ที่อยู่ข้างใน entity ประเภทชื่อคน)

คุณอาจจะคิดว่า โมเดลของเราทำนายผิดในตัวอย่างนี้ เพราะว่ามันทำนายทั้งสี่ token ให้เป็น `I-PER` แต่จริงๆแล้ว ทำนายแบบนี้ก็ไม่ได้ผิดไปซะทั้งหมด
เพราะว่า การทำนายโดยใช้ label `B-` และ `I-` ในงาน NER มีสองแบบ คือ *IOB1* and *IOB2*

IOB1 (สีส้ม) เป็นแบบที่เราใช้ในตัวอย่างข้างบน ส่วน IOB2 (สีม่วง) แตกต่างตรงที่ `B-` เอาไว้ใช้แบ่ง entity สองตัวที่เป็นประเภทเดียว ให้ออกจากกัน
โมเดลที่เราใช้นั้นถูก fine-tune จากชุดข้อมูลที่มี label แบบ IOB2 ทำให้มันทำนาย `S` เป็น `I-PER`


<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions.svg" alt="IOB1 vs IOB2 format"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions-dark.svg" alt="IOB1 vs IOB2 format"/>
</div>

ตอนนี้เราก็พร้อมที่จะคำนวณผลลัพธ์ ให้ได้แบบเดียวกับ pipeline แรกแล้ว โดยที่เราจะใช้คะแนนและ label ของแต่ละ token ที่ไม่ใช่ `O`เท่านั้น

```py
results = []
tokens = inputs.tokens()

for idx, pred in enumerate(predictions):
    label = model.config.id2label[pred]
    if label != "O":
        results.append(
            {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]}
        )

print(results)
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}]
```

จะเห็นว่าตอนนี้ เราได้ผลลัพธ์ที่คล้ายกับผลลัพธ์จาก pipeline ก่อนหน้านี้แล้ว ข้อแตกต่างเดียวก็คือ ผลลัพธ์จาก pipeline จะให้ข้อมูลเกี่ยวกับ ตำแหน่งเริ่มและจบในข้อความของแต่ละ entity ด้วย
ขั้นตอนต่อไป เราจะได้เรียกใช้ค่า offset mapping เพื่อตั้งค่าให้โมเดลคำนวณค่า offset เราจะเช็ต `return_offsets_mapping=True` ในตอนที่เราใช้รันตัวตัดคำ


```py
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
inputs_with_offsets["offset_mapping"]
```

```python out
[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32),
 (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)]
```

แต่ละ tuple ในนี้ จะบันทึกค่า span ของแต่ละ token ไว้ โดยที่พวก token พิเศษ ก็จะมีค่า span เป็น `(0, 0)`
ก่อนหน้านี้ เราได้เห็นว่า token ในตำแหน่งที่ 5 คือ `##yl` มีค่า offset เป็น `(12, 14)`
ถ้าคุณลอง slice ข้อความ input ด้วย index สองค่านี้

```py
example[12:14]
```

คุณจะได้ส่วนของข้อความที่เป็น `yl` โดยที่ `##` จะถูกละออกจากผลลัพธ์:

```python out
yl
```

เราจะใช้วิธีนี้ เพื่อคำนวณผลลัพธ์ให้ได้ผลลัพธ์เหมือนใน pipeline:

```py
results = []
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
tokens = inputs_with_offsets.tokens()
offsets = inputs_with_offsets["offset_mapping"]

for idx, pred in enumerate(predictions):
    label = model.config.id2label[pred]
    if label != "O":
        start, end = offsets[idx]
        results.append(
            {
                "entity": label,
                "score": probabilities[idx][pred],
                "word": tokens[idx],
                "start": start,
                "end": end,
            }
        )

print(results)
```

```python out
[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12},
 {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14},
 {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16},
 {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18},
 {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35},
 {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40},
 {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45},
 {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

ตอนนี้ เราก็ได้ผลลัพธ์แบบเดียวกับผลลัพธ์จาก pipeline แล้ว!

### การรวม entities

ข้อมูลเกี่ยวกับ ตำแหน่งเริ่มและตำแหน่งจบของแต่ละ entity ที่เราได้จาก offset อาจจะไม่เป็นประโยชน์มากนัก
แต่ถ้าคุณต้องการจะรวม token ที่อยู่ในกลุ่ม entity เดียวกันเข้าด้วยกัน ข้อมูลจาก offset พวกนี้ จะมีโยชน์มาก และทำให้คุณไม่ต้องเขียนโค้ดเพิ่มเติมด้วย

ตัวอย่างเช่น ถ้าคุณต้องการจะรวม `Hu`, `##gging`, และ `Face` เข้าด้วยกัน คุณอาจจะเขียนกฎขึ้นมาว่า ให้รวม token แรกกับ token ที่สองเข้าด้วยกัน โดยลบ `##` ออก
แล้วให้รวม token ที่สามโดยใช้ช่องว่างในการเชื่อม เพราะ `Face` ไม่ได้เริ่มต้นด้วย  `##` อย่างไรก็ตามวิธีนี้ ใช้ได้แค่กับตัวตัดคำบางประเภทเท่านั้น
สำหรับตัวตัดคำอื่นๆเช่น แบบ SentencePiece หรือ Byte-Pair-Encoding เราก็จะต้องสร้างกฎขึ้นมาใหม่

การที่เราใช้ค่า offset ทำให้เราไม่จำเป็นต้องเขียนโค้ดเกี่ยวกับกฎพวกนี้ขึ้นมาเอง คุณสามารถใช้ตำแหน่งเริ่มของ token แรก และ ตำแหน่งจบของ token สุดท้าย เป็นค่าในการ slice ข้อความ input เพื่อที่จะคำนวณส่วนของข้อความของ entity ที่คุณสนใจ
ตัวอย่างเช่น ถ้าเรามี 3 token `Hu`, `##gging`, และ `Face` ซึ่งอยู่ในกลุ่ม entity เดียวกัน เราก็จะใช้ค่า 33 (ตำแหน่งเริ่มของ `Hu`) เป็นจุดเริ่มต้น และ 45 (ตำแหน่งจบของ `Hu`) เป็นจุดจบของ entity นี้


```py
example[33:45]
```

```python out
Hugging Face
```

ขั้นตอนต่อไป เราจะมาเขียนโค้ดเพื่อปรับแต่งผลลัพธ์จากโมเดลกัน (post-processing) โดยจะทำไปพร้อมๆกับการรวมกลุ่ม entity ด้วยวิธีต่อไปนี้

เริ่มจาก token แรกของ entity ซึ่งเป็นได้ทั้ง `B-XXX` และ `I-XXX` จากนั้นให้รวม token ตัวถัดๆไป ที่เป็น `I-XXX` ทั้งหมดเข้าด้วยกัน จนกว่าจะเห็น `O` (แปลว่าเรากำลังอ่านถึง token ที่ไม่ใช่ entity ใดๆ ให้เราหยุดการรวม) หรือ `B-XXX` (ให้เราเริ่มต้นรวม entity ตัวใหม่)

```py
import numpy as np

results = []
inputs_with_offsets = tokenizer(example, return_offsets_mapping=True)
tokens = inputs_with_offsets.tokens()
offsets = inputs_with_offsets["offset_mapping"]

idx = 0
while idx < len(predictions):
    pred = predictions[idx]
    label = model.config.id2label[pred]
    if label != "O":
        # Remove the B- or I-
        label = label[2:]
        start, _ = offsets[idx]

        # Grab all the tokens labeled with I-label
        all_scores = []
        while (
            idx < len(predictions)
            and model.config.id2label[predictions[idx]] == f"I-{label}"
        ):
            all_scores.append(probabilities[idx][pred])
            _, end = offsets[idx]
            idx += 1

        # The score is the mean of all the scores of the tokens in that grouped entity
        score = np.mean(all_scores).item()
        word = example[start:end]
        results.append(
            {
                "entity_group": label,
                "score": score,
                "word": word,
                "start": start,
                "end": end,
            }
        )
    idx += 1

print(results)
```

ตอนนี้ เราก็ได้ผลลัพธ์แบบเดียวกันกับ pipeline แล้ว!

```python out
[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}]
```

อีกตัวอย่างหนึ่งของงานที่ค่า offset เป็นโยชน์มาก ก็คือ question answering
ในบทถัดไป คุณจะได้เรียนเกี่ยวกับ pipeline แบบเจาะลึกมากขึ้น ซึ่งคุณจะได้รู้จักกับ feature สุดท้ายของ tokenizers ใน 🤗 Transformers library ซึ่งก็คือ การจัดการกับ tokens ที่ถูกตัดออกจากข้อความ input

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/3.mdx" />

### WordPiece tokenization
https://huggingface.co/learn/course/th/chapter6/6.md

# WordPiece tokenization

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section6.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section6.ipynb"},
]} />

WordPiece เป็นอัลกอริทึมสำหรับ tokenization ที่สร้างโดย Google เพื่อ pretrain โมเดล BERT หลังจากนั้นมันได้ถูกนำมาใช้กับโมเดลประเภท Transformer หลายตัวที่เป็นประเภทเดียวกับ BERT เช่น DistilBERT, MobileBERT, Funnel Transformers, และ MPNET

WordPiece มีความคล้ายกับ BPE ในวิธีการเทรน แต่วิธีการแยกคำนั้นแตกต่างกัน

<Youtube id="qpv6ms_t_1A"/>

<Tip>

💡 บทนี้จะพูดถึง WordPiece อย่างละเอียด เราจะเจาะลึกถึงไปถึงการ implement อัลกอริทึมนี้ คุณสามารถข้ามไปตอนท้ายได้ ถ้าคุณสนใจเพียงแค่ภาพรวมคร่าวๆเท่านั้น

</Tip>

## Training algorithm

<Tip warning={true}>

⚠️ เนื่องจาก Google ไม่เปิดเผยโค้ดสำหรับการเทรน WordPiece ดังนั้นโค้ดที่เราจะสอนคุณต่อจากนี้ มาจากการพยายามทำตามข้อมูลที่บอกไว้ใน paper แปลว่าโค้ดอาจจะไม่แม่นยำ 100%

</Tip>

เช่นเดียวกับ BPE อัลกอริทึม WordPiece เริ่มจาก vocabulary ขนาดเล็ก ที่ประกอบไปด้วย token พิเศษที่โมเดลใช้ และตัวอักษรตั้งต้น
เพื่อที่โมเดลจะได้รู้ว่าคำไหนเป็นคำย่อย มันจะเขียน prefix เช่น `##` (ใช้ใน BERT) ไว้ข้างหน้าของแต่ละคำย่อย ในขั้นตอนแรก แต่ละคำจะถูกแบ่งออกเป็นตัวอักษร โดยตัวอักษรที่ไม่ใช่ตัวแรกจะมี prefix นี้

ตัวอย่างเช่น คำว่า `"word"` จะถูกแบ่งดังนี้ :

```
w ##o ##r ##d
```

ดังนั้น vocabulary ตั้งต้น จะประกอบไปด้วยทุกๆตัวอักษรที่อยู่เริ่มต้นของแต่ละคำ และตัวอักษรอื่นๆที่อยู่ข้างในคำนั้น ซึ่งนำหน้าด้วย prefix พิเศษ

เช่นเดียวกันกับ BPE เป้าหมายในการเทรน WordPiece คือเรียนกฎเพื่อการ merge แต่ความแตกต่างคือหลักการในการเลือกคู่ token ที่จะนำมา merge แทนที่จะเลือกคู่ที่พบบ่อยที่สุด WordPiece จะคำนวณ score ให้แต่ละคู่ โดยใช้สูตรต่อไปนี้

$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$

การที่เราหารความถี่ของคู่ token ด้วยผลคูณของความถี่ของแต่ละ token ในคู่ จะทำให้อัลกอริทึมให้คะแนนคู่ ที่แต่ละ token มีความถี่ไม่สูง
ตัวอย่างเช่น เราไม่จำเป็นจำต้อง merge `("un", "##able")` ถึงแม้ว่าคู่นี้จะมีจำนวนมากที่สุดใน vocabulary เพราะว่าทั้ง `"un"` และ `"##able"` ต่างพบได้กับคำอื่นๆด้วย และแต่ละตัวก็มีจำนวนค่อนข้างสูง
ตรงกันข้ามกับ คู่เช่น `("hu", "##gging")` ซึ่งอาจจะถูกรวมเร็วกว่า (ในกรณีที่ "hugging" มีจำนวนสูงใน vocabulary ) เพราะว่า ทั้ง`"hu"` และ `"##gging" ต่างก็พบได้ไม่บ่อย

เราจะใช้ตัวอย่าง เดียวกันกับที่เราใช้ใน BPE :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

หลังจากการแยกแต่ละคำ เราจะได้ :

```
("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)
```

ดังนั้น vocabulary ตั้งต้น คือ `["b", "h", "p", "##g", "##n", "##s", "##u"]` (เราขอละไม่พูดถึง token พิเศษในที่นี้)
คู่ที่พบบ่อยที่สุดคือ `("##u", "##g")` ซึ่งพบ 20 ครั้ง แต่ว่าถ้านับจำนวนของแต่ละ token `"##u"` จะมีจำนวนค่อนข้างสูง ทำให้ score ของคู่นี้ไม่ได้สูงที่สุด (1 / 36)
ทุกๆคู่ที่ประกอบด้วย `"##u"` จะได้ score เดียวกันซึ่งคือ (1 / 36) ดังนั้น score ที่สูงที่สุดจึงมาจากคู่ `("##g", "##s")` เพราะว่ามันไม่มี `"##u"` ซึ่งมี score เป็น 1 / 20 และกฎแรกที่เราได้ก็คือ `("##g", "##s") -> ("##gs")`

โปรดสังเกตว่า เวลาที่เรา merge เราจะลบ ตัว `##` ออกระหว่าง token สองตัวที่เราต้องการจะ merge แปลว่าเราจะได้ `"##gs"` และเราจะเพิ่ม token นี้เข้าไปใน vocabulary จากนั้นเราก็จะใช้กฎนี้กับทุกๆคำใน corpus ด้วย :

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"]
Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5)
```

ตอนนี้ `"##u"` มีอยู่ในทุกๆคู่ แปลว่า ทุกคู่จะมี score เท่ากัน

ในกรณีนี้ เราจะเลือกกฎใดกฎหนึ่งเพื่อ merge ต่อไป เราจะเลือก `("h", "##u") -> "hu"` และได้ผลลัพธ์ต่อไปนี้ :

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"]
Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)
```

ตอนนี้คู่ที่มี score สูงที่สุดคือ `("hu", "##g")` และ `("hu", "##gs")` ซึ่งทั้งสองมี score เท่ากับ 1/15 (ส่วนตัวอื่นๆที่เหลือ มี score เท่ากับ 1/21) เราจะเลือกคู่แรกใน list มาใช้ เพื่อ merge :

```
Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]
Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)
```

เราจะทำแบบนี้จนกว่าจะได้ vocabulary ที่มีขนาดใหญ่มากพอ

<Tip>

✏️ **ตาคุณบ้างแล้ว!** กฎ merge ต่อไปคืออะไร

</Tip>

## Tokenization algorithm

การ tokenization ใน WordPiece แตกต่างจาก BPE ตรงที่ WordPiece จะบันทึกเฉพาะ vocabulary สุดท้ายเท่านั้น และไม่ได้บันทึก กฎ merge
หากเราจะ tokenize คำใดคำหนึ่ง WordPiece จะหาคำย่อยที่ยาวที่สุด ที่พบใน vocabulary จากนั้นจะแยกคำออกตามนั้น
ตัวอย่างเช่น ถ้าเราใช้ vocabulary ที่เทรนแล้วจากตัวอย่างด้านบน และต้องการ tokenize คำว่า `"hugs"` คำย่อยที่ยาวที่สุดก็คือ `"hug"` ดังนั้น เราจะแบ่งมันออกเป็น `["hug", "##s"]`
จากนั้นเราก็จะดูที่ `"##s"` เราพบว่าเป็น token ที่อยู่ใน vocabulary ดังนั้น เราจึงได้ `["hug", "##s"]`
ถ้าเราใช้ BPE เราจะใช้กฎที่เทรนมาตามลำดับ ซึ่งมันจะ tokenize ตัวอย่างของเราออกเป็น `["hu", "##gs"]`

มาดูอีกตัวอย่างกัน เช่นคำว่า `"bugs"`
เราจะเริ่มอ่านจากข้างหน้าของคำไปข้างหลัง คุณจะเห็นว่า `"b"` เป็นคำย่อยที่ยาวที่สุดที่พบใน vocabulary ดังนั้น เราจะแยกคำตรงนี้ และเราจะได้ `["b", "##ugs"]`
จากนั้นเราจะดูคำว่า `"##ugs"` สำหรับคำนี้เราพบว่า `"##u"` คือคำย่อยที่ยาวที่สุดที่พบใน vocabulary ดังนั้น เราจึงจะแยกคำตรงนี้ และได้ผลลัพธ์เป็น  `["b", "##u, "##gs"]`
สุดท้ายเราจะดูที่คำว่า `"##gs"` ซึ่งเราพบว่า มันอยู่ใน vocabulary แล้ว ดังนั้นเราไม่ต้องแบ่งมันอีก

ในกรณีที่เราไม่สามารถหาคำย่อยที่อยู่ใน vocabulary ได้เลย คำหลักที่เรากำลังอ่านนั้นจะถูกแปลงเป็นคำ unknown
ตัวอย่างเช่นคำว่า `"mug"` จะถูก tokenize ให้เป็น `["[UNK]"]` เช่นเดียวกันกับคำว่า `"bum"` ถึงแม้ว่าเราจะเจอ `"b"` และ `"##u"` ใน vocabulary แต่ว่า `"##m"` ไม่ได้อยู่ใน  vocabulary เราจะแปลงทั้งคำเป็น `["[UNK]"]` และจะไม่แยกมันเป็น `["b", "##u", "[UNK]"]`
นี่เป็นสิ่งหนึ่งที่แตกต่างจาก BPE โดย BPE จะดูที่แต่ละตัวอักษร และถ้าตัวไหนไม่พบใน vocabulary ก็จะถูกคัดว่าเป็น unknown

<Tip>

✏️ **ถึงตาคุณแล้ว!** คำว่า `"pugs"` จะถูก tokenize อย่างไร?

</Tip>

## Implementing WordPiece

มาดูกันว่า เราจะ implement อัลกอริทึม WordPiece ได้อย่างไร
เช่นเดียวกับตอนที่เราสอนเรื่อง BPE สิ่งที่เราจะสอนต่อไปนี้เป็นเพียงตัวอย่าง เพื่อให้คุณเข้าใจการทำงานของอัลกอริทึม โค้ดที่ได้อาจจะไม่สามารถใช้ได้กับ corpus ใหญ่ๆ
เราจะใช้ corpus ตัวอย่างเดียวกับที่ใช้ในบท BPE :

```python
corpus = [
    "This is the Hugging Face course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

ก่อนอื่นคุณจะต้อง pre-tokenize corpus เพื่อแยกข้อความเป็นคำๆ เนื่องจากเราจะจำลองการทำงานของ WordPiece tokenizer (เช่น BERT) เราจะใช้ `bert-base-cased` tokenizer ในการ pre-tokenize

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

จากนั้นคำนวณความถี่ของแต่ละคำใน corpus :

```python
from collections import defaultdict

word_freqs = defaultdict(int)
for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

word_freqs
```

```python out
defaultdict(
    int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1,
    'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1,
    ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1,
    'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1})
```

เราจะมาสร้างเซ็ตของ alphabet กัน ซึ่งคือเซ็ตที่ประกอบไปด้วยตัวอักษรแรกของแต่ละคำ และอักษรอื่นๆที่ไม่ใช่ตัวแรกจะมีการใส่ `##` ไว้ข้างหน้า :

```python
alphabet = []
for word in word_freqs.keys():
    if word[0] not in alphabet:
        alphabet.append(word[0])
    for letter in word[1:]:
        if f"##{letter}" not in alphabet:
            alphabet.append(f"##{letter}")

alphabet.sort()
alphabet

print(alphabet)
```

```python out
['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s',
 '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u',
 'w', 'y']
```

เราจะเพิ่ม token พิเศษ เข้าไปด้านหน้าของ list นี้ด้วย สำหรับ BERT คือ `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` :

```python
vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy()
```

จากนั้น เราจะทำการแยกแต่ละคำกัน โดยแยกตัวอักษรแรกออกมา และตัวที่เหลือจะเพิ่ม `##` ไว้ข้างหน้า :

```python
splits = {
    word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]
    for word in word_freqs.keys()
}
```

ตอนนี้เราก็พร้อมที่จะเทรนแล้ว เราจะมาเขียนฟังก์ชันเพื่อคำนวณ score ให้แต่ละคู่ tokenกัน :

```python
def compute_pair_scores(splits):
    letter_freqs = defaultdict(int)
    pair_freqs = defaultdict(int)
    for word, freq in word_freqs.items():
        split = splits[word]
        if len(split) == 1:
            letter_freqs[split[0]] += freq
            continue
        for i in range(len(split) - 1):
            pair = (split[i], split[i + 1])
            letter_freqs[split[i]] += freq
            pair_freqs[pair] += freq
        letter_freqs[split[-1]] += freq

    scores = {
        pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]])
        for pair, freq in pair_freqs.items()
    }
    return scores
```

มาดูกันว่าผลลัพธ์ที่ได้หลังจากการรันครั้งแรกเป็นอย่างไร :

```python
pair_scores = compute_pair_scores(splits)
for i, key in enumerate(pair_scores.keys()):
    print(f"{key}: {pair_scores[key]}")
    if i >= 5:
        break
```

```python out
('T', '##h'): 0.125
('##h', '##i'): 0.03409090909090909
('##i', '##s'): 0.02727272727272727
('i', '##s'): 0.1
('t', '##h'): 0.03571428571428571
('##h', '##e'): 0.011904761904761904
```

จากนั้น เราจะหาคู่ที่มี score สูงที่สุด โดยใช้ loop ง่ายๆ ดังนี้ :

```python
best_pair = ""
max_score = None
for pair, score in pair_scores.items():
    if max_score is None or max_score < score:
        best_pair = pair
        max_score = score

print(best_pair, max_score)
```

```python out
('a', '##b') 0.2
```

กฎที่ได้จากการเทรนครั้งแรกคือ `('a', '##b') -> 'ab'` ดังนั้นเราจะเพิ่ม `'ab'`  เข้าไปใน vocabulary :

```python
vocab.append("ab")
```

ก่อนที่จะทำต่อ เราจะต้องเพิ่มตัวที่ถูก merge เข้าไปใน dictionary `splits` ก่อน โดยเราจะเขียนฟังก์ชันเพื่อการคำนวณนี้ :

```python
def merge_pair(a, b, splits):
    for word in word_freqs:
        split = splits[word]
        if len(split) == 1:
            continue
        i = 0
        while i < len(split) - 1:
            if split[i] == a and split[i + 1] == b:
                merge = a + b[2:] if b.startswith("##") else a + b
                split = split[:i] + [merge] + split[i + 2 :]
            else:
                i += 1
        splits[word] = split
    return splits
```

นี่คือผลลัพธ์ของการ merge ครั้งแรก :

```py
splits = merge_pair("a", "##b", splits)
splits["about"]
```

```python out
['ab', '##o', '##u', '##t']
```

ตอนนี้เราก็มีทุกฟังก์ชันที่จำเป็นสำหรับการเทรนแล้ว เราจะเทรนจนกว่า tokenizer ได้เรียนเกี่ยวกับทุกๆ merge ที่เราต้องการ เราจะตั้งค่าขนาด vocabulary เป็น 70 สำหรับตัวอย่างนี้ :

```python
vocab_size = 70
while len(vocab) < vocab_size:
    scores = compute_pair_scores(splits)
    best_pair, max_score = "", None
    for pair, score in scores.items():
        if max_score is None or max_score < score:
            best_pair = pair
            max_score = score
    splits = merge_pair(*best_pair, splits)
    new_token = (
        best_pair[0] + best_pair[1][2:]
        if best_pair[1].startswith("##")
        else best_pair[0] + best_pair[1]
    )
    vocab.append(new_token)
```

มาดูผลลัพธ์ของ vocabulary กัน :

```py
print(vocab)
```

```python out
['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k',
 '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H',
 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully',
 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat',
 '##ut']
```

ถ้าเทียบกับ BPE คุณจะเห็นว่า tokenizer ตัวนี้สามารถเรียนเกี่ยวกับคำย่อยได้เร็วกว่านิดหน่อย

<Tip>

💡 ถ้าคุณใช้ `train_new_from_iterator()` กับ corpus ตัวอย่างนี้ คุณอาจจะไม่ได้ vocabulary เดียวกัน นั่นก็เพราะ 🤗 Tokenizers library ไม่ได้ใช้ WordPiece ในการเทรน แต่เราใช้ BPE


</Tip>

เมื่อคุณต้องการ tokenize ข้อความใหม่ คุณจะต้องทำการ pre-tokenize ข้อความแล้วจากนั้นจึง tokenize แต่ละคำ ตามหลักการของอัลกอริทึมนี้
เราจะมองหาคำย่อยที่ยาวที่สุด โดยอ่านจากข้างหน้าคำไปข้างหลัง จากนั้นเราจะแยกคำหลักออกตรงคำย่อยนี้ จากนั้นทำขั้นตอนนี้ซ้ำกับส่วนต่อๆไปของคำนั้น แล้วทำเช่นเดียวกันกับคำต่อไป

```python
def encode_word(word):
    tokens = []
    while len(word) > 0:
        i = len(word)
        while i > 0 and word[:i] not in vocab:
            i -= 1
        if i == 0:
            return ["[UNK]"]
        tokens.append(word[:i])
        word = word[i:]
        if len(word) > 0:
            word = f"##{word}"
    return tokens
```

มาทดลอง tokenize คำที่มีใน vocabulary และอีกคำที่ไม่ได้อยู่ใน vocabulary กัน :

```python
print(encode_word("Hugging"))
print(encode_word("HOgging"))
```

```python out
['Hugg', '##i', '##n', '##g']
['[UNK]']
```

ตอนนี้เราจะต้องเขียนฟังก์ชันเพื่อ tokenize ข้อความกัน :

```python
def tokenize(text):
    pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in pre_tokenize_result]
    encoded_words = [encode_word(word) for word in pre_tokenized_text]
    return sum(encoded_words, [])
```

ทดลองฟังก์ชันของเรากับประโยคตัวอย่าง :

```python
tokenize("This is the Hugging Face course!")
```

```python out
['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s',
 '##e', '[UNK]']
```

นี่คือทั้งหมดเกี่ยวกับ WordPiece ในบทถัดไปเราจะมาเรียนเกี่ยวกับ Unigram กัน


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/6.mdx" />

### คำถามท้ายบท
https://huggingface.co/learn/course/th/chapter6/10.md

# คำถามท้ายบท

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

มาทดสอบความรู้ที่คุณได้เรียนในบทนี้กันเถอะ!

### 1. สถานการณ์ไหนที่คุณควรจะเทรน tokenizer ขึ้นมาใหม่?

<Question
	choices={[
		{
			text: "เมื่อชุดข้อมูลเป้าหมาย คล้ายกับชุดข้อมูลที่โมเดลที่มีอยู่แล้วใช้ในการเทรน และคุณต้องการจะเทรนโมเดลขึ้นมาใหม่",
			explain: "ในกรณีนี้ คุณควรจะใช้ tokenizer ตัวเดิมเป็น pretrained model แล้ว fine-tune มันจะดีกว่า เพื่อจะได้ประหยัดเวลาและทรัพยากร"
		},
		{
			text: "เมื่อชุดข้อมูลเป้าหมาย คล้ายกับชุดข้อมูลที่ pretrained model ใช้ในการเทรน และคุณต้องการจะ fine-tune โมเดลตัวนี้",
			explain: "ถ้าคุณต้องการ fine-tune โมเดลจาก pretrained model คุณควรจะใช้ tokenizer ตัวเดียวกันเสมอ"
		},
		{
			text: "เมื่อชุดข้อมูลเป้าหมาย แตกต่างจากชุดข้อมูลที่ pretrained model ใช้ และคุณต้องการจะเทรนโมเดลใหม่ขึ้นมา",
			explain: "ถูกต้อง! ในกรณีนี้ ไม่มีประโยชน์ที่จะใช้ tokenizer ตัวเดิม",
            correct: true
		},
        {
			text: "เมื่อชุดข้อมูลเป้าหมายแตกต่างจากชุดข้อมูลที่ใช้โดย pretrained model แต่คุณต้องการ fine-tune โมเดลตัวนี้เพื่อให้ได้โมเดลใหม่",
			explain: "ในการ fine-tune จาก pretrained model คุณควรใช้ tokenizer ตัวเดียวกันเสมอ"
		}
	]}
/>

### 2. เวลาใช้ `train_new_from_iterator()` อะไรคือข้อดีของการใช้ generator of lists of texts เทียบกับการใช้ list of lists of texts?

<Question
	choices={[
		{
			text: "มันเป็นข้อมูลประเภทเดียวที่ <code>train_new_from_iterator()</code> สามารถใช้ได้",
			explain: "list of lists of texts เป็น generator ประเภทหนึ่ง ดังนั้น method นี้สามารถใช้มันได้เช่นกัน ลองดูใหม่นะ!"
		},
		{
			text: "เพื่อป้องกันไม่ให้คุณต้องโหลดชุดข้อมูลทั้งหมด ลงไปใน memory ภายในครั้งเดียว",
			explain: "ถูกต้อง! แต่ละ batch ของข้อความ จะถูกปล่อยออกจาก memory เวลาที่คุณ iterate มัน คุณจะเห็นประโยชน์ของการทำแบบนี้ได้ชัดเจนยิ่งขึ้น เวลาที่คุณใช้ 🤗 Datasets เพื่อเก็บข้อความ",
			correct: true
		},
		{
			text: "ทำให้ 🤗 Tokenizers library สามารถใช้ multiprocessing ได้",
			explain: "ไม่ถูก เพราะมันจะใช้ multiprocessing ในทั้งสองกรณี"
		},
        {
			text: "tokenizer จะสามารถผลิตข้อความได้ดีขึ้น",
			explain: "tokenizer ไม่สามารถผลิตข้อความได้ -- คุณอาจจะกำลังสับสนมันกับ language model หรือเปล่า"
		}
	]}
/>

### 3. อะไรคือข้อดีของ "fast" tokenizer?

<Question
	choices={[
		{
			text: "มันสามารถประมวลผลข้อความ input ได้เร็วกว่า slow tokenizer เวลาที่คุณใช้ input batch ขนาดใหญ่",
			explain: "ถูกต้อง! มันทำแบบนั้นได้ก็เพราะ การทำงานแบบ parallelism จาก Rust จะทำให้มันประมวลผล input ในรูปแบบ batch ได้เร็วขึ้น นอกจากนี้แล้ว คุณรู้ไหมว่ามันยังมีประโยชน์อะไรอีก",
			correct: true
		},
		{
			text: "Fast tokenizer สามารถประมวลได้เร็วกว่า slow tokenizer เสมอ",
			explain: "ไม่ถูก เพราะว่า fast tokenizer จะช้ากว่าถ้าหาก input น้อยมาก เพราะมันจะไม่สามารถใช้ parallelism ได้"
		},
		{
			text: "มันสามารถทำ padding และ truncation (ตัดบางส่วนของข้อความออก ถ้ายาวไป) ได้",
			explain: "slow tokenizers สามารถทำได้เช่นกัน"
		},
        {
			text: "มันมี feature พิเศษที่ช่วยให้คุณสามารถ map แต่ละ token ไปหา span ของข้อความหลักได้",
			explain: " ถูกต้อง Feature นี้เรียกว่า offset mappings แต่ยังมีอีกข้อที่ถูกเช่นกัน",
			correct: true
		}
	]}
/>

### 4. `token-classification` pipeline มีวิธีจัดการกับ entity ที่ประกอบไปด้วยหลายๆ token ได้อย่างไร?

<Question
	choices={[
		{
			text: "Entity ที่มี label เดียวกันจะถูกรวมเป็น entity เดียว",
			explain: "คำอธิบายนี้ง่ายเกินไป ลองใหม่นะ!"
		},
		{
			text: "มันจะใช้ label ประเภทหนึ่งสำหรับ token ที่อยู่จุดเริ่มต้นของ entity และ label อีกประเภทสำหรับ token ส่วนที่เหลือ",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "ในคำๆหนึ่ง ถ้า token แรกมี label ของ entity คำทั้งคำจะถูก label ให้เป็น entity นั้นด้วย",
			explain: "ถูกต้อง นี่เป็นวิธีหนึ่งในการจัดการกับ entity ลองดูว่ามีคำตอบอื่นที่ถูกต้องอีกมั้ย",
			correct: true
		},
        {
			text: "ถ้า token หนึ่งถูก label ว่าเป็น entity token อื่นๆที่ตามมาที่มี label เดียวกัน จะถูกจัดว่าเป็น entity เดียวกัน ยกเว้นว่า มันจะเป็น label ที่บอกว่าเริ่ม entity ใหม่",
			explain: "ถูกต้อง นี่เป็นวิธีมาตรฐานที่เอาไว้ใช้รวม token ของ entity เข้าด้วยกัน -- ยังมีอีกคำตอบที่ถูกต้องเช่นกัน ลองหาดู",
			correct: true
		}
	]}
/>

### 5. `question-answering` pipeline มีวิธีจัดการกับข้อความส่วนบริบท(context)ที่มีขนาดยาวอย่างไร?

<Question
	choices={[
		{
			text: "มันไม่ได้จัดการอะไร มันแค่ตัด context ให้มีความยาวเท่ากับความยาวสูงสุดที่เป็นไปได้เท่านั้น",
			explain: "เราได้เรียนทริกที่เอาไว้ใช้แก้ปัญหา context ยาวกันแล้ว จำได้มั้ย"
		},
		{
			text: "มันจะแบ่ง context ออกเป็นหลายๆส่วน แล้วคำนวณค่าเฉลี่ยของผลลัพธ์จากแต่ละส่วนเป็นคำตอบ",
			explain: "ไม่ถูก มันไม่สมเหตุสมผลที่จะใช้ค่าเฉลี่ย เพราะว่าบางส่วนของ context อาจจะไม่มีคำตอบอยู่เลยก็ได้"
		},
		{
			text: "มันจะแยก context ออกเป็นหลายๆส่วน โดยให้แต่ละส่วนทับซ้อนกันด้วย (overlap) แล้วมันจะคำนวณ score ให้แต่ละส่วน แล้วเลือกส่วนที่มี score สูงที่สุดเป็นคำตอบ",
			explain: "ถูกต้อง!",
			correct: true
		},
        {
			text: "มันจะแบ่ง context ออกเป็นหลายๆส่วน โดยไม่มีส่วนทับซ้อน เพื่อรักษาประสิทธิภาพ แล้วเลือกส่วนที่มี score สูงที่สุดเป็นคำตอบ",
			explain: "ไม่ถูก เราควรแบ่ง context ให้มีส่วนที่ทับซ้อนด้วย เพื่อป้องกันไม่ให้เกิดกรณีที่ span ของคำตอบ ถูกตัดออกไปอยู่หลายๆส่วน"
		}
	]}
/>

### 6. อะไรคือ normalization?

<Question
	choices={[
		{
			text: "ขั้นตอนที่เกี่ยวข้องกับการทำความสะอาดข้อมูล ที่ tokenizer ทำในขั้นเริ่มต้น",
			explain: "ถูกต้อง ตัวอย่างเช่น ลบ accents หรือ whitespace หรือแปลงข้อความเป็นตัวอักษรเล็ก",
			correct: true
		},
		{
			text: "เทคนิคในการสร้างข้อมูลใหม่จากข้อมูลที่มีอยู่เพื่อขยายชุดข้อมูล (data augmentation) เกี่ยวข้องกับการทำข้อความให้เป็นปกติมากขึ้น โดยลบคำที่พบได้ยากออก",
			explain: "ไม่ถูก! ลองดูใหม่นะ"
		},
		{
			text: "ลำดับสุดท้ายหลังการประมวลผลหลัก ซึ่ง tokenizer จะเพิ่ม token พิเศษเข้าไปใน vocabulary",
			explain: "ไม่ถูก ขั้นตอนนี้คือ post-processing"
		},
        {
			text: "ขั้นตอนการสร้าง embeddings ให้มีค่า mean เท่ากับ 0 และค่า standard deviation เท่ากับ 1 ซึ่งคำนวณได้โดยการลบค่า mean ออกจากค่าหลัก และ หารโดย standard deviation",
			explain: "นี่เป็นขั้นตอน normalization สำหรับงานด้าน computer vision ที่เอาไว้ใช้ปรับค่าของ pixel ให้เป็นมาตรฐาน แต่วิธีนี้ใช้ไม่ได้ใน NLP"
		}
	]}
/>

### 7. อะไรคือขั้นตอนการ pre-tokenization ของ subword tokenizer?

<Question
	choices={[
		{
			text: "ขั้นตอนก่อนการ tokenize ที่มีการทำ data augmentation เช่น random masking",
			explain: "ไม่ถูก ขั้นตอนนี้เป็นส่วนหนึ่งในการประมวลผลหลัก"
		},
		{
			text: "ขั้นตอนก่อนการ tokenize ที่ข้อความจะถูกทำความสะอาด (cleanup)",
			explain: "ไม่ถูก ขั้นตอนนี้เรียกว่า normalization"
		},
		{
			text: "ขั้นตอนก่อนการ tokenize ที่ข้อความจะถูกแยกออกเป็นคำๆ",
			explain: "ถูกต้อง!",
			correct: true
		},
        {
			text: "ขั้นตอนก่อนการ tokenize ที่ข้อความจะถูกแยกออกเป็น token",
			explain: "ไม่ถูก นี่คือขั้นตอนหลักของ tokenizer"
		}
	]}
/>

### 8. เลือกข้อความที่ถูกต้อง เกี่ยวกับ BPE model?

<Question
	choices={[
		{
			text: "BPE คืออัลกอริทึมแบบ subword ที่เริ่มการเทรนด้วย vocabulary ขนาดเล็ก และมันจะเรียนกฎเพื่อรวม token (merge rules)",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "BPE คืออัลกอริทึมแบบ subword ที่เริ่มการเทรนด้วย vocabulary ขนาดใหญ่ และมันจะพยายามจะลบ token ออกจาก vocabulary เรื่อยๆ",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
		{
			text: "BPE มีเป้าหมายคือการเรียนกฎเพื่อรวม token เข้าด้วยกัน (merge rules) โดยมันจะรวมคู่ของ token ที่พบบ่อยที่สุดเข้าด้วยกัน",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "BPE มีเป้าหมายคือการเรียนกฎเพื่อรวม token เข้าด้วยกัน (merge rules) มันจะรวมคู่ของ token ที่มี score สูงที่สุดเข้าด้วยกัน ถ้าหากคู่ token นี้มีจำนวนมากและแต่ละ token ในคู่มีจำนวนน้อย คู่นี้จะมี score สูง",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
		{
			text: "BPE ตัดคำออกเป็นคำย่อย (subwords) โดยแบ่งแต่ละคำออกเป็นตัวอักษร แล้วใช้ merge rules เพื่อรวมคำย่อยพวกนี้เข้าด้วยกัน",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "BPE ตัดคำออกเป็นคำย่อย (subwords) โดยจะแบ่งคำเมื่อเจอคำย่อยที่ยาวที่สุดที่อยู่ใน vocabulary เริ่มจากต้นคำ จากนั้น มันจะทำแบบเดียวกันนี้กับส่วนที่เหลือของข้อความไปเรื่อยๆ",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
	]}
/>

### 9. เลือกข้อความที่ถูกต้อง เกี่ยวกับ WordPiece model?

<Question
	choices={[
		{
			text: "WordPiece คืออัลกอริทึมแบบ subword ที่เริ่มการเทรนด้วย vocabulary ขนาดเล็ก และเรียนกฎสำหรับการรวม token (merge rules)",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "WordPiece คืออัลกอริทึมแบบ subword ที่เริ่มการเทรนด้วย vocabulary ขนาดใหญ่ และมันจะพยายามจะลบ token ออกจาก vocabulary เรื่อยๆ",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
		{
			text: "WordPiece มีเป้าหมายคือการเรียนกฎเพื่อรวม token เข้าด้วยกัน (merge rules) โดยมันจะรวมคู่ของ token ที่พบบ่อยที่สุดเข้าด้วยกัน",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
		{
			text: "WordPiece มีเป้าหมายคือการเรียนกฎเพื่อรวม token เข้าด้วยกัน (merge rules) มันจะรวมคู่ของ token ที่มี score สูงที่สุดเข้าด้วยกัน ถ้าหากคู่ token นี้มีจำนวนมากและแต่ละ token ในคู่มีจำนวนน้อย คู่นี้จะมี score สูง",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "WordPiece ตัดคำให้เป็นคำย่อย (subwords) โดยเลือก segmentation ที่มีความเป็นไปได้มากที่สุดเป็นคำตอบ",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
		{
			text: "WordPiece ตัดคำออกเป็นคำย่อย (subwords) โดยจะแบ่งคำเมื่อเจอคำย่อยที่ยาวที่สุดที่อยู่ใน vocabulary เริ่มจากต้นคำ จากนั้น มันจะทำแบบเดียวกันนี้กับส่วนที่เหลือของข้อความไปเรื่อยๆ",
			explain: "ถูกต้อง นี่คือวิธีที่ WordPiece ทำการ encoding",
			correct: true
		},
	]}
/>

### 10. เลือกข้อความที่ถูกต้อง เกี่ยวกับ Unigram model?

<Question
	choices={[
		{
			text: "Unigram คืออัลกอริทึมแบบ subword ที่เริ่มการเทรนด้วย vocabulary ขนาดเล็ก และเรียนกฎสำหรับการรวม token (merge rules)",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
		{
			text: "Unigram คืออัลกอริทึมแบบ subword ที่เริ่มการเทรนด้วย vocabulary ขนาดใหญ่ และมันจะพยายามจะลบ token ออกจาก vocabulary เรื่อยๆ",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "Unigram จะปรับ vocabulary โดยพยายามลดให้ค่า loss ของ corpus",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "Unigram จะปรับ vocabulary โดยเลือกเก็บ token ที่มีความถี่สูงไว้",
			explain: "ไม่ถูก"
		},
		{
			text: "Unigram WordPiece ตัดคำให้เป็นคำย่อย (subwords) โดยเลือก segmentation ที่มีความเป็นไปได้มากที่สุดเป็นคำตอบ",
			explain: "ถูกต้อง!",
			correct: true
		},
		{
			text: "Unigram ตัดคำออกเป็นคำย่อย (subwords) โดยแบ่งแต่ละคำออกเป็นตัวอักษร แล้วใช้ merge rules เพื่อรวมคำย่อยพวกนี้เข้าด้วยกัน",
			explain: "ไม่ถูก นี่คือคำอธิบายของอัลกอริทึมอีกประเภทต่างหาก"
		},
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/10.mdx" />

### การสร้าง tokenizer ทีละขั้นตอน
https://huggingface.co/learn/course/th/chapter6/8.md

# การสร้าง tokenizer ทีละขั้นตอน

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section8.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section8.ipynb"},
]} />

จากบทก่อนๆ คุณจะเห็นว่า การตัดคำ ประกอบไปด้วยหลายขั้นตอน :

- Normalization (หรือ การปรับข้อความให้เป็นมาตรฐาน หมายถึงการทำความสะอาดข้อความ เช่น ลบช่องว่างหรือเครื่องหมายเน้นเสียง รวมถึงการทำ Unicode normalization และอื่นๆ)
- Pre-tokenization (ขั้นตอนก่อนตัดคำ หมายถึง การแยกข้อความออกเป็นคำๆ)
- ส่ง input เข้าไปในโมเดล (แยกคำที่ได้จากขั้นตอน pre-tokenization ออกเป็นคำย่อยหลายๆคำ)
- Post-processing (ขั้นตอนปรับแต่งผลลัพธ์ เช่น การใส่ token พิเศษของ tokenizer เข้าไปในผลลัพธ์, การสร้าง attention mask และ token type IDs)

เพื่อเป็นการเตือนความจำ มาดูกระบวนการโดยรวมอีกครั้ง :

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>


🤗 Tokenizers library เป็นเครื่องมือสำหรับช่วยดำเนินการขั้นตอนพวกนี้ โดยคุณสามารถผสมผสานเครื่องมือพวกนี้ตามความต้องการได้
ในบทนี้เราจะมาเรียนวิธีสร้าง tokenizer ขึ้นมาตั้งแต่ต้น แทนที่จะเทรนจากตัวที่ถูก implement แล้วด้วยข้อมูลใหม่เท่านั้น อย่างที่เราได้ลองทำกันใน[บทที่ 2](/course/chapter6/2) เมื่อจบบทนี้ คุณจะสามารถสร้าง tokenizer แบบใดก็ได้ตามที่คุณต้องการ

<Youtube id="MR8tZm5ViWU"/>

library นี้ ประกอบด้วยส่วนหลักคือ `Tokenizer` class ที่มีส่วนประกอบย่อยสำคัญอื่นๆ แบ่งเป็นหลาย submodules

- `normalizers` ประกอบไปด้วย `Normalizer` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/normalizers)).
- `pre_tokenizers` ประกอบไปด้วย `PreTokenizer` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)).
- `models` ประกอบไปด้วย `Model` หลายประเภทที่คุณสามารถใช้ได้ เช่น `BPE`, `WordPiece`, และ `Unigram` (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/models)).
- `trainers` ประกอบไปด้วย `Trainer` หลายประเภทที่คุณสามารถใช้เพื่อเทรนโมเดลด้วย corpus ที่มีได้ (แต่ละโมเดลจะมีเทรนเนอร์อย่างละตัว; รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/trainers)).
- `post_processors` ประกอบไปด้วย `PostProcessor` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/post-processors))
- `decoders` ประกอบไปด้วย `Decoder` หลายประเภทที่ใช้เพื่อ decode ผลลัพธ์จากการ tokenization (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/components#decoders))

คุณสามารถดูรายชื่อของส่วนประกอบสำคัญๆต่างๆได้[ที่นี่](https://huggingface.co/docs/tokenizers/components)

## การโหลด corpus

ในการเทรน tokenizer ตัวใหม่ เราจะใช้ corpus เล็กๆ เพื่อที่การคำนวณจะได้รวดเร็ว
การเรียกใช้งาน corpus จะทำคล้ายๆกับวิธีที่เราใช้ใน[ตอนต้นของบทนี้](/course/chapter6/2) แต่ครั้งนี้เราจะใช้ชุดข้อมูลชื่อ [WikiText-2](https://huggingface.co/datasets/wikitext)

```python
from datasets import load_dataset

dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train")


def get_training_corpus():
    for i in range(0, len(dataset), 1000):
        yield dataset[i : i + 1000]["text"]
```

`get_training_corpus()` เป็น generator ที่จะ yield ข้อมูลในรูปแบบ batch โดยแต่ละ batch ประกอบไปด้วย 1,000 ข้อความ
🤗 Tokenizers สามารถเทรนได้จากไฟล์ข้อความโดยตรง ดังนั้นเราจะสร้างไฟล์ข้อความ ที่ประกอบไปด้วย ข้อความทั้งหมดจาก WikiText-2

```python
with open("wikitext-2.txt", "w", encoding="utf-8") as f:
    for i in range(len(dataset)):
        f.write(dataset[i]["text"] + "\n")
```

ต่อไปเราจะพาคุณสร้าง tokenizer แบบ BERT, GPT-2, and XLNet ของคุณเอง ทีละขั้นตอน
ซึ่งคุณก็จะได้ลองใช้งาน tokenization algorithm ต่างๆที่ได้เรียนมาแล้ว เช่น WordPiece, BPE, and Unigram มาเริ่มจาก BERT กัน

## สร้าง WordPiece tokenizer ตั้งแต่เริ่มต้น

ในการสร้าง tokenizer โดยใช้ 🤗 Tokenizers library เราจะเริ่มจากการสร้าง `Tokenizer` object จาก `model` และตั้งค่า attribute ต่างๆ ได้แก่ `normalizer`, `pre_tokenizer`, `post_processor`, และ `decoder` ด้วยค่าที่เราต้องการ

ในตัวอย่างนี้ เราจะสร้าง `Tokenizer` ด้วย WordPiece :

```python
from tokenizers import (
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    Tokenizer,
)

tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))
```

จากนั้นเราจะตั้งค่า `unk_token` เพื่อบอกโมเดลว่าให้มัน return ค่าอะไรหากมันเจอตัวอักษรที่มันไม่รู้จัก
ส่วน argument อื่นๆที่เราสามารถตั้งค่าได้ก็คือ `vocab` (แต่เนื่องจากเราจะเทรนโมเดล ทำให้ตอนนี้เรายังไม่ต้องตั้งค่านี้) และ `max_input_chars_per_word` ซึ่งหมายถึง ความยาวสูงสุดของแต่ละคำ (คำที่ยาวกว่าค่านี้จะถูกแบ่งเป็นหลายๆส่วน)

ขั้นตอนแรกคือ normalization เนื่องจาก BERT เป็นโมเดลที่ได้รับความนิยมมาก เราจึงมี `BertNormalizer` เฉพาะ ซึ่งมี option ต่างๆดังนี้: `lowercase`, `strip_accents`, `clean_text` (ลบ control characters และแทนที่ช่องว่างที่อยู่ต่อกันหลายๆอันด้วยช่องว่างเดียว), และ `handle_chinese_chars` (เพิ่มช่องว่างในตัวอักษรจีน)

เราจะมาเลียนแบบ `bert-base-uncased` tokenizer โดยตั้งค่า normalizer ดังนี้ :

```python
tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True)
```

แต่ปกติแล้ว สำหรับ tokenizer ใหม่ คุณอาจจะไม่สามารถใช้ normalizer ที่จาก 🤗 Tokenizers library ได้ ดังนั้นเราจะมาเรียนวิธีการสร้าง BERT normalizer เองกัน

library นี้ มี normalizer เพื่อ `Lowercase` และเพื่อ `StripAccents` ซึ่งคุณสามารถรวมทั้งสองตัวได้โดยใช้ `Sequence` :

```python
tokenizer.normalizer = normalizers.Sequence(
    [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()]
)
```

เราจะใช้ `NFD` Unicode normalizer ด้วย เพื่อที่จะให้ `StripAccents` สามารถหาสัญลักษณ์ accents ได้ และจะได้ลบพวกมันออก

เพื่อเช็คผลลัพธ์ของ normalizer เราจะใช้ `normalize_str()` method จาก `normalizer` :

```python
print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
hello how are u?
```

<Tip>

**รายละเอียดเพิ่มเติม** ถ้าคุณทดลองใช้งาน normalizer ทั้งสองเวอร์ชันกับข้อความที่มีตัวอักษร unicode `u"\u0085"` คุณจะได้ผลลัพธ์ที่แตกต่างกัน
อย่างไรก็ตาม เราไม่อยากทำให้เวอร์ชันที่สร้างจาก `normalizers.Sequence` ของเรานั้นซับซ้อนเกินไป เราจึงไม่ใช้ Regex ที่ `BertNormalizer` ใช้เวลาที่ `clean_text` ถูกตั้งค่าเป็น `True` ซึ่งเป็นค่าตั้งต้น
แต่คุณไม่ต้องกังวลไป เพราะมันยังมีวิธีที่จะทำให้ผลลัพธ์ออกมาเป็นเหมือนกันโดยที่ไม่ต้องใช้ `BertNormalizer` นั่นคือโดยการเพิ่ม `normalizers.Replace` สองครั้ง เข้าไปใน `normalizers.Sequence`

</Tip>

ขั้นตอนต่อไปคือ การ pre-tokenization เราจะใช้ `BertPreTokenizer` ที่ถูกสร้างมาแล้ว :

```python
tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()
```

หรือจะใช้ตัวที่เราสร้างขึ้นมาเองก็ได้ :

```python
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
```

โปรดทราบว่า `Whitespace` pre-tokenizer จะตัดข้อความตรงที่มีช่องว่าง และ รวมถึงตัวสัญลักษณ์ที่ไม่ใช้ตัวอักษร ตัวเลข หรือ underscore หรือพูดอีกแบบก็คือ มันจะแบ่งข้อความตรงที่มีช่องว่างและเครื่องหมายวรรคตอน นั่นเอง

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

แต่ถ้าคุณต้องการจะแบ่งข้อความตามช่องว่างเท่านั้น ให้ใช้ `WhitespaceSplit` pre-tokenizer :

```python
pre_tokenizer = pre_tokenizers.WhitespaceSplit()
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))]
```

เหมือนกับใน normalizer เราสามารถใช้ `Sequence` เพื่อรวมหลายๆ pre-tokenizers เข้าด้วยกัน :

```python
pre_tokenizer = pre_tokenizers.Sequence(
    [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()]
)
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```

```python out
[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)),
 ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))]
```

ขั้นตอนต่อไปใน tokenization pipeline คือการใส่ input เข้าไปในโมเดล
เราได้สร้างโมเดลขึ้นมาแล้ว แต่เรายังคงต้องเทรนมัน ซึ่งเราจะใช้ `WordPieceTrainer`

สิ่งสำคัญที่คุณต้องจำคือ เวลาสร้าง (instantiate) trainer ใน 🤗 Tokenizers คุณจะต้องเพิ่ม token พิเศษต่างๆ เข้าไปในเทรนเนอร์เอง ไม่เช่นนั้น มันจะไม่เพิ่ม token พวกนี้เข้าไปใน vocabulary เพราะว่า token พิเศษไม่ได้อยู่ใน training corpus :

```python
special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens)
```

นอกจากเราจะสามารถตั้งค่า `vocab_size` และ `special_tokens` แล้ว เรายังสามารถตั้งค่า `min_frequency` (ความถี่ขั้นต่ำของคำที่ต้องมี เพื่อที่จะได้ถูกเพิ่มลงไปใน vocabulary) หรือ `continuing_subword_prefix` (ถ้าหากเราต้องการใช้สัญลักษณ์อื่น ที่ไม่ใช่`##`) ได้อีกด้วย

เราจะเทรนโมเดลโดยรันคำสั่งต่อไปนี้ :

```python
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

คุณสามารถใช้ไฟล์ข้อความเพื่อเทรนได้ด้วย (แต่ก่อนอื่น คุณจะต้องสร้างโมเดลขึ้นมาใหม่โดยใช้ `WordPiece` ที่ยังว่างเปล่าอยู่) :

```python
tokenizer.model = models.WordPiece(unk_token="[UNK]")
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

สำหรับทั้งสองกรณี คุณสามารถทดลองใช้ tokenizer กับข้อความได้ โดยเรียกใช้ `encode()` method :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.']
```

`encoding` ที่เราได้คือ `Encoding` ที่ประกอบไปด้วยข้อมูลจำเป็นต่างๆสำหรับ tokenizer ได้แก่ `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, และ `overflowing`

ขั้นตอนสุดท้ายของ pipeline คือ post-processing
เราจะเพิ่ม `[CLS]` ไว้ที่ตอนต้นของผลลัพธ์จากการ tokenize และ `[SEP]` ไว้ที่ตอนท้าย หรือหลังสิ้นสุดประโยค ถ้า input ของเรามีสองประโยค

เราจะใช้ `TemplateProcessor` เพื่อช่วยเราทำ แต่ก่อนอื่นเราต้องหา ID ของ `[CLS]` และ `[SEP]` ก่อน :

```python
cls_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")
print(cls_token_id, sep_token_id)
```

```python out
(2, 3)
```

เราจะมาสร้าง template ให้กับ `TemplateProcessor` โดยจะต้องกำหนดว่าเราต้องการให้มันประมวลผล input ที่เป็น ประโยคเดี่ยว และ input ที่มีสองประโยคอย่างไร
สำหรับทั้งสองกรณี เราจะต้องกำหนด token พิเศษ เพื่อแทนประโยคขึ้นมา สำหรับประโยคเดี่ยวหรือประโยคแรก เราจะใช้ `$A` ส่วนสำหรับประโยคที่สอง เราจะใช้ `$B`
สำหรับ token พิเศษพวกนี้และประโยค เราจะสร้าง token type ID ขึ้นมา โดยจะเขียน ID นี้หลังเครื่องหมาย colon

template ของ BERT มีรายละเอียดดังนี้ :

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single=f"[CLS]:0 $A:0 [SEP]:0",
    pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1",
    special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)],
)
```

อย่าลืมว่า เราจะต้องให้ข้อมูลโมเดลเกี่ยวกับ ID ของ token พิเศษด้วย เพื่อที่โมเดลจะได้แปลงมันได้อย่างถูกต้อง

เราจะกลับมาดูตัวอย่างก่อนหน้ากัน :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]']
```

ถ้าเราใส่ input ที่เป็นสองประโยค เราจะได้ :

```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
```

ขั้นตอนสุดท้าย เราจะเพิ่ม decoder เข้าไปใน pipeline :

```python
tokenizer.decoder = decoders.WordPiece(prefix="##")
```

มาดูผลลัพธ์ของ `encoding` กัน :

```python
tokenizer.decode(encoding.ids)
```

```python out
"let's test this tokenizer... on a pair of sentences."
```

เยี่ยมมาก! ตอนนี้เราสามารถบันทึก tokenizer นี้เป็นไฟล์ JSON ได้แล้ว ดังนี้ :

```python
tokenizer.save("tokenizer.json")
```

คุณสามารถโหลดไฟล์นี้ให้เป็น `Tokenizer` object ได้โดยใช้ `from_file()` method :

```python
new_tokenizer = Tokenizer.from_file("tokenizer.json")
```

การจะนำ tokenizer นี้มาใช้ใน 🤗 Transformers เราจะต้อง wrap มันให้เป็น `PreTrainedTokenizerFast` ก่อน
โดยเราใช้ class ปกติ (ถ้า tokenizer ของเรามีโครงสร้างสอดคล้องกันโมเดลหลักเราที่จะใช้งาน) หรือ class ที่ถูกสร้างขึ้นมาแล้ว เช่น `BertTokenizerFast`
ในกรณีที่คุณสร้าง tokenizer ขึ้นมาเองอย่างที่เราได้สอนไว้ข้างต้น คุณจะต้องใช้ตัวเลือกแรก

การจะ wrap tokenizer ของเราให้เป็น `PreTrainedTokenizerFast` เราจะต้องส่งผ่าน tokenizer ของเราเข้าไปเป็น `tokenizer_object` หรือ ส่งผ่านไฟล์ของ tokenizer เป็น `tokenizer_file`
สิ่งสำคัญอีกอย่างคือ คุณจะต้องส่งผ่านข้อมูลเกี่ยวกับ token พิเศษ ต่างๆให้โมเดลเอง เพราะว่า class นี้จะไม่สามารถ infer จาก `tokenizer` object ได้เอง ว่า token ตัวไหนเป็น mask token เช่น `[CLS]` :

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]",
)
```

ถ้าคุณใช้ class ที่เฉพาะเจาะจง เช่น `BertTokenizerFast` คุณเพียงแค่ต้องเพิ่มข้อมูลเกี่ยวกับ token พิเศษที่ต่างจากตัวที่โมเดลใช้อยู่แล้ว ในตัวอย่างของเรา เราไม่ได้ใช้ token พิเศษอื่น จึงไม่ต้องเพิ่มอะไร :

```python
from transformers import BertTokenizerFast

wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer)
```

ตอนนี้เราก็สามารถใช้ tokenizer ที่เราได้สร้างขึ้นมาเอง เหมือนกับ tokenizer ตัวอื่นๆจาก 🤗 Transformers ได้แล้ว นอกจากนั้นคุณยังสามารถเซฟมันได้ โดยใช้ `save_pretrained()` หรืออัพโหลดมันไปที่ Hub โดยใช้ `push_to_hub()`

หลังจากที่เราได้ดูกันแล้วว่าจะสร้าง WordPiece tokenizer อย่างไร เราจะมาดูกันว่า จะทำแบบเดียวกันกับ BPE tokenizer ได้อย่างไร เราจะอธิบายคร่าวๆเท่านั้น เพราะคุณได้เรียนรายละเอียดมาแล้ว และจะพูดถึงแค่ข้อแตกต่างเท่านั้น

## การสร้าง BPE tokenizer ตั้งแต่เริ่มต้น

เราจะมาสร้าง GPT-2 tokenizer กัน เช่นเดียวกับตัวอย่างของ BERT tokenizer เราจะเริ่มด้วยกัน initialize `Tokenizer` ด้วยโมเดล BPE :

```python
tokenizer = Tokenizer(models.BPE())
```

เราสามารถสร้างโมเดลนี้ด้วย vocabulary ที่มีอยู่ได้ โดยส่งผ่าน `vocab` และ `merges` แต่เราจะเทรนตั้งแต่ต้น แปลว่าเราไม่จำเป็นต้องทำขั้นตอนนี้
เราไม่จำเป็นต้องกำหนด `unk_token` เพราะ GPT-2 ใช้ byte-level BPE

นอกจากนั้น GPT-2 ยังไม่ใช้ normalizer อีกด้วย เราจึงจะข้ามขั้นตอนนี้ไปทำ pre-tokenization เลย :

```python
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
```

`add_prefix_space=False` หมายถึงเราจะไม่เพิ่มช่องว่างตรงต้นประโยค (ค่า default จะมีการเพิ่มช่องว่างนี้)

มาดูผลลัพธ์ตัวอย่างของการ pre-tokenization กัน :

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!")
```

```python out
[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)),
 ('tokenization', (15, 27)), ('!', (27, 28))]
```

ขั้นตอนต่อไปคือการเทรนโมเดล  สำหรับ GPT-2 มันจะมี token พิเศษตัวเดียวคือ end-of-text token (อยู่ตรงท้ายของข้อความ) :

```python
trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

เช่นเดียวกันกับ `WordPieceTrainer` คุณสามารถกำหนด `vocab_size`, `special_tokens`, `min_frequency` ได้ ถ้าหากคุณใช้ end-of-word suffix (เช่น `</w>`) คุณก็สามารถตั้งค่ามันได้ด้วย `end_of_word_suffix`

คุณสามารถเทรนด้วยไฟล์ข้อความได้ด้วย :

```python
tokenizer.model = models.BPE()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

มาดูผลลัพธ์ของการ tokenize ข้อความตัวอย่างกัน :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.']
```

เราจะทำการ post-processing แบบ byte-level ให้กับ GPT-2 tokenizer ดังนี้ :

```python
tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)
```

`trim_offsets = False` แปลว่าเราจะไม่เปลี่ยนแปลงค่า offset ของ token ที่ขึ้นต้นด้วย `Ġ` ซึ่งแปลว่าตำแหน่งเริ่มต้นของ offset จะหมายถึง ช่องว่าง และไม่ใช่ตัวอักษรแรกของ token นั้น

มาดูผลลัพธ์ของข้อความที่เราเพิ่งจะ encode กันไป ในตัวอย่างนี้ token ในตำแหน่งที่ 4 คือ `'Ġtest'` :

```python
sentence = "Let's test this tokenizer."
encoding = tokenizer.encode(sentence)
start, end = encoding.offsets[4]
sentence[start:end]
```

```python out
' test'
```

สุดท้ายเราจะเพิ่มส่วนที่เป็น byte-level decoder :

```python
tokenizer.decoder = decoders.ByteLevel()
```

เช็คดูอีกทีว่าผลลัพธ์ถูกต้องหรือไม่ :

```python
tokenizer.decode(encoding.ids)
```

```python out
"Let's test this tokenizer."
```

เยี่ยมมาก! ตอนนี้คุณสามารถเซฟโมเดลได้ ส่วนขั้นต่อไปคือ เราจะ wrap tokenizer ของเราให้เป็น `PreTrainedTokenizerFast` หรือ `GPT2TokenizerFast` เพื่อจะได้เรียกใช้มันได้ใน 🤗 Transformers

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)
```

หรือ :

```python
from transformers import GPT2TokenizerFast

wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer)
```

ต่อไปเราจะสอนวิธีสร้าง Unigram tokenizer บ้าง

## การสร้าง Unigram tokenizer ตั้งแต่เริ่มต้น

เราจะสร้าง XLNet tokenizer โดยเริ่มจาก initialize `Tokenizer` ด้วย Unigram model !

```python
tokenizer = Tokenizer(models.Unigram())
```

คุณสามารถ initialize มันโดยส่งผ่าน vocabulary ที่มีอยู่เข้าไปด้วย

ในขั้นตอน normalization โมเดล XLNet จะมีการแทนที่ตัวอักษรต่างๆ :

```python
from tokenizers import Regex

tokenizer.normalizer = normalizers.Sequence(
    [
        normalizers.Replace("``", '"'),
        normalizers.Replace("''", '"'),
        normalizers.NFKD(),
        normalizers.StripAccents(),
        normalizers.Replace(Regex(" {2,}"), " "),
    ]
)
```

โค้ดข้างบนนี้จะแทนที่ <code>``</code> และ <code>''</code> ด้วย <code>"</code>
และถ้ามีช่องว่างที่อยู่ต่อๆกัน มันจะถูกแปลงให้เป็นช่องว่างเดียว และสุดท้ายมันจะลบสัญลักษณ์ accent ออกด้วย

pre-tokenizer ที่ใช้ใน SentencePiece tokenizer คือ `Metaspace`  :

```python
tokenizer.pre_tokenizer = pre_tokenizers.Metaspace()
```

มาดูผลลัพธ์ของการ pre-tokenization กับข้อความตัวอย่างกัน  :

```python
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!")
```

```python out
[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))]
```

ขั้นต่อไปคือการเทรน  XLNet มีการใช้ token พิเศษอยู่จำนวนหนึ่ง :

```python
special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]
trainer = trainers.UnigramTrainer(
    vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>"
)
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

สิ่งสำคัญเวลาใช้ `UnigramTrainer` คือ อย่าลืมกำหนด argument `unk_token`
นอกจากนั้นคุณยังสามารถตั้งค่า argument อื่นๆที่ต้องใช้กับ Unigram algorithm ได้ด้วย เช่น `shrinking_factor` (ค่าเริ่มต้นคือ  0.75) หรือ `max_piece_length` (ค่าเริ่มต้นคือ 16)

เราสามารถเทรนจากไฟล์ข้อความได้ด้วย  :

```python
tokenizer.model = models.Unigram()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

ลอง tokenize ตัวอย่างง่ายๆดู  :

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.']
```

XLNet จะเพิ่ม token พิเศษ `<cls>` ใส่ในตอนท้ายของประโยค และตั้งค่า type ID มันเป็น 2 เพื่อให้มันแตกต่างจาก token อื่น
การทำแบบนี้ถือว่าเป็นการ padding ทางด้ายซ้าย

เพื่อจัดการกับ token พิเศษ เราจะต้องสร้าง template ขึ้นมา ก่อนอื่นเราต้องดูว่า ID ของ `<cls>` และ `<sep>` คืออะไร :

```python
cls_token_id = tokenizer.token_to_id("<cls>")
sep_token_id = tokenizer.token_to_id("<sep>")
print(cls_token_id, sep_token_id)
```

```python out
0 1
```

นี่คือตัวอย่างการสร้าง template :

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single="$A:0 <sep>:0 <cls>:2",
    pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2",
    special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)],
)
```

เราจะทดลองใช้งานมันโดยการ encode ประโยค input สองประโยค :

```python
encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!")
print(encoding.tokens)
print(encoding.type_ids)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair',
  '▁of', '▁sentence', 's', '!', '<sep>', '<cls>']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
```

จากนั้นตั้งค่า decoder เป็น `Metaspace` :

```python
tokenizer.decoder = decoders.Metaspace()
```

ตอนนี้เราก็เสร็จแล้ว คุณสามารถเซฟ tokenizer และ wrap มันให้เป็น `PreTrainedTokenizerFast` หรือ `XLNetTokenizerFast` ก็ได้ ถ้าคุณต้องการใช้มันใน 🤗 Transformers

สิ่งสำคัญอีกอย่างเวลาใช้ `PreTrainedTokenizerFast` ก็คือเราจะต้องบอก 🤗 Transformers library ว่าเราได้ทำการ padding ทางซ้าย :

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<s>",
    eos_token="</s>",
    unk_token="<unk>",
    pad_token="<pad>",
    cls_token="<cls>",
    sep_token="<sep>",
    mask_token="<mask>",
    padding_side="left",
)
```

อีกวิธีก็คือ :

```python
from transformers import XLNetTokenizerFast

wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer)
```

ตอนนี้ คุณก็ได้เรียนรู้ขั้นตอนในการสร้าง tokenizer ขึ้นมาเองแล้ว โดยการใช้เครื่องมือจาก 🤗 Tokenizers library และได้เรียนวิธีการนำ tokenizer ของคุณไปใช้ใน 🤗 Transformers อีกด้วย


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/8.mdx" />

### Normalization และ pre-tokenization
https://huggingface.co/learn/course/th/chapter6/4.md

# Normalization และ pre-tokenization

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section4.ipynb"},
]} />

ก่อนที่เราจะเจาะลึกเกี่ยวกับอัลกอริทึม 3 แบบ ของ subword tokenization ที่ใช้กับโมเดล Transformer (Byte-Pair Encoding [BPE], WordPiece, และ Unigram)
อันดับแรก เราจะมาเรียนเกี่ยวกับขั้นตอน preprocessing ที่ tokenizer ใช้เพื่อจัดแต่งข้อความก่อนการ tokenize หลักกันก่อน

บทนี้จะเป็นภาพรวมระดับสูงของขั้นตอนต่างๆในไปป์ไลน์ tokenization:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

ก่อนแยกข้อความออกเป็น subtokens ตัว tokenizer จะดำเนินการสองขั้นตอน คือ _normalization_ และ _pre-tokenization_

## Normalization

<Youtube id="4IIC2jI9CaU"/>

ขั้นตอน normalization เกี่ยวข้องกับทำความสะอาดข้อมูลทั่วไป เช่น การลบช่องว่างที่ไม่จำเป็นแปลงข้อความเป็นตัวพิมพ์เล็ก และ/หรือ การลบเครื่องหมายเน้นเสียงออก (accents) หากคุณคุ้นเคยกับ [Unicode normalization](http://www.unicode.org/reports/tr15/) (เช่น NFC หรือ NFKC) นี่ก็เป็นสิ่งที่ tokenizer อาจใช้เช่นกัน

🤗 Transformers `tokenizer` มี attribute ที่เรียกว่า `backend_tokenizer` ที่เราสามารถเรียกใช้ได้ เพื่อเข้าถึง tokenizer พื้นฐานของ 🤗 Tokenizers library:

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
print(type(tokenizer.backend_tokenizer))
```

```python out
<class 'tokenizers.Tokenizer'>
```

attribute ชื่อ `normalizer` ของ `tokenizer` object มี method ชื่อ `normalize_str()` ที่เราสามารถใช้เพื่อดูผลลัพธ์ของการ normalization ได้:

```py
print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
'hello how are u?'
```

ในตัวอย่างนี้ เนื่องจากเราเลือกใช้ checkpoint `bert-base-uncased` การ normalization จึงแปลงข้อความเป็นตัวพิมพ์เล็กและลบเครื่องหมายเน้นเสียงออก

<Tip>

✏️ **ลองดูสิ!** โหลด tokenizer จาก checkpoint `bert-base-cased` และใช้มันกับ input เดียวกันกับข้างบนนี้ แล้วดูว่าผลลัพธ์ต่างกันอย่างไร ระหว่าง tokenizer เวอร์ชัน cased และ uncased

</Tip>

## Pre-tokenization

<Youtube id="grlLV8AIXug"/>

ในหัวข้อถัดไปคุณจะได้เรียนรู้ว่า เราไม่สามารถเทรน tokenizer จาก raw text โดยตรงได้ ก่อนอื่นเราจะต้องแยกข้อความเป็น entity เล็กๆ เช่นแยกออกเป็น คำ ขั้นตอนพวกนี้คือการ pre-tokenization ดังที่คุณเห็นใน[บทที่ 2](/course/chapter2) tokenizer แบบ word-based จะแบ่งข้อความเป็นคำ โดยการแบ่งตรงช่องว่าง และ เครื่องหมายวรรคตอน คำที่ได้จะถูกนำมาใช้เป็นขอบเขตของ subtokens ที่ tokenizer เอาไว้ใช้ในการเทรน

สำหรับ fast tokenizer ถ้าหากเราอยากจะดูว่ามันทำอะไรบ้างในขั้นตอน pre-tokenization เราจะใช้ method ชื่อ `pre_tokenize_str()` ของ attribute ชื่อ `pre_tokenizer` จาก `tokenizer` object:

```py
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

```python out
[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))]
```

คุณจะเห็นว่าตัว tokenizer มีการเก็บข้อมูลเกี่ยวกับ offsets ด้วย ซึ่งทำให้มันสามารถสร้าง offsets mapping ให้เราได้อย่างที่เห็นในบทที่แล้ว สำหรับข้อความ input ในตัวอย่างนี้ ช่องว่างสองช่อง(หลังคำว่า are) ถูกแทนที่ด้วยหนึ่งช่องว่างเท่านั้น แต่เราจะเห็นว่าค่า offsets ยังนับช่องว่างพวกนี้อยู่ สังเกตค่า offsets ตรง `are` และ `you`

เนื่องจากเราใช้ BERT tokenizer ขั้นตอน pre-tokenization คือการตัดข้อความตรงช่องว่างและเครื่องหมายวรรคตอนเท่านั้น ส่วน tokenizer อื่นๆ อาจจะมีการหลักการตัดคำแบบอื่นได้ ตัวอย่างเช่น ถ้าเราใช้ tokenizer ของ GPT-2:

```py
tokenizer = AutoTokenizer.from_pretrained("gpt2")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

มันจะแบ่งข้อความตรงช่องว่างและเครื่องหมายวรรคตอนเช่นเดียวกัน แต่มันจะยังเก็บข้อมูลเกี่ยวกับช่องว่างไว้และใช้เครื่องหมาย `Ġ` เพื่อแทนช่องว่างพวกนี้ การทำแบบนี้ทำให้เราสามารถกู้คืนช่องว่างพวกนี้ได้ตอนที่เรา decode token เหล่านี้

```python out
[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)),
 ('?', (19, 20))]
```

สังเกตว่า ช่องว่างสองช่องจะไม่ถูกรวมเป็นหนึ่งช่องแบบใน BERT tokenizer

ในตัวอย่างสุดท้ายนี้ เราจะมาดู T5 tokenizer กัน ซึ่งใช้อัลกอริทึมที่ชื่อ SentencePiece :

```py
tokenizer = AutoTokenizer.from_pretrained("t5-small")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

```python out
[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))]
```

คล้ายกับใน GPT-2 tokenizer T5 tokenizer จะเก็บข้อมูลเกี่ยวกับช่องว่าง และแทนที่พวกมันด้วยเครื่องหมายพิเศษ (`_`) แต่มันจะแบ่งตรงช่องว่างเท่านั้น และจะไม่แบ่งตรงเครื่องหมายวรรคตอน
สังเกตว่า มันจะเพิ่มช่องว่างตรงต้นประโยคด้วย (ก่อนคำว่า `Hello`) และมันจะไม่นับช่องว่างสองช่องที่อยู่ระหว่าง `are` และ `you`

คุณได้เห็นแล้วว่า tokenizers ต่างๆ ประมวลผลข้อความอย่างไร ตอนนี้เราจะมาดูอัลกอริทึมต่างๆกัน เริ่มที่ SentencePiece ซึ่งเป็นอัลกอริทึมที่ถูกนำมาใช้อย่างกว้างขวาง จากนั้นในอีกสามหัวข้อต่อไป เราจะมาดูเกี่ยวกับอัลกอริทึม 3 แบบ ของ subword tokenization

## SentencePiece

[SentencePiece](https://github.com/google/sentencepiece) คืออัลกอริทึมสำหรับการ preprocessing ข้อความ เพื่อนำข้อความพวกนี้ไปใช้ในโมเดลต่างๆที่คุณจะได้เรียนในอีกสามบทถัดจากนี้ จะมันมองข้อความเป็นอักขระ Unicode และแทนที่ช่องว่างด้วยสัญลักษณ์พิเศษ `▁` ถ้าใช้งานร่วมกับ Unigram algorithm (ดู[บทที่ 7](/course/chapter7/7)) มันจะไม่จำเป็นต้องทำขั้นตอน pre-tokenization เลยด้วย ซึ่งมีประโยชน์สำหรับภาษาที่ไม่ได้ใช้ช่องว่างในการแบ่งคำเช่น ภาษาจีนหรือญี่ปุ่น

ความสามารถหลักอีกอย่างของ SentencePiece คือ *reversible tokenization* (การตัดคำที่แปลงกลับได้): เนื่องจากมันไม่ได้ treat พวกช่องว่างแบบพิเศษ เวลา decode ประโยคที่ตัดแล้วกลับคืน เราสามารถเชื่อม (concatenate)แต่ละ token ได้เลยและ และแทนที่ `_` ด้วยช่องว่าง ผลลัพธ์ก็คือ ข้อความที่ ถูก normalized

อย่างที่คุณได้เห็นก่อนหน้านี้ BERT tokenizer จะลบช่องว่างที่ต่อกันออก ทำให้ตอนรวม token กลับ เราจะไม่ได้ข้อความแบบเดิม

## ภาพรวมของแต่ละอัลกอริทึม

ในบทถัดไป เราจะมาเรียนรู้อย่างละเอียด เกี่ยวกับอัลกอริทึมสามแบบ สำหรับ subword tokenization ได้แก่ BPE (ใช้กับ GPT-2 และ โมเดลอื่นๆ), WordPiece (ใช้กับ BERT), และ Unigram (ใช้กับ T5 และโมเดลอื่นๆ)
ก่อนที่จะไปเริ่มกัน เรามาดูภาพรวมของแต่ละอัลกอริทึมกันก่อน คุณสามารถกลับมาดูตารางนี้ใหม่ได้หลังจากที่อ่านบทถัดไปแล้ว เพื่อจะได้เข้าใจมากขึ้น

โมเดล | BPE | WordPiece | Unigram
:----:|:---:|:---------:|:------:
การเทรน | เริ่มจาก vocabulary ขนาดเล็ก และเรียนกฎในการรวม token เข้าด้วยกัน |  เริ่มจาก vocabulary ขนาดเล็ก และเรียนกฎในการรวม token เข้าด้วยกัน | เริ่มจาก vocabulary ขนาดใหญ่ เรียนกฎเพื่อลบ token ออกจาก vocabulary
ขั้นตอนการเทรน | รวม token ถ้ามันเป็นคู่ที่พบบ่อย | รวม token ถ้ามันเป็นคู่ที่มี score ที่ดีที่สุด โดย score คำนวณจากความถี่ของคู่ token นั้น และให้คะแนนสูงถ้าแต่ละ token มีความถี่ต่ำ | ลบ token ออกจาก vocabulary เพื่อทำให้ค่า loss ลดลง โดยที่ค่า loss คำนวณจาก training corpus
สิ่งที่เรียน | กฎในการรวม token (merge rules) และ vocabulary | เรียนแค่ vocabulary | เรียน vocabulary และ score ของแต่ละ token
Encoding | แยกคำออกเป็นตัวอักษร และทำการรวมโดยใช้กฎที่เรียนระหว่างการเทรน |  หาคำย่อยที่ยาวที่สุดที่อยู่ใน vocabulary เริ่มจากต้นคำและทำต่อไปเรื่อยๆจนหมดคำ | หาการแบ่งคำที่เหมาะสมที่สุดโดยใช้ score ที่เรียนระหว่างการเทรน

ในบทต่อไปเรามาเรียนเกี่ยวกับ BPE อย่างละเอียดกัน!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/4.mdx" />

### Unigram tokenization
https://huggingface.co/learn/course/th/chapter6/7.md

# Unigram tokenization

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section7.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section7.ipynb"},
]} />

อัลกอริทึม Unigram มักจะถูกใช้บ่อยใน SentencePiece ซึ่งเป็นอีกอัลกอริทึมสำหรับการ tokenization ที่ใช้ในโมเดลเช่น AlBERT, T5, mBART, Big Bird, และ XLNet

<Youtube id="TGZfZVuF9Yc"/>

<Tip>

💡 บทนี้จะพูดถึง Unigram อย่างละเอียด เราจะเจาะลึกถึงไปถึงการ implement อัลกอริทึมนี้ คุณสามารถข้ามไปตอนท้ายได้ ถ้าคุณสนใจเพียงแค่ภาพรวมคร่าวๆเท่านั้น

</Tip>

## Training algorithm

เมื่อเทียบกับ BPE และ WordPiece การตัดคำแบบ Unigram ทำงานกลับกันคือ เริ่มจาก vocabulary ตั้งต้นขนาดใหญ่ แล้วอัลกอริทึมจะพยายามลบ token ออกจาก vocabulary จนกว่าจะได้ขนาด vocabulary ที่เราต้องการ
การสร้าง vocabulary ตั้งต้นทำได้หลายวิธี คุณอาจจะใช้คำย่อยที่พบบ่อยที่สุด หรือคุณอาจจะใช้ BPE เพื่อสร้าง vocabulary ตั้งต้น โดยตั้งค่าขนาด vocabulary ให้มีขนาดค่อนข้างใหญ่
ในการเทรนแต่ละครั้ง อัลกอริทึม Unigram จะคำนวณค่า loss ของ training corpus ซึ่งขึ้นกับ vocabulary ที่มีในขณะนั้น
จากนั้น มันจะลองลบ แต่ละ token ออกจาก vocabulary แล้วคำนวณค่า loss อีกที

เป้าหมายคือการค้นหา token ที่เมื่อลบออกแล้วจะทำให้ค่า loss เพิ่มน้อยที่สุด
token พวกนี้คือตัวที่ไม่มีผลต่อค่า loss มาก แปลว่า มันไม่มีประโยชน์มาก ทำให้เราสามารถลบมันออกได้
การคำนวณแบบนี้ค่อนข้างใช้การประมวลผลสูง เราไม่เพียงแค่ลบสัญลักษณ์ออกหนึ่งตัวเท่านั้น แต่เราลบ \\(p\\) เปอร์เซ็นของ token ที่เพิ่มค่าloss น้อยที่สุด (\\(p\\) คือ hyperparameter ที่เราสามารถตั้งค่าได้ ปกติค่าจะอยู่ที่ 10 หรือ 20)
เราจะรันขั้นตอนนี้จนกว่าจะได้ขนาด vocabulary ที่ต้องการ
อย่างไรก็ตาม เราจะไม่ลบตัวอักษรตั้งต้น (base characters) เพื่อที่จะได้มั่นใจว่าเราจะยังสามารถ tokenize ทุกคำได้
ส่วนหลักของอัลกอริทึมนี้คือการคำนวณค่า loss ของ corpus และดูว่าค่า loss มีการเปลี่ยนแปลงอย่างไรถ้าเราลบ token ตัวใดตัวหนึ่งออกจาก vocabulary เราจะมาอธิบายว่ามันทำงานอย่างไรกัน

ขั้นตอนนี้จะใช้อัลกอริทึมสำหรับ tokenization ของโมเดล Unigram
เราจะใช้ corpus เดียวกันกับในตัวอย่างก่อนๆ :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

โดยที่เราจะใช้ทุกๆคำย่อยของแต่ละคำ มาสร้าง vocabulary ตั้งต้น :

```
["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"]
```

## Tokenization algorithm

โมเดล Unigram เป็น language model ประเภทหนึ่งที่ประมวลผลแต่ละ token ในข้อความ โดยมองว่ามันไม่มีส่วนเกี่ยวข้องกับ token ตัวอื่นๆ (not dependent)
Unigram ถือว่าเป็น language model ประเภทที่ซับซ้อนน้อยที่สุด เพราะว่าเวลาที่เราคำนวณความน่าจะเป็น(probability)ของ token ที่อยู่ในข้อความใดข้อความหนึ่ง เราไม่ต้องพิจารณา token ตัวอื่นๆในข้อความด้วย
ดังนั้น ถ้าเราใช้ Unigram language model เพื่อผลิตข้อความ มันก็จะผลิตคำที่พบบ่อยที่สุดทุกๆครั้ง (language model จะ predict คำที่มีความน่าจะเป็นสูงที่สุดเวลาที่มันผลิตข้อความ)

ความน่าจะเป็นของ token หนึ่งจะเท่ากับความถี่ของคำนั้นๆที่เราพบใน corpus หารกับ ผลรวมของความถี่ของ token ทุกตัวใน vocabulary (ส่วนหารนี้จะช่วยทำให้ค่าความน่าจะเป็นของแต่ละ token รวมกันได้ 1)

ตัวอย่างเช่น `"ug"` เป็นคำย่อย (subword) ที่อยู่ใน `"hug"`, `"pug"`, และ `"hugs"` ดังนั้นความถี่ของมันก็คือ 20

ข้างล่างนี้คือความถี่ของคำย่อยทุกๆตัวใน vocabulary ของเรา :

```
("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16)
("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5)
```

และผลรวมของทุกๆความถี่ก็คือ 210 ดังนั้นความน่าจะเป็นของ `"ug"` ก็คือ 20/210

<Tip>

✏️ **ตาคุณบ้างแล้ว!** ลองเขียนโค้ดเพื่อคำนวณความถี่ของแต่ละ token แบบตัวอย่างข้างบน และคำนวณผลรวมของทุกความถี่ด้วย แล้วเช็คว่าผลลัพธ์ของคุณถูกหรือไม่

</Tip>

ในการ tokenize คำๆหนึ่งนั้น เราจะคำนวณทุกๆการตัดคำที่เป็นไปได้ (segmentation) และคำนวณความน่าจะเป็นของแต่ละ segmentation ด้วย โดยใช้วิธีการคำนวณตามโมเดล Unigram
เนื่องจากแต่ละ token ไม่ได้ขึ้นกับ token ตัวอื่น ค่าความน่าจะเป็นของแต่ละ segmentation สามารถคำนวณได้โดย นำค่าความน่าจะเป็นของแต่ละ token ย่อยใน segmentation นั้นมาคูณกัน

ตัวอย่างเช่น ถ้าเรา tokenize คำว่า `"pug"` แล้วได้ `["p", "u", "g"]` ความน่าจะเป็นของ segmentation นี้ก็คือ

$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$

แต่หากเราแบ่งมันออกเป็น `["pu", "g"]` ค่าความน่าจะเป็น ก็จะเท่ากับ :

$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$

ปกติแล้วถ้า segmentation มีจำนวนคำย่อยน้อย มันจะมีค่าความน่าจะเป็นที่สูง (เพราะว่า ในการคำนวณความน่าจะเป็นของแต่ละ token ทุกตัวจะถูกหารด้วยค่าเดียวกันคือ 210)
ผลลัพธ์แบบนี้ถือว่าดี เพราะสอดคล้องกับสิ่งที่เราต้องการ นั่นคือเราต้องการแยกคำออกเป็นคำย่อยๆ โดยให้มีจำนวนคำย่อยน้อยที่สุดเท่าที่จะเป็นไปได้

สำหรับตัวอย่าง `"pug"` เราจะได้ความน่าจะเป็นของแต่ละ segmentation ดังนี้ :

```
["p", "u", "g"] : 0.000389
["p", "ug"] : 0.0022676
["pu", "g"] : 0.0022676
```

จะเห็นว่า `["p", "ug"]` หรือ `["pu", "g"]` มีความน่าจะเป็นเท่ากัน ดังนั้นโปรแกรมของเราจะเลือก segmentation ใดก็ได้ ขึ้นกับว่าโปรแกรมของเราจะอ่านเจอผลลัพธ์ใดก่อน (อย่างไรก็ตามใน corpus ใหญ่ๆ เราจะไม่ค่อยเห็นกรณีแบบนี้ ที่หลาย segmentation มีความน่าจะเป็นเท่ากัน)
เนื่องจากเราใช้ตัวอย่างสั้นๆง่ายๆ อาจจะทำให้ดูเหมือนการคำนวณ segmentation ทั้งหมด รวมถึงค่าความน่าจะเป็น ทำได้อย่างง่ายดาย แต่ปกติแล้วการคำนวณจะยากกว่านี้

อัลกอริทึมหนึ่งที่จะช่วยหา segmentation ที่ดีที่สุดให้เรา ก็คือ *Viterbi algorithm*
มันจะสร้างกราฟที่สามารถคำนวณหา segmentation ที่เป็นไปได้ทั้งหมดของคำที่เราต้องการจะ tokenize ตัวอย่างเช่น ถ้า _a_ และ _b_ เป็นคำย่อยที่มีอยู่ใน vocabulary อัลกอริทึมก็จะสร้างกราฟเชื่อมจาก _a_ ไปหา _b_ และมันก็จะคำนวณค่าความน่าจะเป็นของคำย่อยพวกนี้และบันทึกลงไปในกราฟเพื่อการคำนวณต่อไป
เป้าหมายของเราคือการหาเส้นทางในกราฟที่แสดงถึง segmentation ที่ดีที่สุด ซึ่งก็คือ segmentation ที่มี score สูงที่สุด
เราจะคำนวณ score จากต้นกราฟไปยังปลายกราฟ ในแต่ละตำแหน่งเราจะ loop ตัวอักษรสุดท้ายของแต่ละคำย่อยทุกๆตัวที่เป็นไปได้ในตำแหน่งนั้น และเลือกคำย่อยที่มี score สูงที่สุด
และต่อแบบนี้ไปเรื่อยๆจนถึงตำแหน่งสุดท้าย

มาดูตัวอย่างกับคำว่า `"unhug"` กัน ในแต่ละตำแหน่ง เราได้คำนวณเพื่อหาคำย่อยที่ตัวอักษรสุดท้ายมี score สูงที่สุด :

```
Character 0 (u): "u" (score 0.171429)
Character 1 (n): "un" (score 0.076191)
Character 2 (h): "un" "h" (score 0.005442)
Character 3 (u): "un" "hu" (score 0.005442)
Character 4 (g): "un" "hug" (score 0.005442)
```

ดังนั้น `"unhug"` ก็จะถูกแบ่งเป็น `["un", "hug"]`

<Tip>

✏️ **ตาคุณบ้างแล้ว!** ลองคำนวณการแบ่งคำของ `"huggun"`และ score ของมัน

</Tip>

## กลับมาสู่การเทรน

หลังจากที่คุณได้รู้แล้วว่าอัลกอริทึมนี้ tokenize คำอย่างไร ในหัวข้อนี้ เราจะมาดูกันอย่างละเอียดว่า เราจะคำนวณค่า loss เพื่อการเทรนได้อย่างไร
ในทุกๆรอบของการเทรน เราจะคำนวณค่า loss โดยเราจะ tokenize แต่ละคำใน corpus ตามข้อมูลใน vocabulary และโมเดล Unigram ที่เราสร้างจากการคำนวณความถี่ของแต่ละ token ใน corpus (อย่างที่เราได้เรียนกันแล้วข้างบน)
แต่ละคำใน corpus จะมีค่า score ของมัน ส่วนค่า loss เราจะคำนวณจาก negative log likelihood ของ score พวกนี้ ซึ่งเท่ากับ ผลรวมของ `-log(P(word))` ของทุกๆคำ

กลับมาดูตัวอย่างกัน นี่คือ corpus ของเรา :

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

ข้างล่างนี้คือ segmentation ที่ดีที่สุดของแต่ละคำ และ score ของมัน ที่เราใช้โมเดล Ngram คำนวณ :

```
"hug": ["hug"] (score 0.071428)
"pug": ["pu", "g"] (score 0.007710)
"pun": ["pu", "n"] (score 0.006168)
"bun": ["bu", "n"] (score 0.001451)
"hugs": ["hug", "s"] (score 0.001701)
```

ดังนั้นค่า loss ก็คือ :

```
10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8
```
จากนั้นเราจะคำนวณว่า ถ้าเราลบคำใดคำหนึ่งออก มันจะกระทบค่า loss อย่างไร
ขั้นตอนนี้ค่อนข้างซับซ้อน ดังนั้นเราใช้แค่ token สองตัวเป็นตัวอย่าง และจะเขียนโค้ดเพื่อช่วยคำนวณภายหลัง
ถ้าคุณยังจำได้ เรามีสอง segmentation ที่มี score เท่ากัน ตัวอย่างเช่น `"pug"` สามารถถูกแบ่งเป็น `["p", "ug"]` ได้ด้วย
ดังนั้น ถ้าเราลบ `"pu"` ออกจาก vocabulary เราก็จะยังได้ค่า loss เท่าเดิม
ในทางกลับกัน ถ้าเราลบ `"hug"` ออก ค่า loss จะเพิ่มสูงขึ้นมาก นั่นก็เพราะว่า ผลลัพธ์ของ `"hug"` และ `"hugs"` จะเปลี่ยนเป็น

```
"hug": ["hu", "g"] (score 0.006802)
"hugs": ["hu", "gs"] (score 0.001701)
```

การเปลี่ยนแปลงนี้ทำให้ score เปลี่ยนไป และค่า loss รวมก็จะสูงขึ้น :

```
- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5
```

ดังนั้นเราจึงควรจะลบ `"pu"` ออก แต่เก็บ `"hug"` ไว้

## Implementing Unigram

ในหัวข้อนี้ เราจะมา implement ทุกอย่างกัน
เช่นเดียวกับในตัวอย่างของ BPE และ WordPiece โค้ดที่เราจะสอนต่อไปนี้ไม่ใช่โค้ดที่มีประสิทธิภาพที่สุด เราเพียงต้องการให้คุณเข้าในการทำงานของอัลกอริทึมเท่านั้น
เราจะใช้ corpus เดียวกับตัวอย่างก่อนๆ :

```python
corpus = [
    "This is the Hugging Face course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

ครั้งนี้เราจะใช้โมเดล `xlnet-base-cased` :

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased")
```

เช่นเดียวกันกับ BPE และ WordPiece เราเริ่มจากการนับจำนวน(ความถี่)ของแต่ละคำใน corpus :

```python
from collections import defaultdict

word_freqs = defaultdict(int)
for text in corpus:
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    new_words = [word for word, offset in words_with_offsets]
    for word in new_words:
        word_freqs[word] += 1

word_freqs
```

จากนั้น เราจะต้องสร้าง vocabulary ตั้งต้นให้ใหญ่กว่า ขนาด vocabulary ที่เราต้องการ

vocabulary จะต้องมีตัวอักษรพื้นฐาน ไม่เช่นนั้นเราจะ tokenize ไม่ได้เลย ส่วน token ที่ใหญ่ขึ้น(subwords) เราจะใช้เฉพาะตัวที่พบบ่อยๆเท่านั้น ซึ่งเราจะเรียงลำดับพวกมันตามความถี่ ดังนี้ :

```python
char_freqs = defaultdict(int)
subwords_freqs = defaultdict(int)
for word, freq in word_freqs.items():
    for i in range(len(word)):
        char_freqs[word[i]] += freq
        # Loop through the subwords of length at least 2
        for j in range(i + 2, len(word) + 1):
            subwords_freqs[word[i:j]] += freq

# Sort subwords by frequency
sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True)
sorted_subwords[:10]
```

```python out
[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)]
```

เราจะนำตัวอักษรและ subwords ที่มีความถี่สูงพวกนี้ มารวมกันเพื่อสร้าง vocabulary ตั้งต้น ขนาด 300 token :

```python
token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)]
token_freqs = {token: freq for token, freq in token_freqs}
```

<Tip>

💡 SentencePiece ใช้อัลกอริทึม ชื่อ Enhanced Suffix Array (ESA) ซึ่งมีประสิทธิภาพมากกว่า Ngram ในการสร้าง vocabulary ตั้งต้น

</Tip>

ขั้นตอนต่อไป เราจะคำนวณผลรวมของความถี่ของทุกๆคำ เพื่อแปลงความถี่เป็นค่าความน่าจะเป็น

สำหรับโมเดลของเรา เราจะคำนวณค่าลอการิทึมของความน่าจะเป็น แทนที่จะใช้ความน่าจะเป็นเท่านั้น เพราะว่ามันจะทำให้การคำนวณมีความเสถียรมากกว่า เมื่อเทียบกับการคูณจำนวนน้อยๆหลายๆจำนวน และมันยังช่วยทำให้การคำนวณค่า loss ทำได้ง่ายขึ้นด้วย :

```python
from math import log

total_sum = sum([freq for token, freq in token_freqs.items()])
model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
```

ฟังก์ชันหลักของ tokenizer นี้คือการ tokenize คำ โดยใช้อัลกอริทึม Viterbi ช่วย
อย่างที่คุณได้เห็นมาแล้ว อัลกอริทึม Viterbi จะคำนวณหาการแบ่งคำที่ดีที่สุดให้กับแต่ละคำใน corpus ซึ่ง segmentation พวกนี้จะถูกเซฟไว้ใน list ชื่อ `best_segmentations`
สำหรับคำที่เราต้องการจะ tokenize ในแต่ละตำแหน่ง (เริ่มจาก 0 ถึง ความยาวของคำ) เราจะสร้าง dictionary ขึ้นมา ซึ่งมีคีย์สองตัว ตัวแรกคือ index ของตัวอักษรแรกของ token สุดท้ายใน segmentation ที่ดีที่สุดของคำนั้น และคีย์ตัวที่สองคือค่า score ของ segmentation ที่ดีที่สุด
คีย์ที่เก็บ index นี้ จะช่วยให้เราสามารถคำนวณ segmentation เต็มๆได้ หลังจากที่เราเพิ่มข้อมูลลงใน list แล้ว

เริ่มจากเราจะ for loop สองครั้ง เพื่อค้นหาคำย่อยในคำหลักที่อาจจะอยู่ใน vocabulary โดย loop แรกเอาไว้หาตำแหน่งเริ่มต้นของคำย่อย ส่วน loop ที่สองเอาไว้หาตำแหน่งจบของคำย่อยนั้น
ถ้าเราเจอคำย่อยที่อยู่ใน vocabulary เราจะสร้าง segmentation ใหม่ขึ้นมาโดยแบ่งคำหลักตรงคำย่อยนี้ และก่อนที่จะบันทึก segmentation นี้ลงไป เราจะเทียบกับ score ของมันกับ score ของ segmentation ที่มีอยู่แล้วใน `best_segmentations`
หลังจาก loop จบ เราจะคำนวณหา segmentation ที่ดีที่สุดของคำ input โดยอ่านจากตำแหน่งสุดท้ายของคำไปหาต้นคำ และบันทึกคำย่อยที่ดีที่สุดเอาไว้สำหรับทุกๆตำแหน่ง :

```python
def encode_word(word, model):
    best_segmentations = [{"start": 0, "score": 1}] + [
        {"start": None, "score": None} for _ in range(len(word))
    ]
    for start_idx in range(len(word)):
        # This should be properly filled by the previous steps of the loop
        best_score_at_start = best_segmentations[start_idx]["score"]
        for end_idx in range(start_idx + 1, len(word) + 1):
            token = word[start_idx:end_idx]
            if token in model and best_score_at_start is not None:
                score = model[token] + best_score_at_start
                # If we have found a better segmentation ending at end_idx, we update
                if (
                    best_segmentations[end_idx]["score"] is None
                    or best_segmentations[end_idx]["score"] > score
                ):
                    best_segmentations[end_idx] = {"start": start_idx, "score": score}

    segmentation = best_segmentations[-1]
    if segmentation["score"] is None:
        # We did not find a tokenization of the word -> unknown
        return ["<unk>"], None

    score = segmentation["score"]
    start = segmentation["start"]
    end = len(word)
    tokens = []
    while start != 0:
        tokens.insert(0, word[start:end])
        next_start = best_segmentations[start]["start"]
        end = start
        start = next_start
    tokens.insert(0, word[start:end])
    return tokens, score
```
ตอนนี้เราก็สามารถลองใช้โมเดลได้แล้ว :

```python
print(encode_word("Hopefully", model))
print(encode_word("This", model))
```

```python out
(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402)
(['This'], 6.288267030694535)
```

และเราก็ยังสามารถคำนวณค่า loss ได้อย่างง่ายดายอีกด้วย :

```python
def compute_loss(model):
    loss = 0
    for word, freq in word_freqs.items():
        _, word_loss = encode_word(word, model)
        loss += freq * word_loss
    return loss
```

มาเช็คผลลัพธ์การคำนวณ loss จากโมเดลของเรากัน :

```python
compute_loss(model)
```

```python out
413.10377642940875
```

การคำนวณ score ให้แต่ละ token ก็ไม่ยากเช่นกัน โดยเราจะคำนวณค่า loss หลังจากที่เราลบ token นั้นออก :

```python
import copy


def compute_scores(model):
    scores = {}
    model_loss = compute_loss(model)
    for token, score in model.items():
        # We always keep tokens of length 1
        if len(token) == 1:
            continue
        model_without_token = copy.deepcopy(model)
        _ = model_without_token.pop(token)
        scores[token] = compute_loss(model_without_token) - model_loss
    return scores
```

ลองรันโค้ดกับ token ตัวอย่างดังนี้ :

```python
scores = compute_scores(model)
print(scores["ll"])
print(scores["his"])
```

เนื่องจาก `"ll"` ถูกใช้ในการ tokenize คำว่า `"Hopefully"` ถ้าเราลบมันออก เราจะต้องใช้ `"l"` สองครั้งแทน ในกรณีนี้ ค่า loss จะสูงขึ้น
ส่วน `"his"` ถูกใช้แค่ในคำว่า `"This"` ซึ่งคำนี้ถูก tokenize ให้เป็นตัวมันเอง(ไม่มีการแบ่ง) หากเราลบมันออก `"his"` ก็จะไม่มีผลต่อค่า loss มาดูผลลัพธ์กัน :

```python out
6.376412403623874
0.0
```

<Tip>

💡 วิธีการคำนวณแบบข้างบนนี้ถือว่าไม่มีประสิทธิภาพนัก ดังนั้น SentencePiece จะคำนวณค่า loss แบบคร่าวๆเท่านั้น เวลาที่เราลองลบ token แต่ละตัวออก โดยมันจะแทนที่ token นั้นด้วย segmentation ของมันแทนที่จะใช้ token เต็มๆ
การทำแบบนี้ช่วยให้เราสามารถคำนวณ score ของทุกๆตัวได้ภายในครั้งเดียว และยังสามารถคำนวณไปพร้อมๆกับค่า loss ได้อีกด้วย

</Tip>

สิ่งสุดท้ายที่เราจะต้องทำก็คือ เพิ่ม token พิเศษที่โมเดลใช้ลงไปใน vocabulary จากนั้น loop จนกว่าเราจะลบ token ออกจาก vocabulary จนได้ขนาดที่เราพอใจ :

```python
percent_to_remove = 0.1
while len(model) > 100:
    scores = compute_scores(model)
    sorted_scores = sorted(scores.items(), key=lambda x: x[1])
    # Remove percent_to_remove tokens with the lowest scores.
    for i in range(int(len(model) * percent_to_remove)):
        _ = token_freqs.pop(sorted_scores[i][0])

    total_sum = sum([freq for token, freq in token_freqs.items()])
    model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()}
```

เมื่อเราต้องการจะ tokenize ประโยคหรือข้อความ สิ่งที่เราต้องทำก็คือทำการ pre-tokenization ข้อความนั้น แล้วรันฟังก์ชัน `encode_word()` ของเรา :

```python
def tokenize(text, model):
    words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
    pre_tokenized_text = [word for word, offset in words_with_offsets]
    encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text]
    return sum(encoded_words, [])


tokenize("This is the Hugging Face course.", model)
```

```python out
['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.']
```

จบแล้วสำหรับอัลกอริทึม Unigram หวังว่าถึงตอนนี้คุณจะรู้สึกว่าคุณเป็นผู้เชี่ยวชาญด้าน tokenizer ในระดับหนึ่งแล้ว ในบทถัดไปเราจะพูดถึงส่วนประกอบสำคัญของ 🤗 Tokenizers library และมาดูกันว่าคุณจะใช้มันเพื่อสร้าง tokenizer ของคุณเองได้อย่างไร


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/7.mdx" />

### การใช้งานตัวตัดคำแบบเร็ว (Fast tokenizers) ใน QA pipeline
https://huggingface.co/learn/course/th/chapter6/3b.md

# การใช้งานตัวตัดคำแบบเร็ว (Fast tokenizers) ใน QA pipeline

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section3b_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section3b_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section3b_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section3b_tf.ipynb"},
]} />

{/if}

ในบทนี้ เราจะเรียนเกี่ยวกับการใช้งาน pipeline เพื่อทำ `question-answering` และดูว่าเราจะสามารถใช้ข้อมูลจาก offset เพื่อเอาไว้หาคำตอบให้กับคำถาม input จากบริบทรอบๆ (context) ได้อย่างไร
ขั้นตอนนี้จะคล้ายๆกับตอนที่เราใช้ offset เพื่อรวมรวม entity ประเภทเดียวกันเข้าด้วยกัน ในบทที่แล้ว
จากนั้น เราจะมาดูกันว่าเราจะจัดการกับ context ที่ยาวมากๆ จนบางส่วนต้องถูกตัดทอนออกได้อย่างไร คุณสามารถข้ามส่วนนี้หากคุณไม่สนใจ question answering

{#if fw === 'pt'}

<Youtube id="_wxyB3j3mk4"/>

{:else}

<Youtube id="b3u8RzBCX9Y"/>

{/if}

## การใช้ `question-answering` pipeline

อย่างที่คุณได้เรียนใน[บทที่ 1](/course/chapter1) เราสามารถใช้ `question-answering` pipeline เพื่อคำนวณคำตอบของคำถาม input ได้ :

```py
from transformers import pipeline

question_answerer = pipeline("question-answering")
context = """
🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch, and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""
question = "Which deep learning libraries back 🤗 Transformers?"
question_answerer(question=question, context=context)
```

```python out
{'score': 0.97773,
 'start': 78,
 'end': 105,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

ใน pipeline อื่นๆ เราไม่สามารถตัดทอนและแยกข้อความที่ยาวเกินกว่าความยาวสูงสุดที่โมเดลกำหนดได้ (และอาจพลาดตัดข้อมูลที่ส่วนท้ายของเอกสารได้ด้วย) แต่ pipeline ที่เราจะเรียนกันนี้ สามารถจัดการกับ context ที่ยาวมากได้ และจะ return คำตอบให้กับคำถาม แม้ว่าจะอยู่ในตอนท้าย:

```py
long_context = """
🤗 Transformers: State of the Art NLP

🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction,
question answering, summarization, translation, text generation and more in over 100 languages.
Its aim is to make cutting-edge NLP easier to use for everyone.

🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and
then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and
can be modified to enable quick research experiments.

Why should I use transformers?

1. Easy-to-use state-of-the-art models:
  - High performance on NLU and NLG tasks.
  - Low barrier to entry for educators and practitioners.
  - Few user-facing abstractions with just three classes to learn.
  - A unified API for using all our pretrained models.
  - Lower compute costs, smaller carbon footprint:

2. Researchers can share trained models instead of always retraining.
  - Practitioners can reduce compute time and production costs.
  - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages.

3. Choose the right framework for every part of a model's lifetime:
  - Train state-of-the-art models in 3 lines of code.
  - Move a single model between TF2.0/PyTorch frameworks at will.
  - Seamlessly pick the right framework for training, evaluation and production.

4. Easily customize a model or an example to your needs:
  - We provide examples for each architecture to reproduce the results published by its original authors.
  - Model internals are exposed as consistently as possible.
  - Model files can be used independently of the library for quick experiments.

🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration
between them. It's straightforward to train your models with one before loading them for inference with the other.
"""
question_answerer(question=question, context=long_context)
```

```python out
{'score': 0.97149,
 'start': 1892,
 'end': 1919,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

เรามาดูกันว่ามันทำงานอย่างไร!

## การใช้งานโมเดลสำหรับงาน question answering

เช่นเดียวกับ pipeline อื่นๆ เราจะเริ่มต้นด้วยการ tokenize ข้อความ input ของเรา แล้วส่งผลลัพธ์ที่ได้ต่อไปยังตัวโมเดล
ค่าเริ่มต้นของ checkpoint สำหรับ `question-answering` pipeline คือ [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad)
(คำว่า "squad" มาจากชื่อของชุดข้อมูลที่โมเดลใช้เพื่อ fine-tune ซึ่งก็คือ SQuAD dataset เราจะพูดถึงชุดข้อมูลนี้เพิ่มเติมใน[บทที่ 7](/course/chapter7/7)):

{#if fw === 'pt'}

```py
from transformers import AutoTokenizer, AutoModelForQuestionAnswering

model_checkpoint = "distilbert-base-cased-distilled-squad"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)

inputs = tokenizer(question, context, return_tensors="pt")
outputs = model(**inputs)
```

{:else}

```py
from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering

model_checkpoint = "distilbert-base-cased-distilled-squad"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint)

inputs = tokenizer(question, context, return_tensors="tf")
outputs = model(**inputs)
```

{/if}

เราจะทำการตัดคำให้กับส่วนที่เป็นคำถามและส่วน context ไปด้วยกัน โดยจะตัดคำให้กับส่วนคำถามก่อน

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens.svg" alt="An example of tokenization of question and context"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens-dark.svg" alt="An example of tokenization of question and context"/>
</div>

โมเดลสำหรับ question answering นั้นทำงานแตกต่างไปจากโมเดลอื่น ที่คุณเคยเห็นมาแล้วเล็กน้อย จากภาพด้านบน
โมเดลจะถูกการเทรนให้ทำนาย index ของ token ที่เป็นจุดเริ่มต้นของข้อความคำตอบ (ซึ่งก็คือ index ที่ 21) และ index ของ token สุดท้ายของข้อความคำตอบ
(ซึ่งก็คือ index ที่ 24) นี่คือสาเหตุที่โมเดลเหล่านั้นไม่ return tensor ของ logit หนึ่งตัว แต่สองตัว: tensor แรก คือ logits สำหรับ token เริ่มต้นของคำตอบ
และอีก tensor เป็น logits สำหรับ token สุดท้ายของคำตอบ เนื่องจากในกรณีนี้ เรามีเพียง input เดียว ซึ่งมี 66 token เราจะได้ผลลัพธ์ดังนี้:

```py
start_logits = outputs.start_logits
end_logits = outputs.end_logits
print(start_logits.shape, end_logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([1, 66]) torch.Size([1, 66])
```

{:else}

```python out
(1, 66) (1, 66)
```

{/if}

ในการแปลงค่า logits ให้เป็นค่าความน่าจะเป็น (probabilities) เราจะใช้ฟังก์ชัน softmax  แต่ก่อนอื่น เราจะต้องทำการปกปิด (mask) index ที่ไม่ได้เป็นส่วนหนึ่งของ context ก่อน
อินพุตของเราคือ `[CLS] question [SEP] context [SEP]` ดังนั้น เราจะ mask แต่ละ token ในส่วนที่เป็นคำถาม รวมถึง token `[SEP]` ด้วย อย่างไรก็ตาม เราจะเก็บ `[CLS]` ไว้
เนื่องจากโมเดลบางตัวอาจจะใช้มัน เพื่อระบุว่าคำตอบไม่อยู่ใน context
เนื่องจากเราจะใช้ softmax ในภายหลัง เราจึงเพียงแค่ต้องแทนที่ค่า logits ที่เราต้องการ mask ด้วยตัวเลขติดลบจำนวนมาก ในตัวอย่างนี้ เราใช้ `-10000`:

{#if fw === 'pt'}

```py
import torch

sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
mask = torch.tensor(mask)[None]

start_logits[mask] = -10000
end_logits[mask] = -10000
```

{:else}

```py
import tensorflow as tf

sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
mask = tf.constant(mask)[None]

start_logits = tf.where(mask, -10000, start_logits)
end_logits = tf.where(mask, -10000, end_logits)
```

{/if}

หลังจากที่ เราได้ mask ค่า logits ตำแหน่งที่เราไม่ต้องการจะทำนาย เรียบร้อยแล้ว ตอนนี้เราก็สามารถคำนวณ softmax ได้:

{#if fw === 'pt'}

```py
start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0]
end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0]
```

{:else}

```py
start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy()
end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy()
```

{/if}

ตอนนี้ เราสามารถหาค่า argmax ของ probabilities ของจุดเริ่มต้นและจุดสิ้นสุดได้แล้ว
แต่ปัญหาที่อาจจะเกิดขึ้นก็คือ index ของจุดเริ่มต้น นั้นอยู่เกิน index ของจุดสิ้นสุด ดังนั้นเราจึงต้องหาวิธีจัดการปัญหานี้ เราจะคำนวณ probabilities ของ `start_index` และ `end_index` ที่เป็นไปได้จริง ซึ่งหมายถึง `start_index <= end_index` จากนั้นเราจะเลือกใช้แค่ tuple `(start_index, end_index)` ที่มีความเป็นไปได้สูงสุด
สมมติว่า เหตุการณ์ที่ "คำตอบเริ่มต้นที่ `start_index`" และ "คำตอบสิ้นสุดที่ `end_index`" ไม่มีความเกี่ยวข้องกัน (independent) ความน่าจะเป็นที่ คำตอบจะเริ่มต้นที่ `start_index` และสิ้นสุดที่ `end_index` คือ:

$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$

การคำนวณ score ทำได้โดยคำนวณผลคูณทั้งหมดของ \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) โดยที่ `start_index <= end_index`

ขั้นแรก เราจะคำนวณผลคูณที่เป็นไปได้ทั้งหมด:

```py
scores = start_probabilities[:, None] * end_probabilities[None, :]
```

{#if fw === 'pt'}

จากนั้นเราจะ mask ค่าตรงที่ `start_index > end_index` ให้เป็น `0` (ค่า probabilities อื่นๆ เป็นจำนวนบวกทั้งหมด) ฟังก์ชัน `torch.triu()` จะคำนวณ ส่วนสามเหลี่ยมบนของ tensor 2 มิติ ที่เราใส่ไปเป็น argument ดังนั้นมันจะทำการ mask ให้เรา:

```py
scores = torch.triu(scores)
```

{:else}

จากนั้นเราจะ mask ค่าตรงที่ `start_index > end_index` ให้เป็น `0` (ค่า probabilities อื่นๆ เป็นจำนวนบวกทั้งหมด) ฟังก์ชัน `np.triu()` จะคำนวณ ส่วนสามเหลี่ยมบนของ tensor 2 มิติ ที่เราใส่ไปเป็น argument ดังนั้นมันจะทำการ mask ให้เรา:
```py
scores = np.triu(scores)
```

{/if}

ตอนนี้เราแค่ต้องหา index ที่มีค่า probability สูงสุด เนื่องจาก PyTorch จะ return ค่าในรูป flattened tensor เราจึงต้องใช้การหารแล้วปัดลง (floor division) `//` และโมดูลัส `%` เพื่อคำนวณ `start_index` และ `end_index`:
```py
max_index = scores.argmax().item()
start_index = max_index // scores.shape[1]
end_index = max_index % scores.shape[1]
print(scores[start_index, end_index])
```
ตอนนี้ เราก็ได้ score ที่ถูกต้องสำหรับคำตอบแล้ว (คุณสามารถตรวจสอบได้โดยเปรียบเทียบกับผลลัพธ์แรกในส่วนก่อนหน้า):

```python out
0.97773
```

<Tip>

✏️ **ลองทำดู!** คำนวณ index เริ่มต้นและสิ้นสุด เพื่อหาคำตอบที่น่าจะเป็นไปได้มากที่สุด 5 คำตอบ

</Tip>

เรามี `start_index` และ `end_index` ของ token ที่จะเอามาเป็นคำตอบได้แล้ว ดังนั้นตอนนี้เราเพียงแค่ต้องแปลงเป็น index ของตัวอักษร ใน context เท่านั้น นี่คือจุดที่ offsets จะมีประโยชน์มาก เราสามารถใช้งานมันได้เหมือนที่เราทำใน token classification:

```py
inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True)
offsets = inputs_with_offsets["offset_mapping"]

start_char, _ = offsets[start_index]
_, end_char = offsets[end_index]
answer = context[start_char:end_char]
```

ตอนนี้ เราแค่ต้องฟอร์แมตทุกอย่างเพื่อให้ได้ผลลัพธ์ที่ต้องการ:

```py
result = {
    "answer": answer,
    "start": start_char,
    "end": end_char,
    "score": scores[start_index, end_index],
}
print(result)
```

```python out
{'answer': 'Jax, PyTorch and TensorFlow',
 'start': 78,
 'end': 105,
 'score': 0.97773}
```

ยอดเยี่ยม! เราได้คำตอบเหมือนกับในตัวอย่างแรกของเรา!

<Tip>

✏️ **ลองดูสิ!** ใช้คะแนนที่ดีที่สุดที่คุณคำนวณไว้ก่อนหน้านี้ เพื่อคำนวณคำตอบที่น่าจะเป็นไปได้มากที่สุดห้าลำดับ ในการตรวจสอบผลลัพธ์ของคุณ ให้กลับไปที่ pipeline แรกแล้วตั้งค่า `top_k=5` ตอนที่รัน pipeline
</Tip>

## การจัดการกับบริบทยาว (long contexts)

หากคุณต้องการ tokenize คำถามและบริบทที่ค่อยข้างยาว ที่เราใช้เป็นตัวอย่างก่อนหน้านี้ คุณจะได้ token ที่มีความยาวสูงกว่าความยาวสูงสุดที่จำกัดไว้ใน pipeline `question-answering` (ซึ่งคือ 384):

```py
inputs = tokenizer(question, long_context)
print(len(inputs["input_ids"]))
```

```python out
461
```

ดังนั้น เราจึงจำเป็นจะต้องตัดทอน input ของเราให้ความยาวเท่ากับความยาวสูงสุด มีหลายวิธีที่เราสามารถทำได้ อย่างไรก็ตาม เราไม่ต้องการตัดคำถามให้สั้นลง เราต้องการตัดเฉพาะตัวบริบทเท่านั้น
เนื่องจากบริบทอยู่ในตำแหน่งของประโยคที่สอง เราจะใช้กลยุทธ์การตัดทอนที่เรียกว่า `"only_second"`
อย่างไรก็ตาม ปัญหาหนึ่งที่อาจจะเกิดขึ้นก็คือ คำตอบของคำถามอาจจะอยู่ในส่วนที่ถูกตัดออกไป เช่นในตัวอย่างข้างบน เราได้เลือกคำถามที่คำตอบอยู่ในตอนท้ายของบริบท และเมื่อเราตัดทอนส่วนท้ายของบริบทออกไป คำตอบที่เราต้องการก็จะหายไปด้วย:

```py
inputs = tokenizer(question, long_context, max_length=384, truncation="only_second")
print(tokenizer.decode(inputs["input_ids"]))
```

```python out
"""
[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP

[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction,
question answering, summarization, translation, text generation and more in over 100 languages.
Its aim is to make cutting-edge NLP easier to use for everyone.

[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and
then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and
can be modified to enable quick research experiments.

Why should I use transformers?

1. Easy-to-use state-of-the-art models:
  - High performance on NLU and NLG tasks.
  - Low barrier to entry for educators and practitioners.
  - Few user-facing abstractions with just three classes to learn.
  - A unified API for using all our pretrained models.
  - Lower compute costs, smaller carbon footprint:

2. Researchers can share trained models instead of always retraining.
  - Practitioners can reduce compute time and production costs.
  - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages.

3. Choose the right framework for every part of a model's lifetime:
  - Train state-of-the-art models in 3 lines of code.
  - Move a single model between TF2.0/PyTorch frameworks at will.
  - Seamlessly pick the right framework for training, evaluation and production.

4. Easily customize a model or an example to your needs:
  - We provide examples for each architecture to reproduce the results published by its original authors.
  - Model internal [SEP]
"""
```

ซึ่งหมายความว่า มันจะยากมากที่โมเดลของเราจะเลือกคำตอบได้ถูกต้อง เพื่อแก้ไขปัญหานี้ ไปป์ไลน์ `question-answering` จะแบ่งบริบทออกเป็นส่วนย่อยๆ ที่ไม่ยาวเกินความยาวสูงสุด

เพื่อให้แน่ใจว่า เราจะไม่แบ่งบริบทผิดตำแหน่งจนโมเดลไม่สามารถค้นหาคำตอบได้ เราจะแบ่งโดย ให้บริบทย่อยแต่ละส่วนมีส่วนที่ทับซ้อนกันด้วย
เราสามารถใช้ tokenizer (ทั้งแบบเร็วและช้า) ทำสิ่งนี้ให้เราได้ โดยคุณจะต้องตั้งค่า `return_overflowing_tokens=True` นอกจากนั้น เพื่อกำหนดว่าเราจะให้ข้อความทับซ้อนกันมากแค่ไหน เราจำกำหนดค่าให้กับ argument `stride`

ดูตัวอย่างข้างล่างนี้ :

```py
sentence = "This sentence is not too long but we are going to split it anyway."
inputs = tokenizer(
    sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2
)

for ids in inputs["input_ids"]:
    print(tokenizer.decode(ids))
```

```python out
'[CLS] This sentence is not [SEP]'
'[CLS] is not too long [SEP]'
'[CLS] too long but we [SEP]'
'[CLS] but we are going [SEP]'
'[CLS] are going to split [SEP]'
'[CLS] to split it anyway [SEP]'
'[CLS] it anyway. [SEP]'
```

คุณจะเห็นว่า ตอนนี้ประโยคถูกแบ่งออกเป็นส่วนๆ โดยแต่ละส่วนจะมีไม่เกิน 6 token และมี token ที่ทับซ้อนกัน 2 token (สังเกตว่า ประโยคสุดท้ายมีเพียง 4 token ในกรณี เราจะต้องเพิ่ม padding token ทีหลังเพื่อให้มันยาวเท่ากับส่วนอื่นๆ)

มาดูผลลัพธ์ของการ tokenization อย่างละเอียดยิ่งขึ้น:

```py
print(inputs.keys())
```

```python out
dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping'])
```

ผลลัพธ์จากการแบ่งประโยคนี้ คือ `input_ids` และ `attention_mask` ส่วนคีย์สุดท้าย `overflow_to_sample_mapping` เป็น  map ที่บอกเราว่าแต่ละประโยคย่อยมาจากประโยค input ตำแหน่งที่เท่าไร ในตัวอย่างของเรา เราใช้แค่ประโยคเดียวเป็น input และเราได้ 7 ประโยคย่อยเป็น output แปลว่าทุกประโยคย่อยก็จะถูก map ไปหาประโยคหลักที่มี ID เดียวกัน :
```py
print(inputs["overflow_to_sample_mapping"])
```

```python out
[0, 0, 0, 0, 0, 0, 0]
```

feature นี้จะมีประโยชน์เมื่อเราใช้ประโยคหลายเป็น input ตัวอย่างเช่น:

```py
sentences = [
    "This sentence is not too long but we are going to split it anyway.",
    "This sentence is shorter but will still get split.",
]
inputs = tokenizer(
    sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2
)

print(inputs["overflow_to_sample_mapping"])
```

เราจะได้ :

```python out
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
```

ซึ่งหมายความว่า ประโยคแรกถูกแบ่งออกเป็น 7 ส่วน และ ประโยคที่สองถูกแบ่งออกเป็น 4 ส่วน

กลับมาดูกันว่า เราจะจัดการกับบริบทยาวๆได้อย่างไร ไปป์ไลน์ `question-answering` จำกัดความยาวสูงสุดไว้ที่ 384 และค่า stride ถูกตั้งไว้ที่ 128 ซึ่งสอดคล้องกับค่าที่ใช้ตอนที่โมเดลถูก fine-tune (คุณสามารถปรับ parameters เหล่านั้นได้ โดยตั้งค่า `max_seq_len` และ `stride` เมื่อเรียกไปป์ไลน์) เราจะใช้ค่าเริ่มต้นพวกนี้ในการแบ่งบริบทเป็นส่วนย่อยๆ นอกจากนี้ เราจะตั้งค่า padding (เพื่อให้มีแต่ละส่วนที่มีความยาวเท่ากัน และเพื่อที่เราจะได้นำมันไปสร้าง tensor ได้) และค่า offsets ด้วย:

```py
inputs = tokenizer(
    question,
    long_context,
    stride=128,
    max_length=384,
    padding="longest",
    truncation="only_second",
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)
```

`inputs` เหล่านั้นจะมี input ID และ attention masks เช่นเดียวกับ offsets และ `overflow_to_sample_mapping` ที่เราเพิ่งพูดถึง
 เนื่องจากทั้งสองอย่างหลังนี้ไม่ใช่ parameters ที่ใช้โดยโมเดล เราจะเอามันออกจาก `inputs` ก่อนที่จะแปลง `inputs` เป็น tensor:

{#if fw === 'pt'}

```py
_ = inputs.pop("overflow_to_sample_mapping")
offsets = inputs.pop("offset_mapping")

inputs = inputs.convert_to_tensors("pt")
print(inputs["input_ids"].shape)
```

```python out
torch.Size([2, 384])
```

{:else}

```py
_ = inputs.pop("overflow_to_sample_mapping")
offsets = inputs.pop("offset_mapping")

inputs = inputs.convert_to_tensors("tf")
print(inputs["input_ids"].shape)
```

```python out
(2, 384)
```

{/if}

บริบทแบบยาวของเรา ตอนนี้ถูกแบ่งออกเป็นสองส่วน ซึ่งหมายความว่า หลังจากเราใส่มันเข้าไปในโมเดลแล้ว เราจะได้ค่า start logits และ end logits อย่างละ 2 เซ็ต :

```py
outputs = model(**inputs)

start_logits = outputs.start_logits
end_logits = outputs.end_logits
print(start_logits.shape, end_logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([2, 384]) torch.Size([2, 384])
```

{:else}

```python out
(2, 384) (2, 384)
```

{/if}

เช่นเดียวกับตัวอย่างก่อน ก่อนอื่นเราจะปิด(mask) token ที่ไม่ได้เป็นส่วนหนึ่งของบริบท ก่อนที่จะใช้ softmax นอกจากนี้เราจะปิด padding tokens ทั้งหมดด้วย (ตามการตั้งค่าใน attention mask):

{#if fw === 'pt'}

```py
sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
# Mask all the [PAD] tokens
mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0))

start_logits[mask] = -10000
end_logits[mask] = -10000
```

{:else}

```py
sequence_ids = inputs.sequence_ids()
# Mask everything apart from the tokens of the context
mask = [i != 1 for i in sequence_ids]
# Unmask the [CLS] token
mask[0] = False
# Mask all the [PAD] tokens
mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0)

start_logits = tf.where(mask, -10000, start_logits)
end_logits = tf.where(mask, -10000, end_logits)
```

{/if}

จากนั้นเราจะใช้ softmax เพื่อแปลง logits เป็นความน่าจะเป็น:

{#if fw === 'pt'}

```py
start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)
end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)
```

{:else}

```py
start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy()
end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy()
```

{/if}

ขั้นตอนต่อไปนั้น คล้ายกับสิ่งที่เราทำกับบริบทแบบสั้นก่อนหน้านี้ เราจะรัน process เดียวกันนี้กับประโยคย่อยทั้งสองส่วนที่เราได้มา จากนั้น เราจะแจกจ่าย score ไปให้กับทุกๆ span ของคำตอบที่เป็นไปได้ และสุดท้ายเราจะเลือก span ที่มี score สูงที่สุด

{#if fw === 'pt'}

```py
candidates = []
for start_probs, end_probs in zip(start_probabilities, end_probabilities):
    scores = start_probs[:, None] * end_probs[None, :]
    idx = torch.triu(scores).argmax().item()

    start_idx = idx // scores.shape[1]
    end_idx = idx % scores.shape[1]
    score = scores[start_idx, end_idx].item()
    candidates.append((start_idx, end_idx, score))

print(candidates)
```

{:else}

```py
candidates = []
for start_probs, end_probs in zip(start_probabilities, end_probabilities):
    scores = start_probs[:, None] * end_probs[None, :]
    idx = np.triu(scores).argmax().item()

    start_idx = idx // scores.shape[1]
    end_idx = idx % scores.shape[1]
    score = scores[start_idx, end_idx].item()
    candidates.append((start_idx, end_idx, score))

print(candidates)
```

{/if}

```python out
[(0, 18, 0.33867), (173, 184, 0.97149)]
```

output ที่เราได้คือ span คำตอบที่ดีที่สุดของแต่ละประโยคย่อย ที่โมเดลคำนวณได้ เราจะเห็นว่าโมเดลให้ค่าความมั่นใจที่สูงมากๆกับ span คำตอบในประโยคที่สองมากกว่าประโยคแรก (ซึ่งเป็นสัญญาณที่ดี!) สิ่งที่เราต้องทำหลังจากนี้ก็คือ map ค่า span ไปสู่ตัวอักษร เพื่อดูว่า คำตอบที่โมเดลคำนวณได้คืออะไร
<Tip>

✏️ **ลองดูสิ!** ปรับโค้ดด้านบนเพื่อให้มัน return score และ span ของคำตอบที่น่าจะเป็นไปได้มากที่สุด 5 ลำดับ (โดยเปรียบเทียบ score ของทุกประโยคย่อย)

</Tip>

ค่า `offsets` ที่เราใช้ก่อนหน้านี้ เป็น list ของ offsets โดยที่แต่ละประโยคย่อยจะมีหนึ่ง list :

```py
for candidate, offset in zip(candidates, offsets):
    start_token, end_token, score = candidate
    start_char, _ = offset[start_token]
    _, end_char = offset[end_token]
    answer = long_context[start_char:end_char]
    result = {"answer": answer, "start": start_char, "end": end_char, "score": score}
    print(result)
```

```python out
{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867}
{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149}
```

ถ้าไม่นับผลลัพธ์แรกที่เรา print ออกมาด้วย เราก็จะได้ผลลัพธ์เดียวกันกับผลลัพธ์จากไปป์ไลน์ -- เย้!

<Tip>

✏️ **ลองดูสิ!** ใช้ score ที่ดีที่สุดที่คุณคำนวณได้ก่อนหน้านี้ เพื่อแสดงคำตอบที่น่าจะเป็นไปได้มากที่สุด 5 ลำดับ (สำหรับบริบททั้งหมด ไม่ใช่แต่ละส่วน) เพื่อตรวจสอบผลลัพธ์ของคุณ ให้กลับไปที่ไปป์ไลน์แรกแล้วตั้งค่า `top_k=5` เวลารัน

</Tip>

บทนี้ถือว่าเป็น การสรุปจบการเรียนรู้ความสามารถของ tokenizer แบบละเอียด ในบทต่อไปคุณจะได้ใช้ความรู้ที่เรียนมานี้ เพื่อฝึกฝนอีก โดยคุณจะได้ฝึก fine-tune โมเดลเพื่อ task ทั่วๆไป ของ NLP

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/3b.mdx" />

### การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว
https://huggingface.co/learn/course/th/chapter6/2.md

# การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter6/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter6/section2.ipynb"},
]} />

สมมติว่าคุณต้องการจะใช้ language model ในการทำงานใดงานหนึ่ง แต่ตอนนี้ไม่มีโมเดลในภาษาที่คุณต้องการหรือโมเดลที่มีอยู่นั้นถูกเทรนจากคลังข้อมูลที่แตกต่างจากข้อมูลที่คุณต้องการจะใช้งานมาก
ในกรณีนี้คุณอาจจะจำเป็นต้องเทรน langauge model ขึ้นมาใหม่ เพื่อให้ได้โมเดลที่เหมาะกับการใช้งานของคุณ และในการเทรนนั้นคุณก็ต้องมี tokenizer ที่เหมาะกับข้อมูลของคุณ
แล้ววิธีเทรน tokenizer ขึ้นมาใหม่นั้นทำได้อย่างไร?

ใน[บทที่ 2](/course/chapter2) คุณจะเห็นว่าโมเดล Transformer ส่วนมากใช้เทคนิคการตัดคำที่ใช้หน่วยย่อยของคำ (_subword tokenization algorithm_ )
ในการตัดคำแบบนี้ ตัวตัดคำจะต้องหาหน่วยย่อยของคำ(subword)ที่เป็นประโยชน์และพบบ่อยในคลังข้อมูล ในกระบวนการหาคำย่อยนี้ tokenizer จะต้องอ่านทุกๆข้อความในคลังข้อมูล ขั้นตอนนี้เราเรียกว่าการ*เทรน*

กฎที่ใช้ในการเทรนนั้นขึ้นกับประเภทของ tokenizer ที่เราเลือกใช้ เราจะพูดถึงกับอัลกอริทึม 3 แบบที่ใช้ในการเทรน tokenizer กันในตอนท้ายของบทนี้

<Youtube id="DJimQynXZsQ"/>

<Tip warning={true}>

⚠️ การเทรน tokenize จะไม่เหมือนการกับเทรนโมเดลทั่วไป ในการเทรนโมเดลทั่วไปเราใช้ stochastic gradient descent เพื่อลดค่า loss ในทุก batch กระบวนการนี้มีความ random อยู่ในตัวของมัน (ซึ่งแปลว่า ถ้าคุณเทรนโมเดลสองครั้งแล้วอยากได้ผลลัพธ์ที่เหมือนกัน คุณจะต้องตั้งค่า seed ของการ random ให้เหมือนกันในทุกครั้งที่คุณเทรน)
ส่วนการเทรน tokenize เป็นกระบวนการทางสถิติที่พยายามจะค้นหาคำย่อยที่เหมาะสมที่สุดจากคลังข้อมูลหนึ่ง วิธีในการเลือกค้นหาคำย่อยนี้ก็มีหลากหลายวิธี
ผลลัพธ์ของการเทรนประเภทนี้จะมีความคงที่ (deterministic) ซึ่งแปลว่าคุณจะได้ผลลัพธ์เดิมทุกครั้งหลังจากการเทรน ถ้าหากคุณใช้อัลกอริทึมและข้อมูลเดิมทุกครั้ง
</Tip>

## การสร้างคลังข้อมูล (Assembling a corpus)

🤗 Transformers มี API ที่ใช้งานง่าย ที่สามารถใช้เทรน tokenizer ให้มีลักษณะเหมือน tokenizer ตัวอื่นที่เรามีอยู่แล้ว โดยการใช้ `AutoTokenizer.train_new_from_iterator()`

เพื่อให้คุณเห็นภาพชัดเจน เราจะสมมติว่า คุณต้องการเทรนโมเดล GPT-2 ตั้งแต่เริ่มแรก แต่เป็นภาษาอื่นที่ไม่ใช่ภาษาอังกฤษ
สิ่งที่แรกที่คุณต้องทำคือรวบรวมข้อความในภาษานั้นเพื่อสร้างชุดข้อมูลสำหรับการเทรน
ในตัวอย่างต่อไปนี้ เพื่อให้ผู้อ่านทุกคนเข้าใจได้ง่าย เราจะไม่ใช้ภาษารัสเซียหรือภาษาจีนเป็นตัวอย่าง แต่จะใช้ภาษาหนึ่งที่เป็นภาษาอังกฤษแบบพิเศษ นั่นคือ Python code


เราจะใช้ [🤗 Datasets](https://github.com/huggingface/datasets) library เพื่อช่วยสร้างคลังข้อมูล
และใช้ฟังก์ชัน `load_dataset()` เพื่อดาวโหลดและ cache ชุดข้อมูล [CodeSearchNet](https://huggingface.co/datasets/code_search_net)
ชุดข้อมูลชุดนี้ถูกสร้างขึ้นมาเพื่อใช้ในการแข่งขัน [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark)
และประกอบไปด้วยโค้ดของฟังก์ชันจาก open source libraries จาก GitHub ในหลายๆภาษา เราจะดาวโหลดเฉพาะโค้ดที่เป็น Python


```py
from datasets import load_dataset

# This can take a few minutes to load, so grab a coffee or tea while you wait!
raw_datasets = load_dataset("code_search_net", "python")
```

คุณสามารถเช็คดูข้อมูลส่วนที่ใช้เทรนได้โดยรันโค้ดข้างล่างนี้ เพื่อจะได้ดูว่าในชุดข้อมูลมีคอลัมน์อะไรบ้าง


```py
raw_datasets["train"]
```

```python out
Dataset({
    features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', 
      'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', 
      'func_code_url'
    ],
    num_rows: 412178
})
```

เราจะเห็นว่าในชุดข้อมูลนี้ ส่วนที่เป็น docstrings จะถูกแยก ออกจากส่วนที่เป็น code และนอกจากนั้น แต่ละส่วนยังมีอีกคอลัมน์เพื่อเก็บข้อความที่ถูกตัดออกเป็น token แล้วอีกด้วย
เราจะใช้แค่คอลัมน์ `whole_func_string` ในการเทรน tokenizer ของเรา

คุณสามารถสุ่มตัวอย่างของข้อมูลในแต่ละคอลัมน์มาดูได้ดังนี้

```py
print(raw_datasets["train"][123456]["whole_func_string"])
```

คำสั่งข้างบนจะ print ผลลัพธ์ข้างล่างนี้ :

```out
def handle_simple_responses(
      self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK):
    """Accepts normal responses from the device.

    Args:
      timeout_ms: Timeout in milliseconds to wait for each response.
      info_cb: Optional callback for text sent from the bootloader.

    Returns:
      OKAY packet's message.
    """
    return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms)
```

หลังจากนั้น เราก็จะต้องแปลงชุดข้อมูลนี้เป็น _iterator_ ของ list ของข้อความ (_iterator_ of lists of texts) ตัวอย่างเช่น list ของ list ของข้อความ (list of list of texts)
การใช้ list ของข้อความแบบนี้ จะทำให้การเทรนเร็วขึ้น เพราะว่าการเทรนเป็น batch จะเร็วกว่าการประมวลผลครั้งละหนึ่งข้อความ และสาเหตุที่ input ควรจะเป็น iterator ก็เพื่อป้องกันไม่ให้ Python อ่านข้อความทั้งหมดเข้าไปเก็บใน memory ของคอมพิวเตอร์ภายในครั้งเดียว
ถ้าชุดข้อมูลของคุณนั้นใหญ่มาก คุณอาจจะลองใช้ 🤗 Datasets เพื่อช่วยจัดการชุดข้อมูล เพราะมันจะไม่อ่านข้อมูลทั้งหมดเข้าไปเก็บใน RAM แต่บันทึกข้อมูลใน disk แทน

โค้ดข้างล่างนี้จะสร้าง list ของ list ของ 1,000 ข้อความ (list of lists of 1,000 texts) และจะโหลดข้อมูล input ทั้งหมดไปเก็บใน memory:

```py
# Don't uncomment the following line unless your dataset is small!
# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)]
```

ถ้าหากคุณเปลี่ยนมาใช้ Python generator แทน ก็จะป้องกันไม่ให้ Python โหลดข้อมูลทั้งหมดเข้าไปใน memory ถ้าไม่จำเป็น

วิธีการสร้าง generator ก็ง่ายๆเพียงแค่ แทนที่วงเล็บเหลี่ยม `[` ด้วยเว็บเล็บธรรมดา `(` ในโค้ดข้างบน:

```py
training_corpus = (
    raw_datasets["train"][i : i + 1000]["whole_func_string"]
    for i in range(0, len(raw_datasets["train"]), 1000)
)
```

โค้ดข้างบนนี้ จะไม่โหลดข้อความจาก `raw_datasets` ทั้งหมดเข้าไปใน memory แต่จะสร้าง iterator ซึ่งเป็น Python object ที่เป็นเสมือนตัวเก็บข้อมูลชั่วคราว

การจะเรียกใช้ข้อมูลในนั้น ทำได้โดยใช้ `for` loop   ข้อความใน iterator จะถูกโหลดเข้าไปใน memory ก็ต่อเมื่อคุณจะใช้งานมันเท่านั้น(ซึ่งก็คือ เวลาที่ `for` loop วนไปถึง item นั้น) ในตัวอย่างของเรา ในแต่ละ loop จะมีเพียงแค่ 1000 ข้อความเท่านั้นที่จะถูกโหลดมาเก็บไว้ใน memory การทำแบบนี้จะช่วยไม่ให้ memory ถูกใช้งานมากเกินไป หากคุณมีชุดข้อมูลที่ใหญ่มาก

แต่ข้อเสียของ generator ก็คือเราสามารถใช้มันได้แค่ครั้งเดียว ดูตัวอย่างจากโค้ดข้างล่างนี้
```py
gen = (i for i in range(10))
print(list(gen))
print(list(gen))
```

เราจะเห็นว่าโค้ดนี้ print ผลลัพธ์แค่ครั้งแรก ส่วนในการสั่ง print ครั้งที่สองเราได้เพียง list เปล่ากลับมา:

```python out
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
```

เพื่อแก้ปัญหานี้ เราจะสร้างฟังก์ชันที่ผลิต Python generator เพื่อเอาไว้เก็บชุดข้อมูลแทน:

```py
def get_training_corpus():
    return (
        raw_datasets["train"][i : i + 1000]["whole_func_string"]
        for i in range(0, len(raw_datasets["train"]), 1000)
    )


training_corpus = get_training_corpus()
```

การสร้าง generator ทำได้โดย ใช้ `for` loop และ `yield` statement:

```py
def get_training_corpus():
    dataset = raw_datasets["train"]
    for start_idx in range(0, len(dataset), 1000):
        samples = dataset[start_idx : start_idx + 1000]
        yield samples["whole_func_string"]
```

ฟังก์ชันนี้จะสร้าง generator แบบเดียวกับวิธีการข้างบน แต่ช่วยให้คุณสามารถเขียน logic ที่ซับซ้อนได้มากกว่าการใช้เพียง list comprehension

## การเทรน tokenizer

หลังจากเราก็มี iterator ที่แบ่งชุดข้อมูลเป็น batch แล้ว เราก็พร้อมแล้วที่จะเทรน tokenizer สิ่งแรกที่คุณต้องทำคือโหลด tokenizer ที่คุณต้องการจะใช้คู่กับโมเดลหลัก(ในตัวอย่างนี้โมเดลหลักของเราคือ GPT-2)

```py
from transformers import AutoTokenizer

old_tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

ถึงแม้ว่าเป้าหมายของเราคือการเทรน tokenizer ใหม่ เราจะเริ่มต้นด้วยการโหลด tokenizer ที่ถูกเทรนมาแล้ว เพื่อที่เราจะได้ไม่ต้องเริ่มกระบวนการทั้งหมดตั้งแต่แรก
ข้อดีของการทำแบบนี้ก็คือ คุณไม่ต้องเสียเวลาตั้งค่าต่างๆ เช่น ประเภทอัลกอริทึมของ tokenizer หรือ token พิเศษต่างๆ tokenizer ตัวใหม่ของเราจะมีโครงสร้างเหมือนกับตัวที่ใช้ใน GPT-2 สิ่งเดียวที่แตกต่างคือชุดคำศัพท์(vocabulary) ซึ่งจะเปลี่ยนไปตามชุดข้อมูลใหม่ที่เราจะใช้

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

```py
example = '''def add_numbers(a, b):
    """Add the two numbers `a` and `b`."""
    return a + b'''

tokens = old_tokenizer.tokenize(example)
tokens
```

```python out
['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo',
 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb']
```

tokenizer นี้มีการใช้สัญลักษณ์พิเศษ เช่น `Ġ` ซึ่งเอาไว้แทนช่องว่าง (space) และ `Ċ` ซึ่งแทนการเริ่มบรรทัดใหม่ (newline)
เราจะเห็นว่า ผลลัพธ์ของการตัดคำไม่ค่อยจะดีนัก เพราะว่าช่องว่างที่อยู่ต่อกันนั้น ถูกแบ่งออกเป็นอย่างละ token ซึ่งจริงๆแล้วการแบ่งที่ดีกว่านี้คือ ช่องว่างที่อยู่ติดกันควรจะถูกรวมให้เป็น token เดียว (เพราะว่าการพิมพ์ช่องว่าง 4 หรือ 8 ครั้ง เป็นสิ่งที่พบได้ทั่วไปในการเขียนโค้ด)

นอกจากนั้น tokenizer นี้ยังแบ่งชื่อฟังก์ชันได้ไม่ดีเท่าไหร่ เหมือนกับว่ามันไม่คุ้นเคยกับสัญลักษณ์ `_` ทำให้ชื่อฟังก์ชันถูกแยกออกเป็นสี่ส่วน


เรามาเริ่มเทรน tokenizer ตัวใหม่กัน แล้วดูว่า เราจะแก้ปัญหานี้ได้หรือเปล่า เราจะเริ่มจากการใช้ Python method ชื่อว่า `train_new_from_iterator()`:

```py
tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000)
```

เวลารันคำสั่งนี้ โปรแกรมอาจจะเวลาซักพัก ถ้าคุณใช้ชุดข้อมูลที่ใหญ่มาก แต่สำหรับชุดข้อมูลตัวอย่างของเราที่มีขนาด 1.6 GB การประมวลผลนั้นค่อนข้างเร็ว (ใช้เวลาทั้งหมด 1 นาที 16 วินาที บนซีพียู AMD Ryzen 9 3900X CPU ซึ่งมี 12 cores)

สิ่งหนึ่งที่คุณควรรู้คือ `AutoTokenizer.train_new_from_iterator()` นั้น ใช้งานได้แค่ในกรณีที่ตัวตัดคำเป็นแบบเร็ว

คุณจะได้เห็นในบทต่อไปว่า 🤗 Transformers library มี tokenizer สองประเภท ประเภทแรกคือตัวที่เขียนด้วย Python ล้วน และประเภทที่สอง(แบบเร็ว)ที่สร้างจาก 🤗 Tokenizers library ซึ่งใช้ภาษา [Rust](https://www.rust-lang.org) ในการเขียน
แม้ว่า Python จะเป็นภาษาที่ได้รับความนิยมมากที่สุดในงานด้าน data science และ deep learning แต่ถ้าเราต้องการประมวลผลข้อมูลให้รวดเร็วมากขึ้น โดยใช้การประมวลผลแบบ parallel (หมายถึง ประมวลผลหลายๆงานพร้อมๆกัน) เราจำเป็นต้องเขียนโปรแกรมด้วยภาษาอื่น
ตัวอย่างเช่น การคูณเมทริกซ์ ซึ่งถือเป็นการคำนวณหลักในการประมวลผลของโมเดลประเภท neural network โค้ดส่วนนี้จะถูกเขียนด้วยภาษา CUDA ซึ่งเป็น C library ที่ถูกพัฒนาให้เหมาะกับการใช้งานร่วมกับ GPU
หากเราเขียนโปรแกรมสำหรับเทรน tokenizer ด้วย Python อย่างเดียว จะทำให้การคำนวณช้ามาก นี่คือเหตุผลที่ Huggingface สร้าง 🤗 Tokenizers library ขึ้นมา

แต่ไม่ต้องกังวลกับส่วนนี้ เพราะคุณไม่จำเป็นต้องรู้ภาษา Rust เพื่อจะใช้งานตัวตัดคำแบบเร็วนี้ เหมือนกับที่คุณไม่จำเป็นต้องรู้ภาษา CUDA เพื่อจะรันโมเดลบน GPU

🤗 Tokenizers library มี Python bindings สำหรับ method ที่ต้องเรียกใช้โค้ดจากภาษา Rust
ตัวอย่างเช่น โค้ดส่วนที่ทำให้การเทรน tokenizer เป็นไปแบบ parallel หรือตอนที่เรารัน tokenizer กับ
input แบบ batch [Chapter 3](/course/chapter3)

โมเดล Transformer ส่วนมากรองรับการใช้งานร่วมกับตัวตัดคำแบบเร็ว (แต่มีกรณียกเว้น คุณสามารถเช็คดูได้ที่[นี่](https://huggingface.co/transformers/#supported-frameworks))
สำหรับโมเดลที่รองรับการตัดคำแบบเร็ว `AutoTokenizer` API จะโหลดตัวตัดคำแบบเร็วเป็นค่าเริ่มต้นเสมอ

ใน section ถัดไปเราจะเรียนเกี่ยวกับ feature พิเศษต่างๆของตัวตัดคำแบบเร็ว ซึ่งจะมีประโยชน์ในงานประเภท token classification หรือ question answering

ก่อนที่เราจะไปดูรายละเอียดกัน เรามาดูประสิทธิภาพของ tokenizer ที่เพิ่งเทรนเสร็จแล้วของเรากันดีกว่า เราจะลองใส่ข้อความที่เราใช้ในตัวอย่างด้านบนให้กับ tokenizer ของเราดู

```py
tokens = tokenizer.tokenize(example)
tokens
```

```python out
['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`',
 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb']
```

ในผลลัพธ์ของการตัดคำ คุณจะยังเห็นสัญลักษณ์พิเศษ `Ġ` และ `Ċ` เหมือนในตัวอย่างก่อนหน้า แต่คุณจะสังเกตว่า ตอนนี้ tokenizer ของเรานั้นได้เรียนรู้และเห็นว่า token บางตัวนั้น โดดเด่นกว่าตัวอื่นๆในชุดข้อมูล
ตัวอย่างเช่น token `ĊĠĠĠ` แสดงถึงการย่อหน้า(indentation) และ `Ġ"""` แสดงถึงเครื่องหมายคำพูดสามตัว ที่โปรแกรมเมอร์ใช้เวลาจะเริ่มเขียน docstring
ตัวตัดคำใหม่นี้ ยังแบ่งชื่อฟังก์ชันได้อย่างถูกต้องอีกด้วย โดยแบ่งที่ `_`

การแบ่งคำแบบนี้ ทำให้สัญลักษณ์หรือตัวอักษรต่างๆถูกรวบให้กระทัดรัดขึ้น หากเทียบกับ tokenizer เก่าที่เทรนจากข้อความภาษาอังกฤษปกติ เราจะเห็นว่า ถ้าเราใช้ทั้งสอง tokenizer เพื่อตัดข้อความ input เดียวกัน tokenizer เก่าจะให้ผลลัพธ์ที่ยาวกว่า tokenizer ตัวใหม่



```py
print(len(tokens))
print(len(old_tokenizer.tokenize(example)))
```

```python out
27
36
```

มาดูอีกตัวอย่างกัน :

```python
example = """class LinearLayer():
    def __init__(self, input_size, output_size):
        self.weight = torch.randn(input_size, output_size)
        self.bias = torch.zeros(output_size)

    def __call__(self, x):
        return x @ self.weights + self.bias
    """
tokenizer.tokenize(example)
```

```python out
['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',',
 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_',
 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(',
 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ',
 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ']
```

ในตัวอย่างนี้ นอกจากเราจะเห็น token ที่แสดงถึงย่อหน้าแล้ว เรายังเห็น token ของ double indentation ซึ่งคือ `ĊĠĠĠĠĠĠĠ` ส่วนคำที่มีความหมายพิเศษใน Python เช่น `class`, `init`, `call`, `self` และ `return` ก็ถูกแบ่งให้เป็นอย่างละ token อย่างถูกต้อง

นอกจากนั้น เราจะยังเห็นด้วยว่า tokenizer จะตัดแบ่งข้อความเวลาที่มันเห็นสัญลักษณ์ `_` และ `.` และยังแบ่งข้อความที่เป็น camel-cased ได้อย่างถูกต้อง เช่น `LinearLayer` ถูกแยกออกเป็น `["ĠLinear", "Layer"]`


## การบันทึก tokenizer

เพื่อที่เราจะสามารถใช้งาน tokenizer ที่เราเทรนเมื่อซักครู่นี้ได้อีกในครั้งหน้า เราจำเป็นจะต้องเก็บบันทึกมันไว้ ในการเซฟเราจะใช้ method ชื่อ `save_pretrained()`

```py
tokenizer.save_pretrained("code-search-net-tokenizer")
```

คำสั่งนี้จะสร้างแฟ้มงาน (folder) ขึ้นมาใหม่ ชื่อว่า *code-search-net-tokenizer* ซึ่งเอาไว้บันทึกข้อมูลต่างๆของ tokenizer ที่จำเป็นในการเรียกใช้งานอีกครั้ง
ถ้าคุณต้องการจะแชร์ tokenizer นี้กับเพื่อนร่วมงานหรือเพื่อนของคุณ คุณสามารถอัพโหลดมันไปที่ Hub ของ Huggingface ได้ โดยคุณจะต้อง login เข้าบัญชีก่อน
ถ้าคุณทำงานใน notebook (เช่น Jupyter notebook) คุณสามารถใช้ function ข้างล่างนี้ได้เพื่อความสะดวก

```python
from huggingface_hub import notebook_login

notebook_login()
```

หลังจากคุณรันโค้ดข้างบน คุณจะเห็น widget ให้ล็อกอินเข้าบัญชี Hugging Face
แต่หากคุณไม่ได้ใช้ notebook ให้พิมพ์คำสั่งข้างล่างนี้ใน terminal

```bash
huggingface-cli login
```

หลังจากล็อกอินแล้ว คุณจะสามารถ push tokenizer ของคุณไปที่ Hub ได้ โดยใช้คำสั่งข้างล่างนี้

```py
tokenizer.push_to_hub("code-search-net-tokenizer")
```

คำสั่งนี้จะสร้าง repository ใหม่ในชื่อ `code-search-net-tokenizer` ใน namespace ของคุณ ซึ่ง repository นี้ก็จะเก็บไฟล์เกี่ยวกับ tokenizer ของคุณไว้ หลังจากนั้น คุณก็จะสามารถดาวน์โหลด tokenizer นี้ได้ ด้วยการใช้ `from_pretrained()`
```py
# Replace "huggingface-course" below with your actual namespace to use your own tokenizer
tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer")
```

มาถึงตอนนี้คุณก็พร้อมแล้วที่จะเทรน และ fine-tune language model สำหรับงานที่คุณต้องการ เราจะเรียนเรื่องกันนี้ใน[บทที่ 7](/course/chapter7) แต่ในบทนี้ เราจะเรียนเกี่ยวกับ fast tokenizer ให้ละเอียดมากขึ้นและมาดูกันว่า เวลาคุณรัน `train_new_from_iterator()` มีการคำนวณอะไรเกิดขึ้นบ้าง

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter6/2.mdx" />

### การจัดการกับหลายๆประโยค(multiple sequences)
https://huggingface.co/learn/course/th/chapter2/5.md

# การจัดการกับหลายๆประโยค(multiple sequences)

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section5_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section5_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section5_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section5_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="M6adb1j2jPI"/>
{:else}
<Youtube id="ROxrFOEbsQE"/>
{/if}

ใน section ที่แล้ว เราได้ลองทำตัวอย่างการใช้งานที่ง่ายที่สุด: คือการอนุมาน(inference)โดยใช้ประโยคสั้นๆ เพียงประโยคเดียว อย่างไรก็ตาม ก็มีบางคำถามเกิดขึ้นมา:

- เราจะจัดการกับประโยคหลายๆประโยคอย่างไร?
- เราจะจัดการกับประโยคหลายๆประโยคที่มี *ความยาวต่างกัน* อย่างไร?
- ดัชนีคำศัพท์(vocabulary indices) เป็นเพียงข้อมูลประเภทเดียวที่ทำให้โมเดลทำงานได้ดีหรือไม่?
- มีประโยคที่เป็นประโยคที่ยาวเกินไปหรือไม่?

มาดูกันว่าปัญหาประเภทใดบ้างที่เกิดขึ้นจากคำถามเหล่านี้ และเราจะแก้ไขมันโดยใช้ 🤗 Transformers API ได้อย่างไร

## โมเดลคาดหวังที่จะได้ชุด(batch)ของข้อมูลเป็นอินพุต

ในแบบฝึกหัดที่ผ่านมาคุณได้เห็นแล้วว่าประโยคถูกแปลงไปเป็นลิสท์ของตัวเลขอย่างไร ทีนี้เรามาลองแปลงลิสท์ของตัวเลขนี้ไปเป็น tensor และใส่มันเข้าไปในโมเดล:

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = torch.tensor(ids)
# This line will fail.
model(input_ids)
```

```python out
IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = tf.constant(ids)
# This line will fail.
model(input_ids)
```

```py out
InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape]
```
{/if}

โอ้วไม่นะ! ทำไมมันมีข้อผิดพลาดละ? ในเมื่อเราก็ทำตามขั้นตอนต่างๆ จาก pipeline ใน section 2

ปัญหาก็คือเราใส่ประโยคเพียงประโยคเดียวเข้าไปในโมเดล แต่ในขณะที่โมเดล 🤗 Transformers นั้นต้องการประโยคหลายๆประโยค ในที่นี้เราได้ลองทำทุกอย่างที่ tokenizer ทำอยู่เบื้องหลังเมื่อเราใช้มันกับ `ประโยค(sequence)` แต่ถ้าคุณลองสังเกตุดีๆ คุณจะเห็นว่ามันไม่ได้เพียงแค่แปลง input IDs ไปเป็น tensor เท่านั้น แต่มันยังเพิ่มมิติ(dimension) ของ tensor อีกด้วย:

{#if fw === 'pt'}
```py
tokenized_inputs = tokenizer(sequence, return_tensors="pt")
print(tokenized_inputs["input_ids"])
```

```python out
tensor([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,
          2607,  2026,  2878,  2166,  1012,   102]])
```
{:else}
```py
tokenized_inputs = tokenizer(sequence, return_tensors="tf")
print(tokenized_inputs["input_ids"])
```

```py out
<tf.Tensor: shape=(1, 16), dtype=int32, numpy=
array([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662,
        12172,  2607,  2026,  2878,  2166,  1012,   102]], dtype=int32)>
```
{/if}

ลองอีกครั้งและเพิ่มมิติ(dimension)ใหม่ด้วย:

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = torch.tensor([ids])
print("Input IDs:", input_ids)

output = model(input_ids)
print("Logits:", output.logits)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = tf.constant([ids])
print("Input IDs:", input_ids)

output = model(input_ids)
print("Logits:", output.logits)
```
{/if}

เราแสดง input IDs พร้อมทั้งผล logits และนี่คือผลลัพธ์:

{#if fw === 'pt'}
```python out
Input IDs: [[ 1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607, 2026,  2878,  2166,  1012]]
Logits: [[-2.7276,  2.8789]]
```
{:else}
```py out
Input IDs: tf.Tensor(
[[ 1045  1005  2310  2042  3403  2005  1037 17662 12172  2607  2026  2878
   2166  1012]], shape=(1, 14), dtype=int32)
Logits: tf.Tensor([[-2.7276208  2.8789377]], shape=(1, 2), dtype=float32)
```
{/if}

*Batching* คือ การส่งประโยคหลายๆประโยคเข้าไปยังโมเดลพร้อมๆกันทีเดียว ถ้าคุณมีแค่หนึ่งประโยค คุณก็สามารถสร้างชุด(batch)ของประโยค ที่มีเพียงแค่หนึ่งประโยคได้:

```
batched_ids = [ids, ids]
```

นี่คือชุด(batch)ของข้อมูลที่ประด้วยสองประโยคที่เหมือนกัน!

<Tip>

✏️ **Try it out!** แปลงลิสท์ของ `batched_ids` นี้ไปเป็น tensor และใส่เข้าไปในโมเดลของคุณ แล้วลองตรวจสอบดูว่าคุณได้ logits เหมือนกับที่ได้ก่อนหน้านี้ไหม(แต่ได้สองค่า)!

</Tip>

ฺBatching นั้นทำให้โมเดลสามารถทำงานได้เมื่อคุณใส่ประโยคหลายๆประโยคเข้าไป การใช้ประโยคหลายๆประโยคนั้นก็สามารถทำได้ง่ายเหมือนกับที่ทำกับประโยคเดียว แต่ก็ยังมีปัญหาที่สอง เมื่อคุณพยายามรวมประโยคตั้งแต่สองประโยคขึ้นไปเป็นชุดข้อมูลเดียวกัน แต่ประโยคเหล่านั้นอาจจะมีความยาวที่แตกต่างกัน ถ้าคุณเคยทำ tensors มาก่อนหน้านี้ คุณจะรู้ว่ามันจำเป็นต้องมีขนาดจตุรัส(rectangular) ดังนั้นคุณจะไม่สามารถแปลงลิสท์ของ input IDs ไปเป็น tensor ได้โดยตรง เราสามารถแก้ปัญหานี้ได้ด้วยการ *pad* อินพุต

## การเติม(Padding) อินพุต

ลิสท์ของลิสท์ต่อไปนี้ไม่สามารถถูกแปลงไปเป็น tensor ได้:

```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200]
]
```

ในการแก้ปัญหานี้ เราจะใช้ *padding* เพื่อทำให้ tensor ของเรามีขนาดเป็นจตุรัส Padding จะทำให้ทุกประโยคของเรามีความยาวเท่ากันโดยการเพิ่มคำพิเศษ ที่เรียกว่า *padding token* ไปในประโยค ยกตัวอย่างเช่น ถ้าคุณมี 10 ประโยคที่แต่ละประโยคมี 10 คำ และมี 1 ประโยคที่มี 20 คำ padding ทำให้ทุกประโยคนั้นมี 20 คำเหมือนกัน ในตัวอย่างของเรา tensor ที่เป็นผลลัพท์ของเราเป็นแบบนี้:

```py no-format
padding_id = 100

batched_ids = [
    [200, 200, 200],
    [200, 200, padding_id],
]
```

padding token ID สามารถหาได้ใน `tokenizer.pad_token_id` เรามาลองใช้มันดูและใส่ประโยคสองประโยคของเราเข้าไปในโมเดลทีละอันและใส่แบบเป็นชุด(batch)ด้วย:

{#if fw === 'pt'}
```py no-format
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

print(model(torch.tensor(sequence1_ids)).logits)
print(model(torch.tensor(sequence2_ids)).logits)
print(model(torch.tensor(batched_ids)).logits)
```

```python out
tensor([[ 1.5694, -1.3895]], grad_fn=<AddmmBackward>)
tensor([[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
tensor([[ 1.5694, -1.3895],
        [ 1.3373, -1.2163]], grad_fn=<AddmmBackward>)
```
{:else}
```py no-format
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

print(model(tf.constant(sequence1_ids)).logits)
print(model(tf.constant(sequence2_ids)).logits)
print(model(tf.constant(batched_ids)).logits)
```

```py out
tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32)
tf.Tensor([[ 0.5803005  -0.41252428]], shape=(1, 2), dtype=float32)
tf.Tensor(
[[ 1.5693681 -1.3894582]
 [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32)
```
{/if}

มีอะไรบางอย่างผิดปกติเกิดขึ้นกับ logits ในการทำนายแบบเป็นชุด(batch) ของเรา: แถวที่สองควรจะได้ logits เหมือนกันกับประโยคที่สอง แต่เราได้ที่ต่างกันโดยสิ้นเชิง!

นี่เป็นเพราะว่าคุณลักษณะสำคัญของโมเดล Transformer คือ attention layers ที่พิจารณาบริบทของแต่ละ token โดยจะมีการพิจารณา padding token ด้วย เนื่องจาก attention layers พิจารณาทุก tokens ของประโยค เพื่อให้ได้ผลลัพท์เดียวกันไม่ว่าจะใส่ประโยคที่ความยาวต่างกันเข้าไปทีละประโยคหรือใส่ประโยคเดียวกันนี้ที่มีการ padding ไปพร้อมกันทีละหลายๆประโยค(batch) เราจำเป็นที่จะต้องบอก attention layers เหล่านั้นให้ไม่ต้องพิจารณา padding tokens โดยสามารถทำได้ด้วยการใช้ attention mask

## Attention masks

*Attention masks* คือ tensors ที่มีขนาดเท่ากับ tensor ของ input IDs และประกอบด้วย 0 และ 1: 1 บ่งบอกว่า tokens ในตำแหน่งนั้นๆ จำเป็นต้องพิจารณา, และ 0 บ่งบอกว่า tokens ในตำแหน่งนั้นๆ ไม่ต้องพิจารณา(กล่าวคือ มันควรจะถูกละเลยโดย attention layers ของโมเดล)

มาทำตัวอย่างที่แล้วโดยใช้ attention mask กัน:

{#if fw === 'pt'}
```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

attention_mask = [
    [1, 1, 1],
    [1, 1, 0],
]

outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask))
print(outputs.logits)
```

```python out
tensor([[ 1.5694, -1.3895],
        [ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
```
{:else}
```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200, tokenizer.pad_token_id],
]

attention_mask = [
    [1, 1, 1],
    [1, 1, 0],
]

outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask))
print(outputs.logits)
```

```py out
tf.Tensor(
[[ 1.5693681  -1.3894582 ]
 [ 0.5803021  -0.41252586]], shape=(2, 2), dtype=float32)
```
{/if}

ตอนนี้เราได้ logits เดียวกันสำหรับประโยคที่สองในชุด(batch) ของข้อมูล

สังเกตุว่าค่าสุดท้ายของประโยคที่สองนั้นเป็นอย่างไรใน padding ID ซึ่งก็คือค่า 0 ใน attention mask

<Tip>

✏️ **ลองเลย!** ทำ tokenization กับสองประโยคใน section 2 ("I've been waiting for a HuggingFace course my whole life." และ "I hate this so much!") แล้วใส่เข้าไปในโมเดลและตรวจสอบดูว่าคุณได้ logits เหมือนกับใน section 2 หรือไม่ หลังจากนั้นให้จับประโยครวมกันเป็นชุด(batch) โดยใช้ padding token แล้วสร้าง attention mask ที่ถูกต้อง และตรวจสอบดูว่าคุณได้ผลลัพท์เหมือนกันหรือไม่หลังจากใส่เข้าไปในโมเดลแล้ว!

</Tip>

## ประโยคที่ยาวขึ้น

การใช้โมเดล Transformer นั้นมีข้อจำกัดเรื่องความยาวของประโยคที่เราสามารถใส่เข้าไปได้ โมเดลส่วนใหญ่จะสามารถรองรับประโยคได้อย่างมาก 512 หรือ 1024 tokens และก็จะทำงานไม่ได้ถ้าให้มันประมวลผลประโยคที่ยาวขึ้น มีวีธีแก้ปัญหานี้อยู่ 2 วิธี:

- ใช้โมเดลที่รองรับประโยคที่ยาวขึ้น
- ตัดประโยคของคุณให้สั้นลง

โมเดลต่างๆ รองรับความยาวของประโยคที่แตกต่างกัน และบางโมเดลนั้นเชี่ยวชาญในการจัดการกับประโยคที่ยาวๆ [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) เป็นหนึ่งตัวอย่าง และอีกตัวอย่างก็คือ [LED](https://huggingface.co/transformers/model_doc/led.html) ถ้าคุณกำลังทำงานที่ต้องการประโยคที่ยาวมากๆ เราแนะนำให้คุณลองดูโมเดลเหล่านั้น

หรือในทางตรงกันข้าม เราแนะนำให้คุณตัดประโยคของคุณให้สั้นลงโดยการระบุตัวแปร `max_sequence_length: 

```py
sequence = sequence[:max_sequence_length]
```


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/5.mdx" />

### บทนำ
https://huggingface.co/learn/course/th/chapter2/1.md

# บทนำ

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

อย่างที่คุณเห็นใน [Chapter 1](/course/chapter1), โดยปกติแล้วโมเดล Transformer นั้นจะมีขนาดใหญ่มาก การเทรนและการใช้งานโมเดลเหล่านี้ที่มีตัวแปร (parameters) เป็นล้านไปจนถึง *หมื่นล้าน* ตัวแปรนั้นเป็นเรื่องที่ค่อนข้างซับซ้อน นอกจากนั้นแล้วการที่มีโมเดลใหม่ๆปล่อยออกมาเกือบทุกวันและแต่ละโมเดลก็มีวิธีการสร้าง (implementation) เป็นของตัวเอง ดังนั้นการจะลองทุกโมเดลนั้นไม่ใช่เรื่องที่ง่ายเลย
🤗 Transformers library สร้างขึ้นมาเพื่อแก้ปัญหานี้ จุดประสงค์ก็คือ การทำให้ไม่ว่าจะโมเดล Transformer ใดก็ตามสามารถโหลด, เทรน, และบันทึก ได้ด้วยการใช้ API เพียงอันเดียว จุดเด่นหลักๆของ library ประกอบด้วย

- **ใช้งานง่าย**: การดาวน์โหลด, การโหลด, และการใช้งานโมเดล NLP ที่ประสิทธิภาพดีที่สุด (state-of-the-art) สำหรับการอนุมาน (inference) นั้นสามารถทำได้ด้วยโค้ดเพียง 2 บรรทัด
- **ความยืดหยุ่น**: โดยแก่นแท้แล้วทุกโมเดลนั้นก็เป็นเพียคลาส `nn.Module` ง่ายๆของ PyTorch หรือ `tf.keras.Model` ของ TensorFlow และสามารถถูกจัดการได้เหมือนโมเดลอื่นๆ ใน machine learning (ML) frameworks นั้นๆ
- **ความเรียบง่าย**: การประกาศ abstractions ใดๆข้ามไปมาใน libraries นั้นน้อยมากๆ แนวคิดหลัก (core concept) ก็คือ "ทุกอย่างอยู่ในไฟล์เดียว (All in one file)" เช่น ขั้นตอนการเรียนรู้ของโมเดลใน forward pass นั้นสามารถประกาศทั้งหมดได้ในไฟล์เดียว ดังนั้นตัวโค้ดนั้นสามารถเป็นที่เข้าใจและแก้ไขได้ในตัวมันเอง

จุดเด่นข้อสุดท้ายนี่เองที่ทำให้ 🤗 Transformers ต่างจาก ML libraries อื่นๆ โมเดลต่างๆไม่ได้ถูกสร้างขึ้นมาจากโมดูลต่างๆที่ต้องแชร์ข้ามไฟล์กันไปมา แต่กลับกัน แต่ละโมเดลจะมี layers ของตัวเอง 
นอกจากจะทำให้โมเดลเข้าถึงและเข้าใจได้ง่ายแล้ว ยังทำให้คุณสามารถทดลองโมเดลๆหนึ่งโดยที่ไม่กระทบโมเดลอื่นๆ

บทนี้จะเริ่มด้วยตัวอย่างแบบ end-to-end ซึ่งเราจะใช้โมเดลและ tokenizer ร่วมกันเพื่อทำซ้ำ(เลียนแบบ) ฟังก์ชัน `pipeline()` จากที่เรียนใน [Chapter 1](/course/chapter1) หลังจากนั้นเราจะมาเรียนเกี่ยวกับ API ของโมเดล โดยเราจะเจาะลึกในคลาสของโมเดลและการตั้งค่า (configuration) และจะแสดงวิธีการโหลดโมเดลและกระบวนการที่โมเดลทำการทำนายผลจากชุดข้อมูลเชิงตัวเลข ว่าทำอย่างไร

หลังจากนั้นเราจะไปดูกันที่ tokenizer API ซึ่งเป็นอีกหนึ่งส่วนประกอบหลักของฟังก์ชัน `pipeline()`, Tokenizers จะรับผิดชอบการประมวลผลขั้นแรกและขั้นสุดท้าย ซึ่งก็คือ การแปลงข้อมูลที่เป็นข้อความให้เป็นข้อมูลเชิงตัวเลข เพื่อใช้กับ neural network, และการแปลงข้อมูลกลับไปเป็นตัวอักษร ในกรณีที่จำเป็น และสุดท้ายเราจะแสดงวิธีการจัดการกับการส่งข้อความทีละหลายๆประโยคแบบที่เตรียมไว้เป็นชุดๆ (batch) ไปยังโมเดล และปิดท้ายด้วยฟังก์ชัน `tokenizer()`

<Tip>
⚠️ เพื่อให้ได้ประโยชน์สูงสุดจากคุณลักษณะเด่นทั้งหมดที่มีใน Model Hub และ 🤗 Transformers, เราแนะนำให้คุณ <a href="https://huggingface.co/join">สร้างบัญชี</a>.
</Tip>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/1.mdx" />

### โมเดล
https://huggingface.co/learn/course/th/chapter2/3.md

# โมเดล

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section3_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="AhChOFRegn4"/>
{:else}
<Youtube id="d3JVgghSOew"/>
{/if}

{#if fw === 'pt'}
ใน section นี้ เราจะมาดูวิธีการสร้างและการใช้งานโมเดล เราจะใช้คลาส `AutoModel` ซึ่งเป็นประโยชน์มากหากเราต้องการสร้างโมเดลใดๆ จาก checkpoint หนึ่งๆ

คลาส `AutoModel` และส่วนประกอบของมันทั้งหมดนั้น จริงๆแล้วก็เป็นเพียง wrapper ของโมเดลต่างๆที่มีอยู่ใน library มันเป็น wrapper ที่ฉลาดโดยที่มันสามารถเดาสถาปัตยกรรมของโมเดลที่เหมาะสมสำหรับ checkpoint ของคุณได้ และสร้างโมเดลด้วยสถาปัตยกรรมนั้น 

{:else}
ใน section นี้เราจะมาดูวิธีการสร้างและการใช้งานโมเดล เราจะใช้คลาส `TFAutoModel` ซึ่งเป็นประโยชน์มากหากเราต้องการสร้างโมเดลใดๆ จาก checkpoint หนึ่งๆ

คลาส `TFAutoModel` และส่วนประกอบของมันทั้งหมดนั้น จริงๆแล้วก็เป็นเพียง wrapper ของโมเดลต่างๆที่มีอยู่ใน library มันเป็น wrapper ที่ฉลาดโดยที่มันสามารถเดาสถาปัตยกรรมของโมเดลที่เหมาะสมสำหรับ checkpoint ของคุณได้ และสร้างโมเดลด้วยสถาปัตยกรรมนั้น 

{/if}

แต่อย่างไรก็ตาม ถ้าคุณรู้ว่าคุณต้องการใช้โมเดลประเภทใด คุณสามารถใช้คลาสที่นิยามสถาปัตยกรรมนั้นได้โดยตรง เรามาดูกันว่ามันทำงานยังไงกับโมเดล BERT

## สร้าง Transformer

สิ่งแรกที่เราจำเป็นต้องทำในการเริ่มสร้างโมเดล BERT นั้นก็คือการโหลดวัตถุกำหนดค่า(configuration object):

{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

# Building the config
config = BertConfig()

# Building the model from the config
model = BertModel(config)
```
{:else}
```py
from transformers import BertConfig, TFBertModel

# Building the config
config = BertConfig()

# Building the model from the config
model = TFBertModel(config)
```
{/if}

ใน configuration นั้นประกอบด้วยค่าของคุณสมบัติ(attributes) หลายอย่างๆ ที่ใช้สำหรับสร้างโมเดล:

```py
print(config)
```

```python out
BertConfig {
  [...]
  "hidden_size": 768,
  "intermediate_size": 3072,
  "max_position_embeddings": 512,
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  [...]
}
```

ในขณะที่คุณยังไม่เห็นว่าคุณสมบัติต่างๆ เหล่าทำอะไรบ้าง คุณน่าจะพอจำบางส่วนได้: `hidden_size` ที่นิยามขนาดของเวคเตอร์ `hidden_states`, และ `num_hidden_layers` ที่นิยามจำนวนของเลเยอร์ที่โมเดล Transformer มี

### วิธีการต่างๆในการโหลด

สร้างโมเดลจาก configuration พื้นฐาน และตั้งค่าเริ่มต้นด้วยค่าสุ่ม(random values):

{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

config = BertConfig()
model = BertModel(config)

# โมเดลถูกกำหนดค่าเริ่มต้นด้วยการสุ่ม!
```
{:else}
```py
from transformers import BertConfig, TFBertModel

config = BertConfig()
model = TFBertModel(config)

# โมเดลถูกกำหนดค่าเริ่มต้นด้วยการสุ่ม!
```
{/if}

โมเดลสามารถอยู่ในสถานะนี้ได้ แต่มันจะให้ผลลัพธ์ที่แย่ออกมา; มันจำเป็นต้องผ่านการเรียนรู้ก่อน เราสามารถเทรนโมเดลจากโมเดลเปล่าๆ กับงานที่เรามีได้ แต่อย่างที่คุณเห็นใน [Chapter 1](/course/chapter1), มันใช้เวลานานและข้อมูลจำนวนมาก โดยที่ไม่ได้มีประโยชน์อะไรเพิ่มขึ้นมาก เพื่อลดขึ้นตอนที่ไม่จำเป็นต่างๆ มันสำคัญอย่างยิ่งที่เราจะสามารถแชร์และนำโมเดลที่ผ่านการเทรนมาแล้วมาใช้ใหม่

การโหลดโมเดล Transformer ที่ผ่านการเทรนมาแล้วนั้นง่ายมาก เราสามารถเรียกใช้ `from_pretrained()`: 

{#if fw === 'pt'}
```py
from transformers import BertModel

model = BertModel.from_pretrained("bert-base-cased")
```

เหมือนที่คุณเห็นก่อนหน้านี้ เราสามารถที่จะแทนค่า `BertModel` ด้วยคลาส AutoModel` ที่คล้ายคลึงกัน จากนี้ไปเราจะใช้วิธีการนี้เพื่อเป็นการสร้างโค้ด checkpoint-agnostic; ถ้าโค้ดของคุณสามารถใช้งานได้กับหนึ่ง checkpoint มันก็ควรที่จะสามารถใช้กับอันอื่นได้ด้วย ซึ่งก็รวมถึง ไม่ว่าสถาปัตยกรรมจะแตกต่างกัน ตราบใดที่ checkpoint นั้นถูกเทรนมาสำหรับงานที่เหมือนกันก็ควรใช้ได้เหมือนกัน (ยกตัวอย่างเช่น งาน sentiment analysis) 

{:else}
```py
from transformers import TFBertModel

model = TFBertModel.from_pretrained("bert-base-cased")
```

เหมือนที่คุณเห็นก่อนหน้านี้ เราสามารถที่จะแทนค่า `TFBertModel` ด้วยคลาส `TFAutoModel` ที่คล้ายคลึงกัน จากนี้ไปเราจะใช้วิธีการนี้เพื่อเป็นการสร้างโค้ด checkpoint-agnostic; ถ้าโค้ดของคุณสามารถใช้งานได้กับหนึ่ง checkpoint มันก็ควรที่จะสามารถใช้กับอันอื่นได้ด้วย ซึ่งก็รวมถึง ไม่ว่าสถาปัตยกรรมจะแตกต่างกัน ตราบใดที่ checkpoint นั้นถูกเทรนมาสำหรับงานที่เหมือนกันก็ควรใช้ได้เหมือนกัน (ยกตัวอย่างเช่น งาน sentiment analysis) 

{/if}

ในตัวอย่างโค้ดด้านบน เราไม่ได้ใช้ `BertConfig`, แต่ใช้งานโมเดลที่ผ่านการเทรนมาแล้ว(pretrained) ผ่าน `bert-base-cased` identifier ซึ่งนี่เป็น checkpoint ของโมเดลที่โดนเทรนด้วยผู้ที่ประดิษฐ์ BERT เอง; คุณสามารถดูรายละเอียดเพิ่มเติมได้ที่ [model card](https://huggingface.co/bert-base-cased).

ถึงตอนนี้โมเดลนี้ได้ถูกสร้างและมีค่าตั้งต้นเท่ากับ weights ของ checkpoint มันสามารถถูกนำไปใช้สำหรับการอนุมาน(inference)ได้ทันทีกับงานที่มันถูกเทรนมา และมันสามารถถูกนำมาปรับจูนเพิ่มเติมให้เข้ากับงานใหม่ได้ การเทรนโมเดลที่ใช้ weights ของโมเดลที่ผ่านการเทรนมาแล้ว แทนที่การเทรนจากไม่มีอะไรเลยนั้น ทำให้เราได้ผลลัพธ์ที่ดีในเวลาอันรวดเร็ว

weights ได้ถูกดาวน์โหลด และ เก็บไว้ในโฟลเดอร์ cache(เมื่อเราทำการเรียกใช้งาน `from_pretrained()` อีกในอนาคต weights เหล่านี้จะไม่ถูกดาวน์โหลดซ้ำอีก) โดยโฟลเดอร์มีค่าเริ่มต้น(default) อยู่ที่ *~/.cache/huggingface/transformers* คุณสามารถปรับเปลี่ยนโฟลเดอร์ cache ได้โดยตั้งค่า `HF_HOME` ใน environment variable

identifier ที่ใช้สำหรับโหลดโมเดลสามารถใช้ identifier ของโมเดลใดก็ได้บน Model Hub ตราบใดที่มันเข้ากันได้กับสถาปัตยกรรม BERT ลิสท์ของ BERT checkpoints ทั้งหมดที่มีอยู่สามารถดูได้จาก [ที่นี่](https://huggingface.co/models?filter=bert).

### วิธีสำหรับการบันทึก

การบันทึกโมเดลนั้นเป็นอะไรง่ายพอๆกับการโหลด - เราใช้ `save_pretrained()` ซึ่งก็เปรียบเสมือนกับ `from_pretrained()`:

```py
model.save_pretrained("directory_on_my_computer")
```

นี่เป็นการบันทึกสองไฟล์ลงไปที่ฮาร์ดดิสของคุณ:

{#if fw === 'pt'}
```
ls directory_on_my_computer

config.json model.safetensors
```
{:else}
```
ls directory_on_my_computer

config.json tf_model.h5
```
{/if}

ุุ้ถ้าคุณไปดูที่ไฟล์ *config.json* คุณจะพอนึกออกถึงคุณสมบัติ(attributes) ที่จำเป็นในการสร้างสถาปัตยกรรมของโมเดล ไฟล์นี้ประกอบด้วย metadata เช่น checkpoint เกิดมาจากที่ใด และ 🤗 Transformers เวอร์ชันใดที่คุณใช้ในการบันทึก checkpoint ล่าสุด

{#if fw === 'pt'}
ไฟล์ *model.safetensors* เป็นที่รู้จักในนาม *state dictionary*; มันประกอบด้วย weights ทัั้งหมดของโมเดลคุณ  สองไฟล์ที่มีความเชื่อมโยงกัน ไฟล์ configuration จำเป็นที่จะต้องรู้สถาปัตยกรรมของโมเดลของคุณ ในขณะที่ weights ของโมเดลคุณ ก็คือ ตัวแปร(parameters) ของโมเดลคุณ

{:else}
ไฟล์ *tf_model.h5* เป็นที่รู้จักในนาม *state dictionary*; มันประกอบด้วย weights ทัั้งหมดของโมเดลคุณ  สองไฟล์ที่มีความเชื่อมโยงกัน ไฟล์ configuration จำเป็นที่จะต้องรู้สถาปัตยกรรมของโมเดลของคุณ ในขณะที่ weights ของโมเดลคุณ ก็คือ ตัวแปร(parameters) ของโมเดลคุณ

{/if}

## ใชโมเดล Transformer สำหรับการอนุมาน(inference)

ุถึงตรงนี้คุณรู้วิธีการโหลดและบันทึกโมเดลแล้ว งั้นมาลองใช้มันทำนายอะไรบางอย่างดูกัน โมดล Transformer นั้นสามารถประมวลผลตัวเลขได้อย่างเดียว ซึ่งตัวเลขเหล่านี้ก็ได้มาจากการสร้างขึ้นมาโดยใช้ tokenizer แต่ก่อนที่เราจะไปอธิบายกันถึง tokenizer เรามาลองค้นหากันดูว่าอินพุตแบบไหนที่สามารถใส่เข้าไปในโมเดลได้บ้าง

Tokenizers นั้นสามารถที่จะแปลงอินพุตไปเป็น tensors ที่เหมาะสมสำหรับ framework นั้นๆ แต่เพื่อช่วยให้คุณเข้าใจสิ่งที่เกิดขึ้น เราจะมาดูกันว่าอะไรที่จำเป็นต้องทำก่อนที่เราจะส่งอินพุตเข้าไปในโมเดล

สมมติว่าเรามีคำ สอง สาม คำ:

```py
sequences = ["Hello!", "Cool.", "Nice!"]
```

tokenizer จะทำการแปลงคำเหล่านี้ไปเป็นดัชนีคำศัพท์(vocabulary indices) ซึ่งปกติจะเรียกว่า *input IDs* โดยตอนนี้แต่ละคำกลายเป็นลิสท์ของตัวเลข ผลลัพธ์ที่ได้ก็คือ:

```py no-format
encoded_sequences = [
    [101, 7592, 999, 102],
    [101, 4658, 1012, 102],
    [101, 3835, 999, 102],
]
```

นี่เป็นลิสท์ของคำที่ผ่านการเข้ารหัส(encoded): a list of lists, Tensors สามารถมีขนาดเป็นสี่เหลี่ยมจตุรัสเท่านั้น(ลองนึกถึงแมทริกซ์), "array" นี้มีขนาดเป็นสี่เหลี่ยมจตุรัสอยู่แล้ว ดังนั้นการแปลงมันไปเป็น tensor นั้นง่ายมาก:

{#if fw === 'pt'}
```py
import torch

model_inputs = torch.tensor(encoded_sequences)
```
{:else}
```py
import tensorflow as tf

model_inputs = tf.constant(encoded_sequences)
```
{/if}

### ใช้ tensors เป็นอินพุตเข้าไปยังโมเดล 

การใช้งาน tensor กับโมเดลนั้นง่ายมากๆ - เราก็แค่เรียกโมเดลพร้อมกับใส่อินพุต:

```py
output = model(model_inputs)
```

ในขณะที่โมเดลสามารถรับตัวแปร(arguments) ต่างๆได้มากมาย แค่ input IDs เท่านั้นที่จำเป็น เดี๋ยวเราจะอธิบายกันอีกทีว่าตัวแปรตัวอื่นๆเอาไว้ทำอะไร และจำเป็นต้องใช้เมื่อไหร่,
แต่ขั้นแรกเราต้องเข้าใจ Tokenizers ที่ใช้สร้างอินพุตที่โมเดล Transformer สามารถเข้าใจได้ก่อน


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/3.mdx" />

### ประกอบทุกอย่างเข้าด้วยกัน
https://huggingface.co/learn/course/th/chapter2/6.md

# ประกอบทุกอย่างเข้าด้วยกัน

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section6_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section6_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section6_tf.ipynb"},
]} />

{/if}

ในสองสาม sections ที่ผ่านมา เราได้พยายามทำทุกอย่างด้วยมือของเราเอง เราได้ลองศึกษาว่า tokenizer นั้นทำงานอย่างไรและวิธีการ tokenization, แปลงข้อมูลไปเป็น input IDs, การเติม(padding), การตัด(truncation), และ attention masks 

อย่างไรก็ตาม เหมือนที่เราเห็นใน section 2, 🤗 Transformers API นั้นสามารถจัดการกับสิ่งต่างๆเหล่านั้นให้เราได้ด้วย high-level ฟังก์ชันที่เราจะลงลึงในรายละเอียดกันในที่นี่ เมื่อคุณเรียกใช้งาน `tokenizer` ของคุณตรงๆกับประโยคหนึ่งๆ, คุณได้อินพุตที่พร้อมจะใส่เข้าไปยังโมเดลกลับมา:

```py
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)
```

ในที่นี้ ตัวแปร `model_inputs` นั้นประกอบด้วยทุกอย่างที่จำเป็นสำหรับโมเดลที่จะทำงานได้เป็นอย่างดี สำหรับ DistilBERT นั้นรวมไปถึง input IDs และ attention mask ด้วย ส่วนโมเดลอื่นๆที่รองรับอินพุตต่างๆเพิ่มเติมก็จะได้ผลลัพท์เหล่านั้นจาก `tokenizer` object ด้วย

อย่างที่เราจะได้เห็นในบางตัวอย่างด้านล่างนี้ วิธีนี้เป็นวิธีที่ทรงพลังมาก อันดับแรก มันสามารถที่จะ tokenize ประโยคเพียงประโยคเดียวได้:

```py
sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)
```

มันยังสามารถจัดการกับประโยคหลายๆประโยคได้ในคราวเดียวกัน โดยที่ไม่มีอะไรเปลี่ยนใน  API เลย:

```py
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

model_inputs = tokenizer(sequences)
```

มันสามารถที่จะเติม(padding) ให้สอดคล้องกับหลายๆวัตถุประสงค์:

```py
# จะเติมประโยคไปจนถึงความยาวที่ยาวที่สุดของประโยค
model_inputs = tokenizer(sequences, padding="longest")

# จะเติมประโยคไปจนถึงความยาวที่ยาวที่สุดที่โมเดลรับได้
# (512 for BERT or DistilBERT)
model_inputs = tokenizer(sequences, padding="max_length")

# จะเติมประโยคไปจนถึงความยาวที่ยาวที่สุดที่ระบุไว้
model_inputs = tokenizer(sequences, padding="max_length", max_length=8)
```

มันสามารถตัดประโยคได้อีกด้วย:

```py
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

# จะตัดประโยคที่มีความยาวเกินกว่าความยาวที่โมเดลรับได้
# (512 for BERT or DistilBERT)
model_inputs = tokenizer(sequences, truncation=True)

# จะตัดประโยคที่มีความยาวเกินกว่าความยาวที่ระบุไว้
model_inputs = tokenizer(sequences, max_length=8, truncation=True)
```

`tokenizer` object สามารถที่จะจัดการกับการแปลงข้อมูลไปเป็น tensors สำหรับ framework ที่เฉพาะเจาะจงได้ ซึ่งสามารถที่จะส่งเข้าโมเดลได้ทันที ยกตัวอย่างเช่น ในโค้ดตัวอย่างต่อไปนี้ เราจะสั่งให้ tokenizer ส่ง tensors จาก frameworks ต่างๆ กัน — `"pt"` ให้ PyTorch tensors, `"tf"` ให้ TensorFlow tensors, and `"np"` ให้ NumPy arrays:

```py
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

# Returns PyTorch tensors
model_inputs = tokenizer(sequences, padding=True, return_tensors="pt")

# Returns TensorFlow tensors
model_inputs = tokenizer(sequences, padding=True, return_tensors="tf")

# Returns NumPy arrays
model_inputs = tokenizer(sequences, padding=True, return_tensors="np")
```

## tokens พิเศษ

ถ้าเราดูที่ input IDs ที่ได้จาก tokenizer เราจะเห็นได้ว่ามันค่อนข้างแตกต่างไปจากสิ่งที่เราเคยได้ก่อนหน้านี้:

```py
sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)
print(model_inputs["input_ids"])

tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)
```

```python out
[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102]
[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]
```

มีหนึ่ง token ID ได้ถูกใส่เข้ามาด้านหน้าสุด และอีกหนึ่ง token ID ใส่ด้านหลังสุด มาถอดรหัสสองประโยคของ IDs ด้านบนดูว่ามันเกี่ยกับอะไร:

```py
print(tokenizer.decode(model_inputs["input_ids"]))
print(tokenizer.decode(ids))
```

```python out
"[CLS] i've been waiting for a huggingface course my whole life. [SEP]"
"i've been waiting for a huggingface course my whole life."
```

tokenizer ทำการเพิ่มคำพิเศษ `[CLS]` ที่ด้านหน้าสุด และคำพิเศษ `[SEP]` ที่ด้านหลังสุด นั้นก็เพราะว่าโมเดลนั้นได้ผ่านการเทรนมาแบบนั้น ดังนั้นเพื่อให้ได้ผลลัพท์เดียวกันสำหรับการอนุมาน(inference) เราจำเป็นต้องเพิ่มมันเข้าไปเช่นเดียวกัน แต่ก็ต้องตระหนักว่าบางโมเดลนั้นไม่ได้เพิ่มคำพิเศษ หรือ ใส่คำที่ต่างออกไป; โมเดลอาจจะเพิ่มคำพิเศษเหล่านี้แค่เฉพาะด้านหน้าสุด หรือ ด้านหลังสุดเท่านั้น ไม่ว่าจะในกรณีใดๆ tokenizer รู้ว่าอันไหนเป็นอันที่ต้องการและมันจะจัดการให้คุณเอง:

## สรุป: จาก tokenizer ไปยังโมเดล


ถึงตรงนี้เราได้เห็นขั้นตอนแต่ละอย่างทั้งหมดที่ `tokenizer` ใช้เพื่อประมวลผลข้อความ เรามาดูกันครั้งสุดท้ายว่ามันสามารถจัดการประโยคหลายๆประโยค (padding!), ประโยคยาวๆ, และ tensors หลายๆ ประเภทได้อย่างไรด้วย API หลักของมัน:

{#if fw === 'pt'}
```py
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")
output = model(**tokens)
```
{:else}
```py
import tensorflow as tf
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")
output = model(**tokens)
```
{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/6.mdx" />

### แบบทดสอบท้ายบท
https://huggingface.co/learn/course/th/chapter2/8.md

# แบบทดสอบท้ายบท

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

### 1. ลำดับขั้นตอนใน pipeline ของการทำโมเดลด้านภาษา(language modeling)เป็นอย่างไร ?

<Question
	choices={[
		{
			text: "ขั้นตอนแรก โมเดลจะทำการประมวลผลข้อความและให้ผลการทำนายออกมา หลังจากนั้น tokenizer จะทำการวิเคราะห์ผลการทำนายเหล่านี้และแปลงมันกลับมาเป็นข้อความเมื่อจำเป็น",
			explain: "โมเดลไม่สามารถเข้าใจข้อความได้! tokenizer จำเป็นต้องทำการ tokenize ข้อความก่อนและแปลงมันไปเป็น IDs ที่โมเดลสามารถเข้าใจมันได้"
		},
		{
			text: "ขั้นตอนแรก tokenizer จะประมวลผลข้อความและให้ IDs ออกมา หลังจากนั้นโมเดลจะประมวลผล IDs เหล่านี้และให้ผลการทำนายออกมา ซึ่งอาจจะเป็นข้อความบางอย่าง",
			explain: "ผลการทำนายของโมเดลไม่สามารถเป็นข้อความออกมาได้โดยตรง tokenizer จำเป็นต้องถูกใช้สำหรับการแปลงผลการทำนายกลับไปเป็นข้อความ!"
		},
		{
			text: "tokenizer ประมวลผลข้อความและให้ IDs ออกมา หลังจากนั้นโมเดลจะประมวลผล IDs เหล่านี้และให้ผลการทำนายออกมา จากนั้น tokenizer จะถูกใช้อีกครั้งในการแปลงผลการทำนายเหล่านี้กลับไปเป็นข้อความ",
			explain: "ถูกต้อง! tokenizer สามารถถูกใช้สำหรับทั้งการแปลงข้อความไปเป็น IDs (tokenizing) และการแปลงผลการทำนายกลับไปเป็นข้อความ(de-tokenizing)",
            correct: true
		}
	]}
/>

### 2. tensor ที่เป็นเอาท์พุตออกมาจากโมเดล Transformer แบบพื้นฐานมีขนาดกี่มิติ และมิติเหล่านั้นเป็นอะไรบ้าง?

<Question
	choices={[
		{
			text: "2: ความยาวของประโยค(sequence length) และขนาดของชุดข้อมูล(batch size)",
			explain: "ผิด! tensor ที่ออกมาจากโมเดลมีมิติที่สามด้วย: ขนาดของเลเยอร์ภายใน(hidden size)"
		},
		{
			text: "2: ความยาวของประโยค(sequence length) และขนาดของเลเยอร์ภายใน(hidden size)",
			explain: "ผิด! โมเดล Transformer ทุกโมเดลจะรับข้อมูลแบบชุด(batch), ถึงแม้จะมีประโยคเพียงประโยคเดียว; ขนาดของชุด(batch size) ก็เป็น 1!"
		},
		{
			text: "3: ความยาวของประโยค(sequence length) ขนาดของชุดข้อมูล(batch size) และขนาดของเลเยอร์ภายใน(hidden size)",
			explain: "ถูกต้อง!",
            correct: true
		}
	]}
/>

### 3. ข้อใดต่อไปนี้เป็นตัวอย่างของ tokenization แบบคำย่อย(subword)?

<Question
	choices={[
		{
			text: "WordPiece",
			explain: "ถูกต้อง, ข้อนี้เป็นตัวอย่างหนึ่งของ tokenization แบบคำย่อย(subword)!",
            correct: true
		},
		{
			text: "tokenization ที่เน้นที่ตัวอักษร(Character-based)",
			explain: "tokenization ที่เน้นที่ตัวอักษร(Character-based) ไม่ได้เป็นประเภทของ tokenization แบบคำย่อย(subword)"
		},
		{
			text: "Splitting on whitespace and punctuation",
			explain: "อันนี้เป็น tokenization ประเภทเน้นคำ(word-based)!"
		},
		{
			text: "BPE",
			explain: "ถูกต้อง, ข้อนี้เป็นตัวอย่างหนึ่งของ tokenization แบบคำย่อย(subword)!",
            correct: true
        },
		{
			text: "Unigram",
			explain: "ถูกต้อง, ข้อนี้เป็นตัวอย่างหนึ่งของ tokenization แบบคำย่อย(subword)!",
            correct: true
        },
		{
			text: "ไม่มีคำตอบที่ถูกต้อง",
			explain: "ผิด!"
        }
	]}
/>

### 4. model head คืออะไร?

<Question
	choices={[
		{
			text: "ส่วนประกอบหนึ่งของโมเดล Transformer พื้นฐาน ที่ส่งต่อ tensor ไปยังเลเยอร์ที่ถูกต้องของมัน",
			explain: "ผิด! ไม่มีส่วนประกอบแบบนั้น."
		},
		{
			text: "มันถูกเรียกอีกอย่างหนึ่งว่าเป็นกระบวนการ self-attention, มันดัดแปลงตัวแทน(representation) ของ token หนึ่งๆ เทียบกับ tokens อื่นๆ ในประโยค",
			explain: "ผิด! เลเยอร์ self-attention นั้นมี attention \"heads,\" แต่มันไม่ใช่ตัวที่เอาไว้ดัดแปลงข้อมูล(not adaptation heads)"
		},
		{
			text: "เป็นส่วนประกอบเสริม ที่ประกอบด้วยเลเยอร์อย่างน้อยหนึ่งเลเยอร์ ใช้สำหรับแปลงผลการทำนายของ transformer ไปเป็นผลลัพท์เฉพาะสำหรับงานหนึ่งๆ",
			explain: "ถูกต้อง. Adaptation heads เรียกง่ายๆ ก็คือ heads, มีหลากหลายรูปแบบ:  heads สำหรับโมเดลด้านภาษา(language modeling), heads สำหรับการตอบคำถาม(question answering), heads สำหรับการจำแนกประโยค(sequence classification) ... ",
			correct: true
		} 
	]}
/>

{#if fw === 'pt'}
### 5. AutoModel คืออะไร?

<Question
	choices={[
		{
			text: "โมเดลที่เทรนด้วยข้อมูลของคุณแบบอัตโนมัติ",
			explain: "ผิด คุณสับสนกับผลิตภัณฑ์ <a href='https://huggingface.co/autonlp'>AutoNLP</a> ของเราหรือเปล่า?"
		},
		{
			text: "เป็น object ที่ให้สถาปัตยกรรมที่ถูกต้องสำหรับ checkpoint นั้นๆออกมา",
			explain: "ถูกต้อง: <code>AutoModel</code> จำเป็นต้องรู้เพียงแค่ว่า checkpoint ใดที่จะใช้ในการสร้างโมเดลและให้สถาปัตยกรรมที่ถูกต้องกลับมา",
			correct: true
		},
		{
			text: "โมเดลที่ตรวจหาภาษาที่ใช้สำหรับเป็นอินพุตของมันโดยอัตโนมัติเพื่อที่จะโหลด weights ที่ถูกต้อง",
			explain: "ผิด; ในขณะที่บาง checkpoints และโมเดล นั้นสามารถประมวลผลได้หลายภาษา, แต่ไม่มีเครื่องมือแบบ built-in สำหรับเลือก checkpoint ที่ตรงกับภาษาแบบอัตโนมัติเลย คุณลองไปดูที่ <a href='https://huggingface.co/models'>Model Hub</a> เพื่อหา checkpoint ที่ดีที่สุดสำหรับงานของคุณ!"
		} 
	]}
/>

{:else}
### 5. TFAutoModel คืออะไร?

<Question
	choices={[
		{
			text: "โมเดลที่เทรนด้วยข้อมูลของคุณแบบอัตโนมัติ",
			explain: "ผิด คุณสับสนกับผลิตภัณฑ์ <a href='https://huggingface.co/autonlp'>AutoNLP</a> ของเราหรือเปล่า?"
		},
		{
			text: "เป็น object ที่ให้สถาปัตยกรรมที่ถูกต้องสำหรับ checkpoint นั้นๆออกมา",
			explain: "ถูกต้อง: <code>AutoModel</code> จำเป็นต้องรู้เพียงแค่ว่า checkpoint ใดที่จะใช้ในการสร้างโมเดลและให้สถาปัตยกรรมที่ถูกต้องกลับมา",
			correct: true
		},
		{
			text:"โมเดลที่ตรวจหาภาษาที่ใช้สำหรับเป็นอินพุตของมันโดยอัตโนมัติเพื่อที่จะโหลด weights ที่ถูกต้อง",
			explain: "ผิด; ในขณะที่บาง checkpoints และโมเดล นั้นสามารถประมวลผลได้หลายภาษา, แต่ไม่มีเครื่องมือแบบ built-in สำหรับเลือก checkpoint ที่ตรงกับภาษาแบบอัตโนมัติเลย คุณลองไปดูที่ <a href='https://huggingface.co/models'>Model Hub</a> เพื่อหา checkpoint ที่ดีที่สุดสำหรับงานของคุณ!"
		} 
	]}
/>

{/if}

### 6. มีเทคนิคอะไรบ้างที่เราต้องคำนึงถึงเมื่อจะต้องทำการจัดประโยคที่มีความยาวแตกต่างกันเข้าเป็นชุดเดียวกัน(batching)?
<Question
	choices={[
		{
			text: "การตัด(Truncating)",
			explain: "ถูกต้อง, การตัด(truncation) เป็นวิธีที่ถูกต้องในการทำให้ประโยคนั้นมีความยาวเท่ากันเพื่อให้สามารถใส่เข้าไปแล้วชุดข้อมูลมีขนาดเป็นจตุรัส แต่มันมีเพียงแค่เทคนิคนี้เทคนิคเดียวหรือเปล่า?",
			correct: true
		},
		{
			text: "ให้ผลลัพท์ที่เป็น tensors ออกมา",
			explain: "ในขณะที่ใช้เทคนิคอื่นๆ นั้น คุณสามารถได้ผลลัพท์ที่เป็น tensors แบบจตุรัสได้, ดังนั้นการให้ผลลัพท์ที่เป็นเพียง tensor นั้นไม่ได้เป็นประโยชน์เมื่อทำการรวมประโยคเข้าเป็นชุดเดียวกัน"
		},
		{
			text: "การเติม(Padding)",
			explain: "ถูกต้อง, การเติม(padding) เป็นวิธีที่ถูกต้องในการทำให้ประโยคนั้นมีความยาวเท่ากันเพื่อให้สามารถใส่เข้าไปแล้วชุดข้อมูลมีขนาดเป็นจตุรัส แต่มันมีเพียงแค่เทคนิคนี้เทคนิคเดียวหรือเปล่า?",
			correct: true
		}, 
		{
			text: "Attention masking",
			explain: "ถูกต้องเลย! Attention masks เป็นกระบวนการที่สำคัญมากในการจัดการประโยคที่มีความยาวแตกต่างกัน แต่นั้นก็ไม่ใช่เพียงวิธีเดียว",
			correct: true
		} 
	]}
/>

### 7. อะไรคือจุดประสงค์ของการใช้ฟังก์ชัน SoftMax กับผลลัพท์ที่เป็น logits ที่ได้จากโมเดลสำหรับจำแนกประโยค (sequence classification model)?

<Question
	choices={[
		{
			text: "มันจะช่วยลดค่าของ logits ลงทำให้มันมีความน่าเชื่อถือมากขึ้น",
			explain: "ผิด, ฟังก์ชัน SoftMax ไม่ได้ส่งผลอะไรกับความน่าเชื่อถือของผลลัพท์"
		},
		{
			text: "มันกำหนดขอบเขตค่าบนและล่าง เพื่อทำให้ค่าที่ได้สามารถเป็นที่เข้าใจได้",
			explain: "ถูกต้อง! ค่าผลลัพท์ที่ได้มีขอบเขตระหว่าง 0 และ 1 แต่นี่ก็ไม่ได้เป็นเพียงเหตุผลเดียวที่เราใช้ฟังก์ชัน SoftMax",
            correct: true
		},
		{
			text: "ผลรวมของผลลัพท์เท่ากับ 1 ส่งผลให้สามารถแปลความหมายทางสถิติได้",
			explain: "ถูกต้อง! แต่นี่ก็ไม่ได้เป็นเพียงเหตุผลเดียวที่เราใช้ฟังก์ชัน SoftMax",
            correct: true
		}
	]}
/>

### 8. วิธีใดที่เป็นหัวใจหลักของ tokenizer API ส่วนใหญ่?

<Question
	choices={[
		{
			text: "<code>encode</code>, เพราะมันสามารถเข้ารหัสข้อความไปเป็น IDs และ IDs ไปเป็นคำทำนายได้",
			explain: "ผิด! ในขณะที่วิธีการ <code>encode</code> นั้นไม่มีอยู่ใน tokenizers, มันก็ไม่มีอยู่ในโมเดลเช่นเดียวกัน"
		},
		{
			text: "การเรียก tokenizer object โดยตรง",
			explain: "ถูกต้อง! วิธี <code>__call__</code> ของ tokenizer เป็นวิธีการที่ทรงพลังมากที่สามารถจัดการได้เกือบทุกอย่าง และมันก็เป็นวิธีการที่ถูกใช้ในการเอาผลการทำนายออกมาจากโมเดลด้วย",
			correct: true
		},
		{
			text: "<code>pad</code>",
			explain: "ผิด! การเติม(Padding) เป็นประโยชน์มาก แต่มันก็เป็นแค่ส่วนหนึ่งของ tokenizer API"
		},
		{
			text: "<code>tokenize</code>",
			explain: "วิธี <code>tokenize</code> เป็นหนึ่งวิธีที่มีประโยชน์มากแต่มันก็ไม่ใช่หัวใจหลักของ tokenizer API"
		}
	]}
/>

### 9. ตัวแปร `result` ในตัวอย่างโค้ดนี้มีค่าอะไรอยู่บ้าง?

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
result = tokenizer.tokenize("Hello!")
```

<Question
	choices={[
		{
			text: "ลิสท์ของกลุ่มตัวอักษร(A list of strings), โดยที่แต่ละ string นั้นเป็น token",
			explain: "ถูกต้อง! แปลง token ไปเป็น IDs และส่งเข้าไปยังโมเดล!",
            correct: true
		},
		{
			text: "ลิสท์ของ IDs",
			explain: "ไม่ถูกต้อง; นั้นเป็นหน้าที่ของ <code>__call__</code> หรือ <code>convert_tokens_to_ids</code>!"
		},
		{
			text: "string ที่ประกอบด้วย tokens ทั้งหมด",
			explain: "นั้นอาจจะไม่ใช่วิธีที่ดีที่สุด, เมื่อเป้าหมายคือการแบ่ง string ออกเป็นหลายๆ tokens "
		}
	]}
/>

{#if fw === 'pt'}
### 10. มีอะไรบางอย่างที่ผิดปกติกับโค้ดต่อไปนี้หรือไม่?

```py
from transformers import AutoTokenizer, AutoModel

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
model = AutoModel.from_pretrained("gpt2")

encoded = tokenizer("Hey!", return_tensors="pt")
result = model(**encoded)
```

<Question
	choices={[
		{
			text: "ไม่ ดูเหมือนว่ามันจะปกติ",
			explain: "โชคไม่ดีเลย, การต่อโมเดลกับ tokenizer ที่ผ่านการเทรนที่มาจาก checkpoint ที่แตกต่างกันนั้นเป็นไอเดียที่ไม่ค่อยดีเท่าไหร่ โมเดลไม่ได้ถูกเทรนมาให้สามารถเข้าใจผลลัพท์ที่ได้จาก tokenizer นี้ ดังนั้นผลลัพท์ของโมเดล(ถ้ามันสามารถรันได้!)ก็จะดูไม่เป็นเหตุเป็นผลซักเท่าไหร่"
		},
		{
			text: "tokenizer และโมเดลควรจะมาจาก checkpoint เดียวกันเสมอ",
			explain: "ถูกต้อง!",
            correct: true
		},
		{
			text: "มันเป็นสิ่งที่ดีในการเติม(pad) และ ตัด(truncate) โดยใช้ tokenizer ซึ่งทุกอินพุดก็เป็นชุดของข้อมูลอยู่แล้ว",
			explain: "ถูกต้องที่ว่าทุกอินพุตของโมเดลจำเป็นต้องอยู่ในรูปแบบของชุด(batch) แต่อย่างไรก็ตาม การตัด(truncating) หรือ การเติม(padding) ประโยคนี้อาจจะไม่สมเหตุสมผล เนื่องจากมันมีแค่ประโยคเดียวและเทคนิคเหล่านั้นเป็นเทคนิคสำหรับการรวมกลุ่มประโยค(a list of sentences)เข้าด้วยกันให้เป็นชุดๆ(batch)"
		}
	]}
/>

{:else}
### 10. มีอะไรบางอย่างที่ผิดปกติกับโค้ดต่อไปนี้หรือไม่?

```py
from transformers import AutoTokenizer, TFAutoModel

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
model = TFAutoModel.from_pretrained("gpt2")

encoded = tokenizer("Hey!", return_tensors="pt")
result = model(**encoded)
```

<Question
	choices={[
		{
			text: "ไม่ ดูเหมือนว่ามันจะปกติ",
			explain:  "โชคไม่ดีเลย, การต่อโมเดลกับ tokenizer ที่ผ่านการเทรนที่มาจาก checkpoint ที่แตกต่างกันนั้นเป็นไอเดียที่ไม่ค่อยดีเท่าไหร่ โมเดลไม่ได้ถูกเทรนมาให้สามารถเข้าใจผลลัพท์ที่ได้จาก tokenizer นี้ ดังนั้นผลลัพท์ของโมเดล(ถ้ามันสามารถรันได้!)ก็จะดูไม่เป็นเหตุเป็นผลซักเท่าไหร่"
		},
		{
			text: "tokenizer และโมเดลควรจะมาจาก checkpoint เดียวกันเสมอ",
			explain: "ถูกต้อง!",
            correct: true
		},
		{
			text: "มันเป็นสิ่งที่ดีในการเติม(pad) และ ตัด(truncate) โดยใช้ tokenizer ซึ่งทุกอินพุดก็เป็นชุดของข้อมูลอยู่แล้ว",
			explain: "ถูกต้องที่ว่าทุกอินพุตของโมเดลจำเป็นต้องอยู่ในรูปแบบของชุด(batch) แต่อย่างไรก็ตาม การตัด(truncating) หรือ การเติม(padding) ประโยคนี้อาจจะไม่สมเหตุสมผล เนื่องจากมันมีแค่ประโยคเดียวและเทคนิคเหล่านั้นเป็นเทคนิคสำหรับการรวมกลุ่มประโยค(a list of sentences)เข้าด้วยกันให้เป็นชุดๆ(batch)"
		}
	]}
/>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/8.mdx" />

### Tokenizers
https://huggingface.co/learn/course/th/chapter2/4.md

# Tokenizers

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_tf.ipynb"},
]} />

{/if}

<Youtube id="VFp38yj8h3A"/>

Tokenizers เป็นหนึ่งในส่วนประกอบหลักของ NLP pipeline โดยมีจุดประสงค์เดียวคือ เพื่อแปลงข้อความไปเป็นข้อมูลที่โมเดลสามารถประมวลผลได้ โมเดลสามารถประมวผลได้เพียงแค่ตัวเลขเท่านั้น ดังนั้น tokenizers จึงจำเป็นต้องแปลงข้อความของเราไปเป็นข้อมูลตัวเลข ใน section นี้  เราจะมาลองดูกันว่ามีเกิดอะไรขึ้นบ้างใน tokenization pipeline

ในงาน NLP ข้อมูลโดยทั่วไปแล้วจะเป็นข้อความ นี่เป็นตัวอย่างของข้อความดังกล่าว:

```
Jim Henson was a puppeteer
```

แต่อย่างไรก็ตาม โมเดลสามารถประมวผลได้เพียงแค่ตัวเลขเท่านั้น ดังนั้นเราจำเป็นต้องหาทางแปลงข้อความดิบไปเป็นตัวเลข นั่นคือสิ่งที่ tokenizer ทำ และก็มีหลายวิธีมากในการทำ เป้าหมายก็คือ หาตัวแทน(representation)ที่มีความหมายที่สุด - หมายความว่า สิ่งที่โมเดลจะเข้าใจได้มากที่สุด - และ ถ้าเป็นไปได้ เป็นตัวแทนที่มีขนาดเล็กที่สุด

ลองมาดูตัวอย่างของ tokenization algorithms และพยายามตอบคำถามบางคำถามที่คุณอาจจะมีเกี่ยวกับ tokenization

## เน้นที่คำ (Word-based)

<Youtube id="nhJxYji1aho"/>

Tokenizer ประเภทแรกที่เรานึกถึงคือ _word-based_ มันติดตั้งและใช้งานง่ายมากโดยมีกฏเพียงไม่กี่ข้อและมันก็ให้ผลลัพธ์ที่ดีทีเดียว ยกตัวอย่างเช่น ในรูปภาพด้านล่างนี้ เป้าหมายก็คือการแยกข้อความออกเป็นคำๆ และหาตัวแทนที่เป็นตัวเลขของแต่ละคำ:

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization.svg" alt="An example of word-based tokenization."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization-dark.svg" alt="An example of word-based tokenization."/>
</div>

วิธีการในการแยกข้อความนั้นมีหลายวิธีแตกต่างกันไป ยกตัวอย่างเช่น เราสามารถใช้ ช่องว่าง(whitespace) ในการแยกข้อความขอเป็นคำๆ โดยใช้ฟังก์ชัน `split()` ของ Python: 

```py
tokenized_text = "Jim Henson was a puppeteer".split()
print(tokenized_text)
```

```python out
['Jim', 'Henson', 'was', 'a', 'puppeteer']
```

แล้วก็มี tokenizers สำหรับแยกคำอีกหลายแบบที่มีกฏเพิ่มเติมสำหรับ เครื่องหมายวรรคตอน(punctuation) ถ้าเราใช้ tokenizer ประเภทนี้ เราจะได้กลุ่มของคำศัพท์("vocabularies") ที่ค่อนข้างใหญ่มาก ซึ่งคำศัพท์จะถูกนิยามโดยจำนวน tokens อิสระทั้งหมดที่เรามีในคลังข้อมูล(corpus) ของเรา

แต่ละคำจะได้ ID  โดยเริ่มจาก 0 และเพิ่มขึ้นเท่ากับขนาดของคำศัพท์ โดยโมเดลจะใช้ IDs เหล่านี้ในการระบุตัวตนของแต่ละคำ

ถ้าเราต้องการที่จะให้ครอบคลุมคำทั้งหมดในหนึ่งภาษาด้วย word-based tokenizer เราจำเป็นต้องมีตัวระบุตัวตนสำหรับแต่ละคำในภาษาๆนั้นๆ ซึ่งจะทำให้มีการสร้าง tokens จำนวนมหาศาล ยกตัวอย่างเช่น ในภาษาอังกฤษมีคำทั้งหมด 500,000 คำ ดังนั้นการที่จะสร้างความเชื่อมโยงระหว่างแต่ละคำกับ ID เราจำเป็นที่จะต้องจำ ID ทั้งหลายเหล่านั้น นอกจากนี้แล้ว คำอย่างเช่น "dog" จะมีค่าที่ต่างจากคำเช่น "dogs" และโมเดลจะไม่มีทางรู้เลยว่าค่าว่า "dog" และ "dogs" นั้นคล้ายคลึงกัน: มันจะจำแนกสองคำนี้เป็นคำที่ไม่มีความเกี่ยวข้องกัน และคำอื่นๆก็จะเป็นเช่นเดียวกัน เช่นคำว่า "run" และ "running" ซึ่งโมเดลก็จะไม่มองว่าเป็นคำคล้ายๆกัน

สุดท้ายแล้ว เราจำเป็นต้องมี Token เฉพาะสำหรับแทนค่าคำต่างๆที่ไม่อยู่กลุ่มคำศัพท์ของเรา ซึ่งสิ่งนี้เรียกว่า "unknown" token ที่โดยทั่วไปแล้วจะมีค่าเป็น  "[UNK]" หรือ "&lt;unk&gt;" และมันจะเป็นสัญญาณที่ไม่ดีเท่าไหร่ถ้าคุณเห็น tokenizer ผลิต tokens เหล่านี้ออกมาเยอะๆ เนื่องจากมันไม่สามารถที่จะหาค่าที่สมเหตุสมผลมาแทนคำๆนั้นได้และคุณก็จะสูญเสียข้อมูลไปตามรายทางเรื่อยๆ เป้าหมายเมื่อเราสร้างกลุ่มคำศัพท์ คือ ทำยังไงก็ได้ให้ Tokenizers สร้าง unknown token ให้น้อยที่สุด

วิธีการหนึ่งที่จะลดจำนวนของ unknown tokens ได้ก็คือ การลงลึกไปอีกหนึ่งขั้น โดยใช้ _character-based_ tokenizer

## เน้นที่ตัวอักษร(Character-based)

<Youtube id="ssLq_EK2jLE"/>

Character-based tokenizers จะแบ่งข้อความออกเป็นตัวอักษร แทนการแบ่งเป็นคำ โดยมีประโยชน์หลักๆ สอง อย่าง:

- กลุ่มของคำศัพท์จะเล็กกว่ามาก
- มี tokens ที่อยู่นอกกลุ่มคำศัพท์(unknown) น้อยกว่ามาก เนื่องจากคำทุกคำสามารถสร้างได้จากกลุ่มของตัวอักษร

แต่ก็มีคำถามเกี่ยวกับ ช่องว่าง(spaces) และ เครื่องหมายวรรคตอน(punctuation):

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization.svg" alt="An example of character-based tokenization."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization-dark.svg" alt="An example of character-based tokenization."/>
</div>

วิธีการนี้ก็ไม่ใช่วิธีการที่ดีทีสุดเช่นกัน เนื่องจากค่าที่ได้จะขึ้นอยู่กับตัวอักษรแทนที่จะเป็นคำ บางคนอาจจะบอกว่า มันไม่บ่งบอกความหมาย: แต่ละตัวอักษรไม่ได้มีความหมายอะไรมากมายในตัวมันเอง แต่กลับกันคำกลับบ่งบอกความหมายมากกว่า แต่อย่างไรก็ตามมันก็ขึ้นอยู่กับแต่ละภาษา; เช่น แต่ละตัวอักษรในภาษาจีนนั้นมีความหมายมากกว่าตัวอักษรในภาษาละติน 

อีกหนึ่งสิ่งที่ควรพิจารณาก็คือเราจะได้ tokens จำนวนมหาศาลที่โมเดลของเราจะต้องประมวลผล: แต่ในทางตรงกันข้าม คำหนึ่งคำจะมีแค่หนึ่ง token ถ้าใช้ word-based tokenizer แต่มันจะกลายเป็น 10 หรือมากกว่านั้นเมื่อเราแปลงเป็นตัวอักษร

เพื่อให้ได้สิ่งที่เป็นประโยชน์ที่สุดจากทั้งสองแบบ เราสามารถสร้างเทคนิคที่สามที่รวมเอาสองวิธีเข้าด้วยกัน: *subword tokenization*

## คำย่อย(Subword) tokenization

<Youtube id="zHvTiHr506c"/>

อัลกอลิธึม Subword tokenization อยู่บนแนวคิดที่ว่า คำที่ใช้บ่อย ไม่ควรที่จะถูกแบ่งออกเป็นคำย่อยเล็กๆ แต่คำที่ไม่ค่อยได้ใช้ควรที่จะถูกแบ่งออกเป็นคำย่อยๆ ที่มีความหมาย

ยกตัวอย่างเช่น "annoyingly" อาจจะเป็นคำที่ไม่ค่อยถูกใช้บ่อยนัก ดังนั้นมันสามารถที่จะถูกแบ่งย่อยเป็น "annoying" และ "ly"  สองคำนี้มีความเป็นไปได้ที่เจอได้บ่อยเมื่อมันเป็นคำย่อยเดี่ยวๆ ในขณะเดียวกัน ความหมายของ "annoyingly" ก็ยังคงอยู่ โดยเป็นความหมายร่วมของ "annoying" และ "ly"

ตัวอย่างนี้แสดงวิธีการที่อัลกอลิธึม subword tokenization ทำการแปลงประโยค "Let's do tokenization!" ว่าทำอย่างไร:

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword.svg" alt="A subword tokenization algorithm."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword-dark.svg" alt="A subword tokenization algorithm."/>
</div>

ท้ายที่สุดแล้ว คำย่อยเหล่านี้จะให้ความหมายเชิงความหมาย(semantic meaning) มากกว่า, ในตัวอย่างข้างต้น "tokenization" ถูกแบ่งออกเป็น "token" และ "ization", เป็น สอง tokens ที่มีความหมายเชิงความหมายพร้อมทั้งทำให้ประหยัดเนื้อที่ (มีแค่ 2 tokens เท่านั้นที่จะใช้แทนค่าคำยาวๆ) นี่จะทำให้เราสามารถครอบคลุมคำต่างๆได้ด้วยกลุ่มของคำศัพท์เล็กๆเท่านั้น และแทบไม่มี unknown tokens

วิธีการนี้เป็นประโยชน์อย่างยิ่งในภาษารูปคำติดต่อ(agglutinative languages) อย่างเช่น ภาษาตุรกี(Turkish) ที่คุณสามารถสร้างคำที่ยาวและซับซ้อนได้ด้วยการต่อคำย่อยๆหลายคำๆเข้าด้วยกัน

### และอื่นๆ !

ไม่น่าแปลกใจเลยที่จะมีอีกหลากหลายเทคนิค  ถ้าจะเอ่ยบางชื่อ:

- Byte-level BPE, เหมือนที่ใช้ใน GPT-2
- WordPiece, เหมือนที่ใช้ใน BERT
- SentencePiece or Unigram, เหมือนที่ใช้ในหลายๆโมเดลที่เป็นโมเดลสำหรับหลายๆ ภาษา(multilingual models)

ถึงตรงนี้คุณน่าจะมีความรู้เพียงพอว่า tokenizers ทำงานอย่างไร เพื่อเริ่มใช้งาน API

## การโหลดและการบันทึก

การโหลดและการบันทึก tokenizers นั้นง่ายพอๆกับการโหลดและการบันทึกโมเดล โดยทั่วไปแล้วมันจะใช้สองวิธี: `from_pretrained()` และ `save_pretrained()` สองวิธีการนี้จะโหลด หรือ บันทึกอัลกอลิธึมที่ใช้โดย tokenizer (คล้ายๆกับ *สถาปัตยกรรม* ของโมเดล) และ กลุ่มคำศัพท์ของมัน (คล้ายๆ กับ *weights* ของโมเดล)

โหลด BERT tokenizer ที่ผ่านการเทรนมาแล้วด้วย checkpoint เดียวกันกับ BERT สามารถทำได้เหมือนกับการโหลดโมเดล ยกเว้น เราใช้คลาส `BertTokenizer`:

```py
from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
```

{#if fw === 'pt'}
เหมือนกับ `AutoModel, `AutoTokenizer` จะทำการดึงเอาคลาส tokenizer ที่เหมาะสมที่อยู่ใน library โดยอ้างอิงกับชื่อของ checkpoint และสามารถใช้กับ checkpoint ใดก็ได้:

{:else}
เหมือนกับ `TFAutoModel, `AutoTokenizer` จะทำการดึงเอาคลาส tokenizer ที่เหมาะสมที่อยู่ใน library โดยอ้างอิงกับชื่อของ checkpoint และสามารถใช้กับ checkpoint ใดก็ได้:

{/if}

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

ตอนนี้เราสามารถใช้ tokenizer เหมือนที่แสดงใน section ที่แล้ว:

```python
tokenizer("Using a Transformer network is simple")
```

```python out
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],
 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
```

การบันทึก tokenizer ก็เหมือนกับการบันทึกโมเดล:

```py
tokenizer.save_pretrained("directory_on_my_computer")
```

เราจะพูดเพิ่มเติมเกี่ยวกับ `token_type_ids` ใน [Chapter 3](/course/chapter3), และจะอธิบายเกี่ยวกับ `attention_mask` ในภายหลัง ในขั้นแรกนี้เรามาดูกันว่า `input_ids` นั้นถูกสร้างขึ้นมาอย่างไร ซึ่งการที่จะทำเช่นนี้ เราจำเป็นที่จะต้องดูวิธีระหว่างกลางของ tokenizer 

## การเข้ารหัส(Encoding)

<Youtube id="Yffk5aydLzg"/>

การแปลงข้อความไปเป็นตัวเลขนั้นเรียกอีกอย่างว่า _encoding_ การเข้ารหัส(Encoding) นั้นสามารถทำได้ด้วยกระบวนการที่ประกอบด้วย 2 ขั้นตอน: tokenization และตามด้วยการแปลงไปเป็น input IDs

อย่างที่เราเห็นมาก่อนหน้านี้ ขั้นตอนแรกก็คือการแบ่งข้อความออกเป็นคำ (หรือส่วนของคำ, เครื่องหมายวรรคตอน เป็นต้น) เหล่านี้ปกติจะถูกเรียกว่า *tokens* มีหลายกฏเกณฑ์ที่ใช้ในการควบคุมกระบวนนี้ ซึ่งนั้นก็เป็นเหตุผลว่าทำไมเราถึงจำเป็นต้องสร้าง tokenizer โดยใช้ชื่อของโมเดล ก็เพื่อให้มั่นใจว่าเราใช้กฏเกณฑ์เดียวกันกับที่เราใช้ตอนที่โมเดลนั้นผ่านการเทรนมาก่อนหน้านี้

ขั้นตอนที่สองก็คือการแปลง tokens เหล่านั้นไปเป็นตัวเลข ซึ่งเราจึงจะสามารถสร้าง tensor จากมันได้และใส่เข้าไปยังโมเดลได้ ในการทำเช่นนี้ tokenizer จะมี *vocabulary* ซึ่งเป็นส่วนที่เราดาวน์โหลดมาแล้วตอนที่เราสร้าง tokenizer ด้วยวิธีการ `from_pretrained()` เช่นเดียวกัน เราจำเป็นต้องใช้คำศัพท์เหมือนกับที่ใช้ตอนโมเดลนั้นเทรนมา

เพื่อให้เข้าใจสองขั้นตอนนี้มากยิ่งขึ้น เราจะมาดูแต่ละขั้นตอนแยกกัน เราจะใช้วิธีการบางวิธีที่ทำบางส่วนของ tokenization pipeline แยกกันเพื่อที่จะแสดงให้คุณดูว่าผลลัพธ์ระหว่างกลาง(intermediate) ของทั้งสองขั้นตอนนั้นเป็นอย่างไร แต่ในทางปฏิบัติ คุณควรจะประมวลผลข้อมูลอินพุตของคุณโดยเรียกใช้ tokenizer ตรงๆ(เหมือนที่แสดงใน section 2)

### Tokenization

กระบวนการ tokenization นั้นทำได้โดยใช้ `tokenize()` ของ tokenizer:

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

sequence = "Using a Transformer network is simple"
tokens = tokenizer.tokenize(sequence)

print(tokens)
```

ผลลัพธ์ของวิธีนี้ คือ ลิสท์ของกลุ่มของตัวอักษร(strings) หรือ tokens:

```python out
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
```

tokenizer นี้คือ subword tokenizer: มันจะแบ่งคำไปเรื่อยๆจนกว่าจะได้ tokens ที่สามารถแทนค่าได้ด้วยคำศัพท์(vocabulary) ของมันเอง ซึ่งนั้นก็เหมือนกับกรณีที่คำว่า `transformer`ถูกแบ่งออกเป็น 2 tokens:  `transform` และ `##er`

### จาก tokens ไปเป็น input IDs

การแปลงไปเป็น input IDs นั้นจะถูกจัดการโดย `convert_tokens_to_ids()` ที่เป็นเมธอดของ tokenizer:

```py
ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)
```

```python out
[7993, 170, 11303, 1200, 2443, 1110, 3014]
```

ผลลัพธ์เหล่านี้ เมื่อทำการแปลงไปเป็น tensor ที่เหมาะสมของ framework นั้นๆ แล้ว มันสามารถถูกนำไปใช้เป็นอินพุตของโมเดลเหมือนที่เราเห็นก่อนหน้าในนี้ในบทนี้

<Tip>

✏️ **ลองดูสิ!** ทำซ้ำสองขั้นตอนสุดท้าย(tokenization และแปลงไปเป็น input IDs) กับข้อความที่เราใช้เป็นอินพุตใน section 2 ("I've been waiting for a HuggingFace course my whole life." และ "I hate this so much!") และลองดูว่าคุณได้ input IDs เดียวกันกับที่เราได้ก่อนหน้านี้ไหม!

</Tip>

## การถอดรหัส(Decoding)

*การถอดรหัส(Decoding)* ก็จะเป็นกระบวนการในทางตรงกันข้าม จากดัชนีคำศัพท์(vocabulary indices) เราต้องการที่จะได้กลุ่มของตัวอักษร(string) ซึ่งสามารถทำได้ด้วยวิธี `decode()` ดังนี้:

```py
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])
print(decoded_string)
```

```python out
'Using a Transformer network is simple'
```

วิธี `decode` ไม่ได้ทำแค่การแปลงดัชนี(indices) ไปเป็น token เท่านั้น แต่ยังทำการรวม tokens ต่างๆที่เป็นส่วนหนึ่งของคำเดียวกันเพื่อสร้างประโยคที่สามารถอ่านได้ กระบวนการเช่นนี้จะเป็นประโยชน์อย่างมากเมื่อเราใช้โมเดลสำหรับทำนายข้อความใหม่(ไม่ว่าจะเป็นข้อความที่สร้างจาก prompt หรือปัญหาประเภท sequence-to-sequence เช่น การแปล(translation) หรือ การสรุปใจความสำคัญ(summarization))

ถึงตรงนี้คุณน่าจะเข้าใจกระบวนการเล็กๆ น้อยๆ ต่างๆ ที่ tokenizer สามารถทำได้: tokenization, การแปลงไปเป็น IDs, และการแปลง IDs กลับมาเป็นคำ แต่อย่างไรก็ตาม เราก็แค่เพิ่งจะขุดแค่ปลายของภูเขาน้ำแข็ง ใน section ต่อไป เราจะใช้วิธีการของเราไปจนถึงลิมิตของมันและดูว่าเราจะแก้ปัญหาอย่างไร

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/4.mdx" />

### การใช้งานเบื้องต้นสำเร็จแล้ว!
https://huggingface.co/learn/course/th/chapter2/7.md

# การใช้งานเบื้องต้นสำเร็จแล้ว!

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

คุณเยี่ยมมากที่เรียนมาได้ถึงตรงนี่ เรามาทบทวนกันว่าในบทนี้คุณ:

- เรียนรู้ blocks พื้นฐานของโมเดล Transformer
- เรียนรู้ว่ามีอะไรบ้างที่ประกอบกันเป็น tokenization pipeline.
- เห็นการใช้งานจริงของโมเดล Transformer.
- เรียนรู้การใช้งาน tokenizer ในการแปลงข้อความไปเป็น tensor ที่โมเดลสามารถเข้าใจได้
- ประกอบ tokenizer และ โมเดล เข้าด้วยกัน เพื่อให้ได้มาซึ่งคำทำนายของข้อความ
- เรียนรู้ข้อจำกับของ input IDs และเรียนรู้เกี่ยวกับ attention masks
- ได้ลองเล่นเมธอดต่างๆ ที่สามารถปรับแต่งค่าได้ของ Tokenizer 

ต่อจากนี้ไป คุณควรจะสามารถใช้งานคู่มือ 🤗 Transformers ได้อย่างอิสระ: คำศัพท์ต่างๆ จะฟังดูคุ้นหู และคุณก็ได้เห็นเมธอดต่างๆ ที่คุณจะใช้บ่อยๆ


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/7.mdx" />

### เบื้องหลังของ pipeline
https://huggingface.co/learn/course/th/chapter2/2.md

# เบื้องหลังของ pipeline

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section2_tf.ipynb"},
]} />

{/if}

<Tip>
Section นี้จะเป็น Section แรกที่เนื้อหาจะค่อนข้างแตกต่างกันขึ้นอยู่กับว่าคุณใช้ PyTorch หรือ TensorFlow คุณสามารถเลือก plateform ที่คุณต้องการได้จากปุ่มที่อยู่ด้านบนของชื่อหัวข้อ!
</Tip>

{#if fw === 'pt'}
<Youtube id="1pedAIvTWXk"/>
{:else}
<Youtube id="wVN12smEvqg"/>
{/if}

เรามาเริ่มกันด้วยตัวอย่างนี้ โดยเรามาดูกันว่าเกิดอะไรขึ้นในเบื้องหลังเมื่อเราทำการสั่งการ(executed) โค้ดด้านล่างนี้จาก [Chapter 1](/course/chapter1):

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier(
    [
        "I've been waiting for a HuggingFace course my whole life.",
        "I hate this so much!",
    ]
)
```

และ ผลลัพธ์ที่ได้:

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

อย่างที่เราเห็นใน [Chapter 1](/course/chapter1), pipeline นี้เป็นการรวมเอา 3 ขั้นตอน : แ(preprocessing), ส่งข้อมูลเข้าไปยังโมเดล, และการประมวลผลข้อมูลที่ออกมาจากโมเดล (postprocessing)

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline-dark.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
</div>

เรามาดูแต่ละขั้นตอนเหล่านี้กันอย่างละเอียดเลยดีกว่า

## การประมวลผลข้อมูลขั้นต้น(Preprocessing) ด้วย tokenizer

เหมือนกับโครงข่ายประสาท(neural networks) อื่นๆ, โมเดล Transformer ไม่สามารถที่จะประมวลผลข้อมูลที่เป็นข้อความ(text) ได้ตรงๆ ดังนั้นขั้นแรกของ pipeline ก็คือการแปลงข้อความให้เป็นตัวเลข(numbers) ที่โมเดลนั้นสามารถประมวลผลได้ ในกระบวนการนี้เราจะใช้ *tokenizer* ซึ่งจะรับผิดชอบในการทำ :

- แบ่งข้อมูลออกเป็น คำ(words), หน่วยย่อยของคำ (subwords), หรือ สัญลักษณ์ (เช่น เครื่องหมายวรรคตอน (punctuation)) เหล่านี้เราเรียกว่า *token*
- ทำการเชื่อมโยง (Mapping) แต่ละ token ไปเป็นตัวเลข (integer)
- เพิ่มเติมข้อมูลที่อาจจะเป็นประโยชน์กับโมเดล

กระบวนการประมวลผลข้อมูลขั้นต้น(preprocessing) ทั้งหมดนี้จำเป็นที่จะต้องเป็นไปในแนวทางที่เหมือนกับตอนที่โมเดลได้ผ่านการเทรนมาก่อนหน้านี้(pretrained), ดังนั้นสิ่งแรกที่เราจำเป็นต้องทำคือดาวน์โหลดข้อมูลจาก [Model Hub](https://huggingface.co/models) ในการดาวน์โหลดนี้ เราสามารถทำได้โดยใช้คลาส `AutoTokenizer` และเรียกเมธอด `from_pretrained()` โดยหากระบุชื่อ checkpoint ของโมเดล เมธอด `from_pretrained()` จะทำการดึงข้อมูลทั้งหมดที่เกี่ยวกับ tokenizer ของโมเดลมาเก็บไว้(จะดาวน์โหลดเพียงครั้งเดียวตอนที่เรารันโค้ดด้านล่างนี้ครั้งแรกเท่านั้น)

เนื่องจาก checkpoint เริ่มต้น(default) ของ `sentiment-analysis` pipeline คือ  `distilbert-base-uncased-finetuned-sst-2-english` (สามารถดู model card [ที่นี่](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), ดังนั้นเราจะรันโค้ดนี้:

```python
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

เมื่อเรามี tokenizer แล้ว เราสามารถใส่ประโยคข้อความของเราเข้าไปและเราจะได้สารานุกรม(dictionary) ที่พร้อมนำไปใส่ในโมเดลออกมา! สิ่งเดียวที่เหลือที่ต้องทำ คือ การแปลงข้อมูลอัตลักษณ์(IDs) ของข้อมูลที่ใส่เข้าไป(input) ไปเป็น tensors

คุณสามารถใช้ 🤗 Transformers โดยที่ไม่จำเป็นต้องกังวลเลยว่า ML framework ตัวไหนที่ใช้เป็น backend; มันอาจจะเป็น PyTorch หรือ TensorFlow, หรือ Flax สำหรับบางโมเดล แต่อย่างไรก็ตามโมเดล Transformer จะรับเพียง *tensor* เป็นข้อมูลที่ใส่เข้าไป(input) เท่านั้น ถ้านี่เป็นครั้งแรกที่คุณได้ยินเกี่ยวกับคำว่า tensor คุณสามารถเปรียบเทียบมันเป็นเหมือน NumPy arrays แทนก็ได้ โดยที่ NumPy array สามารถเป็นได้ทั้ง scalar (0D), vector (1D), matrix (2D), หรือมีหลายๆมิติ. เหล่านี้ก็คือ tensor ดีๆนี่เอง  tensor ของ ML frameworks อื่นๆ ก็มีลักษณะคล้ายๆกัน และสามารถสร้างขึ้นได้ง่ายเหมือน NumPy arrays 

การกำหนดประเภทของ tensor ที่เราต้องการได้กลับมา (PyTorch, TensorFlow, หรือ NumPy) เราสามารถใช้ตัวแปร(argument) `return_tensors`:

{#if fw === 'pt'}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
print(inputs)
```
{:else}
```python
raw_inputs = [
    "I've been waiting for a HuggingFace course my whole life.",
    "I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf")
print(inputs)
```
{/if}

ไม่ต้องกังวลเกี่ยวการเพิ่ม(padding) และการตัดออก(truncation) ในตอนนี้ เราจะอธิบายทีหลัง สิ่งหลักๆ ที่ควรจำคือ คุณสามารถที่จะส่งผ่านประโยคหนึ่งประโยค หรือ รายการ(list)ของประโยค พร้อมทั้งระบุประเภทของ tensor ที่คุณต้องการได้กลับมา(ถ้าไม่ระบุประเภท คุณจะได้ผลกลับมาเป็น list of lists)

{#if fw === 'pt'}

ผลลัพธ์ที่เป็น tensor ของ PyTorch ก็จะหน้าตาประมาณนี้

```python out
{
    'input_ids': tensor([
        [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172, 2607,  2026,  2878,  2166,  1012,   102],
        [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
    ]), 
    'attention_mask': tensor([
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ])
}
```
{:else}

ผลลัพธ์ที่เป็น tensor ของ PyTorch ก็จะหน้าตาประมาณนี้

```python out
{
    'input_ids': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607,  2026,  2878,  2166,  1012,   102],
            [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
        ], dtype=int32)>, 
    'attention_mask': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ], dtype=int32)>
}
```
{/if}

ผลลัพธ์จะเป็นสารานุกรม(dictionary) ที่มี 2 คีย์(keys), `input_ids` และ `attention_mask` โดยที่ `input_ids` จะมีข้อมูลเป็นตัวเลข(integers)จำนวนสองแถว (หนึ่งแถวต่อหนึ่งประโยค) ซึ่งเป็นอัตลักษณ์ที่เฉพาะเจาะจงของ token ในแต่ละประโยค ส่วน `attention_mask` เราจะอธิบายในบทนี้อีกครั้งหลังจากนี้

## มาอธิบายเกี่ยวกับโมเดลกัน

{#if fw === 'pt'}
เราสามารถดาวน์โหลดโมเดลที่ผ่านการเทรนมาแล้ว(pretrained) ของเราได้เหมือนกับที่เราทำกับ tokenizer ของเรา 🤗 Transformers มีคลาส `AutoModel` ซึ่งก็มีเมธอด `from_pretrained()` เช่นกัน:

```python
from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
```
{:else}
เราสามารถดาวน์โหลดโมเดลที่ผ่านการเทรนมาแล้ว(pretrained) ของเราได้เหมือนกับที่เราทำกับ tokenizer ของเรา 🤗 Transformers มีคลาส `TFAutoModel` ซึ่งก็มีเมธอด `from_pretrained()` เช่นกัน:

```python
from transformers import TFAutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModel.from_pretrained(checkpoint)
```
{/if}

ในตัวอย่างโค้ดนี้ เราดาวน์โหลด checkpoint เดียวกันกับที่เราใช้ใน pipeline ของเราก่อนหน้านี้ (มันน่ามีการเก็บ(cached)ไว้แล้ว) และสร้างโมเดลขึ้นมาพร้อมกัน

ในสถาปัตยกรรมนี้จะมีเฉพาะโมดูล Transformer พื้นฐาน: ให้ข้อมูลเข้าไป และมันให้สิ่งที่เราเรียกว่า *hidden states* ออกมา หรือที่เรารู้จักกันในนาม *features* สำหรับข้อมูลอินพุตของแต่ละโมเดล เราจะทำการหาเวกเตอร์หลายมิติ(high-dimensional vector) ที่บ่งบอก **ความเข้าใจสภาวะแวดล้อมของข้อมูลนั้นโดนโมเดล Transformer**

ถ้านี้ฟังดูไม่สมเหตุสมผล ไม่ต้องกังวล เดี๋ยวเราจะอธิบายทัั้งหมดอีกครั้ง

ในขณะที่ hidden states เหล่านี้เป็นประโยชน์ในตัวมันอยู่แล้ว มันเลยถูกนำไปใช้กับส่วนอื่นของโมเดลด้วย ที่เรารู้จักกันในนาม *head* ใน [Chapter 1](/course/chapter1), งานที่แตกต่างกันอาจจะสามารถใช้สถาปัตยกรรมเหมือนกันได้ แต่งานแต่ละอย่างจะมีส่วนหัว(head)ที่แตกต่างกันไป

### เวคเตอร์หลายมิติ (A high-dimensional vector) ?

เวอเตอร์ที่ได้จากโมดูลของ Transformer นั้นปกติจะมีขนาดใหญ่ โดยทั่วไปแล้วมันจะมี 3 มิติ:

- **ขนาดของชุด(ฺBatch size)**: จำนวนของประโยคที่ผ่านการประมวลผล (2 ประโยคในตัวอย่างของเรา)
- **ความยาวของประโยค(Sequence length)**: ความยาวของตัวเลขที่เป็นตัวแทนของประโยค (16 ตัวเลขในตัวอย่างของเรา)
- **ขนาดของ Hidden states (Hidden size)**: มิติของเวคเตอร์ของแต่ละข้อมูลที่ให้เข้าไปยังโมเดล

มันจะถูกบอกว่ามันเป็นเวคเตอร์ "หลายมิติ(high dimensional)" ก็เพราะค่าสุดท้าย ขนาดของ hidden states นั้นสามารถมีขนาดที่ใหญ่มาก(786 เป็นค่าที่ใช้กันทั่วไปสำหรับโมเดลขนาดเล็ก, ส่วนในโมเดลขนาดใหญ่นั้นสามารถขึ้นไปได้ถึง 3072 หรือมากกว่านั้น)

เราจะเห็นได้ว่าถ้าเราใส่ข้อมูลที่เราประมวลผลมาแล้วเข้าไปยังโมเดลของเรา:

{#if fw === 'pt'}
```python
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
```

```python out
torch.Size([2, 16, 768])
```
{:else}
```py
outputs = model(inputs)
print(outputs.last_hidden_state.shape)
```

```python out
(2, 16, 768)
```
{/if}

จะสังเกตว่าข้อมูลที่ออกจากโมเดล 🤗 Transformers นั้นจะมีลักษณะเหมือนกับ `namedtuple`s หรือ dictionaries คุณสามารถเข้าถึงองค์ประกอบต่าง(elements) ได้ด้วย attributes (เหมือนที่เราทำ) หรือด้วย key (`outputs["last_hidden_state"]`) หรือแม้กระทั่งด้วย index ถ้าคุณรู้ว่าสิ่งที่คุณมองหานั้นอยู่ตรงไหน (`outputs[0]`)

### Model heads: ทำความเข้าใจจากตัวเลข

โมเดล heads รับเวคเตอร์หลายมิติ(high-dimensional) ของ hidden states เข้าไปและจะทำการโปรเจคเวคเตอร์ดังกล่าวไปยังมิติอื่น ซึ่งโดยปกติโมเดล heads จะประกอบด้วยเลเยอร์เชิงเส้น(linear layers) อย่างน้อยหนึ่งเลเยอร์:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head.svg" alt="A Transformer network alongside its head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head-dark.svg" alt="A Transformer network alongside its head."/>
</div>

เอาท์พุตของโมเดล Transformer จะส่งตรงไปที่หัวโมเดล(model head)เพื่อทำการประมวลผล

ในไดอะแกรมนี้ โมเดลจะถูกแทนที่ด้วยเลเยอร์ฝังตัว(embeddings layer) และเลเยอร์ย่อย(subsequent layers) ของตัวมันเอง โดยเลเยอร์ฝังตัว(embeddings layer) จะทำการแปลงตัวบ่งชี้ตัวตนของอินพุต(Input ID) ที่อยู่ในอินพุตที่เป็น tokenized ไปเป็นเวคเตอร์ที่่เป็นตัวแทนของ token ที่เกี่ยวข้อง ส่วนเลเยอร์ย่อยอื่นๆจะจัดการเวคเตอร์อื่นโดยใช้กระบวนการ attention เพื่อให้ได้มาซึ่งตัวแทน(representation) สุดท้ายของประโยค

มีหลายสถาปัตยกรรมใน 🤗 Transformers โดยที่หนึ่งสถาปัตยกรรมถูกออกแบบมาให้ใช้กับงานเฉพาะหนึ่งงาน นี่เป็นเพียงส่วนหนึ่งจากหลายๆโมเดล :

- `*Model` (retrieve the hidden states)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- and others 🤗

{#if fw === 'pt'}
สำหรับในตัวอย่างของเรานั้น เราต้องการโมเดลที่มี head สำหรับการจำแนกประโยค(sequence classification) (โดยสามารถที่จะจำแนกประโยคว่าเป็นประโยคเชิงบวก หรือ ลบ) ดังนั้น เราจะไม่ใช้คลาส `AutoModel` แต่จะใช้  `AutoModelForSequenceClassification`:

```python
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
```
{:else}
สำหรับในตัวอย่างของเรานั้น เราต้องการโมเดลที่มี head สำหรับการจำแนกประโยค(sequence classification) (โดยสามารถที่จะจำแนกประโยคว่าเป็นประโยคเชิงบวก หรือ ลบ) ดังนั้น เราจะไม่ใช้คลาส `TFAutoModel` แต่จะใช้ `TFAutoModelForSequenceClassification`:

```python
from transformers import TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(inputs)
```
{/if}

ถ้าเราดูที่ขนาด(shape) ของอินพุต มิติ(dimensionality)จะน้อยกว่ามาก : model head ที่รับเอาเวคเตอร์ขนาดหลายมิติเป็นอินพุตเหมือนที่เราเห็นก่อนหน้านี้ และให้เอาท์พุตเป็นเวคเตอร์ที่มีสองค่า (หนึ่งค่าต่อหนึ่งสัญลักษณ์(label)) :

```python
print(outputs.logits.shape)
```

{#if fw === 'pt'}
```python out
torch.Size([2, 2])
```
{:else}
```python out
(2, 2)
```
{/if}

เนื่องจากเรามีแค่สองประโยคและสองสัญลักษณ์(labels) ผลลัพธ์ที่ได้จากโมเดลของเราจึงมีขนาด 2x2

## การประมวลหลังจากได้ผลลัพธ์มาแล้ว (Postprocessing)

ค่าที่เราได้มาจากโมเดลนั้นไม่จำเป็นต้องดูเป็นเหตุเป็นผลในตัวมันเอง เดี๋ยวเราลองมาดูกัน:

```python
print(outputs.logits)
```

{#if fw === 'pt'}
```python out
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
```
{:else}
```python out
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-1.5606991,  1.6122842],
           [ 4.169231 , -3.3464472]], dtype=float32)>
```
{/if}

โมเดลของเราทำนาย `[-1.5607, 1.6123]` สำหรับประโยคแรก และ `[ 4.1692, -3.3464]` สำหรับประโยคที่สอง ค่าเหล่านี้ไม่ใช่ค่าความน่าจะเป็น(probabilities) แต่เป็นค่า *logits* เป็นคะแนนดิบที่ยังไม่ผ่านการ normalized ที่ส่งออกมาจากเลเยอร์สุดท้ายของโมเดล การแปลงค่าคะแนนไปเป็นค่าความน่าจะเป็น(probabilities) คะแนนเหล่านี้จำเป็นที่จะต้องผ่านเลเยอร์ที่ชื่อว่า [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (โมเดลของ 🤗 Transformers ทั้งหมด จะส่งข้อมูลออกมาเป็น logits โดยที่ loss function ของการเทรนโมเดลจะทำการรวม activation function ของเลเยอร์สุดท้าย เช่น SoftMax เข้ากับ loss function หลัก เช่น cross entropy):

{#if fw === 'pt'}
```py
import torch

predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
```
{:else}
```py
import tensorflow as tf

predictions = tf.math.softmax(outputs.logits, axis=-1)
print(predictions)
```
{/if}

{#if fw === 'pt'}
```python out
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
```
{:else}
```python out
tf.Tensor(
[[4.01951671e-02 9.59804833e-01]
 [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32)
```
{/if}

ตอนนี้เราจะเห็นว่าโมเดลทำนาย `[0.0402, 0.9598]` สำหรับประโยคแรก และ `[0.9995,  0.0005]` สำหรับประโยคที่สอง ซึ่งคะแนนเหล่านี้คือคะแนนความน่าจะเป็น(probabilities score) ที่สามารถนำไปจำแนกได้

เพื่อที่จะให้ได้สัญลักษณ์(label) ของแต่ละตำแหน่ง เราสามารถดูได้จากคุณสมบัติ `id2label` ของโมเดล model config (เดี๋ยวเราจะอธิบายเพิ่มเติมใน section ถัดไป):

```python
model.config.id2label
```

```python out
{0: 'NEGATIVE', 1: 'POSITIVE'}
```

เราสามารถที่จะสรุปได้ว่าโมเดลทำการทำนายดังต่อไปนี้

- ประโยคแรก: NEGATIVE: 0.0402, POSITIVE: 0.9598
- ประโยคที่สอง: NEGATIVE: 0.9995, POSITIVE: 0.0005

ถึงตรงนี้ประสบความสำเร็จในการลองทำ สาม ขั้นตอนของ pipeline: การประมวลผลเบื้องต้น(preprocessing)โดยใช้ tokenizers, ส่งข้อมูลเข้าไปยังโมเดล,และการประมวลผลข้อมูลที่ได้จากโมเดล! ต่อจากนี้เราจะไปลงลึกในรายละเอียดของแต่ละขั้นตอน

<Tip>

✏️ **ลองเลย!** เลือกสอง(หรือมากกว่านั้น) ข้อความของคุณเองและลองใส่มันเข้าไปใน `sentiment-analysis` pipeline. แล้วทำขั้นตอนต่างๆ ที่คุณเรียนผ่านมาใน section นี้และตรวจสอบดูว่าคุณได้ผลเหมือนเดิมหรือไม่!

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/2.mdx" />

### บทนำ
https://huggingface.co/learn/course/th/chapter0/1.md

# บทนำ

ยินดีต้อนรับเข้าสู่คอร์ส Hugging Face! ในส่วนเริ่มต้นนี้จะพาคุณติดตั้งโปรแกรมหรือสิ่งแวดล้อมในการทำงาน(หรือเรียกว่า working environment) หากคุณเพิ่งเริ่มคอร์สนี้ เราแนะนำให้คุณไปดู [บทที่ 1](/course/chapter1) ก่อน จากนั้นค่อยกลับมาติดตั้ง environtment เพื่อทดลองโค้ดด้วยตัวเอง

คลังชุดคำสั่ง (หรือเรียกว่า library) ต่าง ๆ ที่ใช้ในคอร์สนี้สามารถใช้เป็นชุดคำสั่งสำเร็จในภาษา Python (หรือเรียกว่า Python package) ได้ทั้งหมด ดังนั้น ในส่วนนี้เราจะแสดงวิธีการติดตั้ง Python environment และลง library ที่จำเป็นทั้งหมด

ในส่วนนี้เราจะแสดงวิธีการติดตั้ง working environment อยู่ทั้งหมดสองวิธี ได้แก่ การใช้ Colab notebook และ การใช้สิ่งแวดล้อมจำลองใน Python (หรือเรียกว่า Python virtual environment) คุณสามารถใช้วิธีใดก็ได้ที่ถนัด สำหรับผู้เริ่มต้นแล้ว เราแนะนำให้ใช้ Colab notebook

อย่างไรก็ตาม ในที่นี้จะไม่ได้แสดงวิธีการติดตั้งสำหรับระบบปฏิบัติการ Windows หากคุณใช้ Windows แนะนำให้ทำตามวิธีการลงด้วย Colab notebook หากคุณใช้ Linux หรือ macOS คุณสามารถทำตามวิธีใดก็ได้

หากคุณจะทำตามตัวอย่างในคอร์สนี้ คุณจำเป็นต้องมีบัญชี Hugging Face หากยังไม่มีบัญชี คุณสามารถ [สร้างบัญชีใหม่ที่นี่](https://huggingface.co/join)


## การใช้งาน Colab notebook (หรือเรียกว่า Google Colab)

การติดตั้งโดยใช้ Colab notebook เป็นวิธีการติดตั้งที่ง่ายที่สุด เพียงแค่คุณเปิดเว็บบราวเซอร์ เช่น Google Chrome, Microsoft Edge, Mozilla Firefox, Brave, หรือ Safari ก็สามารถใช้งานได้เลย

หากคุณยังไม่เคยใช้ Colab เราแนะนำให้คุณเริ่มต้นจาก [แนะนำการใช้งาน Colab](https://colab.research.google.com/notebooks/intro.ipynb) โดย Colab อนุญาตให้คุณใช้ฮาร์ดแวร์เพิ่มความเร็ว เช่น GPU หรือ TPU ได้ฟรีสำหรับงานเล็ก ๆ

เมื่อคุณคุ้นเคยกับการใช้งาน Colab แล้ว มาสร้าง notebook ใหม่กัน ผลจะออกมาหน้าตาเหมือนด้านล่าง:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/new_colab.png" alt="An empty colab notebook" width="80%"/>
</div>

ขั้นตอนต่อไป คือ การติดตั้ง library ที่ต้องใช้งานในคอร์สนี้ โดยเราจะใช้คำสั่ง `pip` ซึ่งเป็นตัวจัดการ package ใน Python เพื่อใช้ในการติดตั้ง โดยใน notebook นั้น คุณสามารถใช้งานคำสั่งระบบได้ด้วยการใส่ตัวอักษร `!` ด้านหน้าคำสั่ง ดังนั้นคุณสามารถติดตั้ง library 🤗 Transformers ด้วยคำสั่งดังต่อไปนี้:

```
!pip install transformers
```

คุณสามารถตรวจสอบว่า คุณติดตั้ง package เรียบร้อยแล้วหรือไม่ด้วยการ import เข้าไปใน Python ด้วยคำสั่งดังต่อไปนี้:

```
import transformers
```

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/install.gif" alt="A gif showing the result of the two commands above: installation and import" width="80%"/>
</div>

ขั้นตอนนี้เป็นการติดตั้ง 🤗 Transformers แบบเวอร์ชั่นเล็กไปก่อน แต่ยังไม่ได้ติดตั้ง framework ใด ๆ ที่เกี่ยวข้องกับ machine learning เช่น PyTorch หรือ TensorFlow เนื่องจากเราต้องใช้ feature ต่าง ๆ ของ library เราจึงแนะนำให้ติดตั้งเวอร์ชันพัฒนาซึ่งมาพร้อมกับ library อื่น ๆ ที่จำเป็น(หรือเรียกว่า dependency) เพื่อให้ครอบคลุมการใช้งานทั้งหมด ด้วยคำสั่งดังต่อไปนี้

```
!pip install transformers[sentencepiece]
```

ขั้นตอนนี้จะใช้เวลานานนิดหน่อย แต่เมื่อจบแล้วก็ถือเป็นอันเสร็จสิ้น คุณพร้อมจะเริ่มใช้งานโค้ดต่าง ๆ ในคอร์สนี้ได้แล้ว!


## การใช้งานสิ่งแวดล้อมจำลองใน Python (หรือเรียกว่า Python virtual environment)

หากคุณถนัดการใช้งาน Python virtual environment มากกว่า ขั้นตอนแรกที่ต้องทำคือการติดตั้ง Python ลงบนเครื่องของคุณ เราแนะนำให้ทำตาม [ขั้นตอนนี้](https://realpython.com/installing-python/)

เมื่อคุณติดตั้ง Python เสร็จเรียบร้อย คุณสามารถรันคำสั่ง Python ที่ terminal ของคุณได้ โดยใช้คำสั่งดังต่อไปนี้: `python --version` หากคำสั่งนี้แสดงผลออกมาเป็น version ของ Python แสดงว่าระบบได้ติดตั้ง Python ลงบนเครื่องของคุณเรียบร้อยแล้วจริง ๆ

เมื่อคุณใช้คำสั่ง Python ที่ terminal เช่น `python --version` ตัวโปรแกรมจะไปเรียก Python "ชุดหลัก"จากระบบของคุณ เราไม่แนะนำให้คุณติดตั้ง package ใด ๆ ลงบน Python "ชุดหลัก" ดังกล่าว แต่ให้ใช้ในการสร้าง environment แยกออกมาในแต่ละการใช้งาน ดังนั้น แต่ละงานจะมี package และ dependency ของตนเอง ทำให้คุณไม่ต้องกังวลกับปัญหาใช้งานไม่ได้เพราะเวอร์ชันไม่ตรงเนื่องจากเวอร์ชันของ library ที่งานหนึ่งไปขัดกับ library เดียวกันที่อีกงานหนึ่ง

สำหรับ Python แล้ว กระบวนการนี้สามารถทำได้โดยใช้ [*virtual environment*](https://docs.python.org/3/tutorial/venv.html) ซึ่งเป็นการเก็บ directory ทั้งหมดในการติดตั้ง package ที่ต้องการในเวอร์ชันที่เราใช้งาน การสร้าง virtual environment สามารถทำได้หลายวิธี แต่เราจะใช้ package อย่างเป็นทางการจาก Python ชื่อว่า [`venv`](https://docs.python.org/3/library/venv.html#module-venv).

ขั้นแรกให้สร้าง directory สำหรับการใช้งาน เช่น หากคุณต้องการสร้าง directory ใหม่ ชื่อว่า *transformer-course* ที่ root ของ home directory สามารถใช้คำสั่งดังต่อไปนี้:

```
mkdir ~/transformers-course
cd ~/transformers-course
```

ภายใน directory นี้ ให้สร้าง virtual environment โดยใช้โมดูล `venv` ด้วยคำสั่งดังต่อไปนี้:

```
python -m venv .env
```

คุณจะพบ directory ชื่อ *.env* ใน directory ที่เคยว่างเปล่าของคุณ ด้วยคำสั่งดังต่อไปนี้:

```
ls -a
```

```out
.      ..    .env
```

คุณสามารถเข้าและออก virtual environment ได้ด้วยคำสั่ง `activate` และ `deactivate` ดังต่อไปนี้:

```
# Activate the virtual environment
source .env/bin/activate

# Deactivate the virtual environment
deactivate
```

คุณสามารถตรวจสอบได้ว่า คุณอยู่ใน environment ใดได้ด้วยคำสั่ง `which python` ระบบจะแสดงผล environment ที่คุณกำลังใช้งานอยู่

```
which python
```

```out
/home/<user>/transformers-course/.env/bin/python
```

### การติดตั้ง dependency

ดังที่แสดงไว้ข้างต้นในส่วนของการใช้งาน Google Colab คุณจำเป็นต้องติดตั้ง package ต่าง ๆ เพื่อให้ใช้งานได้ โดยคุณสามารถติดตั้งเวอร์ชันพัฒนาของ 🤗 Transformers โดยใช้คำสั่ง `pip` ดังต่อไปนี้:

```
pip install "transformers[sentencepiece]"
```

จากนี้ก็เป็นอันเสร็จสิ้น คุณพร้อมสำหรับการเริ่มต้นแล้ว!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter0/1.mdx" />

### สรุป
https://huggingface.co/learn/course/th/chapter1/9.md

# สรุป

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

ในบทนี้คุณได้เรียนรู้การแก้ปัญหา NLP แบบต่าง ๆ โดยใช้ฟังก์ชัน `pipeline()` จาก 🤗 Transformers รวมถึงได้เรียนรู้การค้นหาและใช้งานโมเดลใน Hub อีกทั้งยังเรียนรู้การใช้งาน Inference API ในการทดสอบโมเดลจากเว็บบราวเซอร์

นอกจากนี้เรายังได้พูดเกี่ยวกับการทำงานของโมเดล Transformer และความสำคัญของการใช้งาน transfer learning และการ fine-tune สิ่งสำคัญเลยคือ คุณสามารถใช้โมเดลแบบเต็มรูปแบบหรือจะใช้เพียงแค่ส่วน encoder หรือ decoder ก็ได้ ขึ้นอยู่กับว่าต้องการใช้งานแบบไหน ตารางด้านล่างสรุปการใช้งานไว้ดังนี้:


| โมเดล           | ตัวอย่าง                                   | งาน                                                                            |
|-----------------|--------------------------------------------|----------------------------------------------------------------------------------|
| Encoder         | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | การแยกแยะประโยค, การระบุประเภทคำ, การสกัดคำถามคำตอบ |
| Decoder         | CTRL, GPT, GPT-2, Transformer XL           | การสร้างข้อความ                                                                   |
| Encoder-decoder | BART, T5, Marian, mBART                    | การสรุปความ, การแปลภาษา, การสร้างคำตอบจากคำถาม    |


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/9.mdx" />

### โมเดล Encoder
https://huggingface.co/learn/course/th/chapter1/5.md

# โมเดล Encoder

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="MUqNwgPjJvQ" />

โมเดล encoder ใช้เพียงส่วน encoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำทุกคำในประโยคได้ โมเดลเหล่านี้ส่วนใหญ่จะใช้ attention แบบสองทาง (หรือเรียกว่า bi-directional attention) และถูกเรียกว่า *โมเดล auto-encoding*

โมเดล pretrain ในกลุ่มนี้จะเทรนโดยการให้ประโยคเริ่มต้นและประโยควิบัติ(เช่น เว้นว่างคำบางคำในประโยค) และเป้าหมายของโมเดลคือหาวิธีสร้างประโยคเริ่มต้นให้ดีดังเดิม

โมเดล encoder เหมาะกับงานแบบนี้ที่สุด เพราะงานเหล่านี้ต้องการความเข้าใจประโยคทั้งประโยค ตัวอย่างงานแบบนี้เช่น การแยกแยะประโยค, การระบุคำเฉพาะในประโยค (รวมถึงการแยกแยะประเภทคำ), และการสกัดคำถามคำตอบ

ตัวแทนโมเดลในกลุ่มนี้ได้แก่:

- [ALBERT](https://huggingface.co/transformers/model_doc/albert.html)
- [BERT](https://huggingface.co/transformers/model_doc/bert.html)
- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html)
- [ELECTRA](https://huggingface.co/transformers/model_doc/electra.html)
- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/5.mdx" />

### บทนำ
https://huggingface.co/learn/course/th/chapter1/1.md

# บทนำ

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

## ยินดีต้อนรับเข้าสู่คอร์ส 🤗!

<Youtube id="00GKzGyWFEs" />

คอร์สนี้จะสอนคุณเกี่ยวกับการประมวลผลภาษาธรรมชาติหรือ(หรือเรียกว่า NLP) โดยใช้กลุ่ม library ของ [Hugging Face](https://huggingface.co/) ได้แก่ [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), และ [🤗 Accelerate](https://github.com/huggingface/accelerate) รวมไปถึงการใช้งาน [Hugging Face Hub](https://huggingface.co/models) โดยคอร์สนี้เป็นคอร์สฟรีและไม่มีโฆษณา


## คุณจะได้อะไรจากการเรียนคอร์สนี้?

ภาพรวมของคอร์สเป็นดังนี้:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary.svg" alt="Brief overview of the chapters of the course.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary-dark.svg" alt="Brief overview of the chapters of the course.">
</div>

- บทที่ 1 ถึง 4 จะพาคุณไปรู้จักกับคอนเซปต์หลัก ๆ ของ library 🤗 Transformers เมื่อเรียนเนื้อหาในส่วนนี้จบแล้วคุณจะเรียนรู้ว่าโมเดล Transformer ทำงานอย่างไร และเรียนรู้การใช้งานโมเดลจาก [Hugging Face Hub](https://huggingface.co/models), fine-tune โมเดลผ่าน dataset, และแชร์ผลลัพธ์ขึ้น Hub
- บทที่ 5 ถึง 8 จะสอนเกี่ยวกับ 🤗 Datasets และ 🤗 Tokenizers พื้นฐานก่อนที่จะไปลุยกันในส่วนของงานทางด้าน NLP แบบดั้งเดิม หลังจากเรียนเนื้อหาส่วนนี้จบ คุณจะสามารถแก้ปัญหา NLP ทั่วไปเองได้แล้ว
- บทที่ 9 ถึง 12 จะเจาะลึกเนื้อหามากกว่า NLP โดยจะเรียนรู้วิธีการที่โมเดล Transformer จัดการกับการประมวลผลคำพูด(หรือเรียกว่า speech processing) รวมถึงงานทางด้านการประมวลผลภาพ(หรือเรียกว่า computer vision)  ระหว่างนี้คุณจะได้เรียนรู้วิธีการสร้างและแชร์เดโมของโมเดล รวมถึงการ optimize เพื่อให้สามารถใช้งานในระดับ production ได้จริง หลังจากเรียนเนื้อหาส่วนนี้จบ คุณก็พร้อมที่จะใช้ 🤗 Transformers ในการแก้(เกือบ)ทุกปัญหาเกี่ยวกับ machine learning ได้แล้ว!


การเรียนคอร์สนี้:

* ผู้เรียนต้องมีความรู้ในการเขียนโปรแกรมภาษา Python อย่างดี
* ไม่ได้สอนพื้นฐาน deep learning ให้ ผู้เรียนควรเรียนคอร์สพื้นฐานอื่น ๆ มาก่อนแล้ว เช่น คอร์ส [Practical Deep Learning for Coders](https://course.fast.ai/) โดย [fast.ai's](https://www.fast.ai/) หรือคอร์สจาก [DeepLearning.AI](https://www.deeplearning.ai/)
* ผู้เรียนไม่จำเป็นต้องมีความรู้เกี่ยวกับ [PyTorch](https://pytorch.org/) หรือ [TensorFlow](https://www.tensorflow.org/) มาก่อน แต่ถ้าหากเคยใช้จะสามารถเรียนรู้ได้ง่ายยิ่งขึ้น


หลังจากเรียนจบคอร์สนี้ เราแนะนำให้คุณไปเรียนคอร์ส [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) ของ DeepLearning.AI ซึ่งสอนเนื้อหาเกี่ยวกับโมเดล NLP พื้นฐานที่ควรรู้ เช่น Naive Bayes และ LSTM 


## ผู้สอนมีใครบ้าง?

ทีมงานผู้สอนได้แก่:

**Matthew Carrigan** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face อาศัยอยู่ที่เมือง Dublin ประเทศ Ireland เคยทำงานเป็น ML Engineer ที่ Parse.ly และเคยเป็นนักวิจัยหลังปริญญาเอกที่ Trinity College ใน Dublin เขาเองไม่เคยเชื่อว่าจะสามารถสร้างปัญญาประดิษฐ์ที่สามารถคิดเรื่องทั่วไปได้เหมือนมนุษย์จริง ๆ (หรือเรียกว่า Aritificial General Intelligence หรือตัวย่อว่า AGI) ได้ด้วยการขยายโครงสร้างโมเดลที่มีอยู่ปัจจุบัน แต่เขาก็ยังหวังเป็นอย่างมากเกี่ยวกับการเป็นอมตะของหุ่นยนต์

**Lysandre Debut** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ตั้งแต่เริ่มพัฒนา library 🤗 Transformers วาดฝันไว้ว่าจะทำให้ทุกคนใช้งาน NLP ได้ด้วยการพัฒนาเครื่องมือต่าง ๆ ผ่าน API ที่ใช้งานได้ง่าย

**Sylvain Gugger** ทำงานตำแหน่ง Research Engineer ที่ Hugging Face และเป็นหนึ่งในผู้ดูแลหลักของ library 🤗 Transformers ก่อนหน้านี้เป็น Research Scientist ที่ fast.ai และเป็นผู้ร่วมแต่งหนังสือ _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ กับ Jeremy Howard เป้าหมายหลักของงานวิจัย คือ การทำให้คนทั่วไปใช้งานโมเดล deep learning ได้ง่ายโดยการออกแบบและปรับปรุงเทคนิคต่าง ๆ ที่ทำให้การ train โมเดลรวดเร็วยิ่งขึ้นบนทรัพยากรคอมพิวเตอร์ที่จำกัด

**Merve Noyan** ทำงานตำแหน่ง Developer Advocate ที่ Hugging Face ทำหน้าที่พัฒนาเครื่องมือในการทำงานต่าง ๆ รวมถึงผลิตสื่อที่มีเนื้อหาเกี่ยวกับเครื่องมือเหล่านี้เพื่อให้ทุกคนสามารถใช้งาน machine learning ได้ง่าย

**Lucile Saulnier** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทำหน้าที่พัฒนาและสนับสนุนการใช้งานเครื่องมือ open-source เธอเองเป็นส่วนหนึ่งในโปรเจควิจัยหลายโปรเจคในเกี่ยวกับ NLP เช่น collaborative training และ BigScience

**Lewis Tunstall** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทำหน้าที่พัฒนาเครื่องมือ open-source เพื่อให้มีการใช้งานอย่างแพร่หลายในชุมชน เป็นผู้ร่วมแต่งหนังสือ [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) ที่กำลังจะตีพิมพ์เร็ว ๆ นี้


**Leandro von Werra**  ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทีม open-source และเป็นผู้ร่วมแต่งหนังสือ [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) เช่นกัน มีประสบการณ์หลายปีในการนำโปรเจค NLP สู่การใช้งานจริงในอุตสาหกรรม


พร้อมกันรึยัง? ในบทนี้ คุณจะได้เรียน:
* วิธีการใช้ฟังก์ชัน `pipeline()` เพื่อแก้ปัญหา NLP เช่น การสร้างข้อความ(หรือเรียกว่า text generation) หรือการแบ่งประเภทข้อความ(หรือเรียกว่า text classification)
* โครงสร้างของ Transformer
* วิธีการแยกระหว่าง encoder, decoder, และ โครงสร้าง encoder-decoder รวมถึงการใช้งานต่าง ๆ



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/1.mdx" />

### Transformers ชื่อนี้มีดียังไง?
https://huggingface.co/learn/course/th/chapter1/3.md

# Transformers ชื่อนี้มีดียังไง?

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter1/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter1/section3.ipynb"},
]} />

ในส่วนนี้ เราจะมาดูกันว่า Transformer นั้นทำอะไรได้บ้าง และมาใช้งานเครื่องมือชิ้นแรกจาก library 🤗 Transformers ซึ่งก็คือฟังก์ชัน `pipeline()`

<Tip>
👀 เห็นปุ่ม <em>Open in Colab</em> ทางด้านบนขวานั่นมั้ย? ลองคลิกเปิดดู Google Colab notebook ได้ ด้านในจะมีตัวอย่างโค้ดที่เกี่ยวข้องทั้งหมดในหน้านี้ โดยปุ่นแบบนี้จะมีในทุก ๆ หน้าที่มีโค้ดตัวอย่าง

แต่หากคุณต้องการรันโค้ดตัวอย่างบนเครื่องของตนเอง เราแนะนำให้เปิดดู <a href="/course/chapter0">ติดตั้งโปรแกรม</a>
</Tip>

## Transformers, Transformers เต็มไปหมดเลย!

โมเดล Transformer นั้นสามารถนำไปใช้แก้ปัญหา NLP ที่ได้กล่าวไปก่อนหน้านี้ได้ทั้งหมดเลย ด้านล่างก็เป็นตัวอย่างหน่วยงานที่ใช้ Hugging Face และโมเดล Transformer รวมถึงยังแบ่งปันโมเดลที่สร้างกลับไปยังชุมชนด้วยเช่นกัน


<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/companies.PNG" alt="Companies using Hugging Face" width="100%">

[library 🤗 Transformers](https://github.com/huggingface/transformers) มีฟังก์ชันในการสร้างและใช้งานโมเดลที่มีคนแบ่งมาให้ใช้ ใน [Model Hub](https://huggingface.co/models) มีโมเดลที่ผ่านการเทรนมาแล้ว (หรือเรียกว่า pretrained model) มากกว่าหนึ่งพันโมเดลที่ใคร ๆ ก็สามารถดาวน์โหลดไปใช้งานได้ รวมถึงคุณเองก็เป็นส่วนหนึ่งในการแบ่งปันนี้ได้เช่นกัน

<Tip>
⚠️ Hugging Face Hub ไม่ได้จำกัดอยู่แค่เพียงโมเดล Transformer เท่านั้น ใคร ๆ ก็สามารถแบ่งปันโมเดลหรือชุดข้อมูลอะไรก็ได้ตามต้องการ มา <a href="https://huggingface.co/join">สร้างบัญชี huggingface.co</a> เพื่อใช้งานฟีเจอร์เหล่านี้ด้วยกันนะ!
</Tip>

ก่อนจะเจาะลึกเข้าไปถึงเบื้องหลังการทำงานของโมเดล Transformer มาดูตัวอย่างกันว่าโมเดล Transformer นั้นสามารถแก้ปัญหา NLP ได้อย่างไรบ้าง

## การใช้งานคำสั่ง pipelines

<Youtube id="tiZFewofSLM" />

คำสั่งพื้นฐานที่สุดของ library 🤗 Transformers คือฟังก์ชัน `pipeline()` โดยฟังก์ชันนี้จะเชื่อมต่อโมเดลกับขั้นตอนอื่น ๆ ที่สำคัญ เช่น กระบวนการจัดการข้อมูลก่อนการประมวลผล(หรือเรียกว่า preprocessing) และการจัดการข้อมูลหลังการประมวลผล(หรือเรียกว่า postprocessing) ทำให้เราสามารถใส่ข้อความเป็นอินพุตและได้คำตอบอันชาญฉลาดได้โดยตรง:

```python
from transformers import pipeline

classifier = pipeline("sentiment-analysis")
classifier("I've been waiting for a HuggingFace course my whole life.")
```

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437}]
```

หรือใส่ไปหลาย ๆ ประโยคก็ได้!

```python
classifier(
    ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"]
)
```

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

โดยปกติแล้ว pipeline จะเลือก pretrained model ที่ fine-tune มาเพื่อการเข้าใจความรู้สึกของผู้เขียนในภาษาอังกฤษ ซึ่งในการใช้งานคำสั่ง `classifier` ครั้งแรก คำสั่งนี้จะไปดาวน์โหลดโมเดลมาจาก Hub และเก็บไว้ใน cache เมื่อคุณใช้งานคำสั่งนี้ซ้ำอีกครั้งจะเป็นการเรียกใช้โมเดลที่เก็บไว้ใน cache แทน จะได้ไม่ต้องดาวน์โหลดใหม่บ่อย ๆ

เมื่อคุณส่งข้อความเข้าไปยัง pipeline ในเบื้องหลังจะมีกระบวนการต่อไปนี้เกิดขึ้น:

1. ข้อความจะถูกแปลงเป็นอินพุตในรูปแบบที่โมเดลสามารถเข้าใจได้
2. ส่งอินพุตเข้าไปคำนวณในโมเดล
3. นำผลการทำนายจากโมเดลมาประมวลผลต่อเพื่อให้มนุษย์เข้าใจได้ง่าย

ในปัจจุบันงานบางอย่างสามารถใช้งานจาก[คำสั่ง pipeline](https://huggingface.co/transformers/main_classes/pipelines.html) ได้โดยตรง ได้แก่:

- `feature-extraction` (เปลี่ยนข้อความเป็น vector)
- `fill-mask` (เติมคำในช่องว่าง)
- `ner` (named entity recognition; การระบุชื่อเฉพาะ)
- `question-answering` (ถาม-ตอบ)
- `sentiment-analysis` (รับรู้ความรู้สึกของผู้เขียน)
- `summarization` (สรุปความ)
- `text-generation` (สร้างข้อความ)
- `translation` (แปลภาษา)
- `zero-shot-classification` (แยกแยะหมวดหมู่โดยไม่ต้องสอน)

มาดูกันว่าแต่อย่างใช้งานกันยังไง!

## แยกแยะหมวดหมู่โดยไม่ต้องสอน

มาเริ่มต้นด้วยงานที่ค่อนข้างท้าทายกันซักหน่อย งานนี้ต้องการแยกแยะหมวดหมู่(หรือเรียกว่า classify) ข้อความที่ไม่เคยถูกระบุหมวดหมู่(หรือเรียกว่า label)มาก่อน เนื่องจากปกติแล้วงานในการ classify ข้อความนั้น โมเดลต้องการเข้าใจการจับคู่ระหว่างข้อความและ label แต่ในความเป็นจริงแล้ว การเอาข้อความมาให้คนคอยกำหนด label ก่อนส่งไปยังโมเดล เป็นกระบวนการที่กินเวลามากและต้องการคนที่มีความรู้เฉพาะแขนง สำหรับกรณีนี้ pipeline `zero-shot-classification` นั้นนับว่าชาญฉลาดมาก ตัว pipeline สามารถระบุกลุ่ม label ที่จะใช้ในการ classify นี้ ดังนั้นเราจึงไม่จำเป็นต้องอาศัย label จาก pretrained model คุณเห็นมาแล้วว่าโมเดลสามารถ classify ประโยคได้ว่าเป็นประโยคที่พูดแง่บวกหรือแง่ลบโดยใช้ label ทั้งสองนี้ โดย `pipeline` ฉลาดกว่านั้นเพราะสามารถ classify ข้อความโดยใช้ label อื่น ๆ ที่เราต้องการได้

```python
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
classifier(
    "This is a course about the Transformers library",
    candidate_labels=["education", "politics", "business"],
)
```

```python out
{'sequence': 'This is a course about the Transformers library',
 'labels': ['education', 'business', 'politics'],
 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]}
```

pipeline นี้เรียกว่า _zero-shot_ เพราะว่าเราไม่ต้อง fine-tune โมเดลด้วยข้อมูลของเราก่อนจะนำไปใช้ ตัวโมเดลสามารถระบุค่าความน่าจะเป็นตาม list ของ label ที่เราต้องการได้เลย!

<Tip>

✏️ **ลองเลย!** ทดลองโดยใช้ข้อความอะไรก็ได้ของเราเองแล้วดูว่าโมเดลส่งค่าอะไรคืนให้เราบ้าง

</Tip>


## การสร้างข้อความ

ทีนี้ เรามาดูกันว่า pipeline จะใช้สร้างข้อความได้ยังไง โดยหลักการแล้ว เพียงคุณส่งข้อความสั้น ๆ ไปให้โมเดล โมเดลจะเดาข้อความที่เหลือทั้งหมดให้อัตโนมัติ วิธีการนี้ก็คล้าย ๆ กับการที่โทรศัพท์มือถือเดาข้อความที่เรากำลังจะพิมพ์ สิ่งที่ควรทราบเพิ่มเติมคือ การสร้างข้อความนั้นมีการสุ่มรวมอยู่ในกระบวนการด้วย ดังนั้นหากเราทดสอบซ้ำ ๆ แล้วได้ผลลัพธ์ไม่เหมือนกันก็นับเป็นเรื่องปกติ ตัวอย่างการสร้างข้อความสามารถดูได้ดังตัวอย่างด้านล่าง

```python
from transformers import pipeline

generator = pipeline("text-generation")
generator("In this course, we will teach you how to")
```

```python out
[{'generated_text': 'In this course, we will teach you how to understand and use '
                    'data flow and data interchange when handling user data. We '
                    'will be working with one or more of the most commonly used '
                    'data flows — data flows of various types, as seen by the '
                    'HTTP'}]
```

คุณสามารถควบคุมจำนวนข้อความที่สร้างขึ้นได้โดยกำหนดค่าที่ argument `num_return_sequences` และกำหนดความยาวของข้อความที่สร้างขึ้นมาด้วย argument `max_length`


<Tip>

✏️ **ลองเลย!** ปรับค่า argument `num_return_sequences` และ `max_length` ให้สร้างข้อความขึ้นมาสองประโยค ประโยคละ 15 คำ

</Tip>


## การใช้งานโมเดลใด ๆ จาก Hub ใน pipeline

ตัวอย่างที่ผ่านมาเป็นการใช้โมเดลเริ่มต้นสำหรับงานใด ๆ แต่ในบางครั้งเราอาจต้องเลือกโมเดลที่เฉพาะเจาะจงสำหรับแต่ละงานจาก Hub ใน pipeline ตัวอย่างเช่นการสร้างข้อความ ลองเข้าไปที่ [Model Hub](https://huggingface.co/models) และเลือกหมวดหมู่ทางด้านซ้ายเพื่อดูว่ามีโมเดลใดให้ใช้บ้างสำหรับงานแต่ละอย่าง คุณจะได้เพจที่หน้าตา [ดังนี้](https://huggingface.co/models?pipeline_tag=text-generation)

ลองใช้โมเดล [`distilgpt2`](https://huggingface.co/distilgpt2) โดยสามารถทำตามได้ดังนี้:

```python
from transformers import pipeline

generator = pipeline("text-generation", model="distilgpt2")
generator(
    "In this course, we will teach you how to",
    max_length=30,
    num_return_sequences=2,
)
```

```python out
[{'generated_text': 'In this course, we will teach you how to manipulate the world and '
                    'move your mental and physical capabilities to your advantage.'},
 {'generated_text': 'In this course, we will teach you how to become an expert and '
                    'practice realtime, and with a hands on experience on both real '
                    'time and real'}]
```

คุณสามารถปรับค่าการค้นหาโมเดลได้ด้วยการคลิกที่หมวดหมู่ภาษา และเลือกโมเดลที่สามารถสร้างข้อความในภาษาอื่นได้ ซึ่งใน Model Hub นี้จะมีโมเดลที่รองรับหลายภาษาเช่นกัน

เมื่อคุณเลือกโมเดลโดยการคลิกที่ชื่อโมเดล คุณจะเห็นว่าจะมีแถบ widget เล็ก ๆ ขึ้นมาให้คุณลองก่อนแบบออนไลน์ ด้วยวิธีการนี้ คุณสามารถทดสอบความสามารถของโมเดลได้ก่อนจะดาวน์โหลดไปใช้

<Tip>

✏️ **ลองเลย!** ใช้ตัวกรองหาโมเดลสร้างข้อความสำหรับภาษาอื่น แนะนำให้ลองกับ widget บน pipeline ดูก่อนเลย

</Tip>

### The Inference API

โมเดลทั้งหมดสามารถทดสอบได้โดยตรงผ่านเว็บบราวเซอร์โดยใช้ API ประเมินผล(หรือเรียกว่า Inference API) ซึ่งสามารถใช้ได้ผ่าน[เว็บไซต์ Hugging Face](https://huggingface.co/) คุณสามารถเล่นกับโมเดลต่าง ๆ ได้โดยตรงที่หน้านี้โดยกำหนดข้อความอินพุตได้ตามต้องการและดูว่าโมเดลประมวลผลข้อมูลเหล่านั้นยังไง

Inference API ที่อยู่เบื้องหลัง widget นี้สามารถนำไปใช้งานในเชิงพาณิชย์ได้แบบง่าย ๆ เลยหากคุณต้องการนำไปใช้ในงานของคุณ โปรดดูรายละเอียดเพิ่มเติมได้ที่[หน้ารายการราคา](https://huggingface.co/pricing)

## เติมคำในช่องว่าง

pipeline ต่อไปก็หรือการเติมคำในช่องว่าง หรือ `fill-mask` โดยพื้นฐานแล้วงานนี้ก็คือการเติมคำในช่องว่างที่เว้นไว้ระหว่างข้อความที่กำหนดให้ เช่น

```python
from transformers import pipeline

unmasker = pipeline("fill-mask")
unmasker("This course will teach you all about <mask> models.", top_k=2)
```

```python out
[{'sequence': 'This course will teach you all about mathematical models.',
  'score': 0.19619831442832947,
  'token': 30412,
  'token_str': ' mathematical'},
 {'sequence': 'This course will teach you all about computational models.',
  'score': 0.04052725434303284,
  'token': 38163,
  'token_str': ' computational'}]
```

argument `top_k` ควบคุมจำนวนข้อความที่ต้องการแสดง โดยโมเดลจะเติมคำลงไปที่คำพิเศษที่เขียนว่า `<mask>` ซึ่งหมายถึง *คำที่ละไว้* ทั้งนี้ โมเดลเติมคำในช่องว่างโมเดลอื่นอาจใช้คำที่ละไว้นี้เป็นอย่างอื่น ดังนั้น โปรดรับรู้ไว้เสมอว่า หากจะใช้โมเดลใดให้ศึกษาให้แน่ชัดว่าโมเดลนั้นใช้คำที่ละไว้ว่าอะไร วิธีการหนึ่งที่ทำได้คือให้ตรวจสอบคำที่ละไว้ที่ใช้ใน widget นี้

<Tip>

✏️ **ลองเลย!** หาโมเดล `bert-base-cased` ใน Hub และตรวจสอบคำที่ละไว้ที่ใช้ใน widget ของ Inference API โมเดลนี้ทำนายอะไรออกมาหากเราใส่ประโยคที่ใส่เข้าไปในตัวอย่าง `pipeline` ด้านบน


</Tip>

## การระบุชื่อเฉพาะ

การระบุชื่อเฉพาะ(หรือเรียกว่า Named entity recognition; NER) คืองานที่โมเดลจะต้องหาว่าส่วนใดในประโยคหมายถึงชื่อเฉพาะของ คน สัตว์ สิ่งของ สถานที่ หรือองค์กรใด ๆ มาดูตัวอย่างกัน:

```python
from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, 
 {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, 
 {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57}
]
```

ในส่วนนี้ โมเดลสามารถระบุได้ว่า Sylvian เป็นชื่อคน (PER) Hugging Face เป็นชื่อหน่วยงาน (ORG), และ Brooklyn เป็นชื่อสถานที่ (LOC)

เราเพิ่มตัวเลือก `grouped_entities=True` ตอนสร้างฟังก์ชัน pipeline เพื่อระบุให้ pipeline จับกลุ่มคำที่เป็นการระบุชื่อเฉพาะของสิ่ง ๆ เดียว ในที่นี้ โมเดลจับกลุ่มคำว่า "Hugging" และ "Face" เข้าไปเป็นชื่อองค์กรองค์กรเดียว แม้ว่าจะเป็นการรวมคำหลายคำเข้าด้วยกันก็ตาม ซึ่งจริง ๆ แล้ว ในบทต่อไปเราจะเห็นว่าการประมวลผลนั้นจะแบ่งคำบางคำออกมาเป็นส่วนที่แยกย่อยลงไปอีก ตัวอย่างเช่น คำว่า `Sylvian` ถูกแบ่งออกเป็น 4 ส่วน ได้แก่ `S`, `##yl`, `##va`, และ `##in` และระหว่างการ post-processing ตัว pipeline ก็จะนำแต่ละส่วนนี้มาประกอบเข้าด้วยกัน

<Tip>

✏️ **ลองเลย!** หาโมเดลใน Model Hub ที่ทำงานเกี่ยวกับการระบุชื่อเฉพาะ(หรือเรียกว่า part-of-speech tagging ย่อว่า POS)ในภาษาอังกฤษ รู้มั้ยว่าโมเดลนี้ทำนายอะไรในตัวอย่างประโยคข้างต้น?
</Tip>

## ถาม-ตอบคำถาม

pipeline การถามตอบคำถาม(หรือเรียกว่า `question-answering`) เป็นการตอบคำถามโดยใช้ข้อมูลจากกบริบทที่ให้มา เช่น:

```python
from transformers import pipeline

question_answerer = pipeline("question-answering")
question_answerer(
    question="Where do I work?",
    context="My name is Sylvain and I work at Hugging Face in Brooklyn",
)
```

```python out
{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'}
```

โปรดทราบเอาไว้ว่า pipeline นี้ทำงานโดยการเอาข้อมูลจากบริบทที่ได้มาไปประมวลผล แต่จะไม่ได้สร้างคำตอบออกมา

## การสรุปความ

การสรุปความเป็นงานในการลดข้อความลงมาให้เป็นข้อความที่สั้นลงโดยเก็บรักษาใจความสำคัญให้ได้มากที่สุด ตัวอย่างเช่น:

```python
from transformers import pipeline

summarizer = pipeline("summarization")
summarizer(
    """
    America has changed dramatically during recent years. Not only has the number of 
    graduates in traditional engineering disciplines such as mechanical, civil, 
    electrical, chemical, and aeronautical engineering declined, but in most of 
    the premier American universities engineering curricula now concentrate on 
    and encourage largely the study of engineering science. As a result, there 
    are declining offerings in engineering subjects dealing with infrastructure, 
    the environment, and related issues, and greater concentration on high 
    technology subjects, largely supporting increasingly complex scientific 
    developments. While the latter is important, it should not be at the expense 
    of more traditional engineering.

    Rapidly developing economies such as China and India, as well as other 
    industrial countries in Europe and Asia, continue to encourage and advance 
    the teaching of engineering. Both China and India, respectively, graduate 
    six and eight times as many traditional engineers as does the United States. 
    Other industrial countries at minimum maintain their output, while America 
    suffers an increasingly serious decline in the number of engineering graduates 
    and a lack of well-educated engineers.
"""
)
```

```python out
[{'summary_text': ' America has changed dramatically during recent years . The '
                  'number of engineering graduates in the U.S. has declined in '
                  'traditional engineering disciplines such as mechanical, civil '
                  ', electrical, chemical, and aeronautical engineering . Rapidly '
                  'developing economies such as China and India, as well as other '
                  'industrial countries in Europe and Asia, continue to encourage '
                  'and advance engineering .'}]
```

คุณสามารถระบุ `max_length` หรือ `min_length` เพื่อให้ได้ผลลัพธ์ที่ต้องการได้เหมือนการสร้างข้อความ

## การแปลภาษา

สำหรับการแปลภาษาแล้ว คุณสามารถใช้โมเดลเริ่มต้นได้เลยหากคุณกำหนดคู่ภาษาตั้งต้นและภาษาปลายทางที่ชื่องาน(เช่น `"translation_en_to_fr"`) แต่มีวิธีที่ง่ายกว่าก็คือให้เลือกโมเดลที่ต้องการจาก [Model Hub](https://huggingface.co/models) ตัวอย่างด้านล่างแสดงการแปลภาษาจากภาษาฝรั่งเศสไปยังภาษาอังกฤษ:

```python
from transformers import pipeline

translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en")
translator("Ce cours est produit par Hugging Face.")
```

```python out
[{'translation_text': 'This course is produced by Hugging Face.'}]
```

คุณสามารถกำหนด argument `max_length` หรือ `min_length` เพื่อระบุผลลัพธ์ที่ต้องการเหมือนการสร้างข้อความและการสรุปความ

<Tip>

✏️ **ลองเลย!** ลองค้นหาโมเดลแปลภาษาในภาษาอื่น ๆ และทดลองแปลภาษาจากข้อความด้านบนไปยังภาษาอื่น ๆ

</Tip>

คำสั่ง pipeline ที่แสดงด้านบนเป็นเพียงตัวอย่างเบื้องต้นเท่านั้น การใช้งานของคำสั่งนี้ค่อนข้างเฉพาะเจาะจงและไม่สามารถแก้ไขอะไรเบื้องหลังได้มากนัก ในบทหลัง ๆ คุณจะได้เรียนรู้หลักการทำงานเบื้องหลังของฟังก์ชัน `pipeline()` และวิธีการปรับแต่งการทำงาน


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/3.mdx" />

### โมเดล Decoder
https://huggingface.co/learn/course/th/chapter1/6.md

# โมเดล Decoder

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="d_ixlCubqQw" />

โมเดล decoder ใช้เพียงส่วน decoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำที่อยู่ตำแหน่งก่อนหน้าในประโยคได้เท่านั้น โมเดลเหล่านี้เรียกว่า *โมเดล auto-regressive*

โมเดล pretrain ในกลุ่มนี้ใช้ในการทำนายคำต่อไปในประโยค เหมาะสำหรับงานในการสร้างข้อความ

ตัวแทนโมเดลในกลุ่มนี้ได้แก่:

- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html)
- [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt)
- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html)
- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/6.mdx" />

### คำถามท้ายบท
https://huggingface.co/learn/course/th/chapter1/10.md

# คำถามท้ายบท

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

บทนี้พูดถึงพื้นฐานค่อนข้างเยอะมาก ไม่ต้องกังวลไปหากคุณไม่เข้าใจรายละเอียดทั้งหมด บทหน้าจะช่วยอธิบายว่าแต่ละอย่างทำงานกันเบื้องหลังอย่างไร

ตอนนี้มาทดสอบกันดีกว่าว่าคุณได้เรียนรู้อะไรมาบ้างในบทนี้!

### 1. เปิดหา checkpoint `roberta-large-mnli` ใน Hub โมเดลนี้ใช้ในงานอะไร


<Question
	choices={[
		{
			text: "การสรุปความ",
			explain: "โปรดดูที่<a href=\"https://huggingface.co/roberta-large-mnli\">หน้าเพจ roberta-large-mnli</a>อีกครั้ง"
		},
		{
			text: "การแยกแยะข้อความ",
			explain: "โมเดลนี้แยกแยะว่าประโยคสองประโยคนั้นเข้าข่ายกรณีใดดังต่อไปนี้ (หักล้างกัน, กลาง, ส่งเสริมกัน) หรือเรียกอีกชื่อหนึ่งว่า<em>การอนุมาน</em>",
			correct: true
		},
		{
			text: "การสร้างข้อความ",
			explain: "โปรดดูที่<a href=\"https://huggingface.co/roberta-large-mnli\">หน้าเพจ roberta-large-mnli</a>อีกครั้ง"
		}
	]}
/>

### 2. โค้ดต่อไปนี้ให้ผลลัพธ์ว่าอย่างไร?

```py
from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

<Question
	choices={[
		{
			text: "ได้ผลออกมาเป็นคะแนนระบุว่าประโยคดังกล่าวเป็นข้อความ \"ด้านบวก\" หรือ \"ด้านลบ\" ",
			explain: "ข้อนี้ผิด — ผลลัพธ์นี้ได้จาก pipeline <code>sentiment-analysis</code>"
		},
		{
			text: "ได้ผลออกมาเป็นข้อความที่ทำให้ประโยคสมบูรณ์",
			explain: "ข้อนี้ผิด — ผลลัพธ์นี้ได้จาก pipeline <code>text-generation</code>",
		},
		{
			text: "ได้ผลออกมาระบุว่าคำใดเป็นบุคคล, องค์กร, หรือสถานที่",
			explain: "หากตั้งค่าว่า <code>grouped_entities=True</code> จะสามารถรวมคำหลายคำที่ระบุสิ่งเดียวกันไว้ได้ เช่น \"Hugging Face\" ประกอบด้วยคำสองคำ แต่ระบุถึงสิ่งสิ่งเดียว",
			correct: true
		}
	]}
/>

### 3. เราควรแทนค่า ... ในโค้ดด้านล่างว่าอะไร?

```py
from transformers import pipeline

filler = pipeline("fill-mask", model="bert-base-cased")
result = filler("...")
```

<Question
	choices={[
		{
			text: "This &#60;mask> has been waiting for you.",
			explain: "ข้อนี้ผิด โปรดดูรายละเอียดของโมเดล <code>bert-base-cased</code> แล้วลองตรวจสอบว่าทำผิดตรงไหนไป"
		},
		{
			text: "This [MASK] has been waiting for you.",
			explain: "ถูกต้อง! โมเดลนี้ เว้นช่องว่างด้วยโทเคน [MASK]",
			correct: true
		},
		{
			text: "This man has been waiting for you.",
			explain: "ข้อนี้ผิด pipeline ระบุว่าทำงาน `fill-mask` ซึ่งก็คือการเติมคำในช่องว่าง แต่ไม่มีโทเคนใดระบุช่องว่างในประโยคเลย"
		}
	]}
/>

### 4. ทำไมโค้ดด้านล่างรันไม่ออก?

```py
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
result = classifier("This is a course about the Transformers library")
```

<Question
	choices={[
		{
			text: "pipline นี้ต้องการระบุ label เพื่อใช้ในการแยกแยะประโยค",
			explain: "ถูกต้อง โค้ดที่รันออกจะต้องเพิ่ม <code>candidate_labels=[...]</code> เข้าไปด้วย",
			correct: true
		},
		{
			text: "pipeline นี้ต้องการประโยค input มากกว่าหนึ่งประโยค",
			explain: "ข้อนี้ผิด ถึงแม้ว่าความจริงแล้วจะสามารถใส่ประโยคหลายประโยคเป็น list เข้าไปเป็น input เพื่อรันได้(เหมือน pipeline อื่น ๆ)"
		},
		{
			text: "library 🤗 Transformers พังแบบงง ๆ เหมือนทุกทีน่ะแหละ",
			explain: "ขออนุญาตงดแสดงความคิดเห็นกับคนเลือกข้อนี้นะ"
		},
		{
			text: "pipeline นี้ต้องการประโยค input ที่ยาวกว่านี้ ประโยคนี้สั้นเกินไป",
			explain: "ข้อนี้ผิด และหากใส่ประโยคที่ยาวเกินไปใน pipeline นี้ก็จะโดนตัดให้สั้นลงอยู่ดี"
		}
	]}
/>

### 5. "transfer learning" (การเรียนรู้แบบส่งต่อ) หมายความว่าอย่างไร?

<Question
	choices={[
		{
			text: "ส่งต่อความรู้จาก pretrain model ไปยังโมเดลใหม่โดยเทรนกับข้อมูลเดิม",
			explain: "ข้อนี้ผิด หากทำแบบนี้ก็จะเป็นการสร้างโมเดลเดิมสองครั้ง"
		},
		{
			text: "ส่งต่อความรู้จาก pretrain model ไปยังโมเดลใหม่โดยโมเดลใหม่นี้จะเริ่มต้นการเทรนจาก weight ของโมเดลแรก",
			explain: "ถูกต้อง พอเราเริ่มเทรนโมเดลกับงานใหม่ ความรู้จะถูก*ส่งต่อ*มาจากจากโมเดลแรก",
			correct: true
		},
		{
			text: "ส่งต่อความรู้จาก pretrain model ไปยังโมเดลใหม่โดยสร้างโมเดลใหม่ด้วยสถาปัตยกรรมเดียวกับโมเดลแรก",
			explain: "สถาปัตยกรรมเป็นเพียงวิธีการสร้างโมเดล ไม่มีความรู้ใด ๆ รวมอยู่ข้างใน"
		}
	]}
/>

### 6. ประโยคต่อไปนี้ถูกหรือผิด? โมเดลบริบทภาษาเป็นการเทรนล่วงหน้าที่ไม่ต้องการ label ในการเทรน


<Question
	choices={[
		{
			text: "ถูก",
			explain: "การเทรนล่วงหน้านั้นส่วนใหญ่จะเป็นการ <em>self-supervise</em> นั่นคือ label จะถูกสร้างขึ้นอัตโนมัติจาก input เอง (เช่นการทำนายคำต่อไปในข้อความ หรือเติมคำในช่องว่าง)",
			correct: true
		},
		{
			text: "ผิด",
			explain: "คำตอบนี้ผิด"
		}
	]}
/>

### 7. โปรดเลือกประโยคที่อธิบายคำว่า "model", "architecture" และ "weight" ได้อย่างถูกต้อง"

<Question
	choices={[
		{
			text: "หากเปรียบ model เป็นตึก architecture ก็เป็นแผนผัง และ weight ก็เป็นผู้คนด้านใน",
			explain: "หากเทียบตามคำเปรียบเปรยนี้ weight ควรจะเป็นอิฐหรือวัสดุอื่น ๆ ในการสร้างตึกมากกว่า"
		},
		{
			text: "หากเปรียบ architecture เป็นแผนที่ในการสร้าง model ค่า weight แต่ละค่าก็เป็นเหมือนเมืองต่าง ๆ ในแผนที่",
			explain: "ข้อนี้เปรียบเทียบได้ไม่ถูกต้องเท่าไหร่ เพราะว่าจะมีเพียงเมืองเดียวที่อยู่บนแผนที่ที่ตำแหน่งเดียวกัน (ตัวอย่างเช่น มีเมืองเพียงเมืองเดียวในฝรั่งเศสที่ชื่อปารีส) สำหรับ architecture ใด ๆ ค่าชุด weight สามารถตั้งค่าหลากหลายแตกต่างกันได้"
		},
		{
			text: "architecture คือฟังก์ชันทางคณิตศาสตร์ที่ใช้ในการสร้าง model โดย weight ก็คือค่าคงที่ที่ใช้ในฟังก์ชันเหล่านั้น",
			explain: "ฟังก์ชันทางคณิตศาสตร์ชุดเดิม (architecture) สามารถใช้ในการสร้าง model ต่าง ๆ กันได้โดยใช้ค่าคงที่ (weight) ที่แตกต่างกัน",
			correct: true
		}
	]}
/>


### 8. โมเดลใดต่อไปนี้เหมาะสมในการใช้สำหรับงานสร้างคำที่หายไปในประโยค?

<Question
	choices={[
		{
			text: "โมเดล encoder",
			explain: "โมเดล encoder สร้างตัวแทนของประโยคทั้งประโยค เหมาะสำหรับงานเช่น การแยกแยะประเภทของประโยค"
		},
		{
			text: "โมเดล decoder",
			explain: "โมเดล decoder เหมาะสำหรับงานสร้างคำที่หายไปจากข้อความในประโยคมากที่สุด",
			correct: true
		},
		{
			text: "โมเดล sequence-to-sequence",
			explain: "โมเดล sequence-to-sequence เหมาะสำหรับงานที่ต้องการสร้างประโยคที่มีความสัมพันธ์กับประโยคที่ใส่เข้ามา ไม่ใช่แค่คำบางคำจากในประโยค"
		}
	]}
/>

### 9. โมเดลประเภทใดต่อไปนี้เหมาะสำหรับงานในการสรุปความ?

<Question
	choices={[
		{
			text: "โมเดล encoder",
			explain: "โมเดล encoder สร้างตัวแทนของประโยคทั้งประโยค เหมาะสำหรับงานเช่น การแยกแยะประเภทของประโยค"
		},
		{
			text: "โมเดล decoder",
			explain: "โมเดล decoder สามารถใช้ในการสร้างข้อความได้ (เช่น ข้อความสรุป) แต่โมเดลนี้ไม่สามารถเข้าใจข้อความทั้งหมดเพื่อทำการสรุปได้"
		},
		{
			text: "โมเดล sequence-to-sequence",
			explain: "โมเดล sequence-to-sequence เหมาะสำหรับงานสรุปความที่สุด",
			correct: true
		}
	]}
/>

### 10. โมเดลประเภทใดต่อไปนี้เหมาะสำหรับงานในการแยกแยะประเภทประโยคตาม label ที่กำหนดให้?

<Question
	choices={[
		{
			text: "โมเดล encoder",
			explain: "โมเดล encoder สร้างตัวแทนของประโยคทั้งประโยค เหมาะสำหรับงานเช่น การแยกแยะประเภทของประโยคแบบนี้ที่สุด",
			correct: true
		},
		{
			text: "โมเดล decoder",
			explain: "โมเดล decoder เหมาะสำหรับงานในการสร้างข้อความ ไม่เหมาะสำหรับการสกัด label ออกจากประโยคแบบนี้"
		},
		{
			text: "โมเดล sequence-to-sequence",
			explain: "โมเดล sequence-to-sequence เหมาะสำหรับงานที่คุณต้องการสร้างข้อความจากประโยค input ไม่ใช่จาก label",
		}
	]}
/>

### 11. อคติของโมเดลสามารถเกิดได้จากข้อใดต่อไปนี้ได้บ้าง?

<Question
	choices={[
		{
			text: "fine-tune โมเดลมาจากโมเดล pretrain ทำให้โมเดลที่ fine-tune นั้นรับอคติมาจากโมเดล pretrain",
			explain: "เมื่อคุณใช้งาน transfer learning อคติจากโมเดล pretrain จะส่งต่อไปยังโมเดลที่ fine-tune",
			correct: true
		},
		{
			text: "ข้อมูลที่ใช้เทรนโมเดลเป็นข้อมูลที่มีอคติปนอยู่",
			explain: "ข้อนี้เป็นแหล่งกำเนิดอคติที่ชัดที่สุด แต่ว่าไม่ได้มีข้อนี้ข้อเดียว",
			correct: true
		},
		{
			text: "metric ที่ใช้วัดระหว่างการ optimize มีอคติปนอยู่",
			explain: "ข้อนี้อาจดูไม่เหมือนว่าจะทำให้เกิดอคติในโมเดล แต่ว่าโมเดลของคุณจะปรับปรุงตัวเองไปเรื่อย ๆ ระหว่างเทรนตาม metric ที่เราเลือกโดยไม่มีการคิดซ้ำอีกครั้ง",
			correct: true
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/10.mdx" />

### ข้อจำกัดจากอคติของข้อมูล
https://huggingface.co/learn/course/th/chapter1/8.md

# ข้อจำกัดจากอคติของข้อมูล

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter1/section8.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter1/section8.ipynb"},
]} />

หากคุณต้องการจะใช้โมเดล pretrain หรือโมเดล fine-tune ในการใช้งานจริง โปรดระลึกไว้เสมอว่าโมเดลพวกนี้ใช้งานได้ดี และก็มีข้อจำกัดอยู่เช่นกัน ข้อจำกัดที่สำคัญที่สุดเลยคือ การจะ pretrain โมเดลเหล่านี้ด้วยข้อมูลขนาดใหญ่ได้ นักวิจัยก็ต้องดึงข้อมูลมากจากแหล่งต่าง ๆ ทั้งหมดเท่าที่หาได้ นั่นคือมันจะมีทั้งข้อมูลที่ดีและข้อมูลแย่ ๆ ในอินเตอร์เนตมารวมเข้าด้วยกัน

เพื่อให้เห็นภาพ ลองมาดูตัวอย่างการ `fill-mask` ด้วยโมเดล BERT:

```python
from transformers import pipeline

unmasker = pipeline("fill-mask", model="bert-base-uncased")
result = unmasker("This man works as a [MASK].")
print([r["token_str"] for r in result])

result = unmasker("This woman works as a [MASK].")
print([r["token_str"] for r in result])
```

```python out
['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic']
['nurse', 'waitress', 'teacher', 'maid', 'prostitute']
```

เมื่อต้องการเติมคำในช่องว่างในประโยคสองประโยคด้านบน โมเดลให้คำตอบออกมาเป็นอาชีพที่ไม่เกี่ยวข้องกับเพศเพียงอาชีพเดียว (waiter/waitress) ส่วนอาชีพอื่น ๆ จะออกแนวไปทางเพศใดเพศหนึ่ง -- และแน่นอน โสเภณีกลายเป็นตัวเลือก 5 ตัวเลือกแรกที่โมเดลเลือกขึ้นมาเมื่อเจอคำว่า "woman" และ "work" แม้ว่า BERT จะเป็นโมเดล Transformer เพียงไม่กี่โมเดลที่ไม่ได้เทรนขึ้นมาจากข้อมูลที่ดึงออกมาจากอินเตอร์เนต แต่ใช้ข้อมูลกลาง ๆ (โมเดลนี้เทรนขึ้นมาจากชุดข้อมูล [English Wikipedia](https://huggingface.co/datasets/wikipedia) และ [BookCorpus](https://huggingface.co/datasets/bookcorpus))

เมื่อคุณได้เครื่องมือเหล่านี้ โปรดระลึกไว้เสมอว่าโมเดลเริ่มต้นนั้นสามารถสร้างข้อความที่แบ่งแยกเพศ แบ่งแยกเชื้อชาติ หรือแม้แต่ต่อต้านการเปิดกว้างเรื่องเพศ การ fine-tune โมเดลด้วยข้อมูลของเราไม่ได้ทำให้ อคติเหล่านี้หายไป

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/8.mdx" />

### Transformer ทำงานยังไง?
https://huggingface.co/learn/course/th/chapter1/4.md

# Transformer ทำงานยังไง?

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

ในส่วนนี้เราจะมาดูกันว่าสถาปัตยกรรมของโมเดล Transformer ทำงานกันยังไง


## ประวัติของ Transformer เบื้องต้น

รูปด้านล่างแสดงรายชื่อของโมเดล Transformer ตามแต่ละช่วงเวลา:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono.svg" alt="A brief chronology of Transformers models.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono-dark.svg" alt="A brief chronology of Transformers models.">
</div>

[สถาปัตยกรรม Transformer](https://arxiv.org/abs/1706.03762) ถูกสร้างขึ้นเมื่อปี 2017 เป้าหมายในการสร้างโมเดลก็เพื่องานในการแปลภาษา หลังจากนั้นก็มีโมเดลที่ได้รับแรงบันดาลใจต่อ ๆ มา เช่น

- **มิถุนายน 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), โมเดล Transformer ตัวแรก ที่มาพร้อมกับพร้อมกับโมเดลที่เทรนมาแล้ว ใช้ในการ fine-tune งานทางด้าน NLP ได้หลายงานและได้ผลลัพธ์ที่เทียบเท่างานวิจัยล่าสุด

- **ตุลาคม 2018**: [BERT](https://arxiv.org/abs/1810.04805), โมเดลขนาดใหญ่อีกตัวหนึ่งที่ผ่านการเทรนมาแล้ว ซึ่งออกแบบมาสำหรับงานในการสรุปความ (อ่านต่อได้ในบทถัดไป)

- **กุมภาพันธ์ 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), โมเดล GPT รุ่นพัฒนาที่ดีขึ้น(และใหญ่ขึ้นด้วย) แต่ว่าไม่ได้เปิดให้ใช้สาธารณะในทันทีเนื่องจากอาจมีการนำไปใช้ในด้านไม่ดี

- **ตุลาคม 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), โมเดล BERT ขนาดย่อ ซึ่งเร็วกว่า 60% และใช้หน่วยความจำลดลง 40% แต่ยังคงประสิทธิภาพไว้ที่ 97% ของโมเดล BERT ปกติ

- **ตุลาคม 2019**: [BART](https://arxiv.org/abs/1910.13461) และ [T5](https://arxiv.org/abs/1910.10683), โมเดลที่ผ่านการเทรนมาแล้วทั้งสองตัวที่ใช้สถาปัตยกรรมเดียวกันกับโมเดล Transformer ต้นฉบับ (โมเดลแรกที่ทำแบบนี้)

- **พฤษภาคม 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), โมเดล GPT-2 ที่ใหญ่ขึ้นไปอีกซึ่งสามารถทำงานได้หลายอย่างโดยไม่ต้องการการ fine-tune (หรือเรียกว่า _zero-shot learning_)


ที่จริงแล้วยังมีโมเดลอื่น ๆ อีกมากนอกเหนือจากรายชื่อที่กล่าวไป ในที่นี้ต้องการที่จะเน้นย้ำอีกครั้งในส่วนของประเภทของโมเดล Transformer ซึ่งสามารถแบ่งได้ดังนี้:

- กลุ่ม GPT (หรือเรียกว่า โมเดล Transformer แบบ _auto-regressive_)
- กลุ่ม BERT (หรือเรียกว่า โมเดล Transformer แบบ _auto-encoding_)
- กลุ่ม BART/T5 (หรือเรียกว่า โมเดล Transformer แบบ _sequence-to-sequence_)

โดยเราจะกล่าวถึงแต่ละกลุ่มในรายละเอียดกันภายหลัง


## Transformers ก็คือโมเดลบริบททางภาษาแบบหนึ่งนั่นแหละ

โมเดล Transformer ที่กล่าวไปด้านบนทั้งหมด (GPT, BERT, BART, T5 เป็นต้น) ถูกเทรนให้เป็น*โมเดลบริบททางภาษา* (หรือเรียกว่า language model) นั่นคือโมเดลเหล่านี้ถูกเทรนผ่านข้อความดิบขนาดใหญ่เรียนรู้ด้วยตนเอง (หรือเรียกว่า self-supervised) การ self-supervised นั้นคือการเทรนแบบหนึ่งซึ่งเป้าหมายในการเทรนจะถูกคำนวณออกมาอัตโนมัติจาก input ที่ใส่เข้าไป ดังนั้นการเทรนแบบนี้จึงไม่จำเป็นต้อง label ข้อมูลเลย!

โมเดลประเภทนี้จะเข้าในภาษาที่ถูกเทรนมาในเชิงสถิติ แต่ในการใช้งานจริงอาจไม่มีประโยชน์เท่าไหร่ ดังนั้นโมเดลที่ผ่านการเทรนแบบนี้มาจึงถูกนำไปใช้ในกระบวนการ *transfer learning* ซึ่งก็คือโมเดลนี้จะถูกนำไป fine-tune ต่อในงานอื่นที่มี label จากมนุษย์

ตัวอย่างงานดังกล่าวได้แก่ การเดาคำต่อไปหลังจากอ่านคำในประโยค *n* คำ สิ่งนี้เรียกว่า *causal language model* เนื่องจาก output ขึ้นอยู่กับ input ในอดีตและปัจจุบันเท่านั้น


<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling-dark.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted.">
</div>

อีกตัวอย่างหนึ่งได้แก่ การเติมคำในช่องว่าง (หรือเรียกว่า *masked language modeling*) เป็นการเดาคำที่เว้นว่างไว้ในประโยค โดยในที่นี้จะใช้คำว่า `[MASK]` แทนการเว้นว่าง

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling-dark.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted.">
</div>

## Transformers มัน ใหญ่ มาก

ถ้าไม่นับพวกโมเดลแปลก ๆ อย่าง DistilBERT การจะได้มาซึ่งผลลัพธ์ที่ดีขึ้นก็ต้องเพิ่มขนาดโมเดลและขนาดข้อมูลที่นำมาเทรนโมเดล


<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/model_parameters.png" alt="Number of parameters of recent Transformers models" width="90%">
</div>

ซึ่งในการเทรนโมเดลโดยเฉพาะโมเดลขนาดใหญ่ต้องการข้อมูลในการเทรนจำนวนมาก ดังนั้นการเทรนแต่ละครั้งจึงกินทั้งทรัพยากรเวลาและทรัพยากรในการคำนวณมากขึ้นไปอีก ซึ่งหมายถึงผลกระทบต่อสิ่งแวดล้อมดังแสดงในภาพด้านล่าง


<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint.svg" alt="The carbon footprint of a large language model.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint-dark.svg" alt="The carbon footprint of a large language model.">
</div>

<Youtube id="ftWlj4FBHTg"/>

รูปภาพทำขึ้นเพื่อจะสื่อสารไปยังทีมพัฒนาต่าง ๆ ให้เห็นถึงผลกระทบต่อสิ่งแวดล้อมในการเทรนโมเดลขนาดใหญ่(มาก ๆ) และหากต้องการจะทดลองเพื่อหา hyperparameter ที่ดีสุด ผลกระทบย่อมเพิ่มขึ้นอีกเป็นเงาตามตัว

ลองนึกสภาพว่าหากแต่ละทีมวิจัย, กลุ่มการศึกษา, หรือบริษัทเอกชนใด ๆ ต้องการเทรนโมเดลซักโมเดลหนึ่ง โดยเริ่มต้นมาจากศูนย์ทุกครั้ง จะก่อให้เกิดค่าใช้จ่ายที่ไม่จำเป็นอย่างใหญ่หลวงมาก

เพราะฉะนั้นการแบ่งปันโมเดลที่เทรนไว้แล้วจึงเป็นเรื่องสำคัญอย่างมาก การแบ่งปัน weight ของโมเดลและสร้างสิ่งต่าง ๆ ผ่าน weight ที่มีคนทำมาแล้วจะช่วยลดค่าใช้จ่ายและลดค่า carbon footprint โดยทั่วกัน


## Transfer Learning

<Youtube id="BqqfQnyjmgg" />

*Pretraining* เป็นการเทรนโมเดลจากศูนย์ ซึ่งก็คือ weight ในโมเดลเริ่มต้นจากค่าสุ่ม และเริ่มเทรนโดยไม่มีความรู้อะไรเลย

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining.svg" alt="The pretraining of a language model is costly in both time and money.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining-dark.svg" alt="The pretraining of a language model is costly in both time and money.">
</div>

โดยมากแล้วการ pretrain จะใช้ข้อมูลขนาดใหญ่มาก ๆ ดังนั้นจึงต้องการข้อมูลภาษาขนาดใหญ่และใช้เวลาเทรนหลายสัปดาห์

*Fine-tuning* เป็นการเทรนโมเดล**หลังจาก**การ pretrain ซึ่งก็คือ นำ language model ที่ผ่านการ pretrain แล้ว มาเทรนเพิ่มเติมด้วยชุดข้อมูลที่เฉพาะเจาะจงในงานที่ต้องการ ว่าแต่ ทำไมไม่เทรนโมเดลสำหรับงานสุดท้ายไปทีเดียวเลยล่ะ? มันมีเหตุผลอยู่ ดังนี้:

* โมเดลที่ผ่านการ pretrain มาแล้วมีความเข้าใจความเหมือนบางอย่างในชุดข้อมูลที่นำมา fine-tune ดังนั้น การ fine-tune จึงสามารถนำความรู้ที่ได้จากการ pretrain มาใช้ประโยชน์ได้ทันที (เช่น โมเดลที่ pretrain มาแล้วทางด้าน NLP พอจะมีเข้าใจเบื้องต้นเกี่ยวกับภาษาที่เรากำลังใช้อยู่ในเชิงสถิติ)
* เนื่องจากโมเดลที่ผ่านการ pretrain มาแล้วนั้นถูกเทรนด้วยข้อมูลขนาดใหญ่มาก การใช้ข้อมูลเพียงเล็กน้อยในการ fine-tune ก็สามารถให้ผลเป็นที่พึงพอใจได้
* และด้วยเหตุผลเดียวกันนี้ เวลาและทรัพยากรที่ใช้เพื่อให้ได้ผลลัพธ์ที่ดีก็น้อยกว่าเช่นกัน

ตัวอย่างเช่น เราสามารถใช้โมเดล pretrain ในภาษาอังกฤษและ fine-tune ด้วยข้อมูลภาษาจากใน arXiv (แหล่งข้อมูลค้นคว้าวิจัยทางด้านวิทยาศาสตร์) ทำให้ได้โมเดลที่เก่งทางด้านวิทยาศาสตร์และการวิจัย การ fine-tune นี้ต้องการข้อมูลเพียงเล็กน้อยเท่านั้น ความรู้ความเข้าใจทางภาษาของโมเดลถูกส่งต่อมาจากโมเดล pretrain ดังนั้นกระบวนการนี้จึงเรียกว่า *transfer learning*

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning-dark.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money.">
</div>

ด้วยเหตุนี้การ fine-tune จึงใช้เวลา ข้อมูล ค่าใช้จ่าย และผลกระทบต่อสิ่งแวดล้อมน้อยกว่า รวมถึงว่าเราสามารถทดลองรูปแบบการ fine-tune ต่าง ๆ ได้ง่ายกว่าและเร็วกว่าอีกด้วย เนื่องจากการเทรนแบบนี้มีเงื่อนไขต่ำกว่าการเทรนเต็มรูปแบบ

กระบวนการดังกล่าวนี้ยังให้ผลลัพธ์ที่ดีกว่าการเทรนทุกอย่างจากศูนย์อีกด้วย(เว้นแต่ว่าคุณจะมีข้อมูลเยอะมากจริง ๆ) และนี่ก็เป็นอีกสาเหตุหนึ่งที่คุณควรใช้โมเดล pretrain ที่ใกล้เคียงกับงานที่คุณต้องการทำที่สุดและแค่ fine-tune มันออกมา

## สถาปัตยกรรมทั่วไป

ในส่วนนี้เราจะพูดถึงสถาปัตยกรรมทั่วไปของโมเดล Transformer ไม่ต้องกังวลไปหากคุณไม่เข้าใจเนื้อหาบางส่วนตอนนี้ เนื้อหาในส่วนหลังจะอธิบานเรื่องเหล่านี้ในรายละเอียดเพิ่มเติมให้

<Youtube id="H39Z_720T5s" />

## เริ่มต้น

โมเดล Transformer ประกอบด้วยส่วนใหญ่ ๆ สองส่วน ได้แก่:

* **ส่วนเข้ารหัส (หรือเรียกว่า Encoder) (ซ้าย)**: encoder รับ input เข้ามาและสร้างตัวแทน input เหล่านั้น โดยตัวแทนเหล่านี้เรียกว่า feature นั่นหมายความว่าโมเดลจะปรับแต่งตัวเองให้เข้าใจ input ได้ในส่วนนี้
* **ส่วนถอดรหัส (หรือเรียกว่า Decoder) (ขวา)**: decoder สร้าง output ออกมาเป็นข้อมูลลำดับ (หรือเรียกว่า sequence) โดยอาศัย feature และ input อื่น ๆ นั่นหมายถึงโมเดลปรับแต่งตัวเองให้สร้าง output ได้ในส่วนนี้

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks.svg" alt="Architecture of a Transformers models">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks-dark.svg" alt="Architecture of a Transformers models">
</div>

แต่ละส่วนดังกล่าวสามารถแยกใช้อิสระต่อกันได้ ขึ้นอยู่กับว่าใช้ในงานอะไร เช่น:

* **โมเดลที่ใช้ encoder อย่างเดียว**: สำหรับงานที่ต้องการให้เข้าใจ input เช่น การแยกหมวดหมู่ประโยคและการระบุชนิดของคำในประโยค
* **โมเดลที่ใช้ decoder อย่างเดียว**: สำหรับงานในการสร้าง output ออกมา เช่น การสร้างข้อความ
* **โมเดลที่ใช้ทั้ง encoder และ decoder** หรือ **โมเดล sequence-to-sequence** สำหรับงานในการสร้าง output ที่ต้องการ input เข้ามาด้วย เช่น การแปลภาษาหรือการสรุปความ

ซึ่งเราจะเจาะลงไปในรายละเอียดของแต่ละสถาปัตยกรรมกันต่อในภายหลัง

## Attention layers

ฟีเจอร์สำคัญของโมเดล Transformer ก็คือโมเดลนี้ถูกสร้างขึ้นมาจาก layer พิเศษที่ชื่อว่า *attention layer* ซึ่งเป็นสาเหตุที่ชื่อของงานวิจัยที่พูดถึงสถาปัตยกรรม Transformer เป็นครั้งแรกถูกตั้งชื่อว่า ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) (Attention เป็นทุกอย่างให้เธอแล้ว) เราจะมาอธิบายเกี่ยวกับ attention layer ในรายละเอียดกันในช่วงหลังของคอร์สนี้ แต่สำหรับตอนนี้ คุณแค่เข้าใจว่า attention layer ทำหน้าที่บอกให้โมเดลให้ความสนใจ(attend)กับคำที่ตำแหน่งใดเป็นพิเศษจากประโยคส่งให้(รวมถึงบอกให้มองข้ามคำที่ตำแหน่งอื่นด้วย)เมื่อต้องการอธิบายความหมายของคำใด ๆ

เพื่อให้เห็นภาพมากขึ้น ลองนึกถึงงานในการแปลข้อความจากภาษาอังกฤษเป็นภาษาฝรั่งเศส ตัวอย่างเช่นประโยค "You like this course" โมเดลแปลภาษาต้องสนใจไปที่คำว่า "You" เพื่อให้ได้คำแปลของคำว่า "like" ที่เหมาะสม เนื่องจากในภาษาฝรั่งเศสคำว่า "like" นั้นเปลี่ยนรูปไปตามประธานในประโยค คำอื่น ๆ ที่เหลือในประโยคไม่ได้ส่งผลต่อการแปลคำว่า "like" เลย ในลักษณะเดียวกัน การแปลคำว่า "this" โมเดลต้องสนใจไปที่คำว่า "course" เนื่องจากคำว่า "this" ในภาษาฝรั่งเศสจะแปลออกมาว่าอย่างไรขึ้นอยู่กับว่าคำนั้นเป็นเพศชายหรือหญิง(ในบางภาษาคำแต่ละคำจะมีเพศกำกับไว้แม้ว่าจะไม่ได้เป็นสิ่งมีชีวิตที่มีเพศจริง ๆ ก็ตาม โดยไวยากรณ์ของภาษานั้น ๆ จะผันตามเพศของคำ) และแน่นอนคำอื่น ๆ ในประโยคไม่ได้ส่งผลต่อการแปลว่าคำว่า "this" เลย และเมื่อประโยคซับซ้อนมากยิ่งขึ้น(ส่งผลให้กฎไวยากรณ์ซับซ้อนมากยิ่งขึ้น) โมเดลยิ่งต้องให้ความสนใจไปยังคำที่ไกลออกไปในประโยคเพื่อแปลคำแต่ละคำให้เหมาะสม

แนวคิดเดียวกันนี้ใช้ได้กับงาน NLP อื่น ๆ นั่นคือ คำแต่ละคำมีความหมายในตัวมันเอง โดยความหมายเหล่านี้ส่งผลมาจากบริบทของคำ ซึ่งก็คือคำอื่นที่อยู่รอบ ๆ คำนั้น ๆ

ตอนนี้คุณก็พอจะเข้าใจแล้วว่า attention layer คืออะไร ลองมาดูรายละเอียดของสถาปัตยกรรม Transformer กัน


## สถาปัตยกรรมต้นฉบับ

ในตอนแรก Transformer ออกแบบมาเพื่องานแปลภาษา ระหว่างการเทรน ตัว encoder รับ input เป็นประโยคในภาษาหนึ่ง ในขณะที่ decoder เองก็รับประโยคเดียวกันในภาษาเป้าหมาย โดย attention layer ใน encoder สามารถใช้คำทั้งหมดในประโยคได้(เนื่องจากการแปลคำใด ๆ ขึ้นอยู่กับคำที่อาจจะอยู่ข้างหน้าหรือข้างหลังคำนั้น ๆ ก็ได้) ในขณะที่ decoder ต้องสร้างคำเป็นลำดับออกมาทีละคำ นั่นคือ decoder สามารถสนใจคำที่แปลออกมาก่อนได้เท่านั้น ตัวอย่างเช่น เมื่อเราแปลคำในภาษาเป้าหมายไป 3 คำแรก เราก็จะเอา 3 คำนี้ส่งไปยัง decoder รวมกับ input อื่นจาก encoder เพื่อทำนายคำที่ 4 ออกมา


เพื่อให้กระบวนการเทรนเร็วมากขึ้น(เมื่อโมเดลเข้าถึงประโยคในภาษาเป้าหมายได้) decoder จะรับประโยคในภาษาเป้าหมายไปทั้งประโยค แต่แค่ไม่อนุญาตให้เข้าถึงคำที่กำลังจะแปลในอนาคต(ลองคิดดูว่าถ้ากำลังทำนายคำในประโยคเป้าหมายตำแหน่งที่ 2 แล้วโมเดลก็เข้าถึงคำในตำแหน่งที่ 2 ในประโยคเป้าหมายได้ ปัญหามันก็ดูจะแก้ได้ง่ายเกินไปแหละ) หรืออีกนัยหนึ่งคือ ขณะที่กำลังทำนายคำที่ 4 ในประโยค ตัว attention layer จะเข้าถึงได้แค่คำที่ตำแหน่ง 1 ถึง 3 เท่านั้น

รูปด้านล่างแสดงสถาปัตยกรรม Transformer โดย encoder อยู่ด้านซ้ายและ decoder อยู่ด้านขวา:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers.svg" alt="Architecture of a Transformers models">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers-dark.svg" alt="Architecture of a Transformers models">
</div>

หมายเหตุไว้หน่อยว่า attention layer แรกใน decoder สนใจไปที่ input (ในอดีต)ทั้งหมดของ decoder แต่ attention layer ที่สองใช้ output ของ encoder ด้วย นั่นคือ มันสามารถเข้าถึงประโยค input ทั้งหมดเพื่อทำนายคำในปัจจุบันได้ ส่วนนี้มีประโยชน์มากเนื่องจากแต่ละภาษาก็มีหลักไวยากรณ์ในการวางตำแหน่งคำที่ต่างกัน หรือบริบทที่ถูกกล่าวถึงทีหลังในประโยคอาจส่งผลต่อการหาคำแปลที่เหมาะสมของคำในปัจจุบัน

*attention mask* เป็นอีกส่วนประกอบที่ใช้ได้กับทั้ง encoder และ decoder เพื่อป้องกันไม่ให้โมเดลสนใจไปยังคำบางคำ ตัวอย่างเช่น คำพิเศษบางคำที่เอามาเติมในประโยคให้ประโยคใน input ที่ความยาวเท่ากับเวลาประมวลผลประโยคพร้อมกัน

##  สถาปัตยกรรม(หรือเรียกว่า Architecture) vs. จุดเซฟ(หรือเรียกว่า checkpoints)

ดังที่เราได้เรียนรู้โมเดล Transformer มาในคอร์สนี้ คุณจะเห็นคำที่ความหมายคล้าย ๆ กัน อย่างเช่น *architecture* และ *checkpoint* รวมถึง *model* อย่างไรก็ตาม คำแหล่านี้มีความหมายแตกต่างกันเล็กน้อย ดังนี้:

* **Architecture**: เป็นโครงสร้างของโมเดล นั่นคือ เป็นการกำหนดนิยามของ layer (เช่น บอกว่า มีทั้งหมดกี่ layer และ layer แต่ละ layer เป็นประเภทใด) รวมถึงบอกว่า layer นั้น ๆ จะมีกระบวนการการทำอะไรบ้าง
* **Checkpoints**: เป็นเพียงการเซฟ weight หรือค่าคงที่ของ parameter ในแต่ละ layer ที่จะเอาไปใช้ใน architecture ที่กำหนดได้
* **Model**: เป็นคำกว้าง ๆ ไม่ได้ระบุความหมายเฉพาะเจาะจงเหมือนคำว่า "architecture" หรือ "checkpoint" ซึ่งจริง ๆ อาจจะหมายถึงคำใดก็ได้ แต่ในคอร์สนี้จะใช้คำว่า *architecture* หรือ *checkpoint* ไปเลยเพื่อหลีกเลี่ยงความสับสน

ยกตัวอย่างง่าย ๆ เช่น BERT เป็น architecture ในขณะที่ `bert-base-cased` เป็น checkpoint ที่เซฟ weight จากการเทรน BERT ที่ทีม Google ปล่อยออกมาตอนเปิดให้ใช้งานครั้งแรก โดยเราสามารถเรียกสิ่งเหล่านี้ว่า"โมเดล BERT" หรือ "โมเดล `bert-base-cased`" ก็ถือว่าถูกต้องทั้งคู่




<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/4.mdx" />

### โมเดล sequence-to-sequence
https://huggingface.co/learn/course/th/chapter1/7.md

# โมเดล sequence-to-sequence

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="0_4KEb08xrE" />

โมเดล encoder-decoder (หรือเรียกอีกชื่อหนึ่งว่า *โมเดล sequence-to-sequence*) ใช้ทั้งสองส่วนในสถาปัตยกรรม Transformer ในแต่ละชั้น attention layer ของ encoder จะเข้าถึงคำทั้งหมดในประโยคเริ่มต้นได้ ในขณะที่ attention layer ของ decoder สามารถเข้าถึงได้เพียงคำที่อยู่ตำแหน่งก่อนหน้าคำที่กำหนดใน input เท่านั้น

โมเดล pretrain สามารถเทรนมาในลักษณะเดียวกับโมเดล encoder หรือโมเดล decoder ก็ได้ แต่โดยมากแล้วจะซับซ้อนมากกว่า ตัวอย่างเช่น [T5](https://huggingface.co/t5-base) ถูกเทรนมาโดยการแทนที่กลุ่มคำ(ซึ่งอาจจะมีเพียงคำเดียวหรือหลายคำก็ได้)ด้วยคำพิเศษคำเดียว และเป้าหมายคือให้ทำนายข้อความที่คำพิเศษคำนี้แทนที่มา

โมเดล sequence-to-sequence เหมาะกับงานในการสร้างประโยคขึ้นมาใหม่จาก input ที่กำหนดให้ เช่น การสรุปความ, การแปลภาษา, หรือการสร้างคำตอบจากคำถาม

ตัวแทนโมเดลในกลุ่มนี้ได้แก่:

- [BART](https://huggingface.co/transformers/model_doc/bart.html)
- [mBART](https://huggingface.co/transformers/model_doc/mbart.html)
- [Marian](https://huggingface.co/transformers/model_doc/marian.html)
- [T5](https://huggingface.co/transformers/model_doc/t5.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/7.mdx" />

### การประมวลผลภาษาธรรมชาติ(หรือเรียกว่า NLP)
https://huggingface.co/learn/course/th/chapter1/2.md

# การประมวลผลภาษาธรรมชาติ(หรือเรียกว่า NLP)

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

ก่อนจะเข้าเนื้อหาส่วนโมเดล Transformer เรามาดูกันในภาพรวมก่อนว่า NLP คืออะไร แล้วทำไมเราต้องศึกษาและเรียนรู้มัน

## NLP คืออะไร?

NLP เป็นสาขาหนึ่งของวิชาภาษาศาสตร์และวิชาการเรียนรู้ของเครื่องจักร (หรือเรียกว่า machine learning ย่อว่า ML) ที่พยายามจะเข้าใจทุกอย่างที่เกี่ยวข้องกับภาษาที่มนุษย์ใช้สื่อสารกัน เป้าหมายของ NLP ไม่ได้ต้องการแค่จะเข้าใจความหมายของสิ่งที่สื่อสารออกมาเป็นคำ ๆ ไป แต่ต้องการจะเข้าใจบริบทรอบข้างของคำเหล่านั้นด้วย

งานทางด้าน NLP มีตัวอย่างดังต่อไปนี้:

- **แยกหมวดหมู่ของประโยคทั้งประโยค**: เข้าใจความรู้สึกผู้เขียนจากข้อความรีวิว, ตรวจสอบว่าอีเมลที่ส่งมาเป็นสแปมหรือไม่, ตรวจสอบว่าประโยคถูกต้องตามหลักไวยากรณ์หรือไม่, หรือเปรียบเทียบความหมายของประโยคสองประโยค
- **แยกหมวดหมู่ของคำในประโยค**: ระบุว่าคำแต่ละคำเป็นองค์ประกอบใดของประโยค (เช่น ประธาน, กริยา, กรรม), หรือระบุว่าคำนี้เป็นชื่อเฉพาะของอะไร (ชื่อคน, ชื่อสถานที่, ชื่อองค์กร)
- **สร้างข้อความ**: เดาคำต่อไปที่จะพิมพ์หลังจากเห็นบางส่วนของข้อความ, เติมคำในช่องที่เว้นว่างไว้
- **หาคำตอบจากข้อความ**: เมื่อส่งคำถามและข้อความยาว ๆ ที่มีคำตอบอยู่ข้างใน ให้หาคำตอบของคำถามนั้นจากข้อความที่ให้มา
- **สร้างประโยคใหม่จากข้อความที่ให้**: แปลข้อความจากภาษาหนึ่งไปยังอีกภาษาหนึ่ง, สรุปข้อความ

NLP เองนั้นก็ไม่ได้จำกัดอยู่ที่ภาษาเขียนอย่างเดียว แต่ยังสามารถใช้แก้ปัญหาทางด้านภาษาพูด(หรือเรียกว่า speech recognition) และการมองเห็นของคอมพิวเตอร์(หรือเรียกว่า computer vision)ได้ด้วย


## ทำไม NLP ถึงท้าทาย?

อย่างที่รู้กันว่าคอมพิวเตอร์ไม่ได้ประมวลผลข้อมูลแบบเดียวกับที่มนุษย์ทำ ตัวอย่างเช่น เมื่อเราอ่านข้อความว่า "หิวจัง" เราก็สามารถเข้าใจความหมายได้ไม่ยาก และในทางเดียวกัน เมื่อเราอ่านสองประโยคนี้ "หิวจัง" และ "เสียใจจัง" เราก็สามารถเข้าใจความเหมือนของมันได้อย่างง่ายดาย แต่สำหรับ ML แล้ว การจะเข้าใจสองประโยคนี้ค่อนข้างยากเลยทีเดียว ข้อความที่ใส่ไปนั้นจะถูกนำไปประมวลผลเพื่อให้คอมพิวเตอร์สามารถเรียนรู้ได้ และเนื่องจากภาษาเองก็เป็นเรื่องซับซ้อน เราจึงต้องคิดอย่างรอบคอบว่าจะนำไปประมวลผลอย่างไร มีงานวิจัยมากมายพยายามแสดงวิธีประมวลผลข้อความเหล่านี้ให้ออกมาในรูปแบบต่าง ๆ ซึ่งในบทต่อ ๆ ไป เราจะมาดูกันว่าตัวอย่างวิธีการประมวลผลข้อความเหล่านี้ทำงานกันอย่างไร

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter1/2.mdx" />
