1298 lines
54 KiB
HTML
1298 lines
54 KiB
HTML
<!--
|
|
Automatically generated HTML file from DocOnce source
|
|
(https://github.com/hplgit/doconce/)
|
|
-->
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<meta name="generator" content="DocOnce: https://github.com/hplgit/doconce/" />
|
|
<meta name="description" content="Data Analysis and Machine Learning Lectures: Optimization and Gradient Methods">
|
|
|
|
<title>Data Analysis and Machine Learning Lectures: Optimization and Gradient Methods</title>
|
|
|
|
|
|
<link href="https://cdn.rawgit.com/hplgit/doconce/master/bundled/html_styles/style_solarized_box/css/solarized_light_code.css" rel="stylesheet" type="text/css" title="light"/>
|
|
<script src="https://cdn.rawgit.com/hplgit/doconce/master/bundled/html_styles/style_solarized_box/js/highlight.pack.js"></script>
|
|
<script>hljs.initHighlightingOnLoad();</script>
|
|
|
|
<link href="https://thomasf.github.io/solarized-css/solarized-light.min.css" rel="stylesheet">
|
|
<style type="text/css">
|
|
h1 {color: #b58900;} /* yellow */
|
|
/* h1 {color: #cb4b16;} orange */
|
|
/* h1 {color: #d33682;} magenta, the original choice of thomasf */
|
|
code { padding: 0px; background-color: inherit; }
|
|
pre {
|
|
border: 0pt solid #93a1a1;
|
|
box-shadow: none;
|
|
}
|
|
.alert-text-small { font-size: 80%; }
|
|
.alert-text-large { font-size: 130%; }
|
|
.alert-text-normal { font-size: 90%; }
|
|
.alert {
|
|
padding:8px 35px 8px 14px; margin-bottom:18px;
|
|
text-shadow:0 1px 0 rgba(255,255,255,0.5);
|
|
border:1px solid #93a1a1;
|
|
border-radius: 4px;
|
|
-webkit-border-radius: 4px;
|
|
-moz-border-radius: 4px;
|
|
color: #555;
|
|
background-color: #eee8d5;
|
|
background-position: 10px 5px;
|
|
background-repeat: no-repeat;
|
|
background-size: 38px;
|
|
padding-left: 55px;
|
|
width: 75%;
|
|
}
|
|
.alert-block {padding-top:14px; padding-bottom:14px}
|
|
.alert-block > p, .alert-block > ul {margin-bottom:1em}
|
|
.alert li {margin-top: 1em}
|
|
.alert-block p+p {margin-top:5px}
|
|
.alert-notice { background-image: url(https://cdn.rawgit.com/hplgit/doconce/master/bundled/html_images/small_yellow_notice.png); }
|
|
.alert-summary { background-image:url(https://cdn.rawgit.com/hplgit/doconce/master/bundled/html_images/small_yellow_summary.png); }
|
|
.alert-warning { background-image: url(https://cdn.rawgit.com/hplgit/doconce/master/bundled/html_images/small_yellow_warning.png); }
|
|
.alert-question {background-image:url(https://cdn.rawgit.com/hplgit/doconce/master/bundled/html_images/small_yellow_question.png); }
|
|
|
|
div { text-align: justify; text-justify: inter-word; }
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
<!-- tocinfo
|
|
{'highest level': 2,
|
|
'sections': [('Optimization, the central part of any Machine Learning '
|
|
'algortithm',
|
|
2,
|
|
None,
|
|
'___sec0'),
|
|
('Revisiting our Logistic Regression case', 2, None, '___sec1'),
|
|
('The equations to solve', 2, None, '___sec2'),
|
|
("Solving using Newton-Raphson's method", 2, None, '___sec3'),
|
|
("Brief reminder on Newton-Raphson's method", 2, None, '___sec4'),
|
|
('The equations', 2, None, '___sec5'),
|
|
('Simple geometric interpretation', 2, None, '___sec6'),
|
|
('Extending to more than one variable', 2, None, '___sec7'),
|
|
('Steepest descent', 2, None, '___sec8'),
|
|
('More on Steepest descent', 2, None, '___sec9'),
|
|
('The ideal', 2, None, '___sec10'),
|
|
('The sensitiveness of the gradient descent',
|
|
2,
|
|
None,
|
|
'___sec11'),
|
|
('Convex functions', 2, None, '___sec12'),
|
|
('Convex function', 2, None, '___sec13'),
|
|
('Conditions on convex functions', 2, None, '___sec14'),
|
|
('More on convex functions', 2, None, '___sec15'),
|
|
('Some simple problems', 2, None, '___sec16'),
|
|
('Standard steepest descent', 2, None, '___sec17'),
|
|
('Gradient method', 2, None, '___sec18'),
|
|
('Steepest descent method', 2, None, '___sec19'),
|
|
('Steepest descent method', 2, None, '___sec20'),
|
|
('Gradient descent method', 2, None, '___sec21'),
|
|
('Final expressions', 2, None, '___sec22'),
|
|
('The Steepest descent algorithm', 2, None, '___sec23'),
|
|
('Simple codes for steepest descent and conjugate gradient '
|
|
'using a $2\\times 2$ matrix, in c++, Python code to come',
|
|
2,
|
|
None,
|
|
'___sec24'),
|
|
('The routine for the steepest descent method',
|
|
2,
|
|
None,
|
|
'___sec25'),
|
|
('Revisiting our first homework', 2, None, '___sec26'),
|
|
('Gradient descent example', 2, None, '___sec27'),
|
|
('The derivative of the cost/loss function', 2, None, '___sec28'),
|
|
('The Hessian matrix', 2, None, '___sec29'),
|
|
('Simple program', 2, None, '___sec30'),
|
|
('Gradient Descent Example', 2, None, '___sec31'),
|
|
('And a corresponding example using _scikit-learn_',
|
|
2,
|
|
None,
|
|
'___sec32'),
|
|
('Gradient descent and Ridge', 2, None, '___sec33'),
|
|
('Stochastic Gradient Descent', 2, None, '___sec34'),
|
|
('Computation of gradients', 2, None, '___sec35'),
|
|
('SGD example', 2, None, '___sec36'),
|
|
('The gradient step', 2, None, '___sec37'),
|
|
('Simple example code', 2, None, '___sec38'),
|
|
('When do we stop?', 2, None, '___sec39'),
|
|
('Slightly different approach', 2, None, '___sec40')]}
|
|
end of tocinfo -->
|
|
|
|
<body>
|
|
|
|
|
|
|
|
<script type="text/x-mathjax-config">
|
|
MathJax.Hub.Config({
|
|
TeX: {
|
|
equationNumbers: { autoNumber: "AMS" },
|
|
extensions: ["AMSmath.js", "AMSsymbols.js", "autobold.js", "color.js"]
|
|
}
|
|
});
|
|
</script>
|
|
<script type="text/javascript" async
|
|
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<!-- ------------------- main content ---------------------- -->
|
|
|
|
|
|
|
|
<center><h1>Data Analysis and Machine Learning Lectures: Optimization and Gradient Methods</h1></center> <!-- document title -->
|
|
|
|
<p>
|
|
<!-- author(s): Morten Hjorth-Jensen -->
|
|
|
|
<center>
|
|
<b>Morten Hjorth-Jensen</b> [1, 2]
|
|
</center>
|
|
|
|
<p>
|
|
<!-- institution(s) -->
|
|
|
|
<center>[1] <b>Department of Physics, University of Oslo</b></center>
|
|
<center>[2] <b>Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University</b></center>
|
|
<br>
|
|
<p>
|
|
<center><h4>Sep 27, 2018</h4></center> <!-- date -->
|
|
<br>
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec0">Optimization, the central part of any Machine Learning algortithm </h2>
|
|
|
|
<p>
|
|
Almost every problem in machine learning and data science starts with
|
|
a dataset \( X \), a model \( g(\beta) \), which is a function of the
|
|
parameters \( \beta \) and a cost function \( C(X, g(\beta)) \) that allows
|
|
us to judge how well the model \( g(\beta) \) explains the observations
|
|
\( X \). The model is fit by finding the values of \( \beta \) that minimize
|
|
the cost function. Ideally we would be able to solve for \( \beta \)
|
|
analytically, however this is not possible in general and we must use
|
|
some approximative/numerical method to compute the minimum.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec1">Revisiting our Logistic Regression case </h2>
|
|
|
|
<p>
|
|
In our discussion on Logistic Regression we defined we studied first the
|
|
case of
|
|
two classes, with \( y_i \) either
|
|
\( 0 \) or \( 1 \). Furthermore we assumed also that we have only two
|
|
parameters \( \beta \) in our fitting of the Sigmoid function, that is we
|
|
defined probabilities
|
|
|
|
$$
|
|
\begin{align*}
|
|
p(y_i=1|x_i,\hat{\beta}) &= \frac{\exp{(\beta_0+\beta_1x_i)}}{1+\exp{(\beta_0+\beta_1x_i)}},\nonumber\\
|
|
p(y_i=0|x_i,\hat{\beta}) &= 1 - p(y_i=1|x_i,\hat{\beta}),
|
|
\end{align*}
|
|
$$
|
|
|
|
where \( \hat{\beta} \) are the weights we wish to extract from data, in our case \( \beta_0 \) and \( \beta_1 \).
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec2">The equations to solve </h2>
|
|
|
|
<p>
|
|
Our compact equations used a definition of a vector \( \hat{y} \) with \( n \)
|
|
elements \( y_i \), an \( n\times p \) matrix \( \hat{X} \) which contains the
|
|
\( x_i \) values and a vector \( \hat{p} \) of fitted probabilities
|
|
\( p(y_i\vert x_i,\hat{\beta}) \). We rewrote in a more compact form
|
|
the first derivative of the cost function as
|
|
|
|
$$
|
|
\frac{\partial \mathcal{C}(\hat{\beta})}{\partial \hat{\beta}} = -\hat{X}^T\left(\hat{y}-\hat{p}\right).
|
|
$$
|
|
|
|
<p>
|
|
If we in addition define a diagonal matrix \( \hat{W} \) with elements
|
|
\( p(y_i\vert x_i,\hat{\beta})(1-p(y_i\vert x_i,\hat{\beta}) \), we can obtain a compact expression of the second derivative as
|
|
|
|
$$
|
|
\frac{\partial^2 \mathcal{C}(\hat{\beta})}{\partial \hat{\beta}\partial \hat{\beta}^T} = \hat{X}^T\hat{W}\hat{X}.
|
|
$$
|
|
|
|
This defines what we call the Hessian.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec3">Solving using Newton-Raphson's method </h2>
|
|
|
|
<p>
|
|
If we can set up these equations, Newton-Raphson's iterative method is the nomrally the method of choice. It requires however that we setting the matrices that define the first and second derivatives.
|
|
|
|
<p>
|
|
Our iterative scheme is then given by
|
|
|
|
$$
|
|
\hat{\beta}^{\mathrm{new}} = \hat{\beta}^{\mathrm{old}}-\left(\frac{\partial^2 \mathcal{C}(\hat{\beta})}{\partial \hat{\beta}\partial \hat{\beta}^T}\right)^{-1}\times \left(\frac{\partial \mathcal{C}(\hat{\beta})}{\partial \hat{\beta}}\right)_{\hat{\beta}^{\mathrm{old}}},
|
|
$$
|
|
|
|
or in matrix form as
|
|
|
|
$$
|
|
\hat{\beta}^{\mathrm{new}} = \hat{\beta}^{\mathrm{old}}-\left(\hat{X}^T\hat{W}\hat{X} \right)^{-1}\times \left(-\hat{X}^T(\hat{y}-\hat{p}) \right)_{\hat{\beta}^{\mathrm{old}}}.
|
|
$$
|
|
|
|
The right-hand side is computed with the old values of \( \beta \).
|
|
|
|
<p>
|
|
If we can compute these matrices, in particular the Hessian, the above is often the easiest method to implement.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec4">Brief reminder on Newton-Raphson's method </h2>
|
|
|
|
<p>
|
|
Let us quicly remind ourselves how we derive the above method.
|
|
|
|
<p>
|
|
Perhaps the most celebrated of all one-dimensional root-finding
|
|
routines is Newton's method, also called the Newton-Raphson
|
|
method. This method is distinguished from the previously discussed
|
|
methods by the fact that it requires the evaluation of both the
|
|
function \( f \) and its derivative \( f' \) at arbitrary points. In this
|
|
sense, it is taylored to cases with e.g., transcendental equations.
|
|
If you can only calculate the derivative
|
|
numerically and/or your function is not of the smooth type, we
|
|
discourage the use of this method.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec5">The equations </h2>
|
|
|
|
<p>
|
|
The Newton-Raphson formula consists geometrically of extending the
|
|
tangent line at a current point until it crosses zero, then setting
|
|
the next guess to the abscissa of that zero-crossing. The mathematics
|
|
behind this method is rather simple. Employing a Taylor expansion for
|
|
\( x \) sufficiently close to the solution \( s \), we have
|
|
|
|
$$
|
|
f(s)=0=f(x)+(s-x)f'(x)+\frac{(s-x)^2}{2}f''(x) +\dots.
|
|
\label{eq:taylornr}
|
|
$$
|
|
|
|
<p>
|
|
For small enough values of the function and for well-behaved
|
|
functions, the terms beyond linear are unimportant, hence we obtain
|
|
|
|
$$
|
|
f(x)+(s-x)f'(x)\approx 0,
|
|
$$
|
|
|
|
yielding
|
|
$$
|
|
s\approx x-\frac{f(x)}{f'(x)}.
|
|
$$
|
|
|
|
<p>
|
|
Having in mind an iterative procedure, it is natural to start iterating with
|
|
$$
|
|
x_{n+1}=x_n-\frac{f(x_n)}{f'(x_n)}.
|
|
$$
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec6">Simple geometric interpretation </h2>
|
|
|
|
<p>
|
|
The above is Newton-Raphson's method. It has a simple geometric
|
|
interpretation, namely \( x_{n+1} \) is the point where the tangent from
|
|
\( (x_n,f(x_n)) \) crosses the $x-$axis. Close to the solution,
|
|
Newton-Raphson converges fast to the desired result. However, if we
|
|
are far from a root, where the higher-order terms in the series are
|
|
important, the Newton-Raphson formula can give grossly inaccurate
|
|
results. For instance, the initial guess for the root might be so far
|
|
from the true root as to let the search interval include a local
|
|
maximum or minimum of the function. If an iteration places a trial
|
|
guess near such a local extremum, so that the first derivative nearly
|
|
vanishes, then Newton-Raphson may fail totally
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec7">Extending to more than one variable </h2>
|
|
|
|
<p>
|
|
Newton's method can be generalized to systems of several non-linear equations
|
|
and variables. Consider the case with two equations
|
|
$$
|
|
\begin{array}{cc} f_1(x_1,x_2) &=0\\
|
|
f_2(x_1,x_2) &=0\end{array},
|
|
$$
|
|
|
|
which we Taylor expand to obtain
|
|
|
|
$$
|
|
\begin{array}{cc} 0=f_1(x_1+h_1,x_2+h_2)=&f_1(x_1,x_2)+h_1
|
|
\partial f_1/\partial x_1+h_2
|
|
\partial f_1/\partial x_2+\dots\\
|
|
0=f_2(x_1+h_1,x_2+h_2)=&f_2(x_1,x_2)+h_1
|
|
\partial f_2/\partial x_1+h_2
|
|
\partial f_2/\partial x_2+\dots
|
|
\end{array}.
|
|
$$
|
|
|
|
Defining the Jacobian matrix \( {\bf \hat{J}} \) we have
|
|
$$
|
|
{\bf \hat{J}}=\left( \begin{array}{cc}
|
|
\partial f_1/\partial x_1 & \partial f_1/\partial x_2 \\
|
|
\partial f_2/\partial x_1 &\partial f_2/\partial x_2
|
|
\end{array} \right),
|
|
$$
|
|
|
|
we can rephrase Newton's method as
|
|
$$
|
|
\left(\begin{array}{c} x_1^{n+1} \\ x_2^{n+1} \end{array} \right)=
|
|
\left(\begin{array}{c} x_1^{n} \\ x_2^{n} \end{array} \right)+
|
|
\left(\begin{array}{c} h_1^{n} \\ h_2^{n} \end{array} \right),
|
|
$$
|
|
|
|
where we have defined
|
|
$$
|
|
\left(\begin{array}{c} h_1^{n} \\ h_2^{n} \end{array} \right)=
|
|
-{\bf \hat{J}}^{-1}
|
|
\left(\begin{array}{c} f_1(x_1^{n},x_2^{n}) \\ f_2(x_1^{n},x_2^{n}) \end{array} \right).
|
|
$$
|
|
|
|
We need thus to compute the inverse of the Jacobian matrix and it
|
|
is to understand that difficulties may
|
|
arise in case \( {\bf \hat{J}} \) is nearly singular.
|
|
|
|
<p>
|
|
It is rather straightforward to extend the above scheme to systems of
|
|
more than two non-linear equations. In our case, the Jacobian matrix is given by the Hessian that represents the second derivative of cost function.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec8">Steepest descent </h2>
|
|
|
|
<p>
|
|
The method of steepest descent The basic idea of gradient descent is
|
|
that a function \( F(\mathbf{x}) \),
|
|
\( \mathbf{x} \equiv (x_1,\cdots,x_n) \), decreases fastest if one goes from \( \bf {x} \) in the
|
|
direction of the negative gradient \( -\nabla F(\mathbf{x}) \).
|
|
|
|
<p>
|
|
It can be shown that if
|
|
$$
|
|
\mathbf{x}_{k+1} = \mathbf{x}_k - \gamma_k \nabla F(\mathbf{x}_k),
|
|
$$
|
|
|
|
with \( \gamma_k > 0 \).
|
|
|
|
<p>
|
|
For \( \gamma_k \) small enough, then \( F(\mathbf{x}_{k+1}) \leq
|
|
F(\mathbf{x}_k) \). This means that for a sufficiently small \( \gamma_k \)
|
|
we are always moving towards smaller function values, i.e a minimum.
|
|
|
|
<p>
|
|
<!-- !split -->
|
|
|
|
<h2 id="___sec9">More on Steepest descent </h2>
|
|
|
|
<p>
|
|
The previous observation is the basis of the method of steepest
|
|
descent, which is also referred to as just gradient descent (GD). One
|
|
starts with an initial guess \( \mathbf{x}_0 \) for a minimum of \( F \) and
|
|
computes new approximations according to
|
|
|
|
$$
|
|
\mathbf{x}_{k+1} = \mathbf{x}_k - \gamma_k \nabla F(\mathbf{x}_k), \ \ k \geq 0.
|
|
$$
|
|
|
|
<p>
|
|
The parameter \( \gamma_k \) is often referred to as the step length or
|
|
the learning rate within the context of Machine Learning.
|
|
|
|
<p>
|
|
<!-- !split -->
|
|
|
|
<h2 id="___sec10">The ideal </h2>
|
|
|
|
<p>
|
|
Ideally the sequence \( \{\mathbf{x}_k \}_{k=0} \) converges to a global
|
|
minimum of the function \( F \). In general we do not know if we are in a
|
|
global or local minimum. In the special case when \( F \) is a convex
|
|
function, all local minima are also global minima, so in this case
|
|
gradient descent can converge to the global solution. The advantage of
|
|
this scheme is that it is conceptually simple and straightforward to
|
|
implement. However the method in this form has some severe
|
|
limitations:
|
|
|
|
<p>
|
|
In machine learing we are often faced with non-convex high dimensional
|
|
cost functions with many local minima. Since GD is deterministic we
|
|
will get stuck in a local minimum, if the method converges, unless we
|
|
have a very good intial guess. This also implies that the scheme is
|
|
sensitive to the chosen initial condition.
|
|
|
|
<p>
|
|
Note that the gradient is a function of \( \mathbf{x} =
|
|
(x_1,\cdots,x_n) \) which makes it expensive to compute numerically.
|
|
|
|
<p>
|
|
<!-- !split -->
|
|
|
|
<h2 id="___sec11">The sensitiveness of the gradient descent </h2>
|
|
|
|
<p>
|
|
The gradient descent method
|
|
is sensitive to the choice of learning rate \( \gamma_k \). This is due
|
|
to the fact that we are only guaranteed that \( F(\mathbf{x}_{k+1}) \leq
|
|
F(\mathbf{x}_k) \) for sufficiently small \( \gamma_k \). The problem is to
|
|
determine an optimal learning rate. If the learning rate is chosen too
|
|
small the method will take a long time to converge and if it is too
|
|
large we can experience erratic behavior.
|
|
|
|
<p>
|
|
Many of these shortcomings can be alleviated by introducing
|
|
randomness. One such method is that of Stochastic Gradient Descent
|
|
(SGD), see below.
|
|
|
|
<p>
|
|
<!-- !split -->
|
|
|
|
<h2 id="___sec12">Convex functions </h2>
|
|
|
|
<p>
|
|
Ideally we want our cost/loss function to be convex(concave).
|
|
|
|
<p>
|
|
First we give the definition of a convex set: A set \( C \) in
|
|
\( \mathbb{R}^n \) is said to be convex if, for all \( x \) and \( y \) in \( C \) and
|
|
all \( t \in (0,1) \) , the point \( (1 − t)x + ty \) also belongs to
|
|
C. Geometrically this means that every point on the line segment
|
|
connecting \( x \) and \( y \) is in \( C \) as discussed below.
|
|
|
|
<p>
|
|
The convex subsets of \( \mathbb{R} \) are the intervals of
|
|
\( \mathbb{R} \). Examples of convex sets of \( \mathbb{R}^2 \) are the
|
|
regular polygons (triangles, rectangles, pentagons, etc...).
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec13">Convex function </h2>
|
|
|
|
<p>
|
|
<b>Convex function</b>: Let \( X \subset \mathbb{R}^n \) be a convex set. Assume that the function \( f: X \rightarrow \mathbb{R} \) is continuous, then \( f \) is said to be convex if $$f(tx_1 + (1-t)x_2) \leq tf(x_1) + (1-t)f(x_2) $$ for all \( x_1, x_2 \in X \) and for all \( t \in [0,1] \). If \( \leq \) is replaced with a strict inequaltiy in the definition, we demand \( x_1 \neq x_2 \) and \( t\in(0,1) \) then \( f \) is said to be strictly convex. For a single variable function, convexity means that if you draw a straight line connecting \( f(x_1) \) and \( f(x_2) \), the value of the function on the interval \( [x_1,x_2] \) is always below the line as illustrated below.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec14">Conditions on convex functions </h2>
|
|
|
|
<p>
|
|
In the following we state first and second-order conditions which
|
|
ensures convexity of a function \( f \). We write \( D_f \) to denote the
|
|
domain of \( f \), i.e the subset of \( R^n \) where \( f \) is defined. For more
|
|
details and proofs we refer to: <a href="http://stanford.edu/boyd/cvxbook/, 2004" target="_blank">S. Boyd and L. Vandenberghe. Convex Optimization. Cambridge University Press</a>.
|
|
|
|
<p>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b>First order condition.</b>
|
|
<p>
|
|
Suppose \( f \) is differentiable (i.e \( \nabla f(x) \) is well defined for
|
|
all \( x \) in the domain of \( f \)). Then \( f \) is convex if and only if \( D_f \)
|
|
is a convex set and $$f(y) \geq f(x) + \nabla f(x)^T (y-x) $$ holds
|
|
for all \( x,y \in D_f \). This condition means that for a convex function
|
|
the first order Taylor expansion (right hand side above) at any point
|
|
a global under estimator of the function. To convince yourself you can
|
|
make a drawing of \( f(x) = x^2+1 \) and draw the tangent line to \( f(x) \) and
|
|
note that it is always below the graph.
|
|
</div>
|
|
|
|
|
|
<p>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b>Second order condition.</b>
|
|
<p>
|
|
Assume that \( f \) is twice
|
|
differentiable, i.e the Hessian matrix exists at each point in
|
|
\( D_f \). Then \( f \) is convex if and only if \( D_f \) is a convex set and its
|
|
Hessian is positive semi-definite for all \( x\in D_f \). For a
|
|
single-variable function this reduces to \( f''(x) \geq 0 \). Geometrically this means that \( f \) has nonnegative curvature
|
|
everywhere.
|
|
</div>
|
|
|
|
|
|
<p>
|
|
This condition is particularly useful since it gives us an procedure for determining if the function under consideration is convex, apart from using the definition.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec15">More on convex functions </h2>
|
|
|
|
<p>
|
|
The next result is of great importance to us and the reason why we are
|
|
going on about convex functions. In machine learning we frequently
|
|
have to minimize a loss/cost function in order to find the best
|
|
parameters for the model we are considering.
|
|
|
|
<p>
|
|
Ideally we want the
|
|
global minimum (for high-dimensional models it is hard to know
|
|
if we have local or global minimum). However, if the cost/loss function
|
|
is convex the following result provides invaluable information:
|
|
|
|
<p>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b>Any minimum is global for convex functions.</b>
|
|
<p>
|
|
Consider the problem of finding \( x \in \mathbb{R}^n \) such that \( f(x) \)
|
|
is minimal, where \( f \) is convex and differentiable. Then, any point
|
|
\( x^* \) that satisfies \( \nabla f(x^*) = 0 \) is a global minimum.
|
|
</div>
|
|
|
|
|
|
<p>
|
|
This result means that if we know that the cost/loss function is convex and we are able to find a minimum, we are guaranteed that it is a global minimum.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec16">Some simple problems </h2>
|
|
|
|
<ol>
|
|
<li> Show that \( f(x)=x^2 \) is convex for \( x \in \mathbb{R} \) using the definition of convexity. Hint: If you re-write the definition, \( f \) is convex if the following holds for all \( x,y \in D_f \) and any \( \lambda \in [0,1] \) $\lambda f(x)+(1-\lambda)f(y)-f(\lambda x + (1-\lambda) y ) \geq 0$.</li>
|
|
<li> Using the second order condition show that the following functions are convex on the specified domain.</li>
|
|
|
|
<ul>
|
|
<li> \( f(x) = e^x \) is convex for \( x \in \mathbb{R} \).</li>
|
|
<li> \( g(x) = -\ln(x) \) is convex for \( x \in (0,\infty) \).</li>
|
|
</ul>
|
|
|
|
<li> Let \( f(x) = x^2 \) and \( g(x) = e^x \). Show that \( f(g(x)) \) and \( g(f(x)) \) is convex for \( x \in \mathbb{R} \). Also show that if \( f(x) \) is any convex function than \( h(x) = e^{f(x)} \) is convex.</li>
|
|
<li> A norm is any function that satisfy the following properties</li>
|
|
|
|
<ul>
|
|
<li> \( f(\alpha x) = |\alpha| f(x) \) for all \( \alpha \in \mathbb{R} \).</li>
|
|
<li> \( f(x+y) \leq f(x) + f(y) \)</li>
|
|
<li> \( f(x) \leq 0 \) for all \( x \in \mathbb{R}^n \) with equality if and only if \( x = 0 \)</li>
|
|
</ul>
|
|
|
|
</ol>
|
|
|
|
Using the definition of convexity, try to show that a function satisfying the properties above is convex (the third condition is not needed to show this).
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec17">Standard steepest descent </h2>
|
|
|
|
<p>
|
|
Before we proceed, we would like to mention the approach called the <b>standard Steepest descent</b>, which again leads to us having to be able to compute a matrix.
|
|
|
|
<p>
|
|
<a href="https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf" target="_blank">The success of the CG method</a>
|
|
for finding solutions of non-linear problems is based on the theory
|
|
of conjugate gradients for linear systems of equations. It belongs to
|
|
the class of iterative methods for solving problems from linear
|
|
algebra of the type
|
|
$$
|
|
\begin{equation*}
|
|
\hat{A}\hat{x} = \hat{b}.
|
|
\end{equation*}
|
|
$$
|
|
|
|
<p>
|
|
In the iterative process we end up with a problem like
|
|
|
|
$$
|
|
\begin{equation*}
|
|
\hat{r}= \hat{b}-\hat{A}\hat{x},
|
|
\end{equation*}
|
|
$$
|
|
|
|
where \( \hat{r} \) is the so-called residual or error in the iterative process.
|
|
|
|
<p>
|
|
When we have found the exact solution, \( \hat{r}=0 \).
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec18">Gradient method </h2>
|
|
|
|
<p>
|
|
The residual is zero when we reach the minimum of the quadratic equation
|
|
$$
|
|
\begin{equation*}
|
|
P(\hat{x})=\frac{1}{2}\hat{x}^T\hat{A}\hat{x} - \hat{x}^T\hat{b},
|
|
\end{equation*}
|
|
$$
|
|
|
|
<p>
|
|
with the constraint that the matrix \( \hat{A} \) is positive definite and
|
|
symmetric. If we search for a minimum of the quantum mechanical
|
|
variance, then the matrix \( \hat{A} \), which is called the Hessian, is
|
|
given by the second-derivative of the function we want to minimize.
|
|
This quantity is always positive definite.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec19">Steepest descent method </h2>
|
|
|
|
<p>
|
|
We denote the initial guess for \( \hat{x} \) as \( \hat{x}_0 \).
|
|
We can assume without loss of generality that
|
|
$$
|
|
\begin{equation*}
|
|
\hat{x}_0=0,
|
|
\end{equation*}
|
|
$$
|
|
|
|
or consider the system
|
|
$$
|
|
\begin{equation*}
|
|
\hat{A}\hat{z} = \hat{b}-\hat{A}\hat{x}_0,
|
|
\end{equation*}
|
|
$$
|
|
|
|
instead.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec20">Steepest descent method </h2>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b></b>
|
|
<p>
|
|
One can show that the solution \( \hat{x} \) is also the unique minimizer of the quadratic form
|
|
$$
|
|
\begin{equation*}
|
|
f(\hat{x}) = \frac{1}{2}\hat{x}^T\hat{A}\hat{x} - \hat{x}^T \hat{x} , \quad \hat{x}\in\mathbf{R}^n.
|
|
\end{equation*}
|
|
$$
|
|
|
|
This suggests taking the first basis vector \( \hat{p}_1 \)
|
|
to be the gradient of \( f \) at \( \hat{x}=\hat{x}_0 \),
|
|
which equals
|
|
$$
|
|
\begin{equation*}
|
|
\hat{A}\hat{x}_0-\hat{b},
|
|
\end{equation*}
|
|
$$
|
|
|
|
and
|
|
\( \hat{x}_0=0 \) it is equal \( -\hat{b} \).
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec21">Gradient descent method </h2>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b></b>
|
|
<p>
|
|
Let \( \hat{r}_k \) be the residual at the \( k \)-th step:
|
|
$$
|
|
\begin{equation*}
|
|
\hat{r}_k=\hat{b}-\hat{A}\hat{x}_k.
|
|
\end{equation*}
|
|
$$
|
|
|
|
Note that \( \hat{r}_k \) is the negative gradient of \( f \) at
|
|
\( \hat{x}=\hat{x}_k \),
|
|
so the gradient descent method would be to move in the direction \( \hat{r}_k \).
|
|
This gives the following expression
|
|
$$
|
|
\begin{equation*}
|
|
\hat{p}_{k+1}=\hat{r}_k-\frac{\hat{p}_k^T \hat{A}\hat{r}_k}{\hat{p}_k^T\hat{A}\hat{p}_k} \hat{p}_k.
|
|
\end{equation*}
|
|
$$
|
|
</div>
|
|
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec22">Final expressions </h2>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b></b>
|
|
<p>
|
|
We can also compute the residual iteratively as
|
|
$$
|
|
\begin{equation*}
|
|
\hat{r}_{k+1}=\hat{b}-\hat{A}\hat{x}_{k+1},
|
|
\end{equation*}
|
|
$$
|
|
|
|
which equals
|
|
$$
|
|
\begin{equation*}
|
|
\hat{b}-\hat{A}(\hat{x}_k+\alpha_k\hat{p}_k),
|
|
\end{equation*}
|
|
$$
|
|
|
|
or
|
|
$$
|
|
\begin{equation*}
|
|
(\hat{b}-\hat{A}\hat{x}_k)-\alpha_k\hat{A}\hat{p}_k,
|
|
\end{equation*}
|
|
$$
|
|
|
|
which gives
|
|
|
|
$$
|
|
\begin{equation*}
|
|
\hat{r}_{k+1}=\hat{r}_k-\hat{A}\hat{p}_{k},
|
|
\end{equation*}
|
|
$$
|
|
</div>
|
|
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec23">The Steepest descent algorithm </h2>
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec24">Simple codes for steepest descent and conjugate gradient using a \( 2\times 2 \) matrix, in c++, Python code to come </h2>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b></b>
|
|
<p>
|
|
<p>
|
|
|
|
<!-- code=c++ (!bc cppcod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eee8d5"><pre style="line-height: 125%"><span></span><span style="color: #1e889b">#include</span> <span style="color: #228B22"><cmath></span><span style="color: #1e889b"></span>
|
|
<span style="color: #1e889b">#include</span> <span style="color: #228B22"><iostream></span><span style="color: #1e889b"></span>
|
|
<span style="color: #1e889b">#include</span> <span style="color: #228B22"><fstream></span><span style="color: #1e889b"></span>
|
|
<span style="color: #1e889b">#include</span> <span style="color: #228B22"><iomanip></span><span style="color: #1e889b"></span>
|
|
<span style="color: #1e889b">#include</span> <span style="color: #228B22">"vectormatrixclass.h"</span><span style="color: #1e889b"></span>
|
|
<span style="color: #8B008B; font-weight: bold">using</span> <span style="color: #8B008B; font-weight: bold">namespace</span> std;
|
|
<span style="color: #228B22">// Main function begins here</span>
|
|
<span style="color: #00688B; font-weight: bold">int</span> <span style="color: #008b45">main</span>(<span style="color: #00688B; font-weight: bold">int</span> argc, <span style="color: #00688B; font-weight: bold">char</span> * argv[]){
|
|
<span style="color: #00688B; font-weight: bold">int</span> dim = <span style="color: #B452CD">2</span>;
|
|
Vector x(dim),xsd(dim), b(dim),x0(dim);
|
|
Matrix A(dim,dim);
|
|
|
|
<span style="color: #228B22">// Set our initial guess</span>
|
|
x0(<span style="color: #B452CD">0</span>) = x0(<span style="color: #B452CD">1</span>) = <span style="color: #B452CD">0</span>;
|
|
<span style="color: #228B22">// Set the matrix</span>
|
|
A(<span style="color: #B452CD">0</span>,<span style="color: #B452CD">0</span>) = <span style="color: #B452CD">3</span>; A(<span style="color: #B452CD">1</span>,<span style="color: #B452CD">0</span>) = <span style="color: #B452CD">2</span>; A(<span style="color: #B452CD">0</span>,<span style="color: #B452CD">1</span>) = <span style="color: #B452CD">2</span>; A(<span style="color: #B452CD">1</span>,<span style="color: #B452CD">1</span>) = <span style="color: #B452CD">6</span>;
|
|
b(<span style="color: #B452CD">0</span>) = <span style="color: #B452CD">2</span>; b(<span style="color: #B452CD">1</span>) = -<span style="color: #B452CD">8</span>;
|
|
cout << <span style="color: #CD5555">"The Matrix A that we are using: "</span> << endl;
|
|
A.Print();
|
|
cout << endl;
|
|
xsd = SteepestDescent(A,b,x0);
|
|
cout << <span style="color: #CD5555">"The approximate solution using Steepest Descent is: "</span> << endl;
|
|
xsd.Print();
|
|
cout << endl;
|
|
}
|
|
</pre></div>
|
|
|
|
</div>
|
|
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec25">The routine for the steepest descent method </h2>
|
|
<div class="alert alert-block alert-block alert-text-normal">
|
|
<b></b>
|
|
<p>
|
|
<p>
|
|
|
|
<!-- code=c++ (!bc cppcod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eee8d5"><pre style="line-height: 125%"><span></span>Vector <span style="color: #008b45">SteepestDescent</span>(Matrix A, Vector b, Vector x0){
|
|
<span style="color: #00688B; font-weight: bold">int</span> IterMax, i;
|
|
<span style="color: #00688B; font-weight: bold">int</span> dim = x0.Dimension();
|
|
<span style="color: #8B008B; font-weight: bold">const</span> <span style="color: #00688B; font-weight: bold">double</span> tolerance = <span style="color: #B452CD">1.0e-14</span>;
|
|
Vector x(dim),f(dim),z(dim);
|
|
<span style="color: #00688B; font-weight: bold">double</span> c,alpha,d;
|
|
IterMax = <span style="color: #B452CD">30</span>;
|
|
x = x0;
|
|
f = A*x-b;
|
|
i = <span style="color: #B452CD">0</span>;
|
|
<span style="color: #8B008B; font-weight: bold">while</span> (i <= IterMax){
|
|
z = A*f;
|
|
c = dot(f,f);
|
|
alpha = c/dot(f,z);
|
|
x = x - alpha*f;
|
|
f = A*x-b;
|
|
<span style="color: #8B008B; font-weight: bold">if</span>(sqrt(dot(f,f)) < tolerance) <span style="color: #8B008B; font-weight: bold">break</span>;
|
|
i++;
|
|
}
|
|
<span style="color: #8B008B; font-weight: bold">return</span> x;
|
|
}
|
|
</pre></div>
|
|
|
|
</div>
|
|
|
|
|
|
<p>
|
|
<!-- !split -->
|
|
|
|
<h2 id="___sec26">Revisiting our first homework </h2>
|
|
|
|
<p>
|
|
We will use linear regression as a case study for the gradient descent
|
|
methods. Linear regression is a great test case for the gradient
|
|
descent methods discussed in the lectures since it has several
|
|
desirable properties such as:
|
|
|
|
<ol>
|
|
<li> An analytical solution (recall homework set 1).</li>
|
|
<li> The gradient can be computed analytically.</li>
|
|
<li> The cost function is convex which guarantees that gradient descent converges for small enough learning rates</li>
|
|
</ol>
|
|
|
|
We revisit the example from homework set 1 where we had
|
|
$$
|
|
y_i = 5x_i^2 + 0.1\xi_i, \ i=1,\cdots,100
|
|
$$
|
|
|
|
with \( x_i \in [0,1] \) chosen randomly with a uniform distribution. Additionally \( \xi_i \) represents stochastic noise chosen according to a normal distribution \( \cal {N}(0,1) \).
|
|
The linear regression model is given by
|
|
$$
|
|
h_\beta(x) = \hat{y} = \beta_0 + \beta_1 x,
|
|
$$
|
|
|
|
such that
|
|
$$
|
|
\hat{y}_i = \beta_0 + \beta_1 x_i.
|
|
$$
|
|
|
|
<p>
|
|
<!-- !split -->
|
|
|
|
<h2 id="___sec27">Gradient descent example </h2>
|
|
|
|
<p>
|
|
Let \( \mathbf{y} = (y_1,\cdots,y_n)^T \), \( \mathbf{\hat{y}} = (\hat{y}_1,\cdots,\hat{y}_n)^T \) and \( \beta = (\beta_0, \beta_1)^T \)
|
|
|
|
<p>
|
|
It is convenient to write \( \mathbf{\hat{y}} = X\beta \) where \( X \in \mathbb{R}^{100 \times 2} \) is the design matrix given by
|
|
$$
|
|
X \equiv \begin{bmatrix}
|
|
1 & x_1 \\
|
|
\vdots & \vdots \\
|
|
1 & x_{100} & \\
|
|
\end{bmatrix}.
|
|
$$
|
|
|
|
The loss function is given by
|
|
$$
|
|
C(\beta) = ||X\beta-\mathbf{y}||^2 = ||X\beta||^2 - 2 \mathbf{y}^T X\beta + ||\mathbf{y}||^2 = \sum_{i=1}^{100} (\beta_0 + \beta_1 x_i)^2 - 2 y_i (\beta_0 + \beta_1 x_i) + y_i^2
|
|
$$
|
|
|
|
and we want to find \( \beta \) such that \( C(\beta) \) is minimized.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec28">The derivative of the cost/loss function </h2>
|
|
|
|
<p>
|
|
Computing \( \partial C(\beta) / \partial \beta_0 \) and \( \partial C(\beta) / \partial \beta_1 \) we can show that the gradient can be written as
|
|
$$
|
|
\nabla_{\beta} C(\beta) = (\partial C(\beta) / \partial \beta_0, \partial C(\beta) / \partial \beta_1)^T = 2\begin{bmatrix} \sum_{i=1}^{100} \left(\beta_0+\beta_1x_i-y_i\right) \\
|
|
\sum_{i=1}^{100}\left( x_i (\beta_0+\beta_1x_i)-y_ix_i\right) \\
|
|
\end{bmatrix} = 2X^T(X\beta - \mathbf{y}),
|
|
$$
|
|
|
|
where \( X \) is the design matrix defined above.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec29">The Hessian matrix </h2>
|
|
The Hessian matrix of \( C(\beta) \) is given by
|
|
$$
|
|
\hat{H} \equiv \begin{bmatrix}
|
|
\frac{\partial^2 C(\beta)}{\partial \beta_0^2} & \frac{\partial^2 C(\beta)}{\partial \beta_0 \partial \beta_1} \\
|
|
\frac{\partial^2 C(\beta)}{\partial \beta_0 \partial \beta_1} & \frac{\partial^2 C(\beta)}{\partial \beta_1^2} & \\
|
|
\end{bmatrix} = 2X^T X.
|
|
$$
|
|
|
|
This result implies that \( C(\beta) \) is a convex function since the matrix \( X^T X \) always is positive semi-definite.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec30">Simple program </h2>
|
|
|
|
<p>
|
|
We can now write a program that minimizes \( C(\beta) \) using the gradient descent method with a constant learning rate \( \gamma \) according to
|
|
$$
|
|
\beta_{k+1} = \beta_k - \gamma \nabla_\beta C(\beta_k), \ k=0,1,\cdots
|
|
$$
|
|
|
|
<p>
|
|
We can use the expression we computed for the gradient and let use a
|
|
\( \beta_0 \) be chosen randomly and let \( \gamma = 0.001 \). Stop iterating
|
|
when \( ||\nabla_\beta C(\beta_k) || \leq \epsilon = 10^{-8} \).
|
|
|
|
<p>
|
|
And finally we can compare our solution for \( \beta \) with the analytic result given by
|
|
\( \beta= (X^TX)^{-1} X^T \mathbf{y} \).
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="line-height: 125%"><span></span><span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">numpy</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">np</span>
|
|
|
|
<span style="color: #CD5555">"""</span>
|
|
<span style="color: #CD5555">The following setup is just a suggestion, feel free to write it the way you like.</span>
|
|
<span style="color: #CD5555">"""</span>
|
|
|
|
<span style="color: #228B22">#Setup problem described in the exercise</span>
|
|
N = <span style="color: #B452CD">100</span> <span style="color: #228B22">#Nr of datapoints</span>
|
|
M = <span style="color: #B452CD">2</span> <span style="color: #228B22">#Nr of features</span>
|
|
x = np.random.rand(N) <span style="color: #228B22">#Uniformly generated x-values in [0,1]</span>
|
|
y = <span style="color: #B452CD">5</span>*x**<span style="color: #B452CD">2</span> + <span style="color: #B452CD">0.1</span>*np.random.randn(N)
|
|
X = np.c_[np.ones(N),x] <span style="color: #228B22">#Construct design matrix</span>
|
|
|
|
<span style="color: #228B22">#Compute beta according to normal equations to compare with GD solution</span>
|
|
Xt_X_inv = np.linalg.inv(np.dot(X.T,X))
|
|
Xt_y = np.dot(X.transpose(),y)
|
|
beta_NE = np.dot(Xt_X_inv,Xt_y)
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(beta_NE)
|
|
</pre></div>
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec31">Gradient Descent Example </h2>
|
|
|
|
<p>
|
|
Another simple example is here
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="line-height: 125%"><span></span><span style="color: #228B22"># Importing various packages</span>
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">random</span> <span style="color: #8B008B; font-weight: bold">import</span> random, seed
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">numpy</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">np</span>
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">matplotlib.pyplot</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">plt</span>
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">mpl_toolkits.mplot3d</span> <span style="color: #8B008B; font-weight: bold">import</span> Axes3D
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">matplotlib</span> <span style="color: #8B008B; font-weight: bold">import</span> cm
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">matplotlib.ticker</span> <span style="color: #8B008B; font-weight: bold">import</span> LinearLocator, FormatStrFormatter
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">sys</span>
|
|
|
|
x = <span style="color: #B452CD">2</span>*np.random.rand(<span style="color: #B452CD">100</span>,<span style="color: #B452CD">1</span>)
|
|
y = <span style="color: #B452CD">4</span>+<span style="color: #B452CD">3</span>*x+np.random.randn(<span style="color: #B452CD">100</span>,<span style="color: #B452CD">1</span>)
|
|
|
|
xb = np.c_[np.ones((<span style="color: #B452CD">100</span>,<span style="color: #B452CD">1</span>)), x]
|
|
beta_linreg = np.linalg.inv(xb.T.dot(xb)).dot(xb.T).dot(y)
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(beta_linreg)
|
|
beta = np.random.randn(<span style="color: #B452CD">2</span>,<span style="color: #B452CD">1</span>)
|
|
|
|
eta = <span style="color: #B452CD">0.1</span>
|
|
Niterations = <span style="color: #B452CD">1000</span>
|
|
m = <span style="color: #B452CD">100</span>
|
|
|
|
<span style="color: #8B008B; font-weight: bold">for</span> <span style="color: #658b00">iter</span> <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(Niterations):
|
|
gradients = <span style="color: #B452CD">2.0</span>/m*xb.T.dot(xb.dot(beta)-y)
|
|
beta -= eta*gradients
|
|
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(beta)
|
|
xnew = np.array([[<span style="color: #B452CD">0</span>],[<span style="color: #B452CD">2</span>]])
|
|
xbnew = np.c_[np.ones((<span style="color: #B452CD">2</span>,<span style="color: #B452CD">1</span>)), xnew]
|
|
ypredict = xbnew.dot(beta)
|
|
ypredict2 = xbnew.dot(beta_linreg)
|
|
plt.plot(xnew, ypredict, <span style="color: #CD5555">"r-"</span>)
|
|
plt.plot(xnew, ypredict2, <span style="color: #CD5555">"b-"</span>)
|
|
plt.plot(x, y ,<span style="color: #CD5555">'ro'</span>)
|
|
plt.axis([<span style="color: #B452CD">0</span>,<span style="color: #B452CD">2.0</span>,<span style="color: #B452CD">0</span>, <span style="color: #B452CD">15.0</span>])
|
|
plt.xlabel(<span style="color: #CD5555">r'$x$'</span>)
|
|
plt.ylabel(<span style="color: #CD5555">r'$y$'</span>)
|
|
plt.title(<span style="color: #CD5555">r'Gradient descent example'</span>)
|
|
plt.show()
|
|
</pre></div>
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec32">And a corresponding example using <b>scikit-learn</b> </h2>
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="line-height: 125%"><span></span><span style="color: #228B22"># Importing various packages</span>
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">random</span> <span style="color: #8B008B; font-weight: bold">import</span> random, seed
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">numpy</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">np</span>
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">matplotlib.pyplot</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">plt</span>
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">sklearn.linear_model</span> <span style="color: #8B008B; font-weight: bold">import</span> SGDRegressor
|
|
|
|
x = <span style="color: #B452CD">2</span>*np.random.rand(<span style="color: #B452CD">100</span>,<span style="color: #B452CD">1</span>)
|
|
y = <span style="color: #B452CD">4</span>+<span style="color: #B452CD">3</span>*x+np.random.randn(<span style="color: #B452CD">100</span>,<span style="color: #B452CD">1</span>)
|
|
|
|
xb = np.c_[np.ones((<span style="color: #B452CD">100</span>,<span style="color: #B452CD">1</span>)), x]
|
|
beta_linreg = np.linalg.inv(xb.T.dot(xb)).dot(xb.T).dot(y)
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(beta_linreg)
|
|
sgdreg = SGDRegressor(n_iter = <span style="color: #B452CD">50</span>, penalty=<span style="color: #658b00">None</span>, eta0=<span style="color: #B452CD">0.1</span>)
|
|
sgdreg.fit(x,y.ravel())
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(sgdreg.intercept_, sgdreg.coef_)
|
|
</pre></div>
|
|
<p>
|
|
<!-- !split -->
|
|
|
|
<h2 id="___sec33">Gradient descent and Ridge </h2>
|
|
|
|
<p>
|
|
We have also discussed Ridge regression where the loss function contains a regularized given by the \( L_2 \) norm of \( \beta \),
|
|
$$
|
|
C_{\text{ridge}}(\beta) = ||X\beta -\mathbf{y}||^2 + \lambda ||\beta||^2, \ \lambda \geq 0.
|
|
$$
|
|
|
|
<p>
|
|
In order to minimize \( C_{\text{ridge}}(\beta) \) using GD we only have adjust the gradient as follows
|
|
$$
|
|
\nabla_\beta C_{\text{ridge}}(\beta) = 2\begin{bmatrix} \sum_{i=1}^{100} \left(\beta_0+\beta_1x_i-y_i\right) \\
|
|
\sum_{i=1}^{100}\left( x_i (\beta_0+\beta_1x_i)-y_ix_i\right) \\
|
|
\end{bmatrix} + 2\lambda\begin{bmatrix} \beta_0 \\ \beta_1\end{bmatrix} = 2 (X^T(X\beta - \mathbf{y})+\lambda \beta).
|
|
$$
|
|
|
|
<p>
|
|
We can now extend our program to minimize \( C_{\text{ridge}}(\beta) \) using gradient descent and compare with the analytical solution given by
|
|
$$
|
|
\beta_{\text{ridge}} = \left(X^T X + \lambda I_{2 \times 2} \right)^{-1} X^T \mathbf{y},
|
|
$$
|
|
|
|
for \( \lambda = {0,1,10,50,100} \) (\( \lambda = 0 \) corresponds to ordinary least squares).
|
|
We can then compute \( ||\beta_{\text{ridge}}|| \) for each \( \lambda \).
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="line-height: 125%"><span></span><span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">numpy</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">np</span>
|
|
|
|
<span style="color: #CD5555">"""</span>
|
|
<span style="color: #CD5555">The following setup is just a suggestion, feel free to write it the way you like.</span>
|
|
<span style="color: #CD5555">"""</span>
|
|
|
|
<span style="color: #228B22">#Setup problem described in the exercise</span>
|
|
N = <span style="color: #B452CD">100</span> <span style="color: #228B22">#Nr of datapoints</span>
|
|
M = <span style="color: #B452CD">2</span> <span style="color: #228B22">#Nr of features</span>
|
|
x = np.random.rand(N)
|
|
y = <span style="color: #B452CD">5</span>*x**<span style="color: #B452CD">2</span> + <span style="color: #B452CD">0.1</span>*np.random.randn(N)
|
|
|
|
|
|
<span style="color: #228B22">#Compute analytic beta for Ridge regression </span>
|
|
X = np.c_[np.ones(N),x]
|
|
XT_X = np.dot(X.T,X)
|
|
|
|
l = <span style="color: #B452CD">0.1</span> <span style="color: #228B22">#Ridge parameter lambda</span>
|
|
Id = np.eye(XT_X.shape[<span style="color: #B452CD">0</span>])
|
|
|
|
Z = np.linalg.inv(XT_X+l*Id)
|
|
beta_ridge = np.dot(Z,np.dot(X.T,y))
|
|
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(beta_ridge)
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(np.linalg.norm(beta_ridge)) <span style="color: #228B22">#||beta||</span>
|
|
</pre></div>
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec34">Stochastic Gradient Descent </h2>
|
|
|
|
<p>
|
|
Stochastic gradient descent (SGD) and variants thereof address some of
|
|
the shortcomings of the Gradient descent method discussed above.
|
|
|
|
<p>
|
|
The underlying idea of SGD comes from the observation that the cost
|
|
function, which we want to minimize, can almost always be written as a
|
|
sum over \( n \) data points \( \{\mathbf{x}_i\}_{i=1}^n \),
|
|
$$
|
|
C(\mathbf{\beta}) = \sum_{i=1}^n c_i(\mathbf{x}_i,
|
|
\mathbf{\beta}).
|
|
$$
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec35">Computation of gradients </h2>
|
|
|
|
<p>
|
|
This in turn means that the gradient can be
|
|
computed as a sum over \( i \)-gradients
|
|
$$
|
|
\nabla_\beta C(\mathbf{\beta}) = \sum_i^n \nabla_\beta c_i(\mathbf{x}_i,
|
|
\mathbf{\beta}).
|
|
$$
|
|
|
|
<p>
|
|
Stochasticity/randomness is introduced by only taking the
|
|
gradient on a subset of the data called minibatches. If there are \( n \)
|
|
data points and the size of each minibatch is \( M \), there will be \( n/M \)
|
|
minibatches. We denote these minibatches by \( B_k \) where
|
|
\( k=1,\cdots,n/M \).
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec36">SGD example </h2>
|
|
As an example, suppose we have \( 10 \) data points \( (\mathbf{x}_1,\cdots, \mathbf{x}_{10}) \)
|
|
and we choose to have \( M=5 \) minibathces,
|
|
then each minibatch contains two data points. In particular we have
|
|
\( B_1 = (\mathbf{x}_1,\mathbf{x}_2), \cdots, B_5 =
|
|
(\mathbf{x}_9,\mathbf{x}_{10}) \). Note that if you choose \( M=1 \) you
|
|
have only a single batch with all data points and on the other extreme,
|
|
you may choose \( M=n \) resulting in a minibatch for each datapoint, i.e
|
|
\( B_k = \mathbf{x}_k \).
|
|
|
|
<p>
|
|
The idea is now to approximate the gradient by replacing the sum over
|
|
all data points with a sum over the data points in one the minibatches
|
|
picked at random in each gradient descent step
|
|
$$
|
|
\nabla_{\beta}
|
|
C(\mathbf{\beta}) = \sum_{i=1}^n \nabla_\beta c_i(\mathbf{x}_i,
|
|
\mathbf{\beta}) \rightarrow \sum_{i \in B_k}^n \nabla_\beta
|
|
c_i(\mathbf{x}_i, \mathbf{\beta}).
|
|
$$
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec37">The gradient step </h2>
|
|
|
|
<p>
|
|
Thus a gradient descent step now looks like
|
|
$$
|
|
\beta_{j+1} = \beta_j - \gamma_j \sum_{i \in B_k}^n \nabla_\beta c_i(\mathbf{x}_i,
|
|
\mathbf{\beta})
|
|
$$
|
|
|
|
<p>
|
|
where \( k \) is picked at random with equal
|
|
probability from \( [1,n/M] \). An iteration over the number of
|
|
minibathces (n/M) is commonly referred to as an epoch. Thus it is
|
|
typical to choose a number of epochs and for each epoch iterate over
|
|
the number of minibatches, as exemplified in the code below.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec38">Simple example code </h2>
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="line-height: 125%"><span></span><span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">numpy</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">np</span>
|
|
|
|
n = <span style="color: #B452CD">100</span> <span style="color: #228B22">#100 datapoints </span>
|
|
M = <span style="color: #B452CD">5</span> <span style="color: #228B22">#size of each minibatch</span>
|
|
m = <span style="color: #658b00">int</span>(n/M) <span style="color: #228B22">#number of minibatches</span>
|
|
n_epochs = <span style="color: #B452CD">10</span> <span style="color: #228B22">#number of epochs</span>
|
|
|
|
j = <span style="color: #B452CD">0</span>
|
|
<span style="color: #8B008B; font-weight: bold">for</span> epoch <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(<span style="color: #B452CD">1</span>,n_epochs+<span style="color: #B452CD">1</span>):
|
|
<span style="color: #8B008B; font-weight: bold">for</span> i <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(m):
|
|
k = np.random.randint(m) <span style="color: #228B22">#Pick the k-th minibatch at random</span>
|
|
<span style="color: #228B22">#Compute the gradient using the data in minibatch Bk</span>
|
|
<span style="color: #228B22">#Compute new suggestion for </span>
|
|
j += <span style="color: #B452CD">1</span>
|
|
</pre></div>
|
|
<p>
|
|
Taking the gradient only on a subset of the data has two important
|
|
benefits. First, it introduces randomness which decreases the chance
|
|
that our opmization scheme gets stuck in a local minima. Second, if
|
|
the size of the minibatches are small relative to the number of
|
|
datapoints (\( M < n \)), the computation of the gradient is much
|
|
cheaper since we sum over the datapoints in the \( k-th \) minibatch and not
|
|
all \( n \) datapoints.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec39">When do we stop? </h2>
|
|
|
|
<p>
|
|
A natural question is when do we stop the search for a new minimum?
|
|
One possibility is to compute the full gradient after a given number
|
|
of epochs and check if the norm of the gradient is smaller than some
|
|
threshold and stop if true. However, the condition that the gradient
|
|
is zero is valid also for local minima, so this would only tell us
|
|
that we are close to a local/global minimum. However, we could also
|
|
evaluate the cost function at this point, store the result and
|
|
continue the search. If the test kicks in at a later stage we can
|
|
compare the values of the cost function and keep the \( \beta \) that
|
|
gave the lowest value.
|
|
|
|
<p>
|
|
<!-- !split --><br><br><br><br><br><br><br><br><br><br>
|
|
|
|
<h2 id="___sec40">Slightly different approach </h2>
|
|
|
|
<p>
|
|
Another approach is to let the step length \( \gamma_j \) depend on the
|
|
number of epochs in such a way that it becomes very small after a
|
|
reasonable time such that we do not move at all.
|
|
|
|
<p>
|
|
As an example, let \( e = 0,1,2,3,\cdots \) denote the current epoch and let \( t_0, t_1 > 0 \) be two fixed numbers. Furthermore, let \( t = e \cdot m + i \) where \( m \) is the number of minibatches and \( i=0,\cdots,m-1 \). Then the function $$\gamma_j(t; t_0, t_1) = \frac{t_0}{t+t_1} $$ goes to zero as the number of epochs gets large. I.e. we start with a step length \( \gamma_j (0; t_0, t_1) = t_0/t_1 \) which decays in <em>time</em> \( t \).
|
|
|
|
<p>
|
|
In this way we can fix the number of epochs, compute \( \beta \) and
|
|
evaluate the cost function at the end. Repeating the computation will
|
|
give a different result since the scheme is random by design. Then we
|
|
pick the final \( \beta \) that gives the lowest value of the cost
|
|
function.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="line-height: 125%"><span></span><span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">numpy</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">np</span>
|
|
|
|
<span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">step_length</span>(t,t0,t1):
|
|
<span style="color: #8B008B; font-weight: bold">return</span> t0/(t+t1)
|
|
|
|
n = <span style="color: #B452CD">100</span> <span style="color: #228B22">#100 datapoints </span>
|
|
M = <span style="color: #B452CD">5</span> <span style="color: #228B22">#size of each minibatch</span>
|
|
m = <span style="color: #658b00">int</span>(n/M) <span style="color: #228B22">#number of minibatches</span>
|
|
n_epochs = <span style="color: #B452CD">500</span> <span style="color: #228B22">#number of epochs</span>
|
|
t0 = <span style="color: #B452CD">1.0</span>
|
|
t1 = <span style="color: #B452CD">10</span>
|
|
|
|
gamma_j = t0/t1
|
|
j = <span style="color: #B452CD">0</span>
|
|
<span style="color: #8B008B; font-weight: bold">for</span> epoch <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(<span style="color: #B452CD">1</span>,n_epochs+<span style="color: #B452CD">1</span>):
|
|
<span style="color: #8B008B; font-weight: bold">for</span> i <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(m):
|
|
k = np.random.randint(m) <span style="color: #228B22">#Pick the k-th minibatch at random</span>
|
|
<span style="color: #228B22">#Compute the gradient using the data in minibatch Bk</span>
|
|
<span style="color: #228B22">#Compute new suggestion for beta</span>
|
|
t = epoch*m+i
|
|
gamma_j = step_length(t,t0,t1)
|
|
j += <span style="color: #B452CD">1</span>
|
|
|
|
<span style="color: #8B008B; font-weight: bold">print</span>(<span style="color: #CD5555">"gamma_j after %d epochs: %g"</span> % (n_epochs,gamma_j))
|
|
</pre></div>
|
|
<p>
|
|
|
|
<!-- ------------------- end of main content --------------- -->
|
|
|
|
|
|
<center style="font-size:80%">
|
|
<!-- copyright --> © 1999-2018, Morten Hjorth-Jensen. Released under CC Attribution-NonCommercial 4.0 license
|
|
</center>
|
|
|
|
|
|
</body>
|
|
</html>
|
|
|
|
|