outTexture.getBytes(&buffer, bytesPerRow: T*sizeof(Float32.self), from: region, mipmapLevel: 0)
}
最后,我们需要用Metal kernel函数。这里我们执行卷积的地方。一个非常简单的实现可以这样:
二维卷积
在处理图像时,卷积在二维数据上进行。这时,图像是由矩阵X[n,m]而不是一维数组来表示。
下图显示了如何计算输出矩阵Y的元素Y[1,2]的卷积结果,高亮显示的元素是卷积运算的中央。
和一维卷积一样,我也可以在这里给出二维情况下Swift的例子,Accelerate框架的和Metal的,但我把这个留给你们当作练习。记得沿行和列反转W。此外,记得填充P-1和Q-1个零到矩阵X的两端。
卷积神经网络
下图突出了一个全连接的神经网络,并且有2个隐藏层(L1和L2)。
正如 前一篇文章中 讨论的,网络由层组成,每一层由神经元组成。让我们看看隐藏层L1的神经元N0,它的输入是上一层L0的每个神经元的输出的加权和:
关于这个表达式,
是L1层的神经元N0的输入,
是L0层的神经元Ni的输出,
是L0层的神经元Ni和L1层的神经元N0之间的权重。
同样的,下面的方程表示了L1层神经元N1的输入:
类似的方程适用于L1层其余的神经元,L2层和L3层也是如此。
如果我们用一个列数组表示L1层的输入,用一个矩阵表示L0层和L1层之间的权重,用一个列数组表示输出层L0,我们可以得到以下方程:
现在,如果我想要用这个全连接的神经网络来处理图像,输入层必须有一定数量的神经元,其个数与输入图像的像素相等。所以,我们需要一个有10000个神经元的输入层来处理仅仅100x100像素的图像。这意味着在前面的方程(4)中矩阵W的列数是10000。这计算实在消耗巨大。此外,每个像素独立于相邻像素处理。
因此,我们需要优化处理。如果我们仔细观察前面的方程(2)和(3),我们会注意到,它们看起来非常类似于卷积方程(1)。因此,与其计算不同矩阵的乘法(每层一个),我们可以使用快速卷积算法。这使我们能够用CNN实现取代全连接的神经网络实现。
类似于全连接神经网络,CNN就是一连续的层。下面的图强调了一个典型的CNN结构(其他结构在文献中有被提出):
每个CNN层是由两个操作组成:一个卷积后跟一个池。下图突出了一个CNN的第一个卷积层:
在iOS 10和macOS 10.12中,Metal Performance Shaders框架(Accelerate框架也有)提供了一个新的类来配置CNN的特定的卷积操作。
正如你在前面的图中所看到的,输入图像被分解成3个通道(红、绿、蓝)。每个通道与不同的训练得到的核进行卷积。这3个结果再组成特征图。在前面的图中,我展示的只是4特征图。在一个真正的CNN里,通常有16、32或更多特征图。所以,你需要16x3或32x3个核。