evaluate loose pieces

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
Evert
Posts: 2929
Joined: Sat Jan 22, 2011 12:42 am
Location: NL

Re: evaluate loose pieces

Post by Evert »

lucasart wrote:The idea is to make the bonus non linear, in the same logic as king safety. After all the reason why these bonus for attacking hanging pieces are small, are because the hanging pieces can be defended and it's hard to know whether we'll end up losing a piece or not. But one thing is sure, the more undefended hanging pieces, the more probability we won't be able to defend all of them.
Makes sense to me.
On my TODO list is an entry to experiment with a cumulative "piece safety" evaluation term, similar to the "king safety" term.

When I added a simple "hanging piece" evaluation to Sjaak, it improved performance in regular chess only slightly, but it made a huge difference for the more tactical Capablanca/Gothic chess.
lech
Posts: 1169
Joined: Sun Feb 14, 2010 10:02 pm

Re: evaluate loose pieces

Post by lech »

mcostalba wrote:

Code: Select all

  template<Color Us>
  Score evaluate_threats(const Position& pos, EvalInfo& ei) {

    const Color Them = (Us == WHITE ? BLACK : WHITE);

    Bitboard b, bb;
    Score score = SCORE_ZERO;

    // Enemy pieces not defended by a pawn and under our attack
    Bitboard weakEnemies =  pos.pieces(Them)
                          & ~ei.attackedBy[Them][PAWN]
                          & ei.attackedBy[Us][0];
    if (!weakEnemies)
        return SCORE_ZERO;

    Bitboard undefended = weakEnemies & ~ei.attackedBy[Them][0];

    // Add bonus according to type of attacked enemy piece and to the
    // type of attacking piece, from knights to queens. Kings are not
    // considered because are already handled in king evaluation.
    for (PieceType pt1 = KNIGHT; pt1 < KING; pt1++)
    {
        b = ei.attackedBy[Us][pt1] & weakEnemies;
        if (b)
            for (PieceType pt2 = PAWN; pt2 < KING; pt2++)
                if (bb = b & pos.pieces(pt2), bb)
                    score += bb & undefended ? 2 * ThreatBonus[pt1][pt2]
                                             : 1 * ThreatBonus[pt1][pt2];
    }
    return score;
  }

Code: Select all

  template<Color Us>
  Score evaluate_threats(const Position& pos, EvalInfo& ei) {

    const Color Them = (Us == WHITE ? BLACK : WHITE);

    Bitboard b, bb;
    Score score = SCORE_ZERO;

    // Enemy pieces not defended by a pawn and under our attack
    Bitboard weakEnemies =  pos.pieces(Them)
                          & ~ei.attackedBy[Them][PAWN]
                          & ei.attackedBy[Us][0];
    if (!weakEnemies)
        return SCORE_ZERO;

    Bitboard undefended = weakEnemies & ~ei.attackedBy[Them][0];

    // Add bonus according to type of attacked enemy piece and to the
    // type of attacking piece, from knights to queens. Kings are not
    // considered because are already handled in king evaluation.
    for (PieceType pt1 = KNIGHT; pt1 < KING; pt1++)
    {
        b = ei.attackedBy[Us][pt1] & weakEnemies;
        if (b)
            for (PieceType pt2 = PAWN; pt2 < KING; pt2++)
                if (bb = b & pos.pieces(pt2), bb)
                    score += bb & undefended ? 2 * ThreatBonus[pt1][pt2]
                                             : pt2 > pt1 ? ThreatBonus[pt1][pt2] : 0;
    }
    return score;
  }
Maybe, I can't be friendly, but let me be useful.
User avatar
Eelco de Groot
Posts: 4669
Joined: Sun Mar 12, 2006 2:40 am
Full name:   Eelco de Groot

Re: evaluate loose pieces

Post by Eelco de Groot »

mcostalba wrote:
Eelco de Groot wrote: I think the change you propose is not really what Lucas means with "loose pieces" the usual term,
I have looked at his code, not how it is called.
Eelco de Groot wrote: My own version of that code in Stockfish is a bit experimental and not very streamlined code
Your code is too complicated to work ;-)

You are attacking 3 concepts (loose pieces, passed pawns and king attacks) in a single piece of code: it doesn't work like this. I'd suggest a more orthogonal approach, one different function for each concept. A possible implementation of your idea that seems an acceptable compromise between speed and accuracy could be:

