Kelp.Net是一個用c#編寫的深度學習庫
基於C#的機器學習--c# .NET中直觀的深度學習
在本章中,將會學到:
l 如何使用Kelp.Net來執行自己的測試
l 如何編寫測試
l 如何對函數進行基准測試
Kelp.Net是一個用c#編寫的深度學習庫。由於能夠將函數鏈到函數堆棧中,它在一個非常靈活和直觀的平台中提供了驚人的功能。它還充分利用OpenCL語言平台,在支持cpu和gpu的設備上實現無縫操作。深度學習是一個非常強大的工具,對Caffe和Chainer模型加載的本機支持使這個平台更加強大。您將看到,只需幾行代碼就可以創建一個100萬個隱藏層的深度學習網絡。
Kelp.Net還使得從磁盤存儲中保存和加載模型變得非常容易。這是一個非常強大的特性,允許您執行訓練、保存模型,然后根據需要加載和測試。它還使代碼的產品化變得更加容易,並且真正地將訓練和測試階段分離開來。
其中,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(單精度浮點數的四個向量),它的長度為2、3、4、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的強大功能和靈活性。
下一章開始,我們進入實際應用階段。