Gradient descent không tìm thấy giải pháp cho bình phương tối thiểu thông thường trên tập dữ liệu này?


12

Tôi đã nghiên cứu hồi quy tuyến tính và đã thử nó ở bên dưới tập {(x, y)}, trong đó x chỉ định diện tích nhà bằng feet vuông và y chỉ định giá bằng đô la. Đây là ví dụ đầu tiên trong Andrew Ng Notes .

2104,400
1600.330
2400.369
1416.232
3000,540

Tôi đã phát triển một mã mẫu nhưng khi tôi chạy nó, chi phí sẽ tăng theo từng bước trong khi nó sẽ giảm dần theo từng bước. Mã và đầu ra được đưa ra dưới đây. biaslà W 0 X 0 , trong đó X 0 = 1. featureWeightslà một mảng của [X 1 , X 2 , ..., X N ]

Tôi cũng đã thử một giải pháp python trực tuyến có sẵn ở đây , và giải thích ở đây . Nhưng ví dụ này cũng cho đầu ra tương tự.

Đâu là khoảng trống trong việc hiểu khái niệm?

Mã số:

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);
    }
}

Đầu ra:

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

Đây là chủ đề ở đây.
Michael R. Chernick

3
Nếu mọi thứ nổ tung đến vô tận như họ làm ở đây, có lẽ bạn đang quên phân chia theo tỷ lệ của vectơ ở đâu đó.
StasK

5
Câu trả lời được chấp nhận bởi Matthew rõ ràng là thống kê. Điều này có nghĩa là câu hỏi đòi hỏi phải có chuyên môn về thống kê (và không phải lập trình) để trả lời; nó làm cho nó theo chủ đề theo định nghĩa. Tôi bỏ phiếu để mở lại.
amip nói rằng Phục hồi Monica

Câu trả lời:


35

Câu trả lời ngắn gọn là kích thước bước của bạn quá lớn. Thay vì đi xuống bức tường hẻm núi, bước chân của bạn lớn đến mức bạn nhảy từ bên này sang bên kia cao hơn !

Hàm chi phí dưới đây:

nhập mô tả hình ảnh ở đây

Câu trả lời dài là rất khó để một người gốc gradient ngây thơ giải quyết vấn đề này bởi vì các tập cấp hàm của hàm chi phí của bạn là các hình elip có độ giãn dài thay vì hình tròn. Để giải quyết vấn đề này một cách mạnh mẽ, lưu ý rằng có nhiều cách tinh vi hơn để chọn:

  • một kích thước bước (hơn mã hóa một hằng số).
  • một hướng bước (hơn độ dốc gốc).

Vấn đề cơ bản

Vấn đề cơ bản là các bộ mức của hàm chi phí của bạn là các hình elip có độ giãn dài cao và điều này gây ra vấn đề cho việc giảm độ dốc. Hình dưới đây cho thấy tập cấp cho hàm chi phí.

  • 026.789 dọc theo sàn hẻm núi) nhưng nó là dành cho các tính năng khác, nơi các đạo hàm riêng có độ dốc lớn hơn nhiều .
  • Nếu kích thước bước quá lớn, bạn sẽ nhảy qua khu vực màu xanh thấp hơn và tăng dần thay vì hạ xuống.
  • θ0

Tôi đề nghị đọc câu trả lời này trên Quora.

nhập mô tả hình ảnh ở đây

Khắc phục nhanh 1:

Thay đổi mã của bạn thành private float ALPHA = 0.0000002f; và bạn sẽ ngừng quá mức.

Khắc phục nhanh 2:

X'X

Sửa lỗi nâng cao hơn

Nếu mục tiêu là giải quyết hiệu quả các bình phương tối thiểu thông thường thay vì chỉ đơn giản là học độ dốc cho một lớp, hãy quan sát rằng:

  • Có nhiều cách tinh vi hơn để tính kích thước bước, chẳng hạn như tìm kiếm dòngquy tắc Armijo .
  • Gần một câu trả lời trong đó điều kiện địa phương chiếm ưu thế, phương pháp của Newton có được sự hội tụ bậc hai và là một cách tuyệt vời để chọn hướng và kích thước bước.
  • Giải các bình phương tối thiểu tương đương với việc giải một hệ tuyến tính. Các thuật toán hiện đại không sử dụng gốc dốc ngây thơ. Thay thế:
    • k
    • Đối với các hệ thống lớn, họ thực sự coi đó là một vấn đề tối ưu hóa và sử dụng các phương pháp lặp như phương pháp không gian con Krylov .

(X'X)b= =X'yb

Giải pháp thực tế là

  26.789880528523071
   0.165118878075797

Bạn sẽ thấy rằng những người đạt được giá trị tối thiểu cho hàm chi phí.


5
+1 thật là xa xỉ khi để người khác gỡ lỗi mã!
Haitao Du

4
@ hxd1011 Lúc đầu, tôi nghĩ rằng đó là một lỗi mã hóa ngu ngốc, nhưng thay vào đó, nó biến (imho) thành một ví dụ khá hướng dẫn về những gì có thể xảy ra với độ dốc dốc ngây thơ.
Matthew Gunn

@MatthewGunn Tôi đã nhận được giải pháp b = 0.99970686, m = 0.17655967 (y = mx + b). Và ý của bạn là "kích thước bước hơn mã hóa hằng số" là gì? Điều đó có nghĩa là chúng ta nên thay đổi nó cho mỗi lần lặp? hoặc chúng ta cần tính toán nó dựa trên các giá trị đầu vào?
Amber Beriwal

αTôiTôiααTôif

@AmberBeriwal Bạn sẽ thấy rằng (26.789, .1651) sẽ có chi phí thấp hơn một chút. Nó hơi xuống dốc từ (.9997, .1766) theo hướng mà hàm chi phí có độ dốc nhỏ.
Matthew Gunn

2

Như Matthew (Gunn) đã chỉ ra, các đường viền của hàm chi phí hoặc hiệu suất 3 chiều có tính elip cao trong trường hợp này. Kể từ mã Java của bạn sử dụng một giá trị bước kích thước duy nhất cho các tính toán gradient descent, các bản cập nhật để các trọng (tức là chặn trục y và độ dốc của hàm tuyến tính) đều được cả hai điều chỉnh bởi đơn bước kích thước này.

Do đó, kích thước bước rất nhỏ được yêu cầu để kiểm soát cập nhật trọng số liên quan đến độ dốc lớn hơn (trong trường hợp này là độ dốc của hàm tuyến tính) hạn chế đáng kể trọng lượng khác với độ dốc nhỏ hơn ( đánh chặn trục y của hàm tuyến tính) được cập nhật. Trong các điều kiện hiện tại, trọng lượng sau không hội tụ về giá trị thực của nó là khoảng 26,7.

Với thời gian và công sức mà bạn đã đầu tư để viết mã Java, tôi sẽ đề nghị sửa đổi nó để sử dụng hai giá trị kích thước bước riêng biệt, một kích thước bước thích hợp cho mỗi trọng số. Andrew Ng gợi ý trong các ghi chú của mình rằng tốt hơn là sử dụng tính năng chia tỷ lệ để đảm bảo rằng các đường viền của hàm chi phí thường xuyên hơn (tức là hình tròn). Tuy nhiên, sửa đổi mã Java của bạn để sử dụng một kích thước bước khác nhau cho mỗi trọng lượng có thể là một bài tập tốt ngoài việc xem xét tỷ lệ tính năng.

Một ý tưởng khác để xem xét là làm thế nào các giá trị trọng lượng ban đầu được chọn. Trong mã Java của bạn, bạn đã khởi tạo cả hai giá trị về 0. Nó cũng khá phổ biến để khởi tạo các trọng số thành các giá trị nhỏ, phân số. Tuy nhiên, trong trường hợp cụ thể này, cả hai cách tiếp cận này sẽ không hoạt động dưới ánh sáng của các đường viền hình elip (tức là không tròn) của hàm chi phí ba chiều. Có thể tìm thấy các trọng số cho vấn đề này bằng các phương pháp khác, như giải pháp cho hệ thống tuyến tính được đề xuất bởi Matthew ở cuối bài đăng của mình, bạn có thể thử khởi tạo các trọng số cho các giá trị gần hơn với các trọng số chính xác và xem mã gốc của bạn như thế nào sử dụng một bước hội tụ kích thước duy nhất.

Mã Python bạn tìm thấy tiếp cận giải pháp theo cùng cách với mã Java của bạn - cả hai đều sử dụng một tham số kích thước bước duy nhất. Tôi đã sửa đổi mã Python này để sử dụng một kích thước bước khác nhau cho mỗi trọng lượng. Tôi đã bao gồm nó dưới đây.

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()

Nó chạy trong Python 3, đòi hỏi các dấu ngoặc đơn xung quanh đối số cho các câu lệnh "in". Nếu không, nó sẽ chạy trong Python 2 bằng cách loại bỏ dấu ngoặc đơn. Bạn sẽ cần tạo tệp CSV với dữ liệu từ ví dụ của Andrew Ng.

Sử dụng có thể tham chiếu chéo mã Python để kiểm tra mã Java của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.