Пример алгоритма скалолазания

Пример алгоритма скалолазания

1. обзор

В этом руководстве мы покажем алгоритм Hill-Climbing и его реализацию. Мы также рассмотрим его преимущества и недостатки. Прежде чем перейти непосредственно к этому, давайте кратко обсудим подход к созданию и тестированию алгоритмов.

2. Алгоритм создания и тестирования

Это очень простой метод, который позволяет нам алгоритмизировать поиск решений:

  1. Определите текущее состояние как начальное состояние

  2. Примените любую возможную операцию к текущему состоянию и сгенерируйте возможное решение.

  3. Сравните вновь сгенерированное решение с состоянием цели

  4. Если цель достигнута или новые состояния не могут быть созданы, выйдите. В противном случае вернитесь к шагу 2

Это очень хорошо работает с простыми проблемами. Поскольку это исчерпывающий поиск, его невозможно рассмотреть при работе с большими проблемными пространствами. Он также известен как алгоритм Британского музея (пытается найти артефакт в Британском музее, исследуя его случайным образом).

Это также главная идея, стоящая за Атакой на холме в мире биометрии. Этот подход может быть использован для генерации синтетических биометрических данных.

3. Введение в простой алгоритм восхождения на холм

В технике скалолазания, начинающейся у подножия холма, мы идем вверх, пока не достигнем вершины холма. Другими словами, мы начинаем с начального состояния и продолжаем улучшать решение, пока оно не станет оптимальным.

Это вариант алгоритма генерации и тестирования, который отбрасывает все состояния, которые не выглядят многообещающими или вряд ли приведут нас к целевому состоянию. Для принятия таких решений используется эвристика (функция оценки), которая указывает, насколько близко текущее состояние к целевому состоянию.

Проще говоря,Hill-Climbing = generate-and-test + heuristics

Давайте посмотрим на алгоритм лазания по простому холму:

  1. Определите текущее состояние как начальное состояние

  2. Цикл, пока не будет достигнуто целевое состояние, или к текущему состоянию больше нельзя применять операторы:

    1. Применить операцию к текущему состоянию иget a new state

    2. Compare новое состояние с целью

    3. Quit, если целевое состояние достигнуто

    4. Оцените новое состояние с помощью эвристической функции иcompare it with the current state

    5. If the newer state is closer к цели по сравнению с текущим состоянием,update the current state

Как мы видим, он достигает целевого состояния с итеративными улучшениями. В алгоритме альпинизма, поиск цели эквивалентен достижению вершины холма.

4. пример

Алгоритм восхождения на холм можно отнести к категории информированного поиска. Таким образом, мы можем реализовать любой поиск на основе узлов или проблемы, такие как проблема n-queens, используя его. Чтобы понять концепцию легко, мы возьмем очень простой пример.

Давайте посмотрим на изображение ниже:

image

Ключевым моментом при решении любой задачи по восхождению на холм является выбор подходящей эвристической функции.

Определим такую ​​функциюh:

h(x) = +1 для всех блоков в опорной конструкции, если блок правильно установлен в противном случае -1 для всех блоков в опорной конструкции.

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

image 5. Реализация

Теперь давайте реализуем тот же пример, используя алгоритм Hill-Climbing.

Прежде всего, нам нужен классState, который будет хранить список стеков, представляющих позиции блоков в каждом состоянии. Он также будет хранить эвристику для этого конкретного состояния:

public class State {
    private List> state;
    private int heuristics;

    // copy constructor, setters, and getters
}

Нам также нужен метод, который будет вычислять эвристическую ценность состояния.

public int getHeuristicsValue(
  List> currentState, Stack goalStateStack) {

    Integer heuristicValue;
    heuristicValue = currentState.stream()
      .mapToInt(stack -> {
          return getHeuristicsValueForStack(
            stack, currentState, goalStateStack);
    }).sum();

    return heuristicValue;
}

public int getHeuristicsValueForStack(
  Stack stack,
  List> currentState,
  Stack goalStateStack) {

    int stackHeuristics = 0;
    boolean isPositioneCorrect = true;
    int goalStartIndex = 0;
    for (String currentBlock : stack) {
        if (isPositioneCorrect
          && currentBlock.equals(goalStateStack.get(goalStartIndex))) {
            stackHeuristics += goalStartIndex;
        } else {
            stackHeuristics -= goalStartIndex;
            isPositioneCorrect = false;
        }
        goalStartIndex++;
    }
    return stackHeuristics;
}

Кроме того, нам нужно определить методы операторов, которые приведут нас в новое состояние. Для нашего примера мы определим два из этих методов:

  1. Вытащите блок из стека и поместите его в новый стек

  2. Вытащите блок из стека и поместите его в один из других стеков.

private State pushElementToNewStack(
  List> currentStackList,
  String block,
  int currentStateHeuristics,
  Stack goalStateStack) {

    State newState = null;
    Stack newStack = new Stack<>();
    newStack.push(block);
    currentStackList.add(newStack);
    int newStateHeuristics
      = getHeuristicsValue(currentStackList, goalStateStack);
    if (newStateHeuristics > currentStateHeuristics) {
        newState = new State(currentStackList, newStateHeuristics);
    } else {
        currentStackList.remove(newStack);
    }
    return newState;
}
private State pushElementToExistingStacks(
  Stack currentStack,
  List> currentStackList,
  String block,
  int currentStateHeuristics,
  Stack goalStateStack) {

    return currentStackList.stream()
      .filter(stack -> stack != currentStack)
      .map(stack -> {
          return pushElementToStack(
            stack, block, currentStackList,
            currentStateHeuristics, goalStateStack);
        })
      .filter(Objects::nonNull)
      .findFirst()
      .orElse(null);
}

