当前位置:   article > 正文

pytorch 实现情感分类问题_load_sentence_polarity

load_sentence_polarity

1、词表映射

无论是深度学习还是传统的统计机器学习方法处理自然语言,都需要先将输入的语言符号(通常为标记Token),映射为大于等于0、小于词表大小的整数,该整数也被称作一个标记的索引值或下标。

vocab类实现标记和索引之间的相互映射。

  1. from collections import defaultdict, Counter
  2. class Vocab:
  3. def __init__(self, tokens=None):
  4. self.idx_to_token = list()
  5. self.token_to_idx = dict()
  6. if tokens is not None:
  7. if "<unk>" not in tokens:
  8. tokens = tokens + ["<unk>"]
  9. for token in tokens:
  10. self.idx_to_token.append(token)
  11. self.token_to_idx[token] = len(self.idx_to_token) - 1
  12. self.unk = self.token_to_idx['<unk>']
  13. @classmethod
  14. def build(cls, text, min_freq=1, reserved_tokens=None):
  15. token_freqs = defaultdict(int)
  16. for sentence in text:
  17. for token in sentence:
  18. token_freqs[token] += 1
  19. uniq_tokens = ["<unk>"] + (reserved_tokens if reserved_tokens else [])
  20. uniq_tokens += [token for token, freq in token_freqs.items() \
  21. if freq >= min_freq and token != "<unk>"]
  22. return cls(uniq_tokens)
  23. def __len__(self):
  24. #返回词表大小,即词表有多少个互不相同的标记
  25. return len(self.idx_to_token)
  26. def __getitem__(self, token):
  27. #查找对应输入标记的索引
  28. #不存在,返回标记<unk>的索引
  29. return self.token_to_idx.get(token, self.unk)
  30. def convert_tokens_to_ids(self, tokens):
  31. #查找一系列输入标记对应的索引
  32. return [self[token] for token in tokens]
  33. def convert_ids_to_tokens(self, indices):
  34. #查找一系列索引值对应的标记
  35. return [self.idx_to_token[index] for index in indices]
  36. def save_vocab(vocab, path):
  37. with open(path, 'w') as writer:
  38. writer.write("\n".join(vocab.idx_to_token))
  39. def read_vocab(path):
  40. with open(path, 'r') as f:
  41. tokens = f.read().split('\n')
  42. return Vocab(tokens)

2、词向量

在使用深度学习进行自然语言处理时,将一个词(或者标记) 转换为一个低维、稠密、连续的词向量(也称 Embedding ) 是一种基本的词表示方法,通过 torch.nn 包提供的 Embedding 层即可实现该功能。

创建 Embedding 对象,需要两个参数

  • num_embeddings :词表的大小

  • enbeading-dim: Embedding 向量的维度

实现的功能是将输入的整数张量中每个整数(通过词表映射功能获得标记对应的整数)映射为相应维度(embedding_dim)的张量。

示例

  1. import torch
  2. from torch import nn
  3. #词表大小为8,Embedding向量维度3
  4. embedding=nn.Embedding(8,3)
  5. #输入形状(2,4)的整数张量,相当于2个长度为4的整数序列,每个整数范围是0-7
  6. input=torch.tensor([[0,1,2,1],[4,6,6,7]],dtype=torch.long)
  7. #调用embedding
  8. output=embedding(input)
  9. print(output)
  10. print(output.shape)

输出

张量形状为[2, 4, 3],即在原始输入最后增加一个长度为3 的维

  1. tensor([[[-0.9536, 0.3367, -1.1639],
  2. [-0.4816, -0.8973, -0.7700],
  3. [ 1.9007, 1.3130, -1.2717],
  4. [-0.4816, -0.8973, -0.7700]],
  5. [[ 0.5755, 0.8908, -1.0384],
  6. [ 0.3724, 0.1216, 2.4466],
  7. [ 0.3724, 0.1216, 2.4466],
  8. [ 1.4089, 0.2458, 0.2263]]], grad_fn=<EmbeddingBackward0>)
  9. torch.Size([2, 4, 3])

3、融入词向量层的多层感知器

