ヒルクライムアルゴリズムの例

ヒルクライミングアルゴリズムの例

1. 概要

このチュートリアルでは、山登りアルゴリズムとその実装を示します。 また、その長所と短所についても見ていきます。 直接それに飛び込む前に、生成とテストのアルゴリズムのアプローチについて簡単に説明しましょう。

2. 生成とテストのアルゴリズム

これは非常に単純な手法であり、解決策を見つけるためのアルゴリズムを作成できます。

  1. 現在の状態を初期状態として定義する

  2. 現在の状態で可能な操作を適用し、可能なソリューションを生成します

  3. 新しく生成されたソリューションを目標状態と比較します

  4. 目標が達成された場合、または新しい状態を作成できない場合は、終了します。 それ以外の場合は、手順2に戻ります

単純な問題でも非常にうまく機能します。 これは徹底的な検索であるため、大きな問題空間を扱う際に検討することは現実的ではありません。 また、大英博物館のアルゴリズムとして知られています(ランダムに探索して大英博物館でアーティファクトを見つけようとします)。

それはまた、生体認証の世界でのヒルクライミング攻撃の背後にある主要なアイデアです。 このアプローチは、合成生体認証データの生成に使用できます。

3. 単純な山登りアルゴリズムの概要

ヒルクライミング技術では、丘のふもとから始まり、丘の頂上に到達するまで上向きに歩きます。 つまり、初期状態から始めて、最適になるまでソリューションを改善し続けます。

これは、生成とテストのアルゴリズムのバリエーションであり、有望に見えない、または目標の状態につながる可能性が低いと思われるすべての状態を破棄します。 このような決定を行うために、現在の状態が目標状態にどれだけ近いかを示すヒューリスティック(評価関数)を使用します。

簡単に言えば、Hill-Climbing = generate-and-test + heuristics

Simple Hillクライミングアルゴリズムを見てみましょう。

  1. 現在の状態を初期状態として定義する

  2. 目標状態が達成されるか、現在の状態に適用できる演算子がなくなるまでループします。

    1. 現在の状態とget a new stateに操作を適用します

    2. Compare目標のある新しい状態

    3. 目標状態が達成された場合はQuit

    4. ヒューリスティック関数とcompare it with the current stateを使用して新しい状態を評価します

    5. 現在の状態と比較した目標へのIf the newer state is closerupdate the current state

ご覧のとおり、繰り返し改善されて目標状態に到達しています。 ヒルクライミングアルゴリズムでは、目標を見つけることは丘の頂上に到達することと同等です。

4. 例

ヒルクライミングアルゴリズムは、インフォームドサーチとして分類できます。 したがって、ノードベースの検索や、それを使用したn-queens問題などの問題を実装できます。 概念を簡単に理解するために、非常に単純な例を取り上げます。

下の画像を見てみましょう。

image

山登り問題を解決する際の重要なポイントは、適切なヒューリスティック関数を選択することです。

そのような関数h:を定義しましょう

ブロックが正しく配置されている場合は、サポート構造内のすべてのブロックに対してh(x) = +1、それ以外の場合は、サポート構造内のすべてのブロックに対して-1。

ここで、ゴール状態と同じサポート構造を持っている場合、ブロックを正しく配置します。 前に説明した山登り法の手順に従って、目標状態に到達するためのすべての反復とそのヒューリスティックを見てみましょう。

image 5. 実装

それでは、山登りアルゴリズムを使用して同じ例を実装しましょう。

まず、各状態でのブロックの位置を表すスタックのリストを格納する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;
}

さらに、新しい状態を取得する演算子メソッドを定義する必要があります。 この例では、これらのメソッドのうち2つを定義します。

  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. 最も急な上り坂の山登りアルゴリズム

最急上昇ヒルクライミングアルゴリズム(勾配探索)は、ヒルクライミングアルゴリズムの変形です。 単純なアルゴリズムにわずかな変更を加えて実装できます。 このアルゴリズムでは、単純な山登り法とは異なり、現在の状態からconsider all possible states、次にpick the best one as successorを取得します。

言い換えれば、山登り技術の場合、現在の状態よりも目標に近い状態を後継機として選択しましたが、最急上昇ヒルクライミングアルゴリズムでは、すべての可能な後継機の中から最適な後継機を選択して更新します現在の状態。

7. デメリット

ヒルクライミングは、目先の可能性のみを評価するため、近視眼のテクニックです。 したがって、それ以上の状態を選択できないいくつかの状況で終わる可能性があります。 これらの状態とそれらのいくつかの解決策を見てみましょう。

  1. Local maximum:これはすべてのネイバーよりも優れた状態ですが、現在の状態からはほど遠い、より優れた状態が存在します。極大値がソリューションの視界内で発生する場合、それは「丘陵地帯」として知られています

  2. Plateau:この状態では、隣接するすべての状態のヒューリスティック値が同じであるため、ローカル比較を行って次の状態を選択することは不明確です。

  3. Ridge:周囲の州よりも高いエリアですが、1回の移動で到達することはできません。たとえば、探索する4つの可能な方向(N、E、W、S)があり、エリアはNE方向に存在します

これらの状況を克服するためのソリューションはほとんどありません。

  1. 前の状態の1つにbacktrackして、他の方向を探索できます

  2. いくつかの状態をスキップして、新しい方向にjumpを作成できます

  3. explore several directionsで正しいパスを見つけることができます

8. 結論

山登り法は徹底的な検索よりもはるかに優れていますが、大きな問題のある場所ではまだ最適ではありません。

グローバル情報をヒューリスティック関数にいつでもエンコードしてよりスマートな決定を下すことができますが、計算の複雑さは以前よりもはるかに高くなります。

ヒルクライミングアルゴリズムは、他の手法でクラブを組む場合に非常に有益です。 いつものように、すべての例の完全なコードはover on GitHubにあります。