【α-β剪枝】——對Minimax方法的優化


(轉自——http://www.cnblogs.com/speeding/archive/2012/09/20/2694704.html)

基本概念:

      節點:在中國象棋中就是一個棋盤的當前局面Board,當然該輪到誰走棋也是確定的。這里的圓形節點表示終止節點,在中國象棋里就是一方被將死的情況(或者到達了搜索的最大深度),后續不會再有着法產生,游戲如果走到這里就會結束。在引擎里通常給紅方一個很大的評估值,如+30000,給黑方一個很小的評估值,如-30000,來方便地判斷這種結束局面。(勝利局面有稍微不同的表示法,用-30000+層數ply來表示)

      連線:表示一步着法Move,通過這步着法后,局面發生變化,先后手也要交換。

      層:通常的術語是ply,復數形式是plies,也有稱為levels,當然與depth也是對應的。這個術語是為了與比賽里常說的回合相區分,一個回合通常包含2步,這個ply就表示某一方走了一步。根節點記為0層,以下的層數遞增。

      深度depth:要注意是從下到上的,還是從上到下的。(1)通常的算法書中都是從下到上遞增的,即根節點為最大搜索深度,走到最底部的葉子結點為0,這種算法只要記住一個depth值就可以了。(2)而另一種記法是根部結點為0,越向下depth增加,這時在搜索時就要傳遞2個參數值,depth和maxDepth,稍微有點啰嗦,應該也會影響一點效率。另外在探查置換表中的結點時,用第(1)種記法也方便一些,因為要知道從當前節點迭代的深度值,否則還要在置換表中保存depth和maxDepth兩個值。

 

 

        AlphaBeta剪枝方法是對Minimax方法的優化,它們產生的結果是完全相同的,只不過運行效率不一樣。

        這種方法的前提假設與Minimax也是一樣的:

1)雙方都按自己認為的最佳着法行棋。

2)對給定的盤面用一個分值來評估,這個評估值永遠是從一方(搜索程序)來評價的,紅方有利時給一個正數,黑方有利時給一個負數。(如果紅方有利時返回正數,當輪到黑方走棋時,評估值又轉換到黑方的觀點,如果認為黑方有利,也返回正數,這種評估方法都不適合於常規的算法描述)

3)從我們的搜索程序(通常把它稱為Max)看來,分值大的數表示對己方有利,而對於對方Min來說,它會選擇分值小的着法。

但要注意:用Negamax風格來描述的AlphaBeta中的評估函數,對輪到誰走棋是敏感的。

也就是說:

在Minimax風格的AlphaBeta算法中,輪紅方走棋時,評估值為100,輪黑方走棋評估值仍是100。

但在Negamax風格的AlphaBeta算法中,輪紅方走棋時,評估值為100,輪黑方走棋時評估值要為-100。

 

alphabeta

貼一段偽代碼:

 
def ABNegaMax (board, depth, maxDepth, alpha, beta)
if ( board.isGameOver() or depth == maxDepth )
    return board.evaluate(), null
bestMove = null
bestScore = -INFINITY
for move in board.getMoves()
    newBoard = board.makeMove(move)
    score = ABNegaMax(newBoard, maxDepth, depth+1, -beta, -max(alpha, bestScore))
    score = -score
    if ( score > bestScore )
        bestScore = score
        bestMove = move
        # early loop exit (pruning)
        if ( bestScore >= beta ) return bestScore, bestMove
return bestScore, bestMove


用下列語句開始調用:

ABNegaMax(board, player, maxDepth, 0, -INFINITY, INFINITY) 


    


    


    

