【RL】advantages 与 ratio之间的关系

好的,我们来梳理一下整个流程,澄清 ratioadvantages 的关系以及各自的作用。

核心思想:advantagesratio 是两个独立计算的、服务于同一个最终目标的组件。

  • advantages (优势) 回答了问题:"在某个状态下,采取某个动作,得到的结果比平均水平好多少? " 它的作用是提供学习信号的方向和强度

    • advantages > 0:这是个好动作,应该增加它的概率。
    • advantages < 0:这是个坏动作,应该降低它的概率。
    • advantages绝对值越大,这个学习信号就越强。
  • ratio (重要性采样比率) 回答了问题:"我的新策略相比于产生这些数据的旧策略,在多大程度上改变了这个动作的概率? " 它的作用是修正因为策略变化而带来的分布不匹配

它们在PPO损失函数中被结合在一起,但它们的计算过程是完全分开的。


1. advantages 是如何计算的?(回顾)

advantages 的计算发生在 ratio 计算之前 ,并且完全不依赖于 ratio 。根据我们之前的分析(grpo模式),它的计算流程如下:

  1. 获取序列级奖励 R_i:从Reward Model得到每个完整序列的得分。
  2. 组内奖励归一化 :在同一prompt生成的N个序列组成的组内,对 R_i 进行标准化 (R_i - group_mean) / group_std。这个结果就是序列级优势 Â_i
  3. 扩展与传播 :将 Â_i 放在序列末尾,然后通过 compute_reinforce_return 以折扣累积的方式向前传播,得到每个token的优势 advantages 矩阵。

关键点:到这一步为止,我们只用到了奖励(Reward)和折扣因子(gamma),完全没有用到新旧策略的概率。


2. ratio 计算后干什么用的?

ratio 计算出来后,它的唯一作用就是作为PPO裁剪替代目标函数中的一个核心乘数,来**调制(modulate)**已经计算好的advantages

让我们来看 ActorWorker.loss_func 的核心计算:

python 复制代码
# 1. advantages 已经作为输入 data.batch["advantages"] 准备好了
advantages = data.batch["advantages"]

# 2. 在这里计算 ratio
ratio = (log_probs - old_log_probs).exp()

# 3. ratio 和 advantages 在这里第一次"相遇"并结合
#    它们共同构成了PPO的两个替代目标 (surrogate objectives)
surr1 = ratio * advantages
surr2 = ratio.clamp(1 - pg_clip, 1 + pg_clip) * advantages

# 4. 计算最终的PPO损失
loss = -torch.min(surr1, surr2)

ratio 的作用可以这样理解:

  • 假设我们有一个token,它的advantages+2.0(一个非常好的信号)。

    • 如果 ratio1.5,意味着新策略已经将这个好动作的概率提高了50%。surr1 将会是 1.5 * 2.0 = 3.0,这是一个很强的正向梯度信号,鼓励策略继续提高概率。
    • 如果 ratio0.7,意味着新策略反而降低了这个好动作的概率。surr1 将是 0.7 * 2.0 = 1.4。虽然梯度信号仍然是正向的(因为优势是正的),但它的强度被ratio削弱了。这会驱动模型"纠正错误",转而提高这个动作的概率。
  • 假设另一个token的advantages-1.0(一个坏信号)。

    • 如果 ratio0.6,意味着新策略已经成功地将这个坏动作的概率降低了40%。surr10.6 * (-1.0) = -0.6。损失 -surr1 就是 0.6,一个正的损失值。
    • 如果 ratio1.3,意味着新策略错误地提高了这个坏动作的概率。surr11.3 * (-1.0) = -1.3。损失 -surr1 就是 1.3,一个更大的损失值,会产生更强的梯度来惩罚这个错误的方向。

