【动手学深度学习】#4 深度学习计算

主要参考学习资料:

《动手学深度学习》阿斯顿·张 等 著

【动手学深度学习 PyTorch版】哔哩哔哩@跟李牧学AI

概述

  • 为了实现更复杂的网络,我们需要研究比层更高一级的单元,在编程中由表示。通过自定义层和块,我们能更灵活地搭建网络。
  • 我们有多种方法可以访问、初始化和绑定模型参数。
  • 为了加载和保存权重向量和整个模型,我们需要进行文件读写
  • 将计算设备指定为GPU而不是CPU,可以更高效地完成深度学习的计算任务。

目录

  • 4.1 层和块
    • 4.1.1 自定义块
    • 4.1.2 顺序块
  • 4.2 参数管理
    • 4.2.1 参数访问
      • 1.目标参数
      • 2.一次性访问所有参数
      • 3.从嵌套块收集参数
    • 4.2.2 参数初始化
      • 1.内置初始化
      • 2.自定义初始化
    • 4.2.3 参数绑定
  • 4.3 自定义层
    • 4.3.1 不带参数的层
    • 4.3.2 带参数的层
  • 4.4 读写文件
    • 4.4.1 加载和保存张量
    • 4.4.2 加载和保存模型参数
  • 4.5 GPU
    • 4.5.1 计算设备
    • 4.5.2 张量与GPU
    • 4.5.3 神经网络与GPU

4.1 层和块

事实证明,研究讨论“比单个层大”但“比整个模型小”的组件更有价值。计算机视觉中广泛流行的ResNet-152架构有数百层,这些层是由层组的重复模式组成。为了实现更复杂的网络,我们引入神经网络的概念。

块可以描述单个层、由多个层组成的组件或整个模型本身。使用块的好处是可以将一些块组成更大的组件,这一过程通常是递归的。

在编程中,块由表示,类的任何子类都必须定义一个将其输入转换为输出的前向传播函数,并且必须存储任何必需的参数。为了计算梯度,块必须具有反向传播函数。在自定义块时,由于自动微分提供了一些后端实现,我们只需要考虑前向传播函数和必须的参数。

4.1.1 自定义块

块必须提供的基本功能:

  • 将输入数据作为其前向传播函数的参数。
  • 通过前向传播函数来生成输出。
  • 计算其输出关于输入的梯度,可通过其反向传播函数进行访问,通常是自动完成的。
  • 存储和访问前向传播计算所需的参数。
  • 根据需要初始化模型参数。
import torch  
from torch import nn  
#functional模块提供一些函数化的神经网络操作,可直接应用于张量
from torch.nn import functional as F  #以Module为父类定义MLP类
class MLP(nn.Module):  def __init__(self):  #初始化继承自Module的数据属性super().__init__()  #实例化两个全连接层self.hidden = nn.Linear(20, 256)  self.out = nn.Linear(256, 10)  #定义模型的前向传播,根据输入X返回模型输出def forward(self, X):  return self.out(F.relu(self.hidden(X)))

下面这个例子展示了自定义块的灵活性:

class FixedHiddenMLP(nn.Module)def __init__(self):super().__init__()#设置不随梯度更新的固定的权重,即常量参数self.rand_weight = torch.rand((20, 20), requires_grad=False)self.linear = nn.Linear(20, 20)#在前向传播函数中执行自定义代码def forward(self, X):X = self.linear(X)X = F.relu(torch.mm(X, self.rand_weight) + 1)#复用全连接层相当于这两个全连接层共享参数X = self.linear(X)#控制流while X.abs().sum() > 1:X /= 2return X.sum()

4.1.2 顺序块

Sequential类的设计目的是将其他模块串起来。构建自己的简化的MySequential只需定义如下函数:

  • 将块逐个追加到列表中的函数。
  • 前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。
class MySequential(nn.Module):  #args传入任意数量的模块def __init__(self, *args):  super().__init__()  #遍历模块并为每个模块分配一个字符串键存入_modules#_modules的变量类型是OrderedDictfor idx, module in enumerate(args):  self._modules[str(idx)] = module  def forward(self, X):  #OrderedDict保证按照成员添加的顺序遍历它们for block in self._modules.values():  X = block(X)  return X

4.2 参数管理

4.2.1 参数访问

import torch
from torch import nnnet = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))

通过索引访问模型的任意层,state_dict返回所访问层的参数:

