Tensor是pytorch中最基本的数据类型, 相当于numpy中的ndarray, 是具有追踪运算, 自动求导功能的多维数组.
x = torch.rand(5, 3) # 随机二维数组
x = torch.empty(5, 3) # 创建二维数组但不进行初始化
x = torch.zeros(5, 3) # 创建全零的二维数组
x = torch.tensor([1, 2, 3]) # 从list创建tensor
x = torch.from_numpy(np.array([1, 2, 3])) # 使用ndarray创建张量
# 从已有张量创建新张量, 新张量复用原有张量的未指定参数
x = x.new_ones(5, 3, dtype=torch.long)
x = torch.randn_like(x, dtype = torch.double) # reuse the size and override the type
张量加法:
x + y
torch.add(x, y)
y.add_(x) # 所有带_后缀的方法都是在原地修改张量
切片: 语法和numpy一致
修改shape: 使用x.view(dim1, dim2, ..)
使用-1
表示按照其他维度自适应
矩阵乘: 使用torch.mm(tensor1, tensor2)
计算矩阵乘
矩阵转置: 二维矩阵的转置torch.Tensor.t()
高维矩阵的转置: torch.Tensor.transpose(dim1, dim2, dim3, ...)
, dim是原矩阵的维度index
元素点乘: 使用troch.mul(tensor1, tensor2)
计算元素范围的乘法
numpy桥梁: 对张量使用x.numpy()
获得张量对应的np.ndarray, 二者共享底层的数据, 即修改该array能够直接修改tensor的数据
获取值: 对于只含有一个元素的tensor, 可以使用x.item()
获得数值, 对于含有多个元素的tensor, 使用x. tolist()
.
在使用GPU计算前, 需要将训练集, 标签, 模型都转入到GPU中, 对于tensor, 使用x.to(device)
转移到对应的设备中.
expand函数对tensor按照size进行扩展, 这种扩展不会增加内存, 也就是说, 他们是指向的同一对象, 修改其中任意一个会修改全部. size与原tensor.size()的关系, size能够在原tensor的size为1的维扩展, 其他维保持不变, 也可以使用-1
代替其他维的shape, 但是不能够不一致. 可以直接扩展为更高的维, 更高的维一定在现有维之前.
*size是解序列, 这里例如传入一个list, *list将list解析为它的所有元素
In [42]: a = torch.tensor([1, 2, 3, 4])
In [43]: a.unsqueeze(1).expand(4, 2) # (4,1)->(4,2)
Out[43]:
tensor([[1, 1],
[2, 2],
[3, 3],
[4, 4]])
In [45]: a.unsqueeze(1).expand(-1, 2) #(4,1) - (-1, 2) -> (4, 2)
Out[45]:
tensor([[1, 1],
[2, 2],
[3, 3],
[4, 4]])
a.unsqueeze(1).expand(5, -1, 2) #(4,1) -> (5, 4, 2)
a.unsqueeze(1).expand(-1, 2, 5) # 报错, 高维在前
a.unsqueeze(1).expand(3, 2) # 报错, size不为1的dim的size, 4和3不能够对应
和expand的功能类似, 但是是对原tensor的复制, 而不是引用. 参数是每个维度的重复次数.
In [5]: a = torch.tensor([1, 2, 3, 4])
In [6]: a.repeat(2, 2)
Out[6]:
tensor([[1, 2, 3, 4, 1, 2, 3, 4],
[1, 2, 3, 4, 1, 2, 3, 4]])
对tensor在dim维进行索引, 例如对于4*3的tensor, 对dim1进行索引, 则index的所有元素都应该在0-2. 此外, 被索引的tensor和index的维度, 在除了dim维都需要保持一致, 上例中, index的shape应当是(4, *).
In [25]: a=torch.tensor([[.1,.2,.3],
...: [1.1,1.2,1.3],
...: [2.1,2.2,2.3],
...: [3.1,3.2,3.3]])
In [40]: a.gather(1, torch.tensor([[0, 1, 2, 1]]).t())
Out[40]:
tensor([[0.1000],
[1.2000],
[2.3000],
[3.2000]])
In [41]: a.gather(1, torch.tensor([[0, 1, 2, 1],[2, 1, 0, 1]]).t())
Out[41]:
tensor([[0.1000, 0.3000],
[1.2000, 1.2000],
[2.3000, 2.1000],
[3.2000, 3.2000]])
a.gather(1, torch.tensor([[0, 1, 2, 1]])) # 报错tensor(4, 3), index(1, 4), dim1, 对不上
a.gather(1, torch.tensor([0, 1, 2, 1])) # 报错tensor(4, 3), index(4,) dim1, 对不上
a.gather(1, torch.tensor([[0, 1, 2, 3]]).t()) # 报错, dim1范围0-2, 出现3
autograd
包是pytorch的核心包, 它提供了对于在tensor上的所有操作自动计算微分的功能, 使用者只需要关心网络的正向传播结构, 而不用处理反向传播. 对于tensor
来说, 当设置其requires_grad
参数为True
之后, tensor开始追踪在其上的所有操作, 当调用.backward()
方法时, 会计算从开始记录到当前tensor的操作的导数, 并留在沿计算流程的每个tensor的.grad
属性中, 当不希望tensor记录历史时, 使用.detach()
方法停止它记录计算历史, 来避免未来的计算被记录. 除了使用该方法, 也可以对代码块添加with torch.no_grad():
. 当对模型进行评估时, 这个功能十分有用, 能够屏蔽requires_grad =True
的参数发生改变.
要获得任意层的权重的导数 ∂ L o s s ∂ W i \frac{\partial Loss}{\partial W_i} ∂Wi∂Loss, 包括两步,
loss.backward() # loss -- tensor
print(w_i.grad) # loss对w_i的偏导值
grad
属性最初为None, 当使用backward
方法时, 该属性会成为一个tensor, 多次使用backward
, 同一tensor的grad会累加, 因此需要对其重新置0, 或直接调用nn.Module.zero_grad()
方法.
默认情况下, backward
被调用后会释放已经完成反向传播的这部分计算图的内存, 当一次迭代的后面部分还需要用到前面的计算图时, 例如A3C的情况, 就可以对backward
方法加上retain_graph
参数.
一个典型的神经网络训练过程如下:
weight = weight - learning_rate * gradient
nn.Module
的子类__init__
, forward(self, input)
方法, 当调用该类的实例时, 其实是调用的forward
方法.构造函数必须先调用父类的构造函数,super(Linear, self).__init__()
或nn.Module.__init__()
, 前者更加普遍.forward
方法中实现nn.Module
是最基本的容器类, 其他网络模块都是其子类, module可以相互嵌套. 自己写的model
也应该是其子类.
nn.Sequential
是比较简单的容器, 能够构建线性网络的结构.
# Example of using Sequential
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
pytorch提供了大量的层供使用, 具体参考documentation
大部分nn中的层class都有nn.function对应,其区别是
由于两者性能差异不大,所以具体使用取决于个人喜好。对于激活函数和池化层,由于没有可学习参数,一般使用nn.functional
完成,其他的有学习参数的部分则使用类。但是Droupout由于在训练和测试时操作不同,所以建议使用nn.Module
实现,它能够通过model.eval
加以区分。
nn.functional
不需要放入__init__进行构造,所以不具有可学习参数的部分可以使用nn.functional
进行代替。
两者主要的区别就是对于可学习参数nn.Parameter
的识别能力,所以可以使用nn.functional
提供的函数构造nn.Module
, 构造时, 将参数tensor注册成Parameter
类即可.
class Linear(nn.Module):
def __init__(self, in_features, out_features):
# nn.Module.__init__(self)
super(Linear, self).__init__()
self.w = nn.Parameter(t.randn(out_features, in_features)) # nn.Parameter是Tensor的子类
self.b = nn.Parameter(t.randn(out_features))
def forward(self, x):
# wx+b
return F.linear(x, self.w, self.b)
layer = Linear(4, 3)
input = V(t.randn(2, 4))
output = layer(input)
print(output)
class Net(nn.Module)
的方式定义网络的__init__
和forward
, forward返回的应当是网络的输出, 即预测.nn.MSELoss()
等类定义loss函数, loss函数实例的输入是网络输出和标签, 输出是loss值.net
的梯度清零后, 从loss进行反向传播import torch.optim as optim
import torch.nn as nn
net = Net(params)
critierion = nn.MSELoss()
optimizer = optim.SGD(net.parameters(), lr)
optimizer.zero_grad()
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()
import torch
import torchvision
import torchvision.transforms as transforms
# The output of torchvision datasets are PILImage images of range [0, 1]. We transform them to Tensors of normalized range [-1, 1].
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# build network
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
# define loss
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
# train the network
for epoch in range(2): # loop over the dataset multiple times
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# get the inputs; data is a list of [inputs, labels]
inputs, labels = data
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.item()
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
# Test the network
dataiter = iter(testloader)
images, labels = dataiter.next()
outputs = net(images)
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
for j in range(4)))
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
pytorch提供了Dataset
类用于描述一个抽象的数据集, 使用时自定义类重写__getitems__()
方法和__len__()
方法实现一个映射型的数据集, 可以通过datasetclass[idx]
获得第idx个数据. 也可以重写__iter__()
方法实现一个iterableDataset
, 这主要适合随机访问开销很大的情形和batch size与数据集大小有关的情形.
在Dataset
的基础上, pytorch提供了DataLoader
类对dataset
进行进一步的处理, 这个类实现了
几个功能, 建议数据导入阶段都将数据包装成Dataset
并交给DataLoader
来管理.
参考
- DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ
- 『PyTorch』第十二弹_nn.Module和nn.functional