Optimlib - это легкая библиотека C ++ с помощью методов численной оптимизации для нелинейных функций.
Функции:
float ) или библиотеки с двойной точностью ( double ).Список доступных в настоящее время алгоритмы включает в себя:
Полная документация доступна в Интернете:
PDF -версия документации доступна здесь.
API Optimlib следует относительно простому соглашению с большинством алгоритмов следующим образом:
algorithm_id(<initial/final values>, <objective function>, <objective function data>);
Входные данные, по порядку, являются:
Например, алгоритм BFGS называется с помощью
bfgs (ColVec_t& init_out_vals, std::function< double ( const ColVec_t& vals_inp, ColVec_t* grad_out, void * opt_data)> opt_objfn, void* opt_data); где ColVec_t используется для представления, например, arma::vec или Eigen::VectorXd .
Optimlib доступен в виде скомпилированной общей библиотеки, или в виде библиотеки только для заголовков, только для систем Unix, а также для систем Unix (например, популярных дистрибутов на основе Linux, а также MacOS). Использование этой библиотеки с системами Windows с MSVC или без него не поддерживается .
Optimlib требует линейных библиотек линейных алгебр Armadillo или собственного C ++. (Обратите внимание, что для собственной версии 3.4.0 требуется 14-совместимый компилятор C ++.)
Прежде чем включить файлы заголовков, определите одно из следующих:
# define OPTIM_ENABLE_ARMA_WRAPPERS
# define OPTIM_ENABLE_EIGEN_WRAPPERSПример:
# define OPTIM_ENABLE_EIGEN_WRAPPERS
# include " optim.hpp " Библиотека может быть установлена на систем Unix-alike через стандартный ./configure && make Method.
Сначала клонировать библиотеку и любые необходимые подмодули:
# clone optim into the current directory
git clone https://github.com/kthohr/optim ./optim
# change directory
cd ./optim
# clone necessary submodules
git submodule update --init Установите (один) из следующих переменных среды перед запуском configure :
export ARMA_INCLUDE_PATH=/path/to/armadillo
export EIGEN_INCLUDE_PATH=/path/to/eigenОкончательно:
# build and install with Eigen
./configure -i " /usr/local " -l eigen -p
make
make install Окончательная команда будет установить Optimlib в /usr/local .
Параметры конфигурации (см ./configure -h ):
Начальный
-h Печать помощи-i Путь установки; По умолчанию: каталог сборки-f режим точности с плавающей точкой; по умолчанию: double-l Укажите выбор библиотеки линейной алгебры; Выберите arma или eigen-m Укажите библиотеки BLA и LAPACK для связи; Например, -m "-lopenblas" или -m "-framework Accelerate"-o параметры оптимизации компилятора; по умолчанию -O3 -march=native -ffp-contract=fast -flto -DARMA_NO_DEBUG-p Включить функции параллелей открытой параллелизации ( рекомендуется )Второстепенный
-c Сборка покрытия (используется с Codecov)-d A 'Development Build-g отладочная сборка (флаги оптимизации установлены на -O0 -g )Особенный
--header-only-version генерируйте только заголовок версию Optimlib (см. Ниже) Optimlib также доступен в виде библиотеки только для заголовка (т.е. без необходимости компилировать общую библиотеку). Просто запустите configure с опцией --header-only-version .
./configure --header-only-version Это создаст новый каталог, header_only_version , содержащий копию Optimlib, модифицированную для работы на линейной основе. С этой версией только для заголовка просто включите файлы заголовка ( #include "optim.hpp ) и установите include path to head_only_version каталог (например, -I/path/to/optimlib/header_only_version ).
Чтобы использовать Optimlib с помощью R-пакета, сначала генерируйте версию библиотеки только для заголовка (см. Выше). Затем просто добавьте определение компилятора, прежде чем включать файлы Optimlib.
# define OPTIM_USE_RCPP_ARMADILLO
# include " optim.hpp "# define OPTIM_USE_RCPP_EIGEN
# include " optim.hpp " Чтобы проиллюстрировать Optimlib на работе, рассмотрите возможность поиска глобального минимума функции Ackley:

Это хорошо известная тестовая функция со многими локальными минимумами. Методы типа Ньютона (такие как BFG) чувствительны к выбору начальных значений и будут работать довольно плохо здесь. Таким образом, мы будем использовать глобальный метод поиска-в этом случае: дифференциальная эволюция.
Код:
# define OPTIM_ENABLE_EIGEN_WRAPPERS
# include " optim.hpp "
# define OPTIM_PI 3.14159265358979
double
ackley_fn ( const Eigen::VectorXd& vals_inp, Eigen::VectorXd* grad_out, void * opt_data)
{
const double x = vals_inp ( 0 );
const double y = vals_inp ( 1 );
const double obj_val = 20 + std::exp ( 1 ) - 20 * std::exp ( - 0.2 * std::sqrt ( 0.5 *(x*x + y*y)) ) - std::exp ( 0.5 *( std::cos ( 2 * OPTIM_PI * x) + std::cos ( 2 * OPTIM_PI * y)) );
return obj_val;
}
int main ()
{
Eigen::VectorXd x = 2.0 * Eigen::VectorXd::Ones ( 2 ); // initial values: (2,2)
bool success = optim::de (x, ackley_fn, nullptr );
if (success) {
std::cout << " de: Ackley test completed successfully. " << std::endl;
} else {
std::cout << " de: Ackley test completed unsuccessfully. " << std::endl;
}
std::cout << " de: solution to Ackley test: n " << x << std::endl;
return 0 ;
}На компьютерах на основе x86 этот пример можно собрать с помощью:
g++ -Wall -std=c++14 -O3 -march=native -ffp-contract=fast -I/path/to/eigen -I/path/to/optim/include optim_de_ex.cpp -o optim_de_ex.out -L/path/to/optim/lib -loptimВыход:
de: Ackley test completed successfully.
elapsed time: 0.028167s
de: solution to Ackley test:
-1.2702e-17
-3.8432e-16
На стандартном ноутбуке Optimlib будет вычислять решение внутри машины с точностью машины во доли секунды.
Версия этого примера на основе Armadillo:
# define OPTIM_ENABLE_ARMA_WRAPPERS
# include " optim.hpp "
# define OPTIM_PI 3.14159265358979
double
ackley_fn ( const arma::vec& vals_inp, arma::vec* grad_out, void * opt_data)
{
const double x = vals_inp ( 0 );
const double y = vals_inp ( 1 );
const double obj_val = 20 + std::exp ( 1 ) - 20 * std::exp ( - 0.2 * std::sqrt ( 0.5 *(x*x + y*y)) ) - std::exp ( 0.5 *( std::cos ( 2 * OPTIM_PI * x) + std::cos ( 2 * OPTIM_PI * y)) );
return obj_val;
}
int main ()
{
arma::vec x = arma::ones ( 2 , 1 ) + 1.0 ; // initial values: (2,2)
bool success = optim::de (x, ackley_fn, nullptr );
if (success) {
std::cout << " de: Ackley test completed successfully. " << std::endl;
} else {
std::cout << " de: Ackley test completed unsuccessfully. " << std::endl;
}
arma::cout << " de: solution to Ackley test: n " << x << arma::endl;
return 0 ;
}Скомпилируйте и запустите:
g++ -Wall -std=c++11 -O3 -march=native -ffp-contract=fast -I/path/to/armadillo -I/path/to/optim/include optim_de_ex.cpp -o optim_de_ex.out -L/path/to/optim/lib -loptim
./optim_de_ex.out Проверьте каталог /tests для получения дополнительных примеров и https://optimlib.readthedocs.io/en/latest/ для подробного описания каждого алгоритма.
Для примера, основанного на данных, рассмотрим оценку максимальной вероятности модели логита, распространенной в статистике и машинном обучении. В этом случае у нас есть выражения закрытой формы для градиента и гессиана. Мы будем использовать популярный метод спуска градиента, Адам (оценка адаптивного момента) и сравниться с чистым алгоритмом на основе Ньютона.
# define OPTIM_ENABLE_ARMA_WRAPPERS
# include " optim.hpp "
// sigmoid function
inline
arma::mat sigm ( const arma::mat& X)
{
return 1.0 / ( 1.0 + arma::exp (-X));
}
// log-likelihood function data
struct ll_data_t
{
arma::vec Y;
arma::mat X;
};
// log-likelihood function with hessian
double ll_fn_whess ( const arma::vec& vals_inp, arma::vec* grad_out, arma::mat* hess_out, void * opt_data)
{
ll_data_t * objfn_data = reinterpret_cast < ll_data_t *>(opt_data);
arma::vec Y = objfn_data-> Y ;
arma::mat X = objfn_data-> X ;
arma::vec mu = sigm (X*vals_inp);
const double norm_term = static_cast < double >(Y. n_elem );
const double obj_val = - arma::accu ( Y% arma::log (mu) + ( 1.0 -Y)% arma::log ( 1.0 -mu) ) / norm_term;
//
if (grad_out)
{
*grad_out = X. t () * (mu - Y) / norm_term;
}
//
if (hess_out)
{
arma::mat S = arma::diagmat ( mu%( 1.0 -mu) );
*hess_out = X. t () * S * X / norm_term;
}
//
return obj_val;
}
// log-likelihood function for Adam
double ll_fn ( const arma::vec& vals_inp, arma::vec* grad_out, void * opt_data)
{
return ll_fn_whess (vals_inp,grad_out, nullptr ,opt_data);
}
//
int main ()
{
int n_dim = 5 ; // dimension of parameter vector
int n_samp = 4000 ; // sample length
arma::mat X = arma::randn (n_samp,n_dim);
arma::vec theta_0 = 1.0 + 3.0 * arma::randu (n_dim, 1 );
arma::vec mu = sigm (X*theta_0);
arma::vec Y (n_samp);
for ( int i= 0 ; i < n_samp; i++)
{
Y (i) = ( arma::as_scalar ( arma::randu ( 1 )) < mu (i) ) ? 1.0 : 0.0 ;
}
// fn data and initial values
ll_data_t opt_data;
opt_data. Y = std::move (Y);
opt_data. X = std::move (X);
arma::vec x = arma::ones (n_dim, 1 ) + 1.0 ; // initial values
// run Adam-based optim
optim:: algo_settings_t settings;
settings. gd_method = 6 ;
settings. gd_settings . step_size = 0.1 ;
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now ();
bool success = optim::gd (x,ll_fn,&opt_data,settings);
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now ();
std::chrono::duration< double > elapsed_seconds = end-start;
//
if (success) {
std::cout << " Adam: logit_reg test completed successfully. n "
<< " elapsed time: " << elapsed_seconds. count () << " s n " ;
} else {
std::cout << " Adam: logit_reg test completed unsuccessfully. " << std::endl;
}
arma::cout << " n Adam: true values vs estimates: n " << arma::join_rows (theta_0,x) << arma::endl;
//
// run Newton-based optim
x = arma::ones (n_dim, 1 ) + 1.0 ; // initial values
start = std::chrono::system_clock::now ();
success = optim::newton (x,ll_fn_whess,&opt_data);
end = std::chrono::system_clock::now ();
elapsed_seconds = end-start;
//
if (success) {
std::cout << " newton: logit_reg test completed successfully. n "
<< " elapsed time: " << elapsed_seconds. count () << " s n " ;
} else {
std::cout << " newton: logit_reg test completed unsuccessfully. " << std::endl;
}
arma::cout << " n newton: true values vs estimates: n " << arma::join_rows (theta_0,x) << arma::endl;
return 0 ;
}Выход:
Adam: logit_reg test completed successfully.
elapsed time: 0.025128s
Adam: true values vs estimates:
2.7850 2.6993
3.6561 3.6798
2.3379 2.3860
2.3167 2.4313
2.2465 2.3064
newton: logit_reg test completed successfully.
elapsed time: 0.255909s
newton: true values vs estimates:
2.7850 2.6993
3.6561 3.6798
2.3379 2.3860
2.3167 2.4313
2.2465 2.3064
Объединяя собственное с библиотекой Autodiff, Optimlib обеспечивает экспериментальную поддержку для автоматической дифференциации.
Пример с использованием автоматической дифференциации прямого режима с BFGS для функции сферы:
# define OPTIM_ENABLE_EIGEN_WRAPPERS
# include " optim.hpp "
# include < autodiff/forward/real.hpp >
# include < autodiff/forward/real/eigen.hpp >
//
autodiff::real
opt_fnd ( const autodiff::ArrayXreal& x)
{
return x. cwiseProduct (x). sum ();
}
double
opt_fn ( const Eigen::VectorXd& x, Eigen::VectorXd* grad_out, void * opt_data)
{
autodiff::real u;
autodiff::ArrayXreal xd = x. eval ();
if (grad_out) {
Eigen::VectorXd grad_tmp = autodiff::gradient (opt_fnd, autodiff::wrt (xd), autodiff::at (xd), u);
*grad_out = grad_tmp;
} else {
u = opt_fnd (xd);
}
return u. val ();
}
int main ()
{
Eigen::VectorXd x ( 5 );
x << 1 , 2 , 3 , 4 , 5 ;
bool success = optim::bfgs (x, opt_fn, nullptr );
if (success) {
std::cout << " bfgs: forward-mode autodiff test completed successfully. n " << std::endl;
} else {
std::cout << " bfgs: forward-mode autodiff test completed unsuccessfully. n " << std::endl;
}
std::cout << " solution: x = n " << x << std::endl;
return 0 ;
}Скомпилируйте с:
g++ -Wall -std=c++17 -O3 -march=native -ffp-contract=fast -I/path/to/eigen -I/path/to/autodiff -I/path/to/optim/include optim_autodiff_ex.cpp -o optim_autodiff_ex.out -L/path/to/optim/lib -loptimСмотрите документацию для более подробной информации по этой теме.
Кит О'Хара
Apache версия 2