Qu

grad

Builtin index · REPL & diagnostics

grad(loss, wrt=X) or grad(loss, X, Y, ...)

Input: loss, wrt · Output: no output

What it is

Takes loss — a tracked scalar that is the result of a computation built from tracked inputs — plus either a keyword argument wrt=X (one tracked tensor X) or one or more positional tracked tensors X, Y, .... Runs the backprop walk over the tape. The keyword form wrt=X returns a single gradient value with the same shape as X (a number for a scalar X, a matrix for a matrix X); the positional form grad(loss, X, Y, ...) returns a List of such gradients, one per argument, in the same order (positional rather than wrt=[X, Y], because Qu's [...] bracket literal is numeric-matrix syntax and would silently flatten a list of tensors down to plain numbers, losing the node identity grad needs).

Overloads

2 forms. Each carries its own inputs, outputs and worked example — pick the tab for the one you are calling.

CodeInputOutputDescription
result = grad(loss, wrt=X)
loss scalarnot documented yet
wrt scalar (default X)not documented yet
result number, scalar, or matrix
A single gradient value shaped like X — a number for a scalar X, a matrix for a matrix X.

Examples

From the documentation, and run by the build -- the output and the figure below are what the engine actually produced. Simplest first.

x = track(3.0)
y = x * x + 2 * x
dy_dx = grad(y, wrt=x)      # 2*x + 2 = 8

print("dy/dx = {dy_dx}")

from repl-diagnostics

x = track(3.0)
y = track(2.0)
loss = x * x + y * y
dloss_dx = grad(loss, wrt=x)          # 2*x = 6

grads = grad(loss, x, y)              # [2*x, 2*y] = (6, 4)

print("wrt=x form: {dloss_dx}")
print("positional form: {grads}")
output
wrt=x form: 6
positional form: (6, 4)

from repl-diagnostics

target = stop_grad(track(5.0))
x = track(2.0)
loss = (x - target) * (x - target)
dloss_dx = grad(loss, wrt=x)
print("dloss/dx = {dloss_dx}")

from repl-diagnostics

raw = lstm_init(2, 3, seed = 1)
w = {wf=param(raw.wf), uf=param(raw.uf), bf=param(raw.bf),
     wi=param(raw.wi), ui=param(raw.ui), bi=param(raw.bi),
     wo=param(raw.wo), uo=param(raw.uo), bo=param(raw.bo),
     wg=param(raw.wg), ug=param(raw.ug), bg=param(raw.bg)}
Xseq = [0.5, -0.3, 0.8; 0.2, 0.4, -0.1]   # (input_size, seq_len)

hs = lstm_forward(Xseq, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], w)
h_last = hs[length(hs) - 1]
loss = sum(h_last .^ 2)
g = grad(loss, wrt = w.wf)                # through time, on the same tape

print("loss: {round(loss, 4)}")
print("dL/dwf: {round(g, 4)}")
output
loss: 0.0027
dL/dwf: [0.0001, 0; 0.0005, 0; 0.0021, -0.0002]

from statistics-ml

CodeInputOutputDescription
result = grad(loss, X, Y, ...)
loss scalarnot documented yet
X scalarnot documented yet
Y scalarnot documented yet
... scalar, repeatablenot documented yet
result List
A List of gradient values, one per positional argument, in the same order.

Examples

From the documentation, and run by the build -- the output and the figure below are what the engine actually produced. Simplest first.

x = track(3.0)
y = x * x + 2 * x
dy_dx = grad(y, wrt=x)      # 2*x + 2 = 8

print("dy/dx = {dy_dx}")

from repl-diagnostics

x = track(3.0)
y = track(2.0)
loss = x * x + y * y
dloss_dx = grad(loss, wrt=x)          # 2*x = 6

grads = grad(loss, x, y)              # [2*x, 2*y] = (6, 4)

print("wrt=x form: {dloss_dx}")
print("positional form: {grads}")
output
wrt=x form: 6
positional form: (6, 4)

from repl-diagnostics

target = stop_grad(track(5.0))
x = track(2.0)
loss = (x - target) * (x - target)
dloss_dx = grad(loss, wrt=x)
print("dloss/dx = {dloss_dx}")

from repl-diagnostics

raw = lstm_init(2, 3, seed = 1)
w = {wf=param(raw.wf), uf=param(raw.uf), bf=param(raw.bf),
     wi=param(raw.wi), ui=param(raw.ui), bi=param(raw.bi),
     wo=param(raw.wo), uo=param(raw.uo), bo=param(raw.bo),
     wg=param(raw.wg), ug=param(raw.ug), bg=param(raw.bg)}
Xseq = [0.5, -0.3, 0.8; 0.2, 0.4, -0.1]   # (input_size, seq_len)

hs = lstm_forward(Xseq, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], w)
h_last = hs[length(hs) - 1]
loss = sum(h_last .^ 2)
g = grad(loss, wrt = w.wf)                # through time, on the same tape

print("loss: {round(loss, 4)}")
print("dL/dwf: {round(g, 4)}")
output
loss: 0.0027
dL/dwf: [0.0001, 0; 0.0005, 0; 0.0021, -0.0002]

from statistics-ml

How it works

grad(loss, wrt=X) / grad(loss, wrt=[X, ...]) — see Interp::eval_grad's own doc comment for the backprop walk. grad(loss, wrt=X) differentiates against one tensor; grad(loss, X, Y, ...) (multiple *positional* tensors after loss) differentiates against all of them at once, returning a Value::List of gradients in the same order — the positional form rather than wrt=[X, Y] because Qu's [...] bracket literal is numeric-matrix syntax (see block_to_value) and would silently flatten a list of Tensors down to their plain numbers, losing exactly the node identity grad needs.

From the note above this builtin in the interpreter, where the reasoning was written down as it was built.