ディープラーニング勉強記録③

Tags: Deep Learning, Introductory

ディープラーニング備忘録③

はじめに

3日目では,1D-CNNの理解を深めた後,Transformerを構成する主要な部品へ進んだ.
主なテーマは,複数層CNN,受容野,Global Average Pooling,Residual Connection,勾配消失,系列データとEmbedding,Positional Encoding,Self-Attention,Multi-Head Attention,Transformer Encoder Block,Transformerの3つの型である.

大きな流れは次の通りである.

CNNの深掘り

Residual Connection

勾配消失

系列データとEmbedding

Positional Encoding

Self-Attention

Multi-Head Attention

Transformer Encoder Block

Encoder-only / Decoder-only / Encoder-Decoder

1.2日目の軽い復習

2日目では,機械学習の目的は訓練データを丸暗記することではなく,未知データに対して正しく予測できるようにすること,つまり 汎化 であることを学んだ.

典型的な学習状態は次のように見分ける.

train loss 低い,validation loss 低い
→ 良い学習

train loss 高い,validation loss 高い
→ 未学習

train loss 低い,validation loss 高い
→ 過学習

過学習を防ぐ方法として,weight decay,Dropout,Early Stopping,Data Augmentation,Batch Normalizationなどを学んだ.

また,Adamは勾配の移動平均と勾配二乗の移動平均を使って,パラメータごとに更新量を調整するoptimizerである.

BatchNormはbatch方向の統計量を使って正規化し,LayerNormは1サンプル内のfeature方向で正規化する.
Transformerでは,batch sizeに依存しにくいLayerNormがよく使われる.


2.CNNの深掘り

2.1 Conv1dの復習

1D-CNNでは,入力を次のshapeで扱う.

[batch, channels, length]

例えば,

[64, 1, 700]

なら,

64個のデータ
1チャネル
長さ700の時系列

を意味する.

Conv1dの例として,

nn.Conv1d(
    in_channels=1,
    out_channels=16,
    kernel_size=5,
    padding=2
)

を考えると,これは,

入力チャネル数:1
出力チャネル数:16
フィルタ長:5
padding:左右に2個ずつ0を追加

という意味である.

out_channels=16 は,16種類のフィルタを学習するという意味である.
つまり,16種類の局所パターン検出器を作る.

2.2 Conv1dの出力長

dilation=1の場合,Conv1dの出力長は次の式で計算できる.

Lout=L+2PKS+1L_{\mathrm{out}} = \left\lfloor \frac{L + 2P - K}{S} \right\rfloor + 1

ここで,

である.

例えば,

L = 700
P = 2
K = 5
S = 1

なら,

Lout=700+2×251+1=700L_{\mathrm{out}} = \left\lfloor \frac{700 + 2 \times 2 - 5}{1} \right\rfloor + 1 = 700

である.

したがって,

[64, 1, 700]
↓ Conv1d(1 → 16, kernel_size=5, padding=2)
[64, 16, 700]

となる.

2.3 複数層CNN

1層のCNNでは,短い局所パターンを検出する.
しかし,CNNを複数層重ねることで,より広い範囲の情報を使えるようになる.

CNNでは,層ごとに次のような特徴を学習すると考えられる.

低層:単純な局所特徴
中層:局所特徴の組み合わせ
高層:より抽象的な特徴

例えば時系列では,

低層:
小さなピーク,急な上昇,急な下降,局所的な振動

中層:
ピークの後に谷がある,複数の局所パターンが連続する

高層:
より広い範囲の特徴的な構造

のようなイメージである.


3.受容野

受容野 とは,ある出力値が入力のどの範囲を見て計算されているかを表す.
英語では receptive field という.

例えば,kernel size=5のConv1dを1層だけ使うと,各出力位置は入力の連続する5点を見る.
この場合,受容野は5である.

stride=1,dilation=1で,kernel sizeを KK,層数を nn とすると,受容野はおおよそ,

1+n(K1)1 + n(K-1)

である.

例えば,K=5K=5n=3n=3 なら,

1+3(51)=131 + 3(5-1) = 13

である.

つまり,kernel size=5の畳み込みを3層重ねると,1つの出力が元入力の13点ぶんに依存する.

受容野を広げる方法には次がある.

層を深くする
kernel sizeを大きくする
strideを大きくする
poolingを使う
dilationを使う

dilationは,フィルタの中に間隔を空ける方法である.
例えばkernel size=3,dilation=2なら,連続する3点ではなく1つ飛ばしで見る.
これにより,パラメータ数を増やさずに広い範囲を見られる.


4.poolingとGlobal Pooling

4.1 pooling

poolingは,近傍の値を集約して,系列長や空間サイズを小さくする処理である.

例えば,

入力: [1, 3, 2, 5, 0, 4]

に対して MaxPool1d(kernel_size=2) を使うと,

max(1, 3) = 3
max(2, 5) = 5
max(0, 4) = 4

なので,

出力: [3, 5, 4]

となる.

poolingは,畳み込みのように重みを学習する処理ではなく,特徴マップを要約する操作である.

MaxPool:
局所範囲で最も強く反応した値を残す

AveragePool:
局所範囲の平均的な反応を残す

4.2 flattenの問題

1日目の簡単な1D-CNNでは,次のような構造を使った.

[64, 1, 700]
↓ Conv1d
[64, 16, 700]
↓ MaxPool1d(2)
[64, 16, 350]
↓ flatten
[64, 5600]
↓ Linear(5600 → 256)
[64, 256]

このとき,最後のLinear層は,

nn.Linear(16 * 350, 256)

である.

重み数は,

(16×350)×256=1,433,600(16 \times 350) \times 256 = 1,433,600

である.

biasを含めるとさらに256個増える.
つまり,畳み込み層単体は軽くても,flatten後の全結合層が非常に大きくなることがある.

4.3 Global Average Pooling

flattenの問題を軽くする方法の1つが Global Average Pooling である.
略してGAPと呼ばれることがある.

1Dの場合,Global Average Poolingはlength方向に平均を取る.

[batch, channels, length]
↓ length方向に平均
[batch, channels]

例として,

[64, 16, 350]
↓ length方向に平均
[64, 16]

である.

その後,分類層に入れるなら,

nn.Linear(16, 256)

で済む.

重み数は,

16×256=409616 \times 256 = 4096

である.

flattenの場合の,

5600×256=1,433,6005600 \times 256 = 1,433,600

と比べて非常に少ない.

4.4 GAPは情報を消す

Global Average Poolingは便利だが,かなり大胆に情報を捨てる.

例えば,

[0, 0, 10, 0, 0]

の平均は2である.

一方,

[2, 2, 2, 2, 2]

の平均も2である.

したがって,GAP後には,

一箇所だけ強く反応した
全体的に弱く反応した

という違いが消える.

GAPは,

どこに出たか

を捨てて,

その特徴が全体としてどれくらい出たか

を残す処理である.

そのため,位置情報が重要な問題では注意が必要である.

4.5 Global Max Pooling

Global Max Poolingは,length方向の最大値を取る.

[batch, channels, length]
↓ length方向にmax
[batch, channels]

Global Average Poolingは,

全体としてどれくらい反応したか

を見る.

Global Max Poolingは,

どこかで強く反応したか

を見る.

どちらが良いかはタスクによる.


5.nn.Linearとunsqueeze

5.1 nn.Linear

nn.Linear(in_features, out_features) は,全結合層である.
やっている処理は,

y=xWT+by = xW^T + b

である.

例えば,

nn.Linear(16, 256)

は,

16次元の入力ベクトルを受け取り,
256次元の出力ベクトルを作る

という意味である.

PyTorchでは,

weight.shape = [256, 16]
bias.shape   = [256]

である.

入力がbatch付きで,

x.shape = [batch, 16]

なら,内部では,

y = x @ weight.T + bias

を計算する.

shapeは,

x.shape        = [batch, 16]
weight.T.shape = [16, 256]
y.shape        = [batch, 256]

である.

nn.Linear は,普通に一層の全結合ニューラルネットワークを行っているだけだと理解できる.

CNNでは,

Conv部分 = 特徴抽出器
Linear部分 = 分類器

と考えられる.

5.2 unsqueeze

unsqueeze は,テンソルにサイズ1の次元を追加する操作である.
値そのものは変えない.
0で埋める操作ではない.

例えば,

x = torch.tensor([10, 20, 30])

なら,shapeは,

[3]

である.

x.unsqueeze(0) は,

[[10, 20, 30]]

となり,shapeは,

[1, 3]

になる.

x.unsqueeze(1) は,

[[10],
 [20],
 [30]]

となり,shapeは,

[3, 1]

になる.

CNNでは,元の入力が,

[64, 700]

のとき,Conv1dに入れるために,

x = x.unsqueeze(1)

