深度理解卷积--使用numpy实现卷积

卷积

在深度学习里CNN卷积神经网络是最常见的概念,可以算AI届的hello world了。https://www.jianshu.com/p/fc9175065d87这个文章中用动图很好的解释了什么叫做卷积。
其实很早的图像处理里,使用一个滤波器扫一遍图像就类似现在深度学习里卷积的计算过程,只是AI中核是需要通过学习得到的。
本文就不从理论上详细介绍卷积了,程序员就要有程序员的亚子,所以我直接上代码介绍怎么用numpy实现卷积

numpy实现卷积

基础定义

以CV中对图像卷积为例,图像卷积一般都是
输入:四维数组[B,H,W,C_in]
卷积核:四维数组[C_in,K,K,C_out]
输出:四维数组[B,H2,W2,C_out]

B---batchsize输入对图片张数
H,W---输入图片对高和宽
C_in---输入图片对通道数,比如RGB图像就是三通道,C_in=3
K---卷积核对宽/高,通常宽=高
C_out---有多少个卷积核
H2,W2---输出特征图对高核宽

为什么H和H2不一致,是因为需要根据padding等情况而定。
在卷积时还有stride等概念,本文设置stride=1,因为理解了本文代码后,其他情况完全可以方便实现。

单个核卷积单通道

首先我们从低维入手,图片张数为1,单个卷积核,单个通道输入:
输入---[H,W]
卷积核---[K,K]
输出---[H2,W2]
也就是下图过程:

image.png

上图我们发现输入核输出不一致,是因为它使用VALID模式的padding,如果我们希望输入和输出一致,就需要使用SAME模式,如下图
image.png

padding模式对输出大小做下简单介绍:
如果是VALID模式,我们输出会变小,输出大小为
(H-K+1,W-K+1)
如果是SMAE模式,输出和输入需要一样大小,所以需要Padding值,通常为0,假设数组H=W=N,padding的维度为p,即上下左右都进行添加p
(N+2p-K+1,N+2p-K+1)=(N,N)
所以
p=(k-1)/2

好了~直接上代码,过程有详细注释:

def numpy_conv(inputs,filter,padding="VALID"):
    H, W = inputs.shape
    filter_size = filter.shape[0]
    # default np.floor
    filter_center = int(filter_size / 2.0)
    filter_center_ceil = int(np.ceil(filter_size / 2.0))

    # SAME模式,输入和输出大小一致,所以要在外面填充0
    if padding == "SAME":
        padding_inputs = np.zeros([H + filter_center_ceil, W + filter_center_ceil], np.float32)
        padding_inputs[filter_center:-filter_center, filter_center:-filter_center] = inputs
        inputs = padding_inputs
    #这里先定义一个和输入一样的大空间,但是周围一圈后面会截掉
    result = np.zeros((inputs.shape))
    #更新下新输入,SAME模式下,会改变HW
    H, W = inputs.shape
    #print("new size",H,W)
    #卷积核通过输入的每块区域,stride=1,注意输出坐标起始位置
    for r in range(filter_center,H -filter_center):
        for c in range(filter_center,W -filter_center ):
            #获取卷积核大小的输入区域
            cur_input = inputs[r -filter_center :r +filter_center_ceil,
                        c - filter_center:c + filter_center_ceil]
            #和核进行乘法计算
            cur_output = cur_input * filter
            #再把所有值求和
            conv_sum = np.sum(cur_output)
            #当前点输出值
            result[r, c] = conv_sum
    # 外面一圈都是0,裁减掉
    final_result = result[filter_center:result.shape[0] - filter_center,
                   filter_center:result.shape[1] -filter_center]
    return final_result

现在我们完成了最简单的单核卷积。下面我们进行扩展,用多个核卷积多个通道维度的数据。

多卷积核多通道

和以上逻辑一致,只是多了一个维度而已,
输入---[C_in,H,W]
卷积核---[C_out,K,K]
输出---[C_out,C_in,H2,W2]
下图过程是单个核卷积多通道的过程:


image.png

image.png

多个核就是上面过程执行多遍,然后把结果累加。了解理论后就知道,就是多两个for循环的事:
(1)一个循环是输入通道数对循环:把卷积核在每个通道数据上卷积,然后结果累加
(2)一个循环是核个数对循环:每个卷积核执行步骤(1),然后把结果累加
是不是很简单~
上代码:

