6
8

Python初心者の備忘録 #17 ~機械学習入門編03~

Last updated at Posted at 2024-07-29

はじめに

今回私は最近はやりのchatGPTに興味を持ち、深層学習について学んでみたいと思い立ちました!
深層学習といえばPythonということなので、最終的にはPythonを使って深層学習ができるとこまでコツコツと学習していくことにしました。
ただ、勉強するだけではなく少しでもアウトプットをしようということで、備忘録として学習した内容をまとめていこうと思います。
この記事が少しでも誰かの糧になることを願っております!
※投稿主の環境はWindowsなのでMacの方は多少違う部分が出てくると思いますが、ご了承ください。
最初の記事:Python初心者の備忘録 #01
前の記事:Python初心者の備忘録 #16 ~機械学習入門編02~
次の記事:まだ

今回は非線形回帰正則化項についてまとめております。

■学習に使用している資料

Udemy:【前編】米国データサイエンティストがやさしく教える機械学習超入門【Pythonで実践】

■非線形回帰

  • 線形回帰モデルは解釈の幅は広いがそこまで精度の高いモデルではない
  • 真の$f(X)$が線形であることはほぼなく、非線形なモデルが必要になることが多い
  • 今回は多項式特徴量kNNについて説明する

linear_and_no_linear_graph.jpg

▶多項式特徴量(Polynomial Features)

  • 多項式特徴量 = 多項式回帰(Polynomial Regression)
  • 特徴量を多項式化することで非線形化する
    $y=\theta_0+\theta_1X_1$
    → $y=\theta_0+\theta_1X_1+\theta_2X_1^2+\theta_3X_1^3+...++\theta_dX_1^d$
    ※特徴量を平方数などにして新しい特徴量として考える
  • 元の特徴量空間では非線形になるが、変換後の特徴量空間では線形モデルのまま

example_polynomial.jpg

交互作用項(Interaction Term)

  • 特徴量が複数ある場合それぞれの特徴量を掛け合わせた交互作用項を作成することもできる

$f(X)=\theta_0+\theta_1X_1+\theta_2X_2$
→ $f(X)=\theta_0+\theta_1X_1+\theta_2X_2+\theta_3X_1^2+\theta_4X_2^2+\theta_5X_1X_2$($X_1X_2$が交互作用項)
「広さ」と「駅からの距離」の例:広くて駅から近いと相乗効果で家賃がさらに上がる

すべての交互作用項や多項式項を使う必要はない
t検定などで項を落とす、まったく関係ない特徴量同士の交互作用項は作成しないなど

※単体だとp値が0.05以上で有意差がないとしても、交互作用項では有意差があるという場合は元となる特徴量は残す

▶Pythonで多項式特徴量(多項式回帰)

  • sklearn.preprocessiong.PolynomialFeaturesクラスを使用
    1.PolynomialFeatures(degree, include_bias=True)でインスタンス生成
    - degree:何次元までの多項式にするか
    - include_bias:バイアス項の生成を決める、普通はいらないのでFalseを指定する
    2..fit_transform(X)で多項式特徴量に変換
    3.変換後のXをmodel.fit(X, y)で線形回帰を学習
# データ準備
import seaborn as sns
df = sns.load_dataset('mpg') # mile per gallon
df.dropna(inplace=True)
X = df['horsepower'].values.reshape(-1, 1)
y = df['mpg']
# 本来ならhold-out, 5-foldなどして学習データとテストデータに分けておく
sns.scatterplot(x=df['horsepower'], y=df['mpg'])

example_mpg_plot_by_dot.jpg

# 多項式特徴量
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(2, include_bias=False)
X_poly = poly.fit_transform(X)

# 線形回帰
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_poly, y)

# 描画
import numpy as np
import matplotlib.pyplot as plt
x_axis = np.arange(50, 230).reshape(-1, 1)
x_axis_ = poly.fit_transform(x_axis)
pred = model.predict(x_axis_)
sns.scatterplot(x=df['horsepower'], y=df['mpg'])
plt.plot(x_axis, pred, 'r')

example_mpg_plot_with_polynomial_regression.jpg

Challenge

