强化学习——不完全观测问题、MCTS

前言

本文的知识点总结自《深度学习强化学习》,如有错误,欢迎指出


不完全观测问题

前面几篇博客介绍的强化学习算法,智能体都可以从环境中获知总体状态,类似于MOBA类游戏的观战系统,可以观测到地图上双方的动向。然而在某些场景下,智能体只能从环境中获知局部状态,例如王者荣耀中,视野机制导致每位玩家仅能看到整个地图中的一小部分状态。

t t t时刻智能体观测到的状态为 o t o_t ot,我们可以仅依据 o t o_t ot让策略网络做出决策,但这么做效果通常不佳。一个更好的策略是让策略网络依据过去 t t t个时刻观测到的状态进行预测,即策略网络的输入为 [ o 1 、 o 2 、 . . . 、 o t ] [o_1、o_2、...、o_t] [o1o2...ot]这一序列。可以使用RNN、LSTM、Transformer等模型处理此类序列化数据。

在这里插入图片描述

如上图所示,以RNN为例,观测到 n n n时刻的状态 o n o_n on时,将序列 [ o 1 、 o 2 、 . . . 、 o n ] [o_1、o_2、...、o_n] [o1o2...on]输入到 n n n个CNN中, n n n个CNN的输出在经过RNN的处理,RNN的第 n n n个输出输入到全连接网络中,全连接网络的输出智能体执行每个动作的概率


MCTS

MCTS(蒙特卡洛树搜索)是强化学习中一个很重要的方法,被应用于包括Alpha Go、Alpha Go Zero在内的诸多强化学习算法。MCTS的基本原理就是模拟未来可能发生的情况,从而找出最优的动作,可以看成是有智慧的枚举,这也意味着MCTS会消耗大量算力。

MCTS每次模拟都会选择一个动作,执行完这个动作后,把一局游戏进行到底,依据胜负来评价该动作的好坏。MCTS包括选择(Selection)、扩展(expansion)、模拟(Simulation)、回溯(Backup)四个步骤。值得一提的是,策略网络和价值网络会辅助MCTS的计算,在进行MCTS前,需要提前训练好策略网络与价值网络。


选择

假设动作空间中存在 n n n个动作,MCTS的“选择”步骤就是选出其中胜算较高的动作,其通过模拟计算出的动作价值以及策略网络 π ( a ∣ s ; θ ) \pi(a|s;\theta) π(as;θ)给出的动作评分(概率值)来评估动作 a a a的好坏。具体而言,其通过下式评价一个动作的好坏:
s c o r e ( a ) = Q ( a ) + α 1 + N ( a ) π ( a ∣ s ; θ ) (1.0) score(a)=Q(a)+\frac{\alpha}{1+N(a)}\pi(a|s;\theta)\tag{1.0} score(a)=Q(a)+1+N(a)απ(as;θ)(1.0)
其中

  • α \alpha α为超参数
  • N ( a ) N(a) N(a)表示动作 a a a被已经被选中的次数,初始阶段 N ( a ) = 0 N(a)=0 N(a)=0,动作 a a a每被选择一次, N ( a ) + 1 N(a)+1 N(a)+1
  • Q ( a ) Q(a) Q(a)表示之前 N ( a ) N(a) N(a)次模拟计算出的动作价值,主要由胜率和价值函数决定。动作 a a a每被选中一次,就会更新一次 Q ( a ) Q(a) Q(a)

当动作 a a a没被选择过时, Q ( a ) Q(a) Q(a) N ( a ) N(a) N(a)均为0,此时score(a)完全由策略网络决定。当动作 a a a被选择的次数很多时, Q ( a ) Q(a) Q(a)对score(a)的影响会越来越大,即score(a)的取值更依赖于依据过去模拟计算出的动作价值(即实际情况,而不是策略网络的预测)。系数 α 1 + N ( a ) \frac{\alpha}{1+N(a)} 1+N(a)α的一个作用是鼓励MCTS选择历史执行次数较少的动作,假设两个动作具有类似的 Q ( a ) Q(a) Q(a) π ( a ∣ s ; θ ) \pi(a|s;\theta) π(as;θ),则 N ( a ) N(a) N(a)较小的动作将被选中。

在选择阶段,MCTS选择式1.0取值最大的动作,让智能体在模拟器中执行该动作(不是实际场景)。


扩展

智能体在模拟器中执行完第一步“选择”中选中的动作后,通过扩展阶段模拟对手做出的下一步动作。MCTS可以通过提前训练好的策略网络 π ( a ∣ s t ; θ ) \pi(a|s_t;\theta) π(ast;θ)模拟对手,根据策略网络的输出概率抽取动作。值得一提的是,MCTS在模拟器中执行完动作后,模拟器需要返回一个新的状态,因此模拟器需要模拟实际的状态转移函数。对于大多数问题,例如无人驾驶等,模拟实际的状态转移函数相当困难。但是对于MOBA类游戏、围棋等较为简单的问题,对手做出的动作后,棋盘、游戏对局的情况就是模拟器中的下一个状态,因此只需要训练一个足够模拟实际情况的策略网络即可模拟状态转移函数。


模拟

“选择”和“扩展”阶段,智能体本身和对手都选择了一个动作,并在模拟器中执行,得到新的状态 s t + 1 s_{t+1} st+1。而在模拟阶段,双方将通过提前训练好的两个策略网络模拟后续的对局情况,依据后续的对局情况计算状态 s t + 1 s_{t+1} st+1的价值。以围棋为例,AlphaGo在这个阶段将通过两个策略网络模拟对局,直到对局结束。如果己方获胜,那么奖励 r r r的取值为1,反之则为-1。此外,AlphaGo引入了提前训练好的价值网络 V ( s ; θ ) V(s;\theta) V(s;θ),用于评价状态 s t + 1 s_{t+1} st+1的优劣。AlphaGo对状态 s t + 1 s_{t+1} st+1的评价 v ( s t + 1 ) v(s_{t+1}) v(st+1)为:

v ( s t + 1 ) = r + V ( s t + 1 ; θ ) 2 v(s_{t+1})=\frac{r+V(s_{t+1};\theta)}{2} v(st+1)=2r+V(st+1;θ)
由于依据策略网络输出的概率进行抽样,因此在状态 s t + 1 s_{t+1} st+1上每次模拟执行的动作可能有所不同。MCTS在状态 s t + 1 s_{t+1} st+1上的模拟将会进行多次,如下图所示,一个状态 s t + 1 s_{t+1} st+1含有多个评价。
在这里插入图片描述

回溯

依据上一节的内容,第 t t t步执行的动作 a t a_t at下含有多个关于状态的记录,这些状态的平局值即为式1.0中的 Q ( a ) Q(a) Q(a)。这个数值将用于下一轮迭代时计算式1.0(上述四个流程构成MCTS的一次迭代,MCTS会重复上述四个步骤多次)。


决策

在进行成千上万次迭代模拟后,MCTS选择在状态 t t t时执行次数最多的动作,即 a = arg max ⁡ a N ( a ) a=\argmax_a N(a) a=aargmaxN(a)。看完MCTS的原理后,不难理解为什么AlphaGo和AlphaGo Zero能赢人类,其实就是靠算力暴力碾压。

价值网络与策略网络的训练

前面提到过,MCTS需要使用提前训练好的价值网络和策略网络辅助。本小节将总结AlphaGo和AlphaGo Zero中训练两个网络的方式。

AlphaGo

AlphaGo训练策略网络的流程为

  • 使用监督数据训练策略网络 π ( a ∣ s ; θ ) \pi(a|s;\theta) π(as;θ)
  • 设第一步完全训练完的策略网络为 π ( a ∣ s ; θ n o w ) \pi(a|s;\theta_{now}) π(as;θnow),随机选择一份训练时的策略网络 π ( a ∣ s : θ o l d ) \pi(a|s:\theta_{old}) π(as:θold)(参数固定)。让两个策略网络模拟对局,若 π ( a ∣ s ; θ n o w ) \pi(a|s;\theta_{now}) π(as;θnow)胜利,则回报 u u u为1,否则为-1。接着使用REINFORCE更新策略网络 π ( a ∣ s ; θ n o w ) \pi(a|s;\theta_{now}) π(as;θnow)的参数,重复上述过程直至收敛。

依据上述过程,可得一系列的状态数组 ( s t , u t ) (s_t,u_t) (st,ut),利用上述数据,让价值网络 v ( s ; θ ) v(s;\theta) v(s;θ)进行监督学习,损失函数为MSE:
L ( θ ) = 1 N ∑ t = 1 N [ v ( s t ; θ ) − u t ] 2 L(\theta)=\frac{1}{N}\sum_{t=1}^N[v(s_t;\theta)-u_t]^2 L(θ)=N1t=1N[v(st;θ)ut]2

