在 Colab 中打开

1. 深度学习的开端:基本原理与技术演进的脉络

探索深度学习DNA的开始

“真正的技术创新诞生于过去的失败” - 杰弗里·辛顿,2018年图灵奖获奖演讲

1.1 本书的目的

深度学习是机器学习的一个领域,近年来取得了惊人的成果并迅速发展。出现了像GPT-4、Gemini这样的大型语言模型(LLM),对通用人工智能(AGI)的期望与担忧并存。随着研究论文和技术的快速发展,即使是专家也难以跟上。

这种情况类似于20世纪80年代末个人电脑和编程语言普及的时代。当时出现了许多技术,但最终只有少数核心技术成为了现代计算的基础。同样地,在当前的各种深度学习架构中,如神经网络、CNN、RNN、变压器、扩散模型、多模态等,只有少数共享核心DNA的技术会成为AI的基石,并持续发展。

本书就是从这一视角开始的。不仅仅介绍简单的API使用方法或基础理论和示例,而是剖析技术发展的DNA。从1943年的McCulloch-Pitts神经元模型到2025年最新的多模态架构,如同生物进化过程一样,聚焦于每项技术出现的背景、试图解决的根本问题以及与前代技术之间的联系。即绘制深度学习技术谱系图。1.2节将简要总结其内容。

为此,本书具有以下特点。

  • DNA视角的解释: 不仅仅是罗列技术,而是解释每项技术为何出现、解决了什么问题以及与前代技术有何种关系,即以技术的谱系(phylogeny)为中心进行解释。
  • 简洁而深入的说明: 帮助清晰理解核心概念和原理,同时大胆省略不必要的细节。
  • 反映最新技术趋势: 包括截至2025年的最新技术(例如,记忆网络、专家混合模型、多模态模型),涵盖深度学习发展的最前沿。
  • 连接实践与研究的桥梁: 均衡地提供实用代码示例和数学直觉,将理论与实际相联系。
  • 高级示例: 不仅仅提供可运行的代码,还提供了足够成熟、可以直接应用于实际研究或开发中的示例。

通过这些内容,本书旨在帮助从业者和研究人员提高专业水平。同时,也探讨了AI技术所具有的伦理和社会影响以及技术民主化的问题。

1.2 深度学习的历史

挑战: 如何让机器像人类一样思考和学习?

研究者的苦恼: 模仿人脑复杂的运作方式是一项极其困难的任务。初期研究人员依赖简单的规则基础系统或有限的知识数据库,但这些方法在处理现实世界的多样性和复杂性方面存在局限性。要创建真正智能的系统,需要具备从数据中自我学习、识别复杂模式和理解抽象概念的能力。如何实现这一点是核心难题。

深度学习的历史始于1943年Warren McCulloch和Walter Pitts提出McCulloch-Pitts 神经元这一数学模型来描述神经元的工作方式,这定义了神经网络的基本组成部分。1949年,Donald Hebb提出了Hebbian 学习规则,解释了突触权重调整,即学习的基本原理。1958年,Frank Rosenblatt的感知机(Perceptron)是首个实用的神经网络,但面临着非线性分类问题(如XOR问题)的限制。

20世纪80年代是一个重要的突破期。1980年,Kunihiko Fukushima提出了Neocognitron (卷积原理的基础),这成为了后来CNN的核心思想。最重要的发展是1986年Geoffrey Hinton研究团队开发的反向传播(backpropagation)算法,该算法使得多层神经网络的有效学习成为可能,并奠定了神经网络学习的核心地位。2006年Hinton提出“深度学习”术语,标志着一个新的转折点。

此后,随着大规模数据和计算能力的发展,深度学习迅速增长。2012年,AlexNet在ImageNet竞赛中以压倒性的性能获胜,证明了深度学习的实用性。之后,出现了使用循环网络系列中的LSTM (1997)注意力机制 (2014) 的创新架构。特别是,2017年谷歌推出的Transformer彻底改变了自然语言处理范式。它通过自注意力将输入序列的每一部分直接与其他部分连接起来,解决了长距离依赖问题

基于Transformer的BERT、GPT系列出现,使语言模型性能大幅提升。2013年的Word2Vec 开启了词嵌入的新视野。在生成模型领域,从2014年的GAN2020年的扩散模型,高质量图像生成成为可能。2021年,随着视觉Transformer (ViT) 的出现,Transformer开始成功应用于图像处理,并加速了多模态学习的发展。

最近,像GPT-4、Gemini这样的大型语言模型提高了实现AGI的可能性。这些模型利用了如2023年的Retentive Networks等先进的架构、2023年之后的FlashAttention等高效技术以及2024年的Mixture of Experts (MoE) 等方法,变得更加精细。此外,能够处理文本、图像、音频等多种形式数据的多模态模型(从2024年的Gemini Ultra 2.0到2025年的Gemini 2.0)在进化过程中不仅超越了简单的问答,还展示了推理、创造和解决问题等高层次认知能力。

深度学习的发展基于以下关键要素。 1. 大规模数据可用性增加 2. GPU等高性能计算资源发展 3. Backpropagation, Attention, Transformer 等高效学习算法及 Core Architecture, Generative Models 开发

这些发展在继续,但仍然存在需要解决的问题。模型的可解释性(Interpretability)、数据效率、能源消耗以及 Efficiency & Advanced Concepts 发展问题仍然是重要的挑战。

以下是技术DNA谱系图的可视化。

Code
# 2025 Deep Learning Technology DNA Tree (Multimodal Updated)
from anytree import Node, RenderTree

# ==== Core Mathematical Foundations & Algorithms ====
root = Node("1943: McCulloch-Pitts Neuron")

math_lineage = Node("Mathematical Foundations & Algorithms", parent=root)
hebbian = Node("1949: Hebbian Learning", parent=math_lineage)
backprop = Node("1986: Backpropagation (Rumelhart, Hinton, Williams)", parent=math_lineage)
neuroplasticity = Node("1958: Cortical Plasticity Theory (Mountcastle)", parent=math_lineage)
sparsity = Node("2023: Sparse Symbolic Representations (DeepMind)", parent=backprop)
liquid_clocks = Node("2024: Liquid Time-constant Networks", parent=sparsity)
dynamic_manifolds = Node("2025: Dynamic Neural Manifolds", parent=liquid_clocks)

# ==== Core Architecture ====
core_arch = Node("Core Architecture", parent=root)
conv_principle = Node("1980: Convolution Principle (Neocognitron - Fukushima)", parent=core_arch)
attention = Node("2014: Attention Mechanism (Bahdanau)", parent=core_arch)
transformer = Node("2017: Transformer (Vaswani)", parent=attention)
retentive_net = Node("2023: Retentive Networks (Microsoft)", parent=transformer)
hybrid_ssm = Node("2024: Hybrid State-Space Models", parent=retentive_net)

# ==== Computer Vision ====
cv_lineage = Node("Computer Vision", parent=root)
lenet = Node("1998: LeNet-5 (LeCun)", parent=cv_lineage)
alexnet = Node("2012: AlexNet (Krizhevsky)", parent=lenet)
resnet = Node("2015: ResNet (He)", parent=alexnet)
vision_transformer = Node("2021: ViT (Vision Transformer) (Dosovitskiy)", parent=resnet)
vit22b = Node("2023: ViT-22B (Google)", parent=vision_transformer)
masked_autoenc = Node("2024: MAE v3 (Meta)", parent=vit22b)
vit40b = Node("2025: ViT-40B (Google/Sydney)", parent=masked_autoenc)
efficient_vit = Node("2025: EfficientViT-XXL", parent=vit40b)

