深度学习学习笔记(1)——pytorch基础知识

 本文记录了在学习pytorch版《动手学深度学习》时所涉及到的知识和所做的练习。

 原书地址:https://tangshusen.me/Dive-into-DL-PyTorch/#/

 PyTorch是一个开源的Python机器学习库,它提供两个高级功能:1、具有强大的GPU加速的张量计算。2、包含自动求导系统的的深度神经网络。

1 tensor

 pytorch中最基础的就是tensor,tensor的意思是张量,类似于一个多维的数组。简单来说,一个标量可以看做是0维张量,一个向量可以看做是1维张量,一个矩阵则可以看做是一个二维张量。在pytorch中,张量就是用来存储数据和做数据变换的一个容器,并且支持GPU运算。

1.1 tensor的创建

 在tensor中可以自己指定数据类型,一般整型数据指定为torch.long,浮点类型指定为torch.float64。tensor创建的方法有很多种,包括:

  • torch.empty()创建未初始化的tensor
  • torch.rand()创建随机初始化的tensor
  • torch.zeros()创建全0的tensor
  • torch.randint()创建随机整数的tensor
  • 其他方法可以查阅官方文档
    import torch as t
    
    # 创建一个未初始化的5*5的tensor
    t1 = t.empty(5,5)
    # 创建一个随机初始化的5*6的tensor
    t2 = t.rand(5,6)
    # 创建一个全为0的5*4的tensor
    t3 = t.zeros(5,4)
    # 创建一个指定类型为long,并且全为0的5*4的tensor
    t4 = t.zeros(5,4,dtype=t.long)
    # 根据已有的tensor,创建一个同类型的,全为1的4*4的tensor
    t5 = t4.new_ones(4,4)
    # 根据已有的tensor创建新随机的tensor,数据类型和原来一样,但是可以重新指定类型
    t6 = t.rand_like(t5,dtype=t.float64)
    # 创建指定范围的tensor,有arange()和range两种方法,前者创建的时候是开区间,类型是整型,后者是闭区间,类型是浮点型
    t7 = t.arange(1,5)
    t8 = t.range(1,5)
    # 输出tensor的形状,两种方法都可以
    size1 = t8.size()
    size2 = t8.shape
    
    print('t1:',t1)
    print('t2:',t2)
    print('t3:',t3)
    print('t4:',t4)
    print('t5:',t5)
    print('t6:',t6)
    print('t7:',t7)
    print('t8:',t8)
    print('size1:',size1)
    print('size2:',size2)
    输出结果:
    t1: tensor([[1.0194e-38, 1.0469e-38, 1.0010e-38, 9.6429e-39, 9.4592e-39],
            [1.0286e-38, 1.0653e-38, 1.0194e-38, 8.4490e-39, 1.0102e-38],
            [9.6429e-39, 1.0194e-38, 1.0010e-38, 1.0102e-38, 8.4490e-39],
            [1.0102e-38, 9.0919e-39, 1.0102e-38, 8.9082e-39, 9.9184e-39],
            [9.0000e-39, 1.0561e-38, 1.0653e-38, 4.1327e-39, 8.9082e-39]])
    t2: tensor([[0.1622, 0.0746, 0.6507, 0.4525, 0.1242, 0.0143],
            [0.7459, 0.2877, 0.0562, 0.9807, 0.5375, 0.6766],
            [0.8532, 0.4450, 0.0539, 0.1505, 0.5969, 0.0238],
            [0.5486, 0.8683, 0.7916, 0.9673, 0.4057, 0.6486],
            [0.6380, 0.7872, 0.6088, 0.4025, 0.3769, 0.1251]])
    t3: tensor([[0., 0., 0., 0.],
            [0., 0., 0., 0.],
            [0., 0., 0., 0.],
            [0., 0., 0., 0.],
            [0., 0., 0., 0.]])
    t4: tensor([[0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0]])
    t5: tensor([[1, 1, 1, 1],
            [1, 1, 1, 1],
            [1, 1, 1, 1],
            [1, 1, 1, 1]])
    t6: tensor([[0.9433, 0.2262, 0.5703, 0.1419],
            [0.7280, 0.2498, 0.9855, 0.2230],
            [0.0695, 0.5250, 0.4081, 0.5708],
            [0.5811, 0.3831, 0.6728, 0.9198]], dtype=torch.float64)
    t7: tensor([1, 2, 3, 4])
    t8: tensor([1., 2., 3., 4., 5.])
    size1: torch.Size([5])
    size2: torch.Size([5])

    1.2 tensor的操作

     tensor的操作包括:
  • 加减法
  • 对应元素乘除法
  • 幂运算
  • 开方运算
  • 指数对数
  • 近似值
  • 矩阵乘法
  • 剪裁
import torch as t

# 创建两个随机3*3整数的tensor,前两个参数是随机的下限和上限,第三个参数是tensor的size
x = t.randint(5,10,[3,3])
y = t.randint(1,5,[3,3])
print("-----随机生成的x,y------")
print('x:',x)
print('y:',y)

# tensor加法一:直接+号连接
result1 = x + y
# tensor加法二:先创建一个空的tensor,然后使用torch.add,指定out输出给这个创建的空tensor,创建时最好要指定类型
result2 = t.empty(3,3,dtype=t.long)
t.add(x,y,out=result2)
# tensor加法三:所有操作如果加了下划线_,那么这个函数表示replace原来的值,下面的语句就表示x加y,得到的结果replace原来的x
# 如果是不加下划线_的函数,则要有一个变量来接收运算的结果,即可以写成 result=x.add(y)
x.add_(y)
# 三种方法的输出结果都是一样的
print("-----加法结果------")
print('加法1:',result1)
print('加法2:',result2)
print('加法3:',x)

输出结果:

-----随机生成的x,y------
x: tensor([[8, 8, 9],
        [9, 5, 8],
        [6, 8, 5]])
y: tensor([[4, 2, 1],
        [1, 2, 3],
        [3, 3, 4]])
-----加法结果------
加法1: tensor([[12, 10, 10],
        [10,  7, 11],
        [ 9, 11,  9]])
加法2: tensor([[12, 10, 10],
        [10,  7, 11],
        [ 9, 11,  9]])
加法3: tensor([[12, 10, 10],
        [10,  7, 11],
        [ 9, 11,  9]])

import torch as t

# 创建两个随机3*3整数的tensor,前两个参数是随机的下限和上限,第三个参数是tensor的size
x = t.randint(5,10,[3,3])
y = t.rand(3,3)
print("-----随机生成的x,y------")
print('x:',x)
print('y:',y)

# tensor对应元素的减法
result = t.empty(3,3)
t.sub(x,y,out=result)
print("-----对应元素的减法结果------")
print('减法:',result)

# tensor对应元素的乘法
result = t.empty(3,3)
t.mul(x,y,out=result)
print("-----对应元素的乘法结果------")
print('乘法:',result)

# tensor对应元素的除法
result = t.empty(3,3)
t.div(x,y,out=result)
print("-----对应元素的除法结果------")
print('除法:',result)

# tensor对应元素的平方
result = x.pow(2)
print("-----对应元素的平方结果------")
print('平方:',result)

# tensor对应元素的平方根
x = x.float() # long类型不支持开根号运算,所以先转为float类型
result = x.sqrt()
print("-----对应元素的平方根结果------")
print('平方根:',result)
# tensor对应元素的平方根的倒数
result = x.rsqrt()
print("-----对应元素的平方根的倒数结果------")
print('平方根的倒数:',result)

# tensor对应元素的对数,torch.log()其实是以e为底的,也即是ln,如果要以2为底的log,则用torch.log2(),10为底的用torch.log10()
result = t.log(x)
print("-----对应元素的对数结果------")
print('对数:',result)

# tensor对应元素的近似值
# 向下取整
result1 = y.floor()
# 向上取整
result2 = y.ceil()
# 四舍五入
result3 = y.round()
# 只取小数
result4 = y.frac()
print("-----对应元素的近似值结果------")
print('向下取整:',result1)
print('向上取整:',result2)
print('四舍五入:',result3)
print('只取小数:',result4)

输出结果:
-----随机生成的x,y------
x: tensor([[8, 8, 6],
        [8, 7, 8],
        [7, 8, 8]])
y: tensor([[0.1627, 0.3057, 0.9029],
        [0.0086, 0.3006, 0.7814],
        [0.0504, 0.7216, 0.6553]])
-----对应元素的减法结果------
减法: tensor([[7.8373, 7.6943, 5.0971],
        [7.9914, 6.6994, 7.2186],
        [6.9496, 7.2784, 7.3447]])
