一个60分钟的教程
一.张量
pytorch与numpy中的ndarray具有相同功能,也可以通过GPU进行加速计算
>>> import torch
>>>
>>> x = torch.empty(5,5)
>>> print(x)
tensor([[8.9082e-39, 1.0194e-38, 9.1837e-39, 4.6837e-39, 9.2755e-39],
[1.0837e-38, 8.4490e-39, 9.9184e-39, 9.9184e-39, 9.0000e-39],
[1.0561e-38, 1.0653e-38, 4.1327e-39, 8.9082e-39, 9.8265e-39],
[9.4592e-39, 1.0561e-38, 1.0286e-38, 1.0469e-38, 1.0194e-38],
[1.0286e-38, 1.0653e-38, 1.0194e-38, 8.4490e-39, 8.7245e-39]])
>>> torch.rand(5,5)
tensor([[0.6960, 0.8001, 0.9463, 0.4089, 0.5145],
[0.4576, 0.0875, 0.6935, 0.9393, 0.4668],
[0.0299, 0.1402, 0.0177, 0.9930, 0.5962],
[0.9423, 0.1099, 0.2706, 0.4339, 0.5585],
[0.3378, 0.9370, 0.3657, 0.0202, 0.2063]])
# 一个类型为long的零矩阵
>>> x = torch.zeros(5, 5, dtype = torch.long)
>>> print(x)
tensor([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]])
>>> torch.tensor([3.3, 6])
tensor([3.3000, 6.0000])
二.操作
>>> x = torch.rand(5,5)
>>> y = torch.rand(5,5)
>>> torch.add(x,y)
[1.1771, 0.7440, 0.8619, 1.1943, 0.6510],
[0.9086, 0.6496, 0.2579, 0.2594, 1.2505],
[0.2617, 1.5798, 0.9825, 0.9910, 0.6327],
[0.2287, 1.7591, 0.8986, 0.8129, 1.1495]])
>>> x
tensor([[0.8418, 0.8945, 0.8226, 0.9070, 0.3377],
[0.9784, 0.5607, 0.2233, 0.3952, 0.5474],
[0.8023, 0.6251, 0.2440, 0.0614, 0.4917],
[0.0945, 0.8356, 0.2282, 0.1586, 0.0984],
[0.1122, 0.8062, 0.8406, 0.1943, 0.5918]])
>>> y
tensor([[0.6914, 0.9881, 0.5148, 0.4077, 0.2767],
[0.1987, 0.1833, 0.6386, 0.7991, 0.1037],
[0.1062, 0.0245, 0.0139, 0.1980, 0.7588],
[0.1672, 0.7442, 0.7543, 0.8324, 0.5343],
[0.1166, 0.9529, 0.0580, 0.6186, 0.5577]])
# 在不增加内存的基础上进行加法操作
>>> y.add_(x)
tensor([[1.5332, 1.8826, 1.3374, 1.3147, 0.6145],
[1.1771, 0.7440, 0.8619, 1.1943, 0.6510],
[0.9086, 0.6496, 0.2579, 0.2594, 1.2505],
[0.2617, 1.5798, 0.9825, 0.9910, 0.6327],
[0.2287, 1.7591, 0.8986, 0.8129, 1.1495]])
>>> y
tensor([[1.5332, 1.8826, 1.3374, 1.3147, 0.6145],
[1.1771, 0.7440, 0.8619, 1.1943, 0.6510],
[0.9086, 0.6496, 0.2579, 0.2594, 1.2505],
[0.2617, 1.5798, 0.9825, 0.9910, 0.6327],
[0.2287, 1.7591, 0.8986, 0.8129, 1.1495]])
# 更改y
>>> y.copy_(x)
tensor([[0.8418, 0.8945, 0.8226, 0.9070, 0.3377],
[0.9784, 0.5607, 0.2233, 0.3952, 0.5474],
[0.8023, 0.6251, 0.2440, 0.0614, 0.4917],
[0.0945, 0.8356, 0.2282, 0.1586, 0.0984],
[0.1122, 0.8062, 0.8406, 0.1943, 0.5918]])
>>> y
tensor([[0.8418, 0.8945, 0.8226, 0.9070, 0.3377],
[0.9784, 0.5607, 0.2233, 0.3952, 0.5474],
[0.8023, 0.6251, 0.2440, 0.0614, 0.4917],
[0.0945, 0.8356, 0.2282, 0.1586, 0.0984],
[0.1122, 0.8062, 0.8406, 0.1943, 0.5918]])
>>> y = x.view(25)
>>> y
tensor([0.8418, 0.8945, 0.8226, 0.9070, 0.3377, 0.9784, 0.5607, 0.2233, 0.3952,
0.5474, 0.8023, 0.6251, 0.2440, 0.0614, 0.4917, 0.0945, 0.8356, 0.2282,
0.1586, 0.0984, 0.1122, 0.8062, 0.8406, 0.1943, 0.5918])
>>> z = x.view(-1)
>>> z
tensor([0.8418, 0.8945, 0.8226, 0.9070, 0.3377, 0.9784, 0.5607, 0.2233, 0.3952,
0.5474, 0.8023, 0.6251, 0.2440, 0.0614, 0.4917, 0.0945, 0.8356, 0.2282,
0.1586, 0.0984, 0.1122, 0.8062, 0.8406, 0.1943, 0.5918])
>>> print(x.size(), y.size(), z.size())
torch.Size([5, 5]) torch.Size([25]) torch.Size([25])
>>> z = x.view(-1, 5)
>>> print(x.size(), y.size(), z.size())
torch.Size([5, 5]) torch.Size([25]) torch.Size([5, 5])
# 当张量只有一个数值的时候,该函数可以得到python数值
>>> x = torch.rand(1)
>>> print(x)
tensor([0.2856])
>>> print(x.item)
<built-in method item of Tensor object at 0x000001A2AD5214C8>
>>> print(x.item())
0.28560036420822144
>>> print(type(x))
<class 'torch.Tensor'>
>>> print(type(x.item()))
<class 'float'>
>>> x = torch.rand(5,5)
>>> print(x[:, 1])
tensor([0.1337, 0.5896, 0.0374, 0.9078, 0.6913])
>>> x
tensor([[0.1924, 0.1337, 0.7970, 0.5721, 0.3210],
[0.8471, 0.5896, 0.0455, 0.9673, 0.8247],
[0.7994, 0.0374, 0.0646, 0.2361, 0.1117],
[0.4075, 0.9078, 0.3300, 0.3394, 0.7403],
[0.0351, 0.6913, 0.5214, 0.6421, 0.6503]])
点击这里可以查看更多操作
三.torch与numpy
torch张量和numpy的数组可以互相转换,而且在cpu模式下,这两者共享内存,即改变其中一个,另一个也会发生改变。
1.torch tensor —> numpy array
>>> a = torch.ones(5)
>>> a
tensor([1., 1., 1., 1., 1.])
>>> b = a.numpy()
>>> b
array([1., 1., 1., 1., 1.], dtype=float32)
>>> # 查看是否共享内存
...
>>> a.add_(3)
tensor([4., 4., 4., 4., 4.])
>>> a
tensor([4., 4., 4., 4., 4.])
>>> b
array([4., 4., 4., 4., 4.], dtype=float32)
2.numpy array —> torch tensor
>>> import numpy as np
>>> a = np.ones(10)
>>> a
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
>>> b = torch.from_numpy(a)
>>> b
tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], dtype=torch.float64)
>>> np.add(a, 1, out = a)
array([2., 2., 2., 2., 2., 2., 2., 2., 2., 2.])
>>> a
array([2., 2., 2., 2., 2., 2., 2., 2., 2., 2.])
>>> b
tensor([2., 2., 2., 2., 2., 2., 2., 2., 2., 2.], dtype=torch.float64)
以上转换,只有chartensor(8 bit 有符号整形(-128~127))不能转换,默认的tensor是floatTensor(32bit浮点)
四.CUDA tensor
使用.to
方法可以将tensor迁移至任意一个设备
>>> if torch.cuda.is_available():
... device = torch.device("cuda") # a CUDA device object
... y = torch.ones_like(x, device=device) # directly create a tensor on GPU
... x = x.to(device) # or just use strings ``.to("cuda")``
... z = x + y
... print(z)
... print(z.to("cpu", torch.double)) # ``.to`` can also change dtype
tensor([2.2956], device='cuda:0')
tensor([2.2956], dtype=torch.float64
五. 自动微分
在PyTorch中神经网络的核心是 autograd 包。先简单介绍一下这个包,然后训练我们的第一个的神经网络。
autograd 包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义的框架(即动态图结构,与tensorflow之前的静态图结构不同),这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
让我们用一些简单的例子来看看吧。
张量
torch.Tensor
是这个包的核心类。如果设置属性.requires_grad = True
,可以追踪该张量所有的操作。当你完成你的计算后可以调用 .backward()
,来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad
属性。
要阻止一个张量被跟踪历史,可以调用 .detach()
方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。
为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad():
中。在评估模型时特别有用,因为模型可能具有 requires_grad = True
的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。
还有一个类对于autograd的实现非常重要:Function
。
Tensor
和 Function
互相连接生成了一个图无环图,它编码了完整的计算历史。每个张量都有一个 .grad_fn
属性,该属性引用了创建 Tensor
自身的Function
(除非这个张量是用户手动创建的,即这个张量的 grad_fn is None
)。
如果想计算导数,可以在 Tensor
上调用 .backward()
。如果 Tensor
是一个标量(即它包含一个元素的数据),则不需要为 backward()
指定任何参数,但是如果它有更多的元素,则需要指定一个 gradient
参数,该参数是形状匹配的张量
import torch
创建一个张量,设置requires_grad = True去追踪计算过程:
x = torch.ones(2, 2, requires_grad = True)
print(x)
out:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
做一个张量计算:
y = x + 2
print(y)
out:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y
被创造出来,拥有grad_fn
属性。
print(y.grad_fn)
out:
<AddBackward0 object at 0x7f1b248453c8>
对y做更多操作:
z = y * y * 3
out = z.mean()
print(z, out)
out:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_( ... )
在原地改变一个张量的requires_grad
. 如果这个值没有被指定的话默认是False
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
Out:
False
True
<SumBackward0 object at 0x7f9e2407a080>
梯度
backprop反向传播的学习。因为输出out
包含一个简单的标量,out.backward()
等同于 out.backward(torch.tensor(1.))
out.backward()
输出导数 d(out)/dx
print(x.grad)
Out:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
现在有一个数值为4.5的矩阵。设 out
= “o”,
o
=
1
4
s
u
m
i
=
1
n
3
(
x
+
2
)
i
2
o = \frac{1}{4}sum_{i=1}^n 3 (x + 2)_i^2
o=41sumi=1n3(x+2)i2 $\frac{\partial o}{\partial x} = \frac{3}{2} (x + 2) $
从数学上看如果一个向量值函数
y
⃗
=
f
(
x
⃗
)
\vec{y} = f(\vec{x})
y=f(x) 那么对于
x
⃗
\vec{x}
x 的梯度是一个Jacobian矩阵:
∣
∂
y
1
∂
x
1
.
.
.
∂
y
1
∂
x
n
.
.
.
.
.
.
.
.
.
∂
y
m
∂
x
1
.
.
.
∂
y
m
∂
x
n
∣
\begin{vmatrix} \frac{\partial y_1}{\partial x_1} & ... &\frac{\partial y_1}{\partial x_n} \\ ... & ... & ...\\ \frac{\partial y_m}{\partial x_1} & ... & \frac{\partial y_m}{\partial x_n} \end{vmatrix}
∣∣∣∣∣∣∂x1∂y1...∂x1∂ym.........∂xn∂y1...∂xn∂ym∣∣∣∣∣∣
通常来说,torch.autograd
是计算Jacobian矩阵的一个“引擎”。
Jacobian向量积的这一属性使得将外部梯度输入到有矢量输出的模型中变得非常方便。
现在我们来看一个Jacobian向量积的例子:
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
# y.data.norm() 是计算y的二范数,每个元素的平方和再开方
print(y)
Out:
tensor([-1095.5557, 760.4928, 1443.1665], grad_fn=<MulBackward0>)
现在y
不再是一个标量。torch.autograd
不能完整的直接计算Jacobian,但如果我们只想计算jacobian向量积结果,将这个向量传给 backward
参数即可。
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
Out:
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])
也可以通过将代码块包装在 with torch.no_grad():
中,来阻止autograd跟踪设置了 .requires_grad=True
的张量的历史记录。
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
Out:
True
True
False
或使用.detach()
获得一个新的张量而不需要有梯度属性。
print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
Out:
True
False
tensor(True)
六.神经网络