CNN与ResNet源码拆解:从普通卷积到残差块的深度学习实践

发布时间:2026/10/19 14:54:34

CNN与ResNet源码拆解:从普通卷积到残差块的深度学习实践 简介面向深度学习初学者和进阶者这份Python源码资源聚焦CNN与ResNet的简易搭建适合课程实验、毕业设计或算法入门时边读代码边理解模型原理。资源用代码直接回答了“ResNet是CNN吗”的问题ResNet并非独立于CNN的新结构而是在原有卷积网络上引入残差块与跳跃连接的改进版本有效缓解了深层网络训练时的梯度消失与退化问题。资源共24个文件具体为5个Python脚本、6个H5数据集、8个XML配置、2个IML模块文件和3个PYC缓存压缩包约25MB目录按“第一周基础CNN”和“第二周ResNet”清晰划分便于循序渐进学习。第一周代码覆盖卷积层、池化层、全连接层和激活函数的搭建第二周集中展示残差块、跳跃连接与批量归一化的实现从数据加载、模型定义到训练评估均有对应代码配合可复用的工具模块和测试脚本可完整跑通训练与验证流程。已有1099人学习下载适合需要对照源码动手复现的读者完成项目后能掌握从零构造CNN和ResNet的方法并了解ResNet18等常见变体的实际用法。1. ResNet到底是不是CNN拆完这份Python源码你就别再纠结了ResNet残差网络不是CNN的另一种形式它就是CNN的一种扩展和深化版本。很多初学者在资源里翻到“简易CNN和ResNet搭建”第一反应是“ResNet是不是要单独学一套网络”实际拆完这份Python源码你会发现ResNet只是把普通CNN的一层改成了“残差块”主体结构依然是卷积、池化、全连接那一套。这份资源分成第一周和第二周前一周教你搭基础CNN后一周在同样的数据上改成ResNet正好能直观感受“加了几条跳跃连接”带来的变化。适合刚看完深度学习理论、想动手敲代码的人也适合要复现课程作业但卡在数据加载和维度对不齐的从业者。2. 第一周源码拆解从零搭CNN用手势数据跑通全流程2.1 文件清单和数据格式先弄清H5文件里装了什么打开压缩包第一周目录下的核心文件是cnn_utils.py、test.py和datasets里的train_signs.h5、test_signs.h5。这里最容易踩的坑是直接拿h5py去读键名结果不知道X_train和Y_train是不是自己想要的形状。我一般会先写一段探针代码import h5py import numpy as np with h5py.File(datasets/train_signs.h5, r) as f: for key in f.keys(): dataset f[key] print(key, dataset.shape, dataset.dtype)输出里通常会有X_train、Y_train、X_test、Y_test这几个键。X_train的形状是(m, height, width, channels)也就是样本数、图像高、宽、通道数Y_train是(m, )或者 one-hot 后的(m, classes)。这个细节决定了后面所有卷积层的输入维度。如果你看到Y_train是标签索引而不是 one-hot那就需要在cnn_utils.py里补一个convert_to_one_hot的过程。参数说明H5文件是课程里最常见的图像数据集容器通道数在手势数据里通常是3因为它是RGB图片。如果打印出来发现通道数在最后一位就不用动如果你习惯(channels, height, width)的 PyTorch 格式那就要用np.transpose把轴换过来。2.2 cnn_utils.py前向传播、初始化、损失函数是怎么串成一条线的cnn_utils.py在整套代码里扮演“公共工具库”的角色里面通常会封装参数初始化、前向传播、计算损失和反向传播的辅助函数。在TensorFlow 1.x的年代这类课程代码喜欢手写tf.Variable和tf.nn.conv2d但这份资源的写法更像“半框架半手动”既用了框架的卷积算子又自己组织了参数矩阵。我建议你第一步先不看实现直接看函数签名把调用链画出来from cnn_utils import initialize_parameters, forward_propagation, compute_cost # 常见调用方式 parameters initialize_parameters(seed1) # 初始化 W1, b1, W2, b2 AL, caches forward_propagation(X_train, parameters) # 前向传播返回最后一层激活值 cost compute_cost(AL, Y_train) # 交叉熵损失initialize_parameters里最需要关注的是每层卷积的nt滤波器数量和n_h、n_w这些参数与输入图片尺寸直接挂钩。forward_propagation内部通常依次是卷积、批量归一化或激活、池化、再卷积、再池化最后展平接全连接。如果你在这里发现代码里同时存在tf.nn.conv2d和tf.contrib.layers的调用直接忽略contrib部分的警告就行因为在旧版 TensorFlow 里那是官方自带库。逻辑说明前向传播就是让张量按层流动中间每一层都要记住缓存caches因为反向传播要用到。compute_cost计算的是 softmax 交叉熵手势数据集有6类所以输出维度是(6, m)。2.3 test.py超参数怎么设跑通一次训练要改哪几个数字test.py是主训练脚本里面定义了学习率、迭代次数、mini-batch大小。我第一次跑的时候用了默认的学习率 0.009结果 loss 在 1.7 左右震荡上不去下不来。后来把迭代次数从 2000 提到 2500并且换了随机初始化种子才跑到 80% 以上。核心训练循环一般长这样for epoch in range(num_epochs): # 通常设 2000~3000 minibatch_cost 0 num_batches int(m / minibatch_size) # 默认 minibatch_size64 seed seed 1 minibatches random_mini_batches(X_train, Y_train, minibatch_size, seed) for minibatch in minibatches: (minibatch_X, minibatch_Y) minibatch _, temp_cost sess.run([optimizer, cost], feed_dict{ X: minibatch_X, Y: minibatch_Y}) minibatch_cost temp_cost / num_batches if epoch % 100 0: print(Cost after epoch %i: %f % (epoch, minibatch_cost))这里的sess.run是 TensorFlow 1.x 的会话写法如果是 2.x 版本就得改成tf.compat.v1.Session或者整体换成tf.function。我一般会先确认自己的 TensorFlow 版本2.x 直接跑旧代码大概率报AttributeError。最简单的办法是在文件顶部加上tf.compat.v1.disable_eager_execution()并统一替换sess.run的写法。参数说明minibatch_size64是性价比比较高的默认值num_epochs在 CPU 上跑 2000 轮大约需要 10 分钟如果发现网络半天不收敛先降低到 64 的 batch 再观察 loss。学习率超过 0.01 时梯度很容易爆导致 loss 变成 nan这时候把学习率除以 10 重跑。3. 第二周源码拆解ResNet的残差块、跳跃连接和批量归一化到底改在哪3.1 从普通CNN到残差块跳跃连接加在哪一步第二周的resnets_utils.py和kt_utils.py是重头戏。ResNet 的核心残差块在代码上实现并不复杂就是在两层卷积之间插入一条“短路”def residual_block(X, filters, stage, block): # 第一层卷积 conv1 tf.nn.conv2d(X, filters[0], strides[1,1,1,1], paddingSAME) batch_norm1 tf.layers.batch_normalization(conv1, trainingTrue) relu1 tf.nn.relu(batch_norm1) # 第二层卷积 conv2 tf.nn.conv2d(relu1, filters[1], strides[1,1,1,1], paddingSAME) batch_norm2 tf.layers.batch_normalization(conv2, trainingTrue) # 跳跃连接 short_cut X output tf.nn.relu(batch_norm2 short_cut) return output这段代码最直观的变化是最后的 short_cut。普通CNN是output relu(bn(conv2(relu(bn(conv1(X))))))ResNet 则把输入X直接加到第二层卷积的输出上再做 ReLU。这就是“恒等映射”它让网络在反向传播时多了一条梯度通道即使两个卷积层的权重变得很小梯度也能顺着短路传回去。参数说明strides在残差块里通常保持[1,1,1,1]只有当尺寸减半时才设为[1,2,2,1]。paddingSAME保证输出和输入同尺寸这样跳跃连接才能直接相加。如果你发现相加时维度对不上要么改 padding要么在跳跃连接上再加一个1x1卷积做投影。3.2 resnets_utils.py 里那些函数加载数据、随机初始化、批量归一化resnets_utils.py除了包含残差块定义通常还带load_dataset、random_mini_batches和convert_to_one_hot。相比第一周这里多了一个kt_utils.py它是专门给“快乐数据集”train_happy.h5用的加载器。注意这两个文件的函数名有重复导入时要重命名import resnets_utils as ru import kt_utils as ku X_train_happy, Y_train_happy ku.load_happy_dataset() X_train_signs, Y_train_signs ru.load_signs_dataset()逻辑说明train_happy.h5是一个二分类任务快乐/不快乐train_signs.h5是六分类手势。两套数据在Y的形状上有区别二分类的Y可能是(m,1)的 0/1 标签六分类则要做 one-hot。如果你直接把load_signs_dataset用在快乐数据上最后全连接层的节点数就不匹配报错信息是logits and labels must have the same first dimension。批量归一化batch normalization在tf.layers.batch_normalization里默认对每个通道做归一化训练时必须让trainingTrue否则均值方差用的是滑动平均训练前期会非常不稳定。这也是很多人发现 ResNet 在验证集上 loss 下不去的原因之一。3.3 快乐数据集和手势数据集两类任务在代码里的差异把两个数据集放在同一套 ResNet 代码里跑最能看出问题的部分是输出层。快乐数据集只有2类手势数据集有6类。主脚本里test.py开头通常有类似这样的配置classes 6 # 手势数据集 # classes 2 # 快乐数据集改成2并重新初始化参数如果你只是改了classes但忘了重新初始化parameters训练会继续沿用上一轮的权重只不过最后一层的维度还是6tf.nn.softmax_cross_entropy_with_logits会立刻报维度错误。正确的做法是在切换数据集后重新执行参数初始化并且确认convert_to_one_hot的类别参数同步。另外train_happy.h5的图片尺寸通常比手势数据小预处理的缩放尺寸可能不同。resnets_utils.py里可能写死了RESHAPE (64, 64, 3)如果换成 32x32 的数据需要同步修改X tf.placeholder(tf.float32, shape[None, height, width, channels])里的占位符尺寸否则 CNN 只能吃固定尺寸的输入。4. 避坑指南CNN与ResNet训练中五个高频翻车点4.1 现象tf.global_variables_initializer()之后直接跑报Variable conv1/W does not exist原因TensorFlow 1.x 和 2.x 混用或者批量归一化层的training参数没有正确传入导致tf.layers.batch_normalization里的变量在初始化时被跳过。解决在训练循环里加上update_ops tf.get_collection(tf.GraphKeys.UPDATE_OPS)然后每次sess.run优化器时把update_ops一起跑with tf.control_dependencies(update_ops): train_op optimizer.minimize(cost)这能保证批量归一化的均值方差在每次训练时都被更新。4.2 现象ResNet 训练 loss 不降甚至在第三个 epoch 后变成nan原因学习率太高或者残差块里的跳跃连接直接相加导致数值爆炸。我试过学习率 0.009两个卷积层之后batch_norm2的数值范围在[-3, 3]再加上short_cut的原始像素范围[0, 255]直接冲爆了。解决学习率降到0.001同时把输入数据除以255归一化到[0,1]。如果跳连相加后的数值仍然过大可以在跳连后面再接一个批量归一化但最简单的是先归一化输入。4.3 现象残差块报维度错误ValueError: Dimensions must be equal原因两层卷积的步长不一致或者padding用了VALID导致输出尺寸比输入小跳连的short_cut无法直接相加。解决检查每个卷积层的strides和padding。如果只有第一层卷积用了stride2来降采样那short_cut也要跟着降采样。常见做法是在跳跃连接上添加一个1x1卷积strides设为[1,2,2,1]这样两边尺寸一致。代码块里这样补short_cut tf.layers.conv2d(X, filters[1], kernel_size1, strides2, paddingSAME)4.4 现象验证集准确率很高但测试集一塌糊涂原因典型的过拟合。手势训练集样本不多如果迭代次数超过 3000网络就开始死记硬背。另外dropout参数可能被注释掉了。解决在test.py里检查是否有keep_prob占位符。如果有在前向传播里把最后一个卷积后的输出传给 dropout如果没有就手动加。更有效的手段是早停每 100 个 epoch 记录一次验证集 loss连续 3 次上升就中断训练。4.5 现象test.py在自己电脑上跑出不同的准确率有时 70% 有时 90%原因numpy.random.seed没有固定导致 mini-batch 划分的随机性和参数初始化每次都变。这不是模型问题是训练过程的随机性暴露了。解决在load_dataset之后立即设置多个随机种子包括np.random.seed(1)、tf.set_random_seed(2)并保持每次运行前先tf.reset_default_graph()。我习惯把种子写成一个常量放在文件顶部运行前检查一次这样复现实验时才不会被“随机准确率”误导。5. 把源码换成自己的数据集三个必须改的配置点5.1 输入尺寸和通道数从H5换到普通图片文件夹这套源码默认从H5文件读取数据但实际项目里更多是从文件夹读图。换数据时第一步是统一图片尺寸和通道数。常见做法是写一个预处理函数import cv2 import glob def load_images_from_folder(folder, size(64, 64)): images [] for file in glob.glob(folder /*.jpg): img cv2.imread(file) img cv2.resize(img, size) # 统一尺寸 img img.astype(np.float32) / 255.0 # 归一化 images.append(img) return np.array(images)参数说明size(64, 64)与源码里RESHAPE保持一致如果你的 GPU 显存小可以用(32, 32)但要记得同步修改占位符和第一层卷积的输入形状。通道数默认为3BGR如果用cv2.imread读出来是(h,w,3)和源码里channels_last一致不用转。如果你用matplotlib.image.imread得到的是 RGB也还是3通道问题不大。第二个要改的是Y标签的编码方式。文件夹通常按子目录名作为类别你需要手动建立一个字典把字符串映射成整数再做 one-hot。这一步很容易漏因为源码里的convert_to_one_hot直接接受整数数组。5.2 类别数改动后全连接层和损失函数要同步普通 CNN 和 ResNet 最后都有一个全连接层输出维度等于类别数。假设你把手势数据换成了猫狗二分类那么classes 2 # 最后一层全连接 Z3 tf.contrib.layers.fully_connected(P2, num_outputsclasses, activation_fnNone) # 损失函数 cost tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logitsZ3, labelsY))如果你发现Z3的维度还是6那说明你只改了一个文件里的数字另一个文件里的num_outputs还是写死的。我强烈建议用全局搜索在 IDE 里搜num_outputs把所有出现的位置全列出来逐个确认。另外Y的 one-hot 深度也要改成2否则softmax_cross_entropy会报标签维度不匹配。5.3 训练轮数和批量大小小数据集上怎么调小数据集几百到几千张图最容易出现过拟合。批量大小不建议太小否则梯度噪声大loss 曲线像心电图也不建议太大否则一个 epoch 只更新几次参数收敛慢。我一般先固定minibatch_size32然后观察 loss 曲线。如果 loss 在 1.0 左右徘徊超过 200 轮就把学习率从0.001往上调一点到0.005。训练轮数方面先不急着跑几千轮用 500 轮做“冒烟测试”。如果 500 轮内准确率还没超过 60%说明网络结构或数据预处理有问题调大轮数只是浪费时间。等冒烟测试通过再逐步加到 2000~3000 轮。源码里那个 2500 轮是手势数据的经验值换数据后直接照搬容易摔跤。最后记得在测试集上评估前关掉 dropout 和批量归一化的训练模式。batch_normalization需要trainingFalse才能用累计均值否则测试结果会随机波动。这个细节点在源码里经常是keep_prob1和is_trainingFalse同时出现漏掉一个都白搭。6. 验证残差连接是否真的有效一个只需要改一行代码的实验读完前面几章你可能会想“加上那条跳跃连接之后真能解决梯度消失吗”我建议你做一个极简实验把残差块里的output tf.nn.relu(batch_norm2 short_cut)改成output tf.nn.relu(batch_norm2)其他什么都不动然后分别打印两个版本的梯度范数。具体做法如下。在test.py里找到反向传播的梯度计算加上一行调试输出# 假设 optimizer 已经定义了取第一个卷积层的权重梯度 grad tf.gradients(cost, parameters[W1])[0] grad_norm tf.norm(grad) # 在训练循环里每100轮打印一次 if epoch % 100 0: gnorm sess.run(grad_norm, feed_dict{X: minibatch_X, Y: minibatch_Y}) print(Epoch %d, grad_norm: %.6f % (epoch, gnorm))你会观察到没有残差连接时grad_norm在 500 轮以后快速衰减到1e-6甚至更小而带残差连接时grad_norm能稳定在1e-2到1e-3区间。这个数量级差距就是“恒等映射”带来的梯度高速公路。我建议你顺便把中间层的激活输出也打印一下对比标准差你会发现残差网络的每层激活值分布更均匀不会出现某层激活值全为0的“死神经元”现象。这个实验不需要新数据集手头的train_signs.h5就能做只是要把模型换成两个版本各跑一遍。我在实际调参过程中每次改动网络结构后都会跑一遍这个梯度对比因为单纯看 loss 曲线很难分辨是网络变强了还是学习率刚好碰巧合适。梯度范数能更早暴露问题如果加了残差之后梯度范数反而变小说明跳跃连接的维度投影写错了。从那以后我每次搭建或改造 CNN 结构都会强制走一遍“梯度范数对比”的流程先固定一个数据子集跑 100 轮分别记录带跳连和不带跳连的梯度范数再决定要不要保留这个结构设计。这套代码里的 ResNet 部分就是最好的实验台改一行、跑一轮、看一条曲线你对残差的直觉会比看十篇文章都准。希望你能亲手拆一遍这份源码把第一章开始时那个“ResNet是不是CNN”的问题用实验数据回答给自己。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/19 14:54:34

SpringBoot+Vue实战:大学生创新创业项目管理系统全栈开发指南

做“大学生创新创业项目管理系统”这种题目,算是Java全栈方向里非常经典的一类实战项目。它是典型的业务管理系统,数据模型不算复杂,业务链路却足够长:申报、审核、立项、中期检查、结题验收,每个环节都有状态流转&…

2026/10/19 14:54:34

基于Python的单目三维重建:从相机标定到点云生成

简介:面向计算机、通信、人工智能、自动化等专业学生与从业者的单目三维重建毕设项目,基于Python实现完整三维重建流程,涵盖相机标定、特征点匹配、三角测量等关键环节,解决从二维图像恢复三维结构的实践难题。压缩包共12个文件&a…

2026/10/19 14:54:34

从质面到球面:引力位函数与壳层定理详解

“质面的位、球面的位、引力”,这三个词放在一起,乍看像是物理教材的目录碎片,实际上是一条非常完整的逻辑链:把质量从“点”解放到“面”,研究一张质量面产生的引力位;再把平面弯成球壳,看“球…

2026/10/19 15:44:53

大模型领域知识注入与灾难性遗忘:微调平衡实战指南

1. 先搞明白:行业黑话到底在难为谁做模型落地这几年,我越来越觉得“领域知识注入”这件事,十有八九卡在了一个特别尴尬的地方——不是模型不行,而是我们跟模型之间经常“交流不畅”。你以为是数学题做不对,实际是题目里…

2026/10/19 15:44:53

open-code-review 落地指南:用 AST 级扫描拯救流于形式的代码审查

今年年初我们团队做内部复盘,有个数据挺扎心:上半年合并的 80 多个 MR,平均每个 review 时长只有 12 分钟,近三分之一的老哥直接把一个 "LGTM" 和几个表情当审查意见提交。真正把线上搞挂的一次事故,问题代码…

2026/10/19 15:44:53

DeepSeek LeetCode 668. Kth Smallest Number in Multiplication Table Java Implement

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 15:44:53

open-code-review:代码审查自动化工具的设计与实战解析

搞代码审查这么多年,我一直觉得这活儿本身才是团队里最需要被“审查”的一环。每次提到 code review,老程序员想到的是又一轮口水战,新程序员想到的是提心吊胆的“批斗会”,管理层看到的是进度表上永远悬着的那根刺。直到我动手把…

2026/10/19 15:44:53

超长文本语义分析:基于百万token窗口的‘撒网法’全解析

网撒得开,鱼才进得来;问提得准,理才出得去。两者之间,隔着一整片文本的海。最近一直在处理一部约280万字的长篇书稿,做语义层面的整体分析。这个体量刚好超出单个百万token窗口的覆盖范围,只能拆成三个百万…

2026/10/19 15:39:53

28:分离整数的各个数位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/19 0:04:56

悟空开源无代码平台实战:从实体建模到应用设计避坑指南

简介:基于悟空开源无代码平台的多种应用设计源码,面向希望在不编写代码的情况下快速搭建CRM、HRM、财务系统、ERP、SRM等业务应用的企业与技术人员,通过拖放式界面和内置模板即可完成应用定制与部署。压缩包共697个文件,约10.74MB…

2026/10/19 0:04:56

基于.NET9的多租户权限系统:数据权限与动态API落地实践

简介:这份资源是一套面向 .NET 与 Vue 全栈开发者的中台权限管理系统,后端基于 .Net9,前端采用 Vue3 Element Plus,并配套 uniapp uviewui 移动端方案,实现前后端分离。系统覆盖多租户隔离、数据权限、动态 Api、任务…

2026/10/19 0:04:56

场地预约小程序毕设实战:SSM+微信小程序+MySQL数据库设计到避坑

简介:面向毕业设计场景的场地预约小程序完整项目,基于微信小程序SSMMySql技术栈开发,适合计算机相关专业学生作为选题参考、功能扩展或二次开发起点。系统实现场地类型管理、场地管理、预约管理、取消申请与押金退回等功能,分为管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