Tensorflow卷积实现原理+手写python代码实现卷积教程


Posted in Python onMay 22, 2020

从一个通道的图片进行卷积生成新的单通道图的过程很容易理解,对于多个通道卷积后生成多个通道的图理解起来有点抽象。本文以通俗易懂的方式讲述卷积,并辅以图片解释,能快速理解卷积的实现原理。最后手写python代码实现卷积过程,让Tensorflow卷积在我们面前不再是黑箱子!

注意:

本文只针对batch_size=1,padding='SAME',stride=[1,1,1,1]进行实验和解释,其他如果不是这个参数设置,原理也是一样。

1 Tensorflow卷积实现原理

先看一下卷积实现原理,对于in_c个通道的输入图,如果需要经过卷积后输出out_c个通道图,那么总共需要in_c * out_c个卷积核参与运算。参考下图:

Tensorflow卷积实现原理+手写python代码实现卷积教程

如上图,输入为[h:5,w:5,c:4],那么对应输出的每个通道,需要4个卷积核。上图中,输出为3个通道,所以总共需要3*4=12个卷积核。对于单个输出通道中的每个点,取值为对应的一组4个不同的卷积核经过卷积计算后的和。

接下来,我们以输入为2个通道宽高分别为5的输入、3*3的卷积核、1个通道宽高分别为5的输出,作为一个例子展开。

2个通道,5*5的输入定义如下:

#输入,shape=[c,h,w]
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]],
 
  ]

对于输出为1通道map,根据前面计算方法,需要2*1个卷积核。定义卷积核如下:

#卷积核,shape=[in_c,k,k]=[2,3,3]
weights_data=[ 
  [[ 1, 0, 1],
  [-1, 1, 0],
  [ 0,-1, 0]],
  [[-1, 0, 1],
  [ 0, 0, 1],
  [ 1, 1, 1]] 
  ]

上面定义的数据,在接下来的计算对应关系将按下图所描述的方式进行。

Tensorflow卷积实现原理+手写python代码实现卷积教程

由于Tensorflow定义的tensor的shape为[n,h,w,c],这里我们可以直接把n设为1,即batch size为1。还有一个问题,就是我们刚才定义的输入为[c,h,w],所以需要将[c,h,w]转为[h,w,c]。转换方式如下,注释已经解释很详细,这里不再解释。

def get_shape(tensor):
 [s1,s2,s3]= tensor.get_shape() 
 s1=int(s1)
 s2=int(s2)
 s3=int(s3)
 return s1,s2,s3

def chw2hwc(chw_tensor): 
 [c,h,w]=get_shape(chw_tensor) 
 cols=[]
 
 for i in range(c):
 #每个通道里面的二维数组转为[w*h,1]即1列 
 line = tf.reshape(chw_tensor[i],[h*w,1])
 cols.append(line)

 #横向连接,即将所有竖直数组横向排列连接
 input = tf.concat(cols,1)#[w*h,c]
 #[w*h,c]-->[h,w,c]
 input = tf.reshape(input,[h,w,c])
 return input

同理,Tensorflow使用卷积核的时候,使用的格式是[k,k,in_c,out_c]。而我们在定义卷积核的时候,是按[in_c,k,k]的方式定义的,这里需要将[in_c,k,k]转为[k,k,in_c],由于为了简化工作量,我们规定输出为1个通道,即out_c=1。所以这里我们可以直接简单地对weights_data调用chw2hwc,再在第3维度扩充一下即可。

接下来,贴出完整的代码:

import tensorflow as tf
import numpy as np
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]],
 
  ]
weights_data=[ 
  [[ 1, 0, 1],
  [-1, 1, 0],
  [ 0,-1, 0]],
  [[-1, 0, 1],
  [ 0, 0, 1],
  [ 1, 1, 1]] 
  ]
def get_shape(tensor):
 [s1,s2,s3]= tensor.get_shape() 
 s1=int(s1)
 s2=int(s2)
 s3=int(s3)
 return s1,s2,s3

def chw2hwc(chw_tensor): 
 [c,h,w]=get_shape(chw_tensor) 
 cols=[]
 
 for i in range(c):
 #每个通道里面的二维数组转为[w*h,1]即1列 
 line = tf.reshape(chw_tensor[i],[h*w,1])
 cols.append(line)

 #横向连接,即将所有竖直数组横向排列连接
 input = tf.concat(cols,1)#[w*h,c]
 #[w*h,c]-->[h,w,c]
 input = tf.reshape(input,[h,w,c])
 return input

