交叉验证为什么能提高精度?

作者: 康康 分类: 机器学习 发布时间: 2018-04-01 10:09

先来说说什么是交叉验证:

1. 验证集

在使用一个机器学习模型时,通常有一些参数需要设置,比如:

  • KNN中的kk,距离函数;
  • SVM算法中的(C, gamma);
  • GBDT中的迭代次数,树的深度;

这些参数称为超参数(hyperparameters),好的参数可以极大提高算法的预测性能。选择合适的模型参数过程称为模型选择(model selection)。那么如何选择这些参数呢?在模型学习过程中,通常做法是将数据分为训练集和测试集,其中训练集用来训练模型,测试集用来预测模型在未知数据上的预测性能。需要注意的是,绝对不能用测试集来调整这些超参数

用测试集来调整参数的危害之一是,模型可能在测试集上取得较好地预测性能,然而当我们实际部署模型时,却发现性能很差。实际上,模型对测试集产生了过拟合。换种思路看这个问题的话,若我们使用测试集调整参数,实际上我们已经将测试集当做训练集来使用,这样模型在看的见的数据上取得不错的性能,当部署模型到实际应用时,模型对于没见过的数据预测性能很差,也就是说模型泛化能力很弱。

正确的做法是在整个过程中,测试集只能被使用一次,而且是在最后一步。那么怎么样调整这些参数呢,可以将训练集分为两部分,其中数据多的部分用来训练模型,数据少的部分,用来调整参数,这部分也称为验证集。

2. 交叉验证

当我们用来训练模型的数据规模(包括训练集和验证集)不大时,将其中部分数据划分为验证集用来调整参数有些浪费,增加了模型过拟合的可能性。这时可以采用K重交叉验证的办法。

K重交叉验证相比把数据集分为(test, validation, train sets)的做法可以充分利用所有的数据,另一方面,也可以避免过拟合。做法为:将训练集分为K份,选择其中K-1份作为train set,另一份作为validation set,训练K次,同时也测试K次,将K次的平均作为该参数下的validation结果。然后对于不同的参数,重复这样的训练,选择准确率最高的参数作为最后的参数。需要注意的是,在训练过程中不接触test set。5重交叉验证的一个例子如下图所示:
这里写图片描述

在python中使用K重交叉验证的一个示例代码为:

<pre class="prettyprint"><code class="language-python hljs  has-numbering"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calc_params</span><span class="hljs-params">(X, y, clf, param_values, param_name, K)</span>:</span>
    <span class="hljs-comment"># initialize training and testing scores with zeros</span>
    train_scores = np.zeros(len(param_values))
    test_scores = np.zeros(len(param_values))

    <span class="hljs-comment"># iterate over the different parameter values</span>
    <span class="hljs-keyword">for</span> i, param_value <span class="hljs-keyword">in</span> enumerate(param_values):
        <span class="hljs-keyword">print</span> param_name, <span class="hljs-string">' = '</span>, param_value

        <span class="hljs-comment"># set classifier parameters</span>
        clf.set_params(**{param_name:param_value})

        <span class="hljs-comment"># initialize the K scores obtained for each fold</span>
        k_train_scores = np.zeros(K)
        k_test_scores = np.zeros(K)

        <span class="hljs-comment"># create KFold cross validation</span>
        cv = KFold(n_samples, K, shuffle=<span class="hljs-keyword">True</span>, random_state=<span class="hljs-number">0</span>)

        <span class="hljs-comment"># iterate over the K folds</span>
        <span class="hljs-keyword">for</span> j, (train, test) <span class="hljs-keyword">in</span> enumerate(cv):
            <span class="hljs-comment"># fit the classifier in the corresponding fold</span>
            <span class="hljs-comment"># and obtain the corresponding accuracy scores on train and test sets</span>
            clf.fit([X[k] <span class="hljs-keyword">for</span> k <span class="hljs-keyword">in</span> train], y[train])
            k_train_scores[j] = clf.score([X[k] <span class="hljs-keyword">for</span> k <span class="hljs-keyword">in</span> train], y[train])
            k_test_scores[j] = clf.score([X[k] <span class="hljs-keyword">for</span> k <span class="hljs-keyword">in</span> test], y[test])

        <span class="hljs-comment"># store the mean of the K fold scores</span>
        train_scores[i] = np.mean(k_train_scores)
        test_scores[i] = np.mean(k_test_scores)

    <span class="hljs-comment"># plot the training and testing scores in a log scale</span>
    plt.semilogx(param_values, train_scores, alpha=<span class="hljs-number">0.4</span>, lw=<span class="hljs-number">2</span>, c=<span class="hljs-string">'b'</span>)
    plt.semilogx(param_values, test_scores, alpha=<span class="hljs-number">0.4</span>, lw=<span class="hljs-number">2</span>, c=<span class="hljs-string">'g'</span>)

    plt.xlabel(param_name + <span class="hljs-string">" values"</span>)
    plt.ylabel(<span class="hljs-string">"Mean cross validation accuracy"</span>)

    <span class="hljs-comment"># return the training and testing scores on each parameter value</span>
    <span class="hljs-keyword">return</span> train_scores, test_scores
</code>

采用3重交叉验证的函数调用代码:

<pre class="prettyprint"><code class="language-python hljs  has-numbering">alphas = np.logspace(-<span class="hljs-number">7</span>, <span class="hljs-number">0</span>, <span class="hljs-number">8</span>)
train_scores, test_scores = calc_params(X, y, clf, alphas, <span class="hljs-string">'nb__alpha'</span>, <span class="hljs-number">3</span>)
<span class="hljs-keyword">print</span> <span class="hljs-string">'training scores: '</span>, train_scores
<span class="hljs-keyword">print</span> <span class="hljs-string">'testing scores: '</span>, test_scores
</code>

运行结果:

<pre class="prettyprint"><code class="language-python hljs  has-numbering">nb__alpha  =  <span class="hljs-number">1e-07</span>
nb__alpha  =  <span class="hljs-number">1e-06</span>
nb__alpha  =  <span class="hljs-number">1e-05</span>
nb__alpha  =  <span class="hljs-number">0.0001</span>
nb__alpha  =  <span class="hljs-number">0.001</span>
nb__alpha  =  <span class="hljs-number">0.01</span>
nb__alpha  =  <span class="hljs-number">0.1</span>
nb__alpha  =  <span class="hljs-number">1.0</span>

training scores:  [ <span class="hljs-number">1.</span> <span class="hljs-number">1.</span> <span class="hljs-number">1.</span> <span class="hljs-number">1.</span>  <span class="hljs-number">1.</span>  <span class="hljs-number">1.</span>  <span class="hljs-number">0.99683333</span>  <span class="hljs-number">0.97416667</span>]
testing scores:  [ <span class="hljs-number">0.7713</span>  <span class="hljs-number">0.7766</span>  <span class="hljs-number">0.7823</span>  <span class="hljs-number">0.7943</span>  <span class="hljs-number">0.8033</span> <span class="hljs-number">0.814</span>  <span class="hljs-number">0.8073</span>  <span class="hljs-number">0.7453</span>]
</code>

可以看出,当参数为0.01时,取得最好的测试结果。

3. 交叉验证选择特征

交叉验证除了选择模型参数外,还可以用于特征的选择。对于分类或者回归算法来说,并不是说特征的数量越多越好,一般需要对提取到的特征进行选择(feature selection)。首先,需要对特征的预测能力进行排序,然后通过交叉验证,选择最优比例的特征组合,来作为最终使用的特征。

下面给出一个决策树算法通过交叉验证选择最优特征比例的python代码:

<pre class="prettyprint"><code class="language-python hljs  has-numbering"><span class="hljs-keyword">from</span> sklearn <span class="hljs-keyword">import</span> cross_validation

percentiles = range(<span class="hljs-number">1</span>, <span class="hljs-number">100</span>, <span class="hljs-number">5</span>)
results = []
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, <span class="hljs-number">100</span>, <span class="hljs-number">5</span>):
    fs = feature_selection.SelectPercentile(feature_selection.chi2, percentile=i)
    X_train_fs = fs.fit_transform(X_train, y_train)
    scores = cross_validation.cross_val_score(dt, X_train_fs, y_train, cv=<span class="hljs-number">5</span>)
    <span class="hljs-comment">#print i,scores.mean()</span>
    results = np.append(results, scores.mean())

optimal_percentil = np.where(results == results.max())[<span class="hljs-number">0</span>]
<span class="hljs-keyword">print</span> <span class="hljs-string">"Optimal number of features:{0}"</span>.format(percentiles[optimal_percentil]), <span class="hljs-string">"\n"


运行结果为:

<pre class="prettyprint"><code class="language-python hljs  has-numbering">Optimal number of features:<span class="hljs-number">6</span> 
Mean scores: [ <span class="hljs-number">0.83332303</span>  <span class="hljs-number">0.87804576</span>  <span class="hljs-number">0.87195424</span>  <span class="hljs-number">0.86994434</span>  <span class="hljs-number">0.87399505</span>  <span class="hljs-number">0.86891363</span>
  <span class="hljs-number">0.86992373</span>  <span class="hljs-number">0.86991342</span>  <span class="hljs-number">0.87195424</span>  <span class="hljs-number">0.86991342</span>  <span class="hljs-number">0.87194393</span>  <span class="hljs-number">0.87398475</span>
  <span class="hljs-number">0.86991342</span>  <span class="hljs-number">0.87093383</span>  <span class="hljs-number">0.86992373</span>  <span class="hljs-number">0.86074005</span>  <span class="hljs-number">0.86583179</span>  <span class="hljs-number">0.86790353</span>
  <span class="hljs-number">0.86891363</span>  <span class="hljs-number">0.8648423</span> ]


