线性代数¶
标量由只有一个元素的张量表示
import torch
x = torch.tensor([3.0])
y = torch.tensor([2.0])
x + y, x - y, x * y, x / y, x ** y
(tensor([5.]), tensor([1.]), tensor([6.]), tensor([1.5000]), tensor([9.]))
可以将向量视为标量值组成的列表
x = torch.arange(4)
x
tensor([0, 1, 2, 3])
通过张量的索引来访问任何元素
x[2]
tensor(2)
可以使用len()
来访问张量的长度
len(x)
4
只有一个轴的张量,形状只有一个元素
x.shape
torch.Size([4])
x
tensor([0, 1, 2, 3])
上述一轴张量形状为[1, 4]
,只有一个元素1
被省略,为了对比,下面为[4, 3]
形状的张量。
x_4 = torch.tensor([[2, 2, 2], [3, 3, 3], [4, 4, 4], [5, 5, 5]])
x_4.shape
torch.Size([4, 3])
x_4
tensor([[2, 2, 2], [3, 3, 3], [4, 4, 4], [5, 5, 5]])
通过指定两个分量m
和n
来创建一个形状为m x n
的矩阵
A = torch.arange(20).reshape(5, 4)
A
tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19]])
可以使用T
方法来转制该矩阵
A.T
tensor([[ 0, 4, 8, 12, 16], [ 1, 5, 9, 13, 17], [ 2, 6, 10, 14, 18], [ 3, 7, 11, 15, 19]])
手动创建一个对称矩阵B
,对于对称矩阵B
来说,其转制就等于其自身,既B = B.T
B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B
tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
B == B.T
tensor([[True, True, True], [True, True, True], [True, True, True]])
就像向量是标量的推广,矩阵是向量的推广一样,我们可以构建具有更多轴的数据结构。
torch.arange(24)
创建长为24的向量,reshape(2, 3, 4)
更改形状,使其称为2个,3行,4列的矩阵。
(人的直觉对于高维的矩阵是有局限性的)
X = torch.arange(24).reshape(2, 3, 4)
X
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]]])
给定具有相同形状的两个任意两个张量,任何元素的二维运算的结果都是相同形状的张量。
A = torch.arange(20, dtype=torch.float32).reshape(4, 5)
B = A.clone() # 通过分配新内存,将A的副本分配给B
A, A + B
(tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]]), tensor([[ 0., 2., 4., 6., 8.], [10., 12., 14., 16., 18.], [20., 22., 24., 26., 28.], [30., 32., 34., 36., 38.]]))
两个矩阵的按元素乘法称为哈达玛积(Hadamard product)(数学符号为⊙)
A * B
tensor([[ 0., 1., 4., 9., 16.], [ 25., 36., 49., 64., 81.], [100., 121., 144., 169., 196.], [225., 256., 289., 324., 361.]])
A
tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]])
矩阵加上一个常数,其结果为这个矩阵的所有元素加上这个常数。
a = 3
A + a
tensor([[ 3., 4., 5., 6., 7.], [ 8., 9., 10., 11., 12.], [13., 14., 15., 16., 17.], [18., 19., 20., 21., 22.]])
矩阵乘上一个常数,其结果为这个矩阵的所有元素乘上这个常数。
A * a
tensor([[ 0., 3., 6., 9., 12.], [15., 18., 21., 24., 27.], [30., 33., 36., 39., 42.], [45., 48., 51., 54., 57.]])
sum()
函数可以计算矩阵元素的和
A
tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]])
A.sum()
tensor(190.)
不同形状的矩阵求和结果
P = A.reshape(2, 2, 5)
P
tensor([[[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.]], [[10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]]])
P.sum()
tensor(190.)
指定求和汇总张量的轴
A
tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]])
对列求和
A_sum_axis0 = A.sum(axis=0)
A_sum_axis0
tensor([30., 34., 38., 42., 46.])
对行求和
A_sum_axis1 = A.sum(axis=1)
A_sum_axis1
tensor([10., 35., 60., 85.])
一个与求和相关的量是平均值,使用mean()
函数求矩阵元素的平均值
A
tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]])
A.mean()
tensor(9.5000)
同样可以按照行或者列求均值
A_mean_axis0 = A.mean(axis=0)
A_mean_axis1 = A.mean(axis=1)
A_mean_axis0, A_mean_axis1
(tensor([ 7.5000, 8.5000, 9.5000, 10.5000, 11.5000]), tensor([ 2., 7., 12., 17.]))
其实求和函数和平均值函数里的axis并非简单的按行或者按列求和。 下面来具体明确理解求和中axis(维度求和)的真正含义:
首先创建一个形状为2两个4行5列矩阵的张量O
O = torch.arange(40).reshape(2, 4, 5)
O
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, 24], [25, 26, 27, 28, 29], [30, 31, 32, 33, 34], [35, 36, 37, 38, 39]]])
可以使用shape
来查看张量O的形状
可以发现其形状确实为2, 4, 5
O.shape
torch.Size([2, 4, 5])
我们分别查看一下对axis为0, 1, 2对求和情况
O.sum(axis=0)
tensor([[20, 22, 24, 26, 28], [30, 32, 34, 36, 38], [40, 42, 44, 46, 48], [50, 52, 54, 56, 58]])
O.sum(axis=1)
tensor([[ 30, 34, 38, 42, 46], [110, 114, 118, 122, 126]])
O.sum(axis=2)
tensor([[ 10, 35, 60, 85], [110, 135, 160, 185]])
通过观察可以发现,axis值的其实就是(2, 4, 5)的其中一个。比如axis=0,就是按照(2, 4, 5)里的第一个“2”求和,也就是两个矩阵求和。
axis=0,就是按照(2, 4, 5)里的第一个“4”求和,也就是按照各个矩阵的行求和。
以此类推,axis=2,就是按照(2, 4, 5)里的第一个“5”求和,也就是按照各个矩阵的列求和。
所以我们按某一个维度求和会丢到这个维度,所以如果我们想保留这个维度经常会使用keepdims=True
。
O_sum_axis2_keepdims = O.sum(axis=2, keepdims=True)
O_sum_axis2_keepdims
tensor([[[ 10], [ 35], [ 60], [ 85]], [[110], [135], [160], [185]]])
此时当两个张量形状不相等的时候,O / O_sum_axis2_keepdims的结果会通过广播机制实现。
O / O_sum_axis2_keepdims
tensor([[[0.0000, 0.1000, 0.2000, 0.3000, 0.4000], [0.1429, 0.1714, 0.2000, 0.2286, 0.2571], [0.1667, 0.1833, 0.2000, 0.2167, 0.2333], [0.1765, 0.1882, 0.2000, 0.2118, 0.2235]], [[0.1818, 0.1909, 0.2000, 0.2091, 0.2182], [0.1852, 0.1926, 0.2000, 0.2074, 0.2148], [0.1875, 0.1937, 0.2000, 0.2062, 0.2125], [0.1892, 0.1946, 0.2000, 0.2054, 0.2108]]])
如果还对上面的内容不太理解可以看一下这里更详细的解释说明
我们先创建一个形状为2, 5, 4的张量a
a = torch.ones((2, 5, 4))
a
tensor([[[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]])
a.shape
torch.Size([2, 5, 4])
a.sum(axis = 1).shape
torch.Size([2, 4])
a.sum(axis = 2).shape
torch.Size([2, 5])
a.sum(axis = [0, 2]).shape
torch.Size([5])
从上面的例子中不难发现,对张量求sum,指定axis,我们把这个张量原本的shape看作一个数组,axis指定的实际上就是这个数组上的位置,所以最终结果的形状通俗来说就是去掉axis指定的在这个数组上的数字。
a.sum(axis = 1, keepdims = True).shape
torch.Size([2, 1, 4])
a.sum(axis = [0, 1], keepdims = True).shape
torch.Size([1, 1, 4])
如果使用keepdims = True
,通俗的说就是结果的形状是把这个张量axis所指定的变为1
。
累计总和通过cumsum()
实现
O.cumsum(axis=1)
tensor([[[ 0, 1, 2, 3, 4], [ 5, 7, 9, 11, 13], [ 15, 18, 21, 24, 27], [ 30, 34, 38, 42, 46]], [[ 20, 21, 22, 23, 24], [ 45, 47, 49, 51, 53], [ 75, 78, 81, 84, 87], [110, 114, 118, 122, 126]]])
点积torch.dot()
是相同位置按元素乘积的和(1D tensor expects)
x = torch.tensor(([0, 1, 2, 3]), dtype=torch.float32)
x
tensor([0., 1., 2., 3.])
y = torch.ones(4, dtype=torch.float32)
y
tensor([1., 1., 1., 1.])
torch.dot(x, y)
tensor(6.)
我们可以通过执行按元素乘法,然后进行求和来表示两个向量的点积
torch.sum(x * y)
tensor(6.)
在代码中使用张量表示矩阵-向量积,我们使用mv函数。 当我们为矩阵A和向量x调用torch.mv(A, x)时,会执行矩阵-向量积。 注意,A的列维数(沿轴1的长度)必须与x的维数(其长度)相同。
x = torch.tensor(([0, 1, 2, 3, 4]), dtype=torch.float32)
A, x
(tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]]), tensor([0., 1., 2., 3., 4.]))
A.shape, x.shape
(torch.Size([4, 5]), torch.Size([5]))
torch.mv(A, x)
tensor([ 30., 80., 130., 180.])
我们可以将矩阵-矩阵乘法AB
看作简单地执行m
次矩阵-向量积,并将结果拼接在一起,形成一个n x m
矩阵。 在下面的代码中,我们在A和B上执行矩阵乘法。 这里的A是一个4行5列的矩阵,B是一个5行3列的矩阵。 两者相乘后,我们得到了一个5行3列的矩阵。
B = torch.ones(5, 3)
A, B
(tensor([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]]), tensor([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]))
torch.mm(A, B)
tensor([[10., 10., 10.], [35., 35., 35.], [60., 60., 60.], [85., 85., 85.]])
线性代数中最有用的一些运算符是范数(norm)。 非正式地说,向量的范数是表示一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。
u = torch.tensor([3.0, -4.0])
u
tensor([ 3., -4.])
torch.norm(u)
tensor(5.)
范数听起来很像距离的度量。 欧几里得距离和毕达哥拉斯定理中的非负性概念和三角不等式可能会给出一些启发。 事实上,欧几里得距离是一个L2
范数
深度学习中更经常地使用L2
范数的平方,也会经常遇到L1
范数,它表示为向量元素的绝对值之和:
torch.abs(u).sum()
tensor(7.)
矩阵也有很多不一样的范数
最长用的是F范数,其实就是矩阵元素的平方和的平方根
torch.norm(torch.ones((4, 9)))
tensor(6.)
范数和目标
在深度学习中,我们经常试图解决优化问题: 最大化分配给观测数据的概率; 最小化预测和真实观测之间的距离。 用向量表示物品(如单词、产品或新闻文章),以便最小化相似项目之间的距离,最大化不同项目之间的距离。 目标,或许是深度学习算法最重要的组成部分(除了数据),通常被表达为范数。