暇さえあればアルゴリズムいじり

暇があればアルゴリズムいじり

Obsessed with algorithms whenever I have a free moment.

AI & IT Engineer / Father of 3

"Dream shall be realized with dream — Always tinkering with algorithms"

Pursuitのチーム連携強化学習の実装(15)

本日はチーム連携の強化学習を行って捕獲を行っていくPursuitのトライアルについてです。 前回改修で以下を課題として、改修を行いました。

  • Cross-Attentionのゲートが「クエリ(自分自身)」だけから計算されている
  • チーム全体の同時クリッピングが、遅れているエージェントの学習を妨げる可能性

ですが劇的before & afterとはなりませんでした。 現状でチーム連携は出来ているのですが、一角を捕獲し尽すと停滞してしまう傾向がありました。

本日テーマ:

味方集結して、捕獲しつくした後にすぐに切り替え出来るようにする

課題の整理

開始直後には問題ありません。味方を目指して一角に集結する動作を行います。 そして、その後のチーム連携の動作も問題ありません。

課題となっているのは、一角の敵をとりつくした後も、チームで一角に居座り続けるという挙動を行っています。

結果としてより高得点を目指すことが難しくなっています。

一角をとりつくした後は次の行動=チームとして敵を探索しに行くという行動を推奨するようにする必要があります。

課題の原因と対策

課題としている事実の原因についてコードと見比べっこして確認してみました。 主な課題と感じた部分を挙げます。

原因1: 索敵フェーズの報酬設計が「留まる」ことを積極的には罰していない

PursuitWrapper.step() の索敵フェーズ(current_min_dist is None、つまり視界内に敵がいない)の報酬ロジックを見てみます。

if current_min_dist is None:
    allies_in_view = np.sum(ally_layer > 0) + 1
    ...
    if allies_in_view == 4 and has_moved:
        individual_reward += self.search_coop_move_bonus       # +0.05
    elif not has_moved:
        individual_reward += self.search_stagnation_penalty    # -0.1

一見「留まったら罰則がある」ように見えますが、よく見ると条件分岐が allies_in_view == 4 and has_moved のケース以外は全部 elif not has_moved に落ちるわけではありません。具体的には:

  • allies_in_view == 4 かつ 動いていない → どちらの分岐にも入らず、individual_reward += 0(何も加算されない)
  • allies_in_view != 4 かつ 動いていない → search_stagnation_penalty(-0.1) が適用される
  • allies_in_view != 4 かつ 動いている → 何も加算されない(ボーナスなし、罰則なし)

つまり、「4人ちょうど集まった状態で止まっている」場合だけ、唯一ペナルティが免除されるという抜け穴があります。一角を捕獲し終えた直後、生き残った4体(あるいはそれ以上)がちょうど集まった状態で近くにいると、この条件に該当してペナルティを受けずに停止し続けられてしまいます。

さらに、allies_in_view は「視界7x7以内」に限定されているため、実際には索敵行動が必要な広いマップでも、近くの味方の頭数さえ数が合えば「留まる」ことが咎められない設計になっています。

対策

  • 停滞ペナルティを allies_in_view の値に関わらず一律で適用する(「4人ちょうど揃っている場合の免除」をなくす)
  • あるいは、免除条件を「4人揃っていて、かつそのうち少なくとも1体は動いている」のようなチーム単位の判定にする(個々のエージェントの has_moved だけでなくチーム全体で見る)
if current_min_dist is None:
    allies_in_view = np.sum(ally_layer > 0) + 1
    prev_pos = self.prev_agent_positions.get(agent)
    has_moved = prev_pos is not None and curr_pos is not None and prev_pos != curr_pos

    if has_moved:
        if allies_in_view == 4:
            individual_reward += self.search_coop_move_bonus
        # 動いていれば人数に関わらず停滞ペナルティは受けない(現状維持)
    else:
        # 🌟 修正: 4人揃っていても動いていなければペナルティを免除しない
        individual_reward += self.search_stagnation_penalty

原因2: 「その場にとどまる」ことが、学習された方策としてローカルな最適解になっている(探索不足)

探索を行うことのメリットが非常に小さいことです。

search_stagnation_penalty=-0.1 に対して、動いて索敵し次のターゲットに辿り着くまでの過程は、distance_reward_scale=0.001(かなり小さい)でしか報われません。つまり:

  • 留まることの罰: -0.1/step(明確で即時)
  • 正しく索敵に動くことの報酬: ターゲットが見つかるまでは基本的に何も得られない(見つかってようやく distance_reward_scale による小さな報酬が始まる)

このバランスだと、探索空間が広い(マップが大きい、残りpreyが少ない)状況ほど、「留まって-0.1を受け続ける」方が「見えないゴールを求めて動き回る」より学習上"損切りしやすい"局所解になりがちです。特にエントロピー係数 entropy_coef=0.01 が小さいと、学習が進むにつれ探索的な行動が失われ、この局所解に収束しやすくなります。

対策

  • search_coop_move_bonus を明確に「動いて索敵している」ことに対して継続的に与える設計にする(現状はallies_in_view==4の場合のみで発生条件が狭すぎます)
  • 索敵時の移動そのものに小さな正の報酬を与える(has_movedなら常に微小ボーナス、動かなければ罰、というシンプルな設計に一度戻して検証する)
  • entropy_coef を一時的に上げて(例: 0.01 → 0.03)、停滞という局所解からの脱出を促す

実装

上記節で検討した対策案を実施するため、以下の2点の想定原因の解決のための実装を行います。

  1. 停滞ペナルティの抜け穴を撤廃: 「4人ちょうど視界内にいれば免除」という条件をなくし、周囲に敵がいない時に動かなければ常に罰する
  2. 索敵中のチーム単位の接近報酬: 局所観測内には敵が見えなくても、環境の実座標(raw_env)を使って「最寄りの未捕獲preyまでの距離」を計算し、それが縮まった上でチームの一定数以上が同時に接近した場合にボーナスを与える

局所観測だけでは「敵の方向」が分からないため、報酬計算にはグローバル情報(raw_env)を使います。これは観測(エージェントに見せる入力)を変えるわけではなく、あくまで報酬シェーピングのための特権情報利用なので、CTDE(中央集権的学習)の枠組みでは一般的な手法です。

変更箇所

変更箇所1. __init__ に状態追跡用の変数とパラメータを追加

def __init__(self, render_mode=None, max_cycles=500, obs_range=7):
    ...(既存のコードはそのまま)...

    # 🌟 追加: 索敵フェーズ用の追加パラメータ
    self.search_stagnation_penalty = -0.1   # 既存。仕様は変更せず流用(免除条件だけ撤廃)
    self.search_approach_bonus = 0.05       # 🌟 追加: 個別に、未捕獲preyへグローバル距離が縮まったときの報酬
    self.search_team_approach_bonus = 0.2   # 🌟 追加: チーム(規定人数以上)が同時に接近したときの追加ボーナス
    self.search_team_approach_threshold = 3 # 🌟 追加: 「チームで接近」とみなす最低人数

    # 🌟 追加: 索敵フェーズでのグローバル距離追跡用
    self.prev_global_min_dist = {}
    self._search_approach_registry = {}
    self._search_team_bonus_given_cycle = -1

変更箇所2. reset() に初期化を追加

def reset(self):
    self.env.reset()
    self.prev_min_distances = {agent: None for agent in self.possible_agents}
    self.prev_agent_positions = {agent: None for agent in self.possible_agents}
    self.last_actions = np.zeros((self.num_agents, 5), dtype=np.float32)
    self.last_actions[:, 4] = 1.0
    self.capture_count = 0
    self.captured_prey_ids = set()

    # 🌟 追加
    self.prev_global_min_dist = {agent: None for agent in self.possible_agents}
    self._search_approach_registry = {}
    self._search_team_bonus_given_cycle = -1

変更箇所3. グローバル距離を計算するヘルパーを追加

前回実装した compute_priority_scores とほぼ同じロジックですが、単一エージェント用に切り出します。既存の compute_priority_scores があるなら、内部でこちらを呼び出す形に統一しても構いません。

def _compute_global_min_dist_to_evader(self, agent) -> float | None:
    """
    局所観測(7x7)の視界に関わらず、環境の実座標を使って
    エージェントから最寄りの未捕獲preyまでのマンハッタン距離を計算する。
    報酬シェーピング専用(観測には使わない特権情報)。
    未捕獲preyが存在しない、または座標取得に失敗した場合はNoneを返す。
    """
    raw_env = self.env.unwrapped

    try:
        evader_positions = [(e.state[1], e.state[0]) for e in raw_env.evaders]
    except Exception:
        return None

    if not evader_positions:
        return None

    try:
        agent_obj = next(a for a in raw_env.agents if a.name == agent)
        ay, ax = agent_obj.state[1], agent_obj.state[0]
    except Exception:
        return None

    return min(abs(ay - py) + abs(ax - px) for py, px in evader_positions)

変更箇所4. step() の索敵フェーズ部分を修正

該当箇所(if current_min_dist is None: のブロック)を以下に差し替えてください。

    def step(self, agent, action):
        if agent not in self.env.agents:
            return np.zeros(self.obs_dim, dtype=np.float32), 0.0, True, True, {}, 0

        current_cycle = getattr(self.env.unwrapped, 'cycles', 0)
        _, _, terminated, truncated, _ = self.env.last(agent)
        step_action = None if (terminated or truncated) else action

        agent_idx = int(agent.split('_')[-1])
        if step_action is not None:
            self.last_actions[agent_idx] = 0.0
            self.last_actions[agent_idx, step_action] = 1.0

        self.env.step(step_action)

        if agent not in self.env.agents:
            return np.zeros(self.obs_dim, dtype=np.float32), 0.0, True, True, {}, 0

        obs_flat, team_reward, terminated, truncated, info = self.env.last(agent)
        obs = self.get_obs(agent)

        individual_reward = 0.0

        raw_env = self.env.unwrapped
        curr_pos = None
        try:
            agent_obj = next(a for a in raw_env.agents if a.name == agent)
            curr_pos = (agent_obj.state[1], agent_obj.state[0])
        except Exception:
            pass

        current_min_dist, allies_count, flank_allies, shaping_reward, count_capture, coop_density_reward = self._analyze_observation(agent, obs)
        count_capture = count_capture if count_capture else 0
        team_reward += self.surround_reward * count_capture

        # -----------------------------------------------------------------
        # 🌟 索敵フェーズ(視界内に敵がいない)の処理 — ここを修正
        # -----------------------------------------------------------------
        if current_min_dist is None:
            prev_pos = self.prev_agent_positions.get(agent)
            has_moved = False
            if prev_pos is not None and curr_pos is not None:
                if prev_pos != curr_pos:
                    has_moved = True

            # 🌟 修正1: 停滞ペナルティは人数条件に関わらず一律で適用する
            #    (「4人揃っていれば免除」という抜け穴を撤廃)
            if not has_moved:
                individual_reward += self.search_stagnation_penalty
                is_search_approaching = False
            else:
                # 🌟 修正2: グローバル座標を使い、未捕獲preyへの距離が縮まったかを評価
                current_global_dist = self._compute_global_min_dist_to_evader(agent)
                prev_global_dist = self.prev_global_min_dist.get(agent)

                is_search_approaching = False
                if current_global_dist is not None and prev_global_dist is not None:
                    if current_global_dist < prev_global_dist:
                        individual_reward += self.search_approach_bonus
                        is_search_approaching = True

                self.prev_global_min_dist[agent] = current_global_dist

            # 🌟 修正3: チーム単位で同時に接近しているかを集計し、
            #    規定人数以上が同時接近していれば追加ボーナス
            if current_cycle not in self._search_approach_registry:
                self._search_approach_registry[current_cycle] = {}
            self._search_approach_registry[current_cycle][agent] = is_search_approaching

            if len(self._search_approach_registry[current_cycle]) >= len(self.env.agents):
                approaching_count = sum(self._search_approach_registry[current_cycle].values())
                if (approaching_count >= self.search_team_approach_threshold
                        and self._search_team_bonus_given_cycle != current_cycle):
                    individual_reward += self.search_team_approach_bonus
                    self._search_team_bonus_given_cycle = current_cycle
                    self._search_approach_registry = {
                        k: v for k, v in self._search_approach_registry.items() if k >= current_cycle
                    }
        else:
            # 敵が視界内にいる場合は、既存の包囲シェイピング・人数最適化報酬を適用
            individual_reward += shaping_reward
            individual_reward += coop_density_reward
            # 🌟 視界内に敵が現れたら、索敵フェーズ用のグローバル距離追跡はリセットしておく
            self.prev_global_min_dist[agent] = None

        # 座標の更新
        if curr_pos is not None:
            self.prev_agent_positions[agent] = curr_pos

        # 1. 距離・回り込みベースの評価(視界内に敵がいる場合のみ意味を持つ既存ロジック)
        reward_distance = 0.0
        prev_dist = self.prev_min_distances.get(agent)

        is_approaching = False
        if current_min_dist is not None and prev_dist is not None:
            change = prev_dist - current_min_dist
            reward_distance = change * self.distance_reward_scale
            if change > 0:
                is_approaching = True
                if flank_allies == 0:
                    reward_distance += self.flanking_bonus_scale

        self.prev_min_distances[agent] = current_min_dist
        individual_reward += reward_distance

        # 2. 協調行動(接近+包囲網)の評価
        reward_coop = 0.0
        if current_min_dist is not None and current_min_dist <= 2:
            if allies_count >= 1:
                reward_coop += self.coop_reward_scale
            if allies_count >= 3:
                reward_coop += self.surround_reward
        individual_reward += reward_coop

        # 3. 同時アプローチ(視界内に敵が見えている状態での4人同時接近)のチーム集計とボーナス適用
        if not hasattr(self, '_approach_registry'):
            self._approach_registry = {}
            self._cycle_bonus_given = -1

        if current_cycle not in self._approach_registry:
            self._approach_registry[current_cycle] = {}

        self._approach_registry[current_cycle][agent] = is_approaching

        if len(self._approach_registry[current_cycle]) >= len(self.env.agents):
            approaching_count = sum(self._approach_registry[current_cycle].values())
            if approaching_count == 4 and self._cycle_bonus_given != current_cycle:
                individual_reward += self.simultaneous_approach_bonus
                self._cycle_bonus_given = current_cycle
                self._approach_registry = {k: v for k, v in self._approach_registry.items() if k >= current_cycle}

        # 4. 衝突ペナルティの評価
        if info.get('wasted_move', False):
            individual_reward += self.collision_penalty

        # 5. 完全捕獲成功時のボーナス
        if terminated and not truncated:
            if team_reward > 0:
                time_bonus = max(0, 500 - current_cycle) * 1.0
                individual_reward += (500.0 + time_bonus)
                print(f"--- 🎉 TRUE CAPTURE SUCCESS! Agent: {agent} | Bonus: {500.0 + time_bonus} ---")

        hybrid_reward = (team_reward + individual_reward) * 0.05

        return obs, hybrid_reward, terminated, truncated, info, count_capture

主な変更点の解説

修正1: 停滞ペナルティの一律適用

旧コードは elif not has_moved: という分岐で、「allies_in_view == 4 and has_moved」の条件に合致しない場合の一部だけしかペナルティが発生しませんでした(allies_in_view==4 かつ動いていない場合は両方の条件に該当せず何も起きなかった)。新コードでは if not has_moved: を最初の分岐にして、視界内に敵がいない状態で動いていなければ、味方の人数に関係なく常にペナルティが入るようにしました。

修正2: グローバル距離を使った個別接近報酬

_compute_global_min_dist_to_evader で、局所観測に映っていない敵も含めた「実際に一番近い未捕獲prey」までの距離を計算し、前ステップと比較して縮まっていれば search_approach_bonus を与えます。これにより、視界の外にいる敵に向かって正しい方向に歩いている場合にだけ報酬が発生します(ランダムに動いただけでは平均的には縮まらないので、報われません)。

修正3: チーム単位の同時接近ボーナス

既存の「視界内に敵がいる場合の simultaneous_approach_bonus」と同じレジストリパターンを、索敵フェーズ専用に複製しました(_search_approach_registry)。同じサイクル内で search_team_approach_threshold(デフォルト3)人以上が同時にグローバル距離を縮めていれば、それぞれに search_team_approach_bonus を追加で与えます。これが「チーム単位で敵に向かって移動する」ことへの直接的な報酬になります。

学習

学習の推移

今回実装コードによる学習の推移を前回と比較してみました。 報酬はどうでしょう・・・?上がってないことは間違いありません。 エントロピは上がっています。報酬系が上がったことで想定と異なることが増えたという感じでしょうか。 もう少し安定化するまで学習させてみても良いという結果だったかもしれません。

実際の動作

学習後のエージェントの動作について確認してみます。 まず前回確認されたような一カ所に集結をそこまで必死にしなくなったようです。

その上で捕獲後に停滞はせず、一角の敵が少なくなった後、チーム全体で北上するような動作が確認出来ました。

これは・・・期待通りでは。 捕獲数が思ったより伸びませんでしたが、捕獲後に停滞するという挙動ではなくなったと考えます。

result of team collaboration



総括

今回トライアルの結果をまとめていきます。

今回の改修で効いたポイント

  1. 停滞ペナルティの抜け穴をなくした

    • 旧:4人ちょうど集まっていると、止まってもペナルティがかからなかった
    • 新:視界内に敵がいないときは、動いていなければ常にペナルティ
      → 捕獲後に一角に居座る局所解が弱まり、チームで次の敵を探しに行く動きが出るようになった
  2. グローバル情報を使った「索敵中の接近報酬」を追加

    • 局所観測だけでは「敵の方向」が分からず、探索のメリットが小さかった
    • 環境の実座標から最寄りの未捕獲preyまでの距離を計算し、縮まれば個別に報酬
    • さらに、チームの一定人数以上が同時に距離を縮めたら追加ボーナス
      → 視界外の敵に向かってチームで移動する行動が報酬づけされ、実際に「一角クリア後に北上」などの探索行動が確認された
  3. エントロピーが上がり、局所解から抜け出しやすくなった

    • 報酬設計の変更で停滞が不利になり、探索的な行動が相対的に有利に
    • 学習曲線ではエントロピー上昇 → 局所解からの脱出が進んでいる

残っている課題

  • 報酬はまだ伸びきっていない

    • 停滞は減ったが、捕獲数・総報酬は大きく向上していない
    • 報酬スケールや学習ステップの調整が必要な可能性
  • 探索空間が広い局面での「損切り」設計

    • 遠くの敵を探しに行くほどすぐには報酬が得られない
    • 索敵中の移動そのものに小さな常時ボーナスを追加するなど、探索インセンティブの強化余地あり
  • 報酬設計がやや複雑

    • 個別接近・チーム接近・停滞ペナルティなどシェイピングが増えた
    • 今後はどの報酬がどの行動に効いているかを分離評価し、不要な要素を削るか重み調整が必要になる可能性

結論

期待通りではありました。ただ成績に結びつかなかったという感じでしょうか。

  • 「捕獲後に一角に居座る」という局所解が弱まり、チームで次の敵を探しに行く行動が学習されるようになった
  • 報酬はまだ伸びていないが、チーム連携の質と探索性は改善しており、今後は報酬バランスと学習ステップの調整でさらなる性能向上が見込める

ニューラルネットワークの破滅的忘却の原因と対策について

ニューラルネットワークについて学習している初期、ファインチューニングしたら学習した問題は正解するようになったけど、もともとできていた問題が解けなくなるということがよくありました。

原因は破滅的忘却(catastrophic forgetting)と呼ばれるものです。

本日テーマ:

破滅的忘却について説明した上で、対策法について挙げてみる

概要

破滅的忘却 とは、ニューラルネットワークが新しいタスクやデータを学習する際に、以前に学習した知識を急激に失ってしまう現象のことです。

経緯

破滅的忘却はLLMが出てくる以前に既に問題と考えられてきました。 破滅的忘却が問題視されてきた経緯を、年代順に整理すると以下のようになります。

