Phân phối TensorFlow: Giới thiệu nhẹ nhàng

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Trong sổ tay này, chúng ta sẽ khám phá Phân phối TensorFlow (viết tắt là TFD). Mục tiêu của máy tính xách tay này là giúp bạn dễ dàng bắt đầu quá trình học tập, bao gồm cả việc hiểu cách xử lý của TFD đối với các hình dạng tensor. Sổ tay này cố gắng trình bày các ví dụ trước thay vì các khái niệm trừu tượng. Chúng tôi sẽ trình bày các cách dễ dàng chuẩn tắc để thực hiện mọi việc trước tiên và lưu chế độ xem tóm tắt chung nhất cho đến khi kết thúc. Nếu bạn thuộc tuýp người thích một trừu tượng hơn và tài liệu tham khảo theo phong cách hướng dẫn, kiểm tra Hiểu TensorFlow phân phối Shapes . Nếu bạn có thắc mắc về vật liệu ở đây, đừng ngần ngại liên lạc (hoặc tham gia) trong danh sách gửi thư xác suất TensorFlow . Chúng tôi rất vui khi được trợ giúp.

Trước khi bắt đầu, chúng ta cần nhập các thư viện thích hợp. Thư viện chung của chúng tôi là tensorflow_probability . Theo quy ước, chúng ta thường đề cập đến các thư viện phân phối như tfd .

Tensorflow Háo hức là một môi trường thực thi mệnh lệnh cho TensorFlow. Trong TensorFlow háo hức, mọi hoạt động TF ngay lập tức được đánh giá và tạo ra kết quả. Điều này trái ngược với chế độ "đồ thị" tiêu chuẩn của TensorFlow, trong đó các phép toán TF thêm các nút vào một đồ thị mà sau đó được thực thi. Toàn bộ sổ tay này được viết bằng TF Eager, mặc dù không có khái niệm nào được trình bày ở đây dựa vào đó và TFP có thể được sử dụng trong chế độ biểu đồ.

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

Phân phối đơn biến cơ bản

Hãy đi sâu vào và tạo phân phối chuẩn:

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

Chúng tôi có thể vẽ một mẫu từ nó:

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

Chúng tôi có thể vẽ nhiều mẫu:

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

Chúng ta có thể đánh giá một bản ghi log:

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

Chúng tôi có thể đánh giá nhiều xác suất nhật ký:

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

Chúng tôi có một loạt các bản phân phối. Hãy thử Bernoulli:

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)>

Phân phối đa biến

Chúng tôi sẽ tạo một chuẩn tắc đa biến với hiệp phương sai đường chéo:

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

So sánh điều này với bình thường đơn biến mà chúng ta đã tạo trước đó, có gì khác biệt?

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

Chúng tôi thấy rằng các đơn biến bình thường có event_shape của () , cho thấy đó là một phân phối vô hướng. Bình thường đa biến có event_shape của 2 , cho thấy cơ bản [không gian sự kiện] (https://en.wikipedia.org/wiki/Event_ (probability_theory)) của phân phối này là hai chiều.

Lấy mẫu hoạt động giống như trước đây:

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>

Các chuẩn tắc đa biến nói chung không có hiệp phương sai đường chéo. TFD cung cấp nhiều cách để tạo các chuẩn tắc đa biến, bao gồm đặc tả hiệp phương sai đầy đủ, chúng tôi sử dụng ở đây.

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

Nhiều phân phối

Bản phân phối Bernoulli đầu tiên của chúng tôi đại diện cho việc lật một đồng xu công bằng duy nhất. Chúng tôi cũng có thể tạo ra một loạt các bản phân phối Bernoulli độc lập, mỗi với các thông số riêng của họ, trong một đơn Distribution đối tượng:

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

Điều quan trọng là phải rõ ràng điều này có nghĩa là gì. Các cuộc gọi trên định nghĩa ba phân phối Bernoulli độc lập, diễn ra được chứa trong cùng Python Distribution đối tượng. Ba bản phân phối không thể được thao tác riêng lẻ. Lưu ý cách batch_shape(3,) , cho thấy một loạt ba phân phối, và event_shape() , cho thấy sự phân bố cá nhân có một không gian sự kiện đơn biến.

Nếu chúng ta gọi là sample , chúng tôi có được một mẫu từ cả ba:

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)>

Nếu chúng ta gọi là prob , (điều này có ngữ nghĩa hình dạng giống như log_prob ; chúng tôi sử dụng prob với những ví dụ Bernoulli nhỏ cho rõ ràng, mặc dù log_prob thường ưa thích trong các ứng dụng), chúng tôi có thể vượt qua nó một vector và đánh giá khả năng của mỗi đồng tiền có lãi suất giá trị mà :

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

