searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

基于Lora的SFT简述

2025-02-06 03:02:11
4
0

## 一、引言

在自然语言处理领域,大型预训练语言模型展现出了强大的能力,但直接在特定任务上应用这些模型往往效果不佳。监督微调(SFT)是一种有效的方法,它通过在特定的标注数据集上对预训练模型进行微调,使模型能够更好地适应具体任务。而 LoRA 作为一种轻量级的微调技术,通过低秩分解的方式,显著减少了需要训练的参数数量,降低了计算资源的需求,同时保持了与全量微调相近的性能。Hugging Face 提供了丰富的工具和模型库,方便我们进行 LoRA 微调。然而,在国内网络环境下,需要解决一些网络访问和数据下载的问题,下面将详细介绍整个 SFT 过程。

## 二、环境准备

### 2.1 网络设置

由于 Hugging Face 的部分资源在国内访问受限,我们可以使用国内的镜像源来加速模型和数据集的下载。例如,使用清华大学的镜像源:
```
export HF_ENDPOINT=hf-mirror
```

### 2.2 安装依赖库

我们需要安装一些必要的 Python 库,包括 `transformers`、`peft`(用于 LoRA 实现)、`datasets` 等。可以使用以下命令进行安装:

```
pip install transformers peft datasets evaluate accelerate
```

## 三、数据处理

### 3.1 数据集选择


这里我们选择一个简单的文本分类数据集,例如 `imdb` 影评数据集。使用 `datasets` 库进行加载:
```
from datasets import load_dataset

# 加载数据集
dataset = load_dataset("imdb")
```

### 3.2 数据预处理

对数据集进行预处理,包括分词等操作。这里我们使用 `AutoTokenizer` 进行分词:

```
from transformers import AutoTokenizer

# 选择合适的分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def preprocess_function(examples):
    return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=128)

# 对数据集进行预处理
tokenized_dataset = dataset.map(preprocess_function, batched=True)
```

## 四、模型选择

我们选择 `bert-base-uncased` 作为基础预训练模型:
```
from transformers import AutoModelForSequenceClassification

# 加载预训练模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
```

## 五、LoRA 配置

### 5.1 引入 LoRA

使用 `peft` 库来配置 LoRA:

```
from peft import LoraConfig, get_peft_model

# 配置 LoRA
config = LoraConfig(
    r=8,  # 低秩矩阵的秩
    lora_alpha=16,
    target_modules=["query", "key", "value"],  # 要应用 LoRA 的模块
    lora_dropout=0.1,
    bias="none",
    task_type="SEQ_CLS"
)

# 将 LoRA 配置应用到模型上
model = get_peft_model(model, config)
```

### 5.2 查看可训练参数

查看经过 LoRA 配置后,模型中可训练的参数数量:

```
model.print_trainable_parameters()
```

## 六、微调训练

### 6.1 训练参数配置

使用 `transformers` 库中的 `TrainingArguments` 来配置训练参数:

```
from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=64,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=10,
    evaluation_strategy="steps",
    eval_steps=50,
    save_strategy="steps",
    save_steps=100,
    load_best_model_at_end=True
)
```

### 6.2 定义评估指标


使用 `evaluate` 库定义评估指标,这里我们使用准确率:

```
import evaluate
import numpy as np

metric = evaluate.load("accuracy")

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)
```

### 6.3 创建训练器并开始训练
```
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"],
    eval_dataset=tokenized_dataset["test"],
    compute_metrics=compute_metrics
)

# 开始训练
trainer.train()
```

## 七、模型评估与保存

### 7.1 模型评估
训练完成后,对模型进行评估:

```
eval_results = trainer.evaluate()
print(f"Evaluation results: {eval_results}")
```

### 7.2 模型保存

将微调后的模型保存到本地:

```
model.save_pretrained("./fine_tuned_model")
```

