Passed
Push — master ( 89268e...ba7114 )
by Arkadiusz
02:42
created

SupportVectorMachine/SupportVectorMachine.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\SupportVectorMachine;
6
7
use Phpml\Exception\InvalidArgumentException;
8
use Phpml\Exception\LibsvmCommandException;
9
use Phpml\Helper\Trainable;
10
11
class SupportVectorMachine
12
{
13
    use Trainable;
14
15
    /**
16
     * @var int
17
     */
18
    private $type;
19
20
    /**
21
     * @var int
22
     */
23
    private $kernel;
24
25
    /**
26
     * @var float
27
     */
28
    private $cost;
29
30
    /**
31
     * @var float
32
     */
33
    private $nu;
34
35
    /**
36
     * @var int
37
     */
38
    private $degree;
39
40
    /**
41
     * @var float|null
42
     */
43
    private $gamma;
44
45
    /**
46
     * @var float
47
     */
48
    private $coef0;
49
50
    /**
51
     * @var float
52
     */
53
    private $epsilon;
54
55
    /**
56
     * @var float
57
     */
58
    private $tolerance;
59
60
    /**
61
     * @var int
62
     */
63
    private $cacheSize;
64
65
    /**
66
     * @var bool
67
     */
68
    private $shrinking;
69
70
    /**
71
     * @var bool
72
     */
73
    private $probabilityEstimates;
74
75
    /**
76
     * @var string
77
     */
78
    private $binPath;
79
80
    /**
81
     * @var string
82
     */
83
    private $varPath;
84
85
    /**
86
     * @var string
87
     */
88
    private $model;
89
90
    /**
91
     * @var array
92
     */
93
    private $targets = [];
94
95
    public function __construct(
96
        int $type,
97
        int $kernel,
98
        float $cost = 1.0,
99
        float $nu = 0.5,
100
        int $degree = 3,
101
        ?float $gamma = null,
102
        float $coef0 = 0.0,
103
        float $epsilon = 0.1,
104
        float $tolerance = 0.001,
105
        int $cacheSize = 100,
106
        bool $shrinking = true,
107
        bool $probabilityEstimates = false
108
    ) {
109
        $this->type = $type;
110
        $this->kernel = $kernel;
111
        $this->cost = $cost;
112
        $this->nu = $nu;
113
        $this->degree = $degree;
114
        $this->gamma = $gamma;
115
        $this->coef0 = $coef0;
116
        $this->epsilon = $epsilon;
117
        $this->tolerance = $tolerance;
118
        $this->cacheSize = $cacheSize;
119
        $this->shrinking = $shrinking;
120
        $this->probabilityEstimates = $probabilityEstimates;
121
122
        $rootPath = realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', '..'])).DIRECTORY_SEPARATOR;
123
124
        $this->binPath = $rootPath.'bin'.DIRECTORY_SEPARATOR.'libsvm'.DIRECTORY_SEPARATOR;
125
        $this->varPath = $rootPath.'var'.DIRECTORY_SEPARATOR;
126
    }
127
128
    public function setBinPath(string $binPath): void
129
    {
130
        $this->ensureDirectorySeparator($binPath);
131
        $this->verifyBinPath($binPath);
132
133
        $this->binPath = $binPath;
134
    }
135
136
    public function setVarPath(string $varPath): void
137
    {
138
        if (!is_writable($varPath)) {
139
            throw InvalidArgumentException::pathNotWritable($varPath);
140
        }
141
142
        $this->ensureDirectorySeparator($varPath);
143
        $this->varPath = $varPath;
144
    }
145
146
    public function train(array $samples, array $targets): void
147
    {
148
        $this->samples = array_merge($this->samples, $samples);
149
        $this->targets = array_merge($this->targets, $targets);
150
151
        $trainingSet = DataTransformer::trainingSet($this->samples, $this->targets, in_array($this->type, [Type::EPSILON_SVR, Type::NU_SVR]));
152
        file_put_contents($trainingSetFileName = $this->varPath.uniqid('phpml', true), $trainingSet);
153
        $modelFileName = $trainingSetFileName.'-model';
154
155
        $command = $this->buildTrainCommand($trainingSetFileName, $modelFileName);
156
        $output = [];
157
        exec(escapeshellcmd($command).' 2>&1', $output, $return);
158
159
        unlink($trainingSetFileName);
160
161
        if ($return !== 0) {
162
            throw LibsvmCommandException::failedToRun($command, array_pop($output));
0 ignored issues
show
array_pop($output) is of type array|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
163
        }
164
165
        $this->model = file_get_contents($modelFileName);
166
167
        unlink($modelFileName);
168
    }
169
170
    public function getModel(): string
171
    {
172
        return $this->model;
173
    }
174
175
    /**
176
     * @return array|string
177
     *
178
     * @throws LibsvmCommandException
179
     */
180
    public function predict(array $samples)
181
    {
182
        $testSet = DataTransformer::testSet($samples);
183
        file_put_contents($testSetFileName = $this->varPath.uniqid('phpml', true), $testSet);
184
        file_put_contents($modelFileName = $testSetFileName.'-model', $this->model);
185
        $outputFileName = $testSetFileName.'-output';
186
187
        $command = sprintf('%ssvm-predict%s %s %s %s', $this->binPath, $this->getOSExtension(), $testSetFileName, $modelFileName, $outputFileName);
188
        $output = [];
189
        exec(escapeshellcmd($command).' 2>&1', $output, $return);
190
191
        unlink($testSetFileName);
192
        unlink($modelFileName);
193
        $predictions = file_get_contents($outputFileName);
194
195
        unlink($outputFileName);
196
197
        if ($return !== 0) {
198
            throw LibsvmCommandException::failedToRun($command, array_pop($output));
0 ignored issues
show
array_pop($output) is of type array|null, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
199
        }
200
201
        if (in_array($this->type, [Type::C_SVC, Type::NU_SVC])) {
202
            $predictions = DataTransformer::predictions($predictions, $this->targets);
203
        } else {
204
            $predictions = explode(PHP_EOL, trim($predictions));
205
        }
206
207
        if (!is_array($samples[0])) {
208
            return $predictions[0];
209
        }
210
211
        return $predictions;
212
    }
213
214
    private function getOSExtension(): string
215
    {
216
        $os = strtoupper(substr(PHP_OS, 0, 3));
217
        if ($os === 'WIN') {
218
            return '.exe';
219
        } elseif ($os === 'DAR') {
220
            return '-osx';
221
        }
222
223
        return '';
224
    }
225
226
    private function buildTrainCommand(string $trainingSetFileName, string $modelFileName): string
227
    {
228
        return sprintf(
229
            '%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',
230
            $this->binPath,
231
            $this->getOSExtension(),
232
            $this->type,
233
            $this->kernel,
234
            $this->cost,
235
            $this->nu,
236
            $this->degree,
237
            $this->gamma !== null ? ' -g '.$this->gamma : '',
238
            $this->coef0,
239
            $this->epsilon,
240
            $this->cacheSize,
241
            $this->tolerance,
242
            $this->shrinking,
243
            $this->probabilityEstimates,
244
            escapeshellarg($trainingSetFileName),
245
            escapeshellarg($modelFileName)
246
        );
247
    }
248
249
    private function ensureDirectorySeparator(string &$path): void
250
    {
251
        if (substr($path, -1) !== DIRECTORY_SEPARATOR) {
252
            $path .= DIRECTORY_SEPARATOR;
253
        }
254
    }
255
256
    private function verifyBinPath(string $path): void
257
    {
258
        if (!is_dir($path)) {
259
            throw InvalidArgumentException::pathNotFound($path);
260
        }
261
262
        $osExtension = $this->getOSExtension();
263
        foreach (['svm-predict', 'svm-scale', 'svm-train'] as $filename) {
264
            $filePath = $path.$filename.$osExtension;
265
            if (!file_exists($filePath)) {
266
                throw InvalidArgumentException::fileNotFound($filePath);
267
            }
268
269
            if (!is_executable($filePath)) {
270
                throw InvalidArgumentException::fileNotExecutable($filePath);
271
            }
272
        }
273
    }
274
}
275