ทำความเข้าใจรูปร่างการกระจาย TensorFlow

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

import tensorflow as tf
tf.compat.v2.enable_v2_behavior()

import tensorflow_probability as tfp
tfd = tfp.distributions
tfb = tfp.bijectors

พื้นฐาน

มีแนวคิดที่สำคัญสามประการที่เกี่ยวข้องกับรูปร่างการกระจาย TensorFlow:

  • รูปร่างเหตุการณ์อธิบายรูปร่างของวาดเดียวจากการจัดจำหน่ายนั้น มันอาจจะขึ้นอยู่กับข้ามมิติ สำหรับการกระจายเกลารูปร่างเหตุการณ์เป็น [] สำหรับ MultivariateNormal 5 มิติรูปเหตุการณ์เป็น [5]
  • รูปร่าง Batch อธิบายอิสระไม่กระจายเหมือนดึงอาคาเป็น "แบทช์" ของการกระจาย
  • รูปร่างตัวอย่างอธิบายอิสระกันกระจายดึงของแบตช์จากครอบครัวกระจาย

รูปร่างเหตุการณ์และรูปร่างชุดที่มีคุณสมบัติของ Distribution วัตถุในขณะที่รูปร่างของกลุ่มตัวอย่างมีความเกี่ยวข้องกับการเรียกร้องที่เฉพาะเจาะจงให้ sample หรือ log_prob

จุดประสงค์ของสมุดบันทึกเล่มนี้คือเพื่อแสดงแนวคิดเหล่านี้ผ่านตัวอย่าง ดังนั้นหากไม่ชัดเจนในทันที ก็ไม่ต้องกังวลไป!

อีกภาพรวมแนวคิดของแนวคิดเหล่านี้ให้ดู บล็อกโพสต์นี้

หมายเหตุเกี่ยวกับ TensorFlow Eager

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

การกระจายสเกลาร์

ในฐานะที่เราตั้งข้อสังเกตข้างต้น Distribution วัตถุได้กำหนดจัดกิจกรรมและรูปทรงชุด เราจะเริ่มต้นด้วยยูทิลิตี้เพื่ออธิบายการแจกแจง:

def describe_distributions(distributions):
  print('\n'.join([str(d) for d in distributions]))

ในส่วนนี้เราจะศึกษาการกระจายเกลา: การกระจายกับรูปร่างของเหตุการณ์ [] ตัวอย่างทั่วไปคือการกระจาย Poisson ระบุโดย rate :

poisson_distributions = [
    tfd.Poisson(rate=1., name='One Poisson Scalar Batch'),
    tfd.Poisson(rate=[1., 10., 100.], name='Three Poissons'),
    tfd.Poisson(rate=[[1., 10., 100.,], [2., 20., 200.]],
                name='Two-by-Three Poissons'),
    tfd.Poisson(rate=[1.], name='One Poisson Vector Batch'),
    tfd.Poisson(rate=[[1.]], name='One Poisson Expanded Batch')
]

