cs50ai0-------Search


基础知识

(1) search problem



上图是搜索问题的一般形式

每个名词具体解释如下:

initial state:state是agent与environment的一个配置或者说构造,initial state就是初始的state

actions:在state下可以做出的所有action

transition model:

对在任何state下执行可执行的action所产生的状态的描述

goal test:确认当前state是否是goal state

path cost function:与某一个path相关的cost

(2) dfs与bfs



上图就是dfs与bfs的具体算法流程

首先 从一个包含着initial state的frontier与空的explored set开始,从frontier中安照规则移除一个node(包含parent state,当前state,以及从parent到当前node的action),拓展该node,将拓展得到的不在frontier中和explored set中的node加入frontier,重复上述操作,直到frontier为空或者到达目标state

区别就是frontier采用的数据结构不同,dfs是stack,bfs是queue

(3) greedy best-first search(gbs)与A*搜索

上面提到的dfs与bfs属于无信息搜索即盲目搜索

而贪心搜索属于有信息搜索也叫启发式搜索

具体定义为:

扩展最接近目标的节点的搜索算法,这个最接近的节点由启发式函数 h(n) 估计



如上图所示 在迷宫问题中的h(n)是到目标的manhattan距离

但是贪心搜索可能导致找到的是局部最优解,不是全局最优路径

而A*搜索则是使用g(n)与h(n)来计算path cost




相当于已经走过的cost和估计的cost之和

A*算法是最优的在如下所示的情况下:



要h(n)要满足不过分估计同时保持一致性

这一点我理解的不是很清楚

(4)对抗性搜索或者说博弈?

重要元素:

s0:初始state

Player(s):定义此时该谁动

Actions(s):此状态下的合法移动集合

Result(s,a):转移模型,定义行动的结果

Terminal(s):终止测试,游戏结束返回真,否则假。

Utility(s):效用函数,定义游戏者p在终止状态s下的数值

minmax算法就是其中的一种





如上面几张图所示

极大极小核心算法建立在尽力削弱对手的value上面

比如说下面这颗树:



箭头向上表示该player要最大化得分

向下则表示要最小化得分

那么player想要自己的得分最大,要考虑对手的想法,需要遍历对手的每一种action,比如说让对手走4这颗子树,会产生4 8 5这三种结果,对手绘最小化自己的得分,所以自己最终会得到四分,同样其它两颗子树自己会得到3和2分,应该选择最大的4这颗子树

而这种算法的问题也很明显,遍历对手的所有想法,树的深度一增加,复杂度就会指数倍上升,因此就有了两种方法来解决此问题

一个是剪枝,即假如我们已经能提前估计下面这颗子树的效果不如当前最好的子树效果好,我们可以直接将整颗子树放弃掉

一个是限制树的深度,比如说就限制树只要十层,这里需要额外的一个evaluate function,来估计某个state下预期的utility,从而提前做出action

课后题目

(1)degrees

文件说明:机翻稍改

small与big是两组csv数据文件

现在,打开small/stars.csv。 此文件在 people.csv 中的人物和 movie.csv 中的电影之间建立关系。 每行都是一对 person_id 值和 movie_id 值。 例如,第一行(忽略标题)指出 id 为 102 的人主演了 id 为 104257 的电影。对照 people.csv 和 movie.csv 检查这一点,您会发现这一行表示 Kevin Bacon 主演电影《好人寥寥无几》。

接下来,看一下 Degrees.py。 在顶部,定义了多个数据结构来存储 CSV 文件中的信息。 名字字典是一种通过名字查找人的方法:它将名字映射到一组相应的 ID(因为多个演员可能具有相同的名字)。people 字典将每个人的 id 映射到另一个字典,其中包含该人的姓名、出生年份以及他们主演的所有电影的集合。电影字典将每个电影的 id 映射到另一个包含该电影标题值的字典, 发行年份,以及所有电影明星的阵容。 load_data 函数将数据从 CSV 文件加载到这些数据结构中。

该程序中的主函数首先将数据加载到内存中(加载数据的目录可以通过命令行参数指定)。 然后,该函数提示用户输入两个名字。 person_id_for_name 函数检索任何人的 ID(如果多个人具有相同的名称,则处理提示用户进行澄清)。 然后该函数调用shortest_path函数来计算两个人之间的最短路径,并打印出该路径。

然而,shortest_path 函数尚未实现。这就是我们要实现的地方

具体说明:

完成shortest_path函数的实现,使其返回从具有source id的人到具有target id的人的最短路径。

假设存在从源到目标的路径,您的函数应返回一个列表,其中每个列表项都是从源到目标的路径中的下一个 (movie_id, person_id) 对。 每对应该是两个字符串的元组。