# ==== NLP ====
nlp_lineage = Node("NLP", parent=root)
word2vec = Node("2013: Word2Vec (Mikolov)", parent=nlp_lineage)
bert = Node("2018: BERT (Devlin)", parent=word2vec)
gpt3 = Node("2020: GPT-3 (OpenAI)", parent=bert)
gpt5 = Node("2023: GPT-5 (OpenAI)", parent=gpt3)
gpt55_turbo = Node("2024: GPT-5.5 Turbo", parent=gpt5)
gpt6 = Node("2025: GPT-6 (Multimodal Agent)", parent=gpt55_turbo)

# ==== Multimodal Learning ====
mm_lineage = Node("Multimodal Learning", parent=root)
clip = Node("2021: CLIP (OpenAI)", parent=mm_lineage)
flamingo = Node("2022: Flamingo (DeepMind)", parent=clip)
kosmos = Node("2023: Kosmos-2.5 (Microsoft)", parent=flamingo)
gemini = Node("2024: Gemini Ultra 2.0 (Google)", parent=kosmos)
gemini_multiverse = Node("2025: Gemini Multiverse (Google)", parent=gemini)
project_starline = Node("2025: Project Starline 2.0 (3D Multimodal)", parent=gemini_multiverse)

# ==== Generative Models ====
gen_lineage = Node("Generative Models", parent=root)
vae = Node("2013: VAE (Kingma)", parent=gen_lineage)
gan = Node("2014: GAN (Goodfellow)", parent=vae)
stylegan = Node("2018: StyleGAN (Karras)", parent=gan)
diffusion = Node("2020: Diffusion Models (Ho)", parent=stylegan)
sdxl_turbo = Node("2023: SDXL-Turbo (Stability AI)", parent=diffusion)
meta3d_diff = Node("2024: Meta 3D Diffusion", parent=sdxl_turbo)
holo_gen = Node("2025: HoloGen (Neural Holography)", parent=meta3d_diff)

# ==== Reinforcement Learning ====
rl_lineage = Node("Reinforcement Learning", parent=root)
tdlearn = Node("1988: TD Learning (Sutton)", parent=rl_lineage)
dqn = Node("2013: DQN (DeepMind)", parent=tdlearn)
alphago = Node("2016: AlphaGo (Silver)", parent=dqn)
muzero = Node("2019: MuZero (DeepMind)", parent=alphago)
robot_transformer = Node("2023: RT-2 (Google)", parent=muzero)
agentic_cortex = Node("2024: Agentic Cortex (DeepMind)", parent=robot_transformer)
autogpt5 = Node("2025: AutoGPT-5 (Fully Autonomous Agent)", parent=agentic_cortex)

# ==== Efficiency & Advanced Concepts ====
eff_lineage = Node("Efficiency & Advanced Concepts", parent=root)
flash_attn3 = Node("2023: FlashAttention-v3", parent=eff_lineage)
moa = Node("2024: MoA (Mixture of Agents)", parent=flash_attn3)
nas3 = Node("2025: Neural Architecture Search 3.0", parent=moa)

# ==== Print Tree Structure ====
print("2025 Neural Network Evolution Tree:\n")
for pre, _, node in RenderTree(root):
    print(f"{pre}{node.name}")
2025 Neural Network Evolution Tree:

1943: McCulloch-Pitts Neuron
├── Mathematical Foundations & Algorithms
│   ├── 1949: Hebbian Learning
│   ├── 1986: Backpropagation (Rumelhart, Hinton, Williams)
│   │   └── 2023: Sparse Symbolic Representations (DeepMind)
│   │       └── 2024: Liquid Time-constant Networks
│   │           └── 2025: Dynamic Neural Manifolds
│   └── 1958: Cortical Plasticity Theory (Mountcastle)
├── Core Architecture
│   ├── 1980: Convolution Principle (Neocognitron - Fukushima)
│   └── 2014: Attention Mechanism (Bahdanau)
│       └── 2017: Transformer (Vaswani)
│           └── 2023: Retentive Networks (Microsoft)
│               └── 2024: Hybrid State-Space Models
├── Computer Vision
│   └── 1998: LeNet-5 (LeCun)
│       └── 2012: AlexNet (Krizhevsky)
│           └── 2015: ResNet (He)
│               └── 2021: ViT (Vision Transformer) (Dosovitskiy)
│                   └── 2023: ViT-22B (Google)
│                       └── 2024: MAE v3 (Meta)
│                           └── 2025: ViT-40B (Google/Sydney)
│                               └── 2025: EfficientViT-XXL
├── NLP
│   └── 2013: Word2Vec (Mikolov)
│       └── 2018: BERT (Devlin)
│           └── 2020: GPT-3 (OpenAI)
│               └── 2023: GPT-5 (OpenAI)
│                   └── 2024: GPT-5.5 Turbo
│                       └── 2025: GPT-6 (Multimodal Agent)
├── Multimodal Learning
│   └── 2021: CLIP (OpenAI)
│       └── 2022: Flamingo (DeepMind)
│           └── 2023: Kosmos-2.5 (Microsoft)
│               └── 2024: Gemini Ultra 2.0 (Google)
│                   └── 2025: Gemini Multiverse (Google)
│                       └── 2025: Project Starline 2.0 (3D Multimodal)
├── Generative Models
│   └── 2013: VAE (Kingma)
│       └── 2014: GAN (Goodfellow)
│           └── 2018: StyleGAN (Karras)
│               └── 2020: Diffusion Models (Ho)
│                   └── 2023: SDXL-Turbo (Stability AI)
│                       └── 2024: Meta 3D Diffusion
│                           └── 2025: HoloGen (Neural Holography)
├── Reinforcement Learning
│   └── 1988: TD Learning (Sutton)
│       └── 2013: DQN (DeepMind)
│           └── 2016: AlphaGo (Silver)
│               └── 2019: MuZero (DeepMind)
│                   └── 2023: RT-2 (Google)
│                       └── 2024: Agentic Cortex (DeepMind)
│                           └── 2025: AutoGPT-5 (Fully Autonomous Agent)
└── Efficiency & Advanced Concepts
    └── 2023: FlashAttention-v3
        └── 2024: MoA (Mixture of Agents)
            └── 2025: Neural Architecture Search 3.0

1.3 Hebbian 学习

在沃伦·麦卡洛克(Warren McCulloch)和沃尔特·皮茨(Walter Pitts)于1943年提出人工神经网络模型(McCulloch-Pitts Neuron)之后,1949年加拿大心理学家唐纳德·赫布(Donald O. Hebb)在他的著作《行为的组织》(“The Organization of Behavior”)中提出了神经网络学习的基本原理。这一原理被称为赫布规则(Hebb’s Rule)赫布学习(Hebbian Learning),对包括深度学习在内的人工神经网络研究产生了深远的影响。

1.3.1 赫布学习规则

