为什么 `class::knn()` 函数给出的结果与具有固定 k 的 `kknn::kknn()` 不同?
Why does the `class::knn()` function give different results from `kknn::kknn()` with a fixed k?
我正在尝试将统计学习简介中的基础 R 代码转换为 R tidymodels
生态系统。本书使用class::knn()
,tidymodels
使用kknn::kknn()
。在使用固定的 k 进行 knn 时,我得到了不同的结果。所以我去掉了 tidymodels 并尝试使用 class::knn()
和 kknn::kknn()
进行比较,但我仍然得到了不同的结果。 class::knn
使用欧几里德距离,kknn::kknn
使用距离参数为2的闵可夫斯基距离,根据维基百科,这是欧几里德距离。我将 kknn 中的内核设置为“矩形”,根据文档,它是未加权的。固定k的knn建模结果不应该一样吗?
这里是(基本上)带有class::knn书中代码的基础 R:
library(ISLR2)
# base R class
train <- (Smarket$Year < 2005)
Smarket.2005 <- Smarket[!train, ]
dim(Smarket.2005)
Direction.2005 <- Smarket$Direction[!train]
train.X <- cbind(Smarket$Lag1, Smarket$Lag2)[train, ]
test.X <- cbind(Smarket$Lag1, Smarket$Lag2)[!train, ]
train.Direction <- Smarket$Direction[train]
the_k <- 3 # 30 shows larger discrepancies
library(class)
knn.pred <- knn(train.X, test.X, train.Direction, k = the_k)
这是我的 tidyverse 和 kknn::kknn 代码
# tidyverse kknn
library(tidyverse)
Smarket_train <- Smarket %>%
filter(Year != 2005)
Smarket_test <- Smarket %>% # Smarket.2005
filter(Year == 2005)
library(kknn)
the_knn <-
kknn(
Direction ~ Lag1 + Lag2, Smarket_train, Smarket_test, k = the_k,
distance = 2, kernel = "rectangular"
)
fit <- fitted(the_knn)
这显示了差异:
the_k
# class
table(Direction.2005, knn.pred)
# kknn
table(Smarket_test$Direction, fit)
我是不是在编码中犯了一个愚蠢的错误?如果不是,谁能解释一下 class::knn()
和 kknn::kknn()
之间的区别?
好吧,这里面发生了很多事情。首先,我们从 class::knn()
的文档中看到 the classification is decided by majority vote, with ties broken at random.
所以看来我们应该从查看 class::knn()
的输出开始,看看会发生什么。
我反复打电话
which(fitted(knn.pred) != fitted(knn.pred))
过了一会儿,我得到了 28 和 66。所以这些是测试数据集中的观察结果,其中有一些随机性。要查看为什么这两个观察很麻烦,我们可以在 class::knn()
中设置 prob = TRUE
来获得预测概率。
knn.pred <- knn(train.X, test.X, train.Direction, k = the_k, prob = TRUE)
attr(knn.pred, "prob")
#> [1] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 1.0000000 0.6666667
#> [8] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 1.0000000
#> [15] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667
#> [22] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.5000000
#> [29] 0.6666667 0.6666667 1.0000000 0.6666667 0.6666667 0.6666667 0.6666667
#> [36] 1.0000000 0.6666667 0.6666667 0.6666667 1.0000000 1.0000000 1.0000000
#> [43] 0.6666667 0.6666667 0.6666667 0.6666667 1.0000000 0.6666667 1.0000000
#> [50] 1.0000000 0.6666667 1.0000000 0.6666667 0.6666667 1.0000000 1.0000000
#> [57] 0.6666667 0.6666667 0.6666667 1.0000000 0.6666667 0.6666667 0.6666667
#> [64] 0.6666667 1.0000000 0.5000000 0.6666667 1.0000000 0.6666667 1.0000000
...
在这里我们看到观测值 28 和 66 的预测概率都是 0.5
。但是那怎么可能因为我们有 k=3
?
为了回答这个问题,我们将查看这些点的最近邻居。我将使用 RANN::nn2()
函数来计算训练集和测试集之间的距离。让我们以第一个观察为例,我们计算距离并将它们拉出来
dists <- RANN::nn2(train.X, test.X)
dists$nn.dists[1, ]
#> [1] 0.01063015 0.05632051 0.06985700 0.08469357 0.08495881 0.08561542
#> [7] 0.10823123 0.12003333 0.12621014 0.12657014
距离本身并没有多大作用,我们想知道的是什么
他们在训练集中的观察结果和他们的 类.
我们可以用 $nn.idx
来解决这个问题
dists$nn.idx[1, ]
#> [1] 503 411 166 964 981 611 840 705 562 578
train.Direction[dists$nn.idx[1, 1:3]]
#> [1] Up Down Down
#> Levels: Down Up
我们在这里看到第一个观测值的最近邻居是 Up
、Down
和 Down
。从而给出Down
.
的分类
如果我们查看第 66 个观察结果,我们会发现一些不同的东西。请注意第三和第四最近的邻居如何具有完全相同的距离?
dists$nn.dists[66, ]
#> [1] 0.06500000 0.06754258 0.07465253 0.07465253 0.07746612 0.07778175
#> [7] 0.08905055 0.09651943 0.11036757 0.11928118
train.Direction[dists$nn.idx[66, 1:4]]
#> [1] Down Down Up Up
#> Levels: Down Up
而当我们查看他们的 类 时,有 2 个 Up
和 2 个 Down
。这就是差异出现的地方。class::knn()
将所有这 4 个观察值都算作“3 个最近的邻居”,这给出了一个平局,随机打破。 kknn::kknn()
取前 3 个邻居,忽略距离上的这种关系,并预测 Down
,因为前 3 个邻居有 2 个 Down
和 1 个 Up
。
predict(the_knn, type = "prob")[66, ]
#> Down Up
#> [1,] 0.6666667 0.3333333
我正在尝试将统计学习简介中的基础 R 代码转换为 R tidymodels
生态系统。本书使用class::knn()
,tidymodels
使用kknn::kknn()
。在使用固定的 k 进行 knn 时,我得到了不同的结果。所以我去掉了 tidymodels 并尝试使用 class::knn()
和 kknn::kknn()
进行比较,但我仍然得到了不同的结果。 class::knn
使用欧几里德距离,kknn::kknn
使用距离参数为2的闵可夫斯基距离,根据维基百科,这是欧几里德距离。我将 kknn 中的内核设置为“矩形”,根据文档,它是未加权的。固定k的knn建模结果不应该一样吗?
这里是(基本上)带有class::knn书中代码的基础 R:
library(ISLR2)
# base R class
train <- (Smarket$Year < 2005)
Smarket.2005 <- Smarket[!train, ]
dim(Smarket.2005)
Direction.2005 <- Smarket$Direction[!train]
train.X <- cbind(Smarket$Lag1, Smarket$Lag2)[train, ]
test.X <- cbind(Smarket$Lag1, Smarket$Lag2)[!train, ]
train.Direction <- Smarket$Direction[train]
the_k <- 3 # 30 shows larger discrepancies
library(class)
knn.pred <- knn(train.X, test.X, train.Direction, k = the_k)
这是我的 tidyverse 和 kknn::kknn 代码
# tidyverse kknn
library(tidyverse)
Smarket_train <- Smarket %>%
filter(Year != 2005)
Smarket_test <- Smarket %>% # Smarket.2005
filter(Year == 2005)
library(kknn)
the_knn <-
kknn(
Direction ~ Lag1 + Lag2, Smarket_train, Smarket_test, k = the_k,
distance = 2, kernel = "rectangular"
)
fit <- fitted(the_knn)
这显示了差异:
the_k
# class
table(Direction.2005, knn.pred)
# kknn
table(Smarket_test$Direction, fit)
我是不是在编码中犯了一个愚蠢的错误?如果不是,谁能解释一下 class::knn()
和 kknn::kknn()
之间的区别?
好吧,这里面发生了很多事情。首先,我们从 class::knn()
的文档中看到 the classification is decided by majority vote, with ties broken at random.
所以看来我们应该从查看 class::knn()
的输出开始,看看会发生什么。
我反复打电话
which(fitted(knn.pred) != fitted(knn.pred))
过了一会儿,我得到了 28 和 66。所以这些是测试数据集中的观察结果,其中有一些随机性。要查看为什么这两个观察很麻烦,我们可以在 class::knn()
中设置 prob = TRUE
来获得预测概率。
knn.pred <- knn(train.X, test.X, train.Direction, k = the_k, prob = TRUE)
attr(knn.pred, "prob")
#> [1] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 1.0000000 0.6666667
#> [8] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 1.0000000
#> [15] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667
#> [22] 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.6666667 0.5000000
#> [29] 0.6666667 0.6666667 1.0000000 0.6666667 0.6666667 0.6666667 0.6666667
#> [36] 1.0000000 0.6666667 0.6666667 0.6666667 1.0000000 1.0000000 1.0000000
#> [43] 0.6666667 0.6666667 0.6666667 0.6666667 1.0000000 0.6666667 1.0000000
#> [50] 1.0000000 0.6666667 1.0000000 0.6666667 0.6666667 1.0000000 1.0000000
#> [57] 0.6666667 0.6666667 0.6666667 1.0000000 0.6666667 0.6666667 0.6666667
#> [64] 0.6666667 1.0000000 0.5000000 0.6666667 1.0000000 0.6666667 1.0000000
...
在这里我们看到观测值 28 和 66 的预测概率都是 0.5
。但是那怎么可能因为我们有 k=3
?
为了回答这个问题,我们将查看这些点的最近邻居。我将使用 RANN::nn2()
函数来计算训练集和测试集之间的距离。让我们以第一个观察为例,我们计算距离并将它们拉出来
dists <- RANN::nn2(train.X, test.X)
dists$nn.dists[1, ]
#> [1] 0.01063015 0.05632051 0.06985700 0.08469357 0.08495881 0.08561542
#> [7] 0.10823123 0.12003333 0.12621014 0.12657014
距离本身并没有多大作用,我们想知道的是什么 他们在训练集中的观察结果和他们的 类.
我们可以用 $nn.idx
dists$nn.idx[1, ]
#> [1] 503 411 166 964 981 611 840 705 562 578
train.Direction[dists$nn.idx[1, 1:3]]
#> [1] Up Down Down
#> Levels: Down Up
我们在这里看到第一个观测值的最近邻居是 Up
、Down
和 Down
。从而给出Down
.
如果我们查看第 66 个观察结果,我们会发现一些不同的东西。请注意第三和第四最近的邻居如何具有完全相同的距离?
dists$nn.dists[66, ]
#> [1] 0.06500000 0.06754258 0.07465253 0.07465253 0.07746612 0.07778175
#> [7] 0.08905055 0.09651943 0.11036757 0.11928118
train.Direction[dists$nn.idx[66, 1:4]]
#> [1] Down Down Up Up
#> Levels: Down Up
而当我们查看他们的 类 时,有 2 个 Up
和 2 个 Down
。这就是差异出现的地方。class::knn()
将所有这 4 个观察值都算作“3 个最近的邻居”,这给出了一个平局,随机打破。 kknn::kknn()
取前 3 个邻居,忽略距离上的这种关系,并预测 Down
,因为前 3 个邻居有 2 个 Down
和 1 个 Up
。
predict(the_knn, type = "prob")[66, ]
#> Down Up
#> [1,] 0.6666667 0.3333333