として,

[64, 700]

[64, 1, 700]

にする.

ここで追加された1は,チャネル数が1であることを表す.


6.CNNのチャネル設計と位置ズレ

CNNでは,層が深くなるにつれてチャネル数を増やすことが多い.

例えば,

length:700 → 350 → 175
channels:16 → 32 → 64

のような設計である.

これは,

位置方向は粗くする
代わりに,各位置で持てる特徴の種類を増やす

という考え方である.

低層では,単純な局所特徴を検出する.
高層では,それらの組み合わせによるより多様で抽象的な特徴を表現したい.

したがって,深くなるにつれてチャネル方向に豊かな特徴を持たせることが多い.

ただし,分類直前やボトルネック構造ではチャネル数を減らして情報を集約する設計も普通に使われる.

Convは,同じフィルタを全位置に適用するため,同じパターンが少しずれても検出しやすい.
ただし,出力特徴マップ上では反応位置もずれる.

つまりConvは,平行移動に対して equivariant である.

入力パターンが右にずれる

特徴マップ上の反応も右にずれる

一方,分類では,

どこに出たかに関係なく同じクラスと判断したい

ことがある.

このとき,poolingやGlobal Poolingによって位置ズレに対して invariant に近づける.

equivariance:
入力が変換されると,出力も対応して変換される

invariance:
入力が変換されても,出力が変わらない

7.Residual Connection

Residual Connectionは,層の出力に入力をそのまま足す仕組みである.

通常の層は,

y=F(x)y = F(x)

である.

Residual Connectionでは,

y=x+F(x)y = x + F(x)

とする.

ここで,xx は生データそのものではなく,そのblockに入ってきた中間特徴表現である.

生データ

Conv / Linear など

中間特徴 x

Residual Block
    F(x)を計算
    y = x + F(x)

つまり,Residual Connectionは,

現在の特徴表現に,学習した修正量を足す

仕組みである.

Residualは残差という意味である.
F(x)F(x) は,

入力xに対して,どれだけ修正を加えるか

を学習していると見なせる.

不要な層であれば,

F(x)0F(x) \approx 0

となれば,

yxy \approx x

であり,ほぼ素通しできる.

Residual Connectionでは,

x+F(x)x + F(x)

を計算するため,xxF(x)F(x) のshapeが同じでなければならない.

shapeが違う場合は,shortcut側にも変換を入れる.
例えば1D-CNNでは,

nn.Conv1d(16, 32, kernel_size=1, stride=2)

のような1x1 Convを使い,チャネル数や長さを合わせる.


8.勾配消失とResidualの逆伝播

8.1 勾配消失

勾配消失とは,深いネットワークで入力に近い層まで勾配がほとんど届かなくなる現象である.

ニューラルネットワークは合成関数であるため,逆伝播では連鎖律により,各層の微分が何度も掛け合わされる.

例えば,

Lh1=Lhnhnhn1hn1hn2h2h1\frac{\partial L}{\partial h_1} = \frac{\partial L}{\partial h_n} \frac{\partial h_n}{\partial h_{n-1}} \frac{\partial h_{n-1}}{\partial h_{n-2}} \cdots \frac{\partial h_2}{\partial h_1}

である.

もし各層の微分が0.5程度だとしても,10層なら,

0.510=0.00097656250.5^{10} = 0.0009765625

である.

30層ならほぼ0に近くなる.
これが勾配消失の基本的なイメージである.

8.2 sigmoid,tanh,ReLU

sigmoidは,

σ(x)=11+ex\sigma(x)=\frac{1}{1+e^{-x}}

である.

sigmoidの微分は最大でも0.25であるため,深いネットワークでは勾配消失が起きやすい.
また,大きな正や負の入力では出力が0や1に張り付き,微分がほぼ0になる.
これを飽和という.

ReLUは,

ReLU(x)=max(0,x)\mathrm{ReLU}(x)=\max(0,x)

である.

微分は,

