Lesson 4.3 梯度下降(Gradient Descent)基本原理与手动实现

news2024/9/20 18:36:44

文章目录

  • 一、梯度下降基本原理与学习率
    • 1. 数据背景与最小二乘法求解
    • 2. 一维梯度下降基本流程
      • 2.1 参数移动第一步
      • 2.2 梯度下降的多轮迭代
    • 3. 梯度下降算法特性与学习率
  • 二、梯度下降一般建模流程与多维梯度下降
    • 1. 多维梯度下降与损失函数可视化
    • 2. 梯度下降计算流程
    • 3. 二维梯度下降过程的可视化展示
      • 3.1 等高线图
      • 3.2 损失函数取值变化图
    • 4. 线性回归的梯度下降求解过程
  • 三、梯度下降算法评价
    • 1. 梯度下降算法优势
    • 2. 梯度下降算法局限
      • 2.1 局部最小值点(local minimun)陷阱
      • 2.2 鞍点(saddle point)陷阱
    • 3. 梯度下降算法本质与改善方法

  • 在上文当中,我们已经成功的构建了逻辑回归的损失函数,但由于逻辑回归模型本身的特殊性,我们在构造损失函数时无法采用类似 SSE 的计算思路(此时损失函数不是凸函数),因此最终的损失函数也无法用最小二乘法进行直接求解。
  • 当然,这其实也不仅仅是逻辑回归模型的问题,对于大多数机器学习模型来说,损失函数都无法直接利用最小二乘法进行求解。此时,就需要我们掌握一些可以针对更为一般函数形式进行最小值求解的优化方法。而在机器学习领域,最为基础、同时也最为通用的方法就是梯度下降算法
# 科学计算模块
import numpy as np
import pandas as pd

# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt

# 自定义模块
from ML_basic_function import *

一、梯度下降基本原理与学习率

1. 数据背景与最小二乘法求解

  • 梯度下降算法本身是属于数学理论推导相对复杂,但实际应用过程并不难理解的一种方法。
  • 因此,我们将先从一个简单的例子入手,通过和最小二乘法计算过程对比,来查看梯度下降的一般过程,然后我们再进行更加严谨的理论推导。
  • 例如,有简单数据集表示如下:
xy
12
24
36
  • 其中 x x x 是数据集特征, y y y 是数据集标签,假设,我们使用简单线性回归 y = w x y = wx y=wx 对该组数据进行拟合,则此处我们可以构造损失函数如下: S S E L o s s ( w ) = ( 2 − 1 ∗ w ) 2 + ( 4 − 2 ∗ w ) 2 + ( 6 − 3 ∗ w ) 2 = w 2 − 4 w + 4 + 4 w 2 − 16 w + 16 + 9 w 2 − 36 w + 36 = 14 w 2 − 56 w + 56 = 14 ( w 2 − 4 w + 4 ) = 14 ( w − 2 ) 2 \begin{aligned} SSELoss(w) & = (2-1*w)^2 + (4-2*w)^2 + (6-3*w)^2 \\ & = w^2-4w+4+4w^2-16w+16+9w^2-36w+36 \\ & = 14w^2-56w+56 \\ & = 14(w^2-4w+4) \\ & = 14(w-2)^2 \end{aligned} SSELoss(w)=(21w)2+(42w)2+(63w)2=w24w+4+4w216w+16+9w236w+36=14w256w+56=14(w24w+4)=14(w2)2
  • 此时,损失函数 S S E L o s s ( w ) SSELoss(w) SSELoss(w) 就是一个关于 w w w 的一元函数。围绕该损失函数进行最小值求解,可考虑使用最小二乘法进行计算,即令 S S E L o s s ( w ) SSELoss(w) SSELoss(w) 导函数取值为 0,此时有:
    ∂ S S E L o s s ( w ) ∂ ( w ) = 28 ( w − 2 ) = 0 \begin{aligned} \frac{\partial{SSELoss{(w)}}}{\partial{(w)}} & = 28(w-2) \\ & = 0 \end{aligned} (w)SSELoss(w)=28(w2)=0
  • 因此, w w w 的最优解为: w = 2 w=2 w=2
  • 当然,我们也可以通过绘制一个图像来观察损失函数的基本情况:
x = np.linspace(-1, 5, 40)
SSELoss = 14 * np.power(x - 2, 2)
plt.plot(x, SSELoss)

在这里插入图片描述

2. 一维梯度下降基本流程

  • 当然,围绕上述形式非常简单的损失函数,我们也可以尝试使用梯度下降算法进行求解。
  • 首先梯度下降算法的目标仍然是求最小值,但和最小二乘法这种一步到位、通过解方程组直接求得最小值的方式不同,梯度下降是通过一种迭代求解的方式来进行最小值的求解。
  • 其整体求解过程可以粗略描述为,先随机选取一组参数初始值,然后沿着某个方向,一步一步移动到最小值点。例如:
show_trace(gd(lr = 0.01))
plt.plot(2, 0, 'ro')

在这里插入图片描述

  • 该图像的绘制函数会在稍后的内容中进行讲解。

