勾配降下法は、このデータセットの通常の最小二乗法の解を見つけられませんか?


12

私は線形回帰を研究しており、以下のセット{(x、y)}で試してみました。xは平方フィートで家の面積を指定し、yはドルで価格を指定しました。これはAndrew Ng Notesの最初の例です。

2104,400
1600,330
2400,369
1416,232
3000,540

サンプルコードを開発しましたが、実行すると、コストは各ステップで増加しますが、各ステップで減少するはずです。以下に示すコードと出力。biasはW 0 X 0で、X 0 = 1です。featureWeightsは[X 1、X 2、...、X N ]の配列です

また、ここ利用可能なオンラインpythonソリューションを試し、ここ説明しました。しかし、この例でも同じ出力が得られます。

概念を理解する際のギャップはどこにありますか?

コード:

package com.practice.cnn;

import java.util.Arrays;

public class LinearRegressionExample {

    private float ALPHA = 0.0001f;
    private int featureCount = 0;
    private int rowCount = 0;

    private float bias = 1.0f;
    private float[] featureWeights = null;

    private float optimumCost = Float.MAX_VALUE;

    private boolean status = true;

    private float trainingInput[][] = null;
    private float trainingOutput[] = null;

    public void train(float[][] input, float[] output) {
        if (input == null || output == null) {
            return;
        }

        if (input.length != output.length) {
            return;
        }

        if (input.length == 0) {
            return;
        }

        rowCount = input.length;
        featureCount = input[0].length;

        for (int i = 1; i < rowCount; i++) {
            if (input[i] == null) {
                return;
            }

            if (featureCount != input[i].length) {
                return;
            }
        }

        featureWeights = new float[featureCount];
        Arrays.fill(featureWeights, 1.0f);

        bias = 0;   //temp-update-1
        featureWeights[0] = 0;  //temp-update-1

        this.trainingInput = input;
        this.trainingOutput = output;

        int count = 0;
        while (true) {
            float cost = getCost();

            System.out.print("Iteration[" + (count++) + "] ==> ");
            System.out.print("bias -> " + bias);
            for (int i = 0; i < featureCount; i++) {
                System.out.print(", featureWeights[" + i + "] -> " + featureWeights[i]);
            }
            System.out.print(", cost -> " + cost);
            System.out.println();

//          if (cost > optimumCost) {
//              status = false;
//              break;
//          } else {
//              optimumCost = cost;
//          }

            optimumCost = cost;

            float newBias = bias + (ALPHA * getGradientDescent(-1));

            float[] newFeaturesWeights = new float[featureCount];
            for (int i = 0; i < featureCount; i++) {
                newFeaturesWeights[i] = featureWeights[i] + (ALPHA * getGradientDescent(i));
            }

            bias = newBias;

            for (int i = 0; i < featureCount; i++) {
                featureWeights[i] = newFeaturesWeights[i];
            }
        }
    }

    private float getCost() {
        float sum = 0;
        for (int i = 0; i < rowCount; i++) {
            float temp = bias;
            for (int j = 0; j < featureCount; j++) {
                temp += featureWeights[j] * trainingInput[i][j];
            }

            float x = (temp - trainingOutput[i]) * (temp - trainingOutput[i]);
            sum += x;
        }
        return (sum / rowCount);
    }

    private float getGradientDescent(final int index) {
        float sum = 0;
        for (int i = 0; i < rowCount; i++) {
            float temp = bias;
            for (int j = 0; j < featureCount; j++) {
                temp += featureWeights[j] * trainingInput[i][j];
            }

            float x = trainingOutput[i] - (temp);
            sum += (index == -1) ? x : (x * trainingInput[i][index]);
        }
        return ((sum * 2) / rowCount);
    }

    public static void main(String[] args) {
        float[][] input = new float[][] { { 2104 }, { 1600 }, { 2400 }, { 1416 }, { 3000 } };

        float[] output = new float[] { 400, 330, 369, 232, 540 };

        LinearRegressionExample example = new LinearRegressionExample();
        example.train(input, output);
    }
}

出力:

Iteration[0] ==> bias -> 0.0, featureWeights[0] -> 0.0, cost -> 150097.0
Iteration[1] ==> bias -> 0.07484, featureWeights[0] -> 168.14847, cost -> 1.34029099E11
Iteration[2] ==> bias -> -70.60721, featureWeights[0] -> -159417.34, cost -> 1.20725801E17
Iteration[3] ==> bias -> 67012.305, featureWeights[0] -> 1.51299168E8, cost -> 1.0874295E23
Iteration[4] ==> bias -> -6.3599688E7, featureWeights[0] -> -1.43594258E11, cost -> 9.794949E28
Iteration[5] ==> bias -> 6.036088E10, featureWeights[0] -> 1.36281745E14, cost -> 8.822738E34
Iteration[6] ==> bias -> -5.7287012E13, featureWeights[0] -> -1.29341617E17, cost -> Infinity
Iteration[7] ==> bias -> 5.4369677E16, featureWeights[0] -> 1.2275491E20, cost -> Infinity
Iteration[8] ==> bias -> -5.1600908E19, featureWeights[0] -> -1.1650362E23, cost -> Infinity
Iteration[9] ==> bias -> 4.897313E22, featureWeights[0] -> 1.1057068E26, cost -> Infinity
Iteration[10] ==> bias -> -4.6479177E25, featureWeights[0] -> -1.0493987E29, cost -> Infinity
Iteration[11] ==> bias -> 4.411223E28, featureWeights[0] -> 9.959581E31, cost -> Infinity
Iteration[12] ==> bias -> -4.186581E31, featureWeights[0] -> -Infinity, cost -> Infinity
Iteration[13] ==> bias -> Infinity, featureWeights[0] -> NaN, cost -> NaN
Iteration[14] ==> bias -> NaN, featureWeights[0] -> NaN, cost -> NaN

これはここではトピック外です。
マイケルR.チャーニック

3
ここでのように物事が無限に爆発する場合、おそらくどこかでベクトルのスケールで割ることを忘れているでしょう。
StasK

5
マシューによる受け入れられた答えは、明らかに統計的です。これは、質問に答えるために統計的な(プログラミングではなく)専門知識が必要であることを意味します。定義上、トピックに基づいています。私は再開することに投票します。
アメーバは、モニカを復活させる

回答:


35

簡単な答えは、ステップサイズが大きすぎるということです。代わりに、峡谷の壁を降順で、あなたのステップは非常に大きいものに片側から全体であなたしているジャンプが高い他にアップ!

以下のコスト関数:

ここに画像の説明を入力してください

長い答えは、単純な勾配降下ではこの問題を解決するのが難しいということです。コスト関数のレベルセットは円ではなく非常に細長い楕円だからです。この問題を確実に解決するには、より洗練された選択方法があることに注意してください。

  • ステップサイズ(定数をハードコーディングするより)。
  • ステップ方向(勾配降下よりも)。

根本的な問題

根本的な問題は、コスト関数のレベルセットが非常に細長い楕円であり、これが勾配降下の問題を引き起こすことです。次の図は、コスト関数のレベルセットを示しています。

  • 026.789
  • ステップサイズが大きすぎる場合は、文字通り青い下の領域を飛び越えて、下降ではなく上昇します。
  • θ0

Quoraでこの回答を読むことをお勧めします。

ここに画像の説明を入力してください

クイックフィックス1:

コードをに変更するとprivate float ALPHA = 0.0000002f;、オーバーシュートが停止します。

クイックフィックス2:

バツバツ

より高度な修正

クラスの勾配降下を単に学習するのではなく、通常の最小二乗を効率的に解くことが目標である場合は、次のことに注意してください。

  • 行検索Armijoルールなど、ステップサイズを計算するより洗練された方法があります。
  • 局所条件が優先される答えの近くで、Newtonの方法は2次収束を取得し、ステップの方向とサイズを選択するのに最適な方法です。
  • 最小二乗を解くことは、線形システムを解くことと同等です。最新のアルゴリズムは、単純な勾配降下を使用しません。代わりに:
    • k
    • 大規模なシステムの場合、最適化問題であると定式化し、クリロフ部分空間法などの反復法を使用します。

バツバツb=バツyb

実際の解決策は

  26.789880528523071
   0.165118878075797

これらがコスト関数の最小値に達することがわかります。


5
+1他の人がコードをデバッグできるようにするのは贅沢です!
ハイタオデュ

4
@ hxd1011私はそれが最初は愚かなコーディングエラーだと思っていましたが、その代わりに、単純な勾配降下で何がうまくいかないかについての非常に有益な例になります。
マシューガン

@MatthewGunn解b = 0.99970686、m = 0.17655967(y = mx + b)を得ました。「定数をハードコーディングするよりもステップサイズが大きい」とはどういう意味ですか?繰り返しごとに変更する必要があるということですか?または、入力値に基づいて計算する必要がありますか?
アンバーベリヴァル