PPO的裁剪机制(surr2 进一步限制了ratio的作用。如果ratio变得过大或过小,它会被裁剪到一个固定的范围内,防止单次更新对策略的改变过大,从而保证了训练的稳定性。


总结:清晰的流程图

为了彻底理清思路,我们可以画出这样一个计算流程:

复制代码
+---------------------------+       +---------------------------------+
|     Reward Model          |       |      Policy Model (Actor)       |
+---------------------------+       +---------------------------------+
            |                               |           |
            v                               |           v
+---------------------------+               |  +---------------------------+
|  `response_level_rewards` |               |  |  `logits` (from new policy) |
+---------------------------+               |  +---------------------------+
            |                               |                 |
            v                               |                 v
+---------------------------+               |  +--------------------------------+
|  `reward_postprocess`     |               |  |  `op_compute_log_probs`        |
|  (Group Normalization)    |               |  +--------------------------------+
+---------------------------+               |                 |
            |                               |                 v
            v                               |  +--------------------------------+
|  `expand_to_token_level`  |               |  | `log_probs` (new policy logp)  |
+---------------------------+               |  +--------------------------------+
            |                               |
            v                               |
+---------------------------+               |
| `compute_reinforce_return`|               |
+---------------------------+               |
            |                               |
            v                               |
+---------------------------+               |
|  **advantages**           |               |
|   (Shape: bs, seq_len)    |               |
+---------------------------+               |
            |                               |
            |                               |       +--------------------------------+
            |                               +------>| `old_log_probs` (from memory)|
            |                                       +--------------------------------+
            |                                                     |
            |   +-------------------------------------------------+
            |   |
            v   v
+---------------------------+
| `ratio` = exp(log_probs - |
|            old_log_probs) |
+---------------------------+
            |
            v
+---------------------------+
|  PPO Loss Calculation     |
| `loss = -min(r*A, c(r)*A)`|
+---------------------------+
            |
            v
+---------------------------+
|   Final Scalar Loss       |
|    (for backprop)         |
+---------------------------+

结论:
ratioadvantages是PPO算法中两个正交(Orthogonal)的组件。advantages由奖励信号计算而来,提供了**"学什么"(方向和强度)的信息。ratio由新旧策略的概率计算而来,提供了"学了多少"**(策略变化程度)的信息,并用于修正学习过程。它们在最终的PPPO损失函数中才被结合起来,共同决定了最终的梯度更新。

您提的问题非常好,这正是理解策略梯度方法中损失函数符号的关键!我们来彻底澄清一下。

核心原则:梯度下降与目标最大化

  1. 机器学习的通用框架 :我们通常使用梯度下降(Gradient Descent)来优化模型。梯度下降的目的是 最小化(minimize)一个损失函数(Loss Function)
  2. 强化学习的目标 :在强化学习(包括PPO)中,我们的目标是最大化(maximize)一个期望回报目标函数(Objective Function) ,我们用 J(θ) 表示。我们希望找到一组模型参数 θ,使得 J(θ) 最大。

这两个目标是相反的!一个是最小化,一个是最大化。如何统一呢?

解决方案 :将我们的损失函数(Loss)定义为目标函数(Objective)的相反数
Loss(θ) = -J(θ)

这样一来,最小化Loss(θ) 就等价于 最大化J(θ)


应用到PPO

在PPO中,我们的目标函数 J(θ) 是:
J(θ) = E [ min( ratio * A, clip(ratio) * A ) ]

我们希望最大化这个 J(θ)。因此,我们定义的损失函数 pg_loss 就是:
pg_loss = - E [ min( ratio * A, clip(ratio) * A ) ]

或者在代码中,对于单个样本的token:
loss = -torch.min(surr1, surr2)

现在,我们来分析 pg_loss 的符号应该是正还是负。

pg_loss 应该是正还是负?

答案是:一个健康的、正在学习的模型的 pg_loss 通常应该是负的。

我们来分析两种情况:

情况1:好的动作 (advantages > 0)
  • 我们希望模型增加 这个好动作的概率,也就是让 ratio > 1
  • 此时,ratio * A 是一个正数
  • clip(ratio) * A 也是一个正数
  • 因此,min(surr1, surr2) 是一个正数
  • 那么,loss = -min(...) 就是一个负数

直观理解 :当模型做出正确的行为(采取了带来正优势的动作)时,我们不应该"惩罚"它。但梯度下降只能最小化损失。怎么办?我们给它一个负的损失 。对于梯度下降来说,最小化一个负数意味着让它变得更负(例如,从-0.1变到-0.5)。这个"让损失更负"的梯度方向,恰好就是"增加好动作概率"的正确方向。

情况2:坏的动作 (advantages < 0)
  • 我们希望模型减小 这个坏动作的概率,也就是让 ratio < 1
  • 此时,ratio * A 是一个正数 (负数 * 负数 = 正数,假设 ratio 也为负,但实际上ratio恒为正)。让我们更严谨一点:
    • advantages 是负数。
    • ratio 总是正数。
    • 所以 ratio * A 是一个负数
    • clip(ratio) * A 也是一个负数
  • 因此,min(surr1, surr2) 是一个负数
  • 那么,loss = -min(...) 就是一个正数

直观理解 :当模型做出错误的行为(采取了带来负优势的动作)时,我们应该惩罚它 。一个正的损失正好扮演了这个"惩罚"的角色。梯度下降会努力去最小化这个正的损失,而最小化它的方向,恰好就是"减小坏动作概率"的正确方向。


回到您的例子:surr1 = 1.3 * (-1.0) = -1.3

这个例子属于情况2 的变种,是一个特别需要被惩罚的情况。

  • advantages = -1.0 : 这是一个坏动作
  • ratio = 1.3 : 模型不但没有降低这个坏动作的概率,反而错误地增加了它
  • min(surr1, surr2) : 此时 surr1-1.3surr2(裁剪后的)也会是一个负数。所以 min(...) 是一个负数
  • loss = -min(...) = 1.3 : 我们得到了一个正的损失值 1.3

这个正的损失 1.3 意味着什么?

它意味着一个惩罚信号 。梯度下降算法看到一个正的损失,就会产生梯度来减小它。如何减小这个损失呢?那就是要减小 ratio 。减小ratio就意味着降低当前策略 π_θ 采取这个坏动作的概率。

"一个更大的损失值,会产生更强的梯度来惩罚这个错误的方向" 这句话的含义是:

  • 假设在另一个场景中,ratio=1.1, advantages=-1.0,那么 loss 会是 1.1
  • 我们的场景中,ratio=1.3, advantages=-1.0loss1.3
  • 因为 1.3 > 1.1,所以当前场景的惩罚更重 ,产生的梯度也更强,会更有力地"纠正"模型,让它不要再增加这个坏动作的概率。

总结

  • pg_loss 的符号是有意义的

    • 负损失 ≈ 奖励信号(Reward Signal),鼓励当前方向。
    • 正损失 ≈ 惩罚信号(Penalty Signal),抑制当前方向。
  • 一个合理的 pg_loss 曲线

    • 在整个批次中,通常好的动作比坏的动作多(或者说,我们希望模型能更多地做出好动作)。
    • 因此,批次中所有token的损失加权平均后,总的 pg_loss 倾向于是负的
    • 随着训练的进行,模型越来越好,能获得更高的优势,所以 pg_loss 会变得越来越负

这完美地解释了您在第一张健康的 pg_loss 图中看到的现象:曲线是负的,并且有下降(变得更负)的趋势。

好的,我们来通过一个完整的、具体的例子,从advantages的计算一直到最终loss的形状,来详细说明这个过程。

我们将设定一个batch_size=2seq_len=5pg_clip=0.2 的小场景。


1. advantages 的计算(GRPO/REINFORCE 风格)

假设我们已经经过了奖励模型和奖励归一化,得到了一个稀疏的token_level_rewards张量。这个张量在序列的最后非填充位置有一个值(即序列级优势Â_i),其他地方都是0。我们还假设折扣因子gamma=0.99

输入:token_level_rewards
  • 样本1 : 长度为4,序列级优势 Â_1 = 1.5
  • 样本2 : 长度为5,序列级优势 Â_2 = -0.8
python 复制代码
import torch

token_level_rewards = torch.tensor([
    #  t=0,  t=1,  t=2,  t=3,  t=4(pad)
    [ 0.0,  0.0,  0.0,  1.5,  0.0],  # 样本 1
    [ 0.0,  0.0,  0.0,  0.0, -0.8]   # 样本 2
])
计算过程:compute_reinforce_return

这个函数从后向前遍历序列,计算每个时间步的未来折扣奖励总和。

  • 对于样本 1 (Â_1 = 1.5, T=3):

    • adv[t=4](pad) = 0
    • adv[t=3] = reward[3] + gamma * adv[4] = 1.5 + 0.99 * 0 = 1.5
    • adv[t=2] = reward[2] + gamma * adv[3] = 0.0 + 0.99 * 1.5 = 1.485
    • adv[t=1] = reward[1] + gamma * adv[2] = 0.0 + 0.99 * 1.485 = 1.470
    • adv[t=0] = reward[0] + gamma * adv[1] = 0.0 + 0.99 * 1.470 = 1.455
  • 对于样本 2 (Â_2 = -0.8, T=4):

    • adv[t=4] = reward[4] + gamma * 0 = -0.8 + 0 = -0.8
    • adv[t=3] = reward[3] + gamma * adv[4] = 0.0 + 0.99 * (-0.8) = -0.792
    • adv[t=2] = reward[2] + gamma * adv[3] = 0.0 + 0.99 * (-0.792) = -0.784
    • adv[t=1] = reward[1] + gamma * adv[2] = 0.0 + 0.99 * (-0.784) = -0.776
    • adv[t=0] = reward[0] + gamma * adv[1] = 0.0 + 0.99 * (-0.776) = -0.768
输出:advantages 矩阵
python 复制代码
advantages = torch.tensor([
    [1.455, 1.470, 1.485,  1.500,  0.000],  # 样本 1, 注意最后一个是padding, adv应为0
    [-0.768, -0.776, -0.784, -0.792, -0.800]  # 样本 2
])

# advantages 形状: torch.Size([2, 5])

(注意: 实际代码中会用mask将padding位置的advantage清零)


2. loss 的计算

现在我们有了advantages,还需要一个ratio矩阵。我们假设已经计算好了ratio

输入
  • advantages 矩阵 (如上)

  • ratio 矩阵 (假设值):

    python 复制代码
    ratio = torch.tensor([
        [1.10, 1.25, 0.90, 1.05, 1.00],  # 样本 1
        [0.70, 0.95, 1.15, 1.30, 0.85]   # 样本 2
    ])
  • pg_clip = 0.2,所以裁剪区间是 [0.8, 1.2]

计算过程
a) 计算 surr1 = ratio * advantages