// method call with depth 5 and minimum and maximum boundaries
// minimaxValue = alphaBeta(board, 5, -MATE, +MATE)
int alphaBeta(ChessBoard board, int depth, int alpha, int beta)
{
    int value;
    if( depth == 0 || board.isEnded())
    {
         value = evaluate(board);
         return value;
    }
    board.getOrderedMoves();
    int best = -MATE-1;
    int move; 
    ChessBoard nextBoard;
    while (board.hasMoreMoves())
    {
        move = board.getNextMove();
        nextBoard = board.makeMove(move);
        value = -alphaBeta(nextBoard, depth-1,-beta,-alpha);
        if(value > best)
             best = value;
        if(best > alpha)
             alpha = best;
        if(best >= beta)
              break;
     }
     return best;
}

 

    下面這個PDF更清楚地說明了Negamax風格的alphabeta算法的過程:

    http://www.cs.colostate.edu/~anderson/cs440/index.html/lib/exe/fetch.php?media=notes:negamax2.pdf

 

為了更准確地理解minmax和negamax兩種不同風格的搜索執行過程,畫出了詳細的圖解,發現negamax更容易理解了,代碼也確實精練了不少。

當采用了置換表算法后,還需要對PV-Nodes, Cut-Nodes和All-Nodes三種類型結點的含義以及如何變化有更准確地了解。

alphabeta搜索的詳細過程(min,max風格)

 

alphabeta搜索的詳細過程(negamax風格)

 

 

可以發現,Negamax風格的算法有下面的特點(代碼來自象棋巫師):

int AlphaBeta(int depth, int alpha, int beta) { 
              int hashf = hashfALPHA;    //開始時的結點類型應該是All-Nodes,有些地方稱為ALPHA類型結點 
              //這里要探查置換表 
              if (depth == 0) {   // 葉子結點,評估,寫入置換表,返回即可 
                 val = Evaluate(); 
                 RecordHash(depth, val, hashfEXACT); // 葉子結點肯定是PV-Nodes 
                 return val; 
              } 
              GenerateLegalMoves(); 
              while (MovesLeft()) { 
                 MakeNextMove(); 
                       //注意Negamax風格的調用方式,前面有個負號,后面的參數是-beta和-alpha

               // Negamax的含義中Nega就是指這里的負號 
                 val = -AlphaBeta(depth - 1, -beta, -alpha);   
                 UnmakeMove(); 
                 if (val >= beta) {   // 剪枝情況判斷 
                    RecordHash(depth, beta, hashfBETA);  //這時的結點類型是Cut-Nodes 
                    return beta; 
                 } 
                 if (val > alpha) {   // Negamax中的max就是指的這一段,要記錄最大的評估值,這里沒有引入一個新變量,直接就用了alpha變量 
                    hashf = hashfEXACT;   // 只要alpha值一發生變化,這個結點類型就是PV-Nodes了! 
                    alpha = val; 
                 } 
               } 
               RecordHash(depth, alpha, hashf); 
               return alpha;   // 此時的alpha就是記錄了當前結點的所有子結點的最大的負評估值! 
           }

 

 

    這面的代碼在置換表探查方面感覺有點問題,

    又從Marek Strejczek的論文《Some aspects of chess programming》的第87頁上找到一段代碼,感覺這段代碼充分利用了置換表中存儲的信息。

chSCORE alphabetaWithTT(chPOSITION node,chSCORE alpha,beta) {
if (isLeaf(node) ) return evaluate(node);
    if ( (entry = getFromHash(node) ) != NULL) {
        if (TT_entry_deep_enough) {
        // data in hash table comes from a search to the 
        // same depth as current or deeper – so it is reliable
        if (entry.flag == UPPER) {
            if (entry.score <= alpha) {
                return alpha
            }
            if (entry.score < beta)
                beta = flag.score;
            }
        }
        if (entry.flag == LOWER) {
             if (entry.score >= beta) {
                 return beta;
             }
             if (entry.score > alpha) {
                 alpha = flag.score;
             }
         }
         if (entry.flag == EXACT) {
             return entry.score
         }
    }
    else {
        // TT entry represents results of a shallower
        // depth search – no cutoffs possible, but still 
        // a valuable move to try first can be used
        if (entry.move != NULL) {
             try_hash_move_first = true;
        }
    }
}
g = alpha;
x = left_most_child(node);
hash_flag = UPPER;
best_move = NULL;
while (isNotNull (x) ) {
    g = -alphabeta(x, -beta, -alpha);
    if (g > alpha) {
        alpha = g;
        if (g >= beta) {
            hash_flag = LOWER;
            best_move = current_move;
            saveHash(node,g,hash_flag,best_move);
            return beta;
        }
        hash_flag = EXACT;
        best_move = current_move;
    }
    x = next_brother(x);
}
putToHash(node, g, hash_flag, best_move)
return alpha;
} // end of alphabetaWithTT

 

 

 

 

 

 

 

 

