R optim():使用父环境时的意外行为

Posted

技术标签:

【中文标题】R optim():使用父环境时的意外行为【英文标题】:R optim(): unexpected behavior when working with parent environments 【发布时间】:2019-05-18 11:41:05 【问题描述】:

考虑函数fn(),它将最近的输入x 及其返回值ret <- x^2 存储在父环境中。

makeFn <- function()
    xx <- ret <- NA
    fn <- function(x)
       if(!is.na(xx) && x==xx)
           cat("x=", xx, ", ret=", ret, " (memory)", fill=TRUE, sep="")
           return(ret)
       
       xx <<- x; ret <<- sum(x^2)
       cat("x=", xx, ", ret=", ret, " (calculate)", fill=TRUE, sep="")
       ret
    
    fn

fn <- makeFn()

fn() 仅在提供不同输入值时进行计算。否则,它会从父环境中读取ret

fn(2)
# x=2, ret=4 (calculate)
# [1] 4
fn(3)
# x=3, ret=9 (calculate)
# [1] 9
fn(3)
# x=3, ret=9 (memory)
# [1] 9

当插件fn()optim() 中找到其最小值时,会出现以下意外行为结果:

optim(par=10, f=fn, method="L-BFGS-B")
# x=10, ret=100 (calculate)
# x=10.001, ret=100.02 (calculate)
# x=9.999, ret=100.02 (memory)
# $par
# [1] 10
# 
# $value
# [1] 100
#
# (...)

这是一个错误吗?怎么会这样?

即使使用 R 的 C-API,我也很难想象这种行为是如何实现的。有什么想法吗?


注意:

作品:

library("optimParallel") # (parallel) wrapper to optim(method="L-BFGS-B")
cl <- makeCluster(2); setDefaultCluster(cl)
optimParallel(par=10, f=fn)

作品:

optimize(f=fn, interval=c(-10, 10))

作品:

optim(par=10, fn=fn)

失败:

optim(par=10, fn=fn, method="BFGS")

作品:

library("lbfgs"); library("numDeriv")
lbfgs(call_eval=fn, call_grad=function(x) grad(func=fn, x=x), vars=10)

作品:

library("memoise")
fn_mem <- memoise(function(x) x^2)
optim(par=10, f=fn_mem, method="L-BFGS-B")

使用 R 版本 3.5.0 测试。

【问题讨论】:

我会将示例发送到 R-devel 列表 (stat.ethz.ch/mailman/listinfo/r-devel) 【参考方案1】:

出现问题是因为x 的内存地址在“B​​FGS”或“L-BFGS-B”下的优化算法的第三次迭代中被修改时没有更新方法,应该的。

相反,x 的内存地址在第三次迭代时保持与xx 的内存地址相同,这使得xxfn 函数之前更新为x 的值第三次运行,从而使函数返回ret的“内存”值。

如果您运行以下代码,使用envnames 或data.table 包的address() 函数在fn() 中检索xxx 的内存地址,您可以自己验证这一点:

library(envnames)

makeFn <- function()
  xx <- ret <- NA
  fn <- function(x)
    cat("\nAddress of x and xx at start of fn:\n")
    cat("address(x):", address(x), "\n")
    cat("address(xx):", address(xx), "\n")
    if(!is.na(xx) && x==xx)
      cat("x=", xx, ", ret=", ret, " (memory)", fill=TRUE, sep="")
      return(ret)
    
    xx <<- x; ret <<- sum(x^2)
    cat("x=", xx, ", ret=", ret, " (calculate)", fill=TRUE, sep="")
    ret
  
  fn


fn <- makeFn()

# Run the optimization process
optim(par=0.1, fn=fn, method="L-BFGS-B")

其部分输出(假设在运行此代码 sn-p 之前未进行任何优化运行)将类似于以下内容:

Address of x and xx at start of fn:
address(x): 0000000013C89DA8 
address(xx): 00000000192182D0 
x=0.1, ret=0.010201 (calculate)

Address of x and xx at start of fn:
address(x): 0000000013C8A160 
address(xx): 00000000192182D0 
x=0.101, ret=0.010201 (calculate)

Address of x and xx at start of fn:
address(x): 0000000013C8A160 
address(xx): 0000000013C8A160 
x=0.099, ret=0.010201 (memory)

这个问题不会发生在optim() 中可用的其他优化方法中,例如默认的。

注意:如前所述,data.table 包也可用于检索对象的内存地址,但在这里我借此机会宣传我最近发布的包envnames(除了检索对象的内存地址,它还从他们的内存地址中检索用户定义的环境名称——等等)

【讨论】:

跟踪内存地址的好主意。 - 我认为“BFGS”和“L-BFGS-B”方法都会出现问题。我可能很快会向 CRAN 报告错误。 是的,“BFGS”和“L-BFGS-B”两种方法都会出现问题。

以上是关于R optim():使用父环境时的意外行为的主要内容,如果未能解决你的问题,请参考以下文章

使用两个布尔数组索引 2D np.array 时的意外行为

使用 Comparator.comparing(HashMap::get) 作为比较器时的意外行为

尝试使用 outerWidth() 和 $(window).resize() 水平居中动态宽度元素时的意外行为

iOS UICollectionView 推送刷新时的意外行为

NHibernate:查询未映射实体时的意外行为

调用 setSortingEnabled(1) 时的意外行为