算法学习:Pac-Man的简单对抗

来源:互联网 发布:网页在线客服源码 编辑:程序博客网 时间:2024/05/18 15:06

Pacman项目是加州大学伯克利分校提供的一个可视化的AI学习平台。其主体利用python完成。该项目提供了丰富的说明文档,以及预先实现了一些简单的算法供参考各接口的使用。

http://ai.berkeley.edu/project_overview.html

本文利用Pac-Man平台实现简单的对抗搜索。

Part1 : Reflex Agent

提供的ReflexAgent有以下接口:

class ReflexAgent(Agent):    def getAction(self,gameState)    def evaluationFunction(self, currentGameState, action)

根据对getAction的分析:

def getAction(self, gameState) #根据当前的评估函数确定下一步的动作        #获取当前可能的下一步的方向 有stop east ...        legalMoves = gameState.getLegalActions()        #根据评估函数获取所有的下一步的权值        scores = [self.evaluationFunction(gameState, action) for action in legalMoves]        #获取最好的分数        bestScore = max(scores)        #根据最好的分数获取最好的行动选择        bestIndices = [index for index in range(len(scores)) if scores[index] == bestScore]        #从最好的行动集合中随机选择一个最为下一步的动作        chosenIndex = random.choice(bestIndices)        return legalMoves[chosenIndex]

可知evaluationFunction是这个Agent的灵魂,评估每一个输入的分数。根据提供的下面函数的接口,设计了一个简单的Pacman的AI。

初始版本的AI如该类所述,只提供了基本的反射。该agent所处环境包括以下内容

  • 食物到Agent的距离
  • 怪兽到Agent的距离
  • 超级豆子到Agent的距离
  • 下一个状态能否吃到豆子 或者被Ghost吃掉

计算出这些参数,给这些参数以固定的权值,就写出了最基础的AI

    def evaluationFunction(self, currentGameState, action):        # 获取当前游戏状态 其中G表示为Ghost %表示为墙 角标表示pacman 角标方向代表上一次选择的方向        successorGameState = currentGameState.generatePacmanSuccessor(action)        # print 'successorGameState\n',successorGameState        # 获取这样移动后新的位置        newPos = successorGameState.getPacmanPosition()        # print 'newPos',newPos        # 获取食物在图中的分布(二维数组,有失误为T没食物为F)        newFood = successorGameState.getFood()        curFood = currentGameState.getFood()        # print 'newFood',newFood        # 获取Ghost的位置        newGhostStates = successorGameState.getGhostStates()        # print 'ghostState',newGhostStates[0].getPosition()        # 获取吃超级豆子之后 Ghost害怕还剩余的时间        newScaredTimes = [ghostState.scaredTimer for ghostState in newGhostStates]        # 对这个选择评估的分数        currscore = 0        if action == "Stop":            return -100        # 如果当前状态能够使ghost害怕,将所有的时间加入进来        for st in newScaredTimes:            currscore += st        # 根据Ghost所在的位置,获取与当前位置的距离        ghost_distances = []        for gs in newGhostStates:            ghost_distances += [manhattanDistance(gs.getPosition(),newPos)]        # 获取food所在的所有pos        foodList = newFood.asList()        curfoodList = curFood.asList()        # 获取food所在的所有wall        wallList = currentGameState.getWalls().asList()        # 保存food的距离        food_distences = []        # 获取所有食物到达当前位置的距离        for foodpos in foodList:            food_distences += [manhattanDistance(newPos,foodpos)]        # 对食物的距离取反        inverse_food_distences=0;        if len(food_distences)>0 and min(food_distences) > 0:            inverse_food_distences = 1.0 / min(food_distences)        # 考虑了ghost与当前的距离,其权值更大        currscore += min(ghost_distances)*(inverse_food_distences**4)        # 获取当前系统判定的分数 又可能当前吃到了豆子 分数更高些        currscore+=successorGameState.getScore()        if newPos in curfoodList:            currscore = currscore * 1.1        return currscore

运行测试

python autograder.py -q q1

10次测试如下

Pacman emerges victorious! Score: 1228Pacman emerges victorious! Score: 1253Pacman emerges victorious! Score: 1246Pacman emerges victorious! Score: 1255Pacman emerges victorious! Score: 1247Pacman emerges victorious! Score: 1257Pacman emerges victorious! Score: 1244Pacman emerges victorious! Score: 1260Pacman emerges victorious! Score: 1261Pacman emerges victorious! Score: 1258Average Score: 1250.9Scores:        1228.0, 1253.0, 1246.0, 1255.0, 1247.0, 1257.0, 1244.0, 1260.0, 1261.0, 1258.0Win Rate:      10/10 (1.00)Record:        Win, Win, Win, Win, Win, Win, Win, Win, Win, Win

测试GUI

这里写图片描述

直接运行游戏GUI

python pacman.py -p ReflexAgent -k 2

