下面的代码展示了如何创建CNN层的卷积操作。
let convDesc = MPSCNNConvolutionDescriptor(kernelWidth: 3, kernelHeight: 3, inputFeatureChannels: 3, outputFeatureChannels: 4, neuronFilter: nil)
var conv0 = MPSCNNConvolution(device: device, convolutionDescriptor: convDesc, kernelWeights: featureFilters, biasTerms: convBias, flags: .none)
类似于其他Metal对象,我们需要使用描述符来创建卷积。在这个特殊的例子里,我们使用MPSCNNConvolutionDescriptor类。之后,我们可以创建MPSCNNConvolution类的一个实例。
让我们看看池操作。
池
CNN层中的另一个操作是池。它基本上是一个压缩操作,目的是缩减图像大小,它贯穿图像处理的输入和输出。
池有双重功能。首先,它降低了图像分辨率,减少了传递到下一CNN层的图像的详细信息。第二,它降低了下一层的计算量。
有不同的技术来减少图像大小。苹果提供了两种类型的池:Max池和Average池。下面的图显示了Max池和Average池是如何工作的。
Max池取图像在一个区域内的最大像素值。Average池则取平均值。例如,前例中的图像在Average池里产生一个值为(90 + 96 + 75 + 96)/ 4 = 84.75的像素。
使用MPSCNNPoolingMax类,我们可以用下面的代码转化之前的图:
var pool = MPSCNNPoolingMax(device: device, kernelWidth: 2, kernelHeight: 2, strideInPixelsX: 2, strideInPixelsY: 2)
类似地,你可以使用MPSCNNPoolingAverage得到Average池:
var pool = MPSCNNPoolingAverage(device: device, kernelWidth: 2, kernelHeight: 2, strideInPixelsX: 2, strideInPixelsY: 2)
全连接层
链接不同的卷积层后,CNN的最后一层是全连接层。因为它可以被认为是一个特殊的卷积层,Metal Performance Shaders框架给全连接层提供了一个非常类似的API:
let fcDesc = MPSCNNConvolutionDescriptor(kernelWidth: kWidth, kernelHeight: kHeight, inputFeatureChannels: 128, outputFeatureChannels: 1)
var fc = MPSCNNFullyConnected(device: device, convolutionDescriptor: fcDesc, kernelWeights: fcFeatureFilters, biasTerms: fcBias, flags: .none)
MPSImage和MPSTemporaryImage
我们如何处理Metal CNN中的数据?Metal Performance Shaders框架提供了两个新类:MPSImage和MPSTemporaryImage。
正如先前所展示的,卷积层的输出生成多个特征图(16或32)。MPSImage用通道来组成这些特征图。因为MTLTexure只有4个通道(RGBA),苹果推出了两个新类来处理多于4通道的情况。所以,MPSImage实际是Metal的一个二维纹理数组的多个片。由于图像是组成的,所以MPSImage的每个像素包含4个通道。也因此,32特征图是由8(=32/4)片组成的MPSImage表示的。
这是你用MPSImage所需要的API:
let imgDesc = MPSImageDescriptor(channelFormat: .float16, width: width, height: height, featureChannels: 32)
var img = MPSImage(device: device, imageDescriptor: imgDesc)
你使用MPSImage来作CNN的输入和输出图像。对于中间结果,你应该使用MPSTemporaryImage类。使用这个临时图像的好处是一旦命令被提交到缓冲区它就会被丢弃。这减少了内存分配和CPU耗费。
创建一个MPSTemporaryImage和MPSImage非常相似:
let img1Desc = MPSImageDescriptor(channelFormat: float16, width: 40, height: 40, featureChannels: 16)
img1 = MPSTemporaryImage(device: device, imageDescriptor: img1Desc)
CNN训练
为了用CNN,你需要先训练。训练生成一组权重,然后在推理阶段使用它们。Metal Performance Shaders的APIs只允许你实现CNN推理。训练阶段不可以用这些APIs。苹果建议使用第三方工具。
神经网络的结构、层数和每层的神经元数量需要在这个领域的一定的经验。除了知道它背后的数学,你需要大量的CNNs的实践经验。
总结
在这篇文章中,我为你们概述了iOS 10和macOS 10.12中Metal Performance Shaders框架的新的APIs。在之前的文章中,我也为你们介绍了iOS上的机器学习(ML)和人工神经网络(ANN)。