Coursera ML-最適化アルゴリズムの選択は、マルチクラスロジスティック回帰の精度に影響しますか?


7

最近、Pythonを使用したCourseraでのAndrew Ngの機械学習の演習3を完了しました。

演習のパート1.4から1.4.1を最初に完了すると、トレーニング済みモデルが予想される94.9%に一致する精度を持っていることを確認するのが困難になりました。デバッグして、コストと勾配関数にバグがないこと、および予測子コードが正しく機能していることを確認した後でも、精度は90.3%しかありませんでした。で共役勾配(CG)アルゴリズムを使用していましたscipy.optimize.minimize

好奇心から、別のアルゴリズムを試すことにし、ブロイデン-フレッチャー-ゴールドファーブ-シャノン(BFGS)を使用しました。驚いたことに、精度は大幅に96.5%に向上し、予想を上回りました。これら2つの異なる結果のCGとBFGSの比較は、ノートブックのヘッダー「最適化アルゴリズムの違いによる精度の違い」の下で確認できます

最適化アルゴリズムの選択が異なるため、この精度の違いの理由はありますか?はいの場合、誰かが理由を説明できますか?

また、これを引き起こしている関数にバグがないことを確認するためだけにコードをレビューしていただければ幸いです。

ありがとうございました。

編集:ここで、読者にJupyterノートブックへのリンクを紹介するのではなく、このページで行うコメントのリクエストに、質問に含まれるコードを追加しました。

モデルのコスト関数:

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

def compute_cost_regularized(theta, X, y, lda):
    reg =lda/(2*len(y)) * np.sum(theta[1:]**2) 
    return 1/len(y) * np.sum(-y @ np.log(sigmoid(X@theta)) 
                             - (1-y) @ np.log(1-sigmoid(X@theta))) + reg

def compute_gradient_regularized(theta, X, y, lda):
    gradient = np.zeros(len(theta))
    XT = X.T
    beta = sigmoid(X@theta) - y
    regterm = lda/len(y) * theta
    # theta_0 does not get regularized, so a 0 is substituted in its place
    regterm[0] = 0 
    gradient = (1/len(y) * XT@beta).T + regterm
    return gradient

1対すべての分類トレーニングを実装する関数:

from scipy.optimize import minimize

def train_one_vs_all(X, y, opt_method):
    theta_all = np.zeros((y.max()-y.min()+1, X.shape[1]))
    for k in range(y.min(),y.max()+1):
        grdtruth = np.where(y==k, 1,0)
        results = minimize(compute_cost_regularized, theta_all[k-1,:], 
                           args = (X,grdtruth,0.1),
                           method = opt_method, 
                           jac = compute_gradient_regularized)
        # optimized parameters are accessible through the x attribute
        theta_optimized = results.x
        # Assign thetheta_optimized vector to the appropriate row in the 
        # theta_all matrix
        theta_all[k-1,:] = theta_optimized
    return theta_all

関数を呼び出して、さまざまな最適化方法でモデルをトレーニングします。

theta_all_optimized_cg = train_one_vs_all(X_bias, y, 'CG')  # Optimization performed using Conjugate Gradient
theta_all_optimized_bfgs = train_one_vs_all(X_bias, y, 'BFGS') # optimization performed using Broyden–Fletcher–Goldfarb–Shanno

予測結果は、使用するアルゴリズムによって異なることがわかります。

def predict_one_vs_all(X, theta):
    return np.mean(np.argmax(sigmoid(X@theta.T), axis=1)+1 == y)*100

In[16]: predict_one_vs_all(X_bias, theta_all_optimized_cg)
Out[16]: 90.319999999999993

In[17]: predict_one_vs_all(X_bias, theta_all_optimized_bfgs)
Out[17]: 96.480000000000004

コードを試すためにデータを取得したい人は、この投稿にリンクされているように、私のGithubで見つけることができます。


1
ロジスティック回帰は単一の安定した最小値(線形回帰のように)を持つ必要があるため、気づかなかった何かがこれを引き起こしている可能性があります
Neil Slater

では、最小コストへの収束が保証されている必要がありますか?コードレビューをお願いできますか?
AKKA 2017

