rmlr

How to use mlr to customize classification model


I am studying mlr and try to customize my own classification model. I am using the example from https://mlr-org.github.io/mlr-tutorial/release/html/create_learner/index.html#classification. Here is my code:

library(mlr)
library(MASS)
makeRLearner.classif.lda = function() {
  makeRLearnerClassif(
    cl = "classif.lda",
    package = "MASS",
    par.set = makeParamSet(
      makeDiscreteLearnerParam(id = "method", default = "moment", values = c("moment", "mle", "mve", "t")),
      makeNumericLearnerParam(id = "nu", lower = 2, requires = quote(method == "t")),
      makeNumericLearnerParam(id = "tol", default = 1e-4, lower = 0),
      makeDiscreteLearnerParam(id = "predict.method", values = c("plug-in", "predictive", "debiased"),
                               default = "plug-in", when = "predict"),
      makeLogicalLearnerParam(id = "CV", default = FALSE, tunable = FALSE)
),
    properties = c("twoclass", "multiclass", "numerics", "factors", "prob"),
    name = "Linear Discriminant Analysis",
    short.name = "lda",
    note = "Learner param 'predict.method' maps to 'method' in predict.lda."
  )
}
trainLearner.classif.lda = function(.learner, .task, .subset, .weights = NULL, ...) {
  f = getTaskFormula(.task)
  MASS::lda(f, data = getTaskData(.task, .subset), ...)
}
predictLearner.classif.lda = function(.learner, .model, .newdata,   predict.method = "plug-in", ...) {
  p = predict(.model$learner.model, newdata = .newdata, method = predict.method, ...)
  if (.learner$predict.type == "response") 
    return(p$class) else return(p$posterior)
}
data(iris)
train = sample(1:nrow(iris), nrow(iris) / 1.5)
test = sample(1:nrow(iris), nrow(iris) / 6)
task <- makeClassifTask(data=iris,target='Species')
lrn <- makeRLearner.classif.lda()
tr <- trainLearner.classif.lda(.learner=lrn,.task=task,.subset=train)
pred <- predictLearner.classif.lda(.learner=lrn,.model=tr,.newdata=test)

I just copied and pasted the three functions from the website. But I have got the following error:

Error in UseMethod("predict") : 
  no applicable method for 'predict' applied to an object of class "NULL"

I found that there is no $learner.model in my tr, which is supposed to be there and be transfered into prediction function. my tr has:

> str(tr)
List of 10
 $ prior  : Named num [1:3] 0.38 0.3 0.32
  ..- attr(*, "names")= chr [1:3] "setosa" "versicolor" "virginica"
 $ counts : Named int [1:3] 38 30 32
  ..- attr(*, "names")= chr [1:3] "setosa" "versicolor" "virginica"
 $ means  : num [1:3, 1:4] 5.02 5.94 6.65 3.47 2.83 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:3] "setosa" "versicolor" "virginica"
  .. ..$ : chr   [1:4] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width"
 $ scaling: num [1:4, 1:2] 0.869 1.384 -2.214 -2.954 0.157 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:4] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width"
  .. ..$ : chr [1:2] "LD1" "LD2"
 $ lev    : chr [1:3] "setosa" "versicolor" "virginica"
 $ svd    : num [1:2] 41.78 2.91
 $ N      : int 100
 $ call   : language lda(formula = f, data = getTaskData(.task, .subset))
 $ terms  :Classes 'terms', 'formula'  language Species ~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width
  .. ..- attr(*, "variables")= language list(Species, Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)
  .. ..- attr(*, "factors")= int [1:5, 1:4] 0 1 0 0 0 0 0 1 0 0 ...
  .. .. ..- attr(*, "dimnames")=List of 2
  .. .. .. ..$ : chr [1:5] "Species" "Sepal.Length" "Sepal.Width" "Petal.Length" ...
  .. .. .. ..$ : chr [1:4] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width"
  .. ..- attr(*, "term.labels")= chr [1:4] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width"
  .. ..- attr(*, "order")= int [1:4] 1 1 1 1
  .. ..- attr(*, "intercept")= int 1
  .. ..- attr(*, "response")= int 1
  .. ..- attr(*, ".Environment")=<environment: 0x00000000213a8150> 
  .. ..- attr(*, "predvars")= language list(Species, Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)
  .. ..- attr(*, "dataClasses")= Named chr [1:5] "factor" "numeric" "numeric" "numeric" ...
  .. .. ..- attr(*, "names")= chr [1:5] "Species" "Sepal.Length" "Sepal.Width" "Petal.Length" ...
 $ xlevels: Named list()
 - attr(*, "class")= chr "lda"

I tried register learners, but I guess my way is not right. Here is my code:

registerS3method("makeRLearner.classif.lda", "<awesome_new_learner_class>", makeRLearner.classif.lda.<awesome_new_learner_class>)
registerS3method("trainLearner.classif.lda", "<awesome_new_learner_class>", trainLearner.classif.lda.<awesome_new_learner_class>)
registerS3method("predictLearner.classif.lda", "<awesome_new_learner_class>", predictLearner.classif.lda.<awesome_new_learner_class>)

Probably I should not just copy the code from the website. But I do not know how to do it. I am really new to mlr package.


Solution

  • Here's a complete example, using the iris task that comes with mlr. In addition to a closer look at the mlr documentation, you might find a general introduction to programming in R useful, in particular with respect to parameter names -- the cause of the error you're seeing is that you didn't pass a model to predict() (.model is not the name of that parameter, it's what you called the variable in your definition).

    library(mlr)
    library(MASS)
    makeRLearner.classif.lda1 = function() {
      makeRLearnerClassif(
        cl = "classif.lda1",
        package = "MASS",
        par.set = makeParamSet(
          makeDiscreteLearnerParam(id = "method", default = "moment", values = c("moment", "mle", "mve", "t")),
          makeNumericLearnerParam(id = "nu", lower = 2, requires = quote(method == "t")),
          makeNumericLearnerParam(id = "tol", default = 1e-4, lower = 0),
          makeDiscreteLearnerParam(id = "predict.method", values = c("plug-in", "predictive", "debiased"),
                                   default = "plug-in", when = "predict"),
          makeLogicalLearnerParam(id = "CV", default = FALSE, tunable = FALSE)
    ),
        properties = c("twoclass", "multiclass", "numerics", "factors", "prob"),
        name = "Linear Discriminant Analysis",
        short.name = "lda",
        note = "Learner param 'predict.method' maps to 'method' in predict.lda."
      )
    }
    trainLearner.classif.lda1 = function(.learner, .task, .subset, .weights = NULL, ...) {
      f = getTaskFormula(.task)
      MASS::lda(f, data = getTaskData(.task, .subset), ...)
    }
    predictLearner.classif.lda1 = function(.learner, .model, .newdata,   predict.method = "plug-in", ...) {
      p = predict(.model$learner.model, newdata = .newdata, method = predict.method, ...)
      if (.learner$predict.type == "response") 
        return(p$class) else return(p$posterior)
    }
    
    
    registerS3method("makeRLearner", "classif.lda1", makeRLearner.classif.lda1)
    registerS3method("trainLearner", "classif.lda1", trainLearner.classif.lda1)
    registerS3method("predictLearner", "classif.lda1", predictLearner.classif.lda1)
    
    lrn = makeLearner("classif.lda1")
    mod = train(lrn, iris.task)
    pred = predict(mod, iris.task)