这里写图片描述

Part 2 : MinMax

利用MinMax博弈树,这里模拟的Ghost可能不止一个,在计算Min节点的时候增加了对多Ghost的支持。

实际运行游戏时候,可以用-k参数(<3)来选择Ghost个数。

与gameState.getNumAgents()对接,可以模拟多个Ghost,从而选择威胁最大的那个作为最终的min节点。

class MinimaxAgent(MultiAgentSearchAgent):      def getAction(self, gameState):        def max_value(state, currentDepth):            # 当前深度加一            currentDepth=currentDepth+1            # 若当前状态已经赢了或输了 或者 已经到达了规定的深度            if state.isWin() or state.isLose() or currentDepth == self.depth:                return self.evaluationFunction(state)            # 初始化v            v= float('-Inf')            # 对每个min分支求max            for pAction in state.getLegalActions(0):                v=max(v, min_value(state.generateSuccessor(0, pAction), currentDepth, 1))            return v        def min_value(state, currentDepth, ghostNum):            # 若当前状态已经赢了或输了            if state.isWin() or state.isLose():                return self.evaluationFunction(state)            # 初始化v            v=float('Inf')            # 对每个max分支求min 其中有多个Ghost 所有多个Ghost分支            for pAction in state.getLegalActions(ghostNum):                if ghostNum == gameState.getNumAgents()-1:                    #所有Ghost的min找完了 开始找下一个max                    v=min(v, max_value(state.generateSuccessor(ghostNum, pAction), currentDepth))                else:                    #继续下一个Ghost                    v=min(v, min_value(state.generateSuccessor(ghostNum, pAction), currentDepth, ghostNum+1))            return v        # pacman下一个状态可能的行动        Pacman_Actions = gameState.getLegalActions(0)        maximum = float('-Inf')        result = ''        # 针对下一个状态 寻找获胜概率最高的move        for action in Pacman_Actions:            if(action != "Stop"):                currentDepth = 0                # 而所有的Ghost希望胜利概率最低的选择                currentMax = min_value(gameState.generateSuccessor(0, action), currentDepth , 1)                if currentMax > maximum:                    maximum=currentMax                    result =action        return result

当开始跑测试的时候,就发现在下面参数的情况下,走一步已经非常吃力了,其时间复杂度过高。

运行命令python pacman.py -p MinimaxAgent -k 2 -a depth=4

输出每步决策所消耗的时间,每步大致花费时间如下,Time的单位为ms:

Go  East Value is  43.0  Time:  1994.49194336Go  East Value is  50.0  Time:  6805.0690918Go  East Value is  58.0  Time:  4821.87817383Go  East Value is  67.0  Time:  3456.60791016Go  North Value is  76.0  Time:  1419.2019043Go  North Value is  63.0  Time:  3504.87597656Go  East Value is  72.0  Time:  2485.83081055Go  East Value is  103.0  Time:  1185.18701172

每步所花费时间大概在3s左右,当三个Agent的选择均非常多的情况下,每步所消耗的时间有可能会到达6s

急需AlphaBate剪枝,下面加入AlphaBate剪枝。

Part 3 : Alpha-Bate 剪枝

class AlphaBetaAgent(MultiAgentSearchAgent):    def getAction(self, gameState):        def max_value(state, alpha, beta, currentDepth):            # 当前深度加一            currentDepth=currentDepth+1            # 若当前状态已经赢了或输了 或者 已经到达了规定的深度            if state.isWin() or state.isLose() or currentDepth == self.depth:                return self.evaluationFunction(state)            v=float('-Inf')            # 对每个min分支求max            for pAction in state.getLegalActions(0):                if pAction!="Stop":                    v=max(v, min_value(state.generateSuccessor(0, pAction), alpha, beta, currentDepth, 1))                    # 若已经比beta要大了 就没有搜索下去的必要了                    if v >= beta:                        return v                    # 更新alpha的值                    alpha=max(alpha, v)            return v        def min_value(state, alpha, beta, currentDepth, ghostNum):            # 若当前状态已经赢了或输了            if state.isWin() or state.isLose():                return self.evaluationFunction(state)            # 初始化v            v=float('Inf')            # 对每个max分支求min 其中有多个Ghost 所有多个Ghost分支            for pAction in state.getLegalActions(ghostNum):                if ghostNum == gameState.getNumAgents()-1:                    # 所有Ghost的min找完了 开始找下一个max                    v=min(v, max_value(state.generateSuccessor(ghostNum, pAction), alpha, beta, currentDepth))                else:                    # 继续下一个Ghost                    v=min(v,                          min_value(state.generateSuccessor(ghostNum, pAction), alpha, beta, currentDepth, ghostNum+1))                # 若比alpha还要小了 就没搜索的必要了                if v <= alpha:                    return v                # 更新beta的值                beta=min(beta, v)            return v        # pacman下一个状态可能的行动        pacmanActions=gameState.getLegalActions(0)        maximum=float('-Inf')        # 初始化alpha bate        alpha=float('-Inf')        beta=float('Inf')        maxAction=''        # 针对下一个状态 寻找获胜概率最高的move        for action in pacmanActions:            if action!="Stop":                currentDepth=0                # 而所有的Ghost希望胜利概率最低的选择                currentMax=min_value(gameState.generateSuccessor(0, action), alpha, beta, currentDepth, 1)                if currentMax > maximum:                    maximum=currentMax                    maxAction=action        print maximum        return maxAction

