【Pytorch神经网络实战案例】31 TextCNN模型分析IMDB数据集评论的积极与消极

卷积神经网络不仅在图像视觉领域有很好的效果,而且在基于文本的NLP领域也有很好的效果。TextCN如模型是卷积神经网络用于文本处理方面的一个模型。

在TextCNN模型中,通过多分支卷积技术实现对文本的分类功能。

1 TextCNN

1.1 TextCNN模型结构

TexCNN模型是利用卷积神经网络对文本进行分类的模型,该模型的结构可以分为以下4个层次:

1.1.1 词嵌入层

将每个词对应的向量转化成多维度的词嵌入向量,将每个句子当作一幅图来进行处理(词的个数词×嵌入向量维度)。

1.1.2 多分支卷积层

使用3、4、5等不同大小的卷积核对词嵌入转化后的句子做卷积操作,生成大小不同的特征数据。

1.1.3 多分支全局最大池化层

对多分支卷积层中输出的每个分支的特征数据做全局最大池化操作。

1.1.4 全连接分类输出层

将池化后的结果输入全连接网络中,输出分类个数,得到最终结果。

1.2 TextCNN模型图解

因为卷积神经网络具有提取局部特征的功能,所以可用卷积神经网络提取句子中类似N-Gram算法的关键信息。

1.3 数据集IMDB

MDB数据集相当于图片处理领域的MNIST数据集,在NLP任务中经常被使用。

1.3.1 IMDB结构组成

IMDB数据集包含50000条评论,平均分成训练数据集(25000条评论)和测试数据集(25000条评论)。标签的总体分布是平衡的(25000条正面评论和25000条负面评论)。

另外,还包括额外的50000份无标签文件,用于无监督学习。

1.3.2 IMDB文件夹组成

IMDB数据集主要包括两个文件夹train与test,分别存放训练数据集与测试数据集。每个文件夹中都包含正样本和负样本,分别放在pos与neg子文件中。rain文件夹下还额外包含一个unsup子文件夹,用于非监督训练。

1.3.3 IMDB文件命名规则

每个样本文件的命名规则为“序号_评级”。其中“评级”可以分为0~9级。

 IMDB是torchtext库的内置数据集,可以直接通过运行torchtext库的接口进行获取。

2 代码实现:TextCNN模型分析IMDB数据集评论的积极与消极

2.1 案例描述

同一个记录评论语句的数据集,分为正面和负面两种情绪。通过训练,让模型能够理解正面与负面两种情绪的语义,并对评论文本进行分类。

2.1.1 案例理解分析

本例的任务可以理解为通过句子中的关键信息进行语义分类,这与TextCNN模型的功能是相匹配的。TextCNN模型中使用了池化操作,在这个过程中丢失了一些信息,所以导致该模型所表征的句子特征有限。如果要使用处理相近语义的分类任务,则还需要对其进一步进行调整。

2.2 代码实现:引入基础库: 固定PyTorch中的随机种子和GPU运算方式---TextCNN.py(第1部分)

# 1.1 引入基础库: 固定PyTorch中的随机种子和GPU运算方式。
import random #引入基础库
import time
import torch#引入PyTorch库
import torch.nn as nn
import torch.nn.functional as F
from torchtext.legacy import data ,datasets,vocab #引入文本处理库
import spacytorch.manual_seed(1234) # 固定随机种子,使其每次运行时对权重参数的初始化值一致。
# 固定GPU运算方式:提高GPU的运算效率,通常PyTorch会调用自动寻找最适合当前配置的高效算法进行计算,这一过程会导致每次运算的结果可能出现不一致的情况。
torch.backends.cudnn.deterministic = True # 表明不使用寻找高效算法的功能,使得每次的运算结果一致。[仅GPU有效]

2.3 代码实现:用torchtext加载IMDB并拆分为数据集---TextCNN.py(第2部分)

# 1.2 用torchtext加载IMDB并拆分为数据集
# IMDB是torchtext库的内置数据集,可以直接通过torchtext库中的datasets.MDB进行处理。
# 在处理之前将数据集的字段类型和分词方法指定正确即可。# 定义字段,并按照指定标记化函数进行分词
TEXT =  data.Field(tokenize = 'spacy',lower=True) # data.Field函数指定数据集中的文本字段用spaCy库进行分词处理,并将其统一改为小写字母。tokenize参数,不设置则默认使用str。
LABEL = data.LabelField(dtype=torch.float)# 加载数据集,并根据IMDB两个文件夹,返回两个数据集。
# datasets.MDB.splits()进行数据集的加载。该代码执行时会在本地目录的.data文件夹下查找是否有MDB数据集,如果没有,则下载;如果有,则将其加载到内存。
# 被载入内存的数据集会放到数据集对象train_data与test_data中。
train_data , test_data = datasets.IMDB.splits(text_field=TEXT,label_field=LABEL)
print("-----------输出一条数据-----------")
# print(vars(train_data.example[0]),len(train_data.example))
print(vars(train_data.examples[0]),len(train_data.examples))
print("---------------------------")# 将训练数据集再次拆分
# 从训练数据中拆分出一部分作为验证数据集。数据集对象train_data的split方法默认按照70%、30%的比例进行拆分。
train_data,valid_data = train_data.split(random_state = random.seed(1234))
print("训练数据集: ", len(train_data),"条")
print("验证数据集: ", len(valid_data),"条")
print("测试数据集: ", len(test_data),"条")

2.4 代码实现:加载预训练词向量并进行样本数据化---TextCNN.py(第3部分)

