人工智能第一次作业

作业题目:

  1. 你对人工智能定义的理解?

人工智能是智能科学中涉及研究、设计与应用智能机器和智能系统的一个分支,而智能科学是一门与计算机科学并行的学科。

人工智能是智能机器所执行的通常与人类智能有关的智能行为,这些智能行为涉及学习、感知、思考、理解、识别、判断、推理、证明、通信、设计、规划、行动和问题求解等活动。

  1. 解释什么是图灵测试?

图灵测试(The Turing test)由艾伦·麦席森·图灵发明,指测试者与被测试者(一个人和一台机器)隔开的情况下,通过一些装置(如键盘)向被测试者随意提问。进行多次测试后,如果机器让平均每个参与者做出超过30%的误判,那么这台机器就通过了测试,并被认为具有人类智能。图灵测试一词来源于计算机科学和密码学的先驱艾伦·麦席森·图灵写于1950年的一篇论文《计算机器与智能》,其中30%是图灵对2000年时的机器思考能力的一个预测,目前我们已远远落后于这个预测。

  1. 你希望人工智能帮你做些什么?

回答问题:回答关于各种领域的问题,对人所不知道的提供信息和解释概念。

写作和编辑:撰写文章、报告、邮件和其他文本。

编程和技术支持:解决编程问题、编写代码或提供技术支持。

语言翻译

根据用户的需求提供帮助和支持,使我们的生活更加便捷和高效。

4、8数码问题,如上图所示的过程,要求:空格按照左、上、右、下的顺序依次扩展。

分别用BFS、DFS(深度为5)、UCS、Greedy (启发函数h自拟) 、A*(启发函数h自拟)算法进行求解。

BFS:通过广度优先搜索从初始状态逐步向目标状态前进,记录每一步的移动操作,直到找到解决方案。

创建一个双端队列(deque),用于进行广度优先搜索。

队列中的元素是一个三元组 (当前状态, 移动序列, 步数),其中:

当前状态 表示当前的数码状态。

移动序列 用于记录达到当前状态的移动序列。

步数 表示从初始状态到当前状态的步数。

from collections import deque
# 定义初始状态和目标状态
initial_state = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
goal_state = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
# 定义移动方向:左、上、右、下
directions = [(0, -1), (-1, 0), (0, 1), (1, 0)]
direction_names = ['Left', 'Up', 'Right', 'Down']
# 创建一个队列,用于BFS
queue = deque([(initial_state, "", 0)])  # (当前状态,移动序列,步数)
# BFS算法
while queue:
    current_state, move_sequence, steps = queue.popleft()
    # 检查是否达到目标状态
    if current_state == goal_state:
        print("找到解决方案!")
        print("移动序列:", move_sequence)
        print("步数:", steps)
        break
    # 扩展当前状态
    empty_row, empty_col = None, None
    for row in range(3):
        for col in range(3):
            if current_state[row][col] == 0:
                empty_row, empty_col = row, col
    for i in range(4):
        new_row, new_col = empty_row + directions[i][0], empty_col + directions[i][1]
        if 0 <= new_row < 3 and 0 <= new_col < 3:
            # 创建新状态
            new_state = [list(row) for row in current_state]
            new_state[empty_row][empty_col], new_state[new_row][new_col] = new_state[new_row][new_col], \
                                                                           new_state[empty_row][empty_col]
            new_move_sequence = move_sequence + direction_names[i]
            # 将新状态加入队列
            queue.append((new_state, new_move_sequence, steps + 1))

DFS:通过深度优先搜索从初始状态开始,沿着一个分支一直深入到底,直到找到解决方案或达到深度限制。然后,它会回溯到前一个状态,继续搜索其他分支,直到找到解决方案为止。

创建一个堆栈(deque),用于进行深度优先搜索。

堆栈中的元素是一个三元组 (当前状态, 移动序列, 步数),其中:

当前状态 表示当前的数码状态。

移动序列 用于记录达到当前状态的移动序列。

步数 表示从初始状态到当前状态的步数。

