您的位置:首页 > 财经 > 产业 > N3 - Pytorch文本分类入门

N3 - Pytorch文本分类入门

2024/10/6 2:23:11 来源:https://blog.csdn.net/wodehaoyoule/article/details/140604350  浏览:    关键词:N3 - Pytorch文本分类入门
  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊

目录

  • 文本分类的基本流程
  • 常用的数据清洗方法
  • 如何使用jieba实现英文分词
  • 如何构建文本向量
  • 代码实践
    • 数据准备
    • 构建词典
    • 生成数据批次和迭代器
    • 模型设计
    • 模型创建
    • 模型训练
    • 评估模型
  • 总结与心得体会


文本分类的基本流程

文本分类的基本流程

常用的数据清洗方法

如何使用jieba实现英文分词

如何构建文本向量

代码实践

数据准备

使用AG News数据集进行文本分类。

AG News(AG’s News Topic Classification Dataset)是一个广泛用于文本分类任务的数据集,尤其是在新闻领域。该数据集是由AG’s Corpus of News Articles收集整理而来,包含了四个主要类别: 世界、体育、商业和科技。

# 使用torchtext导入数据集
import torch
torch.utils.data.datapipes.utils.common.DILL_AVAILABLE = torch.utils._import_utils.dill_available()from torchtext.datasets import AG_NEWS
train_iter = AG_NEWS(split='train')

我们通过打印数据内容查看一下数据集的格式

for i, data in enumerate(train_iter):print(data)if i == 3:break

数据集格式
由此可见数据集的每一个条目是一个元组,包含新闻文章所属的类别和新闻文章的文本内容,其中类别是一个整数,从1到4,分别对应 世界、科技、体育和商业。

构建词典

要构建词典,需要一个分词器,将句子分成分散的词后,再创建词典。也就是上图文本分类任务中的:文本清洗、分词、文本向量化这三步做的事情。

from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iteratortokenizer = get_tokenizer('basic_english')def yield_tokens(data_iter):for _, text in data_iter:yield tokenizer(text)vocab = build_vocab_from_iterator(yield_tokens(train_iter), specials=['<unk>'])
# 给未知单词设置一个默认索引,当一个单词不在词库中,就取默认索引,将它表示为<unk>
vocab.set_default_index(vocab['<unk>'])

get_tokenizer用于获取分词器函数,分词器可以将一个字符串转换成一个单词的列表

print(tokenizer('Here is the example'))

分词器
vocab是使用torchtext的函数构建出的字典对象,可以使用它直接将单词转换为对应的词典序号,然后可以将序号转换为词向量(例如使用one-hot编码)。

print(vocab(['here', 'is', 'the', 'example']))

将单词转换为序号

生成数据批次和迭代器

import torch
from torch.utils.data import DataLoadertext_pipeline = lambda x: vocab(tokenizer(x))
label_pipeline = lambda x: int(x) - 1device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')def collate_batch(batch):label_list, text_list, offsets = [], [], [0]for _label, _text in batch:# 将一个批次的标签汇集起来label_list.append(label_pipeline(_label))# 将一个批次的文本转换成序号汇集起来processed_text = torch.tensor(text_pipeline(_text), dtype=torch.int64)text_list.append(processed_text)# 当前批次中每个句子的长度offsets.append(processed_text.size(0))label_list = torch.tensor(label_list, dtype=torch.int64)text_list = torch.cat(text_list, dim=0)offsets = torch.tensor(offsets[:-1]).cumsum(dim=0) # 把每个句子的长度累计求合,成为真正的偏移量return label_list.to(device), text_list.to(device), offsets.to(device)# 生成DataLoader
dataloader = DataLoader(train_iter, batch_size=8, shuffle=False, collate_fn=collate_batch)

模型设计

模型结构
模型的结构如上图所示,对文本进行嵌入后,将句子的嵌入结果进行均值聚合,也就是使用EmbeddingBag mode为mean

from torch import nn
class TextClassificationModel(nn.Module):def __init__(self, vocab_size, embed_dim, num_classes):super().__init__()self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse=False)self.fc = nn.Linear(embed_dim, num_class)self.init_weights()def init_weights(self):initrange = 0.5self.embedding.weight.data.uniform_(-initrange, initrange)self.fc.weight.data.uniform_(-initrange, initrange)self.fc.bias.data.zero_()def forward(self, text, offsets):embedded = self.embedding(text, offsets)return self.fc(embedded)

