第9章 生成式深度学习:从概率中创造新内容

本章技术主题: 自回归、潜空间与扩散模型的生成机制。
本章技术实验: 固定条件或随机种子,观察自回归、潜空间和扩散生成的变化。
家庭项目: “制作家庭记忆故事盒”是本章主项目;阳台植物科普卡是低隐私替代项目。
跨章位置: 接收第7章的来源编号与检索证据,交付带事实分层、授权和撤回记录的作品。
技术主线: 训练数据分布 → 条件与随机起点 → 生成模型逐步采样 → 候选作品 → 真实性与授权检查。

学习目标

学完本章,你将能够:

  1. 区分分类模型与生成模型,说明生成并非简单查表复制,同时可能记忆或近似复现训练样本;
  2. 用通俗语言比较自回归生成、潜空间生成与扩散去噪;
  3. 通过固定条件和随机种子,观察生成结果的可控性与多样性;
  4. 从条件符合度、伪影、事实分层、材料授权、撤回要求和发布说明判断作品能否使用。

项目导入

分类模型从预设类别中选择结果,生成模型则要产生新的文字、图像或声音。它学习训练数据中复杂的概率分布,再从这个分布中采样。相同条件可能得到不同作品,不同条件也可能得到相似结果。生成内容看起来完整,并不表示其中的文字、结构和事实都正确。

判别式模型关注“输入属于什么”或“数值是多少”,生成式模型关注“可能的数据样本怎样产生”。二者都可以使用深度神经网络,区别主要在学习目标和输出方式,不在于界面是否带有生成按钮。调用一个通用模型写宣传语属于生成应用;继续追问训练目标、概率分布和采样过程,才进入本章的技术本质。

本章的正式项目是“制作家庭记忆故事盒”,成品必须有来源。学生只使用家庭成员明确授权、已经脱敏的照片副本和口述文字,为每份材料建立来源编号;作品把“照片中可直接看到的事实”“讲述者的口述记忆”和“模型为连贯表达生成的连接想象”分层呈现,不能把三者混写成已经证实的历史事实。参与者可以在制作和展示阶段撤回材料,项目必须能按来源编号找到并移除相关内容。

为了观察图像生成的条件控制与随机性,封面实验不使用人物照片,而生成“旧收音机、两张旧车票、木桌、留白、无文字”的记忆物件画面;随后固定种子,把“两张车票”改为“三张车票”。项目不做人脸识别、身份推断、声音克隆或模仿亲属声线,也不把私人作品默认公开。原“阳台植物观察科普卡”保留为低隐私替代项目:不能取得家庭材料授权的学生,可用同样的条件、种子和采用判断完成植物卡。

本章成果

考点提示

生成模型学习的是数据模式和概率分布,不保存一套可检索的标准答案。自回归模型逐个产生后续元素;变分自编码器等模型在潜空间采样并解码;扩散模型从噪声出发逐步去噪。条件控制生成方向,随机种子帮助复现起点,但不能保证作品正确、原创或可发布。

第一节 用授权材料制作有来源的家庭记忆故事盒

一、把创作要求分成条件和验收

故事盒的材料先登记为三类:照片副本记录来源编号、授权范围和可见事实;口述文字记录讲述者、记录日期和允许使用范围;模型生成内容记录模型版本、条件和随机种子。姓名、住址、证件、联系方式、车牌、精确地点等无关信息先删除。照片中出现人物并不授权系统识别其身份;本项目优先裁取物件,不做人脸识别。

正文必须显式分层。例如“照片事实/P-01:桌面上有一台旧收音机”和“口述记忆/O-01:讲述者说它曾陪伴一家人听晚间节目”可以并列保留;“模型连接想象/G-01:旋钮转动时,房间仿佛又安静下来”只能标为创作性连接。生成条件不是历史事实来源,语言自然也不能把“仿佛”改写成“当年确实如此”。

封面条件分为三类。内容条件规定“旧收音机、两张旧车票和木桌”;形式条件规定横向4:3、低饱和插画和右侧留白;禁止条件规定不得出现人物、人脸、姓名、地址、号码、品牌、伪文字和未经授权角色。验收表检查物件数量、结构、留白、伪文字和相似风险。

二、与开发与学习AI共同设计最小实验

