python神经网络编程入门(十一)——CNN卷积层的反向传播—— 为什么要把卷积核转 180°?
第 10 篇我们亲手写出了卷积的前向传播学会了用滑动窗口做“互相关”运算。现在神经网络要想学习必须让梯度从后面传回来更新卷积核的权重也把误差继续往前传。这件事就叫反向传播。卷积层的反向传播几乎每个人都卡在同一个地方“为什么要把卷积核转 180 度再去卷积” 很多文章直接给一个公式看得人更晕。今天我们不用任何花哨的符号就用第 10 篇那个 5×5 的图像和 3×3 的卷积核再假定一个具体的“误差矩阵”一步一步手算用数字说话让你亲眼看见“旋转 180 度”是怎么被逼出来的。看完你会发现这不过就是把同一个滑窗游戏反过来玩了一遍。1. 固定数据先明确我们在算什么我们要处理的数据和第十篇完全相同不引入任何新面孔。输入图像5×5[[1, 1, 1, 0, 0], [0, 1, 1, 1, 0], [0, 0, 1, 1, 1], [0, 0, 1, 1, 0], [0, 1, 1, 0, 0]]卷积核3×3[[1, 0, 1], [0, 1, 0], [1, 0, 1]]前向传播时步长为 1不补零得到 3×3 的输出特征图我们之前已经算出具体数字是[[4, 3, 4], [2, 4, 3], [2, 3, 4]]现在假设损失函数已经计算出了对输出特征图的梯度记作dout。为了好算我们不随机生成而是指定一个简单的数字矩阵dout [[1, 2, 3], [4, 5, 6], [7, 8, 9]]你可以把它理解为输出层的每个位置对最终损失负有多大的“责任”这个矩阵就是具体数值。反向传播要算出两个东西dW损失对卷积核里每个权重的梯度3×3矩阵用来告诉卷积核该怎样调整。dX损失对输入图像每个像素的梯度5×5矩阵用来继续往网络的前面传。2. 先求 dW用误差矩阵去“滑”原始图像我们先回忆一下前向传播里一个卷积核权重是干什么的。比如卷积核左上角那个权重W[0,0]1它在前向时会和图像上每一个滑窗的左上角像素相乘。也就是说W[0,0]一共参与了 9 次计算因为输出是 3×3每个输出点用一次。每次参与时乘以的那个输入像素值是多少我们把这些像素找出来再乘上对应的误差dout值全加起来就是dW[0,0]的梯度。2.1 用手工算dW[0,0]输出位置 (0,0)滑窗的左上角像素是image[0,0] 1误差是dout[0,0]1贡献1×11。输出位置 (0,1)左上角是image[0,1]1误差dout[0,1]2贡献1×22。输出位置 (0,2)左上角image[0,2]1误差dout[0,2]3贡献1×33。输出位置 (1,0)左上角image[1,0]0误差4贡献0。输出位置 (1,1)左上角image[1,1]1误差5贡献5。输出位置 (1,2)左上角image[1,2]1误差6贡献6。输出位置 (2,0)左上角image[2,0]0误差7贡献0。输出位置 (2,1)左上角image[2,1]0误差8贡献0。输出位置 (2,2)左上角image[2,2]1误差9贡献9。全部加起来123056009 26。所以dW[0,0] 26。2.2 再算dW[0,1]观察规律W[0,1]在前向时总是乘滑窗的第一行第二列像素。找出这些像素(0,0) 窗口image[0,1]1 → 误差 1 → 1(0,1) 窗口image[0,2]1 → 误差 2 → 2(0,2) 窗口image[0,3]0 → 误差 3 → 0(1,0) 窗口image[1,1]1 → 误差 4 → 4(1,1) 窗口image[1,2]1 → 误差 5 → 5(1,2) 窗口image[1,3]1 → 误差 6 → 6(2,0) 窗口image[2,1]0 → 误差 7 → 0(2,1) 窗口image[2,2]1 → 误差 8 → 8(2,2) 窗口image[2,3]1 → 误差 9 → 9总和120456089 35。你看这个计算过程其实就是把dout这个 3×3 矩阵当作卷积核在原始图像X上从左到右、从上到下滑动每盖住一个 3×3 的区域就把对应的数值相乘再相加。这不是和前向传播完全一样的操作吗只是角色换了一下原来卷积核是W现在“卷积核”变成了dout输入仍然是X。于是求整个dW的方法就很简单了用dout做核对原图X做一次互相关步长 1不填充。写出来的代码就是import numpy as np image np.array([[1,1,1,0,0], [0,1,1,1,0], [0,0,1,1,1], [0,0,1,1,0], [0,1,1,0,0]], dtypenp.float32) kernel np.array([[1,0,1], [0,1,0], [1,0,1]], dtypenp.float32) dout np.array([[1,2,3], [4,5,6], [7,8,9]], dtypenp.float32) def compute_dW(dout, X, kH, kW): dW np.zeros((kH, kW), dtypenp.float32) for i in range(kH): # 遍历卷积核的行 for j in range(kW): # 遍历卷积核的列 for m in range(dout.shape[0]): # 遍历 dout 的行 for n in range(dout.shape[1]): # 遍历 dout 的列 dW[i,j] dout[m,n] * X[mi, nj] return dW dW compute_dW(dout, image, 3, 3) print(dW:\n, dW)运行后得到的结果是这就是正确的dW与我们手工算的前两个数字完全吻合。这个矩阵就是损失对卷积核每个权重的梯度。小结一下dW就是用误差矩阵dout当“临时卷积核”在原图上滑动一遍。你可以这样记前向是“核滑图”反向求dW是“误差滑图”。3. 再求 dX误差怎样传回输入接下来是重头戏我们要知道输入图像上每一个像素该承担多少责任也就是dX。为什么dX更绕因为输入上的一个像素可能被多个滑窗覆盖过参与了多个输出位置的计算。举个例子图像正中间那个像素image[2,2]1它同时被 9 个滑窗3×3 的输出每个输出对应的窗口都包含它使用过。所以9 个误差信号都会和它扯上关系。我们要把这 9 条路径的贡献全部累加起来。3.1 手算dX[2,2]找出image[2,2]在每次前向滑窗中乘的是卷积核的哪个权重输出 (0,0) 的窗口行0-2列0-2image[2,2]在窗口中的位置是 (2,2)乘的权重是W[2,2]1。误差dout[0,0]1贡献1*11。输出 (0,1)行0-2列1-3它的位置是 (2,1)乘权重W[2,1]0贡献2*00。输出 (0,2)行0-2列2-4位置 (2,0)乘权重W[2,0]1贡献3*13。输出 (1,0)行1-3列0-2位置 (1,2)乘权重W[1,2]0贡献4*00。输出 (1,1)行1-3列1-3位置 (1,1)乘权重W[1,1]1贡献5*15。输出 (1,2)行1-3列2-4位置 (1,0)乘权重W[1,0]0贡献6*00。输出 (2,0)行2-4列0-2位置 (0,2)乘权重W[0,2]1贡献7*17。输出 (2,1)行2-4列1-3位置 (0,1)乘权重W[0,1]0贡献8*00。输出 (2,2)行2-4列2-4位置 (0,0)乘权重W[0,0]1贡献9*19。总和103050709 25。所以dX[2,2] 25。3.2 发现规律旋转 180° 就来自这里请仔细观察上面的加和顺序dout[0,0]乘的是W[2,2]dout[0,1]乘的是W[2,1]dout[2,2]乘的是W[0,0]。原本卷积核的左上角权重W[0,0]在这里被放到了dout的右下角去相乘而右下角的W[2,2]又被放到了dout的左上角。这恰好等价于先把卷积核旋转 180°上下翻转 左右翻转再和 dout 做正常的滑窗乘加。用我们这个对称的卷积核不明显但如果换一个不对称的比如[[1,2],[3,4]]旋转后就变成[[4,3],[2,1]]效果一目了然。更重要的是dout是 3×3而我们想要的是 5×5 的dX尺寸不够。怎么办自然想到在dout外面补零。补多少圈因为卷积核是 3×3我们需要让滑窗能覆盖到dout的每个边缘且最终输出是 5×5所以补3-12圈零刚刚好。于是dX的计算步骤变成把卷积核W旋转 180°。把dout外围补上两圈 0因为 3×3 核补 3-12变成一个 7×7 的矩阵。用旋转后的核在这个 7×7 上做一次正常的互相关步长 1得到的就是 5×5 的dX。这就是那个反直觉的“旋转 180° 再做卷积”的全部真相。4. 用图片把“旋转”和“补零”具象化5. 完整的反向传播代码并验证 dX[2,2]我们把 dW 和 dX 的计算写成一个统一的函数。def conv2d_backward(dout, X, W, stride1, padding0): kH, kW W.shape H_out, W_out dout.shape # 1. 计算 dWdout 当核在 X 上滑动 dW np.zeros_like(W) for i in range(kH): for j in range(kW): for m in range(H_out): for n in range(W_out): dW[i,j] dout[m,n] * X[m*stride i, n*stride j] # 2. 计算 dX旋转核 补零 互相关 W_rot np.flip(W, axis(0,1)) # 旋转 180° pad_total kH - 1 # 需要补的圈数 dout_padded np.pad(dout, pad_total, modeconstant) dX np.zeros_like(X) for i in range(X.shape[0]): for j in range(X.shape[1]): region dout_padded[i:ikH, j:jkW] dX[i,j] np.sum(region * W_rot) return dX, dW dX, dW conv2d_backward(dout, image, kernel) print(dX[2,2] , dX[2,2]) # 应该是 25 print(dW:\n, dW)运行后dX[2,2]会输出 25dW的值就是刚才我们手工核对的矩阵。一切都对上了。6. 用 PyTorch 做最终的对照自己写的东西必须和成熟框架的结果一致心里才踏实。我们用 PyTorch 的自动求导验证import torch import torch.nn.functional as F X_t torch.tensor(image, requires_gradTrue) W_t torch.tensor(kernel, requires_gradTrue) Y_t F.conv2d(X_t.view(1,1,5,5), W_t.view(1,1,3,3), stride1, padding0) dout_t torch.tensor(dout).view(1,1,3,3) Y_t.backward(dout_t) dX_torch X_t.grad.squeeze().numpy() dW_torch W_t.grad.squeeze().numpy() print(dX 一致:, np.allclose(dX, dX_torch, atol1e-6)) print(dW 一致:, np.allclose(dW, dW_torch, atol1e-6))7. 总结现在你可以完全扔掉对“旋转 180°”的恐惧了。卷积反向传播说白了就是两件事求dW把误差矩阵dout当成一个卷积核在原始输入图像上做一次互相关。求dX先把卷积核旋转 180°再把dout外面补上核大小−1圈零然后做互相关。这两步都是对前向滑窗逻辑的逆向运用没有任何神秘公式。8. 下篇预告到这一篇为止卷积层的全部正向和反向你已经亲手实现并验证。下一篇文章我们将攻下 CNN 另一个基础组件池化层。池化层没有权重但它的反向传播有一条非常直观的规则——最大值独享梯度平均值均分梯度。我们将继续用 NumPy 从零实现 MaxPooling 的前向和反向并且再次和 PyTorch 硬核对账。卷积 池化 全连接这三件兵器即将凑齐LeNet-5 已近在眼前。

