按照官网安装,一般是两行命令,第一行装的是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())
使用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
# 生成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或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()读取后会进入哪个模式,但是如果保存模型时是评估模式,读取模式时应该也是评估模式。
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