本章我們將學習以下內容:
l 什么是微基准測試
l 如何將它應用到代碼中
l 什么是激活函數
l 如何繪制和基准測試激活函數
每個開發人員都需要有一個好的基准測試工具。質量基准無處不在;你們每天都能聽到,這個減少了10%那個增加了25%還記得那句老話嗎,當你聽到一個數字被拋出時,98.4%的情況下這個數字是假的。順便說一下,這個數字也是我編的。當你聽到這樣的話,讓那個人證明一下,你會得到什么?我們不需要定性的結果;我們需要能夠被證明和持續復制的量化結果。可重復的結果是非常重要的,不僅對一致性,而且對可信度和准確性。這就是微基准測試發揮作用的地方。
我們將使用BenchmarkDotNet庫,您可以在這里找到:https://github.com/dotnet/BenchmarkDotNet。我認為它是您可以使用的最不可替代的框架之一,我認為它的重要性不亞於單元測試和集成測試
為了展示這個工具的價值,我們將繪制幾個激活函數並比較它們的運行時。作為其中的一部分,我們將考慮預熱、遺留和RyuJIT、冷啟動以及程序執行的更多方面。最后,我們會得到一組定量的結果來證明函數的精確度量。如果在2.0版本中,我們看到某些東西運行得比較慢,我們可以重新運行基准並進行比較。
我強烈建議將其集成到您的持續集成/持續構建過程中,以便在每個版本中都可以比較基准數據。
在本章中,我們將有兩個樣本。第一個是激活函數查看器;它將繪制每個激活函數,以便我們可以看到它的外觀。可以在Colin Green的SharpNEAT中找到它,它是開源的。這個包絕對是不可思議的。我在它的基礎上創建了新的ui以及高級版本來滿足我的需求,它是目前能找到的最靈活的工具。第一個示例應用程序帶有最新的SharpNEAT包,可以在https://github.com/colgreen/sharpneat找到它。
使用視覺繪圖方法
下面是一個局部和全局最小值的圖,它是由SharpNEAT的自定義版本繪制的。
如前所述,我們將繪制並測試幾個激活函數。我們到處都聽到激活函數這個詞,但我們真的知道它的意思嗎?讓我們從一個快速的解釋開始。
一個激活函數用來決定一個神經元是否被激活。有些人喜歡用fired來代替activated。不管怎樣,它最終決定了某個東西是開還是關,是被觸發還是沒有,是被激活還是沒有。
.讓我們首先看一個單個激活函數的圖:
這是邏輯陡峭近似和Swish激活函數單獨繪制時的樣子,因為有很多類型的激活函數,這是我們所有的激活函數一起繪制時的樣子:
在這一點上,你可能會想,我們為什么還要關心情節是什么樣的呢?好問題。我們關心這些,因為一旦你進入神經網絡或其他領域,你會經常用到這些。這是非常方便的,能夠知道你的激活函數是否將你的神經元的值在開或關的狀態,以及它將保持或需要的值在什么范圍內。毫無疑問,你將在作為機器學習開發人員的職業生涯中遇到和/或使用激活函數,了解TanH和LeakyReLU激活函數之間的區別非常重要。
繪制所有函數
所有激活函數的繪圖都是在一個函數內完成的,這個函數名為PlotAllFunctions:
private void PlotAllFunctions() { // 首先,從母版窗格中清除所有舊的GraphPane collection MasterPane master = zed.MasterPane; master.PaneList.Clear(); // 顯示母版窗格標題,並設置 outer margin to 10 points master.Title.IsVisible = true; master.Margin.All = 10; // 在主窗格上繪制多個函數. PlotOnMasterPane(Functions.LogisticApproximantSteep,"Logistic Steep (Approximant)"); PlotOnMasterPane(Functions.LogisticFunctionSteep,"Logistic Steep (Function)"); PlotOnMasterPane(Functions.SoftSign, "Soft Sign"); PlotOnMasterPane(Functions.PolynomialApproximant,"Polynomial Approximant"); PlotOnMasterPane(Functions.QuadraticSigmoid,"Quadratic Sigmoid"); PlotOnMasterPane(Functions.ReLU, "ReLU"); PlotOnMasterPane(Functions.LeakyReLU, "Leaky ReLU"); PlotOnMasterPane(Functions.LeakyReLUShifted,"Leaky ReLU (Shifted)"); PlotOnMasterPane(Functions.SReLU, "S-Shaped ReLU"); PlotOnMasterPane(Functions.SReLUShifted,"S-Shaped ReLU (Shifted)"); PlotOnMasterPane(Functions.ArcTan, "ArcTan"); PlotOnMasterPane(Functions.TanH, "TanH"); PlotOnMasterPane(Functions.ArcSinH, "ArcSinH"); PlotOnMasterPane(Functions.ScaledELU,"Scaled Exponential Linear Unit"); // 重新設置GraphPanes的軸范圍。 zed.AxisChange(); // 使用默認窗格布局布局GraphPanes. using (Graphics g = this.CreateGraphics()) { master.SetLayout(g, PaneLayout.SquareColPreferred); }
主繪制函數
在后台,Plot函數負責執行和繪制每個函數:
private void Plot(Func<double, double> fn, string fnName,Color graphColor, GraphPane gpane = null) { const double xmin = -2.0; const double xmax = 2.0; const int resolution = 2000; zed.IsShowPointValues = true; zed.PointValueFormat = "e"; var pane = gpane ?? zed.GraphPane; pane.XAxis.MajorGrid.IsVisible = true; pane.YAxis.MajorGrid.IsVisible = true; pane.Title.Text = fnName; pane.YAxis.Title.Text = string.Empty; pane.XAxis.Title.Text = string.Empty; double[] xarr = new double[resolution]; double[] yarr = new double[resolution]; double incr = (xmax - xmin) / resolution; double x = xmin; for(int i=0; i < resolution; i++, x+=incr) { xarr[i] = x; yarr[i] = fn(x); } PointPairList list1 = new PointPairList(xarr, yarr); LineItem li=pane.AddCurve(string.Empty,list1,graphColor,SymbolType.None); li.Symbol.Fill = new Fill(Color.White); pane.Chart.Fill = new Fill(Color.White, Color.LightGoldenrodYellow, 45.0F); }
這是執行我們傳入的激活函數的地方,它的值用於y軸標繪值。著名的ZedGraph開源繪圖包用於所有圖形繪制。一旦執行了每個函數,就會生成相應的圖。
確定基准點
BenchmarkDotNet生成了幾個報告,其中一個是HTML報告,類似於在這里看到的:
Excel報告提供了運行程序時使用的每個參數的詳細信息,是最廣泛的信息來源。在很多情況下,這些參數中的大多數都使用默認值,超出了我們的需要,但至少我們可以選擇刪除我們需要刪除的內容:
我們將在下一節中描述其中的一些參數,當我們回顧創建之前看到的內容的源代碼時:
static void Main(string[] args) { var config = ManualConfig.Create(DefaultConfig.Instance); // 建立一個結果導出器。 // 請注意。默認情況下,結果文件將位於.BenchmarkDotNet.Artifactsresults目錄 config.Add(new CsvExporter
(CsvSeparator.CurrentCulture,
new BenchmarkDotNet.Reports.SummaryStyle { PrintUnitsInHeader = true, PrintUnitsInContent = false, TimeUnit = TimeUnit.Microsecond, SizeUnit = BenchmarkDotNet.Columns.SizeUnit.KB }
)
); // 遺留JITter 測試. config.Add(new Job(EnvMode.LegacyJitX64,EnvMode.Clr, RunMode.Short) { Env = { Runtime = Runtime.Clr, Platform = Platform.X64 }, Run = { LaunchCount = 1, WarmupCount = 1, TargetCount = 1, RunStrategy = BenchmarkDotNet.Engines.RunStrategy.Throughput }, Accuracy = { RemoveOutliers = true } }.WithGcAllowVeryLargeObjects(true)
); // RyuJIT測試。 config.Add(new Job(EnvMode.RyuJitX64, EnvMode.Clr,RunMode.Short) { Env = { Runtime = Runtime.Clr, Platform = Platform.X64 }, Run = { LaunchCount = 1, WarmupCount = 1, TargetCount = 1, RunStrategy = BenchmarkDotNet.Engines.RunStrategy.Throughput }, Accuracy = { RemoveOutliers = true } }.WithGcAllowVeryLargeObjects(true)
); // 取消注釋以允許對未優化的程序集進行基准測試。 //config.Add(JitOptimizationsValidator.DontFailOnError); // 運行基准測試。 var summary = BenchmarkRunner.Run<FunctionBenchmarks>(config); }
讓我們進一步分析這段代碼:
首先,我們將創建一個手動配置對象,其中包含用於基准測試的配置參數:
var config = ManualConfig.Create(DefaultConfig.Instance);
接下來,我們將設置一個導出器來保存用於導出結果的參數。我們將使用微秒計時和千字節大小將結果導出到.csv文件:
config.Add(new CsvExporter
(CsvSeparator.CurrentCulture,
new BenchmarkDotNet.Reports.SummaryStyle { PrintUnitsInHeader = true, PrintUnitsInContent = false, TimeUnit = TimeUnit.Microsecond, SizeUnit = BenchmarkDotNet.Columns.SizeUnit.KB }
)
);
接下來,我們將創建一個基准作業,它將處理x64體系結構上LegacyJitX64的度量。您可以隨意更改此參數和任何其他參數,以進行實驗,或者包含測試場景所需或需要的任何結果。在我們的例子中,我們將使用x64平台;啟動計數、預熱計數和目標計數為1;以及吞吐量的運行策略。我們也會對RyuJIT做同樣的事情,但是我們不會在這里顯示代碼:
config.Add(new Job(EnvMode.LegacyJitX64, EnvMode.Clr,RunMode.Short) { Env = { Runtime = Runtime.Clr, Platform = Platform.X64 }, Run = { LaunchCount = 1, WarmupCount = 1, TargetCount = 1, RunStrategy = Throughput }, Accuracy = { RemoveOutliers = true } }.WithGcAllowVeryLargeObjects(true)
);
最后,我們將運行BenchmarkRunner來執行我們的測試:
var summary = BenchmarkRunner.Run<FunctionBenchmarks>(config);
BenchmarkDotNet將作為DOS命令行應用程序運行,下面是執行上述代碼的一個例子:
讓我們來看一個被繪制的激活函數的例子:
[Benchmark] public double LogisticFunctionSteepDouble() { double a = 0.0; for(int i=0; i<__loops; i++) { a = Functions.LogisticFunctionSteep(_x[i % _x.Length]); }
return a; }
此處使用了[Benchmark]屬性。這向BenchmarkDotNet表明,這將是一個需要進行基准測試的測試。在內部調用如下函數:
對於logisticfunction陡峭函數,其實現與大多數激活函數一樣簡單(假設你知道公式)。在這種情況下不是繪制激活函數,而是對其進行基准測試。
你將注意到函數接受並返回double。我們也通過使用和返回浮點變量對相同的函數進行基准測試,因此我們使用double對函數之間的差異進行基准測試和浮動。因此,人們可以看到,有時性能影響比他們想象的要大:
總結
在本章中,我們學習了如何將微基准測試應用到代碼中。我們還了解了如何繪制和基准測試激活函數,以及如何使用微基准測試。現在,您有了一個最強大的基准測試庫,可以將其添加到所有代碼中。在下一章中,我們將深入探討直觀的深度學習,並向您展示c#開發人員可以使用的最強大的機器學習測試框架之一。