Skip to content

Commit

Permalink
Fix activation functions support (#163)
Browse files Browse the repository at this point in the history
- Backpropagation using the neuron activation functions derivative
- instead of hardcoded sigmoid derivative
- Added missing activation functions derivatives
- Sigmoid forced for the output layer
- Updated ThresholdedReLU default threshold to 0 (acts as a ReLU)
- Unit tests for derivatives
- Unit tests for classifiers using different activation functions
- Added missing docs
  • Loading branch information
dmonllao authored and akondas committed Jan 9, 2018
1 parent 9938cf2 commit e83f7b9
Show file tree
Hide file tree
Showing 18 changed files with 254 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,6 @@ $mlp->predict([[1, 1, 1, 1], [0, 0, 0, 0]]);
* BinaryStep
* Gaussian
* HyperbolicTangent
* Parametric Rectified Linear Unit
* Sigmoid (default)
* Thresholded Rectified Linear Unit
6 changes: 6 additions & 0 deletions src/Phpml/NeuralNetwork/ActivationFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ interface ActivationFunction
* @param float|int $value
*/
public function compute($value): float;

/**
* @param float|int $value
* @param float|int $computedvalue
*/
public function differentiate($value, $computedvalue): float;
}
13 changes: 13 additions & 0 deletions src/Phpml/NeuralNetwork/ActivationFunction/BinaryStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,17 @@ public function compute($value): float
{
return $value >= 0 ? 1.0 : 0.0;
}

/**
* @param float|int $value
* @param float|int $computedvalue
*/
public function differentiate($value, $computedvalue): float
{
if ($value === 0 || $value === 0.0) {
return 1;
}

return 0;
}
}
9 changes: 9 additions & 0 deletions src/Phpml/NeuralNetwork/ActivationFunction/Gaussian.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ public function compute($value): float
{
return exp(-pow($value, 2));
}

/**
* @param float|int $value
* @param float|int $calculatedvalue
*/
public function differentiate($value, $calculatedvalue): float
{
return -2 * $value * $calculatedvalue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@ public function compute($value): float
{
return tanh($this->beta * $value);
}

/**
* @param float|int $value
* @param float|int $computedvalue
*/
public function differentiate($value, $computedvalue): float
{
return 1 - pow($computedvalue, 2);
}
}
9 changes: 9 additions & 0 deletions src/Phpml/NeuralNetwork/ActivationFunction/PReLU.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@ public function compute($value): float
{
return $value >= 0 ? $value : $this->beta * $value;
}

/**
* @param float|int $value
* @param float|int $computedvalue
*/
public function differentiate($value, $computedvalue): float
{
return $computedvalue >= 0 ? 1.0 : $this->beta;
}
}
9 changes: 9 additions & 0 deletions src/Phpml/NeuralNetwork/ActivationFunction/Sigmoid.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@ public function compute($value): float
{
return 1 / (1 + exp(-$this->beta * $value));
}

/**
* @param float|int $value
* @param float|int $computedvalue
*/
public function differentiate($value, $computedvalue): float
{
return $computedvalue * (1 - $computedvalue);
}
}
11 changes: 10 additions & 1 deletion src/Phpml/NeuralNetwork/ActivationFunction/ThresholdedReLU.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ThresholdedReLU implements ActivationFunction
*/
private $theta;

public function __construct(float $theta = 1.0)
public function __construct(float $theta = 0.0)
{
$this->theta = $theta;
}
Expand All @@ -25,4 +25,13 @@ public function compute($value): float
{
return $value > $this->theta ? $value : 0.0;
}

