グラフ機械学習と強化学習について

主にグラフ機械学習や強化学習手法を記載します。

ベイズ最適化:ガウシアンプロセス

創薬を行っていくとき、定量的構造活性相関(QSAR)を用いればハイスループットスクリーニングが可能となりますが、本当に欲しいのは所望の特性を満たす化合物です。目標となる活性値や物性値を得るために用いられる方法は、

  1. 数理最適化
  2. ベイズ最適化
  3. 強化学習

などが考えられます。QSARモデルが得られてしまえば、逆方向の計算をするだけです。例えば、線形回帰で活性相関のあるモデルが得られれば、あとは何らかの数理最適化アルゴリズムを用いればよいです。勾配法, 遺伝的アルゴリズム, 進化計算, PSO, 焼きなまし法など、何でもよいです。決定論的なアルゴリズムだと最適解(局所解)が求まります。

ベイズ的なアプローチでは分布が求まりますので、様々な候補を得ることができます。また、獲得関数を計算すれば次の実験を行うための指針にもなります。ここでは、ベイズ最適化で良く用いられるガウシアンプロセス、ガウス過程についての理論を記載します。また、MCMCによりベイズモデルを求め、ベイズ最適化をするのも良いです。ブートストラップを用いた機械学習モデルも使用することができます。

詳細なサンプルコードはgithubにあります。PRMLの通りに実装しています。

PRML/06_Kernel_Methods at master · Masatsugar/PRML · GitHub

ガウス過程回帰

カーネル関数

 y xの関係を y=f(x, w)とモデル化し wに対する事前分布を定めるという方法でベイズ的な分析が行えます。

しかし、パラメータ wを導入せず、事前分布を直接以下のように定めることができます。

\begin{align} y \sim \mathcal{N}(0, K) \end{align}

ここで、Kは

\begin{align} K_{ij} = k(x_i, x_j) = \frac{1}{\alpha} \Psi(x_i)^{T} \Psi(x_j) \end{align}

で定まるグラム行列です。このとき

\begin{align} k(x, x') = \mathbb{E}[y(x)y(x')] \end{align}

であるので、カーネル関数 k(x, x')を共分散関数と解釈することができます。

  • 例 Gaussian kernelとExponential kernel

\begin{align} k(x, x') &= \exp(\frac{- |x-x'|^{2}}{2 σ^{2}}) \\ k(x,x') &= \exp(-\theta |x-x'|) \end{align}

ソースコード

import numpy as np
import matplotlib.pyplot as plt

def gaussian_kernel(x1, x2):
    return np.exp(-(x1 - x2)**2/2*SIGMA**2)

def exponential_kernel(x1, x2):
    return np.exp(-THETA*abs(x1-x2))

def Gram(kern, x):
    N = x.shape[0]
    K = np.zeros((N, N))
    for i in range(N):
        for j in range(N):
            K[i, j] = kern(x[i], x[j])
    return K

SIGMA = 1.
THETA = 1.
ALPHA = 1.

N = 100
x = np.linspace(-1, 1, N)
kernel = gaussian_kernel
K = Gram(kernel, x)
for i in range(5):
    y = np.random.multivariate_normal(np.zeros(N), K/ALPHA)
    plt.plot(x, y)
plt.xlim(-1, 1)
plt.show()

結果

f:id:udnp:20190330140406p:plain
gaussian kernel & exponential kernel

なお、カーネル法では観測データ \mathbf{t} とすると、レプリゼンタ定理より

\begin{align} y(\mathbf{x}) &= \mathbf{w}^{T} \phi(\mathbf{x}) \\ &=(\mathbf{K} + \lambda \mathbf{I})^{-1} \mathbf{t} \end{align}

という回帰式が得られます。ガウス過程回帰では、これをベイズ的アプローチを加えます。

ガウス過程回帰

観測データの組 (\mathbf{x}_i, \mathbf{t}_i)

\begin{align} t_i = y_i + \varepsilon_i \end{align} と表されるとします。 \varepsilon_iはノイズを表す確率変数です。 \varepsilon_i正規分布に従うとします。

\begin{align} p(t_i|y_i) = \mathcal{N}(t_i|y_i, \beta^{-1}) \end{align}

とおきます。 \betaは精度パラメータです。個々のノイズがi.i.dで生じるならば

\begin{align} p(t|y) = \mathcal{N}(t|y, \beta^{-1}\mathbf{I}) \end{align}

と書けるので、さらに、 yガウス過程としてモデル化し、

\begin{align} p(\mathbf{y}) = \mathcal{N}(0, \mathbf{K}) \end{align}

とします。このとき、観測値の分布は

\begin{align} p(\mathbf{t}) &= \int p(\mathbf{t}|\mathbf{y})p(\mathbf{y})d\mathbf{y} \\ &=\mathcal{N}(\mathbf{t}|0, \mathbf{C}) \hspace{10pt} (\mathbf{C}=\mathbf{K}+\beta^{-1}\mathbf{I}) \end{align}

です。このCの逆行列を求めればよいです。


正規分布の周辺化の公式 \begin{align} p(\mathbf{x})&=\mathcal{N}(\mathbf{x}|\boldsymbol{\mu}, \boldsymbol{\Lambda}^{-1}) \\ p(\mathbf{y}|\mathbf{x})&=\mathcal{N}(\mathbf{y}|\mathbf{A}\mathbf{x}+\mathbf{b}, \mathbf{L}^{-1}) \end{align}

このとき、 p(y)は下式です。 \begin{align} p(\mathbf{y})=\mathcal{N}(\mathbf{y}|\mathbf{A}\boldsymbol{\mu}+\mathbf{b}, \mathbf{L}^{-1}+\mathbf{A}\boldsymbol{\Lambda}^{-1}\mathbf{A}^T) \end{align}


さて、目的は学習データ {(\mathbf{x}_1, t_1), ...,(\mathbf{x}_n, t_n) }が与えられているときに、新しい \mathbf{x}_{n+1}に対する t_{n+1}を予測することです。つまり、 \mathbf{t}=(t_1,...,t_n)^{T}が与えられたとき t_{n+1}の分布を求めればよいです。

\begin{align} p(t_{n+1}|\mathbf{t}) \end{align}

まず、先ほどと同様にして、

