当前位置: 首页 > news >正文

机器学习:卷积介绍及代码实现卷积操作

在这里插入图片描述

传统卷积运算是将卷积核以滑动窗口的方式在输入图上滑动,当前窗口内对应元素相乘然后求和得到结果,一个窗口一个结果。相乘然后求和恰好也是向量内积的计算方式,所以可以将每个窗口内的元素拉成向量,通过向量内积进行运算,多个窗口的向量放在一起就成了矩阵,每个卷积核也拉成向量,多个卷积核的向量排在一起也成了矩阵,于是,卷积运算转化成了矩阵乘法运算。下图很好地演示了矩阵乘法的运算过程:

im2col

将卷积运算转化为矩阵乘法,从乘法和加法的运算次数上看,两者没什么差别,但是转化成矩阵后,运算时需要的数据被存在连续的内存上,这样访问速度大大提升(cache),同时,矩阵乘法有很多库提供了高效的实现方法,像BLAS、MKL等,转化成矩阵运算后可以通过这些库进行加速。

缺点呢?这是一种空间换时间的方法,消耗了更多的内存——转化的过程中数据被冗余存储。

代码实现

太久没写python代码,面试的时候居然想用c++来实现,其实肯定能实现,但是比起使用python复杂太多了,所以这里使用python中的numpy来实现。

一、滑动窗口版本实现(这个好理解)

import numpy as np# 为了简化运算,默认batch_size = 1
class my_conv(object):def __init__(self, input_data, weight_data, stride, padding = 'SAME'):self.input = np.asarray(input_data, np.float32)self.weights = np.asarray(weight_data, np.float32)self.stride = strideself.padding = paddingdef my_conv2d(self):"""self.input: c * h * w  # 输入的数据格式self.weights: c * h * w"""[c, h, w] = self.input.shape[kc, k, _] = self.weights.shape  # 这里默认卷积核的长宽相等assert c == kc  # 如果输入的channel与卷积核的channel不一致即报错output = []# 分通道卷积,最后再加起来for i in range(c):  f_map = self.input[i]kernel = self.weights[i]rs = self.compute_conv(f_map, kernel)if output == []:output = rselse:output += rsreturn output# padding和rs的宽高计算全部基于rs_h = (h - k + 2p)//s + 1def compute_conv(self, fm, kernel):[h, w] = fm.shape[k, _] = kernel.shapeif self.padding == 'SAME': # 知道rs_hw,求pad_hwrs_h = h // self.striders_w = w // self.stridepad_h = (self.stride * (rs_h - 1) + k - h) // 2pad_w = (self.stride * (rs_w - 1) + k - w) // 2elif self.padding == 'VALID': # 知道pad_hw,求rspad_h = 0pad_w = 0rs_h = (h - k) // self.stride + 1rs_w = (w - k) // self.stride + 1elif self.padding == 'FULL': # 知道pad_hw,求rs_hwpad_h = k - 1pad_w = k - 1rs_h = (h + 2 * pad_h - k) // self.stride + 1rs_w = (w + 2 * pad_w - k) // self.stride + 1padding_fm = np.zeros([h + 2 * pad_h, w + 2 * pad_w], np.float32)padding_fm[pad_h:pad_h+h, pad_w:pad_w+w] = fm  # 完成对fm的zeros paddingrs = np.zeros([rs_h, rs_w], np.float32)for i in range(rs_h):for j in range(rs_w):roi = padding_fm[i*self.stride:(i*self.stride + k), j*self.stride:(j*self.stride + k)]rs[i, j] = np.sum(roi * kernel) # np.asarray格式下的 * 是对应元素相乘return rsif __name__=='__main__':input_data = [[[1, 0, 1, 2, 1],[0, 2, 1, 0, 1],[1, 1, 0, 2, 0],[2, 2, 1, 1, 0],[2, 0, 1, 2, 0],],[[2, 0, 2, 1, 1],[0, 1, 0, 0, 2],[1, 0, 0, 2, 1],[1, 1, 2, 1, 0],[1, 0, 1, 1, 1],],]weight_data = [[[1, 0, 1],[-1, 1, 0],[0, -1, 0],],[[-1, 0, 1],[0, 0, 1],[1, 1, 1],]]conv = my_conv(input_data, weight_data, 1, 'SAME')print(conv.my_conv2d())

二、矩阵乘法版本实现

