新手模型微调认知篇四训练过程与原理详解深入理解模型训练的内部机制包括损失计算、权重更新和自回归生成。Q1SFTTrainer的内部是如何完成微调的ASFTTrainer是trl库提供的监督微调训练器内部流程如下整体流程输入数据对话样本 ↓ 1. Tokenization分词 ↓ 2. 前向传播模型预测 ↓ 3. 计算损失与标准答案对比 ↓ 4. 反向传播计算梯度 ↓ 5. 更新权重优化器 ↓ 重复步骤 1-5直到训练完成详细步骤1. Tokenization分词输入{instruction:什么是机器学习,output:机器学习是人工智能的一个分支...}格式化为 Llama-3.2 格式|begin_of_text||start_header_id|user|end_header_id| 什么是机器学习|eot_id||start_header_id|assistant|end_header_id| 机器学习是人工智能的一个分支...|eot_id|Tokenizeinput_idstokenizer.encode(formatted_text)# [128000, 128006, 882, 128007, 271, 3923, 374, ...]2. 前向传播模型预测输入input_idstoken ID 序列模型处理input_ids ↓ Embedding 层将 token ID 转换为向量 ↓ Transformer Layer 0 ↓ Transformer Layer 1 ↓ ... ↓ Transformer Layer 15 ↓ Output Layer预测下一个 token 的概率分布输出logits每个位置对所有 vocab token 的预测分数logits.shape(batch_size,seq_len,vocab_size)# 例如(1, 100, 128256)# 表示100 个位置每个位置对 128256 个 token 的预测分数3. 计算损失Loss目标让模型的预测尽可能接近标准答案损失函数Cross-Entropy Loss交叉熵损失# 对于每个位置 ipredicted_logitslogits[i]# 模型对第 i 个位置的预测true_tokeninput_ids[i1]# 实际的下一个 token# 计算交叉熵loss-log(softmax(predicted_logits)[true_token])直观理解标准答案的 token: 机器 模型的预测概率: 机器: 0.3 → loss -log(0.3) 1.2 学习: 0.5 → 如果预测 学习 则 loss -log(0.5) 0.69 人工: 0.1 → 如果预测 人工 则 loss -log(0.1) 2.3 loss 越小 → 预测越接近标准答案总损失对所有位置求平均total_lossmean(loss_over_all_positions)4. 反向传播Backpropagation目标计算每个参数对损失的贡献梯度过程loss ↓ 链式法则Chain Rule ↓ 计算每个参数的梯度 ↓ gradient ∂loss / ∂parameter梯度含义gradient 0 → 增大该参数会增大 loss → 应该减小该参数 gradient 0 → 增大该参数会减小 loss → 应该增大该参数 gradient ≈ 0 → 该参数对 loss 影响很小5. 更新权重Optimizer优化器AdamW自适应学习率优化器更新公式parameter_newparameter_old-learning_rate*gradientAdamW 的特点自适应学习率每个参数有自己的学习率动量考虑历史梯度权重衰减防止过拟合完整示例# 1. Tokenizationinput_idstokenizer.encode(你好世界)# [128000, 57668, 53901, 382, 102616, 128009]# 2. 前向传播outputsmodel(input_ids)logitsoutputs.logits# (1, 6, 128256)# 3. 计算损失loss_fctCrossEntropyLoss()shift_logitslogits[:,:-1,:]# 去掉最后一个位置shift_labelsinput_ids[:,1:]# 去掉第一个 tokenlossloss_fct(shift_logits.view(-1,128256),shift_labels.view(-1))# 4. 反向传播loss.backward()# 5. 更新权重optimizer.step()optimizer.zero_grad()Q2模型的回答与标准答案都是文本是如何对比的A不是直接对比文本而是对比token 序列的概率分布。对比机制标准答案机器学习是人工智能的一个分支 → token IDs: [12345, 67890, 11111, 22222, 33333, 44444, 55555]模型预测对于每个位置模型输出一个概率分布对 128256 个 token 位置 0: 预测 机器 的概率: 0.3 预测 学习 的概率: 0.5 预测 人工 的概率: 0.1 ... 位置 1: 预测 学习 的概率: 0.4 预测 是 的概率: 0.3 ...损失计算对于每个位置# 标准答案的 tokentrue_token12345# 机器# 模型预测的概率分布predicted_probssoftmax(logits[position])# 标准答案 token 的概率true_probpredicted_probs[true_token]# 例如 0.3# 损失 -log(概率)loss-log(true_prob)# -log(0.3) 1.2直观理解如果模型预测正确 token 的概率很高 true_prob 0.9 → loss -log(0.9) 0.105 (很小) 如果模型预测正确 token 的概率很低 true_prob 0.01 → loss -log(0.01) 4.6 (很大) loss 越小 → 预测越准确为什么不是直接对比文本问题 1文本对比太严格标准答案: 机器学习是人工智能的一个分支 模型回答: 机器学习是 AI 的一个分支 文本对比: 完全不同 ❌ 语义对比: 几乎相同 ✅ 直接对比文本会忽略同义词、近义词问题 2语言模型的本质是概率预测语言模型不理解语义 它只学习 token 序列的概率分布 训练目标: 让正确 token 的概率尽可能高 而不是让生成的文本和标准答案完全相同问题 3自回归生成的累积误差生成 机器学习是人工智能的一个分支 位置 0: 预测 机器 (正确) 位置 1: 预测 学习 (正确) 位置 2: 预测 是 (正确) 位置 3: 预测 人工 (错误预测了 AI) 位置 4: 预测 的 (基于 AI 继续预测) ... 一个错误会导致后续全部偏离 直接对比文本会过度惩罚这种累积误差实际训练中的处理Teacher Forcing训练时不使用模型自己的预测而是使用标准答案作为输入 标准答案: [A, B, C, D, E] 训练输入: [start, A, B, C, D] 训练目标: 预测 [A, B, C, D, E] 这样每个位置的预测都是独立的不会累积误差损失计算# 对于每个位置 iinput_tokeninput_ids[i]# 标准答案的第 i 个 tokentarget_tokeninput_ids[i1]# 标准答案的第 i1 个 token# 模型基于 input_token 预测 target_tokenpredicted_logitsmodel(input_token)losscross_entropy(predicted_logits,target_token)Q3模型是如何回答问题的返回的各个字符是如何得到的A模型使用自回归生成Autoregressive Generation。自回归生成原理核心思想每次只预测一个 token然后将预测的 token 加入输入再预测下一个。示例生成 “你好世界”初始输入: |begin_of_text|你好 步骤 1: 输入: |begin_of_text|你好 模型预测下一个 token 的概率分布 选择概率最高的: 世 (概率 0.4) 步骤 2: 输入: |begin_of_text|你好世 模型预测下一个 token 的概率分布 选择概率最高的: 界 (概率 0.5) 步骤 3: 输入: |begin_of_text|你好世界 模型预测下一个 token 的概率分布 选择概率最高的: |eot_id| (概率 0.8) 生成结束: 你好世界详细流程1. 准备输入prompt什么是机器学习# 格式化为 Llama-3.2 格式formattedf|begin_of_text||start_header_id|user|end_header_id|\n\nformattedf{prompt}|eot_id||start_header_id|assistant|end_header_id|\n\n# Tokenizeinput_idstokenizer.encode(formatted)# [128000, 128006, 882, 128007, 271, 3923, 374, 128009, 128006, 78191, 128007, 271]2. 自回归循环generatedinput_ids.copy()forstepinrange(max_new_tokens):# 模型预测outputsmodel(generated)logitsoutputs.logits[:,-1,:]# 取最后一个位置的预测# 采样策略temperature, top_p, top_kprobssoftmax(logits/temperature)next_tokensample(probs,top_p0.9,top_k50)# 添加到生成序列generated.append(next_token)# 检查是否生成结束符ifnext_tokeneos_token_id:break3. 解码输出# 去掉输入部分只保留生成的部分generated_tokensgenerated[len(input_ids):]# 解码为文本responsetokenizer.decode(generated_tokens,skip_special_tokensTrue)print(response)# 机器学习是人工智能的一个分支它让计算机能够从数据中学习...采样策略问题如何选择下一个 token策略 1Greedy贪心next_tokenargmax(probs)# 总是选择概率最高的优点确定性强缺点容易生成重复、无聊的文本策略 2Temperature Sampling温度采样# temperature 1: 使分布更尖锐更确定# temperature 1: 使分布更平坦更随机adjusted_probssoftmax(logits/temperature)next_tokensample(adjusted_probs)策略 3Top-p Sampling核采样# 只从累积概率达到 p 的最小 token 集合中采样# 例如 top_p0.9 → 只考虑累积概率前 90% 的 tokensorted_probssort(probs,descendingTrue)cumsumcumsum(sorted_probs)cutoffcumsumtop_p filtered_probssorted_probs*cutoff next_tokensample(filtered_probs)策略 4Top-k Sampling# 只从概率最高的 k 个 token 中采样top_k_probstopk(probs,k50)next_tokensample(top_k_probs)实际示例# 配置生成参数generation_config{max_new_tokens:256,do_sample:True,temperature:0.7,top_p:0.9,top_k:50}# 生成outputmodel.generate(input_ids,**generation_config)responsetokenizer.decode(output[0],skip_special_tokensTrue)Q4当模型的回答与标准答案有差异时微调是如何做的A通过梯度下降Gradient Descent更新权重。核心思想不是直接改成标准答案而是计算当前预测与标准答案的差异损失计算每个参数对损失的贡献梯度沿着减小损失的方向调整参数详细过程1. 前向传播得到预测# 标准答案target机器学习是人工智能的一个分支target_idstokenizer.encode(target)# [12345, 67890, 11111, 22222, 33333, 44444, 55555]# 模型预测logitsmodel(input_ids)# logits.shape (1, 7, 128256)# 对于每个位置预测所有 128256 个 token 的分数2. 计算损失量化差异loss0foriinrange(len(target_ids)-1):predictedlogits[0,i,:]# 位置 i 的预测true_tokentarget_ids[i1]# 位置 i1 的真实 token# 交叉熵损失probsoftmax(predicted)[true_token]loss-log(prob)lossloss/len(target_ids)# 例如 loss 2.5表示预测与真实答案有较大差异3. 反向传播计算梯度loss.backward()# 对于每个参数 W# 计算 ∂loss/∂W梯度# 梯度表示如果增大 Wloss 会如何变化梯度的含义参数 W[i,j] 的梯度 0.05 → 增大 W[i,j] 会使 loss 增大 0.05 → 应该减小 W[i,j] 来减小 loss 参数 W[i,j] 的梯度 -0.03 → 增大 W[i,j] 会使 loss 减小 0.03 → 应该增大 W[i,j] 来减小 loss4. 更新权重梯度下降learning_rate0.0002# 对于每个参数 WW_newW_old-learning_rate*gradient# 示例W_old[0,0]-0.0179443359gradient[0,0]-0.000441321W_new[0,0]-0.0179443359-0.0002*(-0.000441321)-0.01794433590.0000000883-0.0179442476更新幅度learning_rate 0.0002很小 gradient 通常也很小 1 所以每次更新的幅度很小约 0.01% ~ 1% 这就是微调的含义 不是大幅改变而是微小调整5. 重复训练forepochinrange(num_epochs):forbatchindataloader:# 1. 前向传播logitsmodel(batch.input_ids)# 2. 计算损失losscompute_loss(logits,batch.labels)# 3. 反向传播loss.backward()# 4. 更新权重optimizer.step()# 5. 清空梯度optimizer.zero_grad()直观理解类比调整收音机旋钮目标调到某个频率标准答案 当前偏离目标频率模型的预测 操作 1. 听一下当前频率的清晰度计算损失 2. 判断应该往哪个方向调计算梯度 3. 微小地转动旋钮更新权重 4. 重复直到清晰损失最小为什么是微调而不是大改预训练模型已经学会了语言理解能力 微调只需要在特定任务上做微小调整 学习率很小0.0002 → 每次更新只改变权重的 0.01% ~ 1% → 保留预训练模型的绝大部分能力 → 只调整与特定任务相关的部分Q5训练过程中loss是如何变化的Aloss通常会逐渐下降但有波动。典型的 loss 曲线Loss 3.0 ┤● │ ● 2.5 ┤ ● │ ● 2.0 ┤ ● ● │ ● ● 1.5 ┤ ● ● ● │ ● ● 1.0 ┤ ● ● ● │ ● ● 0.5 ┤ └────────────────────────────────── 0 100 200 300 400 500 Training Steps三个阶段阶段 1快速下降0-100 步模型从完全不懂到基本理解 loss 从 3.0 快速下降到 1.5 学习率逐渐增大warmup阶段 2缓慢下降100-400 步模型从基本理解到熟练掌握 loss 从 1.5 缓慢下降到 0.8 学习率达到峰值后逐渐衰减阶段 3收敛400-500 步模型接近最优 loss 在 0.5 ~ 0.8 之间波动 学习率很小更新幅度很小为什么 loss 会波动原因 1小 batch sizebatch_size 1 每个 batch 只有一个样本 不同样本的难度不同 → loss 波动大原因 2学习率调度学习率按 cosine 调度 → 先增大warmup → 达到峰值 → 逐渐衰减到 0 学习率变化 → 更新幅度变化 → loss 波动原因 3数据分布训练数据中 简单样本: loss 低 困难样本: loss 高 不同 batch 包含不同难度的样本 → loss 波动如何判断训练效果1. 观察训练 loss✅ 正常: loss 逐渐下降有小幅波动 ⚠️ 异常: loss 不下降或上升 ❌ 问题: loss 剧烈波动2. 观察验证 loss✅ 正常: 验证 loss 也逐渐下降 ⚠️ 过拟合: 训练 loss 下降验证 loss 上升 ❌ 欠拟合: 训练 loss 和验证 loss 都很高3. 对比 checkpointcheckpoint-100: val_loss 0.52 checkpoint-200: val_loss 0.51 ← 最佳 checkpoint-300: val_loss 0.53 ← 开始过拟合 checkpoint-400: val_loss 0.55 选择 checkpoint-200 作为最终模型小结本节深入讲解了训练过程的核心机制SFTTrainer 流程 Tokenization → 前向传播 → 计算损失 → 反向传播 → 更新权重损失计算 不是直接对比文本而是对比 token 概率分布自回归生成 每次预测一个 token逐步生成完整回答权重更新 通过梯度下降微小调整不是直接改成标准答案Loss 变化 通常逐渐下降但有波动是正常的下一节将深入讲解 LoRA 的原理和权重合并。