TF-IDF 详解

在自然语言处理(NLP)和信息检索领域,TF-IDF(Term Frequency-Inverse Document Frequency,词频-逆文档频率)是一种常用的文本权重计算方法。它能够衡量单词在文档中的重要性,并在文本分类、关键词提取、搜索引擎等多个领域广泛应用。

TF-IDF 是一种经典的文本表示方法,能够衡量单词在文档中的重要性。它在信息检索、文本分类和关键词提取等任务中广泛应用。然而,它无法捕捉语义信息,通常与词向量(如 Word2Vec、BERT)等方法结合使用,以提高文本分析的效果

1. TF-IDF 简介

TF-IDF 主要用于评估某个词语在一篇文档中的重要性,基本思想是:

  • 词频(Term Frequency, TF) 衡量某个词在文档中出现的频率。
  • 逆文档频率(Inverse Document Frequency, IDF) 衡量该词在整个语料库中的稀有程度。

通过 TF 和 IDF 的结合,我们可以计算出一个词的重要性,常见应用包括:

  • 关键词提取:自动识别文档的核心词汇。
  • 文本相似度计算:用于文本分类和推荐系统。
  • 搜索引擎排序:衡量查询词与文档的相关性。

2. TF-IDF 计算公式

TF-IDF 的计算包含两个部分:

2.1 词频(TF)

词频用于衡量某个单词在一篇文档中出现的频率。常见的计算方法如下:

$$ TF(t, d) = \frac{f(t, d)}{\sum_{w \in d} f(w, d)} $$

其中:

  • ( f(t, d) ) 表示词 ( t ) 在文档 ( d ) 中出现的次数。
  • ( \sum_{w \in d} f(w, d) ) 表示文档 ( d ) 中所有单词的总出现次数。

2.2 逆文档频率(IDF)

逆文档频率用于衡量某个单词在整个文档集合中是否具有区分度。其计算公式为:

$$ IDF(t) = \log \frac{N}{1 + DF(t)} $$

其中:

  • ( N ) 是文档总数。
  • ( DF(t) ) 是包含词 ( t ) 的文档数量。
  • 分母加 1 是为了避免除零错误。

2.3 TF-IDF 计算

最终,TF-IDF 计算公式为:

$$ TFIDF(t, d) = TF(t, d) \times IDF(t) $$

3. TF-IDF 计算示例

假设我们有如下三篇文档:

文档 1: “机器学习 是 人工智能 的 一个 分支”

文档 2: “深度学习 是 机器学习 的 一个 重要 方向”

文档 3: “自然语言处理 是 人工智能 的 一个 重要 领域”

计算 “机器学习” 在 文档 2 中的 TF-IDF 值:

  • TF(“机器学习”, 文档 2) = 1 / 7 ≈ 0.142
  • IDF(“机器学习”) = log(3 / 2) ≈ 0.176
  • TF-IDF(“机器学习”, 文档 2) ≈ 0.142 × 0.176 ≈ 0.025

4. TF-IDF 的优缺点

4.1 优点

✅ 计算简单,易于理解和实现。
✅ 在搜索引擎和文本分析任务中表现良好。
✅ 适用于高维文本数据。

4.2 缺点

❌ 无法捕捉单词的语义信息,例如 “苹果” 可以指水果也可以指公司。
❌ 对长文本不够鲁棒,容易造成高频词权重偏高。
❌ 不能处理同义词、上下文信息,需要结合词向量等方法。

5. TF-IDF 在 NLP 领域的应用

  • 搜索引擎:计算查询词与网页的相关性,提高搜索质量。
  • 文本分类:作为文本特征用于机器学习模型。
  • 关键词提取:自动提取文档的核心关键词。
  • 文档相似度计算:用于推荐系统、聚类分析等。

6. Python 代码实现 TF-IDF

Python 提供了 sklearn.feature_extraction.text.TfidfVectorizer 方便计算 TF-IDF,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.feature_extraction.text import TfidfVectorizer

# 定义文档集
documents = [
"机器学习 是 人工智能 的 一个 分支",
"深度学习 是 机器学习 的 一个 重要 方向",
"自然语言处理 是 人工智能 的 一个 重要 领域"
]

# 初始化 TF-IDF 计算器
vectorizer = TfidfVectorizer()
tf_idf_matrix = vectorizer.fit_transform(documents)

# 获取特征词汇表
words = vectorizer.get_feature_names_out()

# 转换为数组并输出
print(tf_idf_matrix.toarray())

多生成器架构

多生成器架构(Multiple Generator Architecture)

多生成器架构是为了解决模式崩溃和提高生成样本多样性而提出的一种方法。与单一生成器的训练方式不同,多生成器架构通过引入多个生成器,在同一个GAN模型中同时训练多个生成器,并让它们互相竞争或合作,共同提高生成的样本质量和多样性。

原理

在传统的单生成器模型中,生成器通常会为了骗过判别器而选择生成某些“简单”的样本,导致生成样本的多样性受到限制,最终出现模式崩溃。通过引入多个生成器,每个生成器可以专注于生成不同种类的数据,避免过度拟合到某一类样本,从而提高生成器在多样性方面的表现。

多生成器架构的工作方式通常是:

  • 多个生成器共享同一个判别器:所有生成器的目标都是生成能够欺骗判别器的样本,但它们生成的数据集通常是多样化的。
  • 生成器之间的合作与竞争:生成器之间可能会互相合作或竞争,在生成样本时不完全重复彼此的模式,从而避免单一模式的产生。

在GAN中的作用

  • 减少模式崩溃:通过引入多个生成器,每个生成器可以生成不同种类的样本,避免生成器仅仅生成一种或少数几种样本的情况。这有助于提高生成样本的多样性,缓解模式崩溃现象。
  • 增强生成样本的多样性:多个生成器可以专注于不同样本的生成,拓宽生成样本的分布范围,使得生成的数据更为多样化和复杂。
  • 提升生成质量:多个生成器可以通过不同的生成策略来共同优化,从而提高生成样本的质量。

常见的多生成器架构变体

  • 条件生成对抗网络(Conditional GAN):在传统的GAN架构中,生成器是随机生成样本的,但通过引入条件变量(例如标签),生成器能够生成特定类型的样本。多个生成器可以被设计成专注于不同类型的生成任务,从而生成更多样化的结果。
  • 协作与竞争式架构:例如,生成器可以根据不同的条件输入生成不同的样本类型,判别器则会根据生成器输出的多样性来调整评判标准。

这种方法虽然能有效增加生成样本的多样性,但也带来了更高的计算成本,因为需要同时训练多个生成器,这使得训练过程更加复杂,且对计算资源的要求更高。

层归一化

省流

层归一化就是对每个样本的所有特征进行归一化处理,而不是像批归一化那样对一批数据的同一特征维度进行归一化。

层归一化的原理是通过对每个样本的特征进行独立处理,计算每个样本在所有特征上的均值和方差,然后对该样本进行归一化。这样,每个样本在其所有特征维度上的值都会被调整到一个相对统一的尺度。

这种方法的好处在于,它消除了批次大小的依赖,适用于那些批次大小较小或者批次中样本数量不一致(NLP)的任务。对于RNN、Transformer等序列模型来说,层归一化表现尤为出色,因为这些模型的输入数据通常是具有时序性质的,批归一化可能会在这种情况下丧失时序信息。

