機械学習実践チュートリアル (9): モデルの一般化

一般化

モデルの一般化とは、機械学習モデルが新しい未知のデータに適応する能力を指します。機械学習では、通常、既存のデータ セットをトレーニング セットとテスト セットに分割し、トレーニング セットを使用してモデルをトレーニングし、次にテスト セットを使用してモデルのパフォーマンスを評価します。トレーニング セットで良好なパフォーマンスを発揮するモデルが、テスト セットや実際のアプリケーションで良好なパフォーマンスを発揮するとは限りません。したがって、実際のシナリオでの有効性を確保するには、モデルに優れた一般化機能があることを確認する必要があります。

モデルの汎化能力を向上させるには、通常、データセットのサイズの増加、特徴の選択、特徴のスケーリング、正則化、相互検証などの一連の措置を講じる必要があります。これらの方法により、モデルの過学習を軽減し、新しいデータの予測能力を向上させることができます。

つまり、モデルの一般化は機械学習において非常に重要な概念です。これは実際のアプリケーションにおけるモデルの効果に直接関係しており、機械学習のアルゴリズムやモデルを評価するための重要な指標の 1 つでもあります。

モデルの評価と選択

エラー分析

マシンの予測はダーツを投げるようなもので、ターゲットに近づくほど予測の精度が高くなります。誤差は、バイアスと分散の 2 つのカテゴリに分類できます。次の図を使用すると、次のことを明確に表すことができます。
ここに画像の説明を挿入します
学習タスクに関して、仮定された関数が十分に優れていない場合、フィッティング結果に 2 つの問題が発生する可能性があります。

  • 過小適合(アンダーフィット):パラメータが少なすぎ、仮説関数が自由すぎて単純すぎ、サンプルセットさえもうまく適合していないため、予測が一方に偏りやすく、偏差が大きくなります。
  • 過学習(オーバーフィット):パラメータが多すぎる、仮定した関数が自由すぎる、干渉に弱い、サンプルセットにはよく適合するが、仮定した関数が変形しすぎて予測が左右に振れる、大きな差異があります。

アンダーフィッティングとオーバーフィッティングは、次の図で明確に説明できます。
ここに画像の説明を挿入します
モデルの複雑さとトレーニング セットのサイズを変更するときの、トレーニング セットとテスト セットの誤差の関数グラフ (モデルの複雑さを変更したときの誤差)モデル): データ
ここに画像の説明を挿入します
セットの変更 時間誤差

アンダーフィッティングを解決するのは比較的簡単で、パラメーターまたは特徴を追加するだけですが、問題はオーバーフィッティングです。
過学習に対する解決策には次のようなものがあります。

  • このモデルのパラメータを減らすか、より単純なモデルに変更します。
  • 正規化。
  • トレーニングセットを増やす、ノイズ成分を減らすなど。

一般化エラー

θ \シータθ はハイパーパラメータを表し、J は不明です J_{unknown}J未知{ \{{ θ \シータ} \}}はトレーニング済みモデルθ \thetaθパラメータの後の未知のデータの誤差が小さいほど、一般化能力が高くなります。J test J_{test}Jテスト_ _{ \{{ θ \シータ} \}}はテストマシンに対するモデルの誤差を表しており、誤差が小さいほど汎化能力が強いことを示します。

