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

news2025/1/21 3:00:12

在这里插入图片描述

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

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 = stride
        self.padding = padding
    def 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 = rs
            else:
                output += rs
        return output
    # padding和rs的宽高计算全部基于rs_h = (h - k + 2p)//s + 1
    def compute_conv(self, fm, kernel):
        [h, w] = fm.shape
        [k, _] = kernel.shape

        if self.padding == 'SAME': # 知道rs_hw,求pad_hw
            rs_h = h // self.stride
            rs_w = w // self.stride
            pad_h = (self.stride * (rs_h - 1) + k - h) // 2
            pad_w = (self.stride * (rs_w - 1) + k - w) // 2
        elif self.padding == 'VALID': # 知道pad_hw,求rs
            pad_h = 0
            pad_w = 0
            rs_h = (h - k) // self.stride + 1
            rs_w = (w - k) // self.stride + 1
        elif self.padding == 'FULL': # 知道pad_hw,求rs_hw
            pad_h = k - 1
            pad_w = k - 1
            rs_h = (h + 2 * pad_h - k) // self.stride + 1
            rs_w = (w + 2 * pad_w - k) // self.stride + 1
        padding_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 padding
        rs = 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 rs

if __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 = stride
        self.padding = padding
    def 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) // 2
            pad_w = (self.stride * (w - 1) + k - w) // 2
            rs_h = h
            rs_w = w
        elif self.padding == 'VALID':
            pad_h = 0
            pad_w = 0
            rs_h = (h - k) // self.stride + 1
            rs_w = (w - k) // self.stride + 1
        elif self.padding == 'FULL':
            pad_h = k - 1
            pad_w = k - 1
            rs_h = (h + 2 * pad_h - k) // self.stride + 1
            rs_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.weights
        mat_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 rs

if __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、面试基础–深度学习 卷积及其代码实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1446989.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 音视频原理

本篇文章我们来描述一下音视频原理 音视频录制原理: 下面是对这张思维导图的介绍 摄像头部分: 麦克风采集声音 摄像头采集画面 摄像头采集回来的数据可以用RGB也可以用YUV来表示 图像帧帧率 一秒能处理多少张图像 图像处理 :调亮度 图像帧队列 :意思是将数据取…

算法刷题:盛水最多的容器

盛水最多的容器 .习题链接题目题目解析算法原理我的答案 . 习题链接 盛水最多的容器 题目 题目解析 VH*W h为左右两边低的一边,w为左右两边之间的距离 算法原理 定义两个指针 left0,rightn-1; left从左往右对数组进行遍历,right从右往左进行遍历 遍历的过程中,每一次都需要…

10块钱的AI数字人!

🔥10元的小报童写‬出了500元的‬价值! 所以就爆了🔥! 不到两天‬就冲到‬了近3000人! 太恐怖了,大佬的微‬信都被加‬爆了, 10块钱还配套‬了一个‬群 就是这‬么牛逼! 可能‬…

有状态DHCPv6快速模式配置及EUI-64介绍

正文共:1024 字 15 图,预估阅读时间:3 分钟 我们现在已经熟悉了IPv6的地址架构(IPv6地址架构一本通),掌握了IPv6地址的手工配置方式(IPv6从入门到精通)和DHCPv6有状态地址配置&#…

svg基础(八)滤镜-feTurbulence(湍流)

feTurbulence:湍流滤镜 湍流滤镜,不稳定气流,能够实现半透明的烟熏或波状图像。 通常用于实现一些特殊的纹理。滤镜利用 Perlin 噪声函数创建了一个图像。噪声在模拟云雾效果时非常有用,能产生非常复杂的质感,利用它可…

算法-----高精度算法1(高精度加法,高精度减法)(详解)

什么是高精度算法? 高精度的意思就是他得名字----高的精度,简单说就是位数很大,而高精度算法就是将这些高精度数(位数很大在几百几千几万位的数叫高精度数)通过计算机的型式模拟出来结果。 为什么要用高精度算法&…

【运维测试】移动测试自动化知识总结第1篇:移动端测试介绍(md文档已分享)