# 1.3 加载预训练词向量并进行样本数据化
# 将数据集中的样本数据转化为词向量,并将其按照指定的批次大小进行组合。
# buld_vocab方法实现文本到词向量数据的转化:从数据集对象train_data中取出前25000个高频词,并用指定的预训练词向量glove.6B.100d进行映射。
TEXT.build_vocab(train_data,max_size=25000,vectors="glove.6B.100d",unk_init = torch.Tensor.normal_) # 将样本数据转化为词向量
# glove.6B.100d为torchtext库中内置的英文词向量,主要将每个词映射成维度为100的浮点型数据,该文件会被下载到本地.vector_cache文件夹下。
LABEL.build_vocab(train_data)
#  ---start---创建批次数据:将数据集按照指定批次进行组合。
BATCH_SIZE = 64
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits((train_data, valid_data, test_data), batch_size = BATCH_SIZE, device = device)
#  ---end---创建批次数据:将数据集按照指定批次进行组合。

2.5 代码实现:定义带有Mish激活函数的TextCNN模型---TextCNN.py(第4部分)

class Mish(nn.Module):def __init__(self):super(Mish, self).__init__()def forward(self,x):x = x * (torch.tanh(F.softplus(x)))return x# 在TextCNN类中,一共有两个方法:
# ①初始化方法.按照指定个数定义多分支卷积层,并将它们统一放在nn.ModuleList数组中。
# ②前向传播方法:先将输入数据依次输入每个分支的卷积层中进行处理,再对处理结果进行最大池化,最后对池化结果进行连接并回归处理
class TextCNN(nn.Module): #定义TextCNN模型# TextCNN类继承了nn.Module类,在该类中定义的网络层列表必须要使用nn.ModuleList进行转化,才可以被TextCNN类识别。# 如果直接使用列表的话,在训练模型时无法通过TextCNN类对象的parameters方法获得权重。# 定义初始化方法def __init__(self, vocab_size, embedding_dim, n_filters, filter_sizes, output_dim,dropout, pad_idx):super().__init__()self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx = pad_idx) # 定义词向量权重# 定义多分支卷积层# 将定义好的多分支卷积层以列表形式存放,以便在前向传播方法中使用。# 每个分支中卷积核的第一个维度由参数filter_sizes设置,第二个维度都是embedding_dim,即只在纵轴的方向上实现了真正的卷积操作,在横轴的方向上是全尺度卷积,可以起到一维卷积的效果。self.convs = nn.ModuleList([nn.Conv2d(in_channels = 1,out_channels = n_filters,kernel_size = (fs, embedding_dim))for fs in filter_sizes])  #########注意不能用list# 定义输出层self.fc = nn.Linear(len(filter_sizes) * n_filters, output_dim)self.dropout = nn.Dropout(dropout)self.mish = Mish()  # 实例化激活函数对象# 定义前向传播方法def forward(self,text): # 输入形状为[sent len,batch size]text = text.permute(1, 0)  # 将形状变为[batch size, sent len]embedded = self.embedding(text)  # 对于输入数据进行词向量映射,形状为[batch size, sent len, emb dim]embedded = embedded.unsqueeze(1)  # 进行维度变化,形状为[batch size, 1, sent len, emb dim]# len(filter_sizes)个元素,每个元素形状为[batch size, n_filters, sent len - filter_sizes[n] + 1]# 多分支卷积处理conved = [self.mish(conv(embedded)).squeeze(3) for conv in self.convs] # 将输入数据进行多分支卷积处理。该代码执行后,会得到一个含有len(fiter_sizes)个元素的列表,其中每个元素形状为[batchsize,n_filters,sentlen-fltersizes[n]+1],该元素最后一个维度的公式是由卷积公式计算而来的。# 对于每个卷积结果进行最大池化操作pooled = [F.max_pool1d(conv, conv.shape[2]).squeeze(2) for conv in conved]# 将池化结果进行连接cat = self.dropout(torch.cat(pooled, dim=1))  # 形状为[batch size, n_filters * len(filter_sizes)]return self.fc(cat) # 输入全连接,进行回归输出

2.6 代码实现:用数据集参数实例化模型---TextCNN.py(第5部分)

# 1.5 用数据集参数实例化模型
if __name__ == '__main__':# 根据处理好的数据集参数对TextCNN模型进行实例化。INPUT_DIM = len(TEXT.vocab)  # 25002EMBEDDING_DIM = TEXT.vocab.vectors.size()[1]  # 100N_FILTERS = 100 # 定义每个分支的数据通道数量FILTER_SIZES = [3, 4, 5] # 定义多分支卷积中每个分支的卷积核尺寸OUTPUT_DIM = 1 # 定义输出维度DROPOUT = 0.5 # 定义Dropout丢弃率PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token] # 定义填充值:获取数据集中填充字符对应的索引。在词向量映射过程中对齐数据时会使用该索引进行填充。# 实例化模型model = TextCNN(INPUT_DIM, EMBEDDING_DIM, N_FILTERS, FILTER_SIZES, OUTPUT_DIM, DROPOUT, PAD_IDX)

2.7 代码实现:用预训练词向量初始化模型---TextCNN.py(第6部分)

# 1.6 用预训练词向量初始化模型:将加载好的TEXT字段词向量复制到模型中,为其初始化。# 复制词向量model.embedding.weight.data.copy_(TEXT.vocab.vectors)# 将填充的词向量清0UNK_IDX = TEXT.vocab.stoi[TEXT.unk_token]model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM) #对未识别词进行清零处理 :使该词在词向量空间中失去意义,目的是防止后面填充字符对原有的词向量空间进行干扰。model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM) #对填充词进行清零处理 :使该词在词向量空间中失去意义,目的是防止后面填充字符对原有的词向量空间进行干扰。

