所有的深度学习都是关于张量的计算,它一般是矩阵可以被索引到高于2维。
首先,让我们看看我们能用张量做什么。
import torch
import torch.autograd as autograd
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
torch.manual_seed(1)
创建张量
张量可以从Python列表中使用torch.Tensor()函数创建。
# torch.tensor(data) creates a torch.Tensor object with the given data.
V_data = [1., 2., 3.]
V = torch.tensor(V_data)
# Creates a matrix
M_data = [[1., 2., 3.], [4., 5., 6]]
M = torch.tensor(M_data)
# Create a 3D tensor of size 2x2x2.
T_data = [[[1., 2.], [3., 4.]],
[[5., 6.], [7., 8.]]]
T = torch.tensor(T_data)
什么是三维张量?
像这样思考。
如果你有一个向量,对向量的索引会给你一个标量。
如果你有一个矩阵,对矩阵的索引会给你一个向量。
如果你有一个三维张量,那么对张量的索引就会得到一个矩阵!
注意术语:
当我在本教程中说“张量”时,它指的是任何torch.Tensor对象。
矩阵和向量是torch的特殊情况。
张量,它们的维数分别是1和2。
当我说到3D张量时,我将明确地使用“3D张量”这个术语。
# Index into V and get a scalar (0 dimensional tensor)
print(V[0])
# Get a Python number from it
print(V[0].item())
# Index into M and get a vector
print(M[0])
# Index into T and get a matrix
print(T[0])
你也可以创建其他数据类型的张量。默认值,如您所见,是Float。
要创建整数类型的张量,请尝试一下torch.LongTensor()。
检查文档以获得更多的数据类型,但是Float和Long将是最常见的。
你可以用随机数和提供的维度来创建一个张量:torch.randn()
x = torch.randn((3, 4, 5))
print(x)
你可以按照你期望的方式对张量进行操作。
x = torch.tensor([1., 2., 3.])
y = torch.tensor([4., 5., 6.])
z = x + y
print(z)
tensor([ 5., 7., 9.])
它们的扩展 比数学运算还要多。
我们稍后将使用的一个有用的操作是串联。
默认按照 行 连接
# By default, it concatenates along the first axis (concatenates rows)
x_1 = torch.randn(2, 5)
y_1 = torch.randn(3, 5)
z_1 = torch.cat([x_1, y_1])
print(z_1)
tensor([[-0.8029, 0.2366, 0.2857, 0.6898, -0.6331],
[ 0.8795, -0.6842, 0.4533, 0.2912, -0.8317],
[-0.5525, 0.6355, -0.3968, -0.6571, -1.6428],
[ 0.9803, -0.0421, -0.8206, 0.3133, -1.1352],
[ 0.3773, -0.2824, -2.5667, -1.4303, 0.5009]])
按照 列 连接
# Concatenate columns:
x_2 = torch.randn(2, 3)
y_2 = torch.randn(2, 5)
# second arg specifies which axis to concat along
z_2 = torch.cat([x_2, y_2], 1)
print(z_2)
# If your tensors are not compatible, torch will complain. Uncomment to see the error
# torch.cat([x_1, x_2])
tensor([[ 0.5438, -0.4057, 1.1341, -0.1473, 0.6272, 1.0935, 0.0939,1.2381],
[-1.1115, 0.3501, -0.7703, -1.3459, 0.5119, -0.6933, -0.1668,-0.9999]])
Reshaping Tensors 重塑张量
Use the .view() method to reshape a tensor.
这种方法得到了大量的使用,因为许多神经网络组件期望它们的输入有一定的形状。
通常,在将数据传递给组件之前,您需要进行重构。
x = torch.randn(2, 3, 4)
print(x)
print(x.view(2, 12)) # Reshape to 2 rows, 12 columns
# Same as above. If one of the dimensions is -1, its size can be inferred
print(x.view(2, -1))
tensor([[[ 0.4175, -0.2127, -0.8400, -0.4200],
[-0.6240, -0.9773, 0.8748, 0.9873],
[-0.0594, -2.4919, 0.2423, 0.2883]],
[[-0.1095, 0.3126, 1.5038, 0.5038],
[ 0.6223, -0.4481, -0.2856, 0.3880],
[-1.1435, -0.6512, -0.1032, 0.6937]]])
tensor([[ 0.4175, -0.2127, -0.8400, -0.4200, -0.6240, -0.9773, 0.8748,
0.9873, -0.0594, -2.4919, 0.2423, 0.2883],
[-0.1095, 0.3126, 1.5038, 0.5038, 0.6223, -0.4481, -0.2856,
0.3880, -1.1435, -0.6512, -0.1032, 0.6937]])
tensor([[ 0.4175, -0.2127, -0.8400, -0.4200, -0.6240, -0.9773, 0.8748,
0.9873, -0.0594, -2.4919, 0.2423, 0.2883],
[-0.1095, 0.3126, 1.5038, 0.5038, 0.6223, -0.4481, -0.2856,
0.3880, -1.1435, -0.6512, -0.1032, 0.6937]])
计算图和自动微分
计算图的概念对于有效的深度学习编程是至关重要的,因为它允许您不必自己编写反向传播梯度。
计算图只是说明如何将数据组合起来以获得输出的规范。
由于图形完全指定了哪些参数与哪些操作有关,所以它包含了足够的信息来计算导数。
这可能听起来很模糊,所以让我们看看使用基本标志requires_grad的情况
首先,从程序员的角度来看。
在torch中储存了什么。
我们在上面创建的张量对象?
很明显,数据和形状,还有一些其他的东西。
但是当我们把两个张量加在一起时,我们得到了一个输出张量。
所有这些输出张量都知道它的数据和形状。
它不知道它是另外两个张量的和(它可能是从文件中读取的,可能是一些文件的结果其他操作,等等)。
If requires_grad=True,张量对象跟踪它是如何被创建的。
让我们在行动中看到它
# Tensor factory methods have a ``requires_grad`` flag
x = torch.tensor([1., 2., 3], requires_grad=True)
# With requires_grad=True, you can still do all the operations you previously could
y = torch.tensor([4., 5., 6], requires_grad=True)
z = x + y
print(z)
# BUT z knows something extra.
print(z.grad_fn)
tensor([ 5., 7., 9.])
<AddBackward1 object at 0x000001F74B564828>
所以张量知道是什么创造了它们。
z知道它不是从一个文件中读取的,它不是一个乘法或指数的结果,或者别的什么。
如果你一直跟着z。你会在x和y发现自己。
但这如何帮助我们计算梯度呢?
# Lets sum up all the entries in z
s = z.sum()
print(s)
print(s.grad_fn)
tensor(21.)
<SumBackward0 object at 0x000001F74B564518>
那么现在,x的第一个分量的和的导数是什么呢?
嗯,s知道它是作为张量z的和。z知道它是x+y的总和。
所以s包含了足够的信息来确定我们想要的导数是1!
当然,这掩盖了如何计算导数的挑战。
这里的重点是s携带了足够的信息,可以计算它。
在现实中,Pytorch程序的开发人员可以通过sum()和+操作来计算他们的梯度,并运行反向传播算法。
对该算法的深入讨论超出了本教程的范围。
让Pytorch计算梯度,看看我们是对的:
注意,如果你多次运行这个block,梯度将会增加。
这是因为Pytorch将梯度累积到.grad的属性中,因为对于许多模型来说,这是非常方便的。
# calling .backward() on any variable will run backprop, starting from it.
s.backward()
print(x.grad)
tensor([ 6., 6., 6.])
理解下面这一段的内容对于成为一个成功的深度学习的程序员是至关重要的。
x = torch.randn(2, 2)
y = torch.randn(2, 2)
# By default, user created Tensors have ``requires_grad=False``
print(x.requires_grad, y.requires_grad)
False False
z = x + y
# So you can't backprop through z
print(z.grad_fn)
None
# ``.requires_grad_( ... )`` changes an existing Tensor's ``requires_grad`` flag in-place.
# The input flag defaults to ``True`` if not given.
x = x.requires_grad_()
y = y.requires_grad_()
# z contains enough information to compute gradients, as we saw above
z = x + y
print(z.grad_fn)
<AddBackward1 object at 0x000001F74B564240>
# If any input to an operation has ``requires_grad=True``, so will the output
print(z.requires_grad)
True
# Now z has the computation history that relates itself to x and y
# Can we just take its values, and **detach** it from its history?
new_z = z.detach()
# ... does new_z have information to backprop to x and y? # NO!
print(new_z.grad_fn)
# And how could it? ``z.detach()`` returns a tensor that shares the same storage as ``z``, but with the computation history forgotten.
# It doesn't know anything about how it was computed.
# In essence, we have broken the Tensor away from its past history
None
你也可以阻止autograd在张量上跟踪历史requiresgrad=True,用结束代码块with torch.no_grad():
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
True
True
False
网友评论