尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RL强化学习从小白到老鸟(一)——速通贪吃蛇游戏

RL强化学习从小白到老鸟(一)——速通贪吃蛇游戏 RL强化学习从小白到老鸟(一)——速通贪吃蛇游戏第二篇手撕 GPT零基础保姆级教学第三篇让贪吃蛇训练更稳定、更容易复现简介很久没有更新了刚好最近在带新人学习强化学习相关的内容就写一个教学贴吧。游戏用的是经典的贪吃蛇游戏因为是为了教学就没有设计特别花哨的背景音乐了纯python代码手撸出来的想要加音乐和图片的可以自己改进废话不多说直接整活我发布的所有文章均是免费的如果变成VIP了那一定是平台干的请私聊我修改设置为全部可见环境python3.9 (都2024了至少也要3.9了吧)依赖库 pygame gym pytorch numpy (pytorch带不带cuda,本项目都能跑代码中自动适配了cpu和cuda)Windows 系统不然可视化不方便哈代码支持在linux训练介绍一下用到的算法–SAC用过SAC(soft-actor-critic)算法的同学可以直接跳过本节看后续。SACSoft Actor-Critic算法是一种基于策略的强化学习算法属于Actor-Critic方法的一种变体。它由Tuomas Haarnoja等人于2018年提出旨在解决连续动作空间中的强化学习问题。SAC算法结合了最大熵强化学习Maximum Entropy Reinforcement Learning和确定性策略梯度Deterministic Policy Gradient的思想通过最大化策略的期望回报和策略的熵来实现探索和利用的平衡。算法简介SAC算法的核心思想是在传统的Actor-Critic框架中引入最大熵目标即在优化策略的同时鼓励策略产生多样化的动作以增加探索。这通过在目标函数中添加策略的熵项来实现使得策略不仅追求高回报还追求高不确定性。SAC算法的主要组成部分包括Actor策略网络负责根据当前状态选择动作。在SAC中Actor输出动作的概率分布而不是确定性动作。Critic价值网络评估在给定状态下采取某个动作的价值。SAC通常使用两个独立的Critic网络来减少估计误差。目标函数SAC的目标函数是期望回报和策略熵的加权和即最大化以下目标[ J(\pi) \mathbb{E}{s_t \sim \rho\pi, a_t \sim \pi}[r(s_t, a_t) \alpha \pazocal{H}(\pi(\cdot|s_t))] ]其中( r(s_t, a_t) ) 是奖励函数(\rho_\pi) 是策略(\pi)下的状态分布(\pazocal{H}(\pi(\cdot|s_t))) 是策略在状态(s_t)下的熵(\alpha) 是温度参数控制熵的相对重要性。温度参数(\alpha)用于平衡期望回报和熵的权重。在训练过程中SAC会自动调整这个参数以确保策略既有足够的探索性又能获得高回报。算法作用SAC算法的主要作用和优势包括连续动作空间SAC专门设计用于处理连续动作空间这在机器人控制、游戏AI等领域非常常见。稳定性和样本效率SAC算法通常表现出较高的稳定性和样本效率这得益于其基于策略梯度的方法和最大熵目标的结合。探索与利用的平衡通过最大化策略的熵SAC能够在探索未知状态和利用已知信息之间找到平衡这有助于避免局部最优并提高学习效率。自适应温度调节SAC算法中的温度参数(\alpha)可以自适应调节这有助于算法在不同的环境中自动找到合适的探索-利用平衡点。多目标优化SAC通过同时优化期望回报和策略熵实现了多目标优化这有助于提高策略的鲁棒性和泛化能力。易于并行化SAC算法可以很容易地并行化通过在多个环境中同时运行策略来收集数据加快学习速度。应用领域SAC算法在多个领域都有应用包括但不限于机器人控制用于机器人的运动规划和控制如四足机器人行走、机械臂操作等。游戏AI在需要连续动作空间决策的游戏中如模拟赛车、模拟飞行等。自动驾驶用于车辆的轨迹规划和控制。金融交易在自动交易系统中用于生成交易策略。总之SAC算法是一种高效、稳定的强化学习算法特别适合处理连续动作空间的复杂决策问题。通过结合最大熵强化学习和Actor-Critic框架SAC在多个应用领域都展现出了优异的性能。项目逻辑代码逻辑主要分为三个部分网络模型环境设计训练参数游戏在二维平面上我们可以将游戏环境视为一个图像信息(环境信息二维矩阵加上实体类型可以视为一个灰度图信息)于是可以锁定模型为卷积网络更为适合处理空间上的变化情况。以下是网络模型的核心代码importtorchimporttorch.nnasnnclassConvActorCritic(nn.Module):def__init__(self,input_channels,output_dim,grid_size,lr1e-3,weight_decay1e-5):super(ConvActorCritic,self).__init__()self.conv18self.conv216self.feature_extractornn.Sequential(nn.Conv2d(input_channels,self.conv1,kernel_size3,stride1,padding1),nn.ReLU(),nn.Flatten())reduced_grid_sizegrid_size//1# 下采样后特征边缘缩小2倍self.actornn.Sequential(nn.Linear(self.conv1*reduced_grid_size*reduced_grid_size,output_dim),nn.Softmax(dim-1))self.criticnn.Linear(self.conv1*reduced_grid_size*reduced_grid_size,1)self.optimizertorch.optim.Adam(self.parameters(),lr,weight_decayweight_decay)defforward(self,x):featuresself.feature_extractor(x)action_probsself.actor(features)valueself.critic(features).squeeze(-1)returnaction_probs,value可以看到这里使用了一个非常小的卷积参数只用了一层卷积网络这将会训练收敛得比较快因为初始任务在一个10X10的网格中任务比较简单这里采用了Relu函数作为激活函数,帮助网络快速收敛这里有两个网络一个是actor一个是critic顾名思义一个是用于行动的网络一个是用于评估的网络这里采用了forward向前传播的算法返回动作和评估价值。环境的设计主要䢍两部一部分是强化学习训练需要用到的奖励函数设计逻辑一部分是游戏可视化的逻辑。先讲一下游戏可视化吧这一部分代码也是为了方便直观得感受训练的效果。defrender(self,modehuman,fps1000):ifnothasattr(self,screen):pygame.init()self.cell_size20self.screen_widthself.grid_size*self.cell_size self.screen_heightself.grid_size*self.cell_size self.screenpygame.display.set_mode((self.screen_width,self.screen_height))self.clockpygame.time.Clock()foreventinpygame.event.get():ifevent.typepygame.QUIT:pygame.quit()returnself.screen.fill((0,0,0))# 背景设为黑色# 绘制网格forxinrange(0,self.screen_width,self.cell_size):foryinrange(0,self.screen_height,self.cell_size):rectpygame.Rect(x,y,self.cell_size,self.cell_size)pygame.draw.rect(self.screen,(50,50,50),rect,1)# 网格颜色较深# 绘制食物food_x,food_yself.food_pos pygame.draw.rect(self.screen,(0,255,0),(food_x*self.cell_size,food_y*self.cell_size,self.cell_size,self.cell_size))# 绘制蛇# 蛇头head_color(255,0,0)# 蛇头颜色例如红色head_x,head_yself.snake[0]head_rect(head_x*self.cell_size,head_y*self.cell_size,self.cell_size,self.cell_size)self._draw_head(self.screen,head_rect,head_color)# 蛇身forsegmentinself.snake[1:]:x,ysegment pygame.draw.rect(self.screen,(255,0,0),(x*self.cell_size,y*self.cell_size,self.cell_size,self.cell_size))# 结束标志ifself.game_over:iflen(self.snake)self.end_score:stringYOU ARE WIN!else:stringYOU ARE LOST!ifmodehuman:print(string)# 显示WIN!fontpygame.font.Font(None,36)textfont.render(string,True,(255,255,255))text_recttext.get_rect(center(self.screen_width//2,self.screen_height//2))self.screen.blit(text,text_rect)# 停止游戏更新但保持渲染whileTrue:foreventinpygame.event.get():ifevent.typepygame.QUIT:pygame.quit()returnself.clock.tick(fps)# 控制帧率pygame.display.flip()else:pygame.display.flip()self.clock.tick(fps)# 控制帧率def_draw_head(self,screen,rect,color):# 绘制圆形蛇头pygame.draw.circle(screen,color,(rect[0]self.cell_size//2,rect[1]self.cell_size//2),self.cell_size//2)# 根据蛇头方向绘制眼睛eye_color(0,0,0)# 眼睛颜色例如黑色ifself.current_direction[0]0:# 向右eye_pos(rect[0]self.cell_size//4,rect[1]self.cell_size//4)elifself.current_direction[0]0:# 向左eye_pos(rect[0]3*self.cell_size//4,rect[1]self.cell_size//4)elifself.current_direction[1]0:# 向下eye_pos(rect[0]self.cell_size//4,rect[1]3*self.cell_size//4)else:# 向上eye_pos(rect[0]3*self.cell_size//4,rect[1]3*self.cell_size//4)# 绘制两个眼睛pygame.draw.circle(screen,eye_color,eye_pos,self.cell_size//8)pygame.draw.circle(screen,eye_color,(eye_pos[0],eye_pos[1]self.cell_size//2),self.cell_size//8)代码中绘制了游戏的网格背景和食物以及我们的代理蛇专门画了一个蛇头方便观察运动中的方向选择情况。也支持手动玩不过需要添加手动输入的判断逻辑。接下来讲一下强化学习训练需要用到的一个重要函数step这是强化学习中用来迭代agent与环境互动的重要步骤。代码如下defstep(self,action):self.reward0info{}# 尝试所有可能的动作并选择一个最佳的best_actionaction best_reward-float(inf)#---------------------------------预训练结束后注释本区代码------------------------fortrial_actioninrange(3):trial_directionself._get_direction(trial_action)trial_headself.snake[0]trial_directionifself._is_safe(trial_head)andself._bfs_safe_path(trial_head):trial_rewardself._calculate_potential_reward(trial_head)iftrial_rewardbest_reward:best_rewardtrial_reward best_actiontrial_action# 执行最佳动作self.current_directionself._get_direction(best_action)new_headself.snake[0]self.current_direction# 检查新的蛇头位置是否超出边界或者撞到自己ifself._is_collision(new_head)orany(new_head0)orany(new_head)self.grid_size:self.game_overTrueself.reward-5returnself._get_observation(),self.reward,self.game_over,info# --------------------------------------------------------------------------------self._calculate_reward(new_head)self.last_actionaction doneself._update_snake_and_food(new_head)returnself._get_observation(),self.reward,done,info这段代码是预训练的代码预训练成功后正式训练的时候注释掉备注中的代码即可。代码中设置了一个最佳动作和最佳奖励这是为了通过动作空间选择评估在预训练的时候获得一些比较不错的动作策略后面的训练可以直接基于动作策略学习能加快训练收敛。所以预训练的设计非常关键然后是在训练过程中奖励函数的设置会引导AI学习我们期望的动作最后通过验证就完成这个学习的过程了。下面就是训练的代码了if__name____main__:# 检查是否有可用的CUDA设备devicetorch.device(cudaiftorch.cuda.is_available()elsecpu)model_pathr./save_modellog_dirr./logsgrid_size20# 或其他你的环境设置的值end_score60# 游戏结束条件envSnakeEnv(grid_sizegrid_size,end_scoreend_score)max_episodes10000000max_steps2000eval_freq1000# 评估评率num_eval_episodes50# 评估周期save_interval500# 固定保存频率lr1e-4weight_decay1e-5gamma0.99alpha0.2beta1input_channels3# Assuming the observation space is a single-channel imageoutput_dimenv.action_space.n agentConvActorCritic(input_channels,output_dim,grid_size,lr,weight_decay).to(device)ifnotos.path.exists(model_path):os.makedirs(model_path)load_model_pathf{model_path}\\model_2500.pthifos.path.exists(load_model_path):agent.load_model(agent.to(device),filenameload_model_path)print(fLoaded model from{load_model_path})else:print(No model to load. Starting a new training session.)ifnotos.path.exists(log_dir):os.makedirs(log_dir)writerSummaryWriter(log_dir)best_rewardfloat(-inf)# 初始化最佳奖励值为负无穷forepisodeinrange(2501,max_episodes):stateenv.reset()statetorch.tensor(state,dtypetorch.float).unsqueeze(0).to(device)# # 为了输入卷积增加一个维度卷积4维# print(state)step_count0episode_reward0doneFalsewhilenotdoneandstep_countmax_steps:action_probs,valueagent(state)disttorch.distributions.Categorical(action_probs)actiondist.sample()next_state,reward,done,_env.step(action.item())next_statetorch.tensor(next_state,dtypetorch.float).unsqueeze(0).to(device)# 智能体根据奖励函数更新策略agent.update(state,action,reward,next_state,done,gamma)episode_rewardreward statenext_state.to(device)ifdone:breakstep_count1# env.render(modetrain,fps100) # 运行可视化writer.add_scalar(Reward,round(episode_reward,5),episode)writer.add_scalar(Steps,step_count,episode)# 记录步长print(fEpisode{episode}, Reward:{round(episode_reward,3)}, step_count:{step_count})ifepisode%eval_freq0andepisode!0:avg_reward,rewardsevaluate_model(agent,env,device,num_eval_episodes)# 更新最佳模型ifavg_rewardbest_reward:best_rewardavg_reward agent.save_model(agent.to(device),filenamef{model_path}\\best_model.pth)# 早停逻辑# if early_stopping_criteria(rewards, patience30): # patience 连续多个周期没有表现提升# print(Early stopping triggered.)# break# 保存周期性模型ifepisode%save_interval0:agent.save_model(agent.to(device),filenamef{model_path}\\model_{episode}.pth)env.close()# 结束游戏运行writer.close()在这里可以设置不同的网格大小以及任务结束的标志这里默认是吃到50个食物就认为通关了这里已经加入了tensorboard,这个训练过程都是可视化的可以通过tensorboard监视奖励的变化情况。命令tensorboard--logdir.\logs验证模型可视化游戏为了查看训练的模型效果这里提供了一个加载模型和运行游戏的代码defplay_game(model_path):devicetorch.device(cudaiftorch.cuda.is_available()elsecpu)# 初始化环境和模型grid_size20end_score80envSnakeEnv(grid_sizegrid_size,end_scoreend_score)input_channels3output_dimenv.action_space.n modelConvActorCritic(input_channels,output_dim,grid_size).to(device)# 加载训练好的模型modelload_model(model,model_path)stateenv.reset()doneFalsewhilenotdone:statetorch.tensor(state,dtypetorch.float).unsqueeze(0).to(device)withtorch.no_grad():action_probs,_model(state)actiontorch.argmax(action_probs).item()# 选择概率最高的动作state,_,done,_env.step(action)env.render(fps1000)# 展示游戏动画if__name____main__:model_pathsave_model/best_model.pth# 修改为你的模型路径# model_path save_model/model_500.pth # 修改为你的模型路径play_game(model_path)这里可以选择自己训练的模型加载测试一下效果我训练了几千轮后基本就打通关了游戏。附图这蛇头蛮可爱的吧哈哈哈哈这个项目的代码已经放到github了需要完整项目代码的同学可以直接跳转去完整项目源码 记得给我star哈如果star够多后面会更新更多的学习源码喲遇到环境安装问题可以看我这篇文章python从爬虫开始一——Python3的安装与环境配置以及网络爬虫的手把手教学第二篇出炉了欢迎大家阅读RL强化学习从小白到老鸟(二)——手撕GPT零基础保姆级教学
返回列表