LLM(11):使用滑动窗口进行数据采样

接下来要创建 LLM 嵌入,目的是生成训练 LLM 所需的 input-target 对。这些 input-target 对是什么样子的?正如我们已经了解到的,LLMs 通过预测文本中的下一个单词来进行预训练,如图 2.12 所示。

在这里插入图片描述

图 2.12 给定一个文本样本,提取作为 LLM 输入的输入块作为子样本,而 LLM 在训练期间的预测任务是预测紧跟在输入块之后的下一个单词。在训练过程中,我们遮蔽了目标(target)之后的所有单词。请注意,此图中显示的文本在 LLM 能够处理之前必须经过分词;然而,为了清晰起见,此图省略了分词步骤。

实现一个数据加载器,使用滑动窗口方法从训练数据集中获取图 2.1 2中的 input-target 对。首先,使用 BPE 分词器对整个《The Verdict》短篇故事进行分词:

with open("the-verdict.txt", "r", encoding="utf-8") as f:
    raw_text = f.read()
enc_text = tokenizer.encode(raw_text)
print(len(enc_text))

执行这段代码后,得到的打印结果是 5145,即训练集中的总 token 数。

接下来,为了演示的目的,我们从数据集中移除了前 50 个tokens,在随后的步骤中,会看到这么做的效果:

enc_sample = enc_text[50:]

为下一个单词预测任务创建 input-target 对的一种最简单且最直观的方法是创建两个变量 xy,其中 x 包含输入的 tokens,而 y 包含 target 值,target 是 input 序列向右移动一位的结果:

在这里插入图片描述

执行上述代码,输出如下:

x: [290, 4920, 2241, 287]
y:      [4920, 2241, 287, 257]

通过处理 input 以及对应的 target(这些 target 是 input 向右移动一个位置的结果),我们可以创建下一个单词预测任务(见图 2.12),具体如下:

for i in range(1, context_size+1):
    context = enc_sample[:i]
    desired = enc_sample[i]
    print(context, "---->", desired)

这段代码打印出:

[290] ----> 4920
[290, 4920] ----> 2241
[290, 4920, 2241] ----> 287
[290, 4920, 2241, 287] ----> 257

箭头左边的(---->)指的是 LLM 将接收的 input,而箭头右边的 token ID 代表 LLM 应预测的 target 的 token ID。重复前面的代码,但将 token IDs 转换为文本:

for i in range(1, context_size+1):
    context = enc_sample[:i]
    desired = enc_sample[i]
    print(tokenizer.decode(context), "---->", tokenizer.decode([desired]))

下面的输出显示了 input 和 target 在文本格式中的样子:

and ----> established
and established ----> himself
and established himself ----> in
and established himself in ----> a

现在,我们已经创建了可用于 LLM 训练的 input-target 对。在将 token 转化为嵌入之前,还有一项任务:实现一个高效的数据加载器,它遍历输入数据集,并以 PyTorch 张量类型返回 input 和 target,可以将这些张量视为多维数组。所返回的张量,一个是 input 张量,包含了 LLM 所见文本;另一个是 target 张量,包含 LLM 需要预测值。如图 2.13 所示。虽然图中为了说明清晰,仅展示了字符串格式的tokens,但代码实现时将在 token IDs 上直接操作,因为 BPE 分词器的 encode 方法将分词和转换为 token IDs 作为一个步骤完成。

在这里插入图片描述

图 2.13 为了实现高效的数据加载器,将 input 收集到一个张量 x 中,其中每一行代表一个输入上下文。第二个张量 y 包含对应的 target 预测值(下一个单词),这些 target 是通过将输入向右移动一个位置创建的。

注意:为了实现高效的数据加载器,我们将使用 PyTorch 内置的 DatasetDataLoader 类。有关安装 PyTorch 的更多信息和指导,请参见相关 PyTorch 的资料。

数据集类的代码如下所示。

