Pytorch版动手学深度学习深度学
继承Module类来构造模型
Module类是nn模块里提供的一个模型构造类,是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型。Module类的__init__函数和forward函数分别用于创建模型参数和定义前向计算。
importtorchfromtorchimportnnclassMLP(nn.Module):#声明带有模型参数的层,这里声明了两个全连接层def__init__(self,**kwargs):#调用MLP父类Module的构造函数来进行必要的初始化。super(MLP,self).__init__(**kwargs)self.hidden=nn.Linear(,)self.act=nn.ReLU()self.output=nn.Linear(,10)#定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出defforward(self,x):a=self.act(self.hidden(x))returnself.output(a)net=MLP()//net(X)会调用MLP继承自Module类的__call__函数,这个函数将调用MLP类定义的forward函数来完成前向计算。net(X)
以上的MLP类中无须定义反向传播函数。系统将通过自动求梯度而自动生成反向传播所需的backward函数。
Module类是一个可供自由组建的部件(通用的部件),它的子类既可以是一个层(如PyTorch提供的Linear类),又可以是一个模型(如这里定义的MLP类),或者是模型的一个部分。事实上,PyTorch还实现了继承自Module的可以方便构建模型的类:如Sequential、ModuleList和ModuleDict等等。
Sequential类:当模型的前向计算为简单串联各个层的计算时,Sequential类可以通过更加简单的方式定义模型。它可以接收一个子模块的有序字典(OrderedDict)或者一系列子模块作为参数来逐一添加Module的实例,而模型的前向计算就是将这些实例按添加的顺序进行逐一计算。
classMySequential(nn.Module):fromcollectionsimportOrderedDictdef__init__(self,*args):super(MySequential,self).__init__()#如果传入的是一个OrderedDictiflen(args)==1andisinstance(args[0],OrderedDict):forkey,moduleinargs[0].items():#add_module方法会将module添加进self._modules(一个OrderedDict)self.add_module(key,module)else:#传入的是一些Moduleforidx,moduleinenumerate(args):self.add_module(str(idx),module)defforward(self,input):#self._modules返回一个OrderedDict,保证会按照成员添加时的顺序遍历成员formoduleinself._modules.values():input=module(input)returninput
net=MySequential(nn.Linear(,),nn.ReLU(),nn.Linear(,10),)
ModuleList类:接收一个子模块的列表作为输入,然后也可以类似List那样进行append和extend操作。
net=nn.ModuleList([nn.Linear(,),nn.ReLU()])net.append(nn.Linear(,10))
既然Sequential和ModuleList都可以进行列表化构造网络,那二者区别是什么呢?ModuleList仅仅是一个储存各种模块的列表,这些模块之间没有联系也没有顺序(所以不用保证相邻层的输入输出维度匹配),而且没有实现forward功能,需要自己实现;而Sequential内的模块需要按照顺序排列,要保证相邻层的输入输出大小相匹配,内部forward功能已经实现。另外,ModuleList不同于一般的Python的list,加入到ModuleList里面的所有模块的参数会被自动添加到整个网络中。
classMyModule(nn.Module):def__init__(self):super(MyModule,self)._init__()self.linears=nn.ModuleList([nn.Linear(10,10)])#self.linears=[nn.Linear(10,10)]defforward(self,x):y=self.linears(x)returny
ModuleDict类:接收一个子模块的字典作为输入,然后也可以类似字典那样进行添加访问操作。
net=nn.ModuleDict({linear:nn.Linear(,),act:nn.ReLU(),})net[output]=nn.Linear(,10)#添加
和ModuleList一样,ModuleDict实例仅仅是存放了一些模块的字典,并没有定义forward函数需要自己定义。同样,ModuleDict也与Python的Dict有所不同,ModuleDict里的所有模块的参数会被自动添加到整个网络中。
可以嵌套调用Module子类,比如Sequential、ModuleList、ModuleDict类以及自定义继承自nn.Module的类。
classNestMLP(nn.Module):def__init__(self):super(NestMLP,self).__init__()self.net=nn.Sequential(nn.Linear(40,30),nn.ReLU())defforward(self,x):returnself.net(x)net=nn.Sequential(NestMLP(),nn.Linear(30,20),FancyMLP())
#输出Sequential((0):NestMLP((net):Sequential((0):Linear(in_features=40,out_features=30,bias=True)(1):ReLU()))(1):Linear(in_features=30,out_features=20,bias=True)(2):FancyMLP((linear):Linear(in_features=20,out_features=20,bias=True)))
小结
可以通过继承Module类来构造模型。
Sequential、ModuleList、ModuleDict类都继承自Module类。
与Sequential不同,ModuleList和ModuleDict并没有定义一个完整的网络,它们只是将不同的模块存放在一起,需要自己定义forward函数。
虽然Sequential等类可以使模型构造更加简单,但直接继承Module类可以极大地拓展模型构造的灵活性。
=============================================
使用默认方式初始化含单隐层的多层感知机的参数,并做一次前向计算。
importtorchfromtorchimportnnfromtorch.nnimportinitnet=nn.Sequential(nn.Linear(4,3),nn.ReLU(),nn.Linear(3,1))X=torch.rand(2,4)Y=net(X).sum()
对于Sequential实例中含模型参数的层,我们可以通过Module类的parameters()或者named_parameters方法来访问所有参数。其中,named_parameters方法除了返回参数张量外,还会返回其名字。
net=nn.Sequential(nn.Linear(4,3),nn.ReLU(),nn.Linear(3,1))forname,paraminnet.named_parameters():print(name,param.size())0.weighttorch.Size([3,4])0.biastorch.Size([3])2.weighttorch.Size([1,3])2.biastorch.Size([1])
返回的名字自动加上了层数的索引作为前缀。
对于使用Sequential类构造的神经网络,我们可以通过方括号[]来访问网络的任一层。因为这里是单层的,所以返回的名字没有了层数索引的前缀。
net=nn.Sequential(nn.Linear(4,3),nn.ReLU(),nn.Linear(3,1))forname,paraminnet[0].named_parameters():print(name,param.size(),type(param))weighttorch.Size([3,4])classtorch.nn.parameter.Parameterbiastorch.Size([3])classtorch.nn.parameter.Parameter
返回的param的类型为torch.nn.parameter.Parameter,其实这是Tensor的子类。和Tensor不同的是如果一个Tensor是Parameter,那么它会自动被添加到模型的参数列表里。
classMyModule(nn.Module):def__init__(self):super(MyModule,self).__init__()#只有weight1被添加到模型的参数列表里,weight2则不会self.weight1=nn.Parameter(torch.rand(20,20))self.weight2=torch.rand(20,20)defforward(self,X):pass
因为Parameter是Tensor,即Tensor拥有的属性它都有,比如可以根据data来访问参数数值,用grad来访问参数梯度。
PyTorch的init模块里提供了多种预设的初始化方法。
net=nn.Sequential(nn.Linear(4,3),nn.ReLU(),nn.Linear(3,1))forname,paraminnet.named_parameters():ifweightinname:init.normal_(param,mean=0,std=0.01)ifbiasinname:init.constant_(param,val=0)
有时候我们需要的初始化方法并没有在init模块中提供。这时,可以实现一个初始化方法,从而能够像使用其他初始化方法那样使用它。例如,PyTorch实现torch.nn.init.normal_如下所示。这是一个inplace原地改变Tensor值的函数,而且这个过程是不记录梯度的。
defnormal_(tensor,mean=0,std=1):#在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。#而对于tensor的计算操作,默认是要进行计算图的构建的,在这种情况下,可以使用withtorch.no_grad(),强制之后的内容不进行计算图构建。withtorch.no_grad():returntensor.normal_(mean,std)
类似的我们来实现一个自定义的初始化方法。
definit_weight_(tensor):withtorch.no_grad():tensor.uniform_(-10,10)
在有些情况下,我们希望在多个层之间共享模型参数。例如,Module类的forward函数里多次调用同一个层。此外,如果传入Sequential的模块是同一个Module实例的话参数也是共享的。
linear=nn.Linear(1,1,bias=False)net=nn.Sequential(linear,linear)forname,paraminnet.named_parameters():init.constant_(param,val=3)#输出Sequential((0):Linear(in_features=1,out_features=1)(1):Linear(in_features=1,out_features=1))0.weighttensor([[3.]])#在内存中,这两个线性层其实一个对象:print(id(net[0])==id(net[1]))
===========================================================================
PyTorch提供了大量常用的层,但有时候我们依然希望自定义层。本节将介绍如何使用Module来自定义层,从而可以被重复调用。
不含模型参数的自定义层
importtorchfromtorchimportnn#自定义了一个将输入减掉均值后输出的层classCenteredLayer(nn.Module):def__init__(self):super(CenteredLayer,self).__init__()defforward(self,x):returnx-x.mean()
含模型参数的自定义层
Parameter类其实是Tensor的子类。如果一个Tensor是Parameter,那么它会自动被添加到模型的参数列表里。所以在自定义含模型参数的层时,我们应该将参数定义成Parameter。
除了直接定义成Parameter类外,还可以使用ParameterList和ParameterDict分别定义参数的列表和字典。ParameterList接收一个Parameter实例的列表作为输入然后得到一个参数列表。
classMyDens(nn.Module):def__init__(self):super(MyDense,self).__init__()self.params=nn.ParameterList([nn.Parameter(torch.rand(4,4))foriinrange(3)])self.params.append(nn.Parameter(torch.rand(4,1)))defforward(self,x):foriinrange(len(self.params)):x=torch.mm(x,self.params[i])returnxprint(net)MyDense((params):ParameterList((0):Parametercontaining:[torch.FloatTensorofsize4x4](1):Parametercontaining:[torch.FloatTensorofsize4x4](2):Parametercontaining:[torch.FloatTensorofsize4x4](3):Parametercontaining:[torch.FloatTensorofsize4x1]))
ParameterDict接收一个Parameter实例的字典作为输入然后得到一个参数字典。
classMyDictDense(nn.Module):def__init__(self):super(MyDictDense,self).__init__()self.params=nn.ParameterDict({linear1:nn.Parameter(torch.randn(4,4))linear2:nn.Parameter(torch.randn(4,1))})self.params.update({linear3:nn.Parameter(torch.randn(4,2))})defforward(self,x,choice=linear1):returntorch.mm(x,self.params[choice])
===========================================================================
到目前为止,我们介绍了如何处理数据以及如何构建、训练和测试深度学习模型。然而在实际中,我们有时需要把训练好的模型部署到很多不同的设备。在这种情况下,我们可以把内存中训练好的模型参数存储在硬盘上供后续读取使用。
我们可以直接使用save函数和load函数分别存储和读取Tensor。
save将对象进行序列化,然后将序列化的对象保存到disk,使用save可以保存各种对象,包括模型、张量和字典等。而load可以将对象文件反序列化到内存中。
importtorchfromtorchimportnnx=torch.ones(3)torch.save(x,x.pt)torch.save([x,y],xy.pt)torch.save({x:x,y:y},xy_dict.pt)x_=torch.load(x.pt)
state_dict是一个从参数名称映射到参数Tesnor的字典对象。
classMLP(nn.Module):def__init__(self):super(MLP,self).__init__()self.hidden=nn.Linear(3,2)self.act=nn.ReLU()self.output=nn.Linear(2,1)defforward(self,x):returnself.output(self.act(self.hidden(x)))print(net.state_dict())OrderedDict([(hidden.weight,tensor([[0.,0.,-0.],[0.,-0.,-0.]])),(hidden.bias,tensor([-0.,-0.])),(output.weight,tensor([[-0.,0.]])),(output.bias,tensor([-0.]))])
只有具有可学习参数的层(卷积层、线性层等)才有state_dict中的条目。优化器(optim)也有一个state_dict,其中包含关于优化器状态以及所使用的超参数的信息。
optimizer=torch.optim.SGD(net.parameters(),lr=0.,momentum=0.9)print(optimizer.state_dict()){param_groups:[{dampening:0,lr:0.,momentum:0.9,nesterov:False,params:[,,,],weight_decay:0}],state:{}}
PyTorch中保存和加载训练模型有两种常见的方法:
仅保存和加载模型参数(state_dict)[推荐]
#保存torch.save(model.state_dict(),path)#加载model=MLP()model.load_state_dict(torch.load(path))
保存和加载整个模型
torch.save(model,path)model=torch.load(path)
=============================================
对复杂的神经网络和大规模的数据来说,使用CPU来计算可能不够高效。在本节中,我们将介绍如何使用单块NVIDIAGPU来计算,所以需要确保已经安装好了PyTorchGPU版本。
可以通过nvidia-smi命令来查看显卡信息。
PyTorch可以指定用来存储和计算的设备,如使用内存的CPU或者使用显存的GPU。默认情况下,PyTorch会将数据创建在内存,然后利用CPU来计算。
importtorchfromtorchimportnntorch.cuda.is_available()#查看GPU是否可用:输出Truetorch.cuda.device_count()#查看GPU数量:输出1torch.cuda.current_device()#查看当前GPU索引号:输出0torch.cuda.get_device_name(0)#根据索引号查看GPU名字:输出GeForceGTX
默认情况下,Tensor会被存在内存上。因此,之前每次打印Tensor的时候看不到GPU的相关标识。
x=torch.tensor([1,2,3])print(x)#输出tensor([1,2,3])
使用.cuda()可以将CPU上的Tensor转换(复制)到GPU上。如果有多块GPU,我们用.cuda(i)来表示第i块GPU及相应的显存(ii从0开始)且cuda(0)和cuda()等价。
x=x.cuda(0)print(x)#输出tensor([1,2,3],device=cuda:0)print(x.device)#通过device属性查看该Tensor所在的设备,输出device(type=cuda,index=0)
可以直接在创建的时候就指定设备。
devide=torch.device(cudaiftorch.cuda.is_available()elsecpu)#两种创建方式x=torch.tensor([1,2,3],device=device)x=torch.tensor([1,2,3]).to(device)
如果对在GPU上的数据进行运算,那么结果还是存放在GPU上。存储在不同位置中的数据是不可以直接进行计算的。即存放在CPU上的数据不可以直接与存放在GPU上的数据进行运算,位于不同GPU上的数据也是不能直接进行计算的。
同Tensor类似,PyTorch模型也可以通过.cuda转换到GPU上。我们可以通过检查模型参数的device属性来查看存放模型的设备。
net=nn.Linear(3,1)list(net.parameters())[0].device#输出device(type=cpu)net.cuda()list(net.parameters())[0].device#输出device(type=cuda,index=0)
同样,需要保证模型输入的Tensor和模型都在同一设备上,否则会报错。
预览时标签不可点收录于话题#个上一篇下一篇转载请注明:http://www.sonphie.com/jibzd/14619.html