这是逐元素的乘法。

python 复制代码
surr1 = ratio * advantages

# surr1 的值:
# tensor([
#   [1.10*1.455, 1.25*1.470, 0.90*1.485, 1.05*1.500, 1.00*0.0],
#   [0.70*(-0.768), 0.95*(-0.776), 1.15*(-0.784), 1.30*(-0.792), 0.85*(-0.800)]
# ])
#
# tensor([
#   [ 1.600,  1.838,  1.337,  1.575,  0.000],
#   [-0.538, -0.737, -0.902, -1.030, -0.680]
# ])
b) 计算 surr2 = ratio.clamp(...) * advantages
  1. 首先裁剪 ratio:

    python 复制代码
    ratio_clamped = ratio.clamp(0.8, 1.2)
    # tensor([
    #   [1.10, 1.20, 0.90, 1.05, 1.00],  # 1.25被裁剪到1.2
    #   [0.80, 0.95, 1.15, 1.20, 0.85]   # 0.70被裁剪到0.8, 1.30被裁剪到1.2
    # ])
  2. 然后与 advantages 相乘:

    python 复制代码
    surr2 = ratio_clamped * advantages
    # tensor([
    #   [1.10*1.455, 1.20*1.470, 0.90*1.485, 1.05*1.500, 1.00*0.0],
    #   [0.80*(-0.768), 0.95*(-0.776), 1.15*(-0.784), 1.20*(-0.792), 0.85*(-0.800)]
    # ])
    #
    # tensor([
    #   [ 1.600,  1.764,  1.337,  1.575,  0.000],
    #   [-0.614, -0.737, -0.902, -0.950, -0.680]
    # ])