本系列文章md笔记(已分享)主要讨论移动测试相关知识。主要知识点包括:移动测试分类及android环境搭建,adb常用命令,appium环境搭建及使用,pytest框架学习,PO模式,数据驱动&#xff0…

力扣精选算法100道——【模板】前缀和(一维)

【模板】前缀和_牛客题霸_牛客网 (nowcoder.com) 目录 🚩了解题意 🚩算法原理 🎈设定下标为1开始 🎈取值的范围 🚩实现代码 🚩了解题意 第一行的3和2,3代表行数,2代表q次查询(…

Codeforces Round 303 (Div. 2)C. Kefa and Park(DFS、实现)

文章目录 题面链接题意题解代码总结 题面 链接 C. Kefa and Park 题意 求叶节点数量,叶节点满足,从根节点到叶节点的路径上最长连续1的长度小于m 题解 这道题目主要是实现,当不满足条件时直接返回。 到达叶节点后统计答案,用…

结构体,位段问题

结构体,位段问题 一、结构体二、结构体内存分配问题三、存在内存对齐的原因四、结构体传参问题五、结构体实现位段 一、结构体 1.简单说说结构体是什么?  结构体就是把不同的数据类型整合到一起,组成的一个数据类型!!  2.结构体的创建和初始化 struct Stu {char…

【复现】Supabase后端服务 SQL注入漏洞_48

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一: 四.修复建议: 五. 搜索语法: 六.免责声明 一.概述 Supabase是什么 Supabase将自己定位为Firebase的开源替代品,提供了一套工具来帮助开发者构建web或移动应用程序。 Sup…

【MySQL探索之旅】MySQL数据库下载及安装教程

📚博客主页:爱敲代码的小杨. ✨专栏:《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️ 🙏小杨水平有…

1921:【02NOIP普及组】过河卒

1921:【02NOIP普及组】过河卒 【题目描述】 如图,A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。 同时在棋盘上的任一点有一个对方的马(如上图的C点),该马所在的点和所有…

leetcode(双指针)11.盛最多水的容器(C++详细解释)DAY9

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回…

CTFshow web(php命令执行 68-71)

web68 还是那句话,没看到flag在哪,那就优先找到flag位置 这里cvar_dump(scandir("/")); 直接输出根目录的位置,然后查看源代码,发现flag位置为flag.txt 知道flag在根目录下面的flag.txt直接访问就好了 cinclude(/flag…

【C++ 02】类和对象 1:初识类和对象

文章目录 🌈 Ⅰ 面向对象介绍🌈 Ⅱ 类的引入🌈 Ⅲ 类的定义格式1. 声明和定义不分离2. 声明和定义分离 🌈 Ⅳ 类的访问限定符🌈 Ⅴ 类的作用域🌈 Ⅵ 类的实例化🌈 Ⅶ this 指针 🌈 Ⅰ…

17 ABCD数码管显示与动态扫描原理

1. 驱动八位数码管循环点亮 1.1 数码管结构图 数码管有两种结构,共阴极和共阳极,ACX720板上的是共阳极数码管,低电平点亮。 1.2 三位数码管等效电路图 为了节约I/O接口,各个数码管的各段发光管被连在一起,通过sel端…

【c++基础】国王的魔镜

说明 国王有一个魔镜,可以把任何接触镜面的东西变成原来的两倍——只是,因为是镜子嘛,增加的那部分是反的。 比如一条项链,我们用AB来表示,不同的字母表示不同颜色的珍珠。如果把B端接触镜面的话,魔镜会把…

LeetCode.145. 二叉树的后序遍历

题目 145. 二叉树的后序遍历 分析 上篇文章我们讲了前序遍历,这道题目是后序遍历。 首先要知道二叉树的后序遍历是什么?【左 右 根】 然后利用递归的思想,就可以得到这道题的答案,任何的递归都可以采用 栈 的结构来实现&#…

Vulnhub靶机:DC2

一、介绍 运行环境:Virtualbox 攻击机:kali(10.0.2.15) 靶机:DC2(10.0.2.55) 目标:获取靶机root权限和flag 靶机下载地址:https://www.vulnhub.com/entry/dc-2,311/…