1
確認する必要のあるコードがたくさんある場合は、多分codereview.stackexchange.comに投稿してください。問題を再現するために必要な量が少ない場合は、ここに質問に追加できます(コードブロックとして編集してください。問題を完全に再現するのに十分な数を含めてください)。
Neil Slater 2017

大域的最小値を確保することで最適化アルゴリズムに関係なく同じ結果が得られることは事実ですが、アルゴリズムの実装(つまり、数値安定性を処理する方法など)には微妙に異なるソリューションが生じる可能性があります。ソリューションのこれらの小さな違いは、小さなテストセットで評価した場合、パフォーマンスの大きな違いにつながる可能性があります。それはあなたのケースでそのような大きなパフォーマンスの違いを引き起こしているかもしれません。そして、はい、一般に、最適化アルゴリズムは学習結果に大きく影響します。ところで、MATLABで望ましい結果を得ました。
2017

1
@NeilSlater:わかりました。編集としてコードを質問に直接追加しました。大丈夫ですか?
AKKA 2017

回答:


3

数値の精度と安定性の限界により、最適化ルーチンが苦労しています。

これは、正規化項を0.0に変更することで最も簡単に確認できます。これが原理的に機能しない理由はなく、特にそれを必要とする機能エンジニアリングを使用していません。正則化を0.0に設定すると、コスト関数を計算するときに精度の限界に達し、ログの0を取得しようとします。2つの異なる最適化ルーチンは、ルート上の異なるサンプルポイントを最小に取るため、異なる影響を受けます。

正則化の項を高く設定すると、数値の不安定性を取り除くことができますが、計算で実際に何が行われているのか分からないという犠牲を払って、実際には、正則化の項が難しいトレーニングの例で支配的になります。

コスト関数を変更することで、いくつかの精度の問題を相殺できます。

def compute_cost_regularized(theta, X, y, lda):
    reg =lda/(2*len(y)) * np.sum(theta[1:]**2) 
    return reg - 1/len(y) * np.sum(
      y @ np.log( np.maximum(sigmoid(X@theta), 1e-10) ) 
      + (1-y) @ np.log( np.maximum(1-sigmoid(X@theta), 1e-10) ) )

また、トレーニング中にフィードバックを得るために、

                       options = {
                           'disp': True
                       }

への呼び出しにminimize

この変更により、正則化項をゼロに設定して試すことができます。これを行うと、次のようになります。

predict_one_vs_all(X_bias, theta_all_optimized_cg)
Out[156]:
94.760000000000005
In [157]:

predict_one_vs_all(X_bias, theta_all_optimized_bfgs)
/usr/local/lib/python3.6/site-packages/ipykernel/__main__.py:2: RuntimeWarning: overflow encountered in exp
  from ipykernel import kernelapp as app
Out[157]:
98.839999999999989

94.76のCG値は期待される結果とうまく一致しているようです。これは、正規化なしで行われたのではないかと思います。BFGS値はまだ「より良い」ですが、トレーニングと評価中に警告メッセージが出されたときにどれだけ信頼できるかはわかりません。この明らかに優れたトレーニング結果が実際に優れた桁検出につながるかどうかを判断するには、ホールドアウトテストセットで結果を測定する必要があります。


回答で提供した分析に本当に感謝します。のように、コスト関数に加えた変更についてまだ質問があります。しきい値としてnp.maximum(sigmoid(X@theta), 1e-10)使用することをどのようにして知りました1e-10か?また、合計の個々の項の負の符号を外にシフトしreg - 、正則化の項から合計の項を差し引いたようにそれを引き出したことにも気づきました。これも重要ですか?
AKKA 2017

あなたが示唆したように、正則化項を0.0に設定しようとしました。ゼロ除算エラーが発生するだけでなく、実行時間も非常に長くなります!エラーゼロによる除算については、なぜなのかよくわかりません。どうやってそれが起こったのですか?これはアルゴリズムの実装の詳細と関係がありますか?数値解法に慣れていないのでご
容赦ください

@AKKA:私は1e-10を任意に選択しただけで、前後の用語の入れ替えは、コードを再確認して理解したことによる副作用でした。どちらも大きな違いはないと思います。技術的にはゼロによる除算ではありませんがnp.log( array_containing_a_zero )、最適化検索中の1つ以上の例で負または正の合計が大きいために発生しました。
Neil Slater

