Apery(WCSC28)の評価関数パラメータを読み込む

 開発中の将棋ソフトについて、評価関数部分以外の性能を強豪ソフトと比較するため、評価関数パラメータを読み込む機能を実装した。

 AperyとはBonaPieceにおいて持ち駒0枚に数字を割り振っているかどうかや、盤上の駒を示す順番が角→馬→飛車→竜か角→飛車→馬→竜かという細かい違いはあったが、読み込んだのちパラメータを格納するタイミングで吸収できる。BonaPiece(海底ではPieceState)の変換は、洗練されていないコードになるが、次のようにした。

    auto changeToAperyPieceState = [&](int64_t p) {
        if (p >= white_hand_rook) {
            if (black_rook <= p && p < black_horse) {
                p += 81 * 2;
            } else if (black_horse <= p && p < black_dragon) {
                p -= 81 * 2;
            }
            return p + 14;
        } else if (p >= black_hand_rook) {
            return p + 13;
        } else if (p >= white_hand_bishop) {
            return p + 12;
        } else if (p >= black_hand_bishop) {
            return p + 11;
        } else if (p >= white_hand_gold) {
            return p + 10;
        } else if (p >= black_hand_gold) {
            return p + 9;
        } else if (p >= white_hand_silver) {
            return p + 8;
        } else if (p >= black_hand_silver) {
            return p + 7;
        } else if (p >= white_hand_knight) {
            return p + 6;
        } else if (p >= black_hand_knight) {
            return p + 5;
        } else if (p >= white_hand_lance) {
            return p + 4;
        } else if (p >= black_hand_lance) {
            return p + 3;
        } else if (p >= white_hand_pawn) {
            return p + 2;
        } else if (p >= black_hand_pawn) {
            return p + 1;
        }
    };

 問題はパラメータ自体の読み込み部分であり、どうもパラメータがおかしくなっているようだった。やねうら王を見てもAperyを見てもよくわからなかったが、Noviceのevaluate.cxxを見ることで解決した。KPPT型評価関数はKKPにもT部分を持っているということを知らず、KKPの方をただのint16_tで読み込もうとしていた。実際にはKKPにも手番ボーナスがあるため、型としてはstd::array<int16_t, 2>とするのが正しいようだ。

 現在の評価パラメータとApery(WCWC28)の評価パラメータを今の探索部にそれぞれ読み込み、自己対局を行った。現在の評価パラメータから見た対局結果が次のようになる。

対局数 勝利数 引き分け数 敗北数
2323 67 7 2249

 引き分けを0.5勝として勝率は約3.0%、Eloレート差にして601.8であった。

 予想よりも勝率が高い(0%になると思っていた)が、本来KKPTとして調整されている値の手番なしの方だけを抜き出して使用しているのでAperyパラメータの正しい棋力が発揮されていないことは確実である。今後は海底もKKPT_KPPT型に変更する。再学習が必要になるため検証はまたしばらく後になる。

 勝率が高い他の原因としては多様性を確保するために初手6手をランダムにしたため思考開始時点で形勢に差がついていることが原因かもしれない。しかし勝っていた棋譜をいくらか眺めると必ずしもそうではないようだった。

 コメントに残しておいた読み筋などを見るとAperyの評価パラメータを読み込んだ方に中終盤で致命的な読み抜けが発生しており、探索部に問題があるのではないかとも思われる。読み抜けが発生した局面を新規に検討させたところ実際に指された悪手は出てこなかったため、置換表に問題があるのではないかという疑いが生まれた。次回は置換表について検討する。

 以下が読み込み部分の全体となる。

template<typename T>
inline void EvalParams<T>::readAperyFile(std::string path) {
    constexpr int64_t AperyPieceStateNum = 1548;
    using KKPType = std::array<int16_t, 2>;
    using KPPType = std::array<int16_t, 2>;
    std::vector<KKPType> kkp_tmp(SqNum * SqNum * AperyPieceStateNum);
    std::vector<KPPType> kppt_tmp(SqNum * AperyPieceStateNum * AperyPieceStateNum);
    std::string kkp_file_name = path + "KKP.bin";
    std::string kppt_file_name = path + "KPP.bin";
    std::ifstream kkp_ifs(kkp_file_name, std::ios::binary);
    if (kkp_ifs.fail()) {
        std::cerr << kkp_file_name + " cannot open." << std::endl;
        clear();
        return;
    }
    std::ifstream kppt_ifs(kppt_file_name, std::ios::binary);
    if (kppt_ifs.fail()) {
        std::cerr << kppt_file_name + " cannot open." << std::endl;
        clear();
        return;
    }
    kkp_ifs.read(reinterpret_cast<char*>(kkp_tmp.data()), sizeof(KKPType) * kkp_tmp.size());
    kppt_ifs.read(reinterpret_cast<char*>(kppt_tmp.data()), sizeof(KPPType) * kppt_tmp.size());

    for (int64_t k1 = 0; k1 < SqNum; k1++) {
        for (int64_t p1 = 0; p1 < PieceStateNum; p1++) {
            int64_t apery_p1 = changeToAperyPieceState(p1);
            for (int64_t k2 = 0; k2 < SqNum; k2++) {
                auto key = k1 * SqNum * AperyPieceStateNum + k2 * AperyPieceStateNum + apery_p1;
                auto val = kkp_tmp[key][0];
                kkp[k1][k2][p1] = val;
            }
            for (int64_t p2 = 0; p2 < PieceStateNum; p2++) {
                int64_t apery_p2 = changeToAperyPieceState(p2);
                auto key = k1 * AperyPieceStateNum * AperyPieceStateNum + apery_p1 * AperyPieceStateNum + apery_p2;
                auto val = kppt_tmp[key];
                kppt[k1][p1][p2][0] = val[0];
                kppt[k1][p1][p2][1] = val[1];
            }
        }
    }
}