from collections import deque
# 定义初始状态和目标状态
initial_state = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
goal_state = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
# 定义移动方向:左、上、右、下
directions = [(0, -1), (-1, 0), (0, 1), (1, 0)]
direction_names = ['Left', 'Up', 'Right', 'Down']
# 创建一个堆栈,用于DFS
stack = deque([(initial_state, "", 0)])  # (当前状态,移动序列,步数)
# DFS算法
while stack:
    current_state, move_sequence, steps = stack.pop()
    # 检查是否达到目标状态
    if current_state == goal_state:
        print("找到解决方案!")
        print("移动序列:", move_sequence)
        print("步数:", steps)
        break
    # 扩展当前状态
    empty_row, empty_col = None, None
    for row in range(3):
        for col in range(3):
            if current_state[row][col] == 0:
                empty_row, empty_col = row, col
    if steps < 5:  # 深度限制为5
        for i in range(4):
            new_row, new_col = empty_row + directions[i][0], empty_col + directions[i][1]
            if 0 <= new_row < 3 and 0 <= new_col < 3:
                # 创建新状态
                new_state = [list(row) for row in current_state]
                new_state[empty_row][empty_col], new_state[new_row][new_col] = new_state[new_row][new_col], \
                                                                               new_state[empty_row][empty_col]
                new_move_sequence = move_sequence + direction_names[i]
                # 将新状态加入堆栈
                stack.append((new_state, new_move_sequence, steps + 1))

UCS:通过 UCS 算法以最小代价的方式从初始状态开始,逐步扩展状态直到找到目标状态为止。

创建一个优先队列(priority_queue),用于进行UCS搜索。

优先队列中的元素是元组 (total_cost, current_state, move_sequence, steps),按照 total_cost 的大小进行优先级排序。

使用 UCS 算法进行搜索:

不断从优先队列中弹出具有最小 total_cost 的节点。

import heapq
# 定义初始状态和目标状态
initial_state = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
goal_state = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
# 定义移动方向:左、上、右、下
directions = [(0, -1), (-1, 0), (0, 1), (1, 0)]
direction_names = ['Left', 'Up', 'Right', 'Down']
# 定义初始节点
initial_node = (0, initial_state, "", 0)  # (总代价,当前状态,移动序列,步数)
# 创建一个优先队列,用于UCS
priority_queue = [initial_node]
# UCS算法
while priority_queue:
    total_cost, current_state, move_sequence, steps = heapq.heappop(priority_queue)
    # 检查是否达到目标状态
    if current_state == goal_state:
        print("找到解决方案!")
        print("移动序列:", move_sequence)
        print("步数:", steps)
        break
    # 扩展当前状态
    empty_row, empty_col = None, None
    for row in range(3):
        for col in range(3):
            if current_state[row][col] == 0:
                empty_row, empty_col = row, col
    for i in range(4):
        new_row, new_col = empty_row + directions[i][0], empty_col + directions[i][1]
        if 0 <= new_row < 3 and 0 <= new_col < 3:
            # 创建新状态
            new_state = [list(row) for row in current_state]
            new_state[empty_row][empty_col], new_state[new_row][new_col] = new_state[new_row][new_col], \
                                                                           new_state[empty_row][empty_col]
            new_move_sequence = move_sequence + direction_names[i]
            new_cost = steps + 1
            # 计算总代价
            new_total_cost = new_cost
            # 将新状态加入优先队列
            heapq.heappush(priority_queue, (new_total_cost, new_state, new_move_sequence, new_cost))

Greedy:Greedy算法是一种启发式搜索算法,它在每一步都选择具有最小启发函数值的状态,因此它可能会陷入局部最优解,但通常运行速度很快。我使用不匹配数量作为启发函数,以贪婪的方式尝试接近目标状态。

定义启发函数(heuristic function):

heuristic(state) 计算每个状态与目标状态的不匹配的数量,用于评估状态的优劣。这里的启发函数采用了"不匹配数量",即当前状态中与目标状态不相同的方格的数量,作为估计的代价。