import torch
from torch.utils.data import Dataset, DataLoader

class GPTDatasetV1(Dataset):
    def __init__(self, txt, tokenizer, max_length, stride):
        self.input_ids = []
        self.target_ids = []
        token_ids = tokenizer.encode(txt) //将文本转换为 token ID
        //使用滑动窗口将文本分割为最大长度为 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 len(self.input_ids)
    def __getitem__(self, idx): //返回数据集中的某一行
        return self.input_ids[idx], self.target_ids[idx]

GPTDatasetV1 类基于 PyTorch 的 Dataset 类,定义了如何从数据集中获取单个行,其中每一行包含分配给 input_chunk 张量的若干 token ID(基于 max_length)。target_chunk 张量则包含相应的 target 值。建议继续阅读,以了解当我们结合 PyTorch 的 DataLoader 使用该数据集时,返回的数据是什么样子的——这将带来更多的直观理解和清晰度。

以下代码使用 GPTDatasetV1 通过 PyTorch 的 DataLoader 以批次形式加载输入数据。

在这里插入图片描述

按照如下代码,设置 batch_size = 1max_length=4 ,调用 create_dataloader_v1 函数。

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)     //将 dataloader 转换为 Python 迭代器
first_batch = next(data_iter)    //而后通过内置函数 next() 调用下一项
print(first_batch)

执行上述代码,输出如下:

[tensor([[ 40, 367, 2885, 1464]]), tensor([[ 367, 2885, 1464, 1807]])]

变量 first_batch 包含两个张量:第一个张量存储 input 的 token ID,第二个张量存储 target 的 token ID。由于 max_length 设置为 4,这两个张量各自包含了四个 token ID。注意,input 大小为 4 实际上很小,这里仅为了简化说明而选用。通常训练 LLM 时会使用至少 256 的 input 大小。

为了理解 stride=1 的含义,让我们从这个数据集中获取另一批次:

second_batch = next(data_iter)
print(second_batch)

第二个批次包含以下内容:

[tensor([[ 367, 2885, 1464, 1807]]), tensor([[2885, 1464, 1807, 3619]])]

如果我们比较第一和第二批次,可以看到第二批次的 token ID 向右移动了一位(例如,第一批次输入中的第二个 ID 是 367,这是第二批次输入的第一个 ID)。stride 设置决定了跨批次 input 移动的位置数量,模拟了滑动窗口的方法,如图 2.14 所示。
在这里插入图片描述

图 2.14 当从 input 数据集中创建多个批次时,在文本上滑动一个输入窗口。如果步长(stride)设置为 1,在创建下一个批次时将输入窗口移动一个位置。如果将步长设置为等于输入窗口的大小,则可以避免批次之间的重叠。

到目前为止,我们从数据加载器中采样的 batch 大小为 1,这有助于说明目的。如果你有深度学习的经验,可能知道,较小的 batch 值在训练过程中需要较少的内存,但会导致模型更新时更加嘈杂。就像在常规深度学习中一样,batch 的大小是一个需要权衡的,并且是训练 LLMs 时要尝试的一个超参数。

在深度学习中使用PyTorch时,batch size 设置得比较小会对模型产生多方面的影响,具体如下:

  • 训练时间:由于每次迭代处理的数据量较少,达到相同训练效果需要更多的迭代次数,通常会增加训练时间。

  • 内存占用:较小的batch size意味着在内存中同时处理的数据量减少,这对于内存有限的设备较为友好,可以避免因内存不足而导致的程序崩溃,还可能允许使用更大的模型或更复杂的网络结构。

  • 模型收敛:

    • 优化算法:较小的batch size使得每次更新的梯度估计可能更加不稳定。因为梯度是基于小部分数据计算得出的,可能无法准确代表整个数据集的梯度方向,导致优化过程出现波动,影响模型收敛的速度和稳定性。
    • 泛化能力:在一定程度上,较小的batch size可能具有类似正则化的效果。因为它使得模型在训练过程中看到的数据分布更加多样化,每次更新都基于不同的小批次数据,这有助于模型学习到更鲁棒的特征,从而可能提高模型的泛化能力。但如果batch size过小,可能会导致模型无法充分学习到数据中的模式,出现欠拟合现象,使模型在训练集和测试集上的表现都不佳。
  • 计算效率:现代深度学习框架和硬件(如GPU)通常针对较大的batch size进行了优化,以充分利用并行计算能力。较小的batch size可能无法充分发挥这些硬件的性能,导致计算资源利用率低下,因为GPU在处理大量数据时能够更高效地并行计算。

