Search in sources :

Example 16 with ColumnMemberships

use of org.knime.base.node.mine.treeensemble2.data.memberships.ColumnMemberships in project knime-core by knime.

the class TreeNominalColumnData method calcBestSplitClassificationBinaryTwoClass.

private NominalBinarySplitCandidate calcBestSplitClassificationBinaryTwoClass(final ColumnMemberships columnMemberships, final ClassificationPriors targetPriors, final TreeTargetNominalColumnData targetColumn, final IImpurity impCriterion, final NominalValueRepresentation[] nomVals, final NominalValueRepresentation[] targetVals, final RandomData rd) {
    if (targetColumn.getMetaData().getValues().length != 2) {
        throw new IllegalArgumentException("This method can only be used for two class problems.");
    }
    final TreeEnsembleLearnerConfiguration config = getConfiguration();
    final int minChildSize = config.getMinChildSize();
    final boolean useXGBoostMissingValueHandling = config.getMissingValueHandling() == MissingValueHandling.XGBoost;
    int start = 0;
    final int firstClass = targetColumn.getMetaData().getValues()[0].getAssignedInteger();
    double totalWeight = 0.0;
    double totalFirstClassWeight = 0.0;
    final ArrayList<NomValProbabilityPair> nomValProbabilities = new ArrayList<NomValProbabilityPair>();
    if (!columnMemberships.next()) {
        throw new IllegalStateException("The columnMemberships has not been reset or is empty.");
    }
    final int lengthNonMissing = containsMissingValues() ? nomVals.length - 1 : nomVals.length;
    // final int attToConsider = useXGBoostMissingValueHandling ? nomVals.length : lengthNonMissing;
    boolean branchContainsMissingValues = containsMissingValues();
    // calculate probabilities for first class in each nominal value
    for (int att = 0; att < /*attToConsider*/
    lengthNonMissing; att++) {
        int end = start + m_nominalValueCounts[att];
        double attFirstClassWeight = 0;
        double attWeight = 0;
        boolean reachedEnd = false;
        for (int index = columnMemberships.getIndexInColumn(); index < end; index = columnMemberships.getIndexInColumn()) {
            double weight = columnMemberships.getRowWeight();
            assert weight > EPSILON : "Instances in columnMemberships must have weights larger than EPSILON.";
            final int instanceClass = targetColumn.getValueFor(columnMemberships.getOriginalIndex());
            if (instanceClass == firstClass) {
                attFirstClassWeight += weight;
                totalFirstClassWeight += weight;
            }
            attWeight += weight;
            totalWeight += weight;
            if (!columnMemberships.next()) {
                // reached end of columnMemberships
                reachedEnd = true;
                if (att == nomVals.length - 1) {
                    // if the column contains no missing values, the last possible nominal value is
                    // not the missing value and therefore branchContainsMissingValues needs to be false
                    branchContainsMissingValues = branchContainsMissingValues && true;
                }
                break;
            }
        }
        if (attWeight > 0) {
            final double firstClassProbability = attFirstClassWeight / attWeight;
            final NominalValueRepresentation nomVal = getMetaData().getValues()[att];
            nomValProbabilities.add(new NomValProbabilityPair(nomVal, firstClassProbability, attWeight, attFirstClassWeight));
        }
        start = end;
        if (reachedEnd) {
            break;
        }
    }
    // account for missing values and their weight
    double missingWeight = 0.0;
    double missingWeightFirstClass = 0.0;
    // otherwise the current indexInColumn won't be larger than start
    if (columnMemberships.getIndexInColumn() >= start) {
        do {
            final double recordWeight = columnMemberships.getRowWeight();
            missingWeight += recordWeight;
            final int recordClass = targetColumn.getValueFor(columnMemberships.getOriginalIndex());
            if (recordClass == firstClass) {
                missingWeightFirstClass += recordWeight;
            }
        } while (columnMemberships.next());
    }
    if (missingWeight > EPSILON) {
        branchContainsMissingValues = true;
    }
    nomValProbabilities.sort(null);
    int highestBitPosition = getMetaData().getValues().length - 1;
    if (containsMissingValues()) {
        highestBitPosition--;
    }
    final double[] targetCountsSplitPartition = new double[2];
    final double[] targetCountsSplitRemaining = new double[2];
    final double[] binaryImpurityValues = new double[2];
    final double[] binaryPartitionWeights = new double[2];
    BigInteger partitionMask = BigInteger.ZERO;
    double bestPartitionGain = Double.NEGATIVE_INFINITY;
    BigInteger bestPartitionMask = null;
    boolean isBestSplitValid = false;
    double sumWeightsPartitionTotal = 0.0;
    double sumWeightsPartitionFirstClass = 0.0;
    boolean missingsGoLeft = false;
    final double priorImpurity = useXGBoostMissingValueHandling ? targetPriors.getPriorImpurity() : impCriterion.getPartitionImpurity(subtractMissingClassCounts(targetPriors.getDistribution(), createMissingClassCountsTwoClass(missingWeight, missingWeightFirstClass)), totalWeight);
    // we don't need to iterate over the full list because we always need some value on the other side
    for (int i = 0; i < nomValProbabilities.size() - 1; i++) {
        NomValProbabilityPair nomVal = nomValProbabilities.get(i);
        sumWeightsPartitionTotal += nomVal.m_sumWeights;
        sumWeightsPartitionFirstClass += nomVal.m_firstClassSumWeights;
        partitionMask = partitionMask.or(nomVal.m_bitMask);
        // check if split represented by currentSplitList is in the right branch
        // by convention a split goes towards the right branch if the highest possible bit is set to 1
        final boolean isRightBranch = partitionMask.testBit(highestBitPosition);
        double gain;
        boolean isValidSplit;
        boolean tempMissingsGoLeft = true;
        if (branchContainsMissingValues && useXGBoostMissingValueHandling) {
            // send missing values both ways and take the better direction
            // send missings left
            targetCountsSplitPartition[0] = sumWeightsPartitionFirstClass + missingWeightFirstClass;
            targetCountsSplitPartition[1] = sumWeightsPartitionTotal + missingWeight - targetCountsSplitPartition[0];
            binaryPartitionWeights[1] = sumWeightsPartitionTotal + missingWeight;
            // totalFirstClassWeight and totalWeight only include non missing values
            targetCountsSplitRemaining[0] = totalFirstClassWeight - sumWeightsPartitionFirstClass;
            targetCountsSplitRemaining[1] = totalWeight - sumWeightsPartitionTotal - targetCountsSplitRemaining[0];
            binaryPartitionWeights[0] = totalWeight - sumWeightsPartitionTotal;
            boolean isValidSplitLeft = binaryPartitionWeights[0] >= minChildSize && binaryPartitionWeights[1] >= minChildSize;
            binaryImpurityValues[0] = impCriterion.getPartitionImpurity(targetCountsSplitRemaining, binaryPartitionWeights[0]);
            binaryImpurityValues[1] = impCriterion.getPartitionImpurity(targetCountsSplitPartition, binaryPartitionWeights[1]);
            double postSplitImpurity = impCriterion.getPostSplitImpurity(binaryImpurityValues, binaryPartitionWeights, totalWeight + missingWeight);
            double gainLeft = impCriterion.getGain(priorImpurity, postSplitImpurity, binaryPartitionWeights, totalWeight + missingWeight);
            // send missings right
            targetCountsSplitPartition[0] = sumWeightsPartitionFirstClass;
            targetCountsSplitPartition[1] = sumWeightsPartitionTotal - sumWeightsPartitionFirstClass;
            binaryPartitionWeights[1] = sumWeightsPartitionTotal;
            targetCountsSplitRemaining[0] = totalFirstClassWeight - sumWeightsPartitionFirstClass + missingWeightFirstClass;
            targetCountsSplitRemaining[1] = totalWeight - sumWeightsPartitionTotal + missingWeight - targetCountsSplitRemaining[0];
            binaryPartitionWeights[0] = totalWeight + missingWeight - sumWeightsPartitionTotal;
            boolean isValidSplitRight = binaryPartitionWeights[0] >= minChildSize && binaryPartitionWeights[1] >= minChildSize;
            binaryImpurityValues[0] = impCriterion.getPartitionImpurity(targetCountsSplitRemaining, binaryPartitionWeights[0]);
            binaryImpurityValues[1] = impCriterion.getPartitionImpurity(targetCountsSplitPartition, binaryPartitionWeights[1]);
            postSplitImpurity = impCriterion.getPostSplitImpurity(binaryImpurityValues, binaryPartitionWeights, totalWeight + missingWeight);
            double gainRight = impCriterion.getGain(priorImpurity, postSplitImpurity, binaryPartitionWeights, totalWeight + missingWeight);
            // decide which is better (better gain)
            if (gainLeft >= gainRight) {
                gain = gainLeft;
                isValidSplit = isValidSplitLeft;
                tempMissingsGoLeft = true;
            } else {
                gain = gainRight;
                isValidSplit = isValidSplitRight;
                tempMissingsGoLeft = false;
            }
        } else {
            // assign weights to branches
            targetCountsSplitPartition[0] = sumWeightsPartitionFirstClass;
            targetCountsSplitPartition[1] = sumWeightsPartitionTotal - sumWeightsPartitionFirstClass;
            binaryPartitionWeights[1] = sumWeightsPartitionTotal;
            targetCountsSplitRemaining[0] = totalFirstClassWeight - sumWeightsPartitionFirstClass;
            targetCountsSplitRemaining[1] = totalWeight - sumWeightsPartitionTotal - targetCountsSplitRemaining[0];
            binaryPartitionWeights[0] = totalWeight - sumWeightsPartitionTotal;
            isValidSplit = binaryPartitionWeights[0] >= minChildSize && binaryPartitionWeights[1] >= minChildSize;
            binaryImpurityValues[0] = impCriterion.getPartitionImpurity(targetCountsSplitRemaining, binaryPartitionWeights[0]);
            binaryImpurityValues[1] = impCriterion.getPartitionImpurity(targetCountsSplitPartition, binaryPartitionWeights[1]);
            double postSplitImpurity = impCriterion.getPostSplitImpurity(binaryImpurityValues, binaryPartitionWeights, totalWeight);
            gain = impCriterion.getGain(priorImpurity, postSplitImpurity, binaryPartitionWeights, totalWeight);
        }
        // use random tie breaker if gains are equal
        boolean randomTieBreaker = gain == bestPartitionGain ? rd.nextInt(0, 1) == 1 : false;
        // store if better than before or first valid split
        if (gain > bestPartitionGain || (!isBestSplitValid && isValidSplit) || randomTieBreaker) {
            if (isValidSplit || !isBestSplitValid) {
                bestPartitionGain = gain;
                bestPartitionMask = isRightBranch ? partitionMask : BigInteger.ZERO.setBit(highestBitPosition + 1).subtract(BigInteger.ONE).xor(partitionMask);
                isBestSplitValid = isValidSplit;
                // missingsGoLeft is only used later on if XGBoost Missing Value Handling is used
                if (branchContainsMissingValues) {
                    // missingsGoLeft = isRightBranch;
                    missingsGoLeft = tempMissingsGoLeft;
                } else {
                    // no missing values in this branch
                    // send missing values with the majority
                    missingsGoLeft = isRightBranch ? sumWeightsPartitionTotal < 0.5 * totalWeight : sumWeightsPartitionTotal >= 0.5 * totalWeight;
                }
            }
        }
    }
    if (isBestSplitValid && bestPartitionGain > 0.0) {
        if (useXGBoostMissingValueHandling) {
            return new NominalBinarySplitCandidate(this, bestPartitionGain, bestPartitionMask, NO_MISSED_ROWS, missingsGoLeft ? NominalBinarySplitCandidate.MISSINGS_GO_LEFT : NominalBinarySplitCandidate.MISSINGS_GO_RIGHT);
        }
        return new NominalBinarySplitCandidate(this, bestPartitionGain, bestPartitionMask, getMissedRows(columnMemberships), NominalBinarySplitCandidate.NO_MISSINGS);
    }
    return null;
}
Also used : TreeEnsembleLearnerConfiguration(org.knime.base.node.mine.treeensemble2.node.learner.TreeEnsembleLearnerConfiguration) ArrayList(java.util.ArrayList) BigInteger(java.math.BigInteger) NominalBinarySplitCandidate(org.knime.base.node.mine.treeensemble2.learner.NominalBinarySplitCandidate)

