卷积计算加速方法--slice卷积

news2024/11/17 1:31:19

文章目录

    • 1、前言
    • 2、分块卷积存在的问题
    • 3、分块卷积问题的解决方案--slice卷积
    • 4、slice卷积每层所需切分尺寸计算
    • 4、结论及加速效果

1、前言

  我们在上一篇卷积计算加速方法中讨论过,当卷积的输入太大导致内存不够用时,考虑将一大块卷积分成多个小块分别进行卷积,相当于将原始输入分成几个小的输入经过同一组卷积核分别卷积,每块之间互不影响,其中每块小的输入都是原始输入的子集,最后将结果合并,实现分块卷积的输出结果与整个输入卷积后的结果完全一致,这种分块卷积的算法可以减小内存消耗同时大大提高运行效率。不了解的可以先看看这篇博文:卷积计算加速方法–分块卷积。但是这种算法有个问题,如果单纯的简单划分的话卷积到后面会越来越少,也就是说会有信息损失。因此在分块的时候会有overlap的出现,并且这个overlap会随着层数的增加会累积。
在这里插入图片描述

2、分块卷积存在的问题

  由第一节我们知道,分块卷积的做法会有overlap的出现,并且会随着层数的增加累积,如下图当kernel_size=3,stride=1,padding=1,分块的块数block=2时,一层卷积的时候overlap为1,两层卷积时累积到了2 … … 。
在这里插入图片描述
  从上图可以看到,随着层数的增加,overlap的尺寸也会增加,而且会随着层数累积,那就可能会出现一个问题。即层数很多时,这个overlap累积的尺寸非常大,甚至大过其他块的输入,那就根本实现不了卷积同时也没有实现计算的加速。

3、分块卷积问题的解决方案–slice卷积

  下面考虑怎么解决这个问题,由于这个问题是随着层数增加而出现的,那么如果每层单独解决overlap的问题,不把这个overlap往上传递是不是就解决了这个问题呢?答案是肯定的,这就是我们本文要讲的slice卷积,每层分块的时候左边加上一些overlap,然后将卷积之后的结果slice一部分给右边的一块,保证他们的结果合并后和普通卷积结果一致,并且每层之间互不影响。
在这里插入图片描述

4、slice卷积每层所需切分尺寸计算

  先按普通卷积的步骤算出每层的输出,然后将最后一层的输出分块,并对每一个分块往上倒推,将每一层的slice尺寸往后面一块添加,需要注意,如果stride大于1,普通卷积会与除不尽的情况,也就是可能会有几个像素点丢失,倒推的时候记得将这几个丢失的像素点找回来,并且由于丢失的像素点是在最后一个滑窗上面,所以像素点找回来的时候也要加在最后一块上。下面代码实现了求每层slice尺寸的功能。

