从零手写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 的结构,搭出神经网络来了。

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

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

相关文章
|
物联网 Python 网络性能优化
在ESP32上使用umqtt接入阿里云物联网平台
网络上已经有使用python 模拟设备接入阿里云,手里刚好有块ESP32的小板子,在上面跑过Alios Things,然后接入阿里云。但是终究感觉麻烦,有的时候只是做一个很简单的应用,跑os确实不太方便。
12817 1
|
5月前
|
人工智能
阿里云OPC专属套餐正式发布!业界首个OPC全生命周期套餐,三大套餐将创业所需AI能力+云产品一站打包!
阿里云首发OPC专属套餐,覆盖Starter/Lite/Pro三档,一站式集成AI与云产品,支持一人公司从验证、增长到规模化全周期发展,低成本试错、高稳架构、全球加速,助你轻松创业!
|
7月前
|
人工智能 API 网络安全
神级组合!阿里云部署 OpenClaw X 飞书 CLI,开启 Agent 基建新时代!(附免费使用6个月服务器)
2026年,AI 与自动化基础设施进入全面落地阶段,各类厂商纷纷开放命令行工具(CLI),标志着软件交互从“为人设计”正式转向“为 AI 设计”。本文以阿里云轻量应用服务器(Lighthouse)为载体,完整呈现**一键部署 OpenClaw、对接飞书 CLI、实现 AI 全自动执行任务**的全流程,让 AI 真正拥有“动手能力”,实现消息自动发送、文献自动整理、知识库自动维护等高频办公场景,真正做到一句话下达指令,AI 全程独立完成。
825 26
|
5月前
|
人工智能 安全 语音技术
别再让 AI 只会聊天了!Hermes Agent 自带 47 个实用工具,一键解锁智能体真正实力
Hermes Agent 内置 47 个实用工具,覆盖网络搜索、终端执行、浏览器自动化、AI 绘图等 8 大类别,可一键启用工具集。本文带你全面掌握智能体工具使用,释放 AI 全能力。
877 0
|
7月前
|
Web App开发 人工智能 持续交付
OpenClaw一键部署安装代码实现过程详解
OpenClaw一键部署脚本详解:通过.bat自动化实现Python环境检测、pip升级、Playwright安装及Chromium浏览器下载,支持国内镜像加速。用户双击即完成全环境配置,大幅降低AI Agent部署门槛,提升效率与稳定性。(238字)
|
10月前
|
Java 程序员 微服务
【RuoYi-SpringBoot3-Pro】:热更新,设置一次,效率翻倍
【RuoYi-SpringBoot3-Pro】提升开发效率必备:热更新配置指南!告别手动重启,详解Spring Boot DevTools与JRebel插件的使用与对比,实现代码修改即时生效,大幅提升开发体验。免费+高效方案一键掌握!(239字)
584 3
【RuoYi-SpringBoot3-Pro】:热更新,设置一次,效率翻倍
|
存储 缓存 固态存储
固态硬盘为什么会出现故障?
近年来,固态硬盘(SSD)因速度快广受用户青睐,但使用中也出现故障频发的问题,如开机异常、数据丢失、系统卡顿等。本文解析SSD故障原因,包括寿命限制、主控设计缺陷、电压波动、固件问题等,并提供数据抢救方法与延长SSD寿命的实用技巧,助你避免数据丢失风险。
|
12月前
|
消息中间件 编解码 Kafka
企业微信ipad协议:Silk语音流转码为PCM的实时管线设计企业微信ipad协议:Silk语音流转码为PCM的实时管线设计企业微信ipad协议:Silk语音流转码为PCM的实时管线设计企业微信ipad协议:Silk语音流转码为PCM的实时管线设计
企业微信iPad协议中,语音消息采用Silk V3编码。为实现低延迟ASR,网关需实时将流式Silk解码为PCM并推送Kafka。通过内存池缓存解码器、无锁环形队列传输、逐块分窗处理,实现零拷贝、P99延迟≤10ms。单核支持200路并发,为实时字幕与语义分析提供高效数据底座。(238字)
1767 0
|
人工智能 自动驾驶 数据可视化
魔搭社区模型速递(3.30-4.12)
魔搭ModelScope本期社区进展:新增1911个模型,297个数据集,113个创新应用, 10篇内容。
916 3
魔搭社区模型速递(3.30-4.12)
|
监控 搜索推荐 调度
【内测征集】LarkVR 播控系统上新:VR 应用一站式专业播控与管理工具
Paraverse 平行云专为 VR 应用场景运营方打造的一站式专业播控与管理工具LarkVR播控系统,基于LarkXR实时云渲染平台方案,整合VR空间运营、播控管理、设备运营等场景衍生的VR管理工具,旨在帮助其高效管理设备和运营自己的业务,提升整体运营效率与用户体验。 VR 运营者不再局限于单一终端的串流业务系统,通过 LarkVR播控系统,一键部署前后端业务系统,运营人员使用 pad即可完成快捷操作。支持本地内网部署,支持个性化需求定制接入,兼容市面上主流的各品牌 VR 一体机。
596 12

热门文章

最新文章