機械学習
そもそも機械学習モデリングはどのようなフローで行うか?
- 問題設定:どのような問題を機械学習に解決させるか(そもそも機械学習を使うかどうか)
- データ選定:どのようなデータを使うか
- データの前処理:モデルに学習させられるようにデータを変換
- 機械学習モデルの選定:どの機械学習モデルを利用するか
- モデルの学習:パラメータ推定
- モデルの評価:ハイパーパラメータの選定やモデル精度の測定
今回の話は「機械学習モデルの選定」の際に、選択肢となる機械学習モデルの概要と具体的な手法の中身。
線形回帰
回帰問題とは
ある入力値(離散、あるいは連続値)から出力(連続値)を推定する問題。
線形・非線形の違い
ざっくりと言えば、直線で予測するのが線形回帰、曲線で予測するのが非線形回帰。
関数$f$が線形であるためには、以下の2条件を見たす必要がある。
- 加法性:$f(x+y)=f(x)+f(y)$が成り立つ。
- 斉次性:定数$k$を用いて、$f(kx) = kf(x)$が成り立つ。
線形回帰モデル
教師あり学習で、入力とパラメータの線形結合によって表されるモデル。
パラメータは
\boldsymbol{w} = (w_1,w_2, \dots,w_m)^\top \in \mathbb{R}^m
で表すことができ、これを用いると、入力系列$x$を用いて出力$y$の推定値$\hat{y}$は以下のように、パラメータと入力の線形結合で書くことができる。
\hat{y} = \boldsymbol{w}^T \boldsymbol{x} + w_0 = \sum_{i=1}^{m}w_i x_i + w_0
このパラメータ$\boldsymbol{w}$が学習するべきパラメータで、最小二乗法によって求める。
最小二乗法は、モデル出力と教師データの二乗誤差の和を最小化することによって、パラメータを求める手法。
ここで出てくる、最小化するべき関数は、**MSE(平均二乗誤差)**と呼ばれ、以下の式で表される。
※$N$はデータ数
\mathrm{MSE}_{train}= \frac{1}{N} \sum_{i=1}^{N}(y_i - \hat{y}_i)^2
この$\mathrm{MSE}_{train}$を最小化するためには、この関数を$\boldsymbol{w}$で偏微分すればよい。
すると、
\cfrac{\partial }{\partial \boldsymbol{w}} \mathrm{MSE}_{train} = 0 \\
\Rightarrow \cfrac{\partial }{\partial \boldsymbol{w}} \left(\frac{1}{N} \sum_{i=1}^{N}(y_i - \hat{y}_i)^2 \right)=0 \\
\Rightarrow \hat{\boldsymbol{w}} = (\bf{X}^\top \bf{X})^{\mathrm{-1}}\bf{X}^\top \boldsymbol{y}
ハンズオン
まずは、適当な1次関数にノイズを載せたデータを作成、pythonのnumpyのみを用いて、線形回帰を最小二乗法を用いて行う
import numpy as np
import matplotlib.pyplot as plt
# ランダムデータ生成
x = np.array(range(0,100,1))
y = np.array([2 * value + 10 + np.random.normal(0,20,1) for value in x])
# 線形回帰をする関数
def LinearRegression(x,y):
a_child = np.dot(x, y) - x.sum() * y.sum() / len(x)
a_mother = (x**2).sum() - x.sum()**2 / len(x)
a = a_child / a_mother
b = (y.sum() - a * x.sum()) / len(x)
return a, b
a,b = LinearRegression(x,y)
y_estimate = a * x + b
# 結果を描画
plt.figure(figsize = (8,6))
plt.scatter(x,y, label = 'data')
plt.plot(x, y_estimate, color = 'orange', label = 'regression')
plt.legend(fontsize = 12)
plt.show()
結果が以下。
青いデータ点が作成したデータ。オレンジの直線が線形回帰で求めた直線。
ボストンの住宅データセットを線形回帰モデルで分析。今度はsklearnを用いる。
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn import linear_model
boston_raw_data = load_boston()
# 説明変数と目的変数読み込み
X = boston_raw_data.data
y = boston_raw_data.target
# 訓練とテストに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
# 学習
model = linear_model.LinearRegression()
model.fit(X_train, y_train)
# 評価値を見てみる
print(model.score(X_train, y_train))
print(model.score(X_test, y_test))
結果は
0.7730135569264234
0.5892223849182507
となる。予測精度としてはあまり高くはない。
次に、データセットの項目のうち、'CRIM'(犯罪率)と'RM'(部屋数)のみを用いて回帰を行ってみる。
import pandas as pd
import numpy as np
df = pd.DataFrame(X, columns = boston_raw_data.feature_names).assign(MEDV=np.array(y))
X = np.array(df.loc[:,['CRIM','RM']])
y = np.array(df.loc[:,['MEDV']])
# 訓練とテストに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
# 学習
model = linear_model.LinearRegression()
model.fit(X_train, y_train)
# 評価値を見てみる
print(model.score(X_test, y_test))
print(model.predict([[0.3,4]]))
結果は以下。
0.3872466946091787
[[3.72851066]]
精度は落ちた。このモデルでは、犯罪率が0.3で部屋数が4の物件の価格は、約3.72ということになる。(課題)
データの分割とモデルの汎化性能測定
上記のハンズオン中でボストンの住宅価格データセットを用いる際、データの分割を行った。これは、データへの当てはまりの良さではなく、未知のデータに対してどれくらい精度が高いかを測定するため。
データは2つに分割する。
- 学習用データ:機械学習モデルの学習に利用するデータ
- 検証用データ:学習済みモデルの精度を検証するためのデータ
学習用データでモデルを学習後、検証用データでモデルの未知のデータへの当てはまりの良さ(汎化性能)を測定する。
非線形回帰
ここまでやってきたような線形回帰で捉えられるデータ構造は限られている。
そこで、学習するパラメータ$\boldsymbol{w}$と目的変数との関係が線形ではない、非線形回帰モデルを用いる。
例えば、以下のようなもの。
y = e^x \\
y = w_0 + w_1 x_1^2 \\
y = w_0 + w_1 \sin{x_1} + w_2 \cos{x_2}
ただし、基底関数法を用いれば、上のようなモデルも線形回帰と同じように扱うことが可能。
例えば、$\phi(x)=x^2$や$\phi(x)=\sin{x}$などを用いると、上記の式も
y = w_0 + w_1 \phi(x_1) + w_2 \phi(x_2) + \dots + w_m \phi(x_m)
と変形できる。この式は、パラメータ$\boldsymbol{w}$については線形な関係が成り立っているので、線形回帰と同じような扱いが可能。
より正確な書き方をすると、
y_i = w_0 + \Sigma_{j=1}^{m}w_j \phi_j(\boldsymbol{x}_i)+ \varepsilon_i
この式における、$\phi_j(\boldsymbol{x}_i)$を基底関数と呼ぶ。
この時よく使われる基底関数は
- 多項式関数
- ガウス型基底関数
- スプライン型/Bスプライン関数
など。
未学習と過学習
回帰を実行するにあたりきをつけなければいけないこと。
- 未学習:学習データに対して、十分小さな誤差が得られないモデル。もっと表現力の高いモデルを利用する必要がある。
- 過学習:学習データに対する誤差は小さいが、テストデータに対する誤差が大きいモデル。が奇襲データを増やしたり、モデルの表現力を下げる等の対策が必要。
特に過学習については、基底関数を多く用いると頻発するため、基底関数の数や位置・バンド幅などを調整する必要がある。
正則化法
モデルの複雑さに伴ってパラメータの値が大きくなる正則化項に罰則項を課した関数を最小化。
過学習が起きるモデルのパラメータの値が極端に大きいことが背景にある。
与える罰則項にはいくつか種類がある。
まず、L1ノルムを利用するのがLasso回帰。L1ノルムは以下の形で表される。
||\boldsymbol{w}|| = |w_0| + |w_1| + \dots + |w_m|
続いて、L2ノルムを利用するのがRidge回帰。L2ノルムは以下の形で表される。
||\boldsymbol{w}||^2 = |w_0|^2 + |w_1|^2 + \dots + |w_m|^2
ハンズオン
まずは、実験に使うデータ作成
# データの作成
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
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.figure(figsize = (8,6))
plt.scatter(data, target)
plt.title('NonLinear Regression')
このデータに対して、sklearnのKernelRidgeクラスを用いて非線形回帰を行う。
from sklearn.kernel_ridge import KernelRidge
data = data.reshape(-1,1)
target = target.reshape(-1,1)
# 学習
model = KernelRidge(alpha=0.0002, kernel='rbf')
model.fit(data, target)
# 推定
p_kridge = model.predict(data)
# グラフ描画
plt.figure(figsize = (8,6))
plt.scatter(data, target, label='data')
plt.plot(data, p_kridge, color='orange', linestyle='-', linewidth=3, markersize=6, label='kernel ridge')
plt.legend()
ここで用いたrbfは放射基底関数。予測はデータによく適合しているように見える。
検証方法
学習データと検証データの分け方、パラメータの探し方にも種類がある。
ホールドアウト法
有限のデータを学習用とテスト用の2つに分割し、「学習用データで学習させたモデル(パラメータを求めたモデル)でテスト用データの予測精度を測定」する使い方をする。前述した方法。
手元にデータが大量にない限りは、性能評価があまりよくならない。
クロスバリデーション(交差検証)
手元にあるデータをn分割(nは5や10が多い)、そのうち一つをテスト用、残りを学習用としたモデル評価をn回行う。その精度の平均をモデルの汎化性能とする(この精度の平均をCV値と呼ぶ)。
グリッドサーチ
全てのチューニングパラメータの組み合わせを試して評価値を算出し、最もいい評価値を持つパラメータの組み合わせを選出する。