:RNN模型搭建)
情感分析項目代碼詳解三textrnn.py 模型搭建前兩篇完成了數(shù)據(jù)準備詞匯表建好了文本也變成了定長的數(shù)字序列還寫好了分批讀取的迭代器。這一篇開始搭建真正的大腦——情感分類模型。textrnn.py 用經(jīng)典的 RNN循環(huán)神經(jīng)網(wǎng)絡(luò)家族成員 LSTM 來做文本分類代碼只有二十幾行但每一行都值得細看。一、導(dǎo)入庫import torch import torch.nn as nntorch 是 PyTorch 主庫nn 是它的神經(jīng)網(wǎng)絡(luò)模塊里面封裝好了各種常用層嵌入層、LSTM、全連接層等。我們只需要像搭積木一樣把它們拼起來。二、定義模型類class Model(nn.Module): def __init__(self, embedding_pretained, n_vocab, embed, num_classes): super(Model, self).__init__()在 PyTorch 里所有自定義模型都要繼承 nn.Module 這個基類這樣它才具備可訓(xùn)練、可保存、可遷移等能力。__init__ 接收四個參數(shù)embedding_pretained預(yù)訓(xùn)練的字向量沒有就傳 None、n_vocab詞匯表大小、embed每個字的向量維度、num_classes分類數(shù)本項目是 4 種情緒。super().__init__() 先調(diào)用父類的初始化方法這是繼承的標(biāo)準寫法必須放在第一行。三、嵌入層 Embeddingif embedding_pretained is not None: self.embedding nn.Embedding.from_pretrained(embedding_pretained, padding_idxn_vocab - 1, freezeFalse) else: self.embedding nn.Embedding(n_vocab, embed, padding_idxn_vocab - 1)嵌入層的作用把字編號變成向量。編號本身只是個整數(shù)沒有任何含義Embedding 相當(dāng)于一張大查找表把每個編號映射成一個稠密向量讓意思相近的字向量也相近。if 分支如果提供了預(yù)訓(xùn)練向量比如別人在大語料上訓(xùn)練好的就用 from_pretrained 直接加載freezeFalse 表示不凍結(jié)訓(xùn)練過程中還可以繼續(xù)微調(diào)這些向量。else 分支沒有預(yù)訓(xùn)練向量就新建一個 Embedding 層隨機初始化邊訓(xùn)練邊學(xué)。padding_idxn_vocab-1第二篇里 PAD 的編號是最后一個n_vocab-1。指定它之后填充位置的向量永遠是 0梯度也不會更新它——模型不會把填充當(dāng)成有效信息。四、LSTM 層self.lstm nn.LSTM(embed, 128, 3, bidirectionalTrue, batch_firstTrue, dropout0.3)參數(shù)逐個看embed 是輸入維度字的向量長度128 是隱藏層大小可以理解為 LSTM 的記憶容量3 是層數(shù)堆疊 3 層 LSTM越深能學(xué)到越抽象的特征。bidirectionalTrue雙向 LSTM一條從句首往句尾讀一條從句尾往句首讀。比如單獨一個不字光看前半句不知道它后面跟什么雙向就能兼顧前后文。batch_firstTrue約定輸入格式為 [批次, 長度, 維度]和第二篇迭代器輸出的形狀對得上省去轉(zhuǎn)置的麻煩。dropout0.3訓(xùn)練時隨機丟棄30% 的神經(jīng)元防止模型死記硬背訓(xùn)練集過擬合。五、全連接層self.fc nn.Linear(128 * 2, num_classes)LSTM 是雙向的正向 128 維 反向 128 維拼在一起是 256 維所以輸入維度寫成 128*2。輸出 num_classes 個分數(shù)對應(yīng) 4 種情緒哪個分數(shù)最高就預(yù)測哪一類。六、forward 前向傳播上def forward(self, x): x, seq_len x emb self.embedding(x) # [B, L, E]forward 定義了數(shù)據(jù)在模型里怎么流動調(diào)用 model(x) 時會自動執(zhí)行它。第二篇的迭代器返回 ((x, seq_len), y)所以第一行先把元組拆開x 是字編號矩陣seq_len 是每條樣本的真實長度。注釋里的 B、L、E 分別代表批次大小、序列長度70、字向量維度。嵌入后每個字都變成一條 E 維向量。七、forward 前向傳播中out, _ self.lstm(emb) # [B, L, 2H]把向量送進 LSTMout 是每一個時刻每個字位置的輸出形狀 [B, 70, 256]。LSTM 還會返回最后一刻的隱藏狀態(tài)和記憶單元這里用下劃線 _ 接收后直接丟掉——因為我們接下來要用 seq_len 精確取位置用不上它。八、取真實最后一步idx (seq_len - 1).clamp(min0) # [B] last out[torch.arange(out.size(0), deviceout.device), idx] # [B, 2H]關(guān)鍵點短句子后面補了 PAD如果直接用最后一位取到的是一堆填充向量等于白看。所以要根據(jù) seq_len 找到每個樣本真實的最后一個字的位置。seq_len - 1 是最后一個字的索引索引從 0 開始clamp(min0) 是保險防止空句出現(xiàn) -1。torch.arange(out.size(0)) 生成 [0,1,2,...] 批次序號配合 idx 做花式索引對第 i 條樣本取它第 idx[i] 個位置的輸出。一句話從每條序列里摳出真實最后一個字的 LSTM 編碼。九、分類輸出return self.fc(last)把 256 維的特征送進全連接層得到 4 個類別的分數(shù)并返回。下一篇的訓(xùn)練代碼會拿這個分數(shù)和真實標(biāo)簽計算損失。小結(jié)textrnn.py 雖然短但完整走通了編號→向量→LSTM 編碼→取真實最后狀態(tài)→分類的鏈路。模型建好了下一篇就來看怎么訓(xùn)練它、評估它、測試它。