その他

E資格 学習内容まとめ・実装演習(非線形回帰)

はじめに

今回の記事は、前回に引き続き、私が受講しているE資格 JDLA認定プログラムの「ラビットチャレンジ」のレポート記事です。

今回のテーマは機械学習の線形回帰です。
コード実装を取り入れながら行っていきます。

概要

非線形回帰は、教師あり学習で回帰タスクのための非線形の数理モデルです。
複雑な非線形構造を内在する現象に対して、モデリングを実施します。
なお、非線形な構造を捉えられる仕組みが必要で、データの構造を線形で捉えられる場合は限られます。

基底展開法に基づく非線形回帰のモデル

線形回帰モデルは、説明変数に対して目的変数が線形なので、データの非線形性に対応できません。
非線形性に対応させるには、基底展開法が必要です。
基底展開法は、基底関数により説明変数を非線形変換してから、線形回帰モデルで学習することができます。
回帰関数として、基底関数と呼ばれる既知の非線形関数とパラメータベクトルの線形結合を使用します。
未知のパラメータは線形回帰モデルと同様に、最小二乗法や最尤法により推定します。
$$
y_{i}=f(x_{i})+\epsilon_{i}
$$
$$
y_{i}=w_{0}+\sum^{m}_{i=1}{w_{j}\phi_{j}(x_{i})}+\epsilon_{i}
$$
基底関数は、\(\phi_{j}(x_{i})\)で表しています。

推定の流れ

推定の流れは以下のとおりです。

説明変数

$$
x_{i}=(x_{i1},x_{i2},…x_{i1m})\in\mathbb{R}^m
$$
説明変数の数は\(m\)で表しています。

非線形関数ベクトル

基底関数の写像で変換し、\(k\)次元の特徴ベクトルとします。
$$
\phi(x_{i})=(\phi_{1}(x_{i}),\phi_{2}(x_{i}),…\phi_{k}(x_{i}))^T\in\mathbb{R}^k
$$
基底関数の数は\(k\)で表します。

非線形の計画行列

$$
\Phi^{(train)}=(\phi(x_{1}),\phi(x_{2}),…\phi(x_{n}))^T\in\mathbb{R}^{n{\times}k}
$$

最尤法による予測

$$
\hat{y}=\Phi(\Phi^{(train)T}\Phi^{(train)})^{-1}\Phi^{(train)T}y^{(train)}
$$
同様に最小二乗法でも同じように\(\hat{y}\)を求めることができる。

基底関数の種類

多項式関数

$$
\phi_{j}=x^{j}
$$

ガウス型基底関数

$$
\phi_{j}(x)=exp\left(\frac{(x-\mu_{j})^2}{2h_{j}}\right)
$$

2次元ガウス型基底関数の場合

$$
\phi_{j}(x)=exp\left(\frac{(x-\mu_{j})^{T}(x-\mu_{j})}{2h_{j}}\right)
$$

スプライン関数 / Bスプライン関数

未学習と過学習

未学習(underfitting)

学習不足と呼ぶことも多いです。
学習データに対して、十分小さな誤差が得られないモデルを指します。
対策として、モデルの表現力が低いため、表現力の高いモデルを利用することが必要です。

過学習(overfitting)

小さな誤差は得られたものの、テスト集合誤差との差が大きいモデルを指します。
学習データの影響を強く受けすぎて、未知のデータに対して正しく予測できません。
対策として、

  • 学習データの数を増やす
  • 不要な基底関数(変数)を削除して表現力を抑止
  • 正則化法を利用して表現力を抑止

正則化法(罰則化法)

モデルの複雑さに伴い、正則化項(罰則化項)を大きくし、モデルを最小化します。
正則化項(罰則化項)は、形状によっていくつもの種類があり、それぞれ推定量の性質が異なります。
$$
S_{\gamma}=(y-\Phi{w})^T(y-\Phi{w})+\gamma{R}(w)
$$
\(Sγ\):正則化項を課した関数
\(y\):目的変数
\(Φ\):計画行列
\(w\):回帰係数
\(γR(w)\):正則化項
\(γ\):正則化パラメータ

正則化項(罰則項)の役割

正則化項がない場合、最小二乗推定量となります。
L1ノルムを利用した場合、Lasso推定量になります。
$$
R(w)=\sum_{k=1}^m|w_k|=|w_1|+|w_2|+|w_3|+\cdots +|w_m|
$$
L2ノルムの場合は、Ridge推定量になります。
$$
R(w)=\sqrt{\sum_{k=1}^m w_k^2}=\sqrt{w_1^2+w_2^2+w_3^2+\cdots +w_m^2}
$$
Lasso推定量は、いくつかのパラメータを正確に0に推定します。
Ridge推定量は、パラメータを0に近づけるように推定します。

