2
$\begingroup$

I'm working on implementing Newton's method to perform second-order gradient descent in a neural network and having trouble computing the second order derivatives. I understand that in practice, computing the Hessian (and its inverse) is often practically infeasible because it has $w^2$ entries, where $w$ is the number of weights in the network. I'm just trying to learn how these things work though, and so wanted to start by computing the Hessian directly before tackling optimizations like BFGS or congruent gradients.

I haven't been able to find many resources spelling out what the second-order derivatives look like, my guess is this is because of the practical limitations. Going at it from base principles, it seems like the Hessian matrix should always be zero, which is clearly wrong, and I'm hoping someone can explain how the Hessian isn't always zero and if possible what the equations for the entries looks like.

Say we have a standard feedforward neural network consisting of a series of linear combinations and activations functions. Something like $$\overline x \longrightarrow \overline a^{(1)} = W^{(1)}\overline x \longrightarrow \overline h^{(1)} = \phi^{(1)}(\overline h^{(1)}) \longrightarrow \dots \longrightarrow \overline a^{(k)} = W^{(k)} \overline h^{(k-1)} \longrightarrow \overline h^{(k)} = \phi^{(k)}(\overline a^{(k)}) \longrightarrow L(\overline a^{(k)})$$ Where $\overline x$ is the input vector, $\overline a^{(l)}$ is the vector of pre-activation values in the $l$th layer, $\overline h^{(l)}$ is the vector of post-activation values in the $l$th layer, $W^{(l)}$ is the weight matrix of the $l$th layer, $\phi^{(l)}$ is the activation function of the $l$th layer, and $L$ is the loss function.

Zooming in on a single neuron, the $n$th neuron in the $l$th layer, the preactivation value $a_i^{(l)}$ is a linear combination of the outputs $\overline h^{(l -1)}$ of the preceding layer with the $i$th row of the weight matrix. That is, $$a_i^{(l)} = \displaystyle \sum_{j} W_{ij} \overline h^{(l-1)}$$ where $j$ counts to the number of neurons in layer $l-1$. Expanding the sum, $$a_i^{(l)} = W_{i1}h_1^{(l-1)} + W_{i2}h_2^{(l-1)} + \dots + W_{ij}h_j^{(l-1)}$$ If we take the first-order derivative of just this function (ignoring all the other paths we need to consider, for a moment), which is a function of a $\overline h^{(l-1)}$, we get a gradient $\nabla f = \langle W_{i1}, W_{i2}, \dots, W_{ij} \rangle$. How is the second derivative of this function ever going to be something other than 0? It's a function of $\overline h$, and we already differentiated away all the $h$ terms. That is the heart of my confusion, but obviously this is part of a much larger gradient which needs to consider all of the neurons which depend on $\overline h^{(l-1)}$.

At a higher level, the network is a composition of functions, and every other function is linear. Won't the second order derivative of the linear functions always be zero, and by the chain rule this will "zero out" the composite second-order derivative every time we cross a linear combination during backpropagation?

The only equation I've found for the second order derivatives is in the Wikipedia article, https://en.wikipedia.org/wiki/Backpropagation#Second-order_gradient_descent. There they have the following (with variables names changed to match what I've used so far) $$\dfrac{\partial^2 L}{\partial h_{j_1}^{(l)} \partial h_{j_2}^{(l)}} = \displaystyle \sum_{j_1 j_2} W_{i_1j_1} W_{i_2j_2} \dfrac{\partial^2L}{\partial a_{i_1}^{(l+1)} \partial a_{i_2}^{(l+1)}}$$ Can someone explain where this comes from? Also, I'm confused about the summation indices in this equation - it seems like we already chose particular $j_1$ and $j_2$ values in the LHS.

$\endgroup$

1 Answer 1

1
$\begingroup$

Welcome to DS.SE ! This is a good question and gives me the opportunity to brush up on my own understanding in this area.

Addressing Second-Order Derivatives and the Hessian in Deep Neural Networks

Theoretical Background and Hessian Challenges

In neural networks with $w$ trainable parameters (weights and biases, collectively denoted by $\theta$ in what follows), directly computing the Hessian, which requires $w^2$ entries, is infeasible as $w$ grows large. For deep networks, $w$ can reach millions, as it scales with the number of neurons and connections across layers. For instance, a fully connected layer with 1,000 neurons connected to another layer of 1,000 neurons has $1,000 \times 1,000 = 1,000,000$ weights alone.

While backpropagation traditionally relies on first-order gradients, second-order information through the Hessian captures interlayer dependencies, offering a more detailed mapping of the loss landscape. Despite the memory demands, approximating the Hessian in a structured way can enhance convergence by making use of this curvature data.

