PyMC4のInstallation failsというissueに対応するPR送った
TL;DR
次のissueにあるように, 現状だと依存性の解決の部分でfailしてインストールできないのでとりあえずforkして dependency-resolution
というbranchで修正してみた.
see: Installation fails · Issue #23 · pymc-devs/pymc4 · GitHub
修正点
これを
tf-nightly==1.9.0.dev20180607 tfp-nightly==0.3.0.dev20180725 tb-nightly==1.9.0a20180613
こうするだけの簡単なお仕事.
tf-nightly tfp-nightly tb-nightly
とりあえず本体にPR送った(`・ω・´)
おまけ
pip
コマンドよくわかってないオプションがあったので軽く調べた.
pip install --user git+https://github.com/pymc-devs/pymc4.git#egg=pymc4
- —user
- ユーザのローカルディレクトリ(
~/.local/
など)に対象のパッケージをインストールする
- ユーザのローカルディレクトリ(
- egg=[プロジェクト名を明示的に指定]
また, 次のようにbranch名も指定できる * @[branch名]
pip install --user git+https://github.com/yukinagae/pymc4@dependecny-resolution.git#egg=pymc4
参考資料
Add model.target_log_prob_fn() sampling [a703c21] - pymc4のソースコード読んでみた
TL;DR
- ターゲットとなる
unobserved(未観測なRandomVariableインスタンス)
の対数確率の合計を返すメソッドを実装しています.
コミット
2018/06/18のコミットです.
以下ファイルが修正されています.
- pymc4/model/base.py:
Model
クラスに次の2つのメソッドが追加- target_log_prob_fn(self, args, kwargs)
- unobserved(self) *@property
- pymc4/util/interceptors.py
VariableDescription
クラスにrv(RandomVariableのインスタンス)
のプロパティが追加されただけです. この追加プロパティはtarget_log_prob_fn
で使用されています(後述).
target_log_prob_fn
unobserved(未観測なrv)
の log_probability(対数確率)
の合計を返します.
※ちなみにちょっとした罠ですが, 変数名の i
というのはこのコードの実装では index
ではなく, dictの key
なので, 単に k
などとした方がよいです.
def target_log_prob_fn(self, *args, **kwargs): logp = 0 for i in self.unobserved.keys(): print(kwargs.get(i)) logp += self.unobserved[i].rv.distribution.log_prob(value=kwargs.get(i)) return logp
unobserved
self.variables
(RandomVariableインスタンスのリスト)から unobserved
を抽出しているだけです(= observed
ではないものを抽出).
ちなみに, わざわざ collections.OrderedDict
で順序を保持したdictionary配列にしている理由はこの時点ではよくわかりません.
@property def unobserved(self): unobserved = {} for i in self.variables: if self.variables[i] not in self.observed.values(): unobserved[i] = self.variables[i] unobserved = collections.OrderedDict(unobserved) return unobserved
参考資料
- Get mcmc sampling to work by sharanry · Pull Request #9 · pymc-devs/pymc4 · GitHub
- 確率質量関数 - Wikipedia: probability mass function(PMF)- 離散値用の確率関数
- 確率密度関数 - Wikipedia: probability density function(PDF)- 連続値用の確率変数
python3.5削除 + pep8対応 [bb4de21] - pymc4のソースコード読んでみた
TL;DR
- python3.5は対応せず、3.6以上対応の方針
- pep8のコードスタイルの修正なので、特に重要な点はなさそうです。今後はlint系のコミットは冗長なので省略するかもしれません。
コミット
2018/06/11のコミットです。
以下ファイルが修正・追加されていますが、基本的にpep8のコードスタイルの修正なので、説明は割愛します。
- .travis.yml
- pymc4/distributions/base.py
- pymc4/inference_sampling_sample.py
- requirements-dev.txt
- setup.cfg
- tests/test_model.py
- tests/test_nothing.py
python3.5削除(3.6以上対応の方針)
.travis_yml
を見ると、pythonのバージョン 3.5
が削除されています。
以下の議論を見ると、基本的に3.6対応していく流れみたいです。
see: Supported Python versions? · Issue #2 · pymc-devs/pymc4 · GitHub
3.6の新機能として、主に以下の f-strings
literal と variable annotation
があるみたいなので、これらを使用するなら3.5は捨てる必要があります(正確には annotation
は3.5では単に無視されるみたいですが、 f-strings
が使えないということです)
- PEP 498, formatted string literals.
- PEP 526, syntax for variable annotations.
Debian 9 (stable) では3.5が使われているようですが、結論としては miniconda使えばいいじゃん
ということになったみたいです。
参考資料
テストサンプルの生成 [e334115, d07338e, 93bc07b] - pymc4のソースコード読んでみた
概要
Model
クラスのサンプル生成のメソッドを読んでみます。test_point
コミット
2018/06/09から2018/06/11の間のコミットです。
- tmp · pymc-devs/pymc4@e334115 · GitHub
- restructure + test point implementation · pymc-devs/pymc4@d07338e · GitHub
- fixes · pymc-devs/pymc4@93bc07b · GitHub
Model - pymc4/model/base.py
test_point
まずはこの test_point
メソッドがでどのように呼ばれるか調べます。pymc4のプロジェクト内では以下の tests/test_model.py
のみで使用されています。
@pm.inline def model(cfg): ed.Normal(0., 1., name='normal') testval_random = model.test_point() testval_mode = model.test_point(sample=False) assert testval_mode['normal'] == 0. assert testval_mode['normal'] != testval_random['normal']
ここでは2種類の呼び方があるみたいです。
- test_point(): デフォルトでsample=True
- test_point(sample=False)
事前情報として、ed.Normal(0., 1., name='normal')
というのは、正規分布で平均が0かつ標準偏差が1という意味です。
test_point(sample=True)
Modelに設定されているRandomVariableのインスタンスの確率分布に沿って、1つの乱数が生成されます。
例) 正規分布で平均が0かつ標準偏差が1の確率分布から乱数を生成する
test_point(sample=False)
Modelに設定されているRandomVariableのインスタンスの確率分布に沿って、中央値が返されます。
例) 正規分布で平均が0かつ標準偏差が1の確率分布の中央値は0なので、常に0が返される
さっと test_point
メソッドの実装も見てみます。
以下がそのメソッドの全容です。
def test_point(self, sample=True): # 1 def not_observed(var, *args, **kwargs): return kwargs['name'] not in self.observed values_collector = interceptors.CollectVariables(filter=not_observed) chain = [values_collector] # 2 if not sample: def get_mode(state, rv, *args, **kwargs): return rv.distribution.mode() chain.insert(0, interceptors.Generic(after=get_mode)) # 3 with self.graph.as_default(), ed.interception(interceptors.Chain(*chain)): self._f(self.cfg) # 4 with self.session.as_default(): returns = self.session.run(list(values_collector.result.values())) return dict(zip(values_collector.result.keys(), returns))
一つひとつバラバラに見ていきます。
#1
この辺りの挙動はあまりよくわかっていないので飛ばします。
def not_observed(var, *args, **kwargs): return kwargs['name'] not in self.observed values_collector = interceptors.CollectVariables(filter=not_observed) chain = [values_collector]
#2
test_point(sample=False)
の場合の処理です。やはり予想通り、中央値を返すようになっています。
if not sample: def get_mode(state, rv, *args, **kwargs): return rv.distribution.mode() chain.insert(0, interceptors.Generic(after=get_mode))
#3
以前、 edward2のinterception処理 の記事で確認したので飛ばします。
with self.graph.as_default(), ed.interception(interceptors.Chain(*chain)):
self._f(self.cfg)
#4
tensorflowのsessionを実行して、実際にGraph内の演算処理を実行しています。この辺りも今後もう少し理解できてくるはずです。
with self.session.as_default(): returns = self.session.run(list(values_collector.result.values())) return dict(zip(values_collector.result.keys(), returns))
The tf.Session.run method is the main mechanism for running a tf.Operation or evaluating a tf.Tensor. You can pass one or more tf.Operation or tf.Tensor objects to tf.Session.run, and TensorFlow will execute the operations that are needed to compute the result.
see: Graphs and Sessions | TensorFlow
このコミットを読み続けるのも疲れたので、次回から次のコミットに進みます。
TODO
- [ ] #1 の
not_observed
の処理の内容確認
参考資料
tensorflowのグラフ構造 [e334115, d07338e, 93bc07b] - pymc4のソースコード読んでみた
概要
- まずは
Model
クラスの初期化処理系のメソッドを読んでみます。_init_variables
: 今回はここのself.graph.as_default()
の処理を読みます
コミット
2018/06/09から2018/06/11の間のコミットです。
- tmp · pymc-devs/pymc4@e334115 · GitHub
- restructure + test point implementation · pymc-devs/pymc4@d07338e · GitHub
- fixes · pymc-devs/pymc4@93bc07b · GitHub
Model - pymc4/model/base.py
_init_variables
今回は _init_variables
内の self.graph.as_default()
の処理を見ていきます。
info_collector = interceptors.CollectVariablesInfo() # info_collector() で呼べるcallable with self.graph.as_default(), ed.interception(info_collector): self._f(self.cfg) self._variables = info_collector.result
self.graph
は Model
クラス内で以下のように定義されているので、実際には self.session.graph
== tf.session.graph
と同等です。
@property def graph(self): return self.session.graph
また、今回の読む対象に絞って [余計な情報を省略] + [変数を読み替える]、を行うと以下のコードと同等になります。
with tf.Session.graph().as_default(): ed.Normal(0., 1., name='normal') <= ここはed.XXX()
上記の XX
部分は tensorflow_probability
の distribution
クラスをedward2の RandomVariable
クラスでwrapしたものです。
see: probability/generated_random_variables.py at master · tensorflow/probability · GitHub
結局これらを理解するには tensorflow
のGraphの動作を理解する方が早そうです。
see: Graphs and Sessions | TensorFlow
実際に as_default()
の使用例を見てみると、 以下のように with
スコープで実行された tf.XXX
の処理の結果がそのスコープのGraphに追加されていくようです。
g_1 = tf.Graph() with g_1.as_default(): # Operations created in this scope will be added to `g_1`. c = tf.constant("Node in g_1") # Sessions created in this scope will run operations from `g_1`. sess_1 = tf.Session() g_2 = tf.Graph() with g_2.as_default(): # Operations created in this scope will be added to `g_2`. d = tf.constant("Node in g_2") # Alternatively, you can pass a graph when constructing a <a href="../api_docs/python/tf/Session"><code>tf.Session</code></a>: # `sess_2` will run operations from `g_2`. sess_2 = tf.Session(graph=g_2) assert c.graph is g_1 assert sess_1.graph is g_1 assert d.graph is g_2 assert sess_2.graph is g_2
つまり以下のコードを簡略的に理解すると、 g1(graph)
-> c(constant)
という紐付きをGraphとして構造化することができるということです。
with g_1.as_default(): # Operations created in this scope will be added to `g_1`. c = tf.constant("Node in g_1")
同様に以下のコードも、tf.Session.graph()
-> ed.Normal()
と紐付けることができます。
with tf.Session.graph().as_default(): ed.Normal(0., 1., name='normal') <= ここはed.XXX()
参考資料
edward2のinterception処理 [e334115, d07338e, 93bc07b] - pymc4のソースコード読んでみた
概要
- まずは
Model
クラスの初期化処理系のメソッドを読んでみます。_init_variables
: 今回はここのed.interception()
の処理を読みます
コミット
2018/06/09から2018/06/11の間のコミットです。
- tmp · pymc-devs/pymc4@e334115 · GitHub
- restructure + test point implementation · pymc-devs/pymc4@d07338e · GitHub
- fixes · pymc-devs/pymc4@93bc07b · GitHub
Model - pymc4/model/base.py
_init_variables
今回は _init_variables
内のtensorflow及びtensorflow_probabilityに関連する処理を見ていきます。 ed.interception
がわかればなんとなく処理がわかってくるはずです。
info_collector = interceptors.CollectVariablesInfo() # info_collector() で呼べるcallable with self.graph.as_default(), ed.interception(info_collector): self._f(self.cfg) self._variables = info_collector.result
以下の ed.interception
の使い方を見てみましょう。
see: probability/interceptor.py at master · tensorflow/probability · GitHub
def model(): return ed.Poisson(rate=1.5, name="y") def interceptor(f, *args, **kwargs): if kwargs.get("name") == "y": kwargs["value"] = 42 return interceptable(f)(*args, **kwargs) with ed.interception(interceptor): y = model() with tf.Session() as sess: assert sess.run(y.value) == 42
interceptという名前の通り、 def interceptor(f, *args, **kwargs)
のような関数を渡すと、 f
を呼んだ結果のインスタンスに値をセットすることができます。
上記の例では、 y
というModelインスタンスに y.value = 42
の値をセットしています。
他のコードを読んでみると ed.interception(interceptors.Chain(*chain))
という形で使用している箇所もあるので、名前から察するにinterceptの処理を複数重ねていくことができるみたいです。例えば、上記の例で言えば、別のintercept処理を重ねて y.result = [42]
のように y
のインスタンスに新たに result
という名前の配列を作成していくこともできるはずです。
以下の _init_variables
の処理を見ると、 self._f
を実行した戻り値がedward2の RandomVariable
クラスのインスタンスであり、そのインスタンスに info_collector
というinterceptの処理をかませていることが見て取れます。前回見た通り、 CollectVariablesInfo
内では 単に f
を実行し、info_collector
の result
プロパティに RandomVariable
の配列を格納していました。
with self.graph.as_default(), ed.interception(info_collector): self._f(self.cfg) # <= RandomVariableクラスのインスタンス
結果的に、 with文内で RandomVariable
のインスタンスの結果を受け取ることなく、 info_collector
から結果の配列を受け取ることができます。
self._variables = info_collector.result <= RandomVariableクラスのインスタンス配列
次回は、どう使われているかわからない self.graph.as_default()
部分を読み解いていく予定です。
参考資料
Modelの_init_vatiables内のInterceptor処理 [e334115, d07338e, 93bc07b] - pymc4のソースコード読んでみた
概要
- まずは
Model
クラスの初期化処理系のメソッドを読んでみます。_init_variables
: 今回はここを読みます
コミット
2018/06/09から2018/06/11の間のコミットです。
- tmp · pymc-devs/pymc4@e334115 · GitHub
- restructure + test point implementation · pymc-devs/pymc4@d07338e · GitHub
- fixes · pymc-devs/pymc4@93bc07b · GitHub
Model - pymc4/model/base.py
_init_variables
前回わからなかった、 interceptors
周りの処理を読んでいきます。
from pymc4.util import 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
ここでの interceptors
は util
内で自前で定義している CollectVariablesInfo
を読んでみます。
CollectVariablesInfo - pymc4/util/interceptors.py
CollectVariablesInfo
クラスは Interceptor
クラスを継承しているので、まずはそちらを読みます。
VariableDescription = collections.namedtuple('VariableDescription', 'Dist,shape') class Interceptor(object): def name_scope(self): return tf.name_scope(self.__class__.__name__.lower()) def __call__(self, f, *args, **kwargs): if kwargs.get('name') is None: raise SyntaxError('Every random variable should have a name') f, args, kwargs = self.before(f, *args, **kwargs) rv = f(*args, **kwargs) return self.after(rv, *args, **kwargs) def before(self, f, *args, **kwargs): return f, args, kwargs def after(self, rv, *args, **kwargs): return rv
__call__
が定義されているので、おそらく以下のように使用できるはずです。
intercptor = Interceptor() # Interceptorのインスタンス生成 rv = intercptor(f, name="hoge") # intercptor.__call__(f, *args, **kwargs) が呼べる
keyword引数に name
が無いと SyntaxError('Every random variable should have a name')
になります。
if kwargs.get('name') is None: raise SyntaxError('Every random variable should have a name')
あとはただ before
と after
メソッドが呼ばれているだけです。ぱっと見ると、 rv
を f
関数で生成して、 after
で何かしてるようです。
( before
と after
は子クラスでoverrideされるメソッドです。)
f, args, kwargs = self.before(f, *args, **kwargs) rv = f(*args, **kwargs) return self.after(rv, *args, **kwargs) def before(self, f, *args, **kwargs): return f, args, kwargs def after(self, rv, *args, **kwargs): return rv
CollectVariablesInfo
も見てみましょう。この中では親クラスの after
メソッドがoverrideされています。
class CollectVariablesInfo(Interceptor): def __init__(self): self.result = collections.OrderedDict() def after(self, rv, *args, **kwargs): name = kwargs["name"] if name not in self.result: self.result[name] = VariableDescription(rv.distribution.__class__, rv.shape) else: raise KeyError(name, 'Duplicate name') return rv
親クラスの __call__
メソッド内で既にkeyword引数に name
が含まれていることのチェックが行われているため、ここでは単に name = kwargs["name"]
で値を取得できることが保証されています。
あとは以下の通り、 result
プロパティ(OrderedDict)に name
をkeyに、valueに rv(RandomVariable)
の情報を VariableDescription
として rv
のクラス名とshapeをセットしています。
self.result[name] = VariableDescription(rv.distribution.__class__, rv.shape)
VariableDescription
はただのnamedtuple(名前付きタプル型)です。
VariableDescription = collections.namedtuple('VariableDescription', 'Dist,shape')
次回は _init_variables
内のtensorflow及びtensorflow_probabilityに関連する処理を見ていきます。 ed.interception
がわかればなんとなく処理がわかってくるはずです。
see: probability/interceptor.py at master · tensorflow/probability · GitHub
info_collector = interceptors.CollectVariablesInfo() # info_collector() で呼べるcallable with self.graph.as_default(), ed.interception(info_collector): self._f(self.cfg)