BellmanOptimalityEquation
次はベルマン最適方程式をやってみる.
前回の式との違いは、和となっていたところがmaxとなっている部分である.
は言ってしまえば方策なので、方策のうち一番良いものを選ぶことで最適なものを選んでいるわけである.
これ自体は実装としては非常に簡単で、前回のグリッドワールドにちょっと変更を加えるだけである.
auto [nextState, reward] = Step(Vec2i{ i,j }, GetMoveDirection(action));
// ベルマン方程式
values.push_back(reward + DISCOUNT * worlds[nextState.x * WORLD_SIZE + nextState.y]);
この時計算した結果を一旦保持しておく.
あとは最大値をとればよいだけ、これがmaxの処理に相当.
これをまとめて書くと以下のようになる.
Array<double> values;
for (const auto& action : actions)
{
auto [nextState, reward] = Step(Vec2i{ i,j }, GetMoveDirection(action));
// ベルマン方程式
values.push_back(reward + DISCOUNT * worlds[nextState.x * WORLD_SIZE + nextState.y]);
}
newValues[i * WORLD_SIZE + j] = *std::max_element(values.begin(), values.end());
これの目的は、の中で一番良い方策を可視化することである.
まずは周囲の数値を計算していく.
```c++
// 周囲の数値を計算
Array<std::tuple<double, Direction>> nextValues;
for (const auto& action : actions)
{
auto [nextState, reward] = Step(Vec2i{ i,j }, GetMoveDirection(action));
if (reward == -1) { continue; } // 範囲外を省いておく
nextValues.push_back({ worlds[nextState.x * WORLD_SIZE + nextState.y], action });
}
更に言うと、外側に出るような移動の場合は省略しておく.
// 最大値を取る
std::tuple<double, Direction> maxValue = *std::max_element(
nextValues.begin(), nextValues.end(),
[](const std::tuple<double, Direction>& a, const std::tuple<double, Direction>& b) {
return std::get<0>(a) < std::get<0>(b);
});
最大の値となる場合は最適な方向なので、これで最適なが見つかることになる.
// 最大値に近い方向を取得
String arrow;
for (auto& value : nextValues)
{
if (std::get<0>(maxValue) - std::get<0>(value) < 1e-16)
{
arrow += GetMoveFigs(std::get<1>(value));
}
}
ただし最大の方向は複数存在する可能性があるので、見つかった最大値と比較して、ある程度近ければそれが最適な方向となる.
これをまとめれば以下のようになる.
// 周囲の数値を計算
Array<std::tuple<double, Direction>> nextValues;
for (const auto& action : actions)
{
auto [nextState, reward] = Step(Vec2i{ i,j }, GetMoveDirection(action));
if (reward == -1) { continue; } // 範囲外を省いておく
nextValues.push_back({ worlds[nextState.x * WORLD_SIZE + nextState.y], action });
}
// 最大値を取る
std::tuple<double, Direction> maxValue = *std::max_element(
nextValues.begin(), nextValues.end(),
[](const std::tuple<double, Direction>& a, const std::tuple<double, Direction>& b) {
return std::get<0>(a) < std::get<0>(b);
});
// 最大値に近い方向を取得
String arrow;
for (auto& value : nextValues)
{
if (std::get<0>(maxValue) - std::get<0>(value) < 1e-16)
{
arrow += GetMoveFigs(std::get<1>(value));
}
}

矢印の方向が最適な方向.
基本的に最大の方向に向かっているのが分かる.
これで三章の内容は終わり!