Agent 训练与反馈学习
正在读取学习记录…
训练,是根据样本调整模型内部的数值,让它下次遇到相似输入时更容易做出合适选择。这些可调整的数值叫参数。Agent 的动作选择也可以训练:资料充分时直接回答,资料不足时先检索。
想象带一位新同事处理咨询:你给他看几次“先查资料再答”的完整过程,再指出哪次处理得好。机器把这些反馈变成数字,算出参数该往哪个方向调整。从一个只有两个参数的小模型开始,我们就能实际观察这个过程。
1. 反馈怎样变成训练材料
一段任务的完整操作记录叫轨迹(trajectory):看到了什么、选了什么动作、工具返回了什么、最后有没有完成任务。
| 时刻 | 观察 | 动作 | 环境返回 |
|---|---|---|---|
| 1 | 用户问退货期限,当前上下文缺少规则 | 检索退货说明 | 找到“收到商品后 7 天内” |
| 2 | 已取得规则与出处 | 回答用户 | 用户得到期限和依据 |
反馈可以给整段轨迹打标签,也可以指出某一步应换一个动作。记录“满意”还太粗:回答正确但泄露了个人信息时,应把泄露那一步单独标出来。完成度、隐私和工具使用分别有各自的检查项。
| 方法 | 训练材料 | 参数更新的方向 |
|---|---|---|
| SFT,监督微调 | 输入与示范回答,或示范操作轨迹 | 提高示范输出的概率 |
| DPO,直接偏好优化 | 同一个输入下,一份更好的输出与一份较差的输出 | 根据成对偏好调整两份输出的相对概率 |
| RL,强化学习 | 实际行动后得到的奖励 | 根据行动结果提高高回报行为的概率 |
对“查退款进度”,SFT 可以示范“先查订单”;DPO 可以比较“有依据的进度答复”和“编造到账时间”;RL 则让 Agent 在任务环境中操作,根据是否查到正确订单及是否越权得到反馈。
SFT 文档介绍训练器与数据格式;DPO 论文解释成对偏好目标。下面实现动作选择器的监督学习:两个动作,两个参数,每一步都能算。
2. 从分数到选择概率
给资料充分程度一个数 x:充分为 1,不足为 -1。第二个特征固定为 1,用来学习整体偏好,也叫偏置。模型用参数 w 与 b 计算:
分数 z = w × x + b
直接回答概率 p = 1 / (1 + e^(-z))
先检索概率 = 1 - pe 约为 2.718,是数学里的自然常数。这个 sigmoid(S 形函数)把分数压到 0 到 1 之间。z 为 0 时 p 为 0.5;z 越大,直接回答的概率越高。初始参数为零,两种输入的概率都是 0.5。
| 特征 x | 人工标签 y | 希望发生的动作 |
|---|---|---|
| 1 | 1 | 直接回答 |
| -1 | 0 | 先检索 |
损失 loss 衡量模型离标签有多远。二分类交叉熵的公式为:
单条损失 = -[y × ln(p) + (1-y) × ln(1-p)]
平均损失 = 各条损失之和 / 样本数量ln 表示自然对数。标签为 1 时,只留下 -ln(p):正确动作的概率越高,损失越小。初始 p 为 0.5,损失约为 0.6931。
梯度给出参数变化时损失的变化方向。代码先算 error = p - y,累积 error × 特征,再执行“原参数减去学习率 × 梯度”。学习率决定每次走多大一步。一轮(epoch)表示把训练样本完整用一遍。
3. 跑出一次参数更新
在学习目录打开 PowerShell,创建并进入文件夹:
mkdir training-demo
cd training-demo保存为 training_demo.py。probability 计算动作概率,train 更新参数,loss 检查学习前后的变化。
"""从人工反馈训练一个两参数动作选择器。"""
import math
def probability(weights, features):
# 特征一表示资料是否充分,特征二固定为 1,用来学习偏置。
if len(weights) != 2 or len(features) != 2:
raise ValueError("权重和特征都需要两个数")
if not all(type(x) in (int, float) and math.isfinite(x)
for x in [*weights, *features]):
raise ValueError("输入需要有限数值")
score = sum(w * x for w, x in zip(weights, features))
# 分支写法避免很大的负分数导致指数溢出。
return 1 / (1 + math.exp(-score)) if score >= 0 else math.exp(score) / (1 + math.exp(score))
def loss(weights, rows):
# 交叉熵衡量:真实应当选择的动作,获得了多少概率。
total = 0.0
for features, label in rows:
p = min(max(probability(weights, features), 1e-12), 1 - 1e-12)
total -= label * math.log(p) + (1 - label) * math.log(1 - p)
return total / len(rows)
def train(rows, epochs=50, rate=0.2):
if not rows or type(epochs) is not int or epochs <= 0:
raise ValueError("训练需要样本和正整数轮数")
if type(rate) not in (int, float) or not math.isfinite(rate) or rate <= 0:
raise ValueError("学习率需要正数")
for features, label in rows:
if type(label) is not int or label not in (0, 1):
raise ValueError("标签需要整数 0 或 1")
probability([0.0, 0.0], features)
weights = [0.0, 0.0]
for _ in range(epochs):
gradient = [0.0, 0.0]
for features, label in rows:
error = probability(weights, features) - label
for i in range(2):
gradient[i] += error * features[i] / len(rows)
# 按梯度的反方向走一小步,让下一轮损失降低。
weights = [w - rate * g for w, g in zip(weights, gradient)]
return weights
def split_groups(records, held_out):
# 同一任务的不同轨迹必须在同一侧,防止训练答案混入评测。
return ([value for task, value in records if task not in held_out],
[value for task, value in records if task in held_out])
def main():
# 1 表示直接回答;0 表示先检索资料。标签来自预先写好的反馈。
rows = [([1.0, 1.0], 1), ([-1.0, 1.0], 0)]
weights = train(rows)
print(f"训练前损失:{loss([0.0, 0.0], rows):.4f}")
print(f"训练后损失:{loss(weights, rows):.4f}")
print(f"资料充分时直接回答概率:{probability(weights, [0.8, 1.0]):.4f}")
print(f"资料不足时直接回答概率:{probability(weights, [-0.8, 1.0]):.4f}")
if __name__ == "__main__":
main()再保存 test_training_demo.py:
测试代码:test_training_demo.py
"""用固定数据检查参数更新、留出验证与错误输入。"""
import unittest
from training_demo import probability, train, loss, split_groups
class TrainingTests(unittest.TestCase):
def test_zero_weights_give_equal_probability(self):
self.assertEqual(probability([0.0, 0.0], [1.0, 1.0]), 0.5)
def test_learning_reduces_loss(self):
rows = [([1.0, 1.0], 1), ([-1.0, 1.0], 0)]
weights = train(rows)
self.assertLess(loss(weights, rows), loss([0.0, 0.0], rows))
def test_holdout_examples_follow_direction(self):
weights = train([([1.0, 1.0], 1), ([-1.0, 1.0], 0)])
self.assertGreater(probability(weights, [0.8, 1.0]), 0.8)
self.assertLess(probability(weights, [-0.8, 1.0]), 0.2)
def test_reversing_feedback_reverses_behavior(self):
weights = train([([1.0, 1.0], 0), ([-1.0, 1.0], 1)])
self.assertLess(probability(weights, [1.0, 1.0]), 0.2)
def test_task_groups_stay_separate(self):
# 同一道任务的多条轨迹一起划分,避免答案泄漏。
groups = [("a", 1), ("a", 2), ("b", 3)]
self.assertEqual(split_groups(groups, {"b"}), ([1, 2], [3]))
def test_empty_training_data(self):
with self.assertRaises(ValueError):
train([])
def test_bad_label(self):
with self.assertRaises(ValueError):
train([([1.0, 1.0], 2)])
def test_nonfinite_feature(self):
with self.assertRaises(ValueError):
probability([0.0, 0.0], [float("nan"), 1.0])
if __name__ == "__main__":
unittest.main()在同一个窗口检查文件并运行:
ls
python -X utf8 training_demo.py
python -X utf8 -m unittest -v test_training_demo.py程序输出:
训练前损失:0.6931
训练后损失:0.1057
资料充分时直接回答概率:0.8526
资料不足时直接回答概率:0.1474训练输入是 1 与 -1,最后用 0.8 与 -0.8 检查新的输入。两个参数学出了不同倾向。八项测试还检查标签反转、错误输入与任务分组。
沿 main → train → probability 读:参数从零开始;每轮用现有参数计算概率;与标签比较、积累梯度;一轮结束同时更新两个参数。loss 自身没有修改参数。
4. 改轮数,观察学习程度
在 main 中把 weights = train(rows) 改为 weights = train(rows, epochs=1),运行:
python -X utf8 training_demo.py训练后损失变成 0.6444,充分时直接回答概率为 0.5200,不足时为 0.4800。只学一轮,两种动作的倾向刚刚分开。恢复 weights = train(rows),重新运行程序和测试。
再把 main 中两条标签 1、0 对调,程序会向相反方向学习:充分时更倾向检索,不足时更倾向回答。最后把标签恢复原样。标签直接影响训练方向,所以反馈审核首先要确认“这个标签代表我们希望的行为吗”。
5. 从选择器走向 Agent 训练
语言模型有大量参数,输出是一串 token;训练在回答或动作序列上计算损失。Agent 还需要环境:工具、任务状态、允许的操作与完成检查。完整轨迹把奖励与具体操作联系起来。
| 当前问题 | 优先处理位置 | 检查变化 |
|---|---|---|
| 经常选错工具 | 工具说明、示范轨迹、动作训练 | 相同任务集上的工具选择 |
| 回答不错但请求总超时 | 网络与运行时 | 请求耗时、排队和重试 |
| 回答格式常常错 | 输出约束、格式样本与校验 | 校验失败种类与占比 |
| 只会熟悉题目 | 数据划分、任务覆盖 | 新任务完成率 |
训练集更新参数;验证集挑选版本与参数;测试集用于最后的独立比较。先按任务、会话或来源分组,再划分。同一问题换几个说法,仍可能包含相同答案。split_groups 按任务 ID 把记录放到同一侧。
强化学习还要设计奖励:只奖励“回复快”,Agent 可能省掉检索;只奖励“用户点赞”,可能更爱迎合。一起观察完成度、权限遵守与资源消耗,保留逐步轨迹以便定位行为。
| 现象 | 检查位置 | 处理动作 |
|---|---|---|
| 程序提示标签错误 | rows 的第二项 | 使用整数 0 或 1 |
| 程序提示输入错误 | 特征与参数 | 每组两个有限数值 |
| 损失下降,新任务反而变差 | 样本与评测划分 | 排查重复任务、来源泄漏和标签偏差 |
| 修改后测试失败 | main 与默认训练函数 | 按改参步骤恢复示例,重跑测试 |
训练让反馈改变参数;反馈质量决定学习方向;独立评测检查这种方向是否帮助新任务。三个步骤连起来,才能决定采用哪一个版本。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“Agent 训练与反馈学习”,哪种做法更稳妥?
对应知识点:L9-training
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L9-training
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L9-training
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L9-training
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。