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

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

pymc4のソースコード読んでみた - “Model”, 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 クラスを読んでみます。

Model - model.py

Model クラスのメソッドは以下の通りです。

  • new
  • init
  • model - @property
  • description - @property
  • get_contexts - @classmethod
  • get_context - @classmethod
  • add_random_variable

まずは __new__ メソッドのインスタンス生成部分です。単にインスタンス生成した後は以下3つのパターンで parent をセットしています。

  1. kwargs のキーワード引数に model が存在する場合: parent にその model インスタンスをセット
  2. クラスメソッドの get_contexts が存在する場合: parentget_contexts の末尾をセット
  3. それ以外: parent にNoneをセット
def __new__(cls, *args, **kwargs):
    instance = super(Model, cls).__new__(cls)
    if kwargs.get('model') is not None:
        instance.parent = kwargs.get('model')
    elif cls.get_contexts():
        instance.parent = cls.get_contexts()[-1]
    else:
        instance.parent = None

__init__ メソッドでは named_vars フィールドを treedict インスタンスをセットしているだけみたいです。 parent がある場合にはそれもセットしています。

def __init__(self, name="", model=None, ):
    self.name = name
    if self.parent is not None:
        self.named_vars = treedict(parent=self.parent.named_vars)
    else:
        self.named_vars = treedict()

@property 属性が指定されている以下メソッドは単なるgetterとして用意されているみたいなので、まだどのように使われるかわからないので飛ばします。 description メソッドは実装もされていません。

@property
def model(self):
    return self
    
@property
def decription(self):
    return

get_contextsget_context は同時に読みます。

cls.contexts でクラス単位のフィールドをチェックしていますが、これは継承元の Context クラスで定義されている threading.local() と等価です。またコメントに記載あるように、クラス単位の contexts フィールドはスレッドセーフなオブジェクトとなっています。

get_contexts では単に contexts フィールドの有無をチェックし、 stack を初期化 or stack を返却しているだけです。

get_context ではcontexts.stack の末尾のインスタンスを返却しています。配列にアクセスしているため、 IndexError が発生した場合を想定しています。

@classmethod
def get_contexts(cls):
    # no race-condition here, cls.contexts is a thread-local object
    # be sure not to override contexts in a subclass however!
    if not hasattr(cls.contexts, 'stack'):
        cls.contexts.stack = []
    return cls.contexts.stack

@classmethod
def get_context(cls):
    """Return the deepest context on the stack."""
    try:
        return cls.get_contexts()[-1]
    except IndexError:
        raise TypeError("No context on context stack")

最後に add_random_variable メソッドです。

named_vars フィールド内に引数の var インスタンス(= RandomVariable)をセットしています。すでに存在する場合には ValueError を投げます。 tree_contains メソッドを呼んでいるので、 parent まで辿って存在の有無をチェックをしています。

def add_random_variable(self, var):
    """Add a random variable to the named variables of the model."""
    if self.named_vars.tree_contains(var.name):
        raise ValueError(
            "Variable name {} already exists.".format(var.name))
    self.named_vars[var.name] = var

TODO

  • [ ] Model クラス内で __new__ メソッド内で parent の値をセットしているが、 __init__ 内で実行する場合との違いは? __init__ 内で実行しても問題ないのでは?

References