繁体   English   中英

试图了解linq /延迟执行的工作方式

[英]Trying to understand how linq/deferred execution works

我有以下方法,这是执行分层k折交叉验证的部分逻辑。

private static IEnumerable<IEnumerable<int>> GenerateFolds(
   IClassificationProblemData problemData, int numberOfFolds) 
{
   IRandom random = new MersenneTwister();
   IEnumerable<double> values = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);

   var valuesIndices = 
       problemData.TrainingIndices.Zip(values, (i, v) => new { Index = i, Value = v });

   IEnumerable<IEnumerable<IEnumerable<int>>> foldsByClass = 
        valuesIndices.GroupBy(x => x.Value, x => x.Index)
                     .Select(g => GenerateFolds(g, g.Count(), numberOfFolds));

   var enumerators = foldsByClass.Select(x => x.GetEnumerator()).ToList();

   while (enumerators.All(e => e.MoveNext())) 
   {
       var fold = enumerators.SelectMany(e => e.Current).OrderBy(x => random.Next());
       yield return fold.ToList();
   }
}

折痕产生:

private static IEnumerable<IEnumerable<T>> GenerateFolds<T>(
    IEnumerable<T> values, int valuesCount, int numberOfFolds) 
{
    // number of folds rounded to integer and remainder
    int f = valuesCount / numberOfFolds, r = valuesCount % numberOfFolds; 
    int start = 0, end = f;

    for (int i = 0; i < numberOfFolds; ++i)
    {
        if (r > 0) 
        {
          ++end;
          --r;
        }

        yield return values.Skip(start).Take(end - start);
        start = end;
        end += f;
    }
 }

通用GenerateFolds<T方法简单地分割一个IEnumerable<T>成的序列IEnumerable根据折叠的指定数s。 例如,如果我有101个训练样本,它将生成11倍大小的10倍和10倍大小的9倍。

上面的方法根据类别值对样本进行分组,将每个组划分为指定的折痕数,然后将按类别的折痕合并为最终的折痕,以确保类别标签的分布相同。

我的问题是关于行yield return fold.ToList() ToList() ,该方法可以正常工作,但是如果删除ToList() ,结果将不再正确。 在我的测试用例中,我有641个训练样本和10折,这意味着第一折应为65倍,其余应为64倍。但是当我删除ToList() ,所有折应为64倍且类别标签为没有正确分配。 有什么想法吗? 谢谢。

让我们考虑什么是fold变量:

var fold = enumerators.SelectMany(e => e.Current).OrderBy(x => random.Next());

这不是查询执行的结果。 这是一个查询定义 因为SelectManyOrderBy都是具有延迟执行方式的运算符。 因此,它只保存了有关平整所有枚举器中的当前项目并以随机顺序返回它们的知识。 我已突出显示单词current ,因为它是查询执行时的当前项目。

现在让我们考虑一下何时执行该查询。 的结果GenerateFolds方法执行是IEnumerableIEnumerable<int> 查询 以下代码不执行任何查询:

var folds = GenerateFolds(indices, values, numberOfFolds);

再次只是一个查询。 您可以通过调用ToList()或枚举它来执行它:

var f = folds.ToList();

但是即使现在,内部查询也不会执行。 它们全部返回,但不执行。 while将查询保存到列表f执行了GenerateFolds while循环。 e.MoveNext()已被调用多次,直到退出循环为止:

while (enumerators.All(e => e.MoveNext()))
{
    var fold = enumerators.SelectMany(e => e.Current).OrderBy(x => random.Next());
    yield return fold;
}

那么, f什么呢? 它保存查询列表。 因此,您已经掌握了所有内容, 当前项目是每个枚举器的最后一个项目(请记住-我们在此时间点完全循环了while循环)。 但是这些查询都没有执行! 在这里,您首先执行它们:

f[0].Count() 

您将获得第一个查询返回的项目计数(在问题顶部定义)。 但是因此您已经枚举了所有查询,当前项目是最后一项。 这样您就可以得到最后一项的索引计数。

现在看看

folds.First().Count()

在这里,您没有列举所有查询以将其保存在列表中。 while循环仅执行一次, 当前项是第一项。 这就是为什么第一项中有索引计数的原因。 这就是为什么这些价值观不同的原因。

最后一个问题-为什么在while循环中添加ToList()时一切正常。 答案很简单-执行每个查询。 并且您有索引列表而不是查询定义。 每个查询都会在每次迭代中执行,因此当前项目始终是不同的。 而且您的代码工作正常。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM