การกระจาย TensorFlow: บทนำที่อ่อนโยน

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

ในสมุดบันทึกนี้ เราจะมาสำรวจ TensorFlow Distributions (ย่อมาจาก TFD) เป้าหมายของโน้ตบุ๊กนี้คือช่วยให้คุณค่อยๆ เข้าสู่ช่วงการเรียนรู้ รวมถึงการทำความเข้าใจการจัดการรูปร่างเทนเซอร์ของ TFD สมุดบันทึกนี้พยายามนำเสนอตัวอย่างมาก่อนมากกว่าแนวคิดที่เป็นนามธรรม เราจะนำเสนอวิธีง่ายๆ ที่เป็นที่ยอมรับในการทำสิ่งต่างๆ ก่อน และบันทึกมุมมองนามธรรมทั่วไปที่สุดไว้จนกว่าจะสิ้นสุด หากคุณเป็นประเภทที่ชอบนามธรรมมากขึ้นและการอ้างอิงสไตล์การสอนให้ตรวจสอบ การทำความเข้าใจ TensorFlow กระจายรูปร่าง หากคุณมีคำถามใด ๆ เกี่ยวกับวัสดุที่นี่อย่าลังเลที่จะติดต่อ (หรือเข้าร่วม) TensorFlow ความน่าจะเป็นรายการทางไปรษณีย์ เรายินดีที่จะช่วยเหลือ

ก่อนที่เราจะเริ่ม เราต้องนำเข้าไลบรารีที่เหมาะสม ห้องสมุดโดยรวมของเราคือ tensorflow_probability โดยการประชุมเราโดยทั่วไปหมายถึงห้องสมุดกระจายเป็น tfd

Tensorflow กระตือรือร้นที่ เป็นสภาพแวดล้อมการดำเนินการความจำเป็นสำหรับ TensorFlow ในความกระตือรือร้นของ TensorFlow การดำเนินการ TF ทุกครั้งจะได้รับการประเมินและให้ผลลัพธ์ทันที ซึ่งตรงกันข้ามกับโหมด "กราฟ" มาตรฐานของ TensorFlow ซึ่งการดำเนินการ TF จะเพิ่มโหนดลงในกราฟซึ่งจะดำเนินการในภายหลัง สมุดบันทึกทั้งหมดนี้เขียนโดยใช้ TF Eager แม้ว่าจะไม่มีแนวคิดใดที่นำเสนอในที่นี้ และสามารถใช้ TFP ในโหมดกราฟได้

import collections

import tensorflow as tf
import tensorflow_probability as tfp
tfd = tfp.distributions

try:
  tf.compat.v1.enable_eager_execution()
except ValueError:
  pass

import matplotlib.pyplot as plt

การแจกแจงแบบตัวแปรเดียวขั้นพื้นฐาน

มาดำดิ่งลงไปและสร้างการแจกแจงแบบปกติ:

n = tfd.Normal(loc=0., scale=1.)
n
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>

เราสามารถวาดตัวอย่างจากมัน:

n.sample()
<tf.Tensor: shape=(), dtype=float32, numpy=0.25322816>

เราสามารถวาดตัวอย่างได้หลายตัวอย่าง:

n.sample(3)
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-1.4658079, -0.5653636,  0.9314412], dtype=float32)>

เราสามารถประเมินปัญหาบันทึกได้:

n.log_prob(0.)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.9189385>

เราสามารถประเมินความน่าจะเป็นของบันทึกหลายรายการ:

n.log_prob([0., 2., 4.])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-0.9189385, -2.9189386, -8.918939 ], dtype=float32)>

เรามีการแจกจ่ายที่หลากหลาย มาลองเบอร์นูลลีกัน:

b = tfd.Bernoulli(probs=0.7)
b
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[] event_shape=[] dtype=int32>
b.sample()
<tf.Tensor: shape=(), dtype=int32, numpy=1>
b.sample(8)
<tf.Tensor: shape=(8,), dtype=int32, numpy=array([1, 0, 0, 0, 1, 0, 1, 0], dtype=int32)>
b.log_prob(1)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.35667497>
b.log_prob([1, 0, 1, 0])
<tf.Tensor: shape=(4,), dtype=float32, numpy=array([-0.35667497, -1.2039728 , -0.35667497, -1.2039728 ], dtype=float32)>

