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. Django4全栈进阶之路8 createsuperuser创建超级管理员账号

    在 Django 4 中,可以使用 createsuperuser 命令来创建超级管理员账号.超级管理员拥有管理后台的所有权限,包括创建.编辑和删除用户.组.权限等操作. 下面是创建超级管理员账号的步 ...

  2. 基于Expression Lambda表达式树的通用复杂动态查询构建器——《构思篇一》

    在上一篇中构思了把查询子句描述出来的数据结构,那么能否用代码将其表达出来,如何表达呢? 再次回顾考察,看下面的查询子句: Id>1 and Id<10 如上所示,有两个独立的条件分别为Id ...

  3. Kubernetes GoRoutineMap工具包代码详解

    1.概述 GoRoutineMap 定义了一种类型,可以运行具有名称的 goroutine 并跟踪它们的状态.它防止创建具有相同名称的多个goroutine,并且在上一个具有该名称的 goroutin ...

  4. 分布式数据库 Join 查询设计与实现浅析

    相对于单例数据库的查询操作,分布式数据查询会有很多技术难题. 本文记录 Mysql 分库分表 和 Elasticsearch Join 查询的实现思路,了解分布式场景数据处理的设计方案. 文章从常用的 ...

  5. 纠删码技术在vivo存储系统的演进【上篇】

    作者:vivo 互联网服务器团队- Gong Bing 本文将学术界和工业界的纠删码技术的核心研究成果进行了相应的梳理,然后针对公司线上存储系统的纠删码进行分析,结合互联网企业通用的IDC资源.服务器 ...

  6. JS工具函数

    工具函数 用于工程化开发,记录,备用 返回 [min, max) 间的随机整数 /** 返回 [min, max) 间的随机整数 */ export function getRandom(min, m ...

  7. PyTorch与机器学习中的随机化:减少噪声和随机性

    目录 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成与测试 4. 应用示例与代码实现讲 ...

  8. Apple、AWS 这些科技巨头,已悄然入局隐私计算

    随着数字化时代的到来,数据已经成为企业竞争的重要资源.然而,与此同时,数据隐私泄露的风险也在不断增加,这已经成为了公共安全和个人权利保护的重要问题.为了解决这个问题,科技巨头谷歌.苹果.亚马逊纷纷入局 ...

  9. 组合数学_第4章_Polya定理

    第4章 Polya定理 4.1 群的概念 4.1.1 群的定义 给定一个集合\(G=\{a,b,c,\cdots\}\)和集合\(G\)上的二元运算"\(\cdot\)",并满足下 ...

  10. Go 语言 context 都能做什么?

    原文链接: Go 语言 context 都能做什么? 很多 Go 项目的源码,在读的过程中会发现一个很常见的参数 ctx,而且基本都是作为函数的第一个参数. 为什么要这么写呢?这个参数到底有什么用呢? ...