【PyTorch学习笔记】8:激活函数,损失函数,使用PyTorch求导

激活函数

在PyTorch的老版本里这些激活函数在torch.nn.functional下,现在大多已经改到了torch下。

Sigmoid

数据将被映射到0到1之间。
f ( x ) = σ ( x ) = 1 1 + e − x f(x)=\sigma (x)=\frac{1}{1+e^{-x}} f(x)=σ(x)=1+ex1

import torch

a = torch.linspace(-100, 100, 10)
print(torch.sigmoid(a))

运行结果:

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

可以由Sigmoid变换得到,数据将被映射到-1到1之间。
f ( x ) = t a n h ( x ) = e x − e − x e x + e − x = 2 S i g m o i d ( 2 x ) − 1 f(x)=tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}\\=2Sigmoid(2x)-1 f(x)=tanh(x)=ex+exexex=2Sigmoid(2x)1

b = torch.linspace(-1, 1, 10)
print(torch.tanh(b))

运行结果:

tensor([-0.7616, -0.6514, -0.5047, -0.3215, -0.1107,  0.1107,  0.3215,  0.5047,
         0.6514,  0.7616])
ReLU

即Rectified Linear Unit,是现在DL用得最多的激活函数。ReLU在输入正信号时保证输出信号的导数是1,避免了当输入过大时引起(前面Sigmoid或者Tanh都会出现的)梯度弥散。
f ( x ) = { 0   f o r   x &lt; 0 x   f o r   x ≥ 0 f(x)=\left\{ \begin{aligned} 0\ for\ x&lt;0 \\ x\ for\ x\ge0 \end{aligned} \right. f(x)={0 for x<0x for x0

c = torch.linspace(-1, 1, 10)
print(torch.relu_(c))

运行结果:

tensor([0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.1111, 0.3333, 0.5556, 0.7778,
        1.0000])
Softmax

可以用于将多个输出转换成多个概率值,使每个值都符合概率的定义(在0到1),且概率相加和为1,非常适合多分类问题。Softmax往往用在最后对输出值y的处理上。它会将原来大的值相对缩放得更大,而原来很小的值压缩到比较密集的空间(这从指数函数图像就可以理解)。

f ( y i ) = S ( y i ) = e y i ∑ j e y j f(y_i)=S(y_i)=\frac{e^{y_i}}{\sum\limits_j e^{y_j}} f(yi)=S(yi)=jeyjeyi

import torch
from torch.nn import functional as F

y = torch.rand(3, requires_grad=True)
print(y)
# 因为进行Softmax操作涉及到组内的其它结点的值,所以要特别注意dim的设置
p = F.softmax(y, dim=0)
print(p)

运行结果:

tensor([0.7440, 0.1704, 0.7994], requires_grad=True)
tensor([0.3816, 0.2150, 0.4034], grad_fn=<SoftmaxBackward>)

注意,因为Softmax涉及其它结点的值的信息,每个结点 y k y_k yk经Softmax得到结点 p k p_k pk,计算导数时也要计算对其它结点的偏导(i不等于j的情况):
∂ p i ∂ y j = { p i ( 1 − p j )   ,   i = j − p i ⋅ p j   , i ≠ j \frac{\partial p_i}{\partial y_j}=\left\{ \begin{aligned} p_i(1-p_j) \ , \ i=j \\ -p_i \cdot p_j \ , i \neq j \end{aligned} \right. yjpi={pi(1pj) , i=jpipj ,i̸=j
这里也能看出,当 i = j i=j i=j时得到的导数值为正,也就是 y k y_k yk越大,自然得到的概率 p k p_k pk也就应该越大;而当 i ≠ j i\ne j i̸=j时得到的导数值为负,因为 y j y_j yj越大,肯定要让得到其它类别的概率 p i p_i pi越小,这是符合对多分类的概率的直观感受的。

# 注意计算导数时,LOSS只能是一个值的,不能是有多个分量的
# 但是可以对多个东西去求偏导

# 计算p1对y0,y1,y2的导数
print(torch.autograd.grad(p[1], [y], retain_graph=True))
# 计算p2对y0,y1,y2的导数
print(torch.autograd.grad(p[2], [y]))

运行结果:

(tensor([-0.0821,  0.1688, -0.0867]),)
(tensor([-0.1539, -0.0867,  0.2407]),)

损失函数

这些LOSS都定义在torch.nn.functional下,可以直接使用。

Mean Squared Error

MSE即均方误差,常用在数值型输出上。
M S E = ∑ ( y − f θ ( x i ) ) 2 MSE= \sum (y-f_\theta(x_i))^2 MSE=(yfθ(xi))2
其中 θ \theta θ是网络的参数,取决于使用的网络结构,例如如果只是普通的线性感知器,那么:
f θ ( x ) = f ( x ; w , b ) = w x + b f_\theta(x)=f(x;w,b)=wx+b fθ(x)=f(x;w,b)=wx+b
注意MSE和L2范数相比,L2范数是做了开平方操作的,所以如果要使用它来求MSE,最后只要.pow(2)平方一下就可以了。

import torch

# 实际值
y = 0.2
# 预测值:采样自N~(0,1)的10个值
pred = torch.randn(10)
# 计算MSE
mse = (pred - y).norm(2).pow(2)
print(mse)

运行结果:

tensor(10.1975)

在进行梯度下降时,将MSE对网络中要调整的参数 θ \theta θ求导,有:
∇ l o s s ∇ θ = − 2 ∑ ( ( y − f θ ( x i ) ) ⋅ ∇ f θ ( x i ) ∇ θ ) \frac{\nabla loss}{\nabla \theta}=-2 \sum \bigg((y-f_\theta(x_i))\cdot \frac{\nabla f_\theta(x_i)}{\nabla \theta}\bigg) θloss=2((yfθ(xi))θfθ(xi))

Cross Entorpy Loss

交叉熵损失既可以用于二分类,也适用于多分类,并且往往和Softmax激活函数搭配使用。

关于PyTorch的交叉熵相关的损失函数,网上这篇文章讲的很详细。

使用PyTorch求导

两种方式。

使用torch.autograd.grad()

以MSE为例,模型pred = w*x

import torch
from torch.nn import functional as F

# pred = x*w
x = torch.ones(1)
w = torch.full([1], 2)  # dim=1,长度为1,值初始化为2
y = torch.ones(1)  # 实际输出
mse = F.mse_loss(x * w, y)
print(mse)

得到

tensor(1.)

再求导

res = torch.autograd.grad(mse, [w])  # 让MSE对参数w自动求导

发生错误

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

这个错误是因为在前面定义w时,没有设定其需要梯度信息,所以PyTorch在建图的时候就没有设置它具有梯度信息。

可以在定义完w后将其设置为需要梯度信息:

w = torch.full([1], 2)
w.requires_grad_()

也可以在建立Tensor时用参数指明它需要梯度信息:

w = torch.full([1], 2, requires_grad=True)

将得到的求导结果res输出,可以得到:

(tensor([2.]),)

这和手动计算的结果相同。

使用loss.backward()

使用LOSS对象的backward()方法,让PyTorch做反向的搜索,将需要grad信息的Tensor求出这个LOSS关于它的导数出来。

import torch
from torch.nn import functional as FF

# pred = x*w
x = torch.ones(1)
w = torch.full([1], 2, requires_grad=True)  # dim=1,长度为1,值初始化为2
y = torch.ones(1)  # 实际输出
mse = FF.mse_loss(x * w, y)
# 自动向图的反向搜索,求出相对于这个MSE的导数来
mse.backward()
print(w.grad)

运行结果:

(tensor([2.]),)

注意,这个API会将之前的梯度信息覆盖掉,所以多次调用这个LOSS对象的.backward()方法时会报错,如果要再次调用以生成新的梯度信息,要给它一个参数:

mse.backward(retain_graph=True)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值