我試圖在Java中實現多變量梯度下降算法(來自AI coursera課程),並且我無法確定位於我的代碼中的故障位於何處。多變量梯度下降的Java實現
這是下面程序的輸出:
Before train: parameters := [0.0, 0.0, 0.0] -> cost function := 2.5021875E9
After first iteration: parameters := [378.5833333333333, 2.214166666666667, 50043.75000000001] -> cost function := 5.404438291015627E9
正如你所看到的,第一次迭代後,該值是路要走。我究竟做錯了什麼?
這是我想要實現的算法:
,代碼:
import java.util.*;
public class GradientDescent {
private double[][] trainingData;
private double[] means;
private double[] scale;
private double[] parameters;
private double learningRate;
GradientDescent() {
this.learningRate = 0D;
}
public double predict(double[] inp){
double[] features = new double[inp.length + 1];
features[0] = 1;
for(int i = 0; i < inp.length; i++) {
features[i+1] = inp[i];
}
double prediction = 0;
for(int i = 0; i < parameters.length; i++) {
prediction = parameters[i] * features[i];
}
return prediction;
}
public void train(){
double[] tempParameters = new double[parameters.length];
for(int i = 0; i < parameters.length; i++) {
tempParameters[i] = parameters[i] - learningRate * partialDerivative(i);
//System.out.println(tempParameters[i] + " = " + parameters[i] + " - " + learningRate + " * " + partialDerivative(i));
}
System.out.println("Before train: parameters := " + Arrays.toString(parameters) + " -> cost function := " + costFunction());
parameters = tempParameters;
System.out.println("After first iteration: parameters := " + Arrays.toString(parameters) + " -> cost function := " + costFunction());
}
private double partialDerivative(int index) {
double sum = 0;
for(int i = 0; i < trainingData.length; i++) {
double[] input = new double[trainingData[i].length - 1];
int j = 0;
for(; j < trainingData[i].length - 1; j++) {
input[j] = trainingData[i][j];
}
sum += ((predict(input) - trainingData[i][j]) * trainingData[i][index]);
}
return (1D/trainingData.length) * sum;
}
public double[][] getTrainingData() {
return trainingData;
}
public void setTrainingData(double[][] data) {
this.trainingData = data;
this.means = new double[this.trainingData[0].length-1];
this.scale = new double[this.trainingData[0].length-1];
for(int j = 0; j < data[0].length-1; j++) {
double min = data[0][j], max = data[0][j];
double sum = 0;
for(int i = 0; i < data.length; i++) {
if(data[i][j] < min) min = data[i][j];
if(data[i][j] > max) max = data[i][j];
sum += data[i][j];
}
scale[j] = max - min;
means[j] = sum/data.length;
}
}
public double[] getParameters() {
return parameters;
}
public void setParameters(double[] parameters) {
this.parameters = parameters;
}
public double getLearningRate() {
return learningRate;
}
public void setLearningRate(double learningRate) {
this.learningRate = learningRate;
}
/** 1 m i i 2
* J(theta) = ----- * SUM(h (x) - y )
* 2*m i=1 theta
*/
public double costFunction() {
double sum = 0;
for(int i = 0; i < trainingData.length; i++) {
double[] input = new double[trainingData[i].length - 1];
int j = 0;
for(; j < trainingData[i].length - 1; j++) {
input[j] = trainingData[i][j];
}
sum += Math.pow(predict(input) - trainingData[i][j], 2);
}
double factor = 1D/(2*trainingData.length);
return factor * sum;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("hypothesis: ");
int i = 0;
sb.append(parameters[i++] + " + ");
for(; i < parameters.length-1; i++) {
sb.append(parameters[i] + "*x" + i + " + ");
}
sb.append(parameters[i] + "*x" + i);
sb.append("\n Feature scale: ");
for(i = 0; i < scale.length-1; i++) {
sb.append(scale[i] + " ");
}
sb.append(scale[i]);
sb.append("\n Feature means: ");
for(i = 0; i < means.length-1; i++) {
sb.append(means[i] + " ");
}
sb.append(means[i]);
sb.append("\n Cost fuction: " + costFunction());
return sb.toString();
}
public static void main(String[] args) {
final double[][] TDATA = {
{200, 2, 20000},
{300, 2, 41000},
{400, 3, 51000},
{500, 3, 61500},
{800, 4, 41000},
{900, 5, 141000}
};
GradientDescent gd = new GradientDescent();
gd.setTrainingData(TDATA);
gd.setParameters(new double[]{0D,0D,0D});
gd.setLearningRate(0.00001);
gd.train();
//System.out.println(gd);
//System.out.println("PREDICTION: " + gd.predict(new double[]{300, 2}));
}
}
編輯:
我已經更新了使其更具可讀性的代碼,並試圖將其映射到道格拉斯使用的符號。我認爲現在效果更好,但仍然存在我不完全瞭解的陰暗區域。
看來,如果我有多個參數(如下面的例子中,房間數量和麪積),預測與第二個參數(在這個區域中)強烈相關,並且它沒有太多影響更改第一個參數(房間數量)。
這裏是預測{2, 200}
:
PREDICTION: 200000.00686158828
這裏是{5, 200}
預測:
PREDICTION: 200003.0068315415
正如你可以看到有勉強的兩個值之間的差額。
當我試圖將數學轉化爲代碼時,仍然存在錯誤嗎?
下面是更新後的代碼:
import java.util.*;
public class GradientDescent {
private double[][] trainingData;
private double[] means;
private double[] scale;
private double[] parameters;
private double learningRate;
GradientDescent() {
this.learningRate = 0D;
}
public double predict(double[] inp) {
return predict(inp, this.parameters);
}
private double predict(double[] inp, double[] parameters){
double[] features = concatenate(new double[]{1}, inp);
double prediction = 0;
for(int j = 0; j < features.length; j++) {
prediction += parameters[j] * features[j];
}
return prediction;
}
public void train(){
readjustLearningRate();
double costFunctionDelta = Math.abs(costFunction() - costFunction(iterateGradient()));
while(costFunctionDelta > 0.0000000001) {
System.out.println("Old cost function : " + costFunction());
System.out.println("New cost function : " + costFunction(iterateGradient()));
System.out.println("Delta: " + costFunctionDelta);
parameters = iterateGradient();
costFunctionDelta = Math.abs(costFunction() - costFunction(iterateGradient()));
readjustLearningRate();
}
}
private double[] iterateGradient() {
double[] nextParameters = new double[parameters.length];
// Calculate parameters for the next iteration
for(int r = 0; r < parameters.length; r++) {
nextParameters[r] = parameters[r] - learningRate * partialDerivative(r);
}
return nextParameters;
}
private double partialDerivative(int index) {
double sum = 0;
for(int i = 0; i < trainingData.length; i++) {
int indexOfResult = trainingData[i].length - 1;
double[] input = Arrays.copyOfRange(trainingData[i], 0, indexOfResult);
sum += ((predict(input) - trainingData[i][indexOfResult]) * trainingData[i][index]);
}
return sum/trainingData.length ;
}
private void readjustLearningRate() {
while(costFunction(iterateGradient()) > costFunction()) {
// If the cost function of the new parameters is higher that the current cost function
// it means the gradient is diverging and we have to adjust the learning rate
// and recalculate new parameters
System.out.print("Learning rate: " + learningRate + " is too big, readjusted to: ");
learningRate = learningRate/2;
System.out.println(learningRate);
}
// otherwise we are taking small enough steps, we have the right learning rate
}
public double[][] getTrainingData() {
return trainingData;
}
public void setTrainingData(double[][] data) {
this.trainingData = data;
this.means = new double[this.trainingData[0].length-1];
this.scale = new double[this.trainingData[0].length-1];
for(int j = 0; j < data[0].length-1; j++) {
double min = data[0][j], max = data[0][j];
double sum = 0;
for(int i = 0; i < data.length; i++) {
if(data[i][j] < min) min = data[i][j];
if(data[i][j] > max) max = data[i][j];
sum += data[i][j];
}
scale[j] = max - min;
means[j] = sum/data.length;
}
}
public double[] getParameters() {
return parameters;
}
public void setParameters(double[] parameters) {
this.parameters = parameters;
}
public double getLearningRate() {
return learningRate;
}
public void setLearningRate(double learningRate) {
this.learningRate = learningRate;
}
/** 1 m i i 2
* J(theta) = ----- * SUM(h (x) - y )
* 2*m i=1 theta
*/
public double costFunction() {
return costFunction(this.parameters);
}
private double costFunction(double[] parameters) {
int m = trainingData.length;
double sum = 0;
for(int i = 0; i < m; i++) {
int indexOfResult = trainingData[i].length - 1;
double[] input = Arrays.copyOfRange(trainingData[i], 0, indexOfResult);
sum += Math.pow(predict(input, parameters) - trainingData[i][indexOfResult], 2);
}
double factor = 1D/(2*m);
return factor * sum;
}
private double[] normalize(double[] input) {
double[] normalized = new double[input.length];
for(int i = 0; i < input.length; i++) {
normalized[i] = (input[i] - means[i])/scale[i];
}
return normalized;
}
private double[] concatenate(double[] a, double[] b) {
int size = a.length + b.length;
double[] concatArray = new double[size];
int index = 0;
for(double d : a) {
concatArray[index++] = d;
}
for(double d : b) {
concatArray[index++] = d;
}
return concatArray;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("hypothesis: ");
int i = 0;
sb.append(parameters[i++] + " + ");
for(; i < parameters.length-1; i++) {
sb.append(parameters[i] + "*x" + i + " + ");
}
sb.append(parameters[i] + "*x" + i);
sb.append("\n Feature scale: ");
for(i = 0; i < scale.length-1; i++) {
sb.append(scale[i] + " ");
}
sb.append(scale[i]);
sb.append("\n Feature means: ");
for(i = 0; i < means.length-1; i++) {
sb.append(means[i] + " ");
}
sb.append(means[i]);
sb.append("\n Cost fuction: " + costFunction());
return sb.toString();
}
public static void main(String[] args) {
final double[][] TDATA = {
//number of rooms, area, price
{2, 200, 200000},
{3, 300, 300000},
{4, 400, 400000},
{5, 500, 500000},
{8, 800, 800000},
{9, 900, 900000}
};
GradientDescent gd = new GradientDescent();
gd.setTrainingData(TDATA);
gd.setParameters(new double[]{0D, 0D, 0D});
gd.setLearningRate(0.1);
gd.train();
System.out.println(gd);
System.out.println("PREDICTION: " + gd.predict(new double[]{3, 600}));
}
}
你有收斂功能嗎?如果是這樣,我很樂意看到你用它做了什麼。事實上,它可能對你(專業)和其他人有用,並將它放在GitHub上進行一些示例培訓和結果。我很樂意提供產生我的符號的LaTex。 –
哪部分你不完全理解? – FauChristian
您可能希望簡單地研究一個工作實現:https://github.com/Globegitter/gradient-descent –