层归一化相较于批归一化,在模型训练过程中更适合处理长序列数据。层归一化则能够确保每个样本的特征独立归一化,从而保持模型在每个时刻的数据分布稳定。

比如:
假设我们有一个 批次大小为 2,序列长度为 3,隐藏维度为 4 的输入张量:
[
X \in \mathbb{R}^{\text{Batch_size} \times \text{Seq_len} \times \text{Dim}}
]
假设数据如下:
[
X =
\begin{bmatrix}
\begin{bmatrix} 1.0 & 2.0 & 3.0 & 4.0 \end{bmatrix} \
\begin{bmatrix} 5.0 & 6.0 & 7.0 & 8.0 \end{bmatrix} \
\begin{bmatrix} 9.0 & 10.0 & 11.0 & 12.0 \end{bmatrix}
\end{bmatrix}
]

计算层归一化
层归一化是在 每个 token(每一行) 内部进行归一化,即 沿着 Dim 维度计算均值和标准差

第一个 token:([1.0, 2.0, 3.0, 4.0])

计算均值
[
\mu = \frac{1.0 + 2.0 + 3.0 + 4.0}{4} = 2.5
]

计算标准差
[
\sigma = \sqrt{\frac{(1.0 - 2.5)^2 + (2.0 - 2.5)^2 + (3.0 - 2.5)^2 + (4.0 - 2.5)^2}{4}}
]
[
= \sqrt{\frac{2.25 + 0.25 + 0.25 + 2.25}{4}} = \sqrt{1.25} \approx 1.12
]

归一化
[
\hat{x} = \frac{x - \mu}{\sigma} = \left[ \frac{1.0 - 2.5}{1.12}, \frac{2.0 - 2.5}{1.12}, \frac{3.0 - 2.5}{1.12}, \frac{4.0 - 2.5}{1.12} \right]
]
[
\approx [-1.34, -0.45, 0.45, 1.34]
]

第二个 token:([5.0, 6.0, 7.0, 8.0])
[
\mu = \frac{5+6+7+8}{4} = 6.5, \quad \sigma = \sqrt{\frac{(-1.5)^2 + (-0.5)^2 + (0.5)^2 + (1.5)^2}{4}} = 1.12
]
[
\hat{x} = \left[ \frac{5.0 - 6.5}{1.12}, \frac{6.0 - 6.5}{1.12}, \frac{7.0 - 6.5}{1.12}, \frac{8.0 - 6.5}{1.12} \right]
]
[
\approx [-1.34, -0.45, 0.45, 1.34]
]

第三个 token:([9.0, 10.0, 11.0, 12.0])
[
\mu = 10.5, \quad \sigma = 1.12
]
[
\hat{x} \approx [-1.34, -0.45, 0.45, 1.34]
]

  • 最终归一化结果
    [
    \hat{X} =
    \begin{bmatrix}
    \begin{bmatrix} -1.34 & -0.45 & 0.45 & 1.34 \end{bmatrix} \
    \begin{bmatrix} -1.34 & -0.45 & 0.45 & 1.34 \end{bmatrix} \
    \begin{bmatrix} -1.34 & -0.45 & 0.45 & 1.34 \end{bmatrix}
    \end{bmatrix}
    ]

  • 乘以缩放参数 (\gamma) 并加上偏移参数 (\beta)
    [
    y = \hat{x} \cdot \gamma + \beta
    ]
    其中,(\gamma) 和 (\beta) 是可学习参数(形状为 (Dim,)),它们帮助恢复特征表达能力。

如果 (\gamma = [1, 1, 1, 1]) 且 (\beta = [0, 0, 0, 0]),则最终输出与 (\hat{x}) 相同。

总的来说,层归一化不仅提高了模型的稳定性,也让模型在处理时序数据时具有更好的表现,特别是在自然语言处理和序列生成任务中。

层归一化(Layer Normalization)详解

1. 概述

层归一化(Layer Normalization, LN)是一种用于深度神经网络的归一化方法,通常用于 自然语言处理(NLP)自注意力网络(Transformer) 中。与批归一化(Batch Normalization, BN)不同,LN 主要 针对单个样本的所有特征维度 进行归一化,而不是在 batch 维度上计算均值和标准差。

2. 计算公式

对于输入 ( X \in \mathbb{R}^{\text{Batch_size} \times \text{Seq_len} \times \text{Dim}} ),层归一化的计算过程如下:

计算均值

每个 token 的所有特征维度 计算均值:
[
\mu = \frac{1}{\text{Dim}} \sum_{i=1}^{\text{Dim}} x_i
]

计算标准差

[
\sigma = \sqrt{\frac{1}{\text{Dim}} \sum_{i=1}^{\text{Dim}} (x_i - \mu)^2 + \epsilon}
]
其中,(\epsilon) 是一个很小的数,用于防止分母为零。

归一化

[
\hat{x} = \frac{x - \mu}{\sigma}
]

缩放和平移

为了恢复模型的表达能力,LN 引入了可学习参数 (\gamma) 和 (\beta)(形状均为 (Dim,)):
[
y = \hat{x} \cdot \gamma + \beta
]

3. 归一化作用范围

假设输入数据形状为:
[
X \in \mathbb{R}^{\text{Batch_size} \times \text{Seq_len} \times \text{Dim}}
]
层归一化会 针对每个 token 的 Dim 维度进行归一化,即计算每个 token 内部的均值和标准差,而 不考虑 batch 维度或序列长度 Seq_len

4. 层归一化 vs. 批归一化

归一化方法 归一化维度 计算均值/方差的范围 适用场景
批归一化(BN) 纵向(Batch 维度) 计算相同特征维度在整个 batch 维度上的均值和标准差 CNN,计算机视觉
层归一化(LN) 横向(特征维度) 计算每个 token 的所有特征维度的均值和标准差 NLP,Transformer

5. 计算示例

假设:
[
X =
\begin{bmatrix}
\begin{bmatrix} 1.0 & 2.0 & 3.0 & 4.0 \end{bmatrix} \
\begin{bmatrix} 5.0 & 6.0 & 7.0 & 8.0 \end{bmatrix} \
\begin{bmatrix} 9.0 & 10.0 & 11.0 & 12.0 \end{bmatrix}
\end{bmatrix}
]

对于第一行 ([1.0, 2.0, 3.0, 4.0]):
[
\mu = 2.5, \quad \sigma = \sqrt{1.25} \approx 1.12
]

归一化:
[
\hat{x} \approx [-1.34, -0.45, 0.45, 1.34]
]

最终:
[
y = \hat{x} \cdot \gamma + \beta
]

6. 为什么 Transformer 使用层归一化?

  1. 适用于变长序列
    • Transformer 处理变长文本,BN 依赖 batch 统计信息,不适用于 NLP。
  2. 对 batch size 不敏感
    • BN 在 batch size 小(甚至为 1)时效果较差,而 LN 仍然稳定。
  3. 更适合自注意力机制
    • Transformer 依赖 token 间的关系,LN 可以在 每个 token 内部 归一化,使模型更加稳定。

Inception Score

Inception Score (IS)

Inception Score(简称IS)是衡量生成模型(例如GAN)生成图像质量的一个重要指标。其核心思想是衡量生成图像的“清晰度”和“多样性”。

计算方式

  • 清晰度:生成的图像是否能被分类网络(通常是Inception网络)准确地分类。如果生成的图像属于某个类别并且网络能够自信地给出一个高概率,那么说明图像的内容清晰,容易被识别。
  • 多样性:如果生成的图像种类非常丰富,分类网络的输出应该会具有较大的分布差异。换句话说,生成图像的类别不应该过于集中在某一类。