2.8 代码实现:使用Ranger优化器训练模型---TextCNN.py(第7部分)

# 1.7 使用Ranger优化器训练模型import torch.optim as optim # 引入优化器库from functools import partial # 引入偏函数库from ranger import * # 载入Ranger优化器# 为Ranger优化器设置参数opt_func = partial(Ranger, betas=(.9, 0.99), eps=1e-6)  # betas=(Momentum,alpha)optimizer = opt_func(model.parameters(), lr=0.004)# 定义损失函数criterion = nn.BCEWithLogitsLoss()  # nn.BCEWithLogitsLoss函数是带有Sigmoid函数的二分类交叉熵,即先对模型的输出结果进行Sigmoid计算,再对其余标签一起做Cross_entropy计算。# 分配运算资源model = model.to(device)criterion = criterion.to(device)# 定义函数,计算精确率def binary_accuracy(preds, y):  # 计算准确率rounded_preds = torch.round(torch.sigmoid(preds))  # 把概率的结果 四舍五入correct = (rounded_preds == y).float()  # True False -> 转为 1, 0acc = correct.sum() / len(correct)return acc # 返回精确率#定义函数,训练模型def train(model, iterator, optimizer, criterion):epoch_loss = 0epoch_acc = 0model.train()  # 设置模型标志,保证Dropout在训练模式下for batch in iterator: # 遍历数据集进行训练optimizer.zero_grad()predictions = model(batch.text).squeeze(1)  # 在第1个维度上去除维度loss = criterion(predictions, batch.label)  # 计算损失acc = binary_accuracy(predictions, batch.label) # 计算精确率loss.backward() # 损失函数反向optimizer.step()    # 优化处理epoch_loss += loss.item()   # 统计损失epoch_acc += acc.item() # 统计精确率return epoch_loss / len(iterator), epoch_acc / len(iterator)# 定义函数,评估模型def evaluate(model, iterator, criterion):epoch_loss = 0epoch_acc = 0model.eval()    # 设置模型标志,保证Dropout在评估模型下with torch.no_grad():   # 禁止梯度计算for batch in iterator:predictions = model(batch.text).squeeze(1)  # 计算结果loss = criterion(predictions, batch.label)  # 计算损失acc = binary_accuracy(predictions, batch.label) # 计算精确率epoch_loss += loss.item()epoch_acc += acc.item()return epoch_loss / len(iterator), epoch_acc / len(iterator)# 定义函数,计算时间差def epoch_time(start_time, end_time):elapsed_time = end_time - start_timeelapsed_mins = int(elapsed_time / 60)elapsed_secs = int(elapsed_time - (elapsed_mins * 60))return elapsed_mins, elapsed_secsN_EPOCHS = 100    # 设置训练的迭代次数best_valid_loss = float('inf')  # 设置损失初始值,用于保存最优模型for epoch in range(N_EPOCHS):   # 按照迭代次数进行训练start_time = time.time()train_loss, train_acc = train(model, train_iterator, optimizer, criterion)valid_loss, valid_acc = evaluate(model, valid_iterator, criterion)end_time = time.time()# 计算迭代时间消耗epoch_mins, epoch_secs = epoch_time(start_time, end_time)if valid_loss < best_valid_loss:  # 保存最优模型best_valid_loss = valid_losstorch.save(model.state_dict(), 'textcnn-model.pt')# 输出训练结果print(f'Epoch: {epoch + 1:02} | Epoch Time: {epoch_mins}m {epoch_secs}s')print(f'\t训练损失: {train_loss:.3f} | 训练精确率: {train_acc * 100:.2f}%')print(f'\t Val. Loss: {valid_loss:.3f} |  Val. Acc: {valid_acc * 100:.2f}%')# 测试模型效果model.load_state_dict(torch.load('textcnn-model.pt'))test_loss, test_acc = evaluate(model, test_iterator, criterion)print(f'Test Loss: {test_loss:.3f} | Test Acc: {test_acc * 100:.2f}%')

2.9 代码实现:使用模型进行训练---TextCNN.py(第8部分)

# 1.8 使用模型进行训练:编写模型预测接口函数,对指定句子进行预测。列举几个句子输入模型预测接口函数进行预测,查看预测结果。nlp = spacy.load("en_core_web_sm")# 用spacy加载英文语言包# 定义函数,实现预测接口# (1)将长度不足5的句子用′<pad>'字符补齐。(2)将句子中的单词转为索引。# (3)为张量增加维度,以与训练场景下的输入形状保持一致。(4)输入模型进行预测,并对结果进行Sigmoid计算。因为模型在训练时,使用的计算损失函数自带Sigmoid处理,但模型中没有Sigmoid处理,所以要对结果增加Sigmoid处理。def predict_sentiment(model, sentence, min_len=5): # 设置最小长度为5model.eval() # 设置模型标志,保证Dropout在评估模型下tokenized = nlp.tokenizer(sentence).text.split()  #拆分输入的句子if len(tokenized) < min_len:  # 长度不足,在后面填充tokenized += ['<pad>'] * (min_len - len(tokenized))indexed = [TEXT.vocab.stoi[t] for t in tokenized] # 将单词转化为索引tensor = torch.LongTensor(indexed).to(device)tensor = tensor.unsqueeze(1)    # 为张量增加维度,模拟批次prediction = torch.sigmoid(model(tensor))   # 输入模型进行预测return prediction.item()    # 返回预测结果# 使用句子进行预测:大于0.5为正面评论,小于0.5为负面评论sen = "This film is terrible"print('\n预测 sen = ', sen)print('预测 结果:', predict_sentiment(model, sen))sen = "This film is great"print('\n预测 sen = ', sen)print('预测 结果:', predict_sentiment(model, sen))sen = "I like this film very much!"print('\n预测 sen = ', sen)print('预测 结果:', predict_sentiment(model, sen))

3 代码总览

3.1 TextCNN.py

# 1.1 引入基础库: 固定PyTorch中的随机种子和GPU运算方式。
import random #引入基础库
import time
import torch#引入PyTorch库
import torch.nn as nn
import torch.nn.functional as F
from torchtext.legacy import data ,datasets,vocab #引入文本处理库
import spacytorch.manual_seed(1234) # 固定随机种子,使其每次运行时对权重参数的初始化值一致。
# 固定GPU运算方式:提高GPU的运算效率,通常PyTorch会调用自动寻找最适合当前配置的高效算法进行计算,这一过程会导致每次运算的结果可能出现不一致的情况。
torch.backends.cudnn.deterministic = True # 表明不使用寻找高效算法的功能,使得每次的运算结果一致。[仅GPU有效]# 1.2 用torchtext加载IMDB并拆分为数据集
# IMDB是torchtext库的内置数据集,可以直接通过torchtext库中的datasets.MDB进行处理。
# 在处理之前将数据集的字段类型和分词方法指定正确即可。# 定义字段,并按照指定标记化函数进行分词
TEXT =  data.Field(tokenize = 'spacy',lower=True) # data.Field函数指定数据集中的文本字段用spaCy库进行分词处理,并将其统一改为小写字母。tokenize参数,不设置则默认使用str。
LABEL = data.LabelField(dtype=torch.float)# 加载数据集,并根据IMDB两个文件夹,返回两个数据集。
# datasets.MDB.splits()进行数据集的加载。该代码执行时会在本地目录的.data文件夹下查找是否有MDB数据集,如果没有,则下载;如果有,则将其加载到内存。
# 被载入内存的数据集会放到数据集对象train_data与test_data中。
train_data , test_data = datasets.IMDB.splits(text_field=TEXT,label_field=LABEL)
print("-----------输出一条数据-----------")
# print(vars(train_data.example[0]),len(train_data.example))
print(vars(train_data.examples[0]),len(train_data.examples))
print("---------------------------")# 将训练数据集再次拆分
# 从训练数据中拆分出一部分作为验证数据集。数据集对象train_data的split方法默认按照70%、30%的比例进行拆分。
train_data,valid_data = train_data.split(random_state = random.seed(1234))
print("训练数据集: ", len(train_data),"条")
print("验证数据集: ", len(valid_data),"条")
print("测试数据集: ", len(test_data),"条")# 1.3 加载预训练词向量并进行样本数据化
# 将数据集中的样本数据转化为词向量,并将其按照指定的批次大小进行组合。
# buld_vocab方法实现文本到词向量数据的转化:从数据集对象train_data中取出前25000个高频词,并用指定的预训练词向量glove.6B.100d进行映射。
TEXT.build_vocab(train_data,max_size=25000,vectors="glove.6B.100d",unk_init = torch.Tensor.normal_) # 将样本数据转化为词向量
# glove.6B.100d为torchtext库中内置的英文词向量,主要将每个词映射成维度为100的浮点型数据,该文件会被下载到本地.vector_cache文件夹下。
LABEL.build_vocab(train_data)
#  ---start---创建批次数据:将数据集按照指定批次进行组合。
BATCH_SIZE = 64
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits((train_data, valid_data, test_data), batch_size = BATCH_SIZE, device = device)
#  ---end---创建批次数据:将数据集按照指定批次进行组合。# 1.4 定义带有Mish激活函数的TextCNN模型class Mish(nn.Module):def __init__(self):super(Mish, self).__init__()def forward(self,x):x = x * (torch.tanh(F.softplus(x)))return x# 在TextCNN类中,一共有两个方法:
# ①初始化方法.按照指定个数定义多分支卷积层,并将它们统一放在nn.ModuleList数组中。
# ②前向传播方法:先将输入数据依次输入每个分支的卷积层中进行处理,再对处理结果进行最大池化,最后对池化结果进行连接并回归处理
class TextCNN(nn.Module): #定义TextCNN模型# TextCNN类继承了nn.Module类,在该类中定义的网络层列表必须要使用nn.ModuleList进行转化,才可以被TextCNN类识别。# 如果直接使用列表的话,在训练模型时无法通过TextCNN类对象的parameters方法获得权重。# 定义初始化方法def __init__(self, vocab_size, embedding_dim, n_filters, filter_sizes, output_dim,dropout, pad_idx):super().__init__()self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx = pad_idx) # 定义词向量权重# 定义多分支卷积层# 将定义好的多分支卷积层以列表形式存放,以便在前向传播方法中使用。# 每个分支中卷积核的第一个维度由参数filter_sizes设置,第二个维度都是embedding_dim,即只在纵轴的方向上实现了真正的卷积操作,在横轴的方向上是全尺度卷积,可以起到一维卷积的效果。self.convs = nn.ModuleList([nn.Conv2d(in_channels = 1,out_channels = n_filters,kernel_size = (fs, embedding_dim))for fs in filter_sizes])  #########注意不能用list# 定义输出层self.fc = nn.Linear(len(filter_sizes) * n_filters, output_dim)self.dropout = nn.Dropout(dropout)self.mish = Mish()  # 实例化激活函数对象# 定义前向传播方法def forward(self,text): # 输入形状为[sent len,batch size]text = text.permute(1, 0)  # 将形状变为[batch size, sent len]embedded = self.embedding(text)  # 对于输入数据进行词向量映射,形状为[batch size, sent len, emb dim]embedded = embedded.unsqueeze(1)  # 进行维度变化,形状为[batch size, 1, sent len, emb dim]# len(filter_sizes)个元素,每个元素形状为[batch size, n_filters, sent len - filter_sizes[n] + 1]# 多分支卷积处理conved = [self.mish(conv(embedded)).squeeze(3) for conv in self.convs] # 将输入数据进行多分支卷积处理。该代码执行后,会得到一个含有len(fiter_sizes)个元素的列表,其中每个元素形状为[batchsize,n_filters,sentlen-fltersizes[n]+1],该元素最后一个维度的公式是由卷积公式计算而来的。# 对于每个卷积结果进行最大池化操作pooled = [F.max_pool1d(conv, conv.shape[2]).squeeze(2) for conv in conved]# 将池化结果进行连接cat = self.dropout(torch.cat(pooled, dim=1))  # 形状为[batch size, n_filters * len(filter_sizes)]return self.fc(cat) # 输入全连接,进行回归输出# 1.5 用数据集参数实例化模型
if __name__ == '__main__':# 根据处理好的数据集参数对TextCNN模型进行实例化。INPUT_DIM = len(TEXT.vocab)  # 25002EMBEDDING_DIM = TEXT.vocab.vectors.size()[1]  # 100N_FILTERS = 100 # 定义每个分支的数据通道数量FILTER_SIZES = [3, 4, 5] # 定义多分支卷积中每个分支的卷积核尺寸OUTPUT_DIM = 1 # 定义输出维度DROPOUT = 0.5 # 定义Dropout丢弃率PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token] # 定义填充值:获取数据集中填充字符对应的索引。在词向量映射过程中对齐数据时会使用该索引进行填充。# 实例化模型model = TextCNN(INPUT_DIM, EMBEDDING_DIM, N_FILTERS, FILTER_SIZES, OUTPUT_DIM, DROPOUT, PAD_IDX)# 1.6 用预训练词向量初始化模型:将加载好的TEXT字段词向量复制到模型中,为其初始化。# 复制词向量model.embedding.weight.data.copy_(TEXT.vocab.vectors)# 将填充的词向量清0UNK_IDX = TEXT.vocab.stoi[TEXT.unk_token]model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM) #对未识别词进行清零处理 :使该词在词向量空间中失去意义,目的是防止后面填充字符对原有的词向量空间进行干扰。model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM) #对填充词进行清零处理 :使该词在词向量空间中失去意义,目的是防止后面填充字符对原有的词向量空间进行干扰。# 1.7 使用Ranger优化器训练模型import torch.optim as optim # 引入优化器库from functools import partial # 引入偏函数库from ranger import * # 载入Ranger优化器# 为Ranger优化器设置参数opt_func = partial(Ranger, betas=(.9, 0.99), eps=1e-6)  # betas=(Momentum,alpha)optimizer = opt_func(model.parameters(), lr=0.004)# 定义损失函数criterion = nn.BCEWithLogitsLoss()  # nn.BCEWithLogitsLoss函数是带有Sigmoid函数的二分类交叉熵,即先对模型的输出结果进行Sigmoid计算,再对其余标签一起做Cross_entropy计算。# 分配运算资源model = model.to(device)criterion = criterion.to(device)# 定义函数,计算精确率def binary_accuracy(preds, y):  # 计算准确率rounded_preds = torch.round(torch.sigmoid(preds))  # 把概率的结果 四舍五入correct = (rounded_preds == y).float()  # True False -> 转为 1, 0acc = correct.sum() / len(correct)return acc # 返回精确率#定义函数,训练模型def train(model, iterator, optimizer, criterion):epoch_loss = 0epoch_acc = 0model.train()  # 设置模型标志,保证Dropout在训练模式下for batch in iterator: # 遍历数据集进行训练optimizer.zero_grad()predictions = model(batch.text).squeeze(1)  # 在第1个维度上去除维度loss = criterion(predictions, batch.label)  # 计算损失acc = binary_accuracy(predictions, batch.label) # 计算精确率loss.backward() # 损失函数反向optimizer.step()    # 优化处理epoch_loss += loss.item()   # 统计损失epoch_acc += acc.item() # 统计精确率return epoch_loss / len(iterator), epoch_acc / len(iterator)# 定义函数,评估模型def evaluate(model, iterator, criterion):epoch_loss = 0epoch_acc = 0model.eval()    # 设置模型标志,保证Dropout在评估模型下with torch.no_grad():   # 禁止梯度计算for batch in iterator:predictions = model(batch.text).squeeze(1)  # 计算结果loss = criterion(predictions, batch.label)  # 计算损失acc = binary_accuracy(predictions, batch.label) # 计算精确率epoch_loss += loss.item()epoch_acc += acc.item()return epoch_loss / len(iterator), epoch_acc / len(iterator)# 定义函数,计算时间差def epoch_time(start_time, end_time):elapsed_time = end_time - start_timeelapsed_mins = int(elapsed_time / 60)elapsed_secs = int(elapsed_time - (elapsed_mins * 60))return elapsed_mins, elapsed_secsN_EPOCHS = 100    # 设置训练的迭代次数best_valid_loss = float('inf')  # 设置损失初始值,用于保存最优模型for epoch in range(N_EPOCHS):   # 按照迭代次数进行训练start_time = time.time()train_loss, train_acc = train(model, train_iterator, optimizer, criterion)valid_loss, valid_acc = evaluate(model, valid_iterator, criterion)end_time = time.time()# 计算迭代时间消耗epoch_mins, epoch_secs = epoch_time(start_time, end_time)if valid_loss < best_valid_loss:  # 保存最优模型best_valid_loss = valid_losstorch.save(model.state_dict(), 'textcnn-model.pt')# 输出训练结果print(f'Epoch: {epoch + 1:02} | Epoch Time: {epoch_mins}m {epoch_secs}s')print(f'\t训练损失: {train_loss:.3f} | 训练精确率: {train_acc * 100:.2f}%')print(f'\t Val. Loss: {valid_loss:.3f} |  Val. Acc: {valid_acc * 100:.2f}%')# 测试模型效果model.load_state_dict(torch.load('textcnn-model.pt'))test_loss, test_acc = evaluate(model, test_iterator, criterion)print(f'Test Loss: {test_loss:.3f} | Test Acc: {test_acc * 100:.2f}%')# 1.8 使用模型进行训练:编写模型预测接口函数,对指定句子进行预测。列举几个句子输入模型预测接口函数进行预测,查看预测结果。nlp = spacy.load("en_core_web_sm")# 用spacy加载英文语言包# 定义函数,实现预测接口# (1)将长度不足5的句子用′<pad>'字符补齐。(2)将句子中的单词转为索引。# (3)为张量增加维度,以与训练场景下的输入形状保持一致。(4)输入模型进行预测,并对结果进行Sigmoid计算。因为模型在训练时,使用的计算损失函数自带Sigmoid处理,但模型中没有Sigmoid处理,所以要对结果增加Sigmoid处理。def predict_sentiment(model, sentence, min_len=5): # 设置最小长度为5model.eval() # 设置模型标志,保证Dropout在评估模型下tokenized = nlp.tokenizer(sentence).text.split()  #拆分输入的句子if len(tokenized) < min_len:  # 长度不足,在后面填充tokenized += ['<pad>'] * (min_len - len(tokenized))indexed = [TEXT.vocab.stoi[t] for t in tokenized] # 将单词转化为索引tensor = torch.LongTensor(indexed).to(device)tensor = tensor.unsqueeze(1)    # 为张量增加维度,模拟批次prediction = torch.sigmoid(model(tensor))   # 输入模型进行预测return prediction.item()    # 返回预测结果# 使用句子进行预测:大于0.5为正面评论,小于0.5为负面评论sen = "This film is terrible"print('\n预测 sen = ', sen)print('预测 结果:', predict_sentiment(model, sen))sen = "This film is great"print('\n预测 sen = ', sen)print('预测 结果:', predict_sentiment(model, sen))sen = "I like this film very much!"print('\n预测 sen = ', sen)print('预测 结果:', predict_sentiment(model, sen))

