深度学习中的卷积和反卷积(四)——卷积和反卷积的梯度

本系列已完结,全部文章地址为:

深度学习中的卷积和反卷积(一)——卷积的介绍

深度学习中的卷积和反卷积(二)——反卷积的介绍

深度学习中的卷积和反卷积(三)——卷积和反卷积的计算

深度学习中的卷积和反卷积(四)——卷积和反卷积的梯度

1 卷积的梯度计算

1.1 Tensorflow中矩阵梯度运算的说明

请注意,计算y对x的梯度时,如果y、x都是矩阵,梯度理应是每一个y对每一个x求偏导的结果。但在Tensorflow中,gradient是返回了总和的梯度。如果想求出每个分量的梯度,应该使用Jacobian矩阵。这一点困扰了笔者很久,直到翻到文档才恍然大悟。文档地址:梯度和自动微分简介  |  TensorFlow Core

import tensorflow as tfx = tf.Variable(2.0)
# 求gradient,结果为7
with tf.GradientTape() as tape:y = x * [3., 4.]
print(tape.gradient(y, x).numpy())
# 求gradient,结果为[3. 4.]
with tf.GradientTape() as tape:y = x * [3., 4.]
print(tape.jacobian(y, x).numpy())

1.2 卷积对input的梯度

沿用上一篇的例子如下图:

数值例子为:

 输入是3*3的维度,因此梯度维度也是3*3,表示对每一个a中的元素求梯度的结果

观察卷积输出的结果,例如a_{11}​,参与了y_{11}​的计算,系数是k_{11}​,因此梯度为k_{11}​。同理,所有的y对所有的输入都可以计算梯度。以y_{11}​为例:

\frac{\partial {y_{11}}}{\partial {a_{11}}}=k_{11}=1\frac{\partial {y_{11}}}{\partial {a_{12}}}=k_{12}=2\frac{\partial {y_{11}}}{\partial {a_{13}}}=0
\frac{\partial {y_{11}}}{\partial {a_{21}}}=k_{21}=3\frac{\partial {y_{11}}}{\partial {a_{22}}}=k_{22}=4\frac{\partial {y_{11}}}{\partial {a_{23}}}=0
\frac{\partial {y_{11}}}{\partial {a_{31}}}=0\frac{\partial {y_{11}}}{\partial {a_{32}}}=0\frac{\partial {y_{11}}}{\partial {a_{33}}}=0

在Tensorflow中验证如下

@tf.function
def compute_gradient(x, filters):with tf.GradientTape() as tape:tape.watch(x)  # 监视xy = tf.nn.conv2d(x, filters, [1, 1, 1, 1], "VALID")return tape.jacobian(y, x)  # 计算y相对于x的梯度print(compute_gradient(x, filters).numpy().reshape([4, 3, 3]))

输出如下,y_{11}​的输出结果与上文中表格一致,其余y分量不再赘述。

[[[1. 2. 0.][3. 4. 0.][0. 0. 0.]][[0. 1. 2.][0. 3. 4.][0. 0. 0.]][[0. 0. 0.][1. 2. 0.][3. 4. 0.]][[0. 0. 0.][0. 1. 2.][0. 3. 4.]]]

1.3 卷积对kernel的梯度

 对卷积核计算的梯度,就是每一个y对每一个k求梯度,例如每一个y对于k_{11}​的梯度,就是下图红框中的部分,分别是1、2、4、5。

还是以y_{11}​为例,在Tensorflow中验证如下

@tf.function
def compute_kernel_gradient(x, filters):with tf.GradientTape() as tape:tape.watch(filters)  # 监视xy = tf.nn.conv2d(x, filters, [1, 1, 1, 1], "VALID")return tape.jacobian(y, filters)print(compute_kernel_gradient(x, filters).numpy().reshape([4, 2, 2]))

输出如下,符合预期。

[[[1. 2.][4. 5.]][[2. 3.][5. 6.]][[4. 5.][7. 8.]][[5. 6.][8. 9.]]]

2 反卷积的梯度计算

由于反卷积的计算相当于对矩阵先做填充再做卷积,因此反卷积的梯度等价于对填充后的输入矩阵做卷积的梯度。

2.1 反卷积对input的梯度

以前文的数据为例,首先对输入矩阵填充0,然后翻转卷积核,得到4*4的输出。

