snake_ai 学习笔记
前置知识
讲点没用的(bushi)
机器学习
- 监督学习
- 无监督学习
- 半监督学习
- 强化学习
前面三个是依靠样本和标记学习,强化学习(Reinforcement Learning,简称RL)依靠奖励和惩罚
- Agent,一般译为智能体,就是我们要训练的模型,
- action(简记为a),即智能体能做的行为
- Environment,即环境,它是提供reward的某个对象
- reward(简记为r),这个奖赏可以类比为在明确目标的情况下,接近目标意味着做得好则奖,远离目标意味着做的不好则惩,最终达到收益/奖励最大化,且这个奖励是强化学习的核心
- State(简介为s),可以理解成环境的状态,简称状态
Agent依据策略决策从而执行动作action,然后通过感知环境Environment从而获取环境的状态state,进而,最后得到奖励reward(以便下次再到相同状态时能采取更优的动作),然后再继续按此流程“依据策略执行动作-感知状态–得到奖励”循环进行
机器学习的方法
- 基于值函数的方法,通过求解一个状态或者状态下某个动作的估值为手段,从而寻找最佳的价值函数,找到价值函数后,再提取最佳策略 比如Q-learning、DQN等,适合离散的环境下,比如围棋和某些游戏领域
- 基于策略的方法,一般先进行策略评估,即对当前已经搜索到的策略函数进行估值,得到估值后,进行策略改进,不断重复这两步直至策略收敛
比如策略梯度法(policy gradient,简称PG),适合连续动作的场景,比如机器人控制领域
以及Actor-Criti(一般被翻译为演员-评论家算法),Actor学习参数化的策略即策略函数,Criti学习值函数用来评估状态-动作对,不过,Actor-Criti本质上是属于基于策略的算法,毕竟算法的目标是优化一个带参数的策略,只是会额外学习价值函数,从而帮助策略函数更好的学习
此外,还有对策略梯度算法的改进,比如TRPO算法、PPO算法,当然PPO算法也可称之为是一种Actor-Critic架构
策略梯度算法
概念
- 策略: 策略是一个函数,它根据当前状态(或状态-动作对)来选择动作。在策略梯度算法中,策略通常被表示为一个参数化的函数,如神经网络,这些参数可以通过梯度上升(最大化)来优化。
- 目标函数: 在策略梯度算法中,目标函数通常是期望回报的某种形式,例如,从开始状态到结束状态的累积折扣回报的期望值。策略梯度算法的目标是找到最大化这个期望回报的策略参数。
- 梯度: 梯度是目标函数关于策略参数的导数,它指出了如何调整策略参数以最大化目标函数。
步骤
- 初始化策略参数
- 收集数据
- 计算梯度
- 更新策略
- 重复步骤2~4
关于梯度方向:
梯度上升,即沿着奖励函数上升最快的方向;
梯度下降,即沿着损失函数下降最快的方向
PPO算法近端策略优化算法
PPO算法是梯度策略算法的一种具体实现和改进。梯度策略算法是一类强化学习算法,其核心思想是通过直接优化策略的参数来最大化累积奖励。PPO算法在梯度策略方法的基础上,引入了重要性采样和截断机制,从而有效地解决了传统策略梯度方法中更新步长难以确定的问题,提高了算法的稳定性和性能。
MLP多层感知器
包含两个或两个以上隐藏层的前馈神经网络
单层神经网络输入层和输出层去全连接,即每个之间都有权重,难以解决非线性和复杂的问题,引入隐藏层
结构
- 输入层 (Input Layer): 同样作为网络的起点,包含多个神经元,每个神经元接收一个输入特征,共同构成输入向量。
- 隐藏层 (Hidden Layers): 多层神经网络的核心部分,包括一个或多个隐藏层。每一层都包含一定数量的隐藏神经元。各层之间全连接,即每个神经元与其下一层的所有神经元都有连接。隐藏层通过非线性激活函数对前一层的输出进行变换,生成新的特征表示。
- 输出层 (Output Layer): 最后一层,根据任务需求(如分类、回归等)设计相应数量和类型的神经元,并应用适当的激活函数生成最终输出。
- 全连接 (Fully Connected): 每一层的神经元与下一层的所有神经元之间均有独立的权重 (w_{ij}) 连接,权重决定了信号在神经元间的传递强度。
工作原理
- 前向传播 (Forward Propagation):- 输入到第一隐藏层: 与两层神经网络相似,输入特征经过权重与偏置运算,经激活函数得到第一隐藏层的激活值。- 隐藏层间传递: 对于后续的隐藏层,每一层的激活值作为下一层的输入,重复上述过程。- 最后一层到输出: 最后一个隐藏层的激活值通过权重和偏置传递到输出层,应用输出层激活函数得到最终输出。
- 学习与权重更新: 多层神经网络采用反向传播算法结合梯度下降法或其他优化算法更新所有权重和偏置。反向传播从输出层开始,逐层计算损失函数相对于各层权重和偏置的梯度,然后沿相反方向传播这些梯度,直至输入层,完成一次迭代更新:- 反向传播: 从输出层开始,计算损失函数对输出层权重和偏置的梯度,然后递归地计算每层隐藏层的权重和偏置梯度。- 权重更新: 根据计算出的梯度和选定的学习率更新所有权重和偏置。
CNN卷积神经网络
- 输入层: 输入图像等信息
- 卷积层: 用来提取图像的底层特征拿卷积核(滤波器,也是二维矩阵)在图像上移动去和对应范围内的矩阵做内积,得到卷积图像
- 池化层: 防止过拟合,将数据维度减小在有多个卷积核的时候图像数据会很大,所以我们取一块二维空间,将这块空间上面的数据取平均值(平均池化)或者取最大值(最大池化),将卷积图像不断地缩小
- 全连接层: 汇总卷积层和池化层得到的图像的底层特征和信息,展开成一维数组
- 输出层: 根据全连接层的信息得到概率最大的结果,将一维数组经过计算得到概率
*CNN通过反向传播算法(根据预测结果与实际值之间的误差,计算损失函数的梯度,并通过链式法则反向传播,更新各层参数,减小损失函数的值)不断调整卷积核和全连接层的参数,以最小化预测值与实际值之间的误差*
原理讲完了,本文结束,你可以自己实现ai贪吃蛇了
代码调优
用python3.12.4 torch=2.3.1+cu118 sb3=2.3.2 gym=0.26.2
绝对不是我conda上虚拟环境装了一天装不起来
主要是random的问题,应该是pyhton3.12的改进
- random.randint()
seed = random.randint(0, 1e9)//原始代码,snake_game.py 251行
seed = random.randint(0, int(1e9))//需要强制类型转换成int
同理test、train文件中也有randint需要修改
- random.sample()
food = random.sample(self.non_snake, 1)[0]//原始代码,snake_game.py 133行
food = random.sample(list(self.non_snake), 1)[0]//同样强制类型转化,sample只能处理序列(列表、元祖等)
改完之后就能够运行游戏
算法比较
MLP
观察空间
数据从-1到1,以32位float型
奖励表达
长度奖励
结束时,达到最大长度时,reward=max_size;
食物奖励
吃到食物时,reward=exp((max_size - step)/max_size) (0,e)
方向奖励
朝着食物走时,reward=1/size 背着时,reward=-1/size### 迭代头为1,食物为-1,蛇身从0.8到0.2递减,以此来为ai标记不同的数据## CNN### 观察空间数据从0到255,以无符号8位整数,图像为84*84的RGB三通道图像### 奖励表达- 长度奖励 - 结束时,达到最大长度reward=size - max_size; reward=-(max_size-init_size)^((max_size-size)/(max_size-init_size)) (-max_size,-1)
食物奖励
吃到食物时,reward=size/max_size (0,1)
方向奖励
朝着食物走时,reward=1/size 背着时,reward=-1/size### 迭代头为红,食物为蓝色,蛇身为绿颜色递减,以此来为ai标记不同的数据### 其余代码train代码
# 根据MPS(Metal Performance Shaders,苹果GPU加速)的可用性设置环境数量if torch.backends.mps.is_available(): NUM_ENV = 32 * 2else: NUM_ENV = 32 # 环境数量控制LOG_DIR = "logs" # 日志存储目录os.makedirs(LOG_DIR, exist_ok=True) # 创建日志目录,若已存在则不报错# 线性插值调度器def linear_schedule(initial_value, final_value=0.0): """ 线性插值调度器,用于根据进度调整参数值。 参数: initial_value (Union[float, str]): 初始值,可以是浮点数或字符串表示的浮点数。 final_value (float, optional): 最终值,默认为0.0。 返回: Callable[[float], float]: 一个接受进度参数并返回插值结果的函数。 注意: 如果initial_value是字符串,则将其转换为浮点数并确保其大于0。 """ if isinstance(initial_value, str): initial_value = float(initial_value) final_value = float(final_value) assert initial_value > 0.0 # 转换字符串为浮点数并且确保大于0 def scheduler(progress): return final_value + progress * (initial_value - final_value) return schedulerdef make_env(seed=0): """ 创建一个Snake环境的初始化函数。 参数: seed (int, optional): 环境种子,默认为0。 返回: Callable[[], stable_baselines3.common.env_checker.EnvCheckerWrapper]: 一个返回已包装环境的函数。 """ def _init(): env = SnakeEnv(seed=seed) env = ActionMasker(env, SnakeEnv.get_action_mask) # 添加动作掩码 env = Monitor(env) # 监控环境表现 env.seed(seed) # 设置环境种子 return env return _init# ... (省略了main函数的部分代码,因为它们已经在上面的注释中详细说明了)if __name__ == "__main__": main() # 当此脚本作为主程序运行时,执行main函数
代码作者:林亦LYi ------------------------------ orz膜拜大佬 github网址 https://github.com/linyiLYi/snake-ai b站视频网址 https://www.bilibili.com/video/BV1ag4y1F7x4/
版权归原作者 rammstein1234789 所有, 如有侵权,请联系我们删除。