🐥

能動学習ライブラリmodALを使ったactive regression

2023/04/23に公開

はじめに

機械学習を行う場合の一般論として、データは多い方がいいというものがあります。
ただ、材料の物性データを取得するコストは一般的に高く、数百や数千のレコードを用意しようと思うと膨大なコスト(最低限のデータが集まるまでにXX年とかYY万円とか・・・)を伴うことがほとんどです。そうなると、できるだけ少ないデータで効率的に学習モデルを作りたくなってきます。
効率的な学習モデルの構築というのは、言い換えれば、現状のモデルの精度を向上させるために効果的な水準を選び、そのデータを追加して再学習して、また次の水準を選び・・・、ということになります。こういった一連の流れを統計的にうまく選択していく手法を能動学習 (active learning)といい、能動学習の一通りのアプローチを利用できるようにしたpythonライブラリにmodALというものがあります。

ということで、今回はmodALを使って1変数関数の回帰 (active regression)を能動学習によって行ってみます。

https://modal-python.readthedocs.io/en/latest/index.html

modALのインストール

下記のコマンドのみでOKです。

pip install modAL

今回の題材

今回はシンプルなケースとして正弦波にノイズを加えたものを使います。

import numpy as np
import matplotlib.pyplot as plt

X = np.random.choice(np.linspace(0, 20, 10000), size=200, replace=False).reshape(-1, 1)
y = np.sin(X) + np.random.normal(scale=0.3, size=X.shape)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(X, y)
ax.set_xlabel(r'$X$')
ax.set_ylabel(r'$y$')

学習データをランダムにサンプリングして学習させてみる

通常の学習だとどうなるんでしょうか?まずは、上記のノイズあり正弦波のデータから5点を学習データとしてランダムにサンプリングし、そのデータで学習させてみます。学習モデルにはガウス過程回帰を使います。

from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import WhiteKernel, RBF

n_initial = 5
initial_idx = np.random.choice(range(len(X)), size=n_initial, replace=False)
X_training, y_training = X[initial_idx], y[initial_idx]

kernel = RBF(length_scale=1.0, length_scale_bounds=(1e-2, 1e3)) 
         + WhiteKernel(noise_level=1, noise_level_bounds=(1e-10, 1e+1))
gpr = GaussianProcessRegressor(kernel=kernel, alpha=0)
gpr.fit(X_training, y_training) 

X_grid = np.linspace(0, 20, 1000)
pred_mu, pred_sigma = gpr.predict(X_grid.reshape(-1, 1), return_std=True)
pred_mu, pred_sigma = pred_mu.ravel(), pred_sigma.ravel()

fig = plt.figure(figsize=(4,3))
ax = fig.add_subplot(111)
ax.plot(X_grid, pred_mu, color='blue')
ax.fill_between(X_grid, pred_mu-pred_sigma, pred_mu+pred_sigma, alpha=0.2, color='blue')
ax.scatter(X, y, color='skyblue', s=10)
ax.scatter(X_training, y_training, color='red')
ax.set_xlabel(r'$X$')
ax.set_ylabel(r'$y$')
ax.set_ylim(-2, 2)

5個の学習データでは全然ダメそうですね・・・。学習データを増やすとどうなるでしょうか?

n_initial=30ぐらいであればそこそこ良さそうです。つまり、当てずっぽうでデータを取ってきた場合は30個ぐらいの学習データが必要なことになります。もちろん、サンプリングしてくる点によってはもっと少ない点で良好なモデルができる場合もあります。

Active regressionによる学習

それでは上記のモデル構築をactive regressionでやってみます。能動学習では次に評価すべき点を判断するために、各説明変数に対するモデル改善の可能性を定量化します。これを獲得関数 (acquisition function)やquery strategyと言います。

獲得関数やquery strategyにはいろいろなものがありますが、今回は各評価点における分散を獲得関数として用います。ガウス過程回帰においてデータ点が少ない領域では予測値の分散が大きくなるので、この獲得関数を用いるとデータ点が少ないところを優先的に評価していくような戦略をとることになります。

def GP_regression_std(regressor, X):
    _, std = regressor.predict(X, return_std=True)
    query_idx = np.argmax(std)
    return query_idx, X[query_idx]

modALで能動学習を行う場合にはActiveLearnerというクラスを使います。このクラスに学習器(estimator)、獲得関数 (query strategy)、最初に使用する説明変数 (X_training)と目的変数(y_training)を指定します。ここでは上の例と同様にランダムに5つ選択した点をX_training, y_trainingとしています。

from modAL.models import ActiveLearner

initial_idx = np.random.choice(range(len(X)), size=n_initial, replace=False)
X_training, y_training = X[initial_idx], y[initial_idx]

regressor = ActiveLearner(
    estimator=GaussianProcessRegressor(kernel=kernel),
    query_strategy=GP_regression_std,
    X_training=X_training.reshape(-1, 1), y_training=y_training.reshape(-1, 1)
)

実際にactive regressionをやってみます。n_queriesが探索の回数、query_strategyに基づいて次に追加するべきデータを提案する部分がregressor.query()、新たなデータを学習器に追加して再学習する部分がregressor.teach()に対応しています。

n_queries = 10
fig = plt.figure(figsize=(8,4))
X_taught = []
y_taught = []
for idx in range(n_queries):
    ax = fig.add_subplot(2, 5, idx+1)
    query_idx, query_instance = regressor.query(X)
    regressor.teach(X[query_idx].reshape(1, -1), y[query_idx].reshape(1, -1))
    pred_mu, pred_sigma = regressor.predict(X_grid.reshape(-1, 1), return_std=True)
    pred_mu, pred_sigma = pred_mu.ravel(), pred_sigma.ravel()
    ax.scatter(X, y, color='skyblue', s=10)
    ax.plot(X_grid, pred_mu, color='blue')
    ax.fill_between(X_grid, pred_mu-pred_sigma, pred_mu+pred_sigma, alpha=0.2, color='blue')
    ax.scatter(X_training, y_training, color='salmon')
    ax.scatter(X_taught, y_taught, color='salmon')
    ax.scatter(X[query_idx].reshape(1, -1), y[query_idx].reshape(1, -1), color='red')

    X_taught.append(X[query_idx].reshape(1, -1))
    y_taught.append(y[query_idx].reshape(1, -1))

    ax.set_xlabel(r'$X$')
    ax.set_ylabel(r'$y$')
    ax.set_ylim(-2, 2)
    ax.set_title('n_queries=%s'%(idx+1))
    ax.label_outer()
plt.tight_layout()
plt.show()

上記のデモコードでは最初に選んだ5点とquery_strategyに基づく次点の提案と再学習という操作を10回繰り返し、各繰り返し回ごとに提案された点、学習に用いた点、モデルの推定結果を可視化しています。
データの追加に伴うモデルの変化を示したのが下記です。確かに分散が大きい点が次の観測点として選択されていることがわかると思います。またactive regressionのn_queries=6 (合計データ数11)はランダムに20点のデータを選択した場合よりもうまくフィッティングできていることがわかります。

Discussion