ReLU(x)={1x>00x<0\mathrm{ReLU}'(x) = \begin{cases} 1 & x > 0 \\ 0 & x < 0 \end{cases}

である.

正の領域では微分が1なので,sigmoidやtanhより勾配消失しにくい.
ただし,負の領域では勾配が0になるため,dead ReLUが起きる可能性がある.

8.3 Residual Connectionの逆伝播

Residual blockを,

y=x+F(x)y = x + F(x)

とする.

ここで,

u=F(x)u = F(x)

と置くと,

y=x+uy = x + u

である.

足し算ノードでは,

yx=1\frac{\partial y}{\partial x} = 1 yu=1\frac{\partial y}{\partial u} = 1

なので,後ろから来た勾配を両方にそのまま流す.

後ろから来る勾配を,

gy=Lyg_y = \frac{\partial L}{\partial y}

とすると,skip経路には,

gxskip=gyg_x^{\mathrm{skip}} = g_y

が流れる.

F経路には,

gu=gyg_u = g_y

が流れ,さらにFを逆伝播して,

gxF=gyFxg_x^F = g_y \frac{\partial F}{\partial x}

となる.

xx はforwardで2箇所に使われているため,最終的なxxへの勾配は2経路から来た勾配の和である.

gx=gxskip+gxFg_x = g_x^{\mathrm{skip}} + g_x^F

したがって,

gx=gy+gyFxg_x = g_y + g_y \frac{\partial F}{\partial x} gx=gy(I+Fx)g_x = g_y \left( I + \frac{\partial F}{\partial x} \right)

となる.

つまり,F経路の微分が小さくても,skip経路から gyg_y がそのまま戻れる.
これがResidual Connectionによって勾配が流れやすくなる理由である.


9.Transformerの入力表現

1D-CNNでは,時系列データを,

[batch, channels, length]

として扱った.

Transformerでは,多くの場合,

[batch, sequence_length, embedding_dim]

として扱う.

例えば,

[64, 700, 128]

なら,

64個のデータ
各データは700個のtokenからなる
各tokenは128次元ベクトル

である.

ここで,

sequence_length = tokenの個数
embedding_dim = 各tokenを表すベクトルの次元

である.

時系列データでは,各時刻を1 tokenにする方法と,短い区間を1 tokenにするpatch化がある.

各時刻を1 token:
[batch, 700, 1]
↓ Linear(1 → 128)
[batch, 700, 128]

短い区間を1 token:
[batch, 700]
↓ reshape
[batch, 70, 10]
↓ Linear(10 → 128)
[batch, 70, 128]

Embeddingとは,生の入力をモデル内部で扱いやすいベクトル表現に変換することである.


10.Positional Encoding

TransformerのSelf-Attentionは,token同士の関係を見るが,そのままでは順序情報を自然には持たない.

そのため,token embeddingに位置情報を加える必要がある.
これが Positional Encoding である.

Transformer入力は,多くの場合,

token embedding + positional encoding

で作る.

入力が,

x.shape = [batch, sequence_length, embedding_dim]

で,Positional Encodingが,

pos.shape = [sequence_length, embedding_dim]

なら,batch方向にbroadcastして足す.

x.shape   = [64, 700, 128]
pos.shape = [    700, 128]

x + pos   = [64, 700, 128]

Positional Encodingを足してもshapeは変わらない.

10.1 sin/cos型Positional Encoding

元のTransformerでは,sinとcosを使ったPositional Encodingが使われた.

位置を pospos,次元番号を ii,モデル次元を dmodeld_{\mathrm{model}} とすると,

PE(pos,2i)=sin(pos100002i/dmodel)PE(pos, 2i) = \sin\left( \frac{pos}{10000^{2i/d_{\mathrm{model}}}} \right) PE(pos,2i+1)=cos(pos100002i/dmodel)PE(pos, 2i+1) = \cos\left( \frac{pos}{10000^{2i/d_{\mathrm{model}}}} \right)

である.

ここで言う「次元ごと」とは,embeddingベクトルの各成分ごとという意味である.

例えば,dmodel=128d_{\mathrm{model}}=128 なら,Positional Encodingは128次元であり,

0次元目:短い周期のsin
1次元目:短い周期のcos
2次元目:少し長い周期のsin
3次元目:少し長い周期のcos
...

のように,周期の異なるsin/cosペアで位置を表現する.

より正確には,

2i次元と2i+1次元は同じ周期のsin/cosペア
iが変わると周期が変わる

である.

[ \sin\left(\frac{pos}{a}\right) ]

の周期は,

2πa2\pi a

である.

ここで,

a=100002i/dmodela = 10000^{2i/d_{\mathrm{model}}}

なので,ii が大きくなると周期が長くなる.

10.2 Positional Encodingは衝突しないのか

sinやcosは周期関数なので,各次元だけを見ると周期的に同じ値が出る.
しかし,sin/cos型Positional Encodingは複数の異なる周期のsin/cosペアを組み合わせているため,ベクトル全体が通常の系列長で簡単に衝突することはほぼない.

ただし,非常に長い系列や相対距離が重要な場合には,単純な絶対位置encodingだけでは不十分なことがある.
そのため,relative positional encoding,relative position bias,RoPE,ALiBiなどの方法も使われる.

10.3 位置情報を足す意味

token embeddingを eie_i,位置encodingを pip_i とすると,Transformerへの入力は,

xi=ei+pix_i = e_i + p_i

である.

これにより,内容情報と位置情報は同じベクトル内に混ざる.

これは,単に同じ単語を異なる位置で識別するだけではなく,

順序
前後関係
距離関係を推定する材料

をモデルに与えるためである.


11.Self-Attention

Self-Attentionは,各tokenが他のどのtokenをどれくらい参考にするかを決める仕組みである.

各tokenから次の3種類のベクトルを作る.

Query
Key
Value

略して,

Q
K
V

である.

役割は次の通りである.

Query:
私は何を探しているか

Key:
私はどんな情報を持っているか,何に反応すべきか

Value:
実際に渡す中身

検索で例えると,

Query:検索語
Key:各ページのタイトルやタグ
Value:ページ本文
attention weight:検索結果の関連度

である.

あるtoken ii のQuery qiq_i と,token jj のKey kjk_j の内積を取る.

scoreij=qikjscore_{ij} = q_i \cdot k_j

scoreをsoftmaxに通すことで,attention weightにする.

αij=exp(scoreij)jexp(scoreij)\alpha_{ij} = \frac{\exp(score_{ij})} {\sum_{j'} \exp(score_{ij'})}

