mod|通过强化学习和官方API制作《星露谷物语》的自动钓鱼mod( 二 )



Q-learning 中关键问题是要获取曾经处于哪个状态和采取了哪些行动、到达哪个新的状态 , 以及执行这个行动中得到的奖励 。 有了这些数据 , 我们可以使用像价值迭代 (Value Iteration 一种动态规划算法)这样的简单算法将奖励从最终状态(获胜状态)开始分析 , 逐渐往回推直至推至所有状态 。 因此对于每个可能的状态 , 模型都会知道最大化其未来回报的方向 。但是我不会使用价值迭代来训练模型 , 因为真正的问题往往有太多的状态并且动态规划需要很长时间 。
上面的价值迭代只是为了说明在 C# 中保存每个条目的方式 。这里使用缓存从最后一帧获取状态和动作 , 并将所有这些与当前帧的状态和奖励一起存储 。
replayMemory[updateCounter0
= OldState[0
;
replayMemory[updateCounter1
= OldState[1
;
replayMemory[updateCounter2
= OldState[2
;
replayMemory[updateCounter3
= NewState[0
;
replayMemory[updateCounter4
= NewState[1
;
replayMemory[updateCounter5
= NewState[2
;
replayMemory[updateCounter6
= reward;
replayMemory[updateCounter7
= actionBuffer? 1 : 0;
所有这些数据都变成了一个巨大的 csv 文件 , 这样可以通过 Python 加载并用于训练 DQN 模型 。
DQN 模型【mod|通过强化学习和官方API制作《星露谷物语》的自动钓鱼mod】使用神经网络估计 Q-table的 Q-Learning称为Deep Q-Learning 。 这个方法在很多个 Pytorch 教程中都有很好的解释 , 我从里面复制了很多代码并为我们的问题对其进行了一些修改 。 主要思想是使用两个神经网络 。 一个将估计 Q(sa) 的值(Policy Net) , 另一个将估计未来 Q-values的值(Target Net) 。 然后我们对这两个网络的差异进行反向传播 。
这是 Q-Learning算法的基本方程 。 我们将使用一个网络来估计当前状态 Q(sa) 的正确值 , 另一个将估计下一个状态的最大可能值 。 两个网络都使用随机值进行初始化 , 并且每隔几次迭代将Policy Net权重复制到Target Net 。 Policy Net则通过反向传播更新权重, 通过反向传播这种 , Policy Net 最终将学会估计这两个值 。

α 是学习率 , \uD835\uDEFE 是用于选择为 Q 的未来值给出的重要值的折扣因子(discount factor) 。 强化学习是比较难易理解的所以最后会整理一堆链接 , 它们会做更好的细节解释 。
训练训练过程是“自我驱动的\uD83D\uDE02” , 首先要自己玩游戏收集状态和奖励数据 , 然后训练一个初始化的效果很差的模型让它自动玩游戏 , 并为我们收集新的数据 。 然后使用这些数据在 Python 端训练新模型 , 生成一个新的 ONNX格式模型 , 该模型将每 1000 帧左右重新加载一次 , 然后使用新模型继续玩游戏并生成数据来训练新模型 。因为C?# 必须编译 mod 并将其打包到与游戏可执行文件兼容的 Windows DLL 中 , 我没有找到一个可以生成正确的 .NET 机器学习框架二进制文件(Stardew Valley 是在 .NET 5 中编译的) , 所以我放弃了 , 这里直接用 Python 编写了这部分 。
另外一个重要决定是该模型不需要在线训练 。Q-Learning就是要找到函数 Q(sa) 的良好近似值 , 即估计在特定状态 s 下执行特定动作 a 的值的函数 。 所以模型的目的是数据彻底探索这个状态空间 , 无论是你(人肉)还是模型玩游戏都没有关系 , 当然如果能够全部自动化拿看起来肯定更加的高大上 。
从 C # 中读取 ONNX 模型C# 端唯一真正的 ML 代码是 ONNX 进行推理(预测) , 它定义了张量类型和会话的对象 , 可以发送张量输入并从序列化的 ONNX 模型获取张量输出 。下面的代码非常简单明了 。更新函数在每一帧都运行 , 并以当前状态作为输入查询训练模型的动作 , 最后几行只是用于获取模型输出的 argMax一些代码 , 这是与产生的动作对应的索引 。 序列化模型的重量只有 120kb 左右 , 所以运行起来非常轻巧 。