IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> 实现卷积的几种代码方式 -> 正文阅读

[人工智能]实现卷积的几种代码方式

目录

摘要

卷积(convolution)

1、pytorch实现

2、对input展开矩阵相乘

3、对kernel展开以及矩阵相乘

转置卷积

1、API实现

2、对kernel矩阵转置+矩阵相乘

总结

摘要

卷积的基本元素有着input size、kernel size、stride、padding、group以及dilation等等。在卷积中有着卷积(convolution)和转置卷积(transpose convolution)。其中卷积常常用于局部建模和下采样,而转置卷积则多用于上采用。本次学习针对二者的具体代码展开,并分别对官方api和手动复现进行学习比对。

卷积(convolution)

1、pytorch实现

在pytorch中有两个实现卷积的方法。一种是以类的形式,另外一种是以函数的进行进行调用。二者区别则为,以函数的进行调用无需手动化实例kernel,若以类的形式进行调用的话,则需自己手动将相关张量实例化。

首先对一些张量进行初始化,在二维的卷积中,input_size一搬是四维的张量。

import torch
import torch.nn as nn
import torch.nn.functional as F
import math
?
in_channels = 1 #输入的通道数
out_channels = 1 #输出的通道数
kernel_size = 3 #卷积核大小
batch_size = 1 ?#样本的数目
bias = False
input_size = [batch_size, in_channels, 4, 4]

通过类进行实现。首先实例化二维卷积对象,其次生成输入、调用正态分布随机函数,最后将将input_feature_map作为conv_layer的输入得到output_feature_map。

conv_layer = torch.nn.Conv2d(in_channels,out_channels,kernel_size,bias=bias)#实例化二维卷积的对象
input_feature_map = torch.randn(input_size) #生成输入,调用正态分布的随机函数
output_feature_map = conv_layer(input_feature_map) #将input_feature_map作为conv_layer的输入

通过函数进行实现,直接传入input,和kernel张量。

output_feature_map1 = F.conv2d(input_feature_map, conv_layer.weight)

最后来看二者的结果是否相同。

print(output_feature_map)
print(output_feature_map1)
print(torch.allclose(output_feature_map,output_feature_map1))

经验证二者结果是相同的。

2、对input展开矩阵相乘

将每次滑动相乘区域的input拉直,然后将这些向量拼凑成一个矩阵,之后和kernel矩阵进行矩阵相乘。在这里即可以手动写,也可以通过调用torch.Unfold完成。

先对一些张量进行初始化。

input = torch.randn(5,5) ? #卷积输入特征图
kernel = torch.randn(3,3) ? #卷积核
bias = torch.randn(1) ? ? ? #卷积偏置,默认输出通道数目等于一,长度为1的随机量

step1: 用原始的矩阵运算来实现二维卷积,先不考虑batchsize维度和channels维度。pytorch中的维度是反过来的,从里到外,即从左到右,从上到下进行填充。

def matrix_multiplication_for_conv2d(input, kernel, bias=0, stride=1, padding=0): ? ?
    if padding > 0: ? ? ? 
        input = F.pad(input, (padding, padding, padding, padding)) #对input进行填充操作 ?
 ? ? ? ?
    input_h, input_w = input.shape ?
 ? ?kernel_h, kernel_w = kernel.shape ? 
 ? ?
 ? ?output_h = (math.floor((input_h - kernel_h)/stride) + 1) ? ?#卷积输出的高度 ? ?
 ? ?output_w = (math.floor((input_w - kernel_w)/stride) + 1) ? ?#卷积输出的宽度 ? ?
 ? ?output = torch.zeros(output_h, output_w) ? ?#初始化输出矩阵 ? ?
 ? ?
 ? ?for i in range(0, input_h-kernel_h+1,stride): ? ? #对高度进行遍历 ? ? 
 ? ? ? ?for j in range(0, input_w-kernel_w+1,stride): ? ? #对宽度进行遍历 ? ? ? ? ?        
 ? ? ? ? ? ?region=input[i:i+kernel_h, j:j+kernel_w] ? ?#取出被核滑动到的区域 ? ? ? ? ? ?
 ? ? ? ? ? ?output[int(i/stride), int(j/stride)] = torch.sum(region * kernel) +bias ? ? #点乘,并赋值给输出位置的元素 ?
 ? ? ? ? ? ?
    return output

