Pytorch-book之前馈神经网络(二)

295 阅读6分钟

torch.nn.Module是nn中最重要的类,定义网络时需要继承它并实现forward(input)

1. 前馈神经网络

1.1 手写LeNet

  • 网络结构
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.conv1 = nn.Conv2d(1,6,5)#默认stride=1
        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.max_pool2d(F.relu(self.conv1(x)),(2,2))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)
        
        x = x.view(x.size()[0])#注意view得到改变纬度后的tensor,但和原tensor是共享内存的
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
net = Net()
print(net)

  • 查看参数
#net.parameters()查看具体值,net.named_parameters()查看名字和对应的纬度
net.parameters()#生成器<generator object Module.parameters at 0x11ab719d0>
params = list(net.parameters())#常转化成列表,长度为10的矩阵值,5个weight+5个bias

for name, parameters in net.named_parameters():
    print(name,":",parameters.size())
  • 整体训练流程
 #输入输出
inpt = t.randn(1,1,32,32)#bs,c,w,h
out = net(inpt)#纬度是(bs,10)
#损失函数
target = t.arange(0,10).view(1,10).float()
criterion = nn.MSELoss()
loss = criterion(out,target)
#优化器:optimizer用于更新参数
import torch.optim as optim
optimizer = optim.SGD(net.parameters(),lr = 0.01)
optimizer.zero_grad()
#反向传播
net.zero_grad()
print('反向传播之前 conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向传播之后 conv1.bias的梯度')
print(net.conv1.bias.grad)
#更新参数
optimizer.step()

1.2 手写线性层

class Linear(nn.Module):
    def __init__(self,in_features,out_features):
        super(Linear,self).__init__()
        self.w = nn.Parameter(t.randn(in_features,out_features))#用nn.Parameter封装成特殊的tensor
        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)#输入4输出3
input = t.randn(2,4)#2个样本
output = layer(input)
output

1.3 手写感知机

#手写感知机
class Perception(nn.Module):
    def __init__(self,in_features,hidden_features,out_features):
        super(Perception,self).__init__()
        self.layer1 = Linear(in_features,hidden_features)#一个linear就是一个w*x+b而已
        self.layer2 = Linear(hidden_features,out_features)
    def forward(self,x):
        x = layer1(x)
        x = t.sigmoid(x)
        return self.layer2(x)

1.4 常用神经网络层

  • 卷积层
    注意:
    (1) transforms.ToTensor() 将numpy的ndarray或PIL.Image读的图片转换成形状为(C,H, W)的Tensor格式,且/255归一化到[0,1.0]之间
    (2) 通道的具体顺序与cv2读的还是PIL.Image读的图片有关系
    cv2:(B,G,R)
    PIL.Image:(R, G, B)
from PIL import Image
#__call__允许一个类的实例像函数一样被调用
#两个类,ToTensor()(img)这样使用,会调用__call__方法,也就是F.to_tensor()
from torchvision.transforms import ToTensor,ToPILImage
#from torchvision.transforms.functional import to_tensor

lena = Image.open('imgs/lena.png')#可以显示出图片
input = ToTensor()(lena).unsqueeze(0)#图片->tensor,再调用unsqeeze使bs=1

#锐化卷积和示例
#设置一个输入输出1通道,大小为3*3,weight为指定值的kernel
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)#指定conv.weight.data的值,conv是nn.parameter,不能直接用tensor赋值!

out = conv(input)
ToPILImage()(out.squeeze(0))
  • 池化层
    torch.nn.functional.avg_pool2d((input),(2,2))是往方法中传参数,得到的是结果,而nn.AvgPool2d(2,2)是定义的nn.Parameter,两者是不一样的!
#池化层示例:没有参数
pool = nn.AvgPool2d(2,2)#或者F.max_pool2d()
list(pool.parameters())
  • 全连接层
input = t.randn(2,3)
linear = nn.Linear(3,4)
h = linear(input)
  • bn层
  1. 当输入是(N,C)时,BatchNorm1d(C)
  2. 当输入是(N,C,H,W)时,BatchNorm2d(C)
    输入输出同纬度
bn = nn.BatchNorm1d(4)#与特征数对应
bn.weight.data = t.ones(4) * 4#初始化w
bn.bias.data = t.zeros(4)#初始化b
bn_out = bn(h)
bn_out.mean(0), bn_out.var(0,unbiased = False)#方差是标准差的平方,计算无偏方差分母会减1,使用unbiased=False 分母不减1
  • dropout层
dropout = nn.Dropout(0.5)#概率
o = dropout(bn_out)#输出o会发现bn_out有一半参数都是0了
  • 激活层
relu = nn.ReLU(inplace = True)#inplace直接覆盖
input = t.randn(2,3)
print(input)
output = relu(input)
print(output)

