Day 132 — カスタムアーキテクチャ設計② — Gated MLPブロックの自作設計

2026-08-22 金 / Phase 6(Grandmaster、テーマ2/5群 の 2日目) コーディング Highway Network / Transform Gate / ゲート初期化バイアス

📚 背景知識(読んでから問題へ)

🏁
Phase6テーマ2「カスタムアーキテクチャ設計」の2日目です。昨日(Day131)はH(x) = F(x) + xというResidual Connectionを実装し、「変換結果F(x)」と「入力x」を常に1:1で足し算する設計を学びました。今日はこの設計を一歩発展させ、「足す割合」自体をネットワークに学習させるHighway Networks(Srivastava et al., 2015)のゲート機構に挑みます。
🚰
「常に1:1で足す」で本当に十分なのか: Residual Connectionの式H(x) = F(x) + xには選択の余地がありません。ブロックが「今回はほとんど何もしたくない」と思っても「ほぼ全部F(x)を使いたい」と思っても、足し算の重みは常に固定です。Highway Networksは、入力xから計算されるTransform Gate T(x) = sigmoid(Linear(x))(0〜1の値を要素ごとに出力)を使い、H(x) = T(x)⊙F(x) + (1-T(x))⊙xという式で「配分比率」自体を学習可能にします。
入力 x
→
ゲート T(x)=sigmoid(Linear(x))
0〜1の配分比率
→
H(x)=T(x)F(x)+(1-T(x))x

Highway Networkの核心の数式:

H(x) = T(x) ⊙ F(x) + (1 − T(x)) ⊙ x

ゲートの初期値がなぜ重要か: Transform GateのLinear層のバイアスを学習開始時にわざと負の大きな値(例: -3)に初期化すると、sigmoid(-3)≈0.047となりH(x)≈x(ほぼ完全な恒等写像)からスタートできる。ネットワークは学習が進むにつれ、必要な場所だけゲートを少しずつ開いていく。

gate_bias_init=0 → T≈0.5:
gate_bias_init=-3 → T≈0.05:

🪄 今日の比較の設計(3種類のブロック × ゲート初期化2パターン)

a

PlainMLP

Day131と同じ対照群。正規化なし・Skipなし(劣化問題の再現用)

b

ResMLP

Day131のResidualMLPBlock。H(x)=F(x)+x(配分は固定・1:1)

c

GatedMLP

今日新規実装。H(x)=T(x)F(x)+(1-T(x))x(配分は学習可能)

GatedMLPBlockはResidualMLPBlockのF(x)部分をそのまま流用し、return x + hをreturn t*h + (1-t)*xに置き換えたものと捉えると理解しやすい。今日はさらにgate_bias_init(ゲートの初期開き具合)を0と-3の2パターンで比較する。

🎯 問題

Day131と同系統の合成分類データ・共通骨格(DeepNet)を使い、ResidualMLPBlockに加えて新しくGatedMLPBlockを実装し、比較します。

項目値
タスク5クラス分類(make_classification, n_samples=2400, n_features=20)
ラベルノイズ率flip_y=0.03(3%程度)
train / test先頭1800件 / 残り600件
ブロック内部次元HIDDEN=64(全ブロック共通)
比較対象PlainMLP / ResMLP(Day131) / GatedMLP(新規, gate_bias_init可変)
評価軸test精度 + 入力層の勾配ノルム(1バッチ目) + ブロック別の平均ゲート値
import numpy as np
from sklearn.datasets import make_classification

# --- 合成データ生成(Day131と同一) ---
X, y = make_classification(
    n_samples=2400, n_features=20, n_informative=12,
    n_classes=5, n_clusters_per_class=1, random_state=42,
    flip_y=0.03,
)