应用中的AI包括整理故事草稿的文字生成模型和生成无人物封面的图像模型。开发与学习AI帮助学生把模糊创意改成可比较的条件、建立来源与生成记录、解释采样参数和检查结果。学生负责取得授权、核对事实、标注想象、响应撤回以及决定是否展示。

作品:家庭记忆故事盒封面,横向4:3,不使用人物材料。
固定条件:低饱和插画,木桌上的旧收音机和两张旧车票,右侧留白。
禁止内容:人物、人脸、姓名、地址、号码、品牌、现成角色和自动生成文字。
实验A:条件和模型固定,只改变随机种子,生成4张。
实验B:种子和模型固定,只把“两张车票”改为“三张车票”。
记录:条件、种子、生成时间、物件数量、伪影、相似风险和是否采用。

学生不必手写模型代码,却要能在完整Notebook中找到条件怎样编码、随机噪声在哪里产生、模型怎样逐步更新样本以及图像怎样保存。规划中的数字资源应提供小规模教学模型和预训练推理两条路径;前者观察机制,后者完成作品。

两条路径不能混为一次实验。教学模型使用小规模授权数据,能看到训练损失和中间样本,但生成质量有限;预训练模型生成质量较高,学生通常看不到完整训练数据和训练过程。记录中应分别写“我们亲自训练了什么”和“我们调用了什么”,不能因会调用预训练模型就声称完成了大模型训练。

三、保留候选而不是只展示最好结果

第一次生成四张图,可能出现车票数量不符、收音机旋钮和刻度断裂、留白被占满或票面出现似字非字纹理。故事草稿还可能把口述内容改成照片事实,或擅自补出年代、地点和人物关系。全部候选、分层文本和参数都应保存;只展示最好的一份会隐藏模型的随机性,也无法检查事实层是否被改写。

首轮记录可以采用五项简单尺度:条件符合、结构完整、可编辑、事实分层、授权有效。每项用“通过、需修改、不通过”标记并写具体位置。评分不用于证明艺术价值,而用于比较同一任务的不同生成结果。

第二节 比较自回归、潜空间与扩散去噪

一、三种生成路线解决同一个问题

自回归模型把已经生成的部分作为条件,预测下一个词元、像素块或音频单元,再把新结果加入序列。它适合解释语言模型逐词元生成,也可用于图像和声音。前面生成的错误会成为后续条件,因此可能逐步累积。

自编码器先把输入压缩到较短表示,再从表示重建数据。变分自编码器让潜空间具有可采样的连续结构,可以在两个点之间插值,观察形状或风格平滑变化。潜空间不是人手设计的属性表,某个方向不一定能直接命名。

潜空间方法训练时既要求重建结果接近输入,也约束潜变量分布便于采样。压缩过强会丢失细节,约束过弱又可能形成难以连续采样的空洞。课堂通过在两个潜向量之间取若干中间点,观察生成图怎样渐变,而不是把潜空间解释为可随意拨动的“创意按钮”。

扩散模型训练时逐步向数据加入噪声,并学习预测噪声或去噪方向;生成时从随机噪声开始,多步去噪形成样本[20]。文字等条件会影响每一步朝什么方向更新。一次去噪不是“从图库找相似图”,而是模型根据学到的分布估计当前噪声样本应如何变化。

许多图像系统会先把图片压到潜空间,在较小的潜表示上扩散,再解码回像素,这称为潜扩散。它降低计算量,但不改变“逐步去噪采样”的核心。条件引导越强,结果通常越贴近文字,变化空间也可能减小,甚至出现结构僵硬或伪影;条件强度应像其他变量一样通过对照实验选择。

路线 起点 生成动作 课堂观察重点
自回归 已有序列或起始标记 预测下一个元素 顺序、条件和错误累积
潜空间 一个潜变量 解码为完整样本 插值和整体结构
扩散 随机噪声 多步去噪 随机起点和逐步形成

二、读懂扩散Notebook的完整链

规划中的教学Notebook应按六段组织:加载经授权的小图数据并归一化;随机选择噪声强度;把原图与噪声混合;让小型神经网络预测加入的噪声;依据预测误差训练;从固定种子噪声开始反复去噪并保存中间图。

训练时的目标不是判断图片类别,而是让预测噪声接近真实加入的噪声。若用(x_0)表示原图、(\epsilon)表示随机噪声、(t)表示噪声阶段,可以把加噪写成:

[ x_t=\sqrt{\bar{\alpha}_t}x_0+\sqrt{1-\bar{\alpha}_t}\epsilon ]

学生不需要推导公式,但要读出两件事:阶段较早时原图成分多,阶段较晚时噪声成分多;网络接收当前带噪样本和阶段信息,学习预测噪声。采样过程按多个阶段反向更新,最后形成候选图。

完整Notebook必须显示数据样例、带噪程度、训练损失、固定种子的中间去噪图和最终样本。只给一个生成按钮而隐藏这些步骤,不足以支撑本章学习。

训练损失下降表示模型在当前训练目标上改善,不等于作品质量、事实或授权已经合格。评价至少分三层:模型层观察损失和固定种子样本;作品层检查条件符合、结构和多样性;使用层检查事实、来源、风险和发布场景。三层结论不能用一个“生成成功”代替。

下面的微型条件扩散实验在二维点上完整展示“加噪—学习噪声—按条件去噪”。类别0的数据中心在左侧,类别1在右侧;类别独热向量作为条件输入。二维点不是故事盒封面图片,但训练和反向采样与更大扩散模型共享核心逻辑,几秒到数分钟即可观察。

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow import keras

tf.keras.utils.set_random_seed(7)
rng = np.random.default_rng(7)
count, steps = 2400, 30
labels = rng.integers(0, 2, size=count)
centers = np.array([[-1.2, 0.0], [1.2, 0.0]], dtype="float32")
clean = centers[labels] + rng.normal(0, 0.25, size=(count, 2))
clean = clean.astype("float32")

betas = np.linspace(0.0005, 0.05, steps, dtype="float32")
alphas = 1.0 - betas
alpha_bar = np.cumprod(alphas)
time_index = rng.integers(0, steps, size=count)
noise = rng.normal(size=clean.shape).astype("float32")
strength = alpha_bar[time_index, None]
noisy = np.sqrt(strength) * clean + np.sqrt(1.0 - strength) * noise
condition = np.eye(2, dtype="float32")[labels]
features = np.concatenate([
    noisy,
    (time_index[:, None] / (steps - 1)).astype("float32"),
    condition,
], axis=1)

denoiser = keras.Sequential([
    keras.layers.Input((5,)),
    keras.layers.Dense(64, activation="swish"),
    keras.layers.Dense(64, activation="swish"),
    keras.layers.Dense(2),
])
denoiser.compile(optimizer="adam", loss="mse")
history = denoiser.fit(features, noise, batch_size=64, epochs=35, verbose=0)

def sample(class_id, sample_count=200, seed=21):
    local_rng = np.random.default_rng(seed)
    points = local_rng.normal(size=(sample_count, 2)).astype("float32")
    snapshots = {"initial": points.copy()}
    class_condition = np.eye(2, dtype="float32")[
        np.full(sample_count, class_id)]
    for step in reversed(range(steps)):
        time_column = np.full((sample_count, 1), step / (steps - 1),
                              dtype="float32")
        model_input = np.concatenate(
            [points, time_column, class_condition], axis=1)
        predicted_noise = denoiser.predict(model_input, verbose=0)
        mean = (points - betas[step] / np.sqrt(1.0 - alpha_bar[step])
                * predicted_noise) / np.sqrt(alphas[step])
        if step > 0:
            previous = alpha_bar[step - 1]
            variance = betas[step] * (1.0 - previous) / (1.0 - alpha_bar[step])
            random_noise = local_rng.normal(size=points.shape).astype("float32")
            points = mean + np.sqrt(variance) * random_noise
        else:
            points = mean
        if step in {20, 10, 0}:
            snapshots[f"step_{step}"] = points.copy()
    return points, snapshots

left, left_steps = sample(0, seed=21)
right, right_steps = sample(1, seed=21)
assert np.allclose(left_steps["initial"], right_steps["initial"])
print("类别0生成中心:", np.round(left.mean(axis=0), 2))
print("类别1生成中心:", np.round(right.mean(axis=0), 2))
for name, points in left_steps.items():
    print("类别0", name, "中心:", np.round(points.mean(axis=0), 2))

preview_count = 400
view_step = 20
view_rng = np.random.default_rng(99)
view_noise = view_rng.normal(size=(preview_count, 2)).astype("float32")
fixed_noisy = (
    np.sqrt(alpha_bar[view_step]) * clean[:preview_count]
    + np.sqrt(1.0 - alpha_bar[view_step]) * view_noise
)

figure, axes = plt.subplots(2, 3, figsize=(12, 8))
for class_id, color in [(0, "tab:blue"), (1, "tab:orange")]:
    selected = labels[:preview_count] == class_id
    axes[0, 0].scatter(
        clean[:preview_count][selected, 0], clean[:preview_count][selected, 1],
        s=8, alpha=0.45, color=color, label=f"class {class_id}"
    )
axes[0, 0].set_title("clean training points")
axes[0, 0].legend()
axes[0, 1].scatter(fixed_noisy[:, 0], fixed_noisy[:, 1], s=8, alpha=0.35)
axes[0, 1].set_title(f"noise at fixed step {view_step}")
axes[0, 2].plot(history.history["loss"])
axes[0, 2].set_title("noise-prediction loss")
axes[0, 2].set_xlabel("epoch")
axes[0, 2].set_ylabel("MSE")
axes[1, 0].scatter(
    left_steps["initial"][:, 0], left_steps["initial"][:, 1],
    s=8, alpha=0.35, color="tab:gray")
axes[1, 0].set_title("shared initial noise")
axes[1, 1].scatter(left[:, 0], left[:, 1], s=8, alpha=0.45, color="tab:blue")
axes[1, 1].scatter(*centers[0], marker="x", s=80, color="black")
axes[1, 1].set_title("generated: class 0")
axes[1, 2].scatter(right[:, 0], right[:, 1], s=8, alpha=0.45, color="tab:orange")
axes[1, 2].scatter(*centers[1], marker="x", s=80, color="black")
axes[1, 2].set_title("generated: class 1")
for axis in [axes[0, 0], axes[0, 1], axes[1, 0], axes[1, 1], axes[1, 2]]:
    axis.set_aspect("equal", adjustable="box")
    axis.set_xlim(-3, 3)
    axis.set_ylim(-3, 3)
    axis.grid(alpha=0.2)
figure.tight_layout()
figure.savefig("diffusion_points.png", dpi=150)
plt.show()

程序保存的diffusion_points.png把干净训练点、固定第20步的加噪点、训练损失、共同初始噪声和两个类别的生成点并列显示;各散点图固定相同坐标范围,避免自动缩放制造“更接近”的错觉。left_steps还保留第20、10、0步的中间状态。学生应先看加噪后结构怎样变弱,再检查去噪状态怎样变化,最后核对生成点是否分别回到左右训练分布;不能只看打印出来的两个中心。固定种子只改变class_id时,断言保证两类从完全相同的初始噪声出发;固定类别只改变种子,则观察同一条件下的多样性。代码中没有文字条件,因此它不能执行“两张车票—三张车票”实验。故事盒封面实验调用另一个已经具备文字条件编码的预训练模型,两条证据分别记录,不互相冒充。

三、分别改变种子和条件

实验A固定条件,只改变随机种子。四张图应保持旧收音机、两张车票和木桌主题大致一致,同时在物件姿态和布局上有所变化。多样性太低可能说明条件过强或模型模式单一;变化太大则说明条件控制不足。

实验B固定随机种子,只把“两张旧车票”改为“三张旧车票”。若主要变化集中在数量,说明条件有一定控制作用;若收音机、构图、颜色和风格也大幅改变,说明条件之间存在耦合。不能同时改变条件、种子、模型和采样步数,否则无法解释差异来源。

种子用于复现随机起点。在完全相同的模型、软件环境和采样算法下,同一种子通常有助于复现;模型版本或计算过程变化后仍可能不同。种子不是作品身份证,也不能证明训练材料来源。

第三节 检查随机性、材料来源与作品真实性

一、主动寻找生成失败

正常样例是常见物件构图与清楚条件;边界样例要求精确车票数量或遮挡关系;陌生样例要求训练分布中很少见的对象组合;故障样例包括模型文件缺失、显存不足、生成中断、来源编号丢失和材料已撤回但衍生文本仍被保留。

图像测试关注物件数量错误、旋钮或票面结构异常、局部重复、伪文字和不合理阴影。文字生成还要测试虚构事实、来源错配和前后矛盾;若比较声音生成,则只使用授权的非人物材料,测试突变和噪声,不进行亲属声音模仿。失败类型随数据形态变化,但都要保留原始候选和条件。

还可以加入简单基线:用已授权的物件照片和确定性排版模板制作同一封面。生成路线若只节省少量构图时间,却增加大量伪影、相似性和授权核对,模板可能更合适;若任务需要多种低风险创意草图,生成模型可能有价值。不能取得家庭材料授权时,应切换到阳台植物科普卡等低隐私替代项目,而不是降低授权标准。

生成过程不是简单从图库检索一张图片,但不能据此断言模型绝不复制。模型可能记住少见训练样本,某些条件下产生高度近似内容。课堂可用与授权样例的近邻比较作为风险筛查,但相似度低也不能完成法律判断;不清楚来源和许可时应限制发布。

二、区分生成、编辑与事实表达

生成模型产生候选素材和连接句,普通编辑软件负责裁切、排版和文字,人工负责来源核对与最终表达。故事盒每段应保留“照片事实/来源编号”“口述记忆/讲述者授权”或“模型连接想象/模型与条件记录”标签;无人物封面也应说明使用了生成式工具。若作品过度接近现成角色、标志或某位创作者的独特作品,应停止展示并更换条件或素材路线。

开发与学习AI可以帮助列检查项、比较候选和生成替代条件,但不能替家庭成员授权,也不能推断照片中人物身份。材料提供者撤回P-01或O-01后,学生应依据来源索引移出原文件、相关故事段落和衍生封面,更新展示清单并保留最少撤回记录;已经由他人另行保存的公开副本未必能自动收回,因此本项目默认在受控范围展示。训练数据通常无法由单张输出反推出完整清单,“没有看到复制痕迹”也不等于没有权利风险。

三、形成作品采用判断

判断卡至少记录:材料来源编号与授权范围;照片事实、口述记忆和模型连接想象怎样分层;撤回后删除哪些原件和衍生物;所用条件、种子、模型和资源版本;候选数量;条件符合和伪影情况;生成使用怎样说明;哪些位置由人重新编辑;遇到相似风险或无法核实来源时怎样停止。

适合采用的范围是获得授权、可人工检查、允许创意变化的辅助表达。故事盒不是家族史档案,模型连接想象不能作为历史证据;要求精确结构、真实记录、专业诊断或身份证明的场景,也不应把生成结果当作原始事实。决定不用生成模型,改用经授权照片、手绘、模板或低隐私植物卡,同样体现了对技术的理解。

本章小结

生成式深度学习不是让聊天模型写一句话的操作名称,而是让模型学习数据分布并从中采样。自回归、潜空间和扩散模型采用不同生成路径;条件与随机起点共同影响结果。

保存所有候选、固定变量、观察逐步生成和检查失败,能够把创作体验转成技术学习。模型生成素材和明确标注的连接想象,普通软件编辑,人工核对照片事实、口述来源、授权与撤回。可生成不等于可展示,随机新颖也不自动等于原创。

关键术语

目标测试

一、单项选择题

  1. 生成模型与分类模型的主要区别是( )。A.生成新样本与选择预设类别 B.是否用电脑 C.是否有文件 D.是否需要人
  2. 扩散模型生成时通常从( )开始。A.标准答案 B.随机噪声 C.分类标签表 D.网页按钮
  3. 固定条件只改变随机种子主要观察( )。A.数据授权 B.同一条件下的多样性 C.模型是否训练 D.文字来源
  4. 故事盒中由模型补写的连接句应( )。A.改成照片事实 B.标为模型连接想象并由人决定是否保留 C.删除来源编号 D.当作讲述者原话

二、判断并改错

  1. “相同随机种子在任何模型版本上都保证得到完全相同作品。”请改正。
  2. “生成结果没有明显复制痕迹,就能证明不存在授权风险。”请改正。

三、简答与操作题

  1. 分别用一句话说明自回归、潜空间和扩散生成的起点与过程。
  2. 以“旧收音机和两张车票”的无人物封面为例,设计一个固定条件、只改变种子的对照实验,并列出两项记录指标。
  3. 为什么故事盒必须区分照片事实、口述记忆和模型连接想象?若一份材料被撤回,应怎样处理?
  4. 写出一项适合采用生成模型的低风险任务和一项不适合的任务,并说明理由;不能取得家庭材料授权时可选择什么替代项目?

答案编号:A1-9-01—A1-9-10。完整答案和评价要点统一放入书后“目标测试参考答案”。