Search code examples
rmlr3

Tuning Over Multiple Learners using mutations and target transformations via pipelines


Next to a mutation, I want to transform the target variable through a pipeline.

My orginal graph, I've created through the following code:

pomut = po("mutate")
mutations = list(
  PM.Trafo = ~ (PM*T)  
)
pomut$param_set$values$mutation = mutations


preproc_1 = po("select", selector = selector_name(c("T", "H", "PM")), id = "slct_1") 
nop_1 = po("nop", id = "nop_1") 
nop_2 = po("nop", id = "nop_2") 
nop_3 = po("nop", id = "nop_3") 
# ---------------------------------------------------------------------------------------------------------

# Regression Graphlearner
# ----------------------------------------------------------------------------------------
graph = preproc_1 %>>%
  po("branch", c("lrn_xgboost", "lrn_ranger", "lrn_MLR", "lrn_ridge")) %>>%
  gunion(list(nop_1, nop_2, pomut, nop_3)) %>>%
  gunion(lapply(c(learners[[1]], learners[[2]], learners[[3]], learners[[4]]), po)) %>>%
  po("unbranch")
graph$plot()

If I plot the graph via graph$plot I get the following figure enter image description here

I consulted the following resources:

https://mlr-org.com/gallery/pipelines/2020-02-01-tuning-multiplexer/

https://mlr3book.mlr-org.com/chapters/chapter8/non-sequential_pipelines_and_tuning.html

https://mlr3book.mlr-org.com/chapters/chapter8/non-sequential_pipelines_and_tuning.html#sec-pipelines-ppl

and especially this source:

https://mlr-org.com/gallery/pipelines/2020-06-15-target-transformations-via-pipelines/index.html

Using this source and the others, I couldn't figure out how to implement a target transformation in the code structure of my existing code. There I've produced a graph with multiple learners branching out into different data channels.

It seems like I need to use edges, but I couldn't find any examples, where the edges weren't just added to an existing graph. Also in the same examples with edges I've only seen graphs including only one learner.

Could somebody point me towards the right direction?

Basically I want to adjust the section in my graph with the MLR learner (first figure) adding a target transformation along with a mutation of a feature creating the new feature called PM.Trafo (see my first code snippet).

I want to add the structure shown in the below figure into my original graph considering also my mutation pomut of course: enter image description here Thanks in advance!

Update to my question as asked for:

Example data I generated with dput():

structure(list(T = c(7.59788888888889, 8.23433333333333, 7.93344444444444, 
8.94322222222222, 8.55277777777778, 8.34922222222222, 7.89555555555556, 
7.26855555555556, 6.94344444444444, 6.72822222222222, 6.62888888888889, 
6.31922222222222, 6.04177777777778, 5.70044444444444, 5.02055555555556, 
4.51822222222222, 4.35844444444444, 4.12322222222222, 3.99844444444444, 
3.94066666666667, 3.76055555555556, 3.50411111111111, 3.09, 2.62688888888889, 
2.48433333333333, 2.12711111111111, 1.67322222222222, 1.32533333333333, 
1.22144444444444, 0.921333333333333), H = c(48.4822222222222, 
47.1135555555556, 46.5808888888889, 45.451, 47.0155555555556, 
47.928, 48.5743333333333, 49.6898888888889, 49.8497777777778, 
50.1487777777778, 50.8611111111111, 52.1987777777778, 52.8051111111111, 
54.4601111111111, 56.0526666666667, 57.3194444444444, 57.5327777777778, 
58.3103333333333, 58.7714444444444, 59.1435555555556, 59.6875555555556, 
60.5934444444444, 62.2244444444444, 64.2908888888889, 65.8232222222222, 
67.77, 69.5827777777778, 70.8958888888889, 71.353, 72.5042222222222
), PM = c(7.34311111111111, 7.28566666666667, 7.02722222222222, 
7.17544444444444, 7.37488888888889, 7.02455555555556, 7.13433333333333, 
6.93877777777778, 7.21222222222222, 6.874, 6.65711111111111, 
6.99066666666667, 6.84911111111111, 7.28111111111111, 7.12877777777778, 
7.86688888888889, 8.906, 9.51888888888889, 10.4127777777778, 
11.7454444444444, 12.4998888888889, 11.8266666666667, 13.0438888888889, 
14.4334444444444, 21.1485555555556, 22.3486666666667, 23.4202222222222, 
27.6978888888889, 29.2524444444444, 24.732), observation = c(8.0374, 
7.962, 7.8073, 8.1299, 8.382, 8.5875, 7.7543, 7.958, 8.1562, 
7.7912, 7.7931, 7.507, 7.8805, 7.7832, 8.0104, 8.1814, 8.9031, 
9.8926, 10.7987, 11.8408, 13.8759, 13.6611, 13.6381, 15.5016, 
17.9112, 22.1155, 21.1066, 25.1956, 29.8835, 25.2838)), row.names = c(NA, 
30L), class = "data.frame")

