pytorch笔记:一些常见操作,构建全连接神经网络,验证操作,评估模式

文章目录

  • 生成tensor
    • 格式转换
    • 设置requires_grad参数
  • 构建全连接神经网络
    • 构建全连接神经网络模型
    • 选择损失函数
    • 选择优化方法
    • 反向传播
    • 批训练操作
    • GPU加速
    • 保存和读取模式
    • 所有代码
  • 快速搭建模型与添加模型结构
    • 快速搭建模型
    • 添加模型结构
  • 输出模型结构或者中间层权重
    • 输出模型结构
    • 输出参数取值
  • 验证操作与模型的评估模式
    • 验证操作
    • 评估模式
  • torchvision

  Pytorch是神经网络届的numpy,numpy会把array放到CPU中加速计算,torch会把tensor放到GPU中加速计算。torch和numpy有很多用法是一样的,只不过numpy的方法接受的是array或者matrix,torch接受的是tensor。

  按照官网安装,一般是两行命令,第一行装的是pytorch的主模块,第二行安装的torchvision,会包含一些数据库,例如图片数据库,还有一些预先训练好的模型,例如VGG,ResNet,在写神经网络模型时,可以直接照搬里面的模板。

  在使用之前需要导入库,一般有

import torch       
from torch import nn    # 这个库用于构建神经网络的结构,neural networks的缩写
                        # 里面包括线性层,卷积层,循环神经网络单元等, 当然也包括很多的激活函数
from torch import optim   # 优化方法,MSE,交叉熵等
from torch.utils.data import DataLoader    # 用于mini-batch SGD的喂数据的类

  还有一些类可以根据需要选择是否导入

from torch.autograd import Variable     # 变量类,以前需要把tensor类型变成variable类型才可以反向传播
                                        # 现在版本一般合并了tensor类型和variable类型,直接用tensor类型就可以反向传播  
from collections import OrderedDict  # 字典一般没有先后顺序,OrderedDict 用以生成有先后顺序的字典
from torch.utils.data.sampler import SubsetRandomSampler    # 手动抽样的类,根据需要选择是否导入这个库

我们可以通过下面的方法,查看pytorch的版本,以及是否支持使用GPU加速

print(torch.__version__) 
print(torch.cuda.is_available())

可以通过下面的方法查看可以使用多少GPU

print(torch.cuda.device_count())

生成tensor

  使用pytorch时,要注意三种大的数据类型,一是传统python的List和numpy的nd.array,二是pytorch中的tensor,三是pytorch中的variable;早期版本,我们需要沿着这条线,从传统python数据结构转换到tensor,在从tensor转换到variable,只有variable才可以进行反向传播,但是较新的版本的pytorch合并了tensor和variable的功能(variable类仍然存在,仍可以按照老版代码写),不需要转换variable,只需要把tensor的requires_grad参数设置为True,如果requires_grad=Fasle时不需要更新梯度, 适用于冻结某些层的梯度

# 法一:从numpy库生成模板
data = [1,2,3,4]
data = np.array(data)
tensor = torch.from_numpy(data)    # 默认生成torch.int

data = [1.,2,3,4]    
data = np.array(data)
tensor = torch.from_numpy(data)    # 默认生成torch.float64

## 同样也可以把tensor转换成numpy形式
data2 = tensor.numpy()

#  法二:利用pytorch的一些函数,这些函数一般和numpy中的类似
tensor = torch.zeros(1, requires_grad=True)  # 对应numpy中的np.zeros()     
                #  requires_grad=True表示tensor写入计算图,进行梯度反向传播
tensor = torch.linspace(-5,5,200)   # 对应numpy中的np.linspace()
tensor = torch.add(tensor,tensor)  # 对应numpy中的np.add()
tensor = torch.mean(tensor)  # 对应numpy中的np.mean()

# 法三:当然也可以直接转换成FloatTensor等tensor形式
tensor = torch.FloatTensor([[1,2],[3,4]])   # 直接把list转化成浮点形式的tensor

格式转换

data = [1,2,3,4]
data = np.array(data)
tensor = torch.from_numpy(data)    # 默认生成torch.int
tensor = tensor.type(torch.float)    # 转化为torch.float32
tensor = tensor.type(torch.float64)    # 转化为torch.float64
tensor = tensor.type(torch.float16)    # 转化为torch.float16
tensor = tensor.type(torch.int8)     # 转化为torch.int8

设置requires_grad参数

# 生成tensor变量时就设置requires_grad
tensor = torch.zeros(1, requires_grad=True)   # 生成时默认requires_grad参数是False, 除非声明requires_grad=True

# 单个tensor变量改变requires_grad
tensor = torch.FloatTensor([[1,2],[3,4]])   # 生成时默认requires_grad参数是False,torch.FloatTensor没有requires_grad参数
tensor.requires_grad = False

# 或者
tensor.requires_grad_(True)

# 整个模型改变requires_grad
for param in model.parameters():
    param.requires_grad = False

注意:只有浮点类型支持backword自动求导,且求导,只能是【标量】对标量,或者【标量】对向量/矩阵求导!,一般loss函数求出来的就是一个标量。如果自变量是向量、矩阵、张量等,需要定义backword()中的gradients参数。


构建全连接神经网络

  在PyTorch中,所有的neural network module都是class torch.nn.Module的子类,在Modules中可以包含其它的Modules。

构建全连接神经网络模型

# 搭建神经网络
class Net(torch.nn.Module):   # 标准模板
    def __init__(self, n_features, n_hidden, n_output):   # 标准模板,定义生成模型时传入的参数
        super(Net, self).__init__()    # 标准模板
        self.hidden = torch.nn.Linear(n_features, n_hidden)   # 全连接层,输入n_features, 输出n_hidden
        self.predict = torch.nn.Linear(n_hidden, n_output)   # 全连接层,输入n_hidden, 输出n_output
        
    def forward(self, x):  # 标准模板,  定义前向传播, self一定要有, x是输入模型的数据
        self.x = x
        self.x = F.relu(self.hidden(self.x))
        self.x = self.predict(self.x)
        
        return self.x

选择损失函数

# 回归一般用MSE
loss_func = torch.nn.MSELoss()
# 分类一般用交叉熵
loss_func = torch.nn.CrossEntropyLoss()

选择优化方法

  如果没有特殊的选择,就选择Adam,Adam可以适用于大多数的场景。

opt = torch.optim.Adam(net.parameters(), lr = 0.2)

  pytorch中,学习率可以动态更新,例如在迭代了75%的epoch时,把学习率缩小为原来的十分之一

lr_new = 0.001        # 新定义的学习率
for param_group in opt.param_groups:
        param_group['lr'] = lr_new

反向传播

opt.zero_grad()   # 标准流程,梯度清零,猜测是因为要根据batch中的所有数据的梯度求平均,
                      # 所以每个参数位置都有batch个梯度,在进入下一个batch循环前,要清零
loss.backward()  # 标准流程,反向传播计算梯度,进行一次反向传播,计算图就会被销毁,如果不想让计算图销毁,可以retain_graph=True
opt.step()  # 标准流程,更新所有参数
# backward只把梯度传给每个变量,优化器再根据梯度来优化这些变量的值

批训练操作

  生成dataloader:

# 批数据训练
BATCH_SIZE = 10
torch_dataset = Data.TensorDataset(x,y)   # 这一步是把x,y融合在一起,
                            # 当然可以不做这一步,dataloader中的dataset传入list,list的size是 
                            # 依次为list类型,list或tuple类型,ndarray,而不是全ndarray
                            # 这样的输入有效避免了特征和标签数据量不匹配生成ndarray.object,使得dataloader报错。
loader = Data.DataLoader(
    dataset = torch_dataset,
    batch_size = BATCH_SIZE,
    shuffle = True,         # 是否打乱数据
)

  当然dataloader可以传入sampler参数(一个列表),自定义从数据集中提取样本的顺序。如果指定sampler,则忽略shuffle参数。

  使用dataloader时,利用了python的enumerate

for epoch in range(10):
    for step, (batch_x, batch_y) in enumerate(loader):
            
        pred = net(batch_x)
        loss = loss_func(pred, batch_y)
        
        opt.zero_grad()
        loss.backward()
        opt.step()

GPU加速

   先定义使用哪个GPU或CPU,默认GPU是从零开始编号,也就是说,如果设置torch.device(“cuda:3”),就使用了第4块显卡。

device = torch.device("cuda:3" if torch.cuda.is_available() else "cpu")
print(device)

# 输出:
# device(type='cuda', index=3)

  pytorch中想要使用GPU加速,只需把模型、数据后面加上" .to(device) "即可,优化方法和损失函数加不加都可以。注意这个时候数据类型已经不同,如果CPU下数据类型是torch.FloatTensor,GPU模式下数据类型就变成了torch.cuda.FloatTensor。

print(torch.FloatTensor([1.]))    # 输出:tensor([1.])
print(torch.FloatTensor([1.]).to(device))     # 输出:tensor([1.], device='cuda:3')

  在变回numpy前,需要用.cpu()切回CPU模式

print(torch.FloatTensor([1.]).to(device).cpu())    # 输出:tensor([1.])
print(torch.FloatTensor([1.]).to(device).cpu().numpy())     # 输出:[1.]

保存和读取模式

保存

# 保存
# 法一,保存整个神经网络
torch.save(net, 'net.pkl')

# 法二,保存神经网络的参数
torch.save(net.state_dict(), 'net_para.pkl')

# 以元祖形式保存,可以保存更多的东西
torch.save({'epoch': i, 'model': model, 'train_loss': train_loss,
                        'valid_loss': valid_loss},  model_filename + '.model')

提取

# 提取
# 存储方法一对应的读取,整个神经网络
net2 = torch.load('net.pkl')
prediction = net2(x)

# 存储方法二对应的读取,神经网络的参数,需要先搭建神经网络,然后把参数放进去   # 这里是一种快速搭建法
net3 = torch.nn.Sequential(
    torch.nn.Linear(1,10),
    torch.nn.ReLU(),      # 注意这里的ReLU的大写成分
    torch.nn.Linear(10,1)
)
net3.load_state_dict(torch.load('net_para.pkl'))
prediction = net3(x)

# 元祖对应读取
best_model = torch.load(model_filename + '.model').get('model')

所有代码

# 初始化数据
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)  # x data (tensor), shape=(100, 1)  
   # 注意这里x不需要requires_grad=True,如果设置为True,就会放到计算图中,一次backward后就会销毁
   # 第二次循环的backward就会报错
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
  # 同样,这里y也不需要requires_grad=True

# 批数据训练
BATCH_SIZE = 10
torch_dataset = Data.TensorDataset(x,y)   # 这一步是把x,y融合在一起,
loader = Data.DataLoader(
    dataset = torch_dataset,
    batch_size = BATCH_SIZE,
    shuffle = True,         # 是否打乱数据
)

# 搭建神经网络
class Net(torch.nn.Module):
    def __init__(self, n_features, n_hidden, n_output):
        super(Net, self).__init__()
        self.hidden = torch.nn.Linear(n_features, n_hidden)
        self.predict = torch.nn.Linear(n_hidden, n_output)
        
    def forward(self, x):
        self.x = x
        self.x = F.relu(self.hidden(self.x))
        self.x = self.predict(self.x)
        
        return self.x
    
# 实例化
net = Net(1,10,1)

# 训练模式
net.train()   
    
# 优化方法和损失函数
loss_func = torch.nn.MSELoss()
opt = torch.optim.Adam(net.parameters(), lr = 0.2)


# 迭代训练
for epoch in range(10):
    total_train_loss = 0
    for step, (batch_x, batch_y) in enumerate(loader):
        
        pred = net(batch_x)
        loss = loss_func(pred, batch_y)
        
        total_train_loss += loss.item()
        
        opt.zero_grad()
        loss.backward()
        opt.step()
    
    print('epoch:', epoch, 'total_train_loss:', total_train_loss)

快速搭建模型与添加模型结构

快速搭建模型

  pytorch除了像上面那样中规中矩的定义模型之外,还可以快速的定义模型结构,用到了torch.nn.Sequential

net = torch.nn.Sequential(OrderedDict([
    ('linear0', torch.nn.Linear(2,10)),
    ('relu0', torch.nn.ReLU()),      # 注意这里的ReLU的大写成分
    ('linear1', torch.nn.Linear(10,2))
]))

添加模型结构

  如果模型比较庞大,我们可以通过循环定义,这需要添加模型结构

net.add_module('linear_last', torch.nn.Linear(2, 2))

循环定义

ACTIVATION = torch.tanh

class Net(nn.Module):
    def __init__(self, batch_normalization=False):
        super(Net, self).__init__()
        self.do_bn = batch_normalization
        self.fcs = []
        self.bns = []
        self.input_bn = nn.BatchNorm1d(1, momentum=0.5)
        for i in range(N_HIDDEN):
            input_size = 1 if i == 0 else 10
            fc = nn.Linear(input_size, 10)
#             setattr(self, 'fc%i' % i, fc)
            self.add_module('fc%i' % i, fc)
            self.fcs.append(fc)
            if self.do_bn:
                bn = nn.BatchNorm1d(10, momentum=0.5)
#                 setattr(self, 'bn%i' % i, bn)
                self.add_module('bn%i' % i, bn)
                self.bns.append(bn)
        self.predict = nn.Linear(10,1)
        self._set_init(self.predict)
        
    def _set_init(self, layer):
        init.normal_(layer.weight, mean=0., std=.1)
        init.constant_(layer.bias, B_INIT)
        
    def forward(self, x):
        pre_activation = [x]
        if self.do_bn: x = self.input_bn(x) 
        input_layer = [x]
        for i in range(N_HIDDEN):
            x = self.fcs[i](x)
            pre_activation.append(x)
            if self.do_bn:
                x = self.bns[i](x)
            x = ACTIVATION(x)
            input_layer.append(x)
        out = self.predict(x)
        return out, pre_activation, input_layer
        
