Pyro 0.3.0 : Pyro モデルへのイントロダクション

Pyro 0.3.0 : Pyro モデルへのイントロダクション (翻訳)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/15/2018 (v0.3.0)

* 本ページは、Pyro のドキュメント An Introduction to Models in Pyro を翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー開催中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

Pyro モデルへのイントロダクション

確率的プログラムの基本ユニットは確率関数 (= stochastic function) です。これは次の 2 つの構成要素を結合した任意の Python callable です :

  • 決定論的 Python コード; そして
  • プリミティブ確率関数、これはランダム数生成器を呼び出します。

具体的には、確率関数は (関数、メソッド、あるいは PyTorch nn.Module のように) __call__() メソッドを持つ任意の Python オブジェクトであり得ます。

チュートリアルとドキュメントを通して、しばしば確率関数をモデルと呼びます、何故ならば確率関数は (プロセスによって) データが生成されるプロセスの単純化されたあるいは抽象的な記述を表わすために使用できるからです。モデルを確率関数として表わすことはモデルがちょうど通常の Python callable のように組み合わされ、再利用され、インポートされ、そしてシリアライズされることを意味します。

import torch
import pyro

pyro.set_rng_seed(101)

 

プリミティブ確率関数

プリミティブ確率関数、あるいは分布は、確率関数の重要なクラスです、そのために入力が与えられたときに出力の確率を明示的に計算することができます。PyTorch 0.4 と Pyro 0.2 の時点では、Pyro は PyTorch の 分布ライブラリ を使用します。貴方は transforms を使用してカスタム分布もまた作成することができます。

プリミティブ確率関数を使用することは簡単です。例えば、単位正規分布 $\mathcal{N}(0,1)$ からサンプル x をドローするためには、次を行ないます :

loc = 0.   # mean zero
scale = 1. # unit variance
normal = torch.distributions.Normal(loc, scale) # create a normal distribution object
x = normal.rsample() # draw a sample from N(0,1)
print("sample", x)
print("log prob", normal.log_prob(x)) # score the sample from N(0,1)
sample tensor(-1.3905)
log prob tensor(-1.8857)

ここで、torch.distributions.Normal は Distribution クラスのインスタンスで、パラメータを取り sample と score メソッドを提供します。Pyro の distribution ライブラリ pyro.distributions は torch.distributions まわりのラッパーです、何故ならば推論の間 PyTorch の高速な tensor 数学と autograd 機能を活用することを望むからです。

 

単純なモデル

総ての確率プログラムは確率関数と決定論的計算を組み合わせることによって構築されます。究極的には確率プログラミングに興味がありますので、何故ならば現実世界の何かをモデル化することを望むからですが、具体的な何かのモデルから開始しましょう。

日々の平均気温と雲量を持つ多くのデータを持つと仮定しましょう。気温が晴れか曇りであったこととどのように相互作用するかについて合理化することを望みます。そのデータがどのように生成されたかを記述する単純な確率関数は以下で与えられます :

def weather():
    cloudy = torch.distributions.Bernoulli(0.3).sample()
    cloudy = 'cloudy' if cloudy.item() == 1.0 else 'sunny'
    mean_temp = {'cloudy': 55.0, 'sunny': 75.0}[cloudy]
    scale_temp = {'cloudy': 10.0, 'sunny': 15.0}[cloudy]
    temp = torch.distributions.Normal(mean_temp, scale_temp).rsample()
    return cloudy, temp.item()

これを行毎に調べていきましょう。最初に、行 2-3 で二値確率変数 ‘cloudy’ を定義しています、これは 0.3 のパラメータを持つベルヌーイ分布からのドローにより与えられます。ベルヌーイ分布は 0 か 1 を返しますので、行 4 では weather の返り値がより簡単に解析できるように値 cloudy を文字列に変換します。それでこのモデルに従えば時間の 30% は曇りで 70% は晴れです。

行 5-6 では行 7-9 で気温をサンプリングするために使用していくパラメータを定義します。これらのパラメータは行 2 でサンプリングした cloudy の特定の値に依拠します。例えば、平均気温は曇りの日には 55 度 (華氏) で晴れの日には 75 度です。最後に行 10 で 2 つの値 cloudy と temp を返します。

けれども、weather は Pyro から完全に独立しています – それは PyTorch を呼び出すだけです。fake データをサンプリングする以外の何かのためにこのモデルを使用することを望むのであればそれを Pyro プログラムに変える必要があります。

 

pyro.sample プリミティブ

weather を Pyro プログラムに変えるために、torch.distributions を pyro.distributions に、そして .sample() と .rsample() 呼び出しを (Pyro の中心的な言語プリミティブの一つである) pyro.sample に置き換えます。pyro.sample を使用することは一つの重要な違いとともにプリミティブ確率関数を呼ぶように単純です :

x = pyro.sample("my_sample", pyro.distributions.Normal(loc, scale))
print(x)
tensor(-0.8152)