多項式特徴量と線形回帰の制度の比較を行う

  • horsepowerからmpgを予測するモデルを作成
    - degree=1(線形回帰)
    - degree=2
    - degree=3
  • 5k-Foldを3回繰り返して、MSEで汎化性能を比較
解答例
from sklearn.model_selection import cross_val_score, RepeatedKFold
from sklearn.pipeline import Pipeline
import seaborn as sns
import pandas as pd

# データ準備
df = sns.load_dataset('mpg') # mile per gallon
df.dropna(inplace=True)
X = df['horsepower'].values.reshape(-1, 1)
y = df['mpg']
degrees = [1, 2, 3]
k = 5
n_repeats = 3
cv = RepeatedKFold(n_splits=k, n_repeats=n_repeats, random_state=0)
results = {}

# 評価
for d in degrees:
    # pipelineを使うことでコードを簡潔に書くことが可能
    pipeline = Pipeline(steps=[('poly', PolynomialFeatures(d)), ('model', LinearRegression())])
    scores = cross_val_score(pipeline, X, y, scoring='neg_mean_squared_error', cv=cv)
    results[f"degree {d}"] = {'scores_mean': -np.mean(scores), 'scores_std': np.std(scores)}

# 出力
pd.DataFrame(results)
degree 1 degree 2 degree 3
scores_mean 24.235392 19.294970 19.433812
scores_std 3.346678 4.413627 4.508224

▶kNN(k Nearest Neighbor:k最近傍法)

  • 分類によく使用されるが、回帰にも使用できる(kNN回帰)
  • 数式モデルを使用しないノンパラメトリックな非線形回帰モデル
    → ノンパラメトリックなので、より柔軟な回帰曲線を引ける
  • 最も近いk個の学習データの平均を予測値にする
  • kNNのような距離ベースのアルゴリズムでは特徴量スケーリングが重要

kNN_scaring_example.jpg

距離の定義

  • ユークリッド距離:$\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}$
  • マンハッタン距離:$|x_2-x_1|+|y_2-y_1|$

▶PythonでkNN回帰

  • sklearn.neighbors.KNeighborsRegressorクラスを使用
    1. KNeighborsRegressor(n_neighbors)でインスタンス生成
    n_neighborsで何個のデータを使用するか指定する
    2..fit(X)で学習
    3..predict(X)で予測
# データ準備
from sklearn.model_selection import train_test_split
# データが多すぎるので10%だけ使用する
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.9, random_state=0)
sns.scatterplot(x=X_train[:, 0], y=y_train)

kNN_train_plot.jpg

# kNN回帰モデルを作成
from sklearn.neighbors import KNeighborsRegressor
model = KNeighborsRegressor(n_neighbors=20)
model.fit(X_train, y_train)

# 結果を描画
x_axis = np.arange(50, 230).reshape(-1, 1)
y_pred = model.predict(x_axis)
sns.scatterplot(x=X_train[:, 0], y=y_train)
plt.plot(x_axis, y_pred, 'r')
plt.xlabel('horsepower')

kNN_predict_plot.jpg

# n_neighborsの値を小さくすれば、より学習データにフィットしたモデルになる
model = KNeighborsRegressor(n_neighbors=2)

kNN_predict_plot_by_small_neighbors.jpg

Challenge

様々なkでのkNNの精度を比較する

  • horsepowerからmsgを予測するモデルを作成
    - k=1~30
  • 5-Foldを3回繰り返して汎化性能を比較
  • MSEを評価指標とし、kが増加したときのMSEの推移をplotする
解答例
import seaborn as sns
import numpy as np
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedKFold
from sklearn.neighbors import KNeighborsRegressor
import matplotlib as plt

# データ準備
df = sns.load_dataset('mpg') # mile per gallon
df.dropna(inplace=True)
X = df['horsepower'].values.reshape(-1, 1)
y = df['mpg']

# 5-Fold cvを3回繰り返す
k_list = np.arange(1, 31)
k = 5
n_repeats = 3
cv = RepeatedKFold(n_splits=k, n_repeats=n_repeats, random_state=0)

score_list = []

# 学習+評価
for k in k_list:
    model = KNeighborsRegressor(n_neighbors=k)
    scores = cross_val_score(model, X, y, scoring='neg_mean_squared_error', cv=cv)
    score_list.append(-np.mean(scores))

