【问题标题】:tidymodels does not respect fixed set_engine parameterstidymodels 不尊重固定的 set_engine 参数
【发布时间】:2020-10-28 17:54:47
【问题描述】:

(已根据 Julia 的回复在最后更新TL;DR:这似乎是底层 kknn 包的问题,而不是 tidymodels)

我正在使用 tidymodels 做一些 k-最近邻回归模型。这是通过nearest_neighbor() 函数实现的。我想看看有没有对特征进行归一化的结果有什么区别。

现在set_engine("kknn") 在底层使用kknn::train.kknn() 函数,它有一个标准化参数scale = TRUE。我想将模型与scale = FALSEscale = TRUE 进行比较(实际上,我想在配方中这样做,但这是不可能的,我将在下面解释)。

但我似乎无法通过 tidymodels 可靠地设置 scale = FALSE。下面是一个代表我看到的内容。

问题这么久:我做错了什么还是这是一个错误?如果它是一个错误,它是否已知并且我可以在某处阅读它吗?如果有人能阐明这一点,我将不胜感激。

设置代表

这里我将使用mtcars:

library(tidymodels)
data("mtcars")

训练测试拆分是:

set.seed(1)
mtcars_split <- initial_split(mtcars, prop = 0.7)

这是我将使用的常见食谱:

mtcars_recipe <- recipe(mpg ~ disp + wt, data = mtcars)

这是模型 1(称为knn_FALSE),其中scale = FALSE

knn_FALSE <- nearest_neighbor(neighbors = 5) %>% 
  set_mode("regression") %>% 
  set_engine("kknn", scale = FALSE)

这是模型 2(称为knn_TRUE),其中scale = TRUE

knn_TRUE <- nearest_neighbor(neighbors = 5) %>% 
  set_mode("regression") %>% 
  set_engine("kknn", scale = TRUE)

我将这两个模型捆绑到两个工作流程中:

## Workflow with scale = FALSE
wf_FALSE <- workflow() %>% 
  add_model(knn_FALSE) %>% 
  add_recipe(mtcars_recipe)

## Worflow with scale = TRUE
wf_TRUE <- workflow() %>% 
  add_model(knn_TRUE) %>% 
  add_recipe(mtcars_recipe)

使用fit(),可以有scale = FALSE

在工作流中使用fit() 时,似乎可以有一个带有scale = TRUE 的版本和一个带有scale = FALSE 的版本。

例如,对于scale = TRUE,我得到:

wf_TRUE %>% fit(mtcars)
== Workflow [trained] ===============================================================================================
Preprocessor: Recipe
Model: nearest_neighbor()

-- Preprocessor -----------------------------------------------------------------------------------------------------
0 Recipe Steps

-- Model ------------------------------------------------------------------------------------------------------------

Call:
kknn::train.kknn(formula = ..y ~ ., data = data, ks = ~5, scale = ~TRUE)

Type of response variable: continuous
minimal mean absolute error: 2.09425
Minimal mean squared error: 7.219114
Best kernel: optimal
Best k: 5

而对于scale = FALSE 我有:

wf_FALSE %>% fit(mtcars)
== Workflow [trained] ===============================================================================================
Preprocessor: Recipe
Model: nearest_neighbor()

-- Preprocessor -----------------------------------------------------------------------------------------------------
0 Recipe Steps

-- Model ------------------------------------------------------------------------------------------------------------

Call:
kknn::train.kknn(formula = ..y ~ ., data = data, ks = ~5, scale = ~FALSE)

Type of response variable: continuous
minimal mean absolute error: 2.1665
Minimal mean squared error: 6.538769
Best kernel: optimal
Best k: 5

结果明显不同,这来自于scale参数的不同。

但情节变厚了。

last_fit()没有区别

但是,当使用 last_fit() 时,scale = TRUEscale = FALSE 的结果是相同的。

对于scale = TRUE

wf_TRUE %>% last_fit(mtcars_split) %>% collect_metrics()
# A tibble: 2 x 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       3.16 
2 rsq     standard       0.663

而对于scale = FALSE

wf_FALSE %>% last_fit(mtcars_split) %>% collect_metrics()
# A tibble: 2 x 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       3.16 
2 rsq     standard       0.663

这些显然 --- 出乎意料地 --- 相同。

使用tune_grid()调优也没有区别

如果我使用tune_grid()validation_split() 进行调优,scale = TRUEscale = FALSE 的结果也没有区别。

下面是代码:

## Tune grid
knn_grid <- tibble(neighbors = c(5, 15))

## Tune Model 1: kNN regresson with no scaling in train.kknn
knn_FALSE_tune <- nearest_neighbor(neighbors = tune()) %>% 
  set_mode("regression") %>% 
  set_engine("kknn", scale = FALSE)

