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

news2024/11/8 2:47:46

任务目标:在使用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 tokenizer
        self.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 和 labels
    input_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 和 labels
    return {'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 # 初始化训练损失为0
    train_acc = 0 # 初始化训练准确率为0
    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() # 清空梯度
        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 # 初始化测试损失为0
    test_acc = 0 # 初始化测试准确率为0
    with 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个 epoch
    train_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 embedding
    sign_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 # 初始化训练损失为0
    train_acc = 0 # 初始化训练准确率为0
    tokenizer = 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_ids
        perturbed_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 = 0
    test_acc = 0
    with 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 # 初始化训练损失为0
    train_acc = 0 # 初始化训练准确率为0
    tokenizer = 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_ids
        perturbed_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 = 0
    test_acc = 0
    with 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://www.coloradmin.cn/o/624580.html

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

相关文章

2023年牛客网最新版大厂Java八股文面试题总结(覆盖所有面试题考点)

程序员真的是需要将终生学习贯彻到底的职业,一旦停止学习,离被淘汰,也就不远了。 金九银十跳槽季,这是一个千年不变的话题,每到这个时候,很多人都会临阵磨枪,相信不快也光。于是,大…

多业务线下,IT企业如何应对市场经济下行危机?

多业务线下,IT企业如何应对市场经济下行危机? 市场经济下行就像是一辆行驶的车子遇到了坡道,速度开始变慢甚至停下来。在这个情况下,经济的增长变得较为缓慢,消费减少,投资减少,也对企业会带来…

运筹说 第25期 | 对偶理论经典例题讲解

对偶理论是研究线性规划中原始问题与对偶问题之间关系的理论,主要研究经济学中的相互确定关系,涉及到经济学的诸多方面。产出与成本的对偶、效用与支出的对偶,是经济学中典型的对偶关系。 对偶理论中最有力的武器是影子价格,影子…

【MySQL】主从复制部署

文章目录 概述SQL数据库的三大范式 主从复制技术产生原因主从形式原理图主节点 binary log dump 线程从节点I/O线程作用从节点SQL线程作用 复制过程复制模式异步模式(mysql async-mode)半同步模式(mysql semi-sync)全同步模式 复制机制binlog记录模式GTI…

android frida检测绕过

Frida检测是一种常见的安卓逆向技术,常用于防止应用程序被反向工程。如果您遇到了Frida检测,您可以尝试以下方法来绕过它: 使用Magisk Hide模块:Magisk是一个强大的安卓root工具,它附带了一个Magisk Hide模块&#xff…

二阳大规模来袭,热图地图分析新冠疫情期间的高发地点,掌握防控重点!

一、概述 最近,新冠疫情似乎又要“卷土重来”... 身边逐渐有人传来“二阳”或者“三羊”的消息,网上相关的讨论和报道也变得越来越多。 据「钟南山院士」在大湾区科学论坛上的发言,预测模型seirs显示,第二波新冠疫情已于4月中旬开…

当数据汇聚成海,Excel 表成为我们的航海图,如何在茫茫数据中找到目标?——Excel 表中某个范围内的单元格遍历思路

本篇博客会讲解力扣“2194. Excel 表中某个范围内的单元格”的解题思路,这是题目链接。 先来审题: 以下是输出示例和提示: 这道题的解题思路是:模拟,遍历每一列,某一列遍历完后遍历下一列。 下面我们需…

爆肝整理,性能测试-全链路压测与普通压测区别总结,进阶高级测试...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 抛出一个问题&…

Shell脚本:expect脚本免交互

Shell脚本:expect脚本免交互 expect脚本免交互 一、免交互基本概述:1.交互与免交互的区别:2.格式:3.通过read实现免交互:4.通过cat实现查看和重定向:5.变量替换: 二、expect安装:1.…

Docker Registry部署

之前执行 docker pull的命令都是从 docker hub上拉取的,是docker 公共仓库,如果在公司中使用docker,我们不可能把自己的镜像上传到公共仓库,这个时候就需要一个自己的仓库(私有仓库),在局域网之…

usb 驱动

usb 驱动 usb 的基本概念 这个忽略, 基本上usb 是啥都知道 usb 的拓扑结构 usb 是一种主从结构的系统 usb主机由usb主控之器(Host Controller)和根集线器(Root Hub) 构成 usb 主控制器: 主要负责数据处理(就是我…

微信怎么批量自动添加好友?

如何批量加客户资源到微信,怎么加微信好友,这个基本上熟悉的人都会知道。 实际上,你知道所有添加微信好友的方式吗?或者说,你知道如何批量加客户微信吗? 比如说在一定时间内,把你所有的客户资…

AIGC文本生成智能应用(ChatGPT)提示工程师技巧

我是荔园微风,作为一名在IT界整整25年的老兵,今天来看一下AIGC文本生成智能提示工程师技巧。 当你在使用类似于ChatGPT这样的AIGC文本生成智能应用时,有没有想过,你所问的问题中的每一个词语对AIGC文本生成智能应用给你的回答的好…

记忆的助记器|如何使用联想记忆法

联想记忆法,也被称为记忆宫殿技术,已经使用了数千年,其记录可以追溯到古罗马和希腊。虽然现代第二大脑工具抢走了它的一些人气,但联想记忆法仍然是最有效的记忆方法之一。下面是你为什么需要在你的知识管理系统中使用它。 什么是…

比较快捷的设置第三方app 有系统权限和签名

1. app 里面添加 android:sharedUserId"android.uid.system" 2.编译出app.apk ,拷贝到源码目录 里面有很多签名文件 build\target\product\security 3. 拷贝签名工具到这个目录 签名工具原目录 out/host/linux-x86/framework/signapk.jar 4.拷贝需要的库文件到这…

Retry重试机制(五十)

当新的世界出现,请立即向他奔去 上一章简单介绍了Melody 监控(四十九), 如果没有看过,请观看上一章 本章节文章参考: https://juejin.cn/post/7234107489390116925 https://blog.csdn.net/hongyuan19/article/details/118995696 一. 重试 一.一 什么是重试 重…

身份证信息查看 案例

7-14位:出生年、月、日 17位:性别(奇数男性、偶数女性) 人物信息为:出生年月日:XXXX年X月X日 性别为:男/女 /** Copyright (c) 2017, 2023, zxy.cn All rights reserved.**/ package cn.str…

做了大半年软测,上班接触不到技术性的东西,是在浪费时间吗?

最近接到粉丝私信,苦恼目前的工作状态: 来这个公司大半年,现在主要做的是类似于淘宝的购物商城,以前也做应用系统什么的,可是感觉公司的软件测试岗位都是不着边的,因为做的都是功能测试,来了这么…

让你的文字更出色:编辑和校对的有效策略

要让你的文字更出色,掌握编辑和校对的有效策略至关重要。 以下是一些建议,帮助你提高编辑和校对水平,让你的作品更具吸引力和说服力。 1.分阶段进行编辑和校对 编辑和校对最好分阶段进行。先进行大局观的编辑,关注文章结构、逻辑…

Wildfly配置Datasources

配置Datasources前,需要先配置对应的JDBC驱动,配置方法,可以参考: Wildfly配置mysql8.0的JDBC驱动 之后,配置对应的Datasources。配置方法有两种,一种是通过管理后台配置,一种是通过手工修改配…