TRAIN_LEN = 1800   # 先頭1800件をtrain、残り600件をtest
HIDDEN = 64        # ブロック内部の次元(全ブロック共通)
# 今日比較する3種類:
#   (a) PlainMLP  : Day131と同じ(正規化・Skipなし、劣化問題の対照群)
#   (b) ResMLP    : Day131のResidualMLPBlock(H(x) = F(x) + x、配分は固定)
#   (c) GatedMLP  : 今日新規実装。H(x) = T(x)*F(x) + (1-T(x))*x(配分は学習可能)
1
GatedMLPBlockの自作設計(メイン): dim次元の入出力を持つブロックをnn.Moduleとして実装。F(x)はResidualMLPBlockと同構成、別途T(x)=sigmoid(Linear(x))というTransform Gateを計算する層を追加し、T(x)*F(x)+(1-T(x))*xを返す
2
ゲートのバイアス初期化: gate_bias_init引数でTransform Gateの Linear層のバイアス初期値を指定可能にする(0付近 vs Highway流の-3)
3
3種類のネットワークの学習と比較(N_BLOCKS=20固定): PlainMLP・ResMLP・GatedMLP(gate_bias_init=-3)をepochs=60で学習し、test精度を比較する
4
ゲート初期化バイアスの比較実験: GatedMLPについてgate_bias_init=0と-3の2パターンで、1バッチ目の勾配ノルムと最終test精度を比較する
5
深さ方向のゲートの開き具合を可視化: 学習済みGatedMLPの各ブロックのT(x)の平均値をブロック番号ごとに計算し、浅い層と深い層で傾向差を確認する
6
(考察): ゲートを学習可能にしたことで何が変わったか、gate_bias_initがなぜ重要だったかを100字程度で結論づける

💡 ヒント

ヒント1方向性

GatedMLPBlockは、Day131のResidualMLPBlockのreturn x + hという1行を、return t * h + (1 - t) * xに置き換えたものだと考えると理解しやすいです。ResidualMLPBlockは「xを1倍・F(x)を1倍」という固定の配分でしたが、GatedMLPBlockは「xを(1-t)倍・F(x)をt倍」という入力に応じて変化する配分にする、というのが今日実装する差分のすべてです。tは0〜1の範囲の値を要素ごとに持つTensorであることに注意してください(スカラーではありません)。

ヒント2アプローチ
  • Transform GateのLinear層は、F(x)を計算するfc1・fc2とは別の、独立したLinear(dim, dim)層として用意する(重みを共有してはいけない)
  • バイアスを負の値に初期化するにはnn.init.constant_(self.gate_fc.bias, gate_bias_init)のように書く
  • sigmoid(-3) ≈ 0.047、sigmoid(0) = 0.5。gate_bias_init=-3ならゲートは学習開始時「ほぼ閉じている(≈5%だけF(x)を使う)」状態からスタートし、0なら「五分五分」からスタートする
  • ブロックごとの平均ゲート値を集計するには、GatedMLPBlockに「直前のforwardで計算したtの平均値を保持する属性(例: self.last_gate_mean)」を持たせておくと集計しやすい
ヒント3コード骨格
import torch
import torch.nn as nn

class GatedMLPBlock(nn.Module):
    def __init__(self, dim, gate_bias_init=-3.0):
        super().__init__()
        self.norm1 = nn.LayerNorm(dim)
        self.fc1 = nn.Linear(dim, dim)
        self.act = nn.ReLU()
        self.norm2 = nn.LayerNorm(dim)
        self.fc2 = nn.Linear(dim, dim)
        self.gate_fc = nn.Linear(dim, dim)          # Transform Gate専用の層
        nn.init.constant_(self.gate_fc.bias, gate_bias_init)  # ← ここが今日のキモ
        self.last_gate_mean = None                   # 可視化用

    def forward(self, x):
        h = self.norm1(x)
        h = self.act(self.fc1(h))
        h = self.norm2(h)
        f = self.fc2(h)                              # F(x)
        t = torch.sigmoid(self.gate_fc(x))            # T(x)  0〜1の要素ごとのゲート
        self.last_gate_mean = t.mean().item()
        return t * f + (1 - t) * x                    # Highway: T(x)F(x) + (1-T(x))x

✅ 模範解答

設計要素↔コード変数の対応表

設計上の要素役割コード上の変数
F(x)ブロック内部の変換(Day131と同じ構成)forward内のf
T(x)=sigmoid(Linear(x))配分比率を決めるTransform Gateforward内のt
H(x)=T(x)⊙F(x)+(1-T(x))⊙xブロックの最終出力forwardの戻り値
gate_bias_init学習開始時の配分(0=五分五分, -3=ほぼ閉)gate_fc.bias
last_gate_mean深さ方向のゲートの開き具合の可視化用last_gate_mean
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from sklearn.datasets import make_classification
from torch.utils.data import TensorDataset, DataLoader

torch.manual_seed(0)

# --- サンプルデータ生成(Day131と同一) ---
X, y = make_classification(
    n_samples=2400, n_features=20, n_informative=12,
    n_classes=5, n_clusters_per_class=1, random_state=42, flip_y=0.03,
)
TRAIN_LEN = 1800
HIDDEN = 64

