Throw proper exception (#259)

* Throw proper exception

* Fix coding style
This commit is contained in:
Yuji Uchiyama 2018-03-07 07:26:36 +09:00 committed by Arkadiusz Kondas
parent a40c50b48b
commit 66ca874062
18 changed files with 242 additions and 54 deletions

View File

@ -4,10 +4,10 @@ declare(strict_types=1);
namespace Phpml\Classification\Ensemble;
use Exception;
use Phpml\Classification\Classifier;
use Phpml\Classification\Linear\DecisionStump;
use Phpml\Classification\WeightedClassifier;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Helper\Predictable;
use Phpml\Helper\Trainable;
use Phpml\Math\Statistic\Mean;
@ -93,14 +93,14 @@ class AdaBoost implements Classifier
}
/**
* @throws \Exception
* @throws InvalidArgumentException
*/
public function train(array $samples, array $targets): void
{
// Initialize usual variables
$this->labels = array_keys(array_count_values($targets));
if (count($this->labels) != 2) {
throw new Exception('AdaBoost is a binary classifier and can classify between two classes only');
throw new InvalidArgumentException('AdaBoost is a binary classifier and can classify between two classes only');
}
// Set all target values to either -1 or 1

View File

@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Phpml\Classification\Ensemble;
use Exception;
use Phpml\Classification\Classifier;
use Phpml\Classification\DecisionTree;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Helper\Predictable;
use Phpml\Helper\Trainable;
use ReflectionClass;
@ -77,12 +77,12 @@ class Bagging implements Classifier
*
* @return $this
*
* @throws \Exception
* @throws InvalidArgumentException
*/
public function setSubsetRatio(float $ratio)
{
if ($ratio < 0.1 || $ratio > 1.0) {
throw new Exception('Subset ratio should be between 0.1 and 1.0');
throw new InvalidArgumentException('Subset ratio should be between 0.1 and 1.0');
}
$this->subsetRatio = $ratio;

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Phpml\Classification\Linear;
use Exception;
use Phpml\Exception\InvalidArgumentException;
class Adaline extends Perceptron
{
@ -34,7 +34,7 @@ class Adaline extends Perceptron
* If normalizeInputs is set to true, then every input given to the algorithm will be standardized
* by use of standard deviation and mean calculation
*
* @throws \Exception
* @throws InvalidArgumentException
*/
public function __construct(
float $learningRate = 0.001,
@ -43,7 +43,7 @@ class Adaline extends Perceptron
int $trainingType = self::BATCH_TRAINING
) {
if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING], true)) {
throw new Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm');
throw new InvalidArgumentException('Adaline can only be trained with batch and online/stochastic gradient descent algorithm');
}
$this->trainingType = $trainingType;

View File

@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Phpml\Classification\Linear;
use Exception;
use Phpml\Classification\DecisionTree;
use Phpml\Classification\WeightedClassifier;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Helper\OneVsRest;
use Phpml\Helper\Predictable;
use Phpml\Math\Comparison;
@ -104,7 +104,7 @@ class DecisionStump extends WeightedClassifier
}
/**
* @throws \Exception
* @throws InvalidArgumentException
*/
protected function trainBinary(array $samples, array $targets, array $labels): void
{
@ -121,7 +121,7 @@ class DecisionStump extends WeightedClassifier
if (!empty($this->weights)) {
$numWeights = count($this->weights);
if ($numWeights != count($samples)) {
throw new Exception('Number of sample weights does not match with number of samples');
throw new InvalidArgumentException('Number of sample weights does not match with number of samples');
}
} else {
$this->weights = array_fill(0, count($samples), 1);

View File

@ -6,6 +6,7 @@ namespace Phpml\Classification\Linear;
use Closure;
use Exception;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Helper\Optimizer\ConjugateGradient;
class LogisticRegression extends Adaline
@ -61,7 +62,7 @@ class LogisticRegression extends Adaline
*
* Penalty (Regularization term) can be 'L2' or empty string to cancel penalty term
*
* @throws \Exception
* @throws InvalidArgumentException
*/
public function __construct(
int $maxIterations = 500,
@ -72,18 +73,24 @@ class LogisticRegression extends Adaline
) {
$trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING);
if (!in_array($trainingType, $trainingTypes, true)) {
throw new Exception('Logistic regression can only be trained with '.
throw new InvalidArgumentException(
'Logistic regression can only be trained with '.
'batch (gradient descent), online (stochastic gradient descent) '.
'or conjugate batch (conjugate gradients) algorithms');
'or conjugate batch (conjugate gradients) algorithms'
);
}
if (!in_array($cost, ['log', 'sse'], true)) {
throw new Exception("Logistic regression cost function can be one of the following: \n".
"'log' for log-likelihood and 'sse' for sum of squared errors");
throw new InvalidArgumentException(
"Logistic regression cost function can be one of the following: \n".
"'log' for log-likelihood and 'sse' for sum of squared errors"
);
}
if ($penalty != '' && strtoupper($penalty) !== 'L2') {
throw new Exception("Logistic regression supports only 'L2' regularization");
throw new InvalidArgumentException(
"Logistic regression supports only 'L2' regularization"
);
}
$this->learningRate = 0.001;
@ -140,7 +147,8 @@ class LogisticRegression extends Adaline
return;
default:
throw new Exception('Logistic regression has invalid training type: %s.', $this->trainingType);
// Not reached
throw new Exception(sprintf('Logistic regression has invalid training type: %d.', $this->trainingType));
}
}
@ -232,6 +240,7 @@ class LogisticRegression extends Adaline
return $callback;
default:
// Not reached
throw new Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction));
}
}

