强化学习ROS实战-RLagent解析(下)

来源:互联网 发布:软件开发分录 编辑:程序博客网 时间:2024/04/29 22:10

2.深入了解Agent

2.1.agent整体架构

由于统一接口的需要,ROS端的agent接口全部可以由agent.cpp提供交互,其中提供了可以由命令行参数修改的各种引擎配置。其自带引擎有:

  • DiscretizationAgent
  • QLearner
  • ModelBasedAgent
  • SavedPolicy
  • Dyna
  • Sarsa

接口提供了两个publisher,分别为

  • out_rl_action 发布由引擎决策的下一部动作 发布在topic rl_agent/rl_action 消息类型为RLAction
  • out_exp_infp 在每一个周期结束时候发布训练信息 发布在topic rl_agent/rl_experiment_info 消息类型为RLExperimentInfo

三个subscriber,分别为:

  • rl_description 获取环境基本信息 订阅rl_env/rl_env_description 消息类型为 RLEnvDescription
  • rl_state 获取当前环境的状态 订阅rl_env/rl_state_reward
  • rl_seed 订阅rl_env/rl_seed

processEnvDescription:
初始化引擎中的环境信息,一般所有状态的价值都先假定初始为0
重置迭代状态,episode_number和episode_reward为0
设置firstAction标记为true

processState:
输入环境反馈的当前状态,返回一个决策动作
如果当前动作为第一个动作,则调用引擎first_action方法直接反馈决策
若当前动作不为第一个
累加状态收益
如果当前状态为结束状态,则调用last_action方法输出动作,并输出该次迭代结果
否则 调用next_action 并发布下一步动作

processSeed:
提供一系列的经验 用来初始化引擎。

2.2.SARSA Agent

了解大概的架构之后,让我们再深入对一个引擎进行了解。在这里,先选择SARSA算法的实现作为例子。在之前的文章已经介绍过了,sara是一种在线时间差分控制算法。他在状态转换是一个马尔科夫过程的前提下,通过单个的状态经验进行动作-状态价值函数的估计并决定最佳动作。

2.2.1.sarsa 引擎结构

SARSA引擎的初始化定义: Sarsa(int numactions, float gamma,
float initialvalue, float alpha, float epsilon, float lambda,
Random rng = Random())
参数列表及其解释:

  • \param numactions 可能采取的动作的数目
  • \param gamma 衰减系数(0~1)
  • \param initialvalue 每个动作收益评价Q(s,a)的初始值
  • \param alpha 学习率
  • \param epsilon 采取随机策略的概率
  • \param rng 随机数种子

提供方法:
virtual int first_action(const std::vector &s);
引擎初始时刻调用,输入初始状态,输出决策动作
virtual int next_action(float r, const std::vector &s);
引擎工作时刻调用,输入奖励和状态s,输出决策动作
virtual void last_action(float r);
状态链结束时刻调用,输出最终动作
virtual void setDebug(bool d);
debug模式标记
virtual void seedExp(std::vector);
预训练模型框架
virtual void savePolicy(const char* filename);
存储策略
void printState(const std::vector &s);
打印当前状态
float getValue(std::vector state);
获得当前状态的收益值

2.2.2.状态定义

首先需要解决的是状态量化问题,在很多问题中状态的数目可能是无穷多个的,但是计算机没有办法处理无穷个状态,所以需要想办法把状态量化到有穷单位上面去。
SARSA agent用了一个set表达所有遇到过的状态,

std::set<std::vector<float> > statespace;

并且提供了的Sarsa::state_t Sarsa::canonicalize(const std::vector &s) 函数来维护状态的数目,的设计哲学在于:只维护已知的状态,也就是说只在状态列表里面管理出现过的状态。由此状态-动作函数的Q矩阵和追踪迹矩阵eligible也相应的只维护出现过的状态,在出现新状态的时候会对map做插入处理。

