在过去几年里,深度学习给世界带来惊喜,推动了计算机视觉、自然语言处理、自动语音识别、强化学习和统计建模等领域的快速发展。
感知机
给定输入x,权重w,和偏移b,感知机输出:

这是一个二分类的问题:输出1/-1
- 回归问题:输出一个实数;而二分类问题输出一个类别
- Softmax回归:多分类问题,输出概率
感知机的训练算法

初始化:将权重向量和偏置都初始化为0。
循环:算法会不断循环遍历训练数据,直到所有的样本都被正确的分类。
判断错误分类:
- 这里的是真实的标签(为+1或-1)
- 是模型对样本的预测输出值
- 含义:如果和预测值的符号相反(乘积为负),或者预测值为0,说明分类错误。
更新规则:
- 含义:当发现一个错误样本时,利用该样本的信息来调整权重。
- 如果是正样本()被误判为负,就加上(让向的方向靠近)
- 如果是负样本()被误判为正,就减去(让远离)
更新规则的数学逻辑:
感知机的预测完全取决于内积 的正负(为了简化,我们先忽略偏置 )。
当 (正样本)被误判:此时目前的 。我们希望这个值 变大 (最好变成正数)。如果我们更新 ,那么新的内积为:
因为 永远是正数,所以更新后的内积 一定会增加 。这让该样本在下次预测时更趋向于被判定为正。
当 (负样本)被误判:此时目前的 。我们希望这个值变小(最好变成负数)。如果我们更新 ,新的内积为:
内积一定会减小,从而让该样本更趋向于被判定为负。
优化视角解释
以上的感知机训练算法,等价于使用批量大小为1的梯度下降,并使用如下的损失函数:
理解这个公式:
- 如果分类正确:,那么 ,经过 后损失为 0 。此时梯度为 0,参数 不更新 。
- 如果分类错误: ,那么 ,损失为正。
更新规则:当分类错误时,损失函数对 求导(梯度)是 。
代入 SGD 更新公式(假设学习率为 1):
感知机收敛定理
- 数据半径 :假设所有的输入数据 都分布在一个半径为 的圆(或高维球体)内。即数据的大小(范数)是有界的。
- 余量 (Margin) :这代表了正负两类样本之间“最宽”的那条缝隙。
感知机的步数上限:
感知机的问题
感知机不能拟合XOR函数,它只能产生线性分割面。导致了第一次AI的寒冬。
XOR问题:无法使用一条直线,将两种颜色的球完全分开。

多层感知机
上面我们提到单层感知机模型无法解决非线性问题,而引入隐藏层可以解决这一问题。

在这个问题中,隐藏层相当于两条辅助线,这就好比我们在做特征工程,我们将原始复杂的分类任务拆解成了两个简单的子任务:
- 蓝色特征:
- 网络学会的第一条规则是区分左右(即x轴的符号)
- 左边为 + ,右边为 - ,这对应图中的蓝色竖线
- 黄色特征:
- 网络学会的第二条规则是区分上下(即y轴的符号)
- 上边为 + , 下边为 - ,这对应了图中的黄色横线。
现在,我们有了两个新的特征(蓝色和黄色)。神经网络的输出层(Output Layer)所做的工作,就是将这两个特征进行 非线性组合 (在这里可以理解为乘法或异或逻辑):
对应到神经网络架构,这就是一个最简单的多层感知机(MLP):
- 输入层(x, y):也就是原始数据的坐标。
- 隐藏层:这里有两个神经元:
- 一个负责学习蓝色规则
- 一个负责学习黄色规则
- 输出层:接收隐藏层信号,完整最终的逻辑判断
单隐藏层

- 隐藏层的大小是一个超参数。
输入输出的大小都是由数据和类别决定的。
单分类
核心组件
单分类指输出为一个标量的情况。
输入层:
- :代表输入是一个n维向量。
隐藏层:
- :这是第一层的权重矩阵。它将n维输入映射到m维空间。
- :偏置项,对应隐藏层的m个神经元。
输出层:
- :单输出的情况,是一个一维向量。
数学表达式
- 隐藏层计算:
- 这里先进行线性变换
- (激活函数):按元素做运算的函数。后续细讲。
- 输出层计算:
- 这是将隐藏层提取到的特征h进行加权求和,得到最终的预测值。
激活函数 Activation function
激活函数通过计算加权和并加上偏置来确定神经元是否应该被激活,它们将输入信号转换为输出的可微运算。大多数激活函数都是非线性的。 由于激活函数是深度学习的基础。
为什么需要激活函数
简单来说,如果没有激活函数,再深的网络也只是一层。
假设我们有一个两层的神经网络,但没有激活函数。
- 第一层输出:
- 第二层输出:
我们将第一层代入第二层:
如果我们令 且 ,那么公式就变成了:
结论: 无论你堆叠多少层,只要是线性的,它们最终都可以合并成一个单一的线性层。这意味着你的“深度”网络在表达能力上和最简单的感知机没有任何区别,无法处理复杂的任务。
激活函数的作用:它像一个”开关”或”过滤器”,决定了神经元的哪些信息应该传递到下一层。它引入了非线性,让网络能够拟合出复杂的边界。
- 万能近似定理:只要神经网络拥有至少一个包含足够多神经元的隐藏层,并配合非线性激活函数,它就可以以任意精度拟合闭区间内的连续函数。
常见的激活函数
ReLU函数
修正线性单元(Rectified linear unit, ReLU),这是最受欢迎的激活函数。

ReLU导数的图像:

注意:当输入值精确等于0时,ReLU函数不可导。在此时,我们默认使用左侧的导数。我们可以忽略这种情况,因为输入可能永远都不会是0。 这里引用一句古老的谚语,“如果微妙的边界条件很重要,我们很可能是在研究数学而非工程”, 这个观点正好适用于这里。
优点:
- 实现简单。
- 无需计算指数,计算速度快。
- 求导表现好
sigmoid函数
sigmoid函数将输入变换为区间(0, 1)上的输出。

sigmoid函数的导数:

针对梯度下降的学习时,sigmoid是一个自然的选择,因为他是一个平滑的、可微的阈值单元近似。然而,sigmoid在隐藏层中已经较少使用, 它在大部分时候被更简单、更容易训练的ReLU所取代。
tanh函数
与sigmoid函数类似, tanh(双曲正切)函数也能将其输入压缩转换到区间(-1, 1)上。 tanh函数的公式如下:

tanh函数的导数:

tanh函数和sigmoid函数计算都比较复杂,在深度学习中算力是宝贵的资源,所以一般情况下都选择使用ReLU函数。
多类分类

输入层:
隐藏层:
输出层:
这里不再是一个向量,而是一个矩阵。它把m维的隐藏特征映射到k个类别的评分上。
计算公式:
- 隐藏层激活:
- 输出层线性变换:
- 最终输出:
多类分类与softmax回归没有本质区别,只是增加了隐藏层。
多隐藏层

计算公式:
针对多层感知机,我们需要设计其中的超参数:
- 隐藏层数
- 每层隐藏层的大小
多层感知机的从零开始实现
我们使用softmax中相同的数据集进行训练——Fashion-MNIST。
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # 直接使用d2l保中现成的导入函数
# 让我们现在可以先把注意力放在模型训练上
初始化模型参数
我们将要实现一个单隐藏层的多层感知机,它包含256个隐藏单元。
隐藏层的数量和宽度都是超参数。通常我们选择2的幂次作为层的宽度,因为内存硬件中分配和寻址方式,这么做可以在计算上更高效。
我们用几个张量来表示我们的参数。
每一层都要记录一个权重矩阵和偏置向量。
# 定义网络架构的维度:输入特征784、输出类别10、隐藏层单元 256
num_inputs, num_outputs, num_hiddens = 784, 10, 256
# 初始化第一层的权重矩阵 W1
# 形状为(784, 256)
# 使用均值为0、标准差为1的随机正态分布,并乘以0.01。等价于(0, 0.01)的正太分布
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
# 初始化第一层的偏置项b1
# 形状为隐藏层维度,初始值全部设为0
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
# 初始化第二层(输出层)的权重矩阵W2
W2 = nn.Parameter(torch.randn(
num_hiddens, num_outputs, requires_grad=True) * 0.01)
# 初始化第三层的偏置项b2
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
# 将所有需要反向传播更新的参数封装进一个列表
params = [W1, b1, W2, b2]
激活函数
def relu(X):
# 创建一个与输入张量X形状(shape)和类型完全相同、但元素全部为0的新张量
# 这一步是为了后续进行逐元素的对比
a = torch.zeros_like(X)
# 比较X和a中的每一个对应位置的数值,返回其中较大的那个
# 数学表达式:ReLU(x) = max(0, x)
return torch.max(X, a)
模型
因为我们忽略了空间结构,所以我们使用 reshape将每个二维图像转化为一个长度为 num_inputs的向量。只需要几行代码就可以实现模型:
def net(X):
# 将输入X展平为二维向量
X = X.reshape((-1, num_inputs))
# 计算隐藏层输出:
# 1. X @ W1: 输入与第一层权重进行矩阵乘法
# 2. + b1: 加上第一层的偏置项
# 3. relu(...): 对计算结果应用ReLU激活函数,引入非线性
H = relu(X @ W1 + b1) # 这里“@”代表矩阵乘法
# 计算输出层的预测值:
return (H @ W2 + b2)
损失函数
这里的损失函数与softmax中的完全一样,我们直接使用高级API来简化:
loss = nn.CrossEntropyLoss(reduction='none')
训练
多层感知机的训练过程与softmax回归的训练过程完全相同:
def updater(batch_size):
return d2l.sgd(params, lr, batch_size)
num_epochs, lr = 10, 0.1
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)

多层感知机的简洁实现
首先导入包:
import torch
from torch import nn
from d2l import torch as d2l
模型
与softmax回归的简洁实现相比,唯一的区别是我们添加了两个全连接层(之前我们只添加了一个全连接层)。
# 使用Sequential 容器按顺序堆叠各个层
net = nn.Sequential(
# 将输入的四维图像张量,展平为二维张量
nn.Flatten(),
# 定义第一个全连接层: 输入784维,输出256维
nn.Linear(784, 256),
# 添加ReLU激活函数,为模型引入非线性,必须放在两个全连接层之间
nn.ReLU(),
# 定义第二个全连接层(输出层)
nn.Linear(256, 10))
# 定义一个初始化权重的函数
def init_weights(m):
# 检查当前层m是否为全连接层(nn.Linear)
if type(m) == nn.Linear:
# 使用正太分布(0, 0.01)初始化该层的权重矩阵
nn.init.normal_(m.weight, std=0.01)
# 将init_weights 函数递归地应用到net的每一个子模型(即Sequential里的每一层)上
# 对于不是nn.Linear 的层,if判断不成立,直接跳过
net.apply(init_weights);
训练过程的实现与我们实现softmax回归时完全相同, 这种模块化设计使我们能够将与模型架构有关的内容独立出来。
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

模型选择
训练误差和泛化误差
- 训练误差:模型在训练数据上的误差
- 泛化误差:模型在新数据上的误差
- 例子:根据模考成绩来预测未来考试分数
- 在过去的考试中表现很好(训练误差)不代表未来考试一定会好
- 学生A 通过背书在模考中拿到很好的成绩
- 学生B 知道答案后面的原因
验证数据集和测试数据集
-
验证数据集:一个用来评估模型好坏的数据集
- 本质作用:帮助模型做选择 。它虽然不直接参与梯度下降(即不直接更新权重),但它间接参与了训练过程。
- 如何影响:我们根据验证集上的表现来调整超参数(比如学习率、层数、Dropout比例)或者决定何时停止训练(Early Stopping)。
- 潜台词:模型其实是“看”过验证集的,我们的训练策略针对它进行了优化。
-
测试数据集:只用一次的数据集。
- 本质作用 : 无偏估计真实能力 。它是用来模拟模型上线后在真实世界中会遇到的未知数据。
- 如何影响 : 完全不影响 。它绝不能参与任何参数更新或模型选择的决策。
- 潜台词 :模型在看到测试集之前,必须已经完全定型(冻结参数)。
如果把训练模型比作学生备考:
- 训练集 :是 课后作业 。学生(模型)通过做题来学习知识点,做错了就改(更新参数)。
- 验证集 :是 模拟考试 。
- 考完后,老师会根据成绩告诉你:“你这次复习策略不对,要调整一下重点”(调整超参数)。
- 你可以考很多次模拟考,不断调整学习方法,直到模拟考成绩满意为止。
- 测试集 :是 真正的高考(或未来的考试) 。
- PPT里提到的“只用一次”就是这个意思。
- 你不能考了一半觉得分低,就要求老师把试卷拿回去让你重新复习再考一遍。它的结果就是你的最终能力体现。
K-则交叉验证
在没有足够多的数据时使用。
算法:
- 将训练数据分割成K块
- For i = 1, …, K
- 使用第i块作为验证数据集,其余的作为训练数据集
- 报告K个验证集误差的平均
常用:K = 5或10