## Model 2: kNN regresson with  scaling in train.kknn
knn_TRUE_tune <- nearest_neighbor(neighbors = tune()) %>% 
  set_mode("regression") %>% 
  set_engine("kknn", scale = TRUE)

## Workflow with scale = FALSE
wf_FALSE_tune <- workflow() %>% 
  add_model(knn_FALSE_tune) %>% 
  add_recipe(mtcars_recipe)

## Worflow with scale = TRUE
wf_TRUE_tune <- workflow() %>% 
  add_model(knn_TRUE_tune) %>% 
  add_recipe(mtcars_recipe)

## Validation split
mtcars_val <- validation_split(mtcars)

## Tune results: Without scaling
wf_FALSE_tune %>% 
  tune_grid(resamples = mtcars_val, 
            grid = knn_grid) %>% 
  collect_metrics()

## Tune results: With scaling
wf_TRUE_tune %>% 
  tune_grid(resamples = mtcars_val, 
            grid = knn_grid) %>% 
  collect_metrics()

scale = FALSE时的结果:

> wf_FALSE_tune %>% 
+   tune_grid(resamples = mtcars_val, 
+             grid = knn_grid) %>% 
+   collect_metrics()
# A tibble: 4 x 7
  neighbors .metric .estimator  mean     n std_err .config
      <dbl> <chr>   <chr>      <dbl> <int>   <dbl> <chr>  
1         5 rmse    standard   1.64      1      NA Model1 
2         5 rsq     standard   0.920     1      NA Model1 
3        15 rmse    standard   2.55      1      NA Model2 
4        15 rsq     standard   0.956     1      NA Model2 

scale = TRUE时的结果:

> wf_TRUE_tune %>% 
+   tune_grid(resamples = mtcars_val, 
+             grid = knn_grid) %>% 
+   collect_metrics()
# A tibble: 4 x 7
  neighbors .metric .estimator  mean     n std_err .config
      <dbl> <chr>   <chr>      <dbl> <int>   <dbl> <chr>  
1         5 rmse    standard   1.64      1      NA Model1 
2         5 rsq     standard   0.920     1      NA Model1 
3        15 rmse    standard   2.55      1      NA Model2 
4        15 rsq     standard   0.956     1      NA Model2 

问题

是我误解(或遗漏了我自己的错误),还是 last_fit()tune_grid() 函数不尊重我对 scale 的选择?

我是 tidymodels 的新手,所以我可能错过了一些东西。非常感谢您的回答。

我希望在配方中使用step_normalize() 自己进行规范化,但由于我无法在底层引擎中可靠地设置scale = FALSE,因此我无法对此进行试验。

朱莉娅回复后更新

正如 Julia 所示,来自train.kknn() 的预测为scale = FALSEscale = TRUE 提供相同的预测。所以这不是 tidymodels 问题。而是 kknn:::predict.train.kknn() 函数在预测时不尊重传递给 train.kknn() 的所有参数。

考虑以下使用kknn() 而不是train.kknn() 的输出:

kknn::kknn(formula = mpg ~ disp + wt, train = training(mtcars_split), 
           test = testing(mtcars_split), k = 5, scale = FALSE) %>% 
  predict(newdata = testing(mtcars_split))
## [1] 21.276 21.276 16.860 16.276 21.276 16.404 29.680 15.700 16.020
kknn::kknn(formula = mpg ~ disp + wt, train = training(mtcars_split), 
           test = testing(mtcars_split), k = 5, scale = TRUE) %>% 
  predict(newdata = testing(mtcars_split))
## [1] 21.032 21.784 16.668 16.052 21.264 16.404 26.340 16.076 15.620

它们应该是不同的。问题是kknn:::predict.train.kknn() 调用kknn(),但没有传递scale(和其他一些可选参数):

function (object, newdata, ...) 
{
    if (missing(newdata)) 
        return(predict(object, ...))
    res <- kknn(formula(terms(object)), object$data, newdata, 
        k = object$best.parameters$k, kernel = object$best.parameters$kernel, 
        distance = object$distance)
    return(predict(res, ...))
}
<bytecode: 0x55e2304fba10>
<environment: namespace:kknn>

【问题讨论】:

标签: r tidymodels


【解决方案1】:

我认为您没有错误或问题,只是误解了last_fit() 和朋友们预测的性能。

library(tidymodels)
set.seed(1)
mtcars_split <- initial_split(mtcars, prop = 0.7)

knn_FALSE <- nearest_neighbor(neighbors = 5) %>% 
  set_mode("regression") %>% 
  set_engine("kknn", scale = FALSE)

