《深度学习框架PyTorch入门与实践》系列:03-autograd自动微分:反向传播的引擎

autograd自动微分:反向传播的引擎

本文你将学到requires_grad的传播规则、动态计算图的构建与反向传播原理、backward()四个参数的含义、grad_fnnext_functions如何串起整张图、叶子节点(leaf tensor)的判定标准、梯度为什么会累加以及如何正确清零、no_grad/detach/tensor.data三种"脱离计算图"手段的区别、如何用hook拿到非叶子节点的梯度、如何继承Function自定义求导规则,最后用纯autograd手写一个完整可运行的线性回归,与手动求导版本对照,直观感受自动微分的价值。

文章目录

一、为什么需要autograd

神经网络的训练靠反向传播:先前向计算损失,再逐层求偏导得到梯度,最后用梯度下降更新参数。对一个线性回归,手推梯度公式不难;但换成几十层的卷积网络,手动实现反向传播不仅费时费力,而且极易出错、难以排查。

torch.autograd就是为此而生的自动求导引擎:它在前向传播时自动记录每一步Tensor运算,构建出计算图;调用backward()时沿图反向遍历,用链式法则自动算出所有梯度。你只管写前向逻辑,导数交给框架。

二、requires_grad与backward基本用法

autograd的核心数据结构仍然是Tensor,只是多了一个开关属性requires_grad。它默认为False,设为True后,该Tensor上的所有运算都会被记录:

python 复制代码
import torch as t

# 三种等价写法
a = t.randn(3, 4, requires_grad=True)      # 创建时指定
a = t.randn(3, 4).requires_grad_()         # inplace开启
a = t.randn(3, 4); a.requires_grad = True  # 直接赋值属性

来看最小可用示例:

python 复制代码
b = t.zeros(3, 4).requires_grad_()
c = (a + b).sum()   # c是一个标量
c.backward()        # 反向传播
a.grad
# tensor([[1., 1., 1., 1.],
#         [1., 1., 1., 1.],
#         [1., 1., 1., 1.]])

c对a的每个元素的偏导都是1,autograd算得完全正确。注意一个重要的传播规则 :我们从未设置c的requires_grad,但它自动变成了True------只要某个Tensor依赖于任何一个需要求导的Tensor,它自己也需要求导 。原因很直白:对x → y → z,要算∂z/∂x,按链式法则必须先算∂z/∂y,所以y也得被追踪。

python 复制代码
a.requires_grad, b.requires_grad, c.requires_grad   # (True, True, True)

backward()的完整签名

python 复制代码
torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False)
  • tensors :从哪个Tensor开始反向传播,y.backward()等价于torch.autograd.backward(y)
  • grad_tensors:形状与y一致的"上游梯度"。若y是标量(如loss)可省略;若y是张量则必须传,它相当于链式法则 dz/dx = dz/dy × dy/dx 中的 dz/dy。
  • retain_graph :反向传播会用到前向时缓存的中间结果,传播完这些缓存默认被清空。要对同一张图多次backward,需传retain_graph=True
  • create_graph:对反向传播过程本身再建图,用于求高阶导数。

三、验证:autograd算的和手推公式一致吗

以函数 y = x²·eˣ 为例,手推的导函数是 dy/dx = 2x·eˣ + x²·eˣ。让autograd和公式对个账:

python 复制代码
def f(x):
    '''前向计算y'''
    return x**2 * t.exp(x)

def gradf(x):
    '''手动推导的导函数'''
    return 2*x*t.exp(x) + x**2*t.exp(x)

x = t.randn(3, 4, requires_grad=True)
y = f(x)

y.backward(t.ones(y.size()))       # y不是标量,需传入grad_tensors
assert t.all(x.grad == gradf(x))   # 断言通过:二者完全一致

这里y.backward(t.ones(y.size()))传入的全1张量,含义是"y的每个元素对最终目标的梯度都是1",相当于对y.sum()求导。断言没有抛异常,说明autograd的结果和手推公式逐元素相等。

四、计算图:autograd的底层原理

