动手学深度学习第二章笔记
1. 深度学习的运算基本单位
深度学习的本质就是通过一堆网络和参数形成一个映射 f ,来拟合各种任务的输入和输出之间的关系。输入转换为输出的关键就在于大量的计算,其中的计算基本单位是张量类。
根据深度学习框架的不同,有不同的张量类。如:MXNet 中的 ndarray,Pytorch 和 TensorFlow 中的 Tensor。张量和 Numpy 中的 ndarray 很相似,可以看做一个数组(根据维度可以分别看作向量、高维矩阵)。但是张量类支持 GPU 加速计算,Numpy 只支持 CPU 计算,并且张量类还支持自动微分,非常适合深度学习。
张量可以具有任意维度,最简单的张量是一维的,可以看成一个向量。
1 | import torch |
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
高维的张量可以看作矩阵,如通过 reshape 函数将 上面的 x 转换为 (3,4) 的矩阵。
1 | X = x.reshape(3,4) |
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
2. 张量的基本操作
2.1 张量形状
在深度模型的搭建过程中,弄清当前输入、输出张量的形状很重要。访问张量的形状可以很多方法实现:
1 | #通过.shape属性或size()函数访问张量形状 |
torch.Size([3, 4])
torch.Size([3, 4])
12
2.2 转换形状
知道了张量的形状,我们可以通过 reshape 函数将张量转换为我们喜欢的形状。
1 | #将(3,4)的X转换为(4,3)的形状 |
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]])
2.3 张量创建
张量的创建可以通过函数生成,也可以通过现有的 ndarray 或列表数据转换而成。
1 | #通过zeros函数创建全零张量 |
1 | tensor([[[0., 0., 0., 0.], |
1 | #通过tensor函数将列表或ndarray转换 |
tensor([[2, 1, 4, 3],
[1, 2, 3, 4],
[4, 3, 2, 1]])
2.4 张量计算
张量的运算是按元素方式进行的,即单元运算会对所有元素进行运算,多元运算会对同一位置的元素进行运算。
1 | x = torch.tensor([1.0, 2, 4, 8]) |
1 | (tensor([ 3., 4., 6., 10.]), |
张量运算还有一个重要的性质是广播机制。当两个形状不一致的张量进行运算时,会将两个张量都扩展到一样的形状然后进行运算。
1 | a = torch.arange(3).reshape((3, 1)) |
tensor([[0, 1],
[1, 2],
[2, 3]])
2.5 张量索引与切片
张量和数组的索引一样,第一个元素为0,最后一个元素为-1。
1 | x = torch.arange(12).reshape(3,4) |
tensor([ 8, 9, 10, 11])
tensor([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
张量通过 X[ 行索引 ,列索引 ] 的方式进行索引,行列之间用逗号隔开,而行索引和列索引可以使用 : 来指定具体的行列范围。其中行列索引也可以使用 index 列表进行索引。
1 | x = torch.arange(12).reshape(3,4) |
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])tensor([[1, 2],
[5, 6]])
tensor([1, 6])
tensor([[1, 2],
[5, 6]])
2.6 向量-矩阵运算
该节主要包括三种运算:向量间的点积、向量矩阵之间的积、矩阵之间的积。
向量点积在线性表示中有重要的意义,可以简化公式。向量点积产生的结果为一个标量(是一个数值)。
1 | x = torch.arange(4, dtype = torch.float32) |
tensor([0., 1., 2., 3.])
tensor([1., 1., 1., 1.])
tensor(6.)
矩阵与向量之间的积是通过 mv 函数进行的,可以用于将矩阵变为向量。
1 | x = torch.arange(4, dtype = torch.float32) |
tensor([0., 1., 2., 3.])
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.],
[16., 17., 18., 19.]])
tensor([ 14., 38., 62., 86., 110.])
矩阵之间的积分为矩阵-矩阵乘法和 Hadamard 积。
1 | #Hadamard积是两个矩阵的对应元素相乘,两个矩阵形状相同 |
1 | tensor([[ 0., 1., 2., 3.], |
3. 梯度
这一节学起来比较迷糊的是不知道各种函数的梯度结果是什么结构。
对于
,来说,如果 , 都是标量,则导数是标量。 如果
是标量, 是向量,则梯度(导数)是向量,梯度向量与 同形状。梯度的分量为对 的各分量 求的偏导。 - 如果
是向量, 是向量,则梯度是矩阵,矩阵元素为 的分量 对 的分量求偏导。 - 同理可以推出,若
是向量, 是矩阵,则梯度应该为3维张量。
掌握了梯度结果的结构,还需要知道一些求梯度的运算法则,一些常用的求梯度法则如下:

4. 自动微分
理解第3部分的梯度结构之后,学习自动微分处的代码就可以更好地理解了。
第一个例子是对向量点积求梯度,从上述的求梯度法则可以看出来,梯度结果应该是 2*X 。因此下述代码通过自动微分求出来的梯度正好是两倍的 X。
1 | import torch |
tensor([0., 1., 2., 3.])
tensor(28., grad_fn=<MulBackward0>)
1 | y.backward() |
tensor([ 0., 4., 8., 12.])
tensor([True, True, True, True])
第二个例子是对和函数求梯度。这个梯度结构仍然是向量,但是没有求导法则,因此需要我们自己推导。
推导结果如下:

因此最终梯度应该为全1的向量,我感觉这里作者可能只是想让我们知道对于和函数求梯度,结果是全1的向量。根据链式法则,我们将求和函数作为外部函数时,最终的梯度需要乘以和函数的梯度。但由于和函数的梯度全为1,因此最终梯度大小不会因为和函数的加入而改变大小。
1 | # 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值 |
tensor([1., 1., 1., 1.])
虽然和函数不会改变最终梯度的大小,但他可以改变梯度结构,由矩阵转换为向量。
在这个例子中,如果不使用和函数,最终的梯度是一个矩阵,使用了和函数,梯度则变为了一个向量。
如果不求和,梯度矩阵推导出来的结果如下:

同时,在深度学习中,我们一般是批次化训练模型,将一批数据送进模型,然后输出一批损失函数的值(大小为批次大小的损失向量,包含各个样本的损失值)。
参数优化需要通过求损失函数的梯度进行,因此此时有两种办法,一种是将所有损失求和,另一种是将损失求平均,获取能表征整个批次样本的损失值。前者会导致损失很大,参数更新会比较慢,因此一般会将学习率放大 batch_size 倍以作平衡。
因此我猜测,这里举例进行求和,是想告诉我们,求和不但可以获取整个批次的损失表征,还可以将梯度结构转化为向量。
1 | # 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。 |
tensor([0., 2., 4., 6.])