def hwc2chw(hwc_tensor):
 [h,w,c]=get_shape(hwc_tensor) 
 cs=[] 
 for i in range(c): 
 #[h,w]-->[1,h,w] 
 channel=tf.expand_dims(hwc_tensor[:,:,i],0)
 cs.append(channel)
 #[1,h,w]...[1,h,w]---->[c,h,w]
 input = tf.concat(cs,0)#[c,h,w]
 return input

def tf_conv2d(input,weights):
 conv = tf.nn.conv2d(input, weights, strides=[1, 1, 1, 1], padding='SAME')
 return conv

def main(): 
 const_input = tf.constant(input_data , tf.float32)
 const_weights = tf.constant(weights_data , tf.float32 )

 
 input = tf.Variable(const_input,name="input")
 #[2,5,5]------>[5,5,2]
 input=chw2hwc(input)
 #[5,5,2]------>[1,5,5,2]
 input=tf.expand_dims(input,0)

 
 weights = tf.Variable(const_weights,name="weights")
 #[2,3,3]-->[3,3,2]
 weights=chw2hwc(weights)
 #[3,3,2]-->[3,3,2,1]
 weights=tf.expand_dims(weights,3) 

 #[b,h,w,c]
 conv=tf_conv2d(input,weights)
 rs=hwc2chw(conv[0]) 

 init=tf.global_variables_initializer()
 sess=tf.Session()
 sess.run(init)
 conv_val = sess.run(rs)
 
 print(conv_val[0]) 


if __name__=='__main__':
 main()

上面代码有几个地方需要提一下,

由于输出通道为1,因此可以对卷积核数据转换的时候直接调用chw2hwc,如果输入通道不为1,则不能这样完成转换。

输入完成chw转hwc后,记得在第0维扩充维数,因为卷积要求输入为[n,h,w,c]

为了方便我们查看结果,记得将hwc的shape转为chw

执行上面代码,运行结果如下:

[[ 2. 0. 2. 4. 0.]
 [ 1. 4. 4. 3. 5.]
 [ 4. 3. 5. 9. -1.]
 [ 3. 4. 6. 2. 1.]
 [ 5. 3. 5. 1. -2.]]

这个计算结果是怎么计算出来的?为了让大家更清晰的学习其中细节,我特地制作了一个GIF图,看完这个图后,如果你还看不懂卷积的计算过程,你可以来打我。。。。

Tensorflow卷积实现原理+手写python代码实现卷积教程

2 手写Python代码实现卷积

自己实现卷积时,就无须将定义的数据[c,h,w]转为[h,w,c]了。

import numpy as np
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]] 
  ]
weights_data=[ 
  [[ 1, 0, 1],
  [-1, 1, 0],
  [ 0,-1, 0]],
  [[-1, 0, 1],
  [ 0, 0, 1],
  [ 1, 1, 1]] 

  ]

#fm:[h,w]
#kernel:[k,k]
#return rs:[h,w] 
def compute_conv(fm,kernel):
 [h,w]=fm.shape
 [k,_]=kernel.shape 
 r=int(k/2)
 #定义边界填充0后的map
 padding_fm=np.zeros([h+2,w+2],np.float32)
 #保存计算结果
 rs=np.zeros([h,w],np.float32)
 #将输入在指定该区域赋值,即除了4个边界后,剩下的区域
 padding_fm[1:h+1,1:w+1]=fm 
 #对每个点为中心的区域遍历
 for i in range(1,h+1):
 for j in range(1,w+1): 
  #取出当前点为中心的k*k区域
  roi=padding_fm[i-r:i+r+1,j-r:j+r+1]
  #计算当前点的卷积,对k*k个点点乘后求和
  rs[i-1][j-1]=np.sum(roi*kernel)
 
 return rs
 
def my_conv2d(input,weights):
 [c,h,w]=input.shape
 [_,k,_]=weights.shape
 outputs=np.zeros([h,w],np.float32)

 #对每个feature map遍历,从而对每个feature map进行卷积
 for i in range(c):
 #feature map==>[h,w]
 f_map=input[i]
 #kernel ==>[k,k]
 w=weights[i]
 rs =compute_conv(f_map,w)
 outputs=outputs+rs 

 return outputs

def main(): 
 
 #shape=[c,h,w]
 input = np.asarray(input_data,np.float32)
 #shape=[in_c,k,k]
 weights = np.asarray(weights_data,np.float32) 
 rs=my_conv2d(input,weights) 
 print(rs) 


if __name__=='__main__':
 main()

代码无须太多解释,直接看注释。然后跑出来的结果如下:

[[ 2. 0. 2. 4. 0.]
 [ 1. 4. 4. 3. 5.]
 [ 4. 3. 5. 9. -1.]
 [ 3. 4. 6. 2. 1.]
 [ 5. 3. 5. 1. -2.]]

