R 中的基本运算在 Windows 和 Linux 上给出不同的结果

Rai*_*Rai 10 linux windows debian r blas

我一直在 R 中运行一些代码,在测试时意识到 Windows 和 Linux 上的结果是不同的。我试图理解为什么会发生这种情况,但找不到答案。让我们用一个例子来说明它:

这些是一些用于再现性的硬编码值,始终从干净的环境开始。我已经检查过这些值的位表示在 Windows 和 Linux 计算机中完全相同:

data <- structure(list(x = c(0.1, 0.1, 0.1, 5, 5, 5, 10, 10, 10, 20, 20, 20), 
                       y = c(0.013624804, 0.014023006, 0.013169554, 0.70540352, 
                             0.68711807, 0.69233506, 1.4235181, 1.348244, 1.4141854, 2.779813, 
                             2.7567347, 2.7436437)), class = c("data.frame"), row.names = c(NA, 12L))
val <- c(43.3065849160736, 0.00134925463859564, 1.03218302435548, 270.328323775978)
theta <- 1.60812569803848
init <- c(b0 = 2.76836653333333, b1 = 0.0134350095, b2 = 2.15105945932773, 
          b3 = 6.85922519794374)
Run Code Online (Sandbox Code Playgroud)

现在我定义了一个新变量W,它在 Windows 和 Linux 中的位表示形式再次完全相同:

f <- function(X, b0, b1, b2, b3) {
  b0 + (b1 - b0) / (1 + exp(b2*(log(X) - log(b3))))
}

W <- 1 / f(data$x, val[1], val[2], val[3], val[4])^theta
Run Code Online (Sandbox Code Playgroud)

最后我应用一个optim函数:

SSw <- function(Y, X, b0, b1, b2, b3, w) {
  sum(w * (Y - f(X, b0, b1, b2, b3))^2)
}

SSw.vec <- function(par) SSw(data$y, data$x, par[1], par[2], par[3], par[4], W)

mod <- optim(init, SSw.vec, method = "L-BFGS-B", lower = c(-Inf,-Inf,-Inf,0))

print(mod$par)
# In Windows it returns:
#          b0           b1           b2           b3 
# 3.097283e+01 1.831543e-03 1.047613e+00 1.842448e+02
# In Linux it returns: 
#           b0           b1           b2           b3 
# 3.459241e+01 1.530134e-03 1.040363e+00 2.101996e+02 
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,差异非常显着,但即使不是……为什么会有差异呢?

任何帮助将非常感激!

编辑

sessionInfo()这里我在Windows和Linux上都添加了。

在 Windows 上:

R version 3.6.3 (2020-02-29)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)

Matrix products: default

locale:
[1] LC_COLLATE=English_United Kingdom.1252  LC_CTYPE=English_United Kingdom.1252    LC_MONETARY=English_United Kingdom.1252
[4] LC_NUMERIC=C                            LC_TIME=English_United Kingdom.1252    

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.8.3        plyr_1.8.6          cellranger_1.1.0    compiler_3.6.3      pillar_1.7.0        nloptr_1.2.2.2      tools_3.6.3        
 [8] bit_4.0.4           boot_1.3-24         lme4_1.1-29         lifecycle_1.0.0     tibble_3.1.7        nlme_3.1-144        gtable_0.3.0       
[15] lattice_0.20-38     pkgconfig_2.0.3     rlang_1.0.2         Matrix_1.2-18       cli_3.4.1           rstudioapi_0.11     dplyr_1.0.6        
[22] generics_0.1.0      vctrs_0.3.8         lmerTest_3.1-3      grid_3.6.3          tidyselect_1.1.1    glue_1.4.2          R6_2.4.1           
[29] fansi_0.4.1         readxl_1.3.1        minqa_1.2.4         ggplot2_3.3.6       purrr_0.3.5         magrittr_1.5        scales_1.1.1       
[36] ellipsis_0.3.2      MASS_7.3-51.5       splines_3.6.3       colorspace_1.4-1    numDeriv_2016.8-1.1 renv_0.13.2         utf8_1.1.4         
[43] munsell_0.5.0       crayon_1.3.4       
Run Code Online (Sandbox Code Playgroud)

在 Linux 上:

R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 10 (buster)

Matrix products: default
BLAS:   /opt/r/lib/R/lib/libRblas.so
LAPACK: /opt/r/lib/R/lib/libRlapack.so

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
 [4] LC_COLLATE=C           LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
 [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
[10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

loaded via a namespace (and not attached):
[1] compiler_3.6.3 tools_3.6.3    renv_0.13.2   

Run Code Online (Sandbox Code Playgroud)

shs*_*shs 16

我在本地计算机上使用 Fedora 37 和 R 4.2.2 运行了您的代码。和其他评论者一样,我也得到了你在 Windows 上得到的结果。然后我拉取了R 版本 3.6.3 的摇杆图像:

\n
docker run -ti rocker/r-ver:3.6.3 R\n
Run Code Online (Sandbox Code Playgroud)\n

该镜像也是基于 Debian 的。在这里,我能够重新创建您在系统上得到的结果。

\n

然后我转移到摇杆版本 4.0.0:

\n
docker run -ti rocker/r-ver:4.0.0 R\n
Run Code Online (Sandbox Code Playgroud)\n

这里的结果与你在 Windows 上得到的结果以及其他人在他们的机器上得到的结果相同。必须指出的是,在 R 4.0.0 中,rocker 项目从基于 Debian 的映像转移到了 Ubuntu LTS。

\n

Fedora 能够通过软件包轻松切换 BLAS/LAPACK 后端flexiblas。因此,我能够使用系统上可用的八个不同后端来测试您的代码。如下所示,它们确实会产生不同的结果。特别是,ATLAS后端与您得到的结果有些接近。相比之下,OPENBLAS-OPENMP(Fedora 上的默认设置)、其他OPENBLAS变体NETLIB都产生与 Windows 上相同的结果。第三个家庭BLIS又产生了另一组可能的结果。

\n

其中一个结果是否比其他结果更好?是的!optim()寻找最小化所提供函数的结果。在返回的列表中,它不仅报告最小化参数,还报告它们的值。我已将其包含在下表中。所以ATLAS后端在这里获胜。必须指出的是,这optim()并没有在分析上最小化。所以它总是给出近似结果。这就是为什么初始值和方法对于我们得到的结果很重要。显然,对于你的功能来说,后端也很重要。如果您查看 Buster 上获得的参数,该函数将转到0.002800321。所以它实际上比我们在更现代的系统上得到的结果更好,除了我得到的结果ATLAS。这也恰好比其他后端慢得多。看来,较新的后端可能已经用速度换取了准确性。

\n

如果您的目标是跨平台的一致性,您可以将系统升级到 Debian 11 Bullseye,因为这似乎有一个后端产生与其他现代平台相同的结果,正如@jay.sf 的回答所示。您还可以调查是否可以找到 Buster for Windows 上使用的相同 BLAS 后端版本。

\n

此外,您可以尝试更改为当前系统上的另一个 blas 库。以下是如何执行此操作的指南。虽然它适用于 Ubuntu,但由于两者都使用apt,它也应该适用于您的系统。(编辑:我在 Buster 的 VM 中尝试过。可用的 BLAS 后端都没有产生与更现代的系统相同的结果)

\n

最后,如果您觉得您的旧系统上必须有一个更新的 BLAS 库,那么您可以尝试自己向后移植它。我对此没有经验。我不知道这样做是否明智,也不知道成功的可能性有多大。我只是为了完整性而提及它。

\n
library(flexiblas)\nlibrary(tidyverse)\n\ntest_fun <- function(i) {\n  flexiblas_switch(i)\n  data <- structure(list(x = c(0.1, 0.1, 0.1, 5, 5, 5, 10, 10, 10, 20, 20, 20),\n                         y = c(0.013624804, 0.014023006, 0.013169554, 0.70540352,\n                               0.68711807, 0.69233506, 1.4235181, 1.348244, 1.4141854, 2.779813,\n                               2.7567347, 2.7436437)), class = c("data.frame"), row.names = c(NA, 12L))\n  val <- c(43.3065849160736, 0.00134925463859564, 1.03218302435548, 270.328323775978)\n  theta <- 1.60812569803848\n  init <- c(b0 = 2.76836653333333, b1 = 0.0134350095, b2 = 2.15105945932773,\n            b3 = 6.85922519794374)\n  f <- function(X, b0, b1, b2, b3) {\n    b0 + (b1 - b0) / (1 + exp(b2*(log(X) - log(b3))))\n  }\n  W <- 1 / f(data$x, val[1], val[2], val[3], val[4])^theta\n  SSw <- function(Y, X, b0, b1, b2, b3, w) {\n    sum(w * (Y - f(X, b0, b1, b2, b3))^2)\n  }\n  SSw.vec <- function(par) SSw(data$y, data$x, par[1], par[2], par[3], par[4], W)\n  mod <- optim(init, SSw.vec, method = "L-BFGS-B", lower = c(-Inf,-Inf,-Inf,0))\n  return(c(mod$par, value = mod$value))\n}\n
Run Code Online (Sandbox Code Playgroud)\n
flexiblas_list() |>\n  setdiff("__FALLBACK__") |>\n  tibble(backend = _) |>\n  mutate(\n    idx = flexiblas_load_backend(backend),\n    res = map(idx, test_fun)\n  ) |>\n  unnest_wider(res)\n#> # A tibble: 8 \xc3\x97 7\n#>   backend            idx    b0      b1    b2    b3   value\n#>   <chr>            <int> <dbl>   <dbl> <dbl> <dbl>   <dbl>\n#> 1 NETLIB               2  31.0 0.00183  1.05  184. 0.00282\n#> 2 OPENBLAS-OPENMP      1  31.0 0.00183  1.05  184. 0.00282\n#> 3 ATLAS                3  34.7 0.00168  1.04  209. 0.00280\n#> 4 BLIS-SERIAL          4  27.1 0.00225  1.06  158. 0.00285\n#> 5 BLIS-OPENMP          5  27.1 0.00225  1.06  158. 0.00285\n#> 6 BLIS-THREADS         6  27.1 0.00225  1.06  158. 0.00285\n#> 7 OPENBLAS-SERIAL      7  31.0 0.00183  1.05  184. 0.00282\n#> 8 OPENBLAS-THREADS     8  31.0 0.00183  1.05  184. 0.00282\n
Run Code Online (Sandbox Code Playgroud)\n


jay*_*.sf 6

我现在可以确认你的问题了。我在虚拟机上安装了 Debian Buster,apt install r-base并获得了 R3.5.2,运行了你的代码,它显示了与 OP 相同(可能)“有缺陷”的 Linux 结果。

\n

然而,后来我更新到了 R.4.2.2,但“有缺陷”的结果并没有改变!它使用了libblas3.8. 在我的真实机器上,我运行 Ubuntu、R4.2.2、libblas3.10 并获得(可能)“正确”的 Windows 结果。

\n

不幸的是,我无法libblas3.10在 Debian Buster 上安装,并且我不确定这是否可能(请参阅链接中的 Buster 下未列出它)。请注意,Debian Bullseye 现在是实际版本,而您的系统实际上已经过时了。

\n

正如我的评论\xe2\x80\x94 中所述,过时的 BLAS/LAPACK 可能 \xe2 \x80\x95 仍然被认为会产生错误的结果,因为这些是实际的代数引擎。您也许可以在 Debian Buster 上安装更新的 BLAS/LAPACK,但我倾向于建议您升级到 Debian Bullseye。

\n


Ben*_*ker 5

这有点离题,但减轻平台间差异最简单的方法可能是使用

control = list(parscale = abs(init))
Run Code Online (Sandbox Code Playgroud)

在你的optim()通话中。

这样做的原因是,除非指定梯度函数,否则会自动使用具有固定步长( ,所有参数默认为 1e-3 )的L-BFGS-B有限差分来近似梯度。这通常足够好,但可能会导致困难/不稳定的优化问题,或者当参数的规模非常不同时。 如上所述,告诉我们如何在内部缩放参数,通常会改善结果。ndepsparscaleoptim

通过分析(或自动微分)梯度可能会更好,但这需要更多工作......