创建一个优先队列(priority_queue),用于进行Greedy算法搜索。使用Greedy算法进行搜索:

不断从优先队列中弹出具有最小 heuristic_value 的节点。

from collections import deque
# 定义初始状态和目标状态
initial_state = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
goal_state = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
# 定义移动方向:左、上、右、下
directions = [(0, -1), (-1, 0), (0, 1), (1, 0)]
direction_names = ['Left', 'Up', 'Right', 'Down']
# 定义启发函数:计算每个状态与目标状态的不匹配的数量
def heuristic(state):
    mismatch_count = 0
    for row in range(3):
        for col in range(3):
            if state[row][col] != goal_state[row][col]:
                mismatch_count += 1
    return mismatch_count
# 创建一个优先队列,用于Greedy 算法
priority_queue = deque([(initial_state, "", 0, 0)])  # (当前状态,移动序列,启发函数值,步数)
# Greedy 算法
while priority_queue:
    current_state, move_sequence, heuristic_value, steps = priority_queue.popleft()
    # 检查是否达到目标状态
    if current_state == goal_state:
        print("找到解决方案!")
        print("移动序列:", move_sequence)
        print("步数:", steps)
        break
    # 扩展当前状态
    empty_row, empty_col = None, None
    for row in range(3):
        for col in range(3):
            if current_state[row][col] == 0:
                empty_row, empty_col = row, col
    for i in range(4):
        new_row, new_col = empty_row + directions[i][0], empty_col + directions[i][1]
        if 0 <= new_row < 3 and 0 <= new_col < 3:
            # 创建新状态
            new_state = [list(row) for row in current_state]
            new_state[empty_row][empty_col], new_state[new_row][new_col] = new_state[new_row][new_col], \
                                                                           new_state[empty_row][empty_col]
            new_move_sequence = move_sequence + direction_names[i]
            new_heuristic_value = heuristic(new_state)
            new_steps = steps + 1
            # 将新状态加入优先队列,按照启发函数值排序
            priority_queue.append((new_state, new_move_sequence, new_heuristic_value, new_steps))
            priority_queue = deque(sorted(priority_queue, key=lambda x: x[2]))

A*算法:A*算法是一种启发式搜索算法,它综合考虑了每个状态的启发函数值和已经花费的代价(步数),以找到最优的路径。我利用不匹配数量作为启发函数,并按照总代价(启发函数值 + 步数)的大小进行搜索,以确保在估计代价和实际代价之间取得平衡,找到最优的解决方案。

使用A*算法进行搜索:

不断从优先队列中弹出具有最小 total_cost 的节点。

检查当前状态是否达到目标状态,如果是,输出解决方案并结束搜索。

如果当前状态不是目标状态,则扩展当前状态,生成新的可能状态,并将它们加入优先队列中继续搜索。

扩展状态的过程:

找到当前状态中的空格的位置 (empty_row, empty_col)。

针对空格的四个移动方向,尝试生成新的状态,即将空格与相邻方格进行交换,生成一个新的数码状态。

计算新的 heuristic_value,这里使用启发函数计算。

计算新的 steps,即步数。

计算新的 total_cost,即启发函数值与步数之和。

将新状态加入优先队列,并按照 total_cost 的大小重新排序队列,以确保按照总代价的A*策略进行搜索。

from collections import deque
# 定义初始状态和目标状态
initial_state = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
goal_state = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
# 定义移动方向:左、上、右、下
directions = [(0, -1), (-1, 0), (0, 1), (1, 0)]
direction_names = ['Left', 'Up', 'Right', 'Down']
# 创建一个优先队列,用于A*算法
priority_queue = deque([(initial_state, "", 0, 0)])  # (当前状态,移动序列,启发函数值,步数)
# 定义启发函数:计算每个状态与目标状态的不匹配的数量
def heuristic(state):
    mismatch_count = 0
    for row in range(3):
        for col in range(3):
            if state[row][col] != goal_state[row][col]:
                mismatch_count += 1
    return mismatch_count
# A*算法
while priority_queue:
    current_state, move_sequence, heuristic_value, steps = priority_queue.popleft()
    # 检查是否达到目标状态
    if current_state == goal_state:
        print("找到解决方案!")
        print("移动序列:", move_sequence)
        print("步数:", steps)
        break
    # 扩展当前状态
    empty_row, empty_col = None, None
    for row in range(3):
        for col in range(3):
            if current_state[row][col] == 0:
                empty_row, empty_col = row, col
    for i in range(4):
        new_row, new_col = empty_row + directions[i][0], empty_col + directions[i][1]
        if 0 <= new_row < 3 and 0 <= new_col < 3:
            # 创建新状态
            new_state = [list(row) for row in current_state]
            new_state[empty_row][empty_col], new_state[new_row][new_col] = new_state[new_row][new_col], \
                                                                           new_state[empty_row][empty_col]
            new_move_sequence = move_sequence + direction_names[i]
            new_heuristic_value = heuristic(new_state)
            new_steps = steps + 1
            # 计算总代价(启发函数值 + 步数)
            new_total_cost = new_heuristic_value + new_steps
            # 将新状态加入优先队列,按照总代价排序
            priority_queue.append((new_state, new_move_sequence, new_heuristic_value, new_steps))
            priority_queue = deque(sorted(priority_queue, key=lambda x: x[2] + x[3]))

alpha-beta-large.png

  1. 请分别用极小化极大算法和α-β剪枝算法计算博弈树

 极小化极大算法: minimax 函数是算法的核心,它采用了递归的方式来搜索博弈树,同时应用了极小化极大策略。这个函数的参数包括当前轮到哪个玩家(player)和当前博弈树节点(node)。

如果轮到玩家1(player == 1),则选择子节点中的最大值作为当前节点的值;如果轮到玩家0,则选择子节点中的最小值作为当前节点的值。这是博弈树中玩家1追求最大化结果,而玩家0追求最小化结果的基本原则。

递归结束后,将当前节点的值设置为计算出的最大值或最小值,并返回该值。   

# 极小化极大算法
def minimax(player, node):
    if not node["value"]:
        values = []
        for child in node["children"]:
            values.append(minimax(player ^ 1, child))
        if player == 1:
            node["value"] = max(values)
        else:
            node["value"] = min(values)
    return node["value"]
# 构建完全二叉树
tree1 = {
    "value": None,
    "children": [
        {
            "value": None,
            "children": [
                {
                    "value": None,
                    "children": [
                        {
                            "value": 10,
                            "children": [],
                        },
                        {
                            "value": 6,
                            "children": [],
                        },
                    ],
                },
                {
                    "value": None,
                    "children": [
                        {
                            "value": 100,
                            "children": [],
                        },
{
                            "value": 8,
                            "children": [],
                        },
                    ],
                },
            ],
        },
        {
            "value": None,
            "children": [
                {
                    "value": None,
                    "children": [
                        {
                            "value": 1,
                            "children": [],
                        },
                        {
                            "value": 2,
                            "children": [],
                        },
                    ],
                },
                {
                    "value": None,
                    "children": [
                        {
                            "value": 20,
                            "children": [],
                        },
                        {
                            "value": 4,
                            "children": [],
                        },
                    ],
                },
            ],
        },
    ],
}
def main(tree):
    res = minimax(1, tree)
    print("最佳值:", res)
    print(tree)
main(tree1)

α-β剪枝算法:在极小化极大算法中应用α-β剪枝,以加速博弈树的搜索过程。这种技术可以显著减少搜索的分支数,提高算法的效率,特别是在博弈树很大的情况下。

根据α-β剪枝的原则来更新 alpha 和 beta。如果在搜索过程中发现某个子节点的值已经超出了当前玩家的上界 beta(对于玩家0)或下界 alpha(对于玩家1),则可以剪枝,停止继续搜索该分支,因为已经找到更好的选择。

# 极小化极大算法 + α-β剪枝
def alpha_beta(player, node, alpha, beta):
    if not node["value"]:
        values = []
        for child in node["children"]:
            val = alpha_beta(player ^ 1, child, alpha, beta)
            values.append(val)
            if player == 1:
                # max层 子元素的值大于当前下界的值 更新下界
                alpha = max(alpha, val)
                # max层 下界大于等于上界直接返回下界(返回较大者)
                if alpha >= beta:
                    return alpha
            else:
                # min层 子元素的值小于当前上界的值 更新上界
                beta = min(beta, val)
                # min层 下界大于等于上界直接返回上界(返回较小者)
                if alpha >= beta:
                    return beta
        # 返回当前层节点的极值,便于父元素进行剪枝(并不是返回alpha或者beta)
        if player == 1:
            node["value"] = max(values)
        else:
            node["value"] = min(values)
    return node["value"]

(二叉树构建部分与上面相同)

8a91b34d4ed958911499ebd857bca16

  1. 课程设计作业:井字棋

在3*3格子上进行的连珠游戏,和五子棋比较类似,由于棋盘一般不画边框,格线排成井字故得名。游戏由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X)。由最先在任意一条直线上成功连接三个标记的一方获胜。

使用深度优先搜索、结合极小化极大搜索、结合α-β剪枝算法分别设计出井字棋游戏。

编程语言不限。

输入用鼠标或键盘操作:(三选一)

①可以用鼠标选择人类的着棋位置

②也可以用上下左右键选择位置按Enter键着子

③也可以通过输入坐标(0,1)来输入着棋位置

输出:

①可以通过图形界面实时输出

②也可以通过在控制台打印3*3数组的形式输出没步任何电脑着棋后的状态例如:print(“XOO\nOXX\nOOX\n”)

设计思路:

输入操作:通过输入坐标(0,1)来输入着棋位置

输出:在控制台打印3*3数组的形式输出每步电脑着棋后的状态

check_win(board, player) 函数:检查是否有获胜者。它会检查每一行、每一列以及两个对角线上是否有连续的三个相同符号("X"或"O"),如果有,就返回True。

check_tie(board) 函数:检查游戏是否平局。如果棋盘上没有空格(" "),则返回True。

get_player_move(board) 函数:获取玩家的落子位置。通过输入行数和列数,然后检查该位置是否为空,如果为空,则返回选择的位置。

minimax(board, depth, alpha, beta, maximizing) 函数:在深度优先搜索的基础上实现极小化极大算法(Minimax)结合α-β剪枝。这是一个递归函数,用于模拟玩家和电脑的决策过程,以确定最佳落子位置。如果当前局面有玩家获胜("X"胜利)返回-10,如果电脑获胜("O"胜利)返回10,如果平局返回0。

如果当前是最大化玩家(电脑)的回合(maximizing为True),则尝试所有可能的落子位置,对每个位置递归调用minimax来计算分数。同时,使用α-β剪枝来减少搜索空间,如果发现某个分支已经比β大,就可以剪枝不再搜索,因为这个分支对最终结果不会有影响。最终返回最高分数。

如果当前是最小化玩家(玩家)的回合(maximizing为False),同样尝试所有可能的落子位置,对每个位置递归调用minimax来计算分数。同时,使用α-β剪枝来减少搜索空间,如果发现某个分支已经比α小,就可以剪枝不再搜索,因为这个分支对最终结果不会有影响。最终返回最低分数。

play_game() 函数:这是游戏的主函数,包括玩家选择先后手、游戏主循环。在游戏主循环中,根据玩家选择的先后手,交替进行玩家和电脑的回合。玩家的回合通过输入来获取落子位置,电脑的回合通过minimax函数来计算最佳落子位置。

游戏开始后,玩家可以选择先手("X")或者电脑先手("O"),然后游戏会交替进行,直到有一方获胜或者游戏平局。

设计的关键在于minimax函数,它通过递归搜索棋盘状态树来找到最佳落子位置,同时通过α-β剪枝来减少搜索时间,从而提高了计算效率。我把minimax算法的搜索深度(depth)设置为9,表示电脑将考虑9步后的棋盘状态来做出决策。增加搜索深度会使电脑更聪明,但也会增加计算时间。根据我的测试,9步时的电脑我已经无法战胜,且运行时间较快,可以很好的满足要求。