汎化性能

汎化性能とは、学習に使用した入力だけでなく、これまで見たことのない新たな入力に対する予測性能のことです。
汎化誤差(テスト誤差)が小さいものが良い性能を持ったモデルで、汎化誤差は通常、学習データとは別に収集された検証データでの性能を測ることで推定します。

また、手元のモデルがデータに未学習しているか過学習しているかについては、下記のようにわかります。

  • 訓練誤差も汎化誤差もどちらも小さい ⇨ 汎化しているモデルの可能性
  • 訓練誤差は小さいが汎化誤差が大きい ⇨ 過学習
  • 訓練誤差も汎化誤差もどちらも小さくならない ⇨ 未学習

訓練誤差

目的変数との差がどれくらいかという指標です。
どれくらい正しく学習できたかがわかります。
小さぎると過学習が起きている可能性があるので注意が必要です。
$$
MSE_{train}=\frac{1}{n_{train}}\sum_{i=1}^{n_{train}}{(\hat{y}^{train}-y^{train})^2}
$$

汎化誤差(テスト誤差)

モデルの性能を表す指標です。
未知のデータをどれだけ精度良く予測できたかがわかります。
$$
MSE_{test}=\frac{1}{n_{test}}\sum_{i=1}^{n_{test}}{(\hat{y}^{test}-y^{test})^2}
$$
汎化誤差はバイアス、バリアンス、ノイズに分解できます。(バイアス・バリアンス分解)

バイアス・バリアンス分解

実データを用いてモデルをフィッティングした場合には、真のモデルとの間に「バイアス、バリアンス、ノイズ」の3つの要素によってズレが生じます。このズレ具合(特にバイアスとバリアンス)を評価することで、未学習や過学習などを直感的に理解できるようになります。

  • バイアス:モデルの予測と目的変数との平均のズレ
  • バリアンス:モデルの複雑さ(モデルの分散)

ホールドアウト法

ホールドアウト法は、データ全体を訓練データセットテストデータセットに分割し、モデルの精度を確かめる一般的な手法です。
訓練データセットはモデルの訓練に使い、テストデータセットはモデルの汎化性能を評価するために使います。

ただし、一般に機械学習を応用するには、未知のデータに対する予測性能をさらに向上させるために、さまざまなパラメータ設定のチューニングや比較を行うことも重要となります。
このプロセスはモデル選択と呼ばれます。これはチューニングパラメータの最適な値を選択する分類問題を指します。このチューニングパラメータはハイパーパラメータとも呼ばれます。

ただ、モデル選択において同じテストデータセットを繰り返し使った場合、それは訓練データセットの一部となるので、過学習に陥りやすくなってしまいます。

そこで、訓練データセットとテストデータセットだけでなく、検証データセットの3つに分割することで、より効果的に評価できます。

流れとしては以下の通りです。
訓練データでモデルの学習 ⇨ 検証データでモデル性能の評価 ⇨ チューニングを繰り返し行う
⇨ 満足したらテストデータでモデルの汎化誤差を評価する

訓練ステップとモデル選択ステップに未知のテストデータセットを使えるので、モデルの汎化能力のバイアス(学習不足)が低くなるという利点があります。

また、基底展開法に基づく非線形回帰モデルでは、基底関数の数、位置、バンド幅の値とハイパーパラメータをホールドアウト値を小さくするモデルで決定します。

クロスバリデーション(交差検証)

ホールドアウト法には、1つ問題点があります。
元の訓練データセットをどのように分割するかによって、評価が変わってくるからです。
そこでより効果的な性能評価法のクロスバリデーション(交差検証)を取り上げます。

まず、クロスバリデーション(交差検証)とは、汎化性能を評価する統計的な手法で、分類でも回帰でも用いることができます。
データをいくつかのブロックに分割し、1つのブロックをテストデータ、他を学習データとします。これをイテレータと呼びます。

分割した数だけイテレータを繰り返してモデルをそれぞれ作っていき、この時に、テストデータとするブロックを順に変えていきます。つまり、各イテレータの学習データ、テストデータは別々のものが使用されることになります。

できたモデルに対してそれぞれテストデータを与え、得られた精度の平均(CV値)をそれぞれ算出し、最もCV値が小さいものが一番性能が良いと言えます。

