美文网首页
TensorFlow vs PyTorch 4: 自动微分

TensorFlow vs PyTorch 4: 自动微分

作者: LabVIEW_Python | 来源:发表于2021-11-02 12:02 被阅读0次

    使用反向传播法训练神经网络时,模型的参数依据损失函数与对应参数的梯度来调整,即:

    model.parameters = model.parameters + learning_rate * gradients

    自动微分是机器学习工具包必备的工具,它可以自动计算整个计算图的微分。

    PyTorch内建了一个叫做torch.autograd的自动微分引擎,该引擎支持的数据类型为:浮点数Tensor类型 ( half, float, double and bfloat16) 和复数Tensor 类型(cfloat, cdouble)

    PyTorch中与自动微分相关的常用的Tensor属性和函数:

    • 属性requires_grad:默认值为False,表明该Tensor不会被自动微分引擎计算微分。设置为True,表明让自动微分引擎计算该Tensor的微分
    • 属性grad:存储自动微分的计算结果,即调用backward()方法后的计算结果
    • 方法backward: 计算微分
      范例:
    import torch 
    # 模型:z = x@w + b;激活函数:Softmax
    x = torch.ones(5)  # 输入张量,shape=(5,)
    labels = torch.zeros(3) # 标签值,shape=(3,)
    w = torch.randn(5,3,requires_grad=True) # 模型参数,需要计算微分, shape=(5,3)
    b = torch.randn(3, requires_grad=True)  # 模型参数,需要计算微分, shape=(3,)
    z = x@w + b # 模型前向计算
    outputs = torch.nn.functional.softmax(z) # 激活函数
    print(z)
    print(outputs)
    loss = torch.nn.functional.binary_cross_entropy(outputs, labels)
    # 查看loss函数的微分计算函数
    print('Gradient function for loss =', loss.grad_fn)
    # 调用loss函数的backward()方法计算模型参数的微分
    loss.backward()
    # 查看模型参数的微分值
    print(w.grad)
    print(b.grad)
    

    tensor([ 2.6084, -0.4021, -0.7369], grad_fn=<AddBackward0>)
    tensor([0.9221, 0.0454, 0.0325], grad_fn=<SoftmaxBackward0>)
    Gradient function for loss = <BinaryCrossEntropyBackward0 object at 0x000001FED7888880>
    tensor([[ 0.2824, -0.1645, -0.1179],
    [ 0.2824, -0.1645, -0.1179],
    [ 0.2824, -0.1645, -0.1179],
    [ 0.2824, -0.1645, -0.1179],
    [ 0.2824, -0.1645, -0.1179]])
    tensor([ 0.2824, -0.1645, -0.1179])

    • 上下文管理禁用自动微分:no_grad(), 一般用于模型评估或推理计算这些不需要执行自动微分计算的地方,以减少内存和算力的消耗。另外禁止在模型参数上自动计算微分,即不允许更新该参数,即所谓的冻结参数(frozen parameters)。

      范例: torch.no_grad()使用范例
    • PyTorch的微分是自动积累的,需要用zero_grad()方法手动清零
    • backward()方法,一般不带参数,等效于:backward(torch.tensor(1.0))。若backward()方法在DAG的root上调用,它会依据链式法则自动计算DAG所有枝叶上的微分。

    TensorFlow通过tf.GradientTape API来自动追踪和计算微分,GradientTape,翻译为微分带,Tape有点儿历史上磁带机的味道,即在Tape上记录下所有的计算和计算结果。
    tf.GradientTape在tf.Variable而非tf.Tensor上计算,因为在TensorFlow中,tf.Tensor为不可变对象,tf.Variable为可变对象;通常用tf.Variable来存储模型参数
    tf.Variable有一个trainable属性,该属性tf.Tensor没有,类似PyTorch Tensor的requires_grad, 即告知自动微分引擎是否追踪该tf.Variable,并自动计算该tf.Variable的微分。
    范例:

    import tensorflow as tf 
    
    # 模型:z = x@w + b;激活函数:Softmax
    x = tf.ones([1,5])  # 输入张量,shape=(5,)
    labels = tf.zeros(3) # 标签值,shape=(3,)
    
    # 创建模型参数
    w = tf.Variable(tf.random.normal([5,3]), trainable=True) # 模型参数,需要计算微分, shape=(5,3)
    b = tf.Variable(tf.random.normal([3]), trainable=True)  # 模型参数,需要计算微分, shape=(3,)
    
    # 追踪需要自动计算微分的操作
    with tf.GradientTape() as tape:
        z = x@w + b # 模型前向计算
        outputs = tf.nn.softmax(z) # 激活函数
        loss = tf.keras.metrics.binary_crossentropy(labels, outputs, from_logits=False)
    print(z)
    print(outputs)
    
    # 调用tape.gradient()方法计算模型参数的微分
    [dl_dw, dl_db] = tape.gradient(loss, [w,b])
    
    # 查看模型参数的微分值和形状
    print(dl_dw, dl_dw.shape)
    print(dl_db, dl_db.shape)
    

    tf.Tensor([[5.82673 5.987774 3.7632523]], shape=(1, 3), dtype=float32)
    tf.Tensor([[0.4344524 0.5103672 0.05518046]], shape=(1, 3), dtype=float32)
    tf.Tensor(
    [[-0.01459036 0.02949907 -0.01490874]
    [-0.01459036 0.02949907 -0.01490874]
    [-0.01459036 0.02949907 -0.01490874]
    [-0.01459036 0.02949907 -0.01490874]
    [-0.01459036 0.02949907 -0.01490874]], shape=(5, 3), dtype=float32) (5, 3)
    tf.Tensor([-0.01459036 0.02949907 -0.01490874], shape=(3,), dtype=float32) (3,)

    从上述可以看到,TensorFlow的自动微分实现方式与PyTorch大不相同,而且没有把参数和参数的微信封装成一个对象,这点非常不User-Friendly,或者说封装的不好
    为了方便实现模型,模型的参数,与模型参数的微分,TensorFlow又提供了另外一套机制:模型的微分(Gradients with respect to a model), 意思是:TensorFlow开发团队也知道了用tf.Variable实现模型参数,然后用tape.gradient()方法计算微分,tf.Variable和它对应的微分是分离的,是没有封装好的,这种方式对开发者不友好,所以,TensorFlow开发者团队对于构建模型的基础类: tf.Module 或者它的子类 (layers.Layer, keras.Model),提供了一个 Module.trainable_variables的属性,该属性把模型参数都封装好了,使用起来比较方便。不过对应微分还是没封装,坚持自己的个性...对于我们开发者,还是选择遵循...
    范例:

    import tensorflow as tf
    
    # 模型:z = x@w + b;激活函数:Softmax
    x = tf.ones([1,5])  # 输入张量,shape=(1,5)
    labels = tf.zeros([1,3]) # 标签值,shape=(1,3)
    
    layer = tf.keras.layers.Dense(3, activation='relu')
    
    with tf.GradientTape() as tape:
        # 前向计算
        logits = layer(x)
        # 计算损失
        loss = tf.keras.metrics.binary_crossentropy(labels, logits, from_logits=True)
    # 计算梯度
    grad = tape.gradient(loss, layer.trainable_variables)
    
    for var, g in zip(layer.trainable_variables, grad):
        print(f"{var.name}, shape:{g.shape}")
    

    dense/kernel:0, shape:(5, 3)
    dense/bias:0, shape:(3,)

    参考资料:

    1. 《Introduction to gradients and automatic differentiation》
    2. 《AUTOMATIC DIFFERENTIATION WITH TORCH.AUTOGRAD》

    相关文章

      网友评论

          本文标题:TensorFlow vs PyTorch 4: 自动微分

          本文链接:https://www.haomeiwen.com/subject/axktzltx.html