ちょうど torch.distributions.Normal().rsample() への直接呼び出しのように、これは単位正規分布からのサンプルを返します。決定的な違いはこのサンプルが名前付けられていることです。Pyro のバックエンドは、実行時に sample ステートメントを一意に識別して (enclosing 確率関数がどのように使用されているかに依拠して) それらの挙動を変更するためにこれらの名前を使用します。私達が見ていくように、これは、推論アルゴリズムの基礎となる様々な操作を Pyro がどのように実装できるかです。

pyro.sample and pyro.distributions を導入した今、私達の単純なモデルを Pyro プログラムとして書き直すことができます :

def weather():
    cloudy = pyro.sample('cloudy', pyro.distributions.Bernoulli(0.3))
    cloudy = 'cloudy' if cloudy.item() == 1.0 else 'sunny'
    mean_temp = {'cloudy': 55.0, 'sunny': 75.0}[cloudy]
    scale_temp = {'cloudy': 10.0, 'sunny': 15.0}[cloudy]
    temp = pyro.sample('temp', pyro.distributions.Normal(mean_temp, scale_temp))
    return cloudy, temp.item()

for _ in range(3):
    print(weather())
('cloudy', 64.5440444946289)
('sunny', 94.37557983398438)
('sunny', 72.5186767578125)

手続き的には、weather() は依然として 2 つのランダム・サンプルを返す非決定論的 Python callable です。何故ならば pyro.sample によりランダムネスが引き起こされるからです、けれども、それは遥かにそれ以上です。特に weather() は 2 つの名前付けられた確率変数: cloudy と temp に渡る同時確率分布を指定します。そのようなものとして、それは確率理論のテクニックを使用して (合理化できる) 確率モデルを定義します。例えば私達は尋ねるかもしれません: 私が 70 度の気温を観測する場合、どのくらいの尤度で曇りでしょう?これらの類の質問をどのように定式化して答えるかが次のチュートリアルの主題です。

 

普遍性: 確率的再帰 (Stochastic Recursion)、高階確率関数、そしてランダム制御フロー

単純なモデルをどのように定義するかを今見てきました。それを作り直すことは簡単です。例えば :

def ice_cream_sales():
    cloudy, temp = weather()
    expected_sales = 200. if cloudy == 'sunny' and temp > 80.0 else 50.
    ice_cream = pyro.sample('ice_cream', pyro.distributions.Normal(expected_sales, 10.0))
    return ice_cream

この類のモジュール性は、どのプログラマにも馴染みがあり、明らかに非常にパワフルです。しかしそれは (私達が表現したい) 総ての異なる類のモデルを包含するに十分なほどパワフルでしょうか?

Pyro は Python に埋め込まれていますので、確率関数は複雑な決定論的 Python を任意に含むことができてランダムネスは制御フローに自由に影響を与えられることが判明しています。例えば、pyro.sample の一意なサンプル名を (それが呼び出されたときにはいつでも) 渡すことに留意すると仮定すれば、それらの再帰性を非決定論的に打ち切る再帰関数を構築することができます。例えば最初の成功まで失敗の数をカウントする幾何分布をこのように定義できます :

def geometric(p, t=None):
    if t is None:
        t = 0
    x = pyro.sample("x_{}".format(t), pyro.distributions.Bernoulli(p))
    if x.item() == 1:
        return 0
    else:
        return 1 + geometric(p, t + 1)

print(geometric(0.5))
0

geometric() の名前 x_0, x_1, etc. は動的に生成されて異なる実行は名前付けられた確率変数の異なる数を持てることに注意してください。

他の確率関数を入力として受け取り出力として生成する確率関数もまた自由に定義できます :

def normal_product(loc, scale):
    z1 = pyro.sample("z1", pyro.distributions.Normal(loc, scale))
    z2 = pyro.sample("z2", pyro.distributions.Normal(loc, scale))
    y = z1 * z2
    return y

def make_normal_normal():
    mu_latent = pyro.sample("mu_latent", pyro.distributions.Normal(0, 1))
    fn = lambda scale: normal_product(mu_latent, scale)
    return fn

print(make_normal_normal()(1.))
tensor(2.1493)

ここで make_normal_normal() は確率関数で、一つの引数を取り、そして実行時には 3 つの名前付けられた確率変数を生成します。

Pyro が、ランダム制御フローと連動したこのような — 反復、再帰、高階関数, etc.— 任意の Python コードをサポートするという事実は Pyro 確率関数が universal である、i.e. それらが任意の計算可能な確率分布を表わすために使用できることを意味します。続くチュートリアルで見るように、これは信じられないほどパワフルです。

これが何故 Pyro が PyTorch 上に構築されたかの一つの理由であることを強調する価値があります: 動的計算グラフは GPU で高速化された tensor math から恩恵を受けられる普遍モデルを可能にする際の重要な構成要素です。

 

Next Steps

Pyro でモデルを表わすためにどのように確率関数とプリミティブ分布を使用できるかを示しました。データからモデルを学習してそれらについて合理化するためには推論を行なえることが必要です。これは 次のチュートリアル の主題です。

 

以上