def unit_allocation(alist, num, block):    # 递归分块函数
    if block == 1:
        alist.insert(len(alist)//2, num)
        return alist
    elif block == 2:
        alist.insert(len(alist)//2, num//2)
        alist.insert(len(alist)//2, num - (num//2))
        return alist
    alist.insert(len(alist)//2,num//block)
    alist.insert(len(alist)//2,num//block)
    return unit_allocation(alist,num - (num//block * 2),block - 2)

def reverse_input(block = 2, params_file = "slice_params.csv"):
    """计算每层卷积slice尺寸的函数
    主要功能:当卷积的参数量太大导致内存不够用时,考虑分成几块进行卷积,然后将第一块的一部分slice给第二块,
            保证第二块卷积加上第一块的slice部分刚好是对的,第二块、第三块以此类推;最后将结果合并,可以减小
            内存消耗同时大大提高运行效率,这个函数就是用来计算所需slice的尺寸。
    params:
            unit:切分块数.
            file:各层卷积或反卷积所需的相应参数.
            file::(kernel_size,stride,padding):每层卷积所需的参数尺寸.
            unit_allocation:递归分块函数,每块尺寸比整除方式更平均.
    return:
            slice方法进行卷积每层所需slice的尺寸.
    """
    params = []
    with open(params_file,"r") as file:
        for line in file.readlines():
            params.append(line.replace("\n","").split(","))
    print("输入卷积的各层参数:")
    print("input_size = {0}".format(params[1][0]))
    for i in range(2, len(params)):
        print(params[i])
    print("\t" + "-"*80)

    k_size = []
    stride = []
    padding = []
    slice_size = []             # 保存倒推每层需要slice的尺寸   
    reverse_size = []              # 保存正推往下每层的尺寸      
    forward_size = [int(params[1][0])]            # 保存倒推往上每层的尺寸
    for i in range(3,len(params)):
        k_size.append(int(params[i][0]))
        stride.append(int(params[i][1]))
        padding.append(int(params[i][2]))
    for i in range(0,len(params) - 3):
        forward_size.append((forward_size[i] - k_size[i] + 2*padding[i])//stride[i] + 1)
        
    layers = len(k_size)
    _unit_size = []
    unit_allocation(_unit_size, forward_size[-1], block)   # 递归平均分块,也可对unit_size手动指定分块
    reverse_size.append(_unit_size)
    
    for i in range(layers - 1,-1,-1):
        reverse0 = (reverse_size[layers - i - 1][0] - 1)*stride[i] + k_size[i] - padding[i]
        remainder = (forward_size[i] - k_size[i] + 2*padding[i])%stride[i]
        _slice = [k_size[i] - stride[i]]
        _reverse = [reverse0]
        slice_size.append(_slice)
        for j in range(1, block - 1):
            reverse_j = (reverse_size[layers - i - 1][j] - 1)*stride[i] + k_size[i] - slice_size[layers - i - 1][j - 1]
            _reverse.append(reverse_j)
        reverse1 = (reverse_size[layers - i - 1][-1] - 1)*stride[i] + k_size[i] - padding[i] + remainder - slice_size[layers - i - 1][-1]
        _reverse.append(reverse1)
        reverse_size.append(_reverse)
    # 打印输出,将每一层前后需要slice的尺寸打印出来
    print("正向每层的输出尺寸\t逆向倒推slice及输出的尺寸")
    output = [reverse_size[0]]
    slice_size.insert(0, [0, 0])
    
    for i in range(1, layers + 1):
        _out = []
        layer_out0 = repr(reverse_size[i][0])
        _out.append(layer_out0)
        for j in range(1, block - 1):
            layer_out_j = "(" + repr(slice_size[i][j - 1]) + ")+" + repr(reverse_size[i][j])
            _out.append(layer_out_j)
        layer_out1 = "(" + repr(slice_size[i][-1]) + ")+" + repr(reverse_size[i][-1])
        _out.append(layer_out1)
        output.append(_out)

    for i in range(len(output)):
        print("  第{0}层: {1}\t\t{2}".format(i+1, forward_size[i], output[len(output) - i - 1]))
        
reverse_input(block = 3, params_file = "slice_params.csv")

  例如输入为[1, 3, 224, 224],连着三层卷积,第一层参数为:kernel_size=3,stride=1,padding=1;第二层参数为:kernel_size=3,stride=2,padding=1;第三层参数为:kernel_size=4,stride=2,padding=1。经过三层卷积后输出尺寸为56。
  先计算每层普通卷积往下的尺寸,然后将最后一层分块成3份[18,20,18],倒推向上推出每层后面一块需要从前面一块slice过来的像素尺寸,结果如下图所示。从下图可以看出,如果第一层输入为[75,(2)+80,(2)+69]时,其中(2)表示从前面slice 2个像素点到后面,第一层卷积后的结果再将第一块slice一个像素给第二块,第二块slice一个像素给第三块,… … 依此类推,经过上述三层卷积之后输出再concat的结果与普通卷积的结果完全一致。
在这里插入图片描述
用pytorch的代码验证一下:

## slice卷积结果测试
inputs = torch.randn([1, 3, 224, 224])
weight1 = torch.randn([32, 3, 3, 3])
weight2 = torch.randn([64, 32, 3, 3])
weight3 = torch.randn([3, 64, 4, 4])

def conv2d(x, w1,w2,w3):
    y1 = torch.nn.functional.conv2d(x, w1, stride=1, padding=1)
    y2 = torch.nn.functional.conv2d(y1, w2, stride=2, padding=1)
    y3 = torch.nn.functional.conv2d(y2, w3, stride=2, padding=1)
    return y1
    
def conv2d_slice(x, w1,w2,w3):
    pad1 = torch.nn.ZeroPad2d([1, 1, 1, 0])
    pad2 = torch.nn.ZeroPad2d([1, 1, 0, 0])
    pad3 = torch.nn.ZeroPad2d([1, 1, 0, 1])
    x1 = x[:, :, 0:75, :]
    x2 = x[:, :, 75-2:155, :]
    x3 = x[:, :, 155-2:, :]
    
    x1 = pad1(x1)
    x2 = pad2(x2)
    x3 = pad3(x3)
    y1_1 = torch.nn.functional.conv2d(x1, w1, stride=1, padding=0)
    y1_2 = torch.nn.functional.conv2d(x2, w1, stride=1, padding=0)
    y1_3 = torch.nn.functional.conv2d(x3, w1, stride=1, padding=0)
    y1_1_temp = y1_1[:, :, 0:74, :]
    y1_1_slice = y1_1[:, :, 74-1:74, :]
    y1_2_slice = y1_2[:, :, 80-1:80, :]
    y1_2_overlap = torch.cat([y1_1_slice, y1_2], dim=2)
    y1_3_overlap = torch.cat([y1_2_slice, y1_3], dim=2)
    
    y1_1_temp = pad1(y1_1_temp)
    y1_2_overlap = pad2(y1_2_overlap)
    y1_3_overlap = pad3(y1_3_overlap)
    y2_1 = torch.nn.functional.conv2d(y1_1_temp, w2, stride=2, padding=0)
    y2_2 = torch.nn.functional.conv2d(y1_2_overlap, w2, stride=2, padding=0)
    y2_3 = torch.nn.functional.conv2d(y1_3_overlap, w2, stride=2, padding=0)
    y2_1_temp = y2_1[:, :, 0:37, :]
    y2_1_slice = y2_1[:, :, 37-2:37, :]
    y2_2_slice = y2_2[:, :, 40-2:40, :]
    y2_2_overlap = torch.cat([y2_1_slice, y2_2], dim=2)
    y2_3_overlap = torch.cat([y2_2_slice, y2_3], dim=2)
    
    y2_1_temp = pad1(y2_1_temp)
    y2_2_overlap = pad2(y2_2_overlap)
    y2_3_overlap = pad3(y2_3_overlap)
    y3_1 = torch.nn.functional.conv2d(y2_1_temp, w3, stride=2, padding=0)
    y3_2 = torch.nn.functional.conv2d(y2_2_overlap, w3, stride=2, padding=0)
    y3_3 = torch.nn.functional.conv2d(y2_3_overlap, w3, stride=2, padding=0)

    y = torch.cat([y1_1, y1_2, y1_3], dim=2)
    return y

out1 = conv2d(inputs, weight1, weight2, weight3)
out2 = conv2d_slice(inputs, weight1, weight2, weight3)

print(out1.shape)
print(out2.shape)
print(torch.allclose(out1, out2))       # 判断两个tensor是否相等

输出:

>>torch.Size([1, 32, 224, 224])
>>torch.Size([1, 32, 224, 224])
>>True

4、结论及加速效果

  由上述示例可以看出,如果输入为[75,(2)+80,(2)+69],其中(2)表示从前面slice 2个像素点到后面,第一层卷积后的结果再将第一块slice一个像素给第二块,第二块slice一个像素给第三块,… … 依此类推,经过上述三层卷积之后输出再concat的结果与普通卷积的结果完全一致,也就是说利用这种slice卷积的思想,当卷积的输入太大时可以减少内存占用,同时加速卷积的计算。
  经测试,在自研芯片上输入尺寸为[1,3,2224,2224],内存占用超过6MB时,普通卷积的帧率FPS为72,分三块slice卷积的帧率FPS为119,效率提升65.28%,加速效果明显!

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

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

相关文章

多快好省!硫元素循环分析内容又升级啦!

元素循环是生物地球化学循环的重要环节,主要涉及碳、氮、磷、硫等元素的循环过程。凌恩生物强势推出基于宏基因组的硫循环研究方案,构建了完整的硫循环循环模式图,对宏基因组数据进行深入挖掘,各部分结果图可直接用于文章发表&…

iOS 开发 | 自定义不规则 label

把我之前发布在简书的博客搬运过来。 目录 场景思路具体实现1. 自定义一个继承自UILabel的IrregularLabel2. 在初始化方法中进行相应初始化和设置3. 在layoutSubviews方法中进行路径的设置 最终效果箭头 label 场景 最近 App 改版,以下是截取的部分 UI 设计图&…

报表测试如何做?软件测试实战,超详细测试点分析(全覆盖)

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 报表测试是一项重…

qt学习 tcp实现 c++

这里写目录标题 qt网络编程qt下的TCP 编程设计ui界面监听关闭和发送调试 查找网络调试助手,用助手当客户端测试 编写的服务端newConnection()newConnection_Slot() Tcp Client界面设计代码部分1关闭客户端发送客户端整体疑惑 https://www.bilibili.com/video/BV1tp4…

Android CMake

首先了解几个名词 NDK The Android Native Development Kit The Android NDK is a toolset that lets you implement parts of your app in native code, using languages such as C and C. For certain types of apps, this can help you reuse code libraries written in t…

虚实相生的元宇宙,不仅仅是在做虚拟社交?

互联网迭代速度已经超出了人们的想象,从Web1.0到Web 2.0,以及紧随其后的 Web 3.0。 不管我们愿不愿意承认,元宇宙的时代已经真真切切地到来了,它的兴起也是社会发展到一定阶段的必然现象。随着时代的发展,如今创作者的…

OpenWrt uci网络配置详解

配置文件 OpenWrt所有配置保存在/etc/config目录,以下为主要的网络配置文件 网络接口配置 /etc/config/network网络服务配置 /etc/config/dhcp防火墙配置 /etc/config/firewall 网络接口 OpenWrt网络接口一般包含lan口和wan口,但如果是X86等设备&…

十五.EtherCAT开发之对象字典的映射原理

十五.EtherCAT开发之对象字典的映射原理 15.1 协议栈文件含义 仔细阅读四个代码文件 l 文件el9800appl.c:主函数,数据收发函数所在 l 文件el9800appl.h:对象字典定义所在,包含对象字典的类型、权限、长度、映射关系、链接变量…

Restful风格笔记

Restful风格知识点 RestController注解 在类上添加RestController可以默认类中的所有方法都带有ResponseBody注解,可以省去一个个添加的麻烦。 RestController RequestMapping("/restful") //CrossOrigin(origins {"http://localhost:8080"…

第六节 元组、字典

文章目录 掌握知识点1. 元组1.1 元组概述1.2 语法格式1.3 元组场景使用 2. 字典2.1 概述2.2 字典的语法结构与注意2.3 字典CURD2.3.1 字典获取2.3.2 字典添加和修改2.3.3 字典删除2.3.4 字典遍历 2.4 enumerate 函数2.5 扩展练习2.5.1 判断是否能全部购买2.5.2 学生信息的排序2…

图像中的脸部、四肢问题及其解决方法

在SD绘图的时候经常会出现多个头部、多个身体部位或者多个手指的问题这里介绍一些通用的简单的解决办法。 文章目录 多个人物或者部位没有全身出境使用纵向尺寸脸部乱码和眼睛问题人物手指乱问题 多个人物或者部位 绘图基本信息 正面词 <lora:DynastyWarriors_wu_cloth:1…

若依框架学习

1.若依&#xff08;前后端分离版&#xff09; 1.1什么是若依 开源项目&#xff0c;学习开源项目的目的&#xff1a; 1.用别人的&#xff0c;减少自己的工作量 2.学习他的底层编程思想&#xff0c;设计思路&#xff0c;提高自己的编程能力 官网&#xff1a;www.ruoyi.vip …

“AI+RPA+数据+机器人”:深入解析“数字员工”的内涵和价值

世界正进入数字经济快速发展的时期。 2021年12月&#xff0c;中央网络安全和信息化委员会印发《“十四五”国家信息化规划》&#xff0c;提出加快建设数字中国&#xff0c;大力发展数字经济的总体目标&#xff1b;2022年1月&#xff0c;央行印发《金融科技发展规划&#xff08…

第十章 番外篇:DDP

参考教程&#xff1a; what is DDP pytorch distributed overview 文章目录 DDP介绍什么是DDPDistributedSampler()DistributedDataParallel() 使用DDP代码示例multiprocessing.spawn()save and load checkpoints DDP介绍 什么是DDP DDP的全称是DistributedDataParallel&…

Qt控件学习

目录 QPushButton QToolButton QRadioButton QCheckBox QPushButton MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);ui->n1->setText("我的世界");ui->n1->setIcon(QIcon(":/111…

异构广告混排在美团到店业务的探索与实践

转子&#xff1a;https://tech.meituan.com/2022/03/10/exploration-and-practice-of-heterogeneous-ad-mixed-ranking-in-meituan-ads.html 1 背景与简介 1.1 背景 美团到店广告负责美团搜索流量的商业变现&#xff0c;服务于到店餐饮、休娱亲子、丽人医美、酒店旅游等众多…

文言一心,ChatGLM-6B和ChatGPT等模型概述

原文首发于博客文章大语言模型概况 定义 &#xff08;个人理解的&#xff09;大语言模型&#xff08;Large Language Model&#xff09;是一种基于深度学习技术的自然语言处理通用模型&#xff0c;它可以通过学习大规模文本数据的模式和规律&#xff0c;从而实现对自然语言的理…

【面试】你知道数据库能抗多大并发压力吗?

文章目录 前言一、一般业务系统运行流程图二、一台4核8G的机器能扛多少并发量呢&#xff1f;三、高并发来袭时数据库会先被打死吗&#xff1f;四、数据库架构可以从哪些方面优化&#xff1f;4.1、根据业务系统拆分多个数据库机器优化方案4.2、读写分离架构优化方案4.3、分库分表…

华为手表上架(1)HarmonyOS应用 打包 .app

华为手表上架&#xff08;1&#xff09;HarmonyOS应用 打包 .app 初环境与设备先找到配置项目结构的地方&#xff1a; Project Structure配置证书打包 .app 在本文中&#xff0c;我们介绍探讨华为手表上架的流程&#xff0c;并重点介绍HarmonyOS应用的打包过程。了解如何将应用…

locust学习教程(5) - 分布式执行

目录 前言 1、基础 2、操作步骤 2.1、启动主节点 2.2、启动从节点 2.2.1、启动自己电脑的从节点 2.2.2、启动同事电脑的从节点 2.3、开始并发 3、无web界面&#xff0c;定时运行&#xff0c;数据存储在csv中、等待4个节点连接后自动开始 4、有web界面&#xff0c;定时运…