私たちは、このモデルが一般化できること、つまりトレーニングされていない未知の状況でも機能できることを望んでいます。一般化誤差は、未知のデータを処理するときのモデルのコスト関数を指します: J Unknown J_{Unknown}J未知{ \{{ θ \シータ} \}}
値。モデルの汎化能力を定量化できます。
ただし、モデルをトレーニングしてテストする場合、未知のデータは存在しません。トレーニング セットでのパフォーマンスに基づいてモデルを改善し、トレーニングとテストを実施します。ただし、テスト セットの最終計算は次のようになります:J test J_{test}Jテスト_ _{ \{{ θ \シータ} \}テスト セットは最適化されており、汎化誤差の推定値は明らかに楽観的すぎて低すぎるでしょうつまり、モデルを実際に適用した場合の効果は、予想よりもはるかに悪いということになります。
この問題を解決するために、人々は相互検証の方法を提案しました。

相互検証

相互検証の手順

  1. トレーニング セットはさらにサブトレーニング セットと相互検証セットに分割されます。テスト セットを非表示にし、まだ使用しないでください。(テストセットは未知のデータのシミュレーションです)
  2. さまざまなモデルを使用してサブトレーニング セットでトレーニングし、相互検証セットで各モデルのJ cv J_{cv}を測定しますJc v{ \{{ θ \シータ} \}}
  3. J cv J_{cv}を選択してくださいJc v{ \{{ θ \シータ} \}最小のモデルが最良とみなされます。サブトレーニング セットと相互検証セットをトレーニング セットに結合して、最終モデルをトレーニングします。

ここに画像の説明を挿入します
改良された相互検証方法は、K 分割相互検証です (図 6)。トレーニング セットは多くの小さなブロックに分割され、それぞれのケースで小さなブロックが相互検証セットとして取得され、残りの部分は次のようになります。サブトレーニング セットとしてマージされ、モデルのJ cv J_{cv}Jc v{ \{{ θ \シータ} \}}、各状況を計算し、モデルの平均J cv J_{cv}Jc v{ \{{ θ \シータ} \}}の場合、平均が最小のモデルが最良のモデルとみなされます。最終的には、最適なモデルがトレーニング セット全体でトレーニングされ、汎化誤差がテスト セットで推定されます。

ここに画像の説明を挿入します
K 分割相互検証の利点は、相互検証セットが特別ではないことをさらに保証し、汎化誤差の推定がより正確になることです。

KFold 分割

sklearn では、KFold クラスを使用して k 分割相互検証を実装できます。
k 分割相互検証を実行する場合、KFold オブジェクトは元のデータ セットをランダムに k 個のほぼサイズのサブセットに分割します。各サブセットは「フォールド」と呼ばれます。たとえば、k=5 の場合、10 要素の配列は 5 つのデータ セットに分割され、折り畳まれた各データ セットは 2 つで、5 つの折り畳まれたデータ セットがテスト マシンとして使用されるため、5 つの組み合わせが存在します。 。

from sklearn.model_selection import KFold
import numpy as np

# 创建一个包含10个元素的数组作为样本数据
X = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# 定义K值
k = 5

# 创建KFold对象,并指定n_splits参数为K
kf = KFold(n_splits=k)

# 遍历KFold对象中的每一组训练集和测试集
for train_index, test_index in kf.split(X):
    print("train_index:", train_index, "test_index:", test_index)

出力は次のとおりです。

train_index: [2 3 4 5 6 7 8 9] test_index: [0 1]
train_index: [0 1 4 5 6 7 8 9] test_index: [2 3]
train_index: [0 1 2 3 6 7 8 9] test_index: [4 5]
train_index: [0 1 2 3 4 5 8 9] test_index: [6 7]
train_index: [0 1 2 3 4 5 6 7] test_index: [8 9]

フォールドの値は、CV データセットを使用して最終的に検証されるスコアの数も決定します。

実際の戦闘でのcross_val_score

Cross_val_score 関数は、モデルのパフォーマンスを評価するための Scikit-learn ライブラリの簡単なメソッドの 1 つです。相互検証ベースのモデル スコアを計算し、各フォールドのテスト パフォーマンス スコアを返します。KFold とは異なり、cross_val_score は分割されたデータセットを表示する必要はありません。評価用のモデルとデータセットを提供するだけで、関数が相互検証プロセスを自動的に処理するため、コードがよりクリーンで理解しやすくなります。

データセットとモデル

load_digits は、手書き数字画像データセットをロードするための Scikit-learn ライブラリの関数です。このデータセットには 8x8 ピクセル サイズの 1797 個の手書き数字画像が含まれており、各画像は 0 から 9 までの数値ラベルに対応しています。

from sklearn.datasets import load_digits

digits = load_digits()
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=1, ncols=5, figsize=(10, 3))

for i, ax in enumerate(axes):
    ax.imshow(digits.images[i], cmap='gray')
    ax.set_title(digits.target[i])

plt.show()

ここに画像の説明を挿入します
Scikit-learn ライブラリのKNeighborsClassifier は、k 最近傍アルゴリズムを実装します。このアルゴリズムでは、ハイパーパラメーター k と p がモデルのパフォーマンスに影響します。

  • n_neighbors (つまり、k): 考慮する最近傍の数を指定します。デフォルトでは 5 です。これは、新しいサンプルを予測するときに、データ セット内の最も近い 5 つのデータ ポイントのラベルが使用され、5 つの中で最も多くのラベルが現在のデータのラベルであることを意味します。
  • p: 距離の計算に使用されるメトリック。デフォルトでは、ミンコフスキー距離が使用され、p は 2 です。これは、ユークリッド距離が使用されることを意味します。異なる p 値は異なる距離測定方法に対応します。たとえば、p=1 はマンハッタン距離を表し、p=3 はより複雑なマンハッタン距離測定方法を使用できます。

データセットとテストセットを使用して最良の k,p を取得します

データ セットをトレーニング セットとテスト セットに分割し、k の範囲は 1 ~ 11、p は 1 ~ 6 で、トレーニング セットのスコアをテストして、最良の k と p を取得します。

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

digits = datasets.load_digits()
x = digits.data
y = digits.target

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.4, random_state=666)

best_score, best_p, best_k = 0, 0, 0 
for k in range(2, 11):
    for  p in range(1, 6):
        knn_clf = KNeighborsClassifier(weights="distance", n_neighbors=k, p=p)
        knn_clf.fit(x_train, y_train)
        score = knn_clf.score(x_test, y_test)
        if score > best_score:
            best_score, best_p, best_k = score, p, k

print("Best K=", best_k)
print("Best P=", best_p)
print("Best score=", best_score)

出力結果:

Best K= 3
Best P= 4
Best score= 0.9860917941585535

相互検証を使用して最適な k,p を取得する

Cross_val_score 関数でデフォルトで使用される相互検証方法は 3 分割相互検証です。これはデータセットを 3 つの等しい部分に分割し、そのうちの 2 つはトレーニングに使用され、1 つはテストに使用されます。各フォールド反復では、テスト セットを使用してパフォーマンス メトリック スコアが取得され、すべてのフォールドの結果が平均化されて返されます。

Cross_val_score には cv という名前のパラメータもあり、これを使用して相互検証の分割数、つまり k 値を指定することができることに注意してください。たとえば、 cv=5 は 5 分割相互検証を意味し、データセットを 5 つの等しい部分、つまりトレーニング用に 4 つの部分とテスト用に 1 つの部分に分割します。分類問題と回帰問題の場合、通常は 3、5、または 10 分割交差検証が選択されます。一般に、相互検証のフォールド数が多いほど、モデル評価結果の信頼性は高くなりますが、計算コストも増加します。

つまり、cv パラメーターが明示的に設定されていない場合、cross_val_score はデフォルトで 3 分割相互検証を使用します。つまり、デフォルトの k 値は 3 です。

best_score, best_p, best_k = 0, 0, 0 
for k in range(2, 11):
    for  p in range(1, 6):
        knn_clf = KNeighborsClassifier(weights="distance", n_neighbors=k, p=p)
        scores = cross_val_score(knn_clf, x_train, y_train)
        score = np.mean(scores)
        if score > best_score:
            best_score, best_p, best_k = score, p, k

print("Best K=", best_k)
print("Best P=", best_p)
print("Best score=", best_score)

出力

Best K= 2
Best P= 2
Best score= 0.9823599874006478

最初のケースを比較すると、得られる最適なハイパーパラメータが異なることがわかり、スコアは若干低くなりますが、一般的には 2 番目のケースの方が信頼性が高くなります。ただし、このスコアは、このパラメーターのセットが最適であることを示すだけであり、テスト セット上のモデルの精度を参照するものではないため、次に精度を見てみましょう。

