Degrade iniş, bu veri kümesindeki sıradan en küçük karelere çözüm bulamıyor mu?


12

Doğrusal regresyonu inceledim ve x'in evin alanını metre kare cinsinden ve y'nin fiyatı dolar cinsinden belirlediği {{x, y)} setinin altında denedim. Bu Andrew Ng Notes'taki ilk örnektir .

2104.400
1600.330
2400.369
1416.232
3000.540

Bir örnek kod geliştirdim ama çalıştırdığımda maliyet her adımda artıyor, her adımda azalıyor. Kod ve çıktı aşağıda verilmiştir. biasW 0 X 0 , burada X 0 = 1. featureWeightsbir dizidir [X 1 , X, 2 , ..., x K ]

Ayrıca burada bulunan çevrimiçi bir python çözümünü denedim ve burada açıkladım . Ancak bu örnek de aynı çıktıyı vermektedir.

Kavramı anlamadaki boşluk nerede?

Kod:

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

Çıktı:

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

Bu konu dışı.
Michael R.Chernick

3
Burada olduğu gibi şeyler sonsuza kadar patlarsa, muhtemelen bir yerde vektörün ölçeğine bölmeyi unutuyorsunuz.
StasK

5
Matthew tarafından kabul edilen cevap açıkça istatistikseldir. Bu, sorunun yanıtlanması için istatistiksel (programlama değil) uzmanlık gerektirdiği anlamına gelir; tanımı gereği konuyla ilgili yapar. Yeniden açmak için oy kullanıyorum.
amip diyor ki Reinstate Monica

Yanıtlar:


35

Kısa cevap, adım büyüklüğünüzün çok büyük olmasıdır. Bunun yerine kanyon duvarı azalan, senin adım kadar büyük olduğunu bir taraftan karşısında sen atlama yüksek diğer tarafta yukarı!

Aşağıdaki maliyet fonksiyonu:

resim açıklamasını buraya girin

Uzun cevap, saf bir degrade inişin bu sorunu çözmesinin zor olmasıdır, çünkü maliyet fonksiyonunuzun seviye kümeleri daireler yerine oldukça uzamış elipslerdir. Bu sorunu sağlam bir şekilde çözmek için, seçmenin daha karmaşık yollarının olduğunu unutmayın:

  • bir adım boyutu (bir sabit kodlamaya göre).
  • adım yönü (gradyan inişinden).

Altında yatan problem

Temel sorun, maliyet işlevinizin düzey kümelerinin oldukça uzun elips olmasıdır ve bu da degrade iniş için sorunlara neden olur. Aşağıdaki şekilde maliyet fonksiyonu için seviye setleri gösterilmektedir.

  • Yüksek derecede eliptik seviye kümeleri ile, en dik iniş yönü çözeltinin yönüyle zar zor hizalanabilir. Örneğin bu problemde, kesme noktasının ("önyargı" olarak adlandırdığınız) büyük bir mesafe kat etmesi gerekir (kanyon tabanı boyunca ila ), ancak kısmi türevin çok daha büyük olduğu diğer özellik içindir. eğim.026.789
  • Adım boyutu çok büyükse, kelimenin tam anlamıyla alt mavi bölgenin üzerine atlayacak ve inmek yerine yükseleceksiniz.
  • AMA Eğer adım büyüklüğünüzü azaltırsanız, değerini uygun değere getirme ilerlemeniz acı verici bir şekilde yavaşlar.θ0

Bu cevabı Quora'da okumanızı tavsiye ederim .

resim açıklamasını buraya girin

Hızlı düzeltme 1:

Kodunuzu olarak değiştirin private float ALPHA = 0.0000002f;ve aşımı durdurun.

Hızlı düzeltme 2:

X verilerinizi 2.104, 1.600, vb. Olarak yeniden ölçeklerseniz ... seviye kümeleriniz küresel hale gelir ve gradyan inişi hızla daha yüksek bir öğrenme oranıyla birleşir. Bu, tasarım matrisiniz koşul sayısını azaltır .XX

Daha gelişmiş düzeltmeler

Hedef, bir sınıfın gradyan inişini öğrenmek yerine sıradan en küçük kareleri verimli bir şekilde çözmekse, aşağıdakilere dikkat edin:

  • Satır boyutunu ve Armijo kuralı gibi adım boyutunu hesaplamanın daha karmaşık yolları vardır .
  • Yerel koşulların hüküm sürdüğü bir cevabın yanında Newton yöntemi ikinci dereceden yakınsama elde eder ve bir adım yönü ve boyutu seçmenin harika bir yoludur.
  • En küçük kareleri çözmek, doğrusal bir sistemi çözmekle eşdeğerdir. Modern algoritmalar saf degrade iniş kullanmaz. Yerine:
    • Küçük sistemler için (birkaç bin veya daha az bir düzende ), kısmi pivotlama ile QR ayrışması gibi bir şey kullanırlar.k
    • Büyük sistemler için, bunun bir optimizasyon problemi olduğunu formüle ederler ve Krylov altuzay yöntemleri gibi tekrarlamalı yöntemler kullanırlar .

