学习卷积神经网络

举个例子 最早的卷积网络lenet

**LeNet在Fashion-MNIST数据集上的表现**

nn.Flatten是保留第一维批量维度的情况下,其他维度全部展平

python 复制代码
import torch
from torch import nn
from d2l import torch as d2l

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Flatten(),
    nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.Sigmoid(),
    nn.Linear(84, 10))

16*5*5是展平后所有的特征输入

注意:pytorch中使用交叉熵作为损失函数会自动在最后加上softmax不用自己定义

84是lenet模型作者原先定义的,你也可以根据你自己定义的模型修改

结果:查看

数据

python 复制代码
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

求精度 不需要梯度 所以eval 外加 with torch.no_grad() 该函数在训练函数中被调用来统计:

python 复制代码
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
    """使用GPU计算模型在数据集上的精度"""
    if isinstance(net, nn.Module):
        net.eval()  # 设置为评估模式
        if not device:
            device = next(iter(net.parameters())).device
#如果没传设备,就找网络的第一个参数的设备
    # 正确预测的数量,总预测的数量
    metric = d2l.Accumulator(2)
    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(X, list):
                # BERT微调所需的(之后将介绍)
                X = [x.to(device) for x in X]
            else:
                X = X.to(device)
            y = y.to(device)
            metric.add(d2l.accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]
python 复制代码
逐行解释
def evaluate_accuracy_gpu(net, data_iter, device=None):​
定义函数,接收三个参数:
net:待评估的神经网络模型。
data_iter:数据迭代器,提供测试集的批量数据。
device:指定运行的设备(CPU或GPU),若不提供则自动从模型参数推断。
if isinstance(net, nn.Module):​
检查 net是否是 nn.Module的实例(几乎总是 True)。
net.eval()​
将模型切换到评估模式。这会关闭 Dropout、BatchNorm 的训练行为(如 BatchNorm 停止更新均值和方差,使用固定的 running stats)。
if not device:​
如果没有指定设备,则自动获取模型参数的设备:next(iter(net.parameters())).device。
metric = d2l.Accumulator(2)​
创建一个累加器,用于累计两个数值:正确预测的数量(accuracy 返回值)和总样本数(y.numel())。Accumulator是 d2l 提供的工具类,支持 add方法累加,通过下标 [0]、[1]访问。
with torch.no_grad():​
在该上下文内,所有计算都不会记录梯度,节省显存并加速推理。
for X, y in data_iter:​
遍历测试集的每个批次。
if isinstance(X, list):​
某些模型(如 BERT)的输入可能是一个列表(例如 token_ids, attention_mask 等),需要逐个移到设备上。否则直接 X = X.to(device)。
y = y.to(device)​
标签也移到相同设备。
metric.add(d2l.accuracy(net(X), y), y.numel())​
net(X):前向传播,得到预测 logits。
d2l.accuracy(net(X), y):计算该批次中预测正确的样本数(返回整数)。
y.numel():该批次的样本总数。
metric.add(...):将这两个数值累加到累加器中。
return metric[0] / metric[1]​
返回总体准确率 = 总正确数 / 总样本数。

训练函数

python 复制代码
#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
    """用GPU训练模型(在第六章定义)"""
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on', device)
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = d2l.Timer(), len(train_iter)
    for epoch in range(num_epochs):
        # 训练损失之和,训练准确率之和,样本数
        metric = d2l.Accumulator(3)
        net.train()
        for i, (X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            with torch.no_grad():
                metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (train_l, train_acc, None))
        test_acc = evaluate_accuracy_gpu(net, test_iter)
        animator.add(epoch + 1, (None, None, test_acc))
    print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
          f'test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(device)}')
