一个基于模块类的简单线性回归类的实现
import torch
import torch.nn as nn
class LinearModel(nn.Module):
"""输入x的维度是(1,ndim)"""
def __init__(self,ndim):
self.weights = nn.Parameter(torch.randn(ndim,1))
self.bias = nn.Parameter(torch.randn(1))
def forward(self,x):
return x.mm(self.weights)+self.bias
常用方法:
方法名 | 功能 |
---|---|
named_parameters 和parameters |
获取模型参数,返回结果是生成器,可以直接传入优化器 |
train 和eval |
切换训练和测试状态,dropout层和BN层在测试和训练时有所不同 |
named_buffer 和buffer |
显示计算图中缓存的张量,例如BN层中的Mean和Std |
named_children 和children |
模型的子模块 |
apply |
传入函数/匿名函数,对模块类递归的调用该函数,见下文 |
apply
方法应用案例
import torch
import torch.nn as nn
# 注意,torch.no_grad既可以做上下文管理器,也可以做装饰器
@torch.no_grad()
def init_weights(m):
"""将所有的线性层的参数设置为1.0"""
print(m)
if type(m) == nn.Linear:
m.weight.fill_(1.0)
print(m.weight)
net = nn.Sequential(nn.Linear(2,2),nn.Linear(2,2))
net.apply(init_weights)
PyTorch的计算图是动态图,即每次反向传播后会自动释放前向传播的计算图,每次前向传播不得不重新规划计算图,这点与TensorFlow正好相反,但是两者在数据结构上,同属于有向无环图(DAG),其结点就是张量,分为三种类型:
requires_grad=True
,会被自动作为叶子结点(leaf node)添加到计算图,可通过a.is_leaf
查看,一般有两种指定方法,a = torch.tensor(3,4,requires_grad=True)
或者a = torch.tensor(3,4).requires_grad_()
,其梯度会保存在grad
属性中;autograd根据用户对variable的操作构建其计算图。对变量的操作抽象为Function
,节点默认不需要求导,即requires_grad
属性默认为False,如果某一个节点requires_grad被设置为True,那么所有依赖它的节点requires_grad
都为True。
计算图通过张量自带的grad_fn
方法,携带计算图信息,该方法的next_functions
可跟踪该节点的下一个/下一批节点;张量的梯度信息则会被存储在grad
属性中。
在使用grad_fn.next_functions
进行溯源时,需要求导的leaf_node
会具有AccumulateGrad
标识,这表明它们的梯度会随着backward的增加而不断累加(前提是retain_graph=True)
PyTorch提供了自动求导的包torch.autograd
用以简化繁琐的计算图求解,主要用到的是两个函数
torch.autograd.backward
反向传播
传入两个参数,根节点张量+对应形状的根节点初始梯度,如果根节点是标量(tensor.scalar),则默认初始梯度等于1;然后会计算 根节点 和 所有叶子节点 之间的梯度;结果存储在叶子节点的grad
属性中。
注意:
每次反向传播后,计算图会被自动释放,需重新forward之后才能backward,如果想要保存forward的计算图,可设置retain_graph=True
;
反向传播也是一个计算过程,也可以得到对应的计算图,可设置create_graph=True
,这在求高阶导数时会用到
叶子节点的梯度会累积,需要手动清空;中间节点的梯度会作为buffer被自动释放。
不过一般不会使用torch.autograd.backward(z,1)
,而是使用z.backward(1)
,如果z是标量,默认缺省参数grad_variables
为1。
torch.autograd.grad
传入 根节点和特定的叶子节点,该方法不会改变叶子节点的grad
属性,而是直接返回结果。
同理,可设置是否释放forward计算图 和 是否保留backward计算图
有些节点,我们不想其参加求导,可以设置比reqiure_grad
优先级更高的volatile
属性,默认为False,如果被设置为True,则所有依赖它的节点的volatile
属性都为True,即不需要求导。
在测试推理时,我们一般不需要打开autograd,此时可以将代码放在上下文管理器torch.no_grad()
的作用域内,或者使用torch.set_grad_enabled(False)
,或者使用装饰器@torch.no_grad()
。
有时候,我们也想只修改个别leaf node的值,但是不想被autograd跟踪,可以选择修改张量的data属性,例如a.data.sigmoid_()
可以避免被跟踪;有时候我们只是想查看/统计个别leaf node的值,可以使用b=a.detach()
b.mean()
。
上述只是涉及到查看/修改叶子节点,而中间节点的梯度信息随用随空,如果想查看中间节点的梯度信息,有两种方法
利用torch.autograd.grad(z,y)
,隐式调用z.backward()
注册hook【推荐】
# 第二种方法:使用hook
# hook是一个函数,输入是梯度,不应该有返回值
def variable_hook(grad):
print('y的梯度:',grad)
x = t.ones(3, requires_grad=True)
w = t.rand(3, requires_grad=True)
y = x * w
# 注册hook
hook_handle = y.register_hook(variable_hook)
z = y.sum()
z.backward()
# 除非你每次都要用hook,否则用完之后记得移除hook
hook_handle.remove()
有两种损失函数,一个是torch.nn.functional
中的函数,一个是torch.nn
中的模块,两者基本等价。
损失函数因为要从多维归约到一维的标量,要么是求和,要么是取平均,后者居多。
Loss function | 功能 |
---|---|
torch.nn.BCELoss |
二分类交叉熵损失函数,接受的一般是sigmoid的输出,第一个参数是正分类标签的概率值,第二个参数是以0为负样本标签,1为正样本标签的target,注意都必须是 浮点型 |
torch.nn.BCEWithLogitsLoss |
带对数的二分类交叉熵损失函数,相当于是torch.nn.BCELoss 和sigmoid 的结合,但是又做了进一步优化,以及增加了一个pos_weight 参数,用来在precision和recall之间取一个trade-off。BCEWithLogitsLoss — PyTorch 1.10.0 documentation |
Loss function | 功能 |
---|---|
torch.nn.NLLLoss |
负对数似然函数,预测值 需要 经过softmax然后取对数,目标值是 one-hot编码 |
torch.nn.CrossEntropyLoss |
If provided, the optional argument weight should be a 1D Tensor assigning weight to each of the classes. This is particularly useful when you have an unbalanced training set. |
ℓ ( x , y ) = L = { l 1 , … , l N } ⊤ ℓ(x,y)=L=\{l _1,…,l _N\} ^⊤ ℓ(x,y)=L={l1,…,lN}⊤
l n = − w y n l o g e x p ( x n , y n ) ∑ c = 1 C e x p ( x n , c ) ⋅ 1 { y n ≠ i g n o r e i n d e x } l_n = -w_{yn}log{\frac{exp(x_{n,yn})}{\sum_{c=1}^{C}exp(x_{n,c})}}\cdot1\{y_n\neq {ignore_index}\} ln=−wynlog∑c=1Cexp(xn,c)exp(xn,yn)⋅1{yn=ignoreindex}
其中的 ℓ ( x , y ) ℓ(x,y) ℓ(x,y)可以根据reduction
是’mean’还是’sum’确定归约形式,C表示总分类数,weight
可设置不同类别的权重,可以应用在数据不均衡的案例中。
CrossEntropyLoss — PyTorch 1.10.0 documentation
demo: 数据集是Boston的房价,共有13个特征
import torch
from torch._C import dtype
import torch.nn as nn
from sklearn.datasets import load_boston
class LinearModel(nn.Module):
"""输入x的维度是(1,ndim)"""
def __init__(self,ndim):
self.weights = nn.Parameter(torch.randn(ndim,1),requires_grad=True)
self.bias = nn.Parameter(torch.randn(1),requires_grad=True)
def forward(self,x):
return x.mm(self.weights)+self.bias
boston = load_boston()
lm = LinearModel(13)
optimizer = torch.optim.SGD(lm.parameters(),lr=1e-3)
criterion = torch.nn.CrossEntropyLoss()
train_data = torch.tensor(boston['data'],requires_grad = True,dtype = torch.float32)
target = torch.tensor(boston['target'],dtype=torch.float32)
for i in range(10000):
predict = lm(train_data)
loss = criterion(predict,target)
if i%1000 == 0:
print("loss:%.3f"%loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
注意: 优化之前,需要先将 叶子节点的梯度清零,然后从根节点loss开始backward
当然可以可设置针对不同模块设置不同的学习率,例如
optimizer = torch.optim.SGD([
{'params':model.base.parameters()},
{'params':model.classifier.parameters(),'lr':1e-2}
],lr=1e-3)
常见优化器
Adagrad — PyTorch 1.10.0 documentation
RMSprop — PyTorch 1.10.0 documentation
Adam — PyTorch 1.10.0 documentation