Tensorflow卷积实现原理+手写python代码实现卷积教程
作者:huachao1001 发布时间:2021-12-21 20:12:52
从一个通道的图片进行卷积生成新的单通道图的过程很容易理解,对于多个通道卷积后生成多个通道的图理解起来有点抽象。本文以通俗易懂的方式讲述卷积,并辅以图片解释,能快速理解卷积的实现原理。最后手写python代码实现卷积过程,让Tensorflow卷积在我们面前不再是黑箱子!
注意:
本文只针对batch_size=1,padding='SAME',stride=[1,1,1,1]进行实验和解释,其他如果不是这个参数设置,原理也是一样。
1 Tensorflow卷积实现原理
先看一下卷积实现原理,对于in_c个通道的输入图,如果需要经过卷积后输出out_c个通道图,那么总共需要in_c * out_c个卷积核参与运算。参考下图:
如上图,输入为[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定义的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图,看完这个图后,如果你还看不懂卷积的计算过程,你可以来打我。。。。
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的卷积,其实输出通道数不影响我们学习卷积原理。后面如果有机会的话,我们去实现一个更加健全,完整的卷积。
来源:https://blog.csdn.net/huachao1001/article/details/79120521
猜你喜欢
- JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。 JSO
- 很多小伙伴在学习Django的时候,总是搞不定版本的问题,下面来一起看一张表,轻松解决Python版本和Django版本的兼容问题。Djan
- 在图像处理以及图像特效中,经常会用到一种成高斯分布的蒙版,蒙版可以用来做图像融合,将不同内容的两张图像结合蒙版,可以营造不同的艺术效果。这里
- 其中如果有问题,有更好的意见或者建议都可在最后留言,都将对您感激不尽。 具体的代码如下: <!DOCTYPE HTML> <
- input高级限制级用法1.取消按钮按下时的虚线框 在input里添加属性值 hideFocus 或者 HideFocus=true2.只读
- 一、前情提要为什么要使用Scrapy 框架?前两篇深造篇介绍了多线程这个概念和实战多线程网页爬取多线程爬取网页项目实战经过之前的学习,我们基
- 前言近期在工作中遇到某表某字段是可扩展数据内容,信息以逗号分隔生成的,现需求要根据此字段数据在其它表查询相关的内容展现出来,第一想法是切割数
- 导语日常开发中,定位程序异常,追溯事件发生场景都需要通过日志记录的方式。可以说一个好的开发日志设计可以让开发人员在后续项目维护的过程中节省时
- 本文实例讲述了JS实现字符串转驼峰格式的方法。分享给大家供大家参考,具体如下:实现效果如:border-bottom-color =>
- 自己前端开发中常用到的一些技巧及问题解决方法,会常更新,希望对前端路上的朋友有帮助。1、文章标题列表中日期居右显示的方法(提供了两种方法,使
- 问题:pycharm无法调用pip安装的包原因:pycharm没有设置解析器解决方法:打开pycharm->File->Sett
- 为什么使用事务 当对多个表进行更新的时候,某条执行失败。为了保持数据的完整性,需要使用事务回滚。 显示设置事务 代码如下:beg
- 本文分析了让ThinkPHP的模板引擎达到最佳效率的方法。分享给大家供大家参考,具体如下:默认情况下ThinkPHP框架系统默认使用的模板引
- 介绍本文将介绍基于OpenCV实现视频的循环播放。有以下三个步骤:首先设置一个frame的设置参数frame_counter,值为0在读帧时
- 本文实例讲述了Python Matplotlib库安装与基本作图。分享给大家供大家参考,具体如下:不论是数据挖掘还是数据建模,都免不了数据可
- upload.htm <html><head><title>网站维护 -
- 使用pytorch的dataloader报错:RuntimeError: stack expects each tensor to be e
- 本文实例为大家分享了python正则提取电话的具体代码,供大家参考,具体内容如下主要用到正则import reimport xlrddef
- 废话不多说,直接上代码吧~model.zero_grad()optimizer.zero_grad()首先,这两种方式都是把模型中参数的梯度
- 层及索引levels,刚开始学习pandas的时候没有太多的操作关于groupby,仅仅是简单的count、sum、size等等,没有更深入