/**
* @param float|int $value
* @param float|int $calculatedvalue
*/
public function differentiate($value, $calculatedvalue): float
{
return $calculatedvalue >= $this->theta ? 1.0 : 0.0;
}
}
6 changes: 5 additions & 1 deletion src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Phpml\Helper\Predictable;
use Phpml\IncrementalEstimator;
use Phpml\NeuralNetwork\ActivationFunction;
use Phpml\NeuralNetwork\ActivationFunction\Sigmoid;
use Phpml\NeuralNetwork\Layer;
use Phpml\NeuralNetwork\Node\Bias;
use Phpml\NeuralNetwork\Node\Input;
Expand Down Expand Up @@ -125,7 +126,10 @@ private function initNetwork(): void
{
$this->addInputLayer($this->inputLayerFeatures);
$this->addNeuronLayers($this->hiddenLayers, $this->activationFunction);
$this->addNeuronLayers([count($this->classes)], $this->activationFunction);

// Sigmoid function for the output layer as we want a value from 0 to 1.
$sigmoid = new Sigmoid();
$this->addNeuronLayers([count($this->classes)], $sigmoid);

$this->addBiasNodes();
$this->generateSynapses();
Expand Down
17 changes: 14 additions & 3 deletions src/Phpml/NeuralNetwork/Node/Neuron.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class Neuron implements Node
*/
protected $output = 0.0;

/**
* @var float
*/
protected $z = 0.0;

public function __construct(?ActivationFunction $activationFunction = null)
{
$this->activationFunction = $activationFunction ?: new Sigmoid();
Expand All @@ -47,19 +52,25 @@ public function getSynapses()
public function getOutput(): float
{
if ($this->output === 0.0) {
$sum = 0.0;
$this->z = 0;
foreach ($this->synapses as $synapse) {
$sum += $synapse->getOutput();
$this->z += $synapse->getOutput();
}

$this->output = $this->activationFunction->compute($sum);
$this->output = $this->activationFunction->compute($this->z);
}

return $this->output;
}

public function getDerivative(): float
{
return $this->activationFunction->differentiate($this->z, $this->output);
}

public function reset(): void
{
$this->output = 0.0;
$this->z = 0.0;
}
}
2 changes: 1 addition & 1 deletion src/Phpml/NeuralNetwork/Training/Backpropagation.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function backpropagate(array $layers, $targetClass): void
private function getSigma(Neuron $neuron, int $targetClass, int $key, bool $lastLayer): float
{
$neuronOutput = $neuron->getOutput();
$sigma = $neuronOutput * (1 - $neuronOutput);
$sigma = $neuron->getDerivative();

if ($lastLayer) {
$value = 0;
Expand Down
32 changes: 32 additions & 0 deletions tests/Phpml/Classification/MLPClassifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
use Phpml\Classification\MLPClassifier;
use Phpml\Exception\InvalidArgumentException;
use Phpml\ModelManager;
use Phpml\NeuralNetwork\ActivationFunction;
use Phpml\NeuralNetwork\ActivationFunction\HyperbolicTangent;
use Phpml\NeuralNetwork\ActivationFunction\PReLU;
use Phpml\NeuralNetwork\ActivationFunction\Sigmoid;
use Phpml\NeuralNetwork\ActivationFunction\ThresholdedReLU;
use Phpml\NeuralNetwork\Node\Neuron;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -141,6 +146,33 @@ public function testBackpropagationLearningMulticlass(): void
$this->assertEquals(4, $network->predict([0, 0, 0, 0, 0]));
}

/**
* @dataProvider activationFunctionsProvider
*/
public function testBackpropagationActivationFunctions(ActivationFunction $activationFunction): void
{
$network = new MLPClassifier(5, [3], ['a', 'b'], 10000, $activationFunction);
$network->train(
[[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 1, 0], [1, 1, 1, 1, 1]],
['a', 'b', 'a', 'a']
);

$this->assertEquals('a', $network->predict([1, 0, 0, 0, 0]));
$this->assertEquals('b', $network->predict([0, 1, 0, 0, 0]));
$this->assertEquals('a', $network->predict([0, 0, 1, 1, 0]));
$this->assertEquals('a', $network->predict([1, 1, 1, 1, 1]));
}

public function activationFunctionsProvider(): array
{
return [
[new Sigmoid()],
[new HyperbolicTangent()],
[new PReLU()],
[new ThresholdedReLU()],
];
}

public function testSaveAndRestore(): void
{
// Instantinate new Percetron trained for OR problem
Expand Down
19 changes: 19 additions & 0 deletions tests/Phpml/NeuralNetwork/ActivationFunction/BinaryStepTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,23 @@ public function binaryStepProvider(): array
[0, -0.1],
];
}

/**
* @dataProvider binaryStepDerivativeProvider
*/
public function testBinaryStepDerivative($expected, $value): void
{
$binaryStep = new BinaryStep();
$activatedValue = $binaryStep->compute($value);
$this->assertEquals($expected, $binaryStep->differentiate($value, $activatedValue));
}

public function binaryStepDerivativeProvider(): array
{
return [
[0, -1],
[1, 0],
[0, 1],
];
}
}
23 changes: 23 additions & 0 deletions tests/Phpml/NeuralNetwork/ActivationFunction/GaussianTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,27 @@ public function gaussianProvider(): array
[0, -3],
];
}

