Bert+FGSM/PGD实现中文文本分类(Loss=0.5L1+0.5L2)

任务目标:在使用FGSM/PGD来训练Bert模型进行文本分类,其实现原理可以简单概括为以下几个步骤:

  1. 对原始文本每个词转换为对应的嵌入向量。
  2. 将每个嵌入向量与一个小的扰动向量相加,从而生成对抗样本。这个扰动向量的大小可以通过一个超参数来控制。
  3. 将生成的对抗样本和原始样本一起用于训练模型。具体来说,可以将它们组成一个batch,然后使用交叉熵损失函数来训练模型。
  4. 在训练过程中,可以周期性地增加扰动向量的大小,从而使得模型逐渐适应更强的攻击。这个过程可以称为“逐步增强对抗性训练”。
  5. 通过使用FGSM/PGD来训练Bert模型,可以使得模型对对抗样本更加鲁棒,从而提高其在真实场景中的泛化能力和分类准确率。
  6. 在训练过程中我们设置 总样本Loss=0.5原样本Loss+0.5对抗样本Loss,来提升模型的鲁棒性。

目录

一、导入所需的库和模块

二、加载数据集

三、定义模型和优化器

四、 基于原生Bert文本分类

4.1 定义训练函数

4.2 定义测试函数

五、Bert+FGSM文本分类 

5.1 定义FGSM对抗训练函数 

5.2 定义训练模型函数

5.3 定义测试函数

六、Bert+PGD文本分类

6.1 定义PGD攻击函数

6.2 定义训练函数

6.3 定义测试函数


在使用FGSM/PGD来训练Bert模型进行文本分类时,其实现原理可以概括为以下几个步骤: 

一、导入所需的库和模块

这段代码主要是导入了一些必要的 PyTorch 和 transformers 库中的类和函数,其中: torch 是 PyTorch 库的主要模块,包含了大量的张量操作和神经网络模块等。 nn 是 PyTorch 中的神经网络模块,包含了各种神经网络层和模型等。 optim 是 PyTorch 中的优化器模块,包含了各种优化算法,如 SGD、Adam 等。 DataLoader 和 Dataset 是 PyTorch 中的数据集和数据加载器模块,用于加载和处理数据集。 BertTokenizerFast 是 transformers 库中的类,用于将文本转换为 BERT 模型的输入格式。 BertForSequenceClassification 是 transformers 库中的类,用于进行文本分类任务。 

# 导入 PyTorch 库
import torch
# 导入 PyTorch 中的神经网络模块
import torch.nn as nn
# 导入 PyTorch 中的优化器模块
import torch.optim as optim
# 导入 PyTorch 中的数据集和数据加载器模块
from torch.utils.data import DataLoader, Dataset
# 导入 transformers 库中的 BertTokenizerFast 和 BertForSequenceClassification 类
from transformers import BertTokenizerFast, BertForSequenceClassification
import numpy as np

二、加载数据集

这段代码的主要作用是创建一个用于加载 THUCNews 数据集的数据集类 THUCNewsDataset,并实现 len 和 getitem 方法。其中: tqdm 库用于显示进度条,可以让我们在读取数据集时更直观地了解进度。 BertTokenizerFast.from_pretrained('bert-base-chinese') 创建了一个 BertTokenizerFast 对象,用于将文本转换为 BERT 模型的输入格式。 self.data 列表用于存储数据集中的每个样本,每个样本是一个元组,包含文本和标签。 init 方法用于初始化数据集对象。在该方法中,我们打开数据集文件,并逐行读取数据。对于每一行数据,我们使用 strip() 方法去除空格和换行符,然后使用 split('\t') 方法将文本和标签分开。最后,我们将文本和标签封装成一个元组,并将其添加到 self.data 列表中。 len 方法用于返回数据集的长度,即数据集中样本的个数。 getitem 方法用于获取数据集中的一个样本。在该方法中,我们从 self.data 列表中获取第 idx 个样本的文本和标签。然后,我们使用 self.tokenizer 将文本转换为 BERT 模型的输入格式,并将标签转换为 tensor,并将其添加到 inputs 字典中。最后,我们返回 inputs 字典。 

