2026-01-16

ラビット・チャレンジ - Stage 4. 深層学習 後編 (Day 4)

提出したレポートです。

絶対書きすぎですが、行間を埋めたくなるので仕方ない。

記事が下書きのまま忘れてた。認定取れました。


Rabbit Challenge - Stage 4. 深層学習 後編 (Day 4)

1. 強化学習

強化学習は、環境と相互作用しながら、長期的な報酬を最大化する方策を獲得すること を目的とした機械学習手法である。

教師あり学習や教師なし学習が「データに含まれるパターンの学習」を目的とするのに対し、強化学習では「優れた行動選択規則(方策)を獲得すること」が本質的な目標となる。

強化学習は以下の要素から構成される。

  • 環境と状態 $s$
  • エージェント
  • 行動 $a$
  • 報酬 $r$
  • 方策 $\pi$
  • 価値関数 $V(s)$ または $Q(s,a)$

重要な概念として、探索と利用のトレードオフ が存在する。

既知の最良行動のみを選択し続けると新たな最適行動を発見できず、一方で未知の行動のみを選択すると過去の経験を活かせない。このバランスを制御することが、強化学習アルゴリズム設計の重要な課題となる。

価値関数には以下の2種類がある。

  • 状態価値関数 $V(s)$
  • 行動価値関数 $Q(s,a)$

また、方策を直接確率分布として表現する手法として 方策勾配法 が紹介されている。

方策の良さを表す目的関数 $J(\theta)$ を定義し、その勾配に基づいてパラメータ $\theta$ を更新する。

さらに、TD(Temporal Difference)学習 は、動的計画法とモンテカルロ法の特徴を併せ持つ学習法であり、エピソード終了を待たずに価値更新を行える点が特徴である。

TD学習を行動価値関数へ拡張した手法として、Q 学習(オフ方策)SARSA(オン方策) が紹介されている。

実装演習

演習用コードがなかったので、簡易的なものを作成する。

このコードでは、4×4 のグリッドワールド環境を用いて、強化学習の基本概念である 方策評価 および TD 学習による制御(SARSA と Q 学習) を最小構成で確認している。

まず、一様ランダム方策に対してベルマン期待方程式を反復的に解くことで、各状態の価値関数 $V(s)$ を求め、方策評価が理論どおりに機能することを確認している。

次に、同一環境に対して SARSA(on-policy)と Q 学習(off-policy)を適用し、それぞれの更新式に基づいて行動価値関数 $Q(s,a)$ を学習する。

学習後は、得られた $Q(s,a)$ から greedy 方策を導出し、方策の形状、TD 誤差、平均リターンを比較することで、両手法の挙動の違いを可視化している。

import numpy as np
from dataclasses import dataclass

np.random.seed(0)

# ========= 1) 環境:極小グリッドワールド =========
@dataclass
class GridWorld:
    H: int = 4
    W: int = 4
    terminals: tuple = (0, 15)  # (0,0) と (3,3)
    step_reward: float = -1.0
    gamma: float = 0.90

    # 行動: 0=up,1=right,2=down,3=left
    A = 4
    action_names = ["U", "R", "D", "L"]
    deltas = [(-1,0),(0,1),(1,0),(0,-1)]

    def nS(self):
        return self.H * self.W

    def is_terminal(self, s: int) -> bool:
        return s in self.terminals

    def reset(self):
        # 終端以外からランダム開始
        s = np.random.randint(self.nS())
        while self.is_terminal(s):
            s = np.random.randint(self.nS())
        return s

    def step(self, s: int, a: int):
        """(s,a) -> (s', r, done)"""
        if self.is_terminal(s):
            return s, 0.0, True

        r = self.step_reward
        y, x = divmod(s, self.W)
        dy, dx = self.deltas[a]
        ny, nx = np.clip(y + dy, 0, self.H - 1), np.clip(x + dx, 0, self.W - 1)
        ns = ny * self.W + nx
        done = self.is_terminal(ns)
        return ns, r, done

env = GridWorld()

# ========= 2) 便利関数:表示 =========
def greedy_policy_from_Q(Q):
    # Q: [S,A]
    return np.argmax(Q, axis=1)

def render_policy(pi, env: GridWorld):
    # pi: [S] (argmax action)
    arrows = {0:"↑",1:"→",2:"↓",3:"←"}
    out = []
    for s in range(env.nS()):
        if s in env.terminals:
            out.append("■")  # terminal
        else:
            out.append(arrows[int(pi[s])])
    for y in range(env.H):
        print(" ".join(out[y*env.W:(y+1)*env.W]))

def rollout(env: GridWorld, pi, max_steps=100):
    s = env.reset()
    total = 0.0
    for _ in range(max_steps):
        a = int(pi[s])
        ns, r, done = env.step(s, a)
        total += r
        s = ns
        if done:
            break
    return total

# ========= 3) 方策評価(ベルマン期待方程式の反復) =========
def policy_evaluation(env: GridWorld, pi_probs, gamma=0.90, tol=1e-10, max_iter=100000):
    """
    pi_probs: [S,A] 行動確率(例:一様方策)
    V(s) = sum_a pi(a|s) [ r + gamma V(s') ]
    """
    S, A = env.nS(), env.A
    V = np.zeros(S, dtype=np.float64)

    for it in range(max_iter):
        delta = 0.0
        V_new = V.copy()
        for s in range(S):
            if env.is_terminal(s):
                V_new[s] = 0.0
                continue
            v = 0.0
            for a in range(A):
                ns, r, done = env.step(s, a)
                v += pi_probs[s, a] * (r + gamma * V[ns] * (0.0 if done else 1.0))
            V_new[s] = v
            delta = max(delta, abs(V_new[s] - V[s]))
        V = V_new
        if delta < tol:
            # print(f"[PolicyEval] converged in {it+1} iters, delta={delta:g}")
            break
    return V

# 一様ランダム方策で V(s) を求める(内容確認:ベルマン期待方程式)
S, A = env.nS(), env.A
pi_uniform = np.full((S, A), 1.0 / A)
V_uniform = policy_evaluation(env, pi_uniform, gamma=env.gamma)

print("=== 方策評価(ランダム方策)の V(s)(4x4を行列表示)===")
print(np.round(V_uniform.reshape(env.H, env.W), 3))

# ========= 4) TD学習:SARSA(on-policy) =========
def train_sarsa(env: GridWorld, episodes=5000, alpha=0.1, gamma=0.90, eps=0.1, max_steps=200):
    """
    SARSA:
      Q(s,a) <- Q(s,a) + alpha * [ r + gamma Q(s',a') - Q(s,a) ]
      (a' は同じ方策(ε-greedy)で選ぶ -> on-policy)
    """
    Q = np.zeros((env.nS(), env.A), dtype=np.float64)

    def eps_greedy(s):
        if env.is_terminal(s):
            return 0
        if np.random.rand() < eps:
            return np.random.randint(env.A)
        return int(np.argmax(Q[s]))

    returns = []
    for ep in range(episodes):
        s = env.reset()
        a = eps_greedy(s)
        G = 0.0
        for _ in range(max_steps):
            ns, r, done = env.step(s, a)
            G += r
            if done:
                td = r - Q[s, a]
                Q[s, a] += alpha * td
                break
            na = eps_greedy(ns)
            td = r + gamma * Q[ns, na] - Q[s, a]
            Q[s, a] += alpha * td
            s, a = ns, na
        returns.append(G)
    return Q, np.array(returns)

# ========= 5) TD学習:Q学習(off-policy) =========
def train_q_learning(env: GridWorld, episodes=5000, alpha=0.1, gamma=0.90, eps=0.1, max_steps=200):
    """
    Q学習:
      Q(s,a) <- Q(s,a) + alpha * [ r + gamma max_a' Q(s',a') - Q(s,a) ]
      (行動選択は ε-greedy でも、ターゲットは greedy -> off-policy)
    """
    Q = np.zeros((env.nS(), env.A), dtype=np.float64)

    def eps_greedy(s):
        if env.is_terminal(s):
            return 0
        if np.random.rand() < eps:
            return np.random.randint(env.A)
        return int(np.argmax(Q[s]))

    returns = []
    for ep in range(episodes):
        s = env.reset()
        G = 0.0
        for _ in range(max_steps):
            a = eps_greedy(s)
            ns, r, done = env.step(s, a)
            G += r
            if done:
                td = r - Q[s, a]
                Q[s, a] += alpha * td
                break
            td = r + gamma * np.max(Q[ns]) - Q[s, a]
            Q[s, a] += alpha * td
            s = ns
        returns.append(G)
    return Q, np.array(returns)

# 学習
Q_sarsa, R_sarsa = train_sarsa(env, episodes=6000, alpha=0.1, gamma=env.gamma, eps=0.1)
Q_ql,    R_ql    = train_q_learning(env, episodes=6000, alpha=0.1, gamma=env.gamma, eps=0.1)

pi_sarsa = greedy_policy_from_Q(Q_sarsa)
pi_ql    = greedy_policy_from_Q(Q_ql)

print("\n=== SARSA 学習後の greedy 方策(↑→↓←、■は終端)===")
render_policy(pi_sarsa, env)

print("\n=== Q学習 学習後の greedy 方策(↑→↓←、■は終端)===")
render_policy(pi_ql, env)

# 内容確認ポイント:TD誤差 δ の例を1回だけ計算して表示
# ある状態 s=5 で greedy 行動を取り、1ステップ分の δ を比較(on/offのターゲット差)
s = 5
a = int(np.argmax(Q_ql[s]))
ns, r, done = env.step(s, a)
if done:
    delta_sarsa = r - Q_sarsa[s, a]
    delta_ql    = r - Q_ql[s, a]
else:
    # SARSA: 次行動も(ここでは greedy で近似表示)
    na = int(np.argmax(Q_sarsa[ns]))
    delta_sarsa = r + env.gamma * Q_sarsa[ns, na] - Q_sarsa[s, a]
    # Q学習: max を使う
    delta_ql    = r + env.gamma * np.max(Q_ql[ns]) - Q_ql[s, a]

print("\n=== TD誤差 δ の例(s=5 で greedy 行動を1回)===")
print(f"s={s}, a={env.action_names[a]}, r={r}, s'={ns}, done={done}")
print(f"SARSA  δ = {delta_sarsa:.4f}")
print(f"Q学習   δ = {delta_ql:.4f}")

# 学習の進み具合(平均リターン)を簡単に確認
def moving_avg(x, k=200):
    if len(x) < k:
        return np.mean(x)
    return np.mean(x[-k:])

print("\n=== 直近200エピソードの平均リターン(大きいほど良い:負が小さいほど良い)===")
print(f"SARSA : {moving_avg(R_sarsa):.3f}")
print(f"Q学習  : {moving_avg(R_ql):.3f}")

# 方策でロールアウトして、どれくらいで終端に行けるかの雰囲気
print("\n=== greedy 方策でロールアウトした総報酬(10回)===")
for name, pi in [("SARSA", pi_sarsa), ("Q学習", pi_ql)]:
    rs = [rollout(env, pi, max_steps=100) for _ in range(10)]
    print(f"{name}: {rs} (avg={np.mean(rs):.2f})")

stage_4_01.png

ランダム方策に対する状態価値 $V(s)$ は、終端状態から遠いほど負の値が大きくなっており、無作為に行動した場合にゴール到達まで多くのステップを要することが確認できた。

これは、方策評価が環境の構造を正しく反映していることを示している。

また、SARSA と Q 学習はいずれも終端に向かう妥当な方策を獲得したが、Q 学習の方が平均リターンがわずかに良く、TD 誤差もほぼ 0 に収束していた。

これは、SARSA が探索を含む方策をそのまま評価する on-policy 手法であるのに対し、Q 学習は常に最適行動を仮定して更新する off-policy 手法であるという理論的差異が、実験結果として現れたものと解釈できる。

以上より、本実験を通して、TD 学習の更新式の違いが学習後の方策や収束特性に影響を与えることを、具体的な数値と挙動として確認できた。

自身の考察結果

本資料を通して、強化学習の本質は「予測」ではなく「意思決定」にある点が明確になった。

特に、教師あり学習との対比によって、強化学習では正解ラベルが存在しない状態で、行動の良し悪しを時間差で評価する という困難さが強調されている。

探索と利用のトレードオフは理論的な問題に留まらず、実運用では報酬設計や安全性とも密接に関係する。

例えば、過度な探索は実システムにおいて重大なリスクを伴う可能性があり、アルゴリズムの性能だけでなく、適用環境を含めた設計思想が重要 であると考えられる。

また、TD 学習に代表される ブートストラップ型の価値推定 は、「逐次更新が可能である」という点で大規模・連続環境に適しており、後述する AlphaGo / AlphaGo Zero における 価値ネットワーク学習や自己対局型強化学習の設計思想 を理解する上で重要な基盤となっている。

参考図書 / 関連記事

強化学習は、マルコフ決定過程(Markov Decision Process; MDP) として定式化されることが多く、環境との相互作用を通じて最適な行動戦略を獲得する学習枠組みである。

具体的には、エージェントの行動を決定する方策(Policy)、行動に対する即時的な報酬を与える報酬シグナル(Reward signal)、将来的な累積報酬の期待値を評価する価値関数(Value function)、そして場合によって環境の挙動を予測するための内部モデル(Model)という要素がある。

これらは強化学習問題を構造的に理解するための枠組みであり、方策や価値関数の最適化アルゴリズム(例: Q 学習や SARSA)は、これらの構成要素を統合して最適方策を導出する手段といえる。

また、強化学習における学習データの取得方法として、環境と直接相互作用しながら学習を進めるオンライン学習と、既存の行動ログのみを用いるオフライン学習という区別が存在することも、実運用を考える上で重要な視点である。

特にオフライン学習は、直接シミュレーションにアクセスできない現実世界のデータを活用する場合に不可欠となる。


2. AlphaGo

AlphaGo は、深層学習とモンテカルロ木探索(MCTS)を組み合わせた囲碁 AI であり、従来困難とされてきた囲碁において人間トップ棋士を破ったことで注目を集めた。

AlphaGo(Lee)では以下の3つの主要要素が用いられている。

  1. Policy Network

    • 局面から着手確率分布を出力
  2. Value Network

    • 局面から 最終的な勝敗の期待値(expected outcome) を $[-1,1]$ のスカラー値として出力(実質的には勝率の指標として解釈される)
  3. モンテカルロ木探索(MCTS)

    • PolicyNet をバイアスとして用い、探索効率を向上

学習は段階的に行われる。

  1. 教師あり学習による PolicyNet の初期化
  2. PolicyNet を用いた自己対局を通じた強化学習
  3. 自己対局データを用いた ValueNet の学習

これらは段階的ではあるが、自己対局を通じて Policy / Value が相互に性能を高め合う構成 となっている。

その後に登場した AlphaGo Zero では、

  • 教師あり学習を完全に排除
  • 特徴量を石の配置のみに限定
  • PolicyNet と ValueNet を統合
  • Residual Network を導入
  • Rollout を廃止

といった大幅な簡略化と性能向上が実現された。

実装演習

演習用コードがなかったので、簡易的なものを作成する。

このコードは、AlphaGo の中核である MCTS(モンテカルロ木探索)+ Policy / Value の利用 を、囲碁の代わりに石取りゲーム(Nim)で 概念検証用に大幅に簡略化した形 で再現し、その挙動を確認するものである。

具体的には、状態(残り石数・手番)をノードとして木を構築し、PUCT により $Q(s,a)$(価値の平均)と $P(s,a)$(事前確率)を用いて探索 する。

葉ノードでは簡易 value 推定 $v$ を与え、それをバックアップして各行動の $N(s,a)$(訪問回数)と $Q(s,a)$ を更新する。

探索後は 訪問回数 $N(s,a)$ を方策 $\pi(a|s)$ の近似 として出力し、さらにその MCTS 方策でランダム方策と対戦させて性能を確認している。

import math
import numpy as np
from dataclasses import dataclass

np.random.seed(0)

# =========================
# 1) 環境:Nim(石取り)
# =========================
@dataclass(frozen=True)
class NimState:
    stones: int        # 残り石数
    player: int        # 手番: +1 / -1(交互)

class NimGame:
    def __init__(self, max_take=3):
        self.max_take = max_take

    def legal_actions(self, s: NimState):
        # 取れるのは 1..min(max_take, stones)
        return list(range(1, min(self.max_take, s.stones) + 1))

    def next_state(self, s: NimState, a: int):
        return NimState(stones=s.stones - a, player=-s.player)

    def is_terminal(self, s: NimState):
        return s.stones == 0

    def terminal_value(self, s: NimState):
        """
        終端(stones==0)に到達した状態 s は「次の手番」の状態。
        直前に石を取り切ったのは -s.player 側なので、そのプレイヤが勝ち。
        Value は「現在の手番プレイヤ(s.player)視点」の勝敗を返す。
        勝ち:+1, 負け:-1
        """
        assert self.is_terminal(s)
        winner = -s.player
        return +1.0 if winner == s.player else -1.0

game = NimGame(max_take=3)

# ==========================================
# 2) Policy/Value の代替(簡易ヒューリスティック)
# ==========================================
def policy_prior(s: NimState, actions):
    """
    AlphaGo の policy network の代替。
    Nim の必勝手((stones-1) % (K+1) で調整)を「少しだけ」優遇するような prior を返す。
    ただし完全に決め打ちだと MCTS の意味が薄れるので、確率として滑らかにする。
    """
    K = game.max_take
    # 理想的には stones を (K+1) の倍数にしたい(通常の Nim の必勝定石)
    target = (s.stones - 1) % (K + 1)
    # target==0 ならどれでも同等(負け局面寄り)
    priors = np.ones(len(actions), dtype=np.float64)

    if target != 0:
        # 取る個数 a が target に一致する手を優遇
        for i, a in enumerate(actions):
            if a == target:
                priors[i] *= 3.0  # ほどほどに
    priors /= priors.sum()
    return priors

def value_estimate(s: NimState):
    """
    AlphaGo の value network の代替。
    Nim の必勝/必敗局面を雑に推定して、-1..+1 を返す。
    (本来はNNが学習するが、ここでは "Value を使う" こと自体の確認が目的)
    """
    if game.is_terminal(s):
        return game.terminal_value(s)

    K = game.max_take
    # 「残り石数が (K+1) の倍数」だと手番側が不利(定石上の負け局面)
    if s.stones % (K + 1) == 0:
        return -0.7
    else:
        return +0.7

# ==========================================
# 3) MCTS(PUCT:AlphaGo 系の選択)
# ==========================================
class Node:
    __slots__ = ("state", "P", "N", "W", "children", "is_expanded")

    def __init__(self, state: NimState):
        self.state = state
        self.P = {}          # prior P(s,a)
        self.N = {}          # visit count N(s,a)
        self.W = {}          # total value W(s,a)
        self.children = {}   # a -> Node
        self.is_expanded = False

    def Q(self, a):
        n = self.N.get(a, 0)
        if n == 0:
            return 0.0
        return self.W[a] / n

    def expand(self):
        assert not self.is_expanded
        actions = game.legal_actions(self.state)
        priors = policy_prior(self.state, actions)
        for a, p in zip(actions, priors):
            self.P[a] = float(p)
            self.N[a] = 0
            self.W[a] = 0.0
            self.children[a] = Node(game.next_state(self.state, a))
        self.is_expanded = True

def puct_select(node: Node, c_puct=1.5):
    """
    a* = argmax_a [ Q(s,a) + U(s,a) ]
    U(s,a) = c_puct * P(s,a) * sqrt(sum_b N(s,b)) / (1 + N(s,a))
    """
    assert node.is_expanded
    total_N = sum(node.N.values())
    best_a, best_score = None, -1e18
    for a in node.children.keys():
        Q = node.Q(a)
        U = c_puct * node.P[a] * math.sqrt(total_N + 1e-8) / (1 + node.N[a])
        score = Q + U
        if score > best_score:
            best_score = score
            best_a = a
    return best_a

def mcts_search(root: Node, num_simulations=200, c_puct=1.5):
    """
    1) Selection: PUCT で葉まで降りる
    2) Expansion: 未展開なら展開
    3) Evaluation: value network で v を得る(終端なら真の勝敗)
    4) Backup: 経路を逆にたどって W,N を更新(手番反転により符号反転)
    """
    for _ in range(num_simulations):
        node = root
        path = []  # (node, action)
        # Selection
        while node.is_expanded and (not game.is_terminal(node.state)):
            a = puct_select(node, c_puct=c_puct)
            path.append((node, a))
            node = node.children[a]

        # Evaluation(終端なら真の値、そうでなければ value 推定)
        if game.is_terminal(node.state):
            v = game.terminal_value(node.state)  # 現在手番視点
        else:
            # Expansion
            node.expand()
            v = value_estimate(node.state)        # 現在手番視点

        # Backup(親へ戻るたびに手番が反転するので符号反転)
        for parent, a in reversed(path):
            parent.N[a] += 1
            parent.W[a] += v
            v = -v

def policy_from_visits(root: Node, temperature=1.0):
    """
    AlphaGo でいう π(a|s) ≈ N(s,a) に基づく方策(temperature で平滑化)
    """
    actions = sorted(root.N.keys())
    visits = np.array([root.N[a] for a in actions], dtype=np.float64)

    if temperature <= 1e-8:
        # ほぼ greedy
        pi = np.zeros_like(visits)
        pi[np.argmax(visits)] = 1.0
    else:
        # N^(1/T)
        pi = visits ** (1.0 / temperature)
        if pi.sum() == 0:
            pi = np.ones_like(pi)
        pi = pi / pi.sum()
    return actions, visits, pi

# ==========================================
# 4) 1局面での MCTS 出力(内容確認)
# ==========================================
start = NimState(stones=10, player=+1)
root = Node(start)
root.expand()

mcts_search(root, num_simulations=400, c_puct=1.5)
actions, visits, pi = policy_from_visits(root, temperature=1.0)

print("=== 1局面での MCTS 結果(stones=10, take=1..3)===")
for a, n, p in zip(actions, visits, pi):
    print(f"action=take {a}, visits={int(n):4d}, pi={p:.3f}, Q={root.Q(a):+.3f}, P(prior)={root.P[a]:.3f}")

best_action = actions[int(np.argmax(visits))]
print(f"\n選択(visit 最大): take {best_action}")

# ==========================================
# 5) 簡易対戦:MCTS vs ランダム
# ==========================================
def mcts_player_action(state: NimState, sims=200, temp=0.0):
    r = Node(state)
    if not game.is_terminal(state):
        r.expand()
        mcts_search(r, num_simulations=sims, c_puct=1.5)
        acts, vts, pi = policy_from_visits(r, temperature=temp if temp > 0 else 1e-9)
        # temp=0 はほぼ greedy
        return int(acts[np.argmax(vts)])
    return 1

def random_player_action(state: NimState):
    return int(np.random.choice(game.legal_actions(state)))

def play_game(stones=10, mcts_sims=200, mcts_first=True):
    s = NimState(stones=stones, player=+1)
    # player=+1 を MCTS, player=-1 を Random と割り当て(mcts_firstで入れ替え)
    mcts_side = +1 if mcts_first else -1

    while not game.is_terminal(s):
        if s.player == mcts_side:
            a = mcts_player_action(s, sims=mcts_sims, temp=0.0)
        else:
            a = random_player_action(s)
        s = game.next_state(s, a)

    # 終端での勝者(直前の手番が勝者)
    winner = -s.player
    return winner

wins = 0
games = 30
for _ in range(games):
    w = play_game(stones=10, mcts_sims=150, mcts_first=True)
    if w == +1:
        wins += 1

print(f"\n=== 対戦結果(MCTS vs Random, {games} games, MCTS first, sims=150)===")
print(f"MCTS win rate: {wins}/{games} = {wins/games:.3f}")

stage_4_02.png

1局面(stones=10)では、探索の結果として take 1 が圧倒的に多く訪問され($\pi \approx 0.98$)、$Q$ も正に大きい ため、MCTS はこの手を最善手として強く支持していることが分かる。

これは、Policy の事前確率 $P$ が探索初期の方向性を与え、探索とバックアップにより $Q$ と $N$ が形成される、という MCTS の仕組み自体は動作していることを示す。

一方で、対戦では MCTS の勝率が 0/30 となっており、これは探索手順そのものよりも、終端報酬(勝敗)の符号定義や value 推定の視点(手番基準)の扱いに不整合が含まれている可能性 を示唆している。

すなわち、出力された $Q$ と $\pi$ は「良い手を選んでいるように見える」が、評価(価値の符号)が誤っているため、ゲーム全体では負け続ける、という状況が示唆される。

自身の考察結果

AlphaGo の本質的な革新は、探索と学習を分離せず、相互に補完させた点 にあると考えられる。

特に、PolicyNet による事前分布を MCTS に組み込むことで、「探索の効率化」と「学習による性能向上」が循環的に強化されている。

AlphaGo Zero において教師あり学習を完全に排除した点は、人間知識への依存を捨て、汎用的な学習能力を獲得した象徴的な進化 である。

これは、報酬設計と自己対局が十分に機能すれば、ドメイン知識に頼らずとも高度な戦略が獲得可能であることを示している。

一方で、この手法は莫大な計算資源を前提としており、実用上は後述する軽量化・高速化技術と不可分である点も重要である。

参考図書 / 関連記事

AlphaZero searches just 80 thousand positions per second in chess and 40 thousand in shogi, compared to 70 million for Stockfish and 35 million for Elmo.

AlphaGo Zero の特徴的な点の一つとして、探索局面数が従来のゲーム AI と比較して極端に少ないにもかかわらず、高い性能を示している ことが挙げられる。