αααf

@AmberBeriwal(26.789、.1651)の方がわずかにコストが低いことがわかります。(.9997、.1766)からわずかに下り坂で、コスト関数の勾配が小さい方向にあります。
マシューガン

2

Matthew(Gunn)がすでに示したように、この場合、3次元のコストまたはパフォーマンス関数の輪郭は非常に楕円形です。Javaコードは勾配降下計算に単一のステップサイズ値を使用するため、重み(つまり、y軸切片と線形関数の勾配)の更新は両方ともこの単一のステップサイズによって管理されます。

その結果、大きな勾配(この場合、線形関数の勾配)に関連する重みの更新を制御するために必要な非常に小さなステップサイズは、小さな勾配を持つ他の重み(線形関数のy軸切片)が更新されます。現在の状況では、後者の重みは真の値である約26.7に収束しません。

Javaコードの作成に費やした時間と労力を考えると、2つの個別のステップサイズ値、各重みに適切なステップサイズを使用するように修正することをお勧めします。Andrew Ngは、ノートで、フィーチャースケーリングを使用して、コスト関数の輪郭がより規則的(つまり、円形)の形になるようにすることをお勧めしています。ただし、機能のスケーリングに加えて、重みごとに異なるステップサイズを使用するようにJavaコードを変更することも適切な演習です。

考慮すべきもう1つのアイデアは、初期重量値の選択方法です。Javaコードで、両方の値をゼロに初期化しました。重みを小さな小数値に初期化することも非常に一般的です。ただし、この特定のケースでは、これらのアプローチは両方とも、3次元コスト関数の非常に楕円形の(つまり、非円形の)輪郭に照らしては機能しません。この問題の重みは、ポストの最後にマシューが提案した線形システムのソリューションのような他の方法を使用して見つけることができるので、正しい重みに近い値に重みを初期化して、元のコードを確認できますシングルステップサイズを使用して収束します。

見つけたPythonコードは、Javaコードと同じ方法でソリューションにアプローチします-どちらも単一のステップサイズパラメーターを使用します。このPythonコードを変更して、重みごとに異なるステップサイズを使用しました。以下に含めました。

from numpy import *

def compute_error_for_line_given_points(b, m, points):
    totalError = 0
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        totalError += (y - (m * x + b)) ** 2
    return totalError / float(len(points))

def step_gradient(b_current, m_current, points, learningRate_1, learningRate_2):
    b_gradient = 0
    m_gradient = 0
    N = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        b_gradient += -(2/N) * (y - ((m_current * x) + b_current))
        m_gradient += -(2/N) * x * (y - ((m_current * x) + b_current))
    new_b = b_current - (learningRate_1 * b_gradient)
    new_m = m_current - (learningRate_2 * m_gradient)
    return [new_b, new_m]

def gradient_descent_runner(points, starting_b, starting_m, learning_rate_1, learning_rate_2, num_iterations):
    b = starting_b
    m = starting_m
    for i in range(num_iterations):
        b, m = step_gradient(b, m, array(points), learning_rate_1, learning_rate_2)
    return [b, m]

def run():
    #points = genfromtxt("data.csv", delimiter=",")
    #learning_rate = 0.0001
    #num_iterations = 200

    points = genfromtxt("test_set.csv", delimiter=",")
    learning_rate_1 = 0.5
    learning_rate_2 = 0.0000001
    num_iterations = 1000

    initial_b = 0 # initial y-intercept guess
    initial_m = 0 # initial slope guess


    print("Starting gradient descent at b = {0}, m = {1}, error = {2}".format(initial_b, initial_m, compute_error_for_line_given_points(initial_b, initial_m, points)))
    print("Running...")

    [b, m] = gradient_descent_runner(points, initial_b, initial_m, learning_rate_1, learning_rate_2, num_iterations)

    print("After {0} iterations b = {1}, m = {2}, error = {3}".format(num_iterations, b, m, compute_error_for_line_given_points(b, m, points)))

if __name__ == '__main__':
    run()

Python 3の下で実行され、「print」ステートメントの引数を括弧で囲む必要があります。それ以外の場合は、括弧を削除することによりPython 2で実行されます。Andrew Ngの例のデータを使用してCSVファイルを作成する必要があります。

を使用すると、Pythonコードを相互参照してJavaコードを確認できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.