1.5 简化前馈网络:sequential和modulelist

  • sequential的三种写法

    • (1) add_module用名字区分
     net1 = nn.Sequential()
     net1.add_module('conv',nn.Conv2d(3,3,3))
     net1.add_module('batchnorm',nn.BatchNorm2d(3))
     net1.add_module('active_layer',nn.ReLU())
    
    • (2) 直接写在定义里面:用012区分
    net2 = nn.Sequential(
         nn.Conv2d(3, 3, 3),
         nn.BatchNorm2d(3),
         nn.ReLU()
         ) 
     - (3) dict用名字区分
     ```python
     from collections import OrderedDict
    net3= nn.Sequential(OrderedDict([
             ('conv1', nn.Conv2d(3, 3, 3)),
             ('bn1', nn.BatchNorm2d(3)),
             ('relu1', nn.ReLU())
           ]))
    

    • 取出子模块:可根据名字或序号取出子module
    #取出子module:可根据名字或序号取出子module
    net1.conv, net2[0], net3.conv1
    
    • 使用:自动实现forward方法
      output = net1(input)
      
  • modulelist:无forward方法,不能output = modellist(input)

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)
#与list的区别:放在module中会被识别成子类  
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#注意:打印网络的时候只有子类会被打印出,nn.parameter()都不行

1.6 损失函数

criterion = nn.CrossEntropyLoss()#也是调用的__call__方法
loss = criterion(score,label)

1.7 优化器

所有优化器都继承自torch.optim.Optimizer()

  • DCG动态计算图 属性的含义:
    • data: 变量中存储的值,如x中存储着1,y中存储着2,z中存储着3
    • requires_grad:该变量有两个值,True 或者 False,如果为True,则加入到反向传播图中参与计算。
    • grad:该属性存储着相关的梯度值。当requires_grad为False时,该属性为None。即使 requires_grad为True,也必须在调用其他节点的backward()之后,该变量的grad才会保存相关的梯度值。否则为None
    • grad_fn:表示用于计算梯度的函数。
    • is_leaf:为True或者False,表示该节点是否为叶子节点。 当调用backward函数时,只有requires_grad为true以及is_leaf为true的节点才会被计算梯度,即grad属性才会被赋予值。
  • backward()的使用
    • 1.最后结果为标量scalar(例如loss值)
      默认调用了loss.backward(gradient=None)或者指定为loss.backward(gradient=torch.Tensor([1.0])
    • 2.最后的结果变量为向量(vector) gradient的维度必须与n的维度相同,因为在执行z.backward(gradient)的时候,如果z不是一个标量,那么先构造一个标量的值:L = torch.sum(z*gradient),再计算关于L对各个leaf Variable的梯度。
  • 优化器
net = Net()
#优化过程:optimizer.zero_grad(),loss.backward(),optimizer.step()
from torch import  optim
optimizer = optim.SGD(params=net.parameters(), lr=1)
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()

input = t.randn(1, 3, 32, 32)
output = net(input)
output.backward(output) # fake backward

optimizer.step() # 执行优化

# 为不同子网络设置不同的学习率,参数中说明是该实例的parameter即可!
# 如果对某个参数不指定学习率,就使用最外层的默认学习率
optimizer =optim.SGD([
                {'params': net.features.parameters()}, # 学习率为1e-5
                {'params': net.classifier.parameters(), 'lr': 1e-2}
            ], lr=1e-5)
optimizer

1.8 nn.functional

更像纯函数,一般没有需要学习的参数的层用他

input = t.randn(2, 3)
model = nn.Linear(3, 4)
output1 = model(input)
output2 = nn.functional.linear(input, model.weight, model.bias)
output1 == output2

b = nn.functional.relu(input)
b2 = nn.ReLU()(input)
b == b2

1.9 初始化策略

from torch.nn import init
linear = nn.Linear(3,4)

t.manual_seed(1)
init.xavier_normal_(linear.weight)
#或者手动
import math
t.manual_seed(1)

# xavier初始化的计算公式
std = math.sqrt(2)/math.sqrt(7.)
linear.weight.data.normal_(0,std)

1.10 nn.module深入分析

def __init__(self):
    self._parameters = OrderedDict()
    self._modules = OrderedDict()
    self._buffers = OrderedDict()
    self._backward_hooks = OrderedDict()
    self._forward_hooks = OrderedDict()
    self.training = True

其中每个属性的解释如下:

  • _parameters:字典,保存用户直接设置的parameter,self.param1 = nn.Parameter(t.randn(3, 3))会被检测到,在字典中加入一个key为'param',value为对应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。
  • training:BatchNorm与Dropout层在训练阶段和测试阶段中采取的策略不同,通过判断training值来决定前向传播策略

上述几个属性中,_parameters_modules_buffers这三个字典中的键值,都可以通过self.key方式获得,效果等价于self._parameters['key'].


所以单独查看net==net._modules!=net._parameters
可以手动设置training属性,也可以直接model.train()和model.eval()避免设置错