その後,Valueを重み付き和する.

oi=jαijvjo_i = \sum_j \alpha_{ij} v_j

つまり,

QK^Tで「どこを見るか」を決める
softmaxでattention weightにする
Valueを重み付き和して「何を集めるか」を決める

という流れである.

Valueは「参照先から実際に持ってくる情報」である.
attention weightだけでは「どこを見るか」は分かるが,次の層へ渡す新しいtoken表現を作れない.


12.Self-Attentionのshapeと scaled dot-product attention

入力を,

X.shape = [batch, seq_len, d_model]

とする.

例えば,

X.shape = [64, 700, 128]

である.

Linear層でQ,K,Vを作る.

q_proj = nn.Linear(128, 128)
k_proj = nn.Linear(128, 128)
v_proj = nn.Linear(128, 128)

Q = q_proj(X)
K = k_proj(X)
V = v_proj(X)

shapeは,

Q.shape = [64, 700, 128]
K.shape = [64, 700, 128]
V.shape = [64, 700, 128]

である.

1サンプルだけ見ると,

Q.shape = [700, 128]
K.shape = [700, 128]
K.T.shape = [128, 700]

なので,

Q @ K.T = [700, 128] @ [128, 700]
        = [700, 700]

である.

batch付きでは,

scores.shape = [64, 700, 700]

となる.

この [700, 700] は,

各tokenが各tokenをどれくらい見るか

を表す関係表である.

Self-Attentionの基本式は,

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

である.

dk\sqrt{d_k} で割るのは,内積の典型的な標準偏差を揃えるためである.
各成分が平均0,分散1程度なら,内積の分散はおおよそ dkd_k になり,標準偏差は dk\sqrt{d_k} になる.


13.Self-Attentionは可変長を扱えるか

Self-Attention自体は原理的には可変長系列を扱える.

[batch, 700, d_model]
[batch, 500, d_model]
[batch, 1000, d_model]

のように,seq_lenが変わっても計算はできる.

ただし,batch内ではshapeを揃える必要があるため,paddingを使うことが多い.
paddingした部分は本物のtokenではないため,attention maskで無視する必要がある.

また,learnable positional embeddingでは最大長の制約がある.
sin/cos型なら任意の位置に対して式で計算できるため,長さの外挿に比較的強い.


14.Multi-Head Attention

Multi-Head Attentionは,Self-Attentionを複数のheadで並列に行う方法である.

例えば,

d_model = 128
num_heads = 8

なら,各headの次元は,

dhead=1288=16d_{\mathrm{head}} = \frac{128}{8} = 16

である.

Multi-Head Attentionでは,同じ入力 XX からheadごとに異なるQ,K,Vを作る.
これは,headごとに異なる学習可能なLinear変換を持つためである.

