pytorch?實(shí)現(xiàn)情感分類問題小結(jié)
1、詞表映射
無論是深度學(xué)習(xí)還是傳統(tǒng)的統(tǒng)計(jì)機(jī)器學(xué)習(xí)方法處理自然語言,都需要先將輸入的語言符號(hào)(通常為標(biāo)記Token),映射為大于等于0、小于詞表大小的整數(shù),該整數(shù)也被稱作一個(gè)標(biāo)記的索引值或下標(biāo)。
vocab類實(shí)現(xiàn)標(biāo)記和索引之間的相互映射。
from collections import defaultdict, Counter class Vocab: def __init__(self, tokens=None): self.idx_to_token = list() self.token_to_idx = dict() if tokens is not None: if "<unk>" not in tokens: tokens = tokens + ["<unk>"] for token in tokens: self.idx_to_token.append(token) self.token_to_idx[token] = len(self.idx_to_token) - 1 self.unk = self.token_to_idx['<unk>'] @classmethod def build(cls, text, min_freq=1, reserved_tokens=None): token_freqs = defaultdict(int) for sentence in text: for token in sentence: token_freqs[token] += 1 uniq_tokens = ["<unk>"] + (reserved_tokens if reserved_tokens else []) uniq_tokens += [token for token, freq in token_freqs.items() \ if freq >= min_freq and token != "<unk>"] return cls(uniq_tokens) def __len__(self): #返回詞表大小,即詞表有多少個(gè)互不相同的標(biāo)記 return len(self.idx_to_token) def __getitem__(self, token): #查找對(duì)應(yīng)輸入標(biāo)記的索引 #不存在,返回標(biāo)記<unk>的索引 return self.token_to_idx.get(token, self.unk) def convert_tokens_to_ids(self, tokens): #查找一系列輸入標(biāo)記對(duì)應(yīng)的索引 return [self[token] for token in tokens] def convert_ids_to_tokens(self, indices): #查找一系列索引值對(duì)應(yīng)的標(biāo)記 return [self.idx_to_token[index] for index in indices] def save_vocab(vocab, path): with open(path, 'w') as writer: writer.write("\n".join(vocab.idx_to_token)) def read_vocab(path): with open(path, 'r') as f: tokens = f.read().split('\n') return Vocab(tokens)
2、詞向量層
在使用深度學(xué)習(xí)進(jìn)行自然語言處理時(shí),將一個(gè)詞(或者標(biāo)記) 轉(zhuǎn)換為一個(gè)低維、稠密、連續(xù)的詞向量(也稱 Embedding ) 是一種基本的詞表示方法,通過 torch.nn 包提供的 Embedding 層即可實(shí)現(xiàn)該功能。
創(chuàng)建 Embedding 對(duì)象,需要兩個(gè)參數(shù)
- num_embeddings :詞表的大小
- enbeading-dim: Embedding 向量的維度
實(shí)現(xiàn)的功能是將輸入的整數(shù)張量中每個(gè)整數(shù)(通過詞表映射功能獲得標(biāo)記對(duì)應(yīng)的整數(shù))映射為相應(yīng)維度(embedding_dim)的張量。
示例
import torch from torch import nn #詞表大小為8,Embedding向量維度3 embedding=nn.Embedding(8,3) #輸入形狀(2,4)的整數(shù)張量,相當(dāng)于2個(gè)長度為4的整數(shù)序列,每個(gè)整數(shù)范圍是0-7 input=torch.tensor([[0,1,2,1],[4,6,6,7]],dtype=torch.long) #調(diào)用embedding output=embedding(input) print(output) print(output.shape)
輸出
張量形狀為[2, 4, 3],即在原始輸入最后增加一個(gè)長度為3 的維
tensor([[[-0.9536, 0.3367, -1.1639], [-0.4816, -0.8973, -0.7700], [ 1.9007, 1.3130, -1.2717], [-0.4816, -0.8973, -0.7700]], [[ 0.5755, 0.8908, -1.0384], [ 0.3724, 0.1216, 2.4466], [ 0.3724, 0.1216, 2.4466], [ 1.4089, 0.2458, 0.2263]]], grad_fn=<EmbeddingBackward0>) torch.Size([2, 4, 3])
3、融入詞向量層的多層感知器
基本的多層感知器,輸入為固定大小的實(shí)數(shù)向量。如果輸人為文本,即整數(shù)序列(假設(shè)已經(jīng)利用詞表映射工具將文本中每個(gè)標(biāo)記映射為了相應(yīng)的整數(shù)),在經(jīng)過多層感知器之前,需要利用詞向量層將輸入的整數(shù)映射為向量。
但是,一個(gè)序列中通常含有多個(gè)詞向量,那么如何將它們表示為一個(gè)多層感知器的輸入向量呢?一種方法是將幾個(gè)向量拼接成一個(gè)大小為的向量,其中d表示每個(gè)詞向量的大小。這樣做的一個(gè)問題是最終的預(yù)測結(jié)果與標(biāo)記在序列中的位置過于相關(guān)。例如,如果在一個(gè)序列前面增加一個(gè)標(biāo)記,則序列中的每個(gè)標(biāo)記位置都變了,也就是它們對(duì)應(yīng)的參數(shù)都發(fā)生了變化,那么模型預(yù)測的結(jié)果可能完全不同,這樣顯然不合理。
在自然語言處理中,可以使用詞袋(Bag-Of-Words, Bow)模型解決該問題。詞袋模型指的是在表示序列時(shí),不考慮其中元素的順序,而是將其簡單地看成是一個(gè)集合。于是就可以采用聚合操作處理一個(gè)序列中的多個(gè)詞向量,如求平均、求和或保留最大值等。融入詞向量層以及詞袋模型的多層感知器代碼如下:
import torch from torch import nn from torch.nn import functional as F class MLP(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_class): super(MLP, self).__init__() # 詞嵌入層 self.embedding = nn.Embedding(vocab_size, embedding_dim) # 線性變換:詞嵌入層->隱含層 self.linear1 = nn.Linear(embedding_dim, hidden_dim) # 使用ReLU激活函數(shù) self.activate = F.relu # 線性變換:激活層->輸出層 self.linear2 = nn.Linear(hidden_dim, num_class) def forward(self, inputs): embeddings = self.embedding(inputs) # 將序列中多個(gè)embedding進(jìn)行聚合(此處是求平均值) embedding = embeddings.mean(dim=1) hidden = self.activate(self.linear1(embedding)) outputs = self.linear2(hidden) # 獲得每個(gè)序列屬于某一類別概率的對(duì)數(shù)值 probs = F.log_softmax(outputs, dim=1) return probs mlp = MLP(vocab_size=8, embedding_dim=3, hidden_dim=5, num_class=2) # 輸入為兩個(gè)長度為4的整數(shù)序列 inputs = torch.tensor([[0, 1, 2, 1], [4, 6, 6, 7]], dtype=torch.long) outputs = mlp(inputs) print(outputs)
輸出:
結(jié)果為每個(gè)序列屬于某一類別的概率的對(duì)數(shù)
tensor([[-1.1612, -0.3756], [-0.8089, -0.5894]], grad_fn=<LogSoftmaxBackward0>)
在實(shí)際的自然語言處理任務(wù)中,一個(gè)批次里輸人的文本長度往往是不固定的,因此無法像上面的代碼一樣簡單地用一個(gè)張量存儲(chǔ)詞向量并求平均值。 PyTorch 提供了一種更靈活的解決方案,即EmnbeddingBag 層。在調(diào)用 Embedding-Bag 層時(shí),首先需要將不定長的序列拼按起來,然后使用一個(gè)偏移向量 ( Offsets ) 記錄每個(gè)序列的起始位置。
4、數(shù)據(jù)處理
第一步是將待處理的數(shù)據(jù)從硬盤或者其他地方加載到程序中,此時(shí)讀入的是原始文本數(shù)據(jù),還需要經(jīng)過分句、標(biāo)記解析等 預(yù)處理過程轉(zhuǎn)換為標(biāo)記序列,然后再使用詞表映射工具將每個(gè)標(biāo)記映射到相應(yīng)的索引值。在此,使用 NLTK 提供的句子傾向性分析數(shù)據(jù) (sentence_polarity) 作為示例,
import torch from vocab import Vocab def load_sentence_polarity(): from nltk.corpus import sentence_polarity #使用全部橘子集合(已經(jīng)過標(biāo)記解析)創(chuàng)建詞表 vocab = Vocab.build(sentence_polarity.sents()) #褒貶各4000句子作為訓(xùn)練數(shù)據(jù),使用創(chuàng)建的詞表將標(biāo)記映射為相應(yīng)的索引 #褒義標(biāo)簽為0,貶義標(biāo)簽為1 #每個(gè)樣例是一個(gè)有索引值列表和標(biāo)簽組成的元祖 train_data = [(vocab.convert_tokens_to_ids(sentence), 0) for sentence in sentence_polarity.sents(categories='pos')[:4000]] \ + [(vocab.convert_tokens_to_ids(sentence), 1) for sentence in sentence_polarity.sents(categories='neg')[:4000]] #其余的作為測試數(shù)據(jù) test_data = [(vocab.convert_tokens_to_ids(sentence), 0) for sentence in sentence_polarity.sents(categories='pos')[4000:]] \ + [(vocab.convert_tokens_to_ids(sentence), 1) for sentence in sentence_polarity.sents(categories='neg')[4000:]] return train_data, test_data, vocab def length_to_mask(lengths): max_len = torch.max(lengths) mask = torch.arange(max_len).expand(lengths.shape[0], max_len) < lengths.unsqueeze(1) return mask def load_treebank(): from nltk.corpus import treebank sents, postags = zip(*(zip(*sent) for sent in treebank.tagged_sents())) vocab = Vocab.build(sents, reserved_tokens=["<pad>"]) tag_vocab = Vocab.build(postags) train_data = [(vocab.convert_tokens_to_ids(sentence), tag_vocab.convert_tokens_to_ids(tags)) for sentence, tags in zip(sents[:3000], postags[:3000])] test_data = [(vocab.convert_tokens_to_ids(sentence), tag_vocab.convert_tokens_to_ids(tags)) for sentence, tags in zip(sents[3000:], postags[3000:])] return train_data, test_data, vocab, tag_vocab
dataset 是 Dataset 類(在torch.utils.data 包中定義)的一個(gè)對(duì)象,用于存儲(chǔ)數(shù)據(jù),一般需要根據(jù)具體的數(shù)據(jù)存取需求創(chuàng)建 Dataset 類的子類。如創(chuàng)建 —個(gè)BowDataset 子類,其中 Bow 是詞袋的意思。具體代碼如下
import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import Dataset, DataLoader from collections import defaultdict from vocab import Vocab from utils import load_sentence_polarity class BowDataset(Dataset): def __init__(self, data): #data為原始數(shù)據(jù) self.data = data def __len__(self): #數(shù)據(jù)集中樣例的數(shù)目 return len(self.data) def __getitem__(self, i): #返回下標(biāo)i的數(shù)據(jù) return self.data[i] #用于對(duì)樣本進(jìn)行整理 def collate_fn(examples): #從獨(dú)立樣本集合中構(gòu)建各批次的輸入輸出 #其中,BowDataset類定義了一個(gè)樣本的數(shù)據(jù)結(jié)構(gòu),即輸入標(biāo)簽和輪出標(biāo)悠的元組 # 因此,將輸入inputs定義為一個(gè)張量的列表,其中每個(gè)張量為原始句子中標(biāo)記序列 inputs = [torch.tensor(ex[0]) for ex in examples] #輸出的目標(biāo)targets 為該批次中全部樣例輸出結(jié)果 (0或1) 構(gòu)成的張量 targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long) # 獲取一個(gè)批次中每個(gè)樣例的序列長度 offsets = [0] + [i.shape[0] for i in inputs] #根據(jù)序列的長度,轉(zhuǎn)換為每個(gè)序列起始位置的偏移量 offsets = torch.tensor(offsets[:-1]).cumsum(dim=0) #將inputs列表中的張量拼接成一個(gè)大的張量 inputs = torch.cat(inputs) return inputs, offsets, targets
5、多層感知機(jī)模型的訓(xùn)練與測試
import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import Dataset, DataLoader from collections import defaultdict from vocab import Vocab from utils import load_sentence_polarity # tqdm是一個(gè)Python模塊,能以進(jìn)度條的方式顯示迭代的進(jìn)度 from tqdm.auto import tqdm # 超參數(shù)設(shè)置 embedding_dim = 128 hidden_dim = 256 num_class = 2 batch_size = 32 num_epoch = 5 # 加載數(shù)據(jù) train_data, test_data, vocab = load_sentence_polarity() train_dataset = BowDataset(train_data) test_dataset = BowDataset(test_data) train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True) test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False) # 加載模型 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = MLP(len(vocab), embedding_dim, hidden_dim, num_class) model.to(device) # 將模型加載到CPU或GPU設(shè)備 #訓(xùn)練過程 nll_loss = nn.NLLLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用Adam優(yōu)化器 model.train() for epoch in range(num_epoch): total_loss = 0 for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"): inputs, offsets, targets = [x.to(device) for x in batch] log_probs = model(inputs, offsets) loss = nll_loss(log_probs, targets) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() print(f"Loss: {total_loss:.2f}") # 測試過程 acc = 0 for batch in tqdm(test_data_loader, desc=f"Testing"): inputs, offsets, targets = [x.to(device) for x in batch] with torch.no_grad(): output = model(inputs, offsets) acc += (output.argmax(dim=1) == targets).sum().item() # 輸出在測試集上的準(zhǔn)確率 print(f"Acc: {acc / len(test_data_loader):.2f}")
訓(xùn)練結(jié)果
6、基于卷積神經(jīng)網(wǎng)絡(luò)的情感分類
第3 節(jié)的詞袋模型表示文本,只考慮了文本中詞語的信息,而忽視了詞組信息,如句子
我不喜歡這部電影
詞袋模型看到文本中有“喜歡”一詞,則很可能將其識(shí)別為褒義。而卷積神經(jīng)網(wǎng)絡(luò)可以提取詞組信息,如將卷積核的大小設(shè)置為 2,則可以提取特征“不 喜歡” 等,顯然這對(duì)于最終情感極性的判斷至關(guān)重要。卷積神經(jīng)網(wǎng)絡(luò)的大部分代碼與多層感知器的實(shí)現(xiàn)一致
其中的不同之處:
- 模型不同,需要從 nn. Module 類派生一個(gè) CMN 子類
- 在調(diào)用卷積神經(jīng)網(wǎng)絡(luò)時(shí),還需要設(shè)置兩個(gè)額外的超參數(shù),分別為filter_size =3(卷積核的大?。┖蚽um_tilter =100(卷積核的個(gè)數(shù))
- 數(shù)據(jù)整理函數(shù)(collate_fn),pad_seguence 兩數(shù)實(shí)現(xiàn)補(bǔ)齊 (Padding)功能,使得一個(gè)批次中 全部宇列長度相同(同最大長度序列),不足的默認(rèn)使用0補(bǔ)齊。
除了以上不同,其他代碼與多層感知器的實(shí)現(xiàn)幾乎一致。如要實(shí)現(xiàn)一個(gè)基于新模型的情感分類任務(wù),只需要定義一個(gè)nn.Module類的子類,并修改數(shù)據(jù)整理函數(shù)(collate_fn)即可
import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import Dataset, DataLoader from torch.nn.utils.rnn import pad_sequence from collections import defaultdict from vocab import Vocab from utils import load_sentence_polarity class CnnDataset(Dataset): def __init__(self, data): self.data = data def __len__(self): return len(self.data) def __getitem__(self, i): return self.data[i] #另外,數(shù)據(jù)整理函數(shù)也需要進(jìn)行一些修改。 # pad_seguence 兩數(shù)實(shí)現(xiàn)補(bǔ)齊 (Padding)功能,使得一個(gè)批次中 全部序列長度相同(同最大長度序列),不足的默認(rèn)使用0補(bǔ)齊。 def collate_fn(examples): inputs = [torch.tensor(ex[0]) for ex in examples] targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long) # 對(duì)batch內(nèi)的樣本進(jìn)行padding,使其具有相同長度 inputs = pad_sequence(inputs, batch_first=True) return inputs, targets #模型不同,需要從nn.Module類派生一個(gè) CNN 子類 class CNN(nn.Module): def __init__(self, vocab_size, embedding_dim, filter_size, num_filter, num_class): super(CNN, self).__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim) #padding=1表示在卷積操作之前,將序列的前后各補(bǔ)充1個(gè)輸入 self.conv1d = nn.Conv1d(embedding_dim, num_filter, filter_size, padding=1) self.activate = F.relu self.linear = nn.Linear(num_filter, num_class) def forward(self, inputs): embedding = self.embedding(inputs) convolution = self.activate(self.conv1d(embedding.permute(0, 2, 1))) pooling = F.max_pool1d(convolution, kernel_size=convolution.shape[2]) outputs = self.linear(pooling.squeeze(dim=2)) log_probs = F.log_softmax(outputs, dim=1) return log_probs #tqdm是一個(gè)Pyth模塊,能以進(jìn)度條的方式顯示迭代的進(jìn)度 from tqdm.auto import tqdm #超參數(shù)設(shè)置 embedding_dim = 128 hidden_dim = 256 num_class = 2 batch_size = 32 num_epoch = 5 #在調(diào)用卷積神經(jīng)網(wǎng)絡(luò)時(shí),還需要設(shè)置兩個(gè)額外的超參數(shù),分別為filter_size =3(卷積核的大?。┖蚽um_tilter =100(卷積核的個(gè)數(shù))。 filter_size = 3 num_filter = 100 #加載數(shù)據(jù) train_data, test_data, vocab = load_sentence_polarity() train_dataset = CnnDataset(train_data) test_dataset = CnnDataset(test_data) train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True) test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False) #加載模型 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = CNN(len(vocab), embedding_dim, filter_size, num_filter, num_class) model.to(device) #將模型加載到CPU或GPU設(shè)備 #訓(xùn)練過程 nll_loss = nn.NLLLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) #使用Adam優(yōu)化器 model.train() for epoch in range(num_epoch): total_loss = 0 for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"): inputs, targets = [x.to(device) for x in batch] log_probs = model(inputs) loss = nll_loss(log_probs, targets) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() print(f"Loss: {total_loss:.2f}") #測試過程 acc = 0 for batch in tqdm(test_data_loader, desc=f"Testing"): inputs, targets = [x.to(device) for x in batch] with torch.no_grad(): output = model(inputs) acc += (output.argmax(dim=1) == targets).sum().item() #輸出在測試集上的準(zhǔn)確率 print(f"Acc: {acc / len(test_data_loader):.2f}")
訓(xùn)練結(jié)果
7、基于循環(huán)神經(jīng)網(wǎng)絡(luò)的情感分類
第3 節(jié)的詞袋模型還忽路了文本中詞的順序信息,因此對(duì)于兩個(gè)句子 “張三行李四”和“李四行張三”,它們的表示是完全相同的,但顯然這并不合理。循環(huán)神經(jīng)網(wǎng)絡(luò)模型能更好地對(duì)序列數(shù)據(jù)進(jìn)行表示。
以長短時(shí)記憶(LSTM) 網(wǎng)絡(luò)為例。
其中,大部分代碼與前面的實(shí)現(xiàn)一致,不同之處:
- 需要從nn.Module派生一個(gè)LSTM子類
- forward中使用 pack_padded_sequence將變長序列打包,功能是將之前補(bǔ)齊過的一個(gè)小批次序列打包成一個(gè)序列,其中每個(gè)原始序列的長度存儲(chǔ)在lengths中,能被self.lstm直接調(diào)用
import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import Dataset, DataLoader from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence from collections import defaultdict from vocab import Vocab from utils import load_sentence_polarity #tqdm是一個(gè)Python模塊,能以進(jìn)度條的方式顯式迭代的進(jìn)度 from tqdm.auto import tqdm class LstmDataset(Dataset): def __init__(self, data): self.data = data def __len__(self): return len(self.data) def __getitem__(self, i): return self.data[i] #整理函數(shù) def collate_fn(examples): #獲得每個(gè)序列的長度 lengths = torch.tensor([len(ex[0]) for ex in examples]) inputs = [torch.tensor(ex[0]) for ex in examples] targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long) # 對(duì)batch內(nèi)的樣本進(jìn)行padding,使其具有相同長度 inputs = pad_sequence(inputs, batch_first=True) return inputs, lengths, targets #需要從nn.Module派生一個(gè)LSTM子類 class LSTM(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_class): super(LSTM, self).__init__() self.embeddings = nn.Embedding(vocab_size, embedding_dim) self.lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True) self.output = nn.Linear(hidden_dim, num_class) def forward(self, inputs, lengths): embeddings = self.embeddings(inputs) #使用 pack_padded_sequence將變長序列打包 x_pack = pack_padded_sequence(embeddings, lengths, batch_first=True, enforce_sorted=False) hidden, (hn, cn) = self.lstm(x_pack) outputs = self.output(hn[-1]) log_probs = F.log_softmax(outputs, dim=-1) return log_probs embedding_dim = 128 hidden_dim = 256 num_class = 2 batch_size = 32 num_epoch = 5 #加載數(shù)據(jù) train_data, test_data, vocab = load_sentence_polarity() train_dataset = LstmDataset(train_data) test_dataset = LstmDataset(test_data) train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True) test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False) #加載模型 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = LSTM(len(vocab), embedding_dim, hidden_dim, num_class) model.to(device) #將模型加載到GPU中(如果已經(jīng)正確安裝) #訓(xùn)練過程 nll_loss = nn.NLLLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) #使用Adam優(yōu)化器 model.train() for epoch in range(num_epoch): total_loss = 0 for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"): inputs, lengths, targets = [x.to(device) for x in batch] log_probs = model(inputs, lengths) loss = nll_loss(log_probs, targets) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() print(f"Loss: {total_loss:.2f}") #測試過程 acc = 0 for batch in tqdm(test_data_loader, desc=f"Testing"): inputs, lengths, targets = [x.to(device) for x in batch] with torch.no_grad(): output = model(inputs, lengths) acc += (output.argmax(dim=1) == targets).sum().item() #輸出在測試集上的準(zhǔn)確率 print(f"Acc: {acc / len(test_data_loader):.2f}")
訓(xùn)練結(jié)果
8、基于 Transformer 的情感分類
基于 Transformer 實(shí)現(xiàn)情感分類與使用 LSTM 也非常相似,主要有一處不同, 即需要定義 Transformer模型。
# Defined in Section 4.6.8 import math import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import Dataset, DataLoader from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence from collections import defaultdict from vocab import Vocab from utils import load_sentence_polarity, length_to_mask # tqdm是一個(gè)Pyth模塊,能以進(jìn)度條的方式顯式迭代的進(jìn)度 from tqdm.auto import tqdm class TransformerDataset(Dataset): def __init__(self, data): self.data = data def __len__(self): return len(self.data) def __getitem__(self, i): return self.data[i] def collate_fn(examples): lengths = torch.tensor([len(ex[0]) for ex in examples]) inputs = [torch.tensor(ex[0]) for ex in examples] targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long) # 對(duì)batch內(nèi)的樣本進(jìn)行padding,使其具有相同長度 inputs = pad_sequence(inputs, batch_first=True) return inputs, lengths, targets class PositionalEncoding(nn.Module): def __init__(self, d_model, dropout=0.1, max_len=512): super(PositionalEncoding, self).__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) pe = pe.unsqueeze(0).transpose(0, 1) self.register_buffer('pe', pe) def forward(self, x): x = x + self.pe[:x.size(0), :] return x class Transformer(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_class, dim_feedforward=512, num_head=2, num_layers=2, dropout=0.1, max_len=128, activation: str = "relu"): super(Transformer, self).__init__() # 詞嵌入層 self.embedding_dim = embedding_dim self.embeddings = nn.Embedding(vocab_size, embedding_dim) self.position_embedding = PositionalEncoding(embedding_dim, dropout, max_len) # 編碼層:使用Transformer encoder_layer = nn.TransformerEncoderLayer(hidden_dim, num_head, dim_feedforward, dropout, activation) self.transformer = nn.TransformerEncoder(encoder_layer, num_layers) # 輸出層 self.output = nn.Linear(hidden_dim, num_class) def forward(self, inputs, lengths): inputs = torch.transpose(inputs, 0, 1) #與LSTM處理情況相同,輸入的數(shù)據(jù)的第一維是批次,需要轉(zhuǎn)換為TransformerEncoder #所需要的第1維是長度,第二維是批次的形狀 hidden_states = self.embeddings(inputs) hidden_states = self.position_embedding(hidden_states) #length_to_mask作用是根據(jù)批次中每個(gè)序列的長度生成mask矩陣,以便處理長度不一致的序列,忽略掉比較短的序列的無效部分 #src_key_padding_mask的參數(shù)正好與length_to_mask的結(jié)果相反(無自注意力的部分為true) attention_mask = length_to_mask(lengths.to('cpu')) == False #根據(jù)批次中的每個(gè)序列長度生成mask矩陣 hidden_states = self.transformer(hidden_states, src_key_padding_mask=attention_mask.to('cuda')) hidden_states = hidden_states[0, :, :] #取第一個(gè)標(biāo)記的輸出結(jié)果作為分類層的輸入 output = self.output(hidden_states) log_probs = F.log_softmax(output, dim=1) return log_probs embedding_dim = 128 hidden_dim = 128 num_class = 2 batch_size = 32 num_epoch = 5 # 加載數(shù)據(jù) train_data, test_data, vocab = load_sentence_polarity() train_dataset = TransformerDataset(train_data) test_dataset = TransformerDataset(test_data) train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True) test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False) # 加載模型 device = torch.device('cuda') model = Transformer(len(vocab), embedding_dim, hidden_dim, num_class) model.to(device) # 將模型加載到GPU中(如果已經(jīng)正確安裝) # 訓(xùn)練過程 nll_loss = nn.NLLLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用Adam優(yōu)化器 model.train() for epoch in range(num_epoch): total_loss = 0 for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"): inputs, lengths, targets = [x.to(device) for x in batch] log_probs = model(inputs, lengths) loss = nll_loss(log_probs, targets) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() print(f"Loss: {total_loss:.2f}") # 測試過程 acc = 0 for batch in tqdm(test_data_loader, desc=f"Testing"): inputs, lengths, targets = [x.to(device) for x in batch] with torch.no_grad(): output = model(inputs, lengths) acc += (output.argmax(dim=1) == targets).sum().item() # 輸出在測試集上的準(zhǔn)確率 print(f"Acc: {acc / len(test_data_loader):.2f}")
訓(xùn)練結(jié)果
到此這篇關(guān)于pytorch 實(shí)現(xiàn)情感分類問題小結(jié)的文章就介紹到這了,更多相關(guān)pytorch 實(shí)現(xiàn)情感分類 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- PyTorch上搭建簡單神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)回歸和分類的示例
- Pytorch 實(shí)現(xiàn)focal_loss 多類別和二分類示例
- pytorch實(shí)現(xiàn)用CNN和LSTM對(duì)文本進(jìn)行分類方式
- pytorch訓(xùn)練imagenet分類的方法
- Python機(jī)器學(xué)習(xí)之基于Pytorch實(shí)現(xiàn)貓狗分類
- Pytorch 實(shí)現(xiàn)計(jì)算分類器準(zhǔn)確率(總分類及子分類)
- 利用pytorch實(shí)現(xiàn)對(duì)CIFAR-10數(shù)據(jù)集的分類
- pytorch分類模型繪制混淆矩陣以及可視化詳解
- 使用PyTorch訓(xùn)練一個(gè)圖像分類器實(shí)例
相關(guān)文章
python利用pymysql和openpyxl實(shí)現(xiàn)操作MySQL數(shù)據(jù)庫并插入數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了如何使用Python連接MySQL數(shù)據(jù)庫,并從Excel文件中讀取數(shù)據(jù),將其插入到MySQL數(shù)據(jù)庫中,有需要的小伙伴可以參考一下2023-10-10python json.loads兼容單引號(hào)數(shù)據(jù)的方法
今天小編就為大家分享一篇python json.loads兼容單引號(hào)數(shù)據(jù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-12-12Python線性網(wǎng)絡(luò)實(shí)現(xiàn)分類糖尿病病例
什么是線性規(guī)劃?想象一下,您有一個(gè)線性方程組和不等式系統(tǒng)。這樣的系統(tǒng)通常有許多可能的解決方案。線性規(guī)劃是一組數(shù)學(xué)和計(jì)算工具,可讓您找到該系統(tǒng)的特定解,該解對(duì)應(yīng)于某些其他線性函數(shù)的最大值或最小值2022-10-10Django-celery-beat動(dòng)態(tài)添加周期性任務(wù)實(shí)現(xiàn)過程解析
這篇文章主要介紹了Django-celery-beat動(dòng)態(tài)添加周期性任務(wù)實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Python自動(dòng)化測試框架之unittest使用詳解
unittest是Python自動(dòng)化測試框架之一,提供了一系列測試工具和接口,支持單元測試、功能測試、集成測試等多種測試類型。unittest使用面向?qū)ο蟮乃枷雽?shí)現(xiàn)測試用例的編寫和管理,可以方便地?cái)U(kuò)展和定制測試框架,支持多種測試結(jié)果輸出格式2023-04-04python3+pyqt5+itchat微信定時(shí)發(fā)送消息的方法
今天小編就為大家分享一篇python3+pyqt5+itchat微信定時(shí)發(fā)送消息的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-02-02Python使用pywebview開發(fā)桌面應(yīng)用的全過程
當(dāng)使用桌面應(yīng)用程序的時(shí)候,有沒有那么一瞬間,想學(xué)習(xí)一下桌面應(yīng)用程序開發(fā)?下面這篇文章主要給大家介紹了關(guān)于Python使用pywebview開發(fā)桌面應(yīng)用的相關(guān)資料,需要的朋友可以參考下2022-06-06