接下来简要看看当 batch_size 的值大于 1 时,如何使用数据加载器进行采样:

dataloader = create_dataloader_v1(
    raw_text, batch_size=8, max_length=4, stride=4, shuffle=False
)
data_iter = iter(dataloader)
inputs, targets = next(data_iter)
print("Inputs:\n", inputs)
print("\nTargets:\n", targets)

输出结果:

Inputs:
tensor([[ 40, 367, 2885, 1464],
        [ 1807, 3619, 402, 271],
        [10899, 2138, 257, 7026],
        [15632, 438, 2016, 257],
        [ 922, 5891, 1576, 438],
        [ 568, 340, 373, 645],
        [ 1049, 5975, 284, 502],
        [ 284, 3285, 326, 11]])

Targets:
tensor([[ 367, 2885, 1464, 1807],
        [ 3619, 402, 271, 10899],
        [ 2138, 257, 7026, 15632],
        [ 438, 2016, 257, 922],
        [ 5891, 1576, 438, 568],
        [ 340, 373, 645, 1049],
        [ 5975, 284, 502, 284],
        [ 3285, 326, 11, 287]])

注意,将步长增加到 4,从而充分利用数据集(不会跳过任何一个单词)。这样做可以避免批次之间(任何两个 batch 之间)的任何重叠,因为更多的重叠可能会导致过拟合的风险增加。


原文:Sebastian Raschka. Build a Large Language Model(From Scratch),此处为原文的中文翻译,为了阅读方便,有适当修改。

