R optim (): неожиданное поведение при работе с родительскими средами - PullRequest
0 голосов
/ 18 декабря 2018

Рассмотрим функцию 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
#
# (...)

Это ошибка?Как это может произойти?

Даже когда я использую C-API R, мне трудно представить, как можно добиться такого поведения.Есть идеи?


Примечание:

  • работы:

    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.

1 Ответ

0 голосов
/ 28 декабря 2018

Проблема возникает из-за того, что адрес памяти x не обновляется, когда он изменяется на третьей итерации алгоритма оптимизации по методу "BFGS" или "L-BFGS-B", как и должно быть.

Вместо этого адрес памяти x остается тем же, что и адрес памяти xx на третьей итерации, и это приводит к обновлению xx до значения *За 1009 * до того, как функция fn запустится в третий раз, что заставит функцию возвращать значение «памяти», равное ret.

. Вы можете проверить это самостоятельно, запустив следующий код, который получаетадрес памяти x и xx внутри fn() с использованием функции address() имен envnames или data.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")

чей частичный вывод (при условии, что оптимизация не выполнялась до запуска этого фрагмента кода) будет похож на следующее:

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 также можно использовать для получения адреса объектов в памяти, но здесь я пользуюсь возможностью для продвижения моего недавно выпущенногоpackage envnames (который, кроме получения адреса памяти объекта, также извлекает определяемые пользователем имена среды из их адреса памяти - среди прочего)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...