计算两个模型的梯度

2024-04-04

假设我们正在构建一个基本的 CNN 来识别猫和狗的图片(二元分类器)。

此类 CNN 的示例如下:

model = Sequential([
  Conv2D(32, (3,3), input_shape=...),
  Activation('relu'),
  MaxPooling2D(pool_size=(2,2),

  Conv2D(32, (3,3), input_shape=...),
  Activation('relu'),
  MaxPooling2D(pool_size=(2,2)

  Conv2D(64, (3,3), input_shape=...),
  Activation('relu'),
  MaxPooling2D(pool_size=(2,2),

  Flatten(),
  Dense(64),
  Activation('relu'),
  Dropout(0.5),
  Dense(1),
  Activation('sigmoid')
])

我们还假设我们希望将模型分成两部分,或两个模型,称为model_0 and model_1.

model_0将处理输入,并且model_1将采取model_0输出并将其作为输入。

例如,之前的模型将变为:

model_0 = Sequential([
  Conv2D(32, (3,3), input_shape=...),
  Activation('relu'),
  MaxPooling2D(pool_size=(2,2),

  Conv2D(32, (3,3), input_shape=...),
  Activation('relu'),
  MaxPooling2D(pool_size=(2,2)

  Conv2D(64, (3,3), input_shape=...),
  Activation('relu'),
  MaxPooling2D(pool_size=(2,2)
])

model_1 = Sequential([
  Flatten(),
  Dense(64),
  Activation('relu'),
  Dropout(0.5),
  Dense(1),
  Activation('sigmoid')
])

如何将这两个模型当作一个模型来训练?我尝试手动设置渐变,但我不明白如何传递渐变model_1 to model_0:

for epoch in range(epochs):
    for step, (x_batch, y_batch) in enumerate(train_generator):

        # model 0
        with tf.GradientTape() as tape_0:
            y_pred_0 = model_0(x_batch, training=True)

        # model 1
        with tf.GradientTape() as tape_1:
            y_pred_1 = model_1(y_pred_0, training=True)

            loss_value = loss_fn(y_batch_tensor, y_pred_1)

        grads_1 = tape_1.gradient(y_pred_1, model_1.trainable_weights)
        grads_0 = tape_0.gradient(y_pred_0, model_0.trainable_weights)
        optimizer.apply_gradients(zip(grads_1, model_1.trainable_weights))
        optimizer.apply_gradients(zip(grads_0, model_0.trainable_weights))

这种方法当然行不通,因为我基本上只是分别训练两个模型并将它们绑定起来,这不是我想要实现的。

这是一个针对此问题的简单版本的 Google Colab 笔记本,仅使用两个全连接层和两个激活函数:https://colab.research.google.com/drive/14Px1rJtiupnB6NwtvbgeVYw56N1xM6JU#scrollTo=PeqtJJWS3wyG https://colab.research.google.com/drive/14Px1rJtiupnB6NwtvbgeVYw56N1xM6JU#scrollTo=PeqtJJWS3wyG

请注意,我知道Sequential([model_0, model_1]),但这不是我想要达到的目标。我想手动执行反向传播步骤。

另外,我想继续使用两个单独的磁带。这里的技巧是使用grads_1计算grads_0.

有什么线索吗?


在寻求帮助并更好地了解动态之后自动微分 (or autodiff),我设法得到了一个可行的、简单的例子来说明我想要实现的目标。尽管这种方法不能完全解决问题,但它使我们在理解如何解决当前问题方面向前迈进了一步。

参考型号

我已将模型简化为更小的模型:

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Activation, Dense, Layer, Flatten, Conv2D
import numpy as np

tf.random.set_seed(0)
# 3 batches, 10x10 images, 1 channel
x = tf.random.uniform((3, 10, 10, 1))
y = tf.cast(tf.random.uniform((3, 1)) > 0.5, tf.float32)

layer_0 = Sequential([Conv2D(filters=6, kernel_size=2, activation="relu")])
layer_1 = Sequential([Conv2D(filters=6, kernel_size=2, activation="relu")])
layer_2 = Sequential([Flatten(), Dense(1), Activation("sigmoid")])

loss_fn = tf.keras.losses.MeanSquaredError()

我们将其分为三个部分,layer_0, layer_1, layer_2。普通方法只是将所有内容放在一起并一一计算梯度(或一步计算):

with tf.GradientTape(persistent=True) as tape:
    out_layer_0 = layer_0(x)
    out_layer_1 = layer_1(out_layer_0)
    out_layer_2 = layer_2(out_layer_1)
    loss = loss_fn(y, out_layer_2)

只需简单调用即可计算不同的梯度tape.gradient:

ref_conv_dLoss_dWeights2 = tape.gradient(loss, layer_2.trainable_weights)
ref_conv_dLoss_dWeights1 = tape.gradient(loss, layer_1.trainable_weights)
ref_conv_dLoss_dWeights0 = tape.gradient(loss, layer_0.trainable_weights)

ref_conv_dLoss_dY = tape.gradient(loss, out_layer_2)
ref_conv_dLoss_dOut1 = tape.gradient(loss, out_layer_1)
ref_conv_dOut2_dOut1 = tape.gradient(out_layer_2, out_layer_1)
ref_conv_dLoss_dOut0 = tape.gradient(loss, out_layer_0)
ref_conv_dOut1_dOut0 = tape.gradient(out_layer_1, out_layer_0)
ref_conv_dOut0_dWeights0 = tape.gradient(out_layer_0, layer_0.trainable_weights)
ref_conv_dOut1_dWeights1 = tape.gradient(out_layer_1, layer_1.trainable_weights)
ref_conv_dOut2_dWeights2 = tape.gradient(out_layer_2, layer_2.trainable_weights)

稍后我们将使用这些值来比较我们方法的正确性。

带手动自动微分功能的分体式型号

对于分裂,我们的意思是每个layer_x需要有自己的GradientTape,负责生成自己的梯度:

with tf.GradientTape(persistent=True) as tape_0:
    out_layer_0 = model.layers[0](x)

with tf.GradientTape(persistent=True) as tape_1:
    tape_1.watch(out_layer_0)
    out_layer_1 = model.layers[1](out_layer_0)

with tf.GradientTape(persistent=True) as tape_2:
    tape_2.watch(out_layer_1)
    out_flatten = model.layers[2](out_layer_1)
    out_layer_2 = model.layers[3](out_flatten)
    loss = loss_fn(y, out_layer_2)

现在,简单地使用tape_n.gradient因为每一步都行不通。我们基本上丢失了很多事后无法恢复的信息。

Instead, we have to use tape.jacobian https://www.tensorflow.org/api_docs/python/tf/GradientTape#jacobian and tape.batch_jacobian https://www.tensorflow.org/api_docs/python/tf/GradientTape#batch_jacobian, except for , as we only have one value as a source.

dOut0_dWeights0 = tape_0.jacobian(out_layer_0, model.layers[0].trainable_weights)

dOut1_dOut0 = tape_1.batch_jacobian(out_layer_1, out_layer_0)
dOut1_dWeights1 = tape_1.jacobian(out_layer_1, model.layers[1].trainable_weights)

dOut2_dOut1 = tape_2.batch_jacobian(out_layer_2, out_layer_1)
dOut2_dWeights2 = tape_2.jacobian(out_layer_2, model.layers[3].trainable_weights)

dLoss_dOut2 = tape_2.gradient(loss, out_layer_2) # or dL/dY

我们将使用几个实用函数来将结果调整为我们想要的:


def add_missing_axes(source_tensor, target_tensor):
    len_missing_axes = len(target_tensor.shape) - len(source_tensor.shape)
    # note: the number of tf.newaxis is determined by the number of axis missing to reach
    # the same dimension of the target tensor
    assert len_missing_axes >= 0

    # convenience renaming
    source_tensor_extended = source_tensor
    # add every missing axis
    for _ in range(len_missing_axes):
        source_tensor_extended = source_tensor_extended[..., tf.newaxis]

    return source_tensor_extended

def upstream_gradient_loss_weights(dOutUpstream_dWeightsLocal, dLoss_dOutUpstream):
    dLoss_dOutUpstream_extended = add_missing_axes(dLoss_dOutUpstream, dOutUpstream_dWeightsLocal)
    # reduce over the first axes
    len_reduce = range(len(dLoss_dOutUpstream.shape))
    return tf.reduce_sum(dOutUpstream_dWeightsLocal * dLoss_dOutUpstream_extended, axis=len_reduce)

def upstream_gradient_loss_out(dOutUpstream_dOutLocal, dLoss_dOutUpstream):
    dLoss_dOutUpstream_extended = add_missing_axes(dLoss_dOutUpstream, dOutUpstream_dOutLocal)
    len_reduce = range(len(dLoss_dOutUpstream.shape))[1:]
    return tf.reduce_sum(dOutUpstream_dOutLocal * dLoss_dOutUpstream_extended, axis=len_reduce)

最后,我们可以应用链式法则:


dOut2_dOut1 = tape_2.batch_jacobian(out_layer_2, out_layer_1)
dOut2_dWeights2 = tape_2.jacobian(out_layer_2, model.layers[3].trainable_weights)

dLoss_dOut2 = tape_2.gradient(loss, out_layer_2) # or dL/dY
dLoss_dWeights2 = upstream_gradient_loss_weights(dOut2_dWeights2[0], dLoss_dOut2)
dLoss_dBias2 = upstream_gradient_loss_weights(dOut2_dWeights2[1], dLoss_dOut2)

dLoss_dOut1 = upstream_gradient_loss_out(dOut2_dOut1, dLoss_dOut2)
dLoss_dWeights1 = upstream_gradient_loss_weights(dOut1_dWeights1[0], dLoss_dOut1)
dLoss_dBias1 = upstream_gradient_loss_weights(dOut1_dWeights1[1], dLoss_dOut1)

dLoss_dOut0 = upstream_gradient_loss_out(dOut1_dOut0, dLoss_dOut1)
dLoss_dWeights0 = upstream_gradient_loss_weights(dOut0_dWeights0[0], dLoss_dOut0)
dLoss_dBias0 = upstream_gradient_loss_weights(dOut0_dWeights0[1], dLoss_dOut0)

print("dLoss_dWeights2 valid:", tf.experimental.numpy.allclose(ref_conv_dLoss_dWeights2[0], dLoss_dWeights2).numpy())
print("dLoss_dBias2 valid:", tf.experimental.numpy.allclose(ref_conv_dLoss_dWeights2[1], dLoss_dBias2).numpy())
print("dLoss_dWeights1 valid:", tf.experimental.numpy.allclose(ref_conv_dLoss_dWeights1[0], dLoss_dWeights1).numpy())
print("dLoss_dBias1 valid:", tf.experimental.numpy.allclose(ref_conv_dLoss_dWeights1[1], dLoss_dBias1).numpy())
print("dLoss_dWeights0 valid:", tf.experimental.numpy.allclose(ref_conv_dLoss_dWeights0[0], dLoss_dWeights0).numpy())
print("dLoss_dBias0 valid:", tf.experimental.numpy.allclose(ref_conv_dLoss_dWeights0[1], dLoss_dBias0).numpy())

输出将是:

dLoss_dWeights2 valid: True
dLoss_dBias2 valid: True
dLoss_dWeights1 valid: True
dLoss_dBias1 valid: True
dLoss_dWeights0 valid: True
dLoss_dBias0 valid: True

因为所有值都彼此接近。请注意,使用雅可比行列式的方法,我们将有一定程度的误差/近似值,大约10^-7,但我认为这已经足够好了。

Gotchas

对于极限模型或玩具模型来说,这是完美的并且效果很好。然而,在实际场景中,您将拥有大量尺寸的大图像。在处理雅可比行列式时,这并不理想,因为雅可比行列式可以很快达到很高的维度。但这本身就是一个问题。

您可以在以下资源中阅读有关该主题的更多信息:

  • (EN) https://mblondel.org/teaching/autodiff-2020.pdf https://mblondel.org/teaching/autodiff-2020.pdf
  • (EN) https://www.sscardapane.it/assets/files/nnds2021/Lecture_3_filled_connected.pdf https://www.sscardapane.it/assets/files/nnds2021/Lecture_3_fully_connected.pdf
  • (ITA) https://iaml.it/blog/differenziazione-automatica-parte-1 https://iaml.it/blog/differenziazione-automatica-parte-1
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

计算两个模型的梯度 的相关文章

  • Cython 函数中的字符串

    我想这样做将字符串传递给 Cython 代码 test py s Bonjour myfunc s test pyx def myfunc char mystr cdef int i for i in range len mystr err
  • 顶级棉花糖模式验证

    From 棉花糖 validation http marshmallow readthedocs org en latest quickstart html validation 我知道我可以在架构中的特定字段上注册验证器 如果验证器失败
  • sphinx 中的分组方法文档字符串

    是否可以使用 sphinx 的 autodoc 功能将多个方法文档字符串分组 以便将它们列在一起 class Test object def a self A method of group foo def b self A method
  • 是否可以在 Sphinx 中隐藏 Python 函数参数?

    假设我有以下函数 该函数记录在Numpydoc 风格 https github com numpy numpy blob master doc HOWTO DOCUMENT rst txt 并且文档是自动生成的Sphinx http sph
  • 如何使用 boto3 从 AWS Cognito 获取经过身份验证的身份响应

    我想使用 boto3 获取访问 AWS 服务的临时凭证 用例是这样的 我的 Cognito 用户池中的用户登录到我的服务器 我希望服务器代码为该用户提供访问其他 AWS 服务的临时凭证 我有一个存储我的用户的 Cognito 用户池 我有一
  • SQLAlchemy:检查给定值是否在列表中

    问题 在 PostgreSQL 中 检查某个字段是否在给定列表中是使用IN操作员 SELECT FROM stars WHERE star type IN Nova Planet SQLAlchemy 的等价物是什么INSQL查询 我尝试过
  • S3 选择检索 CSV 中的标头

    我尝试使用以下代码从存储在 S 存储桶中的 CSV 中获取记录子集 s3 boto3 client s3 bucket bucket file name file sql stmt SELECT S FROM s3object S LIMI
  • 检查多维 numpy 数组的所有边是否都是零数组

    n 维数组有 2n 个边 1 维数组有 2 个端点 2 维数组有 4 个边或边 3 维数组有 6 个 2 维面 4 维数组有 8 个边 ETC 这类似于抽象 n 维立方体发生的情况 我想检查 n 维数组的所有边是否仅由零组成 以下是边由零组
  • __getitem__、__setitem__ 如何处理切片?

    我正在运行 Python 2 7 10 我需要拦截列表中的更改 我所说的 更改 是指在浅层意义上修改列表的任何内容 如果列表由相同顺序的相同对象组成 则列表不会更改 无论这些对象的状态如何 否则 它会更改 我不需要找出来how列表已经改变
  • 使用Python进行图像识别[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有一个想法 就是我想识别图像中的字母 可能是 bmp或 jpg 例如 这是一个包含字母 S 的 bmp 图像 我想做的是使用Pyth
  • 使用 OpenCV 进行相机校准 - 如何调整棋盘方块大小?

    我正在使用 OpenCV Python 示例开发相机校准程序 来自 OpenCV 教程 http opencv python tutroals readthedocs io en latest py tutorials py calib3d
  • 比较两个文本文件并计算差异

    我一直在尝试在Python中比较两个文本文件 本质上我想打开它们并一次比较一个字符 如果字符不同 则向计数器添加1 然后显示该值 这是我到目前为止所拥有的 usr bin env python diff 0 import random im
  • 如何在返回的 AJAX 调用上使用 django 模板标签?

    我有一个简单的 AJAX 脚本 它在名为的搜索字段中获取输入的字符串AJAXBox并调用一个视图函数 该函数使用过滤器查询数据库并返回与输入参数匹配的所有 User 对象的查询集 当我使用 django 模板标签迭代查询集时 它不起作用 我
  • Bottle 是否可以处理没有并发的请求?

    起初 我认为 Bottle 会并发处理请求 所以我编写了如下测试代码 import json from bottle import Bottle run request response get post import time app B
  • Python:使用for循环更改变量后缀

    我知道这个问题被问了很多 但到目前为止我无法使用 理解答案 我想改变for循环中变量的后缀 我尝试了 stackoverflow 搜索提供的所有答案 但很难理解提问者经常提出的具体代码 因此 为了清楚起见 我使用一个简单的示例 这并不意味着
  • 使用 plone.api 创建文件的 Python 脚本在设置文件时出现错误 WrongType

    Dears 我正在创建一个脚本python来在Plone站点中批量上传文件 安装是UnifiedInstaller Plone 4 3 10 该脚本读取了一个txt 并且该txt以分号分隔 在新创建的项目中设置文件时出现错误 下面是脚本 f
  • Docker Build 找不到 pip

    尝试关注一些 1 https aws amazon com blogs aws run docker apps locally using the elastic beanstalk eb cli 2 http docs aws amazo
  • 无法在 Windows 服务器上使 SVN 预提交脚本失败

    我正在编写一个 SVN pre commit bat 文件 该文件调用 Python 脚本来查询我们的问题跟踪系统 以确定用户提供的问题跟踪 ID 是否处于正确的状态 例如 打开 状态 并与正确的关联项目 SVN 服务器运行 Windows
  • 在Python中将罗马数字转换为整数

    根据 user2486 所说 这是我当前的代码 def romanMap map M 1000 CM 900 D 500 CD 400 C 100 XC 90 L 50 XL 40 X 10 IX 9 V 5 V 4 I 1 return
  • 稍微不同的形状会收敛到错误的数字 - 为什么?

    我试图弄清楚为什么 TensorFlow 会做一些令人惊讶的事情 我将其归结为一个测试用例 尝试对一个简单的问题进行线性回归 该问题只需将两个输入加在一起 权重收敛到 1 0 偏差收敛到 0 0 正如它们应该的那样 使用此版本的训练输出 t

随机推荐

  • Python 脚本在 PyCharm 中运行,但不在 Git Bash 中运行

    假设我有一个任意大的模块化 Python 2 7 代码库 project docs etc package module1 submodule1 subsubmodule1 init py subsubmodule2 and so on i
  • java.lang.ClassCastException:oracle.j2ee.ws.saaj.soap.TextImpl 无法转换为 javax.xml.soap.SOAPElement

    我通过尝试与具有 SOAPHeader Handler 实现的 Web 服务通信来得到这个 java lang ClassCastException 错误发生的原因是 String data Text SOAPElement is next
  • 使用 API v4.0 训练 QnA Maker

    我曾经使用 QnA Maker 的预览版 v3 0 API 具有此训练端点 可用于强化正确答案 https westus dev cognitive microsoft com docs services 597029932bcd590e7
  • 在相对布局中动态添加文本框时出现问题

    我在动态添加文本框到我的视图时遇到问题 更具体地说 文本框加法器可以工作 但我试图移动到它下面的按钮却不能 原始视图是以下超链接中的第一张图片 按下 按钮后 应在第二个文本框和决定按钮之间添加一个文本框 然后向下移动 按钮 使其位于新框旁边
  • ASP.NET MVC - 会话为空

    我在 net4 上有一个 MVC3 应用程序 其会话在开发环境中工作 但不在生产环境中工作 在生产中 我记录了会话 ID 然后在我从会话中设置和获取时它是相同的 当我尝试参加会议时 我得到了Null Exception 这是我访问会话的方式
  • 没有持久化:Fluent nHibernate 异常

    我收到异常 没有持久化 MVCTemplate Common Entities User 我谷歌这个问题并应用我找到的所有解决方案 但一切对我来说都没用 有谁知道我做错了什么 我的用户类代码是 public class User publi
  • 如何让Hibernate不删除表

    我正在使用休眠 每当我尝试添加记录时 它都会删除表并再次添加它 它从不使用现有的表并对其进行更改 这是我的相关部分hibernate cfg xml
  • Kivy/Python Countdown App 项目 kivy has no attribute 'built' 错误

    问题 什么是 没有属性 构建 错误 我需要做什么来更正此代码 以便它可以接受日期时间对象并显示倒计时 抱歉发了这么长的帖子 我已经提供了代码和链接 kv 文件 https github com tmusa CySat GroundStati
  • Excel:下拉列表取决于另一个下拉列表

    我想要在 Excel 中执行以下操作 相邻单元格中的两个下拉列表 下拉列表 1 下拉列表 1 Dropdown list 1 One Two Three 如果我在第一个单元格中选择一个 第二个单元格中的列表应包含以下选项 One 1 1 1
  • 当我运行我的 android UIAutomator 代码时,它显示错误

    当我运行我的 android UIAutomator 代码时 它显示以下错误 INSTRUMENTATION RESULT shortMsg java lang RuntimeExceptionINSTRUMENTATION RESULT
  • Objective-C instantiateViewControllerWithIdentifier 返回 nil

    一周后我打开了我的项目 看来对于所有新的UIViewController我创建于StoryBoard instantiateViewControllerWithIdentifier返回为零 一切ViewControllers项目中已经存在的
  • Flask 和 SQLAlchemy 以及 MetaData 对象

    这是我第一次使用这个环境 我愿意使用的 SQLAlchemy 部分只是允许我使用 autoload True 的表对象查询数据库的部分 我这样做是因为我的表已经存在于数据库 mysql 服务器 中 并且不是通过定义 Flask 模型创建的
  • 在单例上使用 Dispose 来清理资源

    我的问题可能更多地与语义有关 而不是与实际使用有关IDisposable 我正在致力于实现一个单例类 该类负责管理在应用程序执行期间创建的数据库实例 当应用程序关闭时 应删除该数据库 现在我正在处理这个删除Cleanup 应用程序在关闭时调
  • 如何在 Bash 脚本中从一系列 _# 文件名中选择最大数字

    我有一个包含文件的目录 heat1 conf heat2 conf heat
  • Silverlight 4 和浏览器外

    有谁知道是否可以对 app current mainwindow width 进行动画处理 以便在以编程方式调整 oob 应用程序窗口大小时获得一个带有缓动的漂亮动画 谢谢 最简单的方法是向页面添加滑块控件 滑块可以折叠 仅用于具有简单的动
  • VBA 自函数返回#VALUE!单元格出错,而在函数窗口中正确返回实际值

    我在下面写的函数是采用一个范围 我对其进行了一些条件格式设置 用于字体颜色 以及另一个用于比较颜色的单元格范围 功能是统计大范围内有多少个单元格与一个单元格范围具有相同的字体颜色 Function CountColor rng As Ran
  • 在logstash中使用docker GELF驱动程序env/labels

    Docker GELF 日志驱动程序允许env and labels日志选项 gelf 日志记录驱动程序支持 lab els 和 env 选项 它添加了额外的密钥extra字段 以下划线 为前缀 ref https docs docker
  • 分配给 Rails ActiveRecord 中的布尔字段时,值如何转换?

    我的问题的简短版本 在 Rails ActiveRecord 中 如果我有一个布尔字段并且我给它分配类似 abc or 2 然后它立即转换为false 价值1被投射到true and nil仍然是nil 为什么会这样呢 在哪里可以找到解释此
  • PHPUnit:存根多个接口

    我正在掌握 PHPUnit 到目前为止发现它非常容易使用 但我遇到了一个给我带来困难的测试用例 我正在针对一组预期对象实现的接口 一些是 PHP 的 一些是自制的 编写代码 并且 SUT 需要一个输入对象来实现多个接口 例如 class M
  • 计算两个模型的梯度

    假设我们正在构建一个基本的 CNN 来识别猫和狗的图片 二元分类器 此类 CNN 的示例如下 model Sequential Conv2D 32 3 3 input shape Activation relu MaxPooling2D p