赫布学习的核心思想非常简单。当两个神经元同时或反复激活时,它们之间的连接强度会增加。相反,如果两个神经元在不同时间点被激活,或者只有一个神经元被激活而另一个没有,则连接强度会减弱甚至消失。

这可以表示为以下公式:

\[ \Delta w_{ij} = \eta \cdot x_i \cdot y_j \]

其中,

  • \(\Delta w_{ij}\)是神经元\(i\)和神经元\(j\)之间的连接强度(权重)变化量。
  • \(\eta\)是学习率(learning rate),用于调节连接强度变化的大小。
  • \(x_i\)是神经元\(i\)的激活值(输入)。
  • \(y_j\)是神经元\(j\)的激活值(输出)。

该公式表明,当神经元\(i\)和神经元\(j\)都被激活时(即\(x_i\)\(y_j\)均为正值),连接强度增加(\(\Delta w_{ij}\)为正)。相反,如果只有一个被激活或两者都未被激活,则连接强度减少或不变。赫布学习是无监督学习(unsupervised learning)的早期形式之一。也就是说,在没有提供正确答案(label)的情况下,神经网络通过输入数据的模式自行调节连接强度进行学习。

1.3.2 与脑可塑性的关联

赫布学习不仅是一个简单的数学规则,还提供了对大脑工作方式的重要洞察。大脑通过经验和学习不断变化,这种变化称为脑可塑性(brain plasticity)神经可塑性(neural plasticity)。赫布学习在解释神经可塑性的一种形式——突触可塑性(synaptic plasticity)中起着关键作用。突触是神经元之间的连接部位,决定了信息传递的效率。赫布学习清晰地展示了突触可塑性的基本原理,即,“一起激活的神经元会一起连接”。长期增强(Long-Term Potentiation, LTP)和长期抑制(Long-Term Depression, LTD)是突触可塑性的典型例子。LTP是指根据赫布学习规则突触连接被加强的现象,而LTD则是其相反现象。LTP和LTD在学习、记忆以及大脑发育过程中起着重要作用。

1.4 神经网络(NN, Neural Network)

神经网络是一种函数逼近器,它接收输入并生成尽可能接近所需输出的值。这在数学上表示为 \(f_\theta\),其中 \(f\) 表示函数,\(\theta\) 表示由权重(weight)和偏置(bias)组成的参数。神经网络的核心在于能够通过数据自动学习这些参数。

1944年,Warren McCullough 和 Walter Pitts 首次提出的神经网络受到生物神经元的启发,但现代神经网络纯粹是数学模型。实际上,神经网络是一个强大的数学工具,可以近似连续函数,这一点已经由通用逼近定理(Universal Approximation Theorem)证明。

1.4.1 神经网络的基本结构

神经网络具有分层结构,由输入层、隐藏层和输出层组成。每一层由节点(神经元)构成,这些节点相互连接以传递信息。基本上,神经网络是由线性变换和非线性激活函数的组合组成的。

从数学角度看,神经网络的每一层执行如下线性变换:

\[ y = Wx + b \]

其中:

  • \(x\) 是输入向量
  • \(W\) 是权重矩阵
  • \(b\) 是偏置向量
  • \(y\) 是输出向量

这种结构看似简单,但拥有足够神经元和层数的神经网络可以以所需的精度逼近任何连续函数。这是神经网络能够学习复杂模式并解决各种问题的原因。

普适近似定理

挑战任务: 如何证明神经网络实际上可以近似任何复杂的函数?

研究者的困惑: 即使神经网络拥有再多的层和神经元,它们是否能够真正表示所有连续函数也不是显而易见的。有担忧认为仅靠简单的线性变换组合可能无法表达复杂的非线性。如果没有理论保证,仅依赖经验结果对神经网络研究的发展是一个很大的障碍。

普适近似定理 (Universal Approximation Theorem)

普适近似定理是支持神经网络强大表达能力的核心理论。该定理证明了具有足够宽隐藏层的单层神经网络可以以所需的精度近似任何连续函数。

核心思想:

  • 非线性激活函数: ReLU、sigmoid、tanh 等非线性激活函数是使神经网络能够表达非线性的关键因素。如果没有这些激活函数,无论堆叠多少层都只是线性变换的组合。
  • 足够宽的隐藏层: 如果隐藏层中的神经元数量足够多,那么神经网络就会具有表示任意复杂函数的“灵活性”。这类似于只要有足够的碎片就可以制作出任何形状的马赛克图片。

数学表达:

定理 (普适近似定理):

\(f : K \rightarrow \mathbb{R}\) 是定义在有界闭集(compact set) \(K \subset \mathbb{R}^d\) 上的任意连续函数。对于给定的任意误差界限 \(\epsilon > 0\),存在一个单层神经网络 \(F(x)\) 满足以下条件。

\(|f(x) - F(x)| < \epsilon\), 对所有 \(x \in K\) 成立。

这里,\(F(x)\) 的形式为:

\(F(x) = \sum_{i=1}^{N} w_i \cdot \sigma(v_i^T x + b_i)\)

详细解释:

  • \(f : K \rightarrow \mathbb{R}\):

    • \(f\) 是要近似的对象函数(target function)。
    • \(K\) 是函数的定义域(domain),即 \(\mathbb{R}^d\) (d维实数空间) 的有界闭集(compact set)。直观上,“有界闭集”意味着“有边界且封闭”的集合。例如,在一维情况下,闭区间 [a, b] 是有界闭集。这个条件在现实情况中并不是很大限制,因为大多数实际输入数据都有有限的范围。
    • \(\mathbb{R}\) 表示实数集合。即函数 \(f\)\(K\) 中的每个点(\(x\))映射为一个实数值(\(f(x)\))。(多变量函数、多输出的情况将在下面进一步解释。)
  • \(\epsilon > 0\): 是表示近似精度的任意正数。\(\epsilon\) 越小,近似越精确。

  • \(|f(x) - F(x)| < \epsilon\): 对所有 \(x \in K\) 成立,表示实际函数值 \(f(x)\) 和神经网络输出 \(F(x)\) 之间的差异小于 \(\epsilon\)。即,神经网络可以在误差范围 \(\epsilon\) 内近似函数 \(f\)

  • \(F(x) = \sum_{i=1}^{N} w_i \cdot \sigma(v_i^T x + b_i)\): 表示单层神经网络的结构。

  • \(N\): 隐藏层的神经元(单元)数量。通用近似定理保证了存在一个 足够大的 \(N\),但并没有具体说明需要多大。

    • \(w_i \in \mathbb{R}\):\(i\)个隐藏层神经元与输出层神经元之间的 输出层权重(output weight)。是一个标量值。
    • \(\sigma\): 非线性激活函数(activation function)。可以使用ReLU、sigmoid、tanh、leaky ReLU等不同的函数。为了使通用近似定理成立,\(\sigma\)必须是非多项式(non-polynomial) 的,并且必须是有界的(bounded)分段连续(piecewise continuous)
    • \(v_i \in \mathbb{R}^d\):\(i\)个隐藏层神经元的 输入层权重向量(input weight vector)。与输入\(x\)具有相同的维度。
    • \(v_i^T x\): 向量\(v_i\)和输入向量\(x\)的内积(inner product, dot product)。
    • \(b_i \in \mathbb{R}\):\(i\)个隐藏层神经元的 偏置(bias)。是一个标量值。