# 导入 tqdm 库
from tqdm import tqdm
# 定义一个 THUCNewsDataset 类,继承自 PyTorch 中的 Dataset 类
class THUCNewsDataset(Dataset):# 定义构造函数,接收一个文件路径作为参数def __init__(self, file_path):# 初始化 BERT tokenizerself.tokenizer = BertTokenizerFast.from_pretrained('bert-base-chinese')# 定义一个列表,用于存储数据集中的每一条数据self.data = []# 打开数据集文件,逐行读取数据并处理with open(file_path, 'r', encoding='utf-8') as f:# 使用 tqdm 库显示读取进度for line in tqdm(f):# 从每一行数据中提取文本和标签,并将其存储到列表中text, label = line.strip().split('\t')self.data.append((text, int(label)))# 定义 __len__ 方法,返回数据集的大小def __len__(self):return len(self.data)# 定义 __getitem__ 方法,根据索引返回数据集中的一条数据def __getitem__(self, idx):# 从列表中获取文本和标签text, label = self.data[idx]# 使用 BERT tokenizer 对文本进行处理,将其转换为 BERT 模型的输入格式inputs = self.tokenizer(text, padding='max_length', truncation=True, max_length=32, return_tensors='pt')# 将标签转换为 PyTorch 的张量格式,并将其添加到输入中inputs['labels'] = torch.tensor(label)# 返回处理后的输入return inputs# 加载训练集、测试集和验证集
train_dataset = THUCNewsDataset('train.txt')
test_dataset = THUCNewsDataset('test.txt')
dev_dataset = THUCNewsDataset('dev.txt')# 导入 PyTorch 库中的 pad_sequence 函数,用于填充序列
from torch.nn.utils.rnn import pad_sequence# 定义一个 collate_fn 函数,用于对数据进行批处理
def collate_fn(batch):# 从批次数据中提取 input_ids、attention_mask 和 labelsinput_ids = [item['input_ids'] for item in batch]attention_mask = [item['attention_mask'] for item in batch]labels = [item['labels'] for item in batch]# 对 input_ids 和 attention_mask 进行填充操作,使它们的长度相同input_ids = pad_sequence(input_ids, batch_first=True, padding_value=0)attention_mask = pad_sequence(attention_mask, batch_first=True, padding_value=0)# 将 labels 转换为 tensor 类型labels = torch.tensor(labels)# 返回一个字典,包含处理后的 input_ids、attention_mask 和 labelsreturn {'input_ids': input_ids, 'attention_mask': attention_mask, 'labels': labels}# 创建数据加载器,用于批量加载数据
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, collate_fn=collate_fn)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, collate_fn=collate_fn)
dev_loader = DataLoader(dev_dataset, batch_size=32, shuffle=False, collate_fn=collate_fn)

三、定义模型和优化器

这段代码的主要作用是创建一个用于文本分类的 BERT 模型,并初始化优化器和损失函数。其中: BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=10) 加载了预训练的 BERT 模型,并创建了一个用于文本分类的 BERT 模型。其中,'bert-base-chinese' 表示使用中文 BERT 模型,num_labels=10 表示模型的输出类别数为 10。 optim.Adam(model.parameters(), lr=2e-5) 创建了一个 Adam 优化器,用于更新模型参数。其中,model.parameters() 表示优化器需要更新的模型参数,lr=2e-5 表示学习率为 2e-5。 nn.CrossEntropyLoss() 创建了一个交叉熵损失函数,用于计算模型的损失。在文本分类任务中,通常使用交叉熵损失函数作为损失函数。 

# 加载预训练的 BERT 模型,并创建一个用于文本分类的 BERT 模型
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=10)
# 创建一个 Adam 优化器,用于更新模型参数
optimizer = optim.Adam(model.parameters(), lr=2e-5)
# 创建一个交叉熵损失函数,用于计算模型的损失
criterion = nn.CrossEntropyLoss()

上面红色的提示是正常的,大概意思是指Bert用在下游任务需要微调。

四、 基于原生Bert文本分类

4.1 定义训练函数

训练函数用于训练模型,测试函数用于测试模型在测试数据集上的性能。

# 训练函数在每个批次中都进行了反向传播和参数更新
def train(model, optimizer, criterion, train_loader, device):model.train() # 将模型设置为训练模式train_loss = 0 # 初始化训练损失为0train_acc = 0 # 初始化训练准确率为0for batch in train_loader: # 遍历训练数据集input_ids = batch['input_ids'].squeeze(1).to(device) # 将输入数据移动到计算设备上attention_mask = batch['attention_mask'].squeeze(1).to(device) # 将输入数据移动到计算设备上labels = batch['labels'].to(device) # 将标签移动到计算设备上optimizer.zero_grad() # 清空梯度outputs = model(input_ids, attention_mask=attention_mask, labels=labels) # 模型前向传播loss = criterion(outputs.logits, labels) # 计算损失train_loss += loss.item() # 累加损失loss.backward() # 反向传播,计算梯度optimizer.step() # 更新参数preds = torch.argmax(outputs.logits, dim=1) # 计算预测结果train_acc += torch.sum(preds == labels).item() # 计算准确率train_loss /= len(train_loader) # 计算平均损失train_acc /= len(train_loader.dataset) # 计算平均准确率return train_loss, train_acc # 返回训练损失和准确率
# 验证函数只进行了前向传播,没有进行反向传播和参数更新
def evaluate(model, criterion, test_loader, device):model.eval() # 将模型设置为评估模式test_loss = 0 # 初始化测试损失为0test_acc = 0 # 初始化测试准确率为0with torch.no_grad(): # 关闭梯度计算for batch in test_loader: # 遍历测试数据集input_ids = batch['input_ids'].squeeze(1).to(device) # 将输入数据移动到计算设备上attention_mask = batch['attention_mask'].squeeze(1).to(device) # 将输入数据移动到计算设备上labels = batch['labels'].to(device) # 将标签移动到计算设备上outputs = model(input_ids, attention_mask=attention_mask, labels=labels) # 模型前向传播loss = criterion(outputs.logits, labels) # 计算损失test_loss += loss.item() # 累加损失preds = torch.argmax(outputs.logits, dim=1) # 计算预测结果test_acc += torch.sum(preds == labels).item() # 计算准确率test_loss /= len(test_loader) # 计算平均损失test_acc /= len(test_loader.dataset) # 计算平均准确率return test_loss, test_acc # 返回测试损失和准确率

4.2 定义测试函数

# 将模型移动到计算设备上(GPU 或 CPU)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)# 训练模型
best_acc = 0 # 初始化最佳准确率为0
for epoch in range(10): # 遍历10个 epochtrain_loss, train_acc = train(model, optimizer, criterion, train_loader, device) # 训练模型test_loss, test_acc = evaluate(model, criterion, test_loader, device) # 在测试集上评估模型dev_loss, dev_acc = evaluate(model, criterion, dev_loader, device) # 在验证集上评估模型# 输出当前 epoch 的训练损失、训练准确率、测试损失、测试准确率、验证损失和验证准确率print(f'Epoch {epoch + 1}: Train Loss {train_loss:.4f}, Train Acc {train_acc:.4f}, Test Loss {test_loss:.4f}, Test Acc {test_acc:.4f}, Dev Loss {dev_loss:.4f}, Dev Acc {dev_acc:.4f}')if dev_acc > best_acc: # 如果当前 epoch 的验证准确率大于历史最佳准确率best_acc = dev_acc # 更新历史最佳准确率torch.save(model.state_dict(), 'best_model.pt') # 保存模型参数到文件 best_model.pt

五、Bert+FGSM文本分类 

以下是使用FGSM在embedding添加干扰,并考虑到对抗性样本的防御的训练和测试函数。 我们在train()函数中添加了epsilon参数,以控制对抗性样本的干扰程度。我们还计算了原始样本的损失和对抗性样本的损失,并将它们加权平均作为总损失。在evaluate()函数中,我们仅对模型进行前向传递,以便计算测试损失和准确率。 BertTokenizerFast 是 transformers 库中的一个高速分词器,它是 BertTokenizer 的改进版本。与 BertTokenizer 不同,BertTokenizerFast 使用 Rust 实现的底层代码,因此在分词速度方面更快。另外,BertTokenizerFast 还支持更多的特殊标记,例如 Truncation,Padding,以及更好的处理未知单词(out-of-vocabulary,OOV)。 如果你的代码需要处理大量文本数据,那么使用 BertTokenizerFast 可以显著提高代码的执行效率。但是,如果你的代码只需要处理少量文本数据,那么使用 BertTokenizer 更加方便和易于使用。 

5.1 定义FGSM对抗训练函数 

这是一个用于生成对抗样本的函数,输入参数包括原始文本嵌入向量embedding,扰动大小epsilon和梯度gradient。该函数会计算梯度的符号,创建扰动,并将扰动限制在有效范围内。最后返回生成的对抗样本。这段代码实现了 FGSM 对抗攻击,目的是在原始输入的嵌入(embeddings)中添加一些干扰,以生成对抗样本(adversarial sample)。

具体来说,这个函数接受三个参数:原始输入的嵌入(embeddings)、对于原始输入的梯度(grad)、添加干扰的程度(epsilon)。它首先将梯度值(grad)符号化(sign),得到输入的梯度符号,然后与干扰程度(epsilon)相乘,得到干扰值(perturb)。最后,将干扰(perturb)添加到原始输入的嵌入中,得到对抗样本的嵌入(perturb_embeds)。

FGSM 对抗攻击的核心思想是在保证对抗样本与原始样本之间尽可能小的距离(即干扰程度尽可能小)的同时,使得对抗样本能够欺骗深度学习模型。这个距离被称为 L_p 距离,通常选择 L_∞ 距离,即干扰程度的上界为 epsilon。

def fgsm_attack(embedding, epsilon, gradient):# 计算梯度的符号if gradient is None:print('gradient is None')return embeddingsign_gradient = gradient.sign()# 创建扰动perturbed_embedding = embedding + epsilon * sign_gradient# 将扰动限制在有效范围内perturbed_embedding = torch.clamp(perturbed_embedding, min=0, max=1)return perturbed_embedding

5.2 定义训练模型函数

这是用于训练模型的函数,输入参数包括模型model,优化器optimizer,损失函数criterion,训练数据集的数据加载器train_loader,计算设备device和。该函数会遍历训练数据集,将输入数据和标签移动到计算设备上,清空梯度,生成对抗样本,计算损失和梯度,更新参数,计算准确率等操作,并返回训练损失和准确率。

学习率调度器的作用是在训练过程中自动调整学习率,以提高模型的训练效果。 ReduceLROnPlateau 是一个 PyTorch 自带的学习率调度器类,它有以下参数: optimizer:优化器对象,用于更新模型参数; mode:模式,可选值为 min、max 或 auto,表示监测的指标是越小越好、越大越好还是自动选择。这里我们选择 max,表示准确率越大越好; factor:学习率缩放因子,每次调整学习率时将当前学习率乘以该因子; patience:当监测指标在 patience 轮内没有变化时,减小学习率; verbose:是否打印调度器信息; epsilon:学习率变化的最小阈值,如果新学习率与旧学习率之间的差异小于该阈值,则不会更新学习率; cooldown:调整学习率后,暂停更新学习率的轮数; min_lr:学习率的下限; eps:数值稳定性参数。 当调用 scheduler.step(acc) 时,调度器会根据当前的准确率 acc 来自动调整学习率。如果在 patience 轮内准确率没有提高,则会将学习率缩小 factor 倍,直到学习率达到下限 min_lr。

# 创建一个 Adam 优化器,用于更新模型参数
parameters = [p for p in model.parameters() if p.requires_grad]
optimizer = optim.Adam(parameters, lr=2e-5)
scheduler = ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=3, verbose=1, epsilon=1e-4, cooldown=0, min_lr=0, eps=1e-8)
def train(model, optimizer, criterion, train_loader, device, epsilon):model.train() # 将模型设置为训练模式train_loss = 0 # 初始化训练损失为0train_acc = 0 # 初始化训练准确率为0tokenizer = BertTokenizerFast.from_pretrained('bert-base-chinese')for batch in train_loader: # 遍历训练数据集input_ids = batch['input_ids'].squeeze(1).to(device) # 将输入数据移动到计算设备上attention_mask = batch['attention_mask'].squeeze(1).to(device) # 将输入数据移动到计算设备上labels = batch['labels'].to(device) # 将标签移动到计算设备上optimizer.zero_grad() # 清空梯度embedding = model.bert.embeddings.word_embeddings(input_ids)embedding = embedding.detach().clone().requires_grad_(True)embedding.retain_grad()  # 保留梯度信息outputs = model(inputs_embeds=embedding, attention_mask=attention_mask)loss = criterion(outputs.logits, labels)embedding_grad = torch.autograd.grad(loss, embedding, allow_unused=True, retain_graph=True)[0]perturbed_embedding = fgsm_attack(embedding, epsilon, embedding_grad) # 添加扰动perturbed_tokens = tokenizer.convert_ids_to_tokens(np.argmax(perturbed_embedding.detach().cpu().numpy(), axis=-1).tolist()[0])perturbed_input_ids = torch.tensor(tokenizer.convert_tokens_to_ids(perturbed_tokens)).unsqueeze(0).to(device) # 将tokens转换为input_idsperturbed_outputs = model(perturbed_input_ids, attention_mask=attention_mask, labels=labels) # 模型前向传播perturbed_loss = criterion(perturbed_outputs.logits, labels) # 计算对抗样本损失optimizer.zero_grad() # 清空梯度loss = 0.5 * loss + 0.5 * perturbed_loss # 计算总损失loss.backward() # 反向传播,计算总梯度optimizer.step() # 更新参数pbar(step=step, info={'loss': loss.item()})preds = torch.argmax(outputs.logits, dim=1) # 计算原样本预测结果train_loss += loss.item() # 累加原样本损失train_acc += torch.sum(preds == labels).item() # 计算原样本准确率train_loss /= len(train_loader) # 计算平均原样本损失train_acc /= len(train_loader.dataset) # 计算平均原样本准确率return train_loss, train_acc # 返回训练损失和准确率def evaluate(model, criterion, test_loader, device):"""测试函数,仅进行前向传播,不生成对抗样本:param model: 模型:param criterion: 损失函数:param test_loader: 测试数据集的数据加载器:param device: 计算设备:return: 测试损失和准确率"""model.eval() # 设置模型为评估模式test_loss = 0test_acc = 0with torch.no_grad(): # 关闭梯度计算for batch in test_loader:input_ids = batch['input_ids'].squeeze(1).to(device) # 将输入数据移动到计算设备上attention_mask = batch['attention_mask'].squeeze(1).to(device)labels = batch['labels'].to(device)outputs = model(input_ids, attention_mask=attention_mask, labels=labels) # 模型前向传播loss = criterion(outputs.logits, labels) # 计算损失test_loss += loss.item() # 加损失preds = torch.argmax(outputs.logits, dim=1) # 计算预测结果test_acc += torch.sum(preds == labels).item() #计算准确率test_loss /= len(test_loader) # 计算平均损失test_acc /= len(test_loader.dataset) # 计算平均准确率return test_loss, test_acc