python 复制代码
逐段解释
函数头部与初始化
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):​
参数:
net:模型
train_iter:训练数据迭代器
test_iter:测试数据迭代器
num_epochs:训练的轮数
lr:学习率
device:设备
def init_weights(m):​
定义一个内部函数,用于初始化模型各层的权重。若层是 Linear或 Conv2d,则使用 Xavier 均匀初始化(xavier_uniform_)。
net.apply(init_weights)​
递归地将 init_weights应用到 net的每个子模块上,完成权重初始化。
net.to(device)​
将模型移动到指定设备(GPU或CPU)。
optimizer = torch.optim.SGD(net.parameters(), lr=lr)​
使用 SGD 优化器,学习率为 lr。
loss = nn.CrossEntropyLoss()​
交叉熵损失函数,适用于多分类任务。
animator = d2l.Animator(...)​
d2l 的可视化工具,用于实时绘制训练曲线(损失、训练准确率、测试准确率)。
timer, num_batches = d2l.Timer(), len(train_iter)​
计时器和训练批次总数。
主训练循环
for epoch in range(num_epochs):​
外层循环,每个 epoch 遍历一次训练集。
metric = d2l.Accumulator(3)​
累加器,记录三个值:总损失(loss * batch_size)、正确预测数、总样本数。
net.train()​
切换到训练模式,启用 Dropout、BatchNorm 等训练行为。
for i, (X, y) in enumerate(train_iter):​
内层循环,遍历每个 mini-batch。
timer.start()​
开始计时(用于统计每秒处理的样本数)。
optimizer.zero_grad()​
清空上一轮的梯度。
X, y = X.to(device), y.to(device)​
将数据移到 GPU。
y_hat = net(X)​
前向传播,得到预测 logits。
l = loss(y_hat, y)​
计算该批次的损失(标量)。
l.backward()​
反向传播,计算梯度。
optimizer.step()​
更新模型参数。
with torch.no_grad():​
以下操作不追踪梯度(仅用于统计)。
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])​
l * X.shape[0]:该批次的总损失(因为 l是平均损失,乘以样本数得到总和)。
d2l.accuracy(y_hat, y):该批次正确预测的样本数。
X.shape[0]:该批次的样本数。
三者累加。
timer.stop()​
停止计时(实际累加时间)。
train_l = metric[0] / metric[2]​
当前 epoch 的平均训练损失 = 总损失 / 总样本数。
train_acc = metric[1] / metric[2]​
当前 epoch 的训练准确率。
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:​
每完成大约 1/5 的批次或最后一个批次时,更新动画曲线(显示训练损失和准确率,测试准确率暂时为 None)。
每个 epoch 结束后
test_acc = evaluate_accuracy_gpu(net, test_iter)​
调用之前定义的评估函数,计算在整个测试集上的准确率。
animator.add(epoch + 1, (None, None, test_acc))​
在动画中添加当前 epoch 的测试准确率点。
训练结束后的输出
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, test acc {test_acc:.3f}')​
打印最终的损失、训练准确率和测试准确率。
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on {str(device)}')​
打印训练速度:总处理样本数 / 总耗时(单位:样本/秒)。

使用gpu

python 复制代码
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
概念 作用
net.eval()vs net.train() 控制 Dropout/BatchNorm 的行为。评估时禁用 dropout,BN 使用固定均值方差。
torch.no_grad() 禁用梯度计算,减少内存消耗,加速推理。
Accumulator 自定义的累加器,用于累积多个数值,便于计算平均值。
xavier_uniform_ 权重初始化方法,使每层输出的方差保持一致,缓解梯度消失/爆炸。
CrossEntropyLoss 内部包含 Softmax+ 负对数似然,适用于多分类。
Animator d2l 的可视化工具,动态绘制训练曲线。

eval就是关闭模型中的dropout功能,调到评价模式;与之相对的是train()

eval的作用是把模型调整到验证状态,不调整权重

with no grad 用在测试层,不需要梯度下降求导,减少内存开销,eval也用于测试层,但不一样的

理解基本正确,但有一些细微之处需要澄清。下面是详细的区别说明:


1. model.eval()的作用

本质 :切换模型的运行模式(训练模式 → 评估模式)。

影响

  • Dropout关闭。在评估模式下,Dropout 不再随机丢弃神经元,而是保留所有神经元,但输出会乘以保留概率(等同于缩放)。

  • BatchNorm :使用训练阶段积累的全局均值和方差,而不是当前 batch 的统计量;同时停止更新 running_mean/running_var。

  • 其他层:某些自定义层也可能依赖此标志。

注意

  • eval()不会禁用梯度计算,也不会清空梯度。它只改变特定层的行为。

  • 在评估模式下,如果仍然执行反向传播,梯度依然会计算(虽然通常没必要)。


2. torch.no_grad()的作用

本质 :一个上下文管理器 ,在其作用域内,所有张量操作都不再跟踪梯度

影响

  • 梯度计算:完全禁用 autograd 引擎,任何操作都不会构建计算图,因此不会占用显存存储中间变量。

  • 性能:大幅降低内存消耗,加速前向推理(因为无需记录梯度相关信息)。

  • 反向传播 :不可能在此作用域内调用 .backward()(因为没有计算图)。

注意

  • no_grad()不会影响 Dropout 或 BatchNorm 的行为------它只控制梯度追踪。

  • 即使在 model.eval()模式下,如果不使用 no_grad(),梯度依然会被记录(尽管通常你不会去用它)。

3. 常见误区纠正

  • **"eval()会清零梯度"**​ ❌

    梯度清零需要手动调用 optimizer.zero_grad()model.zero_grad()eval()不做这件事。

  • **"eval()会禁用梯度计算"**​ ❌

    eval()模式下,如果你不小心调用了 .backward(),梯度仍然会被计算(虽然逻辑上不应该这样做)。

  • **"no_grad()会关闭 Dropout"**​ ❌

    no_grad()下,如果模型处于 train()模式,Dropout 依然会生效(随机丢弃),但这在推理时通常是不希望的。因此需要先用 eval()关闭 Dropout。

现代卷积神经网络:

残差网络(ResNet-18)

何恺明等人 2015年的ImageNet图像识别挑战赛夺魁

它通过残差块构建跨层的数据通道,是计算机视觉中最流行的体系架构

包含了残差块:

python 复制代码
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l


class Residual(nn.Module):  #@save
    def __init__(self, input_channels, num_channels,
                 use_1x1conv=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels,
                               kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels,
                               kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels,
                                   kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)

    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)

init:
input_channels:输入特征图的通道数。
num_channels:输出特征图的通道数(也是中间卷积层的输出通道数)。
use_1x1conv:是否使用 1×1 卷积来调整跳跃连接的通道数或空间尺寸(默认 False)。
strides:第一个卷积层的步长(默认 1),可用于下采样。

conv1:第一个 3×3 卷积,将通道数从 input_channels变为 num_channels,步长为 strides(可能下采样)。
conv2:第二个 3×3 卷积,保持通道数不变(num_channels → num_channels),步长为 1,padding=1 保证空间尺寸不变。

当 use_1x1conv=True时,创建一个 1×1 卷积层,用于将输入 X的通道数从 input_channels变为 num_channels,且步长与 conv1相同(strides)。这样可以使跳跃连接的输出与主路径的输出形状一致,便于相加。
如果 use_1x1conv=False,则 conv3 = None,此时要求 input_channels == num_channels且 strides == 1,否则无法直接相加(形状不匹配)。

两个 BatchNorm 层,分别跟在 conv1和 conv2之后,用于加速训练和稳定梯度。

前向传播:
主路径:
X经过 conv1→ bn1→ ReLU,得到 Y。
Y再经过 conv2→ bn2,得到 Y(此处不加 ReLU,因为后面还要加跳跃连接后再激活)。
跳跃连接:
如果 conv3存在(即 use_1x1conv=True),则将原始输入 X通过 conv3调整形状,得到与 Y相同尺寸的张量。
否则,直接使用原始 X(要求形状已匹配)。
残差相加:Y += X(逐元素相加)。
最终激活:F.relu(Y)输出。
python 复制代码
blk = Residual(3, 3)              # input_channels=3, num_channels=3, use_1x1conv=False, strides=1
X = torch.rand(4, 3, 6, 6)       # 批量大小4,通道3,高6,宽6
Y = blk(X)
Y.shape                           # 输出形状

如果使用 use_1x1conv=Truestrides>1

python 复制代码
blk2 = Residual(3, 6, use_1x1conv=True, strides=2)
X2 = torch.rand(4, 3, 12, 12)
Y2 = blk2(X2)
Y2.shape  # torch.Size([4, 6, 6, 6])
  • 输入 (4, 3, 12, 12),经过 conv1(步长2) → 输出 (4, 6, 6, 6)(空间减半,通道翻倍)。

  • conv2保持 (4, 6, 6, 6)

  • 跳跃连接conv3(1×1 ,步长2)将输入 (4, 3, 12, 12)调整为 (4, 6, 6, 6)

  • 相加后输出 (4, 6, 6, 6)

|-----------------------|--------------------------|
| 跳跃连接(恒等映射或 1×1 投影/卷积) | 让梯度可以直接流过,缓解退化问题 |
| use_1x1conv | 当通道数或空间尺寸不匹配时,用 1×1 卷积对齐 |

模型代码:(经典)

python 复制代码
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                   nn.BatchNorm2d(64), nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

nn.BatchNorm2d(num_features)   # 用于卷积层输出的归一化(输入形状: (N, C, H, W))
其他常见归一化层:
nn.BatchNorm1d(num_features)-- 用于全连接层或1D数据。
nn.LayerNorm(normalized_shape)-- 层归一化(常用于 Transformer)。
nn.InstanceNorm2d(num_features)-- 实例归一化(风格迁移常用)。
nn.GroupNorm(num_groups, num_channels)-- 分组归一化。

def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
    blk = []
    for i in range(num_residuals):#表示残差块组
        if i == 0 and not first_block:
            # 如果不是第一个块组(first_block=False),且是该块组的第一个残差块(i==0)
            # 则需要下采样(strides=2)并用 1x1 卷积调整通道数
            blk.append(Residual(input_channels, num_channels,
                                use_1x1conv=True, strides=2))
        else:
            # 否则:要么是第一个块组(first_block=True)的所有残差块,
            # 要么是其他块组中非第一个残差块(i>0),
            # 都保持空间尺寸和通道数不变
            blk.append(Residual(num_channels, num_channels))
    return blk

i == 0:判断当前是否是该块组内的第一个残差块(局部位置)。
first_block:判断当前块组是否是整个网络的第一个残差块组(全局标识)。
2. 为什么需要同时使用?
目标:只在非第一个块组的第一个残差块中进行下采样和通道变换。
如果是第一个块组(first_block=True),那么即使 i==0,也不下采样,因为 b1已经做过下采样了,b2需要保持尺寸。
如果是后续块组(first_block=False),则只在 i==0时下采样,后续残差块(i>0)保持尺寸。

b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

这里的 *是 Python 的一个内置运算符,叫做解包运算符(Unpacking Operator)。
简单来说,它的作用是把列表(List)或元组(Tuple)里的元素一个个拿出来,作为独立的参数传给函数。
1. 为什么要用 *
这是由 PyTorch 的 nn.Sequential类的特性决定的。
nn.Sequential的要求:它希望你传入的是一连串的独立层(Layer),比如 nn.Conv2d(...), nn.ReLU(), nn.Conv2d(...)。
resnet_block的返回:看上一行代码 return blk,blk是一个列表(List),里面装着两个 Residual对象(比如 [layer1, layer2])。

如果你直接写 nn.Sequential(resnet_block(...)),相当于把整个列表当作第一个元素塞进去了,PyTorch 会报错,因为它期望里面是层,而不是一个列表。

假设 blk的内容是 [A, B]。
❌ 不使用 *:
nn.Sequential(blk)
实际传入的是:nn.Sequential([[A, B]])
结果:PyTorch 认为第一个模块是一个包含 A 和 B 的列表,不是网络层,报错。


net = nn.Sequential(b1, b2, b3, b4, b5,
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten(), nn.Linear(512, 10))
nn.AdaptiveAvgPool2d((1, 1))是 PyTorch 中的自适应平均池化层,它的作用是将任意大小的输入特征图强制池化为指定的输出尺寸(这里是 1×1)。它通过自动计算池化窗口大小和步长来实现,无需手动指定 kernel_size 和 stride。 常用于分类网络的最后,将特征图压缩成固定长度的向量,再接入全连接层。


import os
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
模块 下采样次数 通道变化 目的
b1 2 次(卷积+池化) 1→64 快速降分辨率,提取低级特征
b2 0 次 64→64 在中等分辨率下深化特征,不做下采样
b3/b4/b5 各 1 次 64→128→256→512 逐步降低分辨率、增加通道数,提取高级语义

从b2开始,每个block有两个残差块,除了第一个block两个残差块的输入与输出通道相同,其余的block都是第一个残差块通道加倍大小减半,第二个残差块输入输出通道不变