利用了AlphaBate之后 在同样的参数下 运行速度明显增加。

但是由于效果还是不好,感觉是系统提供的启发函数不太完美。系统提供的接口如下:

class MultiAgentSearchAgent(Agent):    def __init__(self, evalFn = 'scoreEvaluationFunction', depth = '2'):        self.index = 0 # Pacman is always agent index 0        self.evaluationFunction = util.lookup(evalFn, globals())        self.depth = int(depth)

在第一部分的基础反射启发函数基础上,修改默认的启发函数如下:

def scoreEvaluationFunction(currentGameState,cur_score):    # 获取food所在的所有wall    wallList=currentGameState.getWalls().asList()    Pos = currentGameState.getPacmanPosition()    # 获取食物在图中的分布(二维数组,有失误为T没食物为F)    curFood=currentGameState.getFood()    # 获取Ghost的位置    GhostStates=currentGameState.getGhostStates()    # 获取吃超级豆子之后 Ghost害怕还剩余的时间    scaredTimes=[ghostState.scaredTimer for ghostState in GhostStates]    # 对这个选择评估的分数    currscore=0    # 根据Ghost所在的位置,获取与当前位置的距离    ghost_distances=[]    for gs in GhostStates:        ghost_distances+=[manhattanDistance(gs.getPosition(), Pos)]    ghost_index = 0;    min_ghost_distances =  min(ghost_distances);    is_scared = False    for time in scaredTimes:        if time != 0:            is_scared = True        else:            is_scared = False            break    # 获取food所在的所有pos    curfoodList=curFood.asList()    # 保存food的距离    food_distences=[]    # 获取所有食物到达当前位置的距离    for foodpos in curfoodList:        food_distences+=[manhattanDistance(Pos, foodpos)]    # 对食物的距离取反    inverse_food_distences=0;    if len(food_distences) > 0 and min(food_distences) > 0:        inverse_food_distences=1.0 / min(food_distences)    if is_scared and min_ghost_distances!=0:        # if min_ghost_distances < 10:            # min_ghost_distances = 800 min_ghost_distances        # else:            # min_ghost_distances = 600 min_ghost_distances        print "Ghost Scared!"        min_ghost_distances = min_ghost_distances * 0.8    # 考虑了ghost与当前的距离,其权值更大    if min(ghost_distances) == 0:        currscore+=inverse_food_distences    else:        currscore+= min_ghost_distances * (float(inverse_food_distences))    # 获取当前系统判定的分数 又可能当前吃到了豆子 分数更高些    currscore+=currentGameState.getScore()    return currscore

测试命令 python pacman.py -p AlphaBetaAgent -k 2 -a depth=4

输出每步决策所消耗的时间,每步大致花费时间如下,Time的单位为ms:

Go  East Value is  43.0  Time:  215.517822266Go  East Value is  50.0  Time:  820.08203125Go  East Value is  58.0  Time:  712.662841797Go  East Value is  69.0  Time:  320.084960938Go  North Value is  78.0  Time:  1038.60400391Go  North Value is  66.0  Time:  862.83203125Go  East Value is  76.0  Time:  774.194091797Go  East Value is  108.0  Time:  357.637207031Go  East Value is  113.5  Time:  812.993164062Go  North Value is  126.0  Time:  402.672851562

每步用时比没有剪枝的情况减少了一半以上

正常测试平均在1500 point左右,但仍然存在以下问题

  • 当两个Agent分别在Pacman的两边或两边存在同样数量的豆子时,左右徘徊
  • 因为使用MinMax博弈树 而Ghost在离Pacman足够远时威胁很小
  • 导致Agent Pacman在豆子吃了大部分的时候非常谨慎
  • 参数调配问题

由于这些问题,导致了Pacman在某些情况下依旧显得不够智能。在该问题的实际情况中:

  • Ghost其实并没有博弈的概念,所以大部分猜想是浪费时间的

  • Pacman游戏其实只需要局部考虑,无需过多全局考虑,也就是说:

    当Ghost离Agent足够远的时候,其实Ghost的行动对于Pacman影响不大,没必要过多考虑。

    但是比如围棋博弈,这种全局观念就很重要了。

  • 启发函数还是人为为Ghost制定反射

4 0
原创粉丝点击