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

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

pymc4のソースコード読んでみた - “Context”, Initial Model Class, sampling and random variable [aafa32d]

f:id:yukinagae:20171122095115p:plain

コミット

2018/05/30のコミットです。

Initial Model Class, sampling and random variable · pymc-devs/pymc4@aafa32d · GitHub

主に、pymc4の根幹となる ModelRandomVariable クラスが作成されています。

以下ファイルが追加されています。

  • .vscode/settings.json
  • pymc4/init.json
  • pymc4/model.py
  • pymc4/random_variable.py
  • pymc4/sample.py
  • test.ipynb

一つ一つを細かくみたいので、今回は model.pyContext クラスだけを見てみます。

model.py

以下の5つが主に記述されています(以前のコミットと類似の情報は省略します)

  • Contextクラスの作成 <- 今回はContextクラスのコードを読みます
  • dictクラスを継承したtreedictクラスの作成
    • withparentというhelperメソッドの作成
  • Contextクラスを継承したModelクラスの作成
  • modelcontextメソッドの作成
  • plotメソッドの作成(おそらく動作確認テスト用)
class Context(object):
    """Functionality for objects that put themselves in a context using
    the `with` statement.
    """
    contexts = threading.local()

    def __enter__(self):
        type(self).get_contexts().append(self)
        return self

    def __exit__(self, typ, value, traceback):
        type(self).get_contexts().pop()

上記のコードで行われているのは3つのことだけです。

  1. threading.local()contexts を作成
  2. __enter__ メソッドを定義
  3. __exit__ メソッドを定義

threading.local() で contexts を作成

スレッド毎に異なるローカル変数を保持できるストレージみたいなものみたいです。

例えば以下の疑似コードでは、スレッド毎にランダムな数を生成していますがスレッド毎に出力がことなっていることがわかります。

(他メソッドは省略)

def f(d):
    d.val = random.randint(1, 100) <- Thread-1 と Thread-2 ではそれぞれ乱数を生成
    show(d)

if __name__ == '__main__':
    d = threading.local()
    show(d)
    d.val = 999 <- メインスレッドの値
    show(d)

    for i in range(2):
        t = threading.Thread(target=f, args=(d,))
        t.start()

出力: ↓ それぞれのスレッドで値が異なる

(MainThread) value=999
(Thread-1) value=51
(Thread-2) value=19

see: Python Multithreading Tutorial: threading.local() - 2018

enter + exit メソッドを定義

__enter____exit__ メソッドをクラスに定義することで、withステートメントが使えます。

例えば以下のようなサンプルコードを書いてみます。

class Sample(object):
    
    def __init__(self):
        print("initialized")
        
    def __enter__(self):
        print("entered")
        return self
        
    def __exit__(self, typ, value, traceback):
        print("end")
  • 初期化の動作確認

出力: __init__ のみが呼ばれるため、以下の通り initialized だけが表示されます。

s = Sample()

# (出力)
# initialized
  • withを使った場合の動作確認

以下の順番で各処理が走るようです。

  1. __init__ 処理が走って、 initialized が表示される
  2. with文に入った瞬間に __enter__ メソッドが処理され、変数 sインスタンスが束縛される
  3. with文内の print(s)s インスタンスの内容が表示される
  4. with文を出た直後に __exit__ メソッドが処理され、 end が表示される
with Sample() as s:
    print(s)

# (出力)
# initialized
# entered
# <__main__.Sample object at 0x126fb1208>
# end

see: Understanding Python’s “with” statement

参考資料