基本的多层感知器,输入为固定大小的实数向量。如果输人为文本,即整数序列(假设已经利用词表映射工具将文本中每个标记映射为了相应的整数),在经过多层感知器之前,需要利用词向量层将输入的整数映射为向量。

但是,一个序列中通常含有多个词向量,那么如何将它们表示为一个多层感知器的输入向量呢?一种方法是将几个向量拼接成一个大小为的向量,其中d表示每个词向量的大小。这样做的一个问题是最终的预测结果与标记在序列中的位置过于相关。例如,如果在一个序列前面增加一个标记,则序列中的每个标记位置都变了,也就是它们对应的参数都发生了变化,那么模型预测的结果可能完全不同,这样显然不合理。

在自然语言处理中,可以使用词袋(Bag-Of-Words, Bow)模型解决该问题。词袋模型指的是在表示序列时,不考虑其中元素的顺序,而是将其简单地看成是一个集合。于是就可以采用聚合操作处理一个序列中的多个词向量,如求平均、求和或保留最大值等。融入词向量层以及词袋模型的多层感知器代码如下:

  1. import torch
  2. from torch import nn
  3. from torch.nn import functional as F
  4. class MLP(nn.Module):
  5. def __init__(self, vocab_size, embedding_dim, hidden_dim, num_class):
  6. super(MLP, self).__init__()
  7. # 词嵌入层
  8. self.embedding = nn.Embedding(vocab_size, embedding_dim)
  9. # 线性变换:词嵌入层->隐含层
  10. self.linear1 = nn.Linear(embedding_dim, hidden_dim)
  11. # 使用ReLU激活函数
  12. self.activate = F.relu
  13. # 线性变换:激活层->输出层
  14. self.linear2 = nn.Linear(hidden_dim, num_class)
  15. def forward(self, inputs):
  16. embeddings = self.embedding(inputs)
  17. # 将序列中多个embedding进行聚合(此处是求平均值)
  18. embedding = embeddings.mean(dim=1)
  19. hidden = self.activate(self.linear1(embedding))
  20. outputs = self.linear2(hidden)
  21. # 获得每个序列属于某一类别概率的对数值
  22. probs = F.log_softmax(outputs, dim=1)
  23. return probs
  24. mlp = MLP(vocab_size=8, embedding_dim=3, hidden_dim=5, num_class=2)
  25. # 输入为两个长度为4的整数序列
  26. inputs = torch.tensor([[0, 1, 2, 1], [4, 6, 6, 7]], dtype=torch.long)
  27. outputs = mlp(inputs)
  28. print(outputs)

输出:

结果为每个序列属于某一类别的概率的对数

  1. tensor([[-1.1612, -0.3756],
  2. [-0.8089, -0.5894]], grad_fn=<LogSoftmaxBackward0>)

在实际的自然语言处理任务中,一个批次里输人的文本长度往往是不固定的,因此无法像上面的代码一样简单地用一个张量存储词向量并求平均值。 PyTorch 提供了一种更灵活的解决方案,即EmnbeddingBag 层。在调用 Embedding-Bag 层时,首先需要将不定长的序列拼按起来,然后使用一个偏移向量 ( Offsets ) 记录每个序列的起始位置。

4、数据处理