-----对应元素的乘法结果------
乘法: tensor([[1.3020, 2.4459, 5.4176],
        [0.0685, 2.1040, 6.2516],
        [0.3528, 5.7724, 5.2424]])
-----对应元素的除法结果------
除法: tensor([[ 49.1557,  26.1667,   6.6450],
        [934.2344,  23.2887,  10.2374],
        [138.8944,  11.0872,  12.2082]])
-----对应元素的平方结果------
平方: tensor([[64, 64, 36],
        [64, 49, 64],
        [49, 64, 64]])
-----对应元素的平方根结果------
平方根: tensor([[2.8284, 2.8284, 2.4495],
        [2.8284, 2.6458, 2.8284],
        [2.6458, 2.8284, 2.8284]])
-----对应元素的平方根的倒数结果------
平方根的倒数: tensor([[0.3536, 0.3536, 0.4082],
        [0.3536, 0.3780, 0.3536],
        [0.3780, 0.3536, 0.3536]])
-----对应元素的对数结果------
对数: tensor([[2.0794, 2.0794, 1.7918],
        [2.0794, 1.9459, 2.0794],
        [1.9459, 2.0794, 2.0794]])
-----对应元素的近似值结果------
向下取整: tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
向上取整: tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
四舍五入: tensor([[0., 0., 1.],
        [0., 0., 1.],
        [0., 1., 1.]])
只取小数: tensor([[0.1627, 0.3057, 0.9029],
        [0.0086, 0.3006, 0.7814],
        [0.0504, 0.7216, 0.6553]])

 以上的加减乘除都是最简单的tensor的运算,剩下两个运算是矩阵乘法剪裁

 首先是矩阵乘法,对于一般的矩阵,就是2维的张量,矩阵乘法和线性代数里面的矩阵乘法是一致的。

 而实际运用的时候,很多时候会遇到多维矩阵的乘法,在pytorch中多为矩阵的乘法仅仅定义在多维矩阵的最后两个维度上,并且要求前面所有维度的size都必须一致。比如说,一个(3,2,1)的四维矩阵和一个(3,1,2)的四维矩阵做乘法,实际的乘法只在最后两个维度进行,也就是(2,1)和(1,2)两个维度进行矩阵乘法,得到的结果的维度是(2,2),而再把最前面的3这个维度加上去,最后的结果就是(3,2,2)这样的形状。

import torch as t
x = t.randint(0,4,[2,2])
y = t.randint(0,4,[2,3])
print('-----随机生成x,y-----')
print('x',x)
print('y',y)

# 二维矩阵乘法
# 矩阵乘法1:torch.mm() 只能用于二维矩阵乘法
result1 = t.mm(x,y)
# 矩阵乘法2:torch.matmul() 可以用于多维矩阵乘法
result2 = t.matmul(x,y) 
# 矩阵乘法3:@是矩阵乘法的运算符 可以用于多维矩阵乘法
result3 = x @ y
print('-----矩阵乘法结果-----')
print('矩阵乘法1:',result1)
print('矩阵乘法2:',result2)
print('矩阵乘法3:',result3)
print('结果的形状:',result1.size())

#多维矩阵乘法
x = t.randint(0,4,[3,2,1])
y = t.randint(0,4,[3,1,2])
print('-----随机生成x,y-----')
print('x',x)
print('y',y)
result = t.matmul(x,y)
print('-----多维矩阵乘法结果-----')
print('矩阵乘法:',result)
print('结果的形状:',result.size())

输出结果:
-----随机生成x,y-----
x tensor([[0, 3],
        [1, 2]])
y tensor([[2, 3, 2],
        [1, 2, 0]])
-----矩阵乘法结果-----
矩阵乘法1: tensor([[3, 6, 0],
        [4, 7, 2]])
矩阵乘法2: tensor([[3, 6, 0],
        [4, 7, 2]])
矩阵乘法3: tensor([[3, 6, 0],
        [4, 7, 2]])
结果的形状: torch.Size([2, 3])
-----随机生成x,y-----
x tensor([[[3],
         [3]],

        [[2],
         [3]],

        [[3],
         [3]]])
y tensor([[[3, 0]],

        [[0, 3]],

        [[2, 3]]])
-----多维矩阵乘法结果-----
矩阵乘法: tensor([[[9, 0],
         [9, 0]],

        [[0, 6],
         [0, 9]],

        [[6, 9],
         [6, 9]]])
