在MATLAB中使用嵌套SVM调整参数

时间:2015-07-07 13:28:27

标签: matlab cross-validation

我有一个包含20个测试对象的数据集,其中有50个变量,结果向量为10,用于确定其状态。我想建立一个嵌套的交叉验证,这样我在内部折叠中执行特征选择以及调整SVM的超参数。然后应在外部折叠处测试这些参数。

我以前在使用逻辑回归进行特征选择方面做了这个(使用sequentialfs),但我不知道如何同时进行特征选择和超参数调整。

一些示例代码是理想的,但是对给定输出的设置和解释的一般解释也会有所帮助,因为我是SVM的新手。

功能选择必须是前进。如果可能的话,我希望输出是具有参数和所选功能的最佳整体SVM。当然,我也想知道测试集的错误率。使用的内核是二阶多项式,所以我想只有一个参数可以调整?

此外,我希望它在内部和外部交叉验证上都是5倍。

编辑:我找到了一些示例代码,应该做我想要的,但我似乎无法使它工作。任何人都可以解释如何在MATLAB中设置它吗?

https://stats.stackexchange.com/questions/40906/nested-cross-validation-for-classification-in-matlab

1 个答案:

答案 0 :(得分:3)

我提出以下答案,其中allData包含所有数据。每一行都是一个条目,每一列都是一个特征。名为targets的变量将包含目标类。有了这些假设,我将逐一解释下面附带的代码。

变量featSize将包含多个要素。

featSize = size(allData, 2);

变量kFolds将包含您想要的折叠数。您说5,但如果您的数据充足,我建议您至少10

kFolds = 5;

结构bestSVM将是"输出"你需要。您可以清除所有变量,除此之外。该结构将包含最佳SVM及其参数以及将产生最佳性能的特征索引。

bestSVM = struct('SVMModel', NaN, 'C', NaN, 'FeaturesIdx', NaN, 'Score', Inf);     

变量kIdx将包含基于数据和折叠数的交叉验证索引。它使用了matlab的函数crossvalind()

kIdx = crossvalind('Kfold', length(targets), kFolds);

现在,主外部循环将运行您在kFolds中指定的多次折叠,并将准备训练集和测试集(trainData,{{1 }})及其相应的目标(testDatatrainTarg),如下所示:

testTarg

现在,我们准备选择功能。我们将变量for k = 1:kFolds trainData = allData(kIdx~=k, :); trainTarg = targets(kIdx~=k); testData = allData(kIdx==k, :); testTarg = targets(kIdx==k); 初始化为bestFeatScore,以便稍后我们可以将SVM(得分)的性能与该值进行比较(可能会更进一步降低)。我们还初始化了一个结构Inf,其中包含所有可能的要素组合中最好的bestFeatCombo及其相应的要素索引SVM和参数feat

C

可能的功能组合数量为 bestFeatScore = inf; bestFeatCombo = struct('SVM', NaN, 'feat', NaN, 'C', NaN); 。例如,如果您总共有两个功能,则有2^featSize - 1个选项:1)仅选择功能1,2)仅选择功能2,3)仅选择功能1和2。因此,我们需要一个2^2 - 1 = 3循环来遍历所有可能的功能组合。

for

然而,有一个棘手的部分(我确信有更好的解决方案),你想先开始选择功能集。我认为这是一个二进制表示问题。假设您总共有三个要素 for b = 1:(2^featSize) - 1 ,那么,我们可以说二元向量[f1, f2, f3]表示要素[1, 0, 0]的选择,而忽略了其余部分。将二进制向量传递给matlab会给出索引错误。所以,我发现解决这个问题的最好方法是使用matlab的find()函数来查找非零元素的"索引和值"。因此,如果我们f1,变量featCombo = find([1, 0, 0])将等于featCombo。所以,我所做的是使用变量1(来自上面的for循环),它将包含一个数字,指示当前可能的要素组合,并使用matlab将其转换为大小为b的二进制向量&# 39; s函数de2bi()。例如,featSize给出de2bi(1, 3),正如您所注意到的那样,使用左数字作为最不重要的数字。 [1, 0, 0]代表de2bi(3, 3)[1, 1, 0]代表de2bi(5, 3),依此类推。然后,如果您在[1, 0, 1]上使用find()函数,则会导致您要选择的要素的索引。

de2bi()

例如,如果 featCombo = find(de2bi(b, featSize)); ,则:

featSize = 3

会给你这样的东西:

for b = 1:(2^featSize) - 1; display(de2bi(b, featSize)); end;

与find结合如下:

 1     0     0
 0     1     0
 1     1     0
 0     0     1
 1     0     1
 0     1     1
 1     1     1

会给你这样的东西:

for b = 1:(2^featSize) - 1; display(find(de2bi(b, featSize))); end;

适合用作logical indexing。因此, 1 2 1 2 3 1 3 2 3 1 2 3 将包含要选择的要素集(带索引的向量)。

下一部分将为BoxConstraint featCombo初始化grid search的变量,它也称为超参数(取决于您可能有其他参数的SVM类型)。 C将在网格搜索期间包含SVM的最佳性能,bestCScore将包含最佳bestC参数,C将在搜索期间拥有经过最佳训练的SVM。变量bestCSVM将包含搜索空间,在我的示例中,搜索空间从gridC2^-52^-3。如果您有足够的计算资源,我建议您将2^15更改为较小的增量,例如2.^(-5:2:15),或更小,2.^(-5:1:15),但要小心,因为它需要一段时间才能完成。同样,如果您的计算能力较低(或时间有限),则将时间间隔增加到2.^(-5:0.1:15)甚至2.^(-5:3:15),因为它知道它会很难选择超参数。

2.^(-5:4:15)

接下来,我们将基于数组 bestCScore = inf; bestC = NaN; bestCSVM = NaN; gridC = 2.^(-5:2:15); 开始网格搜索,我们将使用matlab的函数fitcsvm()训练SVM。 SVM使用由当前折叠确定的当前gridC进行训练,并使用trainData确定的特定选定要素进行训练。培训目标featCombo也由当前折叠确定。注意a)我使用的是RBF内核类型(因为你没有指定它),以及b)我让matlab 自动确定内核规模。如果要使用其他内核,则需要对此代码进行一些修改。

trainTarg

下一步是确定SVM对该折叠的那组特征的参数执行情况。我们使用SVM函数loss()执行此操作。

        for C = gridC
            anSVMModel = fitcsvm(trainData(:, featCombo), trainTarg, ...
                'KernelFunction', 'RBF', 'KernelScale', 'auto', ...
                'BoxConstraint', C);

如果当前SVM( L = loss(anSVMModel,testData(:, featCombo), testTarg); )的效果优于之前的最佳效果,我们会将得分保存到anSVMModel,将最佳参数保存到bestCScore,将最佳SVM保存到bestC 1}}。

bestCSVM

最内层循环结束时,我们应该拥有当前功能集的最佳超参数和SVM。因此,如果此SVM具有比任何其他任何其他功能集的先前训练的SVM更好的分数,我们将该SVM,该组功能和该超参数保存到由 if L < bestCScore bestCScore = L; bestC = C; bestCSVM = anSVMModel; end end 给出的结构中,{分别为{1}},bestFeatCombo.C`。

bestFeatCombo.SVM

但请注意,在上面的bestFeatCombo.feat语句中,我在 if (bestCScore < bestFeatScore) || ... ((bestCScore == bestFeatScore) && ... (length(featCombo) < length(bestFeatCombo.feat))) bestFeatScore = bestCScore; bestFeatCombo.SVM = bestCSVM; bestFeatCombo.feat = featCombo; bestFeatCombo.C = bestC; end end 子句中创建了一个特例。我说如果当前的SVM执行(得分)与目前最好的相同,但是当前的SVM具有一组较小的功能,可以提供相同的性能,我选择替换目前为止最好使用较少功能的SVM。

现在,在中间循环结束时,我们应该为任何一组功能提供最好的SVM,具有最佳超参数if(在网格范围内)搜索允许)。因此,我们可以将当前最佳(存储在结构or中)与目前总体最佳(存储在结构C中)进行比较

bestFeatCombo

这就是结束。正如我所说,您想要的输出位于bestSVM,其中包含 if bestFeatScore < bestSVM.Score bestSVM.SVMModel = bestFeatCombo.SVM; bestSVM.C = bestFeatCombo.C; bestSVM.FeaturesIdx = bestFeatCombo.feat; bestSVM.Score = bestFeatScore end end 中最佳的SVM,用于最佳功能组合,以及最佳超参数bestSVM作为网格搜索允许。

我希望这是有道理的。以下是使用matlab kFolds数据集的工作示例,该数据集具有C个样本和fisheriris个功能。

工作代码:

100

修改

要回答您编辑过的问题,您希望在参数选择中进行另外5次交叉验证,这就是您需要做的事情。

请将最内层循环更改为如下所示:

4

代码使用标准CV。但请注意以下行很重要:

load fisheriris
inds = ~strcmp(species,'setosa');
allData = meas(inds,:);
targets = species(inds);
featSize = size(allData, 2);
kFolds = 5;     % this is where you specify your number of folds
bestSVM = struct('SVMModel', NaN, ...     % this is to store the best SVM
    'C', NaN, 'FeaturesIdx', NaN, 'Score', Inf);     

kIdx = crossvalind('Kfold', length(targets), kFolds);
for k = 1:kFolds
    trainData = allData(kIdx~=k, :);
    trainTarg = targets(kIdx~=k);
    testData = allData(kIdx==k, :);
    testTarg = targets(kIdx==k);

    % forward feature selection starts
    bestFeatScore = inf;
    bestFeatCombo = struct('SVM', NaN, 'feat', NaN, 'C', NaN);
    for b = 1:(2^featSize) - 1
        % this is to choose the features. e.g. [1 0 0] selects the first
        % feature out of three features.
        featCombo = find(de2bi(b, featSize));

        % this is the grid search for the BoxConstraint
        bestCScore = inf;
        bestC = NaN;
        bestCSVM = NaN;
        gridC = 2.^(-5:2:15);
        for C = gridC
            anSVMModel = fitcsvm(trainData(:, featCombo), trainTarg, ...
                'KernelFunction', 'RBF', 'KernelScale', 'auto', ...
                'BoxConstraint', C);
            L = loss(anSVMModel,testData(:, featCombo), testTarg);
            if L < bestCScore        % saving best SVM on parameter
                bestCScore = L;      % selection
                bestC = C;
                bestCSVM = anSVMModel;
            end
        end

        % saving the best SVM on feature selection
        if (bestCScore < bestFeatScore) || ...
                ((bestCScore == bestFeatScore) && ...
                (length(featCombo) < length(bestFeatCombo.feat)))
            bestFeatScore = bestCScore;
            bestFeatCombo.SVM = bestCSVM;
            bestFeatCombo.feat = featCombo;
            bestFeatCombo.C = bestC;
        end
    end

    % saving the best SVM over all folds
    if bestFeatScore < bestSVM.Score
        bestSVM.SVMModel = bestFeatCombo.SVM;
        bestSVM.C = bestFeatCombo.C;
        bestSVM.FeaturesIdx = bestFeatCombo.feat;
        bestSVM.Score = bestFeatScore
    end
end

在这一行中,您可以保存所有折叠中的所有表演。但请注意,您必须平均,这是CV的目的。

        % this is the grid search for the BoxConstraint
        bestCScore = inf;
        bestC = NaN;
        gridC = 2.^(-5:2:15);
        for C = gridC
            % cross validation for parameter C
            kIdxC = crossvalind('Kfold', length(trainTarg), kFolds);
            L = zeros(1, kFolds);
            for kC = 1:kFolds
                trainDataC = trainData(kIdxC~=kC, :);
                trainTargC = trainTarg(kIdxC~=kC);
                testDataC = trainData(kIdxC==kC, :);
                testTargC = trainTarg(kIdxC==kC);
                anSVMModel = fitcsvm(trainDataC(:, featCombo), trainTargC, ...
                    'KernelFunction', 'RBF', 'KernelScale', 'auto', ...
                    'BoxConstraint', C);
                L(kC) = loss(anSVMModel,testDataC(:, featCombo), testTargC);
            end
            L = mean(L);
            if L < bestCScore
                bestCScore = L;
                bestC = C;
            end
        end
        % we need to retrain here and save the SVM for the best C
        bestCSVM = fitcsvm(trainData(:, featCombo), trainTarg, ...
            'KernelFunction', 'RBF', 'KernelScale', 'auto', ...
            'BoxConstraint', bestC);
        bestCScore = loss(bestCSVM,testData(:, featCombo), testTarg);

你还需要进行一次再培训。