MDL-65769 lib: update PHP-ML to 0.8.0
This commit is contained in:
@@ -4,18 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace Phpml\SupportVectorMachine;
|
||||
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
|
||||
class DataTransformer
|
||||
{
|
||||
/**
|
||||
* @param array $samples
|
||||
* @param array $labels
|
||||
* @param bool $targets
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function trainingSet(array $samples, array $labels, bool $targets = false): string
|
||||
{
|
||||
$set = '';
|
||||
$numericLabels = [];
|
||||
|
||||
if (!$targets) {
|
||||
$numericLabels = self::numericLabels($labels);
|
||||
}
|
||||
@@ -27,13 +24,12 @@ class DataTransformer
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $samples
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function testSet(array $samples): string
|
||||
{
|
||||
if (count($samples) === 0) {
|
||||
throw new InvalidArgumentException('The array has zero elements');
|
||||
}
|
||||
|
||||
if (!is_array($samples[0])) {
|
||||
$samples = [$samples];
|
||||
}
|
||||
@@ -46,30 +42,50 @@ class DataTransformer
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $rawPredictions
|
||||
* @param array $labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function predictions(string $rawPredictions, array $labels): array
|
||||
{
|
||||
$numericLabels = self::numericLabels($labels);
|
||||
$results = [];
|
||||
foreach (explode(PHP_EOL, $rawPredictions) as $result) {
|
||||
if (strlen($result) > 0) {
|
||||
$results[] = array_search($result, $numericLabels);
|
||||
if (isset($result[0])) {
|
||||
$results[] = array_search((int) $result, $numericLabels, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function probabilities(string $rawPredictions, array $labels): array
|
||||
{
|
||||
$numericLabels = self::numericLabels($labels);
|
||||
|
||||
$predictions = explode(PHP_EOL, trim($rawPredictions));
|
||||
|
||||
$header = array_shift($predictions);
|
||||
$headerColumns = explode(' ', (string) $header);
|
||||
array_shift($headerColumns);
|
||||
|
||||
$columnLabels = [];
|
||||
foreach ($headerColumns as $numericLabel) {
|
||||
$columnLabels[] = array_search((int) $numericLabel, $numericLabels, true);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
foreach ($predictions as $rawResult) {
|
||||
$probabilities = explode(' ', $rawResult);
|
||||
array_shift($probabilities);
|
||||
|
||||
$result = [];
|
||||
foreach ($probabilities as $i => $prob) {
|
||||
$result[$columnLabels[$i]] = (float) $prob;
|
||||
}
|
||||
|
||||
$results[] = $result;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public static function numericLabels(array $labels): array
|
||||
{
|
||||
$numericLabels = [];
|
||||
@@ -84,16 +100,11 @@ class DataTransformer
|
||||
return $numericLabels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $sample
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function sampleRow(array $sample): string
|
||||
{
|
||||
$row = [];
|
||||
foreach ($sample as $index => $feature) {
|
||||
$row[] = sprintf('%s:%s', $index + 1, $feature);
|
||||
$row[] = sprintf('%s:%F', $index + 1, $feature);
|
||||
}
|
||||
|
||||
return implode(' ', $row);
|
||||
|
||||
@@ -9,20 +9,20 @@ abstract class Kernel
|
||||
/**
|
||||
* u'*v.
|
||||
*/
|
||||
const LINEAR = 0;
|
||||
public const LINEAR = 0;
|
||||
|
||||
/**
|
||||
* (gamma*u'*v + coef0)^degree.
|
||||
*/
|
||||
const POLYNOMIAL = 1;
|
||||
public const POLYNOMIAL = 1;
|
||||
|
||||
/**
|
||||
* exp(-gamma*|u-v|^2).
|
||||
*/
|
||||
const RBF = 2;
|
||||
public const RBF = 2;
|
||||
|
||||
/**
|
||||
* tanh(gamma*u'*v + coef0).
|
||||
*/
|
||||
const SIGMOID = 3;
|
||||
public const SIGMOID = 3;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Phpml\SupportVectorMachine;
|
||||
|
||||
use Phpml\Exception\InvalidArgumentException;
|
||||
use Phpml\Exception\InvalidOperationException;
|
||||
use Phpml\Exception\LibsvmCommandException;
|
||||
use Phpml\Helper\Trainable;
|
||||
|
||||
class SupportVectorMachine
|
||||
@@ -36,7 +39,7 @@ class SupportVectorMachine
|
||||
private $degree;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
* @var float|null
|
||||
*/
|
||||
private $gamma;
|
||||
|
||||
@@ -90,24 +93,19 @@ class SupportVectorMachine
|
||||
*/
|
||||
private $targets = [];
|
||||
|
||||
/**
|
||||
* @param int $type
|
||||
* @param int $kernel
|
||||
* @param float $cost
|
||||
* @param float $nu
|
||||
* @param int $degree
|
||||
* @param float|null $gamma
|
||||
* @param float $coef0
|
||||
* @param float $epsilon
|
||||
* @param float $tolerance
|
||||
* @param int $cacheSize
|
||||
* @param bool $shrinking
|
||||
* @param bool $probabilityEstimates
|
||||
*/
|
||||
public function __construct(
|
||||
int $type, int $kernel, float $cost = 1.0, float $nu = 0.5, int $degree = 3,
|
||||
float $gamma = null, float $coef0 = 0.0, float $epsilon = 0.1, float $tolerance = 0.001,
|
||||
int $cacheSize = 100, bool $shrinking = true, bool $probabilityEstimates = false
|
||||
int $type,
|
||||
int $kernel,
|
||||
float $cost = 1.0,
|
||||
float $nu = 0.5,
|
||||
int $degree = 3,
|
||||
?float $gamma = null,
|
||||
float $coef0 = 0.0,
|
||||
float $epsilon = 0.1,
|
||||
float $tolerance = 0.001,
|
||||
int $cacheSize = 100,
|
||||
bool $shrinking = true,
|
||||
bool $probabilityEstimates = false
|
||||
) {
|
||||
$this->type = $type;
|
||||
$this->kernel = $kernel;
|
||||
@@ -122,90 +120,71 @@ class SupportVectorMachine
|
||||
$this->shrinking = $shrinking;
|
||||
$this->probabilityEstimates = $probabilityEstimates;
|
||||
|
||||
$rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', '..'])).DIRECTORY_SEPARATOR;
|
||||
$rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..'])).DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR;
|
||||
$this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $binPath
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBinPath(string $binPath)
|
||||
public function setBinPath(string $binPath): void
|
||||
{
|
||||
$this->ensureDirectorySeparator($binPath);
|
||||
$this->verifyBinPath($binPath);
|
||||
|
||||
$this->binPath = $binPath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $varPath
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setVarPath(string $varPath)
|
||||
public function setVarPath(string $varPath): void
|
||||
{
|
||||
$this->varPath = $varPath;
|
||||
if (!is_writable($varPath)) {
|
||||
throw new InvalidArgumentException(sprintf('The specified path "%s" is not writable', $varPath));
|
||||
}
|
||||
|
||||
return $this;
|
||||
$this->ensureDirectorySeparator($varPath);
|
||||
$this->varPath = $varPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $samples
|
||||
* @param array $targets
|
||||
*/
|
||||
public function train(array $samples, array $targets)
|
||||
public function train(array $samples, array $targets): void
|
||||
{
|
||||
$this->samples = array_merge($this->samples, $samples);
|
||||
$this->targets = array_merge($this->targets, $targets);
|
||||
|
||||
$trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR]));
|
||||
$trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR], true));
|
||||
file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet);
|
||||
$modelFileName = $trainingSetFileName.'-model';
|
||||
|
||||
$command = $this->buildTrainCommand($trainingSetFileName, $modelFileName);
|
||||
$output = '';
|
||||
exec(escapeshellcmd($command), $output);
|
||||
|
||||
$this->model = file_get_contents($modelFileName);
|
||||
$output = [];
|
||||
exec(escapeshellcmd($command).' 2>&1', $output, $return);
|
||||
|
||||
unlink($trainingSetFileName);
|
||||
|
||||
if ($return !== 0) {
|
||||
throw new LibsvmCommandException(
|
||||
sprintf('Failed running libsvm command: "%s" with reason: "%s"', $command, array_pop($output))
|
||||
);
|
||||
}
|
||||
|
||||
$this->model = (string) file_get_contents($modelFileName);
|
||||
|
||||
unlink($modelFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getModel()
|
||||
public function getModel(): string
|
||||
{
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $samples
|
||||
* @return array|string
|
||||
*
|
||||
* @return array
|
||||
* @throws LibsvmCommandException
|
||||
*/
|
||||
public function predict(array $samples)
|
||||
{
|
||||
$testSet = DataTransformer::testSet($samples);
|
||||
file_put_contents($testSetFileName = $this->varPath.uniqid('phpml', true), $testSet);
|
||||
file_put_contents($modelFileName = $testSetFileName.'-model', $this->model);
|
||||
$outputFileName = $testSetFileName.'-output';
|
||||
$predictions = $this->runSvmPredict($samples, false);
|
||||
|
||||
$command = sprintf('%ssvm-predict%s %s %s %s', $this->binPath, $this->getOSExtension(), $testSetFileName, $modelFileName, $outputFileName);
|
||||
$output = '';
|
||||
exec(escapeshellcmd($command), $output);
|
||||
|
||||
$predictions = file_get_contents($outputFileName);
|
||||
|
||||
unlink($testSetFileName);
|
||||
unlink($modelFileName);
|
||||
unlink($outputFileName);
|
||||
|
||||
if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
|
||||
if (in_array($this->type, [Type::C_SVC, Type::NU_SVC], true)) {
|
||||
$predictions = DataTransformer::predictions($predictions, $this->targets);
|
||||
} else {
|
||||
$predictions = explode(PHP_EOL, trim($predictions));
|
||||
@@ -219,9 +198,63 @@ class SupportVectorMachine
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return array|string
|
||||
*
|
||||
* @throws LibsvmCommandException
|
||||
*/
|
||||
private function getOSExtension()
|
||||
public function predictProbability(array $samples)
|
||||
{
|
||||
if (!$this->probabilityEstimates) {
|
||||
throw new InvalidOperationException('Model does not support probabiliy estimates');
|
||||
}
|
||||
|
||||
$predictions = $this->runSvmPredict($samples, true);
|
||||
|
||||
if (in_array($this->type, [Type::C_SVC, Type::NU_SVC], true)) {
|
||||
$predictions = DataTransformer::probabilities($predictions, $this->targets);
|
||||
} else {
|
||||
$predictions = explode(PHP_EOL, trim($predictions));
|
||||
}
|
||||
|
||||
if (!is_array($samples[0])) {
|
||||
return $predictions[0];
|
||||
}
|
||||
|
||||
return $predictions;
|
||||
}
|
||||
|
||||
private function runSvmPredict(array $samples, bool $probabilityEstimates): string
|
||||
{
|
||||
$testSet = DataTransformer::testSet($samples);
|
||||
file_put_contents($testSetFileName = $this->varPath.uniqid('phpml', true), $testSet);
|
||||
file_put_contents($modelFileName = $testSetFileName.'-model', $this->model);
|
||||
$outputFileName = $testSetFileName.'-output';
|
||||
|
||||
$command = $this->buildPredictCommand(
|
||||
$testSetFileName,
|
||||
$modelFileName,
|
||||
$outputFileName,
|
||||
$probabilityEstimates
|
||||
);
|
||||
$output = [];
|
||||
exec(escapeshellcmd($command).' 2>&1', $output, $return);
|
||||
|
||||
unlink($testSetFileName);
|
||||
unlink($modelFileName);
|
||||
$predictions = (string) file_get_contents($outputFileName);
|
||||
|
||||
unlink($outputFileName);
|
||||
|
||||
if ($return !== 0) {
|
||||
throw new LibsvmCommandException(
|
||||
sprintf('Failed running libsvm command: "%s" with reason: "%s"', $command, array_pop($output))
|
||||
);
|
||||
}
|
||||
|
||||
return $predictions;
|
||||
}
|
||||
|
||||
private function getOSExtension(): string
|
||||
{
|
||||
$os = strtoupper(substr(PHP_OS, 0, 3));
|
||||
if ($os === 'WIN') {
|
||||
@@ -233,15 +266,10 @@ class SupportVectorMachine
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $trainingSetFileName
|
||||
* @param string $modelFileName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string
|
||||
{
|
||||
return sprintf('%ssvm-train%s -s %s -t %s -c %s -n %s -d %s%s -r %s -p %s -m %s -e %s -h %d -b %d %s %s',
|
||||
return sprintf(
|
||||
'%ssvm-train%s -s %s -t %s -c %s -n %F -d %s%s -r %s -p %F -m %F -e %F -h %d -b %d %s %s',
|
||||
$this->binPath,
|
||||
$this->getOSExtension(),
|
||||
$this->type,
|
||||
@@ -260,4 +288,47 @@ class SupportVectorMachine
|
||||
escapeshellarg($modelFileName)
|
||||
);
|
||||
}
|
||||
|
||||
private function buildPredictCommand(
|
||||
string $testSetFileName,
|
||||
string $modelFileName,
|
||||
string $outputFileName,
|
||||
bool $probabilityEstimates
|
||||
): string {
|
||||
return sprintf(
|
||||
'%ssvm-predict%s -b %d %s %s %s',
|
||||
$this->binPath,
|
||||
$this->getOSExtension(),
|
||||
$probabilityEstimates ? 1 : 0,
|
||||
escapeshellarg($testSetFileName),
|
||||
escapeshellarg($modelFileName),
|
||||
escapeshellarg($outputFileName)
|
||||
);
|
||||
}
|
||||
|
||||
private function ensureDirectorySeparator(string &$path): void
|
||||
{
|
||||
if (substr($path, -1) !== DIRECTORY_SEPARATOR) {
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
||||
|
||||
private function verifyBinPath(string $path): void
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
throw new InvalidArgumentException(sprintf('The specified path "%s" does not exist', $path));
|
||||
}
|
||||
|
||||
$osExtension = $this->getOSExtension();
|
||||
foreach (['svm-predict', 'svm-scale', 'svm-train'] as $filename) {
|
||||
$filePath = $path.$filename.$osExtension;
|
||||
if (!file_exists($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('File "%s" not found', $filePath));
|
||||
}
|
||||
|
||||
if (!is_executable($filePath)) {
|
||||
throw new InvalidArgumentException(sprintf('File "%s" is not executable', $filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,25 @@ abstract class Type
|
||||
/**
|
||||
* classification.
|
||||
*/
|
||||
const C_SVC = 0;
|
||||
public const C_SVC = 0;
|
||||
|
||||
/**
|
||||
* classification.
|
||||
*/
|
||||
const NU_SVC = 1;
|
||||
public const NU_SVC = 1;
|
||||
|
||||
/**
|
||||
* distribution estimation.
|
||||
*/
|
||||
const ONE_CLASS_SVM = 2;
|
||||
public const ONE_CLASS_SVM = 2;
|
||||
|
||||
/**
|
||||
* regression.
|
||||
*/
|
||||
const EPSILON_SVR = 3;
|
||||
public const EPSILON_SVR = 3;
|
||||
|
||||
/**
|
||||
* regression.
|
||||
*/
|
||||
const NU_SVR = 4;
|
||||
public const NU_SVR = 4;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user