2.1 参数移动第一步

  • 对照上述例子,我们可以将参数的第一次移动过程拆分为以下三步:
  • (1) 随机选取初始参数值
  • 在上述例子中,参数只有一个,我们可以随机设置一个初始的参数值,例如我们令 w 0 = 10 w_0=10 w0=10
  • (2) 基于 w 0 w_0 w0,确定参数移动方向
  • 在上述例子中,由于参数组只有一个参数 w w w,因此参数的变化只有两个方向, w w w 增加的方向和 w w w 减少的方向。
  • 在梯度下降中,参数移动的目标是让损失函数取值减少,因此在上例中,由于 w w w 初始取值为10,所以应该沿着 w w w 减少的方向移动(损失函数具体可见上面 S S E L o s s ( w ) SSELoss(w) SSELoss(w))。
  • 注意,如果损失函数包含多个参数的话,参数组变化方向就有多个,例如假设参数组包含两个参数 w = ( a , b ) w=(a,b) w=(a,b),则 w w w 的变化就相当于是在 ( a , b ) (a, b) (a,b) 这个二维平面上的点发生移动。
  • 当然,在实际计算过程中我们肯定无法通过观察来确定参数移动的方向,为确定更为一般情况下参数的移动方向,我们需要回顾 Lesson 2 中所介绍的关于梯度向量的相关概念。
  • Lesson 2 中我们曾提及,设 f ( x ) f(x) f(x) 是一个关于 x x x 的函数,其中 x x x 是向量变元,并且 x = [ x 1 , x 2 , . . . , x n ] T x = [x_1, x_2,...,x_n]^T x=[x1,x2,...,xn]T
  • ∂ f ∂ x = [ ∂ f ∂ x 1 , ∂ f ∂ x 2 , . . . , ∂ f ∂ x n ] T \frac{\partial f}{\partial x} = [\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, ..., \frac{\partial f}{\partial x_n}]^T xf=[x1f,x2f,...,xnf]T
  • 而该表达式也被称为向量求导的梯度向量形式。 ∇ x f ( x ) = ∂ f ∂ x = [ ∂ f ∂ x 1 , ∂ f ∂ x 2 , . . . , ∂ f ∂ x n ] T \nabla _xf(x) = \frac{\partial f}{\partial x} = [\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, ..., \frac{\partial f}{\partial x_n}]^T xf(x)=xf=[x1f,x2f,...,xnf]T
  • 进一步的,如果假设 f ( x ) f(x) f(x) 是关于参数 x x x 的损失函数,则 ∇ x f ( x ) \nabla _xf(x) xf(x) 就是损失函数的梯度向量,或者梯度表达式。
  • 当损失函数的参数 x x x 选定一组取值之后,我们就能计算梯度表达式的取值,该值也被称为损失函数在某组参数取值下的梯度。
  • 例如,在上例中,损失函数 f ( w ) = 14 ( w − 2 ) 2 f(w) = 14(w-2)^2 f(w)=14(w2)2
  • 损失函数的梯度向量表示形式为: ∇ w f ( w ) = ∂ f ∂ w = 28 ( w − 2 ) \nabla _wf(w) = \frac{\partial f}{\partial w}=28(w-2) wf(w)=wf=28(w2)
  • 具体来看,当 w 0 = 10 w_0=10 w0=10 时,梯度计算结果为 ∇ w f ( w 0 ) = 28 ( 10 − 2 ) = 28 ∗ 8 = 224 \nabla _wf(w_0)=28(10-2)=28*8=224 wf(w0)=28(102)=288=224
  • 在确定了梯度之后,接下来参数的移动方向也随之确定,在梯度下降算法中,参数的移动方向是梯度的负方向。
  • 注意,这里有两点需要注意,其一是梯度的负方向,上述计算结果中,224 既是梯度的取值,同时也代表着梯度的方向——正方向。而此时梯度的负方向则是取值减少的方向,和损失函数图像观测结果相同。其二是参数的移动是朝着梯度的负方向移动,也就是朝着数值减少的方向移动,而不是直接在原参数 10 的基础上减去 224,这很明显是不合适的。
  • 对于只包含一个变量的损失函数来说,梯度也就是函数某点的导数
  • (3) 确定移动步长并进行移动
  • 在确定移动的方向之后,接下来需要进一步确定移动距离。
  • 在梯度下降中,我们采用梯度乘以某个人工设置的参数作为每一步移动的距离,这个参数被称为步长或者学习率(Learning rate),例如在上述案例中,我们可以设置学习率为 lr = 0.01,则从 w 0 w_0 w0 进行移动的距离是 0.01 ∗ 224 = 2.24 0.01 * 224 = 2.24 0.01224=2.24,而又是朝向梯度的负方向进行移动,因此 w 0 w_0 w0 最终移动到了 w 1 = 10 − 2.24 = 7.76 w_1 = 10-2.24 = 7.76 w1=102.24=7.76
  • 相比于步长,将人工设置的参数称为学习率会更加贴切,该参数并不代表真实移动的距离,而是影响真实移动距离的一个比率,或者说是真实移动距离学习梯度值的比率。
  • 至此,参数 w w w 就完成了第一次移动, w 0 → w 1 w_0 \rightarrow w_1 w0w1

2.2 梯度下降的多轮迭代

  • 当然, w w w w 0 w_0 w0 移动到 w 1 w_1 w1 其实只移动了一步,要最终抵达损失函数的最小值点 w = 2 w=2 w=2,还有很长一段距离,因此我们还需要移动多次。
  • 参数多次移动过程中的每一步其实都是在重复上述三个步骤。例如,当我们需要第二次移动 w w w 时,过程如下: w 2 = w 1 − l r ⋅ ∇ w f ( w 1 ) w_2 = w_1 - lr \cdot \nabla _wf(w_1) w2=w1lrwf(w1)
  • w 2 w_2 w2 等于 w 1 w_1 w1 沿着 w 1 w_1 w1 的负梯度方向移动了 l r ⋅ ∇ w f ( w 1 ) lr\cdot\nabla _wf(w_1) lrwf(w1) 距离之后得到的结果。此时我们是在 w 1 w_1 w1 的基础上,减去学习率和 w 1 w_1 w1 梯度(将 w 1 w_1 w1 的值带入梯度计算公式当中即可)的乘积。
  • 当然,依此类推,当从 w ( n − 1 ) w_{(n-1)} w(n1) 移动到 w n w_n wn w n w_n wn 的计算公式如下: w n = w ( n − 1 ) − l r ⋅ ∇ w f ( w ( n − 1 ) ) w_n = w_{(n-1)} - lr \cdot \nabla _wf(w_{(n-1)}) wn=w(n1)lrwf(w(n1))
  • 并且,我们可以称从 w 0 w_0 w0 移动到 w 1 w_1 w1 是第一轮迭代,从 w 1 w_1 w1 移动到 w 2 w_2 w2 是第二轮迭代,从 w ( n − 1 ) w_{(n-1)} w(n1) 移动到 w n w_n wn 是第 n 轮迭代。
  • 迭代其实是一个数学意义上的概念,一般指以下计算流程:某次运算时初始条件是上一次运算的结果,而当前运算的结果则是下一次计算的初始条件。
  • 通过带入梯度值进行多轮迭代,最终使得损失函数的取值逐渐下降的算法,就被称为梯度下降

3. 梯度下降算法特性与学习率

  • 首先,我们简单验证,经过上述梯度下降的一系列计算,最终能否有效降低损失函数的计算结果,即使得 w w w 最终朝向最优值 2 靠拢。我们可以通过定义一个函数来帮助我们进行梯度下降迭代计算。
def gd(lr = 0.02, itera_times = 20, w = 10):
    """
    梯度下降计算函数
    :param lr: 学习率
    :param itera_times:迭代次数
    :param w:参数初始取值
    :return results:每一轮迭代的参数计算结果列表
    """                              
    results = [w]
    for i in range(itera_times):
        w -= lr * 28 * (w - 2)            # 梯度计算公式
        results.append(w)
    return results
  • 当我们在以 0.02 作为学习率,初始值为 10,迭代 20 轮时参数每一轮迭代后参数的计算结果如下:
