对于多通道的理解,我们以图片为例,平时大家看到的图片一般都是RGB三色通道,所以在实际处理图像的时候,多通道是很常见的,这个RGB三种颜色的图像可以表示为(3,H,W)的三维数组,再加上批量样本数的维度(N,C,H,W)就是四维数组。
多通道的输入,单通道的输出
from mxnet import autograd,nd
from mxnet.gluon import nn
import d2lzh as d2l
#沿着X和K的第0维(通道维)遍历,*让列表对成为元组对(位置参数)
#互相关运算然后将通道结果累加
def corr2d_multi_in(X,K):
return nd.add_n(*[d2l.corr2d(x,k) for x,k in zip(X,K)])
X=nd.arange(18).reshape(shape=(2,3,3))
K=nd.arange(8).reshape(shape=(2,2,2))
corr2d_multi_in(X,K)
'''
[[268. 296.]
[352. 380.]]
<NDArray 2x2 @cpu(0)>
'''
由于通道维的结果进行了累加运算,所以最终输出的通道数总是为1
多通道的输出
如果想要得到多个通道的输出,我们可以为每个输出通道分别创建形状为(输入通道数,高,宽)的核数组,将他们在输出通道维进行连结,这样卷积核的形状就变成了(输出通道数,输入通道数,高,宽),这样互相关的运算就是多通道的输出,看一个例子(跟上图的维度数量有点区别),从代码的结果来看,也明白了输出的个数取决于卷积核的输出通道数(不是输入通道数)
#对K的第0维遍历,每次和输入的X做互相关运算,最后通过stack进行连结
def corr2d_multi_in_out(X,K):
return nd.stack(*[corr2d_multi_in(X,k) for k in K])
#构造一个多维卷积核
K=nd.stack(K,K+1,K+2)
print(K.shape)#(3, 2, 2, 2)
print(corr2d_multi_in_out(X,K))
'''
[[[268. 296.]
[352. 380.]]
[[320. 356.]
[428. 464.]]
[[372. 416.]
[504. 548.]]]
<NDArray 3x2x2 @cpu(0)>
'''
1x1的卷积层
卷积窗口的形状是1x1(高和宽为1)的多通道,称之为1x1卷积层,其中的卷积运算叫做1x1卷积,对于这样一个卷积核,可能会有疑问,是否能用于前面文章中提到的边缘检测?是的,这种卷积核不能识别相邻元素的差别了,那它的作用是什么。
实际上,1x1卷积的主要计算是发生在通道维上(这也是为什么重点讲解通道),所以输出中的元素是来自输入中的高和宽在相同位置的元素在不同通道之间的加权和。
假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么1x1卷积层的作用和全连接层等价。一般都将这个1x1卷积层用来调整网络层之间的通道数,并控制模型复杂度,验证下是否等价于全连接层。
#使用全连接层中的矩阵乘法(dot点积)来实现1x1卷积
#全连接层的矩阵乘法
def corr2d_multi_in_out_1x1(X,K):
c_i,h,w=X.shape
c_o=K.shape[0]
X=X.reshape((c_i,h*w))#转矩阵(3,9)
K=K.reshape((c_o,c_i))#(2,3)
Y=nd.dot(K,X)
return Y.reshape((c_o,h,w))
XX=nd.random.uniform(shape=(3,3,3))
KK=nd.random.uniform(shape=(2,3,1,1))
FullyY=corr2d_multi_in_out_1x1(XX,KK)
YY=corr2d_multi_in_out(XX,KK)
print(FullyY,YY,(FullyY-YY).mean().asscalar())
'''
[[[1.1940831 0.9574814 0.7932974 ]
[1.1942188 0.9394239 0.984771 ]
[0.8664603 0.93568623 0.9297624 ]]
[[0.5133847 0.27369863 0.34415516]
[0.32436582 0.48742533 0.4451369 ]
[0.14943217 0.41055933 0.39615852]]]
<NDArray 2x3x3 @cpu(0)>
[[[1.1940832 0.9574814 0.7932974 ]
[1.1942186 0.9394239 0.984771 ]
[0.8664603 0.9356863 0.9297624 ]]
[[0.5133847 0.27369866 0.34415516]
[0.32436582 0.48742533 0.4451369 ]
[0.14943217 0.41055933 0.39615852]]]
<NDArray 2x3x3 @cpu(0)>
-4.967054e-09
'''
浮点数存在一点误差,可以看出全连接层的结果和1x1卷积的结果是一样
nd.add_n和nd.stack的用法
from mxnet import autograd,nd
from mxnet.gluon import nn
import d2lzh as d2l
x=nd.arange(10).reshape(2,5)
y=nd.arange(10,20).reshape(2,5)
z=nd.arange(30,40).reshape(2,5)
print(x,y,nd.add_n(x,y))
'''
[[0. 1. 2. 3. 4.]
[5. 6. 7. 8. 9.]]
<NDArray 2x5 @cpu(0)>
[[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]]
<NDArray 2x5 @cpu(0)>
[[10. 12. 14. 16. 18.]
[20. 22. 24. 26. 28.]]
<NDArray 2x5 @cpu(0)>
'''
print(nd.stack(x,y,z))
'''
[[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]]
[[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]]
[[30. 31. 32. 33. 34.]
[35. 36. 37. 38. 39.]]]
<NDArray 3x2x5 @cpu(0)>
'''
print(nd.stack(x,y,z,axis=1))
'''
[[[ 0. 1. 2. 3. 4.]
[10. 11. 12. 13. 14.]
[30. 31. 32. 33. 34.]]
[[ 5. 6. 7. 8. 9.]
[15. 16. 17. 18. 19.]
[35. 36. 37. 38. 39.]]]
<NDArray 2x3x5 @cpu(0)>
'''
#stack需要注意和concat的用法区别
print(nd.concat(x,y,dim=0))
'''
[[ 0. 1. 2. 3. 4.]
[ 5. 6. 7. 8. 9.]
[10. 11. 12. 13. 14.]
[15. 16. 17. 18. 19.]]
<NDArray 4x5 @cpu(0)>
'''