<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Stephen V. Brown, Ph. D.</title>
<link>https://www.stephenvbrown.com/blog/</link>
<atom:link href="https://www.stephenvbrown.com/blog/index.xml" rel="self" type="application/rss+xml"/>
<description>Personal website of Stephen V. Brown</description>
<image>
<url>https://www.stephenvbrown.com/images/portrait.jpg</url>
<title>Stephen V. Brown, Ph. D.</title>
<link>https://www.stephenvbrown.com/blog/</link>
</image>
<generator>quarto-1.6.39</generator>
<lastBuildDate>Wed, 18 Dec 2024 05:00:00 GMT</lastBuildDate>
<item>
  <title>Connecting OLS and Deep Learning</title>
  <link>https://www.stephenvbrown.com/blog/posts/ols-deep-learning/</link>
  <description><![CDATA[ 





<p>I’ve talked with a number of people who have a strong background in statistical analysis and use linear and nonlinear regression models on a regular basis, but who haven’t had the opportunity to use neural networks yet. My area of focus is Natural Language Processing (NLP)—the analysis of text—so my research strongly depends on deep learning models. But if you’re mostly doing hypothesis testing and the like, deep learning likely has less relevance in your daily work.</p>
<p>With this article, I hope to show that if you’re familiar with linear regression models, such as Ordinary Least Squares (OLS), you’re already familiar with some of the key aspects of deep learning models. At the very least, I hope to provide a good bridge to let you explore some of the basics of deep learning.</p>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Warning
</div>
</div>
<div class="callout-body-container callout-body">
<p>I want to be clear that I’m not suggesting you should use neural networks or deep learning over OLS or some other traditional regression model, because they’re intended for very different purposes. They are not really substitutes for most situations. For example, OLS is much more interpretable and is appropriate for hypothesis testing, something neural networks are not generally suited for. Neural networks excel at making predictions with the greatest possible accuracy.</p>
</div>
</div>
<p>First, we’ll generate some very simple linear data and then fit an OLS model to that data. I’ll then show how we can fit that model numerically instead of using the usual OLS closed-form solution. That will allow us to draw comparisons to neural networks and then connect neural networks to deep learning. Along the way I’ll show you the smallest bit of Python code needed to fit each model. Each step is quite small on its own, so hopefully by the end you’ll see the tight connections between all these methods.</p>
<section id="sample-data" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="sample-data">Sample Data</h2>
<p>First, let’s create a simple dataset. We’ll keep it in two dimensions to make it easier to visualize, but essentially everything I discuss in this article extends immediately to higher dimensions.</p>
<p>For this data, we’ll use the function <img src="https://latex.codecogs.com/png.latex?y%20=%20x"> as our data generating process. We’ll choose 100 values for <img src="https://latex.codecogs.com/png.latex?x">, where each value will be a random number between zero and ten. <img src="https://latex.codecogs.com/png.latex?y"> will be the same as <img src="https://latex.codecogs.com/png.latex?x"> but with some noise added so that it will approximate a straight line (<img src="https://latex.codecogs.com/png.latex?y%20=%20x%20+%20%5Cepsilon">). Due to our choice of <img src="https://latex.codecogs.com/png.latex?x"> and <img src="https://latex.codecogs.com/png.latex?y">, any models we fit to this data should tell us that the data is from the function <img src="https://latex.codecogs.com/png.latex?y%20=%20x">.</p>
<p>Using <a href="https://numpy.org/">NumPy</a> to generate the data:</p>
<div id="b3e811ad" class="cell" data-execution_count="2">
<div class="sourceCode cell-code" id="annotated-cell-1" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="annotated-cell-1-2"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1">1</button><span id="annotated-cell-1-3" class="code-annotation-target">rng <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.random.default_rng(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>)</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="2">2</button><span id="annotated-cell-1-4" class="code-annotation-target">x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.random((<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="3">3</button><span id="annotated-cell-1-5" class="code-annotation-target">noise <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> rng.normal(loc<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, scale<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.5</span>, size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>x.shape)</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="4">4</button><span id="annotated-cell-1-6" class="code-annotation-target">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> noise</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="3" data-code-annotation="1">ensure we get the same dataset every time</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="4" data-code-annotation="2"><code>x</code> is a vector of random numbers in [0, 10)</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="5" data-code-annotation="3">random noise from the normal distribution</span>
</dd>
<dt data-target-cell="annotated-cell-1" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="6" data-code-annotation="4">observed <img src="https://latex.codecogs.com/png.latex?y"> is approximately equal to <img src="https://latex.codecogs.com/png.latex?x">; true relationship is <img src="https://latex.codecogs.com/png.latex?y%20=%20x"></span>
</dd>
</dl>
</div>
</div>
<p>If we plot these points and add the line <img src="https://latex.codecogs.com/png.latex?y%20=%20x">, we can see that the data is approximately linear:</p>
<div id="3577faae" class="cell page-columns page-full" data-execution_count="3">

<div class="no-row-height column-margin column-container"><div class="cell-output cell-output-display" data-execution_count="2">
<div>


<table class="dataframe caption-top table table-sm table-striped small" data-quarto-postprocess="true" data-border="1">
<thead>
<tr class="header">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">x</th>
<th data-quarto-table-cell-role="th">y</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td data-quarto-table-cell-role="th">0</td>
<td>7.739560</td>
<td>7.939448</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">1</td>
<td>4.388784</td>
<td>3.936045</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">2</td>
<td>8.585979</td>
<td>8.396898</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">3</td>
<td>6.973680</td>
<td>7.623294</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">4</td>
<td>0.941773</td>
<td>0.763641</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">...</td>
<td>...</td>
<td>...</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">95</td>
<td>6.302826</td>
<td>7.033548</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">96</td>
<td>3.618126</td>
<td>3.064603</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">97</td>
<td>0.876499</td>
<td>0.429136</td>
</tr>
<tr class="even">
<td data-quarto-table-cell-role="th">98</td>
<td>1.180059</td>
<td>1.501722</td>
</tr>
<tr class="odd">
<td data-quarto-table-cell-role="th">99</td>
<td>9.618977</td>
<td>9.421674</td>
</tr>
</tbody>
</table>

<p>100 rows × 2 columns</p>
</div>
</div></div></div>
<div id="b9ae25ec" class="cell" data-execution_count="4">
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://www.stephenvbrown.com/blog/posts/ols-deep-learning/index_files/figure-html/cell-4-output-1.png" width="585" height="429" class="figure-img"></p>
</figure>
</div>
</div>
</div>
</section>
<section id="ordinary-least-squares-ols" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="ordinary-least-squares-ols">Ordinary Least Squares (OLS)</h2>
<p><em>For this section, I assume a basic understanding of OLS regression.</em></p>
<p>As a reminder, a linear regression model with one independent variable (<img src="https://latex.codecogs.com/png.latex?x">) and one dependent variable (<img src="https://latex.codecogs.com/png.latex?y">) can be written as:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Ay%20=%20%5Cbeta_0%20+%20%5Cbeta_1%20x%20+%20%5Cepsilon%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cbeta_0"> is the intercept, <img src="https://latex.codecogs.com/png.latex?%5Cbeta_1"> is the coefficient for <img src="https://latex.codecogs.com/png.latex?x">, and <img src="https://latex.codecogs.com/png.latex?%5Cepsilon"> is the error term, or residual.</p>
<p>Two key features of OLS models are that they’re linear and that they minimize the sum of squared residuals. In other words, the model is trying to find the straight line that minimizes the sum of the squared differences between the observed <img src="https://latex.codecogs.com/png.latex?y"> values and the predicted <img src="https://latex.codecogs.com/png.latex?y"> values—the <em>mean-squared error</em> (MSE).</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7BMSE%7D%20=%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum_%7Bi=1%7D%5E%7Bn%7D%20(y_i%20-%20%5Chat%7By%7D_i)%5E2%0A"></p>
<p>With OLS, we can easily solve this using a closed-form solution that is guaranteed to be the best possible solution:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Chat%7B%5Cbeta%7D%20=%20(x%5ETx)%5E%7B-1%7Dx%5ETy%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Chat%7B%5Cbeta%7D"> is the vector of fitted coefficients (<img src="https://latex.codecogs.com/png.latex?%5Cbeta_0"> and <img src="https://latex.codecogs.com/png.latex?%5Cbeta_1"> in our example) and <img src="https://latex.codecogs.com/png.latex?x"> and <img src="https://latex.codecogs.com/png.latex?y"> are our data vectors.</p>
<p>Now, we’ll fit a standard OLS model to the data:</p>
<div id="3fed7bf8" class="cell" data-execution_count="5">
<div class="sourceCode cell-code" id="annotated-cell-2" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> statsmodels.api <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> sm</span>
<span id="annotated-cell-2-2"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-2" data-target-annotation="1">1</button><span id="annotated-cell-2-3" class="code-annotation-target">x_with_const <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sm.add_constant(x)</span>
<span id="annotated-cell-2-4">ols_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sm.OLS(y, x_with_const).fit()</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-2" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-2" data-code-lines="3" data-code-annotation="1">add a constant term for the intercept</span>
</dd>
</dl>
</div>
</div>
<div id="fe3d623e" class="cell" data-execution_count="6">
<div class="cell-output cell-output-stdout">
<pre><code>                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.969
Model:                            OLS   Adj. R-squared:                  0.969
No. Observations:                 100   F-statistic:                     3053.
Covariance Type:            nonrobust   Prob (F-statistic):           1.14e-75
==============================================================================
                 coef    std err          t      P&gt;|t|      [0.025      0.975]
------------------------------------------------------------------------------
const         -0.0257      0.101     -0.253      0.801      -0.227       0.175
x1             1.0038      0.018     55.257      0.000       0.968       1.040
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.</code></pre>
</div>
</div>
<p>Note that the intercept coefficient is close to zero and the slope coefficient is close to one, which nearly matches our initial data generating process of <img src="https://latex.codecogs.com/png.latex?y%20=%20x">.<sup>1</sup> The fitted line basically overlaps our line for <img src="https://latex.codecogs.com/png.latex?y%20=%20x">, indicating the OLS model is fitting the data well:</p>
<div class="no-row-height column-margin column-container"><div id="fn1"><p><sup>1</sup>&nbsp;The numbers are not exactly zero and one due to randomness in the data generation; they will approach the expected values as the sample size increases.</p></div></div><div id="fa10b14e" class="cell" data-execution_count="7">
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://www.stephenvbrown.com/blog/posts/ols-deep-learning/index_files/figure-html/cell-7-output-1.png" width="585" height="429" class="figure-img"></p>
</figure>
</div>
</div>
</div>
<p>If we used the same data to fit the model again, we’d get the same result because we’re using the closed-form solution, which is guaranteed to be the best possible solution.<sup>2</sup></p>
<div class="no-row-height column-margin column-container"><div id="fn2"><p><sup>2</sup>&nbsp;The only potential source of variation is the inverse of the <img src="https://latex.codecogs.com/png.latex?x%5ETx"> matrix since that is not usually the true inverse. However, the method for calculating this is usually stable within libraries (almost always the Moore-Penrose pseduoinverse).</p></div></div></section>
<section id="sec-sgd" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="sec-sgd">Stochastic Gradient Descent (SGD)</h2>
<p>Rather than the typical OLS approach, we could instead fit the model numerically using an iterative method. This approach could look something like this:</p>
<ol type="1">
<li>Pick a random value for <img src="https://latex.codecogs.com/png.latex?%5Cbeta_0"> and <img src="https://latex.codecogs.com/png.latex?%5Cbeta_1">.</li>
<li>Calculate the mean-squared error for the predicted values (<img src="https://latex.codecogs.com/png.latex?%5Chat%7By%7D">) and the true values (<img src="https://latex.codecogs.com/png.latex?y">).</li>
<li>Pick another value for <img src="https://latex.codecogs.com/png.latex?%5Cbeta_0"> and <img src="https://latex.codecogs.com/png.latex?%5Cbeta_1"> and see if it’s better. If so, keep that one; otherwise, use the original one.</li>
<li>Repeat steps 1-3 N times or until the model converges.</li>
</ol>
<p>This is a brute force approach. We could get clever and try to pick our values for <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> more strategically depending on whether they seem to improve as we move in one direction or the other. There are a variety of algorithms for doing this—optimizing values numerically is an entire subfield of academic research.</p>
<p>One such approach is called Stochastic Gradient Descent (SGD). The <em>gradient</em> is the derivative of our loss function (MSE) with respect to the coefficient and intercept. It tells us the direction and magnitude of the greatest increase in the loss function. We can use the gradient to give us a more informed guess about how we should choose the new <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> values to improve the model fit.<sup>3</sup></p>
<div class="no-row-height column-margin column-container"><div id="fn3"><p><sup>3</sup>&nbsp;The gradient (<img src="https://latex.codecogs.com/png.latex?%5Cnabla">) of the function <img src="https://latex.codecogs.com/png.latex?f(%5Ccdot)"> with respect to the parameters <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> is the vector of partial derivatives of <img src="https://latex.codecogs.com/png.latex?f"> with respect to each model parameter: <img src="https://latex.codecogs.com/png.latex?%5Cnabla_%7B%5Cbeta%7D%20f(%5Cbeta)%20=%0A%5Cleft(%20%5Cfrac%7B%5Cpartial%20f%7D%7B%5Cpartial%20%5Cbeta_0%7D,%20%5Cfrac%7B%5Cpartial%20f%7D%7B%5Cpartial%20%5Cbeta_1%7D%20%5Cright)"></p></div></div><p>Without getting into the precise details of SGD, we can use it to fit a linear model by numerically optimizing the coefficients and intercept:</p>
<div id="db6e4d98" class="cell" data-execution_count="8">
<div class="sourceCode cell-code" id="annotated-cell-3" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sklearn.linear_model <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> SGDRegressor</span>
<span id="annotated-cell-3-2"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-3" data-target-annotation="1">1</button><span id="annotated-cell-3-3" class="code-annotation-target">sgd_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SGDRegressor(loss<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"squared_error"</span>, penalty<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)</span>
<span id="annotated-cell-3-4">sgd_model.fit(x, y.ravel())</span>
<span id="annotated-cell-3-5">sgd_coefficent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sgd_model.coef_[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="annotated-cell-3-6">sgd_intercept <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sgd_model.intercept_[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-3" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-3" data-code-lines="3" data-code-annotation="1"><code>loss="squared_error"</code> is the mean-squared error loss function (this is the default loss, so this arg is not necessary); <code>penalty=None</code> means no L1/L2 regularization to make it closer to what OLS does</span>
</dd>
</dl>
</div>
</div>
<div id="49fbbc56" class="cell" data-execution_count="9">
<div class="cell-output cell-output-stdout">
<pre><code>sgd_coefficient=0.9816
sgd_intercept=0.0730</code></pre>
</div>
</div>
<p>We’re still using the MSE loss function, although we do have other options now. We can also regularize the coefficients, but we’ve turned that off here (<code>penalty=None</code>) to make it closer to OLS.<sup>4</sup> In the end, we have a well-fitting linear model that serves the same purpose as OLS but gets us there in a different way.</p>
<div class="no-row-height column-margin column-container"><div id="fn4"><p><sup>4</sup>&nbsp;Setting <code>penalty=l1</code> performs a Lasso regression and <code>penalty=l2</code> performs a Ridge regression.</p></div></div><p>One difference with OLS is that every time we fit the SGD linear model, we’ll get a different coefficient and intercept. The randomness in this case is due to the input data being randomized before fitting. The takeaway here is that where you begin the numerical optimization matters. It matters so much that you may end up in a local optimum, rather than the global one. Getting stuck like this isn’t possible with OLS.</p>
<section id="what-sgd-loses-versus-ols" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="what-sgd-loses-versus-ols">What SGD Loses Versus OLS</h3>
<p>OLS has interpretable t-statistics and p-values for each coefficient, which can tell us how confident we are that the coefficient is not zero. We have options for this with SGD, such as bootstrapping, but we don’t get it “out of the box.”</p>
<p>Unless we set the <code>random_state</code> parameter of <code>SGDRegressor()</code> to a constant number, we also get some instability in the results. Even if we do prevent the randomness, OLS is a deterministic, guaranteed-best solution, while SGD is only an approximation.</p>
<p>On the other hand, the coefficients and intercept are still interpretable as a straight line. If we do get some sort of confidence interval, we can also make claims about the significance of the coefficients. Overall, we haven’t lost much compared to OLS, although we may have caused ourselves a bit more work.<sup>5</sup></p>
<div class="no-row-height column-margin column-container"><div id="fn5"><p><sup>5</sup>&nbsp;We also need to choose various <em>hyperparameters</em> for SGD, such as the learning rate and number of iterations.</p></div></div></section>
<section id="what-sgd-gains-versus-ols" class="level3">
<h3 class="anchored" data-anchor-id="what-sgd-gains-versus-ols">What SGD Gains Versus OLS</h3>
<p>So is there any reason to choose SGD over OLS?</p>
<p>The short answer is that if OLS works for you, then no. But SGD gives us much more flexibility. For example:</p>
<ol type="1">
<li>We could regularize the parameters to encourage sparsity in our coefficients without switching to a different model. Regularization can also be helpful in addressing multicollinearity.</li>
<li>As an iterative method, SGD can process data in batches, so it will likely have an easier time fitting the linear model if the data is too big to fit into our machine’s memory. Realistically, simple linear models are not going to be frequently used in situations like this though.</li>
<li>Related to #2, we can add more data later and “update” the model without having to start over from scratch.</li>
</ol>
</section>
</section>
<section id="neural-network" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="neural-network">Neural Network</h2>
<p>So far we have used two techniques—OLS from classical statistics and SGD from machine learning—to fit a linear regression model. So how does this connect to deep learning and neural networks? A neural network is a series of linear models. Each linear model is called a <em>layer</em> and each layer is connected to the one before it and after it in the chain. These connections are called <em>weights</em> (what we’ve been calling coefficients) and <em>biases</em> (what we’ve been calling intercepts).</p>
<p>The first layer is called the <em>input layer</em> and the last layer is called the <em>output layer</em>. Any layers in between those two are called <em>hidden layers</em>. We must have an input layer and an output layer, but hidden layers are optional. If we have quite a few hidden layers, we call the neural network a “deep” neural network, thus the term <em>deep learning</em>.<sup>6</sup></p>
<div class="no-row-height column-margin column-container"><div id="fn6"><p><sup>6</sup>&nbsp;“Quite a few” depends on context and who you ask, but this probably means at least 3-5 layers but could mean hundreds or thousands. Even within deep learning models, some have many layers and parameters, others have far fewer.</p></div></div><p>For our simple linear model, we have one input layer (<img src="https://latex.codecogs.com/png.latex?x">), one output layer (<img src="https://latex.codecogs.com/png.latex?y">), and no hidden layers. In general, each layer can take vectors and matrices as inputs and outputs, but our simple example just has a one-dimensional vector (a <em>scalar</em>) for both the input and output layers. If x were instead a vector (<strong>x</strong>), like we would see with multivariate regression, we would have the more general linear model that we frequently see when using OLS:</p>
<div class="cell" data-layout-align="center">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    x["**x**"] --&gt;|bias| y["y"]
    x --&gt;|weight 1| y
    x --&gt;|weight 2| y
    x --&gt;|...| y
    x --&gt;|weight n| y
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>With OLS, we have a closed-form, deterministic solution, although we saw earlier that stochastic gradient descent can do something similar numerically, albeit with some randomness and other potential drawbacks. Unfortunately we no longer have a closed form solution to solve the general representation used by neural networks. On the bright side, since we have to fit the model using optimization techniques anyway, we can make a vast number of changes to the model architecture with little additional penalty.</p>
<p>The optimal weights and biases for the neural network will be solved iteratively through a process called <em>training</em>. I’m not going to get into the specific details of how to train neural networks, but the general procedure is to:</p>
<ol type="1">
<li>Initialize the weights (coefficients) and biases (intercept) to some random values.</li>
<li>Pass the data through the network to get the predicted values using the current weights and biases (the <em>forward pass</em>).</li>
<li>Calculate the difference between the predicted values and the true values. OLS calls this the <em>error</em> or <em>residual</em>; it’s called the <em>loss</em> when training neural networks. We calculate it using a <em>loss function</em>, which is often the mean-squared error.</li>
<li>Use the loss to determine how we can adjust the weights and biases to hopefully improve the model fit. We can use the SGD method from Section&nbsp;3 to do a lot of the heavy lifting for us by using the loss function as the input for the SGD optimizer (begins the <em>backward pass</em>).</li>
<li>Update the weights and biases in the direction that minimizes the loss.</li>
<li>Repeat steps 2-5 N times or until the model converges. Each pass through the data is called an <em>epoch</em>. We will typically set some maximum number of epochs to prevent the model from training forever, with the possibility of stopping early if the model fit is no longer improving.</li>
</ol>
<p>This training loop is implicit with the <code>SGDRegressor()</code> function in the previous section; for neural networks, we’ll need to build it ourselves for maximum control and flexibility. In python code, the iterative training loop looks something like this for our sample data:</p>
<div id="a4bc22ea" class="cell" data-execution_count="10">
<div class="sourceCode cell-code" id="annotated-cell-4" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch</span>
<span id="annotated-cell-4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> torch.optim.sgd <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> SGD</span>
<span id="annotated-cell-4-3"></span>
<span id="annotated-cell-4-4">nn_model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.nn.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1 linear layer with 1 input and 1 output</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1">1</button><span id="annotated-cell-4-5" class="code-annotation-target">calculate_mse <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.nn.MSELoss()</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2">2</button><span id="annotated-cell-4-6" class="code-annotation-target">sgd_optimizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> SGD(nn_model.parameters(), lr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.01</span>)</span>
<span id="annotated-cell-4-7"></span>
<span id="annotated-cell-4-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># convert numpy arrays to pytorch tensors</span></span>
<span id="annotated-cell-4-9">x_tensor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.from_numpy(x).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>()</span>
<span id="annotated-cell-4-10">y_tensor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.from_numpy(y).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>()</span>
<span id="annotated-cell-4-11"></span>
<span id="annotated-cell-4-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># training loop</span></span>
<span id="annotated-cell-4-13">NUM_EPOCHS <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span></span>
<span id="annotated-cell-4-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(NUM_EPOCHS):</span>
<span id="annotated-cell-4-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># forward pass to calculate the loss</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="3">3</button><span id="annotated-cell-4-16" class="code-annotation-target">    outputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn_model.forward(x_tensor)</span>
<span id="annotated-cell-4-17">    loss <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> calculate_mse(outputs, y_tensor)</span>
<span id="annotated-cell-4-18"></span>
<span id="annotated-cell-4-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># backward pass to update weights/biases to try to improve next epoch</span></span>
<span id="annotated-cell-4-20">    sgd_optimizer.zero_grad()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># prepare to run backward pass</span></span>
<span id="annotated-cell-4-21">    loss.backward()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># compute gradient</span></span>
<span id="annotated-cell-4-22">    sgd_optimizer.step()  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># use gradient to update weights</span></span>
<span id="annotated-cell-4-23"></span>
<span id="annotated-cell-4-24">nn_weight <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn_model.weight.item()</span>
<span id="annotated-cell-4-25">nn_bias <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn_model.bias.item()</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="5" data-code-annotation="1">loss function is mean-squared error</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="6" data-code-annotation="2">optimizer is stochastic gradient descent</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="16" data-code-annotation="3"><code>nn_model.forward(x)</code> is equivalent to <code>nn_model(x)</code>; you may see either</span>
</dd>
</dl>
</div>
</div>
<div id="01c96161" class="cell" data-execution_count="11">
<div class="cell-output cell-output-stdout">
<pre><code>nn_weight=1.0026
nn_bias=-0.0179</code></pre>
</div>
</div>
<p>Once again we see our weight/coefficient is close to one and the bias/intercept is close to zero, as expected. OLS, SGD, and neural networks all give us approximately the same result when fitting a linear model to our simple dataset:</p>
<div id="0b2a8a74" class="cell" data-execution_count="12">
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><img src="https://www.stephenvbrown.com/blog/posts/ols-deep-learning/index_files/figure-html/cell-12-output-1.png" width="585" height="429" class="figure-img"></p>
</figure>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled" title="Simplicity Matters">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Simplicity Matters
</div>
</div>
<div class="callout-body-container callout-body">
<p>We should always choose the simplest effective model. Researchers frequently favor interpretability over accuracy so they can effectively test hypotheses, leading them to choose OLS. Simpler models also help reduce overfitting. But if raw accuracy is paramount—often the case when making predictions—or we know we have highly nonlinear data, neural networks give us extreme flexibility.</p>
</div>
</div>
</section>
<section id="deep-learning" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="deep-learning">Deep Learning</h2>
<p>The next question is how to get from our simple neural network to practical deep learning models. At a basic level a deep neural network means a neural network with many hidden layers. But there are many considerations and options when building a usable deep learning model.</p>
<section id="activation-functions" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="activation-functions">Activation Functions</h3>
<p>Our first instinct might be to add more layers to our current neural network since having “many layers” is a prerequisite for deep learning. But it turns out that these mathematically collapse down to a single linear layer. For this to work, we need to add in some sort of nonlinearity to our model. We can do this with a nonlinear <em>activation function</em>, which sits between the hidden layers and takes the linear output of one hidden layer and transforms it into a nonlinear input for the next layer.<sup>7</sup> Some activation functions work better than others; commonly used options are: <sup>8</sup></p>
<div class="no-row-height column-margin column-container"><div id="fn7"><p><sup>7</sup>&nbsp;The <em>Universal Approximation Theorem</em> says that a neural network can approximate any continuous function with the use of hidden layers and nonlinear activation functions. For example, a logistic regression looks similar to our linear regression model but with a sigmoid activation function.</p></div><div id="fn8"><p><sup>8</sup>&nbsp;Research is ongoing regarding optimal activation function choice, but some version of the ReLU is commonly used these days</p></div></div><ol type="1">
<li>Sigmoid: <img src="https://latex.codecogs.com/png.latex?%5Csigma(x)%20=%20%5Cfrac%7B1%7D%7B1%20+%20e%5E%7B-x%7D%7D"></li>
<li>Hyperbolic Tangent: <img src="https://latex.codecogs.com/png.latex?%5Ctanh(x)%20=%20%5Cfrac%7Be%5Ex%20-%20e%5E%7B-x%7D%7D%7Be%5Ex%20+%20e%5E%7B-x%7D%7D"></li>
<li>Rectified Linear Unit (ReLU): <img src="https://latex.codecogs.com/png.latex?relu(x)%20=%20%5Cmax(0,%20x)"></li>
</ol>
</section>
<section id="an-abundance-of-choice" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="an-abundance-of-choice">An Abundance of Choice</h3>
<p>In addition to choosing an activation function, we need to make some other decisions, which is where the art of working with deep learning models comes in. For example:</p>
<ul>
<li><strong>Model Size</strong> How many hidden layers should we have and how many weights should each layer have? A high number of <em>parameters</em> (# weights + # biases) could overfit the data, but too few will underfit—fortunately, there are many techniques for handling overfitting. Large models can take considerably longer and take more resources to train and use.</li>
<li><strong>Training Procedure</strong> We need to tune the training loop by choosing, for example:
<ol type="1">
<li>optimizer algorithm (SGD is only one option) and related parameters, such as learning rate</li>
<li>loss function</li>
<li>number of epochs</li>
<li>batching</li>
<li>early stopping rules</li>
</ol></li>
<li><strong>Training Data</strong> How should we structure the training data? We usually have some sort of train-test split, but there are many options for splitting the data.<sup>9</sup> Do we have enough of the right type of data and how do we know that?</li>
<li><strong>Model Architecture</strong> Real-world models often use a more complex structure than what I’ve presented here. The following architectures are common, with new options being developed all the time:
<ol type="1">
<li><strong><em>Convolutional Neural Networks (CNNs)</em></strong></li>
<li><strong><em>Recurrent Neural Networks (RNNs)</em></strong></li>
<li><strong><em>Long Short-Term Memory (LSTM) networks</em></strong></li>
<li><strong><em>Transformer networks</em></strong>: commonly used in Natural Language Processing (NLP)</li>
</ol></li>
</ul>
<div class="no-row-height column-margin column-container"><div id="fn9"><p><sup>9</sup>&nbsp;The training set is used to fit the model, the test set is used to evaluate the model fit. However, terminology varies wildly and can be used inconsistently between people.</p></div></div></section>
<section id="classification" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="classification">Classification</h3>
<p>So far, I’ve mainly discussed regression, where we try to fit/predict a continuous output variable. I’ve mostly avoided references to classification, which assigns an observation to one of two or more categories.<sup>10</sup> A great deal of deep learning is actually focused on classification, including in the natural language processing (NLP) field.</p>
<div class="no-row-height column-margin column-container"><div id="fn10"><p><sup>10</sup>&nbsp;A common example from statistics is logistic regression, which supports two classes. It uses the sigmoid function as it’s “activation function” so we could easily replicate it with our neural network.</p></div><div id="fn11"><p><sup>11</sup>&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Ctext%7Bsoftmax%7D(x_i)%20=%20%5Cfrac%7Be%5E%7Bx_i%7D%7D%7B%5Csum_%7Bj=1%7D%5E%7Bn%7D%20e%5E%7Bx_j%7D%7D"></p></div><div id="fn12"><p><sup>12</sup>&nbsp;The outputs are all between 0 and 1, and they collectively sum to 1.</p></div></div><p>Not only can the inputs to a neural network be a vector—as they are in multivariate regression—but the outputs can be a vector as well. For classification, we can use this ability to have one output per class we’re interested in predicting. Then we’ll use the softmax function <sup>11</sup> to convert the output of the neural network into a set of numbers we can interpret as probabilities.<sup>12</sup> We will then predict the class as the one with the highest number. How close the number is to 1 gives us an idea about how confident the model is in its prediction.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>I didn’t even scratch the surface of deep learning in this article, but if you’ve been curious about deep learning and haven’t yet taken the plunge, I hope this article has helped you see that you already have a foundation to start from if you know a bit about basic statistics.</p>
<p>Next steps for you might include:</p>
<ul>
<li><strong>Books</strong>:
<ul>
<li><em><a href="https://www.deeplearningillustrated.com/">Deep Learning Illustrated</a></em> by Jon Krohn</li>
<li><em><a href="https://www.deeplearningbook.org/">Deep Learning</a></em> by Ian Goodfellow, Yoshua Bengio, and Aaron Courville</li>
</ul></li>
<li><strong>Videos</strong>:
<ul>
<li><a href="https://www.youtube.com/watch?v=aircAruvnKk&amp;list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi">3Blue1Brown’s Neural Networks Series</a></li>
<li><a href="https://www.youtube.com/@AndrejKarpathy">Andrej Karpathy</a></li>
</ul></li>
</ul>


</section>


<a onclick="window.scrollTo(0, 0); return false;" id="quarto-back-to-top"><i class="bi bi-arrow-up"></i> Back to top</a> ]]></description>
  <category>Deep Learning</category>
  <guid>https://www.stephenvbrown.com/blog/posts/ols-deep-learning/</guid>
  <pubDate>Wed, 18 Dec 2024 05:00:00 GMT</pubDate>
  <media:content url="https://www.stephenvbrown.com/blog/posts/ols-deep-learning/index_files/figure-html/cell-7-output-1.png" medium="image" type="image/png" height="106" width="144"/>
</item>
</channel>
</rss>