c) 计算 torch.min(surr1, surr2)

surr1surr2 逐元素取最小值。

  • advantages > 0 时 (样本1) : 我们希望最大化目标,所以取 min(r*A, clip(r)*A)。当r > 1+ε时,clip(r)*A会更小,起到限制作用。
  • advantages < 0 时 (样本2) : 我们希望最小化目标(因为A是负的),所以取 max(r*A, clip(r)*A)。PPO论文中的目标是 min,但当A<0时,min(r*A, c(r)*A) 等价于 max(r, c(r)) * A。这里我们直接按 min 计算。
python 复制代码
#   surr1:
#   [ 1.600,  1.838,  1.337,  1.575,  0.000]
#   [-0.538, -0.737, -0.902, -1.030, -0.680]
#
#   surr2:
#   [ 1.600,  1.764,  1.337,  1.575,  0.000]
#   [-0.614, -0.737, -0.902, -0.950, -0.680]

min_surr = torch.min(surr1, surr2)
# tensor([
#   [ 1.600,  1.764,  1.337,  1.575,  0.000],  # 1.838 > 1.764,所以取 1.764
#   [-0.614, -0.737, -0.902, -1.030, -0.680]  # -0.538 > -0.614, 所以取 -0.614; -1.030 < -0.950, 所以取 -1.030
# ])
d) 计算 loss = -torch.min(surr1, surr2)

