outTexture.getBytes(&buffer, bytesPerRow: T*sizeof(Float32.self), from: region, mipmapLevel: 0)
}
最后,我們需要用Metal kernel函數(shù)。這里我們執(zhí)行卷積的地方。一個(gè)非常簡(jiǎn)單的實(shí)現(xiàn)可以這樣:

二維卷積
在處理圖像時(shí),卷積在二維數(shù)據(jù)上進(jìn)行。這時(shí),圖像是由矩陣X[n,m]而不是一維數(shù)組來(lái)表示。
下圖顯示了如何計(jì)算輸出矩陣Y的元素Y[1,2]的卷積結(jié)果,高亮顯示的元素是卷積運(yùn)算的中央。

和一維卷積一樣,我也可以在這里給出二維情況下Swift的例子,Accelerate框架的和Metal的,但我把這個(gè)留給你們當(dāng)作練習(xí)。記得沿行和列反轉(zhuǎn)W。此外,記得填充P-1和Q-1個(gè)零到矩陣X的兩端。
卷積神經(jīng)網(wǎng)絡(luò)
下圖突出了一個(gè)全連接的神經(jīng)網(wǎng)絡(luò),并且有2個(gè)隱藏層(L1和L2)。

正如 前一篇文章中 討論的,網(wǎng)絡(luò)由層組成,每一層由神經(jīng)元組成。讓我們看看隱藏層L1的神經(jīng)元N0,它的輸入是上一層L0的每個(gè)神經(jīng)元的輸出的加權(quán)和:

關(guān)于這個(gè)表達(dá)式,

是L1層的神經(jīng)元N0的輸入,

是L0層的神經(jīng)元Ni的輸出,

是L0層的神經(jīng)元Ni和L1層的神經(jīng)元N0之間的權(quán)重。
同樣的,下面的方程表示了L1層神經(jīng)元N1的輸入:

類(lèi)似的方程適用于L1層其余的神經(jīng)元,L2層和L3層也是如此。
如果我們用一個(gè)列數(shù)組表示L1層的輸入,用一個(gè)矩陣表示L0層和L1層之間的權(quán)重,用一個(gè)列數(shù)組表示輸出層L0,我們可以得到以下方程:

現(xiàn)在,如果我想要用這個(gè)全連接的神經(jīng)網(wǎng)絡(luò)來(lái)處理圖像,輸入層必須有一定數(shù)量的神經(jīng)元,其個(gè)數(shù)與輸入圖像的像素相等。所以,我們需要一個(gè)有10000個(gè)神經(jīng)元的輸入層來(lái)處理僅僅100x100像素的圖像。這意味著在前面的方程(4)中矩陣W的列數(shù)是10000。這計(jì)算實(shí)在消耗巨大。此外,每個(gè)像素獨(dú)立于相鄰像素處理。
因此,我們需要優(yōu)化處理。如果我們仔細(xì)觀察前面的方程(2)和(3),我們會(huì)注意到,它們看起來(lái)非常類(lèi)似于卷積方程(1)。因此,與其計(jì)算不同矩陣的乘法(每層一個(gè)),我們可以使用快速卷積算法。這使我們能夠用CNN實(shí)現(xiàn)取代全連接的神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)。
類(lèi)似于全連接神經(jīng)網(wǎng)絡(luò),CNN就是一連續(xù)的層。下面的圖強(qiáng)調(diào)了一個(gè)典型的CNN結(jié)構(gòu)(其他結(jié)構(gòu)在文獻(xiàn)中有被提出):

每個(gè)CNN層是由兩個(gè)操作組成:一個(gè)卷積后跟一個(gè)池。下圖突出了一個(gè)CNN的第一個(gè)卷積層:

在iOS 10和macOS 10.12中,Metal Performance Shaders框架(Accelerate框架也有)提供了一個(gè)新的類(lèi)來(lái)配置CNN的特定的卷積操作。
正如你在前面的圖中所看到的,輸入圖像被分解成3個(gè)通道(紅、綠、藍(lán))。每個(gè)通道與不同的訓(xùn)練得到的核進(jìn)行卷積。這3個(gè)結(jié)果再組成特征圖。在前面的圖中,我展示的只是4特征圖。在一個(gè)真正的CNN里,通常有16、32或更多特征圖。所以,你需要16x3或32x3個(gè)核。