梯度

  • 梯度的方向代表的是从小到大的方向

θt+1=θtαtf(θt)\theta_{t+1}=\theta_t-\alpha_t \nabla f(\theta_t)

An overview of gradient descent optimization algorithms (ruder.io)

搜不到全局最小值的原因

  • 局部最小值
  • 鞍点

大部分情况下,鞍点比局部最小值带来的影响更为严重

常见函数的梯度

常见函数梯度表

激活函数和梯度

sigmoid

sigmoid(x)=11+exsigmoid(x)=\frac{1}{1+e^{-x}}

优点

  • 连续
  • 光滑
  • 范围01,适合于一些输出需要控制在01的场景(eg:概率,rgb值)

缺点

  • 当范围趋于++\infty-\infty时,因为导数较小,所以参数更新会非常的缓慢,收敛速度慢

PyTorch实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [3]: a=torch.linspace(-100,100,10)
In [4]: a
Out[4]:
tensor([-100.0000, -77.7778, -55.5556, -33.3333, -11.1111, 11.1111,
33.3333, 55.5556, 77.7778, 100.0000])
In [5]: torch.sigmoid(a)
Out[5]:
tensor([0.0000e+00, 1.6655e-34, 7.4564e-25, 3.3382e-15, 1.4945e-05, 9.9999e-01,
1.0000e+00, 1.0000e+00, 1.0000e+00, 1.0000e+00])
In [6]: from torch.nn import functional as F
In [7]: F.sigmoid(a)
D:\App\Anaconda\envs\pytorch\lib\site-packages\torch\nn\functional.py:1806: UserWarning: nn.functional.sigmoid is deprecated. Use torch.sigmoid instead.
warnings.warn("nn.functional.sigmoid is deprecated. Use torch.sigmoid instead.")
Out[7]:
tensor([0.0000e+00, 1.6655e-34, 7.4564e-25, 3.3382e-15, 1.4945e-05, 9.9999e-01,
1.0000e+00, 1.0000e+00, 1.0000e+00, 1.0000e+00])

tanh

tanh(x)=exexex+ex=2sigmoid(2x)1tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}=2sigmoid(2x)-1

ddxtanh(x)=1tanh2(x)\frac{d}{dx}tanh(x)=1-tanh^2(x)

tanh function

在RNN中使用较多。

PyTorch实现

1
2
3
4
5
In [3]: a=torch.linspace(-1,1,10)
In [4]: torch.tanh(a)
Out[4]:
tensor([-0.7616, -0.6514, -0.5047, -0.3215, -0.1107, 0.1107, 0.3215, 0.5047,
0.6514, 0.7616])

Rectified Linear Unit(ReLU)

f(x)={0for x<0xfor x0f(x)=\begin{cases} 0\quad for\ x<0\\ x\quad for\ x\ge0 \end{cases}

ReLU function

经过大量实验验证,ReLU函数被证明非常适用于深度学习。

Pytoch实现

1
2
3
4
5
6
7
8
9
10
In [3]: a=torch.linspace(-1,1,10)
In [4]: torch.relu(a)
Out[4]:
tensor([0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.1111, 0.3333, 0.5556, 0.7778,
1.0000])
In [5]: a
Out[5]:
tensor([-1.0000, -0.7778, -0.5556, -0.3333, -0.1111, 0.1111, 0.3333, 0.5556,
0.7778, 1.0000])

Loss函数和梯度

常见的Loss函数

  • 均方差Loss函数(Mean Square Error)
  • Cross Entropy Loss
    • 二分类(binary)
    • 多分类(multi-class)
    • softmax

MSE

loss=[y(xw+b)2]loss=\sum[y-(xw+b)^2]

注意和2范数进行区分(L2_norm)

L2_norm=y(xw+b)2L2\_norm=||y-(xw+b)||_2

loss=norm(y(xw+b))2loss=norm(y-(xw+b))^2

PyTorch中进行表示结果如下:

1
torch.norm(y-pred,2).pow(2)

对mse函数求导表达式如下:

loss=[yfθ(x)]2loss=\sum[y-f_\theta(x)]^2

lossθ=2[yfθ(x)]fθ(x)θ\frac{\nabla loss}{\nabla \theta}=2\sum[y-f_\theta(x)]*\frac{\nabla f_\theta(x)}{\nabla\theta}

补充:在PyTorch中实现自动求导※

autograd.grad

1
2
3
4
5
6
7
8
9
In [3]: x=torch.ones(1)
In [4]: w=torch.tensor([2.],requires_grad=True)# important
In [5]: from torch.nn import functional as F
In [6]: mse=F.mse_loss(torch.ones(1),x*w)#label,pred
In [7]: mse
Out[7]: tensor(1., grad_fn=<MseLossBackward0>)
In [8]: torch.autograd.grad(mse,[w])
Out[8]: (tensor([2.]),)

loss.backward

接上面

1
2
3
4
In [9]:  mse=F.mse_loss(torch.ones(1),x*w)
In [10]: mse.backward()
In [11]: w.grad
Out[11]: tensor([2.])

一般使用下面这种

补充:softmax

soft version of max

softmax原理图

我们假设,左边没有进入softmax层的特征向量为aa右边经过了softmax层的特征向量为pp,那么有。

piaj={pi(1pj)if i=jpjpiif ij\frac{\partial p_i}{\partial a_j}=\begin{cases} p_i(1-p_j)\quad if \ i=j\\ -p_jp_i\quad if\ i\ne j \end{cases}

pytorch 代码验证如下:

1
2
3
4
5
6
7
8
9
10
In [3]: a=torch.rand(3)
In [4]: a.requires_grad_()
Out[4]: tensor([0.1714, 0.4650, 0.7201], requires_grad=True)
In [5]: from torch.nn import functional as F
In [6]: p=F.softmax(a,dim=0)
In [7]: p.sum().backward()# 如果这里不求和,就要在backward中 指定一个和p一样大的tensor,详情见:下面的红色框中的blog
In [8]: a.grad
Out[8]: tensor([0., 0., 0.])
In [9]: p.grad
Out[9]: UserWarning: The .grad attribute of a Tensor that is not a leaf Tensor is being accessed. Its .grad attribute won't be populated during autograd.backward(). If you indeed want the .grad field to be populated for a non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If you access the non-leaf Tensor by mistake, make sure you access the leaf Tensor instead. See github.com/pytorch/pytorch/pull/30531 for more informations. (Triggered internally at aten\src\ATen/core/TensorBody.h:417.)
[grad

can be implicitly created only for scalar outputs_](https://blog.csdn.net/qq_39208832/article/details/117415229#:~:text=1.1 grad can be implicitly created only for,是一个 标量 (即它包含一个元素的数据),则不需要为 backward () 指定任何参数,但是如果它有更多的元素,则需要指定一个 gradient 参数,该参数是形状匹配的张量。) Ps:这里将p求和,和不将p求和对a求导的最终结果是一样的,因为根据链式法则,最后求和每个p的部分前面的系数都是1,所以乘上1不影响最终的结果。

可以发现最后我们是**无法查看**最终的p.sum()对中间变量p的求导参数的(**不保存中间变量的梯度信息**)。详情见下面这篇博客:

通俗讲解PyTorch梯度的相关问题:计算图、torch.no_grad、zero_grad、detach和backward;Variable、Parameter和torch.tensor 这其中也有讲重复backward报错的问题!要解决这个问题,每次backward将retain_graph设为True就好了。 backward(retain_graph=True) 记得再次进行backward前,要根据自己需求确定是否使用a.grad.zero_()将梯度清零。不然梯度会在上一次求出来的基础上进行叠加。

Cross Entropy Loss(交叉熵)

熵(Entropy)

  • 不确定性
  • 用于衡量惊喜程度
  • 熵值越高:越高的不确定性

Entropy=iP(i)logP(i)Entropy=-\sum_iP(i)logP(i)

交叉熵(Cross Entropy)

数学定义

H(p,q)=p(x)log q(x)H(p,q)=-\sum p(x)log \ q(x)

H(p,q)=H(p)+DKL(pq)H(p,q)=H(p)+D_{KL}(p|q)

DKLD_{KL}是散度,用于衡量两个分布的接近程度的,散度越小,分布越接近。

这里p是我们将p定为网络学习出来的分布,q为实际数据的分布,所以我们直观理解优化目标就是,网络学习出来的分布要有较高的确定性(不能觉得同时归属于几个种类的概率是一样的)。也要和实际分布接近(散度小)。

因此对于二分类,我们的 Cross Ebtropy Loss函数可以写为:

H(P,Q)=(ylog(p)+(1y)log(1p))H(P,Q)=-(ylog(p)+(1-y)log(1-p))

为什么使用交叉熵

对于分类问题

  • 基于sigmoid的mse容易发生梯度消失的问题
  • 收敛缓慢

PyTorch实例

注意:cross_entropy函数包含了求softmax,log这些步骤。

PyTorch单层单输出感知机实战

原理图

上标代表第几层,下标代表特征向量的第几个元素。

求导过程如下:

求导过程

这样得知了Ewj0\frac{\partial E}{\partial w_{j0}}后,我们便可以更新w了。

下面使用PyTorch简单的实现上述单层单输出感知机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In [3]: x=torch.randn(1,10)
In [4]: w=torch.randn(1,10,requires_grad=True)
In [5]: o=torch.sigmoid(x@w.t())
In [6]: o.shape
Out[6]: torch.Size([1, 1])
In [7]: from torch.nn import functional as F
In [8]: loss=F.mse_loss(torch.ones(1,1),o)
In [9]: loss.shape
Out[9]: torch.Size([])
In [10]: loss.backward()
In [11]: w.grad
Out[11]:
tensor([[-0.1147, -0.2456, -0.2645, 0.1144, -0.0162, 0.1094, -0.3674, -0.0048,
-0.1127, -0.1605]])

然后,我们便可以使用w.gradw进行更新啦!

PyTorch多输出感知机实战

原理图

求导推导过程如下:

求导过程

下面使用PyTorch简单的实现上述单层多输出感知机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [3]: x=torch.randn(1,10)
In [4]: w=torch.randn(2,10,requires_grad=True)
In [5]: o=torch.sigmoid(x@w.t())
In [6]: o.shape
Out[6]: torch.Size([1, 2])
In [7]: from torch.nn import functional as F
In [8]: loss=F.mse_loss(torch.ones(1,2),o)
In [9]: loss
Out[9]: tensor(0.6030, grad_fn=<MseLossBackward0>)
In [10]: loss.backward(retain_graph=True)
In [11]: w.grad
Out[11]:
tensor([[-0.1720, -0.1305, -0.0129, 0.0506, -0.0449, 0.1076, 0.0133, 0.0291,
0.0757, 0.0186],
[-0.0033, -0.0025, -0.0002, 0.0010, -0.0009, 0.0021, 0.0003, 0.0006,
0.0015, 0.0004]])

链式法则※

通过使用链式法则,我们可以把最后一层的误差,一层一层的输出到中间层的权值上面去,从而得到中间层的梯度信息,进而很好的更新权值,达到反向传播优化模型的效果

链式法则原理图

PyTorch实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In [3]: from torch import autograd
In [4]: x=torch.tensor(1.)
In [5]: w1=torch.tensor(2.,requires_grad=True)
In [6]: b1=torch.tensor(1.)
In [7]: w2=torch.tensor(2.,requires_grad=True)
In [8]: b2=torch.tensor(1.)
In [9]: y1=x*w1+b1
In [10]: y2=y1*w2+b2
In [11]: dy2_dy1=autograd.grad(y2,[y1],retain_graph=True)[0]
In [12]: dy1_dw1=autograd.grad(y1,[w1],retain_graph=True)[0]
In [13]: dy2_dw1=autograd.grad(y2,[w1],retain_graph=True)[0]
In [14]: dy2_dy1*dy1_dw1
Out[14]: tensor(2.)
In [15]: dy2_dw1
Out[15]: tensor(2.)

由上证明了链式法则的正确性!

MLP反向传播

  • 多层感知机

其实原理非常的简单,就是正向传播完成后,倒着一层一层计算导数,每一层的倒数计算同上面的单层单输出感知机和单层多输出感知机。所以整个反向传播的过程相当于是很多个单层n输出感知机接在一起。(感知机的推到步骤见上面)

2D函数优化实例

Himmelblau function

这里我们采用的函数表达式如下:

f(x,y)=(x2+y11)2+(x+y27)2f(x,y)=(x^2+y-11)^2+(x+y^2-7)^2

如下图所示

Himmelblau function

该函数在以下四点取得全局最小值:

  • f(3.0,2.0)=0.0f(3.0,2.0)=0.0
  • f(2.805118,3.131312)=0.0f(-2.805118,3.131312)=0.0
  • f(3.779310,3.283186)=0.0f(-3.779310,-3.283186)=0.0
  • f(3.584428,1.848126)=0.0f(3.584428,-1.848126)=0.0

首先是画图代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
import matplotlib.pyplot as plt
import torch
def himmelblau(x):
return (x[0]**2+x[1]-11)**2+(x[0]+x[1]**2-7)**2

x=np.arange(-6,6,0.1)
y=np.arange(-6,6,0.1)
print('x,y range:',x.shape,y.shape)
X,Y=np.meshgrid(x,y)
print('X,Y maps:',X.shape,Y.shape)
Z=himmelblau([X,Y])

fig=plt.figure('himmelblau')
ax=fig.gca(projection='3d')
ax.plot_surface(X,Y,Z)
ax.view_init(60,-30)
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()

画图结果如下:

Himmelblau function画图结果

然后就是求导找出最优解了,代码如下:

具体细节见代码中的注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 下面使用随机梯度下降的方式进行求解全局最小值
x=torch.tensor([0.,0.],requires_grad=True)
# 使用优化器,优化器会自动根据梯度信息和学习率来更新目标(这里是x)的值

optimizer=torch.optim.Adam([x],lr=1e-3)# 初始化优化器

for step in range(20000):
pred=himmelblau(x)

optimizer.zero_grad()# 清零梯度(不然会出现梯度累加)
pred.backward() # 自动求导获取梯度信息
optimizer.step()# 优化器执行一次更新

if step%2000==0:
print('step {}: x = {}, f(x) = {}'
.format(step,x.tolist(),pred.item()))

最终运行结果如下所示,可以发现最终是找到了一个全局最优解:

找到了一个全局最优解

了解了PyTorch最基本的梯度知识后,下面我们将继续学习如何使用PyTorch构造简单的神经网络了。


本站由 @anonymity 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。