可以看出,最优的特征比例是6%,剩余的大部分特征是冗余的。

交叉验证条件

有时亦称循环估计, 是用来验证分类器的性能的一种统计分析方法。它用于分析机器学习算法的泛化能力(generalization). 其基本思想是将原始数据(data set)进行分组,一部分作为训练集(training set),一部分作为测试集 (testing set)。首先利用训练集对分类器进行训练,再利用测试集来测试得到的模型(model),以此来作为评价分类性能的指标。
交叉验证一般要尽量满足:
1)训练集的比例要足够多,一般大于一半
2)训练集和测试集要均匀抽样

常见的交叉验证类型:

1.Double Cross Validation (记为2-CV)

做法是将数据集分成两个相等大小的子集,进行两回合的分类器训练。在第一回合中,一个子集作为training set,另一个便作为testing set;在第二回合中,则将training set与testing set对换后,再次训练分类器,而其中我们比较关心的是两次testing sets的辨识率。不过在实务上2-CV并不常用,主要原因是training set样本数太少,通常不足以代表母体样本的分布,导致testing阶段辨识率容易出现明显落差。此外,2-CV中分子集的变异度大,往往无法达到“实验过程必须可以被复制”的要求。

2.K-folder cross-validation

将原始数据分成K个子集(一般是均分),将每个子集数据分别做一次测试集 (testing test),其余的K-1组子集数据作为训练集(trainning test),这样会得到K个模型,用这K个模型最终的验证集的分类准确率的平均数作为此K-CV下分类器的性能指标。K一般大于等于2,实际操作时一般从3开始取。交叉验证重复k次,每次选择一个子集作为测试集,并将k次的平均交叉验证识别正确率作为结果。
优点:所有的样本都被作为了训练集和测试集,每个样本都被验证一次。10-folder通常被使用。

3. K * 2 folder cross-validation

是k-folder cross-validation的一个变体,对每一个folder,都平均分成两个集合s0,s1,我们先在集合s0训练用s1测试,然后用s1训练s0测试。
优点是:测试和训练集都足够大,每一个个样本都被作为训练集和测试集。一般使用k=10

4. least-one-out cross-validation(LOOCV)

留一法。假设dataset中有N个样本,那LOOCV也就是N-CV,即每个样本单独作为一次测试集,剩余N-1个样本则做为训练集所以LOO-CV会得到N个模型,用这N个模型最终的验证集的分类准确率的平均数作为此下LOO-CV分类器的性能指标。相比于前面的K-CV,LOO-CV有两个明显的优点.
优点:1)每一回合中几乎所有的样本皆用于训练model,因此最接近原始样本的分布,估测所得的generalization error比较可靠。
2)实验过程中没有随机因素会影响实验数据,确保实验过程是可以被复制的。
缺点:
计算成本高,为需要建立的models数量与总样本数量相同,当总样本数量相当多时,LOOCV在实作上便有困难,除非每次训练model的速度很快,或是可以用平行化计算减少计算所需的时间。

十折交叉验证:10-fold cross validation
常用的测试方法。将数据集分成十分,轮流将其中9份作为训练数据,1份作为测试数据,进行试验。每次试验都会得出相应的正确率(或差错率)。10次的结果的正确率(或差错率)的平均值作为对算法精度的估计,一般还需要进行多次10折交叉验证(例如10次10折交叉验证),再求其均值,作为对算法准确性的估计。
之所以选择将数据集分为10份,是因为通过利用大量数据集、使用不同学习技术进行的大量试验,表明10折是获得最好误差估计的恰当选择,而且也有一些理论根据可以证明这一点。但这并非最终诊断,争议仍然存在。而且似乎5折或者20折与10折所得出的结果也相差无几。

3-fold cross validation

EA 与 k-CV 正确的搭配方法,是将 dataset 分成 k 等份的 subsets 后,每次取 1份 subset 作为 test set,其余 k-1 份作为 training set,并且将该组 training set 套用到 EA 的 fitness function 计算中(至于该 training set 如何进一步利用则没有限制)。因此,正确的 k-CV 会进行共 k 次的 EA 演化,建立 k 个classifiers。而 k-CV 的 test 辨识率,则是 k 组 test sets 对应到 EA 训练所得的 k 个 classifiers 辨识率之平均值。

转载和参考自:https://www.jianshu.com/p/201a164e1b35

             https://blog.csdn.net/u012526120/article/details/49105563

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注