这类主题最值得先看的不是理论推导,而是它到底能不能帮你把一堆抽象概念,比如“生成对抗网络”、“变分自编码器”和“磁存储”,串成一个能动手、能理解的完整链条。很多人学生成式AI,模型原理背得滚瓜烂熟,但一到自己跑代码、调参数,或者想理解模型权重是怎么从硬盘里“变”出来的,就卡住了。这篇文章就围绕“从模型原理到真实图像生成,再到理解数据如何被物理存储”这条线,拆解成一个有先后顺序的实操认知路径。它适合两类人:一是想系统理解GAN和VAE,并能动手生成图片的开发者;二是对“模型文件存在哪、怎么存、底层硬件如何工作”感到好奇的技术爱好者。最关键的价值在于,它把上层算法和底层数据根基联系了起来,让你知道每一次 model.load_state_dict() 背后,到底发生了什么。

1. 先理清核心目标:不是学理论,而是建立“生成-存储”的完整工作流

在开始配置环境或跑代码之前,先明确我们这次要打通的关键环节。生成式人工智能,特别是图像生成,其工作流可以粗略分为“创造”和“留存”两部分。

“创造”部分 ,核心是两种主流模型:

  • 生成对抗网络 :你可以把它想象成一场“造假者”和“鉴宝专家”的竞赛。造假者(生成器)不断学习画假画,鉴宝专家(判别器)则努力分辨画作的真假。两者在对抗中共同进步,最终生成器能画出以假乱真的图像。它的特点是生成的图像通常细节丰富、清晰度高,但训练过程可能不稳定。
  • 变分自编码器 :它更像一个“压缩-重建”工程师。先把一张真实图像压缩成一个概率分布(潜空间中的一点),然后再从这个分布中采样并重建出图像。它的优势是潜空间连续、规整,容易做图像插值、编辑,并且训练相对稳定,但有时生成的图像可能会模糊一些。

“留存”部分 ,核心是 磁存储原理 。这关乎你训练好的生成器、判别器、编码器、解码器的权重参数,以及你生成的海量图片,最终以何种物理形式存在于你的电脑或服务器中。理解这一点,你才能明白为什么加载大模型有时很慢,为什么数据集放在固态硬盘和机械硬盘上训练速度不同,以及“虚拟世界的数据”到底建立在怎样的物理根基之上。

所以,我们的实操路径是: 理解两种生成模型的核心思想 -> 准备一个能跑起来的编程环境 -> 用简单的GAN和VAE生成手写数字图片 -> 观察并保存生成的图片和模型 -> 最后探讨这些“成果”是如何被写入和读取自磁存储介质的 。这个顺序能确保每个环节都有明确的输出和可验证的结果。

2. 环境准备:聚焦最小可行依赖,避开版本地狱

动手的第一步永远是搭建一个干净、可复现的环境。对于生成式AI入门,我建议从最轻量的开始,避免一上来就折腾复杂的深度学习框架和CUDA版本。

2.1 核心工具选择

  • 编程语言 Python 。这是深度学习领域的事实标准,生态最全。
  • 深度学习框架 PyTorch 。对于研究和学习而言,PyTorch的动态图更直观,调试更方便,社区活跃,非常适合实现GAN和VAE这类模型。
  • 辅助库
    • NumPy :数值计算基础。
    • Matplotlib :用于可视化我们生成的图片。
    • Torchvision :PyTorch的视觉工具包,方便我们下载和预处理数据集(如MNIST手写数字)。

2.2 具体环境搭建步骤

我强烈建议使用 conda venv 创建独立的虚拟环境,避免污染系统Python环境。

# 使用 conda 创建环境(假设环境名称为 gen_ai_basic)
conda create -n gen_ai_basic python=3.9
conda activate gen_ai_basic

# 安装 PyTorch(请根据你的CUDA版本去官网获取最新安装命令,这里以CPU版本为例)
# 访问 https://pytorch.org/get-started/locally/ 获取适合你系统的命令
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