X_mean, X_std = X[:TRAIN_LEN].mean(axis=0), X[:TRAIN_LEN].std(axis=0)
X_scaled = (X - X_mean) / X_std

X_train = torch.tensor(X_scaled[:TRAIN_LEN], dtype=torch.float32)
y_train = torch.tensor(y[:TRAIN_LEN], dtype=torch.long)
X_test = torch.tensor(X_scaled[TRAIN_LEN:], dtype=torch.float32)
y_test = torch.tensor(y[TRAIN_LEN:], dtype=torch.long)

train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=32, shuffle=True)

# --- ステップ1&2: GatedMLPBlockの自作設計(ゲートのバイアス初期化つき) ---
class GatedMLPBlock(nn.Module):
    def __init__(self, dim, gate_bias_init=-3.0):
        super().__init__()
        self.norm1 = nn.LayerNorm(dim)
        self.fc1 = nn.Linear(dim, dim)
        self.act = nn.ReLU()
        self.norm2 = nn.LayerNorm(dim)
        self.fc2 = nn.Linear(dim, dim)
        self.gate_fc = nn.Linear(dim, dim)
        nn.init.constant_(self.gate_fc.bias, gate_bias_init)
        self.last_gate_mean = None

    def forward(self, x):
        h = self.norm1(x)
        h = self.act(self.fc1(h))
        h = self.norm2(h)
        f = self.fc2(h)
        t = torch.sigmoid(self.gate_fc(x))
        self.last_gate_mean = t.mean().item()
        return t * f + (1 - t) * x

class ResidualMLPBlock(nn.Module):
    """Day131と同じ実装(比較対象)"""
    def __init__(self, dim):
        super().__init__()
        self.norm1 = nn.LayerNorm(dim)
        self.fc1 = nn.Linear(dim, dim)
        self.act = nn.ReLU()
        self.norm2 = nn.LayerNorm(dim)
        self.fc2 = nn.Linear(dim, dim)

    def forward(self, x):
        h = self.norm1(x)
        h = self.act(self.fc1(h))
        h = self.norm2(h)
        h = self.fc2(h)
        return x + h

class PlainBlock(nn.Module):
    """Day131と同じ実装(劣化問題の対照群)"""
    def __init__(self, dim):
        super().__init__()
        self.fc1 = nn.Linear(dim, dim)
        self.act1 = nn.ReLU()
        self.fc2 = nn.Linear(dim, dim)
        self.act2 = nn.ReLU()

    def forward(self, x):
        h = self.act1(self.fc1(x))
        h = self.act2(self.fc2(h))
        return h

# --- ステップ3: 共通骨格を持つDeepNet(GatedMLPはgate_bias_initを渡せるようにする) ---
class DeepNet(nn.Module):
    def __init__(self, in_dim=20, hidden=HIDDEN, num_classes=5, n_blocks=20,
                 mode="gated", gate_bias_init=-3.0):
        super().__init__()
        self.input_layer = nn.Linear(in_dim, hidden)
        if mode == "plain":
            self.blocks = nn.ModuleList([PlainBlock(hidden) for _ in range(n_blocks)])
        elif mode == "res":
            self.blocks = nn.ModuleList([ResidualMLPBlock(hidden) for _ in range(n_blocks)])
        elif mode == "gated":
            self.blocks = nn.ModuleList(
                [GatedMLPBlock(hidden, gate_bias_init=gate_bias_init) for _ in range(n_blocks)]
            )
        else:
            raise ValueError(mode)
        self.output_layer = nn.Linear(hidden, num_classes)

    def forward(self, x):
        h = self.input_layer(x)
        for block in self.blocks:
            h = block(h)
        return self.output_layer(h)

    def gate_means(self):
        """gatedモードの場合、各ブロックの直近の平均ゲート値をリストで返す"""
        return [b.last_gate_mean for b in self.blocks if hasattr(b, "last_gate_mean")]

def evaluate(model):
    model.eval()
    with torch.no_grad():
        preds = model(X_test).argmax(dim=1)
    return (preds == y_test).float().mean().item()

def train_and_measure(mode, n_blocks=20, epochs=60, lr=1e-3, gate_bias_init=-3.0):
    torch.manual_seed(0)
    model = DeepNet(n_blocks=n_blocks, mode=mode, gate_bias_init=gate_bias_init)
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)

    first_batch_grad_norm = None
    for epoch in range(epochs):
        model.train()
        for xb, yb in train_loader:
            optimizer.zero_grad()
            loss = F.cross_entropy(model(xb), yb)
            loss.backward()
            if first_batch_grad_norm is None:
                first_batch_grad_norm = model.input_layer.weight.grad.norm().item()
            optimizer.step()

    acc = evaluate(model)
    return model, acc, first_batch_grad_norm

