以上代码有以下问题,分析修改:(style_tune) C:\Users\28996\Desktop\AI\persona_contrastive_finetuning>python Contrastive_Training_LM.py Map: 0%| | 0/1 [00:00<?, ? examples/s]ERROR:__main__:无法解析anchor_input_ids: 你如何看待气候变化? ERROR:__main__:无法解析positive_input_ids: 气候变化是严峻的全球危机,我们需要立即采取行动减少碳排放! ERROR:__main__:无法解析negative_input_ids: 哈哈天气什么的随便啦,不如聊聊游戏? Map: 100%|████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 13.02 examples/s] Map: 0%| | 0/1 [00:00<?, ? examples/s]ERROR:__main__:无法解析anchor_input_ids: 你如何看待气候变化? ERROR:__main__:无法解析positive_input_ids: 气候变化是严峻的全球危机,我们需要立即采取行动减少碳排放! ERROR:__main__:无法解析negative_input_ids: 哈哈天气什么的随便啦,不如聊聊游戏? Map: 100%|████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 67.37 examples/s] 训练集样本示例: {'anchor_input_ids': '你如何看待气候变化?', 'positive_input_ids': '气候变化是严峻的全球危机,我们需要立即采取行动减少碳排放!', 'negative_input_ids': '哈哈天气什么的随便啦,不如聊聊游戏?'} 验证集样本示例: {'anchor_input_ids': '你如何看待气候变化?', 'positive_input_ids': '气候变化是严峻的全球危机,我们需要立即采取行动减少碳排放!', 'negative_input_ids': '哈哈天气什么的随便啦,不如聊聊游戏?'} 0%| | 0/3 [00:00<?, ?it/s]ERROR:__main__:无法解析token IDs: 你如何看待气候变化? ERROR:__main__:无法解析token IDs: 气候变化是严峻的全球危机,我们需要立即采取行动减少碳排放! ERROR:__main__:无法解析token IDs: 哈哈天气什么的随便啦,不如聊聊游戏? You're using a Qwen2TokenizerFast tokenizer. Please note that with a fast tokenizer, using the `__call__` method is faster than using a method to encode the text followed by a call to the `pad` method to get a padded encoding. Traceback (most recent call last): File "C:\Users\28996\Desktop\AI\persona_contrastive_finetuning\Contrastive_Training_LM.py", line 281, in <module> trainer.train() File "C:\Users\28996\miniconda3\envs\style_tune\lib\site-packages\transformers\trainer.py", line 2171, in train return inner_training_loop( File "C:\Users\28996\miniconda3\envs\style_tune\lib\site-packages\transformers\trainer.py", line 2480, in _inner_training_loop batch_samples, num_items_in_batch = self.get_batch_samples(epoch_iterator, num_batches) File "C:\Users\28996\miniconda3\envs\style_tune\lib\site-packages\transformers\trainer.py", line 5156, in get_batch_samples batch_samples += [next(epoch_iterator)] File "C:\Users\28996\miniconda3\envs\style_tune\lib\site-packages\accelerate\data_loader.py", line 567, in __iter__ current_batch = next(dataloader_iter) File "C:\Users\28996\miniconda3\envs\style_tune\lib\site-packages\torch\utils\data\dataloader.py", line 701, in __next__ data = self._next_data() File "C:\Users\28996\miniconda3\envs\style_tune\lib\site-packages\torch\utils\data\dataloader.py", line 757, in _next_data data = self._dataset_fetcher.fetch(index) # may raise StopIteration File "C:\Users\28996\miniconda3\envs\style_tune\lib\site-packages\torch\utils\data\_utils\fetch.py", line 55, in fetch return self.collate_fn(data) File "C:\Users\28996\Desktop\AI\persona_contrastive_finetuning\Contrastive_Training_LM.py", line 96, in __call__ "positive_attention_mask": create_to_attention_mask(batch_positive["input_ids"]), NameError: name 'create_to_attention_mask' is not defined. Did you mean: 'create_attention_mask'? 0%| | 0/3 [00:00<?, ?it/s]
时间: 2025-07-20 11:14:40 浏览: 13
<think>我们遇到了两个问题:
1. 数据集中的字段(如anchor_input_ids)存储的是原始文本字符串,而不是token IDs列表。之前的转换函数尝试将字符串解析为整数列表,但显然这些字符串是自然语言文本,无法解析为整数列表。
2. 在数据收集器中,有一个拼写错误:`create_to_attention_mask` 应该是 `create_attention_mask`。
解决方案:
1. 我们需要重新设计数据加载部分:在数据集中,应该存储的是已经经过分词器编码的token IDs(整数列表),而不是原始文本。如果当前数据集存储的是原始文本,那么我们需要在加载数据集后使用分词器进行编码。
2. 修复数据收集器中的拼写错误。
步骤:
1. 修改数据集加载函数,使其能够将原始文本编码为token IDs。假设我们的数据集中的字段是原始文本(如"你如何看待气候变化?"),那么我们需要使用分词器将这些文本转换为input_ids。
2. 在数据集中添加一个预处理步骤,将文本字段编码为token IDs。注意:由于我们的数据集很小(示例中只有1个样本),我们可以直接使用map函数进行编码。
3. 修复数据收集器中的函数名。
注意:为了避免混淆,我们将数据集中的字段名改为表示文本的字段名,例如:
原始数据集可能有:{"anchor": "文本1", "positive": "文本2", "negative": "文本3"}
我们需要将其编码为:{"anchor_input_ids": [1,2,3], "positive_input_ids": [4,5,6], "negative_input_ids": [7,8,9]}
如果当前数据集已经是这样存储的(但存储的是文本而不是token IDs),那么我们需要重新处理数据集。
修改后的代码:</think>### 问题分析
错误信息揭示了两个关键问题:
1. **数据集格式错误**:
- 数据集中的 `anchor_input_ids`、`positive_input_ids` 和 `negative_input_ids` 字段存储的是原始文本(如"你如何看待气候变化?")
- 而代码期望的是分词后的token ID列表(如 `[101, 2043, 2003, ...]`)
2. **代码拼写错误**:
- `create_to_attention_mask` 应该是 `create_attention_mask`(缺少字母 'a')
### 解决方案
需要彻底重构数据处理流程,确保:
1. 数据集包含正确的token ID列表
2. 修复代码中的拼写错误
3. 添加健壮的数据验证机制
以下是完整的修复代码:
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
PreTrainedTokenizerBase
)
from transformers.tokenization_utils_base import PreTrainedTokenizerBase
from transformers.utils import PaddingStrategy
from datasets import load_dataset, Dataset
from typing import Any, Dict, List, Optional, Tuple, Union
import logging
from dataclasses import dataclass
import json
import ast
import numpy as np
# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def tokenize_function(examples, tokenizer, max_length=512):
"""将文本转换为token IDs"""
tokenized = {}
# 对每个字段进行分词
for key in ['anchor', 'positive', 'negative']:
if key in examples:
# 使用分词器处理文本
result = tokenizer(
examples[key],
max_length=max_length,
truncation=True,
padding=False, # 稍后填充
return_tensors=None # 返回Python列表
)
tokenized[f"{key}_input_ids"] = result["input_ids"]
return tokenized
@dataclass
class ContrastiveDataCollator:
"""数据收集器,用于处理对比学习的三元组数据"""
tokenizer: PreTrainedTokenizerBase
padding: Union[bool, str, PaddingStrategy] = True
max_length: Optional[int] = None
pad_to_multiple_of: Optional[int] = None
return_tensors: str = "pt"
def __call__(self, features: List[Dict[str, Any]]) -> Dict[str, torch.Tensor]:
# 验证特征格式
required_keys = ['anchor_input_ids', 'positive_input_ids', 'negative_input_ids']
for key in required_keys:
if key not in features[0]:
raise ValueError(f"数据集缺少必需字段: {key}")
# 分离出三元组的各个部分
anchor_features = [{"input_ids": f["anchor_input_ids"]} for f in features]
positive_features = [{"input_ids": f["positive_input_ids"]} for f in features]
negative_features = [{"input_ids": f["negative_input_ids"]} for f in features]
# 对每个部分分别进行填充
batch_anchor = self.tokenizer.pad(
anchor_features,
padding=self.padding,
max_length=self.max_length,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors=self.return_tensors,
)
batch_positive = self.tokenizer.pad(
positive_features,
padding=self.padding,
max_length=self.max_length,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors=self.return_tensors,
)
batch_negative = self.tokenizer.pad(
negative_features,
padding=self.padding,
max_length=self.max_length,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors=self.return_tensors,
)
# 创建注意力掩码
def create_attention_mask(input_ids):
return (input_ids != self.tokenizer.pad_token_id).int()
return {
"anchor_input_ids": batch_anchor["input_ids"],
"anchor_attention_mask": create_attention_mask(batch_anchor["input_ids"]),
"positive_input_ids": batch_positive["input_ids"],
"positive_attention_mask": create_attention_mask(batch_positive["input_ids"]), # 修复拼写错误
"negative_input_ids": batch_negative["input_ids"],
"negative_attention_mask": create_attention_mask(batch_negative["input_ids"]),
}
class ContrastiveTrainer(Trainer):
"""自定义训练器,实现对比学习损失"""
def __init__(self, *args, contrastive_config=None, **kwargs):
# 修复:使用新的参数名代替将被弃用的tokenizer参数
if "tokenizer" in kwargs:
kwargs["data_collator"] = kwargs.pop("tokenizer")
super().__init__(*args, **kwargs)
if contrastive_config is None:
contrastive_config = {}
# 设置默认值并验证配置
self.temperature = contrastive_config.get("temperature", 0.07)
self.margin = contrastive_config.get("margin", 0.3)
self.contrastive_weight = contrastive_config.get("weight", 0.8)
self.repr_layer = contrastive_config.get("repr_layer", -1)
# 验证必要参数
if not hasattr(self.model.config, "output_hidden_states") or not self.model.config.output_hidden_states:
raise ValueError("模型必须设置output_hidden_states=True以获取隐藏状态")
self.cross_entropy = nn.CrossEntropyLoss()
def compute_contrastive_loss(self, anchor_emb, pos_emb, neg_emb):
"""计算对比损失:结合InfoNCE和三元组损失"""
# 计算余弦相似度
pos_sim = F.cosine_similarity(anchor_emb, pos_emb)
neg_sim = F.cosine_similarity(anchor_emb, neg_emb)
# InfoNCE损失
numerator = torch.exp(pos_sim / self.temperature)
denominator = numerator + torch.exp(neg_sim / self.temperature)
info_nce_loss = -torch.log(numerator / (denominator + 1e-8)).mean()
# 三元组损失
triplet_loss = F.relu(neg_sim - pos_sim + self.margin).mean()
# 加权组合
return info_nce_loss + triplet_loss
def get_sequence_representation(self, outputs, attention_mask):
"""获取序列表示(取最后一个非填充token的隐藏状态)"""
# 获取指定层的隐藏状态
hidden_states = outputs.hidden_states[self.repr_layer]
# 获取每个序列的最后一个非填充token
seq_lengths = attention_mask.sum(dim=1) - 1
batch_indices = torch.arange(hidden_states.size(0))
return hidden_states[batch_indices, seq_lengths]
def compute_loss(self, model, inputs, return_outputs=False):
"""计算总损失:语言模型损失 + 对比损失"""
# 提取输入
anchor_ids = inputs["anchor_input_ids"]
anchor_mask = inputs["anchor_attention_mask"]
positive_ids = inputs["positive_input_ids"]
positive_mask = inputs["positive_attention_mask"]
negative_ids = inputs["negative_input_ids"]
negative_mask = inputs["negative_attention_mask"]
# 前向传播获取隐藏状态
def get_embeddings(input_ids, attention_mask):
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
output_hidden_states=True,
return_dict=True
)
return self.get_sequence_representation(outputs, attention_mask)
# 获取三元组的嵌入表示
anchor_emb = get_embeddings(anchor_ids, anchor_mask)
pos_emb = get_embeddings(positive_ids, positive_mask)
neg_emb = get_embeddings(negative_ids, negative_mask)
# 计算对比损失
cl_loss = self.compute_contrastive_loss(anchor_emb, pos_emb, neg_emb)
cl_loss = cl_loss * self.contrastive_weight
# 计算语言建模损失(仅针对positive回复)
lm_labels = positive_ids.clone()
lm_labels[lm_labels == self.tokenizer.pad_token_id] = -100
lm_outputs = model(
input_ids=positive_ids,
attention_mask=positive_mask,
labels=lm_labels
)
lm_loss = lm_outputs.loss
# 总损失 = LM损失 + 对比损失
total_loss = lm_loss + cl_loss
# 记录自定义指标
self.log({"cl_loss": cl_loss.item(), "lm_loss": lm_loss.item()})
return (total_loss, lm_outputs) if return_outputs else total_loss
# ================ 主程序 ================ #
if __name__ == "__main__":
# 加载模型和分词器
model = AutoModelForCausalLM.from_pretrained(
"model/Qwen/Qwen1.5-1.8B",
output_hidden_states=True, # 必须设置以获取隐藏状态
return_dict_in_generate=True # 修复隐藏状态警告
)
tokenizer = AutoTokenizer.from_pretrained("model/Qwen/Qwen1.5-1.8B")
tokenizer.pad_token = tokenizer.eos_token # 设置填充token
# 加载数据集并正确分词
def load_and_tokenize_dataset(file_path, tokenizer):
"""加载数据集并进行分词处理"""
# 加载原始数据集
raw_dataset = load_dataset('json', data_files=file_path)['train']
# 验证原始数据集格式
required_keys = ['anchor', 'positive', 'negative']
if not all(key in raw_dataset[0] for key in required_keys):
raise ValueError(f"数据集缺少必需字段: {required_keys}")
# 将数据集转换为PyArrow格式以提高性能
dataset = Dataset.from_dict({
'anchor': [ex['anchor'] for ex in raw_dataset],
'positive': [ex['positive'] for ex in raw_dataset],
'negative': [ex['negative'] for ex in raw_dataset]
})
# 应用分词函数
tokenized_dataset = dataset.map(
lambda ex: tokenize_function(ex, tokenizer, max_length=512),
batched=True,
remove_columns=['anchor', 'positive', 'negative'] # 移除原始文本列
)
return tokenized_dataset
# 加载训练集和验证集
train_dataset = load_and_tokenize_dataset('data/processed/train_style_triplets.json', tokenizer)
val_dataset = load_and_tokenize_dataset('data/processed/val_style_triplets.json', tokenizer)
# 验证数据集格式
print("训练集样本示例:", train_dataset[0])
print("验证集样本示例:", val_dataset[0])
# 确保数据类型正确
assert isinstance(train_dataset[0]['anchor_input_ids'], list), "anchor_input_ids 应为列表"
assert isinstance(train_dataset[0]['anchor_input_ids'][0], int), "token IDs 应为整数"
# 训练参数配置
training_args = TrainingArguments(
output_dir="./model/lora_adapter",
per_device_train_batch_size=4,
num_train_epochs=3,
learning_rate=2e-4,
logging_steps=50,
save_steps=500,
fp16=True,
gradient_accumulation_steps=4,
report_to="none", # 禁用默认日志
remove_unused_columns=False, # 保留自定义列
)
# 对比学习配置
contrastive_config = {
"temperature": 0.07, # 温度参数
"margin": 0.3, # 三元组损失边界
"weight": 0.8, # 对比损失权重
"repr_layer": -1 # 使用最后一层隐藏状态
}
# 初始化数据收集器
data_collator = ContrastiveDataCollator(
tokenizer=tokenizer,
max_length=512,
padding="max_length"
)
# 初始化训练器
trainer = ContrastiveTrainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=train_dataset,
eval_dataset=val_dataset,
contrastive_config=contrastive_config
)
# 开始训练
trainer.train()
# 评估模型
eval_results = trainer.evaluate()
print("评估结果:", eval_results)
```
### 关键修复点解释
1. **重构数据加载流程**:
```python
def tokenize_function(examples, tokenizer, max_length=512):
for key in ['anchor', 'positive', 'negative']:
result = tokenizer(examples[key], ...)
tokenized[f"{key}_input_ids"] = result["input_ids"]
```
- 添加专门的分词函数,将原始文本转换为token ID列表
- 使用Hugging Face分词器确保正确性
2. **数据集格式验证**:
```python
# 验证原始数据集格式
required_keys = ['anchor', 'positive', 'negative']
if not all(key in raw_dataset[0] for key in required_keys):
raise ValueError(f"数据集缺少必需字段: {required_keys}")
# 确保数据类型正确
assert isinstance(train_dataset[0]['anchor_input_ids'], list)
assert isinstance(train_dataset[0]['anchor_input_ids'][0], int)
```
- 在多个层级验证数据格式
- 添加断言确保最终token IDs是整数列表
3. **修复拼写错误**:
```python
# 修复前
"positive_attention_mask": create_to_attention_mask(...)
# 修复后
"positive_attention_mask": create_attention_mask(...)
```
- 修正了函数名拼写错误
- 确保注意力掩码正确生成
### 数据集格式要求
原始数据集JSON文件应包含以下格式的数据:
```json
[
{
"anchor": "你如何看待气候变化?",
"positive": "气候变化是严峻的全球危机,我们需要立即采取行动减少碳排放!",
"negative": "哈哈天气什么的随便啦,不如聊聊游戏?"
},
{
"anchor": "另一个锚点文本",
"positive": "对应的正面回复",
"negative": "对应的负面回复"
}
]
```
### 数据处理流程优化
1. **高效分词处理**:
```python
tokenized_dataset = dataset.map(
lambda ex: tokenize_function(ex, tokenizer),
batched=True,
remove_columns=['anchor', 'positive', 'negative']
)
```
- 使用批处理提高分词效率
- 移除原始文本列减少内存占用
2. **数据集缓存**:
```python
# 在map函数中添加
cache_file_name=f"tokenized_cache_{os.path.basename(file_path)}.arrow"
```
- 缓存分词结果加速后续加载
- 避免每次运行重复分词
3. **内存优化**:
```python
# 使用内存映射文件处理大型数据集
dataset = dataset.flatten_indices()
```
阅读全文
相关推荐

















