オーストラリアで勉強してきたMLデザイナーの口語自由詩

主に、データ分析・機械学習・ベイズ・統計について自由に書く。

Modelクラスの初期化処理 [e334115, d07338e, 93bc07b] - pymc4のソースコード読んでみた

f:id:yukinagae:20171122095115p:plain

TL;DR

  • まずは Model クラスの初期化処理系のメソッドを読んでみます。
    • __init__: 初期化処理いろいろ
    • define: self._f を設定して変数初期化しているようですが、今のところテスト用のヘルパー関数に見えます
    • configure: 設定を上書きして変数初期化
    • _init_variables: 次回読みます

コミット

2018/06/09から2018/06/11の間のコミットです。

Model - pymc4/model/base.py

init

初期化処理内では、以下プロパティを設定しているみたいです。

  • _cfg: 設定情報
  • name: モデルの名称(任意)
  • _f: None を設定してるので、後で使用されると推測
  • _variables: None を設定してるので、後で使用されると推測
  • _observed: dict() で初期化してるので、 key(RandomVariableの名前)value(RandomVariableのインスタンス) が入ると推測
  • session: tensorflowの session を初期化もしくは引数から設定しているだけです。
class Model(object):
    def __init__(self, name=None, graph=None, session=None, **config):
        self._cfg = Config(**config)
        self.name = name
        self._f = None
        self._variables = None
        self._observed = dict()
        if session is None:
            session = tf.Session(graph=graph)
        self.session = session

configure

_cfg を上書きして _init_variables を呼んでいるだけです。

    def configure(self, **override):
        self._cfg.update(**override)
        self._init_variables()
        return self

define

引数の f を設定して、 _init_variables を呼んでいるだけです。

    def define(self, f):
        self._f = f
        self._init_variables()
        return f

pymc4/model/base.py の最下段で以下のように define が使われています。

@biwrap.biwrap
def inline(f, **kwargs):
    model = Model(**kwargs)
    model.define(f)
    return model

inlinetests/test_model.py 内の以下にある通り、まずはテスト実行時のヘルパー関数のように使用されています。

def test_testvalue():
    @pm.inline
    def model(cfg):
        ed.Normal(0., 1., name='normal')

つまり、define 内の self._f には def model(cfg) の関数が設定されることがわかります(以下は擬似コードです)

self._f = def model(cfg):
                ed.Normal(0., 1., name='normal')

_init_variables

この辺りは interceptors 周りの動きが分からないと理解できなそうなので、次回読もうと思います。

    def _init_variables(self):
        info_collector = interceptors.CollectVariablesInfo()
        with self.graph.as_default(), ed.interception(info_collector):
            self._f(self.cfg)
        self._variables = info_collector.result

参考資料

なぜPyMC4のバックエンドにTensorFlowが採用されたのか?

f:id:yukinagae:20180920092932p:plain

TL;DR

  • 以下記事をもとに、PyMC4のバックエンドにtensorflowが採用された経緯をまとめました。

see: Theano, TensorFlow and the Future of PyMC – PyMC Developers – Medium

ポイント

  • tensorflowには既に多くのユーザがいること(デファクトスタンダードであること)
  • 確率プログラミングに必要な確率分布や変換処理が実装されているtensorflow probability(edward2)が使えること
  • tensorflow probabilityは低レベルでフレキシブルなAPI、PyMC4は高レベルでユーザフレンドリーなAPIという棲み分けが上手くできること

PyMC4のバックエンドにtensorflowが採用された理由

PyMC3のバックエンドのTheanoが開発停止

まずは2018/05/18の以下記事でPyMC DevelopersからPyMC4のアナウンスが出されています。

see: Theano, TensorFlow and the Future of PyMC – PyMC Developers – Medium

PyMC3 is an open-source library for Bayesian statistical modeling and inference in Python, implementing gradient-based Markov chain Monte Carlo, variational inference, and other approximation methods. These algorithms currently rely on Theano for computation, specifically for providing gradients.

(PyMC4の前バージョンである)PyMC3のバックエンドは Theano に依存していたことがわかります。

Since the Theano team announced that it would cease development and maintenance of Theano within a year, we, the PyMC developers, have been actively discussing what to do about this.

しかし、Theanoのチームが年内の開発とメンテナンスをやめることをアナウンスしたため、PyMCとしては今後はTheano以外のバックエンドを採用する必要があり、そのディスカッションを行ったとのことです。

Theanoのgithubレポジトリを見ると、 MILA will stop developing Theano. と記載されています。

see: GitHub - Theano/Theano: Theano is a Python library that allows you to define, optimize, and evaluate mathematical expressions involving multi-dimensional arrays efficiently. It can use GPUs and perform efficient symbolic differentiation.

また、開発・メンテナンスしていた Mila がTheanoを止めた理由が以下forumに投稿されています。

see: Forum: MILA and the future of Theano

要約すると、MILAチームは約10年程度をTheanoの開発に費やしたが現在の深層学習研究の状況が進んだことで様々なフレームワークが使用可能になったため、Theanoの役目は終わったということのようです。開発停止の告知文からは、Theanoによって様々なイノベーションを推進してきたという誇りが垣間見えます。

Tensorflow (Tensorflow Probability) を採用した理由

理由としては以下が挙げられています。

  • tensorflowには既に多くのユーザがいること(デファクトスタンダードであること)
  • 確率プログラミングに必要な確率分布や変換処理が実装されているtensorflow probability(edward2)が使えること

TFP(tensorflow probability)はフレキシブルな統計モデルを可能にしますが、低レベルのAPIしか提供しないためそこまでユーザフレンドリーではありません。その点を補完する、より使いやすい高レベルなAPIを提供するのがPyMCの役目とのことです。

なぜ他のライブラリが採用されなかったのか?

PyMC4のバックエンドに関するディスカッションの様子は以下から見ることができますが、ここでTheanoの代替として何をバックエンドに採用するかが議論されています。

see: PyMC4 - PyMC Discourse

選択肢として挙がっていたのは、主に以下でした。

  • PyTorch
  • Pyro: PyTorchをバックエンドとした確率プログラミングライブラリ(by Uber
  • TensorFlow
  • Edward: TensorFlowをバックエンドとした確率プログラミングライブラリ
  • Microsoft Cognitive Toolkit (CNTK)
  • MxNet

ディスカッション内でのやり取りを見ると、Edwardの作者であるDustinがPyMC4のオーガナイズをしているtweickiと会話しており、この後実際にミーティングで直に話したようです。

f:id:yukinagae:20180920092932p:plain

細かい詳細はわかりませんが、やはりTFPは低レベルでフレキシブルなAPIを提供し、それをラップするPyMC4は高レベルでユーザフレンドリーなAPIを提供する、という役割分担の点で考えが一致したのが大きな理由のように思えます。

参考資料

biwrapを使用したinline関数 [e334115, d07338e, 93bc07b] - pymc4のソースコード読んでみた

f:id:yukinagae:20171122095115p:plain

TL;DR

コミット

2018/06/09から2018/06/11の間のコミットです。

inline - pymc4/model/base.py

model.define を常に呼ぶようにwrapしたアノテーションのようです。 define メソッドのの中身はおそらく初期化処理等と推測できます( Model クラス内の処理についてはまだ読んでいないのでよくわかかっていません)。

@biwrap.biwrap
def inline(f, **kwargs):
    model = Model(**kwargs)
    model.define(f)
    return model
  • 使い方: tests/test_model.py 参照

以下にある通り、まずはテスト実行時のヘルパー関数のように使用していることがわかります。

def test_testvalue():
    @pm.inline
    def model(cfg):
        ed.Normal(0., 1., name='normal')

参考資料

アーキテクチャをPyMC3からPyMC4へ [e334115, d07338e, 93bc07b] - pymc4のソースコード読んでみた

f:id:yukinagae:20171122095115p:plain

TL;DR

  • 今までのコミットの ModelRandomVariable は削除され、アーキテクチャも変更されています。削除されたコードはpymc3とほぼ同等だったので、試しに書いたコードだったようです汗。ほとんど0から読み進める感じになるので、焦らず読みやすそうな箇所から辿っていきます。

コミット

2018/06/09から2018/06/11の間のコミットです。

以下のファイルが追加・変更・削除されています。多いので少しづつ読んでいきます。

  • pymc4/init.py
  • pymc4/distributions/base.py
  • pymc4/inference/init.py
  • pymc4/inference/sampling/sample.py
  • pymc4/model.py
  • pymc4/random_variable.py
  • pymc4/sample.py → pymc4/sampling.py
  • pymc4/util/init.py
  • pymc4/util/graph.py
  • pymc4/util/interceptors.py
  • tests/test_model.py
  • test/test_nothing.py → tests/test_nothing.py
  • requirements.txt

どこから読むか?

他ドキュメントやブログ記事を読んでいると、とりあず以下のように Model クラスのインスタンスを作成することがわかっているので、 pymc4/model パッケージ内のファイルから読み進めてみます。

model = pm4.Model([引数])

pymc4/modelパッケージ

対象パッケージ内には以下2ファイルが存在します。

  • init.py
  • base.py

init.py

base.py 内の Model クラスと inline 関数をimportしているだけです。

from .base import (
    Model,
    inline
)

base.py

base.py 内では以下3つが定義されています。

  • Configクラス( __all__ に含まれていないのでexportされていない)
  • Modelクラス
  • inline関数

まずは簡単な Config クラスから読みます。

Config - base.py

dict を継承したコンフィグ用のクラスを定義しているだけです。exportされていないので、 Model クラス内でしか使用されないことがわかります。

class Config(dict):
    def __getattr__(self, item):
        try:
            return self.__getitem__(item)
        except KeyError as e:
            error = KeyError(item, '"{i}" is not found in configuration for the model, '
                             'you probably need to pass "{i}" in model definition as '
                             '\n`model = pm.Model(..., {i}=value)`'
                             '\nor'
                             '\n'
                             '\n@pm.inline(..., {i}=value)'
                             '\ndef model(cfg):'
                             '\n    # your model starts here'
                             '\n    ...'.format(i=item))
            raise error from e

__getattr__ が呼ばれた際に、 __get__item を代わりに呼んでいるだけです。

  • 例)c.a => c['a']