第一步是将待处理的数据从硬盘或者其他地方加载到程序中,此时读入的是原始文本数据,还需要经过分句、标记解析等 预处理过程转换为标记序列,然后再使用词表映射工具将每个标记映射到相应的索引值。在此,使用 NLTK 提供的句子倾向性分析数据 (sentence_polarity) 作为示例,

  1. import torch
  2. from vocab import Vocab
  3. def load_sentence_polarity():
  4. from nltk.corpus import sentence_polarity
  5. #使用全部橘子集合(已经过标记解析)创建词表
  6. vocab = Vocab.build(sentence_polarity.sents())
  7. #褒贬各4000句子作为训练数据,使用创建的词表将标记映射为相应的索引
  8. #褒义标签为0,贬义标签为1
  9. #每个样例是一个有索引值列表和标签组成的元祖
  10. train_data = [(vocab.convert_tokens_to_ids(sentence), 0)
  11. for sentence in sentence_polarity.sents(categories='pos')[:4000]] \
  12. + [(vocab.convert_tokens_to_ids(sentence), 1)
  13. for sentence in sentence_polarity.sents(categories='neg')[:4000]]
  14. #其余的作为测试数据
  15. test_data = [(vocab.convert_tokens_to_ids(sentence), 0)
  16. for sentence in sentence_polarity.sents(categories='pos')[4000:]] \
  17. + [(vocab.convert_tokens_to_ids(sentence), 1)
  18. for sentence in sentence_polarity.sents(categories='neg')[4000:]]
  19. return train_data, test_data, vocab
  20. def length_to_mask(lengths):
  21. max_len = torch.max(lengths)
  22. mask = torch.arange(max_len).expand(lengths.shape[0], max_len) < lengths.unsqueeze(1)
  23. return mask
  24. def load_treebank():
  25. from nltk.corpus import treebank
  26. sents, postags = zip(*(zip(*sent) for sent in treebank.tagged_sents()))
  27. vocab = Vocab.build(sents, reserved_tokens=["<pad>"])
  28. tag_vocab = Vocab.build(postags)
  29. train_data = [(vocab.convert_tokens_to_ids(sentence), tag_vocab.convert_tokens_to_ids(tags)) for sentence, tags in zip(sents[:3000], postags[:3000])]
  30. test_data = [(vocab.convert_tokens_to_ids(sentence), tag_vocab.convert_tokens_to_ids(tags)) for sentence, tags in zip(sents[3000:], postags[3000:])]
  31. return train_data, test_data, vocab, tag_vocab

dataset 是 Dataset 类(在torch.utils.data 包中定义)的一个对象,用于存储数据,一般需要根据具体的数据存取需求创建 Dataset 类的子类。如创建 —个BowDataset 子类,其中 Bow 是词袋的意思。具体代码如下

  1. import torch
  2. from torch import nn, optim
  3. from torch.nn import functional as F
  4. from torch.utils.data import Dataset, DataLoader
  5. from collections import defaultdict
  6. from vocab import Vocab
  7. from utils import load_sentence_polarity
  8. class BowDataset(Dataset):
  9. def __init__(self, data):
  10. #data为原始数据
  11. self.data = data
  12. def __len__(self):
  13. #数据集中样例的数目
  14. return len(self.data)
  15. def __getitem__(self, i):
  16. #返回下标i的数据
  17. return self.data[i]
  18. #用于对样本进行整理
  19. def collate_fn(examples):
  20. #从独立样本集合中构建各批次的输入输出
  21. #其中,BowDataset类定义了一个样本的数据结构,即输入标签和轮出标悠的元组
  22. # 因此,将输入inputs定义为一个张量的列表,其中每个张量为原始句子中标记序列
  23. inputs = [torch.tensor(ex[0]) for ex in examples]
  24. #输出的目标targets 为该批次中全部样例输出结果 (0或1) 构成的张量
  25. targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long)
  26. # 获取一个批次中每个样例的序列长度
  27. offsets = [0] + [i.shape[0] for i in inputs]
  28. #根据序列的长度,转换为每个序列起始位置的偏移量
  29. offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)
  30. #将inputs列表中的张量拼接成一个大的张量
  31. inputs = torch.cat(inputs)
  32. return inputs, offsets, targets

