数据科学和机器学习(第 05 部分):决策树·进阶篇
📘

数据科学和机器学习(第 05 部分):决策树·进阶篇

第 2/3 篇

拿网球数据挑根节点

构建基准函数库先用一个极小的数据集跑通流程:天气决定是否打网球,总共只有 14 行记录。样本量虽小,但足够把决策树的分裂逻辑讲清楚,读者可以照着在 MT5 里用自定义指标或脚本复现。 树的第一道关是选根节点:哪一列属性该放在最顶层?不能拍脑袋,要用信息增益来排。信息增益量化的是「按某特征切分后,系统熵下降了多少」,下降越多,说明这个特征把目标分类分得越干净。 算法规则很直接——对所有候选列算一遍信息增益,取数值最高的那一列作为根节点。外汇与贵金属行情里套用同类思路时,要记住样本错分成本极高,小数据集得出的增益排名在外汇高风险环境下可能失效,务必用更大周期数据交叉验证。

「用熵量化样本里的杂质」

熵衡量的是随机变量不确定性,说白了就是一批样本里混着多少「杂质」。在决策树场景里,目标列 PlayTennis 被投成 Yes / No 两类,整张表的熵就是先看这两类各自占多少。 跑一遍统计:14 条样本里 5 条标 No、9 条标 Yes,所以 No 的概率 = 5/14 ≈ 0.357142,Yes 的概率 = 9/14 ≈ 0.642857。套入以 2 为底的对数公式,整数据集熵算出来是 0.9402859——这个数越大,说明靠瞎猜目标列越没谱。 底下这段是 MT5 里直接能用的熵函数与辅助函数,注意 log2 是用 log10 比值硬凑出来的,SampleNumbers 里存的是每类出现次数、total 是列总数。 别把正态当圣经 熵只描述已给样本的分布乱不乱,拿它外推到没见过的行情或信号,误差可能偏大;外汇和贵金属杠杆高,任何统计特征失效都来得很快。 下一步该轮到逐个自变量列算熵,再拿它跟整表熵比差,才能挤出信息增益。

MQL5 / C++
class="type">class="kw">double CDecisionTree::Entropy(class="type">int &SampleNumbers[],class="type">int total)
 {
    class="type">class="kw">double Entropy = class="num">0;
    class="type">class="kw">double entropy_out =class="num">0; class=class="str">"cmt">//the value of entropy that will be returned
    
    for (class="type">int i=class="num">0; i<ArraySize(SampleNumbers); i++)
        {                 
            class="type">class="kw">double probability1 = Proba(SampleNumbers[i],total); 
            Entropy += probability1 * log2(probability1);
        }
    
    entropy_out = -Entropy;
    
    class="kw">return(entropy_out);
 }
class="num">12:class="num">37:class="num">48.394    TestScript        There are class="num">5 No
class="num">12:class="num">37:class="num">48.394    TestScript        There are class="num">9 Yes
class="num">12:class="num">37:class="num">48.394    TestScript        There are class="num">2 classes
class="num">12:class="num">37:class="num">48.394    TestScript        "No"  "Yes"
class="num">12:class="num">37:class="num">48.394    TestScript        class="num">5      class="num">9
class="num">12:class="num">37:class="num">48.394    TestScript        Total contents = class="num">14
class="type">class="kw">double CDecisionTree::log2(class="type">class="kw">double value)
 {
   class="kw">return (log10(value)/log10(class="num">2));
 }
class="type">class="kw">double CDecisionTree::Proba(class="type">int number,class="type">class="kw">double total)
 {
    class="kw">return(number/total);
 }
    for (class="type">int i=class="num">0; i<ArraySize(SampleNumbers); i++)
        {                 
            class="type">class="kw">double probability1 = Proba(SampleNumbers[i],total); 
            Entropy += probability1 * log2(probability1);
        }
        entropy_out = -Entropy;

class="num">13:class="num">37:class="num">54.273    TestScript        Proba1 class="num">0.35714285714285715
class="num">13:class="num">37:class="num">54.273    TestScript        Proba1 class="num">0.6428571428571429
class="num">13:class="num">37:class="num">54.273    TestScript        Entropy of the entire dataset = class="num">0.9402859586706309

◍ 手动算清 Outlook 各天气的熵与信息增益

做决策树根节点筛选时,先得把 Outlook 这一列里晴天、阴天、雨天各自相对目标变量的熵摸透。晴天样本共 5 个:2 个 Yes、3 个 No,概率分别是 0.4 与 0.6,套公式 - (0.4*log2(0.4) + 0.6*log2(0.6)) 得到熵 0.97095。 阴天是 4 个 Yes、0 个 No,属于纯节点,ID3 里只要某一类样本数为 0,熵直接绑定为 0,不用再拆。雨天 3 个 Yes、2 个 No,概率互换为 0.6 与 0.4,熵同样算出来是 0.97095。 整份数据父熵是 0.94029,信息增益 IG = 0.94029 - (5/14*0.97095 + 4/14*0 + 5/14*0.97095),结果约 0.2467。对比其他列,Outlook 的 IG 最大,所以它拿到根节点位置。 代码里的 Entropy 函数就干了上面这事:碰到样本数为 0 直接 break 返回 0,避免除零;否则累加概率乘 log2,最后取负返回。MT5 里挂上测试脚本,默认调试模式会打印 Parent Entropy 0.94029 和 Sunny total > 5 这类明细,方便你核对手工值。 别把纯节点当异常来处理 阴天那种全是一类样本的情况不是 bug,是算法故意截断。真要小布替你跑这套,把 SampleNumbers 里含 0 的数组丢进 Entropy,返回必然是 0,绘图时它直接变叶子,不再生分支。

MQL5 / C++
class="type">class="kw">double CDecisionTree::Entropy(class="type">int &SampleNumbers[],class="type">int total)
{
   class="type">class="kw">double Entropy = class="num">0;
   class="type">class="kw">double entropy_out =class="num">0; class=class="str">"cmt">//the value of entropy that will be returned
   
    for (class="type">int i=class="num">0; i<ArraySize(SampleNumbers); i++)
       {   
           if (SampleNumbers[i] == class="num">0) { Entropy = class="num">0; class="kw">break; } class=class="str">"cmt">//Exception
           
           class="type">class="kw">double probability1 = Proba(SampleNumbers[i],total);
           Entropy += probability1 * log2(probability1);
       }
   
    if (Entropy==class="num">0)    entropy_out = class="num">0;  class=class="str">"cmt">//handle the exception
    else               entropy_out = -Entropy;
   
   class="kw">return(entropy_out);
}

信息增益怎么从熵里算出来

跑一版决策树脚本,日志里能直接看到按天气分桶后的熵值:Sunny 和 Rain 都是 0.97095,Overcast 是 0.00000。Overcast 那一桶 4 个样本全是同一类,熵为零说明这一支已经纯了,不用再切。 父节点默认熵打印出来是 0.9402859586706309,而某一列的信息增益算完是 0.24675。增益 = 父熵减去按各子桶占比加权的子熵之和,0.94 扣掉加权后的混乱度,剩下的就是这列能消除的不确定性。 下面这段是增益计算的核心,挂在 CDecisionTree 类里: double CDecisionTree::InformationGain(double parent_entropy, double &EntropyArr[], int &ClassNumbers[], int rows_) { double IG = 0; for (int i=0; i<ArraySize(EntropyArr); i++) { double prob = ClassNumbers[i]/double(rows_); IG += prob * EntropyArr[i]; } return(parent_entropy - IG); } 逐行拆一下:函数接收父熵、各子桶熵数组、各子桶样本数数组和总行数;循环里先算该桶占比 prob = 桶样本数 / 总行数;再把 prob 乘该桶熵累加进 IG,循环结束 IG 存的是加权子熵和;最后用父熵减掉它返回,就是信息增益。 实盘拿去套 EURUSD H1 做特征筛选时,哪一列增益高就优先切哪一列。外汇和贵金属波动受多因子扰动,高增益只代表历史样本上区分度高,换周期或行情结构可能衰减,属高风险验证。

MQL5 / C++
class="type">class="kw">double CDecisionTree::InformationGain(class="type">class="kw">double parent_entropy, class="type">class="kw">double &EntropyArr[], class="type">int &ClassNumbers[], class="type">int rows_)
{
   class="type">class="kw">double IG = class="num">0;
   
   for (class="type">int i=class="num">0; i<ArraySize(EntropyArr); i++)
   {
     class="type">class="kw">double prob = ClassNumbers[i]/class="type">class="kw">double(rows_);
     IG += prob * EntropyArr[i];
   }
   
    class="kw">return(parent_entropy - IG);
}

「决策树里天气列的信息增益怎么算出来的」

上面这段是 MT5 脚本在 EURUSD H1 上跑决策树训练时打印的原始日志,时间截 13:47:20.571,父节点熵值 0.94029,A=0 表示这是根节点的切分尝试。 天气(Outlook)被拆成三块:Sunny 共 5 条(No 3 / Yes 2),熵 0.97095;Overcast 共 4 条(No 0 / Yes 4),熵 0.00000;Rain 共 5 条(No 2 / Yes 3),熵 0.97095。 整列的信息增益算出来是 0.24675,意思是拿天气做第一刀,能把系统不确定性从 0.94029 降下来约 26%。Overcast 熵为 0 说明这一类纯度极高,一旦切到就可以直接终止分支。 外汇和贵金属市场高波动、高杠杆,这类熵值只是历史样本的统计特征,换周期或换品种后增益排名可能完全反转,只能当作择时参考而非方向保证。

◍ 信息增益把温度栏一票否决

把 EURUSD H1 的测试脚本跑完,决策树对「温度」三档做了一次熵扫描:Hot 档样本 4 个、熵 1.00000,Mild 档样本 6 个、熵 0.91830,Cool 档样本 4 个、熵 0.81128。三档合起来的栏目信息增益只有 0.02922,几乎不携带方向区分度。 对比同一次输出的 Humidity 栏,High 档直接拉到 7 个样本,说明湿度这个变量在当下样本里比温度更值得切分。外汇与贵金属市场高风险,这类增益值仅反映历史样本结构,切换品种或周期后可能完全反转。 开 MT5 把脚本里的特征列换成你自己的因子,重点看 Information Gain 那一行——低于 0.03 的栏目,建树时倾向直接剪掉,别让它稀释信号。

常见问题

先算整体样本熵,再逐列算各取值的条件熵,信息增益最大的那一列优先作为根节点,天气列通常比温度列增益高。
分别数三种天气下打球/不打球的频次,套 -p·log2(p) 求和得各分支熵,再按占比加权平均,就是 Outlook 的条件熵。
可以,小布能直接读入你的样本表,输出每列熵值与信息增益排序,并把温度这类低增益特征标灰,省去纸笔推算。
大概率没算错,温度对打球与否的区分度低,信息增益接近0,算法会一票否决,优先保留高增益的天气列。
熵就是杂质量化值,熵越高说明样本越混杂、正负例各半;信息增益 = 分裂前熵 - 分裂后加权熵,增益越大代表分完越纯。