diff --git a/.gitignore b/.gitignore index bed47c4..38ef5a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ /vendor/ humbuglog.* -/bin/phpunit -.coverage .php_cs.cache -/bin/php-cs-fixer -/bin/coveralls /build diff --git a/.travis.yml b/.travis.yml index 80bd5d8..8f8a68b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ matrix: include: - os: linux php: '7.1' - env: DISABLE_XDEBUG="true" + env: DISABLE_XDEBUG="true" STATIC_ANALYSIS="true" - os: linux php: '7.2' @@ -21,7 +21,7 @@ matrix: before_install: - 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: - 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 script: - - bin/phpunit $PHPUNIT_FLAGS + - vendor/bin/phpunit $PHPUNIT_FLAGS + - if [[ $STATIC_ANALYSIS != "" ]]; then vendor/bin/ecs check src tests; fi after_success: - | 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 diff --git a/composer.json b/composer.json index 58e79a6..0563f3f 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ } ], "autoload": { - "psr-0": { - "Phpml": "src/" + "psr-4": { + "Phpml\\": "src/Phpml" } }, "require": { @@ -22,9 +22,8 @@ "require-dev": { "phpunit/phpunit": "^6.0", "friendsofphp/php-cs-fixer": "^2.4", - "php-coveralls/php-coveralls": "^1.0" - }, - "config": { - "bin-dir": "bin" + "symplify/easy-coding-standard": "dev-master as 2.5", + "symplify/coding-standard": "dev-master as 2.5", + "symplify/package-builder": "dev-master#3604bea as 2.5" } } diff --git a/composer.lock b/composer.lock index 1384e6e..e10b2d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,71 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "c4d5b319f041c2d65249c7852a7f2fc1", + "content-hash": "0b709f785c1e62498755f557e49e1ba4", "packages": [], "packages-dev": [ + { + "name": "composer/semver", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2016-08-30T16:08:34+00:00" + }, { "name": "doctrine/annotations", "version": "v1.5.0", @@ -77,32 +139,32 @@ }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { "athletic/athletic": "~0.1.8", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -127,7 +189,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2017-07-22T11:58:36+00:00" }, { "name": "doctrine/lexer", @@ -185,44 +247,46 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.4.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "63661f3add3609e90e4ab8115113e189ae547bb4" + "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/63661f3add3609e90e4ab8115113e189ae547bb4", - "reference": "63661f3add3609e90e4ab8115113e189ae547bb4", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/04f71e56e03ba2627e345e8c949c80dcef0e683e", + "reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e", "shasum": "" }, "require": { + "composer/semver": "^1.4", "doctrine/annotations": "^1.2", "ext-json": "*", "ext-tokenizer": "*", - "gecko-packages/gecko-php-unit": "^2.0", - "php": "^5.6 || >=7.0 <7.2", - "sebastian/diff": "^1.4", - "symfony/console": "^3.0", - "symfony/event-dispatcher": "^3.0", - "symfony/filesystem": "^3.0", - "symfony/finder": "^3.0", - "symfony/options-resolver": "^3.0", + "gecko-packages/gecko-php-unit": "^2.0 || ^3.0", + "php": "^5.6 || >=7.0 <7.3", + "php-cs-fixer/diff": "^1.2", + "symfony/console": "^3.2 || ^4.0", + "symfony/event-dispatcher": "^3.0 || ^4.0", + "symfony/filesystem": "^3.0 || ^4.0", + "symfony/finder": "^3.0 || ^4.0", + "symfony/options-resolver": "^3.0 || ^4.0", "symfony/polyfill-php70": "^1.0", "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0", - "symfony/stopwatch": "^3.0" + "symfony/process": "^3.0 || ^4.0", + "symfony/stopwatch": "^3.0 || ^4.0" }, "conflict": { "hhvm": "*" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.1", + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0@dev", "justinrainbow/json-schema": "^5.0", - "phpunit/phpunit": "^4.8.35 || ^5.4.3", - "satooshi/php-coveralls": "^1.0", - "symfony/phpunit-bridge": "^3.2.2" + "php-coveralls/php-coveralls": "^1.0.2", + "php-cs-fixer/accessible-object": "^1.0", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "symfony/phpunit-bridge": "^3.2.2 || ^4.0" }, "suggest": { "ext-mbstring": "For handling non-UTF8 characters in cache signature.", @@ -232,11 +296,6 @@ "php-cs-fixer" ], "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -264,173 +323,93 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-07-18T15:35:40+00:00" + "time": "2017-11-09T13:31:39+00:00" }, { "name": "gecko-packages/gecko-php-unit", - "version": "v2.1", + "version": "v3.0", "source": { "type": "git", "url": "https://github.com/GeckoPackages/GeckoPHPUnit.git", - "reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e" + "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/5b9e9622c7efd3b22655270b80c03f9e52878a6e", - "reference": "5b9e9622c7efd3b22655270b80c03f9e52878a6e", + "url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/6a866551dffc2154c1b091bae3a7877d39c25ca3", + "reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3", "shasum": "" }, "require": { - "php": "^5.3.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3" + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-dom": "When testing with xml.", + "ext-libxml": "When testing with xml.", + "phpunit/phpunit": "This is an extension for it so make sure you have it some way." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, "autoload": { "psr-4": { - "GeckoPackages\\PHPUnit\\": "src\\PHPUnit" + "GeckoPackages\\PHPUnit\\": "src/PHPUnit" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Additional PHPUnit tests.", + "description": "Additional PHPUnit asserts and constraints.", "homepage": "https://github.com/GeckoPackages", "keywords": [ "extension", "filesystem", "phpunit" ], - "time": "2017-06-20T11:22:48+00:00" - }, - { - "name": "guzzle/guzzle", - "version": "v3.8.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", - "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": ">=2.1" - }, - "replace": { - "guzzle/batch": "self.version", - "guzzle/cache": "self.version", - "guzzle/common": "self.version", - "guzzle/http": "self.version", - "guzzle/inflection": "self.version", - "guzzle/iterator": "self.version", - "guzzle/log": "self.version", - "guzzle/parser": "self.version", - "guzzle/plugin": "self.version", - "guzzle/plugin-async": "self.version", - "guzzle/plugin-backoff": "self.version", - "guzzle/plugin-cache": "self.version", - "guzzle/plugin-cookie": "self.version", - "guzzle/plugin-curlauth": "self.version", - "guzzle/plugin-error-response": "self.version", - "guzzle/plugin-history": "self.version", - "guzzle/plugin-log": "self.version", - "guzzle/plugin-md5": "self.version", - "guzzle/plugin-mock": "self.version", - "guzzle/plugin-oauth": "self.version", - "guzzle/service": "self.version", - "guzzle/stream": "self.version" - }, - "require-dev": { - "doctrine/cache": "*", - "monolog/monolog": "1.*", - "phpunit/phpunit": "3.7.*", - "psr/log": "1.0.*", - "symfony/class-loader": "*", - "zendframework/zend-cache": "<2.3", - "zendframework/zend-log": "<2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.8-dev" - } - }, - "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" - } - ], - "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ], - "abandoned": "guzzlehttp/guzzle", - "time": "2014-01-28T22:29:15+00:00" + "time": "2017-08-23T07:46:41+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe" + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe", - "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^5.6 || ^7.0" }, "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" }, "type": "library", "autoload": { "psr-4": { "DeepCopy\\": "src/DeepCopy/" - } + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", "keywords": [ "clone", "copy", @@ -438,20 +417,474 @@ "object", "object graph" ], - "time": "2017-01-26T22:05:40+00:00" + "time": "2017-10-19T19:58:43+00:00" }, { - "name": "paragonie/random_compat", - "version": "v2.0.10", + "name": "nette/caching", + "version": "v2.5.6", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + "url": "https://github.com/nette/caching.git", + "reference": "1231735b5135ca02bd381b70482c052d2a90bdc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "url": "https://api.github.com/repos/nette/caching/zipball/1231735b5135ca02bd381b70482c052d2a90bdc9", + "reference": "1231735b5135ca02bd381b70482c052d2a90bdc9", + "shasum": "" + }, + "require": { + "nette/finder": "^2.2 || ~3.0.0", + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "latte/latte": "^2.4", + "nette/di": "^2.4 || ~3.0.0", + "nette/tester": "^2.0", + "tracy/tracy": "^2.4" + }, + "suggest": { + "ext-pdo_sqlite": "to use SQLiteStorage or SQLiteJournal" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "⏱ Nette Caching: library with easy-to-use API and many cache backends.", + "homepage": "https://nette.org", + "keywords": [ + "cache", + "journal", + "memcached", + "nette", + "sqlite" + ], + "time": "2017-08-30T12:12:25+00:00" + }, + { + "name": "nette/di", + "version": "v2.4.10", + "source": { + "type": "git", + "url": "https://github.com/nette/di.git", + "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/di/zipball/a4b3be935b755f23aebea1ce33d7e3c832cdff98", + "reference": "a4b3be935b755f23aebea1ce33d7e3c832cdff98", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "nette/neon": "^2.3.3 || ~3.0.0", + "nette/php-generator": "^2.6.1 || ~3.0.0", + "nette/utils": "^2.4.3 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/bootstrap": "<2.4", + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", + "homepage": "https://nette.org", + "keywords": [ + "compiled", + "di", + "dic", + "factory", + "ioc", + "nette", + "static" + ], + "time": "2017-08-31T22:42:00+00:00" + }, + { + "name": "nette/finder", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/nette/finder.git", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/finder/zipball/4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "reference": "4d43a66d072c57d585bf08a3ef68d3587f7e9547", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "Nette Finder: Files Searching", + "homepage": "https://nette.org", + "time": "2017-07-10T23:47:08+00:00" + }, + { + "name": "nette/neon", + "version": "v2.4.2", + "source": { + "type": "git", + "url": "https://github.com/nette/neon.git", + "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/neon/zipball/9eacd50553b26b53a3977bfb2fea2166d4331622", + "reference": "9eacd50553b26b53a3977bfb2fea2166d4331622", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "Nette NEON: parser & generator for Nette Object Notation", + "homepage": "http://ne-on.org", + "time": "2017-07-11T18:29:08+00:00" + }, + { + "name": "nette/php-generator", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "eb2dbc9c3409e9db40568109ca4994d51373b60c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/eb2dbc9c3409e9db40568109ca4994d51373b60c", + "reference": "eb2dbc9c3409e9db40568109ca4994d51373b60c", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4.2 || ~3.0.0", + "php": ">=7.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.1 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "time": "2017-07-11T19:07:13+00:00" + }, + { + "name": "nette/robot-loader", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/nette/robot-loader.git", + "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/b703b4f5955831b0bcaacbd2f6af76021b056826", + "reference": "b703b4f5955831b0bcaacbd2f6af76021b056826", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "nette/finder": "^2.3 || ^3.0", + "nette/utils": "^2.4 || ^3.0", + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🍀 Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", + "homepage": "https://nette.org", + "keywords": [ + "autoload", + "class", + "interface", + "nette", + "trait" + ], + "time": "2017-07-18T00:09:56+00:00" + }, + { + "name": "nette/utils", + "version": "v2.4.8", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "f1584033b5af945b470533b466b81a789d532034" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/f1584033b5af945b470533b466b81a789d532034", + "reference": "f1584033b5af945b470533b466b81a789d532034", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize() and toAscii()", + "ext-intl": "for script transliteration in Strings::webalize() and toAscii()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "time": "2017-08-20T17:32:29+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.11", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", "shasum": "" }, "require": { @@ -486,81 +919,170 @@ "pseudorandom", "random" ], - "time": "2017-03-13T16:27:32+00:00" + "time": "2017-09-27T21:40:39+00:00" }, { - "name": "php-coveralls/php-coveralls", - "version": "v1.0.2", + "name": "phar-io/manifest", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-coveralls/php-coveralls.git", - "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127" + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/9c07b63acbc9709344948b6fd4f63a32b2ef4127", - "reference": "9c07b63acbc9709344948b6fd4f63a32b2ef4127", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", "shasum": "" }, "require": { - "ext-json": "*", - "ext-simplexml": "*", - "guzzle/guzzle": "^2.8 || ^3.0", - "php": "^5.3.3 || ^7.0", - "psr/log": "^1.0", - "symfony/config": "^2.1 || ^3.0 || ^4.0", - "symfony/console": "^2.1 || ^3.0 || ^4.0", - "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", - "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" - }, - "suggest": { - "symfony/http-kernel": "Allows Symfony integration" - }, - "bin": [ - "bin/coveralls" - ], "type": "library", - "autoload": { - "psr-4": { - "Satooshi\\": "src/Satooshi/" + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Kitamura Satoshi", - "email": "with.no.parachute@gmail.com", - "homepage": "https://www.facebook.com/satooshi.jp" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "description": "PHP client library for Coveralls API", - "homepage": "https://github.com/php-coveralls/php-coveralls", - "keywords": [ - "ci", - "coverage", - "github", - "test" - ], - "time": "2017-10-14T23:15:34+00:00" + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" }, { - "name": "phpdocumentor/reflection-common", - "version": "1.0", + "name": "phar-io/version", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "php-cs-fixer/diff", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/diff.git", + "reference": "f0ef6133d674137e902fdf8a6f2e8e97e14a087b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/f0ef6133d674137e902fdf8a6f2e8e97e14a087b", + "reference": "f0ef6133d674137e902fdf8a6f2e8e97e14a087b", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3", + "symfony/process": "^3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "SpacePossum" + } + ], + "description": "sebastian/diff v2 backport support for PHP5.6", + "homepage": "https://github.com/PHP-CS-Fixer", + "keywords": [ + "diff" + ], + "time": "2017-10-19T09:58:18+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", "shasum": "" }, "require": { @@ -601,26 +1123,26 @@ "reflection", "static analysis" ], - "time": "2015-12-27T11:43:31+00:00" + "time": "2017-09-11T18:02:19+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", + "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^7.0", "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", + "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { @@ -646,24 +1168,24 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "time": "2017-08-30T18:51:59+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^5.5 || ^7.0", "phpdocumentor/reflection-common": "^1.0" }, "require-dev": { @@ -693,26 +1215,26 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25T06:54:22+00:00" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.7.0", + "version": "v1.7.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", + "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", "sebastian/comparator": "^1.1|^2.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, @@ -723,7 +1245,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -756,44 +1278,45 @@ "spy", "stub" ], - "time": "2017-03-02T20:05:34+00:00" + "time": "2017-09-04T11:05:03+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.0.2", + "version": "5.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "531553c4795a1df54114342d68ca337d5d81c8a0" + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/531553c4795a1df54114342d68ca337d5d81c8a0", - "reference": "531553c4795a1df54114342d68ca337d5d81c8a0", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", + "reference": "8e1d2397d8adf59a3f12b2878a3aaa66d1ab189d", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", "php": "^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.11 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^2.0", - "sebastian/version": "^2.0" + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" }, "require-dev": { "ext-xdebug": "^2.5", "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-xdebug": "^2.5.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0.x-dev" + "dev-master": "5.2.x-dev" } }, "autoload": { @@ -819,7 +1342,7 @@ "testing", "xunit" ], - "time": "2017-03-01T09:14:18+00:00" + "time": "2017-11-03T13:47:33+00:00" }, { "name": "phpunit/php-file-iterator", @@ -960,29 +1483,29 @@ }, { "name": "phpunit/php-token-stream", - "version": "1.4.11", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.2.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1005,20 +1528,20 @@ "keywords": [ "tokenizer" ], - "time": "2017-02-27T10:12:30+00:00" + "time": "2017-08-20T05:47:52+00:00" }, { "name": "phpunit/phpunit", - "version": "6.0.8", + "version": "6.4.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "47ee3fa1bca5c50f1d25105201eb20df777bd7b6" + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47ee3fa1bca5c50f1d25105201eb20df777bd7b6", - "reference": "47ee3fa1bca5c50f1d25105201eb20df777bd7b6", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/562f7dc75d46510a4ed5d16189ae57fbe45a9932", + "reference": "562f7dc75d46510a4ed5d16189ae57fbe45a9932", "shasum": "" }, "require": { @@ -1027,22 +1550,24 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.3", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", "php": "^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^5.0", - "phpunit/php-file-iterator": "^1.4", - "phpunit/php-text-template": "^1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^4.0", - "sebastian/comparator": "^1.2.4 || ^2.0", - "sebastian/diff": "^1.2", - "sebastian/environment": "^2.0", - "sebastian/exporter": "^2.0 || ^3.0", - "sebastian/global-state": "^1.1 || ^2.0", - "sebastian/object-enumerator": "^2.0 || ^3.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.2.2", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^4.0.3", + "sebastian/comparator": "^2.0.2", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", "sebastian/resource-operations": "^1.0", - "sebastian/version": "^2.0" + "sebastian/version": "^2.0.1" }, "conflict": { "phpdocumentor/reflection-docblock": "3.0.2", @@ -1061,7 +1586,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.0.x-dev" + "dev-master": "6.4.x-dev" } }, "autoload": { @@ -1087,26 +1612,26 @@ "testing", "xunit" ], - "time": "2017-03-02T15:24:03+00:00" + "time": "2017-11-08T11:26:09+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "4.0.1", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "eabce450df194817a7d7e27e19013569a903a2bf" + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/eabce450df194817a7d7e27e19013569a903a2bf", - "reference": "eabce450df194817a7d7e27e19013569a903a2bf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/2f789b59ab89669015ad984afa350c4ec577ade0", + "reference": "2f789b59ab89669015ad984afa350c4ec577ade0", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", + "doctrine/instantiator": "^1.0.5", "php": "^7.0", - "phpunit/php-text-template": "^1.2", + "phpunit/php-text-template": "^1.2.1", "sebastian/exporter": "^3.0" }, "conflict": { @@ -1146,7 +1671,56 @@ "mock", "xunit" ], - "time": "2017-03-03T06:30:20+00:00" + "time": "2017-08-03T14:08:16+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" }, { "name": "psr/log", @@ -1242,30 +1816,30 @@ }, { "name": "sebastian/comparator", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "20f84f468cb67efee293246e6a09619b891f55f0" + "reference": "1174d9018191e93cb9d719edec01257fc05f8158" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/20f84f468cb67efee293246e6a09619b891f55f0", - "reference": "20f84f468cb67efee293246e6a09619b891f55f0", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1174d9018191e93cb9d719edec01257fc05f8158", + "reference": "1174d9018191e93cb9d719edec01257fc05f8158", "shasum": "" }, "require": { "php": "^7.0", - "sebastian/diff": "^1.2", - "sebastian/exporter": "^3.0" + "sebastian/diff": "^2.0", + "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -1296,38 +1870,38 @@ } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", "equality" ], - "time": "2017-03-03T06:26:08+00:00" + "time": "2017-11-03T07:16:52+00:00" }, { "name": "sebastian/diff", - "version": "1.4.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1354,32 +1928,32 @@ "keywords": [ "diff" ], - "time": "2015-12-08T07:14:41+00:00" + "time": "2017-08-03T08:09:46+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1404,20 +1978,20 @@ "environment", "hhvm" ], - "time": "2016-11-26T07:53:53+00:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "b82d077cb3459e393abcf4867ae8f7230dcb51f6" + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/b82d077cb3459e393abcf4867ae8f7230dcb51f6", - "reference": "b82d077cb3459e393abcf4867ae8f7230dcb51f6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", "shasum": "" }, "require": { @@ -1431,7 +2005,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1471,27 +2045,27 @@ "export", "exporter" ], - "time": "2017-03-03T06:25:06+00:00" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-uopz": "*" @@ -1499,7 +2073,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1522,24 +2096,25 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794" + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/de6e32f7192dfea2e4bedc892434f4830b5c5794", - "reference": "de6e32f7192dfea2e4bedc892434f4830b5c5794", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", "shasum": "" }, "require": { "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", "sebastian/recursion-context": "^3.0" }, "require-dev": { @@ -1568,7 +2143,52 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-03-03T06:21:01+00:00" + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" }, { "name": "sebastian/recursion-context", @@ -1708,6 +2328,94 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "slevomat/coding-standard", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "bab653d0f7f2e3ed13796f7803067d252f00a25a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/bab653d0f7f2e3ed13796f7803067d252f00a25a", + "reference": "bab653d0f7f2e3ed13796f7803067d252f00a25a", + "shasum": "" + }, + "require": { + "php": "^7.0", + "squizlabs/php_codesniffer": "^3.0.1" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "0.9.2", + "phing/phing": "2.16", + "phpstan/phpstan": "0.8.4", + "phpunit/phpunit": "6.3.0" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "time": "2017-09-15T17:47:36+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "d667e245d5dcd4d7bf80f26f2c947d476b66213e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d667e245d5dcd4d7bf80f26f2c947d476b66213e", + "reference": "d667e245d5dcd4d7bf80f26f2c947d476b66213e", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-10-16T22:40:25+00:00" + }, { "name": "symfony/config", "version": "v3.3.12", @@ -1772,20 +2480,20 @@ }, { "name": "symfony/console", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546" + "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a97e45d98c59510f085fa05225a1acb74dfe0546", - "reference": "a97e45d98c59510f085fa05225a1acb74dfe0546", + "url": "https://api.github.com/repos/symfony/console/zipball/099302cc53e57cbb7414fd9f3ace40e5e2767c0b", + "reference": "099302cc53e57cbb7414fd9f3ace40e5e2767c0b", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "symfony/debug": "~2.8|~3.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -1798,7 +2506,6 @@ "symfony/dependency-injection": "~3.3", "symfony/event-dispatcher": "~2.8|~3.0", "symfony/filesystem": "~2.8|~3.0", - "symfony/http-kernel": "~2.8|~3.0", "symfony/process": "~2.8|~3.0" }, "suggest": { @@ -1837,24 +2544,24 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-07-03T13:19:36+00:00" + "time": "2017-11-12T16:53:41+00:00" }, { "name": "symfony/debug", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "63b85a968486d95ff9542228dc2e4247f16f9743" + "reference": "74557880e2846b5c84029faa96b834da37e29810" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/63b85a968486d95ff9542228dc2e4247f16f9743", - "reference": "63b85a968486d95ff9542228dc2e4247f16f9743", + "url": "https://api.github.com/repos/symfony/debug/zipball/74557880e2846b5c84029faa96b834da37e29810", + "reference": "74557880e2846b5c84029faa96b834da37e29810", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^5.5.9|>=7.0.8", "psr/log": "~1.0" }, "conflict": { @@ -1893,24 +2600,94 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-07-05T13:02:37+00:00" + "time": "2017-11-10T16:38:39+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v3.3.5", + "name": "symfony/dependency-injection", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67535f1e3fd662bdc68d7ba317c93eecd973617e", - "reference": "67535f1e3fd662bdc68d7ba317c93eecd973617e", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", + "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8", + "psr/container": "^1.0" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/finder": "<3.3", + "symfony/yaml": "<3.3" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "require-dev": { + "symfony/config": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2017-11-13T18:10:32+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" }, "conflict": { "symfony/dependency-injection": "<3.3" @@ -1956,24 +2733,24 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-06-09T14:53:08+00:00" + "time": "2017-11-05T15:47:03+00:00" }, { "name": "symfony/filesystem", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676" + "reference": "77db266766b54db3ee982fe51868328b887ce15c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/427987eb4eed764c3b6e38d52a0f87989e010676", - "reference": "427987eb4eed764c3b6e38d52a0f87989e010676", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/77db266766b54db3ee982fe51868328b887ce15c", + "reference": "77db266766b54db3ee982fe51868328b887ce15c", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2005,24 +2782,24 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-07-11T07:17:58+00:00" + "time": "2017-11-07T14:12:55+00:00" }, { "name": "symfony/finder", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" + "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", - "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", + "url": "https://api.github.com/repos/symfony/finder/zipball/138af5ec075d4b1d1bd19de08c38a34bb2d7d880", + "reference": "138af5ec075d4b1d1bd19de08c38a34bb2d7d880", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2054,24 +2831,163 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-06-01T21:01:25+00:00" + "time": "2017-11-05T15:47:03+00:00" }, { - "name": "symfony/options-resolver", - "version": "v3.3.5", + "name": "symfony/http-foundation", + "version": "v3.3.12", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "5943f0f19817a7e05992d20a90729b0dc93faf36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", - "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5943f0f19817a7e05992d20a90729b0dc93faf36", + "reference": "5943f0f19817a7e05992d20a90729b0dc93faf36", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2017-11-13T18:13:16+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", + "reference": "371ed63691c1ee8749613a6b48cf0e0cfa2b01e7", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "^3.3.11" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2017-11-13T19:37:21+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "623d9c210a137205f7e6e98166105625402cbb2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/623d9c210a137205f7e6e98166105625402cbb2f", + "reference": "623d9c210a137205f7e6e98166105625402cbb2f", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2108,20 +3024,20 @@ "configuration", "options" ], - "time": "2017-04-12T14:14:56+00:00" + "time": "2017-11-05T15:47:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.4.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", "shasum": "" }, "require": { @@ -2133,7 +3049,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2167,20 +3083,20 @@ "portable", "shim" ], - "time": "2017-06-09T14:24:12+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.4.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874" + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/032fd647d5c11a9ceab8ee8747e13b5448e93874", - "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", + "reference": "0442b9c0596610bd24ae7b5f0a6cdbbc16d9fcff", "shasum": "" }, "require": { @@ -2190,7 +3106,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2226,20 +3142,20 @@ "portable", "shim" ], - "time": "2017-06-09T14:24:12+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.4.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c" + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", - "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/6de4f4884b97abbbed9f0a84a95ff2ff77254254", + "reference": "6de4f4884b97abbbed9f0a84a95ff2ff77254254", "shasum": "" }, "require": { @@ -2248,7 +3164,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -2281,24 +3197,24 @@ "portable", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2017-10-11T12:05:26+00:00" }, { "name": "symfony/process", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" + "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", - "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", + "url": "https://api.github.com/repos/symfony/process/zipball/a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", + "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2330,24 +3246,24 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-07-13T13:05:09+00:00" + "time": "2017-11-13T15:31:11+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.3.5", + "version": "v3.3.12", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" + "reference": "1e93c3139ef6c799831fe03efd0fb1c7aecb3365" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", - "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/1e93c3139ef6c799831fe03efd0fb1c7aecb3365", + "reference": "1e93c3139ef6c799831fe03efd0fb1c7aecb3365", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { @@ -2379,7 +3295,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:14:56+00:00" + "time": "2017-11-10T19:02:53+00:00" }, { "name": "symfony/yaml", @@ -2436,6 +3352,259 @@ "homepage": "https://symfony.com", "time": "2017-11-10T18:26:04+00:00" }, + { + "name": "symplify/coding-standard", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Symplify/CodingStandard.git", + "reference": "309fd562066cdc86b81375ff080b1ee2f900778e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/CodingStandard/zipball/309fd562066cdc86b81375ff080b1ee2f900778e", + "reference": "309fd562066cdc86b81375ff080b1ee2f900778e", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.8", + "nette/finder": "^2.4|^3.0", + "nette/utils": "^2.4|^3.0", + "php": "^7.1", + "slevomat/coding-standard": "^4.0", + "squizlabs/php_codesniffer": "^3.1" + }, + "require-dev": { + "gecko-packages/gecko-php-unit": "3.0 as 2.2", + "nette/application": "^2.4", + "phpunit/phpunit": "^6.4", + "symplify/easy-coding-standard": "^2.5|^3.0", + "symplify/package-builder": "^2.5|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symplify\\CodingStandard\\": "src", + "Symplify\\CodingStandard\\SniffTokenWrapper\\": "packages/SniffTokenWrapper/src", + "Symplify\\CodingStandard\\FixerTokenWrapper\\": "packages/FixerTokenWrapper/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Set of Symplify rules for PHP_CodeSniffer.", + "time": "2017-11-16 00:38:24" + }, + { + "name": "symplify/easy-coding-standard", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Symplify/EasyCodingStandard.git", + "reference": "4bac5271050f063b4455bd870cc215e0db57ddf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/EasyCodingStandard/zipball/4bac5271050f063b4455bd870cc215e0db57ddf8", + "reference": "4bac5271050f063b4455bd870cc215e0db57ddf8", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^2.8", + "nette/caching": "^2.4|^3.0", + "nette/di": "^2.4|^3.0", + "nette/neon": "^2.4|^3.0", + "nette/robot-loader": "^2.4|^3.0.1", + "nette/utils": "^2.4|^3.0", + "php": "^7.1", + "sebastian/diff": "^1.4|^2.0", + "slevomat/coding-standard": "^4.0", + "squizlabs/php_codesniffer": "^3.1", + "symfony/config": "^3.3|^4.0", + "symfony/console": "^3.3|^4.0", + "symfony/dependency-injection": "^3.3|^4.0", + "symfony/finder": "^3.3|^4.0", + "symfony/http-kernel": "^3.3|^4.0", + "symfony/yaml": "^3.3|^4.0", + "symplify/coding-standard": "^2.5|^3.0", + "symplify/package-builder": "^2.5|^3.0", + "tracy/tracy": "^2.4|^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "bin": [ + "bin/easy-coding-standard", + "bin/ecs", + "bin/easy-coding-standard.php" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symplify\\EasyCodingStandard\\": "src", + "Symplify\\EasyCodingStandard\\ChangedFilesDetector\\": "packages/ChangedFilesDetector/src", + "Symplify\\EasyCodingStandard\\Configuration\\": "packages/Configuration/src", + "Symplify\\EasyCodingStandard\\FixerRunner\\": "packages/FixerRunner/src", + "Symplify\\EasyCodingStandard\\SniffRunner\\": "packages/SniffRunner/src", + "Symplify\\EasyCodingStandard\\Performance\\": "packages/Performance/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Use Coding Standard with 0-knowledge of PHP-CS-Fixer and PHP_CodeSniffer.", + "time": "2017-11-16 15:36:21" + }, + { + "name": "symplify/package-builder", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Symplify/PackageBuilder.git", + "reference": "3604beadddfdee295b978d87475a834810a0ffd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Symplify/PackageBuilder/zipball/3604beadddfdee295b978d87475a834810a0ffd4", + "reference": "3604beadddfdee295b978d87475a834810a0ffd4", + "shasum": "" + }, + "require": { + "nette/di": "^2.4|^3.0", + "nette/neon": "^2.4|^3.0", + "php": "^7.1", + "symfony/config": "^3.3|^4.0", + "symfony/console": "^3.3|^4.0", + "symfony/dependency-injection": "^3.3|^4.0", + "symfony/http-kernel": "^3.3|^4.0", + "symfony/yaml": "^3.3|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.4", + "tracy/tracy": "^2.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symplify\\PackageBuilder\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Dependency Injection, Console and Kernel toolkit for Symplify packages.", + "time": "2017-11-16T01:05:48+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "tracy/tracy", + "version": "v2.4.10", + "source": { + "type": "git", + "url": "https://github.com/nette/tracy.git", + "reference": "5b302790edd71924dfe4ec44f499ef61df3f53a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/tracy/zipball/5b302790edd71924dfe4ec44f499ef61df3f53a2", + "reference": "5b302790edd71924dfe4ec44f499ef61df3f53a2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-session": "*", + "php": ">=5.4.4" + }, + "require-dev": { + "nette/di": "~2.3", + "nette/tester": "~1.7" + }, + "suggest": { + "https://nette.org/donate": "Please support Tracy via a donation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "classmap": [ + "src" + ], + "files": [ + "src/shortcuts.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.", + "homepage": "https://tracy.nette.org", + "keywords": [ + "Xdebug", + "debug", + "debugger", + "nette", + "profiler" + ], + "time": "2017-10-04T18:43:42+00:00" + }, { "name": "webmozart/assert", "version": "1.2.0", @@ -2487,9 +3656,32 @@ "time": "2016-11-23T20:04:58+00:00" } ], - "aliases": [], + "aliases": [ + { + "alias": "2.5", + "alias_normalized": "2.5.0.0", + "version": "9999999-dev", + "package": "symplify/easy-coding-standard" + }, + { + "alias": "2.5", + "alias_normalized": "2.5.0.0", + "version": "9999999-dev", + "package": "symplify/coding-standard" + }, + { + "alias": "2.5", + "alias_normalized": "2.5.0.0", + "version": "9999999-dev", + "package": "symplify/package-builder" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "symplify/easy-coding-standard": 20, + "symplify/coding-standard": 20, + "symplify/package-builder": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/easy-coding-standard.neon b/easy-coding-standard.neon new file mode 100644 index 0000000..14d8770 --- /dev/null +++ b/easy-coding-standard.neon @@ -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 diff --git a/phpunit.xml b/phpunit.xml index cbf6c18..455f8bb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,11 +6,9 @@ beStrictAboutTestSize="true" beStrictAboutChangesToGlobalState="true" > - - - tests/* - - ​ + + tests/* + diff --git a/src/Phpml/Association/Apriori.php b/src/Phpml/Association/Apriori.php index f1398d2..e13f556 100644 --- a/src/Phpml/Association/Apriori.php +++ b/src/Phpml/Association/Apriori.php @@ -31,7 +31,7 @@ class Apriori implements Associator * * @var mixed[][][] */ - private $large; + private $large = []; /** * Minimum relative frequency of transactions. @@ -45,7 +45,7 @@ class Apriori implements Associator * * @var mixed[][] */ - private $rules; + private $rules = []; /** * Apriori constructor. @@ -61,7 +61,7 @@ class Apriori implements Associator * * @return mixed[][] */ - public function getRules() : array + public function getRules(): array { if (!$this->large) { $this->large = $this->apriori(); @@ -83,7 +83,7 @@ class Apriori implements Associator * * @return mixed[][][] */ - public function apriori() : array + public function apriori(): array { $L = []; $L[1] = $this->items(); @@ -102,7 +102,7 @@ class Apriori implements Associator * * @return mixed[][] */ - protected function predictSample(array $sample) : array + protected function predictSample(array $sample): array { $predicts = array_values(array_filter($this->getRules(), function ($rule) use ($sample) { return $this->equals($rule[self::ARRAY_KEY_ANTECEDENT], $sample); @@ -133,7 +133,8 @@ class Apriori implements Associator private function generateRules(array $frequent): void { 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)); $this->rules[] = [ self::ARRAY_KEY_ANTECEDENT => $antecedent, @@ -152,7 +153,7 @@ class Apriori implements Associator * * @return mixed[][] */ - private function powerSet(array $sample) : array + private function powerSet(array $sample): array { $results = [[]]; foreach ($sample as $item) { @@ -171,7 +172,7 @@ class Apriori implements Associator * * @return mixed[][] */ - private function antecedents(array $sample) : array + private function antecedents(array $sample): array { $cardinality = count($sample); $antecedents = $this->powerSet($sample); @@ -186,7 +187,7 @@ class Apriori implements Associator * * @return mixed[][] */ - private function items() : array + private function items(): array { $items = []; @@ -210,7 +211,7 @@ class Apriori implements Associator * * @return mixed[][] */ - private function frequent(array $samples) : array + private function frequent(array $samples): array { return array_filter($samples, function ($entry) { return $this->support($entry) >= $this->support; @@ -224,7 +225,7 @@ class Apriori implements Associator * * @return mixed[][] */ - private function candidates(array $samples) : array + private function candidates(array $samples): array { $candidates = []; @@ -259,7 +260,7 @@ class Apriori implements Associator * @param mixed[] $set * @param mixed[] $subset */ - private function confidence(array $set, array $subset) : float + private function confidence(array $set, array $subset): float { return $this->support($set) / $this->support($subset); } @@ -272,7 +273,7 @@ class Apriori implements Associator * * @param mixed[] $sample */ - private function support(array $sample) : float + private function support(array $sample): float { return $this->frequency($sample) / count($this->samples); } @@ -284,7 +285,7 @@ class Apriori implements Associator * * @param mixed[] $sample */ - private function frequency(array $sample) : int + private function frequency(array $sample): int { return count(array_filter($this->samples, function ($entry) use ($sample) { return $this->subset($entry, $sample); @@ -299,7 +300,7 @@ class Apriori implements Associator * @param mixed[][] $system * @param mixed[] $set */ - private function contains(array $system, array $set) : bool + private function contains(array $system, array $set): bool { return (bool) array_filter($system, function ($entry) use ($set) { return $this->equals($entry, $set); @@ -312,7 +313,7 @@ class Apriori implements Associator * @param mixed[] $set * @param mixed[] $subset */ - private function subset(array $set, array $subset) : bool + private function subset(array $set, array $subset): bool { return !array_diff($subset, array_intersect($subset, $set)); } @@ -323,7 +324,7 @@ class Apriori implements Associator * @param mixed[] $set1 * @param mixed[] $set2 */ - private function equals(array $set1, array $set2) : bool + private function equals(array $set1, array $set2): bool { return array_diff($set1, $set2) == array_diff($set2, $set1); } diff --git a/src/Phpml/Classification/DecisionTree.php b/src/Phpml/Classification/DecisionTree.php index 653b1bf..5bb730b 100644 --- a/src/Phpml/Classification/DecisionTree.php +++ b/src/Phpml/Classification/DecisionTree.php @@ -15,22 +15,18 @@ class DecisionTree implements Classifier use Trainable, Predictable; public const CONTINUOUS = 1; + public const NOMINAL = 2; - /** - * @var array - */ - protected $columnTypes; - - /** - * @var array - */ - private $labels = []; - /** * @var int */ - private $featureCount = 0; + public $actualDepth = 0; + + /** + * @var array + */ + protected $columnTypes = []; /** * @var DecisionTreeLeaf @@ -42,10 +38,15 @@ class DecisionTree implements Classifier */ protected $maxDepth; + /** + * @var array + */ + private $labels = []; + /** * @var int */ - public $actualDepth = 0; + private $featureCount = 0; /** * @var int @@ -55,7 +56,7 @@ class DecisionTree implements Classifier /** * @var array */ - private $selectedFeatures; + private $selectedFeatures = []; /** * @var array @@ -100,7 +101,7 @@ class DecisionTree implements Classifier } } - public static function getColumnTypes(array $samples) : array + public static function getColumnTypes(array $samples): array { $types = []; $featureCount = count($samples[0]); @@ -113,7 +114,122 @@ class DecisionTree implements Classifier return $types; } - protected function getSplitLeaf(array $records, int $depth = 0) : DecisionTreeLeaf + /** + * @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.
+ * 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.
+ */ + 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 { $split = $this->getBestSplit($records); $split->level = $depth; @@ -136,6 +252,7 @@ class DecisionTree implements Classifier if ($prevRecord && $prevRecord != $record) { $allSame = false; } + $prevRecord = $record; // According to the split criteron, this record will @@ -163,6 +280,7 @@ class DecisionTree implements Classifier if ($leftRecords) { $split->leftLeaf = $this->getSplitLeaf($leftRecords, $depth + 1); } + if ($rightRecords) { $split->rightLeaf = $this->getSplitLeaf($rightRecords, $depth + 1); } @@ -171,7 +289,7 @@ class DecisionTree implements Classifier return $split; } - protected function getBestSplit(array $records) : DecisionTreeLeaf + protected function getBestSplit(array $records): DecisionTreeLeaf { $targets = array_intersect_key($this->targets, array_flip($records)); $samples = array_intersect_key($this->samples, array_flip($records)); @@ -184,6 +302,7 @@ class DecisionTree implements Classifier foreach ($samples as $index => $row) { $colValues[$index] = $row[$i]; } + $counts = array_count_values($colValues); arsort($counts); $baseValue = key($counts); @@ -227,7 +346,7 @@ class DecisionTree implements Classifier * If any of above methods were not called beforehand, then all features * are returned by default. */ - protected function getSelectedFeatures() : array + protected function getSelectedFeatures(): array { $allFeatures = range(0, $this->featureCount - 1); if ($this->numUsableFeatures === 0 && !$this->selectedFeatures) { @@ -242,6 +361,7 @@ class DecisionTree implements Classifier if ($numFeatures > $this->featureCount) { $numFeatures = $this->featureCount; } + shuffle($allFeatures); $selectedFeatures = array_slice($allFeatures, 0, $numFeatures, false); sort($selectedFeatures); @@ -249,39 +369,7 @@ class DecisionTree implements Classifier 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 // discrete values by using the median as a threshold value @@ -298,14 +386,16 @@ class DecisionTree implements Classifier } } } + $columns[] = $values; } + // Below method is a strange yet very simple & efficient method // to get the transpose of a 2D array return array_map(null, ...$columns); } - protected static function isCategoricalColumn(array $columnValues) : bool + protected static function isCategoricalColumn(array $columnValues): bool { $count = count($columnValues); @@ -329,28 +419,6 @@ class DecisionTree implements Classifier 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.
- * 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 */ @@ -359,71 +427,11 @@ class DecisionTree implements Classifier $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.
- */ - 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 * column as a split criterion */ - protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node) : array + protected function getSplitNodesByColumn(int $column, DecisionTreeLeaf $node): array { if (!$node || $node->isTerminal) { return []; diff --git a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php index 2bcc3ac..f3f9449 100644 --- a/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php +++ b/src/Phpml/Classification/DecisionTree/DecisionTreeLeaf.php @@ -71,7 +71,15 @@ class DecisionTreeLeaf */ public $level = 0; - public function evaluate(array $record) : bool + /** + * HTML representation of the tree without column names + */ + public function __toString(): string + { + return $this->getHTML(); + } + + public function evaluate(array $record): bool { $recordField = $record[$this->columnIndex]; @@ -86,7 +94,7 @@ class DecisionTreeLeaf * Returns Mean Decrease Impurity (MDI) in the node. * For terminal nodes, this value is equal to 0 */ - public function getNodeImpurityDecrease(int $parentRecordCount) : float + public function getNodeImpurityDecrease(int $parentRecordCount): float { if ($this->isTerminal) { return 0.0; @@ -111,7 +119,7 @@ class DecisionTreeLeaf /** * Returns HTML representation of the node including children nodes */ - public function getHTML($columnNames = null) : string + public function getHTML($columnNames = null): string { if ($this->isTerminal) { $value = "$this->classValue"; @@ -154,12 +162,4 @@ class DecisionTreeLeaf return $str; } - - /** - * HTML representation of the tree without column names - */ - public function __toString() : string - { - return $this->getHTML(); - } } diff --git a/src/Phpml/Classification/Ensemble/AdaBoost.php b/src/Phpml/Classification/Ensemble/AdaBoost.php index 5bdca1b..67f7198 100644 --- a/src/Phpml/Classification/Ensemble/AdaBoost.php +++ b/src/Phpml/Classification/Ensemble/AdaBoost.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Classification\Ensemble; +use Exception; use Phpml\Classification\Classifier; use Phpml\Classification\Linear\DecisionStump; use Phpml\Classification\WeightedClassifier; @@ -11,6 +12,7 @@ use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; use Phpml\Math\Statistic\Mean; use Phpml\Math\Statistic\StandardDeviation; +use ReflectionClass; class AdaBoost implements Classifier { @@ -98,11 +100,14 @@ class AdaBoost implements Classifier // Initialize usual variables $this->labels = array_keys(array_count_values($targets)); if (count($this->labels) != 2) { - throw new \Exception('AdaBoost is a binary classifier and can classify between two classes only'); + throw new Exception('AdaBoost is a binary classifier and can classify between two classes only'); } // 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) { $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 * 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) { $classifier = $ref->newInstanceArgs($this->classifierOptions); } else { @@ -160,7 +179,7 @@ class AdaBoost implements Classifier * Resamples the dataset in accordance with the weights and * returns the new dataset */ - protected function resample() : array + protected function resample(): array { $weights = $this->weights; $std = StandardDeviation::population($weights); @@ -173,9 +192,10 @@ class AdaBoost implements Classifier foreach ($weights as $index => $weight) { $z = (int) round(($weight - $mean) / $std) - $minZ + 1; for ($i = 0; $i < $z; ++$i) { - if (rand(0, 1) == 0) { + if (random_int(0, 1) == 0) { continue; } + $samples[] = $this->samples[$index]; $targets[] = $this->targets[$index]; } @@ -187,7 +207,7 @@ class AdaBoost implements Classifier /** * Evaluates the classifier and returns the classification error rate */ - protected function evaluateClassifier(Classifier $classifier) : float + protected function evaluateClassifier(Classifier $classifier): float { $total = (float) array_sum($this->weights); $wrong = 0; @@ -204,7 +224,7 @@ class AdaBoost implements Classifier /** * Calculates alpha of a classifier */ - protected function calculateAlpha(float $errorRate) : float + protected function calculateAlpha(float $errorRate): float { if ($errorRate == 0) { $errorRate = 1e-10; @@ -231,18 +251,4 @@ class AdaBoost implements Classifier $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]; - } } diff --git a/src/Phpml/Classification/Ensemble/Bagging.php b/src/Phpml/Classification/Ensemble/Bagging.php index 8d2bbf9..6fa1ec8 100644 --- a/src/Phpml/Classification/Ensemble/Bagging.php +++ b/src/Phpml/Classification/Ensemble/Bagging.php @@ -4,10 +4,12 @@ declare(strict_types=1); namespace Phpml\Classification\Ensemble; +use Exception; use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; use Phpml\Helper\Predictable; use Phpml\Helper\Trainable; +use ReflectionClass; class Bagging implements Classifier { @@ -18,11 +20,6 @@ class Bagging implements Classifier */ protected $numSamples; - /** - * @var array - */ - private $targets = []; - /** * @var int */ @@ -46,13 +43,18 @@ class Bagging implements Classifier /** * @var array */ - protected $classifiers; + protected $classifiers = []; /** * @var float */ protected $subsetRatio = 0.7; + /** + * @var array + */ + private $targets = []; + /** * @var array */ @@ -80,7 +82,7 @@ class Bagging implements Classifier public function setSubsetRatio(float $ratio) { if ($ratio < 0.1 || $ratio > 1.0) { - throw new \Exception('Subset ratio should be between 0.1 and 1.0'); + throw new Exception('Subset ratio should be between 0.1 and 1.0'); } $this->subsetRatio = $ratio; @@ -123,14 +125,14 @@ class Bagging implements Classifier } } - protected function getRandomSubset(int $index) : array + protected function getRandomSubset(int $index): array { $samples = []; $targets = []; srand($index); $bootstrapSize = $this->subsetRatio * $this->numSamples; for ($i = 0; $i < $bootstrapSize; ++$i) { - $rand = rand(0, $this->numSamples - 1); + $rand = random_int(0, $this->numSamples - 1); $samples[] = $this->samples[$rand]; $targets[] = $this->targets[$rand]; } @@ -138,11 +140,11 @@ class Bagging implements Classifier return [$samples, $targets]; } - protected function initClassifiers() : array + protected function initClassifiers(): array { $classifiers = []; for ($i = 0; $i < $this->numClassifier; ++$i) { - $ref = new \ReflectionClass($this->classifier); + $ref = new ReflectionClass($this->classifier); if ($this->classifierOptions) { $obj = $ref->newInstanceArgs($this->classifierOptions); } else { @@ -155,12 +157,7 @@ class Bagging implements Classifier return $classifiers; } - /** - * @param Classifier $classifier - * - * @return Classifier - */ - protected function initSingleClassifier($classifier) + protected function initSingleClassifier(Classifier $classifier): Classifier { return $classifier; } diff --git a/src/Phpml/Classification/Ensemble/RandomForest.php b/src/Phpml/Classification/Ensemble/RandomForest.php index 4928ea5..59f19c1 100644 --- a/src/Phpml/Classification/Ensemble/RandomForest.php +++ b/src/Phpml/Classification/Ensemble/RandomForest.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Ensemble; +use Exception; +use Phpml\Classification\Classifier; use Phpml\Classification\DecisionTree; class RandomForest extends Bagging @@ -48,11 +50,11 @@ class RandomForest extends Bagging public function setFeatureSubsetRatio($ratio) { 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') { - 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; @@ -70,7 +72,7 @@ class RandomForest extends Bagging public function setClassifer(string $classifier, array $classifierOptions = []) { 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); @@ -81,7 +83,7 @@ class RandomForest extends Bagging * each column in the given dataset. Importance values for a column * is the average importance of that column in all trees in the forest */ - public function getFeatureImportances() : array + public function getFeatureImportances(): array { // Traverse each tree and sum importance of the columns $sum = []; @@ -127,7 +129,7 @@ class RandomForest extends Bagging * * @return DecisionTree */ - protected function initSingleClassifier($classifier) + protected function initSingleClassifier(Classifier $classifier): Classifier { if (is_float($this->featureSubsetRatio)) { $featureCount = (int) ($this->featureSubsetRatio * $this->featureCount); diff --git a/src/Phpml/Classification/KNearestNeighbors.php b/src/Phpml/Classification/KNearestNeighbors.php index a261631..238fc1d 100644 --- a/src/Phpml/Classification/KNearestNeighbors.php +++ b/src/Phpml/Classification/KNearestNeighbors.php @@ -28,7 +28,7 @@ class KNearestNeighbors implements Classifier */ public function __construct(int $k = 3, ?Distance $distanceMetric = null) { - if (null === $distanceMetric) { + if ($distanceMetric === null) { $distanceMetric = new Euclidean(); } @@ -60,7 +60,7 @@ class KNearestNeighbors implements Classifier /** * @throws \Phpml\Exception\InvalidArgumentException */ - private function kNeighborsDistances(array $sample) : array + private function kNeighborsDistances(array $sample): array { $distances = []; diff --git a/src/Phpml/Classification/Linear/Adaline.php b/src/Phpml/Classification/Linear/Adaline.php index 64d25a4..cda746f 100644 --- a/src/Phpml/Classification/Linear/Adaline.php +++ b/src/Phpml/Classification/Linear/Adaline.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; +use Exception; + class Adaline extends Perceptron { /** @@ -41,7 +43,7 @@ class Adaline extends Perceptron int $trainingType = self::BATCH_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; diff --git a/src/Phpml/Classification/Linear/DecisionStump.php b/src/Phpml/Classification/Linear/DecisionStump.php index e1486a6..3f6eb58 100644 --- a/src/Phpml/Classification/Linear/DecisionStump.php +++ b/src/Phpml/Classification/Linear/DecisionStump.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; +use Exception; use Phpml\Classification\DecisionTree; use Phpml\Classification\WeightedClassifier; use Phpml\Helper\OneVsRest; @@ -24,7 +25,7 @@ class DecisionStump extends WeightedClassifier /** * @var array */ - protected $binaryLabels; + protected $binaryLabels = []; /** * Lowest error rate obtained while training/optimizing the model @@ -51,7 +52,7 @@ class DecisionStump extends WeightedClassifier /** * @var array */ - protected $columnTypes; + protected $columnTypes = []; /** * @var int @@ -68,7 +69,7 @@ class DecisionStump extends WeightedClassifier * * @var array */ - protected $prob; + protected $prob = []; /** * A DecisionStump classifier is a one-level deep DecisionTree. It is generally @@ -83,6 +84,25 @@ class DecisionStump extends WeightedClassifier $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 $count 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 */ @@ -101,7 +121,7 @@ class DecisionStump extends WeightedClassifier if ($this->weights) { $numWeights = count($this->weights); if ($numWeights != count($samples)) { - throw new \Exception('Number of sample weights does not match with number of samples'); + throw new Exception('Number of sample weights does not match with number of samples'); } } else { $this->weights = array_fill(0, count($samples), 1); @@ -118,9 +138,12 @@ class DecisionStump extends WeightedClassifier } $bestSplit = [ - 'value' => 0, 'operator' => '', - 'prob' => [], 'column' => 0, - 'trainingErrorRate' => 1.0]; + 'value' => 0, + 'operator' => '', + 'prob' => [], + 'column' => 0, + 'trainingErrorRate' => 1.0, + ]; foreach ($columns as $col) { if ($this->columnTypes[$col] == DecisionTree::CONTINUOUS) { $split = $this->getBestNumericalSplit($samples, $targets, $col); @@ -139,22 +162,10 @@ 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 $count 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 */ - protected function getBestNumericalSplit(array $samples, array $targets, int $col) : array + protected function getBestNumericalSplit(array $samples, array $targets, int $col): array { $values = array_column($samples, $col); // Trying all possible points may be accomplished in two general ways: @@ -173,9 +184,13 @@ class DecisionStump extends WeightedClassifier $threshold = array_sum($values) / (float) count($values); [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($split == null || $errorRate < $split['trainingErrorRate']) { - $split = ['value' => $threshold, 'operator' => $operator, - 'prob' => $prob, 'column' => $col, - 'trainingErrorRate' => $errorRate]; + $split = [ + 'value' => $threshold, + 'operator' => $operator, + 'prob' => $prob, + 'column' => $col, + 'trainingErrorRate' => $errorRate, + ]; } // Try other possible points one by one @@ -183,9 +198,13 @@ class DecisionStump extends WeightedClassifier $threshold = (float) $step; [$errorRate, $prob] = $this->calculateErrorRate($targets, $threshold, $operator, $values); if ($errorRate < $split['trainingErrorRate']) { - $split = ['value' => $threshold, 'operator' => $operator, - 'prob' => $prob, 'column' => $col, - 'trainingErrorRate' => $errorRate]; + $split = [ + 'value' => $threshold, + 'operator' => $operator, + 'prob' => $prob, + 'column' => $col, + 'trainingErrorRate' => $errorRate, + ]; } }// for } @@ -193,7 +212,7 @@ class DecisionStump extends WeightedClassifier return $split; } - protected function getBestNominalSplit(array $samples, array $targets, int $col) : array + protected function getBestNominalSplit(array $samples, array $targets, int $col): array { $values = array_column($samples, $col); $valueCounts = array_count_values($values); @@ -206,9 +225,13 @@ class DecisionStump extends WeightedClassifier [$errorRate, $prob] = $this->calculateErrorRate($targets, $val, $operator, $values); if ($split == null || $split['trainingErrorRate'] < $errorRate) { - $split = ['value' => $val, 'operator' => $operator, - 'prob' => $prob, 'column' => $col, - 'trainingErrorRate' => $errorRate]; + $split = [ + 'value' => $val, + 'operator' => $operator, + 'prob' => $prob, + 'column' => $col, + 'trainingErrorRate' => $errorRate, + ]; } } } @@ -220,7 +243,7 @@ class DecisionStump extends WeightedClassifier * Calculates the ratio of wrong predictions based on the new threshold * value given as the parameter */ - protected function calculateErrorRate(array $targets, float $threshold, string $operator, array $values) : array + protected function calculateErrorRate(array $targets, float $threshold, string $operator, array $values): array { $wrong = 0.0; $prob = []; @@ -242,6 +265,7 @@ class DecisionStump extends WeightedClassifier if (!isset($prob[$predicted][$target])) { $prob[$predicted][$target] = 0; } + ++$prob[$predicted][$target]; } @@ -267,7 +291,7 @@ class DecisionStump extends WeightedClassifier * * @param mixed $label */ - protected function predictProbability(array $sample, $label) : float + protected function predictProbability(array $sample, $label): float { $predicted = $this->predictSampleBinary($sample); if ((string) $predicted == (string) $label) { @@ -292,11 +316,4 @@ class DecisionStump extends WeightedClassifier protected function resetBinary(): void { } - - public function __toString() : string - { - return "IF $this->column $this->operator $this->value ". - 'THEN '.$this->binaryLabels[0].' '. - 'ELSE '.$this->binaryLabels[1]; - } } diff --git a/src/Phpml/Classification/Linear/LogisticRegression.php b/src/Phpml/Classification/Linear/LogisticRegression.php index e8881be..6b8cdd5 100644 --- a/src/Phpml/Classification/Linear/LogisticRegression.php +++ b/src/Phpml/Classification/Linear/LogisticRegression.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; +use Closure; +use Exception; use Phpml\Helper\Optimizer\ConjugateGradient; class LogisticRegression extends Adaline @@ -70,18 +72,18 @@ class LogisticRegression extends Adaline ) { $trainingTypes = range(self::BATCH_TRAINING, self::CONJUGATE_GRAD_TRAINING); 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) '. 'or conjugate batch (conjugate gradients) algorithms'); } 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"); } 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; @@ -132,14 +134,14 @@ class LogisticRegression extends Adaline return $this->runConjugateGradient($samples, $targets, $callback); 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 */ - protected function runConjugateGradient(array $samples, array $targets, \Closure $gradientFunc): void + protected function runConjugateGradient(array $samples, array $targets, Closure $gradientFunc): void { if (empty($this->optimizer)) { $this->optimizer = (new ConjugateGradient($this->featureCount)) @@ -155,7 +157,7 @@ class LogisticRegression extends Adaline * * @throws \Exception */ - protected function getCostFunction() : \Closure + protected function getCostFunction(): Closure { $penalty = 0; if ($this->penalty == 'L2') { @@ -183,9 +185,11 @@ class LogisticRegression extends Adaline if ($hX == 1) { $hX = 1 - 1e-10; } + if ($hX == 0) { $hX = 1e-10; } + $error = -$y * log($hX) - (1 - $y) * log(1 - $hX); $gradient = $hX - $y; @@ -218,16 +222,14 @@ class LogisticRegression extends Adaline return $callback; 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 - * - * @return float */ - protected function output(array $sample) + protected function output(array $sample): float { $sum = parent::output($sample); @@ -237,7 +239,7 @@ class LogisticRegression extends Adaline /** * Returns the class value (either -1 or 1) for the given input */ - protected function outputClass(array $sample) : int + protected function outputClass(array $sample): int { $output = $this->output($sample); @@ -253,10 +255,10 @@ class LogisticRegression extends Adaline * * The probability is simply taken as the distance of the sample * to the decision plane. - + * * @param mixed $label */ - protected function predictProbability(array $sample, $label) : float + protected function predictProbability(array $sample, $label): float { $predicted = $this->predictSampleBinary($sample); diff --git a/src/Phpml/Classification/Linear/Perceptron.php b/src/Phpml/Classification/Linear/Perceptron.php index 77eb717..0db7496 100644 --- a/src/Phpml/Classification/Linear/Perceptron.php +++ b/src/Phpml/Classification/Linear/Perceptron.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Classification\Linear; +use Closure; +use Exception; use Phpml\Classification\Classifier; use Phpml\Helper\OneVsRest; use Phpml\Helper\Optimizer\GD; @@ -34,7 +36,7 @@ class Perceptron implements Classifier, IncrementalEstimator /** * @var array */ - protected $weights; + protected $weights = []; /** * @var float @@ -73,11 +75,11 @@ class Perceptron implements Classifier, IncrementalEstimator public function __construct(float $learningRate = 0.001, int $maxIterations = 1000, bool $normalizeInputs = true) { if ($learningRate <= 0.0 || $learningRate > 1.0) { - throw new \Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); + throw new Exception('Learning rate should be a float value between 0.0(exclusive) and 1.0(inclusive)'); } if ($maxIterations <= 0) { - throw new \Exception('Maximum number of iterations must be an integer greater than 0'); + throw new Exception('Maximum number of iterations must be an integer greater than 0'); } if ($normalizeInputs) { @@ -100,7 +102,10 @@ class Perceptron implements Classifier, IncrementalEstimator } // 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) { $targets[$key] = (string) $target == (string) $this->labels[1] ? 1 : -1; } @@ -111,15 +116,6 @@ class Perceptron implements Classifier, IncrementalEstimator $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 * help saving processing time while in some cases it may result in @@ -140,16 +136,23 @@ class Perceptron implements Classifier, IncrementalEstimator /** * Returns the cost values obtained during the training. */ - public function getCostValues() : array + public function getCostValues(): array { 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 * to get the correct set of weights - * - * @return void|mixed */ protected function runTraining(array $samples, array $targets) { @@ -171,7 +174,7 @@ class Perceptron implements Classifier, IncrementalEstimator * Executes a Gradient Descent algorithm for * 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; @@ -191,7 +194,7 @@ class Perceptron implements Classifier, IncrementalEstimator * Checks if the sample should be normalized and if so, returns the * normalized sample */ - protected function checkNormalizedSample(array $sample) : array + protected function checkNormalizedSample(array $sample): array { if ($this->normalizer) { $samples = [$sample]; @@ -205,7 +208,7 @@ class Perceptron implements Classifier, IncrementalEstimator /** * Calculates net output of the network as a float value for the given input * - * @return int + * @return int|float */ protected function output(array $sample) { @@ -224,7 +227,7 @@ class Perceptron implements Classifier, IncrementalEstimator /** * Returns the class value (either -1 or 1) for the given input */ - protected function outputClass(array $sample) : int + protected function outputClass(array $sample): int { return $this->output($sample) > 0 ? 1 : -1; } @@ -237,7 +240,7 @@ class Perceptron implements Classifier, IncrementalEstimator * * @param mixed $label */ - protected function predictProbability(array $sample, $label) : float + protected function predictProbability(array $sample, $label): float { $predicted = $this->predictSampleBinary($sample); diff --git a/src/Phpml/Classification/MLPClassifier.php b/src/Phpml/Classification/MLPClassifier.php index b76091d..b225a64 100644 --- a/src/Phpml/Classification/MLPClassifier.php +++ b/src/Phpml/Classification/MLPClassifier.php @@ -14,7 +14,7 @@ class MLPClassifier extends MultilayerPerceptron implements Classifier * * @throws InvalidArgumentException */ - public function getTargetClass($target) : int + public function getTargetClass($target): int { if (!in_array($target, $this->classes)) { throw InvalidArgumentException::invalidTarget($target); diff --git a/src/Phpml/Classification/NaiveBayes.php b/src/Phpml/Classification/NaiveBayes.php index 97f734a..a470fd4 100644 --- a/src/Phpml/Classification/NaiveBayes.php +++ b/src/Phpml/Classification/NaiveBayes.php @@ -14,7 +14,9 @@ class NaiveBayes implements Classifier use Trainable, Predictable; public const CONTINUOS = 1; + public const NOMINAL = 2; + 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 * values in private array in order to avoid repeated calculation @@ -108,7 +135,7 @@ class NaiveBayes implements Classifier /** * Calculates the probability P(label|sample_n) */ - private function sampleProbability(array $sample, int $feature, string $label) : float + private function sampleProbability(array $sample, int $feature, string $label): float { $value = $sample[$feature]; if ($this->dataType[$label][$feature] == self::NOMINAL) { @@ -119,6 +146,7 @@ class NaiveBayes implements Classifier return $this->discreteProb[$label][$feature][$value]; } + $std = $this->std[$label][$feature] ; $mean = $this->mean[$label][$feature]; // Calculate the probability density by use of normal/Gaussian distribution @@ -137,7 +165,7 @@ class NaiveBayes implements Classifier /** * Return samples belonging to specific label */ - private function getSamplesByLabel(string $label) : array + private function getSamplesByLabel(string $label): array { $samples = []; for ($i = 0; $i < $this->sampleCount; ++$i) { @@ -148,28 +176,4 @@ class NaiveBayes implements Classifier 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); - } } diff --git a/src/Phpml/Classification/WeightedClassifier.php b/src/Phpml/Classification/WeightedClassifier.php index c9b1f97..9834764 100644 --- a/src/Phpml/Classification/WeightedClassifier.php +++ b/src/Phpml/Classification/WeightedClassifier.php @@ -9,7 +9,7 @@ abstract class WeightedClassifier implements Classifier /** * @var array */ - protected $weights; + protected $weights = []; /** * Sets the array including a weight for each sample diff --git a/src/Phpml/Clustering/Clusterer.php b/src/Phpml/Clustering/Clusterer.php index ad24af2..22e65e6 100644 --- a/src/Phpml/Clustering/Clusterer.php +++ b/src/Phpml/Clustering/Clusterer.php @@ -6,5 +6,5 @@ namespace Phpml\Clustering; interface Clusterer { - public function cluster(array $samples) : array; + public function cluster(array $samples): array; } diff --git a/src/Phpml/Clustering/DBSCAN.php b/src/Phpml/Clustering/DBSCAN.php index 1968b83..3546ebf 100644 --- a/src/Phpml/Clustering/DBSCAN.php +++ b/src/Phpml/Clustering/DBSCAN.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Clustering; +use array_merge; use Phpml\Math\Distance; 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) { - if (null === $distanceMetric) { + if ($distanceMetric === null) { $distanceMetric = new Euclidean(); } @@ -35,7 +36,7 @@ class DBSCAN implements Clusterer $this->distanceMetric = $distanceMetric; } - public function cluster(array $samples) : array + public function cluster(array $samples): array { $clusters = []; $visited = []; @@ -44,6 +45,7 @@ class DBSCAN implements Clusterer if (isset($visited[$index])) { continue; } + $visited[$index] = true; $regionSamples = $this->getSamplesInRegion($sample, $samples); @@ -55,7 +57,7 @@ class DBSCAN implements Clusterer return $clusters; } - private function getSamplesInRegion(array $localSample, array $samples) : array + private function getSamplesInRegion(array $localSample, array $samples): array { $region = []; @@ -68,7 +70,7 @@ class DBSCAN implements Clusterer return $region; } - private function expandCluster(array $samples, array &$visited) : array + private function expandCluster(array $samples, array &$visited): array { $cluster = []; @@ -84,7 +86,8 @@ class DBSCAN implements Clusterer $cluster[$index] = $sample; } - $cluster = \array_merge($cluster, ...$clusterMerge); + + $cluster = array_merge($cluster, ...$clusterMerge); return $cluster; } diff --git a/src/Phpml/Clustering/FuzzyCMeans.php b/src/Phpml/Clustering/FuzzyCMeans.php index 6eccea0..d3be101 100644 --- a/src/Phpml/Clustering/FuzzyCMeans.php +++ b/src/Phpml/Clustering/FuzzyCMeans.php @@ -30,7 +30,7 @@ class FuzzyCMeans implements Clusterer /** * @var array|float[][] */ - private $membership; + private $membership = []; /** * @var float @@ -55,7 +55,7 @@ class FuzzyCMeans implements Clusterer /** * @var array */ - private $samples; + private $samples = []; /** * @throws InvalidArgumentException @@ -65,12 +65,63 @@ class FuzzyCMeans implements Clusterer if ($clustersNumber <= 0) { throw InvalidArgumentException::invalidClustersNumber(); } + $this->clustersNumber = $clustersNumber; $this->fuzziness = $fuzziness; $this->epsilon = $epsilon; $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 { // Membership array is a matrix of cluster number by sample counts @@ -87,7 +138,7 @@ class FuzzyCMeans implements Clusterer $row = []; $total = 0.0; for ($k = 0; $k < $cols; ++$k) { - $val = rand(1, 5) / 10.0; + $val = random_int(1, 5) / 10.0; $row[] = $val; $total += $val; } @@ -146,13 +197,13 @@ class FuzzyCMeans implements Clusterer } } - protected function getDistanceCalc(int $row, int $col) : float + protected function getDistanceCalc(int $row, int $col): float { $sum = 0.0; $distance = new Euclidean(); $dist1 = $distance->distance( - $this->clusters[$row]->getCoordinates(), - $this->samples[$col] + $this->clusters[$row]->getCoordinates(), + $this->samples[$col] ); for ($j = 0; $j < $this->clustersNumber; ++$j) { @@ -187,54 +238,4 @@ class FuzzyCMeans implements Clusterer 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; - } } diff --git a/src/Phpml/Clustering/KMeans.php b/src/Phpml/Clustering/KMeans.php index a4e85bc..78a2e4a 100644 --- a/src/Phpml/Clustering/KMeans.php +++ b/src/Phpml/Clustering/KMeans.php @@ -10,6 +10,7 @@ use Phpml\Exception\InvalidArgumentException; class KMeans implements Clusterer { public const INIT_RANDOM = 1; + public const INIT_KMEANS_PLUS_PLUS = 2; /** @@ -32,7 +33,7 @@ class KMeans implements Clusterer $this->initialization = $initialization; } - public function cluster(array $samples) : array + public function cluster(array $samples): array { $space = new Space(count($samples[0])); foreach ($samples as $sample) { diff --git a/src/Phpml/Clustering/KMeans/Cluster.php b/src/Phpml/Clustering/KMeans/Cluster.php index 22545b6..fea1ff8 100644 --- a/src/Phpml/Clustering/KMeans/Cluster.php +++ b/src/Phpml/Clustering/KMeans/Cluster.php @@ -28,7 +28,7 @@ class Cluster extends Point implements IteratorAggregate, Countable $this->points = new SplObjectStorage(); } - public function getPoints() : array + public function getPoints(): array { $points = []; foreach ($this->points as $point) { @@ -38,7 +38,7 @@ class Cluster extends Point implements IteratorAggregate, Countable return $points; } - public function toArray() : array + public function toArray(): array { return [ 'centroid' => parent::toArray(), @@ -46,7 +46,7 @@ class Cluster extends Point implements IteratorAggregate, Countable ]; } - public function attach(Point $point) : Point + public function attach(Point $point): Point { if ($point instanceof self) { throw new LogicException('cannot attach a cluster to another'); @@ -57,7 +57,7 @@ class Cluster extends Point implements IteratorAggregate, Countable return $point; } - public function detach(Point $point) : Point + public function detach(Point $point): Point { $this->points->detach($point); @@ -76,7 +76,8 @@ class Cluster extends Point implements IteratorAggregate, Countable public function updateCentroid(): void { - if (!$count = count($this->points)) { + $count = count($this->points); + if (!$count) { return; } diff --git a/src/Phpml/Clustering/KMeans/Point.php b/src/Phpml/Clustering/KMeans/Point.php index f90de8a..6aa40a9 100644 --- a/src/Phpml/Clustering/KMeans/Point.php +++ b/src/Phpml/Clustering/KMeans/Point.php @@ -16,7 +16,7 @@ class Point implements ArrayAccess /** * @var array */ - protected $coordinates; + protected $coordinates = []; public function __construct(array $coordinates) { @@ -24,7 +24,7 @@ class Point implements ArrayAccess $this->coordinates = $coordinates; } - public function toArray() : array + public function toArray(): array { return $this->coordinates; } @@ -66,7 +66,7 @@ class Point implements ArrayAccess return $minPoint; } - public function getCoordinates() : array + public function getCoordinates(): array { return $this->coordinates; } diff --git a/src/Phpml/Clustering/KMeans/Space.php b/src/Phpml/Clustering/KMeans/Space.php index 4412d53..0d4adf5 100644 --- a/src/Phpml/Clustering/KMeans/Space.php +++ b/src/Phpml/Clustering/KMeans/Space.php @@ -25,7 +25,7 @@ class Space extends SplObjectStorage $this->dimension = $dimension; } - public function toArray() : array + public function toArray(): array { $points = []; foreach ($this as $point) { @@ -35,7 +35,7 @@ class Space extends SplObjectStorage return ['points' => $points]; } - public function newPoint(array $coordinates) : Point + public function newPoint(array $coordinates): Point { if (count($coordinates) != $this->dimension) { throw new LogicException('('.implode(',', $coordinates).') is not a point of this space'); @@ -65,7 +65,7 @@ class Space extends SplObjectStorage parent::attach($point, $data); } - public function getDimension() : int + public function getDimension(): int { return $this->dimension; } @@ -92,7 +92,7 @@ class Space extends SplObjectStorage return [$min, $max]; } - public function getRandomPoint(Point $min, Point $max) : Point + public function getRandomPoint(Point $min, Point $max): Point { $point = $this->newPoint(array_fill(0, $this->dimension, null)); @@ -106,7 +106,7 @@ class Space extends SplObjectStorage /** * @return array|Cluster[] */ - public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM) : array + public function cluster(int $clustersNumber, int $initMethod = KMeans::INIT_RANDOM): array { $clusters = $this->initializeClusters($clustersNumber, $initMethod); @@ -119,7 +119,7 @@ class Space extends SplObjectStorage /** * @return array|Cluster[] */ - protected function initializeClusters(int $clustersNumber, int $initMethod) : array + protected function initializeClusters(int $clustersNumber, int $initMethod): array { switch ($initMethod) { case KMeans::INIT_RANDOM: @@ -139,7 +139,7 @@ class Space extends SplObjectStorage return $clusters; } - protected function iterate($clusters) : bool + protected function iterate($clusters): bool { $convergence = true; @@ -177,19 +177,7 @@ class Space extends SplObjectStorage 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 = []; $this->rewind(); @@ -218,4 +206,16 @@ class Space extends SplObjectStorage 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; + } } diff --git a/src/Phpml/CrossValidation/Split.php b/src/Phpml/CrossValidation/Split.php index e485ffb..96c9019 100644 --- a/src/Phpml/CrossValidation/Split.php +++ b/src/Phpml/CrossValidation/Split.php @@ -31,39 +31,40 @@ abstract class Split 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'); } + $this->seedGenerator($seed); $this->splitDataset($dataset, $testSize); } - abstract protected function splitDataset(Dataset $dataset, float $testSize); - - public function getTrainSamples() : array + public function getTrainSamples(): array { return $this->trainSamples; } - public function getTestSamples() : array + public function getTestSamples(): array { return $this->testSamples; } - public function getTrainLabels() : array + public function getTrainLabels(): array { return $this->trainLabels; } - public function getTestLabels() : array + public function getTestLabels(): array { return $this->testLabels; } + abstract protected function splitDataset(Dataset $dataset, float $testSize); + protected function seedGenerator(?int $seed = null): void { - if (null === $seed) { + if ($seed === null) { mt_srand(); } else { mt_srand($seed); diff --git a/src/Phpml/CrossValidation/StratifiedRandomSplit.php b/src/Phpml/CrossValidation/StratifiedRandomSplit.php index 153cb8f..d450842 100644 --- a/src/Phpml/CrossValidation/StratifiedRandomSplit.php +++ b/src/Phpml/CrossValidation/StratifiedRandomSplit.php @@ -21,7 +21,7 @@ class StratifiedRandomSplit extends RandomSplit /** * @return Dataset[]|array */ - private function splitByTarget(Dataset $dataset) : array + private function splitByTarget(Dataset $dataset): array { $targets = $dataset->getTargets(); $samples = $dataset->getSamples(); @@ -38,7 +38,7 @@ class StratifiedRandomSplit extends RandomSplit return $datasets; } - private function createDatasets(array $uniqueTargets, array $split) : array + private function createDatasets(array $uniqueTargets, array $split): array { $datasets = []; foreach ($uniqueTargets as $target) { diff --git a/src/Phpml/Dataset/ArrayDataset.php b/src/Phpml/Dataset/ArrayDataset.php index e27b2e3..7d30b0b 100644 --- a/src/Phpml/Dataset/ArrayDataset.php +++ b/src/Phpml/Dataset/ArrayDataset.php @@ -31,12 +31,12 @@ class ArrayDataset implements Dataset $this->targets = $targets; } - public function getSamples() : array + public function getSamples(): array { return $this->samples; } - public function getTargets() : array + public function getTargets(): array { return $this->targets; } diff --git a/src/Phpml/Dataset/CsvDataset.php b/src/Phpml/Dataset/CsvDataset.php index ef33e2c..f88fe31 100644 --- a/src/Phpml/Dataset/CsvDataset.php +++ b/src/Phpml/Dataset/CsvDataset.php @@ -11,7 +11,7 @@ class CsvDataset extends ArrayDataset /** * @var array */ - protected $columnNames; + protected $columnNames = []; /** * @throws FileException @@ -22,7 +22,8 @@ class CsvDataset extends ArrayDataset throw FileException::missingFile(basename($filepath)); } - if (false === $handle = fopen($filepath, 'rb')) { + $handle = fopen($filepath, 'rb'); + if ($handle === false) { throw FileException::cantOpenFile(basename($filepath)); } @@ -44,7 +45,7 @@ class CsvDataset extends ArrayDataset parent::__construct($samples, $targets); } - public function getColumnNames() : array + public function getColumnNames(): array { return $this->columnNames; } diff --git a/src/Phpml/Dataset/Dataset.php b/src/Phpml/Dataset/Dataset.php index ce75a8a..f851d85 100644 --- a/src/Phpml/Dataset/Dataset.php +++ b/src/Phpml/Dataset/Dataset.php @@ -9,10 +9,10 @@ interface Dataset /** * @return array */ - public function getSamples() : array; + public function getSamples(): array; /** * @return array */ - public function getTargets() : array; + public function getTargets(): array; } diff --git a/src/Phpml/DimensionReduction/EigenTransformerBase.php b/src/Phpml/DimensionReduction/EigenTransformerBase.php index a6352ba..ec64163 100644 --- a/src/Phpml/DimensionReduction/EigenTransformerBase.php +++ b/src/Phpml/DimensionReduction/EigenTransformerBase.php @@ -84,7 +84,7 @@ abstract class EigenTransformerBase /** * Returns the reduced data */ - protected function reduce(array $data) : array + protected function reduce(array $data): array { $m1 = new Matrix($data); $m2 = new Matrix($this->eigVectors); diff --git a/src/Phpml/DimensionReduction/KernelPCA.php b/src/Phpml/DimensionReduction/KernelPCA.php index d11e1a6..1981cb5 100644 --- a/src/Phpml/DimensionReduction/KernelPCA.php +++ b/src/Phpml/DimensionReduction/KernelPCA.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\DimensionReduction; +use Closure; +use Exception; use Phpml\Math\Distance\Euclidean; use Phpml\Math\Distance\Manhattan; use Phpml\Math\Matrix; @@ -11,8 +13,11 @@ use Phpml\Math\Matrix; class KernelPCA extends PCA { public const KERNEL_RBF = 1; + public const KERNEL_SIGMOID = 2; + public const KERNEL_LAPLACIAN = 3; + public const KERNEL_LINEAR = 4; /** @@ -34,7 +39,7 @@ class KernelPCA extends PCA * * @var array */ - protected $data; + protected $data = []; /** * 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]; 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); @@ -69,7 +74,7 @@ class KernelPCA extends PCA * $data is an n-by-m matrix and returned array is * n-by-k matrix where k <= m */ - public function fit(array $data) : array + public function fit(array $data): array { $numRows = count($data); $this->data = $data; @@ -88,11 +93,32 @@ class KernelPCA extends PCA return Matrix::transposeArray($this->eigVectors); } + /** + * Transforms the given sample to a lower dimensional vector by using + * the variables obtained during the last run of fit. + * + * @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
* An n-by-m matrix is given and an n-by-n matrix is returned */ - protected function calculateKernelMatrix(array $data, int $numRows) : array + protected function calculateKernelMatrix(array $data, int $numRows): array { $kernelFunc = $this->getKernel(); @@ -116,7 +142,7 @@ class KernelPCA extends PCA * * K′ = K − N.K − K.N + N.K.N where N is n-by-n matrix filled with 1/n */ - protected function centerMatrix(array $matrix, int $n) : array + protected function centerMatrix(array $matrix, int $n): array { $N = array_fill(0, $n, array_fill(0, $n, 1.0 / $n)); $N = new Matrix($N, false); @@ -140,7 +166,7 @@ class KernelPCA extends PCA * * @throws \Exception */ - protected function getKernel(): \Closure + protected function getKernel(): Closure { switch ($this->kernel) { case self::KERNEL_LINEAR: @@ -173,11 +199,11 @@ class KernelPCA extends PCA }; 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)); } } - protected function getDistancePairs(array $sample) : array + protected function getDistancePairs(array $sample): array { $kernel = $this->getKernel(); @@ -189,7 +215,7 @@ class KernelPCA extends PCA return $pairs; } - protected function projectSample(array $pairs) : array + protected function projectSample(array $pairs): array { // Normalize eigenvectors by eig = eigVectors / eigValues $func = function ($eigVal, $eigVect) { @@ -203,25 +229,4 @@ class KernelPCA extends PCA // return k.dot(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 fit. - * - * @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); - } } diff --git a/src/Phpml/DimensionReduction/LDA.php b/src/Phpml/DimensionReduction/LDA.php index 26b2324..6400d14 100644 --- a/src/Phpml/DimensionReduction/LDA.php +++ b/src/Phpml/DimensionReduction/LDA.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\DimensionReduction; +use Exception; use Phpml\Math\Matrix; class LDA extends EigenTransformerBase @@ -16,22 +17,22 @@ class LDA extends EigenTransformerBase /** * @var array */ - public $labels; + public $labels = []; /** * @var array */ - public $means; + public $means = []; /** * @var array */ - public $counts; + public $counts = []; /** * @var float[] */ - public $overallMean; + public $overallMean = []; /** * 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) { 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) { - 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) { - 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) { $this->numFeatures = $numFeatures; } + if ($totalVariance !== null) { $this->totalVariance = $totalVariance; } @@ -70,7 +74,7 @@ class LDA extends EigenTransformerBase /** * Trains the algorithm to transform the given data to a lower dimensional space. */ - public function fit(array $data, array $classes) : array + public function fit(array $data, array $classes): array { $this->labels = $this->getLabels($classes); $this->means = $this->calculateMeans($data, $classes); @@ -86,10 +90,29 @@ class LDA extends EigenTransformerBase return $this->reduce($data); } + /** + * Transforms the given sample to a lower dimensional vector by using + * the eigenVectors obtained in the last run of fit. + * + * @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 */ - protected function getLabels(array $classes) : array + protected function getLabels(array $classes): array { $counts = array_count_values($classes); @@ -100,7 +123,7 @@ class LDA extends EigenTransformerBase * Calculates mean of each column for each class and returns * n by m matrix where n is number of labels and m is number of columns */ - protected function calculateMeans(array $data, array $classes) : array + protected function calculateMeans(array $data, array $classes): array { $means = []; $counts = []; @@ -113,6 +136,7 @@ class LDA extends EigenTransformerBase if (!isset($means[$label][$col])) { $means[$label][$col] = 0.0; } + $means[$label][$col] += $val; $overallMean[$col] += $val; } @@ -146,7 +170,7 @@ class LDA extends EigenTransformerBase * is a n by m matrix where n is number of classes and * m is number of columns */ - protected function calculateClassVar(array $data, array $classes) : Matrix + protected function calculateClassVar(array $data, array $classes): Matrix { // s is an n (number of classes) by m (number of column) matrix $s = array_fill(0, count($data[0]), array_fill(0, count($data[0]), 0)); @@ -169,7 +193,7 @@ class LDA extends EigenTransformerBase * is an n by m matrix where n is number of classes and * m is number of columns */ - protected function calculateClassCov() : Matrix + protected function calculateClassCov(): Matrix { // s is an n (number of classes) by m (number of column) matrix $s = array_fill(0, count($this->overallMean), array_fill(0, count($this->overallMean), 0)); @@ -187,7 +211,7 @@ class LDA extends EigenTransformerBase /** * Returns the result of the calculation (x - m)T.(x - m) */ - protected function calculateVar(array $row, array $means) : Matrix + protected function calculateVar(array $row, array $means): Matrix { $x = new Matrix($row, false); $m = new Matrix($means, false); @@ -195,23 +219,4 @@ class LDA extends EigenTransformerBase return $diff->transpose()->multiply($diff); } - - /** - * Transforms the given sample to a lower dimensional vector by using - * the eigenVectors obtained in the last run of fit. - * - * @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); - } } diff --git a/src/Phpml/DimensionReduction/PCA.php b/src/Phpml/DimensionReduction/PCA.php index 25b7186..18879bb 100644 --- a/src/Phpml/DimensionReduction/PCA.php +++ b/src/Phpml/DimensionReduction/PCA.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\DimensionReduction; +use Exception; use Phpml\Math\Statistic\Covariance; use Phpml\Math\Statistic\Mean; @@ -35,18 +36,21 @@ class PCA extends EigenTransformerBase public function __construct(?float $totalVariance = null, ?int $numFeatures = null) { if ($totalVariance !== null && ($totalVariance < 0.1 || $totalVariance > 0.99)) { - throw new \Exception('Total variance can be a value between 0.1 and 0.99'); + throw new Exception('Total variance can be a value between 0.1 and 0.99'); } + if ($numFeatures !== null && $numFeatures <= 0) { - throw new \Exception('Number of features to be preserved should be greater than 0'); + throw new Exception('Number of features to be preserved should be greater than 0'); } + if ($totalVariance !== null && $numFeatures !== null) { - throw new \Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); + throw new Exception('Either totalVariance or numFeatures should be specified in order to run the algorithm'); } if ($numFeatures !== null) { $this->numFeatures = $numFeatures; } + if ($totalVariance !== null) { $this->totalVariance = $totalVariance; } @@ -58,7 +62,7 @@ class PCA extends EigenTransformerBase * $data is an n-by-m matrix and returned array is * n-by-k matrix where k <= m */ - public function fit(array $data) : array + public function fit(array $data): array { $n = count($data[0]); @@ -73,6 +77,27 @@ class PCA extends EigenTransformerBase return $this->reduce($data); } + /** + * Transforms the given sample to a lower dimensional vector by using + * the eigenVectors obtained in the last run of fit. + * + * @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 { // Calculate means for each dimension @@ -87,7 +112,7 @@ class PCA extends EigenTransformerBase * Normalization of the data includes subtracting mean from * each dimension therefore dimensions will be centered to zero */ - protected function normalize(array $data, int $n) : array + protected function normalize(array $data, int $n): array { if (empty($this->means)) { $this->calculateMeans($data, $n); @@ -102,25 +127,4 @@ class PCA extends EigenTransformerBase return $data; } - - /** - * Transforms the given sample to a lower dimensional vector by using - * the eigenVectors obtained in the last run of fit. - * - * @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); - } } diff --git a/src/Phpml/Exception/DatasetException.php b/src/Phpml/Exception/DatasetException.php index 5d3e0db..8d6d5da 100644 --- a/src/Phpml/Exception/DatasetException.php +++ b/src/Phpml/Exception/DatasetException.php @@ -4,9 +4,11 @@ declare(strict_types=1); 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)); } diff --git a/src/Phpml/Exception/FileException.php b/src/Phpml/Exception/FileException.php index 39b9b03..719c2c2 100644 --- a/src/Phpml/Exception/FileException.php +++ b/src/Phpml/Exception/FileException.php @@ -4,19 +4,21 @@ declare(strict_types=1); 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)); } - 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)); } - 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)); } diff --git a/src/Phpml/Exception/InvalidArgumentException.php b/src/Phpml/Exception/InvalidArgumentException.php index d96bb33..e02d14d 100644 --- a/src/Phpml/Exception/InvalidArgumentException.php +++ b/src/Phpml/Exception/InvalidArgumentException.php @@ -4,39 +4,41 @@ declare(strict_types=1); 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'); } - 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)); } - public static function arrayCantBeEmpty() : InvalidArgumentException + public static function arrayCantBeEmpty(): self { 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)); } - public static function matrixDimensionsDidNotMatch() : InvalidArgumentException + public static function matrixDimensionsDidNotMatch(): self { return new self('Matrix dimensions did not match'); } - public static function inconsistentMatrixSupplied() : InvalidArgumentException + public static function inconsistentMatrixSupplied(): self { return new self('Inconsistent matrix supplied'); } - public static function invalidClustersNumber() : InvalidArgumentException + public static function invalidClustersNumber(): self { return new self('Invalid clusters number'); } @@ -44,57 +46,57 @@ class InvalidArgumentException extends \Exception /** * @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)); } - 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)); } - public static function invalidLayerNodeClass() : InvalidArgumentException + public static function invalidLayerNodeClass(): self { 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'); } - public static function invalidClassesNumber() : InvalidArgumentException + public static function invalidClassesNumber(): self { 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'); } - public static function fileNotFound(string $file) : InvalidArgumentException + public static function fileNotFound(string $file): self { 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)); } - 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)); } - 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)); } - public static function invalidOperator(string $operator) : InvalidArgumentException + public static function invalidOperator(string $operator): self { return new self(sprintf('Invalid operator "%s" provided', $operator)); } diff --git a/src/Phpml/Exception/MatrixException.php b/src/Phpml/Exception/MatrixException.php index a52feaa..b309bff 100644 --- a/src/Phpml/Exception/MatrixException.php +++ b/src/Phpml/Exception/MatrixException.php @@ -4,19 +4,21 @@ declare(strict_types=1); 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'); } - public static function columnOutOfRange() : MatrixException + public static function columnOutOfRange(): self { return new self('Column out of range'); } - public static function singularMatrix() : MatrixException + public static function singularMatrix(): self { return new self('Matrix is singular'); } diff --git a/src/Phpml/Exception/NormalizerException.php b/src/Phpml/Exception/NormalizerException.php index a7604e8..282fa1b 100644 --- a/src/Phpml/Exception/NormalizerException.php +++ b/src/Phpml/Exception/NormalizerException.php @@ -4,9 +4,11 @@ declare(strict_types=1); 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.'); } diff --git a/src/Phpml/Exception/SerializeException.php b/src/Phpml/Exception/SerializeException.php index 913667a..6d1abaa 100644 --- a/src/Phpml/Exception/SerializeException.php +++ b/src/Phpml/Exception/SerializeException.php @@ -4,14 +4,16 @@ declare(strict_types=1); 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)); } - 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)); } diff --git a/src/Phpml/FeatureExtraction/StopWords.php b/src/Phpml/FeatureExtraction/StopWords.php index b6717b9..fdb985f 100644 --- a/src/Phpml/FeatureExtraction/StopWords.php +++ b/src/Phpml/FeatureExtraction/StopWords.php @@ -11,19 +11,19 @@ class StopWords /** * @var array */ - protected $stopWords; + protected $stopWords = []; public function __construct(array $stopWords) { $this->stopWords = array_fill_keys($stopWords, true); } - public function isStopWord(string $token) : bool + public function isStopWord(string $token): bool { 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"; diff --git a/src/Phpml/FeatureExtraction/TfIdfTransformer.php b/src/Phpml/FeatureExtraction/TfIdfTransformer.php index 6efd90f..4b678a4 100644 --- a/src/Phpml/FeatureExtraction/TfIdfTransformer.php +++ b/src/Phpml/FeatureExtraction/TfIdfTransformer.php @@ -11,7 +11,7 @@ class TfIdfTransformer implements Transformer /** * @var array */ - private $idf; + private $idf = []; public function __construct(?array $samples = null) { diff --git a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php index e00fc69..e0bd402 100644 --- a/src/Phpml/FeatureExtraction/TokenCountVectorizer.php +++ b/src/Phpml/FeatureExtraction/TokenCountVectorizer.php @@ -27,21 +27,18 @@ class TokenCountVectorizer implements Transformer /** * @var array */ - private $vocabulary; + private $vocabulary = []; /** * @var array */ - private $frequencies; + private $frequencies = []; public function __construct(Tokenizer $tokenizer, ?StopWords $stopWords = null, float $minDF = 0.0) { $this->tokenizer = $tokenizer; $this->stopWords = $stopWords; $this->minDF = $minDF; - - $this->vocabulary = []; - $this->frequencies = []; } public function fit(array $samples): void @@ -58,7 +55,7 @@ class TokenCountVectorizer implements Transformer $this->checkDocumentFrequency($samples); } - public function getVocabulary() : array + public function getVocabulary(): array { return array_flip($this->vocabulary); } @@ -80,7 +77,7 @@ class TokenCountVectorizer implements Transformer foreach ($tokens as $token) { $index = $this->getTokenIndex($token); - if (false !== $index) { + if ($index !== false) { $this->updateFrequency($token); if (!isset($counts[$index])) { $counts[$index] = 0; @@ -155,7 +152,7 @@ class TokenCountVectorizer implements Transformer } } - private function getBeyondMinimumIndexes(int $samplesCount) : array + private function getBeyondMinimumIndexes(int $samplesCount): array { $indexes = []; foreach ($this->frequencies as $token => $frequency) { diff --git a/src/Phpml/Helper/OneVsRest.php b/src/Phpml/Helper/OneVsRest.php index 15d62d8..4f661ba 100644 --- a/src/Phpml/Helper/OneVsRest.php +++ b/src/Phpml/Helper/OneVsRest.php @@ -36,6 +36,18 @@ trait OneVsRest $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 { // Overwrites the current value if it exist. $allLabels must be provided for each partialTrain run. @@ -44,6 +56,7 @@ trait OneVsRest } else { $this->allLabels = array_keys(array_count_values($targets)); } + sort($this->allLabels, SORT_STRING); // 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. * @@ -105,29 +106,6 @@ trait OneVsRest 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 */ @@ -155,8 +133,6 @@ trait OneVsRest /** * To be overwritten by OneVsRest classifiers. - * - * @return void */ abstract protected function resetBinary(): void; @@ -174,4 +150,27 @@ trait OneVsRest * @return mixed */ 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]; + } } diff --git a/src/Phpml/Helper/Optimizer/ConjugateGradient.php b/src/Phpml/Helper/Optimizer/ConjugateGradient.php index c119eae..153ffcb 100644 --- a/src/Phpml/Helper/Optimizer/ConjugateGradient.php +++ b/src/Phpml/Helper/Optimizer/ConjugateGradient.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Helper\Optimizer; +use Closure; + /** * 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) @@ -17,7 +19,7 @@ namespace Phpml\Helper\Optimizer; */ 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->targets = $targets; @@ -25,7 +27,7 @@ class ConjugateGradient extends GD $this->sampleCount = count($samples); $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) { // Obtain α that minimizes f(θ + α.d) @@ -59,7 +61,7 @@ class ConjugateGradient extends GD * Executes the callback function for the problem and returns * sum of the gradient for all samples & targets. */ - protected function gradient(array $theta) : array + protected function gradient(array $theta): array { [, $gradient] = parent::gradient($theta); @@ -69,7 +71,7 @@ class ConjugateGradient extends GD /** * Returns the value of f(x) for given solution */ - protected function cost(array $theta) : float + protected function cost(array $theta): float { [$cost] = parent::gradient($theta); @@ -90,14 +92,14 @@ class ConjugateGradient extends GD * b-1) If cost function decreases, continue enlarging alpha * b-2) If cost function increases, take the midpoint and try again */ - protected function getAlpha(float $d) : float + protected function getAlpha(float $d): float { $small = 0.0001 * $d; $large = 0.01 * $d; // Obtain θ + α.d for two initial values, x0 and x1 - $x0 = mp::adds($this->theta, $small); - $x1 = mp::adds($this->theta, $large); + $x0 = MP::adds($this->theta, $small); + $x1 = MP::adds($this->theta, $large); $epsilon = 0.0001; $iteration = 0; @@ -113,9 +115,9 @@ class ConjugateGradient extends GD if ($fx1 < $fx0) { $x0 = $x1; - $x1 = mp::adds($x1, 0.01); // Enlarge second + $x1 = MP::adds($x1, 0.01); // Enlarge second } else { - $x1 = mp::divs(mp::add($x1, $x0), 2.0); + $x1 = MP::divs(MP::add($x1, $x0), 2.0); } // Get to the midpoint $error = $fx1 / $this->dimensions; @@ -135,7 +137,7 @@ class ConjugateGradient extends GD * * θ(k+1) = θ(k) + α.d */ - protected function getNewTheta(float $alpha, array $d) : array + protected function getNewTheta(float $alpha, array $d): array { $theta = $this->theta; @@ -164,7 +166,7 @@ class ConjugateGradient extends GD * See: * R. Fletcher and C. M. Reeves, "Function minimization by conjugate gradients", Comput. J. 7 (1964), 149–154. */ - protected function getBeta(array $newTheta) : float + protected function getBeta(array $newTheta): float { $dNew = array_sum($this->gradient($newTheta)); $dOld = array_sum($this->gradient($this->theta)) + 1e-100; @@ -177,11 +179,11 @@ class ConjugateGradient extends GD * * d(k+1) =–∇f(x(k+1)) + β(k).d(k) */ - protected function getNewDirection(array $theta, float $beta, array $d) : array + protected function getNewDirection(array $theta, float $beta, array $d): array { $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,12 +191,12 @@ class ConjugateGradient extends GD * Handles element-wise vector operations between vector-vector * and vector-scalar variables */ -class mp +class MP { /** * Element-wise multiplication of two vectors of the same size */ - public static function mul(array $m1, array $m2) : array + public static function mul(array $m1, array $m2): array { $res = []; foreach ($m1 as $i => $val) { @@ -207,7 +209,7 @@ class mp /** * Element-wise division of two vectors of the same size */ - public static function div(array $m1, array $m2) : array + public static function div(array $m1, array $m2): array { $res = []; foreach ($m1 as $i => $val) { @@ -220,7 +222,7 @@ class mp /** * Element-wise addition of two vectors of the same size */ - public static function add(array $m1, array $m2, int $mag = 1) : array + public static function add(array $m1, array $m2, int $mag = 1): array { $res = []; foreach ($m1 as $i => $val) { @@ -233,7 +235,7 @@ class mp /** * Element-wise subtraction of two vectors of the same size */ - public static function sub(array $m1, array $m2) : array + public static function sub(array $m1, array $m2): array { return self::add($m1, $m2, -1); } @@ -241,7 +243,7 @@ class mp /** * Element-wise multiplication of a vector with a scalar */ - public static function muls(array $m1, float $m2) : array + public static function muls(array $m1, float $m2): array { $res = []; foreach ($m1 as $val) { @@ -254,7 +256,7 @@ class mp /** * Element-wise division of a vector with a scalar */ - public static function divs(array $m1, float $m2) : array + public static function divs(array $m1, float $m2): array { $res = []; foreach ($m1 as $val) { @@ -267,7 +269,7 @@ class mp /** * Element-wise addition of a vector with a scalar */ - public static function adds(array $m1, float $m2, int $mag = 1) : array + public static function adds(array $m1, float $m2, int $mag = 1): array { $res = []; foreach ($m1 as $val) { @@ -280,7 +282,7 @@ class mp /** * Element-wise subtraction of a vector with a scalar */ - public static function subs(array $m1, float $m2) : array + public static function subs(array $m1, float $m2): array { return self::adds($m1, $m2, -1); } diff --git a/src/Phpml/Helper/Optimizer/GD.php b/src/Phpml/Helper/Optimizer/GD.php index 38b4253..4eadf28 100644 --- a/src/Phpml/Helper/Optimizer/GD.php +++ b/src/Phpml/Helper/Optimizer/GD.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Helper\Optimizer; +use Closure; + /** * Batch version of Gradient Descent to optimize the weights * of a classifier given samples, targets and the objective function to minimize @@ -17,7 +19,7 @@ class GD extends StochasticGD */ 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->targets = $targets; @@ -51,7 +53,7 @@ class GD extends StochasticGD * Calculates gradient, cost function and penalty term for each sample * then returns them as an array of values */ - protected function gradient(array $theta) : array + protected function gradient(array $theta): array { $costs = []; $gradient = []; diff --git a/src/Phpml/Helper/Optimizer/Optimizer.php b/src/Phpml/Helper/Optimizer/Optimizer.php index ee61321..2b25acd 100644 --- a/src/Phpml/Helper/Optimizer/Optimizer.php +++ b/src/Phpml/Helper/Optimizer/Optimizer.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace Phpml\Helper\Optimizer; +use Closure; +use Exception; + abstract class Optimizer { /** @@ -11,7 +14,7 @@ abstract class Optimizer * * @var array */ - protected $theta; + protected $theta = []; /** * Number of dimensions @@ -30,7 +33,7 @@ abstract class Optimizer // Inits the weights randomly $this->theta = []; 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) { 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; @@ -56,5 +59,5 @@ abstract class Optimizer * Executes the optimization with the given samples & targets * and returns the weights */ - abstract public function runOptimization(array $samples, array $targets, \Closure $gradientCb); + abstract public function runOptimization(array $samples, array $targets, Closure $gradientCb); } diff --git a/src/Phpml/Helper/Optimizer/StochasticGD.php b/src/Phpml/Helper/Optimizer/StochasticGD.php index f1b0979..07ad216 100644 --- a/src/Phpml/Helper/Optimizer/StochasticGD.php +++ b/src/Phpml/Helper/Optimizer/StochasticGD.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Phpml\Helper\Optimizer; +use Closure; + /** * Stochastic Gradient Descent optimization method * to find a solution for the equation A.ϴ = y where @@ -66,6 +68,7 @@ class StochasticGD extends Optimizer * @var bool */ protected $enableEarlyStop = true; + /** * List of values obtained by evaluating the cost function at each iteration * 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 * 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->targets = $targets; @@ -181,7 +184,16 @@ class StochasticGD extends Optimizer return $this->theta = $bestTheta; } - protected function updateTheta() : float + /** + * 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 { $jValue = 0.0; $theta = $this->theta; @@ -237,15 +249,6 @@ class StochasticGD extends Optimizer 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. */ diff --git a/src/Phpml/Math/Distance.php b/src/Phpml/Math/Distance.php index 696ee4b..9faa8e0 100644 --- a/src/Phpml/Math/Distance.php +++ b/src/Phpml/Math/Distance.php @@ -10,5 +10,5 @@ interface Distance * @param array $a * @param array $b */ - public function distance(array $a, array $b) : float; + public function distance(array $a, array $b): float; } diff --git a/src/Phpml/Math/Distance/Chebyshev.php b/src/Phpml/Math/Distance/Chebyshev.php index 40cdfbc..52e969c 100644 --- a/src/Phpml/Math/Distance/Chebyshev.php +++ b/src/Phpml/Math/Distance/Chebyshev.php @@ -12,7 +12,7 @@ class Chebyshev implements Distance /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Distance/Euclidean.php b/src/Phpml/Math/Distance/Euclidean.php index f6a87cf..4ecc576 100644 --- a/src/Phpml/Math/Distance/Euclidean.php +++ b/src/Phpml/Math/Distance/Euclidean.php @@ -12,7 +12,7 @@ class Euclidean implements Distance /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); @@ -30,7 +30,7 @@ class Euclidean implements Distance /** * Square of Euclidean distance */ - public function sqDistance(array $a, array $b) : float + public function sqDistance(array $a, array $b): float { return $this->distance($a, $b) ** 2; } diff --git a/src/Phpml/Math/Distance/Manhattan.php b/src/Phpml/Math/Distance/Manhattan.php index 6d10b71..457333c 100644 --- a/src/Phpml/Math/Distance/Manhattan.php +++ b/src/Phpml/Math/Distance/Manhattan.php @@ -12,7 +12,7 @@ class Manhattan implements Distance /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Distance/Minkowski.php b/src/Phpml/Math/Distance/Minkowski.php index 17df39d..5ff7364 100644 --- a/src/Phpml/Math/Distance/Minkowski.php +++ b/src/Phpml/Math/Distance/Minkowski.php @@ -22,7 +22,7 @@ class Minkowski implements Distance /** * @throws InvalidArgumentException */ - public function distance(array $a, array $b) : float + public function distance(array $a, array $b): float { if (count($a) !== count($b)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Kernel.php b/src/Phpml/Math/Kernel.php index 6d1461f..9a2cb97 100644 --- a/src/Phpml/Math/Kernel.php +++ b/src/Phpml/Math/Kernel.php @@ -7,10 +7,10 @@ namespace Phpml\Math; interface Kernel { /** - * @param float $a - * @param float $b + * @param float|array $a + * @param float|array $b * - * @return float + * @return float|array */ public function compute($a, $b); } diff --git a/src/Phpml/Math/Kernel/RBF.php b/src/Phpml/Math/Kernel/RBF.php index e47dbb5..4f9cfaf 100644 --- a/src/Phpml/Math/Kernel/RBF.php +++ b/src/Phpml/Math/Kernel/RBF.php @@ -23,12 +23,11 @@ class RBF implements Kernel * @param array $a * @param array $b */ - public function compute($a, $b) + public function compute($a, $b): float { $score = 2 * Product::scalar($a, $b); $squares = Product::scalar($a, $a) + Product::scalar($b, $b); - $result = exp(-$this->gamma * ($squares - $score)); - return $result; + return exp(-$this->gamma * ($squares - $score)); } } diff --git a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php index 6261d26..4d7f662 100644 --- a/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/EigenvalueDecomposition.php @@ -1,6 +1,7 @@ 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
+ * d = real(diag(D)); + */ + public function getRealEigenvalues(): array + { + return $this->d; + } + + /** + * Return the imaginary parts of the eigenvalues
+ * 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. */ @@ -158,6 +226,7 @@ class EigenvalueDecomposition for ($j = 0; $j < $i; ++$j) { $this->e[$j] = 0.0; } + // Apply similarity transformation to remaining columns. for ($j = 0; $j < $i; ++$j) { $f = $this->d[$j]; @@ -168,6 +237,7 @@ class EigenvalueDecomposition $g += $this->V[$k][$j] * $this->d[$k]; $this->e[$k] += $this->V[$k][$j] * $f; } + $this->e[$j] = $g; } @@ -185,16 +255,19 @@ class EigenvalueDecomposition for ($j = 0; $j < $i; ++$j) { $this->e[$j] -= $hh * $this->d[$j]; } + for ($j = 0; $j < $i; ++$j) { $f = $this->d[$j]; $g = $this->e[$j]; for ($k = $j; $k <= $i_; ++$k) { $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]); } + $this->d[$j] = $this->V[$i - 1][$j]; $this->V[$i][$j] = 0.0; } } + $this->d[$i] = $h; } @@ -207,16 +280,19 @@ class EigenvalueDecomposition for ($k = 0; $k <= $i; ++$k) { $this->d[$k] = $this->V[$k][$i + 1] / $h; } + for ($j = 0; $j <= $i; ++$j) { $g = 0.0; for ($k = 0; $k <= $i; ++$k) { $g += $this->V[$k][$i + 1] * $this->V[$k][$j]; } + for ($k = 0; $k <= $i; ++$k) { $this->V[$k][$j] -= $g * $this->d[$k]; } } } + for ($k = 0; $k <= $i; ++$k) { $this->V[$k][$i + 1] = 0.0; } @@ -241,6 +317,7 @@ class EigenvalueDecomposition for ($i = 1; $i < $this->n; ++$i) { $this->e[$i - 1] = $this->e[$i]; } + $this->e[$this->n - 1] = 0.0; $f = 0.0; $tst1 = 0.0; @@ -254,8 +331,10 @@ class EigenvalueDecomposition if (abs($this->e[$m]) <= $eps * $tst1) { break; } + ++$m; } + // If m == l, $this->d[l] is an eigenvalue, // otherwise, iterate. if ($m > $l) { @@ -270,6 +349,7 @@ class EigenvalueDecomposition if ($p < 0) { $r *= -1; } + $this->d[$l] = $this->e[$l] / ($p + $r); $this->d[$l + 1] = $this->e[$l] * ($p + $r); $dl1 = $this->d[$l + 1]; @@ -277,6 +357,7 @@ class EigenvalueDecomposition for ($i = $l + 2; $i < $this->n; ++$i) { $this->d[$i] -= $h; } + $f += $h; // Implicit QL transformation. $p = $this->d[$m]; @@ -303,12 +384,14 @@ class EigenvalueDecomposition $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; } } + $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; $this->e[$l] = $s * $p; $this->d[$l] = $c * $p; // Check for convergence. } while (abs($this->e[$l]) > $eps * $tst1); } + $this->d[$l] = $this->d[$l] + $f; $this->e[$l] = 0.0; } @@ -323,6 +406,7 @@ class EigenvalueDecomposition $p = $this->d[$j]; } } + if ($k != $i) { $this->d[$k] = $this->d[$i]; $this->d[$i] = $p; @@ -354,6 +438,7 @@ class EigenvalueDecomposition for ($i = $m; $i <= $high; ++$i) { $scale = $scale + abs($this->H[$i][$m - 1]); } + if ($scale != 0.0) { // Compute Householder transformation. $h = 0.0; @@ -361,10 +446,12 @@ class EigenvalueDecomposition $this->ort[$i] = $this->H[$i][$m - 1] / $scale; $h += $this->ort[$i] * $this->ort[$i]; } + $g = sqrt($h); if ($this->ort[$m] > 0) { $g *= -1; } + $h -= $this->ort[$m] * $g; $this->ort[$m] -= $g; // Apply Householder similarity transformation @@ -374,21 +461,25 @@ class EigenvalueDecomposition for ($i = $high; $i >= $m; --$i) { $f += $this->ort[$i] * $this->H[$i][$j]; } + $f /= $h; for ($i = $m; $i <= $high; ++$i) { $this->H[$i][$j] -= $f * $this->ort[$i]; } } + for ($i = 0; $i <= $high; ++$i) { $f = 0.0; for ($j = $high; $j >= $m; --$j) { $f += $this->ort[$j] * $this->H[$i][$j]; } + $f = $f / $h; for ($j = $m; $j <= $high; ++$j) { $this->H[$i][$j] -= $f * $this->ort[$j]; } } + $this->ort[$m] = $scale * $this->ort[$m]; $this->H[$m][$m - 1] = $scale * $g; } @@ -400,16 +491,19 @@ class EigenvalueDecomposition $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0); } } + for ($m = $high - 1; $m >= $low + 1; --$m) { if ($this->H[$m][$m - 1] != 0.0) { for ($i = $m + 1; $i <= $high; ++$i) { $this->ort[$i] = $this->H[$i][$m - 1]; } + for ($j = $m; $j <= $high; ++$j) { $g = 0.0; for ($i = $m; $i <= $high; ++$i) { $g += $this->ort[$i] * $this->V[$i][$j]; } + // Double division avoids possible underflow $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1]; for ($i = $m; $i <= $high; ++$i) { @@ -469,6 +563,7 @@ class EigenvalueDecomposition $this->d[$i] = $this->H[$i][$i]; $this->e[$i] = 0.0; } + for ($j = max($i - 1, 0); $j < $nn; ++$j) { $norm = $norm + abs($this->H[$i][$j]); } @@ -484,11 +579,14 @@ class EigenvalueDecomposition if ($s == 0.0) { $s = $norm; } + if (abs($this->H[$l][$l - 1]) < $eps * $s) { break; } + --$l; } + // Check for convergence // One root found if ($l == $n) { @@ -513,11 +611,13 @@ class EigenvalueDecomposition } else { $z = $p - $z; } + $this->d[$n - 1] = $x + $z; $this->d[$n] = $this->d[$n - 1]; if ($z != 0.0) { $this->d[$n] = $x - $w / $z; } + $this->e[$n - 1] = 0.0; $this->e[$n] = 0.0; $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][$j] = $q * $this->H[$n][$j] - $p * $z; } + // Column modification for ($i = 0; $i <= $n; ++$i) { $z = $this->H[$i][$n - 1]; $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n]; $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z; } + // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { $z = $this->V[$i][$n - 1]; $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; } + // Complex pair } else { $this->d[$n - 1] = $x + $p; @@ -552,6 +655,7 @@ class EigenvalueDecomposition $this->e[$n - 1] = $z; $this->e[$n] = -$z; } + $n = $n - 2; $iter = 0; // No convergence yet @@ -564,16 +668,19 @@ class EigenvalueDecomposition $y = $this->H[$n - 1][$n - 1]; $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; } + // Wilkinson's original ad hoc shift if ($iter == 10) { $exshift += $x; for ($i = $low; $i <= $n; ++$i) { $this->H[$i][$i] -= $x; } + $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]); $x = $y = 0.75 * $s; $w = -0.4375 * $s * $s; } + // MATLAB's new ad hoc shift if ($iter == 30) { $s = ($y - $x) / 2.0; @@ -583,14 +690,17 @@ class EigenvalueDecomposition if ($y < $x) { $s = -$s; } + $s = $x - $w / (($y - $x) / 2.0 + $s); for ($i = $low; $i <= $n; ++$i) { $this->H[$i][$i] -= $s; } + $exshift += $s; $x = $y = $w = 0.964; } } + // Could check iteration count here. $iter = $iter + 1; // Look for two consecutive small sub-diagonal elements @@ -609,18 +719,22 @@ class EigenvalueDecomposition if ($m == $l) { break; } + 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])))) { break; } + --$m; } + for ($i = $m + 2; $i <= $n; ++$i) { $this->H[$i][$i - 2] = 0.0; if ($i > $m + 2) { $this->H[$i][$i - 3] = 0.0; } } + // Double QR step involving rows l:n and columns m:n for ($k = $m; $k <= $n - 1; ++$k) { $notlast = ($k != $n - 1); @@ -635,19 +749,23 @@ class EigenvalueDecomposition $r = $r / $x; } } + if ($x == 0.0) { break; } + $s = sqrt($p * $p + $q * $q + $r * $r); if ($p < 0) { $s = -$s; } + if ($s != 0) { if ($k != $m) { $this->H[$k][$k - 1] = -$s * $x; } elseif ($l != $m) { $this->H[$k][$k - 1] = -$this->H[$k][$k - 1]; } + $p = $p + $s; $x = $p / $s; $y = $q / $s; @@ -661,9 +779,11 @@ class EigenvalueDecomposition $p = $p + $r * $this->H[$k + 2][$j]; $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 + 1][$j] = $this->H[$k + 1][$j] - $p * $y; } + // Column modification for ($i = 0; $i <= min($n, $k + 3); ++$i) { $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]; $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 + 1] = $this->H[$i][$k + 1] - $p * $q; } + // Accumulate transformations for ($i = $low; $i <= $high; ++$i) { $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]; $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 + 1] = $this->V[$i][$k + 1] - $p * $q; } @@ -719,6 +842,7 @@ class EigenvalueDecomposition } else { $this->H[$i][$n] = -$r / ($eps * $norm); } + // Solve real equations } else { $x = $this->H[$i][$i + 1]; @@ -732,6 +856,7 @@ class EigenvalueDecomposition $this->H[$i + 1][$n] = (-$s - $y * $t) / $z; } } + // Overflow control $t = abs($this->H[$i][$n]); if (($eps * $t) * $t > 1) { @@ -741,6 +866,7 @@ class EigenvalueDecomposition } } } + // Complex vector } elseif ($q < 0) { $l = $n - 1; @@ -753,6 +879,7 @@ class EigenvalueDecomposition $this->H[$n - 1][$n - 1] = $this->cdivr; $this->H[$n - 1][$n] = $this->cdivi; } + $this->H[$n][$n - 1] = 0.0; $this->H[$n][$n] = 1.0; for ($i = $n - 2; $i >= 0; --$i) { @@ -763,6 +890,7 @@ class EigenvalueDecomposition $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1]; $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; } + $w = $this->H[$i][$i] - $p; if ($this->e[$i] < 0.0) { $z = $w; @@ -783,6 +911,7 @@ class EigenvalueDecomposition if ($vr == 0.0 & $vi == 0.0) { $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->H[$i][$n - 1] = $this->cdivr; $this->H[$i][$n] = $this->cdivi; @@ -795,6 +924,7 @@ class EigenvalueDecomposition $this->H[$i + 1][$n] = $this->cdivi; } } + // Overflow control $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n])); if (($eps * $t) * $t > 1) { @@ -824,81 +954,9 @@ class EigenvalueDecomposition for ($k = $low; $k <= min($j, $high); ++$k) { $z = $z + $this->V[$i][$k] * $this->H[$k][$j]; } + $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
- * d = real(diag(D)); - * - * @return array - */ - public function getRealEigenvalues() - { - return $this->d; - } - - /** - * Return the imaginary parts of the eigenvalues
- * 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; - } } diff --git a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php index 164a72f..6ebd8cb 100644 --- a/src/Phpml/Math/LinearAlgebra/LUDecomposition.php +++ b/src/Phpml/Math/LinearAlgebra/LUDecomposition.php @@ -1,6 +1,7 @@ m; ++$i) { $this->piv[$i] = $i; } + $this->pivsign = 1; $LUcolj = []; @@ -99,6 +101,7 @@ class LUDecomposition for ($i = 0; $i < $this->m; ++$i) { $LUcolj[$i] = &$this->LU[$i][$j]; } + // Apply previous transformations. for ($i = 0; $i < $this->m; ++$i) { $LUrowi = $this->LU[$i]; @@ -108,8 +111,10 @@ class LUDecomposition for ($k = 0; $k < $kmax; ++$k) { $s += $LUrowi[$k] * $LUcolj[$k]; } + $LUrowi[$j] = $LUcolj[$i] -= $s; } + // Find pivot and exchange if necessary. $p = $j; for ($i = $j + 1; $i < $this->m; ++$i) { @@ -117,17 +122,20 @@ class LUDecomposition $p = $i; } } + if ($p != $j) { for ($k = 0; $k < $this->n; ++$k) { $t = $this->LU[$p][$k]; $this->LU[$p][$k] = $this->LU[$j][$k]; $this->LU[$j][$k] = $t; } + $k = $this->piv[$p]; $this->piv[$p] = $this->piv[$j]; $this->piv[$j] = $k; $this->pivsign = $this->pivsign * -1; } + // Compute multipliers. if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) { for ($i = $j + 1; $i < $this->m; ++$i) { @@ -142,7 +150,7 @@ class LUDecomposition * * @return Matrix Lower triangular factor */ - public function getL() : Matrix + public function getL(): Matrix { $L = []; for ($i = 0; $i < $this->m; ++$i) { @@ -165,7 +173,7 @@ class LUDecomposition * * @return Matrix Upper triangular factor */ - public function getU() : Matrix + public function getU(): Matrix { $U = []; for ($i = 0; $i < $this->n; ++$i) { @@ -186,7 +194,7 @@ class LUDecomposition * * @return array Pivot vector */ - public function getPivot() : array + public function getPivot(): array { return $this->piv; } @@ -247,7 +255,7 @@ class LUDecomposition * * @throws MatrixException */ - public function solve(Matrix $B) : array + public function solve(Matrix $B): array { if ($B->getRows() != $this->m) { throw MatrixException::notSquareMatrix(); @@ -268,11 +276,13 @@ class LUDecomposition } } } + // Solve U*X = Y; for ($k = $this->n - 1; $k >= 0; --$k) { for ($j = 0; $j < $nx; ++$j) { $X[$k][$j] /= $this->LU[$k][$k]; } + for ($i = 0; $i < $k; ++$i) { for ($j = 0; $j < $nx; ++$j) { $X[$i][$j] -= $X[$k][$j] * $this->LU[$i][$k]; @@ -283,7 +293,7 @@ class LUDecomposition return $X; } - protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF) : array + protected function getSubMatrix(array $matrix, array $RL, int $j0, int $jF): array { $m = count($RL); $n = $jF - $j0; diff --git a/src/Phpml/Math/Matrix.php b/src/Phpml/Math/Matrix.php index 6145521..7c1ff3e 100644 --- a/src/Phpml/Math/Matrix.php +++ b/src/Phpml/Math/Matrix.php @@ -13,7 +13,7 @@ class Matrix /** * @var array */ - private $matrix; + private $matrix = []; /** * @var int @@ -56,7 +56,7 @@ class Matrix $this->matrix = $matrix; } - public static function fromFlatArray(array $array) : Matrix + public static function fromFlatArray(array $array): self { $matrix = []; foreach ($array as $value) { @@ -66,12 +66,12 @@ class Matrix return new self($matrix); } - public function toArray() : array + public function toArray(): array { return $this->matrix; } - public function toScalar() : float + public function toScalar(): float { return $this->matrix[0][0]; } @@ -89,7 +89,7 @@ class Matrix /** * @throws MatrixException */ - public function getColumnValues($column) : array + public function getColumnValues($column): array { if ($column >= $this->columns) { throw MatrixException::columnOutOfRange(); @@ -123,7 +123,7 @@ class Matrix return $this->columns === $this->rows; } - public function transpose() : Matrix + public function transpose(): self { if ($this->rows == 1) { $matrix = array_map(function ($el) { @@ -136,7 +136,7 @@ class Matrix return new self($matrix, false); } - public function multiply(Matrix $matrix) : Matrix + public function multiply(self $matrix): self { if ($this->columns != $matrix->getRows()) { throw InvalidArgumentException::inconsistentMatrixSupplied(); @@ -157,7 +157,7 @@ class Matrix return new self($product, false); } - public function divideByScalar($value) : Matrix + public function divideByScalar($value): self { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { @@ -169,7 +169,7 @@ class Matrix return new self($newMatrix, false); } - public function multiplyByScalar($value) : Matrix + public function multiplyByScalar($value): self { $newMatrix = []; for ($i = 0; $i < $this->rows; ++$i) { @@ -184,7 +184,7 @@ class Matrix /** * 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); } @@ -192,15 +192,74 @@ class Matrix /** * 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); } + public function inverse(): self + { + if (!$this->isSquare()) { + throw MatrixException::notSquareMatrix(); + } + + $LU = new LUDecomposition($this); + $identity = $this->getIdentity(); + $inverse = $LU->solve($identity); + + return new self($inverse, false); + } + + public function crossOut(int $row, int $column): self + { + $newMatrix = []; + $r = 0; + for ($i = 0; $i < $this->rows; ++$i) { + $c = 0; + if ($row != $i) { + for ($j = 0; $j < $this->columns; ++$j) { + if ($column != $j) { + $newMatrix[$r][$c] = $this->matrix[$i][$j]; + ++$c; + } + } + + ++$r; + } + } + + return new self($newMatrix, false); + } + + public function isSingular(): bool + { + return $this->getDeterminant() == 0; + } + + /** + * Returns the transpose of given array + */ + public static function transposeArray(array $array): array + { + return (new self($array, false))->transpose()->toArray(); + } + + /** + * Returns the dot product of two arrays
+ * Matrix::dot(x, y) ==> x.y' + */ + public static function dot(array $array1, array $array2): array + { + $m1 = new self($array1, false); + $m2 = new self($array2, false); + + return $m1->multiply($m2->transpose())->toArray()[0]; + } + /** * Element-wise addition or substraction depending on the given sign parameter */ - protected function _add(Matrix $other, int $sign = 1) : Matrix + protected function _add(self $other, int $sign = 1): self { $a1 = $this->toArray(); $a2 = $other->toArray(); @@ -215,23 +274,10 @@ class Matrix return new self($newMatrix, false); } - public function inverse() : Matrix - { - if (!$this->isSquare()) { - throw MatrixException::notSquareMatrix(); - } - - $LU = new LUDecomposition($this); - $identity = $this->getIdentity(); - $inverse = $LU->solve($identity); - - return new self($inverse, false); - } - /** * Returns diagonal identity matrix of the same size of this matrix */ - protected function getIdentity() : Matrix + protected function getIdentity(): self { $array = array_fill(0, $this->rows, array_fill(0, $this->columns, 0)); for ($i = 0; $i < $this->rows; ++$i) { @@ -240,49 +286,4 @@ class Matrix return new self($array, false); } - - public function crossOut(int $row, int $column) : Matrix - { - $newMatrix = []; - $r = 0; - for ($i = 0; $i < $this->rows; ++$i) { - $c = 0; - if ($row != $i) { - for ($j = 0; $j < $this->columns; ++$j) { - if ($column != $j) { - $newMatrix[$r][$c] = $this->matrix[$i][$j]; - ++$c; - } - } - ++$r; - } - } - - return new self($newMatrix, false); - } - - public function isSingular() : bool - { - return 0 == $this->getDeterminant(); - } - - /** - * Returns the transpose of given array - */ - public static function transposeArray(array $array) : array - { - return (new self($array, false))->transpose()->toArray(); - } - - /** - * Returns the dot product of two arrays
- * Matrix::dot(x, y) ==> x.y' - */ - public static function dot(array $array1, array $array2) : array - { - $m1 = new self($array1, false); - $m2 = new self($array2, false); - - return $m1->multiply($m2->transpose())->toArray()[0]; - } } diff --git a/src/Phpml/Math/Set.php b/src/Phpml/Math/Set.php index c5c5aa7..a67d5c2 100644 --- a/src/Phpml/Math/Set.php +++ b/src/Phpml/Math/Set.php @@ -4,12 +4,15 @@ declare(strict_types=1); namespace Phpml\Math; -class Set implements \IteratorAggregate +use ArrayIterator; +use IteratorAggregate; + +class Set implements IteratorAggregate { /** * @var string[]|int[]|float[] */ - private $elements; + private $elements = []; /** * @param string[]|int[]|float[] $elements @@ -22,7 +25,7 @@ class Set implements \IteratorAggregate /** * 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())); } @@ -30,7 +33,7 @@ class Set implements \IteratorAggregate /** * 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())); } @@ -38,7 +41,7 @@ class Set implements \IteratorAggregate /** * 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())); } @@ -48,7 +51,7 @@ class Set implements \IteratorAggregate * * @return Set[] */ - public static function cartesian(Set $a, Set $b) : array + public static function cartesian(self $a, self $b): array { $cartesian = []; @@ -66,7 +69,7 @@ class Set implements \IteratorAggregate * * @return Set[] */ - public static function power(Set $a) : array + public static function power(self $a): array { $power = [new self()]; @@ -79,24 +82,10 @@ class Set implements \IteratorAggregate 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 */ - public function add($element) : Set + public function add($element): self { return $this->addAll([$element]); } @@ -104,7 +93,7 @@ class Set implements \IteratorAggregate /** * @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)); @@ -114,7 +103,7 @@ class Set implements \IteratorAggregate /** * @param string|int|float $element */ - public function remove($element) : Set + public function remove($element): self { return $this->removeAll([$element]); } @@ -122,7 +111,7 @@ class Set implements \IteratorAggregate /** * @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)); @@ -132,7 +121,7 @@ class Set implements \IteratorAggregate /** * @param string|int|float $element */ - public function contains($element) : bool + public function contains($element): bool { return $this->containsAll([$element]); } @@ -140,7 +129,7 @@ class Set implements \IteratorAggregate /** * @param string[]|int[]|float[] $elements */ - public function containsAll(array $elements) : bool + public function containsAll(array $elements): bool { return !array_diff($elements, $this->elements); } @@ -148,23 +137,37 @@ class Set implements \IteratorAggregate /** * @return string[]|int[]|float[] */ - public function toArray() : array + public function toArray(): array { 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 { return $this->cardinality() == 0; } - public function cardinality() : int + public function cardinality(): int { 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)); + } } diff --git a/src/Phpml/Math/Statistic/Correlation.php b/src/Phpml/Math/Statistic/Correlation.php index 9bcf271..8803cbf 100644 --- a/src/Phpml/Math/Statistic/Correlation.php +++ b/src/Phpml/Math/Statistic/Correlation.php @@ -14,7 +14,7 @@ class Correlation * * @throws InvalidArgumentException */ - public static function pearson(array $x, array $y) : float + public static function pearson(array $x, array $y): float { if (count($x) !== count($y)) { throw InvalidArgumentException::arraySizeNotMatch(); diff --git a/src/Phpml/Math/Statistic/Covariance.php b/src/Phpml/Math/Statistic/Covariance.php index 627a8a6..a669a7f 100644 --- a/src/Phpml/Math/Statistic/Covariance.php +++ b/src/Phpml/Math/Statistic/Covariance.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Phpml\Math\Statistic; +use Exception; use Phpml\Exception\InvalidArgumentException; class Covariance @@ -13,7 +14,7 @@ class Covariance * * @throws InvalidArgumentException */ - public static function fromXYArrays(array $x, array $y, bool $sample = true, ?float $meanX = null, ?float $meanY = null) : float + public static function fromXYArrays(array $x, array $y, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { if (empty($x) || empty($y)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -51,7 +52,7 @@ class Covariance * @throws InvalidArgumentException * @throws \Exception */ - public static function fromDataset(array $data, int $i, int $k, bool $sample = true, ?float $meanX = null, ?float $meanY = null) : float + public static function fromDataset(array $data, int $i, int $k, bool $sample = true, ?float $meanX = null, ?float $meanY = null): float { if (empty($data)) { throw InvalidArgumentException::arrayCantBeEmpty(); @@ -63,7 +64,7 @@ class Covariance } 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) { @@ -92,10 +93,12 @@ class Covariance if ($index == $i) { $val[0] = $col - $meanX; } + if ($index == $k) { $val[1] = $col - $meanY; } } + $sum += $val[0] * $val[1]; } } @@ -112,7 +115,7 @@ class Covariance * * @param array|null $means */ - public static function covarianceMatrix(array $data, ?array $means = null) : array + public static function covarianceMatrix(array $data, ?array $means = null): array { $n = count($data[0]); diff --git a/src/Phpml/Math/Statistic/Gaussian.php b/src/Phpml/Math/Statistic/Gaussian.php index bdf8308..24aaeea 100644 --- a/src/Phpml/Math/Statistic/Gaussian.php +++ b/src/Phpml/Math/Statistic/Gaussian.php @@ -41,7 +41,7 @@ class Gaussian * Returns probability density value of the given $value based on * given standard deviation and the mean */ - public static function distributionPdf(float $mean, float $std, float $value) : float + public static function distributionPdf(float $mean, float $std, float $value): float { $normal = new self($mean, $std); diff --git a/src/Phpml/Math/Statistic/Mean.php b/src/Phpml/Math/Statistic/Mean.php index eb1baef..8791a65 100644 --- a/src/Phpml/Math/Statistic/Mean.php +++ b/src/Phpml/Math/Statistic/Mean.php @@ -11,7 +11,7 @@ class Mean /** * @throws InvalidArgumentException */ - public static function arithmetic(array $numbers) : float + public static function arithmetic(array $numbers): float { self::checkArrayLength($numbers); @@ -32,7 +32,7 @@ class Mean sort($numbers, SORT_NUMERIC); $median = $numbers[$middleIndex]; - if (0 === $count % 2) { + if ($count % 2 === 0) { $median = ($median + $numbers[$middleIndex - 1]) / 2; } diff --git a/src/Phpml/Math/Statistic/StandardDeviation.php b/src/Phpml/Math/Statistic/StandardDeviation.php index 3da8ef5..8a0d241 100644 --- a/src/Phpml/Math/Statistic/StandardDeviation.php +++ b/src/Phpml/Math/Statistic/StandardDeviation.php @@ -13,7 +13,7 @@ class StandardDeviation * * @throws InvalidArgumentException */ - public static function population(array $a, bool $sample = true) : float + public static function population(array $a, bool $sample = true): float { if (empty($a)) { throw InvalidArgumentException::arrayCantBeEmpty(); diff --git a/src/Phpml/Metric/ClassificationReport.php b/src/Phpml/Metric/ClassificationReport.php index ae4c11a..0f27b06 100644 --- a/src/Phpml/Metric/ClassificationReport.php +++ b/src/Phpml/Metric/ClassificationReport.php @@ -51,27 +51,27 @@ class ClassificationReport $this->computeAverage(); } - public function getPrecision() : array + public function getPrecision(): array { return $this->precision; } - public function getRecall() : array + public function getRecall(): array { return $this->recall; } - public function getF1score() : array + public function getF1score(): array { return $this->f1score; } - public function getSupport() : array + public function getSupport(): array { return $this->support; } - public function getAverage() : array + public function getAverage(): array { return $this->average; } @@ -93,6 +93,7 @@ class ClassificationReport $this->average[$metric] = 0.0; continue; } + $this->average[$metric] = array_sum($values) / count($values); } } @@ -102,7 +103,8 @@ class ClassificationReport */ private function computePrecision(int $truePositive, int $falsePositive) { - if (0 == ($divider = $truePositive + $falsePositive)) { + $divider = $truePositive + $falsePositive; + if ($divider == 0) { return 0.0; } @@ -114,23 +116,25 @@ class ClassificationReport */ private function computeRecall(int $truePositive, int $falseNegative) { - if (0 == ($divider = $truePositive + $falseNegative)) { + $divider = $truePositive + $falseNegative; + if ($divider == 0) { return 0.0; } return $truePositive / $divider; } - 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 2.0 * (($precision * $recall) / $divider); } - private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels) : array + private static function getLabelIndexedArray(array $actualLabels, array $predictedLabels): array { $labels = array_values(array_unique(array_merge($actualLabels, $predictedLabels))); sort($labels); diff --git a/src/Phpml/Metric/ConfusionMatrix.php b/src/Phpml/Metric/ConfusionMatrix.php index 0f0b738..e86a8ed 100644 --- a/src/Phpml/Metric/ConfusionMatrix.php +++ b/src/Phpml/Metric/ConfusionMatrix.php @@ -6,7 +6,7 @@ namespace Phpml\Metric; class ConfusionMatrix { - public static function compute(array $actualLabels, array $predictedLabels, ?array $labels = null) : array + public static function compute(array $actualLabels, array $predictedLabels, ?array $labels = null): array { $labels = $labels ? array_flip($labels) : self::getUniqueLabels($actualLabels); $matrix = self::generateMatrixWithZeros($labels); @@ -31,7 +31,7 @@ class ConfusionMatrix return $matrix; } - private static function generateMatrixWithZeros(array $labels) : array + private static function generateMatrixWithZeros(array $labels): array { $count = count($labels); $matrix = []; @@ -43,7 +43,7 @@ class ConfusionMatrix return $matrix; } - private static function getUniqueLabels(array $labels) : array + private static function getUniqueLabels(array $labels): array { $labels = array_values(array_unique($labels)); sort($labels); diff --git a/src/Phpml/ModelManager.php b/src/Phpml/ModelManager.php index 2fa14f5..ebcdbe4 100644 --- a/src/Phpml/ModelManager.php +++ b/src/Phpml/ModelManager.php @@ -26,7 +26,7 @@ class ModelManager } } - public function restoreFromFile(string $filepath) : Estimator + public function restoreFromFile(string $filepath): Estimator { if (!file_exists($filepath) || !is_readable($filepath)) { throw FileException::cantOpenFile(basename($filepath)); diff --git a/src/Phpml/NeuralNetwork/ActivationFunction.php b/src/Phpml/NeuralNetwork/ActivationFunction.php index 65ba7b4..5b91425 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction.php @@ -9,5 +9,5 @@ interface ActivationFunction /** * @param float|int $value */ - public function compute($value) : float; + public function compute($value): float; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php index 75b2ff1..764bc4e 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php @@ -11,7 +11,7 @@ class BinaryStep implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return $value >= 0 ? 1.0 : 0.0; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php index 081b8a5..da428a4 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php @@ -11,7 +11,7 @@ class Gaussian implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return exp(-pow($value, 2)); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php index 5c66fd9..6378606 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/HyperbolicTangent.php @@ -21,7 +21,7 @@ class HyperbolicTangent implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return tanh($this->beta * $value); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php index 60ade03..fc7ff62 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php @@ -21,7 +21,7 @@ class PReLU implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return $value >= 0 ? $value : $this->beta * $value; } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php index dec45a2..4ae9603 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php @@ -21,7 +21,7 @@ class Sigmoid implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return 1 / (1 + exp(-$this->beta * $value)); } diff --git a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php index dbe8ee6..2bb1cc7 100644 --- a/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php +++ b/src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php @@ -21,7 +21,7 @@ class ThresholdedReLU implements ActivationFunction /** * @param float|int $value */ - public function compute($value) : float + public function compute($value): float { return $value > $this->theta ? $value : 0.0; } diff --git a/src/Phpml/NeuralNetwork/Layer.php b/src/Phpml/NeuralNetwork/Layer.php index c70bdb3..7424348 100644 --- a/src/Phpml/NeuralNetwork/Layer.php +++ b/src/Phpml/NeuralNetwork/Layer.php @@ -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 { $this->nodes[] = $node; @@ -50,8 +36,20 @@ class Layer /** * @return Node[] */ - public function getNodes() : array + public function getNodes(): array { 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(); + } } diff --git a/src/Phpml/NeuralNetwork/Network.php b/src/Phpml/NeuralNetwork/Network.php index af04f4a..c2248a6 100644 --- a/src/Phpml/NeuralNetwork/Network.php +++ b/src/Phpml/NeuralNetwork/Network.php @@ -8,20 +8,15 @@ interface Network { /** * @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); /** * @return Layer[] */ - public function getLayers() : array; + public function getLayers(): array; } diff --git a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php index 3baa5ac..4f05398 100644 --- a/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php +++ b/src/Phpml/NeuralNetwork/Network/LayeredNetwork.php @@ -14,7 +14,7 @@ abstract class LayeredNetwork implements Network /** * @var Layer[] */ - protected $layers; + protected $layers = []; public function addLayer(Layer $layer): void { @@ -24,7 +24,7 @@ abstract class LayeredNetwork implements Network /** * @return Layer[] */ - public function getLayers() : array + public function getLayers(): array { return $this->layers; } @@ -39,7 +39,7 @@ abstract class LayeredNetwork implements Network return $this->layers[count($this->layers) - 1]; } - public function getOutput() : array + public function getOutput(): array { $result = []; foreach ($this->getOutputLayer()->getNodes() as $neuron) { @@ -54,7 +54,7 @@ abstract class LayeredNetwork implements Network * * @return $this */ - public function setInput($input) + public function setInput($input): Network { $firstLayer = $this->layers[0]; diff --git a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php index 94a8423..a38e952 100644 --- a/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php +++ b/src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php @@ -20,41 +20,36 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, { use Predictable; - /** - * @var int - */ - private $inputLayerFeatures; - - /** - * @var array - */ - private $hiddenLayers; - /** * @var array */ protected $classes = []; - /** - * @var int - */ - private $iterations; - /** * @var ActivationFunction */ protected $activationFunction; - /** - * @var float - */ - private $learningRate; - /** * @var Backpropagation */ protected $backpropagation = null; + /** + * @var int + */ + private $inputLayerFeatures; + + /** + * @var array + */ + private $hiddenLayers = []; + + /** + * @var float + */ + private $learningRate; + /** * @throws InvalidArgumentException */ @@ -78,18 +73,6 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 { $this->reset(); @@ -127,6 +110,18 @@ abstract class MultilayerPerceptron extends LayeredNetwork implements Estimator, $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 { $this->addLayer(new Layer($nodes, Input::class)); diff --git a/src/Phpml/NeuralNetwork/Node.php b/src/Phpml/NeuralNetwork/Node.php index 6627c02..0b7726f 100644 --- a/src/Phpml/NeuralNetwork/Node.php +++ b/src/Phpml/NeuralNetwork/Node.php @@ -6,5 +6,5 @@ namespace Phpml\NeuralNetwork; interface Node { - public function getOutput() : float; + public function getOutput(): float; } diff --git a/src/Phpml/NeuralNetwork/Node/Bias.php b/src/Phpml/NeuralNetwork/Node/Bias.php index 4f32884..ac3fb8b 100644 --- a/src/Phpml/NeuralNetwork/Node/Bias.php +++ b/src/Phpml/NeuralNetwork/Node/Bias.php @@ -8,7 +8,7 @@ use Phpml\NeuralNetwork\Node; class Bias implements Node { - public function getOutput() : float + public function getOutput(): float { return 1.0; } diff --git a/src/Phpml/NeuralNetwork/Node/Input.php b/src/Phpml/NeuralNetwork/Node/Input.php index 8ff78ea..ce33439 100644 --- a/src/Phpml/NeuralNetwork/Node/Input.php +++ b/src/Phpml/NeuralNetwork/Node/Input.php @@ -18,7 +18,7 @@ class Input implements Node $this->input = $input; } - public function getOutput() : float + public function getOutput(): float { return $this->input; } diff --git a/src/Phpml/NeuralNetwork/Node/Neuron.php b/src/Phpml/NeuralNetwork/Node/Neuron.php index 096d54f..a6c10e6 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Phpml\NeuralNetwork\Node; use Phpml\NeuralNetwork\ActivationFunction; +use Phpml\NeuralNetwork\ActivationFunction\Sigmoid; use Phpml\NeuralNetwork\Node; use Phpml\NeuralNetwork\Node\Neuron\Synapse; @@ -13,7 +14,7 @@ class Neuron implements Node /** * @var Synapse[] */ - protected $synapses; + protected $synapses = []; /** * @var ActivationFunction @@ -27,7 +28,7 @@ class Neuron implements Node public function __construct(?ActivationFunction $activationFunction = null) { - $this->activationFunction = $activationFunction ?: new ActivationFunction\Sigmoid(); + $this->activationFunction = $activationFunction ?: new Sigmoid(); $this->synapses = []; $this->output = 0; } @@ -45,9 +46,9 @@ class Neuron implements Node return $this->synapses; } - public function getOutput() : float + public function getOutput(): float { - if (0 === $this->output) { + if ($this->output === 0) { $sum = 0; foreach ($this->synapses as $synapse) { $sum += $synapse->getOutput(); diff --git a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php index 0883f4e..08899bf 100644 --- a/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php +++ b/src/Phpml/NeuralNetwork/Node/Neuron/Synapse.php @@ -27,12 +27,7 @@ class Synapse $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(); } @@ -42,7 +37,7 @@ class Synapse $this->weight += $delta; } - public function getWeight() : float + public function getWeight(): float { return $this->weight; } @@ -51,4 +46,9 @@ class Synapse { return $this->node; } + + protected function generateRandomWeight(): float + { + return 1 / random_int(5, 25) * (random_int(0, 1) ? -1 : 1); + } } diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation.php b/src/Phpml/NeuralNetwork/Training/Backpropagation.php index 6722bd1..8382a8e 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation.php @@ -47,6 +47,7 @@ class Backpropagation } } } + $this->prevSigmas = $this->sigmas; } @@ -55,7 +56,7 @@ class Backpropagation $this->prevSigmas = null; } - private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer) : float + private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float { $neuronOutput = $neuron->getOutput(); $sigma = $neuronOutput * (1 - $neuronOutput); @@ -65,6 +66,7 @@ class Backpropagation if ($targetClass === $key) { $value = 1; } + $sigma *= ($value - $neuronOutput); } else { $sigma *= $this->getPrevSigma($neuron); @@ -75,7 +77,7 @@ class Backpropagation return $sigma; } - private function getPrevSigma(Neuron $neuron) : float + private function getPrevSigma(Neuron $neuron): float { $sigma = 0.0; diff --git a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php index 23560fe..f21c7b1 100644 --- a/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php +++ b/src/Phpml/NeuralNetwork/Training/Backpropagation/Sigma.php @@ -29,12 +29,12 @@ class Sigma return $this->neuron; } - public function getSigma() : float + public function getSigma(): float { return $this->sigma; } - public function getSigmaForNeuron(Neuron $neuron) : float + public function getSigmaForNeuron(Neuron $neuron): float { $sigma = 0.0; diff --git a/src/Phpml/Pipeline.php b/src/Phpml/Pipeline.php index a72e634..480a980 100644 --- a/src/Phpml/Pipeline.php +++ b/src/Phpml/Pipeline.php @@ -9,7 +9,7 @@ class Pipeline implements Estimator /** * @var array|Transformer[] */ - private $transformers; + private $transformers = []; /** * @var Estimator @@ -41,7 +41,7 @@ class Pipeline implements Estimator /** * @return array|Transformer[] */ - public function getTransformers() : array + public function getTransformers(): array { return $this->transformers; } diff --git a/src/Phpml/Preprocessing/Imputer.php b/src/Phpml/Preprocessing/Imputer.php index d2dbfcd..bd40948 100644 --- a/src/Phpml/Preprocessing/Imputer.php +++ b/src/Phpml/Preprocessing/Imputer.php @@ -9,6 +9,7 @@ use Phpml\Preprocessing\Imputer\Strategy; class Imputer implements Preprocessor { public const AXIS_COLUMN = 0; + public const AXIS_ROW = 1; /** @@ -64,9 +65,9 @@ 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]); } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php index 91badfb..3ad0321 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MeanStrategy.php @@ -9,7 +9,7 @@ use Phpml\Preprocessing\Imputer\Strategy; class MeanStrategy implements Strategy { - public function replaceValue(array $currentAxis) : float + public function replaceValue(array $currentAxis): float { return Mean::arithmetic($currentAxis); } diff --git a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php index f010bea..ffd9983 100644 --- a/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php +++ b/src/Phpml/Preprocessing/Imputer/Strategy/MedianStrategy.php @@ -9,7 +9,7 @@ use Phpml\Preprocessing\Imputer\Strategy; class MedianStrategy implements Strategy { - public function replaceValue(array $currentAxis) : float + public function replaceValue(array $currentAxis): float { return Mean::median($currentAxis); } diff --git a/src/Phpml/Preprocessing/Normalizer.php b/src/Phpml/Preprocessing/Normalizer.php index f038345..b2721de 100644 --- a/src/Phpml/Preprocessing/Normalizer.php +++ b/src/Phpml/Preprocessing/Normalizer.php @@ -11,7 +11,9 @@ use Phpml\Math\Statistic\StandardDeviation; class Normalizer implements Preprocessor { public const NORM_L1 = 1; + public const NORM_L2 = 2; + public const NORM_STD = 3; /** @@ -27,12 +29,12 @@ class Normalizer implements Preprocessor /** * @var array */ - private $std; + private $std = []; /** * @var array */ - private $mean; + private $mean = []; /** * @throws NormalizerException @@ -69,7 +71,7 @@ class Normalizer implements Preprocessor $methods = [ self::NORM_L1 => 'normalizeL1', self::NORM_L2 => 'normalizeL2', - self::NORM_STD => 'normalizeSTD' + self::NORM_STD => 'normalizeSTD', ]; $method = $methods[$this->norm]; @@ -87,7 +89,7 @@ class Normalizer implements Preprocessor $norm1 += abs($feature); } - if (0 == $norm1) { + if ($norm1 == 0) { $count = count($sample); $sample = array_fill(0, $count, 1.0 / $count); } else { @@ -103,9 +105,10 @@ class Normalizer implements Preprocessor foreach ($sample as $feature) { $norm2 += $feature * $feature; } + $norm2 = sqrt((float) $norm2); - if (0 == $norm2) { + if ($norm2 == 0) { $sample = array_fill(0, count($sample), 1); } else { foreach ($sample as &$feature) { diff --git a/src/Phpml/Regression/LeastSquares.php b/src/Phpml/Regression/LeastSquares.php index f8adcb2..6ecfafc 100644 --- a/src/Phpml/Regression/LeastSquares.php +++ b/src/Phpml/Regression/LeastSquares.php @@ -28,7 +28,7 @@ class LeastSquares implements Regression /** * @var array */ - private $coefficients; + private $coefficients = []; public function train(array $samples, array $targets): void { @@ -51,12 +51,12 @@ class LeastSquares implements Regression return $result; } - public function getCoefficients() : array + public function getCoefficients(): array { return $this->coefficients; } - public function getIntercept() : float + public function getIntercept(): float { return $this->intercept; } @@ -79,7 +79,7 @@ class LeastSquares implements Regression /** * Add one dimension for intercept calculation. */ - private function getSamplesMatrix() : Matrix + private function getSamplesMatrix(): Matrix { $samples = []; foreach ($this->samples as $sample) { @@ -90,7 +90,7 @@ class LeastSquares implements Regression return new Matrix($samples); } - private function getTargetsMatrix() : Matrix + private function getTargetsMatrix(): Matrix { if (is_array($this->targets[0])) { return new Matrix($this->targets); diff --git a/src/Phpml/SupportVectorMachine/DataTransformer.php b/src/Phpml/SupportVectorMachine/DataTransformer.php index b057d01..2ce938e 100644 --- a/src/Phpml/SupportVectorMachine/DataTransformer.php +++ b/src/Phpml/SupportVectorMachine/DataTransformer.php @@ -34,7 +34,7 @@ class DataTransformer return $set; } - public static function predictions(string $rawPredictions, array $labels) : array + public static function predictions(string $rawPredictions, array $labels): array { $numericLabels = self::numericLabels($labels); $results = []; @@ -47,7 +47,7 @@ class DataTransformer return $results; } - public static function numericLabels(array $labels) : array + public static function numericLabels(array $labels): array { $numericLabels = []; foreach ($labels as $label) { diff --git a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php index cbee23d..2415eda 100644 --- a/src/Phpml/SupportVectorMachine/SupportVectorMachine.php +++ b/src/Phpml/SupportVectorMachine/SupportVectorMachine.php @@ -167,7 +167,7 @@ class SupportVectorMachine } /** - * @return array + * @return array|string */ public function predict(array $samples) { diff --git a/src/Phpml/Tokenization/Tokenizer.php b/src/Phpml/Tokenization/Tokenizer.php index e1f0f35..f2dffd9 100644 --- a/src/Phpml/Tokenization/Tokenizer.php +++ b/src/Phpml/Tokenization/Tokenizer.php @@ -6,5 +6,5 @@ namespace Phpml\Tokenization; interface Tokenizer { - public function tokenize(string $text) : array; + public function tokenize(string $text): array; } diff --git a/src/Phpml/Tokenization/WhitespaceTokenizer.php b/src/Phpml/Tokenization/WhitespaceTokenizer.php index 14e7d0a..5b071b8 100644 --- a/src/Phpml/Tokenization/WhitespaceTokenizer.php +++ b/src/Phpml/Tokenization/WhitespaceTokenizer.php @@ -6,7 +6,7 @@ namespace Phpml\Tokenization; class WhitespaceTokenizer implements Tokenizer { - public function tokenize(string $text) : array + public function tokenize(string $text): array { return preg_split('/[\pZ\pC]+/u', $text, -1, PREG_SPLIT_NO_EMPTY); } diff --git a/src/Phpml/Tokenization/WordTokenizer.php b/src/Phpml/Tokenization/WordTokenizer.php index 03d134b..68a75ea 100644 --- a/src/Phpml/Tokenization/WordTokenizer.php +++ b/src/Phpml/Tokenization/WordTokenizer.php @@ -6,7 +6,7 @@ namespace Phpml\Tokenization; class WordTokenizer implements Tokenizer { - public function tokenize(string $text) : array + public function tokenize(string $text): array { $tokens = []; preg_match_all('/\w\w+/u', $text, $tokens); diff --git a/tests/Phpml/Association/AprioriTest.php b/tests/Phpml/Association/AprioriTest.php index 3b47483..7b637c9 100644 --- a/tests/Phpml/Association/AprioriTest.php +++ b/tests/Phpml/Association/AprioriTest.php @@ -7,6 +7,7 @@ namespace tests\Phpml\Classification; use Phpml\Association\Apriori; use Phpml\ModelManager; use PHPUnit\Framework\TestCase; +use ReflectionClass; class AprioriTest extends TestCase { @@ -172,7 +173,6 @@ class AprioriTest extends TestCase /** * 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 array $params Array of params to be passed * @@ -180,7 +180,7 @@ class AprioriTest extends TestCase */ public function invoke(&$object, $method, array $params = []) { - $reflection = new \ReflectionClass(get_class($object)); + $reflection = new ReflectionClass(get_class($object)); $method = $reflection->getMethod($method); $method->setAccessible(true); @@ -195,7 +195,7 @@ class AprioriTest extends TestCase $testSamples = [['alpha', 'epsilon'], ['beta', 'theta']]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/DecisionTreeTest.php b/tests/Phpml/Classification/DecisionTreeTest.php index c7d2d2a..8533500 100644 --- a/tests/Phpml/Classification/DecisionTreeTest.php +++ b/tests/Phpml/Classification/DecisionTreeTest.php @@ -24,7 +24,7 @@ class DecisionTreeTest extends TestCase ['sunny', 75, 70, 'true', 'Play'], ['overcast', 72, 90, 'true', 'Play'], ['overcast', 81, 75, 'false', 'Play'], - ['rain', 71, 80, 'true', 'Dont_play'] + ['rain', 71, 80, 'true', 'Dont_play'], ]; private $extraData = [ @@ -32,16 +32,6 @@ class DecisionTreeTest extends TestCase ['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() { [$data, $targets] = $this->getData($this->data); @@ -68,7 +58,7 @@ class DecisionTreeTest extends TestCase $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -83,6 +73,16 @@ class DecisionTreeTest extends TestCase [$data, $targets] = $this->getData($this->data); $classifier = new DecisionTree(5); $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]; } } diff --git a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php index 2cc8090..e01066c 100644 --- a/tests/Phpml/Classification/Ensemble/AdaBoostTest.php +++ b/tests/Phpml/Classification/Ensemble/AdaBoostTest.php @@ -52,7 +52,7 @@ class AdaBoostTest extends TestCase $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Ensemble/BaggingTest.php b/tests/Phpml/Classification/Ensemble/BaggingTest.php index 5bca8de..175b79a 100644 --- a/tests/Phpml/Classification/Ensemble/BaggingTest.php +++ b/tests/Phpml/Classification/Ensemble/BaggingTest.php @@ -26,7 +26,7 @@ class BaggingTest extends TestCase ['sunny', 75, 70, 'true', 'Play'], ['overcast', 72, 90, 'true', 'Play'], ['overcast', 81, 75, 'false', 'Play'], - ['rain', 71, 80, 'true', 'Dont_play'] + ['rain', 71, 80, 'true', 'Dont_play'], ]; private $extraData = [ @@ -61,7 +61,7 @@ class BaggingTest extends TestCase $testSamples = [['sunny', 78, 72, 'false'], ['overcast', 60, 60, 'false']]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -105,7 +105,7 @@ class BaggingTest extends TestCase { return [ DecisionTree::class => ['depth' => 5], - NaiveBayes::class => [] + NaiveBayes::class => [], ]; } @@ -117,6 +117,7 @@ class BaggingTest extends TestCase for ($i = 0; $i < 20; ++$i) { $populated = array_merge($populated, $input); } + shuffle($populated); $targets = array_column($populated, 4); array_walk($populated, function (&$v): void { diff --git a/tests/Phpml/Classification/Ensemble/RandomForestTest.php b/tests/Phpml/Classification/Ensemble/RandomForestTest.php index e4ca9e5..f2871e3 100644 --- a/tests/Phpml/Classification/Ensemble/RandomForestTest.php +++ b/tests/Phpml/Classification/Ensemble/RandomForestTest.php @@ -7,9 +7,20 @@ namespace tests\Phpml\Classification\Ensemble; use Phpml\Classification\DecisionTree; use Phpml\Classification\Ensemble\RandomForest; use Phpml\Classification\NaiveBayes; +use Throwable; 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) { $classifier = new RandomForest($numBaseClassifiers); @@ -22,15 +33,4 @@ class RandomForestTest extends BaggingTest { 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); - } - } } diff --git a/tests/Phpml/Classification/KNearestNeighborsTest.php b/tests/Phpml/Classification/KNearestNeighborsTest.php index 7ef6182..d9114b5 100644 --- a/tests/Phpml/Classification/KNearestNeighborsTest.php +++ b/tests/Phpml/Classification/KNearestNeighborsTest.php @@ -73,7 +73,7 @@ class KNearestNeighborsTest extends TestCase $classifier->train($trainSamples, $trainLabels); $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Linear/AdalineTest.php b/tests/Phpml/Classification/Linear/AdalineTest.php index 46f1a34..0a90a18 100644 --- a/tests/Phpml/Classification/Linear/AdalineTest.php +++ b/tests/Phpml/Classification/Linear/AdalineTest.php @@ -35,7 +35,7 @@ class AdalineTest extends TestCase $samples = [ [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 - [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]; @@ -55,7 +55,7 @@ class AdalineTest extends TestCase $samples = [ [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 - [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]; $classifier->train($samples, $targets); @@ -74,7 +74,7 @@ class AdalineTest extends TestCase $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Linear/DecisionStumpTest.php b/tests/Phpml/Classification/Linear/DecisionStumpTest.php index fa522ad..7fbabec 100644 --- a/tests/Phpml/Classification/Linear/DecisionStumpTest.php +++ b/tests/Phpml/Classification/Linear/DecisionStumpTest.php @@ -40,7 +40,7 @@ class DecisionStumpTest extends TestCase $samples = [ [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 - [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]; @@ -63,7 +63,7 @@ class DecisionStumpTest extends TestCase $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/Linear/PerceptronTest.php b/tests/Phpml/Classification/Linear/PerceptronTest.php index 2721067..17b1db8 100644 --- a/tests/Phpml/Classification/Linear/PerceptronTest.php +++ b/tests/Phpml/Classification/Linear/PerceptronTest.php @@ -37,7 +37,7 @@ class PerceptronTest extends TestCase $samples = [ [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 - [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]; @@ -58,7 +58,7 @@ class PerceptronTest extends TestCase $samples = [ [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 - [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]; $classifier->train($samples, $targets); @@ -77,7 +77,7 @@ class PerceptronTest extends TestCase $testSamples = [[0, 1], [1, 1], [0.2, 0.1]]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/MLPClassifierTest.php b/tests/Phpml/Classification/MLPClassifierTest.php index 519dc90..20bc5e1 100644 --- a/tests/Phpml/Classification/MLPClassifierTest.php +++ b/tests/Phpml/Classification/MLPClassifierTest.php @@ -150,7 +150,7 @@ class MLPClassifierTest extends TestCase $testSamples = [[0, 0], [1, 0], [0, 1], [1, 1]]; $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); @@ -189,7 +189,7 @@ class MLPClassifierTest extends TestCase new MLPClassifier(2, [2], [0]); } - private function getSynapsesNodes(array $synapses) : array + private function getSynapsesNodes(array $synapses): array { $nodes = []; foreach ($synapses as $synapse) { diff --git a/tests/Phpml/Classification/NaiveBayesTest.php b/tests/Phpml/Classification/NaiveBayesTest.php index 5b14f7e..c423c6d 100644 --- a/tests/Phpml/Classification/NaiveBayesTest.php +++ b/tests/Phpml/Classification/NaiveBayesTest.php @@ -59,7 +59,7 @@ class NaiveBayesTest extends TestCase $classifier->train($trainSamples, $trainLabels); $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Classification/SVCTest.php b/tests/Phpml/Classification/SVCTest.php index f734297..0941b34 100644 --- a/tests/Phpml/Classification/SVCTest.php +++ b/tests/Phpml/Classification/SVCTest.php @@ -57,7 +57,7 @@ class SVCTest extends TestCase $classifier->train($trainSamples, $trainLabels); $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); $modelManager = new ModelManager(); $modelManager->saveToFile($classifier, $filepath); diff --git a/tests/Phpml/Clustering/DBSCANTest.php b/tests/Phpml/Clustering/DBSCANTest.php index d8fb0fe..f12fb03 100644 --- a/tests/Phpml/Clustering/DBSCANTest.php +++ b/tests/Phpml/Clustering/DBSCANTest.php @@ -34,10 +34,25 @@ class DBSCANTest extends TestCase 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 = [ - ['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); diff --git a/tests/Phpml/Clustering/FuzzyCMeansTest.php b/tests/Phpml/Clustering/FuzzyCMeansTest.php index 5aed678..7b19422 100644 --- a/tests/Phpml/Clustering/FuzzyCMeansTest.php +++ b/tests/Phpml/Clustering/FuzzyCMeansTest.php @@ -20,6 +20,7 @@ class FuzzyCMeansTest extends TestCase unset($samples[$index]); } } + $this->assertCount(0, $samples); return $fcm; @@ -35,6 +36,7 @@ class FuzzyCMeansTest extends TestCase foreach ($matrix as $row) { $this->assertCount($sampleCount, $row); } + // Transpose of the matrix array_unshift($matrix, null); $matrix = call_user_func_array('array_map', $matrix); diff --git a/tests/Phpml/Clustering/KMeansTest.php b/tests/Phpml/Clustering/KMeansTest.php index e665c9f..f453340 100644 --- a/tests/Phpml/Clustering/KMeansTest.php +++ b/tests/Phpml/Clustering/KMeansTest.php @@ -23,6 +23,7 @@ class KMeansTest extends TestCase unset($samples[$index]); } } + $this->assertCount(0, $samples); } diff --git a/tests/Phpml/DimensionReduction/KernelPCATest.php b/tests/Phpml/DimensionReduction/KernelPCATest.php index 66b4622..3f37e3c 100644 --- a/tests/Phpml/DimensionReduction/KernelPCATest.php +++ b/tests/Phpml/DimensionReduction/KernelPCATest.php @@ -16,12 +16,12 @@ class KernelPCATest extends TestCase // A simple example whose result is known beforehand $data = [ - [2,2], [1.5,1], [1.,1.5], [1.,1.], - [2.,1.],[2,2.5], [2.,3.], [1.5,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.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] + [2, 2], [1.5, 1], [1., 1.5], [1., 1.], + [2., 1.], [2, 2.5], [2., 3.], [1.5, 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.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], ]; $transformed = [ [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.039234518840831], [0.40858295942991], [0.40110375047242], [-0.10555116296691], [-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); $reducedData = $kpca->fit($data); diff --git a/tests/Phpml/DimensionReduction/LDATest.php b/tests/Phpml/DimensionReduction/LDATest.php index 19124a0..42a0283 100644 --- a/tests/Phpml/DimensionReduction/LDATest.php +++ b/tests/Phpml/DimensionReduction/LDATest.php @@ -28,7 +28,7 @@ class LDATest extends TestCase [4.7, 3.2, 1.3, 0.2], [6.5, 3.0, 5.2, 2.0], [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 = [ [-1.4922092756753, 1.9047102045574], @@ -36,7 +36,7 @@ class LDATest extends TestCase [-1.3487505965419, 1.749846351699], [1.7759343101456, 2.0371552314006], [2.0059819019159, 2.4493123003226], - [1.701474913008, 1.9037880473772] + [1.701474913008, 1.9037880473772], ]; $control = []; diff --git a/tests/Phpml/DimensionReduction/PCATest.php b/tests/Phpml/DimensionReduction/PCATest.php index 0a60004..38b4744 100644 --- a/tests/Phpml/DimensionReduction/PCATest.php +++ b/tests/Phpml/DimensionReduction/PCATest.php @@ -26,12 +26,12 @@ class PCATest extends TestCase [2.0, 1.6], [1.0, 1.1], [1.5, 1.6], - [1.1, 0.9] + [1.1, 0.9], ]; $transformed = [ [-0.827970186], [1.77758033], [-0.992197494], [-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); $reducedData = $pca->fit($data); diff --git a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php index 0e1adbc..a8faacb 100644 --- a/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php +++ b/tests/Phpml/FeatureExtraction/TfIdfTransformerTest.php @@ -14,13 +14,41 @@ class TfIdfTransformerTest extends TestCase // https://en.wikipedia.org/wiki/Tf-idf $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 = [ - [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); diff --git a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php index 3419a29..463570c 100644 --- a/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php +++ b/tests/Phpml/FeatureExtraction/TokenCountVectorizerTest.php @@ -33,9 +33,42 @@ class TokenCountVectorizerTest extends TestCase ]; $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 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 1, 6 => 0, 7 => 2, 8 => 1, 9 => 1], + [ + 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 => 0, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 0, + 5 => 1, + 6 => 0, + 7 => 2, + 8 => 1, + 9 => 1, + ], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer()); @@ -66,10 +99,34 @@ class TokenCountVectorizerTest extends TestCase ]; $tokensCounts = [ - [0 => 1, 1 => 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], + [ + 0 => 1, + 1 => 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); @@ -88,9 +145,39 @@ class TokenCountVectorizerTest extends TestCase ]; $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, 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, + ], + [ + 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); @@ -124,9 +211,36 @@ class TokenCountVectorizerTest extends TestCase ]; $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 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 1, 5 => 0, 6 => 1, 7 => 1], + [ + 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 => 0, + 1 => 0, + 2 => 0, + 3 => 0, + 4 => 1, + 5 => 0, + 6 => 1, + 7 => 1, + ], ]; $vectorizer = new TokenCountVectorizer(new WhitespaceTokenizer(), $stopWords); diff --git a/tests/Phpml/Math/ComparisonTest.php b/tests/Phpml/Math/ComparisonTest.php index d31b1ae..2c72f5f 100644 --- a/tests/Phpml/Math/ComparisonTest.php +++ b/tests/Phpml/Math/ComparisonTest.php @@ -23,18 +23,15 @@ class ComparisonTest extends TestCase } /** - * @expectedException \Phpml\Exception\InvalidArgumentException - * @expectedExceptionMessage Invalid operator "~=" provided - */ + * @expectedException \Phpml\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid operator "~=" provided + */ public function testThrowExceptionWhenOperatorIsInvalid(): void { Comparison::compare(1, 1, '~='); } - /** - * @return array - */ - public function provideData() + public function provideData(): array { return [ // Greater diff --git a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php index 688874c..3956830 100644 --- a/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php +++ b/tests/Phpml/Math/LinearAlgebra/EigenDecompositionTest.php @@ -19,7 +19,7 @@ class EigenDecompositionTest extends TestCase // http://www.cs.otago.ac.nz/cosc453/student_tutorials/principal_components.pdf $matrix = [ [0.616555556, 0.615444444], - [0.614444444, 0.716555556] + [0.614444444, 0.716555556], ]; $knownEigvalues = [0.0490833989, 1.28402771]; $knownEigvectors = [[-0.735178656, 0.677873399], [-0.677873399, -0.735178656]]; @@ -43,7 +43,7 @@ class EigenDecompositionTest extends TestCase if ($i > $k) { $A[$i][$k] = $A[$k][$i]; } else { - $A[$i][$k] = rand(0, 10); + $A[$i][$k] = random_int(0, 10); } } } diff --git a/tests/Phpml/Math/MatrixTest.php b/tests/Phpml/Math/MatrixTest.php index cd9fff2..8d0e1be 100644 --- a/tests/Phpml/Math/MatrixTest.php +++ b/tests/Phpml/Math/MatrixTest.php @@ -239,12 +239,12 @@ class MatrixTest extends TestCase { $array = [ [1, 1, 1], - [2, 2, 2] + [2, 2, 2], ]; $transposed = [ [1, 2], [1, 2], - [1, 2] + [1, 2], ]; $this->assertEquals($transposed, Matrix::transposeArray($array)); diff --git a/tests/Phpml/Math/ProductTest.php b/tests/Phpml/Math/ProductTest.php index 9c3a4b4..bd55d11 100644 --- a/tests/Phpml/Math/ProductTest.php +++ b/tests/Phpml/Math/ProductTest.php @@ -6,6 +6,7 @@ namespace tests\Phpml\Math; use Phpml\Math\Product; use PHPUnit\Framework\TestCase; +use stdClass; class ProductTest extends TestCase { @@ -16,6 +17,6 @@ class ProductTest extends TestCase $this->assertEquals(8, Product::scalar([2], [4])); //test for non numeric values - $this->assertEquals(0, Product::scalar(['', null, [], new \stdClass()], [null])); + $this->assertEquals(0, Product::scalar(['', null, [], new stdClass()], [null])); } } diff --git a/tests/Phpml/Math/SetTest.php b/tests/Phpml/Math/SetTest.php index a572a42..645e607 100644 --- a/tests/Phpml/Math/SetTest.php +++ b/tests/Phpml/Math/SetTest.php @@ -13,7 +13,7 @@ class SetTest extends TestCase { $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(3, $union->cardinality()); } @@ -22,7 +22,7 @@ class SetTest extends TestCase { $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(1, $intersection->cardinality()); } @@ -31,7 +31,7 @@ class SetTest extends TestCase { $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(2, $difference->cardinality()); } diff --git a/tests/Phpml/Math/Statistic/CovarianceTest.php b/tests/Phpml/Math/Statistic/CovarianceTest.php index 3a8c9d3..97ec194 100644 --- a/tests/Phpml/Math/Statistic/CovarianceTest.php +++ b/tests/Phpml/Math/Statistic/CovarianceTest.php @@ -31,7 +31,7 @@ class CovarianceTest extends TestCase ]; $knownCovariance = [ [0.616555556, 0.615444444], - [0.615444444, 0.716555556]]; + [0.615444444, 0.716555556], ]; $x = array_column($matrix, 0); $y = array_column($matrix, 1); diff --git a/tests/Phpml/Metric/ClassificationReportTest.php b/tests/Phpml/Metric/ClassificationReportTest.php index b7ff02d..fb3471a 100644 --- a/tests/Phpml/Metric/ClassificationReportTest.php +++ b/tests/Phpml/Metric/ClassificationReportTest.php @@ -16,11 +16,31 @@ class ClassificationReportTest extends TestCase $report = new ClassificationReport($labels, $predicted); - $precision = ['cat' => 0.5, 'ant' => 0.0, 'bird' => 1.0]; - $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]; + $precision = [ + 'cat' => 0.5, + 'ant' => 0.0, + 'bird' => 1.0, + ]; + $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($recall, $report->getRecall(), '', 0.01); @@ -36,11 +56,31 @@ class ClassificationReportTest extends TestCase $report = new ClassificationReport($labels, $predicted); - $precision = [0 => 0.5, 1 => 0.0, 2 => 1.0]; - $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]; + $precision = [ + 0 => 0.5, + 1 => 0.0, + 2 => 1.0, + ]; + $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($recall, $report->getRecall(), '', 0.01); @@ -56,7 +96,10 @@ class ClassificationReportTest extends TestCase $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 @@ -66,7 +109,11 @@ class ClassificationReportTest extends TestCase $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 diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php index 95e95cb..91fc1c7 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php @@ -19,10 +19,7 @@ class BinaryStepTest extends TestCase $this->assertEquals($expected, $binaryStep->compute($value)); } - /** - * @return array - */ - public function binaryStepProvider() + public function binaryStepProvider(): array { return [ [1, 1], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php index f7af7c0..58b4b87 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php @@ -19,10 +19,7 @@ class GaussianTest extends TestCase $this->assertEquals($expected, $gaussian->compute($value), '', 0.001); } - /** - * @return array - */ - public function gaussianProvider() + public function gaussianProvider(): array { return [ [0.367, 1], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php index 95f437f..00348d9 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/HyperboliTangentTest.php @@ -19,10 +19,7 @@ class HyperboliTangentTest extends TestCase $this->assertEquals($expected, $tanh->compute($value), '', 0.001); } - /** - * @return array - */ - public function tanhProvider() + public function tanhProvider(): array { return [ [1.0, 0.761, 1], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php index 873520e..7e8e718 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php @@ -19,10 +19,7 @@ class PReLUTest extends TestCase $this->assertEquals($expected, $prelu->compute($value), '', 0.001); } - /** - * @return array - */ - public function preluProvider() + public function preluProvider(): array { return [ [0.01, 0.367, 0.367], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php index 096a376..d5a0ea3 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php @@ -19,10 +19,7 @@ class SigmoidTest extends TestCase $this->assertEquals($expected, $sigmoid->compute($value), '', 0.001); } - /** - * @return array - */ - public function sigmoidProvider() + public function sigmoidProvider(): array { return [ [1.0, 1, 7.25], diff --git a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php index 1800d7b..19a0312 100644 --- a/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php +++ b/tests/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLUTest.php @@ -19,16 +19,13 @@ class ThresholdedReLUTest extends TestCase $this->assertEquals($expected, $thresholdedReLU->compute($value)); } - /** - * @return array - */ - public function thresholdProvider() + public function thresholdProvider(): array { return [ [1.0, 0, 1.0], [0.5, 3.75, 3.75], [0.0, 0.5, 0.5], - [0.9, 0, 0.1] + [0.9, 0, 0.1], ]; } } diff --git a/tests/Phpml/NeuralNetwork/LayerTest.php b/tests/Phpml/NeuralNetwork/LayerTest.php index 284b1eb..72d8758 100644 --- a/tests/Phpml/NeuralNetwork/LayerTest.php +++ b/tests/Phpml/NeuralNetwork/LayerTest.php @@ -8,6 +8,7 @@ use Phpml\NeuralNetwork\Layer; use Phpml\NeuralNetwork\Node\Bias; use Phpml\NeuralNetwork\Node\Neuron; use PHPUnit\Framework\TestCase; +use stdClass; class LayerTest extends TestCase { @@ -43,7 +44,7 @@ class LayerTest extends TestCase */ public function testThrowExceptionOnInvalidNodeClass(): void { - new Layer(1, \stdClass::class); + new Layer(1, stdClass::class); } public function testAddNodesToLayer(): void diff --git a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php index 48bee66..c1779b8 100644 --- a/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php +++ b/tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php @@ -44,10 +44,7 @@ class LayeredNetworkTest extends TestCase $this->assertEquals([0.5], $network->getOutput()); } - /** - * @return LayeredNetwork - */ - private function getLayeredNetworkMock() + private function getLayeredNetworkMock(): LayeredNetwork { return $this->getMockForAbstractClass(LayeredNetwork::class); } diff --git a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php index 02d6dfa..1c09eae 100644 --- a/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php +++ b/tests/Phpml/NeuralNetwork/Node/Neuron/SynapseTest.php @@ -7,6 +7,7 @@ namespace tests\Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; class SynapseTest extends TestCase { @@ -39,11 +40,9 @@ class SynapseTest extends TestCase } /** - * @param int $output - * - * @return \PHPUnit_Framework_MockObject_MockObject + * @param int|float $output */ - private function getNodeMock($output = 1) + private function getNodeMock($output = 1): PHPUnit_Framework_MockObject_MockObject { $node = $this->getMockBuilder(Neuron::class)->getMock(); $node->method('getOutput')->willReturn($output); diff --git a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php index 89f1ca1..a58f2ec 100644 --- a/tests/Phpml/NeuralNetwork/Node/NeuronTest.php +++ b/tests/Phpml/NeuralNetwork/Node/NeuronTest.php @@ -8,6 +8,7 @@ use Phpml\NeuralNetwork\ActivationFunction\BinaryStep; use Phpml\NeuralNetwork\Node\Neuron; use Phpml\NeuralNetwork\Node\Neuron\Synapse; use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; class NeuronTest extends TestCase { @@ -52,11 +53,9 @@ class NeuronTest extends TestCase } /** - * @param int $output - * - * @return Synapse|\PHPUnit_Framework_MockObject_MockObject + * @return Synapse|PHPUnit_Framework_MockObject_MockObject */ - private function getSynapseMock($output = 2) + private function getSynapseMock(int $output = 2) { $synapse = $this->getMockBuilder(Synapse::class)->disableOriginalConstructor()->getMock(); $synapse->method('getOutput')->willReturn($output); diff --git a/tests/Phpml/PipelineTest.php b/tests/Phpml/PipelineTest.php index fc06e56..caf1961 100644 --- a/tests/Phpml/PipelineTest.php +++ b/tests/Phpml/PipelineTest.php @@ -72,7 +72,7 @@ class PipelineTest extends TestCase { $transformers = [ new TokenCountVectorizer(new WordTokenizer()), - new TfIdfTransformer() + new TfIdfTransformer(), ]; $estimator = new SVC(); diff --git a/tests/Phpml/Preprocessing/NormalizerTest.php b/tests/Phpml/Preprocessing/NormalizerTest.php index 22ed1bd..3d5940a 100644 --- a/tests/Phpml/Preprocessing/NormalizerTest.php +++ b/tests/Phpml/Preprocessing/NormalizerTest.php @@ -109,8 +109,9 @@ class NormalizerTest extends TestCase for ($i = 0; $i < 10; ++$i) { $sample = array_fill(0, 3, 0); for ($k = 0; $k < 3; ++$k) { - $sample[$k] = rand(1, 100); + $sample[$k] = random_int(1, 100); } + // Last feature's value shared across samples. $sample[] = 1; diff --git a/tests/Phpml/Regression/LeastSquaresTest.php b/tests/Phpml/Regression/LeastSquaresTest.php index 7d835a2..7517a9b 100644 --- a/tests/Phpml/Regression/LeastSquaresTest.php +++ b/tests/Phpml/Regression/LeastSquaresTest.php @@ -81,7 +81,7 @@ class LeastSquaresTest extends TestCase $testSamples = [[9300], [10565], [15000]]; $predicted = $regression->predict($testSamples); - $filename = 'least-squares-test-'.rand(100, 999).'-'.uniqid(); + $filename = 'least-squares-test-'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($regression, $filepath); diff --git a/tests/Phpml/Regression/SVRTest.php b/tests/Phpml/Regression/SVRTest.php index 3cd0ee5..a220d21 100644 --- a/tests/Phpml/Regression/SVRTest.php +++ b/tests/Phpml/Regression/SVRTest.php @@ -48,7 +48,7 @@ class SVRTest extends TestCase $testSamples = [64]; $predicted = $regression->predict($testSamples); - $filename = 'svr-test'.rand(100, 999).'-'.uniqid(); + $filename = 'svr-test'.random_int(100, 999).'-'.uniqid(); $filepath = tempnam(sys_get_temp_dir(), $filename); $modelManager = new ModelManager(); $modelManager->saveToFile($regression, $filepath);