最后,取上一步结果的相反数。

python 复制代码
loss = -min_surr
# tensor([
#   [-1.600, -1.764, -1.337, -1.575, -0.000],
#   [ 0.614,  0.737,  0.902,  1.030,  0.680]
# ])

最终输出:loss 矩阵和形状

最终得到的 loss 是一个与 advantagesratio 形状完全相同的矩阵。

  • loss 矩阵 :

    复制代码
    tensor([
      [-1.600, -1.764, -1.337, -1.575, -0.000],
      [ 0.614,  0.737,  0.902,  1.030,  0.680]
    ])
  • loss 形状 : torch.Size([2, 5]) (即 (batch_size, sequence_length))

这个 loss 矩阵随后会被传入 agg_loss 函数,与 mask 一起被聚合成一个单一的标量值,用于执行反向传播。

观察结果:

  • 对于优势为正的样本1,其所有token的损失都是负的(奖励信号)。
  • 对于优势为负的样本2,其所有token的损失都是正的 (惩罚信号)。
    这与我们之前对pg_loss符号的理论分析完全一致。

您提的这个问题非常敏锐,正指出了PPO损失函数在advantages < 0时的一个微妙但关键的数学变换。这确实是初学者容易困惑的地方。我们来彻底把它讲清楚。

PPO的目标:防止策略变得太差

首先,我们回顾一下PPO裁剪目标的直观思想:

  • advantages > 0(好动作)时:我们想增加 ratio 来提高这个动作的概率,但又不希望 ratio 变得太大 ,以免策略更新过猛。所以我们用 min(ratio * A, (1+ε) * A) 来给它设一个上限。
  • advantages < 0(坏动作)时:我们想减小 ratio 来降低这个动作的概率,但又不希望 ratio 变得太小,以免策略更新过猛。所以我们应该给它设一个下限。

数学推导:为什么 minA<0 时能起到"设下限"的作用

让我们来分析 min(r*A, c(r)*A)A < 0 时的行为。

这里的 rratioc(r)clip(ratio, 1-ε, 1+ε)

我们知道,对于任意两个数 xy,以及一个负数 A
min(x*A, y*A) = max(x, y) * A

为什么?

因为乘以一个负数会颠倒大小关系。例如,3 > 2,但 3 * (-5) < 2 * (-5),即 -15 < -10。所以,原来较大的数乘以负数后反而变小了。因此,取两个负积的最小值,等价于取原来两个正数中的最大值,再乘以这个负数。

把这个性质应用到PPO中:
min(r * A, c(r) * A) = max(r, c(r)) * A (因为 A < 0)

现在我们来分析 max(r, c(r))

  • c(r) 的值域是 [1-ε, 1+ε]
  • rratio,可以是任意正数。

分两种情况讨论 r 的值:

  1. 如果 r[1-ε, 1+ε] 区间内 : 此时 r 没有被裁剪,c(r) = r。那么 max(r, c(r)) = r。目标函数简化为 r * A。这很合理,因为策略变化不大,我们直接使用标准的策略梯度项。

  2. 如果 r 超出 [1-ε, 1+ε] 区间:

    • Case A: r > 1+ε (策略错误地增加了坏动作的概率) :
      • c(r) = 1+ε
      • max(r, c(r)) = max(r, 1+ε) = r
      • 目标函数仍然是 r * A
    • Case B: r < 1-ε (策略正确地降低了坏动作的概率,但可能降得太多) :
      • c(r) = 1-ε
      • max(r, c(r)) = max(r, 1-ε) = 1-ε
      • 目标函数变为 (1-ε) * A

关键洞察

让我们把两种情况的目标函数放在一起看:
J_clipped(θ) = E [ ... ]

其中 [...] 部分是:

  • A > 0: min(r*A, (1+ε)*A)
  • A < 0: max(r*A, (1-ε)*A) (这是从上面推导出来的等价形式)

这两个式子可以统一写成一个 if-else 结构,这在很多PPO的实现中可以看到,例如 Stable Baselines3。

我们现在回到 loss = -torch.min(surr1, surr2) 这种看似简单的实现。 它为什么是对的?

因为它利用了我们最开始推导的数学性质 min(x*A, y*A) = max(x, y) * A

让我们回到您的例子:A = -1.0 (负数),r = 1.3ε = 0.2

  • r*A = 1.3 * (-1.0) = -1.3
  • c(r) = clip(1.3, 0.8, 1.2) = 1.2
  • c(r)*A = 1.2 * (-1.0) = -1.2
  • torch.min(-1.3, -1.2) 的结果是 -1.3