コードは指数をとるのでログを取るため、表示される数値は妥当な範囲内にあるように見えますが、暫定的な計算は極端になる可能性があります。一部のフレームワークは、指数とログが実際に発生しないように式を解決できますが、そのための計算は私を超えています。
Neil Slater

そうですか。それで、あなたが得たより良い結果が過剰適合であったかもしれないと思いますか?最終的にこれを検証するにはテストセットが必要だと言ったのはそのためだと思います...
AKKA

2

CGはBFGSと同様に最小値に収束しません

私自身の質問にもここで回答を追加する場合は、私のコードを見ようと志願した親友にクレジットが与えられます。彼はData Science stackexchangeを利用しておらず、回答を投稿するためだけにアカウントを作成する必要性を感じていなかったため、この機会に私に投稿しました。

数値安定性の問題に関する彼の分析がこれを説明できる可能性があるため、@ Neil Slaterも参照します。

だから私のソリューションの背後にある主な前提は:

コスト関数は凸型であることを知っています。つまり、ローカルはなく、グローバルな最小値のみです。BFGSでトレーニングされたパラメーターを使用した予測は、CGを使用してトレーニングされたパラメーターよりも優れているため、これは、BFGSがCGよりも最小値に近く収束したことを意味します。BFGSがグローバルミニマムに収束したかどうかは確かではありませんが、確実にCGよりも近いと言えます。

したがって、CGを使用してトレーニングされたパラメーターを受け取り、それらをBFGSを使用して最適化ルーチンに渡すと、BFGSがすべてを最小に近づけるため、これらのパラメーターがさらに最適化されることがわかります。これにより、予測精度が向上し、単純なBFGSトレーニングを使用して取得したものに近づくはずです。

以下はこれを検証するコードです。変数名は質問と同じです。

# Copy the old array over, else only a reference is copied, and the 
# original vector gets modified
theta_all_optimized_bfgs_from_cg = np.copy(theta_all_optimized_cg)

for k in range(y.min(),y.max()+1):
    grdtruth = np.where(y==k, 1,0)
    results = minimize(compute_cost_regularized,theta_all_optimized_bfgs_from_cg[k-1,:], 
                       args = (X_bias,grdtruth,0.1),
                       method = "BFGS", 
                       jac = compute_gradient_regularized, options={"disp":True})
    # optimized parameters are accessible through the x attribute
    theta_optimized = results.x
    # Assign thetheta_optimized vector to the appropriate row in the 
    # theta_all matrix
    theta_all_optimized_bfgs_from_cg[k-1,:] = theta_optimized

ループの実行中、反復の1つだけがメッセージを生成し、ゼロ以外の数の最適化ルーチンの反復を示しました。つまり、さらに最適化が実行されました。

Optimization terminated successfully.
         Current function value: 0.078457
         Iterations: 453
         Function evaluations: 455
         Gradient evaluations: 455

そして結果が改善されました:

In[19]:  predict_one_vs_all(X_bias, theta_all_optimized_bfgs_from_cg)
Out[19]:  96.439999999999998

追加のBFGSの実行を通じて、最初にCGから取得されたパラメーターをさらにトレーニングすることにより、それらをさらに最適化して、予測精度をBFGSのみを直接使用することによって取得され96.44%96.48%ものに非常に近いものにしています!

この説明でノートを更新しました。

もちろん、CGがこのコスト関数に対して機能しなかった理由や、CGが機能しなかった理由など、さらに多くの質問が出されますが、これらは別の投稿向けの質問だと思います。


BFGSが壊れていることを除外するために、ホールドアウトテストセットでこれをテストする必要があると思います。しかし、正則化を追加することで損失面が単純にならないかどうかと答えて以来、疑問に思っていました。。。つまり、BFGSの結果はその状況では厳密に優れていますが、このデータセットを正規化しないと不安定になります。
Neil Slater

@NeilSlater:はい、検証と標準のベストプラクティスはテストデータセットで実行することであることに同意します。ただし、テストセットを実行することはCoursera割り当ての一部ではなかったため、そのようなテストセットは提供されませんでした。元のMNISTからチャンクを取り出さなければなりません。正則化しないと共役勾配が改善されるため、あなたが言ったことはもっともらしいようです。ただし、損失面が本当に単純な場合、CGはBFGSよりもパフォーマンスが低下するのはなぜですか?
AKKA 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.