趋近智
大师班
虽然从多样化的数据混合中随机打乱和采样是一种常见做法,但它从一开始就将所有数据点视为信息量均等。思考一下人类是如何学习的:我们通常从更简单的知识点开始,逐步提升至更复杂的知识。我们不会在理解基础算术之前直接跳到高等微积分。课程学习(CL)将类似原则应用于训练机器学习模型,包括大型语言模型。课程学习不是以纯随机顺序从整个数据集中呈现数据点,而是引入一种结构,通常在训练过程中从“简单”示例转向“困难”示例。
基本思想是,从简单示例开始可以帮助模型构建根本表示,并避免在训练早期陷入不良局部最小值。这种初步的立足点可能使模型后续更容易从更复杂或带噪声的数据中学习。在大型语言模型预训练的背景下,“简单”与“困难”的定义可以有多种形式。
对于大型语言模型而言,什么构成“简单”或“困难”的示例?这并非总是直截了当,但常见方法包括:
实现课程学习需要修改数据加载或采样过程。采样器需要了解训练进度(例如,当前周期或步骤),并根据定义的课程计划选择数据,而不是从数据集中均匀采样。
一个简单方法可能涉及根据难度指标(如序列长度)将数据分桶,并控制在不同训练阶段从哪些桶中进行主动采样。
考虑一个通过自定义PyTorch Sampler实现的基础的基于长度的课程。此示例体现了核心逻辑,并非生产级实现。
import torch
from torch.utils.data import Sampler
import numpy as np
class LengthBasedCurriculumSampler(Sampler):
def __init__(self,
data_lengths,
batch_size,
start_percentile=0.1,
end_percentile=1.0,
total_steps=10000):
"""
根据训练中序列长度百分位数递增的方式采样批次。
Args:
data_lengths (list or np.array): 每个数据样本的长度列表。
batch_size (int): 每个批次的大小。
start_percentile (float): 初始长度百分位阈值 (0.0 到 1.0)。
end_percentile (float): 最终长度百分位阈值 (0.0 到 1.0)。
total_steps (int): 课程学习进行的总训练步数。
"""
self.data_lengths = np.array(data_lengths)
self.indices = np.argsort(self.data_lengths) # 按长度排序的索引
self.sorted_lengths = self.data_lengths[self.indices]
self.batch_size = batch_size
self.start_percentile = start_percentile
self.end_percentile = end_percentile
self.total_steps = total_steps
self.current_step = 0
self.num_samples = len(data_lengths)
# 根据百分位数计算初始和最终索引
self.start_idx = int(self.start_percentile * self.num_samples)
self.final_max_idx = int(self.end_percentile * self.num_samples)
def get_current_max_index(self):
# 在总步数内线性增加允许的最大索引
progress = min(1.0, self.current_step / self.total_steps)
increase = progress * (self.final_max_idx - self.start_idx)
current_max_idx = int(self.start_idx + increase)
# 确保我们总是包含至少起始百分位数的数据
return max(self.start_idx, current_max_idx)
def __iter__(self):
current_max_idx = self.get_current_max_index()
# 符合条件的索引是达到当前最大长度阈值的索引
eligible_indices = self.indices[:current_max_idx]
if len(eligible_indices) < self.batch_size:
# 处理符合条件的数据过小的情况(例如,早期步骤)
# 可能会重复样本或使用更小的批次
eligible_indices = np.random.choice(
eligible_indices, size=self.batch_size, replace=True
)
else:
# 为当前周期/步骤打乱符合条件的索引
np.random.shuffle(eligible_indices)
# 生成批次(简化的批处理逻辑)
num_batches = 0
for i in range(0, len(eligible_indices), self.batch_size):
batch_indices = eligible_indices[i : i + self.batch_size]
# 为简单起见,丢弃最后一个不完整的批次
if len(batch_indices) == self.batch_size:
yield batch_indices.tolist()
num_batches += 1
# 在生成此迭代的所有批次后增加步骤
# 在真实的训练器中,步骤更新会发生在每个优化器步骤
# 此简化版本在每次__iter__调用时增加一次
# 大致的步骤增量
self.current_step += num_batches
def __len__(self):
# 每个周期/迭代的估计批次数量
current_max_idx = self.get_current_max_index()
num_eligible = len(self.indices[:current_max_idx])
return num_eligible // self.batch_size
# --- 使用示例 ---
# 假设 `dataset` 是您的PyTorch数据集对象
# 假设 `lengths` 是一个列表,包含 `dataset` 中每个项目的长度
# lengths = [len(item) for item in dataset] # 预计算长度
#
# total_training_steps = 50000 # 示例总步数
# batch_size = 32
#
# sampler = LengthBasedCurriculumSampler(
# lengths, batch_size, total_steps=total_training_steps
# )
# dataloader = torch.utils.data.DataLoader(
# dataset, batch_size=None, sampler=sampler # 采样器使用时 batch_size=None
# )
#
# # 训练循环将使用此数据加载器
# # for epoch in range(num_epochs):
# # for batch in dataloader:
# # # 训练步骤...
# # # 如果需要,更新采样器的内部步骤,
# # # 尽管此示例是每次迭代更新
此采样器在初始化时一次性按长度排序数据。在每次迭代(通常对应一个周期)中,它根据当前的训练进度(current_step)确定允许的最大数据索引。然后它打乱并生成仅包含达到该长度百分位数的数据点的批次。get_current_max_index函数定义了课程的节奏。
课程学习的潜在优点包括:
然而,课程学习也带来挑战:
虽然基于细粒度难度指标的显式复杂课程并非总是训练大型语言模型的默认设置(对于大型语言模型,复杂的数据混合加权常因其可扩展性和经验成功而被偏好),但课程学习的核心思想常指导这些混合如何设计和潜在排序。例如,一种多阶段训练过程,其中模型首先在更干净的数据上训练,然后再接触完整、带噪声的数据集,可视为一种粗粒度课程形式。了解课程学习的原则提供另一种工具,用于优化大型语言模型训练这一要求较高的过程。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造