Code: Select all

  template<Color Us>
  Score evaluate_threats(const Position& pos, EvalInfo& ei) {

    const Color Them = (Us == WHITE ? BLACK : WHITE);

    Bitboard b, bb;
    Score score = SCORE_ZERO;

    // Enemy pieces not defended by a pawn and under our attack
    Bitboard weakEnemies =  pos.pieces(Them)
                          & ~ei.attackedBy[Them][PAWN]
                          & ei.attackedBy[Us][0];
    if (!weakEnemies)
        return SCORE_ZERO;

    Bitboard undefended = weakEnemies & ~ei.attackedBy[Them][0];

    // Add bonus according to type of attacked enemy piece and to the
    // type of attacking piece, from knights to queens. Kings are not
    // considered because are already handled in king evaluation.
    for (PieceType pt1 = KNIGHT; pt1 < KING; pt1++)
    {
        b = ei.attackedBy[Us][pt1] & weakEnemies;
        if (b)
            for (PieceType pt2 = PAWN; pt2 < KING; pt2++)
                if (bb = b & pos.pieces(pt2), bb)
                    score += bb & undefended ? 2 * ThreatBonus[pt1][pt2]
                                             : 1 * ThreatBonus[pt1][pt2];
    }
    return score;
  }
Okay I accept that challenge; I have actually put in more code Marco :P Not that I don't think you guys are right; that this is difficult to test and in particular there is no way to see if any elements are actually making things worse if you don't test all of it separately. But there is an idea to it all that I try to get across, sometimes there are features in a chess position that you can't do so much about. They may have happened "by accident" or your opponent got them with a superior search or superior knowledge implemented. Of for instance they came with the opening. And maybe when you detect it, it is already too late. Now you can include all the knowledge you know in your evaluation function, but here you actually have the chance to do something about some of the elements! The piece is attacked and there is no defending pawn. The piece does not actually have to hang but you do know for sure you can attack it and you even know with what piece you do this. Maybe then it pays off, to see how good your target really is. A parallel with king safety: you start evaluating that fully only if you have more than one attacker in place.

For instance another idea is you could put the results in a separate evalmargin that includes attacker and attacked piece, and specifically use that knowledge in extending moves of the attacking and attacked piece. Just to name an idea what probably is just within limits of my own programming possibilities :) I am maintaining the copyright to my codechanges :P and it now looks like this:

Code: Select all


  template<Color Us>
  Score evaluate_threats(const Position& pos, EvalInfo& ei) {

    const Color Them = (Us == WHITE ? BLACK : WHITE);

    Bitboard b;
    Score score = SCORE_ZERO;

    // Enemy pieces not defended by a pawn and under our attack
    Bitboard weakEnemies =  pos.pieces(Them)
                          & ~ei.attackedBy[Them][PAWN]
                          & ei.attackedBy[Us][0];
    if (!weakEnemies)
        return SCORE_ZERO;

    // Add bonus according to type of attacked enemy piece [pt2] and to the
    // type of attacking piece [pt1], from knights to queens. Kings are not
    // considered under pt2 because that is already handled in king evaluation.
    for (PieceType pt1 = KNIGHT; pt1 <= KING; pt1++)
    {
        b = ei.attackedBy[Us][pt1] & weakEnemies;
        if (b)
            for (PieceType pt2 = PAWN; pt2 < KING; pt2++)
            {
                Bitboard bb = b & pos.pieces(pt2);
                if (bb)
                {
                   score += ThreatBonus[pt1][pt2];
                   do { // Is the piece undefended?
                         bool undefended = false; 
                         Square s = pop_1st_bit(&bb);
                         if (!(ei.attackedBy[Them][0] & s))
                         {
                            undefended = true;
                            score += ThreatBonus[pt1][pt2];
                            if (pt2 == PAWN && (!pos.square_is_empty(s + pawn_push(Them)) || (ei.attackedBy[Us][0] & s + pawn_push(Them))))
                            {
                               score += make_score(5, 10);
                               // Nearing a pawn ending and the pawn may become a candidate pawn or a passed pawn?
                               File f = file_of(s);
                               if (ei.pi->file_is_half_open(Us, f) && !pos.non_pawn_material(Them))
                                   score += make_score(0, 2*relative_rank(Them, s));
                               if (ei.pi->chainbase_pawns(Us) & s)
                                   score += make_score(20, 20);
                            }
                          }
                          // Does the piece attack our king?
                          if ((ei.kingAttackersBB[Us] & s))
                              score += make_score(30 + undefended * 20, 0);
                   } while (bb);
                }
             }
    }
    return score;
  }
