GPyTorch 1.2 Tutorials : GPyTorch 回帰

GPyTorch 1.2 Tutorials : GPyTorch 回帰チュートリアル (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 09/03/2020 (1.2)

* 本ページは、GPyTorch 1.2 ドキュメントの以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

Tutorials : GPyTorch 回帰チュートリアル

イントロダクション

この notebook では、最も単純な例を使用して単純な関数上で RBF カーネル・ガウス過程を訓練し、GPyTorch の多くの設計の特徴を実演します。次の関数を 100 訓練サンプルでモデル化して :

$$
\begin{align}
y &= \sin(2\pi x) + \epsilon \\
\epsilon &\sim \mathcal{N}(0, 0.04)
\end{align}
$$

そして 51 テストサンプル上でテストしていきます。

Note: このノートブックはガウス過程の数学的背景を教えることを必ずしも意図していません、むしろ GPyTorch でどのように単純なものを訓練して予測を行なうかです。数学的扱いについては、Gaussian Processes for Machine Learning の 2 章が GP 回帰への非常に完全なイントロダクションを提供しています (このテキスト全体は高く推奨されます) :

import math
import torch
import gpytorch
from matplotlib import pyplot as plt

%matplotlib inline
%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload

 

訓練データのセットアップ

次のセルで、この例のために訓練データをセットアップします。[0, 1] 上の 100 の規則的な間隔のポイントを使用していきます、その上で関数を評価して訓練ラベルを得るためにガウスノイズを追加します。

# Training data is 100 points in [0,1] inclusive regularly spaced
train_x = torch.linspace(0, 1, 100)
# True function is sin(2*pi*x) with Gaussian noise
train_y = torch.sin(train_x * (2 * math.pi)) + torch.randn(train_x.size()) * math.sqrt(0.04)

 

モデルをセットアップする

次のセルは GPyTorch のユーザ定義ガウス過程モデルの最も重要な特徴を実演します。GPyTorch の GP モデルの構築は様々な方法で異なります。

まず、多くの既存の GP パッケージとは対照的に、ユーザのために full GP モデルは提供しません。むしろ、素早く一つを構築するために必要なツールを提供します。これは何故ならば、標準的な PyTorch でニューラルネットワークを構築することに類似して、どのようなコンポーネントを含む必要があるとしても柔軟性を持つことが重要だと私達は信じるからです。より複雑な例で見られるように、これはカスタムモデルの設計においてユーザに素晴らしい柔軟性を可能にします。

殆どの GP 回帰モデルについて、以下の GPyTorch オブジェクトを構築することが必要です :

  1. GP モデル (gpytorch.models.ExactGP) – これは推論の殆どを扱います。
  2. 尤度 (gpytorch.likelihoods.GaussianLikelihood) – これは GP 回帰のために使用される最も一般的な尤度です。
  3. 平均 (= Mean) – これは GP の事前平均を定義します。(どの平均を使用するべきか知らない場合には、gpytorch.means.ConstantMean() で始めてください。)
  4. カーネル – これは GP の 事前共分散 を定義します。(どのカーネルを使用するべきか知らない場合には、gpytorch.kernels.ScaleKernel(gpytorch.kernels.RBFKernel()) で始めてください。)
  5. MultivariateNormal 分布 (gpytorch.distributions.MultivariateNormal) – これは多変量正規分布を表わすために使用されるオブジェクトです。

 

GP モデル

GPyTorch のユーザ構築 (Exact, i.e. 非変分) GP モデルのコンポーネントは、大まかに言えば :

  1. __init__ メソッド、これは訓練データと尤度を取り、そしてどのようなオブジェクトであれモデルの forward メソッドのために必要なものを構築します。これは通常は mean モジュールとカーネルモジュールのようなものを含みます。
  2. forward メソッドは、ある $n \times d$ データ x を取りそして x で評価された事前平均と共分散を持つ MultivariateNormal を返します。
    換言すれば、GP の事前平均と共分散行列を表わすベクトル $\mu(x)$ と $n \times n$ 行列 $K_{xx}$ を返します。

この仕様はモデルを定義するとき巨大な総量の柔軟性をそのままにしておきます。例えば、2 つのカーネルを加算を通じて組み立てるために、カーネルモジュールを直接加算することもできます :

self.covar_module = ScaleKernel(RBFKernel() + WhiteNoiseKernel())

あるいは forward メソッドでカーネルの出力を加算できます :

covar_x = self.rbf_kernel_module(x) + self.white_noise_module(x)
# We will use the simplest form of GP model, exact inference
class ExactGPModel(gpytorch.models.ExactGP):
    def __init__(self, train_x, train_y, likelihood):
        super(ExactGPModel, self).__init__(train_x, train_y, likelihood)
        self.mean_module = gpytorch.means.ConstantMean()
        self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.RBFKernel())

    def forward(self, x):
        mean_x = self.mean_module(x)
        covar_x = self.covar_module(x)
        return gpytorch.distributions.MultivariateNormal(mean_x, covar_x)