1980年代末〜1990年代初頭:現象の指摘と命名

  • 1989年:McCloskey & Cohen

    • 連想記憶モデル(connectionist models)において、
      新しいタスクを順番に学習すると、以前のタスクの知識が急激に失われることを示すMHTECHIN Technologies
    • この現象を catastrophic interference(破滅的干渉)と呼び、
      ニューラルネットワークが人間と異なり「連続学習」に弱いことを明確にしました。
  • 1990年:Ratcliff

    • 同様の現象を別の観点から確認し、
      小さな逐次更新でも既存知識が大きく破壊されることを示すMHTECHIN Technologies
    • ここで「ニューラルネットは逐次学習に弱い」という認識が広まり、
      破滅的忘却が理論的・実験的に確立された問題として扱われるようになります。

1990年代〜2000年代:理論的・限定的な関心

  • この時期は、ニューラルネットワーク自体がまだ「深いモデル」ではなく、
    実用規模のモデルも少なかったため、破滅的忘却は理論的な興味として扱われることが多かったとされていますWikipedia
  • 連続学習(continual learning)や lifelong learning の文脈で研究は続きますが、
    実用上の大きなボトルネックとして強く意識されるのは、もう少し後になります。

2010年代:ディープラーニングの台頭と再注目

  • 2013年:Goodfellowら

    • An empirical investigation of catastrophic forgetting in gradient-based neural networks(arXiv:1312.6211)で、
      勾配ベースのニューラルネットワークにおける破滅的忘却を体系的に測定・分析Schmidhuber (2015)
    • ディープラーニングが広く使われるようになり、
      「大規模モデルでも同じ問題が起きる」ことが明確になります。
  • 2017年:Kirkpatrickら(DeepMind)

    • Overcoming catastrophic forgetting in neural networks(PNAS)で、
      EWC(Elastic Weight Consolidation) を提案PNAS
    • 重要パラメータを「固める」ことで、新しいタスク学習時に既存タスクの性能を保つ手法を示し、破滅的忘却が実用上の大きな課題として再び注目されます。

2010年代末〜2020年代:LLM・大規模基盤モデル時代の「現実の問題」へ

  • LLM・大規模基盤モデルの普及

    • GPT系・BERT系など、何百万ドル規模の計算コストで学習されたモデルが登場rewire.it
    • モデルを「毎回ゼロから学習し直す」ことが現実的でなくなり、追加学習(ファインチューニング)での破滅的忘却が、サービス運用上の深刻な問題として浮上します。
  • LoRAなどのパラメータ効率手法でも忘却が残る

    • 2020年代の研究では、
      • フルファインチューニングで以前のタスクのF1スコアが89%低下
      • LoRAのような軽量手法でも71%の忘却率が観測されるケースがある
        などが報告されていますrewire.it
    • 「モデル規模が大きいほど忘却が深刻になる」という逆説的な結果も示され、破滅的忘却がLLM時代の中心的な課題として再認識されています。

LLMにおける破滅的忘却の特徴

LLM(大規模言語モデル)でも、以下のような形で破滅的忘却が問題になります。

  • 追加学習(ファインチューニング)の影響
    • あるドメイン(例:法律文書)に特化してファインチューニングすると、
      一般常識や他のドメインの知識が弱くなることがあります。
  • 指示追従(instruction tuning)の影響
    • 「ユーザーの指示に従う」ように学習すると、
      元の言語モデルが持っていた知識の一部が曖昧になることがあります。
  • モデル更新時の挙動
    • 新しいバージョンで追加学習を行うと、以前のバージョンでできていたタスクができなくなる、
      あるいは出力のスタイルが大きく変わることがあります。

なぜ起こるのか

破滅的忘却が起きる仕組みは、ニューラルネットワークの学習が「パラメータの上書き」で行われることにあります。
順を追って説明します。

1. ニューラルネットワークの学習は「地形の形を変える」こと

ニューラルネットワークは、たくさんの「重み(パラメータ)」を持っています。
学習とは、「誤差が小さくなる方向に、少しずつ重みを動かす」 ことです。

  • 入力 → ネットワーク(重み) → 出力
  • 出力と正解の差(誤差)を小さくするように、重みを調整

これを勾配降下法と呼びますが、イメージとしては:

広い地形(誤差の地形)の上で、「今立っている場所の傾き」を見て、
少しずつ「下り坂」方向に歩いていく

という感じです。

2. タスクAを学ぶ=地形の「Aの谷」に落ちる

タスクA(例:数字「0」の認識)を学習すると、
ネットワークの重みは「Aの谷」と呼ばれる誤差が小さい場所に落ち着きます。

  • 入力が「0」のとき → 出力が「0」と正しく出る
  • そのときの重みの組み合わせが「Aの谷」の底

この状態では、タスクAに対してはほぼ完璧です。

3. タスクBを学ぶ=地形全体が「Bの谷」に合わせて変形する

次に、タスクB(例:数字「1」の認識)を学習すると、
ネットワークは同じ重みを使って、今度は「Bの谷」を目指して動き出します。

  • 入力が「1」のとき → 出力が「1」と正しく出るように重みを動かす
  • その過程で、地形全体(誤差の形)がBに合わせて変形する

ここで重要なのは:

ネットワークは「Aの谷を保ったままBの谷を作る」のではなく、
同じ地形をB用に作り替えるだけ

ということです。

4. 破滅的忘却の核心:Aの谷が「消える」か「別の場所に移る」

タスクBを学習するとき、勾配降下法は 「Bの誤差を減らす方向」 にしか重みを動かしません。
Aの誤差は考慮されません。

その結果:

  • Aの谷があった場所は、Bの学習によって別の形(高い丘や別の谷)に変形される
  • あるいは、Aの谷そのものが地形上から消えてしまう

ということが起きます。

これが破滅的忘却です。

新しいタスクBを学ぶために地形を変えたら、
以前のタスクAに最適だった場所(Aの谷)が壊れてしまった

5. なぜ「少しずつ」学習しても壊れるのか

「少しずつ動かしているのに、なぜ急に忘れるの?」と感じるかもしれませんが、
ニューラルネットワークの重みは互いに強く結びついているためです。

  • 1つの重みを少し動かすと、別の入力パターンに対する挙動も連動して変わる
  • 特に深いネットワークでは、多数の重みが協調して1つのパターンを表現している

そのため:

  • タスクBの学習で重みを少し動かす
  • それがタスクAに重要な重みの組み合わせを壊す
  • 結果として、Aの性能が急激に落ちる

という「連鎖的な破壊」が起きます。

6. LLM(大規模言語モデル)での具体例

LLMでも同じ仕組みで破滅的忘却が起きます。

  • まず、大量の一般テキストで学習 → 「一般常識の谷」に落ち着く
  • 次に、法律文書だけを追加学習(ファインチューニング)
    • 法律文書の誤差を減らす方向に重みを動かす
    • その過程で、一般テキストに最適だった重みの組み合わせが壊れる
  • 結果:法律文書には強くなるが、一般常識や他ドメインの知識が弱くなる

対策

破滅的忘却の仕組みは、

新しいタスクBを学ぶために勾配降下法で地形(誤差の地形)を変えると、
古いタスクAに最適だった「Aの谷」が壊れてしまう

というものでした。
対策は、この「Aの谷を壊さないように地形を変える」 ための工夫です。

1. 再学習(Rehearsal)/リハーサルデータ

仕組みとの関係

  • 破滅的忘却:Bだけを学習 → 地形がB用にだけ変形 → Aの谷が壊れる
  • 再学習:AとBを同時に学習 → 地形が「AとBの両方に良い場所」を探す

具体的な方法

  • 過去のタスクAのデータの一部を保存しておき、
    新しいタスクBを学ぶときにAのデータも混ぜて一緒に学習する
  • あるいは、Aのデータを生成する**生成モデル(GANなど)**で擬似的に再現する

なぜ効くか

  • 勾配降下法が「Bの誤差だけ」を見て動くのではなく、
    Aの誤差も同時に見て動くため、
    Aの谷を完全に壊す方向には進みにくくなります。
  • 地形が「AとBの両方に良い谷」を探すように変形されるイメージです。

2. 正則化(Regularization)/EWC など

仕組みとの関係

  • 破滅的忘却:B学習で重みを自由に動かす → Aに重要な重みも動いて壊れる
  • 正則化:Aに重要な重みを「固める」 → B学習でも動かしにくくする

代表例:EWC(Elastic Weight Consolidation)

  • タスクAを学習した後、
    • どの重みがAにとって重要かを推定(Fisher情報など)
    • 重要な重みほど「動かしたら大きなペナルティ」をかける
  • タスクBを学習するとき、
    • 勾配降下法で重みを動かすが、重要重みを大きく動かすと損失が増える
    • 結果として、Aの谷を壊さない範囲でBの谷を探す

なぜ効くか

  • 地形のうち「Aの谷の形を決めている部分」をバネで固定するイメージ
  • B学習で地形を変形させても、Aの谷の底の形はあまり変わらない
  • つまり、Aの谷を壊さずにBの谷を作ることができる

3. アダプタ/LoRA など(一部パラメータだけ更新)

仕組みとの関係

  • 破滅的忘却:全パラメータをB用に動かす → Aの谷全体が変形
  • アダプタ:一部のパラメータだけをB用に追加 → Aの地形はほぼそのまま

代表例:LoRA(Low-Rank Adaptation)

  • 既存の重み行列 WW に、小さな行列 A,BA, B を追加して
    W=W+ABW' = W + AB のようにする
  • 学習時は、元の WW は固定し、追加の A,BA, B だけを更新する

なぜ効くか

  • Aの谷を作っているのは主に元の WW
  • B学習では A,BA, B だけを動かすので、Aの地形はほとんど変わらない
  • Bの谷は「Aの地形の上にちょっとした凹凸を追加する」イメージ
  • 結果として、Aの性能を保ったままBに適応できる

4. マルチタスク学習(同時学習)

仕組みとの関係

  • 破滅的忘却:A→Bと順番に学ぶと、B学習でAの谷が壊れる
  • マルチタスク:AとBを最初から同時に学ぶ → 地形が「AとBの両方に良い谷」に落ちる