describe_distributions(poisson_distributions)
tfp.distributions.Poisson("One_Poisson_Scalar_Batch", batch_shape=[], event_shape=[], dtype=float32)
tfp.distributions.Poisson("Three_Poissons", batch_shape=[3], event_shape=[], dtype=float32)
tfp.distributions.Poisson("Two_by_Three_Poissons", batch_shape=[2, 3], event_shape=[], dtype=float32)
tfp.distributions.Poisson("One_Poisson_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
tfp.distributions.Poisson("One_Poisson_Expanded_Batch", batch_shape=[1, 1], event_shape=[], dtype=float32)

การกระจาย Poisson คือการกระจายเกลาดังนั้นรูปร่างเหตุการณ์อยู่เสมอ [] หากเราระบุอัตราเพิ่มเติม อัตราเหล่านี้จะแสดงในรูปแบบชุดงาน ตัวอย่างคู่สุดท้ายน่าสนใจ: มีเพียงอัตราเดียวเท่านั้น แต่เนื่องจากอัตรานั้นฝังอยู่ในอาร์เรย์จำนวนน้อยที่มีรูปร่างไม่ว่างเปล่า รูปร่างนั้นจึงกลายเป็นรูปร่างเป็นชุด

การแจกแจงแบบปกติมาตรฐานก็เป็นสเกลาร์เช่นกัน รูปร่างเหตุการณ์มันคือ [] เช่นเดียวสำหรับ Poisson แต่เราจะเล่นกับมันเพื่อดูตัวอย่างแรกของเราในการกระจายเสียง ปกติจะถูกระบุโดยใช้ loc และ scale พารามิเตอร์:

normal_distributions = [
    tfd.Normal(loc=0., scale=1., name='Standard'),
    tfd.Normal(loc=[0.], scale=1., name='Standard Vector Batch'),
    tfd.Normal(loc=[0., 1., 2., 3.], scale=1., name='Different Locs'),
    tfd.Normal(loc=[0., 1., 2., 3.], scale=[[1.], [5.]],
               name='Broadcasting Scale')
]

describe_distributions(normal_distributions)
tfp.distributions.Normal("Standard", batch_shape=[], event_shape=[], dtype=float32)
tfp.distributions.Normal("Standard_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
tfp.distributions.Normal("Different_Locs", batch_shape=[4], event_shape=[], dtype=float32)
tfp.distributions.Normal("Broadcasting_Scale", batch_shape=[2, 4], event_shape=[], dtype=float32)

ตัวอย่างที่น่าสนใจดังกล่าวข้างต้นเป็น Broadcasting Scale กระจาย loc พารามิเตอร์มีรูปร่าง [4] และ scale พารามิเตอร์มีรูปร่าง [2, 1] ใช้ กฎกระจายเสียง Numpy รูปร่างชุดคือ [2, 4] เทียบเท่า ( แต่สง่างามน้อยลงและไม่แนะนำ) วิธีการกำหนด "Broadcasting Scale" กระจายจะเป็น:

describe_distributions(
    [tfd.Normal(loc=[[0., 1., 2., 3], [0., 1., 2., 3.]],
                scale=[[1., 1., 1., 1.], [5., 5., 5., 5.]])])
tfp.distributions.Normal("Normal", batch_shape=[2, 4], event_shape=[], dtype=float32)

เราสามารถเห็นได้ว่าเหตุใดสัญกรณ์การออกอากาศจึงมีประโยชน์ แม้ว่าจะเป็นแหล่งของอาการปวดหัวและข้อบกพร่อง

การสุ่มตัวอย่างการกระจายสเกลาร์

มีสองสิ่งหลักที่เราสามารถทำอะไรกับการกระจาย: เราสามารถ sample จากพวกเขาและเราสามารถคำนวณ log_prob s มาสำรวจตัวอย่างกันก่อน กฎพื้นฐานคือว่าเมื่อเราได้ลิ้มลองจากการกระจายส่งผลให้เมตริกซ์มีรูปร่าง [sample_shape, batch_shape, event_shape] ที่ batch_shape และ event_shape จะให้โดย Distribution วัตถุและ sample_shape ให้บริการโดยการเรียกร้องให้ sample สำหรับการกระจายเกลา event_shape = [] ดังนั้น Tensor กลับมาจากตัวอย่างจะมีรูปร่าง [sample_shape, batch_shape] มาลองดูกัน:

def describe_sample_tensor_shape(sample_shape, distribution):
    print('Sample shape:', sample_shape)
    print('Returned sample tensor shape:',
          distribution.sample(sample_shape).shape)

def describe_sample_tensor_shapes(distributions, sample_shapes):
    started = False
    for distribution in distributions:
      print(distribution)
      for sample_shape in sample_shapes:
        describe_sample_tensor_shape(sample_shape, distribution)
      print()

sample_shapes = [1, 2, [1, 5], [3, 4, 5]]
describe_sample_tensor_shapes(poisson_distributions, sample_shapes)
tfp.distributions.Poisson("One_Poisson_Scalar_Batch", batch_shape=[], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1,)
Sample shape: 2
Returned sample tensor shape: (2,)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5)

tfp.distributions.Poisson("Three_Poissons", batch_shape=[3], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 3)
Sample shape: 2
Returned sample tensor shape: (2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 3)

tfp.distributions.Poisson("Two_by_Three_Poissons", batch_shape=[2, 3], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

tfp.distributions.Poisson("One_Poisson_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 1)
Sample shape: 2
Returned sample tensor shape: (2, 1)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 1)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 1)

tfp.distributions.Poisson("One_Poisson_Expanded_Batch", batch_shape=[1, 1], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 1, 1)
Sample shape: 2
Returned sample tensor shape: (2, 1, 1)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 1, 1)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 1, 1)
describe_sample_tensor_shapes(normal_distributions, sample_shapes)
tfp.distributions.Normal("Standard", batch_shape=[], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1,)
Sample shape: 2
Returned sample tensor shape: (2,)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5)

