AI 基础知识二十一 强化学习.蒙特卡洛算法

蒙特卡洛方法的定义

蒙特卡洛方法(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表值)选择动作

蒙特卡洛实现悬崖漫步

  1. 设置最大采样次数1500轮次,网格数量设置为4行8列

  2. 动作选择用贪心算法,从起点开始随机选择动作(上下左右)到结束(跳入悬崖或终点)视一轮,记录经过轨迹 trajectory

  3. 从轨迹最后开始 计算每个状态奖励总和,对每一条轨迹状态 更新计数器

  4. 更新计数器 有两种方式:一条轨迹只记录一次和访问状态都要记录

    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
  5. 样本均值更新表 公式

  6. 蒙特卡洛实现代码

    cpp 复制代码
    void 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]);
		}
	}

}

因为是随机采样数据运行花点时间, 运行结果


感谢大家的支持。