import numpy as np# 为了简化运算,默认batch_size = 1
class my_conv(object):def __init__(self, input_data, weight_data, stride, padding = 'SAME'):self.input = np.asarray(input_data, np.float32)self.weights = np.asarray(weight_data, np.float32)self.stride = strideself.padding = paddingdef my_conv2d(self):"""self.input: c * h * w  # 输入的数据格式self.weights: c * h * w"""[c, h, w] = self.input.shape[kc, k, _] = self.weights.shape  # 这里默认卷积核的长宽相等assert c == kc  # 如果输入的channel与卷积核的channel不一致即报错# rs_h与rs_w为最后输出的feature map的高与宽if self.padding == 'SAME':pad_h = (self.stride * (h - 1) + k - h) // 2pad_w = (self.stride * (w - 1) + k - w) // 2rs_h = hrs_w = welif self.padding == 'VALID':pad_h = 0pad_w = 0rs_h = (h - k) // self.stride + 1rs_w = (w - k) // self.stride + 1elif self.padding == 'FULL':pad_h = k - 1pad_w = k - 1rs_h = (h + 2 * pad_h - k) // self.stride + 1rs_w = (w + 2 * pad_w - k) // self.stride + 1# 对输入进行zeros padding,注意padding后依然是三维的pad_fm = np.zeros([c, h+2*pad_h, w+2*pad_w], np.float32)pad_fm[:, pad_h:pad_h+h, pad_w:pad_w+w] = self.input# 将输入和卷积核转化为矩阵相乘的规格mat_fm = np.zeros([rs_h*rs_w, kc*k*k], np.float32)mat_kernel = self.weightsmat_kernel.shape = (kc*k*k, 1) # 转化为列向量row = 0   for i in range(rs_h):for j in range(rs_w):roi = pad_fm[:, i*self.stride:(i*self.stride+k), j*self.stride:(j*self.stride+k)]mat_fm[row] = roi.flatten()  # 将roi扁平化,即变为行向量row += 1# 卷积的矩阵乘法实现rs = np.dot(mat_fm, mat_kernel).reshape(rs_h, rs_w) return rsif __name__=='__main__':input_data = [[[1, 0, 1, 2, 1],[0, 2, 1, 0, 1],[1, 1, 0, 2, 0],[2, 2, 1, 1, 0],[2, 0, 1, 2, 0],],[[2, 0, 2, 1, 1],[0, 1, 0, 0, 2],[1, 0, 0, 2, 1],[1, 1, 2, 1, 0],[1, 0, 1, 1, 1],],]weight_data = [[[1, 0, 1],[-1, 1, 0],[0, -1, 0],],[[-1, 0, 1],[0, 0, 1],[1, 1, 1],]]conv = my_conv(input_data, weight_data, 1, 'SAME')print(conv.my_conv2d())

参考资料

1、im2col:将卷积运算转为矩阵相乘
2、面试基础–深度学习 卷积及其代码实现

相关文章:

  • 华为第二批难题五:AI技术提升六面体网格生成自动化问题
  • (二)【Jmeter】专栏实战项目靶场drupal部署
  • 炫酷3D按钮
  • λ-矩阵知识点
  • 酷开科技荣获消费者服务平台黑猫投诉“消费者服务之星”称号
  • Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六
  • 《UE5_C++多人TPS完整教程》学习笔记4 ——《P5 局域网连接(LAN Connection)》
  • re:从0开始的CSS之旅 13. 文档流
  • VueCLI核心知识综合案例TodoList
  • Android 自定义BaseFragment
  • Panalog 日志审计系统 sessiptbl.php 前台RCE漏洞复现
  • 蓝桥杯(Web大学组)2022国赛真题:水果消消乐
  • Python学习之路-爬虫进阶:爬虫框架雏形
  • 构建智慧交通平台:架构设计与实现
  • Python爬虫——解析库安装(1)
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • Apache Zeppelin在Apache Trafodion上的可视化
  • golang中接口赋值与方法集
  • Java超时控制的实现
  • java第三方包学习之lombok
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • Logstash 参考指南(目录)
  • maven工程打包jar以及java jar命令的classpath使用
  • Vim Clutch | 面向脚踏板编程……
  • XML已死 ?
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 一些css基础学习笔记
  • 赢得Docker挑战最佳实践
  • 移动端高清、多屏适配方案
  • ​HTTP与HTTPS:网络通信的安全卫士
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • (3)nginx 配置(nginx.conf)
  • (4)Elastix图像配准:3D图像
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (四)图像的%2线性拉伸
  • (学习日记)2024.02.29:UCOSIII第二节
  • (转) Face-Resources
  • (转) 深度模型优化性能 调参
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • .aanva
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET DataGridView数据绑定说明
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET企业级应用架构设计系列之开场白
  • .net实现头像缩放截取功能 -----转载自accp教程网
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?
  • /bin、/sbin、/usr/bin、/usr/sbin
  • @AliasFor注解
  • @property @synthesize @dynamic 及相关属性作用探究
  • @Validated和@Valid校验参数区别
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?