CNN と Transformer — データの構造を活かす設計
画像にはCNN、自然言語にはTransformer。データの構造に応じて専用のアーキテクチャを作るのが、現代深層学習の鉄則です。 なぜそうなるのか、それぞれが何をしているのかを、インタラクティブなデモで体感します。
この章の全体像
専用アーキテクチャが必要
全結合層だけでは データの構造 を活かせない。画像なら空間構造、文章なら系列構造を尊重する設計が必要。
CNN — 画像のために
小さな カーネルが画像上を滑る。同じカーネルを使い回すことで、パラメータ激減 + 平行移動不変性。
Transformer — 系列のために
Self-Attention で、系列内の任意の位置間の関係を直接学習。LLM(ChatGPT, Claude)の中核技術。
💡 核心: ニューラルネットの真価は 「データの構造に合わせた設計」 で発揮される。 CNNは 空間の局所性と平行移動不変性、Transformerは 任意位置間の関係性 を、それぞれ効率よく学習できる構造を持つ。
1. なぜ専用アーキテクチャが必要か
1.1 全結合層だけの問題点
第3章 で見たMLPは、入力をフラットなベクトルとして扱うもの。 画像のような2D構造のあるデータを扱うとき、いくつかの問題があります:
📝 具体例:MLPが画像を扱うときの3つの致命傷(クリックで展開)
問題1: パラメータが爆発する
例:224×224×3 のカラー画像を全結合層で処理すると、入力は 150,528 次元のベクトル。 これを 1024 次元の中間層に繋ぐと、第1層だけで 約 1.5 億個 のパラメータが必要。 数千万のニューロンを持つ深いネットワークでは現実的でない。
📐 コラム・イン・コラム:「224×224×3 → 150,528 次元」って何? 図でゆっくり
① まず「224×224×3」の意味
カラー画像1枚は、コンピュータの中で 3つの数字の組合せ(縦×横×色チャンネル) として表されます:
- 224:縦のピクセル数(高さ)
- 224:横のピクセル数(幅)
- 3:色チャンネルの数 — R(赤), G(緑), B(青)の3枚を重ねたものがカラー画像
② 「次元のベクトル」 = 一直線に並べた数列
全結合層(MLP)は 「縦長の一直線の数値リスト」しか受け取れません。なので画像を扱うには、3次元の数値の塊(224×224×3)を 無理矢理 1列に並べ直す 操作(フラット化)が必要:
計算チェック:$224 \times 224 \times 3 = 50{,}176 \times 3 = 150{,}528$。1枚の画像が150,528個の数字に変換されました。
③ 「全結合層で処理する」とは — 1.5億個のパラメータの正体
全結合層(fully connected layer)は、入力の 全ての数値 が、出力の 全てのニューロン に 1本ずつ独立した重み線で繋がっている 層のこと:
④ なぜ「爆発」と表現するか
- たった 1層目だけ で 1.5 億個のパラメータ
- ChatGPT級の深さ(数十〜数百層)でこの調子だと、すぐ 数兆個 に到達
- 1パラメータあたり4バイトと仮定すると、1.5億 × 4 = 600 MB がこの1層のためだけにメモリに居座る
- 学習データもそのぶん必要(パラメータの数倍以上のデータがないと過学習)→ 現実的に学習不可能
一方、後で見る CNN なら、同じ画像を扱うのに カーネル(例:3×3×3 = 27個)を共有 するので、第1層のパラメータは 数百個〜数千個 で済みます。桁違いの効率です。
💡 1行まとめ: 「224×224×3 → 150,528 次元」は、カラー画像を全結合層が読める一直線の数字リストに変換したサイズ。 それを 1024 個のニューロンに全部繋ぐと、重みが 150,528 × 1024 = 約1.5億個 必要になる、ということ。
問題2: 空間構造が失われる
画像をフラット化すると、「ピクセル(1,1)とピクセル(1,2)が隣接している」という情報が消える。 ネットワークはそれを ゼロから学ばないといけない。
問題3: 平行移動不変性がない
「猫」が画像の左にあっても右にあっても同じ「猫」と認識したい。 しかし全結合層では、各位置に独立した重みがあるので、位置が変わると別物として扱われる。
1.2 解決策 — データの構造を尊重するネット
それぞれのデータ型に合った 専用設計 を作ります:
| データ型 | 構造 | 適した設計 | 代表アーキテクチャ |
|---|---|---|---|
| 画像・盤面 | 2D空間(隣接性、平行移動) | 畳み込み | CNN |
| 系列(文章、時系列、音声) | 1D系列(順序、長距離依存) | Attention | Transformer |
| 表データ | 独立した特徴の組 | 全結合 or 決定木 | MLP / XGBoost |
| グラフ | ノードとエッジ | メッセージパッシング | GNN(Graph NN) |
2. CNN — 画像のためのニューラルネット
CNN(Convolutional Neural Network、畳み込みニューラルネット)は、画像認識の標準アーキテクチャ。 将棋のdlshogiも、囲碁のAlphaGoも、その中核技術。
2.1 カーネル(フィルタ)の概念
CNNの主役は カーネル(kernel)と呼ばれる小さな行列(典型的には3×3や5×5)。 これが 画像上を滑り抜けながら、各位置で 畳み込み演算 を実行します。
📌 まず「カーネル」とは何か — のぞき窓のメタファー
カーネルは 「3×3 の小さな数値の表」 です。これを のぞき窓 だと思ってください:
- 画像(例:28×28ピクセル)の上に、カーネル(3×3)を のぞき窓のように重ねて置く
- 窓の中に 9マスぶんの画像 が見える
- カーネルの9マスと、画像の9マスを 1対1で掛け算して全部足す
- その合計値が、その位置の「点数」になる
カーネルの数値の組み合わせは「採点用テンプレート」です。例えば「縦エッジを高く採点するテンプレート」「丸い形を高く採点するテンプレート」など、目的に応じて中身を変えます。
📌 「スライドする」とは — 全位置で点数を取る
のぞき窓を 左上から右へ1マスずつ、右端まで行ったら次の行へ、と移動させて、各位置で点数を取っていきます。これが「スライド」です。
📌 1回の点数計算で何が起きるか — 具体的な数字で
ある1つの位置で 9個の掛け算と1個の足し算 をします。例えば「縦エッジ検出カーネル」を「縦線がある画像」に当てたとき:
カーネルの中身(採点ルール) によって、検出するパターンが変わります:
- $\begin{pmatrix} -1 & 2 & -1 \\ -1 & 2 & -1 \\ -1 & 2 & -1 \end{pmatrix}$:中央が縦に明るい → 縦エッジ検出
- $\begin{pmatrix} -1 & -1 & -1 \\ 2 & 2 & 2 \\ -1 & -1 & -1 \end{pmatrix}$:中央が横に明るい → 横エッジ検出
- $\frac{1}{9}\begin{pmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{pmatrix}$:周辺と平均 → ぼかし
これら採点ルールは 人が設計する必要はなく、CNN が学習で自動獲得する のが革命的なポイント(§2.3で詳述)。
📐 数式で見る:畳み込み演算の定義(クリックで展開)
畳み込み演算の式:
この式を1つずつ言葉で翻訳:
- $y[i, j]$ :「出力マップの $(i, j)$ セルの点数」を計算しているところ
- $W[m, n]$ :カーネルの $(m, n)$ 番目の値(採点テンプレートの1マス)
- $x[i+m, j+n]$ :画像の $(i+m, j+n)$ ピクセルの値(カーネルが今かぶさっている画像の1マス)
- $\sum_{m, n}$ :カーネルの全 $m, n$(3×3 なら9マス)を足し合わせる
- $b$ :バイアス(点数全体に足す調整値)
要するに 「カーネルと画像の重ね合わせた9マスを掛けて足す」だけ。$i, j$ を1ずつ動かしながら全位置で繰り返せば、特徴マップが完成。
2.2 インタラクティブデモ:畳み込みを目で見る
カーネルの値を変えると、出力画像(特徴マップ)がリアルタイムで変わります。代表的なカーネルプリセットを試してみてください。
カーネル(3×3)の値
プリセット
入力画像
※ 28×28ピクセルの白黒画像で計算しています
入力画像
出力(畳み込み後)
左の画像 ⊛ カーネル = 右の特徴マップ
💡 試してみる:
- エッジ検出カーネル → 物体の輪郭が浮かび上がる
- ぼかしカーネル → 画像が滑らかになる
- Sobel-Xカーネル → 縦方向のエッジだけ強調
- 恒等カーネル → 画像が変わらない(中央 = 1のみ)
実際のCNNでは、このカーネルの値も学習で自動決定される。第1層はエッジ検出、深い層は形を学習する、というのを勝手に獲得するのが革命。
2.3 CNNの2つの天才的アイデア
💡 アイデア1:局所性(Local Connectivity)
出力の1点は、入力の 周辺3×3領域だけ から決まる。全結合層と違い、遠くのピクセルとは直接繋がらない。
これは 画像の特徴が局所的 だという経験則を反映している。「猫の耳」「犬の鼻」のような特徴は、近くのピクセル群で構成される。
💡 アイデア2:パラメータ共有(Weight Sharing)
同じカーネル を画像のすべての位置で使い回す。3×3 = 9個の重み + バイアス1個、これだけで画像全体を処理できる。
これにより:
- パラメータが激減(150万 → 数十)
- 平行移動不変性:同じパターンが画像のどこにあっても認識できる
📊 補足図解:パラメータ数で全結合 vs 畳み込みを比較(クリックで展開)
2.4 パラメータ数で比較
150528次元(224×224×3 画像)→ 256チャンネル の中間層を作るとき:
| 方式 | パラメータ数 | 比率 |
|---|---|---|
| 全結合層(フラット化) | 約 38,500,000 | 1× |
| 畳み込み層(3×3 × 256ch) | 約 7,000 | 約 1/5500 |
5500倍も効率的。これがCNNが画像認識を可能にした最大の理由。
2.5 階層的な特徴学習
CNNを多層に積み重ねると、層ごとに違う抽象度の特徴 を学ぶようになります:
📝 具体例:顔認識で見る、層ごとの学習内容(クリックで展開)
| 層の深さ | 学習される特徴 | 例(顔認識の場合) |
|---|---|---|
| 第1層 | エッジ・色 | 縦線、横線、斜め線 |
| 中間層(浅め) | パターン・テクスチャ | 目の形、鼻のライン |
| 中間層(深め) | 部分 | 目、鼻、口 |
| 最終層 | 全体 | 顔全体 |
誰も「目」をプログラミングしていないのに、ネットが学習で自動的に獲得する。これが End-to-end learning の威力。
3. ResNet — 深いCNNを可能にした残差接続
CNNを 深く積み重ねると、より複雑なパターンを学習できる。しかし2014年頃まで、深いCNN(30層以上)は むしろ精度が落ちる という不思議な現象がありました。
📐 数学の補足:勾配消失問題はなぜ起きるのか(クリックで展開)
① 復習:勾配とは何だったか
第4章で見たように、勾配は「重み $W$ を少し動かしたら損失 $L$ がどれくらい変わるか」を表す数値($\dfrac{\partial L}{\partial W}$)。 学習はこの勾配の 逆方向 に重みを動かすことで進みます:
ここで $\eta$(学習率)は小さい正の数(典型的に 0.001 など)。
② 勾配が「消える」とどうなる?
勾配 $\dfrac{\partial L}{\partial W} \approx 0$ になると:
重みがまったく動かない=学習が止まる。これが「勾配が消えると問題」の核心です。
具体例:学習率 $\eta = 0.001$、勾配が $0.0000266$($0.9^{100}$)のとき
→新しい重みの変化量 = $0.001 \times 0.0000266 = 0.0000000266$
→小数点以下8桁の変化。事実上ゼロ。1万回ステップ回しても、ほぼ変わらない。
③ なぜ勾配が消えるのか — 掛け算の連鎖
誤差逆伝播法(§4.2 で詳述)では、各層の勾配は 後段の勾配 × 自分の偏微分 という形で繋がっています:
各偏微分項が 1未満の値(例:Sigmoid 活性化なら最大でも 0.25 程度)だと、層が深くなるほど 掛け算で指数的に小さくなる:
| 層数 | 各層の偏微分 $\approx 0.9$ のとき | 各層の偏微分 $\approx 0.5$ のとき |
|---|---|---|
| 10層 | $0.9^{10} \approx 0.35$ | $0.5^{10} \approx 0.001$ |
| 50層 | $0.9^{50} \approx 0.005$ | $0.5^{50} \approx 10^{-15}$ |
| 100層 | $0.9^{100} \approx 0.0000266$ | $0.5^{100} \approx 10^{-30}$ |
100層もあると、入力側に近い $W_1$ の勾配は 事実上ゼロ になります。
④ 何が実際に起きるか — アニメで見る
下の図は 勾配が右から左に流れていく様子 をアニメーションで表現したもの。 赤いパルス(=勾配の信号)が出力側(右)で発生し、左に伝わるたびに ×0.9 されて細くなっていきます。 入力側(左端)に着くころには 事実上消滅。
⑤ なぜそれが「有害」なのか
勾配消失が起きると、深いネットは 「浅いネットより悪化する」 という奇妙な状態になります:
- 浅い層(入力に近い)の重みが固まる:低次の特徴(エッジなど)も学習できない
- 固まった重みの上に組み立てられる中間〜出力層は、貧弱な特徴をベースに学習せざるを得ない
- 結果として、深くした分は無駄+足を引っ張る(パラメータが増えただけで、初期値のランダム値が残るのと同じ)
深いネットの理論的な表現力は浅いネットより高いはずなのに、勾配消失のせいで実現できない — これが2014年頃までの深層学習の壁でした。ResNet(§3.2) が「ショートカットで勾配を直接届ける」アイデアでこの壁を打ち破ります。
💡 1行まとめ: 勾配が消える=重みが動かない=学習しない。 100層のNNだと入力側の重みは初期ランダム値のまま固定 → 深さの利点が活かせず、むしろ害になる。
3.2 残差接続(Residual Connection)
2015年、Microsoft の He らが ResNet を提案。アイデアは単純:
残差接続:層の入力を、層の出力にそのまま足す。
ここで $F(x)$ は通常のCNN層、$+x$ が ショートカット(スキップ接続)。
📊 通常層 vs 残差ブロック — 1枚で見る違い
なぜ「ただ足す」だけで勾配消失が解決するのか。並べて見ると一目瞭然:
🧮 数学的に何が起きているか — 「+1」の魔法
残差接続が勾配消失を解決する核心は、連鎖律で計算した勾配の式に「+1」が常に居る ことです。
| 順伝播 $y$ | 逆伝播 $\dfrac{\partial y}{\partial x}$ | |
|---|---|---|
| 通常層 | $y = F(x)$ | $\dfrac{\partial F}{\partial x}$(典型 < 1)→ 掛け算で小さくなる |
| 残差ブロック | $y = F(x) + x$ | $\dfrac{\partial F}{\partial x} \mathbf{+ 1}$ → 最低でも「1」は確保 |
連鎖律で100層分掛け合わせるとき、通常層なら $0.9 \times 0.9 \times \cdots \times 0.9 \approx 0.0000266$ で消えてしまうのに、 残差ブロックなら各層で $(0.9 + 1) = 1.9$ になるため、100層通っても 消えるどころか膨らんでしまうくらい。
🌊 アニメで見る — 勾配が「高速道路」を通って届く
ResNet の本質は、勾配が深いところにも「高速道路(=ショートカット)」を通って直送される こと。下のアニメで見ると分かりやすい:
🤔 なぜ「残差」と呼ぶのか
$y = F(x) + x$ を変形すると、$F(x) = y - x$ となります。つまり $F$ は「入力 $x$ と目標出力 $y$ の差(=残差)」を学習している ことになります。
- 通常層:「$x$ を $y$ に変換する関数」を一から学習(ゼロからの学習)
- 残差ブロック:「$x$ から $y$ への 差分」だけを学習(既に $x$ をベースに持っている)
何も学ばなくても $x$ がそのまま伝わるのが残差ブロックの「初期状態」。 これは「最悪でも前と同じ性能」を保証するので、層を増やしても精度が下がりにくい性質を持ちます。
この単純な工夫で、勾配がショートカット経由で常に流れるため、勾配消失が起きない。結果、150層、1000層といった超深層が学習可能に。
3.3 効果
ResNetは ImageNet 2015 で エラー率3.6% を達成し、人間(5%)を初めて超えました。 それ以降、深層CNNの定番アーキテクチャとして、画像認識・物体検出・将棋AI(dlshogi)まで広く使われています。
💡 意外と単純: ResNetの「革命」は、ただ 「層の入力を出力に足す」 だけの工夫。 しかしこれだけで、深層学習の歴史が決定的に変わった。
4. Transformer — Attention機構の革命
2017年、Google の論文 "Attention is All You Need" で Transformer が提案されました。 自然言語処理を中心に、画像・音声・将棋まで波及した、現代AI最大の発明。 ChatGPT、Claude、Gemini はすべてTransformerベース。
📚 歴史的背景:Transformer以前、RNN/LSTMが何に苦しんでいたか(クリックで展開)
4.1 RNNの限界
Transformer以前の系列モデルは RNN(リカレントNN)/ LSTM でした。 これは「1単語ずつ順番に処理」する構造:
"猫が魚を食べた"
↓
"猫" → 状態1 → "が" → 状態2 → "魚" → 状態3 → "を" → 状態4 → "食べた" → 状態5
問題:
- 並列化できない(順番に処理する必要がある)
- 長距離の依存関係が弱い(最初の単語の情報が、長い系列を経るとぼやける)
- 学習が遅い
4.2 Attention機構の発明
Transformerの中核は Self-Attention(自己注意)。 「各単語が、他のどの単語と どれくらい関連するか を直接計算する」仕組み。
例:「猫が魚を食べた」
「食べた」という単語を理解するとき:
- 「猫」との関連 = 0.6(食べる主体)
- 「魚」との関連 = 0.8(食べる対象)
- 「が」との関連 = 0.1
- 「を」との関連 = 0.2
これらの「関連度(Attention重み)」を計算し、関連度の高い単語の情報を多く取り込む。
4.3 インタラクティブデモ:Attention重みの可視化
単語をクリックすると、その単語が 他の単語にどれくらい注目しているか(=Attention重み)が色で可視化されます。 文章を切り替えて、英語・日本語の両方で試してみてください。
文章を選択
💡 気づき:
- 「食べた」をクリックすると、主語「猫」と目的語「魚」が強く光る
- 動詞は通常、主語と目的語に注目する
- 実際のLLMでは、これを何百層も重ねて、複雑な意味理解を実現している
※ このデモのAttention重みは説明用に簡略化したもの。実際のLLMでは学習で決まる。
📐 数学の補足:Self-Attentionの数式(Query/Key/Value)(クリックで展開)
4.4 Self-Attentionの数式
実際の計算は、各単語に対して 3つのベクトル を作って行います:
- Query($Q$): 「何を探しているか」
- Key($K$): 「自分は何者か」
- Value($V$): 「自分が持っている情報」
Attention重みは「Query と Key の類似度」で決まり、Value を重み付けして取得:
Q, K, V は学習で決まる重み行列によって、入力から作られる。
4.5 Transformerが解いた3つの問題
| 問題 | RNNの場合 | Transformerの場合 |
|---|---|---|
| 並列化 | 不可能(順次処理) | 全位置を並列計算 ✅ |
| 長距離依存 | 弱い(情報が薄れる) | 直接アクセス可能 ✅ |
| 学習速度 | 遅い | 桁違いに速い ✅ |
この3つを一気に解決したことで、大規模言語モデル(LLM)が可能になりました。 GPT-3(1750億パラメータ)以降の巨大化路線は、Transformerだから可能。
📚 歴史的背景:Transformerが侵食した領域(2017〜2023年表)(クリックで展開)
4.6 Transformerが侵食した領域
- 2017: 機械翻訳(元論文)
- 2018: 自然言語理解(BERT)、生成(GPT-1)
- 2020: 画像認識(ViT, Vision Transformer)
- 2021: タンパク質構造予測(AlphaFold2)
- 2022: 一般人にAIが浸透(ChatGPT)
- 2023〜: マルチモーダル(画像+言語、音声+言語)
「言語のためのアーキテクチャ」として生まれたが、今や あらゆる分野の事実上の標準 になっている。
5. 比較表 — どれをいつ使うか
| アーキテクチャ | 得意 | 仕組みの核心 | 代表用途 |
|---|---|---|---|
| MLP | 表データ、低次元入力 | 全結合層 | NNUE評価関数、回帰、分類 |
| CNN | 画像、盤面 | 畳み込み + プーリング | ImageNet、AlphaGo、dlshogi |
| RNN/LSTM | 時系列、系列(旧主流) | 状態の引き継ぎ | 古い翻訳、音声認識(過去の主流) |
| Transformer | 自然言語、画像、音声、何でも | Self-Attention | ChatGPT、Claude、ViT、AlphaFold |
| GNN | グラフ、分子、ソーシャル | メッセージパッシング | 創薬、推薦システム |
💡 使い分けの原則: データの構造を見て、それを尊重するアーキテクチャを選ぶ。 空間構造があれば CNN、系列構造があれば Transformer、独立した特徴なら MLP。
6. JavaScriptで一気に比較(プログラマ向け)
📝 章末ボーナス:JavaScriptで4アーキテクチャを並べて見る(プログラマ向け)(クリックで展開)
ここまでの違いを JavaScript で並べると、各アーキテクチャの本質が一目で分かります。 数式や概念より、コードのほうが理解しやすいプログラマ向けの章末ボーナスです。
すべて 「forward pass(順伝播)」 の実装イメージ。
実際は tf.js や ONNX などのライブラリが行列演算を最適化していますが、ここでは 構造の対比 に集中するため 素のJSで愚直に 書いています。
補助関数 matVecMul / matMul / transpose / softmax などは別途定義されている前提です。
6.1 MLP — 全結合の基本
📌 入力:1次元配列(ベクトル)
📌 演算:行列ベクトル積を直列に重ねる
// MLP の順伝播
// x: number[] — 入力ベクトル
// weights: number[][][] — 各層の重み行列 [layer][out][in]
// biases: number[][] — 各層のバイアス [layer][out]
function mlpForward(x, weights, biases) {
let h = x;
for (let layer = 0; layer < weights.length; layer++) {
h = matVecMul(weights[layer], h); // 行列ベクトル積(全結合)
h = h.map((v, i) => v + biases[layer][i]);
h = h.map(v => Math.max(0, v)); // ReLU 活性化
}
return h; // 出力ベクトル
}
全結合:出力の各成分が、入力の全成分から影響を受ける。空間構造の概念がない。
6.2 CNN — カーネルが画像上を滑る
📌 入力:3次元配列 (H × W × C_in) の画像
📌 演算:小さなカーネルを画像全体でスライド(パラメータ共有)
// 2次元畳み込みの順伝播
// x: number[H][W][C_in] — 画像
// kernels: number[K][K][C_in][C_out] — 小さなフィルタ
// biases: number[C_out]
function conv2dForward(x, kernels, biases) {
const [H, W, Cin] = [x.length, x[0].length, x[0][0].length];
const K = kernels.length;
const Cout = kernels[0][0][0].length;
const out = createZeros3D(H - K + 1, W - K + 1, Cout);
for (let i = 0; i < H - K + 1; i++) { // 画像をスライド
for (let j = 0; j < W - K + 1; j++) {
for (let cOut = 0; cOut < Cout; cOut++) {
let sum = 0;
// ★ 同じカーネルを画像全体で使い回す(パラメータ共有)
for (let di = 0; di < K; di++)
for (let dj = 0; dj < K; dj++)
for (let cIn = 0; cIn < Cin; cIn++)
sum += x[i + di][j + dj][cIn] * kernels[di][dj][cIn][cOut];
out[i][j][cOut] = Math.max(0, sum + biases[cOut]); // ReLU
}
}
}
return out; // (H-K+1) × (W-K+1) × Cout
}
局所性 + パラメータ共有:カーネルは小さい範囲しか見ないが、それを 全位置で使い回す。 「猫の耳」が画像のどこにあっても認識できる平行移動不変性の正体。
6.3 RNN — 時刻ループで状態を持ち越す
📌 入力:2次元配列 (T 時刻 × n_input)
📌 演算:1時刻ずつ処理。前の状態を引き継ぐ
// RNN の順伝播
// xSequence: number[T][nInput] — T時刻の系列
// Wxh: number[nHidden][nInput]
// Whh: number[nHidden][nHidden] — 隠れ状態の自己結合
// bh: number[nHidden]
function rnnForward(xSequence, Wxh, Whh, bh) {
const T = xSequence.length;
const nHidden = Wxh.length;
let h = new Array(nHidden).fill(0); // 初期状態
const outputs = [];
for (let t = 0; t < T; t++) { // ★ 時刻ループ(並列化不可)
const xt = xSequence[t];
// 前の状態 h と現在の入力 xt から、新しい状態 h を作る
const Wxh_xt = matVecMul(Wxh, xt);
const Whh_h = matVecMul(Whh, h);
h = Wxh_xt.map((v, i) => Math.tanh(v + Whh_h[i] + bh[i]));
outputs.push(h);
}
return outputs; // T × nHidden
}
時間軸の再帰:for (let t = 0; t < T; t++) ループの中で、前の h を使って次の h を計算。
この 順次処理が必須 なので、並列化できない(=学習が遅い、長距離依存が弱い)。
6.4 Transformer (Self-Attention) — 全位置を一発で並列計算
📌 入力:系列データ(T × d_model)
📌 演算:Q・K・V から全位置間の関係を行列演算で一気に
// Self-Attention の順伝播
// xSequence: number[T][dModel] — T位置の系列
// Wq, Wk, Wv: 重み行列(Q/K/V を作る)
function selfAttentionForward(xSequence, Wq, Wk, Wv) {
const dK = Wq[0].length;
// ★ 各位置の Q, K, V を一発で並列計算(時刻ループなし!)
const Q = matMul(xSequence, Wq); // T × dK
const K = matMul(xSequence, Wk); // T × dK
const V = matMul(xSequence, Wv); // T × dV
// ★ 全位置間の Attention スコアを行列演算で一気に
const scores = matMul(Q, transpose(K)) // T × T
.map(row => row.map(v => v / Math.sqrt(dK)));
const attnWeights = scores.map(softmax); // 行ごとに softmax
// 重み付き和
return matMul(attnWeights, V); // T × dV
}
並列化と長距離依存:時刻ループがなく、すべての位置間の関係を 1回の行列演算 で取れる。 位置 1 と位置 1000 の関係も、隣同士と同じコストで学べる(RNNなら情報が薄れる)。これが Transformer の革命。
6.5 4アーキテクチャの違いを表で
| アーキテクチャ | 入力の形 | 核心ループ | 並列化 | パラメータ共有 |
|---|---|---|---|---|
| MLP | (n,) 1次元 | 層の直列 | ○ | なし |
| CNN | (H, W, C) 2D + ch | 空間スライド (i, j) | ○ | ★ カーネルを位置で共有 |
| RNN | (T, n) 系列 | ★ 時刻 t(順次) | ×(時刻方向) | ★ 重みを時刻で共有 |
| Transformer | (T, d) 系列 | 行列演算(ループなし) | ○ 全位置同時 | ★ Q/K/V重みを位置で共有 |
💡 プログラマ的に見る本質
- MLP:層の直列。最もシンプル
- CNN:空間ループ(i, j)で同じカーネルを使い回す
- RNN:時刻ループ(t)で前の状態を持ち越す。並列化不可
- Transformer:ループなし、全部 行列演算。GPUで爆速
これが「なぜTransformerが学習で速い・長文を扱えるのか」の答えです。
ループの有無と、共有される重みのスコープが、各アーキテクチャの本質。
6.6 NNUE(将棋AI)のJavaScript版
第1章〜第6章 で何度も登場した NNUE は、本質的に MLP です。ただし、差分更新 という工夫があります:
// NNUE の評価関数
// position: 現在の局面
// prevAccumulator: 前局面の第1層活性化(メモ化、再利用する)
function nnueEvaluate(position, prevAccumulator) {
// ★ 工夫1: 差分更新 — 全再計算しない
const diff = computeChangedFeatures(position); // 変化した特徴ビットだけ抽出
const accumulator = [...prevAccumulator];
for (const idx of diff.added) for (let k = 0; k < accumulator.length; k++) accumulator[k] += W1[k][idx];
for (const idx of diff.removed) for (let k = 0; k < accumulator.length; k++) accumulator[k] -= W1[k][idx];
// 工夫2: あとは普通のMLP(ただし整数演算 int8/int16)
const h1 = clippedRelu(accumulator);
const h2 = clippedRelu(vecAdd(matVecMul(W2, h1), b2));
const h3 = clippedRelu(vecAdd(matVecMul(W3, h2), b3));
const score = dot(W4, h3) + b4; // スカラー出力
return { score, accumulator }; // accumulator は次局面に持ち越す
}
これがCPU上で 秒間数百万局面 を可能にする理由。「1手進めるたびに変化する数ビットだけW1の列を加減算する」 という、極めてプログラマ的な工夫です。
まとめ
- データの構造に応じた専用設計が、深層学習の精度を決める
- CNN:小さなカーネルが画像上を滑る。局所性 + パラメータ共有でパラメータ激減 + 平行移動不変性
- ResNet:層の入力を出力に足す残差接続で、勾配消失を防ぎ深い学習を可能に
- Transformer:Self-Attentionで系列内の任意位置間の関係を直接学習。並列化と長距離依存を一気に解決
- Transformerは 言語以外にも侵食 し、現代AIの事実上の標準
次の章
ここまでで、深層学習の代表アーキテクチャがすべて出揃いました。 次は、これらを組み合わせて 新しいデータを生み出す「生成AI」 の世界を見ていきます。 ChatGPT、画像生成、Diffusion Model などの中身を理解しましょう。
(第6章「生成AI」準備中)