SARSA

SARSA は、次に実際に選ぶ行動まで含めて更新する on-policy 手法です。Q 学習と式はよく似ていますが、policy の中に探索が入っていることを、そのまま価値へ織り込む点が本質的に違います。

参考動画(外部)

授業本編ではなく、別の説明で見直したいときの参考材料です。

「次に実際に選ぶ行動」で学ぶと、方策の危なさも値に入る

SARSA では目標値が r+γQ(s,a)r + \gamma Q(s', a') になります。a' はその場で実際に使っている方策から選ばれるので、探索で危険な行動を踏む可能性まで含めて価値が下がります。

この notebook の例で risky が低く出やすいのは、失敗しうる行動を policy 自身がまだ持っているからです。Q 学習との違いは 1 箇所しかありませんが、その 1 箇所が方策の性格を変えます。

見るべきポイント

SARSA と Q-learning の更新式の差、safe/risky の値差、探索込み policy を学ぶとはどういうことかを追います。

この notebook は、安全側に寄りやすい on-policy 更新の感触を掴むためのものです。一般の制御問題を網羅するのではなく、Q 学習との差分を明瞭に見ることを優先しています。

読み方の軸

数値の大小だけでなく、「誰の policy を評価しているか」を常に意識してください。SARSA は探索込みの自分自身を評価しながら改善していきます。

この最小例で見えてくること

safe/risky の toy 例は単純ですが、探索ノイズを含む policy をそのまま学ぶと、危険な縁を避けやすくなるという SARSA の性格はかなりはっきり現れます。

この比較例の読みどころ

ここで使っている環境は最小の toy ですが、そのぶん Q 学習と SARSA の差が見えやすくなっています。一般の制御問題を模倣しているというより、on-policy 更新が policy の危なさまで値に入れることを確かめるための例として読んでください。

import random
from collections import defaultdict

random.seed(7)

gamma = 0.95
alpha = 0.2
epsilon = 0.2
actions = ['safe', 'risky']


def step(state, action):
    # 2段階のエピソードにして、s' での行動選択が効くようにする
    if state == 's0':
        if action == 'safe':
            return 's1', 0.2
        return 's1', (0.7 if random.random() < 0.5 else -0.6)

    if state == 's1':
        if action == 'safe':
            return 'terminal', 0.6
        return 'terminal', (1.2 if random.random() < 0.2 else -1.0)

    return 'terminal', 0.0


def eps_greedy(Q, state):
    if random.random() < epsilon:
        return random.choice(actions)
    qs = [Q[(state, a)] for a in actions]
    return actions[0] if qs[0] >= qs[1] else actions[1]

1ステップ更新の式

SARSA:

Q(s,a)Q(s,a)+α{r+γQ(s,a)Q(s,a)}Q(s,a) \leftarrow Q(s,a) + \alpha \{r + \gamma Q(s',a') - Q(s,a)\}

Q学習:

Q(s,a)Q(s,a)+α{r+γmaxaQ(s,a)Q(s,a)}Q(s,a) \leftarrow Q(s,a) + \alpha \{r + \gamma \max_{a'}Q(s',a') - Q(s,a)\}

差は目標値の作り方だけですが、学習結果の方策が変わります。

Q_sarsa = defaultdict(float)
Q_q = defaultdict(float)

for _ in range(800):
    # SARSA episode
    s = 's0'
    a = eps_greedy(Q_sarsa, s)
    while s != 'terminal':
        s_next, r = step(s, a)
        if s_next == 'terminal':
            target = r
            Q_sarsa[(s, a)] += alpha * (target - Q_sarsa[(s, a)])
            break
        a_next = eps_greedy(Q_sarsa, s_next)
        target = r + gamma * Q_sarsa[(s_next, a_next)]
        Q_sarsa[(s, a)] += alpha * (target - Q_sarsa[(s, a)])
        s, a = s_next, a_next

    # Q-learning episode
    s = 's0'
    while s != 'terminal':
        a = eps_greedy(Q_q, s)
        s_next, r = step(s, a)
        if s_next == 'terminal':
            target = r
        else:
            target = r + gamma * max(Q_q[(s_next, x)] for x in actions)
        Q_q[(s, a)] += alpha * (target - Q_q[(s, a)])
        s = s_next

print('SARSA Q(s0) =', {a: round(Q_sarsa[('s0', a)], 4) for a in actions})
print('Q-learn Q(s0)=', {a: round(Q_q[('s0', a)], 4) for a in actions})
print('SARSA Q(s1) =', {a: round(Q_sarsa[('s1', a)], 4) for a in actions})
print('Q-learn Q(s1)=', {a: round(Q_q[('s1', a)], 4) for a in actions})

高分散な risky を過大評価しにくいのが SARSA の利点です。実際に使う policy の振る舞いを重視したい場面で、この保守性が効きます。