*/ protected array $cache = [ // ]; /** * @param float $rate * @param float $momentumDecay * @param float $normDecay * @throws InvalidArgumentException */ public function __construct(float $rate = 0.001, float $momentumDecay = 0.1, float $normDecay = 0.001) { if ($rate <= 0.0) { throw new InvalidArgumentException('Learning rate must be' . " greater than 0, $rate given."); } if ($momentumDecay <= 0.0 or $momentumDecay >= 1.0) { throw new InvalidArgumentException('Momentum decay must be' . " between 0 and 1, $momentumDecay given."); } if ($normDecay <= 0.0 or $normDecay >= 1.0) { throw new InvalidArgumentException('Norm decay must be' . " between 0 and 1, $normDecay given."); } $this->rate = $rate; $this->momentumDecay = $momentumDecay; $this->normDecay = $normDecay; } /** * Warm the parameter cache. * * @internal * * @param Parameter $param * @throws RuntimeException */ public function warm(Parameter $param) : void { $class = get_class($param->param()); if ($class === false) { throw new RuntimeException('Could not locate parameter class.'); } $zeros = $class::zeros(...$param->param()->shape()); $this->cache[$param->id()] = [clone $zeros, $zeros]; } /** * Calculate a gradient descent step for a given parameter. * * @internal * * @param Parameter $param * @param Tensor $gradient * @return Tensor */ public function step(Parameter $param, Tensor $gradient) : Tensor { [$velocity, $norm] = $this->cache[$param->id()]; $vHat = $gradient->subtract($velocity) ->multiply($this->momentumDecay); $velocity = $velocity->add($vHat); $nHat = $gradient->square()->subtract($norm) ->multiply($this->normDecay); $norm = $norm->add($nHat); $this->cache[$param->id()] = [$velocity, $norm]; $norm = $norm->sqrt()->clipLower(EPSILON); return $velocity->multiply($this->rate)->divide($norm); } /** * Return the string representation of the object. * * @internal * * @return string */ public function __toString() : string { return "Adam (rate: {$this->rate}, momentum decay: {$this->momentumDecay}," . " norm decay: {$this->normDecay})"; } }