k分割交差検証

クロスバリデーションの中でも代表的なk分割交差検証は、一般にモデルのチューニングに使われます。
非復元抽出を用いて訓練データセットをランダムにk個に分割します。そのうちk-1個をモデルの訓練に使い、1個を性能の評価に使います。
この手段をk回繰り返すことで、k個のモデルを取得し、性能を推定します。
訓練データセットのk個のサブセットに対して、ホールドアウト法をk回繰り返すということです。

例えば、k=10の10分割の場合、9が訓練データセットで1がモデル評価のためのテストデータセットとなります。
非復元抽出をk回数繰り返し、k回の正解率(or誤り率)の平均を性能の評価とします。
納得のいく正解率をだすハイパーパラメータを見つけたら、訓練データセット全体で改めて学習し直します。
k分割交差検証の後に訓練データセット全体でモデルを再び訓練する理由は、学習アルゴリズムに提供する訓練データが多ければ多いほど正確なモデルが得られれるからです。

上述の通り、k分割交差検証は非復元抽出法です。
この手法では、各データ点は訓練と検証(テストサブセット)に1回しか使われないので、ホールドアウト法よりもバリアンス(過学習)の低い性能評価が得られます。

k分割交差検証は多くの場合、kは5〜10で設定します。標準的には10が妥当な選択であるとされています。
ただし、比較的小さなデータセットを扱っている場合は分割数を増やすと良いです。
kの値を大きくすると訓練データが増え、バイアス(学習不足)が低くなります。
一方で実行にかかる時間も長くなり、さらに各訓練サブセットが似てくるため、バリアンス(過学習)が高くなります。
大きなデータセットを扱っている場合は、kの値を小さくし、計算コストを削減しながらモデルの平均性能を評価していきます。

層化k分割交差検証

データセットをk個に分割する際に、データの中身によっては通常のk分割交差検証ではいつもうまくいくとは限りません。
各クラスの比率が均等でない時に、k分割したデータセットそれぞれでクラスの偏りができないような分割(層化抽出分割)をすることで、k分割交差検証の評価のバイアスとバリアンスを改善できます。

グリッドサーチ

グリッドサーチとはハイパーパラメータの最適化手法の1つです。
全てのチューニングパラメータの組み合わせで評価値を算出します。
最も良い評価値を持つチューニングパラメータを持つ組み合わせを「良いモデルのパラメータ」として採用する方式です。

実装演習

必要なライブラリのインポート

まず、必要になるライブラリをインポートしておきます。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline

データの作成

データの作成をします。今回は既存のデータセットではなく、ランダムに乱数を生成し回帰分析します。

# seabornの設定
sns.set()
sns.set_style("darkgrid", {'grid.linestyle': '--'})
sns.set_context("paper")

n=100

def true_func(x):
    z = 1-48*x+218*x**2-315*x**3+145*x**4
    return z 

def linear_func(x):
    z = x
    return z 

データをランダムに生成し、ノイズを加えていきます。

# データ生成
data = np.random.rand(n).astype(np.float32)
data = np.sort(data)
target = true_func(data)

# ノイズを加える
noise = 0.5 * np.random.randn(n) 
target = target  + noise

plt.scatter(data, target)

plt.title('NonLinear Regression')
plt.legend(loc=2)

ランダムなデータのグラフが完成した。
これを、線形・非線形でそれぞれ回帰分析してみる。

線形回帰モデル

from sklearn.linear_model import LinearRegression

clf = LinearRegression()
data = data.reshape(-1,1)
target = target.reshape(-1,1)
clf.fit(data, target)

p_lin = clf.predict(data)

plt.scatter(data, target, label='data')
plt.plot(data, p_lin, color='darkorange', marker='', linestyle='-', linewidth=1, markersize=6, label='linear regression')
plt.legend()
print(clf.score(data, target))

# 出力
0.33994965931324606


決定係数は0.3ほどと、かなり低い値となった。
見てもとれるが、線形回帰には向いていないと言える。

非線形回帰モデル

from sklearn.kernel_ridge import KernelRidge

clf = KernelRidge(alpha=0.0002, kernel='rbf')
clf.fit(data, target)

p_kridge = clf.predict(data)

plt.scatter(data, target, color='blue', label='data')

plt.plot(data, p_kridge, color='orange', linestyle='-', linewidth=3, markersize=6, label='kernel ridge')
plt.legend()
print(clf.score(data, target))

# 出力
0.8123574276019355

線形回帰よりも高い決定係数を出すことができた。