実装では,headごとに別々のLinearを明示的に作るのではなく,大きなLinearを1つ使ってから分割することが多い.

例えば,

q_proj = nn.Linear(128, 128)

で作った128次元のQを,

8 heads × 16 dim

として分割する.

[64, 700, 128]
↓ reshape
[64, 700, 8, 16]
↓ transpose
[64, 8, 700, 16]

対応する重み行列も実質的にはheadごとの重みブロックに分かれている.
これらはランダム初期化によって最初から異なる値を持ち,学習中に異なる役割へ分化し得る.

14.1 Multi-Head Attentionのshape

入力を,

X.shape = [64, 700, 128]

とする.

Q,K,Vを作ると,

Q.shape = [64, 700, 128]
K.shape = [64, 700, 128]
V.shape = [64, 700, 128]

である.

これを8headに分けると,

Q.shape = [64, 8, 700, 16]
K.shape = [64, 8, 700, 16]
V.shape = [64, 8, 700, 16]

となる.

Attention scoreは,

scores = Q @ K.transpose(-2, -1)

であり,

Q.shape           = [64, 8, 700, 16]
K.transpose.shape = [64, 8, 16, 700]
scores.shape      = [64, 8, 700, 700]

となる.

softmax後も,

A.shape = [64, 8, 700, 700]

である.

Valueを混ぜると,

A.shape = [64, 8, 700, 700]
V.shape = [64, 8, 700, 16]

A @ V   = [64, 8, 700, 16]

となる.

各headの出力をconcatすると,

[64, 8, 700, 16]
↓ transpose
[64, 700, 8, 16]
↓ concat
[64, 700, 128]

である.

最後にoutput projectionをかける.

out_proj = nn.Linear(128, 128)

shapeは,

[64, 700, 128]
↓ Linear(128 → 128)
[64, 700, 128]

で変わらない.

14.2 headはどこを見ているか分かるか

学習済みモデルに入力を入れて,各headのattention weightを取り出せば,ある程度は分かる.

Multi-Head Attentionのattention weightは,例えば,

A.shape = [batch, num_heads, seq_len, seq_len]

である.

例えば,

A[0, 3, 100, 250]

は,

batch内の0番目のデータで,
head 3 において,
token 100 が token 250 をどれくらい見たか

を表す.

ただし,attention weightを見ることは,モデルの最終判断理由を完全に説明することとは同じではない.
Attentionの後には,Valueの混合,output projection,Residual Connection,LayerNorm,FFN,次の層などが続くためである.


15.Transformer Encoder Block

Transformer Encoder Blockは,次の部品で構成される.

Multi-Head Self-Attention
Residual Connection
LayerNorm
Feed Forward Network
Residual Connection
LayerNorm

Post-LN型では,

h=LayerNorm(x+MHA(x))h = \mathrm{LayerNorm}(x + \mathrm{MHA}(x)) y=LayerNorm(h+FFN(h))y = \mathrm{LayerNorm}(h + \mathrm{FFN}(h))

である.

Pre-LN型では,

h=x+MHA(LayerNorm(x))h = x + \mathrm{MHA}(\mathrm{LayerNorm}(x)) y=h+FFN(LayerNorm(h))y = h + \mathrm{FFN}(\mathrm{LayerNorm}(h))

である.

Pre-LNは深いTransformerで学習が安定しやすいことが多い.

15.1 LayerNorm

LayerNormは,各tokenの特徴ベクトルの中で平均と分散を整える正規化である.

token表現を,

h=[h1,h2,,hd]h = [h_1, h_2, \ldots, h_d]

とすると,

μ=1dj=1dhj\mu = \frac{1}{d}\sum_{j=1}^{d}h_j σ2=1dj=1d(hjμ)2\sigma^2 = \frac{1}{d}\sum_{j=1}^{d}(h_j-\mu)^2

を計算し,

h^j=hjμσ2+ϵ\hat{h}_j = \frac{h_j-\mu}{\sqrt{\sigma^2+\epsilon}}

とする.

さらに学習可能なscaleとshiftを使って,

yj=γjh^j+βjy_j = \gamma_j \hat{h}_j + \beta_j

とする.

入力が,

[batch, seq_len, d_model]

なら,LayerNormは通常,最後の d_model 方向に対して行われる.
つまり,各tokenごとに,そのtoken内部の特徴分布を整える.

15.2 Feed Forward Network

Transformer Encoder Block内のFFNは,tokenごとに独立に適用される小さなMLPである.

