文章目录
1 预备知识
1.1 数据操作
1.1.1 入门
深度学习框架下的数据结构:
n维数组,也称为张量(tensor)。
import torch
x = torch.arange(12)
var = x.shape
a = x.numel() # number of elements
print(var, a)
x = x.reshape(3, 4)
print(x)
x = x.reshape(-1, 3)
print(x)
#########输出:
torch.Size([12]) 12
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
tensor([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
可以使用arange创建一个行向量x。这个行向量包含从0开始的前12个整数,它们被默认创建为浮点数。张量中的每个值都称为张量的元素(element)。
我们可以通过在希望张量自动推断的维度放置-1来调用此功能。
import torch
y = torch.tensor([[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]],
[[2, 2, 2, 2], [2, 2, 2, 2], [2, 2, 2, 2]]]
)
print(y, y.shape)
########
tensor([[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]],
[[2, 2, 2, 2],
[2, 2, 2, 2],
[2, 2, 2, 2]]]) torch.Size([2, 3, 4])
1.1.2 运算
对于任意具有相同形状的张量,常见的标准算术运算符(+、-、*、/和**)都可以被升级为按元素运算。
我们也可以把多个张量连结(concatenate)在一起,把它们端对端地叠起来形成一个更大的张量。 我们只需要提供张量列表,并给出沿哪个轴连结。下面的例子分别演示了当我们**沿行(轴-0,形状的第一个元素)和按列(轴-1,形状的第二个元素)**连结两个矩阵时会发生什么情况。
X = torch.arange(12, dtype=torch.float32).reshape((3, 4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
A = torch.cat((X, Y), dim=0)
B = torch.cat((X, Y), dim=1)
print(A)
print(B)
print(X == Y) # 执行逻辑判断
print(X.sum()) #
###########
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[ 2., 1., 4., 3.],
[ 1., 2., 3., 4.],
[ 4., 3., 2., 1.]])
tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
[ 4., 5., 6., 7., 1., 2., 3., 4.],
[ 8., 9., 10., 11., 4., 3., 2., 1.]])
tensor([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])
tensor(66.)
2.1.3 广播机制
在某些情况下,即使形状不同,我们仍然可以通过调用广播机制(broadcasting mechanism)来执行按元素操作。这种机制的工作方式如下:首先,通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状。其次,对生成的数组执行按元素操作。
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
print(a, b)
print(a + b)
###########
tensor([[0],
[1],
[2]]) tensor([[0, 1]])
tensor([[0, 1],
[1, 2],
[2, 3]])
1.1.4 索引和切片
像在任何其他Python数组中一样,张量中的元素可以通过索引访问。与任何Python数组一样:第一个元素的索引是0;
import torch
X = torch.arange(12, dtype=torch.float32).reshape(3, 4)
print(X[-1])
print(X[1: 3])
X[1, 2] = 9
X[::2, :] = 12
print(X)
#######
tensor([ 8., 9., 10., 11.])
tensor([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]])
tensor([[12., 12., 12., 12.],
[ 4., 5., 9., 7.],
[12., 12., 12., 12.]])
1.1.5 节省内存
运行一些操作可能会导致为新结果分配内存。例如,如果我们用Y = X + Y,我们将取消引用Y指向的张量,而是指向新分配的内存处的张量。
import torch
X = torch.arange(12, dtype=torch.float32).reshape(3, 4)
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
before = id(Y)
print(id(Y))
Y = X + Y
print(id(Y))
print(id(Y) == before)
#######
1559335469952
1559274127680
False
如果在后续计算中没有重复使用X,可以使用X[:] = X + Y或X += Y来减少操作的内存开销。
1.1.6 转换为其他 Python 对象
import torch
X = torch.arange(12, dtype=torch.float32).reshape(3, 4)
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
A = X.numpy()
B = torch.tensor(A)
print(type(A), type(B))
a = torch.tensor([3.5])
print(a, a.item(), float(a), int(a))
##########
<class 'numpy.ndarray'> <class 'torch.Tensor'>
tensor([3.5000]) 3.5 3.5 3
1.1.7 练习
import torch
a = torch.arange(24).reshape(3, 4, 2)
print(a)
tensor([[[ 0, 1],
[ 2, 3],
[ 4, 5],
[ 6, 7]],
[[ 8, 9],
[10, 11],
[12, 13],
[14, 15]],
[[16, 17],
[18, 19],
[20, 21],
[22, 23]]])
x = torch.arange(24).reshape((3, 4, 2))
y = torch.arange(8).reshape((4, 2))
print(x, y)
c = x + y
print(c)
1.2 数据预处理
1.2.1 读取数据集
举一个例子,我们首先创建一个人工数据集,并存储在csv(逗号分隔值)文件…/data/house_tiny.csv中。
1.3 线性代数
1.3.1 标量、向量、矩阵
标量由只有一个元素的张量表示。
import torch
x = torch.tensor([3.0])
y = torch.tensor([2.0])
x + y, x * y, x / y, x**y
将向量视为标量值组成的列表。将这些标量值称为向量的元素(element)或分量(component)。
import torch
x = torch.arange(4)
print(x, len(x), x.shape)
#######
tensor([0, 1, 2, 3]) 4 torch.Size([4])
正如向量将标量从零阶推广到一阶,矩阵将向量从一阶推广到二阶。在代码中表示为具有两个轴的张量。
作为方矩阵的一种特殊类型,对称矩阵(symmetric matrix)A等于其转置:A=A⊤。
B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
print(B == B.T)
#######
tensor([[True, True, True],
[True, True, True],
[True, True, True]])
1.3.2 张量和算法的基本性质
向量是一阶张量,矩阵是二阶张量。
当我们开始处理图像时,张量将变得更加重要,图像以n维数组形式出现,其中3个轴对应于高度、宽度,以及一个通道(channel)轴,用于堆叠颜色通道(红色、绿色和蓝色)。
import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone() # 通过分配新内存,将A的一个副本分配给B
print(A, A + B, A*B)
张量×张量:
将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。
a = 2
X = torch.arange(24).reshape(2, 3, 4)
print(a * X)
1.3.3 降维
import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A, A.shape, A.sum())
A_sum_axis0 = A.sum(axis=0) # 列求和
print(A_sum_axis0, A_sum_axis0.shape)
A_sum_axis1 = A.sum(axis=1) # 行求和
print(A_sum_axis1, A_sum_axis1.shape)
print(A.sum(axis=[0, 1])) # Same as `A.sum()`
print(A.mean(), A.sum() / A.numel())
print(A.mean(axis=0), A.sum(axis=0) / A.shape[0])
默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。 我们还可以指定张量沿哪一个轴来通过求和降低维度
在调用函数sum()时指定axis=0,则是对每一列的所有元素求和。这样nm的矩阵就变为,1m的行向量。同理,axis=1时,对每一行的元素求和,这样nm的矩阵就变为,n1的列向量。
沿着行和列对矩阵求和,等价于对矩阵的所有元素进行求和。(A.sum(axis=[0, 1]) )
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A)
print(A.mean(), A.sum() / A.numel())
print(A.mean(axis=0), A.sum(axis=0) / A.shape[0])
一个与求和相关的量是平均值(mean或average)。我们通过将总和除以元素总数来计算平均值。在代码中,我们可以调用函数来计算任意形状张量的平均值。同样,计算平均值的函数也可以沿指定轴降低张量的维度。
1.3.4 非降维求和
有时候,我们求和不是为了降维,如何防止呢?
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A.sum(axis=1))
sum_A = A.sum(axis=1, keepdims=True)
print(sum_A)
print(A / sum_A)
print(A)
print(A.cumsum(axis=0))
sum_A在对每行进行求和后仍保持两个轴。
如果我们想沿某个轴计算A元素的累积总和,比如axis=0(按行计算),我们可以调用cumsum函数。此函数不会沿任何轴降低输入张量的维度。
1.3.5 点积、矩阵-向量积、矩阵-矩阵乘法
点积:相同位置的按元素乘积的和。
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
x = torch.arange(4, dtype=torch.float32)
y = torch.tensor([1., 1, 1, 1])
print(x, y, torch.dot(x, y))
#########
tensor([0., 1., 2., 3.]) tensor([1., 1., 1., 1.]) tensor(6.)
注意,我们可以通过执行按元素乘法,然后进行求和来表示两个向量的点积:(torch.sum(x * y))
点积表示加权平均(weighted average)。将两个向量归一化得到单位长度后,点积表示它们夹角的余弦。
矩阵-向量积:比如一个矩阵是mn,一个向量是n1,两者相乘就是m*1的列向量,矩阵的基本运算嘛。
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
x = torch.arange(4, dtype=torch.float32)
a = torch.mv(A, x)
print(a)
##########
tensor([ 14., 38., 62., 86., 110.])
矩阵-矩阵乘法可以简单地称为矩阵乘法,不应与“哈达玛积”混淆。(符合线性代数计算规则。)
1.3.6 范数和目标
线性代数中最有用的一些运算符是范数(norm)。非正式地说,一个向量的范数告诉我们一个向量有多大。
欧几里得距离是一个范数:具体而言,它是L2范数。假设n维向量x中的元素是x1,…,xn,其L2范数是向量元素平方和的平方根。(可以理解为张量的模长)
u = torch([3.0, -4.0])
torch.nomr(u)
######
a = 5.
在深度学习中,我们更经常地使用L2范数的平方。你还会经常遇到L1范数,它表示为向量元素的绝对值之和
与L2范数相比,L1范数受异常值的影响较小。为了计算L1范数,我们将绝对值函数和按元素求和组合起来。
u = torch([3.0, -4.0])
a = torch.abs(u).sum()
#####
a = 7.
L2范数和L1范数都是更一般的Lp范数的特例:
(P = 1, P = 2)
类似于向量的L2范数,矩阵X∈Rm×n的弗罗贝尼乌斯范数(Frobenius norm)是矩阵元素平方和的平方根:
简单的理解就是,把张量拉成向量,然后和向量一样求和取根号。(类似距离公式)
C = torch.norm(torch.ones((4, 9)))
print(torch.norm(C))
########
tensor(6.)
1.3.7 练习
import torch
## 1
A = torch.arange(12, dtype=torch.float32).reshape(3, 4)
C = A.T
print(A == C.T)
## 2
B = torch.rand(12, dtype=torch.float32).reshape(3, 4)
print(A.T + B.T == (A + B).T)
## 3.
A = torch.rand(9, dtype=torch.float32).reshape(3, 3)
C = A + A.T
print(C == C.T)
X = torch.arange(24).reshape(2, 3, 4)
print(len(X)) # 针对0轴求长度
print(X.sum(axis=0))
print(X.sum(axis=1))
print(X.sum(axis=2))
1.4 微分
1.4.1 导数和微分
import numpy as np
from IPython import display
from d2l import torch as d2l
def f(x):
return 3 * x ** 2 - 4 * x
def numerical_lim(f, x, h):
return (f(x + h) - f(x)) / h
h = 0.1
for i in range(5):
print(f'h={h:.5f}, numerical limit={numerical_lim(f, 1, h):.5f}')
h *= 0.1
1.4.2 偏导数
1.4.3 梯度
我们可以连结一个多元函数对其所有变量的偏导数,以得到该函数的梯度(gradient)向量。
import numpy as np
def numerical_lim(f, x1, x2, h):
return (f(x1+h, x2) - f(x1, x2)+(f(x1, x2+h) - f(x1, x2)))/h
def f(x1, x2):
return 3*x1*x1 + 5*np.exp(x2)
h = 0.1
for i in range(5):
print(f'h = {h:.7f}, piandaoshu = {numerical_lim(f,1,1,h)}')
h *= 0.1
1.4.4 链式法则
1.5 自动求导
1.5.1 简单的例子
import torch
x = torch.arange(4.0, requires_grad=True)
print(x.grad) # 默认值是None
y = 2 * torch.dot(x, x) # 函数 y=2x⊤x 关于 x 的梯度应为 4x
print(y)
y.backward()
print(x.grad)
print(x.grad == 4 * x)
#########
None
tensor(28., grad_fn=<MulBackward0>)
tensor([ 0., 4., 8., 12.])
tensor([True, True, True, True])
print(x.grad) # 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
print(x.grad)
y = x.sum()
y.backward()
print(x.grad)
##############
tensor([ 0., 4., 8., 12.])
tensor([0., 0., 0., 0.])
tensor([1., 1., 1., 1.])
这后面的内容,我还不太会,日后慢慢学习吧!┭┮﹏┭┮