# 安装其他依赖
pip install numpy matplotlib

为什么这么选?

  • Python 3.9 :这是一个在稳定性和新特性之间取得较好平衡的版本,兼容性广。
  • PyTorch CPU版本 :对于MNIST这种小数据集和小模型,CPU训练完全足够,可以绕过GPU驱动和CUDA安装的坑。我们的首要目标是理解流程和原理。
  • 先装PyTorch :因为它体积大,可能涉及特定索引源,先安装它可以减少后续依赖冲突。

2.3 验证环境

创建一个简单的Python脚本 test_env.py 来验证:

import torch
import torchvision
import numpy as np
import matplotlib.pyplot as plt

print(f"PyTorch 版本: {torch.__version__}")
print(f"Torchvision 版本: {torchvision.__version__}")
print(f"CUDA 是否可用: {torch.cuda.is_available()}") # 如果是CPU版本,这里会是False,没关系。

# 尝试创建一个张量
x = torch.randn(2, 3)
print(f"随机张量:\n{x}")

运行它,如果没有报错并正确输出版本信息,说明基础环境OK。

3. 实战生成对抗网络:从零构建一个“数字造假工厂”

现在,我们用一个最经典的例子——在MNIST手写数字数据集上训练一个GAN,来生成新的手写数字。

3.1 理解GAN的训练循环

在写代码前,必须清楚GAN每一步在做什么。下面这个表格概括了一个训练批次(batch)内的关键步骤:

步骤 生成器 (G) 判别器 (D) 核心目标
1. 准备数据 接收随机噪声 z 接收 真实图片 real_imgs 生成图片 fake_imgs 为对抗提供素材
2. 前向传播 z 转换为 fake_imgs 判断 real_imgs fake_imgs 的真假,输出概率值 生成假图,并进行判别
3. 计算损失 计算生成器损失:希望 D(fake_imgs) 接近1(骗过D) 计算判别器损失:
- 希望 D(real_imgs) 接近1(认对真的)
- 希望 D(fake_imgs) 接近0(认出假的)
量化“造假”和“鉴真”的水平
4. 反向传播与更新 根据生成器损失, 更新G的参数 根据判别器损失, 更新D的参数 让G更会造假,让D更会鉴真

关键点 :在PyTorch中,通常先更新判别器,再更新生成器。并且,计算生成器损失时,需要 固定判别器的参数 ,只让生成器的梯度流动。

3.2 代码实现分解

我们搭建一个简单的全连接网络GAN。代码结构如下:

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

# 1. 定义生成器 (Generator)
class Generator(nn.Module):
    def __init__(self, latent_dim=100, img_shape=(784,)):
        super(Generator, self).__init__()
        self.img_shape = img_shape
        self.model = nn.Sequential(
            nn.Linear(latent_dim, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 1024),
            nn.LeakyReLU(0.2),
            nn.Linear(1024, int(torch.prod(torch.tensor(img_shape)))),
            nn.Tanh() # 输出范围在[-1, 1],与预处理后的图片数据范围匹配
        )

    def forward(self, z):
        img = self.model(z)
        img = img.view(img.size(0), *self.img_shape) # 重塑为图片形状
        return img

# 2. 定义判别器 (Discriminator)
class Discriminator(nn.Module):
    def __init__(self, img_shape=(784,)):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(int(torch.prod(torch.tensor(img_shape))), 1024),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid() # 输出一个0到1的概率值,表示图片为真的置信度
        )

    def forward(self, img):
        img_flat = img.view(img.size(0), -1)
        validity = self.model(img_flat)
        return validity

# 3. 数据准备
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,)) # 将像素值从[0,1]归一化到[-1,1]
])

train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

# 4. 初始化模型、优化器、损失函数
latent_dim = 100
img_shape = (1, 28, 28) # MNIST图片通道1,高28,宽28

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
generator = Generator(latent_dim, img_shape).to(device)
discriminator = Discriminator(img_shape).to(device)