对比发现,跟Tensorflow的卷积结果是一样的。

3 小结

本文中,我们学习了Tensorflow的卷积实现原理,通过也通过python代码实现了输出通道为1的卷积,其实输出通道数不影响我们学习卷积原理。后面如果有机会的话,我们去实现一个更加健全,完整的卷积。

以上这篇Tensorflow卷积实现原理+手写python代码实现卷积教程就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python3控制路由器——使用requests重启极路由.py
May 11 Python
python读取视频流提取视频帧的两种方法
Oct 22 Python
Python操作mongodb的9个步骤
Jun 04 Python
Python3使用pandas模块读写excel操作示例
Jul 03 Python
Python实现简单的用户交互方法详解
Sep 25 Python
对numpy中的where方法嵌套使用详解
Oct 31 Python
Django2.1.3 中间件使用详解
Nov 26 Python
python 基于TCP协议的套接字编程详解
Jun 29 Python
python文件操作的简单方法总结
Nov 07 Python
Python如何获取Win7,Win10系统缩放大小
Jan 10 Python
python3使用Pillow、tesseract-ocr与pytesseract模块的图片识别的方法
Feb 26 Python
Python yield的用法实例分析
Mar 06 Python
Python实现发票自动校核微信机器人的方法
May 22 #Python
基于django micro搭建网站实现加水印功能
May 22 #Python
基于Tensorflow一维卷积用法详解
May 22 #Python
Python参数传递机制传值和传引用原理详解
May 22 #Python
python filecmp.dircmp实现递归比对两个目录的方法
May 22 #Python
关于keras.layers.Conv1D的kernel_size参数使用介绍
May 22 #Python
Python参数传递对象的引用原理解析
May 22 #Python
You might like
php 删除一个数组中的某个值.兼容多维数组!
2012/02/18 PHP
php实现给图片加灰色半透明效果的方法
2014/10/20 PHP
利用PHP判断文件是否为图片的方法总结
2017/01/06 PHP
PHP的mysqli_set_charset()函数讲解
2019/01/23 PHP
Js中setTimeout()和setInterval() 何时被调用执行的用法
2013/04/12 Javascript
jQuery fadeTo方法调整图片的透明度使用介绍
2013/05/06 Javascript
一个JavaScript变量声明的知识点
2013/10/28 Javascript
seajs中模块的解析规则详解和模块使用总结
2014/03/12 Javascript
Dojo Javascript 编程规范 规范自己的JavaScript书写
2014/10/26 Javascript
基于jquery实现的树形菜单效果代码
2015/09/06 Javascript
JS基于myFocus库实现各种功能的tab选项卡切换效果
2015/09/19 Javascript
BootStrap 智能表单实战系列(五) 表单依赖插件处理
2016/06/13 Javascript
Angular.js中ng-if、ng-show和ng-hide的区别介绍
2017/01/20 Javascript
js判断是否是手机页面
2017/03/17 Javascript
JS实现仿UC浏览器前进后退效果的实例代码
2017/07/17 Javascript
js实现方块上下左右移动效果
2017/08/17 Javascript
解决Vue 浏览器后退无法触发beforeRouteLeave的问题
2017/12/24 Javascript
webpack4.x打包过程详解
2018/07/18 Javascript
js实现固定区域内的不重叠随机圆
2019/10/24 Javascript
如何手写一个简易的 Vuex
2020/10/10 Javascript
[36:29]2018DOTA2亚洲邀请赛 4.1 小组赛 A组加赛 LGD vs TNC
2018/04/02 DOTA
在Linux系统上安装Python的Scrapy框架的教程
2015/06/11 Python
python学习之面向对象【入门初级篇】
2017/01/21 Python
python下解压缩zip文件并删除文件的实例
2018/04/24 Python
Python类和实例的属性机制原理详解
2020/03/21 Python
通过实例解析python创建进程常用方法
2020/06/19 Python
Python如何将装饰器定义为类
2020/07/30 Python
Python unittest如何生成HTMLTestRunner模块
2020/09/08 Python
Python中return函数返回值实例用法
2020/11/19 Python
IE矩阵Matrix滤镜旋转与缩放及如何结合transform
2012/11/29 HTML / CSS
海量信息软件测试笔试题
2015/08/08 面试题
2014年招商工作总结
2014/11/22 职场文书
2015年学校减负工作总结
2015/05/19 职场文书
2016年员工年度考核评语
2015/12/02 职场文书
2019年世界儿童日宣传标语
2019/11/22 职场文书
vue-cropper插件实现图片截取上传组件封装
2021/05/27 Vue.js