Transformer
update งาน NLP กันหน่อย :D
ในบรรดา paper ด้าน NLP ทั้งหลาย ช่วงนี้คงหนีคำว่า “Transformer” ไม่พ้น
จริง ๆ ยังมีคำอื่นอีก อย่าง BERT, GPT-3 แต่เหมือน Transformer จะเป็นพื้นฐานของ model อื่น ๆ เลยต้องอ่านก่อน
paper หลักคือ “Attention is all you need” โดย Vaswani et al. ปี 2017
ปรากฏว่าอ่านไปก็บ่นไปว่าเขียนไรไม่รู้เรื่องเลย ถ้าผมเป็น reviewer คงจะไล่ไปเขียนมาใหม่ 555
ตอนนี้อ่านข้าม ๆ บางส่วนก็มานั่งคิดเองต่อ ได้ข้อสรุปส่วนตัวบางอย่างเลยมาจดไว้ก่อนดีกว่า เดี๋ยวว่างๆ ค่อยไปอ่านต่อให้จบ
Sentence-Encoder และ Word-by-word Decoder
นี่คือข้อสังเกตอย่างแรกที่ทำให้ผมเข้าใจอะไร ๆ มากขึ้น นั่นคือ Encoder นั้น process ทั้งประโยค input ในขณะที่ Decoder สร้างประโยค output ทีละคำ
อันนี้ผมเองว่าเป็น key ที่สำคัญมาก
งาน sequence-to-sequence ที่เคยอ่าน น่าจะปี 2015 เขาใช้พวก RNN อย่าง LSTM เป็นหลัก
idea คือให้ Encoder LSTM process ประโยค input ทีละคำให้หมด แล้วส่ง hidden state ไปเป็น state ตั้งต้นของ Decoder LSTM เพื่อสร้างประโยค output ทีละคำเช่นกัน
idea นี้ฟังดูดี เพราะเอา hidden state มาใช้ encode ความหมาย (semantic) แทนที่จะต้องมาสร้าง parse tree ก่อน
ปัญหาที่เกิดขึ้นคือการ train RNN เหล่านี้ยังต้องใช้ backprop เหมือน RNN ปกติ ซึ่งสำหรับ RNN เราต้อง “คลี่ (unfold)” การทำงานของโหนดต่าง ๆ ออกมาตามเวลาก่อน แล้วจึงค่อยเปรียบเทียบประโยค output กับ target เพื่อคำนวณ gradient และส่งย้อนกลับจนถึงประโยค input
จำนวนคำในประโยค input และ output นั้นมีผลทำให้ค่า gradient ที่ส่งย้อนกลับไปนั้นมี amplitude ลดลงจนหายไปได้
จริงอยู่ว่าโครงสร้างอย่าง LSTM หรือ GRU มีการคำนวณที่ “คล้ายกับ” skip connection ใน ResNet ซึ่งในทางทฤษฎีควรจะช่วยลดปัญหา gradient vanishing ลงได้ แต่ในทางปฏิบัติ เห็นคนเล่าว่ามันยังช่วยไม่พอ คือถ้าประโยคยาว >30 คำ gradient ก็หายแล้ว
Gradient หายแล้วไง?
ก่อนจะไปดู Transformer เราควรลองคิดเรื่องนี้ก่อน
สำหรับ NN เราสามารถมอง gradient ว่าเป็นตัวขนข้อมูล หรือบรรทุกข้อมูล
ถ้าเราส่ง gradient จากโหนดหนึ่งไปอีกโหนดได้ก็แปลว่าเราสามารถส่งข้อมูลระหว่างทั้งสองโหนดได้
สำหรับ Encoder-Decoder RNN สมมติว่าเรา Decode คำที่ 10 ในประโยค output แล้วเราไม่สามารถส่ง gradient กลับไปคำที่ 1 ของประโยค input ได้ นั่นแปลว่า Encoder-Decoder จะไม่รับรู้ถึงความสัมพันธ์ระหว่างคำทั้งสองนี้
นั่นคือสิ่งที่ในบทความต่าง ๆ พูดถึง long-term dependency นั่นเอง
Transformer เปลี่ยนวิธีขนข้อมูล
ข้อสรุปของผมคือ Transformer ไม่ได้แก้ปัญหา gradient vanishing แต่มันเปลี่ยนวิธีขนข้อมูล
คือแทนที่จะเอาผลจาก Encoder มาเป็น state ตั้งต้นในการ decode
มันเอาผลจาก Encoder ไปให้ Decoder ดูตอนที่จะสร้างคำแต่ละคำเลย
ดังนั้นมันสามารถ link ความสัมพันธ์ระหว่างคำในประโยค output กับคำใด ๆ ใน input ได้เสมอ ไม่ว่าจะเป็นระหว่าง output คำที่ล้านกับ input คำที่ 1 ก็ตาม
ดังนั้นถึง gradient จะ vanish ไปก็ไม่เป็นไร ข้อมูลจาก input ที่ Decoder ควรจะรู้นั้นสามารถเข้าไปดูได้ตลอดเวลา
ตัวอย่างการทำงานของ Transformer ที่ผมเข้าใจ (ยังไม่ได้ดูเรื่อง position encoding) น่าจะประมาณนี้
Encoder(“This car is red”) + “” → Decoder → “รถ” → “รถ”
Encoder(“This car is red”) + “รถ”→ Decoder → “คันนี้”→ “รถคันนี้”
Encoder(“This car is red”) + “รถคันนี้”→ Decoder → “สีแดง”→ “รถคันนี้สีแดง”
Query-Key-Value
อันนี้เป็นอีก concept นึงที่อ่านแล้วงง ๆ เอาเท่าที่เข้าใจละกัน
block ที่ผมสนใจเป็นพิเศษคือ block ที่ต่อ key-value จาก Encoder และ query จาก decoder
สมมติว่าเราเก็บคำศัพท์ลง dictionary เช่น “หมา” เป็นคำที่ 1, “แมว” เป็นคำที่ 2 ถ้าเราทำ one-hot encoding ปกติสำหรับทั้ง 2 คำ ก็จะได้
key : value
[1,0] : “หมา”
[0,1] : “แมว”
สมมติว่า query คือ [0,1]
Transformer ใช้วิธีเอา query มา dot กับ key ทั้งสอง แล้วไปผ่าน softmax ได้เป็น weight ของทั้ง 2 คำ
ในกรณีของเรา weight ที่ได้คือ 0 และ 1 ตามลำดับ
ซึ่งทำให้กรณีนี้คำที่ส่งออกจากการ match query คือ “แมว”
ในการใช้งานจริง key และ query ของเราไม่ใช่ one-hot encoding vector แต่เป็น dense vector จะมองว่าเป็นแบบ word2vec ก็คงได้
นั่นคือ key และ query นั้น encode “ความหมาย” ของแต่ละคำไว้
ถ้างั้น value ควรจะเป็นอะไร?
ถ้าเรามาดูส่วน attention ใน Transformer-Decoder ในวงกลมข้างบน หากเราตัดชั้น feed-forward และ linear ทั้งหลายออก และ ignore skip ไปก่อน ค่าที่ส่งออกก็คือค่า value ที่ได้จากการ weight sum ของ softmax-dot product นั่นเอง
ดังนั้น หากจะตีความว่า value นั้น encode “รูปเขียน” ของคำ ก็คงพอได้มั๊ง
ที่น่าสนใจคือ สมมติว่าเราทำงานแปลภาษา อังกฤษ-ไทย
key ที่มาจาก Encoder คือ “ความหมายภาษาอังกฤษ” จากประโยค input
ส่วน query ที่มาจาก Decoder คือ “ความหมายภาษาไทย” ในประโยค output
แต่ key กับ query นี้ต้องอยู่ใน space เดียวกัน เพื่อที่เราจะสามารถเทียบความคล้ายกันได้
space นี้คงเหมือน “ภาษากลาง” ที่นักภาษาศาสตร์อยากได้กันในอดีต (ไม่รู้ตอนนี้เขายังหากันอยู่มั๊ย)
สิ่งที่ Decoder ทำคือสร้าง “ความหมายภาษาไทย” ของคำปัจจุบัน จากคำต่าง ๆ ในประโยคที่มันสร้างก่อนนี้
แล้วเอา “ความหมายภาษาไทย” นี้ไป query เทียบกับ “ความหมายภาษาอังกฤษ” ที่มาจาก Encoder เพื่อเลือก “รูปเขียนภาษาไทย” ที่จะส่งไปผ่าน layer อื่นอีก เพื่อเปลี่ยนเป็นคำศัพท์จริง
ถ้าที่เข้าใจนี่ถูก Encoder เป็นตัวหลักที่ต้องเข้าใจความสัมพันธ์ระหว่างภาษาอังกฤษและไทยจริง ๆ เพราะมันต้องสกัดคู่ (“ความหมายภาษาอังกฤษ”,“รูปเขียนภาษาไทย”) จากประโยค input ที่เป็น “รูปเขียนภาษาอังกฤษ”
ส่วน Decoder นี่คล้าย ๆ language model แบบ classic เลย คือเน้นที่การสร้าง “รูปเขียน” คำใหม่จากประโยคที่สร้างมาแล้ว ที่จะต่างจาก LM เดิมคือมันทำการสร้าง “ความหมาย” ของคำถัดไปก่อน แล้วจึงแปลงเป็น “รูปเขียน”
ที่ไม่ค่อยเก็ตคือ พอเอา query-key-value มาใส่ใน block เดียวกัน อย่างใน Encoder มันจะแปลความหมายว่ายังไงน้อ
BERT
BERT เท่าที่เห็นคือใช้ Transformer-Encoder เป็นหลัก และ train ด้วยการ mask บางคำทิ้ง แล้วให้ NN ทายคำเหล่านั้นออกมา เห็นว่าใช้ได้ดี และใช้หลายภาษาได้ด้วย สามารถทำ multilingual BERT หรือ mBERT ได้ด้วย
ถ้าตามที่เข้าใจในย่อหน้าก่อนนี้ถูก และถ้าความสามารถของ Transformer-Encoder ในการสกัดคู่ (ความหมาย, รูปเขียน) นั้นขึ้นกับโครงสร้าง ไม่ใช่กระบวนการสอนของ Transformer ตัว BERT เองก็น่ามีปัญหาถ้า train กับคู่ภาษา แล้ว transfer ไปภาษาอื่น เพราะมันคงเรียนพวกรูปเขียนไปด้วย
(ต่างจากการเอาภาษาทั้งหมดมารวมกันแล้ว train ใน mBERT นะ)
เท่าที่ลอง google ดูก็เจอ paper ที่ทดลองกับหลายภาษาเจอผลประมาณนี้ด้วยแฮะ ข้อสรุปในนั้นคือถ้าจะทำ transfer learning กับ BERT ควรทำกับภาษาที่คล้าย ๆ กัน
เหมือนจะมีคนสงสัยเรื่องรูปเขียนเหมือนกับผมอยู่อีกนะ ใน paper “How multilingual is Multilingual BERT?” ก็มีคนสงสัยว่าทำไม mBERT ถึงทำ multilingual ได้ทั้ง ๆ ที่บางภาษามีรูปเขียนที่ต่างกันมาก ข้อสรุปของเขาคือการที่ในภาษาต่าง ๆ มีรูปเขียนที่ซ้ำกันอย่างตัวเลข หรือ URL นั้น เป็น key หลัก
ใน paper ของ Wu et al. ข้างบนเรียกรูปเขียนซ้ำกันนี้ว่า “anchor”
Pires et al. คาดว่า anchor นี้เองที่บังคับให้ความหมายในภาษาต่าง ๆ นั้นถูกแปลงมาอยู่ใกล้ ๆ กัน ซึ่งมันก็จะดึงการแปลงความหมายของคำอื่น ๆ ในภาษาต่าง ๆ มาด้วยนั่นเอง
อันนี้ทีม Wu et al. อ้างว่าเราสามารถ transfer ข้ามคู่ภาษาโดยไม่ต้องใช้ anchor ก็ได้นะโดยการ share parameter เช่น weight บน layer ล่าง ๆ ของ Transformer-Encoder ข้ามภาษากัน
ตัวผมเองเดาว่าเราน่าจะสามารถใช้คู่คำศัพท์ใน dictionary ทั่วไปเป็น anchor ที่ช่วยในการ transfer ข้ามภาษาได้ ยังไม่ค่อยเก็ตว่าทำไมไม่ทำกัน หรือถ้าค้นดี ๆ อาจจะมีก็ได้ หรือจะมีใครลองทำก็ดีนะครับ ได้ผลไงบอกด้วย
วันนี้พอแค่นี้ก่อน ไว้มาอ่าน NLP ต่อวันหลัง ท่าจะยังเหลือที่ต้อง update อีกเยอะ >.<