使用sklearn进行集成学习——实践

转自:http://www.cnblogs.com/jasonfreak/p/5720137.htmlhtml

<div id="cnblogs_post_body" class="blogpost-body"><h1>系列</h1> <ul> <li><a href="http://www.cnblogs.com/jasonfreak/p/5657196.html" target="_blank">《使用sklearn进行集成学习——理论》</a></li> <li><a href="http://www.cnblogs.com/jasonfreak/p/5720137.html" target="_blank">《使用sklearn进行集成学习——实践》</a></li> </ul> <h1>目录</h1> <p>1 Random Forest和Gradient Tree Boosting参数详解<br>2 如何调参?<br>  2.1 调参的目标:误差和方差的协调<br>  2.2 参数对总体模型性能的影响<br>  2.3 一个朴实的方案:贪心的坐标降低法<br>    2.3.1 Random Forest调参案例:Digit Recognizer<br>      2.3.1.1 调整过程影响类参数<br>      2.3.1.2 调整子模型影响类参数<br>    2.3.2 Gradient Tree Boosting调参案例:Hackathon3.x<br>      2.3.2.1 调整过程影响类参数<br>      2.3.2.2 调整子模型影响类参数<br>      2.3.2.3 杀一记回马枪<br>  2.4 “局部最优解”(舒适提示:看到这里有彩蛋!)<br>   2.5 类别不均衡的陷阱<br>3 总结<br>4 参考资料</p> <hr> <h1>1 Random Forest和Gradient Tree Boosting参数详解</h1> <p>  在sklearn.ensemble库中,咱们能够找到Random Forest分类和回归的实现:RandomForestClassifier和RandomForestRegression,Gradient Tree Boosting分类和回归的实现:GradientBoostingClassifier和GradientBoostingRegression。有了这些模型后,立立刻手操练起来?少侠请留步!且听我说一说,使用这些模型时常遇到的问题:</p> <ul> <li>明明模型调教得很好了,但是效果离个人想象总有些误差?——模型训练的第一步就是要定好目标,往错误的方向走太多也是后退。</li> <li>凭直觉调了某个参数,但是竟然没有任何做用,有时甚至起到副作用?——定好目标后,接下来就是要肯定哪些参数是影响目标的,其对目标是正影响仍是负影响,影响的大小。</li> <li>感受训练结束遥遥无期,sklearn只是个在小数据上的玩具?——虽然sklearn并非基于分布式计算环境而设计的,但咱们仍是能够经过某些策略提升训练的效率。</li> <li>模型开始训练了,可是训练到哪一步了呢?——饱暖思淫欲啊,目标,性能和效率都得了知足后,咱们有时还须要有别的追求,例如训练过程的输出,袋外得分计算等等。</li>node

</ul> <p>  经过总结这些常见的问题,咱们能够把模型的参数分为4类:目标类、性能类、效率类和附加类。下表详细地展现了4个模型参数的意义:</p> <table border="0" align="center"> <tbody> <tr> <td><strong>参数</strong></td> <td><strong>类型</strong></td> <td><strong>RandomForestClassifier</strong></td> <td><strong>RandomForestRegressor</strong></td> <td><strong>GradientBoostingClassifier</strong></td> <td><strong>GradientBoostingRegressor</strong></td>python

</tr> <tr> <td>loss</td> <td style="background-color: #ff6347;">目标</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td> <p>损失函数</p> <p>● exponential:模型等同AdaBoost</p> <p>★ deviance:和Logistic Regression的损失函数一致</p>git

</td> <td> <p>损失函数</p> <p>● exponential:模型等同AdaBoost</p> <p>★ deviance:和Logistic Regression的损失函数一致</p>算法

</td>bootstrap

</tr> <tr> <td>alpha</td> <td style="background-color: #ff6347;">目标</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td>&nbsp;损失函数为huber或quantile的时,alpha为损失函数中的参数</td> <td>&nbsp;&nbsp;损失函数为huber或quantile的时,alpha为损失函数中的参数</td>数组

</tr> <tr> <td>class_weight</td> <td style="background-color: #ff6347;">目标</td> <td> <p>类别的权值</p>网络

</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td>dom

</tr> <tr> <td>n_estimators&nbsp;</td> <td style="background-color: #ffff00;">性能</td> <td> <p>子模型的数量</p> <p>● int:个数</p> <p>★ 10:默认值</p>机器学习

</td> <td>子模型的数量 <p>● int:个数</p> <p>★ 10:默认值</p>

</td> <td>子模型的数量 <p>● int:个数</p> <p>★ 100:默认值</p>

</td> <td>子模型的数量 <p>● int:个数</p> <p>★ 100:默认值</p>

</td>

</tr> <tr> <td>learning_rate</td> <td style="background-color: #ffff00;">性能</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td>学习率(缩减)</td> <td>学习率(缩减)</td>

</tr> <tr> <td>criterion</td> <td style="background-color: #ffff00;">性能</td> <td> <p>判断节点是否继续分裂采用的计算方法</p> <p>● entropy</p> <p>★ gini</p>

</td> <td> <p>判断节点是否继续分裂采用的计算方法</p> <p>★ mse</p>