/**
* @dataProvider gaussianDerivativeProvider
*/
public function testGaussianDerivative($expected, $value): void
{
$gaussian = new Gaussian();
$activatedValue = $gaussian->compute($value);
$this->assertEquals($expected, $gaussian->differentiate($value, $activatedValue), '', 0.001);
}

public function gaussianDerivativeProvider(): array
{
return [
[0, -5],
[0.735, -1],
[0.779, -0.5],
[0, 0],
[-0.779, 0.5],
[-0.735, 1],
[0, 5],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,28 @@ public function tanhProvider(): array
[0.3, 0, 0],
];
}

/**
* @dataProvider tanhDerivativeProvider
*/
public function testHyperbolicTangentDerivative($beta, $expected, $value): void
{
$tanh = new HyperbolicTangent($beta);
$activatedValue = $tanh->compute($value);
$this->assertEquals($expected, $tanh->differentiate($value, $activatedValue), '', 0.001);
}

public function tanhDerivativeProvider(): array
{
return [
[1.0, 0, -6],
[1.0, 0.419, -1],
[1.0, 1, 0],
[1.0, 0.419, 1],
[1.0, 0, 6],
[0.5, 0.786, 1],
[0.5, 0.786, -1],
[0.3, 1, 0],
];
}
}
23 changes: 23 additions & 0 deletions tests/Phpml/NeuralNetwork/ActivationFunction/PReLUTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,27 @@ public function preluProvider(): array
[0.02, -0.06, -3],
];
}

/**
* @dataProvider preluDerivativeProvider
*/
public function testPReLUDerivative($beta, $expected, $value): void
{
$prelu = new PReLU($beta);
$activatedValue = $prelu->compute($value);
$this->assertEquals($expected, $prelu->differentiate($value, $activatedValue));
}

public function preluDerivativeProvider(): array
{
return [
[0.5, 0.5, -3],
[0.5, 1, 0],
[0.5, 1, 1],
[0.01, 1, 1],
[1, 1, 1],
[0.3, 1, 0.1],
[0.1, 0.1, -0.1],
];
}
}
24 changes: 24 additions & 0 deletions tests/Phpml/NeuralNetwork/ActivationFunction/SigmoidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,28 @@ public function sigmoidProvider(): array
[2.0, 0, -3.75],
];
}

/**
* @dataProvider sigmoidDerivativeProvider
*/
public function testSigmoidDerivative($beta, $expected, $value): void
{
$sigmoid = new Sigmoid($beta);
$activatedValue = $sigmoid->compute($value);
$this->assertEquals($expected, $sigmoid->differentiate($value, $activatedValue), '', 0.001);
}

public function sigmoidDerivativeProvider(): array
{
return [
[1.0, 0, -10],
[1, 0.006, -5],
[1.0, 0.25, 0],
[1, 0.006, 5],
[1.0, 0, 10],
[2.0, 0.25, 0],
[0.5, 0.246, 0.5],
[0.5, 0.241, 0.75],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,26 @@ public function thresholdProvider(): array
[0.9, 0, 0.1],
];
}

/**
* @dataProvider thresholdDerivativeProvider
*/
public function testThresholdedReLUDerivative($theta, $expected, $value): void
{
$thresholdedReLU = new ThresholdedReLU($theta);
$activatedValue = $thresholdedReLU->compute($value);
$this->assertEquals($expected, $thresholdedReLU->differentiate($value, $activatedValue));
}

public function thresholdDerivativeProvider(): array
{
return [
[0, 1, 1],
[0, 1, 0],
[0.5, 1, 1],
[0.5, 1, 1],
[0.5, 0, 0],
[2, 0, -1],
];
}
}

0 comments on commit e83f7b9

Please sign in to comment.