\begin{align} p(\mathbf{t}, t_{n+1})=\mathcal{N}(\mathbf{t}, t_{n+1}|\mathbf{0}, \mathbf{C'}) \end{align}

となります。ここで、

\begin{align} \mathbf{C'}&=\begin{pmatrix} \mathbf{C} & \mathbf{k} \\ \mathbf{k}^T & c \end{pmatrix} \\ \mathbf{k} &= (k(\mathbf{x}_1, \mathbf{x}_{n+1}), ...,\mathbf{x}_n, \mathbf{x}_{n+1}), ) \\ c &= k(\mathbf{x}_{n+1}, \mathbf{x}_{n+1}) + \beta^{-1} \end{align}

なので、もとめる t_{n+1}の分布は

\begin{align} p(t_{n+1} | \mathbf{t} ) = \mathcal{N} (t_{n+1} | \mu, σ^{2}) \end{align}

です。ただし、

\begin{align} \mu &=\mathbf{t}^{T} \mathbf{C}^{-1} \mathbf{k} \\ σ^{2} &= c - \mathbf{k}^{T} \mathbf{C}^{-1} \mathbf{k} \end{align}

となります。従って、 \mathbf{a}=\mathbf{C}^{-1}\mathbf{t}とおけば、

\begin{align} y = \sum_{i=1}^n a_i k(\mathbf{x}_i,\mathbf{x}) \end{align}

となります。これはカーネル法の式と同じです。

Hyper parameterの学習

PRMLの本でよく用いられるカーネルは以下の式と記載されています。 \begin{align} k(x, x') = \theta_0 \exp(-\theta_1 |x-x'|^{2} / 2 ) + \theta_2 + \theta_3 xx' \end{align}

 \boldsymbol{\theta}=(\theta_0,\theta_1,\theta_2,\theta_3)は事前分布の設定に関わるハイパーパラメータです。これを最尤法を使って求めましょう。第二種の最尤推定、経験ベイズとも呼ばれます。

\begin{align} L(\boldsymbol{\theta}|\mathbf{t}) = p(\mathbf{t}|\boldsymbol{\theta}) \end{align}

を最大化します。

\begin{align} p(\mathbf{t}|\boldsymbol{\theta}) &= \mathcal{N}(\mathbf{t}|\mathbf{0}, \mathbf{C}) \hspace{10pt} (\mathbf{C}=\mathbf{K}+\beta^{-1}\mathbf{I}) \\ &=\frac{1}{(2 \pi)^{n} \sqrt{|\mathbf{C}|}} \exp \left\{ -\frac{1}{2} \mathbf{t}^{T} \mathbf{C}^{-1} \mathbf{t} \right\} \end{align}

であるので、対数尤度は

\begin{align} \ln L(\boldsymbol{\theta}|\mathbf{t})=-\frac{1}{2}\ln |\mathbf{C}|-\frac{1}{2}\mathbf{t}^T\mathbf{C}^{-1}\mathbf{t}-\frac{n}{2}\ln(2\pi) \end{align}

です。これを最大化したいので、勾配を計算します。

\begin{align} \frac{\partial}{\partial \theta_i}\ln L(\boldsymbol{\theta}|\mathbf{t})=-\frac{1}{2}\text{Tr}\bigg(\mathbf{C}^{-1}\frac{\partial \mathbf{C}}{\partial \theta_i}\bigg)-\frac{1}{2}\mathbf{t}^T\mathbf{C}^{-1}\frac{\partial \mathbf{C}}{\partial \theta_i}\mathbf{C}^{-1}\mathbf{t} \end{align}


【解説】 \begin{align} \mathbf{C}\mathbf{C}^{-1} = \mathbf{I} \end{align}

の両辺を \theta_i微分すればよい。

\begin{align} \frac{\partial \mathbf{C}}{\partial \theta_i}\mathbf{C}^{-1} + \mathbf{C} \frac{\partial \mathbf{C}^{-1}}{\partial \theta_i}= \mathbf{O} \end{align}

ゆえに、

\begin{align} \frac{\partial \mathbf{C}^{-1}}{\partial \theta_i}=\mathbf{C}^{-1}\frac{\partial \mathbf{C}}{\partial \theta_i}\mathbf{C}^{-1} \end{align}

また、 \mathbf{C}固有値固有ベクトル (\lambda_i, \mathbf{u}_i) \hspace{6pt}(i=1,...,n)とする。 \{\mathbf{u}_i \}は正規直交基底をなすようにとる。 \mathbf{C}は対象行列なので、

\begin{align} \mathbf{C} = \sum_i \lambda_i \mathbf{u}_i \mathbf{u}_{i}^{T} \end{align}

と表します。これを \theta微分すると

\begin{align} \frac{\partial \mathbf{C}}{\partial \theta} = \sum_i \frac{\partial \lambda_i}{\partial \theta} \mathbf{u}_i\mathbf{u}_i^{T} + \sum_i 2\lambda_i \mathbf{u}_i \frac{\partial \mathbf{u}_i^{T}}{\partial \theta} \end{align}

となる。これと、

\begin{align} \mathbf{C}^{-1} = \sum_i \frac{1}{\lambda_i} \mathbf{u}_i \mathbf{u}_i^{T} \end{align}

を掛けて、 \mathbf{u}_i \mathbf{u}_i^{T}=\delta_{ij}を用いると、

\begin{align} \mathbf{C}^{-1}\frac{\partial \mathbf{C}}{\partial \theta} = \sum_i \frac{1}{\lambda_i} \frac{\partial \lambda_i}{\partial \theta} \mathbf{u}_i\mathbf{u}_i^{T} + \sum_i 2 \mathbf{u}_i\frac{\partial \mathbf{u}_i^{T}}{\partial \theta} \end{align}

となります。 \text{Tr}(a b^{T})=a^{T} bより

\begin{align} \text{Tr}\bigg(\mathbf{C}^{-1}\frac{\partial \mathbf{C}}{\partial \theta}\bigg) = \sum_i \frac{1}{\lambda_i} \frac{\partial \lambda_i}{\partial \theta} \mathbf{u}_i^{T}\mathbf{u}_i + \sum_i 2 \mathbf{u}_i^{T} \frac{\partial \mathbf{u}_i}{\partial \theta} \end{align}

となり、 ||\mathbf{u}_i||^2=1とこの両辺を微分して得られる

\begin{align} 2u_i^{T} \frac{\partial u_i}{\partial \theta} = 0 \end{align}

を代入することで、

\begin{align} \text{Tr}\bigg(\mathbf{C}^{-1}\frac{\partial \mathbf{C}}{\partial \theta}\bigg) = \sum_i \frac{1}{\lambda_i} \frac{\partial \lambda_i}{\partial \theta} \end{align}

となります。ここで、 |C|=\prod_i \lambda_iであるから、

\begin{align} \frac{\partial}{\partial \theta} \ln |C| &= \frac{\partial}{\partial \theta} \sum_i \ln \lambda_i \ &= \sum_i \frac{1}{\lambda_i}\frac{\partial \lambda_i}{\partial \theta} \end{align}

となります。以上より、

\begin{align} \frac{\partial}{\partial \theta} \ln |C| =\text{Tr} \bigg( \mathbf{C}^{-1} \frac{\partial \mathbf{C}}{\partial \theta} \bigg) \end{align}

です。本来はこのように直接微分して求めるのではなく、計算機上で近似して求めます。

GPソースコード

上記の一連の式を記載すればガウス過程回帰が行えます。

import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg as LA


class GaussianProcess:
    def __init__(self, kernel=None, alpha=1e-10, beta=2):
        self.kernel = kernel
        self.alpha = alpha
        self.beta = beta
        self.train_x = None
        self.train_t = None
        self.K = None
        self.Cinv = None
        self.a = None
        self.N = None

        if kernel is None:
            SIGMA = 1.0
            self.kernel = lambda x1, x2: np.exp(-(x1 - x2) ** 2 / (2 * SIGMA ** 2))

    def fit(self, train_x, train_t):
        # calculate Gram matrix
        N = train_x.shape[0]
        K = np.zeros((N, N))
        for i in range(N):
            for j in range(N):
                K[i, j] = self.kernel(train_x[i], train_x[j])

        C = K + np.identity(N) / self.beta
        Cinv = LA.inv(C)
        a = Cinv.dot(train_t)

        self.train_x = train_x
        self.train_t = train_t
        self.K = K
        self.Cinv = Cinv
        self.a = a
        self.N = N

    # m (x_{N+1}) = \sum_{n=1}^N a_n k(x_n, x_{N+1})
    def predict(self, new_x):
        y = 0
        for i in range(self.N):
            y += self.a[i] * self.kernel(self.train_x[i], new_x)
        return y

    def sigma(self, new_x):
        vec_k = np.array([self.kernel(train_x[i], new_x) for i in range(self.N)])
        kt_Cinv_k = vec_k.T.dot(self.Cinv).dot(vec_k)
        c = self.kernel(new_x, new_x)
        return np.sqrt(c + 1.0/self.beta - kt_Cinv_k)

    # p(t_{n+1} | t) = N( t | mu(x), sigma(x))
    def cond_p(self, new_x, train_t):
        mu = self.predict(new_x)
        sig = self.sigma(new_x)
        return 1/np.sqrt(2*np.pi*sig**2) * np.exp(-(train_t - mu)**2/(2*sig**2))

    # p(t) = \int p(t|y)p(y)dy = N(0, C)
    # K = phi(x_n) phi(x_m)^t
    # C = K/alpha
    def p_t(self):
        for i in range(10):
            y = np.random.multinomial(np.zeros(self.N), self.K/self.alpha)
        return y

    
def rbf_sine_kernel(x1, x2):
    return sum([np.sin(i*np.pi*x1)*np.sin(i*np.pi*x2) for i in range(n_basis)])

THETA0 = 1;THETA1 = 4;THETA2 = 0;THETA3 = 5
def normal_kernel(x1, x2):
    return THETA0 * np.exp(-THETA1*(x1-x2)**2/2) + THETA2 + THETA3*x1*x2

結果

f:id:udnp:20190330134901p:plain
ガウス過程回帰

カーネルの選択が大事になってきます。ベイズ最適化を行うには、獲得関数を計算することになります。

  • UCB(Upper Confidence Bound)
  • EI (Expected Improvement
  • PI (Probability of Improvement)

などがあります。次回、これらをまとめていきたいと思います。

強化学習入門(理論)その2

強化学習理論のまとめ(その2)です。その1(強化学習の一般的な定義から方策勾配法まで)は以下から。

udnp.hatenablog.com

DPG

paper

方策 \pi (a | s)は現在の状態sでの行動aに対する確率分布としてモデル化されますが、Deterministic policy gradient (DPG)は、決定的な行動 a = \mu (s)として方策がモデル化されます。

論文にある記載の通りに定義していきます。

  •  \rho_0 (s) : 初期の状態分布
  •  \rho^{\mu} (s → s', k) : 状態sからs'にk step変化したときの状態分布

このように定義すると、時間割引された状態分布は下式となります。

\begin{align} \rho^{\mu}(s') = \int_{\mathcal{S}} \sum_{k=0}^{\infty} \gamma^{k} \rho_0 (s) \rho^{\mu}(s → s', k)ds \end{align}

このとき最適化するための目的関数は以下の通りです。 \begin{align} J(\theta) = \int_{\mathcal{S}} \rho^{\mu}(s)Q(s, \mu_{\theta}(s))ds \end{align}

Deterministicのときの方策勾配定理は、上式をパラメータ \theta微分し得られます。

\begin{align} \nabla_{\theta} J(\theta) &= \int_{\mathcal{S}} \rho^{\mu}(s) \nabla_{a} Q^{\mu}(s, a) \nabla_{\theta} \mu_{\theta}(s) |_{a=\mu_{\theta}(s)}ds \\ &= \mathbb{E}_{s \sim \rho^{\mu}} [\nabla_{a} Q^{\mu}(s,a)\nabla_{\theta} \mu_{\theta}(s) |_{a=\mu_{\theta}(s)} ] \end{align}

決定的な方策を確率的な方策の特別な場合と考えることができます。DPGの論文 では確率的な方策 \pi_{\mu, \sigma}は、決定的な方策 \mu(s)とバリエーション \sigmaで表現され、 \sigma = 0とすれば決定的な方策と同一の式になります。

例としてDPGをon policy actor-critic with SARSAとして適用してみます。SARSAは以下の方策の更新式によって行動を決定するようになります。

\begin{align} \delta_t &= R_t + \gamma Q_w (s_{t+1}, a_{t+1}) - Q_w (s_t, a_t) \\ w_{t+1} &= w_t + \alpha_w \delta_t \nabla_w Q_w (s_t, a_t) \\ \theta_{t+1} &= \theta_t + \alpha_{\theta} \nabla_w Q_w (s_t, a_t)\nabla_{\theta} \mu_{\theta}(s) |_{a=\mu_{\theta}(s)} \end{align}

1式目がSARSAのTD誤差を表します。2式目は、TD誤差が得られた時のQ関数の勾配からQ関数のパラメータwを更新します。3式目が、Deterministic policy gradient (DPG)です。

しかし、環境に十分なノイズがなければ、決定的な方策のため十分な探索を宝生することは困難です。そこで方策にノイズを加えることもできます。これはoff-policyのアプローチになります。確率的方策 \beta(a|s)を導入します。

\begin{align} J(\theta) &= \int_{\mathcal{S}} \rho^{\beta}(s)Q(s, \mu_{\theta}(s))ds \\ \nabla_{\theta} J(\theta) &= \mathbb{E}_{s \sim \rho^{\beta}} [\nabla_{a} Q^{\mu}(s,a)\nabla_{\theta} \mu_{\theta}(s) |_{a=\mu_{\theta}(s)} ] \end{align}

ポリシーが決定的であるので、方策勾配定理での \sum_{a} \pi(a|s) Q^{\mu}(s,a)ではなく、状態sでの期待される報酬 Q^{\mu}(s,\mu_{\theta}(s))だけが必要なのがポイントです。行動によりません。off-policyのアプローチでは、行動とターゲット間でのずれを解消するために重点サンプリングがよく用いられます。しかし、行動に対する積分がないため、重点サンプリングせずとも求められます。

DDPG

[paper|code]

DDPG (Lillicrap, et al., 2015) Deep Deterministic Policy GradientはDPGとDQNを組み合わせたoff-policyなactor criticアルゴリズムです。 DQN (Deep Q-Network)はexperience replayとtarget networkの固定によりQ関数の学習を安定化させています。DQNは離散空間で動きますが、DDPGは連続空間で動くアルゴリズムです。

よりよい探索を行うため、探索方策 \mu'はノイズ \mathcal{N}を加えることで構築されます。

\begin{align} \mu'(s) = \mu_\theta (s) + \mathcal{N} \end{align}

加えて、DDPGはsoft update (conservative policy iteration)によるACのパラメータが更新されます。一連の流れは以下のようになります。

critic updateはtarget networkとQ値のMSEを最小化させることで更新します。

\begin{align} y_i &= r_{i} + \gamma Q'(s_{i+1}, \mu'(s_{i+1} | \theta^{\mu'}) | \theta^{Q}) \\ L &= \frac{1}{N} \sum_i (y_i - Q(s_i, a_i | \theta^{Q}) )^{2 } \end{align}

次のactor updateは方策勾配法を用います。

\begin{align} \nabla_{\theta} J(\theta) \approx \frac{1}{N}\sum_{i} [\nabla_{a} Q(s,a | \theta^{\mu})\nabla_{\theta} \mu_{\theta^{\mu}}(s) |_{a=\mu_{\theta}(s)} ] \end{align}

target networkの更新は次の式です。

\begin{align} \theta^{Q'} = τ \theta^{Q} + (1-τ) \theta^{Q'} \\ \theta^{\mu'} = τ \theta^{\mu} + (1-τ ) \theta^{\mu'} \end{align}

ここで、 \tau \ll 1です。このようにtarget networkの値を制約することで、方策はゆるく変化します。一連のDDPGのアルゴリズムは下記のようになります。

f:id:udnp:20190303123910p:plain
DDPG algorithm

D4PG

openreview.net

Distributed Distributional DDPGはDDPGに分布を追加することでDDPGを改善した方法になります。

※ 1. Distributional Critic criticはwでパラメタライズさら分布Zを用いてサンプリングし、Q関数の期待値を推定します。したがって、Q関数を次のように表現できます。

\begin{align} Q_w (s, a ) = \mathbb{E}Z_w(x, a) \end{align}

分布のパラメータを学習するための損失は2つの分布間距離を最小化するように決めます。distributional TD errorを定義します。

\begin{align} L(w) = \mathbb{E} [d (\mathcal{T}_{\mu_{\theta}}, Z_{w'}(s, a), Z_{w}(s, a) ] \end{align}

ここで \mathcal{T}はベルマンオペレータです。決定的な方策勾配は

\begin{align} \nabla_{\theta} J(\theta) \approx \mathbb{E}_{\rho^{\mu}}[\nabla_{a} Q(s,a)\nabla_{\theta} \mu_{\theta}(s) |_{a=\mu_{\theta}(s)} ] \\ =\mathbb{E}_{\rho^{\mu}}[\mathbb{E} [\nabla_{a} Q(s,a)] \nabla_{\theta} \mu_{\theta}(s) |_{a=\mu_{\theta}(s)} ] \end{align}

1式目は、方策勾配法です。2式目はQ値分布の期待値です。

※ 2. N-step報酬 TD誤差を計算するとき、D4PGは報酬の先読みを1stepではなくN-step TD targetを計算します。

\begin{align} Y_i = r(s_0, a_0) + \mathbb{E} [\sum_{n=1}^{N-1} r(s_n, a_n) + \gamma^{N} Q(s_N, \mu_{\theta}(s_N) |s_0, a_0] \end{align}

※ 3. Multiple Distributed Parallel Actors D4PGはKの独立なactorを用いて、並列で経験を集め、同じreplay bufferにデータをフィードします。

※ 4. Prioritized Experience Replay (PER) 最後に一様分布でない分布 p_iとともにreplay buffer Rからサンプリングします。重点サンプリングの重みを (Rp_i)^{-1}としています。

f:id:udnp:20190303132524p:plain
D4PG algorithm

MADDPG

arxiv.org

Multi-agent DDPG (MADDPG)はDDPGをmulti agentに対応させたものです。1つのagentの視点において、環境は静止していません。他のagentの方策はすぐに更新され、その方策は未知のままになってしまいます。MADDPGはそのような環境変化とagent間の相互作用を扱うためのactor-criticモデルになっています。

この問題はMDP, a.k.a Markov gamesにおけるマルチエージェントで定式化できます。それぞれの状態セットSでN agents存在すると仮定します。方策パラメータを \vec{\theta} = \{\theta_1, ..., \theta_{N}\}, 行動セット \{\mathcal{A_1}, ..., \mathcal{A_{N}}\}と観測セット \{\mathcal{O_1}, ..., \mathcal{O_N}\}を持っています。状態遷移関数はすべての状態、行動、そして観測空間 \mathcal{T}: \mathcal{S}×\mathcal{A_1}× ... \mathcal{A_N} \longmapsto \mathcal{S}に関わっています。一方、各agentの確率的方策は自身の状態と行動にのみ関連し、 \pi_{\theta_i} : \mathcal{O_i}×\mathcal{A_i} \longmapsto [0, 1]で記載されます。自身の観測を所与とする行動に対する確率分布、決定的な方策は、 \mu_{\theta_i} : \mathcal{O_i}×\mathcal{A_i}です。

MADDPGのcriticは中央化された行動価値関数 Q_i^{\mathbf{\mu}}(\mathbf{o}, \mathbf{a})を学習します。決定方策、観測、行動はそれぞれベクトルのボールド表記で記載しています( \mathbf{a}=a_1,..., a_Nなど)。各Qは別々に学習されることで、複数のagentは任意の報酬構造を持つことができます。一方、複数のactorは自身の持つパラメータ \theta_iを探索・更新していきます。

  • actorの更新のための勾配

\begin{align} \nabla_{\theta_i} J(\theta) = \mathbb{E}_{\mathbf{o}, a \sim \mathcal{D}}[\nabla_{a_i} Q_i^{\vec{\mu}}(\mathbf{o},\mathbf{a})\nabla_{\theta_i} \vec{\mu}_{\theta_i}(o_i) |_{a_i=\vec{\mu}_{\theta_i}(o_i)} ] \\ \end{align}

ここで、 \mathcal{D}はexperience replay bufferです。全てのagentの経験 (\mathbf{o}, \mathbf{o'}, a_1, ..., a_N, r_1, ..., r_N)を含んでいます。

  • criticの更新(中央化された行動価値関数の更新)

\begin{align} L(\theta_i) &= \mathbb{E}_{\mathbf{o}, \mathbf{a}, \mathbf{r}, \mathbb{o'}} [(Q_{i}^{\vec{\mu}}(\mathbf{o}, \mathbf{a} ) - y)^{2}] \\ y &= r_i + \gamma Q_i^{\vec{\mu'}} (\mathbf{o'},\mathbf{a'}) \end{align}

ここで、 \vec{\mu'}はソフトアップデートされたtarget policyです。概略図を下図に示します。

f:id:udnp:20190303163001p:plain

他のagentからの方策の推論や、方策アンサンブルなども提案されています。他のagentの方策を知っているという前提を除くために、近似の方策を導入時します。これを用いるとagentの対数分布とエントロピーHの正則化を追加すると下式になります。

\begin{align} L(\phi_i^{j}) & = - \mathbb{E}_{o_j, a_j} [\log \vec{\mu}_{i}^{j} (a_j | o_j) + \lambda H(\vec{\mu}_{i}^{j})] \\ \hat{y} &= r_i + \gamma Q_i^{\vec{\mu'}} (\mathbf{o'},\vec{\mu_i}(\mathbf{o})) \end{align}

参考・引用文献

主に以下のサイトを参考にさせて頂きました。実装例はOpen AIのbaselineに大体あります。 大方まとまってきたら、改めてコンパイルしたいと思います。

lilianweng.github.io

Automatic Chemical Design Using a Data-Driven Continuous Representation of Molecules

実装する前にChemical VAEについて論文概要をまとめます。原著は以下にあります。

https://pubs.acs.org/doi/abs/10.1021/acscentsci.7b00572

概要

SMILESをVariational Autoencoderで学習させ、潜在空間のベクトルから物性値の予測モデルを作成することで目的の物性をもつ分子を生成しようとしています。Autoencoderでは、分子を表現するためのSMILESの組み合わせが膨大なため、目標となる物性値をもつ正しいSMILESが生成されません。一方、VAEでは潜在空間をガウス分布で近似するため、より有効なSMILESが生成されるという考え方です。

  • エンコーダーは分子の離散表現を連続的な空間に変換する。
  • デコーダーはこの連続空間を離散空間に復元する。
  • 予測器は分子の潜在空間の連続的なベクトル表現から物性値を予測する。

分子設計の難しさ

薬や材料設計の目的は望ましい物性値を有する新規性(novel)分子を同定することです。これは最適化問題に帰着します。

定量的な切なる願い(quantitative desiderata)を最大化する分子探索と表現されてます。

しかし、分子空間の最適化は極めて困難と言われています。探索空間は広大、離散的かつ構造化されてなく、原子30個の組み合わせで1060の候補があるとされています。実際に合成されているのは108程度です。

このような問題を解決しようと様々な探索方法が研究されています。本論文のVAEやGCN, GAN, RNN, MCTS, 強化学習などを用いた方法などが提案されてきました。本記事では、このVAEの手法について記載します。いずれ他の方法も記載したいと思います。

GCN + GAN + DDPGを使った分子生成法MolGANについてはQiitaに記事を書きました。

qiita.com

方法

f:id:udnp:20190221234237p:plain

(a) 分子設計に用いられる物性予測モデルを含んだオートエンコーダ-のダイアグラムです。分子をSMILESの文字列表現にし、潜在空間に埋め込みます。潜在空間上のある点を与えればデコーダーにより復元できます。物性値はMLPで予測します。

(b) 潜在空間の勾配法による最適化です。学習されたサロゲートモデルf(z)が潜在表現の特徴zを用いて物性値を予測します。目標となる物性値が高いと予測される点を探すためにzに関してf(z)を最適化させます。Gaussian Processを用いています。

アーキテクチャ

SMILESを復元させるためにRNNのGRU, sequence to sequence (seq2seq)を用いています。SMILESではなくInChIでもテストしたようですが、こちらだとSMILESより性能が悪化したようです。おそらくは表現がより複雑であるためです。また、string encodingの畳み込みも実験したところ性能が改善しています。これは環構造や原子団寄与といった化学構造を不変とできたためと説明できます。

ZINC dataset

  • エンコーダー: 3つの 1D 畳込み層。filter size (9, 9, 10), (9, 9, 11), width 196
  • デコーダー: 3層gated recurrent unit (GRU)ネットワーク。隠れ層488
  • 予測器の目的変数: logP, QED, SAS

QM9 dataset

  • エンコーダー: 3つの 1D 畳込み層。filter size (2, 2, 1), (5, 5, 4), width 156
  • デコーダー: 3層gated recurrent unit (GRU)ネットワーク。隠れ層500
  • 予測器の目的変数: HOMO, LUMO, electronic spatial extent

物性予測については、2 fully connected layers of 1000 neurons, dropout 0.20。簡単にするために3 layers of 67 neurons, dropout 0.15も物性予測に用いられている。

コード

実装はkerasでされてます。

ネットワーク

f:id:udnp:20190222225427p:plain

強化学習入門(理論)

強化学習に必要な理論についてまとめていきます。様々な数式表記がありますが、「これからの強化学習」にできるだけ統一していきます。間違った記述があれば、ご指摘いただければ幸いです。日々アップデートしていきます。よろしくお願いいたします。

序文

強化学習機械学習のサブトピックの1つです。機械学習には、

の3つに大別されます。強化学習モデルは環境からの観測と報酬によってのみ学習されます。長期的な報酬を最大化させるようにモデルを構築していきます。

f:id:udnp:20180708214421p:plain

強化学習ですが、教師あり、教師なし学習とは異なり環境探索型の学習です。上図に示すように「環境」における「価値」を最大化させるようにエージェントを学習させていきます。強化学習の課題でよく見かけるのはゲームです。例えば、ブロック崩しでは、エージェントはプレーヤで、その行動はブロックを壊すことです。

出典: https://openai.com/blog/universe/

Markov Decision Process (MDP)

MDPが一般的な強化学習の大枠だと思います。MDPは現在の状態 sと行動 aから次の状態 s'が決まるということです。状態遷移確率 \mathcal{P}は次式のように表されます。

\begin{align} \mathcal{P_{ss'}^a}=\mathcal{P}(s, a, s')=\mathcal{p}\{s_{t+1}=s'| s_t=s, a_t=a\} \end{align} ここで、状態集合を \mathcal{S}= \{ s_1, s_2, ..., s_N \} 、行動集合を \mathcal{A}= \{ a_1, a_2, ..., a_M \} とすれば、 t+1ステップ目における状態 s_{t+1}  tステップ目からの遷移確率によって決まります。

\begin{align} S_{t+1} \sim \mathcal{P}(s' | S_t, A_t) \end{align}

上式からもわかるように、MDPは1つ前の状態および行動から次の状態が決まっています。状態遷移確率が既知のものをモデルベース強化学習と呼ばれます。例えば、囲碁は次の状態遷移が確実にわかるため、モデルベース強化学習の手法を適用することができます。現在の状態 S_tで行動 A_tを選択したとき、報酬 R_tが得られ、次の状態 S_{t+1}に遷移します。すなわち、報酬関数を下式のように表現することができます。

\begin{align} R_{t} = r(S_t, A_t, S_{t+1}) \end{align}

この報酬の履歴を管理する際は、$(S_t, A_t, R_t, S_{t+1})$という形で保存したりします。

累積報酬

教師あり学習」との違いになりますが、強化学習では最終的な報酬である累積報酬和が最大となるように行動するようなモデルになります。そのため次に示すような「累積報酬」 Gを導入します。

\begin{align} G_t = \sum_{τ=0}^{T} \gamma^{τ} R_{t+τ} \end{align}

ここで、 \gammaは時間割引率です。ゴールから遠いステップになるほど報酬の価値は低くなる経済学の理論に基づいています。この値は \gamma=0.99が多いです。1に近いほどより長期的に有益な行動を評価するようになります。割引率が0に近いほど収益はステップ tにおいて得られる報酬に近くなります。

価値関数

価値関数とは、ある状態・行動における累積報酬の予測関数です。この値を基に、ある状態が与えられたときの行動指針である方策(Policy) \piを決定します。具体的には、行動価値関数 Q^{\pi}、状態価値関数 V^{\pi}を用いて、直接行動を選択することができます。

状態価値関数(state value function)

\begin{align} V^\pi(s) = \mathbb{E}[\sum_{τ=0}^\infty \gamma^{τ} R_{τ} | S_τ=s] \end{align}

ある方策 \piに従って行動を選択すると、環境により状態 sが決まります。状態価値とは、この時の状態で得られた累積報酬和の推定量となります。この状態価値が最大となるような行動を選択するための方策を決めるアルゴリズムはon policyや方策ベースと呼ばれます。

行動価値関数(action value function)

\begin{align} Q^\pi(s, a) = \mathbb{E}[\sum_{τ=0}^\infty \gamma^{τ} R_{τ} | S_τ=s, A_τ=a] \end{align}

状態 sのとき行動 aをしたとき期待される報酬値です。ここでの期待価値と実際に得られた報酬を比較することで、価値を最大化させます。この行動価値が最大となるように行動を決める方法はoff policyや価値ベースと呼ばれます。

最適ベルマン方程式

MDP成立時の最適価値を求めるための式になります。上記の価値関数は再帰的な関係があり、ステップ t+2以降で式変形することと次のようになります。

\begin{align} V^\pi(s) &= \mathbb{E}^{\pi} [\sum_{k=0}^\infty \gamma^{k} r_{t+1+k} | s_{t}=s ] \\ &= \mathbb{E} [r_{t+1} | s_t = s ] + \gamma\mathbb{E} [\sum_{k=0}^\infty \gamma^{τ} r_{t+2+k} | s_{t+1}=s' ] \\ \end{align} ここで、状態 sのときの累積期待報酬は、それまで全ての行動( \sum_a \pi(a|s))と状態遷移したときの報酬和で求まります。

\begin{align} \mathbb{E^\pi}[r_{t+1}|s_t=s] = \sum_a \pi(a|s)\sum_{s'}\mathcal{P}(s, a, s')R(s,a,s') \end{align} 上式と、再帰的な式を利用すると

\begin{align} V^\pi(s) &= \sum_a \pi(a|s)\sum_{s'}\mathcal{P}(s, a, s')R(s,a,s') + \gamma \sum_a \pi(a|s)\sum_{s'}\mathcal{P} V^\pi(s') \\ &= \sum_a \pi(a|s)\sum_{s'}\mathcal{P}(s, a, s')\big(R(s,a,s')+\gamma V^\pi(s')\big) \end{align}

Qについても同様です。

\begin{align} Q^\pi(s,a) &= \sum_{s'}\mathcal{P}(s, a, s')\big(R(s,a,s')+\gamma \sum_a \pi(a'|s')Q^\pi(s',a')\big) \end{align}

これらの二つの式を合わせると次のようになります。

\begin{align} Q^\pi(s,a) &= \sum_{s'}\mathcal{P}(s, a, s')\big(R(s,a,s')+\gamma V^{\pi}(s')\big) \end{align}

状態遷移確率が既知ならベルマン方程式を解くことで状態や行動の価値がわかります。しかし、基本的には状態遷移確率はわからないので試行錯誤で決めていきます。ベルマン方程式を解くことができ、価値関数を基にして行動を決定すれば、最適な結果となります。

Policy(方策)とは

環境からの観察により状態が決まり、エージェントは行動を選択します。環境から状態 sを観測したときに、行動 aを取る確率分布が方策(policy)です。この方策を \pi(a|s)とすると A_t \sim \pi(a|s_t)となります。

方策関数には様々なものが提案されています。微分可能な方策関数は後述する方策勾配法で用いられます。活用と探索の例を見ていきます。

greedy 法

{} \begin{align} \pi(a|s) = \left\{ \begin{array}{} 1 & (a = \arg \max_a Q(s, a)) \\ 0 & ( \text{otherwise}) \end{array} \right. \end{align}

greedy方策(貪欲法)は最適行動価値関数が既知であれば最適な行動をとります。しかし、このQ関数は未知であるため、推定していく必要があります。また、初めの状態ではQ関数は間違っているため、この値を基にした方策では間違った行動をし続ける危険性があります。遷移確率が既知であれば、後述するベルマン方程式により価値関数は解析的に求めることができます。しかしながら通常、遷移確率は未知であるため、価値関数を近似するアルゴリズム(Q-learning, Sarsa, DQNなど)を用いてエージェントの行動が決まります。

epsilon-greed法

一方、 \epsilongreedでは乱数の確率が \epsilonより大きければQ関数の最大値を利用、小さければ利用を行う方法です。貪欲法の同じ行動を起こすことを防ぐ方法です。

\begin{align} \pi(a|s) = \left\{ \begin{array}{} 1-\epsilon + \frac{\varepsilon}{|\mathcal{A}(s)|} & (a = \arg \max_a Q(s, a)) \\ \frac{\varepsilon}{|\mathcal{A}(s)|} & ( \text{otherwise}) \end{array} \right. \end{align}

Softmax関数

ボルツマン分布がよく使用されます。

\begin{align} \pi_\theta (a | s) = \frac{\exp(\frac{1}{\beta} Q_\theta(s, a))}{\sum_{j=1}^{T}\exp(\frac{1}{\beta} Q_\theta(s, a))} \end{align}

方策を \thetaでパラメタライズしています。

Dynamic Programming (DP)

動的計画法は、部分問題を交互に記録しながら解いていく方法です。強化学習の枠組みでは、価値反復法と方策反復法の大きく2通りが考えれます。上記ベルマン方程式を求める際に用いられます。環境が既知の場合は、この式は反復法により直接的に解くことができます。

モンテカルロ法(MC)

モンテカルロ法では報酬の期待値を平均とすることで近似しています。状態のパスを通るたびに回数をカウントしていき、それまでの累積報酬和との平均を取ることで状態価値関数を推定します。もしくは一度取ったパスの累積報酬を用いる場合もあります。モンテカルロ法では、エピソードが終了するまで価値の更新を待つ必要があります。

エピソード$i$においてTステップ目までの累積報酬は以下の式で表されます。遠くのステップの報酬ほど時間割引率γによって価値を減衰させていきます。

$$ G_{i, t} = r_{i, t} + \gamma r_{i, t+1} + \gamma^{2} r_{i, t+2} + \dots + \gamma^{T_{i}-1} r_{i, T_t} $$

Every-Visit Monte Carlo

状態価値$V$は次のように更新されます。

$$ \begin{align} N(s) &= N(s) + 1 \\ G(s) &= G(s) + G_{i, t} \\ V^{\pi}(s) &= \frac{G(s)}{N(s)} \end{align} $$

パスカウントを1つ増やし、以前の累積報酬和に対して今回得られたものを足し合わせます。最後にそれをパスカウントで割り、状態$s$の価値とします。Every visit MCではパスを通るたびに状態価値の更新を行います。この場合ではVの推定量は偏ったものになります(バイアス)。しかし収束性が良く、より良いMSEとなります。

First-Visit Monte Carlo

First visit MC法では、Every-Visit MCと異なり、1度取ったパスのみ更新を行います。 直接報酬を使うため状態価値関数の推定量にはバイアスがなく、真の$\mathbb{E}_{\pi} [G_t | s_t = s]$となります。回数が増えるにつれて一定のところに収束します。

Incremental Monte Carlo

一方、パスの途中でも更新する場合は、後述するTD学習に類似する式変形を行います。

$$ \begin{align} V^{\pi}(s_t) := V^{\pi}(s_t) + \alpha[ G_{i, t} - V^{\pi}(s_t)] \end{align} $$

  • $\alpha = \frac{1}{N(s)}$: every visit MCと同一
  • $\alpha \gt \frac{1}{N(s)}$: 古いデータを忘れます。非定常のドメインで役立つ

繰り返し累積報酬和と状態価値の誤差を足し合わせることで状態価値推定します。

Qiitaに書いた記事の多腕バンディット問題でも、状態価値は$\alpha = \frac{1}{n}, G_{i, t} = r_{i, t}$とした場合の逐次モンテカルロによって推定したものです。多腕バンディットでは状態遷移は行われませんが、MC法によって状態価値を推定でき、最適な行動を行えるようになっていきます。

Monte-Carlo Tree Search (MCTS)

モンテカルロ法による価値推定に類似する考え方にMCTSがあります。これはヒューリスティックアルゴリズムです。ボードゲーム、チェスや囲碁のようなモデルベース強化学習で用いられています。

  • Selection -> Expansion -> Simulation -> Backpropagation

という流れでノード(状態)を拡大していくことで最適な行動を探索していくことができます。

Temporal Difference (TD) Learning

TD learningはMCとDPを組み合わせた考え方です。最終的な結果を待たずに価値を推測できます。TD(0)、one-step TDの更新式は下式のように表せます。

\begin{align} V(s_t) := V(s_t) + \alpha[ R_{t} + \gamma V(s_{t+1}) - V(s_t)] \end{align}

TD学習ではTDターゲットと呼ばれる$R_{t} + \gamma V(s_{t+1})$ と $V(s_t)$との差(TD誤差)を使って更新していきます。

SARSA (on-policy TD control)

価値関数のベルマン方程式を解く(近似する)ためのアルゴリズムがSARSAです。一般的に環境の遷移確率や方策は未知です。そこで実際の観測値(s,a,r,s',a')に基づき、行動価値関数を求めます。SARSAの元のベルマン方程式には方策 \piを使用するため、on-policy型と呼ばれています。

\begin{align} Q(s_t, a_t) := Q(s_t, a_t ) + \alpha ( {R_{t} + \gamma Q(s_{t+1}, \pi(s_{t})) - Q(s_t, a_t))} \end{align}

Q-learning (off-policy TD control)

一方Q-leraningでは、次の行動におけるQ関数式に方策が含まれていないのでoff-policyです。(Watkins, 1989)

\begin{align} Q(s_t, a_t) := Q(s_t, a_t ) + \alpha ( {R_{t} + \gamma \max_{a} Q(s_{t+1}, a) - Q(s_t, a_t))} \end{align}

方策勾配法 ( Policy Gradient )

Policy gradient methodsは報酬の勾配を繰り返し推定することで期待報酬を最大化する方法です。

方策勾配定理

 \thetaでパラメタライズされた確率的な方策 \pi_\thetaを考えます。この方策は最適な行動をするための確率分布となります。

\begin{align} \pi_\theta = p_\theta (a_t | s_t) \end{align}

ここで、 pは状態 s_tにおける行動 a_tに対する確率分布です。  τをステップ0からHまでの(state, action)系列  τ = (s_0, a_0, ..., s_H, a_H ) とし、このとき方策の評価関数を下式のように定義します。

\begin{align} J(\theta) &= \mathbb{E}_{\pi_{\theta}} [\sum_{t=0}^H \mathcal{R} (s_t, a_t)] \\ &= \sum_{τ} \mathcal{P}(τ ; \theta) \mathcal{R}(τ) \end{align}

ここで、  \mathcal{R}(τ)=\sum_{t=0}^H \mathcal{R} (s_t, a_t)としています。  \mathcal{P}(τ ; \theta)は状態遷移確率モデルです。すなわち、

\begin{align} \mathcal{P}(τ ; \theta) = \prod_{t=0}^H \mathcal{P}(s_{t+1} | s_t, a_t) \pi_\theta (a_t | s_t) \end{align}

です。これは全ての方策での行動とその結果として得られる状態遷移確率の尤度となります。方策を学習するためには、この尤度関数を最大化させれば良いです。

\begin{align} \max_{\theta} J(\theta) = \max_{\theta} \sum_{τ} \mathcal{P} (τ ; \theta) \mathcal{R}(τ) \end{align}

状態遷移 \mathcal{P}が既知であるものはモデルベース強化学習と呼ばれています。

尤度の最大化

この目的関数を最大にするためにJに対する勾配 \nabla_\theta = \frac{dJ}{d\theta}を求めることで方策を更新していきます。

\begin{align} \nabla_\theta J(\theta) &= \nabla_{\theta}\sum_{τ} \mathcal{P} (τ ; \theta) \mathcal{R}(τ) \\ &=\sum_{τ} \nabla_{\theta} \mathcal{P} (τ ; \theta) \mathcal{R}(τ) \\ &=\sum_{τ} \nabla_{\theta}\frac{ \mathcal{P} (τ ; \theta)}{ \mathcal{P} (τ ; \theta)} \mathcal{R}(τ) \\ &=\sum_{τ} \mathcal{P} (τ ; \theta) \nabla_{\theta} \log{ \mathcal{P} (τ ; \theta)} \mathcal{R}(τ) \\ &= \mathbb{E}_{\pi_\theta} [ \nabla_{\theta} \log{ \mathcal{P} (τ ; \theta)} \mathcal{R}(τ) ] \end{align}

この勾配式より、報酬Rが高い遷移の場合に方策が大きく更新されます。次に方策と遷移確率について立式します。

\begin{align} \nabla_{\theta} \log{ \mathcal{P} (τ ; \theta)} &= \nabla_{\theta} \log{ \prod_{t=0}^H \mathcal{P}(s_{t+1} | s_t, a_t) \pi_\theta (a_t | s_t)} \\ &= \nabla_{\theta} [ \sum_{t=0}^H \log{\mathcal{P}(s_{t+1} | s_t, a_t)] + \nabla_\theta [ \sum_{t=0}^H \log{ \pi_\theta (a_t | s_t)}}] \\ &= \nabla_\theta \sum_{t=0}^H \log{ \pi_\theta (a_t | s_t)} \\ &= \sum_{t=0}^H \nabla_\theta \log{ \pi_\theta (a_t | s_t)} \end{align}

以上により、方策勾配は下式です。

\begin{align} \nabla_\theta J(\theta) = \mathbb{E}_{\pi_\theta} [\sum_{t=0}^H \nabla_\theta \log{ \pi_\theta (a_t | s_t)} \mathcal{R}(s_t, a_t)] \end{align}

Hステップ目まで方策$\pi_{\theta}$に基づいて行動し、これまでの報酬を取得します。方策ネットワークの出力値はsoftmaxで正規化され、行動の出力分布になっています。これを離散値の場合はカテゴリカル分布でサンプリングすることで行動を決定します。方策の勾配を計算するために確率密度関数から対数尤度を計算し、それとこれまで得られた報酬関数のベクトル値をかけて平均を取り最大化させます。

交差エントロピーとの関係性

方策勾配法はクロスエントロピーと密接な関係があります。クロスエントロピーの式は以下となります。

\begin{align} H(p,q) = \mathbb{E}_p [- \log q] = H(p) + D_{KL} (p || q ) \end{align}

ここで、pとqが離散ならば、

\begin{align} H(p, q) = - \sum_x p(x) \log q(x) \end{align}

となります。クロスエントロピーを使った方法でも方策を学習することができます。この場合では、(state, action, reward)を収集し、良かったペアを選別します。そのような行動を取れるように方策を学習させます。すなわち、方策のlogit値と行動ベクトルとのクロスエントロピー誤差を計算し最小となるように方策を学習します。これは方策勾配法での良いエピソードであったらR=1にし、悪いエピソードであった場合0にしたものと同じです。

方策勾配法のアルゴリズム

報酬の決め方によってアルゴリズムがやや異なります。

  1.  \sum_{t=0}^{\infty} r_t: トラジェクトリーの総報酬.
  2.  \sum_{t'=t}^{\infty} r_t': 行動 a_tに続く報酬
  3.  \sum_{t'=t}^{\infty} r_t' - b(s_t): 2. ベースラインを導入したもの
  4.  Q^{\pi}(s_t, a_t): state-action value function
  5.  A^{\pi}(s_t, a_t): advantage function
  6.  r_t + V^{\pi}(s_{t+1}) - V^{\pi}(s_t): TD誤差

https://arxiv.org/pdf/1506.02438.pdf

Baseline

方策勾配の収束性を上げるために、baselineを導入する方法が提案されています。これにより分散を減少させることができます。

\begin{align} \nabla_\theta J(\theta) = \mathbb{E}_{\pi_\theta} [\sum_{t=0}^H \nabla_\theta \log{ \pi_\theta (a_t | s_t)} \big(\mathcal{R}(s_t, a_t) -b \big)] \end{align}

vanilla policy gradient

 \thetaだけでなくbaseline bも最適化します。ここでのベースラインbは、報酬との最小2乗法により求めます。

REINFORCE

複数の遷移情報を使って計算した \mathcal{R}_tの代わりに、その時々の報酬[r_t]を使う方法。baselineは報酬の平均値を用いることが多いです。

  • REINFORCEの問題点

全ての行動における累積報酬は平均値を用いるため、一部の行動により悪い結果となったとしても、その行動が考慮されないことになります。

Actor-Critic

上記アルゴリズムは全て方策ベースのアルゴリズムです。REINFORCEではQ関数を報酬の平均値で近似していますが、上記で述べたように学習に用いられていません。一方、Actor Criticでは価値ベースも加えたhybrid法となっています。

  • Criticでは、どの程度行動が良いかを評価する。 (policy - based)
  • Actorでは、Criticの基準で更新された方策に基づき行動する。(value - based)

  • Q関数との関係

全ての状態を考慮し、時間割引率を \gamma、行動価値関数をQとすると、Actorモデルの更新は下式の方策勾配法で表現できます。

\begin{align} \nabla_{\theta} J(\theta) &= \mathbb{E}_{\pi_\theta} [\sum_{t=0}^{\infty} \nabla_{\theta} \log{\pi_{\theta} (a_t | s_t)} \sum_{k=t}^{\infty} \gamma^{k} r(s_k, a_k) ] \\ &= \mathbb{E}_{\pi_{\theta}} [\sum_{t=0}^{\infty} \nabla_{\theta} \log{ \pi_{\theta} (a_t | s_t)} ] \hat{Q}_w(s_t, a_t) \end{align}

これで、各ステップにおいて方策を更新することができる。その代わりに、Criticモデルはこの価値関数 Qを近似するように学習させる必要がある。 Qをパラメータ w微分し、この勾配を用いてQ関数を更新するが、TD誤差も用いる。

\begin{align} \text{TD}_{Error} = R(s_t, a_t) + \gamma \hat{Q}_w (s_{t+1}, a_{t+1}) - \hat{Q}_w (s_t, a_t) \\ \nabla w = \text{TD}_{Error} \nabla_{w} \hat{Q}_w (s_t, a_t) \end{align}

ActorとCriticモデルにおいて学習率は変え、それぞれ別々に最適化されなければならないです。これは収束性の問題です。

Actor Critic

ステップ tにおいて環境から状態 s_tが得られる。インプットとしてActorとCriticによりそれぞれ、方策 \pi_{\theta}と価値 \hat{Q}_wが得られる。 この方策 \pi_{\theta}に基づき、Actorからのアウトプットとして a_tが得られる。新しい状態 s_{t+1}と報酬 r_{t+1}が得られる。そのおかげで、

Criticはその状態での行動の価値(Q値)を計算。 ActorはこのQ値を用いて方策のパラメータを更新する。

\begin{align} \nabla_\theta = \alpha \nabla_\theta \log{ \pi_\theta (a_t | s_t)} \hat{Q}_w(s_{t}, a_t) \end{align}

パラメータ \thetaが更新されたおかげで、Actorは状態 s_{t+1}において、行動 a_{t+1}を取り、環境から状態 s_{t+2}と報酬 r_{t+1}が得られます。Criticはこの値を元にパラメータ wを更新します。

\begin{align} \text{TD}_{Error} = R(s_t, a_t) + \gamma \hat{Q}_w (s_{t+1}, a_{t+1}) - \hat{Q}_w (s_t, a_t) \\ \nabla w = \text{TD}_{Error} \nabla_{w} \hat{Q}_w (s_t, a_t) \end{align}

A2C

Advantage Actor Criticでは、baselineに状態価値関数 V^{\pi} (s)を、報酬部分にQ(s, a)を用いる方法です。これはそれぞれ、その状態における平均価値Vと状態sを取ったときの行動価値Qであり、その差を取ったAdvantage関数を利用しています。

\begin{align} A(s, a) = Q^{\pi}(s, a) - V_{\theta}(s) \\ \nabla_\theta J(\theta) = \alpha \nabla_\theta \log{ \pi_\theta (a_t | s_t)} A(s, a) \end{align} Advantage関数は、ある状態でとった行動の平均と比較し改善することを意味しています。

  • A(s, a) > 0 : 勾配は正しい方向へ。
  • A(s, a) < 0 : 行動が状態平均よりも悪い。

\begin{align} A(s, a) &= Q(s, a) - V(s) \\ &= r + \gamma V(s') - V(s) \\ &= \text{TD}_{Error} \end{align}

遠くまで先読みするとREINFORCEに近くなります。A(s, a)の二乗が最小となるように更新します。

from ray.rllib.policy.sample_batch import SampleBatch
from ray.rllib.evaluation.postprocessing import discount_cumsum


def discount_cumsum(x: np.ndarray, gamma: float) -> np.ndarray:
    """Calculates the discounted cumulative sum over a reward sequence `x`.
    y[t] - discount*y[t+1] = x[t]
    reversed(y)[t] - discount*reversed(y)[t-1] = reversed(x)[t]
    Args:
        gamma: The discount factor gamma.
    Returns:
        The sequence containing the discounted cumulative sums
        for each individual reward in `x` till the end of the trajectory.
    Examples:
        >>> x = np.array([0.0, 1.0, 2.0, 3.0])
        >>> gamma = 0.9
        >>> discount_cumsum(x, gamma)
        ... array([0.0 + 0.9*1.0 + 0.9^2*2.0 + 0.9^3*3.0,
        ...        1.0 + 0.9*2.0 + 0.9^2*3.0,
        ...        2.0 + 0.9*3.0,
        ...        3.0])
    """
    return scipy.signal.lfilter([1], [1, float(-gamma)], x[::-1], axis=0)[::-1]


rollout= SampleBatch(
    {
        SampleBatch.OBS: np.array(
            [[0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8], [0.9, 1.0, 1.1, 1.2]]
        ),
        SampleBatch.ACTIONS: np.array([0, 1, 1]),
        SampleBatch.REWARDS: np.array([1.0, 1.0, 1.0]),
        SampleBatch.DONES: np.array([False, False, True]),
        SampleBatch.EPS_ID: np.array([1234, 1234, 1234]),
        SampleBatch.AGENT_INDEX: np.array([0, 0, 0]),
    }
)

last_r = 0.0
gamma = 0.99
rewards_plus_v = np.concatenate([rollout[SampleBatch.REWARDS], np.array([last_r])])
discounted_returns = discount_cumsum(rewards_plus_v, gamma)[:-1].astype(np.float32)
> array([2.9701, 1.99  , 1.    ], dtype=float32)
# A = [0.99^2 * 1.0 + 0.99 * 1.0 + 1.0, 0.99 * 1.0 + 1.0, 1.0] = [2.9701, 1.99, 1.0]

GAE

Generalized advantage estimatorでは、方策勾配法学習中のバリアンスを減らすために、上記の価値関数に時間割引率 \gamma \in [0, 1] と パラメータ \lambda \in [0, 1]を導入したものです。

# V(s)と最後の報酬を結合
vpred_t = np.concatenate([rollout[SampleBatch.VF_PREDS], np.array([last_r])])

# TD誤差の計算
delta_t = rollout[SampleBatch.REWARDS] + gamma * vpred_t[1:] - vpred_t[:-1]

# Advantage計算(総和)
rollout[Postprocessing.ADVANTAGES] = discount_cumsum(delta_t, gamma * lambda_)
rollout[Postprocessing.VALUE_TARGETS] = (
            rollout[Postprocessing.ADVANTAGES] + rollout[SampleBatch.VF_PREDS]
        ).astype(np.float32)

条件付き変分オートエンコーダ (conditional VAE, M2)

[変分オートエンコーダー (VAE, M1)]の続きです。

conditional VAE, M2

M1モデルに対して、ラベル付きのデータを入力できるようにしたモデルです。

モデル

 yをクラスラベルを表すとします。 M2のグラフィカルモデルは下図のようになります。

f:id:udnp:20190213103320p:plain
Graphical Model: qがエンコーダー、pがデコーダーを表す。

この図より同時分布は下式のようになります。

\begin{align} q_\phi(x, y, z) &= q(z | x, y) q(y | x) q (x) \\ p_\theta(x, y, z) &= p(x | y, z) p (y) p (z) \end{align}

また推論モデルである q_\phi(y | x)はカテゴリカル分布です。

\begin{align} q_\phi(y | x) = Categorical ( y = k | \theta_k )
\end{align}

参考までに

統計モデリングの視点からカテゴリカル分布を考えます。この分布はベルヌーイ分布の多変量版のようなものです。各カテゴリーの生起確率、すなわちkの目がでる確率が \theta_kであるようなK面のサイコロを表します。多項ロジスティク回帰で用いられます。

多項ロジスティク回帰の例では、ある購入した商品のカテゴリーYのデータがあるとします。その説明変数 \textbf{X}であるとすると、購入者ごとに各カテゴリーを選ぶ確率を \thetaとすると、以下のモデルが考えられます。 \begin{align} \textbf{y}&= \textbf{w}^{T} \textbf{X} \\ \theta &= \text{softmax} (\textbf{y}) \\ \textbf{Y} & \sim \text{Categorical} (\theta)
\end{align}

はてな \thetaのローマン体の書き方がわかりませんでした・・・

多クラス分類ではこの考え方に基づいています。

各モデルの確率分布

変分下限

  • ラベル付き対数尤度のELBO \begin{align} \log p_\theta (x, y) &= \log \int p_\theta (x |z, y) p(z) p(y) dz \\ &= \log \int q_{\phi}(z|x,y) \frac{p_\theta (x |z, y) p(z) p(y)}{q_{\phi}(z|x,y)} dz \\ & \geq \int q_{\phi}(z|x,y) \log \bigg(\frac{p_\theta (x |z, y) p(z) p(y)}{q_{\phi}(z|x,y)} \bigg) dz \\ &= \mathbb{E}_{z \sim q_\phi (z|x, y)} [\log p_\theta (x|z, y) + \log p(z) + \log p(y) - \log q_\phi(z | x,y) ] \\ &= \mathbb{E}_{z \sim q_\phi (z|x, y)} [\log p_\theta (x|z, y) + \log p(y)] + \mathbb{E}_{z \sim q_\phi (z|x, y)}[\log \frac{p(z)}{q_\phi(z | x,y)}] \\ &= \mathbb{E}_{z \sim q_\phi (z|x, y)} [\log p_\theta (x|z, y) + \log p(y)] - D_{KL} (q_\phi(z | x,y) || p(z)) \\ & \simeq [\log p_\theta (x|z, y) + \log p(y)] - D_{KL} (q_\phi(z | x,y) || p(z)) \\ &= L(x, y) \end{align}

これを最大化させればよいです。

  • ラベルなしの対数尤度のELBO。グラフの左側の方。 \begin{align} \log p_\theta (x) & \geq \mathbb{E}_{z, y \sim q_\phi (x|z, y)} [\log p_\theta (x|z, y) + \log p(z) + \log p(y) - \log q_\phi(z, y |x) ] \\ &= \mathbb{E}_{y \sim q_\phi (x|y)} [ \mathbb{E}_{z \sim q_\phi (x|z)} [ \log p_\theta (x|z, y) + \log p(z) + \log p(y) - \log q_\phi(z|x, y) - \log q_\phi(y|x) ]] \\ &= \mathbb{E}_{y \sim q_\phi (x|y)} [ - L(x,y) - \mathbb{E}_{z \sim q_\phi (x|z)} [ \log q_\phi(y|x) ]] \\ &= \mathbb{E}_{y \sim q_\phi (x|y)} [ - L(x,y) - \log q_\phi(y|x) ] \\ &= - U(x) \end{align}

以上より、目的関数は

\begin{align} J = \sum_{label} L(x,y) + \sum_{unlabel} U(x) \end{align}

また、 q_\phi(y|x)はxの属するクラス確率を与えるため、これをクラス分類に使用することができます。そこで学習を次のようにします。

\begin{align} J = \sum_{label} L(x,y) + \sum_{unlabel} U(x) + \alpha \mathbb{E}_{x,y \sim p_l} [- \log q_\phi (y|x)]  \end{align}

ただ、上式を用いなくても条件付VAEの実装はできます。一例です。

from keras.layers import Lambda, Input, Dense, merge
from keras.models import Model
from keras.losses import mse, binary_crossentropy
from keras.utils import plot_model
from keras import backend as K
 
# Conditional Variational Autoenvoder (M2)
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
X_train, y_train = mnist.train.images, mnist.train.labels
X_test, y_test = mnist.test.images, mnist.test.labels
 
m = 50
x_dim = X_train.shape[1]
y_dim = y_train.shape[1]
z_dim = 2
n_epoch = 20
 
# Encoder: Q(z|X,y)
X = Input(batch_shape=(m, x_dim)
cond = Input(batch_shape=(m, y_dim))
 
inputs = merge([X, cond], mode="concat", concat_axis=1)
h_q = Dense(512, activation="relu")(inputs)
z_mean = Dense(z_dim, activation="linear")(h_q)
z_log_var = Dense(z_dim, activation="linear")(h_q)
 
 def sampling(args):
    z_mean, z_log_var = args
    eps = K.random_normal(shape=(m, z_dim), mean=0, std=1.)
    return z_mean + K.exp(0.5*z_log_var) + K.random_normal(shape=(m, z_dim))
 
# Sample z ~ Q(z|X,y)
z = Lambda(sample_z)([z_mean, z_log_var])
z_cond = merge([z, cond], mode="concat", concat_axis=1)
 
# Decoder: P(X|z,y)
decoder_h = Dense(512, activation="relu")
decoder_out = Dense(784, activation="sigmoid")
h_p = decoder_h(z_cond)
outputs = decoder_out(h_p)
 
reconstruction_error = K.sum(K.binary_crossentropy(inputs, outputs), axis=-1)
kl_divergence = 0.5*K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
vae_loss = K.mean(reconstruction_error - kl_divergence)
 
vae = Model(inputs, outputs, name="cvae")
vae.add_loss(vae_loss)
vae.compile(optimizer="adam")

次回

次は q_\phi(y|x)を含め実際にコーディングしたMNISTの結果とM1 + M2およびchemical VAEについて記載します。

  • Auxiliary Deep Generative Modelsも試したい。

変分オートエンコーダー (VAE, M1)

はじめに

Alan先生のラボの論文Chemical VAEsを理解するために、VAEについてまとめます。

Chem VAE

化学構造の文字列情報(SMILES)をVAEに適用しSMILESを生成しようとする試みです。Latent Spaceの尤度を最大とするような分子が生成されます。Inductive biasを回避するためにはVAEよりもGAN basedの方 (ORGANIC)がよいと思いますが、まずはこちらから論文を見ていきたいと思います。githubにコードがあるので試すことはできますが、有効な分子はあまり生成されませんでした。

github.com

2024-02-05追記:Kerasで解説していましたが、PyTorchに書き直しています。

VAE概要

Variational AutoEncoder (VAE)は、教師無し・反教師あり学習に用いることができるオートエンコーダを利用する生成モデルです。

敵対的生成ネットワーク(Generative Adversarial Networks, GANs)と異なり尤度の計算を明示的に行います。

VAEには3つのモデルが提案されています。論文は次の通りです。

  1. [1406.5298] Semi-Supervised Learning with Deep Generative Models (2014)
  2. [1606.05908] Tutorial on Variational Autoencoders

M1

スタンダードな生成モデルです。

M2 (CVAE)

M1を条件付きにしたVAE(Conditional VAE)です。潜在変数にラベルなどを加えて同時に生成をさせてあげることで条件付けすることができます。

M1 + M2

上記のモデルを組み合わせたものです。M1の教師無し学習モデルの隠れ変数zを用いて、M2を用いて半教師あり学習を行っていきます。

M1モデル

オートエンコーダの隠れ変数(潜在変数)$z$が互いに独立であり、これらは正規分布に従うと仮定したものです。平均場近似したものとみなせます。 このようにすることで変分推論を行うことができます。

ベイズの定理

観測変数を$x$としたとき、事後分布は

$$ p(z|x) = \frac{p(x, z)}{\int p(x, z)dz} = \frac{p(x|z)p(z)}{p(x)} $$

と表現することができます。この$p(x)$が厄介で、任意の関数で近似した場合、閉じた形(closed form)にならないため、$p(z|x)$を計算することできません。

そこで、事後分布$p(z|x)$に対して、それを近似する分布$q$を考えてやろうというのがアイディアです。

変分推論

  • エンコーダーの確率分布: $P_\theta (z | x)$
  • 真の確率分布 $Q_\phi (z | x)$

とします。エンコーダーを学習させるためにはQとPのKullback-Leibler divergence (KL-divergence)を最小化させればよいです。

$$ \begin{align} D_{KL} (Q_\phi (z | x) || P_{\theta} (z | x)) &= \mathbb{E}_{z \sim Q_\phi (z|x)} [\log Q_\phi (z|x) - \log P_{\theta} (z | x) ] \\ &= \mathbb{E}_{z \sim Q_\phi (z|x)} [\log Q_\phi (z|x) - \log \frac{P_{\theta} (x | z) P_{\theta} (z)}{P_{\theta} (x) } ] \\ &= \mathbb{E}_{z \sim Q_\phi (z|x)} [\log Q_\phi (z|x) - \log P_{\theta} (x | z) - \log P_{\theta}(z) ] + \log P_{\theta} (x) \\ &= D_{KL} (Q_\phi (z | x) || P_{\theta} (z)) - \mathbb{E}_{z \sim Q_\phi (z|x)} [\log P_{\theta} (x | z)] + \log P_{\theta} (x) \\ \\ \Leftrightarrow \log P_{\theta} (x) - D_{KL} (Q_\phi (z | x) || P_{\theta} (z | x)) &= \mathbb{E}_{z \sim Q_\phi (z|x)} [\log P_{\theta} (x | z)] - D_{KL} (Q_\phi (z | x) || P_{\theta} (z)) \end{align} $$

VAEモデルを学習するための対数尤度を直接最大化することは難しいので、その変分下限を最大化します。 左辺は変分下限(variational lower bound, VLB)もしくはELBO (Evidence Lower Bound)と呼ばれています。

一方で、MCMCなどでサンプリングして、尤度を推定いく方法はcontrasive divergenceと呼ばれます。 このような方法をとる代表的な方法に、Energy-based modelsがあります。

変分問題の定式化

ELBOは次のようにして導出できます。

$$ \begin{align} \log p_{\theta}(x) &= \log \int p(X, z)dz \\ &= \log \int q(z|x) \frac{p(X, z)}{q(z|x)} dz \\ & \geq \int q(z|x) \log \frac{p(X, z)}{q(z|x)} dz \\ &= L(q_\theta (z)) \end{align} $$

ここで、3行目では、Jensenの不等式を用いました。このように、汎函数$L$は周辺尤度(エビデンス)の対数値の下限となっているため、ELBOと呼ばれています。

さらに確率密度関数積分$\int q(z|x)dz = 1$である(等号成立時)ことと、同時分布(joint distribution)に対して乗法定理を用い、式変形をしていけばELBOが求まります。

$$ \begin{align} L (X, z) &= \int q(z|x) \log \frac{p(z | X) p(X)}{q(z|x)} \\ &= \int q(z|x) \log \frac{p(z | X)}{q (z|x) } + \int q(z|x) \log p(X) dz \\ &= - D_{KL} ( q(z|x) || p(z | X) ) + \mathbb{E}_{z \sim q(z|x) }[\log p(X) ] \\ &= \log p(X) - D_{KL} ( q(z|x) || p(z | X)) \end{align} $$

以上より、VAEの目的関数は次のようになります。

$$ L(x, z) = \mathbb{E}_{z \sim Q_\phi (z|x)} [\log P_{\theta} (x | z)] - D_{KL} (Q_\phi (z | x) || P_{\theta} (z)) $$

上式を最大化させていきます(最適化計算の場合は符号を逆転させて最小化する)。

  • 1つ目の項がEncoderとDecoder全体を表す損失関数で、データXの対数尤度の期待値を最大化させることを表します。
  • 2つ目の項がEncoderとDecoderのKLダイバージェンスです。分布が近づくほど、Dは0に近づくため、ELBOを最大化できます。

Reparametarization trick (サンプリング)

この$Q(z | x)$をNNsで近似しますが、出力は確率分布であるためこのままだと計算できません。

そのために、まずEncodeから平均$\mu (z)$および分散$Σ(z)$を予測ます。

次に得られた平均と分散からサンプリングした$z$の値を用いることで近似します。

  1. $\varepsilon \sim \mathcal{N} (0, 1)$
  2. $z = \mu(x) + \varepsilon Σ^{\frac{1}{2}} (x)$

PyTorchを用いると次のようになります。

# Encoderはmu, sigmaを出力する
z_mu, z_lsgms = encoder(x)

# reparameterize
z_sgm = z_lsgms.mul(0.5).exp_()
eps = torch.randn(z_sgm.size())
z = eps * z_sgm + z_mu

# Decoderはzから復元する
x = decoder(z)

expの方がlogよりも数値安定性が良いため$Σ(X) = \log Σ(X)$と変換したのち計算します。

AEと同様に入力と出力が同じになるようなモデルとするため、Reconstruction error

$$ -\mathbb{E}_{z \sim Q_\phi (z|x)} \left[\log P_{\theta} (x | z)\right] $$

を最小化するように学習させます。そのときの損失関数はMSEやクロスエントロピーとなります。これは対数尤度を最大化させる操作と同じです。

import torch.nn.functional as F

reconst_loss = F.binary_crossentropy(inputs, outputs)

ただし、VAEを定義通りに実装するには、対数尤度$\log p(x|z)$の部分をベルヌーイ分布やガウス分布と仮定し、計算する必要があります。

bernoulli, gaussian negative log likelihoodを最小化させます。

KL Divergenceの計算

KL divergenceは解析的に求まります。計算の都合上、損失関数の最小値を求める必要があるため、負の正規分布$\mathcal{N}(0,1)$のKL divergenceは次のようになります。

$$ \mathcal{N} \left( \mu (X), Σ (X) ) || \mathcal{N}(0,1) \right) = \frac{1}{2} \sum_{j} \big(1 + \log (Σ(X)) - \mu^{2} (X) - Σ(X) \big) $$

loss_kl = - 0.5 * torch.sum(1 + z_lsgms - z_mu.pow(2) - z_lsgms.exp())
vae_loss = reconst_loss  + kl_loss

このvae_lossを最小化することは、ELBOを最大化することと同じになります。

次回はM2とM1+M2について記載します。

参考サイト

wiseodd.github.io

qiita.com

cympfh.cc

musyoku.github.io

https://www.ccn.yamanashi.ac.jp/~tmiyamoto/img/variational_bayes1.pdf

はじめに

はじめに

Japan Digital Design(JDD)でリサーチャーとして働いています。また、国立情報学研究所(NII)でポスドクもしてます。

 

以下のような経歴です

NII & JDD ← 総合研究大学院大学国立情報学研究所)← AGC ← 九州大

 

公式のプロフィールはLinkedinを確認してください。

 

修士までの研究はシステム生物学で、代謝反応の数理モデリングやパラメータ推定の研究をしていました。博士課程での研究は、グラフマイニングや強化学習を使った分子グラフ生成やメッセージパッシングアルゴリズムなどの研究をしてました。

 

このブログでは、主に強化学習やグラフ機械学習に関連することについて綴っていきたいと思います。アプリケーションは化合物が多めです。記載事項に間違っているところもあるかもしれません。リファレンスを十分参照いただくことを推奨しております。宜しくお願いします。

 

 

Qiitaも一応

qiita.com

  

githubはこちら。全然コードは載せていませんが・・・

機械学習の実装をできるだけ追加していきたいです。

投稿論文の手法などもこちらにあります。

github.com

 

If you prefer to English, check below. This is English version on this blog.

https://medium.com/@azunan3

 

よろしくお願いします。