res = gd()
res
#[10,
# 5.52,
# 3.5488,
# 2.681472,
# 2.29984768,
# 2.1319329792,
# 2.058050510848,
# 2.02554222477312,
# 2.0112385789001728,
# 2.004944974716076,
# 2.0021757888750735,
# 2.0009573471050324,
# 2.000421232726214,
# 2.000185342399534,
# 2.000081550655795,
# 2.00003588228855,
# 2.000015788206962,
# 2.0000069468110633,
# 2.000003056596868,
# 2.000001344902622,
# 2.000000591757154]
  • 我们发现,参数从 10 开始朝向 2 移动,并且迭代 20 轮时已经非常接近全域最优解 2,此处也验证梯度下降确实能够帮助我们找到最优解。
  • 当然,我们也可以将上述参数移动过程和损失函数的函数图像绘制到一张画布上,具体功能可以通过如下函数实现:
def show_trace(res):
    """
    梯度下降轨迹绘制函数
    """
    f_line = np.arange(-6, 10, 0.1)
    plt.plot(f_line, [14 * np.power(x-2, 2) for x in f_line])
    plt.plot(res, [14 * np.power(x-2, 2) for x in res], '-o')
    plt.xlabel('x')
    plt.ylabel('Loss(x)')
show_trace(res)

在这里插入图片描述

  • 接下来,我们在此基础之上,详细讨论梯度下降算法的基本特性与超参数取值一般规范。
  • 从图像上,我们发现,梯度下降的计算过程中,刚开始 w w w 变化非常快,而随着逐渐接近最小值点 w w w 的变化幅度逐渐减少,也就是说,梯度下降其实并不是一个等步长的移动过程。
  • 根据梯度下降的计算公式, w ( n − 1 ) w_{(n-1)} w(n1) w n w_{n} wn 的差值是: ∣ w n − w ( n − 1 ) ∣ = ∣ l r ⋅ ∇ w f ( w ( n − 1 ) ) ∣ |w_n - w_{(n-1)}| = |lr \cdot \nabla _wf(w_{(n-1)})| wnw(n1)=lrwf(w(n1))
  • 而 lr 又是一个恒定的数值,那么也就是说,每个点的梯度值是不一样的(哪怕梯度方向一样),并且越靠近最小值点梯度值越小,当然这点从梯度计算公式也能看出: ∇ w f ( w ) = ∂ f ∂ w = 28 ( w − 2 ) \nabla _wf(w) = \frac{\partial f}{\partial w}=28(w-2) wf(w)=wf=28(w2)
  • 这也就等价说明,如果某点的梯度值相对较大,那么该点应该距离全域最小值较远。而梯度下降过程中实际也能够做到在远点迭代移动距离较大、近点迭代移动距离较小。当然,在学习率对移动距离进行修正时,学习率不宜设置过小,也不宜设置过大,例如当学习率设置过小时:
plt.subplot(121)
plt.title('lr=0.001')
show_trace(gd(lr=0.001))
plt.subplot(122)
plt.title('lr=0.01')
show_trace(gd(lr=0.01))

在这里插入图片描述

  • 当学习率取值为 0.001 时,迭代 20 轮仍然距离最小值点差距较远,此时我们考虑进一步增加迭代次数:
plt.subplot(121)
plt.title('itera_times=20')
show_trace(gd(itera_times=20, lr=0.001))
plt.subplot(122)
plt.title('itera_times=40')
show_trace(gd(itera_times=40, lr=0.001))

在这里插入图片描述

plt.subplot(121)
plt.title('itera_times=60')
show_trace(gd(itera_times=60, lr=0.001))
plt.subplot(122)
plt.title('itera_times=80')
show_trace(gd(itera_times=80, lr=0.001))

在这里插入图片描述

  • 此处学习率为 0.001 取值的情况下,迭代次数增加了三倍但仍然没有收敛到 2 附近,需要迭代 80 次左右才能收敛至较好效果。而过多次的迭代会增加额外计算量,也就是说如果学习率取值较小,则会耗费更多的计算量。
  • 接下来我们再来看当学习率取值过大时可能出现的状况。当我们将学习率设置为 0.05 时,迭代过程如下所示:
plt.subplot(121)
plt.title('lr=0.01')
show_trace(gd(lr=0.01))
plt.subplot(122)
plt.title('lr=0.05')
show_trace(gd(lr=0.05))

在这里插入图片描述

  • 我们发现当学习率较大时,收敛过程并不平稳,当然小幅度不平稳并不影响最终收敛结果,但学习率继续增加,例如我们将学习率增加至 0.08 时,参数整体迭代过程不仅不会收敛,而且还会逐渐发散:
show_trace(gd(lr=0.08))

在这里插入图片描述

  • 当学习率为 0.08 时,所得每一次迭代的具体数值如下。
gd(lr=0.08)
#[10,
# -7.920000000000002,
# 14.300800000000006,
# -13.25299200000001,
# 20.913710080000012,
# -21.45300049920002,
# 31.081720619008028,
# -34.06133356756996,
# 46.71605362378676,
# -53.447906493495594,
# 70.75540405193455,
# -83.25670102439885,
# 107.71830927025458,
# -129.0907034951157,
# 164.55247233394346,
# -199.56506569408992,
# 251.94068146067156,
# -307.92644501123283,
# 386.30879181392874,
# -474.5429018492717,
# 592.9131982930971]
  • 由此我们不难发现学习率取值是影响结果是否收敛、以及能否在有限次迭代次数中高效收敛的关键参数。
  • 学习率过大会导致结果发散,而学习率过小则又会导致模型无法收敛至最优结果

二、梯度下降一般建模流程与多维梯度下降

1. 多维梯度下降与损失函数可视化

  • 一种更为一般的情况是,一个模型中包含多个参数,而损失函数就是这多个参数共同构成的函数。
  • 例如在 Lesson 1 中我们曾利用带截距项的简单线性回归 y = w x + b y=wx+b y=wx+b 对下述数据进行拟合,并得出带有两个参数的损失函数:
数据特征参数组模型输出数据标签
Whole weight(x) ( w , b ) (w,b) (w,b) y ^ \hat y y^Rings(y)
1(w, b)w+b2
3(w, b)3w+b4

S S E L o s s ( w , b ) = ( y 1 − y ^ 1 ) 2 + ( y 2 − y ^ 2 ) 2 = ( 2 − w − b ) 2 + ( 4 − 3 w − b ) 2 SSELoss(w, b) = (y_1 - ŷ_1)^2 + (y_2 - ŷ_2)^2 = (2 - w - b)^2 + (4 - 3w - b)^2 SSELoss(w,b)=(y1y^1)2+(y2y^2)2=(2wb)2+(43wb)2

  • 而此时损失函数图像就是包含两个变量的三维图像
from mpl_toolkits.mplot3d import Axes3D

