看着父亲苍老的白发和渐渐老态的面容
希望时间再慢一些
—— 24.12.19
一、定义模型
1.初始化模型
① 初始化父类
super(TorchModel, self).__init__(): 调用父类 nn.Module 的初始化方法,确保模型能够正确初始化。
② 创建嵌入层
self.embedding = nn.Embedding(len(vocab), vector_dim): 创建一个嵌入层,将词汇表中的每个词映射到一个 vector_dim 维度的向量。
nn.Embedding():PyTorch 中用于创建词嵌入(Word Embedding)层的类。词嵌入是自然语言处理(NLP)中一项重要技术,它可以将离散的词表示为连续的向量,使得这些向量能够捕捉到词与词之间的语义和句法关系。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
num_embeddings | int | 词表的大小,即词嵌入层中不同词的数量。这通常是语料库中唯一词的总数加 1(考虑到可能的填充标记)。 | 是 | 无 |
embedding_dim | int | 每个词向量的维度,也就是每个词将被表示为多长的向量。较高的维度可以表示更丰富的语义信息,但也会增加计算量和内存消耗。 | 是 | 无 |
padding_idx | int | 可选参数,指定用于填充的词的索引。在处理不同长度的序列时,通常会使用填充标记(如 <PAD> )将序列填充到相同的长度。设置 padding_idx 后,该索引对应的词向量在训练过程中不会被更新,始终保持为零向量。 |
否 | 无 |
max_norm | float | 可选参数,用于限制词向量的最大范数。如果词向量的范数超过 max_norm ,则会对其进行归一化处理。 |
否 | 无 |
norm_type | float | 当 max_norm 被设置时,norm_type 指定用于计算范数的类型。例如,norm_type=2 表示计算 L2 范数。 |
否 | 2.0 |
scale_grad_by_freq | bool | 可选参数,如果设置为 True ,则会根据词在训练数据中出现的频率对梯度进行缩放。出现频率高的词的梯度会被缩小,出现频率低的词的梯度会被放大。 |
否 | False |
sparse | bool | 可选参数,如果设置为 True ,则嵌入层的梯度矩阵将是稀疏矩阵。在某些情况下,使用稀疏梯度可以节省内存和计算资源。 |
否 | False |
③ 创建RNN层
self.rnn = nn.RNN(vector_dim, vector_dim, batch_first=True): 创建一个 RNN 层,输入和输出的特征维度均为 vector_dim,并且输入数据的第一维是批量大小。
nn.RNN():PyTorch 中用于构建循环神经网络(Recurrent Neural Network,RNN)的类。循环神经网络专门用于处理序列数据,在自然语言处理、时间序列分析等领域有广泛应用。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
input_size | int | 输入特征的维度。例如,在处理词嵌入时,input_size 就是词向量的维度。 |
是 | 无 |
hidden_size | int | 隐藏状态的维度。隐藏状态是 RNN 在每个时间步传递的信息,hidden_size 决定了隐藏状态的表达能力。 |
是 | 无 |
num_layers | int | RNN 的层数。多层 RNN 可以学习更复杂的序列模式,但也会增加计算量和训练难度。 | 否 | 1 |
nonlinearity | str | 非线性激活函数的类型,可以是 'tanh' 或 'relu' 。默认使用 'tanh' 。 |
否 | 'tanh' |
bias | bool | 是否使用偏置项。如果为 True ,则在计算隐藏状态和输出时会添加偏置。 |
否 | True |
batch_first | bool | 如果为 True ,则输入和输出的张量形状为 (batch_size, seq_len, input_size) ;否则为 (seq_len, batch_size, input_size) 。默认值为 False 。 |
否 | False |
dropout | float | 在除最后一层外的每一层的输出上应用的 Dropout 概率。取值范围为 [0, 1] ,0 表示不使用 Dropout。 |
否 | 0 |
bidirectional | bool | 是否使用双向 RNN。如果为 True ,则 RNN 会同时处理序列的正向和反向信息,输出的维度会翻倍。 |
否 | False |
④ 创建线性分类层
self.classify = nn.Linear(vector_dim, sentence_length + 1): 创建一个线性层,将 RNN 输出的特征向量映射到 sentence_length + 1 个分类标签。+1 是因为可能有某个词不存在的情况,此时的真实标签被设为 sentence_length。
nn.Linear():PyTorch 中 torch.nn
模块里用于构建全连接层(也称为线性层)的类。全连接层是神经网络中最基础且常用的组件之一,在图像识别、自然语言处理等众多深度学习任务中广泛应用。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
in_features | int | 输入特征的数量,也就是输入向量的维度。例如,在处理图像数据时,如果输入的是经过卷积层处理后的特征图,将其展平为一维向量后,该向量的长度就是 in_features 。 |
是 | 无 |
out_features | int | 输出特征的数量,即输出向量的维度。这决定了该全连接层输出的特征数量,通常会根据后续任务或网络结构进行设置。 | 是 | 无 |
bias | bool | 是否使用偏置项。如果设置为 True ,则会在输出中加上一个可学习的偏置向量;如果设置为 False ,则不使用偏置项。 |
否 | True |
⑤ 定义损失函数
self.loss = nn.functional.cross_entropy: 定义交叉熵损失函数,用于计算模型预测值与真实标签之间的差异。
nn.functional.cross_entropy: PyTorch 中 torch.nn.functional
模块里用于计算交叉熵损失(Cross - Entropy Loss)的函数。交叉熵损失在分类问题中是一种非常常用的损失函数
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
input | Tensor | 模型的原始输出,形状通常为 (N, C) ,其中 N 是批量大小,C 是类别数量。模型的输出不需要经过 softmax 激活函数处理,因为该函数会自动进行 softmax 操作。 |
是 | 无 |
target | Tensor | 真实标签,形状为 (N,) ,其中每个元素是对应样本的类别索引,索引范围是 [0, C - 1] 。 |
是 | 无 |
weight | Tensor | 可选参数,形状为 (C,) ,用于为每个类别设置不同的权重。当数据集中不同类别的样本数量不均衡时,可以使用这个参数来调整每个类别的损失贡献。 |
否 | 无 |
size_average | bool | 已弃用,在旧版本中用于指定是否对损失求平均。现在使用 reduction 参数来控制。 |
否 | 无 |
ignore_index | int | 可选参数,指定一个要忽略的类别索引,在计算损失时会忽略该索引对应的样本。常用于处理填充标签的情况。 | 否 | -100 |
reduce | bool | 已弃用,现在使用 reduction 参数来控制损失的聚合方式。 |
否 | 无 |
reduction | str | 指定损失的聚合方式,可选值为 'none' 、'mean' 或 'sum' 。'none' 表示不进行聚合,返回每个样本的损失;'mean' 表示对所有样本的损失求平均;'sum' 表示对所有样本的损失求和。 |
否 | 'mean' |
class TorchModel(nn.Module):
def __init__(self, vector_dim, sentence_length, vocab):
super(TorchModel, self).__init__()
self.embedding = nn.Embedding(len(vocab), vector_dim) #embedding层
# self.pool = nn.AvgPool1d(sentence_length) #池化层
#可以自行尝试切换使用rnn
self.rnn = nn.RNN(vector_dim, vector_dim, batch_first=True)
# +1的原因是可能出现a不存在的情况,那时的真实label在构造数据时设为了sentence_length
self.classify = nn.Linear(vector_dim, sentence_length + 1)
self.loss = nn.functional.cross_entropy
2、前向传播定义
① 输入嵌入
x = self.embedding(x):将输入 x 通过嵌入层转换为向量表示
② RNN处理
rnn_out, hidden = self.rnn(x):将嵌入后的向量输入到RNN层,得到每个时间步的输出 rnn_out 和最后一个时间步的隐藏状态 hidden。
③ 提取特征
x = rnn_out[:, -1, :]:从RNN的输出中提取最后一个时间步(最后一维)的特征向量。
":"
:在第一个维度(batch_size
)上使用:
表示选取该维度的所有元素,即选取所有的批次。"-1"
:在第二个维度(seq_len
)上使用-1
表示选取该维度的最后一个元素,也就是每个批次中最后一个时间步的输出。":"
:在第三个维度(hidden_size
)上使用:
表示选取该维度的所有元素,即选取最后一个时间步输出的所有隐藏状态维度。
④ 分类
y_pred = self.classify(x):将提取的特征向量通过线性层进行分类,得到预测值 y_pred。
⑤ 损失计算
如果提供了真实标签 y,则计算并返回损失值;否则,返回预测值。
#当输入真实标签,返回loss值;无真实标签,返回预测值
def forward(self, x, y=None):
x = self.embedding(x)
#使用pooling的情况,先使用pooling池化层会丢失模型语句的时序信息
# x = x.transpose(1, 2)
# x = self.pool(x)
# x = x.squeeze()
#使用rnn的情况
# rnn_out:每个字对应的向量 hidden:最后一个输出的隐含层对应的向量
rnn_out, hidden = self.rnn(x)
# 中间维度改变,变成(batch_size数据样本数量, sentence_length文本长度, vector_dim向量维度)
x = rnn_out[:, -1, :] #或者写hidden.squeeze()也是可以的,因为rnn的hidden就是最后一个位置的输出
#接线性层做分类
y_pred = self.classify(x)
if y is not None:
return self.loss(y_pred, y) #预测值和真实值计算损失
else:
return y_pred
二、数据
1.建立词表
① 定义字符集
定义一个字符集 chars,包含字母 'a' 到 'k'。
② 定义字典
初始化一个字典 vocab,其中键为 'pad',值为 0。
③ 遍历字符集
使用 enumerate 遍历字符集 chars,为每个字符分配一个唯一的序号,从 1 开始。
enumrate():用于将一个可迭代对象(如列表、元组、字符串、字典等)组合为一个索引序列,同时列出数据和数据的索引,一般用在 for
循环当中,这样在遍历可迭代对象时,既可以获取元素的值,也可以获取元素对应的索引。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
iterable | 可迭代对象 | 需要进行枚举的可迭代对象,如列表、元组、字符串等。 | 是 | 无 |
start | 整数 | 可选参数,指定索引起始值。默认从 0 开始。 | 否 | 0 |
④ 定义unk键
添加一个特殊的键 'unk',其值为当前字典的长度(即 26)。
⑤ 返回词汇表
将生成的词汇表返回
#字符集随便挑了一些字,实际上还可以扩充
#为每个字生成一个标号
#{"a":1, "b":2, "c":3...}
#abc -> [1,2,3]
def build_vocab():
chars = "abcdefghijk" #字符集
vocab = {"pad":0}
for index, char in enumerate(chars):
vocab[char] = index+1 #每个字对应一个序号
vocab['unk'] = len(vocab) #26
return vocab
2.随机生成样本
① 采样
random.sample(list(vocab.keys()), sentence_length):从词汇表 vocab 的键中随机选择 sentence_length 个不同的字符,生成列表 x
random.sample(): Python 标准库 random
模块中的一个函数,它用于从指定的序列(如列表、元组、字符串等)中随机且不重复地抽取指定数量的元素,返回一个包含这些抽取元素的新列表。以下为你详细介绍该函数的相关信息。
参数 | 类型 | 描述 | 是否必需 |
---|---|---|---|
population | 可迭代对象 | 表示要从中抽取元素的总体序列,可以是列表、元组、字符串等可迭代对象。 | 是 |
k | 整数 | 指定要抽取的元素数量,该值必须是非负整数,且不能大于 population 序列的长度。 |
是 |
② 标签生成
index('a'):检查列表 x 中是否包含字符 "a",如果包含,记录 "a" 在列表中的索引位置为 y,否则,设置 y 为 sentence_length。
index():元组的 index()
方法与列表的 index()
方法类似,用于在元组中查找指定元素第一次出现的位置,并返回该位置的索引。如果未找到指定元素,则会抛出 ValueError
异常。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
x | 任意类型 | 要查找的元素。 | 是 | 无 |
start | 整数 | 可选参数,指定开始查找的索引位置。默认从元组的起始位置(索引为 0)开始查找。 | 否 | 0 |
end | 整数 | 可选参数,指定结束查找的索引位置(不包含该位置)。默认查找到元组的末尾。 | 否 | 元组的长度 |
③ 转换
将列表 x 中的每个字符转换为其在词汇表中的序号,如果字符不在词汇表中,则使用 unk 的序号
vocab.get():主要作用是根据指定的键从字典中获取对应的值。与直接使用方括号 []
访问字典元素不同的是,当指定的键不存在于字典中时,get()
方法不会抛出 KeyError
异常,而是可以返回一个默认值(如果指定了的话),这样可以增强代码的健壮性。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
key | 任意不可变类型 | 要在字典中查找的键,由于字典的键必须是不可变类型(如整数、字符串、元组等),所以 key 也必须是不可变类型。 |
是 | 无 |
default | 任意类型 | 可选参数,当指定的 key 不存在于字典中时,返回的默认值。如果不提供该参数,默认返回 None 。 |
否 | None |
④ 返回结果
返回转换后的列表 x 和标签 y
#随机生成一个样本
def build_sample(vocab, sentence_length):
#注意这里用sample,是不放回的采样,每个字母不会重复出现,但是要求字符串长度要小于词表长度
x = random.sample(list(vocab.keys()), sentence_length)
#指定哪些字出现时为正样本
if "a" in x:
y = x.index("a")
else:
y = sentence_length
x = [vocab.get(word, vocab['unk']) for word in x] #将字转换成序号,为了做embedding
return x, y
3.建立数据集
① 初始化数据集
创建两个空列表 dataset_x 和 dataset_y,用于存储生成的样本和对应的标签
② 生成样本
使用 for 循环,循环次数为 sample_length,即需要生成的样本数量。在每次循环中,调用 build_sample 函数生成一个样本 (x, y),其中 x 是输入数据,y 是标签
③ 存储样本
将生成的样本 x 添加到 dataset_x 列表中。将生成的标签 y 添加到 dataset_y 列表中
append(): append()
方法用于在列表的末尾添加一个新元素,从而改变原列表的内容,增加其长度。此方法会直接修改原列表,而不会返回一个新的列表。
参数 | 类型 | 描述 | 是否必需 |
---|---|---|---|
object | 任意类型 | 要添加到列表末尾的对象,可以是数字、字符串、列表、元组等任意 Python 对象。 | 是 |
④ 返回数据集
将 dataset_x 和 dataset_y 转换为 torch.LongTensor 类型,以便在 PyTorch 中使用。返回转换后的数据集。
torch.LongTensor():在深度学习任务里,经常需要处理整数类型的数据,比如类别标签、索引等,torch.LongTensor()
就是专门用来创建存储这类整数数据的张量的。它可以将 Python 的列表、元组、NumPy 数组等可迭代对象转换为 PyTorch 的张量,并且指定张量的数据类型为 64 位整数。
#建立数据集
#输入需要的样本数量。需要多少生成多少
def build_dataset(sample_length, vocab, sentence_length):
dataset_x = []
dataset_y = []
for i in range(sample_length):
x, y = build_sample(vocab, sentence_length)
dataset_x.append(x)
dataset_y.append(y)
return torch.LongTensor(dataset_x), torch.LongTensor(dataset_y)
三、模型测试、训练、评估
1.建立模型
① 参数:
vocab:词汇表,通常是一个包含所有字符或单词的列表或字典
char_dim:字符的维度,即每个字符在嵌入层中的向量长度
sentence_length:句子的最大长度
② 过程:
使用传入的参数 char_dim、sentence_length 和 vocab 实例化一个 TorchModel 对象并返回
#建立模型
def build_model(vocab, char_dim, sentence_length):
model = TorchModel(char_dim, sentence_length, vocab)
return model
2.测试模型
① 设置模型为评估模式
将模型设置为评估模式,禁用 dropout 等训练时的行为
model.eval():神经网络模型有训练(train)和评估(eval)两种模式。model.eval() 方法的主要作用是将模型切换到评估模式。在评估模式下,模型中的一些特定层(如 Dropout、BatchNorm 等)会调整其行为,以适应评估阶段的需求,确保模型在评估时的表现符合预期。
② 生成测试数据集
调用 build_dataset 函数生成 200 个用于测试的样本
③ 打印样本数量
输出当前测试集中样本的数量
④ 模型预测
使用 torch.no_grad() 禁用梯度计算,提高推理速度并减少内存消耗,然后对生成的测试数据进行预测
torch.no_grad():当我们进行前向传播和反向传播时,默认情况下会跟踪张量的操作历史,以便后续计算梯度。然而,在某些情况下,我们并不需要计算梯度,例如在模型评估阶段(验证、测试)或者进行推理时。torch.no_grad()
上下文管理器的作用就是在其包裹的代码块中,暂停对张量操作的梯度跟踪,从而减少内存消耗并提高计算效率。
⑤ 计算准确率
遍历预测结果和真实标签,统计正确和错误的预测数量,并计算准确率
zip():Python 的内置函数,它用于将多个可迭代对象(如列表、元组、字符串等)中对应位置的元素打包成一个个元组,然后返回一个可迭代的 zip
对象。这个 zip
对象可以通过循环进行遍历,也可以转换为列表、元组等其他数据结构。如果传入的可迭代对象长度不一致,zip()
会以最短的可迭代对象长度为准进行打包。
参数 | 类型 | 描述 | 是否必需 |
---|---|---|---|
*iterables |
可迭代对象 | 可以传入一个或多个可迭代对象,用逗号分隔。 | 是 |
torch.argmax(): PyTorch 中的一个函数,用于返回输入张量中指定维度上最大值的索引。在深度学习中,这个函数常用于分类任务中,通过找到输出张量中最大值的索引来确定预测的类别。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
input |
torch.Tensor |
输入的张量,需要从中找出最大值的索引。 | 是 | 无 |
dim |
整数 | 可选参数,指定在哪个维度上查找最大值的索引。如果不指定 dim ,则会将输入张量展平为一维,然后返回整个张量中最大值的索引。 |
否 | None |
keepdim |
布尔值 | 可选参数,如果设置为 True ,输出的张量会保持与输入张量相同的维度,只是指定的 dim 维度大小为 1;如果设置为 False ,输出的张量会减少一个维度。 |
否 | False |
⑥ 输出结果
打印正确预测的数量和准确率,并返回准确率
#测试代码
#用来测试每轮模型的准确率
def evaluate(model, vocab, sample_length):
model.eval()
x, y = build_dataset(200, vocab, sample_length) #建立200个用于测试的样本
print("本次预测集中共有%d个样本"%(len(y)))
correct, wrong = 0, 0
with torch.no_grad():
y_pred = model(x) #模型预测
for y_p, y_t in zip(y_pred, y): #与真实标签进行对比
if int(torch.argmax(y_p)) == int(y_t):
correct += 1
else:
wrong += 1
print("正确预测个数:%d, 正确率:%f"%(correct, correct/(correct+wrong)))
return correct/(correct+wrong)
3.模型训练
① 配置参数
设置训练轮数epoch_num、批量大小batch_size、训练样本数train_sample、字符维度char_dim、句子长度sentence_length和学习率learning_rate
② 建立字表
调用 build_vocab 函数生成字符到索引的映射。
③ 建立模型
调用 build_model 函数创建模型。
④ 选择优化器
torch.optim.Adam(model.parameters(), lr=learning_rate):使用 Adam 优化器
torch.optim.Adam(): PyTorch 中用于实现 Adam(Adaptive Moment Estimation)优化算法的类。Adam 算法结合了 AdaGrad 和 RMSProp 的优点,基于梯度的一阶矩估计和二阶矩估计来动态调整每个参数的学习率。,能够自适应地为不同参数调整学习率,在深度学习领域应用广泛,常用于训练神经网络模型。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
params |
可迭代对象 | 需要优化的参数迭代器,通常是模型的 parameters() 方法返回的结果,包含了模型中所有需要更新的可学习参数。 |
是 | 无 |
lr |
浮点数 | 学习率(Learning Rate),控制每次参数更新的步长。虽然 Adam 是自适应学习率算法,但学习率仍然是一个重要的超参数,需要根据具体任务进行调整。 | 否 | 0.001 |
betas |
元组(包含两个浮点数) | 用于计算梯度一阶矩估计(均值)和二阶矩估计(方差)的指数衰减率。betas 是一个包含两个浮点数的元组 (beta1, beta2) ,beta1 通常设置为 0.9,beta2 通常设置为 0.999。 |
否 | (0.9, 0.999) |
eps |
浮点数 | 为了数值稳定性而添加到分母中的一个小常数,用于避免除以零的情况。它可以防止在计算中出现数值不稳定的问题。 | 否 | 1e-8 |
weight_decay |
浮点数 | 权重衰减(Weight Decay),也称为 L2 正则化,用于防止模型过拟合。它会在损失函数中添加一个惩罚项,使得模型的参数不会变得过大。 | 否 | 0 |
amsgrad |
布尔值 | 是否使用 AMSGrad 变体。AMSGrad 是 Adam 算法的一种改进,它通过使用最大的二阶矩估计来保证收敛性,在某些情况下可以提高算法的稳定性。 | 否 | False |
⑤ 训练过程
Ⅰ声明模型进入训练模式,model.train():在 PyTorch 里,神经网络模型存在训练(train
)和评估(eval
)两种模式。model.train()
方法的核心功能是把模型切换到训练模式。处于训练模式时,模型中的某些特定层(像 Dropout
、BatchNorm
等)会按照训练时的规则来运行,从而保证模型在训练过程中能够正确学习数据的特征。
Ⅱ 梯度归零,optim.zero_grad():梯度归零,优化器(optim
通常是 torch.optim
模块中优化器的实例,如 Adam
、SGD
等)的一个方法,其作用是将模型中所有可训练参数的梯度清零。
Ⅲ 计算损失:调用模型 model
并传入输入 x
和目标 y
来计算损失。
Ⅳ 反向传播,loss.backward():PyTorch 中用于进行反向传播的方法。在得到损失值 loss
后,调用该方法会根据链式法则自动计算损失函数关于模型中所有可训练参数的梯度。这些梯度会存储在每个参数的 .grad
属性中,后续优化器可以根据这些梯度来更新模型的参数。
Ⅴ 更新权重,optim.step():优化器的一个方法,用于根据计算得到的梯度来更新模型的参数。在调用 loss.backward()
计算出梯度后,优化器会根据预设的优化算法(如随机梯度下降、Adam 等)和学习率等参数,对模型的可训练参数进行更新。
Ⅵ append():Python 中列表(list
)对象的一个方法,用于在列表的末尾添加一个新元素。该方法会直接修改原列表,而不会返回一个新的列表。
Ⅶ loss.item():loss.item()
方法用于将这个包含单个元素的张量转换为 Python 的标量值(如浮点数)。
⑥ 评估模型
每个 epoch 结束后,调用 evaluate 函数评估模型性能。
⑦ 记录日志
记录每个 epoch 的准确率和平均损失。
⑧ 绘制图表
绘制准确率和损失的变化曲线。
⑨ 保存模型和词表
保存模型参数和词表
def main():
#配置参数
epoch_num = 20 #训练轮数
batch_size = 40 #每次训练样本个数
train_sample = 1000 #每轮训练总共训练的样本总数
char_dim = 30 #每个字的维度
sentence_length = 10 #样本文本长度
learning_rate = 0.001 #学习率
# 建立字表
vocab = build_vocab()
# 建立模型
model = build_model(vocab, char_dim, sentence_length)
# 选择优化器
optim = torch.optim.Adam(model.parameters(), lr=learning_rate)
log = []
# 训练过程
for epoch in range(epoch_num):
model.train()
watch_loss = []
for batch in range(int(train_sample / batch_size)):
x, y = build_dataset(batch_size, vocab, sentence_length) #构造一组训练样本
optim.zero_grad() #梯度归零
loss = model(x, y) #计算loss
loss.backward() #计算梯度
optim.step() #更新权重
watch_loss.append(loss.item())
print("=========\n第%d轮平均loss:%f" % (epoch + 1, np.mean(watch_loss)))
acc = evaluate(model, vocab, sentence_length) #测试本轮模型结果
log.append([acc, np.mean(watch_loss)])
#画图
plt.plot(range(len(log)), [l[0] for l in log], label="acc") #画acc曲线
plt.plot(range(len(log)), [l[1] for l in log], label="loss") #画loss曲线
plt.legend()
plt.show()
#保存模型
torch.save(model.state_dict(), "model.pth")
# 保存词表
writer = open("vocab.json", "w", encoding="utf8")
writer.write(json.dumps(vocab, ensure_ascii=False, indent=2))
writer.close()
return
四、模型预测
1.训练并保存模型
if __name__ == "__main__":
main()
2.预测数据
用保存的训练好的模型进行预测
① 初始化参数
设置每个字的维度 char_dim 和样本文本长度 sentence_length
② 加载字符表
从指定路径加载字符表 vocab
③ 建立模型
调用 build_model 函数构建模型
④ 加载模型权重
从指定路径加载预训练的模型权重
model.load_state_dict(): PyTorch 里,模型的可学习参数(如权重和偏置)存储在 state_dict
中,state_dict
本质上是一个 Python 字典对象,它将每一层的参数名称映射到对应的参数张量。model.load_state_dict()
方法的主要功能是将预定义的 state_dict
中的参数加载到当前模型中,从而恢复模型的训练状态或使用预训练的参数。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
state_dict |
字典 | 包含模型参数的字典,键是参数的名称,值是对应的参数张量。通常通过 torch.save() 保存模型参数,再使用 torch.load() 加载得到该字典。 |
是 | 无 |
strict |
布尔值 | 指定是否严格匹配 state_dict 中的键和模型的参数名称。如果为 True ,则要求 state_dict 中的键和模型的参数名称完全一致,否则会抛出异常;如果为 False ,则允许部分匹配,只加载名称匹配的参数。 |
否 | True |
⑤ 序列化输入
将输入字符串转换为模型所需的输入格式
⑥ 模型预测
将输入数据传递给模型进行预测
model.eval(): 神经网络模型有训练(train)和评估(eval)两种模式。model.eval() 方法的主要作用是将模型切换到评估模式。在评估模式下,模型中的一些特定层(如 Dropout、BatchNorm 等)会调整其行为,以适应评估阶段的需求,确保模型在评估时的表现符合预期。
torch.no_grad():当我们进行前向传播和反向传播时,默认情况下会跟踪张量的操作历史,以便后续计算梯度。然而,在某些情况下,我们并不需要计算梯度,例如在模型评估阶段(验证、测试)或者进行推理时。torch.no_grad()
上下文管理器的作用就是在其包裹的代码块中,暂停对张量操作的梯度跟踪,从而减少内存消耗并提高计算效率。
⑦ 输出结果
打印每个输入字符串的预测类别和概率值
enumerate(): 用于将一个可迭代对象(如列表、元组、字符串、字典等)组合为一个索引序列,同时列出数据和数据的索引,一般用在 for
循环当中,这样在遍历可迭代对象时,既可以获取元素的值,也可以获取元素对应的索引。
参数 | 类型 | 描述 | 是否必需 | 默认值 |
---|---|---|---|---|
iterable | 可迭代对象 | 需要进行枚举的可迭代对象,如列表、元组、字符串等。 | 是 | 无 |
start | 整数 | 可选参数,指定索引起始值。默认从 0 开始。 | 否 | 0 |
#使用训练好的模型做预测
def predict(model_path, vocab_path, input_strings):
char_dim = 30 # 每个字的维度
sentence_length = 10 # 样本文本长度
vocab = json.load(open(vocab_path, "r", encoding="utf8")) #加载字符表
model = build_model(vocab, char_dim, sentence_length) #建立模型
model.load_state_dict(torch.load(model_path,weights_only=True)) #加载训练好的权重
x = []
for input_string in input_strings:
x.append([vocab[char] for char in input_string]) #将输入序列化
model.eval() #测试模式
with torch.no_grad(): #不计算梯度
result = model.forward(torch.LongTensor(x)) #模型预测
for i, input_string in enumerate(input_strings):
print("输入:%s, 预测类别:%s, 概率值:%s" % (input_string, torch.argmax(result[i]), result[i])) #打印结果
3.调用函数进行预测
if __name__ == "__main__":
# main()
test_strings = ["kijabcdefh", "gijkbcdeaf", "gkijadfbec", "kijhdefacb"]
predict("model.pth", "vocab.json", test_strings)