结果的形状: torch.Size([3, 2, 2])

 事实上pytorch和numpy一样都存在广播机制,这个机制比较复杂,简单来说就是A和B两个矩阵的shape不一致时,在进行运算的一些特殊的情况下,pytorch会自动转换成可以进行运算的shape,一般来说,可以自动转换的情况分为两种:

  1. A的维度 > B维度

 比如A是四维矩阵(2,2,2,1),而B是三维矩阵(2,1,2),这个时候A和B的后两维可以做乘法,并且第三个维度也保持一致,所以在做乘法的时候,pytorch会自动将B的第四个维度填充为A的第四个维度(2,2,1,2),这样就可以做矩阵乘法了,最终得到的形状就是(2,2,2,2)

  1. A的维度 == B维度 并且 B前面的维度存在1

 比如A是四维矩阵(2,2,2,1),而B是三维矩阵(1,2,1,2),这个时候A和B的后两维可以做乘法,并且维度都是四维,但是A的第一个维度的size是2,而B的第一个维度的size是1,虽然不满足高维矩阵乘法中前面的size要一致这个规定,但是因为size是1,所以广播机制可以让这个size为1的自动填充为和A一致,也就是(2,2,1,2),所以最后结果的形状也就是(2,2,2,2)

#第一种多维矩阵广播机制
x = t.randint(0,4,[2,2,2,1])
y = t.randint(0,4,[2,1,2])
print('-----随机生成x,y-----')
print('x',x)
print('y',y)
result = t.matmul(x,y)
print('-----第一种多维矩阵广播机制-----')
print('矩阵乘法:',result)
print('结果的形状:',result.size())

#第一种多维矩阵广播机制
x = t.randint(0,4,[2,2,2,1])
y = t.randint(0,4,[1,2,1,2])
print('-----随机生成x,y-----')
print('x',x)
print('y',y)
result = t.matmul(x,y)
print('-----第二种多维矩阵广播机制-----')
print('矩阵乘法:',result)
print('结果的形状:',result.size())

输出结果:

-----随机生成x,y-----
x tensor([[[[0],
          [2]],

         [[2],
          [0]]],

        [[[2],
          [3]],

         [[0],
          [2]]]])
y tensor([[[2, 2]],

        [[0, 1]]])
-----第一种多维矩阵广播机制-----
矩阵乘法: tensor([[[[0, 0],
          [4, 4]],

         [[0, 2],
          [0, 0]]],


        [[[4, 4],
          [6, 6]],

         [[0, 0],
          [0, 2]]]])
结果的形状: torch.Size([2, 2, 2, 2])
-----随机生成x,y-----
x tensor([[[[3],
          [2]],

         [[2],
          [0]]],


        [[[0],
          [1]],

         [[0],
          [2]]]])
y tensor([[[[3, 1]],

         [[3, 3]]]])
-----第二种多维矩阵广播机制-----
矩阵乘法: tensor([[[[9, 3],
          [6, 2]],

         [[6, 6],
          [0, 0]]],


        [[[0, 0],
          [3, 1]],

         [[0, 0],
          [6, 6]]]])
结果的形状: torch.Size([2, 2, 2, 2])

 最后一种tensor的操作是剪裁,一般用在梯度剪裁上,来防止梯度爆炸和梯度消失。剪裁的方法有:

  1. tensor.clamp(a)

所有小于a的值都会变成a,相当于剪裁掉所有小于a的值

  1. tensor.clamp(a,b)

所有小于a的值都会变成a,所有大于b的值都会变成b,相当于剪裁掉所有小于a和大于b的值

import torch as t
x = t.rand(3,3)
print('-----随机生成x,y-----')
print('x',x)

maxx = x.max()
minn = x.min()
mean = x.mean()
median = x.median()
print('最大值:',maxx)
print('最小值:',minn)
print('平均值:',mean)
print('中位值:',median)

result1 = x.clamp(0.3)
print('-----剪裁结果-----')
print('剪裁结果小于0.3的.:',result1)

result2 = x.clamp(0.3,0.7)
print('剪裁结果小于0.3和大于0.7的.:',result1)

输出结果:
-----随机生成x,y-----
x tensor([[0.4033, 0.3980, 0.9143],
        [0.3117, 0.8045, 0.6435],
        [0.5271, 0.7502, 0.2920]])
最大值: tensor(0.9143)
最小值: tensor(0.2920)
平均值: tensor(0.5605)
中位值: tensor(0.5271)
-----剪裁结果-----
剪裁结果小于0.3的.: tensor([[0.4033, 0.3980, 0.9143],
        [0.3117, 0.8045, 0.6435],
        [0.5271, 0.7502, 0.3000]])