5、多层感知机模型的训练与测试

  1. import torch
  2. from torch import nn, optim
  3. from torch.nn import functional as F
  4. from torch.utils.data import Dataset, DataLoader
  5. from collections import defaultdict
  6. from vocab import Vocab
  7. from utils import load_sentence_polarity
  8. # tqdm是一个Python模块,能以进度条的方式显示迭代的进度
  9. from tqdm.auto import tqdm
  10. # 超参数设置
  11. embedding_dim = 128
  12. hidden_dim = 256
  13. num_class = 2
  14. batch_size = 32
  15. num_epoch = 5
  16. # 加载数据
  17. train_data, test_data, vocab = load_sentence_polarity()
  18. train_dataset = BowDataset(train_data)
  19. test_dataset = BowDataset(test_data)
  20. train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True)
  21. test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False)
  22. # 加载模型
  23. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  24. model = MLP(len(vocab), embedding_dim, hidden_dim, num_class)
  25. model.to(device) # 将模型加载到CPU或GPU设备
  26. #训练过程
  27. nll_loss = nn.NLLLoss()
  28. optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用Adam优化器
  29. model.train()
  30. for epoch in range(num_epoch):
  31. total_loss = 0
  32. for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"):
  33. inputs, offsets, targets = [x.to(device) for x in batch]
  34. log_probs = model(inputs, offsets)
  35. loss = nll_loss(log_probs, targets)
  36. optimizer.zero_grad()
  37. loss.backward()
  38. optimizer.step()
  39. total_loss += loss.item()
  40. print(f"Loss: {total_loss:.2f}")
  41. # 测试过程
  42. acc = 0
  43. for batch in tqdm(test_data_loader, desc=f"Testing"):
  44. inputs, offsets, targets = [x.to(device) for x in batch]
  45. with torch.no_grad():
  46. output = model(inputs, offsets)
  47. acc += (output.argmax(dim=1) == targets).sum().item()
  48. # 输出在测试集上的准确率
  49. print(f"Acc: {acc / len(test_data_loader):.2f}")

训练结果

6、基于卷积神经网络的情感分类

第3 节的词袋模型表示文本,只考虑了文本中词语的信息,而忽视了词组信息,如句子

我不喜欢这部电影

词袋模型看到文本中有“喜欢”一词,则很可能将其识别为褒义。而卷积神经网络可以提取词组信息,如将卷积核的大小设置为 2,则可以提取特征“不 喜欢” 等,显然这对于最终情感极性的判断至关重要。卷积神经网络的大部分代码与多层感知器的实现一致

其中的不同之处:

  • 模型不同,需要从 nn. Module 类派生一个 CMN 子类

  • 在调用卷积神经网络时,还需要设置两个额外的超参数,分别为filter_size =3(卷积核的大小)和num_tilter =100(卷积核的个数)

  • 数据整理函数(collate_fn),pad_seguence 两数实现补齐 (Padding)功能,使得一个批次中 全部宇列长度相同(同最大长度序列),不足的默认使用0补齐。

除了以上不同,其他代码与多层感知器的实现几乎一致。如要实现一个基于新模型的情感分类任务,只需要定义一个nn.Module类的子类,并修改数据整理函数(collate_fn)即可

  1. import torch
  2. from torch import nn, optim
  3. from torch.nn import functional as F
  4. from torch.utils.data import Dataset, DataLoader
  5. from torch.nn.utils.rnn import pad_sequence
  6. from collections import defaultdict
  7. from vocab import Vocab
  8. from utils import load_sentence_polarity
  9. class CnnDataset(Dataset):
  10. def __init__(self, data):
  11. self.data = data
  12. def __len__(self):
  13. return len(self.data)
  14. def __getitem__(self, i):
  15. return self.data[i]
  16. #另外,数据整理函数也需要进行一些修改。
  17. # pad_seguence 两数实现补齐 (Padding)功能,使得一个批次中 全部序列长度相同(同最大长度序列),不足的默认使用0补齐。
  18. def collate_fn(examples):
  19. inputs = [torch.tensor(ex[0]) for ex in examples]
  20. targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long)
  21. # 对batch内的样本进行padding,使其具有相同长度
  22. inputs = pad_sequence(inputs, batch_first=True)
  23. return inputs, targets
  24. #模型不同,需要从nn.Module类派生一个 CNN 子类
  25. class CNN(nn.Module):
  26. def __init__(self, vocab_size, embedding_dim, filter_size, num_filter, num_class):
  27. super(CNN, self).__init__()
  28. self.embedding = nn.Embedding(vocab_size, embedding_dim)
  29. #padding=1表示在卷积操作之前,将序列的前后各补充1个输入
  30. self.conv1d = nn.Conv1d(embedding_dim, num_filter, filter_size, padding=1)
  31. self.activate = F.relu
  32. self.linear = nn.Linear(num_filter, num_class)
  33. def forward(self, inputs):
  34. embedding = self.embedding(inputs)
  35. convolution = self.activate(self.conv1d(embedding.permute(0, 2, 1)))
  36. pooling = F.max_pool1d(convolution, kernel_size=convolution.shape[2])
  37. outputs = self.linear(pooling.squeeze(dim=2))
  38. log_probs = F.log_softmax(outputs, dim=1)
  39. return log_probs
  40. #tqdm是一个Pyth模块,能以进度条的方式显示迭代的进度
  41. from tqdm.auto import tqdm
  42. #超参数设置
  43. embedding_dim = 128
  44. hidden_dim = 256
  45. num_class = 2
  46. batch_size = 32
  47. num_epoch = 5
  48. #在调用卷积神经网络时,还需要设置两个额外的超参数,分别为filter_size =3(卷积核的大小)和num_tilter =100(卷积核的个数)。
  49. filter_size = 3
  50. num_filter = 100
  51. #加载数据
  52. train_data, test_data, vocab = load_sentence_polarity()
  53. train_dataset = CnnDataset(train_data)
  54. test_dataset = CnnDataset(test_data)
  55. train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True)
  56. test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False)
  57. #加载模型
  58. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  59. model = CNN(len(vocab), embedding_dim, filter_size, num_filter, num_class)
  60. model.to(device) #将模型加载到CPU或GPU设备
  61. #训练过程
  62. nll_loss = nn.NLLLoss()
  63. optimizer = optim.Adam(model.parameters(), lr=0.001) #使用Adam优化器
  64. model.train()
  65. for epoch in range(num_epoch):
  66. total_loss = 0
  67. for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"):
  68. inputs, targets = [x.to(device) for x in batch]
  69. log_probs = model(inputs)
  70. loss = nll_loss(log_probs, targets)
  71. optimizer.zero_grad()
  72. loss.backward()
  73. optimizer.step()
  74. total_loss += loss.item()
  75. print(f"Loss: {total_loss:.2f}")
  76. #测试过程
  77. acc = 0
  78. for batch in tqdm(test_data_loader, desc=f"Testing"):
  79. inputs, targets = [x.to(device) for x in batch]
  80. with torch.no_grad():
  81. output = model(inputs)
  82. acc += (output.argmax(dim=1) == targets).sum().item()
  83. #输出在测试集上的准确率
  84. print(f"Acc: {acc / len(test_data_loader):.2f}")

训练结果

7、基于循环神经网络的情感分类

第3 节的词袋模型还忽路了文本中词的顺序信息,因此对于两个句子 “张三行李四”和“李四行张三”,它们的表示是完全相同的,但显然这并不合理。循环神经网络模型能更好地对序列数据进行表示。

以长短时记忆(LSTM) 网络为例。

其中,大部分代码与前面的实现一致,不同之处:

  • 需要从nn.Module派生一个LSTM子类

  • forward中使用 pack_padded_sequence将变长序列打包,功能是将之前补齐过的一个小批次序列打包成一个序列,其中每个原始序列的长度存储在lengths中,能被self.lstm直接调用

  1. import torch
  2. from torch import nn, optim
  3. from torch.nn import functional as F
  4. from torch.utils.data import Dataset, DataLoader
  5. from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence
  6. from collections import defaultdict
  7. from vocab import Vocab
  8. from utils import load_sentence_polarity
  9. #tqdm是一个Python模块,能以进度条的方式显式迭代的进度
  10. from tqdm.auto import tqdm
  11. class LstmDataset(Dataset):
  12. def __init__(self, data):
  13. self.data = data
  14. def __len__(self):
  15. return len(self.data)
  16. def __getitem__(self, i):
  17. return self.data[i]
  18. #整理函数
  19. def collate_fn(examples):
  20. #获得每个序列的长度
  21. lengths = torch.tensor([len(ex[0]) for ex in examples])
  22. inputs = [torch.tensor(ex[0]) for ex in examples]
  23. targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long)
  24. # 对batch内的样本进行padding,使其具有相同长度
  25. inputs = pad_sequence(inputs, batch_first=True)
  26. return inputs, lengths, targets
  27. #需要从nn.Module派生一个LSTM子类
  28. class LSTM(nn.Module):
  29. def __init__(self, vocab_size, embedding_dim, hidden_dim, num_class):
  30. super(LSTM, self).__init__()
  31. self.embeddings = nn.Embedding(vocab_size, embedding_dim)
  32. self.lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)
  33. self.output = nn.Linear(hidden_dim, num_class)
  34. def forward(self, inputs, lengths):
  35. embeddings = self.embeddings(inputs)
  36. #使用 pack_padded_sequence将变长序列打包
  37. x_pack = pack_padded_sequence(embeddings, lengths, batch_first=True, enforce_sorted=False)
  38. hidden, (hn, cn) = self.lstm(x_pack)
  39. outputs = self.output(hn[-1])
  40. log_probs = F.log_softmax(outputs, dim=-1)
  41. return log_probs
  42. embedding_dim = 128
  43. hidden_dim = 256
  44. num_class = 2
  45. batch_size = 32
  46. num_epoch = 5
  47. #加载数据
  48. train_data, test_data, vocab = load_sentence_polarity()
  49. train_dataset = LstmDataset(train_data)
  50. test_dataset = LstmDataset(test_data)
  51. train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True)
  52. test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False)
  53. #加载模型
  54. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  55. model = LSTM(len(vocab), embedding_dim, hidden_dim, num_class)
  56. model.to(device) #将模型加载到GPU中(如果已经正确安装)
  57. #训练过程
  58. nll_loss = nn.NLLLoss()
  59. optimizer = optim.Adam(model.parameters(), lr=0.001) #使用Adam优化器
  60. model.train()
  61. for epoch in range(num_epoch):
  62. total_loss = 0
  63. for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"):
  64. inputs, lengths, targets = [x.to(device) for x in batch]
  65. log_probs = model(inputs, lengths)
  66. loss = nll_loss(log_probs, targets)
  67. optimizer.zero_grad()
  68. loss.backward()
  69. optimizer.step()
  70. total_loss += loss.item()
  71. print(f"Loss: {total_loss:.2f}")
  72. #测试过程
  73. acc = 0
  74. for batch in tqdm(test_data_loader, desc=f"Testing"):
  75. inputs, lengths, targets = [x.to(device) for x in batch]
  76. with torch.no_grad():
  77. output = model(inputs, lengths)
  78. acc += (output.argmax(dim=1) == targets).sum().item()
  79. #输出在测试集上的准确率
  80. print(f"Acc: {acc / len(test_data_loader):.2f}")

训练结果

8、基于 Transformer 的情感分类