5.3 定义测试函数

这是用于测试模型的函数,输入参数包括模型model,损失函数criterion,测试数据集的数据加载器test_loader和计算设备device。该函数会将模型设置为评估模式,关闭梯度计算,进行前向传播,计算损失和准确率,并返回测试损失和准确率。 

# 将模型移动到计算设备上(GPU 或 CPU)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
best_acc = 0 # 初始化最佳准确率为0
for epoch in range(10): # 进行10轮训练train_loss, train_acc = train(model, optimizer, criterion, train_loader, device, epsilon=0.3) # 训练模型,并获取训练损失和准确率test_loss, test_acc = evaluate(model, criterion, test_loader, device) # 对测试集进行测试,并获取测试损失和准确率dev_loss, dev_acc = evaluate(model, criterion, dev_loader, device) # 对验证集进行测试,并获取验证损失和准确率print(f'Epoch {epoch+1}, Train Loss {train_loss:.4f}, Train Acc {train_acc:.4f}, Test Loss {test_loss:.4f}, Test Acc {test_acc:.4f}, Dev Loss {dev_loss:.4f}, Dev Acc {dev_acc:.4f}')# 打印训练轮数、训练损失和准确率、测试损失和准确率、验证损失和准确率if dev_acc > best_acc: # 如果当前验证准确率大于最佳准确率best_acc = dev_acc # 更新最佳准确率torch.save(model.state_dict(), 'adv_best_model.pt') # 保存模型参数到文件'best_model.pt'

六、Bert+PGD文本分类

FGSM(Fast Gradient Sign Method)和 PGD(Projected Gradient Descent)都是对抗训练中常用的方法,它们的主要区别在于对抗样本的生成方式和训练策略上。

FGSM 是一种基于梯度的对抗样本生成方法,它通过计算损失函数对输入数据的梯度来生成对抗样本。具体来说,对于一个输入样本,FGSM 会计算其梯度,然后将其与一个小的扰动值相乘,从而生成一个对抗样本。FGSM 的优点是计算效率高,但缺点是生成的对抗样本可能不够鲁棒,容易被攻击者攻击。

PGD 是一种基于迭代的对抗样本生成方法,它通过迭代多次生成对抗样本,并在每次迭代中对生成的对抗样本进行投影,以保证其在一定范围内。具体来说,PGD 会在每次迭代中计算输入数据的梯度,然后对其进行一定程度的扰动,并将扰动后的结果进行投影,以保证其在一定范围内。PGD 的优点是生成的对抗样本更加鲁棒,但缺点是计算效率较低。

在 Bert 文本分类任务中,采用 FGSM 和 PGD 对抗训练的区别主要在于训练策略上。FGSM 对抗训练通常采用单次扰动,而 PGD 对抗训练通常采用多次迭代扰动。在 FGSM 对抗训练中,每次训练只使用一个对抗样本,而在 PGD 对抗训练中,每次训练使用多个对抗样本。因此,PGD 对抗训练的鲁棒性更强,但计算代价也更高。

6.1 定义PGD攻击函数

