四川网站设计,一天赚1000块钱的游戏,动漫网站开发与建设,网页设计计划书参考https://github.com/chenyuntc/pytorch-book/tree/v1.0 希望大家直接到上面的网址去查看代码#xff0c;下面是本人的笔记 本章介绍的nn模块是构建与autograd之上的神经网络模块 除了nn外还会介绍神经网络中常用的工具#xff0c;比如优化器optim、初始化init等 1.nn.Mod…参考https://github.com/chenyuntc/pytorch-book/tree/v1.0 希望大家直接到上面的网址去查看代码下面是本人的笔记 本章介绍的nn模块是构建与autograd之上的神经网络模块 除了nn外还会介绍神经网络中常用的工具比如优化器optim、初始化init等 1.nn.Module torch的核心数据结构是Module它是一个抽象的概念既可以表示神经网络中的某个层也可以表示一个包含很多层的神经网络 在实际使用中最常见的做法是继承nn.Module攥写自己的网络层 下面先来看看如何使用nn.Module实现自己的全连接层。全连接层又名仿射层输出y和输入x满足yWx b,W和b是可以学习的参数 import torch as t
from torch import nn
from torch.autograd import Variable as V 定义函数 class Linear(nn.Module): #继承nn.Moduledef __init__(self, in_features, out_features):super(Linear, self).__init__() #等价于nn.Module.__init__(self)self.w nn.Parameter(t.randn(in_features,out_features)) #参数的命名规范下面会说明self.b nn.Parameter(t.randn(out_features))def forward(self, x):x x.mm(self.w)return x self.b.expand_as(x) 运行 layer Linear(4,3)
input V(t.randn(2,4))
output layer(input)
output 返回 tensor([[-0.4199, 3.7252, 1.9104],[ 2.3267, 2.0576, -2.9361]], grad_fnAddBackward0) 查看参数 for name, parameter in layer.named_parameters():print(name, parameter) #即wb 返回 w Parameter containing:
tensor([[ 1.1147, -0.8054, -0.7915],[-0.3828, 0.1073, 2.0440],[-0.3297, 0.0465, 0.0759],[ 0.1022, 0.1638, 1.0872]], requires_gradTrue)
b Parameter containing:
tensor([ 1.2872, 2.3990, -0.7711], requires_gradTrue) 可见全连接层的实现非常简单其代码量不超过10行但需注意以下几点 自定义层Linear必须继承nn.Module并且在其构造函数中需调用nn.Module的构造函数即super(Linear, self).__init__() 或nn.Module.__init__(self)推荐使用第一种用法尽管第二种写法更直观。在构造函数__init__中必须自己定义可学习的参数并封装成Parameter如在本例中我们把w和b封装成parameter。parameter是一种特殊的Variable但其默认需要求导requires_grad True感兴趣的读者可以通过nn.Parameter??查看Parameter类的源代码。forward函数实现前向传播过程其输入可以是一个或多个variable对x的任何操作也必须是variable支持的操作。无需写反向传播函数因其前向传播都是对variable进行操作nn.Module能够利用autograd自动实现反向传播这点比Function简单许多。使用时直观上可将layer看成数学概念中的函数调用layer(input)即可得到input对应的结果。它等价于layers.__call__(input)在__call__函数中主要调用的是 layer.forward(x)另外还对钩子做了一些处理。所以在实际使用中应尽量使用layer(x)而不是使用layer.forward(x)关于钩子技术将在下文讲解。Module中的可学习参数可以通过named_parameters()或者parameters()返回迭代器前者会给每个parameter都附上名字使其更具有辨识度。可见利用Module实现的全连接层比利用Function实现的更为简单因其不再需要写反向传播函数。 Module能够自动检测到自己的Parameter并将其作为学习参数。除了parameter之外Module还包含子Module主Module能够递归查找子Module中的parameter。下面再来看看稍微复杂一点的网络多层感知机。 多层感知机的网络结构如图4-1所示它由两个全连接层组成采用函数作为激活函数图中没有画出。 class Perceptron(nn.Module):def __init__(self, in_features, hidden_features, out_features):super(Perceptron, self).__init__()self.layer1 Linear(in_features, hidden_features) #使用的是上面定义的Linear函数self.layer2 Linear(hidden_features, out_features)def forward(self, x):x self.layer1(x)x t.sigmoid(x) #激活函数return self.layer2(x) 调用 perceptron Perceptron(3,4,1)
for name, param in perceptron.named_parameters():print(name, param.size()) 返回 layer1.w torch.Size([3, 4])
layer1.b torch.Size([4])
layer2.w torch.Size([4, 1])
layer2.b torch.Size([1]) 可见即使是稍复杂的多层感知机其实现依旧很简单。这里新增两个知识点 构造函数__init__中可利用前面自定义的Linear层(module)作为当前module对象的一个子module它的可学习参数也会成为当前module的可学习参数。在前向传播函数中我们有意识地将输出变量都命名成x是为了能让Python回收一些中间层的输出从而节省内存。但并不是所有都会被回收有些variable虽然名字被覆盖但其在反向传播仍需要用到此时Python的内存回收模块将通过检查引用计数不会回收这一部分内存。module中parameter的命名规范 对于类似self.param_name nn.Parameter(t.randn(3, 4))命名为param_name对于子Module中的parameter会其名字之前加上当前Module的名字。如对于self.sub_module SubModel()SubModel中有个parameter的名字叫做param_name那么二者拼接而成的parameter name 就是sub_module.param_name。 为方便用户使用PyTorch实现了神经网络中绝大多数的layer这些layer都继承于nn.Module封装了可学习参数parameter并实现了forward函数且很多都专门针对GPU运算进行了CuDNN优化其速度和性能都十分优异。本书不准备对nn.Module中的所有层进行详细介绍具体内容读者可参照官方文档。阅读文档时应主要关注以下几点 构造函数的参数如nn.Linear(in_features, out_features, bias)需关注这三个参数的作用。属性可学习参数子module。如nn.Linear中有weight和bias两个可学习参数不包含子module。输入输出的形状如nn.linear的输入形状是(N, input_features)输出为(Noutput_features)N是batch_size。这些自定义layer对输入形状都有假设输入的不是单个数据而是一个batch。 若想输入一个数据则必须调用unsqueeze(0)函数将数据伪装成batch_size1的batch 下面将从应用层面出发对一些常用的layer做简单介绍更详细的用法请查看文档这里只作概览参考。 2.常用神经网络层 1.图像相关层 图像相关层主要包括卷积层Conv、池化层Pool等这些层在实际使用中可分为一维(1D)、二维(2D)、三维3D池化方式又分为平均池化AvgPool、最大值池化MaxPool、自适应池化AdaptiveAvgPool等。而卷积层除了常用的前向卷积之外还有逆卷积TransposeConv。下面举例说明一些基础的使用。 from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
to_tensor ToTensor() #img - tensor
to_pil ToPILImage()
lena Image.open(imgs/lena.png) #这是个灰度图像
lena 返回图片 #输入是一个batchbatch_size 1
print(lena.size) #1 200 200
input to_tensor(lena).unsqueeze(0) #变成(1,1,200,200)
print(input)
#锐化卷积核
kernel t.ones(3 ,3)/-9
kernel[1][1] 1
conv nn.Conv2d(1, 1, (3,3), 1, bias False)
conv.weight.data kernel.view(1,1,3,3)out conv(V(input))
to_pil(out.data.squeeze(0)) 返回 (200, 200)
tensor([[[[0.6353, 0.6314, 0.6314, ..., 0.6118, 0.6667, 0.5922],[0.6353, 0.6314, 0.6314, ..., 0.6078, 0.6510, 0.5647],[0.6275, 0.6235, 0.6235, ..., 0.4824, 0.4157, 0.3098],...,[0.1961, 0.2078, 0.2078, ..., 0.2510, 0.3098, 0.3412],[0.1922, 0.2000, 0.2039, ..., 0.3098, 0.3686, 0.3804],[0.1843, 0.2078, 0.1961, ..., 0.3569, 0.3961, 0.4078]]]]) 图示 池化层可以看作是一种特殊的卷积层用来下采样。但池化层没有可学习参数其weight是固定的。 pool nn.AvgPool2d(2,2) #平均池化
list(pool.parameters()) #返回[]因为无参数 out pool(input) #对数据进行池化
to_pil(out.data.squeeze(0)) #显示结果 图示 除了卷积层和池化层深度学习中还将常用到以下几个层 Linear全连接层。BatchNorm批规范化层分为1D、2D和3D。除了标准的BatchNorm之外还有在风格迁移中常用到的InstanceNorm层。Dropoutdropout层用来防止过拟合同样分为1D、2D和3D。 下面通过例子来说明它们的使用。1全连接层 #输入batch_size 2,维度为3
input t.randn(2,3)
linear nn.Linear(3,4)
h linear(input)
h 返回 tensor([[ 0.5406, -0.0327, 0.7291, 0.5262],[ 0.1471, -0.1924, 0.8960, 0.7801]], grad_fnAddmmBackward) 2批规范化即归一化层 #4 channel初始化标准差为4均值为0
bn nn.BatchNorm1d(4) #对小批量(mini-batch)的2d或3d输入进行批标准化(Batch Normalization)操作即归一化
bn.weight.data t.ones(4) * 4
bn.bias.data t.zeros(4)bn_out bn(h)
#注意输出的均值和方差
#方差是标准差的平方计算无偏方差分母会减1
#使用unbiasedFalse 分母不减1
bn_out.mean(0), bn_out.var(0, unbiasedFalse) #归一化后平均值为0,方差为标准单位方差 返回 (tensor([0., 0., 0., 0.], grad_fnMeanBackward0),tensor([15.9959, 15.9749, 15.9771, 15.9901], grad_fnVarBackward1)) Batch归一化使用在z上下面激活函数处的例子可见对其进行卷积 - batch归一化 - 激活函数 3dropout正则化层 #每个元素以0.5的概率舍弃实现dropout正则化消除过拟合问题
dropout nn.Dropout(0.5)
o dropout(bn_out)
o #有一半左右的数变成0 返回 tensor([[ 0.0000, 7.9937, -7.9943, -7.9975],[-0.0000, -0.0000, 7.9943, 7.9975]], grad_fnMulBackward0) 以上很多例子中都对module的属性直接操作其大多数是可学习参数一般会随着学习的进行而不断改变。实际使用中除非需要使用特殊的初始化应尽量不要直接修改这些参数。 2.激活函数 1ReLu relu nn.ReLU(inplaceTrue)
input t.randn(2,3)
print(input)
output relu(input)
print(output) #小于0的都被截断为0
#等价于input.clamp(min0) 返回 tensor([[ 1.2619, -0.9128, 0.6259],[-1.4834, 0.7297, -0.8562]])
tensor([[1.2619, 0.0000, 0.6259],[0.0000, 0.7297, 0.0000]]) ReLU函数有个inplace参数如果设为True它会把输出直接覆盖到输入中这样可以节省内存/显存。之所以可以覆盖是因为在计算ReLU的反向传播时只需根据输出就能够推算出反向传播的梯度。 但是只有少数的autograd操作支持inplace操作如tensor.sigmoid_()除非你明确地知道自己在做什么否则一般不要使用inplace操作。 在以上的例子中基本上都是将每一层的输出直接作为下一层的输入这种网络称为前馈传播网络feedforward neural network。 ⚠️ 对于此类网络如果每次都写复杂的forward函数会有些麻烦在此就有两种简化方式ModuleList和Sequential。其中Sequential是一个特殊的module它包含几个子Module前向传播时会将输入一层接一层的传递下去。ModuleList也是一个特殊的module可以包含几个子module可以像用list一样使用它但不能直接把输入传给ModuleList。下面举例说明。 1)Sequential #Sequential的三种写法
#第一种
net1 nn.Sequential()
net1.add_module(conv, nn.Conv2d(3,3,3))
net1.add_module(batchnorm, nn.BatchNorm2d(3))
net1.add_module(activation_layer, nn.ReLU())#第二种
net2 nn.Sequential(nn.Conv2d(3,3,3),nn.BatchNorm2d(3),nn.ReLU())#第三种
from collections import OrderedDict
net3 nn.Sequential(OrderedDict([(conv1, nn.Conv2d(3,3,3)),(bn1, nn.BatchNorm2d(3)),(relu1,nn.ReLU())]))
print(net1:, net1)
print(net2:, net2)
print(net3:, net3) 返回 net1: Sequential((conv): Conv2d(3, 3, kernel_size(3, 3), stride(1, 1))(batchnorm): BatchNorm2d(3, eps1e-05, momentum0.1, affineTrue, track_running_statsTrue)(activation_layer): ReLU()
)
net2: Sequential((0): Conv2d(3, 3, kernel_size(3, 3), stride(1, 1))(1): BatchNorm2d(3, eps1e-05, momentum0.1, affineTrue, track_running_statsTrue)(2): ReLU()
)
net3: Sequential((conv1): Conv2d(3, 3, kernel_size(3, 3), stride(1, 1))(bn1): BatchNorm2d(3, eps1e-05, momentum0.1, affineTrue, track_running_statsTrue)(relu1): ReLU()
) #可根据名字后序号取出子module
net1.conv, net2[0], net3.conv1 返回 (Conv2d(3, 3, kernel_size(3, 3), stride(1, 1)),Conv2d(3, 3, kernel_size(3, 3), stride(1, 1)),Conv2d(3, 3, kernel_size(3, 3), stride(1, 1))) 调用方式为 input t.rand(1,3,4,4)
output1 net1(input)
output2 net2(input)
output3 net3(input)
output4 net3.relu1(net1.batchnorm(net1.conv(input))) 2ModuleList modellist nn.ModuleList([nn.Linear(3,4), nn.ReLU(), nn.Linear(4,2)])
input t.randn(1,3)
for model in modellist:input model(input)
print(input) 返回 tensor([[-0.6547, 0.8027]], grad_fnAddmmBackward) # 下面会报错,因为modellist没有实现forward方法
output modellist(input) 看到这里读者可能会问为何不直接使用Python中自带的list而非要多此一举呢这是因为ModuleList是Module的子类当在Module中使用它的时候就能自动识别为子module。 下面举一个实现forward的例子进行说明 class MyModule(nn.Module):def __init__(self):super(MyModule, self).__init__()self.list [nn.Linear(3,4), nn.ReLU()]self.module_list nn.ModuleList([nn.Conv2d(3,3,3), nn.ReLU()])def forward(self):pass
model MyModule()
model 返回 MyModule((module_list): ModuleList((0): Conv2d(3, 3, kernel_size(3, 3), stride(1, 1))(1): ReLU())
) 查看参数 for name, param in model.named_parameters():print(name, param.size()) 返回 module_list.0.weight torch.Size([3, 3, 3, 3])
module_list.0.bias torch.Size([3]) 可见list中的子module并不能被主module所识别而ModuleList中的子module能够被主module所识别。这意味着如果用list保存子module将无法调整其参数因其未加入到主module的参数中。 除ModuleList之外还有ParameterList其是一个可以包含多个parameter的类list对象。在实际应用中使用方式与ModuleList类似。 如果在构造函数__init__中用到list、tuple、dict等对象时一定要思考是否应该用ModuleList或ParameterList代替。 3.循环神经网络层后面好好看看 近些年随着深度学习和自然语言处理的结合加深RNN的使用也越来越多关于RNN的基础知识推荐阅读colah的文章1入门。PyTorch中实现了如今最常用的三种RNNRNNvanilla RNN、LSTM和GRU。此外还有对应的三种RNNCell。 RNN和RNNCell层的区别在于前者一次能够处理整个序列而后者一次只处理序列中一个时间点的数据前者封装更完备更易于使用后者更具灵活性。实际上RNN层的一种后端实现方式就是调用RNNCell来实现的。 t.manual_seed(1000)
# 输入batch_size3序列长度都为2序列中每个元素占4维
input t.randn(2, 3, 4)
# lstm输入向量4维隐藏元31层
lstm nn.LSTM(4, 3, 1)
# 初始状态1层batch_size33个隐藏元
h0 t.randn(1, 3, 3)
c0 t.randn(1, 3, 3)
out, hn lstm(input, (h0, c0))
out 返回 tensor([[[-0.3610, -0.1643, 0.1631],[-0.0613, -0.4937, -0.1642],[ 0.5080, -0.4175, 0.2502]],[[-0.0703, -0.0393, -0.0429],[ 0.2085, -0.3005, -0.2686],[ 0.1482, -0.4728, 0.1425]]], grad_fnStackBackward) t.manual_seed(1000)
input t.randn(2, 3, 4)
# 一个LSTMCell对应的层数只能是一层
lstm nn.LSTMCell(4, 3)
hx t.randn(3, 3)
cx t.randn(3, 3)
out []
for i_ in input:hx, cxlstm(i_, (hx, cx))out.append(hx)
t.stack(out) 返回 tensor([[[-0.3610, -0.1643, 0.1631],[-0.0613, -0.4937, -0.1642],[ 0.5080, -0.4175, 0.2502]],[[-0.0703, -0.0393, -0.0429],[ 0.2085, -0.3005, -0.2686],[ 0.1482, -0.4728, 0.1425]]], grad_fnStackBackward) # 有4个词每个词用5维的向量表示
embedding nn.Embedding(4, 5)
# 可以用预训练好的词向量初始化embedding
embedding.weight.data t.arange(0,20).view(4,5) input t.arange(3, 0, -1).long()
output embedding(input)
output 返回 tensor([[15, 16, 17, 18, 19],[10, 11, 12, 13, 14],[ 5, 6, 7, 8, 9]], grad_fnEmbeddingBackward) 4.损失函数 这里以分类中最常用的交叉熵损失CrossEntropyloss为例说明 #batch_size 3,计算对应每个类别的分数只有两个类别
score t.randn(3,2)
#三个样本分别属于101类label必须是LongTensor
label t.Tensor([1,0,1]).long()#loss与普通的layer无差异
criterion nn.CrossEntropyLoss()
loss criterion(score, label)
loss #返回tensor(0.5944) 5.优化器 PyTorch将深度学习中常用的优化方法全部封装在torch.optim中其设计十分灵活能够很方便的扩展成自定义的优化方法。 所有的优化方法都是继承基类optim.Optimizer并实现了自己的优化步骤。下面就以最基本的优化方法——随机梯度下降法SGD举例说明。这里需重点掌握 优化方法的基本使用方法如何对模型的不同部分设置不同的学习率如何调整学习率 #首先定义一个LeNet网络
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.features nn.Sequential(nn.Conv2d(3,6,5),nn.ReLU(),nn.MaxPool2d(2,2),nn.Conv2d(6,16,5),nn.ReLU(),nn.MaxPool2d(2,2))self.classifier nn.Sequential( #全连接层nn.Linear(16*5*5, 120),nn.ReLU(),nn.Linear(120, 84),nn.ReLU(),nn.Linear(84, 10))def forward(self, x):x self.features(x)x x.view(-1,16*5*5) #将数据扁平化处理用传入全连接层x self.classifier(x)return x
net Net() from torch import optim
optimizer optim.SGD(paramsnet.parameters(), lr1)
optimizer.zero_grad() #梯度清零等价于net.zero_grad()input t.randn(1,3,32,32)
output net(input)
output.backward(output) #fake backward后向传播计算梯度optimizer.step() #执行优化 # 为不同子网络设置不同的学习率在finetune中经常用到
# 如果对某个参数不指定学习率就使用最外层的默认学习率
optimizer optim.SGD([{params : net.features.parameters()}, #学习率为1e-5{params : net.classifier.parameters(), lr:1e-2}], lr1e-5)
optimizer 返回 SGD (
Parameter Group 0dampening: 0lr: 1e-05momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.01momentum: 0nesterov: Falseweight_decay: 0
) # 只为两个全连接层设置较大的学习率其余层的学习率较小
special_layers nn.ModuleList([net.classifier[0], net.classifier[2]])
special_layers_params list(map(id, special_layers.parameters()))
base_params filter(lambda p: id(p) not in special_layers_params, net.parameters())optimizer t.optim.SGD([{params: base_params},{params: special_layers.parameters(), lr: 0.01}], lr0.001 )
optimizer 返回 SGD (
Parameter Group 0dampening: 0lr: 0.001momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.01momentum: 0nesterov: Falseweight_decay: 0
) 对于如何调整学习率主要有两种做法 一种是更简单也是较为推荐的做法——新建优化器由于optimizer十分轻量级构建开销很小故而可以构建新的optimizer。但是后者对于使用动量的优化器如Adam会丢失动量等状态信息可能会造成损失函数的收敛出现震荡等情况。一种是修改optimizer.param_groups中对应的学习率1新建优化器 #方法1:调整学习率新建一个optimizer
old_lr 0.1
optimizer1 optim.SGD([{params: net.features.parameters()},{params: net.classifier.parameters(), lr:old_lr *0.1}
], lr 1e-5)
optimizer1 返回 SGD (
Parameter Group 0dampening: 0lr: 1e-05momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.010000000000000002momentum: 0nesterov: Falseweight_decay: 0
) 2调整学习率 #方法2:调整学习率手动衰减保存动量
for param_group in optimizer.param_groups:param_group[lr] * 0.1
optimizer 返回 SGD (
Parameter Group 0dampening: 0lr: 0.0001momentum: 0nesterov: Falseweight_decay: 0Parameter Group 1dampening: 0lr: 0.001momentum: 0nesterov: Falseweight_decay: 0
) 3.nn.functional nn中还有一个很常用的模块nn.functionalnn中的大多数layer在functional中都有一个与之相对应的函数。 nn.functional中的函数和nn.Module的主要区别在于: nn.Module实现的layers是一个特殊的类都是由class layer(nn.Module)定义会自动提取可学习的参数nn.functional中的函数更像是纯函数由def function(input)定义。下面举例说明functional的使用并指出二者的不同之处。 input t.randn(2,3)
model nn.Linear(3,4)
output1 model(input)
#使用上面使用的w,b两种写法返回的结果是相同的
output2 nn.functional.linear(input, model.weight, model.bias)
output1 output2 返回 tensor([[1, 1, 1, 1],[1, 1, 1, 1]], dtypetorch.uint8) b nn.functional.relu(input)
b2 nn.ReLU()(input)
b b2 返回 tensor([[1, 1, 1],[1, 1, 1]], dtypetorch.uint8) 此时读者可能会问应该什么时候使用nn.Module什么时候使用nn.functional呢 答案很简单如果模型有可学习的参数最好用nn.Module否则既可以使用nn.functional也可以使用nn.Module二者在性能上没有太大差异具体的使用取决于个人的喜好。 如激活函数ReLU、sigmoid、tanh池化MaxPool等层由于没有可学习参数则可以使用对应的functional函数代替而对于卷积、全连接等具有可学习参数的网络建议使用nn.Module。 下面举例说明如何在模型中搭配使用nn.Module和nn.functional。 ⚠️另外虽然dropout操作也没有可学习操作但建议还是使用nn.Dropout而不是nn.functional.dropout因为dropout在训练和测试两个阶段的行为有所差别使用nn.Module对象能够通过model.eval操作加以区分。 from torch.nn import functional as F
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 nn.Conv2d(3,6,5)self.conv2 nn.Conv2d(6,16,5)self.fc1 nn.Linear(16*5*5, 120)self.fc2 nn.Linear(120,84)self.fc3 nn.Linear(84, 10)def forward(self,x):x F.pool(F.relu(self.conv1(x)),2)x F.pool(F.relu(self.conv2(x)),2)x x.view(-1, 16*5*5)x F.relu(self.fc1(x))x F.relu(self.fc2(x)) x self.fc3(x)return x 对于不具备可学习参数的层激活层、池化层等将它们用函数代替这样则可以不用放置在构造函数__init__中。对于有可学习参数的模块也可以用functional来代替只不过实现起来较为繁琐需要手动定义参数parameter如前面实现自定义的全连接层就可将weight和bias两个参数单独拿出来在构造函数中初始化为parameter。 class MyLinear(nn.Module):def __init__(self):super(MyLinear, self).__init__()self.weight nn.Parameter(t.randn(3,4))self.bias nn.Parameter(t.zeros(3))def forward(self):return F.linear(input, weight, bias) 4.初始化策略 在深度学习中参数的初始化十分重要良好的初始化能让模型更快收敛并达到更高水平而糟糕的初始化则可能使得模型迅速瘫痪。 PyTorch中nn.Module的模块参数都采取了较为合理的初始化策略因此一般不用我们考虑当然我们也可以用自定义初始化去代替系统的默认初始化。而当我们在使用Parameter时自定义初始化则尤为重要因t.Tensor()返回的是内存中的随机数很可能会有极大值这在实际训练网络中会造成溢出或者梯度消失。 PyTorch中nn.init模块就是专门为初始化而设计如果某种初始化策略nn.init不提供用户也可以自己直接初始化。 使用的初始化策略是 torch.nn.init.xavier_normal_(tensor, gain1) 参数 tensor – n维的torch.Tensorgain - 可选的缩放因子用一个正态分布生成值填充输入的张量或变量。结果张量中的值采样自均值为0标准差为gain * sqrt(2/(fan_in fan_out))的正态分布。也被称为Glorot initialisation. 1直接初始化 #利用nn.init初始化
from torch.nn import init
linear nn.Linear(3,4)t.manual_seed(1)
#等价于linear.weight.data.normal_(0, std),std是正态分布的标准差
init.xavier_normal_(linear.weight) 返回 Parameter containing:
tensor([[ 0.3535, 0.1427, 0.0330],[ 0.3321, -0.2416, -0.0888],[-0.8140, 0.2040, -0.5493],[-0.3010, -0.4769, -0.0311]], requires_gradTrue) 2手动初始化 #手动初始化
import math
t.manual_seed(1)#xavier初始化的计算公式
std math.sqrt(2)/math.sqrt(7.)#34 7
linear.weight.data.normal_(0, std) 返回 tensor([[ 0.3535, 0.1427, 0.0330],[ 0.3321, -0.2416, -0.0888],[-0.8140, 0.2040, -0.5493],[-0.3010, -0.4769, -0.0311]]) #对模型的所有参数进行初始化
for name, params in net.named_parameters():if name.find(linear) ! -1:#init linearparams[0] #weightparams[1] #biaselif name.find(conv) ! -1:passelif name.find(norm) ! -1:pass 5.nn.Module深入分析 如果想要更深入地理解nn.Module究其原理是很有必要的。首先来看看nn.Module基类的构造函数 其中每个属性的解释如下 _parameters字典保存用户直接设置的parameterself.param1 nn.Parameter(t.randn(3, 3))会被检测到在字典中加入一个key为paramvalue为对应parameter的item。而self.submodule nn.Linear(3, 4)中的parameter则不会存于此。_modules子module通过self.submodel nn.Linear(3, 4)指定的子module会保存于此。_buffers缓存。如batchnorm使用momentum机制每次前向传播需用到上一次前向传播的结果。_backward_hooks与_forward_hooks钩子技术用来提取中间变量类似variable的hook。trainingBatchNorm与Dropout层在训练阶段和测试阶段中采取的策略不同通过判断training值来决定前向传播策略。上述几个属性中_parameters、_modules和_buffers这三个字典中的键值都可以通过self.key方式获得效果等价于self._parameters[key]. 下面举例说明 1定义网络 class Net(nn.Module):def __init__(self):super(Net, self).__init__()#等价于self.register_parameter(param1, nn.Parameter(t.randn(3,3)))self.param1 nn.Parameter(t.rand(3,3))self.submodel1 nn.Linear(3,4)def forward(self, input):x self.param1.mm(input)x self.submodel1(x)return x
net Net()
net 返回 Net((submodel1): Linear(in_features3, out_features4, biasTrue)
) 2 net._modules #查看设置的子模块 返回 OrderedDict([(submodel1, Linear(in_features3, out_features4, biasTrue))]) 另一种查看子模块方法 for name, submodel in net.named_modules():print(name, submodel) 返回 Net((submodel1): Linear(in_features3, out_features4, biasTrue)
)
submodel1 Linear(in_features3, out_features4, biasTrue) nn.Module在实际使用中可能层层嵌套一个module包含若干个子module每一个子module又包含了更多的子module。 为方便用户访问各个子modulenn.Module实现了很多方法如函数children可以查看直接子module函数module可以查看所有的子module包括当前module。 与之相对应的还有函数named_childen和named_modules其能够在返回module列表的同时返回它们的名字。 3 net._parameters #查看网络中使用的参数 返回 OrderedDict([(param1, Parameter containing:tensor([[0.3398, 0.5239, 0.7981],[0.7718, 0.0112, 0.8100],[0.6397, 0.9743, 0.8300]], requires_gradTrue))]) 另一种查看参数方法 net.param1 #等价于net._parameters[param1] 返回 Parameter containing:
tensor([[0.3398, 0.5239, 0.7981],[0.7718, 0.0112, 0.8100],[0.6397, 0.9743, 0.8300]], requires_gradTrue) 另一种查看参数方法上面的方法没办法查看到层中使用的w,b参数下面的方法可以查看w,b参数 for name, param in net.named_parameters():print(name, param.size()) 返回 param1 torch.Size([3, 3])
submodel1.weight torch.Size([4, 3])
submodel1.bias torch.Size([4]) 4_buffers bn nn.BatchNorm1d(2)
input t.rand(3,2)
output bn(input)
bn._buffers #上一次前向传播结果 返回 OrderedDict([(running_mean, tensor([0.0514, 0.0749])),(running_var, tensor([0.9116, 0.9068])),(num_batches_tracked, tensor(1))]) 5)training input t.arange(0, 12).float().view(3,4)
model nn.Dropout()
#在训练阶段会有一半的值被设置为0
model(input) 返回 tensor([[ 0., 0., 4., 0.],[ 8., 0., 0., 14.],[ 0., 0., 0., 22.]]) #如果将training设置为False那么dropout在测试阶段将什么都不做
model.training False
model(input) 返回 tensor([[ 0., 1., 2., 3.],[ 4., 5., 6., 7.],[ 8., 9., 10., 11.]]) 对于batchnorm、dropout、instancenorm等在训练和测试阶段行为差距巨大的层如果在测试时不将其training值设为True则可能会有很大影响这在实际使用中要千万注意。 虽然可通过直接设置training属性来将子module设为train和eval模式但这种方式较为繁琐因如果一个模型具有多个dropout层就需要为每个dropout层指定training属性。 更为推荐的做法是调用model.train()函数它会将当前module及其子module中的所有training属性都设为True相应的model.eval()函数会把training属性都设为False。 print(net.training, net.submodel1.training)
net.eval()
net.training, net.submodel1.training 返回 True True
(False, False) 6register_forward_hook/register_backward_hook中间变量 这两个函数的功能类似于variable函数的register_hook可在module前向传播或反向传播时注册钩子。每次前向传播执行结束后会执行钩子函数hook。前向传播的钩子函数具有如下形式hook(module, input, output) - None而反向传播则具有如下形式hook(module, grad_input, grad_output) - Tensor or None。 钩子函数不应修改输入和输出并且在使用后应及时删除以避免每次都运行钩子增加运行负载。钩子函数主要用在获取某些中间结果的情景如中间某一层的输出或某一层的梯度。这些结果本应写在forward函数中但如果在forward函数中专门加上这些处理可能会使处理逻辑比较复杂这时候使用钩子技术就更合适一些。 下面考虑一种场景有一个预训练好的模型需要提取模型的某一层不是最后一层的输出作为特征进行分类但又不希望修改其原有的模型定义文件这时就可以利用钩子函数。 下面给出实现的伪代码 model VGG()
features t.Tensor()
def hook(module, input, output):把这层的输出拷贝到features中features.copy_(output.data)handle model.layer8.register_forward_hook(hook)
_ model(input)
# 用完hook后删除
handle.remove() 7__getattr__ / __setattr__nn.Module对象在构造函数中的行为看起来有些怪异如果想要真正掌握其原理就需要看两个魔法方法__getattr__和__setattr__。 在Python中有两个常用的buildin方法getattr和setattrgetattr(obj, attr1)等价于obj.attr如果getattr函数无法找到所需属性Python会转而调用obj.__getattr__(attr1)方法即getattr函数无法找到的交给__getattr__函数处理没有实现__getattr__或者__getattr__也无法处理的就会raise AttributeError。 setattr(obj, name, value)等价于obj.namevalue如果obj对象实现了__setattr__方法setattr会直接调用obj.__setattr__(name, value)否则调用buildin方法。 总结一下 result obj.name会调用buildin函数getattr(obj, name)如果该属性找不到会调用obj.__getattr__(name)obj.name value会调用buildin函数setattr(obj, name, value)如果obj对象实现了__setattr__方法setattr会直接调用obj.__setattr__(name, value) nn.Module实现了自定义的__setattr__函数当执行module.namevalue时会在__setattr__中判断value是否为Parameter或nn.Module对象如果是则将这些对象加到_parameters和_modules两个字典中而如果是其它类型的对象如Variable、list、dict等则调用默认的操作将这个值保存在__dict__中。 1》 module nn.Module() #直接使用nn.Module()对象
module.param nn.Parameter(t.ones(2,2)) #设置参数
module._parameters 返回 OrderedDict([(param, Parameter containing:tensor([[1., 1.],[1., 1.]], requires_gradTrue))]) 2》 submodule1 nn.Linear(2,2)
submodule2 nn.Linear(2,2)
module_list [submodule1, submodule2]
#对于list对象调用buildin函数保存在__dict__中
module.submodules module_list #设置module因为这里使用的是list所以会存放在__dict__
print(_modules:, module._modules)
print(__dict__[submodules]:, module.__dict__.get(submodules)) 返回 _modules: OrderedDict()
__dict__[submodules]: [Linear(in_features2, out_features2, biasTrue), Linear(in_features2, out_features2, biasTrue)] 3》 module_list nn.ModuleList(module_list)#将上面的list类型转成nn.Module对象类型
module.submodules module_list
#判断是否为nn.Module对象类型
print(ModuleList is instance of nn.Module: , isinstance(module_list, nn.Module))
print(_modules: , module._modules) #这样值就会存储在这里而不是__dict__
print(__dict__[submodules]:, module.__dict__.get(submodules)) 返回 ModuleList is instance of nn.Module: True
_modules: OrderedDict([(submodules, ModuleList((0): Linear(in_features2, out_features2, biasTrue)(1): Linear(in_features2, out_features2, biasTrue)
))])
__dict__[submodules]: None 4》 因_modules和_parameters中的item未保存在__dict__中所以默认的getattr方法无法获取它因而nn.Module实现了自定义的__getattr__方法如果默认的getattr无法处理就调用自定义的__getattr__方法尝试从_modules、_parameters和_buffers这三个字典中获取。 getattr(module, training)#等价于module.training
#如果没有得到值就会调用module.__getattr__(training) 返回 True module.attr1 2
getattr(module, attr1) #返回2 getattr(module, param) 返回 Parameter containing:
tensor([[1., 1.],[1., 1.]], requires_gradTrue) 8state_dict()/load_state_dict() 在PyTorch中保存模型十分简单所有的Module对象都具有state_dict()函数返回当前Module所有的状态数据。将这些状态数据保存后下次使用模型时即可利用model.load_state_dict()函数将状态加载进来。优化器optimizer也有类似的机制不过一般并不需要保存优化器的运行状态 #保存模型
t.save(net.state_dict(), net.pth) #然后就会在本地文件夹中生成一个net.pth文件#加载已经保存的模型
net2 Net()
net2.load_state_dict(t.load(net.pth)) 9运行在GPU 将Module放在GPU上运行也十分简单只需两步 model model.cuda()将模型的所有参数转存到GPUinput.cuda()将输入数据也放置到GPU上至于如何在多个GPU上并行计算PyTorch也提供了两个函数可实现简单高效的并行GPU计算 nn.parallel.data_parallel(module, inputs, device_idsNone, output_deviceNone, dim0, module_kwargsNone)class torch.nn.DataParallel(module, device_idsNone, output_deviceNone, dim0)可见二者的参数十分相似通过device_ids参数可以指定在哪些GPU上进行优化output_device指定输出到哪个GPU上。 唯一的不同就在于前者直接利用多GPU并行计算得出结果而后者则返回一个新的module能够自动在多GPU上进行并行加速。 # method 1
new_net nn.DataParallel(net, device_ids[0, 1])
output new_net(input)# method 2
output nn.parallel.data_parallel(new_net, input, device_ids[0, 1]) DataParallel并行的方式是将输入一个batch的数据均分成多份分别送到对应的GPU进行计算各个GPU得到的梯度累加。与Module相关的所有数据也都会以浅复制的方式复制多份在此需要注意在module中属性应该是只读的。 6.nn和autograd的关系 nn.Module利用的也是autograd技术其主要工作是实现前向传播。在forward函数中nn.Module对输入的tensor进行的各种操作本质上都是用到了autograd技术。这里需要对比autograd.Function和nn.Module之间的区别 autograd.Function利用了Tensor对autograd技术的扩展为autograd实现了新的运算op不仅要实现前向传播还要手动实现反向传播nn.Module利用了autograd技术对nn的功能进行扩展实现了深度学习中更多的层。只需实现前向传播功能autograd即会自动实现反向传播nn.functional是一些autograd操作的集合是经过封装的函数作为两大类扩充PyTorch接口的方法我们在实际使用中应该如何选择呢 如果某一个操作在autograd中尚未支持那么只能实现Function接口对应的前向传播和反向传播。如果某些时候利用autograd接口比较复杂则可以利用Function将多个操作聚合实现优化正如第三章所实现的Sigmoid一样比直接利用autograd低级别的操作要快。而如果只是想在深度学习中增加某一层使用nn.Module进行封装则更为简单高效。 7.小试牛刀搭建ResNet Kaiming He的深度残差网络ResNet[^7]在深度学习的发展中起到了很重要的作用ResNet不仅一举拿下了当年CV下多个比赛项目的冠军更重要的是这一结构解决了训练极深网络时的梯度消失问题。 首先来看看ResNet的网络结构这里选取的是ResNet的一个变种ResNet34。 ResNet的网络结构如图4-2所示可见除了最开始的卷积池化和最后的池化全连接之外网络中有很多结构相似的单元这些重复单元的共同点就是有个跨层直连的shortcut。ResNet中将一个跨层直连的单元称为Residual block其结构如图4-3所示左边部分是普通的卷积网络结构右边是直连但如果输入和输出的通道数不一致或其步长不为1那么就需要有一个专门的单元将二者转成一致使其可以相加。 另外我们可以发现Residual block的大小也是有规律的在最开始的pool之后有连续的几个一模一样的Residual block单元这些单元的通道数一样在这里我们将这几个拥有多个Residual block单元的结构称之为layer注意和之前讲的layer区分开来这里的layer是几个层的集合。 考虑到Residual block和layer出现了多次我们可以把它们实现为一个子Module或函数。这里我们将Residual block实现为一个子moduke而将layer实现为一个函数。下面是实现代码规律总结如下 对于模型中的重复部分实现为子module或用函数生成相应的module make_layernn.Module和nn.Functional结合使用尽量使用nn.Seqential [^7]: He K, Zhang X, Ren S, et al. Deep residual learning for image recognition[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2016: 770-778. from torch import nn
import torch as t
from torch.nn import functional as F 定义网络 class ResidualBlock(nn.Module):实现子module: Residual Blockdef __init__(self, inchannel, outchannel, stride1, shortcutNone):super(ResidualBlock, self).__init__()self.left nn.Sequential(nn.Conv2d(inchannel,outchannel,3,stride, 1,biasFalse),nn.BatchNorm2d(outchannel),nn.ReLU(inplaceTrue),nn.Conv2d(outchannel,outchannel,3,1,1,biasFalse),nn.BatchNorm2d(outchannel) )self.right shortcutdef forward(self, x):out self.left(x)residual x if self.right is None else self.right(x)out residualreturn F.relu(out)class ResNet(nn.Module):实现主moduleResNet34ResNet34 包含多个layer每个layer又包含多个residual block用子module来实现residual block用_make_layer函数来实现layerdef __init__(self, num_classes1000):super(ResNet, self).__init__()# 前几层图像转换self.pre nn.Sequential(nn.Conv2d(3, 64, 7, 2, 3, biasFalse),nn.BatchNorm2d(64),nn.ReLU(inplaceTrue),nn.MaxPool2d(3, 2, 1))# 重复的layer分别有3463个residual blockself.layer1 self._make_layer( 64, 64, 3)self.layer2 self._make_layer( 64, 128, 4, stride2)self.layer3 self._make_layer( 128, 256, 6, stride2)self.layer4 self._make_layer( 256, 512, 3, stride2)#分类用的全连接self.fc nn.Linear(512, num_classes)def _make_layer(self, inchannel, outchannel, block_num, stride1):构建layer,包含多个residual blockshortcut nn.Sequential(nn.Conv2d(inchannel,outchannel,1,stride, biasFalse),nn.BatchNorm2d(outchannel))layers []layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))for i in range(1, block_num):layers.append(ResidualBlock(outchannel, outchannel))return nn.Sequential(*layers)def forward(self, x):x self.pre(x)x self.layer1(x)x self.layer2(x)x self.layer3(x)x self.layer4(x)x F.avg_pool2d(x, 7)x x.view(x.size(0), -1)return self.fc(x) 调用 model ResNet()
input t.randn(1, 3, 224, 224)
o model(input) 感兴趣的读者可以尝试实现Google的Inception网络结构或ResNet的其它变体看看如何能够简洁明了地实现它实现代码尽量控制在80行以内本例去掉空行和注释总共不超过50行。 另外与PyTorch配套的图像工具包torchvision已经实现了深度学习中大多数经典的模型其中就包括ResNet34读者可以通过下面两行代码使用 from torchvision import models
model models.resnet34() 本例中ResNet34的实现就是参考了torchvision中的实现并做了简化感兴趣的读者可以阅读相应的源码比较这里的实现和torchvision中实现的不同。 转载于:https://www.cnblogs.com/wanghui-garcia/p/10633458.html