----------------------------------------------------------------接【MINIMAX】中#字棋模板--------------------------------------------------------------------

    過結合Alpha-beta剪枝能進一步優化效率。Alpha-beta剪枝顧名思義就是裁剪掉一些不必要的分支,以減少遍歷的節點數。實際上是通過傳遞兩個參數alpha和beta到遞歸的極小極大函數中,alpha表示了MAX的最壞情況,beta表示了MIN的最壞情況,因此他們的初始值為負無窮和正無窮。在遞歸的過程中,在輪到MAX的回合,如果極小極大的值比alpha大,則更新alpha;在MIN的回合中,如果極小極大值比beta小,則更新beta。當alpha和beta相交時(即alpha>=beta),這時該節點的所有子節點對於MAX和MIN雙方都不會帶來好的獲益,所以可以忽略掉(裁剪掉)以該節點為父節點的整棵子樹。
 
根據這一定義,可以很輕易地在上面程序的基礎上進行改進:

 

/**
 * 以'x'的角度來考慮的極小極大算法
 */
public   int   minimax( char [] board,   int   depth){
       int [] bestMoves =   new   int [9];
       int   index = 0;
      
       int   bestValue = - INFINITY ;
       for ( int   pos=0; pos<9; pos++){
            
             if (board[pos]== empty ){
                  board[pos] =   x ;
                  
                   int   value = min(board, depth, - INFINITY , + INFINITY );
                   if (value>bestValue){
                        bestValue = value;
                        index = 0;
                        bestMoves[index] = pos;
                  } else
                   if (value==bestValue){
                        index++;
                        bestMoves[index] = pos;
                  }
                  
                  board[pos] =   empty ;
            }
            
      }
      
       if (index>1){
            index = ( new   Random (System. currentTimeMillis ()).nextInt()>>>1)%index;
      }
       return   bestMoves[index];
      
}
/**
 * 對於'x',估值越大對其越有利
 */
public   int   max( char [] board,   int   depth,   int   alpha,   int   beta){
      
       int   evalValue =   gameState (board);
      
       boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );
       if (beta<=alpha){
             return   evalValue;
      }
       if (depth==0 || isGameOver){
             return   evalValue;
      }
      
       int   bestValue = - INFINITY ;
       for ( int   pos=0; pos<9; pos++){
            
             if (board[pos]== empty ){
                   // try
                  board[pos] =   x ;
                  
                   //   maximixing
                  bestValue = Math. max (bestValue, min(board, depth-1, Math. max (bestValue, alpha), beta));
                  
                   // reset
                  board[pos] =   empty ;
            }
            
      }
      
       return   evalValue;
      
}
/**
 * 對於'o',估值越小對其越有利
 */
public   int   min( char [] board,   int   depth,   int   alpha,   int   beta){
      
       int   evalValue =   gameState (board);
      
       boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );
       if (alpha>=beta){
             return   evalValue;
      }
       // try
       if (depth==0 || isGameOver || alpha>=beta){
             return   evalValue;
      }
      
       int   bestValue = + INFINITY ;
       for ( int   pos=0; pos<9; pos++){
            
             if (board[pos]== empty ){
                   // try
                  board[pos] =   o ;
                  
                   //   minimixing
                  bestValue = Math.min(bestValue, max(board, depth-1, alpha, Math.min(bestValue, beta)));
                  
                   // reset
                  board[pos] =   empty ;
            }
            
      }
      
       return   evalValue;
      
} 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM