簡體   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