The Learner MLR (Multiple Linear Regression) with a transformed response or target called "observation" and a transformed feature called "PM" for training a model during a training period next to the features "H" and "T":

MultipleLineareRegression  <- lm(log(observation + 0.001) ~ log(PM + 0.001) + H + T, data = testdata)

Basically MultipleLineareRegression I want to translate into a single column in my graph (column 3, figure 1).

Question regarding this context:

When I later predict using this learner, do I still have to transform my prediction or is it automatically done (Inversion), because I defined the process in my graph column basically, where I invert the target at the end? I would say it should be the case, right?

In base R, I would do it like that:

# After training the model I want to predict from it
obs.predic <- predict(MultipleLineareRegression, testdata)

# but I have to consider the transformation of my prediction based on my model
obs.predic <- exp(obs.predic)  

Solution

  • Hopefully this is helpful.

    I've created one mutate function for the PM variable with po("mutate") to transform the independent var.

    Next I used the prebuilt pipeline (ppl) to create a learner, in my case cloning GLearner (glmnet) with the same tuning tokens, but for you, use whatever model you intend.

    Next I used as_learner() to convert the entire piece of the pipeline with mutations + trafo to a learner module then added it into the graph with the other learners (column 3)

    Of course you can replace any NOP with a module of your choice. Yes, the pipelines will perform mutation on new data and the trafo module will transform the target and call the inverter function exp(x) on the response variable so when it enters unbranch it will be backtransformed.

      ObsTask <- TaskRegr$new(
      id = "ObsTask",
      backend = Data,
      target = "observation"
    )
    
    GLearner = lrn("regr.glmnet",
                   id = "GlmModel",
                   s = to_tune(p_int(0, 15)),
                   alpha = to_tune(p_dbl(0, 1))
    )
    
    RFLearner = lrn("regr.randomForest", 
                    id = "RFModel",
                    ntree = 300,
                    mtry = to_tune(p_int(5,12)),
                    nodesize = to_tune(p_int(1,5)),
                    maxnodes = to_tune(p_int(5,20))
    )
    
    MLearner = lrn("regr.mars", 
                   id = "MarsModel",
                   degree = to_tune(p_int(1,3)),
                   nk = to_tune(p_int(1,200))
    )
    
    
    pomut <- po("mutate")
    mutations = list(
      PM = ~log(PM + 0.001)
    )
    pomut$param_set$values$mutation = mutations
    
    
    tt = ppl("targettrafo", graph = PipeOpLearner$new(GLearner$clone()))
    tt$param_set$values$targetmutate.trafo = function(x) log(x + 0.001)
    tt$param_set$values$targetmutate.inverter = function(x) exp(x) 
    
    MutationGraph = pomut %>>% tt
    plot(MutationGraph)
    
    TrafoLearn <- as_learner(MutationGraph, id = "TRAFO")
    
    nop_1 = po("nop", id = "nop_1") 
    nop_2 = po("nop", id = "nop_2") 
    nop_3 = po("nop", id = "nop_3") 
    nop_4 = po("nop", id = "nop_4") 
    nop_5 = po("nop", id = "nop_5")
    
    learners <- list(GLearner, RFLearner, TrafoLearn, MLearner)
    
    graph = nop_5 %>>%
      po("branch", c("regr.mars", "regr.randomForest", "TRAFO", "regr.glmnet")) %>>%
      gunion(list(nop_1, nop_2, nop_4, nop_3)) %>>%
      gunion(lapply(c(learners[[1]], learners[[2]], learners[[3]], learners[[4]]), po)) %>>%
      po("unbranch")
    graph$plot()
    
    
    FinalLearner <- as_learner(graph)
    
    resampling_inner = rsmp("holdout")
    resampling_outer = rsmp("cv", folds = 3)
    terminator = trm("run_time", secs = 120)
    tuner = tnr("mbo")
    measure = msr("regr.rmse")
    
    instance = tune(
      tuner = tuner,
      task = ObsTask,
      learner = FinalLearner,
      resampling = rsmp("cv", folds = 3),
      measures = measure,
      terminator = terminator
    )
    
    instance$result_learner_param_vals
    instance$result
    

    Trafo Module

    Full Model with Trafo