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

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

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 08/02/2020 (1.4.0)

* 本ページは、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 コード; そして
  • プリミティブ確率関数、これはランダム数 generator を呼び出します。

具体的には、確率関数は (関数、メソッド、あるいは 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 の分布ライブラリ 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 で二値確率変数 ‘cloudy’ を定義しています、これは 0.3 のパラメータを持つベルヌーイ分布からのドローにより与えられます。ベルヌーイ分布は 0 か 1 を返しますので、行 3 では weather の戻り値が容易にパースできるように値 cloudy を文字列に変換しています。そしてこのモデルに従えば (試行) 回数の 30% は曇りで 70% は晴れです。

行 4-5 では行 6 で気温をサンプリングするために使用していくパラメータを定義します。これらのパラメータは行 2 でサンプリングした cloudy の個別の値に依拠します。例えば、平均気温は曇りの日には 55 度 (華氏) で晴れの日には 75 度です。最後に行 7 で 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 と 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 確率関数がユニバーサルである、i.e. それらが任意の計算可能な確率分布を表わすために使用できることを意味します。続くチュートリアルで見るように、これは信じられないほどパワフルです。

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

 

Next Steps

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

 

以上