# 描画
plt.plot(k_list, score_list)
plt.xlabel('k')
plt.ylabel('5fold cv error(MSE)')

challenge_of_kNN_answer_plot.jpg

13あたりが最も損失が少なそう

▶kNNと線形回帰の比較

線形回帰のメリット、デメリット

  • メリット
    - 学習が簡単
    - 解釈がしやすい
    - 仮説検定が可能
  • デメリット
    - 線形以外のモデルを作ることができない
    - 真のfが線形でない場合はkNNより精度が低くなる

■正則化項(Regularization Term)

  • 線形回帰の精度と解釈性をさらに向上させる
    最近は線形回帰をやるなら正則化項をつけるということになっている
  • 最小二乗法の損失関数の式に、生息加工と呼ばれるペナルティを課す項をつける
  • パラメータ$\theta$が大きくなると損失が増えるようにする
    ⇒ パラメータ$\theta$が大きくならないようにする

最小二乗法

$L(\theta)=\sum_{i=1}^m\{y_i-(\theta_0+\sum_{j=1}^n\theta_jx_{ij})\}^2$

正則化項付き損失関数

$L(\theta)=\sum_{i=1}^m\{y_i-(\theta_0+\sum_{j=1}^n\theta_jx_{ij})\}^2+\lambda\sum_{j=1}^n\theta_j^2$ ⇒ Ridge
$L(\theta)=\sum_{i=1}^m\{y_i-(\theta_0+\sum_{j=1}^n\theta_jx_{ij})\}^2+\lambda\sum_{j=1}^n|\theta_j|$ ⇒ Lasso
※$\lambda\sum_{j=1}^n\theta_j^2$:L2ノルム、$\lambda\sum_{j=1}^n|\theta_j|$:L1ノルム

なぜパラメータが大きくならないようにする必要があるのか

  • 一般の線形回帰は特徴量数が多いとlow bias、high varianceになりやすい
    → 最近は技術が向上して特徴量を得やすくなっているため多くなりやすい
  • varianceを下げるために正則化項を使う

$\theta$が小さくなるとその特徴量の係数の値(影響力)が小さくなる
→ 複雑性が下がり、low varianceとなる

例)rent = 7 + 0.27 * spase -> rent = 7 + 0.07 * space
※パラメータ$\theta$が小さくなることで、spaceの影響が低くなっている

▶Ridge(L2ノルム)

  • L2ノルム($\lambda\sum_{j=1}^n\theta_j^2$)の正則化項を使用
  • $\lambda$を大きくするにつれ、各特長量の係数の絶対値は小さくなる
    ※一時的に大きくなる場合もある
  • 特徴量通しのスケールが影響するので、事前に特徴量スケーリングが必要
  • 学習後はすべての特徴量の係数が残ることに注意 ⇒ $\theta_j≠0$

ridge_parameta_image.jpg

▶PythonでRidge

  • sklearn.liear_model.Ridgeクラスを使用
    1.Ridge(alpha)でインスタンスを生成(alphaには$\lambda$を入れる)
    2..fit(X, y)で学習
    3..predict(X)で予測

今回使用するデータは野球選手のデータを使用します。
下記URLにデータがあるので、そこからDLしてください。
https://raw.githubusercontent.com/kirenz/datasets/master/Hitters.csv

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge, LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error

# データ準備
df = pd.read_csv("https://raw.githubusercontent.com/kirenz/datasets/master/Hitters.csv")
# 欠損値対応
df.dropna(inplace=True)

# 今回はSalaryの予測を行う
X = df.loc[:, df.columns!=y_col]
y = df['Salary']

# 標準化のために数値カラムのリストを作成
numeric_cols = X.select_dtypes(include=np.number).columns.to_list()

# ダミー変数作成は数値カラムのリスト作成の後
X = pd.get_dummies(X, drop_first=True)

# hold-out
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

# 標準化
scaler = StandardScaler()
X_train[numeric_cols] = scaler.fit_transform(X_train[numeric_cols])
X_test[numeric_cols] = scaler.transform(X_test[numeric_cols])

