← Back
Editing: MinMaxNormalizer.php
<?php namespace Rubix\ML\Transformers; use Rubix\ML\DataType; use Rubix\ML\Persistable; use Rubix\ML\Datasets\Dataset; use Rubix\ML\Traits\AutotrackRevisions; use Rubix\ML\Specifications\SamplesAreCompatibleWithTransformer; use Rubix\ML\Exceptions\InvalidArgumentException; use Rubix\ML\Exceptions\RuntimeException; use function min; use function max; use const Rubix\ML\EPSILON; /** * Min Max Normalizer * * The *Min Max* Normalizer scales the input features to a value between * a user-specified range (default 0 to 1). * * @category Machine Learning * @package Rubix/ML * @author Andrew DalPino */ class MinMaxNormalizer implements Transformer, Stateful, Elastic, Reversible, Persistable { use AutotrackRevisions; /** * The minimum value of the transformed features. * * @var float */ protected float $min; /** * The maximum value of the transformed features. * * @var float */ protected float $max; /** * The computed minimums of the fitted data. * * @var (int|float)[]|null */ protected ?array $minimums = null; /** * The computed maximums of the fitted data. * * @var (int|float)[]|null */ protected ?array $maximums = null; /** * The scale coefficients of each feature. * * @var float[]|null */ protected ?array $scales = null; /** * @param float $min * @param float $max * @throws InvalidArgumentException */ public function __construct(float $min = 0.0, float $max = 1.0) { if ($min > $max) { throw new InvalidArgumentException('Minimum cannot be greater' . ' than maximum.'); } $this->min = $min; $this->max = $max; } /** * Return the data types that this transformer is compatible with. * * @internal * * @return list<DataType> */ public function compatibility() : array { return DataType::all(); } /** * Is the transformer fitted? * * @return bool */ public function fitted() : bool { return $this->minimums and $this->maximums; } /** * Return the minmums of each feature column. * * @return (int|float)[]|null */ public function minimums() : ?array { return $this->minimums; } /** * Return the maximums of each feature column. * * @return (int|float)[]|null */ public function maximums() : ?array { return $this->maximums; } /** * Fit the transformer to a dataset. * * @param Dataset $dataset */ public function fit(Dataset $dataset) : void { SamplesAreCompatibleWithTransformer::with($dataset, $this)->check(); $this->minimums = $this->maximums = $this->scales = []; foreach ($dataset->featureTypes() as $column => $type) { if ($type->isContinuous()) { $values = $dataset->feature($column); /** @var int|float $min */ $min = min($values); /** @var int|float $max */ $max = max($values); $scale = ($this->max - $this->min) / (($max - $min) ?: EPSILON); $this->minimums[$column] = $min; $this->maximums[$column] = $max; $this->scales[$column] = $scale; } } } /** * Update the fitting of the transformer. * * @param Dataset $dataset */ public function update(Dataset $dataset) : void { if (!isset($this->minimums, $this->maximums, $this->scales)) { $this->fit($dataset); return; } SamplesAreCompatibleWithTransformer::with($dataset, $this)->check(); foreach ($this->scales as $column => &$scale) { $values = $dataset->feature($column); /** @var int|float $min */ $min = min($this->minimums[$column], ...$values); /** @var int|float $max */ $max = max($this->maximums[$column], ...$values); $scale = ($this->max - $this->min) / (($max - $min) ?: EPSILON); $this->minimums[$column] = $min; $this->maximums[$column] = $max; } } /** * Transform the dataset in place. * * @param list<list<mixed>> $samples * @throws RuntimeException */ public function transform(array &$samples) : void { if (!isset($this->minimums, $this->scales)) { throw new RuntimeException('Transformer has not been fitted.'); } foreach ($samples as &$sample) { foreach ($this->scales as $column => $scale) { $value = &$sample[$column]; $min = $this->minimums[$column]; $value *= $scale; $value += $this->min - $min * $scale; } } } /** * Perform the reverse transformation to the samples. * * @param list<list<mixed>> $samples * @throws RuntimeException */ public function reverseTransform(array &$samples) : void { if (!isset($this->minimums, $this->scales)) { throw new RuntimeException('Transformer has not been fitted.'); } foreach ($samples as &$sample) { foreach ($this->scales as $column => $scale) { $value = &$sample[$column]; $min = $this->minimums[$column]; $value -= $this->min - $min * $scale; $value /= $scale; } } } /** * Return the string representation of the object. * * @internal * * @return string */ public function __toString() : string { return "Min Max Normalizer (min: {$this->min}, max: {$this->max})"; } }
Save File
Cancel