Buckets:
| # Tokenizers | |
| {#if fw === 'pt'} | |
| <CourseFloatingBanner chapter={2} | |
| classNames="absolute z-10 right-0 top-0" | |
| notebooks={[ | |
| {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_pt.ipynb"}, | |
| {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_pt.ipynb"}, | |
| ]} /> | |
| {:else} | |
| <CourseFloatingBanner chapter={2} | |
| classNames="absolute z-10 right-0 top-0" | |
| notebooks={[ | |
| {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_tf.ipynb"}, | |
| {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/th/chapter2/section4_tf.ipynb"}, | |
| ]} /> | |
| {/if} | |
| <Youtube id="VFp38yj8h3A"/> | |
| Tokenizers เป็นหนึ่งในส่วนประกอบหลักของ NLP pipeline โดยมีจุดประสงค์เดียวคือ เพื่อแปลงข้อความไปเป็นข้อมูลที่โมเดลสามารถประมวลผลได้ โมเดลสามารถประมวผลได้เพียงแค่ตัวเลขเท่านั้น ดังนั้น tokenizers จึงจำเป็นต้องแปลงข้อความของเราไปเป็นข้อมูลตัวเลข ใน section นี้ เราจะมาลองดูกันว่ามีเกิดอะไรขึ้นบ้างใน tokenization pipeline | |
| ในงาน NLP ข้อมูลโดยทั่วไปแล้วจะเป็นข้อความ นี่เป็นตัวอย่างของข้อความดังกล่าว: | |
| ``` | |
| Jim Henson was a puppeteer | |
| ``` | |
| แต่อย่างไรก็ตาม โมเดลสามารถประมวผลได้เพียงแค่ตัวเลขเท่านั้น ดังนั้นเราจำเป็นต้องหาทางแปลงข้อความดิบไปเป็นตัวเลข นั่นคือสิ่งที่ tokenizer ทำ และก็มีหลายวิธีมากในการทำ เป้าหมายก็คือ หาตัวแทน(representation)ที่มีความหมายที่สุด - หมายความว่า สิ่งที่โมเดลจะเข้าใจได้มากที่สุด - และ ถ้าเป็นไปได้ เป็นตัวแทนที่มีขนาดเล็กที่สุด | |
| ลองมาดูตัวอย่างของ tokenization algorithms และพยายามตอบคำถามบางคำถามที่คุณอาจจะมีเกี่ยวกับ tokenization | |
| ## เน้นที่คำ (Word-based) | |
| <Youtube id="nhJxYji1aho"/> | |
| Tokenizer ประเภทแรกที่เรานึกถึงคือ _word-based_ มันติดตั้งและใช้งานง่ายมากโดยมีกฏเพียงไม่กี่ข้อและมันก็ให้ผลลัพธ์ที่ดีทีเดียว ยกตัวอย่างเช่น ในรูปภาพด้านล่างนี้ เป้าหมายก็คือการแยกข้อความออกเป็นคำๆ และหาตัวแทนที่เป็นตัวเลขของแต่ละคำ: | |
| <div class="flex justify-center"> | |
| <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization.svg" alt="An example of word-based tokenization."/> | |
| <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization-dark.svg" alt="An example of word-based tokenization."/> | |
| </div> | |
| วิธีการในการแยกข้อความนั้นมีหลายวิธีแตกต่างกันไป ยกตัวอย่างเช่น เราสามารถใช้ ช่องว่าง(whitespace) ในการแยกข้อความขอเป็นคำๆ โดยใช้ฟังก์ชัน `split()` ของ Python: | |
| ```py | |
| tokenized_text = "Jim Henson was a puppeteer".split() | |
| print(tokenized_text) | |
| ``` | |
| ```python out | |
| ['Jim', 'Henson', 'was', 'a', 'puppeteer'] | |
| ``` | |
| แล้วก็มี tokenizers สำหรับแยกคำอีกหลายแบบที่มีกฏเพิ่มเติมสำหรับ เครื่องหมายวรรคตอน(punctuation) ถ้าเราใช้ tokenizer ประเภทนี้ เราจะได้กลุ่มของคำศัพท์("vocabularies") ที่ค่อนข้างใหญ่มาก ซึ่งคำศัพท์จะถูกนิยามโดยจำนวน tokens อิสระทั้งหมดที่เรามีในคลังข้อมูล(corpus) ของเรา | |
| แต่ละคำจะได้ ID โดยเริ่มจาก 0 และเพิ่มขึ้นเท่ากับขนาดของคำศัพท์ โดยโมเดลจะใช้ IDs เหล่านี้ในการระบุตัวตนของแต่ละคำ | |
| ถ้าเราต้องการที่จะให้ครอบคลุมคำทั้งหมดในหนึ่งภาษาด้วย word-based tokenizer เราจำเป็นต้องมีตัวระบุตัวตนสำหรับแต่ละคำในภาษาๆนั้นๆ ซึ่งจะทำให้มีการสร้าง tokens จำนวนมหาศาล ยกตัวอย่างเช่น ในภาษาอังกฤษมีคำทั้งหมด 500,000 คำ ดังนั้นการที่จะสร้างความเชื่อมโยงระหว่างแต่ละคำกับ ID เราจำเป็นที่จะต้องจำ ID ทั้งหลายเหล่านั้น นอกจากนี้แล้ว คำอย่างเช่น "dog" จะมีค่าที่ต่างจากคำเช่น "dogs" และโมเดลจะไม่มีทางรู้เลยว่าค่าว่า "dog" และ "dogs" นั้นคล้ายคลึงกัน: มันจะจำแนกสองคำนี้เป็นคำที่ไม่มีความเกี่ยวข้องกัน และคำอื่นๆก็จะเป็นเช่นเดียวกัน เช่นคำว่า "run" และ "running" ซึ่งโมเดลก็จะไม่มองว่าเป็นคำคล้ายๆกัน | |
| สุดท้ายแล้ว เราจำเป็นต้องมี Token เฉพาะสำหรับแทนค่าคำต่างๆที่ไม่อยู่กลุ่มคำศัพท์ของเรา ซึ่งสิ่งนี้เรียกว่า "unknown" token ที่โดยทั่วไปแล้วจะมีค่าเป็น "[UNK]" หรือ "<unk>" และมันจะเป็นสัญญาณที่ไม่ดีเท่าไหร่ถ้าคุณเห็น tokenizer ผลิต tokens เหล่านี้ออกมาเยอะๆ เนื่องจากมันไม่สามารถที่จะหาค่าที่สมเหตุสมผลมาแทนคำๆนั้นได้และคุณก็จะสูญเสียข้อมูลไปตามรายทางเรื่อยๆ เป้าหมายเมื่อเราสร้างกลุ่มคำศัพท์ คือ ทำยังไงก็ได้ให้ Tokenizers สร้าง unknown token ให้น้อยที่สุด | |
| วิธีการหนึ่งที่จะลดจำนวนของ unknown tokens ได้ก็คือ การลงลึกไปอีกหนึ่งขั้น โดยใช้ _character-based_ tokenizer | |
| ## เน้นที่ตัวอักษร(Character-based) | |
| <Youtube id="ssLq_EK2jLE"/> | |
| Character-based tokenizers จะแบ่งข้อความออกเป็นตัวอักษร แทนการแบ่งเป็นคำ โดยมีประโยชน์หลักๆ สอง อย่าง: | |
| - กลุ่มของคำศัพท์จะเล็กกว่ามาก | |
| - มี tokens ที่อยู่นอกกลุ่มคำศัพท์(unknown) น้อยกว่ามาก เนื่องจากคำทุกคำสามารถสร้างได้จากกลุ่มของตัวอักษร | |
| แต่ก็มีคำถามเกี่ยวกับ ช่องว่าง(spaces) และ เครื่องหมายวรรคตอน(punctuation): | |
| <div class="flex justify-center"> | |
| <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization.svg" alt="An example of character-based tokenization."/> | |
| <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization-dark.svg" alt="An example of character-based tokenization."/> | |
| </div> | |
| วิธีการนี้ก็ไม่ใช่วิธีการที่ดีทีสุดเช่นกัน เนื่องจากค่าที่ได้จะขึ้นอยู่กับตัวอักษรแทนที่จะเป็นคำ บางคนอาจจะบอกว่า มันไม่บ่งบอกความหมาย: แต่ละตัวอักษรไม่ได้มีความหมายอะไรมากมายในตัวมันเอง แต่กลับกันคำกลับบ่งบอกความหมายมากกว่า แต่อย่างไรก็ตามมันก็ขึ้นอยู่กับแต่ละภาษา; เช่น แต่ละตัวอักษรในภาษาจีนนั้นมีความหมายมากกว่าตัวอักษรในภาษาละติน | |
| อีกหนึ่งสิ่งที่ควรพิจารณาก็คือเราจะได้ tokens จำนวนมหาศาลที่โมเดลของเราจะต้องประมวลผล: แต่ในทางตรงกันข้าม คำหนึ่งคำจะมีแค่หนึ่ง token ถ้าใช้ word-based tokenizer แต่มันจะกลายเป็น 10 หรือมากกว่านั้นเมื่อเราแปลงเป็นตัวอักษร | |
| เพื่อให้ได้สิ่งที่เป็นประโยชน์ที่สุดจากทั้งสองแบบ เราสามารถสร้างเทคนิคที่สามที่รวมเอาสองวิธีเข้าด้วยกัน: *subword tokenization* | |
| ## คำย่อย(Subword) tokenization | |
| <Youtube id="zHvTiHr506c"/> | |
| อัลกอลิธึม Subword tokenization อยู่บนแนวคิดที่ว่า คำที่ใช้บ่อย ไม่ควรที่จะถูกแบ่งออกเป็นคำย่อยเล็กๆ แต่คำที่ไม่ค่อยได้ใช้ควรที่จะถูกแบ่งออกเป็นคำย่อยๆ ที่มีความหมาย | |
| ยกตัวอย่างเช่น "annoyingly" อาจจะเป็นคำที่ไม่ค่อยถูกใช้บ่อยนัก ดังนั้นมันสามารถที่จะถูกแบ่งย่อยเป็น "annoying" และ "ly" สองคำนี้มีความเป็นไปได้ที่เจอได้บ่อยเมื่อมันเป็นคำย่อยเดี่ยวๆ ในขณะเดียวกัน ความหมายของ "annoyingly" ก็ยังคงอยู่ โดยเป็นความหมายร่วมของ "annoying" และ "ly" | |
| ตัวอย่างนี้แสดงวิธีการที่อัลกอลิธึม subword tokenization ทำการแปลงประโยค "Let's do tokenization!" ว่าทำอย่างไร: | |
| <div class="flex justify-center"> | |
| <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword.svg" alt="A subword tokenization algorithm."/> | |
| <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword-dark.svg" alt="A subword tokenization algorithm."/> | |
| </div> | |
| ท้ายที่สุดแล้ว คำย่อยเหล่านี้จะให้ความหมายเชิงความหมาย(semantic meaning) มากกว่า, ในตัวอย่างข้างต้น "tokenization" ถูกแบ่งออกเป็น "token" และ "ization", เป็น สอง tokens ที่มีความหมายเชิงความหมายพร้อมทั้งทำให้ประหยัดเนื้อที่ (มีแค่ 2 tokens เท่านั้นที่จะใช้แทนค่าคำยาวๆ) นี่จะทำให้เราสามารถครอบคลุมคำต่างๆได้ด้วยกลุ่มของคำศัพท์เล็กๆเท่านั้น และแทบไม่มี unknown tokens | |
| วิธีการนี้เป็นประโยชน์อย่างยิ่งในภาษารูปคำติดต่อ(agglutinative languages) อย่างเช่น ภาษาตุรกี(Turkish) ที่คุณสามารถสร้างคำที่ยาวและซับซ้อนได้ด้วยการต่อคำย่อยๆหลายคำๆเข้าด้วยกัน | |
| ### และอื่นๆ ! | |
| ไม่น่าแปลกใจเลยที่จะมีอีกหลากหลายเทคนิค ถ้าจะเอ่ยบางชื่อ: | |
| - Byte-level BPE, เหมือนที่ใช้ใน GPT-2 | |
| - WordPiece, เหมือนที่ใช้ใน BERT | |
| - SentencePiece or Unigram, เหมือนที่ใช้ในหลายๆโมเดลที่เป็นโมเดลสำหรับหลายๆ ภาษา(multilingual models) | |
| ถึงตรงนี้คุณน่าจะมีความรู้เพียงพอว่า tokenizers ทำงานอย่างไร เพื่อเริ่มใช้งาน API | |
| ## การโหลดและการบันทึก | |
| การโหลดและการบันทึก tokenizers นั้นง่ายพอๆกับการโหลดและการบันทึกโมเดล โดยทั่วไปแล้วมันจะใช้สองวิธี: `from_pretrained()` และ `save_pretrained()` สองวิธีการนี้จะโหลด หรือ บันทึกอัลกอลิธึมที่ใช้โดย tokenizer (คล้ายๆกับ *สถาปัตยกรรม* ของโมเดล) และ กลุ่มคำศัพท์ของมัน (คล้ายๆ กับ *weights* ของโมเดล) | |
| โหลด BERT tokenizer ที่ผ่านการเทรนมาแล้วด้วย checkpoint เดียวกันกับ BERT สามารถทำได้เหมือนกับการโหลดโมเดล ยกเว้น เราใช้คลาส `BertTokenizer`: | |
| ```py | |
| from transformers import BertTokenizer | |
| tokenizer = BertTokenizer.from_pretrained("bert-base-cased") | |
| ``` | |
| {#if fw === 'pt'} | |
| เหมือนกับ `AutoModel, `AutoTokenizer` จะทำการดึงเอาคลาส tokenizer ที่เหมาะสมที่อยู่ใน library โดยอ้างอิงกับชื่อของ checkpoint และสามารถใช้กับ checkpoint ใดก็ได้: | |
| {:else} | |
| เหมือนกับ `TFAutoModel, `AutoTokenizer` จะทำการดึงเอาคลาส tokenizer ที่เหมาะสมที่อยู่ใน library โดยอ้างอิงกับชื่อของ checkpoint และสามารถใช้กับ checkpoint ใดก็ได้: | |
| {/if} | |
| ```py | |
| from transformers import AutoTokenizer | |
| tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") | |
| ``` | |
| ตอนนี้เราสามารถใช้ tokenizer เหมือนที่แสดงใน section ที่แล้ว: | |
| ```python | |
| tokenizer("Using a Transformer network is simple") | |
| ``` | |
| ```python out | |
| {'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], | |
| 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], | |
| 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} | |
| ``` | |
| การบันทึก tokenizer ก็เหมือนกับการบันทึกโมเดล: | |
| ```py | |
| tokenizer.save_pretrained("directory_on_my_computer") | |
| ``` | |
| เราจะพูดเพิ่มเติมเกี่ยวกับ `token_type_ids` ใน [Chapter 3](/course/chapter3), และจะอธิบายเกี่ยวกับ `attention_mask` ในภายหลัง ในขั้นแรกนี้เรามาดูกันว่า `input_ids` นั้นถูกสร้างขึ้นมาอย่างไร ซึ่งการที่จะทำเช่นนี้ เราจำเป็นที่จะต้องดูวิธีระหว่างกลางของ tokenizer | |
| ## การเข้ารหัส(Encoding) | |
| <Youtube id="Yffk5aydLzg"/> | |
| การแปลงข้อความไปเป็นตัวเลขนั้นเรียกอีกอย่างว่า _encoding_ การเข้ารหัส(Encoding) นั้นสามารถทำได้ด้วยกระบวนการที่ประกอบด้วย 2 ขั้นตอน: tokenization และตามด้วยการแปลงไปเป็น input IDs | |
| อย่างที่เราเห็นมาก่อนหน้านี้ ขั้นตอนแรกก็คือการแบ่งข้อความออกเป็นคำ (หรือส่วนของคำ, เครื่องหมายวรรคตอน เป็นต้น) เหล่านี้ปกติจะถูกเรียกว่า *tokens* มีหลายกฏเกณฑ์ที่ใช้ในการควบคุมกระบวนนี้ ซึ่งนั้นก็เป็นเหตุผลว่าทำไมเราถึงจำเป็นต้องสร้าง tokenizer โดยใช้ชื่อของโมเดล ก็เพื่อให้มั่นใจว่าเราใช้กฏเกณฑ์เดียวกันกับที่เราใช้ตอนที่โมเดลนั้นผ่านการเทรนมาก่อนหน้านี้ | |
| ขั้นตอนที่สองก็คือการแปลง tokens เหล่านั้นไปเป็นตัวเลข ซึ่งเราจึงจะสามารถสร้าง tensor จากมันได้และใส่เข้าไปยังโมเดลได้ ในการทำเช่นนี้ tokenizer จะมี *vocabulary* ซึ่งเป็นส่วนที่เราดาวน์โหลดมาแล้วตอนที่เราสร้าง tokenizer ด้วยวิธีการ `from_pretrained()` เช่นเดียวกัน เราจำเป็นต้องใช้คำศัพท์เหมือนกับที่ใช้ตอนโมเดลนั้นเทรนมา | |
| เพื่อให้เข้าใจสองขั้นตอนนี้มากยิ่งขึ้น เราจะมาดูแต่ละขั้นตอนแยกกัน เราจะใช้วิธีการบางวิธีที่ทำบางส่วนของ tokenization pipeline แยกกันเพื่อที่จะแสดงให้คุณดูว่าผลลัพธ์ระหว่างกลาง(intermediate) ของทั้งสองขั้นตอนนั้นเป็นอย่างไร แต่ในทางปฏิบัติ คุณควรจะประมวลผลข้อมูลอินพุตของคุณโดยเรียกใช้ tokenizer ตรงๆ(เหมือนที่แสดงใน section 2) | |
| ### Tokenization | |
| กระบวนการ tokenization นั้นทำได้โดยใช้ `tokenize()` ของ tokenizer: | |
| ```py | |
| from transformers import AutoTokenizer | |
| tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") | |
| sequence = "Using a Transformer network is simple" | |
| tokens = tokenizer.tokenize(sequence) | |
| print(tokens) | |
| ``` | |
| ผลลัพธ์ของวิธีนี้ คือ ลิสท์ของกลุ่มของตัวอักษร(strings) หรือ tokens: | |
| ```python out | |
| ['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] | |
| ``` | |
| tokenizer นี้คือ subword tokenizer: มันจะแบ่งคำไปเรื่อยๆจนกว่าจะได้ tokens ที่สามารถแทนค่าได้ด้วยคำศัพท์(vocabulary) ของมันเอง ซึ่งนั้นก็เหมือนกับกรณีที่คำว่า `transformer`ถูกแบ่งออกเป็น 2 tokens: `transform` และ `##er` | |
| ### จาก tokens ไปเป็น input IDs | |
| การแปลงไปเป็น input IDs นั้นจะถูกจัดการโดย `convert_tokens_to_ids()` ที่เป็นเมธอดของ tokenizer: | |
| ```py | |
| ids = tokenizer.convert_tokens_to_ids(tokens) | |
| print(ids) | |
| ``` | |
| ```python out | |
| [7993, 170, 11303, 1200, 2443, 1110, 3014] | |
| ``` | |
| ผลลัพธ์เหล่านี้ เมื่อทำการแปลงไปเป็น tensor ที่เหมาะสมของ framework นั้นๆ แล้ว มันสามารถถูกนำไปใช้เป็นอินพุตของโมเดลเหมือนที่เราเห็นก่อนหน้าในนี้ในบทนี้ | |
| > [!TIP] | |
| > ✏️ **ลองดูสิ!** ทำซ้ำสองขั้นตอนสุดท้าย(tokenization และแปลงไปเป็น input IDs) กับข้อความที่เราใช้เป็นอินพุตใน section 2 ("I've been waiting for a HuggingFace course my whole life." และ "I hate this so much!") และลองดูว่าคุณได้ input IDs เดียวกันกับที่เราได้ก่อนหน้านี้ไหม! | |
| ## การถอดรหัส(Decoding) | |
| *การถอดรหัส(Decoding)* ก็จะเป็นกระบวนการในทางตรงกันข้าม จากดัชนีคำศัพท์(vocabulary indices) เราต้องการที่จะได้กลุ่มของตัวอักษร(string) ซึ่งสามารถทำได้ด้วยวิธี `decode()` ดังนี้: | |
| ```py | |
| decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) | |
| print(decoded_string) | |
| ``` | |
| ```python out | |
| 'Using a Transformer network is simple' | |
| ``` | |
| วิธี `decode` ไม่ได้ทำแค่การแปลงดัชนี(indices) ไปเป็น token เท่านั้น แต่ยังทำการรวม tokens ต่างๆที่เป็นส่วนหนึ่งของคำเดียวกันเพื่อสร้างประโยคที่สามารถอ่านได้ กระบวนการเช่นนี้จะเป็นประโยชน์อย่างมากเมื่อเราใช้โมเดลสำหรับทำนายข้อความใหม่(ไม่ว่าจะเป็นข้อความที่สร้างจาก prompt หรือปัญหาประเภท sequence-to-sequence เช่น การแปล(translation) หรือ การสรุปใจความสำคัญ(summarization)) | |
| ถึงตรงนี้คุณน่าจะเข้าใจกระบวนการเล็กๆ น้อยๆ ต่างๆ ที่ tokenizer สามารถทำได้: tokenization, การแปลงไปเป็น IDs, และการแปลง IDs กลับมาเป็นคำ แต่อย่างไรก็ตาม เราก็แค่เพิ่งจะขุดแค่ปลายของภูเขาน้ำแข็ง ใน section ต่อไป เราจะใช้วิธีการของเราไปจนถึงลิมิตของมันและดูว่าเราจะแก้ปัญหาอย่างไร | |
| <EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/th/chapter2/4.mdx" /> |
Xet Storage Details
- Size:
- 27.2 kB
- Xet hash:
- 32187afa8fc2dbe7a26d5618da65bc877aad5c142dad694e5699f58be4b405a0
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.