He estado estudiando la regresión lineal y la probé en el siguiente conjunto {(x, y)}, donde x especificó el área de la casa en pies cuadrados e y especificó el precio en dólares. Este es el primer ejemplo en Andrew Ng Notes .
2104,400 1600,330 2400,369 1416,232 3000,540
Desarrollé un código de muestra, pero cuando lo ejecuto, el costo aumenta con cada paso, mientras que debería disminuir con cada paso. Código y salida a continuación. bias
es W 0 X 0 , donde X 0 = 1. featureWeights
es una matriz de [X 1 , X 2 , ..., X N ]
También probé una solución de Python en línea disponible aquí , y lo expliqué aquí . Pero este ejemplo también está dando el mismo resultado.
¿Dónde está la brecha en la comprensión del concepto?
Código:
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);
}
}
Salida:
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
regression
least-squares
gradient-descent
supervised-learning
Amber Beriwal
fuente
fuente
Respuestas:
La respuesta corta es que el tamaño de tu paso es demasiado grande. En lugar de descender por la pared del cañón, su paso es tan grande que usted está saltando a través de un lado a mayor en el otro!
Función de costo a continuación:
La respuesta larga es que es difícil para un descenso de gradiente ingenuo resolver este problema porque los conjuntos de niveles de su función de costo son elipses altamente alargadas en lugar de círculos. Para resolver este problema de manera sólida, tenga en cuenta que hay formas más sofisticadas de elegir:
Problema subyacente
El problema subyacente es que los conjuntos de niveles de su función de costo son elipses altamente alargadas, y esto causa problemas para el descenso del gradiente. La siguiente figura muestra conjuntos de niveles para la función de costo.
Sugiero leer esta respuesta en Quora.
Solución rápida 1:
Cambie su código a
private float ALPHA = 0.0000002f;
y dejará de sobrepasar.Solución rápida 2:
Soluciones más avanzadas
Si el objetivo fuera resolver eficientemente los mínimos cuadrados ordinarios en lugar de simplemente aprender el descenso de gradiente para una clase, observe que:
La solución real es
Encontrará que esos alcanzan el valor mínimo para la función de costo.
fuente
Como Matthew (Gunn) ya ha indicado, los contornos de la función de costo o rendimiento tridimensional son altamente elípticos en este caso. Desde su código Java utiliza un valor único paso de tamaño para los cálculos de descenso de gradiente, los cambios a los pesos (es decir, la intersección del eje Y y la pendiente de la función lineal) están ambos gobernados por esta única etapa de tamaño.
Como resultado, el tamaño de paso muy pequeño que se requiere para controlar la actualización del peso asociado con el gradiente más grande (en este caso, la pendiente de la función lineal) limita drásticamente la velocidad del otro peso con el gradiente más pequeño (el la intersección del eje y de la función lineal) se actualiza. En las condiciones actuales, el último peso no converge a su verdadero valor de aproximadamente 26.7.
Dado el tiempo y el esfuerzo que ha invertido en escribir su código Java, sugeriría modificarlo para usar dos valores discretos de tamaño de paso, un tamaño de paso apropiado para cada peso. Andrew Ng sugiere en sus notas que es mejor usar el escalado de características para garantizar que los contornos de la función de costo sean más regulares (es decir, circulares) en forma. Sin embargo, modificar su código Java para usar un tamaño de paso diferente para cada peso podría ser un buen ejercicio además de considerar el escalado de características.
Otra idea a considerar es cómo se seleccionan los valores de peso iniciales. En su código Java inicializó ambos valores a cero. También es bastante común inicializar los pesos en valores pequeños y fraccionarios. En este caso particular, sin embargo, ambos enfoques no funcionarían a la luz de los contornos altamente elípticos (es decir, no circulares) de la función de costo tridimensional. Dado que los pesos para este problema se pueden encontrar utilizando otros métodos, como la solución para el sistema lineal sugerido por Matthew al final de su publicación, podría intentar inicializar los pesos a valores más cercanos a los pesos correctos y ver cómo su código original utilizando un solo paso de tamaño converge.
El código Python que encontró se acerca a la solución de la misma manera que su código Java: ambos usan un único parámetro de tamaño de paso. Modifiqué este código de Python para usar un tamaño de paso diferente para cada peso. Lo he incluido a continuación.
Se ejecuta bajo Python 3, que requiere los paréntesis alrededor del argumento para las declaraciones "print". De lo contrario, se ejecutará en Python 2 eliminando los paréntesis. Deberá crear un archivo CSV con los datos del ejemplo de Andrew Ng.
El uso puede hacer una referencia cruzada del código Python para verificar su código Java.
fuente