</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td>

</tr> <tr> <td>max_features</td> <td style="background-color: #ffff00;">性能</td> <td> <p>节点分裂时参与判断的最大特征数</p> <p>● int:个数</p> <p>● float:占全部特征的百分比</p> <p>★ auto:全部特征数的开方</p> <p>● sqrt:全部特征数的开方</p> <p>● log2:全部特征数的log2值</p> <p>● None:等于全部特征数</p>

</td> <td> <p>节点分裂时参与判断的最大特征数</p> <p>● int:个数</p> <p>● float:占全部特征的百分比</p> <p>★ auto:全部特征数的开方</p> <p>● sqrt:全部特征数的开方</p> <p>● log2:全部特征数的log2值</p> <p>● None:等于全部特征数</p>

</td> <td> <p>节点分裂时参与判断的最大特征数</p> <p>● int:个数</p> <p>● float:占全部特征的百分比</p> <p>●&nbsp;auto:全部特征数的开方</p> <p>● sqrt:全部特征数的开方</p> <p>● log2:全部特征数的log2值</p> <p>★&nbsp;None:等于全部特征数</p>

</td> <td> <p>节点分裂时参与判断的最大特征数</p> <p>● int:个数</p> <p>● float:占全部特征的百分比</p> <p>●&nbsp;auto:全部特征数的开方</p> <p>● sqrt:全部特征数的开方</p> <p>● log2:全部特征数的log2值</p> <p>★&nbsp;None:等于全部特征数</p>

</td>

</tr> <tr> <td>max_depth</td> <td style="background-color: #ffff00;">性能</td> <td> <p>最大深度,若是max_leaf_nodes参数指定,则忽略</p> <p>● int:深度</p> <p>★ None:树会生长到全部叶子都分到一个类,或者某节点所表明的样本数已小于min_samples_split</p>

</td> <td>最大深度,若是max_leaf_nodes参数指定,则忽略 <p>● int:深度</p> <p>★ None:树会生长到全部叶子都分到一个类,或者某节点所表明的样本数已小于min_samples_split</p>

</td> <td> <p>最大深度,若是max_leaf_nodes参数指定,则忽略</p> <p>● int:深度</p> <p>★ 3:默认值</p>

</td> <td> <p>最大深度,若是max_leaf_nodes参数指定,则忽略</p> <p>● int:深度</p> <p>★ 3:默认值</p>

</td>

</tr> <tr> <td>min_samples_split</td> <td style="background-color: #ffff00;">性能</td> <td> <p>分裂所需的最小样本数</p> <p>● int:样本数</p> <p>★ 2:默认值</p>

</td> <td>&nbsp; <p>分裂所需的最小样本数</p> <p>● int:样本数</p> <p>★ 2:默认值</p>

</td> <td>&nbsp; <p>分裂所需的最小样本数</p> <p>● int:样本数</p> <p>★ 2:默认值</p>

</td> <td>&nbsp; <p>分裂所需的最小样本数</p> <p>● int:样本数</p> <p>★ 2:默认值</p>

</td>

</tr> <tr> <td>min_samples_leaf</td> <td style="background-color: #ffff00;">性能</td> <td> <p>叶节点最小样本数</p> <p>● int:样本数</p> <p>★ 1:默认值</p>

</td> <td> <p>叶节点最小样本数</p> <p>● int:样本数</p> <p>★ 1:默认值</p>

</td> <td> <p>叶节点最小样本数</p> <p>● int:样本数</p> <p>★ 1:默认值</p>

</td> <td> <p>叶节点最小样本数</p> <p>● int:样本数</p> <p>★ 1:默认值</p>

</td>

</tr> <tr> <td>min_weight_fraction_leaf</td> <td style="background-color: #ffff00;">性能</td> <td> <p>叶节点最小样本权重总值</p> <p>● float:权重总值</p> <p>★ 0:默认值</p>

</td> <td> <p>叶节点最小样本权重总值</p> <p>● float:权重总值</p> <p>★ 0:默认值</p>

</td> <td> <p>叶节点最小样本权重总值</p> <p>● float:权重总值</p> <p>★ 0:默认值</p>

</td> <td> <p>叶节点最小样本权重总值</p> <p>● float:权重总值</p> <p>★ 0:默认值</p>

</td>

</tr> <tr> <td>max_leaf_nodes</td> <td style="background-color: #ffff00;">性能</td> <td> <p>最大叶节点数</p> <p>● int:个数</p> <p>★ None:不限制叶节点数</p>

</td> <td> <p>最大叶节点数</p> <p>● int:个数</p> <p>★ None:不限制叶节点数</p>

</td> <td> <p>最大叶节点数</p> <p>● int:个数</p> <p>★ None:不限制叶节点数</p>

</td> <td> <p>最大叶节点数</p> <p>● int:个数</p> <p>★ None:不限制叶节点数</p>

</td>

</tr> <tr> <td>bootstrap</td> <td style="background-color: #ffff00;">性能</td> <td> <p>是否bootstrap对样本抽样</p> <p>● False:子模型的样本一致,子模型间强相关</p> <p>★ True:默认值</p>

