暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MVP手把手 | 聊聊欧几里德距离、.NET泛型数学和System.Numerics.Tensors包

Azure云科技 2023-12-20
705


MVP 手把手




#1

欧几里德距离(欧式距离)

欧几里德距离也称欧式距离 (Euclidean Distance)是一个数学概念,可用于计算二维到多维平面上两个点之间的距离,而两个点也可看作是起点都为原点的向量,于是也可以计算两个向量之间的距离。


而在机器学习领域常见如下用途:

  • 用于计算数据点之间的相似度,例如在聚类、分类、推荐系统等领域。

  • 用于评估模型的性能,例如在回归分析中,欧几里德距离可以作为损失函数,衡量预测值和真实值之间的误差。

  • 用于降维和特征选择,例如在主成分分析(PCA)中,欧几里德距离可以用来保留数据的最大方差,去除冗余的特征。


例如,在进行人像识别时,需要先取得用于查询的人像和目标人像的特征向量,然后再计算两向量间的欧几里德距离,距离越小,说明越相似,那么是同一个人的可能性就越高。


在 .NET 中,我可以用 C# 这样表达:

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static float Distance(float[] x, float[] y)
    {
       if (x is null) throw new ArgumentNullException(nameof(x));
       if (y is null) throw new ArgumentNullException(nameof(y));
       if (x.Length != y.Length) throw new ArgumentException("The length of two vectors must be equal.");


       float sum = 0.0f;
       for (int i = 0; i < x.Length; i++)
       {
           float u = x[i] - y[i];
           sum += u * u;
       }
       return MathF.Sqrt(sum);
    }

    这里用一维数组表示向量,元素类型是32位浮点数。另外还做了参数数组是否为空,两个向量的长度是否一致的安全检查。



    #2

    泛型数学

    上边的方法定义只适用于32位浮点数 Single,如果要支持 Half 和 Double 另外两种浮点数类型,那么需要分别编写方法的重载,这样做很麻烦。从 .NET 7 开始,我们可以使用泛型数学来解决这一问题。


    泛型数学是我作为一个 API 作者来说最爱的功能,它可以让你在不知道具体类型的情况下,对类似于数字的类型进行数学运算。这是通过引入一些新的泛型接口和静态虚拟成员来实现的。泛型接口定义了一些通用的数学操作,例如加法、乘法、比较等,而静态虚拟成员允许你在接口中声明静态的运算符和方法。这样,你就可以使用泛型类型参数来约束你的方法或类型,使它们只能接受实现了某个泛型接口的类型。例如,你可以编写一个泛型方法,它可以对任何实现了 INumber<TSelf> 接口的类型进行加法运算,而不需要为每种类型写一个重载。这样可以减少代码的重复,提高代码的可读性和可维护性,以及支持更多的类型。


    .NET 7 中提供了一系列的泛型数学接口,它们分为四类:数字接口、运算符接口、函数接口和分析和格式化接口。数字接口描述了类似于数字的类型及其可用的功能,例如 IBinaryInteger<TSelf>和 IFloatingPoint<TSelf>。运算符接口定义了一些基本的数学运算符,例如 IAdditionOperators<TSelf, TOther, TResult> 和 IBitwiseOperators<TSelf, TOther, TResult>。函数接口定义了一些数学函数,例如 ITrigonometricFunctions<TSelf> 和 ILogarithmFunctions<TSelf>。分析和格式化接口定义了一些用于分析和格式化数字的方法,例如 IParseable<TSelf>和 IFormattable<TSelf>。你可以根据需要,选择实现或使用这些接口。


    这是我用泛型数学进行的改写

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      static T Distance<T>(T[] x, T[] y)
         where T : IFloatingPointIeee754<T>
      {
         if (x is null) throw new ArgumentNullException(nameof(x));
         if (y is null) throw new ArgumentNullException(nameof(y));
         if (x.Length != y.Length) throw new ArgumentException("The length of two vectors must be equal.");


         T sum = T.Zero;
         for (int i = 0; i < x.Length; i++)
         {
             T u = x[i] - y[i];
             sum += u * u;
         }
         return T.Sqrt(sum);
      }


      这里 T 约束成 IFloatingPointIeee754<TSelf> 接口类型,它是我们需要的浮点数类型,截至.NET 8,有 System.Half 、System.Single、Sysetm.Double、System.Runtime.InteropServices.NFloat 四个类型实现了该接口。而 IFloatingPointIeee754<TSelf>又继承自 IRootFunctions<TSelf> 接口,该接口提供了的静态抽象方法 Sqrt()。



      #3

      System.Numerics.Tensors 包

      随.NET 8 发布的 System.Numerics.Tensors Nuget 包提供了 System.Numerics.Tensors.TensorPrimitives 类,其中所提供的 API 添加了对张量运算的支持,这个包由社区开发人员贡献。不过遗憾的是这个包没有直接定义张量。


      社区开发人员贡献System.Numerics.Tensors 包:

      https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-rc2/


      其中的 Distance() 方法同样计算欧几里德距离,不过它是使用 Intrinsics 进行了高度优化,另外,它只支持32位浮点数。


      有关包的 API 文档参阅:

      https://learn.microsoft.com/en-us/dotnet/api/system.numerics.tensors.tensorprimitives?view=dotnet-plat-ext-8.0



      #4

      Benchmark

      我用以上三个版本方法跑 benchmark。


      可以看出 TensorPrimitives.Distance() 比常规的和泛型数学版本的快8到9倍,毕竟一次处理8个 float!让我意外的是泛型的版本竟然比确定类型的版本略快一些。



      段琳立

      Microsoft MVP

      拥有20多年编程经验,Microsoft Developer Technologies MVP,喜欢剖析底层技术和前沿技术。受云南省内多所院校聘为“客座教授”,曾获“昆明市技术状元”。



      微软最有价值专家(MVP)是微软公司授予第三方技术专业人士的一个全球奖项。30年来,世界各地的技术社区领导者,因其在线上和线下的技术社区中分享专业知识和经验而获得此奖项。MVP 是经过严格挑选的专家团队,他们代表着技术最精湛且最具智慧的人,是对社区投入极大的热情并乐于助人的专家。MVP 致力于通过演讲、论坛问答、创建网站、撰写博客、分享视频、开源项目、组织会议等方式来帮助他人,并最大程度地帮助微软技术社区用户使用 Microsoft 技术。


      更多详情请点击阅读原文登录官方网站:

      https://mvp.microsoft.com/zh-cn


      文章转载自Azure云科技,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论