以上模型中

  • self.embedding 是词嵌入层。作用是将离散的单词表示 (这里直接是单词的词典序号)映射为固定大小的连续向量(也就是单词的向量化)。这些向量捕捉了单词之间的词义关系,并作为网络的输入。
  • self.embedding.weight 是词嵌入层的权重矩阵,它的形状为(vocab_size, embed_dim),其中vocab_size是词汇表的大小,embed_dim是嵌入向量的维度
  • self.embedding.weight.data 是权重矩阵的数据部分,对它进行操作也就直接操作了底层的权重张量
  • .uniform_(-initrange, initrange) 这代表执行了一个原地操作(in-place operation),用于将权重矩阵的值用一个均匀分布进行初始化。均匀分布的范围是[-initrange,initrange],其中initrange是一个正数。

模型创建

num_class = len(set([label for (label, text) in train_iter]))
vocab_size = len(vocab)
em_size = 64
model = TextClassificationModel(vocab_size, em_size, num_class).to(device)

创建模型对象

模型训练

定义训练函数与评估函数

import time
def train(dataloader):model.train()total_acc, train_loss, total_count = 0, 0, 0log_interval = 500start_time = time.time()for idx, (label, text, offsets) in enumerate(dataloader):predicted_label = model(text, offsets)optimizer.zero_grad()loss = criterion(predicted_label, label)loss.backward()optimizer.step()total_acc += (predicted_label.argmax(1) == label).sum().item()train_loss += loss.item()total_count += label.size(0)if idx % log_interval == 0 and idx > 0:elapsed = time.time() - start_timeprint('| epoch {:1d} | {:4d}/{:4d} batches''| train_acc {:4.3f} train_loss {:4.5f}'.format(epoch, idx, len(dataloader), total_acc/total_count, train_loss/total_count))total_acc, train_loss, total_count = 0, 0, 0start_time = time.time()def evaluate(dataloader):model.eval()total_acc, train_loss, total_count = 0, 0, 0with torch.no_grad():for idx, (label, text, offsets) in enumerate(dataloader):predicted_label = model(text, offsets)loss = criterion(predicted_label, label)total_acc += (predicted_label.argmax(1) == label).sum().item()train_loss += loss.item()total_count += label.size(0)return total_acc/total_count, train_loss/total_count

开始训练

from torch.utils.data import random_split
from torchtext.data.functional import to_map_style_dataset# 超参数
EPOCHS = 10
LR = 5
BATCH_SIZE = 64criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=LR)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1.0, gamma=0.1)
total_accu = Nonetrain_iter, test_iter = AG_NEWS()
train_dataset = to_map_style_dataset(train_iter)
test_dataset = to_map_style_dataset(test_iter)
num_train = int(len(train_dataset) * 0.95)split_train_, split_valid_ = random_split(train_dataset, [num_train, len(train_dataset) - num_train])train_dataloader = DataLoader(split_train_, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_batch)
valid_dataloader = DataLoader(split_valid_, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_batch)
test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_batch)for epoch in range(1, EPOCHS + 1):epoch_start_time = time.time()train(train_dataloder)val_acc, val_loss = evaluate(valid_dataloader)if total_accu is not None and total_accu > val_acc:scheduler.step()else:total_accu = val_accprint('-'*69)print('| epoch {:1d} | time: {:4.2f}s | ''valid_acc {:4.3f} valid_loss {:4.3f}'.format(epoch, time.time() - epoch_start_time, val_acc, val_loss))print('-'*69)

在上面的代码中,to_map_style_dataset的作用是将一个迭代的数据集(iterable-style dataset)转换为映射式的数据集(Map-style dataset)。这个转换使得我们可以通过索引更方便地访问数据集中的元素。

在pytorch中,数据集可以分成两种类型,Iterable-style和Map-style。Iterable-style数据集实现了__iter__()方法,可以迭代访问数据集中的元素,但不支持通过索引访问。而Map-style数据集实现了__getitem__()__len__()方法,可以直接通过索引访问特定元素,并能获取数据集的大小。

torchtext是pytorch的一个扩展库,专注于处理文本数据。torchtext.data.functional中的to_map_style_dataset函数可以帮助我们将一个Iterable-style的数据集转换为一个易于操作的Map-style的数据集。然后就可以通过索引直接访问数据集中的特定样本,从而简化训练、验证和测试过程中的数据处理。
训练过程如下:

