在本章中,將會學到:c++
l 如何使用Kelp.Net來執行本身的測試算法
l 如何編寫測試數據庫
l 如何對函數進行基準測試編程
Kelp.Net是一個用c#編寫的深度學習庫。因爲可以將函數鏈到函數堆棧中,它在一個很是靈活和直觀的平臺中提供了驚人的功能。它還充分利用OpenCL語言平臺,在支持cpu和gpu的設備上實現無縫操做。深度學習是一個很是強大的工具,對Caffe和Chainer模型加載的本機支持使這個平臺更增強大。您將看到,只需幾行代碼就能夠建立一個100萬個隱藏層的深度學習網絡。小程序
Kelp.Net還使得從磁盤存儲中保存和加載模型變得很是容易。這是一個很是強大的特性,容許您執行訓練、保存模型,而後根據須要加載和測試。它還使代碼的產品化變得更加容易,而且真正地將訓練和測試階段分離開來。c#
其中,Kelp.Net是一個很是強大的工具,能夠幫助你更好地學習和理解各類類型的函數、它們的交互和性能。例如,你可使用不一樣的優化器在相同的網絡上運行測試,並經過更改一行代碼來查看結果。此外,能夠輕鬆地設計你的測試,以查看使用不一樣批處理大小、隱藏層數、紀元、和更多內容。數組
什麼是深度學習?
深度學習是機器學習和人工智能的一個分支,它使用許多層次的神經網絡層(若是你願意,能夠稱之爲層次結構)來完成它的工做。在不少狀況下,這些網絡的創建是爲了反映咱們對人類大腦的認知,神經元像錯綜複雜的網狀結構同樣將不一樣的層鏈接在一塊兒。這容許以非線性的方式進行數據處理。每一層都處理來自上一層的數據(固然,第一層除外),並將其信息傳遞到下一層。幸運的話,每一層都改進了模型,最終,咱們實現了目標並解決了問題。網絡
OpenCL
Kelp.Net 大量使用了開源計算語言(OpenCL).架構
OpenCL認爲計算系統是由許多計算設備組成的,這些計算設備能夠是中央處理器(CPU),也能夠是附加在主機處理器(CPU)上的圖形處理單元(GPU)等加速器。在OpenCL設備上執行的函數稱爲內核。單個計算設備一般由幾個計算單元組成,這些計算單元又由多個處理元素(PS)組成。一個內核執行能夠在全部或多個PEs上並行運行。框架
在OpenCL中,任務是在命令隊列中調度的。每一個設備至少有一個命令隊列。OpenCL運行時將調度數據的並行任務分紅幾部分,並將這些任務發送給設備處理元素。
OpenCL定義了一個內存層次結構:
Global:由全部處理元素共享,而且具備高延遲。
Read-only:更小,更低的延遲,可由主機CPU寫入,但不包括計算設備。
Local:由流程元素組共享。
Per-elemen:私有內存。
OpenCL還提供了一個更接近數學的API。這能夠在固定長度向量類型的公開中看到,好比float4(單精度浮點數的四個向量),它的長度爲二、三、四、8和16。若是你接觸了更多的Kelp.Net並開始建立本身的函數,你將會遇到OpenCL編程。如今,只要知道它的存在就足夠了,並且它正在被普遍地使用。
OpenCL 層次結構
在Kelp.Net各類OpenCL資源的層次結構以下圖所示:
讓咱們更詳細地描述這些。
Compute kernel
內核對象封裝在程序中聲明的特定內核函數,以及執行此內核函數時使用的參數值。
Compute program
由一組內核組成的OpenCL程序。程序還能夠包含內核函數和常量數據調用的輔助函數。
Compute sampler
描述如何在內核中讀取圖像時對圖像進行採樣的對象。圖像讀取函數以採樣器做爲參數。採樣器指定圖像尋址模式(表示如何處理範圍外的座標)、過濾模式以及輸入圖像座標是規範化仍是非規範化值。
Compute device
計算設備是計算單元的集合。命令隊列用於將命令排隊到設備。命令示例包括執行內核或讀寫內存對象。OpenCL設備一般對應於GPU、多核CPU和其餘處理器,如數字信號處理器(DSP)和cell/B.E.處理器。
Compute resource
能夠由應用程序建立和刪除的OpenCL資源。
Compute object
在OpenCL環境中由句柄標識的對象。
Compute context
計算上下文是內核執行的實際環境和定義同步和內存管理的域。
Compute command queue
命令隊列是一個對象,它包含將在特定設備上執行的命令。命令隊列是在上下文中的特定設備上建立的。對隊列的命令按順序排隊,但能夠按順序執行,也能夠不按順序執行。
Compute buffer
存儲線性字節集合的內存對象。可使用在設備上執行的內核中的指針來訪問緩衝區對象。
Compute event
事件封裝了操做(如命令)的狀態。它可用於同步上下文中的操做。
Compute image
存儲2D或3D結構數組的內存對象。圖像數據只能經過讀寫函數訪問。讀取函數使用採樣器。
Compute platform
主機加上OpenCL框架管理的設備集合,容許應用程序共享資源並在平臺上的設備上執行內核。
Compute user event
這表示用戶建立的事件。
Kelp.Net Framework
函數
函數是Kelp.Net神經網絡的基本組成部分。單個函數在函數堆棧中連接在一塊兒,以建立功能強大且可能複雜的網絡鏈。
咱們須要瞭解四種主要的函數類型:
Single-input functions 單輸入函數
Dual-input functions 雙輸入函數
Multi-input functions 多輸入函數
Multi-output functions 多輸出函數
當從磁盤加載網絡時,函數也被連接在一塊兒。
每一個函數都有一個向前和向後的方法。
public abstract NdArray[] Forward(params NdArray[] xs); public virtual void Backward([CanBeNull] params NdArray[] ys){}
函數棧
函數堆棧是在向前、向後或更新傳遞中同時執行的函數層。當咱們建立一個測試或從磁盤加載一個模型時,將建立函數堆棧。下面是一些函數堆棧的例子。
它們能夠小而簡單:
FunctionStack nn = new FunctionStack( new Linear(2, 2, name: "l1 Linear"), new Sigmoid(name: "l1 Sigmoid"), new Linear(2, 2, name: "l2 Linear"));
它們也能夠在大一點:
FunctionStack nn = new FunctionStack( // Do not forget the GPU flag if necessary new Convolution2D(1, 2, 3, name: "conv1", gpuEnable: true), new ReLU(), new MaxPooling(2, 2), new Convolution2D(2, 2, 2, name: "conv2", gpuEnable: true), new ReLU(), new MaxPooling(2, 2), new Linear(8, 2, name: "fl3"), new ReLU(), new Linear(2, 2, name: "fl4") );
它們也能夠很是大:
FunctionStack nn = new FunctionStack( new Linear(neuronCount * neuronCount, N, name: "l1 Linear"),//L1 new BatchNormalization(N, name: "l1 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l1 LeakyReLU"), new Linear(N, N, name: "l2 Linear"), // L2 new BatchNormalization(N, name: "l2 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l2 LeakyReLU"), new Linear(N, N, name: "l3 Linear"), // L3 new BatchNormalization(N, name: "l3 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l3 LeakyReLU"), new Linear(N, N, name: "l4 Linear"), // L4 new BatchNormalization(N, name: "l4 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l4 LeakyReLU"), new Linear(N, N, name: "l5 Linear"), // L5 new BatchNormalization(N, name: "l5 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l5 LeakyReLU"), new Linear(N, N, name: "l6 Linear"), // L6 new BatchNormalization(N, name: "l6 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l6 LeakyReLU"), new Linear(N, N, name: "l7 Linear"), // L7 new BatchNormalization(N, name: "l7 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l7 ReLU"), new Linear(N, N, name: "l8 Linear"), // L8 new BatchNormalization(N, name: "l8 BatchNorm"), new LeakyReLU(slope: 0.000001, name: "l8 LeakyReLU"), new Linear(N, N, name: "l9 Linear"), // L9 new BatchNormalization(N, name: "l9 BatchNorm"), new PolynomialApproximantSteep(slope: 0.000001, name: "l9 PolynomialApproximantSteep"), new Linear(N, N, name: "l10 Linear"), // L10 new BatchNormalization(N, name: "l10 BatchNorm"), new PolynomialApproximantSteep(slope: 0.000001, name: "l10 PolynomialApproximantSteep"), new Linear(N, N, name: "l11 Linear"), // L11 new BatchNormalization(N, name: "l11 BatchNorm"), new PolynomialApproximantSteep(slope: 0.000001, name: "l11 PolynomialApproximantSteep"), new Linear(N, N, name: "l12 Linear"), // L12 new BatchNormalization(N, name: "l12 BatchNorm"), new PolynomialApproximantSteep(slope: 0.000001, name: "l12 PolynomialApproximantSteep"), new Linear(N, N, name: "l13 Linear"), // L13 new BatchNormalization(N, name: "l13 BatchNorm"), new PolynomialApproximantSteep(slope: 0.000001, name: "l13 PolynomialApproximantSteep"), new Linear(N, N, name: "l14 Linear"), // L14 new BatchNormalization(N, name: "l14 BatchNorm"), new PolynomialApproximantSteep(slope: 0.000001, name: "l14 PolynomialApproximantSteep"), new Linear(N, 10, name: "l15 Linear") // L15 );
函數字典
函數字典是一個可序列化的函數字典(如前所述)。當從磁盤加載網絡模型時,將返回一個函數字典,而且能夠像在代碼中建立函數堆棧同樣對其進行操做。函數字典主要用於Caffe數據模型加載器。
Caffe1
Kelp.Net是圍繞Caffe風格開發的,它支持許多特性。
Caffe爲多媒體科學家和實踐者提供了一個簡潔和可修改的框架,用於最早進的深度學習算法和一組參考模型。該框架是一個bsd許可的c++庫,帶有Python和MATLAB綁定,用於在普通架構上高效地培訓和部署通用卷積神經網絡和其餘深度模型。Caffe經過CUDA GPU計算知足了行業和互聯網規模的媒體需求,在一個K40或Titan GPU上天天處理超過4000萬張圖像(大約每張圖像2毫秒)。經過分離模型表示和實際實現,Caffe容許在平臺之間進行試驗和無縫切換,以簡化開發和部署,從原型機到雲環境。
鏈
「Chainer是一個靈活的神經網絡框架。一個主要的目標是靈活性,所以它必須使咱們可以簡單而直觀地編寫複雜的體系結構。」
Chainer採用了按運行定義的方案,即經過實際的正向計算動態地定義網絡。更準確地說,Chainer存儲的是計算曆史,而不是編程邏輯。例如,Chainer不須要任何東西就能夠將條件和循環引入到網絡定義中。按運行定義方案是Chainer的核心概念。這種策略也使得編寫多gpu並行化變得容易,由於邏輯更接近於網絡操做。
Kelp.Net能夠直接從磁盤加載Chainer模型。
Loss
Kelp.Net由一個抽象的LossFunction類組成,設計用於肯定如何評估損失的特定實例。
在機器學習中,損失函數或成本函數是將一個事件或一個或多個變量的值直觀地映射到一個實數上的函數,表示與該事件相關的一些成本。Kelp.Net提供了兩個開箱即用的損失函數:均方偏差和軟最大交叉熵。咱們能夠很容易地擴展它們以知足咱們的需求。
模型保存和加載
Kelp.Net使得經過調用一個簡單的類來保存和加載模型變得很是容易。ModelIO類同時提供了保存和加載方法,以便輕鬆地保存和加載到磁盤。下面是一個很是簡單的例子,在訓練、從新加載並對模型執行測試以後保存模型:
優化程序
優化算法根據模型的參數最小化或最大化偏差函數。參數的例子有權重和誤差。它們經過最小化損失來幫助計算輸出值並將模型更新到最優解的位置。擴展Kelp.Net以添加咱們本身的優化算法是一個簡單的過程,儘管添加OpenCL和資源方面的東西是一個協調的工做。
Kelp.Net提供了許多預約義的優化器,好比:
AdaDelta
AdaGrad
Adam
GradientClipping
MomentumSGD
RMSprop
SGD
這些都是基於抽象的優化器類。
數據集
Kelp.Net自己支持如下數據集:
CIFAR
MNIST
CIFAR
CIFAR數據集有兩種形式,CIFAR-10和CIFAR 100,它們之間的區別是類的數量。讓咱們簡要地討論一下二者。
CIFAR-10
CIFAR-10數據集包含10個類中的60000張32×32張彩色圖像,每一個類包含6000張圖像。有50,000張訓練圖像和10,000張測試圖像。數據集分爲五個訓練批次和一個測試批次,每一個測試批次有10,000張圖像。測試批次包含從每一個類中隨機選擇的1000個圖像。訓練批次包含隨機順序的剩餘圖像,可是一些訓練批次可能包含一個類的圖像多於另外一個類的圖像。在他們之間,每批訓練包含了5000張圖片。
CIFAR-100
CIFAR-100數據集與CIFAR-10同樣,只是它有100個類,每一個類包含600個圖像。每班有500張訓練圖片和100張測試圖片。CIFAR-100中的100個類被分爲20個超類。每一個圖像都有一個細標籤(它所屬的類)和一個粗標籤(它所屬的超類)。如下是CIFAR-100的類型列表:
Superclass |
Classes |
水生哺乳動物 |
海狸、海豚、水獺、海豹和鯨魚 |
魚 |
水族魚,比目魚,鰩魚,鯊魚和魚 |
花 |
蘭花、罌粟、玫瑰、向日葵和鬱金香 |
食品容器 |
瓶子、碗、罐子、杯子和盤子 |
水果和蔬菜 |
蘋果、蘑菇、桔子、梨和甜椒 |
家用電器設備 |
時鐘、電腦鍵盤、燈、電話和電視 |
家用傢俱 |
牀、椅子、沙發、桌子和衣櫃 |
昆蟲 |
蜜蜂、甲蟲、蝴蝶、毛蟲和蟑螂 |
大型食肉動物 |
熊、豹、獅子、老虎和狼 |
大型人造戶外用品 |
橋、城堡、房子、道路和摩天大樓 |
大型天然戶外景觀 |
雲、林、山、平原、海 |
大型雜食動物和食草動物 |
駱駝、牛、黑猩猩、大象和袋鼠 |
中等大小的哺乳動物 |
狐狸,豪豬,負鼠,浣熊和臭鼬 |
無脊椎動物 |
螃蟹、龍蝦、蝸牛、蜘蛛和蠕蟲 |
人 |
寶貝,男孩,女孩,男人,女人 |
爬行動物 |
鱷魚、恐龍、蜥蜴、蛇和烏龜 |
小型哺乳動物 |
倉鼠,老鼠,兔子,鼩鼱和松鼠 |
樹 |
楓樹、橡樹、棕櫚樹、松樹和柳樹 |
車輛1 |
自行車、公共汽車、摩托車、小貨車和火車 |
車輛2 |
割草機、火箭、有軌電車、坦克和拖拉機 |
MNIST
MNIST數據庫是一個手寫數字的大型數據庫,一般用於訓練各類圖像處理系統。該數據庫還普遍用於機器學習領域的培訓和測試。它有一個包含6萬個例子的訓練集和一個包含1萬個例子的測試集。數字的大小已經標準化,並集中在一個固定大小的圖像中,這使它成爲人們想要嘗試各類學習技術而不須要進行預處理和格式化的標準選擇:
測試
測試是實際的執行事件,也能夠說是小程序。因爲OpenCL的使用,這些程序是在運行時編譯的。要建立一個測試,您只須要提供一個封裝代碼的靜態運行函數。Kelp.Net提供了一個預配置的測試器,這使得添加咱們本身的測試變得很是簡單。
如今,這裏有一個簡單的XOR測試程序的例子:
public static void Run() { const int learningCount = 10000; Real[][] trainData = { new Real[] { 0, 0 }, new Real[] { 1, 0 }, new Real[] { 0, 1 }, new Real[] { 1, 1 } }; Real[][] trainLabel = { new Real[] { 0 }, new Real[] { 1 }, new Real[] { 1 }, new Real[] { 0 } }; FunctionStack nn = new FunctionStack( new Linear(2, 2, name: "l1 Linear"), new ReLU(name: "l1 ReLU"), new Linear(2, 1, name: "l2 Linear")); nn.SetOptimizer(new AdaGrad()); RILogManager.Default?.SendDebug("Training..."); for (int i = 0; i < learningCount; i++) { //use MeanSquaredError for loss function Trainer.Train(nn,trainData[0],trainLabel[0],newMeanSquaredError(), false); Trainer.Train(nn, trainData[1], trainLabel[1], new MeanSquaredError(), false); Trainer.Train(nn, trainData[2], trainLabel[2], new MeanSquaredError(), false); Trainer.Train(nn, trainData[3], trainLabel[3], new MeanSquaredError(), false); //If you do not update every time after training, you can update it as a mini batch nn.Update(); } RILogManager.Default?.SendDebug("Test Start..."); foreach (Real[] val in trainData) { NdArray result = nn.Predict(val)[0]; RILogManager.Default?.SendDebug($"{val[0]} xor {val[1]} = {(result.Data[0] > 0.5 ? 1 : 0)} {result}"); } }
Weaver
Weaver是Kelp.Net的重要組成部分。是運行測試時要執行的第一個對象調用。這個對象包含各類OpenCL對象,好比:
l 計算上下文
l 一組計算設備
l 計算命令隊列
l 一個布爾標誌,代表GPU是否爲啓用狀態
l 可核心計算資源的字典
Weaver是用來告訴咱們的程序咱們將使用CPU仍是GPU,以及咱們將使用哪一個設備(若是咱們的系統可以支持多個設備)的地方。咱們只須要在咱們的程序開始時對Weaver作一個簡單的調用,就像在這裏看到的:
Weaver.Initialize(ComputeDeviceTypes.Gpu);
咱們還能夠避免使用weaver的初始化調用,並容許它肯定須要自動發生什麼。
如下是Weaver的基本內容。它的目的是構建(在運行時動態編譯)將執行的程序:
///<summary>上下文</summary> internal static ComputeContext Context; ///<summary>設備</summary> private static ComputeDevice[] Devices; ///<summary>命令隊列</summary> internal static ComputeCommandQueue CommandQueue; ///<summary>設備的從零開始索引</summary> private static int DeviceIndex; ///<summary>True啓用,false禁用</summary> internal static bool Enable; ///<summary>平臺</summary> private static ComputePlatform Platform; ///<summary>核心資源</summary> private static readonly Dictionary<string, string> KernelSources = new Dictionary<string, string>();
編寫測試
爲Kelp.Net建立測試很是簡單。咱們編寫的每一個測試只須要公開一個運行函數。剩下的就是咱們但願網絡如何運做的邏輯了。
運行函數的通常準則是:
- 負載數據(真實或模擬):
Real[][] trainData = new Real[N][]; Real[][] trainLabel = new Real[N][]; for (int i = 0; i < N; i++) { //Prepare Sin wave for one cycle Real radian = -Math.PI + Math.PI * 2.0 * i / (N - 1); trainData[i] = new[] { radian }; trainLabel[i] = new Real[] { Math.Sin(radian) };
2.建立函數堆棧:
FunctionStack nn = new FunctionStack( new Linear(1, 4, name: "l1 Linear"), new Tanh(name: "l1 Tanh"), new Linear(4, 1, name: "l2 Linear") );
3.選擇優化器:
nn.SetOptimizer(new SGD());
4.訓練數據:
for (int i = 0; i < EPOCH; i++) { Real loss = 0; for (int j = 0; j < N; j++) { //When training is executed in the network, an error is returned to the return value loss += Trainer.Train(nn, trainData[j], trainLabel[j], new MeanSquaredError()); } if (i % (EPOCH / 10) == 0) { RILogManager.Default?.SendDebug("loss:" + loss / N); RILogManager.Default?.SendDebug(""); } }
5.測試數據:
RILogManager.Default?.SendDebug("Test Start..."); foreach (Real[] val in trainData) { RILogManager.Default?.SendDebug(val[0]+":"+nn.Predict(val)[0].Data[0]); }
總結
在這一章中,咱們進入了深度學習的世界。咱們學習瞭如何使用Kelp.Net做爲咱們的研究平臺,它幾乎能夠測試任何假設。咱們還看到了Kelp.Net的強大功能和靈活性。
下一章開始,咱們進入實際應用階段。