前言:
复现论文时发现对pytorch不够熟练,故重新学习并记录
主要根据Pytorch中文手册系统的学习一遍pytorch知识
1. 张量tensor
( 1 )tensor与numpy中的ndarray相似,但是tensor可以使用GPU计算。
import torch
#创建一个5行3列的未初始化矩阵
x = torch.empty(5,3)
#创建一个5行三列的随机矩阵
x = torch.rand(5,3)
#创建一个0填充的矩阵 数据类型为long
x = torch.zeros(5,3,dtype=torch.long)
#通过现有矩阵创建一个矩阵
x = torch.tensor([5.5,3])
#根据现有的张量创建张量,新的张量的属性和现有的一样
#创建一个和现有x的属性一样的5行3列的张量
x = x.new_ones(5,3)
#如果重新设置dtype则张量的属性会发生变化
x = torch.randn_like(x,dtype = torch.float)
Note:
size方法和numpy的shape属性返回的相同
x.size()
# 输出torch.Size([5,3])
( 2 )加法操作
y = torch.rand(5,3)
#第一种方法
x+y
# 第二种方法
torch.add(x,y,out = result)
# 将x加给y,会改变y的值
y.add_(x)
Note:
任何以’'结尾的操作的结果都会替换掉原变量。如x.copy(y)
( 3 )索引操作
和numpy的索引操作一样
x[:,1]
( 4 )改变维度
torch.view和np.reshape相似
#创建一个4行4列的矩阵
x = torch.randn(4,4)
#变成1维
y = x.view(16)
#view的第一个元素为-1时从其他的维度推断该维度应该为多少
#这里是2行8列
z = x.view(-1,8)
Note:view的第一个元素为-1时从其他的维度推断该维度应该为多少
( 5 )numpy转换
tensor和numpy数组之间的转换非常方便,同时tensor和numpy数组共享内存,修改其中一个也会引起另一个的变化
5.1 torch tensor转换为numpy数组
a = torch.ones(5,5)
b = a.numpy()
5.2 numpy数组转换为torch tensor
使用from_numpy自动转化
a = no.ones(5,5)
b = torch.from_numpy(a)
( 6 )将tensor送入gpu中
6.1 tensor可以直接在gpu中创建
if torch.cuda.is_available():
device = torch.device('cuda')
y = torch.ones_like(x, device = device)
6.2 使用.to方法也可以将tensor送入gpu中
x = x.to(device)
2. autograd:自动求导
autograd包为张量上的所有操作提供了自动求导
先看代码
import torch
x = torch.ones(2,2,requires_grad=True)
设置requires_grad=True去追踪所有对于x这个张量的操作
同时通过 a . requires_grad_(True)的操作可以改变张量的requires_grad的属性
y = x+2
y.grad_fn
y被求出来后gran_fn就会被自动记录
接下来继续对y操作
z = y*y*3
out = y.mean()
out.backward()
一般的情况下使用lose.backward()这里的loss是一个标量,如果此时的loss不是一个标量,那么计算会复杂一些。可以参考PyTorch中的backward
这里举例子的out.backward()等价与out.backward(torch.tensor(1))
现在就完成了反向传播计算梯度,那么此时的out对x的梯度为d(out)/d(x)代码则非常简约
x.grad
Note:
.backward()方法自动计算所有的梯度,这个张量的梯度会自动累积到**.grad**属性
在评估模型的时候,一般就不需要再进行梯度的计算了,但是此时的requires_grad=True,那我我们将代码块就包装在with torch.no_grad()中进行评估。代码如下所示,此时是不会进行梯度的计算的
x = torch.randn(2,2,requires_grad = True)
with torch.no_grad():
x**2
3. Neural Networks
( 1 )使用torch.nn来构建神经网络
一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。
在pytorch框架中,如果需要自定义一个net,1.先继承(nn.Moudle),2.再构建组件( __init __),3.最后组装(forward)。这里的网络注释来源于构建简单CNN
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
#定义Net的初始化函数,这个函数定义了该神经网络的基本结构
def __init__(self):
super(Net, self).__init__() #复制并使用Net的父类的初始化方法,即先运行nn.Module的初始化函数
self.conv1 = nn.Conv2d(1, 6, 5) # 定义conv1函数的是图像卷积函数:输入为图像(1个通道,即灰度图),输出为 6张特征图, 卷积核为5x5正方形
self.conv2 = nn.Conv2d(6, 16, 5)# 定义conv2函数的是图像卷积函数:输入为6张特征图,输出为16张特征图, 卷积核为5x5正方形
self.fc1 = nn.Linear(16*5*5, 120) # 定义fc1(fullconnect)全连接函数1为线性函数:y = Wx + b,并将16*5*5个节点连接到120个节点上。
self.fc2 = nn.Linear(120, 84)#定义fc2(fullconnect)全连接函数2为线性函数:y = Wx + b,并将120个节点连接到84个节点上。
self.fc3 = nn.Linear(84, 10)#定义fc3(fullconnect)全连接函数3为线性函数:y = Wx + b,并将84个节点连接到10个节点上。
#定义该神经网络的向前传播函数,该函数必须定义,一旦定义成功,向后传播函数也会自动生成(autograd)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) #输入x经过卷积conv1之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
x = F.max_pool2d(F.relu(self.conv2(x)), 2) #输入x经过卷积conv2之后,经过激活函数ReLU,使用2x2的窗口进行最大池化Max pooling,然后更新到x。
x = x.view(-1, self.num_flat_features(x)) #view函数将张量x变形成一维的向量形式,总特征数并不改变,为接下来的全连接作准备。
x = F.relu(self.fc1(x)) #输入x经过全连接1,再经过ReLU激活函数,然后更新x
x = F.relu(self.fc2(x)) #输入x经过全连接2,再经过ReLU激活函数,然后更新x
x = self.fc3(x) #输入x经过全连接3,然后更新x
return x
Note:
nn.Module详解
我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__和构造函数forward这两个方法。但有一些注意技巧:
(1)一般把网络中具有**可学习参数的层(如全连接层、卷积层等)**放在构造函数__init__()中,当然我也可以吧不具有参数的层也放在里面;
(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数forward中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
(4)上面的是将所有的层都放在了构造函数__init__里面,但是只是定义了一系列的层,各个层之间到底是什么连接关系并没有,而是在forward里面实现所有层的连接关系
总结:所有放在构造函数__init__里面的层的都是这个模型的“固有属性”
( 2 )nn.ModuleList和nn.Sequential
PyTorch 中的 ModuleList 和 Sequential: 区别和使用场景
ModuleList 就是一个储存各种模块的 list,这些模块之间没有联系,没有实现 forward 功能,但相比于普通的 Python list,ModuleList 可以把添加到其中的模块和参数自动注册到网络上。而Sequential 内的模块需要按照顺序排列,要保证相邻层的输入输出大小相匹配,内部 forward 功能已经实现,可以使代码更加整洁
( 3 )选择损失函数
一个损失函数接受一对(output, target)作为输入,计算一个值来估计网络的输出和目标的差距
nn包内有很多的损失函数,要根据不同的场景选择不同的损失函数。
19个损失函数汇总
net = Net()
input = torch.randn(1,1,32,32)
output = net(input)
target = torch.randn(10)
target = target.view(1,-1)
criterion = nn.MSELoss()
loss = criterion(output, target)
( 4 )历史梯度清零和反向传播计算新的梯度
因为前文所讲.grad属性会累加,所以要清零
net.zero_grad()
...中间算呀算output等
loss.backward()
( 5 )更新权重
想使用不同的权重更新规则时,比如SGD,Nesterov-SGD,Adam等,torch.optim实现了所有的这些规则
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.01)
上面这段代码定义了用什么优化器,放在模型定义之后,训练之前。
optimizer.zero_grad()
output = net(input)
criterion = nn.MSELoss()
loss = criterion(output, target)
loss.backward()
optimizer.step()
主要回顾了一下tensor的概念,pytorch的自动求导机制,以及如何使用pytorch定义和训练一个简单网络结构。