autograd底层用计算图 (Computation Graph)这种有向无环图(DAG)记录运算与数据的关系:椭圆表示变量(Tensor),矩形表示算子(Function)。以 z = wx + b 为例,它被分解为 y = w×x 和 z = y + b:
#mermaid-svg-k2sZB7BWtGsRRH5b{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-k2sZB7BWtGsRRH5b .error-icon{fill:#552222;}#mermaid-svg-k2sZB7BWtGsRRH5b .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-k2sZB7BWtGsRRH5b .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-k2sZB7BWtGsRRH5b .marker{fill:#333333;stroke:#333333;}#mermaid-svg-k2sZB7BWtGsRRH5b .marker.cross{stroke:#333333;}#mermaid-svg-k2sZB7BWtGsRRH5b svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-k2sZB7BWtGsRRH5b p{margin:0;}#mermaid-svg-k2sZB7BWtGsRRH5b .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-k2sZB7BWtGsRRH5b .cluster-label text{fill:#333;}#mermaid-svg-k2sZB7BWtGsRRH5b .cluster-label span{color:#333;}#mermaid-svg-k2sZB7BWtGsRRH5b .cluster-label span p{background-color:transparent;}#mermaid-svg-k2sZB7BWtGsRRH5b .label text,#mermaid-svg-k2sZB7BWtGsRRH5b span{fill:#333;color:#333;}#mermaid-svg-k2sZB7BWtGsRRH5b .node rect,#mermaid-svg-k2sZB7BWtGsRRH5b .node circle,#mermaid-svg-k2sZB7BWtGsRRH5b .node ellipse,#mermaid-svg-k2sZB7BWtGsRRH5b .node polygon,#mermaid-svg-k2sZB7BWtGsRRH5b .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-k2sZB7BWtGsRRH5b .rough-node .label text,#mermaid-svg-k2sZB7BWtGsRRH5b .node .label text,#mermaid-svg-k2sZB7BWtGsRRH5b .image-shape .label,#mermaid-svg-k2sZB7BWtGsRRH5b .icon-shape .label{text-anchor:middle;}#mermaid-svg-k2sZB7BWtGsRRH5b .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-k2sZB7BWtGsRRH5b .rough-node .label,#mermaid-svg-k2sZB7BWtGsRRH5b .node .label,#mermaid-svg-k2sZB7BWtGsRRH5b .image-shape .label,#mermaid-svg-k2sZB7BWtGsRRH5b .icon-shape .label{text-align:center;}#mermaid-svg-k2sZB7BWtGsRRH5b .node.clickable{cursor:pointer;}#mermaid-svg-k2sZB7BWtGsRRH5b .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-k2sZB7BWtGsRRH5b .arrowheadPath{fill:#333333;}#mermaid-svg-k2sZB7BWtGsRRH5b .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-k2sZB7BWtGsRRH5b .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-k2sZB7BWtGsRRH5b .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k2sZB7BWtGsRRH5b .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-k2sZB7BWtGsRRH5b .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k2sZB7BWtGsRRH5b .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-k2sZB7BWtGsRRH5b .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-k2sZB7BWtGsRRH5b .cluster text{fill:#333;}#mermaid-svg-k2sZB7BWtGsRRH5b .cluster span{color:#333;}#mermaid-svg-k2sZB7BWtGsRRH5b div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-k2sZB7BWtGsRRH5b .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-k2sZB7BWtGsRRH5b rect.text{fill:none;stroke-width:0;}#mermaid-svg-k2sZB7BWtGsRRH5b .icon-shape,#mermaid-svg-k2sZB7BWtGsRRH5b .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k2sZB7BWtGsRRH5b .icon-shape p,#mermaid-svg-k2sZB7BWtGsRRH5b .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-k2sZB7BWtGsRRH5b .icon-shape .label rect,#mermaid-svg-k2sZB7BWtGsRRH5b .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k2sZB7BWtGsRRH5b .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-k2sZB7BWtGsRRH5b .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-k2sZB7BWtGsRRH5b :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} w
MUL
x
y
ADD
b
z

反向传播就是从根节点z出发沿箭头反向走,用链式法则逐层计算:∂z/∂b = 1,∂z/∂y = 1,∂y/∂w = x,于是 ∂z/∂w = 1×x。用代码把这张图解剖开:

python 复制代码
x = t.ones(1)                        # 不需要求导
b = t.rand(1, requires_grad=True)
w = t.rand(1, requires_grad=True)
y = w * x    # 等价于 y = w.mul(x)
z = y + b    # 等价于 z = y.add(b)

grad_fn记录了每个Tensor是被哪个运算生成的,反向传播时就调用它对应的求导函数(名字通常以Backward结尾):

python 复制代码
z.grad_fn
# <AddBackward0 ...>   z是加法的输出

z.grad_fn.next_functions
# ((<MulBackward0 ...>, 0), (<AccumulateGrad ...>, 0))