# initialize likelihood and model
likelihood = gpytorch.likelihoods.GaussianLikelihood()
model = ExactGPModel(train_x, train_y, likelihood)

 

モデル・モード

殆どの PyTorch モジュールのように、ExactGP は .train() と .eval() モードを持ちます。

  • .train() モードはモデルのハイパーパラメータを最適化するためです。
  • .eval() モードはモデル事後分布を通して予測を計算するためです。

 

モデルを訓練する

次のセルでは、ガウス過程のハイパーパラメータを訓練するために Type-II MLE を使用して処理します。

多くの他の GP 実装と比較してここでの最も明白な違いは、標準的な PyTorch でのように、コア訓練ループがユーザにより書かれることです。GPyTorch では、torch.optim からの標準的な PyTorch optimizer を利用し、そしてモデルの総ての訓練可能なパラメータはタイプ torch.nn.Parameter であるべきです。GP モデルは torch.nn.Module を直接拡張していますので、model.parameters() や model.named_parameters() 関数のようなメソッドへの呼び出しは (想像できるように) PyTorch 由来です。

殆どの場合、下のボイラープレートなコードは上手く動作します。それは標準的な PyTorch 訓練ループと同じ基本的なコンポーネントを持ちます :

  1. 総てのパラメータ勾配をゼロにします。
  2. モデルを呼び出して損失を計算します。
  3. 勾配を満たすために損失上で backward を呼び出します。
  4. optimizer 上で step を取ります。

けれども、カスタム訓練ループを定義することはより素晴らしい柔軟性を可能にします。例えば、訓練の各ステップでパラメータをセーブしたり、異なるパラメータのために異なる学習率を使用することは容易です (これは例えば深層カーネル学習において有用です)。

# this is for running the notebook in our testing framework
import os
smoke_test = ('CI' in os.environ)
training_iter = 2 if smoke_test else 50


# Find optimal model hyperparameters
model.train()
likelihood.train()

# Use the adam optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)  # Includes GaussianLikelihood parameters

# "Loss" for GPs - the marginal log likelihood
mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood, model)

for i in range(training_iter):
    # Zero gradients from previous iteration
    optimizer.zero_grad()
    # Output from model
    output = model(train_x)
    # Calc loss and backprop gradients
    loss = -mll(output, train_y)
    loss.backward()
    print('Iter %d/%d - Loss: %.3f   lengthscale: %.3f   noise: %.3f' % (
        i + 1, training_iter, loss.item(),
        model.covar_module.base_kernel.lengthscale.item(),
        model.likelihood.noise.item()
    ))
    optimizer.step()