# --- ステップ4: PlainMLP・ResMLP・GatedMLP(bias=-3)の3者比較(N_BLOCKS=20) ---
for mode, kwargs in [("plain", {}), ("res", {}), ("gated", {"gate_bias_init": -3.0})]:
    model, acc, grad_norm = train_and_measure(mode, n_blocks=20, **kwargs)
    print(f"{mode:6s}: acc={acc:.4f}  input_layer勾配ノルム(1バッチ目)={grad_norm:.6f}")
# 出力例(乱数・実行環境により変動):
# plain : acc=0.2117  input_layer勾配ノルム(1バッチ目)=0.000031
# res   : acc=0.7217  input_layer勾配ノルム(1バッチ目)=0.183760
# gated : acc=0.7350  input_layer勾配ノルム(1バッチ目)=0.241300
# → gatedはresよりわずかに精度が高く、勾配ノルムも大きい(ゲートが「ほぼ恒等写像」から
#   ゆるやかに開いていくため、深い場合でも勾配が伝わりやすい経路が保たれる)

# --- ステップ5: ゲートのバイアス初期化(0 vs -3)の比較 ---
for bias_init in [0.0, -3.0]:
    _, acc, grad_norm = train_and_measure("gated", n_blocks=20, gate_bias_init=bias_init)
    print(f"gate_bias_init={bias_init:5.1f}: acc={acc:.4f}  勾配ノルム(1バッチ目)={grad_norm:.6f}")
# 出力例の傾向:
# gate_bias_init=  0.0: acc=0.6650  勾配ノルム(1バッチ目)=0.052300
# gate_bias_init= -3.0: acc=0.7350  勾配ノルム(1バッチ目)=0.241300
# → bias=0(五分五分スタート)はresより悪化することがある。bias=-3(ほぼ恒等写像
#   スタート)の方が安定して高精度・高勾配ノルムを維持する

# --- ステップ6: 深さ方向のゲートの開き具合を可視化 ---
gated_model, _, _ = train_and_measure("gated", n_blocks=20, gate_bias_init=-3.0)
gated_model.eval()
with torch.no_grad():
    _ = gated_model(X_test)   # forwardを1回走らせてlast_gate_meanを更新
gate_profile = gated_model.gate_means()
for i, g in enumerate(gate_profile, start=1):
    print(f"block {i:2d}: gate_mean={g:.4f}")
# 出力例の傾向:
# block  1: gate_mean=0.61   浅い層は比較的ゲートが開く
# block 10: gate_mean=0.34
# block 20: gate_mean=0.09   深い層はゲートがほぼ閉じたまま=ほぼ恒等写像として機能
🧭
タスク6・考察: ゲートを学習可能にしたことで、ネットワークは層ごとに「変換をどれだけ使うか」を自律的に調整できるようになり、Residual(配分固定)よりもわずかに高い精度を達成した。gate_bias_initを-3にして学習開始時をほぼ恒等写像にしておく初期化は、五分五分(0初期化)で始めるより大幅に安定し、深い層を安全に積むための初期化の工夫が精度に直結することを示している。

📊 結果の可視化(ゲート初期化の効果・ブロック別ゲート開度)

gate_bias_init別: test精度の比較(N_BLOCKS=20) bias=0 0.665 bias=-3 0.735 → 「ほぼ恒等写像」からスタート(bias=-3)する方が安定して高精度
学習後: ブロック番号別の平均ゲート値 T(x)(浅い層→深い層) 0.0 0.7 1 4 7 10 13 16 20 ■ 浅い層(block1付近)ほどゲートが開き、深い層(block20付近)ほど閉じ気味 → 浅い層が変換の責任を多く負い、深い層はほぼ素通し(恒等写像)として機能する傾向
🔍
これらの図は模式的なイメージ(実際の数値は実行環境・乱数シードにより変動)。重要なのは「ゲートの初期化が最終精度に無視できない差を生む」点と「学習後のゲート開度が層の深さに応じて偏る」という2つの構造である。

🪜 Step-by-Step 解説

1T(x)はスカラーではなく「次元ごとのゲート」であることの意味

