這一層實做的目的,是希望可以讓神經網路層的輸出調整到平方差為 1 平均值為 0。因為每一層的神經網路經過訓練,可能訊號會越傳越大或越小,而 normalization 可以將輸出統一尺度。
batch_example = torch.randn(2, 5)
layer = nn.Sequential(nn.Linear(5, 6), nn.ReLU())
out = layer(batch_example)
舉一個 2 段的 5 維資料為例,透過 PyTorch 的 Sequential 來處理兩層計算,第一層做線性轉換,將 5 維轉 6 維,第二層 ReLU 將負值轉為 0 正值保持不變。
在這樣情況下,計算目前的平均值與平方差,目前還不為 0 跟 1。
mean = out.mean(dim=-1, keepdim=True)
var = out.var(dim=-1, keepdim=True)
如果要達到這個結果我們需要將每一個結果減去平均,並除以標準差,經過中間這一層計算,就可以做到 Normalization。
out_norm = (out - mean) / torch.sqrt(var)
mean = out_norm.mean(dim=-1, keepdim=True)
var = out_norm.var(dim=-1, keepdim=True)
最後將這段封裝進另一個 Layer Class :
class LayerNorm(nn.Module):
def __init__(self, emb_dim):
super().__init__()
self.eps = 1e-5
self.scale = nn.Parameter(torch.ones(emb_dim))
self.shift = nn.Parameter(torch.zeros(emb_dim))
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
而我們也可以在原本的 DummyGPTModel class 中引入:
class DummyGPTModel(nn.Module):
def __init__(self, cfg):
## ... add final_norm
self.final_norm = DummyLayerNorm(cfg["emb_dim"])
self.out_head = nn.Linear(
cfg["emb_dim"], cfg["vocab_size"], bias=False
)
def forward(self, in_idx):
batch_size, seq_len = in_idx.shape
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds
x = self.drop_emb(x)
## ... add final_norm
x = self.final_norm(x)
logits = self.out_head(x)
return logits
前一段略提到了神經網路的 activation function ReLU,但現在更多的使用的是另一個名為 GELU 的 activation function,關於 ReLU 與 GELU 都大量的與數學有關,會在第三段落更多的著墨。
但現階段,可以先暫時回憶之前提到的模型三階段,Model → Loss → Optimization,之前舉了一個最簡單的 y = ax + b 作為 Model 架構的案例。實際上,一個可以更彈性解釋的模型形狀,就是 ReLU 或 GELU,透過曲線擬合來取近似值。
而一樣先開一個 GELU class 來實做數學式:
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) *
(x + 0.044715 * torch.pow(x, 3))
))
接著將此模組用來實做一個小型的神經網路模組 FeedFoward,這個模組會讓輸入的矩陣,經過一次線性層,將維度增加 4 倍、接著透過 GELU 將訊號中,讓很大的正數原樣通過、接近 0 的數只通過一部分,大負數則趨於 0。最後再進行一次線性轉換縮小 4 倍。
class FeedForward(nn.Module):
def __init__(self, cfg):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),
GELU(),
nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),
)
def forward(self, x):
return self.layers(x)
中間維度縮放的過程,有助於讓模型可以在更高為的空間中,找到更深層的特徵抽取,可以理解為像是將視野拉遠,來看有沒有某些共同 pattern。