best_knn_clf = KNeighborsClassifier(weights='distance', n_neighbors=2, p=2)
best_knn_clf.fit(x_train, y_train)
best_knn_clf.score(x_test, y_test)

出力結果: 0.980528511821975、これがモデルの精度です。

正則化

原理

ここに画像の説明を挿入します
正則化とは何かを理解するには、まず上の図の方程式を理解する必要があります。トレーニング用の特徴とデータが非常に少ない場合、左側の座標に対応するアンダーフィッティングがよく発生します。そして、多くの場合、達成したい目標は、トレーニングに適切な特徴とデータが使用される中央の座標です。現実には、結果に影響を与える多くの要因、つまり多くの特徴量が存在するため、モデルをトレーニングする際には、上の図に示すように、過学習が発生することがよくあります。
写真の式を例にとると、よく得られるモデルは次のとおりです。

θ 0 + θ 1 x + θ 2 x 2 + θ 3 x 3 + θ 4 x 4 \theta_{0}+\theta_{1}x+\theta_{2}x^2+\theta_{3}x^3 +\theta_{4}x^40+1バツ+2バツ2+3バツ3+4バツ4

中間の座標のグラフを得るには、θ3 と θ4 ができるだけ小さいことが望まれます。この 2 つの項目が小さいほど 0 に近くなり、中間のグラフが得られます。
損失関数の場合:
( 1 2 m [ ∑ i = 1 m ( h θ ( xi ) − yi ) 2 ] ) ({1\over2m}[\sum_{i=1}^{m}{(h_\theta ( x^i)-y^i)^2}])(2m_ _1[i = 1メートル( h( ×y2 ])
線形回帰では、損失関数の最小値
min ( 1 2 m [ ∑ i = 1 m ( h θ ( xi ) − yi ) 2 ] ) min({1\over2m}[ \sum_{i= 1}^{m}{(h_\theta(x^i)-y^i)^2}])2m_ _1[i = 1メートル( h( ×y2 ])
を計算し、θ \thetaθ値。
最小値を見つけるために損失関数に数値を追加する場合、この数値は 0 に近く、最小値はより小さくなければなりません。
では、この値に何が追加されるのでしょうか?θ \thetaθが0 に近づくほど、損失関数への影響は小さくなり、特徴が減少します。
式を一般化すると、
1 2 m [ ∑ i = 1 m ( h θ ( xi ) − yi ) 2 ] ) + λ ∑ j = 1 n θ j 2 {1\over2m}[\sum_{i=1} ^{ m}{(h_\theta(x^i)-y^i)^2}])+\lambda\sum_{j=1}^{n}\theta_{j}^22m_ _1[i = 1メートル( h( ×y2 ])+j = 1j2

θ 値が 0 に近づくように損失関数の最小値を見つけるという目的は達成されます。
これは、元の損失関数にペナルティ項 (λ 項) を追加することに相当し、
過学習を防ぐ方法であり、通常、L2 正則化と呼ばれ、リッジ回帰とも呼ばれます。

L2 正規項を追加すると、推定パラメータ長が短くなると考えられます。これは数学的に特徴収縮と呼ばれます。

収縮方法の紹介: トレーニングおよびパラメーターの解決プロセス中に係数のサイズを考慮することを指します。ペナルティ係数を設定することで、影響の少ない特徴の係数が 0 に減衰され、重要な特徴のみが保持されます。これにより、モデルの複雑さが軽減され、過剰適合が回避されます。一般的に使用される Shinkage 法には、Lasso (L1 正則化) と Ridge 回帰 (L2 正則化) が含まれます。
ラッソ (L1 正則化) の公式:
1 2 m [ ∑ i = 1 m ( h θ ( xi ) − yi ) 2 ] + λ ∑ j = 1 n ∣ θ j ∣ {1\over2m}[\sum_{i= 1] }^{m}{(h_\theta(x^i)-y^i)^2}]+\lambda\sum_{j=1}^{n}|\theta_{j}|2m_ _1[i = 1メートル( h( ×y2 ]+j = 1θj

上記のロジックは、ラグランジュ乗数法の応用と見なすことができます。

収縮法を使用する主な目的は次の 2 つです。

  1. 一方で、モデルでは多くの不必要な特徴 (モデルにとってのノイズ) が考慮される可能性があるため、縮小によってノイズが除去され、モデルの複雑さが軽減されます。
  2. 一方、モデルの特徴に多重共線性 (変数間の相関関係) がある場合、モデルに複数の解が存在する可能性があり、複数解モデルの 1 つの解がモデルの実際の状況を反映できないことがよくあります。関連する機能を追加し、モデルの安定性を向上させます。

対応グラフィックス

L2 正則化の方程式を簡略化できます:
J = J 0 + λ ∑ ww 2 J=J_{0}+\lambda\sum_ww^2J=J0+w2
J0 は元の損失関数を表し、正則化項は次のように仮定します:
2 つの特徴 w が 2 つの値 w1 と w2 を持つと仮定します
L = λ ( w 1 2 + w 2 2 ) L=\lambda(w_{ 1}^ 2+w_{2}^2)L=λ ( w12+w22)
円の方程式を思い出した方がよいでしょう:
( x − a ) 2 + ( y − b ) 2 = r 2 (xa)^2+(yb)^2=r^2( ×_2+( yb )2=r2 ここで、
(a、b) は円の中心の座標、r は半径です。次に、座標の原点を通過する単位ユニットは次のように書くことができます:
L2 正則化項とまったく同じであると同時に、機械学習のタスクは、いくつかの方法 (たとえば、勾配降下法として)。
このとき、私たちのタスクは、L 制約の下で J0 を最小化する解を見つけることになります (ラグランジュ乗数法)。

J0 を解く過程で等高線を描くことができます。同時に、L2 正則化関数 L も w1w2 の 2 次元平面上に描くことができます。下図に示すように、
ここに画像の説明を挿入します
Lは図中の黒丸で表されていますが、勾配降下法が近づいていくと、初めてその円と交わることになりますが、この交点は座標軸上に現れにくくなります。

これは、L2 正則化が疎行列を取得するのが容易ではないことを示していますが、同時に、損失関数の最小値を見つけるために、過学習を防ぐために w1 と w2 を限りなく 0 に近づけます。

リッジ回帰

これは L2 正則化
テスト ケースです。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)
x = np.random.uniform(-3.0, 3.0, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x + 3 + np.random.normal(0, 1, size=100)

plt.scatter(x, y)
plt.show()

ここに画像の説明を挿入します

フィッティングに 20 個の項を使用する (オーバーフィッティングのシミュレーション)

from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

def PolynomiaRegression(degree):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scale', StandardScaler()),
        ('lin_reg', LinearRegression()),
    ])


np.random.seed(666)
x_train, x_test, y_train, y_test = train_test_split(X, y)

poly_reg = PolynomiaRegression(degree=20)
poly_reg.fit(x_train, y_train)

y_poly_predict = poly_reg.predict(x_test)
print(mean_squared_error(y_test, y_poly_predict))
# 167.9401085999025
import matplotlib.pyplot as plt
x_plot = np.linspace(-3, 3, 100).reshape(100, 1)
y_plot = poly_reg.predict(x_plot)

plt.scatter(x, y)
plt.plot(x_plot[:,0], y_plot, color='r')
plt.axis([-3, 3, 0, 6])
plt.show()

ここに画像の説明を挿入します
関数をカプセル化してテスト セットを生成し、モデルをテストする

def plot_model(model):
    x_plot = np.linspace(-3, 3, 100).reshape(100, 1)
    y_plot = model.predict(x_plot)

    plt.scatter(x, y)
    plt.plot(x_plot[:,0], y_plot, color='r')
    plt.axis([-3, 3, 0, 6])
    plt.show()

リッジ回帰を使用します。

from sklearn.linear_model import Ridge
def RidgeRegression(degree, alpha):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scale', StandardScaler()),
        ('lin_reg', Ridge(alpha=alpha)),
    ])

ridege1_reg = RidgeRegression(20, alpha=0.0001)
ridege1_reg.fit(x_train, y_train)

y1_predict = ridege1_reg.predict(x_test)
print(mean_squared_error(y_test, y1_predict))
# 跟之前的136.相比小了很多
plot_model(ridege1_reg)

出力エラー: 1.3233492754136291 α \alpha
ここに画像の説明を挿入します
を調整しますα =1

ridege2_reg = RidgeRegression(20, alpha=1)
ridege2_reg.fit(x_train, y_train)

y2_predict = ridege2_reg.predict(x_test)
print(mean_squared_error(y_test, y2_predict))
plot_model(ridege2_reg)

出力: 1.1888759304218461アルファ \アルファ
ここに画像の説明を挿入します
を調整a =100

ridege2_reg = RidgeRegression(20, alpha=100)
ridege2_reg.fit(x_train, y_train)

y2_predict = ridege2_reg.predict(x_test)
print(mean_squared_error(y_test, y2_predict))
# 1.3196456113086197
plot_model(ridege2_reg)

出力: 1.3196456113086197アルファ \アルファ
ここに画像の説明を挿入します
を調整a =1000000

ridege2_reg = RidgeRegression(20, alpha=1000000)
ridege2_reg.fit(x_train, y_train)