การแจกแจงหลายตัวแปร

เราจะสร้างค่าปกติหลายตัวแปรที่มีความแปรปรวนร่วมในแนวทแยง:

nd = tfd.MultivariateNormalDiag(loc=[0., 10.], scale_diag=[1., 4.])
nd
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>

เมื่อเปรียบเทียบกับค่าปกติที่ไม่มีตัวแปรที่เราสร้างขึ้นก่อนหน้านี้ มีอะไรแตกต่างกันบ้าง

tfd.Normal(loc=0., scale=1.)
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>

เราจะเห็นว่า univariate ปกติมี event_shape ของ () ซึ่งแสดงว่าการกระจายเกลา หลายตัวแปรปกติมี event_shape ของ 2 แสดงให้เห็น [พื้นที่จัดกิจกรรม] ขั้นพื้นฐาน (https://en.wikipedia.org/wiki/Event_ (probability_theory)) ของการกระจายนี้เป็นสองมิติ

การสุ่มตัวอย่างทำงานเหมือนเมื่อก่อน:

nd.sample()
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-1.2489667, 15.025171 ], dtype=float32)>
nd.sample(5)
<tf.Tensor: shape=(5, 2), dtype=float32, numpy=
array([[-1.5439653 ,  8.9968405 ],
       [-0.38730723, 12.448896  ],
       [-0.8697963 ,  9.330035  ],
       [-1.2541095 , 10.268944  ],
       [ 2.3475595 , 13.184147  ]], dtype=float32)>
nd.log_prob([0., 10])
<tf.Tensor: shape=(), dtype=float32, numpy=-3.2241714>

ค่าปกติแบบหลายตัวแปรโดยทั่วไปจะไม่มีความแปรปรวนร่วมในแนวทแยง TFD มีหลายวิธีในการสร้างค่าปกติของตัวแปรหลายตัว รวมถึงข้อกำหนดความแปรปรวนร่วมแบบเต็ม ซึ่งเราใช้ที่นี่

nd = tfd.MultivariateNormalFullCovariance(
    loc = [0., 5], covariance_matrix = [[1., .7], [.7, 1.]])
data = nd.sample(200)
plt.scatter(data[:, 0], data[:, 1], color='blue', alpha=0.4)
plt.axis([-5, 5, 0, 10])
plt.title("Data set")
plt.show()

png

การกระจายหลายรายการ

การแจกแจงเบอร์นูลลีครั้งแรกของเราแสดงถึงการพลิกเหรียญที่ยุติธรรมเพียงเหรียญเดียว นอกจากนี้เรายังสามารถสร้างชุดของการกระจาย Bernoulli อิสระแต่ละคนมีค่าพารามิเตอร์ของตัวเองในครั้งเดียว Distribution วัตถุ:

b3 = tfd.Bernoulli(probs=[.3, .5, .7])
b3
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>

สิ่งสำคัญคือต้องมีความชัดเจนว่าสิ่งนี้หมายถึงอะไร โทรข้างต้นกำหนดสามกระจาย Bernoulli อิสระซึ่งเกิดขึ้นที่จะมีอยู่ในเดียวกันหลาม Distribution วัตถุ ไม่สามารถจัดการการแจกแจงทั้งสามแบบแยกกันได้ หมายเหตุวิธีการ batch_shape คือ (3,) แสดงให้เห็นชุดของสามกระจายและทั้ง event_shape คือ () แสดงให้เห็นการกระจายของแต่ละบุคคลมีพื้นที่จัดกิจกรรม univariate

ถ้าเราโทรหา sample ที่เราได้รับตัวอย่างจากทั้งสาม:

b3.sample()
<tf.Tensor: shape=(3,), dtype=int32, numpy=array([0, 1, 1], dtype=int32)>
b3.sample(6)
<tf.Tensor: shape=(6, 3), dtype=int32, numpy=
array([[1, 0, 1],
       [0, 1, 1],
       [0, 0, 1],
       [0, 0, 1],
       [0, 0, 1],
       [0, 1, 0]], dtype=int32)>

ถ้าเราเรียก prob (นี้มีความหมายรูปร่างเช่นเดียวกับ log_prob เราใช้ prob กับสิ่งเหล่านี้ตัวอย่าง Bernoulli ขนาดเล็กเพื่อความชัดเจนแม้ว่า log_prob มักจะเป็นที่ต้องการในการใช้งาน) เราสามารถผ่านมันเวกเตอร์และประเมินความน่าจะเป็นของแต่ละเหรียญผลผลิตคุ้มค่าว่า :

b3.prob([1, 1, 0])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5       , 0.29999998], dtype=float32)>

เหตุใด API จึงรวมรูปร่างแบทช์ ความหมายหนึ่งสามารถดำเนินการคำนวณเดียวกันโดยการสร้างรายชื่อของดิและ iterating กว่าพวกเขาด้วย for วง (อย่างน้อยในโหมดกระตือรือร้นในโหมดลุยกราฟที่คุณต้องการต้อง tf.while ห่วง) อย่างไรก็ตาม การมีชุดการแจกแจงแบบกำหนดพารามิเตอร์ที่เหมือนกัน (อาจมีขนาดใหญ่) เหมือนกันนั้นถือเป็นเรื่องปกติอย่างยิ่ง และการใช้การคำนวณแบบเวคเตอร์ทุกครั้งที่ทำได้เป็นองค์ประกอบสำคัญในการคำนวณอย่างรวดเร็วโดยใช้ตัวเร่งฮาร์ดแวร์

การใช้กลุ่มอิสระเพื่อรวมกลุ่มกับเหตุการณ์

ในส่วนก่อนหน้านี้เราได้สร้าง b3 เดียว Distribution วัตถุที่เป็นตัวแทนสามพลิกเหรียญ ถ้าเราเรียกว่า b3.prob บนเวกเตอร์ \(v\)ที่ \(i\)'รายการ TH ก็น่าจะเป็นที่ \(i\)TH เหรียญนำค่า \(v[i]\)

สมมติว่าเราต้องการระบุการกระจายแบบ "ร่วม" แทนตัวแปรสุ่มอิสระจากตระกูลพื้นฐานเดียวกัน นี่คือวัตถุที่แตกต่างกันทางคณิตศาสตร์ในการที่สำหรับการจัดจำหน่ายใหม่นี้ prob บนเวกเตอร์ \(v\) จะกลับค่าเดียวที่เป็นตัวแทนของความน่าจะเป็นที่การตั้งค่าทั้งหมดของเหรียญตรงกับเวกเตอร์ \(v\)

เราจะทำสิ่งนี้ให้สำเร็จได้อย่างไร เราใช้ "ขั้นสูง" ที่เรียกว่าการจัดจำหน่าย Independent ซึ่งจะมีการจัดจำหน่ายและการกระจายผลผลิตใหม่ที่มีรูปร่างชุดย้ายไปรูปร่างเหตุการณ์:

b3_joint = tfd.Independent(b3, reinterpreted_batch_ndims=1)
b3_joint
<tfp.distributions.Independent 'IndependentBernoulli' batch_shape=[] event_shape=[3] dtype=int32>

เปรียบเทียบรูปร่างกับที่ของเดิม b3 :

b3
<tfp.distributions.Bernoulli 'Bernoulli' batch_shape=[3] event_shape=[] dtype=int32>

ตามที่สัญญาเราจะเห็นว่าที่ Independent ได้ย้ายรูปร่างชุดเป็นรูปเหตุการณ์: b3_joint คือการกระจายเดียว ( batch_shape = () ) มากกว่าพื้นที่จัดกิจกรรมสามมิติ ( event_shape = (3,) )