The second-order derivatives are non-zero in neural networks primarily due to the presence of non-linear activation functions. While the linear transformations alone yield zero curvature (second derivatives), non-linear activations (eg., ReLU, sigmoid) following these transformations introduce non-zero curvature in the loss function. This is because the network’s overall function becomes non-linear, leading to a Hessian matrix with non-zero entries due to the chain rule applied through layers.

Kronecker-Factored BFGS Approach for Practical Approximation

The Kronecker-factored block-diagonal BFGS (K-BFGS) method, developed by Goldfarb et al. (2020), approximates the Hessian $H$ (with respect to all parameters $\theta$) using a block-diagonal structure, with each block $H^{(l)}$ corresponding to a network layer. The Hessian for a layer $l$ is approximated by a block $H^{(l)}$, which is represented as a Kronecker product:

$$ H^{(l)} = A^{(l)} \otimes G^{(l)} $$

where $A^{(l)}$ and $G^{(l)}$ are smaller matrices capturing layer-specific curvature. This decomposition retains essential curvature information while significantly reducing memory demands. By focusing on layer-wise blocks, this approach scales efficiently with network depth, making updates feasible even in high-dimensional settings. This structure uses intra-layer dependencies, allowing it to capture second-order interactions across layers without requiring a full Hessian calculation. This layer-wise approximation is particularly useful for large networks.

Damping for Stability

To address high variability and the potential non-convex nature of the Hessian in deep networks, Goldfarb et al. implement a "double damping" technique. This approach stabilises drastic shifts in Hessian eigenvalues, which is important for maintaining training stability when curvature varies widely. Inspired by Powell’s damping, this strategy ensures the Hessian remains positive definite by adjusting the scaling factor $\rho$ as follows:

$$ \rho = \frac{1}{y^T s + \text{damping}} $$ This adjustment helps stabilise the Hessian’s behaviour by limiting large eigenvalues and thus enhances training robustness without requiring frequent recalculations.

Alternative Second-Order Methods

Several alternative second-order methods, including Gauss-Newton and Hessian-Free, offer viable approaches but with trade-offs, as discussed in Tan and Lim (2019):. Gauss-Newton approximates the Hessian using the squared Jacobian, which stabilises updates but can struggle with non-positive-definite matrices in deep networks. Levenberg-Marquardt extends Gauss-Newton by adding a regularisation parameter to maintain definiteness, though this parameter may require careful tuning for optimal performance.

Hessian-Free optimisation bypasses explicit Hessian calculation by using finite differences and conjugate gradient methods to estimate curvature. While efficient in high dimensions, it can be sensitive to gradient noise, which is common in deep networks. Each method has specific strengths and limitations.

Implementation and Practical Benefits

Goldfarb et al demonstrate that K-BFGS can outperform or match first-order methods, such as SGD and Adam, on datasets like MNIST, proving its utility when memory headroom allows for second-order computations. Compared to KFAC, another second-order method that involves more computationally intensive matrix inversions, K-BFGS uses layer-wise updates, making it more efficient with large mini-batches.

Pseudocode for K-BFGS

Below is a basic idea, based on Python, for implementation of K-BFGS for a feedforward neural network, where each layer-wise update maintains positive definiteness and controls step size through damping.

# Initialise Hessian approximation as an identity matrix for each layer
def initialise_hessian_approx(weights: dict) -> dict:
    """Initialises the Hessian approximation as identity matrices for each layer."""
    return {layer: (np.eye(weights[layer].shape[0]), np.eye(weights[layer].shape[1])) for layer in weights}

# BFGS update rule for inverse Hessian approximation
def update_bfgs(A: np.ndarray, G: np.ndarray, s: np.ndarray, y: np.ndarray, damping: float) -> np.ndarray:
    """
    Applies the BFGS formula with damping to update the Hessian approximation.

    Parameters:
        A (np.ndarray): Kronecker factor matrix for layer weights (2D array).
        G (np.ndarray): Kronecker factor matrix for layer gradient updates (2D array).
        s (np.ndarray): Update direction based on factor A (1D array).
        y (np.ndarray): Update direction based on factor G (1D array).
        damping (float): Damping term to stabilise updates.

    Returns:
        np.ndarray: Updated Hessian approximation for the layer (2D array).
        
    Note:
        Includes a small constant (1e-8) in the denominator to avoid division by zero.
    """
    # Compute scaling factor to adjust the step size based on vectors s and y
    rho = 1.0 / (y.T @ s + damping + 1e-8)
    I = np.eye(A.shape[0])
    V = I - rho * np.outer(y, s)
    H_new = V @ A @ V.T + rho * np.outer(s, s)
    return H_new

