本章我們將學習以下內容:
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#開發人員可以使用的最強大的機器學習測試框架之一。