最小二乘法–高斯牛頓迭代法
上一篇文章詳解了最小二乘法的線性擬合。本文將詳解最小二乘法的非線性擬合,高斯牛頓迭代法。
1.原理
高斯—牛頓迭代法的基本思想是使用泰勒級數展開式去近似地代替非線性迴歸模型,然後通過多次迭代,多次修正迴歸係數,使迴歸係數不斷逼近非線性迴歸模型的最佳迴歸係數,最後使原模型的殘差平方和達到最小。
①已知m個點:
②函數原型:
其中:(m>=n)
③目的是找到最優解β,使得殘差平方和最小:
殘差:
④要求最小值,即S的對β偏導數等於0:
⑤在非線性系統中,是變量和參數的函數,沒有close解。因此,我們給定一個初始值,用迭代法逼近解:
其中k是迭代次數,是迭代矢量。
⑥而每次迭代函數是線性的,在處用泰勒級數展開:
其中:J是已知的矩陣,爲了方便迭代,令。
⑦此時殘差表示爲:
⑧帶入公式④有:
化解得:
⑨寫成矩陣形式:
⑩所以最終迭代公式爲:
其中,Jf是函數f=(x,β)對β的雅可比矩陣。
2.代碼
用java代碼實現,解維基百科的例子:
https://en.wikipedia.org/wiki/Gauss%E2%80%93Newton_algorithm
①已知數據:
②函數模型:
③殘差公式:
④對β求偏導數:
⑤代碼如下:
public class GaussNewton {
double[] xData = new double[]{0.038, 0.194, 0.425, 0.626, 1.253, 2.500, 3.740};
double[] yData = new double[]{0.050, 0.127, 0.094, 0.2122, 0.2729, 0.2665, 0.3317};
double[][] bMatrix = new double[2][1];//係數β矩陣
int m = xData.length;
int n = bMatrix.length;
int iterations = 7;//迭代次數
//迭代公式求解,即1中公式⑩
private void magic(){
//β1,β2迭代初值
bMatrix[0][0] = 0.9;
bMatrix[1][0] = 0.2;
//求J矩陣
for(int k = 0; k < iterations; k++) {
double[][] J = new double[m][n];
double[][] JT = new double[n][m];
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++) {
J[i][j] = secondDerivative(xData[i], bMatrix[0][0], bMatrix[1][0], j);
}
}
JT = MatrixMath.invert(J);//求轉置矩陣JT
double[][] invertedPart = MatrixMath.mult(JT, J);//矩陣JT與J相乘
//矩陣invertedPart行列式的值:|JT*J|
double result = MatrixMath.mathDeterminantCalculation(invertedPart);
//求矩陣invertedPart的逆矩陣:(JT*J)^-1
double[][] reversedPart = MatrixMath.getInverseMatrix(invertedPart, result);
//求方差r(β)矩陣: ri = yi - f(xi, b)
double[][] residuals = new double[m][1];
for(int i = 0; i < m; i++) {
residuals[i][0] = yData[i] - (bMatrix[0][0] * xData[i]) / (bMatrix[1][0] + xData[i]);
}
//求矩陣積reversedPart*JT*residuals: (JT*J)^-1*JT*r
double[][] product = MatrixMath.mult(MatrixMath.mult(reversedPart, JT), residuals);
//迭代公式, 即公式⑩
double[][] newB = MatrixMath.plus(bMatrix, product);
bMatrix = newB;
}
//顯示係數值
System.out.println("b1: " + bMatrix[0][0] + "\nb2: " + bMatrix[1][0]);
}
//2中公式④
private static double secondDerivative(double x, double b1, double b2, int index){
switch(index) {
case 0: return x / (b2 + x);//對係數bi求導
case 1: return -1 * (b1 * x) / Math.pow((b2+x), 2);//對係數b2求導
}
return 0;
}
public static void main(String[] args) {
GaussNewton app = new GaussNewton();
app.magic();
}
}
運行,輸出得到:
b1: 0.3618366954234483
b2: 0.5562654497238557
⑥其中用到的矩陣運算代碼如下:
public class MatrixMath {
/**
* 矩陣基本運算:加、減、乘、除、轉置
*/
public final static int OPERATION_ADD = 1;
public final static int OPERATION_SUB = 2;
public final static int OPERATION_MUL = 4;
/**
* 矩陣加法
* @param a 加數
* @param b 被加數
* @return 和
*/
public static double[][] plus(double[][] a, double[][] b){
if(legalOperation(a, b, OPERATION_ADD)) {
for(int i=0; i<a.length; i++) {
for(int j=0; j<a[0].length; j++) {
a[i][j] = a[i][j] + b[i][j];
}
}
}
return a;
}
/**
* 矩陣減法
* @param a 減數
* @param b 被減數
* @return 差
*/
public static double[][] substract(double[][] a, double[][] b){
if(legalOperation(a, b, OPERATION_SUB)) {
for(int i=0; i<a.length; i++) {
for(int j=0; j<a[0].length; j++) {
a[i][j] = a[i][j] - b[i][j];
}
}
}
return a;
}
/**
* 判斷矩陣行列是否符合運算
* @param a 進行運算的矩陣
* @param b 進行運算的矩陣
* @param type 運算類型
* @return 符合/不符合運算
*/
private static boolean legalOperation(double[][] a, double[][] b, int type) {
boolean legal = true;
if(type == OPERATION_ADD || type == OPERATION_SUB)
{
if(a.length != b.length || a[0].length != b[0].length) {
legal = false;
}
}
else if(type == OPERATION_MUL)
{
if(a[0].length != b.length) {
legal = false;
}
}
return legal;
}
/**
* 矩陣乘法
* @param a 乘數
* @param b 被乘數
* @return 積
*/
public static double[][] mult(double[][] a, double[][] b){
if(legalOperation(a, b, OPERATION_MUL)) {
double[][] result = new double[a.length][b[0].length];
for(int i=0; i< a.length; i++) {
for(int j=0; j< b[0].length; j++) {
result[i][j] = calculateSingleResult(a, b, i, j);
}
}
return result;
}
else
{
return null;
}
}
/**
* 矩陣乘法
* @param a 乘數
* @param b 被乘數
* @return 積
*/
public static double[][] mult(double[][] a, int b) {
for(int i=0; i<a.length; i++) {
for(int j=0; j<a[0].length; j++) {
a[i][j] = a[i][j] * b;
}
}
return a;
}
/**
* 乘數矩陣的行元素與被乘數矩陣列元素積的和
* @param a 乘數矩陣
* @param b 被乘數矩陣
* @param row 行
* @param column 列
* @return 值
*/
private static double calculateSingleResult(double[][] a, double[][] b, int row, int column) {
double result = 0.0;
for(int k = 0; k< a[0].length; k++) {
result += a[row][k] * b[k][column];
}
return result;
}
/**
* 矩陣的轉置
* @param a 要轉置的矩陣
* @return 轉置後的矩陣
*/
public static double[][] invert(double[][] a){
double[][] result = new double[a[0].length][a.length];
for(int i=0;i<a.length;i++){
for(int j=0;j<a[0].length;j++){
result[j][i]=a[i][j];
}
}
return result;
}
/**
* 求可逆矩陣(使用代數餘子式的形式)
*/
/**
* 求傳入的矩陣的逆矩陣
* @param value 需要轉換的矩陣
* @return 逆矩陣
*/
public static double[][] getInverseMatrix(double[][] value,double result){
double[][] transferMatrix = new double[value.length][value[0].length];
//計算代數餘子式,並賦值給|A|
for (int i = 0; i < value.length; i++) {
for (int j = 0; j < value[i].length; j++) {
transferMatrix[j][i] = mathDeterminantCalculation(getNewMatrix(i, j, value));
if ((i+j)%2!=0) {
transferMatrix[j][i] = -transferMatrix[j][i];
}
transferMatrix[j][i] /= result;
}
}
return transferMatrix;
}
/***
* 求行列式的值
* @param value 需要算的行列式
* @return 計算的結果
*/
public static double mathDeterminantCalculation(double[][] value){
if (value.length == 1) {
//當行列式爲1階的時候就直接返回本身
return value[0][0];
}
if (value.length == 2) {
//如果行列式爲二階的時候直接進行計算
return value[0][0]*value[1][1]-value[0][1]*value[1][0];
}
//當行列式的階數大於2時
double result = 1;
for (int i = 0; i < value.length; i++) {
//檢查數組對角線位置的數值是否是0,如果是零則對該數組進行調換,查找到一行不爲0的進行調換
if (value[i][i] == 0) {
value = changeDeterminantNoZero(value,i,i);
result*=-1;
}
for (int j = 0; j <i; j++) {
//讓開始處理的行的首位爲0處理爲三角形式
//如果要處理的列爲0則和自己調換一下位置,這樣就省去了計算
if (value[i][j] == 0) {
continue;
}
//如果要是要處理的行是0則和上面的一行進行調換
if (value[j][j]==0) {
double[] temp = value[i];
value[i] = value[i-1];
value[i-1] = temp;
result*=-1;
continue;
}
double ratio = -(value[i][j]/value[j][j]);
value[i] = addValue(value[i],value[j],ratio);
}
}
return mathValue(value,result);
}
/**
* 計算行列式的結果
* @param value
* @return
*/
private static double mathValue(double[][] value,double result){
for (int i = 0; i < value.length; i++) {
//如果對角線上有一個值爲0則全部爲0,直接返回結果
if (value[i][i]==0) {
return 0;
}
result *= value[i][i];
}
return result;
}
/***
* 將i行之前的每一行乘以一個係數,使得從i行的第i列之前的數字置換爲0
* @param currentRow 當前要處理的行
* @param frontRow i行之前的遍歷的行
* @param ratio 要乘以的係數
* @return 將i行i列之前數字置換爲0後的新的行
*/
private static double[] addValue(double[] currentRow,double[] frontRow, double ratio){
for (int i = 0; i < currentRow.length; i++) {
currentRow[i] += frontRow[i]*ratio;
}
return currentRow;
}
/**
* 指定列的位置是否爲0,查找第一個不爲0的位置的行進行位置調換,如果沒有則返回原來的值
* @param determinant 需要處理的行列式
* @param line 要調換的行
* @param row 要判斷的列
*/
private static double[][] changeDeterminantNoZero(double[][] determinant,int line,int column){
for (int i = line; i < determinant.length; i++) {
//進行行調換
if (determinant[i][column] != 0) {
double[] temp = determinant[line];
determinant[line] = determinant[i];
determinant[i] = temp;
return determinant;
}
}
return determinant;
}
/**
* 轉換爲代數餘子式
* @param row 行
* @param line 列
* @param matrix 要轉換的矩陣
* @return 轉換的代數餘子式
*/
private static double[][] getNewMatrix(int row,int line,double[][] matrix){
double[][] newMatrix = new double[matrix.length-1][matrix[0].length-1];
int rowNum = 0,lineNum = 0;
for (int i = 0; i < matrix.length; i++) {
if (i == row){
continue;
}
for (int j = 0; j < matrix[i].length; j++) {
if (j == line) {
continue;
}
newMatrix[rowNum][lineNum++%(matrix[0].length-1)] = matrix[i][j];
}
rowNum++;
}
return newMatrix;
}
public static void main(String[] args) {
//double[][] test = {{0,0,0,1,2},{0,0,0,2,3},{1,1,0,0,0},{0,1,1,0,0},{0,0,1,0,0}};
double[][] test = {
{3.8067488033632655, -2.894113667134647},
{-2.894113667134647, 3.6978894069779504}
};
double result;
try {
double[][] temp = new double[test.length][test[0].length];
for (int i = 0; i < test.length; i++) {
for (int j = 0; j < test[i].length; j++) {
temp[i][j] = test[i][j];
}
}
//先計算矩陣的行列式的值是否等於0,如果不等於0則該矩陣是可逆的
result = mathDeterminantCalculation(temp);
if (result == 0) {
System.out.println("矩陣不可逆");
}else {
System.out.println("矩陣可逆");
//求出逆矩陣
double[][] result11 = getInverseMatrix(test,result);
//打印逆矩陣
for (int i = 0; i < result11.length; i++) {
for (int j = 0; j < result11[i].length; j++) {
System.out.print(result11[i][j]+" ");
}
System.out.println();
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("不是正確的行列式!!");
}
}
}