
原文: Convolutional Neural Networks in iOS 10 and macOS
作者: Geppy Parziale
譯者:ALEX吳浩文
蘋果在iOS 10和macOS 10.12的Metal Performance Shaders框架和Accelerate框架里,引入了新的卷積神經(jīng)網(wǎng)絡(luò)APIs。
我在一篇之前的文章里已經(jīng)介紹了 iOS上的機器學(xué)習(xí)(ML)和人工神經(jīng)網(wǎng)絡(luò)(ANN)。如果你對這些不熟悉,建議你先讀讀那篇文章。
我最近參加了 CVPR 2016 ,一個計算機視覺與模式識別會議。我在那得知最近卷積神經(jīng)網(wǎng)絡(luò)被世界各地的大學(xué)和公司用于幾乎所有的研究工作。卷積神經(jīng)網(wǎng)絡(luò)在計算機視覺的不同領(lǐng)域的流行,再加上手機上又快又強的GPU,使卷積神經(jīng)網(wǎng)絡(luò)也成為移動開發(fā)的一個極具吸引力的利器。卷積神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí)打開了移動應(yīng)用創(chuàng)新的大門。
我從五年前在蘋果工作時開始接觸卷積神經(jīng)網(wǎng)絡(luò)(CNNs)。當(dāng)時可不像今天,可用的文獻和工具都極其有限。我曾用CNNs建立一個iOS和OS X上的光學(xué)字符識別(OCR)。它是iOS 5的一個實現(xiàn)。OCR的準(zhǔn)確度是驚人的,即使當(dāng)時設(shè)備上的實現(xiàn)是用的CPU。
在這之后,我繼續(xù)對其他類型的應(yīng)用CNNs。最近,我用CNNs來進行人臉識別和面部表情識別。我們得到的結(jié)果是驚人的。
卷積(Convolution)
CNN把一種大量使用的很常見的信號處理操作稱為卷積。卷積是把數(shù)組(或矩陣)的鄰近元素進行加權(quán)求和。其中使用的權(quán)重是由一個輸入數(shù)組定義的,它通常被稱為核(kernel)、濾鏡(filter)或卷積的遮罩(mask)。
卷積是一種非常重要的數(shù)字信號(音頻、視頻、圖像)處理,因此圖形處理單元(GPU)優(yōu)化了它。如果你想從事CNNs工作,GPU是最重要的實現(xiàn)工具。
作為人類,我們也在我們的日常活動中使用卷積,尤其那些涉及到我們五感的活動。例如,當(dāng)我們聽音樂或盯著東西看時,我們的大腦對外部世界的聲音和光信息在執(zhí)行著每秒數(shù)百萬次的卷積。
一維卷積的例子
讓我們構(gòu)建一個例子來更好地理解卷積是如何工作的。下面的圖顯示了一個輸入數(shù)組或一維信號x[n]與一維核數(shù)組w[n]的卷積。

在這個例子中,我任意假設(shè)輸入數(shù)組的值是1、2、…7,核數(shù)組的值是1、1、2、1、1。前面的圖顯示了輸出序列y[n]的元素(或樣本)y[2]是如何被計算的。
在一般情況下,核的個數(shù)往往是奇數(shù),這使得在被計算元素周圍的加權(quán)和計算是對稱的。遠小于輸入序列x[n]的核也是很常見的。核的中央元素被用作我們想處理的輸入信號中的元素的重量,其他元素則作為被計算元素左右兩邊的元素的權(quán)重。
概括這個例子,如果x[n]是一個輸入序列,w[m]是一個核序列,那么卷積操作的結(jié)果y[n]可以用以下的數(shù)學(xué)表達式表示:

注意一下序列w[m]在操作中第一個被反轉(zhuǎn)和轉(zhuǎn)化。
如果我們用以前的數(shù)學(xué)公式來計算之前例子中y[n]的每個元素,我們得到以下結(jié)果:

既然卷積由相鄰元素的順序定義,那么靠近數(shù)組結(jié)尾的輸出元素自然存在邊界條件。為了避免這個問題,一鐘很常見的做法是在輸入序列x[n]的兩端添加足夠的元素(稱為鬼元素)。如果你添加0,這個操作被稱為零填充。其他方法也可以。在實現(xiàn)卷積時,你需要解決填充問題。
Swift的卷積
讓我們來看看如何用Swift實現(xiàn)卷積。假設(shè)我們有以下的輸入數(shù)組x和核數(shù)組w:
let x: [Float] = [1, 2, 3, 4, 5], M = x.count
let w: [Float] = [1, 2, 3], N = w.count
let T = N+M-1 // 這個之后需要
在我們開始之前,如上所述讓我們添加N-1個0到序列x,和M-1個0到核來容納計算。你可以使用以下函數(shù):
func pad(sequence x: [Float], other sequence: [Float]) -> [Float] {
return x + [Float](repeatElement(0, count: sequence.count-1))
}
所以,填充過的新序列是:
let paddedX: [Float] = pad(sequence: x, other: kernel)