mirror of
https://github.com/Llewellynvdm/php-ml.git
synced 2024-11-21 20:45: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;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
|
||||
abstract class Optimizer
|
||||
{
|
||||
public $initialTheta;
|
||||
|
||||
/**
|
||||
* Unknown variables to be found
|
||||
*
|
||||
@ -33,21 +35,16 @@ abstract class Optimizer
|
||||
// Inits the weights randomly
|
||||
$this->theta = [];
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Phpml\Helper\Optimizer;
|
||||
|
||||
use Closure;
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Stochastic Gradient Descent optimization method
|
||||
@ -88,6 +89,17 @@ class StochasticGD extends Optimizer
|
||||
$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
|
||||
* between iterations to continue the iterations.<br>
|
||||
|
@ -225,25 +225,14 @@ class LUDecomposition
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count determinants
|
||||
*
|
||||
* @return float|int d matrix determinant
|
||||
*
|
||||
* @throws MatrixException
|
||||
*/
|
||||
public function det()
|
||||
public function det(): float
|
||||
{
|
||||
if ($this->m !== $this->n) {
|
||||
throw MatrixException::notSquareMatrix();
|
||||
}
|
||||
|
||||
$d = $this->pivsign;
|
||||
for ($j = 0; $j < $this->n; ++$j) {
|
||||
$d *= $this->LU[$j][$j];
|
||||
}
|
||||
|
||||
return $d;
|
||||
return (float) $d;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Phpml\Tests\Clustering;
|
||||
|
||||
use Phpml\Clustering\FuzzyCMeans;
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FuzzyCMeansTest extends TestCase
|
||||
@ -45,4 +46,19 @@ class FuzzyCMeansTest extends TestCase
|
||||
$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;
|
||||
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
use Phpml\Helper\Optimizer\ConjugateGradient;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@ -35,6 +36,34 @@ class ConjugateGradientTest extends TestCase
|
||||
$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
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
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