## 八、RoberTa
然而,BERT 模型并非完美无缺,随着研究的深入,其一些局限性逐渐显现,例如在预训练过程中,掩码标记(\[MASK])只在预训练时出现,在微调阶段并不存在,这可能导致预训练 - 微调之间的不匹配问题。此外,NSP 任务的有效性也受到了质疑,一些研究表明该任务对模型性能的提升贡献有限。
。为了进一步提升模型性能,Facebook AI 研究团队提出了 RoBERTa(A Robustly Optimized BERT Pretraining Approach)。RoBERTa 在 BERT 的基础上进行了一系列优化和改进。
* **训练策略调整**

  * **动态掩码**:BERT 在预训练时对输入序列进行静态掩码,即在整个预训练过程中,每个序列的掩码位置是固定的。而 RoBERTa 采用动态掩码策略,在每次输入时都随机生成掩码位置,增加了训练数据的多样性,使模型能够学习到更鲁棒的语言表示。
  * **去除 NSP 任务**:由于 NSP 任务的有效性受到质疑,RoBERTa 去除了该任务,仅使用 MLM 任务进行预训练。这样可以让模型更专注于学习文本的语义信息,避免了 NSP 任务可能带来的干扰。

* **数据处理优化**

  * **更多的数据**:RoBERTa 使用了比 BERT 更多的训练数据,包括 CommonCrawl、Wikipedia 等大规模语料库。更多的数据有助于模型学习到更丰富的语言知识和模式,提升模型的泛化能力。
  * **改进的 BPE 算法**:RoBERTa 在字节对编码(Byte Pair Encoding, BPE)算法上进行了改进,能够更好地处理未登录词,提高了模型对不同文本的适应性。

```
model = RobertaForSequenceClassification.from_pretrained('roberta-base', num_labels=len(set(labels)))

# 自定义数据集类
class TextDataset(Dataset):
    def __init__(self, texts, labels, tokenizer, max_length):
        self.texts = texts
        self.labels = labels
        self.tokenizer = tokenizer
        self.max_length = max_length

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):
        text = self.texts[idx]
        label = self.labels[idx]
        encoding = self.tokenizer(text, return_tensors='pt', padding='max_length', truncation=True, max_length=self.max_length)
        return {
            'input_ids': encoding['input_ids'].flatten(),
            'attention_mask': encoding['attention_mask'].flatten(),
            'labels': torch.tensor(label, dtype=torch.long)
        }

# 创建数据集和数据加载器
train_dataset = TextDataset(train_texts, train_labels, tokenizer, max_length=128)
test_dataset = TextDataset(test_texts, test_labels, tokenizer, max_length=128)

train_dataloader = DataLoader(train_dataset, batch_size=16, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=16, shuffle=False)

# 定义训练参数
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

# 训练模型
num_epochs = 3
for epoch in range(num_epochs):
    model.train()
    total_loss = 0
    for batch in train_dataloader:
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        labels = batch['labels'].to(device)

        optimizer.zero_grad()
        outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
        loss = outputs.loss
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

    print(f'Epoch {epoch + 1}, Loss: {total_loss / len(train_dataloader)}')

# 评估模型
model.eval()
correct_predictions = 0
total_predictions = 0
with torch.no_grad():
    for batch in test_dataloader:
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        labels = batch['labels'].to(device)

        outputs = model(input_ids, attention_mask=attention_mask)
        logits = outputs.logits
        predictions = torch.argmax(logits, dim=1)
        correct_predictions += (predictions == labels).sum().item()
        total_predictions += labels.size(0)

accuracy = correct_predictions / total_predictions
print(f'Test Accuracy: {accuracy}')
```
RoBERTa 在训练策略和数据处理上的优化。动态掩码策略使得模型在训练过程中能够接触到更多样化的输入,从而学习到更鲁棒的特征表示;去除 NSP 任务让模型更专注于文本语义的理解;使用更多的数据和改进的 BPE 算法进一步提升了模型的泛化能力和对不同文本的处理能力。

## 九、展望&结论

通过本文的介绍,我们利用 Hugging Face 平台和 LoRA 技术成功完成了对预训练模型的监督微调(SFT)过程。LoRA 技术的应用大大减少了计算资源的需求,使得在普通设备上也能进行有效的模型微调。同时,Hugging Face 提供的丰富工具和库,让整个微调过程更加便捷和高效。未来,我们可以在此基础上进一步探索不同的数据集和模型,以适应更多的自然语言处理任务。

0条评论
0 / 1000
张****群
2文章数
0粉丝数
张****群
2 文章 | 0 粉丝
张****群
2文章数
0粉丝数
张****群
2 文章 | 0 粉丝
原创