tfp.distributions.Normal("Standard_Vector_Batch", batch_shape=[1], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 1)
Sample shape: 2
Returned sample tensor shape: (2, 1)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 1)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 1)

tfp.distributions.Normal("Different_Locs", batch_shape=[4], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 4)
Sample shape: 2
Returned sample tensor shape: (2, 4)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 4)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 4)

tfp.distributions.Normal("Broadcasting_Scale", batch_shape=[2, 4], event_shape=[], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 4)
Sample shape: 2
Returned sample tensor shape: (2, 2, 4)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 4)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 4)

ที่เกี่ยวกับสิ่งที่มีคือการพูดเกี่ยวกับ sample : กลับเทนเซอร์ตัวอย่างมีรูปร่าง [sample_shape, batch_shape, event_shape]

คอมพิวเตอร์ log_prob สำหรับเกลากระจาย

ตอนนี้ลองมาดูที่ log_prob ซึ่งค่อนข้างยุ่งยาก log_prob จะเป็นใส่ (ไม่ว่าง) เมตริกซ์เป็นตัวแทนของสถานที่ตั้ง (s) ที่การคำนวณ log_prob สำหรับการจัดจำหน่าย ในกรณีที่ตรงไปตรงมามากที่สุดเมตริกซ์นี้จะมีรูปร่างของรูปแบบ [sample_shape, batch_shape, event_shape] ที่ batch_shape และ event_shape การจับคู่ชุดและเหตุการณ์รูปร่างของการกระจาย จำอีกครั้งว่าสำหรับการกระจายเกลา event_shape = [] ดังนั้นเมตริกซ์การป้อนข้อมูลที่มีรูปร่าง [sample_shape, batch_shape] ในกรณีนี้เราได้รับกลับมาเมตริกซ์ของรูปร่าง [sample_shape, batch_shape] :

three_poissons = tfd.Poisson(rate=[1., 10., 100.], name='Three Poissons')
three_poissons
<tfp.distributions.Poisson 'Three_Poissons' batch_shape=[3] event_shape=[] dtype=float32>
three_poissons.log_prob([[1., 10., 100.], [100., 10., 1]])  # sample_shape is [2].
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -2.0785608,   -3.2223587],
       [-364.73938  ,   -2.0785608,  -95.39484  ]], dtype=float32)>
three_poissons.log_prob([[[[1., 10., 100.], [100., 10., 1.]]]])  # sample_shape is [1, 1, 2].
<tf.Tensor: shape=(1, 1, 2, 3), dtype=float32, numpy=
array([[[[  -1.       ,   -2.0785608,   -3.2223587],
         [-364.73938  ,   -2.0785608,  -95.39484  ]]]], dtype=float32)>

หมายเหตุวิธีการในตัวอย่างแรกเข้าและส่งออกมีรูปร่าง [2, 3] และในตัวอย่างที่สองพวกเขามีรูปร่าง [1, 1, 2, 3]

