feat: p2
This commit is contained in:
@ -4,6 +4,8 @@ PPCA AI 吃豆人项目
|
||||
|
||||
本项目基于[加州大学伯克利分校的CS 188《Introduction to Artificial Intelligence》课程项目](https://inst.eecs.berkeley.edu/~cs188/sp24/projects/)
|
||||
|
||||
[http://ai.berkeley.edu](http://ai.berkeley.edu)
|
||||
|
||||
## Week 1
|
||||
|
||||
### 环境配置
|
||||
@ -57,3 +59,8 @@ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/f
|
||||
|
||||
相关文件和介绍在 [search](https://github.com/ACMClassCourse-2023/PPCA-AIPacMan-2024/tree/main/search) 文件夹下。
|
||||
|
||||
## Week2
|
||||
|
||||
### MultiAgent
|
||||
|
||||
相关文件和介绍在 [multiagent](https://github.com/ACMClassCourse-2023/PPCA-AIPacMan-2024/tree/main/multiagent) 文件夹下。
|
@ -116,6 +116,22 @@ Alpha-Beta 剪枝的主要思想是:在某些情况下,可以提前停止对
|
||||
|
||||
如果在搜索过程中发现一个节点的评估值无法改进当前的 Alpha 或 Beta 值,就可以停止对该节点的进一步搜索。
|
||||
|
||||
要评估的节点数量,从而提高了效率。
|
||||
|
||||
### Alpha-Beta 剪枝的步骤
|
||||
|
||||
1. 初始条件:
|
||||
|
||||
- Alpha 的初始值为负无穷(表示当前最大化玩家的已知最优值)。
|
||||
- Beta 的初始值为正无穷(表示当前最小化玩家的已知最优值)。
|
||||
2. 剪枝条件:
|
||||
|
||||
- 在最大化层(Max 层),如果当前节点的评估值大于或等于 Beta 值,则不再评估该节点的后续分支。因为这一分支返回的评估值不会小于当前评估值,即已经比 Beta 更大,因而上一层的最小化玩家不会选择这一分支,而是选择之前的 Beta(剪枝)。
|
||||
- 在最小化层(Min 层),如果当前节点的评估值小于或等于 Alpha 值,则不再评估该节点的后续分支,因为这一分支返回的评估值不会大于当前评估值,即已经比 Alpha 更小,因而上一层最大化玩家不会选择这一分支,而是选择之前的 Alpha(剪枝)。
|
||||
3. 递归过程:
|
||||
|
||||
- 在递归过程中,算法不断更新 Alpha 和 Beta 值。Alpha 表示当前最大化玩家可以保证的最高值,Beta 表示当前最小化玩家可以保证的最低值。
|
||||
- 每次评估一个节点后,根据评估结果更新 Alpha 或 Beta 值,并判断是否需要进行剪枝。
|
||||
|
||||
### 带 Alpha-Beta 剪枝的伪代码
|
||||
|
185
multiagent/README.md
Normal file
185
multiagent/README.md
Normal file
@ -0,0 +1,185 @@
|
||||
#### 介绍
|
||||
|
||||
在这个项目中,你将为经典版 Pacman 设计 agent,包括幽灵。
|
||||
|
||||
代码库与之前的项目相比没有太大变化,但请从全新开始,而不是混合项目 1 中的文件。
|
||||
|
||||
你需要补全的代码文件有:
|
||||
|
||||
- `multiAgents.py`
|
||||
|
||||
你可以阅读并参考来帮助你实现代码的文件有:
|
||||
|
||||
- `pacman.py`
|
||||
- `game.py`
|
||||
- `util.py`
|
||||
|
||||
你可以忽略其他支持文件。
|
||||
|
||||
#### Q1: Reflex Agent
|
||||
|
||||
你需要改进 `multiAgents.py` 中的 `ReflexAgent` 以使其表现出色。提供的 `ReflexAgent` 代码提供了一些查询 `GameState` 信息的方法的示例。与之前不同的是,你每次只需返回一个动作,而不是一个动作列表。详情请见代码。
|
||||
|
||||
一个好的 `ReflexAgent` 需要同时考虑食物位置和幽灵位置以取得好的表现。你的 agent 应该能够轻松且可靠地通过 `testClassic` 布局测试。
|
||||
```
|
||||
python pacman.py -p ReflexAgent -l testClassic
|
||||
```
|
||||
|
||||
在默认布局 `mediumClassic` 上尝试使用一个或两个幽灵来执行反射 agent (并关闭动画以加快显示速度):
|
||||
```
|
||||
python pacman.py --frameTime 0 -p ReflexAgent -k 1
|
||||
python pacman.py --frameTime 0 -p ReflexAgent -k 2
|
||||
```
|
||||
|
||||
注意:`newFood` 具有 `asList()` 功能。
|
||||
|
||||
注意:作为特征,尝试重要值的倒数(例如到食物的距离),而不仅仅是值本身。
|
||||
|
||||
注意:你正在编写的评估函数正在评估 `state-action` 对;在项目的后续部分,你将评估 `state`。
|
||||
|
||||
注意:你可能会发现查看各种对象的内部内容对于调试很有用。你可以通过打印对象的字符串表示来实现这一点。例如,你可以对 `newGhostStates` 使用打印 `print(newGhostStates)`。
|
||||
|
||||
选项:默认幽灵是随机的;你也可以使用 `-g DirectionalGhost` 来玩一些更智能的定向幽灵。如果随机性阻止你判断 agent 是否正在改进,你可以使用 `-f` 以固定的随机种子运行(每场游戏都使用相同的随机选择)。你还可以使用 `-n` 连续玩多场游戏。使用 `-q` 关闭图形以快速运行大量游戏。
|
||||
|
||||
评分:我们将在 `openClassic` 布局上运行你的 agent 10 次。如果你的 agent 超时或从未获胜,你将获得 0 分。如果你的 agent 至少获胜 5 次,你将获得 1 分,如果你的 agent 赢得所有 10 场比赛,你将获得 2 分。如果你的 agent 的平均得分大于 500,你将获得额外的 1 分,如果大于 1000,你将获得 2 分。
|
||||
|
||||
运行以下命令来查看你的实现是否通过了所有自动评分测试用例。
|
||||
|
||||
```
|
||||
python autograder.py -q q1
|
||||
```
|
||||
|
||||
要运行不带图形的程序,请使用:
|
||||
|
||||
```
|
||||
python autograder.py -q q1 --no-graphics
|
||||
```
|
||||
|
||||
不过,不要在这个问题上花费太多时间,因为项目的核心还在后面。
|
||||
|
||||
#### Q2: Minimax
|
||||
|
||||
[Q2 和 Q3 前置知识介绍](https://github.com/ACMClassCourse-2023/PPCA-AIPacMan-2024/blob/main/docs/minimax.md)
|
||||
|
||||
现在,你需要在提供的 `multiAgents.py` 文件中的 `MinimaxAgent` 类中编写一个对抗搜索 agent。你的 minimax agent 应该适用于任意数量的幽灵,因此你需要编写一个比介绍中所见的稍微更通用的算法。特别地,对于每一层最大化节点(max layer),你的 Minimax 树将有多个最小化节点(min layer)(每个幽灵一个)。
|
||||
|
||||
你的代码还应该将游戏树扩展到任意深度。使用提供的 `self.evaluationFunction` 来评估 minimax 树的叶节点,该函数默认为 `scoreEvaluationFunction`。`MinimaxAgent` 继承了 `MultiAgentSearchAgent`,这使得你可以访问 `self.depth` 和 `self.evaluationFunction`。确保你的 minimax 代码在适当的地方引用了这两个变量,因为这些变量是根据命令行选项填充的。
|
||||
|
||||
**重要提示**:一次搜索轮次被认为是一次吃豆人(Pacman)移动和所有幽灵的回应,所以深度为 2 的搜索将涉及吃豆人和每个幽灵移动两次(参见下图)。
|
||||
|
||||

|
||||
|
||||
评分:我们将检查你的代码,以确定它是否探索了正确数量的游戏状态。这是检测 minimax 实现中某些非常微妙的错误的唯一可靠方法。因此,自动评分器对你调用 `GameState.generateSuccessor` 的次数会非常挑剔。如果你调用的次数多于或少于必要的次数,自动评分器将会报错。要测试和调试你的代码,请运行:
|
||||
|
||||
```
|
||||
python autograder.py -q q2
|
||||
```
|
||||
|
||||
要在没有图形的情况下运行它,请使用:
|
||||
|
||||
```
|
||||
python autograder.py -q q2 --no-graphics
|
||||
```
|
||||
|
||||
提示和观察:
|
||||
|
||||
- 使用辅助函数递归地实现算法。
|
||||
- minimax 算法的正确实现会导致吃豆人在某些测试中输掉游戏。这不是问题,因为这是正确的行为,它会通过测试。
|
||||
- 这一部分的吃豆人测试的评估函数已经写好(`self.evaluationFunction`)。你不应该更改这个函数,但要认识到现在我们是在评估状态(`state`),而不是像反射 agent 那样评估动作(`action`)。前瞻 agent 评估未来的状态,而反射 agent 评估当前状态的动作。
|
||||
- **吃豆人总是 agent 0**,agent 按 agent 索引递增的顺序移动。
|
||||
- minimax 中的所有状态都应该是 `GameStates`,要么传递给 `getAction`,要么通过 `GameState.generateSuccessor` 生成。在这个项目中,你不会抽象到简化的状态。
|
||||
- 在更大的场景下,如 `openClassic` 和 `mediumClassic`(默认),你会发现吃豆人很擅长不死,但很难赢。他常常会毫无进展地四处游荡。有时,他甚至会在一个豆子旁边游荡而不吃掉它,因为他不知道吃掉那个豆子后要去哪儿。如果你看到这种行为,不要担心,第五个问题会解决这些问题。
|
||||
- 当吃豆人认为他的死亡是不可避免的时,他会试图尽快结束游戏,因为存在持续的生存惩罚。有时,对于随机幽灵来说,这是错误的做法,但 minimax agent 总是假设最坏情况:
|
||||
```
|
||||
python pacman.py -p MinimaxAgent -l trappedClassic -a depth=3
|
||||
```
|
||||
确保你理解为什么在这种情况下吃豆人会冲向最近的幽灵。
|
||||
|
||||
#### Q3 : Alpha-Beta Pruning
|
||||
|
||||
在 `AlphaBetaAgent` 中编写一个使用 alpha-beta 剪枝以更高效地探索 minimax 树的新 agent 。同样,你的算法将比介绍中的伪代码稍微更通用,因此,部分挑战在于适当地将 alpha-beta 剪枝逻辑扩展到多个最小化 agent 。
|
||||
|
||||
你应该能看到速度提升(也许深度为 3 的 alpha-beta 剪枝运行速度能与深度为 2 的 minimax 相当)。理想情况下,在 smallClassic 布局中,深度 3 的每次移动应该只需几秒钟或更快。
|
||||
|
||||
```
|
||||
python pacman.py -p AlphaBetaAgent -a depth=3 -l smallClassic
|
||||
```
|
||||
|
||||
`AlphaBetaAgent` 的 minimax 值应该与 `MinimaxAgent` 的 minimax 值相同,尽管它选择的动作可能会有所不同,因为它们的平局决策行为不同。
|
||||
|
||||
评分:因为我们检查你的代码是否探索了正确数量的状态,所以重要的是你在不重新排序子节点的情况下执行 alpha-beta 剪枝。换句话说,后继状态应该始终按 `GameState.getLegalActions` 返回的顺序处理。同样,不要多于必要地调用 `GameState.generateSuccessor`。
|
||||
|
||||
为了匹配我们的自动评分器探索的状态集,**你不能在相等时进行剪枝**。(确实,另一种方法是允许在相等时进行剪枝,并在根节点的每个子节点上调用一次 alpha-beta,但这将不符合我们的自动评分器。)
|
||||
|
||||
下面的伪代码表示你应该为这个问题实现的算法。
|
||||
|
||||

|
||||
|
||||
要测试和调试代码,请运行
|
||||
|
||||
```
|
||||
python autograder.py -q q3
|
||||
```
|
||||
|
||||
要在没有图形的情况下运行它,请使用:
|
||||
|
||||
```
|
||||
python autograder.py -q q3 --no-graphics
|
||||
```
|
||||
|
||||
正确实施 alpha-beta 剪枝将导致 Pacman 输掉部分测试。这不是问题:因为它是正确的行为,所以它将通过测试。
|
||||
|
||||
#### Q4: Expectimax
|
||||
|
||||
minimax 和 alpha-beta 算法都很棒,但它们都假设你在与一个做出最优决策的对手对战。任何赢过井字游戏的人都会告诉你,这并不总是如此。在这个问题中,你将实现 `ExpectimaxAgent`,它对于模拟可能做出次优选择的 agent 的概率行为非常有用。
|
||||
|
||||
随机幽灵当然不是最优的 minimax agent ,因此用 minimax 搜索来模拟它们可能不合适。`ExpectimaxAgent` 不再对所有幽灵动作取最小值,而是根据你对幽灵行为模型的期望值。为了简化你的代码,假设你只会对一个在其 `getLegalActions` 中均匀随机选择的对手运行。
|
||||
|
||||
你可以使用以下命令在小型游戏树上调试你的实现:
|
||||
|
||||
```
|
||||
python autograder.py -q q4
|
||||
```
|
||||
|
||||
建议在这些小而易管理的测试用例上进行调试,这将帮助你快速找到错误。
|
||||
|
||||
一旦你的算法在小型树上运行正常,你可以在吃豆人中观察其成功情况。要观察 `ExpectimaxAgent` 在吃豆人中的表现,请运行:
|
||||
|
||||
```
|
||||
python pacman.py -p ExpectimaxAgent -l minimaxClassic -a depth=3
|
||||
```
|
||||
|
||||
你现在应该会观察到在幽灵近距离接触时,`ExpectimaxAgent` 会采取更大胆的策略。特别是,如果吃豆人认为自己可能被困住但可以逃脱以抓住更多的食物,他至少会尝试。研究以下两种情境的结果:
|
||||
|
||||
```
|
||||
python pacman.py -p AlphaBetaAgent -l trappedClassic -a depth=3 -q -n 10
|
||||
python pacman.py -p ExpectimaxAgent -l trappedClassic -a depth=3 -q -n 10
|
||||
```
|
||||
|
||||
你应该会发现你的 `ExpectimaxAgent` 大约能赢一半,而你的 `AlphaBetaAgent` 总是输。确保你理解为什么这里的行为与 minimax 情况不同。
|
||||
|
||||
正确实现的 expectimax 将导致吃豆人在一些测试中输掉比赛。这不是问题:因为这是正确的行为,它会通过测试。
|
||||
|
||||
#### Q5: Evaluation Function
|
||||
|
||||
在提供的 `betterEvaluationFunction` 函数中为吃豆人编写一个更好的评估函数。这个评估函数应该评估状态(`state`),而不是像你的反射 agent 评估函数那样评估动作(`action`)。使用深度为 2 的搜索时,你的评估函数应该能在 `smallClassic` 布局中面对一个随机幽灵时胜率超过一半,并且运行速度合理(要获得满分,吃豆人在获胜时的平均得分应约为 1000 分)。
|
||||
|
||||
评分:自动评分器将在 `smallClassic` 布局上运行你的 agent 10 次。我们将以以下方式给你的评估函数打分:
|
||||
|
||||
- 如果你至少赢一次且没有超时,你将获得1分。任何不满足这些标准的 agent 将获得0分。
|
||||
- 赢至少5次加1分,赢所有10次加2分。
|
||||
- 平均得分至少500加1分,平均得分至少1000加2分(包括输掉比赛的得分)。
|
||||
- 如果在自动评分器机器上运行时,游戏平均用时少于30秒,加1分(使用 --no-graphics 运行)。
|
||||
- 平均得分和计算时间的额外分数仅在你至少赢5次的情况下才会被授予。
|
||||
|
||||
请不要复制项目1中的任何文件,因为它们不会通过。你可以在以下条件下尝试你的 agent:
|
||||
|
||||
```
|
||||
python autograder.py -q q5
|
||||
```
|
||||
|
||||
要无图形界面运行,请使用:
|
||||
|
||||
```
|
||||
python autograder.py -q q5 --no-graphics
|
||||
```
|
@ -13,6 +13,8 @@
|
||||
- `game.py`
|
||||
- `util.py`
|
||||
|
||||
你可以忽略其他支持文件。
|
||||
|
||||
运行 `python pacman.py` 即可启动吃豆人游戏,你可以用你的键盘操作吃豆人并探索。
|
||||
|
||||
在 `searchAgents.py` 中,将会存在不同的 `agent` 类来决定吃豆人的行为模式。它规划出一条吃豆人穿越迷宫的路径,然后逐步执行该路径。
|
||||
@ -82,7 +84,7 @@ python autograder.py -q q2
|
||||
|
||||
#### Q3:改变成本函数
|
||||
|
||||
虽然 BFS 会找到一条到达目标的最少操作路径,但我们可能希望找到其他意义上“最佳”的路径。考虑 `mediumDottedMaze` 和 `mediumScaryMaze` 这两个地图。通过改变成本函数,我们可以鼓励吃豆人寻找不同的路径。例如,我们可以对鬼魂出没地区的危险步骤收取更多费用,或对食物丰富地区的步骤收取更少费用,而理性的吃豆人代理应该会据此调整其行为。
|
||||
虽然 BFS 会找到一条到达目标的最少操作路径,但我们可能希望找到其他意义上“最佳”的路径。考虑 `mediumDottedMaze` 和 `mediumScaryMaze` 这两个地图。通过改变成本函数,我们可以鼓励吃豆人寻找不同的路径。例如,我们可以对鬼魂出没地区的危险步骤收取更多费用,或对食物丰富地区的步骤收取更少费用,而理性的吃豆人 agent 应该会据此调整其行为。
|
||||
|
||||
你需要在 `search.py` 中的 `uniformCostSearch` 函数中实现均匀成本图搜索算法。我们鼓励你仔细查看 `util.py` 一些可能对你的实现有用的数据结构。现在你应该在以下所有三种地图中观察到成功的行为,其中下面的 agent 都是 UCS agent,它们仅在使用的成本函数上有所不同(agent 和成本函数已经为你编写好):
|
||||
|
||||
@ -102,7 +104,7 @@ python autograder.py -q q3
|
||||
|
||||
#### Q4:A* 搜索
|
||||
|
||||
[A* 介绍](https://oi-wiki.org/search/astar/)
|
||||
[介绍](https://github.com/ACMClassCourse-2023/PPCA-AIPacMan-2024/blob/main/docs/A*.md)
|
||||
|
||||
简单介绍启发式搜索:有一个启发式函数 $h$,在搜索时优先搜索值最小的方向。
|
||||
|
||||
@ -227,7 +229,7 @@ python pacman.py -l tinySearch -p AStarFoodSearchAgent
|
||||
python pacman.py -l trickySearch -p AStarFoodSearchAgent
|
||||
```
|
||||
|
||||
我们的 UCS 代理在大约 13 秒内找到了最佳解决方案,探索了超过 16,000 个节点。
|
||||
我们的 UCS agent 在大约 13 秒内找到了最佳解决方案,探索了超过 16,000 个节点。
|
||||
|
||||
```
|
||||
python pacman.py -l trickySearch -p SearchAgent -a fn=ucs,prob=FoodSearchProblem
|
||||
@ -253,9 +255,9 @@ python autograder.py -q q7
|
||||
|
||||
#### Q8:次优搜索
|
||||
|
||||
有时,即使使用 A* 和一个好的启发式函数,找到所有点的最优路径也是困难的。在这种情况下,我们仍希望能够快速找到一条相对较好的路径。在这一部分,你将编写一个代理,它总是贪婪地吃掉最近的点。`ClosestDotSearchAgent` 已在 `searchAgents.py` 中实现,但缺少一个找到最近点路径的关键函数。
|
||||
有时,即使使用 A* 和一个好的启发式函数,找到所有点的最优路径也是困难的。在这种情况下,我们仍希望能够快速找到一条相对较好的路径。在这一部分,你将编写一个 agent,它总是贪婪地吃掉最近的点。`ClosestDotSearchAgent` 已在 `searchAgents.py` 中实现,但缺少一个找到最近点路径的关键函数。
|
||||
|
||||
在 `searchAgents.py` 中实现函数 `findPathToClosestDot`。我们的代理能够在不到一秒钟的时间内以 350 的路径代价,次优地解决了这个迷宫:
|
||||
在 `searchAgents.py` 中实现函数 `findPathToClosestDot`。我们的 agent 能够在不到一秒钟的时间内以 350 的路径代价,次优地解决了这个迷宫:
|
||||
|
||||
```
|
||||
python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5
|
||||
|
Reference in New Issue
Block a user