next_functions保存了上游节点,正是它把整张图串了起来:

  • 第一个是y的MulBackward0------y由乘法生成;
  • 第二个是b的AccumulateGrad------b是需要求导的叶子节点,它的梯度会累加保存(这个名字暗示了第六节的坑)。
python 复制代码
y.grad_fn.next_functions
# ((<AccumulateGrad ...>, 0), (None, 0))
# w需要求导 → AccumulateGrad;x不需要求导 → None

w.grad_fn, x.grad_fn   # (None, None),用户创建的叶子节点没有grad_fn

动态图:每次前向都重新建图

PyTorch采用动态图设计:计算图在每次前向传播时从头构建。这意味着可以用原生Python控制流(if/for)动态决定图结构,每次迭代的图甚至可以不一样:

python 复制代码
def f(x):
    result = 1
    for ii in x:
        if ii.item() > 0:
            result = ii * result   # 只有正数参与乘积
    return result

x = t.arange(-2, 4).float().requires_grad_()
y = f(x)        # 本次输入下 y = x[3]*x[4]*x[5] = 1*2*3
y.backward()
x.grad          # tensor([0., 0., 0., 6., 3., 2.]),负数位置梯度为0

前3个元素根本没进计算图,所以梯度是0;后3个的梯度正是乘积对各自的偏导。这种"图随代码走"的特性在NLP等输入结构多变的场景中特别有用,也是PyTorch调试友好的根源。

五、叶子节点(Leaf Tensor)

计算图中is_leaf=True的Tensor称为叶子节点,判定规则:

  1. requires_grad=False的Tensor都是叶子节点;
  2. requires_grad=True由用户直接创建(而非运算生成)的Tensor是叶子节点。

设计叶子节点的目的是省内存 :backward之后,只有"需要求导的叶子节点"的梯度会保留在.grad里,非叶子节点(中间结果)的梯度算完即被释放,因为通常没人用它们。

python 复制代码
a = t.rand(10, requires_grad=True)
a.is_leaf                # True:用户创建

c = t.rand(10, requires_grad=True) + 2
c.is_leaf                # False:由加法运算生成

b = t.rand(10, requires_grad=True).cuda(0)
b.is_leaf                # False!.cuda()是一次运算,b是运算结果

e = t.rand(10).cuda(0).requires_grad_()
e.is_leaf                # True:先搬到GPU(此时不追踪),再开启求导

be的对比很有实践意义:想在GPU上创建一个可训练的叶子参数,应当先转设备再开requires_grad ,否则拿到的是非叶子节点,.grad不会被保留。另外记住:叶子节点的grad_fn为None------它们不是任何运算的输出。

六、梯度累加与清零

autograd有一个必须刻进肌肉记忆的行为:backward得到的梯度不是覆盖,而是累加到.grad(AccumulateGrad的字面含义)。

python 复制代码
x = t.ones(2, 2, requires_grad=True)
y = x.sum()

y.backward(retain_graph=True); print(x.grad)  # 全1
y.backward(retain_graph=True); print(x.grad)  # 全2,累加了!
y.backward(retain_graph=True); print(x.grad)  # 全3

x.grad.data.zero_()          # 手动清零(inplace)
y.backward(); print(x.grad)  # 恢复全1,正确

为什么这样设计?因为它给了框架灵活性------比如显存不够时,可以把大batch拆成几个小batch分别backward,梯度自动累加后再统一更新,等效于大batch训练。但代价是:常规训练中每次backward前必须清零,否则本轮梯度会混入上一轮的残留。实际写法有三种:

python 复制代码
x.grad.data.zero_()       # 对单个Tensor手动清零
net.zero_grad()           # 对nn.Module的全部参数清零
optimizer.zero_grad()     # 对优化器管理的全部参数清零(最常用)

另外注意上例中的retain_graph=True:计算梯度需要用到前向时缓存的中间值(比如算∂y/∂w需要x的值),backward后缓存默认清空,所以想对同一张图连续backward必须显式保留。

七、关闭求导:no_grad、detach与tensor.data

追踪运算要缓存中间结果,有内存/显存开销。推理、测试等不需要梯度的场景应当关闭求导,能明显提速并节省约一半显存。共有三种手段,语义各不相同。

7.1 with t.no_grad():上下文级关闭

python 复制代码
x = t.ones(1)
w = t.rand(1, requires_grad=True)

with t.no_grad():
    y = x * w
y.requires_grad     # False!虽然w需要求导,但该运算未被记录

块内的所有运算都不进计算图,是推理阶段的标准写法 。类似的全局开关是t.set_grad_enabled(False),用完记得恢复。

7.2 tensor.detach():切断某个变量

detach()返回一个与原Tensor共享数据但脱离计算图 的新Tensor(requires_grad=False)。常用于"这段特征我只当常量用,别对它求导"的场景,例如GAN中固定判别器训练生成器、截断RNN的历史梯度。

python 复制代码
a = t.ones(3, 4, requires_grad=True)
b = a.detach()      # 与a共享内存,但b上的运算不再被追踪
b.requires_grad     # False

7.3 tensor.data:不推荐的老写法

tensor.data同样返回脱离计算图、共享数据的Tensor。但它和detach()有个危险区别:通过.data修改数值,autograd完全无法察觉 ,如果这个值恰好是backward要用的中间结果,会得到静默的错误梯度。而修改detach()的结果在backward时会触发报错,起码错得明明白白。因此现代代码应优先用detach(),避免直接操作.data

八、获取非叶子节点的梯度:autograd.grad与hook

第五节说过,非叶子节点的梯度算完即被清空。调试时若想看中间变量的梯度,有两种办法:

python 复制代码
x = t.ones(3, requires_grad=True)
w = t.rand(3, requires_grad=True)
y = x * w
z = y.sum()

z.backward()
x.grad, w.grad, y.grad   # (tensor(...), tensor([1., 1., 1.]), None)
                         # y是非叶子节点,grad为None

方法一:autograd.grad直接求

python 复制代码
y = x * w
z = y.sum()
t.autograd.grad(z, y)    # (tensor([1., 1., 1.]),),隐式调用backward

方法二:注册hook(推荐)。hook是一个以梯度为输入的回调函数,在反向传播经过该Tensor时被触发:

python 复制代码
def variable_hook(grad):
    print('y的梯度:', grad)

y = x * w
hook_handle = y.register_hook(variable_hook)   # 注册
z = y.sum()
z.backward()             # 输出:y的梯度: tensor([1., 1., 1.])
hook_handle.remove()     # 用完及时移除,避免每次backward都触发

九、扩展autograd:自定义Function

绝大多数运算autograd都能自动求导,但如果你实现了一个它不认识的复杂运算(比如调用了外部库),就需要继承torch.autograd.Function,手动写出前向和反向逻辑:

python 复制代码
from torch.autograd import Function

class MultiplyAdd(Function):

    @staticmethod
    def forward(ctx, w, x, b):
        ctx.save_for_backward(w, x)   # 缓存反向传播要用的中间值
        output = w * x + b
        return output

    @staticmethod
    def backward(ctx, grad_output):
        w, x = ctx.saved_tensors      # 取回缓存
        grad_w = grad_output * x      # ∂z/∂w = x
        grad_x = grad_output * w      # ∂z/∂x = w
        grad_b = grad_output * 1      # ∂z/∂b = 1
        return grad_w, grad_x, grad_b

要点有四:

  • forward/backward都是静态方法 ,没有__init__
  • backward的输出与forward的输入一一对应 (forward吃w, x, b,backward就得吐grad_w, grad_x, grad_b);backward的输入grad_output对应forward的输出;
  • 反向要用的中间结果必须在forward里用ctx.save_for_backward保存,否则前向结束就被释放了;
  • 调用时用Function.apply(...),不要直接调forward:
python 复制代码
x = t.ones(1)
w = t.rand(1, requires_grad=True)
b = t.rand(1, requires_grad=True)

z = MultiplyAdd.apply(w, x, b)   # 前向
z.backward()                     # 反向,自动调用我们写的backward
x.grad, w.grad, b.grad           # (None, tensor([1.]), tensor([1.]))

x不需要求导,所以最终x.grad是None(中间会算但随即被清空)。

十、实战:用autograd手写线性回归

最后用一个完整例子收尾。目标:从带噪声的数据里学出 y = 2x + 3 的参数w和b。用Tensor手动推导梯度也能实现同样的功能(书中3.2节就是这么做的),这次让autograd代劳,直接对比出它的价值。

python 复制代码
import torch as t
import numpy as np
from matplotlib import pyplot as plt

t.manual_seed(1000)   # 固定随机种子,保证结果可复现

def get_fake_data(batch_size=8):
    '''产生随机数据:y = 2x + 3,加上噪声'''
    x = t.rand(batch_size, 1) * 5
    y = x * 2 + 3 + t.randn(batch_size, 1)
    return x, y

# 随机初始化参数,开启求导------它们就是计算图的叶子节点
w = t.rand(1, 1, requires_grad=True)
b = t.zeros(1, 1, requires_grad=True)
losses = np.zeros(500)

