MiniMind

数据集分类:

  • 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'

    # 初始化tokenizer
    tokenizer = Tokenizer(models.BPE())
    tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)

    # 定义特殊token
    special_tokens = ["<unk>", "<s>", "</s>"]

    # 设置训练器并添加特殊token
    trainer = 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)

    # 训练tokenizer
    tokenizer.train_from_iterator(texts, trainer=trainer)

    # 设置解码器
    tokenizer.decoder = decoders.ByteLevel()

    # 检查特殊token的索引
    assert tokenizer.token_to_id("<unk>") == 0
    assert tokenizer.token_to_id("<s>") == 1
    assert tokenizer.token_to_id("</s>") == 2

    # 保存tokenizer
    tokenizer_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_len
        input_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.Linear
    linear_names = []
    for name, module in model.named_modules():
        if isinstance(module, cls):
            for key in keys:
                if key in name:
                    linear_names.append(name)
                    break
    return linear_names
 
target_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')

接着进行评估:
在这里插入图片描述
个人见解:这是一个很好的项目,但是个人想要依靠这个项目去做某个领域的对话模型还是没办法。应该需要预训练后表现就良好的模型作为基准模型才行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值