t = torch.sigmoid(self.gate_fc(x))   # shape: (batch, dim)
return t * f + (1 - t) * x           # 要素ごとの積(アダマール積)
🔗
なぜこうするか: もしT(x)が「ブロック全体で1つの数値」(スカラー)だったら、そのブロックの全次元が同じ配分比率で混ぜられることになる。しかし実際には、ある次元は「ほぼそのまま通したい」、別の次元は「大きく変換したい」という状況がありうる。gate_fcの出力を(batch, dim)というxと同じ形にしておくことで、次元ごとに異なる配分比率を学習でき、ブロックの中の一部の次元だけを選択的に変換する、といった柔軟な振る舞いが可能になる。

2gate_fcをfc1・fc2と共有してはいけない理由

🧩
なぜこうするか: もし「ゲートの計算」と「変換F(x)の計算」で同じ重みを使い回すと、1つのLinear層が「どれくらい変換するか(ゲート)」と「どう変換するか(変換内容)」という性質の異なる2つの役割を同時に背負うことになり、勾配降下法が両方の目的を同時に最適化しようとして学習が不安定になりやすい。Highway NetworksがT(x)専用のLinear層を独立に持つのは、「開閉の判断」と「変換の中身」を関心の分離として切り分けるためである。

3なぜgate_bias_init=-3が0より優れた結果になったのか

# sigmoid(-3) ≈ 0.047 → 学習開始時は「95%くらいxをそのまま通す」
# sigmoid(0)  = 0.5   → 学習開始時は「五分五分で混ぜる」
🎯
なぜこうするか: N_BLOCKS=20のような深いネットワークでは、たとえ1ブロックあたりの情報の劣化がわずかでも、それが20回積み重なると無視できない量になる。gate_bias_init=0(五分五分スタート)の場合、学習の初期段階でまだF(x)が意味のある変換を学習していないにもかかわらず、各ブロックが入力の50%を「未学習のランダムな変換結果」に置き換えてしまい、20層を経由するころには元の情報がかなり失われてしまう。一方gate_bias_init=-3(ほぼ閉スタート)は、深いネットワーク全体がほぼ恒等写像からスタートでき、そこから必要な箇所だけ少しずつゲートを開いていく、という安全な学習経路をたどれる。

4深さ方向のゲートの開き具合のパターンが示すもの

⚖️
なぜこうするか: 学習後のブロック別ゲート平均値を見ると、浅い層ほどゲートが開き気味(F(x)を積極的に使う)、深い層ほどゲートが閉じ気味(ほぼxをそのまま通す)という傾向が観察されることが多い。これは「浅い層で入力の生の特徴量から有用な変換を行い、深い層はその結果を壊さないよう慎重に保持する」という役割分担をネットワークが自律的に学習した結果と解釈できる。Residual Connectionでは全ブロックが機械的に同じ配分で足すしかなかったのに対し、Gated MLPでは「どの層にどれだけ変換の責任を持たせるか」という設計判断自体を学習に委ねられるという点が、昨日との本質的な違いである。

🧮 数学・統計の補足(文系向け)

🚰
「ゲート」を、蛇口の開き具合で理解する:

Residual Connectionは「エスカレーター(x)に乗ったまま、決まった分だけ必ず階段(F(x))も歩く」という設計でした。今日のGated MLPはさらに一歩進んで、「階段をどれだけ歩くかを、その場の状況を見て自分で決められる蛇口」を追加したイメージです。蛇口が完全に閉まっていれば(T(x)≈0)エスカレーターの力だけで移動し、蛇口を開けば開けるほど(T(x)が1に近づくほど)階段を歩く量(F(x)の影響)が増えていきます。「バイアスを-3にして学習開始時は蛇口をほぼ閉じておく」という工夫は、「最初は安全側に倒しておき、必要になった分だけ少しずつ蛇口を開けることを覚えさせる」という初期化の知恵だと考えると分かりやすいです。
🌡️
「シグモイド関数によるゲート」を、体温計の目盛りで理解する:

sigmoid関数は、どんな実数の入力(−∞〜+∞)を受け取っても、必ず0〜1の範囲に収まる出力を返す関数です。体温計の目盛りが「実際の体の状態」を「決まった範囲の数値」に変換して表示するのに似ています。gate_bias_init=-3にすると、ゲートを決めるLinear層の出力が「かなり低い体温」を指すように設定されるため、sigmoidを通した後の目盛り(ゲートの開き具合)は常に低め(0に近い)からスタートします。学習が進むにつれて、必要な場所だけ「体温」が上がり、目盛り(ゲート)が徐々に開いていく、というイメージです。