knn_FALSE %>% translate()
#> K-Nearest Neighbor Model Specification (regression)
#> 
#> Main Arguments:
#>   neighbors = 5
#> 
#> Engine-Specific Arguments:
#>   scale = FALSE
#> 
#> Computational engine: kknn 
#> 
#> Model fit template:
#> kknn::train.kknn(formula = missing_arg(), data = missing_arg(), 
#>     ks = min_rows(5, data, 5), scale = FALSE)

knn_TRUE <- nearest_neighbor(neighbors = 5) %>% 
  set_mode("regression") %>% 
  set_engine("kknn", scale = TRUE)

knn_TRUE %>% translate()
#> K-Nearest Neighbor Model Specification (regression)
#> 
#> Main Arguments:
#>   neighbors = 5
#> 
#> Engine-Specific Arguments:
#>   scale = TRUE
#> 
#> Computational engine: kknn 
#> 
#> Model fit template:
#> kknn::train.kknn(formula = missing_arg(), data = missing_arg(), 
#>     ks = min_rows(5, data, 5), scale = TRUE)

请注意,两个欧洲防风草模型都正确地将 scale 参数传递给底层引擎。

我们现在可以使用公式预处理器将这两个欧洲防风草模型添加到 workflow()(配方也可以)。

wf_FALSE <- workflow() %>% 
  add_model(knn_FALSE) %>% 
  add_formula(mpg ~ disp + wt)

## Worflow with scale = TRUE
wf_TRUE <- workflow() %>% 
  add_model(knn_TRUE) %>% 
  add_formula(mpg ~ disp + wt)

函数last_fit()适合训练数据,预测测试数据。我们可以通过我们的工作流程手动完成。重要的是,请注意,对于测试集中的这些示例,预测是相同的,因此您将获得的指标是相同的。

wf_TRUE %>% fit(training(mtcars_split)) %>% predict(testing(mtcars_split))
#> # A tibble: 9 x 1
#>   .pred
#>   <dbl>
#> 1  21.0
#> 2  21.8
#> 3  16.7
#> 4  16.1
#> 5  21.3
#> 6  16.4
#> 7  26.3
#> 8  16.1
#> 9  15.6
wf_FALSE %>% fit(training(mtcars_split)) %>% predict(testing(mtcars_split))
#> # A tibble: 9 x 1
#>   .pred
#>   <dbl>
#> 1  21.0
#> 2  21.8
#> 3  16.7
#> 4  16.1
#> 5  21.3
#> 6  16.4
#> 7  26.3
#> 8  16.1
#> 9  15.6

直接拟合模型也是如此:

knn_TRUE %>% 
  fit(mpg ~ disp + wt, data = training(mtcars_split)) %>% 
  predict(testing(mtcars_split))
#> # A tibble: 9 x 1
#>   .pred
#>   <dbl>
#> 1  21.0
#> 2  21.8
#> 3  16.7
#> 4  16.1
#> 5  21.3
#> 6  16.4
#> 7  26.3
#> 8  16.1
#> 9  15.6
knn_FALSE %>% 
  fit(mpg ~ disp + wt, data = training(mtcars_split)) %>% 
  predict(testing(mtcars_split))
#> # A tibble: 9 x 1
#>   .pred
#>   <dbl>
#> 1  21.0
#> 2  21.8
#> 3  16.7
#> 4  16.1
#> 5  21.3
#> 6  16.4
#> 7  26.3
#> 8  16.1
#> 9  15.6

如果我们直接拟合底层的 kknn 模型,实际上也是如此:

kknn::train.kknn(formula = mpg ~ disp + wt, data = training(mtcars_split), 
                 ks = 5, scale = FALSE) %>% 
  predict(testing(mtcars_split))
#> [1] 21.032 21.784 16.668 16.052 21.264 16.404 26.340 16.076 15.620
kknn::train.kknn(formula = mpg ~ disp + wt, data = training(mtcars_split), 
                 ks = 5, scale = TRUE) %>% 
  predict(testing(mtcars_split))
#> [1] 21.032 21.784 16.668 16.052 21.264 16.404 26.340 16.076 15.620

reprex package (v0.3.0.9001) 于 2020 年 11 月 12 日创建

scale 参数已正确传递给底层引擎;它只是不会改变这些测试用例的预测。

【讨论】:

  • 非常感谢 Julia 调查此事,感谢您在这些软件包上所做的出色工作。我应该先检查 kknn 包。看起来 predict.train.kknn() 存在问题,其中不尊重像比例这样的非常规参数。所以预测是一样的,但它不应该是一样的。不过,使用 kknn() 而不是 train.kknn() 会很好。我将更新上面的问题以报告此问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-28
  • 2015-10-07
相关资源
最近更新 更多