考虑函数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测试。
答案 0 :(得分:7)
之所以出现此问题,是因为在优化算法的第三次迭代中,在“ BFGS”或“ L-BFGS-”下修改x
的内存地址后,它们没有更新。 B”方法。
相反,在第三次迭代中,x
的存储器地址与xx
的存储器地址保持相同,这使得xx
被更新为{{1 }}在x
函数第三次运行之前,因此使该函数返回fn
的“内存”值。
如果运行以下代码,使用{{的ret
函数来检索x
内的xx
和fn()
的内存地址,则可以自己进行验证。 3}}或envnames软件包:
address()
其部分输出(假设在运行此代码段之前未执行优化运行)将类似于以下内容:
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")
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(除检索对象的内存地址,它还从其内存地址中检索用户定义的环境名称-其他)