lr = 0.005   # 学习率

for ii in range(500):
    x, y = get_fake_data(batch_size=32)

    # forward:计算均方误差损失
    y_pred = x.mm(w) + b.expand_as(y)   # expand_as利用了广播
    loss = 0.5 * (y_pred - y) ** 2
    loss = loss.sum()
    losses[ii] = loss.item()

    # backward:一行代码,自动计算所有梯度
    loss.backward()

    # 更新参数:通过.data操作,避免更新过程本身被记录进计算图
    w.data.sub_(lr * w.grad.data)
    b.data.sub_(lr * b.grad.data)

    # 梯度清零:不清零下一轮就会累加出错
    w.grad.data.zero_()
    b.grad.data.zero_()

print(f'w: {w.item():.3f}, b: {b.item():.3f}')
# 输出约为 w: 2.026, b: 2.973,非常接近真实值2和3

plt.plot(losses)   # loss曲线整体下降后趋于平稳
plt.show()

500次迭代后学到 w≈2.03、b≈2.97,与真实参数(2, 3)基本一致。回看这段代码,训练循环浓缩成了固定的四拍节奏:

  1. forward算loss;
  2. **loss.backward()**自动求梯度------手动版里那五六行链式求导代码(dy_pred、dw、db......)全部消失;
  3. w.grad更新参数 (更新用.data绕开追踪,这里是它为数不多的合理用法;工程中更规范的做法是包在with t.no_grad():里,或者直接交给optimizer.step());
  4. 梯度清零

模型再复杂,这个骨架也不变------第一篇CIFAR-10训练循环里的zero_grad → forward → backward → step就是它的优化器封装版。手动求导版本中最容易写错的部分(对矩阵求导、转置、求和的顺序)被autograd整个接管,这正是现代深度学习框架的核心生产力。

总结

  • requires_grad:求导开关,具有传染性------依赖需要求导的Tensor的一切运算结果都会被追踪。
  • 计算图 :椭圆是变量、矩形是算子的DAG;grad_fn记录生成运算,next_functions串联上游;PyTorch是动态图,每次前向重新构建,支持原生Python控制流。
  • backward :非标量输出要传grad_tensors;多次反向传播需retain_graph=Truecreate_graph=True可求高阶导。
  • 叶子节点 :用户直接创建的Tensor;grad_fn为None;只有需要求导的叶子节点的梯度会保留在.grad中;.cuda()等运算会让结果变成非叶子节点。
  • 梯度累加 :backward是累加而非覆盖,训练循环每轮必须zero_grad();这一设计也支持了小batch梯度累积等技巧。
  • 关闭求导 :推理用with t.no_grad();切断单个变量用detach();尽量不要用.data直接改值,错误会静默发生。
  • 中间梯度 :非叶子节点梯度默认被清空,可用autograd.grad或hook获取。
  • 自定义Function :静态的forward/backward、ctx.save_for_backward缓存中间值、用apply()调用。

到这里,Tensor(数据)和autograd(梯度)两块地基都已打牢。**下一篇《nn模块详解:用积木搭建神经网络》**将进入更高层的抽象:nn.Module的参数管理机制、常用层与损失函数、Sequential等容器,以及如何优雅地组织一个真正的深度网络。

相关推荐
过期的秋刀鱼!1 小时前
倾斜数据集的错误指标-混淆矩阵
人工智能·python·深度学习·机器学习·概率论·模型评估
math_hongfan1 小时前
鸿蒙企业级数据存储高级架构:从读写分离到冷热数据分层/归档策略/数据生命周期管理最佳实践
人工智能·学习·华为·架构·harmonyos·鸿蒙
南方程序猴1 小时前
Windows一键安装Codex所有环境
人工智能·gpt·ai·chatgpt·ai编程
MartinYeung51 小时前
[论文学习]LLM智能体无法保密:多智能体系统中的隐私评估
人工智能·学习
only-qi1 小时前
大模型应用如何保证实时性和多轮对话一致性
人工智能·llm
数据智研1 小时前
【数据分享】中国民政统计年鉴(1949-2025)
大数据·人工智能·信息可视化·数据分析
探物 AI1 小时前
yolov6的骨干网络组件18:RepVGG,多分支进行训练,单分支进行推理(我有了SCI的主意)
网络·深度学习·yolo
武子康1 小时前
GPT-Live 可能怎样实现:从公开行为到架构约束的证据梯度
人工智能
问商十三载1 小时前
大模型采信内容看的不是字数?2026语义信噪比模型深度解析
人工智能