Debugging is twice as hard as writing the code in the first
place. Therefore, if you write the code as cleverly as possible, you
are, by definition, not smart enough to debug it.
-- Brian W. Kernighan
gladius
Posts: 568
Joined: Tue Dec 12, 2006 10:10 am
Full name: Gary Linscott

Re: evaluate loose pieces

Post by gladius »

mcostalba wrote:A possible implementation of your idea that seems an acceptable compromise between speed and accuracy could be:

Code: Select all

  template<Color Us>
  Score evaluate_threats(const Position& pos, EvalInfo& ei) {

    const Color Them = (Us == WHITE ? BLACK : WHITE);

    Bitboard b, bb;
    Score score = SCORE_ZERO;

    // Enemy pieces not defended by a pawn and under our attack
    Bitboard weakEnemies =  pos.pieces(Them)
                          & ~ei.attackedBy[Them][PAWN]
                          & ei.attackedBy[Us][0];
    if (!weakEnemies)
        return SCORE_ZERO;

    Bitboard undefended = weakEnemies & ~ei.attackedBy[Them][0];

    // Add bonus according to type of attacked enemy piece and to the
    // type of attacking piece, from knights to queens. Kings are not
    // considered because are already handled in king evaluation.
    for (PieceType pt1 = KNIGHT; pt1 < KING; pt1++)
    {
        b = ei.attackedBy[Us][pt1] & weakEnemies;
        if (b)
            for (PieceType pt2 = PAWN; pt2 < KING; pt2++)
                if (bb = b & pos.pieces(pt2), bb)
                    score += bb & undefended ? 2 * ThreatBonus[pt1][pt2]
                                             : 1 * ThreatBonus[pt1][pt2];
    }
    return score;
  }
I gave this idea a try, and it seems that 2x threat bonus is too much. Didn't test too long, but after 150 games, about -40 elo.

Tried a more modest variation of:

Code: Select all

score += bb & undefended ? ((5 * ThreatBonus[pt1][pt2]) / 4)
+																						 : ((3 * ThreatBonus[pt1][pt2]) / 4);
And that was:
Score of base vs Stockfish 120227 64bit: 178 - 180 - 635 [0.50] 993
ELO difference: -1

So, no real help :). I'll try 1.5x and 0.5x.
lech
Posts: 1169
Joined: Sun Feb 14, 2010 10:02 pm

Re: evaluate loose pieces

Post by lech »

Prominent programmers are like the Egyptian priests. :D
It is very difficult to change anything in Stockfish evoluation.
A lot of values ​​is tuned and they should probably be retuned after each change.
It dosn't mean that this evoluation works well. :wink:
Maybe, I can't be friendly, but let me be useful.
zamar
Posts: 613
Joined: Sun Jan 18, 2009 7:03 am

Re: evaluate loose pieces

Post by zamar »

lech wrote:Prominent programmers are like the Egyptian priests. :D
It is very difficult to change anything in Stockfish evoluation.
SF's evaluation is very standard and based on publicly available ideas.
It took >30 years of time and many brilliant people to find the correct ideas. Sure it's difficult to improve!

But if you want to try I'd suggest to concentrate on the king safety and passed pawns, they are the least standardized evaluation features of the chess engines (in general).

Also SF lacks a lot of endgame knowledge. We'd need to find the correct scaling factor for many different end game types and situation. But implementing this requires a lot of time and skill which very few people have. It's easy to make a 5 line change, but when one needs to design and implement something new (and make it correctly & without bugs) it's where most people fail.
A lot of values ​​is tuned and they should probably be retuned after each change.
In my experience: Good idea is +10 elo. Fine tuning it is +5 elo. So if you find a good idea, it should work also without tuning. Tuning can only give a little extra push.
It dosn't mean that this evoluation works well. :wink:
Good! Please point out something better.
Joona Kiiski
gladius
Posts: 568
Joined: Tue Dec 12, 2006 10:10 am
Full name: Gary Linscott

Re: evaluate loose pieces

Post by gladius »

lech wrote:Prominent programmers are like the Egyptian priests. :D
It is very difficult to change anything in Stockfish evoluation.
A lot of values ​​is tuned and they should probably be retuned after each change.
It dosn't mean that this evoluation works well. :wink:
Indeed, it is not easy to find improvements! I have a huge amount of respect for folks that are able to take a top performing engine and improve it even 5 elo.

Oh, and trying 1.5x and 0.5x was about -40 elo as well, after 250 games.
lech
Posts: 1169
Joined: Sun Feb 14, 2010 10:02 pm

Re: evaluate loose pieces

Post by lech »

