65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 CNTK 和 C# 进行描述性统计和数据归一化

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2018 年 7 月 2 日

CPOL

3分钟阅读

viewsIcon

12712

如何计算数据集的一些基本统计操作。

引言

您可能知道,CNTK 是 Microsoft 用于深度学习的认知工具包。它是一个开源库,被各种 Microsoft 产品使用。此外,CNTK 是一个强大的库,可用于开发来自不同领域、使用不同平台和语言的自定义 ML 解决方案。CNTK 的强大之处还在于其实现方式。实际上,该库被实现为一系列计算图,这些计算图被完全阐述为在深度神经网络训练中执行的步骤序列。

每个 CNTK 计算图都由一组节点创建,其中每个节点代表数值(数学)运算。图中节点之间的边代表运算之间的数据流。这种表示允许 CNTK 在底层硬件 GPU 或 CPU 上调度计算。CNTK 可以动态分析这些图,以优化延迟并有效利用资源。其中最强大的部分是 CNTK 可以计算任何构建的操作集的导数,这可用于网络参数的有效学习过程。下图显示了 CNTK 的核心架构。

另一方面,任何操作都可以在 CPU 或 GPU 上执行,只需进行最少的代码更改。实际上,我们可以实现一种可以自动采用 GPU 计算(如果可用)的方法。CNTK 是第一个为 .NET 开发人员提供开发 GPU 感知 .NET 应用程序的 .NET 库。

这意味着使用这个强大的库,您可以使用 C# 在 .NET 中直接将复杂的数学计算开发到 GPU,这在使用标准 .NET 库时目前是不可能的。

在这篇博文中,我将展示如何计算数据集上的一些基本统计运算。

假设我们有一个包含 4 列(特征)和 20 行(样本)的数据集。此 2D 数组的 C# 实现如下面的代码片段所示

static float[][] mData = new float[][] {
new float[] { 5.1f, 3.5f, 1.4f, 0.2f},
new float[] { 4.9f, 3.0f, 1.4f, 0.2f},
new float[] { 4.7f, 3.2f, 1.3f, 0.2f},
new float[] { 4.6f, 3.1f, 1.5f, 0.2f},
new float[] { 6.9f, 3.1f, 4.9f, 1.5f},
new float[] { 5.5f, 2.3f, 4.0f, 1.3f},
new float[] { 6.5f, 2.8f, 4.6f, 1.5f},
new float[] { 5.0f, 3.4f, 1.5f, 0.2f},
new float[] { 4.4f, 2.9f, 1.4f, 0.2f},
new float[] { 4.9f, 3.1f, 1.5f, 0.1f},
new float[] { 5.4f, 3.7f, 1.5f, 0.2f},
new float[] { 4.8f, 3.4f, 1.6f, 0.2f},
new float[] { 4.8f, 3.0f, 1.4f, 0.1f},
new float[] { 4.3f, 3.0f, 1.1f, 0.1f},
new float[] { 6.5f, 3.0f, 5.8f, 2.2f},
new float[] { 7.6f, 3.0f, 6.6f, 2.1f},
new float[] { 4.9f, 2.5f, 4.5f, 1.7f},
new float[] { 7.3f, 2.9f, 6.3f, 1.8f},
new float[] { 5.7f, 3.8f, 1.7f, 0.3f},
new float[] { 5.1f, 3.8f, 1.5f, 0.3f},};

如果您想使用 CNTK 和数学计算,您需要一些微积分知识,以及向量、矩阵和张量。同样在 CNTK 中,任何操作都作为矩阵操作执行,这可以简化您的计算过程。以标准方式,您必须在计算过程中处理多维数组。据我所知,目前没有可以在 GPU 上执行数学运算的 .NET 库,这限制了 .NET 平台实现高性能应用程序。

如果我们想要计算每列的平均值和标准差,我们可以使用 CNTK 以非常简单的方式完成。一旦我们计算出这些值,我们就可以通过计算标准分数(高斯标准化)来使用它们来标准化数据集。

高斯标准化是通过以下公式计算的

nValue= \frac{X-\nu}{\sigma},
其中 X- 是列值,\nu – 列平均值,以及 \sigma– 列的标准差。

对于这个例子,我们将执行三个统计操作,CNTK 自动为我们提供了在 GPU 上计算这些值的能力。如果您拥有包含数百万行的数据集,并且计算可以在几毫秒内执行,这一点非常重要。

CNTK 中的任何计算过程都可以通过以下几个步骤实现

  1. 从外部源或内存数据读取数据
  2. 定义 ValueVariable 对象
  3. 定义用于计算的函数
  4. 通过传递 VariableValue 对象来执行函数的评估
  5. 检索计算结果并显示结果

以上所有步骤都在以下实现中实现

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CNTK;
namespace DataNormalizationWithCNTK
{
    class Program
    {
       static float[][] mData = new float[][] {
        new float[] { 5.1f, 3.5f, 1.4f, 0.2f},
        new float[] { 4.9f, 3.0f, 1.4f, 0.2f},
        new float[] { 4.7f, 3.2f, 1.3f, 0.2f},
        new float[] { 4.6f, 3.1f, 1.5f, 0.2f},
        new float[] { 6.9f, 3.1f, 4.9f, 1.5f},
        new float[] { 5.5f, 2.3f, 4.0f, 1.3f},
        new float[] { 6.5f, 2.8f, 4.6f, 1.5f},
        new float[] { 5.0f, 3.4f, 1.5f, 0.2f},
        new float[] { 4.4f, 2.9f, 1.4f, 0.2f},
        new float[] { 4.9f, 3.1f, 1.5f, 0.1f},
        new float[] { 5.4f, 3.7f, 1.5f, 0.2f},
        new float[] { 4.8f, 3.4f, 1.6f, 0.2f},
        new float[] { 4.8f, 3.0f, 1.4f, 0.1f},
        new float[] { 4.3f, 3.0f, 1.1f, 0.1f},
        new float[] { 6.5f, 3.0f, 5.8f, 2.2f},
        new float[] { 7.6f, 3.0f, 6.6f, 2.1f},
        new float[] { 4.9f, 2.5f, 4.5f, 1.7f},
        new float[] { 7.3f, 2.9f, 6.3f, 1.8f},
        new float[] { 5.7f, 3.8f, 1.7f, 0.3f},
        new float[] { 5.1f, 3.8f, 1.5f, 0.3f},};
        static void Main(string[] args)
        {
            //define device where the calculation will executes
            var device = DeviceDescriptor.UseDefaultDevice();
 
            //print data to console
            Console.WriteLine($"X1,\tX2,\tX3,\tX4");
            Console.WriteLine($"-----,\t-----,\t-----,\t-----");
            foreach (var row in mData)
            {
                Console.WriteLine($"{row[0]},\t{row[1]},\t{row[2]},\t{row[3]}");
            }
            Console.WriteLine($"-----,\t-----,\t-----,\t-----");
 
            //convert data into enumerable list
            var data = mData.ToEnumerable<ienumerable<float>>(); 
             
            //assign the values 
            var vData = Value.CreateBatchOfSequences<float>(new int[] {4},data, device);
            //create variable to describe the data
            var features = Variable.InputVariable(vData.Shape, DataType.Float);
 
            //define mean function for the variable
            var mean =  CNTKLib.ReduceMean(features, new Axis(2));//Axis(2)- means calculate 
                                      //mean along the third axes which represent 4 features
             
            //map variables and data
            var inputDataMap = new Dictionary<variable, value="">() { { features, vData } };
            var meanDataMap = new Dictionary<variable, value="">() { { mean, null } };
 
            //mean calculation
            mean.Evaluate(inputDataMap,meanDataMap,device);
            //get result
            var meanValues = meanDataMap[mean].GetDenseData<float>(mean);
 
            Console.WriteLine($"");
            Console.WriteLine($"Average values for each features 
            x1={meanValues[0][0]},x2={meanValues[0][1]},x3={meanValues[0][2]},
            x4={meanValues[0][3]}");
 
            //Calculation of standard deviation
            var std = calculateStd(features);
            var stdDataMap = new Dictionary<variable, value="">() { { std, null } };
            //mean calculation
            std.Evaluate(inputDataMap, stdDataMap, device);
            //get result
            var stdValues = stdDataMap[std].GetDenseData<float>(std);
             
            Console.WriteLine($"");
            Console.WriteLine($"STD of features x1={stdValues[0][0]},
            x2={stdValues[0][1]},x3={stdValues[0][2]},x4={stdValues[0][3]}");
 
            //Once we have mean and std, we can calculate Standardized values for the data
            var gaussNormalization = 
                CNTKLib.ElementDivide(CNTKLib.Minus(features, mean), std);
            var gaussDataMap = new Dictionary<variable, value="">() 
                               { { gaussNormalization, null } };
            //mean calculation
            gaussNormalization.Evaluate(inputDataMap, gaussDataMap, device);
 
            //get result
            var normValues = 
                gaussDataMap[gaussNormalization].GetDenseData<float>(gaussNormalization);
            //print data to console
            Console.WriteLine($"-------------------------------------------");
            Console.WriteLine($"Normalized values for the above data set");
            Console.WriteLine($"");
            Console.WriteLine($"X1,\tX2,\tX3,\tX4");
            Console.WriteLine($"-----,\t-----,\t-----,\t-----");
            var row2 = normValues[0];
            for (int j = 0; j < 80; j += 4)
            {
                Console.WriteLine($"{row2[j]},
                                  \t{row2[j + 1]},\t{row2[j + 2]},\t{row2[j + 3]}");
            }
            Console.WriteLine($"-----,\t-----,\t-----,\t-----");
        }
 
        private static Function calculateStd(Variable features)
        {
            var mean = CNTKLib.ReduceMean(features,new Axis(2));
            var remainder = CNTKLib.Minus(features, mean);
            var squared = CNTKLib.Square(remainder);
            //the last dimension indicate the number of samples
            var n = new Constant(new NDShape(0), 
                    DataType.Float, features.Shape.Dimensions.Last()-1);
            var elm = CNTKLib.ElementDivide(squared, n);
            var sum = CNTKLib.ReduceSum(elm, new Axis(2));
            var stdVal = CNTKLib.Sqrt(sum);
            return stdVal;
        }
    }
 
    public static class ArrayExtensions
    {
        public static IEnumerable<t> ToEnumerable<t>(this Array target)
        {
            foreach (var item in target)
                yield return (T)item;
        }
    }
}

上面源代码的输出应该如下所示

历史

  • 2018 年 7 月 2 日:初始版本
© . All rights reserved.