数据集分类:
- tokenizer训练集:这个数据集用于训练分词器(tokenizer),是文本处理中的一个重要步骤。它可以帮助模型更好地理解文本数据的结构。
- Pretrain数据:这是用于预训练模型的数据集,它可以帮助模型学习语言的基本结构和特征。
- SFT数据:SFT(Supervised Fine-Tuning)数据集,用于监督式微调,可以提高模型在特定任务上的性能。
- DPO数据1和DPO数据2:这两个数据集是用于训练奖励模型的,它们包含了人工标注的偏好数据,可以帮助模型优化回复质量,使其更加符合人类的偏好。
训练分词器
模型用的是BPE分词模型,BPE的核心概念是从字母开始,反复合并频率最高且相邻的两个token,直到达到目标词数。 具体可以看。
分词器训练代码:
def train_tokenizer():# 读取JSONL文件并提取文本数据def read_texts_from_jsonl(file_path):with open(file_path, 'r', encoding='utf-8') as f:for line in f:data = json.loads(line)yield data['text']data_path = './dataset/tokenizer_train.jsonl'# 初始化tokenizertokenizer = Tokenizer(models.BPE())tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)# 定义特殊tokenspecial_tokens = ["<unk>", "<s>", "</s>"]# 设置训练器并添加特殊tokentrainer = trainers.BpeTrainer(vocab_size=6400,special_tokens=special_tokens, # 确保这三个token被包含show_progress=True,initial_alphabet=pre_tokenizers.ByteLevel.alphabet())# 读取文本数据texts = read_texts_from_jsonl(data_path)# 训练tokenizertokenizer.train_from_iterator(texts, trainer=trainer)# 设置解码器tokenizer.decoder = decoders.ByteLevel()# 检查特殊token的索引assert tokenizer.token_to_id("<unk>") == 0assert tokenizer.token_to_id("<s>") == 1assert tokenizer.token_to_id("</s>") == 2# 保存tokenizertokenizer_dir = "./model/minimind_tokenizer"os.makedirs(tokenizer_dir, exist_ok=True)tokenizer.save(os.path.join(tokenizer_dir, "tokenizer.json"))tokenizer.model.save("./model/minimind_tokenizer")
其中:
tokenizer = Tokenizer(models.BPE())
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
这里的 pre_tokenizers.ByteLevel 是一个预分词器(PreTokenizer),它的作用是将输入的文本按照字节级别进行分割,并且将每个字节转换成一个对应的可见字符表示。 add_prefix_space=False 表示在预分词的过程中,不在每个字节前面添加空格。(加了空格可以区分单词的开始,设置为False是为了减少词典大小吧)
trainer = trainers.BpeTrainer(
vocab_size=6400,
special_tokens=special_tokens, # 确保这三个token被包含
show_progress=True,
initial_alphabet=pre_tokenizers.ByteLevel.alphabet()
)
initial_alphabet=pre_tokenizers.ByteLevel.alphabet(): 这个参数指定了初始字母表。在这里,initial_alphabet 被设置为 pre_tokenizers.ByteLevel.alphabet(),意味着初始字母表将包含所有可能的字节级别的字符。(与上面设置的pre_tokenizers.ByteLevel搭配使用)
准备预训练数据
其中,‘/dataset/mobvoi_seq_monkey_general_open_corpus.jsonl’此文件如上述所说,是一个清洗过后的数据文件,‘./data_process.py’文件的做法就是创建一个列名为text的csv文件,将jsonl文件内的text存入到csv中。
预训练
先加载分词器和模型
model, tokenizer = init_model()
def init_model():def count_parameters(model):return sum(p.numel() for p in model.parameters() if p.requires_grad)tokenizer = AutoTokenizer.from_pretrained('./model/minimind_tokenizer')model = Transformer(lm_config).to(args.device)# moe_path = '_moe' if lm_config.use_moe else ''Logger(f'LLM总参数量:{count_parameters(model) / 1e6:.3f} 百万')return model, tokenizer
加载数据
df = pd.read_csv(args.data_path)
#随机采样
df = df.sample(frac=1.0)
#自定义的数据格式
train_ds = PretrainDataset(df, tokenizer, max_length=max_seq_len)
#DistributedSampler 确保在分布式训练中每个进程只处理数据集的一部分,避免数据重复。
train_sampler = DistributedSampler(train_ds) if ddp else None
train_loader = DataLoader(train_ds,batch_size=args.batch_size,pin_memory=True,drop_last=False,shuffle=False,num_workers=args.num_workers,sampler=train_sampler
)#教师强制(Teacher Forcing)数据处理方式:
class PretrainDataset(Dataset):def __getitem__(self, index: int):#获取样本sample = self.df.iloc[index]#文本拼接开始结束标记text = f"{self.tokenizer.bos_token}{str(sample['text'])}{self.tokenizer.eos_token}"input_id = self.tokenizer(text).data['input_ids'][:self.max_length]text_len = len(input_id)# 没满最大长度的剩余部分padding_len = self.max_length - text_leninput_id = input_id + [self.padding] * padding_len# 0表示不计算损失loss_mask = [1] * text_len + [0] * padding_len#将输入序列(X)和目标序列(Y)错开一个时间步,以便模型可以学习预测下一个时间步的输出。input_id = np.array(input_id)X = np.array(input_id[:-1]).astype(np.int64)Y = np.array(input_id[1:]).astype(np.int64)loss_mask = np.array(loss_mask[1:]).astype(np.int64)return torch.from_numpy(X), torch.from_numpy(Y), torch.from_numpy(loss_mask)
开始预训练
Lora微调荔枝数据集
Lora微调模型:
#查找具有特定键(如 "wq", "wk")的 torch.nn.Linear 模块。这些模块将被选为 LoRA 适配器的目标。
def find_linear_with_keys(model, keys=["wq", "wk"]):cls = torch.nn.Linearlinear_names = []for name, module in model.named_modules():if isinstance(module, cls):for key in keys:if key in name:linear_names.append(name)breakreturn linear_namestarget_modules = find_linear_with_keys(model)
peft_config = LoraConfig(r=8,target_modules=target_modules
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
数据处理:
json格式处理为训练的格式
变为
微调
基础模型选用了官网上作者训练好的无历史对话模型
文本数据只有400多行,所以微调轮次设置为4,训练完成后保存权重如下:
这是lora微调直接保存下的权重参数,接着将微调后的权重文件与基础模型合并一下:
from safetensors.torch import load_file
import torch# 加载适配器模型的权重
adapter_state_dict = load_file('adapter_model.safetensors')# 加载基础模型的权重,这里以 rl_512.pth 为例
base_state_dict = torch.load('rl_512.pth', map_location='cuda')# 假设适配器权重文件中的键与基础模型权重文件中的键相匹配
# 你可以直接更新基础模型的权重
base_state_dict.update(adapter_state_dict)# 保存合并后的权重
torch.save(base_state_dict, 'merged_model.pth')
接着进行评估:
个人见解:这是一个很好的项目,但是个人想要依靠这个项目去做某个领域的对话模型还是没办法。应该需要预训练后表现就良好的模型作为基准模型才行。