private State pushElementToStack(
  Stack stack,
  String block,
  List> currentStackList,
  int currentStateHeuristics,
  Stack goalStateStack) {

    stack.push(block);
    int newStateHeuristics
      = getHeuristicsValue(currentStackList, goalStateStack);
    if (newStateHeuristics > currentStateHeuristics) {
        return new State(currentStackList, newStateHeuristics);
    }
    stack.pop();
    return null;
}

Теперь, когда у нас есть вспомогательные методы, давайте напишем метод для реализации техники восхождения на холм.

Здесьwe keep computing new states which are closer to goals than their predecessors.. Мы продолжаем добавлять их на нашем пути, пока не достигнем цели.

Если мы не обнаруживаем новых состояний, алгоритм завершает работу с сообщением об ошибке:

public List getRouteWithHillClimbing(
  Stack initStateStack, Stack goalStateStack) throws Exception {
    // instantiate initState with initStateStack
    // ...
    List resultPath = new ArrayList<>();
    resultPath.add(new State(initState));

    State currentState = initState;
    boolean noStateFound = false;

    while (
      !currentState.getState().get(0).equals(goalStateStack)
      || noStateFound) {
        noStateFound = true;
        State nextState = findNextState(currentState, goalStateStack);
        if (nextState != null) {
            noStateFound = false;
            currentState = nextState;
            resultPath.add(new State(nextState));
        }
    }
    return resultPath;
}

В дополнение к этому нам также нужен методfindNextState, который применяет все возможные операции к текущему состоянию, чтобы получить следующее состояние:

public State findNextState(State currentState, Stack goalStateStack) {
    List> listOfStacks = currentState.getState();
    int currentStateHeuristics = currentState.getHeuristics();

     return listOfStacks.stream()
      .map(stack -> {
          return applyOperationsOnState(
            listOfStacks, stack, currentStateHeuristics, goalStateStack);
      })
      .filter(Objects::nonNull)
      .findFirst()
      .orElse(null);
}

public State applyOperationsOnState(
  List> listOfStacks,
  Stack stack,
  int currentStateHeuristics,
  Stack goalStateStack) {

    State tempState;
    List> tempStackList
      = new ArrayList<>(listOfStacks);
    String block = stack.pop();
    if (stack.size() == 0)
      tempStackList.remove(stack);
    tempState = pushElementToNewStack(
      tempStackList, block, currentStateHeuristics, goalStateStack);
    if (tempState == null) {
        tempState = pushElementToExistingStacks(
          stack, tempStackList, block,
          currentStateHeuristics, goalStateStack);
        stack.push(block);
    }
    return tempState;
}

6. Алгоритм восхождения на самый крутой подъем

Алгоритм крутого восхождения Hill Hill Climbing (градиентный поиск) является вариантом алгоритма Hill Climbing. Мы можем реализовать это с небольшими изменениями в нашем простом алгоритме. В этом алгоритме мыconsider all possible states из текущего состояния, а затемpick the best one as successor, в отличие от простой техники восхождения на холм.

Другими словами, в случае техники восхождения на холм мы выбрали любое состояние в качестве преемника, которое было ближе к цели, чем текущее состояние, тогда как в алгоритме крутого восхождения на холм мы выбираем лучшего преемника среди всех возможных преемников и затем обновляем текущее состояние.

7. Недостатки

Скалолазание - это недальновидная техника, поскольку она оценивает только непосредственные возможности. Таким образом, он может оказаться в нескольких ситуациях, из которых он не может выбрать дальнейшие состояния. Давайте посмотрим на эти состояния и некоторые решения для них:

  1. Local maximum: Это состояние лучше, чем все соседи, но существует лучшее состояние, которое далеко от текущего состояния; если локальный максимум происходит в пределах видимости решения, он известен как «предгорье»

  2. Plateau: В этом состоянии все соседние состояния имеют одинаковые эвристические значения, поэтому неясно выбрать следующее состояние путем локальных сравнений.

  3. Ridge: Это область выше окружающих состояний, но не может быть достигнута одним движением; Например, у нас есть четыре возможных направления для исследования (север, восток, запад, юг), и существует область в северо-восточном направлении.

Есть несколько решений для преодоления этих ситуаций:

  1. Мы можемbacktrack перейти в одно из предыдущих состояний и исследовать другие направления

  2. Мы можем пропустить несколько состояний и сделатьjump в новых направлениях

  3. Мы можемexplore several directions найти правильный путь

8. Заключение

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

Мы всегда можем закодировать глобальную информацию в эвристические функции, чтобы принимать более разумные решения, но тогда вычислительная сложность будет намного выше, чем была раньше.

Алгоритм скалолазания может быть очень полезным, когда он забит другими техниками. Как всегда, полный код для всех примеров можно найти вover on GitHub.