พูดได้แค่นี้ถ้าไม่ได้ออกอากาศ นี่คือกฎเมื่อเราคำนึงถึงการออกอากาศ เราอธิบายโดยทั่วไปและสังเกตการแจกแจงแบบง่ายสำหรับการแจกแจงสเกลาร์:

  1. กำหนด n = len(batch_shape) + len(event_shape) (สำหรับการกระจายเกลา len(event_shape)=0 .)
  2. หากอินพุตเมตริกซ์ t มีน้อยกว่า n มิติแผ่นรูปร่างของมันโดยการเพิ่มขนาดของขนาด 1 ด้านซ้ายจนกว่าจะมีตรง n มิติ โทรที่เกิดเมตริกซ์ t'
  3. ออกอากาศ n มิติขวาสุดของ t' กับ [batch_shape, event_shape] ของการกระจายที่คุณกำลังคำนวณ log_prob สำหรับ ในรายละเอียดเพิ่มเติมได้ที่: สำหรับมิติที่ t' แล้วตรงกับการกระจายการไม่ทำอะไรเลยและสำหรับขนาดที่ t' มีซิงเกิลที่ซ้ำว่าเดี่ยวจำนวนที่เหมาะสมของเวลา สถานการณ์อื่นใดเป็นข้อผิดพลาด (สำหรับการกระจายเกลาเราออกอากาศกับ batch_shape ตั้งแต่ event_shape = [] .)
  4. ตอนนี้เราจนสามารถคำนวณ log_prob เมตริกซ์ที่เกิดจะมีรูปร่าง [sample_shape, batch_shape] ที่ sample_shape ถูกกำหนดให้เป็นมิติใด ๆ ของ t หรือ t' ไปทางซ้ายของ n -rightmost ขนาด: sample_shape = shape(t)[:-n]

นี่อาจเป็นเรื่องยุ่งเหยิงหากคุณไม่รู้ว่ามันหมายถึงอะไร ลองมาดูตัวอย่างกัน:

three_poissons.log_prob([10.])
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([-16.104412 ,  -2.0785608, -69.05272  ], dtype=float32)>

เมตริกซ์ [10.] (ที่มีรูปร่าง [1] ) จะออกอากาศทั่ว batch_shape 3 ดังนั้นเราประเมินความน่าจะบันทึกทั้งสาม Poissons' ที่คุ้มค่า 10

three_poissons.log_prob([[[1.], [10.]], [[100.], [1000.]]])
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[-1.0000000e+00, -7.6974149e+00, -9.5394836e+01],
        [-1.6104412e+01, -2.0785608e+00, -6.9052719e+01]],

       [[-3.6473938e+02, -1.4348087e+02, -3.2223587e+00],
        [-5.9131279e+03, -3.6195427e+03, -1.4069575e+03]]], dtype=float32)>

ในตัวอย่างข้างต้น, เมตริกซ์การป้อนข้อมูลที่มีรูปร่าง [2, 2, 1] ในขณะที่วัตถุกระจายมีรูปร่างชุด 3. ดังนั้นสำหรับแต่ละ [2, 2] ขนาดตัวอย่างค่าเดียวที่ให้บริการได้รับ broadcats แต่ละ ของปัวซองทั้งสาม

วิธีที่มีประโยชน์อาจจะคิดว่ามันเพราะ three_poissons มี batch_shape = [2, 3] , เรียกร้องให้ log_prob ต้องใช้เทนเซอร์ที่มีมิติสุดท้ายคือ 1 หรือ 3; อย่างอื่นเป็นข้อผิดพลาด (กฎกระจายเสียง numpy รักษากรณีพิเศษของสเกลาเป็นทั้งหมดเทียบเท่ากับ Tensor ของรูปร่าง [1] .)

ทดสอบลองสับของเราด้วยการเล่นกับการกระจาย Poisson ที่ซับซ้อนมากขึ้นด้วย batch_shape = [2, 3] :

poisson_2_by_3 = tfd.Poisson(
    rate=[[1., 10., 100.,], [2., 20., 200.]],
    name='Two-by-Three Poissons')