x = np.arange(-1,3,0.05)
y = np.arange(-1,3,0.05)
w, b = np.meshgrid(x, y)
SSE = (2 - w - b) ** 2 + (4 - 3 * w - b) ** 2

ax = plt.axes(projection='3d')

ax.plot_surface(w, b, SSE, cmap='rainbow')
ax.contour(w, b, SSE, zdir='z', offset=0, cmap="rainbow")  #生成z方向投影,投到x-y平面
plt.xlabel('w')
plt.ylabel('b')
plt.show()

在这里插入图片描述

  • 并且,我们通过最小二乘法能够找出全域最小值点为 (1,1),也就是当 w = 1 , b = 1 w=1,b=1 w=1,b=1 时,能够让损失函数取值最小。当然,围绕该问题,我们也可以采用梯度下降进行最优解求解。

2. 梯度下降计算流程

  • 此处给出更加严谨的梯度下降实现步骤:
  • (1) 确定数据及模型
  • 首先还是要明确数据和模型,然后才能进一步确定参数、梯度计算公式等变量。
  • 此处我们以 y = w x + b y=wx+b y=wx+b 简单线性回归对上述简单数据集进行拟合,参数向量包含两个分量。
  • (2) 设置初始参数值
  • 然后我们需要选取一组参数值,一种更加通用的方法,是给初始参数赋予一组随机数作为初始取值。
  • 由于参数包含两个分量,因此参数向量的初始值可以由如下过程确定:
np.random.seed(24)
w = np.random.randn(2, 1)                # 参数都默认为列向量
w
#array([[ 1.32921217],
#       [-0.77003345]])
  • (3) 根据损失函数求出梯度表达式
  • 只有确定了梯度表达式,才能在后续每一轮迭代过程中快速计算梯度。
  • 线性回归的损失函数是围绕 SSE 及其衍生的指标所构建的损失函数,此处以 MSE 作为损失函数的构建指标,此处可借助已经定义好的 SSELoss 函数执行计算。
    S S E L o s s ( w ^ ) = ∣ ∣ y − X w ^ ∣ ∣ 2 2 = ( y − X w ^ ) T ( y − X w ^ ) SSELoss(\hat w) = ||y - X\hat w||_2^2 = (y - X\hat w)^T(y - X\hat w) SSELoss(w^)=∣∣yXw^22=(yXw^)T(yXw^) M S E L o s s ( w ^ ) = ∣ ∣ y − X w ^ ∣ ∣ 2 2 m = ( y − X w ^ ) T ( y − X w ^ ) m MSELoss(\hat w) = \frac{||y - X\hat w||_2^2}{m} = \frac{(y - X\hat w)^T(y - X\hat w)}{m} MSELoss(w^)=m∣∣yXw^22=m(yXw^)T(yXw^)
  • 其中 m 为训练数据总数。
def MSELoss(X, w, y):
    """
    MSE指标计算函数
    """
    SSE = SSELoss(X, w, y)
    MSE = SSE / X.shape[0]
    return MSE
  • 此时数据为:
features = np.array([1, 3]).reshape(-1, 1)
features = np.concatenate((features, np.ones_like(features)), axis=1)
features
#array([[1, 1],
#       [3, 1]])

labels = np.array([2, 4]).reshape(-1, 1)
labels
#array([[2],
#       [4]])

# 计算w取值时SSE
SSELoss(features, w, labels)
#array([[2.68811092]])

# 计算w取值时MSE
MSELoss(features, w, labels)
#array([[1.34405546]])
  • 而对于线性回归损失函数梯度表达式,则可根据 Lesson 2 中线性回归损失函数梯度求导结果得出: S S E L o s s ( w ^ ) ∂ w ^ = ∂ ∣ ∣ y − X w ^ ∣ ∣ 2 2 ∂ w ^ = ∂ ( y − X w ^ ) T ( y − X w ^ ) ∂ w ^ = ∂ ( y T − w ^ T X T ) ( y − X w ^ ) ∂ w ^ = ∂ ( y T y − w ^ T X T y − y T X w ^ + w ^ T X T X w ^ ) ∂ w ^ = 0 − X T y − X T y + X T X w ^ + ( X T X ) T w ^ = 0 − X T y − X T y + 2 X T X w ^ = 2 ( X T X w ^ − X T y ) = 2 X T ( X w ^ − y ) \begin{aligned} \frac{SSELoss(\hat w)}{\partial{\boldsymbol{\hat w}}} &= \frac{\partial{||\boldsymbol{y} - \boldsymbol{X\hat w}||_2}^2}{\partial{\boldsymbol{\hat w}}} \\ &= \frac{\partial(\boldsymbol{y} - \boldsymbol{X\hat w})^T(\boldsymbol{y} - \boldsymbol{X\hat w})}{\partial{\boldsymbol{\hat w}}} \\ & =\frac{\partial(\boldsymbol{y}^T - \boldsymbol{\hat w^T X^T})(\boldsymbol{y} - \boldsymbol{X\hat w})}{\partial{\boldsymbol{\hat w}}}\\ &=\frac{\partial(\boldsymbol{y}^T\boldsymbol{y} - \boldsymbol{\hat w^T X^Ty}-\boldsymbol{y}^T\boldsymbol{X \hat w} +\boldsymbol{\hat w^TX^T}\boldsymbol{X\hat w})}{\partial{\boldsymbol{\hat w}}}\\ & = 0 - \boldsymbol{X^Ty} - \boldsymbol{X^Ty}+X^TX\hat w+(X^TX)^T\hat w \\ &= 0 - \boldsymbol{X^Ty} - \boldsymbol{X^Ty} + 2\boldsymbol{X^TX\hat w}\\ &= 2(\boldsymbol{X^TX\hat w} - \boldsymbol{X^Ty}) \\ &= 2X^T(X\hat w -y) \end{aligned} w^SSELoss(w^)=w^∣∣yXw^22=w^(yXw^)T(yXw^)=w^(yTw^TXT)(yXw^)=w^(yTyw^TXTyyTXw^+w^TXTXw^)=0XTyXTy+XTXw^+(XTX)Tw^=0XTyXTy+2XTXw^=2(XTXw^XTy)=2XT(Xw^y)
  • 此处我们使用 MSE 作为损失函数,则该损失函数的梯度表达式为:
    M S E L o s s ( w ^ ) ∂ w ^ = 2 X T ( X w ^ − y ) m \frac{MSELoss(\hat w)}{\partial{\boldsymbol{\hat w}}} = \frac{2X^T(X\hat w -y)}{m} w^MSELoss(w^)=m2XT(Xw^y)
  • 这里需要注意的是,矩阵运算也是可以提取公因式的。
def lr_gd(X, w, y):
    """
    线性回归梯度计算公式
    """
    m = X.shape[0]
    grad = 2 * X.T.dot((X.dot(w) - y)) / m
    return grad

# 计算w取值时梯度
lr_gd(features, w, labels)
#array([[-3.78801208],
#       [-2.22321821]])
  • 该步骤其实也是在手动实现梯度下降计算过程中至关重要的一步,正如此前所介绍的,只有将方程组转化为矩阵形式,才能更好的利用程序进行计算。
  • 但值得注意的是,并不是所有的损失函数的梯度计算都能简单表示成矩阵运算形式。
  • (4) 执行梯度下降迭代
  • 在给定梯度计算公式和参数初始值的情况下,我们就能够开始进行梯度下降迭代计算了。在梯度下降过程中,参数更新参照如下公式: w n = w ( n − 1 ) − l r ⋅ ∇ w f ( w ( n − 1 ) ) w_n = w_{(n-1)} - lr \cdot \nabla _wf(w_{(n-1)}) wn=w(n1)lrwf(w(n1))
  • 则可定义相应函数:
def w_cal(X, w, y, gd_cal, lr = 0.02, itera_times = 20):
    """
    梯度下降中参数更新函数 
    :param X: 训练数据特征
    :param w: 初始参数取值
    :param y: 训练数据标签
    :param gd_cal:梯度计算公式
    :param lr: 学习率
    :param itera_times: 迭代次数       
    :return w:最终参数计算结果   
    """
    for i in range(itera_times):
        w -= lr * gd_cal(X, w, y)
    return w
  • 接下来执行梯度下降计算,在学习率为 0.1 的情况下迭代 100 轮:
np.random.seed(24)
w = np.random.randn(2, 1)                # 参数都默认为列向量
w
#array([[ 1.32921217],
#       [-0.77003345]])

w = w_cal(features, w, labels, gd_cal = lr_gd, lr = 0.1, itera_times = 100)
w
#array([[1.02052278],
#       [0.95045363]])

# 计算w取值时SSE
SSELoss(features, w, labels)
#array([[0.0009869]])

# 计算w取值时MSE
MSELoss(features, w, labels)
#array([[0.00049345]])

# 计算w取值时梯度
lr_gd(features, w, labels)
#array([[ 0.0070423 ],
#       [-0.01700163]])
  • 我们发现,最终参数结果逼近 (1,1) 点,也就是说整体计算过程能够在迭代多轮之后逼近全域最小值点。
  • 据此也验证了梯度下降本身的有效性。当然,此处是以二维梯度下降为例进行的展示,更高维度的梯度下降过程也是类似,并且可以借助 NumPy 当中数组的优秀性质来完成相关梯度的计算。

3. 二维梯度下降过程的可视化展示

3.1 等高线图

  • 为了方便后续其他梯度下降算法的性质展示,我们通过绘制损失函数的等高线图来观察二维梯度下降过程中参数点移动的过程。首先在上述迭代 100 次的过程中,我们可以记录每一次迭代的计算结果:
def w_cal_rec(X, w, y, gd_cal, lr = 0.02, itera_times = 20):
    w_res = [np.copy(w)]
    for i in range(itera_times):
        w -= lr * gd_cal(X, w, y)
        w_res.append(np.copy(w))
    return w, w_res

np.random.seed(24)
w = np.random.randn(2, 1)

w, w_res = w_cal_rec(features, w, labels, gd_cal = lr_gd, lr = 0.1, itera_times = 100)

w_res
  • 迭代计算后的结果如下。

在这里插入图片描述

# 所有点的横坐标
np.array(w_res)[:, 0]

在这里插入图片描述

  • 参数移动轨迹图
  • 据此我们可以将所有点在迭代过程的移动轨迹绘制在图片上:
plt.plot(np.array(w_res)[:, 0], np.array(w_res)[:, 1], '-o', color='#ff7f0e')

在这里插入图片描述

  • 我们发现,参数在参数平面上的移动轨迹其实并不是一条直线。当然,我们也可以通过等高线图来观察参数点逼近 (1,1) 点的移动情况。
# 网格点坐标
x1, x2 = np.meshgrid(np.arange(1, 2, 0.001), np.arange(-1, 1, 0.001))
  • 其中 np.meshgrid 生成两个序列,第一个序列根据输入的第一个参数按照列排列,第二个序列根据输入的第二个参数按行排列。该函数输出结果主要用于带网格的图像绘制情况。
np.meshgrid(np.arange(3), np.arange(1, 5))
#[array([[0, 1, 2],
#        [0, 1, 2],
#        [0, 1, 2],
#        [0, 1, 2]]),
# array([[1, 1, 1],
#        [2, 2, 2],
#        [3, 3, 3],
#        [4, 4, 4]])]

# 绘制等高线图
plt.contour(x1, x2, (2-x1-x2)**2+(4-3*x1-x2)**2)# 绘制参数点移动轨迹图
plt.plot(np.array(w_res)[:, 0], np.array(w_res)[:, 1], '-o', color='#ff7f0e')

在这里插入图片描述

  • 其中,等高线图要求输入网格的范围和间隔(即 x 1 x_1 x1 x 2 x_2 x2),以及输入对应函数关系(即 x 1 x_1 x1 x 2 x_2 x2二者作为自变量的函数表达式)。

3.2 损失函数取值变化图

  • 从一个更加严谨的角度,我们可以绘制其损失函数变化曲线:
loss_value = np.array([MSELoss(features, np.array(w), labels) for w in w_res]).flatten()

loss_value
#array([1.34405546e+00, 5.18623852e-01, 4.63471078e-01, 4.31655465e-01,
#       4.02524386e-01, 3.75373031e-01, 3.50053485e-01, 3.26441796e-01,
#       3.04422755e-01, 2.83888935e-01, 2.64740155e-01, 2.46882992e-01,
#       2.30230325e-01, 2.14700907e-01, 2.00218974e-01, 1.86713872e-01,
#       1.74119712e-01, 1.62375048e-01, 1.51422581e-01, 1.41208877e-01,
#       1.31684104e-01, 1.22801792e-01, 1.14518608e-01, 1.06794137e-01,
#       9.95906956e-02, 9.28731379e-02, 8.66086905e-02, 8.07667906e-02,
#       7.53189365e-02, 7.02385492e-02, 6.55008424e-02, 6.10827019e-02,
#       5.69625722e-02, 5.31203522e-02, 4.95372963e-02, 4.61959234e-02,
#       4.30799317e-02, 4.01741188e-02, 3.74643078e-02, 3.49372780e-02,
#       3.25807006e-02, 3.03830783e-02, 2.83336892e-02, 2.64225348e-02,
#       2.46402910e-02, 2.29782625e-02, 2.14283405e-02, 1.99829634e-02,
#       1.86350793e-02, 1.73781123e-02, 1.62059298e-02, 1.51128129e-02,
#       1.40934286e-02, 1.31428034e-02, 1.22562995e-02, 1.14295918e-02,
#       1.06586468e-02, 9.93970341e-03, 9.26925391e-03, 8.64402735e-03,
#       8.06097336e-03, 7.51724732e-03, 7.01019651e-03, 6.53734712e-03,
#       6.09639220e-03, 5.68518043e-03, 5.30170557e-03, 4.94409673e-03,
#       4.61060920e-03, 4.29961595e-03, 4.00959972e-03, 3.73914556e-03,
#       3.48693399e-03, 3.25173450e-03, 3.03239961e-03, 2.82785922e-03,
#       2.63711543e-03, 2.45923761e-03, 2.29335795e-03, 2.13866715e-03,
#       1.99441050e-03, 1.85988420e-03, 1.73443193e-03, 1.61744161e-03,
#       1.50834249e-03, 1.40660229e-03, 1.31172463e-03, 1.22324662e-03,
#       1.14073661e-03, 1.06379203e-03, 9.92037492e-04, 9.25122916e-04,
#       8.62721840e-04, 8.04529820e-04, 7.50262948e-04, 6.99656466e-04,
#       6.52463476e-04, 6.08453732e-04, 5.67412517e-04, 5.29139601e-04,
#       4.93448257e-04])