Aggregations

ColumnMemberships (org.knime.base.node.mine.treeensemble2.data.memberships.ColumnMemberships)10 BitSet (java.util.BitSet)7 BigInteger (java.math.BigInteger)5 NominalBinarySplitCandidate (org.knime.base.node.mine.treeensemble2.learner.NominalBinarySplitCandidate)5 TreeEnsembleLearnerConfiguration (org.knime.base.node.mine.treeensemble2.node.learner.TreeEnsembleLearnerConfiguration)5 IImpurity (org.knime.base.node.mine.treeensemble2.learner.IImpurity)3 BitSplitCandidate (org.knime.base.node.mine.treeensemble2.learner.BitSplitCandidate)2 NumericSplitCandidate (org.knime.base.node.mine.treeensemble2.learner.NumericSplitCandidate)2 ArrayList (java.util.ArrayList)1 LinkedHashMap (java.util.LinkedHashMap)1 RealVector (org.apache.commons.math3.linear.RealVector)1 Test (org.junit.Test)1 CombinedAttributeValues (org.knime.base.node.mine.treeensemble2.data.BinaryNominalSplitsPCA.CombinedAttributeValues)1 TestDataGenerator (org.knime.base.node.mine.treeensemble2.data.TestDataGenerator)1 TreeData (org.knime.base.node.mine.treeensemble2.data.TreeData)1 TreeNodeBitCondition (org.knime.base.node.mine.treeensemble2.model.TreeNodeBitCondition)1 TreeNodeNumericCondition (org.knime.base.node.mine.treeensemble2.model.TreeNodeNumericCondition)1 NumericOperator (org.knime.base.node.mine.treeensemble2.model.TreeNodeNumericCondition.NumericOperator)1 DefaultRowSample (org.knime.base.node.mine.treeensemble2.sample.row.DefaultRowSample)1 RowSample (org.knime.base.node.mine.treeensemble2.sample.row.RowSample)1