net = Net(True)
print(net)

# 输出:
Net(
#   (input_bn): BatchNorm1d(1, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc0): Linear(in_features=1, out_features=10, bias=True)
#   (bn0): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc1): Linear(in_features=10, out_features=10, bias=True)
#   (bn1): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc2): Linear(in_features=10, out_features=10, bias=True)
#   (bn2): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc3): Linear(in_features=10, out_features=10, bias=True)
#   (bn3): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc4): Linear(in_features=10, out_features=10, bias=True)
#   (bn4): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc5): Linear(in_features=10, out_features=10, bias=True)
#   (bn5): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc6): Linear(in_features=10, out_features=10, bias=True)
#   (bn6): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (fc7): Linear(in_features=10, out_features=10, bias=True)
#   (bn7): BatchNorm1d(10, eps=1e-05, momentum=0.5, affine=True, track_running_stats=True)
#   (predict): Linear(in_features=10, out_features=1, bias=True)
# )

输出模型结构或者中间层权重

输出模型结构

  在PyTorch中,所有的neural network module都是class torch.nn.Module的子类,在Modules中可以包含其它的Modules,以一种树状结构进行嵌套。当需要返回神经网络中的各个模块时,Module.modules()方法返回网络中所有模块的一个iterator,而Module.children()方法返回所有直接子模块的一个iterator。

import torchvision.models as models
resnet18 = models.resnet18()

# 法一:直接print
print(resnet18)

# 法二:利用model.modules迭代器,返回一个包含 当前模型所有模块的迭代器,输出的比children()多很多
for i in resnet18.modules():
    print(i)

# 法三:利用model.children()迭代器
for i in resnet18.children():
    print(i)

输出参数取值

  参数包括权重,偏置等

import torchvision.models as models
resnet18 = models.resnet18()

# 法一:
print(resnet18.state_dict())

# 法二:
for param in resnet18.parameters():
    print(param)
    
# 法三
for name,parameters in resnet18.named_parameters():
    print(name,':',parameters.size())

验证操作与模型的评估模式

验证操作

  早期版本可以设置volatile=True相当于requires_grad=False,适用于推断阶段,不需要反向传播。这个现在已经取消了,可以使用with torch.no_grad()来替代,在跑验证集数据或测试集数据时用的多。

x = torch.zeros(1, requires_grad=True)
with torch.no_grad(): # 将y 从计算图中排除
    y = x * 2
print(y.requires_grad)
# 输出:False

   使用with.no_grad()可以减少内存消耗,同时增加训练速度。

评估模式

  如果模型中有dropout操作和batch normalization操作,在每次迭代训练后,要进行验证前,要把模型切入评估模式。模型有两种模式,model.train()是训练模式,用于训练集,model.eval()是评估模式,用于验证集和测试集,表示我们用验证集或测试集进行评估时,不改变dropout和batch normalization的参数。

  注意model.eval()并不会阻止模型进行反向梯度更新,在pytorch的官方讨论中,有人曾提出这个问题

So why is torch.no_grad() is not enabled by default inside model.eval() function? Is there a situation where we want to compute some gradients when in evaluation mode? Even if that’s the case, it seems like no_grad() method should be made an optional argument to eval(), and set to True by default.

意思是问什么torch.no_grad()不嵌在model.eval()中,即进入评估模式,自动设置成不求梯度,而不用像现在这样单独分成两个操作,在实际使用中,我们基本不会碰到在评估模式求梯度的场景,即使有,也可以把torch.no_grad()当做model.eval()的一个参数。

  回答如下:

Some user can have a use case for this. The problem with doing this I guess is that no_grad is a context manager to work with the autograd engine while eval() is changing the state of an nn.Module.

就是说有些人真会碰到这种使用场景,为何分开torch.no_grad()与model.eval(),可能是因为这两种操作的性质确实不同,一个设置不求梯度,一个设置进入评估模式。

  目前并不知道模型通过torch.load()读取后会进入哪个模式,但是如果保存模型时是评估模式,读取模式时应该也是评估模式。


torchvision

https://blog.csdn.net/qq_34097715/article/details/83345189


参考文献:
莫烦python
pytorch加载模型和初始化权重:https://msd.misuland.com/pd/2884250137616453910
PyTorch简明笔记[2]-Tensor的自动求导(AoutoGrad):https://www.jianshu.com/p/a105858567df

你可能感兴趣的:(深度学习)