print(net[2].state_dict())
OrderedDict([('weight', tensor([[ 0.2889, -0.2738, -0.3096, -0.3151,  0.2594,  0.1743, -0.2288, -0.1173]])), ('bias', tensor([0.0921]))])

1.目标参数

每个参数都表示为参数类的一个实例:

print(type(net[2].bias))
<class 'torch.nn.parameter.Parameter'>

参数是复合的对象,如下访问参数实例,并进一步访问该参数的值(张量类型)和梯度:

print(net[2].bias)
print(net[2].bias.data)
net[2].weight.grad == True
Parameter containing:
tensor([0.0921], requires_grad=True)
tensor([0.0921])
False

另一种访问网络参数的方式:

net.state_dict()['2.bias'].data
tensor([0.0921])

2.一次性访问所有参数

访问一个全连接层的参数:

print(*[(name, param.shape) for name, param in net[0].named_parameters()])
('weight', torch.Size([8, 4])) ('bias', torch.Size([8]))

访问所有层的参数:

print(*[(name, param.shape) for name, param in net.named_parameters()])
('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))

3.从嵌套块收集参数

定义生成块的函数:

def block1():return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),nn.Linear(8, 4), nn.ReLU())def block2():net = nn.Sequential()for i in range(4):net.add_module(f'block {i}', block1())return net

设计一个嵌套块的网络,并观察其架构:

rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
print(rgnet)
Sequential((0): Sequential((block 0): Sequential((0): Linear(in_features=4, out_features=8, bias=True)(1): ReLU()(2): Linear(in_features=8, out_features=4, bias=True)(3): ReLU())(block 1): Sequential((0): Linear(in_features=4, out_features=8, bias=True)(1): ReLU()(2): Linear(in_features=8, out_features=4, bias=True)(3): ReLU())(block 2): Sequential((0): Linear(in_features=4, out_features=8, bias=True)(1): ReLU()(2): Linear(in_features=8, out_features=4, bias=True)(3): ReLU())(block 3): Sequential((0): Linear(in_features=4, out_features=8, bias=True)(1): ReLU()(2): Linear(in_features=8, out_features=4, bias=True)(3): ReLU()))(1): Linear(in_features=4, out_features=1, bias=True)
)

因为层是分层嵌套的,因此可以像通过嵌套列表索引一样访问层。下面访问第一个主要的块中第二个子块的第一层的偏置:

rgnet[0][1][0].bias.data
tensor([ 0.1969, -0.0747, -0.1520,  0.0474, -0.2245, -0.2320, -0.2794,  0.3429])

4.2.2 参数初始化

1.内置初始化

normal_将权重参数初始化为标准差为0.01的高斯随机变量,zeros_将偏置参数设置为0:

def init_normal(m):if type(m) == nn.Linear:nn.init.normal_(m.weight, mean=0, std=0.01)nn.init.zeros_(m.bias)

constant_将所有参数初始化为给定的常量:

def init_constant(m):if type(m) == nn.Linear:nn.init.constant_(m.weight, 1)nn.init.zeros_(m.bias)

xavier_uniform_为上一章中介绍的Xavier均匀分布初始化:

def init_xavier(m):if type(m) == nn.Linear:nn.init.xavier_uniform_(m.weight)

2.自定义初始化

以如下分布为例为任意权重参数 w w w定义初始化方法:

w ∼ { U ( 5 , 10 ) , 可能性为 1 4 0 , 可能性为 1 2 U ( − 10 , − 5 ) , 可能性为 1 4 w\sim\left\{\begin{matrix}U(5,10),&\displaystyle可能性为\frac14\\0,&\displaystyle可能性为\frac12\\U(-10,-5),&\displaystyle可能性为\frac14\end{matrix}\right. w U(5,10),0,U(10,5),可能性为41可能性为21可能性为41

def my_init(m):if type(m) == nn.Linear:#以(-10, 10)为边界的均匀分布nn.init.uniform_(m.weight, -10, 10)#将绝对值小于5的权重清零m.weight.data *= m.weight.data.abs() >= 5

4.2.3 参数绑定

定义一个层,并使用这个层的参数来设置另一个层的参数可以实现参数共享:

shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),shared, nn.ReLU,shared, nn.ReLU,nn.Linear(8, 1))

改变shared的参数也会同步改变net中两个对应层的参数。

4.3 自定义层