Tại sao API bao gồm hình dạng lô? Ngữ nghĩa, người ta có thể thực hiện các tính toán tương tự bằng cách tạo ra một danh sách các bản phân phối và lặp qua chúng với một for vòng lặp (ít nhất là trong chế độ háo hức, trong chế độ TF đồ thị, bạn sẽ cần một tf.while loop). Tuy nhiên, việc có một tập hợp các phân phối được tham số hóa giống hệt nhau là cực kỳ phổ biến và việc sử dụng các phép tính vectơ hóa bất cứ khi nào có thể là một thành phần quan trọng để có thể thực hiện các phép tính nhanh chóng bằng cách sử dụng bộ tăng tốc phần cứng.

Sử dụng độc lập để tổng hợp các lô thành sự kiện

Trong phần trước, chúng tôi tạo ra b3 , một đơn Distribution đối tượng đại diện cho ba đồng xu flips. Nếu chúng ta gọi là b3.prob trên một vector \(v\), các \(i\)'th entry là xác suất mà \(i\)đồng xu thứ mất giá trị \(v[i]\).

Giả sử thay vào đó, chúng tôi muốn chỉ định một phân phối "chung" trên các biến ngẫu nhiên độc lập từ cùng một họ cơ bản. Đây là một đối tượng khác nhau về mặt toán học, trong đó để phân phối mới này, prob trên một vector \(v\) sẽ trả về một giá trị duy nhất đại diện cho xác suất mà toàn bộ các đồng tiền phù hợp với vector \(v\).

Chúng ta đạt được điều này như thế nào? Chúng tôi sử dụng một "bậc cao" phân phối được gọi là Independent , trong đó có một bản phân phối và mang lại một bản phân phối mới với hình dạng hàng loạt chuyển sang hình sự kiện:

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

Hãy so sánh hình dạng như của bản gốc b3 :

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

Như đã hứa, chúng ta thấy rằng Independent đã chuyển hình dạng hàng loạt vào hình dạng sự kiện: b3_joint là một phân phối duy nhất ( batch_shape = () ) trong một không gian sự kiện ba chiều ( event_shape = (3,) ).

Hãy kiểm tra ngữ nghĩa:

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

Một cách khác để có được kết quả tương tự sẽ được xác suất tính toán sử dụng b3 và làm giảm bằng tay bằng cách nhân (hoặc, trong trường hợp thông thường hơn nơi xác suất log được sử dụng, tổng hợp):

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

Indpendent cho phép người sử dụng để đại diện cho một cách rõ ràng hơn khái niệm mong muốn. Chúng tôi coi điều này là cực kỳ hữu ích, mặc dù nó không hoàn toàn cần thiết.

Những điều lý thú:

  • b3.sampleb3_joint.sample có triển khai khái niệm khác nhau, nhưng kết quả đầu ra không thể phân biệt: sự khác biệt giữa một loạt các bản phân phối độc lập và phân phối duy nhất được tạo ra từ hàng loạt sử dụng Independent chương trình lên khi tính probabilites, không phải khi lấy mẫu.
  • MultivariateNormalDiag thể được thực hiện bằng cách sử dụng trivially vô hướng NormalIndependent phân phối (nó không phải là thực sự thực hiện theo cách này, nhưng nó có thể được).

Các mẻ chưng cất đa biến

Hãy tạo một loạt ba chuẩn tắc đa biến hai chiều hiệp phương sai đầy đủ:

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>

Chúng ta thấy batch_shape = (3,) , vì vậy có ba normals đa biến độc lập, và event_shape = (2,) , vì vậy mỗi đa biến bình thường là hai chiều. Trong ví dụ này, các bản phân phối riêng lẻ không có các phần tử độc lập.

Công việc lấy mẫu:

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)>

Kể từ batch_shape = (3,)event_shape = (2,) , chúng tôi vượt qua một tensor hình dạng (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)>

Phát thanh truyền hình, hay còn gọi là Tại sao điều này lại khó hiểu?

Trừu tượng ra những gì chúng tôi đã làm cho đến nay, tất cả các phân phối có một hình dạng batch B và một hình dạng sự kiện E . Để cho BE là nối của các hình dạng sự kiện:

  • Đối với các bản phân phối vô hướng đơn biến nb , BE = (). .
  • Đối với normals đa biến hai chiều nd . BE = (2).
  • Đối với cả hai b3b3_joint , BE = (3).
  • Đối với các lô normals đa biến ndb , BE = (3, 2).