🏆 Kaggleでの実践的な使い方

よく使われるコンペカテゴリ: ☑ 表形式データ(Tabular) / ☐ 自然言語処理(NLP) / ☐ 画像認識(CV) / ☐ 時系列(Time Series)

実例

🔀 LSTM/GRUのゲート機構との共通点

忘却ゲート・更新ゲートと同じ道具立て

「シグモイドで0〜1の配分比率を学習する」という発想は、LSTM/GRUの「忘却ゲート」「更新ゲート」と全く同じ数学的道具立てである。時系列コンペでLSTM/GRUを使う際、内部で何が起きているかをこの「蛇口」のイメージで理解しておくと応用が効きやすい。

応用

🎛️ GLU系Transformerブロックの前段階

Gated Linear Unitへの導入

TransformerのFeed-Forward層やAttention出力の合成にも、単純な加算ではなくゲート付き合成(GLU: Gated Linear Unitなど)を使う設計が近年広く使われている。今日のGatedMLPBlockはGLU系アーキテクチャの直感的な導入として位置づけられる。

発展

🔎 「責任分担」を可視化する武器として

解釈性のアピールにもなる

ブロック別ゲート平均値の可視化は、単に精度を競うだけでなく「モデルが内部で何をしているか」を人間が解釈するための手段にもなる。「浅い層と深い層で役割が分かれている」という説明ができれば、Kaggle Notebookの質の高さのアピールにもなる。

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
GatedMLPは常にResidualMLPより優れると思い込む「配分を学習できる方が柔軟で万能」という直感パラメータ数が増える分、データが少ない・エポック数が短い状況では学習が追いつかず、シンプルなResidualの方が安定することもある。今日の実験結果は今日のデータ・設定下での傾向であり、常に一般化できるとは限らない
gate_bias_initの初期化を「好みの問題」程度に軽視してしまうバイアスの初期値はネットワークの精度に直接関係しない些細な設定に見える深いネットワークでは、学習開始時の「実質的な関数」が恒等写像に近いかどうかが、その後の学習の安定性を大きく左右する。0と-3で最終精度に無視できない差が出たのはその一例
Transform Gate T(x)をブロック全体で1つのスカラー値だと勘違いする「開くか閉じるか」という言葉のイメージから単純な0/1やスカラーを連想しやすい実装上T(x)はxと同じ次元数を持つベクトル(Tensor)であり、次元ごとに異なる開閉度を学習する。「ブロック全体が何%開いているか」ではなく「各次元がそれぞれ何%開いているか」という粒度の細かい機構である
ゲートが学習後に「常に0.5前後」に収束すると思い込むシグモイドの初期値が0.5に近い(bias=0の場合)ため、そこに落ち着きそうに感じるブロック別可視化で見たように、学習後のゲート値は層によって大きく偏る(浅い層は高め、深い層は低め)ことが多く、「五分五分に落ち着く」という予想は必ずしも正しくない

🚀 次のステップ

  • 発展: 今日はT(x)をブロック内部のLinear層1つから計算しましたが、Highway Networks以降の研究では「複数のゲート(更新ゲート・出力ゲートなど役割の異なる複数のゲート)を組み合わせる」設計(LSTM/GRUなど)も広く使われています。GatedMLPBlockを拡張し、T(x)とは別に「出力に直接掛けるゲート」をもう1つ追加した2ゲート版を実装し、精度やゲートの振る舞いがどう変わるか試してみましょう
  • 次回予告: Phase6テーマ2「カスタムアーキテクチャ設計」3日目。今日のResidual/Gated系ブロックとは異なるアプローチとして、Squeeze-and-Excitation(SE)ブロックを自作します。「各特徴量チャネルの重要度を、そのチャネル自身の統計量(平均値など)から動的に再重み付けする」という、Attentionの前身にあたる軽量な機構を表形式データに応用します

Phase 6 の学習マップ(全20テーマ)

1-5
SOTA手法追跡・論文実装(完了)
6-10
カスタムアーキテクチャ設計(本日2/5)
11-15
高度なアンサンブル(Pseudo Labeling・TTA)
16-20
コンペ戦略・Gold 3枚達成プラン

Day131でResidual Connectionを実装し、今日はその配分比率を学習可能にするGated MLPブロックを実装。次回はチャネル単位の再重み付けを行うSqueeze-and-Exciteブロックへと進む。

📝 自己評価(解いた後に記入)

自分の回答・気づき・メモ: