UNPKG

scalar-autograd

Version:

Scalar-based reverse-mode automatic differentiation in TypeScript.

432 lines (431 loc) 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Value = exports.Losses = exports.AdamW = exports.Adam = exports.SGD = exports.Optimizer = exports.V = void 0; var V_1 = require("./V"); Object.defineProperty(exports, "V", { enumerable: true, get: function () { return V_1.V; } }); var Optimizers_1 = require("./Optimizers"); Object.defineProperty(exports, "Optimizer", { enumerable: true, get: function () { return Optimizers_1.Optimizer; } }); Object.defineProperty(exports, "SGD", { enumerable: true, get: function () { return Optimizers_1.SGD; } }); Object.defineProperty(exports, "Adam", { enumerable: true, get: function () { return Optimizers_1.Adam; } }); Object.defineProperty(exports, "AdamW", { enumerable: true, get: function () { return Optimizers_1.AdamW; } }); var Losses_1 = require("./Losses"); Object.defineProperty(exports, "Losses", { enumerable: true, get: function () { return Losses_1.Losses; } }); const EPS = 1e-12; const ValueTrig_1 = require("./ValueTrig"); const ValueActivation_1 = require("./ValueActivation"); const ValueArithmetic_1 = require("./ValueArithmetic"); const ValueComparison_1 = require("./ValueComparison"); class Value { static no_grad_mode = false; data; grad = 0; requiresGrad; backwardFn = () => { }; prev = []; label; constructor(data, label = "", requiresGrad = false) { if (typeof data !== 'number' || Number.isNaN(data) || !Number.isFinite(data)) { throw new Error(`Invalid number passed to Value: ${data}`); } this.data = data; this.label = label; this.requiresGrad = requiresGrad; } static ensureValue(x) { return typeof x === 'number' ? new Value(x) : x; } /** * Returns sin(this). * @returns New Value with sin. */ sin() { return ValueTrig_1.ValueTrig.sin(this); } /** * Returns cos(this). * @returns New Value with cos. */ cos() { return ValueTrig_1.ValueTrig.cos(this); } /** * Returns tan(this). * @returns New Value with tan. */ tan() { return ValueTrig_1.ValueTrig.tan(this); } /** * Returns asin(this). * @returns New Value with asin. */ asin() { return ValueTrig_1.ValueTrig.asin(this); } /** * Returns acos(this). * @returns New Value with acos. */ acos() { return ValueTrig_1.ValueTrig.acos(this); } /** * Returns atan(this). * @returns New Value with atan. */ atan() { return ValueTrig_1.ValueTrig.atan(this); } /** * Returns relu(this). * @returns New Value with relu. */ relu() { return ValueActivation_1.ValueActivation.relu(this); } /** * Returns abs(this). * @returns New Value with abs. */ abs() { return ValueArithmetic_1.ValueArithmetic.abs(this); } /** * Returns exp(this). * @returns New Value with exp. */ exp() { return ValueArithmetic_1.ValueArithmetic.exp(this); } /** * Returns log(this). * @returns New Value with log. */ log() { return ValueArithmetic_1.ValueArithmetic.log(this, EPS); } /** * Returns min(this, other). * @param other Value to compare * @returns New Value with min. */ min(other) { return ValueArithmetic_1.ValueArithmetic.min(this, other); } /** * Returns max(this, other). * @param other Value to compare * @returns New Value with max. */ max(other) { return ValueArithmetic_1.ValueArithmetic.max(this, other); } /** * Adds this and other. * @param other Value or number to add * @returns New Value with sum. */ add(other) { return ValueArithmetic_1.ValueArithmetic.add(this, Value.ensureValue(other)); } /** * Multiplies this and other. * @param other Value or number to multiply * @returns New Value with product. */ mul(other) { return ValueArithmetic_1.ValueArithmetic.mul(this, Value.ensureValue(other)); } /** * Subtracts other from this. * @param other Value or number to subtract * @returns New Value with difference. */ sub(other) { return ValueArithmetic_1.ValueArithmetic.sub(this, Value.ensureValue(other)); } /** * Divides this by other. * @param other Value or number divisor * @returns New Value with quotient. */ div(other) { return ValueArithmetic_1.ValueArithmetic.div(this, Value.ensureValue(other), EPS); } /** * Raises this to the power exp. * @param exp Exponent * @returns New Value with pow(this, exp) */ pow(exp) { return ValueArithmetic_1.ValueArithmetic.pow(this, exp); } /** * Raises this to a dynamic Value (other). * @param other Exponent Value or number * @returns New Value with pow(this, other) */ powValue(other) { return ValueArithmetic_1.ValueArithmetic.powValue(this, Value.ensureValue(other), EPS); } /** * Returns this modulo other. * @param other Divisor Value * @returns New Value with modulo. */ mod(other) { return ValueArithmetic_1.ValueArithmetic.mod(this, other); } /** * Returns Value indicating if this equals other. * @param other Value to compare * @returns New Value (1 if equal, else 0) */ eq(other) { return ValueComparison_1.ValueComparison.eq(this, other); } /** * Returns Value indicating if this not equals other. * @param other Value to compare * @returns New Value (1 if not equal, else 0) */ neq(other) { return ValueComparison_1.ValueComparison.neq(this, other); } /** * Returns Value indicating if this greater than other. * @param other Value to compare * @returns New Value (1 if true, else 0) */ gt(other) { return ValueComparison_1.ValueComparison.gt(this, other); } /** * Returns Value indicating if this less than other. * @param other Value to compare * @returns New Value (1 if true, else 0) */ lt(other) { return ValueComparison_1.ValueComparison.lt(this, other); } /** * Returns Value indicating if this greater than or equal to other. * @param other Value to compare * @returns New Value (1 if true, else 0) */ gte(other) { return ValueComparison_1.ValueComparison.gte(this, other); } /** * Returns Value indicating if this less than or equal to other. * @param other Value to compare * @returns New Value (1 if true, else 0) */ lte(other) { return ValueComparison_1.ValueComparison.lte(this, other); } /** * Returns softplus(this). * @returns New Value with softplus. */ softplus() { return ValueActivation_1.ValueActivation.softplus(this); } /** * Returns the floor of this Value. * @returns New Value with floor(data). */ floor() { return ValueArithmetic_1.ValueArithmetic.floor(this); } /** * Returns the ceiling of this Value. * @returns New Value with ceil(data). */ ceil() { return ValueArithmetic_1.ValueArithmetic.ceil(this); } /** * Returns the rounded value of this Value. * @returns New Value with rounded data. */ round() { return ValueArithmetic_1.ValueArithmetic.round(this); } /** * Returns the square of this Value. * @returns New Value with squared data. */ square() { return ValueArithmetic_1.ValueArithmetic.square(this); } /** * Returns the cube of this Value. * @returns New Value with cubed data. */ cube() { return ValueArithmetic_1.ValueArithmetic.cube(this); } /** * Returns the reciprocal (1/x) of this Value. * @returns New Value with reciprocal. */ reciprocal() { return ValueArithmetic_1.ValueArithmetic.reciprocal(this, EPS); } /** * Clamps this between min and max. * @param min Minimum value * @param max Maximum value * @returns New clamped Value */ clamp(min, max) { return ValueArithmetic_1.ValueArithmetic.clamp(this, min, max); } /** * Returns the negation (-this) Value. * @returns New Value which is the negation. */ neg() { return ValueArithmetic_1.ValueArithmetic.neg(this); } /** * Returns sign(this). * @returns New Value with sign. */ sign() { return ValueArithmetic_1.ValueArithmetic.sign(this); } /** * Returns the sum of the given Values. * @param vals Array of Value objects * @returns New Value holding their sum. */ static sum(vals) { return ValueArithmetic_1.ValueArithmetic.sum(vals); } /** * Returns the mean of the given Values. * @param vals Array of Value objects * @returns New Value holding their mean. */ static mean(vals) { return ValueArithmetic_1.ValueArithmetic.mean(vals); } /** * Returns tanh(this). * @returns New Value with tanh. */ tanh() { return ValueActivation_1.ValueActivation.tanh(this); } /** * Returns sigmoid(this). * @returns New Value with sigmoid. */ sigmoid() { return ValueActivation_1.ValueActivation.sigmoid(this); } /** * Performs a reverse-mode autodiff backward pass from this Value. * @param zeroGrad If true, zeroes all grads in the graph before backward */ backward(zeroGrad = false) { // Only allow backward on scalars (not arrays), i.e. single value outputs // (output shape check is redundant for this codebase, but keep to scalar-by-convention) if (zeroGrad) Value.zeroGradTree(this); const topo = []; const visited = new Set(); const buildTopo = (v) => { if (!visited.has(v)) { visited.add(v); for (const child of v.prev) { buildTopo(child); } topo.push(v); } }; buildTopo(this); this.grad = 1; for (let i = topo.length - 1; i >= 0; i--) { if (topo[i].requiresGrad) { topo[i].backwardFn(); } } } /** * Sets all grad fields in the computation tree (from root) to 0. * @param root Value to zero tree from */ static zeroGradTree(root) { const visited = new Set(); const visit = (v) => { if (!visited.has(v)) { visited.add(v); v.grad = 0; for (const child of v.prev) visit(child); } }; visit(root); } /** * Sets all grad fields in all supplied trees to 0. * @param vals Values whose trees to zero */ static zeroGradAll(vals) { const visited = new Set(); for (const v of vals) { const visit = (u) => { if (!visited.has(u)) { visited.add(u); u.grad = 0; for (const child of u.prev) visit(child); } }; visit(v); } } /** * Internal helper to construct a Value with correct backward fn and grads. * @param data Output value data * @param left Left operand Value * @param right Right operand Value or null * @param backwardFnBuilder Function to create backward closure * @param label Node label for debugging * @returns New Value node */ static make(data, left, right, backwardFnBuilder, label) { const requiresGrad = !Value.no_grad_mode && [left, right].filter(Boolean).some(v => v.requiresGrad); const out = new Value(data, label, requiresGrad); out.prev = Value.no_grad_mode ? [] : [left, right].filter(Boolean); if (requiresGrad) { out.backwardFn = backwardFnBuilder(out); } return out; } /** * Returns string representation for debugging. * @returns String summary of Value */ toString() { return `Value(data=${this.data.toFixed(4)}, grad=${this.grad.toFixed(4)}, label=${this.label})`; } /** * Temporarily disables gradient tracking within the callback scope, like torch.no_grad(). * Restores the previous state after running fn. */ static withNoGrad(fn) { const prev = Value.no_grad_mode; Value.no_grad_mode = true; try { return fn(); } finally { Value.no_grad_mode = prev; } } } exports.Value = Value;