额外说明 (多变量函数,多输出):

  • 多变量函数: 当输入\(x\)为向量时(\(x \in \mathbb{R}^d\), \(d > 1\)),通用近似定理仍然适用。\(v_i^T x\)(内积)运算自然地处理了多变量输入。

  • 多输出: 当函数\(f\)具有多个输出值时(\(f : K \rightarrow \mathbb{R}^m\), \(m > 1\)),可以为每个输出使用单独的输出层神经元和权重。即,\(F(x)\)将具有向量形式的输出,并且可以使每个输出的近似误差小于\(\epsilon\)

误差收敛速度 (Barron定理):

根据Barron定理,在某些条件下(激活函数和待逼近函数的傅里叶变换的条件),误差\(\epsilon\)与神经元数\(N\)之间的关系如下:

\(\epsilon(N) = O(N^{-1/2})\)

这意味着随着神经元数量的增加,误差以\(N^{-1/2}\)的速度减少。也就是说,当将神经元的数量增加4倍时,误差大约会减半。这是一般情况下的收敛速度,对于特定函数或激活函数可能会表现出更快或更慢的收敛速度。

反例及限制:

  • 边界逼近不可能:\(e^{-1/x^2}\)这样的函数,在\(x=0\)处无限次可微,但变化急剧的函数可能难以在\(x=0\)附近用神经网络近似。这类函数的泰勒级数为零,但函数本身不为零,这就是问题所在。

  • 离散函数的指数复杂性: 近似\(n\)变量布尔函数(Boolean function)所需的神经元数量,在最坏情况下可能与\(2^n / n\)成比例。这意味着随着输入变量数量的增加,所需神经元的数量可能会呈指数级增长。这表明神经网络不能高效地近似所有函数。

核心总结: 普遍近似定理表明,具有足够宽的隐藏层单层神经网络可以以所需的精度逼近在有界闭集上定义的任意连续函数。激活函数必须是非多项式的。这意味着神经网络具有非常强大的表示能力(representational power),并为深度学习提供了理论基础。Barron定理提供了关于误差收敛速度的见解。

重要点

  • 存在性证明: 普遍近似定理是证明存在性,而不是提供学习算法。它保证了这样的神经网络存在,但如何实际找到这些神经网络是一个单独的问题。(反向传播算法和梯度下降法是解决这个问题的方法之一。)
  • 单层与多层: 实际上,相比于单层神经网络,多层神经网络(deep neural network)通常更有效率并且具有更好的泛化性能。普遍近似定理为深度学习提供了理论基础,但深度学习的成功是多种因素结合的结果,包括多层结构、特定的架构和高效的学习算法等。从理论上讲,单层神经网络可以表示所有内容,但在实际训练中则困难得多。
  • 认识到局限性: 普遍近似定理是一个强大的结果,但并不保证能够有效地逼近所有函数。正如反例所示,某些函数可能需要非常大量的神经元才能逼近。

参考文献:

  1. Cybenko, G. (1989). Approximation by superpositions of a sigmoidal function. Mathematics of Control, Signals, and Systems, 2(4), 303-314. (关于Sigmoid激活函数的初始普遍近似定理)
  2. Hornik, K., Stinchcombe, M., & White, H. (1989). Multilayer feedforward networks are universal approximators. Neural Networks, 2(5), 359-366. (关于更一般的激活函数的普遍近似定理)
  3. Barron, A. R. (1993). Universal approximation bounds for superpositions of a sigmoidal function. IEEE Transactions on Information Theory, 39(3), 930-945. (关于误差收敛速度的Barron定理)
  4. Pinkus, A. (1999). Approximation theory of the MLP model in neural networks. Acta Numerica, 8, 143-195. (关于普遍近似定理的深入综述)
  5. Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press. (第6.4章:深度学习教科书。包含与普遍近似定理相关的内容)

1.4.2 使用线性近似器(linear approximator)进行房价预测

为了理解神经网络的基本概念,我们将考察一个简单的线性回归(Linear Regression)问题。在这里,我们使用 scikit-learn 库的加利福尼亚住房价格数据集。该数据集包含房屋的多种特征(feature),可以利用这些特征来构建预测房屋价格的模型。为了给出一个简单的例子,假设房屋价格仅由一个特征即中位数收入(MedInc)决定,并实现线性近似器。

Code
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# Load the California housing dataset
housing = fetch_california_housing(as_frame=True)
data = housing.frame

# Use only Median Income (MedInc) and Median House Value (MedHouseVal)
data = data[["MedInc", "MedHouseVal"]]

# Display the first 5 rows of the data
print(data.head())

# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(
    data[["MedInc"]], data["MedHouseVal"], test_size=0.2, random_state=42
)

# Create and train a linear regression model
model = LinearRegression()
model.fit(X_train, y_train)

# Make predictions on the test data
y_pred = model.predict(X_test)

# Prepare data for visualization
plot_data = pd.DataFrame({'MedInc': X_test['MedInc'], 'MedHouseVal': y_test, 'Predicted': y_pred})
# Sort for better line plot visualization.  Crucially, sort *after* prediction.
plot_data = plot_data.sort_values(by='MedInc')


# Visualize using Seaborn
plt.figure(figsize=(10, 6))
sns.scatterplot(x='MedInc', y='MedHouseVal', data=plot_data, label='Actual', alpha=0.6)
sns.lineplot(x='MedInc', y='Predicted', data=plot_data, color='red', label='Predicted', linewidth=2.5)
plt.title('California Housing Prices Prediction (Linear Regression)')
plt.xlabel('Median Income (MedInc)')
plt.ylabel('Median House Value (MedHouseVal)')
plt.legend()
plt.show()


# Print the trained weight (coefficient) and bias (intercept)
print("Weight (Coefficient):", model.coef_[0])
print("Bias (Intercept):", model.intercept_)
   MedInc  MedHouseVal
0  8.3252        4.526
1  8.3014        3.585
2  7.2574        3.521
3  5.6431        3.413
4  3.8462        3.422

Weight (Coefficient): 0.4193384939381271
Bias (Intercept): 0.4445972916907879

该代码首先使用 fetch_california_housing 函数加载加利福尼亚房价数据集。通过设置 as_frame=True,以 Pandas DataFrame 的形式获取数据,并仅选择房屋价格(MedHouseVal)和中位收入(MedInc)特征。使用 train_test_split 函数将数据分割为训练集和测试集,然后使用 LinearRegression 类创建线性回归模型,并通过 fit 方法对训练数据进行拟合。训练好的模型通过 predict 方法对测试数据进行预测,并使用 Seaborn 可视化实际值和预测值。最后输出训练好的模型的权重和偏置。

这样,即使简单的线性变换也能在一定程度上实现预测。神经网络则在此基础上添加非线性激活函数并堆叠多层来近似更复杂的函数。

1.4.3 通往神经网络之路:矩阵运算的过程

神经网络的前阶段是线性逼近器。这里我们将详细探讨前面的例子是如何达到实际值的。最简单的线性方程形式为 \(\boldsymbol y = \boldsymbol x \boldsymbol W + \boldsymbol b\),针对实际值 \(\boldsymbol y\)

