Skip to content

Commit

Permalink
Configure an Activation Function per hidden layer (#208)
Browse files Browse the repository at this point in the history
* ability to specify per-layer activation function

* some tests for new addition to layer

* appease style CI whitespace issue

* more flexible addition of layers, and developer can pass Layer object in manually

* new test for layer object in mlp constructor

* documentation for added MLP functionality
  • Loading branch information
jonbaldie authored and akondas committed Feb 1, 2018
1 parent 8daed24 commit c32bf3f
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ $mlp = new MLPClassifier(4, [2], ['a', 'b', 'c']);
```

An Activation Function may also be passed in with each individual hidden layer. Example:

```
use Phpml\NeuralNetwork\ActivationFunction\PReLU;
use Phpml\NeuralNetwork\ActivationFunction\Sigmoid;
$mlp = new MLPClassifier(4, [[2, new PReLU], [2, new Sigmoid]], ['a', 'b', 'c']);
```

Instead of configuring each hidden layer as an array, they may also be configured with Layer objects. Example:

```
use Phpml\NeuralNetwork\Layer;
use Phpml\NeuralNetwork\Node\Neuron;
$layer1 = new Layer(2, Neuron::class, new PReLU);
$layer2 = new Layer(2, Neuron::class, new Sigmoid);
$mlp = new MLPClassifier(4, [$layer1, $layer2], ['a', 'b', 'c']);
```

## Train

To train a MLP simply provide train samples and labels (as array). Example:
Expand Down
13 changes: 10 additions & 3 deletions src/Phpml/NeuralNetwork/Network/MultilayerPerceptron.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,17 @@ private function addInputLayer(int $nodes): void
$this->addLayer(new Layer($nodes, Input::class));
}

private function addNeuronLayers(array $layers, ?ActivationFunction $activationFunction = null): void
private function addNeuronLayers(array $layers, ?ActivationFunction $defaultActivationFunction = null): void
{
foreach ($layers as $neurons) {
$this->addLayer(new Layer($neurons, Neuron::class, $activationFunction));
foreach ($layers as $layer) {
if (is_array($layer)) {
$function = $layer[1] instanceof ActivationFunction ? $layer[1] : $defaultActivationFunction;
$this->addLayer(new Layer($layer[0], Neuron::class, $function));
} elseif ($layer instanceof Layer) {
$this->addLayer($layer);
} else {
$this->addLayer(new Layer($layer, Neuron::class, $defaultActivationFunction));
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions tests/Phpml/NeuralNetwork/Network/LayeredNetworkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Phpml\Tests\NeuralNetwork\Network;

use Phpml\NeuralNetwork\ActivationFunction;
use Phpml\NeuralNetwork\Layer;
use Phpml\NeuralNetwork\Network\LayeredNetwork;
use Phpml\NeuralNetwork\Node\Input;
Expand Down Expand Up @@ -45,11 +46,28 @@ public function testSetInputAndGetOutput(): void
$this->assertEquals([0.5], $network->getOutput());
}

public function testSetInputAndGetOutputWithCustomActivationFunctions(): void
{
$network = $this->getLayeredNetworkMock();
$network->addLayer(new Layer(2, Input::class, $this->getActivationFunctionMock()));

$network->setInput($input = [34, 43]);
$this->assertEquals($input, $network->getOutput());
}

/**
* @return LayeredNetwork|PHPUnit_Framework_MockObject_MockObject
*/
private function getLayeredNetworkMock()
{
return $this->getMockForAbstractClass(LayeredNetwork::class);
}

/**
* @return ActivationFunction|PHPUnit_Framework_MockObject_MockObject
*/
private function getActivationFunctionMock()
{
return $this->getMockForAbstractClass(ActivationFunction::class);
}
}
52 changes: 52 additions & 0 deletions tests/Phpml/NeuralNetwork/Network/MultilayerPerceptronTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

namespace Phpml\Tests\NeuralNetwork\Network;

use Phpml\NeuralNetwork\ActivationFunction;
use Phpml\NeuralNetwork\Layer;
use Phpml\NeuralNetwork\Network\MultilayerPerceptron;
use Phpml\NeuralNetwork\Node\Neuron;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject;

class MultilayerPerceptronTest extends TestCase
{
Expand All @@ -26,4 +30,52 @@ public function testLearningRateSetter(): void
$backprop = $this->readAttribute($mlp, 'backpropagation');
$this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate'));
}

public function testLearningRateSetterWithCustomActivationFunctions(): void
{
$activation_function = $this->getActivationFunctionMock();

/** @var MultilayerPerceptron $mlp */
$mlp = $this->getMockForAbstractClass(
MultilayerPerceptron::class,
[5, [[3, $activation_function], [5, $activation_function]], [0, 1], 1000, null, 0.42]
);

$this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate'));
$backprop = $this->readAttribute($mlp, 'backpropagation');
$this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate'));

$mlp->setLearningRate(0.24);
$this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate'));
$backprop = $this->readAttribute($mlp, 'backpropagation');
$this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate'));
}

public function testLearningRateSetterWithLayerObject(): void
{
$activation_function = $this->getActivationFunctionMock();

/** @var MultilayerPerceptron $mlp */
$mlp = $this->getMockForAbstractClass(
MultilayerPerceptron::class,
[5, [new Layer(3, Neuron::class, $activation_function), new Layer(5, Neuron::class, $activation_function)], [0, 1], 1000, null, 0.42]
);

$this->assertEquals(0.42, $this->readAttribute($mlp, 'learningRate'));
$backprop = $this->readAttribute($mlp, 'backpropagation');
$this->assertEquals(0.42, $this->readAttribute($backprop, 'learningRate'));

$mlp->setLearningRate(0.24);
$this->assertEquals(0.24, $this->readAttribute($mlp, 'learningRate'));
$backprop = $this->readAttribute($mlp, 'backpropagation');
$this->assertEquals(0.24, $this->readAttribute($backprop, 'learningRate'));
}

/**
* @return ActivationFunction|PHPUnit_Framework_MockObject_MockObject
*/
private function getActivationFunctionMock()
{
return $this->getMockForAbstractClass(ActivationFunction::class);
}
}

0 comments on commit c32bf3f

Please sign in to comment.