# K-BFGS update function with damping and initialisation
def kbfgs_update(weights: dict, grads: dict, hessian_approx: dict, learning_rate: float, damping: float = 1e-4):
    """Performs a K-BFGS update step on network weights using layer-wise Hessian approximations."""
    for layer in weights:
        A, G = hessian_approx[layer]  # Kronecker factors representing layer-wise Hessian approximation
        
        # Compute update directions based on Kronecker factors
        s = A @ grads[layer]
        y = G @ grads[layer]
        
        # Update the inverse Hessian approximation using the BFGS formula
        hessian_approx[layer] = update_bfgs(A, G, s, y, damping)
        
        # Compute the update direction and apply it to the weights
        update_direction = hessian_approx[layer] @ grads[layer]
        weights[layer] -= learning_rate * update_direction

# Example usage
weights = {'layer1': np.random.rand(3, 3), 'layer2': np.random.rand(4, 4)}
grads = {'layer1': np.random.rand(3, 3), 'layer2': np.random.rand(4, 4)}
hessian_approx = initialise_hessian_approx(weights)
learning_rate = 0.01
damping = 1e-4

# Perform a single update step
kbfgs_update(weights, grads, hessian_approx, learning_rate, damping)

Addressing the additional questions:

The only equation I've found for the second order derivatives is in the Wikipedia article, https://en.wikipedia.org/wiki/Backpropagation#Second-order_gradient_descent. There they have the following (with variables names changed to match what I've used so far) $$\dfrac{\partial^2 L}{\partial h_{j_1}^{(l)} \partial h_{j_2}^{(l)}} = \displaystyle \sum_{j_1 j_2} W_{i_1j_1} W_{i_2j_2} \dfrac{\partial^2L}{\partial a_{i_1}^{(l+1)} \partial a_{i_2}^{(l+1)}}$$

Can someone explain where this comes from? Also, I'm confused about the summation indices in this equation - it seems like we already chose particular $j_1$ and $j_2$ values in the LHS.

The equation from Wikipedia represents the Hessian of the loss function $L$ with respect to the post-activation values $h^{(l)}$ at layer 𝑙 l. It’s derived by applying the chain rule for second derivatives, as the dependencies cascade back through each layer of the network.

The term $\dfrac{\partial^2 L}{\partial h_{j_1}^{(l)} \partial h_{j_2}^{(l)}}$ on the left-hand side represents the Hessian with respect to the post-activation values at layer 𝑙 l, while the sum on the right-hand side expresses how this quantity depends on the pre-activation values $a^{(l+1)}$ in the following layer. Each $W_{i_1j_1}$ and $W_{i_2j_2}$ term represents the weight connections from neurons in layer $l$ to neurons in layer $l+1$, capturing how variations in $h^{(l)}$ propagate forward.

The summation over $j_1$ and $j_2$ accounts for all neurons in the previous layer, accumulating their influence on the second derivative of the loss. Although we specify particular $j_1$ and $j_2$ values on the left-hand side, the sum over these indices captures all relevant connections between layers.

Summing up

Quasi-Newton methods like K-BFGS offer a balanced alternative to full second-order approaches by using Kronecker-factored approximations to capture curvature without excessive memory demands. Although somewhat memory-intensive, these methods effectively handle layer-wise curvature and enable faster convergence in deep networks. For scenarios with some memory headroom, K-BFGS is a valuable middle ground between computational efficiency and the enhanced convergence benefits of second-order methods.


$\dagger$ In this context, the Hessian is a square matrix of second-order partial derivatives of the neural network's loss function with respect to all pairs of trainable parameters (weights and biases). Each entry in the Hessian matrix, $H_{ij}$, is defined as:

$$ H_{ij} = \frac{\partial^2 L}{\partial \theta_i \partial \theta_j} $$

where $L$ is the loss function, and $\theta_i$ and $\theta_j$ are individual parameters. This matrix captures how the gradient of one parameter changes with respect to another, effectively mapping the curvature of the loss landscape. Such curvature information helps optimise the training process by adapting update steps according to the local geometry of the loss function.

Refererences:

Goldfarb, D., Ren, Y., & Bahamou, A. (2020). Practical quasi-newton methods for training deep neural networks. Advances in Neural Information Processing Systems, 33, 2386-2396.
https://proceedings.neurips.cc/paper_files/paper/2020/file/192fc044e74dffea144f9ac5dc9f3395-Paper.pdf

Powell, M. J. D. (1978). Algorithms for nonlinear constraints that use Lagrange functions. Mathematical Programming, 14(1), 224-248.

Tan, H. H., & Lim, K. H. (2019, April). Review of second-order optimization techniques in artificial neural networks backpropagation. In IOP conference series: materials science and engineering (Vol. 495, No. 1, p. 012003). IOP Publishing: https://iopscience.iop.org/article/10.1088/1757-899X/495/1/012003/pdf

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.