基于Lora的SFT简述

2025-02-06 03:02:11
4
0

## 一、引言

在自然语言处理领域,大型预训练语言模型展现出了强大的能力,但直接在特定任务上应用这些模型往往效果不佳。监督微调(SFT)是一种有效的方法,它通过在特定的标注数据集上对预训练模型进行微调,使模型能够更好地适应具体任务。而 LoRA 作为一种轻量级的微调技术,通过低秩分解的方式,显著减少了需要训练的参数数量,降低了计算资源的需求,同时保持了与全量微调相近的性能。Hugging Face 提供了丰富的工具和模型库,方便我们进行 LoRA 微调。然而,在国内网络环境下,需要解决一些网络访问和数据下载的问题,下面将详细介绍整个 SFT 过程。

## 二、环境准备

### 2.1 网络设置

由于 Hugging Face 的部分资源在国内访问受限,我们可以使用国内的镜像源来加速模型和数据集的下载。例如,使用清华大学的镜像源:
```
export HF_ENDPOINT=hf-mirror
```

### 2.2 安装依赖库

我们需要安装一些必要的 Python 库,包括 `transformers`、`peft`(用于 LoRA 实现)、`datasets` 等。可以使用以下命令进行安装:

```
pip install transformers peft datasets evaluate accelerate
```

## 三、数据处理

### 3.1 数据集选择


这里我们选择一个简单的文本分类数据集,例如 `imdb` 影评数据集。使用 `datasets` 库进行加载:
```
from datasets import load_dataset

# 加载数据集
dataset = load_dataset("imdb")
```

### 3.2 数据预处理

对数据集进行预处理,包括分词等操作。这里我们使用 `AutoTokenizer` 进行分词:

```
from transformers import AutoTokenizer

# 选择合适的分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def preprocess_function(examples):
    return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=128)

# 对数据集进行预处理
tokenized_dataset = dataset.map(preprocess_function, batched=True)
```

## 四、模型选择

我们选择 `bert-base-uncased` 作为基础预训练模型:
```
from transformers import AutoModelForSequenceClassification

# 加载预训练模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
```

## 五、LoRA 配置

### 5.1 引入 LoRA

使用 `peft` 库来配置 LoRA:

```
from peft import LoraConfig, get_peft_model

# 配置 LoRA
config = LoraConfig(
    r=8,  # 低秩矩阵的秩
    lora_alpha=16,
    target_modules=["query", "key", "value"],  # 要应用 LoRA 的模块
    lora_dropout=0.1,
    bias="none",
    task_type="SEQ_CLS"
)

# 将 LoRA 配置应用到模型上
model = get_peft_model(model, config)
```

### 5.2 查看可训练参数

查看经过 LoRA 配置后,模型中可训练的参数数量:

```
model.print_trainable_parameters()
```

## 六、微调训练

### 6.1 训练参数配置

使用 `transformers` 库中的 `TrainingArguments` 来配置训练参数:

```
from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=64,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=10,
    evaluation_strategy="steps",
    eval_steps=50,
    save_strategy="steps",
    save_steps=100,
    load_best_model_at_end=True
)
```

### 6.2 定义评估指标


使用 `evaluate` 库定义评估指标,这里我们使用准确率:

```
import evaluate
import numpy as np

metric = evaluate.load("accuracy")

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)
```

### 6.3 创建训练器并开始训练
```
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"],
    eval_dataset=tokenized_dataset["test"],
    compute_metrics=compute_metrics
)

# 开始训练
trainer.train()
```

## 七、模型评估与保存

### 7.1 模型评估
训练完成后,对模型进行评估:

```
eval_results = trainer.evaluate()
print(f"Evaluation results: {eval_results}")
```

### 7.2 模型保存

将微调后的模型保存到本地:

```
model.save_pretrained("./fine_tuned_model")
```