具体的な方法

  • 学習データにAとBの両方を混ぜて、最初からマルチタスクで学習する
  • あるいは、追加タスクBを学ぶときも、Aのデータを混ぜ続ける

なぜ効くか

  • 勾配降下法が常にAとBの両方の誤差を見て地形を変形する
  • そのため、地形は「Aだけ」「Bだけ」に特化せず、
    両方にバランスよく良い谷を探す
  • 順番学習で起きる「Aの谷を壊してBの谷を作る」という動きがそもそも起きない

5. タスクごとの専用ヘッド/マスク

仕組みとの関係

  • 破滅的忘却:同じネットワークをAとBで共用 → 重みが干渉
  • 専用ヘッド:タスクごとに出力層(ヘッド)を分ける → 干渉を減らす

具体的な方法

  • 共通の「特徴抽出部分」は共有し、
    • タスクA用の出力層
    • タスクB用の出力層
      を別々に持つ
  • Bを学ぶときは、B用のヘッドだけを更新し、A用のヘッドは固定する

なぜ効くか

  • Aの谷は「共通部分+Aヘッド」で決まり、
    Bの谷は「共通部分+Bヘッド」で決まる
  • B学習でBヘッドだけを動かせば、共通部分の地形はあまり変わらない
  • 結果として、Aの性能を保ちやすい

総括

破滅的忘却の根本的な原因

  • ニューラルネットワークは同じ重み(パラメータ)を複数のタスクで共有している
  • 新しいタスクBを学ぶとき、勾配降下法で重みをB用に動かすと、
    古いタスクAに最適だった重みの組み合わせが上書きされて壊れる
  • その結果、Aの性能が急激に落ちる=破滅的忘却

気をつけるべきこと

  • 順番学習(A→B)は危険:Bだけを学ぶとAの知識が壊れやすい
  • Aのデータも混ぜて再学習する(Rehearsal)
  • Aに重要な重みを動かしにくくする(EWCなどの正則化)
  • 一部パラメータだけを更新する(LoRAなどのアダプタ)
  • 可能なら最初からAとBを同時に学ぶ(マルチタスク学習)
  • LLM時代は「毎回ゼロから学習し直せない」ので、
    追加学習時の破滅的忘却が運用上の深刻な問題になる

要するに、

「Aの谷を壊さずにBの谷を作る」 という意識で学習設計をしないと、新しいことを学ぶたびに古いことを忘れてしまう

というのが破滅的忘却の本質です。

 

グラフ理論(1): NetwokXのイントロ

グラフ理論はネットワーク解析や、グラフニューラルネットワークを理解する上で必要な知識です。

そしてグラフ理論をPython上で実装する際にスタンダードとなっているライブラリがNetworkXです。

本日テーマ;

NetworkXについて紹介し、何が出来るかイメージできるようにする

NetworkXとは

NetworkX は、グラフ(ネットワーク)構造の作成・操作・解析・可視化を行うための Python パッケージです。

NetworkX の主な特徴

  • グラフ構造のモデル化
    現実世界の「つながり」をグラフとして表現できます。

    • 例:SNSのフォロー関係、道路網、分子構造、Webページのリンク関係など
  • さまざまなグラフ型をサポート

    • Graph:無向グラフ
    • DiGraph:有向グラフ
    • MultiGraph:多重辺を持つ無向グラフ
    • MultiDiGraph:多重辺を持つ有向グラフ
  • 豊富なアルゴリズム

    • 最短経路探索
    • 中心性(ページランク、次数中心性など)
    • 連結成分、コミュニティ検出
    • マッチング、フロー、巡回問題 など
  • 可視化機能

    • matplotlib と連携してグラフを描画できます(ノード・エッジの色・サイズなどを調整可能)。
  • データの入出力

    • グラフを JSON、GML、GraphML、Pajek 形式などで読み書きできます。

簡単な使用例

import networkx as nx

# 無向グラフを作成
G = nx.Graph()

# ノードとエッジを追加
G.add_edge("A", "B", weight=4)
G.add_edge("B", "D", weight=2)
G.add_edge("A", "C", weight=3)
G.add_edge("C", "D", weight=4)

# A から D への最短経路を計算
path = nx.shortest_path(G, "A", "D", weight="weight")
print(path)  # ['A', 'B', 'D']

インストール方法

pip install networkx

公式情報

ライブラリの開発について

NetworkX の開発主体開発背景について、公式ドキュメントや関連資料に基づいて整理します。

開発主体

  • オリジナル開発者

    • Aric A. Hagberg
    • Daniel A. Schult
    • Pieter J. Swart

    の 3 名によって 2002 年頃から設計・実装が始められましたNetworkX - WikipediaNetworkX History

  • 所属・支援組織

    • 開発は主に ロスアラモス国立研究所(Los Alamos National Laboratory) において進められました。
    • 米国エネルギー省(U.S. Department of Energy)の National Nuclear Security Administration によって支援されていますNetworkX - Wikipedia
  • 現在の開発体制

    • 現在は、上記オリジナル開発者に加え、多数のコントリビュータからなるオープンソース・コミュニティによって開発・保守が続けられていますNetworkX About Us

開発背景・動機

NetworkX が作られた背景としては、主に次のようなニーズがありました。

  1. 複雑ネットワークの研究ツールとして

    • 社会ネットワーク、生物学的ネットワーク、インフラネットワークなど、現実世界の「つながり」をグラフとしてモデル化し、その構造・ダイナミクス・機能を解析するためのツールが必要でした。
  2. 疫病伝播モデリングへの応用

    • 特に、感染症の伝播(epidemic spread) をモデル化し、その制御戦略を評価するために、ネットワーク構造を扱うライブラリが求められていましたNetworkX Fundamentals
  3. Python でのグラフ表現の簡便化

    • Guido van Rossum による 1998 年のエッセイ「Python Patterns - Implementing Graphs」に触発され、Python でグラフを直感的に扱えるようにすることを目指して設計されましたNetworkX - Wikipedia
  4. 科学計算コミュニティとの連携

    • 2004 年の SciPy カンファレンスで初めて公開され、Python の科学計算コミュニティ(NumPy, SciPy, matplotlib など)と連携しやすい形で設計されていますNetworkX Fundamentals

より詳しい経緯や技術的背景については、Hagberg らによる論文
“Exploring network structure, dynamics, and function using NetworkX” (SciPy 2008) も参考になりますExploring network structure, dynamics, and function

出来ること

NetworkX でできることを、大きく次のカテゴリに分けてご説明します。