def pgd_attack(model, embedding, attention_mask, labels, epsilon, embedding_grad, alpha, num_iters):"""PGD 攻击函数:param model: 模型:param embedding: 原始输入的嵌入表示:param attention_mask: 输入的注意力掩码:param labels: 标签:param epsilon: 扰动范围:param embedding_grad: 原始输入的嵌入表示的梯度:param alpha: 步长:param num_iters: 迭代次数:return: 添加扰动后的嵌入表示"""perturbed_embedding = torch.nn.Parameter(embedding) # 将嵌入表示转换为可训练的参数for i in range(num_iters):perturbed_embedding.requires_grad = True # 设置扰动为可求导perturbed_outputs = model(inputs_embeds=perturbed_embedding, attention_mask=attention_mask, labels=labels) # 模型前向传播perturbed_loss = criterion(perturbed_outputs.logits, labels) # 计算对抗样本损失perturbed_grad = torch.autograd.grad(perturbed_loss, perturbed_embedding, allow_unused=True, retain_graph=True)[0] # 计算梯度perturbed_embedding = perturbed_embedding.detach() + alpha * torch.sign(perturbed_grad) # 梯度方向上进行一定的步长更新perturbed_embedding = torch.max(torch.min(perturbed_embedding, embedding + epsilon), embedding - epsilon) # 将扰动限制在一定范围内perturbed_embedding = torch.nn.Parameter(perturbed_embedding) # 将更新后的嵌入表示重新转换为可训练的参数return perturbed_embedding

6.2 定义训练函数

def train(model, optimizer, criterion, train_loader, device, epsilon, alpha, num_iters):model.train() # 将模型设置为训练模式train_loss = 0 # 初始化训练损失为0train_acc = 0 # 初始化训练准确率为0tokenizer = BertTokenizerFast.from_pretrained('bert-base-chinese')for batch in train_loader: # 遍历训练数据集input_ids = batch['input_ids'].squeeze(1).to(device) # 将输入数据移动到计算设备上attention_mask = batch['attention_mask'].squeeze(1).to(device) # 将输入数据移动到计算设备上labels = batch['labels'].to(device) # 将标签移动到计算设备上optimizer.zero_grad() # 清空梯度embedding = model.bert.embeddings.word_embeddings(input_ids)embedding = embedding.detach().clone().requires_grad_(True)embedding.retain_grad()  # 保留梯度信息outputs = model(inputs_embeds=embedding, attention_mask=attention_mask)loss = criterion(outputs.logits, labels)embedding_grad = torch.autograd.grad(loss, embedding, allow_unused=True, retain_graph=True)[0]perturbed_embedding = pgd_attack(model, embedding, attention_mask, labels, epsilon, embedding_grad, alpha, num_iters)  # 添加扰动perturbed_tokens = tokenizer.convert_ids_to_tokens(np.argmax(perturbed_embedding.detach().cpu().numpy(), axis=-1).tolist()[0])perturbed_input_ids = torch.tensor(tokenizer.convert_tokens_to_ids(perturbed_tokens)).unsqueeze(0).to(device) # 将tokens转换为input_idsperturbed_outputs = model(perturbed_input_ids, attention_mask=attention_mask, labels=labels) # 模型前向传播perturbed_loss = criterion(perturbed_outputs.logits, labels) # 计算对抗样本损失optimizer.zero_grad() # 清空梯度loss = 0.5 * loss + 0.5 * perturbed_loss # 计算总损失loss.backward() # 反向传播,计算总梯度optimizer.step() # 更新参数preds = torch.argmax(outputs.logits, dim=1) # 计算原样本预测结果train_loss += loss.item() # 累加原样本损失train_acc += torch.sum(preds == labels).item() # 计算原样本准确率train_loss /= len(train_loader) # 计算平均原样本损失train_acc /= len(train_loader.dataset) # 计算平均原样本准确率return train_loss, train_acc # 返回训练损失和准确率def evaluate(model, criterion, test_loader, device):"""测试函数,仅进行前向传播,不生成对抗样本:param model: 模型:param criterion: 损失函数:param test_loader: 测试数据集的数据加载器:param device: 计算设备:return: 测试损失和准确率"""model.eval() # 设置模型为评估模式test_loss = 0test_acc = 0with torch.no_grad(): # 关闭梯度计算for batch in test_loader:input_ids = batch['input_ids'].squeeze(1).to(device) # 将输入数据移动到计算设备上attention_mask = batch['attention_mask'].squeeze(1).to(device)labels = batch['labels'].to(device)outputs = model(input_ids, attention_mask=attention_mask, labels=labels) # 模型前向传播loss = criterion(outputs.logits, labels) # 计算损失test_loss += loss.item() # 加损失preds = torch.argmax(outputs.logits, dim=1) # 计算预测结果test_acc += torch.sum(preds == labels).item() #计算准确率test_loss /= len(test_loader) # 计算平均损失test_acc /= len(test_loader.dataset) # 计算平均准确率return test_loss, test_acc

6.3 定义测试函数

