Почему мой поиск A* возвращает то же расширенное пространство, что и мой поиск UniformCostSearch?

Я работаю с двумя разными структурами данных над этой проблемой поиска. Поиск по унифицированной стоимости реализует PriorityQueue, а поиск A* реализует PriorityQueueWithFunction, которые оба предопределены для меня:

class PriorityQueue:
    def __init__(self):
        self.heap = []
        self.count = 0

    def push(self, item, priority):
        entry = (priority, self.count, item)
        heapq.heappush(self.heap, entry)
        self.count += 1

    def pop(self):
        (_, _, item) = heapq.heappop(self.heap)
        return item

    def isEmpty(self):
        return len(self.heap) == 0

class PriorityQueueWithFunction(PriorityQueue):
    # priorityFunction(item) -> priority
    def __init__(self, priorityFunction):
        self.priorityFunction = priorityFunction
        PriorityQueue.__init__(self) # super-class call

    def push(self, item):
        # Adds an item to the Queue with priority from the priority function
        PriorityQueue.push(self, item, self.priorityFunction(item))

Итак, вот мой метод UniformCostSearch, который оптимален для моей реализации агента, ищущего цель через лабиринт. SearchProblem имеет три компонента: состояние, которое представляет собой кортеж из координат int, стоимость перехода в состояние и направления для перехода в состояние с самого начала:

def uniformCostSearch(problem):
    # An empty list to store already expanded states
    closed = set()
    fringe = PriorityQueue()
    fringe.push((problem.getStartState(), 0, []), 0)

    while not fringe.isEmpty():
        node, cost, directions = fringe.pop()

        if problem.isGoalState(node):
            return directions

        if not (node in closed):
            closed.add(node)

            for node, direction, step_cost in problem.getSuccessors(node):
                fringe.push((node, cost + step_cost, directions + [direction]), 
                                                           cost + step_cost)

    if fringe.isEmpty():
        return []

Это оптимально и возвращает общее значение узлов, развернутое как 620, с использованием определенного макета лабиринта. Моя проблема в моей реализации A * Search:

def aStarSearch(problem, heuristic):
    closed = set()
    totalCost = 0 # Instantiate a totalCost counter
    # A* uses the total cost up to current node + heuristic to goal to decide priority
    fringe = PriorityQueueWithFunction(lambda x: totalCost +
                                       heuristic(problem.getStartState(), problem)
    fringe.push((problem.getStartState(), 0, []))

    while not fringe.isEmpty():
        node, cost, directions = fringe.pop()

        if problem.isGoalState(node):
            return directions

        if not (node in closed):
            closed.append(node)
            totalCost += cost

            for node, direction, cost in problem.getSuccessors(node):
                fringe.push((node, cost, directions + [direction]))

    if fringe.isEmpty():
        return []

И A* Search, и UniformCostSearch работают и находят решение, однако я получаю одно и то же значение расширенных узлов поиска, что является моим вопросом. Почему A* возвращает 620, если UCS также возвращает 620? (Расширение целевого узла для A* в этом сценарии равно 549)


person Jodo1992    schedule 16.02.2016    source источник


Ответы (1)


Я думаю, что вы неправильно обрабатываете расходы для обоих ваших поисков.

Для uniformCostSearch вы указываете только стоимость последнего шага для каждого узла (cost, возвращаемого getSuccessors). Поскольку это константа, ваша приоритетная очередь — это обычная очередь, и все это — поиск в ширину. Теперь, поскольку очередь с приоритетом предпочитает более старые значения (с более низкими counts), это на самом деле ничем не отличается от того, что вы получили бы, если бы фактически передали реальное значение стоимости (например, старую стоимость плюс стоимость нового шага), но вы вероятно, следует сделать это правильно в первую очередь:

def uniformCostSearch(problem):
    # An empty list to store already expanded states
    closed = []
    fringe = PriorityQueue()
    fringe.push((problem.getStartState(), 0, []), 0)

    while not fringe.isEmpty():
        node, cost, directions = fringe.pop()

        if problem.isGoalState(node):
            return directions

        if not (node in closed):
            closed.append(node)

            for node, direction, step_cost in problem.getSuccessors(node):
                fringe.push((node, cost + step_cost, directions + [direction]),
                            cost + step_cost)

    if fringe.isEmpty():
        return []

Ваш поиск A* еще больше запутался в затратах. Функция стоимости игнорирует свои входные данные и всегда передает один и тот же узел эвристической функции. Результатом добавления каждого значения стоимости к total_cost является то, что каждый узел получает последовательно более высокую стоимость при добавлении в очередь. Это заставляет узлы расширяться так же, как поиск по единой стоимости, FIFO.

Вам нужно заставить функцию стоимости проверять стоимость своего аргумента и использовать узел аргумента в качестве аргумента эвристической функции. Попробуйте что-то вроде этого:

def aStarSearch(problem, heuristic):
    closed = []
    # A* uses the total cost up to current node + heuristic to goal to decide priority
    def cost_func(tup):
        node, cost_so_far, directions = tup   # unpack argument tuple
        return cost_so_far + heuristic(node, problem) # I'm guessing at heuristic's API

    fringe = PriorityQueueWithFunction(cost_func)
    fringe.push((problem.getStartState(), 0, []))

    while not fringe.isEmpty():
        node, cost, directions = fringe.pop()

        if problem.isGoalState(node):
            return directions

        if not (node in closed):
            closed.append(node)

            for node, direction, step_cost in problem.getSuccessors(node):
                fringe.push((node, cost + step_cost, directions + [direction]))

    if fringe.isEmpty():
        return []

Последнее предложение — использовать set вместо списка closed. set будет намного быстрее при тестировании членства с is, чем со списком (постоянное время, а не O(N)), и вы можете add присваивать им новые значения за (амортизированное) постоянное время.

person Blckknght    schedule 16.02.2016
comment
Единственный дополнительный вопрос, который у меня есть, это когда вы создаете экземпляр fringe = PriorityQueueWithFunction(), почему параметр функции не был передан, так как он ожидает аргумент. - person Jodo1992; 16.02.2016
comment
Упс, вызов конструктора очереди без аргументов был ошибкой. Я исправил это в ответе. - person Blckknght; 16.02.2016
comment
Предоставленная вами реализация A* не работает, потому что priorityFunction необходимо передать item в качестве аргумента, а tuple не является item. Первая реализация великолепна, но мне еще нужно кое-что для части 2 - person Jodo1992; 16.02.2016
comment
Как вы думаете, почему кортеж не является элементом? Метод очереди push передает все, что ему дано для отправки, в функцию стоимости, которой в данном случае является кортеж (node, cost + step_cost, directions). Моя функция стоимости распаковывает значения и использует их для вычисления полной стоимости. Обратите внимание, что вы можете использовать свою обычную очередь с приоритетом, если хотите, и просто предварительно вычислить приоритет cost+heuristic при отправке элемента. - person Blckknght; 16.02.2016