step2: 用原始的矩阵运算来实现二维卷积,先不考虑batchsize维度和channels维度,flatten input版本。

def matrix_multiplication_for_conv2d_flatten(input, kernel, bias=0, stride=1, padding=0):
    if padding>0:
        input = F.pad(input,(padding, padding, padding, padding))

    input_h, input_w = input.shape
    kernel_h, kernel_w = kernel.shape

    output_h = (math.floor((input_h-kernel_h)/stride)+1)    #卷积输出的高度
    output_w = (math.floor((input_w-kernel_w)/stride)+1)    #卷积输出的高度
    output = torch.zeros(output_h,output_w)     #初始化输出矩阵

    region_matrix = torch.zeros(output.numel(), kernel.numel())  #存储着所有的拉平后特征区域
    kernel_matrix = kernel.reshape((kernel.numel(), 1))     #kernel的列向量(矩阵)形式
    row_index = 0
    for i in range(0,input_h-kernel_h+1,stride):    #对高度维进行遍历
        for j in range(0,input_w-kernel_w+1,stride):    #对宽度维进行遍历
            region = input[i:i+kernel_h,j:j+kernel_w]   #取出被核滑动到的区域
            region_vector = torch.flatten(region)
            region_matrix[row_index] = region_vector
            row_index +=1

    output_matrix = region_matrix @ kernel_matrix
    output=output_matrix.reshape(output_h, output_w) +bias

    return  output

对三者的结果进行验证。

#矩阵运算实现卷积的结果
mat_mul_conv_output = matrix_multiplication_for_conv2d(input, kernel, bias=bias, padding=1, stride=2)
?
#调用PyTorch API卷积的结果
pytorch_api_conv_output = F.conv2d(input.reshape((1,1,input.shape[0],input.shape[1])),\ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? kernel.reshape((1,1,kernel.shape[0],kernel.shape[1])),\ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? padding=1,\ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?bias=bias, stride=2).squeeze(0).squeeze(0)
?
#矩阵运算实现卷积的结果,flatten input版本
mat_mul_conv_output_flatten = matrix_multiplication_for_conv2d_flatten(input, kernel, bias=bias, padding=1, stride=2)
?
#验证flatten版本卷积、非flatten版本卷积与PyTorch官方的卷积结果
print(mat_mul_conv_output_flatten)
print(mat_mul_conv_output)
print(pytorch_api_conv_output)
flag1 = torch.allclose(mat_mul_conv_output_flatten, pytorch_api_conv_output)
flag2 = torch.allclose(mat_mul_conv_output, pytorch_api_conv_output)
flag3 = torch.allclose(mat_mul_conv_output_flatten,mat_mul_conv_output)
?
print(flag1)
print(flag2)
print(flag3)
验证结果为三者一致。

step3: 用原始的矩阵运算来实现二维卷积,考虑batchsize维度和channels维度

def matrix_multiplication_for_conv2d_full(input, kernel, bias=0, stride=1, padding=0):
    #input、kernel都是思维张量
    if padding > 0:
        input = F.pad(input, (padding, padding, padding, padding, 0, 0, 0, 0))

    bs ,in_channel, input_h ,input_w = input.shape
    out_channel, in_channel, kernel_h ,kernel_w = kernel.shape
    if bias is None:
        bias = torch.zeros(out_channel)

    output_h = (math.floor((input_h - kernel_h) / stride) + 1)  # 卷积输出的高度
    output_w = (math.floor((input_w - kernel_w) / stride) + 1)  # 卷积输出的宽度
    output = torch.zeros(bs, out_channel, output_h, output_w)   #初始化输出矩阵

    for ind in range(bs):
        for oc in range(out_channel):
            for ic in range(in_channel):
                for i in range(0, input_h-kernel_h+1,stride):   #对高度进行遍历
                    for j in range(0, input_w-kernel_w+1,stride):   #对宽度进行遍历
                        region = input[ind, ic, i:i+kernel_h,j:j+kernel_w]  #取出被核滑动到的区域
                        output[ind, oc, int(i/stride), int(j/stride)] +=torch.sum(region * kernel[oc, ic])   #点乘,并赋值给输出位置的元素
            output[ind,oc] +=bias[oc]

    return output

