3
$\begingroup$

I wonder which ML algorithm is able to learn the appearently simple rule:

If [category] == 1 Then [A]

Below a demo script with an input variable [A] that contains noise, but if [category] == 1 then [A] matches exactly the target variable. Even after 5000 samples it doesn't get the rule right.

Which ML algorithm would you recommend that is able to learn that rule in a regression problem?

from math import floor, ceil
import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeRegressor

seq_length = 6
rows = 5000
max_value = 100
test_data_factor = 0.2

input = pd.DataFrame(np.random.uniform(0, max_value, size=(rows)), columns=["target"])
input["category"] = (list(range(1, seq_length + 1)) * ceil(rows / seq_length))[:rows]

def f_a(row):
    if row["category"] == 1:
        return row["target"]
    else:
        return row["target"] * np.random.uniform(0.8, 1.2)

input["A"] = input.apply(f_a, axis=1)

cnt_input_data = len(input.index)
rows_test_data = floor(cnt_input_data * test_data_factor)
rows_train_data = cnt_input_data - rows_test_data

train_data = input.head(rows_train_data)
test_data = input.tail(rows_test_data)

model = DecisionTreeRegressor()
model.fit(train_data.drop("target", axis=1), train_data["target"])
test_data["pred"] = model.predict(test_data.drop("target", axis=1))

print(test_data[test_data["category"] == 1])
$\endgroup$
4
  • 3
    $\begingroup$ It looks like you have a randomly-generated input category variable, and a randomly generated target variable. When the category is 1, the true output is just the target variable, but if it's not, the true output is some scaling between 0.8 and 1.2 times the target. But the target is still just a random number at heart - there is no link between category and target, since the target is always random no matter what - choosing whether to return the random number or some scaling of a random number doesn't change anything. You're asking an ML algorithm to predict a randomly generated number, no? $\endgroup$ Commented Oct 12, 2020 at 14:07
  • $\begingroup$ category is a sequence, target is a random number and column A matches exactly the target if category==1. So even when the target is a random number, there is a very simple rule that enables the prediction of that target exactly if category==1. Obviously the tree is not able to learn it, I wonder if there are other methods that can do this? $\endgroup$
    – HansHupe
    Commented Oct 12, 2020 at 14:12
  • $\begingroup$ OK, so the input consists of the category and column A? That makes more sense. $\endgroup$ Commented Oct 12, 2020 at 14:40
  • $\begingroup$ yes exactly category and A are both inputs $\endgroup$
    – HansHupe
    Commented Oct 12, 2020 at 14:41

2 Answers 2

6
$\begingroup$

The reason that the Decision Tree does poorly here is the algorithm isn't equipped to deal with the situation you're throwing at it. You need to understand how a CART model gives its predicted output value for a continuous response.

You fit a CART model to the response target, predicted by inputs category and A. You want the decision tree to learn the rule if category == 1, predict target = A. But all the classical CART algorithm can do is partition the space based on the input values, and then output a final predicted value based on the responses only (target) that fall into the given partition; it doesn't incorporate predictor information like you want it to in the final prediction. So it can only do things like if category == 1, predict target = (mean target of all observations with category == 1). Since the observations that fall into category 1 are just uniform random variates, you won't do very well predicting their value by grouping them up and just taking the mean, right?

Sounds like a "model-tree" based approach might be more appropriate (disclaimer: I'm not an expert in these). In the terminal node of the tree, instead of simply predicting the mean of all values falling into that node (like CART), model-trees fit a linear model to all observations in the terminal node, using all predictors that gave rise to the splits that define that terminal node (that's a mouthful, I know, not sure how else to say it).

I'll give an example in sloppy R code (sorry, too nooby in Python) wherein I:

  1. setup dummy data
  2. fit a CART model to show how bad it is
  3. fit a Cubist model to show that it fits well on the category == 1 data and poorly on the category != 1 data

Step 1: Setup data

set.seed(111)

library(rpart) # CART model
library(Cubist) # model-trees model

seq_length = 6
rows = 30000
max_value = 100
test_data_factor = 0.2

df <- data.frame(category = as.character(rep(1:seq_length, length.out = rows)),
                 target = runif(rows, 0, max_value))

df$A <- df$target
for(i in 1:rows) if(df$category[i] != 1) df$A[i] <- df$A[i] * runif(1, 0.8, 1.2)

test_ind <- 1:floor(test_data_factor * nrow(df))

training <- df[-test_ind, ]

test <- df[test_ind, ]
test_1 <- test[test$category == 1, ] # Test observations w/ cat 1
test_not1 <- test[test$category != 1, ] # Test observations w/ other categories

Step 2: Fit a CART model and show how crappy it is

treemod <- rpart(data = training, target ~ .)

treepred_1 <- predict(treemod, newdata = test_1) # CART predictions in category 1
treepred_not1 <- predict(treemod, newdata = test_not1) # CART predictions in other categories
print(paste0("Mean Absolute Error of CART Model in Category 1: ", round(mean(abs(treepred_1 - test_1$target)), 3)))
print(paste0("Mean Absolute Error of CART Model other Categories: ", round(mean(abs(treepred_not1 - test_not1$target)), 3)))

[1] "Mean Absolute Error of CART Model in Category 1: 4.061"
[1] "Mean Absolute Error of CART Model other Categories: 6.178

Step 3: Fit a Cubist model and show improvement in Category 1

cubistmod <- cubist(x = training[ , -2], y = training$target)

cubistpred_1 <- predict(cubistmod, newdata = test_1)
cubistpred_not1 <- predict(cubistmod, newdata = test_not1)

print(paste0("Mean Absolute Error of Cubist Model in Category 1: ", round(mean(abs(cubistpred_1 - test_1$target)), 3)))
print(paste0("Mean Absolute Error of Cubist Model other Categories: ", round(mean(abs(cubistpred_not1 - test_not1$target)), 3)))

[1] "Mean Absolute Error of Cubist Model in Category 1: 0.01"
[1] "Mean Absolute Error of Cubist Model other Categories: 4.434"

So the test error in category 1 has gone from about 4.1 to 0.01 by switching from CART to Cubist. The error is non-zero so it's not learning like a human might that if the category is 1, then just return A exactly. But perhaps the analyst might notice the minute error and consider that this is likely just numerical precision issues. Indeed, if you check summary(cubistmod), which lists the model splits and resulting models, you'll see among the rules:

if
    category = 1
    then
    outcome = 0 + 1 A

I'm not sure what other kinds of algorithms could help you out, but just some random thoughts: you could maybe check out association rule learning or literature in the data mining community ("data mining" being kind of a buzzword but this idea of finding hidden relationships among variables in the dataset seems to be a common motif in the lit of the self-professed miners)

$\endgroup$
5
  • $\begingroup$ Thx. for sharing this I got the idea. I think model trees (M5) are a nice approach, unfortunately poorly supported in Python (sklearn). But honestly I am surprised that such simple interaction rules are obviously not an easy thing for standard ML methods. $\endgroup$
    – HansHupe
    Commented Oct 12, 2020 at 15:41
  • $\begingroup$ @HansHupe This is kind of like complaining that a screwdriver doesn't do a very good job of driving nails. There are many types of machine learning models because there are many types of problems, and a model which does well against one problem may not do well against another type of problem. For more information, you may be interested in reading about the so-called "No Free Lunch Theorem." en.wikipedia.org/wiki/No_free_lunch_theorem $\endgroup$
    – Sycorax
    Commented Oct 12, 2020 at 15:54
  • $\begingroup$ Sorry for complaining, but it looks like that no algorithm of the widely used ML toolbox sklearn in Python is able to learn that rule in a satisfying way (maybe MLPRegressor could but it would be overshooting I think and needs a lot of tuning). For me this structure of problem is a very practical challenge. $\endgroup$
    – HansHupe
    Commented Oct 12, 2020 at 16:02
  • $\begingroup$ @HansHupe sklearn is convenient, but limiting yourself to the models that it implements will not give you very deep coverage of the kinds of problems you might need to solve. Indeed, you've discovered one such blindspot in the case of this problem. $\endgroup$
    – Sycorax
    Commented Oct 12, 2020 at 16:30
  • 2
    $\begingroup$ fwiw, There were some discussions for model-based recursive partitioning in sklearn: github.com/scikit-learn/scikit-learn/issues/11647 $\endgroup$ Commented Oct 12, 2020 at 18:22
3
$\begingroup$

This is not how decision trees work. Roughly speaking, decision tree splits data into bins (branches), conditionally on the features, and per each bin it predicts mean of the target variable. So for decision tree to predict something like identity function $y = f(y)$, you would need decision tree with the number of branches equal to the size of the data, i.e. one that literally memorized the data.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.