【问题标题】:How to best implement K-nearest neighbours in C# for large number of dimensions?如何在 C# 中针对大量维度最好地实现 K 近邻?
【发布时间】:2014-08-28 06:44:37
【问题描述】:

我正在 C# 中实现 K-最近邻分类算法,用于训练和测试集,每个样本约有 20,000 个样本和 25 个维度。

在我的实现中只有两个类,分别用“0”和“1”表示。目前,我有以下简单的实现:

// testSamples and trainSamples consists of about 20k vectors each with 25 dimensions
// trainClasses contains 0 or 1 signifying the corresponding class for each sample in trainSamples
static int[] TestKnnCase(IList<double[]> trainSamples, IList<double[]> testSamples, IList<int[]> trainClasses, int K)
{
    Console.WriteLine("Performing KNN with K = "+K);

    var testResults = new int[testSamples.Count()]; 

    var testNumber = testSamples.Count();
    var trainNumber = trainSamples.Count();
    // Declaring these here so that I don't have to 'new' them over and over again in the main loop, 
    // just to save some overhead
    var distances = new double[trainNumber][]; 
    for (var i = 0; i < trainNumber; i++)
    {
       distances[i] = new double[2]; // Will store both distance and index in here
    }

    // Performing KNN ...
    for (var tst = 0; tst < testNumber; tst++)
    {
        // For every test sample, calculate distance from every training sample
        Parallel.For(0, trainNumber, trn =>
        {
            var dist = GetDistance(testSamples[tst], trainSamples[trn]);
            // Storing distance as well as index 
            distances[trn][0] = dist;
            distances[trn][1] = trn;
        });

        // Sort distances and take top K (?What happens in case of multiple points at the same distance?)
        var votingDistances = distances.AsParallel().OrderBy(t => t[0]).Take(K);

        // Do a 'majority vote' to classify test sample
        var yea = 0.0;
        var nay = 0.0;

        foreach (var voter in votingDistances)
        {
            if (trainClasses[(int)voter[1]] == 1)  
               yea++;
            else
               nay++;
        }
        if (yea > nay)
            testResults[tst] = 1;
        else
            testResults[tst] = 0;

    }

    return testResults;
}

// Calculates and returns square of Euclidean distance between two vectors
static double GetDistance(IList<double> sample1, IList<double> sample2)
{
    var distance = 0.0;
    // assume sample1 and sample2 are valid i.e. same length 

    for (var i = 0; i < sample1.Count; i++)
    {   
        var temp = sample1[i] - sample2[i];
        distance += temp * temp;
    }
    return distance;
}

这需要相当长的时间来执行。在我的系统上,大约需要 80 秒才能完成。我该如何优化它,同时确保它也可以扩展到更多的数据样本?如您所见,我尝试使用 PLINQ 和并行 for 循环,这确实有帮助(没有这些,大约需要 120 秒)。我还能做什么?

我读过关于 KD-trees 一般对 KNN 有效,但我读到的每个资料都表示它们对更高维度无效。

我也找到了this stackoverflow discussion,但这似乎已经有 3 年历史了,我希望现在有人知道这个问题的更好解决方案。

我查看了 C# 中的机器学习库,但由于各种原因,我不想从我的 C# 程序中调用 R 或 C 代码,而且我看到的其他一些库并不比我看到的代码更有效书面。现在我只是想弄清楚如何自己编写最优化的代码。

编辑添加 - 我不能使用 PCA 或其他东西减少维度的数量。对于这个特定模型,需要 25 个维度。

【问题讨论】:

  • 您的代码目前似乎可以运行,并且您正在寻求改进它。一般来说,这些问题对于本网站来说过于固执己见,但您可能会在CodeReview.SE 找到更好的运气。记得阅读their requirements,因为他们比这个网站更严格。
  • 我不知道,谢谢@gunr2171,我也去那里试试。但是我仍然认为这对于这个网站来说也是一个有效的问题,因为我希望就可能使用不同的数据结构(如 KD-trees)来解决这个问题进行讨论,就像在我链接的 stackoverflow 帖子中一样。跨度>
  • programmers.stackexchange.com 可能会更好。寻找替代算法对于 SO 来说是“太宽泛”的边界。查看相关问题 - 有时其他语言的解决方案已经存在。
  • 也会尝试@AlexeiLevenkov,谢谢。我仍在寻找关于此的最新讨论。
  • 我目前正在开发一个 C# 模块,以优化高维问题(10 到 1000 维)中的 K-最近邻搜索。我使用希尔伯特曲线取得了巨大的成功。对于 K=50 个邻居、200 个维度、10,000 个点,我的线性扫描速度提高了 40 倍。将 n-D 点映射到 1-D Hilbert 索引,执行二进制搜索,然后使用距离函数对较小的列表进行排序。请参阅这篇文章:J. Shepherd、X. Zhu 和 N. Megiddo。 “一种用于多维最近邻搜索的快速索引方法”。

标签: c# optimization classification knn


【解决方案1】:

每当您尝试提高代码的性能时,第一步是分析当前性能,以准确了解它在哪里花费时间。一个好的分析器对此至关重要。在我以前的工作中,我能够使用dotTrace profiler 取得良好效果; Visual Studio 也有一个built-in profiler。一个好的分析器会准确地告诉你代码在哪里花费时间逐个方法甚至逐行。

话虽如此,在阅读您的实现时会想到一些事情:

  1. 您正在并行化一些内部循环。你可以并行化外循环吗?委托调用(请参阅herehere)有一个很小但非零的成本,这可能会在“Parallel.For”回调中影响您。

  2. 同样,使用其 IList 接口对数组进行索引也会有小的性能损失。您可以考虑将数组参数显式声明为“GetDistance()”。

  3. 与训练数组的大小相比,K 有多大?您正在对“距离”数组进行完全排序并取前 K,但如果 K 远小于数组大小,则使用 partial sort / selection 算法可能是有意义的,例如使用 SortedSet 和当集合大小超过 K 时替换最小元素。

【讨论】:

  • 感谢@dbc 的建议,我确实使用了 Visual Studio 分析器。它向我展示了 61% 的运行时间都花在了 GetDistance() 函数上。我还尝试更改 Parallel.For 循环以包含 GetDistance() 函数的代码而不是对该函数的调用,这以可读性为代价节省了我几秒钟的时间。 K 是 10,非常小,所以我也会尝试您的其他建议。
猜你喜欢
  • 2014-03-03
  • 2012-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-26
  • 2011-10-23
  • 2011-04-27
  • 2014-04-12
相关资源
最近更新 更多