需要掌握:大模型原理:预训练目标-因果语言建模(CLM)-使用滑动窗口进行数据采样
介绍
为了实现高效的数据加载器,将使用PyTorch内置的Dataset类和DataLoader类。
安装
poetry add numpy==2.2
poetry add torch==2.9
实现
导入模块
from importlib.metadata import version
import tiktoken
import torch
from torch.utils.data import Dataset, DataLoader
print("查看 version 版本号:", version("tiktoken"))
print("查看所有支持的编码:", tiktoken.list_encoding_names())
定义Dataset类
该类创建对象时,传入文本、分词器、窗口大小、步长,然后对文本进行编码,根据窗口大小、步长提出 输入-目标对
class GPTDatasetV1(Dataset):
"""
GPTDatasetV1类继承自PyTorch的Dataset类,并定义了如何从数据集中提取单行数据。
每行数据包含多个词元ID(数量由max_length参数决定),这些词元ID被分配给input_chunk张量,
而target_chunk张量包含相应的目标词元ID。
"""
def __init__(self, txt, tokenizer, max_length, stride):
"""
初始化
:param txt: 文本
:param tokenizer: 分词器
:param max_length: 窗口大小
:param stride: 步长
"""
self.input_ids = []
self.target_ids = []
# 对全部文本进行分词
token_ids = tokenizer.encode(txt)
# 使用滑动窗口将文本划分为长度为max_length的重叠序列
for i in range(0, len(token_ids) - max_length, stride):
input_chunk = token_ids[i:i + max_length]
# 目标数据是将输入数据向后移动一个位置得到的
target_chunk = token_ids[i + 1:i + max_length + 1]
self.input_ids.append(torch.tensor(input_chunk))
self.target_ids.append(torch.tensor(target_chunk))
def __len__(self):
"""
返回数据集的总行数,否则传入数据加载器报错
:return:
"""
return len(self.input_ids)
def __getitem__(self, idx):
"""
返回指定索引的数据,必须实现,否则传入数据加载器报错
:param idx:
:return:
"""
return self.input_ids[idx], self.target_ids[idx]
创建数据加载器函数
定义函数 create_dataloader_v1,实现下面功能:
- 初始化分词器
- 创建数据集
- 创建、并返回数据加载器
def create_dataloader_v1(txt, batch_size=4, max_length=256, stride=128, shuffle=True, drop_last=True, num_workers=0):
"""
数据加载器,用于批量生成输入-目标对
:param txt:
:param batch_size:
:param max_length:
:param stride:
:param shuffle:
:param drop_last:
:param num_workers:
:return:
"""
# 初始化分词器,选择对应模型的编码,不同模型编码规则不同
tokenizer = tiktoken.get_encoding("gpt2")
# 创建数据集
dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
# 创建数据加载器
dataloader = DataLoader(
dataset,
batch_size=batch_size,
shuffle=shuffle,
drop_last=drop_last, # 如果drop_last为True且批次大小小于指定的batch_size,则会删除最后一批,以防止在训练期间出现损失剧增
num_workers=num_workers # 用于预处理的CPU进程数
)
return dataloader
执行
- 读取文本
- 调用数据加载器函数
- 打印前几张数据
# 读取文本
with open("the-verdict.txt", "r", encoding="utf-8") as f:
raw_text = f.read()
dataloader = create_dataloader_v1(raw_text, batch_size=1, max_length=4, stride=1, shuffle=False)
data_iter = iter(dataloader)
first_batch = next(data_iter)
print("第1组输入目标对:", first_batch)
second_batch = next(data_iter)
print("第2组输入目标对:", second_batch)
执行结果:
查看 version 版本号: 0.12.0
查看所有支持的编码: ['gpt2', 'r50k_base', 'p50k_base', 'p50k_edit', 'cl100k_base', 'o200k_base', 'o200k_harmony']
第1组输入目标对: [tensor([[ 40, 367, 2885, 1464]]), tensor([[ 367, 2885, 1464, 1807]])]
第2组输入目标对: [tensor([[ 367, 2885, 1464, 1807]]), tensor([[2885, 1464, 1807, 3619]])]
变量 first_batch包含两个张量:
- 第一个张量存储输入词元ID
- 第二个张量存储目标词元ID
由于 max_length 被设置为 4,因此这两个张量各自包含 4个词元ID。
注意:这里是为了简单,将输入大小设置为 4,在实际训练大语言模型时,输入大小通常 不小于 256
步长的含义
详见链接:
https://www.malaoshi.top/show_1GW2YXBPPimC.html
批次大小的含义
调用 create_dataloader_v1() 函数时,传值 batch_size=2,其他不变:
dataloader = create_dataloader_v1(raw_text, batch_size=2, max_length=4, stride=1, shuffle=False)
data_iter = iter(dataloader)
first_batch = next(data_iter)
print("第1组输入目标对:", first_batch)
second_batch = next(data_iter)
print("第2组输入目标对:", second_batch)
执行结果:
查看 version 版本号: 0.12.0
查看所有支持的编码: ['gpt2', 'r50k_base', 'p50k_base', 'p50k_edit', 'cl100k_base', 'o200k_base', 'o200k_harmony']
第1组输入目标对: [tensor([[ 40, 367, 2885, 1464],
[ 367, 2885, 1464, 1807]]), tensor([[ 367, 2885, 1464, 1807],
[2885, 1464, 1807, 3619]])]
第2组输入目标对: [tensor([[2885, 1464, 1807, 3619],
[1464, 1807, 3619, 402]]), tensor([[1464, 1807, 3619, 402],
[1807, 3619, 402, 271]])]
每次读取 两组数据