# 将模型移动到计算设备上(GPU 或 CPU)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
best_acc = 0 # 初始化最佳准确率为0
for epoch in range(10): # 进行10轮训练train_loss, train_acc = train(model, optimizer, criterion, train_loader, device, epsilon=0.3, alpha=0.04, num_iters=5) # 训练模型,并获取训练损失和准确率test_loss, test_acc = evaluate(model, criterion, test_loader, device) # 对测试集进行测试,并获取测试损失和准确率dev_loss, dev_acc = evaluate(model, criterion, dev_loader, device) # 对验证集进行测试,并获取验证损失和准确率print(f'Epoch {epoch+1}, Train Loss {train_loss:.4f}, Train Acc {train_acc:.4f}, Test Loss {test_loss:.4f}, Test Acc {test_acc:.4f}, Dev Loss {dev_loss:.4f}, Dev Acc {dev_acc:.4f}')# 打印训练轮数、训练损失和准确率、测试损失和准确率、验证损失和准确率if dev_acc > best_acc: # 如果当前验证准确率大于最佳准确率best_acc = dev_acc # 更新最佳准确率torch.save(model.state_dict(), 'adv_best_model.pt') # 保存模型参数到文件'best_model.pt'

综上所述,在Bert文本分类的基础上,分别加上FGSM、PGD对抗训练,分类结果基本差不多,而FGSM的训练速度要比PGD快。当然分类准确率可能跟数据集有关,理论上PGD攻击的鲁棒性和效果应该更好。 

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

火狐浏览器 Mozilla Firefox v36.0.3 便携增强版

简介 Mozilla Firefox,又称:火狐浏览器,是由谋智网络开发的一款老牌的开源网页浏览器,具有体积小巧且运行速度快等特性,专为中国用户量身定制的全方位网页浏览体验。拥有标签式浏览,使上网冲浪更快&#xf…

老版资源嗅探浏览器 - 遨游浏览器稀有绿色版

“ 老版资源嗅探浏览器 - 遨游浏览器稀有绿色版” 平时在想下载网上的视频,Yama正常的操作都是用chrome自带的插件下载,这类的插件很多就不过多赘述 下载youtube、Twitter、微博...就会到在线网站下载,这样的网站我在博客写了很多~ 不知道…

linux火狐浏览器50版本,firefox 52 下载-Firefox(火狐浏览器)52版下载 v52.0.2官方版--pc6下载站...

Firefox火狐浏览器52版主要增强了Sync同步功能中发送标签页的特性,让触屏版更好支持多进程运行,之前介绍过的在非HTTPS页面上如果出现密码框则会有安全提示在52版本中已经部署,削减了对一些旧插件的支持,还优化了下载时的体验。。…

0基础学习VR全景平台篇第36篇:场景功能-导览

大家好,欢迎观看蛙色VR官方系列——后台使用课程! 本期为大家带来蛙色VR平台,场景管理—导览功能操作。 功能位置示意 一、本功能将用在哪里? 导览,指给VR漫游作品预先设置好路线,并且可以自定义路线的旋…

1、2、3类LSA解析

拓扑 需求 (1)企业核心机房,连接不同楼宇,实现不同楼宇互通; (2)企业核心机房设置为OSPF骨干区域; (3)其他办公楼宇为非骨干区域,通过路由器与…

excel下拉菜单创建

1)从另一个表创建下拉选项 2)通过数据有效性来设置选项

Excel2021单元格怎么做下拉菜单

环境: Win10 专业版 Excel 2021 问题描述: Excel2021单元格怎么做下拉菜单 解决方案: 1.选中要设置的这列,点击数据菜单-数据验证 2.点击数据验证 3.跳出菜单,选中序列 4.填写下拉菜单数据以,分隔 5.完成&…

【yolov5系列】yolov5目标检测的原理梳理+核心代码解析

打算写yolov5源码阅读和总结&#xff0c;已经打算了一年&#xff0c;如今已经更新到yolov8&#xff0c;只能说自己行动太慢了&#xff0c;哭泣(๑>؂<๑&#xff09;。趁着看要yolov8一起赶紧把yolov5总结总结。 一、Yolov5的网络结构 模型主要分为3部分 backbone&#x…

html 联想下拉菜单,这种Excel下拉菜单你见过吗?具有联想动态下拉菜单

之前有教过大家在Excel中制作下拉菜单功能&#xff0c;下拉菜单功能可以帮助我们在工作中快速的选择内容&#xff0c;而不需要我们手动输入&#xff0c;大大的节省了我们录入内容的时间。不过除了下拉菜单之外&#xff0c;其实&#xff0c;Excel还可以制作出“联想动态下拉菜单…

Excel表格中如何快速生成下拉菜单

