TensorFlow vs PyTorch 4: 自动微分

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

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》
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容