相关新闻

2026轻创业商城小程序怎么选:低成本、易运营、好上手的工具参考

2026轻创业商城小程序怎么选:低成本、易运营、好上手的工具参考

轻创业做商城小程序,最怕的不是功能少一点,而是前期投入太重、后台太难用、后期收费不清楚。很多个体商家、小店老板和兼职创业者,真正需要的是一套能较快开店、能接订单、能做会员和活动、后续还能继续调整的小程序工具。选平台时不要只看“…

2026/7/16 23:14:58阅读更多 →
C++模板进阶:非类型参数与模板特化实战指南

C++模板进阶:非类型参数与模板特化实战指南

1. 项目概述&#xff1a;从“能用”到“精通”的C模板进阶之路如果你已经对C模板有了基础了解&#xff0c;知道它能用来写泛型函数和类&#xff0c;比如std::vector<T>&#xff0c;那么恭喜你&#xff0c;你已经迈出了第一步。但很多时候&#xff0c;你会发现仅仅会声明一…

2026/7/16 23:14:58阅读更多 →
Papers with Code实战指南:从追踪SOTA到高效复现代码

Papers with Code实战指南:从追踪SOTA到高效复现代码

1. 为什么你需要Papers with Code&#xff1f;作为一名AI领域的研究者或工程师&#xff0c;最头疼的问题莫过于&#xff1a;刚读完一篇顶会论文热血沸腾&#xff0c;结果发现作者没开源代码&#xff1b;好不容易找到GitHub仓库&#xff0c;却发现README写得像天书&#xff1b;折…