Excel表格中如何快速生成下拉菜单 目录 Excel表格中如何快速生成下拉菜单 1、例如下表先手动输入前几组的“等级”类别“优良中差” ​2、同时按住 alt ↓键&#xff08;键盘下翻键&#xff09;出现下拉选项&#xff0c;点击即可。 1、例如下表先手动输入前几组的“等级”…

html单元格下拉菜单怎么做,Excel 2013如何制作下拉菜单?(excel下拉菜单怎么做?)...

我以输入男女为例&#xff0c;打开一个Excel2013&#xff0c;我们要在性别这一列中设置一个下拉菜单&#xff0c;内容为“男”“女” excel下拉菜单 选中姓名后面所对着的“性别”单元格 依次打开“数据”—“有效性” excel下拉菜单 在新出来的对话框中&#xff0c;打开“设置…

excel实用技巧:如何构建多级下拉菜单

使用数据有效性制作下拉菜单对大多数小伙伴来说都不陌生&#xff0c;但说到二级和三级下拉菜单大家可能就不是那么熟悉了。 什么是二级和三级下拉菜单呢&#xff1f;举个例子&#xff0c;在一个单元格选择某个省后&#xff0c;第二个单元格选项只能出现该省份所属的市&#xff…

自定义Excel下拉菜单

http://aleven.blog.51cto.com/519381/469832 今天无意中学会了Excel2007下拉列表的功能&#xff0c;在这里作一个例题巩固一下&#xff1a; 1. 在Sheet2中输入以下城市名信息&#xff1a; 2. 然后在“公式”--“名称管理器”&#xff0c;自定义名称名称&#xff1a; 新建定义…

php excel多级下拉菜单自动生成,Excel下拉菜单怎么做 多级联动+自动匹配教程

Excel一直是近年来办公室工作中的必要软件之一,这个软件功能非常强大,如果你只学会了皮毛那就有些可惜了,而Excel隐藏了许多许多的小技巧。今天UU为大家带来的是Excel下拉菜单怎么做,其中包括多级联动+自动匹配教程,感兴趣的朋友跟随UU一起了解下吧。 首先我们来介绍下Exc…

你知道TikTok的推荐算法吗?TikTok数据分析平台哪家好?

作为当下最受欢迎的社交媒体&#xff0c;TikTok这几年的成绩大家也是有目共睹了&#xff0c;超10亿的月活加上大量活跃的年轻人&#xff0c;让无数企业和品牌为之心动。入局的人越来越多&#xff0c;想要在众多竞争者中脱颖而出&#xff0c;入局前需要了解TikTok底层逻辑和推荐…

volatile - (C语言)

volatile关键字和const一样都是一种类型修饰符&#xff0c;用它修饰过的变量表示可以被某些编译器未知的因素更改&#xff0c;比如操作系统、硬件或者是其它线程等。 该关键字是不希望被编译器优化&#xff0c;从而达到稳定访问内存的目的。 示例代码&#xff1a; #include&…

FL Studio21水果软件各个版本功能区别对比

作为音乐人&#xff0c;在电脑上进行编曲&#xff0c;混音&#xff0c;合成是家常便饭&#xff0c;而市面上大家常用的音乐编曲制作软件很多&#xff0c;小编在这里就给大家做一个推荐。 大家常听到的音乐编曲制作软件大多是Cubase、Nuendo、Pro Tools、 SONAR等等&#xff0c…

不能注册DLL/OCX:RegSvr32失败 0x5

为这个错误累死了&#xff0c;知道是权限的问题&#xff0c;但无从下手&#xff0c;查了一圏网上资料&#xff0c;包括在INNO SETUP安装配置文件中加入64位的标识之类的都做了&#xff0c;包括超级用户ADMINISTRATOR登录 &#xff0c;还有设置用户权利&#xff0c;没想到仅仅关…

牛客网刷题总结(1.有序序列判断,2.获得月份天数,3.矩阵相等判定,4.矩阵转换,5.井字棋判断输赢,6.递归进行进制转化)

&#x1f495;"痛苦难以避免&#xff0c;而磨难可以选择。"-->村上春树&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;数据在内存中的存储 目录 1.有序序列判断 题目描述&#xff1a; 分析过程&#xff1a; 代码实现&#xff1a; 2.获得月份…

rundll32.exe和regsvr32.exe

众所周知&#xff0c;DLL文件是不能独自运行的&#xff0c;需要被进程加载到其地址空间后才能执行。 那怎么运行一个DLL呢? 答案&#xff1a;可以借助Windows所提供的rundll32.exe或regsvr32.exe。 rundll32.exe 通过rundll32.exe可以直接调用DLL的导出函数来执行功能。 命令…