pytorch?實現(xiàn)情感分類問題小結(jié)
1、詞表映射
無論是深度學(xué)習(xí)還是傳統(tǒng)的統(tǒng)計機(jī)器學(xué)習(xí)方法處理自然語言,都需要先將輸入的語言符號(通常為標(biāo)記Token),映射為大于等于0、小于詞表大小的整數(shù),該整數(shù)也被稱作一個標(biāo)記的索引值或下標(biāo)。
vocab類實現(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):
#返回詞表大小,即詞表有多少個互不相同的標(biāo)記
return len(self.idx_to_token)
def __getitem__(self, token):
#查找對應(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)記對應(yīng)的索引
return [self[token] for token in tokens]
def convert_ids_to_tokens(self, indices):
#查找一系列索引值對應(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)行自然語言處理時,將一個詞(或者標(biāo)記) 轉(zhuǎn)換為一個低維、稠密、連續(xù)的詞向量(也稱 Embedding ) 是一種基本的詞表示方法,通過 torch.nn 包提供的 Embedding 層即可實現(xiàn)該功能。
創(chuàng)建 Embedding 對象,需要兩個參數(shù)
- num_embeddings :詞表的大小
- enbeading-dim: Embedding 向量的維度
實現(xiàn)的功能是將輸入的整數(shù)張量中每個整數(shù)(通過詞表映射功能獲得標(biāo)記對應(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個長度為4的整數(shù)序列,每個整數(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],即在原始輸入最后增加一個長度為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è)已經(jīng)利用詞表映射工具將文本中每個標(biāo)記映射為了相應(yīng)的整數(shù)),在經(jīng)過多層感知器之前,需要利用詞向量層將輸入的整數(shù)映射為向量。
但是,一個序列中通常含有多個詞向量,那么如何將它們表示為一個多層感知器的輸入向量呢?一種方法是將幾個向量拼接成一個大小為
的向量,其中d表示每個詞向量的大小。這樣做的一個問題是最終的預(yù)測結(jié)果與標(biāo)記在序列中的位置過于相關(guān)。例如,如果在一個序列前面增加一個標(biāo)記,則序列中的每個標(biāo)記位置都變了,也就是它們對應(yīng)的參數(shù)都發(fā)生了變化,那么模型預(yù)測的結(jié)果可能完全不同,這樣顯然不合理。
在自然語言處理中,可以使用詞袋(Bag-Of-Words, Bow)模型解決該問題。詞袋模型指的是在表示序列時,不考慮其中元素的順序,而是將其簡單地看成是一個集合。于是就可以采用聚合操作處理一個序列中的多個詞向量,如求平均、求和或保留最大值等。融入詞向量層以及詞袋模型的多層感知器代碼如下:
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)
# 將序列中多個embedding進(jìn)行聚合(此處是求平均值)
embedding = embeddings.mean(dim=1)
hidden = self.activate(self.linear1(embedding))
outputs = self.linear2(hidden)
# 獲得每個序列屬于某一類別概率的對數(shù)值
probs = F.log_softmax(outputs, dim=1)
return probs
mlp = MLP(vocab_size=8, embedding_dim=3, hidden_dim=5, num_class=2)
# 輸入為兩個長度為4的整數(shù)序列
inputs = torch.tensor([[0, 1, 2, 1], [4, 6, 6, 7]], dtype=torch.long)
outputs = mlp(inputs)
print(outputs)輸出:
結(jié)果為每個序列屬于某一類別的概率的對數(shù)
tensor([[-1.1612, -0.3756],
[-0.8089, -0.5894]], grad_fn=<LogSoftmaxBackward0>)在實際的自然語言處理任務(wù)中,一個批次里輸人的文本長度往往是不固定的,因此無法像上面的代碼一樣簡單地用一個張量存儲詞向量并求平均值。 PyTorch 提供了一種更靈活的解決方案,即EmnbeddingBag 層。在調(diào)用 Embedding-Bag 層時,首先需要將不定長的序列拼按起來,然后使用一個偏移向量 ( Offsets ) 記錄每個序列的起始位置。
4、數(shù)據(jù)處理
第一步是將待處理的數(shù)據(jù)從硬盤或者其他地方加載到程序中,此時讀入的是原始文本數(shù)據(jù),還需要經(jīng)過分句、標(biāo)記解析等 預(yù)處理過程轉(zhuǎn)換為標(biāo)記序列,然后再使用詞表映射工具將每個標(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
#每個樣例是一個有索引值列表和標(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 包中定義)的一個對象,用于存儲數(shù)據(jù),一般需要根據(jù)具體的數(shù)據(jù)存取需求創(chuàng)建 Dataset 類的子類。如創(chuàng)建 —個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]
#用于對樣本進(jìn)行整理
def collate_fn(examples):
#從獨立樣本集合中構(gòu)建各批次的輸入輸出
#其中,BowDataset類定義了一個樣本的數(shù)據(jù)結(jié)構(gòu),即輸入標(biāo)簽和輪出標(biāo)悠的元組
# 因此,將輸入inputs定義為一個張量的列表,其中每個張量為原始句子中標(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)
# 獲取一個批次中每個樣例的序列長度
offsets = [0] + [i.shape[0] for i in inputs]
#根據(jù)序列的長度,轉(zhuǎn)換為每個序列起始位置的偏移量
offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)
#將inputs列表中的張量拼接成一個大的張量
inputs = torch.cat(inputs)
return inputs, offsets, targets5、多層感知機(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是一個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é)的詞袋模型表示文本,只考慮了文本中詞語的信息,而忽視了詞組信息,如句子
我不喜歡這部電影
詞袋模型看到文本中有“喜歡”一詞,則很可能將其識別為褒義。而卷積神經(jīng)網(wǎng)絡(luò)可以提取詞組信息,如將卷積核的大小設(shè)置為 2,則可以提取特征“不 喜歡” 等,顯然這對于最終情感極性的判斷至關(guān)重要。卷積神經(jīng)網(wǎng)絡(luò)的大部分代碼與多層感知器的實現(xiàn)一致
其中的不同之處:
- 模型不同,需要從 nn. Module 類派生一個 CMN 子類
- 在調(diào)用卷積神經(jīng)網(wǎng)絡(luò)時,還需要設(shè)置兩個額外的超參數(shù),分別為filter_size =3(卷積核的大?。┖蚽um_tilter =100(卷積核的個數(shù))
- 數(shù)據(jù)整理函數(shù)(collate_fn),pad_seguence 兩數(shù)實現(xiàn)補齊 (Padding)功能,使得一個批次中 全部宇列長度相同(同最大長度序列),不足的默認(rèn)使用0補齊。
除了以上不同,其他代碼與多層感知器的實現(xiàn)幾乎一致。如要實現(xiàn)一個基于新模型的情感分類任務(wù),只需要定義一個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ù)實現(xiàn)補齊 (Padding)功能,使得一個批次中 全部序列長度相同(同最大長度序列),不足的默認(rèn)使用0補齊。
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)
# 對batch內(nèi)的樣本進(jìn)行padding,使其具有相同長度
inputs = pad_sequence(inputs, batch_first=True)
return inputs, targets
#模型不同,需要從nn.Module類派生一個 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表示在卷積操作之前,將序列的前后各補充1個輸入
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是一個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ù),分別為filter_size =3(卷積核的大?。┖蚽um_tilter =100(卷積核的個數(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é)的詞袋模型還忽路了文本中詞的順序信息,因此對于兩個句子 “張三行李四”和“李四行張三”,它們的表示是完全相同的,但顯然這并不合理。循環(huán)神經(jīng)網(wǎng)絡(luò)模型能更好地對序列數(shù)據(jù)進(jìn)行表示。
以長短時記憶(LSTM) 網(wǎng)絡(luò)為例。
其中,大部分代碼與前面的實現(xiàn)一致,不同之處:
- 需要從nn.Module派生一個LSTM子類
- forward中使用 pack_padded_sequence將變長序列打包,功能是將之前補齊過的一個小批次序列打包成一個序列,其中每個原始序列的長度存儲在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是一個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):
#獲得每個序列的長度
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)
# 對batch內(nèi)的樣本進(jìn)行padding,使其具有相同長度
inputs = pad_sequence(inputs, batch_first=True)
return inputs, lengths, targets
#需要從nn.Module派生一個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 實現(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是一個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)
# 對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ù)批次中每個序列的長度生成mask矩陣,以便處理長度不一致的序列,忽略掉比較短的序列的無效部分
#src_key_padding_mask的參數(shù)正好與length_to_mask的結(jié)果相反(無自注意力的部分為true)
attention_mask = length_to_mask(lengths.to('cpu')) == False
#根據(jù)批次中的每個序列長度生成mask矩陣
hidden_states = self.transformer(hidden_states, src_key_padding_mask=attention_mask.to('cuda'))
hidden_states = hidden_states[0, :, :]
#取第一個標(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 實現(xiàn)情感分類問題小結(jié)的文章就介紹到這了,更多相關(guān)pytorch 實現(xiàn)情感分類 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- PyTorch上搭建簡單神經(jīng)網(wǎng)絡(luò)實現(xiàn)回歸和分類的示例
- Pytorch 實現(xiàn)focal_loss 多類別和二分類示例
- pytorch實現(xiàn)用CNN和LSTM對文本進(jìn)行分類方式
- pytorch訓(xùn)練imagenet分類的方法
- Python機(jī)器學(xué)習(xí)之基于Pytorch實現(xiàn)貓狗分類
- Pytorch 實現(xiàn)計算分類器準(zhǔn)確率(總分類及子分類)
- 利用pytorch實現(xiàn)對CIFAR-10數(shù)據(jù)集的分類
- pytorch分類模型繪制混淆矩陣以及可視化詳解
- 使用PyTorch訓(xùn)練一個圖像分類器實例
相關(guān)文章
python利用pymysql和openpyxl實現(xiàn)操作MySQL數(shù)據(jù)庫并插入數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了如何使用Python連接MySQL數(shù)據(jù)庫,并從Excel文件中讀取數(shù)據(jù),將其插入到MySQL數(shù)據(jù)庫中,有需要的小伙伴可以參考一下2023-10-10
python json.loads兼容單引號數(shù)據(jù)的方法
今天小編就為大家分享一篇python json.loads兼容單引號數(shù)據(jù)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-12-12
Python線性網(wǎng)絡(luò)實現(xiàn)分類糖尿病病例
什么是線性規(guī)劃?想象一下,您有一個線性方程組和不等式系統(tǒng)。這樣的系統(tǒng)通常有許多可能的解決方案。線性規(guī)劃是一組數(shù)學(xué)和計算工具,可讓您找到該系統(tǒng)的特定解,該解對應(yīng)于某些其他線性函數(shù)的最大值或最小值2022-10-10
Django-celery-beat動態(tài)添加周期性任務(wù)實現(xiàn)過程解析
這篇文章主要介紹了Django-celery-beat動態(tài)添加周期性任務(wù)實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11
python3+pyqt5+itchat微信定時發(fā)送消息的方法
今天小編就為大家分享一篇python3+pyqt5+itchat微信定時發(fā)送消息的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-02-02
Python使用pywebview開發(fā)桌面應(yīng)用的全過程
當(dāng)使用桌面應(yīng)用程序的時候,有沒有那么一瞬間,想學(xué)習(xí)一下桌面應(yīng)用程序開發(fā)?下面這篇文章主要給大家介紹了關(guān)于Python使用pywebview開發(fā)桌面應(yīng)用的相關(guān)資料,需要的朋友可以參考下2022-06-06

