基础
张量
张量(Tensor)是 PyTorch 操作的基本单位,创建方式多种多样:
# 直接从 list 创建
data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)
# 从 numpy 数组创建
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
# 从另一个张量创建
x_ones = torch.ones_like(x_data) # retains the properties of x_data
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")
张量的参数
张量有3个基本参数,shape,dtype 和 device。
shape是张量的形状dtype是张量存储的数据类型device是张量存储的设备
张量操作与计算图
张量有多种多样的操作,许多操作可以在线性代数中找到原型,张量也可以看作是一种向量。操作函数手册
PyTorch 的张量操作分为原地操作(inplace)和非原地(out-of-place)操作,其中非原地操作会为结果分配一块新内存,返回一个新的张量。
x_data = [[1, 2, 3]]
x = torch.tensor(x_data)
# torch.Size([1, 3])
print(x.shape)
# squeeze 会移除张量的一个维度,返回一个新的向量
# 除了下面这种写法,也可以写成 x = torch.squeeze(x, 1)
x = x.squeeze(0)
# torch.Size([3])
print(x.shape)
而原地操作(比如 torch.add_())会直接修改张量的内存,这会破坏计算图(computational graph),PyTorch 无法追踪这样的修改。
首先,通过 torch.tensor 创建的张量,默认不会要求梯度追踪,也就不会被纳入计算图构建,因此需要指定 require_grad=True。
举例来说,以下代码计算了一个梯度:
import torch
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) # 注意需要 float 类型且设置梯度追踪
y = x ** 2
y0 = torch.tensor([1.0, 1.0, 1.0]) # 同样是 float
loss = 0.5 * (y - y0) ** 2
loss = loss.sum() # 把 loss 向量各个分量求和成一个标量,注意它依然是 Tensor,形状是 torch.Size([])
# 使用 backward 自动进行反向传播梯度计算
loss.backward()
# 此时 x.grad 存储的是 ∂(loss)/∂x
print(x.grad) # 输出梯度
能计算的原因是,PyTorch 从 x 开始追踪了一整条计算的链路,形成了计算图;最开始 x 创建时要求梯度追踪,随后 y 通过 x ** 2 创建,loss 通过 0.5 * (y - y0) ** 2 创建,都被纳入计算图中。当 loss 调用 backward() 方法时,PyTorch 会根据存储的计算图找到各个张量之间的关系,也是利用链式法则计算出 loss 对与之相关的所有张量的梯度,即
\[ \frac{\partial{loss}}{\partial{x}}=\frac{\partial{loss}}{\partial{y}}\frac{\partial{y}}{\partial{x}}=2(y-y_0)x \]
而如果计算图被破坏,则无法正常求解梯度:
import torch
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) # 注意需要 float 类型且设置梯度追踪
# 这里 item() 方法提取出了张量索引0处的标量数值,y 是通过这个数值创建的,y 和 x 已经没有关系了
y = torch.tensor([x[0].cpu().item(), x[0].cpu().item(), x[0].cpu().item()])
y0 = torch.tensor([1.0, 1.0, 1.0]) # 同样是 float
loss = 0.5 * (y - y0) ** 2
loss = loss.sum()
loss.backward()
# 此时 x.grad 存储的是 ∂(loss)/∂x
print(x.grad) # 输出梯度(None!)
因此,原地操作、从张量中取数值等操作要慎重,一般我是在最后全部一轮的训练流程计算完毕后通过 item() 方法拿到想要的张量中的数值存起来用于后期画图和信息输出等,此时这些张量已经不会继续参与运算了,下一个训练步,PyTorch 又会从零开始构建计算图。
注意 backward() 方法在计算出相关张量的 grad 后默认会清空计算图。
利用 x 中的某一个元素来构造 y 同时防止计算图断裂的办法如下
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x[0].repeat(3) # 或者 torch.stack([x[0], x[0], x[0]])