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

Flo*_*ian 11 optimization r mathematical-optimization nonlinear-optimization

考虑在父环境中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()
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

当插件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
#
# (...)
Run Code Online (Sandbox Code Playgroud)

这是错误吗?怎么会这样

即使使用R的C-API,我也很难想象如何实现此行为。有任何想法吗?


注意:

mas*_*opi 7

发生问题的原因x是,当在“ BFGS”或“ L-BFGS-B”方法下的优化算法的第三次迭代中修改时,更新的内存地址。

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

您可以通过自己,如果你运行下面的代码检索的内存地址进行验证xxx内部fn()使用address()的功能envnamesdata.table包:

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")
Run Code Online (Sandbox Code Playgroud)

其部分输出(假设在运行此代码段之前未执行优化运行)将类似于以下内容:

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)
Run Code Online (Sandbox Code Playgroud)

使用中提供的其他优化方法(例如默认方法)不会发生此问题optim()

注意:如前所述,该data.table软件包还可以用于检索对象的内存地址,但是在这里,我借此机会宣传我最近发布的软件包envname(除了检索对象的内存地址之外,它还可以检索用户定义的内存地址中的环境名称-等等