TensorFlow Probability : Tutorials : ガウス過程回帰 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/30/2018
* 本ページは、TensorFlow の本家サイトの TensorFlow Probability – Tutorials の次のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
TensorFlow Probability : ガウス過程回帰
この colab では、TensorFlow と TensorFlow Probability を使用してガウス過程回帰を探究します。ある知られている関数(s) からのノイズを持つ観測を生成して GP モデルをそれらのデータに適合させます。それから GP 事後分布からサンプリングされた関数値をそれらの領域でグリッドに渡りプロットします。
背景
$\mathcal{X}$ を任意の集合とします。ガウス過程 (GP) は $\mathcal{X}$ によりインデックス付けられた確率変数のコレクションで、$\{X_1, \ldots, X_n\} \subset \mathcal{X}$ を任意の有限な部分集合とする場合に、周辺密度関数 $p(X_1 = x_1, \ldots, X_n = x_n)$ が多変量ガウス分布であるようなものです。任意のガウス分布はその最初と 2 番目の中心モーメント (平均と共分散) で完全に指定され、そして GP のものも例外ではありません。GP はその平均関数 $\mu : \mathcal{X} \to \mathbb{R}$ と共分散関数 $k : \mathcal{X} \times \mathcal{X} \to \mathbb{R}$ によって完全に指定できます。GP の表現力の殆どは共分散関数の選択にカプセル化されています。様々な理由で、共分散関数はまたカーネル関数として参照されます。対照的 (= symmetirc) で正定値 (= positive-definite) であることだけが要求されます (Rasmussen & Williams の Ch. 4 参照)。下では指数二次 (= ExponentiatedQuadratic) 共分散カーネルを活用します。その形式は :
\[
k(x, x’) := \sigma^2 \exp \left( \frac{\|x – x’\|^2}{\lambda^2} \right)
\]
$\sigma^2$ は「振幅 (= amplitude)」そして $\lambda$ は長さスケールと呼ばれます。カーネル・パラメータは最大尤度最適化手続きを通して選択可能です。
GP からのフル・サンプルは空間全体 $\mathcal{X}$ に渡る実数値関数から成りそして実際には認識することは実行不可能です; しばしば (そこでサンプルを観測するために) 点の集合を選択してそしてそれらの点で関数値を描画します。これは適切な (有限次元の) 多変量ガウス分布からサンプリングすることで獲得されます。
上の定義に従えば、任意の有限次元多変量ガウス分布もまたガウス過程であることに注意してください。通常は、GP に言及するとき、インデックス集合はある $\mathbb{R}^n$ であることが暗黙的でここでも実際にこの仮定をします。
機械学習におけるガウス過程の一般的な応用はガウス過程回帰です。考え方は、有限個の点 $\{x_1, \ldots x_N\}$ における関数のノイズを伴う観測 $\{y_1, \ldots, y_N\}$ が与えられたとき未知の関数を推定することを望むことです。生成過程を推測します :
\[
\begin{align}
f \sim \: & \textsf{GaussianProcess}\left(
\text{mean_fn}=\mu(x),
\text{covariance_fn}=k(x, x’)\right) \\
y_i \sim \: & \textsf{Normal}\left(
\text{loc}=f(x_i),
\text{scale}=\sigma\right), i = 1, \ldots, N
\end{align}
\]
上で記されたように、サンプリングされた関数は計算することは不可能です、何故ならば無限の数の点におけるその値を必要とするからです。代わりに、多変量ガウス分布からの有限個のサンプルを考えます。
$$
\begin{gather}
\begin{bmatrix}
f(x_1) \\
\vdots \\
f(x_N)
\end{bmatrix}
\sim
\textsf{MultivariateNormal} \left( \:
\text{loc}=
\begin{bmatrix}
\mu(x_1) \\
\vdots \\
\mu(x_N)
\end{bmatrix} \:,\:
\text{scale}=
\begin{bmatrix}
k(x_1, x_1) & \cdots & k(x_1, x_N) \\
\vdots & \ddots & \vdots \\
k(x_N, x_1) & \cdots & k(x_N, x_N) \\
\end{bmatrix}^{1/2}
\: \right)
\end{gather} \\
y_i \sim \textsf{Normal} \left(
\text{loc}=f(x_i),
\text{scale}=\sigma
\right)
$$
共分散行列上の指数 $\frac{1}{2}$ に注意してください: これはコレスキー分解を表しています。コレスキーを計算することは必要です、何故ならば MVN は location-scale family 分布だからです。不幸にも、コレスキー分解は計算的に高価で、$O(N^3)$ 時間と $O(N^2)$ 空間がかかります。大半の GP 文献はこの表面上は無害で小さな指数を処理することに焦点を置いています。
事前分布平均関数を定数、しばしばゼロに取ることは一般的です。また、幾つかの表記法の慣習は便利です。サンプリングされた関数値の有限ベクトルのために $\mathbf{f}$ としばしば書きます。多くの興味深い表記法が、入力ペアへの $k$ の適用に由来する共分散行列のために使用されます。(Quiñonero-Candela, 2005) に従って、行列の成分は特定の入力点における関数値の共分散であることに注意します。こうして共分散行列を $K_{AB}$ として示すことができます、そこでは $A$ と $B$ は与えられた行列次元に沿った関数値のコレクションの幾つかの indicator です。
例えば、含蓄される潜在関数値 $\mathbf{f}$ を持つ観測されたデータ $(\mathbf{x}, \mathbf{y})$ が与えられたとき、次のように書けます :
$$
K_{\mathbf{f},\mathbf{f}} = \begin{bmatrix}
k(x_1, x_1) & \cdots & k(x_1, x_N) \\
\vdots & \ddots & \vdots \\
k(x_N, x_1) & \cdots & k(x_N, x_N) \\
\end{bmatrix}
$$
同様に、以下の内のように、入力の集合を混合できます :
$$
K_{\mathbf{f},*} = \begin{bmatrix}
k(x_1, x^*_1) & \cdots & k(x_1, x^*_T) \\
\vdots & \ddots & \vdots \\
k(x_N, x^*_1) & \cdots & k(x_N, x^*_T) \\
\end{bmatrix}
$$
ここで $N$ 訓練入力、そして $T$ テスト入力があると仮定します。そして上の生成過程は次のように簡潔に書くことができます :
$$
\begin{align}
\mathbf{f} \sim \: & \textsf{MultivariateNormal} \left(
\text{loc}=\mathbf{0},
\text{scale}=K_{\mathbf{f},\mathbf{f}}^{1/2}
\right) \\
y_i \sim \: & \textsf{Normal} \left(
\text{loc}=f_i,
\text{scale}=\sigma \right), i = 1, \ldots, N
\end{align}
$$
最初の行のサンプリング演算は (一つの) 多変量正規分布からの $N$ 関数値の有限集合を生成します — 上の GP ドロー記法内のような関数全体 (= an entire function) ではありません。2 番目の行は固定された観測ノイズ $\sigma^2$ を持ち、様々な関数値を中心とする単変量正規分布 (= univariate Gaussians) からの $N$ ドローのコレクションを表します。
上の適所にある生成モデルで、事後分布問題を考えることに進めます。これは上の仮定からの観測されたノイズを持つデータ上で条件付けられた、テスト点の新しい集合における関数値に渡る事後分布を生成します。
上の適所にある表記で、相当する入力と訓練データ上で条件付けられた未来 (ノイズを持つ) 観測に渡る事後予測分布を次のように簡潔に書くことができます (より詳細については、Rasmussen & Williams の 2.2 参照)。
$$
\mathbf{y}^* \mid \mathbf{x}^*, \mathbf{x}, \mathbf{y} \sim \textsf{Normal} \left(
\text{loc}=\mathbf{\mu}^*,
\text{scale}=(\Sigma^*)^{1/2}
\right)
$$
ここで
$$
\mathbf{\mu}^* = K_{*,\mathbf{f}}\left(K_{\mathbf{f},\mathbf{f}} + \sigma^2 I \right)^{-1} \mathbf{y}
$$
そして
$$
\Sigma^* = K_{*,*} – K_{*,\mathbf{f}}
\left(K_{\mathbf{f},\mathbf{f}} + \sigma^2 I \right)^{-1} K_{\mathbf{f},*}
$$
インポート
import numpy as np import tensorflow as tf from mpl_toolkits.mplot3d import Axes3D from tensorflow_probability import distributions as tfd from tensorflow_probability import positive_semidefinite_kernels as tfk %pylab inline # Configure plot defaults plt.rcParams['axes.facecolor'] = 'white' plt.rcParams['grid.color'] = '#666666' %config InlineBackend.figure_format = 'png'
numpy と matplotlib から対話的な名前空間を取り込んでいます。
例: ノイズを持つ正弦波データ上の正確な GP 回帰
ここではノイズを持つ正弦波から訓練データを生成し、それから GP 回帰モデルの事後分布から多くの曲線をサンプリングします。カーネル・ハイパーパラメータを最適化するために Adam を使用します (事前分布のもとのデータの負の対数尤度を最小化します)。訓練している曲線に続いて、正解関数と事後サンプルをプロットします。
def reset_session(): """Creates a new global, interactive session in Graph-mode.""" global sess try: tf.reset_default_graph() sess.close() except: pass sess = tf.InteractiveSession() reset_session()
def sinusoid(x): return np.sin(3 * np.pi * x[..., 0]) def generate_1d_data(num_training_points, observation_noise_variance): """Generate noisy sinusoidal observations at a random set of points. Returns: observation_index_points, observations """ index_points_ = np.random.uniform(-1., 1., (num_training_points, 1)) index_points_ = index_points_.astype(np.float64) # y = f(x) + noise observations_ = (sinusoid(index_points_) + np.random.normal(loc=0, scale=np.sqrt(observation_noise_variance), size=(num_training_points))) return index_points_, observations_
# Generate training data with a known noise level (we'll later try to recover # this value from the data). NUM_TRAINING_POINTS = 100 observation_index_points_, observations_ = generate_1d_data( num_training_points=NUM_TRAINING_POINTS, observation_noise_variance=.1)
# Create the trainable model parameters, which we'll subsequently optimize. # Note that we constrain them to be strictly positive. amplitude = (np.finfo(np.float64).tiny + tf.nn.softplus(tf.Variable(initial_value=1., name='amplitude', dtype=np.float64))) length_scale = (np.finfo(np.float64).tiny + tf.nn.softplus(tf.Variable(initial_value=1., name='length_scale', dtype=np.float64))) observation_noise_variance = ( np.finfo(np.float64).tiny + tf.nn.softplus(tf.Variable(initial_value=1e-6, name='observation_noise_variance', dtype=np.float64)))
# Create the covariance kernel, which will be shared between the prior (which we # use for maximum likelihood training) and the posterior (which we use for # posterior predictive sampling) kernel = tfk.ExponentiatedQuadratic(amplitude, length_scale)
# Create the GP prior distribution, which we will use to train the model # parameters. gp = tfd.GaussianProcess( kernel=kernel, index_points=observation_index_points_, observation_noise_variance=observation_noise_variance) # This lets us compute the log likelihood of the observed data. Then we can # maximize this quantity to find optimal model parameters. log_likelihood = gp.log_prob(observations_)
# Define the optimization ops for maximizing likelihood (minimizing neg # log-likelihood!) optimizer = tf.train.AdamOptimizer(learning_rate=.01) train_op = optimizer.minimize(-log_likelihood)
# Now we optimize the model parameters. num_iters = 1000 # Store the likelihood values during training, so we can plot the progress lls_ = np.zeros(num_iters, np.float64) sess.run(tf.global_variables_initializer()) for i in range(num_iters): _, lls_[i] = sess.run([train_op, log_likelihood]) [amplitude_, length_scale_, observation_noise_variance_] = sess.run([ amplitude, length_scale, observation_noise_variance]) print('Trained parameters:'.format(amplitude_)) print('amplitude: {}'.format(amplitude_)) print('length_scale: {}'.format(length_scale_)) print('observation_noise_variance: {}'.format(observation_noise_variance_))
Trained parameters: amplitude: 0.737212035424 length_scale: 0.120468090569 observation_noise_variance: 0.0978894813992
# Plot the loss evolution plt.figure(figsize=(12, 4)) plt.plot(lls_) plt.xlabel("Training iteration") plt.ylabel("Log marginal likelihood") plt.show()
# Having trained the model, we'd like to sample from the posterior conditioned # on observations. We'd like the samples to be at points other than the training # inputs. predictive_index_points_ = np.linspace(-1.2, 1.2, 200, dtype=np.float64) # Reshape to [200, 1] -- 1 is the dimensionality of the feature space. predictive_index_points_ = predictive_index_points_[..., np.newaxis] gprm = tfd.GaussianProcessRegressionModel( kernel=kernel, # Reuse the same kernel instance, with the same params index_points=predictive_index_points_, observation_index_points=observation_index_points_, observations=observations_, observation_noise_variance=observation_noise_variance, predictive_noise_variance=0.) # Create op to draw 50 independent samples, each of which is a *joint* draw # from the posterior at the predictive_index_points_. Since we have 200 input # locations as defined above, this posterior distribution over corresponding # function values is a 200-dimensional multivariate Gaussian distribution! num_samples = 50 samples = gprm.sample(num_samples)
# Draw samples and visualize. samples_ = sess.run(samples) # Plot the true function, observations, and posterior samples. plt.figure(figsize=(12, 4)) plt.plot(predictive_index_points_, sinusoid(predictive_index_points_), label='True fn') plt.scatter(observation_index_points_[:, 0], observations_, label='Observations') for i in range(num_samples): plt.plot(predictive_index_points_, samples_[i, :], c='r', alpha=.1, label='Posterior Sample' if i == 0 else None) leg = plt.legend() for lh in leg.legendHandles: lh.set_alpha(1) plt.xlabel(r"Index points ($\mathbb{R}^1)") plt.ylabel("Observation space") plt.show()
Note: 上のコードを何回か実行する場合、時にはそれは素晴らしく見えて他の回にはそれは酷く見えます!パラメータを訓練する最大尤度は非常に敏感で時々貧弱なモデルに収束します。最善のアプローチはモデル・ハイパーパラメータを周辺化するために MCMC を使用することです。
以上