adversarial_loss = nn.BCELoss() # 二元交叉熵损失
optimizer_G = optim.Adam(generator.parameters(), lr=0.0002, betas=(0.5, 0.999))
optimizer_D = optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0.5, 0.999))

# 5. 训练循环
num_epochs = 50
for epoch in range(num_epochs):
    for i, (real_imgs, _) in enumerate(train_loader):
        batch_size = real_imgs.size(0)
        real_imgs = real_imgs.to(device)

        # 创建标签
        valid = torch.ones(batch_size, 1, device=device) # 真图片标签为1
        fake = torch.zeros(batch_size, 1, device=device) # 假图片标签为0

        # ---------------------
        #  训练判别器
        # ---------------------
        optimizer_D.zero_grad()

        # 计算真实图片的损失
        real_loss = adversarial_loss(discriminator(real_imgs), valid)

        # 生成假图片
        z = torch.randn(batch_size, latent_dim, device=device)
        gen_imgs = generator(z)

        # 计算假图片的损失
        fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)

        # 判别器总损失
        d_loss = (real_loss + fake_loss) / 2
        d_loss.backward()
        optimizer_D.step()

        # -----------------
        #  训练生成器
        # -----------------
        optimizer_G.zero_grad()

        # 生成器希望生成的图片被判别为真
        z = torch.randn(batch_size, latent_dim, device=device)
        gen_imgs = generator(z)
        g_loss = adversarial_loss(discriminator(gen_imgs), valid)

        g_loss.backward()
        optimizer_G.step()

        # 每隔一些批次打印一次损失
        if i % 200 == 0:
            print(f"[Epoch {epoch}/{num_epochs}] [Batch {i}/{len(train_loader)}] [D loss: {d_loss.item():.4f}] [G loss: {g_loss.item():.4f}]")

    # 每个epoch结束后,保存一些生成的图片
    if epoch % 10 == 0:
        with torch.no_grad():
            z = torch.randn(16, latent_dim, device=device)
            gen_imgs = generator(z).cpu()
            # 将图片从[-1,1]反归一化回[0,1]以便显示
            gen_imgs = 0.5 * gen_imgs + 0.5
            # ... 保存或显示gen_imgs的代码 ...

3.3 关键参数与现象解读

  • 潜在维度 latent_dim :输入生成器的随机噪声向量的长度。可以理解为“创意种子”的丰富程度。太小可能导致生成多样性不足,太大会增加训练难度。100是一个常用的起始值。
  • 学习率 lr 和优化器参数 betas :GAN训练 notoriously tricky( notoriously tricky 以难以训练著称)。使用Adam优化器并设置较小的学习率(如0.0002)和特定的betas参数(如(0.5, 0.999))是实践中稳定训练的常见技巧。
  • 判别器中的 Dropout :一种正则化技术,随机“丢弃”一部分神经元,防止判别器过强导致生成器无法学习。如果发现判别器损失很快降到0而生成器损失很高,可以适当增加Dropout率。
  • 损失值观察 :GAN的训练损失没有明确的“越低越好”的指标。理想情况是判别器损失和生成器损失在动态博弈中震荡,最终都维持在一个相对稳定的值。如果判别器损失一直为0,说明生成器太弱;如果生成器损失一直为0,可能是模式崩溃(生成器只学会生成少数几种样本)。

注意 :第一次跑,建议把 num_epochs 设为5或10,先快速看下流程能否跑通,损失是否有变化,是否能输出一些模糊的像素块。确认无误后再进行长时间训练。

4. 实战变分自编码器:构建一个“数字压缩与重建引擎”

VAE的思路与GAN不同,它旨在学习数据的潜空间分布。我们同样用MNIST来实现。

4.1 理解VAE的核心:重参数化技巧

VAE的编码器输出不是潜空间的一个点,而是两个向量:均值 mu 和对数方差 log_var 。这定义了一个高斯分布。为了从这个分布中采样并能够反向传播,我们使用重参数化技巧: z = mu + epsilon * exp(log_var * 0.5) ,其中 epsilon 来自标准正态分布。这样,随机性由 epsilon 承担,而 mu log_var 是可导的。