3.2 ranger.py

#Ranger deep learning optimizer - RAdam + Lookahead combined.
#https://github.com/lessw2020/Ranger-Deep-Learning-Optimizer#Ranger has now been used to capture 12 records on the FastAI leaderboard.#This version = 9.3.19  #Credits:
#RAdam -->  https://github.com/LiyuanLucasLiu/RAdam
#Lookahead --> rewritten by lessw2020, but big thanks to Github @LonePatient and @RWightman for ideas from their code.
#Lookahead paper --> MZhang,G Hinton  https://arxiv.org/abs/1907.08610#summary of changes: 
#full code integration with all updates at param level instead of group, moves slow weights into state dict (from generic weights), 
#supports group learning rates (thanks @SHolderbach), fixes sporadic load from saved model issues.
#changes 8/31/19 - fix references to *self*.N_sma_threshold; #changed eps to 1e-5 as better default than 1e-8.import math
import torch
from torch.optim.optimizer import Optimizer, required
import itertools as itclass Ranger(Optimizer):def __init__(self, params, lr=1e-3, alpha=0.5, k=6, N_sma_threshhold=5, betas=(.95,0.999), eps=1e-5, weight_decay=0):#parameter checksif not 0.0 <= alpha <= 1.0:raise ValueError(f'Invalid slow update rate: {alpha}')if not 1 <= k:raise ValueError(f'Invalid lookahead steps: {k}')if not lr > 0:raise ValueError(f'Invalid Learning Rate: {lr}')if not eps > 0:raise ValueError(f'Invalid eps: {eps}')#parameter comments:# beta1 (momentum) of .95 seems to work better than .90...#N_sma_threshold of 5 seems better in testing than 4.#In both cases, worth testing on your dataset (.90 vs .95, 4 vs 5) to make sure which works best for you.#prep defaults and init torch.optim basedefaults = dict(lr=lr, alpha=alpha, k=k, step_counter=0, betas=betas, N_sma_threshhold=N_sma_threshhold, eps=eps, weight_decay=weight_decay)super().__init__(params,defaults)#adjustable thresholdself.N_sma_threshhold = N_sma_threshhold#now we can get to work...#removed as we now use step from RAdam...no need for duplicate step counting#for group in self.param_groups:#    group["step_counter"] = 0#print("group step counter init")#look ahead paramsself.alpha = alphaself.k = k #radam buffer for stateself.radam_buffer = [[None,None,None] for ind in range(10)]#self.first_run_check=0#lookahead weights#9/2/19 - lookahead param tensors have been moved to state storage.  #This should resolve issues with load/save where weights were left in GPU memory from first load, slowing down future runs.#self.slow_weights = [[p.clone().detach() for p in group['params']]#                     for group in self.param_groups]#don't use grad for lookahead weights#for w in it.chain(*self.slow_weights):#    w.requires_grad = Falsedef __setstate__(self, state):print("set state called")super(Ranger, self).__setstate__(state)def step(self, closure=None):loss = None#note - below is commented out b/c I have other work that passes back the loss as a float, and thus not a callable closure.  #Uncomment if you need to use the actual closure...#if closure is not None:#loss = closure()#Evaluate averages and grad, update param tensorsfor group in self.param_groups:for p in group['params']:if p.grad is None:continuegrad = p.grad.data.float()if grad.is_sparse:raise RuntimeError('Ranger optimizer does not support sparse gradients')p_data_fp32 = p.data.float()state = self.state[p]  #get state dict for this paramif len(state) == 0:   #if first time to run...init dictionary with our desired entries#if self.first_run_check==0:#self.first_run_check=1#print("Initializing slow buffer...should not see this at load from saved model!")state['step'] = 0state['exp_avg'] = torch.zeros_like(p_data_fp32)state['exp_avg_sq'] = torch.zeros_like(p_data_fp32)#look ahead weight storage now in state dict state['slow_buffer'] = torch.empty_like(p.data)state['slow_buffer'].copy_(p.data)else:state['exp_avg'] = state['exp_avg'].type_as(p_data_fp32)state['exp_avg_sq'] = state['exp_avg_sq'].type_as(p_data_fp32)#begin computations exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']beta1, beta2 = group['betas']#compute variance mov avgexp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)#compute mean moving avgexp_avg.mul_(beta1).add_(1 - beta1, grad)state['step'] += 1buffered = self.radam_buffer[int(state['step'] % 10)]if state['step'] == buffered[0]:N_sma, step_size = buffered[1], buffered[2]else:buffered[0] = state['step']beta2_t = beta2 ** state['step']N_sma_max = 2 / (1 - beta2) - 1N_sma = N_sma_max - 2 * state['step'] * beta2_t / (1 - beta2_t)buffered[1] = N_smaif N_sma > self.N_sma_threshhold:step_size = math.sqrt((1 - beta2_t) * (N_sma - 4) / (N_sma_max - 4) * (N_sma - 2) / N_sma * N_sma_max / (N_sma_max - 2)) / (1 - beta1 ** state['step'])else:step_size = 1.0 / (1 - beta1 ** state['step'])buffered[2] = step_sizeif group['weight_decay'] != 0:p_data_fp32.add_(-group['weight_decay'] * group['lr'], p_data_fp32)if N_sma > self.N_sma_threshhold:denom = exp_avg_sq.sqrt().add_(group['eps'])p_data_fp32.addcdiv_(-step_size * group['lr'], exp_avg, denom)else:p_data_fp32.add_(-step_size * group['lr'], exp_avg)p.data.copy_(p_data_fp32)#integrated look ahead...#we do it at the param level instead of group levelif state['step'] % group['k'] == 0:slow_p = state['slow_buffer'] #get access to slow param tensorslow_p.add_(self.alpha, p.data - slow_p)  #(fast weights - slow weights) * alphap.data.copy_(slow_p)  #copy interpolated weights to RAdam param tensorreturn loss

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/469186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python怎么画出好看的统计图_用最简单的 Python ,画最好看的图 [简单数据可视化]...