这与我们推导的 max(r, c(r)) * A 的结果是否一致?

  • max(r, c(r)) = max(1.3, 1.2) = 1.3
  • max(r, c(r)) * A = 1.3 * (-1.0) = -1.3
    结果完全一致!

再看一个例子:A = -1.0 (负数),r = 0.7ε = 0.2

  • r*A = 0.7 * (-1.0) = -0.7
  • c(r) = clip(0.7, 0.8, 1.2) = 0.8
  • c(r)*A = 0.8 * (-1.0) = -0.8
  • torch.min(-0.7, -0.8) 的结果是 -0.8

这与我们推导的 max(r, c(r)) * A 的结果是否一致?

  • max(r, c(r)) = max(0.7, 0.8) = 0.8
  • max(r, c(r)) * A = 0.8 * (-1.0) = -0.8
    结果再次完全一致!

结论与直观解释

PPO论文中那个统一的 min 公式是一个非常巧妙的数学构造。它通过一个简单的 min 操作,同时实现了两种情况下的目标:

  • A > 0:

    • min(r*A, (1+ε)*A)
    • 它实际上是在惩罚 r 过大 (r > 1+ε) 的情况,通过选择更小的 (1+ε)*A限制梯度的上限
  • A < 0:

    • min(r*A, (1-ε)*A)
    • 它实际上是在惩罚 r 过小 (r < 1-ε) 的情况,通过选择更小的 r*A (因为 r*A(1-ε)*A更负,所以更小)来限制梯度的下限。等一下,这里的逻辑反了。
    • 让我们重新审视 A<0 的情况。我们希望 最大化 J(θ)。因为 A 是负数,所以 r*Ac(r)*A 都是负数。要最大化一个负数,就要让它的绝对值变得更小(更接近0)。
    • min(r*A, c(r)*A) 选出的是更负的那个值,也就是绝对值更大的那个。
    • 这意味着,当 r 偏离 c(r) 时,PPO会选择一个更差 的目标值(更负的目标值),从而产生一个更大的正损失 (loss = -J(θ)) 来惩罚这种偏离。
    • 例子 : r=0.7, A=-1.0. r*A=-0.7, c(r)*A=-0.8. min 选了-0.8。而r*A(未裁剪的目标)是-0.7。因为 -0.8 < -0.7,所以PPO选择了一个更低的目标值,这会导致更大的惩罚。这正好起到了限制r不要变得过小的作用。

最终总结 :

PPO的 min(...) 公式是一个简洁而强大的统一形式。它在 A>0A<0 时,通过与 advantages 相乘的符号翻转效应,都巧妙地实现了对 ratio 偏离 [1-ε, 1+ε] 区间的惩罚,从而将策略更新限制在一个可信的范围内。尽管 A<0 时的推导看起来不那么直观,但数学上是完全成立的,并且代码中直接使用 torch.min 就是正确的实现方式。

相关推荐
攻城狮7号9 小时前
通用 GUI 智能体基座 MAI-UI 开源:告别“人工智障”?
人工智能·mai-ui·tongyi-mai·阿里通义实验室·gui智能体
寻星探路9 小时前
【深度长文】深入理解网络原理:TCP/IP 协议栈核心实战与性能调优
java·网络·人工智能·python·网络协议·tcp/ip·ai
轻竹办公PPT9 小时前
实测多款 AI:2026 年工作计划 PPT 哪种更好修改
人工智能·python·powerpoint
AIHubPro未来百科9 小时前
三天用AI开发完成开源WordPress导航主题:要哇棱镜主题详解 + 完整部署教程
人工智能·开源
切糕师学AI9 小时前
AI 领域中的 Prompt(提示词/提示)是什么?
人工智能·prompt
HZZD_HZZD9 小时前
喜讯|合众致达成功中标宁夏宝丰集团水电表计量结算管理平台项目
大数据·人工智能
AI_56789 小时前
基于职业发展的Python与Java深度对比分析
java·人工智能·python·信息可视化
凤希AI伴侣9 小时前
告别文件存储的混乱:我用SQLite重构了AI对话记录管理
人工智能·重构·sqlite·凤希ai伴侣
工藤学编程12 小时前
零基础学AI大模型之LangChain智能体之initialize_agent开发实战
人工智能·langchain