def _conv(inputs, filter,padding="SAME"):
    C_in, H, W = inputs.shape
    #C_out指核对个数,也是最后结果对通道个数
    C_out = filter.shape[0]
    #同样我们任务核对宽高相等
    filter_size = filter.shape[1]
    filter_center_ceil = int(np.ceil(filter_size / 2.0))
    # VALID模式,输入和输出不一致,需要减少;SAME模式,输入和输出一致
    if padding == "VALID":
        result = np.zeros([C_out, H-filter_center_ceil, W-filter_center_ceil], np.float32)
    else:
        result = np.zeros([C_out, H, W], np.float32)
    #核个数对循环
    for channel_out in range(C_out):
        #输入通道数对循环
        for channel_in in range(C_in):
            #当前通道对数据
            channel_data=inputs[channel_in]
            #采用上面对逻辑,单核单通道卷积,然后累计
            result[channel_out,:,:] += numpy_conv(channel_data,filter[channel_out][channel_in],padding)
      
    #print(result)
    return result

如果有多张图片呢?每个图片进行以上结果,然后cancat就好了~不就是再加一个循环的事么~~~~
现在!应该!!!!对卷积!!!完全懂了!!!!吧!!!!!

我们上完整代码

# -*- coding: utf-8 -*-
import numpy as np

def numpy_conv(inputs,filter,padding="VALID"):
    H, W = inputs.shape
    filter_size = filter.shape[0]
    # default np.floor
    filter_center = int(filter_size / 2.0)
    filter_center_ceil = int(np.ceil(filter_size / 2.0))

    # SAME模式,输入和输出大小一致,所以要在外面填充0
    if padding == "SAME":
        padding_inputs = np.zeros([H + filter_center_ceil, W + filter_center_ceil], np.float32)
        padding_inputs[filter_center:-filter_center, filter_center:-filter_center] = inputs
        inputs = padding_inputs
    #这里先定义一个和输入一样的大空间,但是周围一圈后面会截掉
    result = np.zeros((inputs.shape))
    #更新下新输入,SAME模式下,会改变HW
    H, W = inputs.shape
    #print("new size",H,W)
    #卷积核通过输入的每块区域,stride=1,注意输出坐标起始位置
    for r in range(filter_center,H -filter_center):
        for c in range(filter_center,W -filter_center ):
            #获取卷积核大小的输入区域
            cur_input = inputs[r -filter_center :r +filter_center_ceil,
                        c - filter_center:c + filter_center_ceil]
            #和核进行乘法计算
            cur_output = cur_input * filter
            #再把所有值求和
            conv_sum = np.sum(cur_output)
            #当前点输出值
            result[r, c] = conv_sum
    # 外面一圈都是0,裁减掉
    final_result = result[filter_center:result.shape[0] - filter_center,
                   filter_center:result.shape[1] -filter_center]
    return final_result



def _conv(inputs, filter,padding="SAME"):
    C_in, H, W = inputs.shape
    #C_out指核对个数,也是最后结果对通道个数
    C_out = filter.shape[0]
    #同样我们任务核对宽高相等
    filter_size = filter.shape[1]
    filter_center_ceil = int(np.ceil(filter_size / 2.0))
    # VALID模式,输入和输出不一致,需要减少;SAME模式,输入和输出一致
    if padding == "VALID":
        result = np.zeros([C_out, H-filter_center_ceil, W-filter_center_ceil], np.float32)
    else:
        result = np.zeros([C_out, H, W], np.float32)
    #核个数对循环
    for channel_out in range(C_out):
        #输入通道数对循环
        for channel_in in range(C_in):
            #当前通道对数据
            channel_data=inputs[channel_in]
            #采用上面对逻辑,单核单通道卷积,然后累计
            result[channel_out,:,:] += numpy_conv(channel_data,filter[channel_out][channel_in],padding)
       
    #print(result)
    return result


if __name__ == '__main__':
    #输入[C_in,H,W]
    inputs = np.zeros([3,9,9])
    for i in range(3):
        for j in range(9):
            for z in range(9):
                inputs[i][j][z] = i+j+z

    print("input:\n",inputs,"\n")

    #卷积核[C_out,C_in,K,K]
    filter = np.zeros([2, 3, 3, 3])
    for i in range(2):
        for j in range(3):
            for x in range(3):
                for y in range(3):
                    filter[i][j][x][y] = i + j + x + y


    print("filter\n",filter,"\n")

    final_result = _conv(inputs,filter,"SAME")

    print("result\n",final_result,"\n")

我怎么知道我结果是对还是错的呢~下次我们用tensorflow的API来进行验证!
CSDN:https://blog.csdn.net/xiongwei111/article/details/100610364
不知道为什么搬到简书,图就不动了。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352