plt.plot(np.arange(101), loss_value)

在这里插入图片描述

  • 能够发现,在梯度下降过程中损失值是严格递减的。当然我们也可以将上述过程分装为一个函数:
def loss_vis(X, w_res, y, loss_func):
    loss_value = np.array([loss_func(X, np.array(w), y) for w in w_res]).flatten()
    plt.plot(np.arange(len(loss_value)), loss_value)

# 验证函数是否可执行
loss_vis(features, w_res, labels, MSELoss)

在这里插入图片描述

4. 线性回归的梯度下降求解过程

  • 在掌握梯度下降基本原理即基本性质之后,我们可以尝试在手动创建的线性回归数据集上进行梯度下降方法的参数求解。
  • 围绕 Lesson 3.3 中的数据集进行线性回归参数求解,梯度下降求解过程仍然按照上述过程执行:
  • (1) 确定数据集和模型
  • 仍然还是创建扰动项不大、基本满足 y = 2 x 1 − x 2 + 1 y=2x_1-x_2+1 y=2x1x2+1 规律的数据集。
# 设置随机数种子
np.random.seed(24)# 扰动项取值为0.01
features, labels = arrayGenReg(delta=0.01)
  • (2) 设置初始参数
np.random.seed(24)  
w = np.random.randn(3, 1)
w
#array([[ 1.32921217],
#       [-0.77003345],
#       [-0.31628036]])
  • (3) 构建损失函数与梯度表达式
# 计算w取值时SSE
SSELoss(features, w, labels)
#array([[2093.52940481]])

# 计算w取值时MSE
MSELoss(features, w, labels)
#array([[2.0935294]])

# 计算w取值时梯度
lr_gd(features, w, labels)
#array([[-1.15082596],
#       [ 0.3808195 ],
#       [-2.52877477]])
  • (4) 执行梯度下降计算
  • 接下来,借助此前所定义的参数更新函数进行梯度下降参数求解:
w = w_cal(features, w, labels, gd_cal = lr_gd, lr = 0.1, itera_times = 100)
w
#array([[ 1.99961892],
#       [-0.99985281],
#       [ 0.99970541]])

# 计算w取值时SSE
SSELoss(features, w, labels)
#array([[0.09300731]])

# 计算w取值时MSE
MSELoss(features, w, labels)
#array([[9.30073138e-05]])
  • 至此,我们就完成了梯度下降算法的手动实现过程。

三、梯度下降算法评价

1. 梯度下降算法优势

  • 以线性回归的损失函数构建了梯度表达式来进行梯度下降的计算,尽管线性回归的损失函数可以用最小二乘法直接进行求解,但梯度下降算法在某些场景下仍然具备一定优势。
  • 这些优势主要体现在两个方面,其一是相比大规模数值矩阵运算,梯度下降所遵循的迭代求解效率更高(尽管大规模矩阵运算也可以通过分块矩阵的划分来减少每一次计算的数据量),其二则是对于某些最小二乘法无法计算全域唯一最优解的情况,梯度下降仍然能够有效进行最小值点(或者解空间)的寻找。
  • 对于通过严格数学公式计算得出的结果,类似最小二乘法计算结果,也被称为解析解
  • 由某种计算方法或者计算流程得出的结果,例如梯度下降算得的结果,也被称为数值解

2. 梯度下降算法局限

  • 但是,对于上述梯度下降算法还是存在一定的局限性。当损失函数不是凸函数时,也就是有可能存在局部最小值或者鞍点时,梯度下降并不一定能够准确找到全域最小值。

2.1 局部最小值点(local minimun)陷阱

  • 所谓局部最小值,指的是该点左右两端取值都大于该点,但是该点不是全域最小值点。例如函数: f ( x ) = x ⋅ c o s ( π x ) f(x)=x\cdot cos(\pi x) f(x)=xcos(πx)
  • 该函数函数图像如下:
x = np.arange(-1, 2, 0.1)
y = x * np.cos(np.pi * x)
fig = plt.plot(x, y)[0]
fig.axes.annotate('local minimun', xytext=(-0.77, -1), 
                  arrowprops=dict(arrowstyle='->'), xy=(-0.3, -0.25))
fig.axes.annotate('global minimun', xytext=(0.6, 0.8), 
                  arrowprops=dict(arrowstyle='->'), xy=(1.1, -0.95))
plt.xlabel('x')
plt.ylabel('x·cos(pi·x)')

在这里插入图片描述

  • 其中 plt.plot(x, y)[0] 是返回图像对象,axes.annotate 是在图像上添加文字或图形,xytext 是文字起始坐标,xy 是图形起始坐标。
  • 根据此前所介绍的梯度下降的距离衰减理论,由于局部最小值点梯度也是零,因此如果参数点陷入局部最小值,则不可能跨过局部最小值抵达全域最小值点。
  • 假设 f ( x ) = x ⋅ c o s ( π x ) f(x)=x\cdot cos(\pi x) f(x)=xcos(πx) 就是此时的损失函数,并且此时存在唯一的参数 x,则梯度下降迭代计算有如下过程。
  • 这里需要注意的是,此处我们在未知数据、模型和评估指标的情况下,直接给出了损失函数。
  • 首先是由损失函数到梯度计算表达式的过程,由于损失函数只有一个参数,所以梯度表达式为 f ′ ( x ) = ( x ⋅ c o s ( π x ) ) ′ = c o s ( π x ) + x ⋅ ( − s i n ( π x ) ) ⋅ π f'(x) = (x\cdot cos(\pi x))' = cos(\pi x) + x\cdot (-sin(\pi x)) \cdot \pi f(x)=(xcos(πx))=cos(πx)+x(sin(πx))π
  • 我们可以定义该函数的函数表达式函数及导函数函数表达式。