Iter 1/50 - Loss: 0.939   lengthscale: 0.693   noise: 0.693
Iter 2/50 - Loss: 0.908   lengthscale: 0.644   noise: 0.644
Iter 3/50 - Loss: 0.874   lengthscale: 0.598   noise: 0.598
Iter 4/50 - Loss: 0.837   lengthscale: 0.555   noise: 0.554
Iter 5/50 - Loss: 0.795   lengthscale: 0.514   noise: 0.513
Iter 6/50 - Loss: 0.749   lengthscale: 0.476   noise: 0.474
Iter 7/50 - Loss: 0.699   lengthscale: 0.440   noise: 0.437
Iter 8/50 - Loss: 0.649   lengthscale: 0.405   noise: 0.402
Iter 9/50 - Loss: 0.600   lengthscale: 0.372   noise: 0.369
Iter 10/50 - Loss: 0.556   lengthscale: 0.342   noise: 0.339
Iter 11/50 - Loss: 0.516   lengthscale: 0.315   noise: 0.310
Iter 12/50 - Loss: 0.480   lengthscale: 0.291   noise: 0.284
Iter 13/50 - Loss: 0.448   lengthscale: 0.270   noise: 0.259
Iter 14/50 - Loss: 0.413   lengthscale: 0.254   noise: 0.237
Iter 15/50 - Loss: 0.380   lengthscale: 0.241   noise: 0.216
Iter 16/50 - Loss: 0.355   lengthscale: 0.231   noise: 0.197
Iter 17/50 - Loss: 0.314   lengthscale: 0.223   noise: 0.179
Iter 18/50 - Loss: 0.292   lengthscale: 0.218   noise: 0.163
Iter 19/50 - Loss: 0.262   lengthscale: 0.214   noise: 0.148
Iter 20/50 - Loss: 0.236   lengthscale: 0.214   noise: 0.135
Iter 21/50 - Loss: 0.201   lengthscale: 0.216   noise: 0.122
Iter 22/50 - Loss: 0.176   lengthscale: 0.220   noise: 0.111
Iter 23/50 - Loss: 0.158   lengthscale: 0.224   noise: 0.102
Iter 24/50 - Loss: 0.125   lengthscale: 0.231   noise: 0.093
Iter 25/50 - Loss: 0.101   lengthscale: 0.239   noise: 0.085
Iter 26/50 - Loss: 0.078   lengthscale: 0.247   noise: 0.077
Iter 27/50 - Loss: 0.066   lengthscale: 0.256   noise: 0.071
Iter 28/50 - Loss: 0.052   lengthscale: 0.265   noise: 0.065
Iter 29/50 - Loss: 0.036   lengthscale: 0.276   noise: 0.060
Iter 30/50 - Loss: 0.036   lengthscale: 0.286   noise: 0.056
Iter 31/50 - Loss: 0.031   lengthscale: 0.297   noise: 0.052
Iter 32/50 - Loss: 0.028   lengthscale: 0.306   noise: 0.048
Iter 33/50 - Loss: 0.030   lengthscale: 0.315   noise: 0.045
Iter 34/50 - Loss: 0.035   lengthscale: 0.322   noise: 0.043
Iter 35/50 - Loss: 0.039   lengthscale: 0.326   noise: 0.041
Iter 36/50 - Loss: 0.043   lengthscale: 0.329   noise: 0.039
Iter 37/50 - Loss: 0.047   lengthscale: 0.327   noise: 0.038
Iter 38/50 - Loss: 0.052   lengthscale: 0.323   noise: 0.037
Iter 39/50 - Loss: 0.048   lengthscale: 0.317   noise: 0.036
Iter 40/50 - Loss: 0.051   lengthscale: 0.309   noise: 0.036
Iter 41/50 - Loss: 0.051   lengthscale: 0.302   noise: 0.036
Iter 42/50 - Loss: 0.047   lengthscale: 0.295   noise: 0.036
Iter 43/50 - Loss: 0.048   lengthscale: 0.288   noise: 0.036
Iter 44/50 - Loss: 0.047   lengthscale: 0.281   noise: 0.037
Iter 45/50 - Loss: 0.047   lengthscale: 0.276   noise: 0.037
Iter 46/50 - Loss: 0.040   lengthscale: 0.273   noise: 0.038
Iter 47/50 - Loss: 0.037   lengthscale: 0.271   noise: 0.039
Iter 48/50 - Loss: 0.040   lengthscale: 0.270   noise: 0.040
Iter 49/50 - Loss: 0.033   lengthscale: 0.269   noise: 0.042
Iter 50/50 - Loss: 0.032   lengthscale: 0.269   noise: 0.043

 

モデルで予測する

次のセルでは、モデルで予測を行ないます。これを行なうには、単純にモデルと尤度を eval モードにして、テストデータ上で両者のモジュールを呼び出します。

ちょうどユーザ定義の GP モデルが forward から事前平均と共分散を含む MultivariateNormal を返すように、eval モードの訓練された GP モデルは事後平均と共分散を含む MultivariateNormal を返します。こうして、予測平均と分散を得て、それから与えられたテストポイントにおける GP からのサンプリング関数は次のような呼び出しで達成されるでしょう :

f_preds = model(test_x)
y_preds = likelihood(model(test_x))

f_mean = f_preds.mean
f_var = f_preds.variance
f_covar = f_preds.covariance_matrix
f_samples = f_preds.sample(sample_shape=torch.Size(1000,))

gpytorch.settings.fast_pred_var コンテキストは必要ではありませんが、ここでは LOVE を使用してより高速な予測分布を得て、クールな特徴の一つを使用するプレビューを与えています :

# Get into evaluation (predictive posterior) mode
model.eval()
likelihood.eval()

# Test points are regularly spaced along [0,1]
# Make predictions by feeding model through likelihood
with torch.no_grad(), gpytorch.settings.fast_pred_var():
    test_x = torch.linspace(0, 1, 51)
    observed_pred = likelihood(model(test_x))

 

モデル fit をプロットする

次のセルでは、ガウス過程モデルの平均と信頼 (= confidence) 領域をプロットします。confidence_region メソッドは平均の上と下の 2 標準偏差を返すヘルパー・メソッドです。

with torch.no_grad():
    # Initialize plot
    f, ax = plt.subplots(1, 1, figsize=(4, 3))

    # Get upper and lower confidence bounds
    lower, upper = observed_pred.confidence_region()
    # Plot training data as black stars
    ax.plot(train_x.numpy(), train_y.numpy(), 'k*')
    # Plot predictive means as blue line
    ax.plot(test_x.numpy(), observed_pred.mean.numpy(), 'b')
    # Shade between the lower and upper confidence bounds
    ax.fill_between(test_x.numpy(), lower.numpy(), upper.numpy(), alpha=0.5)
    ax.set_ylim([-3, 3])
    ax.legend(['Observed Data', 'Mean', 'Confidence'])

 

以上