poisson_2_by_3.log_prob(1.)
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -17.004269 , -194.70169  ]], dtype=float32)>
poisson_2_by_3.log_prob([1.])  # Exactly equivalent to above, demonstrating the scalar special case.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -17.004269 , -194.70169  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 1., 1.], [1., 1., 1.]])  # Another way to write the same thing. No broadcasting.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -17.004269 , -194.70169  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 10., 100.]])  # Input is [1, 3] broadcast to [2, 3].
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[ -1.       ,  -2.0785608,  -3.2223587],
       [ -1.3068528,  -5.14709  , -33.90767  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 10., 100.], [1., 10., 100.]])  # Equivalent to above. No broadcasting.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[ -1.       ,  -2.0785608,  -3.2223587],
       [ -1.3068528,  -5.14709  , -33.90767  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1., 1., 1.], [2., 2., 2.]])  # No broadcasting.
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -14.701683 , -190.09653  ]], dtype=float32)>
poisson_2_by_3.log_prob([[1.], [2.]])  # Equivalent to above. Input shape [2, 1] broadcast to [2, 3].
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [  -1.3068528,  -14.701683 , -190.09653  ]], dtype=float32)>

ตัวอย่างข้างต้นเกี่ยวข้องกับการแพร่ภาพเป็นกลุ่ม แต่รูปร่างตัวอย่างว่างเปล่า สมมติว่าเรามีชุดของค่า และเราต้องการรับความน่าจะเป็นของบันทึกของแต่ละค่าที่แต่ละจุดในชุดงาน เราสามารถทำได้ด้วยตนเอง:

poisson_2_by_3.log_prob([[[1., 1., 1.], [1., 1., 1.]], [[2., 2., 2.], [2., 2., 2.]]])  # Input shape [2, 2, 3].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

หรือเราอาจปล่อยให้การแพร่ภาพจัดการมิติแบตช์สุดท้าย:

poisson_2_by_3.log_prob([[[1.], [1.]], [[2.], [2.]]])  # Input shape [2, 2, 1].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

นอกจากนี้เรายังสามารถ (อาจจะค่อนข้างเป็นธรรมชาติน้อยกว่า) ให้การออกอากาศจัดการเฉพาะมิติชุดแรก:

poisson_2_by_3.log_prob([[[1., 1., 1.]], [[2., 2., 2.]]])  # Input shape [2, 1, 3].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

หรือเราสามารถปล่อยให้ออกอากาศจับทั้งชุดขนาด:

poisson_2_by_3.log_prob([[[1.]], [[2.]]])  # Input shape [2, 1, 1].
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

ด้านบนทำงานได้ดีเมื่อเรามีเพียงสองค่าที่เราต้องการ แต่สมมติว่าเรามีรายการค่าจำนวนมากที่เราต้องการประเมินในทุกจุดของชุดงาน สำหรับสิ่งนั้น สัญกรณ์ต่อไปนี้ ซึ่งเพิ่มมิติพิเศษขนาด 1 ไปทางด้านขวาของรูปร่าง มีประโยชน์อย่างยิ่ง:

poisson_2_by_3.log_prob(tf.constant([1., 2.])[..., tf.newaxis, tf.newaxis])
<tf.Tensor: shape=(2, 2, 3), dtype=float32, numpy=
array([[[  -1.       ,   -7.697415 ,  -95.39484  ],
        [  -1.3068528,  -17.004269 , -194.70169  ]],

       [[  -1.6931472,   -6.087977 ,  -91.48282  ],
        [  -1.3068528,  -14.701683 , -190.09653  ]]], dtype=float32)>

นี่คือตัวอย่างของ สัญกรณ์ชิ้น strided ซึ่งเป็นมูลค่ารู้

จะกลับไป three_poissons เพื่อความสมบูรณ์ตัวอย่างลักษณะเดียวกันเช่น:

three_poissons.log_prob([[1.], [10.], [50.], [100.]])
<tf.Tensor: shape=(4, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [ -16.104412 ,   -2.0785608,  -69.05272  ],
       [-149.47777  ,  -43.34851  ,  -18.219261 ],
       [-364.73938  , -143.48087  ,   -3.2223587]], dtype=float32)>
three_poissons.log_prob(tf.constant([1., 10., 50., 100.])[..., tf.newaxis])  # Equivalent to above.
<tf.Tensor: shape=(4, 3), dtype=float32, numpy=
array([[  -1.       ,   -7.697415 ,  -95.39484  ],
       [ -16.104412 ,   -2.0785608,  -69.05272  ],
       [-149.47777  ,  -43.34851  ,  -18.219261 ],
       [-364.73938  , -143.48087  ,   -3.2223587]], dtype=float32)>

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

ตอนนี้เราเปลี่ยนเป็นการแจกแจงหลายตัวแปรซึ่งมีรูปร่างเหตุการณ์ที่ไม่ว่างเปล่า ลองดูการแจกแจงพหุนามกัน

multinomial_distributions = [
    # Multinomial is a vector-valued distribution: if we have k classes,
    # an individual sample from the distribution has k values in it, so the
    # event_shape is `[k]`.
    tfd.Multinomial(total_count=100., probs=[.5, .4, .1],
                    name='One Multinomial'),
    tfd.Multinomial(total_count=[100., 1000.], probs=[.5, .4, .1],
                    name='Two Multinomials Same Probs'),
    tfd.Multinomial(total_count=100., probs=[[.5, .4, .1], [.1, .2, .7]],
                    name='Two Multinomials Same Counts'),
    tfd.Multinomial(total_count=[100., 1000.],
                    probs=[[.5, .4, .1], [.1, .2, .7]],
                    name='Two Multinomials Different Everything')

]

describe_distributions(multinomial_distributions)
tfp.distributions.Multinomial("One_Multinomial", batch_shape=[], event_shape=[3], dtype=float32)
tfp.distributions.Multinomial("Two_Multinomials_Same_Probs", batch_shape=[2], event_shape=[3], dtype=float32)
tfp.distributions.Multinomial("Two_Multinomials_Same_Counts", batch_shape=[2], event_shape=[3], dtype=float32)
tfp.distributions.Multinomial("Two_Multinomials_Different_Everything", batch_shape=[2], event_shape=[3], dtype=float32)

หมายเหตุวิธีการในสามตัวอย่างที่ผ่านมา batch_shape อยู่เสมอ [2] แต่เราสามารถใช้ออกอากาศทั้งมีการใช้ร่วมกัน total_count หรือใช้ร่วมกัน probs (หรือไม่) เพราะภายใต้ประทุนที่พวกเขามีการออกอากาศจะมีรูปร่างเหมือนกัน

การสุ่มตัวอย่างตรงไปตรงมา จากสิ่งที่เรารู้อยู่แล้ว:

describe_sample_tensor_shapes(multinomial_distributions, sample_shapes)
tfp.distributions.Multinomial("One_Multinomial", batch_shape=[], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 3)
Sample shape: 2
Returned sample tensor shape: (2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 3)

tfp.distributions.Multinomial("Two_Multinomials_Same_Probs", batch_shape=[2], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

tfp.distributions.Multinomial("Two_Multinomials_Same_Counts", batch_shape=[2], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

tfp.distributions.Multinomial("Two_Multinomials_Different_Everything", batch_shape=[2], event_shape=[3], dtype=float32)
Sample shape: 1
Returned sample tensor shape: (1, 2, 3)
Sample shape: 2
Returned sample tensor shape: (2, 2, 3)
Sample shape: [1, 5]
Returned sample tensor shape: (1, 5, 2, 3)
Sample shape: [3, 4, 5]
Returned sample tensor shape: (3, 4, 5, 2, 3)

ความน่าจะเป็นของบันทึกการคำนวณนั้นตรงไปตรงมาพอๆ กัน มาลองใช้ตัวอย่างกับการแจกแจงแบบปกติหลายตัวแปรในแนวทแยงกัน (พหุนามไม่ค่อยเป็นมิตรกับการออกอากาศ เนื่องจากข้อจำกัดในการนับและความน่าจะเป็นหมายถึงการออกอากาศมักจะสร้างค่าที่ยอมรับไม่ได้) เราจะใช้ชุดการแจกแจงแบบ 3 มิติ 2 ชุดที่มีค่าเฉลี่ยเท่ากันแต่สเกลต่างกัน (ส่วนเบี่ยงเบนมาตรฐาน):

two_multivariate_normals = tfd.MultivariateNormalDiag(loc=[1., 2., 3.], scale_identity_multiplier=[1., 2.])
two_multivariate_normals
<tfp.distributions.MultivariateNormalDiag 'MultivariateNormalDiag' batch_shape=[2] event_shape=[3] dtype=float32>

(หมายเหตุว่าแม้ว่าเราใช้การแจกแจงที่ชั่งน้ำหนักเป็นทวีคูณของตัวตนที่นี้ไม่ได้เป็นข้อ จำกัด ในการที่เราสามารถผ่าน scale แทน scale_identity_multiplier .)

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

two_multivariate_normals.log_prob([[[1., 2., 3.]], [[3., 4., 5.]]])  # Input has shape [2,1,3].
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-2.7568154, -4.836257 ],
       [-8.756816 , -6.336257 ]], dtype=float32)>

ว่าเท่าที่เราสามารถใช้ https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/strided-slice แทรกรูปร่างพิเศษ = 1 มิติในช่วงกลางของค่าคงที่:

two_multivariate_normals.log_prob(
    tf.constant([[1., 2., 3.], [3., 4., 5.]])[:, tf.newaxis, :])  # Equivalent to above.
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[-2.7568154, -4.836257 ],
       [-8.756816 , -6.336257 ]], dtype=float32)>

ในทางกลับกันถ้าเราทำไม่ได้แทรกมิติพิเศษเราผ่าน [1., 2., 3.] ไปยังจุดที่ชุดแรกและ [3., 4., 5.] ที่สอง:

two_multivariate_normals.log_prob(tf.constant([[1., 2., 3.], [3., 4., 5.]]))
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-2.7568154, -6.336257 ], dtype=float32)>

เทคนิคการจัดการรูปร่าง

The Reshape Bijector

Reshape bijector สามารถใช้ในการก่อร่างใหม่ event_shape ของการกระจายที่ มาดูตัวอย่างกัน:

six_way_multinomial = tfd.Multinomial(total_count=1000., probs=[.3, .25, .2, .15, .08, .02])
six_way_multinomial
<tfp.distributions.Multinomial 'Multinomial' batch_shape=[] event_shape=[6] dtype=float32>

เราได้สร้างพหุนามที่มีรูปร่างกรณีของ [6] Reshape Bijector ช่วยให้เราในการรักษานี้เช่นการกระจายกับรูปร่างกรณีที่มีการ [2, 3]

Bijector หมายถึงอนุพันธ์หนึ่งต่อหนึ่งฟังก์ชั่นในเซตเปิด \({\mathbb R}^n\)Bijectors จะใช้ร่วมกับ TransformedDistribution ซึ่งรูปแบบการกระจาย \(p(y)\) ในแง่ของการกระจายฐาน \(p(x)\) และ Bijector ที่แสดงถึง \(Y = g(X)\)มาดูการทำงานกัน:

transformed_multinomial = tfd.TransformedDistribution(
    distribution=six_way_multinomial,
    bijector=tfb.Reshape(event_shape_out=[2, 3]))
transformed_multinomial
<tfp.distributions.TransformedDistribution 'reshapeMultinomial' batch_shape=[] event_shape=[2, 3] dtype=float32>
six_way_multinomial.log_prob([500., 100., 100., 150., 100., 50.])
<tf.Tensor: shape=(), dtype=float32, numpy=-178.22021>
transformed_multinomial.log_prob([[500., 100., 100.], [150., 100., 50.]])
<tf.Tensor: shape=(), dtype=float32, numpy=-178.22021>