我们计算每一个输出对每一个输入的梯度,输出是4*4,输入是3*3,因此算梯度的Jacobian矩阵维度是4*4*3*3。

我们以y_{32}a_{22}的梯度为例,先看y_{32}是怎么算出来的

y_{32}=a_{21}*k_{22}+a_{22}*k_{21}+a_{31}*k_{12}+a_{32}*k_{11}

因此y_{32}a_{22}的梯度为k_{21}=3

Tensorflow中验证如下:

import numpy as np
import tensorflow as tfdef conv2d_transpose(x, filters):return tf.nn.conv2d_transpose(x, filters, [1, 4, 4, 1], strides=1, padding="VALID")@tf.function
def compute_conv2d_transpose_i_gradient(x, filters):with tf.GradientTape() as tape:tape.watch(x)y = conv2d_transpose(x, filters)return tape.jacobian(y, x)x = tf.constant(np.arange(1, 10).reshape([1, 3, 3, 1]), dtype=tf.float32)
filters = tf.constant(np.arange(1, 5).reshape(2, 2, 1, 1), dtype=tf.float32)
print(compute_conv2d_transpose_i_gradient(x, filters).numpy().reshape([4, 4, 3, 3])[2][1][1][1])  # 注意下标是从0开始的,[2][1]代表y32,[1][1]代表a22

输出为3,与手算结果一致。

3.0

2.2 反卷积对kernel的梯度

与反卷积对input梯度类似,也等价于对填充后的输入矩阵做卷积时计算梯度。同样用数值例子验证。

计算 y_{32}k_{21}的梯度,结合上节的表达式,得到梯度为a_{22}=5

Tensorflow中验证如下:

import numpy as np
import tensorflow as tfdef conv2d_transpose(x, filters):return tf.nn.conv2d_transpose(x, filters, [1, 4, 4, 1], strides=1, padding="VALID")@tf.function
def compute_conv2d_transpose_k_gradient(x, filters):with tf.GradientTape() as tape:tape.watch(filters)y = conv2d_transpose(x, filters)return tape.jacobian(y, filters)x = tf.constant(np.arange(1, 10).reshape([1, 3, 3, 1]), dtype=tf.float32)
filters = tf.constant(np.arange(1, 5).reshape(2, 2, 1, 1), dtype=tf.float32)
print(compute_conv2d_transpose_k_gradient(x, filters).numpy().reshape([4, 4, 2, 2])[2][1][1][0])

输出为5,与手算结果一致。

5.0

3 反卷积等价于误差反向传播

https://zhuanlan.zhihu.com/p/338780702

下图是Tensorflow中反卷积函数的源码,可以看出反卷积等价于将input作为卷积下层误差反向传播,本节进行推导。

