Pytorch搭建模型的五大层级级别、自定义模型、自定义网络层

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就比较直观,就表示的是所谓的“孩子”,所以没有层层迭代深入。

Pytorch教程之nn.Module类详解----使用Module类来定义网络层

转自:Module类来定义网络层

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值