源码:

import math

def print_board(board):
    # 打印游戏棋盘
    for row in board:
        print(" | ".join(row))
    print()

def check_win(board, player):
    # 检查是否有获胜者
    for i in range(3):
        if all(board[i][j] == player for j in range(3)):
            return True
        if all(board[j][i] == player for j in range(3)):
            return True
    if all(board[i][i] == player for i in range(3)):
        return True
    if all(board[i][2 - i] == player for i in range(3)):
        return True
    return False

def check_tie(board):
    # 检查是否平局
    for row in board:
        for cell in row:
            if cell == " ":
                return False
    return True

def get_player_move(board):
    # 获取玩家落子位置
    while True:
        try:
            row = int(input("输入行数 (1-3): ")) - 1
            col = int(input("输入列数 (1-3): ")) - 1
            if board[row][col] == " ":
                return row, col
            else:
                print("这个位置已经被占,请重新输入.")
        except ValueError:
            print("请输入1-3之间的数.")

def minimax(board, depth, alpha, beta, maximizing):
    # 极大极小/α-β剪枝算法
    if check_win(board, "X"):
        return -10, None
    if check_win(board, "O"):
        return 10, None
    if check_tie(board):
        return 0, None
    if maximizing:
        best_score = -math.inf
        best_move = None
        for row in range(3):
            for col in range(3):
                if board[row][col] == " ":
                    board[row][col] = "O"
                    score, _ = minimax(board, depth - 1, alpha, beta, False)
                    board[row][col] = " "
                    if score > best_score:
                        best_score = score
                        best_move = (row, col)
                    alpha = max(alpha, best_score)
                    if beta <= alpha:
                        break
        return best_score, best_move
    else:
        best_score = math.inf
        best_move = None
        for row in range(3):
            for col in range(3):
                if board[row][col] == " ":
                    board[row][col] = "X"
                    score, _ = minimax(board, depth - 1, alpha, beta, True)
                    board[row][col] = " "
                    if score < best_score:
                        best_score = score
                        best_move = (row, col)
                    beta = min(beta, best_score)
                    if beta <= alpha:
                        break
        return best_score, best_move

def play_game():
    board = [[" " for j in range(3)] for i in range(3)]
    print_board(board)
    # 玩家选择先后手
    while True:
        user_choice = input("选择X或者O(X先手): ")
        if user_choice.lower() == "x":
            player = "X"
            break
        elif user_choice.lower() == "o":
            player = "O"
            break
        else:
            print("请输入X或O.")
    # 游戏主循环
    while True:
        if player == "X":
            # 玩家先手
            while not check_win(board, "X") and not check_tie(board):
                row, col = get_player_move(board)
                board[row][col] = "X"
                print_board(board)
                if check_win(board, "X"):
                    print("玩家获胜!")
                    return
                if check_tie(board):
                    print("平局!")
                    return
                _, (row, col) = minimax(board, 9, -math.inf, math.inf, True)
                print("电脑思考后落子", (row + 1, col + 1))
                board[row][col] = "O"
                print_board(board)
                if check_win(board, "O"):
                    print("玩家失败!")
                    return
                if check_tie(board):
                    print("平局!")
                    return
        else:
            # 电脑先手
            while not check_win(board, "O") and not check_tie(board):
                _, (row, col) = minimax(board, 9, -math.inf, math.inf, True)
                print("电脑思考后落子", (row + 1, col + 1))
                board[row][col] = "X"
                print_board(board)
                if check_win(board, "X"):
                    print("玩家失败!")
                    return
                if check_tie(board):
                    print("平局!")
                    return
                row, col = get_player_move(board)
                board[row][col] = "O"
                print_board(board)
                if check_win(board, "O"):
                    print("玩家获胜!")
                    return
                if check_tie(board):
                    print("平局!")
                    return

if __name__ == '__main__':
    play_game()