文章目錄
- 前言
- 1. 加載與預處理數據集
- 數據讀取與詞元化
- 構建詞匯表
- 截斷、填充與數據迭代器
- 2. 構建循環神經網絡模型
- 雙向RNN模型(BiRNN)詳解
- 權重初始化
- 3. 加載預訓練詞向量
- 構建詞向量加載器
- 將預訓練向量注入模型
- 4. 訓練與評估模型
- 定義訓練函數
- 可視化訓練過程
- 5. 模型預測
- 編寫預測函數
- 實例測試
- 6. 總結
前言
在信息爆炸的時代,從海量的文本數據中提取有價值的信息變得至關重要。無論是電商網站的商品評論、社交媒體上的用戶反饋,還是新聞文章中的觀點傾向,理解文本背后的情感色彩——即情感分析——都有著廣泛的應用。
循環神經網絡(RNN)由于其對序列數據的強大建模能力,天然地適用于處理文本這類具有時序特征的數據。在本篇博客中,我們將從零開始,使用PyTorch框架構建一個基于雙向循環神經網絡(Bi-RNN)的情感分析模型。我們不僅會詳細講解數據預處理、模型構建、訓練評估的全過程,還將引入預訓練的GloVe詞向量來提升模型的性能。
這篇博客的目標是“從代碼學習深度學習”。因此,我們將完整地展示每一個模塊的代碼,并配以詳盡的解釋,力求讓讀者不僅能看懂代碼,更能理解每一行代碼背后的原理和設計思想。無論您是深度學習初學者,還是希望系統學習PyTorch在自然語言處理中應用的開發者,相信都能從中獲益。
讓我們一起踏上這場代碼與思想的探索之旅吧!
完整代碼:下載鏈接
1. 加載與預處理數據集
任何成功的NLP項目都始于堅實的數據處理。我們的任務是分析IMDb電影評論的情感,這是一個經典的二分類問題(正面/負面)。在這一步,我們將完成從原始文本文件到PyTorch數據迭代器的全部轉換過程。
主邏輯由load_data_imdb
函數驅動,它調用了一系列輔助函數來完成任務。
# 情感分析:使用循環神經網絡.ipynbimport torch
import utils_for_data
from torch import nnbatch_size = 64
train_iter, test_iter, vocab = utils_for_data.load_data_imdb(batch_size)
上面的代碼是我們的入口,它調用utils_for_data.load_data_imdb
來獲取訓練/測試數據迭代器和詞匯表。現在,讓我們深入utils_for_data.py
和utils_for_vocab.py
,看看這一切是如何實現的。
數據讀取與詞元化
首先,我們需要從壓縮包中讀取IMDb數據集的文本和標簽。read_imdb
函數負責遍歷指定目錄,讀取每個評論文件并為其打上正面(1)或負面(0)的標簽。
# utils_for_data.pyimport os
import zipfile
import tarfile
import utils_for_vocab
import torch.utils.data as data
import torchdef extract(name, folder=None):"""下載并解壓zip/tar文件參數:name (str): 要解壓的文件名/路徑,維度: [字符串]folder (str, optional): 指定的文件夾名稱,維度: [字符串] 或 None返回:str: 解壓后的目錄路徑,維度: [字符串]"""base_dir = os.path.dirname(name)data_dir, ext = os.path.splitext(name)if ext == '.zip':fp = zipfile.ZipFile(name, 'r')elif ext in ('.tar', '.gz'):fp = tarfile.open(name, 'r')else:assert False, '只有zip/tar文件可以被解壓縮'fp.extractall(base_dir)fp.close()return os.path.join(base_dir, folder) if folder else data_dirdef read_imdb(data_dir, is_train):"""讀取IMDb評論數據集文本序列和標簽參數:data_dir (str): 數據集根目錄路徑is_train (bool): 是否讀取訓練集,True為訓練集,False為測試集返回:tuple: (data, labels)data (list): 評論文本列表,維度為 [樣本數量]labels (list): 標簽列表,維度為 [樣本數量],1表示正面評價,0表示負面評價"""data = []labels = []for label in ('pos', 'neg'):folder_name = os.path.join(data_dir, 'train' if is_train else 'test', label)for file in os.listdir(folder_name):file_path = os.path.join(folder_name, file)with open(file_path, 'rb') as f:review = f.read().decode('utf-8').replace('\n', '')data.append(review)labels.append(1 if label == 'pos' else 0)return data, labels
拿到原始文本后,我們需要將其分解為模型可以理解的基本單元——詞元(Token)。這個過程稱為詞元化(Tokenization)。tokenize
函數可以按單詞或字符進行分割。
# utils_for_vocab.pyimport torch
import torch.utils.data
from collections import Counterdef tokenize(lines, token='word'):"""將文本行拆分為單詞或字符詞元參數:lines (list): 文本行列表,維度: [行數],每個元素為字符串token (str): 詞元化類型,維度: [標量],'word'表示按單詞分割,'char'表示按字符分割返回:tokenized_lines (list): 詞元化后的文本,維度: [行數 × 詞元數],嵌套列表結構"""if token == 'word':return [line.split() for line in lines]elif token == 'char':return [list(line) for line in lines]else:print('錯誤:未知詞元類型:' + token)
構建詞匯表
計算機無法直接處理文本,我們需要將詞元映射為數字索引。Vocab
類就是為此設計的。它會統計所有詞元的頻率,并只保留那些出現頻率高于min_freq
的詞元,其余的都歸為未知詞元<unk>
。這不僅能減小詞匯表的大小,還能過濾掉噪音。
# utils_for_vocab.pydef count_corpus(tokens):"""統計詞元出現頻率參數:tokens (list): 詞元列表,維度: [詞元數] 或 [序列數 × 詞元數](嵌套列表)返回:counter (Counter): 詞元頻率統計對象,鍵為詞元,值為出現次數"""if len(tokens) == 0 or isinstance(tokens[0], list):tokens = [token for line in tokens for token in line]return Counter(tokens)class Vocab:"""文本詞匯表類,用于管理詞元到索引的映射關系"""def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):"""初始化詞匯表參數:tokens (list): 詞元列表,維度: [詞元數] 或 [序列數 × 詞元數]min_freq (int): 最小詞頻閾值,維度: [標量],低于此頻率的詞元將被忽略reserved_tokens (list): 保留詞元列表,維度: [保留詞元數],如特殊標記"""if tokens is None:tokens = []if reserved_tokens is None:reserved_tokens = []counter = count_corpus(tokens)self._token_freqs = sorted(counter.items()<