蒙特卡洛方法的定义
蒙特卡洛方法(Monte-Carlo methods)也被称为统计模拟 方法,是一种基于概率统计的数值计算方法,它利用随机采样、样本均值估计以逼近真实值。
比如要**"爱心圆"**的计算面积

用蒙特卡洛方法是把它放到正方形 里面,正方形 内部随机产生若干个点,统计落在爱心圆 内的点数和落在正方形 内点数,爱心圆 / 正方形的点数 == 圆面积 / 正方形面积
如果我们随机产生的点的数量越多,计算得到爱心圆面积就越接近于它真实的面积。蒙特卡洛方法也可以用来解悬崖漫步的问题,属于无模型的强化学习。
无模型的强化学习
强化学习算法分为两种:
- 基于模型的强化学习: 对环境的状态转移概率和奖励函数进行建模
- 无模型的强化学习: 根据智能体与环境交互到的采样数据
以悬崖漫步为例, 环境只要知道网格数量几行几列,在当前状态下采取动作的反馈(一下个状态,奖励,是否结束),并存储简称Q表, 动作价值函数 采样得到的数据。
环境初始化代码:
cpp
CliffWalkingEnv::CliffWalkingEnv(int r, int c)
{
m_nCol = c;
m_nRow = r;
}
///重置
int CliffWalkingEnv::Reset()
{
m_nx = 0;
m_ny = 0;
return 0;
}
动作反馈代码:
cpp
StateInfo CliffWalkingEnv::Step(int action)
{
auto p = GetActionNextPos(m_nx, m_ny, action);
m_nx = p.first;
m_ny = p.second;
int idx = m_ny * m_nCol + m_nx;
double done = 0;
double reward = -1;
if (0 < idx && idx < m_nCol)
{
reward = -100;
done = 1;
if (idx == (m_nCol - 1))
{
reward = 1;
}
}
return { 1, idx, reward, done };
}
贪心算法
- **利用(exploitation):**根据已知数据情况下选择期望奖励最大的动作。
- **探索(exploration):**尝试更多的可能选择,这个动作不一定获得最大的奖励。
贪心算法 是一种比较经典的一种方法完全贪婪算法的基础上添加了噪声,每次以概率
选择以往经验中期望奖励估值最大的动作(利用 ),以概率
随机选择一个动作(探索)
贪心算法代码
cpp
int FreeModel::TakeAction(int s0)
{
double x = m_rand01(m_gen);
int action = 0;
if (x < m_dbEpsilon)
{
action = m_rand04(m_gen);
}
else
{
const auto& row = m_2dQtable[s0];
action = std::max_element(row.begin(), row.end()) - row.begin();
}
return action;
}
设置为 **10% 探索, 90%**根据以往经验(Q表值)选择动作
蒙特卡洛实现悬崖漫步
-
设置最大采样次数1500轮次,网格数量设置为4行8列
-
动作选择用
贪心算法,从起点开始随机选择动作(上下左右)到结束(跳入悬崖或终点)视一轮,记录经过轨迹 trajectory
-
从轨迹最后开始 计算每个状态
奖励总和,对每一条轨迹状态 更新计数器
和
表
-
更新计数器
有两种方式:一条轨迹只记录一次和访问状态都要记录
cpp#if 0 v2dCount[s][a] += 1; m_2dQtable[s][a] += (G - m_2dQtable[s][a]) / v2dCount[s][a]; #else // 一条轨迹只记录一次 auto key = make_tuple(s, a); if (visited.find(key) == visited.end()) { visited.insert(key); v2dCount[s][a] += 1; m_2dQtable[s][a] += (G - m_2dQtable[s][a]) / v2dCount[s][a]; } #endif -
样本均值更新
表 公式
-
蒙特卡洛实现代码
cppvoid FreeModel::MonteCarloMethods(int maxCount) { cout << "\n蒙特卡洛方法" << endl; VectorDouble2D v2dCount; auto S = m_objEnv.GetTableSize(); v2dCount.resize(S, std::vector<double>(MaxAction, 0.0)); m_2dQtable.resize(S, std::vector<double>(MaxAction, 0.0)); for (size_t i = 0; i < maxCount; i++) { vector<MonteCarlo> trajectory; auto s = m_objEnv.Reset(); auto a = TakeAction(s); bool b = true; while (b) { auto info = m_objEnv.Step(a); // { 1, idx, reward, done }; b = GetTuple(3, info) == 0; auto s1 = GetTuple(1, info); auto r = GetTuple(2, info); auto a1 = TakeAction(s1); MonteCarlo item; item.s = s; item.a = a; item.r = r; item.done = !b; //trajectory.push_back(item); trajectory.emplace_back(item); a = a1; s = s1; } double G = 0.0; set<tuple<int, int>> visited; for (int n = trajectory.size()-1; 0<= n; n--) { G = trajectory[n].r + m_dbGamma * G; int s = trajectory[n].s; int a = trajectory[n].a; #if 0 v2dCount[s][a] += 1; m_2dQtable[s][a] += (G - m_2dQtable[s][a]) / v2dCount[s][a]; #else auto key = make_tuple(s, a); if (visited.find(key) == visited.end()) { visited.insert(key); v2dCount[s][a] += 1; m_2dQtable[s][a] += (G - m_2dQtable[s][a]) / v2dCount[s][a]; } #endif } } PrintPi(); }
打印线路 先将表转换成
状态价值表,策略 根据
状态价值表打印路线,直接用
打印策略 也是可以的,个人认为用
表更好一些。
cpp
void FreeModel::PrintPi()
{
cout << endl;
vector<string> action;
action.push_back("↑");
action.push_back("↓");
action.push_back("←");
action.push_back("→");
auto R = m_objEnv.GetRow();
auto C = m_objEnv.GetCol();
vector<double> vecValue(R*C, 0);
ActionList m2dPI;
m2dPI.resize(R * C, { 0,0,0,0 });
for (int r = 0; r < R; r++)
{
for (size_t c = 0; c < C; c++)
{
int idx = r * C + c;
if (0 < idx && idx < C - 1)
{
vecValue[idx] = -100;
cout << " **** ";
}
else if (idx == C - 1)
{
vecValue[idx] = 100;
cout << " EEEE ";
}
else
{
const auto& row = m_2dQtable[idx];
auto maxIdx = std::max_element(row.begin(), row.end());
auto v = *maxIdx;
vecValue[idx] = v;
cout << setw(4) << fixed << setprecision(2) << v << " ";
}
}
cout << endl;
}
cout << endl;
for (int r = 0; r < R; r++)
{
for (int c = 0; c < C; c++)
{
int idx = r * C + c;
UpdatePi(idx, m2dPI,vecValue);
if (0 < idx && idx < C - 1)
{
cout << " **** ";
}
else if (idx == C - 1)
{
cout << " EEEE ";
}
else
{
for (int i = 0; i < MaxAction; i++)
{
if (0 < GetTuple(i, m2dPI[idx]))
{
cout << action[i];
}
else
{
cout << "o";
}
}
cout << " ";
}
}
cout << endl;
}
}
void FreeModel::UpdatePi(int idx, ActionList& m2dPI, const vector<double>& vecValue)
{
double d = -999;
for (int i=0;i<MaxAction;i++)
{
auto idx1 = GetActionNextPos(idx,i);
if (idx1 != idx)
{
d = max(vecValue[idx1], d);
}
}
for (int i = 0; i < MaxAction; i++)
{
auto idx1 = GetActionNextPos(idx, i);
if (vecValue[idx1] == d)
{
SetTuple(i, 1, m2dPI[idx]);
}
}
}
因为是随机采样数据运行花点时间, 运行结果

感谢大家的支持。