其中,\(\boldsymbol W\) 是权重(weight parameter),\(\boldsymbol b\) 是偏置(bias)。优化这两个参数是神经网络学习的核心。在1.4中我们将看到,神经网络通过添加激活函数引入非线性,并通过反向传播优化参数。这里我们仅通过线性变换和反向传播来观察简单的计算过程。

初始时,参数被设置为任意值。

\[ \boldsymbol W = \begin{bmatrix} 0.1 \\ 0.1 \\ \end{bmatrix} \]

\[ \boldsymbol b = \begin{bmatrix} 0 \\ 0 \\ 0 \\ \end{bmatrix} \]

使用这些值进行预测:

\[ \hat{\boldsymbol y} = \begin{bmatrix} 1.5 & 1 \\ 2.4 & 2 \\ 3.5 & 3 \\ \end{bmatrix} \begin{bmatrix} 0.1 \\ 0.1 \\ \end{bmatrix} + \begin{bmatrix} 0 \\ 0 \\ 0 \\ \end{bmatrix} = \begin{bmatrix} 0.25 \\ 0.44 \\ 0.65 \\ \end{bmatrix}\]

这里 \(\hat{\boldsymbol y}\) 表示预测值。与实际值的差异(损失)如下:

\[ L = \boldsymbol y - \hat {\boldsymbol y} = \begin{bmatrix} 2.1 \\ 4.2 \\ 5.9 \\ \end{bmatrix} - \begin{bmatrix} 0.25 \\ 0.44 \\ 0.65 \\ \end{bmatrix} = \begin{bmatrix} 1.85 \\ 3.76 \\ 5.25 \\ \end{bmatrix} \]

参数优化使用梯度(gradient)。因为梯度指向误差增加的方向,因此从当前参数中减去梯度可以减少误差。 引入学习率 (\(\eta\)) 后,

\[ \text{new parameters} = \text{current parameters} - \eta \times \text{gradients} \]

例如当 \(\eta=0.01\) 时,权重更新如下:

\[ \begin{bmatrix} 0.30116 \\ 0.26746667 \\ \end{bmatrix} = \begin{bmatrix} 0.1 \\ 0.1 \\ \end{bmatrix} - 0.01 \times \begin{bmatrix} -20.116 \\ -16.74666667 \\ \end{bmatrix}\]

偏置也以相同的方式更新。通过重复前向(forward)计算和后向(backward)计算来优化参数,这是神经网络的学习过程。

1.3.4 使用Numpy实现

接下来我们探讨使用Numpy实现线性逼近器的方法。首先准备输入数据和目标值。

Code
import numpy as np

# Set input values and target values
X = np.array([[1.5, 1], [2.4, 2], [3.5, 3]])
y = np.array([2.1, 4.2, 5.9])
learning_rate = 0.01  #  Adding the learning_rate variable here, even though it's unused, for consistency.

print("X =", X)
print("y =", y)
X = [[1.5 1. ]
 [2.4 2. ]
 [3.5 3. ]]
y = [2.1 4.2 5.9]

学习率设置为0.01。学习率是影响模型学习速度和稳定性的超参数。初始化权重和偏置。

Code
m, n = X.shape

# Initialize weights and bias
weights = np.array([0.1, 0.1])
bias = 0.0  # Corrected: Bias should be a single scalar value.

print("X.shape =", X.shape)
print("Initial weights =", weights)
print("Initial bias =", bias)
X.shape = (3, 2)
Initial weights = [0.1 0.1]
Initial bias = 0.0

前向计算执行线性变换。公式如下。 \[ \boldsymbol y = \boldsymbol X \boldsymbol W + \boldsymbol b \]

Code
y_predicted = np.dot(X, weights) + bias
print("Predicted values =", y_predicted)

error = y - y_predicted
print("Error =", error)
Predicted values = [0.25 0.44 0.65]
Error = [1.85 3.76 5.25]

已经计算了损失。接下来是从损失中计算梯度。怎么做呢?权重和偏置的梯度如下:

\(\nabla_w = -\frac{2}{m}\mathbf{X}^T\mathbf{e}\)

\(\nabla_b = -\frac{2}{m}\mathbf{e}\)

其中 \(\mathbf{e}\) 是误差向量。得到梯度后,从现有参数中减去梯度值以获得参数更新后的新值。

Code
weights_gradient = -2/m * np.dot(X.T, error)
bias_gradient = -2/m * error.sum()  # Corrected: Sum the errors for bias gradient

weights -= learning_rate * weights_gradient
bias -= learning_rate * bias_gradient

print("Updated weights =", weights)
print("Updated bias =", bias)
Updated weights = [0.50232    0.43493333]
Updated bias = 0.14479999999999998

上述步骤是反向(backward)计算。由于它是按逆序依次传递计算梯度,因此也被称为反向传播(backpropagation)。现在我们将整个训练过程实现为一个函数。

Code
def train(X: np.ndarray, y: np.ndarray, lr: float, iters: int = 100, verbose: bool = False) -> tuple:
    """Linear regression training function.

    Args:
        X: Input data, shape (m, n)
        y: Target values, shape (m,)
        lr: Learning rate
        iters: Number of iterations
        verbose: Whether to print intermediate steps

    Returns:
        Tuple: Trained weights and bias
    """
    m, n = X.shape
    weights = np.array([0.1, 0.1])
    bias = 0.0  # Corrected: Bias should be a scalar

    for i in range(iters):
        # Forward pass
        y_predicted = np.dot(X, weights) + bias
        error = y - y_predicted

        # Backward pass
        weights_gradient = -2/m * np.dot(X.T, error)
        bias_gradient = -2/m * error 

        weights -= lr * weights_gradient
        bias -= lr * bias_gradient

        if verbose:
            print(f"Iteration {i+1}:")
            print("Weights gradient =", weights_gradient)
            print("Bias gradient =", bias_gradient)
            print("Updated weights =", weights)
            print("Updated bias =", bias)

    return weights, bias

测试训练好的模型。

Code
# Train the model
weights, bias = train(X, y, learning_rate, iters=2000)
print("Trained weights:", weights)
print("Trained bias:", bias)

# Test predictions
test_X = np.array([[1.5, 1], [2.4, 2], [3.5, 3]])
test_y = np.dot(test_X, weights) + bias
print("Predictions:", test_y)
Trained weights: [0.93453357 0.83998906]
Trained bias: [-0.14178921  0.27714103  0.10916541]
Predictions: [2.10000021 4.19999973 5.9000001 ]

可以看出几乎与实际值没有差异。如果减少重复次数会怎样呢?

Code
# Train the model
weights, bias = train(X, y, learning_rate, iters=50)
print("Trained weights:", weights)
print("Trained bias:", bias)

# Test predictions
test_X = np.array([[1.5, 1], [2.4, 2], [3.5, 3]])
test_y = np.dot(test_X, weights) + bias
print("Predictions:", test_y)
Trained weights: [0.95069505 0.82053576]
Trained bias: [-1.23073214e-04  1.51665327e-01  1.39109392e-01]
Predictions: [2.24645526 4.07440496 5.92814933]

在50次迭代的情况下,可以观察到预测值和实际值之间的误差相当大。另一个需要注意的是学习率。为什么要在梯度上乘以一个非常小的值呢?让我们只进行一次迭代并输出计算出的参数值。

Code
num_iters = 1
weights, bias = train(X, y, learning_rate, iters=num_iters, verbose=True)
Iteration 1:
Weights gradient = [-20.116      -16.74666667]
Bias gradient = [-1.23333333 -2.50666667 -3.5       ]
Updated weights = [0.30116    0.26746667]
Updated bias = [0.01233333 0.02506667 0.035     ]

通过1000次迭代训练获得的训练权重和偏置值与梯度值进行比较,可以发现梯度值非常大。如果不通过学习率将梯度值大大减小,参数将无法减少误差,并会继续振荡。建议您尝试使用较大的学习率进行测试。

这个“线性逼近器”与神经网络逼近器有什么不同呢?区别只有一点:在线性计算之后,将其传递给激活函数(activation function)。用公式表示如下:

\[ \boldsymbol y = f_{active} ( \boldsymbol x \boldsymbol W + \boldsymbol b ) \]

实现起来也很简单。激活函数有多种类型,如果使用tanh函数,则代码如下所示。

Code
y_predicted = np.tanh(np.dot(X, weights) + bias)

神经网络通常用层(layer)的概念来表示每个阶段的线性变换和激活函数的应用。因此,如下所示,分两个步骤实现更符合层的表达,故而更为优选。

Code
out_1 = np.dot(X, weights) + bias  # First layer
y_predicted = np.tanh(out_1)       # Second layer (activation)

大脑皮层可塑性理论 (Cortical Plasticity Theory)

Mountcastle 的大脑皮层可塑性理论

Vernon Mountcastle 是 20 世纪后半叶对神经科学领域做出重大贡献的科学家,尤其是在大脑皮层的功能组织研究方面。Mountcastle 的主要成就之一是 柱状结构(Columnar Organization) 的发现。他发现大脑皮层以垂直的柱(柱状)形式组织,并且同一柱内的神经元会对相似的刺激作出反应。

Mountcastle 的理论为理解大脑皮层的可塑性提供了重要的基础。根据他的理论:

  • 作为功能单元的柱: 大脑皮层由基本的功能单元——柱构成。每个柱包含对特定感觉方式(modality)或特定运动模式有反应的神经元群。
  • 柱的可塑性: 柱的结构和功能可以根据经验而改变。对特定刺激的重复暴露可以使处理该刺激的柱增大,或增强其反应性。相反地,缺乏刺激可以使柱缩小或减弱其反应性。
  • 竞争性相互作用: 相邻的柱彼此之间进行竞争性的相互作用。一个柱活动增加可能会抑制其他柱的活动,并且这是经验导致皮层重组(cortical reorganization)的基础机制。例如,频繁使用特定的手指可以使负责该手指的皮层区域扩大,而负责其他手指的区域则可能相对缩小。

Mountcastle 的柱状结构和可塑性理论具有以下临床意义:

  • 脑损伤后的恢复: 脑卒中或创伤性脑损伤后功能恢复可以通过受损区域周围的皮层重组来实现。
  • 感觉丧失及康复: 视觉或听觉丧失后,负责该感觉的皮层区域可以用于处理其他感官(交叉模态可塑性, cross-modal plasticity)。
  • 学习和技术掌握: 学习新技术或通过重复训练提高特定功能可以通过诱导负责该功能的皮层柱的变化来实现。

与深度学习的联系

Mountcastle 的大脑皮层可塑性理论对深度学习,尤其是人工神经网络(Artificial Neural Networks, ANN)的结构和学习原理产生了许多启发。

  • 层次结构 (Hierarchical Structure): 大脑皮层的柱状结构类似于深度学习模型的层次结构。深度学习模型由多层(layer)组成,每层从输入数据中逐步提取更抽象的特征(feature)。这与大脑皮层通过柱对感官信息进行分级处理以执行复杂的认知功能的方式相似。
  • 权重调整 (Weight Adjustment): 深度学习模型在学习过程中调整连接强度(权重)以学习输入数据和输出之间的关系。这类似于 Mountcastle 提出的柱内神经元间连接强度的变化机制。就像根据经验对特定刺激的反应性增强或减弱一样,深度学习模型也通过学习数据来调整权重以提高性能。
  • 竞争学习 (Competitive Learning): 深度学习的一些模型,尤其是自组织映射(Self-Organizing Map, SOM),使用与 Mountcastle 的柱间竞争性相互作用相似的原理。SOM 基于输入数据的特征进行竞争性的学习,只有获胜神经元被激活并更新其周围神经元的权重。这类似于皮层中相邻柱通过互相抑制而竞争性地分担功能的方式。 蒙特卡斯特的大脑皮层可塑性理论不仅扩展了对大脑功能组织和学习机制的理解,还为深度学习模型的开发提供了重要的洞察。模仿大脑工作方式的深度学习模型正在人工智能领域的发展中发挥重要作用,并且预计未来神经科学与人工智能领域的互动将更加活跃。

1.5 深度神经网络

深度学习是一种通过堆叠多层神经网络进行学习的方法。由于层数较深,因此使用了“deep”这一术语。基本组成部分是线性变换层,也称为全连接层(Fully Connected Layer)或密集层(Dense Layer)。这些层以如下结构连接:

全连接层1 - 激活层1 - 全连接层2 - 激活层2 - …

激活层在神经网络中起着核心作用。如果只连续堆叠线性层,从数学上讲,它们等同于一个单一的线性变换。例如,两个线性层相连可以表示为:

\[ \boldsymbol y = (\boldsymbol X \boldsymbol W_1 + \boldsymbol b_1)\boldsymbol W_2 + \boldsymbol b_2 = \boldsymbol X(\boldsymbol W_1\boldsymbol W_2) + (\boldsymbol b_1\boldsymbol W_2 + \boldsymbol b_2) = \boldsymbol X\boldsymbol W + \boldsymbol b \]

这最终又是一个单独的线性变换。因此,堆叠多层的优势将消失。激活层打破了这种线性关系,使每一层可以独立学习。深度学习之所以强大,正是因为随着层数的增加,它可以学习到更复杂的模式。

1.5.1 深度神经网络的结构

image info

每一层的输出成为下一层的输入,依次计算。前向传播是一系列相对简单的运算。

在反向传播过程中,每层会计算两种类型的梯度:

  1. 权重的梯度:\(\frac{\partial E}{\partial \boldsymbol W}\) - 用于参数更新

  2. 输入的梯度:\(\frac{\partial E}{\partial \boldsymbol x}\) - 传回前一层

这两种梯度需要分别独立存储和管理。权重梯度由优化器用于参数更新,输入梯度在反向传播过程中用于前一层的学习。

1.5.2 神经网络的实现

为了实现神经网络的基本结构,我们采用基于层的设计。首先定义所有层将继承的基础类。

Code
import numpy as np

class BaseLayer():
    # __init__ can be omitted as it implicitly inherits from 'object' in Python 3

    def forward(self, x):
        raise NotImplementedError  # Should be implemented in derived classes

    def backward(self, output_error, lr):
        raise NotImplementedError  # Should be implemented in derived classes

    def print_params(self):
        # Default implementation (optional).  Child classes should override.
        print("Layer parameters (Not implemented in BaseLayer)")
        # raise NotImplementedError # Or keep NotImplementedError

BaseLayer 定义了前向传播(forward)和反向传播(backward)运算的接口。每个层通过实现此接口来执行其独特的运算。以下是全连接层的实现。

Code

class FCLayer(BaseLayer):
    def __init__(self, in_size, out_size):
        # super().__init__()  # No need to call super() for object inheritance
        self.in_size = in_size
        self.out_size = out_size

        # He initialization (weights)
        self.weights = np.random.randn(in_size, out_size) * np.sqrt(2.0 / in_size)
        # Bias initialization (zeros)
        self.bias = np.zeros(out_size)  # or np.zeros((out_size,))

    def forward(self, x):
        self.in_x = x  # Store input for use in backward pass
        return np.dot(x, self.weights) + self.bias

    def backward(self, out_error, lr):
        # Matrix multiplication order: out_error (batch_size, out_size), self.weights (in_size, out_size)
        in_x_gradient = np.dot(out_error, self.weights.T)
        weight_gradient = np.dot(self.in_x.T, out_error)
        bias_gradient = np.sum(out_error, axis=0)  # Sum over all samples (rows)

        self.weights -= lr * weight_gradient
        self.bias -= lr * bias_gradient
        return in_x_gradient

    def print_params(self):
        print("Weights:\n", self.weights)
        print("Bias:\n", self.bias)

全连接层使用权重和偏置来转换输入。权重初始化采用了He初始化方法1。这是2015年由He等人提出的方法,与ReLU激活函数一起使用时特别有效。

Code
import numpy as np

def relu(x):
    return np.maximum(x, 0)

def relu_deriv(x):
    return np.array(x > 0, dtype=np.float32)  # or dtype=int

def leaky_relu(x):
    return np.maximum(0.01 * x, x)

def leaky_relu_deriv(x):
    dx = np.ones_like(x)
    dx[x < 0] = 0.01
    return dx

def tanh(x):
    return np.tanh(x)

def tanh_deriv(x):
    return 1 - np.tanh(x)**2

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_deriv(x):  # Numerically stable version
    s = sigmoid(x)
    return s * (1 - s)

ReLU自2011年首次提出后,已成为深度学习的标准激活函数。它不仅有效地解决了梯度消失问题,而且计算简单。为了反向传播计算,我们声明激活函数的导数函数relu_deriv()。ReLU是一个当输入值小于0时返回0,大于0时返回其自身值的函数。因此,其导数函数对于小于等于0的值返回0,大于0的值返回1。在这里,我们使用Tanh作为激活函数。以下是激活层。

Code
class ActivationLayer(BaseLayer):
    def __init__(self, activation, activation_deriv):
        super().__init__()
        self.activation = activation
        self.activation_deriv = activation_deriv
    
    def forward(self, input_data):
        self.input = input_data
        return self.activation(input_data)
    
    def backward(self, output_error, lr):
        return self.activation_deriv(self.input) * output_error

激活层通过添加非线性使神经网络能够逼近复杂的函数。在反向传播过程中,根据链式法则将导数与输出误差相乘。

Code
def mse(y_label, y_pred):
    return (np.mean(np.power(y_label - y_pred,2)))

def mse_deriv(y_label, y_pred):
    return (2/y_label.size) * (y_pred - y_label) 

均方误差(MSE)是回归问题中广泛使用的损失函数。它计算预测值和实际值之差的平方的平均值。 通过整合这些组件,可以实现整个神经网络。

Code
class Network:
    def __init__(self):
        self.layers = []
        self.loss = None
        self.loss_deriv = None

    def add_layer(self, layer):
        self.layers.append(layer)

    def set_loss(self, loss, loss_deriv):
        self.loss = loss
        self.loss_deriv = loss_deriv

    def _forward_pass(self, x):
        output = x
        for layer in self.layers:
            output = layer.forward(output)
        return output

    def predict(self, inputs):
        predictions = []
        for x in inputs:
            output = self._forward_pass(x)
            predictions.append(output)
        return predictions
    
    def train(self, x_train, y_train, epochs, lr):
        for epoch in range(epochs):
            epoch_loss = 0
            
            for x, y in zip(x_train, y_train):
                # Forward pass
                output = self._forward_pass(x)
                
                # Calculate loss
                epoch_loss += self.loss(y, output)

                # Backward pass
                error = self.loss_deriv(y, output)
                for layer in reversed(self.layers):
                    error = layer.backward(error, lr)

            # Calculate average loss for the epoch
            avg_loss = epoch_loss / len(x_train)
            if epoch == epochs -1:
                print(f'epoch {epoch+1}/{epochs}   error={avg_loss:.6f}')

1.5.3 神经网络训练

神经网络的训练是通过反复进行前向传播和反向传播来优化权重的过程。首先,我们将通过 XOR 问题来观察神经网络的学习过程。

Code
# XOR training data
x_train = np.array([[[0,0]], [[0,1]], [[1,0]], [[1,1]]])
y_train = np.array([[[0]], [[1]], [[1]], [[0]]])

# Network architecture
net = Network()
net.add_layer(FCLayer(2, 30))                     # Input layer -> Hidden layer
net.add_layer(ActivationLayer(tanh, tanh_deriv))  # tanh activation
net.add_layer(FCLayer(30, 1))                     # Hidden layer -> Output layer
net.add_layer(ActivationLayer(tanh, tanh_deriv))  # tanh activation


# Training settings and execution
net.set_loss(mse, mse_deriv)
net.train(x_train, y_train, epochs=2000, lr=5e-3)

# Prediction test
out = net.predict(x_train)
print(f"predict={out}")
epoch 2000/2000   error=0.002251
predict=[array([[0.00471695]]), array([[0.93254742]]), array([[0.93421712]]), array([[0.0080288]])]

激活函数使用了tanh()进行训练。可以验证,神经网络已经训练到能够为XOR输出逻辑产生相似的值。现在,我们将通过MNIST手写数字分类问题来探讨针对实际数据集的神经网络学习。

以下是 MNIST 手写数字示例。首先导入使用 PyTorch 所需的库。

Code
import torch
import torchvision
import torch.nn.functional as F
from torchvision.datasets import MNIST
import matplotlib.pyplot as plt
import torchvision.transforms as transforms
from torch.utils.data import random_split

dataset = MNIST(root = 'data/', download = True)
print(len(dataset))
60000
Code
image, label = dataset[10]
plt.imshow(image, cmap = 'gray')
print('Label:', label)
Label: 3

手写数字的标签是整数形式,不是分类形式。我们将创建一个类似于keras中to_category的函数来使用。

Code
def to_categorical(y, num_classes):
    """ 1-hot encodes a tensor """
    return np.eye(num_classes, dtype='uint8')[y]
Code
import numpy as np

## MNIST dataset(images and labels)
mnist_dataset = MNIST(root = 'data/', train = True, transform = transforms.ToTensor())
train_data, test_data = random_split(mnist_dataset , [50000, 10000])

train_loader = torch.utils.data.DataLoader(train_data, batch_size=2000, shuffle=True, num_workers=1)
test_loader = torch.utils.data.DataLoader(train_data, batch_size=10, shuffle=True, num_workers=1)

train_images, train_labels = next(iter(train_loader)) # 한번의 배치만 가져온다.
x_train = train_images.reshape(train_images.shape[0], 1, 28*28)
y_train = to_categorical(train_labels, 10)


print(x_train.shape)
print(y_train.shape)
torch.Size([2000, 1, 784])
(2000, 10)