</td> <td> <p>是否bootstrap对样本抽样</p> <p>● False:子模型的样本一致,子模型间强相关</p> <p>★ True:默认值</p>

</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td>

</tr> <tr> <td>subsample</td> <td style="background-color: #ffff00;">性能</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td> <p>子采样率</p> <p>● float:采样率</p> <p>★ 1.0:默认值</p>

</td> <td> <p>子采样率</p> <p>● float:采样率</p> <p>★ 1.0:默认值</p>

</td>

</tr> <tr> <td>init</td> <td style="background-color: #ffff00;">性能</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td>初始子模型</td> <td>初始子模型</td>

</tr> <tr> <td>n_jobs</td> <td style="background-color: #40e0d0;">效率</td> <td> <p>并行数</p> <p>● int:个数</p> <p>● -1:跟CPU核数一致</p> <p>★ 1:默认值</p>

</td> <td>并行数 <p>● int:个数</p> <p>● -1:跟CPU核数一致</p> <p>★ 1:默认值</p>

</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td>

</tr> <tr> <td>warm_start</td> <td style="background-color: #40e0d0;">效率</td> <td> <p>是否热启动,若是是,则下一次训练是以追加树的形式进行</p> <p>● bool:热启动</p> <p>★ False:默认值</p>

</td> <td> <p>是否热启动,若是是,则下一次训练是以追加树的形式进行</p> <p>● bool:热启动</p> <p>★ False:默认值</p>

</td> <td> <p>是否热启动,若是是,则下一次训练是以追加树的形式进行</p> <p>● bool:热启动</p> <p>★ False:默认值</p>

</td> <td> <p>是否热启动,若是是,则下一次训练是以追加树的形式进行</p> <p>● bool:热启动</p> <p>★ False:默认值</p>

</td>

</tr> <tr> <td>&nbsp;presort</td> <td style="background-color: #40e0d0;">效率</td> <td style="background-color: #dcdcdc;"> <p>&nbsp;</p>

</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td>&nbsp;是否预排序,预排序能够加速查找最佳分裂点,对于稀疏数据无论用 <p>● Bool</p> <p>★ auto:非稀疏数据则预排序,若稀疏数据则不预排序</p>

</td> <td>是否预排序,预排序能够加速查找最佳分裂点,对于稀疏数据无论用 <p>● Bool</p> <p>★ auto:非稀疏数据则预排序,若稀疏数据则不预排序</p>

</td>

</tr> <tr> <td>oob_score</td> <td style="background-color: #dda0dd;">附加</td> <td> <p>是否计算<a href="http://scikit-learn.org/stable/auto_examples/ensemble/plot_ensemble_oob.html" target="_blank">袋外得分</a></p> <p>★&nbsp;False:默认值</p>

</td> <td> <p>是否计算<a href="http://scikit-learn.org/stable/auto_examples/ensemble/plot_ensemble_oob.html" target="_blank">袋外得分</a></p> <p>★&nbsp;False:默认值</p>

</td> <td style="background-color: #dcdcdc;">&nbsp;</td> <td style="background-color: #dcdcdc;">&nbsp;</td>

</tr> <tr> <td>random_state</td> <td style="background-color: #dda0dd;">附加</td> <td>随机器对象</td> <td>随机器对象</td> <td>随机器对象</td> <td>随机器对象</td>

</tr> <tr> <td>verbose</td> <td style="background-color: #dda0dd;">附加</td> <td> <p>日志冗长度</p> <p>● int:冗长度</p> <p>★ 0:不输出训练过程</p> <p>● 1:偶尔输出</p> <p>● &gt;1:对每一个子模型都输出</p>

</td> <td> <p>日志冗长度</p> <p>● int:冗长度</p> <p>★ 0:不输出训练过程</p> <p>● 1:偶尔输出</p> <p>● &gt;1:对每一个子模型都输出</p>

</td> <td> <p>日志冗长度</p> <p>● int:冗长度</p> <p>★ 0:不输出训练过程</p> <p>● 1:偶尔输出</p> <p>● &gt;1:对每一个子模型都输出</p>

</td> <td> <p>日志冗长度</p> <p>● int:冗长度</p> <p>★ 0:不输出训练过程</p> <p>● 1:偶尔输出</p> <p>● &gt;1:对每一个子模型都输出</p>

</td>

</tr>

</tbody>

