186 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			186 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
|   | <?php | ||
|  | 
 | ||
|  | // Levenberg-Marquardt in PHP
 | ||
|  | 
 | ||
|  | // http://www.idiom.com/~zilla/Computer/Javanumeric/LM.java
 | ||
|  | 
 | ||
|  | class LevenbergMarquardt { | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * Calculate the current sum-squared-error | ||
|  | 	 * | ||
|  | 	 * Chi-squared is the distribution of squared Gaussian errors, | ||
|  | 	 * thus the name. | ||
|  | 	 * | ||
|  | 	 * @param double[][] $x | ||
|  | 	 * @param double[] $a | ||
|  | 	 * @param double[] $y, | ||
|  | 	 * @param double[] $s, | ||
|  | 	 * @param object $f | ||
|  | 	 */ | ||
|  | 	function chiSquared($x, $a, $y, $s, $f) { | ||
|  | 		$npts = count($y); | ||
|  | 		$sum = 0.0; | ||
|  | 
 | ||
|  | 		for ($i = 0; $i < $npts; ++$i) { | ||
|  | 			$d = $y[$i] - $f->val($x[$i], $a); | ||
|  | 			$d = $d / $s[$i]; | ||
|  | 			$sum = $sum + ($d*$d); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return $sum; | ||
|  | 	}	//	function chiSquared()
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * Minimize E = sum {(y[k] - f(x[k],a)) / s[k]}^2 | ||
|  | 	 * The individual errors are optionally scaled by s[k]. | ||
|  | 	 * Note that LMfunc implements the value and gradient of f(x,a), | ||
|  | 	 * NOT the value and gradient of E with respect to a! | ||
|  | 	 * | ||
|  | 	 * @param x array of domain points, each may be multidimensional | ||
|  | 	 * @param y corresponding array of values | ||
|  | 	 * @param a the parameters/state of the model | ||
|  | 	 * @param vary false to indicate the corresponding a[k] is to be held fixed | ||
|  | 	 * @param s2 sigma^2 for point i | ||
|  | 	 * @param lambda blend between steepest descent (lambda high) and | ||
|  | 	 *	jump to bottom of quadratic (lambda zero). | ||
|  | 	 * 	Start with 0.001. | ||
|  | 	 * @param termepsilon termination accuracy (0.01) | ||
|  | 	 * @param maxiter	stop and return after this many iterations if not done | ||
|  | 	 * @param verbose	set to zero (no prints), 1, 2 | ||
|  | 	 * | ||
|  | 	 * @return the new lambda for future iterations. | ||
|  | 	 *  Can use this and maxiter to interleave the LM descent with some other | ||
|  | 	 *  task, setting maxiter to something small. | ||
|  | 	 */ | ||
|  | 	function solve($x, $a, $y, $s, $vary, $f, $lambda, $termepsilon, $maxiter, $verbose) { | ||
|  | 		$npts = count($y); | ||
|  | 		$nparm = count($a); | ||
|  | 
 | ||
|  | 		if ($verbose > 0) { | ||
|  | 			print("solve x[".count($x)."][".count($x[0])."]"); | ||
|  | 			print(" a[".count($a)."]"); | ||
|  | 			println(" y[".count(length)."]"); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		$e0 = $this->chiSquared($x, $a, $y, $s, $f); | ||
|  | 
 | ||
|  | 		//double lambda = 0.001;
 | ||
|  | 		$done = false; | ||
|  | 
 | ||
|  | 		// g = gradient, H = hessian, d = step to minimum
 | ||
|  | 		// H d = -g, solve for d
 | ||
|  | 		$H = array(); | ||
|  | 		$g = array(); | ||
|  | 
 | ||
|  | 		//double[] d = new double[nparm];
 | ||
|  | 
 | ||
|  | 		$oos2 = array(); | ||
|  | 
 | ||
|  | 		for($i = 0; $i < $npts; ++$i) { | ||
|  | 			$oos2[$i] = 1./($s[$i]*$s[$i]); | ||
|  | 		} | ||
|  | 		$iter = 0; | ||
|  | 		$term = 0;	// termination count test
 | ||
|  | 
 | ||
|  | 		do { | ||
|  | 			++$iter; | ||
|  | 
 | ||
|  | 			// hessian approximation
 | ||
|  | 			for( $r = 0; $r < $nparm; ++$r) { | ||
|  | 				for( $c = 0; $c < $nparm; ++$c) { | ||
|  | 					for( $i = 0; $i < $npts; ++$i) { | ||
|  | 						if ($i == 0) $H[$r][$c] = 0.; | ||
|  | 						$xi = $x[$i]; | ||
|  | 						$H[$r][$c] += ($oos2[$i] * $f->grad($xi, $a, $r) * $f->grad($xi, $a, $c)); | ||
|  | 					}  //npts
 | ||
|  | 				} //c
 | ||
|  | 			} //r
 | ||
|  | 
 | ||
|  | 			// boost diagonal towards gradient descent
 | ||
|  | 			for( $r = 0; $r < $nparm; ++$r) | ||
|  | 				$H[$r][$r] *= (1. + $lambda); | ||
|  | 
 | ||
|  | 			// gradient
 | ||
|  | 			for( $r = 0; $r < $nparm; ++$r) { | ||
|  | 				for( $i = 0; $i < $npts; ++$i) { | ||
|  | 					if ($i == 0) $g[$r] = 0.; | ||
|  | 					$xi = $x[$i]; | ||
|  | 					$g[$r] += ($oos2[$i] * ($y[$i]-$f->val($xi,$a)) * $f->grad($xi, $a, $r)); | ||
|  | 				} | ||
|  | 			} //npts
 | ||
|  | 
 | ||
|  | 			// scale (for consistency with NR, not necessary)
 | ||
|  | 			if ($false) { | ||
|  | 				for( $r = 0; $r < $nparm; ++$r) { | ||
|  | 					$g[$r] = -0.5 * $g[$r]; | ||
|  | 					for( $c = 0; $c < $nparm; ++$c) { | ||
|  | 						$H[$r][$c] *= 0.5; | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// solve H d = -g, evaluate error at new location
 | ||
|  | 			//double[] d = DoubleMatrix.solve(H, g);
 | ||
|  | //			double[] d = (new Matrix(H)).lu().solve(new Matrix(g, nparm)).getRowPackedCopy();
 | ||
|  | 			//double[] na = DoubleVector.add(a, d);
 | ||
|  | //			double[] na = (new Matrix(a, nparm)).plus(new Matrix(d, nparm)).getRowPackedCopy();
 | ||
|  | //			double e1 = chiSquared(x, na, y, s, f);
 | ||
|  | 
 | ||
|  | //			if (verbose > 0) {
 | ||
|  | //				System.out.println("\n\niteration "+iter+" lambda = "+lambda);
 | ||
|  | //				System.out.print("a = ");
 | ||
|  | //				(new Matrix(a, nparm)).print(10, 2);
 | ||
|  | //				if (verbose > 1) {
 | ||
|  | //					System.out.print("H = ");
 | ||
|  | //					(new Matrix(H)).print(10, 2);
 | ||
|  | //					System.out.print("g = ");
 | ||
|  | //					(new Matrix(g, nparm)).print(10, 2);
 | ||
|  | //					System.out.print("d = ");
 | ||
|  | //					(new Matrix(d, nparm)).print(10, 2);
 | ||
|  | //				}
 | ||
|  | //				System.out.print("e0 = " + e0 + ": ");
 | ||
|  | //				System.out.print("moved from ");
 | ||
|  | //				(new Matrix(a, nparm)).print(10, 2);
 | ||
|  | //				System.out.print("e1 = " + e1 + ": ");
 | ||
|  | //				if (e1 < e0) {
 | ||
|  | //					System.out.print("to ");
 | ||
|  | //					(new Matrix(na, nparm)).print(10, 2);
 | ||
|  | //				} else {
 | ||
|  | //					System.out.println("move rejected");
 | ||
|  | //				}
 | ||
|  | //			}
 | ||
|  | 
 | ||
|  | 			// termination test (slightly different than NR)
 | ||
|  | //			if (Math.abs(e1-e0) > termepsilon) {
 | ||
|  | //				term = 0;
 | ||
|  | //			} else {
 | ||
|  | //				term++;
 | ||
|  | //				if (term == 4) {
 | ||
|  | //					System.out.println("terminating after " + iter + " iterations");
 | ||
|  | //					done = true;
 | ||
|  | //				}
 | ||
|  | //			}
 | ||
|  | //			if (iter >= maxiter) done = true;
 | ||
|  | 
 | ||
|  | 			// in the C++ version, found that changing this to e1 >= e0
 | ||
|  | 			// was not a good idea.  See comment there.
 | ||
|  | 			//
 | ||
|  | //			if (e1 > e0 || Double.isNaN(e1)) { // new location worse than before
 | ||
|  | //				lambda *= 10.;
 | ||
|  | //			} else {		// new location better, accept new parameters
 | ||
|  | //				lambda *= 0.1;
 | ||
|  | //				e0 = e1;
 | ||
|  | //				// simply assigning a = na will not get results copied back to caller
 | ||
|  | //				for( int i = 0; i < nparm; i++ ) {
 | ||
|  | //					if (vary[i]) a[i] = na[i];
 | ||
|  | //				}
 | ||
|  | //			}
 | ||
|  | 		} while(!$done); | ||
|  | 
 | ||
|  | 		return $lambda; | ||
|  | 	}	//	function solve()
 | ||
|  | 
 | ||
|  | }	//	class LevenbergMarquardt
 |