PyTorch 的动态计算图机制是其核心特性之一,它使得深度学习模型的开发更加灵活和高效。
0 计算图
计算图(Computation Graph)是一种用于表示数学表达式或程序流程的图形结构,可以将复杂的表达式分解成一系列简单的操作,并以节点和边的形式展示这些操作及其之间的关系,能够清晰地展示计算过程中的依赖关系
- 节点(Nodes): 表示变量或常量,也可以表示操作(如加法、乘法等)。
 - 边(Edges) :表示数据流的方向,即一个操作的结果如何作为输入传递给下一个操作。
 
举例说明
假设我们有如下的表达式:
  
     
      
       
       
         x 
        
       
         = 
        
       
         a 
        
       
         + 
        
       
         b 
        
       
      
        x=a+b 
       
      
    x=a+b
  
     
      
       
       
         y 
        
       
         = 
        
       
         c 
        
       
         ∗ 
        
       
         d 
        
       
      
        y=c*d 
       
      
    y=c∗d
  
     
      
       
       
         z 
        
       
         = 
        
       
         x 
        
       
         + 
        
       
         y 
        
       
      
        z=x+y 
       
      
    z=x+y
 
其中 a , b , c , d a,b,c,d a,b,c,d是输入变量, z z z是输出变量。
假设令 
     
      
       
       
         a 
        
       
         = 
        
       
         1 
        
       
         , 
        
       
         b 
        
       
         = 
        
       
         2 
        
       
         , 
        
       
         c 
        
       
         = 
        
       
         3 
        
       
         , 
        
       
         d 
        
       
         = 
        
       
         4 
        
       
      
        a=1,b=2,c=3,d=4 
       
      
    a=1,b=2,c=3,d=4,则可以根据上面的计算图,计算出 
     
      
       
       
         z 
        
       
      
        z 
       
      
    z,计算过程如下:
  
     
      
       
       
         x 
        
       
         = 
        
       
         a 
        
       
         + 
        
       
         b 
        
       
         = 
        
       
         1 
        
       
         + 
        
       
         2 
        
       
         = 
        
       
         3 
        
       
      
        x=a+b=1+2=3 
       
      
    x=a+b=1+2=3
  
     
      
       
       
         y 
        
       
         = 
        
       
         c 
        
       
         ∗ 
        
       
         d 
        
       
         = 
        
       
         3 
        
       
         ∗ 
        
       
         4 
        
       
         = 
        
       
         12 
        
       
      
        y=c*d=3*4=12 
       
      
    y=c∗d=3∗4=12
  
     
      
       
       
         z 
        
       
         = 
        
       
         x 
        
       
         + 
        
       
         y 
        
       
         = 
        
       
         12 
        
       
         + 
        
       
         3 
        
       
         = 
        
       
         5 
        
       
      
        z=x+y=12+3=5 
       
      
    z=x+y=12+3=5
1 前向传播、反向传播
但是在机器学习中,计算图不仅展示了计算路径,还为计算梯度提供了基础,这里涉及到前向传播和反向传播
1.1 前向传播(计算输出)
根据当前的模型参数计算网络的输出,前向传播的输出作用是:
- 评估模型性能: 通过比较网络的预测值与实际值,可以计算损失函数的值,从而评估模型当前的性能。
 - 准备反向传播: 前向传播计算出的中间结果(例如每个隐藏层的输出)是反向传播中计算梯度所必需的。上面的计算过程就是前向传播。

 
1.2 反向传播(计算梯度)
目的是计算损失函数关于每个模型参数的梯度。这些梯度信息用于更新模型参数,使模型在下一次迭代中表现得更好,关于梯度是什么,在从0开始深度学习(2)——自动微分解释了梯度的概念和计算过程。
 
现在依然以上面的例子举例,假设我么们有一个损失函数 
     
      
       
       
         L 
        
       
      
        L 
       
      
    L,并且 
     
      
       
       
         L 
        
       
      
        L 
       
      
    L关于 
     
      
       
       
         z 
        
       
      
        z 
       
      
    z的梯度已知为 
     
      
       
        
         
         
           ∂ 
          
         
           L 
          
         
         
         
           ∂ 
          
         
           z 
          
         
        
       
         = 
        
       
         1 
        
       
      
        \frac{\partial L}{\partial z}=1 
       
      
    ∂z∂L=1(通常都这样设置,且适用于多种函数),目标是计算 
     
      
       
       
         a 
        
       
         , 
        
       
         b 
        
       
         , 
        
       
         c 
        
       
         , 
        
       
         d 
        
       
      
        a,b,c,d 
       
      
    a,b,c,d关于 
     
      
       
       
         L 
        
       
      
        L 
       
      
    L的梯度,反着计算。
 
1 静态计算图
静态图是通过先定义后运行的方式,先搭建图,然后再输入数据进行计算,典型代表是Tensorflow 1.0 版本,Tensorflow名字的来源就是因为张量Tensor在预先定义的图中流动(Flow)
 
2 动态计算图
动态图是指计算图的运算和搭建同时运行,也就是可以先计算前面的节点的值,再根据这些值搭建后面的图,如下图所示:
 
2.1核心概念
2.1.1 张量
参考从0开始深度学习(1)—— 代码实现线性代数中的概念解释
2.1.2 自动微分
pytorch框架中借助自动微分机制来实现动态计算图,意味着计算图是在运行时动态构建的,它使得计算复杂模型的梯度变得非常方便。
- 计算图在每次前向传播时重新构建。这意味着每个操作都会记录下来,并在反向传播时按需计算梯度。
 - 每个张量(Tensor)都有一个 
.grad属性来存储梯度,以及一个.grad_fn属性来记录创建该张量的操作。 
关键属性:
data:包含tensor的实际数据grad:存储tensor的梯度。在开始训练前,我们会使用zero_grad()把梯度清零,第一次反向传播后,x.grad会包含当前损失函数关于x的梯度,如果第二次反向传播前不清零,则新的梯度会累加到x.grad,即包含两次反向传播的梯度之和grad_fn:记录了创建tensor的操作,表明该tensor是通过何种前向传播计算出来的,例如x是通过加法计算出来的,则x.grad_fn指向一个加法操作的函数对象
2.2 举例说明
依然以上述例子为例,我先举一个静态图的例子,是在TensorFlow 1.x 中,需要提前定义好整个计算图,然后在会话中执行:
import tensorflow as tf
# 定义输入和参数
a = tf.placeholder(tf.float32, shape=[])
b = tf.placeholder(tf.float32, shape=[])
c = tf.placeholder(tf.float32, shape=[])
d = tf.placeholder(tf.float32, shape=[])
# 前向传播,此处无法查看,只能在Session中查看
x = a + b
y = c * d
z = x + y
# 定义损失函数
L = z
# 定义梯度
grad_a, grad_b, grad_c, grad_d = tf.gradients(L, [a, b, c, d])
# 创建会话
with tf.Session() as sess:
    # 前向传播
    print("Starting forward pass...")
    x_val, y_val, z_val = sess.run([x, y, z], feed_dict={a: 1.0, b: 2.0, c: 3.0, d: 4.0})
    print(f"x: {x_val}, grad_fn: N/A")
    print(f"y: {y_val}, grad_fn: N/A")
    print(f"z: {z_val}, grad_fn: N/A")
    # 打印前向传播的结果
    print(f"x: {x_val}")  # 3
    print(f"y: {y_val}")  # 12
    print(f"z: {z_val}")  # 15
    # 反向传播
    print("Starting backward pass...")
    grad_a_val, grad_b_val, grad_c_val, grad_d_val = sess.run([grad_a, grad_b, grad_c, grad_d], feed_dict={a: 1.0, b: 2.0, c: 3.0, d: 4.0})
    # 打印梯度
    print(f"a.grad: {grad_a_val}")  # 1
    print(f"b.grad: {grad_b_val}")  # 1
    print(f"c.grad: {grad_c_val}")  # 4
    print(f"d.grad: {grad_d_val}")  # 3
 
接下来举一个动态图的例子
 pytorch动态图例子:
import torch
# 定义输入和参数
a = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([2.0], requires_grad=True)
c = torch.tensor([3.0], requires_grad=True)
d = torch.tensor([4.0], requires_grad=True)
# 前向传播,在定义操作中,就可以查看梯度和操作,不像静态图中,必须在Session中才能查看
print("Starting forward pass...")
x = a + b
print(f"x: {x.item()}, grad_fn: {x.grad_fn}")
# 输出结果:x: 3.0, grad_fn: <AddBackward0 object at 0x000002113E68B790>
y = c * d
print(f"y: {y.item()}, grad_fn: {y.grad_fn}")
# 输出结果:y: 12.0, grad_fn: <MulBackward0 object at 0x000002113D43C070>
z = x + y
print(f"z: {z.item()}, grad_fn: {z.grad_fn}")
# z: 15.0, grad_fn: <AddBackward0 object at 0x000002113E68B790>
# 打印前向传播的结果
print(f"x: {x.item()}")  # 3
print(f"y: {y.item()}")  # 12
print(f"z: {z.item()}")  # 15
'''
输出结果:
x: 3.0
y: 12.0
z: 15.0
'''
# 假设损失函数 L = z
L = z
# 反向传播
print("Starting backward pass...")
L.backward()
# 打印梯度
print(f"a.grad: {a.grad.item()}")  # 1
print(f"b.grad: {b.grad.item()}")  # 1
print(f"c.grad: {c.grad.item()}")  # 4
print(f"d.grad: {d.grad.item()}")  # 3
'''
输出结果:.
a.grad: 1.0
b.grad: 1.0
c.grad: 4.0
d.grad: 3.0
'''
 
3 利用动态图机制,修改流程
我们这里希望根据 a a a的值来决定计算流程,详情看注释
import torch
# 定义输入和参数
a = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([2.0], requires_grad=True)
# 采用简洁的代码,可以动态的修改计算流程,控制更灵活
if a.item() > 0:
    x = a + b
    print(f"x: {x.item()}")  # 3
else:
    x = a - b
# 前向传播
y = x * 2
print(f"y: {y.item()}")  # 6
# 反向传播
y.backward()
# 打印梯度
print(f"a.grad: {a.grad.item()}")  # 2
print(f"b.grad: {b.grad.item()}")  # 2
 
如果是在静态图中需要通过控制流来决定,与动态图相比,操作更复杂,不直观
# 条件分支
x = tf.cond(a > 0, lambda: a + b, lambda: a - b)
                


