典型的には,

FFN(x)=W2σ(W1x+b1)+b2\mathrm{FFN}(x) = W_2 \sigma(W_1x + b_1) + b_2

である.

構造としては,

Linear(d_model → d_ff)
Activation
Linear(d_ff → d_model)

である.

例えば,

d_model = 128
d_ff = 512

なら,

[64, 700, 128]
↓ Linear(128 → 512)
[64, 700, 512]
↓ GELU/ReLU
[64, 700, 512]
↓ Linear(512 → 128)
[64, 700, 128]

となる.

Attentionはtoken間の情報交換を担当し,FFNは各token内部の特徴加工を担当する.

Attention:
どこから情報を集めるか

FFN:
集めた情報をどう加工するか

である.

15.3 Encoderを重ねる意味

Self-Attentionは1層でも全tokenを見られる.
しかし,Encoder Blockを重ねる意味はある.

理由は,2層目以降のAttentionは,元のtoken embeddingではなく,前の層で更新されたtoken表現を見るからである.

1層目:
元の埋め込み表現どうしの関係を見る

2層目:
1層目で文脈を取り込んだ表現どうしの関係を見る

3層目:
さらに高次の文脈表現どうしの関係を見る

つまり,Encoderを重ねる意味は,単に見る範囲を広げるためではなく,表現を段階的に洗練するためである.


16.Transformerによる分類

Transformer Encoderの出力は,

[batch, seq_len, d_model]

である.

これは各tokenごとの文脈化された表現である.

系列全体に対して1つのクラスを予測する場合,この出力を集約する必要がある.

代表的な方法は次である.

CLS tokenを使う
Global Average Poolingする
Global Max Poolingする
Attention Poolingする
最後のtokenを使う

分類の流れは,

生入力
[batch, length]

↓ embedding
[batch, seq_len, d_model]

↓ positional encoding
[batch, seq_len, d_model]

↓ Transformer Encoder Blocks
[batch, seq_len, d_model]

↓ pooling or CLS
[batch, d_model]

↓ Linear classifier
[batch, num_classes]

である.

例えば,

[64, 700]
↓ Linear/Conv embedding
[64, 700, 128]
↓ Transformer Encoder
[64, 700, 128]
↓ mean pooling
[64, 128]
↓ Linear(128 → 256)
[64, 256]

となる.


17.Transformerの3つの型

Transformerには大きく分けて3種類の使い方がある.

Encoder-only
Decoder-only
Encoder-Decoder

17.1 Encoder-only

Encoder-onlyは,Transformer Encoderだけを使うモデルである.
各tokenが前後両方のtokenを見られる双方向Self-Attentionを使う.

向いているタスクは,

分類
特徴抽出
系列ラベリング
穴埋め
異常検知

などである.

BERT系が代表例である.

17.2 Decoder-only

Decoder-onlyは,未来を見ないcausal self-attentionを使い,次tokenを予測するモデルである.
GPT系はこのタイプである.

プロンプトも生成済みtokenも,すべて1本のtoken列として扱い,過去文脈を参照しながら次tokenを順に生成する.

プロンプトtoken列

Decoder-only Transformer

次tokenを予測

そのtokenを末尾に追加

また次tokenを予測

17.3 Encoder-Decoder

Encoder-Decoder型は,元祖Transformerの機械翻訳モデルに近い構造である.

入力系列

Encoder

Encoder出力

Decoder

出力系列

翻訳や要約のように,入力系列から出力系列を生成するタスクに向いている.


18.maskとCross-Attention

18.1 causal mask

Decoder-onlyやEncoder-DecoderのDecoderでは,未来のtokenを見てはいけない.
そのため,causal maskを使う.

例えば5 tokenある場合,見ることができる範囲は下三角になる.

      見られる側
        1 2 3 4 5
見る 1  ○ × × × ×
側   2  ○ ○ × × ×
     3  ○ ○ ○ × ×
     4  ○ ○ ○ ○ ×
     5  ○ ○ ○ ○ ○

現在位置より未来のtokenを見えなくすることで,次token予測で未来の正解を見てしまうことを防ぐ.

18.2 Self-AttentionとCross-Attention

Self-Attentionでは,Q,K,Vは同じ系列から作る.

Q = XW_Q
K = XW_K
V = XW_V

Cross-Attentionでは,

QはDecoder側から作る
K,VはEncoder出力から作る

式で書くと,