View File

@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Phpml\Classification\Linear;
use Closure;
use Exception;
use Phpml\Classification\Classifier;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Helper\OneVsRest;
use Phpml\Helper\Optimizer\GD;
use Phpml\Helper\Optimizer\StochasticGD;
@ -70,16 +70,16 @@ class Perceptron implements Classifier, IncrementalEstimator
* @param float $learningRate Value between 0.0(exclusive) and 1.0(inclusive)
* @param int $maxIterations Must be at least 1
*
* @throws \Exception
* @throws InvalidArgumentException
*/
public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true)
{
if ($learningRate <= 0.0 || $learningRate > 1.0) {
throw new Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)');
throw new InvalidArgumentException('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)');
}
if ($maxIterations <= 0) {
throw new Exception('Maximum number of iterations must be an integer greater than 0');
throw new InvalidArgumentException('Maximum number of iterations must be an integer greater than 0');
}
if ($normalizeInputs) {

View File

@ -6,6 +6,8 @@ namespace Phpml\DimensionReduction;
use Closure;
use Exception;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\InvalidOperationException;
use Phpml\Math\Distance\Euclidean;
use Phpml\Math\Distance\Manhattan;
use Phpml\Math\Matrix;
@ -53,13 +55,13 @@ class KernelPCA extends PCA
* @param int $numFeatures Number of columns to be returned
* @param float $gamma Gamma parameter is used with RBF and Sigmoid kernels
*
* @throws \Exception
* @throws InvalidArgumentException
*/
public function __construct(int $kernel = self::KERNEL_RBF, ?float $totalVariance = null, ?int $numFeatures = null, ?float $gamma = null)
{
$availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR];
if (!in_array($kernel, $availableKernels, true)) {
throw new Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian');
throw new InvalidArgumentException('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian');
}
parent::__construct($totalVariance, $numFeatures);
@ -97,16 +99,17 @@ class KernelPCA extends PCA
* Transforms the given sample to a lower dimensional vector by using
* the variables obtained during the last run of <code>fit</code>.
*
* @throws \Exception
* @throws InvalidArgumentException
* @throws InvalidOperationException
*/
public function transform(array $sample): array
{
if (!$this->fit) {
throw new Exception('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first');
throw new InvalidOperationException('KernelPCA has not been fitted with respect to original dataset, please run KernelPCA::fit() first');
}
if (is_array($sample[0])) {
throw new Exception('KernelPCA::transform() accepts only one-dimensional arrays');
throw new InvalidArgumentException('KernelPCA::transform() accepts only one-dimensional arrays');
}
$pairs = $this->getDistancePairs($sample);
@ -199,6 +202,7 @@ class KernelPCA extends PCA
};
default:
// Not reached
throw new Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel));
}
}

View File

