Test Failed
Pull Request — master (#63)
by
unknown
02:47
created

GD   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 98
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 9
lcom 1
cbo 1
dl 0
loc 98
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
B runOptimization() 0 27 3
A gradient() 0 21 2
A updateWeightsWithUpdates() 0 19 4
1
<?php declare(strict_types=1);
2
3
namespace Phpml\Helper\Optimizer;
4
5
/**
6
 * Batch version of Gradient Descent to optimize the weights
7
 * of a classifier given samples, targets and the objective function to minimize
8
 */
9
class GD extends StochasticGD
10
{
11
    /**
12
     * Number of samples given
13
     *
14
     * @var int
15
     */
16
    protected $sampleCount;
17
18
    /**
19
     * @param array $samples
20
     * @param array $targets
21
     * @param \Closure $gradientCb
22
     *
23
     * @return array
24
     */
25
    public function runOptimization(array $samples, array $targets, \Closure $gradientCb)
26
    {
27
        $this->samples = $samples;
28
        $this->targets = $targets;
29
        $this->gradientCb = $gradientCb;
30
        $this->sampleCount = count($this->samples);
31
32
        // Batch learning is executed:
33
        $currIter = 0;
34
        $this->costValues = [];
35
        while ($this->maxIterations > $currIter++) {
36
            $theta = $this->theta;
37
38
            // Calculate update terms for each sample
39
            list($errors, $updates, $totalPenalty) = $this->gradient($theta);
40
41
            $this->updateWeightsWithUpdates($updates, $totalPenalty);
42
43
            $this->costValues[] = array_sum($errors)/$this->sampleCount;
44
45
            if ($this->earlyStop($theta)) {
46
                break;
47
            }
48
        }
49
50
        return $this->theta;
51
    }
52
53
    /**
54
     * Calculates gradient, cost function and penalty term for each sample
55
     * then returns them as an array of values
56
     *
57
     * @param array $theta
58
     *
59
     * @return array
60
     */
61
    protected function gradient(array $theta)
62
    {
63
        $costs = [];
64
        $gradient= [];
65
        $totalPenalty = 0;
66
67
        foreach ($this->samples as $index => $sample) {
68
            $target = $this->targets[$index];
69
70
            $result = ($this->gradientCb)($theta, $sample, $target);
71
            list($cost, $grad, $penalty) = array_pad($result, 3, 0);
72
73
            $costs[] = $cost;
74
            $gradient[]= $grad;
75
            $totalPenalty += $penalty;
76
        }
77
78
        $totalPenalty /= $this->sampleCount;
79
80
        return [$costs, $gradient, $totalPenalty];
81
    }
82
83
    /**
84
     * @param array $updates
85
     * @param float $penalty
86
     */
87
    protected function updateWeightsWithUpdates(array $updates, float $penalty)
88
    {
89
        // Updates all weights at once
90
        for ($i=0; $i <= $this->dimensions; $i++) {
91
            if ($i == 0) {
92
                $this->theta[0] -= $this->learningRate * array_sum($updates);
93
            } else {
94
                $col = array_column($this->samples, $i - 1);
95
96
                $error = 0;
97
                foreach ($col as $index => $val) {
98
                    $error += $val * $updates[$index];
99
                }
100
101
                $this->theta[$i] -= $this->learningRate *
102
                    ($error + $penalty * $this->theta[$i]);
103
            }
104
        }
105
    }
106
}
107