2026/7/16 23:14:58阅读更多 →
txtai:全功能AI框架的语义搜索与LLM编排实践指南

txtai:全功能AI框架的语义搜索与LLM编排实践指南

今天来看一个功能强大的AI框架——txtai。这个由NeuML开源的项目在GitHub上已经获得了12.7k星&#xff0c;是一个集成了语义搜索、LLM编排和语言模型工作流的全功能AI框架。txtai的核心是一个嵌入数据库&#xff0c;它结合了向量索引&#xff08;稀疏和密集&#xff09;、图网络…

2026/7/17 2:15:11阅读更多 →
Ubuntu安装搜狗输入法全攻略

Ubuntu安装搜狗输入法全攻略

1. Ubuntu下安装搜狗输入法全指南在Linux桌面环境中&#xff0c;中文输入一直是用户关注的焦点问题。作为国内最受欢迎的中文输入法之一&#xff0c;搜狗输入法Linux版凭借其词库丰富、智能联想和云输入等特性&#xff0c;成为Ubuntu用户的首选。不同于Windows下的即装即用&…

2026/7/17 2:15:11阅读更多 →
LIS2DH12加速度传感器驱动工程包:支持I2C/SPI、含寄存器封装与原始数据读取示例

LIS2DH12加速度传感器驱动工程包:支持I2C/SPI、含寄存器封装与原始数据读取示例

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;LIS2DH12三轴加速度计的轻量级驱动工程&#xff0c;包含标准C编写的底层寄存器操作文件lis2dh12_reg.c和lis2dh12_reg.h&#xff0c;覆盖初始化、中断配置、模式设置等基础功能&#xff1b;read_data_simple.c提…

