下面的代碼展示了如何創(chuàng)建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對象,我們需要使用描述符來創(chuàng)建卷積。在這個特殊的例子里,我們使用MPSCNNConvolutionDescriptor類。之后,我們可以創(chuàng)建MPSCNNConvolution類的一個實例。
讓我們看看池操作。
池
CNN層中的另一個操作是池。它基本上是一個壓縮操作,目的是縮減圖像大小,它貫穿圖像處理的輸入和輸出。
池有雙重功能。首先,它降低了圖像分辨率,減少了傳遞到下一CNN層的圖像的詳細信息。第二,它降低了下一層的計算量。
有不同的技術(shù)來減少圖像大小。蘋果提供了兩種類型的池:Max池和Average池。下面的圖顯示了Max池和Average池是如何工作的。

Max池取圖像在一個區(qū)域內(nèi)的最大像素值。Average池則取平均值。例如,前例中的圖像在Average池里產(chǎn)生一個值為(90 + 96 + 75 + 96)/ 4 = 84.75的像素。
使用MPSCNNPoolingMax類,我們可以用下面的代碼轉(zhuǎn)化之前的圖:
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
我們?nèi)绾翁幚鞰etal CNN中的數(shù)據(jù)?Metal Performance Shaders框架提供了兩個新類:MPSImage和MPSTemporaryImage。
正如先前所展示的,卷積層的輸出生成多個特征圖(16或32)。MPSImage用通道來組成這些特征圖。因為MTLTexure只有4個通道(RGBA),蘋果推出了兩個新類來處理多于4通道的情況。所以,MPSImage實際是Metal的一個二維紋理數(shù)組的多個片。由于圖像是組成的,所以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的輸入和輸出圖像。對于中間結(jié)果,你應(yīng)該使用MPSTemporaryImage類。使用這個臨時圖像的好處是一旦命令被提交到緩沖區(qū)它就會被丟棄。這減少了內(nèi)存分配和CPU耗費。
創(chuàng)建一個MPSTemporaryImage和MPSImage非常相似:
let img1Desc = MPSImageDescriptor(channelFormat: float16, width: 40, height: 40, featureChannels: 16)
img1 = MPSTemporaryImage(device: device, imageDescriptor: img1Desc)
CNN訓(xùn)練
為了用CNN,你需要先訓(xùn)練。訓(xùn)練生成一組權(quán)重,然后在推理階段使用它們。Metal Performance Shaders的APIs只允許你實現(xiàn)CNN推理。訓(xùn)練階段不可以用這些APIs。蘋果建議使用第三方工具。
神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)、層數(shù)和每層的神經(jīng)元數(shù)量需要在這個領(lǐng)域的一定的經(jīng)驗。除了知道它背后的數(shù)學(xué),你需要大量的CNNs的實踐經(jīng)驗。
總結(jié)
在這篇文章中,我為你們概述了iOS 10和macOS 10.12中Metal Performance Shaders框架的新的APIs。在之前的文章中,我也為你們介紹了iOS上的機器學(xué)習(xí)(ML)和人工神經(jīng)網(wǎng)絡(luò)(ANN)。