剪裁结果小于0.3和大于0.7的.: tensor([[0.4033, 0.3980, 0.9143],
        [0.3117, 0.8045, 0.6435],
        [0.5271, 0.7502, 0.3000]])

1.3 tensor的索引切片

 和numpy一样,可以通过索引值对tensor进行切片操作,但是注意:切片出来的值和原来的值是共享内存的,也就是修改其中一个,另外一个也会跟着修改。

import torch as t
x = t.randint(0,10,[3,3])
print('-----随机生成x-----')
print('x:',x)

y = x[0, :]
y *= 2
print('y:',y)
print('x:',x)

输出结果:
-----随机生成x-----
x: tensor([[5, 8, 4],
        [7, 7, 5],
        [2, 4, 3]])
y: tensor([10, 16,  8])
x: tensor([[10, 16,  8],
        [ 7,  7,  5],
        [ 2,  4,  3]])

1.4 tensor的形状

 关于tensor的形状,在构建神经网络的时候经常会去关心输入输出的数据形状是什么样的。而view这个函数可以对tensor的形状进行变换,但是必须要合乎规范的变换,比如原来是35的形状,可以用view(5,3)来变成53的形状。但是要变成2*6的形状,这就是不可以的了,因为总共还是15个元素。

但是注意:view以后的值和原来的值依然是共享data的(共享data不是共享内存,view后的依然是一个新的tensor),所以view函数其实只是变换了这个tensor查看的角度。如果想要得到一个副本,而不是共享data,一般可以先clone下来,再进行view的操作。

import torch as t
x = t.randint(0,10,[3,5])
print('-----随机生成x-----')
print('x:',x)

y = x.view(5,3)
print('y:',y)

输出结果:
-----随机生成x-----
x: tensor([[9, 9, 5, 2, 0],
        [1, 2, 2, 1, 3],
        [1, 5, 9, 4, 6]])
y: tensor([[9, 9, 5],
        [2, 0, 1],
        [2, 2, 1],
        [3, 1, 5],
        [9, 4, 6]])

1.5 tensor使用gpu

 tensor的很大一个特点就是支持使用gpu进行计算,如果不指名使用gpu,所有的tensor都是在cpu上创建的。

import torch

# 查看cuda是否可以使用
CUDA = torch.cuda.is_available()
print(CUDA)

# 如果cuda可以使用,则定义device为'cuda',否则为'cpu'
device = 'cuda' if CUDA else 'cpu'
print(device)

# 随机初始化一个tensor on cpu
x = torch.rand(3,3)
print('cpu:',x)

# 如果cuda可以使用,把这个tensor转移到gpu上
if CUDA:
    x = x.cuda()
    print('gpu:',x)

输出结果:
True
cuda
cpu: tensor([[0.7649, 0.7135, 0.1929],
        [0.3266, 0.4663, 0.5937],
        [0.0491, 0.1793, 0.7989]])
gpu: tensor([[0.7649, 0.7135, 0.1929],
        [0.3266, 0.4663, 0.5937],
        [0.0491, 0.1793, 0.7989]], device='cuda:0')

2 梯度

 在深度学习的问题中,我们需要对整个网络的各个参数进行不断的调整,最终让网络达到最优的效果。这个调整的过程实际上是不容易的,我们所采取的办法就是计算损失函数,根据损失函数的值来调整各个参数值。如果损失函数达到了一个最小值,则判断当前的参数为最优的参数。

 一般来说,在数学中,要求一个函数的最小值,通常使用的是$\frac{\mathrm{d}x}{\mathrm{d}y}=0$,所以只需要求解这微分方程,就可以把最优的情况解出来了。但是实际上这个方程式很难解的,而且计算机并不擅长解方程,所以这里就使用了求近似解的方式来求解方程,只要不断的代入值进行尝试,就可以找到这个微分方程的近似解。

 为了提高求解的速度,更普遍采用的就是梯度下降法,让损失函数沿着函数值上升速度最快的方向(即梯度)下降,就能很快收敛到损失函数的极值。

 在pytorch里面,梯度是不需要我们去自己写函数来计算的,只需要设置tensor.requires_grad = True,这一个tensor的所有操作都会被记录下来,我们要求梯度的时候,直接调用tensor.backward()就会自动进行链式求导,返回梯度值。

 下面是在pytorch中关于梯度和计算图的一些操作。