zamar wrote:
It dosn't mean that this evoluation works well. :wink:
Good! Please point out something better.
I was watching many games (13 min + 2 sec/move; PIV 2 threads) Stockfish 2.1.1 - Houdini 1.5a. IMO Houdini has a very good evoluation.
I have many ideas.
My first sugestion: increase shelter!
Maybe, I can't be friendly, but let me be useful.
User avatar
Eelco de Groot
Posts: 4669
Joined: Sun Mar 12, 2006 2:40 am
Full name:   Eelco de Groot

Re: evaluate loose pieces

Post by Eelco de Groot »

zamar wrote:
lech wrote:Prominent programmers are like the Egyptian priests. :D
It is very difficult to change anything in Stockfish evoluation.
SF's evaluation is very standard and based on publicly available ideas.
It took >30 years of time and many brilliant people to find the correct ideas. Sure it's difficult to improve!

But if you want to try I'd suggest to concentrate on the king safety and passed pawns, they are the least standardized evaluation features of the chess engines (in general).

Also SF lacks a lot of endgame knowledge. We'd need to find the correct scaling factor for many different end game types and situation. But implementing this requires a lot of time and skill which very few people have. It's easy to make a 5 line change, but when one needs to design and implement something new (and make it correctly & without bugs) it's where most people fail.
A lot of values ​​is tuned and they should probably be retuned after each change.
In my experience: Good idea is +10 elo. Fine tuning it is +5 elo. So if you find a good idea, it should work also without tuning. Tuning can only give a little extra push.
It dosn't mean that this evoluation works well. :wink:
Good! Please point out something better.

I was just thinking about a rather simple idea, that is along Sting goals too and could very probably be used there, if it can be tuned. Right now in margins() we (Stockfish) only store a bad King Safety situation with the idea that, only if you have the move, you could improve your situation because you have the right to move right now, capturing one of the pieces that threaten your king can improve your king safety, even if the material value would get worse. We ignore any possible improvement for instance to the other side, if we are actually the King attacker that can take out any of the defending pieces, and more in general any possible positional value that could either improve, get better so there is compensation for any sacrificed/lost material, or alternatively get worse so the hoped for compensarion eventually evaporates to the better material possessed by the other side. Then there is the error in the evaluation itself which also will have some statistical properties, if it is noise like error it will also probably have some bell shaped distribution around a more perfect, and imaginary errorfree eval.

I think this simple idea could be condensed to no more than a one line change, in evaluate.cpp, just for example we could be changing at the end of this fragment, line 446-453:

Code: Select all

      else
          // Endgame with opposite-colored bishops, but also other pieces. Still
          // a bit drawish, but not as drawish as with only the two bishops.
           sf = ScaleFactor(50);
  }

  margin = margins[pos.side_to_move()];
  Value v = interpolate(score, ei.mi->game_phase(), sf);
Adding one line:

Code: Select all

      else
          // Endgame with opposite-colored bishops, but also other pieces. Still
          // a bit drawish, but not as drawish as with only the two bishops.
           sf = ScaleFactor(50);
  }

  // Interpolate between the middle game and the endgame score
  margins[pos.side_to_move()] += scale_by_game_phase(std::max(score - ei.mi->material_value(), ei.mi->material_value() - score)/(32), ei.mi->game_phase(), sf);
  margin = margins[pos.side_to_move()];
  Value v = scale_by_game_phase(score, ei.mi->game_phase(), sf);
The division here by 32 is arbitrarily chosen, it should be tuned. Maybe it should be some expression but in that case there is the danger that you run into "division by zero" errors very quickly, and Stockfish will crash with an 0xc0000094 error code. The error margin should be a positive number to work as an futility margin for the side to move, especially if the idea is that, without Zugzwang, the side to move should be able to improve his position (goal is to judge if we can reach a beta cutoff, eval > alpha = beta - 1 in an alpha-beta framework. We also ignore the fact for the moment that advantages on your side do not simply evaporate if you just don't happen to have the move right now). Thinking along statistical lines there should probably some sort of second order term just like the Standard deviation is some average error squared, but I have not bothered about that yet.

Okay it is just some idea but as it is a one line change to the Stockfish code and can be tuned in many ways, by dividing by some simple number the impact on search extensions can be made as small as you wish, I think this was the most worthwhile eval idea I had today 8-)

Regards, Eelco
Debugging is twice as hard as writing the code in the first
place. Therefore, if you write the code as cleverly as possible, you
are, by definition, not smart enough to debug it.
-- Brian W. Kernighan