"Các quy tắc đánh giá" mà chúng tôi đã sử dụng cho đến nay là:

  • Mẫu không có lập luận trả về một tensor với hình dạng BE ; lấy mẫu với một vô hướng n lợi nhuận một "n bởi BE " tensor.
  • problog_prob mất một tensor hình dạng BE và trả về một kết quả của hình dạng B .

"Quy tắc đánh giá" thực tế cho problog_prob trở nên phức tạp hơn, trong một cách mà cung cấp tiềm năng sức mạnh và tốc độ mà còn phức tạp và thách thức. Nguyên tắc thực tế là (cơ bản) mà lập luận để log_prob phải broadcastable chống BE ; bất kỳ kích thước "bổ sung" nào được giữ nguyên trong đầu ra.

Hãy cùng khám phá hàm ý. Đối với bình thường đơn biến n , BE = () , vì vậy log_prob hy vọng một vô hướng. Nếu chúng ta vượt qua log_prob một tensor với hình dạng không trống, những hiển thị như kích thước hàng loạt trong đầu ra:

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)>

Hãy chuyển sang bình thường đa biến hai chiều nd (thông số thay đổi cho mục đích minh họa):

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

log_prob "hy vọng" một cuộc tranh cãi với hình dạng (2,) , nhưng nó sẽ chấp nhận bất cứ lập luận rằng chương trình phát sóng chống lại hình dạng này:

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

Nhưng chúng ta có thể vượt qua trong "nhiều hơn" ví dụ, và đánh giá tất cả họ log_prob là cùng một lúc:

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)>

Có lẽ ít hấp dẫn hơn, chúng tôi có thể phát trên các thứ nguyên của sự kiện:

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)>

Phát sóng theo cách này là kết quả của thiết kế "cho phép phát sóng bất cứ khi nào có thể" của chúng tôi; cách sử dụng này có phần gây tranh cãi và có thể bị loại bỏ trong phiên bản TFP trong tương lai.

Bây giờ chúng ta hãy xem xét lại ví dụ về ba đồng tiền:

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

Ở đây, sử dụng phát sóng để đại diện cho xác suất mà mỗi đồng xu đi lên đứng đầu là khá trực quan:

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

(Hãy so sánh này để b3.prob([1., 1., 1.]) , mà chúng ta sẽ phải sử dụng trở lại nơi b3 đã được giới thiệu.)

Bây giờ giả sử chúng ta muốn biết, đối với từng đồng xu, xác suất đồng xu đi lên đầu xác suất nó đi lên đuôi. Chúng tôi có thể tưởng tượng thử:

b3.log_prob([0, 1])

Thật không may, điều này tạo ra một lỗi với một dấu vết ngăn xếp dài và không thể đọc được. b3BE = (3) , vì vậy chúng tôi phải vượt qua b3.prob một cái gì đó broadcastable chống (3,) . [0, 1] có hình dạng (2) , vì vậy nó không phát sóng và tạo ra một lỗi. Thay vào đó, chúng ta phải nói:

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)>

Tại sao? [[0], [1]] có hình dạng (2, 1) , vì vậy nó phát sóng chống lại hình dạng (3) để thực hiện một hình dạng phát sóng của (2, 3) .

Broadcasting khá mạnh mẽ: có những trường hợp nó cho phép giảm dung lượng bộ nhớ được sử dụng theo thứ tự và nó thường làm cho mã người dùng ngắn hơn. Tuy nhiên, nó có thể là một thách thức để lập trình với. Nếu bạn gọi log_prob và nhận được một lỗi, một thất bại trong việc phát sóng gần như lúc nào cũng là vấn đề.

Đi xa hơn

Trong hướng dẫn này, chúng tôi (hy vọng) đã cung cấp một phần giới thiệu đơn giản. Một số gợi ý để đi xa hơn:

  • event_shape , batch_shapesample_shape có thể rank tùy ý (trong hướng dẫn này chúng luôn luôn hoặc vô hướng hoặc cấp bậc 1). Điều này làm tăng sức mạnh nhưng một lần nữa có thể dẫn đến những thách thức về lập trình, đặc biệt là khi có liên quan đến việc phát sóng. Đối với một lặn sâu thêm vào thao tác hình dạng, thấy Hiểu TensorFlow phân phối Shapes .
  • TFP bao gồm một trừu tượng mạnh mẽ được gọi là Bijectors , mà kết hợp với TransformedDistribution , mang lại một cách thành phần linh hoạt để dễ dàng tạo ra các bản phân phối mới được biến đổi nghịch của các bản phân phối hiện có. Chúng tôi sẽ cố gắng để viết một hướng dẫn về sớm này, nhưng trong khi chờ đợi, hãy kiểm tra các tài liệu hướng dẫn