Visualizing Jacobians/Gradients(?) - How's this?

I’ve been studying calculus and in the online course I’m following, the instructor says (in relation to a plot of a particular function):

the Jacobian is simply a vector that we can calculate for each location on this plot which points in the direction of the steepest uphill slope. Furthermore, the steeper the slope, the greater the magnitude of the Jacobian at that point.

I decided to take a crack at this in Julia and I’m posting here to share as well as to solicit feedback (i.e., is it correct? does it do a good job of illustrating the concept?):

The code to produce this is here:

using ForwardDiff
using Plots
using Plots.PlotMeasures
pyplot(size=(1200,600))

f(x,y) = -5*x*y*exp(-x^2-y^2)
x = -1:.02:1
y = -1:.02:1
z = [f(i,j) for i in x, j in y]
p = plot(
    x,
    y,
    z,
    linetype=:surface,
    color=:blues,
    legend=false,
    xlabel="x",
    ylabel="y",
    zlabel="z",
    camera=(-20,30),
    title="f(x,y) = -5*x*y*exp(-x^2-y^2)"
)
p1 = contour(
    x,
    y,
    z,
    color=:blues,
    legend=false,
    xlabel="x",
    ylabel="y",
    title="Countour With Jacobians"
)
# Get all of the xy coordinates in the space
xy = [(i,j) for i in x for j in y]
# This is the same function as above, just modified so that it will work with ForwardDiff
g(x,y) = [-5*x*y*exp(-x^2-y^2)]
# Jacobians are scaled by 0.05 so that the vector arrows aren't too long when plotted
J = .05 .* [ForwardDiff.jacobian(x -> g(x[1], x[2]), [i[1],i[2]]) for i in xy]
# Every 7th x (if we plot a jacobian at every single point, the graph gets too messy)
xs = [xy[i][1] for i in 1:7:length(xy)]
# Every 7th y 
ys = [xy[i][2] for i in 1:7:length(xy)]
# We need u,v for the quiver plot
u = [J[i][1] for i in 1:7:length(J)]
v = [J[i][2] for i in 1:7:length(J)]
quiver!(xs,ys,quiver=(u,v),color=:lightblue,linewidth=0.5)
plot(p,p1,layout=2,left_margin=5mm)
4 Likes

Nice plots!

I’m used to a Jacobian being a matrix of first derivatives of a vector, i.e., while the first derivative of a scalar function is denoted the gradient.

In your case, I’d say that you are plotting the gradient, and not the Jacobian.

The Hessian is the matrix of second order derivatives of a scalar, which, I guess, would be the Jacobian of the gradient.

But maybe the vocabulary varies in different fields.

2 Likes

That would explain why I’ve been confused about the difference between the gradient and the Jacobian :smiley: The quote above is a direct quote and the instructor indeed describes the Jacobian as a vector in this case, but I’ve been scratching my head trying to figure out how/if it’s different from the gradient.

Hopefully this will all become clearer as I progress through the course.

I swapped out ForwardDiff.jacobian with ForwardDiff.gradient to see if the result is the same, but it throws an error. I’ll have to figure out how to compute the gradient and then compare to see if they are indeed the same in this case (I assume they are).

I figured this out in Makie.jl and the result is quite stunning (all I had to do was add the arrows, they already had an example of how to plot a surface/wireframe/contour):

using ForwardDiff
using Makie
using StatsMakie

f(x) = -5*x[1]*x[2]*exp(-x[1]^2-x[2]^2)
x = -1:0.1:1.0
y = -1:0.1:1.0
z = [f([i,j]) for i in x, j in y]

# All the x,y coordinates
xy = [(i,j) for i in x for j in y]
# Function to return the gradient at a given point
g = x -> ForwardDiff.gradient(f, x)
# Collect all the gradients and scale down so that arrows aren't too big in the plot
G = 0.05 .* [g([i[1],i[2]]) for i in xy]
# All the x coordinates
xs = [xy[i][1] for i in 1:length(xy)]
# All the y coordinates
ys = [xy[i][2] for i in 1:length(xy)]
# We need u,v for the arrow plot
u = [G[i][1] for i in 1:length(G)]
v = [G[i][2] for i in 1:length(G)]

# Plot in Makie
scene = surface(x,y,z)
xm, ym, zm = minimum(scene_limits(scene))
contour!(scene, x, y, z, levels = 15, linewidth = 2, transformation = (:xy, zm))
arrows!(xs,ys,u,v, arrowsize=0.025, linewidth=0.5, transformation = (:xy, zm))
wireframe!(scene, x, y, z, overdraw = true, transparency = true, color = (:black, 0.1))
center!(scene) 

5 Likes