Python实现简单的梯度下降法
<h1>Python 实现简单的梯度下降法</h1><p>机器学习算法常常可以归结为求解一个最优化问题,而梯度下降法就是求解最优化问题的一个方法。</p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>梯度下降法</strong></span>(gradient descent)或<strong>最速下降法</strong>(steepest decent),是求解<strong>无约束最优化问题</strong>的一种最常用的方法。</p>
<p>梯度下降法实现简单,是一种迭代算法,<span style="color: rgba(255, 0, 0, 1)"><strong>每一步会求解目标函数的梯度向量</strong></span>。</p>
<p>本文分为理论和 Python 代码实践,希望实现<strong>简单的梯度下降法</strong>,相关代码已放在 GitHub 中。</p>
<h2>理论</h2>
<h3>问题定义</h3>
<p>那么什么是目标函数,在机器学习中这常常是一个损失函数。不管怎么称呼,它就是一个函数 $f(x)$,<strong>而梯度下降法的目的就是获取这个函数的极小值</strong>。</p>
<p>下面给出一个较为正式的问题定义。</p>
<blockquote>
<p>假设 $f(x)$ 是 $R^n$ 上具有一阶连续偏导数的函数。需要求解的无约束最优化问题是:</p>
<p>$$\underset{x\in R^n}{min}f(x)$$</p>
<p>即需要求出目标函数 $f(x)$ 的极小点 $x^*$。</p>
</blockquote>
<h3>算法思想和推导</h3>
<p>要理解梯度下降法,首先要理解<strong>梯度</strong>和<strong>负梯度</strong>的概念。</p>
<p><strong>梯度是从 n 维推广出来的概念,类似于斜率。梯度的本意是一个向量,表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)</strong>。具体定义和公式可以参考百度定义。</p>
<p>举个例子再体会一下梯度是表示方向的一个向量:</p>
<p>对于函数 $f(x_1,x_2)=2x_1^3-x_2^2$ 来说,它的梯度就是 $g(x_1,x_2)=$。对于给定点 $$ 的附近处,它在 $$ 方向变化率最大,而其负梯度方向就是 $[-6x_1^2,2x_2]$。例如,在点 $$ 附近处,它的负梯度方向就是 $[-24, -6]$。在此处,点 $$ 向这个方向移动,会使得 $f(x_1,x_2)=2x_1^3-x_2^2$ 值减小的速率最快。反之,如果点 $$ 向梯度方向 $$ 移动,会使得 $f(x_1,x_2)=2x_1^3-x_2^2$ 值增加的速率最快。</p>
<p> </p>
<p>理解了梯度之后,其实就可以很容易推导出梯度下降法的算法过程了。</p>
<p><strong>梯度下降法的思想,就是选取适当的初值 $x_{0}$,不断迭代更新 $x$ 的值,极小化目标函数,最终收敛</strong>。</p>
<p>由于负梯度方向是使函数值下降最快的方向,<span style="color: rgba(255, 0, 0, 1)"><strong>因此梯度下降在每一步采用负梯度方向更新 $x$ 的值</strong></span>,最终达到函数值最小。</p>
<p>可以看出,梯度下降法采用的是贪心的思想。</p>
<p>根据一阶泰勒展开,当 $x$ 趋近于 $x_k$ 时:</p>
<p>$$f(x)\approx f(x_k)+g_{k}(x-x_k)$$</p>
<p>这里,$g_k=g(x_k)=\bigtriangledown f(x_k)$ 是 $f(x)$ 在 $x_k$ 的梯度。</p>
<p>我们假设设定了一个初始值 $x_0$,现在需要确定一个 $x_1$,代入上式可得:</p>
<p>$$f(x_1)\approx f(x_0)+g_{0}(x_1-x_0)$$</p>
<p>假设 $x_1$ 和 $x_0$ 之间的距离一定时,为了让 $f(x_1)$ 最小(贪心策略),应该有:</p>
<p>$$g_{0}(x_1-x_0)=\left | g_{0} \right | \left | x_1-x_0 \right |cos\theta =-\left | g_{0} \right | \left | x_1-x_0 \right |$$</p>
<p><strong>也就是需要让 $x_1-x_0$ 和梯度 $g_{0}$ 的夹角 $\theta$ 为 180°,使得 $cos\theta =-1$。换言之,$x_1-x_0$ 和梯度 $g_{0}$ 方向相反。</strong></p>
<p>由于 $x_1-x_0=-\frac{g_0}{\left | g_0 \right |}\left | x_1-x_0 \right |$,那么可以得到:</p>
<p>$$x_1=x_0-\frac{g_0}{\left | g_0 \right |}\left | x_1-x_0 \right |=x_0-g_0\lambda_0$$</p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>其中 $\lambda_0=\frac{\left | x_1-x_0 \right |}{\left | g_0 \right |}$ 定义为学习率,它实际上步长除以梯度的模</strong>。<strong>因此当学习率一定时,步长其实是一直变化的。当梯度较大时,步长也较大;而当梯度较小时,步长也较小</strong></span>。这往往是我们希望的性质,因为当接近于局部最优解时,梯度变得较小,这时往往也需要步长变得更小,以利于找到局部最优解。</p>
<p>同理,我们可以得到 $x_2=x_1-g_1\lambda_1$ ,依次类推,有:</p>
<p>$$x_{k+1}=x_k-g_k\lambda_k$$</p>
<p>其中,学习率<strong> $\lambda_k$ 要足够小</strong>,使得:</p>
<ol>
<li>满足泰勒公式所需要的精度。</li>
<li>能够很好地捕捉到极小值。</li>
</ol>
<p>这是一个显式表达式,可以不断求出 $x_{k+1}$,当满足收敛条件时(如梯度足够小或者 $x_{k+1}$ 更新变化量足够小),退出迭代,此时 $f(x_{k+1})$ 就是一个求解出来的最小函数值。</p>
<p>至此完成了梯度下降法逻辑上的推导。 </p>
<h2>Python 代码实现</h2>
<p>理论已经足够多了,接下来敲一敲实在的代码吧。</p>
<h3>一维问题</h3>
<p>假设我们需要求解的目标函数是:</p>
<p>$$f(x)=x^2+1$$</p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1046925/201906/1046925-20190629211500124-1871874583.png"></p>
<p>显然一眼就知道它的最小值是 $x=0$ 处,但是这里我们需要用梯度下降法的 Python 代码来实现。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">!/usr/bin/env python</span>
<span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> -*- coding: utf-8 -*-</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(128, 0, 0, 1)">一维问题的梯度下降法示例
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span>
<span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> func_1d(x):
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(128, 0, 0, 1)"> 目标函数
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(128, 0, 0, 1)"> :param x: 自变量,标量
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(128, 0, 0, 1)"> :return: 因变量,标量
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 255, 1)">return</span> x ** 2 + 1
<span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span>
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> grad_1d(x):
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(128, 0, 0, 1)"> 目标函数的梯度
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(128, 0, 0, 1)"> :param x: 自变量,标量
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(128, 0, 0, 1)"> :return: 因变量,标量
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 255, 1)">return</span> x * 2
<span style="color: rgba(0, 128, 128, 1)">24</span>
<span style="color: rgba(0, 128, 128, 1)">25</span>
<span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 255, 1)">def</span> gradient_descent_1d(grad, cur_x=0.1, learning_rate=0.01, precision=0.0001, max_iters=10000<span style="color: rgba(0, 0, 0, 1)">):
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(128, 0, 0, 1)"> 一维问题的梯度下降法
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(128, 0, 0, 1)"> :param grad: 目标函数的梯度
</span><span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(128, 0, 0, 1)"> :param cur_x: 当前 x 值,通过参数可以提供初始值
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(128, 0, 0, 1)"> :param learning_rate: 学习率,也相当于设置的步长
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(128, 0, 0, 1)"> :param precision: 设置收敛精度
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(128, 0, 0, 1)"> :param max_iters: 最大迭代次数
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(128, 0, 0, 1)"> :return: 局部最小值 x*
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(0, 0, 255, 1)">for</span> i <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> range(max_iters):
</span><span style="color: rgba(0, 128, 128, 1)">37</span> grad_cur =<span style="color: rgba(0, 0, 0, 1)"> grad(cur_x)
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 255, 1)">if</span> abs(grad_cur) <<span style="color: rgba(0, 0, 0, 1)"> precision:
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 当梯度趋近为 0 时,视为收敛</span>
<span style="color: rgba(0, 128, 128, 1)">40</span> cur_x = cur_x - grad_cur *<span style="color: rgba(0, 0, 0, 1)"> learning_rate
</span><span style="color: rgba(0, 128, 128, 1)">41</span> <span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">第</span><span style="color: rgba(128, 0, 0, 1)">"</span>, i, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">次迭代:x 值为 </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, cur_x)
</span><span style="color: rgba(0, 128, 128, 1)">42</span>
<span style="color: rgba(0, 128, 128, 1)">43</span> <span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">局部最小值 x =</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, cur_x)
</span><span style="color: rgba(0, 128, 128, 1)">44</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> cur_x
</span><span style="color: rgba(0, 128, 128, 1)">45</span>
<span style="color: rgba(0, 128, 128, 1)">46</span>
<span style="color: rgba(0, 128, 128, 1)">47</span> <span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(128, 0, 128, 1)">__name__</span> == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">__main__</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 128, 128, 1)">48</span> gradient_descent_1d(grad_1d, cur_x=10, learning_rate=0.2, precision=0.000001, max_iters=10000)</pre>
</div>
<p>其输出结果如下:</p>
<div class="cnblogs_code">
<pre>第 0 次迭代:x 值为6.0<span>
第 1 次迭代:x 值为3.5999999999999996<span>
第 2 次迭代:x 值为2.1599999999999997<span>
第 3 次迭代:x 值为1.2959999999999998<span>
第 4 次迭代:x 值为0.7775999999999998<span>
第 5 次迭代:x 值为0.46655999999999986<span>
第 6 次迭代:x 值为0.2799359999999999<span>
第 7 次迭代:x 值为0.16796159999999993<span>
第 8 次迭代:x 值为0.10077695999999996<span>
第 9 次迭代:x 值为0.06046617599999997<span>
第 10 次迭代:x 值为0.036279705599999976<span>
第 11 次迭代:x 值为0.021767823359999987<span>
第 12 次迭代:x 值为0.013060694015999992<span>
第 13 次迭代:x 值为0.007836416409599995<span>
第 14 次迭代:x 值为0.004701849845759997<span>
第 15 次迭代:x 值为0.002821109907455998<span>
第 16 次迭代:x 值为0.0016926659444735988<span>
第 17 次迭代:x 值为0.0010155995666841593<span>
第 18 次迭代:x 值为0.0006093597400104956<span>
第 19 次迭代:x 值为0.0003656158440062973<span>
第 20 次迭代:x 值为0.0002193695064037784<span>
第 21 次迭代:x 值为0.00013162170384226703<span>
第 22 次迭代:x 值为7.897302230536021e-05<span>
第 23 次迭代:x 值为4.7383813383216124e-05<span>
第 24 次迭代:x 值为2.8430288029929674e-05<span>
第 25 次迭代:x 值为1.7058172817957805e-05<span>
第 26 次迭代:x 值为1.0234903690774682e-05<span>
第 27 次迭代:x 值为6.1409422144648085e-06<span>
第 28 次迭代:x 值为3.684565328678885e-06<span>
第 29 次迭代:x 值为2.210739197207331e-06<span>
第 30 次迭代:x 值为1.3264435183243986e-06<span>
第 31 次迭代:x 值为7.958661109946391e-07<span>
第 32 次迭代:x 值为4.775196665967835e-07<span>
局部最小值 x = 4.775196665967835e-07</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<h3>二维问题</h3>
<p>接下来推广到二维,目标函数设为:</p>
<p>$$f(x,y) = -e^{-(x^2 + y^2)}$$</p>
<p> </p>
<p style="text-align: center"><img src="https://img2018.cnblogs.com/blog/1046925/201906/1046925-20190629232822055-37516335.png"></p>
<p>该函数在 $$ 处有最小值。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">!/usr/bin/env python</span>
<span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> -*- coding: utf-8 -*-</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(128, 0, 0, 1)">二维问题的梯度下降法示例
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> math
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> numpy as np
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> func_2d(x):
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(128, 0, 0, 1)"> 目标函数
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(128, 0, 0, 1)"> :param x: 自变量,二维向量
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(128, 0, 0, 1)"> :return: 因变量,标量
</span><span style="color: rgba(0, 128, 128, 1)">15</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 255, 1)">return</span> - math.exp(-(x ** 2 + x ** 2<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 128, 128, 1)">17</span>
<span style="color: rgba(0, 128, 128, 1)">18</span>
<span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> grad_2d(x):
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(128, 0, 0, 1)"> 目标函数的梯度
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(128, 0, 0, 1)"> :param x: 自变量,二维向量
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(128, 0, 0, 1)"> :return: 因变量,二维向量
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">25</span> deriv0 = 2 * x * math.exp(-(x ** 2 + x ** 2<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 128, 128, 1)">26</span> deriv1 = 2 * x * math.exp(-(x ** 2 + x ** 2<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> np.array()
</span><span style="color: rgba(0, 128, 128, 1)">28</span>
<span style="color: rgba(0, 128, 128, 1)">29</span>
<span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 255, 1)">def</span> gradient_descent_2d(grad, cur_x=np.array(), learning_rate=0.01, precision=0.0001, max_iters=10000<span style="color: rgba(0, 0, 0, 1)">):
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(128, 0, 0, 1)"> 二维问题的梯度下降法
</span><span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(128, 0, 0, 1)"> :param grad: 目标函数的梯度
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(128, 0, 0, 1)"> :param cur_x: 当前 x 值,通过参数可以提供初始值
</span><span style="color: rgba(0, 128, 128, 1)">35</span> <span style="color: rgba(128, 0, 0, 1)"> :param learning_rate: 学习率,也相当于设置的步长
</span><span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(128, 0, 0, 1)"> :param precision: 设置收敛精度
</span><span style="color: rgba(0, 128, 128, 1)">37</span> <span style="color: rgba(128, 0, 0, 1)"> :param max_iters: 最大迭代次数
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(128, 0, 0, 1)"> :return: 局部最小值 x*
</span><span style="color: rgba(0, 128, 128, 1)">39</span> <span style="color: rgba(128, 0, 0, 1)">"""</span>
<span style="color: rgba(0, 128, 128, 1)">40</span> <span style="color: rgba(0, 0, 255, 1)">print</span>(f<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{cur_x} 作为初始值开始迭代...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">41</span> <span style="color: rgba(0, 0, 255, 1)">for</span> i <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> range(max_iters):
</span><span style="color: rgba(0, 128, 128, 1)">42</span> grad_cur =<span style="color: rgba(0, 0, 0, 1)"> grad(cur_x)
</span><span style="color: rgba(0, 128, 128, 1)">43</span> <span style="color: rgba(0, 0, 255, 1)">if</span> np.linalg.norm(grad_cur, ord=2) <<span style="color: rgba(0, 0, 0, 1)"> precision:
</span><span style="color: rgba(0, 128, 128, 1)">44</span> <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 当梯度趋近为 0 时,视为收敛</span>
<span style="color: rgba(0, 128, 128, 1)">45</span> cur_x = cur_x - grad_cur *<span style="color: rgba(0, 0, 0, 1)"> learning_rate
</span><span style="color: rgba(0, 128, 128, 1)">46</span> <span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">第</span><span style="color: rgba(128, 0, 0, 1)">"</span>, i, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">次迭代:x 值为 </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, cur_x)
</span><span style="color: rgba(0, 128, 128, 1)">47</span>
<span style="color: rgba(0, 128, 128, 1)">48</span> <span style="color: rgba(0, 0, 255, 1)">print</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">局部最小值 x =</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, cur_x)
</span><span style="color: rgba(0, 128, 128, 1)">49</span> <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> cur_x
</span><span style="color: rgba(0, 128, 128, 1)">50</span>
<span style="color: rgba(0, 128, 128, 1)">51</span>
<span style="color: rgba(0, 128, 128, 1)">52</span> <span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(128, 0, 128, 1)">__name__</span> == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">__main__</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 128, 128, 1)">53</span> gradient_descent_2d(grad_2d, cur_x=np.array(), learning_rate=0.2, precision=0.000001, max_iters=10000<span style="color: rgba(0, 0, 0, 1)">)</span><span style="color: rgba(0, 128, 0, 1)"><br></span></pre>
</div>
<p>$x_0$ 的初始值设为 $$ ,运行后的结果如下:</p>
<div class="cnblogs_code">
<pre>[ 1 -1<span>] 作为初始值开始迭代...
第 0 次迭代:x 值为[ 0.94586589 -0.94586589<span>]
第 1 次迭代:x 值为[ 0.88265443 -0.88265443<span>]
第 2 次迭代:x 值为[ 0.80832661 -0.80832661<span>]
第 3 次迭代:x 值为[ 0.72080448 -0.72080448<span>]
第 4 次迭代:x 值为[ 0.61880589 -0.61880589<span>]
第 5 次迭代:x 值为[ 0.50372222 -0.50372222<span>]
第 6 次迭代:x 值为[ 0.3824228 -0.3824228<span>]
第 7 次迭代:x 值为[ 0.26824673 -0.26824673<span>]
第 8 次迭代:x 值为[ 0.17532999 -0.17532999<span>]
第 9 次迭代:x 值为[ 0.10937992 -0.10937992<span>]
第 10 次迭代:x 值为[ 0.06666242 -0.06666242<span>]
第 11 次迭代:x 值为[ 0.04023339 -0.04023339<span>]
第 12 次迭代:x 值为[ 0.02419205 -0.02419205<span>]
第 13 次迭代:x 值为[ 0.01452655 -0.01452655<span>]
第 14 次迭代:x 值为[ 0.00871838 -0.00871838<span>]
第 15 次迭代:x 值为[ 0.00523156 -0.00523156<span>]
第 16 次迭代:x 值为[ 0.00313905 -0.00313905<span>]
第 17 次迭代:x 值为[ 0.00188346 -0.00188346<span>]
第 18 次迭代:x 值为[ 0.00113008 -0.00113008<span>]
第 19 次迭代:x 值为[ 0.00067805 -0.00067805<span>]
第 20 次迭代:x 值为[ 0.00040683 -0.00040683<span>]
第 21 次迭代:x 值为[ 0.0002441 -0.0002441<span>]
第 22 次迭代:x 值为[ 0.00014646 -0.00014646<span>]
第 23 次迭代:x 值为[ 8.78751305e-05 -8.78751305e-05<span>]
第 24 次迭代:x 值为[ 5.27250788e-05 -5.27250788e-05<span>]
第 25 次迭代:x 值为[ 3.16350474e-05 -3.16350474e-05<span>]
第 26 次迭代:x 值为[ 1.89810285e-05 -1.89810285e-05<span>]
第 27 次迭代:x 值为[ 1.13886171e-05 -1.13886171e-05<span>]
第 28 次迭代:x 值为[ 6.83317026e-06 -6.83317026e-06<span>]
第 29 次迭代:x 值为[ 4.09990215e-06 -4.09990215e-06<span>]
第 30 次迭代:x 值为[ 2.45994129e-06 -2.45994129e-06<span>]
第 31 次迭代:x 值为[ 1.47596478e-06 -1.47596478e-06<span>]
第 32 次迭代:x 值为[ 8.85578865e-07 -8.85578865e-07<span>]
第 33 次迭代:x 值为[ 5.31347319e-07 -5.31347319e-07<span>]
第 34 次迭代:x 值为[ 3.18808392e-07 -3.18808392e-07<span>]
局部最小值 x = [ 3.18808392e-07 -3.18808392e-07]</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p>我们再试着以初始值 $$ 处开始寻找最小值,即:</p>
<div class="cnblogs_code">
<pre>gradient_descent_2d(grad_2d, cur_x=np.array(), learning_rate=0.2, precision=0.000001, max_iters=10000)</pre>
</div>
<p>结果可能出乎人意料:</p>
<div class="cnblogs_code">
<p>[ 3 -3] 作为初始值开始迭代...<br>局部最小值 x = [ 3 -3]</p>
</div>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>梯度下降法没有找到真正的极小值点!</strong></span></p>
<p>如果仔细观察目标函数的图像,以及梯度下降法的算法原理,你就很容易发现问题所在了。<span style="color: rgba(255, 0, 0, 1)"><strong>在 $$ 处的梯度就几乎为 0 了!</strong></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">print</span>(grad_2d(np.array()))</pre>
</div>
<div class="cnblogs_code">
<pre>[ 9.13798785e-08 -9.13798785e-08]</pre>
</div>
<p><strong>由于“梯度过小”,梯度下降法可能无法确定前进的方向了</strong>。即使人为增加收敛条件中的精度,也会由于梯度过小,导致迭代中前进的步长距离过小,循环时间过长。</p>
<h2>梯度下降法的局限性</h2>
<p>梯度下降法实现简单,原理也易于理解,但它有自身的局限性,因此有了后面很多算法对它的改进。</p>
<p>对于梯度过小的情况,梯度下降法可能难以求解。</p>
<p>此外,梯度下降法适合求解只有一个局部最优解的目标函数,对于存在多个局部最优解的目标函数,一般情况下梯度下降法不保证得到全局最优解(由于凸函数有个性质是只存在一个局部最优解,所有也有文献的提法是:<strong>当目标函数是凸函数时,梯度下降法的解才是全局最优解</strong>)。</p>
<p>由于泰勒公式的展开是近似公式,要求迭代步长要足够小,因此梯度下降法的收敛速度并非很快的。</p>
<h2>总结</h2>
<p>以上是对用 Python 实现简单梯度下降法的思考与总结,有何建议和问题请留下您的反馈,谢谢!</p>
<p>原文作者:雨先生<br>原文链接:https://www.cnblogs.com/noluye/p/11108513.html <br>许可协议:知识共享署名-非商业性使用 4.0 国际许可协议</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/noluye/p/11108513.html
頁:
[1]