y2_predict = ridege2_reg.predict(x_test)
print(mean_squared_error(y_test, y2_predict))
# 1.8404103153255003
plot_model(ridege2_reg)

出力: 1.8404103153255003
ここに画像の説明を挿入します
上記のアルファ値から、1 ~ 100 の間でより詳細な検索を実行し、適合する最も適切な比較的滑らかな曲線を見つけることができることがわかります。これがL2規則性です。

LASSO 正則化

カプセル化

#%%

import numpy as np
import matplotlib.pyplot as plt
from skimage.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
np.random.seed(42)
x = np.random.uniform(-3.0, 3.0, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x + 3 + np.random.normal(0, 1, size=100)
np.random.seed(666)
x_train, x_test, y_train, y_test = train_test_split(X, y)

plt.scatter(x, y)
plt.show()

#%%

from sklearn.linear_model import Lasso
def plot_model(model):
    x_plot = np.linspace(-3, 3, 100).reshape(100, 1)
    y_plot = model.predict(x_plot)

    plt.scatter(x, y)
    plt.plot(x_plot[:,0], y_plot, color='r')
    plt.axis([-3, 3, 0, 6])
    plt.show()
def LassoRegression(degree, alpha):
    return Pipeline([
        ('poly', PolynomialFeatures(degree=degree)),
        ('std_scale', StandardScaler()),
        ('lin_reg', Lasso(alpha=alpha)),
    ])
def TestRegression(degree, alpha):
    lasso1_reg = LassoRegression(degree, alpha) 
    #这里相比Ridge的alpha小了很多,这是因为在Ridge中是平方项
    lasso1_reg.fit(x_train, y_train)
    
    y1_predict = lasso1_reg.predict(x_test)
    print(mean_squared_error(y_test, y1_predict))
    # 1.149608084325997
    plot_model(lasso1_reg)

ラッソ回帰の使用: α \alpha
の調整α =0.01

TestRegression(20,0.01)

出力: 1.149608084325997
ここに画像の説明を挿入します

α \alphaを調整するα =0.1

TestRegression(20,0.1)

出力: 1.1213911351818648アルファ \アルファ
ここに画像の説明を挿入します
を調整α =1

TestRegression(20,1)

出力: 1.8408939659515595
ここに画像の説明を挿入します

Ridge と LASSO について説明する

ここに画像の説明を挿入します
2 つの図を比較すると、LASSO によって適合されたモデルは直線である可能性が高いのに対し、Ridge によって適合されたモデルは曲線である可能性が高いことがわかります。
これは 2 つの規則性が本質的に異なるためで、Ridgeは全体の合計をできるだけ小さくする傾向があるのに対し、Lasso
は一部の値が 0 になる傾向があり、特徴選択に使用できるため、このように呼ばれます。選択: 操作の理由。

おすすめ

転載: blog.csdn.net/liaomin416100569/article/details/130289602