- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、pytorch文本分类预览
Pytorch文本分类涉及将文本数据转换为机器可以理解和处理的形式,然后使用深度学习模型进行训练,以预测文本的类别。这个过程通常包括以下几个步骤:
- 文本预处理:
- 分词:将文本分割成单词或标记。在中文文本中,这通常涉及到将汉字转换为拼音或将其分割成词组。
- 去停用词:从文本中移除常见的、无意义的停用词(如“的”、“是”等)。
- 词干提取/词形还原:将单词转换为它们的基本形式。
- 词向量化:将单词转换为数值向量,以便模型可以处理。常见的词向量化方法包括One-hot编码、TF-IDF和Word Embedding。
- 构建模型:
- 嵌入层:使用预训练的词向量或自己训练的词向量来初始化模型。
- 序列处理层:如LSTM、GRU或Transformer,用于捕捉文本中的序列信息。
- 全连接层:用于将序列特征转换为分类输出。
- 损失函数:如交叉熵损失,用于计算模型预测与真实标签之间的差异。
- 优化器:如SGD、Adam,用于更新模型参数。
- 训练模型:
- 使用训练数据对模型进行训练,通过反向传播和优化器更新模型参数。
- 定期验证模型的性能,并根据需要调整模型和超参数。
- 评估模型:
- 使用验证数据评估模型的性能。
- 计算准确率、召回率、F1分数等评估指标。
- 部署模型:
- 将训练好的模型部署到生产环境中,用于对新文本进行分类。
Pytorch文本分类的原理在于利用深度学习模型的强大表达能力,通过训练学习文本数据中的特征,然后使用这些特征来预测文本的类别。模型的性能很大程度上取决于预处理的质量、模型的设计、训练数据的质量和数量以及超参数的选择。
- 将训练好的模型部署到生产环境中,用于对新文本进行分类。
什么是文本偏移量,为什么有文本偏移量
文本偏移量(offsets)在处理文本数据时是用来记录每个样本的文本在批量数据中的起始位置。这种做法通常用于处理序列数据,如文本数据,当文本长度不一致时。
在文本分类任务中,每个样本可能包含不同数量的文本,这意味着在将文本数据传递给模型之前,需要对它们进行适当的重组。文本偏移量的作用是在批量数据中正确地对齐每个样本的文本。具体来说,偏移量是一个列表,其中每个元素代表批量数据中对应样本的文本开始位置。
例如,假设我们有三个文本样本,它们的文本长度分别为5、8和10:
- 样本1: “here is”
- 样本2: “an example”
- 样本3: “another example”
如果我们将这三个样本组合成一个批量,我们需要知道每个样本的文本在批量中的起始位置。文本偏移量可以帮助我们做到这一点: - 偏移量: [0, 5, 13]
这意味着: - 样本1的文本从批量数据的索引0开始。
- 样本2的文本从批量数据的索引5开始(因为它是紧随样本1的文本之后的)。
- 样本3的文本从批量数据的索引13开始(因为它是紧随样本2的文本之后的)。
在模型前向传播时,我们使用这些偏移量来正确地对齐每个样本的文本,并将其传递给模型。这样,即使文本长度不同,模型也能正确地处理每个样本。
二、实战
1.导入初始包,检查设备是否可用
import torch
import torch.nn as nn
import torchvision
from torchvision import transforms, datasets
import os,PIL,pathlib,warnings
warnings.filterwarnings("ignore") #忽略警告信息
# win10系统,调用GPU运行
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
device
输出:device(type=‘cuda’)
2.导入数据,分割训练验证集
from torchtext.datasets import AG_NEWS
train_iter = AG_NEWS(split='train')
test_iter = AG_NEWS(split='test')
3.处理文本数据,构建词汇表
from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator
tokenizer = 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>"])
vocab.set_default_index(vocab["<unk>"]) #设置默认索引,如果找不到单词,则会选择默认索引
这段代码使用了PyTorch的
torchtext
库来处理文本数据,并构建了一个词汇表(vocab)。
from torchtext.data.utils import get_tokenizer
: 从torchtext.data.utils
模块中导入get_tokenizer
函数。这个函数用于获取一个分词器(tokenizer),它可以将文本数据分割成单词或标记。from torchtext.vocab import build_vocab_from_iterator
: 从torchtext.vocab
模块中导入build_vocab_from_iterator
函数。这个函数用于从迭代器中构建词汇表,它会统计文本数据中出现的所有单词,并为它们分配索引。tokenizer = get_tokenizer('basic_english')
: 调用get_tokenizer
函数,并传入参数'basic_english'
,创建一个基本的英文分词器。这个分词器能够处理基本的英文文本,将文本分割成单词。def yield_tokens(data_iter):
: 定义一个名为yield_tokens
的函数,它接受一个参数data_iter
,这个参数预计是一个文本数据的迭代器。for _, text in data_iter:
: 在yield_tokens
函数内部,这行代码开始一个循环,遍历data_iter
中的每一项。这里的下划线_
表示我们不关心迭代器中的第一个元素(通常是一个标签),我们只关心文本内容。yield tokenizer(text)
: 在循环内部,这行代码对每个文本调用分词器tokenizer
,并将分词结果作为生成器的一部分yield
出去。这意味着yield_tokens
函数是一个生成器函数,它会产生一个包含所有文本分词结果的迭代器。vocab = build_vocab_from_iterator(yield_tokens(train_iter), specials = [ "<unk>"])
:
调用build_vocab_from_iterator
函数,传入yield_tokens(train_iter)
作为参数,这意味着它会使用train_iter
迭代器中的文本数据来构建词汇表。specials=["<unk>"]
参数表示在构建词汇表时,我们希望添加一个特殊的未知(unknown)标记"<unk>"
,用于表示词汇表中未出现的单词。vocab.set_default_index(vocab["<unk>"])
: 设置词汇表的默认索引。如果在使用词汇表时遇到一个未在词汇表中出现的单词,它会使用默认索引来代替。在这里,默认索引被设置为"<unk>"
的索引,这样任何未知的单词都会被映射到这个索引上。
这里是调用几个词进行抽查索引↓↓↓↓↓
vocab(['here', 'is', 'an', 'example'])
输出:[475, 21, 30, 5297]
text_pipeline = lambda x: vocab(tokenizer(x))
label_pipeline = lambda x: int(x) - 1
text_pipeline('here is the an example')
输出: [475, 21, 2, 30, 5297]
这段代码定义了两个管道(pipeline)函数,
text_pipeline
和label_pipeline
,用于处理文本数据和标签数据。
text_pipeline = lambda x: vocab(tokenizer(x))
: 定义了一个名为text_pipeline
的匿名函数(lambda function),它接受一个参数x
,并返回vocab(tokenizer(x))
的结果。这个函数的作用是将输入的文本x
通过分词器tokenizer
分词,然后将分词结果传递给词汇表vocab
,以获取对应的词汇索引。label_pipeline = lambda x: int(x) - 1
: 定义了一个名为label_pipeline
的匿名函数,它接受一个参数x
,并返回int(x) - 1
的结果。这个函数的作用是将输入的标签字符串x
转换为整数,然后减去1。这是因为在PyTorch的分类任务中,通常使用one-hot编码,其中第一个类别索引是0,而不是1。text_pipeline('here is the an example')
: 调用text_pipeline
函数,并将字符串'here is the an example'
作为参数传递给它。这个函数的作用是将输入的文本'here isthe an example'
分词,然后将分词结果传递给词汇表vocab
,以获取对应的词汇索引。输出将是这些词汇的索引。
label_pipeline('10')
输出:9
根据上文提供的代码,
label_pipeline
是一个定义好的函数,它用于处理标签数据。这个函数将一个标签字符串转换为一个整数,然后减去1。这是因为PyTorch的分类任务通常使用one-hot编码,其中第一个类别索引是0,而不是1。
所以,label_pipeline('10')
的意思是将字符串'10'
作为参数传递给label_pipeline
函数。这个函数会将'10'
转换为一个整数,即10
,然后减去1,得到9
。
在机器学习中,这样的处理是为了使标签与模型的输出对齐。模型的输出通常是一个概率分布,其中每个类别的概率可以通过索引访问。通过将字符串标签转换为整数,我们可以将这个整数作为索引来访问模型输出中对应类别的概率。
4.生成数据批次和迭代器
这里文本偏移量的知识可以往前翻
from torch.utils.data import DataLoader
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)
offsets = torch.tensor(offsets[:-1]).cumsum(dim=0) #返回维度dim中输入元素的累计和
return label_list.to(device), text_list.to(device), offsets.to(device)
# 数据加载器
dataloader = DataLoader(train_iter,
batch_size=8,
shuffle =False,
collate_fn=collate_batch)
这段代码定义了一个名为
collate_batch
的函数,用于将多个批次的数据组合成一个批次。这个函数被用于DataLoader
实例,以处理文本分类任务中的数据。
from torch.utils.data import DataLoader
: 从PyTorch的torch.utils.data
模块中导入DataLoader
类。这个类用于创建一个迭代器,它可以高效地加载数据,并为模型提供批量数据。def collate_batch(batch):
: 定义一个名为collate_batch
的函数,它接受一个参数batch
。这个参数是一个包含多个样本的数据列表,每个样本是一个包含标签和文本数据的元组。label_list, text_list, offsets = [], [], [0]
: 初始化三个列表,分别用于存储标签、处理后的文本和偏移量。偏移量用于指示每个样本的文本在批量数据中的开始位置。label_list 和 text_list 是在遍历批量数据时动态构建的,所以它们的初始值不需要设置为 0。相反,offsets 列表需要一个初始值 0,因为它是用来跟踪每个样本的文本在批量数据中的开始位置,而第一个样本的文本自然是从索引 0 开始的。随着循环的进行,每个新样本的文本长度会被添加到 offsets 列表中,形成一个偏移量列表,以便在处理每个样本的文本时正确地对齐
for (_label, _text) in batch:
: 遍历batch
中的每个样本,其中每个样本是一个包含标签和文本数据的元组。label_list.append(label_pipeline(_label))
: 将每个样本的标签通过label_pipeline
函数处理后,添加到label_list
中。processed_text = torch.tensor(text_pipeline(_text), dtype=torch.int64)
:
将每个样本的文本通过text_pipeline
函数处理后,转换为PyTorch张量,并指定数据类型为整数(torch.int64
)。text_list.append(processed_text)
: 将处理后的文本添加到text_list
中。offsets.append(processed_text.size(0))
: 将每个样本的文本长度添加到offsets
列表中,这将是偏移量列表的一部分。label_list = torch.tensor(label_list, dtype=torch.int64)
: 将label_list
转换为PyTorch张量,并指定数据类型为整数(torch.int64
)。text_list = torch.cat(text_list)
: 将text_list
中的所有文本张量合并成一个张量。offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)
: 将offsets
列表转换为PyTorch张量,并计算累积和,用于指示每个样本的文本在批量数据中的开始位置。return label_list.to(device), text_list.to(device), offsets.to(device)
: 返回三个张量,分别是标签、文本和偏移量,并将它们发送到指定的设备(如GPU)。dataloader = DataLoader(train_iter, batch_size=8, shuffle=False, collate_fn=collate_batch)
:
创建一个DataLoader
实例,用于加载训练数据。train_iter
是一个包含训练数据的迭代器,batch_size=8
表示每个批次包含8个样本,shuffle=False
表示批次中的样本顺序不会被打乱,collate_fn=collate_batch
表示使用collate_batch
函数来组合批次数据。
5.定义模型
from torch import nn
class TextClassificationModel(nn.Module):
def __init__(self, vocab_size, embed_dim, num_class):
super(TextClassificationModel, self).__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.5
self.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)
这段代码定义了一个名为
TextClassificationModel
的神经网络模型类,用于文本分类任务。
from torch import nn
: 从PyTorch库中导入nn
模块,它提供了构建神经网络所需的多种工具和类。class TextClassificationModel(nn.Module):
: 定义一个名为TextClassificationModel
的类,它继承自nn.Module
类。nn.Module
是所有PyTorch神经网络模型的基类,它提供了定义模型结构、前向传播和反向传播等功能的基础。def __init__(self, vocab_size, embed_dim, num_class):
: 定义类的构造函数__init__
,它接受三个参数:vocab_size
(词汇表大小)、embed_dim
(嵌入维度)和
num_class
(类别数量)。这些参数用于初始化模型。super(TextClassificationModel, self).__init__()
: 调用父类的构造函数,确保模型被正确初始化。self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse=False)
: 初始化一个嵌入层(self.embedding
),使用nn.EmbeddingBag
类。这个类用于计算一个“bag”中所有嵌入向量的平均值,这个“bag”是指一系列索引的集合。在这个上下文中,vocab_size
是词汇表的大小,embed_dim
是嵌入的维度。self.fc = nn.Linear(embed_dim, num_class)
: 初始化一个全连接层(self.fc
),使用nn.Linear
类。这个层将嵌入向量(embed_dim
维度)转换为类别预测(num_class
维度)。self.init_weights()
: 定义一个名为init_weights
的方法,用于初始化模型的权重。def init_weights(self):
: 定义init_weights
方法。initrange = 0.5
: 定义一个名为initrange
的变量,用于初始化权重。self.embedding.weight.data.uniform_(-initrange, initrange)
: 使用uniform_
方法对嵌入层的权重进行初始化,确保权重在[-initrange, initrange]
范围内。self.fc.weight.data.uniform_(-initrange, initrange)
: 使用uniform_
方法对全连接层的权重进行初始化,确保权重在[-initrange, initrange]
范围内。self.fc.bias.data.zero_()
: 使用zero_
方法对全连接层的偏置进行初始化,将偏置设置为0。def forward(self, text, offsets):
: 定义forward
方法,这是PyTorch模型中必须定义的一个方法,用于定义模型的前向传播过程。embedded = self.embedding(text, offsets)
: 使用self.embedding
层对输入的文本数据进行处理,生成嵌入向量。text
是包含文本数据的张量,offsets
是用于计算每个“bag”中嵌入向量平均值的偏移量。return self.fc(embedded)
: 将嵌入向量通过self.fc
层,得到最终的类别预测。
6.定义实例
num_class = len(set([label for (label, text) in train_iter]))
vocab_size = len(vocab)
embedding_size = 64
model = TextClassificationModel(vocab_size, embedding_size, num_class).to(device)
这段代码定义了一个神经网络模型,用于文本分类任务,并将其转移到指定的设备上。
num_class = len(set([label for (label, text) in train_iter]))
:
train_iter
是一个包含训练数据的迭代器。- 这句话的意思是从
train_iter
中提取所有的标签(label),并将它们转换为一个列表。- 使用
set
函数将这个列表转换为无重复元素的集合。- 最后,使用
len
函数计算集合中唯一标签的数量,这个数量代表了模型的类别数量(num_class
)。vocab_size = len(vocab)
:
vocab
是通过build_vocab_from_iterator
函数构建的词汇表。- 这行代码计算词汇表中单词的数量,这个数量代表了模型的词汇量(
vocab_size
)。embedding_size = 64
:
- 这行代码定义了嵌入层的维度,即每个单词或标记的嵌入向量的大小。在这个例子中,嵌入向量的大小被设置为64。
model = TextClassificationModel(vocab_size, embedding_size, num_class).to(device)
:
- 这行代码创建了一个
TextClassificationModel
实例。vocab_size
是模型的词汇量,embedding_size
是嵌入层的维度,num_class
是模型的类别数量。- 最后,使用
.to(device)
方法将模型转移到指定的设备上。这个设备可以是CPU或GPU,取决于你的计算需求和可用资源。 总结来说,这段代码定义了一个文本分类模型,并将其转移到适当的设备上,以便于后续的训练和评估。
7.定义训练参数与评估函数
import time
import torch.nn as nn
# Assuming model is your neural network model
criterion = nn.CrossEntropyLoss()
def train(dataloader):
model.train() #切换为训练模式
total_acc, train_loss, total_count = 0,0,0
log_interval = 500
start_time = time.time()
for idx, (label, text, offsets) in enumerate(dataloader):
predicted_label = model(text, offsets)
optimizer.zero_grad()
loss = criterion(predicted_label, label) #计算网络输出和真实值之间的差距,label为真实值
loss.backward()
optimizer.step() #每一步自动更新
#记录acc与loss
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_time
print('| 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,0
start_time = time.time()
def evaluate(dataloader):
model.eval() # 切换为测试模式
total_acc, train_loss, = 0, 0
total_count = 0
with torch.no_grad():
for idx, (label, text, offsets) in enumerate(dataloader):
predicted_label = model(text, offsets)
loss = criterion(predicted_label, label) # 计算loss值
# 记录测试数据
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
这段代码定义了两个函数:
train
和evaluate
,用于训练和评估一个神经网络模型。
import time
: 导入Python的标准库time
,用于计时和性能评估。import torch.nn as nn
: 导入PyTorch的nn
模块,它提供了构建神经网络所需的多种工具和类。# Assuming model is your neural network model
: 这是一个注释,假设model
是一个已经定义好的神经网络模型。criterion = nn.CrossEntropyLoss()
: 创建一个交叉熵损失函数实例,这是分类问题中常用的损失函数。def train(dataloader):
: 定义一个名为train
的函数,它接受一个参数dataloader
,这是一个包含训练数据的DataLoader实例。model.train()
: 将模型设置为训练模式,这意味着在训练过程中会启用例如dropout和batch normalization等层。total_acc, train_loss, total_count = 0,0,0
: 初始化三个变量,用于累积训练过程中的准确率、损失和总样本数。log_interval = 500
: 设置日志打印的间隔,每500个批次打印一次训练状态。start_time = time.time()
: 记录训练开始的时间。for idx, (label, text, offsets) in enumerate(dataloader):
: 遍历dataloader
中的每个批次,idx
是批次的索引,(label, text, offsets)
是每个批次的标签、文本数据和偏移量。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()
: 累积准确预测的样本数。这里使用了PyTorch的自动求导功能来计算准确率。train_loss += loss.item()
: 累积损失。total_count += label.size(0)
: 累积处理的样本总数。if idx % log_interval == 0 and idx > 0:
: 如果当前批次的索引是日志间隔的倍数,并且不是第一个批次,则打印训练状态。elapsed = time.time() - start_time
: 计算自训练开始以来的经过时间。print('| 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,0
: 重置累积变量,为下一个日志间隔做准备。start_time = time.time()
: 重置开始时间,为下一个日志间隔做准备。def evaluate(dataloader):
: 定义一个名为evaluate
的函数,它接受一个参数dataloader
,这是一个包含评估数据的DataLoader实例。model.eval()
: 将模型设置为评估模式,这意味着在评估过程中不会更新模型的参数,例如不会执行dropout和batch normalization等层的操作。total_acc, train_loss, = 0, 0
: 初始化两个变量,用于累积评估过程中的准确率和损失率total_acc, train_loss, = 0, 0
: 初始化两个变量,用于累积评估过程中的准确率和损失。total_count = 0
: 初始化一个变量,用于累积处理的样本总数。with torch.no_grad():
: 使用PyTorch的上下文管理器,确保在评估过程中不计算梯度,这有助于节省内存和计算资源。for idx, (label, text, offsets) in enumerate(dataloader):
: 遍历dataloader
中的每个批次,idx
是批次的索引,(label, text, offsets)
是每个批次的标签、文本数据和偏移量。predicted_label = model(text, offsets)
: 使用模型对输入数据进行前向传播,得到预测的标签。loss = criterion(predicted_label, label)
: 计算预测标签和真实标签之间的交叉熵损失。total_acc += (predicted_label.argmax(1) == label).sum().item()
: 累积准确预测的样本数。这里使用了PyTorch的自动求导功能来计算准确率。train_loss += loss.item()
: 累积损失。total_count += label.size(0)
: 累积处理的样本总数。在 PyTorch 中,
Tensor
对象的.size()
方法返回一个torch.Size
对象,它是一个包含张量所有维度的元组。对于二维张量(如图像),size(0)
会返回第一维的大小,size(1)
会返回第二维的大小。对于多维张量,size(0)
通常返回第一维的大小,以此类推。
当你调用label.size(0)
时,label
是一个张量,它的.size()
方法返回一个torch.Size
对象,其中包含张量的所有维度。size(0)
方法从这个torch.Size
对象中提取第一个元素,并返回一个整数,这个整数是张量在该维度的大小。
例如,假设label
是一个二维张量,其维度为(5, 10)
,那么label.size()
会返回torch.Size([5, 10])
,而label.size(0)
会返回5
,label.size(1)
会返回10
。
因此,label.size(0)
返回的是张量在该维度的大小,它是一个整数,而不是一个torch.Size
对象。所以,你可以安全地将它与total_count
相加,因为它们都是整数类型。
return total_acc / total_count, train_loss / total_count
: 返回平均准确率和平均损失,这些值是在评估过程中累积得到的。 总结来说,这段代码定义了两个函数:train
和evaluate
,用于训练和评估一个神经网络模型。train
函数用于训练模型,而evaluate
函数用于评估模型在验证或测试数据上的性能。这两个函数都使用了一个数据加载器(dataloader
)来提供批量的训练或评估数据。
8.训练模型
from torch.utils.data.dataset import random_split
from torchtext.data.functional import to_map_style_dataset
# 超参数
EPOCHS = 10 # epoch
LR = 5 # 学习率
BATCH_SIZE = 64 # batch size for training
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=LR)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1.0, gamma=0.1)
total_acc = None
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_dataloader)
val_acc, val_loss = evaluate(valid_dataloader)
if total_acc is not None and total_acc > val_acc:
scheduler.step()
else:
total_accu = val_acc
print('| epoch {:d} | 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)
这段代码是用于训练和评估一个神经网络模型,包括数据集的分割、加载器和训练过程。
from torch.utils.data.dataset import random_split
: 从torch.utils.data.dataset
模块中导入random_split
函数,用于将数据集随机分割成多个子集。from torchtext.data.functional import to_map_style_dataset
: 从torchtext.data.functional
模块中导入to_map_style_dataset
函数,用于将数据转换为map风格的dataset,这种格式在PyTorch中更高效。EPOCHS = 10 # epoch
: 定义训练过程中的 epoch 数量,一个 epoch 表示整个数据集被完整地遍历一次。LR = 5 # 学习率
: 定义学习率,用于控制优化器更新模型参数的速度。BATCH_SIZE = 64 # batch size for training
: 定义训练时的批次大小,即每次从数据集中取出的样本数量。criterion = torch.nn.CrossEntropyLoss()
: 定义一个交叉熵损失函数实例,用于计算模型预测和真实标签之间的差异。optimizer = torch.optim.SGD(model.parameters(), lr=LR)
: 定义一个SGD优化器实例,用于更新模型的参数。model.parameters()
返回模型所有参数的列表。scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1.0, gamma=0.1)
: 定义一个学习率调度器实例,用于在训练过程中动态调整学习率。StepLR
表示每过一定数量的
epoch,学习率就会按照一定的比例减少。total_acc = None
: 定义一个变量total_acc
,用于存储当前最优的验证集准确率。train_dataset = to_map_style_dataset(train_iter)
: 将训练数据转换为map风格的dataset。test_dataset = to_map_style_dataset(test_iter)
: 将测试数据转换为map风格的dataset。num_train = int(len(train_dataset) * 0.95)
: 计算训练集的95%作为训练数据,其余的作为验证数据。split_train_, split_valid_ = random_split(train_dataset, [num_train, len(train_dataset) - num_train])
: 使用random_split
函数将训练数据集随机分割成训练和验证两部分。train_dataloader = DataLoader(split_train_, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_batch)
:
创建一个训练数据加载器,它负责将数据分成批次,并对批次进行打乱(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 数量。epoch_start_time = time.time()
: 记录每个 epoch 开始的时间。train(train_dataloader)
: 调用train
函数,使用train_dataloader
进行训练。这行代码假设train
函数已经被定义并且能够接收train_dataloader
作为输入。
抱歉,之前的回答被截断了。让我们继续从第20行开始解释:val_acc, val_loss = evaluate(valid_dataloader)
: 调用evaluate
函数,使用valid_dataloader
评估模型在验证集上的性能,并返回准确率和损失。这行代码假设evaluate
函数已经被定义并且能够接收valid_dataloader
作为输入。if total_acc is not None and total_acc > val_acc:
: 如果total_acc
已经被定义并且当前val_acc
小于total_acc
,则继续执行下一行代码。total_acc
是一个全局变量,用于存储当前最好的验证集准确率。scheduler.step()
: 更新学习率调度器,如果val_acc
更好,则可能需要调整学习率。这行代码假设scheduler
已经被定义并且具有step
方法。else:
: 如果val_acc
更好,则执行以下代码块。total_accu = val_acc
: 更新total_accu
为当前val_acc
,因为它是当前最好的验证集准确率。print('| epoch {:d} | time:{:4.2f}s | '
: 开始打印当前epoch
的状态。'valid_acc {:4.3f} valid_loss {:4.3f}'.format(epoch, time.time() - epoch_start_time, val_acc, val_loss)
: 打印当前epoch
的状态,包括时间、验证集准确率和验证集损失。print('-' * 69)
: 打印一个长横线,用于在输出中分隔不同的epoch
状态。 这段代码的逻辑是,在每个epoch
开始时,它首先进行训练,然后评估模型在验证集上的性能。如果当前epoch
的验证集准确率比之前存储的最优准确率更好,它会更新学习率调度器以调整学习率。否则,它会将当前epoch
的验证集准确率存储为最优准确率。最后,它打印当前epoch
的状态,包括时间、验证集准确率和验证集损失。
我的结果
9.验证测试
print('Checking the results of test dataset.')
test_acc, test_loss = evaluate(test_dataloader)
print('test accuracy {:.8f}'.format(test_acc))
结果
三、过程中的问题
这次需要新的包,不过解决方案和N1周的文章是一样的
假如出现这种未定义的报错,就要去检查这个报错来源于哪个函数还是参数,涉及哪些库,是不是没导入成功等等
我就倒回去做了个检查,发现不是加载数据集的问题
有时候出现NONE的报错就是有个参数成为none了,将涉及的代码里面的参数换成none看是不是报错一样的就知道是哪个出了问题
假如出现这个报错,解决方案如下
Python中对错误NameError: name ‘xxx’ is not defined进行总结这篇文章总结的很好,推荐遇到这个问题的人去看。