diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index 5139c38b7..b473ea93c 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -104,4 +104,26 @@ title: เรียนจบเรื่อง tokenizer แล้ว! - local: chapter6/10 title: คำถามท้ายบท - quiz: 6 \ No newline at end of file + quiz: 6 + +- title: 7. หน้าที่หลักของ NLP + sections: + - local: chapter7/1 + title: บทนำ + - local: chapter7/2 + title: การจำแนกประเภทคำ (Token classification) + - local: chapter7/3 + title: การปรับแต่งโมเดลภาษา (Fine-tuning a masked language model) + - local: chapter7/4 + title: การแปลความหมาย + - local: chapter7/5 + title: การสรุปความหมาย (Summarization) + - local: chapter7/6 + title: การเทร็นภาษาเชิงสาเหตุตั้งแต่เริ่มต้น (Training a causal language model from scratch) + - local: chapter7/7 + title: การตอบคำถาม (Question answering) + - local: chapter7/8 + title: การเชี่ยวชาญใน NLP + - local: chapter7/9 + title: คำถามท้ายบท + quiz: 7 \ No newline at end of file diff --git a/chapters/th/chapter7/1.mdx b/chapters/th/chapter7/1.mdx new file mode 100644 index 000000000..8f4f261a5 --- /dev/null +++ b/chapters/th/chapter7/1.mdx @@ -0,0 +1,38 @@ + + +# บทนำ[[บทนำ]] + + + +ใน [บทที่ 3](/course/th/chapter3), คุณได้เห็นวิธีการปรับแต่งโมเดลสำหรับการจัดหมวดหมู่ข้อความแล้ว ในบทนี้ เราจะพูดถึงหน้าที่ NLP โดยทั่วไป ดังนี้: + +- การจำแนกประเภทคำ (Token classification) +- การปรับแต่งโมเดลภาษา (Masked language modeling) เช่น BERT +- การสรุปความหมาย (Summarization) +- การแปลความหมาย (Translation) +- โมเดลภาษาเชิงสาเหตุ (Causal language modeling pretraining) เช่น GPT-2 +- การตอบคำถาม (Question answering) + +{#if fw === 'pt'} + +ในการดำเนินการนี้ คุณจะต้องใช้ประโยชน์จากทุกสิ่งที่คุณได้เรียนรู้เกี่ยวกับ `Trainer` API และ 🤗 Accelerate ไลบรารี่ใน [บทที่ 3](/course/th/chapter3) ไลบรารี 🤗 ชุดข้อมูลใน [บทที่ 5](/course/th/chapter5 ) และไลบรารี 🤗 Tokenizers ใน [บทที่ 6](/course/th/chapter6) นอกจากนี้เรายังจะอัปโหลดผลลัพธ์ของเราไปยัง Model Hub เช่นเดียวกับที่เราทำใน [บทที่ 4](/course/th/chapter4) ดังนั้นนี่คือบทที่ทุกอย่างมารวมกันจริงๆ! + +แต่ละส่วนสามารถอ่านแยกกันได้ และจะแสดงวิธีฝึกโมเดลด้วย `Trainer` API หรือด้วยลูปการฝึกของคุณเอง โดยใช้ 🤗 Accelerate คุณสามารถข้ามส่วนใดส่วนหนึ่งและมุ่งความสนใจไปที่ส่วนที่คุณสนใจมากที่สุดได้เลย: `Trainer` API นั้นยอดเยี่ยมสำหรับการปรับแต่ง (fine-tuning) หรือฝึกฝน (training) โมเดลของคุณโดยไม่ต้องกังวลกับสิ่งที่เกิดขึ้นเบื้องหลัง ในขณะที่ลูปการฝึกฝนด้วย `Accelerate` จะช่วยให้คุณปรับแต่งส่วนใด ๆ ที่คุณต้องการได้ง่ายขึ้น + +{:else} + +ในการดำเนินการนี้ คุณจะต้องใช้ประโยชน์จากทุกสิ่งที่คุณได้เรียนรู้เกี่ยวกับโมเดลการฝึกอบรมด้วย Keras API ใน [บทที่ 3](/course/th/chapter3) ไลบรารี 🤗 ชุดข้อมูลใน [บทที่ 5](/course/th/chapter5) และ 🤗 ไลบรารี Tokenizers ใน [บทที่ 6](/course/th/chapter6) นอกจากนี้เรายังจะอัปโหลดผลลัพธ์ของเราไปยัง Model Hub เช่นเดียวกับที่เราทำใน [บทที่ 4](/course/th/chapter4) ดังนั้นนี่คือบทที่ทุกอย่างมารวมกันจริงๆ! + +แต่ละส่วนสามารถอ่านได้อย่างอิสระ + +{/if} + + + + +หากคุณอ่านส่วนต่างๆ ตามลำดับ คุณจะสังเกตเห็นว่ามีโค้ดและข้อความค่อนข้างเหมือนกัน การทำซ้ำนี้ มีเจตนาเพื่อให้คุณสามารถเข้าไปทำงานใดๆ ที่คุณสนใจ (หรือกลับมาใหม่ทีหลัง) และค้นหาตัวอย่างการทำงานที่สมบูรณ์ได้ + + diff --git a/chapters/th/chapter7/2.mdx b/chapters/th/chapter7/2.mdx new file mode 100644 index 000000000..a48fe5d31 --- /dev/null +++ b/chapters/th/chapter7/2.mdx @@ -0,0 +1,981 @@ + + +# การจำแนกประเภทคำ[[การจำแนกประเภทคำ]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +แอปพลิเคชันแรกที่เราจะสำรวจคือการจำแนกคำ งานทั่วไปนี้ครอบคลุมปัญหาใดๆ ที่สามารถกำหนดเป็น "การกำหนดป้ายกำกับให้กับแต่ละคำในประโยค" เช่น: + +- **Named entity recognition (NER)**: ค้นหาเอนทิตี (เช่น บุคคล สถานที่ หรือองค์กร) ในประโยค สิ่งนี้สามารถกำหนดเป็นการกำหนดป้ายกำกับให้กับแต่ละคำโดยมีหนึ่งคลาสต่อเอนทิตีและหนึ่งคลาสสำหรับ "ไม่มีเอนทิตี" +- **Part-of-speech tagging (POS)**: ทำเครื่องหมายแต่ละคำในประโยคว่าสอดคล้องกับส่วนของคำพูด (เช่น คำนาม กริยา คำคุณศัพท์ ฯลฯ) +- **Chunking**: ค้นหาคำ (token) ที่เป็นของเอนทิตีเดียวกัน งานนี้ (ซึ่งสามารถใช้ร่วมกับ POS หรือ NER) สามารถกำหนดเป็นป้ายกำกับ (label) เดียว (โดยปกติคือ `B-`) ให้กับคำใด ๆ ที่อยู่ที่จุดเริ่มต้นของก้อน และอีกป้ายกำกับหนึ่ง (โดยปกติคือ `I-`) ให้กับคำที่ อยู่ภายในก้อนข้อมูล และป้ายกำกับที่สาม (โดยปกติคือ `O`) ของคำที่ไม่ได้เป็นของก้อนใดก้อนหนึ่ง + + + +แน่นอนว่ามีปัญหาการจำแนกคำประเภทอื่นๆ อีกหลายประเภท นี่เป็นเพียงตัวอย่างบางส่วนเท่านั้น ในส่วนนี้ เราจะปรับแต่งโมเดล (BERT) ในงาน NER ซึ่งจะสามารถคำนวณการคาดการณ์เช่นนี้ได้: + + + + +One-hot encoded labels for question answering. + + + +คุณค้นหาโมเดลที่เราจะฝึกและอัปโหลดไปยัง Hub และตรวจสอบการคาดการณ์อีกครั้งได้ [ที่นี่](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn). + +## การเตรียมข้อมูล[[การเตรียมข้อมูล]] + +ก่อนอื่น เราต้องการชุดข้อมูลที่เหมาะสำหรับการจำแนกคำ ในส่วนนี้ เราจะใช้ [CoNLL-2003 dataset](https://huggingface.co/datasets/conll2003) ซึ่งรวบรวมข่าวจาก Reuters + + + +💡 ตราบใดที่ชุดข้อมูลของคุณประกอบด้วยข้อความที่แบ่งออกเป็นคำโดยมีป้ายกำกับที่เกี่ยวข้อง คุณจะสามารถปรับขั้นตอนการประมวลผลข้อมูลที่อธิบายไว้ที่นี่กับชุดข้อมูลของคุณเองได้ ย้อนกลับไปที่ [บทที่ 5](/course/th/chapter5) หากคุณต้องการทบทวนวิธีโหลดข้อมูลที่คุณกำหนดเองใน `ชุดข้อมูล` + + + +### The CoNLL-2003 dataset[[the-conll-2003-dataset]] + +ในการโหลดชุดข้อมูล CoNLL-2003 เราใช้เมธอด `load_dataset()` จากไลบรารี 🤗 ชุดข้อมูล: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +การดำเนินการนี้จะดาวน์โหลดและแคชชุดข้อมูล ดังที่เราเห็นใน [บทที่ 3](/course/th/chapter3) สำหรับชุดข้อมูล GLUE MRPC การตรวจสอบออบเจ็กต์นี้จะแสดงให้เราเห็นคอลัมน์ที่มีอยู่และการแบ่งระหว่างชุดการฝึก การตรวจสอบ และการทดสอบ: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 14041 + }) + validation: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3250 + }) + test: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3453 + }) +}) +``` + +โดยเฉพาะอย่างยิ่ง เราจะเห็นว่าชุดข้อมูลมีป้ายกำกับ (label) สำหรับงานสามอย่างที่เรากล่าวถึงก่อนหน้านี้: NER, POS และ chunking จะเห็นว่าความแตกต่างอย่างมากจากชุดข้อมูลอื่นๆ ก็คือข้อความที่ป้อนไม่ได้ถูกนำเสนอเป็นประโยคหรือเอกสาร แต่เป็นรายการของคำ (คอลัมน์สุดท้ายเรียกว่า `โทเค็น` แต่มีคำในแง่ที่ว่าสิ่งเหล่านี้เป็นอินพุตโทเค็นล่วงหน้าที่ยังคงต้องการ เพื่อผ่านโทเค็นไนเซอร์สำหรับโทเค็นคำย่อย) + +มาดูองค์ประกอบแรกของชุดการฝึกกันดีกว่า: + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +เนื่องจากเราต้องการดำเนินการจดจำเอนทิตีที่มีชื่อ เราจะดูที่แท็ก NER: + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +สิ่งเหล่านี้คือป้ายกำกับว่าเป็นจำนวนเต็มพร้อมสำหรับการฝึก แต่ก็ไม่จำเป็นเสมอไปเมื่อเราต้องการตรวจสอบข้อมูล เช่นเดียวกับการจัดหมวดหมู่ข้อความ เราสามารถเข้าถึงความสอดคล้องระหว่างจำนวนเต็มเหล่านั้นกับชื่อป้ายกำกับได้โดยดูที่แอตทริบิวต์ `features` ของชุดข้อมูลของเรา: + +```py +ner_feature = raw_datasets["train"].features["ner_tags"] +ner_feature +``` + +```python out +Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None) +``` + +ดังนั้นคอลัมน์นี้จึงมีองค์ประกอบที่เป็นลำดับของ `ClassLabel`s ประเภทขององค์ประกอบของลำดับอยู่ในแอตทริบิวต์ `feature` ของ `ner_feature` นี้ และเราสามารถเข้าถึงรายชื่อได้โดยดูที่แอตทริบิวต์ `names` ของ `feature` นั้น: + +```py +label_names = ner_feature.feature.names +label_names +``` + +```python out +['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] +``` + +เราเห็นป้ายกำกับ (label) เหล่านี้แล้วเมื่อเจาะลึกไปป์ไลน์ `การจำแนกคำ` ใน [บทที่ 6](/course/th/chapter6/3) แต่เพื่อการทบทวนอย่างรวดเร็ว: + +- `O` หมายความว่าคำนี้ไม่สอดคล้องกับเอนทิตีใด ๆ +- `B-PER`/`I-PER` หมายถึงคำที่ตรงกับจุดเริ่มต้นของ หรือ อยู่ภายในเอนทิตี *บุคคล* +- `B-ORG`/`I-ORG` หมายถึงคำที่ตรงกับจุดเริ่มต้นของ หรือ อยู่ภายในเอนทิตี *องค์กร* +- `B-LOC`/`I-LOC` หมายถึงคำที่สอดคล้องกับจุดเริ่มต้นของ หรือ อยู่ภายในเอนทิตี *สถานที่* +- `B-MISC`/`I-MISC` หมายถึงคำที่สอดคล้องกับจุดเริ่มต้นของ หรือ อยู่ภายในเอนทิตี *เบ็ดเตล็ด* + +มาดูการถอดรหัสป้ายกำกับที่เราเห็นก่อนหน้านี้ ซึ่งทำให้เราได้สิ่งนี้: + +```python +words = raw_datasets["train"][0]["tokens"] +labels = raw_datasets["train"][0]["ner_tags"] +line1 = "" +line2 = "" +for word, label in zip(words, labels): + full_label = label_names[label] + max_length = max(len(word), len(full_label)) + line1 += word + " " * (max_length - len(word) + 1) + line2 += full_label + " " * (max_length - len(full_label) + 1) + +print(line1) +print(line2) +``` + +```python out +'EU rejects German call to boycott British lamb .' +'B-ORG O B-MISC O O O B-MISC O O' +``` + +และสำหรับตัวอย่างการผสมป้ายกำกับ `B-` และ `I-` นี่คือสิ่งที่โค้ดเดียวกันนี้ให้กับองค์ประกอบของชุดการฝึกที่ดัชนี 4: + +```python out +'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .' +'B-LOC O O O O B-ORG I-ORG O O O B-PER I-PER O O O O O O O O O O O B-LOC O O O O O O O' +``` + +ดังที่เราเห็น เอนทิตีที่ประกอบด้วยคำสองคำ เช่น "European Union" และ "Werner Zwingmann" จะถูกจัดว่าเป็นป้ายกำกับ `B-` สำหรับคำแรก และป้ายกำกับ 'I-` สำหรับคำที่สอง + + + +✏️ **ถึงตาคุณแล้ว!** พิมพ์สองประโยคเดียวกันด้วย POS หรือป้ายกำกับแบบแยกส่วน + + + +### การประมวลผลข้อมูล[[การประมวลผลข้อมูล]] + + + +ตามปกติ ข้อความของเราต้องแปลงเป็นรหัสโทเค็นก่อนที่โมเดลจะเข้าใจได้ ดังที่เราเห็นใน [บทที่ 6](/course/th/chapter6/) ความแตกต่างที่สำคัญในกรณีของงานการจำแนกโทเค็นก็คือ เรามีอินพุตโทเค็นล่วงหน้า โชคดีที่ tokenizer API สามารถจัดการกับสิ่งนั้นได้อย่างง่ายดาย เราแค่ต้องเตือน `tokenizer` ด้วยแฟล็กพิเศษ + +ขั้นแรก เรามาสร้างออบเจ็กต์ `tokenizer` กัน ดังที่เราได้กล่าวไว้ก่อนหน้านี้ เราจะใช้โมเดลที่ได้รับการฝึกล่วงหน้าของ BERT ดังนั้นเราจะเริ่มต้นด้วยการดาวน์โหลดและแคชโทเค็นไนเซอร์ที่เกี่ยวข้อง: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +คุณสามารถแทนที่ `model_checkpoint` ด้วยโมเดลอื่นใดก็ได้ที่คุณต้องการจาก [Hub](https://huggingface.co/models) หรือด้วยโฟลเดอร์ในเครื่องที่คุณได้บันทึกโมเดลที่ฝึกไว้ล่วงหน้าและ tokenizer. มีข้อจำกัดเพียงอย่างเดียวคือโทเค็นต้องได้รับการสนับสนุนโดยไลบรารี 🤗 Tokenizers ดังนั้นจึงมีเวอร์ชัน "fast" ให้ใช้งาน คุณสามารถดูสถาปัตยกรรมทั้งหมดที่มาพร้อมกับเวอร์ชันที่รวดเร็วได้ใน [ตารางใหญ่นี้](https://huggingface.co/transformers/#supported-frameworks) และเพื่อตรวจสอบว่าวัตถุ `tokenizer` ที่คุณใช้อยู่นั้นเป็นจริง สนับสนุนโดย 🤗 Tokenizers คุณสามารถดูแอตทริบิวต์ `is_fast` ได้: + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +หากต้องการโทเค็นอินพุตที่แปลงเป็นโทเค็นล่วงหน้า (a pre-tokenized input) เราสามารถใช้ `tokenizer` ตามปกติและเพียงเพิ่ม `is_split_into_words=True`: + +```py +inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) +inputs.tokens() +``` + +```python out +['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]'] +``` + +ดังที่เราเห็น Tokenizer ได้เพิ่มโทเค็นพิเศษที่ใช้โดยโมเดล (`[CLS]` ที่จุดเริ่มต้นและ `[SEP]` ในตอนท้าย) และปล่อยให้คำส่วนใหญ่ไม่ถูกแตะต้อง อย่างไรก็ตาม คำว่า `lamb` ถูกแปลงเป็นคำย่อย 2 คำ คือ `la` และ `##mb` สิ่งนี้ทำให้เกิดความไม่ตรงกันระหว่างอินพุตของเราและป้ายกำกับ: รายการป้ายกำกับมีเพียง 9 องค์ประกอบ ในขณะที่อินพุตของเราตอนนี้มี 12 โทเค็น การบัญชีสำหรับโทเค็นพิเศษเป็นเรื่องง่าย (เรารู้ว่ามันอยู่ที่จุดเริ่มต้นและจุดสิ้นสุด) แต่เรายังต้องตรวจสอบให้แน่ใจด้วยว่าเราจัดตำแหน่งป้ายกำกับทั้งหมดด้วยคำที่เหมาะสม + +โชคดีมาก เนื่องจากเราใช้ tokenizer ที่รวดเร็ว เราจึงสามารถเข้าถึง 🤗 Tokenizers มหาอำนาจได้ ซึ่งหมายความว่าเราสามารถจับคู่โทเค็นแต่ละโทเค็นกับคำที่เกี่ยวข้องได้อย่างง่ายดาย (ดังที่เห็นใน [บทที่ 6](/course/th/chapter6/3)): + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +ด้วยการทำงานเพียงเล็กน้อย เราก็สามารถขยายรายการป้ายกำกับของเราให้ตรงกับโทเค็นได้ กฎข้อแรกที่เราจะใช้คือโทเค็นพิเศษจะมีป้ายกำกับ `-100` เนื่องจากโดยค่าเริ่มต้น `-100` คือดัชนีที่ถูกละเว้นในฟังก์ชันการสูญเสีย (loss function) ที่เราจะใช้ (cross entropy) จากนั้น แต่ละโทเค็นจะมีป้ายกำกับเดียวกันกับโทเค็นที่ขึ้นต้นด้วยคำว่าอยู่ข้างใน เนื่องจากเป็นส่วนหนึ่งของเอนทิตีเดียวกัน สำหรับโทเค็นที่อยู่ในคำ แต่ไม่ใช่ที่จุดเริ่มต้น เราจะแทนที่ `B-` ด้วย `I-` (เนื่องจากโทเค็นไม่ได้ขึ้นต้นเอนทิตี): + +```python +def align_labels_with_tokens(labels, word_ids): + new_labels = [] + current_word = None + for word_id in word_ids: + if word_id != current_word: + # Start of a new word! + current_word = word_id + label = -100 if word_id is None else labels[word_id] + new_labels.append(label) + elif word_id is None: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = labels[word_id] + # If the label is B-XXX we change it to I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +เรามาลองใช้ประโยคแรกกันดีกว่า: + +```py +labels = raw_datasets["train"][0]["ner_tags"] +word_ids = inputs.word_ids() +print(labels) +print(align_labels_with_tokens(labels, word_ids)) +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +``` + +ดังที่เราเห็น ฟังก์ชั่นของเราเพิ่ม `-100` สำหรับโทเค็นพิเศษสองตัวที่จุดเริ่มต้นและจุดสิ้นสุด และ `0` ใหม่สำหรับคำของเราที่แบ่งออกเป็นสองโทเค็น + + + +✏️ **ถึงตาคุณทดลองแล้ว!** นักวิจัยบางคนชอบที่จะระบุป้ายกำกับเพียง 1 ป้ายต่อคำ และกำหนด `-100` ให้กับโทเค็นย่อยอื่นๆ ในคำที่กำหนด นี่คือการหลีกเลี่ยงคำยาวๆ ที่แบ่งออกเป็นโทเค็นย่อยจำนวนมากซึ่งมีส่วนทำให้เกิดการสูญเสียอย่างมาก เปลี่ยนฟังก์ชันก่อนหน้าเพื่อจัดแนวป้ายกำกับกับ ID อินพุตโดยปฏิบัติตามกฎนี้ + + + +ในการประมวลผลชุดข้อมูลทั้งหมดของเราล่วงหน้า เราจำเป็นต้องแปลงโทเค็นอินพุตทั้งหมดและใช้ `align_labels_with_tokens()` กับป้ายกำกับทั้งหมด เพื่อใช้ประโยชน์จากความเร็วของโทเค็นเซอร์ที่รวดเร็วของเรา วิธีที่ดีที่สุดคือโทเค็นข้อความจำนวนมากในเวลาเดียวกัน ดังนั้นเราจะเขียนฟังก์ชันที่ประมวลผลรายการตัวอย่างและใช้เมธอด `Dataset.map()` พร้อมตัวเลือก `batched=True` สิ่งเดียวที่แตกต่างจากตัวอย่างก่อนหน้านี้คือฟังก์ชัน `word_ids()` จำเป็นต้องได้รับดัชนีของตัวอย่างที่เราต้องการ ID คำเมื่ออินพุตไปยังโทเค็นไนเซอร์เป็นรายการข้อความ (หรือในกรณีของเรา รายการ ของรายการคำศัพท์) เราก็เลยเพิ่มเข้าไปด้วย: + +```py +def tokenize_and_align_labels(examples): + tokenized_inputs = tokenizer( + examples["tokens"], truncation=True, is_split_into_words=True + ) + all_labels = examples["ner_tags"] + new_labels = [] + for i, labels in enumerate(all_labels): + word_ids = tokenized_inputs.word_ids(i) + new_labels.append(align_labels_with_tokens(labels, word_ids)) + + tokenized_inputs["labels"] = new_labels + return tokenized_inputs +``` + +โปรดทราบว่าเรายังไม่ได้เพิ่มอินพุตของเรา เราจะดำเนินการดังกล่าวในภายหลัง เมื่อสร้างแบทช์ด้วยตัวรวบรวมข้อมูล + +ตอนนี้เราสามารถใช้การประมวลผลล่วงหน้าทั้งหมดนั้นในคราวเดียวกับการแยกชุดข้อมูลอื่นๆ ของเรา: + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +เราได้ทำส่วนที่ยากที่สุดแล้ว! เมื่อข้อมูลได้รับการประมวลผลล่วงหน้าแล้ว การฝึกอบรมจริงจะมีลักษณะเหมือนกับที่เราทำใน [บทที่ 3](/course/th/chapter3) มาก + +{#if fw === 'pt'} + +## ปรับแต่งโมเดลอย่างละเอียดด้วย `Trainer` API + +โค้ดจริงที่ใช้ `Trainer` จะเหมือนกับเมื่อก่อน การเปลี่ยนแปลงเพียงอย่างเดียวคือวิธีการจัดเรียงข้อมูลเป็นชุดและฟังก์ชันการคำนวณหน่วยเมตริก + +{:else} + +## ปรับแต่งโมเดลอย่างละเอียดด้วย Keras + +โค้ดจริงที่ใช้ Keras จะคล้ายกับเมื่อก่อนมาก การเปลี่ยนแปลงเพียงอย่างเดียวคือวิธีการจัดเรียงข้อมูลเป็นชุดและฟังก์ชันการคำนวณหน่วยเมตริก + +{/if} + + +### การจัดเรียงข้อมูล[[การจัดเรียงข้อมูล]] + +เราไม่สามารถใช้ `DataCollatorWithPadding` เหมือนใน [บทที่ 3](/course/th/chapter3) ได้ เพราะนั่นเป็นเพียงการแพดอินพุตเท่านั้น (ID อินพุต, attention mask และ ID ประเภทโทเค็น) ในที่นี้ป้ายกำกับของเราควรได้รับการบุในลักษณะเดียวกับอินพุตเพื่อให้มีขนาดเท่ากัน โดยใช้ค่า "-100" เพื่อที่การคาดการณ์ที่เกี่ยวข้องจะถูกละเว้นในการคำนวณการสูญเสีย + +ทั้งหมดนี้ทำโดย [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification) เช่นเดียวกับ `DataCollatorWithPadding` จะใช้ `tokenizer` ที่ใช้ในการประมวลผลอินพุตล่วงหน้า: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer) +``` + +{:else} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification( + tokenizer=tokenizer, return_tensors="tf" +) +``` + +{/if} + +เพื่อทดสอบสิ่งนี้กับตัวอย่างบางส่วน เราสามารถเรียกมันในรายการตัวอย่างจากชุดการฝึกโทเค็นของเรา: + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(2)]) +batch["labels"] +``` + +```python out +tensor([[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100], + [-100, 1, 2, -100, -100, -100, -100, -100, -100, -100, -100, -100]]) +``` + +ลองเปรียบเทียบสิ่งนี้กับป้ายกำกับสำหรับองค์ประกอบที่หนึ่งและที่สองในชุดข้อมูลของเรา: + +```py +for i in range(2): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +[-100, 1, 2, -100] +``` + +{#if fw === 'pt'} + +ดังที่เราเห็น ป้ายกำกับชุดที่สองได้รับการเสริมให้เท่ากับความยาวของป้ายกำกับแรกโดยใช้ "-100" + +{:else} + +เครื่องมือรวบรวมข้อมูลของเราพร้อมแล้ว! ตอนนี้เรามาใช้เพื่อสร้าง `tf.data.Dataset` ด้วยเมธอด `to_tf_dataset()` คุณยังสามารถใช้ `model.prepare_tf_dataset()` เพื่อทำสิ่งนี้โดยใช้โค้ดสำเร็จรูปน้อยลงเล็กน้อย คุณจะเห็นสิ่งนี้ในส่วนอื่นๆ บางส่วนของบทนี้ + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) + +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + + + จุดต่อไป: โมเดลนั่นเอง + +{/if} + +{#if fw === 'tf'} + +### การกำหนดโมเดล[[การกำหนดโมเดล]] + +เนื่องจากเรากำลังแก้ไขปัญหาการจำแนกโทเค็น เราจะใช้คลาส `TFAutoModelForTokenClassification` สิ่งสำคัญที่ต้องจำเมื่อกำหนดโมเดลนี้คือการส่งข้อมูลบางอย่างเกี่ยวกับจำนวนป้ายกำกับที่เรามี วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการส่งผ่านตัวเลขนั้นด้วยอาร์กิวเมนต์ `num_labels` แต่หากเราต้องการให้วิดเจ็ตการอนุมานที่ดีทำงานเหมือนกับที่เราเห็นในตอนต้นของส่วนนี้ จะเป็นการดีกว่าถ้าตั้งค่าการโต้ตอบป้ายกำกับที่ถูกต้องแทน + +ควรกำหนดโดยพจนานุกรม 2 ฉบับ ได้แก่ `id2label` และ `label2id` ซึ่งมีการจับคู่จาก ID ไปยัง label และในทางกลับกัน: + +```py +id2label = {i: label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +ตอนนี้เราสามารถส่งต่อไปยังเมธอด `TFAutoModelForTokenClassification.from_pretrained()` ได้ และพวกมันจะถูกตั้งค่าในการกำหนดค่าของโมเดล จากนั้นจึงบันทึกและอัปโหลดไปยัง Hub อย่างเหมาะสม: + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +เช่นเดียวกับเมื่อเรากำหนด `TFAutoModelForSequenceClassification` ของเราใน [บทที่ 3](/course/th/chapter3) การสร้างแบบจำลองจะออกคำเตือนว่าน้ำหนัก (weight) บางอย่างไม่ได้ถูกใช้ (น้ำหนักจากส่วนหัวของการฝึกล่วงหน้า) และน้ำหนักอื่น ๆ บางส่วนจะถูกเตรียมใช้งานแบบสุ่ม (weight จากส่วนหัวการจัดประเภทโทเค็นใหม่) และโมเดลนี้ควรได้รับการฝึกอบรม เราจะดำเนินการดังกล่าวภายในไม่กี่นาที แต่ก่อนอื่น โปรดตรวจสอบอีกครั้งว่าโมเดลของเรามีจำนวนป้ายกำกับ (label) ที่ถูกต้อง: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ หากคุณมีโมเดลที่มีจำนวนป้ายกำกับไม่ถูกต้อง คุณจะได้รับข้อผิดพลาดที่ไม่ชัดเจนเมื่อเรียก `model.fit()` ในภายหลัง การแก้ไขข้อบกพร่องนี้อาจสร้างความรำคาญได้ ดังนั้นโปรดตรวจสอบให้แน่ใจว่าคุณมีป้ายกำกับถึงจำนวนที่คาดไว้ + + + +### การปรับแต่งโมเดล[[การปรับแต่งโมเดล]] + +ตอนนี้เราพร้อมที่จะฝึกโมเดลของเราแล้ว! เรายังมีงานดูแลบ้านอีกเล็กน้อยที่ต้องทำก่อน: เราควรเข้าสู่ระบบ Hugging Face และกำหนดไฮเปอร์พารามิเตอร์การฝึกอบรมของเรา หากคุณกำลังใช้งานโน้ตบุ๊ก มีฟังก์ชันอำนวยความสะดวกที่จะช่วยคุณในเรื่องนี้: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +นี่จะแสดงวิดเจ็ตที่คุณสามารถป้อนข้อมูลรับรองการเข้าสู่ระบบ Hugging Face ของคุณได้ + +หากคุณไม่ได้ทำงานในโน้ตบุ๊ก เพียงพิมพ์บรรทัดต่อไปนี้ในเทอร์มินัลของคุณ: + +```bash +huggingface-cli login +``` + +หลังจากเข้าสู่ระบบแล้ว เราก็สามารถเตรียมทุกอย่างที่จำเป็นเพื่อคอมไพล์โมเดลของเราได้ 🤗 Transformers มีฟังก์ชัน `create_optimizer()` ที่สะดวกสบาย ซึ่งจะให้เครื่องมือเพิ่มประสิทธิภาพ `AdamW` แก่คุณพร้อมการตั้งค่าที่เหมาะสมสำหรับการลดน้ำหนักและการลดอัตราการเรียนรู้ ซึ่งทั้งสองอย่างนี้จะช่วยปรับปรุงประสิทธิภาพของโมเดลของคุณเมื่อเทียบกับเครื่องมือเพิ่มประสิทธิภาพ `Adam` ในตัว : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Train in mixed-precision float16 +# Comment this line out if you're using a GPU that will not benefit from this +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# 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_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) +``` + +โปรดทราบว่าเราไม่ได้ระบุอาร์กิวเมนต์ `loss` ให้กับ `compile()` เนื่องจากแบบจำลองสามารถคำนวณการสูญเสียภายในได้จริง หากคุณคอมไพล์โดยไม่มีการสูญเสียและระบุป้ายกำกับของคุณในพจนานุกรมอินพุต (เช่นเดียวกับที่เราทำในชุดข้อมูลของเรา) แบบจำลองจะฝึกโดยใช้การสูญเสียภายในนั้น ซึ่งจะเหมาะสมสำหรับ งานและประเภทโมเดลที่คุณเลือก + +ต่อไป เราจะกำหนด `PushToHubCallback` เพื่ออัปโหลดโมเดลของเราไปยัง Hub ในระหว่างการฝึก และปรับโมเดลให้เข้ากับคอลแบ็กนั้น: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-ner", tokenizer=tokenizer) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +คุณสามารถระบุชื่อเต็มของพื้นที่เก็บข้อมูลที่คุณต้องการพุชด้วยอาร์กิวเมนต์ `hub_model_id` (โดยเฉพาะ คุณจะต้องใช้อาร์กิวเมนต์นี้เพื่อพุชไปยังองค์กร) ตัวอย่างเช่น เมื่อเราผลักดันโมเดลไปที่ [`huggingface-course` Organization](https://huggingface.co/huggingface-course) เราได้เพิ่ม `hub_model_id="huggingface-course/bert-finetuned-ner"` ตามค่าเริ่มต้น พื้นที่เก็บข้อมูลที่ใช้จะอยู่ในเนมสเปซของคุณและตั้งชื่อตามไดเร็กทอรีเอาต์พุตที่คุณตั้งค่าไว้ เช่น `"cool_huggingface_user/bert-finetuned-ner"` + + + +💡 หากไดเร็กทอรีเอาต์พุตที่คุณใช้มีอยู่แล้ว จะต้องเป็นโคลนในเครื่องของที่เก็บที่คุณต้องการพุชไป หากไม่เป็นเช่นนั้น คุณจะได้รับข้อผิดพลาดเมื่อเรียก `model.fit()` และจะต้องตั้งชื่อใหม่ + + + +โปรดทราบว่าในขณะที่การฝึกเกิดขึ้น แต่ละครั้งที่มีการบันทึกโมเดล (ที่นี่ ทุก epoch) โมเดลจะถูกอัปโหลดไปยัง Hub ในเบื้องหลัง ด้วยวิธีนี้ คุณจะสามารถกลับมาฝึกต่อในเครื่องอื่นได้หากจำเป็น + +ในขั้นตอนนี้ คุณสามารถใช้วิดเจ็ตการอนุมานบน Model Hub เพื่อทดสอบโมเดลของคุณและแบ่งปันกับเพื่อนๆ ของคุณได้ คุณได้ปรับแต่งโมเดลในงานจำแนกโทเค็นสำเร็จแล้ว ขอแสดงความยินดีด้วย! แต่โมเดลของเราดีจริงแค่ไหน? เราควรประเมินตัวชี้วัดบางอย่างต่อ + +{/if} + + +### เมตริก[[เมตริก]] + +{#if fw === 'pt'} + +หากต้องการให้ `Trainer` คำนวณเมตริกทุก epoch เราจะต้องกำหนดฟังก์ชัน `compute_metrics()` ที่จะรับอาร์เรย์ของการคาดคะเนและป้ายกำกับ แล้วส่งคืนพจนานุกรมพร้อมชื่อและค่าเมตริก + +เฟรมเวิร์คแบบดั้งเดิมที่ใช้ในการประเมินการทำนายการจัดหมวดหมู่โทเค็นคือ [*seqeval*](https://github.com/chakki-works/seqeval) หากต้องการใช้หน่วยวัดนี้ เราต้องติดตั้งไลบรารี *seqeval* ก่อน: + +```py +!pip install seqeval +``` + +จากนั้นเราสามารถโหลดมันผ่านฟังก์ชัน `evaluate.load()` เหมือนที่เราทำใน [บทที่ 3](/course/th/chapter3): + +{:else} + +เฟรมเวิร์คแบบดั้งเดิมที่ใช้ในการประเมินการทำนายการจัดหมวดหมู่โทเค็นคือ [*seqeval*](https://github.com/chakki-works/seqeval). หากต้องการใช้หน่วยวัดนี้ เราต้องติดตั้งไลบรารี *seqeval* ก่อน: + +```py +!pip install seqeval +``` + +จากนั้นเราสามารถโหลดมันผ่านฟังก์ชัน `evaluate.load()` เหมือนที่เราทำใน [บทที่ 3](/course/th/chapter3): + +{/if} + +```py +import evaluate + +metric = evaluate.load("seqeval") +``` + +เมตริกนี้ไม่ทำงานเหมือนความแม่นยำมาตรฐาน โดยจะใช้รายการป้ายกำกับเป็นสตริง ไม่ใช่จำนวนเต็ม ดังนั้นเราจะต้องถอดรหัสการคาดคะเนและป้ายกำกับทั้งหมดก่อนที่จะส่งต่อไปยังเมตริก มาดูกันว่ามันทำงานอย่างไร ขั้นแรก เราจะได้ป้ายกำกับสำหรับตัวอย่างการฝึกอบรมแรกของเรา: + +```py +labels = raw_datasets["train"][0]["ner_tags"] +labels = [label_names[i] for i in labels] +labels +``` + +```python out +['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O'] +``` + +จากนั้นเราสามารถสร้างการคาดการณ์ปลอมสำหรับสิ่งเหล่านั้นได้โดยเพียงแค่เปลี่ยนค่าที่ดัชนี 2: + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +โปรดทราบว่าเมตริกจะใช้รายการการคาดการณ์ (ไม่ใช่แค่รายการเดียว) และรายการป้ายกำกับ นี่คือผลลัพธ์: + +```python out +{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2}, + 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1}, + 'overall_precision': 1.0, + 'overall_recall': 0.67, + 'overall_f1': 0.8, + 'overall_accuracy': 0.89} +``` + +{#if fw === 'pt'} + +นี่เป็นการส่งข้อมูลกลับมาจำนวนมาก! เราได้รับความแม่นยำ การเรียกคืน (recall) และคะแนน F1 สำหรับแต่ละเอนทิตีที่แยกจากกัน รวมถึงโดยรวมด้วย สำหรับการคำนวณตัวชี้วัดของเรา เราจะเก็บเฉพาะคะแนนโดยรวมเท่านั้น แต่คุณสามารถปรับแต่งฟังก์ชัน `compute_metrics()` เพื่อส่งคืนตัวชี้วัดทั้งหมดที่คุณต้องการรายงาน + +ฟังก์ชัน `compute_metrics()` นี้จะนำ argmax ของ logits มาแปลงเป็นการคาดการณ์ (ตามปกติ logits และความน่าจะเป็นอยู่ในลำดับเดียวกัน ดังนั้นเราจึงไม่จำเป็นต้องใช้ softmax) จากนั้นเราจะต้องแปลงทั้งป้ายกำกับและการทำนายจากจำนวนเต็มเป็นสตริง เราจะลบค่าทั้งหมดที่มีป้ายกำกับเป็น `-100` จากนั้นส่งผลลัพธ์ไปยังเมธอด `metric.compute()`: + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Remove ignored index (special tokens) and convert to labels + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + all_metrics = metric.compute(predictions=true_predictions, references=true_labels) + return { + "precision": all_metrics["overall_precision"], + "recall": all_metrics["overall_recall"], + "f1": all_metrics["overall_f1"], + "accuracy": all_metrics["overall_accuracy"], + } +``` + +เมื่อเสร็จแล้ว เราก็เกือบจะพร้อมที่จะให้คำจำกัดความ `Trainer` ของเราแล้ว เราแค่ต้องการ `model` เพื่อปรับแต่ง! + +{:else} + +นี่เป็นการส่งข้อมูลกลับมาจำนวนมาก! เราได้รับความแม่นยำ การเรียกคืน (recall) และคะแนน F1 สำหรับแต่ละเอนทิตีที่แยกจากกัน รวมถึงโดยรวมด้วย ทีนี้มาดูว่าจะเกิดอะไรขึ้นถ้าเราลองใช้การทำนายแบบจำลองจริงเพื่อคำนวณคะแนนจริง + +TensorFlow ไม่ชอบการเชื่อมโยงการคาดการณ์ของเราเข้าด้วยกัน เนื่องจากมีความยาวลำดับที่แปรผันได้ ซึ่งหมายความว่าเราไม่สามารถใช้ `model.predict()` ได้เพียงอย่างเดียว -- แต่นั่นจะไม่หยุดเรา เราจะรับการคาดการณ์ทีละชุดและนำมาต่อกันเป็นรายการใหญ่ๆ รายการเดียวในระหว่างที่เราดำเนินการ โดยทิ้งโทเค็น `-100` ที่ระบุการมาสก์/การเติม จากนั้นจึงคำนวณเมตริกในรายการที่ตอนท้าย: + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict_on_batch(batch)["logits"] + labels = batch["labels"] + predictions = np.argmax(logits, axis=-1) + for prediction, label in zip(predictions, labels): + for predicted_idx, label_idx in zip(prediction, label): + if label_idx == -100: + continue + all_predictions.append(label_names[predicted_idx]) + all_labels.append(label_names[label_idx]) +metric.compute(predictions=[all_predictions], references=[all_labels]) +``` + + +```python out +{'LOC': {'precision': 0.91, 'recall': 0.92, 'f1': 0.91, 'number': 1668}, + 'MISC': {'precision': 0.70, 'recall': 0.79, 'f1': 0.74, 'number': 702}, + 'ORG': {'precision': 0.85, 'recall': 0.90, 'f1': 0.88, 'number': 1661}, + 'PER': {'precision': 0.95, 'recall': 0.95, 'f1': 0.95, 'number': 1617}, + 'overall_precision': 0.87, + 'overall_recall': 0.91, + 'overall_f1': 0.89, + 'overall_accuracy': 0.97} +``` + +โมเดลของคุณเป็นอย่างไรบ้าง เมื่อเทียบกับของเรา? หากคุณมีตัวเลขใกล้เคียงกัน แสดงว่าการฝึกของคุณสำเร็จ! + +{/if} + +{#if fw === 'pt'} + +### การกำหนดโมเดล[[การกำหนดโมเดล]] + +เนื่องจากเรากำลังแก้ไขปัญหาการจำแนกโทเค็น เราจะใช้คลาส `AutoModelForTokenClassification` สิ่งสำคัญที่ต้องจำเมื่อกำหนดโมเดลนี้คือการส่งข้อมูลบางอย่างเกี่ยวกับจำนวนป้ายกำกับที่เรามี วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการส่งผ่านตัวเลขนั้นด้วยอาร์กิวเมนต์ `num_labels` แต่หากเราต้องการให้วิดเจ็ตการอนุมานที่ดีทำงานเหมือนกับที่เราเห็นในตอนต้นของส่วนนี้ จะเป็นการดีกว่าถ้าตั้งค่าการโต้ตอบป้ายกำกับที่ถูกต้องแทน + +ควรกำหนดโดยพจนานุกรม 2 ฉบับ ได้แก่ `id2label` และ `label2id` ซึ่งมีการแมปจาก ID ไปยัง label และในทางกลับกัน: + +```py +id2label = {i: label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +ตอนนี้เราสามารถส่งต่อไปยังเมธอด `AutoModelForTokenClassification.from_pretrained()` ได้ และพวกมันจะถูกตั้งค่าในการกำหนดค่าของโมเดล จากนั้นจึงบันทึกและอัปโหลดไปยัง Hub อย่างเหมาะสม: + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +เช่นเดียวกับเมื่อเรากำหนด `AutoModelForSequenceClassification` ของเราใน [บทที่ 3](/course/th/chapter3) การสร้างแบบจำลองจะออกคำเตือนว่าน้ำหนักบางอย่างไม่ได้ถูกใช้ (น้ำหนักจากส่วนหัวของการฝึกล่วงหน้า) และน้ำหนักอื่นๆ บางส่วนจะถูกเตรียมใช้งานแบบสุ่ม (น้ำหนักนั้น จากส่วนหัวการจัดประเภทโทเค็นใหม่) และโมเดลนี้ควรได้รับการฝึกอบรม เราจะดำเนินการดังกล่าวภายในไม่กี่นาที แต่ก่อนอื่น โปรดตรวจสอบอีกครั้งว่าโมเดลของเรามีจำนวนป้ายกำกับที่ถูกต้อง: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ หากคุณมีโมเดลที่มีจำนวนป้ายกำกับไม่ถูกต้อง คุณจะได้รับข้อผิดพลาดที่ไม่ชัดเจนเมื่อเรียกใช้เมธอด `Trainer.train()` ในภายหลัง (บางอย่างเช่น "CUDA error: device-side assert triggered") นี่คือสาเหตุอันดับหนึ่งของข้อบกพร่องที่ผู้ใช้รายงานเกี่ยวกับข้อผิดพลาดดังกล่าว ดังนั้นโปรดตรวจสอบให้แน่ใจว่าคุณได้ตรวจสอบนี้เพื่อยืนยันว่าคุณมีป้ายกำกับตามจำนวนที่คาดไว้ + + + +### การปรับแต่งโมเดล[[การปรับแต่งโมเดล]] + +ตอนนี้เราพร้อมที่จะฝึกโมเดลของเราแล้ว! เราเพียงแค่ต้องทำสองสิ่งสุดท้ายก่อนที่เราจะกำหนด `Trainer` ของเรา: เข้าสู่ระบบ Hugging Face และกำหนดข้อโต้แย้งในการฝึกของเรา หากคุณกำลังใช้งานโน้ตบุ๊ก มีฟังก์ชันอำนวยความสะดวกที่จะช่วยคุณในเรื่องนี้: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +นี่จะแสดงวิดเจ็ตที่คุณสามารถป้อนข้อมูลรับรองการเข้าสู่ระบบ Hugging Face ของคุณได้ + +หากคุณไม่ได้ทำงานในโน้ตบุ๊ก เพียงพิมพ์บรรทัดต่อไปนี้ในเทอร์มินัลของคุณ: + +```bash +huggingface-cli login +``` + +เมื่อเสร็จแล้ว เราก็สามารถกำหนด `TrainingArguments` ของเราได้: + +```python +from transformers import TrainingArguments + +args = TrainingArguments( + "bert-finetuned-ner", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, + push_to_hub=True, +) +``` + +คุณเคยเห็นสิ่งเหล่านี้มาแล้วส่วนใหญ่: เราตั้งค่าไฮเปอร์พารามิเตอร์บางอย่าง (เช่น อัตราการเรียนรู้ จำนวน epoch ที่จะฝึก และการลดของน้ำหนัก) และเราระบุ `push_to_hub=True` เพื่อระบุว่าเราต้องการบันทึกโมเดล และประเมินผลในตอนท้ายของทุก epoch และเราต้องการอัปโหลดผลลัพธ์ของเราไปยัง Model Hub โปรดทราบว่าคุณสามารถระบุชื่อของพื้นที่เก็บข้อมูลที่คุณต้องการพุชไปได้ด้วยอาร์กิวเมนต์ `hub_model_id` (โดยเฉพาะ คุณจะต้องใช้อาร์กิวเมนต์นี้เพื่อพุชไปยังองค์กร) ตัวอย่างเช่น เมื่อเราผลักโมเดลไปที่ [`huggingface-course` Organization](https://huggingface.co/huggingface-course) เราได้เพิ่ม `hub_model_id="huggingface-course/bert-finetuned-ner"` ลงใน `TrainingArguments` ตามค่าเริ่มต้น พื้นที่เก็บข้อมูลที่ใช้จะอยู่ในเนมสเปซของคุณและตั้งชื่อตามไดเร็กทอรีเอาต์พุตที่คุณตั้งค่า ดังนั้นในกรณีของเราจะเป็น `"sgugger/bert-finetuned-ner"` + + + +💡 หากไดเร็กทอรีเอาต์พุตที่คุณใช้มีอยู่แล้ว จะต้องเป็นโคลนในเครื่องของที่เก็บที่คุณต้องการพุชไป หากไม่เป็นเช่นนั้น คุณจะได้รับข้อผิดพลาดเมื่อกำหนด `Trainer` ของคุณและจะต้องตั้งชื่อใหม่ + + + +ในที่สุด เราก็ส่งทุกอย่างให้กับ `Trainer` และเริ่มต้นการฝึกอบรม: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + compute_metrics=compute_metrics, + tokenizer=tokenizer, +) +trainer.train() +``` + +โปรดทราบว่าในขณะที่การฝึกเกิดขึ้น แต่ละครั้งที่มีการบันทึกโมเดล (ที่นี่ ทุก epoch) โมเดลจะถูกอัปโหลดไปยัง Hub ในเบื้องหลัง ด้วยวิธีนี้ คุณจะสามารถกลับมาฝึกต่อในเครื่องอื่นได้หากจำเป็น + +เมื่อการฝึกอบรมเสร็จสิ้น เราจะใช้เมธอด `push_to_hub()` เพื่อให้แน่ใจว่าเราจะอัปโหลดโมเดลเวอร์ชันล่าสุด: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +คำสั่งนี้จะส่งคืน URL ของการคอมมิตที่เพิ่งทำไป หากคุณต้องการตรวจสอบ: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +`Trainer` ยังร่างการ์ดโมเดลพร้อมผลการประเมินทั้งหมดแล้วอัปโหลด ในขั้นตอนนี้ คุณสามารถใช้วิดเจ็ตการอนุมานบน Model Hub เพื่อทดสอบโมเดลของคุณและแบ่งปันกับเพื่อนๆ ของคุณได้ คุณได้ปรับแต่งโมเดลในงานจำแนกโทเค็นสำเร็จแล้ว ขอแสดงความยินดีด้วย! + +หากคุณต้องการเจาะลึกลงไปในวงจรการฝึกซ้อมอีกสักหน่อย ตอนนี้เราจะแสดงให้คุณเห็นถึงวิธีการทำสิ่งเดียวกันโดยใช้ 🤗 Accelerate + +## การกำหนดเทร็นนิ่งลูปเฉพาะ[[การกำหนดเทร็นนิ่งลูปเฉพาะ]] + +ตอนนี้เรามาดูวงจรการฝึกซ้อมทั้งหมดกัน เพื่อให้คุณปรับแต่งส่วนต่างๆ ที่ต้องการได้อย่างง่ายดาย มันจะดูเหมือนกับสิ่งที่เราทำใน [บทที่ 3](/course/th/chapter3/4) มาก โดยมีการเปลี่ยนแปลงเล็กน้อยสำหรับการประเมิน + +### เตรียมทุกอย่างเพื่อการฝึก[[เตรียมทุกอย่างเพื่อการฝึก]] + +ก่อนอื่นเราต้องสร้าง `DataLoader`s จากชุดข้อมูลของเรา เราจะใช้ `data_collator` ของเราซ้ำเป็น `collate_fn` และสับเปลี่ยนชุดการฝึก แต่ไม่ใช่ชุดการตรวจสอบ: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +ต่อไป เราจะสร้างโมเดลของเราขึ้นมาใหม่ เพื่อให้แน่ใจว่าเราจะไม่ทำการปรับแต่งแบบละเอียดจากเมื่อก่อนต่อไป แต่เริ่มต้นจากโมเดล BERT ที่ได้รับการฝึกไว้ล่วงหน้าอีกครั้ง: + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +จากนั้นเราจะต้องมีเครื่องมือเพิ่มประสิทธิภาพ เราจะใช้ `AdamW` แบบคลาสสิกซึ่งคล้ายกับ 'Adam` แต่มีการแก้ไขวิธีการลดน้ำหนัก: + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +เมื่อเรามีอ็อบเจ็กต์ทั้งหมดแล้ว เราก็สามารถส่งมันไปที่เมธอด `accelerator.prepare()` ได้: + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 หากคุณกำลังฝึกบน TPU คุณจะต้องย้ายโค้ดทั้งหมดที่เริ่มต้นจากเซลล์ด้านบนไปยังฟังก์ชันการฝึกเฉพาะ ดู [บทที่ 3](/course/th/chapter3) สำหรับรายละเอียดเพิ่มเติม + + + +ตอนนี้เราได้ส่ง `train_dataloader` ไปที่ `accelerator.prepare()` แล้ว เราสามารถใช้ความยาวของมันเพื่อคำนวณจำนวนขั้นตอนการฝึกได้ โปรดจำไว้ว่าเราควรทำเช่นนี้เสมอหลังจากเตรียม dataloader เนื่องจากวิธีการนั้นจะเปลี่ยนความยาวของมัน เราใช้กำหนดการเชิงเส้นแบบคลาสสิกจากอัตราการเรียนรู้ถึง 0: + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +สุดท้ายนี้ ในการผลักดันโมเดลของเราไปที่ Hub เราจะต้องสร้างออบเจ็กต์ `Repository` ในโฟลเดอร์ที่ใช้งานได้ ขั้นแรกให้เข้าสู่ระบบ Hugging Face หากคุณยังไม่ได้เข้าสู่ระบบ เราจะกำหนดชื่อที่เก็บจาก ID โมเดลที่เราต้องการให้กับโมเดลของเรา (อย่าลังเลที่จะแทนที่ `repo_name` ด้วยตัวเลือกของคุณเอง เพียงต้องมีชื่อผู้ใช้ของคุณ ซึ่งเป็นสิ่งที่ฟังก์ชัน `get_full_repo_name()` ทำ ): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "bert-finetuned-ner-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/bert-finetuned-ner-accelerate' +``` + +จากนั้นเราสามารถโคลนพื้นที่เก็บข้อมูลนั้นในโฟลเดอร์ในเครื่องได้ หากมีอยู่แล้ว โฟลเดอร์ในเครื่องนี้ควรเป็นโคลนของพื้นที่เก็บข้อมูลที่เรากำลังทำงานด้วย: + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +ตอนนี้เราสามารถอัปโหลดทุกสิ่งที่เราบันทึกไว้ใน `output_dir` ได้โดยการเรียกเมธอด `repo.push_to_hub()` ซึ่งจะช่วยให้เราอัปโหลดโมเดลระดับกลางในตอนท้ายของแต่ละ epoch ได้ + +### ลูปการฝึกอบรม[[ลูปการฝึกอบรม]] + +ตอนนี้เราพร้อมที่จะเขียนลูปการฝึกอบรม (training loop) ฉบับเต็มแล้ว เพื่อให้ส่วนการประเมินง่ายขึ้น เราได้กำหนดฟังก์ชัน `postprocess()` นี้ ซึ่งใช้การคาดการณ์และป้ายกำกับและแปลงเป็นรายการสตริง เหมือนกับที่วัตถุ `เมตริก` ของเราคาดหวัง: + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Remove ignored index (special tokens) and convert to labels + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + return true_labels, true_predictions +``` + +จากนั้นเราก็สามารถเขียนลูปการฝึกอบรมได้ หลังจากกำหนดแถบความคืบหน้าเพื่อติดตามว่าการฝึกดำเนินไปอย่างไร ลูปจะมีสามส่วน: + +- การฝึกในตัวเอง ซึ่งเป็นการวนซ้ำแบบคลาสสิกบน `train_dataloader` คือการส่งต่อผ่านโมเดล จากนั้นย้อนกลับและขั้นตอนการเพิ่มประสิทธิภาพ +- การประเมิน ซึ่งมีความแปลกใหม่หลังจากได้รับผลลัพธ์ของแบบจำลองของเราเป็นชุด: เนื่องจากกระบวนการสองกระบวนการอาจมีการเสริมอินพุตและป้ายกำกับเป็นรูปร่างที่แตกต่างกัน เราจึงจำเป็นต้องใช้ `accelerator.pad_across_processes()` เพื่อทำการคาดการณ์และ ติดป้ายกำกับรูปร่างเดียวกันก่อนที่จะเรียกเมธอด `gather()` หากเราไม่ทำเช่นนี้ การประเมินจะเกิดข้อผิดพลาดหรือหยุดทำงานตลอดไป จากนั้นเราจะส่งผลลัพธ์ไปที่ `metric.add_batch()` และเรียก `metric.compute()` เมื่อลูปการประเมินสิ้นสุดลง +- การบันทึกและการอัปโหลด โดยที่เราจะบันทึกโมเดลและโทเค็นไนเซอร์ก่อน จากนั้นจึงเรียก `repo.push_to_hub()` โปรดสังเกตว่าเราใช้อาร์กิวเมนต์ `blocking=False` เพื่อบอกไลบรารี 🤗 Hub ให้พุชในกระบวนการอะซิงโครนัส ด้วยวิธีนี้ การฝึกอบรมจะดำเนินต่อไปตามปกติและคำสั่ง (แบบยาว) นี้จะดำเนินการในเบื้องหลัง + +นี่คือโค้ดที่สมบูรณ์สำหรับลูปการฝึกอบรม: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Necessary to pad predictions and labels for being gathered + predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(predictions) + labels_gathered = accelerator.gather(labels) + + true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=true_predictions, references=true_labels) + + results = metric.compute() + print( + f"epoch {epoch}:", + { + key: results[f"overall_{key}"] + for key in ["precision", "recall", "f1", "accuracy"] + }, + ) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +ในกรณีที่นี่เป็นครั้งแรกที่คุณเห็นโมเดลที่บันทึกไว้ด้วย 🤗 Accelerate ลองใช้เวลาสักครู่เพื่อตรวจสอบโค้ดสามบรรทัดที่มาพร้อมกับโมเดลนั้น: + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +บรรทัดแรกอธิบายในตัวมันเอง: มันบอกกระบวนการทั้งหมดให้รอจนกว่าทุกคนจะถึงขั้นตอนนั้นก่อนจะดำเนินการต่อ เพื่อให้แน่ใจว่าเรามีโมเดลเดียวกันในทุกกระบวนการก่อนที่จะบันทึก จากนั้นเราก็คว้า `unwrapped_model` ซึ่งเป็นโมเดลพื้นฐานที่เรากำหนดไว้ เมธอด `accelerator.prepare()` เปลี่ยนโมเดลให้ทำงานในการฝึกแบบกระจาย ดังนั้นจะไม่มีเมธอด `save_pretrained()` อีกต่อไป เมธอด `accelerator.unwrap_model()` จะยกเลิกขั้นตอนนั้น สุดท้ายนี้ เราเรียก `save_pretrained()` แต่บอกวิธีการนั้นให้ใช้ `accelerator.save()` แทน `torch.save()` + +เมื่อเสร็จแล้ว คุณควรมีโมเดลที่สร้างผลลัพธ์ที่ค่อนข้างคล้ายกับโมเดลที่ได้รับการฝึกกับ `Trainer` คุณสามารถตรวจสอบโมเดลที่เราฝึกได้โดยใช้โค้ดนี้ที่ [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate) และหากคุณต้องการทดสอบการปรับแต่งใดๆ ในลูปการฝึก คุณสามารถนำไปใช้ได้โดยตรงโดยแก้ไขโค้ดที่แสดงด้านบน! + +{/if} + +## การใช้โมเดลที่ปรับแต่งแล้ว[[การใช้โมเดลที่ปรับแต่งแล้ว]] + +เราได้แสดงให้คุณเห็นแล้วว่าคุณสามารถใช้โมเดลที่เราปรับแต่งอย่างละเอียดบน Model Hub ด้วยวิดเจ็ตการอนุมานได้อย่างไร หากต้องการใช้ภายในเครื่องใน `pipeline` คุณเพียงแค่ต้องระบุตัวระบุโมเดลที่เหมาะสม: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +model_checkpoint = "huggingface-course/bert-finetuned-ner" +token_classifier = pipeline( + "token-classification", model=model_checkpoint, aggregation_strategy="simple" +) +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +ยอดเยี่ยม! โมเดลของเราใช้งานได้เช่นเดียวกับโมเดลเริ่มต้นสำหรับไปป์ไลน์นี้ diff --git a/chapters/th/chapter7/3.mdx b/chapters/th/chapter7/3.mdx new file mode 100644 index 000000000..09f657bfb --- /dev/null +++ b/chapters/th/chapter7/3.mdx @@ -0,0 +1,1045 @@ + + +# การปรับแต่งโมเดลภาษา[[การปรับแต่งโมเดลภาษา]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +สำหรับแอปพลิเคชัน NLP จำนวนมากที่เกี่ยวข้องกับโมเดล Transformer คุณสามารถใช้โมเดลที่ได้รับการฝึกล่วงหน้าจาก Hugging Face Hub และปรับแต่งข้อมูลของคุณโดยตรงสำหรับงานที่มีอยู่ โดยมีเงื่อนไขว่าคลังข้อมูลที่ใช้สำหรับการฝึกล่วงหน้า (pretraining) ไม่แตกต่างจากคลังข้อมูลที่ใช้ในการปรับแต่งอย่างละเอียดมากนัก การเรียนรู้แบบถ่ายโอน (transfer learning) มักจะให้ผลลัพธ์ที่ดี + +อย่างไรก็ตาม มีบางกรณีที่คุณจะต้องปรับแต่งโมเดลภาษาในข้อมูลของคุณก่อน ก่อนที่จะฝึกหัวหน้างานเฉพาะด้าน ตัวอย่างเช่น หากชุดข้อมูลของคุณมีสัญญาทางกฎหมายหรือบทความทางวิทยาศาสตร์ โมเดล Transformer ธรรมดา เช่น BERT โดยทั่วไปจะถือว่าคำเฉพาะโดเมนในคลังข้อมูลของคุณเป็นโทเค็นที่หายาก และประสิทธิภาพผลลัพธ์ที่ได้อาจน้อยกว่าที่น่าพอใจ ด้วยการปรับแต่งโมเดลภาษาบนข้อมูลในโดเมน คุณจะเพิ่มประสิทธิภาพของงานดาวน์สตรีมหลายๆ งานได้ ซึ่งหมายความว่าโดยปกติคุณจะต้องทำขั้นตอนนี้เพียงครั้งเดียวเท่านั้น! + +กระบวนการปรับแต่งโมเดลภาษาที่ได้รับการฝึกล่วงหน้าอย่างละเอียดในข้อมูลในโดเมนนี้มักจะเรียกว่า _domain adaptation_ ได้รับความนิยมในปี 2018 โดย [ULMFiT](https://arxiv.org/abs/1801.06146) ซึ่งเป็นหนึ่งในสถาปัตยกรรมประสาทแรกๆ (อิงจาก LSTM) ที่ทำให้การเรียนรู้แบบถ่ายโอนใช้งานได้จริงสำหรับ NLP ตัวอย่างการปรับโดเมนด้วย ULMFiT แสดงอยู่ในภาพด้านล่าง ในส่วนนี้ เราจะทำสิ่งที่คล้ายกัน แต่ใช้ Transformer แทน LSTM! + +
+ULMFiT. + +
+ +ในตอนท้ายของส่วนนี้ คุณจะมี[โมเดลภาษาที่ปกปิด](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK %5D.) บน Hub ที่สามารถเติมประโยคอัตโนมัติได้ดังที่แสดงด้านล่าง: + + + +มาเริ่มเรียนรู้กันเถอะ! + + + + + +🙋 หากคำว่า "masked language modeling" และ "pretrained model" ฟังดูไม่คุ้นเคยสำหรับคุณ โปรดดู [บทที่ 1](/course/th/chapter1) ซึ่งเราจะอธิบายแนวคิดหลักเหล่านี้ทั้งหมดพร้อมวิดีโอ + + + +## การเลือกโมเดลที่ได้รับการฝึกอบรมมาล่วงหน้าสำหรับการสร้างแบบจำลองภาษา[[การเลือกโมเดลที่ได้รับการฝึกอบรมมาล่วงหน้าสำหรับการสร้างแบบจำลองภาษา]] + +ในการเริ่มต้น เรามาเลือกโมเดลที่ได้รับการฝึกล่วงหน้าที่เหมาะสมสำหรับการสร้างแบบจำลองภาษา ดังที่แสดงในภาพหน้าจอต่อไปนี้ คุณสามารถค้นหารายชื่อผู้สมัครได้โดยใช้ตัวกรอง "Fill-Mask" บน [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads): + +
+Hub models. +
+ +แม้ว่าโมเดลตระกูล BERT และ RoBERTa จะมีการดาวน์โหลดมากที่สุด แต่เราจะใช้โมเดลชื่อ [DistilBERT](https://huggingface.co/distilbert-base-uncased) +ที่สามารถฝึกได้เร็วกว่ามากโดยไม่สูญเสียประสิทธิภาพดาวน์สตรีมเพียงเล็กน้อยหรือไม่มีเลย โมเดลนี้ได้รับการฝึกอบรมโดยใช้เทคนิคพิเศษที่เรียกว่า [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation) โดยใช้ "โมเดลครู" ขนาดใหญ่อย่าง BERT เพื่อเป็นแนวทางในการฝึกอบรม "โมเดลนักเรียน" ซึ่งมีพารามิเตอร์น้อยกว่ามาก คำอธิบายรายละเอียดการกลั่นกรองความรู้อาจพาเราไปไกลเกินไปในส่วนนี้ แต่หากคุณสนใจ คุณสามารถอ่านรายละเอียดทั้งหมดได้ใน [_Natural Language Processing with Transformers_](https://www.oreilly.com/library /view/natural-Language-processing/9781098136789/) (เรียกขานว่าหนังสือเรียน Transformers) + +{#if fw === 'pt'} + +ไปดาวน์โหลด DistilBERT โดยใช้คลาส `AutoModelForMaskedLM` กันเลย: + +```python +from transformers import AutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +เราสามารถดูว่าโมเดลนี้มีกี่พารามิเตอร์ได้โดยการเรียกเมธอด `num_parameters()`: + +```python +distilbert_num_parameters = model.num_parameters() / 1_000_000 +print(f"'>>> DistilBERT number of parameters: {round(distilbert_num_parameters)}M'") +print(f"'>>> BERT number of parameters: 110M'") +``` + +```python out +'>>> DistilBERT number of parameters: 67M' +'>>> BERT number of parameters: 110M' +``` + +{:else} + +ไปดาวน์โหลด DistilBERT โดยใช้คลาส `AutoModelForMaskedLM` กันเลย: + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +เราสามารถดูว่าโมเดลนี้มีกี่พารามิเตอร์ได้โดยการเรียกเมธอด `summary()`: + +```python +model.summary() +``` + +```python out +Model: "tf_distil_bert_for_masked_lm" +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +distilbert (TFDistilBertMain multiple 66362880 +_________________________________________________________________ +vocab_transform (Dense) multiple 590592 +_________________________________________________________________ +vocab_layer_norm (LayerNorma multiple 1536 +_________________________________________________________________ +vocab_projector (TFDistilBer multiple 23866170 +================================================================= +Total params: 66,985,530 +Trainable params: 66,985,530 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +ด้วยพารามิเตอร์ประมาณ 67 ล้านตัว DistilBERT จึงมีขนาดเล็กกว่ารุ่นพื้นฐานของ BERT ประมาณสองเท่า ซึ่งแปลคร่าวๆ ได้ว่าเป็นการเร่งความเร็วสองเท่าในการฝึกฝน ดีมาก! ตอนนี้เรามาดูกันว่าโทเค็นประเภทใดที่แบบจำลองนี้ทำนายว่ามีแนวโน้มมากที่สุดที่จะเสร็จสิ้นจากตัวอย่างข้อความขนาดเล็ก: + +```python +text = "This is a great [MASK]." +``` + +ในฐานะมนุษย์ เราสามารถจินตนาการถึงความเป็นไปได้มากมายสำหรับโทเค็น `[MASK]` เช่น "day" "ride" หรือ "painting" สำหรับโมเดลที่ได้รับการฝึกล่วงหน้า การคาดการณ์จะขึ้นอยู่กับคลังข้อมูลของโมเดลที่ได้รับการฝึก เนื่องจากโมเดลจะเรียนรู้ที่จะรับรูปแบบทางสถิติที่มีอยู่ในข้อมูล เช่นเดียวกับ BERT DitilBERT ได้รับการฝึกอบรมเกี่ยวกับชุดข้อมูล [English Wikipedia](https://huggingface.co/datasets/wikipedia) และ [BookCorpus](https://huggingface.co/datasets/bookcorpus) ดังนั้นเราจึงคาดหวังการคาดการณ์สำหรับ `[MASK]` เพื่อสะท้อนถึงโดเมนเหล่านี้ เพื่อทำนายมาสก์ เราต้องใช้โทเค็นของ DistilBERT เพื่อสร้างอินพุตสำหรับโมเดล ดังนั้นมาดาวน์โหลดจาก Hub กัน: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +ด้วยโทเค็นไนเซอร์และโมเดล ขณะนี้เราสามารถส่งตัวอย่างข้อความของเราไปยังโมเดล, logits และพิมพ์ตัวเลือก 5 อันดับแรก: + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Find the location of [MASK] and extract its logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist() + +for token in top_5_tokens: + print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'") +``` + +{:else} + +```python +import numpy as np +import tensorflow as tf + +inputs = tokenizer(text, return_tensors="np") +token_logits = model(**inputs).logits +# Find the location of [MASK] and extract its logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +# We negate the array before argsort to get the largest, not the smallest, logits +top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist() + +for token in top_5_tokens: + print(f">>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}") +``` + +{/if} + +```python out +'>>> This is a great deal.' +'>>> This is a great success.' +'>>> This is a great adventure.' +'>>> This is a great idea.' +'>>> This is a great feat.' +``` + +จากผลลัพธ์ เราจะเห็นได้ว่าการคาดการณ์ของแบบจำลองนั้นอ้างอิงถึงคำศัพท์ในชีวิตประจำวัน ซึ่งอาจไม่น่าแปลกใจเมื่อพิจารณาจากพื้นฐานของวิกิพีเดียภาษาอังกฤษ มาดูกันว่าเราจะเปลี่ยนโดเมนนี้ให้มีลักษณะเฉพาะมากขึ้นได้อย่างไร -- บทวิจารณ์ภาพยนตร์ที่มีการแบ่งขั้วอย่างมาก! + + +## ชุดข้อมูล[[ชุดข้อมูล]] + +เพื่อแสดงการปรับเปลี่ยนโดเมน เราจะใช้ [ชุดข้อมูลบทวิจารณ์ภาพยนตร์ขนาดใหญ่](https://huggingface.co/datasets/imdb) (หรือเรียกสั้น ๆ ว่า IMDb) ซึ่งเป็นคลังบทวิจารณ์ภาพยนตร์ที่มักใช้ในการเปรียบเทียบ แบบจำลองการวิเคราะห์ความรู้สึก ด้วยการปรับแต่ง DistilBERT อย่างละเอียดในคลังข้อมูลนี้ เราคาดหวังว่าโมเดลภาษาจะปรับคำศัพท์จากข้อมูลข้อเท็จจริงของวิกิพีเดียที่ได้รับการฝึกฝนมาล่วงหน้ากับองค์ประกอบที่เป็นอัตวิสัยมากขึ้นของบทวิจารณ์ภาพยนตร์ เราสามารถรับข้อมูลจาก Hugging Face Hub ด้วยฟังก์ชัน `load_dataset()` จาก 🤗 Datasets: + +```python +from datasets import load_dataset + +imdb_dataset = load_dataset("imdb") +imdb_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + test: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['text', 'label'], + num_rows: 50000 + }) +}) +``` + +เราจะเห็นว่าการแยก `train` และ `test` แต่ละรายการประกอบด้วยบทวิจารณ์ 25,000 รายการ ในขณะที่มีการแบ่งแยกที่ไม่มีป้ายกำกับเรียกว่า `unsupervised` ซึ่งมีบทวิจารณ์ 50,000 รายการ ลองมาดูตัวอย่างบางส่วนเพื่อดูว่าเรากำลังจัดการกับข้อความประเภทใด ดังที่เราได้ทำไปแล้วในบทก่อนๆ ของหลักสูตร เราจะเชื่อมโยงฟังก์ชัน `Dataset.shuffle()` และ `Dataset.select()` เพื่อสร้างตัวอย่างแบบสุ่ม: + +```python +sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) + +for row in sample: + print(f"\n'>>> Review: {row['text']}'") + print(f"'>>> Label: {row['label']}'") +``` + +```python out + +'>>> Review: This is your typical Priyadarshan movie--a bunch of loony characters out on some silly mission. His signature climax has the entire cast of the film coming together and fighting each other in some crazy moshpit over hidden money. Whether it is a winning lottery ticket in Malamaal Weekly, black money in Hera Pheri, "kodokoo" in Phir Hera Pheri, etc., etc., the director is becoming ridiculously predictable. Don\'t get me wrong; as clichéd and preposterous his movies may be, I usually end up enjoying the comedy. However, in most his previous movies there has actually been some good humor, (Hungama and Hera Pheri being noteworthy ones). Now, the hilarity of his films is fading as he is using the same formula over and over again.

Songs are good. Tanushree Datta looks awesome. Rajpal Yadav is irritating, and Tusshar is not a whole lot better. Kunal Khemu is OK, and Sharman Joshi is the best.' +'>>> Label: 0' + +'>>> Review: Okay, the story makes no sense, the characters lack any dimensionally, the best dialogue is ad-libs about the low quality of movie, the cinematography is dismal, and only editing saves a bit of the muddle, but Sam" Peckinpah directed the film. Somehow, his direction is not enough. For those who appreciate Peckinpah and his great work, this movie is a disappointment. Even a great cast cannot redeem the time the viewer wastes with this minimal effort.

The proper response to the movie is the contempt that the director San Peckinpah, James Caan, Robert Duvall, Burt Young, Bo Hopkins, Arthur Hill, and even Gig Young bring to their work. Watch the great Peckinpah films. Skip this mess.' +'>>> Label: 0' + +'>>> Review: I saw this movie at the theaters when I was about 6 or 7 years old. I loved it then, and have recently come to own a VHS version.

My 4 and 6 year old children love this movie and have been asking again and again to watch it.

I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.

I do not have older children so I do not know what they would think of it.

The songs are very cute. My daughter keeps singing them over and over.

Hope this helps.' +'>>> Label: 1' +``` + +ใช่แล้ว นี่คือบทวิจารณ์ภาพยนตร์อย่างแน่นอน และหากคุณอายุมากพอ คุณอาจจะเข้าใจความคิดเห็นในรีวิวที่แล้วเกี่ยวกับการเป็นเจ้าของเวอร์ชัน VHS 😜! แม้ว่าเราจะไม่จำเป็นต้องมีป้ายกำกับสำหรับการสร้างแบบจำลองภาษา แต่เราก็เห็นแล้วว่า `0` หมายถึงบทวิจารณ์เชิงลบ ในขณะที่ `1` สอดคล้องกับบทวิจารณ์เชิงบวก + + + +✏️ **ลองเลย!** สร้างตัวอย่างแบบสุ่มของการแยก `unsupervised` และตรวจสอบว่าป้ายกำกับไม่ใช่ `0` หรือ `1` ขณะที่คุณดำเนินการอยู่ คุณสามารถตรวจสอบได้ว่าป้ายกำกับในการแบ่ง `train` และ `test` นั้นเป็น `0` หรือ `1` จริงๆ นี่เป็นการตรวจสอบสุขภาพที่เป็นประโยชน์ที่ผู้ปฏิบัติงาน NLP ทุกคนควรทำตั้งแต่เริ่มต้น ของโปรเจ็กต์ใหม่! + + + +ตอนนี้เราได้ดูข้อมูลโดยสรุปแล้ว เรามาเจาะลึกในการเตรียมข้อมูลสำหรับการสร้างแบบจำลองภาษาที่ปกปิดกัน ดังที่เราจะได้เห็น มีขั้นตอนเพิ่มเติมบางอย่างที่ต้องทำเปรียบเทียบกับงานการจำแนกลำดับที่เราเห็นใน [บทที่ 3](/course/th/chapter3) ไปกันเถอะ! + +## การประมวลผลข้อมูลล่วงหน้า[[การประมวลผลข้อมูลล่วงหน้า]] + + + +สำหรับการสร้างแบบจำลองภาษาแบบถดถอยอัตโนมัติและแบบมาสก์ ขั้นตอนก่อนการประมวลผลทั่วไปคือการต่อตัวอย่างทั้งหมดเข้าด้วยกัน จากนั้นแยกคลังข้อมูลทั้งหมดออกเป็นชิ้นที่มีขนาดเท่ากัน สิ่งนี้ค่อนข้างแตกต่างจากแนวทางปกติของเรา โดยที่เราเพียงแต่สร้างโทเค็นตัวอย่างแต่ละรายการ ทำไมต้องเชื่อมทุกอย่างเข้าด้วยกัน? เหตุผลก็คือแต่ละตัวอย่างอาจถูกตัดทอนหากยาวเกินไป และอาจส่งผลให้สูญเสียข้อมูลที่อาจเป็นประโยชน์สำหรับงานการสร้างแบบจำลองภาษา! + +ในการเริ่มต้น ก่อนอื่นเราจะโทเค็นคลังข้อมูลของเราตามปกติ แต่ `ไม่มี`_ การตั้งค่าตัวเลือก `truncation=True` ในโทเค็นของเรา นอกจากนี้เรายังจะคว้ารหัสคำหากมี ((ซึ่งจะเป็นหากเราใช้โทเค็นไนเซอร์แบบเร็ว ดังที่อธิบายไว้ใน [บทที่ 6](/course/th/chapter6/3)) เนื่องจากเราต้องการในภายหลัง เพื่อปิดบังคำทั้งหมด เราจะสรุปสิ่งนี้ด้วยฟังก์ชันง่ายๆ และในขณะที่เรากำลังดำเนินการอยู่ เราจะลบคอลัมน์ `text` และ `label` เนื่องจากเราไม่ต้องการมันอีกต่อไปแล้ว: + +```python +def tokenize_function(examples): + result = tokenizer(examples["text"]) + if tokenizer.is_fast: + result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))] + return result + + +# Use batched=True to activate fast multithreading! +tokenized_datasets = imdb_dataset.map( + tokenize_function, batched=True, remove_columns=["text", "label"] +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 50000 + }) +}) +``` + +เนื่องจาก DistilBERT เป็นโมเดลที่คล้ายกับ BERT เราจะเห็นได้ว่าข้อความที่เข้ารหัสประกอบด้วย `input_ids` และ `attention_mask` ที่เราเคยเห็นในบทอื่นๆ เช่นเดียวกับ `word_ids` ที่เราเพิ่มเข้าไป + +ตอนนี้เราได้โทเค็นบทวิจารณ์ภาพยนตร์ของเราแล้ว ขั้นตอนต่อไปคือการจัดกลุ่มบทวิจารณ์ทั้งหมดเข้าด้วยกันและแบ่งผลลัพธ์ออกเป็นชิ้น ๆ แต่ชิ้นเหล่านี้ควรใหญ่แค่ไหน? ในที่สุดสิ่งนี้จะถูกกำหนดโดยจำนวนหน่วยความจำ GPU ที่คุณมีอยู่ แต่จุดเริ่มต้นที่ดีคือการดูว่าขนาดบริบทสูงสุดของโมเดลคือเท่าใด สามารถอนุมานได้โดยการตรวจสอบแอตทริบิวต์ `model_max_length` ของโทเค็น: + +```python +tokenizer.model_max_length +``` + +```python out +512 +``` + +ค่านี้ได้มาจากไฟล์ *tokenizer_config.json* ที่เกี่ยวข้องกับจุดตรวจสอบ ในกรณีนี้เราจะเห็นว่าขนาดบริบทคือ 512 โทเค็น เช่นเดียวกับ BERT + + + +✏️ **ลองดูสิ!** Transformer บางรุ่น เช่น [BigBird](https://huggingface.co/google/bigbird-roberta-base) และ [Longformer](hf.co/allenai/longformer-base-4096) มีความยาวบริบทที่ยาวกว่า BERT และ Transformer รุ่นแรกๆ มาก สร้างอินสแตนซ์โทเค็นไนเซอร์สำหรับจุดตรวจสอบจุดใดจุดหนึ่งเหล่านี้ และตรวจสอบว่า `model_max_length` สอดคล้องกับสิ่งที่อ้างถึงในการ์ดโมเดล + + + + +ดังนั้น เพื่อทำการทดลองกับ GPU เช่นเดียวกับที่พบใน Google Colab เราจะเลือกสิ่งที่เล็กกว่าเล็กน้อยที่สามารถใส่ในหน่วยความจำได้: + +```python +chunk_size = 128 +``` + + + +โปรดทราบว่าการใช้ขนาดก้อนเล็กอาจส่งผลเสียในสถานการณ์จริง ดังนั้น คุณควรใช้ขนาดที่สอดคล้องกับกรณีการใช้งานที่คุณจะนำโมเดลไปใช้ + + + +ตอนนี้ส่วนที่สนุกมา เพื่อแสดงให้เห็นว่าการต่อข้อมูลทำงานอย่างไร เรามาทบทวนบางส่วนจากชุดการฝึกอบรมโทเค็นของเรา และพิมพ์จำนวนโทเค็นต่อการตรวจสอบ: + +```python +# Slicing produces a list of lists for each feature +tokenized_samples = tokenized_datasets["train"][:3] + +for idx, sample in enumerate(tokenized_samples["input_ids"]): + print(f"'>>> Review {idx} length: {len(sample)}'") +``` + +```python out +'>>> Review 0 length: 200' +'>>> Review 1 length: 559' +'>>> Review 2 length: 192' +``` + +จากนั้นเราสามารถเชื่อมตัวอย่างเหล่านี้ทั้งหมดเข้าด้วยกันด้วยความเข้าใจในพจนานุกรมง่ายๆ ดังนี้: + +```python +concatenated_examples = { + k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys() +} +total_length = len(concatenated_examples["input_ids"]) +print(f"'>>> Concatenated reviews length: {total_length}'") +``` + +```python out +'>>> Concatenated reviews length: 951' +``` + +เยี่ยมมาก ตรวจสอบความยาวทั้งหมดแล้ว ตอนนี้เรามาแบ่งบทวิจารณ์ที่ต่อกันออกเป็นส่วนๆ ตามขนาดที่กำหนดโดย `chunk_size` ในการทำเช่นนั้น เราจะทำซ้ำคุณลักษณะต่างๆ ใน `concatenated_examples` และใช้รายการความเข้าใจเพื่อสร้างชิ้นส่วนของแต่ละคุณลักษณะ ผลลัพธ์ที่ได้คือพจนานุกรมที่ประกอบด้วยส่วนต่างๆ สำหรับแต่ละฟีเจอร์: + +```python +chunks = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() +} + +for chunk in chunks["input_ids"]: + print(f"'>>> Chunk length: {len(chunk)}'") +``` + +```python out +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 55' +``` + +ดังที่คุณเห็นในตัวอย่างนี้ โดยทั่วไปชิ้นสุดท้ายจะเล็กกว่าขนาดชิ้นสูงสุด มีสองกลยุทธ์หลักในการจัดการกับสิ่งนี้: + +* ลบชิ้นสุดท้ายหากมีขนาดเล็กกว่า `chunk_size` +* แพดชิ้นสุดท้ายจนกระทั่งความยาวเท่ากับ `chunk_size` + +เราจะใช้แนวทางแรกที่นี่ ดังนั้นเราจะรวมตรรกะข้างต้นทั้งหมดไว้ในฟังก์ชันเดียวที่เราสามารถนำไปใช้กับชุดข้อมูลโทเค็นของเราได้: + +```python +def group_texts(examples): + # Concatenate all texts + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Compute length of concatenated texts + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # We drop the last chunk if it's smaller than chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Split by chunks of max_len + result = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() + } + # Create a new labels column + result["labels"] = result["input_ids"].copy() + return result +``` + +โปรดทราบว่าในขั้นตอนสุดท้ายของ `group_texts()` เราจะสร้างคอลัมน์ `labels` ใหม่ ซึ่งเป็นสำเนาของคอลัมน์ `input_ids` ดังที่เราจะได้เห็นในเร็วๆ นี้ นั่นเป็นเพราะในการสร้างแบบจำลองภาษาที่สวมหน้ากากนั้น มีวัตถุประสงค์เพื่อคาดการณ์โทเค็นที่สวมหน้ากากแบบสุ่มในชุดอินพุต และด้วยการสร้างคอลัมน์ `labels` เราจึงจัดเตรียมความจริงพื้นฐานสำหรับโมเดลภาษาของเราในการเรียนรู้ + +ตอนนี้ลองใช้ `group_texts()` กับชุดข้อมูลโทเค็นของเราโดยใช้ฟังก์ชัน `Dataset.map()` ที่เชื่อถือได้ของเรา: + +```python +lm_datasets = tokenized_datasets.map(group_texts, batched=True) +lm_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 61289 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 59905 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 122963 + }) +}) +``` + +คุณจะเห็นว่าการจัดกลุ่มและการแบ่งส่วนข้อความทำให้เกิดตัวอย่างมากกว่า 25,000 แบบเดิมของเราสำหรับการแยกแบบ `train` และ `test` นั่นเป็นเพราะว่าตอนนี้เรามีตัวอย่างที่เกี่ยวข้องกับ _contiguous tokens_ ที่ครอบคลุมหลายตัวอย่างจากคลังข้อมูลดั้งเดิม คุณสามารถดูสิ่งนี้ได้อย่างชัดเจนโดยมองหาโทเค็นพิเศษ `[SEP]` และ `[CLS]` ในส่วนใดส่วนหนึ่ง: + +```python +tokenizer.decode(lm_datasets["train"][1]["input_ids"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +ในตัวอย่างนี้ คุณจะเห็นบทวิจารณ์ภาพยนตร์สองเรื่องที่ทับซ้อนกัน เรื่องหนึ่งเกี่ยวกับภาพยนตร์ระดับมัธยมศึกษาตอนปลาย และอีกเรื่องเกี่ยวกับคนไร้บ้าน นอกจากนี้ เรามาดูกันว่าป้ายกำกับสำหรับการสร้างแบบจำลองภาษาที่สวมหน้ากากมีลักษณะอย่างไร: + +```python out +tokenizer.decode(lm_datasets["train"][1]["labels"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +ตามที่คาดไว้จากฟังก์ชัน `group_texts()` ด้านบน สิ่งนี้ดูเหมือนกับ `input_ids` ที่ถอดรหัสแล้ว -- แต่แล้วแบบจำลองของเราจะเรียนรู้อะไรได้อย่างไร เราขาดขั้นตอนสำคัญ: การแทรกโทเค็น `[MASK]` ที่ตำแหน่งสุ่มในอินพุต! มาดูกันว่าเราสามารถดำเนินการนี้ได้ทันทีระหว่างการปรับแต่งแบบละเอียดโดยใช้ตัวรวบรวมข้อมูลพิเศษได้อย่างไร + +## ปรับแต่ง DistilBERT อย่างละเอียดด้วย `Trainer` API + +การปรับแต่งโมเดลภาษามาสก์อย่างละเอียดเกือบจะเหมือนกับการปรับแต่งโมเดลการจำแนกลำดับอย่างละเอียด เหมือนที่เราทำใน [บทที่ 3](/course/th/chapter3) ข้อแตกต่างเพียงอย่างเดียวคือเราต้องการตัวรวบรวมข้อมูลพิเศษที่สามารถสุ่มปกปิดโทเค็นบางส่วนในข้อความแต่ละชุดได้ โชคดีที่ 🤗 Transformers มาพร้อมกับ `DataCollatorForLanguageModeling` โดยเฉพาะสำหรับงานนี้ เราแค่ต้องส่งโทเค็นไนเซอร์และอาร์กิวเมนต์ `mlm_probability` ที่ระบุเศษส่วนของโทเค็นที่จะปกปิด เราจะเลือก 15% ซึ่งเป็นจำนวนที่ใช้สำหรับ BERT และตัวเลือกทั่วไปในวรรณกรรม: + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +หากต้องการดูวิธีการทำงานของการมาสก์แบบสุ่ม เราจะป้อนตัวอย่างบางส่วนให้กับผู้รวบรวมข้อมูล เนื่องจากคาดว่าจะมีรายการ `dict` โดยที่ `dict` แต่ละอันแสดงถึงข้อความที่ต่อเนื่องกันเพียงก้อนเดียว เราจึงวนซ้ำชุดข้อมูลก่อนก่อนที่จะป้อนแบทช์ให้กับผู้เปรียบเทียบ เราลบคีย์ `"word_ids"` สำหรับผู้รวบรวมข้อมูลนี้เนื่องจากไม่ได้คาดหวัง: + +```python +samples = [lm_datasets["train"][i] for i in range(2)] +for sample in samples: + _ = sample.pop("word_ids") + +for chunk in data_collator(samples)["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python output +'>>> [CLS] bromwell [MASK] is a cartoon comedy. it ran at the same [MASK] as some other [MASK] about school life, [MASK] as " teachers ". [MASK] [MASK] [MASK] in the teaching [MASK] lead [MASK] to believe that bromwell high\'[MASK] satire is much closer to reality than is " teachers ". the scramble [MASK] [MASK] financially, the [MASK]ful students whogn [MASK] right through [MASK] pathetic teachers\'pomp, the pettiness of the whole situation, distinction remind me of the schools i knew and their students. when i saw [MASK] episode in [MASK] a student repeatedly tried to burn down the school, [MASK] immediately recalled. [MASK]...' + +'>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless' +``` + +เยี่ยมเลย มันได้ผล! เราจะเห็นว่าโทเค็น `[MASK]` ได้รับการสุ่มแทรกในตำแหน่งต่างๆ ในข้อความของเรา สิ่งเหล่านี้จะเป็นโทเค็นที่แบบจำลองของเราจะต้องทำนายระหว่างการฝึก และข้อดีของผู้รวบรวมข้อมูลก็คือ มันจะสุ่มการแทรก `[MASK]` ในทุกชุด! + + + +✏️ **ลองดูสิ!** เรียกใช้ข้อมูลโค้ดด้านบนหลายๆ ครั้งเพื่อดูว่าการมาสก์แบบสุ่มเกิดขึ้นต่อหน้าต่อตาคุณ! นอกจากนี้ ให้แทนที่เมธอด `tokenizer.decode()` ด้วย `tokenizer.convert_ids_to_tokens()` เพื่อดูว่าบางครั้งโทเค็นเดียวจากคำที่กำหนดจะถูกปกปิด ไม่ใช่โทเค็นอื่นๆ + + + +{#if fw === 'pt'} + +ผลข้างเคียงอย่างหนึ่งของการมาสก์แบบสุ่มคือเมตริกการประเมินของเราจะไม่ถูกกำหนดไว้เมื่อใช้ `Trainer` เนื่องจากเราใช้ตัวเปรียบเทียบข้อมูลเดียวกันสำหรับชุดการฝึกและการทดสอบ เราจะเห็นในภายหลังเมื่อเราดูการปรับแต่งอย่างละเอียดด้วย 🤗 Accelerate เราจะใช้ความยืดหยุ่นของลูปการประเมินแบบกำหนดเองเพื่อหยุดการสุ่มได้อย่างไร + +{/if} + +เมื่อฝึกโมเดลสำหรับการสร้างแบบจำลองภาษาที่ปกปิด เทคนิคหนึ่งที่สามารถใช้ได้คือการมาสก์ทั้งคำเข้าด้วยกัน ไม่ใช่แค่โทเค็นเดี่ยวๆ วิธีการนี้เรียกว่า _whole word masking_ หากเราต้องการใช้การมาสก์ทั้งคำ เราจะต้องสร้างตัวเปรียบเทียบข้อมูลด้วยตัวเอง ตัวรวบรวมข้อมูลเป็นเพียงฟังก์ชันที่รับรายการตัวอย่างและแปลงเป็นชุด ดังนั้นเรามาเริ่มกันเลย! เราจะใช้รหัสคำที่คำนวณไว้ก่อนหน้านี้เพื่อสร้างแผนที่ระหว่างดัชนีคำและโทเค็นที่เกี่ยวข้อง จากนั้นสุ่มตัดสินใจว่าจะปกปิดคำใดและใช้มาสก์นั้นกับอินพุต โปรดทราบว่าป้ายกำกับทั้งหมดเป็น "-100" ยกเว้นป้ายกำกับที่เกี่ยวข้องกับคำมาสก์ + +{#if fw === 'pt'} + +```py +import collections +import numpy as np + +from transformers import default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Create a map between words and corresponding token indices + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Randomly mask words + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + feature["labels"] = new_labels + + return default_data_collator(features) +``` + +{:else} + +```py +import collections +import numpy as np + +from transformers.data.data_collator import tf_default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Create a map between words and corresponding token indices + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Randomly mask words + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + feature["labels"] = new_labels + + return tf_default_data_collator(features) +``` + +{/if} + +ต่อไป เราสามารถลองใช้ตัวอย่างเดียวกันกับเมื่อก่อนได้: + +```py +samples = [lm_datasets["train"][i] for i in range(2)] +batch = whole_word_masking_data_collator(samples) + +for chunk in batch["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python out +'>>> [CLS] bromwell high is a cartoon comedy [MASK] it ran at the same time as some other programs about school life, such as " teachers ". my 35 years in the teaching profession lead me to believe that bromwell high\'s satire is much closer to reality than is " teachers ". the scramble to survive financially, the insightful students who can see right through their pathetic teachers\'pomp, the pettiness of the whole situation, all remind me of the schools i knew and their students. when i saw the episode in which a student repeatedly tried to burn down the school, i immediately recalled.....' + +'>>> .... [MASK] [MASK] [MASK] [MASK]....... high. a classic line : inspector : i\'m here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn\'t! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless' +``` + + + +✏️ **ลองดูสิ!** เรียกใช้ข้อมูลโค้ดด้านบนหลายๆ ครั้งเพื่อดูว่าการมาสก์แบบสุ่มเกิดขึ้นต่อหน้าต่อตาคุณ! นอกจากนี้ ให้แทนที่เมธอด `tokenizer.decode()` ด้วย `tokenizer.convert_ids_to_tokens()` เพื่อดูว่าโทเค็นจากคำที่กำหนดจะถูกมาสก์ไว้ด้วยกันเสมอ + + + +ตอนนี้เรามีผู้รวบรวมข้อมูลสองกลุ่มแล้ว ขั้นตอนการปรับแต่งที่เหลือถือเป็นมาตรฐาน การฝึกอบรมอาจใช้เวลาสักระยะบน Google Colab หากคุณโชคดีไม่พอที่จะได้ P100 GPU อันเป็นตำนาน 😭 ดังนั้นก่อนอื่นเราจะลดขนาดชุดการฝึกลงเหลือเพียงไม่กี่พันตัวอย่างก่อน ไม่ต้องกังวล เรายังคงได้รับโมเดลภาษาที่ดีพอสมควร! วิธีที่รวดเร็วในการสุ่มตัวอย่างชุดข้อมูลใน 🤗 ชุดข้อมูลคือผ่านฟังก์ชัน `Dataset.train_test_split()` ที่เราเห็นใน [บทที่ 5](/course/th/chapter5): + +```python +train_size = 10_000 +test_size = int(0.1 * train_size) + +downsampled_dataset = lm_datasets["train"].train_test_split( + train_size=train_size, test_size=test_size, seed=42 +) +downsampled_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 10000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 1000 + }) +}) +``` + +สิ่งนี้ได้สร้างการแยก `train` และ `test` ใหม่โดยอัตโนมัติ โดยกำหนดขนาดชุดการฝึกไว้ที่ 10,000 ตัวอย่าง และการตรวจสอบความถูกต้องตั้งไว้ที่ 10% ของจำนวนนั้น คุณสามารถเพิ่มสิ่งนี้ได้ตามใจชอบ หากคุณมี GPU ที่แข็งแกร่ง! สิ่งต่อไปที่เราต้องทำคือเข้าสู่ระบบ Hugging Face Hub หากคุณใช้โค้ดนี้ในโน้ตบุ๊ก คุณสามารถทำได้โดยใช้ฟังก์ชันยูทิลิตีต่อไปนี้: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +ซึ่งจะแสดงวิดเจ็ตที่คุณสามารถป้อนข้อมูลรับรองของคุณได้ หรือคุณสามารถเรียกใช้: + +``` +huggingface-cli login +``` + +ในเทอร์มินัลที่คุณชื่นชอบและเข้าสู่ระบบที่นั่น + +{#if fw === 'tf'} + +เมื่อเราเข้าสู่ระบบแล้ว เราสามารถสร้างชุดข้อมูล `tf.data` ได้ ในการดำเนินการดังกล่าว เราจะใช้เมธอด `prepare_tf_dataset()` ซึ่งใช้แบบจำลองของเราในการอนุมานโดยอัตโนมัติว่าคอลัมน์ใดควรอยู่ในชุดข้อมูล หากคุณต้องการควบคุมคอลัมน์ที่จะใช้อย่างชัดเจน คุณสามารถใช้เมธอด `Dataset.to_tf_dataset()` แทนได้ เพื่อให้ทุกอย่างง่ายขึ้น เราจะใช้ตัวรวบรวมข้อมูลมาตรฐานที่นี่ แต่คุณยังสามารถลองใช้ตัวเปรียบเทียบการปกปิดคำทั้งหมดและเปรียบเทียบผลลัพธ์เป็นแบบฝึกหัดได้: + +```python +tf_train_dataset = model.prepare_tf_dataset( + downsampled_dataset["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = model.prepare_tf_dataset( + downsampled_dataset["test"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +ต่อไป เราจะตั้งค่าไฮเปอร์พารามิเตอร์การฝึกอบรมและคอมไพล์โมเดลของเรา เราใช้ฟังก์ชัน `create_optimizer()` จากไลบรารี 🤗 Transformers ซึ่งให้เครื่องมือเพิ่มประสิทธิภาพ `AdamW` แก่เราพร้อมการลดอัตราการเรียนรู้เชิงเส้น นอกจากนี้เรายังใช้การสูญเสียในตัวของโมเดล ซึ่งเป็นค่าเริ่มต้นเมื่อไม่มีการระบุการสูญเสียเป็นอาร์กิวเมนต์สำหรับ `compile()` และเราตั้งค่าความแม่นยำในการฝึกอบรมเป็น `"mixed_float16"` โปรดทราบว่าหากคุณใช้ Colab GPU หรือ GPU อื่นๆ ที่ไม่รองรับการเร่งความเร็ว float16 คุณน่าจะ comment ในบรรทัดนั้นเพื่อไม่ให้คำสั่งบรรทัดนั้นทำงาน + +นอกจากนี้ เรายังตั้งค่า `PushToHubCallback` ที่จะบันทึกโมเดลไปที่ Hub หลังจากแต่ละ epoch คุณสามารถระบุชื่อของพื้นที่เก็บข้อมูลที่คุณต้องการพุชด้วยอาร์กิวเมนต์ `hub_model_id` (โดยเฉพาะ คุณจะต้องใช้อาร์กิวเมนต์นี้เพื่อพุชไปยังองค์กร) ตัวอย่างเช่น ในการผลักดันโมเดลไปที่ [`huggingface-course` Organization](https://huggingface.co/huggingface-course) เราได้เพิ่ม `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` ตามค่าเริ่มต้น พื้นที่เก็บข้อมูลที่ใช้จะอยู่ในเนมสเปซของคุณและตั้งชื่อตามไดเร็กทอรีเอาต์พุตที่คุณตั้งค่า ดังนั้นในกรณีของเราจะเป็น `"lewtun/distilbert-finetuned-imdb"` + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +model_name = model_checkpoint.split("/")[-1] +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +ตอนนี้เราพร้อมที่จะรัน `model.fit()` แล้ว -- แต่ก่อนที่จะดำเนินการ เรามาดู _perplexity_ สั้นๆ กันก่อน ซึ่งเป็นตัวชี้วัดทั่วไปในการประเมินประสิทธิภาพของโมเดลภาษา + +{:else} + +เมื่อเราเข้าสู่ระบบแล้ว เราสามารถระบุข้อโต้แย้งสำหรับ `Trainer` ได้: + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Show the training loss with every epoch +logging_steps = len(downsampled_dataset["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +training_args = TrainingArguments( + output_dir=f"{model_name}-finetuned-imdb", + overwrite_output_dir=True, + evaluation_strategy="epoch", + learning_rate=2e-5, + weight_decay=0.01, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + push_to_hub=True, + fp16=True, + logging_steps=logging_steps, +) +``` + +ที่นี่เราได้ปรับแต่งตัวเลือกเริ่มต้นบางส่วน รวมถึง `logging_steps` เพื่อให้แน่ใจว่าเราจะติดตามการสูญเสียการฝึกในแต่ละ epoch นอกจากนี้ เรายังใช้ `fp16=True` เพื่อเปิดใช้การฝึกแบบผสมความแม่นยำ ซึ่งช่วยให้เราเร่งความเร็วได้อีกครั้ง ตามค่าเริ่มต้น `Trainer` จะลบคอลัมน์ใดๆ ที่ไม่ได้เป็นส่วนหนึ่งของเมธอด `forward()` ของโมเดล ซึ่งหมายความว่าหากคุณใช้ตัวจับคู่การมาสก์ทั้งคำ คุณจะต้องตั้งค่า `remove_unused_columns=False` เพื่อให้แน่ใจว่าเราจะไม่สูญเสียคอลัมน์ `word_ids` ในระหว่างการฝึก + +โปรดทราบว่าคุณสามารถระบุชื่อของพื้นที่เก็บข้อมูลที่คุณต้องการพุชไปได้ด้วยอาร์กิวเมนต์ `hub_model_id` (โดยเฉพาะ คุณจะต้องใช้อาร์กิวเมนต์นี้เพื่อพุชไปยังองค์กร) ตัวอย่างเช่น เมื่อเราผลักโมเดลไปที่ [`huggingface-course` Organization](https://huggingface.co/huggingface-course) เราได้เพิ่ม `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` ลงใน `TrainingArguments` ตามค่าเริ่มต้น พื้นที่เก็บข้อมูลที่ใช้จะอยู่ในเนมสเปซของคุณและตั้งชื่อตามไดเร็กทอรีเอาต์พุตที่คุณตั้งค่า ดังนั้นในกรณีของเราจะเป็น `"lewtun/distilbert-finetuned-imdb"` + +ตอนนี้เรามีส่วนผสมทั้งหมดในการสร้างตัวอย่าง `Trainer` แล้ว ในที่นี้เราใช้ `data_collator` มาตรฐาน แต่คุณสามารถลองใช้ตัวเปรียบเทียบการปกปิดทั้งคำและเปรียบเทียบผลลัพธ์เป็นแบบฝึกหัดได้: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=training_args, + train_dataset=downsampled_dataset["train"], + eval_dataset=downsampled_dataset["test"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +ตอนนี้เราพร้อมที่จะรัน `trainer.train()` แล้ว -- แต่ก่อนที่จะดำเนินการ เรามาดู _perplexity_ สั้นๆ กันก่อน ซึ่งเป็นตัวชี้วัดทั่วไปในการประเมินประสิทธิภาพของโมเดลภาษา + +{/if} + +### ความฉงนสนเท่ห์สำหรับแบบจำลองภาษา[[ความฉงนสนเท่ห์สำหรับแบบจำลองภาษา]] + + + +แตกต่างจากงานอื่นๆ เช่น การจัดหมวดหมู่ข้อความ หรือการตอบคำถามโดยที่เราได้รับคลังข้อมูลที่มีป้ายกำกับไว้เพื่อฝึกฝน เนื่องจากการสร้างแบบจำลองภาษาเราไม่มีป้ายกำกับที่ชัดเจน แล้วเราจะทราบได้อย่างไรว่าอะไรคือสิ่งที่ทำให้โมเดลภาษาที่ดี? เช่นเดียวกับคุณสมบัติการแก้ไขอัตโนมัติในโทรศัพท์ของคุณ รูปแบบภาษาที่ดีคือรูปแบบที่กำหนดความน่าจะเป็นสูงให้กับประโยคที่ถูกต้องตามหลักไวยากรณ์ และความน่าจะเป็นต่ำสำหรับประโยคไร้สาระ เพื่อให้คุณเข้าใจได้ดีขึ้นว่าสิ่งนี้เป็นอย่างไร คุณสามารถค้นหา "autocorrect fails" ทั้งชุดทางออนไลน์ ซึ่งโมเดลในโทรศัพท์ของบุคคลนั้นได้สร้างการดำเนินการที่ค่อนข้างตลก (และมักไม่เหมาะสม)! + +{#if fw === 'pt'} + +สมมติว่าชุดทดสอบของเราประกอบด้วยประโยคส่วนใหญ่ที่ถูกต้องตามหลักไวยากรณ์ ดังนั้นวิธีหนึ่งในการวัดคุณภาพของแบบจำลองภาษาของเราคือการคำนวณความน่าจะเป็นที่แบบจำลองกำหนดให้กับคำถัดไปในประโยคทั้งหมดของชุดทดสอบ ความน่าจะเป็นสูงบ่งชี้ว่าโมเดลไม่ได้ "ประหลาดใจ" หรือ "งุนงง" กับตัวอย่างที่มองไม่เห็น และแนะนำว่าโมเดลได้เรียนรู้รูปแบบพื้นฐานของไวยากรณ์ในภาษาแล้ว มีคำจำกัดความทางคณิตศาสตร์หลายประการของความฉงนสนเท่ห์ แต่คำที่เราจะใช้กำหนดว่ามันเป็นเลขชี้กำลังของการสูญเสีย cross-entropy ดังนั้น เราสามารถคำนวณความงุนงงของแบบจำลองที่ฝึกไว้ล่วงหน้าได้โดยใช้ฟังก์ชัน `Trainer.evaluate()` เพื่อคำนวณการสูญเสีย cross-entropy ข้ามในชุดทดสอบ จากนั้นหาเลขชี้กำลังของผลลัพธ์: + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +สมมติว่าชุดทดสอบของเราประกอบด้วยประโยคส่วนใหญ่ที่ถูกต้องตามหลักไวยากรณ์ ดังนั้นวิธีหนึ่งในการวัดคุณภาพของแบบจำลองภาษาของเราคือการคำนวณความน่าจะเป็นที่แบบจำลองกำหนดให้กับคำถัดไปในประโยคทั้งหมดของชุดทดสอบ ความน่าจะเป็นสูงบ่งชี้ว่าแบบจำลองบ่งชี้ว่าแบบจำลองนั้นไม่ "แปลกใจ" หรือ "งุนงง" กับตัวอย่างที่มองไม่เห็น และแนะนำว่าแบบจำลองได้เรียนรู้รูปแบบพื้นฐานของไวยากรณ์ในภาษาแล้ว มีคำจำกัดความทางคณิตศาสตร์หลายประการของความฉงนสนเท่ห์ แต่คำที่เราจะใช้กำหนดว่ามันเป็นเลขชี้กำลังของการสูญเสีย cross-entropy ดังนั้น เราสามารถคำนวณความฉงนสนเท่ห์ของแบบจำลองที่ฝึกไว้ล่วงหน้าได้โดยใช้วิธี `model.evaluate()` เพื่อคำนวณการสูญเสีย cross-entropy ในชุดทดสอบ จากนั้นหาเลขชี้กำลังของผลลัพธ์: + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 21.75 +``` + +คะแนนความฉงนสนเท่ห์ที่ต่ำกว่าหมายถึงโมเดลภาษาที่ดีกว่า และเราจะเห็นได้ที่นี่ว่าโมเดลเริ่มต้นของเรามีค่าค่อนข้างมาก มาดูกันว่าเราจะลดมันลงโดยการปรับแบบละเอียดได้ไหม! เพื่อทำเช่นนั้น ขั้นแรกเราจะรันลูปการฝึกซ้อม: + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +แล้วจึงคำนวณความฉงนสนเท่ห์ที่เกิดขึ้นกับชุดทดสอบดังเดิม: + +{#if fw === 'pt'} + +```python +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +```python +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 11.32 +``` + +เยี่ยมเลย -- นี่เป็นการลดความซับซ้อนได้ค่อนข้างมาก ซึ่งบอกเราว่าโมเดลได้เรียนรู้บางอย่างเกี่ยวกับขอบเขตของการวิจารณ์ภาพยนตร์! + +{#if fw === 'pt'} + +เมื่อการฝึกเสร็จสิ้น เราสามารถส่งการ์ดโมเดลพร้อมข้อมูลการฝึกไปยัง Hub ได้ (จุดตรวจสอบจะถูกบันทึกไว้ระหว่างการฝึก): + +```python +trainer.push_to_hub() +``` + +{/if} + + + +✏️ **ตาคุณแล้ว!** ดำเนินการฝึกอบรมข้างต้นหลังจากเปลี่ยนตัวเปรียบเทียบข้อมูลเป็นตัวเปรียบเทียบการปกปิดทั้งคำ คุณได้รับผลลัพธ์ที่ดีขึ้นหรือไม่? + + + +{#if fw === 'pt'} + +ในกรณีการใช้งานของเรา เราไม่จำเป็นต้องทำอะไรเป็นพิเศษกับลูปการฝึกอบรม แต่ในบางกรณี คุณอาจจำเป็นต้องใช้ตรรกะแบบกำหนดเองบางอย่าง สำหรับแอปพลิเคชันเหล่านี้ คุณสามารถใช้ 🤗 Accelerate -- มาดูกัน! + +## ปรับแต่ง DistilBERT ปรับแต่ง 🤗 Accelerate + +ดังที่เราเห็นใน `Trainer` การปรับแต่งโมเดลภาษาที่สวมหน้ากากอย่างละเอียดนั้นคล้ายคลึงกับตัวอย่างการจัดประเภทข้อความจาก [บทที่ 3](/course/th/chapter3) มาก อันที่จริง รายละเอียดปลีกย่อยเพียงอย่างเดียวคือการใช้ตัวรวบรวมข้อมูลพิเศษ และเราได้กล่าวถึงไปแล้วก่อนหน้านี้ในส่วนนี้! + +อย่างไรก็ตาม เราพบว่า `DataCollatorForLanguageModeling` ยังใช้การมาสก์แบบสุ่มกับการประเมินแต่ละครั้ง ดังนั้นเราจะเห็นความผันผวนของคะแนนความงุนงงของเราในการฝึกแต่ละครั้ง วิธีหนึ่งในการกำจัดแหล่งที่มาของการสุ่มนี้คือการใช้การมาสก์ _once_ กับชุดการทดสอบทั้งหมด จากนั้นใช้ตัวรวบรวมข้อมูลเริ่มต้นใน 🤗 Transformers เพื่อรวบรวมแบทช์ระหว่างการประเมิน หากต้องการดูวิธีการทำงาน ลองใช้ฟังก์ชันง่ายๆ ที่ใช้การมาสก์กับแบทช์ คล้ายกับการเผชิญหน้าครั้งแรกกับ `DataCollatorForLanguageModeling`: + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Create a new "masked" column for each column in the dataset + return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} +``` + +ต่อไป เราจะใช้ฟังก์ชันนี้กับชุดทดสอบของเรา และวางคอลัมน์ที่ไม่ได้ปิดบังไว้ เพื่อให้เราสามารถแทนที่คอลัมน์เหล่านั้นด้วยคอลัมน์ที่ปิดบังไว้ได้ คุณสามารถใช้การมาสก์ทั้งคำได้โดยแทนที่ `data_collator` ด้านบนด้วยคำที่เหมาะสม ซึ่งในกรณีนี้คุณควรลบบรรทัดแรกที่นี่: + +```py +downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) +eval_dataset = downsampled_dataset["test"].map( + insert_random_mask, + batched=True, + remove_columns=downsampled_dataset["test"].column_names, +) +eval_dataset = eval_dataset.rename_columns( + { + "masked_input_ids": "input_ids", + "masked_attention_mask": "attention_mask", + "masked_labels": "labels", + } +) +``` + +จากนั้นเราสามารถตั้งค่าตัวโหลดข้อมูลได้ตามปกติ แต่เราจะใช้ `default_data_collator` จาก 🤗 Transformers สำหรับชุดการประเมิน: + +```python +from torch.utils.data import DataLoader +from transformers import default_data_collator + +batch_size = 64 +train_dataloader = DataLoader( + downsampled_dataset["train"], + shuffle=True, + batch_size=batch_size, + collate_fn=data_collator, +) +eval_dataloader = DataLoader( + eval_dataset, batch_size=batch_size, collate_fn=default_data_collator +) +``` + +แบบที่นี่เราทำตามขั้นตอนมาตรฐานด้วย 🤗 Accelerate ลำดับแรกของธุรกิจคือการโหลดโมเดลที่ผ่านการฝึกอบรมเวอร์ชันใหม่: + +``` +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +จากนั้นเราจำเป็นต้องระบุเครื่องมือเพิ่มประสิทธิภาพ เราจะใช้มาตรฐาน `AdamW`: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +ด้วยวัตถุเหล่านี้ ตอนนี้เราสามารถเตรียมทุกอย่างสำหรับการฝึกด้วยวัตถุ 'Accelerator': + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +เมื่อโมเดล เครื่องมือเพิ่มประสิทธิภาพ และตัวโหลดข้อมูลของเราได้รับการกำหนดค่าแล้ว เราก็สามารถระบุตัวกำหนดเวลาอัตราการเรียนรู้ได้ดังต่อไปนี้: + +```python +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +มีเพียงสิ่งสุดท้ายที่ต้องทำก่อนการฝึก: สร้างที่เก็บแบบจำลองบน Hugging Face Hub! เราสามารถใช้ไลบรารี 🤗 Hub เพื่อสร้างชื่อเต็มของ repo ของเราก่อน: + +```python +from huggingface_hub import get_full_repo_name + +model_name = "distilbert-base-uncased-finetuned-imdb-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' +``` + +จากนั้นสร้างและโคลนพื้นที่เก็บข้อมูลโดยใช้คลาส `Repository` จาก 🤗 Hub: + +```python +from huggingface_hub import Repository + +output_dir = model_name +repo = Repository(output_dir, clone_from=repo_name) +``` + +เมื่อทำเสร็จแล้ว การเขียนลูปการฝึกอบรมและการประเมินผลทั้งหมดก็เป็นเรื่องง่าย: + +```python +from tqdm.auto import tqdm +import torch +import math + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(**batch) + + loss = outputs.loss + losses.append(accelerator.gather(loss.repeat(batch_size))) + + losses = torch.cat(losses) + losses = losses[: len(eval_dataset)] + try: + perplexity = math.exp(torch.mean(losses)) + except OverflowError: + perplexity = float("inf") + + print(f">>> Epoch {epoch}: Perplexity: {perplexity}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +>>> Epoch 0: Perplexity: 11.397545307900472 +>>> Epoch 1: Perplexity: 10.904909330983092 +>>> Epoch 2: Perplexity: 10.729503505340409 +``` + +เยี่ยมเลย เราสามารถประเมินความซับซ้อนในแต่ละ epoch ได้ และรับรองว่าการฝึกซ้อมหลายครั้งสามารถทำซ้ำได้! + +{/if} + +## การใช้งานโมเดลที่ปรับแต่งของเรา[[การใช้งานโมเดลที่ปรับแต่งของเรา]] + +คุณสามารถโต้ตอบกับโมเดลที่ได้รับการปรับแต่งของคุณโดยใช้วิดเจ็ตบน Hub หรือในเครื่องด้วย `pipeline` จาก 🤗 Transformers ลองใช้อันหลังเพื่อดาวน์โหลดโมเดลของเราโดยใช้ไปป์ไลน์ `fill-mask`: + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +จากนั้นเราจะป้อนข้อความตัวอย่าง "This is a great [MASK]" ให้กับไปป์ไลน์ และดูว่าคำทำนาย 5 อันดับแรกคืออะไร: + +```python +preds = mask_filler(text) + +for pred in preds: + print(f">>> {pred['sequence']}") +``` + +```python out +'>>> this is a great movie.' +'>>> this is a great film.' +'>>> this is a great story.' +'>>> this is a great movies.' +'>>> this is a great character.' +``` + +เรียบร้อย -- โมเดลของเราได้ปรับน้ำหนักอย่างชัดเจนเพื่อคาดเดาคำที่เกี่ยวข้องกับภาพยนตร์มากขึ้น! + + + +นี่เป็นการสรุปการทดลองครั้งแรกของเราด้วยการฝึกโมเดลภาษา ใน [ส่วนที่ 6](/course/th/chapter7/6) คุณจะได้เรียนรู้วิธีฝึกโมเดลการถดถอยอัตโนมัติ เช่น GPT-2 ตั้งแต่เริ่มต้น ไปที่นั่นถ้าคุณต้องการดูว่าคุณสามารถฝึกโมเดล Transformer ของคุณเองล่วงหน้าได้อย่างไร! + + + +✏️ **ลองดูสิ!** หากต้องการหาปริมาณประโยชน์ของการปรับโดเมน ให้ปรับแต่งตัวแยกประเภทบนป้ายกำกับ IMDb สำหรับทั้งจุดตรวจสอบ DistilBERT ที่ได้รับการฝึกมาแล้วและที่ได้รับการปรับแต่งอย่างละเอียด หากคุณต้องการทบทวนการจัดหมวดหมู่ข้อความ โปรดดู [บทที่ 3](/course/th/chapter3) + + diff --git a/chapters/th/chapter7/4.mdx b/chapters/th/chapter7/4.mdx new file mode 100644 index 000000000..3b0301f3d --- /dev/null +++ b/chapters/th/chapter7/4.mdx @@ -0,0 +1,1001 @@ + + +# การแปลความหมาย[[การแปลความหมาย]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +ตอนนี้มาดำดิ่งสู่การแปล นี่เป็นอีกหนึ่ง [งานลำดับต่อลำดับ](/course/chapter1/7) ซึ่งหมายความว่าเป็นปัญหาที่สามารถกำหนดได้ว่าเป็นการไปจากลำดับหนึ่งไปอีกลำดับหนึ่ง ในแง่นั้น ปัญหาค่อนข้างใกล้เคียงกับ [การสรุป](/course/th/chapter7/6) และคุณสามารถปรับสิ่งที่เราจะเห็นที่นี่กับปัญหาตามลำดับอื่นๆ เช่น: + +- **การถ่ายโอนรูปแบบ**: การสร้างโมเดลที่ *แปล* ข้อความที่เขียนด้วยสไตล์หนึ่งไปเป็นอีกสไตล์หนึ่ง (เช่น จากเป็นทางการไปเป็นไม่เป็นทางการ หรือจากเช็คสเปียร์จากภาษาอังกฤษเป็นภาษาอังกฤษสมัยใหม่) +- **การตอบคำถามเชิงสร้างสรรค์**: การสร้างแบบจำลองที่สร้างคำตอบสำหรับคำถามตามบริบท + + + +หากคุณมีคลังข้อความที่ใหญ่เพียงพอในสองภาษา (หรือมากกว่า) คุณสามารถฝึกโมเดลการแปลใหม่ตั้งแต่ต้นได้เหมือนกับที่เราฝึกในหัวข้อ [การสร้างแบบจำลองภาษาเชิงสาเหตุ](/course/th/chapter7/6) อย่างไรก็ตาม การปรับแต่งโมเดลการแปลที่มีอยู่อย่างละเอียดจะเร็วขึ้น ไม่ว่าจะเป็นโมเดลหลายภาษา เช่น mT5 หรือ mBART ที่คุณต้องการปรับแต่งให้เข้ากับคู่ภาษาเฉพาะ หรือแม้แต่โมเดลเฉพาะสำหรับการแปลจากภาษาหนึ่งเป็นอีกภาษาหนึ่ง ที่คุณต้องการปรับแต่งให้เข้ากับคลังข้อมูลเฉพาะของคุณ + +ในส่วนนี้ เราจะปรับแต่งโมเดล Marian ที่ได้รับการฝึกล่วงหน้าเพื่อแปลจากภาษาอังกฤษเป็นภาษาฝรั่งเศส (เนื่องจากพนักงาน Hugging Face จำนวนมากพูดทั้งสองภาษาเหล่านั้น) บน [ชุดข้อมูล KDE4](https://huggingface.co/datasets/kde4 ) ซึ่งเป็นชุดข้อมูลของไฟล์ที่แปลแล้วสำหรับ [แอป KDE](https://apps.kde.org/) แบบจำลองที่เราจะใช้ได้รับการฝึกอบรมล่วงหน้าในคลังข้อความภาษาฝรั่งเศสและอังกฤษขนาดใหญ่ที่นำมาจาก [ชุดข้อมูล Opus](https://opus.nlpl.eu/) ซึ่งมีชุดข้อมูล KDE4 จริงๆ แต่ถึงแม้ว่าโมเดลที่ได้รับการฝึกล่วงหน้าที่เราใช้จะได้เห็นข้อมูลนั้นในระหว่างการฝึกล่วงหน้าแล้ว เราก็จะเห็นว่าเราสามารถรับเวอร์ชันที่ดีขึ้นได้หลังจากการปรับแต่งอย่างละเอียด + +เมื่อเสร็จแล้วเราจะได้โมเดลที่สามารถทำนายได้ดังนี้: + + + + +One-hot encoded labels for question answering. + + + +เช่นเดียวกับในส่วนก่อนหน้า คุณจะพบโมเดลจริงที่เราจะฝึกและอัปโหลดไปยัง Hub โดยใช้โค้ดด้านล่าง และตรวจสอบการคาดการณ์อีกครั้ง [ที่นี่](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). + +## การเตรียมข้อมูล[[การเตรียมข้อมูล]] + +หากต้องการปรับแต่งหรือฝึกโมเดลการแปลตั้งแต่เริ่มต้น เราจำเป็นต้องมีชุดข้อมูลที่เหมาะกับงานนี้ ตามที่กล่าวไว้ก่อนหน้านี้ เราจะใช้ [ชุดข้อมูล KDE4](https://huggingface.co/datasets/kde4) ในส่วนนี้ แต่คุณสามารถปรับโค้ดเพื่อใช้ข้อมูลของคุณเองได้อย่างง่ายดาย ตราบใดที่คุณมีคู่กัน ของประโยคในสองภาษาที่คุณต้องการแปลจากและเป็นไปในนั้น ย้อนกลับไปที่ [บทที่ 5](/course/th/chapter5) หากคุณต้องการคำเตือนเกี่ยวกับวิธีการโหลดข้อมูลที่กำหนดเองใน `Dataset`. + +### ชุดข้อมูล KDE4[[ชุดข้อมูล-kde4]] + +ตามปกติ เราจะดาวน์โหลดชุดข้อมูลของเราโดยใช้ฟังก์ชัน `load_dataset()`: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +หากคุณต้องการทำงานกับคู่ภาษาอื่น คุณสามารถระบุภาษาเหล่านั้นด้วยรหัสของพวกเขาได้ ชุดข้อมูลนี้มีภาษาให้เลือกทั้งหมด 92 ภาษา คุณสามารถดูทั้งหมดได้โดยขยายแท็กภาษาใน [การ์ดชุดข้อมูล](https://huggingface.co/datasets/kde4) + +Language available for the KDE4 dataset. + +มาดูชุดข้อมูลกันดีกว่า: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +เรามีประโยค 210,173 คู่ แต่ในการแยกประโยคเดียว ดังนั้น เราจะต้องสร้างชุดการตรวจสอบของเราเอง ดังที่เราเห็นใน [บทที่ 5](/course/th/chapter5) `Dataset` มีเมธอด `train_test_split()` ที่สามารถช่วยเราได้ เราจะจัดเตรียมเมล็ดพันธุ์สำหรับการทำซ้ำ: + +```py +split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) +split_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 189155 + }) + test: Dataset({ + features: ['id', 'translation'], + num_rows: 21018 + }) +}) +``` + +เราสามารถเปลี่ยนชื่อคีย์ `"test"` เป็น `"validation"` ได้ดังนี้: + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +ตอนนี้เรามาดูองค์ประกอบหนึ่งของชุดข้อมูลกัน: + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +เราได้รับพจนานุกรมที่มีสองประโยคในคู่ภาษาที่เราขอ ความพิเศษประการหนึ่งของชุดข้อมูลนี้ซึ่งเต็มไปด้วยคำศัพท์ทางเทคนิคด้านวิทยาการคอมพิวเตอร์ก็คือคำศัพท์ทั้งหมดได้รับการแปลเป็นภาษาฝรั่งเศสทั้งหมด อย่างไรก็ตาม วิศวกรชาวฝรั่งเศสจะทิ้งคำเฉพาะด้านวิทยาการคอมพิวเตอร์ส่วนใหญ่เป็นภาษาอังกฤษเมื่อพวกเขาพูด ตัวอย่างเช่น คำว่า "threads" อาจปรากฏในประโยคภาษาฝรั่งเศส โดยเฉพาะอย่างยิ่งในการสนทนาทางเทคนิค แต่ในชุดข้อมูลนี้ มันถูกแปลเป็น "fils de discussion" ที่ถูกต้องมากขึ้น โมเดลที่ฝึกไว้ล่วงหน้าที่เราใช้ ซึ่งได้รับการฝึกฝนกับคลังประโยคภาษาฝรั่งเศสและอังกฤษที่มีขนาดใหญ่กว่า เลือกใช้ตัวเลือกที่ง่ายกว่าในการคงคำไว้ดังนี้: + +```py +from transformers import pipeline + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut pour les threads élargis'}] +``` + +อีกตัวอย่างหนึ่งของพฤติกรรมนี้สามารถเห็นได้จากคำว่า "plugin" ซึ่งไม่ใช่คำภาษาฝรั่งเศสอย่างเป็นทางการ แต่เจ้าของภาษาส่วนใหญ่จะเข้าใจและไม่สนใจที่จะแปล +ในชุดข้อมูล KDE4 คำนี้ได้รับการแปลเป็นภาษาฝรั่งเศสเป็นภาษา "module d'extension" ที่เป็นทางการมากขึ้น: + +```py +split_datasets["train"][172]["translation"] +``` + +```python out +{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.', + 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."} +``` + +อย่างไรก็ตาม โมเดลที่ได้รับการฝึกล่วงหน้าของเรายังคงยึดตามคำภาษาอังกฤษที่กระชับและคุ้นเคย: + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] +``` + +น่าสนใจที่จะดูว่าโมเดลที่ได้รับการปรับแต่งของเรานั้นคำนึงถึงความเฉพาะเจาะจงของชุดข้อมูลหรือไม่ (เตือนไว้ก่อน: มันจะเป็นเช่นนั้น) + + + + + +✏️ **ตาคุณ!** อีกคำภาษาอังกฤษที่มักใช้ในภาษาฝรั่งเศสคือ "email" ค้นหาตัวอย่างแรกในชุดข้อมูลการฝึกอบรมที่ใช้คำนี้ มันแปลยังไงบ้าง? โมเดลที่ผ่านการฝึกอบรมจะแปลประโยคภาษาอังกฤษเดียวกันได้อย่างไร + + + +### การประมวลผลข้อมูล[[การประมวลผลข้อมูล]] + + + +ตอนนี้คุณควรรู้การเจาะลึกแล้ว: ข้อความทั้งหมดจำเป็นต้องแปลงเป็นชุดรหัสโทเค็นเพื่อให้แบบจำลองเข้าใจได้ สำหรับงานนี้ เราจะต้องสร้างโทเค็นทั้งอินพุตและเป้าหมาย ภารกิจแรกของเราคือสร้างวัตถุ `tokenizer` ของเรา ตามที่ระบุไว้ก่อนหน้านี้ เราจะใช้โมเดลฝึกภาษาอังกฤษแบบ Marian เป็นภาษาฝรั่งเศส หากคุณกำลังลองใช้โค้ดนี้กับคู่ภาษาอื่น ตรวจสอบให้แน่ใจว่าได้ปรับจุดตรวจสอบโมเดลแล้ว องค์กร [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) มีโมเดลมากกว่าพันรายการในหลายภาษา + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt") +``` + +คุณยังสามารถแทนที่ `model_checkpoint` ด้วยโมเดลอื่นๆ ที่คุณต้องการจาก [Hub](https://huggingface.co/models) หรือโฟลเดอร์ในเครื่องที่คุณได้บันทึกโมเดลที่ฝึกไว้ล่วงหน้าและ tokenizer + + + +💡 หากคุณใช้โทเค็นไนเซอร์หลายภาษา เช่น mBART, mBART-50 หรือ M2M100 คุณจะต้องตั้งค่ารหัสภาษาของอินพุตและเป้าหมายของคุณในโทเค็นโดยการตั้งค่า `tokenizer.src_lang` และ `tokenizer.tgt_lang` ทางด้านขวา ค่านิยม + + + +การจัดเตรียมข้อมูลของเราค่อนข้างตรงไปตรงมา มีเพียงสิ่งเดียวที่ต้องจำ คุณต้องแน่ใจว่า tokenizer ประมวลผลเป้าหมายในภาษาเอาต์พุต (ในที่นี้คือภาษาฝรั่งเศส) คุณสามารถทำได้โดยส่งเป้าหมายไปยังอาร์กิวเมนต์ `text_targets` ของเมธอด `__call__` ของ tokenizer + +หากต้องการดูวิธีการทำงาน เราจะประมวลผลตัวอย่างของแต่ละภาษาในชุดการฝึกอบรมหนึ่งตัวอย่าง: + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence, text_target=fr_sentence) +inputs +``` + +```python out +{'input_ids': [47591, 12, 9842, 19634, 9, 0], 'attention_mask': [1, 1, 1, 1, 1, 1], 'labels': [577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]} +``` + +ดังที่เราเห็น ผลลัพธ์มี ID อินพุตที่เกี่ยวข้องกับประโยคภาษาอังกฤษ ในขณะที่ ID ที่เกี่ยวข้องกับภาษาฝรั่งเศสจะถูกเก็บไว้ในฟิลด์ `labels` หากคุณลืมระบุว่าคุณกำลังโทเค็นป้ายกำกับ ป้ายเหล่านั้นจะถูกโทเค็นโดยอินพุตโทเค็นไนเซอร์ ซึ่งในกรณีของโมเดล Marian จะทำงานได้ไม่ดีเลย: + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(inputs["labels"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +ดังที่เราเห็น การใช้ tokenizer ภาษาอังกฤษเพื่อประมวลผลประโยคภาษาฝรั่งเศสล่วงหน้าส่งผลให้มี token มากขึ้น เนื่องจาก tokenizer ไม่รู้จักคำภาษาฝรั่งเศสใดๆ (ยกเว้นคำที่ปรากฏในภาษาอังกฤษ เช่น "discussion") + +เนื่องจาก `inputs` เป็นพจนานุกรมที่มีคีย์ปกติของเรา (ID อินพุต มาสก์ความสนใจ ฯลฯ) ขั้นตอนสุดท้ายคือการกำหนดฟังก์ชันการประมวลผลล่วงหน้าที่เราจะนำไปใช้กับชุดข้อมูล: + +```python +max_length = 128 + + +def preprocess_function(examples): + inputs = [ex["en"] for ex in examples["translation"]] + targets = [ex["fr"] for ex in examples["translation"]] + model_inputs = tokenizer( + inputs, text_target=targets, max_length=max_length, truncation=True + ) + return model_inputs +``` + +โปรดทราบว่าเราตั้งค่าความยาวสูงสุดเท่ากันสำหรับอินพุตและเอาต์พุตของเรา เนื่องจากข้อความที่เรากำลังพูดถึงดูค่อนข้างสั้น เราจึงใช้ 128 + + + +💡 หากคุณกำลังใช้โมเดล T5 (โดยเฉพาะอย่างยิ่ง หนึ่งในจุดตรวจสอบ `t5-xxx`) โมเดลจะคาดหวังว่าอินพุตข้อความจะมีคำนำหน้าระบุงานที่ทำอยู่ เช่น `translate: English to French:` + + + + + +⚠️ เราไม่ใส่ใจกับหน้ากากความสนใจของเป้าหมาย เนื่องจากโมเดลไม่ได้คาดหวังไว้ แต่ควรตั้งค่าป้ายกำกับที่สอดคล้องกับโทเค็นการเติมเป็น `-100` แทน เพื่อที่ป้ายเหล่านั้นจะถูกละเว้นในการคำนวณการสูญเสีย ผู้รวบรวมข้อมูลของเราจะดำเนินการนี้ในภายหลัง เนื่องจากเราใช้การเติมแบบไดนามิก แต่หากคุณใช้การเติมภายในที่นี่ คุณควรปรับฟังก์ชันการประมวลผลล่วงหน้าเพื่อตั้งค่าป้ายกำกับทั้งหมดที่สอดคล้องกับโทเค็นการเติมเป็น `-100` + + + +ตอนนี้เราสามารถใช้การประมวลผลล่วงหน้านั้นกับการแยกชุดข้อมูลทั้งหมดของเราได้ในคราวเดียว: + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +เมื่อข้อมูลได้รับการประมวลผลล่วงหน้าแล้ว เราก็พร้อมที่จะปรับแต่งโมเดลที่ฝึกไว้ล่วงหน้าของเราแล้ว! + +{#if fw === 'pt'} + +## การปรับแต่งโมเดลอย่างละเอียดด้วย `Trainer` API[[การปรับแต่งโมเดลอย่างละเอียดด้วย-trainer-api]] + +โค้ดจริงที่ใช้ `Trainer` จะเหมือนเดิม โดยมีการเปลี่ยนแปลงเพียงเล็กน้อย: เราใช้ [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) ที่นี่ ซึ่งเป็นคลาสย่อยของ `Trainer` ที่จะช่วยให้เราจัดการกับการประเมินได้อย่างเหมาะสม โดยใช้เมธอด `generate()` เพื่อทำนายเอาต์พุตจากอินพุต เราจะเจาะลึกรายละเอียดมากขึ้นเมื่อเราพูดถึงการคำนวณหน่วยเมตริก + +ก่อนอื่น เราต้องการโมเดลจริงเพื่อปรับแต่งอย่างละเอียด เราจะใช้ `AutoModel` API ตามปกติ: + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## การปรับแต่งโมเดลอย่างละเอียดด้วย Keras[[การปรับแต่งโมเดลอย่างละเอียดด้วย-keras]] + +ก่อนอื่น เราต้องการโมเดลจริงเพื่อปรับแต่งอย่างละเอียด เราจะใช้ `AutoModel` API ตามปกติ: + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 จุด checkpoint `Helsinki-NLP/opus-mt-en-fr` มีเพียงน้ำหนักของ PyTorch ดังนั้น +คุณจะได้รับข้อผิดพลาดหากคุณพยายามโหลดโมเดลโดยไม่ใช้ +อาร์กิวเมนต์ `from_pt=True` ในเมธอด `from_pretrained()` เมื่อคุณระบุ +`from_pt=True` ไลบรารีจะดาวน์โหลดและแปลงน้ำหนัก PyTorch สำหรับคุณโดยอัตโนมัติ อย่างที่คุณเห็น มันง่ายมากที่จะสลับไปมาระหว่าง +เฟรมเวิร์กใน 🤗 Transformers! + + + +{/if} + +ปรดทราบว่าครั้งนี้ เรากำลังใช้แบบจำลองที่ได้รับการฝึกในงานแปลและสามารถใช้งานได้จริงแล้ว ดังนั้นจึงไม่มีคำเตือนเกี่ยวกับน้ำหนักที่หายไปหรือน้ำหนักที่เริ่มต้นใหม่ + +### การรวบรวมข้อมูล[[การรวบรวมข้อมูล]] + +เราจะต้องมีตัวเปรียบเทียบข้อมูลเพื่อจัดการกับช่องว่างภายในสำหรับการจัดชุดแบบไดนามิก เราไม่สามารถใช้ `DataCollatorWithPadding` เหมือนใน [บทที่ 3](/course/th/chapter3) ในกรณีนี้ไม่ได้ เนื่องจากนั่นเป็นเพียงการเสริมอินพุตเท่านั้น (ID อินพุต, มาสก์ความสนใจ และ ID ประเภทโทเค็น) ฉลากของเราควรได้รับการบุให้มีความยาวสูงสุดที่พบในฉลาก และตามที่กล่าวไว้ก่อนหน้านี้ ค่าการเสริมที่ใช้ในการเสริมป้ายกำกับควรเป็น `-100` และไม่ใช่โทเค็นการเสริมของโทเค็น เพื่อให้แน่ใจว่าค่าที่เสริมไว้เหล่านั้นจะถูกละเว้นในการคำนวณการสูญเสีย + +ทั้งหมดนี้ทำโดย [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq) เช่นเดียวกับ `DataCollatorWithPadding` มันต้องใช้ `tokenizer` ที่ใช้ในการประมวลผลอินพุตล่วงหน้า แต่ก็ใช้ `model` ด้วย เนื่องจากตัวรวบรวมข้อมูลนี้ยังต้องรับผิดชอบในการเตรียม ID อินพุตตัวถอดรหัส ซึ่งเป็นเวอร์ชันที่ถูกเลื่อนของป้ายกำกับด้วยโทเค็นพิเศษที่จุดเริ่มต้น เนื่องจากการเปลี่ยนแปลงนี้ทำแตกต่างกันเล็กน้อยสำหรับสถาปัตยกรรมที่แตกต่างกัน `DataCollatorForSeq2Seq` จำเป็นต้องทราบอ็อบเจ็กต์ `model`: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +เพื่อทดสอบสิ่งนี้กับตัวอย่างบางส่วน เราเพียงเรียกมันในรายการตัวอย่างจากชุดการฝึกโทเค็นของเรา: + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) +batch.keys() +``` + +```python out +dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) +``` + +เราสามารถตรวจสอบได้ว่าฉลากของเราได้รับการเสริมความยาวสูงสุดของชุดโดยใช้ `-100`: + +```py +batch["labels"] +``` + +```python out +tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, + -100, -100, -100, -100, -100, -100], + [ 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, + 550, 7032, 5821, 7907, 12649, 0]]) +``` + +และเรายังสามารถดู ID อินพุตของตัวถอดรหัสเพื่อดูว่าเป็นเวอร์ชันที่เลื่อนของป้ายกำกับ: + +```py +batch["decoder_input_ids"] +``` + +```python out +tensor([[59513, 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, + 59513, 59513, 59513, 59513, 59513, 59513], + [59513, 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, + 817, 550, 7032, 5821, 7907, 12649]]) +``` + +ต่อไปนี้เป็นป้ายกำกับสำหรับองค์ประกอบที่หนึ่งและที่สองในชุดข้อมูลของเรา: + +```py +for i in range(1, 3): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0] +[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0] +``` + +{#if fw === 'pt'} + +เราจะส่ง `data_collator` นี้ไปยัง `Seq2SeqTrainer` ต่อไปเรามาดูตัวชี้วัดกัน + +{:else} + +ตอนนี้เราสามารถใช้ `data_collator` นี้เพื่อแปลงชุดข้อมูลแต่ละชุดของเราเป็น `tf.data.Dataset` พร้อมสำหรับการฝึกอบรม: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### เมตริก[[เมตริก]] + + + +{#if fw === 'pt'} + +คุณลักษณะที่ `Seq2SeqTrainer` เพิ่มให้กับ `Trainer` ระดับซูเปอร์คลาสคือความสามารถในการใช้เมธอด `generate()` ในระหว่างการประเมินหรือการทำนาย ในระหว่างการฝึก โมเดลจะใช้ `decoder_input_ids` พร้อมกับมาส์กความสนใจเพื่อให้แน่ใจว่าจะไม่ใช้โทเค็นหลังจากโทเค็นที่พยายามคาดการณ์ เพื่อเร่งความเร็วในการฝึก ในระหว่างการอนุมาน เราจะไม่สามารถใช้สิ่งเหล่านั้นได้เนื่องจากเราไม่มีป้ายกำกับ ดังนั้นจึงเป็นความคิดที่ดีที่จะประเมินแบบจำลองของเราด้วยการตั้งค่าเดียวกัน + +ดังที่เราเห็นใน [บทที่ 1](/course/th/chapter1/6) ตัวถอดรหัสทำการอนุมานโดยการทำนายโทเค็นทีละรายการ ซึ่งเป็นสิ่งที่นำไปใช้เบื้องหลังใน 🤗 Transformers ด้วยเมธอด `generate()` `Seq2SeqTrainer` จะให้เราใช้วิธีการนั้นในการประเมินหากเราตั้งค่า `predict_with_generate=True` + +{/if} + +ตัวชี้วัดแบบดั้งเดิมที่ใช้สำหรับการแปลคือ [คะแนน BLEU](https://en.wikipedia.org/wiki/BLEU) ซึ่งนำมาใช้ใน [บทความปี 2002](https://aclanthology.org/P02-1040.pdf) โดย Kishore Papineni และคณะ คะแนน BLEU จะประเมินว่าคำแปลใกล้เคียงกับป้ายกำกับเพียงใด ไม่ได้วัดความชัดเจนหรือความถูกต้องทางไวยากรณ์ของผลลัพธ์ที่สร้างของแบบจำลอง แต่ใช้กฎทางสถิติเพื่อให้แน่ใจว่าคำทั้งหมดในผลลัพธ์ที่สร้างขึ้นจะปรากฏในเป้าหมายด้วย นอกจากนี้ ยังมีกฎที่ลงโทษการใช้คำเดียวกันซ้ำๆ หากไม่ซ้ำกันในเป้าหมายด้วย (เพื่อหลีกเลี่ยงรูปแบบที่ส่งออกประโยค เช่น `"the the the the"`) และประโยคเอาต์พุตที่สั้นกว่าที่อยู่ใน เป้าหมาย (เพื่อหลีกเลี่ยงโมเดลที่ส่งออกประโยคเช่น `"the"`) + +จุดอ่อนประการหนึ่งของ BLEU คือคาดว่าข้อความจะถูกโทเค็นไนซ์แล้ว ซึ่งทำให้ยากต่อการเปรียบเทียบคะแนนระหว่างโมเดลที่ใช้โทเค็นไนเซอร์ที่แตกต่างกัน ดังนั้น เมตริกที่ใช้บ่อยที่สุดสำหรับการเปรียบเทียบโมเดลการแปลในปัจจุบันคือ [SacreBLEU](https://github.com/mjpost/sacrebleu) ซึ่งจัดการกับจุดอ่อนนี้ (และอื่นๆ) ด้วยการกำหนดขั้นตอนโทเค็นให้เป็นมาตรฐาน หากต้องการใช้ตัวชี้วัดนี้ เราต้องติดตั้งไลบรารี SacreBLEU ก่อน: + +```py +!pip install sacrebleu +``` + +จากนั้นเราสามารถโหลดมันผ่าน `evaluate.load()` เหมือนที่เราทำใน [บทที่ 3](/course/th/chapter3): + +```py +import evaluate + +metric = evaluate.load("sacrebleu") +``` + +เมตริกนี้จะใช้ข้อความเป็นอินพุตและเป้าหมาย ได้รับการออกแบบมาเพื่อยอมรับเป้าหมายที่ยอมรับได้หลายเป้าหมาย เนื่องจากมักจะมีการแปลประโยคเดียวกันที่ยอมรับได้หลายรายการ ชุดข้อมูลที่เราใช้จะมีเพียงเป้าหมายเดียว แต่ไม่ใช่เรื่องแปลกใน NLP ที่จะค้นหาชุดข้อมูลที่มีหลายประโยคเป็นป้ายกำกับ ดังนั้นการคาดคะเนควรเป็นรายการประโยค แต่การอ้างอิงควรเป็นรายการประโยค + +มาทดสอบตัวอย่างกัน: + +```py +predictions = [ + "This plugin lets you translate web pages between several languages automatically." +] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 46.750469682990165, + 'counts': [11, 6, 4, 3], + 'totals': [12, 11, 10, 9], + 'precisions': [91.67, 54.54, 40.0, 33.33], + 'bp': 0.9200444146293233, + 'sys_len': 12, + 'ref_len': 13} +``` + +ซึ่งได้คะแนน BLEU ที่ 46.75 ซึ่งค่อนข้างดี สำหรับการอ้างอิง โมเดล Transformer ดั้งเดิมในเอกสาร ["Attention Is All You Need"](https://arxiv.org/pdf/1706.03762.pdf) ได้รับ คะแนน BLEU 41.8 ในงานแปลที่คล้ายกันระหว่างภาษาอังกฤษและภาษาฝรั่งเศส! (สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวชี้วัดแต่ละรายการ เช่น `counts` และ `bp` โปรดดูที่ [พื้นที่เก็บข้อมูล SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74 ).) ในทางกลับกัน ถ้าเราลองใช้การทำนายที่ไม่ดีสองประเภท (การซ้ำหลายครั้งหรือสั้นเกินไป) ซึ่งมักจะมาจากโมเดลการแปล เราจะได้คะแนน BLEU ที่ค่อนข้างแย่: + +```py +predictions = ["This This This This"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 1.683602693167689, + 'counts': [1, 0, 0, 0], + 'totals': [4, 3, 2, 1], + 'precisions': [25.0, 16.67, 12.5, 12.5], + 'bp': 0.10539922456186433, + 'sys_len': 4, + 'ref_len': 13} +``` + +```py +predictions = ["This plugin"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 0.0, + 'counts': [2, 1, 0, 0], + 'totals': [2, 1, 0, 0], + 'precisions': [100.0, 100.0, 0.0, 0.0], + 'bp': 0.004086771438464067, + 'sys_len': 2, + 'ref_len': 13} +``` + +คะแนนสามารถอยู่ระหว่าง 0 ถึง 100 และยิ่งสูงก็ยิ่งดี + +{#if fw === 'tf'} + +หากต้องการรับจากเอาต์พุตของโมเดลเป็นข้อความที่หน่วยวัดสามารถใช้ได้ เราจะใช้เมธอด `tokenizer.batch_decode()` เราแค่ต้องทำความสะอาด `-100` ทั้งหมดในฉลาก โทเค็นไนเซอร์จะทำเช่นเดียวกันกับโทเค็นการเติมโดยอัตโนมัติ เรามากำหนดฟังก์ชันที่ใช้โมเดลและชุดข้อมูลของเราและคำนวณเมตริกจากโมเดลนั้น นอกจากนี้เรายังจะใช้เคล็ดลับที่เพิ่มประสิทธิภาพได้อย่างมาก ด้วยการคอมไพล์โค้ดการสร้างของเราด้วย [XLA](https://www.tensorflow.org/xla) ซึ่งเป็นคอมไพเลอร์พีชคณิตเชิงเส้นแบบเร่งของ TensorFlow XLA ใช้การปรับให้เหมาะสมต่างๆ กับกราฟการคำนวณของโมเดล และส่งผลให้มีการปรับปรุงความเร็วและการใช้หน่วยความจำอย่างมีนัยสำคัญ ตามที่อธิบายไว้ใน Hugging Face [บล็อก](https://huggingface.co/blog/tf-xla-generate) XLA ทำงานได้ดีที่สุดเมื่อรูปร่างอินพุตของเราไม่แตกต่างกันมากเกินไป เพื่อจัดการสิ่งนี้ เราจะแพดอินพุตของเราให้เป็นทวีคูณของ 128 และสร้างชุดข้อมูลใหม่ด้วยตัวเปรียบเทียบการแพด จากนั้นเราจะใช้มัณฑนากร `@tf.function(jit_compile=True)` กับฟังก์ชันการสร้างของเรา ซึ่ง ทำเครื่องหมายฟังก์ชันทั้งหมดสำหรับการคอมไพล์ด้วย XLA + +```py +import numpy as np +import tensorflow as tf +from tqdm import tqdm + +generation_data_collator = DataCollatorForSeq2Seq( + tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=128 +) + +tf_generate_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=generation_data_collator, + shuffle=False, + batch_size=8, +) + + +@tf.function(jit_compile=True) +def generate_with_xla(batch): + return model.generate( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + max_new_tokens=128, + ) + + +def compute_metrics(): + all_preds = [] + all_labels = [] + + for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = labels.numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) + + result = metric.compute(predictions=all_preds, references=all_labels) + return {"bleu": result["score"]} +``` + +{:else} + +หากต้องการรับจากเอาต์พุตของโมเดลเป็นข้อความที่หน่วยวัดสามารถใช้ได้ เราจะใช้เมธอด `tokenizer.batch_decode()` เราเพียงแค่ต้องล้าง `-100` ทั้งหมดในป้ายกำกับ (โทเค็นจะทำเช่นเดียวกันกับโทเค็นการเติมโดยอัตโนมัติ): + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # In case the model returns more than the prediction logits + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100s in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + + result = metric.compute(predictions=decoded_preds, references=decoded_labels) + return {"bleu": result["score"]} +``` + +{/if} + +เมื่อเสร็จแล้ว เราก็พร้อมที่จะปรับแต่งโมเดลของเรา! + + +### การปรับแต่งโมเดลอย่างละเอียด[[การปรับแต่งโมเดลอย่างละเอียด]] + +ขั้นตอนแรกคือการเข้าสู่ระบบ Hugging Face เพื่อให้คุณสามารถอัปโหลดผลลัพธ์ของคุณไปยัง Model Hub มีฟังก์ชันอำนวยความสะดวกที่จะช่วยคุณในเรื่องนี้ในโน้ตบุ๊ก: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +นี่จะแสดงวิดเจ็ตที่คุณสามารถป้อนข้อมูลรับรองการเข้าสู่ระบบ Hugging Face ของคุณได้ + +หากคุณไม่ได้ทำงานในโน้ตบุ๊ก เพียงพิมพ์บรรทัดต่อไปนี้ในเทอร์มินัลของคุณ: + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +ก่อนที่เราจะเริ่มต้น มาดูกันว่าเราได้รับผลลัพธ์ประเภทใดจากโมเดลของเราโดยไม่มีการฝึกอบรมใดๆ: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +เมื่อเสร็จแล้ว เราก็เตรียมทุกอย่างที่จำเป็นเพื่อคอมไพล์และฝึกโมเดลของเราได้ โปรดสังเกตการใช้ `tf.keras.mixed_precision.set_global_policy("mixed_float16")` -- ซึ่งจะเป็นการบอกให้ Keras ฝึกฝนการใช้ float16 ซึ่งสามารถเร่งความเร็วได้อย่างมากบน GPU ที่รองรับ (Nvidia 20xx/V100 หรือใหม่กว่า) + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# 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_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +ต่อไป เราจะกำหนด `PushToHubCallback` เพื่ออัปโหลดโมเดลของเราไปยัง Hub ในระหว่างการฝึก ดังที่เราเห็นใน [ส่วนที่ 2]((/course/th/chapter7/2)) จากนั้นเราก็ปรับโมเดลให้เข้ากับ callback นั้น: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir="marian-finetuned-kde4-en-to-fr", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +โปรดทราบว่าคุณสามารถระบุชื่อของพื้นที่เก็บข้อมูลที่คุณต้องการพุชไปได้ด้วยอาร์กิวเมนต์ `hub_model_id` (โดยเฉพาะ คุณจะต้องใช้อาร์กิวเมนต์นี้เพื่อพุชไปยังองค์กร) ตัวอย่างเช่น เมื่อเราผลักโมเดลไปที่ [`huggingface-course` Organization](https://huggingface.co/huggingface-course) เราได้เพิ่ม `hub_model_id="huggingface-course/marian-finetuned-kde4-en- to-fr"` ถึง `Seq2SeqTrainingArguments` ตามค่าเริ่มต้น พื้นที่เก็บข้อมูลที่ใช้จะอยู่ในเนมสเปซของคุณและตั้งชื่อตามไดเร็กทอรีเอาต์พุตที่คุณตั้งค่า ดังนั้นที่นี่จะเป็น `"sgugger/marian-finetuned-kde4-en-to-fr"` (ซึ่งเป็นโมเดลที่เราเชื่อมโยงด้วย ในตอนต้นของส่วนนี้) + + + +💡 หากไดเร็กทอรีเอาต์พุตที่คุณใช้มีอยู่แล้ว จะต้องเป็นโคลนในเครื่องของที่เก็บที่คุณต้องการพุชไป หากไม่เป็นเช่นนั้น คุณจะได้รับข้อผิดพลาดเมื่อเรียก `model.fit()` และจะต้องตั้งชื่อใหม่ + + + +สุดท้ายนี้ เรามาดูกันว่าเมตริกของเราจะเป็นอย่างไรเมื่อการฝึกอบรมเสร็จสิ้นแล้ว: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +ในขั้นตอนนี้ คุณสามารถใช้วิดเจ็ตการอนุมานบน Model Hub เพื่อทดสอบโมเดลของคุณและแบ่งปันกับเพื่อนๆ ของคุณได้ คุณปรับแต่งโมเดลในงานแปลได้สำเร็จ ขอแสดงความยินดีด้วย! + +{:else} + +เมื่อเสร็จแล้ว เราก็สามารถกำหนด `Seq2SeqTrainingArguments` ของเราได้ เช่นเดียวกับ `Trainer` เราใช้คลาสย่อยของ `TrainingArguments` ที่มีตัวแปลเพิ่มเติมสองสามตัว: + +```python +from transformers import Seq2SeqTrainingArguments + +args = Seq2SeqTrainingArguments( + f"marian-finetuned-kde4-en-to-fr", + evaluation_strategy="no", + save_strategy="epoch", + learning_rate=2e-5, + per_device_train_batch_size=32, + per_device_eval_batch_size=64, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=3, + predict_with_generate=True, + fp16=True, + push_to_hub=True, +) +``` + +นอกเหนือจากไฮเปอร์พารามิเตอร์ตามปกติ (เช่น learning rate, number of epochs, batch size, and some weight decay), ต่อไปนี้คือการเปลี่ยนแปลงเล็กๆ น้อยๆ เมื่อเทียบกับสิ่งที่เราเห็นในส่วนที่แล้ว: + +- เราไม่ได้กำหนดการประเมินเป็นประจำ เนื่องจากการประเมินจะใช้เวลาระยะหนึ่ง เราจะประเมินแบบจำลองของเราหนึ่งครั้งก่อนการฝึกและหลังการฝึก +- เราตั้งค่า `fp16=True` ซึ่งจะช่วยเร่งความเร็วการฝึกอบรมบน GPU สมัยใหม่ +- เราตั้งค่า `predict_with_generate=True` ตามที่กล่าวไว้ข้างต้น +- เราใช้ `push_to_hub=True` เพื่ออัปโหลดโมเดลไปยัง Hub เมื่อสิ้นสุดแต่ละ epoch + +โปรดทราบว่าคุณสามารถระบุชื่อเต็มของพื้นที่เก็บข้อมูลที่คุณต้องการพุชไปได้ด้วยอาร์กิวเมนต์ `hub_model_id` (โดยเฉพาะ คุณจะต้องใช้อาร์กิวเมนต์นี้เพื่อพุชไปยังองค์กร) ตัวอย่างเช่น เมื่อเราผลักโมเดลไปที่ [`huggingface-course` Organization](https://huggingface.co/huggingface-course) เราได้เพิ่ม `hub_model_id="huggingface-course/marian-finetuned-kde4-en- to-fr"` ถึง `Seq2SeqTrainingArguments` ตามค่าเริ่มต้น พื้นที่เก็บข้อมูลที่ใช้จะอยู่ในเนมสเปซของคุณและตั้งชื่อตามไดเร็กทอรีเอาต์พุตที่คุณตั้งค่า ดังนั้นในกรณีของเราจะเป็น `"sgugger/marian-finetuned-kde4-en-to-fr"` (ซึ่งเป็นโมเดลที่เรา เชื่อมโยงกับตอนต้นของส่วนนี้) + + + +💡 หากไดเร็กทอรีเอาต์พุตที่คุณใช้มีอยู่แล้ว จะต้องเป็นโคลนในเครื่องของที่เก็บที่คุณต้องการพุชไป หากไม่เป็นเช่นนั้น คุณจะได้รับข้อผิดพลาดเมื่อกำหนด `Seq2SeqTrainer` ของคุณและจะต้องตั้งชื่อใหม่ + + + + +ในที่สุด เราก็ส่งทุกอย่างไปที่ `Seq2SeqTrainer`: + +```python +from transformers import Seq2SeqTrainer + +trainer = Seq2SeqTrainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +ก่อนการฝึก เราจะดูคะแนนที่โมเดลของเราได้รับก่อน เพื่อตรวจสอบอีกครั้งว่าเราไม่ได้ทำให้สิ่งต่าง ๆ แย่ลงด้วยการปรับแต่งของเรา คำสั่งนี้จะใช้เวลาสักครู่ ดังนั้นคุณจึงสามารถดื่มกาแฟในขณะที่ดำเนินการได้: + +```python +trainer.evaluate(max_length=max_length) +``` + +```python out +{'eval_loss': 1.6964408159255981, + 'eval_bleu': 39.26865061007616, + 'eval_runtime': 965.8884, + 'eval_samples_per_second': 21.76, + 'eval_steps_per_second': 0.341} +``` + +คะแนน BLEU ที่ 39 ก็ไม่ได้แย่นัก ซึ่งสะท้อนให้เห็นว่าแบบจำลองของเราแปลประโยคภาษาอังกฤษเป็นภาษาฝรั่งเศสได้ดีอยู่แล้ว + +ต่อไปคือการฝึกอบรมซึ่งจะใช้เวลาสักหน่อย: + +```python +trainer.train() +``` + +โปรดทราบว่าในขณะที่การฝึกเกิดขึ้น แต่ละครั้งที่มีการบันทึกโมเดล (ที่นี่ ทุก epoch) โมเดลจะถูกอัปโหลดไปยัง Hub ในเบื้องหลัง ด้วยวิธีนี้ คุณจะสามารถกลับมาฝึกต่อในเครื่องอื่นได้หากจำเป็น + +เมื่อการฝึกอบรมเสร็จสิ้น เราจะประเมินแบบจำลองของเราอีกครั้ง หวังว่าเราจะได้เห็นการปรับปรุงในคะแนน BLEU + +```py +trainer.evaluate(max_length=max_length) +``` + +```python out +{'eval_loss': 0.8558505773544312, + 'eval_bleu': 52.94161337775576, + 'eval_runtime': 714.2576, + 'eval_samples_per_second': 29.426, + 'eval_steps_per_second': 0.461, + 'epoch': 3.0} +``` + +นั่นเป็นการปรับปรุงเกือบ 14 แต้มซึ่งดีมาก + +สุดท้ายนี้ เราใช้เมธอด `push_to_hub()` เพื่อให้แน่ใจว่าเราจะอัปโหลดโมเดลเวอร์ชันล่าสุด `Trainer` ยังร่างการ์ดโมเดลพร้อมผลการประเมินทั้งหมดแล้วอัปโหลด การ์ดโมเดลนี้มีข้อมูลเมตาที่ช่วยให้ Model Hub เลือกวิดเจ็ตสำหรับการสาธิตการอนุมาน โดยปกติแล้ว ไม่จำเป็นต้องพูดอะไร เนื่องจากสามารถอนุมานวิดเจ็ตที่ถูกต้องจากคลาสโมเดลได้ แต่ในกรณีนี้ สามารถใช้คลาสโมเดลเดียวกันสำหรับปัญหาลำดับต่อลำดับทุกประเภท ดังนั้นเราจึงระบุว่าเป็นการแปล แบบอย่าง: + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +คำสั่งนี้จะส่งคืน URL ของการคอมมิตที่เพิ่งทำไป หากคุณต้องการตรวจสอบ: + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +ในขั้นตอนนี้ คุณสามารถใช้วิดเจ็ตการอนุมานบน Model Hub เพื่อทดสอบโมเดลของคุณและแบ่งปันกับเพื่อนๆ ของคุณได้ คุณปรับแต่งโมเดลในงานแปลได้สำเร็จ ขอแสดงความยินดีด้วย! + +หากคุณต้องการเจาะลึกลงไปในลูปการฝึกซ้อมมากขึ้น ตอนนี้เราจะแสดงวิธีทำสิ่งเดียวกันโดยใช้ 🤗 Accelerate + +{/if} + +{#if fw === 'pt'} + +## การวนลูปฝึกอบรมแบบกำหนดเอง[[การวนลูปฝึกอบรมแบบกำหนดเอง]] + +ตอนนี้เรามาดูวงจรการฝึกซ้อมทั้งหมดกัน เพื่อให้คุณปรับแต่งส่วนต่างๆ ที่ต้องการได้อย่างง่ายดาย มันจะดูเหมือนกับสิ่งที่เราทำใน [ส่วนที 2](/course/th/chapter7/2) และ [บทที่ 3](/course/th/chapter3/4) มาก + +### เตรียมทุกอย่างเพื่อการฝึก[[เตรียมทุกอย่างเพื่อการฝึก]] + +คุณเคยเห็นทั้งหมดนี้มาสองสามครั้งแล้ว ดังนั้นเราจะอธิบายโค้ดอย่างรวดเร็ว ก่อนอื่น เราจะสร้าง `DataLoader`s จากชุดข้อมูลของเรา หลังจากตั้งค่าชุดข้อมูลเป็นรูปแบบ `"torch"` แล้ว เราก็จะได้ PyTorch tensors: + +```py +from torch.utils.data import DataLoader + +tokenized_datasets.set_format("torch") +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +ต่อไป เราจะสร้างโมเดลของเราขึ้นมาใหม่ เพื่อให้แน่ใจว่าเราจะไม่ทำการปรับแต่งแบบละเอียดจากเมื่อก่อนอีกต่อไป แต่เริ่มต้นจากโมเดลที่ได้รับการฝึกไว้ล่วงหน้าอีกครั้ง: + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +จากนั้นเราจะต้องมีเครื่องมือเพิ่มประสิทธิภาพ: + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +เมื่อเรามีอ็อบเจ็กต์ทั้งหมดแล้ว เราก็สามารถส่งมันไปที่เมธอด `accelerator.prepare()` ได้ โปรดทราบว่าหากคุณต้องการฝึก TPU ในสมุดบันทึก Colab คุณจะต้องย้ายโค้ดทั้งหมดนี้ไปยังฟังก์ชันการฝึก และไม่ควรเรียกใช้เซลล์ใดๆ ที่สร้างอินสแตนซ์ `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +ตอนนี้เราได้ส่ง `train_dataloader` ไปที่ `accelerator.prepare()` แล้ว เราสามารถใช้ความยาวของมันเพื่อคำนวณจำนวนขั้นตอนการฝึกได้ โปรดจำไว้ว่าเราควรทำเช่นนี้เสมอหลังจากเตรียม dataloader เนื่องจากวิธีการดังกล่าวจะเปลี่ยนความยาวของ `DataLoader` เราใช้กำหนดการเชิงเส้นแบบคลาสสิกจากอัตราการเรียนรู้ถึง 0: + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +สุดท้ายนี้ ในการผลักดันโมเดลของเราไปที่ Hub เราจะต้องสร้างออบเจ็กต์ `Repository` ในโฟลเดอร์ที่ใช้งานได้ ขั้นแรกให้เข้าสู่ระบบ Hugging Face Hub หากคุณยังไม่ได้เข้าสู่ระบบ เราจะกำหนดชื่อที่เก็บจาก ID โมเดลที่เราต้องการให้กับโมเดลของเรา (อย่าลังเลที่จะแทนที่ `repo_name` ด้วยตัวเลือกของคุณเอง เพียงต้องมีชื่อผู้ใช้ของคุณ ซึ่งเป็นสิ่งที่ฟังก์ชัน `get_full_repo_name()` ทำ ): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "marian-finetuned-kde4-en-to-fr-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/marian-finetuned-kde4-en-to-fr-accelerate' +``` + +จากนั้นเราสามารถโคลนพื้นที่เก็บข้อมูลนั้นในโฟลเดอร์ในเครื่องได้ หากมีอยู่แล้ว โฟลเดอร์ในเครื่องนี้ควรเป็นโคลนของพื้นที่เก็บข้อมูลที่เรากำลังทำงานด้วย: + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +ตอนนี้เราสามารถอัปโหลดทุกสิ่งที่เราบันทึกไว้ใน `output_dir` ได้โดยการเรียกเมธอด `repo.push_to_hub()` ซึ่งจะช่วยให้เราอัปโหลดโมเดลระดับกลางในตอนท้ายของแต่ละ epoch ได้ + +### วงจรการฝึกอบรม[[วงจรการฝึกอบรม]] + +ตอนนี้เราพร้อมที่จะเขียนลูปการฝึกอบรมฉบับเต็มแล้ว เพื่อให้ส่วนการประเมินง่ายขึ้น เราได้กำหนดฟังก์ชัน `postprocess()` นี้ ซึ่งใช้การคาดการณ์และป้ายกำกับ และแปลงเป็นรายการสตริงที่ออบเจ็กต์ `metric` ของเราจะคาดหวัง: + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Replace -100 in the labels as we can't decode them. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +วงจรการฝึกอบรมมีลักษณะคล้ายกับใน [ส่วนที่ 2](/course/th/chapter7/2) และ [บทที่ 3](/course/th/chapter3) มาก โดยมีความแตกต่างเล็กน้อยในส่วนการประเมิน ดังนั้นเรามาเน้นที่เรื่องนั้นกันดีกว่า + +สิ่งแรกที่ควรทราบคือเราใช้เมธอด `generate()` เพื่อคำนวณการคาดการณ์ แต่นี่เป็นวิธีการในโมเดลพื้นฐานของเรา ไม่ใช่โมเดลที่ห่อ 🤗 Accelerate สร้างขึ้นในเมธอด `prepare()` นั่นคือสาเหตุที่เราแกะโมเดลออกก่อน แล้วจึงเรียกเมธอดนี้ + +อย่างที่สองก็คือ เช่นเดียวกับ [การจำแนกโทเค็น](/course/th/chapter7/2) สองกระบวนการอาจมีการเสริมอินพุตและป้ายกำกับเป็นรูปร่างที่แตกต่างกัน ดังนั้นเราจึงใช้ `accelerator.pad_across_processes()` เพื่อทำการคาดการณ์และป้ายกำกับ รูปร่างเดียวกันก่อนที่จะเรียกเมธอด `gather()` หากเราไม่ทำเช่นนี้ การประเมินจะเกิดข้อผิดพลาดหรือหยุดทำงานตลอดไป + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for batch in tqdm(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + max_length=128, + ) + labels = batch["labels"] + + # Necessary to pad predictions and labels for being gathered + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(generated_tokens) + labels_gathered = accelerator.gather(labels) + + decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=decoded_preds, references=decoded_labels) + + results = metric.compute() + print(f"epoch {epoch}, BLEU score: {results['score']:.2f}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +epoch 0, BLEU score: 53.47 +epoch 1, BLEU score: 54.24 +epoch 2, BLEU score: 54.44 +``` + +เมื่อเสร็จแล้ว คุณควรมีโมเดลที่มีผลลัพธ์ค่อนข้างคล้ายกับโมเดลที่ได้รับการฝึกด้วย `Seq2SeqTrainer` คุณสามารถตรวจสอบอันที่เราฝึกได้โดยใช้โค้ดนี้ที่ [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate) และหากคุณต้องการทดสอบการปรับแต่งใดๆ ในลูปการฝึก คุณสามารถนำไปใช้ได้โดยตรงโดยแก้ไขโค้ดที่แสดงด้านบน! + +{/if} + +## การใช้โมเดลที่ปรับแต่งแล้ว[[การใช้โมเดลที่ปรับแต่งแล้ว]] + +เราได้แสดงให้คุณเห็นแล้วว่าคุณสามารถใช้โมเดลที่เราปรับแต่งอย่างละเอียดบน Model Hub ด้วยวิดเจ็ตการอนุมานได้อย่างไร หากต้องการใช้ภายในเครื่องใน `pipeline` เราเพียงแค่ต้องระบุตัวระบุโมเดลที่เหมาะสม: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut, développer les fils de discussion'}] +``` + +ตามที่คาดไว้ โมเดลที่ได้รับการฝึกล่วงหน้าของเราจะปรับความรู้ให้เข้ากับคลังข้อมูลที่เราปรับแต่ง และแทนที่จะปล่อยให้คำว่า "threads" ในภาษาอังกฤษเพียงอย่างเดียว ตอนนี้กลับแปลเป็นเวอร์ชันทางการของภาษาฝรั่งเศส เช่นเดียวกับ "plugin": + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}] +``` + +อีกตัวอย่างที่ดีของการปรับใช้โดเมน! + + + +✏️ **ตาคุณแล้ว!** โมเดลส่งคืนอะไรในตัวอย่างที่มีคำว่า "email" ที่คุณระบุไว้ก่อนหน้านี้ + + diff --git a/chapters/th/chapter7/5.mdx b/chapters/th/chapter7/5.mdx new file mode 100644 index 000000000..090f19d48 --- /dev/null +++ b/chapters/th/chapter7/5.mdx @@ -0,0 +1,1072 @@ + + +# การสรุปความหมาย[[การสรุปความหมาย]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +ในส่วนนี้ เราจะมาดูกันว่าโมเดล Transformer สามารถใช้เพื่อย่อเอกสารขนาดยาวให้เป็นบทสรุปได้อย่างไร งานที่เรียกว่า _text summarization_ นี่เป็นหนึ่งในงาน NLP ที่ท้าทายที่สุด เนื่องจากต้องใช้ความสามารถที่หลากหลาย เช่น การทำความเข้าใจข้อความที่ยาว และการสร้างข้อความที่สอดคล้องกันซึ่งรวบรวมหัวข้อหลักในเอกสาร อย่างไรก็ตาม เมื่อทำได้ดี การสรุปข้อความเป็นเครื่องมืออันทรงพลังที่สามารถเพิ่มความเร็วให้กับกระบวนการทางธุรกิจต่างๆ โดยแบ่งเบาภาระของผู้เชี่ยวชาญโดเมนในการอ่านเอกสารขนาดยาวอย่างละเอียด + + + +แม้ว่าจะมีโมเดลที่ได้รับการปรับแต่งอย่างละเอียดสำหรับการสรุปอยู่แล้วบน [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads) แต่โมเดลเกือบทั้งหมดเหมาะสำหรับเอกสารภาษาอังกฤษเท่านั้น ดังนั้น เพื่อเพิ่มความแปลกใหม่ในส่วนนี้ เราจะฝึกโมเดลสองภาษาสำหรับภาษาอังกฤษและสเปน ในตอนท้ายของส่วนนี้ คุณจะมี [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) ที่สามารถสรุปบทวิจารณ์ของลูกค้าได้เหมือนกับที่แสดงไว้ที่นี่ : + + + +ดังที่เราจะเห็นว่าการสรุปเหล่านี้มีความกระชับเนื่องจากได้เรียนรู้จากชื่อที่ลูกค้าระบุไว้ในการวิจารณ์ผลิตภัณฑ์ของตน เริ่มต้นด้วยการรวบรวมคลังข้อมูลสองภาษาที่เหมาะสมสำหรับงานนี้ + +## การเตรียมคลังข้อมูลหลายภาษา[[การเตรียมคลังข้อมูลหลายภาษา]] + +เราจะใช้ [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) เพื่อสร้างตัวสรุปสองภาษาของเรา คลังข้อมูลนี้ประกอบด้วยบทวิจารณ์ผลิตภัณฑ์ของ Amazon ในหกภาษา และโดยทั่วไปจะใช้เพื่อเปรียบเทียบตัวแยกประเภทหลายภาษา อย่างไรก็ตาม เนื่องจากการรีวิวแต่ละครั้งมีชื่อย่อประกอบ เราจึงสามารถใช้ชื่อดังกล่าวเป็นบทสรุปเป้าหมายเพื่อให้โมเดลของเราเรียนรู้ได้! ในการเริ่มต้น ให้ดาวน์โหลดชุดย่อยภาษาอังกฤษและสเปนจาก Hugging Face Hub: + +```python +from datasets import load_dataset + +spanish_dataset = load_dataset("amazon_reviews_multi", "es") +english_dataset = load_dataset("amazon_reviews_multi", "en") +english_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 200000 + }) + validation: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) + test: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) +}) +``` + +อย่างที่คุณเห็น สำหรับแต่ละภาษา มีบทวิจารณ์ 200,000 รายการสำหรับการแยก 'train' และ 5,000 บทวิจารณ์สำหรับการแยก `validation` และ `test` แต่ละรายการ ข้อมูลบทวิจารณ์ที่เราสนใจมีอยู่ในคอลัมน์ `review_body` และ `review_title` ลองมาดูตัวอย่างบางส่วนโดยการสร้างฟังก์ชันง่ายๆ ที่จะสุ่มตัวอย่างจากชุดการฝึกด้วยเทคนิคที่เราเรียนรู้ใน [บทที่ 5](/course/th/chapter5): + +```python +def show_samples(dataset, num_samples=3, seed=42): + sample = dataset["train"].shuffle(seed=seed).select(range(num_samples)) + for example in sample: + print(f"\n'>> Title: {example['review_title']}'") + print(f"'>> Review: {example['review_body']}'") + + +show_samples(english_dataset) +``` + +```python out +'>> Title: Worked in front position, not rear' +'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.' + +'>> Title: meh' +'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue' + +'>> Title: Can\'t beat these for the money' +'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.' +``` + + + +✏️ **ลองดูสิ!** เปลี่ยนการสุ่ม seed ในคำสั่ง `Dataset.shuffle()` เพื่อสำรวจบทวิจารณ์อื่นๆ ในคลังข้อมูล หากคุณเป็นผู้พูดภาษาสเปน ลองดูบทวิจารณ์บางส่วนใน `spanish_dataset` เพื่อดูว่าชื่อต่างๆ ดูเหมือนเป็นบทสรุปที่สมเหตุสมผลหรือไม่ + + + +ตัวอย่างนี้แสดงความหลากหลายของบทวิจารณ์ที่มักพบทางออนไลน์ ตั้งแต่เชิงบวกไปจนถึงเชิงลบ (และทุกอย่างในระหว่างนั้น!) แม้ว่าตัวอย่างที่มีชื่อ "meh" จะไม่ได้ให้ข้อมูลมากนัก แต่ชื่ออื่นๆ ก็ดูเหมือนเป็นการสรุปบทวิจารณ์ที่ดีพอสมควร การฝึกอบรมโมเดลการสรุปสำหรับการตรวจสอบทั้งหมด 400,000 รายการอาจใช้เวลานานเกินไปบน GPU ตัวเดียว ดังนั้น เราจะมุ่งเน้นไปที่การสร้างข้อมูลสรุปสำหรับโดเมนเดียวของผลิตภัณฑ์แทน เพื่อให้เข้าใจว่าเราสามารถเลือกโดเมนใดได้ ให้แปลง `english_dataset` เป็น `pandas.DataFrame` และคำนวณจำนวนบทวิจารณ์ต่อหมวดหมู่ผลิตภัณฑ์: + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Show counts for top 20 products +english_df["product_category"].value_counts()[:20] +``` + +```python out +home 17679 +apparel 15951 +wireless 15717 +other 13418 +beauty 12091 +drugstore 11730 +kitchen 10382 +toy 8745 +sports 8277 +automotive 7506 +lawn_and_garden 7327 +home_improvement 7136 +pet_products 7082 +digital_ebook_purchase 6749 +pc 6401 +electronics 6186 +office_product 5521 +shoes 5197 +grocery 4730 +book 3756 +Name: product_category, dtype: int64 +``` + +สินค้ายอดนิยมในชุดข้อมูลภาษาอังกฤษคือสินค้าในครัวเรือน เสื้อผ้า และอุปกรณ์อิเล็กทรอนิกส์ไร้สาย อย่างไรก็ตาม เพื่อให้ยึดถือธีมของ Amazon ต่อไป เราจะมุ่งเน้นไปที่การสรุปบทวิจารณ์หนังสือ เพราะนี่คือสิ่งที่บริษัทก่อตั้งขึ้น! เราเห็นหมวดหมู่ผลิตภัณฑ์สองหมวดหมู่ที่เหมาะกับใบเรียกเก็บเงิน (`หนังสือ` และ `digital_ebook_purchase`) ดังนั้น เรามากรองชุดข้อมูลทั้งสองภาษาสำหรับผลิตภัณฑ์เหล่านี้เท่านั้น ดังที่เราเห็นใน [บทที่ 5](/course/th/chapter5) ฟังก์ชัน `Dataset.filter()` ช่วยให้เราสามารถแบ่งชุดข้อมูลได้อย่างมีประสิทธิภาพมาก ดังนั้นเราจึงกำหนดฟังก์ชันง่ายๆ เพื่อทำสิ่งนี้ได้: + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +ตอนนี้เมื่อเราใช้ฟังก์ชันนี้กับ `english_dataset` และ `spanish_dataset` ผลลัพธ์จะมีเฉพาะแถวที่เกี่ยวข้องกับหมวดหมู่หนังสือ ก่อนที่จะใช้ตัวกรอง มาเปลี่ยนรูปแบบของ `english_dataset` จาก `"pandas"` กลับไปเป็น `"arrow"`: + +```python +english_dataset.reset_format() +``` + +จากนั้น เราสามารถใช้ฟังก์ชันตัวกรอง และเพื่อเป็นการตรวจสอบความเหมาะสม เราจะตรวจสอบตัวอย่างบทวิจารณ์เพื่อดูว่าเกี่ยวข้องกับหนังสือจริงหรือไม่: + +```python +spanish_books = spanish_dataset.filter(filter_books) +english_books = english_dataset.filter(filter_books) +show_samples(english_books) +``` + +```python out +'>> Title: I\'m dissapointed.' +'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.' + +'>> Title: Good art, good price, poor design' +'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar' + +'>> Title: Helpful' +'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' +``` + +โอเค เราจะเห็นว่าบทวิจารณ์ไม่ได้เกี่ยวกับหนังสืออย่างเคร่งครัด และอาจอ้างถึงสิ่งต่างๆ เช่น ปฏิทินและแอปพลิเคชันอิเล็กทรอนิกส์ เช่น OneNote อย่างไรก็ตาม ดูเหมือนว่าโดเมนจะมีสิทธิ์ในการฝึกอบรมโมเดลการสรุป ก่อนที่เราจะดูโมเดลต่างๆ ที่เหมาะกับงานนี้ เรามีการเตรียมข้อมูลขั้นตอนสุดท้ายที่ต้องทำ: รวมบทวิจารณ์ภาษาอังกฤษและสเปนเป็นออบเจ็กต์ `DatasetDict` เดียว 🤗 Dataset มีฟังก์ชัน `concatenate_datasets()` ที่มีประโยชน์ ซึ่ง (ตามชื่อที่แนะนำ) จะซ้อนวัตถุ `Dataset` สองชิ้นไว้ซ้อนกัน ดังนั้น ในการสร้างชุดข้อมูลสองภาษา เราจะวนซ้ำแต่ละการแยก เชื่อมต่อชุดข้อมูลสำหรับการแยกนั้น และสับเปลี่ยนผลลัพธ์เพื่อให้แน่ใจว่าแบบจำลองของเราจะไม่พอดีกับภาษาเดียว: + +```python +from datasets import concatenate_datasets, DatasetDict + +books_dataset = DatasetDict() + +for split in english_books.keys(): + books_dataset[split] = concatenate_datasets( + [english_books[split], spanish_books[split]] + ) + books_dataset[split] = books_dataset[split].shuffle(seed=42) + +# Peek at a few examples +show_samples(books_dataset) +``` + +```python out +'>> Title: Easy to follow!!!!' +'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.' + +'>> Title: PARCIALMENTE DAÑADO' +'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).' + +'>> Title: no lo he podido descargar' +'>> Review: igual que el anterior' +``` + +ดูเหมือนว่ารีวิวนี้เป็นการผสมผสานระหว่างรีวิวภาษาอังกฤษและสเปนอย่างแน่นอน! ตอนนี้เรามีคลังข้อมูลการฝึกอบรมแล้ว สิ่งสุดท้ายที่ต้องตรวจสอบคือการแจกแจงคำในบทวิจารณ์และชื่อเรื่อง นี่เป็นสิ่งสำคัญอย่างยิ่งสำหรับงานการสรุป โดยที่การสรุปอ้างอิงสั้นๆ ในข้อมูลสามารถโน้มน้าวโมเดลให้ส่งออกเพียงหนึ่งหรือสองคำในสรุปที่สร้างขึ้นได้ โครงเรื่องด้านล่างแสดงการแจกแจงคำ และเราจะเห็นว่าชื่อเรื่องมีการบิดเบือนอย่างมากไปเพียง 1-2 คำ: + +
+Word count distributions for the review titles and texts. + +
+ +เพื่อจัดการกับสิ่งนี้ เราจะกรองตัวอย่างที่มีชื่อสั้นมากออก เพื่อให้แบบจำลองของเราสามารถสร้างบทสรุปที่น่าสนใจยิ่งขึ้น เนื่องจากเรากำลังจัดการกับข้อความภาษาอังกฤษและสเปน เราจึงสามารถใช้การศึกษาพฤติกรรมคร่าวๆ เพื่อแยกชื่อบนช่องว่าง จากนั้นใช้เมธอด `Dataset.filter()` ที่เชื่อถือได้ของเราดังนี้: + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +ตอนนี้เราได้เตรียมคลังข้อมูลของเราแล้ว เรามาดูโมเดล Transformer ที่เป็นไปได้สองสามแบบที่อาจปรับแต่งได้! + +## โมเดลสำหรับการสรุปข้อความ[[โมเดลสำหรับการสรุปข้อความ]] + +หากคุณลองคิดดู การสรุปข้อความเป็นงานประเภทเดียวกับการแปลด้วยคอมพิวเตอร์ เรามีเนื้อหาข้อความเช่นบทวิจารณ์ที่เราต้องการ "translate" เป็นเวอร์ชันที่สั้นกว่าซึ่งรวบรวมคุณลักษณะเด่นของอินพุต ดังนั้น โมเดล Transformer ส่วนใหญ่สำหรับการสรุปจึงใช้สถาปัตยกรรมตัวเข้ารหัส-ตัวถอดรหัสที่เราพบครั้งแรกใน [บทที่ 1](/course/th/chapter1) แม้ว่าจะมีข้อยกเว้นบางประการ เช่น ตระกูลโมเดล GPT ซึ่งสามารถใช้ในการสรุปได้ในไม่กี่- การตั้งค่าช็อต ตารางต่อไปนี้แสดงรายการโมเดลที่ได้รับการฝึกล่วงหน้ายอดนิยมบางรุ่นที่สามารถปรับแต่งอย่างละเอียดเพื่อการสรุปได้ + +| Transformer model | Description | Multilingual? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | +| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | + +ดังที่คุณเห็นจากตารางนี้ โมเดล Transformer ส่วนใหญ่สำหรับการสรุป (และงาน NLP ส่วนใหญ่) เป็นแบบภาษาเดียว วิธีนี้จะดีมากหากงานของคุณเป็นภาษาที่มี "ทรัพยากรสูง" เช่น อังกฤษหรือเยอรมัน แต่จะน้อยกว่านั้นสำหรับภาษาอื่นๆ หลายพันภาษาที่ใช้ทั่วโลก โชคดีที่มีรุ่น Transformer หลายภาษา เช่น mT5 และ mBART เข้ามาช่วยเหลือ แบบจำลองเหล่านี้ได้รับการฝึกอบรมล่วงหน้าโดยใช้การสร้างแบบจำลองภาษา แต่มีจุดหักมุม: แทนที่จะฝึกอบรมเกี่ยวกับคลังข้อมูลของภาษาเดียว แบบจำลองเหล่านี้ได้รับการฝึกอบรมร่วมกันเกี่ยวกับข้อความในกว่า 50 ภาษาในคราวเดียว! + +เราจะมุ่งเน้นไปที่ mT5 ซึ่งเป็นสถาปัตยกรรมที่น่าสนใจซึ่งใช้ T5 ซึ่งได้รับการฝึกฝนล่วงหน้าในกรอบงานข้อความเป็นข้อความ ใน T5 งาน NLP ทุกงานได้รับการกำหนดไว้ในรูปแบบของคำนำหน้าพร้อมท์ เช่น `summarize:` ซึ่งเป็นเงื่อนไขให้โมเดลปรับข้อความที่สร้างขึ้นให้เข้ากับพร้อมท์ ดังแสดงในรูปด้านล่าง สิ่งนี้ทำให้ T5 มีความหลากหลายอย่างยิ่ง เนื่องจากคุณสามารถแก้ไขงานต่างๆ มากมายด้วยโมเดลเดียว! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 ไม่ได้ใช้คำนำหน้า แต่มีความอเนกประสงค์เหมือนกับ T5 อยู่มาก และมีข้อดีคือสามารถพูดได้หลายภาษา ตอนนี้เราได้เลือกแบบจำลองแล้ว มาดูการเตรียมข้อมูลสำหรับการฝึกอบรมกันดีกว่า + + + + +✏️ **ลองดูสิ!** เมื่อคุณได้ศึกษาส่วนนี้แล้ว มาดูกันว่า mT5 เปรียบเทียบกับ mBART ได้ดีเพียงใดโดยการปรับแต่งส่วนหลังด้วยเทคนิคเดียวกัน สำหรับคะแนนโบนัส คุณสามารถลองปรับแต่ง T5 อย่างละเอียดเฉพาะบทวิจารณ์ภาษาอังกฤษได้ เนื่องจาก T5 มีพรอมต์คำนำหน้าพิเศษ คุณจะต้องเติม `summarize:` ไว้ข้างหน้าตัวอย่างอินพุตในขั้นตอนการประมวลผลล่วงหน้าด้านล่าง + + + +## การประมวลผลข้อมูลล่วงหน้า[[การประมวลผลข้อมูลล่วงหน้า]] + + + +ภารกิจต่อไปของเราคือการสร้างโทเค็นและเข้ารหัสบทวิจารณ์และชื่อเรื่องของเรา ตามปกติ เราจะเริ่มต้นด้วยการโหลดโทเค็นไนเซอร์ที่เกี่ยวข้องกับจุดตรวจสอบโมเดลที่ได้รับการฝึกล่วงหน้า เราจะใช้ `mt5-small` เป็นจุดตรวจสอบของเรา เพื่อให้เราปรับแต่งโมเดลได้อย่างละเอียดในระยะเวลาที่เหมาะสม: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 ในช่วงแรกของโครงการ NLP แนวทางปฏิบัติที่ดีคือการฝึกอบรมคลาสโมเดล "small" บนตัวอย่างข้อมูลขนาดเล็ก ซึ่งจะทำให้คุณสามารถแก้ไขจุดบกพร่องและทำซ้ำได้เร็วขึ้นในเวิร์กโฟลว์ตั้งแต่ต้นทางถึงปลายทาง เมื่อคุณมั่นใจในผลลัพธ์แล้ว คุณสามารถขยายขนาดโมเดลได้ตลอดเวลาโดยเพียงแค่เปลี่ยนจุดตรวจสอบโมเดล! + + + +มาทดสอบโทเค็น mT5 ด้วยตัวอย่างเล็กๆ น้อยๆ: + +```python +inputs = tokenizer("I loved reading the Hunger Games!") +inputs +``` + +```python out +{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +ที่นี่เราจะเห็น `input_ids` และ `attention_mask` ที่คุ้นเคยซึ่งเราพบในการทดลองปรับแต่งครั้งแรกใน [บทที่ 3](/course/th/chapter3) มาถอดรหัส ID อินพุตเหล่านี้ด้วยฟังก์ชัน `convert_ids_to_tokens()` ของโทเค็นเพื่อดูว่าโทเค็นที่เรากำลังจัดการกับอยู่ประเภทใด: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +อักขระ Unicode พิเศษ `▁` และโทเค็นจุดสิ้นสุดของลำดับ `` ระบุว่าเรากำลังจัดการกับโทเค็น Sentence Piece ซึ่งขึ้นอยู่กับอัลกอริธึมการแบ่งส่วน Unigram ที่กล่าวถึงใน [บทที่ 6](/course/th/chapter6 ). Unigram มีประโยชน์อย่างยิ่งสำหรับคลังข้อมูลหลายภาษา เนื่องจากทำให้ Sentence Piece ไม่เชื่อเรื่องสำเนียง เครื่องหมายวรรคตอน และข้อเท็จจริงที่ว่าหลายภาษา เช่น ภาษาญี่ปุ่น ไม่มีอักขระช่องว่าง + +ในการสร้างโทเค็นคลังข้อมูลของเรา เราต้องจัดการกับรายละเอียดปลีกย่อยที่เกี่ยวข้องกับการสรุป เนื่องจากป้ายกำกับของเราเป็นข้อความด้วย จึงเป็นไปได้ที่ป้ายเหล่านี้มีขนาดเกินขนาดบริบทสูงสุดของโมเดล ซึ่งหมายความว่าเราจำเป็นต้องใช้การตัดทอนทั้งบทวิจารณ์และชื่อเรื่องเพื่อให้แน่ใจว่าเราจะไม่ส่งข้อมูลที่ยาวเกินไปไปยังโมเดลของเรา โทเค็นไนเซอร์ใน 🤗 Transformers มีอาร์กิวเมนต์ `text_target` ที่ดี ซึ่งช่วยให้คุณสร้างโทเค็นป้ายกำกับขนานกับอินพุตได้ นี่คือตัวอย่างวิธีประมวลผลอินพุตและเป้าหมายสำหรับ mT5: + +```python +max_input_length = 512 +max_target_length = 30 + + +def preprocess_function(examples): + model_inputs = tokenizer( + examples["review_body"], + max_length=max_input_length, + truncation=True, + ) + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +มาดูโค้ดนี้เพื่อทำความเข้าใจว่าเกิดอะไรขึ้น สิ่งแรกที่เราทำคือกำหนดค่าสำหรับ `max_input_length` และ `max_target_length` ซึ่งกำหนดขีดจำกัดสูงสุดสำหรับระยะเวลาในการวิจารณ์และชื่อของเรา เนื่องจากโดยทั่วไปเนื้อหาบทวิจารณ์จะมีขนาดใหญ่กว่าชื่อเรื่องมาก เราจึงปรับขนาดค่าเหล่านี้ให้สอดคล้องกัน + +ด้วย `preprocess_function()` จึงเป็นเรื่องง่ายที่จะสร้างโทเค็นคลังข้อมูลทั้งหมดโดยใช้ฟังก์ชัน `Dataset.map()` ที่มีประโยชน์ซึ่งเราใช้อย่างกว้างขวางตลอดหลักสูตรนี้: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +เมื่อคลังข้อมูลได้รับการประมวลผลล่วงหน้าแล้ว เรามาดูหน่วยวัดบางส่วนที่ใช้กันทั่วไปสำหรับการสรุปกัน ดังที่เราจะเห็นแล้วว่าการวัดคุณภาพของข้อความที่เครื่องสร้างขึ้นนั้นไม่มีประเด็นเสียเลย + + + +💡 คุณอาจสังเกตเห็นว่าเราใช้ `batched=True` ในฟังก์ชัน `Dataset.map()` ด้านบน วิธีนี้จะเข้ารหัสตัวอย่างเป็นกลุ่มละ 1,000 รายการ (ค่าเริ่มต้น) และช่วยให้คุณสามารถใช้ความสามารถแบบมัลติเธรดของโทเค็นไนเซอร์ที่รวดเร็วใน 🤗 Transformers หากเป็นไปได้ ลองใช้ `batched=True` เพื่อให้ได้ประโยชน์สูงสุดจากการประมวลผลล่วงหน้าของคุณ! + + + + +## เมตริกสำหรับการสรุปข้อความ[[เมตริกสำหรับการสรุปข้อความ]] + + + +เมื่อเปรียบเทียบกับงานอื่นๆ ส่วนใหญ่ที่เราได้กล่าวถึงในหลักสูตรนี้ การวัดประสิทธิภาพของงานการสร้างข้อความ เช่น การสรุปหรือการแปลนั้นไม่ได้ตรงไปตรงมามากนัก ตัวอย่างเช่น เมื่อได้รับบทวิจารณ์เช่น "ฉันชอบอ่าน Hunger Games" มีบทสรุปที่ถูกต้องหลายรายการ เช่น "ฉันชอบ Hunger Games" หรือ "Hunger Games เป็นหนังสือที่น่าอ่าน" เห็นได้ชัดว่าการใช้การจับคู่แบบตรงทั้งหมดระหว่างบทสรุปที่สร้างขึ้นกับป้ายกำกับไม่ใช่วิธีแก้ปัญหาที่ดี แม้แต่มนุษย์ก็ยังได้รับผลที่ไม่ดีหากใช้เมตริกดังกล่าว เนื่องจากเราทุกคนมีสไตล์การเขียนเป็นของตัวเอง + +สำหรับการสรุป ตัวชี้วัดที่ใช้บ่อยที่สุดประการหนึ่งคือ [คะแนน ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (ย่อมาจาก Recall-Oriented Understudy for Gisting Evaling) แนวคิดพื้นฐานเบื้องหลังหน่วยวัดนี้คือการเปรียบเทียบข้อมูลสรุปที่สร้างขึ้นกับชุดข้อมูลสรุปอ้างอิงที่โดยทั่วไปแล้วมนุษย์สร้างขึ้น เพื่อให้แม่นยำยิ่งขึ้น สมมติว่าเราต้องการเปรียบเทียบข้อมูลสรุปสองรายการต่อไปนี้: + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +วิธีหนึ่งในการเปรียบเทียบอาจเป็นการนับจำนวนคำที่ทับซ้อนกัน ซึ่งในกรณีนี้คือ 6 อย่างไรก็ตาม วิธีนี้ค่อนข้างหยาบ ดังนั้น ROUGE จึงใช้การคำนวณคะแนน _precision_ และ _recall_ สำหรับการทับซ้อนกันแทน + + + +🙋 ไม่ต้องกังวลหากนี่เป็นครั้งแรกที่คุณได้ยินเกี่ยวกับความแม่นยำ (precision) และการจดจำ (recall) เราจะพิจารณาตัวอย่างที่ชัดเจนร่วมกันเพื่อให้ทุกอย่างชัดเจน เมตริกเหล่านี้มักจะพบในงานจำแนกประเภท ดังนั้นหากคุณต้องการทำความเข้าใจว่าความแม่นยำและการจดจำถูกกำหนดไว้อย่างไรในบริบทนั้น เราขอแนะนำให้ตรวจสอบ [คำแนะนำ](https://scikit-learn.org/stable) `scikit-learn` /auto_examples/model_selection/plot_precision_recall.html) + + + +สำหรับ ROUGE การเรียกคืนจะวัดว่าข้อมูลสรุปที่สร้างขึ้นบันทึกได้มากน้อยเพียงใด หากเราเพียงเปรียบเทียบคำศัพท์ การจำสามารถคำนวณได้ตามสูตรต่อไปนี้: + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +สำหรับตัวอย่างง่ายๆ ข้างต้น สูตรนี้ให้ค่าที่จำได้สมบูรณ์แบบคือ 6/6 = 1; กล่าวคือ คำทั้งหมดในข้อมูลสรุปอ้างอิงถูกสร้างขึ้นโดยแบบจำลอง อาจฟังดูดี แต่ลองจินตนาการดูว่าบทสรุปที่เราสร้างขึ้นคือ "ฉันชอบอ่าน Hunger Games ทั้งคืนจริงๆ" สิ่งนี้น่าจะมีการเรียกคืนได้อย่างสมบูรณ์แบบ แต่อาจเป็นบทสรุปที่แย่กว่านั้นเนื่องจากมีรายละเอียดมาก เพื่อจัดการกับสถานการณ์เหล่านี้ เรายังคำนวณความแม่นยำด้วย ซึ่งในบริบท ROUGE จะวัดว่าสรุปที่สร้างขึ้นมีความเกี่ยวข้องมากน้อยเพียงใด: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +การใช้สิ่งนี้กับการสรุปแบบละเอียดของเราจะให้ความแม่นยำ 6/10 = 0.6 ซึ่งแย่กว่าความแม่นยำที่ 6/7 = 0.86 ที่ได้จากอันที่สั้นกว่าของเราอย่างมาก ในทางปฏิบัติ มักจะคำนวณทั้งความแม่นยำและการเรียกคืน จากนั้นจึงรายงานคะแนน F1 (ค่าเฉลี่ยฮาร์มอนิกของความแม่นยำและการเรียกคืน) เราสามารถทำได้ง่ายๆ ใน 🤗 ชุดข้อมูลโดยการติดตั้งแพ็คเกจ `rouge_score` ก่อน: + +```py +!pip install rouge_score +``` + +จากนั้นโหลดเมตริก ROUGE ดังต่อไปนี้: + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +จากนั้นเราสามารถใช้ฟังก์ชัน `rouge_score.compute()` เพื่อคำนวณเมตริกทั้งหมดในคราวเดียว: + +```python +scores = rouge_score.compute( + predictions=[generated_summary], references=[reference_summary] +) +scores +``` + +```python out +{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)), + 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} +``` + +โอ้ มีข้อมูลมากมายในผลลัพธ์นั้น ทั้งหมดนี้หมายความว่าอย่างไร อันดับแรก 🤗 Datasets จะคำนวณช่วงความเชื่อมั่นเพื่อความแม่นยำ การเรียกคืน และคะแนน F1 คุณลักษณะเหล่านี้คือคุณลักษณะ `low`, `mid`, และ `high` ที่คุณเห็นได้ที่นี่ นอกจากนี้ 🤗 Datasets ยังคำนวณคะแนน ROUGE ที่หลากหลาย ซึ่งขึ้นอยู่กับความละเอียดของข้อความประเภทต่างๆ เมื่อเปรียบเทียบผลสรุปที่สร้างขึ้นและผลอ้างอิง รูปแบบ `rouge1` คือการทับซ้อนของยูนิแกรม นี่เป็นเพียงวิธีที่หรูหราในการพูดการทับซ้อนกันของคำ และเป็นหน่วยเมตริกที่เราได้กล่าวถึงข้างต้นทุกประการ เพื่อยืนยันสิ่งนี้ เราจะดึงค่า `mid` ของคะแนนของเราออกมา: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +เยี่ยมมาก ความแม่นยำและการจดจำตัวเลขตรงกัน! แล้วคะแนน ROUGE อื่นๆ เหล่านั้นล่ะ? `rouge2` วัดการทับซ้อนระหว่างบิ๊กแกรม (ลองนึกถึงการทับซ้อนกันของคู่คำ) ในขณะที่ `rougeL` และ `rougeLsum` วัดลำดับการจับคู่ที่ยาวที่สุดโดยการค้นหาสตริงย่อยทั่วไปที่ยาวที่สุดในข้อมูลสรุปที่สร้างขึ้นและการอ้างอิง "ผลรวม" ใน `rougeLsum` หมายถึงข้อเท็จจริงที่ว่าเมตริกนี้คำนวณจากผลสรุปทั้งหมด ในขณะที่ `rougeL` คำนวณเป็นค่าเฉลี่ยในแต่ละประโยค + + + +✏️ **ลองดูสิ!** สร้างตัวอย่างของคุณเองของการสรุปที่สร้างขึ้นและอ้างอิง และดูว่าคะแนน ROUGE ที่ได้นั้นสอดคล้องกับการคำนวณด้วยตนเองตามสูตรเพื่อความแม่นยำและการจดจำหรือไม่ สำหรับคะแนนโบนัส ให้แบ่งข้อความออกเป็นบิ๊กแกรมและเปรียบเทียบความแม่นยำและการจดจำสำหรับเมตริก `rouge2` + + + +เราจะใช้คะแนน ROUGE เหล่านี้เพื่อติดตามประสิทธิภาพของแบบจำลองของเรา แต่ก่อนที่จะทำเช่นนั้น เรามาทำสิ่งที่ผู้ปฏิบัติงาน NLP ที่ดีทุกคนควรทำ: สร้างบรรทัดฐาน (baseline) ที่แข็งแกร่งแต่เรียบง่าย! + +### การสร้างบรรทัดฐานที่แข็งแกร่ง[[การสร้างบรรทัดฐานที่แข็งแกร่ง]] + +บรรทัดฐานทั่วไปสำหรับการสรุปข้อความคือการใช้สามประโยคแรกของบทความ ซึ่งมักเรียกว่าบรรทัดฐาน _lead-3_ เราสามารถใช้จุดหยุดเพื่อติดตามขอบเขตของประโยคได้ แต่จะล้มเหลวเมื่อใช้ตัวย่อเช่น "U.S." หรือ "U.N." -- ดังนั้น เราจะใช้ไลบรารี `nltk` แทน ซึ่งมีอัลกอริธึมที่ดีกว่าในการจัดการกรณีเหล่านี้ คุณสามารถติดตั้งแพ็คเกจโดยใช้ `pip` ดังนี้: + +```python +!pip install nltk +``` + +จากนั้นดาวน์โหลดกฎเครื่องหมายวรรคตอน: + +```python +import nltk + +nltk.download("punkt") +``` + +ต่อไป เราจะนำเข้า tokenizer ประโยคจาก `nltk` และสร้างฟังก์ชันง่ายๆ เพื่อแยกสามประโยคแรกในการทบทวน แบบแผนในการสรุปข้อความคือการแยกแต่ละสรุปด้วยการขึ้นบรรทัดใหม่ ดังนั้นเรามารวมสิ่งนี้และทดสอบกับตัวอย่างการฝึกอบรม: + +```python +from nltk.tokenize import sent_tokenize + + +def three_sentence_summary(text): + return "\n".join(sent_tokenize(text)[:3]) + + +print(three_sentence_summary(books_dataset["train"][1]["review_body"])) +``` + +```python out +'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' +'She found Strangers.' +``` + +ดูเหมือนว่าจะได้ผล ดังนั้นตอนนี้เรามาใช้ฟังก์ชันที่แยก "summaries" เหล่านี้จากชุดข้อมูลและคำนวณคะแนน ROUGE สำหรับข้อมูลพื้นฐาน: + +```python +def evaluate_baseline(dataset, metric): + summaries = [three_sentence_summary(text) for text in dataset["review_body"]] + return metric.compute(predictions=summaries, references=dataset["review_title"]) +``` + +จากนั้นเราสามารถใช้ฟังก์ชันนี้เพื่อคำนวณคะแนน ROUGE เหนือชุดการตรวจสอบความถูกต้อง และทำให้สวยงามขึ้นเล็กน้อยโดยใช้ Pandas: + +```python +import pandas as pd + +score = evaluate_baseline(books_dataset["validation"], rouge_score) +rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"] +rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names) +rouge_dict +``` + +```python out +{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} +``` + +เราจะเห็นได้ว่าคะแนน `rouge2` ต่ำกว่าคะแนนที่เหลืออย่างมาก สิ่งนี้น่าจะสะท้อนถึงความจริงที่ว่าชื่อบทวิจารณ์มักจะกระชับ ดังนั้นบรรทัดฐานของ Lead-3 จึงละเอียดเกินไป ตอนนี้เรามีพื้นฐานที่ดีแล้ว เรามามุ่งความสนใจไปที่การปรับแต่ง mT5 กันดีกว่า! + +{#if fw === 'pt'} + +## ปรับแต่ง mT5 อย่างละเอียดด้วย `Trainer` API[[ปรับแต่ง-mT5-อย่างละเอียดด้วย-trainer-api]] + +การปรับแต่งแบบจำลองอย่างละเอียดสำหรับการสรุปจะคล้ายกับงานอื่นๆ ที่เรากล่าวถึงในบทนี้มาก สิ่งแรกที่เราต้องทำคือโหลดโมเดลที่ได้รับการฝึกมาจากจุดตรวจสอบ `mt5-small` เนื่องจากการสรุปเป็นงานตามลำดับ เราจึงสามารถโหลดโมเดลด้วยคลาส `AutoModelForSeq2SeqLM` ซึ่งจะดาวน์โหลดและแคชน้ำหนักโดยอัตโนมัติ: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## การปรับแต่ง mT5 อย่างละเอียดด้วย Keras[[การปรับแต่ง-mt5-อย่างละเอียดด้วย-keras]] + +การปรับแต่งแบบจำลองอย่างละเอียดสำหรับการสรุปจะคล้ายกับงานอื่นๆ ที่เรากล่าวถึงในบทนี้มาก สิ่งแรกที่เราต้องทำคือโหลดโมเดลที่ได้รับการฝึกมาจากจุดตรวจสอบ `mt5-small` เนื่องจากการสรุปเป็นงานตามลำดับ เราจึงสามารถโหลดโมเดลด้วยคลาส `TFAutoModelForSeq2SeqLM` ซึ่งจะดาวน์โหลดและแคชน้ำหนักโดยอัตโนมัติ: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 หากคุณสงสัยว่าเหตุใดคุณไม่เห็นคำเตือนใดๆ เกี่ยวกับการปรับแต่งโมเดลในงานดาวน์สตรีม นั่นเป็นเพราะว่าสำหรับงานตามลำดับต่อลำดับ เราจะเก็บน้ำหนักทั้งหมดของเครือข่ายไว้ เปรียบเทียบสิ่งนี้กับโมเดลการจัดหมวดหมู่ข้อความของเราใน [บทที่ 3](/course/th/chapter3) โดยที่ส่วนหัวของโมเดลที่ฝึกไว้ล่วงหน้าถูกแทนที่ด้วยเครือข่ายที่เริ่มต้นแบบสุ่ม + + + +สิ่งต่อไปที่เราต้องทำคือเข้าสู่ระบบ Hugging Face Hub หากคุณใช้โค้ดนี้ในโน้ตบุ๊ก คุณสามารถทำได้โดยใช้ฟังก์ชันยูทิลิตีต่อไปนี้: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +ซึ่งจะแสดงวิดเจ็ตที่คุณสามารถป้อนข้อมูลรับรองของคุณได้ หรือคุณสามารถรันคำสั่งนี้ในเทอร์มินัลของคุณและเข้าสู่ระบบที่นั่น: + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +เราจะต้องสร้างข้อมูลสรุปเพื่อคำนวณคะแนน ROUGE ระหว่างการฝึกอบรม โชคดีที่ 🤗 Transformers มีคลาส `Seq2SeqTrainingArguments` และ `Seq2SeqTrainer` โดยเฉพาะที่สามารถทำสิ่งนี้ให้เราได้โดยอัตโนมัติ! หากต้องการดูวิธีการทำงาน ขั้นแรกเรามากำหนดไฮเปอร์พารามิเตอร์และข้อโต้แย้งอื่นๆ สำหรับการทดสอบของเรากันก่อน: + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +ในที่นี้ อาร์กิวเมนต์ `predict_with_generate` ได้รับการตั้งค่าเพื่อระบุว่าเราควรสร้างบทสรุประหว่างการประเมิน เพื่อให้เราสามารถคำนวณคะแนน ROUGE สำหรับแต่ละยุคได้ ตามที่กล่าวไว้ใน [บทที่ 1](/course/th/chapter1) ตัวถอดรหัสทำการอนุมานโดยการทำนายโทเค็นทีละรายการ และดำเนินการโดยใช้เมธอด `generate()` ของโมเดล การตั้งค่า `predict_with_generate=True` จะบอก `Seq2SeqTrainer` ให้ใช้วิธีการนั้นในการประเมิน นอกจากนี้เรายังได้ปรับไฮเปอร์พารามิเตอร์เริ่มต้นบางส่วนด้วย เช่น อัตราการเรียนรู้ จำนวนยุค และการลดลงของน้ำหนัก และเราได้ตั้งค่าตัวเลือก `save_total_limit` ให้บันทึกจุดตรวจสอบได้สูงสุด 3 จุดในระหว่างการฝึกเท่านั้น ทั้งนี้เป็นเพราะแม้แต่ mT5 เวอร์ชัน "small" ใช้พื้นที่ฮาร์ดไดรฟ์ประมาณ GB และเราสามารถประหยัดพื้นที่ได้เล็กน้อยด้วยการจำกัดจำนวนสำเนาที่เราบันทึก + +อาร์กิวเมนต์ `push_to_hub=True` จะช่วยให้เราสามารถผลักดันโมเดลไปที่ Hub หลังจากการฝึกอบรม คุณจะพบพื้นที่เก็บข้อมูลใต้โปรไฟล์ผู้ใช้ของคุณในตำแหน่งที่กำหนดโดย `output_dir` โปรดทราบว่าคุณสามารถระบุชื่อของพื้นที่เก็บข้อมูลที่คุณต้องการพุชไปได้ด้วยอาร์กิวเมนต์ `hub_model_id` (โดยเฉพาะ คุณจะต้องใช้อาร์กิวเมนต์นี้เพื่อพุชไปยังองค์กร) ตัวอย่างเช่น เมื่อเราผลักโมเดลไปที่ [`huggingface-course` Organization](https://huggingface.co/huggingface-course) เราได้เพิ่ม `hub_model_id="huggingface-course/mt5-finetuned-amazon-en- es"` ถึง `Seq2SeqTrainingArguments` + +สิ่งต่อไปที่เราต้องทำคือจัดเตรียมฟังก์ชัน `compute_metrics()` ให้กับเทรนเนอร์ เพื่อให้เราสามารถประเมินโมเดลของเราระหว่างการฝึกได้ สำหรับการสรุป สิ่งนี้มีความเกี่ยวข้องมากกว่าการเรียก `rouge_score.compute()` กับการทำนายของแบบจำลอง เนื่องจากเราจำเป็นต้อง _decode_ ผลลัพธ์และป้ายกำกับเป็นข้อความก่อนจึงจะสามารถคำนวณคะแนน ROUGE ได้ ฟังก์ชันต่อไปนี้ทำหน้าที่ดังกล่าวทุกประการ และยังใช้ฟังก์ชัน `sent_tokenize()` จาก `nltk` เพื่อแยกประโยคสรุปด้วยการขึ้นบรรทัดใหม่: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +ต่อไป เราต้องกำหนดตัวเปรียบเทียบข้อมูลสำหรับงานลำดับต่อลำดับของเรา เนื่องจาก mT5 เป็นโมเดล Transformer ตัวเข้ารหัส-ตัวถอดรหัส (encoder-decoder) ความละเอียดอ่อนอย่างหนึ่งในการเตรียมแบทช์ของเราก็คือในระหว่างการถอดรหัส เราจำเป็นต้องเลื่อนป้ายกำกับไปทางขวาทีละป้าย สิ่งนี้จำเป็นเพื่อให้แน่ใจว่าตัวถอดรหัสจะเห็นเฉพาะป้ายความจริงภาคพื้นดินก่อนหน้าเท่านั้น ไม่ใช่ป้ายปัจจุบันหรืออนาคต ซึ่งจะง่ายต่อการจดจำสำหรับแบบจำลอง สิ่งนี้คล้ายกับวิธีการเอาแต่สนใจตนเองมาใช้กับอินพุตในงาน เช่น [การสร้างแบบจำลองภาษาเชิงสาเหตุ](/course/th/chapter7/6) + +โชคดีที่ 🤗 Transformers มีตัวเปรียบเทียบ `DataCollatorForSeq2Seq` ที่จะแพดอินพุตและป้ายกำกับแบบไดนามิกให้เรา หากต้องการสร้างตัวอย่าง collator นี้ เราเพียงแค่ต้องจัดเตรียม `tokenizer` และ `model`: + +{#if fw === 'pt'} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +มาดูกันว่าตัวเปรียบเทียบนี้ผลิตอะไรได้บ้างเมื่อป้อนตัวอย่างชุดเล็กๆ ขั้นแรก เราต้องลบคอลัมน์ที่มีสตริงออก เนื่องจากตัวเปรียบเทียบไม่ทราบวิธีแพดองค์ประกอบเหล่านี้: + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +เนื่องจากผู้เปรียบเทียบคาดหวังรายการ `dict`s โดยที่ `dict` แต่ละรายการแสดงถึงตัวอย่างเดียวในชุดข้อมูล เราจึงต้องโต้แย้งข้อมูลให้อยู่ในรูปแบบที่คาดหวังก่อนที่จะส่งต่อไปยังตัวเปรียบเทียบข้อมูล: + +```python +features = [tokenized_datasets["train"][i] for i in range(2)] +data_collator(features) +``` + +```python out +{'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, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[ 1494, 259, 8622, 390, 259, 262, 2316, 3435, 955, + 772, 281, 772, 1617, 263, 305, 14701, 260, 1385, + 3031, 259, 24146, 332, 1037, 259, 43906, 305, 336, + 260, 1, 0, 0, 0, 0, 0, 0], + [ 259, 27531, 13483, 259, 7505, 260, 112240, 15192, 305, + 53198, 276, 259, 74060, 263, 260, 459, 25640, 776, + 2119, 336, 259, 2220, 259, 18896, 288, 4906, 288, + 1037, 3931, 260, 7083, 101476, 1143, 260, 1]]), 'labels': tensor([[ 7483, 259, 2364, 15695, 1, -100], + [ 259, 27531, 13483, 259, 7505, 1]]), 'decoder_input_ids': tensor([[ 0, 7483, 259, 2364, 15695, 1], + [ 0, 259, 27531, 13483, 259, 7505]])} +``` + +สิ่งสำคัญที่ต้องสังเกตที่นี่คือตัวอย่างแรกยาวกว่าตัวอย่างที่สอง ดังนั้น `input_ids` และ `attention_mask` ของตัวอย่างที่สองจึงได้รับการเสริมไว้ทางด้านขวาด้วยโทเค็น `[PAD]` (ซึ่งมี ID เป็น ` 0`) ในทำนองเดียวกัน เราจะเห็นว่า `labels` ได้รับการเสริมด้วย `-100`s เพื่อให้แน่ใจว่าโทเค็นการเสริมจะถูกละเว้นโดยฟังก์ชันการสูญเสีย และสุดท้าย เราจะเห็น `decoder_input_ids` ใหม่ ซึ่งได้เลื่อนป้ายกำกับไปทางขวาโดยการใส่โทเค็น `[PAD]` ในรายการแรก + +{#if fw === 'pt'} + +ในที่สุดเราก็มีส่วนผสมทั้งหมดที่จำเป็นในการฝึกแล้ว! ตอนนี้เราเพียงแค่ต้องยกตัวอย่างผู้ฝึกสอนด้วยอาร์กิวเมนต์มาตรฐาน: + +```python +from transformers import Seq2SeqTrainer + +trainer = Seq2SeqTrainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +and launch our training run: + +```python +trainer.train() +``` + +ในระหว่างการฝึก คุณควรเห็นว่าการสูญเสียการฝึกลดลงและคะแนน ROUGE จะเพิ่มขึ้นในแต่ละยุค เมื่อการฝึกอบรมเสร็จสิ้น คุณสามารถดูคะแนน ROUGE สุดท้ายได้โดยการเรียกใช้ `Trainer.evaluate()`: + +```python +trainer.evaluate() +``` + +```python out +{'eval_loss': 3.028524398803711, + 'eval_rouge1': 16.9728, + 'eval_rouge2': 8.2969, + 'eval_rougeL': 16.8366, + 'eval_rougeLsum': 16.851, + 'eval_gen_len': 10.1597, + 'eval_runtime': 6.1054, + 'eval_samples_per_second': 38.982, + 'eval_steps_per_second': 4.914} +``` + +จากคะแนนเราจะเห็นว่าโมเดลของเรามีประสิทธิภาพเหนือกว่าพื้นฐาน lead-3 ของเราอย่างคล่องแคล่ว เยี่ยมมาก! สิ่งสุดท้ายที่ต้องทำคือดันตุ้มน้ำหนักโมเดลไปที่ฮับ ดังนี้: + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +การดำเนินการนี้จะบันทึกจุดตรวจสอบและไฟล์การกำหนดค่าไปที่ `output_dir` ก่อนที่จะอัปโหลดไฟล์ทั้งหมดไปยัง Hub ด้วยการระบุอาร์กิวเมนต์ `tags` เรายังมั่นใจได้ว่าวิดเจ็ตบน Hub จะเป็นอันหนึ่งสำหรับไปป์ไลน์การสรุป แทนที่จะเป็นการสร้างข้อความเริ่มต้นที่เชื่อมโยงกับสถาปัตยกรรม mT5 (สำหรับข้อมูลเพิ่มเติมเกี่ยวกับแท็กโมเดล โปรดดู [🤗 เอกสารประกอบ Hub ](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)) เอาต์พุตจาก `trainer.push_to_hub()` คือ URL ไปยังคอมมิตแฮชของ Git ดังนั้นคุณจึงสามารถดูการเปลี่ยนแปลงที่เกิดขึ้นกับที่เก็บโมเดลได้อย่างง่ายดาย! + +เพื่อสรุปส่วนนี้ เรามาดูกันว่าเราจะปรับแต่ง mT5 อย่างละเอียดได้อย่างไรโดยใช้ฟีเจอร์ระดับต่ำที่ 🤗 Accelerate มอบให้ + +{:else} + +เราเกือบจะพร้อมที่จะฝึกแล้ว! เราเพียงแค่ต้องแปลงชุดข้อมูลของเราเป็น `tf.data.Dataset`s โดยใช้ตัวรวบรวมข้อมูลที่เรากำหนดไว้ด้านบน จากนั้นจึง `compile()` และ `fit()` โมเดล, ขั้นตอนแรก datasets: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +ตอนนี้ เรากำหนดไฮเปอร์พารามิเตอร์การฝึกอบรมและคอมไพล์: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# 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_epochs = 8 +num_train_steps = len(tf_train_dataset) * num_train_epochs +model_name = model_checkpoint.split("/")[-1] + +optimizer, schedule = create_optimizer( + init_lr=5.6e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) + +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +และสุดท้าย เราก็พอดีกับโมเดล เราใช้ `PushToHubCallback` เพื่อบันทึกโมเดลลงใน Hub หลังจากแต่ละ epoch ซึ่งจะทำให้เราใช้แบบจำลองนั้นเพื่อการอนุมานในภายหลัง: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8 +) +``` + +เราได้รับค่าที่สูญเสียไประหว่างการฝึก แต่จริงๆ แล้วเราต้องการเห็นหน่วยเมตริก ROUGE ที่เราคำนวณไว้ก่อนหน้านี้ ในการรับหน่วยเมตริกเหล่านั้น เราจะต้องสร้างเอาต์พุตจากโมเดลและแปลงเป็นสตริง มาสร้างรายการป้ายกำกับและการคาดคะเนสำหรับเมตริก ROUGE เพื่อเปรียบเทียบกัน (โปรดทราบว่าหากคุณได้รับข้อผิดพลาดในการนำเข้าสำหรับส่วนนี้ คุณอาจต้องรันคำสั่ง `!pip install tqdm`) นอกจากนี้เรายังจะใช้เคล็ดลับที่เพิ่มประสิทธิภาพได้อย่างมาก ด้วยการคอมไพล์โค้ดการสร้างของเราด้วย [XLA](https://www.tensorflow.org/xla) ซึ่งเป็นคอมไพเลอร์พีชคณิตเชิงเส้นแบบเร่งของ TensorFlow XLA ใช้การปรับให้เหมาะสมต่างๆ กับกราฟการคำนวณของโมเดล และส่งผลให้มีการปรับปรุงความเร็วและการใช้หน่วยความจำอย่างมีนัยสำคัญ ตามที่อธิบายไว้ใน Hugging Face [บล็อก](https://huggingface.co/blog/tf-xla-generate) XLA ทำงานได้ดีที่สุดเมื่อรูปร่างอินพุตของเราไม่แตกต่างกันมากเกินไป เพื่อจัดการสิ่งนี้ เราจะแพดอินพุตของเราให้เป็นทวีคูณของ 128 และสร้างชุดข้อมูลใหม่ด้วยตัวเปรียบเทียบการแพด จากนั้นเราจะใช้ decorator `@tf.function(jit_compile=True)` กับฟังก์ชันการสร้างของเรา ซึ่ง ทำเครื่องหมายฟังก์ชันทั้งหมดสำหรับการคอมไพล์ด้วย XLA + +```python +from tqdm import tqdm +import numpy as np + +generation_data_collator = DataCollatorForSeq2Seq( + tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=320 +) + +tf_generate_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=generation_data_collator, + shuffle=False, + batch_size=8, + drop_remainder=True, +) + + +@tf.function(jit_compile=True) +def generate_with_xla(batch): + return model.generate( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + max_new_tokens=32, + ) + + +all_preds = [] +all_labels = [] +for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = labels.numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) +``` + +เมื่อเรามีรายการป้ายกำกับและสตริงการทำนายแล้ว การคำนวณคะแนน ROUGE ก็เป็นเรื่องง่าย: + +```python +result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True +) +result = {key: value.mid.fmeasure * 100 for key, value in result.items()} +{k: round(v, 4) for k, v in result.items()} +``` + +``` +{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815} +``` + + +{/if} + +{#if fw === 'pt'} + +## ปรับแต่ง mT5 อย่างละเอียดด้วย 🤗 Accelerate[[ปรับแต่ง-mT5-อย่างละเอียดด้วย-accelerate]] + +การปรับแต่งโมเดลของเราอย่างละเอียดด้วย 🤗 Accelerate นั้นคล้ายคลึงกับตัวอย่างการจัดหมวดหมู่ข้อความที่เราพบใน [บทที่ 3](/course/th/chapter3) มาก ความแตกต่างที่สำคัญคือความจำเป็นในการสร้างข้อมูลสรุปอย่างชัดเจนระหว่างการฝึกอบรมและกำหนดวิธีที่เราคำนวณคะแนน ROUGE (โปรดจำไว้ว่า `Seq2SeqTrainer` ดูแลรุ่นของเรา) มาดูกันว่าเราจะนำข้อกำหนดทั้งสองนี้ไปปฏิบัติได้อย่างไรภายใน 🤗 เร่งความเร็ว! + +### เตรียมทุกอย่างเพื่อการฝึกโมเดล[[เตรียมทุกอย่างเพื่อการฝึกโมเดล]] + +สิ่งแรกที่เราต้องทำคือสร้าง `DataLoader` สำหรับการแยกแต่ละส่วนของเรา เนื่องจากตัวโหลดข้อมูล PyTorch คาดว่าจะมีเทนเซอร์เป็นชุด เราจึงต้องตั้งค่ารูปแบบเป็น `"torch"` ในชุดข้อมูลของเรา: + +```python +tokenized_datasets.set_format("torch") +``` + +ตอนนี้เรามีชุดข้อมูลที่ประกอบด้วยเทนเซอร์เท่านั้น สิ่งต่อไปที่ต้องทำคือสร้างอินสแตนซ์ `DataCollatorForSeq2Seq` อีกครั้ง สำหรับสิ่งนี้ เราจำเป็นต้องจัดเตรียมโมเดลเวอร์ชันใหม่ ดังนั้นมาโหลดอีกครั้งจากแคชของเรา: + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +จากนั้นเราสามารถจำลองตัวรวบรวมข้อมูลและใช้สิ่งนี้เพื่อกำหนดตัวโหลดข้อมูลของเรา: + +```python +from torch.utils.data import DataLoader + +batch_size = 8 +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=batch_size, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size +) +``` + +สิ่งต่อไปที่ต้องทำคือกำหนดเครื่องมือเพิ่มประสิทธิภาพที่เราต้องการใช้ เช่นเดียวกับตัวอย่างอื่นๆ เราจะใช้ `AdamW` ซึ่งทำงานได้ดีกับปัญหาส่วนใหญ่: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +สุดท้ายนี้ เราป้อนโมเดล เครื่องมือเพิ่มประสิทธิภาพ และตัวโหลดข้อมูลให้กับเมธอด `accelerator.prepare()`: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 หากคุณกำลังฝึกบน TPU คุณจะต้องย้ายโค้ดทั้งหมดข้างต้นไปยังฟังก์ชันการฝึกโดยเฉพาะ ดู [บทที่ 3](/course/th/chapter3) สำหรับรายละเอียดเพิ่มเติม + + + +ตอนนี้เราได้เตรียมวัตถุของเราแล้ว เหลืออีก 3 อย่างที่ต้องทำ: + +* กำหนดตารางอัตราการเรียนรู้ (learning rate schedule) +* สร้้างฟังก์ชันเพื่อวิเคราะห์ผลข้อมูลสรุปหลังการประเมินผล +* สร้างพื้นที่เก็บข้อมูลบน Hub ที่เราสามารถผลักดันโมเดลของเราไปได้ + +สำหรับตารางอัตราการเรียนรู้ เราจะใช้ตารางเชิงเส้นมาตรฐานจากส่วนก่อนหน้า: + +```python +from transformers import get_scheduler + +num_train_epochs = 10 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +สำหรับการวิเคราะห์หลังการประมวลผล เราจำเป็นต้องมีฟังก์ชันที่แยกข้อมูลสรุปที่สร้างขึ้นออกเป็นประโยคที่คั่นด้วยการขึ้นบรรทัดใหม่ นี่คือรูปแบบที่หน่วยเมตริก ROUGE คาดหวัง และเราสามารถทำได้โดยใช้ข้อมูลโค้ดต่อไปนี้: + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +สิ่งนี้น่าจะดูคุ้นเคยสำหรับคุณ หากคุณจำได้ว่าเรากำหนดฟังก์ชัน `compute_metrics()` ของ `Seq2SeqTrainer` ได้อย่างไร + +สุดท้าย เราจำเป็นต้องสร้างที่เก็บแบบจำลองบน Hugging Face Hub สำหรับสิ่งนี้ เราสามารถใช้ไลบรารี 🤗 Hub ที่มีชื่อเหมาะสมได้ เราเพียงแค่ต้องกำหนดชื่อสำหรับพื้นที่เก็บข้อมูลของเรา และไลบรารีก็มีฟังก์ชันยูทิลิตี้เพื่อรวมรหัสพื้นที่เก็บข้อมูลเข้ากับโปรไฟล์ผู้ใช้: + +```python +from huggingface_hub import get_full_repo_name + +model_name = "test-bert-finetuned-squad-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/mt5-finetuned-amazon-en-es-accelerate' +``` + +ตอนนี้เราสามารถใช้ชื่อพื้นที่เก็บข้อมูลนี้เพื่อโคลนเวอร์ชันในเครื่องไปยังไดเร็กทอรีผลลัพธ์ของเราซึ่งจะจัดเก็บสิ่งประดิษฐ์การฝึกอบรม: + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +สิ่งนี้จะช่วยให้เราสามารถผลักดันสิ่งประดิษฐ์กลับไปที่ Hub โดยการเรียกเมธอด `repo.push_to_hub()` ในระหว่างการฝึกอบรม! ตอนนี้เรามาสรุปการวิเคราะห์ของเราโดยเขียนลูปการฝึกอบรมออกไป + +### ลูปการฝึกอบรม[[ลูปการฝึกอบรม]] + +ลูปการฝึกอบรมสำหรับการสรุปค่อนข้างคล้ายกับตัวอย่างอื่นๆของ 🤗 Accelerate ที่เราเคยพบและแบ่งออกเป็นสี่ขั้นตอนหลักคร่าวๆ: + +1. ฝึกโมเดลโดยวนซ้ำตัวอย่างทั้งหมดใน `train_dataloader` สำหรับแต่ละ epoch +2. สร้างสรุปโมเดลในตอนท้ายของแต่ละ epoch โดยสร้างโทเค็นก่อน จากนั้นจึงถอดรหัสโทเค็น (และสรุปข้อมูลอ้างอิง) ให้เป็นข้อความ +3. คำนวณคะแนน ROUGE โดยใช้เทคนิคเดียวกับที่เราเห็นก่อนหน้านี้ +4. บันทึกจุดตรวจและผลักดันทุกอย่างเข้าสู่ศูนย์กลาง ที่นี่เราอาศัยอาร์กิวเมนต์ `blocking=False` ที่ดีของอ็อบเจ็กต์ `Repository` เพื่อให้เราสามารถผลักดันจุดตรวจสอบต่อยุค _asynchronously_ ได้ สิ่งนี้ช่วยให้เราสามารถฝึกอบรมต่อไปได้โดยไม่ต้องรอการอัปโหลดที่ค่อนข้างช้าที่เกี่ยวข้องกับโมเดลขนาด GB! + +ขั้นตอนเหล่านี้สามารถดูได้ในบล็อคโค้ดต่อไปนี้: + +```python +from tqdm.auto import tqdm +import torch +import numpy as np + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005} +Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306} +Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468} +Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518} +Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029} +Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913} +Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701} +Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194} +Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744} +Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} +``` + +แค่นั้นแหละ! เมื่อคุณรันสิ่งนี้ คุณจะมีโมเดลและผลลัพธ์ที่ค่อนข้างคล้ายกับที่เราได้รับจาก `Trainer`. + +{/if} + +## การใช้โมเดลที่ปรับแต่งของคุณ[[การใช้โมเดลที่ปรับแต่งของคุณ]] + +เมื่อคุณผลักโมเดลไปที่ฮับแล้ว คุณสามารถเล่นกับมันผ่านวิดเจ็ตการอนุมานหรือด้วยอ็อบเจ็กต์ `pipeline` ดังต่อไปนี้: + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +เราสามารถป้อนตัวอย่างจากชุดทดสอบ (ซึ่งโมเดลไม่เคยเห็น) ไปยังไปป์ไลน์ของเราเพื่อให้เข้าใจถึงคุณภาพของสรุป ขั้นแรก ลองใช้ฟังก์ชันง่ายๆ เพื่อแสดงบทวิจารณ์ ชื่อ และข้อมูลสรุปที่สร้างขึ้นด้วยกัน: + +```python +def print_summary(idx): + review = books_dataset["test"][idx]["review_body"] + title = books_dataset["test"][idx]["review_title"] + summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"] + print(f"'>>> Review: {review}'") + print(f"\n'>>> Title: {title}'") + print(f"\n'>>> Summary: {summary}'") +``` + +ลองมาดูตัวอย่างภาษาอังกฤษตัวอย่างหนึ่งที่เราได้รับ: + +```python +print_summary(100) +``` + +```python out +'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.' + +'>>> Title: Not impressed at all... buy something else' + +'>>> Summary: Nothing special at all about this product' +``` + +นี่ก็ไม่ได้แย่เกินไป! เราจะเห็นได้ว่าแบบจำลองของเราสามารถทำการสรุป _abstractive_ ได้โดยการเพิ่มส่วนของบทวิจารณ์ด้วยคำศัพท์ใหม่ และบางทีแง่มุมที่ยอดเยี่ยมที่สุดของโมเดลของเราก็คือเป็นแบบสองภาษา ดังนั้นเราจึงสามารถสร้างบทสรุปบทวิจารณ์ภาษาสเปนได้: + +```python +print_summary(0) +``` + +```python out +'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' + +'>>> Title: Buena literatura para adolescentes' + +'>>> Summary: Muy facil de leer' +``` + +สรุปแปลเป็นภาษาอังกฤษว่า "Very easy to read" ซึ่งเราเห็นในกรณีนี้ก็ดึงมาจากรีวิวโดยตรง อย่างไรก็ตาม สิ่งนี้แสดงให้เห็นถึงความอเนกประสงค์ของรุ่น mT5 และทำให้คุณได้สัมผัสประสบการณ์ในการจัดการกับคลังข้อมูลที่พูดได้หลายภาษา! + +ต่อไป เราจะหันความสนใจไปที่งานที่ซับซ้อนมากขึ้นเล็กน้อย: การฝึกโมเดลภาษาตั้งแต่เริ่มต้น diff --git a/chapters/th/chapter7/6.mdx b/chapters/th/chapter7/6.mdx new file mode 100644 index 000000000..b90600396 --- /dev/null +++ b/chapters/th/chapter7/6.mdx @@ -0,0 +1,914 @@ + + +# การเทร็นภาษาเชิงสาเหตุตั้งแต่เริ่มต้น[[การเทร็นภาษาเชิงสาเหตุตั้งแต่เริ่มต้น]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +จนถึงตอนนี้ ส่วนใหญ่แล้วเราใช้โมเดลที่ได้รับการฝึกล่วงหน้าและปรับแต่งโมเดลเหล่านั้นสำหรับกรณีการใช้งานใหม่โดยการนำน้ำหนักจากการฝึกล่วงหน้ามาใช้ซ้ำ ดังที่เราเห็นใน [บทที่ 1](/course/th/chapter1) โดยทั่วไปเรียกว่า _transfer Learning_ และเป็นกลยุทธ์ที่ประสบความสำเร็จอย่างมากในการใช้โมเดล Transformer กับกรณีการใช้งานจริงส่วนใหญ่ที่ข้อมูลที่ติดป้ายกำกับกระจัดกระจาย ในบทนี้ เราจะใช้แนวทางที่แตกต่างออกไปและฝึกฝนโมเดลใหม่ทั้งหมดตั้งแต่เริ่มต้น นี่เป็นแนวทางที่ดีหากคุณมีข้อมูลจำนวนมาก และแตกต่างจากข้อมูลการฝึกอบรมล่วงหน้าที่ใช้สำหรับโมเดลที่มีอยู่อย่างมาก อย่างไรก็ตาม ยังต้องใช้ทรัพยากรการประมวลผลมากขึ้นอย่างมากในการฝึกโมเดลภาษาล่วงหน้า มากกว่าเพียงเพื่อปรับแต่งโมเดลที่มีอยู่ ตัวอย่างที่สมเหตุสมผลในการฝึกโมเดลใหม่ได้แก่ ชุดข้อมูลที่ประกอบด้วยโน้ตดนตรี ลำดับโมเลกุล เช่น DNA หรือภาษาการเขียนโปรแกรม หลังได้รับแรงผลักดันเมื่อเร็ว ๆ นี้ด้วยเครื่องมือเช่น Copilot ของ TabNine และ GitHub ซึ่งขับเคลื่อนโดยโมเดล Codex ของ OpenAI ที่สามารถสร้างลำดับโค้ดที่ยาวได้ งานสร้างข้อความนี้ได้รับการแก้ไขได้ดีที่สุดด้วยโมเดลภาษาแบบถดถอยอัตโนมัติ (auto-regressive) หรือเชิงสาเหตุ เช่น GPT-2 + +ในส่วนนี้ เราจะสร้างแบบจำลองการสร้างโค้ดในเวอร์ชันที่ลดขนาดลง: เราจะมุ่งเน้นไปที่การทำให้เสร็จสมบูรณ์ในบรรทัดเดียว แทนที่จะเป็นฟังก์ชันหรือคลาสแบบเต็ม โดยใช้ชุดย่อยของโค้ด Python เมื่อทำงานกับข้อมูลใน Python คุณจะติดต่อกับสแต็กวิทยาศาสตร์ข้อมูลของ Python บ่อยครั้ง ซึ่งประกอบด้วยไลบรารี `matplotlib`, `seaborn`, `pandas` และ `scikit-learn` เมื่อใช้เฟรมเวิร์กเหล่านั้น เป็นเรื่องปกติที่จะต้องค้นหาคำสั่งเฉพาะ ดังนั้นจึงคงจะดีถ้าเราสามารถใช้แบบจำลองเพื่อดำเนินการเรียกเหล่านี้ให้เราได้ + + + +ใน [บทที่ 6](/course/th/chapter6) เราได้สร้างโทเค็นไนเซอร์ที่มีประสิทธิภาพเพื่อประมวลผลซอร์สโค้ด Python แต่สิ่งที่เรายังต้องการคือชุดข้อมูลขนาดใหญ่เพื่อฝึกโมเดลล่วงหน้า ที่นี่ เราจะใช้โทเค็นของเรากับคลังโค้ด Python ที่ได้มาจากที่เก็บ GitHub จากนั้นเราจะใช้ `Trainer` API และ 🤗 Accelerate เพื่อฝึกโมเดล มาเริ่มกันเลย! + + + +นี่เป็นการจัดแสดงโมเดลที่ได้รับการฝึกอบรมและอัปโหลดไปยัง Hub โดยใช้โค้ดที่แสดงในส่วนนี้ ดูได้[ที่นี่](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28) โปรดทราบว่าเนื่องจากมีการสุ่มเกิดขึ้นในการสร้างข้อความ คุณอาจได้รับผลลัพธ์ที่แตกต่างออกไปเล็กน้อย + +## การรวบรวมข้อมูล[[การรวบรวมข้อมูล]] + +โค้ด Python มีมากมายจากที่เก็บโค้ด เช่น GitHub ซึ่งเราสามารถใช้เพื่อสร้างชุดข้อมูลโดยการคัดลอกสำหรับที่เก็บ Python ทุกอัน นี่เป็นแนวทางที่ใช้ใน [หนังสือเรียน Transformers](https://learning.oreilly.com/library/view/natural-Language-processing/9781098136789/) เพื่อฝึกโมเดล GPT-2 ขนาดใหญ่ล่วงหน้า ด้วยการใช้การถ่ายโอนข้อมูล GitHub ประมาณ 180 GB ที่มีไฟล์ Python ประมาณ 20 ล้านไฟล์ที่เรียกว่า `codeparrot` ผู้เขียนจึงสร้างชุดข้อมูลที่พวกเขาแชร์บน [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot) + +อย่างไรก็ตาม การฝึกอบรมเกี่ยวกับคลังข้อมูลทั้งหมดนั้นใช้เวลาและการคำนวณมากและเราต้องการเพียงชุดย่อยของชุดข้อมูลที่เกี่ยวข้องกับสแต็กวิทยาศาสตร์ข้อมูล Python เท่านั้น ดังนั้น มาเริ่มด้วยการกรองชุดข้อมูล `codeparrot` สำหรับไฟล์ทั้งหมดที่มีไลบรารีใดๆ ในสแต็กนี้ เนื่องจากขนาดของชุดข้อมูล เราจึงต้องการหลีกเลี่ยงการดาวน์โหลด เราจะใช้คุณสมบัติสตรีมมิ่งเพื่อกรองข้อมูลทันทีแทน เพื่อช่วยเรากรองตัวอย่างโค้ดโดยใช้ไลบรารีที่เรากล่าวถึงก่อนหน้านี้ เราจะใช้ฟังก์ชันต่อไปนี้: + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +ลองทดสอบด้วยสองตัวอย่าง: + +```py +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] +example_1 = "import numpy as np" +example_2 = "import pandas as pd" + +print( + any_keyword_in_string(example_1, filters), any_keyword_in_string(example_2, filters) +) +``` + +```python out +False True +``` + +เราสามารถใช้สิ่งนี้เพื่อสร้างฟังก์ชันที่จะสตรีมชุดข้อมูลและกรององค์ประกอบที่เราต้องการ: + +```py +from collections import defaultdict +from tqdm import tqdm +from datasets import Dataset + + +def filter_streaming_dataset(dataset, filters): + filtered_dict = defaultdict(list) + total = 0 + for sample in tqdm(iter(dataset)): + total += 1 + if any_keyword_in_string(sample["content"], filters): + for k, v in sample.items(): + filtered_dict[k].append(v) + print(f"{len(filtered_dict['content'])/total:.2%} of data after filtering.") + return Dataset.from_dict(filtered_dict) +``` + +จากนั้นเราก็สามารถใช้ฟังก์ชันนี้กับชุดข้อมูลสตรีมมิ่งได้: + +```py +# This cell will take a very long time to execute, so you should skip it and go to +# the next one! +from datasets import load_dataset + +split = "train" # "valid" +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] + +data = load_dataset(f"transformersbook/codeparrot-{split}", split=split, streaming=True) +filtered_data = filter_streaming_dataset(data, filters) +``` + +```python out +3.26% of data after filtering. +``` + +นี่ทำให้เราเหลือประมาณ 3% ของชุดข้อมูลดั้งเดิม ซึ่งยังคงค่อนข้างใหญ่ - ชุดข้อมูลที่ได้คือ 6 GB และประกอบด้วยสคริปต์ Python 600,000 สคริปต์! + +การกรองชุดข้อมูลทั้งหมดอาจใช้เวลา 2-3 ชั่วโมง ขึ้นอยู่กับเครื่องและแบนด์วิดท์ของคุณ หากคุณไม่ต้องการดำเนินการตามกระบวนการที่ยืดยาวนี้ด้วยตนเอง เรามีชุดข้อมูลที่กรองแล้วบน Hub เพื่อให้คุณดาวน์โหลด: + +```py +from datasets import load_dataset, DatasetDict + +ds_train = load_dataset("huggingface-course/codeparrot-ds-train", split="train") +ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="validation") + +raw_datasets = DatasetDict( + { + "train": ds_train, # .shuffle().select(range(50000)), + "valid": ds_valid, # .shuffle().select(range(500)) + } +) + +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 606720 + }) + valid: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 3322 + }) +}) +``` + + + +การฝึกโมเดลภาษาล่วงหน้าจะใช้เวลาสักครู่ เราขอแนะนำให้คุณรันลูปการฝึกกับตัวอย่างข้อมูลก่อนโดยยกเลิกการใส่เครื่องหมายในสองบรรทัดด้านบน และตรวจสอบให้แน่ใจว่าการฝึกเสร็จสมบูรณ์และโมเดลถูกจัดเก็บไว้ ไม่มีอะไรน่าหงุดหงิดไปกว่าการวิ่งฝึกซ้อมที่ล้มเหลวในขั้นตอนสุดท้ายเพราะคุณลืมสร้างโฟลเดอร์หรือเนื่องจากมีการพิมพ์ผิดที่ส่วนท้ายของลูปการฝึกซ้อม! + + + +ลองดูตัวอย่างจากชุดข้อมูล เราจะแสดงอักขระ 200 ตัวแรกของแต่ละฟิลด์: + +```py +for key in raw_datasets["train"][0]: + print(f"{key.upper()}: {raw_datasets['train'][0][key][:200]}") +``` + +```python out +'REPO_NAME: kmike/scikit-learn' +'PATH: sklearn/utils/__init__.py' +'COPIES: 3' +'SIZE: 10094' +'''CONTENT: """ +The :mod:`sklearn.utils` module includes various utilites. +""" + +from collections import Sequence + +import numpy as np +from scipy.sparse import issparse +import warnings + +from .murmurhash import murm +LICENSE: bsd-3-clause''' +``` + +เราจะเห็นว่าฟิลด์ `content` มีโค้ดที่เราต้องการให้โมเดลของเราฝึกฝน ตอนนี้เรามีชุดข้อมูลแล้ว เราต้องเตรียมข้อความให้อยู่ในรูปแบบที่เหมาะสมสำหรับการฝึกล่วงหน้า + +## การเตรียมชุดข้อมูล[[การเตรียมชุดข้อมูล]] + + + +ขั้นตอนแรกคือการสร้างข้อมูลให้เป็นโทเค็น เพื่อให้เราสามารถนำไปใช้ในการฝึกอบรมได้ เนื่องจากเป้าหมายของเราคือการเติมฟังก์ชันสั้น ๆ อัตโนมัติเป็นหลัก เราจึงสามารถรักษาขนาดบริบทให้เล็กได้ สิ่งนี้มีประโยชน์ตรงที่เราสามารถฝึกโมเดลได้เร็วยิ่งขึ้น และต้องใช้หน่วยความจำน้อยลงอย่างมาก หากเป็นสิ่งสำคัญสำหรับแอปพลิเคชันของคุณที่จะต้องมีบริบทมากขึ้น (เช่น หากคุณต้องการให้โมเดลเขียนการทดสอบหน่วยตามไฟล์ที่มีคำจำกัดความฟังก์ชัน) ตรวจสอบให้แน่ใจว่าคุณเพิ่มจำนวนนั้น แต่โปรดจำไว้ว่าสิ่งนี้มาพร้อมกับ หน่วยความจำ GPU ที่มากขึ้น สำหรับตอนนี้ เราจะแก้ไขขนาดบริบทที่ 128 โทเค็น แทนที่จะเป็น 1,024 หรือ 2,048 ที่ใช้ใน GPT-2 หรือ GPT-3 ตามลำดับ + +เอกสารส่วนใหญ่มีโทเค็นมากกว่า 128 รายการ ดังนั้นการตัดทอนอินพุตให้เหลือความยาวสูงสุดจะกำจัดชุดข้อมูลส่วนใหญ่ของเราออกไป แต่เราจะใช้ตัวเลือก `return_overflowing_tokens` เพื่อโทเค็นอินพุตทั้งหมดและแบ่งออกเป็นหลายส่วน ดังที่เราทำใน [บทที่ 6](/course/th/chapter6/4) นอกจากนี้เรายังใช้ตัวเลือก `return_length` เพื่อส่งคืนความยาวของแต่ละส่วนที่สร้างขึ้นโดยอัตโนมัติ บ่อยครั้งที่ส่วนสุดท้ายจะเล็กกว่าขนาดบริบท และเราจะกำจัดส่วนเหล่านี้ออกเพื่อหลีกเลี่ยงปัญหาการเติม เราไม่ต้องการมันจริงๆ เนื่องจากเรามีข้อมูลมากมายอยู่แล้ว + +
+Chunking a large texts in several pieces. + +
+ +เรามาดูกันว่าวิธีนี้ทำงานอย่างไรโดยดูจากสองตัวอย่างแรก: + +```py +from transformers import AutoTokenizer + +context_length = 128 +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") + +outputs = tokenizer( + raw_datasets["train"][:2]["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, +) + +print(f"Input IDs length: {len(outputs['input_ids'])}") +print(f"Input chunk lengths: {(outputs['length'])}") +print(f"Chunk mapping: {outputs['overflow_to_sample_mapping']}") +``` + +```python out +Input IDs length: 34 +Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 117, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 41] +Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +``` + +เราจะเห็นว่าเราได้รับทั้งหมด 34 ส่วนจากสองตัวอย่างนี้ เมื่อพิจารณาความยาวชิ้น เราจะเห็นว่าชิ้นส่วนที่ส่วนท้ายของเอกสารทั้งสองมีโทเค็นน้อยกว่า 128 โทเค็น (117 และ 41 ตามลำดับ) สิ่งเหล่านี้เป็นเพียงเศษเสี้ยวเล็กๆ ของชิ้นส่วนทั้งหมดที่เรามี ดังนั้นเราจึงสามารถทิ้งมันทิ้งได้อย่างปลอดภัย ด้วยฟิลด์ `overflow_to_sample_mapping` เรายังสามารถสร้างใหม่ได้ว่าชิ้นใดเป็นของตัวอย่างอินพุตใด + +ด้วยการดำเนินการนี้ เรากำลังใช้คุณสมบัติที่มีประโยชน์ของฟังก์ชัน `Dataset.map()` ในชุดข้อมูล 🤗 ซึ่งก็คือมันไม่จำเป็นต้องมีแผนที่แบบหนึ่งต่อหนึ่ง ดังที่เราเห็นใน [ส่วนที่ 3](/course/th/chapter7/3) เราสามารถสร้างแบทช์ที่มีองค์ประกอบมากหรือน้อยกว่าแบทช์อินพุตได้ สิ่งนี้มีประโยชน์เมื่อดำเนินการเช่นการเพิ่มข้อมูลหรือการกรองข้อมูลที่เปลี่ยนจำนวนองค์ประกอบ ในกรณีของเรา เมื่อโทเค็นแต่ละองค์ประกอบเป็นชิ้นตามขนาดบริบทที่ระบุ เราจะสร้างตัวอย่างจำนวนมากจากแต่ละเอกสาร เราแค่ต้องแน่ใจว่าได้ลบคอลัมน์ที่มีอยู่แล้ว เนื่องจากคอลัมน์เหล่านั้นมีขนาดที่ขัดแย้งกัน หากเราต้องการเก็บไว้ เราสามารถทำซ้ำได้อย่างเหมาะสมและส่งคืนภายในการเรียก `Dataset.map()`: + +```py +def tokenize(element): + outputs = tokenizer( + element["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, + ) + input_batch = [] + for length, input_ids in zip(outputs["length"], outputs["input_ids"]): + if length == context_length: + input_batch.append(input_ids) + return {"input_ids": input_batch} + + +tokenized_datasets = raw_datasets.map( + tokenize, batched=True, remove_columns=raw_datasets["train"].column_names +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['input_ids'], + num_rows: 16702061 + }) + valid: Dataset({ + features: ['input_ids'], + num_rows: 93164 + }) +}) +``` + +ขณะนี้เรามีตัวอย่าง 16.7 ล้านตัวอย่าง แต่ละโทเค็นมี 128 โทเค็น ซึ่งคิดเป็นทั้งหมดประมาณ 2.1 พันล้านโทเค็น สำหรับการอ้างอิง โมเดล GPT-3 และ Codex ของ OpenAI ได้รับการฝึกฝนเกี่ยวกับโทเค็น 300 และ 100 พันล้านตามลำดับ โดยที่โมเดล Codex จะเริ่มต้นได้จากจุดตรวจสอบ GPT-3 เป้าหมายของเราในส่วนนี้ไม่ใช่เพื่อแข่งขันกับโมเดลเหล่านี้ ซึ่งสามารถสร้างข้อความที่ยาวและสอดคล้องกัน แต่เพื่อสร้างเวอร์ชันที่ลดขนาดลงเพื่อให้ฟังก์ชันเติมข้อความอัตโนมัติอย่างรวดเร็วสำหรับนักวิทยาศาสตร์ข้อมูล + +ตอนนี้เรามีชุดข้อมูลพร้อมแล้ว เรามาตั้งค่าโมเดลกันดีกว่า! + + + +✏️ **ลองดูสิ!** การกำจัดส่วนที่เล็กกว่าขนาดบริบทไม่ใช่ปัญหาใหญ่ที่นี่เนื่องจากเราใช้หน้าต่างบริบทขนาดเล็ก เมื่อคุณเพิ่มขนาดบริบท (หรือถ้าคุณมีคลังเอกสารขนาดสั้น) เศษของชิ้นส่วนที่ถูกโยนทิ้งก็จะเพิ่มขึ้นเช่นกัน วิธีที่มีประสิทธิภาพมากกว่าในการจัดเตรียมข้อมูลคือการรวมตัวอย่างโทเค็นทั้งหมดเข้าด้วยกันเป็นชุดโดยมีโทเค็น `eos_token_id` อยู่ระหว่างนั้น จากนั้นจึงทำการแยกส่วนตามลำดับที่ต่อกัน ในแบบฝึกหัด ให้ปรับเปลี่ยนฟังก์ชัน `tokenize()` เพื่อใช้ประโยชน์จากแนวทางนั้น โปรดทราบว่าคุณจะต้องตั้งค่า `truncation=False` และลบอาร์กิวเมนต์อื่นๆ ออกจากโทเค็นเพื่อรับลำดับรหัสโทเค็นทั้งหมด + + + + +## การเริ่มต้นโมเดลใหม่[[การเริ่มต้นโมเดลใหม่]] + +ขั้นตอนแรกของเราคือการเริ่มต้นโมเดล GPT-2 ใหม่ เราจะใช้การกำหนดค่าเดียวกันสำหรับโมเดลของเราเช่นเดียวกับรุ่น GPT-2 ขนาดเล็ก ดังนั้นเราจึงโหลดการกำหนดค่าที่ได้รับการฝึกไว้ล่วงหน้า ตรวจสอบให้แน่ใจว่าขนาดโทเค็นตรงกับขนาดคำศัพท์ของโมเดล และส่งผ่าน `bos` และ `eos` (เริ่มต้นและ สิ้นสุดลำดับ) รหัสโทเค็น: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, GPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +ด้วยการกำหนดค่านั้น เราสามารถโหลดโมเดลใหม่ได้ โปรดทราบว่านี่เป็นครั้งแรกที่เราไม่ใช้ฟังก์ชัน `from_pretrained()` เนื่องจากเรากำลังเริ่มต้นโมเดลด้วยตัวเราเอง: + +```py +model = GPT2LMHeadModel(config) +model_size = sum(t.numel() for t in model.parameters()) +print(f"GPT-2 size: {model_size/1000**2:.1f}M parameters") +``` + +```python out +GPT-2 size: 124.2M parameters +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFGPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +ด้วยการกำหนดค่านั้น เราสามารถโหลดโมเดลใหม่ได้ โปรดทราบว่านี่เป็นครั้งแรกที่เราไม่ใช้ฟังก์ชัน `from_pretrained()` เนื่องจากเรากำลังเริ่มต้นโมเดลด้วยตัวเราเอง: + +```py +model = TFGPT2LMHeadModel(config) +model(model.dummy_inputs) # Builds the model +model.summary() +``` + +```python out +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +transformer (TFGPT2MainLayer multiple 124242432 +================================================================= +Total params: 124,242,432 +Trainable params: 124,242,432 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +โมเดลของเรามีพารามิเตอร์ 124M ที่เราจะต้องปรับแต่ง ก่อนที่เราจะเริ่มการฝึกอบรมได้ เราต้องตั้งค่าตัวรวบรวมข้อมูลที่จะดูแลการสร้างแบทช์ก่อน เราสามารถใช้ตัวเปรียบเทียบ `DataCollatorForLanguageModeling` ซึ่งได้รับการออกแบบมาโดยเฉพาะสำหรับการสร้างแบบจำลองภาษา (ตามชื่อที่แนะนำอย่างละเอียด) นอกจากการซ้อนและแพตช์แล้ว มันยังดูแลการสร้างเลเบลโมเดลภาษาด้วย -- ในการสร้างแบบจำลองภาษาเชิงสาเหตุ อินพุตยังทำหน้าที่เป็นป้ายกำกับด้วย (เลื่อนไปเพียงองค์ประกอบเดียว) และผู้เปรียบเทียบข้อมูลนี้จะสร้างมันขึ้นมาทันทีระหว่างการฝึกอบรมดังนั้นเราจึงไม่ ไม่จำเป็นต้องทำซ้ำ `input_ids` + +โปรดทราบว่า `DataCollatorForLanguageModeling` รองรับทั้งการสร้างแบบจำลองภาษามาสก์ (MLM) และการสร้างแบบจำลองภาษาเชิงสาเหตุ (CLM) ตามค่าเริ่มต้น จะเตรียมข้อมูลสำหรับ MLM แต่เราสามารถเปลี่ยนไปใช้ CLM ได้โดยตั้งค่าอาร์กิวเมนต์ `mlm=False`: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False) +``` + +{:else} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_tensors="tf") +``` + +{/if} + +ลองมาดูตัวอย่าง: + +```py +out = data_collator([tokenized_datasets["train"][i] for i in range(5)]) +for key in out: + print(f"{key} shape: {out[key].shape}") +``` + +{#if fw === 'pt'} + +```python out +input_ids shape: torch.Size([5, 128]) +attention_mask shape: torch.Size([5, 128]) +labels shape: torch.Size([5, 128]) +``` + +{:else} + +```python out +input_ids shape: (5, 128) +attention_mask shape: (5, 128) +labels shape: (5, 128) +``` + +{/if} + +เราจะเห็นว่าตัวอย่างถูกซ้อนกันและเทนเซอร์ทั้งหมดมีรูปร่างเหมือนกัน + +{#if fw === 'tf'} + +ตอนนี้เราสามารถใช้เมธอด `prepare_tf_dataset()` เพื่อแปลงชุดข้อมูลของเราเป็นชุดข้อมูล TensorFlow ด้วยตัวรวบรวมข้อมูลที่เราสร้างขึ้นด้านบน: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["valid"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ การเปลี่ยนอินพุตและป้ายกำกับเพื่อจัดแนวเกิดขึ้นภายในโมเดล ดังนั้นตัวรวบรวมข้อมูลจึงเพียงคัดลอกอินพุตเพื่อสร้างป้ายกำกับ + + + + +ตอนนี้เรามีทุกอย่างพร้อมแล้วที่จะฝึกฝนโมเดลของเราได้จริง ซึ่งก็ไม่ได้ผลอะไรมาก! ก่อนที่เราจะเริ่มการฝึกเราควรเข้าสู่ระบบ Hugging Face หากคุณกำลังทำงานในโน้ตบุ๊ก คุณสามารถทำได้โดยใช้ฟังก์ชันยูทิลิตี้ต่อไปนี้: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +นี่จะแสดงวิดเจ็ตที่คุณสามารถป้อนข้อมูลรับรองการเข้าสู่ระบบ Hugging Face ของคุณได้ + +หากคุณไม่ได้ทำงานในโน้ตบุ๊ก เพียงพิมพ์บรรทัดต่อไปนี้ในเทอร์มินัลของคุณ: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +สิ่งที่คุณต้องทำคือกำหนดค่าอาร์กิวเมนต์การฝึกอบรมและเปิดการทำงานของ `Trainer` เราจะใช้ตารางอัตราการเรียนรู้โคไซน์กับการวอร์มอัพและขนาดแบทช์ที่มีประสิทธิภาพ 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`) การสะสมการไล่ระดับ (Gradient) จะใช้เมื่อแบตช์เดียวไม่พอดีกับหน่วยความจำ และค่อยๆ สร้างการไล่ระดับผ่านการส่งต่อ/ย้อนกลับหลายๆ ครั้ง เราจะเห็นการดำเนินการนี้เมื่อเราสร้างลูปการฝึกซ้อมด้วย 🤗 Accelerate + +```py +from transformers import Trainer, TrainingArguments + +args = TrainingArguments( + output_dir="codeparrot-ds", + per_device_train_batch_size=32, + per_device_eval_batch_size=32, + evaluation_strategy="steps", + eval_steps=5_000, + logging_steps=5_000, + gradient_accumulation_steps=8, + num_train_epochs=1, + weight_decay=0.1, + warmup_steps=1_000, + lr_scheduler_type="cosine", + learning_rate=5e-4, + save_steps=5_000, + fp16=True, + push_to_hub=True, +) + +trainer = Trainer( + model=model, + tokenizer=tokenizer, + args=args, + data_collator=data_collator, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["valid"], +) +``` + +ตอนนี้เราสามารถเริ่ม `Trainer` และรอให้การฝึกเสร็จสิ้น ขึ้นอยู่กับว่าคุณออกกำลังกายแบบเต็มหรือชุดย่อยของชุดฝึกซ้อม โดยจะใช้เวลา 20 หรือ 2 ชั่วโมงตามลำดับ ดังนั้นคว้ากาแฟสักแก้วและหนังสือดีๆ สักเล่มมาอ่าน! + +```py +trainer.train() +``` + +หลังจากการฝึกอบรมเสร็จสิ้น เราสามารถส่งโมเดลและโทเค็นไนเซอร์ไปที่ Hub ได้: + +```py +trainer.push_to_hub() +``` + +{:else} + +สิ่งที่คุณต้องทำคือกำหนดค่าไฮเปอร์พารามิเตอร์การฝึกอบรมและเรียก `compile()` และ `fit()` เราจะใช้ตารางอัตราการเรียนรู้กับการวอร์มอัพเพื่อปรับปรุงความเสถียรของการฝึกซ้อม: + +```py +from transformers import create_optimizer +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +ตอนนี้เราสามารถเรียก `model.fit()` แล้วรอให้การฝึกเสร็จสิ้น ขึ้นอยู่กับว่าคุณออกกำลังกายแบบเต็มหรือชุดย่อยของชุดฝึกซ้อม โดยจะใช้เวลา 20 หรือ 2 ชั่วโมงตามลำดับ ดังนั้นคว้ากาแฟสักแก้วและหนังสือดีๆ สักเล่มมาอ่าน! หลังจากการฝึกอบรมเสร็จสิ้นแล้ว เราสามารถผลักดันโมเดลและโทเค็นไนเซอร์ไปที่ Hub ได้: + +```py +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="codeparrot-ds", tokenizer=tokenizer) + +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + + + +✏️ **ลองดูสิ!** เราใช้เวลาเขียนโค้ดเพียงประมาณ 30 บรรทัด นอกเหนือจาก `TrainingArguments` เพื่อรับจากข้อความดิบไปจนถึงการฝึก GPT-2 ลองใช้ชุดข้อมูลของคุณเองแล้วดูว่าคุณจะได้ผลลัพธ์ที่ดีหรือไม่! + + + + + +{#if fw === 'pt'} + +💡 หากคุณมีสิทธิ์เข้าถึงเครื่องที่มี GPU หลายตัว ให้ลองเรียกใช้โค้ดที่นั่น `Trainer` จะจัดการเครื่องจักรหลายเครื่องโดยอัตโนมัติ และสิ่งนี้สามารถเร่งการฝึกอบรมได้อย่างมาก + +{:else} + +💡 หากคุณมีสิทธิ์เข้าถึงเครื่องที่มี GPU หลายตัว คุณสามารถลองใช้บริบท `MirroredStrategy` เพื่อเร่งการฝึกอบรมได้อย่างมาก คุณจะต้องสร้างออบเจ็กต์ `tf.distribute.MirroredStrategy` และตรวจสอบให้แน่ใจว่าเมธอด `to_tf_dataset()` หรือ `prepare_tf_dataset()` ใดๆ รวมถึงการสร้างแบบจำลองและการเรียก `fit()` ทำงานทั้งหมด ในบริบท `ขอบเขต()` ดูเอกสารประกอบได้[ที่นี่](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit) + +{/if} + + + +## การสร้างโค้ดด้วยไปป์ไลน์[[การสร้างโค้ดด้วยไปป์ไลน์]] + +ตอนนี้เป็นช่วงเวลาแห่งความจริง มาดูกันว่าโมเดลที่ผ่านการฝึกอบรมนั้นทำงานได้ดีแค่ไหน! เราเห็นได้ในบันทึกว่าการสูญเสียลดลงอย่างต่อเนื่อง แต่เพื่อทดสอบโมเดล เรามาดูกันว่ามันทำงานได้ดีแค่ไหนในการแจ้งเตือนบางอย่าง ในการทำเช่นนั้น เราจะรวมโมเดลไว้ใน `ไปป์ไลน์` ของการสร้างข้อความ และเราจะใส่มันลงบน GPU สำหรับรุ่นที่เร็ว หากมี: + +{#if fw === 'pt'} + +```py +import torch +from transformers import pipeline + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +pipe = pipeline( + "text-generation", model="huggingface-course/codeparrot-ds", device=device +) +``` + +{:else} + +```py +from transformers import pipeline + +course_model = TFGPT2LMHeadModel.from_pretrained("huggingface-course/codeparrot-ds") +course_tokenizer = AutoTokenizer.from_pretrained("huggingface-course/codeparrot-ds") +pipe = pipeline( + "text-generation", model=course_model, tokenizer=course_tokenizer, device=0 +) +``` + +{/if} + +เริ่มจากงานง่ายๆ ในการสร้างแผนภูมิกระจาย (scatter plot): + +```py +txt = """\ +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create scatter plot with x, y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create scatter plot with x, y +plt.scatter(x, y) + +# create scatter +``` + +ผลลัพธ์ดูถูกต้อง มันใช้ได้กับปฏิบัติการของ `pandas` ด้วยหรือเปล่า? มาดูกันว่าเราจะสามารถสร้าง `DataFrame` จากสองอาร์เรย์ได้หรือไม่: + +```py +txt = """\ +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create dataframe from x and y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create dataframe from x and y +df = pd.DataFrame({'x': x, 'y': y}) +df.insert(0,'x', x) +for +``` + +เยี่ยมเลย นั่นเป็นคำตอบที่ถูกต้อง แม้ว่าจะแทรกคอลัมน์ `x` อีกครั้งก็ตาม เนื่องจากจำนวนโทเค็นที่สร้างขึ้นมีจำกัด การวนซ้ำ 'for' ต่อไปนี้จึงถูกตัดออก มาดูกันว่าเราสามารถทำอะไรที่ซับซ้อนกว่านี้อีกหน่อยได้ไหม และให้โมเดลช่วยเราใช้การดำเนินการ `groupby`: + +```py +txt = """\ +# dataframe with profession, income and name +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculate the mean income per profession +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# dataframe with profession, income and name +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculate the mean income per profession +profession = df.groupby(['profession']).mean() + +# compute the +``` + +ก็ไม่เลวนะ; นั่นเป็นวิธีที่ถูกต้องที่จะทำ สุดท้ายนี้ เรามาดูกันว่าเราจะสามารถใช้มันสำหรับ `scikit-learn` และตั้งค่าโมเดล Random Forest ได้หรือไม่: + +```py +txt = """ +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# fit random forest model with 300 estimators on X, y: +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# fit random forest model with 300 estimators on X, y: +rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) +rf.fit(X, y) +rf +``` + +{#if fw === 'tf'} + +เมื่อดูตัวอย่างเล็กๆ น้อยๆ เหล่านี้ ดูเหมือนว่าโมเดลจะได้เรียนรู้ไวยากรณ์บางส่วนของสแต็กวิทยาศาสตร์ข้อมูล Python แล้ว แน่นอนว่าเราจะต้องประเมินโมเดลอย่างละเอียดมากขึ้นก่อนที่จะนำไปใช้งานในโลกแห่งความเป็นจริง แต่นี่ก็ยังคงเป็นต้นแบบที่น่าประทับใจ + +{:else} + +เมื่อดูตัวอย่างเล็กๆ น้อยๆ เหล่านี้ ดูเหมือนว่าโมเดลได้เรียนรู้ไวยากรณ์บางอย่างของสแต็กวิทยาศาสตร์ข้อมูล Python (แน่นอนว่า เราจะต้องประเมินอย่างละเอียดมากขึ้นก่อนที่จะปรับใช้โมเดลในโลกแห่งความเป็นจริง) อย่างไรก็ตาม บางครั้งจำเป็นต้องมีการปรับแต่งโมเดลการฝึกเพิ่มเติมเพื่อให้บรรลุประสิทธิภาพที่จำเป็นสำหรับกรณีการใช้งานที่กำหนด ตัวอย่างเช่น จะเป็นอย่างไรหากเราต้องการอัปเดตขนาดแบตช์แบบไดนามิก หรือมีลูปการฝึกอบรมแบบมีเงื่อนไขที่ข้ามตัวอย่างที่ไม่ดีได้ทันที ทางเลือกหนึ่งคือคลาสย่อย `Trainer` และเพิ่มการเปลี่ยนแปลงที่จำเป็น แต่บางครั้งการเขียนลูปการฝึกตั้งแต่เริ่มต้นจะง่ายกว่า ดังนั้นจึงมี 🤗 Accelerate เข้ามาช่วย + +{/if} + +{#if fw === 'pt'} + +## เทรนกับ 🤗 Accelerate[[เทรนกับ-accelerate]] + +เราได้เห็นวิธีการฝึกโมเดลด้วย `Trainer` ซึ่งสามารถปรับแต่งบางอย่างได้ อย่างไรก็ตาม บางครั้งเราต้องการควบคุมลูปการฝึกซ้อมอย่างสมบูรณ์ หรือเราต้องการเปลี่ยนแปลงที่แปลกใหม่ ในกรณีนี้ 🤗 Accelerate เป็นตัวเลือกที่ยอดเยี่ยม และในส่วนนี้ เราจะอธิบายขั้นตอนต่างๆ เพื่อใช้ในการฝึกโมเดลของเรา เพื่อให้สิ่งต่าง ๆ น่าสนใจยิ่งขึ้น เรายังเพิ่มจุดหักมุมให้กับวงจรการฝึกซ้อมด้วย + + + +เนื่องจากเราสนใจเรื่องการเติมข้อความอัตโนมัติที่สมเหตุสมผลสำหรับไลบรารีวิทยาศาสตร์ข้อมูลเป็นหลัก จึงสมเหตุสมผลที่จะให้น้ำหนักกับตัวอย่างการฝึกอบรมที่ใช้ประโยชน์จากไลบรารีเหล่านี้มากขึ้น เราสามารถระบุตัวอย่างเหล่านี้ได้อย่างง่ายดายผ่านการใช้คำหลักเช่น `plt`, `pd`, `sk`, `fit` และ `predict` ซึ่งเป็นชื่อนำเข้าที่พบบ่อยที่สุดสำหรับ `matplotlib.pyplot`, `pandas ` และ `sklearn` รวมถึงรูปแบบพอดี/คาดการณ์ของอย่างหลัง หากสิ่งเหล่านี้แสดงเป็นโทเค็นเดียว เราสามารถตรวจสอบได้อย่างง่ายดายว่าเกิดขึ้นในลำดับอินพุตหรือไม่ โทเค็นสามารถมีช่องว่างนำหน้าได้ ดังนั้นเราจะตรวจสอบเวอร์ชันเหล่านั้นในคำศัพท์ของโทเค็นไนเซอร์ด้วย เพื่อตรวจสอบว่าใช้งานได้ เราจะเพิ่มโทเค็นทดสอบหนึ่งโทเค็นซึ่งควรแบ่งออกเป็นหลายโทเค็น: + +```py +keytoken_ids = [] +for keyword in [ + "plt", + "pd", + "sk", + "fit", + "predict", + " plt", + " pd", + " sk", + " fit", + " predict", + "testtest", +]: + ids = tokenizer([keyword]).input_ids[0] + if len(ids) == 1: + keytoken_ids.append(ids[0]) + else: + print(f"Keyword has not single token: {keyword}") +``` + +```python out +'Keyword has not single token: testtest' +``` + +เยี่ยมมาก ดูเหมือนว่าจะทำงานได้ดี! ตอนนี้เราสามารถเขียนฟังก์ชันการสูญเสียแบบกำหนดเองที่รับลำดับอินพุต บันทึก และโทเค็นคีย์ที่เราเพิ่งเลือกเป็นอินพุต ก่อนอื่น เราต้องจัดตำแหน่งบันทึกและอินพุต: ลำดับอินพุตที่ถูกเลื่อนไปทางขวาหนึ่งจะสร้างป้ายกำกับ เนื่องจากโทเค็นถัดไปคือป้ายกำกับสำหรับโทเค็นปัจจุบัน เราสามารถทำได้โดยการเริ่มป้ายกำกับจากโทเค็นที่สองของลำดับอินพุต เนื่องจากโมเดลไม่ได้ทำการคาดการณ์สำหรับโทเค็นแรกอยู่แล้ว จากนั้นเราจะตัดบันทึกสุดท้ายออก เนื่องจากเราไม่มีป้ายกำกับสำหรับโทเค็นที่เป็นไปตามลำดับอินพุตทั้งหมด ด้วยเหตุนี้ เราจึงสามารถคำนวณการสูญเสียต่อตัวอย่างและนับจำนวนคำหลักทั้งหมดในแต่ละตัวอย่างได้ สุดท้าย เราจะคำนวณค่าเฉลี่ยถ่วงน้ำหนักของตัวอย่างทั้งหมดโดยใช้เหตุการณ์ที่เกิดขึ้นเป็นน้ำหนัก เนื่องจากเราไม่ต้องการทิ้งตัวอย่างทั้งหมดที่ไม่มีคำหลัก เราจึงเพิ่ม 1 เข้าไปในน้ำหนัก: + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Shift so that tokens < n predict n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calculate per-token loss + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Resize and average loss per sample + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculate and scale weighting + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculate weighted average + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +ก่อนที่เราจะเริ่มฝึกฝนด้วยฟังก์ชันการสูญเสียใหม่ที่ยอดเยี่ยมนี้ เราต้องเตรียมบางสิ่งก่อน: + +- เราต้องการตัวโหลดข้อมูลเพื่อโหลดข้อมูลเป็นชุด (batches) +- เราจำเป็นต้องตั้งค่าพารามิเตอร์การสลายตัวของน้ำหนัก (weight decay parameters) +- ในบางครั้ง เราต้องการประเมิน ดังนั้นจึงเหมาะสมที่จะรวมโค้ดการประเมินไว้ในฟังก์ชัน + +เริ่มจากตัวโหลดข้อมูลกันก่อน เราเพียงต้องตั้งค่ารูปแบบของชุดข้อมูลเป็น `"torch"` จากนั้นเราสามารถส่งต่อไปยัง PyTorch `DataLoader` ด้วยขนาดแบทช์ที่เหมาะสม: + +```py +from torch.utils.data.dataloader import DataLoader + +tokenized_datasets.set_format("torch") +train_dataloader = DataLoader(tokenized_datasets["train"], batch_size=32, shuffle=True) +eval_dataloader = DataLoader(tokenized_datasets["valid"], batch_size=32) +``` + +ต่อไป เราจะจัดกลุ่มพารามิเตอร์เพื่อให้เครื่องมือเพิ่มประสิทธิภาพทราบว่าพารามิเตอร์ใดจะได้รับน้ำหนักที่ลดลงเพิ่มเติม โดยปกติแล้ว ข้อกำหนดอคติ (bias) และน้ำหนัก LayerNorm ทั้งหมดจะได้รับการยกเว้นจากสิ่งนี้ นี่คือวิธีที่เราสามารถทำได้: + +```py +weight_decay = 0.1 + + +def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): + params_with_wd, params_without_wd = [], [] + for n, p in model.named_parameters(): + if any(nd in n for nd in no_decay): + params_without_wd.append(p) + else: + params_with_wd.append(p) + return [ + {"params": params_with_wd, "weight_decay": weight_decay}, + {"params": params_without_wd, "weight_decay": 0.0}, + ] +``` + +เนื่องจากเราต้องการประเมินโมเดลอย่างสม่ำเสมอในชุดการตรวจสอบความถูกต้องระหว่างการฝึก เรามาเขียนฟังก์ชันสำหรับสิ่งนั้นด้วยเช่นกัน มันแค่ทำงานผ่านตัวโหลดข้อมูลการประเมินและรวบรวมความสูญเสียทั้งหมดระหว่างกระบวนการ: + +```py +def evaluate(): + model.eval() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(batch["input_ids"], labels=batch["input_ids"]) + + losses.append(accelerator.gather(outputs.loss)) + loss = torch.mean(torch.cat(losses)) + try: + perplexity = torch.exp(loss) + except OverflowError: + perplexity = float("inf") + return loss.item(), perplexity.item() +``` + +ด้วยฟังก์ชัน `evaluate()` เราสามารถรายงานการสูญเสียและ [ความฉงนสนเท่ห์](/course/th/chapter7/3) ในช่วงเวลาสม่ำเสมอ ต่อไป เราจะกำหนดโมเดลของเราใหม่เพื่อให้แน่ใจว่าเราฝึกตั้งแต่ต้นอีกครั้ง: + +```py +model = GPT2LMHeadModel(config) +``` + +จากนั้นเราสามารถกำหนดเครื่องมือเพิ่มประสิทธิภาพของเราได้ โดยใช้ฟังก์ชันจากก่อนหน้าเพื่อแยกพารามิเตอร์สำหรับการลดน้ำหนัก: + +```py +from torch.optim import AdamW + +optimizer = AdamW(get_grouped_params(model), lr=5e-4) +``` + +ตอนนี้เรามาเตรียมโมเดล เครื่องมือเพิ่มประสิทธิภาพ และตัวโหลดข้อมูลเพื่อให้เราสามารถเริ่มการฝึกอบรมได้: + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) + +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 หากคุณกำลังฝึกบน TPU คุณจะต้องย้ายโค้ดทั้งหมดที่เริ่มต้นจากเซลล์ด้านบนไปยังฟังก์ชันการฝึกเฉพาะ ดู [บทที่ 3](/course/th/chapter3) สำหรับรายละเอียดเพิ่มเติม + + + +ตอนนี้เราได้ส่ง `train_dataloader` ไปที่ `accelerator.prepare()` แล้ว เราสามารถใช้ความยาวของมันเพื่อคำนวณจำนวนขั้นตอนการฝึกได้ โปรดจำไว้ว่าเราควรทำเช่นนี้เสมอหลังจากเตรียม dataloader เนื่องจากวิธีการนั้นจะเปลี่ยนความยาวของมัน เราใช้กำหนดการเชิงเส้นแบบคลาสสิก (classic linear schedule) จากอัตราการเรียนรู้ (learning rate) ถึง 0: + +```py +from transformers import get_scheduler + +num_train_epochs = 1 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + name="linear", + optimizer=optimizer, + num_warmup_steps=1_000, + num_training_steps=num_training_steps, +) +``` + +สุดท้ายนี้ ในการผลักดันโมเดลของเราไปที่ Hub เราจะต้องสร้างออบเจ็กต์ `Repository` ในโฟลเดอร์ที่ใช้งานได้ ขั้นแรกให้เข้าสู่ระบบ Hugging Face Hub หากคุณยังไม่ได้เข้าสู่ระบบ เราจะกำหนดชื่อที่เก็บจาก ID โมเดลที่เราต้องการให้กับโมเดลของเรา (อย่าลังเลที่จะแทนที่ `repo_name` ด้วยตัวเลือกของคุณเอง เพียงต้องมีชื่อผู้ใช้ของคุณ ซึ่งเป็นสิ่งที่ฟังก์ชัน `get_full_repo_name()` ทำ ): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "codeparrot-ds-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/codeparrot-ds-accelerate' +``` + +จากนั้นเราสามารถโคลนพื้นที่เก็บข้อมูลนั้นในโฟลเดอร์ในเครื่องได้ หากมีอยู่แล้ว โฟลเดอร์ในเครื่องนี้ควรเป็นโคลนของพื้นที่เก็บข้อมูลที่เรากำลังทำงานด้วย: + +```py +output_dir = "codeparrot-ds-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +ตอนนี้เราสามารถอัปโหลดทุกสิ่งที่เราบันทึกไว้ใน `output_dir` ได้โดยการเรียกเมธอด `repo.push_to_hub()` ซึ่งจะช่วยให้เราอัปโหลดโมเดลระดับกลางในตอนท้ายของแต่ละ epoch ได้ + +ก่อนที่เราจะฝึก เรามาทำการทดสอบสั้นๆ เพื่อดูว่าฟังก์ชันการประเมินผลทำงานอย่างถูกต้องหรือไม่: + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +นี่เป็นค่าที่สูงมากสำหรับการสูญเสียและความฉงนสนเท่ห์ แต่นั่นก็ไม่น่าแปลกใจเนื่องจากเรายังไม่ได้ฝึกโมเดลนี้ ด้วยเหตุนี้ เราจึงมีทุกสิ่งที่เตรียมไว้เพื่อเขียนส่วนหลักของสคริปต์การฝึกอบรม: ลูปการฝึกอบรม ในลูปการฝึกเราจะวนซ้ำตัวโหลดข้อมูลและส่งต่อแบทช์ไปยังโมเดล ด้วยการบันทึก เราจึงสามารถประเมินฟังก์ชันการสูญเสียแบบกำหนดเองของเราได้ เราปรับขนาดการสูญเสียตามจำนวนขั้นตอนการสะสมไล่ระดับ เพื่อไม่ให้เกิดการสูญเสียที่มากขึ้นเมื่อรวมขั้นตอนเพิ่มเติม ก่อนที่เราจะปรับให้เหมาะสม เรายังตัดการไล่ระดับเพื่อการบรรจบกันที่ดีขึ้น สุดท้ายนี้ ทุกสองสามขั้นตอน เราจะประเมินโมเดลในชุดการประเมินด้วยฟังก์ชัน `evaluate()` ใหม่ของเรา: + +```py +from tqdm.notebook import tqdm + +gradient_accumulation_steps = 8 +eval_steps = 5_000 + +model.train() +completed_steps = 0 +for epoch in range(num_train_epochs): + for step, batch in tqdm( + enumerate(train_dataloader, start=1), total=num_training_steps + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "samples": step * samples_per_step, + "steps": completed_steps, + "loss/train": loss.item() * gradient_accumulation_steps, + } + ) + loss = loss / gradient_accumulation_steps + accelerator.backward(loss) + if step % gradient_accumulation_steps == 0: + accelerator.clip_grad_norm_(model.parameters(), 1.0) + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + completed_steps += 1 + if (step % (eval_steps * gradient_accumulation_steps)) == 0: + eval_loss, perplexity = evaluate() + accelerator.print({"loss/eval": eval_loss, "perplexity": perplexity}) + model.train() + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress step {step}", blocking=False + ) +``` + +เพียงเท่านี้ คุณก็มีลูปการฝึกอบรมแบบกำหนดเองสำหรับโมเดลภาษาเชิงสาเหตุ เช่น GPT-2 ที่คุณสามารถปรับแต่งเพิ่มเติมตามความต้องการของคุณได้ + + + +✏️ **ลองดูสิ!** สร้างฟังก์ชันการสูญเสียที่คุณกำหนดเองซึ่งปรับแต่งให้เหมาะกับกรณีการใช้งานของคุณ หรือเพิ่มขั้นตอนที่กำหนดเองอื่นลงในลูปการฝึก + + + + + +✏️ **ลองดูสิ!** เมื่อทำการทดสอบการฝึกระยะยาว เป็นความคิดที่ดีที่จะบันทึกเมตริกที่สำคัญโดยใช้เครื่องมือ เช่น TensorBoard หรือ Weights & Biases เพิ่มการบันทึกที่เหมาะสมให้กับลูปการฝึก เพื่อให้คุณสามารถตรวจสอบได้ตลอดเวลาว่าการฝึกดำเนินไปอย่างไร + + + +{/if} diff --git a/chapters/th/chapter7/7.mdx b/chapters/th/chapter7/7.mdx new file mode 100644 index 000000000..6b9d2aa0b --- /dev/null +++ b/chapters/th/chapter7/7.mdx @@ -0,0 +1,1203 @@ + + +# การตอบคำถาม[[การตอบคำถาม]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +ถึงเวลาดูการตอบคำถาม! งานนี้มีหลายรูปแบบ แต่งานที่เราจะเน้นในส่วนนี้เรียกว่าการตอบคำถามแบบ *extractive* ซึ่งเกี่ยวข้องกับการตั้งคำถามเกี่ยวกับเอกสารและการระบุคำตอบเป็น __spans of text_ ในเอกสารนั้นเอง + + + +เราจะปรับแต่งโมเดล BERT บน [ชุดข้อมูล SQuAD](https://rajpurkar.github.io/SQuAD-explorer/) ซึ่งประกอบด้วยคำถามที่โพสต์โดยผู้ทำงานมวลชนในชุดบทความ Wikipedia สิ่งนี้จะทำให้เรามีแบบจำลองที่สามารถคำนวณการทำนายได้ดังนี้: + + + +นี่เป็นการจัดแสดงโมเดลที่ได้รับการฝึกอบรมและอัปโหลดไปยัง Hub โดยใช้โค้ดที่แสดงในส่วนนี้ คุณสามารถค้นหาและตรวจสอบคำทำนายอีกครั้งได้[ที่นี่](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F). + + + +💡 โมเดลเฉพาะตัวเข้ารหัสเช่น BERT มีแนวโน้มที่จะแยกคำตอบของคำถามที่เป็นข้อเท็จจริงเช่น "ใครเป็นผู้คิดค้นสถาปัตยกรรม Transformer?" แต่กลับเจอคำถามปลายเปิดอย่าง "ทำไมท้องฟ้าถึงเป็นสีฟ้า" ในกรณีที่ท้าทายกว่านี้ โดยทั่วไปโมเดลตัวเข้ารหัส-ตัวถอดรหัส เช่น T5 และ BART มักจะใช้เพื่อสังเคราะห์ข้อมูลในลักษณะที่ค่อนข้างคล้ายกับ [การสรุปข้อความ](/course/th/chapter7/5) หากคุณสนใจในการตอบคำถาม *ทั่วไป (generative)* ประเภทนี้ เราขอแนะนำให้ดู [การสาธิต](https://yjernite.github.io/lfqa.html) โดยอิงตาม [ชุดข้อมูล ELI5](https:// Huggingface.co/datasets/eli5) + + + +## การเตรียมข้อมูล[[การเตรียมข้อมูล]] + +ชุดข้อมูลที่ใช้เป็นเกณฑ์มาตรฐานทางวิชาการสำหรับการตอบคำถามแบบดึงข้อมูลมากที่สุดคือ [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/) นั่นคือชุดข้อมูลที่เราจะใช้ที่นี่ นอกจากนี้ยังมีเกณฑ์มาตรฐาน [SQuAD v2](https://huggingface.co/datasets/squad_v2) ที่ยากขึ้นอีกด้วย ซึ่งรวมถึงคำถามที่ไม่มีคำตอบด้วย ตราบใดที่ชุดข้อมูลของคุณมีคอลัมน์สำหรับบริบท คอลัมน์สำหรับคำถาม และคอลัมน์สำหรับคำตอบ คุณควรจะปรับเปลี่ยนขั้นตอนด้านล่างนี้ได้ + +### ชุดข้อมูล SQuAD[[ชุดข้อมูล-squad]] + +ตามปกติ เราสามารถดาวน์โหลดและแคชชุดข้อมูลได้ในขั้นตอนเดียวด้วย `load_dataset()`: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +จากนั้นเราจะดูออบเจ็กต์นี้เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับชุดข้อมูล SQuAD: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 87599 + }) + validation: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 10570 + }) +}) +``` + +ดูเหมือนว่าเรามีทุกสิ่งที่เราต้องการในช่อง `context`, `question` และ `answers` ดังนั้นมาพิมพ์สิ่งเหล่านั้นสำหรับองค์ประกอบแรกของชุดการฝึกของเรากันดีกว่า: + +```py +print("Context: ", raw_datasets["train"][0]["context"]) +print("Question: ", raw_datasets["train"][0]["question"]) +print("Answer: ", raw_datasets["train"][0]["answers"]) +``` + +```python out +Context: 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.' +Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' +Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} +``` + +ตัวแปล `context` และ `question` นั้นใช้งานได้ตรงไปตรงมามาก ตัวแปล `answers` จะซับซ้อนกว่าเล็กน้อยเนื่องจากจะเปรียบเทียบพจนานุกรมที่มีสองช่องซึ่งเป็นทั้งสองรายการ นี่คือรูปแบบที่เมตริก `squad` คาดหวังในระหว่างการประเมิน หากคุณใช้ข้อมูลของคุณเอง คุณไม่จำเป็นต้องกังวลเกี่ยวกับการใส่คำตอบในรูปแบบเดียวกัน ตัวแปล `text` ค่อนข้างชัดเจน และตัวแปล `answer_start` มีดัชนีอักขระเริ่มต้นของแต่ละคำตอบในบริบท + +ในระหว่างการฝึกมีคำตอบเดียวเท่านั้น เราสามารถตรวจสอบอีกครั้งได้โดยใช้เมธอด `Dataset.filter()`: + +```py +raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1) +``` + +```python out +Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 0 +}) +``` + +อย่างไรก็ตาม สำหรับการประเมิน มีคำตอบที่เป็นไปได้หลายคำตอบสำหรับแต่ละตัวอย่าง ซึ่งอาจเหมือนหรือต่างกัน: + +```py +print(raw_datasets["validation"][0]["answers"]) +print(raw_datasets["validation"][2]["answers"]) +``` + +```python out +{'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]} +{'text': ['Santa Clara, California', "Levi's Stadium", "Levi's Stadium in the San Francisco Bay Area at Santa Clara, California."], 'answer_start': [403, 355, 355]} +``` + +เราจะไม่เจาะลึกสคริปต์การประเมินเนื่องจากทั้งหมดจะถูกรวมไว้ด้วย 🤗 Datasets เมตริก สำหรับเรา แต่เวอร์ชันสั้นคือคำถามบางข้อมีคำตอบที่เป็นไปได้หลายข้อ และสคริปต์นี้จะเปรียบเทียบคำตอบที่คาดการณ์ไว้กับคำตอบทั้งหมด คำตอบที่ยอมรับได้และรับคะแนนที่ดีที่สุด หากเราดูตัวอย่างที่ดัชนี 2 เช่น: + +```py +print(raw_datasets["validation"][2]["context"]) +print(raw_datasets["validation"][2]["question"]) +``` + +```python out +'Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi\'s Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.' +'Where did Super Bowl 50 take place?' +``` + +เราจะเห็นได้ว่าคำตอบสามารถเป็นหนึ่งในสามความเป็นไปได้ที่เราเคยเห็นมาก่อน + +### การประมวลผลข้อมูลการฝึกอบรม[[การประมวลผลข้อมูลการฝึกอบรม]] + + + +เริ่มต้นด้วยการประมวลผลข้อมูลการฝึกอบรมล่วงหน้า ส่วนที่ยากคือการสร้างป้ายกำกับสำหรับคำตอบของคำถาม ซึ่งจะเป็นตำแหน่งเริ่มต้นและจุดสิ้นสุดของโทเค็นที่สอดคล้องกับคำตอบภายในบริบท + +แต่อย่าพึ่งรีบก้าวไปข้างหน้าตัวเราเองนัก ขั้นแรก เราต้องแปลงข้อความในอินพุตเป็น ID ที่โมเดลสามารถเข้าใจได้ โดยใช้โทเค็นไนเซอร์: + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +ดังที่ได้กล่าวไว้ก่อนหน้านี้ เราจะปรับแต่งโมเดล BERT อย่างละเอียด แต่คุณสามารถใช้โมเดลประเภทอื่นได้ตราบใดที่มีการใช้งานโทเค็นไนเซอร์ที่รวดเร็ว คุณสามารถดูสถาปัตยกรรมทั้งหมดที่มาพร้อมกับเวอร์ชันที่รวดเร็วได้ใน [ตารางใหญ่นี้](https://huggingface.co/transformers/#supported-frameworks) และเพื่อตรวจสอบว่าวัตถุ `tokenizer` ที่คุณใช้อยู่นั้นเป็นจริง สนับสนุนโดย 🤗 Tokenizers คุณสามารถดูแอตทริบิวต์ `is_fast` ได้: + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +เราสามารถส่งคำถามและบริบทให้กับ tokenizer ของเราร่วมกันได้ และมันจะแทรกโทเค็นพิเศษอย่างเหมาะสมเพื่อสร้างประโยคดังนี้: + +``` +[CLS] question [SEP] context [SEP] +``` + +มาตรวจสอบอีกครั้ง: + +```py +context = raw_datasets["train"][0]["context"] +question = raw_datasets["train"][0]["question"] + +inputs = tokenizer(question, context) +tokenizer.decode(inputs["input_ids"]) +``` + +```python out +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, ' +'the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin ' +'Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms ' +'upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred ' +'Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a ' +'replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette ' +'Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues ' +'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' +``` + +ป้ายกำกับจะเป็นดัชนีของโทเค็นที่เริ่มต้นและสิ้นสุดคำตอบ และโมเดลจะได้รับมอบหมายให้คาดการณ์ค่า logit เริ่มต้นและสิ้นสุดต่อโทเค็นในอินพุต โดยมีป้ายกำกับทางทฤษฎีดังต่อไปนี้: + +
+One-hot encoded labels for question answering. + +
+ +ในกรณีนี้บริบทไม่ยาวเกินไป แต่ตัวอย่างบางส่วนในชุดข้อมูลมีบริบทที่ยาวมากซึ่งจะเกินความยาวสูงสุดที่เราตั้งไว้ (ซึ่งในกรณีนี้คือ 384) ดังที่เราเห็นใน [บทที่ 6](/course/th/chapter6/4) เมื่อเราสำรวจภายในของไปป์ไลน์ `question-answering` เราจะจัดการกับบริบทที่ยาวโดยการสร้างฟีเจอร์การฝึกอบรมหลายอย่างจากตัวอย่างชุดข้อมูลของเราชุดเดียว โดยมี หน้าต่างบานเลื่อนระหว่างพวกเขา + +หากต้องการดูวิธีการทำงานโดยใช้ตัวอย่างปัจจุบัน เราสามารถจำกัดความยาวไว้ที่ 100 และใช้หน้าต่างเลื่อนขนาด 50 โทเค็น เพื่อเป็นการเตือนใจ เราใช้: + +- `max_length` เพื่อกำหนดความยาวสูงสุด (ในที่นี้คือ 100) +- `truncation="only_second"` เพื่อตัดบริบท (ซึ่งอยู่ในตำแหน่งที่สอง) เมื่อคำถามที่มีบริบทยาวเกินไป +- `stride` เพื่อกำหนดจำนวนโทเค็นที่ทับซ้อนกันระหว่างสองชิ้นต่อเนื่องกัน (ที่นี่ 50) +- `return_overflowing_tokens=True` เพื่อให้โทเค็นทราบว่าเราต้องการโทเค็นที่ล้น + +```py +inputs = tokenizer( + question, + context, + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basi [SEP]' +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin [SEP]' +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 [SEP]' +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP]. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' +``` + +ดังที่เราเห็น ตัวอย่างของเราแบ่งออกเป็นสี่อินพุต แต่ละอินพุตมีคำถามและบริบทบางส่วน โปรดทราบว่าคำตอบของคำถาม ("Bernadette Soubirous") จะปรากฏเฉพาะในการป้อนข้อมูลครั้งที่ 3 และสุดท้าย ดังนั้นโดยการจัดการกับบริบทที่ยาวด้วยวิธีนี้ เราจะสร้างตัวอย่างการฝึกอบรมบางส่วนโดยที่คำตอบไม่ได้รวมอยู่ในบริบท สำหรับตัวอย่างเหล่านั้น ป้ายกำกับจะเป็น `start_position = end_position = 0` (ดังนั้นเราจึงคาดการณ์โทเค็น `[CLS]`) นอกจากนี้เรายังจะตั้งค่าป้ายกำกับเหล่านั้นในกรณีที่โชคร้ายซึ่งคำตอบถูกตัดออก เพื่อให้เรามีเพียงจุดเริ่มต้น (หรือสิ้นสุด) ของคำตอบเท่านั้น สำหรับตัวอย่างที่คำตอบอยู่ในบริบทโดยสมบูรณ์ ป้ายกำกับจะเป็นดัชนีของโทเค็นที่คำตอบเริ่มต้นและดัชนีของโทเค็นที่คำตอบสิ้นสุด + +ชุดข้อมูลจะแสดงอักขระเริ่มต้นของคำตอบในบริบท และโดยการเพิ่มความยาวของคำตอบ เราก็สามารถค้นหาอักขระสิ้นสุดในบริบทได้ หากต้องการจับคู่ดัชนีเหล่านั้นกับดัชนีโทเค็น เราจะต้องใช้การแมปออฟเซ็ตที่เราศึกษาใน [บทที่ 6](/course/th/chapter6/4) เราสามารถให้โทเค็นของเราส่งคืนสิ่งเหล่านี้ได้โดยส่งผ่าน `return_offsets_mapping=True`: + +```py +inputs = tokenizer( + question, + context, + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +inputs.keys() +``` + +```python out +dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) +``` + +ดังที่เราเห็น เราได้รับ ID อินพุตปกติ ID ประเภทโทเค็น และรูปแบบความสนใจ รวมถึงการแมปออฟเซ็ตที่เราต้องการและคีย์พิเศษ `overflow_to_sample_mapping` ค่าที่สอดคล้องกันจะเป็นประโยชน์กับเราเมื่อเราสร้างโทเค็นหลายข้อความในเวลาเดียวกัน (ซึ่งเราควรทำเพื่อให้ได้รับประโยชน์จากความจริงที่ว่าโทเค็นไนเซอร์ของเราได้รับการสนับสนุนโดย Rust) เนื่องจากตัวอย่างหนึ่งสามารถให้คุณสมบัติได้หลายอย่าง จึงแมปแต่ละคุณสมบัติกับตัวอย่างที่มาจากตัวอย่างนั้น เนื่องจากที่นี่เราสร้างโทเค็นเพียงตัวอย่างเดียว เราจึงได้รายการ `0`: + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +แต่ถ้าเราสร้างตัวอย่างให้มากขึ้น สิ่งนี้จะมีประโยชน์มากขึ้น: + +```py +inputs = tokenizer( + raw_datasets["train"][2:6]["question"], + raw_datasets["train"][2:6]["context"], + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, + return_offsets_mapping=True, +) + +print(f"The 4 examples gave {len(inputs['input_ids'])} features.") +print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.") +``` + +```python out +'The 4 examples gave 19 features.' +'Here is where each comes from: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3].' +``` + +ดังที่เราเห็น ตัวอย่างสามตัวแรก (ที่ดัชนี 2, 3 และ 4 ในชุดการฝึก) แต่ละตัวอย่างให้คุณสมบัติสี่ประการ และตัวอย่างสุดท้าย (ที่ดัชนี 5 ในชุดการฝึก) ให้คุณสมบัติ 7 ประการ + +ข้อมูลนี้จะเป็นประโยชน์ในการแมปคุณลักษณะแต่ละรายการที่เราได้รับกับป้ายกำกับที่เกี่ยวข้อง ดังที่ได้กล่าวไว้ก่อนหน้านี้ ป้ายกำกับเหล่านั้นคือ: + +- `(0, 0)` หากคำตอบไม่อยู่ในช่วงบริบทที่สอดคล้องกัน +- `(start_position, end_position)` หากคำตอบอยู่ในช่วงที่สอดคล้องกันของบริบท โดยที่ `start_position` เป็นดัชนีของโทเค็น (ใน ID อินพุต) ที่จุดเริ่มต้นของคำตอบ และ `end_position` เป็นดัชนีของ โทเค็น (ใน ID อินพุต) ที่คำตอบสิ้นสุด + +เพื่อพิจารณาว่ากรณีใดเป็นกรณีและหากเกี่ยวข้อง ตำแหน่งของโทเค็น เราจะค้นหาดัชนีที่เริ่มต้นและสิ้นสุดบริบทใน ID อินพุตก่อน เราสามารถใช้ ID ประเภทโทเค็นเพื่อทำสิ่งนี้ได้ แต่เนื่องจากสิ่งเหล่านั้นไม่จำเป็นสำหรับทุกรุ่น (เช่น DistilBERT ไม่จำเป็นต้องใช้) เราจะใช้วิธี `sequence_ids()` ของ `BatchEncoding` ของเราแทน tokenizer กลับมา + +เมื่อเรามีดัชนีโทเค็นเหล่านั้นแล้ว เราจะดูออฟเซ็ตที่สอดคล้องกัน ซึ่งเป็นสิ่งอันดับของจำนวนเต็มสองตัวที่แสดงถึงช่วงของอักขระในบริบทดั้งเดิม ดังนั้นเราจึงสามารถตรวจจับได้ว่าส่วนบริบทในคุณลักษณะนี้เริ่มต้นหลังจากคำตอบหรือสิ้นสุดก่อนที่คำตอบจะเริ่มต้น (ซึ่งในกรณีนี้ป้ายกำกับคือ `(0, 0)`) หากไม่เป็นเช่นนั้น เราจะวนซ้ำเพื่อค้นหาโทเค็นแรกและโทเค็นสุดท้ายของคำตอบ: + +```py +answers = raw_datasets["train"][2:6]["answers"] +start_positions = [] +end_positions = [] + +for i, offset in enumerate(inputs["offset_mapping"]): + sample_idx = inputs["overflow_to_sample_mapping"][i] + answer = answers[sample_idx] + start_char = answer["answer_start"][0] + end_char = answer["answer_start"][0] + len(answer["text"][0]) + sequence_ids = inputs.sequence_ids(i) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (0, 0) + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: + start_positions.append(0) + end_positions.append(0) + else: + # Otherwise it's the start and end token positions + idx = context_start + while idx <= context_end and offset[idx][0] <= start_char: + idx += 1 + start_positions.append(idx - 1) + + idx = context_end + while idx >= context_start and offset[idx][1] >= end_char: + idx -= 1 + end_positions.append(idx + 1) + +start_positions, end_positions +``` + +```python out +([83, 51, 19, 0, 0, 64, 27, 0, 34, 0, 0, 0, 67, 34, 0, 0, 0, 0, 0], + [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0]) +``` + +มาดูผลลัพธ์บางส่วนเพื่อยืนยันว่าแนวทางของเราถูกต้อง สำหรับคุณลักษณะแรก เราพบว่า `(83, 85)` เป็นป้ายกำกับ ดังนั้นเรามาเปรียบเทียบคำตอบทางทฤษฎีกับช่วงโทเค็นที่ถอดรหัสตั้งแต่ 83 ถึง 85 (รวม): + +```py +idx = 0 +sample_idx = inputs["overflow_to_sample_mapping"][idx] +answer = answers[sample_idx]["text"][0] + +start = start_positions[idx] +end = end_positions[idx] +labeled_answer = tokenizer.decode(inputs["input_ids"][idx][start : end + 1]) + +print(f"Theoretical answer: {answer}, labels give: {labeled_answer}") +``` + +```python out +'Theoretical answer: the Main Building, labels give: the Main Building' +``` + +นี่มันแมตช์กันแล้ว! ตอนนี้เรามาตรวจสอบดัชนี 4 โดยที่เราตั้งค่าป้ายกำกับเป็น `(0, 0)` ซึ่งหมายความว่าคำตอบไม่อยู่ในบริบทของคุณลักษณะนั้น: + +```py +idx = 4 +sample_idx = inputs["overflow_to_sample_mapping"][idx] +answer = answers[sample_idx]["text"][0] + +decoded_example = tokenizer.decode(inputs["input_ids"][idx]) +print(f"Theoretical answer: {answer}, decoded example: {decoded_example}") +``` + +```python out +'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grot [SEP]' +``` + +อันที่จริงเราไม่เห็นคำตอบในบริบท + + + +✏️ **ตาคุณแล้ว!** เมื่อใช้สถาปัตยกรรม XLNet จะมีการใช้ช่องว่างภายในทางด้านซ้าย และสลับคำถามและบริบท ปรับโค้ดทั้งหมดที่เราเพิ่งเห็นเข้ากับสถาปัตยกรรม XLNet (และเพิ่ม `padding=True`) โปรดทราบว่าโทเค็น `[CLS]` อาจไม่อยู่ที่ตำแหน่ง 0 โดยมีการใช้การเติม + + + +ตอนนี้เราได้เห็นวิธีประมวลผลข้อมูลการฝึกอบรมล่วงหน้าทีละขั้นตอนแล้ว เราก็สามารถจัดกลุ่มข้อมูลดังกล่าวเป็นฟังก์ชันที่เราจะนำไปใช้กับชุดข้อมูลการฝึกอบรมทั้งหมดได้ เราจะแพดทุกฟีเจอร์ให้มีความยาวสูงสุดที่เราตั้งไว้ เนื่องจากบริบทส่วนใหญ่จะยาว (และตัวอย่างที่เกี่ยวข้องจะถูกแบ่งออกเป็นหลายฟีเจอร์) ดังนั้นจึงไม่มีประโยชน์ที่แท้จริงในการใช้ช่องว่างภายในแบบไดนามิกที่นี่: + +```py +max_length = 384 +stride = 128 + + +def preprocess_training_examples(examples): + questions = [q.strip() for q in examples["question"]] + inputs = tokenizer( + questions, + examples["context"], + max_length=max_length, + truncation="only_second", + stride=stride, + return_overflowing_tokens=True, + return_offsets_mapping=True, + padding="max_length", + ) + + offset_mapping = inputs.pop("offset_mapping") + sample_map = inputs.pop("overflow_to_sample_mapping") + answers = examples["answers"] + start_positions = [] + end_positions = [] + + for i, offset in enumerate(offset_mapping): + sample_idx = sample_map[i] + answer = answers[sample_idx] + start_char = answer["answer_start"][0] + end_char = answer["answer_start"][0] + len(answer["text"][0]) + sequence_ids = inputs.sequence_ids(i) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (0, 0) + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: + start_positions.append(0) + end_positions.append(0) + else: + # Otherwise it's the start and end token positions + idx = context_start + while idx <= context_end and offset[idx][0] <= start_char: + idx += 1 + start_positions.append(idx - 1) + + idx = context_end + while idx >= context_start and offset[idx][1] >= end_char: + idx -= 1 + end_positions.append(idx + 1) + + inputs["start_positions"] = start_positions + inputs["end_positions"] = end_positions + return inputs +``` + +โปรดทราบว่าเราได้กำหนดค่าคงที่สองตัวเพื่อกำหนดความยาวสูงสุดที่ใช้ตลอดจนความยาวของหน้าต่างบานเลื่อน และเราได้เพิ่มการล้างข้อมูลเล็กน้อยก่อนที่จะสร้างโทเค็น: คำถามบางข้อในชุดข้อมูล SQuAD มีช่องว่างเพิ่มเติมที่จุดเริ่มต้นและส่วน ที่ไม่ได้เพิ่มอะไรเลย (และใช้พื้นที่เมื่อถูกโทเค็นหากคุณใช้โมเดลเช่น RoBERTa) ดังนั้นเราจึงลบช่องว่างพิเศษเหล่านั้นออก + +หากต้องการใช้ฟังก์ชันนี้กับชุดการฝึกทั้งหมด เราใช้เมธอด `Dataset.map()` พร้อมกับแฟล็ก `batched=True` จำเป็นที่นี่เนื่องจากเรากำลังเปลี่ยนความยาวของชุดข้อมูล (เนื่องจากตัวอย่างหนึ่งสามารถให้คุณสมบัติการฝึกอบรมได้หลายอย่าง): + +```py +train_dataset = raw_datasets["train"].map( + preprocess_training_examples, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +len(raw_datasets["train"]), len(train_dataset) +``` + +```python out +(87599, 88729) +``` + +ดังที่เราเห็น การประมวลผลล่วงหน้าได้เพิ่มคุณสมบัติประมาณ 1,000 รายการ ชุดการฝึกอบรมของเราพร้อมใช้งานแล้ว -- มาดูการประมวลผลล่วงหน้าของชุดการตรวจสอบกันดีกว่า! + +### การประมวลผลของการตรวจสอบข้อมูล[[การประมวลผลของการตรวจสอบข้อมูล]] + +การประมวลผลข้อมูลการตรวจสอบล่วงหน้าจะง่ายขึ้นเล็กน้อย เนื่องจากเราไม่จำเป็นต้องสร้างป้ายกำกับ (เว้นแต่ว่าเราต้องการคำนวณการสูญเสียการตรวจสอบความถูกต้อง แต่ตัวเลขนั้นไม่ได้ช่วยให้เราเข้าใจว่าแบบจำลองนั้นดีเพียงใด) ความสุขที่แท้จริงคือการตีความการทำนายของแบบจำลองให้ครอบคลุมช่วงบริบทดั้งเดิม สำหรับสิ่งนี้ เราเพียงแค่ต้องจัดเก็บทั้งการแมปออฟเซ็ตและวิธีการบางอย่างในการจับคู่คุณลักษณะที่สร้างขึ้นแต่ละอย่างกับตัวอย่างดั้งเดิมที่มาจากนั้น เนื่องจากมีคอลัมน์ ID ในชุดข้อมูลดั้งเดิม เราจะใช้ ID นั้น + +สิ่งเดียวที่เราจะเพิ่มที่นี่คือการทำความสะอาดการแมปออฟเซ็ตเล็กน้อย โดยจะมีการชดเชยสำหรับคำถามและบริบท แต่เมื่อเราอยู่ในขั้นตอนหลังการประมวลผล เราจะไม่มีทางรู้ได้เลยว่าส่วนใดของ ID อินพุตที่สอดคล้องกับบริบท และส่วนใดเป็นคำถาม (the ` sequence_ids()` ที่เราใช้นั้นใช้ได้กับเอาต์พุตของ tokenizer เท่านั้น) ดังนั้น เราจะตั้งค่าออฟเซ็ตที่สอดคล้องกับคำถามเป็น `None`: + +```py +def preprocess_validation_examples(examples): + questions = [q.strip() for q in examples["question"]] + inputs = tokenizer( + questions, + examples["context"], + max_length=max_length, + truncation="only_second", + stride=stride, + return_overflowing_tokens=True, + return_offsets_mapping=True, + padding="max_length", + ) + + sample_map = inputs.pop("overflow_to_sample_mapping") + example_ids = [] + + for i in range(len(inputs["input_ids"])): + sample_idx = sample_map[i] + example_ids.append(examples["id"][sample_idx]) + + sequence_ids = inputs.sequence_ids(i) + offset = inputs["offset_mapping"][i] + inputs["offset_mapping"][i] = [ + o if sequence_ids[k] == 1 else None for k, o in enumerate(offset) + ] + + inputs["example_id"] = example_ids + return inputs +``` + +เราสามารถใช้ฟังก์ชันนี้กับชุดข้อมูลการตรวจสอบทั้งหมดได้เหมือนเมื่อก่อน: + +```py +validation_dataset = raw_datasets["validation"].map( + preprocess_validation_examples, + batched=True, + remove_columns=raw_datasets["validation"].column_names, +) +len(raw_datasets["validation"]), len(validation_dataset) +``` + +```python out +(10570, 10822) +``` + +ในกรณีนี้ เราได้เพิ่มตัวอย่างเพียงไม่กี่ร้อยตัวอย่าง ดังนั้นจึงดูเหมือนว่าบริบทในชุดข้อมูลการตรวจสอบจะสั้นลงเล็กน้อย + +ตอนนี้เราได้ประมวลผลข้อมูลทั้งหมดล่วงหน้าแล้ว เราก็เข้าสู่การฝึกอบรมได้ + +{#if fw === 'pt'} + +## ปรับแต่งโมเดลอย่างละเอียดด้วย `Trainer` API[[ปรับแต่งโมเดลอย่างละเอียดด้วย-trainer-api]] + +โค้ดการฝึกอบรมสำหรับตัวอย่างนี้จะมีลักษณะคล้ายกับโค้ดในส่วนก่อนหน้ามาก สิ่งที่ยากที่สุดคือการเขียนฟังก์ชัน `compute_metrics()` เนื่องจากเราได้เพิ่มตัวอย่างทั้งหมดตามความยาวสูงสุดที่เราตั้งไว้ จึงไม่มีตัวเปรียบเทียบข้อมูลให้กำหนด ดังนั้นการคำนวณหน่วยเมตริกนี้จึงเป็นสิ่งเดียวที่เราต้องกังวลจริงๆ ส่วนที่ยากคือการประมวลผลการทำนายแบบจำลองในช่วงข้อความในตัวอย่างดั้งเดิม เมื่อเราทำสิ่งนั้นแล้ว หน่วยเมตริกจากไลบรารี 🤗 Datasets จะทำงานส่วนใหญ่ให้เรา + +{:else} + +## ปรับแต่งโมเดลอย่างละเอียดด้วย Keras[[ปรับแต่งโมเดลอย่างละเอียดด้วย-keras]] + +โค้ดการฝึกอบรมสำหรับตัวอย่างนี้จะมีลักษณะคล้ายกับโค้ดในส่วนก่อนหน้ามาก แต่การคำนวณหน่วยเมตริกจะมีความท้าทายเป็นพิเศษ เนื่องจากเราได้เพิ่มตัวอย่างทั้งหมดตามความยาวสูงสุดที่เราตั้งไว้ จึงไม่มีตัวเปรียบเทียบข้อมูลให้กำหนด ดังนั้นการคำนวณหน่วยเมตริกนี้จึงเป็นสิ่งเดียวที่เราต้องกังวลจริงๆ ส่วนที่ยากคือการประมวลผลการทำนายแบบจำลองในช่วงข้อความในตัวอย่างดั้งเดิม เมื่อเราทำสิ่งนั้นแล้ว หน่วยเมตริกจากไลบรารี 🤗 Datasets จะทำงานส่วนใหญ่ให้เรา + +{/if} + +### หลังการประมวลผล[[หลังการประมวลผล]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +โมเดลจะแสดง logits สำหรับตำแหน่งเริ่มต้นและสิ้นสุดของคำตอบใน ID อินพุต ดังที่เราเห็นระหว่างการสำรวจไปป์ไลน์ [`การตอบคำถาม''](/course/th/chapter6/3b) ขั้นตอนหลังการประมวลผลจะคล้ายกับสิ่งที่เราทำที่นั่น ดังนั้นนี่คือคำเตือนสั้นๆ เกี่ยวกับการดำเนินการที่เราดำเนินการ: + +- เราปิดบังการบันทึกเริ่มต้นและสิ้นสุดที่สอดคล้องกับโทเค็นนอกบริบท +- จากนั้นเราแปลงบันทึกการเริ่มต้นและสิ้นสุดให้เป็นความน่าจะเป็นโดยใช้ softmax +- เราระบุคะแนนของคู่ `(start_token, end_token)` แต่ละคู่โดยหาผลคูณของความน่าจะเป็นสองรายการที่สอดคล้องกัน +- เรามองหาคู่ที่มีคะแนนสูงสุดที่ให้คำตอบที่ถูกต้อง (เช่น `start_token` ต่ำกว่า `end_token`) + +ที่นี่เราจะเปลี่ยนแปลงกระบวนการนี้เล็กน้อยเนื่องจากเราไม่จำเป็นต้องคำนวณคะแนนจริง (เพียงคำตอบที่คาดการณ์ไว้) ซึ่งหมายความว่าเราสามารถข้ามขั้นตอน softmax ได้ เพื่อให้เร็วขึ้น เราจะไม่ให้คะแนนคู่ `(start_token, end_token)` ที่เป็นไปได้ทั้งหมด แต่จะให้คะแนนเฉพาะคู่ที่สอดคล้องกับบันทึก `n_best` สูงสุด (ด้วย `n_best=20`) เนื่องจากเราจะข้าม softmax คะแนนเหล่านั้นจะเป็นคะแนน logit และจะได้รับจากผลรวมของการบันทึกเริ่มต้นและสิ้นสุด (แทนที่จะเป็นผลคูณ เนื่องจากกฎ \\(\log(ab) = \log(a) + \log(b)\\)) + +เพื่อแสดงให้เห็นทั้งหมดนี้ เราจำเป็นต้องมีการคาดการณ์บางอย่าง เนื่องจากเรายังไม่ได้ฝึกโมเดลของเรา เราจะใช้โมเดลเริ่มต้นสำหรับไปป์ไลน์ QA เพื่อสร้างการคาดการณ์ในส่วนเล็กๆ ของชุดการตรวจสอบ เราสามารถใช้ฟังก์ชันการประมวลผลเหมือนเดิมได้ เนื่องจากมันอาศัย `tokenizer` คงที่ทั่วโลก เราจึงต้องเปลี่ยนอ็อบเจ็กต์นั้นเป็น tokenizer ของโมเดลที่เราต้องการใช้ชั่วคราว: + +```python +small_eval_set = raw_datasets["validation"].select(range(100)) +trained_checkpoint = "distilbert-base-cased-distilled-squad" + +tokenizer = AutoTokenizer.from_pretrained(trained_checkpoint) +eval_set = small_eval_set.map( + preprocess_validation_examples, + batched=True, + remove_columns=raw_datasets["validation"].column_names, +) +``` + +ตอนนี้เมื่อการประมวลผลล่วงหน้าเสร็จสิ้น เราจะเปลี่ยนโทเค็นไนเซอร์กลับไปเป็นอันที่เราเลือกไว้ตั้งแต่แรก: + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +จากนั้นเราจะลบคอลัมน์ของ `eval_set` ของเราที่โมเดลไม่ได้คาดหวังไว้ สร้างแบทช์ที่มีชุดการตรวจสอบความถูกต้องเล็กๆ ทั้งหมดนั้น แล้วส่งต่อผ่านโมเดล หากมี GPU เราจะใช้มันเพื่อให้ทำงานเร็วขึ้น: + +{#if fw === 'pt'} + +```python +import torch +from transformers import AutoModelForQuestionAnswering + +eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) +eval_set_for_model.set_format("torch") + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +batch = {k: eval_set_for_model[k].to(device) for k in eval_set_for_model.column_names} +trained_model = AutoModelForQuestionAnswering.from_pretrained(trained_checkpoint).to( + device +) + +with torch.no_grad(): + outputs = trained_model(**batch) +``` + +เนื่องจาก `Trainer` จะให้การคาดการณ์แก่เราเป็นอาร์เรย์ NumPy เราจึงจับบันทึกการเริ่มต้นและสิ้นสุดแล้วแปลงเป็นรูปแบบนั้น: + +```python +start_logits = outputs.start_logits.cpu().numpy() +end_logits = outputs.end_logits.cpu().numpy() +``` + +{:else} + +```python +import tensorflow as tf +from transformers import TFAutoModelForQuestionAnswering + +eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) +eval_set_for_model.set_format("numpy") + +batch = {k: eval_set_for_model[k] for k in eval_set_for_model.column_names} +trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoint) + +outputs = trained_model(**batch) +``` + +เพื่อความสะดวกในการทดลอง ลองแปลงเอาต์พุตเหล่านี้เป็นอาร์เรย์ NumPy: + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +ตอนนี้ เราจำเป็นต้องค้นหาคำตอบที่คาดการณ์ไว้สำหรับแต่ละตัวอย่างใน `small_eval_set` ของเรา ตัวอย่างหนึ่งอาจถูกแบ่งออกเป็นหลายคุณลักษณะใน `eval_set` ดังนั้นขั้นตอนแรกคือการแมปแต่ละตัวอย่างใน `small_eval_set` กับคุณลักษณะที่เกี่ยวข้องใน `eval_set`: + +```python +import collections + +example_to_features = collections.defaultdict(list) +for idx, feature in enumerate(eval_set): + example_to_features[feature["example_id"]].append(idx) +``` + +ด้วยสิ่งนี้ เราจึงสามารถทำงานได้จริงๆ โดยการวนซ้ำตัวอย่างทั้งหมด และสำหรับแต่ละตัวอย่าง ผ่านฟีเจอร์ที่เกี่ยวข้องทั้งหมด ดังที่เราได้กล่าวไว้ก่อนหน้านี้ เราจะดูคะแนน logit สำหรับการบันทึกเริ่มต้นและบันทึกสิ้นสุด `n_best` ยกเว้นตำแหน่งที่ให้: + +- คำตอบที่ไม่อยู่ในบริบท +- คำตอบที่มีความยาวเป็นลบ +- คำตอบที่ยาวเกินไป (เราจำกัดความเป็นไปได้ที่ `max_answer_length=30`) + +เมื่อเราได้คะแนนคำตอบที่เป็นไปได้ทั้งหมดสำหรับตัวอย่างหนึ่งแล้ว เราก็เลือกคำตอบที่มีคะแนน logit ที่ดีที่สุด: + +```python +import numpy as np + +n_best = 20 +max_answer_length = 30 +predicted_answers = [] + +for example in small_eval_set: + example_id = example["id"] + context = example["context"] + answers = [] + + for feature_index in example_to_features[example_id]: + start_logit = start_logits[feature_index] + end_logit = end_logits[feature_index] + offsets = eval_set["offset_mapping"][feature_index] + + start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() + end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() + for start_index in start_indexes: + for end_index in end_indexes: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > max_answer_length. + if ( + end_index < start_index + or end_index - start_index + 1 > max_answer_length + ): + continue + + answers.append( + { + "text": context[offsets[start_index][0] : offsets[end_index][1]], + "logit_score": start_logit[start_index] + end_logit[end_index], + } + ) + + best_answer = max(answers, key=lambda x: x["logit_score"]) + predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) +``` + +รูปแบบสุดท้ายของคำตอบที่คาดการณ์คือคำตอบที่คาดหวังจากเมตริกที่เราจะใช้ ตามปกติ เราสามารถโหลดมันได้ด้วยความช่วยเหลือของ 🤗 Evaluate ไลบรารี่: + +```python +import evaluate + +metric = evaluate.load("squad") +``` + +ตัวชี้วัดนี้คาดหวังคำตอบที่ทำนายไว้ในรูปแบบที่เราเห็นด้านบน (รายการพจนานุกรมที่มีหนึ่งคีย์สำหรับ ID ของตัวอย่างและหนึ่งคีย์สำหรับข้อความที่คาดเดา) และคำตอบทางทฤษฎีในรูปแบบด้านล่าง (รายการพจนานุกรมที่มีหนึ่งคีย์ สำหรับ ID ของตัวอย่างและหนึ่งคีย์สำหรับคำตอบที่เป็นไปได้): + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +ตอนนี้เราสามารถตรวจสอบได้ว่าเราได้รับผลลัพธ์ที่สมเหตุสมผลโดยดูที่องค์ประกอบแรกของทั้งสองรายการ: + +```python +print(predicted_answers[0]) +print(theoretical_answers[0]) +``` + +```python out +{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'} +{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}} +``` + +ก็ไม่เลวนะ! ตอนนี้เรามาดูคะแนนที่หน่วยวัดมอบให้เรา: + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +นั่นค่อนข้างดีอีกครั้งเมื่อพิจารณาว่าตาม [เอกสาร] (https://arxiv.org/abs/1910.01108v2) DitilBERT ที่ปรับแต่งอย่างละเอียดบน SQuAD จะได้รับ 79.1 และ 86.9 สำหรับคะแนนเหล่านั้นในชุดข้อมูลทั้งหมด + +{#if fw === 'pt'} + +ตอนนี้เรามาใส่ทุกสิ่งที่เราเพิ่งทำไว้ในฟังก์ชัน `compute_metrics()` ที่เราจะใช้ใน `Trainer` โดยปกติแล้ว ฟังก์ชัน `compute_metrics()` นั้นจะได้รับเฉพาะทูเพิล `eval_preds` พร้อมด้วยบันทึกและป้ายกำกับ ที่นี่เราจะต้องเพิ่มอีกเล็กน้อย เนื่องจากเราต้องดูในชุดข้อมูลของคุณลักษณะสำหรับออฟเซ็ตและในชุดข้อมูลของตัวอย่างสำหรับบริบทดั้งเดิม ดังนั้นเราจะไม่สามารถใช้ฟังก์ชันนี้เพื่อรับผลการประเมินปกติระหว่างการฝึกอบรมได้ . เราจะใช้เมื่อสิ้นสุดการฝึกเพื่อตรวจสอบผลลัพธ์เท่านั้น + +ฟังก์ชัน `compute_metrics()` จัดกลุ่มขั้นตอนเดียวกันกับเมื่อก่อน เราเพียงเพิ่มเช็คเล็กน้อยในกรณีที่เราไม่ได้คำตอบที่ถูกต้อง (ซึ่งในกรณีนี้เราจะคาดเดาสตริงว่าง) + +{:else} + +ตอนนี้เรามาใส่ทุกสิ่งที่เราเพิ่งทำไว้ในฟังก์ชัน `compute_metrics()` ที่เราจะใช้หลังจากการฝึกโมเดลของเรา เราจะต้องส่งผ่านมากกว่าแค่บันทึกเอาท์พุตเล็กน้อย เนื่องจากเราต้องดูในชุดข้อมูลของคุณลักษณะสำหรับออฟเซ็ต และในชุดข้อมูลตัวอย่างสำหรับบริบทดั้งเดิม: + +{/if} + +```python +from tqdm.auto import tqdm + + +def compute_metrics(start_logits, end_logits, features, examples): + example_to_features = collections.defaultdict(list) + for idx, feature in enumerate(features): + example_to_features[feature["example_id"]].append(idx) + + predicted_answers = [] + for example in tqdm(examples): + example_id = example["id"] + context = example["context"] + answers = [] + + # Loop through all features associated with that example + for feature_index in example_to_features[example_id]: + start_logit = start_logits[feature_index] + end_logit = end_logits[feature_index] + offsets = features[feature_index]["offset_mapping"] + + start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() + end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() + for start_index in start_indexes: + for end_index in end_indexes: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > max_answer_length + if ( + end_index < start_index + or end_index - start_index + 1 > max_answer_length + ): + continue + + answer = { + "text": context[offsets[start_index][0] : offsets[end_index][1]], + "logit_score": start_logit[start_index] + end_logit[end_index], + } + answers.append(answer) + + # Select the answer with the best score + if len(answers) > 0: + best_answer = max(answers, key=lambda x: x["logit_score"]) + predicted_answers.append( + {"id": example_id, "prediction_text": best_answer["text"]} + ) + else: + predicted_answers.append({"id": example_id, "prediction_text": ""}) + + theoretical_answers = [{"id": ex["id"], "answers": ex["answers"]} for ex in examples] + return metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +เราสามารถตรวจสอบการทำงานกับการทำนายของเราได้: + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +ดูดีนะ! ตอนนี้เรามาใช้สิ่งนี้เพื่อปรับแต่งโมเดลของเรา + +### การปรับแต่งโมเดลอย่างละเอียด[[การปรับแต่งโมเดลอย่างละเอียด]] + +{#if fw === 'pt'} + +ตอนนี้เราพร้อมที่จะฝึกโมเดลของเราแล้ว มาสร้างมันขึ้นมาก่อนโดยใช้คลาส `AutoModelForQuestionAnswering` เหมือนเมื่อก่อน: + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +ตอนนี้เราพร้อมที่จะฝึกโมเดลของเราแล้ว มาสร้างมันขึ้นมาก่อนโดยใช้คลาส `TFAutoModelForQuestionAnswering` เหมือนเมื่อก่อน: + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +ตามปกติแล้ว เราได้รับคำเตือนว่าตุ้มน้ำหนักบางตัวไม่ได้ถูกใช้ (อันจากส่วนหัวการฝึกล่วงหน้า) และบางตัวจะถูกเตรียมใช้งานแบบสุ่ม (อันสำหรับหัวตอบคำถาม) ตอนนี้คุณน่าจะคุ้นเคยกับสิ่งนี้แล้ว แต่นั่นหมายความว่าโมเดลนี้ยังไม่พร้อมใช้งานและจำเป็นต้องได้รับการปรับแต่งอย่างละเอียด สิ่งดีๆ ที่เรากำลังดำเนินการอยู่! + +เพื่อให้สามารถผลักดันโมเดลของเราไปยัง Hub ได้ เราจะต้องเข้าสู่ระบบ Hugging Face หากคุณใช้โค้ดนี้ในโน้ตบุ๊ก คุณสามารถทำได้โดยใช้ฟังก์ชันยูทิลิตี้ต่อไปนี้ ซึ่งจะแสดงวิดเจ็ตที่คุณสามารถป้อนข้อมูลรับรองการเข้าสู่ระบบของคุณได้: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +หากคุณไม่ได้ทำงานในโน้ตบุ๊ก เพียงพิมพ์บรรทัดต่อไปนี้ในเทอร์มินัลของคุณ: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +เมื่อเสร็จแล้ว เราก็สามารถกำหนด `TrainingArguments` ของเราได้ ดังที่เราได้กล่าวไปแล้วเมื่อเรากำหนดฟังก์ชันเพื่อคำนวณหน่วยเมตริก เราจะไม่สามารถวนรอบการประเมินตามปกติได้เนื่องจากลายเซ็นต์ของฟังก์ชัน `compute_metrics()` เราสามารถเขียนคลาสย่อยของ `Trainer` ของเราเองเพื่อทำสิ่งนี้ (แนวทางที่คุณสามารถพบได้ใน [สคริปต์ตัวอย่างการตอบคำถาม](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question- answering/trainer_qa.py)) แต่ยาวเกินไปสำหรับส่วนนี้เล็กน้อย แต่เราจะประเมินโมเดลเมื่อสิ้นสุดการฝึกที่นี่เท่านั้น และแสดงวิธีการประเมินตามปกติใน "ลูปการฝึกแบบกำหนดเอง" ด้านล่าง + +นี่คือจุดที่ `Trainer` API แสดงขีดจำกัดของมันและไลบรารี 🤗 Accelerate ก็โดดเด่น: การปรับแต่งคลาสให้เหมาะกับกรณีการใช้งานเฉพาะอาจเป็นเรื่องที่ยุ่งยาก แต่การปรับแต่งลูปการฝึกอบรมที่เปิดกว้างทั้งหมดนั้นเป็นเรื่องง่าย + +มาดู `TrainingArguments` ของเรากัน: + +```python +from transformers import TrainingArguments + +args = TrainingArguments( + "bert-finetuned-squad", + evaluation_strategy="no", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, + fp16=True, + push_to_hub=True, +) +``` + +เราเคยเห็นสิ่งเหล่านี้มาก่อนแล้ว: เราตั้งค่าไฮเปอร์พารามิเตอร์บางอย่าง (เช่น อัตราการเรียนรู้ จำนวนยุคที่เราฝึก และน้ำหนักที่ลดลง) และระบุว่าเราต้องการบันทึกโมเดลเมื่อสิ้นสุดทุก epoch ข้ามการประเมิน และอัปโหลดผลลัพธ์ของเราไปยัง Model Hub นอกจากนี้เรายังเปิดใช้งานการฝึกแบบผสมความแม่นยำด้วย `fp16=True` เนื่องจากสามารถเร่งการฝึกบน GPU ล่าสุดได้อย่างดี + +{:else} + +เมื่อเสร็จแล้ว เราสามารถสร้างชุดข้อมูล TF ของเราได้ เราสามารถใช้ตัวรวบรวมข้อมูลเริ่มต้นอย่างง่ายในครั้งนี้: + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +และตอนนี้เราสร้างชุดข้อมูลตามปกติ + +```python +tf_train_dataset = model.prepare_tf_dataset( + train_dataset, + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = model.prepare_tf_dataset( + validation_dataset, + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +ต่อไป เราจะตั้งค่าไฮเปอร์พารามิเตอร์การฝึกอบรมและรวบรวมโมเดลของเรา: + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# 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_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_train_epochs +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +ในที่สุด เราก็พร้อมที่จะฝึกกับ `model.fit()` แล้ว เราใช้ `PushToHubCallback` เพื่ออัปโหลดโมเดลไปยัง Hub หลังจากแต่ละ epoch + +{/if} + +ตามค่าเริ่มต้น พื้นที่เก็บข้อมูลที่ใช้จะอยู่ในเนมสเปซของคุณและตั้งชื่อตามไดเร็กทอรีเอาต์พุตที่คุณตั้งค่า ดังนั้นในกรณีของเรา มันจะอยู่ใน `"sgugger/bert-finetuned-squad"` เราสามารถแทนที่สิ่งนี้ได้โดยส่ง `hub_model_id`; ตัวอย่างเช่น ในการผลักดันโมเดลไปยังองค์กร `huggingface_course` เราใช้ `hub_model_id="huggingface_course/bert-finetuned-squad"` (ซึ่งเป็นโมเดลที่เราเชื่อมโยงไว้ตอนต้นของส่วนนี้) + +{#if fw === 'pt'} + + + +💡 หากมีไดเร็กทอรีเอาต์พุตที่คุณใช้อยู่ จะต้องเป็นโคลนในเครื่องของที่เก็บที่คุณต้องการพุชไป (ดังนั้นให้ตั้งชื่อใหม่หากคุณได้รับข้อผิดพลาดเมื่อกำหนด `Trainer` ของคุณ) + + + +ในที่สุด เราก็ส่งทุกอย่างไปยังคลาส `Trainer` และเริ่มต้นการฝึกอบรม: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=train_dataset, + eval_dataset=validation_dataset, + tokenizer=tokenizer, +) +trainer.train() +``` + +{:else} + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer) + +# เราจะทำการตรวจสอบในภายหลัง ดังนั้นจึงไม่มีการตรวจสอบความถูกต้องระหว่างการฝึกอบรม +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +โปรดทราบว่าในขณะที่การฝึกเกิดขึ้น แต่ละครั้งที่มีการบันทึกโมเดล (ที่นี่ ทุก epoch) โมเดลจะถูกอัปโหลดไปยัง Hub ในเบื้องหลัง ด้วยวิธีนี้ คุณจะสามารถกลับมาฝึกต่อในเครื่องอื่นได้หากจำเป็น การฝึกอบรมทั้งหมดใช้เวลาสักครู่ (หนึ่งชั่วโมงกว่าๆ บน Titan RTX) ดังนั้นคุณจึงสามารถดื่มกาแฟหรืออ่านซ้ำบางส่วนของหลักสูตรที่คุณพบว่ามีความท้าทายมากขึ้นในขณะที่ดำเนินไป โปรดทราบว่าทันทีที่ยุคแรกเสร็จสิ้น คุณจะเห็นน้ำหนักบางส่วนถูกอัปโหลดไปยังฮับ และคุณสามารถเริ่มเล่นกับโมเดลของคุณบนหน้าของมันได้ + +{#if fw === 'pt'} + +เมื่อการฝึกอบรมเสร็จสิ้น ในที่สุดเราก็สามารถประเมินแบบจำลองของเราได้ (และอธิษฐานว่าเราไม่ได้ใช้เวลาคำนวณทั้งหมดนั้นโดยไม่ได้อะไรเลย) เมธอด `predict()` ของ `Trainer` จะส่งคืนทูเพิลโดยที่องค์ประกอบแรกจะเป็นการคาดการณ์ของโมเดล (ในที่นี้จะจับคู่กับบันทึกการเริ่มต้นและสิ้นสุด) เราส่งข้อมูลนี้ไปยังฟังก์ชัน `compute_metrics()` ของเรา: + +```python +predictions, _, _ = trainer.predict(validation_dataset) +start_logits, end_logits = predictions +compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) +``` + +{:else} + +เมื่อการฝึกอบรมเสร็จสิ้น ในที่สุดเราก็สามารถประเมินแบบจำลองของเราได้ (และอธิษฐานว่าเราไม่ได้ใช้เวลาคำนวณทั้งหมดนั้นโดยไม่ได้อะไรเลย) วิธีการ `predict()` ของ `model` ของเราจะดูแลการรับการคาดการณ์ และเนื่องจากเราได้ทำงานหนักทั้งหมดในการกำหนดฟังก์ชัน `compute_metrics()` ก่อนหน้านี้ เราจึงสามารถได้ผลลัพธ์ในบรรทัดเดียว: + +```python +predictions = model.predict(tf_eval_dataset) +compute_metrics( + predictions["start_logits"], + predictions["end_logits"], + validation_dataset, + raw_datasets["validation"], +) +``` + +{/if} + +```python out +{'exact_match': 81.18259224219489, 'f1': 88.67381321905516} +``` + +ยอดเยี่ยม! จากการเปรียบเทียบ คะแนนพื้นฐานที่รายงานในบทความ BERT สำหรับโมเดลนี้คือ 80.8 และ 88.5 ดังนั้นเราจึงมาถูกที่ที่ควรจะเป็น + +{#if fw === 'pt'} + +สุดท้ายนี้ เราใช้เมธอด `push_to_hub()` เพื่อให้แน่ใจว่าเราจะอัปโหลดโมเดลเวอร์ชันล่าสุด: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +สิ่งนี้จะส่งคืน URL ของการคอมมิตที่เพิ่งทำไป หากคุณต้องการตรวจสอบ: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +`Trainer` ยังร่างรายละเอียดของโมเดลพร้อมผลการประเมินทั้งหมดแล้วอัปโหลด + +{/if} + +ในขั้นตอนนี้ คุณสามารถใช้วิดเจ็ตการอนุมานบน Model Hub เพื่อทดสอบโมเดลและแชร์กับเพื่อน ครอบครัว และสัตว์เลี้ยงตัวโปรดของคุณได้ คุณปรับแต่งแบบจำลองในงานตอบคำถามได้สำเร็จ ขอแสดงความยินดีด้วย! + + + +✏️ **ตาคุณแล้ว!** ลองใช้สถาปัตยกรรมโมเดลอื่นเพื่อดูว่าจะทำงานได้ดีกว่าในงานนี้หรือไม่! + + + +{#if fw === 'pt'} + +หากคุณต้องการเจาะลึกลงไปในลูปการฝึกซ้อมมากขึ้น ตอนนี้เราจะแสดงวิธีทำสิ่งเดียวกันโดยใช้ 🤗 Accelerate + +## ลูปการฝึกแบบกำหนดเอง[[ลูปการฝึกแบบกำหนดเอง]] + +ตอนนี้เรามาดูวงจรการฝึกซ้อมทั้งหมดกัน เพื่อให้คุณปรับแต่งส่วนต่างๆ ที่ต้องการได้อย่างง่ายดาย มันจะดูเหมือนลูปการฝึกอบรมใน [บทที่ 3](/course/th/chapter3/4) มาก ยกเว้นลูปการประเมินผล เราจะสามารถประเมินโมเดลได้อย่างสม่ำเสมอเนื่องจากเราไม่ได้ถูกจำกัดโดยคลาส `Trainer` อีกต่อไป + +### เตรียมทุกอย่างเพื่อการฝึก[[เตรียมทุกอย่างเพื่อการฝึก]] + +ก่อนอื่นเราต้องสร้าง `DataLoader`s จากชุดข้อมูลของเรา เราตั้งค่ารูปแบบของชุดข้อมูลเหล่านั้นเป็น `"torch"` และลบคอลัมน์ในชุดการตรวจสอบความถูกต้องที่แบบจำลองไม่ได้ใช้ จากนั้น เราสามารถใช้ `default_data_collator` ที่ได้รับจาก Transformers เป็น `collate_fn` และสับเปลี่ยนชุดการฝึก แต่ไม่ใช่ชุดการตรวจสอบ: + +```py +from torch.utils.data import DataLoader +from transformers import default_data_collator + +train_dataset.set_format("torch") +validation_set = validation_dataset.remove_columns(["example_id", "offset_mapping"]) +validation_set.set_format("torch") + +train_dataloader = DataLoader( + train_dataset, + shuffle=True, + collate_fn=default_data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + validation_set, collate_fn=default_data_collator, batch_size=8 +) +``` + +ต่อไป เราจะสร้างโมเดลของเราขึ้นมาใหม่ เพื่อให้แน่ใจว่าเราจะไม่ทำการปรับแต่งแบบละเอียดจากเมื่อก่อนต่อไป แต่เริ่มต้นจากโมเดล BERT ที่ได้รับการฝึกไว้ล่วงหน้าอีกครั้ง: + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +จากนั้นเราจะต้องมีเครื่องมือเพิ่มประสิทธิภาพ ตามปกติเราใช้ `AdamW` แบบคลาสสิก ซึ่งเหมือนกับ Adam แต่มีการแก้ไขวิธีการลดน้ำหนัก: + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +เมื่อเรามีอ็อบเจ็กต์ทั้งหมดแล้ว เราก็สามารถส่งมันไปที่เมธอด `accelerator.prepare()` ได้ โปรดทราบว่าหากคุณต้องการฝึก TPU ในสมุดบันทึก Colab คุณจะต้องย้ายโค้ดทั้งหมดนี้ไปยังฟังก์ชันการฝึก และไม่ควรเรียกใช้เซลล์ใดๆ ที่สร้างอินสแตนซ์ "ตัวเร่งความเร็ว" เราสามารถบังคับการฝึกแบบผสมความแม่นยำโดยส่ง `fp16=True` ไปยัง `Accelerator` (หรือหากคุณกำลังรันโค้ดเป็นสคริปต์ เพียงอย่าลืมกรอก 🤗 Accelerate `config` อย่างเหมาะสม) + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +ดังที่คุณควรทราบจากส่วนก่อนหน้านี้ เราสามารถใช้เฉพาะความยาว `train_dataloader` เพื่อคำนวณจำนวนขั้นตอนการฝึกหลังจากที่ใช้เมธอด `accelerator.prepare()` แล้ว เราใช้กำหนดการเชิงเส้นเดียวกันกับในส่วนก่อนหน้า: + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +ในการผลักดันโมเดลของเราไปยังฮับ เราจะต้องสร้างออบเจ็กต์ 'พื้นที่เก็บข้อมูล' ในโฟลเดอร์ที่ใช้งานได้ ขั้นแรกให้เข้าสู่ระบบ Hugging Face Hub หากคุณยังไม่ได้เข้าสู่ระบบ เราจะกำหนดชื่อที่เก็บจาก ID โมเดลที่เราต้องการให้กับโมเดลของเรา (อย่าลังเลที่จะแทนที่ `repo_name` ด้วยตัวเลือกของคุณเอง เพียงต้องมีชื่อผู้ใช้ของคุณ ซึ่งเป็นสิ่งที่ฟังก์ชัน `get_full_repo_name()` ทำ ): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "bert-finetuned-squad-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/bert-finetuned-squad-accelerate' +``` + +จากนั้นเราสามารถโคลนพื้นที่เก็บข้อมูลนั้นในโฟลเดอร์ในเครื่องได้ หากมีอยู่แล้ว โฟลเดอร์ในเครื่องนี้ควรเป็นโคลนของพื้นที่เก็บข้อมูลที่เรากำลังทำงานด้วย: + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +ตอนนี้เราสามารถอัปโหลดทุกสิ่งที่เราบันทึกไว้ใน `output_dir` ได้โดยการเรียกเมธอด `repo.push_to_hub()` ซึ่งจะช่วยให้เราอัปโหลดโมเดลระดับกลางในตอนท้ายของแต่ละยุคได้ + +## ลูปการฝึกอบรม[[ลูปการฝึกอบรม]] + +ตอนนี้เราพร้อมที่จะเขียนลูปการฝึกอบรมฉบับเต็มแล้ว หลังจากกำหนดแถบความคืบหน้าเพื่อติดตามว่าการฝึกดำเนินไปอย่างไร ลูปจะมีสามส่วน: + +- การฝึกในตัวเอง ซึ่งเป็นการวนซ้ำแบบคลาสสิกบน `train_dataloader` คือการส่งต่อผ่านโมเดล จากนั้นย้อนกลับและขั้นตอนการเพิ่มประสิทธิภาพ +- การประเมิน ซึ่งเรารวบรวมค่าทั้งหมดสำหรับ `start_logits` และ `end_logits` ก่อนที่จะแปลงเป็นอาร์เรย์ NumPy เมื่อลูปการประเมินเสร็จสิ้น เราจะเชื่อมต่อผลลัพธ์ทั้งหมดเข้าด้วยกัน โปรดทราบว่าเราจำเป็นต้องตัดทอนเนื่องจาก `Accelerator` อาจเพิ่มตัวอย่างบางส่วนในตอนท้ายเพื่อให้แน่ใจว่าเรามีตัวอย่างจำนวนเท่ากันในแต่ละกระบวนการ +- การบันทึกและการอัปโหลด โดยที่เราจะบันทึกโมเดลและโทเค็นไนเซอร์ก่อน จากนั้นจึงเรียก `repo.push_to_hub()` อย่างที่เราเคยทำมาก่อน เราใช้อาร์กิวเมนต์ `blocking=False` เพื่อบอกให้ไลบรารี 🤗 Hub พุชในกระบวนการอะซิงโครนัส ด้วยวิธีนี้ การฝึกอบรมจะดำเนินต่อไปตามปกติและคำสั่ง (แบบยาว) นี้จะดำเนินการในเบื้องหลัง + +นี่คือโค้ดที่สมบูรณ์สำหรับลูปการฝึกอบรม: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + start_logits = [] + end_logits = [] + accelerator.print("Evaluation!") + for batch in tqdm(eval_dataloader): + with torch.no_grad(): + outputs = model(**batch) + + start_logits.append(accelerator.gather(outputs.start_logits).cpu().numpy()) + end_logits.append(accelerator.gather(outputs.end_logits).cpu().numpy()) + + start_logits = np.concatenate(start_logits) + end_logits = np.concatenate(end_logits) + start_logits = start_logits[: len(validation_dataset)] + end_logits = end_logits[: len(validation_dataset)] + + metrics = compute_metrics( + start_logits, end_logits, validation_dataset, raw_datasets["validation"] + ) + print(f"epoch {epoch}:", metrics) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +ในกรณีที่นี่เป็นครั้งแรกที่คุณเห็นโมเดลที่บันทึกไว้ด้วย 🤗 Accelerate ลองใช้เวลาสักครู่เพื่อตรวจสอบโค้ดสามบรรทัดที่มาพร้อมกับโมเดลนั้น: + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +บรรทัดแรกอธิบายในตัวมันเอง: มันบอกกระบวนการทั้งหมดให้รอจนกว่าทุกคนจะถึงขั้นตอนนั้นก่อนจะดำเนินการต่อ ทั้งนี้เพื่อให้แน่ใจว่าเรามีโมเดลเดียวกันในทุกกระบวนการก่อนที่จะบันทึก จากนั้นเราก็คว้า `unwrapped_model` ซึ่งเป็นโมเดลพื้นฐานที่เรากำหนดไว้ เมธอด `accelerator.prepare()` เปลี่ยนโมเดลให้ทำงานในการฝึกแบบกระจาย ดังนั้นจะไม่มีเมธอด `save_pretrained()` อีกต่อไป เมธอด `accelerator.unwrap_model()` จะยกเลิกขั้นตอนนั้น สุดท้ายนี้ เราเรียก `save_pretrained()` แต่บอกวิธีการนั้นให้ใช้ `accelerator.save()` แทน `torch.save()` + +เมื่อเสร็จแล้ว คุณควรมีโมเดลที่สร้างผลลัพธ์ที่ค่อนข้างคล้ายกับโมเดลที่ได้รับการฝึกกับ `Trainer` คุณสามารถตรวจสอบโมเดลที่เราฝึกได้โดยใช้โค้ดนี้ที่ [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate) และหากคุณต้องการทดสอบการปรับแต่งใดๆ ในลูปการฝึก คุณสามารถนำไปใช้ได้โดยตรงโดยแก้ไขโค้ดที่แสดงด้านบน! + +{/if} + +## การใช้โมเดลที่ปรับแต่งแล้ว[[การใช้โมเดลที่ปรับแต่งแล้ว]] + +เราได้แสดงให้คุณเห็นแล้วว่าคุณสามารถใช้โมเดลที่เราปรับแต่งอย่างละเอียดบน Model Hub ด้วยวิดเจ็ตการอนุมานได้อย่างไร หากต้องการใช้ภายในเครื่องใน `pipeline` คุณเพียงแค่ต้องระบุตัวระบุโมเดล: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +model_checkpoint = "huggingface-course/bert-finetuned-squad" +question_answerer = pipeline("question-answering", model=model_checkpoint) + +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.9979003071784973, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +ยอดเยี่ยม! โมเดลของเราใช้งานได้เช่นเดียวกับโมเดลเริ่มต้นสำหรับไปป์ไลน์นี้ diff --git a/chapters/th/chapter7/8.mdx b/chapters/th/chapter7/8.mdx new file mode 100644 index 000000000..23bca2867 --- /dev/null +++ b/chapters/th/chapter7/8.mdx @@ -0,0 +1,22 @@ +# การเชี่ยวชาญใน NLP[[การเชี่ยวชาญใน-nlp]] + + + +หากคุณมาไกลขนาดนี้ในหลักสูตร ขอแสดงความยินดีด้วย ตอนนี้คุณมีความรู้และเครื่องมือทั้งหมดที่จำเป็นในการจัดการ (เกือบ) งาน NLP ใดๆ ด้วย 🤗 Transformers และระบบนิเวศ Hugging Face! + +เราได้เห็นผู้รวบรวมข้อมูลต่างๆ มากมาย ดังนั้นเราจึงสร้างวิดีโอเล็กๆ นี้ขึ้นมาเพื่อช่วยคุณค้นหาว่าจะใช้อันไหนสำหรับแต่ละงาน: + + + +หลังจากเสร็จสิ้นการทัวร์ชมสายฟ้าแลบผ่านงาน NLP หลักแล้ว คุณควร: + +* รู้ว่าสถาปัตยกรรมใด (encoder, decoder, หรือ encoder-decoder) ที่เหมาะสมที่สุดสำหรับแต่ละงาน +* เข้าใจความแตกต่างระหว่างการฝึกล่วงหน้าและการปรับแต่งโมเดลภาษาอย่างละเอียด +* รู้วิธีฝึกโมเดล Transformer โดยใช้ `Trainer` API และฟีเจอร์การฝึกแบบกระจายของ 🤗 Accelerate หรือ TensorFlow และ Keras ขึ้นอยู่กับเส้นทางที่คุณติดตาม +* ทำความเข้าใจความหมายและข้อจำกัดของหน่วยเมตริก เช่น ROUGE และ BLEU สำหรับงานสร้างข้อความ +* รู้วิธีโต้ตอบกับโมเดลที่ได้รับการปรับแต่งของคุณ ทั้งบน Hub และการใช้ `pipeline` จาก 🤗 Transformers + +แม้จะมีความรู้ทั้งหมดนี้ แต่ก็ถึงเวลาที่คุณจะพบข้อผิดพลาดที่ยากในโค้ดของคุณ หรือมีคำถามเกี่ยวกับวิธีการแก้ไขปัญหา NLP โดยเฉพาะ โชคดีที่ชุมชน Hugging Face พร้อมช่วยเหลือคุณแล้ว! ในบทสุดท้ายของหลักสูตรส่วนนี้ เราจะสำรวจว่าคุณสามารถดีบักโมเดล Transformer ของคุณและขอความช่วยเหลืออย่างมีประสิทธิภาพได้อย่างไร \ No newline at end of file diff --git a/chapters/th/chapter7/9.mdx b/chapters/th/chapter7/9.mdx new file mode 100644 index 000000000..b953739a4 --- /dev/null +++ b/chapters/th/chapter7/9.mdx @@ -0,0 +1,329 @@ + + + + +# คำถามท้ายบท[[คำถามท้ายบท]] + + + +มาทดสอบสิ่งที่คุณเรียนรู้ในบทนี้กัน! + +### 1. งานใดต่อไปนี้สามารถจัดว่าเป็นปัญหาการจำแนกโทเค็นได้? + + + +### 2. ส่วนใดของการประมวลผลล่วงหน้าสำหรับการจัดประเภทโทเค็นที่แตกต่างจากไปป์ไลน์การประมวลผลล่วงหน้าอื่นๆ? + +-100 เพื่อติดป้ายกำกับโทเค็นพิเศษ", + explain: "นั่นไม่ได้เฉพาะเจาะจงกับการจำแนกโทเค็น - เราจะใช้ -100 เป็นป้ายกำกับสำหรับโทเค็นที่เราต้องการเพิกเฉยใน loss" + }, + { + text: "เราจำเป็นต้องตรวจสอบให้แน่ใจว่าได้ตัดหรือแพดฉลากให้มีขนาดเดียวกันกับอินพุต เมื่อใช้การตัดทอน/แพดดิ้ง", + explain: "อย่างแท้จริง! นั่นไม่ใช่ความแตกต่างเพียงอย่างเดียว", + correct: true + } + ]} +/> + +### 3. ปัญหาอะไรเกิดขึ้นเมื่อเราโทเค็นคำในปัญหาการจำแนกโทเค็นและต้องการติดป้ายกำกับโทเค็น? + +-100 เหล่านี้เพื่อที่พวกมันจะถูกละเว้นใน loss" + }, + { + text: "แต่ละคำสามารถสร้างโทเค็นได้หลายรายการ ดังนั้นเราจึงมีโทเค็นมากกว่าที่เรามีป้ายกำกับ", + explain: "นั่นคือปัญหาหลัก และเราจำเป็นต้องปรับป้ายกำกับเดิมให้ตรงกับโทเค็น", + correct: true + }, + { + text: "โทเค็นที่เพิ่มไม่มีป้ายกำกับ ดังนั้นจึงไม่มีปัญหา", + explain: "นั่นไม่ถูกต้อง เราต้องการป้ายกำกับให้มากที่สุดเท่าที่เรามีโทเค็น ไม่เช่นนั้นแบบจำลองของเราจะเกิดข้อผิดพลาด" + } + ]} +/> + +### 4. "การปรับโดเมน (domain adaptation)" หมายถึงอะไร? + + + +### 5. อะไรคือป้ายกำกับ (label) ในปัญหาการสร้างแบบจำลองภาษาที่ปกปิด (masked language modeling)? + + + +### 6. งานใดต่อไปนี้ที่สามารถมองได้ว่าเป็นปัญหาแบบลำดับต่อลำดับ (sequence-to-sequence)? + + + +### 7. วิธีที่เหมาะสมในการประมวลผลข้อมูลล่วงหน้าสำหรับปัญหาตามลำดับ (sequence-to-sequence) คืออะไร? + +inputs=... และ targets=...", + explain: "นี่อาจเป็น API ที่เราเพิ่มในอนาคต แต่ตอนนี้ไม่สามารถทำได้" + }, + { + text: "อินพุตและเป้าหมายทั้งสองจะต้องได้รับการประมวลผลล่วงหน้า โดยเรียกโทเค็นไนเซอร์แยกกันสองครั้ง", + explain: "นั่นเป็นเรื่องจริงแต่ไม่สมบูรณ์ มีสิ่งที่คุณต้องทำเพื่อให้แน่ใจว่า tokenizer ประมวลผลทั้งสองอย่างถูกต้อง" + }, + { + text: "ตามปกติเราเพียงแค่ต้องโทเค็นอินพุต", + explain: "ไม่อยู่ในปัญหาการจำแนกลำดับ เป้าหมายก็คือข้อความที่เราต้องแปลงเป็นตัวเลขด้วย!" + }, + { + text: "อินพุตจะต้องถูกส่งไปยัง tokenizer และเป้าหมายด้วย แต่อยู่ภายใต้ตัวจัดการบริบทพิเศษ", + explain: "ถูกต้อง โทเค็นไนเซอร์จะต้องถูกใส่เข้าสู่โหมดเป้าหมายโดยตัวจัดการบริบทนั้น", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. เหตุใดจึงมีคลาสย่อยเฉพาะของ 'Trainer' สำหรับปัญหาตามลำดับ (sequence-to-sequence)? + +-100", + explain: "นั่นไม่ใช่การสูญเสียที่กำหนดเอง แต่เป็นวิธีการคำนวณการสูญเสีย (loss) เสมอ" + }, + { + text: "เนื่องจากปัญหาแบบลำดับต่อลำดับจำเป็นต้องมีการวนรอบการประเมินพิเศษ", + explain: "ถูกต้อง. การคาดการณ์ของโมเดลแบบเรียงลำดับตามลำดับมักจะทำงานโดยใช้เมธอด generate()", + correct: true + }, + { + text: "เพราะเป้าหมายคือข้อความในโจทย์ปัญหาตามลำดับ", + explain: "Trainer ไม่สนใจเรื่องนี้มากนัก เนื่องจากเคยได้รับการประมวลผลมาก่อนแล้ว" + }, + { + text: "เนื่องจากเราใช้สองแบบจำลองในการแก้ปัญหาตามลำดับ", + explain: "เราใช้โมเดลสองแบบในวิธีหนึ่ง นั่นคือตัวเข้ารหัสและตัวถอดรหัส แต่จะรวมกลุ่มเข้าด้วยกันเป็นโมเดลเดียว" + } + ]} +/> + +{:else} + +### 9. เหตุใดจึงมักไม่จำเป็นต้องระบุการสูญเสีย (loss) เมื่อเรียก `compile()` ในโมเดล Transformer? + + + +{/if} + +### 10. เมื่อใดที่คุณควรฝึกโมเดลใหม่ล่วงหน้า? + + + +### 11. เหตุใดจึงเป็นเรื่องง่ายที่จะฝึกโมเดลภาษาล่วงหน้ากับข้อความจำนวนมาก? + + + +### 12. อะไรคือความท้าทายหลักเมื่อประมวลผลข้อมูลล่วงหน้าสำหรับงานตอบคำถาม (question-answering task)? + + + +### 13. โดยทั่วไปแล้วการประมวลผลภายหลังจะตอบคำถามอย่างไร? + +