## 八、RoberTa
然而,BERT 模型并非完美无缺,随着研究的深入,其一些局限性逐渐显现,例如在预训练过程中,掩码标记(\[MASK])只在预训练时出现,在微调阶段并不存在,这可能导致预训练 - 微调之间的不匹配问题。此外,NSP 任务的有效性也受到了质疑,一些研究表明该任务对模型性能的提升贡献有限。
。为了进一步提升模型性能,Facebook AI 研究团队提出了 RoBERTa(A Robustly Optimized BERT Pretraining Approach)。RoBERTa 在 BERT 的基础上进行了一系列优化和改进。
* **训练策略调整**

  * **动态掩码**:BERT 在预训练时对输入序列进行静态掩码,即在整个预训练过程中,每个序列的掩码位置是固定的。而 RoBERTa 采用动态掩码策略,在每次输入时都随机生成掩码位置,增加了训练数据的多样性,使模型能够学习到更鲁棒的语言表示。
  * **去除 NSP 任务**:由于 NSP 任务的有效性受到质疑,RoBERTa 去除了该任务,仅使用 MLM 任务进行预训练。这样可以让模型更专注于学习文本的语义信息,避免了 NSP 任务可能带来的干扰。

* **数据处理优化**

  * **更多的数据**:RoBERTa 使用了比 BERT 更多的训练数据,包括 CommonCrawl、Wikipedia 等大规模语料库。更多的数据有助于模型学习到更丰富的语言知识和模式,提升模型的泛化能力。
  * **改进的 BPE 算法**:RoBERTa 在字节对编码(Byte Pair Encoding, BPE)算法上进行了改进,能够更好地处理未登录词,提高了模型对不同文本的适应性。

```
model = RobertaForSequenceClassification.from_pretrained('roberta-base', num_labels=len(set(labels)))

# 自定义数据集类
class TextDataset(Dataset):
    def __init__(self, texts, labels, tokenizer, max_length):
        self.texts = texts
        self.labels = labels
        self.tokenizer = tokenizer
        self.max_length = max_length

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):
        text = self.texts[idx]
        label = self.labels[idx]
        encoding = self.tokenizer(text, return_tensors='pt', padding='max_length', truncation=True, max_length=self.max_length)
        return {
            'input_ids': encoding['input_ids'].flatten(),
            'attention_mask': encoding['attention_mask'].flatten(),
            'labels': torch.tensor(label, dtype=torch.long)
        }

# 创建数据集和数据加载器
train_dataset = TextDataset(train_texts, train_labels, tokenizer, max_length=128)
test_dataset = TextDataset(test_texts, test_labels, tokenizer, max_length=128)

train_dataloader = DataLoader(train_dataset, batch_size=16, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=16, shuffle=False)

# 定义训练参数
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

# 训练模型
num_epochs = 3
for epoch in range(num_epochs):
    model.train()
    total_loss = 0
    for batch in train_dataloader:
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        labels = batch['labels'].to(device)

        optimizer.zero_grad()
        outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
        loss = outputs.loss
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

    print(f'Epoch {epoch + 1}, Loss: {total_loss / len(train_dataloader)}')

# 评估模型
model.eval()
correct_predictions = 0
total_predictions = 0
with torch.no_grad():
    for batch in test_dataloader:
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        labels = batch['labels'].to(device)

        outputs = model(input_ids, attention_mask=attention_mask)
        logits = outputs.logits
        predictions = torch.argmax(logits, dim=1)
        correct_predictions += (predictions == labels).sum().item()
        total_predictions += labels.size(0)

accuracy = correct_predictions / total_predictions
print(f'Test Accuracy: {accuracy}')
```
RoBERTa 在训练策略和数据处理上的优化。动态掩码策略使得模型在训练过程中能够接触到更多样化的输入,从而学习到更鲁棒的特征表示;去除 NSP 任务让模型更专注于文本语义的理解;使用更多的数据和改进的 BPE 算法进一步提升了模型的泛化能力和对不同文本的处理能力。

## 九、展望&结论

通过本文的介绍,我们利用 Hugging Face 平台和 LoRA 技术成功完成了对预训练模型的监督微调(SFT)过程。LoRA 技术的应用大大减少了计算资源的需求,使得在普通设备上也能进行有效的模型微调。同时,Hugging Face 提供的丰富工具和库,让整个微调过程更加便捷和高效。未来,我们可以在此基础上进一步探索不同的数据集和模型,以适应更多的自然语言处理任务。

文章来自个人专栏
模型微调
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0