Some bugfixes and misc
This commit is contained in:
@@ -5,11 +5,11 @@ namespace App\Events;
|
||||
use App\Models\ActivationsFunctions;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PerceptronInitialization implements ShouldBroadcast
|
||||
class PerceptronInitialization implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PerceptronTrainingEnded implements ShouldBroadcast
|
||||
class PerceptronTrainingEnded implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class PerceptronTrainingIteration implements ShouldBroadcast
|
||||
class PerceptronTrainingIteration implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
@@ -39,19 +39,20 @@ class PerceptronTrainingIteration implements ShouldBroadcast
|
||||
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
$weights = collect($this->iterations)
|
||||
$iterations = self::normalizeForJson($this->iterations);
|
||||
$weights = collect($iterations)
|
||||
->pluck('weights')
|
||||
->first(fn (array $weights): bool => $weights !== []);
|
||||
$shouldBroadcastAllWeights = $weights !== null
|
||||
&& count(Arr::flatten($weights)) <= config('perceptron.max_displayed_weights');
|
||||
|
||||
$lastIterationIndex = count($this->iterations) - 1;
|
||||
$lastIterationIndex = count($iterations) - 1;
|
||||
$iterations = array_map(
|
||||
fn (array $iteration, int $index): array => $shouldBroadcastAllWeights || $index === $lastIterationIndex
|
||||
? $iteration
|
||||
: [...$iteration, 'weights' => []],
|
||||
$this->iterations,
|
||||
array_keys($this->iterations),
|
||||
$iterations,
|
||||
array_keys($iterations),
|
||||
);
|
||||
|
||||
return [
|
||||
@@ -59,4 +60,20 @@ class PerceptronTrainingIteration implements ShouldBroadcast
|
||||
'trainingId' => $this->trainingId,
|
||||
];
|
||||
}
|
||||
|
||||
public static function normalizeForJson(mixed $value): mixed
|
||||
{
|
||||
if (is_float($value) && ! is_finite($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return array_map(
|
||||
fn (mixed $item): mixed => self::normalizeForJson($item),
|
||||
$value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
|
||||
use App\Services\SynapticWeightsProvider\RandomSynapticWeights;
|
||||
use App\Services\SynapticWeightsProvider\ZeroSynapticWeights;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PerceptronController extends Controller
|
||||
{
|
||||
@@ -134,6 +133,11 @@ class PerceptronController extends Controller
|
||||
break;
|
||||
case 'table_2_11':
|
||||
$dataset['defaultMinError'] = 0.02;
|
||||
switch ($perceptronType) {
|
||||
case 'monolayer':
|
||||
$dataset['defaultLearningRate'] = 0.0015;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'table_4_12':
|
||||
switch ($perceptronType) {
|
||||
@@ -145,11 +149,13 @@ class PerceptronController extends Controller
|
||||
}
|
||||
break;
|
||||
case 'table_4_17':
|
||||
$dataset['defaultMinError'] = 0.055;
|
||||
switch ($perceptronType) {
|
||||
case 'multilayer':
|
||||
$dataset['defaultLearningRate'] = 0.5;
|
||||
$dataset['defaultMinError'] = 0.08;
|
||||
$dataset['defaultLearningRate'] = 0.3;
|
||||
$dataset['defaultMaxIterations'] = 400;
|
||||
$dataset['defaultHiddenLayers'] = 2;
|
||||
$dataset['defaultHiddenLayersNeurons'] = 2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -183,9 +189,6 @@ class PerceptronController extends Controller
|
||||
$sessionId = $request->input('session_id', session()->getId());
|
||||
$trainingId = $request->input('training_id');
|
||||
|
||||
// Remove the jobs for the sessionId
|
||||
DB::table('jobs')->where('payload', 'like', '%s:9:\"sessionId\";s:40:\"'.$sessionId.'\";%')->delete();
|
||||
|
||||
// Zero initialization prevents hidden layers from receiving a gradient.
|
||||
if ($perceptronType === 'multilayer' && $weightInitMethod === 'zeros') {
|
||||
$synapticWeightsProvider = new RandomSynapticWeights;
|
||||
|
||||
@@ -20,6 +20,7 @@ class MonoLayerPerceptronTraining extends NetworkTraining
|
||||
private array $labels;
|
||||
|
||||
public ActivationsFunctions $activationFunction = ActivationsFunctions::LINEAR;
|
||||
|
||||
public ?ActivationsFunctions $presentationLayerActivationFunction = ActivationsFunctions::STEP;
|
||||
|
||||
private float $epochError;
|
||||
@@ -62,7 +63,7 @@ class MonoLayerPerceptronTraining extends NetworkTraining
|
||||
while ($nextRow = $this->datasetReader->getNextLine()) {
|
||||
$inputsForCurrentEpoch[] = $nextRow;
|
||||
$inputs = array_slice($nextRow, 0, -1);
|
||||
$correctOutput = (int) end($nextRow);
|
||||
$correctOutput = (float) end($nextRow);
|
||||
|
||||
$iterationError = $this->iterationFunction($inputs, $correctOutput);
|
||||
|
||||
@@ -108,7 +109,7 @@ class MonoLayerPerceptronTraining extends NetworkTraining
|
||||
return $condition;
|
||||
}
|
||||
|
||||
private function iterationFunction(array $inputs, int $correctOutput): array
|
||||
private function iterationFunction(array $inputs, float $correctOutput): array
|
||||
{
|
||||
$outputs = $this->network->test($inputs);
|
||||
$desiredOutput = $this->getDesiredOutputFromCorrectOutput($correctOutput);
|
||||
@@ -137,10 +138,10 @@ class MonoLayerPerceptronTraining extends NetworkTraining
|
||||
return [$updatedWeights];
|
||||
}
|
||||
|
||||
private function getDesiredOutputFromCorrectOutput(int $correctOutput): array
|
||||
private function getDesiredOutputFromCorrectOutput(float $correctOutput): array
|
||||
{
|
||||
$desiredOutput = array_fill(0, count($this->labels), -1);
|
||||
$labelIndex = Arr::first(array_keys($this->labels), fn($key) => $this->labels[$key] == $correctOutput);
|
||||
$labelIndex = Arr::first(array_keys($this->labels), fn ($key) => $this->labels[$key] == $correctOutput);
|
||||
if ($labelIndex !== null) {
|
||||
$desiredOutput[$labelIndex] = 1;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Models\Perceptrons;
|
||||
|
||||
class InputNeuron extends Perceptron
|
||||
{
|
||||
private float $input = 0.0;
|
||||
|
||||
public function __construct(
|
||||
) {
|
||||
parent::__construct([]);
|
||||
|
||||
@@ -8,6 +8,8 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer
|
||||
{
|
||||
private array $data = [];
|
||||
|
||||
private ?float $lastBroadcastAt = null;
|
||||
|
||||
public function __construct(
|
||||
private string $sessionId,
|
||||
private string $trainingId,
|
||||
@@ -19,7 +21,9 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer
|
||||
return;
|
||||
}
|
||||
|
||||
$this->waitForBroadcastInterval();
|
||||
event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId));
|
||||
$this->lastBroadcastAt = microtime(true);
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
@@ -48,8 +52,22 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer
|
||||
private function payloadExceedsLimit(): bool
|
||||
{
|
||||
return strlen(json_encode([
|
||||
'iterations' => $this->data,
|
||||
'iterations' => PerceptronTrainingIteration::normalizeForJson($this->data),
|
||||
'trainingId' => $this->trainingId,
|
||||
], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size');
|
||||
}
|
||||
|
||||
private function waitForBroadcastInterval(): void
|
||||
{
|
||||
if ($this->lastBroadcastAt === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$minimumInterval = config('perceptron.broadcast_minimum_interval_ms') / 1000;
|
||||
$remainingInterval = $minimumInterval - (microtime(true) - $this->lastBroadcastAt);
|
||||
|
||||
if ($remainingInterval > 0) {
|
||||
usleep((int) ceil($remainingInterval * 1_000_000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff
|
||||
|
||||
private bool $shouldBroadcastEpoch = false;
|
||||
|
||||
private ?float $lastBroadcastAt = null;
|
||||
|
||||
public function __construct(
|
||||
private string $sessionId,
|
||||
private string $trainingId,
|
||||
@@ -24,7 +26,9 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff
|
||||
return;
|
||||
}
|
||||
|
||||
$this->waitForBroadcastInterval();
|
||||
event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId));
|
||||
$this->lastBroadcastAt = microtime(true);
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
@@ -57,8 +61,22 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff
|
||||
private function payloadExceedsLimit(): bool
|
||||
{
|
||||
return strlen(json_encode([
|
||||
'iterations' => $this->data,
|
||||
'iterations' => PerceptronTrainingIteration::normalizeForJson($this->data),
|
||||
'trainingId' => $this->trainingId,
|
||||
], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size');
|
||||
}
|
||||
|
||||
private function waitForBroadcastInterval(): void
|
||||
{
|
||||
if ($this->lastBroadcastAt === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$minimumInterval = config('perceptron.broadcast_minimum_interval_ms') / 1000;
|
||||
$remainingInterval = $minimumInterval - (microtime(true) - $this->lastBroadcastAt);
|
||||
|
||||
if ($remainingInterval > 0) {
|
||||
usleep((int) ceil($remainingInterval * 1_000_000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ return [
|
||||
'limited_broadcast_iterations' => 100,
|
||||
|
||||
/**
|
||||
* How much broadcasts is sent when in limmited broadcast mode
|
||||
* How much broadcasts is sent when in limited broadcast mode
|
||||
*/
|
||||
'limited_broadcast_number' => 100,
|
||||
|
||||
@@ -19,6 +19,11 @@ return [
|
||||
*/
|
||||
'broadcast_iteration_size' => 75,
|
||||
|
||||
/**
|
||||
* Minimum time between training progress broadcasts, in milliseconds.
|
||||
*/
|
||||
'broadcast_minimum_interval_ms' => 150,
|
||||
|
||||
/**
|
||||
* Maximum number of weights for which all iteration weights are broadcast
|
||||
* and displayed in the iteration table.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import type { ComputedRef } from 'vue';
|
||||
import type { Iteration } from '@/types/perceptron';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -13,7 +14,9 @@ const props = defineProps<{
|
||||
const allWeightPerIteration: ComputedRef<number[][]> = computed(() => {
|
||||
return props.iterations.map((iteration) => {
|
||||
// We flatten the weights
|
||||
return iteration.weights.flat(2);
|
||||
return iteration.weights
|
||||
.flat(2)
|
||||
.filter((weight): weight is number => weight !== null && Number.isFinite(weight));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,10 +64,10 @@ const rowBgDark = computed(() => {
|
||||
v-for="(weight, weightIndex) in allWeightPerIteration[index]"
|
||||
v-bind:key="weightIndex"
|
||||
>
|
||||
{{ weight.toFixed(2) }}
|
||||
{{ Number.isFinite(weight) ? weight.toFixed(2) : 'N/A' }}
|
||||
</td>
|
||||
</template>
|
||||
<td>{{ iteration.error.toFixed(2) }}</td>
|
||||
<td>{{ iteration.error === null ? 'N/A' : iteration.error.toFixed(2) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
|
||||
@@ -28,6 +28,8 @@ const datasets = computed<ErrorDataset[]>(() => {
|
||||
const exampleCountPerEpoch: Record<number, number> = {};
|
||||
|
||||
props.iterations.forEach((iteration) => {
|
||||
const error = iteration.error ?? 0;
|
||||
|
||||
if (!epochErrorOnly.value) {
|
||||
const exampleLabel = `Exemple ${iteration.exampleIndex}`;
|
||||
let dataset = datasets.find((d) => d.label === exampleLabel);
|
||||
@@ -45,8 +47,8 @@ const datasets = computed<ErrorDataset[]>(() => {
|
||||
}
|
||||
dataset.data.push(
|
||||
props.isRegression
|
||||
? Math.abs(iteration.error)
|
||||
: iteration.error,
|
||||
? Math.abs(error)
|
||||
: error,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +57,7 @@ const datasets = computed<ErrorDataset[]>(() => {
|
||||
// Epoch error
|
||||
epochAverageError[iteration.epoch] =
|
||||
(epochAverageError[iteration.epoch] || 0) +
|
||||
iteration.error ** 2 / 2;
|
||||
error ** 2 / 2;
|
||||
});
|
||||
|
||||
// Sort dataset by label (Exemple 0, Exemple 1, ...)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export type Iteration = {
|
||||
epoch: number;
|
||||
exampleIndex: number;
|
||||
weights: number[][][];
|
||||
error: number;
|
||||
weights: (number | null)[][][];
|
||||
error: number | null;
|
||||
};
|
||||
|
||||
export type Dataset = {
|
||||
|
||||
@@ -38,4 +38,22 @@ class IterationEventBufferTest extends TestCase
|
||||
&& $event->iterations[0]['epoch'] === 1;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_non_finite_values_are_normalized_before_broadcasting(): void
|
||||
{
|
||||
$event = new PerceptronTrainingIteration([
|
||||
[
|
||||
'epoch' => 1,
|
||||
'exampleIndex' => 0,
|
||||
'error' => NAN,
|
||||
'weights' => [[[INF]]],
|
||||
],
|
||||
], 'session', 'training');
|
||||
|
||||
$payload = $event->broadcastWith();
|
||||
|
||||
$this->assertNull($payload['iterations'][0]['error']);
|
||||
$this->assertNull($payload['iterations'][0]['weights'][0][0][0]);
|
||||
$this->assertJson(json_encode($payload, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user