zoukankan      html  css  js  c++  java
  • Stacking

    Ensemble Learning的基本思想就是将多个基学习器组合在一起,产生泛化能力更强的模型。
    组合策略有许多种,包括Voting、平均法和Stacking等,Stacking就是选择某种学习器作为组合基学习器的方式。
    在这里插入图片描述
    既然要结合多个基学习器的优点,那么基学习器的选择最好是“准而不同”,元学习器一般选择比较简单的模型(如逻辑回归),防止过拟合。
    比较简单的想法就是将全部训练集用于训练基学习器,将基学习器的预测结果作为元学习器的训练集,从而得到整个模型。
    这样做的问题在于:基学习器最终在训练集上的表现非常好,再用基学习器在该训练集上的预测结果作为次级训练集,同样元学习器在该训练集上表现也会非常好,但是模型的泛化能力不一定很好,有过拟合的风险。
    因此,采用K折交叉验证的方式,用训练基学习器未使用的样本来产生次级训练集。
    在这里插入图片描述
    具体来讲:
    在训练阶段(假设训练集(400*10)),对于每个基学习器(假设有3个基学习器),进行5次训练与验证,得到(400*1)的验证结果,那么最终次级训练集是(400*3)(类标签还是原始类标签),用这些数据训练次级学习器,完成后再用全部的训练集训练所有的基学习器(可选,提高基学习器性能);
    在测试阶段,如果训练阶段选了最后一步,那么每个基学习器直接得到一个结果,就会得到3个测试结果,送入次级学习器,得到最终的预测结果;如果训练时没有选最后一步,那么每个基学习器都有5个小模型,将测试样本用5个小模型分别测试,将5个结果平均得到某个基学习器的预测结果,也会得到3个测试结果。
    在这里插入图片描述
    Weka里的实现:

    /**
       * Buildclassifier selects a classifier from the set of classifiers
       * by minimising error on the training data.
       *
       * @param data the training data to be used for generating the
       * boosted classifier.
       * @throws Exception if the classifier could not be built successfully
       */
      // 建立整个模型
      public void buildClassifier(Instances data) throws Exception {
        if (m_MetaClassifier == null) {
          throw new IllegalArgumentException("No meta classifier has been set");
        }
        // 判断分类器是否有能力处理该数据集
        getCapabilities().testWithFail(data);
        // 删除类标签缺失数据
        Instances newData = new Instances(data);
        m_BaseFormat = new Instances(data, 0);
        newData.deleteWithMissingClass();
        
        Random random = new Random(m_Seed);
        newData.randomize(random); // 打乱整个数据集
        // 如果是分类问题,分层抽样
        // 原始数据按照类标签集中在一起,按m_NumFolds为步长重新抽取数据,保持训练集/验证集数据分布一致性, 避免因数据划分引入额外的偏差
        if (newData.classAttribute().isNominal()) {
          newData.stratify(m_NumFolds);
        }
    
        // 处理原始数据得到新的数据,建立meta classifier
        generateMetaLevel(newData, random);
      
        // restart the executor pool because at the end of processing
        // a set of classifiers it gets shutdown to prevent the program
        // executing as a server
        // 创建线程池,为下面的基学习器训练做准备
        super.buildClassifier(newData);
    
        // 提高基础模型的准确度,使其在测试数据表现更好,用所有的训练集进行基学习器的训练
        // 这里为了节省时间,测试时,可以直接在多个基学习器预测后取平均
        // Rebuild all the base classifiers on the full training data
        buildClassifiers(newData);
      }
    
    /**
       * Generates the meta data
       * 
       * @param newData the data to work on
       * @param random the random number generator to use for cross-validation
       * @throws Exception if generation fails
       */
      protected void generateMetaLevel(Instances newData, Random random) 
        throws Exception {
        // 先用newData得到metaData的格式m_MetaFormat
        // 确定元分类器需要的属性
        Instances metaData = metaFormat(newData);
        m_MetaFormat = new Instances(metaData, 0);
    
        for (int j = 0; j < m_NumFolds; j++) {
          // 得到训练集
          Instances train = newData.trainCV(m_NumFolds, j, random);
          
          // start the executor pool (if necessary)
          // has to be done after each set of classifiers as the
          // executor pool gets shut down in order to prevent the
          // program executing as a server (and not returning to
          // the command prompt when run from the command line
          // 线程池,多线程并行构建基学习器
          super.buildClassifier(train);
    
          // 构建基学习器
          buildClassifiers(train);
          
          // Classify test instances and add to meta data
          // 将未使用过的原始训练数据通过基学习器预测后加入metadata作为新的训练集
          Instances test = newData.testCV(m_NumFolds, j);
          for (int i = 0; i < test.numInstances(); i++) {
    	    metaData.add(metaInstance(test.instance(i)));
          }
        }
        // 利用元数据建立元分类器
        m_MetaClassifier.buildClassifier(metaData);    
      }
    

    因为基学习器之间的训练是独立的,所以每次交叉验证划分好数据后,都是利用线程池并行训练。
    如果是在分层抽样的基础上划分训练集和验证集,trainCV()抽取数据后,需要将新的训练集Shuffle,保证独立同分布。

    /**
       * Makes the format for the level-1 data.
       *
       * @param instances the level-0 format
       * @return the format for the meta data
       * @throws Exception if the format generation fails
       */
      // 生成元数据格式
      protected Instances metaFormat(Instances instances) throws Exception {
        // 如果m_BaseFormat属性连续,就加入m_Classifiers.length个属性
        // 如果是离散的,每次要加入level 0类别属性取值个数个属性
        ArrayList<Attribute> attributes = new ArrayList<Attribute>();
        Instances metaFormat;
    
        // 遍历基学习器
        for (int k = 0; k < m_Classifiers.length; k++) {
          Classifier classifier = (Classifier) getClassifier(k);
          String name = classifier.getClass().getName() + "-" + (k+1);
          if (m_BaseFormat.classAttribute().isNumeric()) {
    	    attributes.add(new Attribute(name));
          } else {
              // 如果离散,后续会通过每个取值的概率来判断,比如杂色、圆花,这2种特性不能用一个属性表示,所以每个取值都要独立成单独的属性
              // 来保存概率值
    	      for (int j = 0; j < m_BaseFormat.classAttribute().numValues(); j++) {
    	        attributes.add(
    	          new Attribute(
    		      name + ":" + m_BaseFormat.classAttribute().value(j)));
    	    }
          }
        }
        // 加上原始类标签
        attributes.add((Attribute) m_BaseFormat.classAttribute().copy());
        // 形成元数据格式
        metaFormat = new Instances("Meta format", attributes, 0);
        metaFormat.setClassIndex(metaFormat.numAttributes() - 1);
        return metaFormat;
      }
    

    生成元数据格式时,如果是分类问题,类标签的每个属性都被作为一个新的属性:
    在这里插入图片描述
    这里我个人这样理解:有的基分类器可以输出属于某个类的概率(如逻辑回归),将概率作为元属性而不是直接将基学习器的分类结果作为元属性,这样做能够减小基学习器的分类误差带给元学习器的影响,模型整体更加精确:
    在这里插入图片描述

    /**
       * Makes a level-1 instance from the given instance.
       * 
       * @param instance the instance to be transformed
       * @return the level-1 instance
       * @throws Exception if the instance generation fails
       */
      // 产生元数据
      protected Instance metaInstance(Instance instance) throws Exception {
    
        // values保存分类结果,连续属性直接保存,离散属性则先求得分布,将每种取值的分布加入values,设置为m_MetaFormat格式返回
        double[] values = new double[m_MetaFormat.numAttributes()];
        Instance metaInstance;
        int i = 0;
        for (int k = 0; k < m_Classifiers.length; k++) {
          Classifier classifier = getClassifier(k);
          if (m_BaseFormat.classAttribute().isNumeric()) {
    	    values[i++] = classifier.classifyInstance(instance);
          } else {
            // 基学习器对该实例的分类的概率分布, sum(dist)=1
    	    double[] dist = classifier.distributionForInstance(instance);
    	    // 将该基学习器对该实例的预测概率输出到对应的元属性
    	    for (int j = 0; j < dist.length; j++) {
    	      values[i++] = dist[j];
    	    }
          }
        }
        // 标签值对应最后一个元属性
        values[i] = instance.classValue();
        metaInstance = new DenseInstance(1, values);
        metaInstance.setDataset(m_MetaFormat);
        return metaInstance;
      }
    

    在实际数据集上的结果其实不一定比其他模型效果好,可能是我参数调的不好吧(雾)~

  • 相关阅读:
    Android学习---- 十月
    第17章 使用PHP和MySQL实现身份验证
    第13章 MySQL高级编程
    第12章 MySQL高级管理
    第11章 使用PHP从Web访问MySQL数据库
    第10章 使用MySQL数据库
    第9章 创建Web数据库
    第5章 代码重用与函数编写
    NOIP模拟赛-奶牛晒衣服(dry)
    BZOJ1008 /乘法原理+快速幂 解题报告
  • 原文地址:https://www.cnblogs.com/EIMadrigal/p/13950505.html
Copyright © 2011-2022 走看看