<think>嗯,用户现在问的是大语言模型的用法。首先,我需要明确用户的需求是什么。可能他们想了解如何有效使用像ChatGPT这样的模型,或者想知道不同场景下的应用方法。用户可能是开发者、学生,或者普通用户,需要不同层面的信息。 接下来,我得回顾之前的历史对话。用户之前询问过关于YOLOv5的实现细节,可能对技术内容比较熟悉。但这次的问题更偏向于大语言模型的应用,可能需要调整回答的深度,既要有技术细节,也要有实际用例。 用户可能需要一个结构化的回答,涵盖基本用法、高级技巧、优化方法以及常见问题。考虑到他们之前对代码实现的关注,可能对API调用和开发集成部分特别感兴趣。不过,也要确保回答不过于技术化,保持易懂。 我需要检查是否有系统级的指令需要注意,比如使用中文、LaTeX格式的数学公式等。但当前问题可能不涉及数学公式,所以重点放在清晰的结构和实用信息上。 另外,用户可能隐含地想知道如何解决使用大语言模型时的常见问题,比如提示词效果不佳或生成内容不准确。因此,在回答中加入常见问题及解决方案会有帮助。 最后,要确保回答覆盖不同层次的应用,从基础到进阶,并提供实际例子,这样用户可以根据自身需求选择合适的方法。同时,避免使用专业术语过多,必要时进行解释,确保内容的可访问性。</think>以下是关于大语言模型(LLM)用法的系统性说明,涵盖基础应用、进阶技巧和优化方法: --- ### 一、基础应用场景 #### 1. 通用文本生成 - **对话交互** ```python # OpenAI API示例 response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "你是一个专业翻译"}, {"role": "user", "content": "将'Hello World'翻译成法语"} ] ) ``` **输出优化技巧**: 设置`temperature=0.7`控制随机性(0-2范围),`max_tokens=500`限制生成长度 #### 2. 信息检索与问答 - **结构化知识提取** ```text 用户输入:解释量子纠缠现象,用三句话说明,包含数学公式 系统提示:你是一位物理学教授,用$$...$$格式展示关键公式 ``` 典型输出: $$|\Psi\rangle = \frac{1}{\sqrt{2}}(|00\rangle + |11\rangle)$$ #### 3. 代码辅助开发 - **代码生成与纠错** ```python # 请求示例 "用Python实现快速排序,要求: 1. 添加类型标注 2. 包含时间复杂度注释 3. 使用递归实现" ``` 输出特征: ```python def quick_sort(arr: list[int]) -> list[int]: """时间复杂度:平均O(n log n),最坏O()""" if len(arr) <= 1: return arr pivot = arr[len(arr)//2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quick_sort(left) + middle + quick_sort(right) ``` --- ### 二、进阶使用技巧 #### 1. 提示工程(Prompt Engineering) | 方法 | 示例 | 效果提升点 | |-----------------|--------------------------|-----------------------| | 思维链(CoT) | "请分步骤解释..." | 提升复杂问题推理能力 | | 少样本学习 | 提供3个输入输出示例 | 增强格式控制精度 | | 角色扮演 | "假设你是资深律师..." | 提升领域专业性 | #### 2. 参数调优组合 ```python # 典型参数配置 generation_config = { "temperature": 0.3, # 创造性(低→严谨,高→多样) "top_p": 0.9, # 核采样阈值 "frequency_penalty": 0.5, # 抑制重复内容 "presence_penalty": 0.2, # 促进新话题出现 "stop": ["\n###"] # 停止生成标记 } ``` #### 3. 记忆增强方法 - **上下文窗口管理** 使用滑动窗口技术维护对话历史: ```text 最新对话(保留): [t-3, t-2, t-1, t] 早期摘要(压缩): "用户讨论了机器学习基础概念..." ``` --- ### 三、企业级应用方案 #### 1. 私有化部署架构 ``` 数据流架构: 用户请求 → API网关 → 负载均衡 → [LLM实例1, LLM实例2] → 向量数据库 → 输出 ``` #### 2. 性能优化策略 | 技术 | 效果 | 实现示例 | |-----------------------|--------------------------|------------------------| | 模型量化 | 显存占用减少50% | `bitsandbytes`库8-bit量化 | | 请求批处理 | 吞吐量提升3-5倍 | 动态padding批处理 | | 缓存机制 | 重复查询响应时间<100ms | Redis缓存常见问答 | #### 3. 安全控制方案 - **内容过滤层** ```python def safety_check(text): if any(word in text for word in blocklist): return "内容不符合安全规范" return text ``` - **审计日志** 记录所有API请求的`(timestamp, user_id, prompt_hash)` --- ### 四、常见问题解决方案 #### 1. 生成内容不准确 - **对策**:启用检索增强生成(RAG) ```python def rag_pipeline(query): results = vector_db.search(query, top_k=3) context = "\n".join(results) return llm.generate(f"基于以下信息回答:{context}\n问题:{query}") ``` #### 2. 长文本处理限制 - **解决方案**: 使用`LangChain`的`map-reduce`方法: ```text 原始文本 → 分块处理 → 各块摘要 → 综合摘要 ``` #### 3. 多语言支持 - **最佳实践**: 添加语言识别中间件: ```python from langdetect import detect lang = detect(user_input) system_prompt += f"请用{lang}语种回答" ``` --- 如果需要具体场景的代码实现方案(如客服系统集成、知识库构建等),可说明具体需求方向。对于模型微调(Fine-tuning)、分布式训练等深度技术细节,也可提供专项说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CS创新实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值