15.矩阵运算与img2col方式的卷积

news2024/11/25 7:30:26

使用矩阵计算卷积

GEMM算法

矩阵乘法运算(General Matrix Multiplication),形如:

C = A B , A ∈ R m × k , B ∈ R k × n , C ∈ R m × n C = AB, A\in \mathbb{R}^{m\times k},B\in \mathbb{R}^{k\times n},C\in \mathbb{R}^{m\times n} C=AB,ARm×k,BRk×n,CRm×n

矩阵乘法的计算可以写成如下公式:

C m , n = ∑ k = 1 K A m , k B k , n ; m , n , k ∈ R C_{m,n}=\sum_{k=1}^{K}A_{m,k}B_{k,n};m,n,k\in \mathbb{R} Cm,n=k=1KAm,kBk,n;m,n,kR

矩阵的乘法操作可以写成如下图所示,

写成伪代码的形式为:

for i=1 to n
   for j=1 to n    
     c[i][j]=0
     for k=1 to n
         c[i][j] = c[i][j]+a[i][k]*b[k][j]

从上面可以看到使用普通的矩阵乘法运算,其复杂度是 O ( n 3 ) O(n^3) O(n3),这个复杂度相对来说是很高的,当矩阵的维度很大时,运算量会显著增加。

矩阵运算作为基础运算,有大量的科学家对其进行研究,试图降低其运算复杂度。

Strassen’s Matrix Multiplication Algorithm为例,其将矩阵乘法的运算复杂度降低到了 O ( n l o g 2 7 ) ≈ O ( n 2.81 ) O(n^{log 2^7})\approx O(n^{2.81}) O(nlog27)O(n2.81)

这个算法是Strassen1969年发布的论文《Gaussian Elimination is not Optimal.》中提出的,主要是通过矩阵块的操作,减少乘法运算的次数。

将矩阵写成矩阵块的形式:

A = ( A 11 A 12 A 21 A 22 ) , B = ( B 11 B 12 B 21 B 22 ) , C = ( C 11 C 12 C 21 C 22 ) A=\begin{pmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{pmatrix},B=\begin{pmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{pmatrix},C=\begin{pmatrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{pmatrix} A=(A11A21A12A22),B=(B11B21B12B22),C=(C11C21C12C22)

C = A B C=AB C=AB,可得:

C 11 = A 11 B 11 + A 12 B 21 C 12 = A 11 B 12 + A 12 B 22 C 21 = A 21 B 11 + A 22 B 21 C 22 = A 21 B 12 + A 22 B 22 \begin{matrix} C_{11} = A_{11}B_{11} + A_{12}B_{21} \\ C_{12} = A_{11}B_{12} + A_{12}B_{22} \\ C_{21} = A_{21}B_{11} + A_{22}B_{21} \\ C_{22} = A_{21}B_{12} + A_{22}B_{22} \end{matrix} C11=A11B11+A12B21C12=A11B12+A12B22C21=A21B11+A22B21C22=A21B12+A22B22

写成上面的形式,只是把矩阵的乘法写成了块的运算,并没能改变乘法的次数,除了分块可以在硬件上实现并行运算,并没有改变算法本身的复杂度。

Strassen将矩阵的运算写成如下形式,

M 1 = ( A 11 + A 22 ) ( B 11 + B 22 ) M 2 = ( A 21 + A 22 ) B 11 M 3 = A 11 ( B 12 − B 22 ) M 4 = A 22 ( B 21 − B 11 ) M 5 = ( A 11 + A 12 ) B 22 M 6 = ( A 21 − A 11 ) ( B 11 + B 12 ) M 7 = ( A 12 − A 22 ) ( B 21 + B 22 ) \begin{align} M_1 & = (A_{11} + A_{22}) (B_{11} + B_{22}) \\ M_2 & = (A_{21} + A_{22})B_{11} \\ M_3 & = A_{11}(B_{12} − B_{22}) \\ M_4 & = A_{22}(B_{21} − B_{11}) \\ M_5 & = (A_{11} + A_{12})B_{22} \\ M_6 & = (A_{21} − A_{11})(B_{11} + B_{12}) \\ M_7 & = (A_{12} − A_{22})(B_{21} + B_{22})\end{align} M1M2M3M4M5M6M7=(A11+A22)(B11+B22)=(A21+A22)B11=A11(B12B22)=A22(B21B11)=(A11+A12)B22=(A21A11)(B11+B12)=(A12A22)(B21+B22)

C 11 = M 1 + M 4 − M 5 + M 7 C 12 = M 3 + M 5 C 21 = M 2 + M 4 C 22 = M 1 − M 2 + M 3 + M 6 \begin{align} C_{11} &= M_1 + M_4 − M_5 + M_7 \\ C_{12} &= M_3 + M_5 \\ C_{21} &= M_2 + M_4 \\ C_{22} &= M_1 − M_2 + M_3 + M_6 \end{align} C11C12C21C22=M1+M4M5+M7=M3+M5=M2+M4=M1M2+M3+M6

从上面这11个公式中可以看到,一次分治后降低到了7次乘法运算,相较于原来的8次少了一次,可以证明,这样可以将矩阵的乘法复杂度降低到 O ( n l o g 2 7 ) O(n^{log 2^7}) O(nlog27)

在此之后1990CoppersmithWinograd合作提出的Coppersmith–Winograd算法将复杂度降低到了 O ( n 2.376 ) O(n^{2.376}) O(n2.376),2014年斯坦福大学的Virginia Williams发表的论文Multiplying matrices in o(n2.373) time将复杂度降低到了 O ( n 2.373 ) O(n^{2.373}) O(n2.373),其演变过程如下图:

卷积运算

卷积运算的过程就是将卷积核在输入数据上滑动乘积求和的过程,其运算过程可以参考之前的文章

1.常规卷积神经网络

其计算方式第一直观的想法肯定是根据卷积的定义,逐元素相乘求和,写成伪代码的形式如下:

input[C][H][W];
kernels[M][K][K][C];
output[M][H][W];
for h in 1 to H do
    for w in 1 to W do
        for o in 1 to M do
            sum = 0;
            for x in 1 to K do
                for y in 1 to K do
                    for i in 1 to C do
                    sum += input[i][h+y][w+x] * kernels[o][x][y][i];
         output[o][w][h] = sum;

这种方式落到了逐个元素的运算上,不利于对运算做优化,考虑第一部分对矩阵乘法运算优化的介绍,考虑能不能将卷积运算转换成矩阵乘法的运算呢?答案当然是可以。

这正是引入img2col算法的初衷,因为现在的GPU/CPU基本都有对矩阵运算加速的BLAS库(Basic Linear Algebra Subprograms)

img2col的方法将卷积的输入和卷积核都转成了矩阵的形式,最后卷积的运算变成了矩阵的乘法,如此就可以使用矩阵的加速运算了。

如上图,

输入是 W i × H i × C i = 3 × 3 × 3 W_i\times H_i \times C_i=3\times 3 \times 3 Wi×Hi×Ci=3×3×3的图像

卷积核的大小为 C i × C o × k w × k h C_{i}\times C_{o}\times k_{w} \times k_{h} Ci×Co×kw×kh

卷积步长 s t r i d e = 1 stride=1 stride=1

输出的大小为 W o × H o × C o = 2 × 2 × 2 W_o\times H_o \times C_o=2\times 2 \times 2 Wo×Ho×Co=2×2×2

转换成矩阵乘法时,

输入数据转换成矩阵的 s h a p e = ( W o ∗ H o ) × ( k w ∗ k h ∗ C i ) shape=(W_o*H_o)\times(k_w* k_h *C_i) shape=(WoHo)×(kwkhCi)

卷积核转换成矩阵的 s h a p e = ( k w ∗ k h ∗ C i ) × C o shape=(k_w* k_h *C_i)\times C_o shape=(kwkhCi)×Co

使用乘法后得到的卷积运算的输出 s h a p e = ( W o ∗ H o ) × C o shape=(W_o*H_o)\times C_o shape=(WoHo)×Co

可以看到,输出的矩阵,一列表示一幅卷积输出的图像,想比这是为什么称为img2col吧。

使用img2col实现卷积的代码如下:

int Conv2D::img2col(const std::vector<Eigen::MatrixXd> &x, std::vector<Eigen::MatrixXd> &out) const
{
    out.clear();
    assert(x.size() == channel_in_);
    int w_o = x[0].rows();
    int h_o = x[0].cols();
    w_o  = (w_o  - kernel_w_) / stride_ + 1;
    h_o = (h_o - kernel_h_) / stride_ + 1;
    Eigen::MatrixXd x_mat(w_o * h_o, kernel_w_ * kernel_h_ * channel_in_);
    for(int r = 0; r < h_o; ++r) {
        for(size_t c = 0; c < w_o; ++c) {
            for(size_t i = 0; i < channel_in_; ++i) {
                for(size_t h = 0; h < kernel_h_; h++) {
                    for(size_t w = 0; w < kernel_w_; w++) {
                        x_mat(r * w_o + c, i * kernel_h_ * kernel_w_ + h * kernel_w_ + w) 
                        = x[i](r * stride_ + h, c * stride_ + w);
                    }
                }
            }
        }
    }
    auto t1 = std::chrono::steady_clock::now();
    Eigen::MatrixXd res = x_mat * (*kernel_mat_);
    auto t2 = std::chrono::steady_clock::now();
    auto d = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
    std::cout << "img2col duration: " << d << "us" << std::endl;
    for(size_t i = 0; i < channel_out_; i++) {
        auto so = res.col(i);
        Eigen::MatrixXd m(h_o, w_o);
        for(int r = 0; r < h_o; ++r) {
            for(size_t c = 0; c < w_o; ++c) {
                m(r, c) = so(r * h_o + c);
            }
        }
        out.push_back(m);
    }
    return 0;
}

通过实验对比,img2col的方式比普通卷积的要快很多。

raw conv duration: 3530us
img2col duration: 260us

完整的实验代码可以参考仓库:

https://gitee.com/lx_r/basic_cplusplus_examples/tree/master/basic_cv

reference

  • 1.https://stanford.edu/~rezab/classes/cme323/S16/notes/Lecture03/cme323_lec3.pdf
  • 2.https://zhenhuaw.me/blog/2019/gemm-optimization.html
  • 3.https://inria.hal.science/file/index/docid/112631/filename/p1038112283956.pdf

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

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

相关文章

vite4.x+vue3.x中使用装饰器语法,eslint校验不识别@的报错处理方法

在项目中&#xff0c;使用了pre-commit校验代码&#xff0c;eslint校验无法识别,导致一直无法提交代码&#xff0c;查找了资料&#xff0c;eslint版本过低&#xff0c;不能解决现在遇到的问题 最终正确的配置方法&#xff1a; 装饰器配置文件babel.config.js module.exports …

了解应用层

应用层 1. 概述2. 应用程序组织方式2.1 C/S方式2.1 P2P方式 3. 动态主机配置协议DHCP3.1 DHCP工作流程 4. 域名系统DNS4.1 域名结构4.2 域名分类4.3 域名服务器4.3.1 分类 4.4 DNS域名解析过程 5. 文件传输协议FTP5.1 FTP工作流程 6. 电子邮件系统6.1 邮件信息格式6.2 简单邮件…

EtherCAT转TCP/IP网关EtherCAT解决方案

你是否曾经为生产管理系统的数据互联互通问题烦恼过&#xff1f;曾经因为协议不同导致通讯问题而感到困惑&#xff1f;现在&#xff0c;我们迎来了突破性的进展&#xff01; 介绍捷米特JM-TCPIP-ECT&#xff0c;一款自主研发的Ethercat从站功能的通讯网关。它能够连接到Etherc…

12.面板问题

面板问题 html部分 <h1>Lorem ipsum dolor sit, amet consectetur adipisicing.</h1><div class"container"><div class"faq"><div class"title-box"><h3 class"title">Lorem, ipsum dolor.<…

TypeScript 中的常用类型声明大全

文章目录 基本数据类型1.number类型2.String 类型3. Boolean 类型4. undefined 类型5.Null类型6.Symbol类型7.BigInt类型 引用数据类型8.Array 类型9.Object 类型 TS 新增特性数据类型4.联合类型5.字面量类型6.Any 类型7.unknown 类型8.Void 类型9.never 类型10.对象类型12 tup…

基于linux下的高并发服务器开发(第三章)- 3.11 读写锁

读写锁的类型 pthread_rwlock_t int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlo…

macOS 下使用 brew 命令安装 Node.js

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

iOS内存管理--内存的分区

内存分配区域 iOS程序内存分为5个区域 栈区&#xff0c;堆区&#xff0c;BSS&#xff0c;全局变量&#xff0c;代码区 五个区域有两种分配时间 运行时分配&#xff1a;栈区&#xff0c;堆区 栈区&#xff1a;局部变量&#xff0c;函数参数&#xff08;形式参数&#xff09;&…

Hadoop概念学习(无spring集成)

Hadoop 分布式的文件存储系统 三个核心组件 但是现在已经发展到很多组件的s 或者这个图 官网地址: https://hadoop.apache.org 历史 hadoop历史可以看这个: https://zhuanlan.zhihu.com/p/54994736 优点 高可靠性&#xff1a; Hadoop 底层维护多个数据副本&#xff0c;所…

[C初阶]循环和分支语句

目录 if...else语句 ​编辑 易犯错误 打印100以内的奇数 switch...case语句 输出星期 循环语句 对比判断 1.break终止循环 2.continue 读取字符 缓冲区读取 只输出数字字符 for循环 do...while循环 n的阶乘求和 循环实现动态打印 猜数字游戏【总结】 goto ​…

C++的各种用法展示

&#xff43;&#xff0b;&#xff0b;与数学典型算法的结合 阿姆斯特朗数 // A number is called as Armstrong number if sum of cubes of digits of number is // equal to the number itself. // For Example 153 is an Armstrong number because 153 153. #include <…

K8S初级入门系列之十二-计算资源管理

一、前言 K8S集群中着这各类资源&#xff0c;比如计算资源&#xff0c;API资源等&#xff0c;其中最重要的是计算资源&#xff0c;包括CPU&#xff0c;缓存&#xff0c;存储等。管理这些资源就是要在资源创建时进行约束和限制&#xff0c;在运行时监控这些资源的指标&#xff0…

[23] HeadSculpt: Crafting 3D Head Avatars with Text

paper | project 本文主要想解决&#xff1a;1&#xff09;生成图像的不连续问题&#xff1b;2&#xff09;3D修改中的保ID问题。针对第一个问题&#xff0c;本文引入了Landmark-based ControlNet特征图和<back-view>的text embedding&#xff1b;针对第二个问题&#x…

Python 算法基础篇:插入排序和希尔排序

Python 算法基础篇&#xff1a;插入排序和希尔排序 引言 1. 插入排序算法概述2. 插入排序算法实现实例1&#xff1a;插入排序 3. 希尔排序算法概述4. 希尔排序算法实现实例2&#xff1a;希尔排序 5. 插入排序与希尔排序的对比总结 引言 插入排序和希尔排序是两种常用的排序算法…

java后端导出前端展示

效果图 前端代码 exportExcelAll(){window.location.href getBaseUrl() Action/excelDataAll?happenDatethis.params.happenDate;},后端代码 try{Workbook workbooknew XSSFWorkbook();//创建sheetSheet sheet1workbook.createSheet("结果总数拦截记录");//写入…

第一百一十二天学习记录:数据结构与算法基础:循环链表和双向链表以及线性表应用(王卓教学视频)

循环链表 带尾指针循环链表的合并 双向链表 单链表、循环链表和双向链表的时间效率比较 顺序表和链表的比较 链式存储结构的优点 1、结点空间可以动态申请和释放&#xff1b; 2、数据元素的逻辑次序靠结点的指针来指示&#xff0c;插入和删除时不需要移动数据元素。 链式存储…

【pytho】request五种种请求处理为空和非空处理以及上传excel,上传图片处理

一、python中请求处理 request.args获取的是个字典&#xff0c;所以可以通过get方式获取请求参数和值 request.form获取的也是个字典&#xff0c;所以也可以通过get方式获取请求的form参数和值 request.data&#xff0c;使用过JavaScript&#xff0c;api调用方式进行掺入jso…

[数据结构 -- C语言] 二叉树(BinaryTree)

目录 1、树的概念及结构 1.1 树的概念 1.2 树的相关概念&#xff08;很重要&#xff09; 1.3 树的表示 2、二叉树的概念及结构 2.1 概念 2.2 特殊二叉树 2.3 二叉树的性质&#xff08;很重要&#xff09; 2.4 练习题 2.5 二叉树的存储结构 2.5.1 顺序存储 2.5.2 链…

Windows10 下 Neo4j1.5.8 安装教程

前言 Neo4j 是一个高性能的、NOSQL 图形数据库&#xff0c;它将结构化数据存储在网络上而不是表中。基于磁盘的、具备完全的事务特性的 Java 持久化引擎&#xff0c;这里就不把他和常用关系型数据库做对比了。因为篇幅有限&#xff0c;我这里也是第一次使用&#xff0c;所以以…

windows安装cmake快速教程

1、下载cmake cmake官网直直接下载速度都很慢&#xff0c;可以到点击下载地址进行下载。 点击下载地址进去之后&#xff0c;可以看到有很多的版本&#xff0c;这里根据自己的需要选一个版本即可&#xff08;建议不要选择太早的版本&#xff09;&#xff0c;我这里选择的3.22版…