1. グラフの作成・操作

  • さまざまなグラフ型の作成

    • 無向グラフ(Graph
    • 有向グラフ(DiGraph
    • 多重辺を持つ無向・有向グラフ(MultiGraph, MultiDiGraph
  • ノード・エッジの追加・削除・更新

    • ノードに属性(ラベル、重み、カテゴリなど)を付与
    • エッジに重みや任意の属性を付与
  • 既存グラフの操作

    • 部分グラフの抽出
    • グラフの結合・分割
    • サブグラフ、補グラフの生成
  • 代表的なグラフの生成

    • 完全グラフ、格子グラフ、ランダムグラフ(Erdős–Rényi モデルなど)
    • スモールワールドネットワーク、スケールフリーネットワーク(Barabási–Albert モデルなど)

2. アルゴリズムと解析(ネットワーク分析)

NetworkX は、ネットワーク構造・ダイナミクス・機能を解析するための多数のアルゴリズムを提供していますNetworkX Documentation

  • 経路・探索

    • 最短経路(Dijkstra, Bellman–Ford, A* など)
    • 全ノード間の最短経路長
    • 深さ優先探索(DFS)、幅優先探索(BFS)
  • 連結性・成分

    • 連結成分の検出
    • 強連結成分(有向グラフ)
    • 橋(bridge)、関節点(articulation point)の検出
  • 中心性(Centrality)

    • 次数中心性(degree centrality)
    • 近接中心性(closeness centrality)
    • 媒介中心性(betweenness centrality)
    • 固有ベクトル中心性、ページランク(PageRank)
  • コミュニティ検出・クラスタリング

    • クラスタ係数(clustering coefficient)
    • コミュニティ検出アルゴリズム(例:Louvain 法、Girvan–Newman 法など)
  • マッチング・フロー・巡回

    • 最大マッチング
    • 最大フロー/最小カット
    • オイラー路・ハミルトン路の判定
  • グラフの性質

    • 直径、半径、平均経路長
    • 次数分布、スモールワールド性の評価

3. 可視化

  • matplotlib との連携による描画

    • nx.draw() でグラフを描画
    • ノードの色・サイズ・ラベル、エッジの色・太さなどをカスタマイズ可能
  • レイアウトアルゴリズム

    • スプリングレイアウト(spring_layout
    • 円形レイアウト(circular_layout
    • ランダムレイアウト、スペクトルレイアウトなど
  • 外部ライブラリとの連携

    • PyVis, Plotly などと組み合わせてインタラクティブな可視化も可能

4. データ入出力(I/O)

  • 標準フォーマット

    • GraphML, GML, Pajek, Graphviz (DOT), JSON などで読み書き可能
  • 独自データからの読み込み

    • CSV、エッジリスト、隣接行列などからグラフを構築
  • 大規模データ対応

    • 大規模な非標準データセットからの読み込み・処理もサポートされていますNetworkX Documentation

5. パフォーマンス・拡張性

  • 外部ライブラリとの連携

    • NumPy / SciPy による行列演算との連携
    • C/C++/FORTRAN コードとのインターフェースもサポートし、アルゴリズムの高速化が可能NetworkX Documentation
  • 大規模グラフ対応

    • メモリ内で扱える範囲であれば、数十万〜数百万ノード規模のグラフも扱えます(ただし、純 Python 実装のため大規模グラフでは速度面で限界がある場合もあります)。

6. 応用分野の例

  • ソーシャルネットワーク分析(フォロー関係、コミュニティ構造)
  • 交通・物流ネットワーク(最短経路、フロー最適化)
  • 生物ネットワーク(タンパク質相互作用、神経回路)
  • Web リンク構造(ページランク、ハブとオーソリティ)
  • 知識グラフ・推薦システム(エンティティ間の関係分析)

実験

最短経路分析の例題として、次のような問題設定を考えてみましょう。

問題設定

テーマ:

都市間の道路ネットワークにおける最短経路の探索

グラフの構造

  • ノード:主要都市(例:東京、横浜、名古屋、大阪、京都、神戸)
  • エッジ:都市間を結ぶ道路
  • エッジの重み:各道路の所要時間(分) または距離(km)

想定する道路ネットワーク

出発都市 到着都市 所要時間(分)
東京 横浜 30
東京 名古屋 180
横浜 名古屋 150
名古屋 大阪 120
大阪 京都 30
大阪 神戸 20
京都 神戸 25

※上記はあくまで例です。実際の所要時間とは異なります。

解きたい問題

  • 出発地:東京
  • 目的地:神戸

としたとき、

  1. 最短経路(所要時間が最小となる経路) はどの都市を経由するか?
  2. そのときの合計所要時間は何分か?

Pythonコード

前回設定した「都市間道路ネットワークの最短経路」を解く Python コードは以下の通りです。

コード全体

import networkx as nx

# 1. 無向グラフを作成(道路は双方向通行と仮定)
G = nx.Graph()

# 2. ノード(都市)とエッジ(道路)を追加
edges_with_weight = [
    ("東京", "横浜", 30),
    ("東京", "名古屋", 180),
    ("横浜", "名古屋", 150),
    ("名古屋", "大阪", 120),
    ("大阪", "京都", 30),
    ("大阪", "神戸", 20),
    ("京都", "神戸", 25),
]

for u, v, w in edges_with_weight:
    G.add_edge(u, v, weight=w)

# 3. 出発地と目的地を設定
source = "東京"
target = "神戸"

# 4. 最短経路を計算
path = nx.shortest_path(G, source=source, target=target, weight="weight")
total_time = nx.shortest_path_length(G, source=source, target=target, weight="weight")

# 5. 結果を表示
print(f"最短経路: {' → '.join(path)}")
print(f"合計所要時間: {total_time} 分")

実行結果

最短経路: 東京 → 名古屋 → 大阪 → 神戸
合計所要時間: 320 分

※実際の計算結果は、上記の重み設定に基づいて NetworkX が自動的に算出します。

コードのポイント

  • nx.Graph()無向グラフを作成しています(道路が双方向通行のため)。
  • add_edge(u, v, weight=w) で、各道路の所要時間(重み)を設定しています。
  • nx.shortest_path(..., weight="weight") で最短経路(都市の列)を取得。
  • nx.shortest_path_length(...) で最短経路の合計重み(所要時間)を取得。

総括

本日の主張:NetworkX は「グラフ理論を Python で手軽に扱うための標準ツール」

  • グラフ理論は、ネットワーク解析やグラフニューラルネットワーク(GNN)の基礎となる重要な分野です。
  • NetworkX は、そのグラフ理論を Python 上で実装する際の事実上の標準ライブラリです。
  • 主な役割は次の 3 つです:
      1. グラフの作成・操作(無向・有向・多重辺、ランダムグラフ生成など)
      2. 豊富なアルゴリズム(最短経路、中心性、コミュニティ検出など)
      3. 可視化と入出力(matplotlib 連携、標準フォーマットでの読み書き)
  • 開発はロスアラモス国立研究所の研究者らによって始められ、現在はオープンソースコミュニティによって維持されています。
  • 都市間道路ネットワークの最短経路分析の例を通じて、「現実世界のつながりをグラフとしてモデル化し、最短経路を計算・可視化する」という一連の流れを、短いコードで体験できることが確認できました。

以上より、NetworkX を使うことで、グラフ理論の概念を Python コードに落とし込み、実データに適用して結果を可視化するという一連の作業を、比較的簡単に行えることがイメージできると思います。

 

 

 

 

小田原征伐後の秀吉のとった政権安定化の施策

1590年に秀吉は北条征伐を行いました。 北条が降伏した段階で、奥州の大名は秀吉の帰順しており、実質秀吉に抵抗する勢力はなくなりました。 小田原征伐の後に秀吉は統治体制の安定化のために施策を行いました。

本日テーマ:

小田原征伐後に秀吉が行った施策とはどのようなものだったかについて説明してみる

概要

小田原征伐(1590年)は、豊臣秀吉による天下統一の最終段階にあたる大規模な軍事行動でした。この戦いで北条氏を滅ぼしたあと、秀吉は以下のようなことを行っています。 少し駆け足で実際の主だつ施策について列挙してみます。

1. 関東・奥羽の大名配置と支配体制の確立

  • 徳川家康を関東へ移封
    小田原征伐の論功行賞として、徳川家康を駿河・遠江・三河などから関東へ移しました。家康は江戸城を本拠とし、関東240万石を領有することになります。これにより、家康の勢力を中央から離れた関東に封じ込める狙いがあったとされています。

  • 伊達政宗らの服属と奥羽仕置
    伊達政宗は小田原に参陣して秀吉に服属し、本領を安堵されました。一方で、奥羽では「奥州仕置」と呼ばれる検地・知行割・一揆鎮圧などを行い、秀吉政権の支配を奥羽一帯にまで徹底させました。

2. 全国統一の完成と「惣無事令」の徹底

  • 九州平定(1587年)に続き、小田原征伐で東国を平定したことで、秀吉の天下統一はほぼ完成しました。
  • 以後、私的な大名間の戦争を禁じる「惣無事令」を全国に徹底し、大名間の争いは秀吉の裁定で解決する体制を固めました。

3. 朝鮮出兵(文禄・慶長の役)の準備と実行

  • 小田原征伐の翌年(1591年)には、関白職を甥の豊臣秀次に譲り、自らは「太閤」として軍事的・外交的な活動に専念する体制を整えました。
  • 1592年からは、明(中国)を目指す「唐入り」構想のもと、朝鮮出兵(文禄の役)を開始し、日本軍を朝鮮半島へ派遣しました。
  • 1597年には再度出兵(慶長の役)を行い、秀吉の晩年まで朝鮮半島での戦いが続きました。

4. 京都・大坂の都市整備と文化政策

  • 聚楽第や大坂城を中心に、京都・大坂の都市整備を進め、城下町の発展を図りました。
  • 茶の湯を奨励し、千利休を重用するなど、茶道・能楽・建築などの文化を保護・育成しました(いわゆる「桃山文化」の形成に大きく寄与)。

5. 検地・刀狩・身分統制などの政策

  • 全国的な「太閤検地」を推進し、石高制に基づく統一的な年貢・軍役体系を整えました。
  • 「刀狩令」を出して農民から武器を没収し、兵農分離を進めました。
  • 身分統制令(1591年)により、武士・町人・百姓の身分移動を制限し、身分秩序を固定化しました。

6. 秀吉の晩年と後継体制の構築

  • 嫡男・豊臣秀頼の誕生(1593年)後、甥の豊臣秀次を謀反の疑いで切腹させ(1595年)、秀頼を後継者とする体制を固めようとしました。
  • 1598年に秀吉が死去するまで、朝鮮出兵の継続と豊臣政権の安定化に力を注ぎましたが、その死後、豊臣政権は急速に不安定化し、関ヶ原の戦い(1600年)へとつながっていきます。

このように、小田原征伐後の秀吉は、天下統一を完成させたうえで、国内統治の整備(検地・刀狩・身分統制など)と対外進出(朝鮮出兵)を同時に進め、豊臣政権の基盤を固めつつ、晩年には後継問題にも直面していました。 こうしてみると変化は非常に大きいと感じます。

変化の引き金となったことは1585~1590年に向けて四国、九州、関東、奥州といった、本来秀吉の統治下でなかった地域を急ピッチで統一したことだと思います。 本日はこの統治事業について掘り下げてみようと思います。

統一と反動

秀吉の急ピッチな天下統一に対しては、各地でさまざまな「反動」や抵抗が起こっています。主なものは以下のとおりです。

1. 一揆・国人衆の抵抗(奥州仕置・検地への反発)

  • 奥州仕置後、検地や知行割の変更により、それまで地元で勢力を持っていた国人衆や地侍の立場が大きく揺らぎました。
  • その結果、奥羽各地で一揆が頻発し、秀吉政権の支配に抵抗する動きが続きました。
  • 検地そのものに対しても、年貢負担の増加や土地の再編成を嫌う農民・在地勢力の抵抗が各地で見られました。

2. 大名の不満・疑心暗鬼(特に徳川家康との関係)

  • 徳川家康を関東へ移封したことは、形式的には「論功行賞」ですが、家康にとっては長年築いた東海の基盤を失うことでもありました。
  • 秀吉は家康の力を関東に「封じ込める」狙いがあったとされ、家康側から見れば、天下統一の過程で常に警戒され続けた立場に置かれていたとも言えます。
  • このような秀吉の強力な中央集権化は、多くの大名に「いつ取り潰されるか分からない」という不安を抱かせ、豊臣政権への忠誠と同時に、潜在的な不満や警戒心を生みました。

3. 文化・宗教政策への反発(キリシタン禁制など)

  • 秀吉は九州平定後に「バテレン追放令」(1587年)を出し、キリスト教の布教を制限しました。
  • これは、キリスト教が大名や民衆を結びつけ、秀吉の統治秩序とは別の権威を形成しうることを警戒したためとされています。
  • この政策は、キリシタン大名や信者にとっては大きな反動・弾圧であり、のちの江戸幕府によるキリスト教禁制の先駆けともなりました。

4. 刀狩令に対する潜在的な不満

  • 刀狩令(1588年)は、農民から武器を没収し、兵農分離を進める政策でした。
  • 直接的な大規模一揆として表面化した事例は少ないものの、武器を奪われた農民や在地勢力の間には、潜在的な不満や抵抗意識が残りました。
  • この政策は、秀吉政権が「農民は農民、武士は武士」という身分秩序を強制するものであり、戦国時代的な「武装した在地勢力」の力を削ぐ狙いがありました。

顕在化した例

秀吉の急ピッチな天下統一、とくに「奥州仕置」や「太閤検地」に対して、農民や在地勢力の不満・反抗が顕在化した出来事はいくつも起こっています。代表的なものを挙げると、以下のようになります。

1. 葛西・大崎一揆(1590–1591年)

  • 奥州仕置後、葛西氏・大崎氏の旧領(現在の宮城県北部から岩手県南部)で、検地や知行割の変更に不満を持つ在地の国人・農民が蜂起しました。
  • 秀吉はこれを「奥州再仕置」として鎮圧し、伊達政宗らに出兵させて一揆を平定しました。
  • この一揆は、秀吉政権による急激な支配体制の転換に対する、在地勢力の典型的な反発として知られています。

2. 和賀・稗貫一揆(1590–1591年)

  • 現在の岩手県中部(和賀郡・稗貫郡)で、旧領主の和賀氏・稗貫氏を支持する在地の国人・農民が蜂起しました。
  • 奥州仕置で所領を失った旧領主たちと、その家臣・農民が結びつき、秀吉政権の支配に抵抗しました。
  • こちらも伊達政宗らによって鎮圧され、秀吉の奥州支配がさらに強化されました。

3. 九戸政実の乱(1591年)

  • 現在の岩手県九戸郡周辺で、九戸政実が中心となって大規模な反乱を起こしました。
  • 奥州仕置に不満を持つ在地の国人・農民が多数参加し、秀吉政権の支配に正面から対抗する動きとなりました。
  • 秀吉は豊臣秀次を総大将とする大軍を派遣し、九戸城を攻め落として政実を滅ぼしました。この乱の鎮圧をもって、奥州仕置はほぼ完了したとされています。

4. 検地に対する各地の一揆・抵抗

  • 太閤検地は、石高制に基づく統一的な年貢体系を全国に導入するものでしたが、これまでの土地慣行や年貢負担を大きく変えるものでした。
  • そのため、各地で検地に反対する農民・在地勢力の一揆が発生しました。
    • 例:九州平定後の肥後国で起こった国人一揆(肥後国人一揆)なども、検地や知行割の変更に不満を持つ在地勢力の反乱として知られています。
  • 検地帳の改ざんや、検地役人の追い払い、年貢減免要求など、さまざまな形で抵抗が行われました。

秀吉の打ち手

秀吉は中央から遠い地方での反乱にはある程度手を打っていたと考えられます。 その打ち手は九州や奥州において手厚く、治世当初の反動を十分に抑えることが出来ました。

九州仕置き

九州の反対勢力が統一後に反乱を起こす可能性については、加藤清正や立花宗茂などの軍事力に優れた部将の抜擢により対応しました。

1. 九州平定後の大名配置の特徴

  • 九州平定(1587年)後、秀吉は九州の大名配置を大きく組み替えました。
  • 島津氏は本領を安堵されつつも、その勢力を分断・抑制する形で、豊臣系の大名を九州各地に配置しました。
  • 特に、肥後国(現在の熊本県)や筑後国(現在の福岡県南部)など、九州の要衝に、豊臣政権に忠実で軍事力の高い武将を置いています。

2. 加藤清正の肥後入封とその狙い

  • 加藤清正は、小田原征伐後の1591年に肥後北部(約25万石)を与えられ、熊本城を築いて入封しました。
  • 肥後は、もともと国人勢力が強く、島津氏とも近い地域でした。そのため、秀吉はここに清正を置くことで、
    • 島津氏の動向を牽制する
    • 肥後の国人一揆に備える(実際に国人一揆は起きたが、清正があっという間に鎮圧している)
    • 有事には九州全体の抑えとして動員できる という役割を期待していました。
  • 実際、清正は熊本城を堅固な城に築き上げ、軍事拠点としての機能を重視しています。これは「反乱が起きても耐えられる体制」を整える意味合いが強かったと考えられます。

3. 立花宗茂の筑後入封と軍事力の活用

  • 立花宗茂は、もともと大友氏の家臣でしたが、九州平定で戦功を挙げ、筑後柳川13万石を与えられました。
  • 宗茂は「西国無双」と称されるほどの猛将であり、秀吉はその軍事力を高く評価していました。
  • 筑後は、肥後と隣接し、かつ島津氏の影響力も及ぶ地域です。ここに宗茂を置くことで、
    • 肥後・筑後一帯の国人勢力を抑える
    • 島津氏の動きを監視する
    • 九州全体の有事に即応できる軍事拠点とする という役割を担わせました。

4. 肥後国人一揆と清正・宗茂の動員

  • 九州平定直後の1587年、肥後では検地や知行割の変更に不満を持つ国人勢力が大規模な一揆を起こしました(肥後国人一揆)。
  • 秀吉は、この一揆鎮圧のために、加藤清正や立花宗茂らを動員し、徹底的に鎮圧させました。
  • この対応は、
    • 九州平定後も反乱が起こることを想定していた
    • そのためにあらかじめ豊臣系の有力武将を配置していた
    • 実際に反乱が起きたら、彼らを動員して即座に鎮圧する という秀吉の戦略が、九州でも機能していたことを示しています。

奥州仕置き

1. 蒲生氏郷の会津移封と大軍動員能力

  • 小田原征伐後の奥州仕置で、秀吉は蒲生氏郷を伊勢松坂から会津92万石へと大幅に加増・移封しました。
  • 会津は、奥羽の要衝であり、かつて伊達政宗が支配を狙った地域でもあります。ここに氏郷を置いたことは、奥羽の動向を常に監視し、有事には大軍を動員して鎮圧できる体制を整える狙いがあったとされています。
  • 氏郷は、もともと織田信長・豊臣秀吉のもとで戦功を重ねた武将であり、軍事力・統治力ともに高く評価されていました。
  • この配置は、「奥州で反乱が起きた場合、蒲生氏郷が即座に動員して対応できる」という前提があったことを示しています。

2. 伊達政宗の「奥州再仕置」への動員

  • 奥州仕置後、葛西・大崎一揆や和賀・稗貫一揆が起こると、秀吉は伊達政宗に出兵を命じ、一揆の鎮圧に当たらせました。
  • 政宗は、もともと奥州で強い影響力を持つ大名であり、その軍事力と地元の情報網を利用して、反乱勢力を鎮圧する役割を担いました。
  • これは、秀吉が「奥州の反乱は、奥州の有力大名を使って鎮圧する」という方針を持っていたことを示しています。
  • 政宗自身も、小田原参陣が遅れたことなどから秀吉に警戒されていましたが、その軍事力を「反乱鎮圧のための駒」として活用する形で、秀吉政権に組み込まれていきました。

3. 九戸政実の乱での豊臣秀次・蒲生氏郷らの大動員

  • 1591年の九戸政実の乱では、秀吉は豊臣秀次を総大将とする大軍を奥州に派遣しました。
  • この軍には、蒲生氏郷や伊達政宗らも加わり、九戸城を包囲・攻撃して徹底的に鎮圧しました。
  • この規模の動員は、「単なる小規模な一揆」ではなく、「奥州全体の支配を揺るがしかねない大反乱」と秀吉が認識していたことを示します。
  • 秀吉は、反乱が起きるたびに臨時で軍を編成するのではなく、あらかじめ奥州周辺に有力大名を配置し、有事には中央からも大軍を送る体制を整えていました。

4. これらの事例から見える「秀吉の予見」

  • 蒲生氏郷を会津に置いたこと、伊達政宗に反乱鎮圧を命じたこと、九戸政実の乱で豊臣秀次を総大将とする大軍を送ったことなどは、いずれも「奥州で反乱が起きることは想定内」という前提で動いていたことを示しています。
  • 秀吉は、奥州仕置や検地が在地勢力の反発を招くことを十分に理解しており、
    • 平時には有力大名を要衝に配置して抑えと監視を兼ねさせる
    • 反乱が起きたら、その地域の大名と中央軍を組み合わせて迅速に鎮圧する という二段構えの体制を整えていました。

秀吉が発給した公的文章

秀吉自身が九州・奥州の反乱について残した「発言」や「文章」は、完全な形で現存するものは多くありませんが、いくつかの公的文書や命令の形で記録が残っています。代表的なものを挙げると、以下のようになります。

1. 奥州仕置・九戸政実の乱に関する秀吉の文書

(1) 南部信直宛ての朱印状(1590年7月27日付)

  • 九戸政実の乱の背景となる「奥州仕置」に関連して、秀吉は南部信直を南部氏宗家として公認する朱印状を出しています。
  • この文書は、秀吉が南部氏を「近世大名」として組み込み、その支配を認める内容です。
  • これにより、九戸氏は「南部氏の家臣」として位置づけられ、これが九戸政実の反乱の一因になったとされています。

(2) 奥州再仕置軍編成の号令(1591年6月20日)

  • 九戸政実の乱が本格化した際、秀吉は甥の豊臣秀次を総大将とする「奥州再仕置軍」の編成を命じました。
  • この号令は、徳川家康・蒲生氏郷・伊達政宗ら諸将に動員と指揮を指示するもので、秀吉が乱を「天下統一の最終段階として鎮圧すべき対象」と位置づけていたことを示します。
  • 具体的な文言そのものは史料集などに断片的に残っていますが、秀吉の強い意志が読み取れます。

(3) 戦後処理に関する命令

  • 九戸城を落とした後、秀吉の命により蒲生氏郷が城を改修し、南部信直に引き渡したことが記録されています。
  • これは、乱後の奥州支配を確立するための具体的な指示であり、秀吉の「徹底した平定」の姿勢を示すものです。

2. 九州平定・肥後国人一揆に関する秀吉の文書

(1) 九州平定後の大名宛ての朱印状・黒印状

  • 九州平定後、秀吉は島津義久・龍造寺政家・大友義統らに対し、所領安堵や配置変更を命じる朱印状・黒印状を多数発給しています。
  • これらの文書は、九州の大名を豊臣政権の枠組みに組み込むためのもので、反乱を抑え込む意図が込められています。

(2) 肥後国人一揆後の処分に関する命令

  • 肥後国人一揆(1587年)の鎮圧後、秀吉は一揆の首謀者や協力者に対する処分、および新たな領主(加藤清正・小西行長)への知行割を命じる文書を出しています。
  • これらは、反乱の責任を明確にし、再発防止を図るための命令として残されています。

3. 「発言」として残っているものの限界

  • 秀吉自身の「口頭での発言」は、同時代の日記や覚書(『義演准后日記』『多聞院日記』など)に一部引用されることがありますが、多くは後世の編纂物や軍記物に依存しています。
  • 特に九戸政実の乱や肥後国人一揆について、「秀吉がこう言った」という具体的な台詞は、軍記物や後世の編纂史料に頼る部分が多く、一次史料として確実なものは限られています。
  • 一方で、朱印状・黒印状・動員命令などは、公文書として現存しており、秀吉が反乱をどのように位置づけ、どのように対応したかをうかがう重要な手がかりとなっています。

豊臣家衰亡の種

九州、奥州に対して打った手は有効に機能し、急ピッチな統一事業の反動を抑え込むことに成功したと考えています。 一報、それ以外の仕置きで、結果として「秀吉なき後の争乱の種」になったと考えているものもあります。 その中で最大のものは家康の関東転封だと考えています。

1. 家康の関東移封の狙いと実際の効果

  • 秀吉の狙い
    家康を駿河・遠江・三河などから関東へ移し、江戸を本拠とする約240万石の大名としました。これは形式的には「論功行賞」ですが、実際には

    • 家康の勢力を中央から離れた関東に「封じ込める」
    • 家康が長年築いた東海の基盤を切り離す という意図があったとされています。
  • 実際の効果
    しかし、関東240万石は当時日本最大級の石高であり、家康はここで

    • 広大な新田開発
    • 江戸城と城下町の整備
    • 関東一円の支配体制の確立 を進め、むしろ強大な経済力・軍事力を蓄えることになりました。

2. 秀吉死後の「争乱の種」としての側面

(1) 豊臣政権内部での最大の外様大名としての位置づけ

  • 家康は関東移封後も、豊臣政権内で最大の外様大名として存在し続けました。
  • 秀吉の存命中は、その権威によって抑えられていましたが、秀吉の死後は「唯一、豊臣家と対等かそれ以上の力を持つ存在」として浮上します。
  • この「力のバランスの崩れ」が、関ヶ原の戦いへとつながる大きな要因となりました。

(2) 関東という地理的・軍事的優位

  • 関東は、当時の政治的中心である畿内から距離があり、一方で東国・北国へのアクセスに優れていました。
  • 家康は関東を基盤に、東北や関東の諸大名との結びつきを強め、有事には広範な動員が可能な体制を築きました。
  • これは、関ヶ原の戦いで東軍を形成するうえで大きな利点となりました。

(3) 豊臣家との「距離」の確保

  • 家康が関東にいたことで、豊臣家の本拠である大坂・畿内から物理的にも政治的にも距離を置くことができました。
  • 秀吉死後、家康は「五大老筆頭」として中央政権に関与しつつも、必要に応じて関東に戻り、独自の基盤を維持できました。
  • この「距離」が、家康が豊臣家から独立した権力基盤を築くうえで有利に働きました。

3. 秀吉の仕置きが「種」となったメカニズム

  • 秀吉は家康の力を「封じ込める」つもりで関東に移しましたが、結果的には家康に「日本最大の領国と独自の基盤」を与えてしまいました。
  • 秀吉の存命中は、そのカリスマと権威によって家康は従属していましたが、秀吉の死後はその抑えがなくなり、家康が台頭する土台がすでにできていたことになります。
  • つまり、秀吉の仕置きは「短期的には家康を抑える」効果があったものの、「長期的には家康が天下を取るための地盤を整えてしまった」という秀吉が恐らく意図しなかった結果をもたらしました。

総括

秀吉の天下統一後の仕置きは、短期的には非常に成功したが、長期的には豊臣政権の崩壊と新たな争乱の種をまいたという評価が妥当です。

短期的成功(1585〜1590年代)

  • 全国統一の完成
    四国・九州・関東・奥州を急ピッチで平定し、惣無事令で私戦を禁じ、検地・刀狩・身分統制で全国的な統治体制を整えた。

  • 反乱の抑え込み
    奥州仕置後の葛西・大崎一揆、和賀・稗貫一揆、九戸政実の乱、肥後国人一揆などに対し、蒲生氏郷・伊達政宗・加藤清正・立花宗茂らを動員して迅速に鎮圧し、支配を徹底した。

  • 中央集権の強化
    京都・大坂の都市整備、朝鮮出兵を通じて豊臣政権の権威を高め、大名を「豊臣家の家臣」として組み込んだ。

長期的な禍根(秀吉死後〜江戸期)

  • 家康の関東移封が「最大の種」
    家康を関東240万石に封じたことで、日本最大の領国と独自の基盤を与えてしまい、秀吉死後に関ヶ原の戦い・江戸幕府成立へつながる土台を作った。

  • 外様大名の潜在的不満
    島津・伊達らは豊臣政権下で抑え込まれたが、その不満や独立心は幕末の倒幕運動にまで影響した。

  • 在地勢力の弱体化と中央依存
    検地・刀狩・一揆鎮圧で在地勢力を弱め、大名による一元的支配を強めたが、地方の自律性を奪い、中央政権への依存構造を固定化した。

  • 朝鮮出兵の負担と政権の疲弊
    対外戦争は豊臣政権の威信を高めた一方、財政・軍事の負担を増やし、秀吉死後の政権不安定化を招いた。

総評

秀吉の仕置きは、「急ピッチの統一」と「強権的な統治」によって短期的には天下を固めたが、その過程で生まれた不満・緊張・力の偏りが、秀吉の死後に一気に噴出し、豊臣政権の崩壊と新たな争乱(関ヶ原)の原因となったと言えます。
特に家康の関東移封は、秀吉が意図した「封じ込め」とは逆に、家康に天下を取るための地盤を整えてしまったという皮肉な結果をもたらしました。

 

 

 

秀吉再考

秀吉再考

Amazon

 

報酬はどのようにしてロス関数になり学習に用いられるか?

先日強化学習で用いられるアドバンテージについて説明を行いました。 アドバンテージは報酬を用いた行動の良し悪しを、設定した基準からどうだったかということを評価するために用いられます。

強化学習で主流であるニューラルネットワークを用いる強化学習・DQNにおいて、報酬がどのようにしてニューラルネットワークのロス関数に変換されるかについて説明することが、理解する上では重要です。

本日テーマ:

報酬はどのようにしてDQNで学習に用いられるロス関数に変換されているか説明

強化学習におけるロス関数

強化学習における「報酬」と「ロス関数(目的関数)」の関係は、「報酬を最大化する」という目標を、数学的に扱いやすい形(勾配降下など)に落とし込んだものと理解できます。

以下、順を追って説明します。

1. 報酬:エージェントが目指すもの

強化学習では、エージェントは環境と相互作用しながら報酬(reward) を受け取ります。

  • 報酬 rtr_t:時刻 tt でエージェントが得る即時的な評価(例:ゲームのスコア、ロボットの目標達成度など)
  • リターン GtG_t:将来にわたる報酬の総和(割引付き)

    Gt=rt+γrt+1+γ2rt+2+G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots

    ここで γ\gamma は割引率(0 ≤ γ\gamma ≤ 1)です。

強化学習の目標は、このリターン GtG_t の期待値を最大化することです。

2. ロス関数(目的関数):学習アルゴリズムが実際に最適化するもの

一方で、多くの機械学習アルゴリズム(特に深層学習)は、「ロス関数(損失関数)を最小化する」 形で設計されています。
強化学習でも、この枠組みに合わせるために、「報酬の期待値を最大化する」という目標を、ロス関数の形に書き換えます。

(1) 価値ベースの手法(Q学習など)

  • 目的:行動価値関数 Q(s,a)Q(s,a) を、真のリターンに近づける
  • ロス関数の例(TD誤差の二乗):

    L(θ)=E[(r+γmaxaQ(s,a;θ)Q(s,a;θ))2]L(\theta) = \mathbb{E}\left[ \left( r + \gamma \max_{a'} Q(s',a';\theta) - Q(s,a;\theta) \right)^2 \right]

  • ここで、報酬 rr は「目標値(ターゲット)」の一部としてロス関数に組み込まれています。
  • 式における θ\thetaはニューラルネットワークや線形関数などのパラメータ(重み・バイアスなど)の集合を意味します。
  • 上式が意味することは、報酬が大きいほど、Q値も大きくなるように学習されるということになります。

(2) 方策ベースの手法(方策勾配法)

  • 目的:方策 πθ(as)\pi_\theta(a|s) を、報酬の期待値が最大になるように調整する
  • 目的関数(報酬期待値):

    J(θ)=Eτπθ[tγtrt]J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \sum_{t} \gamma^t r_t \right]

  • これを最大化する代わりに、負をとって最小化問題に変換することがあります:

    L(θ)=J(θ)L(\theta) = -J(\theta)

  • 方策勾配法では、勾配 θJ(θ)\nabla_\theta J(\theta) を直接計算し、「報酬が増える方向」にパラメータを更新します。

(3) Actor-Critic 系(PPO, A2C/A3C など)

現在強化学習の主流の手法であるActor-Critic 系に関しては詳細は後述します。 ざっくりと説明すると以下のようになります。

  • Actor(方策)と Critic(価値関数)の両方を持ちます。
  • Critic のロス:
    V(s)V(s) がリターン GtG_t やアドバンテージを正しく予測するように学習(MSEなど)
  • Actor のロス:
    アドバンテージ A(s,a)A(s,a) を重みとして、方策を更新(報酬が増える方向へ)
  • ここでも、報酬はリターンやアドバンテージを通じてロス関数に反映されます。

3. 報酬とロス関数の関係性のまとめ

  1. 報酬は「目的」、ロス関数は「手段」

    • 報酬:エージェントが最大化したいもの(環境からの評価)
    • ロス関数:その最大化を実現するために、アルゴリズムが実際に最適化する数学的な関数
  2. 報酬はロス関数の中に「ターゲット」や「重み」として組み込まれる

    • Q学習:報酬+次の状態のQ値がターゲットとなり、現在のQ値との差をロスとする
    • 方策勾配:報酬和(リターン)やアドバンテージが勾配の重みとなり、方策を更新する
    • Actor-Critic:報酬から計算したアドバンテージが、方策更新の「良さ」の指標となる
  3. ロス関数を最小化すること ≒ 報酬の期待値を最大化すること

    • 多くの場合、報酬の期待値 J(θ)J(\theta) をそのまま最大化する代わりに、
      • J(θ)-J(\theta) を最小化する
      • または、報酬に基づく誤差(TD誤差など)を最小化する
    • という形で、「報酬最大化」を「ロス最小化」に変換しています。

4. 直感的なイメージ

  • 報酬:
    「テストの点数」や「仕事の成果」のような、外から与えられる評価
  • ロス関数:
    「勉強の計画」や「業務改善の指標」のような、内部で使う評価基準
  • 強化学習アルゴリズム:
    報酬という「外部評価」を参考にしながら、ロス関数という「内部指標」を最適化することで、最終的に報酬を最大化する方策を学習する仕組み

Actor-Critic系のロス関数への変換

Actor-Critic系(PPO, A2C/A3Cなど)では、報酬 → リターン/アドバンテージ → ロス関数という流れで学習が行われます。
以下、その変換の方法を段階的に説明します。

1. 報酬からリターン/アドバンテージへの変換

(1) 報酬の収集

エージェントは環境と相互作用し、時刻 tt ごとに報酬 rtr_t を受け取ります。

  • 例:状態 sts_t で行動 ata_t を選び、報酬 rtr_t と次の状態 st+1s_{t+1} を得る

(2) リターン GtG_t の計算

リターン GtG_t は、将来の報酬の総和(割引付き)です。

Gt=rt+γrt+1+γ2rt+2+G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots

実際には、有限長のエピソードや n-step で近似されます。

  • n-step リターンの例:

    Gt(n)=rt+γrt+1++γn1rt+n1+γnV(st+n)G_t^{(n)} = r_t + \gamma r_{t+1} + \dots + \gamma^{n-1} r_{t+n-1} + \gamma^n V(s_{t+n})

    ここで V(st+n)V(s_{t+n}) は Critic が予測する状態価値です。

(3) アドバンテージ A(s,a)A(s,a) の計算

アドバンテージは、「その行動が平均よりどれだけ良いか」 を表す量です。

A(st,at)=GtV(st)A(s_t, a_t) = G_t - V(s_t)

  • V(st)V(s_t):Critic が予測する状態価値
  • GtG_t:実際に得られた(または推定された)リターン
  • したがって、A(st,at)A(s_t, a_t) は「Critic の予測 V(st)V(s_t) からの乖離」として解釈できます。

GAE(Generalized Advantage Estimation)を使う場合は、異なる n-step のアドバンテージを重み付きで組み合わせ、分散とバイアスのトレードオフを調整します。

2. Critic のロス関数:報酬 → リターン → MSE

Critic の役割は、状態価値 Vθ(s)V_\theta(s)リターン GtG_t に近づけることです。

(1) ターゲットの定義

  • ターゲット値:GtG_t(またはその推定値)
  • 予測値:Vθ(st)V_\theta(s_t)

(2) ロス関数(MSE)

Criticは行動の評価者です。Actorの評価を行うため、Criticが予測した価値(状態価値 V(s))と、実際に得られた(または推定された)価値(リターン Gt)の差を最小化するように設計されています。 ということで、Critic のロス関数は、一般に平均二乗誤差(MSE)で定義されます。

Lcritic(θ)=E[(GtVθ(st))2]L_{\text{critic}}(\theta) = \mathbb{E}\left[ \left( G_t - V_\theta(s_t) \right)^2 \right]

  • ここで、報酬は GtG_t を通じてロス関数に反映されます。
  • 学習(勾配降下)では、このロスを最小化するように θ\theta を更新します。

3. Actor のロス関数:報酬 → アドバンテージ → 方策勾配

Actor の役割は、方策 πϕ(as)\pi_\phi(a|s)アドバンテージが正の方向に増えるように更新することです。

(1) 目的関数と勾配

目的関数(報酬期待値)を J(ϕ)J(\phi) とすると、方策勾配定理より:

ϕJ(ϕ)=E[ϕlogπϕ(atst)A(st,at)]\nabla_\phi J(\phi) = \mathbb{E}\left[ \nabla_\phi \log \pi_\phi(a_t|s_t) \cdot A(s_t, a_t) \right]

  • ϕlogπϕ(atst)\nabla_\phi \log \pi_\phi(a_t|s_t):その行動の確率をどれだけ増減させられるかを表す「レバー」の方向
  • A(st,at)A(s_t, a_t):先程のアドバンテージです。その行動がどれだけ良いか(または悪いか)を表す重みとして機能します。

(2) ロス関数としての定式化

多くの実装では、最大化問題を最小化問題に変換するため、負の目的関数をロスとします。

Lactor(ϕ)=E[logπϕ(atst)A(st,at)]L_{\text{actor}}(\phi) = - \mathbb{E}\left[ \log \pi_\phi(a_t|s_t) \cdot A(s_t, a_t) \right]

  • ここで、報酬はアドバンテージ A(st,at)A(s_t, a_t) を通じてロス関数に反映されます。
  • 学習では、このロスを最小化するように ϕ\phi を更新しますが、実質的には
    • A>0A > 0 の行動の確率を上げる方向
    • A<0A < 0 の行動の確率を下げる方向 に更新されます。

(3) PPO における工夫

強化学習の報酬は行動に対して、やや過剰に大きな変動を示す傾向があります。 このため、PPO では、単純なロスではなく、クリップされた目的関数を用いて安定性を高めます。

LactorPPO(ϕ)=E[min(rt(ϕ)At,clip(rt(ϕ),1ϵ,1+ϵ)At)]L_{\text{actor}}^{\text{PPO}}(\phi) = \mathbb{E}\left[ \min\left( r_t(\phi) A_t, \text{clip}(r_t(\phi), 1-\epsilon, 1+\epsilon) A_t \right) \right]

  • rt(ϕ)=πϕ(atst)πϕold(atst)r_t(\phi) = \frac{\pi_\phi(a_t|s_t)}{\pi_{\phi_{\text{old}}}(a_t|s_t)}:確率比
  • ここでも、報酬はアドバンテージ AtA_t を通じてロスに反映されます。

4. 報酬 → ロス関数への変換の流れ

Actor-Critic系では、報酬からロス関数への変換は、おおまかに次の流れで行われます。

  1. 報酬 rtr_t の収集

    • 環境から得られる即時的な評価
  2. リターン GtG_t の計算

    • 報酬の将来和(割引付き)
    • Critic のターゲットとして使用
  3. アドバンテージ A(st,at)A(s_t, a_t) の計算

    • A(st,at)=GtV(st)A(s_t, a_t) = G_t - V(s_t)
    • Actor の更新の「重み」として使用
  4. Critic のロス関数

    • Lcritic=E[(GtVθ(st))2]L_{\text{critic}} = \mathbb{E}[(G_t - V_\theta(s_t))^2]
    • 報酬は GtG_t を通じて反映
  5. Actor のロス関数

    • Lactor=E[logπϕ(atst)A(st,at)]L_{\text{actor}} = -\mathbb{E}[\log \pi_\phi(a_t|s_t) \cdot A(s_t, a_t)](単純な場合)
    • 報酬は A(st,at)A(s_t, a_t) を通じて反映

このように、報酬は直接ロス関数に入るのではなく、リターンやアドバンテージという「中間表現」を経由して、Critic と Actor の学習に組み込まれます。

総括

強化学習、特にニューラルネットワークを用いるDQN(Deep Q-Network)では、報酬は「ターゲットQ値」の一部としてロス関数に組み込まれ、Q関数を正しい方向へ更新する役割を果たします。

1. DQNにおける報酬 → ロス関数への変換

DQNのロス関数(TD誤差の二乗)は、一般に次の形をとります。

L(θ)=E[(r+γmaxaQ(s,a;θ)Q(s,a;θ))2]L(\theta) = \mathbb{E}\left[ \left( r + \gamma \max_{a'} Q(s',a';\theta^-) - Q(s,a;\theta) \right)^2 \right]

ここで:

  • 報酬 rr:環境から得られる即時評価
  • ターゲットQ値r+γmaxaQ(s,a;θ)r + \gamma \max_{a'} Q(s',a';\theta^-)
    • 報酬 rr と、次の状態での最大Q値(割引付き)を足し合わせたもの
  • 現在のQ値Q(s,a;θ)Q(s,a;\theta)

報酬は、この「ターゲットQ値」の一部としてロス関数に組み込まれます。

2. なぜこの変換が必要か

(1) 報酬そのものは「即時的」で「局所的」

  • 報酬 rtr_t は、その行動の即時的な良し悪ししか教えてくれません。
  • しかし、強化学習の目標は将来の報酬の総和(リターン)を最大化することです。

(2) ターゲットQ値としてのリターン近似

  • DQNでは、「次の状態での最大Q値+報酬」 を、リターンの近似(ターゲット)として使います。
  • これにより、
    • 報酬 rr が「今この行動でどれだけ得をしたか」
    • γmaxaQ(s,a)\gamma \max_{a'} Q(s',a') が「この先どれだけ得をする見込みか」 を合わせた将来を見据えた評価をロス関数に反映できます。

(3) ロス関数としての定式化

  • ロス関数は「ターゲットQ値 − 現在のQ値」の二乗誤差です。
  • 報酬が大きいほどターゲットQ値が大きくなり、現在のQ値もそれに追従するように更新されます。
  • つまり、報酬は「Q値をどれだけ大きくすべきか」を決める指標としてロス関数に組み込まれています。

3. まとめ

  • DQNでは、報酬 rrターゲットQ値の一部としてロス関数に組み込まれます。
  • これにより、
    • 即時の報酬だけでなく、将来の見込みも含めた評価(リターン近似)をQ関数に学習させることができます。
    • 「報酬最大化」という目標を、「ターゲットQ値との誤差を最小化する」という形に変換し、勾配降下で扱いやすくしています。

このように、DQNにおける報酬は、「Q値を正しい方向へ導くためのターゲットの一部」 としてロス関数に変換されます。