   # An overview of gradient descent optimization algorithms

ModelingPredictive Analyticsposted by Sebastian Ruder April 27, 2017

Note: If you are looking for a review paper, this blog post is also available as an article on arXiv. Table of contents: Gradient... Note: If you are looking for a review paper, this blog post is also available as an article on arXiv.

Gradient descent is one of the most popular algorithms to perform optimization and by far the most common way to optimize neural networks. At the same time, every state-of-the-art Deep Learning library contains implementations of various algorithms to optimize gradient descent (e.g. lasagne’s, caffe’s, and keras’ documentation). These algorithms, however, are often used as black-box optimizers, as practical explanations of their strengths and weaknesses are hard to come by.

This blog post aims at providing you with intuitions towards the behaviour of different algorithms for optimizing gradient descent that will help you put them to use. We are first going to look at the different variants of gradient descent. We will then briefly summarize challenges during training. Subsequently, we will introduce the most common optimization algorithms by showing their motivation to resolve these challenges and how this leads to the derivation of their update rules. We will also take a short look at algorithms and architectures to optimize gradient descent in a parallel and distributed setting. Finally, we will consider additional strategies that are helpful for optimizing gradient descent.

Gradient descent is a way to minimize an objective function (J(θ)) parameterized by a model’s parameters (θ∈Rd) by updating the parameters in the opposite direction of the gradient of the objective function (∇_θJ(θ)) w.r.t. to the parameters. The learning rate (η) determines the size of the steps we take to reach a (local) minimum. In other words, we follow the direction of the slope of the surface created by the objective function downhill until we reach a valley. If you are unfamiliar with gradient descent, you can find a good introduction on optimizing neural networks here.

There are three variants of gradient descent, which differ in how much data we use to compute the gradient of the objective function. Depending on the amount of data, we make a trade-off between the accuracy of the parameter update and the time it takes to perform an update.

Vanilla gradient descent, aka batch gradient descent, computes the gradient of the cost function w.r.t. to the parameters (θ) for the entire training dataset:

(θ=θ−η⋅∇_θJ(θ))

As we need to calculate the gradients for the whole dataset to perform just one update, batch gradient descent can be very slow and is intractable for datasets that don’t fit in memory. Batch gradient descent also doesn’t allow us to update our model online, i.e. with new examples on-the-fly.

In code, batch gradient descent looks something like this:

``````for i in range(nb_epochs):
params = params - learning_rate * params_grad``````

For a pre-defined number of epochs, we first compute the gradient vector `params_grad` of the loss function for the whole dataset w.r.t. our parameter vector `params`. Note that state-of-the-art deep learning libraries provide automatic differentiation that efficiently computes the gradient w.r.t. some parameters. If you derive the gradients yourself, then gradient checking is a good idea. (See here for some great tips on how to check gradients properly.)

We then update our parameters in the direction of the gradients with the learning rate determining how big of an update we perform. Batch gradient descent is guaranteed to converge to the global minimum for convex error surfaces and to a local minimum for non-convex surfaces.

Stochastic gradient descent (SGD) in contrast performs a parameter update for each training example (x^{(i)}) and label (y^{(i)}):

(θ=θ−η⋅∇_θJ(θ;x^{(i)};y^{(i)}))

Batch gradient descent performs redundant computations for large datasets, as it recomputes gradients for similar examples before each parameter update. SGD does away with this redundancy by performing one update at a time. It is therefore usually much faster and can also be used to learn online.
SGD performs frequent updates with a high variance that cause the objective function to fluctuate heavily as in Image 1.

While batch gradient descent converges to the minimum of the basin the parameters are placed in, SGD’s fluctuation, on the one hand, enables it to jump to new and potentially better local minima. On the other hand, this ultimately complicates convergence to the exact minimum, as SGD will keep overshooting. However, it has been shown that when we slowly decrease the learning rate, SGD shows the same convergence behaviour as batch gradient descent, almost certainly converging to a local or the global minimum for non-convex and convex optimization respectively.
Its code fragment simply adds a loop over the training examples and evaluates the gradient w.r.t. each example. Note that we shuffle the training data at every epoch as explained in this section.

``````for i in range(nb_epochs):
np.random.shuffle(data)
for example in data:
params = params - learning_rate * params_grad``````

