33 KiB
ข้อกำหนดเบื้องต้น
ในบทเรียนนี้ เราจะใช้ไลบรารีที่เรียกว่า OpenAI Gym เพื่อจำลอง สภาพแวดล้อม ต่างๆ คุณสามารถรันโค้ดของบทเรียนนี้ในเครื่องของคุณเอง (เช่น จาก Visual Studio Code) ซึ่งการจำลองจะเปิดในหน้าต่างใหม่ หากคุณรันโค้ดออนไลน์ คุณอาจต้องปรับแต่งโค้ดเล็กน้อยตามที่อธิบายไว้ ที่นี่
OpenAI Gym
ในบทเรียนก่อน กฎของเกมและสถานะถูกกำหนดโดยคลาส Board
ซึ่งเราได้สร้างขึ้นเอง ในที่นี้เราจะใช้ สภาพแวดล้อมจำลอง พิเศษ ซึ่งจะจำลองฟิสิกส์ของการทรงตัวของเสา หนึ่งในสภาพแวดล้อมจำลองที่ได้รับความนิยมมากที่สุดสำหรับการฝึกอัลกอริทึมการเรียนรู้แบบเสริมกำลังคือ Gym ซึ่งได้รับการดูแลโดย OpenAI โดยการใช้ Gym นี้ เราสามารถสร้าง สภาพแวดล้อม ต่างๆ ตั้งแต่การจำลอง CartPole ไปจนถึงเกม Atari
หมายเหตุ: คุณสามารถดูสภาพแวดล้อมอื่นๆ ที่มีใน OpenAI Gym ที่นี่
ก่อนอื่น มาติดตั้ง Gym และนำเข้าไลบรารีที่จำเป็น (code block 1):
import sys
!{sys.executable} -m pip install gym
import gym
import matplotlib.pyplot as plt
import numpy as np
import random
แบบฝึกหัด - การเริ่มต้นสภาพแวดล้อม CartPole
ในการทำงานกับปัญหาการทรงตัวของ CartPole เราจำเป็นต้องเริ่มต้นสภาพแวดล้อมที่เกี่ยวข้อง สภาพแวดล้อมแต่ละแห่งจะมี:
-
Observation space ที่กำหนดโครงสร้างของข้อมูลที่เราได้รับจากสภาพแวดล้อม สำหรับปัญหา CartPole เราจะได้รับตำแหน่งของเสา ความเร็ว และค่าบางอย่างอื่นๆ
-
Action space ที่กำหนดการกระทำที่เป็นไปได้ ในกรณีของเรา Action space เป็นแบบไม่ต่อเนื่อง และประกอบด้วยสองการกระทำ - ซ้าย และ ขวา (code block 2)
-
ในการเริ่มต้น ให้พิมพ์โค้ดต่อไปนี้:
env = gym.make("CartPole-v1") print(env.action_space) print(env.observation_space) print(env.action_space.sample())
เพื่อดูว่าสภาพแวดล้อมทำงานอย่างไร ลองรันการจำลองสั้นๆ เป็นเวลา 100 ขั้นตอน ในแต่ละขั้นตอน เราจะให้การกระทำหนึ่งอย่างที่ต้องทำ - ในการจำลองนี้ เราเพียงแค่สุ่มเลือกการกระทำจาก action_space
-
รันโค้ดด้านล่างและดูผลลัพธ์ที่ได้
✅ จำไว้ว่าควรรันโค้ดนี้ในเครื่อง Python ที่ติดตั้งในเครื่องของคุณ! (code block 3)
env.reset() for i in range(100): env.render() env.step(env.action_space.sample()) env.close()
คุณควรเห็นภาพที่คล้ายกับภาพนี้:
-
ระหว่างการจำลอง เราจำเป็นต้องได้รับข้อมูลการสังเกตเพื่อที่จะตัดสินใจว่าจะทำอะไรต่อไป ในความเป็นจริง ฟังก์ชัน step จะคืนค่าการสังเกตปัจจุบัน ฟังก์ชันรางวัล และธง done ที่ระบุว่าควรดำเนินการจำลองต่อไปหรือไม่: (code block 4)
env.reset() done = False while not done: env.render() obs, rew, done, info = env.step(env.action_space.sample()) print(f"{obs} -> {rew}") env.close()
คุณจะเห็นผลลัพธ์ที่คล้ายกับนี้ในผลลัพธ์ของโน้ตบุ๊ก:
[ 0.03403272 -0.24301182 0.02669811 0.2895829 ] -> 1.0 [ 0.02917248 -0.04828055 0.03248977 0.00543839] -> 1.0 [ 0.02820687 0.14636075 0.03259854 -0.27681916] -> 1.0 [ 0.03113408 0.34100283 0.02706215 -0.55904489] -> 1.0 [ 0.03795414 0.53573468 0.01588125 -0.84308041] -> 1.0 ... [ 0.17299878 0.15868546 -0.20754175 -0.55975453] -> 1.0 [ 0.17617249 0.35602306 -0.21873684 -0.90998894] -> 1.0
เวกเตอร์การสังเกตที่คืนค่ามาในแต่ละขั้นตอนของการจำลองประกอบด้วยค่าต่อไปนี้:
- ตำแหน่งของรถเข็น
- ความเร็วของรถเข็น
- มุมของเสา
- อัตราการหมุนของเสา
-
รับค่าต่ำสุดและค่าสูงสุดของตัวเลขเหล่านั้น: (code block 5)
print(env.observation_space.low) print(env.observation_space.high)
คุณอาจสังเกตเห็นว่าค่ารางวัลในแต่ละขั้นตอนของการจำลองมีค่าเท่ากับ 1 เสมอ นั่นเป็นเพราะเป้าหมายของเราคือการอยู่รอดให้นานที่สุดเท่าที่จะเป็นไปได้ กล่าวคือ รักษาเสาให้อยู่ในตำแหน่งแนวตั้งในระดับที่เหมาะสมให้นานที่สุด
✅ ในความเป็นจริง การจำลอง CartPole ถือว่าแก้ปัญหาได้หากเราสามารถรับรางวัลเฉลี่ย 195 ในการทดลองต่อเนื่อง 100 ครั้ง
การทำสถานะให้เป็นแบบไม่ต่อเนื่อง
ใน Q-Learning เราจำเป็นต้องสร้าง Q-Table ที่กำหนดว่าจะทำอะไรในแต่ละสถานะ เพื่อที่จะทำสิ่งนี้ได้ เราจำเป็นต้องให้สถานะเป็นแบบ ไม่ต่อเนื่อง กล่าวคือ ควรมีจำนวนค่าที่ไม่ต่อเนื่องที่จำกัด ดังนั้นเราจำเป็นต้อง ทำสถานะให้เป็นแบบไม่ต่อเนื่อง โดยการแมปค่าการสังเกตไปยังชุดสถานะที่จำกัด
มีวิธีการบางอย่างที่เราสามารถทำได้:
- แบ่งเป็นช่วง หากเรารู้ช่วงของค่าบางค่า เราสามารถแบ่งช่วงนี้ออกเป็นจำนวน ช่วง และแทนค่าด้วยหมายเลขช่วงที่มันอยู่ วิธีนี้สามารถทำได้โดยใช้เมธอด
digitize
ของ numpy ในกรณีนี้ เราจะทราบขนาดของสถานะอย่างแม่นยำ เพราะมันจะขึ้นอยู่กับจำนวนช่วงที่เราเลือกสำหรับการทำให้เป็นแบบดิจิทัล
✅ เราสามารถใช้การแทรกเชิงเส้นเพื่อปรับค่ามาอยู่ในช่วงที่จำกัด (เช่น จาก -20 ถึง 20) และจากนั้นแปลงตัวเลขเป็นจำนวนเต็มโดยการปัดเศษ วิธีนี้จะให้การควบคุมขนาดของสถานะน้อยลง โดยเฉพาะอย่างยิ่งหากเราไม่ทราบช่วงที่แน่นอนของค่าขาเข้า ตัวอย่างเช่น ในกรณีของเรา 2 ใน 4 ค่าจะไม่มีขอบเขตบน/ล่าง ซึ่งอาจส่งผลให้มีจำนวนสถานะที่ไม่มีที่สิ้นสุด
ในตัวอย่างของเรา เราจะใช้วิธีที่สอง ตามที่คุณอาจสังเกตเห็นในภายหลัง แม้จะไม่มีขอบเขตบน/ล่าง ค่าดังกล่าวมักจะไม่ค่อยมีค่าที่อยู่นอกช่วงที่จำกัด ดังนั้นสถานะที่มีค่าที่สุดขั้วจะเกิดขึ้นได้ยาก
-
นี่คือฟังก์ชันที่จะรับค่าการสังเกตจากโมเดลของเราและสร้างทูเพิลของค่าจำนวนเต็ม 4 ค่า: (code block 6)
def discretize(x): return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
-
ลองสำรวจวิธีการทำให้เป็นแบบไม่ต่อเนื่องด้วยช่วงอีกวิธีหนึ่ง: (code block 7)
def create_bins(i,num): return np.arange(num+1)*(i[1]-i[0])/num+i[0] print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10)) ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter nbins = [20,20,10,10] # number of bins for each parameter bins = [create_bins(ints[i],nbins[i]) for i in range(4)] def discretize_bins(x): return tuple(np.digitize(x[i],bins[i]) for i in range(4))
-
ตอนนี้ลองรันการจำลองสั้นๆ และสังเกตค่าของสภาพแวดล้อมที่ไม่ต่อเนื่อง ลองใช้ทั้ง
discretize
และdiscretize_bins
และดูว่ามีความแตกต่างหรือไม่✅
discretize_bins
คืนหมายเลขช่วง ซึ่งเริ่มต้นที่ 0 ดังนั้นสำหรับค่าของตัวแปรขาเข้าที่อยู่รอบๆ 0 จะคืนค่าจากกลางช่วง (10) ในdiscretize
เราไม่ได้สนใจช่วงของค่าผลลัพธ์ ทำให้ค่าของสถานะไม่ถูกเลื่อน และ 0 ตรงกับ 0 (code block 8)env.reset() done = False while not done: #env.render() obs, rew, done, info = env.step(env.action_space.sample()) #print(discretize_bins(obs)) print(discretize(obs)) env.close()
✅ ยกเลิกการคอมเมนต์บรรทัดที่เริ่มต้นด้วย env.render หากคุณต้องการดูว่าสภาพแวดล้อมทำงานอย่างไร มิฉะนั้นคุณสามารถรันมันในพื้นหลัง ซึ่งจะเร็วกว่า เราจะใช้การรันแบบ "มองไม่เห็น" นี้ในระหว่างกระบวนการ Q-Learning ของเรา
โครงสร้างของ Q-Table
ในบทเรียนก่อน สถานะเป็นคู่ของตัวเลขง่ายๆ จาก 0 ถึง 8 และดังนั้นจึงสะดวกที่จะแสดง Q-Table ด้วยเทนเซอร์ numpy ที่มีรูปร่าง 8x8x2 หากเราใช้การทำให้เป็นแบบไม่ต่อเนื่องด้วยช่วง ขนาดของเวกเตอร์สถานะของเราก็จะทราบเช่นกัน ดังนั้นเราสามารถใช้วิธีเดียวกันและแสดงสถานะด้วยอาร์เรย์ที่มีรูปร่าง 20x20x10x10x2 (ที่นี่ 2 คือมิติของ Action space และมิติแรกสอดคล้องกับจำนวนช่วงที่เราเลือกใช้สำหรับแต่ละพารามิเตอร์ใน Observation space)
อย่างไรก็ตาม บางครั้งขนาดที่แน่นอนของ Observation space อาจไม่ทราบ ในกรณีของฟังก์ชัน discretize
เราอาจไม่สามารถมั่นใจได้ว่าสถานะของเราจะอยู่ในขอบเขตที่แน่นอน เพราะค่าบางค่าของต้นฉบับไม่มีขอบเขต ดังนั้นเราจะใช้วิธีที่แตกต่างออกไปเล็กน้อยและแสดง Q-Table ด้วยดิกชันนารี
-
ใช้คู่ (state,action) เป็นคีย์ของดิกชันนารี และค่าจะสอดคล้องกับค่าของ Q-Table (code block 9)
Q = {} actions = (0,1) def qvalues(state): return [Q.get((state,a),0) for a in actions]
ที่นี่เรายังได้กำหนดฟังก์ชัน
qvalues()
ซึ่งคืนค่ารายการ Q-Table สำหรับสถานะที่กำหนดซึ่งสอดคล้องกับการกระทำที่เป็นไปได้ทั้งหมด หากไม่มีรายการใน Q-Table เราจะคืนค่า 0 เป็นค่าเริ่มต้น
มาเริ่ม Q-Learning กันเถอะ
ตอนนี้เราพร้อมที่จะสอน Peter ให้ทรงตัวแล้ว!
-
ก่อนอื่น มาตั้งค่าพารามิเตอร์ไฮเปอร์บางตัว: (code block 10)
# hyperparameters alpha = 0.3 gamma = 0.9 epsilon = 0.90
ที่นี่
alpha
คือ learning rate ที่กำหนดว่าเราควรปรับค่าปัจจุบันของ Q-Table ในแต่ละขั้นตอนมากน้อยเพียงใด ในบทเรียนก่อนเราเริ่มต้นด้วย 1 และจากนั้นลดalpha
ลงในระหว่างการฝึก ในตัวอย่างนี้เราจะคงค่าคงที่ไว้เพื่อความเรียบง่าย และคุณสามารถทดลองปรับค่าของalpha
ได้ในภายหลังgamma
คือ discount factor ที่แสดงว่าเราควรให้ความสำคัญกับรางวัลในอนาคตมากกว่ารางวัลปัจจุบันมากน้อยเพียงใดepsilon
คือ exploration/exploitation factor ที่กำหนดว่าเราควรเลือกการสำรวจมากกว่าการใช้ประโยชน์หรือไม่ ในอัลกอริทึมของเรา เราจะเลือกการกระทำถัดไปตามค่าของ Q-Table ในเปอร์เซ็นต์ของกรณีที่กำหนดโดยepsilon
และในจำนวนที่เหลือเราจะดำเนินการแบบสุ่ม สิ่งนี้จะช่วยให้เราสำรวจพื้นที่การค้นหาที่เราไม่เคยเห็นมาก่อน✅ ในแง่ของการทรงตัว - การเลือกการกระทำแบบสุ่ม (การสำรวจ) จะทำหน้าที่เป็นการผลักแบบสุ่มในทิศทางที่ผิด และเสาจะต้องเรียนรู้วิธีการฟื้นฟูสมดุลจาก "ข้อผิดพลาด" เหล่านั้น
ปรับปรุงอัลกอริทึม
เรายังสามารถปรับปรุงอัลกอริทึมของเราจากบทเรียนก่อน:
-
คำนวณรางวัลสะสมเฉลี่ย ในการจำลองจำนวนหนึ่ง เราจะพิมพ์ความคืบหน้าทุกๆ 5000 รอบ และเราจะเฉลี่ยรางวัลสะสมในช่วงเวลานั้น หมายความว่าหากเราได้คะแนนมากกว่า 195 เราสามารถถือว่าปัญหาได้รับการแก้ไขแล้ว ด้วยคุณภาพที่สูงกว่าที่กำหนด
-
คำนวณผลลัพธ์สะสมเฉลี่ยสูงสุด
Qmax
และเราจะเก็บ Q-Table ที่สอดคล้องกับผลลัพธ์นั้น เมื่อคุณรันการฝึก คุณจะสังเกตเห็นว่าบางครั้งผลลัพธ์สะสมเฉลี่ยเริ่มลดลง และเราต้องการเก็บค่าของ Q-Table ที่สอดคล้องกับโมเดลที่ดีที่สุดที่สังเกตได้ระหว่างการฝึก
-
เก็บรางวัลสะสมทั้งหมดในแต่ละการจำลองไว้ในเวกเตอร์
rewards
เพื่อการพล็อตในภายหลัง (code block 11)def probs(v,eps=1e-4): v = v-v.min()+eps v = v/v.sum() return v Qmax = 0 cum_rewards = [] rewards = [] for epoch in range(100000): obs = env.reset() done = False cum_reward=0 # == do the simulation == while not done: s = discretize(obs) if random.random()<epsilon: # exploitation - chose the action according to Q-Table probabilities v = probs(np.array(qvalues(s))) a = random.choices(actions,weights=v)[0] else: # exploration - randomly chose the action a = np.random.randint(env.action_space.n) obs, rew, done, info = env.step(a) cum_reward+=rew ns = discretize(obs) Q[(s,a)] = (1 - alpha) * Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns))) cum_rewards.append(cum_reward) rewards.append(cum_reward) # == Periodically print results and calculate average reward == if epoch%5000==0: print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}") if np.average(cum_rewards) > Qmax: Qmax = np.average(cum_rewards) Qbest = Q cum_rewards=[]
สิ่งที่คุณอาจสังเกตเห็นจากผลลัพธ์เหล่านั้น:
-
ใกล้เป้าหมายของเรา เราใกล้จะบรรลุเป้าหมายในการได้รับรางวัลสะสม 195 ในการรันการจำลองต่อเนื่อง 100+ ครั้ง หรือเราอาจบรรลุเป้าหมายแล้ว! แม้ว่าเราจะได้ตัวเลขที่น้อยกว่า เราก็ยังไม่ทราบ เพราะเราเฉลี่ยมากกว่า 5000 รอบ และมีเพียง 100 รอบที่จำเป็นในเกณฑ์อย่างเป็นทางการ
-
รางวัลเริ่มลดลง บางครั้งรางวัลเริ่มลดลง ซึ่งหมายความว่าเราอาจ "ทำลาย" ค่าที่เรียนรู้แล้วใน Q-Table ด้วยค่าที่ทำให้สถานการณ์แย่ลง
การสังเกตนี้จะเห็นได้ชัดเจนขึ้นหากเราพล็อตความคืบหน้าการฝึก
การพล็อตความคืบหน้าการฝึก
ระหว่างการฝึก เราได้เก็บค่ารางวัลสะสมในแต่ละรอบไว้ในเวกเตอร์ rewards
นี่คือสิ่งที่มันดูเหมือนเมื่อเราพล็อตมันกับหมายเลขรอบ:
plt.plot(rewards)
จากกราฟนี้ ไม่สามารถบอกอะไรได้ เพราะลักษณะของกระบวนการฝึกแบบสุ่มทำให้ความยาวของเซสชันการฝึกแตกต่างกันมาก เพื่อให้กราฟนี้มีความหมายมากขึ้น เราสามารถคำนวณ ค่าเฉลี่ยเคลื่อนที่ ในการทดลองชุดหนึ่ง เช่น 100 สิ่งนี้สามารถทำได้อย่างสะดวกโดยใช้ np.convolve
: (code block 12)
def running_average(x,window):
return np.convolve(x,np.ones(window)/window,mode='valid')
plt.plot(running_average(rewards,100))
การปรับพารามิเตอร์ไฮเปอร์
เพื่อให้การเรียนรู้มีเสถียรภาพมากขึ้น มีเหตุผลที่จะปรับพารามิเตอร์ไฮเปอร์บางตัวระหว่างการฝึก โดยเฉพาะ:
-
สำหรับ learning rate
alpha
เราอาจเริ่มต้นด้วยค่าที่ใกล้เคียงกับ 1 และจากนั้นลดค่าพารามิเตอร์ลงเรื่อยๆ เมื่อเวลาผ่านไป เราจะได้รับค่าความน่าจะเป็นที่ดีใน Q-Table และดังนั้นเราควรปรับค่าพวกนั้นเล็กน้อย และไม่เขียนทับด้วยค่าที่ใหม่ทั้งหมด -
เพิ่ม epsilon เราอาจต้องการเพิ่ม
epsilon
อย่างช้าๆ เพื่อสำรวจน้อยลงและใช้ประโยชน์มากขึ้น อาจมีเหตุผลที่จะเริ่มต้นด้วยค่าที่ต่ำของepsilon
และเพิ่มขึ้นจนเกือบถึง 1
งานที่ 1: ลองปรับค่าพารามิเตอร์ต่าง ๆ และดูว่าคุณสามารถทำให้ผลตอบแทนรวมสูงขึ้นได้หรือไม่ คุณได้คะแนนเกิน 195 หรือยัง? งานที่ 2: เพื่อแก้ปัญหาอย่างเป็นทางการ คุณจำเป็นต้องได้ค่าเฉลี่ยรางวัล 195 ในการทดลอง 100 ครั้งติดต่อกัน วัดผลระหว่างการฝึกและตรวจสอบให้แน่ใจว่าคุณได้แก้ปัญหาอย่างเป็นทางการแล้ว!
ดูผลลัพธ์ในทางปฏิบัติ
มันน่าสนใจที่จะเห็นว่ารูปแบบที่ฝึกมาแล้วทำงานอย่างไร ลองรันการจำลองและใช้กลยุทธ์การเลือกการกระทำแบบเดียวกับที่ใช้ระหว่างการฝึก โดยสุ่มตามการแจกแจงความน่าจะเป็นใน Q-Table: (code block 13)
obs = env.reset()
done = False
while not done:
s = discretize(obs)
env.render()
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
obs,_,done,_ = env.step(a)
env.close()
คุณควรเห็นบางสิ่งที่คล้ายกับนี้:
🚀ความท้าทาย
งานที่ 3: ในที่นี้ เราใช้สำเนาสุดท้ายของ Q-Table ซึ่งอาจไม่ใช่ตัวที่ดีที่สุด อย่าลืมว่าเราได้บันทึก Q-Table ที่มีประสิทธิภาพดีที่สุดไว้ในตัวแปร
Qbest
! ลองใช้ตัวอย่างเดียวกันกับ Q-Table ที่มีประสิทธิภาพดีที่สุดโดยคัดลอกQbest
ไปยังQ
และดูว่าคุณสังเกตเห็นความแตกต่างหรือไม่
งานที่ 4: ในที่นี้ เราไม่ได้เลือกการกระทำที่ดีที่สุดในแต่ละขั้นตอน แต่สุ่มตามการแจกแจงความน่าจะเป็นที่เกี่ยวข้อง จะสมเหตุสมผลกว่าหรือไม่ถ้าเลือกการกระทำที่ดีที่สุดเสมอ ซึ่งมีค่าที่สูงที่สุดใน Q-Table? สิ่งนี้สามารถทำได้โดยใช้ฟังก์ชัน
np.argmax
เพื่อค้นหาหมายเลขการกระทำที่สอดคล้องกับค่าที่สูงที่สุดใน Q-Table ลองใช้กลยุทธ์นี้และดูว่ามันช่วยปรับปรุงการทรงตัวหรือไม่
แบบทดสอบหลังการบรรยาย
งานที่ได้รับมอบหมาย
สรุป
ตอนนี้เราได้เรียนรู้วิธีการฝึกตัวแทนเพื่อให้ได้ผลลัพธ์ที่ดีเพียงแค่ให้ฟังก์ชันรางวัลที่กำหนดสถานะที่ต้องการของเกม และให้โอกาสพวกเขาสำรวจพื้นที่ค้นหาอย่างชาญฉลาด เราได้ใช้ Q-Learning algorithm สำเร็จในกรณีของสภาพแวดล้อมแบบไม่ต่อเนื่องและต่อเนื่อง แต่มีการกระทำแบบไม่ต่อเนื่อง
สิ่งสำคัญคือต้องศึกษาสถานการณ์ที่สถานะการกระทำเป็นแบบต่อเนื่อง และเมื่อพื้นที่การสังเกตมีความซับซ้อนมากขึ้น เช่น ภาพจากหน้าจอเกม Atari ในปัญหาเหล่านี้เรามักต้องใช้เทคนิคการเรียนรู้ของเครื่องที่ทรงพลังมากขึ้น เช่น neural networks เพื่อให้ได้ผลลัพธ์ที่ดี หัวข้อที่ก้าวหน้ากว่านี้จะเป็นเนื้อหาในหลักสูตร AI ขั้นสูงของเราในอนาคต
ข้อจำกัดความรับผิดชอบ:
เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI Co-op Translator แม้ว่าเราจะพยายามให้การแปลมีความถูกต้องมากที่สุด แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้