ลองตรวจสอบความหมาย:

b3_joint.prob([1, 1, 0])
<tf.Tensor: shape=(), dtype=float32, numpy=0.044999998>

อีกทางหนึ่งที่จะได้รับผลเดียวกันจะน่าจะคำนวณโดยใช้ b3 และจะลดลงด้วยตนเองโดยการคูณ (หรือในกรณีปกติที่น่าจะเข้าสู่ระบบที่มีการใช้ข้อสรุป):

tf.reduce_prod(b3.prob([1, 1, 0]))
<tf.Tensor: shape=(), dtype=float32, numpy=0.044999994>

Indpendent ช่วยให้ผู้ใช้ให้มากขึ้นอย่างชัดเจนแทนแนวคิดที่ต้องการ เรามองว่าสิ่งนี้มีประโยชน์อย่างยิ่ง แม้ว่าจะไม่จำเป็นอย่างยิ่งก็ตาม

เรื่องน่ารู้:

  • b3.sample และ b3_joint.sample มีการใช้ความคิดแตกต่างกัน แต่ผลจะแยกไม่ออก: ความแตกต่างระหว่างชุดของการกระจายอิสระและการกระจายเดียวที่สร้างขึ้นจากชุดโดยใช้ Independent แสดงขึ้นเมื่อคอมพิวเตอร์probabilitésไม่ได้เมื่อการสุ่มตัวอย่าง
  • MultivariateNormalDiag สามารถดำเนินนิดใช้เกลา Normal และ Independent กระจาย (มันไม่ได้ดำเนินการจริงวิธีนี้ แต่มันอาจจะเป็น)

ชุดของการแจกแจงแบบหลายตัวแปร

มาสร้างชุดของค่าปกติของตัวแปรหลายตัวแปรสองมิติที่มีความแปรปรวนร่วมเต็มสามแบบ:

nd_batch = tfd.MultivariateNormalFullCovariance(
    loc = [[0., 0.], [1., 1.], [2., 2.]],
    covariance_matrix = [[[1., .1], [.1, 1.]], 
                         [[1., .3], [.3, 1.]],
                         [[1., .5], [.5, 1.]]])
nd_batch
<tfp.distributions.MultivariateNormalFullCovariance 'MultivariateNormalFullCovariance' batch_shape=[3] event_shape=[2] dtype=float32>

เราเห็น batch_shape = (3,) จึงมีสามปกติหลายตัวแปรอิสระและ event_shape = (2,) เพื่อให้แต่ละหลายตัวแปรปกติเป็นสองมิติ ในตัวอย่างนี้ การแจกแจงแต่ละรายการไม่มีองค์ประกอบอิสระ

งานสุ่มตัวอย่าง:

nd_batch.sample(4)
<tf.Tensor: shape=(4, 3, 2), dtype=float32, numpy=
array([[[ 0.7367498 ,  2.730996  ],
        [-0.74080074, -0.36466932],
        [ 0.6516018 ,  0.9391426 ]],

       [[ 1.038303  ,  0.12231752],
        [-0.94788766, -1.204232  ],
        [ 4.059758  ,  3.035752  ]],

       [[ 0.56903946, -0.06875849],
        [-0.35127294,  0.5311631 ],
        [ 3.4635801 ,  4.565582  ]],

       [[-0.15989424, -0.25715637],
        [ 0.87479895,  0.97391707],
        [ 0.5211419 ,  2.32108   ]]], dtype=float32)>

ตั้งแต่ batch_shape = (3,) และ event_shape = (2,) , เราผ่านเมตริกซ์ของรูปร่าง (3, 2) เพื่อ log_prob :

nd_batch.log_prob([[0., 0.], [1., 1.], [2., 2.]])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-1.8328519, -1.7907217, -1.694036 ], dtype=float32)>

การกระจายเสียงหรือที่รู้จักว่าเหตุใดจึงสับสน?

