Pytorch搭建模型的五大层级级别、自定义模型、自定义网络层(待学习)
pytorch搭建模型的的五大层次级别
转自:Pytorch搭建模型五大层次级别 博客下的Pytorch搭建模型的五大层次级别
神经网络的基本流程可以分为两大步骤:
网络结构搭建+参数的梯度更新(后者又包括 “前向传播+计算参数的梯度+梯度更新”)
1)原始搭建——使用numpy实现
# -*- coding: utf-8 -*-
import numpy as np
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
# 创建输入、输出数据
x = np.random.randn(N, D_in) #(64,1000)
y = np.random.randn(N, D_out) #(64,10)可以看成是一个10分类问题
# 权值初始化
w1 = np.random.randn(D_in, H) #(1000,100),即输入层到隐藏层的权重
w2 = np.random.randn(H, D_out) #(100,10),即隐藏层到输出层的权重
learning_rate = 1e-6 #学习率
for t in range(500):
# 第一步:数据的前向传播,计算预测值p_pred
h = x.dot(w1)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)
# 第二步:计算计算预测值p_pred与真实值的误差
loss = np.square(y_pred - y).sum()
print(t, loss)
# 第三步:反向传播误差,更新两个权值矩阵
grad_y_pred = 2.0 * (y_pred - y) #注意:这里的导函数也是自己求的,因为这个地方是很简答的函数表达式
grad_w2 = h_relu.T.dot(grad_y_pred)
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0
grad_w1 = x.T.dot(grad_h)
# 更新参数
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
2)使用torch的Tensor原始实现
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # 这里使用CPU,但实际上可以使用GPU
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
# 创建输入、输出数据
x = torch.randn(N, D_in, device=device, dtype=dtype) #(64,1000)
y = torch.randn(N, D_out, device=device, dtype=dtype) #(64,10)可以看成是一个10分类问题
# 权值初始化
w1 = torch.randn(D_in, H, device=device, dtype=dtype) #(1000,100),即输入层到隐藏层的权重
w2 = torch.randn(H, D_out, device=device, dtype=dtype)#(100,10),即隐藏层到输出层的权重
learning_rate = 1e-6
for t in range(500):
# 第一步:数据的前向传播,计算预测值p_pred
h = x.mm(w1)
h_relu = h.clamp(min=0)
y_pred = h_relu.mm(w2)
# 第二步:计算计算预测值p_pred与真实值的误差
loss = (y_pred - y).pow(2).sum().item()
print(t, loss)
# 第三步:反向传播误差,更新两个权值矩阵
grad_y_pred = 2.0 * (y_pred - y) #注意:这里的导函数也是自己求的,因为这个地方是很简答的函数表达式
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
# 参数更新(梯度下降法)
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
3)torch的自动求导autograd
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # 这里使用CPU,但实际上可以使用GPU
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
# 创建输入、输出数据
x = torch.randn(N, D_in, device=device, dtype=dtype) #(64,1000)
y = torch.randn(N, D_out, device=device, dtype=dtype) #(64,10)可以看成是一个10分类问题
# 权值初始化
w1 = torch.randn(D_in, H, device=device, dtype=dtype) #(1000,100),即输入层到隐藏层的权重
w2 = torch.randn(H, D_out, device=device, dtype=dtype)#(100,10),即隐藏层到输出层的权重
learning_rate = 1e-6
for t in range(500):
# 第一步:数据的前向传播,计算预测值p_pred
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# 第二步:计算计算预测值p_pred与真实值的误差
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())
# 第三步:反向传播误差,更新两个权值矩阵,这就是关键了,不再需要自己写出导函数,求导是自动完成的
loss.backward() #一步到位、自动求导
# 参数梯度更新
with torch.no_grad():
w1 -= learning_rate * w1.grad #grad属性获取梯度,其实这个地方就是相当于是梯度下降法,优化的过程可以自己定义,因为这里参数很少
w2 -= learning_rate * w2.grad
求完一次之后将梯度归零
w1.grad.zero_()
w2.grad.zero_()
4)使用pytorch.nn模块
# -*- coding: utf-8 -*-
import torch
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
# 创建输入、输出数据
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
#模型搭建:这是与前面关键的区别,不再需要自己手动进行矩阵相乘,而是这种一步到位的方法
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
#定义损失函数
loss_fn = torch.nn.MSELoss(reduction='sum')
learning_rate = 1e-4
for t in range(500):
# 第一步:数据的前向传播,计算预测值p_pred
y_pred = model(x)
# 第二步:计算计算预测值p_pred与真实值的误差
loss = loss_fn(y_pred, y)
print(t, loss.item())
# 在反向传播之前,将模型的梯度归零
model.zero_grad()
# 第三步:反向传播误差,更新两个权值矩阵,这就是关键了,不再需要自己写出导函数,求导是自动完成的
loss.backward()
#更新参数的梯度
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad #这其实就是梯度下降法,优化参数,通过循环自动实现,不要再一个一个写了,相较于上面的参数更新方法简单了很多。但是还不够
5)使用torch.optim来进一步简化训练过程——进一步省略手动的参数更新,更加一步到位
# -*- coding: utf-8 -*-
import torch
# N是训练的batch size; D_in 是input输入数据的维度;
# H是隐藏层的节点数; D_out 输出的维度,即输出节点数.
N, D_in, H, D_out = 64, 1000, 100, 10
# 创建输入、输出数据
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
#模型搭建:这是与前面关键的区别,不再需要自己手动进行矩阵相乘,而是这种一步到位的方法
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
#定义损失函数
loss_fn = torch.nn.MSELoss(reduction='sum')
learning_rate = 1e-4
#构造一个optimizer对象
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
# 第一步:数据的前向传播,计算预测值p_pred
y_pred = model(x)
# 第二步:计算计算预测值p_pred与真实值的误差
loss = loss_fn(y_pred, y)
print(t, loss.item())
# 在反向传播之前,将模型的梯度归零,这
optimizer.zero_grad()
# 第三步:反向传播误差
loss.backward()
# 直接通过梯度一步到位,更新完整个网络的训练参数,一句话优化所有的参数,是不是很牛逼
optimizer.step()
使用Pytorch搭建神经网络的步骤:
模型相关配置三步走:
(1)第一步:搭建网络的结构,得到一个model。
(2)第二步:定义损失函数。
(3)第三步:定义优化方式。构造一个optimizer对象
模型训练五步走:
(1)第一步:计算y_pred;
(2)第二步:根据损失函数计算loss
(3)第三步:梯度归零,optimizer.zero_grad()
(4)第四步:反向传播误差,loss.backward()
(5)更新参数,使用step(),optimizer.step()
Pytorch教程之nn.Module类详解----使用Module类来自定义模型
参考博客 使用Module类来自定义模型
一、torch.nn.Module类概述
pytorch里面一切自定义操作基本上都是继承nn.Module类来实现的
二、torch.nn.Module类的简介
class Module(object):
def __init__(self):
def forward(self, *input):
def add_module(self, name, module):
def cuda(self, device=None):
def cpu(self):
def __call__(self, *input, **kwargs):
def parameters(self, recurse=True):
def named_parameters(self, prefix='', recurse=True):
def children(self):
def named_children(self):
def modules(self):
def named_modules(self, memo=None, prefix=''):
def train(self, mode=True):
def eval(self):
def zero_grad(self):
def __repr__(self):
def __dir__(self):
'''
有一部分没有完全列出来
'''
我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__构造函数和forward这两个方法。但有一些注意技巧:
(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,当然我也可以吧不具有参数的层也放在里面;
(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替。
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
# -*- coding:utf-8 -*-
import torch
class MyNet(torch.nn.Module):
def __init__(self):
super(MyNet, self).__init__() # 第一句话,调用父类的构造函数
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu1=torch.nn.ReLU()
self.max_pooling1=torch.nn.MaxPool2d(2,1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu2=torch.nn.ReLU()
self.max_pooling2=torch.nn.MaxPool2d(2,1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = self.relu1(x)
x = self.max_pooling1(x)
x = self.conv2(x)
x = self.relu2(x)
x = self.max_pooling2(x)
x = self.dense1(x)
x = self.dense2(x)
return x
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU()
(max_pooling1): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU()
(max_pooling2): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
'''
注意:上面的是将所有的层都放在了构造函数__init__里面,但是只是定义了一系列的层,各个层之间到底是什么连接关系并没有,而是在forward里面实现所有层的连接关系,当然这里依然是顺序连接的。
import torch
import torch.nn.functional as F
class MyNet(torch.nn.Module):
def __init__(self):
super(MyNet, self).__init__() # 第一句话,调用父类的构造函数
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x)
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x)
x = self.dense1(x)
x = self.dense2(x)
return x
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
'''
注意:此时,将没有训练参数的层没有放在构造函数里面了,所以这些层就不会出现在model里面,但是运行关系是在forward里面通过functional的方法实现的。
总结:所有放在构造函数__init__里面的层的都是这个模型的“固有属性”.
三、torch.nn.Module类的多种实现
Module类是非常灵活的,可以有很多灵活的实现方式。
3.1 通过Sequential来包装层
即将几个层包装在一起作为一个大的层(块),前面的一篇文章详细介绍了Sequential类的使用,包括常见的三种方式,以及每一种方式的优缺点,参见:nn.Sequential类详解
torch的核心是Module类(Module类位于torch\nn\modules\module.py、Sequential类位于torch\nn\modules\container.py)container.py里面存在的容器
class Container(Module):
class Sequential(Module):
class ModuleList(Module):
class ModuleDict(Module):
class ParameterList(Module):
class ParameterDict(Module):
(1)最简单的序贯模型(每一层是没有名称的, 默认的是以0、1、2、3来命名)
import torch.nn as nn
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
print(model)
print(model[2]) # 通过索引获取第几个层
'''运行结果为:
Sequential(
(0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(1): ReLU()
(2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
(3): ReLU()
)
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
'''
(2)给每一个层添加名称
import torch.nn as nn
from collections import OrderedDict
model = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))
print(model)
print(model[2]) # 通过索引获取第几个层
'''运行结果为:
Sequential(
(conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(relu1): ReLU()
(conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
(relu2): ReLU()
)
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
'''
(3)Sequential的第三种实现
import torch.nn as nn
from collections import OrderedDict
model = nn.Sequential()
model.add_module("conv1",nn.Conv2d(1,20,5))
model.add_module('relu1', nn.ReLU())
model.add_module('conv2', nn.Conv2d(20,64,5))
model.add_module('relu2', nn.ReLU())
print(model)
print(model[2]) # 通过索引获取第几个层
3.2 Module类的几个常见方法使用
特别注意:Sequential类虽然继承自Module类,二者有相似部分,但是也有很多不同的部分,集中体现在:Sequenrial类实现了整数索引,故而可以使用model[index] 这样的方式获取一个层,但是Module类并没有实现整数索引,不能够通过整数索引来获得层。如下方法:
def children(self):
def named_children(self):
def modules(self):
def named_modules(self, memo=None, prefix=''):
'''
注意:这几个方法返回的都是一个Iterator迭代器,故而通过for循环访问,当然也可以通过next
'''
(1)model.children()和model.named_children()方法返回的是迭代器iterator;
(2)model.children():每一次迭代返回的每一个元素实际上是 Sequential 类型,而Sequential类型又可以使用下标index索引来获取每一个Sequenrial 里面的具体层,比如conv层、dense层等;
(3)model.named_children():每一次迭代返回的每一个元素实际上是 一个元组类型,元组的第一个元素是名称,第二个元素就是对应的层或者是Sequential。
(4)model.modules()和model.named_modules()方法返回的是迭代器iterator;
(5)model的modules()方法和named_modules()方法都会将整个模型的所有构成(包括包装层、单独的层、自定义层等)由浅入深依次遍历出来,只不过modules()返回的每一个元素是直接返回的层对象本身,而named_modules()返回的每一个元素是一个元组,第一个元素是名称,第二个元素才是层对象本身。
(6)如何理解children和modules之间的这种差异性。注意pytorch里面不管是模型、层、激活函数、损失函数都可以当成是Module的拓展,所以modules和named_modules会层层迭代,由浅入深,将每一个自定义块block、然后block里面的每一个层都当成是module来迭代。而children就比较直观,就表示的是所谓的“孩子”,所以没有层层迭代深入。