可以直接修改参数使用&#xff0c;非常的方便。import numpy as np import pandas as pd import holoviews as hv hv.extension(bokeh) macro_df pd.read_csv(http://assets.holoviews.org/macro.csv, \t) key_dimensions [(year, Year), (country, Country)] value_dimensio…

combobox之下拉宽度自适应

效果对比 先看下优化前后的效果&#xff0c;再看实现过程. 优化前 优化后 从上图中可看到&#xff0c;combobox优化后可以自适应不同长度的字符串&#xff0c;保证每个字符串都能够显示完整。 实现过程 当我们触发CBN_DROPDOWN事件时&#xff0c;不再使用默认的实现&#xff0c…

Python工具:将文件夹下的视频按照帧数输出图片文件(含代码)

1、描述 将一个视频流按帧数截取大量的图片 2、用途 AI的数据集制作&#xff0c;得到大量的图片&#xff0c;之后将其打标签 3、案例文件截图 4、代码实现&#xff1a; import cv2 import argparse import os# 边里该文件夹下的文件名称 def read_directory(directory_nam…

用Python语言对任意图像进行m*n的均匀分块(思路非常清晰,步骤简单)

主要用途&#xff1a;处理图片数据集 1 对单个图片进行分块 import numpy as np import matplotlib.pyplot as plt import cv2def divide_method1(img,m,n):#分割成m行n列print(img.shape)h, w img.shape[0],img.shape[1]gx np.round(h).astype(np.int)gy np.round(w).asty…