</table> <p><em># ★:默认值</em></p> <p>  不难发现,基于bagging的Random Forest模型和基于boosting的Gradient Tree Boosting模型有很多共同的参数,然而某些参数的默认值又相差甚远。在<a href="http://www.cnblogs.com/jasonfreak/p/5657196.html" target="_blank">《使用sklearn进行集成学习——理论》</a>一文中,咱们对bagging和boosting两种集成学习技术有了初步的了解。Random Forest的子模型都拥有较低的误差,总体模型的训练过程旨在下降方差,故其须要较少的子模型(n_estimators默认值为10)且子模型不为弱模型(max_depth的默认值为None),同时,下降子模型间的相关度能够起到减小总体模型的方差的效果(max_features的默认值为auto)。另外一方面,Gradient Tree Boosting的子模型都拥有较低的方差,总体模型的训练过程旨在下降误差,故其须要较多的子模型(n_estimators默认值为100)且子模型为弱模型(max_depth的默认值为3),可是下降子模型间的相关度不能显著减小总体模型的方差(max_features的默认值为None)。</p> <hr> <p>&nbsp;</p> <h1>2 如何调参?</h1> <p>  聪明的读者应当要发问了:”博主,就算你列出来每一个参数的意义,然并卵啊!我仍是不知道无从下手啊!”</p> <p>  参数分类的目的在于缩小调参的范围,首先咱们要明确训练的目标,把目标类的参数定下来。接下来,咱们须要根据数据集的大小,考虑是否采用一些提升训练效率的策略,不然一次训练就三天三夜,法国人孩子都生出来了。而后,咱们终于进入到了重中之重的环节:调整那些影响总体模型性能的参数。</p> <h2>2.1 调参的目标:误差和方差的协调</h2> <p>  一样在<a href="http://www.cnblogs.com/jasonfreak/p/5657196.html" target="_blank">《使用sklearn进行集成学习——理论》</a>中,咱们已讨论过误差和方差是怎样影响着模型的性能——准确度。调参的目标就是为了达到总体模型的误差和方差的大和谐!进一步,这些参数又可分为两类:过程影响类及子模型影响类。在子模型不变的前提下,某些参数能够经过改变训练的过程,从而影响模型的性能,诸如:“子模型数”(n_estimators)、“学习率”(learning_rate)等。另外,咱们还能够经过改变子模型性能来影响总体模型的性能,诸如:“最大树深度”(max_depth)、“分裂条件”(criterion)等。正因为bagging的训练过程旨在下降方差,而boosting的训练过程旨在下降误差,过程影响类的参数可以引发总体模型性能的大幅度变化。通常来讲,在此前提下,咱们继续微调子模型影响类的参数,从而进一步提升模型的性能。</p> <h2>2.2 参数对总体模型性能的影响</h2> <p>  假设模型是一个多元函数F,其输出值为模型的准确度。咱们能够固定其余参数,从而对某个参数对总体模型性能的影响进行分析:是正影响仍是负影响,影响的单调性?</p> <p>  对Random Forest来讲,增长“子模型数”(n_estimators)能够明显下降总体模型的方差,且不会对子模型的误差和方差有任何影响。模型的准确度会随着“子模型数”的增长而提升。因为减小的是总体模型方差公式的第二项,故准确度的提升有一个上限。在不一样的场景下,“分裂条件”(criterion)对模型的准确度的影响也不同,该参数须要在实际运用时灵活调整。调整“最大叶节点数”(max_leaf_nodes)以及“最大树深度”(max_depth)之一,能够粗粒度地调整树的结构:叶节点越多或者树越深,意味着子模型的误差越低,方差越高;同时,调整“分裂所需最小样本数”(min_samples_split)、“叶节点最小样本数”(min_samples_leaf)及“叶节点最小权重总值”(min_weight_fraction_leaf),能够更细粒度地调整树的结构:分裂所需样本数越少或者叶节点所需样本越少,也意味着子模型越复杂。通常来讲,咱们总采用bootstrap对样本进行子采样来下降子模型之间的关联度,从而下降总体模型的方差。适当地减小“分裂时考虑的最大特征数”(max_features),给子模型注入了另外的随机性,一样也达到了下降子模型之间关联度的效果。可是一味地下降该参数也是不行的,由于分裂时可选特征变少,模型的误差会愈来愈大。在下图中,咱们能够看到这些参数对Random Forest总体模型性能的影响:</p> <p><img src="//images2015.cnblogs.com/blog/927391/201607/927391-20160731184710919-487730249.jpg" alt="" width="1091" height="774"></p> <p>  对Gradient Tree Boosting来讲,“子模型数”(n_estimators)和“学习率”(learning_rate)须要联合调整才能尽量地提升模型的准确度:想象一下,A方案是走4步,每步走3米,B方案是走5步,每步走2米,哪一个方案能够更接近10米远的终点?同理,子模型越复杂,对应总体模型误差低,方差高,故“最大叶节点数”(max_leaf_nodes)、“最大树深度”(max_depth)等控制子模型结构的参数是与Random Forest一致的。相似“分裂时考虑的最大特征数”(max_features),下降“子采样率”(subsample),也会形成子模型间的关联度下降,总体模型的方差减少,可是当子采样率低到必定程度时,子模型的误差增大,将引发总体模型的准确度下降。还记得“初始模型”(init)是什么吗?不一样的损失函数有不同的初始模型定义,一般,初始模型是一个更加弱的模型(以“平均”状况来预测),虽然说支持自定义,大多数状况下保持默认便可。在下图中,咱们能够看到这些参数对Gradient Tree Boosting总体模型性能的影响:</p> <p><img src="//images2015.cnblogs.com/blog/927391/201607/927391-20160731184748841-69767136.jpg" alt="" width="1115" height="717"></p> <h2>2.3 一个朴实的方案:贪心的坐标降低法</h2> <p>  到此为止,咱们终于知道须要调整哪些参数,对于单个参数,咱们也知道怎么调整才能提高性能。然而,表示模型的函数F并非一元函数,这些参数须要共同调整才能获得全局最优解。也就是说,把这些参数丢给调参算法(诸如Grid Search)咯?对于小数据集,咱们还能这么任性,可是参数组合爆炸,在大数据集上,或许个人子子孙孙可以看到训练结果吧。实际上网格搜索也不必定能获得全局最优解,而另外一些研究者从解优化问题的角度尝试解决调参问题。</p> <p>  <a href="https://zh.wikipedia.org/wiki/%E5%9D%90%E6%A0%87%E4%B8%8B%E9%99%8D%E6%B3%95" target="_blank">坐标降低法</a>是一类优化算法,其最大的优点在于不用计算待优化的目标函数的梯度。咱们最容易想到一种特别朴实的相似于坐标降低法的方法,与坐标降低法不一样的是,其不是循环使用各个参数进行调整,而是贪心地选取了对总体模型性能影响最大的参数。参数对总体模型性能的影响力是动态变化的,故每一轮坐标选取的过程当中,这种方法在对每一个坐标的降低方向进行一次直线搜索(line search)。首先,找到那些可以提高总体模型性能的参数,其次确保提高是单调或近似单调的。这意味着,咱们筛选出来的参数是对总体模型性能有正影响的,且这种影响不是偶然性的,要知道,训练过程的随机性也会致使总体模型性能的细微区别,而这种区别是不具备单调性的。最后,在这些筛选出来的参数中,选取影响最大的参数进行调整便可。</p> <p>  没法对总体模型性能进行量化,也就谈不上去比较参数影响总体模型性能的程度。是的,咱们尚未一个准确的方法来量化总体模型性能,只能经过交叉验证来近似计算总体模型性能。然而交叉验证也存在随机性,假设咱们以验证集上的平均准确度做为总体模型的准确度,咱们还得关心在各个验证集上准确度的变异系数,若是变异系数过大,则平均值做为总体模型的准确度也是不合适的。在接下来的案例分析中,咱们所谈及的总体模型性能均是指平均准确度,请各位留心。</p> <h3>2.3.1 Random Forest调参案例:Digit Recognizer</h3> <p>  在这里,咱们选取Kaggle上101教学赛中的<a href="https://www.kaggle.com/c/digit-recognizer" target="_blank">Digit Recognizer</a>做为案例来演示对RandomForestClassifier调参的过程。固然,咱们也不要傻乎乎地手工去设定不一样的参数,而后训练模型。借助sklearn.grid_search库中的GridSearchCV类,不只能够自动化调参,同时还能够对每一种参数组合进行交叉验证计算平均准确度。</p> <h4>2.3.1.1 调整过程影响类参数</h4> <p>  首先,咱们须要对过程影响类参数进行调整,而Random Forest的过程影响类参数只有“子模型数”(n_estimators)。“子模型数”的默认值为10,在此基础上,咱们以10为单位,考察取值范围在1至201的调参状况:</p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730162932106-838038825.png" alt=""></p> <p><em># 左图为模型在验证集上的平均准确度,右图为准确度的变异系数。横轴为参数的取值。</em></p> <p>  经过上图咱们能够看到,随着“子模型数”的增长,总体模型的方差减小,其防止过拟合的能力加强,故总体模型的准确度提升。当“子模型数”增长到40以上时,准确度的提高逐渐不明显。考虑到训练的效率,最终咱们选择“子模型数”为200。此时,在Kaggle上提交结果,得分为:0.96500,很凑合。</p> <h4>2.3.1.2 调整子模型影响类参数</h4> <p>  在设定“子模型数”(n_estimators)为200的前提下,咱们依次对子模型影响类的参数对总体模型性能的影响力进行分析。</p> <p>  对“分裂条件”(criterion)分别取值gini和entropy,获得调参结果以下:</p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730165700763-1666232416.png" alt=""></p> <p>  显见,在此问题中,“分裂条件”保持默认值gini更加合适。</p> <p>  对“分裂时参与判断的最大特征数”(max_feature)以1为单位,设定取值范围为28至47,获得调参结果以下:</p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730170223903-942174073.png" alt=""></p> <p>  </p> <p>  “分裂时参与判断的最大特征数”的默认值auto,即总特征数(sqrt(784)=28)的开方。经过提高该参数,总体模型的准确度获得了提高。可见,该参数的默认值太小,致使了子模型的误差过大,从而总体模型的误差过大。同时,咱们还注意到,该参数对总体模型性能的影响是近似单调的:从28到38,模型的准确度逐步抖动提高。因此,咱们可考虑将该参数归入下一步的调参工做。</p> <p>  对“最大深度”(max_depth)以10为单位,设定取值范围为10到100,获得调参结果以下:</p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730171248153-89195782.png" alt=""></p> <p>  随着树的深度加深,子模型的误差减小,总体模型的准确度获得提高。从理论上来讲,子模型训练的后期,随着方差增大,子模型的准确度稍微下降,从而影响总体模型的准确度下降。看图中,彷佛取值范围从40到60的状况能够印证这一观点。不妨以1为单位,设定取值范围为40到59,更加细致地分析:</p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730171849434-652571651.png" alt=""></p> <p>  有点傻眼了,怎么跟预想的不太同样?为何模型准确度的变化在40到59之间没有鲜明的“规律”了?要分析这个问题,咱们得先思考一下,少一层子节点对子模型意味着什么?若少的那一层给原子模型带来的是方差增大,则新子模型会准确度提升;若少的那一层给原子模型带来的是误差减少,则新子模型会准确度下降。因此,细粒度的层次变化既可能使总体模型的准确度提高,也可能使总体模型的准确度下降。从而也说明了,该参数更适合进行粗粒度的调整。在训练的现阶段,“抖动”现象的发生说明,此时对该参数的调整已不太合适了。</p> <p>  对“分裂所需的最小样本数”(min_samples_split)以1为单位,设定取值范围为2到11,获得调参的结果:</p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730173505919-181940029.png" alt=""></p> <p>  咱们看到,随着分裂所需的最小样本数的增长,子模型的结构变得愈来愈简单,理论上来讲,首先应当因方差减少致使总体模型的准确度提高。可是,在训练的现阶段,子模型的误差增大的幅度比方差减少的幅度更大,因此总体模型的准确度持续降低。该参数的默认值为2,调参后,最优解保持2不变。</p> <p>  对“叶节点最小样本数”<span style="line-height: 1.5;">(min_samples_leaf)</span><span style="line-height: 1.5;">以1为单位,设定取值范围为1到10,获得调参结果以下:</span></p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730174140309-373143237.png" alt=""></p> <p>  同“分裂所需的最小样本数”,该参数也在调参后,保持最优解1不变。</p> <p>  对“最大叶节点数”(max_leaf_nodes<span style="line-height: 1.5;">)以100为单位,设定取值范围为2500到3400,获得调参结果以下:</span></p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160730174432372-770058569.png" alt=""></p> <p>  相似于“最大深度”,该参数的增大会带来模型准确的提高,但是因为后期“不规律”的抖动,咱们暂时不进行处理。</p> <p>  经过对以上参数的调参状况,咱们能够总结以下:</p> <table border="0" align="center"> <tbody> <tr> <td>参数</td> <td>默认值准确度</td> <td>调整后最佳准确度</td> <td>提高幅度</td>

</tr> <tr> <td>分裂条件(criterion)</td> <td>0.964023809524</td> <td>0.964023809524</td> <td>0</td>

</tr> <tr> <td>分裂时参与判断的最大特征数(max_feature)</td> <td>0.963380952381</td> <td>0.964428571429</td> <td>0.00104762</td>

</tr> <tr> <td>最大深度(max_depth)</td> <td>&nbsp;</td> <td>&nbsp;</td> <td>抖动</td>

</tr> <tr> <td>分裂所需的最小样本数(min_samples_split)</td> <td>0.963976190476</td> <td>0.963976190476</td> <td>0</td>

</tr> <tr> <td>叶节点最小样本数(min_samples_leaf)</td> <td>0.963595238095</td> <td>0.963595238095</td> <td>0</td>

</tr> <tr> <td>最大叶节点数(max_leaf_nodes)</td> <td>&nbsp;</td> <td>&nbsp;</td> <td>抖动</td>

</tr>

</tbody>

</table> <p>  接下来,咱们固定分裂时参与判断的最大特征(max_features)为38,在Kaggle上提交一次结果:<span style="color: #444444; font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; line-height: 1.5;">0.96671,比上一次调参好了</span><span style="color: #444444; font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;">0.00171,基本与咱们预期的提高效果一致。</span></p> <p>  还须要继续下一轮坐标降低式调参吗?通常来讲没有太大的必要,在本轮中出现了两个发生抖动现象的参数,而其余参数的调整均没有提高总体模型的性能。仍是得老调重弹:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。在DR竞赛中,与其期待经过对<span style="color: #444444; font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;"><span style="color: #000000; font-family: verdana, Arial, Helvetica, sans-serif; line-height: 1.5;">RandomForestClassifier调参来进一步提高总体模型的性能,不如挖掘出更有价值的特征,或者使用自带特征挖掘技能的模型(正如此题,图分类的问题更适合用神经网络来学习)。可是,在这里,咱们仍是能够自信地说,经过贪心的坐标降低法,比那些用</span></span><span style="line-height: 1.5;">网格搜索法穷举全部参数组合</span><span style="line-height: 1.5;">,自觉得获得最优解的朋友们更进了一步。</span></p> <h3>2.3.2 Gradient Tree Boosting调参案例:Hackathon3.x</h3> <p>  在这里,咱们选取Analytics Vidhya上的<a href="https://datahack.analyticsvidhya.com/contest/data-hackathon-3x/" target="_blank">Hackathon3.x</a>做为案例来演示对GradientBoostingClassifier调参的过程。</p> <h4>2.3.2.1 调整过程影响类参数</h4> <p>  GradientBoostingClassifier的过程影响类参数有“子模型数”(n_estimators)和“学习率”(learning_rate),咱们可使用GridSearchCV找到关于这两个参数的最优解。慢着!这里留了一个很大的陷阱:“子模型数”和“学习率”带来的性能提高是不均衡的,在前期会比较高,在后期会比较低,若是一开始咱们将这两个参数调成最优,这样很容易陷入一个“局部最优解”。在目标函数都不肯定的状况下(如是否凸?),谈局部最优解就是耍流氓,本文中“局部最优解”指的是调整各参数都无明显性能提高的一种状态,因此打了引号。下图中展现了这个两个参数的调参结果:</p> <p><img style="display: block; margin-left: auto; margin-right: auto;" src="//images2015.cnblogs.com/blog/927391/201607/927391-20160731161516950-670327363.png" alt=""></p> <p><em># 图中颜色越深表示总体模型的性能越高</em></p> <p>  在此,咱们先直觉地选择“子模型数”为60,“学习率”为0.1,此时的总体模型性能(平均准确度为0.8253)不是最好,可是也不差,良好水准。</p> <h4>2.3.2.2 调整子模型影响类参数</h4> <p>  对子模型影响类参数的调整与Random Forest相似。最终咱们对参数的调整以下:</p> <table border="0" align="center"> <tbody> <tr> <td> <p>子模型数</p> <p>n_estimators</p>

</td> <td> <p>学习率</p> <p>learning_rate</p>

</td> <td> <p>叶节点最小样本数</p> <p>min_samples_leaf</p>

</td> <td> <p>最大深度</p> <p>max_depth</p>

</td> <td> <p>子采样率</p> <p>subsample</p>

</td> <td> <p>分裂时参与判断的最大特征数</p> <p>max_feature</p>

</td>

</tr> <tr> <td>60</td> <td>0.1</td> <td>12</td> <td>4</td> <td>0.77</td> <td>10</td>

</tr>

</tbody>

</table> <p>  到此,总体模型性能为0.8313,与workbench(0.8253)相比,提高了约0.006。</p> <h4>2.3.2.3 杀一记回马枪</h4> <p>  还记得一开始咱们对“子模型数”(n_estimators)和“学习率”(learning_rate)手下留情了吗?如今咱们能够回过头来,调整这两个参数,调整的方法为成倍地放大“子模型数”,对应成倍地缩小“学习率”(learning_rate)。经过该方法,本例中总体模型性能又提高了约0.002。</p> <h2>2.4 “局部最优解”</h2> <p>  目前来讲,在调参工做中,普遍使用的还是一些经验法则。<a href="https://www.analyticsvidhya.com/blog/author/aarshay/" target="_blank">Aarshay Jain</a>对Gradient Tree Boosting总结了一套<a href="https://www.analyticsvidhya.com/blog/2016/02/complete-guide-parameter-tuning-gradient-boosting-gbm-python/" target="_blank">调参方法</a>,其核心思想在于:对过程影响类参数进行调整,毕竟它们对总体模型性能的影响最大,而后依据经验,在其余参数中选择对总体模型性能影响最大的参数,进行下一步调参。这种方法的关键是依照对总体模型性能的影响力给参数排序,而后按照该顺序对的参数进行调整。如何衡量参数对总体模型性能的影响力呢?基于经验,Aarshay提出他的看法:“最大叶节点数”(max_leaf_nodes)和“最大树深度”(max_depth)对总体模型性能的影响大于“分裂所需最小样本数”(min_samples_split)、“叶节点最小样本数”(min_samples_leaf)及“叶节点最小权重总值”(min_weight_fraction_leaf),而“分裂时考虑的最大特征数”(max_features)的影响力最小。</p> <p>  Aarshay提出的方法和贪心的坐标降低法最大的区别在于前者在调参以前就依照对总体模型性能的影响力给参数排序,然后者是一种“很天然”的贪心过程。还记得2.3.2.1小节中咱们讨论过“子模型数”(n_estimators)和“学习率”(learning_rate)的调参问题吗?同理,贪心的坐标降低法容易陷入“局部最优解”。对Random Forest调参时会稍微好一点,由于当“子模型数”调到最佳状态时,有时就只剩下诸如““分裂时参与判断的最大特征数”等Aarshay认为影响力最小的参数可调了。可是,对Gradient Tree Boosting调参时,遇到“局部最优解”的可能性就大得多。</p> <p>  Aarshay一样对Hackathon3.x进行了调参试验,因为特征提取方式的差别,参数赋值相同的状况下,本文的总体模型性能仍与其相差0.007左右(唉,不得再也不说一次,特征工程真的很重要)。首先,在过程影响类参数的选择上,Aarshay的方法与贪心的坐标降低法均选择了“子模型数”为60,“学习率”为0.1。接下来,Aarshay按照其定义的参数对总体模型性能的影响力,按序依次对参数进行调整。当子模型影响类参数肯定完成后,Aarshay的方法提高了约0.008的总体模型性能,略胜于贪心的坐标降低法的0.006。可是,回过头来继续调试“子模型数”和“学习率”以后,Aarshay的方法又提高了约0.01的总体模型性能,远胜于贪心的坐标降低法的0.002。</p> <p>  诶!诶!诶!少侠请住手!你说我为何要在这篇博文中介绍这种“无用”的贪心的坐标降低法?首先,这种方法很容易凭直觉就想到。人们每每花了不少的时间去搞懂模型的参数是什么含义,对总体模型性能有什么影响,搞懂这些已经不易了,因此接下来不少人选择了最直观的贪心的坐标降低法。经过一个实例,咱们更容易记住这种方法的局限性。除了做为反面教材,贪心的坐标降低法就没有意义了吗?不难看到,Aarshay的方法仍有改进的地方,在依次对参数进行调整时,仍是须要像贪心的坐标降低法中同样对参数的“动态”影响力进行分析一下,若是这种影响力是“抖动”的,无关紧要的,那么咱们就不须要对该参数进行调整。</p> <h2>2.5 类别不均衡的陷阱</h2> <p>  哈哈哈,这篇博文再次留了个陷阱,此段文字并非跟全文一块儿发布!有人要说了,按照个人描述,Aarshay的调参试验不可再现啊!其实,我故意没说Aarshay的另外一个关键处理:调参前的参数初始值。由于Hackathon3.x是一个类别不均衡的问题,因此若是直接先调试“最大深度”(max_depth),会发现其会保持默认值3做为最优解,然后面的调参中,“分裂所需最小样本数”(min_samples_split)、“叶节点最小样本数”(min_samples_leaf)再怎么调都没有很大做用。这是由于,正例样本远远小于反例,因此在低深度时,子模型就可能已经对正例过拟合了。因此,在类别不均衡时,只有先肯定“叶节点最小样本数”(min_samples_leaf),再肯定“分裂所需最小样本数”(min_samples_split),才能肯定“最大深度”。而Aarshay设定的初始值,则以经验和直觉避开了这个险恶的陷阱。</p> <p>  若是实在以为经验和直觉不靠谱,我还尝试了一种策略:首先,咱们须要初步地调一次“子采样率”(subsample)和“分裂时考虑的最大特征数”(max_features),在此基础上依次调好“叶节点最小样本数”(min_samples_leaf)、“分裂所需最小样本数”(min_samples_split)以及“最大深度”(max_depth)。而后,按照Aarshay的方法,按影响力从大到小再调一次。经过这种方法,总体模型性能在未等比缩放过程影响类参数前,已达到约0.8352左右,比workbench相比,提高了约0.1,与Aarshay的调参试验差很少,甚至更好一点点。</p> <p>  回过头来,咱们再次看看贪心的坐标降低法是怎么掉入这个陷阱的。在肯定过程影响类参数后,贪心的坐标降低法按照“动态”的对总体模型性能的影响力大小,选择了“叶节点最小样本数”进行调参。这一步看似和上一段的描述是一致的,可是,通常来讲,含随机性(“子采样率”和“分裂时考虑的最大特征数”先初步调过)的“叶节点最小样本数”要大于无随机性。举个例来讲,由于增长了随机性,致使了子采样后,某子样本中只有一个正例,且其能够经过惟一的特征将其分类,可是这个特征并非全部正例的共性,因此此时就要求“叶节点最小样本数”须要比无随机性时大。对贪心的坐标降低来讲,“子采样率”和“分裂时考虑的最大特征数”在当下,对总体模型性能的影响比不上“叶节点最小样本数”,因此栽了个大跟头。</p> <hr> <p>&nbsp;</p> <h2>3 总结</h2> <p>  在这篇博文中,我一反常态,花了大部分时间去试验和说明一个有瑕疵的方案。数据挖掘的工做中的方法和技巧,有很大一部分暂时还未被严谨地证实,因此有很大部分人,特别是刚入门的小青年们(也包括曾经的我),误觉得其是一门玄学。实际上,尽管没有被严谨地证实,咱们仍是能够经过试验、分析,特别是与现有方法进行对比,获得一个近似的合理性论证。</p> <p>  另外,小伙伴们大家有什么独到的调参方法吗?请不要有丝毫吝啬,狠狠地将大家的独门绝技全释放在我身上吧,请大胆留言,残酷批评!</p> <hr> <p>&nbsp;</p> <h2>4 参考资料</h2> <ol> <li><a href="http://www.cnblogs.com/jasonfreak/p/5657196.html" target="_blank">《使用sklearn进行集成学习——理论》</a></li> <li> <p><a href="https://www.analyticsvidhya.com/blog/2016/02/complete-guide-parameter-tuning-gradient-boosting-gbm-python/" target="_blank">Complete Guide to Parameter Tuning in Gradient Boosting (GBM) in Python</a></p>

</li> <li><a href="https://zh.wikipedia.org/wiki/%E5%9D%90%E6%A0%87%E4%B8%8B%E9%99%8D%E6%B3%95" target="_blank">坐标降低法</a></li> <li><a href="https://www.kaggle.com/c/digit-recognizer" target="_blank">Digit Recognizer</a></li> <li><a href="https://datahack.analyticsvidhya.com/contest/data-hackathon-3x/" target="_blank">Hackathon3.x</a></li>

</ol> <p>&nbsp;</p></div>

相关文章
相关标签/搜索