基於C#的機器學習--微基準測試和啟用功能

王振耀發表於2019-07-17

本章我們將學習以下內容:

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#開發人員可以使用的最強大的機器學習測試框架之一。

相關文章