Mini-batch gradient descent finally takes the best of both worlds and performs an update for every mini-batch of (n) training examples:

(θ=θ−η⋅∇_θJ(θ;x^{(i:i+n)};y^{(i:i+n)}))

This way, it a) reduces the variance of the parameter updates, which can lead to more stable convergence; and b) can make use of highly optimized matrix optimizations common to state-of-the-art deep learning libraries that make computing the gradient w.r.t. a mini-batch very efficient. Common mini-batch sizes range between 50 and 256, but can vary for different applications. Mini-batch gradient descent is typically the algorithm of choice when training a neural network and the term SGD usually is employed also when mini-batches are used. Note: In modifications of SGD in the rest of this post, we leave out the parameters (x^{(i:i+n)};y^{(i:i+n)}) for simplicity.

In code, instead of iterating over examples, we now iterate over mini-batches of size 50:

``````for i in range(nb_epochs):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
params = params - learning_rate * params_grad``````

# Challenges

Vanilla mini-batch gradient descent, however, does not guarantee good convergence, but offers a few challenges that need to be addressed:

• Choosing a proper learning rate can be difficult. A learning rate that is too small leads to painfully slow convergence, while a learning rate that is too large can hinder convergence and cause the loss function to fluctuate around the minimum or even to diverge.
• Learning rate schedules  try to adjust the learning rate during training by e.g. annealing, i.e. reducing the learning rate according to a pre-defined schedule or when the change in objective between epochs falls below a threshold. These schedules and thresholds, however, have to be defined in advance and are thus unable to adapt to a dataset’s characteristics .
• Additionally, the same learning rate applies to all parameter updates. If our data is sparse and our features have very different frequencies, we might not want to update all of them to the same extent, but perform a larger update for rarely occurring features.
• Another key challenge of minimizing highly non-convex error functions common for neural networks is avoiding getting trapped in their numerous suboptimal local minima. Dauphin et al.  argue that the difficulty arises in fact not from local minima but from saddle points, i.e. points where one dimension slopes up and another slopes down. These saddle points are usually surrounded by a plateau of the same error, which makes it notoriously hard for SGD to escape, as the gradient is close to zero in all dimensions.

In the following, we will outline some algorithms that are widely used by the deep learning community to deal with the aforementioned challenges. We will not discuss algorithms that are infeasible to compute in practice for high-dimensional data sets, e.g. second-order methods such as Newton’s method.

## Momentum

SGD has trouble navigating ravines, i.e. areas where the surface curves much more steeply in one dimension than in another , which are common around local optima. In these scenarios, SGD oscillates across the slopes of the ravine while only making hesitant progress along the bottom towards the local optimum as in Image 2.

Momentum  is a method that helps accelerate SGD in the relevant direction and dampens oscillations as can be seen in Image 3. It does this by adding a fraction (γ) of the update vector of the past time step to the current update vector:

(vt=γv_{t−1}+η∇_θJ(θ))

(θ=θ−v_t)

Note: Some implementations exchange the signs in the equations. The momentum term (gamma) is usually set to 0.9 or a similar value.

Essentially, when using momentum, we push a ball down a hill. The ball accumulates momentum as it rolls downhill, becoming faster and faster on the way (until it reaches its terminal velocity if there is air resistance, i.e. (gamma < 1)). The same thing happens to our parameter updates: The momentum term increases for dimensions whose gradients point in the same directions and reduces updates for dimensions whose gradients change directions. As a result, we gain faster convergence and reduced oscillation.

However, a ball that rolls down a hill, blindly following the slope, is highly unsatisfactory. We’d like to have a smarter ball, a ball that has a notion of where it is going so that it knows to slow down before the hill slopes up again.

Nesterov accelerated gradient (NAG)  is a way to give our momentum term this kind of prescience. We know that we will use our momentum term (gamma v_{t-1}) to move the parameters (theta). Computing ( theta – gamma v_{t-1} ) thus gives us an approximation of the next position of the parameters (the gradient is missing for the full update), a rough idea where our parameters are going to be. We can now effectively look ahead by calculating the gradient not w.r.t. to our current parameters (theta) but w.r.t. the approximate future position of our parameters:

(v_t = gamma v_{t-1} + eta nabla_theta J( theta – gamma v_{t-1} )).