python爬虫用什么软件写_python爬虫怎么写

如今很多有编程能力的小伙伴已经不满足手动搜索内容了&#xff0c;都希望通过编写爬虫软件来快速获取需要的内容&#xff0c;那么如何使用python制作爬虫呢&#xff1f;下面小编给大家讲解一下思路 工具/原料 python 方法/步骤 1 首先我们需要确定要爬取的目标页面内容&#xf…

花书《深度学习》代码实现:01 线性代数:基本概念+代码实现基本运算

1 标量、向量、矩阵和张量 2 矩阵和向量相乘 3 单位矩阵和逆矩阵 3.0 单位矩阵 a np.identity(3) # 三行三列单位矩阵 3.1 矩阵的逆 A [[1.0,2.0],[3.0,4.0]] A_inv np.linalg.inv(A) print("A 的逆矩阵", A_inv) 3.1 转置 A np.array([[1.0,2.0],[1.0,0…

【Pytorch神经网络理论篇】 38 Transformers:安装说明+应用结构+AutoModel类

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

clone是深拷贝还是浅拷贝_Python中的浅拷贝和深拷贝

本文翻译自copy in Python (Deep Copy and Shallow Copy)&#xff0c;讲述了在Python语言中浅拷贝与深拷贝的不同用法。全文系作者原创&#xff0c;仅供学习参考使用&#xff0c;转载授权请私信联系&#xff0c;否则将视为侵权行为。码字不易&#xff0c;感谢支持。以下为全文内…