@tf_export("nn.conv2d_transpose", v1=[])
@dispatch.add_dispatch_support
def conv2d_transpose_v2(input,  # pylint: disable=redefined-builtinfilters,  # pylint: disable=redefined-builtinoutput_shape,strides,padding="SAME",data_format="NHWC",dilations=None,name=None):......return gen_nn_ops.conv2d_backprop_input(input_sizes=output_shape,filter=filters,out_backprop=input,strides=strides,padding=padding,explicit_paddings=explicit_paddings,data_format=data_format,dilations=dilations,name=name)

3.1 全连接网络的误差反向传播

卷积可视作特殊的全连接网络。全连接网络中每一个输出与每一个输入都使用权重边相连,输出是各输入的加权求和。对于卷积而言,输出只与某些输入有关,但可以理解为所有输出与所有输入相连,只是其中有些权重边固定为0而已。因此,本节先回顾全连接网络的误差反向传播过程,随后推广到卷积的误差反向传播。

如下图所示,我们构造了一个全连接神经网络,忽略偏置。

符号表示如下:

符号含义
A_{i}^{L}L层第i个输入。A指activation,表示L-1层激活后传递给L层的输入
W_{ij}^{L}L层第i个输入连接到第j个输出的权重
Z_{i}^{L}L层第i个输出
B^LL层偏置
L损失函数
\delta_{i}^L最终的误差对于L层第i个输出的梯度,表示反向传播过来的误差

对于L层来说,误差反向传播需要做两件事情:(1)计算误差对本层权重的梯度,从而更新权重;(2)将误差反向传播到上一层,从而更新上层的权重。

3.1.1 误差对本层权重的梯度

根据链式法则,有:

\partial {L}/\partial {W_{ij}^{L}}=\partial {L}/\partial {Z_{j}^{L}}*\partial {Z_{j}^{L}}/\partial {W_{ij}^{L}}

根据\delta_{j}^{L}的定义,前一项即为\delta_{j}^{L}。后一项比较简单,因为Z是由W加权求和而来,因此该项等于A_j^L。在卷积中,卷积核其实就是特殊的权重,因此该项即对应前文讨论的卷积对kernel的梯度。

\delta_{j}^{L}表示误差传播到Z_j^L这个节点的误差,表示节点对于最后误差负的责任,注意这里的Z_j^L是激活之前的输出。\delta_{j}^{L}可以继续分解为最终误差对激活后的结果A的梯度乘A对于Z的梯度,如果是最后一层,则代表损失函数的梯度。后者代表激活函数的梯度。

求出梯度后,根据神经网络的学习规则,权重根据学习率、梯度动态更新。

3.1.2 误差反向传播到上一层

本层需要求出\delta^{L-1},从而使得下一层根据此结果更新权重。以L-1层第j个输出为例

\delta_{j}^{L-1}=\partial {L}/\partial {Z_{j}^{L-1}}=\partial {L}/\partial {A_{j}^{L}}*\partial {A_{j}^{L}}/\partial {Z_{j}^{L-1}}

注意乘号后一项就是激活函数的导数,下面分析乘号前一项。注意L层的Aj会参与到多个输出Z,因此需要将所有输出Zi都考虑在内。

\partial {L}/\partial {A_{j}^{L}}=\sum_i {\partial {L}/\partial {Z_i^L} * \partial {Z_i^L}/\partial {A_{j}^{L}}}=\sum_i {\delta_i^L * \partial {Z_i^L}/\partial {A_{j}^{L}}}

此式后一项即为卷积中对input的梯度。可进一步化简,结果为\partial {L}/\partial {A_{j}^{L}}=\sum_i {\delta_i^L * W_{ji}},因此损失函数对L层输入的梯度可表示为\delta与权重相乘后求和。

3.2 卷积的误差反向传播

误差反向传播,对应前文中“误差反向传播到上一层”这一小节。在卷积中,卷积核其实就是特殊的稀疏权重,其中有很多权重为0。

还是以前文卷积计算为例,我们列出损失函数对所有输入的梯度。代入上式,得到:

注意这里符号表示有所调整,因为全连接网络是把输入和输出展开的,卷积这里输入和输出是二维的,因此下标用二维坐标表示。同时此处只讨论第L层网络情况,不再保留上标。

\partial {L}/\partial{a_{11}}=\delta_{11}*k_{11}

\partial {L}/\partial{a_{12}}=\delta_{11}*k_{12}+\delta_{12}*k_{11}

\partial {L}/\partial{a_{13}}=\delta_{11}*k_{12}+\delta_{12}*k_{12}

\partial {L}/\partial{a_{21}}=\delta_{11}*k_{21}+\delta_{21}*k_{11}

\partial {L}/\partial{a_{22}}=\delta_{11}*k_{22}+\delta_{12}*k_{21}+\delta_{21}*k_{12}+\delta_{22}*k_{11}

\partial {L}/\partial{a_{23}}=\delta_{12}*k_{22}+\delta_{22}*k_{12}

\partial {L}/\partial{a_{31}}=\delta_{21}*k_{21}

\partial {L}/\partial{a_{32}}=\delta_{21}*k_{22}+\delta_{22}*k_{21}

\partial {L}/\partial{a_{33}}=\delta_{22}*k_{22}

可以看到由于a22参与了所有的输出,所以表达式有4项,其他输入只参与了1或2项输出,相当于对剩余输出的权重为0,因此表达式只有1、2项。

这种计算结果等价于对下式求卷积:

可以发现,这正好是反卷积的计算过程。

3.3 Tensorflow的验证

在Tensorflow中验证如下:

def conv2d_transpose(x, filters):return tf.nn.conv2d_transpose(x, filters, [1, 4, 4, 1], strides=1, padding="VALID")x = tf.constant(np.arange(1, 10).reshape([1, 3, 3, 1]), dtype=tf.float32)
filters = tf.constant(np.arange(1, 5).reshape(2, 2, 1, 1), dtype=tf.float32)
print("反卷积结果:")
print(conv2d_transpose(x, filters).numpy().reshape([4, 4]))
# 卷积反向传播
x = tf.constant(np.array([[0, 0, 0, 0, 0],[0, 1, 2, 3, 0],[0, 4, 5, 6, 0],[0, 7, 8, 9, 0],[0, 0, 0, 0, 0]]).reshape([1, 5, 5, 1]), dtype=tf.float32)
filters = tf.constant(np.array([[4, 3],[2, 1]]).reshape(2, 2, 1, 1), dtype=tf.float32)
print("卷积反向传播结果:")
print(tf.nn.conv2d(x, filters, [1, 1, 1, 1], "VALID").numpy().reshape(4, 4))

输出如下图所示,二者一致。

反卷积结果:
[[ 1.  4.  7.  6.][ 7. 23. 33. 24.][19. 53. 63. 42.][21. 52. 59. 36.]]
卷积反向传播结果:
[[ 1.  4.  7.  6.][ 7. 23. 33. 24.][19. 53. 63. 42.][21. 52. 59. 36.]]

参考资料

《卷积神经网络(CNN)反向传播算法详细解析》

《反向传播算法中的权重更新是如何进行的?》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/67181.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】线程

目录 1. 什么是线程 1.1概念 1.2 进程和线程的区别 1.3 线程资源 2. 函数接口 2.1创建线程: pthread_create 2.2 退出线程: pthread_exit 2.3 回收线程资源 练习 1. 什么是线程 1.1概念 线程是一个轻量级的进程,为了提高系统的性能引入线程。 在同一个进…

【C语言】字符串函数详解

文章目录 Ⅰ. strcpy -- 字符串拷贝1、函数介绍2、模拟实现 Ⅱ. strcat -- 字符串追加1、函数介绍2、模拟实现 Ⅲ. strcmp -- 字符串比较1、函数介绍2、模拟实现 Ⅳ. strncpy、strncat、strncmp -- 可限制操作长度Ⅴ. strlen -- 求字符串长度1、函数介绍2、模拟实现&#xff08…

Windows部署NVM并下载多版本Node.js的方法(含删除原有Node的方法)

本文介绍在Windows电脑中,下载、部署NVM(node.js version management)环境,并基于其安装不同版本的Node.js的方法。 在之前的文章Windows系统下载、部署Node.js与npm环境的方法(https://blog.csdn.net/zhebushibiaoshi…

centos 8 中安装Docker

注:本次样式安装使用的是centos8 操作系统。 1、镜像下载 具体的镜像下载地址各位可以去官网下载,选择适合你们的下载即可! 1、CentOS官方下载地址:https://vault.centos.org/ 2、阿里云开源镜像站下载:centos安装包…

STM32-笔记40-BKP(备份寄存器)

一、什么是BKP(备份寄存器)? 备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或…

vue-cli项目配置使用unocss

在了解使用了Unocss后&#xff0c;就完全被它迷住了。接手过的所有项目都配置使用了它&#xff0c;包括一些旧项目&#xff0c;也跟同事分享了使用Unocss的便捷性。 这里分享一下旧项目如何配置和使用Unocss的&#xff0c;项目是vue2vue-cli构建的&#xff0c;node<20平常开…

新增文章分类功能

总说 过程参考黑马程序员SpringBoot3Vue3全套视频教程&#xff0c;springbootvue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili 目录 总说 一、功能实现 1.1 Controller层 1.2 Service层 1.3 Impl层 1.4 Mapper层 1.5 测试接口 二、优化 2.1 2.2 一、…

知识图谱常见的主流图数据库

在知识图谱中&#xff0c;主流使用的图数据库包括以下几种&#xff1a; Neo4j&#xff1a;这是目前全球部署最广泛的图数据库之一&#xff0c;具有强大的查询性能和灵活的数据模型&#xff0c;适用于复杂关系数据的存储和查询。 JanusGraph&#xff1a;JanusGraph是一个开源的…

JavaSE学习心得(多线程与网络编程篇)

多线程-网络编程 前言 多线程&JUC 多线程三种实现方式 第一种实现方式 第二种实现方式 第三种实现方式 常见成员方法 买票引发的安全问题 同步代码块 同步方法 Lock锁 生产者和消费者 常见方法 等待唤醒机制 练习 抢红包 抽奖 多线程统计并求最…

Pytorch基础教程:从零实现手写数字分类

文章目录 1.Pytorch简介2.理解tensor2.1 一维矩阵2.2 二维矩阵2.3 三维矩阵 3.创建tensor3.1 你可以直接从一个Python列表或NumPy数组创建一个tensor&#xff1a;3.2 创建特定形状的tensor3.3 创建三维tensor3.4 使用随机数填充tensor3.5 指定tensor的数据类型 4.tensor基本运算…

candb++ windows11运行报错,找不到mfc140.dll

解决问题记录 mfc140.dll下载 注意&#xff1a;放置位置别搞错了

​公专网一体5G工业路由器,智慧电网全链路加密监控管理

随着可再生能源的集成 电网调度策略复杂性增加 需更精细的并网管理以平衡供需 传统电力网络的通信基础落后 难以适应电力设施的广泛分布 和日益增长的管理维护需求 计讯物联5G公专网一体路由器 通过融合公网和专网的优势 有效解决了现代电网对于 高效、灵活和安全通信的需求 ↓…

【Linux】--- 进程的等待与替换

进程的等待与替换 一、进程等待1、进程等待的必要性2、获取子进程status3、进程等待的方法&#xff08;1&#xff09;wait&#xff08;&#xff09;函数&#xff08;2&#xff09;waitpid函数 4、多进程创建以及等待的代码模型5、非阻塞接口 轮询 二、进程替换1、替换原理2、替…

zerotier搭建虚拟局域网,自建planet

基于该开源项目 自建planet节点&#xff0c;更快速&#xff0c;更安全 本教程依据docker-zerotier-planet 项目文档书写&#xff0c;并以linux(centos 7)和windows作为示例&#xff0c;需要其他系统配置方法&#xff0c;可移步项目文档 一. 前置资源 具有外网ip的服务器 后面…

屏幕轻触间:触摸交互从 “感知” 到 “智算” 的隐秘路径

从用户点击屏幕到前端感知及数据处理全流程剖析 引言 在移动智能设备与触摸交互技术深度融合的当下&#xff0c;当我们的手指轻触手机屏幕&#xff0c;一系列复杂且精妙的技术流程便瞬间启动。这一过程涵盖硬件层、驱动层、操作系统层、应用层&#xff0c;甚至延伸到后端的数…

深入Node.js集群:原理、优势与搭建实战,如何应对高并发

文章目录 一、Node.js 集群简介二、Node.js 集群原理剖析2.1 主从模型2.2 负载均衡机制2.3 进程间通信&#xff08;IPC&#xff09; 三、Node.js 集群优势详解3.1 性能提升3.2 高可用性3.3 资源利用率优化 四、Node.js 集群搭建实战4.1 准备工作4.2 创建主控制节点4.3 工作节点…

数字普惠金融对新质生产力的影响研究(2015-2023年)

基于2015—2023年中国制造业上市公司数据&#xff0c;探讨了数字普惠金融对制造业企业新质生产力的影响及作用机理。研究发现&#xff0c;数字普惠金融有助于促进制造业企业新质生产力的发展&#xff0c;尤其是在数字普惠金融的使用深度较大的情况下&#xff0c;其对新质生产力…

数据仓库基础常见面试题

1.数据仓库是什么 ‌数据仓库&#xff08;Data Warehouse&#xff09;是一个面向主题的、集成的、非易失的、随时间变化的数据集合&#xff0c;用于支持企业的管理决策‌。它不同于传统的操作型数据库&#xff0c;后者主要用于处理日常业务交易和实时查询&#xff0c;而数据仓库…

记一次OpenEuler Linux磁盘分区表损坏的数据恢复

问题复现 原本有一台GIS地图服务器存放大量数据&#xff0c;突然有一天磁盘满了&#xff0c;于是运维人员照常进行磁盘扩容。但由于误操作&#xff0c;导致使用fdisk的时候把分区表损坏了&#xff0c;表现如下&#xff1a; 这里可以看到启动时能看到xvda被分为了xvda1和xvda2…

分布式数据存储基础与HDFS操作实践(副本)

以下为作者本人撰写的报告&#xff0c;步骤略有繁琐&#xff0c;不建议作为参考内容&#xff0c;可以适当浏览&#xff0c;进一步理解。 一、实验目的 1、理解分布式文件系统的基本概念和工作原理。 2、掌握Hadoop分布式文件系统&#xff08;HDFS&#xff09;的基本操作。 …