論文中では、AlphaGo Zero がチェスにおいて 1 秒あたり約 8 万局面しか探索しないのに対し、Stockfish は約 7,000 万局面を探索していることが示されている。それにもかかわらず、AlphaGo Zero は探索量で大きく上回る従来手法に対して一貫して勝利している。

この結果は、探索の性能が単純な「探索量」によって決まるわけではないことを示唆している。

AlphaGo Zero では、深層ニューラルネットワークによって各局面における有望な手の事前確率(Policy)と、その局面の勝敗見込み(Value)が同時に推定される。

MCTS はこれらの情報を用いることで、価値の低い分岐を大量に探索するのではなく、勝利に直結しやすい分岐に探索を集中 させることが可能となっている。

従来の minimax 探索を用いたゲーム AI では、評価関数の誤差を補うために膨大な探索を行う必要があった。一方で AlphaGo Zero は、学習によって獲得した評価能力を前提とし、探索を「確認」と「洗練」のために用いていると解釈できる。

この点において、探索は単なる力任せの列挙ではなく、学習結果を活かすための補助的な役割 へと位置づけが変化している。

以上より、AlphaGo Zero における探索効率の逆転現象は、深層強化学習がもたらした設計思想の転換を象徴している。すなわち、計算資源を増やして網羅的に探索するのではなく、学習によって探索の質そのものを高めることで、高性能な意思決定を実現できることを示している。

この考え方は、ゲーム AI にとどまらず、複雑な意思決定問題全般に対しても重要な示唆を与えるものである。


3. 軽量化・高速化技術

深層学習モデルは高精度である一方、計算量・メモリ消費が大きく、学習および推論の高速化が重要な課題となる。

本資料では、以下の観点から技術が整理されている。

並列化

  • データ並列

    • 同一モデルを複数ワーカーにコピーし、異なるデータを処理
    • 勾配更新の方法として 同期型/非同期型 が存在
  • モデル並列

    • 単一モデルを分割し、層や演算を複数ワーカーで分担して処理

GPU による高速化

GPU は多数の単純な演算を SIMT 型に並列実行 するのに適しており、行列積や畳み込みのような 同一演算の大量反復 が中心となる深層学習と相性が良い。

CUDA や OpenCL が利用される。

モデル軽量化

代表的な手法は以下の3つである。

  • 量子化
    • 重みや活性値のビット幅を削減し、主に 推論時の高速化・省メモリ化 を目的とする
  • 蒸留
    • 高性能モデル(Teacher)の出力を用いて、軽量モデル(Student)を 学習段階で訓練 する
  • プルーニング
    • 寄与の小さい重み・ニューロンを削除し、再学習や微調整を伴ってモデルを簡素化する

実装演習

演習用コードがなかったので、簡易的なものを作成する。

本コードでは、講義で扱われた 軽量化・高速化技術の代表例である 知識蒸留・プルーニング・量子化 について、小規模な多層パーセプトロン(MLP)を用いて挙動を確認している。

まず、大きなモデル(Teacher)を通常の教師あり学習で学習し、その後、パラメータ数の少ない小型モデル(Student)を

  1. 通常のクロスエントロピー損失のみで学習した場合と、
  2. Teacher の出力を用いた知識蒸留によって学習した場合

で比較している。

次に、蒸留済み Student モデルに対して 重みの大きさに基づく非構造化プルーニング を行い、パラメータの疎化が精度に与える影響と、軽い再学習による回復を確認している。

さらに、動的量子化 を適用し、CPU 推論時の精度と推論時間の変化を測定することで、軽量化手法の特徴を定量的に比較している。

import time
import math
import numpy as np

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import TensorDataset, DataLoader

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

torch.manual_seed(0)
np.random.seed(0)

device = torch.device("cpu")

# =========================
# 1) データ(digits: 8x8=64, 10クラス)
# =========================
digits = load_digits()
X = digits.data.astype(np.float32)  # [N,64]
y = digits.target.astype(np.int64)

# 標準化(学習安定化)
scaler = StandardScaler()
X = scaler.fit_transform(X).astype(np.float32)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=0, stratify=y
)

X_train_t = torch.from_numpy(X_train)
y_train_t = torch.from_numpy(y_train)
X_test_t  = torch.from_numpy(X_test)
y_test_t  = torch.from_numpy(y_test)

train_loader = DataLoader(TensorDataset(X_train_t, y_train_t), batch_size=128, shuffle=True)
test_loader  = DataLoader(TensorDataset(X_test_t,  y_test_t),  batch_size=256, shuffle=False)

# =========================
# 2) モデル(Teacher / Student)
# =========================
class MLP(nn.Module):
    def __init__(self, hidden1=256, hidden2=128):
        super().__init__()
        self.fc1 = nn.Linear(64, hidden1)
        self.fc2 = nn.Linear(hidden1, hidden2)
        self.fc3 = nn.Linear(hidden2, 10)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        return self.fc3(x)

class SmallMLP(nn.Module):
    def __init__(self, hidden=64):
        super().__init__()
        self.fc1 = nn.Linear(64, hidden)
        self.fc2 = nn.Linear(hidden, 10)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        return self.fc2(x)

def count_params(model: nn.Module):
    return sum(p.numel() for p in model.parameters())

def count_nonzero_params(model: nn.Module):
    nonzero = 0
    total = 0
    for p in model.parameters():
        if p.requires_grad:
            total += p.numel()
            nonzero += (p.detach() != 0).sum().item()
    return nonzero, total

# =========================
# 3) 評価・推論時間計測
# =========================
@torch.no_grad()
def evaluate(model: nn.Module, loader: DataLoader):
    model.eval()
    correct = 0
    total = 0
    loss_sum = 0.0
    for xb, yb in loader:
        xb, yb = xb.to(device), yb.to(device)
        logits = model(xb)
        loss = F.cross_entropy(logits, yb)
        loss_sum += loss.item() * xb.size(0)
        pred = logits.argmax(dim=1)
        correct += (pred == yb).sum().item()
        total += xb.size(0)
    return loss_sum / total, correct / total

@torch.no_grad()
def measure_inference_time(model: nn.Module, X: torch.Tensor, iters=2000, warmup=200):
    model.eval()
    X = X.to(device)
    # warmup
    for _ in range(warmup):
        _ = model(X)
    t0 = time.perf_counter()
    for _ in range(iters):
        _ = model(X)
    t1 = time.perf_counter()
    return (t1 - t0) / iters  # sec/iter

# =========================
# 4) 学習(通常 / 蒸留)
# =========================
def train_ce(model, epochs=20, lr=1e-3):
    model.to(device)
    opt = torch.optim.Adam(model.parameters(), lr=lr)
    for ep in range(epochs):
        model.train()
        for xb, yb in train_loader:
            xb, yb = xb.to(device), yb.to(device)
            opt.zero_grad()
            logits = model(xb)
            loss = F.cross_entropy(logits, yb)
            loss.backward()
            opt.step()
    return model

def distillation_loss(student_logits, teacher_logits, y_true, T=2.0, alpha=0.7):
    """
    蒸留:
      - hard loss: CE(student, y)
      - soft loss: KL( softmax(teacher/T) || softmax(student/T) ) * T^2
    """
    hard = F.cross_entropy(student_logits, y_true)
    p_t = F.softmax(teacher_logits / T, dim=1)
    log_p_s = F.log_softmax(student_logits / T, dim=1)
    soft = F.kl_div(log_p_s, p_t, reduction="batchmean") * (T * T)
    return alpha * soft + (1 - alpha) * hard

def train_with_distillation(student, teacher, epochs=20, lr=1e-3, T=2.0, alpha=0.7):
    student.to(device)
    teacher.to(device)
    teacher.eval()
    opt = torch.optim.Adam(student.parameters(), lr=lr)
    for ep in range(epochs):
        student.train()
        for xb, yb in train_loader:
            xb, yb = xb.to(device), yb.to(device)
            with torch.no_grad():
                t_logits = teacher(xb)
            opt.zero_grad()
            s_logits = student(xb)
            loss = distillation_loss(s_logits, t_logits, yb, T=T, alpha=alpha)
            loss.backward()
            opt.step()
    return student

# =========================
# 5) プルーニング(重みの小さいものを 0 にする)
# =========================
def magnitude_prune_(model: nn.Module, prune_ratio=0.5):
    """
    代表的な「非構造化プルーニング(unstructured)」の最小実装:
      - Linear層の weight をまとめて閾値で0化
    """
    weights = []
    for m in model.modules():
        if isinstance(m, nn.Linear):
            weights.append(m.weight.detach().abs().flatten())
    all_w = torch.cat(weights)
    k = int(all_w.numel() * prune_ratio)
    if k <= 0:
        return model
    thresh = torch.kthvalue(all_w, k).values.item()  # 下位k番目の絶対値
    # しきい値以下を0化
    for m in model.modules():
        if isinstance(m, nn.Linear):
            w = m.weight.data
            mask = w.abs() <= thresh
            w[mask] = 0.0
    return model

def finetune(model, epochs=5, lr=5e-4):
    # プルーニング後の軽い再学習(回復を見る)
    model.to(device)
    opt = torch.optim.Adam(model.parameters(), lr=lr)
    for ep in range(epochs):
        model.train()
        for xb, yb in train_loader:
            xb, yb = xb.to(device), yb.to(device)
            opt.zero_grad()
            logits = model(xb)
            loss = F.cross_entropy(logits, yb)
            loss.backward()
            opt.step()
    return model

# =========================
# 6) 動的量子化(Linearをint8化)
# =========================
def dynamic_quantize(model: nn.Module):
    # Linear層を動的量子化(推論時のみ量子化演算が働く)
    qmodel = torch.quantization.quantize_dynamic(
        model, {nn.Linear}, dtype=torch.qint8
    )
    return qmodel

# =========================
# 実行
# =========================
print("=== (A) Teacher 学習(大きめMLP)===")
teacher = MLP(hidden1=256, hidden2=128)
teacher = train_ce(teacher, epochs=25, lr=1e-3)
t_loss, t_acc = evaluate(teacher, test_loader)
print(f"Teacher params: {count_params(teacher):,}")
print(f"Teacher test   : loss={t_loss:.4f}, acc={t_acc:.4f}")

print("\n=== (B) Student 学習(小さめMLP, 通常CE)===")
student_ce = SmallMLP(hidden=64)
student_ce = train_ce(student_ce, epochs=25, lr=1e-3)
s_loss, s_acc = evaluate(student_ce, test_loader)
print(f"Student(CE) params: {count_params(student_ce):,}")
print(f"Student(CE) test  : loss={s_loss:.4f}, acc={s_acc:.4f}")

print("\n=== (C) Student 学習(蒸留:Teacher→Student)===")
student_kd = SmallMLP(hidden=64)
student_kd = train_with_distillation(student_kd, teacher, epochs=25, lr=1e-3, T=2.0, alpha=0.7)
kd_loss, kd_acc = evaluate(student_kd, test_loader)
print(f"Student(KD) params: {count_params(student_kd):,}")
print(f"Student(KD) test  : loss={kd_loss:.4f}, acc={kd_acc:.4f}")

# 推論速度(ミニバッチ固定)
X_batch = X_test_t[:256]
t_time = measure_inference_time(teacher, X_batch, iters=1500)
ce_time = measure_inference_time(student_ce, X_batch, iters=1500)
kd_time = measure_inference_time(student_kd, X_batch, iters=1500)
print("\n=== 推論時間(sec/iter, batch=256, CPU)===")
print(f"Teacher     : {t_time*1e6:.2f} us")
print(f"Student(CE) : {ce_time*1e6:.2f} us")
print(f"Student(KD) : {kd_time*1e6:.2f} us")

print("\n=== (D) プルーニング(Student(KD) を疎化)===")
pruned = SmallMLP(hidden=64)
pruned.load_state_dict(student_kd.state_dict())

nonzero_before, total_before = count_nonzero_params(pruned)
magnitude_prune_(pruned, prune_ratio=0.60)  # 60%を0化(目安)
nonzero_after, total_after = count_nonzero_params(pruned)

p_loss, p_acc = evaluate(pruned, test_loader)
print(f"Nonzero params: {nonzero_before:,}/{total_before:,} -> {nonzero_after:,}/{total_after:,} "
      f"(sparsity={1 - nonzero_after/total_after:.2%})")
print(f"Pruned test   : loss={p_loss:.4f}, acc={p_acc:.4f}")

print("\n=== (E) プルーニング後の軽い再学習(回復を見る)===")
pruned_ft = finetune(pruned, epochs=5, lr=5e-4)
pft_loss, pft_acc = evaluate(pruned_ft, test_loader)
print(f"Pruned+FT test: loss={pft_loss:.4f}, acc={pft_acc:.4f}")

pruned_time = measure_inference_time(pruned_ft, X_batch, iters=1500)
print("\n=== 推論時間(sec/iter, batch=256, CPU)===")
print(f"Pruned+FT   : {pruned_time*1e6:.2f} us")

print("\n=== (F) 動的量子化(Student(KD))===")
q_student = dynamic_quantize(student_kd)
q_loss, q_acc = evaluate(q_student, test_loader)
q_time = measure_inference_time(q_student, X_batch, iters=1500)

print(f"Quantized(Student KD) test: loss={q_loss:.4f}, acc={q_acc:.4f}")
print("\n=== 推論時間(sec/iter, batch=256, CPU)===")
print(f"Quantized(KD): {q_time*1e6:.2f} us")

stage_4_03.png

Teacher モデルは約 5 万パラメータを用いて高い精度を達成した一方、Student モデルはパラメータ数を約 10 分の 1 に削減しても、精度低下はごく小さいことが確認できた。これは、モデルサイズの削減が必ずしも大幅な性能劣化を招かないことを示している。

知識蒸留を用いた Student は、今回の条件では通常学習と比べて精度の改善は見られなかったものの、小型モデルに教師モデルの出力分布を伝える枠組み自体は正しく機能している ことが確認できる。また、小型化によって推論時間は Teacher に比べて大幅に短縮されており、計算効率の面で大きな利点がある。

プルーニングでは、約 60% の重みを 0 にしても精度は大きく低下せず、さらに軽い再学習によって性能が回復した。この結果から、ニューラルネットワークには冗長なパラメータが多く含まれていることが分かる。

一方で、非構造化プルーニングは CPU 上では推論時間を大きく改善しない点も確認され、疎性と実行速度が必ずしも直結しない ことが示された。

量子化については、精度をほぼ維持したまま推論を行えることが確認できたが、今回の実行環境では推論時間の短縮は限定的であった。これは、量子化の効果がハードウェアや実行環境に強く依存することを示している。

以上より、本実験を通して、軽量化・高速化技術は「モデルサイズ削減」「精度維持」「実行環境との相性」という複数の観点を考慮して選択すべきであり、単一の手法ですべてを解決できるわけではないことが確認できた。

自身の考察結果

高速化と軽量化は単なる計算効率の問題ではなく、深層学習を社会実装へ接続するための必須条件 であると感じた。

特に IoT やエッジデバイスでは、モデル精度よりも計算制約が支配的になるケースが多い。

蒸留は、単なるモデル圧縮ではなく「知識表現の再構築」と捉えることができ、学習効率の観点でも非常に興味深い。

一方、量子化やプルーニングは精度低下のリスクを伴うため、適用にはタスク依存の慎重な設計が求められる。

参考図書 / 関連記事

When the soft targets have high entropy, they provide much more information per training case than hard targets and much less variance in the gradient between training cases,

This shows that soft targets are a very effective way of communicating the regularities discovered by a model trained on all of the data to another model.

With only 3% of the data … training the baseline model with hard targets leads to severe overfitting … whereas the same model trained with soft targets is able to recover almost all the information in the full training set.

Hinton らは、知識蒸留において用いられる soft targets(教師モデルが出力する確率分布) が、単なる教師信号の代替ではなく、強力な正則化効果を持つ ことを指摘している。

Soft targets は高いエントロピーを持つため、各学習データに対して「正解クラス」だけでなく、「誤分類されやすいクラスとの相対関係」まで含んだ情報を与える。この結果、勾配の分散が小さくなり、学習が安定する。

論文では、音声認識モデルの実験において、訓練データのわずか 3% しか用いない場合でも、hard target のみで学習したモデルが深刻な過学習に陥る一方、soft targets を用いた場合には、ほぼ完全なデータを使った場合に近い汎化性能を示すことが報告されている。これは、soft targets が教師モデルの持つ「一般化の仕方」を小さなモデルへ伝達していることを意味する。

この結果は、知識蒸留が単なるモデル圧縮手法ではなく、正則化手法としても機能する ことを示している。

通常の教師あり学習では、one-hot な hard target によって学習が進むため、モデルは訓練データに強く適合しやすく、データ量が少ない場合には過学習が起こりやすい。

一方、soft targets を用いた学習では、教師モデルが獲得したクラス間の類似構造が損失関数に反映されるため、学習データが少ない状況でも安定した汎化が可能となる。

この観点から見ると、蒸留は「大きなモデルの知識を小さなモデルに移す」手法であると同時に、「教師モデルを正則化器として用いる学習方法」と解釈できる。

実装演習で確認したように、蒸留による精度向上が必ずしも顕著でない場合であっても、学習の安定性や汎化性能の観点では重要な役割を果たしている可能性がある。


4. 応用技術

本セクションでは、軽量化・高性能化を実現した具体的なネットワーク構造が紹介されている。

MobileNet

  • Depthwise Convolution と Pointwise Convolution を組み合わせた Depthwise Separable Convolution を採用
  • 通常の畳み込みと比較して計算量を大幅に削減

DenseNet

  • 各層の出力を後続のすべての層へ結合
  • 勾配消失の抑制と特徴再利用を促進
  • 成長率 $k$ によりネットワーク規模を制御

正規化手法

  • Batch Normalization
  • Layer Normalization
  • Instance Normalization

ミニバッチサイズや用途に応じて使い分ける必要がある。

WaveNet

  • Dilated causal convolution を用いることで 広い受容野を少ないパラメータで実現
  • 学習時の並列化が可能

実装演習

演習用コードがなかったので、簡易的なものを作成する。

このコードでは、講義で扱われた 応用技術(軽量化・正規化・受容野拡大など) の代表例について、数式や理論だけでなく、テンソルの形状・計算量・数値的挙動 として確認することを目的としている。

具体的には、以下の4点を最小構成の実験で検証している。

まず、MobileNet における Depthwise Separable Convolution について、通常の畳み込み層と比較し、出力形状を一致させた上で、パラメータ数・概算 FLOPs・推論時間を比較している。

次に、DenseNet の Dense Block を実装し、各層の出力特徴マップを concat により結合 することで、層を重ねるごとに 入力チャネル数が累積的に増加する 構造を確認している。

さらに、Layer Normalization と Instance Normalization を同一入力に適用し、それぞれが どの次元を正規化対象としているか を、平均および分散の数値として比較している。

最後に、WaveNet で用いられる Dilated Causal Convolution を 1 次元畳み込みで再現し、ダイレーションを大きくすることで受容野が拡大する様子をインパルス応答によって確認している。

import time
import math
import torch
import torch.nn as nn
import torch.nn.functional as F

torch.manual_seed(0)
device = torch.device("cpu")

# -----------------------------
# 共通:パラメータ数
# -----------------------------
def count_params(m: nn.Module) -> int:
    return sum(p.numel() for p in m.parameters())

# -----------------------------
# 1) MobileNet(Depthwise Separable Conv)
# -----------------------------
class StdConv(nn.Module):
    def __init__(self, cin, cout, k=3, stride=1, padding=1):
        super().__init__()
        self.conv = nn.Conv2d(cin, cout, k, stride=stride, padding=padding, bias=False)
    def forward(self, x):
        return self.conv(x)

class DWSeparableConv(nn.Module):
    def __init__(self, cin, cout, k=3, stride=1, padding=1):
        super().__init__()
        # depthwise: groups=cin
        self.dw = nn.Conv2d(cin, cin, k, stride=stride, padding=padding, groups=cin, bias=False)
        # pointwise: 1x1
        self.pw = nn.Conv2d(cin, cout, 1, bias=False)
    def forward(self, x):
        return self.pw(self.dw(x))

def conv_flops_std(H, W, cin, cout, k=3):
    # 概算(バイアス・境界効果等は無視):H*W*cout*(cin*k*k)(MACs)
    return H * W * cout * (cin * k * k)

def conv_flops_dwsep(H, W, cin, cout, k=3):
    # depthwise: H*W*cin*(k*k) + pointwise: H*W*cout*(cin)
    return H * W * cin * (k * k) + H * W * cout * cin

@torch.no_grad()
def measure_time(m: nn.Module, x, iters=500, warmup=50):
    m.eval()
    for _ in range(warmup):
        _ = m(x)
    t0 = time.perf_counter()
    for _ in range(iters):
        _ = m(x)
    t1 = time.perf_counter()
    return (t1 - t0) / iters

print("## (A) MobileNet: 通常Conv vs Depthwise+Pointwise")
B, Cin, H, W, Cout = 32, 32, 32, 32, 64
x = torch.randn(B, Cin, H, W, device=device)

std = StdConv(Cin, Cout).to(device)
dw  = DWSeparableConv(Cin, Cout).to(device)

y_std = std(x)
y_dw  = dw(x)

print(f"Output shape (std) : {tuple(y_std.shape)}")
print(f"Output shape (dwsp): {tuple(y_dw.shape)}")
print(f"Params std  : {count_params(std):,}")
print(f"Params dwsp : {count_params(dw):,}")

flops_std = conv_flops_std(H, W, Cin, Cout, k=3)
flops_dw  = conv_flops_dwsep(H, W, Cin, Cout, k=3)
print(f"Approx FLOPs std  : {flops_std/1e6:.2f} M (概算)")
print(f"Approx FLOPs dwsp : {flops_dw/1e6:.2f} M (概算)")
print(f"FLOPs ratio (dwsp/std): {flops_dw/flops_std:.3f}")

t_std = measure_time(std, x, iters=300)
t_dw  = measure_time(dw,  x, iters=300)
print(f"Inference time std  : {t_std*1e6:.2f} us/iter (CPU, batch={B})")
print(f"Inference time dwsp : {t_dw*1e6:.2f} us/iter (CPU, batch={B})")

# -----------------------------
# 2) DenseNet(Dense Block:concat でチャネルが増える)
# -----------------------------
class DenseLayer(nn.Module):
    def __init__(self, in_ch, growth=16):
        super().__init__()
        self.bn = nn.BatchNorm2d(in_ch)
        self.conv = nn.Conv2d(in_ch, growth, kernel_size=3, padding=1, bias=False)
    def forward(self, x):
        out = self.conv(F.relu(self.bn(x)))
        return torch.cat([x, out], dim=1)  # DenseNet の特徴:concat

class DenseBlock(nn.Module):
    def __init__(self, in_ch, num_layers=4, growth=16):
        super().__init__()
        layers = []
        ch = in_ch
        for _ in range(num_layers):
            layers.append(DenseLayer(ch, growth=growth))
            ch += growth
        self.net = nn.Sequential(*layers)
        self.out_ch = ch
    def forward(self, x):
        return self.net(x)

print("## (B) DenseNet: Dense Block の shape(チャネル増加)確認")
B, C0, H, W = 8, 24, 16, 16
x = torch.randn(B, C0, H, W, device=device)

block = DenseBlock(in_ch=C0, num_layers=4, growth=12).to(device)
y = block(x)

print(f"Input shape : {tuple(x.shape)}")
print(f"Output shape: {tuple(y.shape)}")
print(f"Expected out channels = {block.out_ch} (in {C0} + 4*growth {4*12})")
print(f"DenseBlock params: {count_params(block):,}\n")

# -----------------------------
# 3) LayerNorm vs InstanceNorm(正規化軸の違い)
# -----------------------------
print("## (C) LayerNorm / InstanceNorm: 正規化軸の違い確認(平均・分散)")
B, C, H, W = 2, 3, 4, 5
x = torch.randn(B, C, H, W, device=device) * 3 + 10  # 分かりやすいように平均・分散をずらす

# LayerNorm: (C,H,W) を1サンプル単位で正規化(画像なら全部の要素を正規化)
ln = nn.LayerNorm([C, H, W], elementwise_affine=False).to(device)
# InstanceNorm: (H,W) をチャンネルごとに正規化(サンプル×チャンネル単位)
inn = nn.InstanceNorm2d(C, affine=False, track_running_stats=False).to(device)

y_ln = ln(x)
y_in = inn(x)

# それぞれの「正規化される単位」で平均・分散を測る
def stats_layernorm_unit(t):
    # per-sample stats over (C,H,W)
    mean = t.mean(dim=(1,2,3))
    var  = t.var(dim=(1,2,3), unbiased=False)
    return mean, var

def stats_instancenorm_unit(t):
    # per-sample, per-channel stats over (H,W)
    mean = t.mean(dim=(2,3))
    var  = t.var(dim=(2,3), unbiased=False)
    return mean, var

m0, v0 = stats_layernorm_unit(x)
m1, v1 = stats_layernorm_unit(y_ln)
m2, v2 = stats_instancenorm_unit(y_in)

print("Before norm (per-sample over C,H,W) mean, var:")
print(" mean:", m0.detach().cpu().numpy().round(3), " var:", v0.detach().cpu().numpy().round(3))
print("After LayerNorm (per-sample over C,H,W) mean, var:")
print(" mean:", m1.detach().cpu().numpy().round(3), " var:", v1.detach().cpu().numpy().round(3))
print("After InstanceNorm (per-sample, per-channel over H,W) mean, var:")
print(" mean:\n", m2.detach().cpu().numpy().round(3))
print(" var:\n",  v2.detach().cpu().numpy().round(3))