สรุปจากสิ่งที่เราทำเพื่อให้ห่างไกลการจัดจำหน่ายทุกคนมีรูปร่างชุด B และรูปร่างเหตุการณ์ E อนุญาต BE ต้องเรียงต่อกันของรูปทรงเหตุการณ์:

  • สำหรับการกระจายเกลา univariate n และ b , BE = (). .
  • สำหรับสองมิติปกติหลายตัวแปร nd BE = (2).
  • สำหรับทั้ง b3 และ b3_joint , BE = (3).
  • สำหรับชุดของภาวะปกติหลายตัวแปร ndb , BE = (3, 2).

"กฎการประเมิน" ที่เราใช้จนถึงตอนนี้คือ:

  • ตัวอย่างที่มีการโต้แย้งไม่มีผลตอบแทนเมตริกซ์ที่มีรูปร่าง BE ; การสุ่มตัวอย่างด้วยสเกลา n ผลตอบแทนเป็น "n โดย BE " เมตริกซ์
  • prob และ log_prob ใช้เมตริกซ์ของรูปร่าง BE และกลับเป็นผลมาจากรูปร่าง B

ที่เกิดขึ้นจริง "กฎการประเมินผล" สำหรับ prob และ log_prob มีความซับซ้อนมากขึ้นในทางที่ข้อเสนอพลังงานที่มีศักยภาพและความเร็ว แต่ยังซับซ้อนและความท้าทาย กฎที่เกิดขึ้นจริง (หลัก) ที่โต้แย้งเพื่อ log_prob ต้อง broadcastable กับ BE ; มิติข้อมูล "พิเศษ" ใดๆ จะถูกเก็บไว้ในเอาต์พุต

ลองสำรวจความหมาย สำหรับ univariate ปกติ n , BE = () ดังนั้น log_prob คาดว่าสเกลา ถ้าเราผ่าน log_prob เมตริกซ์ที่มีรูปร่างไม่ว่างเปล่าเหล่านั้นแสดงเป็นมิติชุดในการส่งออก:

n = tfd.Normal(loc=0., scale=1.)
n
<tfp.distributions.Normal 'Normal' batch_shape=[] event_shape=[] dtype=float32>
n.log_prob(0.)
<tf.Tensor: shape=(), dtype=float32, numpy=-0.9189385>
n.log_prob([0.])
<tf.Tensor: shape=(1,), dtype=float32, numpy=array([-0.9189385], dtype=float32)>
n.log_prob([[0., 1.], [-1., 2.]])
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-0.9189385, -1.4189385],
       [-1.4189385, -2.9189386]], dtype=float32)>

เปิด Let 's ไปสองมิติปกติหลายตัวแปร nd (พารามิเตอร์การเปลี่ยนแปลงเพื่อเป็นตัวอย่าง):

nd = tfd.MultivariateNormalDiag(loc=[0., 1.], scale_diag=[1., 1.])
nd
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[] event_shape=[2] dtype=float32>

log_prob "คาดว่า" ข้อโต้แย้งที่มีรูปร่าง (2,) แต่มันจะยอมรับข้อโต้แย้งใด ๆ ที่ออกอากาศกับรูปร่างนี้:

nd.log_prob([0., 0.])
<tf.Tensor: shape=(), dtype=float32, numpy=-2.337877>

แต่เราสามารถผ่านใน "มากกว่า" ตัวอย่างและประเมินผลของพวกเขาทั้งหมด log_prob 's ในครั้งเดียว:

nd.log_prob([[0., 0.],
             [1., 1.],
             [2., 2.]])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-2.337877 , -2.337877 , -4.3378773], dtype=float32)>

เราอาจถ่ายทอดผ่านมิติเหตุการณ์ที่ดูไม่น่าดึงดูดใจน้อยกว่านี้:

nd.log_prob([0.])
<tf.Tensor: shape=(), dtype=float32, numpy=-2.337877>
nd.log_prob([[0.], [1.], [2.]])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-2.337877 , -2.337877 , -4.3378773], dtype=float32)>