例如,如果shortest_path的返回值为[(1, 2), (3, 4)],则意味着source与人物2一起主演电影1,人物2与人物4一起主演电影3,人物 4 是target。

如果从source到target有多个最小长度的路径,则您的函数可以返回其中任何一个。

如果两个参与者之间没有可能的路径,则您的函数应返回 None。

您可以调用neighbors_for_person函数,该函数接受一个人的id作为输入,并为与给定人一起主演电影的所有人返回一组(movie_id,person_id)对。

尽管您可以编写其他函数和/或导入其他 Python 标准库模块,但除了 Shortest_path 函数之外,您不应修改文件中的任何其他内容。

提示:

虽然课程中搜索的实现会在节点从边界弹出时检查目标,但您可以通过在将节点添加到边界时检查目标来提高搜索效率:如果检测到目标节点,则无需要将其添加到边界,您只需立即返回解决方案即可。

欢迎您借用和改编课程示例中的任何代码。 我们已经为您提供了一个文件 util.py,其中包含 Node、StackFrontier 和 QueueFrontier 的课程实现,欢迎您使用(如果您愿意,也可以进行修改)。

(2)tic-tac-toe

文件说明

该项目中有两个主要文件:runner.py 和 tictactoe.py。 tictactoe.py 包含玩游戏和做出最佳动作的所有逻辑。 runner.py 已为您实现,并且包含运行游戏图形界面的所有代码。 完成 tictactoe.py 中所有必需的功能后,您应该能够运行 python runner.py 来与您的 AI 对战!

让我们打开 tictactoe.py 以了解所提供的内容。 首先,我们定义三个变量:X、O 和 EMPTY,来表示棋盘可能的移动。

函数initial_state 返回板的起始状态。 对于这个问题,我们选择将棋盘表示为三个列表的列表(表示棋盘的三行),其中每个内部列表包含三个值,即 X、O 或 EMPTY。 剩下的函数是我们留给您实现的功能

具体说明

完成player、actions、result、winner、terminal、utility和minimax的实现。

player:应将board作为输入,并返回轮到哪个玩家了(X 或 O)。在初始游戏状态下,X 先走一步。随后,玩家交替进行每个额外的动作。

actions:应该返回一组可以在board上执行的所有可能操作。

每个动作应表示为一个元组 (i, j),其中 i 对应于该移动的行(0、1 或 2),j 对应于该行中与该移动相对应的单元格(也是 0、1 或 2)。可能的移动是棋盘上尚未包含 X 或 O 的任何单元格。

result:将board和action作为输入,并应返回新的board状态,而不修改原始棋盘。

如果action对于board是非法的,应该raise an exception

返回的board应该是通过获取原始输入棋盘并让轮到的玩家在输入动作指示的单元格处移动而产生的棋盘。

重要的是,原始棋盘应保持不变:因为 Minimax 最终需要在计算过程中考虑许多不同的棋盘状态。 这意味着简单地更新板本身的单元并不是结果函数的正确实现。在进行任何更改之前,您可能需要先对板进行deep copy。

winner:应接受一个棋盘作为输入,并返回该棋盘的获胜者(如果有)。如果 X 玩家赢得了游戏,您的函数应返回 X。如果 O 玩家赢得了游戏,您的函数应返回 O。

水平、垂直或对角线连续走三步即可赢得比赛。

您可能会假设最多只有一个获胜者。

如果游戏没有获胜者(因为游戏正在进行中,或者因为平局结束),则该函数应返回 None。

terminal:应该接受board作为输入,并返回一个布尔值来指示游戏是否结束。

如果游戏结束,无论是因为有人赢得了游戏,还是因为所有单元格都已填满而没有人获胜,则该函数应返回 True。

否则,如果游戏仍在进行中,该函数应返回 False。

utility:应接受board作为输入并输出该板的utility。

如果 X 赢得了游戏,则效用为 1。如果 O 赢得了游戏,则效用为 -1。 如果比赛以平局结束,则效用为 0。

您可能会假设只有当terminal为 True 时才会在板上调用utility。

minimax: 应将board作为输入,并返回玩家在该board上移动的最佳移动。

返回的移动应该是最优动作 (i, j),这是棋盘上允许的动作之一。 如果多个移动同样是最佳的,那么任何一个移动都是可以接受的。

如果该板是terminal,则 minimax 函数应返回 None。

对于接受板作为输入的所有函数,您可以假设它是一个有效的板(即它是一个包含三行的列表,每行具有 X、O 或 EMPTY 三个值)。 您不应修改所提供的函数声明(每个函数的参数的顺序或数量)。

