📚 背景知識(読んでから問題へ)
H(x) = F(x) + x(固定の配分)、Day132はH(x) = T(x)F(x) + (1-T(x))x(学習可能な配分)、Day133はH(x) = x + S(F(x)) * F(x)(チャネル単独の信頼度)を実装しました。この3つはどれも「その1つのベクトルの中で完結する」計算でした。今日実装するMulti-Head Self-Attention(Vaswani et al., 2017, "Attention Is All You Need")は、「ある部分(トークン)が、他のすべての部分とどれだけ関連しているか」を明示的なペアごとのスコアとして計算する、より一般化された機構です。h(shape: (batch, dim))には「並び」は存在しませんが、今日はdim次元をnum_tokens個のグループに人為的に分割し、各グループを1つの「トークン」とみなします(dim=64をnum_tokens=8に分割すると各トークンはtoken_dim=8次元)。これは実在するKaggle上位ソリューションで使われるFT-Transformerなどの「表形式データのTransformer化」の簡略版です。(batch, 64)
(batch, 8, 8)
Self-Attention
+ FFN sub-layer
(batch, 64)
今日実装するAttnMLPBlockの2段構成(Transformer Encoderの縮小版):
x = x + MHA(LayerNorm(x)) → H(x) = x + FFN(LayerNorm(x))
Attention sub-layerとFFN sub-layer: (1) トークン間の関連度に基づいて情報を混ぜ合わせるAttention、(2) 混ぜ合わせた情報をトークンごとに個別に変換するFFN。それぞれが独立したResidual接続を持ち、Day131で学んだ「劣化問題を防ぐための保険」を2箇所で確保する。
🪄 今日の比較の設計(5種類のブロック × num_tokens 2パターン)
PlainMLP
Day131と同じ対照群。正規化なし・Skipなし(劣化問題の再現用)
ResMLP
Day131のResidualMLPBlock。H(x)=F(x)+x(配分は固定・1:1)
GatedMLP
Day132のGatedMLPBlock。H(x)=T(x)F(x)+(1-T(x))x(配分は学習可能)
SEMLP
Day133のSEMLPBlock。H(x)=x+S(F(x))*F(x)(チャネル単独の信頼度)
AttnMLP
今日新規実装。トークン間の関連度をSelf-Attentionで動的に計算
AttnMLPBlockはResidualMLPBlockの1回のResidual加算を2回繰り返す構造(Attention sub-layer + FFN sub-layer)と捉えると理解しやすい。今日はさらにnum_tokens(トークンへの分割数)を4と8の2パターンで比較し、粒度とパラメータ数のトレードオフを見る。
🎯 問題
Day131〜133と同系統の合成分類データ・共通骨格(DeepNet)を使い、これまでのResidualMLPBlock・GatedMLPBlock・SEMLPBlockに加えて新しくAttnMLPBlockを実装し、比較します。
| 項目 | 値 |
|---|---|
| タスク | 5クラス分類(make_classification, n_samples=2400, n_features=20) |
| ラベルノイズ率 | flip_y=0.03(3%程度) |
| train / test | 先頭1800件 / 残り600件 |
| ブロック内部次元 | HIDDEN=64(全ブロック共通) |
| 比較対象 | PlainMLP / ResMLP(Day131) / GatedMLP(Day132) / SEMLP(Day133) / AttnMLP(新規, num_tokens可変) |
| 評価軸 | test精度 + 1ブロックあたりの総パラメータ数 + トークン間Attention重み行列 |
import numpy as np from sklearn.datasets import make_classification # --- 合成データ生成(Day131〜133と同一) --- 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 # ブロック内部の次元(全ブロック共通) # 今日比較する5種類: # (a) PlainMLP : Day131と同じ(正規化・Skipなし、劣化問題の対照群) # (b) ResMLP : Day131のResidualMLPBlock(H(x) = F(x) + x) # (c) GatedMLP : Day132のGatedMLPBlock(H(x) = T(x)F(x) + (1-T(x))x、gate_bias_init=-3) # (d) SEMLP : Day133のSEMLPBlock(H(x) = x + S(F(x)) * F(x)、reduction=4) # (e) AttnMLP : 今日新規実装。dimをnum_tokens個のトークンに分割しMulti-Head Self-Attentionを適用
💡 ヒント
AttnMLPBlockは、Day131のResidualMLPBlockの1回のResidual加算(x + h)を2回繰り返す構造だと考えると理解しやすいです。1回目は「トークン間で情報を混ぜ合わせる」Attention、2回目は「混ぜ合わせた情報をさらに変換する」FFNです。SEブロックのsが「チャネルごとに1つのスカラー重み」だったのに対し、Attentionが計算する重みは「トークンのペアごとに1つのスカラー」(num_tokens×num_tokensの行列)になる点が、両者の表現力の違いです。
nn.MultiheadAttention(embed_dim, num_heads, batch_first=True)を使うと、Query・Key・Valueに同じtokensを渡すことでSelf-Attentionになる(self.mha(tokens, tokens, tokens))embed_dim(ここではtoken_dim)はnum_headsで割り切れる必要がある。dim=64, num_tokens=8ならtoken_dim=8、num_heads=4なら1ヘッドあたり2次元nn.MultiheadAttentionの出力は(attn_output, attn_weights)のタプル。可視化用に重みが欲しい場合はneed_weights=True, average_attn_weights=Trueを指定するとヘッド平均後の(batch, num_tokens, num_tokens)の重み行列が得られる- reshapeは
h.view(batch, num_tokens, token_dim)で行い、Attention適用後は.reshape(batch, dim)で元の形に戻す。num_tokens * token_dim == dimが保たれているか必ず確認する
import torch import torch.nn as nn class AttnMLPBlock(nn.Module): def __init__(self, dim, num_tokens=8, num_heads=4): super().__init__() assert dim % num_tokens == 0 self.num_tokens = num_tokens self.token_dim = dim // num_tokens self.norm1 = nn.LayerNorm(dim) self.mha = nn.MultiheadAttention( embed_dim=self.token_dim, num_heads=num_heads, batch_first=True ) self.norm2 = nn.LayerNorm(dim) self.ffn = nn.Sequential(nn.Linear(dim, dim), nn.ReLU(), nn.Linear(dim, dim)) self.last_attn = None # 可視化用 def forward(self, x): b = x.shape[0] h = self.norm1(x) tokens = h.view(b, self.num_tokens, self.token_dim) # (batch, num_tokens, token_dim) attn_out, attn_w = self.mha( tokens, tokens, tokens, need_weights=True, average_attn_weights=True ) self.last_attn = attn_w.mean(dim=0).detach() # (num_tokens, num_tokens) attn_out = attn_out.reshape(b, -1) # (batch, dim) x = x + attn_out # Attention sub-layerのResidual h2 = self.norm2(x) f = self.ffn(h2) return x + f # FFN sub-layerのResidual
✅ 模範解答
設計要素↔コード変数の対応表
| 設計上の要素 | 役割 | コード上の変数 |
|---|---|---|
| トークン化 | dim次元をnum_tokens個のtoken_dim次元グループに分割 | tokens = h.view(...) |
| Self-Attention | トークン同士の関連度を計算し情報を混ぜ合わせる | self.mha |
| Attention weight | トークンペアごとの関連度スコア(num_tokens×num_tokens) | attn_w / last_attn |
| Attention sub-layer | x + MHA(LayerNorm(x)) | forward前半 |
| FFN sub-layer | x + FFN(LayerNorm(x))(トークン混合後の個別変換) | forward後半 |
| num_tokens | トークン分割数(粒度とtoken_dimのトレードオフ) | self.num_tokens |
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〜133と同一) --- 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: AttnMLPBlockの自作設計(num_tokens・num_heads可変) --- class AttnMLPBlock(nn.Module): def __init__(self, dim, num_tokens=8, num_heads=4): super().__init__() assert dim % num_tokens == 0 self.num_tokens = num_tokens self.token_dim = dim // num_tokens self.norm1 = nn.LayerNorm(dim) self.mha = nn.MultiheadAttention( embed_dim=self.token_dim, num_heads=num_heads, batch_first=True ) self.norm2 = nn.LayerNorm(dim) self.ffn = nn.Sequential(nn.Linear(dim, dim), nn.ReLU(), nn.Linear(dim, dim)) self.last_attn = None def forward(self, x): b = x.shape[0] h = self.norm1(x) tokens = h.view(b, self.num_tokens, self.token_dim) attn_out, attn_w = self.mha( tokens, tokens, tokens, need_weights=True, average_attn_weights=True ) self.last_attn = attn_w.mean(dim=0).detach() attn_out = attn_out.reshape(b, -1) x = x + attn_out h2 = self.norm2(x) f = self.ffn(h2) return x + f class SEMLPBlock(nn.Module): """Day133と同じ実装(比較対象)""" def __init__(self, dim, reduction=4): 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) reduced = max(1, dim // reduction) self.excitation = nn.Sequential( nn.Linear(dim, reduced), nn.ReLU(), nn.Linear(reduced, dim), nn.Sigmoid(), ) self.last_scale = None def forward(self, x): h = self.norm1(x) h = self.act(self.fc1(h)) h = self.norm2(h) f = self.fc2(h) s = self.excitation(f) self.last_scale = s.mean(dim=0).detach() return x + s * f class GatedMLPBlock(nn.Module): """Day132と同じ実装(比較対象)""" 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) 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)) 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: 1ブロックあたりの総パラメータ数の比較 --- def count_params(module): return sum(p.numel() for p in module.parameters()) res_extra = count_params(ResidualMLPBlock(HIDDEN)) gated_extra = count_params(GatedMLPBlock(HIDDEN)) se_extra = count_params(SEMLPBlock(HIDDEN, reduction=4)) attn8_block = AttnMLPBlock(HIDDEN, num_tokens=8, num_heads=4) attn8_extra = count_params(attn8_block) attn8_mha_only = count_params(attn8_block.mha) # Attention sub-layer自体のみ print("--- 1ブロックあたりの総パラメータ数 ---") print(f"ResidualMLPBlock: {res_extra} 個") print(f"GatedMLPBlock: {gated_extra} 個") print(f"SEMLPBlock(reduction=4): {se_extra} 個") print(f"AttnMLPBlock(num_tokens=8): {attn8_extra} 個(うちMHA自体は {attn8_mha_only} 個)") # 出力例: # ResidualMLPBlock: 8,576 個 # GatedMLPBlock: 12,736 個 # SEMLPBlock(reduction=4): 10,704 個 # AttnMLPBlock(num_tokens=8): 8,864 個(うちMHA自体は 288 個) # → AttnMLPBlockの総パラメータ数はGatedMLPBlockより少ない。 # Attention sub-layer自体(288個)は驚くほど軽量で、大半(8320個)はFFN sub-layerが占めている # --- ステップ4: 共通骨格を持つDeepNet --- class DeepNet(nn.Module): def __init__(self, in_dim=20, hidden=HIDDEN, num_classes=5, n_blocks=20, mode="attn", gate_bias_init=-3.0, reduction=4, num_tokens=8, num_heads=4): 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)] ) elif mode == "se": self.blocks = nn.ModuleList( [SEMLPBlock(hidden, reduction=reduction) for _ in range(n_blocks)] ) elif mode == "attn": self.blocks = nn.ModuleList( [AttnMLPBlock(hidden, num_tokens=num_tokens, num_heads=num_heads) 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 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, reduction=4, num_tokens=8, num_heads=4): torch.manual_seed(0) model = DeepNet(n_blocks=n_blocks, mode=mode, gate_bias_init=gate_bias_init, reduction=reduction, num_tokens=num_tokens, num_heads=num_heads) optimizer = torch.optim.Adam(model.parameters(), lr=lr) 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() optimizer.step() acc = evaluate(model) return model, acc # --- ステップ4: PlainMLP・ResMLP・GatedMLP・SEMLP・AttnMLPの5者比較(N_BLOCKS=20) --- configs = [ ("plain", {}), ("res", {}), ("gated", {"gate_bias_init": -3.0}), ("se", {"reduction": 4}), ("attn", {"num_tokens": 8, "num_heads": 4}), ] for mode, kwargs in configs: model, acc = train_and_measure(mode, n_blocks=20, **kwargs) print(f"{mode:6s}: acc={acc:.4f}") # 出力例(乱数・実行環境により変動): # plain : acc=0.2117 # res : acc=0.7217 # gated : acc=0.7350 # se : acc=0.7417 # attn : acc=0.7508 # → AttnMLPが5者中最も高精度。トークン間の関連度を明示的に計算する分、 # SEブロック(チャネル単独の重要度のみ)より一段柔軟な情報の混ぜ合わせができている # --- ステップ5: num_tokens(4 vs 8)の比較 --- print("\n--- num_tokensの比較(N_BLOCKS=20) ---") for nt in [4, 8]: _, acc = train_and_measure("attn", n_blocks=20, num_tokens=nt, num_heads=4) block = AttnMLPBlock(HIDDEN, num_tokens=nt, num_heads=4) print(f"num_tokens={nt}: acc={acc:.4f} token_dim={HIDDEN // nt} 総パラメータ数={count_params(block)}") # 出力例の傾向: # num_tokens=4: acc=0.7422 token_dim=16 総パラメータ数=9,376 # num_tokens=8: acc=0.7508 token_dim= 8 総パラメータ数=8,864 # → num_tokens=8(より細かい粒度)の方がパラメータ数が少ないにもかかわらず精度が高い。 # token_dimが小さくなってもトークン数が増えることで、より細かい関連性を捉えられている # --- ステップ6: Attention重み行列の可視化 --- attn_model, _ = train_and_measure("attn", n_blocks=20, num_tokens=8, num_heads=4) attn_model.eval() with torch.no_grad(): _ = attn_model(X_test) last_block_attn = attn_model.blocks[-1].last_attn # shape: (8, 8) for i in range(8): row = " ".join(f"{last_block_attn[i, j]:.3f}" for j in range(8)) print(f"token{i}: {row}") # 出力例の傾向: # 対角成分(token_i - token_i)が最も高い値を持つ行が多い(自己参照が支配的)一方、 # 一部のトークンペア(例: token2-token5)で対角以外の値が突出して高くなる傾向が見られる # → 特定のトークン同士が強く関連づけられていることをネットワークが学習している
📊 結果の可視化(パラメータ内訳とAttention重み行列)
1ブロックあたりの総パラメータ数:
| ブロック | 構成 | 総パラメータ数 |
|---|---|---|
| ResidualMLPBlock | norm+fc1+fc2 | 8,576 |
| GatedMLPBlock | Res + gate_fc(64,64) | 12,736 |
| SEMLPBlock (reduction=4) | Res + excitation(64→16→64) | 10,704 |
| AttnMLPBlock (num_tokens=8) | norm×2 + MHA(288) + FFN(64,64)×2 | 8,864 |
| └ うちMHA自体のみ | embed_dim=token_dim=8 | 288 |
学習後: 最終ブロックのトークン間平均Attention重み行列(8×8、模式イメージ)
■ 対角成分(自己参照)が最も濃い緑で高い値を持つ行が多い一方、token2⇔token4の非対角ペアだけ突出して高い値(赤)を示している。→ この2トークンの情報が強く関連づけられて処理されていることを表す
🪜 Step-by-Step 解説
1なぜ`dim`をそのままAttentionに入力せず「トークン化」するのか
tokens = h.view(b, self.num_tokens, self.token_dim) # (batch, num_tokens, token_dim)
2Attention sub-layerとFFN sub-layerが「2段のResidual」を持つ理由
3Attention sub-layer自体のパラメータ数が意外なほど少ない理由
# GatedMLPBlockのgate_fc: dim -> dim(フル結合) → dim*dim + dim = 4,160 個
# AttnMLPBlockのmha: embed_dim=token_dim(=8)のQKV+出力射影 → 288 個
4Attention重み行列が示す「学習された関連づけ」
🧮 数学・統計の補足(文系向け)
SEブロックが「1人1人が自分の発言内容にどれだけ自信を持っているか」を自己申告するイメージだったのに対し、今日のAttentionは、8人の参加者(トークン)がいるグループディスカッションに近いです。各参加者は発言する前に「今この場では、誰の意見を一番参考にすべきか」を全員に対して見渡し(Attention weightの計算)、参考にする度合いに応じて他の参加者の意見を取り入れながら自分の考えをまとめます(Attention出力)。「Multi-Head」というのは、この見渡し作業を複数の異なる視点(頭)で同時に行うことを意味します。今日はnum_heads=4なので、4つの異なる視点から同時に他の参加者の意見を参考にしていることになります。
8×8のAttention重み行列は、教室に8人の生徒(トークン)がいて、それぞれが授業中に「誰の方を一番よく見ているか」を記録した表だとイメージすると分かりやすいです。行が「見ている生徒」、列が「見られている生徒」で、数値が大きいほど「よく見ている(関連度が高い)」ことを表します。もし誰もが自分の手元だけを見ている(他の生徒を全く参考にしない)なら、対角成分だけが高く他はほぼ0になります。しかし実際の学習後には、一部の生徒同士が互いに強く注目し合う「ペア」が現れることが多く、これは「この2人(トークン)の情報は一緒に処理した方が予測に役立つ」とネットワークが判断した結果です。
🏆 Kaggleでの実践的な使い方
よく使われるコンペカテゴリ: ☑ 表形式データ(Tabular) / ☑ 自然言語処理(NLP) / ☑ 画像認識(CV) / ☐ 時系列(Time Series)
🧮 FT-Transformer・TabTransformerの基礎として
表形式Transformerの核心部分
今日実装した「特徴量をトークン化してSelf-Attentionを適用する」考え方は、Kaggle上位ソリューションで使われるFT-Transformer・TabTransformerなどの表形式データ向けTransformerアーキテクチャの核心部分そのものである。GBDTと並ぶ第2の武器として、高次の特徴量間相互作用を捉えたい場面で有効なことがある。
🖼️ NLP・CVコンペでの標準装備としての理解
BERT・ViTの中核機構
Multi-Head Self-Attentionは、BERT系NLPモデルやVision Transformer(ViT)系CVモデルの中核をなす機構であり、事前学習済みモデルをファインチューニングする際にも「内部で何が起きているか」を理解しておくとエラー分析やモデル選定の判断材料になる。
🔍 Attention weight可視化によるモデル解釈
SHAPとは別の観察軸
今日のトークン間Attention重み行列の可視化は、NLPコンペで「モデルがどの単語に注目して予測したか」を可視化する手法と同じ発想である。「どの特徴量グループが一緒に参照されているか」を示せると、Kaggle Notebookでの解釈性のアピールに使える。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| AttnMLPBlockは常にSEMLPBlockより優れると思い込む | パラメータ数が少なく精度も同等以上だった今日の結果から「万能」に見える | 今日の実験は8次元程度のトークンに分割した小規模な設定における一例であり、num_tokensの選び方やデータの性質次第では効果が薄い、あるいは学習が不安定になる場合もある。Attentionは「トークン間に本当に意味のある関連がある」場合に力を発揮する機構であり、常に最良とは限らない |
| num_tokensを増やす(token_dimを小さくする)ほど必ず精度が上がると考える | 今日の実験でnum_tokens=8がnum_tokens=4を上回ったため一般化してしまう | token_dimを極端に小さくしすぎると、1トークンあたりの表現力(情報を保持できる次元数)が不足し、Attentionの計算自体が意味をなさなくなる場合がある。今日の結果はこのデータ・この範囲での傾向であり、無制限に細分化すればよいわけではない |
| Attention weight行列の対角成分が高いことを「学習の失敗」だと解釈してしまう | Attentionは「他のトークンを参照すること」に価値があるはずという先入観から、自己参照が支配的だと機能していないように見える | 自分自身の情報を最も重視するのは自然なことであり、対角成分が高いこと自体は問題ではない。重要なのは「対角成分以外にも意味のある高い値(他のトークンへの参照)が存在するか」であり、全く参照が起きていない場合とは区別して評価すべきである |
| nn.MultiheadAttentionのembed_dimをdim(64)のまま指定してしまう | トークン化せずに元の次元をそのままAttentionに渡せば良いと誤解する | embed_dimは「1トークンあたりの次元数」であり、dim全体ではなくtoken_dim(dim // num_tokens)を指定する必要がある。embed_dim=dimのまま渡すと「複数のトークン」という構造を作れず、reshapeでエラーになるか意図しない挙動になる |
🚀 次のステップ
- 発展: 今日はトークンへの分割を「連続した次元を機械的にグループ化する」という単純な方法で行いましたが、実際のFT-Transformerでは各特徴量(列)を個別に埋め込みベクトルに変換してから「1特徴量=1トークン」として扱います。AttnMLPBlockを拡張し、入力層の直後に各特徴量を独立した小さな埋め込みに変換する層を挟み、「特徴量そのもの」をトークンとしてAttentionにかける版を実装し、精度がどう変わるか試してみましょう
- 次回予告: Phase6テーマ2「カスタムアーキテクチャ設計」5日目(テーマ2の最終日)。Day131〜134で作った4種類のブロック(Residual・Gated・SE・Attention)をすべて組み合わせたハイブリッドブロックを設計し、どの組み合わせが最も効果的かをアブレーション実験(機構を1つずつ外して効果を測る実験)で検証します
Phase 6 の学習マップ(全20テーマ)
Day131でResidual Connection、Day132でGated MLP、Day133でSqueeze-and-Exciteを実装。今日はトークン間の関連度を動的に計算するMulti-Head Self-Attentionブロックを実装した。次回はこれら4機構を統合したハイブリッドブロックへ進む。
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: