- 人工智能
- 机器学习
- 深度学习
【免费下载链接】mlpack
mlpack: a fast, header-only C++ machine learning library
导读
mlpack 中大量机器学习算法(KNN、RangeSearch、EMST、KMeans 等)都依赖某种距离度量,而这类度量往往可以被替换为任意合法的距离函数。本文以 doc/developer/distances.md 为核心,系统讲解 mlpack 的DistanceType模板策略:如何用最小的接口(一个Evaluate()方法与一个默认构造函数)接入任意距离度量,如何将自定义距离直接用于RangeSearch等算法,并逐一深入 mlpack 内置的LMetric、MahalanobisDistance、IPMetric与IoUDistance的实现原理与用法。读完本文,你将能独立编写、测试并部署自定义距离度量到 mlpack 的各类基于距离的算法中。
一、为什么需要 DistanceType 策略
许多机器学习方法都依赖某个距离度量,而这个度量可以是任意的。以最近邻搜索为例:我们可以用标准的欧氏距离(Euclidean distance)寻找某点的最近邻,也可以用曼哈顿距离(Manhattan / city-block distance);但真正的搜索技术本身并不改变。类似地,KMeans 聚类、EMST 最小生成树、范围搜索(RangeSearch)等算法也是如此——具体使用哪种合法的距离度量,不应该改变算法的核心逻辑。
mlpack 的解决方案是把"距离度量"抽象成一个模板参数DistanceType:算法只要求该类型暴露固定的接口,其余全部交给模板在编译期展开。这样,同一个RangeSearch或NeighborSearch代码既可以跑欧氏距离,也可以跑切比雪夫距离、马氏距离乃至用户自实现的任意度量。
从源码结构看,mlpack 的树构建代码(如 BinarySpaceTree)也通过该策略使用距离度量,见 src/mlpack/core/distances/distances.hpp 中文件头注释:这些度量"对 mlpack 的树构建代码及其他应用非常有用"。
二、DistanceType 策略的契约:只有两个要求
任何作为DistanceType模板参数传入的距离度量,都必须满足:
- 一个
Evaluate()函数; - 一个默认构造函数(default constructor)。
仅此而已。这是整个策略的全部接口约束,非常简洁,与 mlpack 的 KernelType 策略 同构。
Evaluate() 的签名
Evaluate()的签名如下:
template<typename VecTypeA, typename VecTypeB> double Evaluate(const VecTypeA& a, const VecTypeB& b);它接收两个向量参数a和b,返回一个double,即这两个向量之间的距离度量值。对于某个距离度量d来说,Evaluate()应当返回d(a, b)。
参数类型的约定
参数a、b的类型VecTypeA、VecTypeB是Armadillo 风格的向量类型,通常是arma::vec、arma::sp_vec或类似的类型。一般来说,可以安全地假设VecTypeA拥有与arma::vec相同的 API。
无状态度量的 static 优化
对于不持有任何状态的距离度量(即Evaluate()的结果只依赖输入参数),可以将Evaluate()标记为static,这样无需实例化对象即可调用。mlpack 内置的LMetric家族正是这样做的,见 lmetric.hpp 中Evaluate()的静态声明。
三、编写第一个自定义距离度量
下面是一个完整的最小示例:实现 L2(欧氏)距离的ExampleDistance类。它满足DistanceType策略的全部要求:
class ExampleDistance { public: // Default constructor is required. ExampleDistance() { } // The example metric holds no state, so we can mark Evaluate() as static. template<typename VecTypeA, typename VecTypeB> static double Evaluate(const VecTypeA& a, const VecTypeB& b) { // Return the L2 norm of the difference between the points, which is the // same as the L2 distance. return arma::norm(a - b); } };要点解读:
- 默认构造函数:
ExampleDistance() { }是空实现,但必须存在,因为算法在内部需要默认构造一个距离对象(例如RangeSearch构造函数的默认参数const DistanceType distance = DistanceType(),见 range_search.hpp)。 - 无状态:该类没有成员变量,因此
Evaluate()声明为static,可以直接以ExampleDistance::Evaluate(a, b)方式调用。 - 模板化的向量类型:
VecTypeA/VecTypeB使得同一份代码同时适配arma::vec、arma::fvec、arma::sp_vec等 Armadillo 类型。
四、把自定义距离接入算法:RangeSearch 实战
编写好距离类之后,就可以把它当作DistanceType模板参数交给任何 mlpack 算法。下面的代码在随机数据集上用ExampleDistance运行范围搜索(RangeSearch):它实例化一个使用ExampleDistance与球树(BallTree)的RangeSearch对象,然后打印查询结果数量。
RangeSearch类共接收三个模板参数:DistanceType、MatType和TreeType。实际声明见 range_search.hpp:
template<typename DistanceType = EuclideanDistance, typename MatType = arma::mat, template<typename TreeDistanceType, typename TreeStatType, typename TreeMatType> class TreeType = KDTree> class RangeSearch;示例代码:
// Create a random dataset with 10 dimensions and 5000 points. arma::mat data = arma::randu<arma::mat>(10, 5000); // Instantiate the RangeSearch object with the ExampleDistance. mlpack::RangeSearch<ExampleDistance, arma::mat, mlpack::BallTree> rs(data); // These vectors will store the results. std::vector<std::vector<size_t>> neighbors; std::vector<std::vector<double>> distances; // Create a random 10-dimensional query point. arma::vec query = arma::randu<arma::vec>(10); // Find those points with distance (according to ExampleDistance) between 1 // and 2 from the query point. rs.Search(query, mlpack::Range(1.0, 2.0), neighbors, distances); // Now, print the number of points inside the desired range. We know that // neighbors and distances will have length 1, since there was only one query // point. std::cout << neighbors[0].size() << " points within the range [1.0, 2.0] of the" << " query point!" << std::endl;几点值得注意:
- 树类型与距离的耦合:
TreeType(这里是mlpack::BallTree)会把DistanceType作为自己的树距离类型传入(Tree = TreeType<DistanceType, RangeSearchStat, MatType>,见 range_search.hpp)。这意味着你的距离度量必须能被树用于剪枝(通常要求满足三角不等式);对不满足三角不等式的度量要格外小心。 - 有状态距离的传递:
RangeSearch的构造函数还接受一个已实例化的DistanceType对象作为可选参数(默认值为DistanceType()),用于处理那些持有数据(如MahalanobisDistance持有的 Q 矩阵)的度量。 - 结果容器:
neighbors与distances是std::vector<std::vector<size_t>>/std::vector<std::vector<double>>,外层下标对应查询点,内层是命中的邻居索引与其距离。因为只有一个查询点,所以外层长度为 1。
五、mlpack 内置的距离度量一览
mlpack 自带若干开箱即用的距离度量,全部满足DistanceType策略。它们集中定义在 src/mlpack/core/distances/ 目录下:
- ManhattanDistance:曼哈顿(L1)距离
- EuclideanDistance:欧氏(L2)距离
- ChebyshevDistance:切比雪夫(L∞)距离
- MahalanobisDistance:马氏距离,用协方差矩阵控制各维权重
- LMetric:任意 L 阶度量(L-p 度量)的通用模板
- IPMetric:内积度量,需要传入一个 KernelType 参数,计算核空间中的诱导距离
- IoUDistance:交并比(IoU)距离,用于边界框重叠度度量
这些度量可用于 KNN、NeighborSearch、RangeSearch、LMNN、EMST、NCA、RANN、KMeans 等多种基于距离的技术。
下面分别深入讲解每一类度量。
六、LMetric:通用的 L-p 度量
LMetric是 mlpack 最基础也最常用的距离模板,实现广义 L 度量(L1、L2、L3……L∞)。数学定义是:
d(x, y) = ( Σ_{i=1}^{n} | x_i - y_i |^p )^(1/p)其模板声明为(见 lmetric.hpp):
template<int TPower, bool TTakeRoot = true> class LMetric;两个模板参数
| 参数 | 含义 | 约束与注意 |
|---|---|---|
Power | 度量阶数,如2表示 L2(欧氏)距离 | 必须是 1 或更大;若为INT_MAX(源码中实际写为2147483647),则为 L∞ 切比雪夫距离 |
TakeRoot | bool,默认true,是否对累加结果开 p 次方根 | 若设为false,返回的是差的 p 次幂之和,计算更快,但不再满足三角不等式,不再是严格意义上的度量 |
便捷 typedef
源码在 lmetric.hpp 中提供了四个常用别名:
ManhattanDistance=LMetric<1, false>(L1;对 L1 而言取不取根结果相同)EuclideanDistance=LMetric<2, true>(L2,开根号)SquaredEuclideanDistance=LMetric<2, false>(平方欧氏距离,注意这不是严格度量)ChebyshevDistance=LMetric<2147483647, false>(L∞,实现为max(|a - b|))
从实现细节看(lmetric_impl.hpp):
- L2 开根版本走
arma::norm(a - b, 2); - 平方版本走
accu(arma::square(a - b)),避免开方; - L∞ 版本走
arma::as_scalar(arma::max(arma::abs(a - b)))。
使用示例
// Create two vectors: [0, 1.0, 5.0] and [1.0, 3.0, 5.0]. arma::vec a("0.0 1.0 5.0"); arma::vec b("1.0 3.0 5.0"); const double d1 = mlpack::ManhattanDistance::Evaluate(a, b); // d1 = 3.0 const double d2 = mlpack::EuclideanDistance::Evaluate(a, b); // d2 = 2.24 const double d3 = mlpack::SquaredEuclideanDistance::Evaluate(a, b); // d3 = 5.0 const double d4 = mlpack::ChebyshevDistance::Evaluate(a, b); // d4 = 2.0 const double d5 = mlpack::LMetric<4>::Evaluate(a, b); // d5 = 2.03 const double d6 = mlpack::LMetric<3, false>::Evaluate(a, b); // d6 = 9.0 std::cout << "Manhattan distance: " << d1 << "." << std::endl; std::cout << "Euclidean distance: " << d2 << "." << std::endl; std::cout << "Squared Euclidean distance: " << d3 << "." << std::endl; std::cout << "Chebyshev distance: " << d4 << "." << std::endl; std::cout << "L4-distance: " << d5 << "." << std::endl; std::cout << "Cubed L3-distance: " << d6 << "." << std::endl; // Compute the distance between two random 10-dimensional vectors in a matrix. arma::mat m(10, 100, arma::fill::randu); const double d7 = mlpack::EuclideanDistance::Evaluate(m.col(0), m.col(7)); std::cout << std::endl; std::cout << "Distance between two random vectors: " << d7 << "." << std::endl; std::cout << std::endl; // Compute the distance between two 32-bit precision `float` vectors. arma::fvec fa("0.0 1.0 5.0"); arma::fvec fb("1.0 3.0 5.0"); const double d8 = mlpack::EuclideanDistance::Evaluate(fa, fb); // d8 = 2.236 std::cout << "Euclidean distance (fvec): " << d8 << "." << std::endl;类型灵活性
传给Evaluate()的向量可以是任何实现了 Armadillo API 的类型,例如arma::fvec、arma::sp_fvec等。返回值类型取VecTypeA::elem_type(对arma::vec是double,对arma::fvec是float)。上面的示例同时演示了arma::vec(double)与arma::fvec(float)两种精度。
测试验证
单元测试位于 src/mlpack/tests/distance_test.cpp,例如:
ManhattanDistanceTest验证a = [1.0, 3.0, 4.0]、b = [3.0, 3.0, 5.0]的曼哈顿距离为 3.0,且LMetric<1, true>与之相同(distance_test.cpp);EuclideanDistanceTest验证欧氏距离等于sqrt(76.0);ArbitraryCaseTest验证LMetric<3, false>结果为 503.0、LMetric<3, true>约为 7.9528(distance_test.cpp);LINFMetricTest验证切比雪夫距离即max(|a - b|);LMetricZerosTest验证各种Power下全零向量自距离为 0。
七、MahalanobisDistance:带权重矩阵的"拉伸欧氏距离"
MahalanobisDistance实现加权欧氏距离(马氏距离)。它需要一个逆协方差矩阵Q来控制各维度在距离计算中的权重,度量定义为:
d_Q(x, y) = sqrt((x - y)^T Q (x - y))模板声明为(见 mahalanobis_distance.hpp):
template<bool TakeRoot = true, typename MatType = arma::mat> class MahalanobisDistance;模板参数
TakeRoot:设为false时省略sqrt(),计算更快,但同样不再满足三角不等式。MatType:表示Q的矩阵类型,需满足 Armadillo API(如arma::mat、arma::fmat)。
构造与属性
| 用法 | 说明 |
|---|---|
md = MahalanobisDistance() | 不初始化 Q;在调用Evaluate()之前必须先通过Q()设置矩阵,否则会抛出异常 |
md = MahalanobisDistance(dimensionality) | 将 Q 初始化为给定维度的单位矩阵,此时等价于欧氏距离 |
md = MahalanobisDistance(matQ) | 用给定的 Q 矩阵构造;matQ必须正定且对称 |
md.Q() | 访问或修改 Q 矩阵,如md.Q() = myCustomQ; |
实现上(mahalanobis_distance_impl.hpp),Evaluate()会先检查q.n_rows != a.n_elem并抛出维度不匹配异常,然后计算sqrt(as_scalar(m.t() * q * m))(TakeRoot为true时)。
使用注意事项
Q是逆协方差:很多教材用C^-1而不是Q。务必确保传入的Q是协方差矩阵的逆(例如可用arma::pinv()求伪逆),不要直接传入协方差矩阵本身。- 性能建议:与其把
MahalanobisDistance直接当作距离度量喂给算法,通常更快的方式是:先把数据集按Q隐含的等价变换拉伸,再对变换后的数据集直接使用欧氏距离。具体做法是分解Q = R^T R(例如上三角 Cholesky 分解),然后用(R * dataset)作为新数据集。 - 与树的兼容性:如果坚持在
KNN等基于树的算法中使用MahalanobisDistance,需要换成非默认的树类型——默认的 KDTree 只支持LMetric类度量(见 mahalanobis_distance.hpp 的注释)。
使用示例
// Create random 10-dimensional data. arma::mat dataset(10, 100, arma::fill::randu); // Create a positive-definite Q matrix by using a weighting matrix W such that // Q = W^T W. arma::mat W(10, 10, arma::fill::randu); arma::mat Q = W.t() * W; // Create a MahalanobisDistance object with the given Q. mlpack::MahalanobisDistance md(std::move(Q)); std::cout << "Mahalanobis distance between points 3 and 4: " << md.Evaluate(dataset.col(3), dataset.col(4)) << "." << std::endl; // Now compare the Mahalanobis distance with the Euclidean distance on the // dataset transformed with W. (They are the same!) arma::mat transformedDataset = W * dataset; std::cout << "Mahalanobis distance between points 2 and 71: " << md.Evaluate(dataset.col(2), dataset.col(71)) << "." << std::endl; std::cout << "Euclidean distance between transformed points 2 and 71: " << mlpack::EuclideanDistance::Evaluate(transformedDataset.col(2), transformedDataset.col(71)) << "." << std::endl; // Create a Mahalanobis distance for 32-bit floating point data. arma::fmat floatDataset(20, 100, arma::fill::randn); // Use a random diagonal matrix as Q. arma::fmat fQ = arma::diagmat(arma::randu<arma::fvec>(20)); mlpack::MahalanobisDistance<false /* do not take square root */, arma::fmat> fmd; fmd.Q() = std::move(fQ); const double d1 = fmd.Evaluate(floatDataset.col(3), floatDataset.col(5)); const double d2 = fmd.Evaluate(floatDataset.col(11), floatDataset.col(31)); std::cout << "Squared Mahalanobis distance on 32-bit floating point data:" << std::endl; std::cout << " - Points 3 and 5: " << d1 << "." << std::endl; std::cout << " - Points 11 and 31: " << d2 << "." << std::endl; // Note that an equivalent transformation matrix can be recovered from Q with // an upper Cholesky decomposition (Q -> R.t() * R). arma::mat recoveredW = arma::chol(md.Q(), "lower"); // A transformed dataset can be created with `(recoveredW * dataset)`.测试验证
测试文件 distance_test.cpp 对马氏距离覆盖了多种场景:
MDUnsetCovarianceTest/MDRootUnsetCovarianceTest:默认构造后手动设置 Q(含TakeRoot=false与true两种);MDEyeCovarianceTest/MDRootEyeCovarianceTest:用维度参数构造单位阵 Q;MDDiagonalCovarianceTest、MDFullCovarianceTest:对角线与非对角线 Q 的数值验证;MahalanobisDistanceSerializationTest:验证 Q 矩阵的序列化(cereal)往返一致性。
测试均以float/double两种精度参数化运行(TEMPLATE_TEST_CASE),并验证了对称性(Evaluate(a, b) == Evaluate(b, a))。
八、IPMetric :核空间中的诱导距离
IPMetric<KernelType>实现由给定核函数诱导出的距离。它利用核k(x, y)在核空间中构成内积这一事实,将距离定义为:
d(x, y) = sqrt(k(x, x) + k(y, y) - 2 k(x, y))模板参数KernelType可以是 mlpack 的任意内置核函数,也可以是用户自实现的自定义核。注意:核必须是 Mercer 核(正定核),否则诱导度量可能无效(见 ip_metric.hpp 的注释)。
该类的一个典型应用是FastMKS(快速最大核搜索):fastmks.hpp 中直接以IPMetric<KernelType>作为树的距离类型。
构造函数与属性
| 用法 | 说明 |
|---|---|
d = IPMetric<KernelType>() | 用默认构造的KernelType创建;要求KernelType有默认构造函数 |
d = IPMetric<KernelType>(kernel) | 用给定的kernel对象创建;注意 kernel 不会被复制,使用期间必须保证kernel不提前离开作用域 |
d = IPMetric<KernelType>(other) | 拷贝构造,会复制内部持有的KernelType |
d = other; | 拷贝赋值,同样复制内部KernelType |
d.Kernel() | 访问内部持有的KernelType(有 const 与可变两个重载) |
从实现看(ip_metric_impl.hpp),IPMetric内部通过指针持有内核,并用kernelOwner标志管理所有权:默认构造与拷贝构造时kernelOwner=true(负责 delete),传入外部内核时kernelOwner=false(不负责)。序列化时若处于加载状态会先释放旧内核再重新分配。
距离计算
d.Evaluate(x1, x2)计算并返回两个向量x1、x2在核空间中的距离。x1、x2应为实现 Armadillo API 的向量类型(如arma::vec、arma::sp_vec),且必须是给定KernelType::Evaluate()的合法输入。
使用示例
// Create a few random points. arma::vec x1(3, arma::fill::randu); arma::vec x2(3, arma::fill::randu); arma::vec x3(3, arma::fill::randu); // Create a metric on the Epanechnikov kernel. mlpack::EpanechnikovKernel ek(1.5 /* bandwidth */); mlpack::IPMetric<mlpack::EpanechnikovKernel> ip1(ek); // Compute distances in kernel space, and compare with kernel evaluations. std::cout << "x1: " << x1.t(); std::cout << "x2: " << x2.t(); std::cout << "x3: " << x3.t(); std::cout << std::endl; std::cout << " ek(x1, x2): " << ek.Evaluate(x1, x2) << "." << std::endl; std::cout << " ip(x1, x2): " << ip1.Evaluate(x1, x2) << "." << std::endl; std::cout << std::endl; std::cout << " ek(x2, x3): " << ek.Evaluate(x2, x3) << "." << std::endl; std::cout << " ip(x2, x3): " << ip1.Evaluate(x2, x3) << "." << std::endl; std::cout << std::endl; std::cout << " ek(x1, x3): " << ek.Evaluate(x1, x3) << "." << std::endl; std::cout << " ip(x1, x3): " << ip1.Evaluate(x1, x3) << "." << std::endl; std::cout << std::endl; // Change the bandwidth of the kernel. ip1.Kernel().Bandwidth(2.0); std::cout << "With bandwidth 2.0:" << std::endl; std::cout << " ek(x1, x3): " << ek.Evaluate(x1, x3) << "." << std::endl; std::cout << " ip(x1, x3): " << ip1.Evaluate(x1, x3) << "." << std::endl; std::cout << std::endl; // Now create a metric on the LinearKernel. // This one is a bit of a trick! For the LinearKernel, the induced metric is // exactly the Euclidean distance. mlpack::IPMetric<mlpack::LinearKernel> ip2; std::cout << " Euclidean distance between x1/x2: " << mlpack::EuclideanDistance::Evaluate(x1, x2) << "." << std::endl; std::cout << " IPMetric<LinearKernel> between x1/x2: " << ip2.Evaluate(x1, x2) << "." << std::endl; // Compute the kernel space distance between two floating-point vectors. arma::fvec fx1(10, arma::fill::randu); arma::fvec fx2(10, arma::fill::randu); std::cout << "IPMetric<EpanechnikovKernel> result between two random " << "10-dimensional 32-bit floating point vectors:" << std::endl; std::cout << " " << ip1.Evaluate(fx1, fx2) << "." << std::endl;有趣的细节:对于线性核(LinearKernel),诱导距离恰好就是欧氏距离。源码中为此专门做了模板特化,直接委托给LMetric<2, true>::Evaluate(a, b)(见 ip_metric_impl.hpp),这比走通用公式更高效,也印证了上面代码中ip2与EuclideanDistance结果一致的现象。
九、IoUDistance:边界框交并比距离
IoUDistance实现交并比(intersection-over-union)距离,用于度量两个边界框的重叠程度,与 Jaccard 指数 相关。其定义为:
d(x, y) = 1 - (intersection area / union area)- 两个边界框完全重叠时距离为 0;
- 完全没有重叠时距离为 1。
从源码看(iou_distance.hpp),它的实现直接复用1.0 - IoU<UseCoordinates>::Evaluate(a, b),其中IoU类位于 src/mlpack/core/metrics/iou_metric.hpp。与纯 IoU 度量相比,这个IoUDistance是满足三角不等式等度量性质的(依据 Kosub (2019) 关于 Jaccard 距离三角不等式的证明,见 iou_distance.hpp 中的参考文献注释)。
UseCoordinates 模板参数
类有一个布尔模板参数UseCoordinates(默认false),控制边界框的表示方式:
IoUDistance<>(即IoUDistance<false>):Evaluate()接收四元素向量[x0, y0, h, w],其中(x0, y0)是边界框左下角,h是高,w是宽。IoUDistance<true>:Evaluate()接收四元素向量[x0, y0, x1, y1],其中(x0, y0)是左下角,(x1, y1)是右上角。
若任一输入向量不是四元素,将抛出异常。建议用稠密向量表示边界框(稀疏表示一般没有必要,因为边界框通常不是稀疏的)。
使用示例
// Create three bounding boxes by representing the lower left and size. arma::vec bb1("0.0 0.0 3.0 5.0"); // Lower left at (0, 0), height=3, width=5. arma::vec bb2("2.0 2.0 5.0 2.0"); // Lower left at (2, 2), height=5, width=2. arma::vec bb3("1.0 1.0 1.5 1.0"); // Lower left at (1, 1), height=1.5, width=1. // Represent the same three bounding boxes in lower left/upper right form. arma::vec bb1Coord("0.0 0.0 5.0 3.0"); // Upper right is (5, 3). arma::vec bb2Coord("2.0 2.0 4.0 7.0"); // Upper right is (4, 7). arma::vec bb3Coord("1.0 1.0 2.0 2.5"); // Upper right is (2, 2.5). // Compute the distance between each of the bounding boxes using the // height/width representation. const double d1 = mlpack::IoUDistance<>::Evaluate(bb1, bb2); const double d2 = mlpack::IoUDistance<>::Evaluate(bb2, bb3); const double d3 = mlpack::IoUDistance<>::Evaluate(bb1, bb3); std::cout << "IoUDistance with width/height bounding box representations:" << std::endl; std::cout << " - ll=(0, 0), h=3, w=5 and ll=(2, 2), h=5, w=2: " << d1 << "." << std::endl; std::cout << " - ll=(0, 0), h=3, w=5 and ll=(1, 1), h=1.5, w=1: " << d3 << "." << std::endl; std::cout << " - ll=(2, 2), h=5, w=2 and ll=(1, 1), h=1.5, w=1: " << d2 << "." << std::endl; // Now compute the same distances with the other representation. const double d1Coord = mlpack::IoUDistance<true>::Evaluate(bb1Coord, bb2Coord); const double d2Coord = mlpack::IoUDistance<true>::Evaluate(bb2Coord, bb3Coord); const double d3Coord = mlpack::IoUDistance<true>::Evaluate(bb1Coord, bb3Coord); std::cout << "IoUDistance with two-coordinate bounding box representations:" << std::endl; std::cout << "(same bounding boxes as above)" << std::endl; std::cout << " - ll=(0, 0), ur=(5, 3) and ll=(2, 2), ur=(4, 7): " << d1Coord << "." << std::endl; std::cout << " - ll=(0, 0), ur=(5, 3) and ll=(1, 1), ur=(2, 2.5): " << d3Coord << "." << std::endl; std::cout << " - ll=(2, 2), ur=(4, 7) and ll=(1, 1), ur=(2, 2.5): " << d2Coord << "." << std::endl;测试验证
IoUDistanceTest(distance_test.cpp)覆盖了两种表示:完全相同框距离为 0、以及坐标/宽高两种表示下与 Python 计算值一致的结果(误差 1e-6 内)。
十、实战要点总结
- 接口最小化:实现
DistanceType策略只需要默认构造函数 +Evaluate();无状态度量记得把Evaluate()标记为static。 - 向量类型泛型化:用
template<typename VecTypeA, typename VecTypeB>编写Evaluate(),即可同时支持arma::vec、arma::fvec、arma::sp_vec等 Armadillo 类型。 - 三角不等式是树算法的硬前提:
TakeRoot=false的平方度量虽然更快,但不满足三角不等式;用于基于树的算法(KNN、RangeSearch、EMST 等)可能产生错误结果。源码中 BinarySpaceTree 的注释也明确警告了这一点(lmetric.hpp)。 - 有状态度量要传递实例:
MahalanobisDistance这类携带 Q 矩阵的度量,构造算法对象时应传入已实例化的距离对象,而非仅依赖默认构造。 - 优先用内置度量:
LMetric的模板特化(lmetric_impl.hpp)对常用阶数做了手工优化(如 L1 用accu(abs(a - b))、L2 用arma::norm),直接使用这些实现通常比自己写的循环更快。
本文内容以 doc/developer/distances.md 的策略说明为骨架,综合 doc/user/core/distances.md 的用户文档、src/mlpack/core/distances/ 的实现源码与 src/mlpack/tests/distance_test.cpp 的单元测试整理而成。建议读者在编写自定义距离后,参照这些测试的模式为自己的度量补充单元测试,确保对称性、零距离与数值精度符合预期。
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】mlpack
mlpack: a fast, header-only C++ machine learning library
相关推荐
NetworkX 距离度量(Distance Measures)完全指南:从离心率、直径到电阻距离与 Kemeny 常数
NetworkX 距离度量(Distance Measures)完全指南:从离心率、直径到电阻距离与 Kemeny 常数 本篇技术指南以 NetworkX 的
图计算数据分析科学计算SciPy 空间距离计算模块 scipy.spatial.distance 完全指南:成对距离、距离矩阵与 19 种距离度量
SciPy 空间距离计算模块 scipy.spatial.distance 完全指南:成对距离、距离矩阵与 19 种距离度量 本文围绕 SciPy 仓库中的 s
科学计算数据科学高性能计算whisper-large-v3模型部署指南:从CPU到GPU的环境配置与性能测试
whisper large v3模型部署指南:从CPU到GPU的环境配置与性能测试 whisper large v3是一款功能强大的语音识别模型,本指南将详细介
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考