mirror of
https://github.com/Llewellynvdm/php-ml.git
synced 2024-11-22 04:55:10 +00:00
Fix Optimizer initial theta randomization (#239)
* Fix Optimizer initial theta randomization * Add more tests for LUDecomposition and FuzzyCMeans
This commit is contained in:
parent
83f3e8de70
commit
a96f03e8dd
@ -5,10 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace Phpml\Helper\Optimizer;
|
namespace Phpml\Helper\Optimizer;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Exception;
|
use Phpml\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
abstract class Optimizer
|
abstract class Optimizer
|
||||||
{
|
{
|
||||||
|
public $initialTheta;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unknown variables to be found
|
* Unknown variables to be found
|
||||||
*
|
*
|
||||||
@ -33,21 +35,16 @@ abstract class Optimizer
|
|||||||
// Inits the weights randomly
|
// Inits the weights randomly
|
||||||
$this->theta = [];
|
$this->theta = [];
|
||||||
for ($i = 0; $i < $this->dimensions; ++$i) {
|
for ($i = 0; $i < $this->dimensions; ++$i) {
|
||||||
$this->theta[] = random_int(0, getrandmax()) / (float) getrandmax();
|
$this->theta[] = (random_int(0, PHP_INT_MAX) / PHP_INT_MAX) + 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->initialTheta = $this->theta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the weights manually
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public function setInitialTheta(array $theta)
|
public function setInitialTheta(array $theta)
|
||||||
{
|
{
|
||||||
if (count($theta) != $this->dimensions) {
|
if (count($theta) != $this->dimensions) {
|
||||||
throw new Exception("Number of values in the weights array should be ${this}->dimensions");
|
throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->theta = $theta;
|
$this->theta = $theta;
|
||||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Phpml\Helper\Optimizer;
|
namespace Phpml\Helper\Optimizer;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Phpml\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stochastic Gradient Descent optimization method
|
* Stochastic Gradient Descent optimization method
|
||||||
@ -88,6 +89,17 @@ class StochasticGD extends Optimizer
|
|||||||
$this->dimensions = $dimensions;
|
$this->dimensions = $dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setInitialTheta(array $theta)
|
||||||
|
{
|
||||||
|
if (count($theta) != $this->dimensions + 1) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Number of values in the weights array should be %s', $this->dimensions + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->theta = $theta;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets minimum value for the change in the theta values
|
* Sets minimum value for the change in the theta values
|
||||||
* between iterations to continue the iterations.<br>
|
* between iterations to continue the iterations.<br>
|
||||||
|
@ -225,25 +225,14 @@ class LUDecomposition
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function det(): float
|
||||||
* Count determinants
|
|
||||||
*
|
|
||||||
* @return float|int d matrix determinant
|
|
||||||
*
|
|
||||||
* @throws MatrixException
|
|
||||||
*/
|
|
||||||
public function det()
|
|
||||||
{
|
{
|
||||||
if ($this->m !== $this->n) {
|
|
||||||
throw MatrixException::notSquareMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
$d = $this->pivsign;
|
$d = $this->pivsign;
|
||||||
for ($j = 0; $j < $this->n; ++$j) {
|
for ($j = 0; $j < $this->n; ++$j) {
|
||||||
$d *= $this->LU[$j][$j];
|
$d *= $this->LU[$j][$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $d;
|
return (float) $d;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Phpml\Tests\Clustering;
|
namespace Phpml\Tests\Clustering;
|
||||||
|
|
||||||
use Phpml\Clustering\FuzzyCMeans;
|
use Phpml\Clustering\FuzzyCMeans;
|
||||||
|
use Phpml\Exception\InvalidArgumentException;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class FuzzyCMeansTest extends TestCase
|
class FuzzyCMeansTest extends TestCase
|
||||||
@ -45,4 +46,19 @@ class FuzzyCMeansTest extends TestCase
|
|||||||
$this->assertEquals(1, array_sum($col));
|
$this->assertEquals(1, array_sum($col));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider invalidClusterNumberProvider
|
||||||
|
*/
|
||||||
|
public function testInvalidClusterNumber(int $clusters): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
new FuzzyCMeans($clusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invalidClusterNumberProvider(): array
|
||||||
|
{
|
||||||
|
return [[0], [-1]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Tests\Helper\Optimizer;
|
namespace Phpml\Tests\Helper\Optimizer;
|
||||||
|
|
||||||
|
use Phpml\Exception\InvalidArgumentException;
|
||||||
use Phpml\Helper\Optimizer\ConjugateGradient;
|
use Phpml\Helper\Optimizer\ConjugateGradient;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
@ -35,6 +36,34 @@ class ConjugateGradientTest extends TestCase
|
|||||||
$this->assertEquals([-1, 2], $theta, '', 0.1);
|
$this->assertEquals([-1, 2], $theta, '', 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRunOptimizationWithCustomInitialTheta(): void
|
||||||
|
{
|
||||||
|
// 200 samples from y = -1 + 2x (i.e. theta = [-1, 2])
|
||||||
|
$samples = [];
|
||||||
|
$targets = [];
|
||||||
|
for ($i = -100; $i <= 100; ++$i) {
|
||||||
|
$x = $i / 100;
|
||||||
|
$samples[] = [$x];
|
||||||
|
$targets[] = -1 + 2 * $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
$callback = function ($theta, $sample, $target) {
|
||||||
|
$y = $theta[0] + $theta[1] * $sample[0];
|
||||||
|
$cost = ($y - $target) ** 2 / 2;
|
||||||
|
$grad = $y - $target;
|
||||||
|
|
||||||
|
return [$cost, $grad];
|
||||||
|
};
|
||||||
|
|
||||||
|
$optimizer = new ConjugateGradient(1);
|
||||||
|
// set very weak theta to trigger very bad result
|
||||||
|
$optimizer->setInitialTheta([0.0000001, 0.0000001]);
|
||||||
|
|
||||||
|
$theta = $optimizer->runOptimization($samples, $targets, $callback);
|
||||||
|
|
||||||
|
$this->assertEquals([-1.087708, 2.212034], $theta, '', 0.000001);
|
||||||
|
}
|
||||||
|
|
||||||
public function testRunOptimization2Dim(): void
|
public function testRunOptimization2Dim(): void
|
||||||
{
|
{
|
||||||
// 100 samples from y = -1 + 2x0 - 3x1 (i.e. theta = [-1, 2, -3])
|
// 100 samples from y = -1 + 2x0 - 3x1 (i.e. theta = [-1, 2, -3])
|
||||||
@ -62,4 +91,12 @@ class ConjugateGradientTest extends TestCase
|
|||||||
|
|
||||||
$this->assertEquals([-1, 2, -3], $theta, '', 0.1);
|
$this->assertEquals([-1, 2, -3], $theta, '', 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testThrowExceptionOnInvalidTheta(): void
|
||||||
|
{
|
||||||
|
$opimizer = new ConjugateGradient(2);
|
||||||
|
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$opimizer->setInitialTheta([0.15]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
31
tests/Math/LinearAlgebra/LUDecompositionTest.php
Normal file
31
tests/Math/LinearAlgebra/LUDecompositionTest.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Phpml\Tests\Math\LinearAlgebra;
|
||||||
|
|
||||||
|
use Phpml\Exception\MatrixException;
|
||||||
|
use Phpml\Math\LinearAlgebra\LUDecomposition;
|
||||||
|
use Phpml\Math\Matrix;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LUDecomposition is used and tested in Matrix::inverse method so not all tests are required
|
||||||
|
*/
|
||||||
|
final class LUDecompositionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testNotSquareMatrix(): void
|
||||||
|
{
|
||||||
|
$this->expectException(MatrixException::class);
|
||||||
|
|
||||||
|
new LUDecomposition(new Matrix([1, 2, 3, 4, 5]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSolveWithInvalidMatrix(): void
|
||||||
|
{
|
||||||
|
$this->expectException(MatrixException::class);
|
||||||
|
|
||||||
|
$lu = new LUDecomposition(new Matrix([[1, 2], [3, 4]]));
|
||||||
|
$lu->solve(new Matrix([1, 2, 3]));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user