# モデル学習
ridge = Ridge()
ridge.fit(X_train, y_train)

# 予測
y_pred = ridge.predict(X_test)
mean_squared_error(y_test, y_pred) # -> 118513.2613545654
mean_absolute_error(y_test, y_pred) # -> 251.0105804238488
# 正則化項なしの線形回帰との比較
lr = LinearRegression()
lr.fit(X_train, y_train)
# X.columns
Index(['AtBat', 'Hits', 'HmRun', 'Runs', 'RBI', 'Walks', 'Years', 'CAtBat',
       'CHits', 'CHmRun', 'CRuns', 'CRBI', 'CWalks', 'PutOuts', 'Assists',
       'Errors', 'League_N', 'Division_W', 'NewLeague_N'],
      dtype='object')
      
# ridge.coef_
array([-255.99352472,  265.41620796,   49.85450911,  -43.4415344 ,
          6.40827279,  126.64463957,  -39.99788141, -180.02267219,
        242.39467113,  -36.09606293,  281.13852012,   46.31799934,
       -164.64027314,   70.88806172,   41.295514  ,  -34.06922615,
         -8.54724705,  -97.91075067,   35.72441367])

# lr.coef_
array([-269.10981701,  272.90887884,   59.97669544,  -55.37512444,
          3.61504229,  131.08295524,  -14.78202567, -514.20110584,
        550.22369897,  -12.29554582,  299.04286135,    4.60615938,
       -156.78117836,   73.32237801,   54.94973217,  -35.64306984,
          1.59251626,  -95.02251105,   28.46798552])

正則化項を使用していない線形回帰のほうが絶対値が若干大きい

Challenge

様々な$\lambda$でのRidgeの精度を比較しよう

  • 様々な特徴量からSalaryを予測するモデルを作成する
    -> $\lambda=10^{-3}~10^{3}$
  • hold-outで学習データとテストデータを7:3で分割し、評価指数はMSE
  • $\lambda$を増加させた際のMSEの推移と係数$\theta_j$の推移をplotする
解答例
# データの準備については上記参照
alphas = np.logspace(-3, 3)
mse_list = []
coefs = []
for alpha in alphas:
    model = Ridge(alpha)
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    mse = mean_squared_error(y_test, y_pred)
    mse_list.append(mse)
    coefs.append(model.coef_)

# 描画
import matplotlib.pyplot as plt
plt.plot(alphas, mse_list)
plt.xscale('log')
plt.xlabel('lambda')
plt.ylabel('test MSE')

ridge_challenge_plot_mse.jpg

plt.plot(alphas, coefs)
plt.xscale('log')
plt.xlabel('lambda')
plt.ylabel('standardized coefficients')

ridge_challenge_plot_theta.jpg

▶Lasso(L1ノルム)

  • L1ノルム($\lambda\sum_{j=1}^n|\theta_j|$)の正則化項を使用
  • $\lambda$を大きくするにつれて各特長量の絶対値が小さくなり、やがて0になる
  • 特徴量同士のスケールが影響するので、事前に特徴量スケーリングが必要
  • 学習後、一部(or全て)の特徴量の係数が0になることを期待する
    -> Lassoは特徴量選択を自動で行うアルゴリズム

lasso_parameta_image.jpg

▶PythonでLasso

  • sklearn.liear_model.Lassoクラスを使用
    1.Lasso(alpha)でインスタンスを生成(alphaには$\lambda$を入れる)
    2..fit(X, y)で学習
    3..predict(X)で予測
# データはRidgeで作成したものを流用
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(X_train, y_train)
y_pred = lasso.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)
print(mse) # -> 117768.90478161341
print(mae) # -> 250.24481877112976
# Lasso vs Ridge vs LinearRegression
#lasso.coef_
array([-263.93470404,  267.52882597,   42.60344047,  -33.86419872,
          7.61638806,  125.23251733,  -29.82564501, -239.24447846,
        330.30504107,   -4.11414372,  259.64536978,    0.        ,
       -160.99905107,   71.08401856,   40.14808014,  -30.18012969,
          0.        ,  -93.87340542,   25.7211037 ])