(theta = theta – v_t).

Again, we set the momentum term (gamma) to a value of around 0.9. While Momentum first computes the current gradient (small blue vector in Image 4) and then takes a big jump in the direction of the updated accumulated gradient (big blue vector), NAG first makes a big jump in the direction of the previous accumulated gradient (brown vector), measures the gradient and then makes a correction (red vector), which results in the complete NAG update (green vector). This anticipatory update prevents us from going too fast and results in increased responsiveness, which has significantly increased the performance of RNNs on a number of tasks .

Refer to here for another explanation about the intuitions behind NAG, while Ilya Sutskever gives a more detailed overview in his PhD thesis .

Now that we are able to adapt our updates to the slope of our error function and speed up SGD in turn, we would also like to adapt our updates to each individual parameter to perform larger or smaller updates depending on their importance.

Previously, we performed an update for all parameters (theta) at once as every parameter (theta_i) used the same learning rate (eta). As Adagrad uses a different learning rate for every parameter (theta_i) at every time step (t), we first show Adagrad’s per-parameter update, which we then vectorize. For brevity, we set (g_{t, i}) to be the gradient of the objective function w.r.t. to the parameter (theta_i) at time step (t):

(g_{t, i} = nabla_theta J( theta_i )).

The SGD update for every parameter (theta_i) at each time step (t) then becomes:

(theta_{t+1, i} = theta_{t, i} – eta cdot g_{t, i}).

In its update rule, Adagrad modifies the general learning rate (eta) at each time step (t) for every parameter (theta_i) based on the past gradients that have been computed for (theta_i):

(theta_{t+1, i} = theta_{t, i} – dfrac{eta}{sqrt{G_{t, ii} + epsilon}} cdot g_{t, i}).

(G_{t} in mathbb{R}^{d times d} ) here is a diagonal matrix where each diagonal element (i, i) is the sum of the squares of the gradients w.r.t. (theta_i) up to time step (t) 24, while (epsilon) is a smoothing term that avoids division by zero (usually on the order of (1e-8)). Interestingly, without the square root operation, the algorithm performs much worse.

As (G_{t}) contains the sum of the squares of the past gradients w.r.t. to all parameters (theta) along its diagonal, we can now vectorize our implementation by performing an element-wise matrix-vector multiplication (odot) between (G_{t}) and (g_{t}):

(theta_{t+1} = theta_{t} – dfrac{eta}{sqrt{G_{t} + epsilon}} odot g_{t}).

One of Adagrad’s main benefits is that it eliminates the need to manually tune the learning rate. Most implementations use a default value of 0.01 and leave it at that.

Adagrad’s main weakness is its accumulation of the squared gradients in the denominator: Since every added term is positive, the accumulated sum keeps growing during training. This in turn causes the learning rate to shrink and eventually become infinitesimally small, at which point the algorithm is no longer able to acquire additional knowledge. The following algorithms aim to resolve this flaw.

Instead of inefficiently storing (w) previous squared gradients, the sum of gradients is recursively defined as a decaying average of all past squared gradients. The running average (E[g^2]_t) at time step (t) then depends (as a fraction (gamma ) similarly to the Momentum term) only on the previous average and the current gradient:

(E[g^2]_t = gamma E[g^2]_{t-1} + (1 – gamma) g^2_t ).

We set (gamma) to a similar value as the momentum term, around 0.9. For clarity, we now rewrite our vanilla SGD update in terms of the parameter update vector ( Delta theta_t ):

(Delta theta_t = – eta cdot g_{t, i}).

(theta_{t+1} = theta_t + Delta theta_t ).

The parameter update vector of Adagrad that we derived previously thus takes the form:

( Delta theta_t = – dfrac{eta}{sqrt{G_{t} + epsilon}} odot g_{t}).

We now simply replace the diagonal matrix (G_{t}) with the decaying average over past squared gradients (E[g^2]_t):

( Delta theta_t = – dfrac{eta}{sqrt{E[g^2]_t + epsilon}} g_{t}).

As the denominator is just the root mean squared (RMS) error criterion of the gradient, we can replace it with the criterion short-hand:

( Delta theta_t = – dfrac{eta}{RMS[g]_{t}} g_t).

