使用Pytorch搭建模型

本来是只用Tenorflow的,但是因为TF有些Numpy特性并不支持,比如对数组使用列表进行切片,所以只能转战Pytorch了(pytorch是支持的)。还好Pytorch比较容易上手,几乎完美复制了Numpy的特性(但还有一些特性不支持),怪不得热度上升得这么快。

1  模型定义

和TF很像,Pytorch也通过继承父类来搭建自定义模型,同样也是实现两个方法。在TF中是__init__()和call(),在Pytorch中则是__init__()和forward()。功能类似,都分别是初始化模型内部结构和进行推理。其它功能比如计算loss和训练函数,你也可以继承在里面,当然这是可选的。下面搭建一个判别MNIST手写字的Demo,首先给出模型代码:

import numpy as np
import matplotlib.pyplot as plt 
import torch 
from torch import nn,optim 
from torchsummary import summary  
from keras.datasets import mnist
from keras.utils import to_categorical
device = torch.device('cuda')  #——————1——————class ModelTest(nn.Module):def __init__(self,device):super().__init__() self.layer1 = nn.Sequential(nn.Flatten(),nn.Linear(28*28,512),nn.ReLU())#——————2——————self.layer2 = nn.Sequential(nn.Linear(512,512),nn.ReLU()) self.layer3 = nn.Sequential(nn.Linear(512,512),nn.ReLU())self.layer4 = nn.Sequential(nn.Linear(512,10),nn.Softmax())  self.to(device) #——————3——————self.opt = optim.SGD(self.parameters(),lr=0.01)#——————4——————def forward(self,inputs): #——————5——————x = self.layer1(inputs)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)return x def get_loss(self,true_labels,predicts):  loss = -true_labels * torch.log(predicts)  #——————6——————loss = torch.mean(loss)return lossdef train(self,imgs,labels): predicts = model(imgs) loss = self.get_loss(labels,predicts)self.opt.zero_grad()#——————7——————loss.backward()#——————8——————self.opt.step()#——————9——————
model = ModelTest(device)
summary(model,(1,28,28),3,device='cuda')  #——————10——————

#1:获取设备,以方便后面的模型与变量进行内存迁移,设备名只有两种:‘cuda’和’cpu’。通常是在你有GPU的情况下需要这样显式进行设备的设置,从而在需要时,你可以将变量从主存迁移到显存中。如果没有GPU,不获取也没事,pytorch会默认将参数都保存在主存中。

#2:模型中层的定义,可以使用Sequential将想要统一管理的层集中表示为一层。

#3:在初始化中将模型参数迁移到GPU显存中,加速运算,当然你也可以在需要时在外部执行model.to(device)进行迁移。

#4:定义模型的优化器,和TF不同,pytorch需要在定义时就将需要梯度下降的参数传入,也就是其中的self.parameters(),表示当前模型的所有参数。实际上你不用担心定义优化器和模型参数的顺序问题,因为self.parameters()的输出并不是模型参数的实例,而是整个模型参数对象的指针,所以即使你在定义优化器之后又定义了一个层,它依然能优化到。当然优化器你也可以在外部定义,传入model.parameters()即可。这里定义了一个随机梯度下降。

#5:模型的前向传播,和TF的call()类似,定义好model()所执行的就是这个函数。

#6:我将获取loss的函数集成在了模型中,这里计算的是真实标签和预测标签之间的交叉熵。

#7/8/9:在TF中,参数梯度是保存在梯度带中的,而在pytorch中,参数梯度是各自集成在对应的参数中的,可以使用tensor.grad来查看。每次对loss执行backward(),pytorch都会将参与loss计算的所有可训练参数关于loss的梯度叠加进去(直接相加)。所以如果我们没有叠加梯度的意愿的话,那就要在backward()之前先把之前的梯度删除。又因为我们前面已经把待训练的参数都传入了优化器,所以,对优化器使用zero_grad(),就能把所有待训练参数中已存在的梯度都清零。那么梯度叠加什么时候用到呢?比如批量梯度下降,当内存不够直接计算整个批量的梯度时,我们只能将批量分成一部分一部分来计算,每算一个部分得到loss就backward()一次,从而得到整个批量的梯度。梯度计算好后,再执行优化器的step(),优化器根据可训练参数的梯度对其执行一步优化。

#10:使用torchsummary函数显示模型结构。奇怪为什么不把这个继承在torch里面,要重新安装一个torchsummary库。

2  训练及可视化

接下来使用模型进行训练,因为pytorch自带的MNIST数据集并不好用,所以我使用的是Keras自带的,定义了一个获取数据的生成器。下面是完整的训练及绘图代码(50次迭代记录一次准确率):

import numpy as np
import matplotlib.pyplot as plt 
import torch 
from torch import nn,optim 
from torchsummary import summary  
from keras.datasets import mnist
from keras.utils import to_categorical
device = torch.device('cuda')  #——————1——————class ModelTest(nn.Module):def __init__(self,device):super().__init__() self.layer1 = nn.Sequential(nn.Flatten(),nn.Linear(28*28,512),nn.ReLU())#——————2——————self.layer2 = nn.Sequential(nn.Linear(512,512),nn.ReLU()) self.layer3 = nn.Sequential(nn.Linear(512,512),nn.ReLU())self.layer4 = nn.Sequential(nn.Linear(512,10),nn.Softmax())  self.to(device) #——————3——————self.opt = optim.SGD(self.parameters(),lr=0.01)#——————4——————def forward(self,inputs): #——————5——————x = self.layer1(inputs)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)return x def get_loss(self,true_labels,predicts):  loss = -true_labels * torch.log(predicts)  #——————6——————loss = torch.mean(loss)return lossdef train(self,imgs,labels): predicts = model(imgs) loss = self.get_loss(labels,predicts)self.opt.zero_grad()#——————7——————loss.backward()#——————8——————self.opt.step()#——————9——————
def get_data(device,is_train = True, batch = 1024, num = 10000):train_data,test_data = mnist.load_data()if is_train:imgs,labels = train_dataelse:imgs,labels = test_data  imgs = (imgs/255*2-1)[:,np.newaxis,...]labels = to_categorical(labels,10) imgs = torch.tensor(imgs,dtype=torch.float32).to(device)labels = torch.tensor(labels,dtype=torch.float32).to(device)i = 0while(True):i += batchif i > num:i = batch yield imgs[i-batch:i],labels[i-batch:i] 
train_dg = get_data(device, True,batch=4096,num=60000) 
test_dg = get_data(device, False,batch=5000,num=10000) model = ModelTest(device) 
summary(model,(1,28,28),11,device='cuda')  
ACCs = []
import time
start = time.time()
for j in range(20000):#训练imgs,labels = next(train_dg)model.train(imgs,labels)#验证img,label = next(test_dg)predicts = model(img) acc = 1 - torch.count_nonzero(torch.argmax(predicts,axis=1) - torch.argmax(label,axis=1))/label.shape[0]if j % 50 == 0:t = time.time() - startstart = time.time()ACCs.append(acc.cpu().numpy())print(j,t,'ACC: ',acc)
#绘图
x = np.linspace(0,len(ACCs),len(ACCs))
plt.plot(x,ACCs)

准确率变化图如下:

3  其它使用技巧

3.1  tensor与array

需要注意的是,pytorch的tensor基于numpy的array,它们是共享内存的。也就是说,如果你把tensor直接插入一个列表,当你修改这个tensor时,列表中的这个tensor也会被修改;更容易被忽略的是,即使你用tensor.detach.numpy(),先将tensor转换为array类型,再插入列表,当你修改原本的tensor时,列表中的这个array也依然会被修改。所以如果我们只是想保存tensor的值而不是整个对象,就要使用np.array(tensor)将tensor的值复制出来。

3.2  自定义层

在TF中,自定义模型通常继承keras的Model,而自定义层则是继承layers.Layer,继承不同的父类通常会造成初学者的困扰。而在pytorch中,自定义层与自定义模型一样,都是继承nn.Module。Pytorch将层与模型都看成了模块,这很容易理解。的确,层与模型之间本来也没有什么明确的界限。并且定义方式与上面定义模型的方式一样,也是实现两个函数即可。代码示例如下:

import torch   
from torch import nn  class ParaDeconv(nn.Module):#——————1——————def __init__(self,in_n,out_n):super().__init__() self.w = nn.Parameter(torch.normal(0,0.01,size = [in_n,out_n]),requires_grad=True)self.b = nn.Parameter(torch.normal(0,0.01,size = [out_n]),requires_grad=True) def forward(self,inputs):x = torch.matmul(inputs,self.w)x = x + self.breturn x 
layer = ParaDeconv(2,3)
y = layer(torch.ones(100,2))#——————2——————
loss = torch.sum(y)#——————3——————
loss.backward()#——————4——————
for i in layer.parameters():#——————5——————print(i.grad)#——————6——————

#1:自定义一个全连接层。层中可训练参数的定义是使用nn.Parameter,如果直接使用torch.tensor是无法在#5中遍历到的。

#2/3/4:输入并计算loss,然后反向传播计算参数梯度。

#5/6:输出完成反向传播后层参数的梯度。

以上定义的层可以和pytorch自带的层一样直接插入模型中使用。

3.3  保存/加载

3.3.1  保存/加载模型

有两种方式,一种是保存模型的参数:

torch.save(model.state_dict(), PATH)                 #保存    
model.load_state_dict(torch.load(PATH),strict=True)  #加载

这种加载方式需要先定义模型,然后再加载参数。如果你定义的模型参数名与保存的参数对不上,就会出错。但如果把strict修改成False,不严格匹配,它就会只匹配对应上的键值,不会因多出或缺少的参数而报错。

另一种是直接保存模型:

torch.save(model, PATH)  #保存
model = torch.load(PATH) #加载

这种方式看似方便,实际上更容易出错。因为python不能保存整个模型的类,所以它只能保存定义类的代码文件位置,以在加载时获取类的结构。如果你改变了定义类的代码位置,就有可能因为找不到类而出错。

3.3.2  保存训练点

当你要保存某个训练阶段的状态,比如包含优化器参数、模型参数、训练迭代次数等,可以进行如下操作:

#保存训练点
torch.save({'epoch': epoch,'model_state_dict': model.state_dict(),'optimizer_state_dict': optimizer.state_dict(),'loss': loss}, PATH)
#加载训练点
model = TheModelClass(*args, **kwargs)
optimizer = TheOptimizerClass(*args, **kwargs)checkpoint = torch.load(PATH)model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']

和保存模型一样,也是使用torch.save()。它很灵活,可以保存字典,因此读取的时候也按照字典索引读取即可。当然要注意,并不是任何类型都能保存的,这里保存的四个类型分别是:

1. int

2. collections.OrderedDict

3. collections.OrderedDict

4. list

3.4  修改模型参数

Pytorch没有提供额外的方式让我们修改模型参数,我们可以使用上面加载模型参数的方式来修改参数。对于某个参数,我们只要把键值和对应要修改的值放在字典中传入load_state_dict即可。如果没传入所有的参数,记得把strict设为False。示例如下:

model.load_state_dict({'weight':torch.tensor([0.])},strict=False)  #修改模型参数

参数名,也就是键值,和对应的参数shape可以通过model.state_dict()查看。

3.5  添加Grad Penalty (GP)

WGAN-GP的grad penalty需要用到二次求导,直接无脑使用backward()方法是不行的。梯度的计算有两种方法,一种是常见的backward(),另一种是autograd.grad()。

下面做一个简单的实验来记录如何使用这两者计算GP。代码如下:

import torch
from torch import nn, optim   
from time import timefrom torch import autogradtorch.manual_seed(1) #————0————
model = nn.Linear(3, 2) #————0————
opt = optim.SGD(model.parameters(), 0.1) #————0————start_t = time()for i in range(10):x = torch.ones([1, 3], requires_grad=True) #————1————y = torch.sum(model(x)) #————1————# ***********************y.backward(retain_graph = False, create_graph = True) #————2————g = x.grad #————2————# g = autograd.grad(y, x, retain_graph = False, create_graph = True)[0]# ***********************print("Input gradient: \n", g.detach().numpy())#————3————for i in model.state_dict(): #————3————print(i+":\n", model.state_dict()[i].numpy()) #————3————gp = torch.sum(g**2) #————4————print("GP: \n", gp.detach().numpy()) print() opt.zero_grad() #————5————gp.backward() #————5————opt.step() #————5————print("Cost time: \n", time() - start_t)

注释:

0、设置随机种子、创建只包含一个全连接层的模型(R3→R2R3→R2)、创建优化器。

1、定义可获取梯度的输入,并通过模型求得输出。

2、使用backward()或autograd.grad()获取输入关于输出的梯度。需要注意的是,create_graph = True 表示在反向传播计算梯度时记录计算图。因为梯度通常直接用来梯度下降,无需关于梯度的计算图,所以默认为False。而我们要用这个梯度再一次反向传播来计算Grad Penalty,所以需要设置为True。另外,retain_graph设置为False,表示计算完梯度后,关于模型输出的前向传播计算图就释放掉。因为我们需要的是输入关于输出的梯度的计算图,因此在获取梯度计算图之后,输入到输出的计算图就可以释放掉了。很多博客都设置为True,这是在浪费资源。

3、打印输入关于输出的梯度,并打印模型的权重值。可以验证,打印出的梯度向量值等于权重行向量之和。

4、计算GP。为了便于理解,这里直接用二范数的平方来代替。

5、使用GP更新模型权重。

输出结果如下:

可以看出权重两个行向量之和越来越接近0。这和数学计算的预期结果是一致的。

以上列举的两个方法,是有计算量上的差异的。backward()会计算所有与输出相关,且可获取梯度的参数的梯度;而autograd.grad()则只会计算输入与输出之间,与输入和输出都相关的梯度,且函数返回的只有输入关于输出的梯度。也就是说,在以上实验中,autograd.grad()不会计算全连接层中bias关于输出的梯度(因为它并没有影响到输入关于输出的梯度)。所以,使用autograd.grad()计算GP会更快。

我们可以使用以上代码进行实验,两种方法在10000次迭代中分别用时3.5s和3.2s,autograd.grad()快快了0.3秒。而这里的bias规模只有2,当规模大起来,节省的时间就很可观了。

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

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

相关文章

广州市网约车平台:照片上传标准与处理技巧全解析

《广州市网络预约出租汽车综合业务管理平台》是一个旨在规范和提升广州市网约车服务质量的在线管理系统。它整合了司机和车辆信息管理、预约服务、监管监控、服务质量评价以及数据分析等功能,确保网约车服务符合当地法规要求,同时为乘客提供安全、便捷的…

浅谈大模型推理成本优化

上回说了,全赞AI的应用里面有用到几十个大模型,我的其他的应用比如渣渣句,熊喵表情都会或多或少的用到一到两个大模型的推理。而众所周知,目前大模型的推理存在两个问题,一个是慢,一个是贵,慢的…

微服务系列之分布式事务理论

概述 事务是由一组操作构成的可靠的独立的工作单元,事务具备ACID的特性,即原子性、一致性、隔离性和持久性。 分类 大多数情况下,分类是没有意义的一件事。但是分类可以一定程度上,加深理解。 实现 从实现角度来看&#xff0…

轻松删除文件名中的符号,使用替换功能,让管理文件更加得心应手!

在我们的日常生活和工作中,文件管理是一项必不可少的任务。而一个整洁、有序的文件名系统则有助于我们快速找到所需的文件。如果你发现文件名中存在一些不必要的符号,那么这款文件重命名工具将是你的得力助手。它具备强大的替换功能,可以轻松…

在 Unity 中获取 Object 对象的编辑器对象

有这个需求的原因是,在编辑器的 Inspector 逻辑中,写了许多生成逻辑。 现在不想挨个在 Inspector 上都点一遍按钮,所以就需要能获取到它们的编辑器对象。 发现可以借助官方的 UnityEditor.Editor.CreateEditor 方法达到目的,如下…

OpcUaHelper实现西门子OPC Server数据交互

Opc ua客户端类库,基于.net 4.6.1创建,基于官方opc ua基金会跨平台库创建,方便的实现和OPC Server进行数据交互。 FormBrowseServer 在开发客户端之前,需要使用本窗口来进行查看服务器的节点状态,因为在请求服务器的节点数据之前,必须知道节点的名称,而节点的名称可以…

分布式技术之负载均衡技术

文章目录 什么是负载均衡?服务请求的负载均衡方法轮询策略随机策略哈希和一致性哈希策略对比分析 负载均衡是分布式可靠性中非常关键的一个问题或技术,在一定程度上反映了分布式系统对业务处理的能力。比如,早期的电商抢购活动,当…

4.36 构建onnx结构模型-Where

前言 构建onnx方式通常有两种: 1、通过代码转换成onnx结构,比如pytorch —> onnx 2、通过onnx 自定义结点,图,生成onnx结构 本文主要是简单学习和使用两种不同onnx结构, 下面以 Where 结点进行分析 方式 方法一…

记录一下imx6ull linux 5.10.9多点电容触摸屏驱动报错问题解决方法

最近再研究如何将linux 5.10.9移植到imx6ull,用的原子的开发板,在移植电容触摸屏驱动时报错gpio gpiochip0: (209c000.gpio): gpiochip_lock_as_irq: tried to flag a GPIO set as output for IRQ,如下图: 该错误的意思就是尝试将…

数据库系统概论SQL编程题合集(包含期末题、考研初试题以及复试题)

二、现有数据库casemanage中表结构如下图 1)请编写sql语句对年龄进行升序排列 select * from afinfo order by birth;2)请编写sql语句查询对“徐”姓开头的人员名单 select * from afinfo where name like 徐%;3)请编写sql语句修改“陈晓”…

2024年【茶艺师(初级)】考试技巧及茶艺师(初级)试题及解析

题库来源:安全生产模拟考试一点通公众号小程序 茶艺师(初级)考试技巧是安全生产模拟考试一点通生成的,茶艺师(初级)证模拟考试题库是根据茶艺师(初级)最新版教材汇编出茶艺师&#…

「Kafka」入门篇

「Kafka」入门篇 基础架构 Kafka 快速入门 集群规划 集群部署 官方下载地址:http://kafka.apache.org/downloads.html 解压安装包: [atguiguhadoop102 software]$ tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module/修改解压后的文件名称: [a…

关于HTTPS

目录 什么是加密 对称加密 非对称加密 中间人攻击 引入证书 HTTPS是一个应用层的协议,是在HTTP协议的基础上引入了一个加密层. HTTP协议内容都是按照文本的方式明文传输,这就导致在传输的过程中出现一些被篡改的情况. 运营商劫持事件 未被劫持的效果,点击下载按钮,就会…

Mybatis分页插件之PageHelper生效and失效原理解析

文章目录 前言整合PageHelperPageHelper生效原理PageHelper的分页参数和线程绑定核心拦截逻辑生成分页SQLdialect.afterAll() PageHelper失效原理分页失效案例分页失效原理总结 Mybatis拦截器系列文章:从零开始的 MyBatis 拦截器之旅:实战经验分享 构建自…

探索 3D 图形处理的奥秘

最近一年多来,在 3Dfx、Intel 们的狂轰滥炸中,在 Quake、古墓丽影们的推波助澜下,三维图形已经成为计算机迷眼中的又一个热点。3D 世界到底是怎样的神奇,我们又是怎样享受它的乐趣呢?就让我们来一探究竟吧。 图形基础…

K8s资源管理介绍

用这个官网下的,kube-flannel.yml ,就不会nodes not-ready --- kind: Namespace apiVersion: v1 metadata:name: kube-flannellabels:k8s-app: flannelpod-security.kubernetes.io/enforce: privileged --- kind: ClusterRole apiVersion: rbac.author…

递归详解之青蛙跳台阶和汉诺塔问题

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - :来于“云”的“羽球人”。…

idea利用JRebel插件,无需重启,实现Spring Boot项目热重载,节省开发时间和精力!

插件介绍 官方介绍 翻译过来的意思是: JRebel 是一款提高开发效率的工具,允许开发者立即重新加载代码更改。它跳过了在Java开发中常见的重新构建、重启和重新部署循环。JRebel 能够让开发者在相同的时间内完成更多工作,并且在编码时能够保持…

CSS与JavaScript的简单认识

CSS:是一门语言,用于控制网页表现,让页面更好看的。 CSS(Cascading Style Sheet):层叠样式表 CSS与html结合的三种方式: 1、内部样式:用style标签,在标签内部定义CSS样式…

vim学习笔记

vim学习笔记 Linux Vim编辑器的基本使用 显示行号 set nu 自动补全 CTRL-N或CTRL-P $到当前行的末尾 u 撤销上一步的操作 Ctrlr 恢复上一步被撤销的操作 vim下配置tab缩进格数 原始文件&#xff1a; helloworld nice 普通缩进 shift > &#xff08;或者 Shift <…