使用gpu常见错误:

1 OutOfMemoryError:

CUDA out of memory. Tried to allocate 144.00 MiB. GPU 0 has a total capacity of 8.00 GiB of which 6.87 GiB is free. Of the allocated memory 51.69 MiB is allocated by PyTorch, and 12.31 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)

CUDA out of memory错误是因为 显存不足。从错误信息看,你的 GPU 总容量为 8 GB,空闲约 6.87 GB,但训练时需要额外分配 144 MB 时失败了。这通常是由于模型或数据太大、batch size 过大导致的

解决:

import os

os.environ'PYTORCH_CUDA_ALLOC_CONF' = 'expandable_segments:True'

  1. 使用 torch.cuda.memory_summary()诊断

可以打印详细的显存使用情况,帮助定位哪个操作占用最多:

复制代码
print(torch.cuda.memory_summary())
  1. 减少网络中的中间变量
  • 使用 inplace=True的激活函数(如 F.relu(..., inplace=True))可节省显存。

  • 使用 checkpoint(梯度检查点)技术,用时间换空间。

import os

os.environ'PYTORCH_CUDA_ALLOC_CONF' = 'expandable_segments:True'

lr, num_epochs, batch_size = 0.05, 10, 256

train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)

d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

2 RuntimeError: DataLoader worker (pid(s) 8568, 25304, 12044, 29264) exited unexpectedly

遇到的 DataLoader worker意外退出,根本原因仍然是 内存不足 (这次可能是系统 RAM 不够,而非 GPU 显存)。你仍然在使用 batch_size=256resize=96,这对你的硬件(8GB GPU + 推测有限的系统内存)来说负担太重

错误链分析

  1. DataLoader 使用多进程加载数据 (默认 num_workers=4),每个 worker 子进程需要将图像从 28×28 放大到 96×96,并批量组织成 tensor。

  2. 每个 worker 都需要占用一定量的系统内存(RAM)来缓存预处理后的图像。当 batch_size 很大(256)且图像尺寸放大后,每个 worker 的内存需求急剧上升。

  3. 系统内存(RAM)耗尽,导致 worker 进程被操作系统强制终止(exited unexpectedly)。

  4. 主进程检测到 worker 死亡,抛出 RuntimeError

为什么 expandable_segments没用?

  • 该选项只针对 CUDA 显存管理,且你的 Windows 平台不支持(警告已提示)。

  • 当前错误是系统内存(RAM)不足,与 GPU 显存无关。

错误总结:

错误 根本原因 解决措施
CUDA OOM GPU 显存碎片化 + 大尺寸/大 batch 减小 resize 和 batch_size
DataLoader worker 退出 系统 RAM 不足 减小 batch_size、减小 resize、减少 num_workers
相关推荐
Nile1 小时前
解密Palantir系列二:1.Foundry · 数据操作系统
大数据·人工智能·ai·ai编程·ai-native
AI服务老曹1 小时前
统一接入百家私有协议:基于 Docker 容器化的 GB28181/RTSP 边缘计算视频中台架构解析(附全源码交付)
人工智能·docker·边缘计算
TMT星球1 小时前
AI定义汽车,赛豆科技发布AI汽车品牌AIVA
人工智能·科技·汽车
在水一缸1 小时前
AI 搜索新纪元:Perplexity 与 SearchGPT 如何颠覆传统搜索
人工智能·搜索引擎·大模型·信息检索·ai搜索·perplexity·searchgpt
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年6月9日
人工智能·python·ai·信息可视化·自然语言处理·ai编程·灵砚智能
keykey6.1 小时前
循环神经网络(RNN)与序列模型:让AI学会“记忆“
开发语言·人工智能·深度学习·机器学习
夜雪闻竹1 小时前
5 种 AI 对话数据格式全解析
人工智能·aigc·ai编程·ai-native·chatcrystal
东方佑1 小时前
递归创世:条件随机、自指递归与分形——一个贯穿真实世界、自然语言和大型语言模型的统一原理
人工智能·语言模型·自然语言处理
jinxindeep3 小时前
CVPR26最佳论文提名:NitroGen,面向通用游戏智能体的 视觉-动作基础模型
人工智能·游戏