Not çözecektir birçok paket olduğunu lineer sistem(XX)b=Xy için ve bu karşı degrade iniş algoritmasının sonuçlarını kontrol edebilirsiniz.b

Asıl çözüm

  26.789880528523071
   0.165118878075797

Bunların maliyet fonksiyonu için minimum değere ulaştığını göreceksiniz.


5
+1, diğer insanların kodu hata ayıklamasına izin vermek lüks!
Haitao Du

4
@ hxd1011 İlk başta aptal bir kodlama hatası olduğunu düşündüm, ancak bunun yerine naif bir degrade iniş ile neyin yanlış gidebileceğine dair oldukça öğretici bir örnek olmaya başladı (imho).
Matthew Gunn

@MatthewGunn b = 0.99970686, m = 0.17655967 (y = mx + b) çözeltisini aldım. Peki "sabit kodlamadan daha büyük bir adım boyutu" ile ne demek istediniz? Bu, her yineleme için değiştirmemiz gerektiği anlamına mı geliyor? ya da girdi değerlerine göre hesaplamamız gerekiyor mu?
Amber Beriwal

@Amber Beriwal Evet, olurdu yineleme özgü olmak . Bir soru, negatif gradyan yönünde ne kadar ileri gidileceğidir. Basit bir strateji (yaptığınız gibi) için sabit kodlanmış bir değere sahip olmaktır (.0001 vardı). Daha karmaşık bir şey, çizgi arama ve / veya Armijo kuralıdır . Hat arama fikri seçmektir aza indirmek için . Bir yön seçin (örneğin, gradyan) ve ardından çizgi boyunca en düşük noktayı bulmak için bir çizgi araması yapın. i α α i fαiiααif
Matthew Gunn

@AmberBeriwal (26.789, .1651) 'in biraz daha düşük bir maliyete sahip olacağını göreceksiniz. Maliyet fonksiyonunun küçük bir eğime sahip olduğu bir yönde (.9997, .1766) biraz yokuş aşağıdır.
Matthew Gunn

2

Matthew'in (Gunn) daha önce belirttiği gibi, 3 boyutlu maliyet veya performans fonksiyonunun konturları bu durumda oldukça eliptiktir. Java kodunuz, degrade iniş hesaplamaları için tek bir adım boyutu değeri kullandığından, ağırlıktaki güncelleştirmelerin (yani, y ekseni kesişimi ve doğrusal işlevin eğimi) her ikisi de bu tek adım boyutu tarafından yönetilir.

Sonuç olarak, daha büyük gradyanla (bu durumda doğrusal işlevin eğimi) ilişkili ağırlığın güncellenmesini kontrol etmek için gereken çok küçük adım boyutu, diğer ağırlığın daha küçük gradyanla ( doğrusal fonksiyonun y ekseni kesişmesi) güncellenir. Mevcut koşullar altında, son ağırlık yaklaşık 26.7 olan gerçek değerine yaklaşmaz.

Java kodunuzu yazmak için harcadığınız zaman ve çaba göz önüne alındığında, her bir ağırlık için uygun bir adım boyutu olan iki ayrı adım boyutu değeri kullanacak şekilde değiştirmenizi öneririm. Andrew Ng notlarında, maliyet fonksiyonunun konturlarının formda daha düzenli (yani dairesel) olmasını sağlamak için özellik ölçeklendirmesinin kullanılmasının daha iyi olduğunu ileri sürmektedir. Ancak, Java kodunuzu her ağırlık için farklı bir adım boyutu kullanacak şekilde değiştirmek, özellik ölçeklendirmesine bakmanın yanı sıra iyi bir egzersiz olabilir.

Dikkate alınması gereken bir başka fikir de başlangıç ​​ağırlık değerlerinin nasıl seçildiğidir. Java kodunuzda her iki değeri de sıfıra başlattınız. Ağırlıkları küçük, kesirli değerlere başlatmak da oldukça yaygındır. Ancak bu özel durumda, bu yaklaşımların her ikisi de üç boyutlu maliyet fonksiyonunun yüksek derecede eliptik (yani dairesel olmayan) konturları ışığında çalışmaz. Bu sorunun ağırlıkları, gönderisinin sonunda Matthew tarafından önerilen doğrusal sistem çözümü gibi diğer yöntemler kullanılarak bulunabileceği göz önüne alındığında, doğru ağırlıklara daha yakın değerlere ağırlıkları başlatmayı deneyebilir ve orijinal kodunuzun nasıl olduğunu görebilirsiniz. tek bir adım boyutu kullanarak yakınsak.

Bulduğunuz Python kodu, çözüme Java kodunuzla aynı şekilde yaklaşır - her ikisi de tek bir adım boyutu parametresi kullanır. Her ağırlık için farklı bir adım boyutu kullanmak için bu Python kodunu değiştirdim. Aşağıya ekledim.

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 altında çalışır, bu da "print" deyimlerinin argümanında parantez gerektirir. Aksi takdirde parantezleri kaldırarak Python 2 altında çalışır. Andrew Ng örneğindeki verilerle bir CSV dosyası oluşturmanız gerekir.

Java kodunuzu kontrol etmek için Python kodunu çapraz referans kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.