นี่คือสิ่งที่เพียง Reshape bijector สามารถทำได้: มันไม่สามารถเปิดมิติเหตุการณ์เข้าไปในมิติชุดหรือในทางกลับกัน

การกระจายอิสระ

Independent กระจายใช้ในการรักษาคอลเลกชันของอิสระที่ไม่จำเป็นต้องเหมือนกัน (aka ชุดของก) การกระจายเป็นกระจายเดียว เพิ่มเติมรัดกุม Independent ช่วยให้การแปลงมิติใน batch_shape มิติใน event_shape เราจะอธิบายโดยตัวอย่าง:

two_by_five_bernoulli = tfd.Bernoulli(
    probs=[[.05, .1, .15, .2, .25], [.3, .35, .4, .45, .5]],
    name="Two By Five Bernoulli")
two_by_five_bernoulli
<tfp.distributions.Bernoulli 'Two_By_Five_Bernoulli' batch_shape=[2, 5] event_shape=[] dtype=int32>

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

pattern = [[1., 0., 0., 1., 0.], [0., 0., 1., 1., 1.]]
two_by_five_bernoulli.log_prob(pattern)
<tf.Tensor: shape=(2, 5), dtype=float32, numpy=
array([[-2.9957323 , -0.10536052, -0.16251892, -1.609438  , -0.2876821 ],
       [-0.35667497, -0.4307829 , -0.9162907 , -0.7985077 , -0.6931472 ]],
      dtype=float32)>

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

two_sets_of_five = tfd.Independent(
    distribution=two_by_five_bernoulli,
    reinterpreted_batch_ndims=1,
    name="Two Sets Of Five")
two_sets_of_five
<tfp.distributions.Independent 'Two_Sets_Of_Five' batch_shape=[2] event_shape=[5] dtype=int32>

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

two_sets_of_five.log_prob(pattern)
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-5.160732 , -3.1954036], dtype=float32)>

เราสามารถไปได้ไกลกว่าและใช้ Independent ในการสร้างการกระจายที่แต่ละเหตุการณ์เป็นชุดของสองโดยห้า Bernoulli ของ:

one_set_of_two_by_five = tfd.Independent(
    distribution=two_by_five_bernoulli, reinterpreted_batch_ndims=2,
    name="One Set Of Two By Five")
one_set_of_two_by_five.log_prob(pattern)
<tf.Tensor: shape=(), dtype=float32, numpy=-8.356134>

มันน่าสังเกตว่าจากมุมมองของ sample โดยใช้ Independent ไม่มีอะไรเปลี่ยนแปลง:

describe_sample_tensor_shapes(
    [two_by_five_bernoulli,
     two_sets_of_five,
     one_set_of_two_by_five],
    [[3, 5]])
tfp.distributions.Bernoulli("Two_By_Five_Bernoulli", batch_shape=[2, 5], event_shape=[], dtype=int32)
Sample shape: [3, 5]
Returned sample tensor shape: (3, 5, 2, 5)

tfp.distributions.Independent("Two_Sets_Of_Five", batch_shape=[2], event_shape=[5], dtype=int32)
Sample shape: [3, 5]
Returned sample tensor shape: (3, 5, 2, 5)

tfp.distributions.Independent("One_Set_Of_Two_By_Five", batch_shape=[], event_shape=[2, 5], dtype=int32)
Sample shape: [3, 5]
Returned sample tensor shape: (3, 5, 2, 5)

ขณะที่การออกกำลังกายที่พรากจากกันสำหรับผู้อ่านเราขอแนะนำให้พิจารณาความแตกต่างและความคล้ายคลึงกันระหว่างชุดเวกเตอร์ของ Normal กระจายและ MultivariateNormalDiag กระจายจากการสุ่มตัวอย่างและความน่าจะบันทึกมุมมอง วิธีการที่เราสามารถใช้ Independent ที่จะสร้าง MultivariateNormalDiag จากชุดของ Normal หรือไม่? (โปรดสังเกตว่า MultivariateNormalDiag ไม่ได้ดำเนินการจริงวิธีนี้.)