# -----------------------------
# 4) WaveNet(Dilated Causal Conv の受容野)
# -----------------------------
print("## (D) WaveNet: Dilated Causal Conv の受容野(インパルス応答)確認")

class CausalConv1d(nn.Module):
    def __init__(self, cin, cout, k=2, dilation=1):
        super().__init__()
        self.k = k
        self.dilation = dilation
        self.conv = nn.Conv1d(cin, cout, kernel_size=k, dilation=dilation, bias=False)
        # 重みを見やすく固定(全て1)
        nn.init.constant_(self.conv.weight, 1.0)

    def forward(self, x):
        # x: [B,C,T]
        pad = (self.k - 1) * self.dilation
        x = F.pad(x, (pad, 0))  # 左だけパディング -> 未来を見ない(causal)
        return self.conv(x)

@torch.no_grad()
def impulse_response(dilations, T=32):
    # 1ch, 1batch
    x = torch.zeros(1, 1, T)
    x[0, 0, 0] = 1.0  # t=0 にインパルス
    y = x
    for d in dilations:
        y = CausalConv1d(1, 1, k=2, dilation=d)(y)
    # どの時刻に影響が出たか
    support = (y[0,0].abs() > 1e-6).nonzero().flatten().tolist()
    return y[0,0], support

# dilation を倍々にすると受容野が急に広がる(WaveNet 的)
dils = [1, 2, 4, 8]
y, support = impulse_response(dils, T=40)

print(f"Dilations: {dils}")
print(f"Non-zero timesteps in output (support): {support[:30]}{'...' if len(support)>30 else ''}")
print(f"Receptive field (max t in support) = {max(support)} (0-indexed)")

stage_4_04.png

MobileNet の実験では、Depthwise Separable Convolution が通常の畳み込みと同一の出力形状を保ちながら、パラメータ数および計算量を大幅に削減できることが確認できた。

実際に、概算 FLOPs は約 1/8 に減少し、CPU 上の推論時間も大きく短縮されており、MobileNet が軽量モデルとして有効である理由が定量的に示された。

DenseNet の実験では、Dense Block 内で各層の出力を結合することで、入力チャネル数に成長率(growth rate)を加えた分だけ出力チャネルが増加することが確認できた。

この構造により、特徴量が層を越えて直接伝播し、特徴再利用が促進されるという DenseNet の設計思想を、形状の変化として理解できる。

正規化手法の比較では、LayerNorm が各サンプル全体(チャネルと空間次元)を正規化するのに対し、InstanceNorm は各サンプル・各チャネルごとに空間方向のみを正規化していることが、平均・分散がそれぞれ 0 と 1 になる単位の違いとして明確に確認できた。

これは、自然言語処理やスタイル変換など、用途によって適切な正規化手法が異なる理由を裏付けている。

WaveNet の実験では、ダイレーションを指数的に増加させることで、少数の層でも広い時間範囲に依存できることが確認できた。

受容野が 15 ステップ先まで広がっていることから、Dilated Convolution が長期依存関係を効率的に捉えるための有効な手法であることが分かる。

以上より、本実装演習を通して、各応用技術が「なぜその構造を採用しているのか」を、計算量・形状・数値挙動の観点から具体的に理解できた。

自身の考察結果

応用技術に共通する設計思想は、単純に層を深くするのではなく、情報の流れを最適化すること にあると感じた。

特に MobileNet や DenseNet は、計算資源制約を前提条件としてネットワーク構造を再設計している点が印象的である。

WaveNet における Dilated Convolution は、時系列処理を RNN に依存しない形で実現しており、後の Transformer 系モデルへの流れを予感させる技術であると考えられる。

参考図書 / 関連記事

BN’s error increases rapidly when the batch size becomes smaller, caused by inaccurate batch statistics estimation.

This limits BN’s usage for training larger models and transferring features to computer vision tasks including detection, segmentation, and video, which require small batches constrained by memory consumption.

GN’s computation is independent of batch sizes, and its accuracy is stable in a wide range of batch sizes.

Batch Normalization(BN)は、学習を安定させる非常に有効な手法である一方で、バッチ次元に沿って平均・分散を推定する という性質を持つ。そのため、バッチサイズが小さくなると、統計量の推定誤差が大きくなり、性能が急激に劣化することが論文中で明確に指摘されている。

この問題は、ImageNet のような大規模分類タスクでは顕在化しにくいが、物体検出・セグメンテーション・動画認識といった応用タスクでは深刻である。

これらのタスクでは、高解像度入力や 3D 畳み込みの影響でメモリ制約が厳しく、1〜2 枚程度の small batch で学習・fine-tuning を行うことが一般的 である。その結果、BN は正常に機能せず、しばしば統計量を固定(freeze)した擬似線形層として用いられる。

一方で Group Normalization(GN)は、チャネルをいくつかのグループに分割し、各グループ内で正規化を行う手法であり、バッチ次元を一切使用しない

このため、バッチサイズに依存せず、small batch 環境においても安定した性能を示すことが、ImageNet、COCO、Kinetics など複数の実験で示されている。

この結果は、正規化手法が単なる数値安定化の工夫ではなく、モデル設計や学習条件と密接に結びついた要素である ことを示している。

Batch Normalization は、十分なバッチサイズを前提とすることで高い性能を発揮するが、その前提が崩れると、学習・転移の両面で制約となる。

実装演習では、LayerNorm や InstanceNorm が「どの軸で正規化を行うか」という構造的違いとして理解できたが、Group Normalization はさらに一歩進み、実運用上の制約(メモリ・バッチサイズ)を考慮した設計 である点に特徴がある。

特に、fine-tuning 時に BN を freeze する必要がある状況では、学習時と推論時で正規化の挙動が不整合になるが、GN はこの問題を根本的に回避できる。

以上より、Group Normalization は「BatchNorm の代替手法」という位置づけに留まらず、small batch が避けられない応用タスクにおいて、モデル設計の自由度を回復するための重要な技術 であると考えられる。


5. ResNet(転移学習)

転移学習の目的と背景

転移学習は、目的タスクにおける教師データが少ない場合に、別のタスクで学習済みのモデルを再利用することで、効率的に高性能なモデルを構築する手法である。

講義資料では、教師あり学習におけるデータ収集・アノテーションコストの高さを背景として、転移学習の有効性が強調されている。

特に画像認識分野では、ImageNet のような大規模データセットで事前学習されたモデル が広く公開されており、これらを活用することで、畳み込み層を特徴抽出器として固定し、全結合層のみを再学習する などの方法により、少量データでも実用的な性能を得ることが可能となる。

ImageNet による事前学習

ImageNet は 1400 万件以上の画像から構成される大規模データセットであり、特に ILSVRC を通じて 多くの画像認識モデルの評価基準として利用されてきた。

講義資料では、ImageNet の 1000 クラス分類タスクで学習された ResNet 系モデル を事前学習モデルとして利用する。

ResNet の代表的なモデルとして、以下が紹介されている。

  • ResNet50
  • ResNet101
  • ResNet152

これらはモデルサイズ・パラメータ数・Top-1 精度のトレードオフを持ち、ハンズオンでは ResNet50 が採用されている。

ResNet の構造と Skip Connection

ResNet の本質的な特徴は、Skip Connection(残差接続) の導入にある。

中間層の出力を $H(x)$、学習対象の写像を $F(x)$ とすると、入力と出力の次元が一致する場合、残差ブロックは次式で表される。

$$ H(x) = F(x) + x $$

この構造により、

  • 勾配消失の回避
  • 勾配爆発の回避
  • 非常に深いネットワークでも学習可能

といった性質が得られ、従来の Plain な CNN と比較して大幅に学習が安定する。

Bottleneck 構造

ResNet では、計算コストを抑えつつ層を深くするために Bottleneck 構造 が採用されている。

Bottleneck 構造では、

  • $1 \times 1$ 畳み込みによる次元削減
  • $3 \times 3$ 畳み込みによる特徴抽出
  • $1 \times 1$ 畳み込みによる次元復元

を組み合わせることで、パラメータ数と計算量を抑えつつ、より深いネットワーク構成 を可能にしている。

Wide ResNet

講義資料では ResNet の派生として Wide ResNet も紹介されている。

Wide ResNet は、

  • 層を深くするのではなく
  • 各層のフィルタ数(チャネル数)を $k$ 倍に拡張

することで性能向上を図る手法である。

この設計により、

  • GPU の並列計算特性を活かしやすい
  • 学習が高速
  • 比較的浅い層数でも高精度

といった利点が得られる。

ファインチューニング

転移学習の実践的手法として、ファインチューニング が説明されている。

ImageNet で事前学習されたモデルに対し、

  • 事前学習済み重みをそのまま固定して利用する
  • 事前学習済み重みを初期値として再学習する

といった選択肢があり、対象タスク(講義では tf_flowers 分類)に応じて適切な方法を選択する必要がある。

実装演習

4_1_transfer-learning.ipynb を実行する。

なお、4_2_wide_resnet.ipynb はエラーで動かなかった。

stage_4_05.png

本実験では、ResNet50 を用いた画像分類において、

  1. 事前学習なし
  2. ImageNet 事前学習モデルを用いた特徴抽出
  3. ImageNet 事前学習モデルを用いたファインチューニング

の 3 手法を比較した。

まず、事前学習なし(No Weight) の場合、学習精度は徐々に向上するものの、検証精度は不安定であり、全体として精度は高くない。これは、TFFlowers のような比較的小規模なデータセットに対して、ResNet50 のような大規模モデルをゼロから学習することが困難であることを示している。特に、学習データに過度に適合しやすく、汎化性能が十分に得られていない点が確認できる。

次に、ImageNet 事前学習モデルを固定して特徴抽出のみを行った場合(Use ImageNet) では、学習・検証ともに精度の向上が緩やかであり、最終的な精度も高くない結果となった。この結果は、ImageNet で学習された特徴が一般的な視覚特徴として有効である一方で、本タスク(花画像分類)に対しては分類器部分のみの学習では適応が不十分であることを示唆している。すなわち、特徴抽出としての転移学習は学習の安定化には寄与するが、タスク固有の特徴を十分に捉えきれていないと考えられる。

一方で、事前学習モデルを用いたファインチューニング(FineTuning) では、学習初期から急速に精度が向上し、学習精度はほぼ 1.0 に達した。また、検証精度も大きく改善し、他の手法と比較して最も高い性能を示している。これは、ImageNet で獲得された汎用的な特徴表現を初期値として利用しつつ、ネットワーク全体をタスクに合わせて微調整することで、データ効率よく高性能なモデルを構築できることを示している。

ただし、ファインチューニングでは学習精度と検証精度の間に差が見られ、過学習の兆候も確認できる。これは、モデル容量が大きいことやデータ数が限られていることに起因すると考えられる。そのため、実運用においては、凍結層の調整や正則化、データ拡張などを併用する必要がある。

以上より、本実験から、小規模データセットに対しては、事前学習済みモデルを用いたファインチューニングが最も効果的である 一方で、特徴抽出のみでは性能が頭打ちになりやすく、ゼロからの学習は非効率であることが確認できた。この結果は、転移学習が少量データ環境において特に有効であることを示している。

自身の考察結果

ResNet を中心とした転移学習の枠組みは、「モデルを一から学習する」という前提を覆した点 に本質的な価値があると感じた。

特に、ImageNet で学習された特徴表現が、異なるドメインにおいても有効に機能するという事実は、CNN が学習する初期層の汎用性の高さ を示している。

Skip Connection による残差学習は、単なる学習安定化手法ではなく、「必要であれば恒等写像を学習すればよい」という設計思想 をネットワークに組み込んだ点が重要である。

この考え方により、深さそのものが性能向上の阻害要因にならなくなった。

また、Wide ResNet が示す「深さより幅」という発想は、ハードウェア特性(GPU)を前提にアルゴリズムを設計する好例であり、理論だけでなく 実装・計算資源を含めた現実的最適化 の重要性を強く感じた。

ファインチューニングについても、単に既存モデルを流用するのではなく、どこまで再学習させるかをタスク特性とデータ量に応じて判断する必要がある 点が示唆されており、実運用を意識した内容であると考えられる。

参考図書 / 関連記事

each fraction of a percent of improved accuracy costs nearly doubling the number of layers

training very deep residual networks has a problem of diminishing feature reuse, which makes these networks very slow to train.

as gradient flows through the network there is nothing to force it to go through residual block weights and it can avoid learning anything during training

Residual Network は skip connection によって勾配消失問題を克服し、1000 層を超えるような非常に深いネットワークの学習を可能にした。しかし著者らは、精度向上のために深さを増やすことが 急激に非効率になる 点を問題として指摘している。実際、わずかな精度改善のために層数を倍増させる必要があり、計算コストと学習時間が大きく増加する。

さらに重要なのが diminishing feature reuse(特徴再利用の低下) という問題である。Residual Block では恒等写像が存在するため、勾配は必ずしも各 Residual Block の重みを通過する必要がなく、学習の過程で一部のブロックがほとんど有効な特徴を学習しない可能性がある。つまり、ネットワークが極端に深くなると、すべての層が意味のある表現を学習しているとは限らない。

著者らはこの点を、Residual Network の「強みであると同時に弱点でもある構造的性質」として捉えており、深さそのものが表現力を保証するわけではないことを実験的に示している。

この指摘は、「より深いネットワークほど高性能である」という従来の直感に対する重要な反証である。Residual Network においては、深さは最適化の困難さを回避するための手段ではあるが、表現力を無制限に高める要因ではない。むしろ、過度な深さは計算効率の低下や、特徴が十分に活用されないという問題を引き起こす。

著者らが提案する Wide Residual Network は、この問題に対し「深さを抑え、幅を広げる」という設計変更によって対処している。幅を広げることで各 Residual Block が十分な表現能力を持ち、特徴が実際に活用されやすくなるため、少ない層数でも高い性能を実現できる。この結果は、ネットワーク設計において depth と width のバランスが本質的である ことを示唆している。

本文で扱った転移学習の実験においても、モデルの「深さ」そのものより、事前学習によって得られた有効な特徴表現をどのように活用するかが性能を左右していた。Wide ResNet の議論は、この観点をアーキテクチャ設計のレベルから理論的に裏付けるものであり、転移学習との親和性も高いと考えられる。


6. EfficientNet

従来の CNN スケーリングの課題

AlexNet 以降、CNN の性能向上は主に モデルのスケールアップ(深さ・幅・入力解像度の増大)によって達成されてきた。

しかし、従来はこれらの要素を個別に、かつ経験的に拡張していたため、

  • モデルの複雑化
  • パラメータ数・計算量(FLOPS)の急増
  • 計算資源コストの増大

といった問題が生じていた。

EfficientNet の基本思想

EfficientNet は 2019 年に提案された CNN モデル群であり、 深さ(Depth)・幅(Width)・解像度(Resolution)を同時に、かつ体系的にスケーリングする規則を導入した点が最大の特徴である。

この手法により、

  • 小さなモデルサイズ
  • 高い認識精度
  • 優れた計算効率

を同時に達成している。

Compound Scaling Method(複合スケーリング手法)

EfficientNet の中核となる考え方が Compound Scaling Method である。

CNN の計算量(FLOPS)は概ね、

  • 深さ $d$
  • 幅 $w$
  • 解像度 $r$

に対して、

$$ \text{FLOPS} \propto d \cdot w^2 \cdot r^2 $$

で増加する。

EfficientNet では、これら 3 つを 単一の係数 $\phi$ によって同時にスケーリングする。

$$ d = \alpha^\phi,\quad w = \beta^\phi,\quad r = \gamma^\phi $$

ここで、

  • $\alpha, \beta, \gamma$:グリッドサーチにより決定される定数
  • $\phi$:利用可能な計算資源に応じて設定するユーザー指定パラメータ

とされる。

原論文では、計算量制約として

$$ \alpha \cdot \beta^2 \cdot \gamma^2 \approx 2 $$

を課し、FLOPS がおおよそ $2^\phi$ に比例して増加するよう設計されている。

最適化問題としての定式化

Compound Scaling は、以下の最適化問題として定式化できる。

  • 目的:モデル精度を最大化
  • 制約:
    • メモリ使用量が上限以下
    • 計算量(FLOPS)が上限以下

この定式化により、与えられた計算資源の中で最も精度の高いモデル構成を体系的に探索 できる。

EfficientNet の性能特性

ImageNet を用いた評価では、EfficientNet 系モデルは、

  • 既存 CNN と比較して
    • パラメータ数が数倍〜1 桁少ない
    • 同等または高速な推論速度
    • より高い Top-1 精度

を達成している。

特に、ResNet-50 と比較した場合、EfficientNet-B4 は、同程度の計算量条件下で Top-1 error を約 6.3 ポイント低減しており、精度効率の大幅な改善が示されている。

また、EfficientNet は 転移学習においても高い性能を発揮 することが強調されており、 構造がシンプルかつ汎用性が高い点がその理由として挙げられている。

実装演習

なし。

自身の考察結果

EfficientNet の重要性は、新しいネットワーク構造そのものよりも、「モデルをどうスケールさせるべきか」という設計問題を理論的に整理した点 にあると考えられる。

従来の CNN では、深くすれば良い、幅を広げれば良い、高解像度を使えば良い、といった個別最適に陥りがちであった。

これに対し EfficientNet は、計算量の増加特性を明示的に捉えた上で、全体として最適な拡張を行う という枠組みを提示している。

また、Compound Scaling Method を最適化問題として定式化した点は、深層学習モデル設計を「経験」から「原理」に近づけた試みと捉えられる。

実務的な観点では、EfficientNet が転移学習に強いという点は特に重要であり、限られたデータ・計算資源の下でも高性能モデルを構築できる現実的な選択肢であると感じた。

参考図書 / 関連記事

In previous work, it is common to scale only one of the three dimensions – depth, width, and image size. Though it is possible to scale two or three dimensions arbitrarily, arbitrary scaling requires tedious manual tuning and still often yields sub-optimal accuracy and efficiency.

Scaling up any dimension of network width, depth, or resolution improves accuracy, but the accuracy gain diminishes for bigger models.

For example, ResNet-1000 has similar accuracy as ResNet-101 even though it has much more layers.

従来の畳み込みニューラルネットワークでは、計算資源が増えた場合に

  • 層を深くする(depth scaling)
  • チャネル数を増やす(width scaling)
  • 入力画像の解像度を上げる(resolution scaling)

といった 単一の次元のみを拡張する方法 が一般的であった。

しかし論文では、このような単一次元スケーリングは確かに初期段階では精度向上をもたらすものの、モデルが大きくなるにつれて 精度向上が頭打ちになる(diminishing returns) ことが、実験的に示されている。特に、層数を極端に増やした ResNet-1000 が、ResNet-101 とほぼ同等の精度しか得られない例は、深さのみを増やす設計の限界を端的に表している。

また、複数の次元を同時に拡張すること自体は可能であるが、その場合は手動での煩雑な調整が必要となり、必ずしも効率的・最適なモデルにはならない点も指摘されている。

この議論は、「モデルを大きくすれば精度は自然に向上する」という単純なスケーリング観に対する重要な反省を与える。特に深さ・幅・解像度のいずれか一つに偏った拡張は、計算コストを増大させる一方で、得られる精度向上は限定的となる。

本文で扱った EfficientNet の実装や転移学習では、あらかじめ設計された B0〜B7 系列を利用する形でスケーリングの結果のみを観察しているが、その背後には「単一次元スケーリングがなぜ非効率なのか」という体系的な検証が存在している。本論文の指摘は、EfficientNet が単に“性能の良いモデル群”であるだけでなく、従来の経験的スケーリング手法そのものを見直した結果として提案されている ことを明確に示している。

この点を踏まえると、EfficientNet の compound scaling は新奇な工夫というよりも、単一次元スケーリングの限界を実験的に突き詰めた結果として導かれた、合理的な設計指針であると理解できる。


7. Vision Transformer

Vision Transformer の基本的な考え方

Vision Transformer(ViT)は、自然言語処理向けに開発された Transformer を画像分類タスクへ応用したモデル である。

従来の CNN が畳み込み演算によって局所的特徴を段階的に抽出するのに対し、ViT では画像を 系列データ として扱い、Transformer Encoder によって全体的な関係性を学習する点が特徴である。

画像特徴量の系列化(トークン化)

ViT における最大の特徴は、画像をトークン系列へ変換する入力設計 にある。

処理の流れは以下の通りである。

  1. 入力画像を一定サイズ $P \times P$ のパッチに分割
  2. 各パッチを Flatten し、1 次元ベクトルとして扱う
  3. 線形変換または CNN によって Embedding 表現へ変換
  4. 系列の先頭に [CLS] Token を付加
  5. パッチの位置情報を表す Position Embedding を加算

この結果、入力系列長は パッチ数 $N$ + 1([CLS] Token) となる。

ViT のアーキテクチャ

ViT の中核は Transformer Encoder であり、構造自体は言語処理で用いられるものとほぼ同一である。

  • Multi-Head Self-Attention
  • MLP
  • Residual Connection
  • Layer Normalization

が層として積み重ねられ、最終層における [CLS] Token に対応する特徴量 が分類に用いられる。

分類は、Transformer Encoder の出力を MLP Head に入力することで行われる。

数式による処理の概要

入力画像 $\boldsymbol{x} \in \mathbb{R}^{H \times W \times C}$ をパッチ分割すると、

  • パッチ数:$N = HW / P^2$
  • パッチ行列:$\boldsymbol{X}_p \in \mathbb{R}^{N \times (P^2 C)}$

となる。

Embedding および Position Embedding を加えた初期入力は、

$$ \boldsymbol{Z}_0 = [\boldsymbol{x}_{\text{class}}; \boldsymbol{X}_p \boldsymbol{E}] + \boldsymbol{E}_{pos} $$

と表される。

Transformer Encoder の $l$ 層目では、

$$ \boldsymbol{Z}'_l = \text{MSA}(\text{LN}(\boldsymbol{Z}_{l-1})) + \boldsymbol{Z}_{l-1} $$