AlphaGo Zero

AlphaGo Zero利用提前准备好的MCTS(例如AlphaGo)模拟两个玩家的对弈情况。基于当前的状态 s t s_t st,MCTS经过多次模拟后,可以得到 n n n个动作的执行次数 N ( 1 ) 、 N ( 2 ) 、 . . . 、 N ( n ) N(1)、N(2)、...、N(n) N(1)N(2)...N(n),对其进行归一化得到 p t p_t pt,即 p t = n o r m a l i z e ( [ N ( 1 ) , N ( 2 ) , . . . , N ( n ) ] ) p_t=normalize([N(1),N(2),...,N(n)]) pt=normalize([N(1),N(2),...,N(n)])
设MCTS模拟的两个玩家经过 m m m个步骤后结束一次对局,可以分别得到两个玩家的一系列轨迹
( s 1 , p 1 , u 1 ) 、 ( s 2 , p 2 , u 2 ) 、 . . . 、 ( s m , p m , u m ) (s_1,p_1,u_1)、(s_2,p_2,u_2)、...、(s_m,p_m,u_m) (s1,p1,u1)(s2,p2,u2)...(sm,pm,um)
对于胜者,回报 u 1 = u 2 = . . . = u m = 1 u_1=u_2=...=u_m=1 u1=u2=...=um=1。对于败者,回报 u 1 = u 2 = . . . = u m = − 1 u_1=u_2=...=u_m=-1 u1=u2=...=um=1,利用上述数据更新策略网络和价值网络(注意对于胜者和败者,轨迹数据中的 s s s p p p是不同的)

对于策略网络 π ( a ∣ s t ; θ ) \pi(a|s_t;\theta) π(ast;θ),其使用监督学习训练,设H(x,y)为交叉熵损失,则损失函数为
L ( θ ) = 1 N ∑ i = 1 N H ( p i , π ( a ∣ s i ; θ ) ) L(\theta)=\frac{1}{N}\sum_{i=1}^NH(p_i,\pi(a|s_i;\theta)) L(θ)=N1i=1NH(pi,π(asi;θ))

更新价值网络的方式与AlphaGo一致。具体的训练流程就是重复下列三个步骤直至算法收敛:

  1. 让MCTS自我博弈,完成一局游戏,收集到m个三元组 ( s 1 , p 1 , u 1 ) 、 ( s 2 , p 2 , u 2 ) 、 . . . 、 ( s m , p m , u m ) (s_1,p_1,u_1)、(s_2,p_2,u_2)、...、(s_m,p_m,u_m) (s1,p1,u1)(s2,p2,u2)...(sm,pm,um)
  2. 依据上述三元组,更新策略网络参数
  3. 依据上述三元组,更新价值网络参数