4.2 代码实现分解

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt

# 1. 定义VAE模型
class VAE(nn.Module):
    def __init__(self, latent_dim=20):
        super(VAE, self).__init__()
        self.latent_dim = latent_dim

        # 编码器
        self.encoder_fc1 = nn.Linear(784, 400)
        self.encoder_fc21 = nn.Linear(400, latent_dim) # 输出均值 mu
        self.encoder_fc22 = nn.Linear(400, latent_dim) # 输出对数方差 log_var

        # 解码器
        self.decoder_fc1 = nn.Linear(latent_dim, 400)
        self.decoder_fc2 = nn.Linear(400, 784)

    def encode(self, x):
        h1 = F.relu(self.encoder_fc1(x))
        return self.encoder_fc21(h1), self.encoder_fc22(h1)

    def reparameterize(self, mu, log_var):
        std = torch.exp(0.5 * log_var)
        eps = torch.randn_like(std)
        return mu + eps * std

    def decode(self, z):
        h3 = F.relu(self.decoder_fc1(z))
        return torch.sigmoid(self.decoder_fc2(h3)) # 输出像素值在[0,1]

    def forward(self, x):
        mu, log_var = self.encode(x.view(-1, 784))
        z = self.reparameterize(mu, log_var)
        return self.decode(z), mu, log_var

# 2. 定义损失函数:重构损失 + KL散度
def loss_function(recon_x, x, mu, log_var):
    BCE = F.binary_cross_entropy(recon_x, x.view(-1, 784), reduction='sum')
    # KL散度:让学到的分布接近标准正态分布
    KLD = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
    return BCE + KLD

# 3. 数据准备(与GAN相同)
transform = transforms.Compose([transforms.ToTensor()]) # VAE输出是sigmoid,输入范围[0,1]即可
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)

# 4. 初始化与训练
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = VAE(latent_dim=20).to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)

num_epochs = 20
for epoch in range(num_epochs):
    model.train()
    train_loss = 0
    for batch_idx, (data, _) in enumerate(train_loader):
        data = data.to(device)
        optimizer.zero_grad()
        recon_batch, mu, log_var = model(data)
        loss = loss_function(recon_batch, data, mu, log_var)
        loss.backward()
        train_loss += loss.item()
        optimizer.step()

    print(f'Epoch {epoch}, Loss: {train_loss / len(train_loader.dataset):.4f}')

    # 每个epoch结束后,可视化重建效果
    model.eval()
    with torch.no_grad():
        sample = next(iter(train_loader))[0][:8].to(device)
        recon, _, _ = model(sample)
        # 对比显示原始图片和重建图片...

4.3 VAE与GAN的直观对比与选择

训练完成后,你可以直观地对比两者:

特性 GAN VAE
训练目标 对抗博弈,生成数据分布 最大化证据下界,学习潜空间分布
输出质量 通常更清晰、锐利 ,细节丰富 有时 相对模糊 ,倾向于生成“平均化”结果
潜空间 通常不规整,难以解释和插值 连续、规整 ,易于进行向量运算和插值
训练稳定性 较不稳定 ,易出现模式崩溃、梯度消失 相对稳定 ,有明确的损失函数指导
主要应用 高保真图像生成、风格迁移 数据生成、去噪、插值、特征学习

如何选择?

  • 如果你的首要目标是 生成尽可能逼真、高质量的图片 ,并且愿意花时间调参稳定训练,首选GAN及其变体(如DCGAN, StyleGAN)。
  • 如果你需要 一个结构化的潜空间来做图像编辑、插值、或进行可控生成 ,或者希望训练过程更稳定、可复现,VAE是更好的起点。
  • 在实际项目中,两者也常结合使用(如VAE-GAN)。

5. 成果留存:模型与数据的保存,以及磁存储原理的关联

当你训练好一个模型并生成了图片,下一步就是保存它们。这时,操作系统的文件系统调用会最终将数据写入硬盘。理解这个过程,就触及了“磁存储底层原理”。

5.1 如何保存你的工作成果

保存生成的图片:

# 假设 gen_imgs 是你生成的一批图片张量,形状为 [N, C, H, W],值范围[0,1]
from torchvision.utils import save_image
# 保存单张图片
save_image(gen_imgs[0], 'generated_digit_0.png')
# 保存一个图片网格(例如4x4)
save_image(gen_imgs[:16], 'generated_grid.png', nrow=4, normalize=True)

save_image 函数最终会调用PIL库将张量转换为像素数组,并编码成PNG等格式的二进制文件,写入磁盘。

保存训练好的模型:

# 保存整个模型(包含结构和参数)
torch.save(generator.state_dict(), 'generator.pth')
torch.save(discriminator.state_dict(), 'discriminator.pth')
# 或者保存整个VAE模型
torch.save(vae_model.state_dict(), 'vae.pth')

# 加载模型
generator = Generator(latent_dim, img_shape).to(device)
generator.load_state_dict(torch.load('generator.pth'))
generator.eval() # 切换到评估模式

torch.save 默认使用Python的pickle协议将模型的状态字典(一个Python字典对象)序列化为字节流,然后写入 .pth 文件。这个文件本质上就是一个二进制文件。

5.2 从文件到磁畴:数据如何被物理存储

当你执行上述保存操作时,数据开始了它的“物理之旅”:

  1. 应用层 :你的Python程序调用 torch.save save_image
  2. 系统调用层 :Python通过操作系统(如Linux, Windows)提供的文件写入API(如 write 系统调用),请求将一段内存缓冲区(你的序列化模型数据或图片像素数据)写入到指定路径的文件中。
  3. 文件系统层 :操作系统内核的文件系统(如NTFS, ext4)负责管理这个请求。它决定这些数据块应该放在硬盘的哪些逻辑扇区,并更新文件元数据(如inode)。
  4. 块设备层 :文件系统将逻辑扇区地址转换为对硬盘控制器的指令。
  5. 物理层 - 磁存储原理 (以传统机械硬盘HDD为例):
    • 硬盘由高速旋转的 盘片 组成,盘片表面覆盖着磁性材料。
    • 数据以 磁畴 的形式存储。每个磁畴就像一个小磁铁,其北极的朝向(向上或向下)代表一个二进制位(0或1)。
    • 硬盘的 读写磁头 悬浮在盘片上方纳米级的距离。写入时,磁头产生磁场,改变下方磁畴的极性;读取时,磁头感应磁畴极性变化产生的磁场,将其转换为电信号。
    • 你保存的 generator.pth 文件,其包含的百万甚至上亿个参数(浮点数),最终被转换成一系列0和1,并映射为硬盘上特定区域无数个磁畴的特定排列方向。

为什么这很重要?

  • 性能理解 :加载大模型(几个GB)慢,是因为磁头需要寻道、盘片需要旋转到正确位置,并顺序读取大量磁畴信息。换成固态硬盘(SSD,基于闪存,无机械部件)会快几个数量级。
  • 数据持久性 :磁畴的状态在断电后依然能保持,这使得硬盘成为 非易失性存储 ,你的模型和数据得以长期保存。
  • 抽象与现实的桥梁 model.load_state_dict() 这个看似魔法的函数,背后是操作系统从硬盘特定位置读取磁信号,经层层转换,最终还原成Python字典对象的过程。理解这一点,你对“数据”和“计算”的认知会更完整。

5.3 实操建议:模型与数据管理

  1. 版本化 :重要的模型检查点,保存时加上epoch编号或日期,如 generator_epoch_50.pth
  2. 分离代码与数据 :将模型文件( .pth )、生成的图片与你的项目源代码放在不同的目录。例如:
    project/
    ├── src/          # 源代码
    ├── checkpoints/  # 保存的模型
    ├── outputs/      # 生成的图片
    └── data/         # 数据集
    
  3. 考虑存储介质 :对于频繁读写的训练过程,将数据集放在SSD上可以极大提升数据加载速度。对于海量的生成结果归档,容量更大的HDD可能更经济。

6. 问题排查与进阶方向

当你按照上述步骤操作时,可能会遇到一些典型问题。

6.1 常见问题排查清单

现象 可能原因 排查步骤
训练时损失为NaN 学习率过高、网络层输出值爆炸、损失函数输入异常。 1. 降低学习率(如从1e-3降到1e-4)。
2. 检查网络中有无除零或对数运算,输入是否经过归一化。
3. 在损失计算前打印张量值,看是否有异常极大/极小值。
GAN生成器损失一直很高,生成图片全是噪声 判别器太强,生成器学不到东西。 1. 减弱判别器:增加Dropout,减少其层数或神经元数。
2. 在训练生成器时,可以暂时减少判别器的更新频率(例如,每更新k次判别器,再更新1次生成器)。
3. 检查生成器和判别器的学习率是否平衡。
VAE生成图片非常模糊 KL散度项的权重可能过大,模型过于注重潜空间规整性而牺牲了重建精度。 1. 尝试调整损失函数,给重构损失(BCE)和KL散度加上权重β: loss = BCE + beta * KLD 。β越小,重建越清晰但潜空间可能越不规整(这就是β-VAE)。
2. 增加解码器的能力(更多层、更宽的网络)。
加载模型时报错 模型结构定义与保存时的结构不匹配,或PyTorch版本不兼容。 1. 确保加载模型前, 完全一致地 定义了模型类( Generator , VAE )。
2. 对于PyTorch,保存时建议同时保存模型结构和参数( torch.save(model, ‘model.pth’) ),但这种方式对代码结构变化敏感。使用 state_dict 更灵活,但必须保证结构匹配。
3. 在加载 state_dict 时使用 strict=False 参数可以忽略不匹配的键,但需谨慎。
内存/显存不足 批量大小太大、模型太大、图片分辨率太高。 1. 减小 batch_size
2. 使用更小的模型(减少层宽和深度)。
3. 对于图片数据,可以先缩放到较低分辨率进行实验。
4. 使用 torch.cuda.empty_cache() 清理GPU缓存。

6.2 从入门到进阶:可以尝试的方向

在跑通基础版本后,你可以选择以下方向深入:

  1. 提升图像质量
    • DCGAN :将全连接层替换为卷积层和转置卷积层,用于生成更复杂的图像(如CIFAR-10, CelebA人脸)。
    • WGAN/WGAN-GP :使用Wasserstein距离和梯度惩罚来改善GAN训练的稳定性。
  2. 探索结构化潜空间
    • β-VAE :调整KL散度的权重,在重建精度和潜空间解耦之间取得平衡,学习更具解释性的特征。
    • 在VAE潜空间中进行向量运算 :例如, z_smile = z_neutral + (z_smiling - z_neutral) ,尝试生成带有特定属性的图片。
  3. 连接理论与存储
    • 编写一个简单的程序,反复写入和读取一个大文件,使用系统监控工具(如 iostat on Linux, Resource Monitor on Windows)观察磁盘活动。
    • 了解SSD(NAND闪存)与HDD(磁存储)在物理原理、读写速度、寿命(擦写次数)上的根本区别,理解为何AI训练平台普遍采用SSD或更快的NVMe SSD。

生成式AI的实践,是一个从高层算法抽象到底层数据物理实现的完整循环。从在Python中定义 nn.Module 子类开始,到在屏幕上看到第一个模糊但属于自己的生成数字,再到理解这些数字背后的参数如何以磁畴的形式永恒刻录,这个过程本身,就是对“虚拟世界数据根基”最生动的洞悉。我建议的路径永远是:先让最简单的模型在本地跑起来,获得正反馈;然后去调整参数,观察变化;最后,再去思考那些支撑这一切运行的、沉默的底层原理。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