2026/7/17 2:15:11阅读更多 →
SpringBoot短链服务源码:支持自定义过期时间+实时访问统计

SpringBoot短链服务源码:支持自定义过期时间+实时访问统计

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;一套开箱即用的SpringBoot短链接系统&#xff0c;能将长URL快速生成简洁短链&#xff0c;点击后自动302跳转到原地址。短链创建时可选永久有效或设置具体过期天数&#xff0c;到期后自动失效不再跳转。每次用户…

2026/7/17 2:15:11阅读更多 →
MATLAB/Simulink电机控制代码生成实战指南

MATLAB/Simulink电机控制代码生成实战指南

1. 从零开始理解MATLAB/Simulink电机控制代码生成电机控制领域正在经历一场工具链革命——传统的手写代码逐渐被模型化设计替代。作为工程师&#xff0c;我们常遇到这样的困境&#xff1a;算法仿真完美&#xff0c;但移植到硬件时却要重写整个控制逻辑。MATLAB/Simulink的代码生…

2026/7/17 2:15:11阅读更多 →
冲锋衣技术参数全解析:防水透气原理与户外工作装备选购指南

冲锋衣技术参数全解析:防水透气原理与户外工作装备选购指南

最近天气越来越热&#xff0c;户外活动也多了起来。作为一名经常需要外出测试设备的开发者&#xff0c;我发现夏季户外装备的选择直接影响工作效率和舒适度。特别是冲锋衣这种看似简单的装备&#xff0c;其实蕴含着不少技术细节。本文将围绕户外装备的技术参数、功能设计和实际…

2026/7/17 2:10:11阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比&#xff1a;全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时&#xff0c;第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/16 8:28:11阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件&#xff1a;5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件&#xff0c;自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/16 6:53:04阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL&#xff1a;跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/16 12:02:41阅读更多 →
VS Code 高效配置与个性化定制全攻略

VS Code 高效配置与个性化定制全攻略

1. VS Code 高效配置基础作为一款轻量级但功能强大的代码编辑器&#xff0c;VS Code 的默认配置已经能满足基本需求&#xff0c;但通过合理调整设置可以大幅提升编码效率。我使用 VS Code 已经有五年多时间&#xff0c;期间尝试过各种配置方案&#xff0c;总结出这套适合大多数…

2026/7/17 0:00:01阅读更多 →
从竞赛代码到桌面工具:让 SuperADD 与 SubspaceAD 真正跑进自己的图像

从竞赛代码到桌面工具:让 SuperADD 与 SubspaceAD 真正跑进自己的图像

在异常检测领域,很多优秀算法最初都是以研究代码的形式发布的。它们能够在固定测试集上复现实验结果,却不一定能被普通用户直接拿来测试自己的图片。尤其是最近很多算法仅提供在固定测试集的测试环境,而gradio的demo演示也不会提供。 对工程应用和在自己的图片上进行测试来…

2026/7/17 0:00:01阅读更多 →
WinRAR高效配置指南:从基础安装到高级压缩实战

WinRAR高效配置指南:从基础安装到高级压缩实战

前几天帮同事处理一个客户发来的压缩包&#xff0c;解压时系统自带的工具弹出一串乱码&#xff0c;换用 WinRAR 却顺利打开了。这种看似简单的场景&#xff0c;恰恰暴露了不同压缩工具在处理非标准编码、分卷压缩或加密文件时的差异。WinRAR 作为一款老牌工具&#xff0c;真正价…

2026/7/17 0:00:01阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时&#xff0c;发现推理速度只有可怜的 1-2 FPS&#xff0c;而别人的演示视频却能跑到 30 FPS 以上&#xff0c;那么问题很可能不在模型本身&#xff0c;而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后&#xff0c;会直接使用官方示例…

2026/7/16 20:13:14阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一&#xff1a;为什么你需要了解 Coze 和 Dify&#xff1f;如果你对 AI 应用开发感兴趣&#xff0c;但一看到“大模型”、“智能体”、“工作流”这些词就头疼&#xff0c;觉得门槛太高&#xff0c;那这篇文章就是为你准备的。很多开发者&#xff0c;包括我自己&#…

2026/7/16 8:58:42阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会&#xff1a;配图一直是个让人头疼的问题。2026年&#xff0c;AI生图工具已经非常成熟了&#xff0c;但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1&#xff1a;速度之王2026年6月11日&#xff0c…

2026/7/16 17:10:26阅读更多 →