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 相关文章推荐
python网络编程学习笔记(二):socket建立网络客户端
Jun 09 Python
Python 正则表达式(转义问题)
Dec 15 Python
介绍Python中几个常用的类方法
Apr 08 Python
解决pandas 作图无法显示中文的问题
May 24 Python
解决tensorflow测试模型时NotFoundError错误的问题
Jul 26 Python
Python3.6简单的操作Mysql数据库的三个实例
Oct 17 Python
如何在Django中添加没有微秒的 DateTimeField 属性详解
Jan 30 Python
python浪漫表白源码
Apr 05 Python
python Canny边缘检测算法的实现
Apr 24 Python
Python基于BeautifulSoup爬取京东商品信息
Jun 01 Python
Python调用ffmpeg开源视频处理库,批量处理视频
Nov 16 Python
快速一键生成Python爬虫请求头
Mar 04 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
10个可以简化php开发过程的MySQL工具
2010/04/11 PHP
PHP 数组和字符串互相转换实现方法
2013/03/26 PHP
浅析php header 跳转
2013/06/17 PHP
php通过执行CutyCapt命令实现网页截图的方法
2016/09/30 PHP
解析 thinkphp 框架中的部分方法
2017/05/07 PHP
PHP PDOStatement::errorInfo讲解
2019/01/31 PHP
jquery HotKeys轻松搞定键盘事件代码
2008/08/30 Javascript
IE8 chrome中table隔行换色解决办法
2010/07/09 Javascript
老生常谈Javascript中的原型和this指针
2016/10/09 Javascript
angularjs之$timeout指令详解
2017/06/13 Javascript
关于在vue-cli中使用微信自动登录和分享的实例
2017/06/22 Javascript
微信小程序 自定义消息提示框
2017/08/06 Javascript
详解nodejs中express搭建权限管理系统
2017/09/15 NodeJs
react-router4 配合webpack require.ensure 实现异步加载的示例
2018/01/18 Javascript
解决vue-cli创建项目的loader问题
2018/03/13 Javascript
小程序实现录音功能
2020/09/22 Javascript
JavaScript实现滑块验证解锁
2021/01/07 Javascript
Python中自定义函数的教程
2015/04/27 Python
新手如何快速入门Python(菜鸟必看篇)
2017/06/10 Python
Python3使用PyQt5制作简单的画板/手写板实例
2017/10/19 Python
Python离线安装PIL 模块的方法
2019/01/08 Python
python实现画循环圆
2019/11/23 Python
Python3 使用selenium插件爬取苏宁商家联系电话
2019/12/23 Python
python实现百度OCR图片识别过程解析
2020/01/17 Python
HTML5之SVG 2D入门12—SVG DOM及DOM操作介绍
2013/01/30 HTML / CSS
世界领先的26岁以下学生和青少年旅行预订网站:StudentUniverse
2018/07/01 全球购物
Brydge英国:适用于Apple iPad和Microsoft Surface Pro的蓝牙键盘
2019/05/16 全球购物
创业女性典型材料
2014/05/02 职场文书
求职信结尾怎么写
2014/05/26 职场文书
会议欢迎标语
2014/06/30 职场文书
入党积极分子学习优秀共产党员先进事迹思想汇报
2014/09/13 职场文书
党的群众路线教育实践活动心得体会(医院)
2014/11/03 职场文书
敬老院志愿者活动总结
2015/05/06 职场文书
运动会班级前导词
2015/07/20 职场文书
就业指导讲座心得体会
2016/01/15 职场文书
Django给表单添加honeypot验证增加安全性
2021/05/06 Python