4.3.1 不带参数的层

构建一个从其输入中减去均值的层:

import torch
import torch.nn.functional as F
from torch import nnclass CenteredLayer(nn.Module):def __init__(self):super().__init__()def forward(self, X):return X - X.mean()

将自定义层作为组件合并到更复杂的模型中:

net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())

该自定义层没有指定输入维度,但框架会在数据第一次通过模型传递时动态地推断出每个层的大小,即延迟初始化

4.3.2 带参数的层

下面是自定义版本的全连接层:

class MyLinear(nn.Module):#传入输入数和输出数作为参数def __init__(self, in_units, units):super().__init__()#通过Parameter实例创建参数self.weight = nn.Parameter(torch.randn(in_units, units))self.bias = nn.Parameter(torch.randn(units,))def forward(self, X):linear = torch.matmul(X, self.weight.data) + self.bias.datareturn F.relu(linear)

4.4 读写文件

4.4.1 加载和保存张量

张量通过load和save函数保存到内存并分别读写它们。

import torch
from torch import nn
from torch.nn import functional as F#单个张量
x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load('x-file')#张量列表
y = torch.zeros(4)
torch.save([x, y], 'x-files')
x2, y2 = torch.load('x-files')#从字符串映射到张量的字典
mydict = {'x': x, 'y': y}  
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')

4.4.2 加载和保存模型参数

深度学习框架提供了内置函数来保存和加载整个网络,但这只保存模型的参数而不是模型,因此架构需要自己生成。先定义一个多层感知机模型:

class MLP(nn.Module):def __init__(self):super().__init__()self.hidden = nn.Linear(20, 256)self.output = nn.Linear(256, 10)def forward(self, X):return self.output(F.relu(self.hidden(X)))net = MLP()

将模型参数存储在文件mlp.params中:

torch.save(net.state_dict(), 'mlp.params')

实例化原多层感知机模型的一个备份,并通过直接读取文件中存储的参数来恢复模型:

clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))

4.5 GPU

GPU具有强大的并行处理能力、浮点运算速度和内存带宽,在处理人工智能这样的复杂计算任务上相较于CPU有无可比拟的优势。首先确保至少安装了一个NVIDIA GPU,然后下载NVIDIA驱动和CUDA,最后使用nvidia-smi命令查看显卡信息:

!nvidia-smi

4

在pytorch中,每个数组都有一个设备,我们通常称其为环境。默认情况下,所有变量和相关的计算都分配给CPU,有时环境可能是GPU。

4.5.1 计算设备