import torch

# 创建的时候可以设置requires_grad为True,默认不设置就是False
x = torch.ones(3,3)
print(x)
print(x.requires_grad)

# 设置x的requires_grad为True,加了下划线就是replace的方式
x.requires_grad_(True)
print(x.requires_grad)

# grad_fn记录了该tensor是使用什么样的function进行创建的,如果tensor不是通过运算得到的,返回值就是None
print(x.grad_fn)

y = x + 5
# y是通过x+5得到的,所以y.grad_fn记录的就是这个加法function
print(y.grad_fn)

z = (3 * y * y).mean()
# z最后是通过求mean得到的,所以y.grad_fn记录的就是这个均值function
print(z.grad_fn)

# backward是反向自动求导
z.backward()
# x.grad是关于x的导数,必须要先backward求导以后才有这个值,否则就是None
print(x.grad)

输出结果:
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
False
True
None
<AddBackward0 object at 0x0000023C59A47948>
<MeanBackward0 object at 0x0000023C59A47948>
tensor([[4., 4., 4.],
        [4., 4., 4.],
        [4., 4., 4.]])

 上面的操作通过一系列的操作创建了x,y,z三个tensor,将y和z都写成关于x的函数关系式,分别是:

  • $y=x+5$
  • $z=\frac{3}{9}\sum_{i=1}^{9}y_i^2=\frac{1}{3}\sum_{i=1}^{9}(x_i+5)^2$

 所以接下来可以求出z关于x的梯度了,即z对x求导$\frac{\partial z}{\partial x_i}=\frac{2}{3}(x_i+5)\mid_{x_i = 1}=4$

 因为上面的z其实是一个标量,标量关于张量求导,相当于张量里的每一个元素都要求导,所以最后的结果也是一个张量。注意:如果是张量A对张量B求导,那么backward()里面还要加一个和张量A同型的张量,这样能正常使用。

 并且反向传播的梯度是累加的,也就是说下一次的backward,会让x的梯度在之前的基础上继续累加。比如让$a=\sum_{i=1}^{9}x_i$,a对$x_i$求偏导的话就是1,但是因为之前x的梯度已经是4了,所以实际上现在x的梯度应该是$4+1=5$。所以一般反向传播之前,都要把当前的梯度清零。

a = x.sum()
# backward是反向自动求导
a.backward()
# x.grad是关于x的导数,必须要先backward求导以后才有这个值,否则就是None
print('梯度累加的情况:')
print(x.grad)

b = x.sum()
# backward是反向自动求导
x.grad.data.zero_()
b.backward()
# x.grad是关于x的导数,必须要先backward求导以后才有这个值,否则就是None
print('梯度清零后的情况:')
print(x.grad)

输出结果:
梯度累加的情况:
tensor([[5., 5., 5.],
        [5., 5., 5.],
        [5., 5., 5.]])
梯度清零后的情况:
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])

 如果仅仅是在某些时候不想追踪梯度,可以考虑将不追踪的语句都写在 with torch.no_grad(): 里面,在这个块里面的所有操作都不会保存到计算图中,这种做法在评估验证时会经常使用到。
import torch
x = torch.ones(1,1,requires_grad=True)
y1 = x * x
# 不会追踪梯度
with torch.no_grad():
    y2 = x * 3
y3 = y1 + y2
print('x :',x.requires_grad)
print('y1:',y1.requires_grad)
print('y2:',y2.requires_grad)
print('y3:',y3.requires_grad)

# 求导计算梯度
y3.backward()
print(x.grad)

输出结果:
x : True
y1: True
y2: False
y3: True
tensor([[2.]])

 针对上面的例子,把函数关系式先写出来:

  • $y_1=x^2$
  • $y_2=3x$
  • $y_3=y1+y2=x^2+3x$

 如果正常求导,$\frac{ {d}y_3}{ {d}x}=2x+3\mid_{x=1}=5$,但是上面运行的结果是2。这就是因为y2实际上并没有追踪他的梯度,所以在反向求导的时候,并不会把y2计算进来,实际上的求导过程就是$\frac{ {d}y_3}{ {d}x}=2x\mid_{x=1}=2$

 如果我们仅仅是想修改x的数值,不想让计算图记录下来,还可以直接对x.data进行修改,这个是独立于计算图之外的计算。