You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
30 KiB
256 lines
30 KiB
<!--
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
{
|
|
"original_hash": "911efd5e595089000cb3c16fce1beab8",
|
|
"translation_date": "2025-09-05T22:02:45+00:00",
|
|
"source_file": "8-Reinforcement/1-QLearning/README.md",
|
|
"language_code": "th"
|
|
}
|
|
-->
|
|
# การแนะนำเกี่ยวกับ Reinforcement Learning และ Q-Learning
|
|
|
|

|
|
> Sketchnote โดย [Tomomi Imura](https://www.twitter.com/girlie_mac)
|
|
|
|
Reinforcement learning เกี่ยวข้องกับแนวคิดสำคัญสามประการ: ตัวแทน (agent), สถานะ (state) และชุดของการกระทำ (action) ต่อสถานะหนึ่งๆ โดยการดำเนินการกระทำในสถานะที่กำหนด ตัวแทนจะได้รับรางวัล ลองจินตนาการถึงเกมคอมพิวเตอร์ Super Mario คุณคือ Mario คุณอยู่ในด่านเกม ยืนอยู่ข้างหน้าผา ด้านบนคุณมีเหรียญ คุณในฐานะ Mario อยู่ในด่านเกม ณ ตำแหน่งที่เฉพาะเจาะจง ... นั่นคือสถานะของคุณ การก้าวไปทางขวาหนึ่งก้าว (การกระทำ) จะทำให้คุณตกหน้าผา และนั่นจะทำให้คุณได้คะแนนต่ำ อย่างไรก็ตาม การกดปุ่มกระโดดจะทำให้คุณได้คะแนนและยังมีชีวิตอยู่ นั่นคือผลลัพธ์ที่ดีและควรให้คะแนนเชิงบวกแก่คุณ
|
|
|
|
โดยการใช้ reinforcement learning และตัวจำลอง (simulator) เช่นเกม คุณสามารถเรียนรู้วิธีการเล่นเกมเพื่อเพิ่มรางวัลให้ได้มากที่สุด ซึ่งก็คือการมีชีวิตรอดและทำคะแนนให้ได้มากที่สุด
|
|
|
|
[](https://www.youtube.com/watch?v=lDq_en8RNOo)
|
|
|
|
> 🎥 คลิกที่ภาพด้านบนเพื่อฟัง Dmitry อธิบายเกี่ยวกับ Reinforcement Learning
|
|
|
|
## [แบบทดสอบก่อนเรียน](https://ff-quizzes.netlify.app/en/ml/)
|
|
|
|
## ความต้องการเบื้องต้นและการตั้งค่า
|
|
|
|
ในบทเรียนนี้ เราจะทดลองกับโค้ดใน Python คุณควรสามารถรันโค้ดใน Jupyter Notebook จากบทเรียนนี้ได้ ไม่ว่าจะบนคอมพิวเตอร์ของคุณหรือในระบบคลาวด์
|
|
|
|
คุณสามารถเปิด [สมุดบันทึกบทเรียน](https://github.com/microsoft/ML-For-Beginners/blob/main/8-Reinforcement/1-QLearning/notebook.ipynb) และทำตามบทเรียนนี้เพื่อสร้างโปรเจกต์
|
|
|
|
> **หมายเหตุ:** หากคุณเปิดโค้ดนี้จากระบบคลาวด์ คุณจะต้องดึงไฟล์ [`rlboard.py`](https://github.com/microsoft/ML-For-Beginners/blob/main/8-Reinforcement/1-QLearning/rlboard.py) ซึ่งใช้ในโค้ดของสมุดบันทึกนี้ด้วย ให้วางไฟล์นี้ในไดเรกทอรีเดียวกับสมุดบันทึก
|
|
|
|
## บทนำ
|
|
|
|
ในบทเรียนนี้ เราจะสำรวจโลกของ **[Peter and the Wolf](https://en.wikipedia.org/wiki/Peter_and_the_Wolf)** ซึ่งได้รับแรงบันดาลใจจากนิทานดนตรีโดยนักประพันธ์ชาวรัสเซีย [Sergei Prokofiev](https://en.wikipedia.org/wiki/Sergei_Prokofiev) เราจะใช้ **Reinforcement Learning** เพื่อให้ Peter สำรวจสภาพแวดล้อม เก็บแอปเปิ้ลอร่อยๆ และหลีกเลี่ยงการพบกับหมาป่า
|
|
|
|
**Reinforcement Learning** (RL) เป็นเทคนิคการเรียนรู้ที่ช่วยให้เราสามารถเรียนรู้พฤติกรรมที่เหมาะสมที่สุดของ **agent** ใน **environment** โดยการทดลองหลายๆ ครั้ง ตัวแทนในสภาพแวดล้อมนี้ควรมี **เป้าหมาย** ซึ่งกำหนดโดย **reward function**
|
|
|
|
## สภาพแวดล้อม
|
|
|
|
เพื่อความง่าย ลองพิจารณาโลกของ Peter เป็นกระดานสี่เหลี่ยมขนาด `width` x `height` ดังนี้:
|
|
|
|

|
|
|
|
แต่ละช่องในกระดานนี้สามารถเป็นได้:
|
|
|
|
* **พื้นดิน** ซึ่ง Peter และสิ่งมีชีวิตอื่นๆ สามารถเดินได้
|
|
* **น้ำ** ซึ่งแน่นอนว่าไม่สามารถเดินได้
|
|
* **ต้นไม้** หรือ **หญ้า** ซึ่งเป็นที่ที่คุณสามารถพักผ่อนได้
|
|
* **แอปเปิ้ล** ซึ่งเป็นสิ่งที่ Peter ยินดีที่จะหาเพื่อเลี้ยงตัวเอง
|
|
* **หมาป่า** ซึ่งเป็นอันตรายและควรหลีกเลี่ยง
|
|
|
|
มีโมดูล Python แยกต่างหาก [`rlboard.py`](https://github.com/microsoft/ML-For-Beginners/blob/main/8-Reinforcement/1-QLearning/rlboard.py) ซึ่งมีโค้ดสำหรับทำงานกับสภาพแวดล้อมนี้ เนื่องจากโค้ดนี้ไม่สำคัญต่อการทำความเข้าใจแนวคิดของเรา เราจะนำเข้าโมดูลและใช้มันเพื่อสร้างกระดานตัวอย่าง (code block 1):
|
|
|
|
```python
|
|
from rlboard import *
|
|
|
|
width, height = 8,8
|
|
m = Board(width,height)
|
|
m.randomize(seed=13)
|
|
m.plot()
|
|
```
|
|
|
|
โค้ดนี้ควรพิมพ์ภาพของสภาพแวดล้อมที่คล้ายกับภาพด้านบน
|
|
|
|
## การกระทำและนโยบาย
|
|
|
|
ในตัวอย่างของเรา เป้าหมายของ Peter คือการหาแอปเปิ้ล ในขณะที่หลีกเลี่ยงหมาป่าและสิ่งกีดขวางอื่นๆ เพื่อทำเช่นนี้ เขาสามารถเดินไปรอบๆ ได้จนกว่าจะพบแอปเปิ้ล
|
|
|
|
ดังนั้น ในตำแหน่งใดๆ เขาสามารถเลือกหนึ่งในสี่การกระทำต่อไปนี้: ขึ้น, ลง, ซ้าย และขวา
|
|
|
|
เราจะกำหนดการกระทำเหล่านี้เป็นพจนานุกรม และจับคู่กับคู่ของการเปลี่ยนแปลงพิกัดที่สอดคล้องกัน ตัวอย่างเช่น การเคลื่อนไปทางขวา (`R`) จะสอดคล้องกับคู่ `(1,0)` (code block 2):
|
|
|
|
```python
|
|
actions = { "U" : (0,-1), "D" : (0,1), "L" : (-1,0), "R" : (1,0) }
|
|
action_idx = { a : i for i,a in enumerate(actions.keys()) }
|
|
```
|
|
|
|
สรุปแล้ว กลยุทธ์และเป้าหมายของสถานการณ์นี้คือ:
|
|
|
|
- **กลยุทธ์** ของตัวแทนของเรา (Peter) ถูกกำหนดโดยสิ่งที่เรียกว่า **policy** นโยบายคือฟังก์ชันที่ส่งคืนการกระทำในสถานะที่กำหนด ในกรณีของเรา สถานะของปัญหาถูกแสดงโดยกระดาน รวมถึงตำแหน่งปัจจุบันของผู้เล่น
|
|
|
|
- **เป้าหมาย** ของ reinforcement learning คือการเรียนรู้นโยบายที่ดีที่จะช่วยให้เราสามารถแก้ปัญหาได้อย่างมีประสิทธิภาพ อย่างไรก็ตาม ในฐานะพื้นฐาน ลองพิจารณานโยบายที่ง่ายที่สุดที่เรียกว่า **random walk**
|
|
|
|
## Random walk
|
|
|
|
ลองแก้ปัญหาของเราด้วยการใช้กลยุทธ์ random walk ก่อน ด้วย random walk เราจะสุ่มเลือกการกระทำถัดไปจากการกระทำที่อนุญาต จนกว่าจะถึงแอปเปิ้ล (code block 3)
|
|
|
|
1. ใช้ random walk ด้วยโค้ดด้านล่าง:
|
|
|
|
```python
|
|
def random_policy(m):
|
|
return random.choice(list(actions))
|
|
|
|
def walk(m,policy,start_position=None):
|
|
n = 0 # number of steps
|
|
# set initial position
|
|
if start_position:
|
|
m.human = start_position
|
|
else:
|
|
m.random_start()
|
|
while True:
|
|
if m.at() == Board.Cell.apple:
|
|
return n # success!
|
|
if m.at() in [Board.Cell.wolf, Board.Cell.water]:
|
|
return -1 # eaten by wolf or drowned
|
|
while True:
|
|
a = actions[policy(m)]
|
|
new_pos = m.move_pos(m.human,a)
|
|
if m.is_valid(new_pos) and m.at(new_pos)!=Board.Cell.water:
|
|
m.move(a) # do the actual move
|
|
break
|
|
n+=1
|
|
|
|
walk(m,random_policy)
|
|
```
|
|
|
|
การเรียก `walk` ควรส่งคืนความยาวของเส้นทางที่สอดคล้องกัน ซึ่งอาจแตกต่างกันไปในแต่ละรอบ
|
|
|
|
1. รันการทดลองเดินหลายครั้ง (เช่น 100 ครั้ง) และพิมพ์สถิติที่ได้ (code block 4):
|
|
|
|
```python
|
|
def print_statistics(policy):
|
|
s,w,n = 0,0,0
|
|
for _ in range(100):
|
|
z = walk(m,policy)
|
|
if z<0:
|
|
w+=1
|
|
else:
|
|
s += z
|
|
n += 1
|
|
print(f"Average path length = {s/n}, eaten by wolf: {w} times")
|
|
|
|
print_statistics(random_policy)
|
|
```
|
|
|
|
สังเกตว่าความยาวเฉลี่ยของเส้นทางอยู่ที่ประมาณ 30-40 ก้าว ซึ่งค่อนข้างมาก เมื่อพิจารณาจากระยะทางเฉลี่ยไปยังแอปเปิ้ลที่ใกล้ที่สุดซึ่งอยู่ที่ประมาณ 5-6 ก้าว
|
|
|
|
คุณยังสามารถดูการเคลื่อนไหวของ Peter ระหว่าง random walk ได้:
|
|
|
|

|
|
|
|
## Reward function
|
|
|
|
เพื่อทำให้นโยบายของเราฉลาดขึ้น เราจำเป็นต้องเข้าใจว่าการเคลื่อนไหวใด "ดีกว่า" การเคลื่อนไหวอื่นๆ เพื่อทำเช่นนี้ เราจำเป็นต้องกำหนดเป้าหมายของเรา
|
|
|
|
เป้าหมายสามารถกำหนดในแง่ของ **reward function** ซึ่งจะส่งคืนค่าคะแนนสำหรับแต่ละสถานะ ยิ่งตัวเลขสูง ยิ่งได้รางวัลที่ดี (code block 5)
|
|
|
|
```python
|
|
move_reward = -0.1
|
|
goal_reward = 10
|
|
end_reward = -10
|
|
|
|
def reward(m,pos=None):
|
|
pos = pos or m.human
|
|
if not m.is_valid(pos):
|
|
return end_reward
|
|
x = m.at(pos)
|
|
if x==Board.Cell.water or x == Board.Cell.wolf:
|
|
return end_reward
|
|
if x==Board.Cell.apple:
|
|
return goal_reward
|
|
return move_reward
|
|
```
|
|
|
|
สิ่งที่น่าสนใจเกี่ยวกับ reward function คือในกรณีส่วนใหญ่ *เราจะได้รับรางวัลที่สำคัญเมื่อสิ้นสุดเกมเท่านั้น* ซึ่งหมายความว่าอัลกอริทึมของเราควรจำ "ก้าวที่ดี" ที่นำไปสู่รางวัลเชิงบวกในตอนท้าย และเพิ่มความสำคัญของมัน ในทำนองเดียวกัน การเคลื่อนไหวทั้งหมดที่นำไปสู่ผลลัพธ์ที่ไม่ดีควรถูกลดความสำคัญลง
|
|
|
|
## Q-Learning
|
|
|
|
อัลกอริทึมที่เราจะพูดถึงที่นี่เรียกว่า **Q-Learning** ในอัลกอริทึมนี้ นโยบายถูกกำหนดโดยฟังก์ชัน (หรือโครงสร้างข้อมูล) ที่เรียกว่า **Q-Table** ซึ่งบันทึก "ความดี" ของแต่ละการกระทำในสถานะที่กำหนด
|
|
|
|
มันถูกเรียกว่า Q-Table เพราะมักจะสะดวกที่จะแสดงมันเป็นตาราง หรืออาร์เรย์หลายมิติ เนื่องจากกระดานของเรามีมิติ `width` x `height` เราสามารถแสดง Q-Table โดยใช้อาร์เรย์ numpy ที่มีรูปร่าง `width` x `height` x `len(actions)`: (code block 6)
|
|
|
|
```python
|
|
Q = np.ones((width,height,len(actions)),dtype=np.float)*1.0/len(actions)
|
|
```
|
|
|
|
สังเกตว่าเราเริ่มต้นค่าทั้งหมดใน Q-Table ด้วยค่าที่เท่ากัน ในกรณีของเรา - 0.25 ซึ่งสอดคล้องกับนโยบาย "random walk" เพราะการเคลื่อนไหวทั้งหมดในแต่ละสถานะมีค่าเท่ากัน เราสามารถส่ง Q-Table ไปยังฟังก์ชัน `plot` เพื่อแสดงภาพตารางบนกระดาน: `m.plot(Q)`
|
|
|
|

|
|
|
|
ในแต่ละช่องจะมี "ลูกศร" ที่ระบุทิศทางการเคลื่อนไหวที่ต้องการ เนื่องจากทุกทิศทางเท่ากัน จึงแสดงเป็นจุด
|
|
|
|
ตอนนี้เราจำเป็นต้องรันการจำลอง สำรวจสภาพแวดล้อมของเรา และเรียนรู้การแจกแจงค่าของ Q-Table ที่ดีกว่า ซึ่งจะช่วยให้เราหาเส้นทางไปยังแอปเปิ้ลได้เร็วขึ้นมาก
|
|
|
|
## แก่นของ Q-Learning: สมการ Bellman
|
|
|
|
เมื่อเราเริ่มเคลื่อนไหว การกระทำแต่ละครั้งจะมีรางวัลที่สอดคล้องกัน กล่าวคือ เราสามารถเลือกการกระทำถัดไปตามรางวัลที่ได้รับทันที อย่างไรก็ตาม ในสถานะส่วนใหญ่ การเคลื่อนไหวจะไม่บรรลุเป้าหมายของเราในการไปถึงแอปเปิ้ล และดังนั้นเราจึงไม่สามารถตัดสินใจได้ทันทีว่าทิศทางใดดีกว่า
|
|
|
|
> จำไว้ว่าสิ่งที่สำคัญไม่ใช่ผลลัพธ์ทันที แต่เป็นผลลัพธ์สุดท้ายที่เราจะได้รับเมื่อสิ้นสุดการจำลอง
|
|
|
|
เพื่อพิจารณารางวัลที่ล่าช้า เราจำเป็นต้องใช้หลักการของ **[dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming)** ซึ่งช่วยให้เราคิดเกี่ยวกับปัญหาในเชิงเวียนกลับ
|
|
|
|
สมมติว่าเรากำลังอยู่ในสถานะ *s* และเราต้องการเคลื่อนไปยังสถานะถัดไป *s'* โดยการทำเช่นนี้ เราจะได้รับรางวัลทันที *r(s,a)* ซึ่งกำหนดโดย reward function บวกกับรางวัลในอนาคตบางส่วน หากเราสมมติว่า Q-Table ของเราสะท้อนถึง "ความน่าสนใจ" ของแต่ละการกระทำอย่างถูกต้องแล้ว ในสถานะ *s'* เราจะเลือกการกระทำ *a'* ที่สอดคล้องกับค่ามากที่สุดของ *Q(s',a')* ดังนั้น รางวัลในอนาคตที่ดีที่สุดที่เราสามารถได้รับในสถานะ *s* จะถูกกำหนดเป็น `max`
|
|
|
|
## การตรวจสอบนโยบาย
|
|
|
|
เนื่องจาก Q-Table แสดง "ความน่าสนใจ" ของแต่ละการกระทำในแต่ละสถานะ การใช้งานเพื่อกำหนดการนำทางที่มีประสิทธิภาพในโลกของเราจึงค่อนข้างง่าย ในกรณีที่ง่ายที่สุด เราสามารถเลือกการกระทำที่มีค่าที่สูงที่สุดใน Q-Table: (code block 9)
|
|
|
|
```python
|
|
def qpolicy_strict(m):
|
|
x,y = m.human
|
|
v = probs(Q[x,y])
|
|
a = list(actions)[np.argmax(v)]
|
|
return a
|
|
|
|
walk(m,qpolicy_strict)
|
|
```
|
|
|
|
> หากคุณลองรันโค้ดด้านบนหลายครั้ง คุณอาจสังเกตเห็นว่าบางครั้งมัน "ค้าง" และคุณต้องกดปุ่ม STOP ในโน้ตบุ๊กเพื่อหยุดการทำงาน สาเหตุเกิดจากสถานการณ์ที่สองสถานะ "ชี้" ไปยังอีกสถานะหนึ่งในแง่ของค่าที่เหมาะสมที่สุดใน Q-Value ซึ่งทำให้ตัวแทนเคลื่อนที่วนเวียนระหว่างสถานะเหล่านั้นอย่างไม่สิ้นสุด
|
|
|
|
## 🚀ความท้าทาย
|
|
|
|
> **งานที่ 1:** ปรับฟังก์ชัน `walk` เพื่อจำกัดความยาวสูงสุดของเส้นทางโดยจำนวนก้าวที่กำหนด (เช่น 100) และดูว่าโค้ดด้านบนคืนค่านี้เป็นครั้งคราว
|
|
|
|
> **งานที่ 2:** ปรับฟังก์ชัน `walk` เพื่อไม่ให้กลับไปยังสถานที่ที่เคยไปมาก่อนแล้ว วิธีนี้จะป้องกันไม่ให้ `walk` วนซ้ำ อย่างไรก็ตาม ตัวแทนอาจยังคงติดอยู่ในตำแหน่งที่ไม่สามารถหลบหนีได้
|
|
|
|
## การนำทาง
|
|
|
|
นโยบายการนำทางที่ดีกว่าคือแบบที่เราใช้ระหว่างการฝึก ซึ่งรวมการใช้ประโยชน์จากข้อมูลที่มีอยู่และการสำรวจ ในนโยบายนี้ เราจะเลือกแต่ละการกระทำด้วยความน่าจะเป็นที่สัมพันธ์กับค่าที่อยู่ใน Q-Table กลยุทธ์นี้อาจยังทำให้ตัวแทนกลับไปยังตำแหน่งที่เคยสำรวจแล้ว แต่ดังที่คุณเห็นจากโค้ดด้านล่าง มันส่งผลให้เส้นทางเฉลี่ยสั้นลงมากไปยังตำแหน่งที่ต้องการ (จำไว้ว่า `print_statistics` รันการจำลอง 100 ครั้ง): (code block 10)
|
|
|
|
```python
|
|
def qpolicy(m):
|
|
x,y = m.human
|
|
v = probs(Q[x,y])
|
|
a = random.choices(list(actions),weights=v)[0]
|
|
return a
|
|
|
|
print_statistics(qpolicy)
|
|
```
|
|
|
|
หลังจากรันโค้ดนี้ คุณควรได้ความยาวเส้นทางเฉลี่ยที่สั้นลงมากกว่าเดิม อยู่ในช่วงประมาณ 3-6
|
|
|
|
## การตรวจสอบกระบวนการเรียนรู้
|
|
|
|
ดังที่เราได้กล่าวไว้ กระบวนการเรียนรู้เป็นการสร้างสมดุลระหว่างการสำรวจและการใช้ความรู้ที่ได้รับเกี่ยวกับโครงสร้างของพื้นที่ปัญหา เราได้เห็นว่าผลลัพธ์ของการเรียนรู้ (ความสามารถในการช่วยตัวแทนค้นหาเส้นทางสั้นไปยังเป้าหมาย) ดีขึ้น แต่สิ่งที่น่าสนใจคือการสังเกตว่าความยาวเส้นทางเฉลี่ยเปลี่ยนแปลงอย่างไรระหว่างกระบวนการเรียนรู้:
|
|
|
|
## สรุปการเรียนรู้
|
|
|
|
- **ความยาวเส้นทางเฉลี่ยเพิ่มขึ้น**: สิ่งที่เราเห็นคือในตอนแรก ความยาวเส้นทางเฉลี่ยเพิ่มขึ้น สาเหตุอาจเกิดจากเมื่อเราไม่รู้อะไรเกี่ยวกับสภาพแวดล้อม เรามักจะติดอยู่ในสถานะที่ไม่ดี เช่น น้ำหรือหมาป่า เมื่อเราเรียนรู้มากขึ้นและเริ่มใช้ความรู้นี้ เราสามารถสำรวจสภาพแวดล้อมได้นานขึ้น แต่เรายังไม่รู้ตำแหน่งของแอปเปิ้ลดีนัก
|
|
|
|
- **ความยาวเส้นทางลดลงเมื่อเรียนรู้มากขึ้น**: เมื่อเราเรียนรู้มากพอ มันจะง่ายขึ้นสำหรับตัวแทนในการบรรลุเป้าหมาย และความยาวเส้นทางเริ่มลดลง อย่างไรก็ตาม เรายังเปิดโอกาสให้สำรวจ ดังนั้นเรามักจะเบี่ยงเบนออกจากเส้นทางที่ดีที่สุด และสำรวจตัวเลือกใหม่ ๆ ทำให้เส้นทางยาวกว่าที่ควรจะเป็น
|
|
|
|
- **ความยาวเพิ่มขึ้นอย่างฉับพลัน**: สิ่งที่เราสังเกตเห็นในกราฟคือในบางจุด ความยาวเพิ่มขึ้นอย่างฉับพลัน สิ่งนี้บ่งชี้ถึงลักษณะสุ่มของกระบวนการ และในบางครั้งเราอาจ "ทำลาย" ค่าสัมประสิทธิ์ใน Q-Table โดยการเขียนทับด้วยค่าที่ใหม่ สิ่งนี้ควรลดลงโดยการลดอัตราการเรียนรู้ (เช่น ในช่วงท้ายของการฝึก เราปรับค่าของ Q-Table เพียงเล็กน้อย)
|
|
|
|
โดยรวมแล้ว สิ่งสำคัญคือต้องจำไว้ว่าความสำเร็จและคุณภาพของกระบวนการเรียนรู้ขึ้นอยู่กับพารามิเตอร์ เช่น อัตราการเรียนรู้ การลดอัตราการเรียนรู้ และตัวคูณลดค่า พารามิเตอร์เหล่านี้มักถูกเรียกว่า **hyperparameters** เพื่อแยกความแตกต่างจาก **parameters** ซึ่งเราปรับแต่งระหว่างการฝึก (เช่น ค่าสัมประสิทธิ์ใน Q-Table) กระบวนการค้นหาค่าที่ดีที่สุดสำหรับ hyperparameters เรียกว่า **hyperparameter optimization** และมันสมควรได้รับการพูดถึงในหัวข้อแยกต่างหาก
|
|
|
|
## [แบบทดสอบหลังการบรรยาย](https://ff-quizzes.netlify.app/en/ml/)
|
|
|
|
## งานที่ได้รับมอบหมาย
|
|
[โลกที่สมจริงยิ่งขึ้น](assignment.md)
|
|
|
|
---
|
|
|
|
**ข้อจำกัดความรับผิดชอบ**:
|
|
เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้องมากที่สุด แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความที่ผิดพลาดซึ่งเกิดจากการใช้การแปลนี้ |