一旦所有功能都正确实现,您应该能够run python runner.py 并与您的 AI 比赛。 而且,由于井字游戏是双方都发挥最佳表现的平局,所以你永远无法击败人工智能(尽管如果你表现不佳,它可能会打败你!)

e我还真输了一局

提示

如果您想在不同的 Python 文件中测试函数,可以使用 from tictactoe import initial_state 等行导入它们。

欢迎您向 tictactoe.py 添加其他辅助函数,前提是它们的名称不会与模块中已有的函数或变量名称冲突。

Alpha-beta 剪枝是可选的,可能会让你的 AI 运行更高效!

代码实践

(1)degrees

采用的是bfs,具体思路见上面的算法流程,基本上是一步步对应的,不过多讲解了

具体代码如下:

def shortest_path(source, target):
"""
Returns the shortest list of (movie_id, person_id) pairs
that connect the source to the target. If no possible path, returns None.
""" # TODO
start = Node(state=source, parent=None, action=None)
frontier = QueueFrontier()
frontier.add(start)
explore = set()
while True:
if frontier.empty():
return None
node = frontier.remove()
if node.state == target:
ac = []
while node.parent is not None:
ac.append((node.action,node.state))
node = node.parent
ac.reverse()
return ac
explore.add(node.state)
neighbours = neighbors_for_person(node.state)
for action,state in neighbours:
if not frontier.contains_state(state) and state not in explore:
child = Node(state=state, parent=node, action=action)
frontier.add(child)

(2)tictactoe

前面几个函数都比较简单

minmax函数也是和前面算法流程实现一致

值得注意的是这里并没有使用剪枝函数

还有player的先手判断,涉及到第一步下哪

这里我试了一下发现,如果是ai先手的话运行时间比较慢

感觉是runner.py的逻辑写的有一点问题?