验证matrix_multiplication_for_conv2d_full与pytorch官方API是否一致

input = torch.randn(2, 2, 5, 5)
kernel = torch.randn(3, 2, 3, 3)
bias = torch.randn(3)

pytorch_conv2d_api_output = F.conv2d(input, kernel ,bias=bias, padding=1, stride=2)
mm_conv2d_full_output = matrix_multiplication_for_conv2d_full(input, kernel ,bias=bias, padding=1, stride=2)
flag = torch.allclose(pytorch_conv2d_api_output,mm_conv2d_full_output)

 print("all close:", flag)

结果为二者一致

?

3、对kernel展开以及矩阵相乘

将每一步滑动相乘看作是把kernel填充到跟input一样大小的矩阵,然后将这个新的矩阵拉直,之后将每一步拉直后的向量堆叠起来构成一个kernel矩阵,再用这个kernel矩阵和input矩阵进行矩阵相乘。

def get_kernel_matrix(kernel, input_size):       
    #基于kenerl和输入特征图的大小来得到填充拉直后的kernel堆叠后的矩阵    
    kernel_h, kernel_w = kernel.shape   
    input_h, input_w = input_size    
    num_out_feat_map = (input_h-kernel_h + 1) * (input_w-kernel_w + 1)  
    result = torch.zeros((num_out_feat_map, input_h*input_w))   #初始化结果矩阵,输出特征图元素个数*输入特征图元素个数    
    count = 0    
    for i in range(0,input_h-kernel_h+1, 1):      
        for j in range(0,input_w-kernel_w+1, 1):          
            padded_kernel = F.pad(kernel, (j,input_w-kernel_w-j, i, input_h-kernel_h-i))    #填充成跟输入特征图一样大小           
            result[count] = padded_kernel.flatten()        
            count += 1   
    return result

result是基于kenerl和输入特征图的大小来得到填充拉直后的kernel堆叠后的矩阵

对结果和官方api进行验证。

kernel = torch.randn(3,3)
input = torch.randn(4,4)
kernel_matrix = get_kernel_matrix(kernel, input.shape)  #4*16

mm_conv2d_full_output = kernel_matrix @ input.reshape((-1, 1))  #通过矩阵乘积来计算卷积
pytorch_conv2d_output = F.conv2d(input.unsqueeze(0).unsqueeze(0), kernel.unsqueeze(0).unsqueeze(0))

print(mm_conv2d_full_output.reshape((2,2)))
print(pytorch_conv2d_output)   #2*2

可见二者是一致的。

?

转置卷积

将kernel矩阵转置再和卷积的输出进行相乘,即实现了上采样效果。同样的在转置卷积中,可以通过调用api和手写完成。

1、API实现

kernel = torch.randn(3,3)
input = torch.randn(4,4)

pytorch_transposed_conv2d_output = F.conv_transpose2d(pytorch_conv2d_output, kernel.unsqueeze(0).unsqueeze(0))

2、对kernel矩阵转置+矩阵相乘

转置就是将kernel_matrix矩阵的负一维和负二维交换一下再与mm_conv2d_full_output矩阵相乘得出,即反向运算。

mm_conv2d_full_output = kernel_matrix @ input.reshape((-1, 1))
mm_transposed_conv2d_output = kernel_matrix.transpose(-1, -2) @ mm_conv2d_full_output  

对于手算和调用api结果进行验证,结论一致。

print(mm_transposed_conv2d_output.reshape((4,4)))
print(pytorch_transposed_conv2d_output)

?

?

总结

在对卷积的相关代码进行学习后,对于卷积的原理认识更加深刻了,在当前的大部分程序中,对于卷积大部分都是调用官方api,但通过手写卷积代码可以加深对其的理解,,手写框架代码也尤为重要。通过上手,对于PyTorch的许多方法不是很熟悉,因此下周准备对PyTorch的相关知识进行系统的学习。

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 19:04:57  更:2022-08-19 19:07:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 18:57:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计