def f_1(x):
    return (x*np.cos(np.pi*x))
f_1(-1)
#1.0

def f_gd_1(x):
    return (np.cos(np.pi*x)-x*np.pi*(np.sin(np.pi*x)))
f_gd_1(-1)
#-1.0000000000000004
  • 以及相应的梯度更新函数。
def gd_1(lr = 0.02, itera_times = 20, w = -1):
    """
    梯度下降计算函数
    :param lr: 学习率
    :param itera_times:迭代次数
    :param w:参数初始取值
    :return results:每一轮迭代的参数计算结果列表
    """                              
    results = [w]
    for i in range(itera_times):
        w -= lr * f_gd_1(w)              # 梯度计算公式
        results.append(w)
    return results
  • 当我们在以 0.02 作为学习率,参数初始值为 -1,迭代 5000 轮时参数每一轮迭代后参数的计算结果如下:
res = gd_1(itera_times = 5000)
res[-1]
#-0.2738526868008511
  • 我们发现,参数最终在停留在 -0.27 附近。当然,对于上述一维梯度下降,我们也可以绘制对应参数变化的轨迹图来进行观察。
def show_trace_1(res):
    """
    梯度下降轨迹绘制函数
    """
    f_line = np.arange(-1, 2, 0.01)
    plt.plot(f_line, [f_1(x) for x in f_line])
    plt.plot(res, [f_1(x) for x in res], '-o')
    plt.xlabel('x')
    plt.ylabel('Loss(x)')
show_trace_1(res)

在这里插入图片描述

  • 我们发现,参数最终停留在局部最小值附近,并且无法跨越该局部最小值点。而根据此前所介绍的梯度下降移动距离衰减理论,最终迭代 5000 轮之后该点梯度也应该是一个趋于 0 的值。当然,这也符合局部最小值的特点,导数为 0。
f_gd_1(res[-1])
#-1.2212453270876722e-15
  • 当然,对于梯度下降的局部最小值陷阱,根本原因还是在于梯度下降过程中参数移动的距离和梯度直接挂钩,而梯度下降的该特点不仅导致了局部最小值陷阱,还有另外一类更加常见的陷阱——鞍点陷阱。

2.2 鞍点(saddle point)陷阱

  • 鞍点,简单来理解就是那些不是极值点但梯度为 0 的点。
  • 极值,指的是那些连续函数上导数为 0、并且所有两边单调性相反的点,极值包括局部最小值、最小值点、局部最大值和最大值点四类。
  • 鞍点和极值点的区别在于导数为 0 点左右两边单调性相同,例如: f ( x ) = x 3 f(x)=x^3 f(x)=x3
  • 我们通过绘制该函数的函数图像来观察鞍点。
x = np.arange(-2, 2, 0.1)
y = np.power(x, 3)
fig = plt.plot(x, y)[0]
fig.axes.annotate('saddle point', xytext=(-0.52, -5), 
                  arrowprops=dict(arrowstyle='->'), xy=(0, -0.2))
plt.xlabel('x')
plt.ylabel('x**3')

在这里插入图片描述

  • 进一步的,我们来看下鞍点是如何影响梯度下降过程的。首先是由损失函数到梯度计算表达式的过程,由于损失函数只有一个参数,所以梯度表达式为: f ′ ( x ) = 3 x 2 f'(x) = 3x^2 f(x)=3x2
  • 我们可以定义该函数、导函数的函数及轨迹绘制函数。
# x**3函数
def f_2(x):
    return np.power(x, 3)
f_2(-1)
#-1

# x**3导函数
def f_gd_2(x):
    return (3*np.power(x, 2))
f_gd_2(-1)
#3
# 梯度更新函数
def gd_2(lr = 0.05, itera_times = 200, w = 1):
    """
    梯度下降计算函数
    :param lr: 学习率
    :param itera_times:迭代次数
    :param w:参数初始取值
    :return results:每一轮迭代的参数计算结果列表
    """                              
    results = [w]
    for i in range(itera_times):
        w -= lr * f_gd_2(w)              # 梯度计算公式
        results.append(w)
    return results
  • 当我们在以 0.05 作为学习率,参数初始值为 1,迭代 5000 轮时参数每一轮迭代后参数的计算结果如下:
res = gd_2(itera_times=5000)
res[-1]
#0.0013297766246039373
def show_trace_2(res):
    """
    梯度下降轨迹绘制函数
    """
    f_line = np.arange(-1, 2, 0.01)
    plt.plot(f_line, [f_2(x) for x in f_line])
    plt.plot(res, [f_2(x) for x in res], '-o')
    plt.xlabel('x')
    plt.ylabel('Loss(x)')
show_trace_2(res)

在这里插入图片描述

  • 同样,梯度下降无法跨越鞍点抵达更小值的点,并且我们判断 5000 次迭代后参数点的梯度已经非常小了:
f_gd_2(res[-1])
#5.304917614029122e-06
  • 我们可以绘制一个包含了鞍点的二维图像进行观察:
x, y = np.mgrid[-1:1:31j,-1:1:31j]
z = x**2- y**2
ax = plt.axes(projection='3d')
ax.plot_wireframe(x, y, z,**{'rstride':2,'cstride':2})
ax.plot([0],[0],[0],'rx')
ticks =[-1,0,1]
plt.xticks(ticks)
plt.yticks(ticks)
ax.set_zticks(ticks)
plt.xlabel('x')
plt.ylabel('y')

在这里插入图片描述

  • 值得注意的是,在实际情况中,鞍点出现的频率高于局部最小值点。

3. 梯度下降算法本质与改善方法

  • 根据局部最小值和鞍点的讨论,我们不难发现梯度下降的本质作用其实是让参数点移动到梯度为 0 的点,当损失函数是严格意义的凸函数时,梯度为 0 的点就是全域最小值点,但如果损失函数不是凸函数,那么梯度为 0 的点就有可能是局部最小值点或者鞍点。
  • 此时受到局部最小值点或者鞍点梯度为 0 的影响,梯度下降无法从该点移出。
  • 尽管大多数线性模型的损失函数都是凸函数,但很多复杂机器学习模型所构建的损失函数都不一定是严格凸函数,要避免局部最小值点或者鞍点陷阱,我们就必须在梯度下降算法基础上进行改进。- 有一种最基础也是最通用的改进办法,就是每次在构建损失函数的时候代入一小部分数据,从而让参数有机会跳出陷阱,这就是所谓的随机梯度下降小批量梯度下降

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/182744.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jvm学习的核心(四)---执行引擎和字符串