def player(board):
"""
Returns player who has the next turn on a board.
"""
## xo数量判断谁先手,数量一致默认x先手
xcount = 0
ocount = 0
for row in board:
xcount += row.count(X)
ocount += row.count(O)
return X if xcount == ocount else O def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
## 返回棋盘上所有可以下的位置
actionmove = set()
for row_index, row in enumerate(board):
for column_index, item in enumerate(row):
if item is None:
actionmove.add((row_index, column_index))
return actionmove def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
## 返回做出相应行动的棋盘状态
player_move = player(board)
i, j = action
## 根据提示这里采用深拷贝
new_board = deepcopy(board)
if board[i][j] is not None:
raise Exception
else:
new_board[i][j] = player_move return new_board def winner(board):
"""
Returns the winner of the game, if there is one.
"""
# Check rows
for row in board:
if all(item == X for item in row):
return X
elif all(item == O for item in row):
return O # Check columns
for col in range(3):
if all(board[row][col] == X for row in range(3)):
return X
elif all(board[row][col] == O for row in range(3)):
return O # Check diagonals
if board[0][0] == board[1][1] == board[2][2] and board[0][0] in [X, O]:
return board[0][0]
if board[0][2] == board[1][1] == board[2][0] and board[0][2] in [X, O]:
return board[0][2] return None # No winner found def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
if winner(board) is None:
if not actions(board):
return True
else:
return False
else:
return True def utility(board):
"""
Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
if winner(board) == X:
return 1
elif winner(board) == O:
return -1
elif winner(board) is None:
return 0 def minimax(board):
"""
Returns the optimal action for the current player on the board.
""" def max_value(board):
optimal_move = ()
if terminal(board):
return utility(board), optimal_move
else:
v = -10000
for action in actions(board):
minval = min_value(result(board, action))[0]
if minval > v:
v = minval
optimal_move = action
return v, optimal_move def min_value(board):
optimal_move = ()
if terminal(board):
return utility(board), optimal_move
else:
v = 10000
for action in actions(board):
maxval = max_value(result(board, action))[0]
if maxval < v:
v = maxval
optimal_move = action
return v, optimal_move curr_player = player(board) if terminal(board):
return None if curr_player == X:
return max_value(board)[1] else:
return min_value(board)[1]

学习链接

参考代码链接:https://github.com/wbsth/cs50ai

https://github.com/PKUFlyingPig/cs50_ai

视频链接(b站中文机翻字幕): https://www.bilibili.com/video/BV1AQ4y1y7wy/?p=5&vd_source=23b9ed7e58fa7e510011caaf2e7e3320

课程官网(全套资源):https://cs50.harvard.edu/ai/2023/

总结

ai入门课,第一节课感觉很不错,课上讲的知识很好的在代码中实现,但也比较基础

总体花费: 3小时

cs50ai0----search的更多相关文章

  1. [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法

    二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...

  2. Leetcode 笔记 99 - Recover Binary Search Tree

    题目链接:Recover Binary Search Tree | LeetCode OJ Two elements of a binary search tree (BST) are swapped ...

  3. Leetcode 笔记 98 - Validate Binary Search Tree

    题目链接:Validate Binary Search Tree | LeetCode OJ Given a binary tree, determine if it is a valid binar ...

  4. 基于WebGL 的3D呈现A* Search Algorithm

    http://www.hightopo.com/demo/astar/astar.html 最近搞个游戏遇到最短路径的常规游戏问题,一时起兴基于HT for Web写了个A*算法的WebGL 3D呈现 ...

  5. Leetcode: Convert sorted list to binary search tree (No. 109)

    Sept. 22, 2015 学一道算法题, 经常回顾一下. 第二次重温, 决定增加一些图片, 帮助自己记忆. 在网上找他人的资料, 不如自己动手. 把从底向上树的算法搞通俗一些. 先做一个例子: 9 ...

  6. [LeetCode] Closest Binary Search Tree Value II 最近的二分搜索树的值之二

    Given a non-empty binary search tree and a target value, find k values in the BST that are closest t ...

  7. [LeetCode] Closest Binary Search Tree Value 最近的二分搜索树的值

    Given a non-empty binary search tree and a target value, find the value in the BST that is closest t ...

  8. [LeetCode] Verify Preorder Sequence in Binary Search Tree 验证二叉搜索树的先序序列

    Given an array of numbers, verify whether it is the correct preorder traversal sequence of a binary ...

  9. [LeetCode] Search a 2D Matrix II 搜索一个二维矩阵之二

    Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the follo ...

  10. [LeetCode] Lowest Common Ancestor of a Binary Search Tree 二叉搜索树的最小共同父节点

    Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BS ...

随机推荐

  1. 2021-10-08:填充每个节点的下一个右侧节点指针。给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找

    2021-10-08:填充每个节点的下一个右侧节点指针.给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点.填充它的每个 next 指针,让这个指针指向其下一个右侧节点.如果找 ...

  2. 11g ADG级联备库基础测试环境准备

    客户通过duplicate生产备库的方式创建cascade备库. 发现每次都会遇到两个文件报错,ORA-17628: Oracle error 19505错误,且每一次跑,报错文件不一样. 现在想帮客 ...

  3. SICP:元循环求值器(Python实现)

    求值器完整实现代码我已经上传到了GitHub仓库:TinySCM,感兴趣的童鞋可以前往查看.这里顺便强烈推荐UC Berkeley的同名课程CS 61A. 在这个层次结构的最底层是对象语言.对象语言只 ...

  4. SQL Server 2014 英文版安装教程

    安装过程如下 1. 点击setup开始安装. 2. 选择如下的全新安装. 3. 自动生成产品密钥,然后点击下一步. 4. 勾选接受条款,然后点击下一步. 5. 自动更新根据实际情况进行选择,点击下一步 ...

  5. 【Java】包名规范及整理

    目录 前言 包名规范 总结 前言 最近学习Java的时候,有一个 class 需要在每一个 java文件中写一写,然后我喜欢一次实验的java文件放到一个 Package 中,这就导致了持续不断的报错 ...

  6. 解密Prompt7. 偏好对齐RLHF-OpenAI·DeepMind·Anthropic对比分析

    前三章都围绕指令微调,这一章来唠唠RLHF.何为优秀的人工智能?抽象说是可以帮助人类解决问题的AI, 也可以简化成3H原则:Helpful + Honesty + Harmless.面向以上1个或多个 ...

  7. 程序员的 Windows 工具箱「GitHub 热点速览」

    如何精简 Windows 并快速配置开发环境呢?本周特推的 winutil 是一个程序员的 Windows 工具箱,它提供了开发工具的一键安装以及减少系统垃圾的功能,一切为了简洁.高效.同样高效的还有 ...

  8. 代码随想录算法训练营Day50 动态规划

    代码随想录算法训练营 代码随想录算法训练营Day50 动态规划| 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV 123.买卖股票的最佳时机III 题目链接:123.买卖股票的最佳时 ...

  9. 有关ODOO的ORM操作

    1.查询操作 sale_id = self.env['sale.order'].search([]) env将对象实例化,search进行搜索,可以根据需求添加搜索条件 search_count([] ...

  10. WPF 入门笔记 - 03 - 样式基础及控件模板

    原学习路线是按照圣殿骑士的<WPF基础到企业应用系列>的路线走的,但是布局之后直接依赖属性学起来有些僵硬,不太好理解,尝试了文章的前部分内容后放弃,调整为本篇博文内容.笔记路线将按照痕迹g ...