基于 Transformer 实现情感分类与使用 LSTM 也非常相似,主要有一处不同, 即需要定义 Transformer模型。

  1. # Defined in Section 4.6.8
  2. import math
  3. import torch
  4. from torch import nn, optim
  5. from torch.nn import functional as F
  6. from torch.utils.data import Dataset, DataLoader
  7. from torch.nn.utils.rnn import pad_sequence, pack_padded_sequence
  8. from collections import defaultdict
  9. from vocab import Vocab
  10. from utils import load_sentence_polarity, length_to_mask
  11. # tqdm是一个Pyth模块,能以进度条的方式显式迭代的进度
  12. from tqdm.auto import tqdm
  13. class TransformerDataset(Dataset):
  14. def __init__(self, data):
  15. self.data = data
  16. def __len__(self):
  17. return len(self.data)
  18. def __getitem__(self, i):
  19. return self.data[i]
  20. def collate_fn(examples):
  21. lengths = torch.tensor([len(ex[0]) for ex in examples])
  22. inputs = [torch.tensor(ex[0]) for ex in examples]
  23. targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long)
  24. # 对batch内的样本进行padding,使其具有相同长度
  25. inputs = pad_sequence(inputs, batch_first=True)
  26. return inputs, lengths, targets
  27. class PositionalEncoding(nn.Module):
  28. def __init__(self, d_model, dropout=0.1, max_len=512):
  29. super(PositionalEncoding, self).__init__()
  30. pe = torch.zeros(max_len, d_model)
  31. position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
  32. div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
  33. pe[:, 0::2] = torch.sin(position * div_term)
  34. pe[:, 1::2] = torch.cos(position * div_term)
  35. pe = pe.unsqueeze(0).transpose(0, 1)
  36. self.register_buffer('pe', pe)
  37. def forward(self, x):
  38. x = x + self.pe[:x.size(0), :]
  39. return x
  40. class Transformer(nn.Module):
  41. def __init__(self, vocab_size, embedding_dim, hidden_dim, num_class,
  42. dim_feedforward=512, num_head=2, num_layers=2, dropout=0.1, max_len=128, activation: str = "relu"):
  43. super(Transformer, self).__init__()
  44. # 词嵌入层
  45. self.embedding_dim = embedding_dim
  46. self.embeddings = nn.Embedding(vocab_size, embedding_dim)
  47. self.position_embedding = PositionalEncoding(embedding_dim, dropout, max_len)
  48. # 编码层:使用Transformer
  49. encoder_layer = nn.TransformerEncoderLayer(hidden_dim, num_head, dim_feedforward, dropout, activation)
  50. self.transformer = nn.TransformerEncoder(encoder_layer, num_layers)
  51. # 输出层
  52. self.output = nn.Linear(hidden_dim, num_class)
  53. def forward(self, inputs, lengths):
  54. inputs = torch.transpose(inputs, 0, 1)
  55. #与LSTM处理情况相同,输入的数据的第一维是批次,需要转换为TransformerEncoder
  56. #所需要的第1维是长度,第二维是批次的形状
  57. hidden_states = self.embeddings(inputs)
  58. hidden_states = self.position_embedding(hidden_states)
  59. #length_to_mask作用是根据批次中每个序列的长度生成mask矩阵,以便处理长度不一致的序列,忽略掉比较短的序列的无效部分
  60. #src_key_padding_mask的参数正好与length_to_mask的结果相反(无自注意力的部分为true)
  61. attention_mask = length_to_mask(lengths.to('cpu')) == False
  62. #根据批次中的每个序列长度生成mask矩阵
  63. hidden_states = self.transformer(hidden_states, src_key_padding_mask=attention_mask.to('cuda'))
  64. hidden_states = hidden_states[0, :, :]
  65. #取第一个标记的输出结果作为分类层的输入
  66. output = self.output(hidden_states)
  67. log_probs = F.log_softmax(output, dim=1)
  68. return log_probs
  69. embedding_dim = 128
  70. hidden_dim = 128
  71. num_class = 2
  72. batch_size = 32
  73. num_epoch = 5
  74. # 加载数据
  75. train_data, test_data, vocab = load_sentence_polarity()
  76. train_dataset = TransformerDataset(train_data)
  77. test_dataset = TransformerDataset(test_data)
  78. train_data_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=True)
  79. test_data_loader = DataLoader(test_dataset, batch_size=1, collate_fn=collate_fn, shuffle=False)
  80. # 加载模型
  81. device = torch.device('cuda')
  82. model = Transformer(len(vocab), embedding_dim, hidden_dim, num_class)
  83. model.to(device) # 将模型加载到GPU中(如果已经正确安装)
  84. # 训练过程
  85. nll_loss = nn.NLLLoss()
  86. optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用Adam优化器
  87. model.train()
  88. for epoch in range(num_epoch):
  89. total_loss = 0
  90. for batch in tqdm(train_data_loader, desc=f"Training Epoch {epoch}"):
  91. inputs, lengths, targets = [x.to(device) for x in batch]
  92. log_probs = model(inputs, lengths)
  93. loss = nll_loss(log_probs, targets)
  94. optimizer.zero_grad()
  95. loss.backward()
  96. optimizer.step()
  97. total_loss += loss.item()
  98. print(f"Loss: {total_loss:.2f}")
  99. # 测试过程
  100. acc = 0
  101. for batch in tqdm(test_data_loader, desc=f"Testing"):
  102. inputs, lengths, targets = [x.to(device) for x in batch]
  103. with torch.no_grad():
  104. output = model(inputs, lengths)
  105. acc += (output.argmax(dim=1) == targets).sum().item()
  106. # 输出在测试集上的准确率
  107. print(f"Acc: {acc / len(test_data_loader):.2f}")

训练结果

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/349871
推荐阅读
相关标签
  

闽ICP备14008679号