具体地,IS是通过以下步骤计算的:

  1. 先通过Inception网络对生成的每一张图片进行分类,得到每张图片的预测概率分布。
  2. 然后计算这些预测概率分布的KL散度(Kullback-Leibler divergence),也就是衡量不同生成图像之间的多样性。
  3. 最后将这些多样性和图像的清晰度结合起来,得到最终的得分。

公式

Inception Score的计算公式为:

[
IS = \exp\left(\mathbb{E}x[D{KL}(p(y|x) || p(y))]\right)
]

其中,(p(y|x)) 是给定生成图像 (x) 后的类别概率分布,(p(y)) 是所有生成图像类别的均匀分布。

优点

  • 计算简单,直接利用了现有的预训练分类网络(如Inception v3)进行评估。
  • 对生成图像的清晰度和多样性都有一定的衡量。

缺点

  • 依赖Inception网络:Inception Score的好坏很大程度上取决于Inception网络的能力。网络的训练集、分类的能力和网络的泛化能力可能影响最终评估的准确性。
  • 局限性:IS没有直接考虑生成样本与真实数据之间的分布差异,可能导致一些细微的生成质量问题无法被反映出来。

关于 GAN 及其衍生模型的笔记与思考

关于 GAN

从单模型到 “左右互搏术” 的对抗式模型,GAN 的对抗式的思路注定了它的革命性意义

GAN之前的思路

GAN 的横空出世之前,生成模型的研究主要依赖于传统的概率图模型和最大似然估计等方法。生成模型的基本目标是通过学习训练数据的分布,来生成类似于训练数据的新样本。

但在当时,主流的生成模型往往依赖于一种显式建模的方式,通常使用自回归模型(如HMM、VAE等),通过明确的概率分布和参数估计进行训练。虽然这些方法可以实现某些生成任务,但始终存在一些局限性:

  1. 生成样本的质量不高:自回归模型常常生成模糊、不清晰的样本。
  2. 难以捕捉数据的复杂性:例如,图像生成任务中,数据的高维度特性使得这些方法很难有效捕捉到所有复杂的细节。

在这些挑战面前,生成模型的研究面临瓶颈,直到GAN的问世,一场生成模型的革命才悄然拉开帷幕。

GAN的思路

深谙“左右互搏术”,G_model 与 D_model 既是对手,亦是良友

GAN 的核心思想是 “对抗”,生成器和判别器在对抗式的博弈中 相互优化

生成器的目标是产生尽可能逼真的假样本,尽量“骗过”判别器;而判别器的目标是区分生成样本和真实样本。两者通过不断“斗智斗勇”的过程,最终达到一种平衡:生成器能够生成与真实样本几乎无差别的图像,而判别器无法轻易分辨。

这一创新的思路彻底改变了生成模型的训练方式,从参数化的显式建模转向了隐式的对抗博弈。这种“左右互搏”的博弈式优化,不仅在图像生成、视频生成等领域取得了突破性进展,也为其他机器学习任务提供了新的启示。

GAN的优点:

  1. 生成能力强大:与传统生成模型相比,GAN能够生成更加逼真和复杂的数据。
  2. 无需明确建模分布:生成器通过与判别器的对抗训练,不需要事先假设数据分布,因此可以更灵活地适应复杂的数据集。
  3. 适应性广泛:无论是图像、音频还是文本,GAN都可以被应用到各类生成任务中,展现出了超强的泛化能力。

但尽管GAN如此强大,其训练过程中却充满了挑战,这也为后续的研究带来了许多值得思考的问题。

存在的问题

尽管GAN具有强大的生成能力,但其训练和调优相对复杂,存在以下挑战:​

  1. 模式崩溃(Mode Collapse)
    为了成功逃避判别器的严厉审查,生成器将为了成功而成功
    趋利避害,本能也

    • 如果有一天,不需要学习很多知识,扩展自己的能力,只需要每天做着简单,轻松,重复的劳动,就可以获得名誉和金钱,万人瞩目,那么绝大多数人将趋之若鹜,无人扩展自己的能力。
    • 如果生成器只需要成功的模拟出一种图像的逼真生成,骗过判别器,那么生成器也会“懒得”去学习其他种类图像的生成逻辑,只会生成这一种来糊弄判别器。

    这就是 模式崩溃

    模型崩溃是指机器学习模型由于在另一个模型(包括其自身的先前版本)的输出上进行未经整理的训练而产生错误,从而逐渐退化的一种现象。
    Shumailov 等人 创造了这个术语,并描述了退化的两个具体阶段:早期模型崩溃和晚期模型崩溃。在早期模型崩溃阶段,模型开始丢失分布尾部的信息–主要影响少数数据。后来的研究强调,早期模型崩溃很难察觉,因为整体性能可能看起来有所改善,而模型在少数数据上的性能却有所下降。
    ——来自维基百科

    也就是说,​生成器可能只能生成有限种类的数据,而忽略了数据集中的其他多样性。

    为了解决这一问题,后续研究者提出了许多改进方法,如使用批量正则化或采用多生成器架构。​

  2. 训练不稳定:

    为了与判别器斗智斗勇,生成器不得不全力以赴,但判别器的挑剔和苛刻,使得整个训练过程充满了矛盾与张力。

    • 假设你每天都要与一位非常严格的评审竞争,如何让他相信你的作品既完美又无可挑剔?但问题是,评审的标准变幻莫测,且总是在不断提高。你试图改进,但他总是能找出新的瑕疵,甚至有时会让你陷入困境,难以找到一个合适的平衡点。最终,你可能陷入一场无休止的噩梦中,无法突破。

    这正是生成器和判别器之间的关系。生成器希望产生尽可能接近真实数据的假样本,而判别器则不断提高自己的标准,试图识别这些假样本。两者的竞争如果没有良好的平衡,可能会导致训练过程的不稳定。训练可能会早期收敛,但结果却远非理想,生成器并没有学到足够的生成策略,或者根本没有学到如何应对判别器的挑战。

    训练不稳定是GAN训练中的一个普遍问题。为了避免生成器和判别器之间的学习速率失衡,研究者提出了WGAN(Wasserstein GAN),它通过引入 “Wasserstein距离” 来缓解这种不稳定性,使得优化过程更加平滑且容易收敛。此外,合理的超参数调整,尤其是学习率和优化器选择,也能在一定程度上改善这一问题,但是这往往是漫长的尝试。

  3. 难以评估:
    生成器的目标是创造看似真实的样本,而判别器的职责是让生成的样本无法再隐藏在真实数据的“伪装”下。但这场较量最终的标准究竟是什么?

    • 假设你是一个画家,创作了一幅作品,你的作品看起来栩栩如生。现在,评审团的任务是评判你作品的艺术价值。问题是,他们无法简单通过传统的评分标准来评价你的作品,因为艺术的美学标准常常难以量化。是否只通过他们的主观评价,还是找到一种能量化的工具来衡量作品的质量呢?

    在GAN中,评估生成模型的质量也是一个复杂的问题。生成器的目标是生成看起来非常真实的数据,而判别器则试图分辨出这些“假冒伪劣”的样本。因此,传统的损失函数难以准确地评估生成器的表现,因为损失函数可能无法捕捉到生成数据与真实数据之间微妙的差别。想象你在画布上做出的每一笔都应该尽可能与真实世界的数据匹配,如何保证每一笔都完美无瑕?

    为了解决这个问题,研究者们提出了一些新的评估方法,如 Fréchet Inception Distance(FID),它通过比较生成样本和真实样本在 Inception 模型特征空间中的分布差异来量化生成数据的质量。这种方法能够 更加客观 地衡量生成样本与真实数据之间的距离,避免仅依赖人工评估的主观性。
    除此之外,还有诸如 Inception Score 等评估方法,试图用更精细的方式来捕捉生成样本的质量。

总结

GAN 通过引入生成器与判别器的 对抗式博弈,极大推动了生成模型的进步,使得机器能够生成几乎与真实数据无异的样本。然而,这种创新的思路也带来了新的挑战:模式崩溃、训练不稳定、评估困难等问题,成为GAN进一步发展的瓶颈。

但正是这些挑战推动了GAN及其变种模型(如WGAN、WGAN-GP等)的不断演进,解决方案也在逐步落地。
Wasserstein距离Fréchet Inception Distance,从 批量正则化多生成器架构,我们已经看到研究者们为克服这些问题所做出的巨大努力。

随着这些挑战的逐步解决,GAN无疑将在未来的机器学习领域中继续发挥其巨大的潜力。


关于 GAN 的衍生模型

DCGAN

创新的 GAN + 卓越的 CNN = 创新卓越的DCGAN

从简单的全连接神经网络架构走向了更复杂的深度卷积网络架构

DCGAN通过深度卷积网络增强了生成器与判别器的能力,尤其是生成器的表现得到了极大改进。相比于传统GAN,DCGAN用卷积层替代了全连接层,这一改动使得生成器能够有效捕捉到图像的空间结构和细节特征。
DCGAN的一个关键创新就是使用 去卷积(Deconvolution)操作,让生成器能够从潜在空间映射到高维数据空间。

效果:

  1. 图像生成质量提高: 通过卷积结构的引入,DCGAN能够生成更加清晰、自然的图像,尤其在面部图像生成和自然场景图像生成方面取得了突破。
  2. 训练更为稳定: DCGAN相比传统GAN,训练过程中的稳定性得到了大幅提升,减少了许多困扰传统GAN的梯度消失问题。

ACGAN

我不仅让你生成,我还得让你知道你生成的是什么

ACGAN的提出,是为了让生成器的输出不再局限于仅生成真实感的图像,而是能够在生成图像的同时,控制图像的类别或标签。这一创新解决了传统GAN在生成任务中 缺乏可控性的问题

ACGAN 在原始 GAN 的基础上,引入了一个 辅助分类器,生成器不仅根据随机噪声生成图像,同时也根据附加的类别标签生成特定类型的图像。
判别器则变得更加复杂,它不仅需要判断样本的真实性,还需要预测样本的类别。这种设计使得 ACGAN 能够在生成的过程中引入条件信息,从而控制生成图像的标签。

效果:

  1. 多样性控制: ACGAN使得生成器能够根据输入的类别标签生成对应的图像,广泛应用于有标签数据的生成任务,例如生成特定类别的动物、植物图像等。
  2. 提升了生成样本的可控性: 生成器不仅追求图像的真实性,也能够有效地根据需求生成多种不同类型的样本。

WGAN

你这数学原理有bug !

WGAN 的提出,打破了传统GAN在训练过程中经常出现的 梯度消失与训练不稳定问题

WGAN的核心创新在于引入了 Wasserstein距离,代替了传统GAN中使用的 JS散度。Wasserstein距离具有更好的数学性质,能够提供更稳定的训练信号,特别是在生成分布与真实分布差异较大时,它能够避免出现梯度消失的情况。同时,WGAN的判别器不再是二分类器,而是一个判别评分器,用于衡量样本的真实性。

为了优化训练过程,WGAN采用了 权重剪切技术,将判别器的权重限制在一定范围内,避免了权重过大导致的训练不稳定。

效果:

  1. 训练过程更平稳: 引入Wasserstein距离的WGAN,不仅训练过程更加稳定,而且能够应对更复杂的生成任务,特别是在生成高质量图像时表现优异。
  2. 解决了梯度消失问题: WGAN通过Wasserstein距离有效缓解了GAN中常见的梯度消失问题,使得生成器和判别器的优化过程更加顺畅。

WGAN-GP

粗暴的裁剪是比不上优雅的梯度惩罚的

WGAN-GP 是对WGAN的一种优化,它引入了梯度惩罚(Gradient Penalty)机制,取代了WGAN中的权重剪切。梯度惩罚的加入进一步提升了模型的稳定性,并解决了WGAN中权重剪切可能带来的副作用。

WGAN-GP通过对判别器的梯度进行惩罚,确保其梯度的平滑性。与WGAN中的权重剪切不同,梯度惩罚使得优化过程更加细致,能够避免生成器和判别器之间的不平衡。其损失函数中加入了梯度惩罚项:
[
L = D(x) - D(G(z)) + \lambda \cdot \mathbb{E}[\left( |\nabla_{\hat{x}} D(\hat{x})|_2 - 1 \right)^2]
]

其中,(\hat{x}) 是生成器输出的线性插值样本,(\lambda) 为梯度惩罚的权重。

效果:

  1. 进一步提升训练稳定性:通过梯度惩罚,WGAN-GP避免了权重剪切可能带来的负面影响,进一步提升了训练过程的稳定性。
  2. 高质量生成:WGAN-GP生成的样本质量更高,尤其在图像生成领域表现出色,能够生成细节更加丰富、逼真度更高的图像。

CycleGAN

循环交叉,交叉循环

CycleGAN 作为一种无监督学习的生成对抗网络,特别适用于图像到图像的转换任务,而无需成对的数据。CycleGAN通过引入循环一致性损失,使得模型能够在没有标签数据的情况下,实现不同领域之间的图像转换。

CycleGAN使用两个生成器和两个判别器。生成器一负责将源域图像转换为目标域图像,另一个生成器则将目标域图像转换回源域图像。关键在于循环一致性损失,通过确保转换回来的图像能够尽可能还原原图,保证生成图像的质量和一致性。

其目标是:

  1. 生成器G将源域图像转换为目标域图像。
  2. 生成器F将目标域图像转换为源域图像。
  3. 循环一致性损失,保证 $𝐺(𝐹(𝑥))≈𝑥$ 和 $𝐹(𝐺(𝑦))≈𝑦$

效果:

  1. 无监督图像转换: CycleGAN可以在没有成对数据的情况下,进行风格迁移、图像合成等任务,应用广泛。
  2. 图像合成和风格迁移: 无论是将夏季图像转换为冬季图像,还是将一张照片转换为油画风格,CycleGAN都能表现出色。

FID

Fréchet Inception Distance (FID)

FID 是一个更为复杂且更为精细的评估生成图像质量的指标。它的灵感来源于计算生成数据和真实数据在特征空间的距离。相较于Inception Score,FID更关注生成样本和真实样本之间的统计差异。

计算方式

  • 通过Inception网络提取生成图像和真实图像的特征(通常是在网络的某一层中提取激活值,像是倒数第二层的输出)。
  • 对这些特征进行统计建模,通常假设它们符合高斯分布。
  • 然后计算生成图像特征和真实图像特征的Fréchet距离(即均值和协方差矩阵的差异)。

Fréchet距离本质上衡量的是两组数据的高斯分布之间的差异。FID越小,表示生成样本和真实数据越相似。

FID公式

假设我们从生成图像和真实图像中分别得到特征均值和协方差:

  • ( \mu_r, \Sigma_r ) 是真实数据的均值和协方差。
  • ( \mu_g, \Sigma_g ) 是生成数据的均值和协方差。

那么,FID的计算公式为:

[
FID = \left| \mu_g - \mu_r \right|^2 + \text{Tr}\left( \Sigma_g + \Sigma_r - 2 \left( \Sigma_g \Sigma_r \right)^{1/2} \right)
]

其中,( \mu_g ) 和 ( \mu_r ) 是生成图像和真实图像的特征均值,( \Sigma_g ) 和 ( \Sigma_r ) 是它们的协方差矩阵。

优点

  • 更符合人类评判:相比于IS,FID更能捕捉生成图像与真实图像的高层次统计差异,更符合人类对图像质量的直觉感知。
  • 不依赖类别:FID不关心生成图像属于哪一类,而是看其是否能够生成真实数据分布中的多样性,这使得它比IS更为全面。

缺点

  • 计算更复杂:FID需要计算特征的均值和协方差,计算过程比Inception Score要复杂。
  • 依赖于Inception网络:虽然FID对生成图像的评价比IS更为全面,但它仍然依赖于一个预训练的Inception网络。如果该网络没有覆盖到某些特定的图像类型,可能会影响评估的准确性。

关于 Transformer 的笔记与思考

关于 Transformer

上下文理解大师

人类理解一句话里的一个词的时候,绝对不会脱离了文本,一定是结合上下文,才能分得清主谓宾,定状补,一词多义,熟词生义,阅读理解,完形填空……这种全局性的理解,正是 Transformer 架构的精髓所在。

Transformer 模型由编码器和解码器两部分组成,核心在于完全基于 自注意力机制。它让模型在处理某个词汇时,关注输入序列中的所有其他词汇,从而捕捉全局依赖关系。这就像是在读小说时,你不仅关注当前章节,还时刻留意其他章节的剧情,以获得全面的理解。

摒弃了传统的 CNN、RNN 的结构(虽然后续研究发现 Transformer 和 CNN、RNN 从某种意义上是等价的),Transformer 可以利用可以并行计算的特性,大幅度提升了计算处理的速度。

Transformer架构设计

Transformer的架构

Transformer 的特殊架构(如上图)主要由以下几个重要组成部分:

  1. 输入嵌入(Input Embedding)
    传统的模型无法直接处理离散数据(如单词或字符 ID),因此需要将输入的每个 token(例如单词)映射为连续的向量。这个映射过程通过 输入嵌入矩阵 实现,最终让模型能够理解每个单词的语义。通过训练,嵌入矩阵会自动调整,使得词向量之间的相似度反映其语义上的接近程度。

  2. 位置编码(Positional Encoding)
    由于 Transformer 完全摒弃了 RNN 和 CNN 的序列结构,它无法像传统模型那样自然而然地获得序列中单词的位置信息。因此,Transformer 引入了位置编码,通过正弦和余弦函数为每个单词的向量加上唯一的位置信息。这种方式可以帮助模型识别词汇在序列中的位置顺序,使得模型能够理解句子的结构。

  3. 自注意力机制(Self-Attention)
    自注意力机制是 Transformer 的核心,它使得模型在处理每个单词时,能够同时考虑到输入序列中其他所有单词的影响。在每个 token 的表示中,不仅包含了当前位置的单词信息,还包括了整个句子中相关词汇的信息。通过自注意力机制,模型能够学习到更丰富的上下文关系。

  4. 多头注意力机制(Multi-Head Attention)
    多头注意力的引入使得 Transformer 能够从多个子空间(多个角度)并行地进行注意力计算,增强了模型在学习不同类型依赖关系方面的能力。它让模型不仅仅关注到局部的关系,而是能够从多个层次去理解输入序列。

  5. 前馈神经网络(Feed-Forward Network)
    在每个编码器和解码器的层中,除了自注意力机制外,还会有一个前馈神经网络,它主要由两个线性变换和一个非线性激活函数(如 ReLU)构成。通过这种方式,模型能够捕捉到更复杂的特征表示。

  6. 残差连接和层归一化(Residual Connection & Layer Normalization)
    为了防止深层网络中的梯度消失或爆炸问题,Transformer 在每一层都采用了 残差连接层归一化。残差连接确保信息在网络中的有效传递,而层归一化帮助模型的训练更稳定、收敛更快。

对于翻译任务

对于学习的数据,应该是一一配对的句子对(两种不同的语言)
对于模型而言,需要分别将句子对的两个视作 源语言目标语言

因为模型无法学习非数值的数据,所以需要对于文本数据进行词嵌入处理,把句子变成tokens,再把tokens变成可学习的向量(维度为embed_dim)。

  • 对于编码器

    1. 分词处理:
      首先,使用分词技术将输入句子拆分为若干个token(分割策略有讲究,可以是基于词的划分,也可以是基于子词或字符的划分等)。分词后的结果是一个形状为 (batch_size, seq_len) 的张量,其中每个token都被映射为其对应的ID。
    2. 词嵌入和位置编码:
      接下来,通过词嵌入(Input Embedding)将token ID映射为对应的高维稠密向量,得到形状为 (batch_size, seq_len, embed_dim) 的数据。这些嵌入向量是通过查找表学习得到的。
      然后,使用位置编码(Positional Encoding)对输入嵌入进行增强。位置编码通过正余弦函数生成,形状为 (batch_size, seq_len, embed_dim),用于注入序列中每个token的位置信息。位置编码会加到输入嵌入中,从而让模型能够感知每个token在序列中的相对位置。
    3. 进入自注意力(Self-Attention)层:
      数据进入自注意力层后,首先通过三个全连接层(embed_dim, d_model)分别计算(即 $X @ W_Q$ )得到查询(Q)、键(K)、值(V)矩阵(batch_size, seq_len, d_model)。这些矩阵的参数会通过后续的梯度下降优化学习得到。
      将三个矩阵分割成 head 个头,得到形状为 (batch_size,head, seq_len, d_model/head) 的子注意力头
      对每个token的查询向量Q与其他token的键向量K进行点积计算,得到注意力得分(batch_size,head, seq_len, seq_len)。通过缩放因子(根号下 d_k)对得分进行缩放,再通过softmax操作将得分转换为概率分布。
      接着,使用该概率分布对值向量V进行加权求和,得到一个融合了上下文信息的新的表示((batch_size,head, seq_len, d_model/head))。
    4. 还原维度:
      将注意力子头的数据相融合得到(batch_size, seq_len, d_model),再经过全连接层 $W_o$(d_model, d_model),得到(batch_size, seq_len,d_model)
    5. 残差连接与层归一化:
      将注意力层的输出与输入数据进行残差连接,然后进行层归一化(Layer Normalization)。残差连接有助于缓解深层网络中的梯度消失问题,而层归一化则有助于提升训练的稳定性,数据形状不变。
    6. 前馈神经网络(MLP):
      接着,经过一个前馈神经网络(MLP)。这个网络通常由两层全连接层组成,其中中间层的大小通常是输入的4倍,最终得到的数据形状为(batch_size, seq_len,d_model)。前馈神经网络的作用是进一步非线性转换每个token的表示。
    7. 再次残差连接与层归一化:
      前馈神经网络的输出会再次与输入数据进行残差连接,并进行层归一化处理,确保模型在深层网络中保持有效的信息流动和稳定的训练过程。
    8. 完成一次Encoder处理:
      这样,我们就完成了一个Transformer编码器层的处理。
    9. 重复堆叠多个编码器层:
      上述的编码器层结构会堆叠若干次,每一层都包括自注意力、前馈神经网络和层归一化等组件。通过多层堆叠,模型能够逐步捕捉更复杂的特征和语义信息。
  • 对于解码器

    1. 目标序列的输入
      解码器的输入是目标语言的 token 序列,首先进行分词处理,得到形状(batch_size, target_seq_len) (每个 token 被映射为 ID)

    2. 词嵌入与位置编码
      通过词嵌入(Input Embedding)将 token ID 映射为高维向量:

      • 形状:(batch_size, target_seq_len, d_model)

      添加位置编码(Positional Encoding),用于注入序列中的位置信息:

      • 形状:(batch_size, target_seq_len, d_model)

      位置编码与词嵌入逐元素相加,形成解码器的输入向量。

    3. 掩码多头自注意力(Masked Multi-Head Self-Attention)
      作用:防止解码器看到未来 token,以确保自回归生成的正确性。
      计算过程:

      • 计算 Q, K, V
        目标序列的输入经过 W_Q, W_K, W_V 三个全连接层后,分别得到Q, K, V
        形状:(batch_size, target_seq_len, d_model)
      • 分割成 head 个独立注意力头:
        形状:(batch_size, head, target_seq_len, d_k),其中 d_k = d_model / head
      • 计算注意力得分

Q @ K^T / sqrt(d_k),得到:
形状:(batch_size, head, target_seq_len, target_seq_len)
- 应用 Mask,将未来 token 的得分置为 -∞,再经过 Softmax 归一化。
- 计算注意力输出
使用 Softmax 权重加权求和 V,得到:
形状:(batch_size, head, target_seq_len, d_k)
- 拼接多个头,经过 W_o 变换,恢复:
形状:(batch_size, target_seq_len, d_model)
- 残差连接与层归一化
形状:(batch_size, target_seq_len, d_model)
4. 编码器-解码器交叉注意力(Encoder-Decoder Attention)
作用:让解码器能够参考编码器的输出,与源序列进行交互。
计算过程:

  • 计算 Q, K, V
    Q 来自上一层解码器的输出,形状:(batch_size, target_seq_len, d_model)
    K, V 来自编码器的最终输出,形状:(batch_size, source_seq_len, d_model)
  • 计算注意力得分
    $Q @ K^T / sqrt(d_k)$,得到:
    形状:(batch_size, head, target_seq_len, source_seq_len)
    经过 Softmax 归一化,生成注意力权重。
  • 计算注意力输出
    使用 Softmax 权重加权求和 V,得到:
    形状:(batch_size, head, target_seq_len, d_k)
  • 拼接多个头,经过 W_o 变换:
    形状:(batch_size, target_seq_len, d_model)
  • 残差连接与层归一化
    形状:(batch_size, target_seq_len, d_model)
  1. 前馈神经网络(Feed-Forward Network, FFN)
    作用:进一步变换特征,提高表示能力。
    计算过程:

    • 经过第一层全连接层,将维度扩展至 4 * d_model,并使用 ReLU 激活:
      形状:(batch_size, target_seq_len, 4 * d_model)
    • 经过第二层全连接层,将维度降回 d_model:
      形状:(batch_size, target_seq_len, d_model)
    • 残差连接与层归一化
      形状:(batch_size, target_seq_len, d_model)
  2. 解码器堆叠多个层
    解码器的上述结构会堆叠 N 层(标准 Transformer 取 N=6)。
    通过多层堆叠,模型能够逐步捕捉复杂的特征,并有效建模目标序列的上下文关系。

  3. 线性层 + Softmax(输出层)
    解码器的最终输出传入 全连接层(Linear),投影至词汇表大小:
    形状:(batch_size, target_seq_len, vocab_size)
    经过 Softmax 归一化,得到每个 token 在词汇表上的概率分布:
    形状:(batch_size, target_seq_len, vocab_size)

  4. 自回归生成(Inference 阶段)
    训练时,整个目标序列同时输入,计算并行进行。
    但推理时(生成文本),解码器采用 自回归(Autoregressive) 方式:
    先输入起始 token(例如 <BOS>),生成第一个 token。
    将已生成的 token 作为新输入,继续生成下一个 token。
    依次重复,直到生成 <EOS> 或达到最大长度。

对于文本情感分类任务

事实上这个任务完全不需要解码器的存在,因为是分类任务而不是生成任务

所以只需要编码器的堆叠,最终通过全连接层进行二分类即可

Transformer的优势

  1. 并行计算:与传统的 RNN 和 CNN 不同,Transformer 可以一次性处理整个序列,大大提高了计算效率。
  2. 长距离依赖建模:通过自注意力机制,Transformer 能够捕捉到序列中任意两位置之间的依赖关系,从而解决了 RNN 无法有效建模长距离依赖的问题。
  3. 高效训练:由于 Transformer 结构的简洁性和并行性,它能够大幅提高训练的速度,尤其是在处理大规模数据时。

Transformer 模型的核心优势在于能够通过自注意力机制捕捉长距离的依赖关系,并且通过并行计算极大地提升了处理速度。这使得它成为了自然语言处理(NLP)领域的革命性突破,并成为许多现代预训练语言模型(如 BERT、GPT)架构的基础。

关于 Transformer 的变体

BERT

你做完型填空的时候也不会只看一边,对吧

假如现在的任务目标不再是翻译,而是纯粹的语言理解(语义情感分析,问答),那么完全不需要 Decoder ,只需要 Encoder 就已经能够充分完成任务了。

BERT 基于 Transformer 的编码器部分,完全摒弃了解码器,主要用于理解输入序列的上下文信息。BERT 的输入是一个句子或一对句子,它通过以下两种方式来获取丰富的上下文信息:

  1. Masked Language Model(MLM):
    为了进行预训练,BERT 会随机遮蔽输入中的一部分单词,通过上下文信息来预测这些被遮蔽的单词。这种训练方式确保模型能有效学习到每个词在上下文中的角色。主要目标是学会词元之间的关系。
  2. Next Sentence Prediction(NSP):
    BERT 还通过预测句子间的关系来进一步增强理解能力。它随机选取两个句子,判断第二个句子是否是第一个句子的下一句,从而训练模型捕捉句子间的语义关系。主要目标是学会句间的关系。

BERT的独特之处还在于其 双向编码能力

既然是纯碎的语言理解,那么就 没必要单向阅读 了吧,所以这里引入了 双向理解 的思路,这样显然而且试验证明的确能提高模型性能。

传统的语言模型(如 GPT)是单向的,只能从左到右或从右到左理解文本,而 BERT 通过双向的方式同时捕捉前后文的关系,从而更好地理解词汇的含义。

GPT

你说话的时候总得想着你上一句是什么吧

理解一句话的意思不仅仅是拼凑单词的意义,而是要在理解词汇的同时,还能生成合乎逻辑且富有创意的内容。这正是 GPT(Generative Pretrained Transformer) 模型的核心能力。它不仅能理解输入的文本,还能在此基础上进行创造性地生成输出。