Q=HdecWQQ = H_{\mathrm{dec}}W_Q K=HencWKK = H_{\mathrm{enc}}W_K V=HencWVV = H_{\mathrm{enc}}W_V

である.

つまり,Decoderは自分が今生成している途中の表現をQueryとして使い,Encoderが作った入力系列の表現をKey/Valueとして参照する.

整理すると,

種類QKV役割
Self-Attention同じ系列同じ系列同じ系列系列内のtoken同士を見る
Cross-AttentionDecoder側Encoder側Encoder側出力側が入力側を参照する

19.GPT系はDecoder-only

元祖Transformerの図を知っていると,プロンプトをEncoderに入れてDecoderが返答を生成しているように感じるかもしれない.
しかし,GPT系は基本的にDecoder-only型である.

プロンプトはEncoderに入るのではなく,token列としてDecoder-only Transformerに入力される.

[ユーザーのプロンプト tokens] + [これまでに生成した回答 tokens]

Decoder-only Transformer

次tokenの分布

次tokenを選ぶ

系列末尾に追加

繰り返す

Decoder-onlyでは,causal maskにより,各tokenはそれ以前のtokenだけを見る.
これにより,プロンプト全体と生成済み文脈を参照しながら,次tokenを予測できる.


3日目の重要まとめ

3日目の内容をまとめると,次の通りである.

CNNは複数層重ねることで階層的特徴を学習する
受容野は,ある出力が入力のどの範囲を見ているかを表す
poolingは近傍の値を集約してlengthを短くする
Global Average Poolingはlength方向を平均し [batch, channels] にする
GAPはパラメータ数を大幅に減らせるが,位置情報を失う
nn.Linearは y = xW^T + b を計算する全結合層である
unsqueezeは値を変えず,サイズ1の次元を追加する
CNNではlengthを縮め,channelsを増やす設計がよく使われる
Convは平行移動に対してequivariantである
PoolingやGlobal Poolingでinvarianceに近づける

Residual Connectionは y = x + F(x) という構造である
Residualのxは生データではなく,そのblockに入ってきた中間特徴である
F(x)は現在の特徴表現に加える修正量として学習される
Residualは情報と勾配の通り道を作る
足し算ノードは逆伝播で勾配を両方の入力にそのまま流す
xへの勾配はskip経路とF経路からの勾配の和になる

Transformerでは系列を [batch, sequence_length, embedding_dim] で扱う
Embeddingは生入力をtokenごとのベクトル表現に変換する
Positional Encodingはtoken embeddingに位置情報を加える
sin/cos型Positional Encodingは複数周期のsin/cosで位置を表す

Self-Attentionは,各tokenが他tokenをどれくらい見るかを学習する
Qは探すもの,Kは照合される見出し,Vは取り出す中身である
QK^Tでattention weightを作り,その重みでVを混ぜる
sqrt(d_k)で割るのは,内積の典型的な標準偏差を揃えるためである

Multi-Head Attentionは複数のAttention headを並列に使う
headごとに異なるQ,K,V変換を持ち,異なる関係を捉え得る
各headの出力をconcatし,output projectionで混ぜ直す

Transformer Encoder Blockは,MHA,Residual,LayerNorm,FFNからなる
Attentionはtoken間の情報交換,FFNはtoken内部の特徴加工を担当する
Encoderを重ねることで,token表現を段階的に洗練する
TransformerにはEncoder-only,Decoder-only,Encoder-Decoderがある
分類にはEncoder-only,生成にはDecoder-onlyやEncoder-Decoderが使われる
GPT系は基本的にDecoder-only型である

現在の進捗メモ

3日目終了時点で,深層学習の主要部品のかなり多くを一度通過した.

分類問題
softmax / cross entropy
勾配降下法 / 逆伝播
MLP
1D-CNN
過学習と正則化
optimizer
BatchNorm / LayerNorm
Residual Connection
CNNの受容野とGlobal Pooling
Embedding
Positional Encoding
Self-Attention
Multi-Head Attention
Transformer Encoder Block
Transformerの3類型

次に進む候補は次である.

1.Transformerを時系列分類に使う全体設計
2.CLS token / pooling / attention poolingの比較
3.RNN / LSTM / GRUの基礎
4.評価指標 accuracy / precision / recall / F1 / confusion matrix
5.論文読解用の実験結果の読み方

Transformerの主要部品はかなり見えてきたため,次は「実際に時系列分類モデルとしてどう組むか」を整理するとよい.