人工智能搜索问题
搜索问题涉及 给定初始状态和目标状态的代理,它返回如何从前者获取到后者的解决方案。
比如导航器应用使用典型的搜索过程。其中代理(程序的思考部分)接收当前位置和所需目的地作为输入,并基于搜索算法返回建议的路径。然而,有很多其他形式的搜索问题,如拼图或迷宫。
-
代理
感知其环境并针对该环境采取行动的实体。例如,在导航器应用中,代理将是汽车的表示形式,该代理需要决定要采取哪些操作才能到达目的地。
-
状态
代理在其环境中的配置。例如,在15个谜题,状态是所有数字排列在棋盘上的任何方式。
-
初始状态
搜索算法从哪个状态开始。在导航器应用中,这将是当前位置。
-
-
行动
可以在状态下做出的选择。更确切地说,操作可以定义为函数。当作为输入接收状态时,返回为输出可以在 state 中执行的操作集。
-
过渡模型
描述在任何状态下执行任何适用操作的结果。更确切地说,过渡模型可以定义为函数。接收状态和操作作为输入时,返回在 状态中执行操作时产生的状态。
-
状态空间
可由任何操作序列从初始状态到达的所有状态的集。
目标测试
确定给定状态是否为目标状态的条件。例如,在导航器应用中,目标测试将是代理的当前位置(汽车的表示形式)是否在目标。如果是 = 问题已解决。如果不是,我们将继续搜索。
路径成本
与给定路径关联的数字成本。例如,导航器应用不会简单地将您带到目标位置;而是将导航器应用带到目标位置。它这样做,同时最大限度地降低路径成本,找到最快的方法,让你达到你的目标状态。
解决搜索问题
-
解决 方案
从初始状态到目标状态的操作序列。
- 最佳解决方案 在所有解决方案中路径成本最低的解决方案。
在搜索过程中,数据通常存储在节点中,一个包含以下数据的数据结构:
- 状态
- 其父节点 ,通过它生成当前节点
- 应用于父级状态以访问当前节点的操作
- 从初始状态到此节点的路径成本
节点包含的信息使其对于搜索算法非常有用。它们包含一个状态,可以使用目标测试检查它是否为最终状态。如果是,节点的路径成本可以与其他节点的路径成本进行比较,从而允许选择最佳解决方案。选择节点后,通过存储父节点和从父节点到当前节点的操作,可以跟踪从初始状态到此节点的每个步骤,并且此操作序列是解决方案。
但是*,节点*只是一个数据结构 - 它们不搜索,它们保存信息。
为了实际搜索,我们使用边界,即"管理"节点的机制。边界首先包含初始状态和一组空的已探索项,然后重复以下操作,直到到达解决方案:
重复:
-
如果边界为空,
- 停下这个问题没有解决办法。
-
从边界中删除节点。这是将考虑的节点。
-
如果节点包含目标状态,
- 返回解决方案。停止。
深度-第一搜索
第 1 阶段,应该删除哪个节点?此选择对解决方案的质量及其实现速度有影响。有多种方法可以先讨论哪些节点,其中两个节点可以通过堆栈(深度优先搜索)和队列*(在广度优先*搜索中)的数据结构来表示。
我们从深度第一搜索 ( DFS) 方法开始.
深度第一搜索算法在尝试另一个方向之前耗尽每个方向。在这些情况下,边界作为堆栈数据结构进行管理。这里你需要记住的流行语是"*最后一个先出"。*将节点添加到边界后,要删除并考虑的第一个节点是最后一个要添加的节点。这将导致一个搜索算法,该算法尽可能深入到第一个方向,该方向会进入,同时将所有其他方向留给以后使用。
(来自外部讲座的一个例子:以你正在寻找钥匙的情况为例。在深度第一搜索方法中,如果您选择从穿裤子搜索开始,首先会浏览每个口袋,清空每个口袋并仔细浏览内容内容。您将停止搜索你的裤子,并开始搜索其他地方,只有一旦你将完全用尽搜索在你的裤子的每一个口袋。
- 优点:
- 充其量,这个算法是最快的。如果它"运气不佳",并且总是选择正确的解决方案路径(偶然*),那么深度第*一搜索需要尽可能小的时间才能找到解决方案。
- 缺点:
- 找到的解决方案可能不是最佳的。
- 在最坏的情况下,此算法将在找到解决方案之前探索所有可能的路径,从而在到达解决方案之前需要尽可能长的时间。
代码示例:
# 定义从边界删除节点并返回它的函数 def remove(self): # 如果边界为空,则终止搜索,因为这意味着没有解决方案。 if self.empty(): raise Exception("empty frontier") else: # 保存列表中的最后一项(这是添加的最新节点) node = self.frontier[-1] # 保存列表中除最后一个节点外的所有项目(即删除最后一个节点) self.frontier = self.frontier[:-1] return node
广度优先搜索
深度优先搜索的对立面是广度优先搜索**(BFS)。
广度优先搜索算法将同时跟随多个方向,在每个可能的方向上迈出一步,然后在每个方向上执行第二步。在这种情况下,边界作为队列数据结构进行管理。这里需要记住的流行语是"先*出先出"。*在这种情况下,所有新节点都成行,并且正在根据先添加的节点来考虑节点(先到先得!这将导致搜索算法,该算法在朝任何一个方向执行第二步之前,在每个可能的方向上执行一个步骤。
(来自外部讲座的一个例子:假设您正在查找钥匙。在这种情况下,如果你开始你的裤子,你会看看你的右口袋。在此之后,你将看看一个抽屉,而不是看你的左口袋。然后在桌子上。等等,在每一个你都想到的地方。只有在你用尽了所有的位置后,你才能回到你的裤子和搜索下一个口袋。
- 优点:
- 该算法保证能找到最佳解决方案。
- 缺点:
- 此算法几乎可以保证比最短的运行时间更长的时间。
- 在最坏的情况下,此算法需要最长的时间运行。
代码示例:
# 定义从边界删除节点并返回它的函数 def remove(self): # 如果边界为空,则终止搜索,因为这意味着没有解决方案 if self.empty(): raise Exception("empty frontier") else: #如果边界为空,则终止搜索,因为这意味着没有解决方案 node = self.frontier[0] #保存列表中除第一个节点外的所有项(即删除第一个节点) self.frontier = self.frontier[1:] return node
理解:深度搜索像舔狗,一旦发现喜欢的女神,会很深情地追求,直到美女告诉他:“傻瓜,你没有机会了。”然后,舔狗又回到了原来的地方,再去寻找另一个女神,继续舔,继续被拒绝,继续返回,直到找到喜欢他的女神(也可能最后没找到)
而广度搜索像海王,每走一步,他都要寻找可以周围一起约会的女孩,之后他会和约会的女孩们继续联系,直到他发现这个是真的白富美,约会结束。
总之,深度搜索就是不到黄河不死心,广搜就是周围的都查一下
贪婪最佳搜索
广度优先和深度优先都是不知情的搜索算法。也就是说,这些算法没有利用任何知识,他们没有得到通过自己的探索获得的问题。然而,最常见的情况是,事实上,对这个问题的一些知识是可用的。例如,当人类迷宫解算器进入一个交汇点时,人类可以看到哪种方向是解决方案的大方向.AI哪些方式不。一种考虑其他知识以提高其性能的算法类型称为知情搜索算法。
贪婪的"最好"搜索将展开最接近目标的节点,由启发函数 h(n) 确定。正如其名称建议的那样,函数估计下一个节点离目标有多近,但它可能是错误的。贪婪的"最好先"算法的效率取决于启发式函数的多少。例如,在迷宫中,算法可以使用启发式函数,该函数依赖于可能节点和迷宫结束之间的曼哈顿距离。曼哈顿的距离忽略了墙壁,并计算从一个位置到目标位置需要多少步,向上、向下或到两侧。这是一个简单的估计,可以基于当前位置和目标位置的 (x, y) 坐标派生。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kwNkjlBa-1605371647284)(https://i.loli.net/2020/11/15/JQ9G6i2xDlLP8qm.png)]
曼哈顿距离
但是,重要的是要强调,与任何启发式方法一样,它可能会出错,并导致算法走比本来要慢的路径。不知情的搜索算法可能会更快地提供更好的解决方案,但与知情算法相比,它不太可能这样做。
A* 搜索
A* 搜索是贪婪的"最先"算法的发展,它不仅考虑h(n),即从当前位置到目标的估计成本,还考虑 g(n),直到当前位置累积的成本。通过结合这两个值,该算法具有更准确的方式来确定解决方案的成本并优化其选择。该算法跟踪(路径成本到现在 = 目标的估计成本),一旦超过某些先前选项的估计成本,该算法将放弃当前路径并返回前一个选项,从而防止自身沿着*h(n)*错误地标记为最佳的长而低效的路径前进。
再次,由于这种算法也依赖于启发式,它和它所采用的启发式算法一样好。在某些情况下,它的效率可能低于贪婪的最佳第一搜索,甚至不知情的算法。使A**搜索最佳,启发函数h(n)*应为:
- 可受理,或从不高估真正的成本,
- 一致,这意味着新节点目标的估计路径成本,以及从上一个节点转换到该节点的成本,都大于或等于前一个节点目标的估计路径成本。以等式形式表示*,如果每个节点 n 和后续节点n’与步*进成本 c、h(n) ≤ h(n’) = c ) ≤ h(n’) = c ) 是一致的。
对抗搜索
然而,以前,我们已经讨论过需要找到问题的答案的算法,在对抗性搜索中,算法面对的是试图实现相反目标的对手。通常,使用对抗搜索的 AI 会玩到游戏,如 井字游戏。
极大 极小
Minimax是对抗搜索中的一种算法,它表示一方的获胜条件为 (-1),另一方表示 (+1)。进一步的行动将受这些条件的驱动,最小化方试图获得最低分数,而最大化者则试图获得最高分。
代表 Tic-Tac-Toe AI:
- *S₀:*初始状态(在我们的案例中,空的 3X3 板)
- 玩家:一个函数,给定一个状态s,返回哪个玩家轮到它(X 或 O)。
- 操作:一个函数,给定一个状态*,*返回此状态中的所有合法移动(板上哪些点是免费的)。
- 结果(s,a):给定状态和操作 a的函数返回新状态。这是由于执行状态a的操作*(在游戏中移动*)而导致的板。
- 终端:一个函数,给定一个状态,检查这是否是游戏中的最后一步,即如果有人赢了或有平局。如果游戏已结束,则返回 True,否则为 False。
- 实用程序:给定终端状态 s 的函数返回状态的效用值:-1、0 或 1。
算法的工作原理:
递归,该算法模拟所有可能的游戏,可以从当前状态开始,直到达到终端状态。每个终端状态被估价为 (-1)、0 或 (+1)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXeSL9Mt-1605371647286)(https://cs50.harvard.edu/ai/2020/notes/0/minimax_tictactoe.png)]
Tic-Tac-Toe 中的迷你最大算法
根据轮到谁的状态,算法可以知道当前玩家在以最佳方式播放时,是否会选择导致具有较低或更高值的状态的操作。这样,在最小化和最大化之间交替,算法为每种可能的操作所导致的状态创建值。为了给出一个更具体的例子,我们可以想像,最大化玩家每时每时每时每时每时每要问:"如果我采取这个行动,就会产生一个新的状态。如果最小化的玩家发挥最佳,该玩家可以采取什么行动,使价值降到最低?"然而,要回答这个问题,最大化玩家必须问:"要知道最小化玩家将做什么,我需要模拟最小化玩家头脑中的相同过程:最小化玩家会尝试问:'如果我采取这个行动,最大化玩家可以采取什么行动才能带来最高价值?这是一个递归的过程,它可能很难包裹你的头周围;查看下面的伪代码会有所帮助。最终,通过这个递归推理过程,最大化的玩家会为每个状态生成值,这些值可能来自当前状态下的所有可能操作。拥有这些值后,最大化玩家选择最高的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g4L8AITo-1605371647287)(https://cs50.harvard.edu/ai/2020/notes/0/minimax_theoretical.png)]
最大化者考虑未来国家的可能值。
要用伪代码表示,Minimax 算法的工作方式如下:
-
给定状态s
- 最大化玩家在动作中**选择产生最小值(结果*,a)的最高值的动作*a 。
- 最小化的玩家在"操作*"中选择产生*最大值(*结果,a)*最低值的操作 a 。
-
函数最大值(状态)
v = - ∞
如果终端(状态):
返回实用程序(状态)
用于操作(状态) 中的操作:
v = 最大值(v,最小值(结果(状态、操作))
返回v
-
函数最小值(状态):
-
v = ∞
-
如果终端(状态):
返回实用程序(状态)
-
用于操作(状态) 中的操作:
v = 最小值(v,最大值(结果(状态、操作))
返回v
-
阿尔法-贝塔修剪
优化Minimax 的一种方法,Alpha-Beta 修剪跳过了一些完全不利的递归计算。在确定一个动作的价值后,如果有初步证据表明以下动作可以使对手获得比已经建立的动作更好的分数,则无需进一步调查此行动,因为它显然不如先前确立的。
这很容易用一个例子来显示:最大化玩家知道,在下一步,最小化的玩家将尝试达到最低分数。假设最大化玩家有三个可能的动作,第一个动作的估值为 4。然后玩家开始为下一个动作生成值。为此,如果当前玩家执行此操作,则玩家将生成最小化器操作的值,但知道最小化器将选择最低操作。但是,在完成最小化器的所有可能操作的计算之前,玩家会看到其中一个选项的值为 3。这意味着没有理由继续探索其他可能的行动,以尽量减少玩家。尚未估价的行动的价值并不重要,无论是 10 还是 (-10)。如果值为 10,则最小化器将选择最低选项 3,该选项已比预先设置的 4 差。如果尚未估价的操作结果为 (-10),则最小化器将此选项 (-10),这对最大化器更加不利。因此,此时为最小化器计算其他可能的操作与最大化器无关,因为最大化玩家已经拥有了明确更好的选择,其值为 4。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzvTK3Vv-1605371647292)(https://cs50.harvard.edu/ai/2020/notes/0/alphabeta.png)]
深度限制迷你最大值
总共有255,168个可能的Tic Tac脚趾游戏,和102⁹10000可能的游戏在国际象棋。到目前为止,minimax 算法需要从某个点生成所有假设游戏到终端条件。虽然计算所有的 Tic-Tac-Toe 游戏对现代计算机来说并不构成挑战,但使用国际象棋目前是不可能的。
深度限制 Minimax只考虑在停止之前预定义的移动数,而永远不会进入终端状态。但是,这不允许获得每个操作的精确值,因为尚未达到假设游戏的末尾。为了解决此问题,深度限制的 Minimax 依赖于一个评估函数,该函数估计游戏从给定状态的预期效用,或者换句话说,将值分配给状态。例如,在国际象棋游戏中,实用程序函数将作为棋盘的当前配置进行输入,尝试评估其预期效用(根据每个玩家拥有的棋子及其在棋盘上的位置),然后返回一个正值或负值,表示棋盘对一个玩家相对于另一个玩家的优值。这些值可用于决定正确的操作,评估函数越好,依赖该值的 Minimax 算法也越好。
-
进入终端状态。但是,这不允许获得每个操作的精确值,因为尚未达到假设游戏的末尾。为了解决此问题,深度限制的 Minimax 依赖于一个评估函数,该函数估计游戏从给定状态的预期效用,或者换句话说,将值分配给状态。例如,在国际象棋游戏中,实用程序函数将作为棋盘的当前配置进行输入,尝试评估其预期效用(根据每个玩家拥有的棋子及其在棋盘上的位置),然后返回一个正值或负值,表示棋盘对一个玩家相对于另一个玩家的优值。这些值可用于决定正确的操作,评估函数越好,依赖该值的 Minimax 算法也越好。