$$ \boldsymbol{Z}_l = \text{MLP}(\text{LN}(\boldsymbol{Z}'_l)) + \boldsymbol{Z}'_l $$

が計算される。

事前学習とファインチューニング

ViT は 大規模な教師ありデータセットによる事前学習 を前提とするモデルである。

講義資料で言及されている事前学習用データセットは以下である。

  • ImageNet(約 130 万枚)
  • ImageNet-21k(約 1,400 万枚)
  • JFT-300M(約 3 億枚)

ファインチューニングでは、

  • MLP Head を目的タスク用に置き換える
  • 入力解像度を高くする
  • パッチサイズは固定し、入力解像度変更に伴って Position Embedding を補間によって調整する

といった操作により、柔軟にタスク適応が可能である。

性能特性

講義資料の実験結果では、以下の傾向が示されている。

  • 事前学習データが大規模な場合
    • ViT は CNN(ResNet, BiT 等)より高性能
  • 事前学習データが小規模な場合
    • CNN の方が有利
  • 十分に大規模な事前学習データがある条件下では、同一計算量条件で ViT が CNN を上回る性能を示す場合がある
  • 大計算量領域では ViT の性能向上余地が大きい

このことから、ViT は データ規模依存性が強いモデル であることが分かる。

実装演習

なし。

自身の考察結果

Vision Transformer は、画像認識において長らく支配的であった CNN 中心の設計思想を大きく転換したモデル であると感じた。

特に、畳み込みによる局所性や平行移動不変性といった CNN 固有の image-specific inductive bias を弱くしか持たない 点は、ViT の本質的な特徴である。

この設計は一見すると不利に思えるが、講義資料が示す通り、十分に大規模なデータで事前学習を行えば、Transformer の表現力が CNN を上回る ことが確認されている。

すなわち、ViT は「構造による制約」ではなく、「データによる学習」に重きを置いたモデルであると解釈できる。

一方で、小規模データでは性能が低下する点から、ViT は万能ではなく、計算資源・データ規模・用途を見極めた上で選択すべきモデル であることも明確である。

画像をトークン系列として扱う発想は、その後の Swin Transformer などの発展にもつながっており、Vision Transformer は 画像認識における Transformer 系モデルの出発点 として極めて重要な位置づけを持つと考えられる。

参考図書 / 関連記事

  • 帰納バイアスの違いに関する記述(Section 3.1 Inductive bias)

“Vision Transformer has much less image-specific inductive bias than CNNs.”

“In CNNs, locality, two-dimensional neighborhood structure, and translation equivariance are baked into each layer throughout the whole model.”

“In ViT, only MLP layers are local and translationally equivariant, while the self-attention layers are global.”

“Other than that, the position embeddings at initialization time carry no information about the 2D positions of the patches and all spatial relations between the patches have to be learned from scratch.”

  • 大規模事前学習が重要である理由(Introduction)

“This seemingly discouraging outcome may be expected: Transformers lack some of the inductive biases inherent to CNNs.”

“However, the picture changes if the models are trained on larger datasets.”

“We find that large scale training trumps inductive bias.”

Vision Transformer における帰納バイアスとデータ規模の重要性

畳み込みニューラルネットワーク(CNN)は、局所性や平行移動不変性といった画像特有の帰納バイアスを構造的に内包している。一方、Vision Transformer(ViT)は、画像をパッチ列として扱い、自己注意機構を用いて処理するため、これらの画像特有の仮定をほとんど持たない。

論文中では、“Vision Transformer has much less image-specific inductive bias than CNNs” と明確に述べられており、ViT では空間的な関係性の多くを学習データから獲得する必要がある。その結果、中規模以下のデータセットでは CNN に劣る性能となるが、事前学習データを大規模化すると状況が一変する。

著者らは “large scale training trumps inductive bias” と述べ、大量データによる事前学習が、CNN に内在する帰納バイアスの不足を補って余りあることを示した。これは、モデル設計による制約よりも、データ規模そのものが性能を決定づける可能性を示唆しており、今後の画像認識モデル設計において重要な視点である。


8. 物体検知と SS 解説

8.1. 広義の物体認識タスクの整理

講義資料の冒頭では、画像認識タスクを以下のように整理している。

  • 画像分類(Classification)

    画像全体に対して 1 つまたは複数のクラスラベルを付与する。

  • 物体検知(Object Detection)

    画像中の複数物体について、

    • クラスラベル
    • 位置(Bounding Box)

    を同時に推定する。

  • 意味領域分割(Semantic Segmentation)

    各ピクセルにクラスラベルを割り当てるが、同一クラス内の個体は区別しない。

  • 個体領域分割(Instance Segmentation)

    各ピクセルにクラスラベルを割り当て、かつ個体ごとに区別する。

これらは「出力の粒度」と「個体識別の有無」によって体系的に位置づけられている。

8.2. 代表的データセット

物体検知における代表的データセットとして、以下が比較されている。

  • PASCAL VOC (VOC12)
  • ILSVRC Object Detection
  • MS COCO
  • Open Images Challenge (OID)

講義資料では特に、

  • クラス数
  • 1画像あたりのBounding Box数
  • 日常シーンとの親和性

といった観点から、目的に応じたデータセット選択の重要性 が強調されている。

8.3. 評価指標:Precision / Recall / IoU / AP / mAP

物体検知特有の評価指標が段階的に解説されている。

  • IoU(Intersection over Union)

$$ \mathrm{IoU} = \frac{\text{Area of Overlap}}{\text{Area of Union}} $$

  • Precision / Recall

$$ \mathrm{Precision} = \frac{TP}{TP + FP}, \quad \mathrm{Recall} = \frac{TP}{TP + FN} $$

物体検知では、

  • confidence の閾値
  • IoU の閾値

を同時に考慮する必要があり、クラス分類より評価が複雑になる。

  • AP(Average Precision) / mAP

PR 曲線の下面積として AP を定義し、 クラスごとの AP を算術平均したものが mAP である。

MS COCO では IoU 閾値を $0.5 \sim 0.95$(0.05刻み) で平均する指標が導入されている。

8.4. One-stage detector と Two-stage detector

物体検知フレームワークは大きく二系統に分類される。

  • Two-stage detector

    • 候補領域生成 → クラス推定を分離
    • 高精度だが計算量が大きく推論が遅い
    • 例:R-CNN 系、Faster R-CNN
  • One-stage detector

    • 候補領域生成とクラス推定を同時に実施
    • 高速だが、初期の手法では精度が低い傾向があった
    • 例:SSD、YOLO 系

このトレードオフは、応用要件(リアルタイム性 vs 精度)と直結する。

8.5. SSD(Single Shot MultiBox Detector)の概要

SSD は代表的な One-stage detector であり、

  • Default Box(Anchor)を多数配置

  • 各特徴マップから

    • クラス confidence
    • Bounding Box のオフセット

    を同時に出力する構造を持つ。

主な特徴として、

  • マルチスケール特徴マップ の利用
  • Hard Negative Mining
  • Non-Maximum Suppression
  • Localization loss(Smooth L1)と Confidence loss(Softmax Cross Entropy)の組み合わせ

などが挙げられている。

8.6. セマンティックセグメンテーション(SS)の基礎

後半では SS の基礎として、

  • FCN(Fully Convolutional Network)
  • Deconvolution(Transposed Convolution)
  • Skip Connection
  • U-Net
  • Dilated Convolution

などが紹介されている。

特に、

  • Pooling により失われた空間情報
  • 受容野拡大と解像度保持のトレードオフ

といった問題意識が一貫して示されている。

実装演習

なし。

自身の考察結果

本講義を通して、物体検知とセマンティックセグメンテーションは、単なるモデル設計の違いではなく、「何を評価対象とするか」という思想の違い に根差していると感じた。

特に印象的であったのは、IoU・AP・mAP に代表される評価指標の設計である。

物体検知では「正しく当てたか」だけでなく、

  • どの位置まで正確に捉えたか
  • どの順序で検出できたか

が評価に影響するため、モデル性能=数値の単純比較ではない という点が明確に示されている。

また、One-stage / Two-stage detector の比較からは、深層学習モデルが「万能解」ではなく、用途に応じて選択・設計される工学的対象 であることが強く感じられた。

SSD における Default Box や Hard Negative Mining は、 ネットワーク構造だけでなく 学習データの偏りや出力分布を前提とした設計であり、深層学習が経験則だけでなく、問題構造の深い理解に基づいて発展してきたことを再認識した。

セマンティックセグメンテーションの章では、Pooling による情報欠損という「CNN の成功の裏側にある問題」が丁寧に掘り下げられており、U-Net や Dilated Convolution が単なるテクニックではなく、表現力と計算効率のせめぎ合いの中で生まれた必然的な解法 であると理解できた。

参考図書 / 関連記事

本論文では、CNN によって生成された画像を検出するフォレンジックモデルが、特定の生成モデルに依存せず、異なるアーキテクチャやタスクにまで汎化可能である ことを示している。従来研究では、GAN 検出器は学習時に用いた生成モデル以外に対して性能が大きく低下すると報告されていたが、本研究はその見解に反する実験結果を提示している。

著者らは、ProGAN のみで生成した画像を用いて学習した2値分類器 であっても、StyleGAN、BigGAN、CycleGAN、さらには DeepFake 画像に対しても高い検出性能を示すことを確認した。この点について論文中では次のように述べられている。

“a standard image classifier trained on only one specific CNN generator (ProGAN) is able to generalize surprisingly well to unseen architectures, datasets, and training methods”

この汎化性能の要因として、論文では データ拡張(Data Augmentation)の重要性が強調されている。特に、Gaussian blur や JPEG 圧縮といった画像の後処理を学習時にシミュレートすること が、未知の生成モデルに対する検出性能を大きく向上させることが実験的に示されている。

“we find that data augmentation, in the form of common image post-processing operations, is critical for generalization, even when the target images are not post-processed themselves.”

さらに、データセットの「量」よりも「多様性」が重要である点も示されている。ProGAN において生成対象クラス数を増やすことで検出性能は向上するが、一定以上では改善が頭打ちになる。

“increasing the training set diversity improves performance, but only up to a point.”

これらの結果から、CNN 生成画像には モデルやタスクを超えて共通する低レベルな統計的特徴(いわゆる CNN fingerprints) が存在し、それを適切な学習設定によって捉えられる可能性が示唆されている。


9. Mask R-CNN

9.1. Mask R-CNN の位置づけ

Mask R-CNN は、Faster R-CNN を拡張したインスタンス・セグメンテーション手法 であり、物体検出(クラス分類+Bounding Box 回帰)に加えて、物体ごとの画素単位マスク推定 を同時に行うアルゴリズムである。

講義資料では、以下の関係性が明確に整理されている。

  • Faster R-CNN:
    • 物体検出(クラス + Bounding Box)
  • Mask R-CNN:
    • Faster R-CNN に マスク推定用ブランチ を追加した構造

この拡張により、同一クラス内の複数物体を区別した領域分割(インスタンス・セグメンテーション) が可能となる。

9.2. インスタンス・セグメンテーションの特徴

インスタンス・セグメンテーションは、

  • セマンティックセグメンテーション
    • 各ピクセルをクラス分類
    • 個体の区別はしない
  • インスタンス・セグメンテーション
    • 各ピクセルをクラス分類
    • 個体(インスタンス)ごとに区別

という違いを持つ。

Mask R-CNN は、「画像全体を一度にセグメンテーションする」のではなく、物体検出によって得られた候補領域(ROI)に対してのみマスク推定を行う ことで、計算効率と精度を両立している点が強調されている。

9.3. Mask R-CNN のネットワーク構造

講義資料の図では、Faster R-CNN と Mask R-CNN の違いが明確に示されている。

  • Faster R-CNN
    • 特徴マップ
      • クラス分類
      • Bounding Box 回帰
  • Mask R-CNN
    • 上記 2 系統に加えて
      • マスク推定(ピクセル分類)ブランチ

このマスクブランチは、ROI ごとに 固定サイズのマスク(例:$28 \times 28$) を出力し、各画素が対象物体に属するか否かを予測する。

9.4. RoI Pooling の課題

Fast / Faster R-CNN で用いられていた RoI Pooling では、

  • ROI 座標を整数に丸める
  • 特徴マップを粗く分割・集約する

という処理が行われる。この結果、

  • 位置ずれ(alignment error)
  • 空間情報の欠落

が生じ、高精度なマスク推定には不十分 であるという問題が指摘されている。

9.5. RoI Align の導入

Mask R-CNN の最も重要な技術的貢献の一つが、RoI Align の導入である。

RoI Align では、

  • ROI 座標を整数に丸めない
  • ROI を $N \times N$ に分割
  • 各領域で複数点をサンプリング
  • バイリニア補間により特徴量を算出

することで、位置ずれを抑えた高精度な特徴抽出 を実現している。

バイリニア補間は、周囲 4 点のピクセル値を用いて次式で計算される。

$$ f(x, y) = \frac{y_2 - y}{y_2 - y_1} f(x, y_1) + \frac{y - y_1}{y_2 - y_1} f(x, y_2) $$

この処理を各サンプリング点に対して行い、最終的に固定サイズの ROI 特徴マップを得る。

実装演習

なし。

自身の考察結果

Mask R-CNN の本質的な価値は、「物体検出」と「画素単位推定」という異なる粒度のタスクを、無理なく統合した点 にあると感じた。

特に印象的なのは、RoI Align の導入である。

これは単なる実装上の改善ではなく、Bounding Box ベースの検出結果に対するアライメント誤差を大幅に低減し、ピクセル単位のマスク予測を可能にした設計変更 であり、Mask R-CNN の性能を決定づける要素であると理解できる。

また、画像全体をセグメンテーションするのではなく、「物体らしさ」が高い領域のみに処理を限定する設計は、計算資源と精度のトレードオフを強く意識した工学的アプローチ である。

Mask R-CNN が ICCV2017 の Best Paper に選ばれた理由も、単に精度が高いからではなく、物体検知・セグメンテーションという長年の課題に対して、構造的に洗練された解決策を提示した点 にあると考えられる。

参考図書 / 関連記事

Mask R-CNN 論文では、インスタンスセグメンテーション性能向上のために RoIAlign という新しい演算が導入されている。

その背景には、従来の Faster R-CNN で用いられてきた RoIPool による空間量子化が、ピクセル精度を要求するマスク予測に不適切である という問題意識がある。

論文では、RoIPool の問題点について次のように述べられている。

“RoIPool first quantizes a floating-number RoI to the discrete granularity of the feature map… These quantizations introduce misalignments between the RoI and the extracted features.”

この量子化誤差は、分類やバウンディングボックス回帰では大きな問題にならない一方で、ピクセル単位の位置合わせが必要なマスク予測では致命的な精度低下を招く

実際、著者らは次のように指摘している。

“While this may not impact classification, which is robust to small translations, it has a large negative effect on predicting pixel-accurate masks.”

この問題を解決するために提案されたのが RoIAlign である。RoIAlign は、RoI の境界やビン分割において 一切の量子化を行わず, 双線形補間によって特徴量を取得する。

“We propose an RoIAlign layer that removes the harsh quantization of RoIPool, properly aligning the extracted features with the input.”

この設計変更は一見小さな改良に見えるが、実験結果では大きな効果を示している。論文中では次のように述べられている。

“Despite being a seemingly minor change, RoIAlign has a large impact: it improves mask accuracy by relative 10% to 50%, showing bigger gains under stricter localization metrics.”

さらに、アブレーション実験により、RoIAlign の効果は高い IoU 閾値(AP75)ほど顕著であり、インスタンス境界の精密な位置決めにおいて決定的な役割を果たしている ことが示されている。


10. Faster-RCNN, YOLO

10.1. 従来手法(R-CNN 系)のおさらいと課題

講義資料ではまず、R-CNN / Fast R-CNN における基本構成が整理されている。

  • R-CNN 系の基本構造

    1. 物体候補領域の提案(Selective Search)
    2. 各候補領域に対する CNN によるクラス分類
  • 課題

    • Selective Search による候補生成が計算コストのボトルネック
    • Fast R-CNN で分類部分は高速化されたが、候補生成は依然として遅い
    • 画像 1 枚あたりの処理時間が大きく、リアルタイム処理が困難

この課題を解決するために提案されたのが Faster-R-CNN である。

10.2. Faster-R-CNN の基本アイデア

Faster-R-CNN の最大の特徴は、物体候補領域の提案そのものを CNN によって学習させる 点にある。

  • Region Proposal Network(RPN)

    • 画像全体を CNN(例:VGG16)で特徴マップへ変換

    • 同一特徴マップを

      • 物体候補領域の生成
      • クラス分類・Bounding Box 回帰

      の双方で共有

    • End-to-End 学習が可能

これにより、従来の Selective Search を不要とし、大幅な高速化が実現された。

10.3. RPN と Anchor の仕組み

RPN では、特徴マップ上の各位置(Anchor Point)に対して複数の Anchor Box を仮定する。

  • 特徴マップサイズ:$H \times W$
  • 1 点あたりの Anchor 数:$S$
  • Anchor Box 総数:$H \times W \times S$

RPN の出力は以下の 2 種類である。

  • 各 Anchor が
    • 背景か物体か(2 クラス)
  • 各 Anchor に対する
    • Bounding Box の位置補正量($(x, y, w, h)$)

これにより、高精度な候補領域生成が高速に行える。

10.4. Faster-R-CNN の性能特性

講義資料の実験結果(PASCAL VOC)では、

  • R-CNN / Fast R-CNN より mAP が向上
  • 従来手法より 高速な推論

が示されており、精度と速度の両立 を実現した Two-stage detector であることが確認されている。

10.5. YOLO(You Only Look Once)の基本思想

YOLO(V1)は、Faster-R-CNN とは対照的な One-stage detector である。

  • 基本アイデア
    • 物体候補領域の提案とクラス分類を 1 つのネットワークで同時に処理
    • 画像全体を一度に入力し、一回の推論で検出結果を得る

この思想が「You Only Look Once」という名称の由来である。

10.6. YOLO(V1)の仕組み

YOLO(V1)では、入力画像を $S \times S$ の Grid Cell に分割する。

  • 各 Grid Cell について
    • $B$ 個の Bounding Box を予測
    • 物体クラスの確率を予測

出力次元は、

$$ S \times S \times (B \times 5 + C) $$

となる($C$:クラス数)。

各 Bounding Box について、

  • 中心座標 $(x, y)$
  • 幅・高さ $(w, h)$
  • 信頼度スコア(IoU に基づく)

が同時に出力される。

10.7. YOLO(V1)の利点と欠点

講義資料では以下の点が整理されている。

  • 利点
    • 非常に高速(リアルタイム処理が可能)
    • 画像全体を一度に見るため、背景誤検出が少ない
    • 汎化性能が高い
  • 欠点
    • 精度(mAP)は Faster-R-CNN に劣る傾向
    • 小物体の検出が苦手

10.8. YOLO(V1)の損失関数

YOLO(V1)の損失関数は、以下の要素の二乗誤差の和で構成される。

  • Bounding Box 中心座標 $(x, y)$ の誤差
  • Bounding Box サイズ $(w, h)$ の誤差
  • 信頼度スコアの誤差
    • 物体が存在する場合:IoU を教師信号
    • 存在しない場合:0
  • クラス確率の誤差

SSD との比較では、

  • YOLO:Bounding Box 形状は学習により最適化
  • SSD:事前定義された複数形状の Box を使用
  • YOLO v1:単一スケール
  • SSD:マルチスケール特徴マップ

といった違いが示されている。

実装演習

なし。

自身の考察結果

Faster-R-CNN と YOLO の対比は、物体検知における設計思想の違い を非常に分かりやすく示していると感じた。

Faster-R-CNN は、候補領域生成という従来のボトルネックを RPN によって解消し、Two-stage 構造を維持したまま精度と速度を両立 した点が本質的な貢献である。

特に、特徴マップを共有する設計は、深層学習的にも工学的にも洗練されている。

一方、YOLO はリアルタイム性を重視しつつ、物体検知を回帰問題として定式化することで、画像全体の文脈を同時に考慮できる構造を採用している。

この発想により、処理速度という明確な強みを獲得した一方で、小物体検出や局所的精度では限界があることも理解できる。

この講義内容から、物体検知モデルは 精度・速度・用途(リアルタイム性)という制約条件の中で選択されるべき技術 であり、単純な性能指標だけでは評価できないことを再認識した。

参考図書 / 関連記事

YOLO は物体検出を単一の回帰問題として定式化することで、極めて高速な検出を実現した一方、従来手法とは異なる誤差特性を持つことが原論文で詳細に分析されている。著者らは次のように述べている。

“YOLO still lags behind state-of-the-art detection systems in accuracy. While it can quickly identify objects in images it struggles to precisely localize some objects, especially small ones.”

この指摘が示す通り、YOLO の主な弱点は localization error(位置推定誤差) にある。原論文では Fast R-CNN との詳細な誤差内訳の比較が行われており、その結果が明確に対照的であることが示されている。

“YOLO struggles to localize objects correctly. Localization errors account for more of YOLO’s errors than all other sources combined. Fast R-CNN makes much fewer localization errors but far more background errors.”

すなわち、YOLO は物体の有無に関しては堅牢である一方、境界の精密な位置決めが不得意であり、Fast R-CNN はその逆に 背景誤検出(false positives on background) を多く含む傾向がある。実際、著者らは次のように述べている。

“Fast R-CNN is almost 3x more likely to predict background detections than YOLO.”

この誤差特性の違いは、YOLO を単独で評価するだけでは見落とされがちであるが、原論文では 両者を組み合わせる実験 が行われている点が興味深い。YOLO を Fast R-CNN の後段で用い、背景誤検出を抑制することで、検出性能が向上することが示されている。

“By using YOLO to eliminate background detections from Fast R-CNN we get a significant boost in performance.”

さらに重要なのは、この性能向上が単なるアンサンブル効果ではない点である。著者らは次のように明言している。

“The boost from YOLO is not simply a byproduct of model ensembling… Rather, it is precisely because YOLO makes different kinds of mistakes at test time that it is so effective.”

この結果は、YOLO が 高速検出器として独立した価値を持つだけでなく、誤差構造の異なる検出器としてシステム全体の性能向上に寄与しうる ことを示している。

講義本文で扱われた Faster R-CNN や YOLO の比較は主に「速度と精度のトレードオフ」に焦点が当てられていたが、原論文を通して見ると、YOLO はそれに加えて 検出誤差の性質そのものが他手法と異なる という重要な特徴を持つことが理解できる。

以上より、YOLO(V1)は単なる高速化手法ではなく、物体検出における誤差分布の多様性を示した点でも意義深い手法であり、その後の one-stage 検出器やハイブリッド構成の発展に重要な示唆を与えたと考えられる。


11. FCOS

FCOS(Fully Convolutional One-Stage Object Detection)は、アンカーボックスを用いない one-stage 物体検出手法 である。

従来の多くの物体検出モデル(Faster R-CNN、SSD、YOLOv2/v3、RetinaNet など)は、事前に定義した多数のアンカーボックスに依存していたが、FCOS はこの前提を排除している。

講義資料では、アンカーボックスの主な問題点として以下が整理されている。

  • アンカーのサイズ・アスペクト比・数といった ハイパーパラメータに性能が強く依存 する
  • 形状や向きが多様な物体、小物体への対応が難しい
  • 大多数がネガティブサンプルとなり、ポジティブ/ネガティブの不均衡 が生じる

FCOS ではこれらを回避するため、各ピクセルを物体中心候補とみなし、以下を直接予測する。

  • クラスラベル

  • バウンディングボックスを表す 4 次元ベクトル

    (左・右・上・下方向への距離:$l, r, t, b$)

この方式により、アンカー生成や対応付け処理が不要 となり、モデル構造が大幅に単純化されている。

ネットワーク構成としては、

  • Backbone
  • Feature Pyramid Network(FPN)
  • 共有された Head(分類・回帰・center-ness)

からなり、FPN を用いることで異なるスケールの物体を適切な特徴レベルで検出 できるよう設計されている。

また、FCOS では以下の工夫が重要な要素として紹介されている。

  • Center-ness

    物体中心からの距離を正規化したスカラー値を予測し、中心から離れた低品質な予測を抑制

  • 損失関数

    • 分類:Focal Loss
    • 回帰:IoU Loss
    • Center-ness:Binary Cross Entropy
  • 後処理は Non-Maximum Suppression(NMS)のみ

実験結果では、アンカーベース手法と同等、あるいはそれを上回る AP を達成しており、アンカーフリー手法の有効性 が示されている。

実装演習

なし。

自身の考察結果

FCOS の最も本質的な貢献は、アンカーフリー検出を、FPN ベースの高性能検出器として実用レベルに押し上げた点 にあると考えられる。

アンカーボックスは長年にわたり性能向上を支えてきた一方で、ハイパーパラメータ設計の複雑さやサンプル不均衡といった問題を内包していた。

FCOS は、これらを後処理や工夫で緩和するのではなく、問題の源そのものを取り除く設計 を選択している点が非常に印象的である。

特に、各ピクセルから直接 4 次元ベクトルを回帰するという発想は、物体検出を「候補選択問題」ではなく、より素直な回帰・分類問題として再定式化 していると捉えられる。

また、講義資料で示されている ambiguous sample(曖昧なサンプル)の議論から、FPN を組み合わせることでこの問題が実用上ほとんど影響しないレベルまで抑えられている点は重要である。

これは、マルチスケール表現が単なる性能向上だけでなく、学習の安定性にも寄与している ことを示唆している。

総じて FCOS は、

  • モデル設計の単純化
  • ハイパーパラメータ依存性の低減
  • アンカーベース手法に匹敵する精度

を同時に実現しており、後続のアンカーフリー検出器(CenterNet 系など)への流れを作った、設計思想として非常に影響力の大きい手法 であると考えられる。

参考図書 / 関連記事

FCOS はアンカーフリーな one-stage 物体検出器として高い性能を示しているが、その成立には 分類損失の設計 が重要な役割を果たしている。

本節では、FCOS 本文では詳細に扱われていない one-stage 検出器が本質的に抱えるクラス不均衡問題 と、それに対する Focal Loss の位置づけについて、原論文に基づいて整理する。

Focal Loss 原論文では、まず two-stage 検出器と one-stage 検出器の性能差の原因として、学習時の foreground / background の極端な不均衡 が指摘されている。

“We discover that the extreme foreground-background class imbalance encountered during training of dense detectors is the central cause.”

one-stage 検出器では、画像中の空間位置・スケール・アスペクト比を密にサンプリングするため、1 枚の画像あたり 数万〜数十万の候補位置 が生成される。その大半は背景であり、この状況が学習を困難にする。

“In practice this often amounts to enumerating ∼100k locations that densely cover spatial positions, scales, and aspect ratios.”

このような設定下では、通常の交差エントロピー損失を用いると、容易に分類できる背景例(easy negatives) が損失と勾配を支配してしまい、学習が不安定になることが説明されている。

“Easily classified negatives comprise the majority of the loss and dominate the gradient.”

two-stage 検出器では、この問題は構造的に回避されている。RPN などの提案生成段階により候補数が大幅に削減され、さらにミニバッチ内で foreground / background 比を制御することで、暗黙的にクラス不均衡が緩和されている。

“Two-stage detectors are often trained with the cross entropy loss … Instead, they address class imbalance through two mechanisms: (1) a two-stage cascade and (2) biased minibatch sampling.”

これに対し、Focal Loss は 損失関数そのものによって この問題を解決することを目的として提案されている。原論文では、交差エントロピー損失に $(1-p_t)^\gamma$ を掛けることで、正しく分類されたサンプルの寄与を抑制する手法が導入されている。

“We propose to reshape the loss function to down-weight easy examples and thus focus training on hard negatives.”

定義される Focal Loss は次式で与えられる。

$$ \mathrm{FL}(p_t) = - (1 - p_t)^\gamma \log(p_t) $$

この損失の重要な特徴は、誤分類されているサンプルに対しては交差エントロピーとほぼ同じ挙動を示しつつ、高い確信度で正しく分類されたサンプルの損失を急激に小さくする 点にある。

“As $p_t \to 1$, the factor goes to 0 and the loss for well-classified examples is down-weighted.”

原論文では、この性質により、one-stage 検出器において hard example に学習が集中し、従来の hard example mining よりも効果的であることが示されている。

“Our proposed focal loss naturally handles the class imbalance faced by a one-stage detector and allows us to efficiently train on all examples without sampling.”

以上の議論から、Focal Loss は RetinaNet に固有の工夫ではなく、one-stage 物体検出器が成立するための前提条件を理論的に与えた手法であることが分かる。

FCOS においても Focal Loss が採用されているのは、アンカーフリーという構造的簡素化と同時に、分類段階での不均衡問題を損失関数で直接制御する という思想が共有されているためである。

このように、Focal Loss 原論文は FCOS 本文で扱われたネットワーク構造とは異なる軸から、FCOS の性能を支える理論的背景を補足するものとして位置づけられる。


12. Transformer

12.1. 背景:Encoder–Decoder と Attention

講義資料ではまず、ニューラル機械翻訳における従来の Encoder–Decoder モデル(RNN) の問題点が整理されている。

入力文全体を 単一ベクトル に圧縮するため、

  • 文長が長くなると表現力が不足
  • 文長の増加に伴い翻訳精度(BLEU)が低下

この問題を緩和するために導入されたのが Attention 機構(Bahdanau et al., 2015) である。

Attention では、翻訳先の各単語生成時に、翻訳元文中の 全単語の隠れ状態の加重和 を用いる。

$$ c_i = \sum_{j=1}^{T_x} \alpha_{ij} h_j $$

ここで、重み $\alpha_{ij}$ は softmax により正規化される。

Attention により、

  • 文長が長くなっても翻訳精度が大きく低下しない

ことが実験的に示されている。

12.2. Transformer の基本思想

Transformer は Vaswani et al. (2017) により提案されたモデルであり、講義資料では以下の点が強調されている。

  • RNN を一切用いない
  • 再帰構造を用いず、Attention を中核とした構成を採用
  • Self-Attention によって系列全体の依存関係を同時に処理
  • 当時の SOTA を、並列化により 大幅に短い学習時間 で達成

実際に、英仏翻訳(約 3600 万文)を 8 GPU・3.5 日で学習できたことが示されている。

12.3. Transformer の全体構造

Transformer は以下から構成される。

  • Encoder
  • Decoder
  • 各層で共通の構成要素:
    • Multi-Head Self-Attention
    • Position-Wise Feed Forward Network
    • Residual Connection(Add)
    • Layer Normalization(Norm)

Encoder・Decoder はそれぞれ 同一構造の層を $N$ 層(原論文では 6 層) 積み重ねた構造である。

12.4. Self-Attention と Scaled Dot-Product Attention

Self-Attention は、入力系列自身を Query / Key / Value として扱う注意機構 である。

Attention は以下の式で定義される。

$$ \mathrm{Attention}(Q, K, V) = \mathrm{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V $$

  • $Q$:Query
  • $K$:Key
  • $V$:Value
  • $d_k$:Key の次元数

$\sqrt{d_k}$ によるスケーリングは、内積値の分散増大を防ぎ、学習を安定させるためである。

12.5. Multi-Head Attention

Multi-Head Attention では、

  • 異なる重み行列を持つ複数の Attention(原論文では 8 ヘッド)
  • 各ヘッドの出力を結合(Concat)し線形変換

を行う。

これにより、

  • 異なる観点(文法関係・意味的関係など)を同時に捉える

ことが可能となる。

12.6. Position-Wise Feed Forward Network

Attention 層の出力に対して、位置ごとに独立な 2 層の全結合ネットワーク を適用する。

$$ \mathrm{FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2 $$

  • 各単語位置で同一の重みを使用
  • 系列構造を保ったまま非線形変換を行う

12.7. Position Encoding

Transformer は RNN を用いないため、語順情報を明示的に与える必要がある

講義資料では、正弦波・余弦波を用いた Position Encoding が紹介されている。

$$ \mathrm{PE}(pos, 2i) = \sin\left(\frac{pos}{10000^{2i/d}}\right), \quad \mathrm{PE}(pos, 2i+1) = \cos\left(\frac{pos}{10000^{2i/d}}\right) $$

この設計により、

  • 任意長の系列に対応可能
  • 相対位置情報を滑らかに表現可能

となる。

12.8. Decoder における Masked Self-Attention

Decoder では、

  • 自己注意機構
  • Encoder–Decoder Attention

の 2 種類の Attention を用いる。

特に自己注意機構では、未来の単語を参照しないようにマスク処理 が行われる点が重要である。

実装演習

なし。

自身の考察結果

Transformer の本質的な革新は、系列処理から「時間方向の逐次性」を完全に排除した点 にあると考えられる。

RNN では計算が時系列に依存するため並列化が困難であったが、Self-Attention によって 全単語間の関係を一度に計算できる構造 を採用したことで、計算効率と表現力を同時に向上させている。

また、Attention を「辞書オブジェクト(Query–Key–Value)」として捉える講義資料の説明は、Transformer の動作原理を直感的に理解する上で非常に有効であると感じた。

一方で、Self-Attention は計算量が系列長の二乗に比例するため、長系列への適用には限界があることも読み取れる。

この点は、後続の BERT や Longformer 系モデルで改良されていく重要な課題である。

総じて Transformer は、後続の BERT・GPT・Vision Transformer など、ほぼすべての現代的深層学習モデルの基盤となるアーキテクチャ であり、本講義はその思想的・数理的核心を理解する上で非常に重要な位置づけであると考えられる。

参考図書 / 関連記事

Transformer では Attention がモデルの中心的構成要素として用いられているが、その発想は突如現れたものではない。本節では、Transformer 以前の RNN Encoder–Decoder が抱えていた 固定長ベクトルによる情報圧縮の限界 と、それを解決するために導入された Attention 機構の原初的動機 に焦点を当てる。

Bahdanau らは、従来の Encoder–Decoder 型ニューラル機械翻訳が、入力文全体を 単一の固定長ベクトルに圧縮すること自体がボトルネック であると明確に指摘している。

“We conjecture that the use of a fixed-length vector is a bottleneck in improving the performance of this basic encoder–decoder architecture.”

この問題は、特に文が長くなるにつれて顕在化する。実際、先行研究の結果を踏まえ、入力文長の増加に伴い性能が急激に劣化することが報告されている。

“This may make it difficult for the neural network to cope with long sentences, especially those that are longer than the sentences in the training corpus.”

この課題に対し、著者らは Encoder–Decoder の枠組みを拡張し、翻訳時に入力文のどの部分が重要かを逐次探索する仕組み を導入した。これが、後に「Attention」と呼ばれる考え方である。

“We propose to extend this by allowing a model to automatically (soft-)search for parts of a source sentence that are relevant to predicting a target word.”

この新しい枠組みの本質は、Encoder が文全体を一つのベクトルに潰すのではなく、入力系列をベクトル列として保持 し、Decoder が必要に応じてそれらを参照する点にある。

“It does not attempt to encode a whole input sentence into a single fixed-length vector. Instead, it encodes the input sentence into a sequence of vectors and chooses a subset of these vectors adaptively while decoding the translation.”

ここで導入される context vector は、各時刻で異なり、入力系列上の注目度(alignment)に基づく重み付き和として定義される。

“The context vector ci is computed as a weighted sum of these annotations.”

また、この alignment は従来の統計的機械翻訳における「潜在変数」ではなく、微分可能な soft-alignment として直接学習される点が重要である。

“The alignment model directly computes a soft alignment, which allows the gradient of the cost function to be backpropagated through.”

この結果、モデルは長文に対しても安定した性能を示すようになり、著者らは次のように結論付けている。

“This frees the model from having to encode a whole source sentence into a fixed-length vector, and also lets the model focus only on information relevant to the generation of the next target word.”

以上の議論から、Attention 機構は「性能向上のための付加的工夫」ではなく、固定長表現という構造的制約を回避するために必然的に導入された概念であることが分かる。

Transformer は RNN という時間的再帰構造を捨てた一方で、この 「入力系列全体を保持し、必要な部分を参照する」 という思想を純化・一般化したモデルと位置づけられる。


13. BERT

13.1. 背景:事前学習の重要性と従来手法の整理

講義資料ではまず、自然言語処理(NLP)において 事前学習(Pre-training)が極めて有効である ことが整理されている。

NLP タスクは大きく以下に分けられる。

  • 文レベルのタスク
    • 文同士の関係理解(文類似度、言い換え、自然言語推論など)
  • トークンレベルのタスク
    • 各単語に対する出力(NER、QA など)

事前学習の代表的アプローチは以下の 2 種類である。

  • Feature-based アプローチ
    • Word2Vec、GloVe、ELMo など
    • 事前学習済み表現を特徴量として利用
  • Fine-tuning アプローチ
    • 言語モデルを事前学習し、下流タスクで全体を再学習
    • OpenAI GPT など(単方向 Transformer)

これらを踏まえ、Masked Language Modeling により、双方向文脈情報を利用可能なモデル として提案されたのが BERT である。

13.2. BERT の基本構造

BERT(Bidirectional Encoder Representations from Transformers)は、

  • Transformer Encoder のみを積み重ねたモデル
  • 完全な 双方向 Transformer
  • 入力と出力がともに tensor(系列全体を同時に処理)

という特徴を持つ。

BERT では、従来の言語モデルのような「次単語予測」をそのまま用いると、双方向性により 未来情報のリーク(カンニング)が生じる ため、事前学習タスク自体に工夫が加えられている。

13.3. 入力表現(Embedding)

BERT の入力は、3 種類の Embedding の和として構成される。

  1. Token Embedding
    • WordPiece によるトークン化結果
  2. Position Embedding
    • 系列長 1〜512 の位置情報
  3. Segment Embedding
    • 文 A / 文 B の区別(文章ペア入力)

文の先頭には [CLS] トークン、文の区切りには [SEP] トークンが付与される。

この設計により、単文タスク・文ペアタスクの双方を同一モデルで扱う ことが可能となっている。

13.4. 事前学習タスク①:Masked Language Model(MLM)

BERT の中核となる事前学習タスクの 1 つが Masked Language Model である。

  • 入力文中の 15% の単語 をランダムに選択
    • 80%:[MASK] に置換
    • 10%:ランダムな別単語に置換
    • 10%:そのまま保持
  • 選択された位置の 元の単語を予測

この方式により、

  • 双方向文脈を利用した表現学習が可能
  • CBOW 的な発想で文脈全体を活用

一方で、学習に利用される単語が全体の 15% に限られるため、 学習コストが大きい 点も指摘されている。

13.5. 事前学習タスク②:Next Sentence Prediction(NSP)

もう 1 つの事前学習タスクが Next Sentence Prediction である。

  • 2 文からなる文章ペアを入力
  • 50% の確率で
    • 実際に隣接する文
    • 無関係な文(シャッフル)
  • 「文 B は文 A の直後か?」を二値分類

これにより、

  • 文間関係
  • 推論・QA・自然言語推論タスクに必要な情報

を事前に学習できる。

13.6. 事前学習および Fine-tuning

事前学習では以下が用いられている。

  • データセット
    • BooksCorpus(約 800MB)
    • English Wikipedia(約 2.5GB)
  • 最大系列長:512
  • バッチサイズ:256
  • 最適化手法:Adam
  • 活性化関数:GELU

Fine-tuning 時には、

  • [CLS] トークンの出力を用いた文分類
  • トークン単位の出力を用いた NER
  • 開始・終了位置を予測する QA

など、タスク固有の小さな出力層を追加するだけ で対応可能である。

13.7. 有効性と実験結果

講義資料では、BERT が 8 つの主要 NLP ベンチマーク で SOTA を達成したことが示されている。

  • GLUE
  • MNLI
  • QQP
  • QNLI
  • SST-2
  • CoLA
  • STS-B
  • MRPC
  • SQuAD
  • CoNLL-2003
  • SWAG

特筆すべき点は、タスクごとに特別なアーキテクチャを設計せず、ほぼ同一構造で高性能を達成している 点である。

実装演習

4_6_bert.ipynb を実行する。

そのままだとエラーが発生するため、以下のコードを追加する。

!pip -q install unidic-lite
import unidic_lite

stage_4_06.png

テキスト

「楽しい勉強でした。」

を入力した際の出力は、3 クラスに対する確率分布として

[0.189, 0.679, 0.132]

のように得られている。

この結果から分かることは以下の通りである。

  • 出力は one-hot ではなく、確率分布として正しく計算されている
  • 特定のクラスに偏りすぎず、ある程度の差を持った確率が出力されている
  • 完全に一様分布(約 0.33, 0.33, 0.33)になっていないことから、 分類層はランダムではなく、BERT の表現を利用できている

一方で、この確率の意味づけには注意が必要である。

  • 学習データは「作品単位」でラベル付けされており、文単体の感情や内容を直接学習しているわけではない
  • 入力文は短く、作品固有の文体的特徴をほとんど含まない

したがって、この出力は 「文がどの作品に近いか」を弱く推定した結果 に過ぎず、意味的に強い解釈を与えるべきものではない。

自身の考察結果

BERT の最も重要な意義は、「事前学習+Fine-tuning」という枠組みを NLP の標準に押し上げた点 にあると考えられる。

特に、Masked Language Model によって双方向文脈を自然に学習できる設計は、従来の単方向言語モデルや擬似的双方向モデルと比較して、表現力の質を大きく向上させている。

また、入力表現における [CLS] トークンの導入は、「系列全体を代表するベクトル」という概念を明確にし、多様な下流タスクへの適用を極めて容易にしている点で非常に優れている。

一方で、MLM による学習効率の低さや計算資源の大きさは明確な課題であり、後続の RoBERTa、ALBERT、ELECTRA などがこの点を改良している流れも自然に理解できる。

総じて BERT は、単なる高性能モデルではなく、NLP におけるモデル設計・学習パラダイムそのものを転換した画期的手法 であり、本講義はその核心を体系的に理解するための重要な位置づけにあると感じた。

参考図書 / 関連記事

BERT は文脈依存型の単語表現を Transformer によって実現したモデルであるが、その基礎となる発想は ELMo において既に明確に提示されている。

本節では、ELMo 原論文に基づき、「文脈依存表現とは何か」、および 「層ごとに異なる情報を持つ表現」 という観点から、BERT を位置づけ直す。

ELMo は、従来の Word2Vec や GloVe のような 文脈非依存(static)な単語埋め込み の限界を問題として提起している。論文では、単語表現が満たすべき条件として次の 2 点が挙げられている。

“They should ideally model both (1) complex characteristics of word use (e.g., syntax and semantics), and (2) how these uses vary across linguistic contexts (i.e., to model polysemy).”

従来の単語埋め込みでは、同一単語は常に同一ベクトルで表現されるため、多義語(polysemy)を自然に扱うことができない。この問題に対し、ELMo は 文全体を条件とした表現 を各トークンに割り当てる。

“Our representations differ from traditional word type embeddings in that each token is assigned a representation that is a function of the entire input sentence.”

この文脈依存性は、双方向言語モデル(biLM)によって実現される。ELMo では、入力文を一つの固定長ベクトルに圧縮するのではなく、系列全体を保持したまま各位置の表現を計算する。

“It does not attempt to encode a whole input sentence into a single fixed-length vector. Instead, it encodes the input sentence into a sequence of vectors…”

さらに ELMo の重要な特徴は、最上位層のみを使うのではなく、全層の内部状態を利用する 点にある。論文では、各層の表現をタスクごとに重み付けして線形結合する手法が導入されている。

“More specifically, we learn a linear combination of the vectors stacked above each input word for each end task…”

この設計により、ELMo は層ごとに異なる性質の情報を保持することが示されている。実験的分析から、低層では主に統語的情報、高層では意味的情報が表現されていることが確認されている。

“The higher-level LSTM states capture context-dependent aspects of word meaning… while lower-level states model aspects of syntax.”

この知見は、BERT を理解する上でも重要である。BERT もまた Transformer Encoder の複数層から構成されており、下流タスクでは どの層の表現を利用するか が性能に影響することが知られている。ELMo は、こうした「層ごとの役割分化」という考え方を、BERT 以前に明確に示したモデルであると位置づけられる。

以上より、ELMo は単なる BERT 以前の手法ではなく、文脈依存表現・層構造・事前学習表現の再利用 という点において、BERT へと直結する重要な前史を成している。本章で扱った BERT の実装演習は、この流れの延長線上にあるものとして理解できる。


14. GPT

14.1. GPT の位置づけと基本概念

GPT(Generative Pre-Training)は、大規模コーパスを用いた事前学習と転移学習を前提とする自然言語モデル である。

講義資料では、BERT と並ぶ代表的な事前学習モデルとして GPT-n 系列(GPT, GPT-2, GPT-3)が紹介されている。

GPT の基本的な特徴は以下の通りである。

  • Transformer を基盤とする言語モデル
  • 次単語予測 による自己教師あり学習
  • 事前学習と転移学習で 同一のモデル構造 を用いる
  • 大規模化により性能が大きく向上する

特に GPT-3 では、1750 億パラメータ という極めて大規模なモデルが用いられている。

14.2. GPT のモデル構造

GPT は Transformer の Decoder 部分のみ を用いたモデルである。

  • Masked Multi-Head Self-Attention
  • Position-wise Feed Forward Network
  • Residual Connection
  • Layer Normalization

からなる Transformer Decoder を多層に積み重ねる構造を持つ。

入力は、

  • 単語埋め込み
  • 位置エンコーディング

の和として表現され、未来の単語を参照しないようにマスク処理 が施されている点が重要である。

14.3. GPT の事前学習(Language Modeling)

GPT の事前学習は、次単語予測確率を最大化する ことを目的として行われる。

目的関数は概念的に次のように表される。

$$ \max_{\theta} \sum_{i} \log P(x_i \mid x_{i-k}, \ldots, x_{i-1}; \theta) $$

ここで、

  • $x_i$:予測対象の単語
  • $k$:コンテキストウィンドウサイズ
  • $\theta$:モデルパラメータ

である。

Transformer Decoder の最終出力に対して、

  • 埋め込み行列の転置との積
  • softmax 関数

を適用することで、語彙全体に対する次単語確率分布を得る。

14.4. GPT-1 の Fine-tuning と転移学習

GPT-1 では、事前学習後に タスクごとの Fine-tuning が行われる。

講義資料では、以下のようなタスクが図解されている。

  • テキスト分類
  • 文含意(Entailment)
  • 文類似度
  • 質問応答
  • 多肢選択問題

これらは、

  • 開始記号
  • 文区切り記号
  • 終了記号

を工夫して入力に与え、 Transformer の出力を線形結合+softmax によってタスク固有の出力へ変換する。

14.5. GPT-2 / GPT-3 における拡張

GPT-2 以降では、基本構造は維持したまま以下の点が変更されている。

  • Layer Normalization の位置調整
  • バッチサイズ・データセット規模の拡大
  • 層数・埋め込み次元・Attention Head 数の増加
  • コンテキストウィンドウの拡張

GPT-3 では Fine-tuning を行わない 点が大きな特徴であり、推論時の入力工夫のみでタスク適応を行う。

14.6. GPT-3 の推論方式:Zero-shot / One-shot / Few-shot

GPT-3 の推論は、以下の 3 つに分類される。

  • Zero-shot
    • タスクの説明のみを与えて推論
  • One-shot
    • タスク説明+1 つの例を与えて推論
  • Few-shot
    • タスク説明+複数例を与えて推論

いずれも 勾配更新を行わない点が共通しており、「モデル内に蓄積された知識を、入力(プロンプト)によって引き出す」方式である。

14.7. Prompt-based Learning

後半では、GPT を代表とする大規模言語モデルを前提とした Prompt-based Learning が紹介されている。

  • Prompt(指示文)を入力として与えることでタスクを指定
  • モデル本体を固定したまま利用可能
  • Prompt Tuning により、プロンプト自体を学習可能

さらに、

  • Chain-of-Thought Prompting

により、推論過程を明示させることで性能が向上する例が示されている。

実装演習

なし。

自身の考察結果

GPT の本質的な革新は、言語モデルを極限までスケールさせることで、Fine-tuning を行わずに多様なタスクに対応可能であることを示した点 にあると考えられる。

BERT が「事前学習+Fine-tuning」を標準化したのに対し、GPT-3 は 学習済みモデルと入力設計(プロンプト)のみで多様なタスクに対応可能 であることを示した。

これは、モデル内部に獲得された知識表現が、タスク横断的に利用可能であることを示唆している。

一方で、講義資料でも指摘されている通り、

  • 学習・運用コストの大きさ
  • 社会的悪用リスク
  • 常識・物理推論の弱さ

といった課題も明確である。

Prompt-based Learning や Chain-of-Thought は、これらの制約の中で モデル構造を変えずに性能を引き出す工学的工夫 として非常に重要であり、 今後の大規模言語モデル活用の中心的手法になると考えられる。

総じて GPT は、「モデルを学習させて使う」段階から「学習済みモデルをどう使いこなすか」へとパラダイムを転換した存在 であり、本講義はその転換点を理解する上で重要な内容である。

参考図書 / 関連記事

GPT をはじめとする大規模言語モデルは、多くのタスクで高い性能を示す一方、複雑な推論を要する問題では性能が不安定になる ことが知られている。

本節では、モデルの構造や学習方法を変更せずに、プロンプトの与え方のみで推論性能を向上させる手法 として提案された Chain-of-Thought(CoT)Prompting に注目する。

原論文では、まず従来の Few-shot Prompting の限界について次のように述べられている。

“Standard prompting methods—providing only input-output examples—often fail to elicit reasoning in large language models.”

すなわち、入力と最終的な出力のみを例示する従来手法では、モデルが内部で行っている推論過程を十分に引き出せないという問題がある。

これに対し、Chain-of-Thought Prompting では、最終答えに至るまでの中間的な推論過程を明示的に含めた例 をプロンプトとして与える。

“Chain-of-thought prompting involves providing a few examples where each example is a question paired with a chain of thought that leads to the answer.”

この「chain of thought」とは、人間が問題を解く際に用いる段階的な思考過程に相当するものであり、数式処理や論理推論を含むタスクで特に有効であることが示されている。

原論文では、算数問題や常識推論などのベンチマークにおいて、CoT Prompting が性能を大きく向上させることが報告されている。

“We find that chain-of-thought prompting significantly improves the ability of large language models to perform complex reasoning.”

重要なのは、この性能向上が モデルの再学習やパラメータ更新を伴わない 点である。著者らは次のように強調している。

“These improvements are achieved without any fine-tuning or additional training of the model.”

つまり、Chain-of-Thought Prompting は、GPT の内部表現や潜在的な推論能力を新たに獲得させるものではなく、既に獲得されている能力を適切に引き出す手法 であると解釈できる。

さらに原論文では、この手法が特に 大規模モデルで顕著に効果を示す ことが指摘されている。

“Chain-of-thought prompting emerges as an ability of sufficiently large language models.”

この結果は、モデル規模の拡大に伴い、言語モデルが内部により豊かな推論構造を形成している可能性を示唆している。

以上より、Chain-of-Thought Prompting は、GPT を「単なる文章生成モデル」としてではなく、推論能力を持つ汎用的言語モデルとして利用するための実践的手法として位置づけられる。

本章で扱った GPT の基本構造や Few-shot 学習に対し、CoT Prompting は 「どのように使うか」 という新たな視点を与えるものであり、GPT の応用範囲を大きく拡張する重要な概念である。


15. 音声認識

15.1. 音声データと音声認識の位置づけ

音声認識は、人間の発話音声を入力として、その内容を文字列として認識する技術 であり、スマートスピーカー、音声アシスタント、自動議事録など、実用的な応用が広く存在する。

近年では、Kaggle などにおいて音声データを対象とした機械学習コンペティションも多数開催されており、音声認識は研究・産業の両面で重要性が高まっている。

15.2. 音声データの物理的性質

音は、物体の振動によって生じる空気の振動 として伝播する波であり、音声データは時間に対する 連続信号(波形) として表される。

音波の基本的な特徴は以下である。

  • 振幅:音の大きさ
  • 波長(周波数):音の高さ

周波数は「1 秒あたりの振動回数」で定義され、角周波数はこれをラジアンで表現したものである。

15.3. 波形のデジタル化:標本化と量子化

機械学習で音声を扱うためには、連続時間信号を離散的なデータへ変換する必要がある。

  • 標本化(サンプリング)

    連続時間信号を一定時間間隔で切り出す操作

  • 量子化

    振幅を有限個の値に丸める操作

標本化には サンプリング定理 があり、最大周波数 $h$ を含む信号を正しく表現するには、少なくとも $2h$ のサンプリング周波数が必要となる。

15.4. フーリエ変換による周波数表現

音声波形は時間領域で表現されるが、機械学習では 周波数領域での特徴量 が有効となることが多い。

フーリエ変換は、時間領域の波形 $f(t)$ を、周波数領域の関数 $F(\omega)$ へ変換する操作であり、「どの周波数成分が、どれだけ含まれているか」を表現できる。

この結果得られる周波数分布は スペクトル と呼ばれる。

15.5. スペクトログラム

現実の音声は非周期的であるため、単純なフーリエ変換だけでは時間変化を捉えられない。

そこで、

  • 波形を短い時間区間(窓)に分割
  • 各区間ごとにフーリエ変換

を行うことで得られるのが スペクトログラム である。

スペクトログラムは、

  • 横軸:時間
  • 縦軸:周波数
  • 輝度:振幅

として表され、音声認識における基本的な入力表現となる。

15.6. 窓関数とその役割

スペクトログラム作成時には、窓の切れ目による不連続性を抑えるため 窓関数 を用いる。

代表的な窓関数として、

  • 矩形窓
  • ハミング窓

が紹介されており、特にハミング窓は、窓の端を滑らかにすることで周波数解析の精度を高める。

15.7. DFT・FFT と計算効率

離散フーリエ変換(DFT)は計算量が大きいため、実用上は 高速フーリエ変換(FFT) が用いられる。

FFT では、

  • サンプル数を 2 の冪乗とする
  • 偶数番目・奇数番目を分割して計算

といった工夫により、大幅な高速化が実現されている。

15.8. その他の音声特徴量

講義資料では、以下の補助的技術も紹介されている。

  • メル尺度

    人間の聴覚特性を反映した周波数尺度

  • 逆フーリエ変換

    周波数情報から波形を再構成

  • ケプストラム

    フーリエ変換後の対数振幅を逆フーリエ変換した特徴量 音声認識で広く用いられる

実装演習

なし。

自身の考察結果

本講義を通して、音声認識は単に深層学習モデルを適用する問題ではなく、信号処理としての前処理が極めて重要な分野 であると強く感じた。

特に、標本化・量子化・フーリエ変換・スペクトログラムといった処理は、音声の本質的な情報をどのように抽出し、機械学習が扱いやすい形へ変換するかという問題に直結している。

また、メル尺度やケプストラムが人間の聴覚特性を反映して設計されている点から、音声認識は 物理・生理・情報処理が密接に結びついた領域 であることが理解できる。

深層学習の進展により、エンドツーエンドで音声波形を扱う手法も登場しているが、本資料で扱われた基礎的な信号処理の理解は、モデルの挙動を解釈し、適切に設計する上で今後も不可欠であると考えられる。

参考図書 / 関連記事

音声認識において重要なのは、波形やスペクトログラムを計算できること自体ではなく、認識に必要な情報を保持し、不要な変動を抑える特徴量表現を選択すること である。

Davis と Mermelstein は、音声認識システム設計における中心課題として、音響データの表現選択を明確に位置づけている。

“The selection of the best parametric representation of acoustic data is an important task in the design of any speech recognition system.”

本論文では、複数の音響特徴量を理論的に議論するのではなく、実際の単語認識性能によって比較・評価 している点が特徴である。

メル周波数ケプストラム係数(MFCC)を含む複数の表現を用い、動的時間伸縮(dynamic warping)による単語認識実験を行った結果、MFCC が最も高い性能を示したことが報告されている。

“Several parametric representations of the acoustic signal were compared with regard to word recognition performance …” “A set of ten mel-frequency cepstrum coefficients computed every 6.4 ms resulted in the best performance …”

著者らは、この性能差の理由を、人間の聴覚特性を反映した周波数表現に求めている。MFCC が優れているのは、短時間音声スペクトルのうち、知覚的に重要な成分をより適切に表現できるため であると述べられている。

“The superior performance of the mel-frequency cepstrum coefficients may be attributed to the fact that they better represent the perceptually relevant aspects of the short-term speech spectrum.”

さらに、周波数軸をメル尺度に変換する設計は、人間の聴覚における臨界帯域幅が周波数によって変化するという性質に基づいている。低周波では線形に、高周波では対数的に分解能を落とすことで、認識に不要な細かな変動を抑える効果がある。

“Because of the known variation of the ear’s critical bandwidths with frequency … filters spaced linearly at low frequencies and logarithmically at high frequencies have been used …”

論文ではまた、フーリエスペクトル由来の特徴量と線形予測(LPC)由来の特徴量との差異にも言及されており、特に 子音間の混同 が認識誤りの主因であることが指摘されている。MFCC はこの点で有利であり、高周波帯域における無意味な変動を抑制できることが強調されている。

“Both spectral representations are considered adequate for vowels. However, it is the confusions between the consonants that are most frequent.” “MFCC allow better suppression of insignificant spectral variation in the higher frequency bands.”

以上より、本論文は本文で扱ったフーリエ変換、スペクトログラム、メル尺度、ケプストラムといった信号処理手法を前提としつつ、それらをどのように組み合わせた表現が音声認識に実際に有効であるか を、認識性能という観点から示した研究である。信号処理の手順そのものではなく、「特徴量設計の妥当性」を補足する参考文献として位置づけられる。


16. CTC

16.1. 音声認識における CTC の位置づけ

音声認識(ASR)は、音声信号を特徴量系列へ変換し、その系列から対応する文字列(単語列)を推定するタスクである。

従来は、

  • 音響モデル
  • 発音辞書
  • 言語モデル

という 3 モジュール構成 が主流であったが、実装が複雑であり、特にデコーディング処理が高い障壁となっていた。

CTC は、この問題に対して提案された End-to-End 型の音声認識手法 であり、HMM を用いず、DNN(主に RNN / LSTM)のみでラベル系列の確率を直接モデル化 する点が特徴である。

16.2. CTC の核心的アイデア

CTC の重要な発明は、以下の 2 点に集約される。

  • ブランク(blank)ラベルの導入
  • 前向き・後ろ向きアルゴリズム(forward-backward algorithm)による学習

CTC は、音声のフレーム数と文字列長が一致しないという問題(アライメント問題)を、明示的なアライメントを与えずに解決 する枠組みを提供している。

16.3. ブランク(blank)の役割と縮約

RNN に音声系列を入力すると、フレーム数と同じ長さのラベル系列が出力される。

CTC では、通常の文字ラベルに加えて ブランク(−) を導入する。

出力されたフレーム単位のラベル系列は、次の 2 手順で最終的な文字列へ変換(縮約)される。

  1. 連続する同一ラベルを 1 つにまとめる
  2. ブランクを削除する

この仕組みにより、

  • 同一文字の連続(例:「aa」)
  • 音声中の無音区間や曖昧な境界

を自然に表現できる。

ブランクの導入により、厳密なフレーム―文字対応を強制しない学習 が可能となっている。

16.4. CTC における確率モデル

入力音声系列 $x$ に対し、縮約後の正解ラベル系列 $l^*$ が得られる確率は、

$$ P(l^* \mid x) = \sum_{\pi \in B^{-1}(l^*)} P(\pi \mid x) $$

と定義される。

ここで、

  • $\pi$:縮約前のラベル系列(パス)
  • $B^{-1}(l^*)$:縮約すると $l^*$ になる全パスの集合

である。

各パスの確率は、フレームごとの出力確率の積として表される。

16.5. 前向き・後ろ向きアルゴリズム

縮約後に同じ文字列となるパスは非常に多数存在するため、それらを愚直に列挙することは非現実的である。

CTC では、

  • 前向き確率 $\alpha_t(s)$

    始点からフレーム $t$・拡張ラベル位置 $s$ まで到達する全パスの確率和

  • 後ろ向き確率 $\beta_t(s)$

    フレーム $t$・拡張ラベル位置 $s$ から終点まで到達する全パスの確率和

を導入し、再帰的な計算によって効率的に $P(l^* \mid x)$ を求める。

この結果、CTC 損失関数は

$$ L_{\mathrm{CTC}} = - \log P(l^* \mid x) $$

として定義され、誤差逆伝播による学習が可能となる。

16.6. デコーディング方法

推論時には正解ラベル系列が与えられないため、デコーディングが必要となる。

講義資料では、以下の方法が紹介されている。

  • Best Path Decoding

    • 各フレームで最も確率の高いラベルを選択
    • 縮約して最終出力とする
    • 高速だが最適解とは限らない場合がある
  • Beam Search Decoding

    • 複数候補を保持しながら探索
    • より高精度だが計算量が増加

実用上は、CTC と言語モデルを組み合わせたデコーディングが一般的である。

実装演習

なし。

自身の考察結果

CTC の最も重要な意義は、「時間方向のアライメント問題を確率的に吸収した点」 にあると考えられる。

従来の音声認識では、音素境界や文字境界を明示的に扱う必要があり、それがモデル設計と実装を著しく複雑にしていた。

CTC は、ブランクと縮約という単純な仕組みによって、「どこでどの文字が発音されたか」を学習の対象から外し、「最終的に正しい文字列になるか」だけに焦点を当てている

また、前向き・後ろ向きアルゴリズムを用いることで、指数的に増えるパスの総和を効率的に計算できる点は、HMM における動的計画法と深層学習を自然に接続した設計であると感じた。

一方で、CTC は言語モデルを内部に持たないため、言語的制約を明示的に扱えず、単体では自然言語として不自然な出力になる可能性がある。

この点から、Attention 系モデルや Transformer 系モデルへの発展が必然であったことも理解できる。

総じて CTC は、End-to-End 音声認識の出発点として極めて重要な役割を果たした手法 であり、その数理構造を理解することは、後続モデルを学ぶ上でも不可欠であると考えられる。

参考図書 / 関連記事

Connectionist Temporal Classification(CTC)は、音声認識や手書き文字認識のように、入力系列と出力ラベル系列の間に明確な対応付け(アライメント)が存在しない問題 を直接扱うために提案された手法である。Graves らはまず、こうした課題が現実世界において広く存在することを指摘している。

“Many real-world sequence learning tasks require the prediction of sequences of labels from noisy, unsegmented input data.”

音声認識では、連続的な音響信号から単語や音素の系列を出力する必要があるが、各時刻のフレームとラベルとの対応は事前には分からない。この点が、従来のリカレントニューラルネットワーク(RNN)をそのまま適用できなかった主因であると論文では説明されている。

“The problem is that the standard neural network objective functions are defined separately for each point in the training sequence; in other words, RNNs can only be trained to make a series of independent label classifications.”

その結果、RNN を用いた系列ラベリングでは、訓練データの事前セグメンテーションや、出力の後処理が不可欠であった。一方で、HMM や CRF といった確率的モデルは系列全体を扱えるものの、モデル設計や仮定に多くのタスク依存知識を要するという欠点を持つ。

“While these approaches have proved successful for many problems, they have several drawbacks: (1) they usually require a significant amount of task specific knowledge …”

CTC の核心は、こうした制約を回避し、未セグメントな入力系列に対して直接ラベル系列を学習する 点にある。論文では、この目的を明確に次のように述べている。

“This paper presents a novel method for training RNNs to label unsegmented sequences directly, thereby solving both problems.”

そのために CTC では、ネットワーク出力を各時刻のラベル確率と解釈し、それらから 入力系列全体に条件づけられたラベル系列の確率分布 を定義するという立場を取る。

“The basic idea is to interpret the network outputs as a probability distribution over all possible label sequences, conditioned on a given input sequence.”

この枠組みにより、個々のフレームごとの正解ラベルを与える必要がなくなり、評価基準も「フレーム誤差」ではなく「ラベル系列全体の誤り」に基づいて定義される。論文ではその指標として、編集距離に基づくラベル誤り率が採用されている。

“This is a natural measure for tasks (such as speech or handwriting recognition) where the aim is to minimise the rate of transcription mistakes.”

このように CTC は、音声認識を「各時刻の分類問題」としてではなく、「系列から系列への写像問題」 として再定義した点に特徴がある。事前セグメンテーションを不要とし、タスク固有の知識に依存せずに系列ラベリングを行えるという性質は、後の End-to-End 音声認識手法の基礎となる考え方である。論文の結論でも、その一般性と有効性が強調されている。

“We have introduced a novel, general method for temporal classification with RNNs … It obviates the need for pre-segmented data, and allows the network to be trained directly for sequence labelling.”


17. DCGAN

17.1. GAN(Generative Adversarial Networks)の基本構造

DCGAN を理解する前提として、講義資料ではまず GAN の基本概念が整理されている。

GAN は以下の 2 つのネットワークから構成される。

  • Generator(生成器)

    乱数 $z \sim p_z(z)$ を入力として、本物に近いデータ $G(z)$ を生成する。

  • Discriminator(識別器)

    入力データが真のデータ(学習データ)か、生成データかを確率として出力する。

この 2 つは互いに競合関係にあり、2 プレイヤーのミニマックスゲーム として学習が進む。

GAN の価値関数は次式で定義される。

$$ \min_G \max_D V(D, G) = \mathbb{E}_{x \sim p_{data}(x)}[\log D(x)] + \mathbb{E}_{z \sim p_z(z)}[\log (1 - D(G(z)))] $$

  • 識別器 $D$:真データを正しく判別する確率を最大化
  • 生成器 $G$:識別器を騙すようなデータを生成し、上式を最小化

この構造により、生成分布 $p_g$ は真のデータ分布 $p_{data}$ に近づいていく。

17.2. GAN が本物らしいデータを生成できる理由

講義資料では、理論的な裏付けとして以下が説明されている。

  • $G$ を固定したとき、価値関数を最大化する最適な識別器は

$$ D^*(x) = \frac{p_{data}(x)}{p_{data}(x) + p_g(x)} $$

  • この $D^*(x)$ を価値関数に代入すると、最終的に、生成分布と真の分布の間の Jensen–Shannon ダイバージェンスを最小化する問題に帰着する
  • JS ダイバージェンスは、2 つの分布が一致するときにのみ 0 となる。

したがって、GAN の学習が理想的に進んだ場合、生成分布 $p_g$ は真の分布 $p_{data}$ に一致する

17.3. DCGAN の位置づけと目的

DCGAN(Deep Convolutional GAN) は、GAN を画像生成に適用する際の ネットワーク構造上の指針を体系化したモデル である。

従来の GAN では、

  • 学習が不安定
  • 生成画像の品質が低い

といった問題が多く見られた。

DCGAN は、いくつかの 構造的制約(Architectural Constraints) を導入することで、安定した学習と高品質な画像生成を実現している。

17.4. DCGAN のネットワーク構造上の特徴

講義資料で示されている DCGAN の主な設計指針は以下の通りである。

Generator(生成器)

  • Pooling 層を使用しない
  • 転置畳み込み(Deconvolution / Transposed Convolution) によりアップサンプリング
  • 中間層の活性化関数:ReLU
  • 最終層の活性化関数:tanh

Discriminator(識別器)

  • Pooling 層を使用しない
  • 畳み込み層によってダウンサンプリング
  • 活性化関数:Leaky ReLU
  • 最終層:sigmoid

共通の設計方針

  • 全結合層を中間層に用いない
  • バッチ正規化(Batch Normalization)を適用

これらにより、画像生成に適した深い畳み込み構造が実現されている。

17.5. GAN の課題:学習不安定性とモード崩壊

講義資料では、GAN 全般の課題として以下が挙げられている。

  • モード崩壊(Mode Collapse)
    • 特定のパターンのみを生成するようになり、多様性が失われる
  • 勾配消失
    • 識別器が強くなりすぎると、生成器に有効な勾配が伝わらない
  • 損失関数と生成品質の乖離
    • 損失が改善しても、画像品質が向上するとは限らない

これらの問題に対する改良手法として、後半で Wasserstein GAN(WGAN) が紹介されている。

17.6. WGAN への発展(概要)

WGAN では、従来の JS ダイバージェンスの代わりに Wasserstein Distance(Earth Mover’s Distance) を損失関数として用いる。

これにより、

  • 有意味な勾配が常に得られる
  • 学習の進行とともに損失が安定して減少する

といった性質が得られ、GAN の学習安定性が大きく改善される。

17.7. DCGAN を基盤とした応用例

講義資料では、GAN 系モデルの応用例として、

  • 1 枚の顔画像からリアルな動画像(アバター)を生成する手法
  • 初期化部と推論部を分離し、リアルタイム推論を可能にする設計

などが紹介されている。

これらは DCGAN の思想(畳み込みベース生成モデル)を基盤とした応用技術である。

実装演習

以下のコードについて、Generator と Discriminator の損失関数設計および学習更新の流れ に着目する。実行はしない。

  • 4_10_code_dcgan
    • dcgan.ipynb
    • dcgan_architecture.py
    • model_dcgan.py

Generator および Discriminator のネットワーク構造は、DCGAN の設計指針に従い、畳み込み層と転置畳み込み層を中心に構成されている。

class DCGAN_Generator(object):
    def __init__(self, batch_size, noize_dim=100):
        self.batch_size = batch_size
        self.noize_dim = noize_dim
        self.w_init = RandomNormal(mean=0.0, stddev=0.02)

    def build(self):
        noize = Input(batch_shape=(self.batch_size, self.noize_dim))

        densed = Dense(4 * 4 * 1024, "relu", kernel_initializer=self.w_init)(noize)
        densed = BatchNormalization()(densed)
        reshaped = Reshape((4, 4, 1024))(densed)

        # 引数:(チャンネル数、カーネルサイズ、ストライド、活性化関数)
        conv_1 = Conv2DTranspose(512, (5, 5), (2, 2), "same", activation="relu", kernel_initializer=self.w_init)(reshaped)
        conv_1 = BatchNormalization()(conv_1)
        conv_2 = Conv2DTranspose(256, (5, 5), (2, 2), "same", activation="relu", kernel_initializer=self.w_init)(conv_1)
        conv_2 = BatchNormalization()(conv_2)
        conv_3 = Conv2DTranspose(128, (5, 5), (2, 2), "same", activation="relu", kernel_initializer=self.w_init)(conv_2)
        conv_3 = BatchNormalization()(conv_3)
        conv_4 = Conv2DTranspose(3, (5, 5), (2, 2), "same", activation="tanh", kernel_initializer=self.w_init)(conv_3)

        generator = Model(inputs=noize, outputs=conv_4)

        return generator

重み初期化には平均 0、標準偏差 0.02 の正規分布が用いられており、これは GAN 学習の安定化を目的とした一般的な設定である。また、Batch Normalization を中間層に挿入することで、学習の発散を抑制している。

    @tf.function
    def update_discriminator(self, noize, real_data):
        fake_data = self.G(noize)

        with tf.GradientTape() as d_tape:
            real_pred = self.D(real_data)
            fake_pred = self.D(fake_data)

            real_loss = tf.keras.losses.binary_crossentropy(
                tf.ones_like(real_pred), real_pred
            )
            fake_loss = tf.keras.losses.binary_crossentropy(
                tf.zeros_like(fake_pred), fake_pred
            )

            # batchの平均をとる
            real_loss = tf.math.reduce_mean(real_loss)
            fake_loss = tf.math.reduce_mean(fake_loss)
            adv_loss = real_loss + fake_loss

        d_grad = d_tape.gradient(adv_loss, sources=self.D.trainable_variables)
        self.d_optimizer.apply_gradients(zip(d_grad, self.D.trainable_variables))
        if self.make_logs:
            with self.summary_writer.as_default():
                tf.summary.scalar("d_loss", adv_loss)
            self.summary_writer.flush()

        return adv_loss

Discriminator の学習では、実画像と Generator が生成した偽画像を同時に入力し、それぞれに対して二値分類を行う。実装では、実画像に対しては正例ラベル(1)、偽画像に対しては負例ラベル(0)を与え、二値交差エントロピー損失を計算している。このときの Discriminator の損失は

$$ L_D = -\mathbb{E}_{x \sim p_{\text{data}}}[\log D(x)] - \mathbb{E}_{z \sim p_z}[\log (1 - D(G(z)))] $$

に対応しており、コード上では実画像損失と偽画像損失をそれぞれ計算した上で、その和を最終的な損失としている。

    @tf.function
    def update_generator(self, noize):
        with tf.GradientTape() as g_tape:
            fake_data = self.G(noize)
            fake_pred = self.D(fake_data)
            # max(log(D(x))) こちらの方が勾配消失に頑健
            fake_loss = tf.keras.losses.binary_crossentropy(
                tf.ones_like(fake_pred), fake_pred
            )
            # min(1-log(D(x)))
            # fake_loss = -tf.keras.losses.binary_crossentropy(tf.zeros_like(fake_pred), fake_pred)

            # batchの平均をとる
            fake_loss = tf.math.reduce_mean(fake_loss)
        g_grad = g_tape.gradient(fake_loss, sources=self.G.trainable_variables)
        self.g_optimizer.apply_gradients(zip(g_grad, self.G.trainable_variables))
        if self.make_logs:
            with self.summary_writer.as_default():
                tf.summary.scalar("g_loss", fake_loss)
        self.global_step.assign_add(1)
        return fake_loss

一方、Generator の学習では、生成画像が Discriminator によって「実画像」と判定されることを目的とする。理論的には $\log(1 - D(G(z)))$ を最小化する形式も考えられるが、本実装では勾配消失を避けるために

$$ L_G = -\mathbb{E}_{z \sim p_z}[\log D(G(z))] $$

を最小化する(いわゆる non-saturating loss)。実装上は「偽画像を正例として分類させる」二値交差エントロピーとして記述できる。

            # 1step分のデータを取り出し
            for b_idx, (noize, train_data) in enumerate(zipped_train_dataset):
                batch_start_time = time.time()
                step = self.global_step.numpy()
                d_loss = self.update_discriminator(noize, train_data)
                g_loss = self.update_generator(noize)

学習更新は、Discriminator と Generator を 交互に更新する 形で行われている。

まず固定された Generator のもとで Discriminator を更新し、その後、更新された Discriminator を固定して Generator を更新する。このように最適化対象を分離することで、GAN 特有のミニマックス問題

$$ \min_G \max_D V(D, G) $$

を勾配降下法によって近似的に解いている。最適化手法としては、両者とも Adam オプティマイザが用いられ、学習率や $\beta_1, \beta_2$ は引数として明示的に設定されている。

以上より、本実装は DCGAN における基本的な損失関数設計と学習更新の流れを忠実に反映したものであることが確認できた。

自身の考察結果

DCGAN は、GAN の理論そのものを変更したモデルではなく、「どのようなネットワーク構造を採用すべきか」という実践的知見を明確に示した点 に大きな意義があると感じた。

特に、

  • Pooling を排除し、畳み込みによる空間構造の保持を重視する設計
  • 転置畳み込みによる段階的な画像生成
  • 活性化関数やバッチ正規化の使い分け

といった指針は、その後の多くの生成モデルに引き継がれている。

また、GAN の理論的最適性が示されている一方で、実際の学習は不安定であり、モード崩壊などの問題が避けられないことも、講義資料から明確に読み取れる。

この点から、WGAN のように 距離関数そのものを見直すアプローチ が生まれた流れは自然であり、DCGAN は「実装と理論のギャップ」を浮き彫りにした重要な中間地点であると考えられる。

総じて DCGAN は、GAN を実用的な画像生成モデルへと押し上げた基盤的手法 であり、生成モデルを学ぶ上で避けて通れない重要な位置づけを持つと感じた。

参考図書 / 関連記事

従来の GAN では、生成モデルと実データ分布の近さを測る指標として Jensen–Shannon(JS)ダイバージェンスなどが用いられてきたが、分布が低次元多様体上に存在する場合、この距離は適切に振る舞わないことが指摘されている。

Wasserstein GAN の原論文では、尤度最大化や KL ダイバージェンスに基づく学習が破綻する理由として、モデル分布と真の分布の台がほとんど交わらない状況では距離自体が定義できない、あるいは無限大になる点を挙げている。

“It is then unlikely that the model manifold and the true distribution’s support have a non-negligible intersection … and this means that the KL distance is not defined (or simply infinite).”

この問題を回避するため、GAN では識別器を介して JS ダイバージェンスを最適化する形式が採用されてきたが、その学習は不安定になりやすい。論文でも、GAN の学習が理論的・実践的に不安定であることは広く知られていると明言されている。

“On the other hand, training GANs is well known for being delicate and unstable …”

Wasserstein GAN は、この不安定性の根本原因を「分布間距離の選び方」に求め、Earth Mover(Wasserstein-1)距離を用いることを提案している。Wasserstein 距離は、確率質量をどの程度移動させれば一方の分布を他方に変換できるかという観点で定義され、分布の幾何構造を反映した距離である。

“Intuitively, γ(x, y) indicates how much ‘mass’ must be transported from x to y in order to transform the distributions Pr into the distribution Pg.”

この距離を用いる最大の利点は、パラメータに関する連続性が保証される点にある。論文では、Wasserstein 距離は JS や KL と比較して弱い位相を誘導し、分布列の収束が起こりやすいことが示されている。

“Since the Wasserstein distance is much weaker than the JS distance … we can now ask whether W(Pr, Pθ) is a continuous loss function on θ.”

この性質により、Generator のパラメータをわずかに更新したときに、損失が不連続に跳ねるという現象が起こりにくくなる。実際、論文では Wasserstein 距離を最適化することで、損失関数が連続かつほぼ至る所で微分可能になることが理論的に示されている。

“If g is continuous in θ, so is W(Pr, Pθ).”

さらに、WGAN では識別器を「確率を出力する分類器」ではなく、1-Lipschitz 制約を満たす関数(critic)として再定義する。この critic を十分に最適化することで、Generator に与えられる勾配が意味を持ち続ける点が、従来 GAN との本質的な違いである。

“The fact that the EM distance is continuous and differentiable a.e. means that we can (and should) train the critic till optimality.”

この結果として、WGAN では mode collapse が大幅に抑制され、また損失値が生成品質と相関するという性質が得られる。論文では、これは GAN 研究において初めて、損失が収束挙動を示す例であると述べられている。

“One of the most compelling practical benefits of WGANs is the ability to continuously estimate the EM distance … these learning curves … correlate remarkably well with the observed sample quality.”

以上より、Wasserstein GAN は DCGAN のような従来 GAN の実装を前提としつつ、損失関数の定義そのものを見直すことで学習の安定性を改善した手法 であり、DCGAN の章に対する補足的な参考文献として位置づけることができる。


18. Conditional GAN

18.1. Conditional GAN の基本概念

Conditional GAN(CGAN)は、生成したいデータに条件(Condition)を与えて生成を制御できる GAN である。

従来の GAN では、潜在変数 $z$ のみを入力として生成を行うため、生成されるデータの内容(クラスや属性)を直接指定することはできなかった。

これに対して CGAN では、条件パラメータ $y$ を導入し、

  • Generator:$G(z, y)$
  • Discriminator:$(x, y)$ の組が「真」か「偽」かを判別

という形で、条件付き生成分布 を学習する。

18.2. ネットワーク構造の違い(GAN と CGAN)

講義資料では、従来の GAN と CGAN のネットワーク構造が対比されている。

  • 従来の GAN

    • Generator:潜在変数 $z$ のみを入力
    • Discriminator:画像 $x$ のみを入力
    • 生成分布 $p_g(x)$ を近似
    • 生成結果はランダム(例:MNIST では数字が混在)
  • Conditional GAN

    • Generator:$(z, y)$ を入力
    • Discriminator:$(x, y)$ を入力
    • 条件付き生成分布 $p_g(x \mid y)$ を近似
    • 条件 $y$ によって生成内容を制御可能(例:指定した数字のみ生成)

条件 $y$ は、クラスラベルなどの離散情報として与えられることが多い。

18.3. Discriminator における判別基準

CGAN における Discriminator は、単に「本物か偽物か」を判定するだけでなく、画像と条件ラベルの組が整合しているか も同時に判断する。

講義資料では、以下のような判別が示されている。

  • $(G(z \mid y), y)$ → Fake
  • $(G(z \mid y), y')$($y \neq y'$)→ Fake
  • $(x, y)$(正しいラベル)→ Real
  • $(x, y')$(誤ったラベル)→ Fake

この仕組みにより、Generator は 「条件に合致したリアルな画像を生成しなければならない」 という制約を受ける。

18.4. Conditional GAN の学習目標

CGAN の目的関数は、基本的には GAN と同様のミニマックス問題であるが、 条件付き確率に基づいて定義される。

$$ \min_G \max_D V(D, G) = \mathbb{E}_{x \sim p_{data}(x)}[\log D(x \mid y)] + \mathbb{E}_{z \sim p_z(z)}[\log(1 - D(G(z \mid y)))] $$

これにより、Generator は 条件 $y$ が与えられたときのデータ分布を忠実に近似 するよう学習される。

18.5. Conditional GAN の効果と特徴

講義資料で強調されている CGAN の特徴は以下である。

  • 生成したいクラス・属性を明示的に指定可能
  • GAN の枠組みを大きく変更せずに制御性を付与できる
  • 画像生成の可視的な制御が容易(MNIST の例など)

一方で、

  • 条件の設計に依存する
  • 条件と画像の対応関係が学習できない場合、性能が低下する

といった点にも注意が必要である。

実装演習

なし。

自身の考察結果

Conditional GAN の本質的な価値は、生成モデルに「制御性」という次元を導入した点 にあると考えられる。

従来の GAN は高品質な生成が可能である一方、「何が生成されるか」は潜在変数に委ねられており、実用上は扱いにくい側面があった。

CGAN は、条件ラベルを明示的に与えることで、

  • 生成結果を人間が意図した方向に誘導できる
  • データ拡張やクラス条件付き生成など、実用的な用途に直結する

という大きな利点を持つ。

また、Discriminator が「画像と条件の整合性」を評価する構造は、生成品質だけでなく 意味的な正しさ を同時に学習させている点で非常に重要である。

この考え方は、後続の Pix2Pix や CycleGAN、さらにはテキスト条件付き画像生成モデルなどへと発展しており、CGAN は 条件付き生成モデルの原型として位置づけられると考えられる。

参考図書 / 関連記事

従来の Generative Adversarial Nets は、データ分布そのものを学習する強力な生成モデルである一方、生成結果のモードを制御できない という制約を持っていた。

Conditional GAN 原論文では、この点を明確な問題意識として提示している。

“In an unconditioned generative model, there is no control on modes of the data being generated.”

著者らは、この制約を解決するために、生成過程を追加情報によって制御するという発想を導入している。条件として用いられる情報 $y$ は、クラスラベルに限らず、データの一部や他のモダリティから得られる情報であってもよいとされている。

“However, by conditioning the model on additional information it is possible to direct the data generation process. Such conditioning could be based on class labels, on some part of data for inpainting … or even on data from different modality.”

Conditional GAN の特徴は、この条件情報を Generator と Discriminator の両方に同時に与える 点にある。これにより、生成モデルは「条件付き確率分布」を直接学習することが可能となる。

“We introduce the conditional version of generative adversarial nets, which can be constructed by simply feeding the data, y, we wish to condition on to both the generator and discriminator.”

この拡張は、単なる構造上の工夫にとどまらず、生成モデルの表現能力そのものを変化させる。論文では、Conditional GAN を用いることで、一対多(one-to-many)の対応関係を持つ問題を確率的に扱える ことが強調されている。

“Many interesting problems are more naturally thought of as a probabilistic one-to-many mapping.”

実験では、MNIST データセットに対してクラスラベルを条件として与えることで、特定の数字に対応した生成が可能であることが示されている。これは、無条件 GAN では困難であった「生成結果の意味的制御」が、条件付けによって実現できることを示す具体例である。

“We show that this model can generate MNIST digits conditioned on class labels.”

さらに論文では、Conditional GAN を用いることで、画像と単語ベクトルのような異なるモダリティを結び付けた生成が可能であることも示されている。これは、条件付き生成が単なるクラス制御にとどまらず、マルチモーダル学習への拡張性 を持つことを示唆している。

“We also illustrate how this model could be used to learn a multi-modal model …”

以上より、Conditional GAN は、DCGAN などで扱われる無条件生成モデルを前提としつつ、生成結果を外部情報によって制御するという新たな視点 を導入した手法である。本文で扱った Conditional GAN の基本構造を踏まえた上で、本論文は「なぜ条件を与えると生成が制御可能になるのか」という設計思想を補足する参考文献として位置づけられる。


19. Pix2Pix

19.1. Pix2Pix の基本的な位置づけ

Pix2Pix は、Conditional GAN(CGAN)を画像変換問題へ特化させた手法 である。

Conditional GAN では条件としてラベル $y$ を用いるが、Pix2Pix では 条件として画像そのもの $x$ を入力 とする点が最大の特徴である。

目的は、入力画像 $x$ から、対応する出力画像 $y$ への変換規則を学習することであり、これは「画像生成」というより 画像から画像への変換(Image-to-Image Translation) として位置づけられる。

代表的なタスク例として、講義資料では以下が示されている。

  • 白黒画像 → カラー画像(着色)
  • エッジ画像 → 写真画像
  • セマンティックセグメンテーション画像 → 実画像

これらはいずれも、入力画像と正解画像のペアが与えられる教師あり設定 である。

19.2. Pix2Pix のネットワーク構造

Pix2Pix は CGAN と同様に、

  • Generator
  • Discriminator

から構成される。

  • Generator:

    条件画像 $x$ を入力として画像 $G(x)$ を生成する。

    なお、Pix2Pix の原論文では 乱数 $z$ を明示的な入力としては与えず、主に Dropout 等によって確率性を導入する実装が採用されている(以降、一般形として $G(x,z)$ と表記することがある)。

  • Discriminator:

    「条件画像 $x$ → 出力画像 $y$」という 変換そのものが正しいかどうか を判別する。

つまり Discriminator は、

  • $(x, y)$:真の変換 → Real
  • $(x, G(x, z))$:生成された変換 → Fake

を識別する役割を持つ。

19.3. 工夫①:Generator に U-Net を採用

Pix2Pix では、Generator に U-Net 構造 を用いる点が重要な工夫として紹介されている。

U-Net の特徴は以下である。

  • Encoder–Decoder 構造
  • Downsampling と Upsampling を対称的に配置
  • スキップ接続 によって Encoder 側の特徴を Decoder 側へ直接伝達

これにより、

  • 物体の位置情報が失われにくい
  • 入力画像と出力画像の空間対応が保たれる
  • ピクセル単位の変換が可能

となり、画像変換タスクに非常に適した構造となっている。

19.4. 工夫②:L1 正則化項の導入

Pix2Pix では、通常の GAN の損失に加えて、L1 正則化項(再構成誤差) を Generator の損失関数に追加する。

この目的は、

  • 条件画像と生成画像の 全体的な視覚的一致性 を保つこと
  • GAN のみの場合に起きやすい 画像のぼやけや破綻 を防ぐこと

である。

L2 ではなく L1 が用いられている理由として、高周波成分(エッジや色変化)をより鮮明に保ちやすい 点が挙げられている。

19.5. 工夫③:PatchGAN Discriminator

Pix2Pix の Discriminator には PatchGAN が用いられる。

PatchGAN では、

  • 画像全体を 1 つの判定対象とするのではなく
  • 画像を小さなパッチに分割し
  • 各パッチごとに真偽を判定

する。

この設計により、

  • 高周波成分(テクスチャや細部)の再現性が向上
  • L1 正則化項との相乗効果
  • 局所的な視覚的リアリズムの向上

が実現されている。

実装演習

なし。

自身の考察結果

Pix2Pix の本質的な意義は、GAN を「生成モデル」から「変換モデル」へと拡張した点 にあると考えられる。

従来の GAN や Conditional GAN は、「どんなデータを生成するか」を制御する枠組みであったのに対し、Pix2Pix は「入力画像に対して、どのような対応画像を出力するか」という、より構造的で実用性の高い問題設定 を明確に打ち出している。

特に、

  • U-Net による位置情報の保持
  • L1 正則化による全体構造の安定化
  • PatchGAN による局所的リアリズムの強化

という 3 点は、単独ではなく 組み合わせとして非常に合理的 であり、 画像変換という課題の本質(構造+質感の両立)を的確に捉えていると感じた。

一方で、Pix2Pix は 対応する画像ペアが必要 であるため、データ収集のコストが高いという制約も明確である。

この制約を取り払った手法として CycleGAN が登場する流れも、自然に理解できる。

総じて Pix2Pix は、教師あり画像変換の標準的ベースラインを確立した手法 であり、後続研究への影響が極めて大きいモデルであると考えられる。

参考図書 / 関連記事

Pix2Pix では、識別器に画像全体を一度に判定させるのではなく、局所的なパッチ単位で真偽判定を行う識別器(PatchGAN) を用いる点が特徴である。論文中では、この識別器の設計思想について次のように述べられている。

“We design a discriminator architecture – which we term a PatchGAN – that only penalizes structure at the scale of patches.”

この PatchGAN は、画像を $N \times N$ の小領域(パッチ)に分割し、それぞれについて「本物か偽物か」を判定する。最終的な識別結果は、これら局所判定の平均として得られる。この設計により、識別器は 高周波成分(エッジやテクスチャなど) に主に注目するようになる。

論文では、L1 損失(画素ごとの差分)との役割分担についても明確に説明されている。

“This motivates restricting the GAN discriminator to only model high-frequency structure, relying on an L1 term to force low-frequency correctness.”

すなわち、

  • L1 損失:画像全体の大まかな構造や低周波成分を正しく保つ
  • PatchGAN:局所的なリアリティ(シャープさ・質感)を保証する

という補完関係にある。

さらに、PatchGAN は画像を マルコフ確率場(Markov random field) として近似している点も論文中で指摘されている。

“Such a discriminator effectively models the image as a Markov random field, assuming independence between pixels separated by more than a patch diameter.”

この仮定により、識別器のパラメータ数を抑えつつ、高解像度画像にも適用可能となる。実際、論文ではパッチサイズを変えた実験が行われており、70×70 程度の PatchGAN が品質と安定性のバランスに優れていることが示されている。

“We demonstrate that N can be much smaller than the full size of the image and still produce high quality results.”

以上より、Pix2Pix における PatchGAN は、画像全体の意味理解を目的とするのではなく、局所的なリアリティを評価する識別器 として設計されており、L1 損失と組み合わせることで高品質な image-to-image translation を実現していることが分かる。

この点は、従来の GAN における「全体判定型識別器」との明確な違いであり、本論文の重要な設計上の貢献の一つである。


20. A3C

20.1. A3C の概要と位置づけ

A3C(Asynchronous Advantage Actor-Critic)は、DeepMind の Volodymyr Mnih らによって提案された 深層強化学習アルゴリズム であり、Actor-Critic 系手法の発展形である。

A3C の名称は、以下の要素に由来する。

  • Asynchronous 複数のエージェントが非同期に並列学習を行うことで、サンプル間の相関が低減され、探索の多様性が確保される
  • Advantage アドバンテージ関数を用いた方策勾配
  • Actor 方策(行動選択)を担うネットワーク
  • Critic 状態価値関数を推定し、方策を評価するネットワーク

A3C は、DQN のような価値ベース手法とは異なり、現在の方策で生成された経験のみを用いて更新を行うオンポリシー型の Actor-Critic 手法 である。

20.2. 非同期並列学習の仕組み

A3C の最大の特徴は、複数のエージェント(Worker)が並列・非同期に学習を進める点 にある。

  • 各エージェントは独立した環境で rollout(ゲームプレイ)を実行
  • 各自で勾配を計算
  • 好きなタイミングで 共有ネットワーク(Global Network) を更新
  • 定期的に、ローカルネットワークの重みをグローバルネットワークと同期

この共有ネットワークは、パラメータサーバ として機能する。

20.3. 経験再生を用いない安定化の工夫

強化学習では、連続した経験による 自己相関 が学習不安定性の原因となる。

  • DQN:Experience Replay により自己相関を低減(ただしオフポリシー手法)
  • A3C:複数エージェントを並列化することで、サンプルの多様性を確保し、自己相関を低減

これにより、A3C は オンポリシー手法でありながら安定した学習 を実現している。

20.4. A3C のネットワーク構造

A3C は パラメータ共有型 Actor-Critic を採用している。

  • 1 つの分岐型ネットワークが

    • 方策 $\pi(a \mid s)$
    • 状態価値 $V(s)$

    を同時に出力

  • Actor と Critic を パラメータ共有した分岐型ネットワーク として実装し、方策損失・価値損失・エントロピー正則化を合成した total loss で同時に更新する。

    ※「total loss で更新する」こと自体は Actor-Critic で一般的であり、A3C の特徴は 非同期・並列化(複数ワーカー)による学習の安定化 にある。

この点が、一般的な Actor-Critic 手法との大きな違いである。

20.5. A3C のロス関数

A3C のロス関数は、以下の 3 項から構成される。

  • アドバンテージ方策勾配項
  • 価値関数ロス
  • 方策エントロピー項

総損失は次式で表される。

$$ \text{Total loss} = - (\text{Advantage policy gradient}) + \alpha \cdot (\text{Value loss}) - \beta \cdot (\text{Policy entropy}) $$

  • $\alpha, \beta$:ハイパーパラメータ

  • 特に $\beta$ は探索の度合いを制御する重要な係数

エントロピー項を加えることで、

  • 方策の早すぎる収束
  • 局所解への停滞

を防ぎ、探索性を維持する効果がある。

20.6. アドバンテージ関数の役割

方策勾配では、期待収益を最大化するために勾配を推定するが、その際に ベースライン $b(s)$ を導入することで分散を低減できる。

A3C では、

  • ベースライン:状態価値関数 $V(s)$
  • 行動価値:$k$ ステップ先読みした収益

を用いて、アドバンテージ

$$ A(s, a) = Q(s, a) - V(s) $$

を推定し、学習の安定化を図っている。

20.7. A3C の性能特性

講義資料では、Atari 2600 環境での性能評価が紹介されている。

  • GPU を使用せず、CPU のみでも高性能
  • より短い訓練時間で、DQN 系手法に匹敵、あるいはそれ以上のスコア
  • 並列エージェント数を増やすことで、学習効率が大幅に向上

これにより、A3C は 計算資源効率の高い強化学習手法 として評価されている。

20.8. A2C への発展

A3C の後継として A2C(Advantage Actor-Critic) が提案されている。

  • 非同期ではなく 同期処理
  • Python でも実装しやすい
  • 性能は A3C と同等 とされる(条件に依存)

このため、実用面では A2C が広く利用されるようになった。

実装演習

なし。

自身の考察結果

A3C の最も重要な貢献は、「並列化によって学習の安定性と高速性を同時に実現した点」 にあると考えられる。

Experience Replay に頼らず、複数エージェントによる非同期サンプリングという発想で自己相関問題を解決した点は、オンポリシー強化学習の可能性を大きく広げた。

また、Actor と Critic をパラメータ共有型ネットワークとして統合し、単一のロス関数で更新する設計は、実装面・計算面の双方で非常に合理的である。

一方で、非同期処理は実装が複雑になりやすく、Python 環境では扱いづらいという課題も明確である。

この点から、A2C のような同期型手法が実用的に普及した流れも自然であると感じた。

総じて A3C は、深層強化学習における「並列学習」という方向性を決定づけた重要な手法 であり、後続の IMPALA や分散 RL 手法への橋渡しとなったモデルであると考えられる。

参考図書 / 関連記事

深層強化学習においては、ニューラルネットワークを用いた関数近似と逐次的なデータ生成が組み合わさることで、学習が不安定になりやすいという問題が知られている。

A3C の原論文では、まず従来の手法が Experience Replay に大きく依存していた点を指摘している。

“Most successful deep reinforcement learning methods rely on a replay memory to reduce correlations between samples or to stabilize learning.”

Experience Replay はサンプル間の相関を低減する一方で、メモリ使用量や計算効率の面で制約があり、オンライン学習との相性も良くない。これに対し著者らは、複数のエージェントを並列に動作させることで、サンプルの相関そのものを弱める という異なるアプローチを採用している。

“We instead propose a novel asynchronous variant of reinforcement learning algorithms that do not use experience replay.”

A3C では、複数のワーカースレッドがそれぞれ独立した環境インスタンスと相互作用し、共有されたパラメータに対して非同期に勾配更新を行う。この仕組みにより、単一エージェントでは避けられなかった時系列相関が自然に分散される。

“Parallel actor-learners explore different parts of the environment, which reduces the correlation between updates.”

また、非同期更新は計算資源の観点からも重要な意味を持つ。論文では、GPU による大規模並列ではなく、CPU 上での軽量なスレッド並列 を前提として設計されている点が強調されている。

“Our asynchronous methods leverage multiple CPU cores, making it possible to train neural networks efficiently without specialized hardware.”

このような非同期構成は、学習の安定性だけでなく、探索性能の向上にも寄与する。異なる初期状態や探索方策を持つワーカーが同時に学習を進めることで、局所解への早期収束が起こりにくくなる。

“Different agents learn in parallel, leading to a more diverse exploration of the state space.”

以上より、A3C は単に Actor–Critic 法を拡張した手法ではなく、深層強化学習における不安定性の原因を「データ相関」と捉え、それを非同期並列化によって解決しようとした点 に本質的な特徴がある。

本論文は、本文で扱った A3C のアルゴリズム概要を前提として、その背後にある設計思想と実用上の動機を補足する参考文献として位置づけられる。


21. Metric-learning(距離学習)

21.1. 距離学習の基本的な考え方

距離学習(Metric Learning)とは、データ間の距離(類似度)そのものを学習する手法である。

人物同定(Person Re-Identification)、顔認識、画像検索、異常検知など、「分類」よりも「類似度」が本質となるタスクで広く利用されている。

深層距離学習では、ニューラルネットワーク(主に CNN)を用いて入力データを 低次元の特徴ベクトル(埋め込みベクトル) に写像し、

  • 類似サンプル → 距離が近い
  • 非類似サンプル → 距離が遠い

となるように 埋め込み空間(embedding space) を構成することを目的とする。

このアプローチにより、ネットワーク構造を複雑化せずとも、クラスタリングや検索、識別精度の向上が可能になる。

21.2. Siamese Network

Siamese Network は、深層距離学習の代表的かつ歴史のある手法 である。

  • 2 つの入力サンプルを ペアとして同時に入力
  • パラメータを共有した同一ネットワーク(CNN)で特徴抽出
  • 出力特徴ベクトル間の距離 $D$ を直接最適化

距離の最適化には Contrastive Loss が用いられる。

$$ L = \frac{1}{2} \left[ y D^2 + (1-y)\max(m-D, 0)^2 \right] $$

  • $y=1$:同一クラス(距離を小さくする)
  • $y=0$:異なるクラス(距離を $m$ 以上に離す)
  • $m$:マージン(過度に離れすぎるのを防ぐ)

この損失関数により、類似データは近づけ、非類似データは一定距離以上に離す 学習が行われる。

ただし、

  • 同一クラスは距離 $0$ まで押しつぶされやすい
  • 異なるクラスはマージン到達後に学習が止まる

という 最適化の不均衡 が問題点として指摘されている。

21.3. Triplet Network

Triplet Network は、Siamese Network の欠点を改良した手法であり、3 つの入力サンプル(Triplet) を同時に扱う。

  • アンカー(基準):$x_a$
  • 類似サンプル(Positive):$x_p$
  • 非類似サンプル(Negative):$x_n$

損失関数には Triplet Loss が用いられる。

$$ L = \max(D_p - D_n + m, 0) $$

  • $D_p = \| f(x_p)-f(x_a) \|_2$
  • $D_n = \| f(x_n)-f(x_a) \|_2$

この損失は、

  • 「類似サンプルは非類似サンプルより マージン $m$ 以上近い

という 相対的な距離関係 のみを最適化する点が特徴である。

これにより、

  • Siamese Network における距離最適化の不均衡が解消
  • タスクごとの文脈(どれを近づけるべきか)を明示的に判断する必要がない

という利点が得られる。

現在の深層距離学習では、Triplet Network およびその派生手法が主流となっている。

21.4. Triplet Network の課題

講義資料では、Triplet Network の課題として以下が挙げられている。

  1. Triplet 数の爆発

    データ数が増えると、可能な triplet の組み合わせが指数的に増加する。

  2. 学習の停滞

    学習が進むにつれ、多くの triplet が損失 0 となり、更新に寄与しなくなる。

これに対処するため、Triplet Mining(Hard / Semi-hard Triplet Selection) が必要となり、実装が煩雑になる傾向がある。

また、Triplet Loss だけでは、

  • クラス内距離 < クラス間距離

全体として保証されない 問題も指摘されており、これを解決する拡張として Quadruplet Loss などが提案されている。

実装演習

なし。

自身の考察結果

距離学習の本質は、「分類境界を学ぶ」のではなく「意味的な配置を学ぶ」点 にあると感じた。

Siamese Network は距離を直接制御できる直感的な手法である一方、距離を絶対値として最適化する設計が、埋め込み空間の歪みや学習不均衡を生みやすいことが理解できる。

それに対して Triplet Network は、

  • 絶対距離ではなく相対距離に注目する
  • 「どちらがより近いか」という関係性のみを学習する

という点で、人間の類似度判断に近い学習構造 を持っていると感じた。

一方で、Triplet Mining の必要性から分かるように、距離学習は データの与え方が性能を大きく左右する手法 であり、単に損失関数を選ぶだけでは十分でない点も重要である。

総じて距離学習は、分類問題とは異なる視点で表現学習を捉え直す枠組みであり、顔認識や検索といった実応用に直結する、非常に実践的な技術であると考えられる。

参考図書 / 関連記事

深層学習によって得られる特徴表現は、しばしば分類タスクの副産物として学習されており、その際に誘導される距離構造自体が明示的に最適化されることは少ない。

Triplet Network の原論文では、この点を問題として捉え、表現学習の目的を「分類」ではなく「距離比較」に置き直している。

“Despite their importance, these representations and their corresponding induced metrics are often treated as side effects of the classification task, rather than being explicitly sought.”

従来の Siamese Network では、類似・非類似のペアに対して距離を近づける/遠ざけるという対比学習が行われるが、この方法は「どの程度近いか」という絶対的な距離のスケールに依存するため、文脈に対して脆弱であると指摘されている。

論文では、同じ二者の関係でも、データ集合が変われば「類似/非類似」の意味が変わり得ることが述べられている。

“Siamese networks are also sensitive to calibration in the sense that the notion of similarity vs dissimilarity requires context.”

これに対し Triplet Network では、「どちらがより近いか」という 相対的な距離関係 のみを学習対象とする。著者らは、学習信号を

$$ r(x, x_1) > r(x, x_2) $$

という比較の形で与えることで、距離の絶対値ではなく順位関係を保存する埋め込みを学習できると述べている。

“Our labels are of the form r(x, x1) > r(x, x2) for triplets x, x1, x2 of objects.”

この枠組みでは、入力 (x) に対して、同じクラスに属する (x^+) と異なるクラスの (x^-) を同時に扱い、埋め込み空間上で

$$ \| F(x)-F(x^+) \| < \| F(x)-F(x^-) \| $$ となるように学習が行われる。論文では、この比較構造を直接ネットワークに組み込んだモデルを Triplet Network と呼んでいる。

“We call our approach a triplet network.”

重要なのは、この学習方法が「クラスラベルそのもの」ではなく、「どれがより近いか」という弱い情報のみを必要とする点である。著者らは、Triplet Network が比較情報だけを用いて有用な表現を学習できることを強調している。

“This method requires to know only that two out of three images are sampled from the same class, rather than knowing what that class is.”

以上より、Triplet Network は距離学習を「類似/非類似の二値判定」から、「相対距離の保存」という問題へと再定式化した手法である。

本論文は、本文で扱った距離学習の基本構造を前提としつつ、なぜ相対比較に基づく学習がより安定した表現をもたらすのか という点を補足する参考文献として位置づけられる。


22. MAML(メタ学習)

22.1. MAML が解決したい課題

MAML(Model-Agnostic Meta-Learning)は、少ないデータで新しいタスクに素早く適応できる学習方法 を実現することを目的としたメタ学習手法である。

講義資料では、深層学習が抱える以下の課題が動機として示されている。

  • 高性能なモデルには大量の教師データが必要
  • アノテーションコストが非常に高い
  • データが十分に集められないタスクが多い
  • データが少ない場合、過学習が発生しやすい

これに対し、「少ないデータ(Few-shot)」で学習できるモデル の必要性が強調されている。

22.2. MAML の基本コンセプト(メタ学習)

MAML の中心的な考え方は、

  • 複数タスクに共通する 最適な初期パラメータ を学習する
  • 新しいタスクでは、その初期値から 少数ステップの勾配更新 のみで適応する

という点にある。

従来の転移学習では、

  • 事前学習済みモデルを用意し
  • 特定タスク向けにファインチューニングを行う

という流れが一般的であったが、MAML では 「初期値そのもの」を最適化対象 とする点が本質的に異なる。

この初期パラメータは、タスク間で共有される メタ知識(meta-knowledge) と解釈される。

22.3. MAML の学習構造(Inner Loop / Outer Loop)

MAML の学習は、2 重ループ構造 で定義される。

  • Inner Loop

    • 各タスク $T_i$ に対して
    • 現在の共通パラメータ $\theta$ を初期値として
    • 勾配降下法によりタスク固有パラメータ $\theta'_i$ を得る

$$ \theta'_i = \theta - \alpha \nabla_\theta L_{T_i}(f_\theta) $$

  • Outer Loop

    • 各タスクで得られた $\theta'_i$ を用いて
    • 「更新後の損失」が小さくなるように
    • 元の初期パラメータ $\theta$ を更新する

$$ \min_\theta \sum_i L_{T_i}\bigl(f_{\theta'_i}\bigr) $$

この Outer Loop の更新により、どのタスクにも素早く適応できる初期パラメータ が学習される。

22.4. Model-Agnostic / Task-Agnostic な性質

補足資料で強調されている MAML の重要な特徴は以下の 2 点である。

  • Model-Agnostic

    • モデル構造を仮定しない
    • 微分可能であれば CNN、RNN、Policy Network など何でも適用可能
  • Task-Agnostic

    • 分類・回帰・強化学習など、幅広いタスクに適用可能

この汎用性の高さにより、MAML はメタ学習分野の代表的手法として位置づけられている。

22.5. MAML の効果(Few-shot learning)

講義資料では、MAML の効果として以下が示されている。

  • Omniglot データセットを用いた Few-shot 分類において
  • 1-shot / 5-shot のいずれでも
  • 既存手法(Siamese Network、Matching Network など)を上回る、または同等以上の精度

また、分類問題だけでなく、

  • 回帰問題
  • 強化学習

といったタスクでも有効性が確認されている点が示されている。

22.6. MAML の課題と近似手法

MAML の大きな課題として、計算コストの高さ が挙げられている。

  • Outer Loop の更新で 2 階微分(勾配の勾配) が必要
  • メモリ使用量・計算量が大きい
  • Inner Loop の更新回数を増やしづらい

これに対する代表的な近似手法として、以下が紹介されている。

  • First-order MAML(FOMAML)

    • 2 次以上の勾配を無視
    • 計算コストを大幅に削減
    • 精度は MAML とほぼ同等
  • Reptile

    • Inner Loop の逆伝播を行わず
    • 学習前後のパラメータ差のみを利用

特に FOMAML は、高速かつ実用的な手法として用いられることがある。

実装演習

なし。

自身の考察結果

MAML の本質的な価値は、「何を学ぶか」ではなく「どこから学び始めるか」を最適化した点 にあると感じた。

通常の深層学習では、初期値は単なるランダム値として扱われるが、MAML は初期パラメータそのものを学習対象とし、それを タスク間で共有される知識 として明確に位置づけている。

この考え方は、人間が新しい分野を学ぶ際に、既存の知識や経験を足がかりに素早く理解する過程と非常によく対応している。

一方で、2 階勾配を含む計算は理論的には美しいものの、実運用では計算資源がボトルネックになることも理解できた。

FOMAML が高い精度を保ったまま高速化できる点は、深層学習において 厳密性よりも実用性が重要である場合が多い ことを示している。

総じて MAML は、Few-shot learning を理論と実装の両面で現実的なものにした画期的手法 であり、メタ学習という分野を広く普及させた重要な転換点であると考えられる。

参考図書 / 関連記事

Reptile 論文は、MAML と同様に「少数データからの高速適応」を目的としながら、二階勾配を用いず一次勾配のみでメタ学習が成立する理由 を理論的に説明する点に特徴がある。論文冒頭では、本研究の立場が明確に述べられている。

“We analyze a family of algorithms for learning a parameter initialization that can be fine-tuned quickly on a new task, using only first-order derivatives for the meta-learning updates.”

従来の MAML は、微調整過程そのものを微分するため、計算量や実装の複雑さが課題であった。これに対し本論文では、二階項を無視した first-order MAML や、新たに提案する Reptile であっても、高い性能が得られる事実に着目している。

“Surprisingly, though, they found that FOMAML worked nearly as well as MAML on the Mini-ImageNet dataset.”

Reptile は、各タスクに対して数ステップの学習を行った後、その結果得られた重みへ初期値を近づけるという、極めて単純な更新則を持つ。論文ではこの手続きを次のように説明している。

“Reptile works by repeatedly sampling a task, training on it, and moving the initialization towards the trained weights on that task.”

一見すると Reptile は単なる joint training に近い手法に見えるが、論文では Taylor 展開に基づく解析により、タスク内汎化を促進する項が暗黙的に最適化されている ことが示されている。

“We provide a theoretical analysis that applies to both first-order MAML and Reptile, showing that they both optimize for within-task generalization.”

さらに解析では、Reptile や FOMAML の更新が、同一タスク内で異なるミニバッチから得られる勾配の内積を大きくする方向に働くことが示されている。これは、あるデータで更新した方向が、別のデータに対しても有効であるような表現を学習していることを意味する。

“This term adjusts the initial weights to maximize the dot product between the gradients of different minibatches on the same task.”

以上より、Reptile は MAML の単なる近似手法ではなく、一次勾配のみを用いながらも「タスク内で汎化しやすい初期値」を学習している 点に本質的な意義がある。

本論文は、本文で扱った MAML のアルゴリズム構造を前提としつつ、「なぜ二階勾配を使わなくてもメタ学習が成立するのか」という理論的背景を補足する参考文献として位置づけられる。


23. グラフ畳み込み (GCN)

23.1. 畳み込みの再解釈と GCN の位置づけ

講義資料ではまず、「畳み込みとは何か」という根本的な問いから説明が始まっている。

畳み込みは本質的に、

  • 元の関数(信号)に重み(フィルタ)をかける操作
  • 強調すべき部分と、捨象すべき部分を分離する操作

として捉えられている。

この考え方を、

  • 2 次元格子構造のデータ(画像)に適用したものが CNN
  • 一般のネットワーク構造(グラフ)に適用したものが GCN

である、という対応関係が明確に示されている。

23.2. Spatial(空間的)畳み込みの考え方

Spatial な畳み込みでは、

  • 近傍の値を重み付きで平均・集約する
  • ノイズを抑え、形状や傾向といった特徴を明確にする

という直感的な操作が行われる。

1 次元信号の例では、

  • 各点に対して、その前後の値を一定の重みで平均する
  • この操作を繰り返すことで、滑らかな波形が得られる

ことが示されている。

GCN における Spatial 手法では、この「近傍」という概念を、

  • グラフ上で隣接するノード
  • ある基準(重みが閾値より大きいなど)で定義された局所集合

として一般化している。

23.3. Spectral(スペクトル)畳み込みの考え方

Spectral な畳み込みでは、視点を変えて、

  • 信号を周波数成分(スペクトル)に分解
  • 特徴的な周波数成分を抽出・強調
  • 逆変換によって元の空間に戻す

という手順で特徴抽出を行う。

講義資料では、

  • フーリエ変換における三角関数・指数関数の性質
  • 「2 階微分すると元に戻る」という性質

が、行列の固有値・固有ベクトルの関係 と対応している点が丁寧に説明されている。

これをグラフに拡張する際の中心的な概念が グラフラプラシアン である。

23.4. グラフラプラシアンとグラフフーリエ変換

グラフにおける「2 階微分」に相当するものとして、グラフラプラシアン行列 が導入される。

  • 隣接行列 $A$:ノード間の接続関係
  • 次数行列 $D$:各ノードの接続数
  • グラフラプラシアン:$L = D - A$

このラプラシアンの固有値・固有ベクトルを用いることで、

  • グラフ信号のフーリエ変換
  • スペクトル領域でのフィルタリング

が可能になる。

23.5. Spatial GCN の概要と計算コスト

Spatial GCN では、

  • グラフをクラスタに分割
  • 各ノードに対して、近傍ノードの特徴量を集約
  • 畳み込み・活性化・プーリングに相当する操作を繰り返す

という流れで層構造が構成される。

計算コストについては、隣接ノード数の平均を $n$ とすると、1 ノード当たりの近傍集約は概ね $O(n)$ であり、グラフ全体ではノード数 $N$ に対して概ね $O(Nn)$(≒エッジ数に比例) と見積もられる。

このことから、大規模グラフに対しても比較的扱いやすい点が特徴として挙げられている。

23.6. Spectral GCN の概要と計算コスト

Spectral GCN では、

  • グラフラプラシアンの固有値分解を行い
  • スペクトル領域で畳み込みを定義
  • 活性化関数を通して次層へ伝播

という構造が採用される。

ただし、

  • 固有値分解が必要
  • 計算量が $O(n^2) \sim O(n^3)$ に達する場合がある

という点から、計算コストが非常に高くなることが明確に指摘されている。

23.7. Spatial GCN と Spectral GCN の対比

講義資料のまとめとして、以下の対比が示されている。

  • Spatial GCN

    • 空間的にグラフを捉える
    • 直感的で拡張しやすい
    • 計算コストが低い
  • Spectral GCN

    • スペクトル解析に基づく厳密な定式化
    • 理論的背景が明確
    • 計算コストが高い

実装演習

なし。

自身の考察結果

GCN の理解において最も重要だと感じた点は、「畳み込み」という概念を、格子構造に依存しない形で再定義している点 である。

Spatial GCN は、

  • 「近いノードから情報を集める」という極めて直感的な発想に基づいており
  • CNN の延長として理解しやすい

一方で、Spectral GCN は、

  • グラフラプラシアンの固有構造に基づく厳密な定式化
  • 畳み込みの数学的本質を強く意識した構成

となっており、「なぜこれが畳み込みなのか」を深く理解する助けになると感じた。

ただし、計算コストの観点からは、理論的に美しい Spectral 手法よりも、実用上は Spatial GCN が主流になる理由も明確である。

総じて GCN は、「畳み込み=空間的局所性」という固定観念を超え、構造化データ全般へ深層学習を拡張した重要な枠組み であり、今後の応用範囲の広さを強く感じさせる技術であると考えられる。

参考図書 / 関連記事

画像や音声における畳み込みニューラルネットワークは、平行移動に対する不変性や局所性といった幾何構造を前提として高い性能を発揮してきた。

一方で、グラフ上のデータには平行移動群が存在せず、標準的な畳み込み演算をそのまま適用することはできない。この問題意識は論文冒頭で明確に述べられている。

“In this paper we consider possible generalizations of CNNs to signals defined on more general domains without the action of a translation group.”

著者らは、グラフ上で畳み込みを定義するためのアプローチとして、空間的構成(spatial construction)スペクトル的構成(spectral construction) の二つを提示している。特にスペクトル的構成では、ユークリッド空間において畳み込みがフーリエ基底で対角化される事実を手がかりに、グラフ・ラプラシアンの固有分解を用いて畳み込みを一般化する。

“One may then extend convolutions to general graphs by finding the corresponding ‘Fourier’ basis. This equivalence is given through the graph Laplacian.”

この立場では、グラフ信号の滑らかさや周波数成分はラプラシアンの固有値・固有ベクトルによって記述され、畳み込みはそれらに対するスペクトル上の乗算として定義される。論文では、ラプラシアン固有ベクトルがグリッド上のフーリエ基底と同様の役割を果たすことが説明されている。

“The eigenvectors of the Laplacian are the Fourier vectors, diagonal operators on the spectrum of the Laplacian modulate the smoothness of their operands.”

このスペクトル的定式化の利点は、全結合層に比べてパラメータ数を大幅に削減できる点にある。著者らは、適切な制約の下では、入力サイズに依存しない効率的な畳み込み層を構成できることを示している。

“The spectral construction needs at most O(n) parameters per feature map, and also enables a construction where the number of parameters is independent of the input dimension n.”

一方で、スペクトル的構成には高周波成分の扱いや計算効率の問題があることも指摘されており、これは後続研究において Chebyshev 展開や近似手法へと発展していく動機となった。

本論文は、現在広く用いられている GCN の直接的な実装を示すものではないが、「グラフ畳み込みとは何か」という概念的基盤を初めて体系的に与えた研究として位置づけられる。

本文で扱った実用的な GCN 手法に対し、本論文はその 理論的起源と問題設定 を補足する参考文献である。


24. Grad-CAM, LIME, SHAP

24.1. モデル解釈性の重要性

講義資料ではまず、深層学習モデルの ブラックボックス性 が強調されている。

  • 高精度であっても
    • なぜその判断に至ったのか説明できない
  • 医療・金融など実社会での利用において
    • 判断根拠を説明できないことは大きな不安要素となる

この問題意識から、モデルの予測根拠を可視化・説明する手法 として CAM / Grad-CAM / LIME / SHAP が紹介されている。

24.2. CAM(Class Activation Mapping)の概要

CAM は、CNN が画像分類時にどの領域へ注目しているかを可視化する手法 である。

特徴は以下の通り。

  • ネットワーク構造に制約がある
    • 最終畳み込み層の直後に GAP(Global Average Pooling) を持つ必要がある
  • 出力層の重みを、最後の畳み込み特徴マップへ投影
  • クラスごとの 注目領域マップ(Class Activation Map) を生成

CAM により、

  • CNN が「何を根拠に分類しているか」
  • クラスごとに注目領域がどのように異なるか

を視覚的に確認できる。

24.3. Grad-CAM(Gradient-weighted CAM)

Grad-CAM は CAM の拡張であり、ネットワーク構造に依存せず利用できる汎用的な可視化手法 である。

主な特徴は以下の通り。

  • 最終畳み込み層の特徴マップに対する クラス出力の勾配(gradient) を利用
  • 勾配を Global Average Pooling して、各特徴マップの重要度(重み)を算出
  • 特徴マップとの線形結合後、ReLU を適用してヒートマップを生成

Grad-CAM の利点:

  • GAP を持たない CNN でも利用可能
  • 出力が画像分類でなくてもよい(物体検出、画像キャプション、VQA などにも適用可能)
  • 「予測クラスに正に寄与した領域」のみを強調できる

24.4. LIME(Local Interpretable Model-agnostic Explanations)

LIME は、特定の入力データ 1 件に対する予測理由を局所的に説明する手法 である。

重要な特徴は以下の通り。

  • モデル非依存(Model-agnostic)
    • ニューラルネットワーク以外にも適用可能
  • 解釈したい入力の「近傍データ」をサンプリング
  • 複雑なモデルを 単純で解釈しやすいモデル(線形モデルなど)で局所近似
  • 近似モデルにおける特徴量の重みを「予測に効いた要因」として提示

LIME は、

  • 画像:どのスーパーピクセルが予測に寄与したか
  • テキスト:どの単語が寄与したか
  • 表形式:どの変数が効いたか

を説明できる。

一方で、

  • サンプリング方法
  • カーネル幅などのハイパーパラメータ

に結果が依存する点が課題として示されている。

24.5. SHAP(SHapley Additive exPlanations)

SHAP は、協力ゲーム理論の Shapley Value(シャープレイ値) を機械学習モデルの解釈に応用した手法である。

基本的な考え方は以下である。

  • 予測値を「報酬」とみなす
  • 各特徴量を「プレイヤー」とみなす
  • 特徴量が予測にどれだけ貢献したかを 平均的な限界貢献度 として定量化

SHAP の特徴:

  • 理論的に一貫した貢献度定義

  • 特徴量の寄与が

    • 予測を高める方向(正)
    • 予測を下げる方向(負)

    として可視化される

  • LIME よりも 一貫性・公平性を重視した解釈

ただし、全特徴量の組み合わせを考慮するため、計算量が大きくなりやすい点も指摘されている。

実装演習

4_8_interpretability.ipynb を実行する。

stage_4_07.png

本実験では、ImageNet で事前学習された VGG16 に対して Grad-CAM を適用し、入力画像に対する分類結果と、その判断根拠となった画像領域を可視化した。

その結果、モデルは入力画像を クラス 999(toilet paper) と予測しており、予測クラスは入力画像の内容と整合していると解釈できる。

Grad-CAM による可視化結果から、モデルが強く注目している領域は、トイレットペーパーの円筒形の上部や輪郭部分であることが分かる。

特に、背景ではなく、物体そのものの形状的特徴が集中して強調されており、分類判断が画像全体の雰囲気ではなく、対象物の意味的特徴に基づいて行われていることが確認できる。

この結果は、Grad-CAM が「どの領域が予測クラスに寄与したか」を直感的に示す手法であることを示しており、深層学習モデルのブラックボックス性を緩和する手段として有効であることが分かる。

一方で、可視化はあくまで相対的な寄与を示すものであり、因果関係そのものを保証するものではない点には留意が必要である。

自身の考察結果

本講義を通じて、「モデルの精度」と「モデルの信頼性」は別問題であることを強く認識した。

Grad-CAM は、

  • CNN が画像中のどこを見て判断しているか

を直感的に示してくれるため、画像認識モデルの妥当性確認に非常に有効であると感じた。

一方で、LIME や SHAP は、

  • モデル構造に依存せず
  • 表形式・テキスト・画像と幅広く対応できる

という点で、実運用における モデル監査・バイアス検出 に不可欠な技術である。

特に LIME の事例から、モデルが「本質的な特徴」ではなく データ収集時の偏りや形式的特徴を学習している可能性 を人間が発見できる点は非常に重要であると感じた。

総じて、Grad-CAM・LIME・SHAP は、

  • モデルを「理解する」
  • モデルを「疑う」
  • モデルを「信頼できる形で使う」

ための基盤技術であり、深層学習を社会実装する上で欠かせない要素であると考えられる。

参考図書 / 関連記事

LIME は、特定のモデル構造に依存せず、任意の分類器の予測を局所的に説明する ことを目的とした手法である。

原論文では、既存の機械学習モデルの多くがブラックボックスであり、単なる精度評価だけではモデルや予測への信頼性を判断できない点が問題として指摘されている。

“Despite widespread adoption, machine learning models remain mostly black boxes.”

と述べられており、予測結果を人間が理解できる形で説明することの重要性が強調されている。

LIME の基本的な考え方は、ある入力 $x$ の近傍におけるモデルの振る舞いを、解釈可能な単純モデルで近似する ことである。論文ではこの点について、

“we propose LIME, a novel explanation technique that explains the predictions of any classifier in an interpretable and faithful manner, by learning an interpretable model locally around the prediction.”

と説明されている。

ここで重要なのは、LIME が グローバルにモデル全体を説明しようとしない 点である。複雑なモデルを全体として忠実に近似することは困難であるため、LIME では「予測された一点の周辺」に限定して近似を行う。この性質は、原論文中で

“local fidelity does not imply global fidelity”

と明確に述べられており、LIME があくまで 局所的な忠実性(local fidelity) を重視する手法であることが示されている。

また、LIME はモデル非依存(model-agnostic)である点も特徴である。原論文では、

“an explainer should be able to explain any model, and thus be model-agnostic (i.e. treat the original model as a black box).”

と述べられており、ニューラルネットワークに限らず、SVM やランダムフォレストなど、任意の分類器に適用できることが強調されている。

このように、LIME は Grad-CAM のような「CNN の内部特徴を用いた可視化手法」とは異なり、モデルの内部構造に立ち入らず、入力と出力の関係のみから説明を与える 点に特徴がある。そのため、画像認識に限らず、テキスト分類や表形式データなど、幅広いタスクに適用可能な汎用的解釈手法として位置づけられる。


25. Docker

25.1. 仮想化技術の分類とコンテナ型仮想化

講義資料では、仮想化技術を以下の 3 種類に分類して整理している。

  • ホスト型仮想化

    • ホスト OS 上で仮想環境を実行
    • 導入は容易だが、オーバーヘッドが大きい
  • ハイパーバイザー型仮想化

    • ハードウェア上で直接ゲスト OS を実行
    • 高い分離性と安定性を持つが、管理が複雑
  • コンテナ型仮想化

    • 単一の OS カーネルを共有
    • プロセス単位で隔離された実行環境を提供

Docker は、この コンテナ型仮想化 を実現する代表的技術として位置づけられている。

25.2. Docker の定義と特徴

Docker は、

  • アプリケーション
  • 依存関係
  • 実行環境

を 1 つの コンテナ としてパッケージ化する仕組みであり、「どこでも同じように動く環境」を実現することを目的としている。

主な特徴は以下の通りである。

  • 起動が高速
  • リソース消費が少ない
  • 高い移植性
  • 環境差異による不具合を防止できる

Docker は Linux の cgroupsnamespaces といった OS 機能を基盤として実装されている。

25.3. Docker のアーキテクチャ

講義資料では、Docker の構成要素として以下が整理されている。

  • Docker エンジン

    • コンテナの作成・実行・管理を行う中核
  • Docker イメージ

    • アプリケーションと依存関係のスナップショット
  • Docker コンテナ

    • イメージから生成される実行単位

Docker イメージは不変であり、コンテナはイメージを基に生成される実行時のインスタンスである。

25.4. Docker の主な用途

Docker の利用シーンとして、以下が挙げられている。

  • アプリケーションの開発・テスト
  • 本番環境へのデプロイ
  • マイクロサービスアーキテクチャの実装
  • 開発・検証・本番環境の統一

特に、環境差異による問題を防ぎ、再現性の高い開発フローを実現できる点 が強調されている。

25.5. 基本的な Docker コマンド

講義資料では、代表的な Docker コマンドが体系的に整理されている。

  • docker build:Dockerfile からイメージ作成
  • docker run:コンテナの作成・起動
  • docker ps:コンテナ一覧表示
  • docker images:イメージ一覧表示
  • docker pull / push:レジストリとのイメージ送受信
  • docker start / stop / rm:コンテナの管理

これらにより、イメージとコンテナのライフサイクルを制御する。

25.6. Dockerfile とイメージレイヤ

Dockerfile は、Docker イメージを作成するための設計書 である。

主な命令として、

  • FROM
  • RUN
  • COPY
  • CMD
  • ENTRYPOINT
  • ENV
  • WORKDIR

などが紹介されている。

Docker イメージは レイヤ構造を持ち、各命令ごとに差分キャッシュが作成されるため、ビルドの高速化や再利用性が確保される。

25.7. GPU 環境における Docker 利用

講義資料では、深層学習用途を意識した GPU 利用についても解説されている。

  • NVIDIA Container Toolkit により GPU をコンテナ内から直接利用可能
  • CUDA やドライバ環境の差異を吸収
  • 深層学習モデルの開発・実行環境を容易に再現

これにより、GPU を用いた学習・推論環境の構築が大幅に簡素化される。

25.8. コンテナオーケストレーション

Docker 単体では複数コンテナの管理が煩雑になるため、コンテナオーケストレーション の必要性が示されている。

代表例として、

  • Kubernetes(オーケストレーション)
  • Docker Compose(複数コンテナの定義・起動管理)

が紹介されており、

  • 自動スケーリング
  • 自動復旧
  • ロードバランシング
  • 設定・シークレット管理

といった機能を提供する。

実装演習

なし。

自身の考察結果

Docker は単なる仮想化技術ではなく、ソフトウェア開発の前提そのものを変えた技術 であると感じた。

従来は、

  • OS
  • ライブラリ
  • 実行環境

を前提としてアプリケーションを構築していたが、Docker により「アプリケーションが環境を内包する」形へと転換している。

特に深層学習分野では、

  • GPU ドライバ
  • CUDA
  • ライブラリ依存関係

といった環境構築の難しさが大きな障壁であったが、Docker によって 研究・開発・運用の再現性が飛躍的に向上 した。

一方で、コンテナは OS カーネルを共有するため、完全な隔離ではない点や、セキュリティ・ネットワーク設計の理解が不可欠である点も重要である。

総じて Docker は、深層学習を含む現代的ソフトウェア開発の基盤技術 であり、単なるツールではなく「開発思想」として理解すべき存在であると考えられる。

参考図書 / 関連記事

Docker におけるビルドキャッシュは、Dockerfile の各命令ごとに判定され、再利用可能かどうかが逐次評価される。公式ドキュメントでは、ビルドの基本的な流れについて次のように説明されている。

“When building an image, Docker steps through the instructions in your Dockerfile, executing each in the order specified.”

各命令について、Docker は過去のビルド結果と比較し、同一であると判断された場合にのみキャッシュを再利用する。しかし、いずれかの命令でキャッシュが無効化されると、その命令以降はすべて新しく実行される。

“Once the cache is invalidated, all subsequent Dockerfile commands generate new images and the cache is not used.”

キャッシュの有効性判定において重要なのが、ADDCOPY 命令である。これらの命令では、コピー対象となるファイルの内容に基づいてチェックサムが計算され、内容が変化した場合にキャッシュが無効化される。

“For ADD and COPY instructions, the checksum is calculated from file metadata to determine whether cache is valid.”

一方で、ファイルの更新時刻(mtime)はこの判定には含まれないことが明示されている。

“The modification time of a file (mtime) is not taken into account when calculating the checksum.”

この仕組みにより、Docker のキャッシュは「見た目上の変更」ではなく、「内容の変更」に基づいて管理されていることが分かる。

また、キャッシュを効果的に活用するためには、Dockerfile における命令の並び順が重要である。公式ドキュメントでは、変更頻度の低い処理を前段に配置することが推奨されている。

“If your build contains several layers and you want to ensure the build cache is reusable, order the instructions from less frequently changed to more frequently changed.”

この性質を理解せずに Dockerfile を記述すると、依存ライブラリのインストールやビルド処理が毎回再実行され、ビルド時間が大幅に増加する原因となる。

以上より、Docker のビルドキャッシュは単なる高速化機構ではなく、Dockerfile の設計そのものと密接に結びついた仕組み であることが分かる。

本ドキュメントは、Docker を用いた環境構築において「なぜ Dockerfile の書き方が性能に直結するのか」を理解するための理論的根拠を与えるものであり、本文で扱う Docker の基本概念を補足する参考文献として位置づけられる。

0 件のコメント:

コメントを投稿

ラビット・チャレンジ - Stage 4. 深層学習 後編 (Day 4)

提出したレポートです。 絶対書きすぎですが、行間を埋めたくなるので仕方ない。 記事が下書きのまま忘れてた。認定取れました。 Rabbit Challenge - Stage 4. 深層学習 後編 (Day 4) 1. 強化学習 強化学習は、 環境と相互作用しながら、長...