@ -4,7 +4,8 @@ declare(strict_types=1);
namespace Phpml\DimensionReduction;
use Exception;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\InvalidOperationException;
use Phpml\Math\Matrix;
class LDA extends EigenTransformerBase
@ -46,20 +47,20 @@ class LDA extends EigenTransformerBase
* @param float|null $totalVariance Total explained variance to be preserved
* @param int|null $numFeatures Number of features to be preserved
*
* @throws \Exception
* @throws InvalidArgumentException
*/
public function __construct(?float $totalVariance = null, ?int $numFeatures = null)
{
if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) {
throw new Exception('Total variance can be a value between 0.1 and 0.99');
throw new InvalidArgumentException('Total variance can be a value between 0.1 and 0.99');
}
if ($numFeatures !== null && $numFeatures <= 0) {
throw new Exception('Number of features to be preserved should be greater than 0');
throw new InvalidArgumentException('Number of features to be preserved should be greater than 0');
}
if ($totalVariance !== null && $numFeatures !== null) {
throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm');
if (($totalVariance !== null) === ($numFeatures !== null)) {
throw new InvalidArgumentException('Either totalVariance or numFeatures should be specified in order to run the algorithm');
}
if ($numFeatures !== null) {
@ -94,12 +95,12 @@ class LDA extends EigenTransformerBase
* Transforms the given sample to a lower dimensional vector by using
* the eigenVectors obtained in the last run of <code>fit</code>.
*
* @throws \Exception
* @throws InvalidOperationException
*/
public function transform(array $sample): array
{
if (!$this->fit) {
throw new Exception('LDA has not been fitted with respect to original dataset, please run LDA::fit() first');
throw new InvalidOperationException('LDA has not been fitted with respect to original dataset, please run LDA::fit() first');
}
if (!is_array($sample[0])) {

View File

@ -4,7 +4,8 @@ declare(strict_types=1);
namespace Phpml\DimensionReduction;
use Exception;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\InvalidOperationException;
use Phpml\Math\Statistic\Covariance;
use Phpml\Math\Statistic\Mean;
@ -31,20 +32,20 @@ class PCA extends EigenTransformerBase
* @param float $totalVariance Total explained variance to be preserved
* @param int $numFeatures Number of features to be preserved
*
* @throws \Exception
* @throws InvalidArgumentException
*/
public function __construct(?float $totalVariance = null, ?int $numFeatures = null)
{
if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) {
throw new Exception('Total variance can be a value between 0.1 and 0.99');
throw new InvalidArgumentException('Total variance can be a value between 0.1 and 0.99');
}
if ($numFeatures !== null && $numFeatures <= 0) {
throw new Exception('Number of features to be preserved should be greater than 0');
throw new InvalidArgumentException('Number of features to be preserved should be greater than 0');
}
if ($totalVariance !== null && $numFeatures !== null) {
throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm');
if (($totalVariance !== null) === ($numFeatures !== null)) {
throw new InvalidArgumentException('Either totalVariance or numFeatures should be specified in order to run the algorithm');
}
if ($numFeatures !== null) {
@ -81,12 +82,12 @@ class PCA extends EigenTransformerBase
* Transforms the given sample to a lower dimensional vector by using
* the eigenVectors obtained in the last run of <code>fit</code>.
*
* @throws \Exception
* @throws InvalidOperationException
*/
public function transform(array $sample): array
{
if (!$this->fit) {
throw new Exception('PCA has not been fitted with respect to original dataset, please run PCA::fit() first');
throw new InvalidOperationException('PCA has not been fitted with respect to original dataset, please run PCA::fit() first');
}
if (!is_array($sample[0])) {

View File

@ -5,12 +5,32 @@ declare(strict_types=1);
namespace Phpml\Tests\Classification\Ensemble;
use Phpml\Classification\Ensemble\AdaBoost;
use Phpml\Exception\InvalidArgumentException;
use Phpml\ModelManager;
use PHPUnit\Framework\TestCase;
class AdaBoostTest extends TestCase
{
public function testPredictSingleSample()
public function testTrainThrowWhenMultiClassTargetGiven(): void
{
$samples = [
[0, 0],
[0.5, 0.5],
[1, 1],
];
$targets = [
0,
1,
2,
];
$classifier = new AdaBoost();
$this->expectException(InvalidArgumentException::class);
$classifier->train($samples, $targets);
}
public function testPredictSingleSample(): void
{
// AND problem
$samples = [[0.1, 0.3], [1, 0], [0, 1], [1, 1], [0.9, 0.8], [1.1, 1.1]];
@ -38,8 +58,6 @@ class AdaBoostTest extends TestCase
$this->assertEquals(0, $classifier->predict([0.1, 0.1]));
$this->assertEquals(1, $classifier->predict([0, 0.999]));
$this->assertEquals(0, $classifier->predict([1.1, 0.8]));
return $classifier;
}
public function testSaveAndRestore(): void

View File

@ -7,6 +7,7 @@ namespace Phpml\Tests\Classification\Ensemble;
use Phpml\Classification\DecisionTree;
use Phpml\Classification\Ensemble\Bagging;
use Phpml\Classification\NaiveBayes;
use Phpml\Exception\InvalidArgumentException;
use Phpml\ModelManager;
use PHPUnit\Framework\TestCase;
@ -34,7 +35,15 @@ class BaggingTest extends TestCase
['scorching', 0, 0, 'false', 'Dont_play'],
];
public function testPredictSingleSample()
public function testSetSubsetRatioThrowWhenRatioOutOfBounds(): void
{
$classifier = $this->getClassifier();
$this->expectException(InvalidArgumentException::class);
$classifier->setSubsetRatio(0);
}
public function testPredictSingleSample(): void
{
[$data, $targets] = $this->getData($this->data);
$classifier = $this->getClassifier();
@ -48,8 +57,6 @@ class BaggingTest extends TestCase
$classifier->train($data, $targets);
$this->assertEquals('Dont_play', $classifier->predict(['scorching', 95, 90, 'true']));
$this->assertEquals('Play', $classifier->predict(['overcast', 60, 60, 'false']));
return $classifier;
}
public function testSaveAndRestore(): void

View File

@ -5,11 +5,23 @@ declare(strict_types=1);
namespace Phpml\Tests\Classification\Linear;
use Phpml\Classification\Linear\Adaline;
use Phpml\Exception\InvalidArgumentException;
use Phpml\ModelManager;
use PHPUnit\Framework\TestCase;
class AdalineTest extends TestCase
{
public function testAdalineThrowWhenInvalidTrainingType(): void
{
$this->expectException(InvalidArgumentException::class);
$classifier = new Adaline(
0.001,
1000,
true,
0
);
}
public function testPredictSingleSample(): void
{
// AND problem

View File

@ -5,11 +5,24 @@ declare(strict_types=1);
namespace Phpml\Tests\Classification\Linear;
use Phpml\Classification\Linear\DecisionStump;
use Phpml\Exception\InvalidArgumentException;
use Phpml\ModelManager;
use PHPUnit\Framework\TestCase;
class DecisionStumpTest extends TestCase
{
public function testTrainThrowWhenSample(): void
{
$samples = [[0, 0], [1, 0], [0, 1], [1, 1]];
$targets = [0, 0, 1, 1];
$classifier = new DecisionStump();
$classifier->setSampleWeights([0.1, 0.1, 0.1]);
$this->expectException(InvalidArgumentException::class);
$classifier->train($samples, $targets);
}
public function testPredictSingleSample()
{
// Samples should be separable with a line perpendicular

View File

@ -5,16 +5,16 @@ declare(strict_types=1);
namespace Phpml\Tests\Classification\Linear;
use Phpml\Classification\Linear\LogisticRegression;
use Phpml\Exception\InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;
use ReflectionProperty;
use Throwable;
class LogisticRegressionTest extends TestCase
{
public function testConstructorThrowWhenInvalidTrainingType(): void
{
$this->expectException(Throwable::class);
$this->expectException(InvalidArgumentException::class);
$classifier = new LogisticRegression(
500,
@ -27,7 +27,7 @@ class LogisticRegressionTest extends TestCase
public function testConstructorThrowWhenInvalidCost(): void
{
$this->expectException(Throwable::class);
$this->expectException(InvalidArgumentException::class);
$classifier = new LogisticRegression(
500,
@ -40,7 +40,7 @@ class LogisticRegressionTest extends TestCase
public function testConstructorThrowWhenInvalidPenalty(): void
{
$this->expectException(Throwable::class);
$this->expectException(InvalidArgumentException::class);
$classifier = new LogisticRegression(
500,

View File

@ -5,11 +5,24 @@ declare(strict_types=1);
namespace Phpml\Tests\Classification\Linear;
use Phpml\Classification\Linear\Perceptron;
use Phpml\Exception\InvalidArgumentException;
use Phpml\ModelManager;
use PHPUnit\Framework\TestCase;
class PerceptronTest extends TestCase
{
public function testPerceptronThrowWhenLearningRateOutOfRange(): void
{
$this->expectException(InvalidArgumentException::class);
$classifier = new Perceptron(0, 5000);
}
public function testPerceptronThrowWhenMaxIterationsOutOfRange(): void
{
$this->expectException(InvalidArgumentException::class);
$classifier = new Perceptron(0.001, 0);
}
public function testPredictSingleSample(): void
{
// AND problem

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace Phpml\Tests\DimensionReduction;
use Phpml\DimensionReduction\KernelPCA;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\InvalidOperationException;
use PHPUnit\Framework\TestCase;
class KernelPCATest extends TestCase
@ -48,4 +50,34 @@ class KernelPCATest extends TestCase
$newTransformed2 = $kpca->transform($newData);
$this->assertEquals(abs($newTransformed[0]), abs($newTransformed2[0]), '', $epsilon);
}
public function testKernelPCAThrowWhenKernelInvalid(): void
{
$this->expectException(InvalidArgumentException::class);
$kpca = new KernelPCA(0, null, 1, 15);
}
public function testTransformThrowWhenNotFitted(): void
{
$samples = [1, 0];
$kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15);
$this->expectException(InvalidOperationException::class);
$kpca->transform($samples);
}
public function testTransformThrowWhenMultiDimensionalArrayGiven(): void
{
$samples = [
[1, 0],
[1, 1],
];
$kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15);
$kpca->fit($samples);
$this->expectException(InvalidArgumentException::class);
$kpca->transform($samples);
}
}

View File

@ -6,6 +6,8 @@ namespace Phpml\Tests\DimensionReduction;
use Phpml\Dataset\Demo\IrisDataset;
use Phpml\DimensionReduction\LDA;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\InvalidOperationException;
use PHPUnit\Framework\TestCase;
class LDATest extends TestCase
@ -62,4 +64,41 @@ class LDATest extends TestCase
array_map($check, $newRow, $newRow2);
}
}
public function testLDAThrowWhenTotalVarianceOutOfRange(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new LDA(0, null);
}
public function testLDAThrowWhenNumFeaturesOutOfRange(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new LDA(null, 0);
}
public function testLDAThrowWhenParameterNotSpecified(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new LDA();
}
public function testLDAThrowWhenBothParameterSpecified(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new LDA(0.9, 1);
}
public function testTransformThrowWhenNotFitted(): void
{
$samples = [
[1, 0],
[1, 1],
];
$pca = new LDA(0.9);
$this->expectException(InvalidOperationException::class);
$pca->transform($samples);
}
}

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace Phpml\Tests\DimensionReduction;
use Phpml\DimensionReduction\PCA;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Exception\InvalidOperationException;
use PHPUnit\Framework\TestCase;
class PCATest extends TestCase
@ -54,4 +56,41 @@ class PCATest extends TestCase
}, $newRow, $newRow2);
}
}
public function testPCAThrowWhenTotalVarianceOutOfRange(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new PCA(0, null);
}
public function testPCAThrowWhenNumFeaturesOutOfRange(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new PCA(null, 0);
}
public function testPCAThrowWhenParameterNotSpecified(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new PCA();
}
public function testPCAThrowWhenBothParameterSpecified(): void
{
$this->expectException(InvalidArgumentException::class);
$pca = new PCA(0.9, 1);
}
public function testTransformThrowWhenNotFitted(): void
{
$samples = [
[1, 0],
[1, 1],
];
$pca = new PCA(0.9);
$this->expectException(InvalidOperationException::class);
$pca->transform($samples);
}
}