Sarsa::state_t Sarsa::canonicalize(const std::vector<float> &s) {    const std::pair<std::set<std::vector<float> >::iterator, bool> result =    statespace.insert(s);    state_t retval = &*result.first; // Dereference iterator then get pointer     if (result.second) { // s is new, so initialize Q(s,a) for all a    std::vector<float> &Q_s = Q[retval];    Q_s.resize(numactions,initialvalue);    std::vector<float> &elig = eligibility[retval];    elig.resize(numactions,0);    }    return retval; }

接下来就是对于状态-动作函数的Q矩阵以及状态-动作概率的eligible矩阵的维护。
SARSA使用一个map std::map<statet,std::vector<float>> eligibility来定义不同状态对应动作的转移概率,其中state_t为一个float的向量,描述当前的状态,后一个映射的向量长度为所有动作的数目,其中数值代表采取不同行动的概率。
同理,另一个map std::map<statet,std::vector<float>> Q以表征不同状态下选取不同动作的收益。
这两个结构初始化的时候都是0,并且在前面也提到过,只有在新状态加入的时候才进行结构的扩充和初始化,从而节约存储资源。

2.2.3.状态维护

Q矩阵和eligible矩阵的更新其实是随着经验和动作决策一起进行的,主要在int Sarsa::next_action(float r, const std::vector &s)函数中进行。这个函数主要做了两件事,一件当然就是判断当前状态不同动作的价值,并作出正确动作选择。另一个当然就是动作的更新了。
动作的更新过程是随着得到的状态和收益来的,这里需要解决这个几个问题:

  • 了解上一个状态是什么(在ROS实现里实际上考虑了好几个之前的状态并附加权重)
  • 了解现在状态的收益

让我们从next-action函数的一段来分析:

// Update value for all with positive eligibilityfor (std::map<state_t, std::vector<float> >::iterator i = eligibility.begin();   i != eligibility.end(); i++){state_t si = (*i).first;std::vector<float> & elig_s = (*i).second;for (int j = 0; j < numactions; j++){  if (elig_s[j] > 0.0){    if (ELIGDEBUG) {      cout << "updating state " << (*((*i).first))[0] << ", " << (*((*i).first))[1] << " act: " << j << " with elig: " << elig_s[j] << endl;    }    // update    Q[si][j] += alpha * elig_s[j] * (r + gamma * (*a) - Q[si][j]);    elig_s[j] *= lambda;  }}}// Set elig to 1eligibility[st][a-Q_s.begin()] = 1.0;

这段代码有两个比较值得注意的地方,其一是状体更新函数:
Q[si][j] += alpha * elig_s[j] * (r + gamma * (*a) - Q[si][j]) 这个是很典型的TD(λ)更新公式,前几个都好理解,但是那个elig_s[j]表达什么呢,这就是我们的第二点:追踪迹矩阵及其更新。
在之前的讨论中也发现了,想要估算某个状态的价值,基于马尔科夫假设必须知道其前几个状态是什么,elig矩阵正是为了记录这个而存在的。我们可以看到,在每次状态更新的时候,都会把不为0的元素做elig_s[j] *= lambda这是为了降低权重,也就是离这个状态越久远的状态,对其影响应该是越低的。而且,在每次更新完毕之后,都会把当前接收到状态的权置1,这样,当新状态前来更新的时候,这个状态就是“上一个状态”了,并且此时它的权值是最高的。

2.2.3.动作维护

动作决策使用的是εgreedy策略,这个策略很简单,就是以一定概率在每次决策的时候“随机”选择一个动作,在其他时刻按照价值最高的动作来决策

// Choose an actionconst std::vector<float>::iterator a =rng.uniform() < epsilon? Q_s.begin() + rng.uniformDiscrete(0, numactions - 1): max;

2.3.Q-learing

Q-learing引擎的初始化定义,参数列表和sarsa的意义一致,这样,也就是数据更新的方式不一样咯。所以有兴趣的请自己参阅代码就好啦。

2.深入了解Enviroment

呃。。。这个因为和实际情况贴合比较多,所以就不详细讲了,比较值得在意的一点就是动作的reward和状态的reward会叠加在在一起返回而已。

0 0
原创粉丝点击