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

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

pymc4のソースコード読んでみた - “treedict”, 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.pyModel クラスだけを見てみようと思ったのですが、Model クラス内で使用している treedict クラスがあるのでまずはこっちを読んでみます(できるだけコード量が少なく、依存性が小さいクラスから読み進める方針です)

treedict - model.py

以下が treedict クラスです。

class treedict(dict):
    """A dict that passes mutable extending operations used in Model
    to parent dict instance.
    Extending treedict you will also extend its parent
    """
    def __init__(self, iterable=(), parent=None, **kwargs):
        super(treedict, self).__init__(iterable, **kwargs)
        assert isinstance(parent, dict) or parent is None
        self.parent = parent
        if self.parent is not None:
            self.parent.update(self)
    # typechecking here works bad
    __setitem__ = withparent(dict.__setitem__)
    update = withparent(dict.update)

    def tree_contains(self, item):
        # needed for `add_random_variable` method
        if isinstance(self.parent, treedict):
            return (dict.__contains__(self, item) or
                    self.parent.tree_contains(item))
        elif isinstance(self.parent, dict):
            return (dict.__contains__(self, item) or
                    self.parent.__contains__(item))
        else:
            return dict.__contains__(self, item)
  • まずはクラスのコメントから読んでみます。

A dict that passes mutable extending operations used in Model to parent dict instance. Extending treedict you will also extend its parent

つまり、 python組み込みの dict クラスを継承して、親クラス(=parent)の dict まで拡張するように変更した自作クラスのようです。また Model クラス内で使用されているとのことです。

次に __init__ 処理に注目してみます。

  1. super().init で初期化
  2. parentdict もしくは None であることをassertチェック
  3. parentdict もしくは None であれば引数の parentself にセットする
    1. もし親の parentdict インスタンスであれば、その親インスタンスにも key:value をセットする
def __init__(self, iterable=(), parent=None, **kwargs):
    super(treedict, self).__init__(iterable, **kwargs)
    assert isinstance(parent, dict) or parent is None
    self.parent = parent
    if self.parent is not None:
        self.parent.update(self)

例えば以下のようになります。

parent がNoneの場合

child1 = treedict({'a': 1, 'b': 2})
child1 # => {'a': 1, 'b': 2}
child1.parent # => None

parentdict インスタンスの場合

child2 = treedict({'a': 1, 'b': 2}, parent={'x': 1, 'y': 2})
child2 # => {'a': 1, 'b': 2}
child2.parent # => {'a': 1, 'b': 2, 'x': 1, 'y': 2}
  • 別メソッドの withparent を呼んでいるみたいなので、次回のコードリーディングに持ち越します。
# typechecking here works bad
__setitem__ = withparent(dict.__setitem__)
update = withparent(dict.update)
  • tree_contains メソッドを見てみます。組み込みの dict クラスの __contains__ メソッドは対象の値が自分の key: value の中に存在するかチェックできますが、 parent まで値をたどることができません。ですので、この treedict メソッドでは親の parent まで値をたどるようにしているようです。
def tree_contains(self, item):
    # needed for `add_random_variable` method
    if isinstance(self.parent, treedict):
        return (dict.__contains__(self, item) or
                self.parent.tree_contains(item))
    elif isinstance(self.parent, dict):
        return (dict.__contains__(self, item) or
                self.parent.__contains__(item))
    else:
        return dict.__contains__(self, item)

3つの if-elif-else の処理を見てみます。

  1. parenttreedict の場合: 対象の値が self 内、、もしくは parent に存在するかチェック
  2. parentdict の場合: 対象の値が self 内、、もしくは parent に存在するかチェック
  3. それ以外の場合(= parent の探索が不要): 対象の値が self 内に存在するかチェック

12 の違いは、 parenttreedictdict かによって呼ぶメソッドを切り替えているだけです。

References