• 0. Linear operator
  • 1. Gradient of a function
  • 2. Jacobian of a function
  • 3. Key matrix derivatives
  • 4. Function is a DAG
  • 5. Autodiff on a DAG
  • 6. VJP and JVP
  • 7. Hessian of a function

Key matrix derivatives

  • 0. f(A)=σi(A)f(A) = \sigma_i(A)f(A)=σi​(A), the i-th singular value of AAA
  • 1. Jacobian of elementwise functions
  • 2. Softmax function
  • 3. Row-wise vector functions

0. f(A)=σi(A)f(A) = \sigma_i(A)f(A)=σi​(A), the i-th singular value of AAA

Assume all singular values are unique. The trick is to find a closed form equation for σi(A)\sigma_i(A)σi​(A) in terms of AAA. Calculating the gradient from that point is the easy part. Given the SVD, A=UDVTA = UDV^TA=UDVT, with ui,viu_i, v_iui​,vi​ as i-th columns of U,VU, VU,V respectively, note that:

σi(A)=uiTAvi=uiTUDVTvi=uiTU⏟eiTDVTvi⏟ei=eiTDei=σi(A)\begin{aligned} \sigma_i(A) &= u_i^TAv_i \\ &= u_i^TUDV^Tv_i \\ &= \underbrace{\textcolor{#e11d48}{u_i^TU}}_{\textcolor{#e11d48}{e_i^T}} D \underbrace{\textcolor{#e11d48}{V^Tv_i}}_{\textcolor{#e11d48}{e_i}} \\ &= e_i^T D e_i \\ &= \sigma_i(A) \end{aligned}σi​(A)​=uiT​Avi​=uiT​UDVTvi​=eiT​uiT​U​​Dei​VTvi​​​=eiT​Dei​=σi​(A)​

This gives us:

df=uiT dA vi=tr(uiT dA vi) (trace of a scalar)=tr(viuiT dA) (cyclic property of trace)=⟨uiviT⏟∇f,dA⟩\begin{aligned} df &= u_i^T\,dA\,v_i \\ &= \text{tr}(u_i^T\,dA\,v_i) \ \textcolor{#71717a}{\text{(trace of a scalar)}} \\ &= \text{tr}(v_iu_i^T\,dA) \ \textcolor{#71717a}{\text{(cyclic property of trace)}} \\ &= \langle \underbrace{\textcolor{#b45309}{u_iv_i^T}}_{\textcolor{#b45309}{\nabla f}}, dA \rangle \end{aligned}df​=uiT​dAvi​=tr(uiT​dAvi​) (trace of a scalar)=tr(vi​uiT​dA) (cyclic property of trace)=⟨∇fui​viT​​​,dA⟩​

Thus

the gradient of σi(A)\sigma_i(A)σi​(A) is given by ∇f=uiviT\nabla f = u_iv_i^T∇f=ui​viT​.

Verification using Python

import torch
x = torch.randn(1000,1000).double()
dx = torch.randn_like(x).mul(1e-7)

u1,d1,v1 = torch.linalg.svd(x)
u2,d2,v2 = torch.linalg.svd(x+dx)

index = 0
df_true = d2[index] - d1[index]
df_pred = ((u1[:,index].view(-1,1) @ v1.t()[:,index].view(1,-1)) * dx).sum()

torch.allclose(df_true, df_pred)

'''
Outputs:
True
'''

1. Jacobian of elementwise functions

Jacobian of an elementwise function f:Rn→Rnf: R^n \rightarrow R^nf:Rn→Rn is a diagonal matrix of shape n×nn \times nn×n. Eg consider the function σ(x)=11+e−x\sigma(x) = \frac{1}{1+e^{-x}}σ(x)=1+e−x1​. Its derivative is σ′(x)=σ(x)(1−σ(x))\sigma'(x) = \sigma(x)(1 - \sigma(x))σ′(x)=σ(x)(1−σ(x)). Applying it to each element of a vector we get:

f([x1x2x3])=[σ(x1)σ(x2)σ(x3)]∇f=[σ′(x1)000σ′(x2)000σ′(x3)]=[σ(x1)(1−σ(x1))000σ(x2)(1−σ(x2))000σ(x3)(1−σ(x3))]\begin{aligned} f\left(\begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix}\right) &= \begin{bmatrix} \textcolor{#1d4ed8}{\sigma(x_1)} \\ \textcolor{#be185d}{\sigma(x_2)} \\ \textcolor{#047857}{\sigma(x_3)} \end{bmatrix} \\[6pt] \\ \nabla f &= \begin{bmatrix} \textcolor{#1d4ed8}{\sigma'(x_1)} & 0 & 0 \\ 0 & \textcolor{#be185d}{\sigma'(x_2)} & 0 \\ 0 & 0 & \textcolor{#047857}{\sigma'(x_3)} \end{bmatrix} \\[6pt] \\ &= \begin{bmatrix} \textcolor{#1d4ed8}{\sigma(x_1)(1 - \sigma(x_1))} & 0 & 0 \\ 0 & \textcolor{#be185d}{\sigma(x_2)(1 - \sigma(x_2))} & 0 \\ 0 & 0 & \textcolor{#047857}{\sigma(x_3)(1 - \sigma(x_3))} \end{bmatrix} \end{aligned}f​​x1​x2​x3​​​​∇f​=​σ(x1​)σ(x2​)σ(x3​)​​=​σ′(x1​)00​0σ′(x2​)0​00σ′(x3​)​​=​σ(x1​)(1−σ(x1​))00​0σ(x2​)(1−σ(x2​))0​00σ(x3​)(1−σ(x3​))​​​

Each diagonal entry σ′(x1)\textcolor{#1d4ed8}{\sigma'(x_1)}σ′(x1​), σ′(x2)\textcolor{#be185d}{\sigma'(x_2)}σ′(x2​), σ′(x3)\textcolor{#047857}{\sigma'(x_3)}σ′(x3​) only depends on its own input xix_ixi​, since σ(xi)\sigma(x_i)σ(xi​) does not depend on xjx_jxj​ for j≠ij \neq ij=i; that is exactly why every off-diagonal entry is 000.

Practically it is easier to store the diagonal matrix as a vector.


2. Softmax function

The softmax function (softmax:Rn→Rn\text{softmax}: R^n \rightarrow R^nsoftmax:Rn→Rn) is defined as:

softmax(x)=ex∑i=1nexi:=y\text{softmax}(x) = \frac{e^x}{\sum_{i=1}^n e^{x_i}} := ysoftmax(x)=∑i=1n​exi​ex​:=y

We calculate the partial derivative ∂yi∂xj\frac{\partial y_i}{\partial x_j}∂xj​∂yi​​ separately for the case i=ji = ji=j (an output coordinate with respect to its own input) and i≠ji \neq ji=j (an output coordinate with respect to a different input):

∂ yi∂xj={yi−yiyj=yi(1−yi)i=j0−yiyj=−yiyji≠j\frac{\partial \ y_i}{\partial x_j} = \begin{cases} \textcolor{#1d4ed8}{y_i} - \textcolor{#be185d}{y_iy_j} = \textcolor{#1d4ed8}{y_i(1 - y_i)} & i = j \\ 0 - \textcolor{#be185d}{y_iy_j} = -\textcolor{#be185d}{y_iy_j} & i \neq j \end{cases}∂xj​∂ yi​​={yi​−yi​yj​=yi​(1−yi​)0−yi​yj​=−yi​yj​​i=ji=j​

This expression forms the entry i,ji,ji,j of the Jacobian matrix. Collecting the i=ji = ji=j entries into diag(y)\textcolor{#1d4ed8}{\text{diag}(y)}diag(y) and the −yiyj-y_iy_j−yi​yj​ entries into −yyT-\textcolor{#be185d}{yy^T}−yyT, the Jacobian takes a succinct form:

Jacobian(y)=diag(y)−yyT\textcolor{#b45309}{\text{Jacobian}(y) = \textcolor{#1d4ed8}{\text{diag}(y)} - \textcolor{#be185d}{yy^T}}Jacobian(y)=diag(y)−yyT

3. Row-wise vector functions

This can be seen as an extension of the idea of elementwise functions. If a function f:Rin→Routf: R^{\text{in}} \rightarrow R^{\text{out}}f:Rin→Rout is applied to each row of a matrix of shape (n,in)(n, \text{in})(n,in), the output is a matrix of shape (n,out)(n, \text{out})(n,out). The Jacobian for each row is a matrix of shape (out,in)(\text{out}, \text{in})(out,in) and there are nnn of them.

[—x1T——x2T——x3T—]→ f y1→ f y2→ f y3\begin{bmatrix} \text{—} & \textcolor{#1d4ed8}{x_1^T} & \text{—} \\ \text{—} & \textcolor{#be185d}{x_2^T} & \text{—} \\ \text{—} & \textcolor{#047857}{x_3^T} & \text{—} \end{bmatrix} \quad \begin{aligned} &\xrightarrow{\ f\ } \textcolor{#1d4ed8}{y_1} \\ &\xrightarrow{\ f\ } \textcolor{#be185d}{y_2} \\ &\xrightarrow{\ f\ } \textcolor{#047857}{y_3} \end{aligned}​———​x1T​x2T​x3T​​———​​​ f ​y1​ f ​y2​ f ​y3​​

fff acts on each row of the matrix with shape (n,in)(n, \text{in})(n,in).

Another point of view is that it can be seen as a function acting on the entire matrix. From that point of view, the Jacobian has a shape (n×out,n×in)(n \times \text{out}, n \times \text{in})(n×out,n×in). Writing J1=∂y1∂x1\textcolor{#1d4ed8}{J_1} = \frac{\partial \textcolor{#1d4ed8}{y_1}}{\partial \textcolor{#1d4ed8}{x_1}}J1​=∂x1​∂y1​​, J2=∂y2∂x2\textcolor{#be185d}{J_2} = \frac{\partial \textcolor{#be185d}{y_2}}{\partial \textcolor{#be185d}{x_2}}J2​=∂x2​∂y2​​, J3=∂y3∂x3\textcolor{#047857}{J_3} = \frac{\partial \textcolor{#047857}{y_3}}{\partial \textcolor{#047857}{x_3}}J3​=∂x3​∂y3​​ for the per-row Jacobians, this whole-matrix Jacobian is:

Jacobian=[J1000J2000J3]\text{Jacobian} = \begin{bmatrix} \textcolor{#1d4ed8}{J_1} & 0 & 0 \\ 0 & \textcolor{#be185d}{J_2} & 0 \\ 0 & 0 & \textcolor{#047857}{J_3} \end{bmatrix}Jacobian=​J1​00​0J2​0​00J3​​​

Each JiJ_iJi​ has shape (out,in)(\text{out}, \text{in})(out,in), one per row, and only xix_ixi​ affects yiy_iyi​, so every off-diagonal block is 000: exactly the same block-diagonal reasoning as the elementwise Jacobian in Section 1, just with matrix blocks in place of scalars.

(out, in)

The Jacobian is a block diagonal matrix, with nnn blocks running down the diagonal and zeros everywhere else. Each block is the per-row Jacobian, of shape (out,in)(\text{out}, \text{in})(out,in).

It is clear that storing the entire block diagonal matrix is a lot more expensive than storing the individual Jacobian matrices. This idea is explored later on in the article on VJP and JVP.


← Jacobian of a function · Function is a DAG →