我们可以指定用于存储和计算的设备。在pytorch中,CPU和GPU可以使用torch.device(‘cpu’)和torch.device(‘cuda’))表示。如果有多个GPU,我们使用torch.device(f’cuda:{i}')来表示第i块GPU(i从0开始),以及cuda:0和cuda等价。

import torch
from torch import nntorch.device('cpu'), torch.device('cuda'), torch.device('cuda:0')
(device(type='cpu'), device(type='cuda'), device(type='cuda', index=0))

查询可用GPU的数量:

torch.cuda.device_count()
1

定义两个函数以允许在不存在所需GPU的情况下执行代码:

def try_gpu(i=0):"""如果存在,返回第i个GPU,否则返回CPU"""if torch.cuda.device_count() >= i + 1:return torch.device(f'cuda:{i}')return torch.device('cpu')def try_all_gpus():"""返回所有可用的GPU,如果没有GPU则返回CPU"""devices = [torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]return devices if devices else [torch.device('cpu')]try_gpu(), try_gpu(10), try_all_gpus()
(device(type='cuda', index=0),device(type='cpu'),[device(type='cuda', index=0)])

4.5.2 张量与GPU

默认情况下,张量是在内存中创建并使用CPU进行计算的。

x = torch.tensor([1, 2, 3])
x.device
device(type='cpu')

在创建张量时指定存储设备来在GPU上存储张量:

X = torch.ones(2, 3, device=try_gpu())
X
tensor([[1., 1., 1.],[1., 1., 1.]], device='cuda:0')

假设我们有两个GPU(博主没有所以移用示例代码),如下在第二个GPU上创建一个随机张量:

Y = torch.rand(2, 3, device=try_gpu(1))
Y
tensor([[0.2506, 0.4556, 0.9745],[0.2359, 0.6094, 0.0410]] device='cuda:1')

如果要计算X+Y,我们需要决定在哪里执行这个操作。张量的计算只能在同一设备上运行,因此要将X复制到第二个GPU才能执行:

Z = X.cuda(1)
print(X)
print(Z)
tensor([[1., 1., 1.],[1., 1., 1.]], device='cuda:0')
tensor([[1., 1., 1.],[1., 1., 1.]], device='cuda:1')

现在可以在同一个GPU上将其相加:

Y + Z
tensor([[1.2506, 1.4556, 1.9745],[1.2359, 1.6094, 1.0410]] device='cuda:1')

4.5.3 神经网络与GPU

类似地,神经网络模型可以指定设备,将模型参数放在GPU上:

net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/899074.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/899074.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在 Windows 上安装并使用 Postman?

Postman 是一个功能强大的API测试工具&#xff0c;它可以帮助程序员更轻松地测试和调试 API。在本文中&#xff0c;我们将讨论如何在 Windows 上安装和使用 Postman。 Windows 如何安装和使用 Postman 教程&#xff1f;

php写入\查询influxdb数据

namespace app\index\controller;use InfluxDB2\Client; use InfluxDB2\Model\WritePrecision; use InfluxDB2\Point;class Demo {/*** 显示资源列表** return \think\Response*/public function index(){$token 你的TOKEN;$org zzlichi;$bucket initdb;$client new Client…

26考研——图_图的基本概念(6)

408答疑 文章目录 一、图的基本概念图的定义非空性非线性结构 顶点和边的表示顶点边 有向图 & 无向图有向图有向图 G 1 G_1 G1​ 的表示 无向图无向图 G 2 G_2 G2​ 的表示 简单图 & 多重图简单图多重图 顶点的度、入度和出度顶点的度有向图的度 路径、路径长度和回路…

面向对象软件工程实践软件案例

智力运动-数字化思维训练课程介绍 数字化思维训练是科技赋能素质教育创新实践项目&#xff0c;通过数字化信息化手段&#xff0c;深度融合优质原创智力运动教育课程资源&#xff0c;服务幼儿园与小学&#xff0c;提供信息时代校园素质教育教学解决方案。在《面向对象软件工程》…

Linux学习笔记(应用篇一)

基于I.MX6ULL.MINI开发板 标准I/O库链接目录删除文件正则表达式系统标识时间堆内存信号标准信号 进程进程组进程间通信线程互斥锁线程安全 本文章是入门篇的概念&#xff0c;有点零散&#xff0c;后续需要补充复习 **inode&#xff08;索引节点&#xff09;**是 Linux 和 Unix …

Modbus RTU ---> Modbus TCP透传技术实现(Modbus透传、RS485透传、RTU透传)分站代码实现、协议转换器

文章目录 Modbus RTU到Modbus TCP透传技术实现1. 透传技术概述1.1 透传基本原理- 协议帧格式转换- 地址映射与管理- 通信时序适配- 错误检测与处理 2. 透传网关硬件架构2.1 典型硬件结构- 微控制器/处理器(ARM、STM32等)- RS-485/RS-232收发器- 以太网控制器(如W5500)- 电源管理…

MySQL数据库中常用的命令

登录&#xff1a; mysql -u username -h ip地址 -P 端口 -p 密码 mysql -u username -S /path/mysql.sock -P -p 用户管理&#xff1a; select user,host from mysql.user;//查看数据库中所用用户信息 create user username%;//创建用户 create user username% identifie…

医学交互作用分析步骤和目的(R语言)

医学交互作用分析的目的和用途&#xff08;R语言&#xff09; 医学交互作用分析一直是医学数据分析的组成部分&#xff0c;总结最近的一些认识。 目的&#xff1a; 在独立危险因素鉴定的研究中&#xff0c;&#xff08;独立危险因素的&#xff09;交互作用可以作为独立危险因…

Javaweb后端登录会话技术jwt令牌

jwt生成与校验 是base4补位的 最后面是签名&#xff0c;签名不是base64&#xff0c;是通过签名算法加密后来的 令牌长度不是固定的&#xff0c;长度取决于原始内容&#xff0c;载荷&#xff0c;大小 头有&#xff0c;类型&#xff0c;签名算法 base64可以对任意的二进制数据进…

Mybatis操作数据库(注解+xml两个方式)

文章目录 1.个人回顾2.关于mybatis注解的说明3.字段和属性不匹配的解决方案3.1第一个方案3.2第二个方案3.3第三个方案 4.xml路径配置5.xml里面的字段映射 1.个人回顾 刚刚翻看了一下自己的这个之前写的博客&#xff0c;上一次和这个javaee相关的博客还是去年写的&#xff0c;也…

SysVinit和Systemd的系统运行级别

Linux运行级别 SysVinit系统(init守护进程)Linux系统运行级别SysVinit系统(init守护进程)查看Linux运行级别SysVinit系统(init守护进程)修改运行级别&#xff1a; Systemd守护进程Linux系统运行级别systemd查看运行级别Systemd查看系统当前运行级别 systemd修改运行级别multi-u…

Mysql-经典实战案例(11):深度解析Sysbench压测(从入门到MySQL服务器性能验证)

引言 如何用Sysbench压测满足mysql生产运行的服务器&#xff1f; Sysbench返回的压测结果如何解读&#xff1f; 别急&#xff0c;本文会教大家如何使用并且如何解读压测的结果信息&#xff0c;如何对mysql服务器进行压测&#xff01; 一、Sysbench核心功能全景解析 1.1 工…

vscode终端不识别npm 无法解析npm

vscode 用以管理员打开识别npm vscode 用普通用户打开不识别npm 刚换了一台新电脑&#xff0c;寻思安装各种环境&#xff0c;一顿操作猛如虎&#xff0c;当最后一个打开vscode后&#xff0c;运行项目发现&#xff0c;新建终端>npm run dev 无法识别。 在cmd 中 打node -…

springboot body 转对象强验证属性多余属性抛错误

在Spring Boot中&#xff0c;当使用RequestBody注解来接收HTTP请求中的JSON数据并将其转换为Java对象时&#xff0c;Spring默认会忽略额外的属性。这意味着如果发送的JSON包含一些目标对象中没有定义的属性&#xff0c;Spring不会报错&#xff0c;这些额外的属性会被简单地忽略…

01. Linux嵌入式系统学习笔记(一)(linux基础指令)

一. linux基础操作指令 1. 新建文件和目录 (1) 新建文件 touch 命令&#xff1a;用于创建空文件。 touch filename.txt 如果文件已存在&#xff0c;touch 会更新文件的访问时间和修改时间。 (2) 新建目录 mkdir 命令&#xff1a;用于创建目录。 mkdir directoryname 使…

Java 列表复制与对象引用

Java 列表复制与对象引用 一、知识点 1. 对象引用的基本概念 在 Java 中&#xff0c;List<School> 这样的集合存储的并不是真正的对象&#xff0c;而是对象的“地址”&#xff08;引用&#xff09;。就好比你有一个文件柜&#xff0c;文件柜里放的不是文件本身&#x…

如何理解 Apache Iceberg 与湖仓一体(Lakehouse)?

一、什么是湖仓一体&#xff08;Lakehouse&#xff09;&#xff1f; 湖仓一体是一种融合了数据湖的灵活存储能力与数据仓库的高效分析功能的现代数据架构。它通过整合两者的优势&#xff0c;解决了传统架构的局限性&#xff0c;为企业数据处理提供了更全面的解决方案。 数据湖…

Android面试总结之Android RecyclerView:从基础机制到缓存优化

引言 在 Android 开发中&#xff0c;RecyclerView是高效展示列表数据的核心组件。其强大的性能源于独特的视图复用机制和四级缓存体系。本文将结合源码与示例&#xff0c;带你深入理解RecyclerView的工作原理与优化策略。 核心组件 RecyclerView&#xff1a;作为容器视图&am…

【鸿蒙开发】Hi3861学习笔记- TCP客户端

00. 目录 文章目录 00. 目录01. TCP概述02. TCP应用场景03. TCP和UDP比较04. TCP相关API05. TCP编程流程06. 硬件设计07. 软件设计08. 实验现象09. 附录 01. TCP概述 TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接、可靠的传输层协议&#xff0c;旨…

【负载均衡系列】Keepalive

一、Keepalived 的核心功能 Keepalived 是一款用于实现 ​高可用(HA)​ 和 ​负载均衡 的开源工具,核心基于 ​VRRP(Virtual Router Redundancy Protocol)​ 协议,工作在网络四层(传输层)和七层(应用层)。 主要用途: 通过虚拟IP(VIP)实现服务高可用(主备切换)。…