mirror of
https://github.com/Llewellynvdm/php-ml.git
synced 2024-11-25 06:17:34 +00:00
Added EasyCodingStandard + lots of code fixes (#156)
* travis: move coveralls here, decouple from package * composer: use PSR4 * phpunit: simpler config * travis: add ecs run * composer: add ecs dev * use standard vendor/bin directory for dependency bins, confuses with local bins and require gitignore handling * ecs: add PSR2 * [cs] PSR2 spacing fixes * [cs] PSR2 class name fix * [cs] PHP7 fixes - return semicolon spaces, old rand functions, typehints * [cs] fix less strict typehints * fix typehints to make tests pass * ecs: ignore typehint-less elements * [cs] standardize arrays * [cs] standardize docblock, remove unused comments * [cs] use self where possible * [cs] sort class elements, from public to private * [cs] do not use yoda (found less yoda-cases, than non-yoda) * space * [cs] do not assign in condition * [cs] use namespace imports if possible * [cs] use ::class over strings * [cs] fix defaults for arrays properties, properties and constants single spacing * cleanup ecs comments * [cs] use item per line in multi-items array * missing line * misc * rebase
This commit is contained in:
parent
b1d40bfa30
commit
726cf4cddf
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,8 +1,4 @@
|
|||||||
/vendor/
|
/vendor/
|
||||||
humbuglog.*
|
humbuglog.*
|
||||||
/bin/phpunit
|
|
||||||
.coverage
|
|
||||||
.php_cs.cache
|
.php_cs.cache
|
||||||
/bin/php-cs-fixer
|
|
||||||
/bin/coveralls
|
|
||||||
/build
|
/build
|
||||||
|
10
.travis.yml
10
.travis.yml
@ -6,7 +6,7 @@ matrix:
|
|||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
php: '7.1'
|
php: '7.1'
|
||||||
env: DISABLE_XDEBUG="true"
|
env: DISABLE_XDEBUG="true" STATIC_ANALYSIS="true"
|
||||||
|
|
||||||
- os: linux
|
- os: linux
|
||||||
php: '7.2'
|
php: '7.2'
|
||||||
@ -21,7 +21,7 @@ matrix:
|
|||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/prepare_osx_env.sh ; fi
|
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/prepare_osx_env.sh ; fi
|
||||||
- if [[ DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi
|
- if [[ $DISABLE_XDEBUG == "true" ]]; then phpenv config-rm xdebug.ini; fi
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/handle_brew_pkg.sh "${_PHP}" ; fi
|
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then /usr/bin/env bash bin/handle_brew_pkg.sh "${_PHP}" ; fi
|
||||||
@ -29,10 +29,12 @@ install:
|
|||||||
- php composer.phar install --dev --no-interaction --ignore-platform-reqs
|
- php composer.phar install --dev --no-interaction --ignore-platform-reqs
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- bin/phpunit $PHPUNIT_FLAGS
|
- vendor/bin/phpunit $PHPUNIT_FLAGS
|
||||||
|
- if [[ $STATIC_ANALYSIS != "" ]]; then vendor/bin/ecs check src tests; fi
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- |
|
- |
|
||||||
if [[ $PHPUNIT_FLAGS != "" ]]; then
|
if [[ $PHPUNIT_FLAGS != "" ]]; then
|
||||||
php bin/coveralls -v
|
wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar;
|
||||||
|
php coveralls.phar --verbose;
|
||||||
fi
|
fi
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-0": {
|
"psr-4": {
|
||||||
"Phpml": "src/"
|
"Phpml\\": "src/Phpml"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -22,9 +22,8 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^6.0",
|
"phpunit/phpunit": "^6.0",
|
||||||
"friendsofphp/php-cs-fixer": "^2.4",
|
"friendsofphp/php-cs-fixer": "^2.4",
|
||||||
"php-coveralls/php-coveralls": "^1.0"
|
"symplify/easy-coding-standard": "dev-master as 2.5",
|
||||||
},
|
"symplify/coding-standard": "dev-master as 2.5",
|
||||||
"config": {
|
"symplify/package-builder": "dev-master#3604bea as 2.5"
|
||||||
"bin-dir": "bin"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1968
composer.lock
generated
1968
composer.lock
generated
File diff suppressed because it is too large
Load Diff
39
easy-coding-standard.neon
Normal file
39
easy-coding-standard.neon
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
includes:
|
||||||
|
- vendor/symplify/easy-coding-standard/config/psr2.neon
|
||||||
|
- vendor/symplify/easy-coding-standard/config/php70.neon
|
||||||
|
- vendor/symplify/easy-coding-standard/config/clean-code.neon
|
||||||
|
- vendor/symplify/easy-coding-standard/config/common/array.neon
|
||||||
|
- vendor/symplify/easy-coding-standard/config/common/docblock.neon
|
||||||
|
- vendor/symplify/easy-coding-standard/config/common/namespaces.neon
|
||||||
|
- vendor/symplify/easy-coding-standard/config/common/control-structures.neon
|
||||||
|
|
||||||
|
# many errors, need help
|
||||||
|
#- vendor/symplify/easy-coding-standard/config/common/strict.neon
|
||||||
|
|
||||||
|
checkers:
|
||||||
|
- Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer
|
||||||
|
- Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer
|
||||||
|
- Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer
|
||||||
|
- Symplify\CodingStandard\Fixer\ClassNotation\PropertyAndConstantSeparationFixer
|
||||||
|
- Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
exclude_checkers:
|
||||||
|
# from strict.neon
|
||||||
|
- PhpCsFixer\Fixer\PhpUnit\PhpUnitStrictFixer
|
||||||
|
skip:
|
||||||
|
PhpCsFixer\Fixer\Alias\RandomApiMigrationFixer:
|
||||||
|
# random_int() breaks code
|
||||||
|
- src/Phpml/CrossValidation/RandomSplit.php
|
||||||
|
SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff:
|
||||||
|
# magic calls
|
||||||
|
- src/Phpml/Preprocessing/Normalizer.php
|
||||||
|
|
||||||
|
skip_codes:
|
||||||
|
# missing typehints
|
||||||
|
- SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingParameterTypeHint
|
||||||
|
- SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableParameterTypeHintSpecification
|
||||||
|
- SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingReturnTypeHint
|
||||||
|
- SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversableReturnTypeHintSpecification
|
||||||
|
- SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingPropertyTypeHint
|
||||||
|
- SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingTraversablePropertyTypeHintSpecification
|
@ -6,11 +6,9 @@
|
|||||||
beStrictAboutTestSize="true"
|
beStrictAboutTestSize="true"
|
||||||
beStrictAboutChangesToGlobalState="true"
|
beStrictAboutChangesToGlobalState="true"
|
||||||
>
|
>
|
||||||
<testsuites>
|
|
||||||
<testsuite name="PHP-ML Test Suite">
|
<testsuite name="PHP-ML Test Suite">
|
||||||
<directory>tests/*</directory>
|
<directory>tests/*</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
|
||||||
|
|
||||||
<filter>
|
<filter>
|
||||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||||
|
@ -31,7 +31,7 @@ class Apriori implements Associator
|
|||||||
*
|
*
|
||||||
* @var mixed[][][]
|
* @var mixed[][][]
|
||||||
*/
|
*/
|
||||||
private $large;
|
private $large = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum relative frequency of transactions.
|
* Minimum relative frequency of transactions.
|
||||||
@ -45,7 +45,7 @@ class Apriori implements Associator
|
|||||||
*
|
*
|
||||||
* @var mixed[][]
|
* @var mixed[][]
|
||||||
*/
|
*/
|
||||||
private $rules;
|
private $rules = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apriori constructor.
|
* Apriori constructor.
|
||||||
@ -133,7 +133,8 @@ class Apriori implements Associator
|
|||||||
private function generateRules(array $frequent): void
|
private function generateRules(array $frequent): void
|
||||||
{
|
{
|
||||||
foreach ($this->antecedents($frequent) as $antecedent) {
|
foreach ($this->antecedents($frequent) as $antecedent) {
|
||||||
if ($this->confidence <= ($confidence = $this->confidence($frequent, $antecedent))) {
|
$confidence = $this->confidence($frequent, $antecedent);
|
||||||
|
if ($this->confidence <= $confidence) {
|
||||||
$consequent = array_values(array_diff($frequent, $antecedent));
|
$consequent = array_values(array_diff($frequent, $antecedent));
|
||||||
$this->rules[] = [
|
$this->rules[] = [
|
||||||
self::ARRAY_KEY_ANTECEDENT => $antecedent,
|
self::ARRAY_KEY_ANTECEDENT => $antecedent,
|
||||||
|
@ -15,22 +15,18 @@ class DecisionTree implements Classifier
|
|||||||
use Trainable, Predictable;
|
use Trainable, Predictable;
|
||||||
|
|
||||||
public const CONTINUOUS = 1;
|
public const CONTINUOUS = 1;
|
||||||
|
|
||||||
public const NOMINAL = 2;
|
public const NOMINAL = 2;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $columnTypes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $labels = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
private $featureCount = 0;
|
public $actualDepth = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $columnTypes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var DecisionTreeLeaf
|
* @var DecisionTreeLeaf
|
||||||
@ -42,10 +38,15 @@ class DecisionTree implements Classifier
|
|||||||
*/
|
*/
|
||||||
protected $maxDepth;
|
protected $maxDepth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $labels = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
public $actualDepth = 0;
|
private $featureCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
@ -55,7 +56,7 @@ class DecisionTree implements Classifier
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $selectedFeatures;
|
private $selectedFeatures = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
@ -113,6 +114,121 @@ class DecisionTree implements Classifier
|
|||||||
return $types;
|
return $types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $baseValue
|
||||||
|
*/
|
||||||
|
public function getGiniIndex($baseValue, array $colValues, array $targets): float
|
||||||
|
{
|
||||||
|
$countMatrix = [];
|
||||||
|
foreach ($this->labels as $label) {
|
||||||
|
$countMatrix[$label] = [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($colValues as $index => $value) {
|
||||||
|
$label = $targets[$index];
|
||||||
|
$rowIndex = $value === $baseValue ? 0 : 1;
|
||||||
|
++$countMatrix[$label][$rowIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
$giniParts = [0, 0];
|
||||||
|
for ($i = 0; $i <= 1; ++$i) {
|
||||||
|
$part = 0;
|
||||||
|
$sum = array_sum(array_column($countMatrix, $i));
|
||||||
|
if ($sum > 0) {
|
||||||
|
foreach ($this->labels as $label) {
|
||||||
|
$part += pow($countMatrix[$label][$i] / (float) $sum, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$giniParts[$i] = (1 - $part) * $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_sum($giniParts) / count($colValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to set number of columns to be used
|
||||||
|
* when deciding a split at an internal node of the tree. <br>
|
||||||
|
* If the value is given 0, then all features are used (default behaviour),
|
||||||
|
* otherwise the given value will be used as a maximum for number of columns
|
||||||
|
* randomly selected for each split operation.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setNumFeatures(int $numFeatures)
|
||||||
|
{
|
||||||
|
if ($numFeatures < 0) {
|
||||||
|
throw new InvalidArgumentException('Selected column count should be greater or equal to zero');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->numUsableFeatures = $numFeatures;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string array to represent columns. Useful when HTML output or
|
||||||
|
* column importances are desired to be inspected.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setColumnNames(array $names)
|
||||||
|
{
|
||||||
|
if ($this->featureCount !== 0 && count($names) !== $this->featureCount) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Length of the given array should be equal to feature count %s', $this->featureCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->columnNames = $names;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHtml(): string
|
||||||
|
{
|
||||||
|
return $this->tree->getHTML($this->columnNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will return an array including an importance value for
|
||||||
|
* each column in the given dataset. The importance values are
|
||||||
|
* normalized and their total makes 1.<br/>
|
||||||
|
*/
|
||||||
|
public function getFeatureImportances(): array
|
||||||
|
{
|
||||||
|
if ($this->featureImportances !== null) {
|
||||||
|
return $this->featureImportances;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sampleCount = count($this->samples);
|
||||||
|
$this->featureImportances = [];
|
||||||
|
foreach ($this->columnNames as $column => $columnName) {
|
||||||
|
$nodes = $this->getSplitNodesByColumn($column, $this->tree);
|
||||||
|
|
||||||
|
$importance = 0;
|
||||||
|
foreach ($nodes as $node) {
|
||||||
|
$importance += $node->getNodeImpurityDecrease($sampleCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->featureImportances[$columnName] = $importance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize & sort the importances
|
||||||
|
$total = array_sum($this->featureImportances);
|
||||||
|
if ($total > 0) {
|
||||||
|
foreach ($this->featureImportances as &$importance) {
|
||||||
|
$importance /= $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
arsort($this->featureImportances);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->featureImportances;
|
||||||
|
}
|
||||||
|
|
||||||
protected function getSplitLeaf(array $records, int $depth = 0): DecisionTreeLeaf
|
protected function getSplitLeaf(array $records, int $depth = 0): DecisionTreeLeaf
|
||||||
{
|
{
|
||||||
$split = $this->getBestSplit($records);
|
$split = $this->getBestSplit($records);
|
||||||
@ -136,6 +252,7 @@ class DecisionTree implements Classifier
|
|||||||
if ($prevRecord && $prevRecord != $record) {
|
if ($prevRecord && $prevRecord != $record) {
|
||||||
$allSame = false;
|
$allSame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$prevRecord = $record;
|
$prevRecord = $record;
|
||||||
|
|
||||||
// According to the split criteron, this record will
|
// According to the split criteron, this record will
|
||||||
@ -163,6 +280,7 @@ class DecisionTree implements Classifier
|
|||||||
if ($leftRecords) {
|
if ($leftRecords) {
|
||||||
$split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1);
|
$split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($rightRecords) {
|
if ($rightRecords) {
|
||||||
$split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1);
|
$split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1);
|
||||||
}
|
}
|
||||||
@ -184,6 +302,7 @@ class DecisionTree implements Classifier
|
|||||||
foreach ($samples as $index => $row) {
|
foreach ($samples as $index => $row) {
|
||||||
$colValues[$index] = $row[$i];
|
$colValues[$index] = $row[$i];
|
||||||
}
|
}
|
||||||
|
|
||||||
$counts = array_count_values($colValues);
|
$counts = array_count_values($colValues);
|
||||||
arsort($counts);
|
arsort($counts);
|
||||||
$baseValue = key($counts);
|
$baseValue = key($counts);
|
||||||
@ -242,6 +361,7 @@ class DecisionTree implements Classifier
|
|||||||
if ($numFeatures > $this->featureCount) {
|
if ($numFeatures > $this->featureCount) {
|
||||||
$numFeatures = $this->featureCount;
|
$numFeatures = $this->featureCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
shuffle($allFeatures);
|
shuffle($allFeatures);
|
||||||
$selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false);
|
$selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false);
|
||||||
sort($selectedFeatures);
|
sort($selectedFeatures);
|
||||||
@ -249,38 +369,6 @@ class DecisionTree implements Classifier
|
|||||||
return $selectedFeatures;
|
return $selectedFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $baseValue
|
|
||||||
*/
|
|
||||||
public function getGiniIndex($baseValue, array $colValues, array $targets) : float
|
|
||||||
{
|
|
||||||
$countMatrix = [];
|
|
||||||
foreach ($this->labels as $label) {
|
|
||||||
$countMatrix[$label] = [0, 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($colValues as $index => $value) {
|
|
||||||
$label = $targets[$index];
|
|
||||||
$rowIndex = $value === $baseValue ? 0 : 1;
|
|
||||||
++$countMatrix[$label][$rowIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
$giniParts = [0, 0];
|
|
||||||
for ($i = 0; $i <= 1; ++$i) {
|
|
||||||
$part = 0;
|
|
||||||
$sum = array_sum(array_column($countMatrix, $i));
|
|
||||||
if ($sum > 0) {
|
|
||||||
foreach ($this->labels as $label) {
|
|
||||||
$part += pow($countMatrix[$label][$i] / (float) $sum, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$giniParts[$i] = (1 - $part) * $sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array_sum($giniParts) / count($colValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function preprocess(array $samples): array
|
protected function preprocess(array $samples): array
|
||||||
{
|
{
|
||||||
// Detect and convert continuous data column values into
|
// Detect and convert continuous data column values into
|
||||||
@ -298,8 +386,10 @@ class DecisionTree implements Classifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$columns[] = $values;
|
$columns[] = $values;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Below method is a strange yet very simple & efficient method
|
// Below method is a strange yet very simple & efficient method
|
||||||
// to get the transpose of a 2D array
|
// to get the transpose of a 2D array
|
||||||
return array_map(null, ...$columns);
|
return array_map(null, ...$columns);
|
||||||
@ -329,28 +419,6 @@ class DecisionTree implements Classifier
|
|||||||
return count($distinctValues) <= $count / 5;
|
return count($distinctValues) <= $count / 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to set number of columns to be used
|
|
||||||
* when deciding a split at an internal node of the tree. <br>
|
|
||||||
* If the value is given 0, then all features are used (default behaviour),
|
|
||||||
* otherwise the given value will be used as a maximum for number of columns
|
|
||||||
* randomly selected for each split operation.
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function setNumFeatures(int $numFeatures)
|
|
||||||
{
|
|
||||||
if ($numFeatures < 0) {
|
|
||||||
throw new InvalidArgumentException('Selected column count should be greater or equal to zero');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->numUsableFeatures = $numFeatures;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to set predefined features to consider while deciding which column to use for a split
|
* Used to set predefined features to consider while deciding which column to use for a split
|
||||||
*/
|
*/
|
||||||
@ -359,66 +427,6 @@ class DecisionTree implements Classifier
|
|||||||
$this->selectedFeatures = $selectedFeatures;
|
$this->selectedFeatures = $selectedFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A string array to represent columns. Useful when HTML output or
|
|
||||||
* column importances are desired to be inspected.
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
*/
|
|
||||||
public function setColumnNames(array $names)
|
|
||||||
{
|
|
||||||
if ($this->featureCount !== 0 && count($names) !== $this->featureCount) {
|
|
||||||
throw new InvalidArgumentException(sprintf('Length of the given array should be equal to feature count %s', $this->featureCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->columnNames = $names;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHtml() : string
|
|
||||||
{
|
|
||||||
return $this->tree->getHTML($this->columnNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will return an array including an importance value for
|
|
||||||
* each column in the given dataset. The importance values are
|
|
||||||
* normalized and their total makes 1.<br/>
|
|
||||||
*/
|
|
||||||
public function getFeatureImportances() : array
|
|
||||||
{
|
|
||||||
if ($this->featureImportances !== null) {
|
|
||||||
return $this->featureImportances;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sampleCount = count($this->samples);
|
|
||||||
$this->featureImportances = [];
|
|
||||||
foreach ($this->columnNames as $column => $columnName) {
|
|
||||||
$nodes = $this->getSplitNodesByColumn($column, $this->tree);
|
|
||||||
|
|
||||||
$importance = 0;
|
|
||||||
foreach ($nodes as $node) {
|
|
||||||
$importance += $node->getNodeImpurityDecrease($sampleCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->featureImportances[$columnName] = $importance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize & sort the importances
|
|
||||||
$total = array_sum($this->featureImportances);
|
|
||||||
if ($total > 0) {
|
|
||||||
foreach ($this->featureImportances as &$importance) {
|
|
||||||
$importance /= $total;
|
|
||||||
}
|
|
||||||
arsort($this->featureImportances);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->featureImportances;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects and returns an array of internal nodes that use the given
|
* Collects and returns an array of internal nodes that use the given
|
||||||
* column as a split criterion
|
* column as a split criterion
|
||||||
|
@ -71,6 +71,14 @@ class DecisionTreeLeaf
|
|||||||
*/
|
*/
|
||||||
public $level = 0;
|
public $level = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTML representation of the tree without column names
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->getHTML();
|
||||||
|
}
|
||||||
|
|
||||||
public function evaluate(array $record): bool
|
public function evaluate(array $record): bool
|
||||||
{
|
{
|
||||||
$recordField = $record[$this->columnIndex];
|
$recordField = $record[$this->columnIndex];
|
||||||
@ -154,12 +162,4 @@ class DecisionTreeLeaf
|
|||||||
|
|
||||||
return $str;
|
return $str;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* HTML representation of the tree without column names
|
|
||||||
*/
|
|
||||||
public function __toString() : string
|
|
||||||
{
|
|
||||||
return $this->getHTML();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Classification\Ensemble;
|
namespace Phpml\Classification\Ensemble;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Phpml\Classification\Classifier;
|
use Phpml\Classification\Classifier;
|
||||||
use Phpml\Classification\Linear\DecisionStump;
|
use Phpml\Classification\Linear\DecisionStump;
|
||||||
use Phpml\Classification\WeightedClassifier;
|
use Phpml\Classification\WeightedClassifier;
|
||||||
@ -11,6 +12,7 @@ use Phpml\Helper\Predictable;
|
|||||||
use Phpml\Helper\Trainable;
|
use Phpml\Helper\Trainable;
|
||||||
use Phpml\Math\Statistic\Mean;
|
use Phpml\Math\Statistic\Mean;
|
||||||
use Phpml\Math\Statistic\StandardDeviation;
|
use Phpml\Math\Statistic\StandardDeviation;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
class AdaBoost implements Classifier
|
class AdaBoost implements Classifier
|
||||||
{
|
{
|
||||||
@ -98,11 +100,14 @@ class AdaBoost implements Classifier
|
|||||||
// Initialize usual variables
|
// Initialize usual variables
|
||||||
$this->labels = array_keys(array_count_values($targets));
|
$this->labels = array_keys(array_count_values($targets));
|
||||||
if (count($this->labels) != 2) {
|
if (count($this->labels) != 2) {
|
||||||
throw new \Exception('AdaBoost is a binary classifier and can classify between two classes only');
|
throw new Exception('AdaBoost is a binary classifier and can classify between two classes only');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set all target values to either -1 or 1
|
// Set all target values to either -1 or 1
|
||||||
$this->labels = [1 => $this->labels[0], -1 => $this->labels[1]];
|
$this->labels = [
|
||||||
|
1 => $this->labels[0],
|
||||||
|
-1 => $this->labels[1],
|
||||||
|
];
|
||||||
foreach ($targets as $target) {
|
foreach ($targets as $target) {
|
||||||
$this->targets[] = $target == $this->labels[1] ? 1 : -1;
|
$this->targets[] = $target == $this->labels[1] ? 1 : -1;
|
||||||
}
|
}
|
||||||
@ -132,13 +137,27 @@ class AdaBoost implements Classifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function predictSample(array $sample)
|
||||||
|
{
|
||||||
|
$sum = 0;
|
||||||
|
foreach ($this->alpha as $index => $alpha) {
|
||||||
|
$h = $this->classifiers[$index]->predict($sample);
|
||||||
|
$sum += $h * $alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->labels[$sum > 0 ? 1 : -1];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the classifier with the lowest error rate with the
|
* Returns the classifier with the lowest error rate with the
|
||||||
* consideration of current sample weights
|
* consideration of current sample weights
|
||||||
*/
|
*/
|
||||||
protected function getBestClassifier(): Classifier
|
protected function getBestClassifier(): Classifier
|
||||||
{
|
{
|
||||||
$ref = new \ReflectionClass($this->baseClassifier);
|
$ref = new ReflectionClass($this->baseClassifier);
|
||||||
if ($this->classifierOptions) {
|
if ($this->classifierOptions) {
|
||||||
$classifier = $ref->newInstanceArgs($this->classifierOptions);
|
$classifier = $ref->newInstanceArgs($this->classifierOptions);
|
||||||
} else {
|
} else {
|
||||||
@ -173,9 +192,10 @@ class AdaBoost implements Classifier
|
|||||||
foreach ($weights as $index => $weight) {
|
foreach ($weights as $index => $weight) {
|
||||||
$z = (int) round(($weight - $mean) / $std) - $minZ + 1;
|
$z = (int) round(($weight - $mean) / $std) - $minZ + 1;
|
||||||
for ($i = 0; $i < $z; ++$i) {
|
for ($i = 0; $i < $z; ++$i) {
|
||||||
if (rand(0, 1) == 0) {
|
if (random_int(0, 1) == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$samples[] = $this->samples[$index];
|
$samples[] = $this->samples[$index];
|
||||||
$targets[] = $this->targets[$index];
|
$targets[] = $this->targets[$index];
|
||||||
}
|
}
|
||||||
@ -231,18 +251,4 @@ class AdaBoost implements Classifier
|
|||||||
|
|
||||||
$this->weights = $weightsT1;
|
$this->weights = $weightsT1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function predictSample(array $sample)
|
|
||||||
{
|
|
||||||
$sum = 0;
|
|
||||||
foreach ($this->alpha as $index => $alpha) {
|
|
||||||
$h = $this->classifiers[$index]->predict($sample);
|
|
||||||
$sum += $h * $alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->labels[$sum > 0 ? 1 : -1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Classification\Ensemble;
|
namespace Phpml\Classification\Ensemble;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Phpml\Classification\Classifier;
|
use Phpml\Classification\Classifier;
|
||||||
use Phpml\Classification\DecisionTree;
|
use Phpml\Classification\DecisionTree;
|
||||||
use Phpml\Helper\Predictable;
|
use Phpml\Helper\Predictable;
|
||||||
use Phpml\Helper\Trainable;
|
use Phpml\Helper\Trainable;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
class Bagging implements Classifier
|
class Bagging implements Classifier
|
||||||
{
|
{
|
||||||
@ -18,11 +20,6 @@ class Bagging implements Classifier
|
|||||||
*/
|
*/
|
||||||
protected $numSamples;
|
protected $numSamples;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $targets = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
@ -46,13 +43,18 @@ class Bagging implements Classifier
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $classifiers;
|
protected $classifiers = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var float
|
* @var float
|
||||||
*/
|
*/
|
||||||
protected $subsetRatio = 0.7;
|
protected $subsetRatio = 0.7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $targets = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -80,7 +82,7 @@ class Bagging implements Classifier
|
|||||||
public function setSubsetRatio(float $ratio)
|
public function setSubsetRatio(float $ratio)
|
||||||
{
|
{
|
||||||
if ($ratio < 0.1 || $ratio > 1.0) {
|
if ($ratio < 0.1 || $ratio > 1.0) {
|
||||||
throw new \Exception('Subset ratio should be between 0.1 and 1.0');
|
throw new Exception('Subset ratio should be between 0.1 and 1.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->subsetRatio = $ratio;
|
$this->subsetRatio = $ratio;
|
||||||
@ -130,7 +132,7 @@ class Bagging implements Classifier
|
|||||||
srand($index);
|
srand($index);
|
||||||
$bootstrapSize = $this->subsetRatio * $this->numSamples;
|
$bootstrapSize = $this->subsetRatio * $this->numSamples;
|
||||||
for ($i = 0; $i < $bootstrapSize; ++$i) {
|
for ($i = 0; $i < $bootstrapSize; ++$i) {
|
||||||
$rand = rand(0, $this->numSamples - 1);
|
$rand = random_int(0, $this->numSamples - 1);
|
||||||
$samples[] = $this->samples[$rand];
|
$samples[] = $this->samples[$rand];
|
||||||
$targets[] = $this->targets[$rand];
|
$targets[] = $this->targets[$rand];
|
||||||
}
|
}
|
||||||
@ -142,7 +144,7 @@ class Bagging implements Classifier
|
|||||||
{
|
{
|
||||||
$classifiers = [];
|
$classifiers = [];
|
||||||
for ($i = 0; $i < $this->numClassifier; ++$i) {
|
for ($i = 0; $i < $this->numClassifier; ++$i) {
|
||||||
$ref = new \ReflectionClass($this->classifier);
|
$ref = new ReflectionClass($this->classifier);
|
||||||
if ($this->classifierOptions) {
|
if ($this->classifierOptions) {
|
||||||
$obj = $ref->newInstanceArgs($this->classifierOptions);
|
$obj = $ref->newInstanceArgs($this->classifierOptions);
|
||||||
} else {
|
} else {
|
||||||
@ -155,12 +157,7 @@ class Bagging implements Classifier
|
|||||||
return $classifiers;
|
return $classifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function initSingleClassifier(Classifier $classifier): Classifier
|
||||||
* @param Classifier $classifier
|
|
||||||
*
|
|
||||||
* @return Classifier
|
|
||||||
*/
|
|
||||||
protected function initSingleClassifier($classifier)
|
|
||||||
{
|
{
|
||||||
return $classifier;
|
return $classifier;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Classification\Ensemble;
|
namespace Phpml\Classification\Ensemble;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Phpml\Classification\Classifier;
|
||||||
use Phpml\Classification\DecisionTree;
|
use Phpml\Classification\DecisionTree;
|
||||||
|
|
||||||
class RandomForest extends Bagging
|
class RandomForest extends Bagging
|
||||||
@ -48,11 +50,11 @@ class RandomForest extends Bagging
|
|||||||
public function setFeatureSubsetRatio($ratio)
|
public function setFeatureSubsetRatio($ratio)
|
||||||
{
|
{
|
||||||
if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) {
|
if (is_float($ratio) && ($ratio < 0.1 || $ratio > 1.0)) {
|
||||||
throw new \Exception('When a float given, feature subset ratio should be between 0.1 and 1.0');
|
throw new Exception('When a float given, feature subset ratio should be between 0.1 and 1.0');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') {
|
if (is_string($ratio) && $ratio != 'sqrt' && $ratio != 'log') {
|
||||||
throw new \Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' ");
|
throw new Exception("When a string given, feature subset ratio can only be 'sqrt' or 'log' ");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->featureSubsetRatio = $ratio;
|
$this->featureSubsetRatio = $ratio;
|
||||||
@ -70,7 +72,7 @@ class RandomForest extends Bagging
|
|||||||
public function setClassifer(string $classifier, array $classifierOptions = [])
|
public function setClassifer(string $classifier, array $classifierOptions = [])
|
||||||
{
|
{
|
||||||
if ($classifier != DecisionTree::class) {
|
if ($classifier != DecisionTree::class) {
|
||||||
throw new \Exception('RandomForest can only use DecisionTree as base classifier');
|
throw new Exception('RandomForest can only use DecisionTree as base classifier');
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::setClassifer($classifier, $classifierOptions);
|
return parent::setClassifer($classifier, $classifierOptions);
|
||||||
@ -127,7 +129,7 @@ class RandomForest extends Bagging
|
|||||||
*
|
*
|
||||||
* @return DecisionTree
|
* @return DecisionTree
|
||||||
*/
|
*/
|
||||||
protected function initSingleClassifier($classifier)
|
protected function initSingleClassifier(Classifier $classifier): Classifier
|
||||||
{
|
{
|
||||||
if (is_float($this->featureSubsetRatio)) {
|
if (is_float($this->featureSubsetRatio)) {
|
||||||
$featureCount = (int) ($this->featureSubsetRatio * $this->featureCount);
|
$featureCount = (int) ($this->featureSubsetRatio * $this->featureCount);
|
||||||
|
@ -28,7 +28,7 @@ class KNearestNeighbors implements Classifier
|
|||||||
*/
|
*/
|
||||||
public function __construct(int $k = 3, ?Distance $distanceMetric = null)
|
public function __construct(int $k = 3, ?Distance $distanceMetric = null)
|
||||||
{
|
{
|
||||||
if (null === $distanceMetric) {
|
if ($distanceMetric === null) {
|
||||||
$distanceMetric = new Euclidean();
|
$distanceMetric = new Euclidean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Classification\Linear;
|
namespace Phpml\Classification\Linear;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
class Adaline extends Perceptron
|
class Adaline extends Perceptron
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -41,7 +43,7 @@ class Adaline extends Perceptron
|
|||||||
int $trainingType = self::BATCH_TRAINING
|
int $trainingType = self::BATCH_TRAINING
|
||||||
) {
|
) {
|
||||||
if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) {
|
if (!in_array($trainingType, [self::BATCH_TRAINING, self::ONLINE_TRAINING])) {
|
||||||
throw new \Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm');
|
throw new Exception('Adaline can only be trained with batch and online/stochastic gradient descent algorithm');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->trainingType = $trainingType;
|
$this->trainingType = $trainingType;
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Classification\Linear;
|
namespace Phpml\Classification\Linear;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Phpml\Classification\DecisionTree;
|
use Phpml\Classification\DecisionTree;
|
||||||
use Phpml\Classification\WeightedClassifier;
|
use Phpml\Classification\WeightedClassifier;
|
||||||
use Phpml\Helper\OneVsRest;
|
use Phpml\Helper\OneVsRest;
|
||||||
@ -24,7 +25,7 @@ class DecisionStump extends WeightedClassifier
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $binaryLabels;
|
protected $binaryLabels = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lowest error rate obtained while training/optimizing the model
|
* Lowest error rate obtained while training/optimizing the model
|
||||||
@ -51,7 +52,7 @@ class DecisionStump extends WeightedClassifier
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $columnTypes;
|
protected $columnTypes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
@ -68,7 +69,7 @@ class DecisionStump extends WeightedClassifier
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $prob;
|
protected $prob = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DecisionStump classifier is a one-level deep DecisionTree. It is generally
|
* A DecisionStump classifier is a one-level deep DecisionTree. It is generally
|
||||||
@ -83,6 +84,25 @@ class DecisionStump extends WeightedClassifier
|
|||||||
$this->givenColumnIndex = $columnIndex;
|
$this->givenColumnIndex = $columnIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return "IF $this->column $this->operator $this->value ".
|
||||||
|
'THEN '.$this->binaryLabels[0].' '.
|
||||||
|
'ELSE '.$this->binaryLabels[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While finding best split point for a numerical valued column,
|
||||||
|
* DecisionStump looks for equally distanced values between minimum and maximum
|
||||||
|
* values in the column. Given <i>$count</i> value determines how many split
|
||||||
|
* points to be probed. The more split counts, the better performance but
|
||||||
|
* worse processing time (Default value is 10.0)
|
||||||
|
*/
|
||||||
|
public function setNumericalSplitCount(float $count): void
|
||||||
|
{
|
||||||
|
$this->numSplitCount = $count;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
@ -101,7 +121,7 @@ class DecisionStump extends WeightedClassifier
|
|||||||
if ($this->weights) {
|
if ($this->weights) {
|
||||||
$numWeights = count($this->weights);
|
$numWeights = count($this->weights);
|
||||||
if ($numWeights != count($samples)) {
|
if ($numWeights != count($samples)) {
|
||||||
throw new \Exception('Number of sample weights does not match with number of samples');
|
throw new Exception('Number of sample weights does not match with number of samples');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->weights = array_fill(0, count($samples), 1);
|
$this->weights = array_fill(0, count($samples), 1);
|
||||||
@ -118,9 +138,12 @@ class DecisionStump extends WeightedClassifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
$bestSplit = [
|
$bestSplit = [
|
||||||
'value' => 0, 'operator' => '',
|
'value' => 0,
|
||||||
'prob' => [], 'column' => 0,
|
'operator' => '',
|
||||||
'trainingErrorRate' => 1.0];
|
'prob' => [],
|
||||||
|
'column' => 0,
|
||||||
|
'trainingErrorRate' => 1.0,
|
||||||
|
];
|
||||||
foreach ($columns as $col) {
|
foreach ($columns as $col) {
|
||||||
if ($this->columnTypes[$col] == DecisionTree::CONTINUOUS) {
|
if ($this->columnTypes[$col] == DecisionTree::CONTINUOUS) {
|
||||||
$split = $this->getBestNumericalSplit($samples, $targets, $col);
|
$split = $this->getBestNumericalSplit($samples, $targets, $col);
|
||||||
@ -139,18 +162,6 @@ class DecisionStump extends WeightedClassifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* While finding best split point for a numerical valued column,
|
|
||||||
* DecisionStump looks for equally distanced values between minimum and maximum
|
|
||||||
* values in the column. Given <i>$count</i> value determines how many split
|
|
||||||
* points to be probed. The more split counts, the better performance but
|
|
||||||
* worse processing time (Default value is 10.0)
|
|
||||||
*/
|
|
||||||
public function setNumericalSplitCount(float $count): void
|
|
||||||
{
|
|
||||||
$this->numSplitCount = $count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines best split point for the given column
|
* Determines best split point for the given column
|
||||||
*/
|
*/
|
||||||
@ -173,9 +184,13 @@ class DecisionStump extends WeightedClassifier
|
|||||||
$threshold = array_sum($values) / (float) count($values);
|
$threshold = array_sum($values) / (float) count($values);
|
||||||
[$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values);
|
[$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values);
|
||||||
if ($split == null || $errorRate < $split['trainingErrorRate']) {
|
if ($split == null || $errorRate < $split['trainingErrorRate']) {
|
||||||
$split = ['value' => $threshold, 'operator' => $operator,
|
$split = [
|
||||||
'prob' => $prob, 'column' => $col,
|
'value' => $threshold,
|
||||||
'trainingErrorRate' => $errorRate];
|
'operator' => $operator,
|
||||||
|
'prob' => $prob,
|
||||||
|
'column' => $col,
|
||||||
|
'trainingErrorRate' => $errorRate,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try other possible points one by one
|
// Try other possible points one by one
|
||||||
@ -183,9 +198,13 @@ class DecisionStump extends WeightedClassifier
|
|||||||
$threshold = (float) $step;
|
$threshold = (float) $step;
|
||||||
[$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values);
|
[$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values);
|
||||||
if ($errorRate < $split['trainingErrorRate']) {
|
if ($errorRate < $split['trainingErrorRate']) {
|
||||||
$split = ['value' => $threshold, 'operator' => $operator,
|
$split = [
|
||||||
'prob' => $prob, 'column' => $col,
|
'value' => $threshold,
|
||||||
'trainingErrorRate' => $errorRate];
|
'operator' => $operator,
|
||||||
|
'prob' => $prob,
|
||||||
|
'column' => $col,
|
||||||
|
'trainingErrorRate' => $errorRate,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}// for
|
}// for
|
||||||
}
|
}
|
||||||
@ -206,9 +225,13 @@ class DecisionStump extends WeightedClassifier
|
|||||||
[$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values);
|
[$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values);
|
||||||
|
|
||||||
if ($split == null || $split['trainingErrorRate'] < $errorRate) {
|
if ($split == null || $split['trainingErrorRate'] < $errorRate) {
|
||||||
$split = ['value' => $val, 'operator' => $operator,
|
$split = [
|
||||||
'prob' => $prob, 'column' => $col,
|
'value' => $val,
|
||||||
'trainingErrorRate' => $errorRate];
|
'operator' => $operator,
|
||||||
|
'prob' => $prob,
|
||||||
|
'column' => $col,
|
||||||
|
'trainingErrorRate' => $errorRate,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,6 +265,7 @@ class DecisionStump extends WeightedClassifier
|
|||||||
if (!isset($prob[$predicted][$target])) {
|
if (!isset($prob[$predicted][$target])) {
|
||||||
$prob[$predicted][$target] = 0;
|
$prob[$predicted][$target] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
++$prob[$predicted][$target];
|
++$prob[$predicted][$target];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,11 +316,4 @@ class DecisionStump extends WeightedClassifier
|
|||||||
protected function resetBinary(): void
|
protected function resetBinary(): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString() : string
|
|
||||||
{
|
|
||||||
return "IF $this->column $this->operator $this->value ".
|
|
||||||
'THEN '.$this->binaryLabels[0].' '.
|
|
||||||
'ELSE '.$this->binaryLabels[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Classification\Linear;
|
namespace Phpml\Classification\Linear;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Exception;
|
||||||
use Phpml\Helper\Optimizer\ConjugateGradient;
|
use Phpml\Helper\Optimizer\ConjugateGradient;
|
||||||
|
|
||||||
class LogisticRegression extends Adaline
|
class LogisticRegression extends Adaline
|
||||||
@ -70,18 +72,18 @@ class LogisticRegression extends Adaline
|
|||||||
) {
|
) {
|
||||||
$trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING);
|
$trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING);
|
||||||
if (!in_array($trainingType, $trainingTypes)) {
|
if (!in_array($trainingType, $trainingTypes)) {
|
||||||
throw new \Exception('Logistic regression can only be trained with '.
|
throw new Exception('Logistic regression can only be trained with '.
|
||||||
'batch (gradient descent), online (stochastic gradient descent) '.
|
'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'])) {
|
if (!in_array($cost, ['log', 'sse'])) {
|
||||||
throw new \Exception("Logistic regression cost function can be one of the following: \n".
|
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");
|
"'log' for log-likelihood and 'sse' for sum of squared errors");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($penalty != '' && strtoupper($penalty) !== 'L2') {
|
if ($penalty != '' && strtoupper($penalty) !== 'L2') {
|
||||||
throw new \Exception("Logistic regression supports only 'L2' regularization");
|
throw new Exception("Logistic regression supports only 'L2' regularization");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->learningRate = 0.001;
|
$this->learningRate = 0.001;
|
||||||
@ -132,14 +134,14 @@ class LogisticRegression extends Adaline
|
|||||||
return $this->runConjugateGradient($samples, $targets, $callback);
|
return $this->runConjugateGradient($samples, $targets, $callback);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception('Logistic regression has invalid training type: %s.', $this->trainingType);
|
throw new Exception('Logistic regression has invalid training type: %s.', $this->trainingType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes Conjugate Gradient method to optimize the weights of the LogReg model
|
* Executes Conjugate Gradient method to optimize the weights of the LogReg model
|
||||||
*/
|
*/
|
||||||
protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc): void
|
protected function runConjugateGradient(array $samples, array $targets, Closure $gradientFunc): void
|
||||||
{
|
{
|
||||||
if (empty($this->optimizer)) {
|
if (empty($this->optimizer)) {
|
||||||
$this->optimizer = (new ConjugateGradient($this->featureCount))
|
$this->optimizer = (new ConjugateGradient($this->featureCount))
|
||||||
@ -155,7 +157,7 @@ class LogisticRegression extends Adaline
|
|||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
protected function getCostFunction() : \Closure
|
protected function getCostFunction(): Closure
|
||||||
{
|
{
|
||||||
$penalty = 0;
|
$penalty = 0;
|
||||||
if ($this->penalty == 'L2') {
|
if ($this->penalty == 'L2') {
|
||||||
@ -183,9 +185,11 @@ class LogisticRegression extends Adaline
|
|||||||
if ($hX == 1) {
|
if ($hX == 1) {
|
||||||
$hX = 1 - 1e-10;
|
$hX = 1 - 1e-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hX == 0) {
|
if ($hX == 0) {
|
||||||
$hX = 1e-10;
|
$hX = 1e-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
$error = -$y * log($hX) - (1 - $y) * log(1 - $hX);
|
$error = -$y * log($hX) - (1 - $y) * log(1 - $hX);
|
||||||
$gradient = $hX - $y;
|
$gradient = $hX - $y;
|
||||||
|
|
||||||
@ -218,16 +222,14 @@ class LogisticRegression extends Adaline
|
|||||||
return $callback;
|
return $callback;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction));
|
throw new Exception(sprintf('Logistic regression has invalid cost function: %s.', $this->costFunction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the output of the network, a float value between 0.0 and 1.0
|
* Returns the output of the network, a float value between 0.0 and 1.0
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
*/
|
*/
|
||||||
protected function output(array $sample)
|
protected function output(array $sample): float
|
||||||
{
|
{
|
||||||
$sum = parent::output($sample);
|
$sum = parent::output($sample);
|
||||||
|
|
||||||
@ -253,7 +255,7 @@ class LogisticRegression extends Adaline
|
|||||||
*
|
*
|
||||||
* The probability is simply taken as the distance of the sample
|
* The probability is simply taken as the distance of the sample
|
||||||
* to the decision plane.
|
* to the decision plane.
|
||||||
|
*
|
||||||
* @param mixed $label
|
* @param mixed $label
|
||||||
*/
|
*/
|
||||||
protected function predictProbability(array $sample, $label): float
|
protected function predictProbability(array $sample, $label): float
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Classification\Linear;
|
namespace Phpml\Classification\Linear;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Exception;
|
||||||
use Phpml\Classification\Classifier;
|
use Phpml\Classification\Classifier;
|
||||||
use Phpml\Helper\OneVsRest;
|
use Phpml\Helper\OneVsRest;
|
||||||
use Phpml\Helper\Optimizer\GD;
|
use Phpml\Helper\Optimizer\GD;
|
||||||
@ -34,7 +36,7 @@ class Perceptron implements Classifier, IncrementalEstimator
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $weights;
|
protected $weights = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var float
|
* @var float
|
||||||
@ -73,11 +75,11 @@ class Perceptron implements Classifier, IncrementalEstimator
|
|||||||
public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true)
|
public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true)
|
||||||
{
|
{
|
||||||
if ($learningRate <= 0.0 || $learningRate > 1.0) {
|
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 Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($maxIterations <= 0) {
|
if ($maxIterations <= 0) {
|
||||||
throw new \Exception('Maximum number of iterations must be an integer greater than 0');
|
throw new Exception('Maximum number of iterations must be an integer greater than 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($normalizeInputs) {
|
if ($normalizeInputs) {
|
||||||
@ -100,7 +102,10 @@ class Perceptron implements Classifier, IncrementalEstimator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set all target values to either -1 or 1
|
// Set all target values to either -1 or 1
|
||||||
$this->labels = [1 => $labels[0], -1 => $labels[1]];
|
$this->labels = [
|
||||||
|
1 => $labels[0],
|
||||||
|
-1 => $labels[1],
|
||||||
|
];
|
||||||
foreach ($targets as $key => $target) {
|
foreach ($targets as $key => $target) {
|
||||||
$targets[$key] = (string) $target == (string) $this->labels[1] ? 1 : -1;
|
$targets[$key] = (string) $target == (string) $this->labels[1] ? 1 : -1;
|
||||||
}
|
}
|
||||||
@ -111,15 +116,6 @@ class Perceptron implements Classifier, IncrementalEstimator
|
|||||||
$this->runTraining($samples, $targets);
|
$this->runTraining($samples, $targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resetBinary(): void
|
|
||||||
{
|
|
||||||
$this->labels = [];
|
|
||||||
$this->optimizer = null;
|
|
||||||
$this->featureCount = 0;
|
|
||||||
$this->weights = null;
|
|
||||||
$this->costValues = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normally enabling early stopping for the optimization procedure may
|
* Normally enabling early stopping for the optimization procedure may
|
||||||
* help saving processing time while in some cases it may result in
|
* help saving processing time while in some cases it may result in
|
||||||
@ -145,11 +141,18 @@ class Perceptron implements Classifier, IncrementalEstimator
|
|||||||
return $this->costValues;
|
return $this->costValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function resetBinary(): void
|
||||||
|
{
|
||||||
|
$this->labels = [];
|
||||||
|
$this->optimizer = null;
|
||||||
|
$this->featureCount = 0;
|
||||||
|
$this->weights = null;
|
||||||
|
$this->costValues = [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trains the perceptron model with Stochastic Gradient Descent optimization
|
* Trains the perceptron model with Stochastic Gradient Descent optimization
|
||||||
* to get the correct set of weights
|
* to get the correct set of weights
|
||||||
*
|
|
||||||
* @return void|mixed
|
|
||||||
*/
|
*/
|
||||||
protected function runTraining(array $samples, array $targets)
|
protected function runTraining(array $samples, array $targets)
|
||||||
{
|
{
|
||||||
@ -171,7 +174,7 @@ class Perceptron implements Classifier, IncrementalEstimator
|
|||||||
* Executes a Gradient Descent algorithm for
|
* Executes a Gradient Descent algorithm for
|
||||||
* the given cost function
|
* the given cost function
|
||||||
*/
|
*/
|
||||||
protected function runGradientDescent(array $samples, array $targets, \Closure $gradientFunc, bool $isBatch = false): void
|
protected function runGradientDescent(array $samples, array $targets, Closure $gradientFunc, bool $isBatch = false): void
|
||||||
{
|
{
|
||||||
$class = $isBatch ? GD::class : StochasticGD::class;
|
$class = $isBatch ? GD::class : StochasticGD::class;
|
||||||
|
|
||||||
@ -205,7 +208,7 @@ class Perceptron implements Classifier, IncrementalEstimator
|
|||||||
/**
|
/**
|
||||||
* Calculates net output of the network as a float value for the given input
|
* Calculates net output of the network as a float value for the given input
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int|float
|
||||||
*/
|
*/
|
||||||
protected function output(array $sample)
|
protected function output(array $sample)
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,9 @@ class NaiveBayes implements Classifier
|
|||||||
use Trainable, Predictable;
|
use Trainable, Predictable;
|
||||||
|
|
||||||
public const CONTINUOS = 1;
|
public const CONTINUOS = 1;
|
||||||
|
|
||||||
public const NOMINAL = 2;
|
public const NOMINAL = 2;
|
||||||
|
|
||||||
public const EPSILON = 1e-10;
|
public const EPSILON = 1e-10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,6 +75,31 @@ class NaiveBayes implements Classifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function predictSample(array $sample)
|
||||||
|
{
|
||||||
|
// Use NaiveBayes assumption for each label using:
|
||||||
|
// P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label)
|
||||||
|
// Then compare probability for each class to determine which label is most likely
|
||||||
|
$predictions = [];
|
||||||
|
foreach ($this->labels as $label) {
|
||||||
|
$p = $this->p[$label];
|
||||||
|
for ($i = 0; $i < $this->featureCount; ++$i) {
|
||||||
|
$Plf = $this->sampleProbability($sample, $i, $label);
|
||||||
|
$p += $Plf;
|
||||||
|
}
|
||||||
|
|
||||||
|
$predictions[$label] = $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
arsort($predictions, SORT_NUMERIC);
|
||||||
|
reset($predictions);
|
||||||
|
|
||||||
|
return key($predictions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates vital statistics for each label & feature. Stores these
|
* Calculates vital statistics for each label & feature. Stores these
|
||||||
* values in private array in order to avoid repeated calculation
|
* values in private array in order to avoid repeated calculation
|
||||||
@ -119,6 +146,7 @@ class NaiveBayes implements Classifier
|
|||||||
|
|
||||||
return $this->discreteProb[$label][$feature][$value];
|
return $this->discreteProb[$label][$feature][$value];
|
||||||
}
|
}
|
||||||
|
|
||||||
$std = $this->std[$label][$feature] ;
|
$std = $this->std[$label][$feature] ;
|
||||||
$mean = $this->mean[$label][$feature];
|
$mean = $this->mean[$label][$feature];
|
||||||
// Calculate the probability density by use of normal/Gaussian distribution
|
// Calculate the probability density by use of normal/Gaussian distribution
|
||||||
@ -148,28 +176,4 @@ class NaiveBayes implements Classifier
|
|||||||
|
|
||||||
return $samples;
|
return $samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
protected function predictSample(array $sample)
|
|
||||||
{
|
|
||||||
// Use NaiveBayes assumption for each label using:
|
|
||||||
// P(label|features) = P(label) * P(feature0|label) * P(feature1|label) .... P(featureN|label)
|
|
||||||
// Then compare probability for each class to determine which label is most likely
|
|
||||||
$predictions = [];
|
|
||||||
foreach ($this->labels as $label) {
|
|
||||||
$p = $this->p[$label];
|
|
||||||
for ($i = 0; $i < $this->featureCount; ++$i) {
|
|
||||||
$Plf = $this->sampleProbability($sample, $i, $label);
|
|
||||||
$p += $Plf;
|
|
||||||
}
|
|
||||||
$predictions[$label] = $p;
|
|
||||||
}
|
|
||||||
|
|
||||||
arsort($predictions, SORT_NUMERIC);
|
|
||||||
reset($predictions);
|
|
||||||
|
|
||||||
return key($predictions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ abstract class WeightedClassifier implements Classifier
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $weights;
|
protected $weights = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the array including a weight for each sample
|
* Sets the array including a weight for each sample
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Clustering;
|
namespace Phpml\Clustering;
|
||||||
|
|
||||||
|
use array_merge;
|
||||||
use Phpml\Math\Distance;
|
use Phpml\Math\Distance;
|
||||||
use Phpml\Math\Distance\Euclidean;
|
use Phpml\Math\Distance\Euclidean;
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ class DBSCAN implements Clusterer
|
|||||||
|
|
||||||
public function __construct(float $epsilon = 0.5, int $minSamples = 3, ?Distance $distanceMetric = null)
|
public function __construct(float $epsilon = 0.5, int $minSamples = 3, ?Distance $distanceMetric = null)
|
||||||
{
|
{
|
||||||
if (null === $distanceMetric) {
|
if ($distanceMetric === null) {
|
||||||
$distanceMetric = new Euclidean();
|
$distanceMetric = new Euclidean();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class DBSCAN implements Clusterer
|
|||||||
if (isset($visited[$index])) {
|
if (isset($visited[$index])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$visited[$index] = true;
|
$visited[$index] = true;
|
||||||
|
|
||||||
$regionSamples = $this->getSamplesInRegion($sample, $samples);
|
$regionSamples = $this->getSamplesInRegion($sample, $samples);
|
||||||
@ -84,7 +86,8 @@ class DBSCAN implements Clusterer
|
|||||||
|
|
||||||
$cluster[$index] = $sample;
|
$cluster[$index] = $sample;
|
||||||
}
|
}
|
||||||
$cluster = \array_merge($cluster, ...$clusterMerge);
|
|
||||||
|
$cluster = array_merge($cluster, ...$clusterMerge);
|
||||||
|
|
||||||
return $cluster;
|
return $cluster;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ class FuzzyCMeans implements Clusterer
|
|||||||
/**
|
/**
|
||||||
* @var array|float[][]
|
* @var array|float[][]
|
||||||
*/
|
*/
|
||||||
private $membership;
|
private $membership = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var float
|
* @var float
|
||||||
@ -55,7 +55,7 @@ class FuzzyCMeans implements Clusterer
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $samples;
|
private $samples = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
@ -65,12 +65,63 @@ class FuzzyCMeans implements Clusterer
|
|||||||
if ($clustersNumber <= 0) {
|
if ($clustersNumber <= 0) {
|
||||||
throw InvalidArgumentException::invalidClustersNumber();
|
throw InvalidArgumentException::invalidClustersNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->clustersNumber = $clustersNumber;
|
$this->clustersNumber = $clustersNumber;
|
||||||
$this->fuzziness = $fuzziness;
|
$this->fuzziness = $fuzziness;
|
||||||
$this->epsilon = $epsilon;
|
$this->epsilon = $epsilon;
|
||||||
$this->maxIterations = $maxIterations;
|
$this->maxIterations = $maxIterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMembershipMatrix(): array
|
||||||
|
{
|
||||||
|
return $this->membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|Point[] $samples
|
||||||
|
*/
|
||||||
|
public function cluster(array $samples): array
|
||||||
|
{
|
||||||
|
// Initialize variables, clusters and membership matrix
|
||||||
|
$this->sampleCount = count($samples);
|
||||||
|
$this->samples = &$samples;
|
||||||
|
$this->space = new Space(count($samples[0]));
|
||||||
|
$this->initClusters();
|
||||||
|
|
||||||
|
// Our goal is minimizing the objective value while
|
||||||
|
// executing the clustering steps at a maximum number of iterations
|
||||||
|
$lastObjective = 0.0;
|
||||||
|
$iterations = 0;
|
||||||
|
do {
|
||||||
|
// Update the membership matrix and cluster centers, respectively
|
||||||
|
$this->updateMembershipMatrix();
|
||||||
|
$this->updateClusters();
|
||||||
|
|
||||||
|
// Calculate the new value of the objective function
|
||||||
|
$objectiveVal = $this->getObjective();
|
||||||
|
$difference = abs($lastObjective - $objectiveVal);
|
||||||
|
$lastObjective = $objectiveVal;
|
||||||
|
} while ($difference > $this->epsilon && $iterations++ <= $this->maxIterations);
|
||||||
|
|
||||||
|
// Attach (hard cluster) each data point to the nearest cluster
|
||||||
|
for ($k = 0; $k < $this->sampleCount; ++$k) {
|
||||||
|
$column = array_column($this->membership, $k);
|
||||||
|
arsort($column);
|
||||||
|
reset($column);
|
||||||
|
$i = key($column);
|
||||||
|
$cluster = $this->clusters[$i];
|
||||||
|
$cluster->attach(new Point($this->samples[$k]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return grouped samples
|
||||||
|
$grouped = [];
|
||||||
|
foreach ($this->clusters as $cluster) {
|
||||||
|
$grouped[] = $cluster->getPoints();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
protected function initClusters(): void
|
protected function initClusters(): void
|
||||||
{
|
{
|
||||||
// Membership array is a matrix of cluster number by sample counts
|
// Membership array is a matrix of cluster number by sample counts
|
||||||
@ -87,7 +138,7 @@ class FuzzyCMeans implements Clusterer
|
|||||||
$row = [];
|
$row = [];
|
||||||
$total = 0.0;
|
$total = 0.0;
|
||||||
for ($k = 0; $k < $cols; ++$k) {
|
for ($k = 0; $k < $cols; ++$k) {
|
||||||
$val = rand(1, 5) / 10.0;
|
$val = random_int(1, 5) / 10.0;
|
||||||
$row[] = $val;
|
$row[] = $val;
|
||||||
$total += $val;
|
$total += $val;
|
||||||
}
|
}
|
||||||
@ -187,54 +238,4 @@ class FuzzyCMeans implements Clusterer
|
|||||||
|
|
||||||
return $sum;
|
return $sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMembershipMatrix() : array
|
|
||||||
{
|
|
||||||
return $this->membership;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array|Point[] $samples
|
|
||||||
*/
|
|
||||||
public function cluster(array $samples) : array
|
|
||||||
{
|
|
||||||
// Initialize variables, clusters and membership matrix
|
|
||||||
$this->sampleCount = count($samples);
|
|
||||||
$this->samples = &$samples;
|
|
||||||
$this->space = new Space(count($samples[0]));
|
|
||||||
$this->initClusters();
|
|
||||||
|
|
||||||
// Our goal is minimizing the objective value while
|
|
||||||
// executing the clustering steps at a maximum number of iterations
|
|
||||||
$lastObjective = 0.0;
|
|
||||||
$iterations = 0;
|
|
||||||
do {
|
|
||||||
// Update the membership matrix and cluster centers, respectively
|
|
||||||
$this->updateMembershipMatrix();
|
|
||||||
$this->updateClusters();
|
|
||||||
|
|
||||||
// Calculate the new value of the objective function
|
|
||||||
$objectiveVal = $this->getObjective();
|
|
||||||
$difference = abs($lastObjective - $objectiveVal);
|
|
||||||
$lastObjective = $objectiveVal;
|
|
||||||
} while ($difference > $this->epsilon && $iterations++ <= $this->maxIterations);
|
|
||||||
|
|
||||||
// Attach (hard cluster) each data point to the nearest cluster
|
|
||||||
for ($k = 0; $k < $this->sampleCount; ++$k) {
|
|
||||||
$column = array_column($this->membership, $k);
|
|
||||||
arsort($column);
|
|
||||||
reset($column);
|
|
||||||
$i = key($column);
|
|
||||||
$cluster = $this->clusters[$i];
|
|
||||||
$cluster->attach(new Point($this->samples[$k]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return grouped samples
|
|
||||||
$grouped = [];
|
|
||||||
foreach ($this->clusters as $cluster) {
|
|
||||||
$grouped[] = $cluster->getPoints();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $grouped;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use Phpml\Exception\InvalidArgumentException;
|
|||||||
class KMeans implements Clusterer
|
class KMeans implements Clusterer
|
||||||
{
|
{
|
||||||
public const INIT_RANDOM = 1;
|
public const INIT_RANDOM = 1;
|
||||||
|
|
||||||
public const INIT_KMEANS_PLUS_PLUS = 2;
|
public const INIT_KMEANS_PLUS_PLUS = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,7 +76,8 @@ class Cluster extends Point implements IteratorAggregate, Countable
|
|||||||
|
|
||||||
public function updateCentroid(): void
|
public function updateCentroid(): void
|
||||||
{
|
{
|
||||||
if (!$count = count($this->points)) {
|
$count = count($this->points);
|
||||||
|
if (!$count) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class Point implements ArrayAccess
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $coordinates;
|
protected $coordinates = [];
|
||||||
|
|
||||||
public function __construct(array $coordinates)
|
public function __construct(array $coordinates)
|
||||||
{
|
{
|
||||||
|
@ -177,18 +177,6 @@ class Space extends SplObjectStorage
|
|||||||
return $convergence;
|
return $convergence;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function initializeRandomClusters(int $clustersNumber) : array
|
|
||||||
{
|
|
||||||
$clusters = [];
|
|
||||||
[$min, $max] = $this->getBoundaries();
|
|
||||||
|
|
||||||
for ($n = 0; $n < $clustersNumber; ++$n) {
|
|
||||||
$clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $clusters;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function initializeKMPPClusters(int $clustersNumber): array
|
protected function initializeKMPPClusters(int $clustersNumber): array
|
||||||
{
|
{
|
||||||
$clusters = [];
|
$clusters = [];
|
||||||
@ -218,4 +206,16 @@ class Space extends SplObjectStorage
|
|||||||
|
|
||||||
return $clusters;
|
return $clusters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function initializeRandomClusters(int $clustersNumber): array
|
||||||
|
{
|
||||||
|
$clusters = [];
|
||||||
|
[$min, $max] = $this->getBoundaries();
|
||||||
|
|
||||||
|
for ($n = 0; $n < $clustersNumber; ++$n) {
|
||||||
|
$clusters[] = new Cluster($this, $this->getRandomPoint($min, $max)->getCoordinates());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clusters;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,16 +31,15 @@ abstract class Split
|
|||||||
|
|
||||||
public function __construct(Dataset $dataset, float $testSize = 0.3, ?int $seed = null)
|
public function __construct(Dataset $dataset, float $testSize = 0.3, ?int $seed = null)
|
||||||
{
|
{
|
||||||
if (0 >= $testSize || 1 <= $testSize) {
|
if ($testSize <= 0 || $testSize >= 1) {
|
||||||
throw InvalidArgumentException::percentNotInRange('testSize');
|
throw InvalidArgumentException::percentNotInRange('testSize');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->seedGenerator($seed);
|
$this->seedGenerator($seed);
|
||||||
|
|
||||||
$this->splitDataset($dataset, $testSize);
|
$this->splitDataset($dataset, $testSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected function splitDataset(Dataset $dataset, float $testSize);
|
|
||||||
|
|
||||||
public function getTrainSamples(): array
|
public function getTrainSamples(): array
|
||||||
{
|
{
|
||||||
return $this->trainSamples;
|
return $this->trainSamples;
|
||||||
@ -61,9 +60,11 @@ abstract class Split
|
|||||||
return $this->testLabels;
|
return $this->testLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract protected function splitDataset(Dataset $dataset, float $testSize);
|
||||||
|
|
||||||
protected function seedGenerator(?int $seed = null): void
|
protected function seedGenerator(?int $seed = null): void
|
||||||
{
|
{
|
||||||
if (null === $seed) {
|
if ($seed === null) {
|
||||||
mt_srand();
|
mt_srand();
|
||||||
} else {
|
} else {
|
||||||
mt_srand($seed);
|
mt_srand($seed);
|
||||||
|
@ -11,7 +11,7 @@ class CsvDataset extends ArrayDataset
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $columnNames;
|
protected $columnNames = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws FileException
|
* @throws FileException
|
||||||
@ -22,7 +22,8 @@ class CsvDataset extends ArrayDataset
|
|||||||
throw FileException::missingFile(basename($filepath));
|
throw FileException::missingFile(basename($filepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (false === $handle = fopen($filepath, 'rb')) {
|
$handle = fopen($filepath, 'rb');
|
||||||
|
if ($handle === false) {
|
||||||
throw FileException::cantOpenFile(basename($filepath));
|
throw FileException::cantOpenFile(basename($filepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\DimensionReduction;
|
namespace Phpml\DimensionReduction;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Exception;
|
||||||
use Phpml\Math\Distance\Euclidean;
|
use Phpml\Math\Distance\Euclidean;
|
||||||
use Phpml\Math\Distance\Manhattan;
|
use Phpml\Math\Distance\Manhattan;
|
||||||
use Phpml\Math\Matrix;
|
use Phpml\Math\Matrix;
|
||||||
@ -11,8 +13,11 @@ use Phpml\Math\Matrix;
|
|||||||
class KernelPCA extends PCA
|
class KernelPCA extends PCA
|
||||||
{
|
{
|
||||||
public const KERNEL_RBF = 1;
|
public const KERNEL_RBF = 1;
|
||||||
|
|
||||||
public const KERNEL_SIGMOID = 2;
|
public const KERNEL_SIGMOID = 2;
|
||||||
|
|
||||||
public const KERNEL_LAPLACIAN = 3;
|
public const KERNEL_LAPLACIAN = 3;
|
||||||
|
|
||||||
public const KERNEL_LINEAR = 4;
|
public const KERNEL_LINEAR = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,7 +39,7 @@ class KernelPCA extends PCA
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $data;
|
protected $data = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kernel principal component analysis (KernelPCA) is an extension of PCA using
|
* Kernel principal component analysis (KernelPCA) is an extension of PCA using
|
||||||
@ -54,7 +59,7 @@ class KernelPCA extends PCA
|
|||||||
{
|
{
|
||||||
$availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR];
|
$availableKernels = [self::KERNEL_RBF, self::KERNEL_SIGMOID, self::KERNEL_LAPLACIAN, self::KERNEL_LINEAR];
|
||||||
if (!in_array($kernel, $availableKernels)) {
|
if (!in_array($kernel, $availableKernels)) {
|
||||||
throw new \Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian');
|
throw new Exception('KernelPCA can be initialized with the following kernels only: Linear, RBF, Sigmoid and Laplacian');
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::__construct($totalVariance, $numFeatures);
|
parent::__construct($totalVariance, $numFeatures);
|
||||||
@ -88,6 +93,27 @@ class KernelPCA extends PCA
|
|||||||
return Matrix::transposeArray($this->eigVectors);
|
return Matrix::transposeArray($this->eigVectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the given sample to a lower dimensional vector by using
|
||||||
|
* the variables obtained during the last run of <code>fit</code>.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($sample[0])) {
|
||||||
|
throw new Exception('KernelPCA::transform() accepts only one-dimensional arrays');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pairs = $this->getDistancePairs($sample);
|
||||||
|
|
||||||
|
return $this->projectSample($pairs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates similarity matrix by use of selected kernel function<br>
|
* Calculates similarity matrix by use of selected kernel function<br>
|
||||||
* An n-by-m matrix is given and an n-by-n matrix is returned
|
* An n-by-m matrix is given and an n-by-n matrix is returned
|
||||||
@ -140,7 +166,7 @@ class KernelPCA extends PCA
|
|||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
protected function getKernel(): \Closure
|
protected function getKernel(): Closure
|
||||||
{
|
{
|
||||||
switch ($this->kernel) {
|
switch ($this->kernel) {
|
||||||
case self::KERNEL_LINEAR:
|
case self::KERNEL_LINEAR:
|
||||||
@ -173,7 +199,7 @@ class KernelPCA extends PCA
|
|||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel));
|
throw new Exception(sprintf('KernelPCA initialized with invalid kernel: %d', $this->kernel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,25 +229,4 @@ class KernelPCA extends PCA
|
|||||||
// return k.dot(eig)
|
// return k.dot(eig)
|
||||||
return Matrix::dot($pairs, $eig);
|
return Matrix::dot($pairs, $eig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the given sample to a lower dimensional vector by using
|
|
||||||
* the variables obtained during the last run of <code>fit</code>.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($sample[0])) {
|
|
||||||
throw new \Exception('KernelPCA::transform() accepts only one-dimensional arrays');
|
|
||||||
}
|
|
||||||
|
|
||||||
$pairs = $this->getDistancePairs($sample);
|
|
||||||
|
|
||||||
return $this->projectSample($pairs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\DimensionReduction;
|
namespace Phpml\DimensionReduction;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Phpml\Math\Matrix;
|
use Phpml\Math\Matrix;
|
||||||
|
|
||||||
class LDA extends EigenTransformerBase
|
class LDA extends EigenTransformerBase
|
||||||
@ -16,22 +17,22 @@ class LDA extends EigenTransformerBase
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $labels;
|
public $labels = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $means;
|
public $means = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $counts;
|
public $counts = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var float[]
|
* @var float[]
|
||||||
*/
|
*/
|
||||||
public $overallMean;
|
public $overallMean = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Linear Discriminant Analysis (LDA) is used to reduce the dimensionality
|
* Linear Discriminant Analysis (LDA) is used to reduce the dimensionality
|
||||||
@ -50,18 +51,21 @@ class LDA extends EigenTransformerBase
|
|||||||
public function __construct(?float $totalVariance = null, ?int $numFeatures = null)
|
public function __construct(?float $totalVariance = null, ?int $numFeatures = null)
|
||||||
{
|
{
|
||||||
if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) {
|
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 Exception('Total variance can be a value between 0.1 and 0.99');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($numFeatures !== null && $numFeatures <= 0) {
|
if ($numFeatures !== null && $numFeatures <= 0) {
|
||||||
throw new \Exception('Number of features to be preserved should be greater than 0');
|
throw new Exception('Number of features to be preserved should be greater than 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($totalVariance !== null && $numFeatures !== null) {
|
if ($totalVariance !== null && $numFeatures !== null) {
|
||||||
throw new \Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm');
|
throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($numFeatures !== null) {
|
if ($numFeatures !== null) {
|
||||||
$this->numFeatures = $numFeatures;
|
$this->numFeatures = $numFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($totalVariance !== null) {
|
if ($totalVariance !== null) {
|
||||||
$this->totalVariance = $totalVariance;
|
$this->totalVariance = $totalVariance;
|
||||||
}
|
}
|
||||||
@ -86,6 +90,25 @@ class LDA extends EigenTransformerBase
|
|||||||
return $this->reduce($data);
|
return $this->reduce($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the given sample to a lower dimensional vector by using
|
||||||
|
* the eigenVectors obtained in the last run of <code>fit</code>.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($sample[0])) {
|
||||||
|
$sample = [$sample];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->reduce($sample);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns unique labels in the dataset
|
* Returns unique labels in the dataset
|
||||||
*/
|
*/
|
||||||
@ -113,6 +136,7 @@ class LDA extends EigenTransformerBase
|
|||||||
if (!isset($means[$label][$col])) {
|
if (!isset($means[$label][$col])) {
|
||||||
$means[$label][$col] = 0.0;
|
$means[$label][$col] = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$means[$label][$col] += $val;
|
$means[$label][$col] += $val;
|
||||||
$overallMean[$col] += $val;
|
$overallMean[$col] += $val;
|
||||||
}
|
}
|
||||||
@ -195,23 +219,4 @@ class LDA extends EigenTransformerBase
|
|||||||
|
|
||||||
return $diff->transpose()->multiply($diff);
|
return $diff->transpose()->multiply($diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the given sample to a lower dimensional vector by using
|
|
||||||
* the eigenVectors obtained in the last run of <code>fit</code>.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_array($sample[0])) {
|
|
||||||
$sample = [$sample];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->reduce($sample);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\DimensionReduction;
|
namespace Phpml\DimensionReduction;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Phpml\Math\Statistic\Covariance;
|
use Phpml\Math\Statistic\Covariance;
|
||||||
use Phpml\Math\Statistic\Mean;
|
use Phpml\Math\Statistic\Mean;
|
||||||
|
|
||||||
@ -35,18 +36,21 @@ class PCA extends EigenTransformerBase
|
|||||||
public function __construct(?float $totalVariance = null, ?int $numFeatures = null)
|
public function __construct(?float $totalVariance = null, ?int $numFeatures = null)
|
||||||
{
|
{
|
||||||
if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) {
|
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 Exception('Total variance can be a value between 0.1 and 0.99');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($numFeatures !== null && $numFeatures <= 0) {
|
if ($numFeatures !== null && $numFeatures <= 0) {
|
||||||
throw new \Exception('Number of features to be preserved should be greater than 0');
|
throw new Exception('Number of features to be preserved should be greater than 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($totalVariance !== null && $numFeatures !== null) {
|
if ($totalVariance !== null && $numFeatures !== null) {
|
||||||
throw new \Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm');
|
throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($numFeatures !== null) {
|
if ($numFeatures !== null) {
|
||||||
$this->numFeatures = $numFeatures;
|
$this->numFeatures = $numFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($totalVariance !== null) {
|
if ($totalVariance !== null) {
|
||||||
$this->totalVariance = $totalVariance;
|
$this->totalVariance = $totalVariance;
|
||||||
}
|
}
|
||||||
@ -73,6 +77,27 @@ class PCA extends EigenTransformerBase
|
|||||||
return $this->reduce($data);
|
return $this->reduce($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the given sample to a lower dimensional vector by using
|
||||||
|
* the eigenVectors obtained in the last run of <code>fit</code>.
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($sample[0])) {
|
||||||
|
$sample = [$sample];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sample = $this->normalize($sample, count($sample[0]));
|
||||||
|
|
||||||
|
return $this->reduce($sample);
|
||||||
|
}
|
||||||
|
|
||||||
protected function calculateMeans(array $data, int $n): void
|
protected function calculateMeans(array $data, int $n): void
|
||||||
{
|
{
|
||||||
// Calculate means for each dimension
|
// Calculate means for each dimension
|
||||||
@ -102,25 +127,4 @@ class PCA extends EigenTransformerBase
|
|||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the given sample to a lower dimensional vector by using
|
|
||||||
* the eigenVectors obtained in the last run of <code>fit</code>.
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_array($sample[0])) {
|
|
||||||
$sample = [$sample];
|
|
||||||
}
|
|
||||||
|
|
||||||
$sample = $this->normalize($sample, count($sample[0]));
|
|
||||||
|
|
||||||
return $this->reduce($sample);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Exception;
|
namespace Phpml\Exception;
|
||||||
|
|
||||||
class DatasetException extends \Exception
|
use Exception;
|
||||||
|
|
||||||
|
class DatasetException extends Exception
|
||||||
{
|
{
|
||||||
public static function missingFolder(string $path) : DatasetException
|
public static function missingFolder(string $path): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('Dataset root folder "%s" missing.', $path));
|
return new self(sprintf('Dataset root folder "%s" missing.', $path));
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,21 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Exception;
|
namespace Phpml\Exception;
|
||||||
|
|
||||||
class FileException extends \Exception
|
use Exception;
|
||||||
|
|
||||||
|
class FileException extends Exception
|
||||||
{
|
{
|
||||||
public static function missingFile(string $filepath) : FileException
|
public static function missingFile(string $filepath): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('File "%s" missing.', $filepath));
|
return new self(sprintf('File "%s" missing.', $filepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function cantOpenFile(string $filepath) : FileException
|
public static function cantOpenFile(string $filepath): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('File "%s" can\'t be open.', $filepath));
|
return new self(sprintf('File "%s" can\'t be open.', $filepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function cantSaveFile(string $filepath) : FileException
|
public static function cantSaveFile(string $filepath): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('File "%s" can\'t be saved.', $filepath));
|
return new self(sprintf('File "%s" can\'t be saved.', $filepath));
|
||||||
}
|
}
|
||||||
|
@ -4,39 +4,41 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Exception;
|
namespace Phpml\Exception;
|
||||||
|
|
||||||
class InvalidArgumentException extends \Exception
|
use Exception;
|
||||||
|
|
||||||
|
class InvalidArgumentException extends Exception
|
||||||
{
|
{
|
||||||
public static function arraySizeNotMatch() : InvalidArgumentException
|
public static function arraySizeNotMatch(): self
|
||||||
{
|
{
|
||||||
return new self('Size of given arrays does not match');
|
return new self('Size of given arrays does not match');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function percentNotInRange($name) : InvalidArgumentException
|
public static function percentNotInRange($name): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('%s must be between 0.0 and 1.0', $name));
|
return new self(sprintf('%s must be between 0.0 and 1.0', $name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function arrayCantBeEmpty() : InvalidArgumentException
|
public static function arrayCantBeEmpty(): self
|
||||||
{
|
{
|
||||||
return new self('The array has zero elements');
|
return new self('The array has zero elements');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function arraySizeToSmall(int $minimumSize = 2) : InvalidArgumentException
|
public static function arraySizeToSmall(int $minimumSize = 2): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('The array must have at least %d elements', $minimumSize));
|
return new self(sprintf('The array must have at least %d elements', $minimumSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function matrixDimensionsDidNotMatch() : InvalidArgumentException
|
public static function matrixDimensionsDidNotMatch(): self
|
||||||
{
|
{
|
||||||
return new self('Matrix dimensions did not match');
|
return new self('Matrix dimensions did not match');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function inconsistentMatrixSupplied() : InvalidArgumentException
|
public static function inconsistentMatrixSupplied(): self
|
||||||
{
|
{
|
||||||
return new self('Inconsistent matrix supplied');
|
return new self('Inconsistent matrix supplied');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function invalidClustersNumber() : InvalidArgumentException
|
public static function invalidClustersNumber(): self
|
||||||
{
|
{
|
||||||
return new self('Invalid clusters number');
|
return new self('Invalid clusters number');
|
||||||
}
|
}
|
||||||
@ -44,57 +46,57 @@ class InvalidArgumentException extends \Exception
|
|||||||
/**
|
/**
|
||||||
* @param mixed $target
|
* @param mixed $target
|
||||||
*/
|
*/
|
||||||
public static function invalidTarget($target) : InvalidArgumentException
|
public static function invalidTarget($target): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('Target with value "%s" is not part of the accepted classes', $target));
|
return new self(sprintf('Target with value "%s" is not part of the accepted classes', $target));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function invalidStopWordsLanguage(string $language) : InvalidArgumentException
|
public static function invalidStopWordsLanguage(string $language): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('Can\'t find "%s" language for StopWords', $language));
|
return new self(sprintf('Can\'t find "%s" language for StopWords', $language));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function invalidLayerNodeClass() : InvalidArgumentException
|
public static function invalidLayerNodeClass(): self
|
||||||
{
|
{
|
||||||
return new self('Layer node class must implement Node interface');
|
return new self('Layer node class must implement Node interface');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function invalidLayersNumber() : InvalidArgumentException
|
public static function invalidLayersNumber(): self
|
||||||
{
|
{
|
||||||
return new self('Provide at least 1 hidden layer');
|
return new self('Provide at least 1 hidden layer');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function invalidClassesNumber() : InvalidArgumentException
|
public static function invalidClassesNumber(): self
|
||||||
{
|
{
|
||||||
return new self('Provide at least 2 different classes');
|
return new self('Provide at least 2 different classes');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function inconsistentClasses() : InvalidArgumentException
|
public static function inconsistentClasses(): self
|
||||||
{
|
{
|
||||||
return new self('The provided classes don\'t match the classes provided in the constructor');
|
return new self('The provided classes don\'t match the classes provided in the constructor');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fileNotFound(string $file) : InvalidArgumentException
|
public static function fileNotFound(string $file): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('File "%s" not found', $file));
|
return new self(sprintf('File "%s" not found', $file));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fileNotExecutable(string $file) : InvalidArgumentException
|
public static function fileNotExecutable(string $file): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('File "%s" is not executable', $file));
|
return new self(sprintf('File "%s" is not executable', $file));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function pathNotFound(string $path) : InvalidArgumentException
|
public static function pathNotFound(string $path): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('The specified path "%s" does not exist', $path));
|
return new self(sprintf('The specified path "%s" does not exist', $path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function pathNotWritable(string $path) : InvalidArgumentException
|
public static function pathNotWritable(string $path): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('The specified path "%s" is not writable', $path));
|
return new self(sprintf('The specified path "%s" is not writable', $path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function invalidOperator(string $operator) : InvalidArgumentException
|
public static function invalidOperator(string $operator): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('Invalid operator "%s" provided', $operator));
|
return new self(sprintf('Invalid operator "%s" provided', $operator));
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,21 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Exception;
|
namespace Phpml\Exception;
|
||||||
|
|
||||||
class MatrixException extends \Exception
|
use Exception;
|
||||||
|
|
||||||
|
class MatrixException extends Exception
|
||||||
{
|
{
|
||||||
public static function notSquareMatrix() : MatrixException
|
public static function notSquareMatrix(): self
|
||||||
{
|
{
|
||||||
return new self('Matrix is not square matrix');
|
return new self('Matrix is not square matrix');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function columnOutOfRange() : MatrixException
|
public static function columnOutOfRange(): self
|
||||||
{
|
{
|
||||||
return new self('Column out of range');
|
return new self('Column out of range');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function singularMatrix() : MatrixException
|
public static function singularMatrix(): self
|
||||||
{
|
{
|
||||||
return new self('Matrix is singular');
|
return new self('Matrix is singular');
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Exception;
|
namespace Phpml\Exception;
|
||||||
|
|
||||||
class NormalizerException extends \Exception
|
use Exception;
|
||||||
|
|
||||||
|
class NormalizerException extends Exception
|
||||||
{
|
{
|
||||||
public static function unknownNorm() : NormalizerException
|
public static function unknownNorm(): self
|
||||||
{
|
{
|
||||||
return new self('Unknown norm supplied.');
|
return new self('Unknown norm supplied.');
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Exception;
|
namespace Phpml\Exception;
|
||||||
|
|
||||||
class SerializeException extends \Exception
|
use Exception;
|
||||||
|
|
||||||
|
class SerializeException extends Exception
|
||||||
{
|
{
|
||||||
public static function cantUnserialize(string $filepath) : SerializeException
|
public static function cantUnserialize(string $filepath): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('"%s" can not be unserialized.', $filepath));
|
return new self(sprintf('"%s" can not be unserialized.', $filepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function cantSerialize(string $classname) : SerializeException
|
public static function cantSerialize(string $classname): self
|
||||||
{
|
{
|
||||||
return new self(sprintf('Class "%s" can not be serialized.', $classname));
|
return new self(sprintf('Class "%s" can not be serialized.', $classname));
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class StopWords
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $stopWords;
|
protected $stopWords = [];
|
||||||
|
|
||||||
public function __construct(array $stopWords)
|
public function __construct(array $stopWords)
|
||||||
{
|
{
|
||||||
@ -23,7 +23,7 @@ class StopWords
|
|||||||
return isset($this->stopWords[$token]);
|
return isset($this->stopWords[$token]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function factory(string $language = 'English') : StopWords
|
public static function factory(string $language = 'English'): self
|
||||||
{
|
{
|
||||||
$className = __NAMESPACE__."\\StopWords\\$language";
|
$className = __NAMESPACE__."\\StopWords\\$language";
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ class TfIdfTransformer implements Transformer
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $idf;
|
private $idf = [];
|
||||||
|
|
||||||
public function __construct(?array $samples = null)
|
public function __construct(?array $samples = null)
|
||||||
{
|
{
|
||||||
|
@ -27,21 +27,18 @@ class TokenCountVectorizer implements Transformer
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $vocabulary;
|
private $vocabulary = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $frequencies;
|
private $frequencies = [];
|
||||||
|
|
||||||
public function __construct(Tokenizer $tokenizer, ?StopWords $stopWords = null, float $minDF = 0.0)
|
public function __construct(Tokenizer $tokenizer, ?StopWords $stopWords = null, float $minDF = 0.0)
|
||||||
{
|
{
|
||||||
$this->tokenizer = $tokenizer;
|
$this->tokenizer = $tokenizer;
|
||||||
$this->stopWords = $stopWords;
|
$this->stopWords = $stopWords;
|
||||||
$this->minDF = $minDF;
|
$this->minDF = $minDF;
|
||||||
|
|
||||||
$this->vocabulary = [];
|
|
||||||
$this->frequencies = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fit(array $samples): void
|
public function fit(array $samples): void
|
||||||
@ -80,7 +77,7 @@ class TokenCountVectorizer implements Transformer
|
|||||||
|
|
||||||
foreach ($tokens as $token) {
|
foreach ($tokens as $token) {
|
||||||
$index = $this->getTokenIndex($token);
|
$index = $this->getTokenIndex($token);
|
||||||
if (false !== $index) {
|
if ($index !== false) {
|
||||||
$this->updateFrequency($token);
|
$this->updateFrequency($token);
|
||||||
if (!isset($counts[$index])) {
|
if (!isset($counts[$index])) {
|
||||||
$counts[$index] = 0;
|
$counts[$index] = 0;
|
||||||
|
@ -36,6 +36,18 @@ trait OneVsRest
|
|||||||
$this->trainBylabel($samples, $targets);
|
$this->trainBylabel($samples, $targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers.
|
||||||
|
*/
|
||||||
|
public function reset(): void
|
||||||
|
{
|
||||||
|
$this->classifiers = [];
|
||||||
|
$this->allLabels = [];
|
||||||
|
$this->costValues = [];
|
||||||
|
|
||||||
|
$this->resetBinary();
|
||||||
|
}
|
||||||
|
|
||||||
protected function trainByLabel(array $samples, array $targets, array $allLabels = []): void
|
protected function trainByLabel(array $samples, array $targets, array $allLabels = []): void
|
||||||
{
|
{
|
||||||
// Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run.
|
// Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run.
|
||||||
@ -44,6 +56,7 @@ trait OneVsRest
|
|||||||
} else {
|
} else {
|
||||||
$this->allLabels = array_keys(array_count_values($targets));
|
$this->allLabels = array_keys(array_count_values($targets));
|
||||||
}
|
}
|
||||||
|
|
||||||
sort($this->allLabels, SORT_STRING);
|
sort($this->allLabels, SORT_STRING);
|
||||||
|
|
||||||
// If there are only two targets, then there is no need to perform OvR
|
// If there are only two targets, then there is no need to perform OvR
|
||||||
@ -77,18 +90,6 @@ trait OneVsRest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the classifier and the vars internally used by OneVsRest to create multiple classifiers.
|
|
||||||
*/
|
|
||||||
public function reset(): void
|
|
||||||
{
|
|
||||||
$this->classifiers = [];
|
|
||||||
$this->allLabels = [];
|
|
||||||
$this->costValues = [];
|
|
||||||
|
|
||||||
$this->resetBinary();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an instance of the current class after cleaning up OneVsRest stuff.
|
* Returns an instance of the current class after cleaning up OneVsRest stuff.
|
||||||
*
|
*
|
||||||
@ -105,29 +106,6 @@ trait OneVsRest
|
|||||||
return $classifier;
|
return $classifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Groups all targets into two groups: Targets equal to
|
|
||||||
* the given label and the others
|
|
||||||
*
|
|
||||||
* $targets is not passed by reference nor contains objects so this method
|
|
||||||
* changes will not affect the caller $targets array.
|
|
||||||
*
|
|
||||||
* @param mixed $label
|
|
||||||
*
|
|
||||||
* @return array Binarized targets and target's labels
|
|
||||||
*/
|
|
||||||
private function binarizeTargets(array $targets, $label) : array
|
|
||||||
{
|
|
||||||
$notLabel = "not_$label";
|
|
||||||
foreach ($targets as $key => $target) {
|
|
||||||
$targets[$key] = $target == $label ? $label : $notLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
$labels = [$label, $notLabel];
|
|
||||||
|
|
||||||
return [$targets, $labels];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
@ -155,8 +133,6 @@ trait OneVsRest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* To be overwritten by OneVsRest classifiers.
|
* To be overwritten by OneVsRest classifiers.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
abstract protected function resetBinary(): void;
|
abstract protected function resetBinary(): void;
|
||||||
|
|
||||||
@ -174,4 +150,27 @@ trait OneVsRest
|
|||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
abstract protected function predictSampleBinary(array $sample);
|
abstract protected function predictSampleBinary(array $sample);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups all targets into two groups: Targets equal to
|
||||||
|
* the given label and the others
|
||||||
|
*
|
||||||
|
* $targets is not passed by reference nor contains objects so this method
|
||||||
|
* changes will not affect the caller $targets array.
|
||||||
|
*
|
||||||
|
* @param mixed $label
|
||||||
|
*
|
||||||
|
* @return array Binarized targets and target's labels
|
||||||
|
*/
|
||||||
|
private function binarizeTargets(array $targets, $label): array
|
||||||
|
{
|
||||||
|
$notLabel = "not_$label";
|
||||||
|
foreach ($targets as $key => $target) {
|
||||||
|
$targets[$key] = $target == $label ? $label : $notLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
$labels = [$label, $notLabel];
|
||||||
|
|
||||||
|
return [$targets, $labels];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Helper\Optimizer;
|
namespace Phpml\Helper\Optimizer;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conjugate Gradient method to solve a non-linear f(x) with respect to unknown x
|
* Conjugate Gradient method to solve a non-linear f(x) with respect to unknown x
|
||||||
* See https://en.wikipedia.org/wiki/Nonlinear_conjugate_gradient_method)
|
* See https://en.wikipedia.org/wiki/Nonlinear_conjugate_gradient_method)
|
||||||
@ -17,7 +19,7 @@ namespace Phpml\Helper\Optimizer;
|
|||||||
*/
|
*/
|
||||||
class ConjugateGradient extends GD
|
class ConjugateGradient extends GD
|
||||||
{
|
{
|
||||||
public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array
|
public function runOptimization(array $samples, array $targets, Closure $gradientCb): array
|
||||||
{
|
{
|
||||||
$this->samples = $samples;
|
$this->samples = $samples;
|
||||||
$this->targets = $targets;
|
$this->targets = $targets;
|
||||||
@ -25,7 +27,7 @@ class ConjugateGradient extends GD
|
|||||||
$this->sampleCount = count($samples);
|
$this->sampleCount = count($samples);
|
||||||
$this->costValues = [];
|
$this->costValues = [];
|
||||||
|
|
||||||
$d = mp::muls($this->gradient($this->theta), -1);
|
$d = MP::muls($this->gradient($this->theta), -1);
|
||||||
|
|
||||||
for ($i = 0; $i < $this->maxIterations; ++$i) {
|
for ($i = 0; $i < $this->maxIterations; ++$i) {
|
||||||
// Obtain α that minimizes f(θ + α.d)
|
// Obtain α that minimizes f(θ + α.d)
|
||||||
@ -96,8 +98,8 @@ class ConjugateGradient extends GD
|
|||||||
$large = 0.01 * $d;
|
$large = 0.01 * $d;
|
||||||
|
|
||||||
// Obtain θ + α.d for two initial values, x0 and x1
|
// Obtain θ + α.d for two initial values, x0 and x1
|
||||||
$x0 = mp::adds($this->theta, $small);
|
$x0 = MP::adds($this->theta, $small);
|
||||||
$x1 = mp::adds($this->theta, $large);
|
$x1 = MP::adds($this->theta, $large);
|
||||||
|
|
||||||
$epsilon = 0.0001;
|
$epsilon = 0.0001;
|
||||||
$iteration = 0;
|
$iteration = 0;
|
||||||
@ -113,9 +115,9 @@ class ConjugateGradient extends GD
|
|||||||
|
|
||||||
if ($fx1 < $fx0) {
|
if ($fx1 < $fx0) {
|
||||||
$x0 = $x1;
|
$x0 = $x1;
|
||||||
$x1 = mp::adds($x1, 0.01); // Enlarge second
|
$x1 = MP::adds($x1, 0.01); // Enlarge second
|
||||||
} else {
|
} else {
|
||||||
$x1 = mp::divs(mp::add($x1, $x0), 2.0);
|
$x1 = MP::divs(MP::add($x1, $x0), 2.0);
|
||||||
} // Get to the midpoint
|
} // Get to the midpoint
|
||||||
|
|
||||||
$error = $fx1 / $this->dimensions;
|
$error = $fx1 / $this->dimensions;
|
||||||
@ -181,7 +183,7 @@ class ConjugateGradient extends GD
|
|||||||
{
|
{
|
||||||
$grad = $this->gradient($theta);
|
$grad = $this->gradient($theta);
|
||||||
|
|
||||||
return mp::add(mp::muls($grad, -1), mp::muls($d, $beta));
|
return MP::add(MP::muls($grad, -1), MP::muls($d, $beta));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +191,7 @@ class ConjugateGradient extends GD
|
|||||||
* Handles element-wise vector operations between vector-vector
|
* Handles element-wise vector operations between vector-vector
|
||||||
* and vector-scalar variables
|
* and vector-scalar variables
|
||||||
*/
|
*/
|
||||||
class mp
|
class MP
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Element-wise <b>multiplication</b> of two vectors of the same size
|
* Element-wise <b>multiplication</b> of two vectors of the same size
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Helper\Optimizer;
|
namespace Phpml\Helper\Optimizer;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Batch version of Gradient Descent to optimize the weights
|
* Batch version of Gradient Descent to optimize the weights
|
||||||
* of a classifier given samples, targets and the objective function to minimize
|
* of a classifier given samples, targets and the objective function to minimize
|
||||||
@ -17,7 +19,7 @@ class GD extends StochasticGD
|
|||||||
*/
|
*/
|
||||||
protected $sampleCount = null;
|
protected $sampleCount = null;
|
||||||
|
|
||||||
public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array
|
public function runOptimization(array $samples, array $targets, Closure $gradientCb): array
|
||||||
{
|
{
|
||||||
$this->samples = $samples;
|
$this->samples = $samples;
|
||||||
$this->targets = $targets;
|
$this->targets = $targets;
|
||||||
|
@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Helper\Optimizer;
|
namespace Phpml\Helper\Optimizer;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
abstract class Optimizer
|
abstract class Optimizer
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -11,7 +14,7 @@ abstract class Optimizer
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $theta;
|
protected $theta = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of dimensions
|
* Number of dimensions
|
||||||
@ -30,7 +33,7 @@ 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[] = rand() / (float) getrandmax();
|
$this->theta[] = random_int(0, getrandmax()) / (float) getrandmax();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +47,7 @@ abstract class Optimizer
|
|||||||
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 Exception("Number of values in the weights array should be $this->dimensions");
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->theta = $theta;
|
$this->theta = $theta;
|
||||||
@ -56,5 +59,5 @@ abstract class Optimizer
|
|||||||
* Executes the optimization with the given samples & targets
|
* Executes the optimization with the given samples & targets
|
||||||
* and returns the weights
|
* and returns the weights
|
||||||
*/
|
*/
|
||||||
abstract public function runOptimization(array $samples, array $targets, \Closure $gradientCb);
|
abstract public function runOptimization(array $samples, array $targets, Closure $gradientCb);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Helper\Optimizer;
|
namespace Phpml\Helper\Optimizer;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stochastic Gradient Descent optimization method
|
* Stochastic Gradient Descent optimization method
|
||||||
* to find a solution for the equation A.ϴ = y where
|
* to find a solution for the equation A.ϴ = y where
|
||||||
@ -66,6 +68,7 @@ class StochasticGD extends Optimizer
|
|||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
protected $enableEarlyStop = true;
|
protected $enableEarlyStop = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of values obtained by evaluating the cost function at each iteration
|
* List of values obtained by evaluating the cost function at each iteration
|
||||||
* of the algorithm
|
* of the algorithm
|
||||||
@ -141,7 +144,7 @@ class StochasticGD extends Optimizer
|
|||||||
* The cost function to minimize and the gradient of the function are to be
|
* The cost function to minimize and the gradient of the function are to be
|
||||||
* handled by the callback function provided as the third parameter of the method.
|
* handled by the callback function provided as the third parameter of the method.
|
||||||
*/
|
*/
|
||||||
public function runOptimization(array $samples, array $targets, \Closure $gradientCb) : array
|
public function runOptimization(array $samples, array $targets, Closure $gradientCb): array
|
||||||
{
|
{
|
||||||
$this->samples = $samples;
|
$this->samples = $samples;
|
||||||
$this->targets = $targets;
|
$this->targets = $targets;
|
||||||
@ -181,6 +184,15 @@ class StochasticGD extends Optimizer
|
|||||||
return $this->theta = $bestTheta;
|
return $this->theta = $bestTheta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of cost values for each iteration executed in
|
||||||
|
* last run of the optimization
|
||||||
|
*/
|
||||||
|
public function getCostValues(): array
|
||||||
|
{
|
||||||
|
return $this->costValues;
|
||||||
|
}
|
||||||
|
|
||||||
protected function updateTheta(): float
|
protected function updateTheta(): float
|
||||||
{
|
{
|
||||||
$jValue = 0.0;
|
$jValue = 0.0;
|
||||||
@ -237,15 +249,6 @@ class StochasticGD extends Optimizer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of cost values for each iteration executed in
|
|
||||||
* last run of the optimization
|
|
||||||
*/
|
|
||||||
public function getCostValues() : array
|
|
||||||
{
|
|
||||||
return $this->costValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the optimizer internal vars after the optimization process.
|
* Clears the optimizer internal vars after the optimization process.
|
||||||
*/
|
*/
|
||||||
|
@ -7,10 +7,10 @@ namespace Phpml\Math;
|
|||||||
interface Kernel
|
interface Kernel
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param float $a
|
* @param float|array $a
|
||||||
* @param float $b
|
* @param float|array $b
|
||||||
*
|
*
|
||||||
* @return float
|
* @return float|array
|
||||||
*/
|
*/
|
||||||
public function compute($a, $b);
|
public function compute($a, $b);
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,11 @@ class RBF implements Kernel
|
|||||||
* @param array $a
|
* @param array $a
|
||||||
* @param array $b
|
* @param array $b
|
||||||
*/
|
*/
|
||||||
public function compute($a, $b)
|
public function compute($a, $b): float
|
||||||
{
|
{
|
||||||
$score = 2 * Product::scalar($a, $b);
|
$score = 2 * Product::scalar($a, $b);
|
||||||
$squares = Product::scalar($a, $a) + Product::scalar($b, $b);
|
$squares = Product::scalar($a, $a) + Product::scalar($b, $b);
|
||||||
$result = exp(-$this->gamma * ($squares - $score));
|
|
||||||
|
|
||||||
return $result;
|
return exp(-$this->gamma * ($squares - $score));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to obtain eigenvalues and eigenvectors of a real matrix.
|
* Class to obtain eigenvalues and eigenvectors of a real matrix.
|
||||||
*
|
*
|
||||||
@ -54,6 +55,7 @@ class EigenvalueDecomposition
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $d = [];
|
private $d = [];
|
||||||
|
|
||||||
private $e = [];
|
private $e = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,7 +77,7 @@ class EigenvalueDecomposition
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $ort;
|
private $ort = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for complex scalar division.
|
* Used for complex scalar division.
|
||||||
@ -83,6 +85,7 @@ class EigenvalueDecomposition
|
|||||||
* @var float
|
* @var float
|
||||||
*/
|
*/
|
||||||
private $cdivr;
|
private $cdivr;
|
||||||
|
|
||||||
private $cdivi;
|
private $cdivi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,6 +119,71 @@ class EigenvalueDecomposition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the eigenvector matrix
|
||||||
|
*/
|
||||||
|
public function getEigenvectors(): array
|
||||||
|
{
|
||||||
|
$vectors = $this->V;
|
||||||
|
|
||||||
|
// Always return the eigenvectors of length 1.0
|
||||||
|
$vectors = new Matrix($vectors);
|
||||||
|
$vectors = array_map(function ($vect) {
|
||||||
|
$sum = 0;
|
||||||
|
for ($i = 0; $i < count($vect); ++$i) {
|
||||||
|
$sum += $vect[$i] ** 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = sqrt($sum);
|
||||||
|
for ($i = 0; $i < count($vect); ++$i) {
|
||||||
|
$vect[$i] /= $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $vect;
|
||||||
|
}, $vectors->transpose()->toArray());
|
||||||
|
|
||||||
|
return $vectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the real parts of the eigenvalues<br>
|
||||||
|
* d = real(diag(D));
|
||||||
|
*/
|
||||||
|
public function getRealEigenvalues(): array
|
||||||
|
{
|
||||||
|
return $this->d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the imaginary parts of the eigenvalues <br>
|
||||||
|
* d = imag(diag(D))
|
||||||
|
*/
|
||||||
|
public function getImagEigenvalues(): array
|
||||||
|
{
|
||||||
|
return $this->e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the block diagonal eigenvalue matrix
|
||||||
|
*/
|
||||||
|
public function getDiagonalEigenvalues(): array
|
||||||
|
{
|
||||||
|
$D = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < $this->n; ++$i) {
|
||||||
|
$D[$i] = array_fill(0, $this->n, 0.0);
|
||||||
|
$D[$i][$i] = $this->d[$i];
|
||||||
|
if ($this->e[$i] == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$o = ($this->e[$i] > 0) ? $i + 1 : $i - 1;
|
||||||
|
$D[$i][$o] = $this->e[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $D;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symmetric Householder reduction to tridiagonal form.
|
* Symmetric Householder reduction to tridiagonal form.
|
||||||
*/
|
*/
|
||||||
@ -158,6 +226,7 @@ class EigenvalueDecomposition
|
|||||||
for ($j = 0; $j < $i; ++$j) {
|
for ($j = 0; $j < $i; ++$j) {
|
||||||
$this->e[$j] = 0.0;
|
$this->e[$j] = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply similarity transformation to remaining columns.
|
// Apply similarity transformation to remaining columns.
|
||||||
for ($j = 0; $j < $i; ++$j) {
|
for ($j = 0; $j < $i; ++$j) {
|
||||||
$f = $this->d[$j];
|
$f = $this->d[$j];
|
||||||
@ -168,6 +237,7 @@ class EigenvalueDecomposition
|
|||||||
$g += $this->V[$k][$j] * $this->d[$k];
|
$g += $this->V[$k][$j] * $this->d[$k];
|
||||||
$this->e[$k] += $this->V[$k][$j] * $f;
|
$this->e[$k] += $this->V[$k][$j] * $f;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->e[$j] = $g;
|
$this->e[$j] = $g;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,16 +255,19 @@ class EigenvalueDecomposition
|
|||||||
for ($j = 0; $j < $i; ++$j) {
|
for ($j = 0; $j < $i; ++$j) {
|
||||||
$this->e[$j] -= $hh * $this->d[$j];
|
$this->e[$j] -= $hh * $this->d[$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($j = 0; $j < $i; ++$j) {
|
for ($j = 0; $j < $i; ++$j) {
|
||||||
$f = $this->d[$j];
|
$f = $this->d[$j];
|
||||||
$g = $this->e[$j];
|
$g = $this->e[$j];
|
||||||
for ($k = $j; $k <= $i_; ++$k) {
|
for ($k = $j; $k <= $i_; ++$k) {
|
||||||
$this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]);
|
$this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->d[$j] = $this->V[$i - 1][$j];
|
$this->d[$j] = $this->V[$i - 1][$j];
|
||||||
$this->V[$i][$j] = 0.0;
|
$this->V[$i][$j] = 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->d[$i] = $h;
|
$this->d[$i] = $h;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,16 +280,19 @@ class EigenvalueDecomposition
|
|||||||
for ($k = 0; $k <= $i; ++$k) {
|
for ($k = 0; $k <= $i; ++$k) {
|
||||||
$this->d[$k] = $this->V[$k][$i + 1] / $h;
|
$this->d[$k] = $this->V[$k][$i + 1] / $h;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($j = 0; $j <= $i; ++$j) {
|
for ($j = 0; $j <= $i; ++$j) {
|
||||||
$g = 0.0;
|
$g = 0.0;
|
||||||
for ($k = 0; $k <= $i; ++$k) {
|
for ($k = 0; $k <= $i; ++$k) {
|
||||||
$g += $this->V[$k][$i + 1] * $this->V[$k][$j];
|
$g += $this->V[$k][$i + 1] * $this->V[$k][$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($k = 0; $k <= $i; ++$k) {
|
for ($k = 0; $k <= $i; ++$k) {
|
||||||
$this->V[$k][$j] -= $g * $this->d[$k];
|
$this->V[$k][$j] -= $g * $this->d[$k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($k = 0; $k <= $i; ++$k) {
|
for ($k = 0; $k <= $i; ++$k) {
|
||||||
$this->V[$k][$i + 1] = 0.0;
|
$this->V[$k][$i + 1] = 0.0;
|
||||||
}
|
}
|
||||||
@ -241,6 +317,7 @@ class EigenvalueDecomposition
|
|||||||
for ($i = 1; $i < $this->n; ++$i) {
|
for ($i = 1; $i < $this->n; ++$i) {
|
||||||
$this->e[$i - 1] = $this->e[$i];
|
$this->e[$i - 1] = $this->e[$i];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->e[$this->n - 1] = 0.0;
|
$this->e[$this->n - 1] = 0.0;
|
||||||
$f = 0.0;
|
$f = 0.0;
|
||||||
$tst1 = 0.0;
|
$tst1 = 0.0;
|
||||||
@ -254,8 +331,10 @@ class EigenvalueDecomposition
|
|||||||
if (abs($this->e[$m]) <= $eps * $tst1) {
|
if (abs($this->e[$m]) <= $eps * $tst1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
++$m;
|
++$m;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If m == l, $this->d[l] is an eigenvalue,
|
// If m == l, $this->d[l] is an eigenvalue,
|
||||||
// otherwise, iterate.
|
// otherwise, iterate.
|
||||||
if ($m > $l) {
|
if ($m > $l) {
|
||||||
@ -270,6 +349,7 @@ class EigenvalueDecomposition
|
|||||||
if ($p < 0) {
|
if ($p < 0) {
|
||||||
$r *= -1;
|
$r *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->d[$l] = $this->e[$l] / ($p + $r);
|
$this->d[$l] = $this->e[$l] / ($p + $r);
|
||||||
$this->d[$l + 1] = $this->e[$l] * ($p + $r);
|
$this->d[$l + 1] = $this->e[$l] * ($p + $r);
|
||||||
$dl1 = $this->d[$l + 1];
|
$dl1 = $this->d[$l + 1];
|
||||||
@ -277,6 +357,7 @@ class EigenvalueDecomposition
|
|||||||
for ($i = $l + 2; $i < $this->n; ++$i) {
|
for ($i = $l + 2; $i < $this->n; ++$i) {
|
||||||
$this->d[$i] -= $h;
|
$this->d[$i] -= $h;
|
||||||
}
|
}
|
||||||
|
|
||||||
$f += $h;
|
$f += $h;
|
||||||
// Implicit QL transformation.
|
// Implicit QL transformation.
|
||||||
$p = $this->d[$m];
|
$p = $this->d[$m];
|
||||||
@ -303,12 +384,14 @@ class EigenvalueDecomposition
|
|||||||
$this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h;
|
$this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1;
|
$p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1;
|
||||||
$this->e[$l] = $s * $p;
|
$this->e[$l] = $s * $p;
|
||||||
$this->d[$l] = $c * $p;
|
$this->d[$l] = $c * $p;
|
||||||
// Check for convergence.
|
// Check for convergence.
|
||||||
} while (abs($this->e[$l]) > $eps * $tst1);
|
} while (abs($this->e[$l]) > $eps * $tst1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->d[$l] = $this->d[$l] + $f;
|
$this->d[$l] = $this->d[$l] + $f;
|
||||||
$this->e[$l] = 0.0;
|
$this->e[$l] = 0.0;
|
||||||
}
|
}
|
||||||
@ -323,6 +406,7 @@ class EigenvalueDecomposition
|
|||||||
$p = $this->d[$j];
|
$p = $this->d[$j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($k != $i) {
|
if ($k != $i) {
|
||||||
$this->d[$k] = $this->d[$i];
|
$this->d[$k] = $this->d[$i];
|
||||||
$this->d[$i] = $p;
|
$this->d[$i] = $p;
|
||||||
@ -354,6 +438,7 @@ class EigenvalueDecomposition
|
|||||||
for ($i = $m; $i <= $high; ++$i) {
|
for ($i = $m; $i <= $high; ++$i) {
|
||||||
$scale = $scale + abs($this->H[$i][$m - 1]);
|
$scale = $scale + abs($this->H[$i][$m - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($scale != 0.0) {
|
if ($scale != 0.0) {
|
||||||
// Compute Householder transformation.
|
// Compute Householder transformation.
|
||||||
$h = 0.0;
|
$h = 0.0;
|
||||||
@ -361,10 +446,12 @@ class EigenvalueDecomposition
|
|||||||
$this->ort[$i] = $this->H[$i][$m - 1] / $scale;
|
$this->ort[$i] = $this->H[$i][$m - 1] / $scale;
|
||||||
$h += $this->ort[$i] * $this->ort[$i];
|
$h += $this->ort[$i] * $this->ort[$i];
|
||||||
}
|
}
|
||||||
|
|
||||||
$g = sqrt($h);
|
$g = sqrt($h);
|
||||||
if ($this->ort[$m] > 0) {
|
if ($this->ort[$m] > 0) {
|
||||||
$g *= -1;
|
$g *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$h -= $this->ort[$m] * $g;
|
$h -= $this->ort[$m] * $g;
|
||||||
$this->ort[$m] -= $g;
|
$this->ort[$m] -= $g;
|
||||||
// Apply Householder similarity transformation
|
// Apply Householder similarity transformation
|
||||||
@ -374,21 +461,25 @@ class EigenvalueDecomposition
|
|||||||
for ($i = $high; $i >= $m; --$i) {
|
for ($i = $high; $i >= $m; --$i) {
|
||||||
$f += $this->ort[$i] * $this->H[$i][$j];
|
$f += $this->ort[$i] * $this->H[$i][$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
$f /= $h;
|
$f /= $h;
|
||||||
for ($i = $m; $i <= $high; ++$i) {
|
for ($i = $m; $i <= $high; ++$i) {
|
||||||
$this->H[$i][$j] -= $f * $this->ort[$i];
|
$this->H[$i][$j] -= $f * $this->ort[$i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($i = 0; $i <= $high; ++$i) {
|
for ($i = 0; $i <= $high; ++$i) {
|
||||||
$f = 0.0;
|
$f = 0.0;
|
||||||
for ($j = $high; $j >= $m; --$j) {
|
for ($j = $high; $j >= $m; --$j) {
|
||||||
$f += $this->ort[$j] * $this->H[$i][$j];
|
$f += $this->ort[$j] * $this->H[$i][$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
$f = $f / $h;
|
$f = $f / $h;
|
||||||
for ($j = $m; $j <= $high; ++$j) {
|
for ($j = $m; $j <= $high; ++$j) {
|
||||||
$this->H[$i][$j] -= $f * $this->ort[$j];
|
$this->H[$i][$j] -= $f * $this->ort[$j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ort[$m] = $scale * $this->ort[$m];
|
$this->ort[$m] = $scale * $this->ort[$m];
|
||||||
$this->H[$m][$m - 1] = $scale * $g;
|
$this->H[$m][$m - 1] = $scale * $g;
|
||||||
}
|
}
|
||||||
@ -400,16 +491,19 @@ class EigenvalueDecomposition
|
|||||||
$this->V[$i][$j] = ($i == $j ? 1.0 : 0.0);
|
$this->V[$i][$j] = ($i == $j ? 1.0 : 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($m = $high - 1; $m >= $low + 1; --$m) {
|
for ($m = $high - 1; $m >= $low + 1; --$m) {
|
||||||
if ($this->H[$m][$m - 1] != 0.0) {
|
if ($this->H[$m][$m - 1] != 0.0) {
|
||||||
for ($i = $m + 1; $i <= $high; ++$i) {
|
for ($i = $m + 1; $i <= $high; ++$i) {
|
||||||
$this->ort[$i] = $this->H[$i][$m - 1];
|
$this->ort[$i] = $this->H[$i][$m - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($j = $m; $j <= $high; ++$j) {
|
for ($j = $m; $j <= $high; ++$j) {
|
||||||
$g = 0.0;
|
$g = 0.0;
|
||||||
for ($i = $m; $i <= $high; ++$i) {
|
for ($i = $m; $i <= $high; ++$i) {
|
||||||
$g += $this->ort[$i] * $this->V[$i][$j];
|
$g += $this->ort[$i] * $this->V[$i][$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double division avoids possible underflow
|
// Double division avoids possible underflow
|
||||||
$g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1];
|
$g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1];
|
||||||
for ($i = $m; $i <= $high; ++$i) {
|
for ($i = $m; $i <= $high; ++$i) {
|
||||||
@ -469,6 +563,7 @@ class EigenvalueDecomposition
|
|||||||
$this->d[$i] = $this->H[$i][$i];
|
$this->d[$i] = $this->H[$i][$i];
|
||||||
$this->e[$i] = 0.0;
|
$this->e[$i] = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($j = max($i - 1, 0); $j < $nn; ++$j) {
|
for ($j = max($i - 1, 0); $j < $nn; ++$j) {
|
||||||
$norm = $norm + abs($this->H[$i][$j]);
|
$norm = $norm + abs($this->H[$i][$j]);
|
||||||
}
|
}
|
||||||
@ -484,11 +579,14 @@ class EigenvalueDecomposition
|
|||||||
if ($s == 0.0) {
|
if ($s == 0.0) {
|
||||||
$s = $norm;
|
$s = $norm;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abs($this->H[$l][$l - 1]) < $eps * $s) {
|
if (abs($this->H[$l][$l - 1]) < $eps * $s) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
--$l;
|
--$l;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for convergence
|
// Check for convergence
|
||||||
// One root found
|
// One root found
|
||||||
if ($l == $n) {
|
if ($l == $n) {
|
||||||
@ -513,11 +611,13 @@ class EigenvalueDecomposition
|
|||||||
} else {
|
} else {
|
||||||
$z = $p - $z;
|
$z = $p - $z;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->d[$n - 1] = $x + $z;
|
$this->d[$n - 1] = $x + $z;
|
||||||
$this->d[$n] = $this->d[$n - 1];
|
$this->d[$n] = $this->d[$n - 1];
|
||||||
if ($z != 0.0) {
|
if ($z != 0.0) {
|
||||||
$this->d[$n] = $x - $w / $z;
|
$this->d[$n] = $x - $w / $z;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->e[$n - 1] = 0.0;
|
$this->e[$n - 1] = 0.0;
|
||||||
$this->e[$n] = 0.0;
|
$this->e[$n] = 0.0;
|
||||||
$x = $this->H[$n][$n - 1];
|
$x = $this->H[$n][$n - 1];
|
||||||
@ -533,18 +633,21 @@ class EigenvalueDecomposition
|
|||||||
$this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j];
|
$this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j];
|
||||||
$this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z;
|
$this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column modification
|
// Column modification
|
||||||
for ($i = 0; $i <= $n; ++$i) {
|
for ($i = 0; $i <= $n; ++$i) {
|
||||||
$z = $this->H[$i][$n - 1];
|
$z = $this->H[$i][$n - 1];
|
||||||
$this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n];
|
$this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n];
|
||||||
$this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z;
|
$this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accumulate transformations
|
// Accumulate transformations
|
||||||
for ($i = $low; $i <= $high; ++$i) {
|
for ($i = $low; $i <= $high; ++$i) {
|
||||||
$z = $this->V[$i][$n - 1];
|
$z = $this->V[$i][$n - 1];
|
||||||
$this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n];
|
$this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n];
|
||||||
$this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z;
|
$this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complex pair
|
// Complex pair
|
||||||
} else {
|
} else {
|
||||||
$this->d[$n - 1] = $x + $p;
|
$this->d[$n - 1] = $x + $p;
|
||||||
@ -552,6 +655,7 @@ class EigenvalueDecomposition
|
|||||||
$this->e[$n - 1] = $z;
|
$this->e[$n - 1] = $z;
|
||||||
$this->e[$n] = -$z;
|
$this->e[$n] = -$z;
|
||||||
}
|
}
|
||||||
|
|
||||||
$n = $n - 2;
|
$n = $n - 2;
|
||||||
$iter = 0;
|
$iter = 0;
|
||||||
// No convergence yet
|
// No convergence yet
|
||||||
@ -564,16 +668,19 @@ class EigenvalueDecomposition
|
|||||||
$y = $this->H[$n - 1][$n - 1];
|
$y = $this->H[$n - 1][$n - 1];
|
||||||
$w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
|
$w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wilkinson's original ad hoc shift
|
// Wilkinson's original ad hoc shift
|
||||||
if ($iter == 10) {
|
if ($iter == 10) {
|
||||||
$exshift += $x;
|
$exshift += $x;
|
||||||
for ($i = $low; $i <= $n; ++$i) {
|
for ($i = $low; $i <= $n; ++$i) {
|
||||||
$this->H[$i][$i] -= $x;
|
$this->H[$i][$i] -= $x;
|
||||||
}
|
}
|
||||||
|
|
||||||
$s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]);
|
$s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]);
|
||||||
$x = $y = 0.75 * $s;
|
$x = $y = 0.75 * $s;
|
||||||
$w = -0.4375 * $s * $s;
|
$w = -0.4375 * $s * $s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MATLAB's new ad hoc shift
|
// MATLAB's new ad hoc shift
|
||||||
if ($iter == 30) {
|
if ($iter == 30) {
|
||||||
$s = ($y - $x) / 2.0;
|
$s = ($y - $x) / 2.0;
|
||||||
@ -583,14 +690,17 @@ class EigenvalueDecomposition
|
|||||||
if ($y < $x) {
|
if ($y < $x) {
|
||||||
$s = -$s;
|
$s = -$s;
|
||||||
}
|
}
|
||||||
|
|
||||||
$s = $x - $w / (($y - $x) / 2.0 + $s);
|
$s = $x - $w / (($y - $x) / 2.0 + $s);
|
||||||
for ($i = $low; $i <= $n; ++$i) {
|
for ($i = $low; $i <= $n; ++$i) {
|
||||||
$this->H[$i][$i] -= $s;
|
$this->H[$i][$i] -= $s;
|
||||||
}
|
}
|
||||||
|
|
||||||
$exshift += $s;
|
$exshift += $s;
|
||||||
$x = $y = $w = 0.964;
|
$x = $y = $w = 0.964;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could check iteration count here.
|
// Could check iteration count here.
|
||||||
$iter = $iter + 1;
|
$iter = $iter + 1;
|
||||||
// Look for two consecutive small sub-diagonal elements
|
// Look for two consecutive small sub-diagonal elements
|
||||||
@ -609,18 +719,22 @@ class EigenvalueDecomposition
|
|||||||
if ($m == $l) {
|
if ($m == $l) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) <
|
if (abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) <
|
||||||
$eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))) {
|
$eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
--$m;
|
--$m;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($i = $m + 2; $i <= $n; ++$i) {
|
for ($i = $m + 2; $i <= $n; ++$i) {
|
||||||
$this->H[$i][$i - 2] = 0.0;
|
$this->H[$i][$i - 2] = 0.0;
|
||||||
if ($i > $m + 2) {
|
if ($i > $m + 2) {
|
||||||
$this->H[$i][$i - 3] = 0.0;
|
$this->H[$i][$i - 3] = 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double QR step involving rows l:n and columns m:n
|
// Double QR step involving rows l:n and columns m:n
|
||||||
for ($k = $m; $k <= $n - 1; ++$k) {
|
for ($k = $m; $k <= $n - 1; ++$k) {
|
||||||
$notlast = ($k != $n - 1);
|
$notlast = ($k != $n - 1);
|
||||||
@ -635,19 +749,23 @@ class EigenvalueDecomposition
|
|||||||
$r = $r / $x;
|
$r = $r / $x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($x == 0.0) {
|
if ($x == 0.0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$s = sqrt($p * $p + $q * $q + $r * $r);
|
$s = sqrt($p * $p + $q * $q + $r * $r);
|
||||||
if ($p < 0) {
|
if ($p < 0) {
|
||||||
$s = -$s;
|
$s = -$s;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($s != 0) {
|
if ($s != 0) {
|
||||||
if ($k != $m) {
|
if ($k != $m) {
|
||||||
$this->H[$k][$k - 1] = -$s * $x;
|
$this->H[$k][$k - 1] = -$s * $x;
|
||||||
} elseif ($l != $m) {
|
} elseif ($l != $m) {
|
||||||
$this->H[$k][$k - 1] = -$this->H[$k][$k - 1];
|
$this->H[$k][$k - 1] = -$this->H[$k][$k - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
$p = $p + $s;
|
$p = $p + $s;
|
||||||
$x = $p / $s;
|
$x = $p / $s;
|
||||||
$y = $q / $s;
|
$y = $q / $s;
|
||||||
@ -661,9 +779,11 @@ class EigenvalueDecomposition
|
|||||||
$p = $p + $r * $this->H[$k + 2][$j];
|
$p = $p + $r * $this->H[$k + 2][$j];
|
||||||
$this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z;
|
$this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->H[$k][$j] = $this->H[$k][$j] - $p * $x;
|
$this->H[$k][$j] = $this->H[$k][$j] - $p * $x;
|
||||||
$this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y;
|
$this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column modification
|
// Column modification
|
||||||
for ($i = 0; $i <= min($n, $k + 3); ++$i) {
|
for ($i = 0; $i <= min($n, $k + 3); ++$i) {
|
||||||
$p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1];
|
$p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1];
|
||||||
@ -671,9 +791,11 @@ class EigenvalueDecomposition
|
|||||||
$p = $p + $z * $this->H[$i][$k + 2];
|
$p = $p + $z * $this->H[$i][$k + 2];
|
||||||
$this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r;
|
$this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->H[$i][$k] = $this->H[$i][$k] - $p;
|
$this->H[$i][$k] = $this->H[$i][$k] - $p;
|
||||||
$this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q;
|
$this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accumulate transformations
|
// Accumulate transformations
|
||||||
for ($i = $low; $i <= $high; ++$i) {
|
for ($i = $low; $i <= $high; ++$i) {
|
||||||
$p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1];
|
$p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1];
|
||||||
@ -681,6 +803,7 @@ class EigenvalueDecomposition
|
|||||||
$p = $p + $z * $this->V[$i][$k + 2];
|
$p = $p + $z * $this->V[$i][$k + 2];
|
||||||
$this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r;
|
$this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->V[$i][$k] = $this->V[$i][$k] - $p;
|
$this->V[$i][$k] = $this->V[$i][$k] - $p;
|
||||||
$this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q;
|
$this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q;
|
||||||
}
|
}
|
||||||
@ -719,6 +842,7 @@ class EigenvalueDecomposition
|
|||||||
} else {
|
} else {
|
||||||
$this->H[$i][$n] = -$r / ($eps * $norm);
|
$this->H[$i][$n] = -$r / ($eps * $norm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve real equations
|
// Solve real equations
|
||||||
} else {
|
} else {
|
||||||
$x = $this->H[$i][$i + 1];
|
$x = $this->H[$i][$i + 1];
|
||||||
@ -732,6 +856,7 @@ class EigenvalueDecomposition
|
|||||||
$this->H[$i + 1][$n] = (-$s - $y * $t) / $z;
|
$this->H[$i + 1][$n] = (-$s - $y * $t) / $z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overflow control
|
// Overflow control
|
||||||
$t = abs($this->H[$i][$n]);
|
$t = abs($this->H[$i][$n]);
|
||||||
if (($eps * $t) * $t > 1) {
|
if (($eps * $t) * $t > 1) {
|
||||||
@ -741,6 +866,7 @@ class EigenvalueDecomposition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complex vector
|
// Complex vector
|
||||||
} elseif ($q < 0) {
|
} elseif ($q < 0) {
|
||||||
$l = $n - 1;
|
$l = $n - 1;
|
||||||
@ -753,6 +879,7 @@ class EigenvalueDecomposition
|
|||||||
$this->H[$n - 1][$n - 1] = $this->cdivr;
|
$this->H[$n - 1][$n - 1] = $this->cdivr;
|
||||||
$this->H[$n - 1][$n] = $this->cdivi;
|
$this->H[$n - 1][$n] = $this->cdivi;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->H[$n][$n - 1] = 0.0;
|
$this->H[$n][$n - 1] = 0.0;
|
||||||
$this->H[$n][$n] = 1.0;
|
$this->H[$n][$n] = 1.0;
|
||||||
for ($i = $n - 2; $i >= 0; --$i) {
|
for ($i = $n - 2; $i >= 0; --$i) {
|
||||||
@ -763,6 +890,7 @@ class EigenvalueDecomposition
|
|||||||
$ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1];
|
$ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1];
|
||||||
$sa = $sa + $this->H[$i][$j] * $this->H[$j][$n];
|
$sa = $sa + $this->H[$i][$j] * $this->H[$j][$n];
|
||||||
}
|
}
|
||||||
|
|
||||||
$w = $this->H[$i][$i] - $p;
|
$w = $this->H[$i][$i] - $p;
|
||||||
if ($this->e[$i] < 0.0) {
|
if ($this->e[$i] < 0.0) {
|
||||||
$z = $w;
|
$z = $w;
|
||||||
@ -783,6 +911,7 @@ class EigenvalueDecomposition
|
|||||||
if ($vr == 0.0 & $vi == 0.0) {
|
if ($vr == 0.0 & $vi == 0.0) {
|
||||||
$vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z));
|
$vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
|
$this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
|
||||||
$this->H[$i][$n - 1] = $this->cdivr;
|
$this->H[$i][$n - 1] = $this->cdivr;
|
||||||
$this->H[$i][$n] = $this->cdivi;
|
$this->H[$i][$n] = $this->cdivi;
|
||||||
@ -795,6 +924,7 @@ class EigenvalueDecomposition
|
|||||||
$this->H[$i + 1][$n] = $this->cdivi;
|
$this->H[$i + 1][$n] = $this->cdivi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overflow control
|
// Overflow control
|
||||||
$t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n]));
|
$t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n]));
|
||||||
if (($eps * $t) * $t > 1) {
|
if (($eps * $t) * $t > 1) {
|
||||||
@ -824,81 +954,9 @@ class EigenvalueDecomposition
|
|||||||
for ($k = $low; $k <= min($j, $high); ++$k) {
|
for ($k = $low; $k <= min($j, $high); ++$k) {
|
||||||
$z = $z + $this->V[$i][$k] * $this->H[$k][$j];
|
$z = $z + $this->V[$i][$k] * $this->H[$k][$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->V[$i][$j] = $z;
|
$this->V[$i][$j] = $z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the eigenvector matrix
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getEigenvectors()
|
|
||||||
{
|
|
||||||
$vectors = $this->V;
|
|
||||||
|
|
||||||
// Always return the eigenvectors of length 1.0
|
|
||||||
$vectors = new Matrix($vectors);
|
|
||||||
$vectors = array_map(function ($vect) {
|
|
||||||
$sum = 0;
|
|
||||||
for ($i = 0; $i < count($vect); ++$i) {
|
|
||||||
$sum += $vect[$i] ** 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sum = sqrt($sum);
|
|
||||||
for ($i = 0; $i < count($vect); ++$i) {
|
|
||||||
$vect[$i] /= $sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $vect;
|
|
||||||
}, $vectors->transpose()->toArray());
|
|
||||||
|
|
||||||
return $vectors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the real parts of the eigenvalues<br>
|
|
||||||
* d = real(diag(D));
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getRealEigenvalues()
|
|
||||||
{
|
|
||||||
return $this->d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the imaginary parts of the eigenvalues <br>
|
|
||||||
* d = imag(diag(D))
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getImagEigenvalues()
|
|
||||||
{
|
|
||||||
return $this->e;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the block diagonal eigenvalue matrix
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getDiagonalEigenvalues()
|
|
||||||
{
|
|
||||||
$D = [];
|
|
||||||
|
|
||||||
for ($i = 0; $i < $this->n; ++$i) {
|
|
||||||
$D[$i] = array_fill(0, $this->n, 0.0);
|
|
||||||
$D[$i][$i] = $this->d[$i];
|
|
||||||
if ($this->e[$i] == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$o = ($this->e[$i] > 0) ? $i + 1 : $i - 1;
|
|
||||||
$D[$i][$o] = $this->e[$i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $D;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @package JAMA
|
* @package JAMA
|
||||||
*
|
*
|
||||||
@ -90,6 +91,7 @@ class LUDecomposition
|
|||||||
for ($i = 0; $i < $this->m; ++$i) {
|
for ($i = 0; $i < $this->m; ++$i) {
|
||||||
$this->piv[$i] = $i;
|
$this->piv[$i] = $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->pivsign = 1;
|
$this->pivsign = 1;
|
||||||
$LUcolj = [];
|
$LUcolj = [];
|
||||||
|
|
||||||
@ -99,6 +101,7 @@ class LUDecomposition
|
|||||||
for ($i = 0; $i < $this->m; ++$i) {
|
for ($i = 0; $i < $this->m; ++$i) {
|
||||||
$LUcolj[$i] = &$this->LU[$i][$j];
|
$LUcolj[$i] = &$this->LU[$i][$j];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply previous transformations.
|
// Apply previous transformations.
|
||||||
for ($i = 0; $i < $this->m; ++$i) {
|
for ($i = 0; $i < $this->m; ++$i) {
|
||||||
$LUrowi = $this->LU[$i];
|
$LUrowi = $this->LU[$i];
|
||||||
@ -108,8 +111,10 @@ class LUDecomposition
|
|||||||
for ($k = 0; $k < $kmax; ++$k) {
|
for ($k = 0; $k < $kmax; ++$k) {
|
||||||
$s += $LUrowi[$k] * $LUcolj[$k];
|
$s += $LUrowi[$k] * $LUcolj[$k];
|
||||||
}
|
}
|
||||||
|
|
||||||
$LUrowi[$j] = $LUcolj[$i] -= $s;
|
$LUrowi[$j] = $LUcolj[$i] -= $s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find pivot and exchange if necessary.
|
// Find pivot and exchange if necessary.
|
||||||
$p = $j;
|
$p = $j;
|
||||||
for ($i = $j + 1; $i < $this->m; ++$i) {
|
for ($i = $j + 1; $i < $this->m; ++$i) {
|
||||||
@ -117,17 +122,20 @@ class LUDecomposition
|
|||||||
$p = $i;
|
$p = $i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($p != $j) {
|
if ($p != $j) {
|
||||||
for ($k = 0; $k < $this->n; ++$k) {
|
for ($k = 0; $k < $this->n; ++$k) {
|
||||||
$t = $this->LU[$p][$k];
|
$t = $this->LU[$p][$k];
|
||||||
$this->LU[$p][$k] = $this->LU[$j][$k];
|
$this->LU[$p][$k] = $this->LU[$j][$k];
|
||||||
$this->LU[$j][$k] = $t;
|
$this->LU[$j][$k] = $t;
|
||||||
}
|
}
|
||||||
|
|
||||||
$k = $this->piv[$p];
|
$k = $this->piv[$p];
|
||||||
$this->piv[$p] = $this->piv[$j];
|
$this->piv[$p] = $this->piv[$j];
|
||||||
$this->piv[$j] = $k;
|
$this->piv[$j] = $k;
|
||||||
$this->pivsign = $this->pivsign * -1;
|
$this->pivsign = $this->pivsign * -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute multipliers.
|
// Compute multipliers.
|
||||||
if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) {
|
if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) {
|
||||||
for ($i = $j + 1; $i < $this->m; ++$i) {
|
for ($i = $j + 1; $i < $this->m; ++$i) {
|
||||||
@ -268,11 +276,13 @@ class LUDecomposition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve U*X = Y;
|
// Solve U*X = Y;
|
||||||
for ($k = $this->n - 1; $k >= 0; --$k) {
|
for ($k = $this->n - 1; $k >= 0; --$k) {
|
||||||
for ($j = 0; $j < $nx; ++$j) {
|
for ($j = 0; $j < $nx; ++$j) {
|
||||||
$X[$k][$j] /= $this->LU[$k][$k];
|
$X[$k][$j] /= $this->LU[$k][$k];
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($i = 0; $i < $k; ++$i) {
|
for ($i = 0; $i < $k; ++$i) {
|
||||||
for ($j = 0; $j < $nx; ++$j) {
|
for ($j = 0; $j < $nx; ++$j) {
|
||||||
$X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k];
|
$X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k];
|
||||||
|
@ -13,7 +13,7 @@ class Matrix
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $matrix;
|
private $matrix = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
@ -56,7 +56,7 @@ class Matrix
|
|||||||
$this->matrix = $matrix;
|
$this->matrix = $matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromFlatArray(array $array) : Matrix
|
public static function fromFlatArray(array $array): self
|
||||||
{
|
{
|
||||||
$matrix = [];
|
$matrix = [];
|
||||||
foreach ($array as $value) {
|
foreach ($array as $value) {
|
||||||
@ -123,7 +123,7 @@ class Matrix
|
|||||||
return $this->columns === $this->rows;
|
return $this->columns === $this->rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transpose() : Matrix
|
public function transpose(): self
|
||||||
{
|
{
|
||||||
if ($this->rows == 1) {
|
if ($this->rows == 1) {
|
||||||
$matrix = array_map(function ($el) {
|
$matrix = array_map(function ($el) {
|
||||||
@ -136,7 +136,7 @@ class Matrix
|
|||||||
return new self($matrix, false);
|
return new self($matrix, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function multiply(Matrix $matrix) : Matrix
|
public function multiply(self $matrix): self
|
||||||
{
|
{
|
||||||
if ($this->columns != $matrix->getRows()) {
|
if ($this->columns != $matrix->getRows()) {
|
||||||
throw InvalidArgumentException::inconsistentMatrixSupplied();
|
throw InvalidArgumentException::inconsistentMatrixSupplied();
|
||||||
@ -157,7 +157,7 @@ class Matrix
|
|||||||
return new self($product, false);
|
return new self($product, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function divideByScalar($value) : Matrix
|
public function divideByScalar($value): self
|
||||||
{
|
{
|
||||||
$newMatrix = [];
|
$newMatrix = [];
|
||||||
for ($i = 0; $i < $this->rows; ++$i) {
|
for ($i = 0; $i < $this->rows; ++$i) {
|
||||||
@ -169,7 +169,7 @@ class Matrix
|
|||||||
return new self($newMatrix, false);
|
return new self($newMatrix, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function multiplyByScalar($value) : Matrix
|
public function multiplyByScalar($value): self
|
||||||
{
|
{
|
||||||
$newMatrix = [];
|
$newMatrix = [];
|
||||||
for ($i = 0; $i < $this->rows; ++$i) {
|
for ($i = 0; $i < $this->rows; ++$i) {
|
||||||
@ -184,7 +184,7 @@ class Matrix
|
|||||||
/**
|
/**
|
||||||
* Element-wise addition of the matrix with another one
|
* Element-wise addition of the matrix with another one
|
||||||
*/
|
*/
|
||||||
public function add(Matrix $other) : Matrix
|
public function add(self $other): self
|
||||||
{
|
{
|
||||||
return $this->_add($other);
|
return $this->_add($other);
|
||||||
}
|
}
|
||||||
@ -192,30 +192,12 @@ class Matrix
|
|||||||
/**
|
/**
|
||||||
* Element-wise subtracting of another matrix from this one
|
* Element-wise subtracting of another matrix from this one
|
||||||
*/
|
*/
|
||||||
public function subtract(Matrix $other) : Matrix
|
public function subtract(self $other): self
|
||||||
{
|
{
|
||||||
return $this->_add($other, -1);
|
return $this->_add($other, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function inverse(): self
|
||||||
* Element-wise addition or substraction depending on the given sign parameter
|
|
||||||
*/
|
|
||||||
protected function _add(Matrix $other, int $sign = 1) : Matrix
|
|
||||||
{
|
|
||||||
$a1 = $this->toArray();
|
|
||||||
$a2 = $other->toArray();
|
|
||||||
|
|
||||||
$newMatrix = [];
|
|
||||||
for ($i = 0; $i < $this->rows; ++$i) {
|
|
||||||
for ($k = 0; $k < $this->columns; ++$k) {
|
|
||||||
$newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new self($newMatrix, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function inverse() : Matrix
|
|
||||||
{
|
{
|
||||||
if (!$this->isSquare()) {
|
if (!$this->isSquare()) {
|
||||||
throw MatrixException::notSquareMatrix();
|
throw MatrixException::notSquareMatrix();
|
||||||
@ -228,20 +210,7 @@ class Matrix
|
|||||||
return new self($inverse, false);
|
return new self($inverse, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function crossOut(int $row, int $column): self
|
||||||
* Returns diagonal identity matrix of the same size of this matrix
|
|
||||||
*/
|
|
||||||
protected function getIdentity() : Matrix
|
|
||||||
{
|
|
||||||
$array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0));
|
|
||||||
for ($i = 0; $i < $this->rows; ++$i) {
|
|
||||||
$array[$i][$i] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new self($array, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function crossOut(int $row, int $column) : Matrix
|
|
||||||
{
|
{
|
||||||
$newMatrix = [];
|
$newMatrix = [];
|
||||||
$r = 0;
|
$r = 0;
|
||||||
@ -254,6 +223,7 @@ class Matrix
|
|||||||
++$c;
|
++$c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
++$r;
|
++$r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,7 +233,7 @@ class Matrix
|
|||||||
|
|
||||||
public function isSingular(): bool
|
public function isSingular(): bool
|
||||||
{
|
{
|
||||||
return 0 == $this->getDeterminant();
|
return $this->getDeterminant() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -285,4 +255,35 @@ class Matrix
|
|||||||
|
|
||||||
return $m1->multiply($m2->transpose())->toArray()[0];
|
return $m1->multiply($m2->transpose())->toArray()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element-wise addition or substraction depending on the given sign parameter
|
||||||
|
*/
|
||||||
|
protected function _add(self $other, int $sign = 1): self
|
||||||
|
{
|
||||||
|
$a1 = $this->toArray();
|
||||||
|
$a2 = $other->toArray();
|
||||||
|
|
||||||
|
$newMatrix = [];
|
||||||
|
for ($i = 0; $i < $this->rows; ++$i) {
|
||||||
|
for ($k = 0; $k < $this->columns; ++$k) {
|
||||||
|
$newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self($newMatrix, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns diagonal identity matrix of the same size of this matrix
|
||||||
|
*/
|
||||||
|
protected function getIdentity(): self
|
||||||
|
{
|
||||||
|
$array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0));
|
||||||
|
for ($i = 0; $i < $this->rows; ++$i) {
|
||||||
|
$array[$i][$i] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new self($array, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Math;
|
namespace Phpml\Math;
|
||||||
|
|
||||||
class Set implements \IteratorAggregate
|
use ArrayIterator;
|
||||||
|
use IteratorAggregate;
|
||||||
|
|
||||||
|
class Set implements IteratorAggregate
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var string[]|int[]|float[]
|
* @var string[]|int[]|float[]
|
||||||
*/
|
*/
|
||||||
private $elements;
|
private $elements = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[]|int[]|float[] $elements
|
* @param string[]|int[]|float[] $elements
|
||||||
@ -22,7 +25,7 @@ class Set implements \IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* Creates the union of A and B.
|
* Creates the union of A and B.
|
||||||
*/
|
*/
|
||||||
public static function union(Set $a, Set $b) : Set
|
public static function union(self $a, self $b): self
|
||||||
{
|
{
|
||||||
return new self(array_merge($a->toArray(), $b->toArray()));
|
return new self(array_merge($a->toArray(), $b->toArray()));
|
||||||
}
|
}
|
||||||
@ -30,7 +33,7 @@ class Set implements \IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* Creates the intersection of A and B.
|
* Creates the intersection of A and B.
|
||||||
*/
|
*/
|
||||||
public static function intersection(Set $a, Set $b) : Set
|
public static function intersection(self $a, self $b): self
|
||||||
{
|
{
|
||||||
return new self(array_intersect($a->toArray(), $b->toArray()));
|
return new self(array_intersect($a->toArray(), $b->toArray()));
|
||||||
}
|
}
|
||||||
@ -38,7 +41,7 @@ class Set implements \IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* Creates the difference of A and B.
|
* Creates the difference of A and B.
|
||||||
*/
|
*/
|
||||||
public static function difference(Set $a, Set $b) : Set
|
public static function difference(self $a, self $b): self
|
||||||
{
|
{
|
||||||
return new self(array_diff($a->toArray(), $b->toArray()));
|
return new self(array_diff($a->toArray(), $b->toArray()));
|
||||||
}
|
}
|
||||||
@ -48,7 +51,7 @@ class Set implements \IteratorAggregate
|
|||||||
*
|
*
|
||||||
* @return Set[]
|
* @return Set[]
|
||||||
*/
|
*/
|
||||||
public static function cartesian(Set $a, Set $b) : array
|
public static function cartesian(self $a, self $b): array
|
||||||
{
|
{
|
||||||
$cartesian = [];
|
$cartesian = [];
|
||||||
|
|
||||||
@ -66,7 +69,7 @@ class Set implements \IteratorAggregate
|
|||||||
*
|
*
|
||||||
* @return Set[]
|
* @return Set[]
|
||||||
*/
|
*/
|
||||||
public static function power(Set $a) : array
|
public static function power(self $a): array
|
||||||
{
|
{
|
||||||
$power = [new self()];
|
$power = [new self()];
|
||||||
|
|
||||||
@ -79,24 +82,10 @@ class Set implements \IteratorAggregate
|
|||||||
return $power;
|
return $power;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes duplicates and rewrites index.
|
|
||||||
*
|
|
||||||
* @param string[]|int[]|float[] $elements
|
|
||||||
*
|
|
||||||
* @return string[]|int[]|float[]
|
|
||||||
*/
|
|
||||||
private static function sanitize(array $elements) : array
|
|
||||||
{
|
|
||||||
sort($elements, SORT_ASC);
|
|
||||||
|
|
||||||
return array_values(array_unique($elements, SORT_ASC));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|int|float $element
|
* @param string|int|float $element
|
||||||
*/
|
*/
|
||||||
public function add($element) : Set
|
public function add($element): self
|
||||||
{
|
{
|
||||||
return $this->addAll([$element]);
|
return $this->addAll([$element]);
|
||||||
}
|
}
|
||||||
@ -104,7 +93,7 @@ class Set implements \IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* @param string[]|int[]|float[] $elements
|
* @param string[]|int[]|float[] $elements
|
||||||
*/
|
*/
|
||||||
public function addAll(array $elements) : Set
|
public function addAll(array $elements): self
|
||||||
{
|
{
|
||||||
$this->elements = self::sanitize(array_merge($this->elements, $elements));
|
$this->elements = self::sanitize(array_merge($this->elements, $elements));
|
||||||
|
|
||||||
@ -114,7 +103,7 @@ class Set implements \IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* @param string|int|float $element
|
* @param string|int|float $element
|
||||||
*/
|
*/
|
||||||
public function remove($element) : Set
|
public function remove($element): self
|
||||||
{
|
{
|
||||||
return $this->removeAll([$element]);
|
return $this->removeAll([$element]);
|
||||||
}
|
}
|
||||||
@ -122,7 +111,7 @@ class Set implements \IteratorAggregate
|
|||||||
/**
|
/**
|
||||||
* @param string[]|int[]|float[] $elements
|
* @param string[]|int[]|float[] $elements
|
||||||
*/
|
*/
|
||||||
public function removeAll(array $elements) : Set
|
public function removeAll(array $elements): self
|
||||||
{
|
{
|
||||||
$this->elements = self::sanitize(array_diff($this->elements, $elements));
|
$this->elements = self::sanitize(array_diff($this->elements, $elements));
|
||||||
|
|
||||||
@ -153,9 +142,9 @@ class Set implements \IteratorAggregate
|
|||||||
return $this->elements;
|
return $this->elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIterator() : \ArrayIterator
|
public function getIterator(): ArrayIterator
|
||||||
{
|
{
|
||||||
return new \ArrayIterator($this->elements);
|
return new ArrayIterator($this->elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isEmpty(): bool
|
public function isEmpty(): bool
|
||||||
@ -167,4 +156,18 @@ class Set implements \IteratorAggregate
|
|||||||
{
|
{
|
||||||
return count($this->elements);
|
return count($this->elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes duplicates and rewrites index.
|
||||||
|
*
|
||||||
|
* @param string[]|int[]|float[] $elements
|
||||||
|
*
|
||||||
|
* @return string[]|int[]|float[]
|
||||||
|
*/
|
||||||
|
private static function sanitize(array $elements): array
|
||||||
|
{
|
||||||
|
sort($elements, SORT_ASC);
|
||||||
|
|
||||||
|
return array_values(array_unique($elements, SORT_ASC));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Phpml\Math\Statistic;
|
namespace Phpml\Math\Statistic;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Phpml\Exception\InvalidArgumentException;
|
use Phpml\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
class Covariance
|
class Covariance
|
||||||
@ -63,7 +64,7 @@ class Covariance
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) {
|
if ($i < 0 || $k < 0 || $i >= $n || $k >= $n) {
|
||||||
throw new \Exception('Given indices i and k do not match with the dimensionality of data');
|
throw new Exception('Given indices i and k do not match with the dimensionality of data');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($meanX === null || $meanY === null) {
|
if ($meanX === null || $meanY === null) {
|
||||||
@ -92,10 +93,12 @@ class Covariance
|
|||||||
if ($index == $i) {
|
if ($index == $i) {
|
||||||
$val[0] = $col - $meanX;
|
$val[0] = $col - $meanX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($index == $k) {
|
if ($index == $k) {
|
||||||
$val[1] = $col - $meanY;
|
$val[1] = $col - $meanY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$sum += $val[0] * $val[1];
|
$sum += $val[0] * $val[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class Mean
|
|||||||
sort($numbers, SORT_NUMERIC);
|
sort($numbers, SORT_NUMERIC);
|
||||||
$median = $numbers[$middleIndex];
|
$median = $numbers[$middleIndex];
|
||||||
|
|
||||||
if (0 === $count % 2) {
|
if ($count % 2 === 0) {
|
||||||
$median = ($median + $numbers[$middleIndex - 1]) / 2;
|
$median = ($median + $numbers[$middleIndex - 1]) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ class ClassificationReport
|
|||||||
$this->average[$metric] = 0.0;
|
$this->average[$metric] = 0.0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->average[$metric] = array_sum($values) / count($values);
|
$this->average[$metric] = array_sum($values) / count($values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,7 +103,8 @@ class ClassificationReport
|
|||||||
*/
|
*/
|
||||||
private function computePrecision(int $truePositive, int $falsePositive)
|
private function computePrecision(int $truePositive, int $falsePositive)
|
||||||
{
|
{
|
||||||
if (0 == ($divider = $truePositive + $falsePositive)) {
|
$divider = $truePositive + $falsePositive;
|
||||||
|
if ($divider == 0) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +116,8 @@ class ClassificationReport
|
|||||||
*/
|
*/
|
||||||
private function computeRecall(int $truePositive, int $falseNegative)
|
private function computeRecall(int $truePositive, int $falseNegative)
|
||||||
{
|
{
|
||||||
if (0 == ($divider = $truePositive + $falseNegative)) {
|
$divider = $truePositive + $falseNegative;
|
||||||
|
if ($divider == 0) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +126,8 @@ class ClassificationReport
|
|||||||
|
|
||||||
private function computeF1Score(float $precision, float $recall): float
|
private function computeF1Score(float $precision, float $recall): float
|
||||||
{
|
{
|
||||||
if (0 == ($divider = $precision + $recall)) {
|
$divider = $precision + $recall;
|
||||||
|
if ($divider == 0) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,20 +28,6 @@ class Layer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ActivationFunction|null $activationFunction
|
|
||||||
*
|
|
||||||
* @return Neuron
|
|
||||||
*/
|
|
||||||
private function createNode(string $nodeClass, ?ActivationFunction $activationFunction = null)
|
|
||||||
{
|
|
||||||
if (Neuron::class == $nodeClass) {
|
|
||||||
return new Neuron($activationFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new $nodeClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addNode(Node $node): void
|
public function addNode(Node $node): void
|
||||||
{
|
{
|
||||||
$this->nodes[] = $node;
|
$this->nodes[] = $node;
|
||||||
@ -54,4 +40,16 @@ class Layer
|
|||||||
{
|
{
|
||||||
return $this->nodes;
|
return $this->nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Neuron
|
||||||
|
*/
|
||||||
|
private function createNode(string $nodeClass, ?ActivationFunction $activationFunction = null): Node
|
||||||
|
{
|
||||||
|
if ($nodeClass == Neuron::class) {
|
||||||
|
return new Neuron($activationFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $nodeClass();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,9 @@ interface Network
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param mixed $input
|
* @param mixed $input
|
||||||
*
|
|
||||||
* @return self
|
|
||||||
*/
|
*/
|
||||||
public function setInput($input);
|
public function setInput($input): self;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getOutput(): array;
|
public function getOutput(): array;
|
||||||
|
|
||||||
public function addLayer(Layer $layer);
|
public function addLayer(Layer $layer);
|
||||||
|
@ -14,7 +14,7 @@ abstract class LayeredNetwork implements Network
|
|||||||
/**
|
/**
|
||||||
* @var Layer[]
|
* @var Layer[]
|
||||||
*/
|
*/
|
||||||
protected $layers;
|
protected $layers = [];
|
||||||
|
|
||||||
public function addLayer(Layer $layer): void
|
public function addLayer(Layer $layer): void
|
||||||
{
|
{
|
||||||
@ -54,7 +54,7 @@ abstract class LayeredNetwork implements Network
|
|||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setInput($input)
|
public function setInput($input): Network
|
||||||
{
|
{
|
||||||
$firstLayer = $this->layers[0];
|
$firstLayer = $this->layers[0];
|
||||||
|
|
||||||
|
@ -20,41 +20,36 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator,
|
|||||||
{
|
{
|
||||||
use Predictable;
|
use Predictable;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $inputLayerFeatures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $hiddenLayers;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $classes = [];
|
protected $classes = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $iterations;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ActivationFunction
|
* @var ActivationFunction
|
||||||
*/
|
*/
|
||||||
protected $activationFunction;
|
protected $activationFunction;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var float
|
|
||||||
*/
|
|
||||||
private $learningRate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Backpropagation
|
* @var Backpropagation
|
||||||
*/
|
*/
|
||||||
protected $backpropagation = null;
|
protected $backpropagation = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $inputLayerFeatures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $hiddenLayers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $learningRate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
@ -78,18 +73,6 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator,
|
|||||||
$this->initNetwork();
|
$this->initNetwork();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function initNetwork(): void
|
|
||||||
{
|
|
||||||
$this->addInputLayer($this->inputLayerFeatures);
|
|
||||||
$this->addNeuronLayers($this->hiddenLayers, $this->activationFunction);
|
|
||||||
$this->addNeuronLayers([count($this->classes)], $this->activationFunction);
|
|
||||||
|
|
||||||
$this->addBiasNodes();
|
|
||||||
$this->generateSynapses();
|
|
||||||
|
|
||||||
$this->backpropagation = new Backpropagation($this->learningRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function train(array $samples, array $targets): void
|
public function train(array $samples, array $targets): void
|
||||||
{
|
{
|
||||||
$this->reset();
|
$this->reset();
|
||||||
@ -127,6 +110,18 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator,
|
|||||||
$this->removeLayers();
|
$this->removeLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function initNetwork(): void
|
||||||
|
{
|
||||||
|
$this->addInputLayer($this->inputLayerFeatures);
|
||||||
|
$this->addNeuronLayers($this->hiddenLayers, $this->activationFunction);
|
||||||
|
$this->addNeuronLayers([count($this->classes)], $this->activationFunction);
|
||||||
|
|
||||||
|
$this->addBiasNodes();
|
||||||
|
$this->generateSynapses();
|
||||||
|
|
||||||
|
$this->backpropagation = new Backpropagation($this->learningRate);
|
||||||
|
}
|
||||||
|
|
||||||
private function addInputLayer(int $nodes): void
|
private function addInputLayer(int $nodes): void
|
||||||
{
|
{
|
||||||
$this->addLayer(new Layer($nodes, Input::class));
|
$this->addLayer(new Layer($nodes, Input::class));
|
||||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Phpml\NeuralNetwork\Node;
|
namespace Phpml\NeuralNetwork\Node;
|
||||||
|
|
||||||
use Phpml\NeuralNetwork\ActivationFunction;
|
use Phpml\NeuralNetwork\ActivationFunction;
|
||||||
|
use Phpml\NeuralNetwork\ActivationFunction\Sigmoid;
|
||||||
use Phpml\NeuralNetwork\Node;
|
use Phpml\NeuralNetwork\Node;
|
||||||
use Phpml\NeuralNetwork\Node\Neuron\Synapse;
|
use Phpml\NeuralNetwork\Node\Neuron\Synapse;
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ class Neuron implements Node
|
|||||||
/**
|
/**
|
||||||
* @var Synapse[]
|
* @var Synapse[]
|
||||||
*/
|
*/
|
||||||
protected $synapses;
|
protected $synapses = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ActivationFunction
|
* @var ActivationFunction
|
||||||
@ -27,7 +28,7 @@ class Neuron implements Node
|
|||||||
|
|
||||||
public function __construct(?ActivationFunction $activationFunction = null)
|
public function __construct(?ActivationFunction $activationFunction = null)
|
||||||
{
|
{
|
||||||
$this->activationFunction = $activationFunction ?: new ActivationFunction\Sigmoid();
|
$this->activationFunction = $activationFunction ?: new Sigmoid();
|
||||||
$this->synapses = [];
|
$this->synapses = [];
|
||||||
$this->output = 0;
|
$this->output = 0;
|
||||||
}
|
}
|
||||||
@ -47,7 +48,7 @@ class Neuron implements Node
|
|||||||
|
|
||||||
public function getOutput(): float
|
public function getOutput(): float
|
||||||
{
|
{
|
||||||
if (0 === $this->output) {
|
if ($this->output === 0) {
|
||||||
$sum = 0;
|
$sum = 0;
|
||||||
foreach ($this->synapses as $synapse) {
|
foreach ($this->synapses as $synapse) {
|
||||||
$sum += $synapse->getOutput();
|
$sum += $synapse->getOutput();
|
||||||
|
@ -27,11 +27,6 @@ class Synapse
|
|||||||
$this->weight = $weight ?: $this->generateRandomWeight();
|
$this->weight = $weight ?: $this->generateRandomWeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generateRandomWeight() : float
|
|
||||||
{
|
|
||||||
return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOutput(): float
|
public function getOutput(): float
|
||||||
{
|
{
|
||||||
return $this->weight * $this->node->getOutput();
|
return $this->weight * $this->node->getOutput();
|
||||||
@ -51,4 +46,9 @@ class Synapse
|
|||||||
{
|
{
|
||||||
return $this->node;
|
return $this->node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function generateRandomWeight(): float
|
||||||
|
{
|
||||||
|
return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ class Backpropagation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->prevSigmas = $this->sigmas;
|
$this->prevSigmas = $this->sigmas;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ class Backpropagation
|
|||||||
if ($targetClass === $key) {
|
if ($targetClass === $key) {
|
||||||
$value = 1;
|
$value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sigma *= ($value - $neuronOutput);
|
$sigma *= ($value - $neuronOutput);
|
||||||
} else {
|
} else {
|
||||||
$sigma *= $this->getPrevSigma($neuron);
|
$sigma *= $this->getPrevSigma($neuron);
|
||||||
|
@ -9,7 +9,7 @@ class Pipeline implements Estimator
|
|||||||
/**
|
/**
|
||||||
* @var array|Transformer[]
|
* @var array|Transformer[]
|
||||||
*/
|
*/
|
||||||
private $transformers;
|
private $transformers = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Estimator
|
* @var Estimator
|
||||||
|
@ -9,6 +9,7 @@ use Phpml\Preprocessing\Imputer\Strategy;
|
|||||||
class Imputer implements Preprocessor
|
class Imputer implements Preprocessor
|
||||||
{
|
{
|
||||||
public const AXIS_COLUMN = 0;
|
public const AXIS_COLUMN = 0;
|
||||||
|
|
||||||
public const AXIS_ROW = 1;
|
public const AXIS_ROW = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,7 +67,7 @@ class Imputer implements Preprocessor
|
|||||||
|
|
||||||
private function getAxis(int $column, array $currentSample): array
|
private function getAxis(int $column, array $currentSample): array
|
||||||
{
|
{
|
||||||
if (self::AXIS_ROW === $this->axis) {
|
if ($this->axis === self::AXIS_ROW) {
|
||||||
return array_diff($currentSample, [$this->missingValue]);
|
return array_diff($currentSample, [$this->missingValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@ use Phpml\Math\Statistic\StandardDeviation;
|
|||||||
class Normalizer implements Preprocessor
|
class Normalizer implements Preprocessor
|
||||||
{
|
{
|
||||||
public const NORM_L1 = 1;
|
public const NORM_L1 = 1;
|
||||||
|
|
||||||
public const NORM_L2 = 2;
|
public const NORM_L2 = 2;
|
||||||
|
|
||||||
public const NORM_STD = 3;
|
public const NORM_STD = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,12 +29,12 @@ class Normalizer implements Preprocessor
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $std;
|
private $std = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $mean;
|
private $mean = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws NormalizerException
|
* @throws NormalizerException
|
||||||
@ -69,7 +71,7 @@ class Normalizer implements Preprocessor
|
|||||||
$methods = [
|
$methods = [
|
||||||
self::NORM_L1 => 'normalizeL1',
|
self::NORM_L1 => 'normalizeL1',
|
||||||
self::NORM_L2 => 'normalizeL2',
|
self::NORM_L2 => 'normalizeL2',
|
||||||
self::NORM_STD => 'normalizeSTD'
|
self::NORM_STD => 'normalizeSTD',
|
||||||
];
|
];
|
||||||
$method = $methods[$this->norm];
|
$method = $methods[$this->norm];
|
||||||
|
|
||||||
@ -87,7 +89,7 @@ class Normalizer implements Preprocessor
|
|||||||
$norm1 += abs($feature);
|
$norm1 += abs($feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 == $norm1) {
|
if ($norm1 == 0) {
|
||||||
$count = count($sample);
|
$count = count($sample);
|
||||||
$sample = array_fill(0, $count, 1.0 / $count);
|
$sample = array_fill(0, $count, 1.0 / $count);
|
||||||
} else {
|
} else {
|
||||||
@ -103,9 +105,10 @@ class Normalizer implements Preprocessor
|
|||||||
foreach ($sample as $feature) {
|
foreach ($sample as $feature) {
|
||||||
$norm2 += $feature * $feature;
|
$norm2 += $feature * $feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
$norm2 = sqrt((float) $norm2);
|
$norm2 = sqrt((float) $norm2);
|
||||||
|
|
||||||
if (0 == $norm2) {
|
if ($norm2 == 0) {
|
||||||
$sample = array_fill(0, count($sample), 1);
|
$sample = array_fill(0, count($sample), 1);
|
||||||
} else {
|
} else {
|
||||||
foreach ($sample as &$feature) {
|
foreach ($sample as &$feature) {
|
||||||
|
@ -28,7 +28,7 @@ class LeastSquares implements Regression
|
|||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $coefficients;
|
private $coefficients = [];
|
||||||
|
|
||||||
public function train(array $samples, array $targets): void
|
public function train(array $samples, array $targets): void
|
||||||
{
|
{
|
||||||
|
@ -167,7 +167,7 @@ class SupportVectorMachine
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array|string
|
||||||
*/
|
*/
|
||||||
public function predict(array $samples)
|
public function predict(array $samples)
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ namespace tests\Phpml\Classification;
|
|||||||
use Phpml\Association\Apriori;
|
use Phpml\Association\Apriori;
|
||||||
use Phpml\ModelManager;
|
use Phpml\ModelManager;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
class AprioriTest extends TestCase
|
class AprioriTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -172,7 +173,6 @@ class AprioriTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* Invokes objects method. Private/protected will be set accessible.
|
* Invokes objects method. Private/protected will be set accessible.
|
||||||
*
|
*
|
||||||
* @param object &$object Instantiated object to be called on
|
|
||||||
* @param string $method Method name to be called
|
* @param string $method Method name to be called
|
||||||
* @param array $params Array of params to be passed
|
* @param array $params Array of params to be passed
|
||||||
*
|
*
|
||||||
@ -180,7 +180,7 @@ class AprioriTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function invoke(&$object, $method, array $params = [])
|
public function invoke(&$object, $method, array $params = [])
|
||||||
{
|
{
|
||||||
$reflection = new \ReflectionClass(get_class($object));
|
$reflection = new ReflectionClass(get_class($object));
|
||||||
$method = $reflection->getMethod($method);
|
$method = $reflection->getMethod($method);
|
||||||
$method->setAccessible(true);
|
$method->setAccessible(true);
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ class AprioriTest extends TestCase
|
|||||||
$testSamples = [['alpha', 'epsilon'], ['beta', 'theta']];
|
$testSamples = [['alpha', 'epsilon'], ['beta', 'theta']];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'apriori-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'apriori-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -24,7 +24,7 @@ class DecisionTreeTest extends TestCase
|
|||||||
['sunny', 75, 70, 'true', 'Play'],
|
['sunny', 75, 70, 'true', 'Play'],
|
||||||
['overcast', 72, 90, 'true', 'Play'],
|
['overcast', 72, 90, 'true', 'Play'],
|
||||||
['overcast', 81, 75, 'false', 'Play'],
|
['overcast', 81, 75, 'false', 'Play'],
|
||||||
['rain', 71, 80, 'true', 'Dont_play']
|
['rain', 71, 80, 'true', 'Dont_play'],
|
||||||
];
|
];
|
||||||
|
|
||||||
private $extraData = [
|
private $extraData = [
|
||||||
@ -32,16 +32,6 @@ class DecisionTreeTest extends TestCase
|
|||||||
['scorching', 100, 93, 'true', 'Dont_play'],
|
['scorching', 100, 93, 'true', 'Dont_play'],
|
||||||
];
|
];
|
||||||
|
|
||||||
private function getData($input)
|
|
||||||
{
|
|
||||||
$targets = array_column($input, 4);
|
|
||||||
array_walk($input, function (&$v): void {
|
|
||||||
array_splice($v, 4, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
return [$input, $targets];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testPredictSingleSample()
|
public function testPredictSingleSample()
|
||||||
{
|
{
|
||||||
[$data, $targets] = $this->getData($this->data);
|
[$data, $targets] = $this->getData($this->data);
|
||||||
@ -68,7 +58,7 @@ class DecisionTreeTest extends TestCase
|
|||||||
$testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']];
|
$testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'decision-tree-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'decision-tree-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
@ -83,6 +73,16 @@ class DecisionTreeTest extends TestCase
|
|||||||
[$data, $targets] = $this->getData($this->data);
|
[$data, $targets] = $this->getData($this->data);
|
||||||
$classifier = new DecisionTree(5);
|
$classifier = new DecisionTree(5);
|
||||||
$classifier->train($data, $targets);
|
$classifier->train($data, $targets);
|
||||||
$this->assertTrue(5 >= $classifier->actualDepth);
|
$this->assertTrue($classifier->actualDepth <= 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getData($input)
|
||||||
|
{
|
||||||
|
$targets = array_column($input, 4);
|
||||||
|
array_walk($input, function (&$v): void {
|
||||||
|
array_splice($v, 4, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [$input, $targets];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class AdaBoostTest extends TestCase
|
|||||||
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'adaboost-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'adaboost-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -26,7 +26,7 @@ class BaggingTest extends TestCase
|
|||||||
['sunny', 75, 70, 'true', 'Play'],
|
['sunny', 75, 70, 'true', 'Play'],
|
||||||
['overcast', 72, 90, 'true', 'Play'],
|
['overcast', 72, 90, 'true', 'Play'],
|
||||||
['overcast', 81, 75, 'false', 'Play'],
|
['overcast', 81, 75, 'false', 'Play'],
|
||||||
['rain', 71, 80, 'true', 'Dont_play']
|
['rain', 71, 80, 'true', 'Dont_play'],
|
||||||
];
|
];
|
||||||
|
|
||||||
private $extraData = [
|
private $extraData = [
|
||||||
@ -61,7 +61,7 @@ class BaggingTest extends TestCase
|
|||||||
$testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']];
|
$testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'bagging-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'bagging-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
@ -105,7 +105,7 @@ class BaggingTest extends TestCase
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
DecisionTree::class => ['depth' => 5],
|
DecisionTree::class => ['depth' => 5],
|
||||||
NaiveBayes::class => []
|
NaiveBayes::class => [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +117,7 @@ class BaggingTest extends TestCase
|
|||||||
for ($i = 0; $i < 20; ++$i) {
|
for ($i = 0; $i < 20; ++$i) {
|
||||||
$populated = array_merge($populated, $input);
|
$populated = array_merge($populated, $input);
|
||||||
}
|
}
|
||||||
|
|
||||||
shuffle($populated);
|
shuffle($populated);
|
||||||
$targets = array_column($populated, 4);
|
$targets = array_column($populated, 4);
|
||||||
array_walk($populated, function (&$v): void {
|
array_walk($populated, function (&$v): void {
|
||||||
|
@ -7,9 +7,20 @@ namespace tests\Phpml\Classification\Ensemble;
|
|||||||
use Phpml\Classification\DecisionTree;
|
use Phpml\Classification\DecisionTree;
|
||||||
use Phpml\Classification\Ensemble\RandomForest;
|
use Phpml\Classification\Ensemble\RandomForest;
|
||||||
use Phpml\Classification\NaiveBayes;
|
use Phpml\Classification\NaiveBayes;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class RandomForestTest extends BaggingTest
|
class RandomForestTest extends BaggingTest
|
||||||
{
|
{
|
||||||
|
public function testOtherBaseClassifier(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$classifier = new RandomForest();
|
||||||
|
$classifier->setClassifer(NaiveBayes::class);
|
||||||
|
$this->assertEquals(0, 1);
|
||||||
|
} catch (Throwable $ex) {
|
||||||
|
$this->assertEquals(1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
protected function getClassifier($numBaseClassifiers = 50)
|
protected function getClassifier($numBaseClassifiers = 50)
|
||||||
{
|
{
|
||||||
$classifier = new RandomForest($numBaseClassifiers);
|
$classifier = new RandomForest($numBaseClassifiers);
|
||||||
@ -22,15 +33,4 @@ class RandomForestTest extends BaggingTest
|
|||||||
{
|
{
|
||||||
return [DecisionTree::class => ['depth' => 5]];
|
return [DecisionTree::class => ['depth' => 5]];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testOtherBaseClassifier(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$classifier = new RandomForest();
|
|
||||||
$classifier->setClassifer(NaiveBayes::class);
|
|
||||||
$this->assertEquals(0, 1);
|
|
||||||
} catch (\Exception $ex) {
|
|
||||||
$this->assertEquals(1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ class KNearestNeighborsTest extends TestCase
|
|||||||
$classifier->train($trainSamples, $trainLabels);
|
$classifier->train($trainSamples, $trainLabels);
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'knearest-neighbors-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'knearest-neighbors-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -35,7 +35,7 @@ class AdalineTest extends TestCase
|
|||||||
$samples = [
|
$samples = [
|
||||||
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
||||||
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
||||||
[3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle
|
[3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle
|
||||||
];
|
];
|
||||||
$targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
$targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ class AdalineTest extends TestCase
|
|||||||
$samples = [
|
$samples = [
|
||||||
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
||||||
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
||||||
[3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle
|
[3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle
|
||||||
];
|
];
|
||||||
$targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1];
|
$targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1];
|
||||||
$classifier->train($samples, $targets);
|
$classifier->train($samples, $targets);
|
||||||
@ -74,7 +74,7 @@ class AdalineTest extends TestCase
|
|||||||
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'adaline-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'adaline-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -40,7 +40,7 @@ class DecisionStumpTest extends TestCase
|
|||||||
$samples = [
|
$samples = [
|
||||||
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
||||||
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
||||||
[3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle
|
[3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle
|
||||||
];
|
];
|
||||||
$targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
$targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ class DecisionStumpTest extends TestCase
|
|||||||
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'dstump-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'dstump-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -37,7 +37,7 @@ class PerceptronTest extends TestCase
|
|||||||
$samples = [
|
$samples = [
|
||||||
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
||||||
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
||||||
[3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle
|
[3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle
|
||||||
];
|
];
|
||||||
$targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
$targets = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2];
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class PerceptronTest extends TestCase
|
|||||||
$samples = [
|
$samples = [
|
||||||
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
[0, 0], [0, 1], [1, 0], [1, 1], // First group : a cluster at bottom-left corner in 2D
|
||||||
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
[5, 5], [6, 5], [5, 6], [7, 5], // Second group: another cluster at the middle-right
|
||||||
[3, 10],[3, 10],[3, 8], [3, 9] // Third group : cluster at the top-middle
|
[3, 10], [3, 10], [3, 8], [3, 9], // Third group : cluster at the top-middle
|
||||||
];
|
];
|
||||||
$targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1];
|
$targets = [2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1];
|
||||||
$classifier->train($samples, $targets);
|
$classifier->train($samples, $targets);
|
||||||
@ -77,7 +77,7 @@ class PerceptronTest extends TestCase
|
|||||||
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
$testSamples = [[0, 1], [1, 1], [0.2, 0.1]];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'perceptron-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -150,7 +150,7 @@ class MLPClassifierTest extends TestCase
|
|||||||
$testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]];
|
$testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]];
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'perceptron-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'perceptron-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -59,7 +59,7 @@ class NaiveBayesTest extends TestCase
|
|||||||
$classifier->train($trainSamples, $trainLabels);
|
$classifier->train($trainSamples, $trainLabels);
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'naive-bayes-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'naive-bayes-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -57,7 +57,7 @@ class SVCTest extends TestCase
|
|||||||
$classifier->train($trainSamples, $trainLabels);
|
$classifier->train($trainSamples, $trainLabels);
|
||||||
$predicted = $classifier->predict($testSamples);
|
$predicted = $classifier->predict($testSamples);
|
||||||
|
|
||||||
$filename = 'svc-test-'.rand(100, 999).'-'.uniqid();
|
$filename = 'svc-test-'.random_int(100, 999).'-'.uniqid();
|
||||||
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
$filepath = tempnam(sys_get_temp_dir(), $filename);
|
||||||
$modelManager = new ModelManager();
|
$modelManager = new ModelManager();
|
||||||
$modelManager->saveToFile($classifier, $filepath);
|
$modelManager->saveToFile($classifier, $filepath);
|
||||||
|
@ -34,10 +34,25 @@ class DBSCANTest extends TestCase
|
|||||||
|
|
||||||
public function testDBSCANSamplesClusteringAssociative(): void
|
public function testDBSCANSamplesClusteringAssociative(): void
|
||||||
{
|
{
|
||||||
$samples = ['a' => [1, 1], 'b' => [9, 9], 'c' => [1, 2], 'd' => [9, 8], 'e' => [7, 7], 'f' => [8, 7]];
|
$samples = [
|
||||||
|
'a' => [1, 1],
|
||||||
|
'b' => [9, 9],
|
||||||
|
'c' => [1, 2],
|
||||||
|
'd' => [9, 8],
|
||||||
|
'e' => [7, 7],
|
||||||
|
'f' => [8, 7],
|
||||||
|
];
|
||||||
$clustered = [
|
$clustered = [
|
||||||
['a' => [1, 1], 'c' => [1, 2]],
|
[
|
||||||
['b' => [9, 9], 'd' => [9, 8], 'e' => [7, 7], 'f' => [8, 7]],
|
'a' => [1, 1],
|
||||||
|
'c' => [1, 2],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'b' => [9, 9],
|
||||||
|
'd' => [9, 8],
|
||||||
|
'e' => [7, 7],
|
||||||
|
'f' => [8, 7],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$dbscan = new DBSCAN($epsilon = 3, $minSamples = 2);
|
$dbscan = new DBSCAN($epsilon = 3, $minSamples = 2);
|
||||||
|
@ -20,6 +20,7 @@ class FuzzyCMeansTest extends TestCase
|
|||||||
unset($samples[$index]);
|
unset($samples[$index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertCount(0, $samples);
|
$this->assertCount(0, $samples);
|
||||||
|
|
||||||
return $fcm;
|
return $fcm;
|
||||||
@ -35,6 +36,7 @@ class FuzzyCMeansTest extends TestCase
|
|||||||
foreach ($matrix as $row) {
|
foreach ($matrix as $row) {
|
||||||
$this->assertCount($sampleCount, $row);
|
$this->assertCount($sampleCount, $row);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transpose of the matrix
|
// Transpose of the matrix
|
||||||
array_unshift($matrix, null);
|
array_unshift($matrix, null);
|
||||||
$matrix = call_user_func_array('array_map', $matrix);
|
$matrix = call_user_func_array('array_map', $matrix);
|
||||||
|
@ -23,6 +23,7 @@ class KMeansTest extends TestCase
|
|||||||
unset($samples[$index]);
|
unset($samples[$index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertCount(0, $samples);
|
$this->assertCount(0, $samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class KernelPCATest extends TestCase
|
|||||||
[1., 2.5], [1., 2.7], [1., 3.], [1, 3],
|
[1., 2.5], [1., 2.7], [1., 3.], [1, 3],
|
||||||
[1, 2], [1.5, 2], [1.5, 2.2], [1.3, 1.7],
|
[1, 2], [1.5, 2], [1.5, 2.2], [1.3, 1.7],
|
||||||
[1.7, 1.3], [1.5, 1.5], [1.5, 1.6], [1.6, 2],
|
[1.7, 1.3], [1.5, 1.5], [1.5, 1.6], [1.6, 2],
|
||||||
[1.7,2.1], [1.3,1.3], [1.3,2.2], [1.4,2.4]
|
[1.7, 2.1], [1.3, 1.3], [1.3, 2.2], [1.4, 2.4],
|
||||||
];
|
];
|
||||||
$transformed = [
|
$transformed = [
|
||||||
[0.016485613899708], [-0.089805657741674], [-0.088695974245924], [-0.069761503810802],
|
[0.016485613899708], [-0.089805657741674], [-0.088695974245924], [-0.069761503810802],
|
||||||
@ -29,7 +29,7 @@ class KernelPCATest extends TestCase
|
|||||||
[-0.10098315410297], [-0.15617881000654], [-0.21266832077299], [-0.21266832077299],
|
[-0.10098315410297], [-0.15617881000654], [-0.21266832077299], [-0.21266832077299],
|
||||||
[-0.039234518840831], [0.40858295942991], [0.40110375047242], [-0.10555116296691],
|
[-0.039234518840831], [0.40858295942991], [0.40110375047242], [-0.10555116296691],
|
||||||
[-0.13128352866095], [-0.20865959471756], [-0.17531601535848], [0.4240660966961],
|
[-0.13128352866095], [-0.20865959471756], [-0.17531601535848], [0.4240660966961],
|
||||||
[0.36351946685163], [-0.14334173054136], [0.22454914091011], [0.15035027480881]];
|
[0.36351946685163], [-0.14334173054136], [0.22454914091011], [0.15035027480881], ];
|
||||||
|
|
||||||
$kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15);
|
$kpca = new KernelPCA(KernelPCA::KERNEL_RBF, null, 1, 15);
|
||||||
$reducedData = $kpca->fit($data);
|
$reducedData = $kpca->fit($data);
|
||||||
|
@ -28,7 +28,7 @@ class LDATest extends TestCase
|
|||||||
[4.7, 3.2, 1.3, 0.2],
|
[4.7, 3.2, 1.3, 0.2],
|
||||||
[6.5, 3.0, 5.2, 2.0],
|
[6.5, 3.0, 5.2, 2.0],
|
||||||
[6.2, 3.4, 5.4, 2.3],
|
[6.2, 3.4, 5.4, 2.3],
|
||||||
[5.9, 3.0, 5.1, 1.8]
|
[5.9, 3.0, 5.1, 1.8],
|
||||||
];
|
];
|
||||||
$transformed2 = [
|
$transformed2 = [
|
||||||
[-1.4922092756753, 1.9047102045574],
|
[-1.4922092756753, 1.9047102045574],
|
||||||
@ -36,7 +36,7 @@ class LDATest extends TestCase
|
|||||||
[-1.3487505965419, 1.749846351699],
|
[-1.3487505965419, 1.749846351699],
|
||||||
[1.7759343101456, 2.0371552314006],
|
[1.7759343101456, 2.0371552314006],
|
||||||
[2.0059819019159, 2.4493123003226],
|
[2.0059819019159, 2.4493123003226],
|
||||||
[1.701474913008, 1.9037880473772]
|
[1.701474913008, 1.9037880473772],
|
||||||
];
|
];
|
||||||
|
|
||||||
$control = [];
|
$control = [];
|
||||||
|
@ -26,12 +26,12 @@ class PCATest extends TestCase
|
|||||||
[2.0, 1.6],
|
[2.0, 1.6],
|
||||||
[1.0, 1.1],
|
[1.0, 1.1],
|
||||||
[1.5, 1.6],
|
[1.5, 1.6],
|
||||||
[1.1, 0.9]
|
[1.1, 0.9],
|
||||||
];
|
];
|
||||||
$transformed = [
|
$transformed = [
|
||||||
[-0.827970186], [1.77758033], [-0.992197494],
|
[-0.827970186], [1.77758033], [-0.992197494],
|
||||||
[-0.274210416], [-1.67580142], [-0.912949103], [0.0991094375],
|
[-0.274210416], [-1.67580142], [-0.912949103], [0.0991094375],
|
||||||
[1.14457216], [0.438046137], [1.22382056]];
|
[1.14457216], [0.438046137], [1.22382056], ];
|
||||||
|
|
||||||
$pca = new PCA(0.90);
|
$pca = new PCA(0.90);
|
||||||
$reducedData = $pca->fit($data);
|
$reducedData = $pca->fit($data);
|
||||||
|
@ -14,13 +14,41 @@ class TfIdfTransformerTest extends TestCase
|
|||||||
// https://en.wikipedia.org/wiki/Tf-idf
|
// https://en.wikipedia.org/wiki/Tf-idf
|
||||||
|
|
||||||
$samples = [
|
$samples = [
|
||||||
[0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 0, 5 => 0],
|
[
|
||||||
[0 => 1, 1 => 1, 2 => 0, 3 => 0, 4 => 2, 5 => 3],
|
0 => 1,
|
||||||
|
1 => 1,
|
||||||
|
2 => 2,
|
||||||
|
3 => 1,
|
||||||
|
4 => 0,
|
||||||
|
5 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 1,
|
||||||
|
1 => 1,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 2,
|
||||||
|
5 => 3,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$tfIdfSamples = [
|
$tfIdfSamples = [
|
||||||
[0 => 0, 1 => 0, 2 => 0.602, 3 => 0.301, 4 => 0, 5 => 0],
|
[
|
||||||
[0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0.602, 5 => 0.903],
|
0 => 0,
|
||||||
|
1 => 0,
|
||||||
|
2 => 0.602,
|
||||||
|
3 => 0.301,
|
||||||
|
4 => 0,
|
||||||
|
5 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 0,
|
||||||
|
1 => 0,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 0.602,
|
||||||
|
5 => 0.903,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$transformer = new TfIdfTransformer($samples);
|
$transformer = new TfIdfTransformer($samples);
|
||||||
|
@ -33,9 +33,42 @@ class TokenCountVectorizerTest extends TestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tokensCounts = [
|
$tokensCounts = [
|
||||||
[0 => 1, 1 => 1, 2 => 2, 3 => 1, 4 => 1, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0],
|
[
|
||||||
[0 => 0, 1 => 1, 2 => 1, 3 => 0, 4 => 0, 5 => 1, 6 => 1, 7 => 0, 8 => 0, 9 => 0],
|
0 => 1,
|
||||||
[0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 1, 6 => 0, 7 => 2, 8 => 1, 9 => 1],
|
1 => 1,
|
||||||
|
2 => 2,
|
||||||
|
3 => 1,
|
||||||
|
4 => 1,
|
||||||
|
5 => 0,
|
||||||
|
6 => 0,
|
||||||
|
7 => 0,
|
||||||
|
8 => 0,
|
||||||
|
9 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
2 => 1,
|
||||||
|
3 => 0,
|
||||||
|
4 => 0,
|
||||||
|
5 => 1,
|
||||||
|
6 => 1,
|
||||||
|
7 => 0,
|
||||||
|
8 => 0,
|
||||||
|
9 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 0,
|
||||||
|
1 => 0,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 0,
|
||||||
|
5 => 1,
|
||||||
|
6 => 0,
|
||||||
|
7 => 2,
|
||||||
|
8 => 1,
|
||||||
|
9 => 1,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer());
|
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer());
|
||||||
@ -66,10 +99,34 @@ class TokenCountVectorizerTest extends TestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tokensCounts = [
|
$tokensCounts = [
|
||||||
[0 => 1, 1 => 1, 2 => 0, 3 => 1, 4 => 1],
|
[
|
||||||
[0 => 1, 1 => 1, 2 => 0, 3 => 1, 4 => 1],
|
0 => 1,
|
||||||
[0 => 0, 1 => 1, 2 => 0, 3 => 1, 4 => 1],
|
1 => 1,
|
||||||
[0 => 0, 1 => 1, 2 => 0, 3 => 1, 4 => 1],
|
2 => 0,
|
||||||
|
3 => 1,
|
||||||
|
4 => 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 1,
|
||||||
|
1 => 1,
|
||||||
|
2 => 0,
|
||||||
|
3 => 1,
|
||||||
|
4 => 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
2 => 0,
|
||||||
|
3 => 1,
|
||||||
|
4 => 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
2 => 0,
|
||||||
|
3 => 1,
|
||||||
|
4 => 1,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 0.5);
|
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 0.5);
|
||||||
@ -88,9 +145,39 @@ class TokenCountVectorizerTest extends TestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tokensCounts = [
|
$tokensCounts = [
|
||||||
[0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0],
|
[
|
||||||
[0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0],
|
0 => 1,
|
||||||
[0 => 1, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0],
|
1 => 0,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 0,
|
||||||
|
5 => 0,
|
||||||
|
6 => 0,
|
||||||
|
7 => 0,
|
||||||
|
8 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 1,
|
||||||
|
1 => 0,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 0,
|
||||||
|
5 => 0,
|
||||||
|
6 => 0,
|
||||||
|
7 => 0,
|
||||||
|
8 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 1,
|
||||||
|
1 => 0,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 0,
|
||||||
|
5 => 0,
|
||||||
|
6 => 0,
|
||||||
|
7 => 0,
|
||||||
|
8 => 0,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 1);
|
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), null, 1);
|
||||||
@ -124,9 +211,36 @@ class TokenCountVectorizerTest extends TestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
$tokensCounts = [
|
$tokensCounts = [
|
||||||
[0 => 1, 1 => 1, 2 => 1, 3 => 1, 4 => 0, 5 => 0, 6 => 0, 7 => 0],
|
[
|
||||||
[0 => 0, 1 => 1, 2 => 0, 3 => 0, 4 => 1, 5 => 1, 6 => 0, 7 => 0],
|
0 => 1,
|
||||||
[0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 1, 5 => 0, 6 => 1, 7 => 1],
|
1 => 1,
|
||||||
|
2 => 1,
|
||||||
|
3 => 1,
|
||||||
|
4 => 0,
|
||||||
|
5 => 0,
|
||||||
|
6 => 0,
|
||||||
|
7 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 1,
|
||||||
|
5 => 1,
|
||||||
|
6 => 0,
|
||||||
|
7 => 0,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0 => 0,
|
||||||
|
1 => 0,
|
||||||
|
2 => 0,
|
||||||
|
3 => 0,
|
||||||
|
4 => 1,
|
||||||
|
5 => 0,
|
||||||
|
6 => 1,
|
||||||
|
7 => 1,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), $stopWords);
|
$vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), $stopWords);
|
||||||
|
@ -31,10 +31,7 @@ class ComparisonTest extends TestCase
|
|||||||
Comparison::compare(1, 1, '~=');
|
Comparison::compare(1, 1, '~=');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function provideData(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function provideData()
|
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
// Greater
|
// Greater
|
||||||
|
@ -19,7 +19,7 @@ class EigenDecompositionTest extends TestCase
|
|||||||
// http://www.cs.otago.ac.nz/cosc453/student_tutorials/principal_components.pdf
|
// http://www.cs.otago.ac.nz/cosc453/student_tutorials/principal_components.pdf
|
||||||
$matrix = [
|
$matrix = [
|
||||||
[0.616555556, 0.615444444],
|
[0.616555556, 0.615444444],
|
||||||
[0.614444444, 0.716555556]
|
[0.614444444, 0.716555556],
|
||||||
];
|
];
|
||||||
$knownEigvalues = [0.0490833989, 1.28402771];
|
$knownEigvalues = [0.0490833989, 1.28402771];
|
||||||
$knownEigvectors = [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]];
|
$knownEigvectors = [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]];
|
||||||
@ -43,7 +43,7 @@ class EigenDecompositionTest extends TestCase
|
|||||||
if ($i > $k) {
|
if ($i > $k) {
|
||||||
$A[$i][$k] = $A[$k][$i];
|
$A[$i][$k] = $A[$k][$i];
|
||||||
} else {
|
} else {
|
||||||
$A[$i][$k] = rand(0, 10);
|
$A[$i][$k] = random_int(0, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,12 +239,12 @@ class MatrixTest extends TestCase
|
|||||||
{
|
{
|
||||||
$array = [
|
$array = [
|
||||||
[1, 1, 1],
|
[1, 1, 1],
|
||||||
[2, 2, 2]
|
[2, 2, 2],
|
||||||
];
|
];
|
||||||
$transposed = [
|
$transposed = [
|
||||||
[1, 2],
|
[1, 2],
|
||||||
[1, 2],
|
[1, 2],
|
||||||
[1, 2]
|
[1, 2],
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->assertEquals($transposed, Matrix::transposeArray($array));
|
$this->assertEquals($transposed, Matrix::transposeArray($array));
|
||||||
|
@ -6,6 +6,7 @@ namespace tests\Phpml\Math;
|
|||||||
|
|
||||||
use Phpml\Math\Product;
|
use Phpml\Math\Product;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class ProductTest extends TestCase
|
class ProductTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -16,6 +17,6 @@ class ProductTest extends TestCase
|
|||||||
$this->assertEquals(8, Product::scalar([2], [4]));
|
$this->assertEquals(8, Product::scalar([2], [4]));
|
||||||
|
|
||||||
//test for non numeric values
|
//test for non numeric values
|
||||||
$this->assertEquals(0, Product::scalar(['', null, [], new \stdClass()], [null]));
|
$this->assertEquals(0, Product::scalar(['', null, [], new stdClass()], [null]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class SetTest extends TestCase
|
|||||||
{
|
{
|
||||||
$union = Set::union(new Set([3, 1]), new Set([3, 2, 2]));
|
$union = Set::union(new Set([3, 1]), new Set([3, 2, 2]));
|
||||||
|
|
||||||
$this->assertInstanceOf('\Phpml\Math\Set', $union);
|
$this->assertInstanceOf(Set::class, $union);
|
||||||
$this->assertEquals(new Set([1, 2, 3]), $union);
|
$this->assertEquals(new Set([1, 2, 3]), $union);
|
||||||
$this->assertEquals(3, $union->cardinality());
|
$this->assertEquals(3, $union->cardinality());
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ class SetTest extends TestCase
|
|||||||
{
|
{
|
||||||
$intersection = Set::intersection(new Set(['C', 'A']), new Set(['B', 'C']));
|
$intersection = Set::intersection(new Set(['C', 'A']), new Set(['B', 'C']));
|
||||||
|
|
||||||
$this->assertInstanceOf('\Phpml\Math\Set', $intersection);
|
$this->assertInstanceOf(Set::class, $intersection);
|
||||||
$this->assertEquals(new Set(['C']), $intersection);
|
$this->assertEquals(new Set(['C']), $intersection);
|
||||||
$this->assertEquals(1, $intersection->cardinality());
|
$this->assertEquals(1, $intersection->cardinality());
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ class SetTest extends TestCase
|
|||||||
{
|
{
|
||||||
$difference = Set::difference(new Set(['C', 'A', 'B']), new Set(['A']));
|
$difference = Set::difference(new Set(['C', 'A', 'B']), new Set(['A']));
|
||||||
|
|
||||||
$this->assertInstanceOf('\Phpml\Math\Set', $difference);
|
$this->assertInstanceOf(Set::class, $difference);
|
||||||
$this->assertEquals(new Set(['B', 'C']), $difference);
|
$this->assertEquals(new Set(['B', 'C']), $difference);
|
||||||
$this->assertEquals(2, $difference->cardinality());
|
$this->assertEquals(2, $difference->cardinality());
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ class CovarianceTest extends TestCase
|
|||||||
];
|
];
|
||||||
$knownCovariance = [
|
$knownCovariance = [
|
||||||
[0.616555556, 0.615444444],
|
[0.616555556, 0.615444444],
|
||||||
[0.615444444, 0.716555556]];
|
[0.615444444, 0.716555556], ];
|
||||||
$x = array_column($matrix, 0);
|
$x = array_column($matrix, 0);
|
||||||
$y = array_column($matrix, 1);
|
$y = array_column($matrix, 1);
|
||||||
|
|
||||||
|
@ -16,11 +16,31 @@ class ClassificationReportTest extends TestCase
|
|||||||
|
|
||||||
$report = new ClassificationReport($labels, $predicted);
|
$report = new ClassificationReport($labels, $predicted);
|
||||||
|
|
||||||
$precision = ['cat' => 0.5, 'ant' => 0.0, 'bird' => 1.0];
|
$precision = [
|
||||||
$recall = ['cat' => 1.0, 'ant' => 0.0, 'bird' => 0.67];
|
'cat' => 0.5,
|
||||||
$f1score = ['cat' => 0.67, 'ant' => 0.0, 'bird' => 0.80];
|
'ant' => 0.0,
|
||||||
$support = ['cat' => 1, 'ant' => 1, 'bird' => 3];
|
'bird' => 1.0,
|
||||||
$average = ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73];
|
];
|
||||||
|
$recall = [
|
||||||
|
'cat' => 1.0,
|
||||||
|
'ant' => 0.0,
|
||||||
|
'bird' => 0.67,
|
||||||
|
];
|
||||||
|
$f1score = [
|
||||||
|
'cat' => 0.67,
|
||||||
|
'ant' => 0.0,
|
||||||
|
'bird' => 0.80,
|
||||||
|
];
|
||||||
|
$support = [
|
||||||
|
'cat' => 1,
|
||||||
|
'ant' => 1,
|
||||||
|
'bird' => 3,
|
||||||
|
];
|
||||||
|
$average = [
|
||||||
|
'precision' => 0.75,
|
||||||
|
'recall' => 0.83,
|
||||||
|
'f1score' => 0.73,
|
||||||
|
];
|
||||||
|
|
||||||
$this->assertEquals($precision, $report->getPrecision(), '', 0.01);
|
$this->assertEquals($precision, $report->getPrecision(), '', 0.01);
|
||||||
$this->assertEquals($recall, $report->getRecall(), '', 0.01);
|
$this->assertEquals($recall, $report->getRecall(), '', 0.01);
|
||||||
@ -36,11 +56,31 @@ class ClassificationReportTest extends TestCase
|
|||||||
|
|
||||||
$report = new ClassificationReport($labels, $predicted);
|
$report = new ClassificationReport($labels, $predicted);
|
||||||
|
|
||||||
$precision = [0 => 0.5, 1 => 0.0, 2 => 1.0];
|
$precision = [
|
||||||
$recall = [0 => 1.0, 1 => 0.0, 2 => 0.67];
|
0 => 0.5,
|
||||||
$f1score = [0 => 0.67, 1 => 0.0, 2 => 0.80];
|
1 => 0.0,
|
||||||
$support = [0 => 1, 1 => 1, 2 => 3];
|
2 => 1.0,
|
||||||
$average = ['precision' => 0.75, 'recall' => 0.83, 'f1score' => 0.73];
|
];
|
||||||
|
$recall = [
|
||||||
|
0 => 1.0,
|
||||||
|
1 => 0.0,
|
||||||
|
2 => 0.67,
|
||||||
|
];
|
||||||
|
$f1score = [
|
||||||
|
0 => 0.67,
|
||||||
|
1 => 0.0,
|
||||||
|
2 => 0.80,
|
||||||
|
];
|
||||||
|
$support = [
|
||||||
|
0 => 1,
|
||||||
|
1 => 1,
|
||||||
|
2 => 3,
|
||||||
|
];
|
||||||
|
$average = [
|
||||||
|
'precision' => 0.75,
|
||||||
|
'recall' => 0.83,
|
||||||
|
'f1score' => 0.73,
|
||||||
|
];
|
||||||
|
|
||||||
$this->assertEquals($precision, $report->getPrecision(), '', 0.01);
|
$this->assertEquals($precision, $report->getPrecision(), '', 0.01);
|
||||||
$this->assertEquals($recall, $report->getRecall(), '', 0.01);
|
$this->assertEquals($recall, $report->getRecall(), '', 0.01);
|
||||||
@ -56,7 +96,10 @@ class ClassificationReportTest extends TestCase
|
|||||||
|
|
||||||
$report = new ClassificationReport($labels, $predicted);
|
$report = new ClassificationReport($labels, $predicted);
|
||||||
|
|
||||||
$this->assertEquals([1 => 0.0, 2 => 0.5], $report->getPrecision(), '', 0.01);
|
$this->assertEquals([
|
||||||
|
1 => 0.0,
|
||||||
|
2 => 0.5,
|
||||||
|
], $report->getPrecision(), '', 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEqualsZero(): void
|
public function testPreventDivideByZeroWhenTruePositiveAndFalseNegativeSumEqualsZero(): void
|
||||||
@ -66,7 +109,11 @@ class ClassificationReportTest extends TestCase
|
|||||||
|
|
||||||
$report = new ClassificationReport($labels, $predicted);
|
$report = new ClassificationReport($labels, $predicted);
|
||||||
|
|
||||||
$this->assertEquals([1 => 0.0, 2 => 1, 3 => 0], $report->getPrecision(), '', 0.01);
|
$this->assertEquals([
|
||||||
|
1 => 0.0,
|
||||||
|
2 => 1,
|
||||||
|
3 => 0,
|
||||||
|
], $report->getPrecision(), '', 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void
|
public function testPreventDividedByZeroWhenPredictedLabelsAllNotMatch(): void
|
||||||
|
@ -19,10 +19,7 @@ class BinaryStepTest extends TestCase
|
|||||||
$this->assertEquals($expected, $binaryStep->compute($value));
|
$this->assertEquals($expected, $binaryStep->compute($value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function binaryStepProvider(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function binaryStepProvider()
|
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[1, 1],
|
[1, 1],
|
||||||
|
@ -19,10 +19,7 @@ class GaussianTest extends TestCase
|
|||||||
$this->assertEquals($expected, $gaussian->compute($value), '', 0.001);
|
$this->assertEquals($expected, $gaussian->compute($value), '', 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function gaussianProvider(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function gaussianProvider()
|
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[0.367, 1],
|
[0.367, 1],
|
||||||
|
@ -19,10 +19,7 @@ class HyperboliTangentTest extends TestCase
|
|||||||
$this->assertEquals($expected, $tanh->compute($value), '', 0.001);
|
$this->assertEquals($expected, $tanh->compute($value), '', 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function tanhProvider(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function tanhProvider()
|
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[1.0, 0.761, 1],
|
[1.0, 0.761, 1],
|
||||||
|
@ -19,10 +19,7 @@ class PReLUTest extends TestCase
|
|||||||
$this->assertEquals($expected, $prelu->compute($value), '', 0.001);
|
$this->assertEquals($expected, $prelu->compute($value), '', 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function preluProvider(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function preluProvider()
|
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[0.01, 0.367, 0.367],
|
[0.01, 0.367, 0.367],
|
||||||
|
@ -19,10 +19,7 @@ class SigmoidTest extends TestCase
|
|||||||
$this->assertEquals($expected, $sigmoid->compute($value), '', 0.001);
|
$this->assertEquals($expected, $sigmoid->compute($value), '', 0.001);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function sigmoidProvider(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function sigmoidProvider()
|
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[1.0, 1, 7.25],
|
[1.0, 1, 7.25],
|
||||||
|
@ -19,16 +19,13 @@ class ThresholdedReLUTest extends TestCase
|
|||||||
$this->assertEquals($expected, $thresholdedReLU->compute($value));
|
$this->assertEquals($expected, $thresholdedReLU->compute($value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function thresholdProvider(): array
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function thresholdProvider()
|
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[1.0, 0, 1.0],
|
[1.0, 0, 1.0],
|
||||||
[0.5, 3.75, 3.75],
|
[0.5, 3.75, 3.75],
|
||||||
[0.0, 0.5, 0.5],
|
[0.0, 0.5, 0.5],
|
||||||
[0.9, 0, 0.1]
|
[0.9, 0, 0.1],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ use Phpml\NeuralNetwork\Layer;
|
|||||||
use Phpml\NeuralNetwork\Node\Bias;
|
use Phpml\NeuralNetwork\Node\Bias;
|
||||||
use Phpml\NeuralNetwork\Node\Neuron;
|
use Phpml\NeuralNetwork\Node\Neuron;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class LayerTest extends TestCase
|
class LayerTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -43,7 +44,7 @@ class LayerTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testThrowExceptionOnInvalidNodeClass(): void
|
public function testThrowExceptionOnInvalidNodeClass(): void
|
||||||
{
|
{
|
||||||
new Layer(1, \stdClass::class);
|
new Layer(1, stdClass::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAddNodesToLayer(): void
|
public function testAddNodesToLayer(): void
|
||||||
|
@ -44,10 +44,7 @@ class LayeredNetworkTest extends TestCase
|
|||||||
$this->assertEquals([0.5], $network->getOutput());
|
$this->assertEquals([0.5], $network->getOutput());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getLayeredNetworkMock(): LayeredNetwork
|
||||||
* @return LayeredNetwork
|
|
||||||
*/
|
|
||||||
private function getLayeredNetworkMock()
|
|
||||||
{
|
{
|
||||||
return $this->getMockForAbstractClass(LayeredNetwork::class);
|
return $this->getMockForAbstractClass(LayeredNetwork::class);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user