知识点总结于b站宋红康老师,视频链接 文章目录1.执行引擎1.1.执行引擎概述1.2.执行引擎的工作流程1.3.jvm的解释器和即时编译器(JIT)1.3.1.解释器,即时编译器概述1.3.2.常见即时编译器1.3.3.热点代码探测1.String Table1.1 string…

Go语言基础入门第三章

常量 常量是一个简单值的标识符,在程序运行时,不会被修改的量。 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。 const identifier [type] value可以省略类型说明符[type],因为编译器…

法律常识(三)《合同法》中的借款合同租赁合同摘录

目录 参考 一、借款合同 二、租赁合同 参考 中华人民共和国合同法 一、借款合同 第一百九十六条 借款合同是借款人向贷款人借款,到期返还借款并支付利息的合同。 第一百九十七条 借款合同采用书面形式,但自然人之间借款另有约定的除外。借款合同…

瑞吉外卖 对象转换器 公共字段自动填充 文件上传/下载

https://blog.csdn.net/weixin_43715214/category_12022798.html大佬记录项目介绍day01功能架构(1)用户层本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI等技术。而在构建移动端应用时,我们会使用到微信小…

Red Giant Magic Bullet Suite介绍

Red Giant Magic Bullet Suite介绍什么是Magic Bullet SuiteMagic Bullet Suite功能介绍什么是Magic Bullet Suite Magic Bullet Suite是电影制作人不可或缺的一套调色降噪插件,它能够为您制作出和好莱坞一样的效果,为电影制作人提供专业的色彩校正。可…

excel函数应用:最简单的条件求和函数DSUM

SUM系列求和函数是我们日常工作中最常用的函数,相信大部分朋友对SUMIF、SUMIFS、SUMPRODUCT等函数都已经比较熟悉了。但是有一个求和函数大家可能都不熟悉,它就是DSUM函数,用于求数据库中记录的满足给定条件的的字段(列&#xff0…

在中国社科院与美国杜兰大学金融管理硕士项目就读,重焕青春活力

在职场摸爬滚打多年后的你,是否有觉得内心疲惫? 是否进入到职场倦怠期?今天是春节后的首个工作日,新的一年意味着新的开始。你有想过在职继续攻读硕士学位吗?在中国社科院与美国杜兰大学金融管理硕士项目就读&#xff…

vue中实现打印

一、VUE 集成 LODOP插件打印 VUE 集成LODOP插件打印 Lodop、C-Lodop使用说明及样例 C-Lodop插件官网:功能演示 - Lodop和C-Lodop官网主站 参考文章:VUE 集成 LODOP插件打印_廷贺的博客-CSDN博客 二、winodw.print() 打印 print() 方法用于打印当前…

vs2015软件打包及常见问题解决方法

一、如程序文件是64位,而项目设置32位,打包项目编译时遇到如下问题 解决办法:选择打包程序项目的属性窗口设置TargetPlatform属性为对应的值,本项目的文件是64位的所以设置打包生成的程序为64位的,如下: …

【可解释性机器学习】排列重要性(Permutation Importance)及案例分析详解

Permutaion Importance:排列重要性引言工作原理代码示例排列重要性结果解读模型检验特征选择补充分析Partial Dependency PlotSharpley ValueLIME总结参考资料当训练得到一个模型之后,除了对模型的预测感兴趣之外,我们往往还想知道模型中哪些…

DDOS渗透与攻防(三)之socktress攻击

系列文章 DDOS渗透与攻防(一)之拒绝服务攻击概念介绍 DDOS渗透与攻防(二)之SYN-Flood攻击 socktress攻击 攻击协议原理介绍说明-socktress 2008年有Jack C.Louis发现,针对TCP服务的拒绝服务攻击: 消耗被攻击目标系统资源,与攻击目标建立…

xml配置JedisUtil

一.背景 习惯了Bean注解方式往sping容器中注入对象,现使用xml方式注入Bean对象总结下,顺便用帮女朋友解决的Jedis问题当做案例来总结。 二.配置JedisPool 从源码来看,JedisPool的构造函数有N多种 我们使用如下的构造函数来实例化JedisPool…

docker部署Nginx和Tomcat

文章目录 前言 目录 文章目录 前言 一、docker部署Nginx 二、docker部署Tomcat 总结 一、docker部署Nginx 下载镜像:docker pull nginx 后台运行镜像 -d 后台运行 --name"nginx01" 给容器命名 -p 宿主机端口:容器内部端口 docker run -d --name"…

2.SpringAop的jdkcglib动态代理xml注解实现切面

1.Spring 的 AOP 简介 1.1 什么是 AOP AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP 是 OOP 的延续,是软件开发中的一个热点,也是…

Linux常用命令——rsync命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) rsync 远程数据同步工具 补充说明 rsync命令是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。rsync使用所谓的“rsync算法”来使本地和远程两个主机之间的文件达到同步&#xff0c…

ARMv8 AArch64异常处理机制概览

1 处理机制概述 相对于ARMv7中的异常向量表(Exception Vector Table),ARMv8异常处理机制更为复杂,涉及处理器的异常等级(Exception Levels, ELn)、运行状态(Execution States)和安全…

汉字乱码状态下的编码转换将导致的问题

实验工具notepad编辑器实验过程步骤1:打开notepad,新建一个文本文件,在其中输入一段汉字文本,查看当前编码格式,如下:分析:由上图可见,从右下角可知当前文件是以UTF-8解码显示的&…

微信小程序——页面事件,.启用下拉刷新监听页面的下拉刷新事件,上拉触底事件,停止下拉刷新的效果

一.页面事件1.什么是下拉刷新下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。2.启用下拉刷新启用下拉刷新有两种方式:a.全局开启下拉刷新在 app.json 的window 节点中,将 enableP…

python 蓝桥杯 矩阵拼接

问题描述已知 3 个矩形的大小依次是 a_{1} \times b_{1}, a_{2} \times b_{2}a1b1,a2b2 和 a_{3} \times b_{3}a3b3 。用这 3 个矩形能拼 出的所有多边形中, 边数最少可以是多少?例如用 3 \times 232 的矩形(用 A 表示)、 4 \times 141 的矩形 (用 BB 表示) 和 2 \…

法律常识(一)婚姻法全文

目录 参考 第一章 总 则 第二章 结 婚 第三章 家庭关系 第四章 离 婚 第五章 救助措施与法律责任 第六章 附 则 参考 中华人民共和国婚姻法http://www.gqb.gov.cn/node2/node3/node5/node9/node101/userobject7ai1290.html 《图解中华人民共和国婚姻法》 &#xff…