diff --git a/docs/EvoKit/minimal_example.rst b/docs/EvoKit/minimal_example.rst index 98ba959f73550e52bb3898a8fafe0a746808b80c..0eb7c66902fe71ebe097586f8385f43952068860 100644 --- a/docs/EvoKit/minimal_example.rst +++ b/docs/EvoKit/minimal_example.rst @@ -37,7 +37,7 @@ step1: 生成预测网络 exe = fluid.Executor(fluid.CPUPlace()) exe.run(fluid.default_startup_program()) fluid.io.save_inference_model( - dirname='cartpole_init_model', + dirname='init_model', feeded_var_names=['obs'], target_vars=[prob], params_filename='params', @@ -46,51 +46,70 @@ step1: 生成预测网络 step2: 构造ESAgent ################### -- 根据配置文件构造一个ESAgent -- 调用 ``load_inference_model`` 函数加载模型参数 + +- 调用 ``load_config`` 加载配置文件。 +- 调用 ``load_inference_model`` 函数加载模型参数。 +- 调用 ``init_solver`` 初始化solver。 配置文件主要是用于指定进化算法类型(比如Gaussian或者CMA),使用的optimizer类型(Adam或者SGD)。 .. code-block:: c++ - ESAgent agent = ESAgent(config_path); - agent->load_inference_model(model_dir); + ESAgent agent = ESAgent(); + agent.load_config(config); + agent.load_inference_model(model_dir); + agent.init_solver(); + + // 附:EvoKit配置项示范 + solver { + type: BASIC_ES + optimizer { // 线下Adam更新 + type: ADAM + base_lr: 0.05 + adam { + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-08 + } + } + sampling { // 线上高斯采样 + type: GAUSSIAN_SAMPLING + gaussian_sampling { + std: 0.5 + cached: true + seed: 1024 + cache_size : 100000 + } + } + } - //附:DeepES配置项示范 - seed: 1024 //随机种子,用于复现 - gaussian_sampling { //高斯采样相关参数 - std: 0.5 - } - optimizer { //离线更新所用的optimizer 类型以及相关超级参数 - type: "Adam" - base_lr: 0.05 - } step3: 生成用于采样的Agent ################### 主要关注三个接口: -- clone(): 生成一个用于sampling的agent。 -- add_noise():给这个agent的参数空间增加噪声,同时返回该噪声对应的唯一信息,这个信息得记录在log中,用于线下更新。 -- predict():提供预测接口。 +- 调用 ``clone`` 生成一个用于sampling的agent。 +- 调用 ``add_noise`` 给这个agent的参数空间增加噪声,同时返回该噪声对应的唯一信息,这个信息得记录在log中,用于线下更新。 +- 调用 ``predict`` 提供预测接口。 .. code-block:: c++ - auto sampling_agent = agent.clone(); - auto sampling_info = sampling_agent.add_noise(); - sampling_agent.predict(feature); + auto sampling_agent = agent.clone(); + auto sampling_info = sampling_agent.add_noise(); + sampling_agent.predict(feature); step4: 用采样的数据更新模型参数 ################### 用户提供两组数据: -- 采样参数过程中用于线下复现采样噪声的key + +- 采样参数过程中用于线下复现采样噪声的sampling_info - 扰动参数后,新参数的评估结果 .. code-block:: c++ - agent.update(info, rewards); + agent.update(sampling_infos, rewards); 主代码以及注释 ################# @@ -99,55 +118,59 @@ step4: 用采样的数据更新模型参数 .. code-block:: c++ - int main(int argc, char* argv[]) { - std::vector envs; - // 构造10个环境,用于多线程训练 - for (int i = 0; i < ITER; ++i) { - envs.push_back(CartPole()); - } - - // 初始化ESAgent - std::string model_dir = "./demo/cartpole_init_model"; - std::string config_path = "./demo/cartpole_config.prototxt"; - std::shared_ptr agent = std::make_shared(model_dir, config_path); - - // 生成10个agent用于同时采样 - std::vector< std::shared_ptr > sampling_agents; - for (int i = 0; i < ITER; ++i) { - sampling_agents.push_back(agent->clone()); - } - - std::vector noisy_keys; - std::vector noisy_rewards(ITER, 0.0f); - noisy_keys.resize(ITER); - omp_set_num_threads(10); - - // 共迭代100轮 - for (int epoch = 0; epoch < 100; ++epoch) { - #pragma omp parallel for schedule(dynamic, 1) - for (int i = 0; i < ITER; ++i) { - std::shared_ptr sampling_agent = sampling_agents[i]; - SamplingInfo key; - bool success = sampling_agent->add_noise(key); - float reward = evaluate(envs[i], sampling_agent); - // 保存采样的key以及对应的评估结果 - noisy_keys[i] = key; - noisy_rewards[i] = reward; - } - // 更新模型参数,注意:参数更新后会自动同步到sampling_agent中 - bool success = agent->update(noisy_keys, noisy_rewards); - - int reward = evaluate(envs[0], agent); - LOG(INFO) << "Epoch:" << epoch << " Reward: " << reward; - } - } + int main(int argc, char* argv[]) { + std::vector envs; + // 构造10个环境,用于多线程训练 + for (int i = 0; i < ITER; ++i) { + envs.push_back(CartPole()); + } + + // 初始化ESAgent + std::string model_dir = "./demo/cartpole/init_model"; + std::string config_path = "./demo/cartpole/config.prototxt"; + std::shared_ptr agent = std::make_shared(); + agent->load_config(config_path); // 加载配置 + + agent->load_inference_model(FLAGS_model_dir); // 加载初始预测模型 + agent->init_solver(); // 初始化solver,注意要在load_inference_model后执行 + + // 生成10个agent用于同时采样 + std::vector> sampling_agents; + for (int i = 0; i < ITER; ++i) { + sampling_agents.push_back(agent->clone()); + } + + std::vector sampling_infos; + std::vector rewards(ITER, 0.0f); + sampling_infos.resize(ITER); + omp_set_num_threads(10); + + // 共迭代100轮 + for (int epoch = 0; epoch < 100; ++epoch) { + #pragma omp parallel for schedule(dynamic, 1) + for (int i = 0; i < ITER; ++i) { + std::shared_ptr sampling_agent = sampling_agents[i]; + SamplingInfo sampling_info; + sampling_agent->add_noise(sampling_info); + float reward = evaluate(envs[i], sampling_agent); + // 保存采样的sampling_info以及对应的评估结果reward + sampling_infos[i] = sampling_info; + rewards[i] = reward; + } + // 更新模型参数,注意:参数更新后会自动同步到sampling_agent中 + agent->update(sampling_infos, rewards); + + int reward = evaluate(envs[0], agent); + LOG(INFO) << "Epoch:" << epoch << " Reward: " << reward; // 打印每一轮reward + } + } 如何运行demo ################# - 下载代码 - 在icode上clone代码,我们的仓库路径是: ``baidu/nlp/deep-es`` + 在icode上clone代码,我们的仓库路径是: ``baidu/nlp/deep-es`` ``TO DO: 修改库路径`` - 编译demo @@ -159,7 +182,7 @@ step4: 用采样的数据更新模型参数 ``export LD_LIBRARY_PATH=./output/so/:$LD_LIBRARY_PATH`` - 运行demo: ``./output/bin/evokit_demo`` + 运行demo: ``./output/bin/cartpole/train`` 问题解决 #################### diff --git a/docs/EvoKit/online_example.rst b/docs/EvoKit/online_example.rst index 7faf13e9f5c8fca1e9d1feecfbd9465b03054c83..c4963f8cb909a240f318b1e85c77ba310c460160 100644 --- a/docs/EvoKit/online_example.rst +++ b/docs/EvoKit/online_example.rst @@ -3,66 +3,122 @@ Example for Online Products ``本教程的目标: 演示通过EvoKit库上线后,如何迭代算法,更新模型参数。`` -在实际的产品线中,线上无法实时拿到用户日志,经常是通过保存用户点击/时长日志,在线下根据用户数据更新模型,然后再推送到线上,完成算法的更新。 +在产品线中,线上无法实时拿到用户日志,经常是通过保存用户点击/时长日志,在线下根据用户数据更新模型,然后再推送到线上,完成算法的更新。 本教程继续围绕经典的CartPole环境,展示如何通过在线采样/离线更新的方式,来更新迭代ES算法。 demo的完整代码示例放在demp/online_example文件夹中。 +``TO DO: 文件夹`` -线上采样 +初始化solver --------------------- +构造solver,对它初始化,并保存到文件。初始化solver仅需在开始时调用一次。 + +.. code-block:: c++ -这部分的逻辑与上一个demo极度相似,主要的区别是采样返回的key以及评估的reward通过二进制的方式记录到log文件中。 + std::shared_ptr agent = std::make_shared(); + agent->load_config(FLAGS_config_path); + agent->load_inference_model(FLAGS_model_dir); + agent->init_solver(); + agent->save_solver(FLAGS_model_dir); + + +线上采样 +--------------------- +加载模型和solver,记录线上采样返回的sampling_info以及评估的reward,并通过二进制的方式记录到log文件中。 .. code-block:: c++ + std::shared_ptr agent = std::make_shared(); + agent->load_config(FLAGS_config_path); + agent->load_inference_model(FLAGS_model_dir); + agent->load_solver(FLAGS_model_dir); + #pragma omp parallel for schedule(dynamic, 1) for (int i = 0; i < ITER; ++i) { std::shared_ptr sampling_agent = sampling_agents[i]; - SamplingInfo key; - bool success = sampling_agent->add_noise(key); + SamplingInfo sampling_info; + sampling_agent->add_noise(sampling_info); float reward = evaluate(envs[i], sampling_agent); - noisy_keys[i] = key; - noisy_rewards[i] = reward; + sampling_infos[i] = sampling_info; + rewards[i] = reward; } // save sampling information and log in binary fomrat std::ofstream log_stream(FLAGS_log_path, std::ios::binary); for (int i = 0; i < ITER; ++i) { - std::string data; - noisy_keys[i].SerializeToString(&data); - int size = data.size(); - log_stream.write((char*) &noisy_rewards[i], sizeof(float)); - log_stream.write((char*) &size, sizeof(int)); - log_stream.write(data.c_str(), size); + std::string data; + sampling_infos[i].SerializeToString(&data); + int size = data.size(); + log_stream.write((char*) &rewards[i], sizeof(float)); + log_stream.write((char*) &size, sizeof(int)); + log_stream.write(data.c_str(), size); } log_stream.close(); - 线下更新 ----------------------- - -在加载好之前记录的log之后,调用 ``update`` 函数进行更新,然后通过 ``save_inference_model`` 函数保存更新后的参数到本地,推送到线上。 +在加载好之前记录的log之后,调用 ``update`` 函数进行更新,然后通过 ``save_inference_model`` 和 ``save_solver`` 函数保存更新后的参数到本地,推送到线上。 .. code-block:: c++ + std::shared_ptr agent = std::make_shared(); + agent->load_config(FLAGS_config_path); + agent->load_inference_model(FLAGS_model_dir); + agent->load_solver(FLAGS_model_dir); + // load training data - std::vector noisy_keys; - std::vector noisy_rewards(ITER, 0.0f); - noisy_keys.resize(ITER); + std::vector sampling_infos; + std::vector rewards(ITER, 0.0f); + sampling_infos.resize(ITER); std::ifstream log_stream(FLAGS_log_path); CHECK(log_stream.good()) << "[EvoKit] cannot open log: " << FLAGS_log_path; char buffer[1000]; for (int i = 0; i < ITER; ++i) { int size; - log_stream.read((char*) &noisy_rewards[i], sizeof(float)); + log_stream.read((char*) &rewards[i], sizeof(float)); log_stream.read((char*) &size, sizeof(int)); log_stream.read(buffer, size); buffer[size] = 0; std::string data(buffer); - noisy_keys[i].ParseFromString(data); + sampling_infos[i].ParseFromString(data); } // update model and save parameter - agent->update(noisy_keys, noisy_rewards); + agent->update(sampling_infos, rewards); agent->save_inference_model(FLAGS_updated_model_dir); + agent->save_solver(FLAGS_updated_model_dir); + + +主代码 +----------------------- + +将以上代码分别编译成可执行文件。 + +- 初始化solver: ``init_solver`` 。 +- 线上采样: ``online_sampling`` 。 +- 线下更新: ``offline update`` 。 + +.. code-block:: shell + + #------------------------init solver------------------------ + ./init_solver \ + --model_dir="./model_warehouse/model_dir_0" \ + --config_path="config.prototxt" + + + for ((epoch=0;epoch<200;++epoch));do + #------------------------online sampling------------------------ + ./online_sampling \ + --log_path="./sampling_log" \ + --model_dir="./model_warehouse/model_dir_$epoch" \ + --config_path="./config.prototxt" + + #------------------------offline update------------------------ + next_epoch=$((epoch+1)) + ./offline_update \ + --log_path='./sampling_log' \ + --model_dir="./model_warehouse/model_dir_$epoch" \ + --updated_model_dir="./model_warehouse/model_dir_${next_epoch}" \ + --config_path="./config.prototxt" + done