由此可得DeepMind是土豪。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【资源说明】 基于深度强化学习的云工作流调度python源码+详细注释+项目说明(毕业设计).zip 关键词:有向无环图,工作流,深度强化学习,图神经网络; 蒙特卡洛树搜索 Python 3.9.7 - torch 1.10 - gym 0.21.0 - networkx 2.6.3 1. 将 Env/___.py 文件注册在gym的环境中; 2. 运行 DAGs_generator.py 生成 train dataset, test dataset. 3. 修改环境代码适应生成数据集的路径; 4. 运行 PPO/DRLagent.py 训练网络; 5. 运行 PPO/DRLtest.py 测试推理。 5. 运行baseline_tableau.py 得出baseline算法效果 5. 运行MonteCarloTreeSearch.py 得出MCTS算法效果 马尔可夫决策过程建模 ### 状态空间 1. 当前执行的时间(1维) 2. 当前资源中剩余的 CPU 资源(1维) 3. 当前资源中剩余的 Memory 资源(1维) 4. Ready_task 任务列表(长度为 10)中的任务要求时间(30维)如果不足30空位补-1 5. Ready_task 任务列表中的 CPU 要求资源(30维) 如果不足30空位补-1 6. Ready_task 任务列表中的 Memory 要求资源(30维) 如果不足30空位补-1 7. 当前未完成 DAG 部分的最大路径长度(1维) 8. 当前未完成 DAG 部分的子节点数(1维) 9. 超出 Ready_task 任务列表长度且准备好的任务的时间要求总和(1维) 10. 超出 Ready_task 任务列表长度且准备好的任务的 CPU 要求总和(1维) 11. 超出 Ready_task 任务列表长度且准备好的任务的 Memory 要求总和(1维) 共98维状态。 更多项目细节及介绍,请见资源中的项目说明文档。 参数设定为: size = [20,30,40,50,60,70,80,90] #DAG中任务的数量 max_out = [1,2,3,4,5] #DAG节点的最大出度 alpha = [0.5,1.0,1.5] #控制DAG 的形状 beta = [0.0,0.5,1.0,2.0] #控制 DAG 的规则度 具体的实现细节如下: 1. 根据公式$length=\frac{\sqrt{size}}{alpha}$计算出生成DAG的层数,并计算平均每层的数量$\frac{size}{length}$. 2. 在以均值为$\frac{size}{length}$,标准差为$beta$的正态分布中采样每层的任务数并向上取整,这样随机采样得到的总任务数可能有偏差,随机在某几层添加或者删除任务,使DAG总任务数等于$size$。 3. 对于第一层到倒数第二层:每一个任务随机在[0, $max\_out]$,并随机连接$n$个下一层的任务。 4. 最后给所有没有入边的任务添加Start作为父节点,所有没有出边的任务添加Exit任务作为子节点,至此一个随机的DAG就生成好了。 在这里$alpha$控制的是DAG的整体形状,如果$alpha$越小,DAG越长,$alpha$越大,DAG越短。 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
以下是一个基于C语言的五子棋蒙特卡洛树搜索(MCTS)示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #define BOARD_SIZE 15 #define MAX_PLAYS 225 #define UCB_C 1.96 typedef struct { int x; int y; } Point; typedef struct Node Node; struct Node { int wins; int plays; Point move; Node *parent; Node *children[MAX_PLAYS]; }; typedef struct { int board[BOARD_SIZE][BOARD_SIZE]; int next_player; } GameState; int opposite(int player) { return 3 - player; } int legal_moves(GameState *state, Point moves[]) { int count = 0; for (int x = 0; x < BOARD_SIZE; x++) { for (int y = 0; y < BOARD_SIZE; y++) { if (state->board[x][y] == 0) { moves[count].x = x; moves[count].y = y; count++; } } } return count; } int winner(GameState *state) { int lines[BOARD_SIZE][BOARD_SIZE][5][2] = {{{{0}}}}; int counts[3] = {0}; for (int x = 0; x < BOARD_SIZE; x++) { for (int y = 0; y < BOARD_SIZE; y++) { int p = state->board[x][y]; if (p == 0) { continue; } for (int i = 0; i < 5; i++) { if (x + i < BOARD_SIZE) { lines[x][y][i][0] = state->board[x + i][y]; } if (y + i < BOARD_SIZE) { lines[x][y][i][1] = state->board[x][y + i]; } } counts[p]++; } } if (counts[1] > counts[2] + 1) { return 2; } if (counts[2] > counts[1]) { return 1; } for (int x = 0; x < BOARD_SIZE; x++) { for (int y = 0; y < BOARD_SIZE; y++) { for (int i = 0; i < 5; i++) { int j; for (j = 0; j < 5; j++) { if (lines[x][y][j][0] != (i + j < 5 ? 0 : lines[x + j - i][y][i + j - 5][0])) { break; } } if (j == 5) { return lines[x][y][0][0]; } for (j = 0; j < 5; j++) { if (lines[x][y][j][1] != (i + j < 5 ? 0 : lines[x][y + j - i][i + j - 5][1])) { break; } } if (j == 5) { return lines[x][y][0][1]; } } } } if (counts[1] + counts[2] == BOARD_SIZE * BOARD_SIZE) { return 0; } return -1; } void play_move(GameState *state, Point move) { state->board[move.x][move.y] = state->next_player; state->next_player = opposite(state->next_player); } Node *new_node(Node *parent, Point move) { Node *node = (Node *) malloc(sizeof(Node)); node->wins = 0; node->plays = 0; node->move = move; node->parent = parent; for (int i = 0; i < MAX_PLAYS; i++) { node->children[i] = NULL; } return node; } void free_tree(Node *node) { for (int i = 0; i < MAX_PLAYS; i++) { if (node->children[i] != NULL) { free_tree(node->children[i]); } } free(node); } int random_playout(GameState *state) { GameState *copy = (GameState *) malloc(sizeof(GameState)); *copy = *state; int result = -1; while (result == -1) { Point moves[MAX_PLAYS]; int count = legal_moves(copy, moves); if (count == 0) { break; } Point move = moves[rand() % count]; play_move(copy, move); result = winner(copy); } free(copy); if (result == -1) { return 0; } if (result == 0) { return 1; } return result == 1 ? -1 : 1; } Node *best_child(Node *node) { double best_score = -1; Node *best_child = NULL; for (int i = 0; i < MAX_PLAYS; i++) { Node *child = node->children[i]; if (child == NULL) { continue; } double score = (double) child->wins / child->plays + UCB_C * sqrt(log(node->plays) / child->plays); if (score > best_score) { best_score = score; best_child = child; } } return best_child; } Node *tree_policy(GameState *state, Node *node) { while (winner(state) == -1) { Point moves[MAX_PLAYS]; int count = legal_moves(state, moves); if (count == 0) { break; } int unexplored = 0; for (int i = 0; i < count; i++) { Point move = moves[i]; int found = 0; for (int j = 0; j < MAX_PLAYS; j++) { if (node->children[j] != NULL && node->children[j]->move.x == move.x && node->children[j]->move.y == move.y) { node = node->children[j]; found = 1; break; } } if (!found) { unexplored = 1; break; } } if (unexplored) { Point move = moves[rand() % count]; node->children[node->plays] = new_node(node, move); node = node->children[node->plays]; } else { node = best_child(node); } play_move(state, node->move); node->plays++; } return node; } void backpropagate(Node *node, int result) { while (node != NULL) { node->wins += result; node->plays++; node = node->parent; result = -result; } } Point select_move(GameState *state, int iterations) { Node *root = new_node(NULL, (Point) {0, 0}); root->plays = 1; for (int i = 0; i < iterations; i++) { GameState *copy = (GameState *) malloc(sizeof(GameState)); *copy = *state; Node *node = tree_policy(copy, root); int result = random_playout(copy); backpropagate(node, result); free(copy); } double best_score = -1; Point best_move = (Point) {-1, -1}; for (int i = 0; i < MAX_PLAYS; i++) { Node *child = root->children[i]; if (child == NULL) { continue; } double score = (double) child->wins / child->plays; if (score > best_score) { best_score = score; best_move = child->move; } } free_tree(root); return best_move; } void print_board(GameState *state) { for (int y = 0; y < BOARD_SIZE; y++) { for (int x = 0; x < BOARD_SIZE; x++) { printf("%c", state->board[x][y] == 1 ? 'X' : state->board[x][y] == 2 ? 'O' : '-'); } printf("\n"); } } int main() { srand(time(NULL)); GameState state = {0}; while (winner(&state) == -1) { print_board(&state); if (state.next_player == 1) { Point move; printf("Enter move: "); scanf("%d %d", &move.x, &move.y); play_move(&state, move); } else { Point move = select_move(&state, 10000); printf("Computer plays: %d %d\n", move.x, move.y); play_move(&state, move); } } print_board(&state); int w = winner(&state); printf("%s wins\n", w == 0 ? "Draw" : w == 1 ? "X" : "O"); return 0; } ``` 该示例代码实现了五子棋蒙特卡洛树搜索,包括游戏状态表示、棋盘的打印、对局的进行(玩家输入和AI自动选择)、胜负判断等功能。其核心是通过蒙特卡洛树搜索来选择AI的下一步棋子位置,具体实现包括: - `new_node`:创建一个新的节点。 - `legal_moves`:获取当前状态下所有合法的落子位置。 - `winner`:判断当前状态下的胜负情况。 - `play_move`:在当前状态下落子。 - `random_playout`:执行一次随机模拟,返回胜负结果。 - `best_child`:找到当前节点中最优的子节点。 - `tree_policy`:根据当前状态和节点,选择下一个要扩展的节点,并返回最终的节点。 - `backpropagate`:在树中回溯更新节点的胜负统计信息。 - `select_move`:根据当前状态,执行多次蒙特卡洛树搜索,返回AI的落子位置。 该示例代码使用了UCB算法来计算节点选择的得分,即使用节点的胜率和置信度上限来决定节点的优先级。经过大量的模拟对局,蒙特卡洛树搜索可以搜索到更深的状态空间,从而提高AI的胜率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值