| epoch 1 |  500/1782 batches| train_acc 0.907 train_loss 0.00429
| epoch 1 | 1000/1782 batches| train_acc 0.906 train_loss 0.00431
| epoch 1 | 1500/1782 batches| train_acc 0.909 train_loss 0.00421
---------------------------------------------------------------------
| epoch 1 time: 6.77s | valid_acc 0.913 valid_loss 0.004
---------------------------------------------------------------------
| epoch 2 |  500/1782 batches| train_acc 0.921 train_loss 0.00369
| epoch 2 | 1000/1782 batches| train_acc 0.920 train_loss 0.00375
| epoch 2 | 1500/1782 batches| train_acc 0.918 train_loss 0.00376
---------------------------------------------------------------------
| epoch 2 time: 6.80s | valid_acc 0.917 valid_loss 0.004
---------------------------------------------------------------------
| epoch 3 |  500/1782 batches| train_acc 0.930 train_loss 0.00323
| epoch 3 | 1000/1782 batches| train_acc 0.926 train_loss 0.00334
| epoch 3 | 1500/1782 batches| train_acc 0.925 train_loss 0.00343
---------------------------------------------------------------------
| epoch 3 time: 6.93s | valid_acc 0.860 valid_loss 0.006
---------------------------------------------------------------------
| epoch 4 |  500/1782 batches| train_acc 0.943 train_loss 0.00267
| epoch 4 | 1000/1782 batches| train_acc 0.945 train_loss 0.00263
| epoch 4 | 1500/1782 batches| train_acc 0.946 train_loss 0.00265
---------------------------------------------------------------------
| epoch 4 time: 6.83s | valid_acc 0.926 valid_loss 0.004
---------------------------------------------------------------------
| epoch 5 |  500/1782 batches| train_acc 0.947 train_loss 0.00256
| epoch 5 | 1000/1782 batches| train_acc 0.947 train_loss 0.00258
| epoch 5 | 1500/1782 batches| train_acc 0.947 train_loss 0.00261
---------------------------------------------------------------------
| epoch 5 time: 6.76s | valid_acc 0.921 valid_loss 0.004
---------------------------------------------------------------------
| epoch 6 |  500/1782 batches| train_acc 0.948 train_loss 0.00253
| epoch 6 | 1000/1782 batches| train_acc 0.949 train_loss 0.00253
| epoch 6 | 1500/1782 batches| train_acc 0.951 train_loss 0.00241
---------------------------------------------------------------------
| epoch 6 time: 6.93s | valid_acc 0.926 valid_loss 0.004
---------------------------------------------------------------------
| epoch 7 |  500/1782 batches| train_acc 0.949 train_loss 0.00248
| epoch 7 | 1000/1782 batches| train_acc 0.949 train_loss 0.00250
| epoch 7 | 1500/1782 batches| train_acc 0.949 train_loss 0.00248
---------------------------------------------------------------------
| epoch 7 time: 6.85s | valid_acc 0.926 valid_loss 0.004
---------------------------------------------------------------------
| epoch 8 |  500/1782 batches| train_acc 0.948 train_loss 0.00247
| epoch 8 | 1000/1782 batches| train_acc 0.950 train_loss 0.00250
| epoch 8 | 1500/1782 batches| train_acc 0.951 train_loss 0.00243
---------------------------------------------------------------------
| epoch 8 time: 6.76s | valid_acc 0.926 valid_loss 0.004
---------------------------------------------------------------------
| epoch 9 |  500/1782 batches| train_acc 0.951 train_loss 0.00239
| epoch 9 | 1000/1782 batches| train_acc 0.948 train_loss 0.00259
| epoch 9 | 1500/1782 batches| train_acc 0.951 train_loss 0.00244
---------------------------------------------------------------------
| epoch 9 time: 6.87s | valid_acc 0.926 valid_loss 0.004
---------------------------------------------------------------------

评估模型

print('Checking the results of test dataset.')
test_acc, test_loss = evaluate(test_dataloader)
print('test accuracy {:8.3f}'.format(test_acc))

评估结果

总结与心得体会

文本分类任务,关键的是前面对文本的处理,合并,嵌入,最后的分类反而非常简直,直接使用了一层全连接层就可以达到不错的效果了。

在复现的过程中,由于使用的库版本不一致导致torchtext库部分代码无法正常运行,卡了好久,后面搜索了一些之前打卡的同学的博客,才找到解决方案。复现模型时尽量不要使用最新的版本,而是使用原来的版本,先运行起来,再改动。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com