加载数据后,将其分为训练集和测试集。使用了PyTorch的DataLoader来加载数据。在这里,为了只使用2000个训练数据,将batch_size设置为2000。通过next(iter(train_loader))仅获取一个批次的数据,并将数据形状从(1, 28, 28)更改为(1, 784),这称为扁平化。对图像和标签数据分别进行处理后,检查其维度。

Code

# # Network
net = Network()
net.add_layer(FCLayer(28*28, 100))                # input_shape=(1, 28*28)    ;   output_shape=(1, 100)
net.add_layer(ActivationLayer(tanh, tanh_deriv))
net.add_layer(FCLayer(100, 50))                   # input_shape=(1, 100)      ;   output_shape=(1, 50)
net.add_layer(ActivationLayer(tanh, tanh_deriv))
net.add_layer(FCLayer(50, 10))                    # input_shape=(1, 50)       ;   output_shape=(1, 10)
net.add_layer(ActivationLayer(tanh, tanh_deriv))

net.set_loss(mse, mse_deriv)
net.train(x_train[0:1000], y_train[0:1000], epochs=35, lr=0.1)
/tmp/ipykernel_936812/3322560381.py:14: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword
  return np.dot(x, self.weights) + self.bias
/tmp/ipykernel_936812/3322560381.py:19: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword
  weight_gradient = np.dot(self.in_x.T, out_error)
epoch 35/35   error=0.002069
Code
# Make predictions with the trained model.
test_images, test_labels = next(iter(test_loader))
x_test = test_images.reshape(test_images.shape[0], 1, 28*28)
y_test = to_categorical(test_labels, 10)

print(len(x_test))

# Use only the first 2 samples for prediction.
out = net.predict(x_test[:2])  # Corrected slicing: use [:2] for the first two samples
print("\n")
print("Predicted values : ")
print(out, end="\n")
print("True values : ")
print(y_test[:2])  # Corrected slicing: use [:2] to match the prediction
10


Predicted values : 
[array([[-0.02857555,  0.04630796,  0.01640415,  0.34762487,  0.01307466,
        -0.14719773,  0.01654099,  0.12845884,  0.74751837,  0.05102324]]), array([[ 0.01248236,  0.00248117,  0.70203826,  0.12074454,  0.088309  ,
        -0.24138211, -0.04961493,  0.20394738,  0.28894724,  0.07850696]])]
True values : 
[[0 0 0 1 0 0 0 0 0 0]
 [0 0 0 1 0 0 0 0 0 0]]
/tmp/ipykernel_936812/3322560381.py:14: DeprecationWarning: __array__ implementation doesn't accept a copy keyword, so passing copy=False failed. __array__ must implement 'dtype' and 'copy' keyword arguments. To learn more, see the migration guide https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword
  return np.dot(x, self.weights) + self.bias

到目前为止,我们已经亲自实现了最基本的神经网络形式,即通过层层叠加线性变换和非线性激活函数来执行预测的“函数逼近器(function approximator)”。从简单的XOR问题到MNIST手写数字分类,我们探讨了神经网络如何通过数据学习并识别复杂模式的核心原理。虽然像PyTorch、TensorFlow这样的深度学习框架使这一过程更加高效和便捷,但其基本工作方式与我们亲自实现的代码相差不大。

本书不仅止步于此,还将从1943年的McCulloch-Pitts神经元开始,追踪到2025年最新的多模态架构,深入探讨深度学习技术发展的DNA。我们将像研究生物进化过程一样,深入了解每项技术为何出现、试图解决哪些根本问题以及与先前技术之间的联系。

第二章将涵盖理解深度学习所必需的数学基础。从线性代数、微积分、概率和统计的核心概念出发,简洁地整理这些内容,以帮助读者更好地理解后续的内容。如果你缺乏数学背景知识,或者对理论不太感兴趣而更关注实际实现,可以直接跳到第三章。从第三章开始,我们将使用PyTorch和Hugging Face库来亲手实现和实验最新的深度学习模型,培养实战感觉。然而,为了深入理解和长期发展深度学习,建立坚实的数学基础是非常重要的。

每章末尾都会通过练习题来检验读者的理解,并为更进一步的探究提供平台。我们希望这不仅能帮助你找到答案,还能在解决问题的过程中更加深刻地掌握深度学习原理,扩展创造性思维。

练习题

1. 基础问题

  1. 数学上解释感知器无法解决XOR问题的原因。
  2. 如果在上述XOR示例中使用relu、relu_deriv等其他激活函数,说明结果的变化。
  3. 结合例子说明反向传播算法中链式法则如何应用。

2. 应用问题

  1. 分析在房价预测模型中使用Swish激活函数替代ReLU的优缺点。
  2. 从函数空间的角度解释为什么三层神经网络的表达能力优于两层神经网络。

3. 深化问题

  1. 数学推导证明ResNet中的跳过连接如何解决梯度消失问题。
  2. 分析Transformer架构中的注意力机制为何适合序列建模。

答案

1. 基础问题答案

  1. XOR 问题: 线性分类器的局限 → 需要非线性决策边界

    import numpy as np
    XOR_input = np.array([[0,0],[0,1],[1,0],[1,1]])
    # 仅通过线性组合无法区分 0 和 1
  2. ReLU 训练问题: ReLU: 对学习率敏感,并且可能出现“死 ReLU”问题(神经元失活导致无法训练)。其他激活函数(如 Leaky ReLU、ELU、Swish 等)可以缓解 Dead ReLU 问题,比 ReLU 更稳定地解决 XOR 问题。Sigmoid 可能因梯度消失问题而难以学习。Tanh 虽然比 ReLU 更稳定,但在深层网络中可能会出现梯度消失问题。

  3. 反向传播链式法则:
    \(\frac{\partial L}{\partial W} = \frac{\partial L}{\partial y}\cdot\frac{\partial y}{\partial W}\)

2. 应用问题答案

  1. Swish 函数优点:
    • 缓解 ReLU 的 dying neuron 问题
    • 可微的曲线提高了学习稳定性
  2. 三层神经网络的优点:
    • 希尔伯特第13问题: 三个变量的连续函数无法用两层网络表示
    • 科尔莫戈罗夫-阿诺德定理: 通过三层可以近似任意连续函数

3. 深化问题答案

  1. ResNet 跳跃连接:
    \(H(x) = F(x) + x\)\(\frac{\partial L}{\partial x} = \frac{\partial L}{\partial H} \cdot (F'(x) + 1)\)

  2. Transformer 的优点:

    • 可并行处理(克服 RNN 的顺序处理限制)
    • 捕获长距离依赖关系(通过注意力权重学习重要性)

必要参考材料

  1. Deep Learning (Goodfellow, Bengio, Courville, 2016)

  2. 通过sigmoid函数的叠加进行逼近 (Cybenko, 1989)

  3. 多层前馈网络是通用逼近器 (Hornik, Stinchcombe, & White, 1989)

  4. 理解训练深层前馈神经网络的难度 (Glorot & Bengio, 2010)

  5. 深入研究整流器:在ImageNet分类中超越人类水平的性能 (He et al., 2015)

  6. 神经网络和深度学习 (Michael Nielsen, 在线书籍)

  7. 你需要的矩阵微积分知识进行深度学习 (Parr & Howard, 2018)