You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Emgu.CV的C# SVM模型构建及K折交叉验证实现咨询

实现Emgu.CV中SVM的K折交叉验证数据划分

我刚好做过类似的C# + Emgu.CV的SVM K折交叉验证任务,给你分享一下我实现数据划分的思路和代码,亲测有效:

核心思路

  1. 先打乱数据:避免原始数据的顺序偏差(比如按类别排序)导致训练/测试集类别分布不均,影响验证结果。
  2. 均等划分K个折:计算每个折的样本量,遍历每个折时,取当前折作为测试集,其余所有折合并作为训练集。
  3. 处理边界情况:如果总样本数不能被K整除,最后一个折会包含剩余的样本,保证每个折的样本量差异最小。

完整代码示例

using Emgu.CV;
using Emgu.CV.ML;
using Emgu.CV.Structure;
using System;
using System.Linq;

// 假设你已经准备好特征矩阵(float类型,每行是一个样本的特征)和标签矩阵(int类型,每行对应一个样本的标签)
Matrix<float> features = /* 你的特征数据 */;
Matrix<int> labels = /* 你的标签数据 */;
int kFolds = 10; // 设置K值,这里用10折为例

int totalSamples = features.Rows;
Random rng = new Random();

// Step 1: 生成打乱的样本索引,避免顺序偏差
int[] shuffledIndices = Enumerable.Range(0, totalSamples)
                                  .OrderBy(_ => rng.Next())
                                  .ToArray();

// Step 2: 根据打乱的索引重新排列特征和标签矩阵
Matrix<float> shuffledFeatures = new Matrix<float>(totalSamples, features.Cols);
Matrix<int> shuffledLabels = new Matrix<int>(totalSamples, 1);
for (int i = 0; i < totalSamples; i++)
{
    // 复制对应索引的行到打乱后的矩阵
    features.GetRow(shuffledIndices[i]).CopyTo(shuffledFeatures.GetRow(i));
    labels.GetRow(shuffledIndices[i]).CopyTo(shuffledLabels.GetRow(i));
}

// Step 3: 执行K折交叉验证循环
for (int foldIdx = 0; foldIdx < kFolds; foldIdx++)
{
    // 计算当前折的测试集范围
    int testStart = foldIdx * totalSamples / kFolds;
    int testEnd = (foldIdx + 1) * totalSamples / kFolds;
    // 最后一折包含所有剩余样本,避免丢数据
    if (foldIdx == kFolds - 1)
    {
        testEnd = totalSamples;
    }
    int testSampleCount = testEnd - testStart;

    // 提取测试集
    Matrix<float> testSetFeatures = shuffledFeatures.GetRows(testStart, testSampleCount);
    Matrix<int> testSetLabels = shuffledLabels.GetRows(testStart, testSampleCount);

    // 提取训练集:合并测试集以外的所有样本
    Matrix<float> trainSetFeatures;
    Matrix<int> trainSetLabels;

    if (testStart == 0)
    {
        // 第一折:训练集是测试集之后的所有样本
        trainSetFeatures = shuffledFeatures.GetRows(testEnd, totalSamples - testEnd);
        trainSetLabels = shuffledLabels.GetRows(testEnd, totalSamples - testEnd);
    }
    else if (testEnd == totalSamples)
    {
        // 最后一折:训练集是测试集之前的所有样本
        trainSetFeatures = shuffledFeatures.GetRows(0, testStart);
        trainSetLabels = shuffledLabels.GetRows(0, testStart);
    }
    else
    {
        // 中间折:合并测试集前后的两部分样本
        Matrix<float> trainPart1 = shuffledFeatures.GetRows(0, testStart);
        Matrix<float> trainPart2 = shuffledFeatures.GetRows(testEnd, totalSamples - testEnd);
        trainSetFeatures = new Matrix<float>(trainPart1.Rows + trainPart2.Rows, trainPart1.Cols);
        trainPart1.CopyTo(trainSetFeatures.GetRow(0));
        trainPart2.CopyTo(trainSetFeatures.GetRow(trainPart1.Rows));

        Matrix<int> trainLabelsPart1 = shuffledLabels.GetRows(0, testStart);
        Matrix<int> trainLabelsPart2 = shuffledLabels.GetRows(testEnd, totalSamples - testEnd);
        trainSetLabels = new Matrix<int>(trainLabelsPart1.Rows + trainLabelsPart2.Rows, 1);
        trainLabelsPart1.CopyTo(trainSetLabels.GetRow(0));
        trainLabelsPart2.CopyTo(trainSetLabels.GetRow(trainLabelsPart1.Rows));

        // 临时资源释放
        trainPart1.Dispose();
        trainPart2.Dispose();
        trainLabelsPart1.Dispose();
        trainLabelsPart2.Dispose();
    }

    // ----------------------
    // 这里执行SVM训练和测试
    // ----------------------
    SVM svmModel = new SVM();
    // 设置SVM参数,这里用线性核为例,你可以根据需求调整(比如RBF核)
    svmModel.SetKernel(SVM.SvmKernelType.Linear);
    svmModel.SetC(1.0); // 惩罚系数
    svmModel.Train(trainSetFeatures, trainSetLabels, null, null);

    // 预测测试集
    Matrix<int> predictedLabels = new Matrix<int>(testSetLabels.Rows, 1);
    svmModel.Predict(testSetFeatures, predictedLabels);

    // 计算当前折的准确率
    int correctCount = predictedLabels.Equals(testSetLabels).Count(x => x[0] == 1);
    float accuracy = (float)correctCount / testSetLabels.Rows;
    Console.WriteLine($"第 {foldIdx + 1} 折验证准确率:{accuracy:P2}");

    // 释放当前折的资源,避免内存泄漏
    svmModel.Dispose();
    predictedLabels.Dispose();
    testSetFeatures.Dispose();
    testSetLabels.Dispose();
    trainSetFeatures.Dispose();
    trainSetLabels.Dispose();
}

// 释放全局打乱后的矩阵资源
shuffledFeatures.Dispose();
shuffledLabels.Dispose();

关键注意事项

  • 内存管理:Emgu.CV的Matrix是基于OpenCV的原生内存,使用后一定要调用Dispose()释放资源,否则会导致内存泄漏。
  • 数据打乱:这一步非常重要,如果原始数据是按类别排序的,不打乱会导致训练集和测试集的类别分布严重失衡,交叉验证结果完全不可信。
  • SVM参数调优:示例中用了简单的线性核和默认参数,实际任务中你可以通过网格搜索(Grid Search)来优化核函数、惩罚系数C等参数,提升模型性能。
  • 样本均衡:如果你的数据集存在类别不平衡问题,还需要在划分时考虑分层K折(保证每个折的类别比例和原始数据集一致),这时候可以先按标签分组,再分别打乱划分。

内容的提问来源于stack exchange,提问作者Імад Мосбах

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:09:33