001/*
002 * Copyright (c) 2015-2020, Oracle and/or its affiliates. All rights reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.tribuo.common.tree;
018
019import com.oracle.labs.mlrg.olcut.config.PropertyException;
020import org.tribuo.Output;
021import org.tribuo.ensemble.BaggingTrainer;
022import org.tribuo.ensemble.EnsembleCombiner;
023
024import java.util.logging.Logger;
025
026/**
027 * A trainer which produces a random forest.
028 * <p>
029 * Random Forests are basically bagged trees, with feature subsampling at each of the nodes.
030 * An exception will be thrown if the user does not supply a decision tree trainer with feature subsampling turned on
031 * and random splitting turned off.
032 * <p>
033 * See:
034 * <pre>
035 * J. Friedman, T. Hastie, &amp; R. Tibshirani.
036 * "The Elements of Statistical Learning"
037 * Springer 2001. <a href="http://web.stanford.edu/~hastie/ElemStatLearn/">PDF</a>
038 * </pre>
039 */
040public class RandomForestTrainer<T extends Output<T>> extends BaggingTrainer<T> {
041
042    private static final Logger logger = Logger.getLogger(RandomForestTrainer.class.getName());
043
044    /**
045     * For the configuration system.
046     */
047    private RandomForestTrainer() { }
048
049    /**
050     * Constructs a RandomForestTrainer with the default seed {@link org.tribuo.Trainer#DEFAULT_SEED}.
051     * <p>
052     * Throws {@link PropertyException} if the trainer is not set to subsample the features.
053     * @param trainer The tree trainer.
054     * @param combiner The combining function for the ensemble.
055     * @param numMembers The number of ensemble members to train.
056     */
057    public RandomForestTrainer(DecisionTreeTrainer<T> trainer, EnsembleCombiner<T> combiner, int numMembers) {
058        super(trainer,combiner,numMembers);
059        postConfig();
060    }
061
062    /**
063     * Constructs a RandomForestTrainer with the supplied seed, trainer, combining function and number of members.
064     * <p>
065     * Throws {@link PropertyException} if the trainer is not set to subsample the features.
066     * @param trainer The tree trainer.
067     * @param combiner The combining function for the ensemble.
068     * @param numMembers The number of ensemble members to train.
069     * @param seed The RNG seed.
070     */
071    public RandomForestTrainer(DecisionTreeTrainer<T> trainer, EnsembleCombiner<T> combiner, int numMembers, long seed) {
072        super(trainer,combiner,numMembers,seed);
073        postConfig();
074    }
075
076    /**
077     * Used by the OLCUT configuration system, and should not be called by external code.
078     */
079    @Override
080    public void postConfig() {
081        super.postConfig();
082        if (!(innerTrainer instanceof DecisionTreeTrainer)) {
083            throw new PropertyException("","innerTrainer","RandomForestTrainer requires a decision tree innerTrainer");
084        }
085
086        DecisionTreeTrainer<T> t = (DecisionTreeTrainer<T>) innerTrainer;
087        if (t.getFractionFeaturesInSplit() == 1f) {
088            throw new PropertyException("","innerTrainer","RandomForestTrainer requires that the decision tree " +
089                    "innerTrainer have fractional features in split.");
090        }
091
092        if (t.getUseRandomSplitPoints()) {
093            throw new PropertyException("","innerTrainer","RandomForestTrainer requires that the decision tree " +
094                    "use non-random splitting, but useRandomSplits was true. If you want random splits, use " +
095                    "ExtraTreesTrainer instead.");
096        }
097    }
098
099    @Override
100    protected String ensembleName() {
101        return "random-forest-ensemble";
102    }
103
104    @Override
105    public String toString() {
106        StringBuilder buffer = new StringBuilder();
107
108        buffer.append("RandomForestTrainer(");
109        buffer.append("innerTrainer=");
110        buffer.append(innerTrainer.toString());
111        buffer.append(",combiner=");
112        buffer.append(combiner.toString());
113        buffer.append(",numMembers=");
114        buffer.append(numMembers);
115        buffer.append(",seed=");
116        buffer.append(seed);
117        buffer.append(")");
118
119        return buffer.toString();
120    }
121    
122}