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层
- 当输入是(N,C)时,BatchNorm1d(C)
- 当输入是(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的梯度。
- 1.最后结果为标量scalar(例如loss值)
- 优化器
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()避免设置错