また __getitem__KeyError が発生する恐れがあるので、エラー情報にusageを含めています。

"{i}" is not found in configuration for the model,
you probably need to pass "{i}" in model definition as
`model = pm.Model(..., {i}=value)`
or

@pm.inline(..., {i}=value)
def model(cfg):
# your model starts here'
...

使い方として以下の2パターンがあることがわかります。

  • pm.Model を使う場合
model = pm.Model(..., {i}=value)
  • @pm.inline を使う場合
@pm.inline(..., {i}=value)
def model(cfg):
    ...

次回はModelクラスを読んでいきます。

参考資料

pymc4のソースコード読んでみた - 依存ライブラリの対応やlint [ac77b27, bec2985, ce1d895]

f:id:yukinagae:20171122095115p:plain

TL;DR

  • 依存ライブラリである tensorflow_probability が原因で requirements.txt の現状の書き方では最新バージョンを上手く取得できない問題があったみたいです。新しいライブラリなので仕方ないですね。

コミット

2018/06/03のコミットです。

以下のファイルが変更されています。

  • requirements.txt
    • バージョン周りの問題の一時的な対応(後述)
  • .travis.yml
  • pymc4/sample.py
    • pylintのエラー対応
  • pymc4/model.py
    • pylintのエラー対応
  • pymc4/random_variable.py
    • pylintのエラー対応
  • pymc4/sample.py
    • pylintのエラー対応

requirements.txt

依存ライブラリである tensorflow_probability が原因で以下を requirements.txt に記載してもlatestバージョンが取得できない問題があったみたいです。

tf-nightly
tfp-nightly
tb-nightly

一時的な対応として結局 tfp-nightly==0.0.1.dev20180515 のようにバージョンを明示的に指定する方法で対応しています。

tf-nightly==1.8.0.dev20180331
tfp-nightly==0.0.1.dev20180515 
tb-nightly==1.8.0a20180424

see: Model context manager, primitive default sampling, random variable class by sharanry · Pull Request #1 · pymc-devs/pymc4 · GitHub

参考資料

pymc4のソースコード読んでみた - “RandomVariable”, Add model tests, fix travis pytest problem [4357d39]

f:id:yukinagae:20171122095115p:plain

TL;DR

  • treedict クラスの依存性を以前のコミットで削除した代わりに、 RandomVariable クラス内で Modelcontexts.stack 全てに値を設定することで、ほぼ同等の動きをしている。

コミット

2018/06/03のコミットです。

RandomVariable - random_variable.py

どちらにしろ挙動が変わらないことがわかる

  • 【before】treedictクラスに依存している場合、ModelがネストするとparentにもRandomVariableが伝搬する
with WithTreeModel(name="model1") as model1:
    rv1 = WithTreeRandomVariable(name="rv1")
    with WithTreeModel(name="model2") as model2:
        rv2 = WithTreeRandomVariable(name="rv2")
        rv3 = WithTreeRandomVariable(name="rv3")
        print("model1: {}".format([v for v in model1.named_vars]))
        print("model2: {}".format([v for v in model2.named_vars]))

# 出力:
# => model1: ['rv1', 'rv2', 'rv3']
# => model2: ['rv2', 'rv3']
  • 【after】treedictクラスに依存せず、RandomVarible内でModel.get_contextsを辿っている場合、ModelがネストするとparentにもRandomVariableが伝搬する
with Model(name="model1") as model1:
    rv1 = RandomVariable(name="rv1")
    with Model(name="model2") as model2:
        rv2 = RandomVariable(name="rv2")
        rv3 = RandomVariable(name="rv3")
        print("model1: {}".format([v for v in model1.named_vars]))
        print("model2: {}".format([v for v in model2.named_vars]))

# 出力:
# => model1: ['rv1', 'rv2', 'rv3']
# => model2: ['rv2', 'rv3']

TODO

  • [ ] ほぼ同じ挙動がに見えるが、設計を少し変更した理由がわからない

参考資料

pymc4のソースコード読んでみた - Add model tests, fix travis pytest problem [4357d39]

f:id:yukinagae:20171122095115p:plain

コミット

2018/06/03のコミットです。

以下ファイルが変更されています。

  • .gitignore
    • .pytest_cache/ を追記
  • .pylintrc
  • .travis.yml
  • pymc4/init.py
    • *(ワイルドカード) のimportをしてもpytlintエラーにならないようにしている(簡便さのため)
  • pymc4/model.py
    • modelcontext__all__ に含めている
  • pymc4/random_variable.py
    • 後述
  • pymc4/sample.py
  • test/test_model.py
    • 後述
  • test/test_nothing.py

RandomVariable - random_variable.py

重要そうなので次回じっくり読みます。

test_model.py

重要そうなので次々回じっくり読みます。

参考資料