GPT 是基于 Transformer 架构的一个衍生模型,最重要的特点是完全依赖 自回归模型,即通过前文的单词逐步生成后续单词,这使得它在生成连贯且自然的文本时具有无与伦比的优势。

BERT 相对,GPT 反而是只聚焦于 解码器,它通过连续生成预测下一个词汇来实现文本生成。在每一个词生成之前,都要把上文已经有的文本进行处理,经过若干解码器的堆叠处理,然后得到最可能的下一个词,如此循环往复,最终得到完整的生成文本。

GPT 的关键特点是自回归生成过程。它从一个初始的输入开始,逐步预测下一个词汇,然后把这个词汇作为新的输入加入到序列中继续预测下一个词汇,直到生成完整的句子或段落。

GPT 的成功在于通过大量数据的预训练,使得模型能够在生成文本时,不仅理解词汇,还能够创作出合理且自然的语言。这让它成为当前自然语言处理领域的重要突破之一。

Reformer

深度压缩与效率的化身

当句子一长,token增多,那么如果对于每一个 token 都需要对其他所有的 token 计算注意力得分,这显然是一个相当低效的过程。

其实想一下,对于每一个 token 而言,对于它比较重要的 token 也没多少,其实不需要对于其他 每一个 token 计算注意力(计算出来的注意力得分权重为0.0000000000001要你有啥用)。如果能将相似注意力的 token 放在一起,只在他们之间计算注意力得分,然后加权求和,就很好的缩小了问题的规模,这就是 Reformer

Reformer架构设计
Reformer 的设计理念是将 注意力机制压缩存储 结合起来,从而使得计算效率和内存使用得到了极大的提升。它的关键创新点包括:

  1. 局部敏感哈希
    Reformer用 局部敏感哈希(LSH) 来替代传统的 全局 自注意力计算。这种方式将注意力计算限制在邻近的词汇之间,避免了传统自注意力计算中计算量过大的问题,从而有效减少了计算复杂度。
  2. 可逆残差网络
    为了降低内存占用,Reformer 引入了 可逆残差网络 的概念。这意味着在计算时,不需要保留中间层的所有输出,而是通过反向传递过程来恢复它们,这大大减少了内存的使用。
  3. 分块计算
    Reformer 对输入数据进行分块处理,每次只处理小块的局部信息,而不是一次性处理整个序列。这样可以进一步降低计算开销,尤其是在处理超大规模数据时。

有了如上的改进,可以在保证原 Transformer 的性能的基础上:

  • 更低的内存消耗:通过局部敏感哈希和可逆残差网络,Reformer 在处理大规模数据时显著减少了内存使用,提升了训练效率。
  • 高效处理长序列:Reformer 在处理长序列时,通过分块计算避免了全局计算的瓶颈,使得它能够在有限资源下处理更长的文本。

CycleGAN 实现莫奈风格画作转化为真实照片

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""
使用 CycleGAN 实现莫奈风格图像转化为真实照片风格图片
在同一根目录下需要:
datasets/monet2photo/trainA 文件夹存放莫奈风格画像
datasets/monet2photo/trainB 文件夹存放真实风景图片
"""
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.datasets import ImageFolder
import os

# 设置参数
batch_size = 1
lr = 0.0002
epochs = 1000
input_channel_size = 3
output_channel_size = 3

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
os.makedirs("output_cyclegan", exist_ok=True)

# 数据预处理
transform = transforms.Compose([
transforms.Resize((256, 256)), # 统一图片大小,方便处理
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # RGB 图片归一化
])

# 加载数据集
dataset_A = ImageFolder(root="datasets/monet2photo/trainA", transform=transform) # 莫奈画像
dataset_B = ImageFolder(root="datasets/monet2photo/trainB", transform=transform) # 真实照片


dataloader_A = DataLoader(dataset_A, batch_size=batch_size, shuffle=True)
dataloader_B = DataLoader(dataset_B, batch_size=batch_size, shuffle=True)


# 残差块
class ResnetBlock(nn.Module):
def __init__(self, dim):
super().__init__()
self.conv_block = nn.Sequential(
nn.ReflectionPad2d(1), # 反射填充,缓解填充带来的差异
nn.Conv2d(dim, dim, kernel_size=3, padding=0, bias=False),
nn.InstanceNorm2d(dim), # 实例归一化
nn.ReLU(True),
nn.ReflectionPad2d(1),
nn.Conv2d(dim, dim, kernel_size=3, padding=0, bias=False),
nn.InstanceNorm2d(dim)
)

def forward(self, x):
return x + self.conv_block(x)


# 生成器
class Generator(nn.Module):
def __init__(self, input_channel_size, output_channel_size):
super().__init__()
self.main = nn.Sequential(
nn.ReflectionPad2d(3),
nn.Conv2d(input_channel_size, 64, kernel_size=7, padding=0, bias=False), # (4,3,256,256)->(4,64,256,256) 感受野(7*7)
nn.InstanceNorm2d(64),
nn.ReLU(True),

nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1, bias=False),# (4,64,256,256)->(4,128,128,128) 感受野(9*9)
nn.InstanceNorm2d(128),
nn.ReLU(True),

nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1, bias=False),# (4,128,128,128)->(4,256,64,64) 感受野(13*13)
nn.InstanceNorm2d(256),
nn.ReLU(True),

ResnetBlock(256),
ResnetBlock(256),
ResnetBlock(256),
ResnetBlock(256), # (4,256,64,64)->(4,256,64,64) 感受野(13*13)

nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, output_padding=1, bias=False),# (4,256,64,64)->(4,128,128,128) 感受野(17*17)
nn.InstanceNorm2d(128),
nn.ReLU(True),

nn.ConvTranspose2d(128, 64, kernel_size=3, stride=2, padding=1, output_padding=1, bias=False),# (4,128,128,128)->(4,64,256,256) 感受野(25*25)
nn.InstanceNorm2d(64),
nn.ReLU(True),

nn.ReflectionPad2d(3),
nn.Conv2d(64, output_channel_size, kernel_size=7, padding=0),# (4,64,128,128)->(4,3,256,256) 感受野(31*31)
nn.Tanh()
)

def forward(self, input):
return self.main(input)


# 判别器
class Discriminator(nn.Module):
def __init__(self, input_channel_size):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(input_channel_size, 64, kernel_size=4, stride=2, padding=1), # (4,3,256,256)->(4,64,128,128)
nn.LeakyReLU(0.2, True),

nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1, bias=False), # (4,64,128,128)->(4,128,64,64)
nn.InstanceNorm2d(128),
nn.LeakyReLU(0.2, True),

nn.Conv2d(128, 256, kernel_size=4, stride=2, padding=1, bias=False), # (4,128,64,64)->(4,256,32,32)
nn.InstanceNorm2d(256),
nn.LeakyReLU(0.2, True),

nn.Conv2d(256, 1, kernel_size=4, stride=2, padding=1) # (4,256,32,32)->(4,1,16,16)
# 使用更为高级的 PatchGAN 判别器而不是普通的判别器设计,这样设计能让模型更加关注局部区域的真实程度,这正对应了图像风格转化的任务特性
)

def forward(self, input):
return self.model(input)


netG = Generator(input_channel_size, output_channel_size).to(device) # A->B
netF = Generator(output_channel_size, input_channel_size).to(device) # B->A
netD_A = Discriminator(input_channel_size).to(device) # 检验图像是否是真实的A
netD_B = Discriminator(output_channel_size).to(device) # 检验图像是否是真实的B

criterion_GAN = nn.MSELoss()
criterion_cycle = nn.L1Loss()

optimizer_GF = optim.Adam(list(netG.parameters()) + list(netF.parameters()), lr=lr, betas=(0.5, 0.999)) # 由于循环一致性的设计,需要将 G 和 F 两个模型绑定在一起联合优化,保证相同的优化速率和效果
optimizer_D_A = optim.Adam(netD_A.parameters(), lr=lr, betas=(0.5, 0.999))
optimizer_D_B = optim.Adam(netD_B.parameters(), lr=lr, betas=(0.5, 0.999))

# 训练循环
for epoch in range(epochs):
for i, ((real_A, _), (real_B, _)) in enumerate(zip(dataloader_A, dataloader_B)):
real_A = real_A.to(device)
real_B = real_B.to(device)

# 训练生成器
optimizer_GF.zero_grad()
# 生成转化之后的图像
fake_B = netG(real_A)
fake_A = netF(real_B)
valid_B = torch.ones_like(netD_B(fake_B))
valid_A = torch.ones_like(netD_A(fake_A))
# 计算损失
# 计算对抗性损失
loss_G_GAN = criterion_GAN(netD_B(fake_B), valid_B)
loss_F_GAN = criterion_GAN(netD_A(fake_A), valid_A)
# 计算循环一致性损失
recovered_A = netF(fake_B) # 将生成出的虚假图像 B 转化回去
recovered_B = netG(fake_A) # 将生成出的虚假图像 A 转化回去
loss_cycle_A = criterion_cycle(recovered_A, real_A)
loss_cycle_B = criterion_cycle(recovered_B, real_B)
total_cycle_loss = (loss_cycle_A + loss_cycle_B) * 10 # 10 是权重
# 计算总损失值
total_G_loss = loss_G_GAN + loss_F_GAN + total_cycle_loss
total_G_loss.backward()
optimizer_GF.step()


# 训练判别器
# 训练判别器 A
real_output_A = netD_A(real_A) # 来自真实的图像
fake_output_A = netD_A(fake_A.detach()) # 来自虚假的图像,细节阻断梯度传播,防止波及生成fake_A 的 netF
loss_D_real_A = criterion_GAN(real_output_A, torch.ones_like(real_output_A)) # 应接近于 1
loss_D_fake_A = criterion_GAN(fake_output_A, torch.zeros_like(fake_output_A)) # 应接近于 0
loss_D_A = 0.5 * (loss_D_real_A + loss_D_fake_A) # 0.5 代表取均值
loss_D_A.backward()
optimizer_D_A.step()
# 训练判别器 B
# 与 A 类似
real_output_B = netD_B(real_B)
fake_output_B = netD_B(fake_B.detach())
loss_D_real_B = criterion_GAN(real_output_B, torch.ones_like(real_output_B))
loss_D_fake_B = criterion_GAN(fake_output_B, torch.zeros_like(fake_output_B))
loss_D_B = 0.5 * (loss_D_real_B + loss_D_fake_B)
loss_D_B.backward()
optimizer_D_B.step()


if i % 100 == 0:
print(f"Epoch [{epoch + 1}/{epochs}] Batch {i} G_loss: {total_G_loss.item():.4f} D_A: {loss_D_A.item():.4f} D_B: {loss_D_B.item():.4f}")
# 保存生成器参数,方便加载使用
torch.save(netG.state_dict(), "generator_monet2real.pth") # monet->real 参数
torch.save(netF.state_dict(), "generator_real2monet.pth") # real->monet 参数

CycleGAN 详解

CycleGAN 是一种无监督图像到图像转换(Image-to-Image Translation)模型,能够在不需要成对训练数据的情况下实现高质量的风格转换。该方法由 Jun-Yan Zhu 等人在 2017 年提出,并在风格迁移、图像修复、医学影像处理等领域有着广泛的应用。

1. CycleGAN 简介

CycleGAN(Cycle-Consistent Generative Adversarial Networks)主要解决未配对数据(Unpaired Data)的图像转换问题。例如,我们可以使用 CycleGAN 在不需要成对的”马”和”斑马”图片的情况下,将一匹马的图像转换为斑马风格,反之亦然。

相比于 Pix2Pix 这样的有监督方法(需要成对数据),CycleGAN 的最大特点是无监督学习,它使用循环一致性损失(Cycle Consistency Loss)来确保图像转换的可逆性。

2. CycleGAN 主要结构

CycleGAN 由两个 GAN 组成,每个 GAN 负责将一种风格转换为另一种:

  • 生成器 G(X → Y):将域 X(如马的图片)转换为域 Y(如斑马的图片)。
  • 生成器 F(Y → X):将域 Y 的图像转换回域 X。
  • 判别器 D_X:判断给定的 X 域图像是真实的还是由 F 生成的。
  • 判别器 D_Y:判断给定的 Y 域图像是真实的还是由 G 生成的。

CycleGAN 的关键点在于循环一致性损失,它确保如果我们将图像从 X → Y,再从 Y → X,得到的图像应该与原始 X 类似。

3. CycleGAN 训练过程

CycleGAN 采用对抗训练框架,同时优化两个目标:

3.1 对抗损失(Adversarial Loss)

CycleGAN 继承了标准 GAN 的损失,使得生成器 G 生成的图像尽可能真实:

$$ L_{GAN}(G, D_Y, X, Y) = \mathbb{E}{y \sim p{data}(y)} [\log D_Y(y)] + \mathbb{E}{x \sim p{data}(x)} [\log (1 - D_Y(G(x)))] $$

类似地,F 也有自己的 GAN 损失:

$$ L_{GAN}(F, D_X, Y, X) = \mathbb{E}{x \sim p{data}(x)} [\log D_X(x)] + \mathbb{E}{y \sim p{data}(y)} [\log (1 - D_X(F(y)))] $$

3.2 循环一致性损失(Cycle Consistency Loss)

为了确保 G(X) 能够转换回 X,我们引入循环一致性损失:

$$ L_{cycle}(G, F) = \mathbb{E}{x \sim p{data}(x)} [||F(G(x)) - x||1] + \mathbb{E}{y \sim p_{data}(y)} [||G(F(y)) - y||_1] $$

3.3 全损失函数

综合以上损失,CycleGAN 的最终目标函数为:

$$ L(G, F, D_X, D_Y) = L_{GAN}(G, D_Y, X, Y) + L_{GAN}(F, D_X, Y, X) + \lambda L_{cycle}(G, F) $$

其中,( \lambda ) 是权重参数,控制循环一致性损失的重要程度。

4. CycleGAN 的应用场景

风格转换:如将照片转换为油画风格,或者将真实图像转换为动漫风格。
图像增强:如提高医学影像的质量或将黑白照片转换为彩色。
域适应:用于将数据从一个领域(Domain)映射到另一个领域。
图像修复:在去雾、去噪声等任务中表现良好。

5. CycleGAN 的优缺点

5.1 优点

  • 无需成对数据,适用于无监督图像转换任务。
  • 效果自然,生成图像更加逼真。
  • 结构简单,训练方法类似于标准 GAN。

5.2 缺点

  • 容易模式崩溃(Mode Collapse),导致生成的图像缺乏多样性。
  • 训练不稳定,对超参数(如学习率、循环损失权重等)敏感。
  • 转换不一定完全准确,对于复杂场景可能生成伪影(Artifacts)。
|