The authors note that the units in this update (as well as in SGD, Momentum, or Adagrad) do not match, i.e. the update should have the same hypothetical units as the parameter. To realize this, they first define another exponentially decaying average, this time not of squared gradients but of squared parameter updates:

(E[Delta theta^2]_t = gamma E[Delta theta^2]_{t-1} + (1 – gamma) Delta theta^2_t ).

The root mean squared error of parameter updates is thus:

(RMS[Delta theta]_{t} = sqrt{E[Delta theta^2]_t + epsilon} ).

Since (RMS[Delta theta]_{t}) is unknown, we approximate it with the RMS of parameter updates until the previous time step. Replacing the learning rate (eta ) in the previous update rule with (RMS[Delta theta]_{t-1}) finally yields the Adadelta update rule:

( Delta theta_t = – dfrac{RMS[Delta theta]_{t-1}}{RMS[g]_{t}} g_{t}).

(theta_{t+1} = theta_t + Delta theta_t ).

With Adadelta, we do not even need to set a default learning rate, as it has been eliminated from the update rule.

## RMSprop

RMSprop is an unpublished, adaptive learning rate method proposed by Geoff Hinton in Lecture 6e of his Coursera Class.

RMSprop and Adadelta have both been developed independently around the same time stemming from the need to resolve Adagrad’s radically diminishing learning rates. RMSprop in fact is identical to the first update vector of Adadelta that we derived above:

(E[g^2]_t = 0.9 E[g^2]_{t-1} + 0.1 g^2_t ).

(theta_{t+1} = theta_{t} – dfrac{eta}{sqrt{E[g^2]_t + epsilon}} g_{t}).

RMSprop as well divides the learning rate by an exponentially decaying average of squared gradients. Hinton suggests (gamma) to be set to 0.9, while a good default value for the learning rate (eta) is 0.001.

(m_t = beta_1 m_{t-1} + (1 – beta_1) g_t ).

(v_t = beta_2 v_{t-1} + (1 – beta_2) g_t^2 ).

(m_t) and (v_t) are estimates of the first moment (the mean) and the second moment (the uncentered variance) of the gradients respectively, hence the name of the method. As (m_t) and (v_t) are initialized as vectors of 0’s, the authors of Adam observe that they are biased towards zero, especially during the initial time steps, and especially when the decay rates are small (i.e. (beta_1) and (beta_2) are close to 1).

They counteract these biases by computing bias-corrected first and second moment estimates:

(hat{m}_t = dfrac{m_t}{1 – beta^t_1} ).

(hat{v}_t = dfrac{v_t}{1 – beta^t_2} ).

They then use these to update the parameters just as we have seen in Adadelta and RMSprop, which yields the Adam update rule:

(theta_{t+1} = theta_{t} – dfrac{eta}{sqrt{hat{v}_t} + epsilon} hat{m}_t).

The authors propose default values of 0.9 for (beta_1), 0.999 for (beta_2), and (10^{-8}) for (epsilon). They show empirically that Adam works well in practice and compares favorably to other adaptive learning-method algorithms.

## Visualization of algorithms

The following two animations (Image credit: Alec Radford) provide some intuitions towards the optimization behaviour of the presented optimization algorithms. Also have a look here for a description of the same images by Karpathy and another concise overview of the algorithms discussed.

In Image 5, we see their behaviour on the contours of a loss surface over time. Note that Adagrad, Adadelta, and RMSprop almost immediately head off in the right direction and converge similarly fast, while Momentum and NAG are led off-track, evoking the image of a ball rolling down the hill. NAG, however, is quickly able to correct its course due to its increased responsiveness by looking ahead and heads to the minimum.

Image 6 shows the behaviour of the algorithms at a saddle point, i.e. a point where one dimension has a positive slope, while the other dimension has a negative slope, which pose a difficulty for SGD as we mentioned before. Notice here that SGD, Momentum, and NAG find it difficulty to break symmetry, although the two latter eventually manage to escape the saddle point, while Adagrad, RMSprop, and Adadelta quickly head down the negative slope.

## Which optimizer to use?

So, which optimizer should you now use? If your input data is sparse, then you likely achieve the best results using one of the adaptive learning-rate methods. An additional benefit is that you won’t need to tune the learning rate but likely achieve the best results with the default value. 