【Pytorch神经网络实战案例】32 使用Transformers库的管道方式实现:加载指定模型+文本分类+掩码语言建模+摘要生成+特征提取+阅读理解+实体词识别

管道方式是Transformers库中高度集成的极简使用方式。使用这种方式来处理NLP任务&#xff0c;只需要编写几行代码就能实现。通过本例的练习可以使读者对Transformers库的使用快速上手。 1 在管道方式中指定NLP任务 Transfomers库的管道方式使用起来非常简单&#xff0c;核心步…

jqprint获取打印页数_如何将每张打印多页PPT的PDF变成常规课件

在工作和学习中&#xff0c;经常会收到各种 PDF 文件&#xff0c;尤其是老师的课件。为了防止学生大量上传到各种文库网站赚积分&#xff0c;或者为了方便学生打印出来预习复习。通常&#xff0c;会在每页 PDF 里面&#xff0c;打印多张 PPT 内容。一般是 6 张或 9 张&#xff…

vba 判断文本框内容是否为空_【VBA】 数据输入 Inputbox 基本语法

在使用Excel 的过程中&#xff0c;如果需要用户输入简单的数据&#xff0c;作为“已知数”&#xff0c;那么可以使用inputbox 函数显示一个对话框&#xff0c;供用户在对话框中输入数据。 Inputbox 函数语法在一对话框来中显示提示&#xff0c;等待用户输入正文或按下按钮&…

无向图的深度优先遍历非递归_LeetCode0429: N叉树的层序遍历

题目介绍描述&#xff1a;给定一个 N 叉树&#xff0c;返回其节点值的层序遍历。 (即从左到右&#xff0c;逐层遍历)。例如&#xff0c;给定一个 3叉树 :返回其层序遍历:[[1],[3,2,4],[5,6] ]说明:树的深度不会超过 1000。 树的节点总数不会超过 5000。解题思路&#xff1a;★ …

一条龙操作有效解决PermissionError: [WinError 5] 拒绝访问的问题

1 问题描述 当在使用pip install 安装包时&#xff0c;如&#xff1a;pip install scrapy scrapyd scrapyd-client spiderkeeper出现报错&#xff1a;PermissionError: [WinError 5] 拒绝访问。: ‘c:\programdata\anaconda3\lib\site-packages\dateutil\easter.py’ 2 解决办…

预订态势图

//预订态势图JS//根据日期得到对应星期几 function getWeekByDay(riqi){//2017-01-23;var getWeek "";var arys1 new Array(); arys1riqi.split(-); //日期为输入日期&#xff0c;格式为 2013-3-10var ssdatenew Date(arys1[0],parseInt(arys1[1]-1),arys1…

altera fpga sdi输出方案_FPGA在电力电子中的应用有哪些?

大家好&#xff0c;很抱歉上周末没有及时更新公众号&#xff0c;本来这期想聊聊IGBT的拖尾电流&#xff0c;但是由于周末去深圳高交会(高新技术成果交易会)逛了一天&#xff0c;时间给耽搁了&#xff0c;感觉要想把拖尾电流讲清楚也不太容易&#xff0c;还得需要点时间&#xf…

【Pytorch神经网络理论篇】 39 Transformers库中的BERTology系列模型

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

Unity之CharacterController2D学习笔记(1)——基础使用

在很多游戏类型中&#xff0c;玩家角色对物理行为的处理往往和场景中其它物体的行为有比较大的区别。比如角色可能会以90多公里的时速狂奔&#xff0c;同时一次跳跃能跳10多米高&#xff0c;与此同时却几乎不会有任何惯性。同时角色在正常情况下当头部碰到障碍物的时候&#xf…

【Pytorch神经网络实战案例】33 使用BERT模型实现完形填空任务

1 案例描述 案例&#xff1a;加载Transformers库中的BERT模型&#xff0c;并用它实现完形填空任务&#xff0c;即预测一个句子中缺失的单词。 2 代码实现&#xff1a;使用BERT模型实现完形填空任务 2.1 代码实现&#xff1a;载入词表&#xff0c;并对输入的文本进行分词转化--…

c++ string 删除字符_字符串操作的全面总结

来自公众号&#xff1a;C语言与cpp编程字符串操作看似简单&#xff0c;其实非常重要&#xff0c;不注意的话&#xff0c;经常出现代码运行结果和自己想要的不一致&#xff0c;甚至崩溃。本文总结了一些构建string对象方法、修改string对象的方法、string类型的操作函数、string…

【Pytorch神经网络理论篇】 40 Transformers中的词表工具Tokenizer

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…