李沐《动手学深度学习》预备知识 张量操作与数据处理
教材:李沐《动手学深度学习》
标量(scalar)
仅包含一个数值被称为标量,标量由只有一个元素的张量表示。
import torch
x = torch.tensor(3.0)
y = torch.tensor(2.0)
x + y, x * y, x / y, x**y
向量
向量可以被视为标量值组成的列表,向量由一维张量表示。一般来说,张量可以具有任意长度,取决于机器的内存限制。
x=torch.arange(4)
x
向量中的标量值被称为元素或分量。在代码中,可以通过张量的索引来访问任一元素。
x[3]
向量的长度、维度、形状
向量的长度通常称为向量的维度(dimension),与普通的Python数组一样,可以通过调用Python的内置len()函数来访问张量的长度。
len(x)
形状(shape)是一个元素组,列出了张量沿每个轴的长度(维数)。
x.shape
矩阵
矩阵通常用粗体、大写字母来表示, 在代码中表示为具有两个轴的张量。
A=torch.arange(20).reshape(5,4)
A
矩阵的转置
A.T
张量
张量是描述具有任意数量轴的维数组的通用方法。
当我们开始处理图像时,张量将变得更加重要,图像以维数组形式出现, 其中3个轴对应于高度、宽度,以及一个通道(channel)轴, 用于表示颜色通道(红色、绿色和蓝色)。
标量、向量、矩阵和任意数量轴的张量有一些实用的属性。 例如,任何按元素的一元运算都不会改变其操作数的形状。 同样,给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量。
矩阵的Hadamard积(Hadamard product)
两个矩阵的按元素乘法称为Hadamard积,数学符号为 ⨀ \bigodot ⨀。矩阵A和矩阵B的Hadamard积为:
A ⨀ B = [ a 11 b 11 a 12 b 12 ⋯ a 1 n b 1 n a 21 b 21 a 22 b 22 ⋯ a 2 n b 2 n ⋮ ⋮ ⋱ ⋮ a m 1 b m 1 a m 2 b m 2 ⋯ a m n b m n ] A\bigodot B=\begin{bmatrix} a_{11} b_{11} & a_{12}b_{12} & \cdots & a_{1n}b_{1n}\\ a_{21} b_{21} & a_{22}b_{22} & \cdots & a_{2n}b_{2n}\\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} b_{m1} & a_{m2}b_{m2} & \cdots & a_{mn}b_{mn}\\ \end{bmatrix} A⨀B= a11b11a21b21⋮am1bm1a12b12a22b22⋮am2bm2⋯⋯⋱⋯a1nb1na2nb2n⋮amnbmn
将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。
a=2
X=torch.arange(24).reshape(2,3,4)
a+X,a*X,(a*X).shape
#向量求和
x=torch.arange(4,dtype=torch.float32)
x,x.sum()
#矩阵求和
A.sum()
可以指定张量沿哪一个轴来通过求和降低维度。 在矩阵中,指定axis=0可以求和所有行的元素,指定axis=1可以求和所有列的元素。#按行求和
A_sum_axis0=A.sum(axis=0)
A_sum_axis0
#按列求和
A_sum_axis1=A.sum(axis=1)
#沿着行和列求和,等价于对所有元素求和
A.sum(axis=[0,1])
sum_A=A.sum(axis=1,keepdims=True)
sum_A,sum_A.shape
由于sum_A在对每行进行求和后仍保持两个轴,我们可以通过广播将A除以sum_A。A/sum_A
调用cumsum函数可以沿某个轴计算A元素的累计总和,此函数不会沿任何轴降低输入张量的维度。A.cumsum(axis=0)
两个向量的点积是相同位置的按元素乘积的和。
import numpy as np
y=np.ones(4)
x,y,np.dot(x,y)
我们也可以通过执行按元素乘法,然后进行求和来表示两个向量的点积。
将两个向量规范化得到单位长度后,点积表示它们夹角的余弦。
矩阵向量积
矩阵向量积Ax是一个列向量,其第i个元素是点积 a i T x a_i^Tx aiTx。在代码中使用mv函数表示矩阵-向量积。 当我们为矩阵A和向量x调用torch.mv(A, x)时,会执行矩阵-向量积。
矩阵向量积运算要求A的列维数(沿轴1的长度)必须与x的维数(其长度)相同。
A.shape,x.shape,torch.mv(A,x)
矩阵乘法
我们可以将矩阵-矩阵乘法看作简单地执行多次矩阵-向量积,并将结果拼接在一起,形成一个矩阵。在代码中使用mm函数表示矩阵乘法。
B=torch.ones(4,3)
torch.mm(A,B)
向量的范数表示一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。
在线性代数中,向量范数是将向量映射到标量的函数 f f f。 给定任意向量 x x x,向量范数满足一些属性:
u=torch.tensor([3.0,-4.0])
#计算L2范数
print(torch.norm(u))
#计算L1范数
print(torch.abs(u).sum())
#计算Frobenius范数
print(torch.norm(torch.ones((4,9))))
在深度学习中,我们经常试图解决优化问题: 最大化分配给观测数据的概率; 最小化预测和真实观测之间的距离。 用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。 目标,或许是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。
深度学习中模型拟合的任务可以分解为两个关键问题:
深度学习框架通过自动计算导数,即 自动微分(automatic differentiation)来加快求导。 自动微分使系统能够随后反向传播梯度。
实际中,根据设计好的模型,系统会构建一个计算图(computational graph),来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
深度学习框架可以自动计算导数:我们首先将梯度附加到想要对其计算偏导数的变量上,然后记录目标值的计算,执行它的反向传播函数,并访问得到的梯度。
#让 backward 可以追踪这个参数并且计算它的梯度
x.requires_grad_(True)
x.grad
y.backward()
x.grad
#返回:tensor([0., 4., 8., 12.])
#在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
y=x.sum()
y.backward()
x.grad
#返回:tensor([1., 1., 1., 1.])
当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。 对于高阶和高维的y和x,求导的结果可以是一个高阶张量。
当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。 这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
假设y是作为x的函数计算的,而z则是作为y和x的函数计算的。 想象一下,我们想计算z关于x的梯度,但由于某种原因,希望将y视为一个常数, 并且只考虑到x在y被计算后发挥的作用。
这里可以分离y来返回一个新变量u,该变量与y具有相同的值, 但丢弃计算图中如何计算y的任何信息。 换句话说,梯度不会向后流经u到x。 因此,反向传播函数计算 z = u ∗ x z=u*x z=u∗x关于x的偏导数,同时将u作为常数处理, 而不是 z = x ∗ x ∗ x z=x*x*x z=x∗x∗x关于x的偏导数。
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == u
#返回:tensor([True, True, True, True])
由于记录了y的计算结果,我们可以随后在y上调用反向传播, 得到y=xx关于的x的导数,即2x。
x.grad.zero_()
y.sum().backward()
x.grad == 2 * x
#返回:tensor([True, True, True, True])
使用自动微分的一个好处是: 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。
在下面的代码中,while循环的迭代次数和if语句的结果都取决于输入a的值。
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a=torch.randn(size=(),requires_grad=True)
d=f(a)
d.backward()
a.grad==d/a
#返回:tensor(True)
PyTorch中的反向传播是通过自动微分(autograd)系统来实现的。自动微分是一种计算梯度的技术,它允许系统在计算图中跟踪操作,并自动计算相对于输入变量的梯度。PyTorch中反向传播的主要步骤:
import torch
# 创建张量,并跟踪操作
x = torch.tensor([2.0], requires_grad=True)
y = x**2
# 执行前向传播
z = y * 3
# 计算梯度
z.backward()
# 输出梯度
print(x.grad)
x.grad.zero_()