从零手写Resnet50实战—手写龟速卷积

简介: 从零手写Resnet50实战—手写龟速卷积

大家好啊,我是董董灿。

这是从零手写Resnet50实战的第3篇文章。

请跟着我的思路,一点点地手动搭建一个可以完成图片分类的神经网络,而且不依赖第三方库,完全自主可控的手写算法。

如对网络中的算法和原理不太熟悉,请移步万字长文解析Resnet50的算法原理。

我的目标是,识别出下面的这张图片是一只猫:

image.png

正文

上一篇文章[我们已经把Resnet50中的所有权值参数都保存到txt中了。

接下来,把上图中猫的图片导入到内存中。

# 使用 Pillar 库来导入图片
# 仅使用该库导入图片
# 不使用该库进行任何其他的计算操作
from PIL import Image

# 读打开图片并读入到 img 中
img = Image.open('../cat.jfif')
# 将图片resize成长宽为(224,224)的图片
img = img.resize((224, 224))

这里说一下为什么要做 resize?

因为 Resnet50 首层卷积,接收的是一个 3 通道的图片数据。如果图片长宽太大,会使得卷积运算量过大,运行速度很慢,这一点在本文后面的实验可以看出来。

而且在试验 Resnet50 中,一个很常见的使用小图做运算的方法,便是将不规则大小的图片 resize 成(224,224)。其中两个 224 分别代表图片的长和宽,3 代表图片有 3 个通道。

在将图片导入到内存中之后,剩下的就是要将图片数据输入到神经网络中。但在此,需要先将核心算法完成,才能搭建成神经网络。

手写算法之——卷积

在Resnet50中,存在 6 种算法,分别是

  1. 卷积(Convolution,Conv)
  2. 批归一化(Batch Normal,BN)
  3. 池化(Pooling)
  4. 激活(Relu)
  5. 加法(Add)
  6. 全连接(Fully Connected, FC)

其中,Conv 和 FC 可以看作一类:都是在某些维度做乘累加计算;Pooling与卷积类似,只不过少了channel维度的累加。

BN是对输入数据的做批归一化操作,算法实现也不太难;而激活和加法就更简单了,属于两行代码就能搞定的算法。

所以,擒贼先擒王,先手写一个卷积算法试试水。

# 使用NHWC的 layout 来计算
# 卷积暂时不考虑 dilation 的存在
# 因为Resnet50中的卷积都不带 dilation 参数
def my_conv2d(img, weight, hi, wi, ci, co, kernel, stride, pad):
  '''
  img:输入图片数据
  weight:权值数据(卷积核)
  hi:输入图片高度-height
  wi:输入图片宽度-width
  ci:输入图片通道-channel,与weight的channel一致
  co:输出图片通道-channle,与weight的个数一致
  kernel:卷积核的大小
  stride:卷积核在输入图片上滑动步长
  pad:输入图片周围补充的pad值
  '''
  # 通过输入参数计算输出图片的长和宽
  # 在 Resnet50 中,卷积核在 h 方向和 w 方向的
  # 尺寸都是一样的,pad 也都是一样的,因此,
  # 这里用一个值来代表。
  ho = (hi + 2 * pad - kernel) // stride + 1
  wo = (wi + 2 * pad - kernel) // stride + 1

  # 将权值数据 reshape 成 co, kh, kw, ci 的形式
  weight = np.array(weight).reshape(co, kernel, kernel, ci)
  # 在输入图片周围补充pad值
  img_pad = np.pad(img, ((pad, pad), (pad, pad), (0, 0)), 'constant')
  # 初始化输出图片
  img_out = np.zeros((ho, wo, co))

  # 下面是卷积计算的核心逻辑
  # 其效果类似于 nn.conv2d
  for co_ in range(co):
    for ho_ in range(ho):
      in_h_origin = ho_ * stride - pad
      for wo_ in range(wo):
        in_w_origin = wo_ * stride - pad
        filter_h_start = max(0, -in_h_origin)
        filter_w_start = max(0, -in_w_origin)
        filter_h_end = min(kernel, hi - in_h_origin)
        filter_w_end = min(kernel, wi - in_w_origin)
        acc = float(0)
        for kh_ in range(filter_h_start, filter_h_end):
          hi_index = in_h_origin + kh_
          for kw_ in range(filter_w_start, filter_w_end):
            wi_index = in_w_origin + kw_
            for ci_ in range(ci):
              in_data = img[hi_index][wi_index][ci_]
              weight_data = weight[co_][kh_][kw_][ci_]
              acc = acc + in_data * weight_data
         img_out[ho_][wo_][co_] = acc
  return img_out

上面是手写的一个卷积算法,采用了最原始的堆叠循环的方式,没有对算法做任何的优化。

之所以这么写,是因为这样可以很清晰地看到卷积的计算过程。

将图片输入给卷积进行运算

在定义完上述卷积运算后,就可以将上一步导入的图片,输入给卷积,计算一下试试水了。

# 读入图片并转换为指定大小
img = Image.open('../cat.jfif')
img = img.resize((224, 224))

# 将Pillow Image对象转换为numpy数组
# data is layout as NHWC
out = np.array(img)

# 这个函数用来从保存的权值文件中读取权值数据
def get_weight_from_file(f):
  k = []
  with open(f, 'r') as f_:
    lines = f_.readlines()
    for l in lines:
      k.append(float(l))
return k

import datetime

# resnet50 第一次卷积的权值保存在项目中的路径
file_name = "../model_parser/dump_txt/resnet50_conv1_weight.txt"
# 将权值加载到内存中,赋值给K
k = get_weight_from_file(file_name)
# 打印当前时间戳
print(datetime.datetime.now())
# 调用手写的卷积进行计算,输出卷积结果
out = my_conv2d(out, k, 224, 224, 3, 64, 7, 2, 3)
# 打印计算完成的时间戳
print(datetime.datetime.now())
# 打印卷积计算结果的 shape
print(out.shape)

上面在调用 my_conv2d 之前,加了两个时间戳打印,看一下这个卷积运算的耗时。

$ 2023-04-13 08:21:20.473301
$ 2023-04-13 08:23:00.855593

从时间戳上可以看到,两个时间戳之间的间隔在1分多钟,说明这个卷积运算消耗了1分多钟,这可能与我用的虚拟机配置很低有关。

不过这种循环堆叠的卷积实现方式,很耗时是真的。

在卷积运算完之后,把这一层的输出的 shape 也打印出来。

$ (112, 112, 64)

可以看到,卷积的输出 shape 为 (112, 112, 64),通道数由输入图片的 3 通道变成了 64 通道,是因为使用了 64 个卷积核。

这里64个通道,实际上可以理解为这一层卷积在原始输入图片的像素之间,抽取出了 64 个特征出来。
至于是什么特征,我也不知道,有可能是猫的鼻子和耳朵。

至于上面说的卷积运算耗时的问题,暂时先不管他。在完成整网的推理,正确识别出来猫之后,我会继续将算法都优化一遍的。

后面还有 Pooling, Bn 算法的手写,写完之后,就可以按照 Resnet50 的结构,搭出神经网络来了。

今天,离识别出来猫,又进了一步。

本文为作者原创,请勿转载,转载请联系作者

相关文章
|
机器学习/深度学习 计算机视觉
用实验数据验证面试题:VGG使用3x3卷积核的优势
用实验数据验证面试题:VGG使用3x3卷积核的优势
494 0
用实验数据验证面试题:VGG使用3x3卷积核的优势
|
6月前
|
机器学习/深度学习 存储 测试技术
【YOLOv10改进-注意力机制】iRMB: 倒置残差移动块 (论文笔记+引入代码)
YOLOv10专栏介绍了融合CNN与Transformer的iRMB模块,用于轻量级模型设计。iRMB在保持高效的同时结合了局部和全局信息处理,减少了资源消耗,提升了移动端性能。在ImageNet等基准上超越SOTA,且在目标检测等任务中表现优秀。代码示例展示了iRMB的实现细节,包括自注意力机制和卷积操作的整合。更多配置信息见相关链接。
|
8月前
|
机器学习/深度学习 存储 测试技术
【YOLOv8改进】iRMB: 倒置残差移动块 (论文笔记+引入代码)
该专栏聚焦YOLO目标检测的创新改进与实战案例,提出了一种融合CNN和Transformer优点的轻量级模型——倒置残差移动块(iRMB)。iRMB旨在平衡参数、运算效率与性能,适用于资源有限的移动端。通过集成多头自注意力和卷积,iRMB在ImageNet-1K等基准上超越SOTA,同时在iPhone14上展现出比EdgeNeXt快2.8-4.0倍的速度。此外,iRMB设计简洁,适用于各种计算机视觉任务,展示出良好的泛化能力。代码示例展示了iRMB模块的实现细节。更多详细信息和配置可在相关链接中找到。
|
7月前
|
机器学习/深度学习 编解码 计算机视觉
【YOLOv8改进】D-LKA Attention:可变形大核注意力 (论文笔记+引入代码)
YOLO目标检测专栏探讨了Transformer在医学图像分割的进展,但计算需求限制了模型的深度和分辨率。为此,提出了可变形大核注意力(D-LKA Attention),它使用大卷积核捕捉上下文信息,通过可变形卷积适应数据模式变化。D-LKA Net结合2D和3D版本的D-LKA Attention,提升了医学分割性能。YOLOv8引入了可变形卷积层以增强目标检测的准确性。相关代码和任务配置可在作者博客找到。
|
7月前
|
机器学习/深度学习 编解码 算法
【YOLOv8改进】Polarized Self-Attention: 极化自注意力 (论文笔记+引入代码)
该专栏专注于YOLO目标检测算法的创新改进和实战应用,包括卷积、主干网络、注意力机制和检测头的改进。作者提出了一种名为极化自注意(PSA)块,结合极化过滤和增强功能,提高像素级回归任务的性能,如关键点估计和分割。PSA通过保持高分辨率和利用通道及空间注意力,减少了信息损失并适应非线性输出分布。实验证明,PSA能提升标准基线和最新技术1-4个百分点。代码示例展示了如何在YOLOv8中实现PSA模块。更多详细信息和配置可在提供的链接中找到。
|
8月前
|
计算机视觉
【YOLOv8改进】 MSDA:多尺度空洞注意力 (论文笔记+引入代码)
该文介绍了DilateFormer,一种新提出的视觉变换器,它在计算效率和关注接受域之间取得平衡。通过分析ViTs,发现浅层的局部性和稀疏性,提出了多尺度扩张注意力(MSDA),用于局部、稀疏的块交互。DilateFormer结合MSDA块和全局多头自注意力块,形成金字塔架构,实现各视觉任务的顶尖性能。与现有最佳模型相比,在ImageNet-1K分类任务上,DilateFormer性能相当但计算成本降低70%,同时在COCO检测/分割和ADE20K语义分割任务上表现优秀。文章还展示了MSDA的创新点,包括多尺度聚合、局部稀疏交互和减少自注意力冗余。此外,
|
8月前
|
机器学习/深度学习 算法 计算机视觉
YOLOv5 | 卷积模块 | 即插即用的可变核卷积AKConv【附代码+小白可上手】
本文介绍了YOLOv5模型的一个改进,即使用AKConv替代标准卷积以提高目标检测效果。AKConv允许卷积核有任意数量的参数和采样形状,增强了对不同目标形状和大小的适应性。教程详细讲解了AKConv的原理,提供了代码实现步骤,包括如何将AKConv添加到YOLOv5中,并给出了相关代码片段。此外,还分享了完整的YOLOv5 AKConv实现代码和GFLOPs计算,鼓励读者动手实践。通过这一改进,网络在保持性能的同时增加了灵活性。
|
7月前
|
计算机视觉 机器学习/深度学习 自然语言处理
【YOLOv8改进】CoTAttention:上下文转换器注意力(论文笔记+引入代码)
本文介绍了YOLO目标检测的创新改进,提出了一种名为Contextual Transformer (CoT)块的新型Transformer模块,用于增强视觉识别能力。CoT块通过3×3卷积编码上下文信息,并结合动态多头注意力矩阵,提高了视觉表示。此外,还提到了Large Separable Kernel Attention (LSKA)模块,它解决了大内核卷积的计算效率问题。CoTNet是基于CoT模块的Transformer风格骨干网络,可替代ResNet中的3×3卷积。CoTAttention类展示了如何在YOLOv8中集成此模块。文章还提供了源码链接和更多实战案例详情。
|
8月前
|
机器学习/深度学习 测试技术 网络架构
【YOLOv8改进】MSCA: 多尺度卷积注意力 (论文笔记+引入代码).md
SegNeXt是提出的一种新的卷积网络架构,专注于语义分割任务,它证明了卷积注意力在编码上下文信息上优于自注意力机制。该模型通过结合深度卷积、多分支深度卷积和1x1逐点卷积实现高效性能提升。在多个基准测试中,SegNeXt超越了现有最佳方法,如在Pascal VOC 2012上达到90.6%的mIoU,参数量仅为EfficientNet-L2 w/ NAS-FPN的1/10。此外,它在ADE20K数据集上的mIoU平均提高了2.0%,同时保持相同的计算量。YOLOv8中引入了名为MSCAAttention的模块,以利用这种多尺度卷积注意力机制。更多详情和配置可参考相关链接。
|
8月前
|
机器学习/深度学习 PyTorch 算法框架/工具
PyTorch深度学习基础之Tensor的变换、拼接、拆分讲解及实战(附源码 超详细必看)
PyTorch深度学习基础之Tensor的变换、拼接、拆分讲解及实战(附源码 超详细必看)
134 0