# ridge.coef_
array([-255.99352472,  265.41620796,   49.85450911,  -43.4415344 ,
          6.40827279,  126.64463957,  -39.99788141, -180.02267219,
        242.39467113,  -36.09606293,  281.13852012,   46.31799934,
       -164.64027314,   70.88806172,   41.295514  ,  -34.06922615,
         -8.54724705,  -97.91075067,   35.72441367])
         
# lr.coef_
array([-269.10981701,  272.90887884,   59.97669544,  -55.37512444,
          3.61504229,  131.08295524,  -14.78202567, -514.20110584,
        550.22369897,  -12.29554582,  299.04286135,    4.60615938,
       -156.78117836,   73.32237801,   54.94973217,  -35.64306984,
          1.59251626,  -95.02251105,   28.46798552])

※Lassoではいくつかの特徴量の係数が0になっている。
→ $\lambda=1$において、Salaryの予測をするうえで必要のない特徴量

Challenge

様々な$\lambda$でのLassoの精度を比較しよう

  • 様々な特徴量からSalaryを予測するモデルを作成する
    -> $\lambda=10^{-3}~10^{3}$
  • hold-outで学習データとテストデータを7:3で分割し、評価指数はMSE
  • $\lambda$を増加させた際のMSEの推移と係数$\theta_j$の推移をplotする
解答例
from sklearn.linear_model import Lasso
# λが小さいと収束しない可能性あり
alphas = np.logspace(-1, 3)
mse_list = []
coefs = []
for alpha in alphas:
    model = Lasso(alpha)
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    mse = mean_squared_error(y_test, y_pred)
    mse_list.append(mse)
    coefs.append(model.coef_)
    
# λ vs MSE
plt.plot(alphas, mse_list)
plt.xscale('log')
plt.xlabel('lambda')
plt.ylabel('test MSE')

lasso_challenge_plot_mse.jpg

# λ vs 係数
plt.plot(alphas, coefs)
plt.xscale('log')
plt.xlabel('lambda')
plt.ylabel('standardized coefficients')

lasso_challenge_plot_theta.jpg

おまけ

# 最適なalpha
alphas[np.argmin(mse_list)] # -> 2.0235896477251565

# 最小のMSE
np.min(mse_list) # -> 116122.4512073138

# 最良のalphaで再度学習して係数を確認
lasso = Lasso(alphas[np.argmin(mse_list)])
lasso.fit(X_train, y_train)
pd.DataFrame({'coef': lasso.coef_, 'column_name': X_train.columns.to_list()})
coef column_name
0 -252.192228 AtBat
1 254.955174 Hits
2 26.344767 HmRun
3 -11.16449 Runs
4 11.554622 RBI
5 117.619609 Walks
6 -41.936832 Years
7 -0.000000 CAtBat
8 139.314907 CHits
9 -0.000000 CHmRun
10 218.619939 CRuns
11 0.000000 CRBI
12 -159.994406 CWalks
13 68.726706 PutOuts
14 26.061780 Assists
15 -25.045089 Errors
16 0.000000 League_N
17 -92.299183 Division_W
18 21.636841 NewLeague_N

RidgeとLassoの比較

  • 目的変数にかかわる特徴量が多い場合はRidgeのほうが精度が高い
  • 目的変数にかかわる特徴量が少ない場合はLassoのほうが精度が高い
    -> 事前にどちらがいいかはわからないので、k-Fold CVで汎化性能が高い方を選ぶ

▶正則化項のもう一つの式

Ridge

※制約$\sum_{j=1}^n\theta_j^2≦s$のもと(sはある一定の値:閾値)
$L(\theta)=\sum_{i=1}^m\{y_i-(\theta_0+\sum_{j=1}^n\theta_jx_{ij})\}^2$を最小化する
another_ridge_image.jpg
$\lambda$が小さくなれば制約の円範囲も小さくなる

Lasso

※制約$\sum_{j=1}^n| \theta_j|≦s$のもと(sはある一定の値:閾値)
$L(\theta)=\sum_{i=1}^m\{y_i-(\theta_0+\sum_{j=1}^n\theta_jx_{ij})\}^2$を最小化する
another_lasso_image.jpg
※角が最適解になりやすいので、0となり特徴量がdropする

次の記事

まだ

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8