การแพร่ภาพด้วยวิธีนี้เป็นผลมาจากการออกแบบ "เปิดใช้งานการออกอากาศทุกเมื่อที่ทำได้" ของเรา การใช้งานนี้ค่อนข้างขัดแย้งและอาจถูกลบใน TFP เวอร์ชันอนาคต

ทีนี้มาดูตัวอย่างเหรียญสามเหรียญอีกครั้ง:

b3 = tfd.Bernoulli(probs=[.3, .5, .7])

ที่นี่ใช้กระจายเสียงเพื่อเป็นตัวแทนของความน่าจะเป็นว่าแต่ละเหรียญขึ้นมาหัวจะง่ายมาก:

b3.prob([1])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.29999998, 0.5       , 0.7       ], dtype=float32)>

(เปรียบเทียบนี้เพื่อ b3.prob([1., 1., 1.]) ซึ่งเราจะต้องกลับสินค้าที่ b3 ได้รับการแนะนำ.)

ตอนนี้สมมติว่าเราต้องการที่จะรู้ว่าแต่ละเหรียญน่าจะเป็นเหรียญขึ้นมาหัวและความน่าจะเป็นมันมาถึงหาง เราสามารถจินตนาการถึงความพยายาม:

b3.log_prob([0, 1])

ขออภัย สิ่งนี้ทำให้เกิดข้อผิดพลาดกับการติดตามสแต็กที่ยาวและอ่านยาก b3 มี BE = (3) ดังนั้นเราจะต้องผ่าน b3.prob บางสิ่งบางอย่างกับ broadcastable (3,) [0, 1] มีรูปร่าง (2) จึงไม่ได้ออกอากาศและสร้างข้อผิดพลาด เราต้องพูดว่า:

b3.prob([[0], [1]])
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[0.7, 0.5, 0.3],
       [0.3, 0.5, 0.7]], dtype=float32)>

ทำไม? [[0], [1]] มีรูปร่าง (2, 1) ดังนั้นจึงเป็นกระบอกเสียงกับรูปร่าง (3) เพื่อให้มีรูปร่างที่ออกอากาศของ (2, 3)

การแพร่ภาพค่อนข้างมีประสิทธิภาพ: มีหลายกรณีที่อนุญาตให้ลดลำดับความสำคัญของปริมาณหน่วยความจำที่ใช้ และมักจะทำให้รหัสผู้ใช้สั้นลง อย่างไรก็ตาม การเขียนโปรแกรมด้วยอาจเป็นเรื่องยาก ถ้าคุณเรียก log_prob และได้รับข้อผิดพลาดความล้มเหลวที่จะออกอากาศเป็นเกือบตลอดเวลาปัญหา

ก้าวไกล

ในบทช่วยสอนนี้ เรา (หวังว่า) จะให้คำแนะนำง่ายๆ คำแนะนำบางประการสำหรับการไปต่อ:

  • event_shape , batch_shape และ sample_shape สามารถยศพล (ในการกวดวิชานี้พวกเขาอยู่เสมอทั้งเกลาหรือตำแหน่ง 1) สิ่งนี้จะเพิ่มพลัง แต่อาจนำไปสู่ความท้าทายในการเขียนโปรแกรมอีกครั้ง โดยเฉพาะอย่างยิ่งเมื่อมีการออกอากาศที่เกี่ยวข้อง สำหรับการดำน้ำลึกเพิ่มเติมลงในการจัดการรูปร่างให้ดูที่ การทำความเข้าใจ TensorFlow กระจายรูปร่าง
  • TFP รวมถึงสิ่งที่เป็นนามธรรมที่มีประสิทธิภาพที่รู้จักในฐานะ Bijectors ซึ่งร่วมกับ TransformedDistribution ถัวเฉลี่ยที่มีความยืดหยุ่นทาง compositional สามารถสร้างการกระจายใหม่ที่มีการแปลงผกผันของการกระจายที่มีอยู่ เราจะพยายามที่จะเขียนกวดวิชาในเร็ว ๆ นี้ แต่ในขณะเดียวกันให้ตรวจสอบ เอกสาร