基於C#的機器學習--微基准測試和激活功能


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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM