大模型預(yù)訓(xùn)練:Pre-Training如何讓模型變聰明
模型的訓(xùn)練過程就是不斷調(diào)整權(quán)重的過程,準(zhǔn)確一點(diǎn)還應(yīng)該加上偏置,模型的訓(xùn)練過程就是不斷調(diào)整權(quán)重和偏置的過程,調(diào)整的過程依賴反向傳播、損失函數(shù)等等。
數(shù)據(jù)集準(zhǔn)備
準(zhǔn)備一些訓(xùn)練數(shù)據(jù),可以用 CSV 格式的文件存儲(chǔ)。
圖片
將數(shù)據(jù)集分割為訓(xùn)練集和測試集,常用的比例為 80% 訓(xùn)練集和 20% 測試集,確保模型能在未見過的數(shù)據(jù)上進(jìn)行有效預(yù)測。
初始化參數(shù)
這里說的參數(shù)主要是指權(quán)重 (W) 和偏置 (b) 。這兩個(gè)變量的初始值非常關(guān)鍵,因?yàn)樗鼈兛梢杂绊懢W(wǎng)絡(luò)的收斂速度,以及是否能夠收斂到一個(gè)好的解。選擇好的初始值可以避免一些問題,如梯度消失或梯度爆炸。我們來看一些常用的權(quán)重和偏置初始化方法。
隨機(jī)初始化
在神經(jīng)網(wǎng)絡(luò)權(quán)重初始化時(shí),通常會(huì)選用較小的隨機(jī)數(shù),這些隨機(jī)值可從均勻分布或正態(tài)分布中抽取。例如,一種常見做法是從 “均值為 0、標(biāo)準(zhǔn)差為 1/√n(n 為當(dāng)前層的輸入節(jié)點(diǎn)數(shù))” 的正態(tài)分布中采樣 —— 這種初始化方式的具體名稱,會(huì)根據(jù)所選分布的方差差異,被稱為 He 初始化、Glorot 初始化或 Xavier 初始化。
以我們之前定義的 Transformer 模型為例,其解碼器層默認(rèn)采用的權(quán)重初始化方法就是 Glorot 初始化。
至于偏置的初始化,一般會(huì)將其設(shè)為 0,或取值極小的正數(shù)(比如 0.01)。這樣設(shè)計(jì)的核心原因是:在模型訓(xùn)練初期,不希望偏置對(duì)輸出結(jié)果產(chǎn)生過大干擾,而是讓模型優(yōu)先通過調(diào)整權(quán)重來學(xué)習(xí)數(shù)據(jù)中的特征與模式。
常數(shù)初始化
將所有權(quán)重或偏置設(shè)置為同一個(gè)常數(shù),比如 0。不過我不太推薦這種方法,因?yàn)樗鼤?huì)導(dǎo)致神經(jīng)網(wǎng)絡(luò)在訓(xùn)練初期每個(gè)神經(jīng)元的行為都相同,這會(huì)阻礙有效的學(xué)習(xí)。特定
分布初始化
對(duì)于某些特定的網(wǎng)絡(luò)架構(gòu)或激活函數(shù),可能需要特定的初始化方法。例如,使用 ReLU 激活函數(shù)的時(shí)候,He 初始化,也就是使用較大的方差來初始化權(quán)重,通常效果更好,因?yàn)樗紤]到了 ReLU 在負(fù)值上的非激活特性。
正交初始化
在某些情況下,特別是在訓(xùn)練深層網(wǎng)絡(luò)或循環(huán)神經(jīng)網(wǎng)絡(luò)(RNNs)的時(shí)候,使用正交初始化方法來初始化權(quán)重有助于減少梯度消失或爆炸的問題。正交初始化保證了權(quán)重矩陣的行或列是正交的,這有助于保持激活和梯度在不同層間的獨(dú)立性。
我們可以查看源代碼來了解各個(gè)層,如 Embedding 層、Linear 層、編碼器、解碼器層使用的初始化方法。使用下面的方法查看默認(rèn)的參數(shù)值:
print(decoder_layer.self_attn.in_proj_weight)在實(shí)際應(yīng)用中,如果默認(rèn)的初始化策略不滿足特定的需求,你也可以用下面的代碼,通過自定義函數(shù)并使用 .apply() 方法來對(duì)模型的所有參數(shù)進(jìn)行自定義初始化。這種方法非常靈活,適用于復(fù)雜的模型結(jié)構(gòu)。
# 創(chuàng)建一個(gè)TransformerDecoderLayer實(shí)例
decoder_layer = nn.TransformerDecoderLayer(d_model=512, nhead=8)
# 如果需要自定義初始化
def custom_init(m):
if isinstance(m, nn.Linear):
torch.nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
torch.nn.init.constant_(m.bias, 0.0)
# 應(yīng)用自定義初始化
decoder_layer.apply(custom_init)前向傳播
在訓(xùn)練過程中,前向傳播就是 Embedding 后的輸入向量一層一層向后傳遞的過程,每一層都有權(quán)重和偏置,我們看一下 Linear 層的權(quán)重和參數(shù)是怎么賦值的。
self.fc = nn.Linear(embed_size, vocab_size)nn.Linear 定義:
def __init__(self, in_features: int, out_features: int, bias: bool = True,
device=None, dtype=None) -> None:
factory_kwargs = {'device': device, 'dtype': dtype}
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs))
if bias:
self.bias = Parameter(torch.empty(out_features, **factory_kwargs))
else:
self.register_parameter('bias', None)
self.reset_parameters()權(quán)重(Weights):一個(gè)形狀為 (embed_size, num_features) 的矩陣。
偏置(Bias):一個(gè)形狀為 (embed_size,) 的向量。
當(dāng)你通過這一層傳遞輸入 input 時(shí)(input = self.fc1(input)),它實(shí)際上執(zhí)行的操作是矩陣乘法加上偏置項(xiàng),你可以看一下計(jì)算公式:output=input×weightT+bias
這里的 “T” 代表矩陣的轉(zhuǎn)置操作,并非次方,這是線性代數(shù)里為調(diào)整矩陣維度、確保矩陣乘法合法執(zhí)行的常用手段。
具體到公式場景中:
- 輸入(input)是一個(gè)形狀為
m×n的矩陣,其中m代表批處理大小(也就是單次計(jì)算的樣本數(shù)量),n代表每個(gè)樣本的特征數(shù)量; - 權(quán)重(weight)是一個(gè)形狀為
d×n的矩陣,其中d代表輸出層(或下一層)的神經(jīng)元數(shù)量。
矩陣乘法有個(gè)核心規(guī)則:前一個(gè)矩陣的列數(shù),必須與后一個(gè)矩陣的行數(shù)相等,乘法才能有效執(zhí)行。但在神經(jīng)網(wǎng)絡(luò)的 nn.Linear 層中,權(quán)重矩陣默認(rèn)是以 d×n 的形式存儲(chǔ)的 —— 這個(gè)形狀無法直接和 m×n 的輸入矩陣相乘(輸入列數(shù) n 與權(quán)重行數(shù) d 不匹配)。
因此,我們需要對(duì)權(quán)重矩陣做轉(zhuǎn)置處理,將其從 d×n 轉(zhuǎn)換為 n×d。此時(shí),m×n 的輸入矩陣與 n×d 的轉(zhuǎn)置后權(quán)重矩陣滿足乘法規(guī)則,相乘后會(huì)得到一個(gè) m×d 的矩陣 —— 這個(gè)結(jié)果就對(duì)應(yīng)著 m 個(gè)樣本經(jīng)過線性層后的輸出。
對(duì)于三層網(wǎng)絡(luò)模型,前向傳播簡單計(jì)算如下:
圖片
其中 σ 是 Sigmoid 函數(shù),一種常用的激活函數(shù)。最后將 A2 作為輸入傳入輸出層,計(jì)算本次前向傳播得到的輸出值,用來計(jì)算損失。
計(jì)算損失
損失是神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程中非常重要的概念,描述本次前向傳播結(jié)果和實(shí)際值的差異,一般來說越低越好,神經(jīng)網(wǎng)絡(luò)根據(jù)損失進(jìn)行反向傳播,找到更合適的權(quán)重和偏置,進(jìn)而更新參數(shù)。對(duì)于我們舉的二元分類問題,最常用的損失函數(shù)是二元交叉熵?fù)p失(Binary Cross-Entropy Loss)。當(dāng)輸出是一個(gè)概率值,并且標(biāo)簽是 0 或 1 的時(shí)候,這種方法非常合適。損失的計(jì)算公式如下:
圖片
其中 N 是樣本數(shù)量,yi 是真實(shí)標(biāo)簽,y^i 是預(yù)測的概率。Python 中可以直接使用下面的函數(shù)。
import torch.nn as nn
criterion = nn.BCEWithLogitsLoss()通過下面這個(gè)方法來使用:
loss = criterion(output, target)到損失值就可以開始反向傳播了。當(dāng)然,如果損失已經(jīng)非常小,并到達(dá)訓(xùn)練目標(biāo)了,那是可以停止訓(xùn)練的。
反向傳播
反向傳播也是神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程中的一個(gè)重要概念,用來根據(jù)損失推算合適的權(quán)重和偏置。為了理解反向傳播的含義,我們還是舉 y=kx+b 的例子。
當(dāng)我們初始化 k 和 b 的值分別為 2 和 1 時(shí),如果輸入值 x=1,那么經(jīng)過計(jì)算 y 的值為 3。如果我們期望的值是 4,那么此處就可以通過調(diào)用損失函數(shù),把前向傳播得到的值 3 和期望值 4 傳入損失函數(shù) L,計(jì)算出損失值。假設(shè)得到的損失值是 0.6,這個(gè)時(shí)候我們需要通過一定的計(jì)算公式反推出合適的 k 和 b,比如當(dāng)輸入 x=1 時(shí),k+b=3,這個(gè)時(shí)候需要找到合適的 k 和 b。因?yàn)?k 和 b 有無數(shù)種組合,那到底該怎么推呢?我們看一下詳細(xì)的過程。
反向傳播的目的是計(jì)算 ?w?L(損失 L 對(duì) w 的梯度)和 ?b?L(損失 L 對(duì) b 的梯度),其中 w 和 b 是網(wǎng)絡(luò)層的權(quán)重和偏置。我們可以應(yīng)用鏈?zhǔn)椒▌t,你看一下這 2 條公式。
圖片
我們?cè)谟?xùn)練的時(shí)候,不用自己計(jì)算,調(diào)用如下代碼就可以計(jì)算梯度了。
loss.backward()更新參數(shù)
得到 ?w?L 和 ?b?L 后,就可以更新參數(shù)了。w 更新:w←w?η?w?Lb 更新:b←b?η?b?L其中,η 是學(xué)習(xí)率,一個(gè)小的正數(shù),用來控制學(xué)習(xí)的步長。我們可以使用下面這行代碼更新權(quán)重參數(shù)。
圖片
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
optimizer.step()這里使用的是 optim.Adam 優(yōu)化器,當(dāng)然也可以使用其他優(yōu)化器,比如 AdaGrad 和 RMSProp 等。接下來就是按照訓(xùn)練的策略,繼續(xù)下一輪訓(xùn)練,直到達(dá)到目標(biāo)或者訓(xùn)練輪數(shù)完成。



































