人工智能(AI)全体系学习——系列二
人工智能(AI)全体系学习——系列二
文章目录
本专栏从机器学习基础出发,逐步深入到深度学习、Transformer、大模型、生成式AI、强化学习及AI Agent等前沿方向,构建完整的人工智能算法体系,并结合工程实践与实际应用,帮助读者形成系统化认知。
系列二:深度学习核心算法(神经网络体系)
👉 核心目标:进入“AI核心能力区”
第6章 神经网络基础
6.1 感知机(Perceptron)
感知机是最早的神经网络模型之一,也是理解整个深度学习体系的起点。它本质上是一个线性分类模型,用来解决最基础的“二分类问题”,例如判断一个样本属于哪一类。
其核心形式可以表示为:
y = sign ( w T x + b ) y = \operatorname{sign}(w^T x + b) y=sign(wTx+b)
其中,x表示输入特征向量,w 是权重,b 是偏置项。整个计算过程可以理解为:先对输入进行加权求和,再根据结果的正负来决定输出类别。
从结构上看,感知机做的事情非常直接。它在特征空间中构造一个超平面,将数据分为两部分。如果是二维数据,这个超平面就是一条直线;如果是三维数据,就是一个平面;在更高维空间中,则是一个高维分界面。模型训练的目标,就是不断调整这条“分界线”的位置,使其能够尽可能正确地区分不同类别的数据。
在训练过程中,如果模型对某个样本分类错误,就会根据该样本来更新参数。更新规则可以写为:
w = w + η y x b = b + η y w = w + \eta y x b = b + \eta y w=w+ηyxb=b+ηy
这里的 η是学习率,用来控制每次更新的幅度。这个更新规则的含义是:如果一个样本被分错了,就朝着“让它分对”的方向去调整参数。例如,一个本该是正类的样本被判成负类,那么模型就会增加该样本特征的权重,使它下次更容易被划到正类一侧。
如果从更直观的角度来看,感知机可以理解为一个“打分决策系统”。每一个输入特征都会乘以一个权重,然后全部加起来得到一个总分。这个总分如果大于零,就判为一类;小于零,就判为另一类。在实际应用中,这种形式可以对应很多简单决策问题。例如,在一个基础的风险评估中,可以把用户的收入、消费频率、历史记录等作为输入,通过加权得到一个风险分数,再根据阈值判断是否存在风险。
在工程实践中,感知机通常用于构建简单的基线模型。例如在使用 sklearn 时,可以直接调用:
from sklearn.linear_model import Perceptron
model = Perceptron()
model.fit(X, y)
这种模型训练速度很快,适合用于快速验证数据是否具备线性可分性。如果一个数据集连感知机都无法较好分类,往往说明问题本身具有明显的非线性特征,需要更复杂的模型。
需要注意的是,感知机的能力是有限的。它只能处理“线性可分”的问题,也就是说,数据必须能够用一条直线(或超平面)分开。例如经典的 XOR 问题(异或问题)就无法用感知机解决,因为它本质上是非线性可分的。这也是为什么后来需要引入多层结构和激活函数,使模型具备更强表达能力。
从整体来看,感知机虽然简单,但它奠定了几个非常重要的思想。第一,模型可以通过参数(权重)来表示决策边界;第二,可以通过样本误差来不断更新参数;第三,学习过程是一个逐步逼近最优解的过程。这些思想在后续的多层感知机、深度神经网络乃至大模型中,依然是核心基础。
可以把感知机看成神经网络的“最小单元”。虽然它本身解决的问题有限,但所有复杂模型,本质上都是在这个基础上的扩展和叠加。理解了感知机,就等于理解了神经网络“如何通过数据学习决策规则”的最原始形态。
6.2 多层感知机(MLP)
多层感知机通过引入隐藏层,将多个线性变换叠加,并结合非线性函数,使模型具备更强表达能力,其形式为:
y = f ( W 2 ⋅ f ( W 1 x + b 1 ) + b 2 ) y = f(W_2 \cdot f(W_1 x + b_1) + b_2) y=f(W2⋅f(W1x+b1)+b2)
这一结构使模型能够逼近任意连续函数,从而突破线性模型的限制。在实际任务中,例如用户行为预测或健康评分系统,可以将多维特征输入网络,第一层提取基础模式,后续层逐步组合特征,最终输出预测结果。
在工程实践中,MLP通常通过深度学习框架实现,例如在 PyTorch 中:
model = torch.nn.Sequential(
torch.nn.Linear(10, 64),
torch.nn.ReLU(),
torch.nn.Linear(64, 1)
)
这种结构广泛用于结构化数据建模,例如推荐系统中的评分预测、用户画像分析以及风险评估模型。
6.3 激活函数(ReLU / Sigmoid / Tanh)
在前面介绍多层感知机时,已经知道神经网络本质上是在做一层一层的线性变换。假设一个输入向量为 x,经过第一层线性计算后得到结果,再经过第二层、第三层继续计算,如果每一层都只是单纯地“乘权重再加偏置”,那么无论堆多少层,最后整体仍然只是一个线性变换。也就是说,网络看起来很深,实际上表达能力并没有真正增强。激活函数的作用,正是打破这种单纯的线性关系,让网络具备处理复杂问题的能力。
从数学上看,若某一层的输出写成:
z = Wx + b
如果后面不接任何非线性函数,而只是继续线性变换:
y = W 2 z + b 2 y = W_2 z + b_2 y=W2z+b2
把前式代入后可以得到:
y = W 2 ( W x + b ) + b 2 = ( W 2 W ) x + ( W 2 b + b 2 ) y = W_2 (Wx + b) + b_2 = (W_2 W)x + (W_2 b + b_2) y=W2(Wx+b)+b2=(W2W)x+(W2b+b2)
可以看出,它本质上还是一个新的线性函数。也就是说,多层线性层叠加起来,最后仍然等价于一层线性模型。这就解释了为什么神经网络中必须加入激活函数。激活函数的意义,不是简单地“多加一个步骤”,而是让模型真正拥有学习弯曲边界、复杂模式和非线性关系的能力。
可以把激活函数理解成神经网络里的“反应机制”。现实世界中的很多问题并不是简单的加和关系。例如,学生成绩是否优秀,不是说“努力程度加学习时间加作业完成度”简单相加就能完全决定,还可能存在阈值效应、组合效应和阶段效应。再比如,在图像识别中,某个像素点本身没有意义,但一片区域组合起来可能就代表眼睛、鼻子或者轮廓。如果网络永远只会做直线式判断,就很难学到这种复杂模式。激活函数的引入,等于告诉网络:不是所有信息都原样通过,有些信息要放大,有些信息要压缩,有些信息要直接截断,这样模型才更接近真实世界的复杂规律。
从直观角度理解,激活函数就像一道“筛子”或者“开关”。输入一个数值之后,它不会机械地原样输出,而是按照某种规则重新加工。例如,有的函数会把结果压到 0到 1之间,有的函数会把负数直接变成 0,有的函数会把输出拉回到 −1到 1 之间。这样做的结果是,神经网络不再只是做简单的加法,而是在每一层都加入了“判断”和“变形”的过程。正因为有了这些变化,网络才能逐步从原始数据中提取更高级、更抽象的特征。
在深度学习中,最常见的激活函数主要有三类,分别是 Sigmoid、Tanh 和 ReLU。它们都承担“引入非线性”的任务,但各自特点不同,适合的场景也不同。
Sigmoid 函数的表达式为:
y = W 2 ( W x + b ) + b 2 = ( W 2 W ) x + ( W 2 b + b 2 ) y = W_2 (Wx + b) + b_2 = (W_2 W)x + (W_2 b + b_2) y=W2(Wx+b)+b2=(W2W)x+(W2b+b2)
它的输出范围在 0 到 1 之间,因此非常适合表示“概率”或者“倾向程度”。如果把它放到二分类任务的输出层中,模型输出的值就可以理解为“属于某一类的概率”。例如在垃圾邮件识别中,如果模型最后输出 0.92,就可以理解为“这封邮件是垃圾邮件的概率约为 92%”;在疾病预测中,如果输出 0.18,可以理解为“患病风险较低”。
Sigmoid 的优点在于形式直观,尤其适合初学者理解神经网络中“概率输出”的概念。因为它能把任意实数映射到 0到 1 之间,所以常常用于模型最后一层,让输出具备明确的概率意义。但它也有明显缺点。首先,当输入值特别大或特别小时,函数曲线会变得很平,这意味着梯度很小,模型训练时参数更新速度会明显下降,这就是常说的梯度消失问题。其次,Sigmoid 的输出始终是正数,不是以 0 为中心,这会让后续层的训练效率受到影响。正因为如此,在现代深度网络的隐藏层中,Sigmoid 已经不再是主流选择,但在输出层,尤其是二分类输出层中,它仍然非常常见。
Tanh 函数的表达式为:
tanh ( x ) = e x − e − x e x + e − x \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+e−xex−e−x
它的输出范围在 −1到 1 之间。与 Sigmoid 相比,Tanh 的一个重要特点是它以 0 为中心,这使得模型训练时参数更新通常更自然一些。因为神经网络在学习过程中,希望某些特征可以表示“正方向的影响”,某些特征可以表示“负方向的影响”,而 Tanh 恰好提供了这种能力。比如在情感分析中,某个词可能对“正面情绪”有促进作用,也可能对“负面情绪”有加强作用,Tanh 的输出方式更容易表达这种双向变化。
如果从形状上看,Tanh 与 Sigmoid 很像,都是两端逐渐饱和、中间变化较快的曲线,因此它们也有类似的问题。当输入过大或过小时,梯度同样会变得很小,所以在很深的网络中也容易出现训练困难。不过在一些序列模型,尤其是早期的 RNN、LSTM 中,Tanh 仍然非常常见,因为它能够较好地控制状态值范围,防止数值过大。
ReLU 函数的表达式最简单:
f ( x ) = max ( 0 , x ) f(x) = \max(0, x) f(x)=max(0,x)
它的规则非常直接:如果输入是正数,就原样输出;如果输入是负数,就输出 0。正因为形式极其简单,ReLU 成为了现代深度学习中最常用的激活函数之一。很多初学者第一次看到它时会觉得“这也太简单了”,但恰恰是这种简单,带来了很高的计算效率和良好的训练效果。
ReLU 的优势主要体现在两个方面。第一,它在正区间内不会像 Sigmoid 和 Tanh 那样出现明显饱和,因此梯度更容易保持,深层网络训练起来更稳定。第二,它的计算非常快,只需要做一个取最大值的操作,不涉及指数运算,这对大规模模型训练尤其重要。正因为如此,卷积神经网络、深层全连接网络中,隐藏层几乎默认优先考虑 ReLU 或它的变体。
当然,ReLU 也并不是完美的。它会把所有负数都变成 0,这意味着如果某个神经元长期接收到负值输入,它可能就一直输出 0,后续梯度也传不回来,这种现象被称为“神经元死亡”。为了解决这一问题,后来又出现了一些改进版本,例如 Leaky ReLU、PReLU、ELU 等,它们的核心思想是:不要让负数部分彻底变成 0,而是保留一点很小的斜率。比如 Leaky ReLU 的表达式可以写成:
f ( x ) = { x , x > 0 α x , x ≤ 0 f(x) = \begin{cases} x, & x > 0 \\ \alpha x, & x \leq 0 \end{cases} f(x)={x,αx,x>0x≤0
其中 α 是一个很小的常数,例如 0.01。这样做之后,负数部分虽然仍然被压缩,但不会完全失去梯度,神经元也就不那么容易“死掉”。
理解激活函数时,最重要的不是死记公式,而是明白它在网络中的真实作用。可以从三个层面来把握。第一,激活函数让网络从“直线判断”变成“复杂判断”。如果没有它,神经网络再深也只是换了一种写法的线性模型。第二,激活函数改变了信息的流动方式,使某些信息被突出,某些信息被压制,这相当于给网络增加了筛选能力。第三,不同的激活函数会影响训练效率、收敛速度以及模型最终效果,因此它不仅关系到“能不能学”,还关系到“学得快不快、稳不稳”。
在实际工程中,激活函数的使用位置通常比较固定。隐藏层一般使用 ReLU 或它的变体,因为这类函数训练效率高,适合深层网络。输出层则根据任务不同进行选择。如果是二分类问题,常常使用 Sigmoid,因为输出值可以直接看作概率;如果是多分类问题,通常不会直接在最后一层使用普通激活函数,而是结合 Softmax 来输出各类别概率;如果是回归问题,例如预测房价、体重、温度等,一般最后一层不使用激活函数,直接输出实数。
例如,在一个用户流失预测任务中,输入特征可以是用户活跃天数、消费金额、登录频率、投诉次数等。模型前面几层隐藏层通常会使用 ReLU:
a ( l ) = max ( 0 , z ( l ) ) a^{(l)} = \max(0, z^{(l)}) a(l)=max(0,z(l))
这样网络可以高效提取复杂特征;最后输出层再接一个 Sigmoid:
y ^ = 1 1 + e − z \hat{y} = \frac{1}{1 + e^{-z}} y^=1+e−z1
这样输出的结果就可以直接解释为“用户流失的概率”。如果输出为 0.83,业务上就可以理解为“该用户存在较高流失风险,需要重点召回”。
再比如在图像识别中,卷积层提取到的特征通常也会接 ReLU。原因很简单,图像任务往往网络层数较深,如果继续使用 Sigmoid 或 Tanh,训练会很慢,甚至学不动。ReLU 在这里的价值不只是“一个函数”,而是它支撑了整个深度视觉模型的可训练性。很多经典模型,包括 AlexNet、VGG、ResNet 等,隐藏层几乎都大量使用 ReLU。
如果用代码实现,激活函数在主流框架中都非常容易调用。在 PyTorch 中,ReLU 可以写成:
import torch
import torch.nn as nn
layer = nn.ReLU()
x = torch.tensor([-2.0, -0.5, 0.0, 1.2, 3.5])
y = layer(x)
print(y)
Sigmoid 可以写成:
import torch
x = torch.tensor([-2.0, 0.0, 2.0])
y = torch.sigmoid(x)
print(y)
在 Keras 或 TensorFlow 中,也通常只需要在层中写上 activation="relu" 或 activation="sigmoid" 即可。例如:
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential([
Dense(64, activation='relu', input_shape=(10,)),
Dense(1, activation='sigmoid')
])
这段代码就表示:前面的隐藏层使用 ReLU,最后输出层使用 Sigmoid,适合一个二分类任务。也就是说,在实际使用中,开发者并不需要每次自己手动写函数公式,而是根据任务目标选择合适的激活函数,直接交给框架调用。
对于初学者来说,可以把激活函数记成一句很关键的话:神经网络真正“聪明”起来,不是因为层数多,而是因为每一层都在通过激活函数改变信息。没有激活函数,深度学习就很难成立;有了激活函数,网络才可以从简单输入中学出复杂规律。从理论上看,它解决的是模型表达能力问题;从工程上看,它决定的是训练难易程度和实际效果;从应用上看,它贯穿了图像识别、语音处理、推荐系统、风险预测、自然语言处理等几乎所有深度学习任务。
因此,学习激活函数时,不应只停留在“背公式”和“记名字”上,更要真正理解:它为什么存在、解决了什么问题、在不同任务里该如何选择。只有把这一层想清楚,后面学习反向传播、损失函数、优化器时,才能真正明白整个神经网络为什么能够训练起来。
第7章 反向传播与优化
7.1 Backpropagation
神经网络之所以能够“学习”,核心就在于它可以根据错误不断调整自身参数,而反向传播就是实现这一过程的关键机制。整个训练流程可以分为两步:先进行前向计算得到预测结果,再根据预测误差反向计算每一层参数的调整方向。
设模型的输出为 y^,真实值为 y,损失函数为 L,则反向传播的核心是计算损失函数对参数的梯度:
∂ L ∂ w = ∂ L ∂ y ^ ⋅ ∂ y ^ ∂ w \frac{\partial L}{\partial w} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial w} ∂w∂L=∂y^∂L⋅∂w∂y^
这个公式体现的是链式法则,也就是“层层传递影响”。因为神经网络是由多层结构组成的,每一层的输出都会影响最终结果,所以误差必须一层一层地往回传,让每一层都知道自己对最终错误“贡献了多少”。
从直观上理解,可以把整个过程看成一次“做题+改错”。模型先根据当前参数做出预测,这一步就是前向传播;然后计算预测结果与真实答案之间的差距,这一步得到损失;接下来就是关键——从结果开始往回分析,看看是最后一层错了多少,再往前一层推,逐层找出每个参数应该如何调整。最终的目标是让下一次预测更接近真实值。
在多层网络中,如果第 lll 层的输出为 a(l),其输入为 z(l),则可以写为:
z ( l ) = W ( l ) a ( l − 1 ) + b ( l ) a ( l ) = f ( z ( l ) ) z^{(l)} = W^{(l)} a^{(l-1)} + b^{(l)} a^{(l)} = f(z^{(l)}) z(l)=W(l)a(l−1)+b(l)a(l)=f(z(l))
反向传播的过程就是从最后一层开始,计算损失对当前层的梯度,然后再通过链式法则传递到前一层,不断重复这一过程,直到输入层为止。
在实际工程中,开发者几乎不需要手动推导这些公式。以 PyTorch 为例,只需要定义好模型和损失函数,执行一次前向计算后,调用:
loss.backward()
框架就会自动完成整个反向传播过程,包括梯度计算和存储。这一机制称为“自动求导”,是现代深度学习框架的核心能力之一。
反向传播不仅决定了模型能不能训练,还直接影响训练效率。例如,如果在某些层中梯度不断变小,传到前面几层时几乎变成零,那么这些层就几乎无法更新,这种现象称为“梯度消失”。这也是为什么在深层网络中需要使用 ReLU、残差结构等方法来缓解这一问题。同样,如果梯度过大,也会导致参数更新不稳定,这被称为“梯度爆炸”。
在实际应用中,反向传播贯穿所有深度学习任务。例如,在一个用户流失预测模型中,模型会根据用户行为数据输出一个流失概率,然后通过交叉熵损失计算误差,再通过反向传播调整每一层参数,使预测越来越准确。在图像识别中,模型通过卷积层提取特征后输出类别概率,同样依赖反向传播不断优化卷积核参数。在大语言模型中,数十亿参数的更新本质上也依赖这一机制,只是规模更大、计算更复杂。
从更高层来看,反向传播解决的是“模型如何改进”的问题。神经网络本身只是一个函数结构,而反向传播提供了一种系统的方法,让这个函数能够根据数据不断修正自身。这也使得深度学习从“固定规则系统”转变为“可自我优化系统”。
理解这一点之后,可以把整个训练过程统一为一个闭环:模型根据输入产生输出,损失函数衡量误差,反向传播计算调整方向,优化器更新参数。反向传播正处在这个闭环的核心位置,它把“结果的错误”转化为“参数的修改依据”,从而让神经网络真正具备学习能力。
7.2 损失函数(Loss)
在神经网络训练过程中,模型不仅要“给出答案”,还必须有一个明确的标准来判断这个答案到底好不好,这个标准就是损失函数。损失函数的作用,是把“预测结果和真实结果之间的差距”转化为一个可以计算、可以优化的数值。
设真实值为 y,模型预测值为 y^,损失函数可以表示为:
L = L ( y , y ^ ) L = L(y, \hat{y}) L=L(y,y^)
这个值越大,说明模型预测越不准确;这个值越小,说明模型预测越接近真实情况。整个神经网络训练的目标,本质就是不断调整参数,使这个损失值尽可能小。
在回归问题中,最常见的是均方误差(MSE):
L = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 L = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 L=n1i=1∑n(yi−y^i)2
这个公式的含义是,把每个样本的预测误差平方后求平均。之所以要平方,是为了放大误差较大的样本,同时避免正负误差相互抵消。在实际任务中,例如预测房价、体重变化、温度等连续数值,这种损失函数非常常见。如果某个样本预测偏差特别大,它在损失中占的比重也会更高,从而促使模型优先去修正这些错误。
在分类问题中,更常用的是交叉熵损失:
L = − ∑ i y i log ( y ^ i ) L = - \sum_{i} y_i \log(\hat{y}_i) L=−i∑yilog(y^i)
这里的 y^i 通常表示模型预测的概率。交叉熵的核心思想是:如果模型对正确类别给出的概率越高,损失就越小;如果给出的概率很低,损失就会迅速增大。例如,在一个二分类任务中,如果真实标签为1,而模型预测为0.9,那么损失很小;如果预测为0.1,损失就会很大。这种机制可以有效推动模型向“正确方向”学习。
从理解角度来看,可以把损失函数看成“评分规则”。模型每做一次预测,就会被打一个分,这个分数反映了它离正确答案有多远。训练的过程,就是不断调整模型,让这个分数越来越低。没有损失函数,模型就不知道自己哪里做错了,也就无法改进。
在工程实践中,损失函数的选择往往与任务类型直接相关。例如,在你的健康管理系统中,如果需要预测用户未来一周的体重变化,那么使用均方误差是合理的,因为这是一个连续值问题;如果需要判断用户是否存在健康风险,那么可以将问题转化为分类任务,使用交叉熵损失,让模型输出一个风险概率。
在代码实现中,损失函数通常直接调用框架接口。例如在 PyTorch 中:
loss_fn = torch.nn.MSELoss()
或者:
loss_fn = torch.nn.CrossEntropyLoss()
在训练过程中,每一次前向计算后,都需要计算损失:
loss = loss_fn(pred, target)
然后再通过反向传播更新参数。
需要注意的是,损失函数不仅仅影响“模型学不学得会”,还会影响“学得快不快、效果好不好”。例如,如果损失函数设计不合理,可能会导致模型对某些错误不敏感,从而影响最终性能;在不平衡数据(例如正负样本比例差异很大)中,还可能需要对损失函数进行加权处理,以避免模型偏向多数类别。
从更本质的角度来看,损失函数定义了模型的优化目标。神经网络并不知道什么是“正确的结果”,它只是在不断尝试让损失变小。因此,损失函数的选择,实际上是在告诉模型:什么样的结果才是你应该追求的方向。理解这一点之后,就能明白,损失函数不仅是一个数学公式,更是整个学习过程的“目标函数”和“指导标准”。
7.3 优化器(SGD / Adam)
在完成损失计算和反向传播之后,模型已经知道“哪里错了”,但还需要一个机制来决定“怎么改”。优化器的作用,就是根据梯度信息去更新模型参数,从而让损失逐步下降。
最基础的更新方式是梯度下降,其形式可以写为:
w = w − η ∇ L w = w - \eta \nabla L w=w−η∇L
其中,w 表示模型参数,∇L表示损失函数对参数的梯度,η是学习率。这个公式的含义是:沿着“让损失下降最快的方向”(梯度的反方向)去更新参数,每次更新的步长由学习率控制。
可以把这个过程想象成在一座山上往最低点走。当前的位置就是模型参数,山的高度就是损失值,梯度指向“上坡最快的方向”,那么我们就沿着相反方向(下坡)走。学习率决定每一步走多远,如果步子太大,可能会直接跨过最低点;如果步子太小,又会走得非常慢。
最基本的优化方法是随机梯度下降(SGD)。它的特点是每次用一小部分数据(一个batch)来计算梯度并更新参数。在实际工程中,SGD简单高效,但在复杂模型中容易出现震荡或者收敛慢的问题。
为了改善这些问题,引入了带动量的梯度下降方法。其核心思想是:不仅看当前梯度,还考虑过去的更新方向,使优化路径更加平滑。其形式可以表示为:
v t = β v t − 1 + ( 1 − β ) ∇ L w = w − η v t v_t = \beta v_{t-1} + (1 - \beta) \nabla L w = w - \eta v_t vt=βvt−1+(1−β)∇Lw=w−ηvt
这里的 vt可以理解为“带惯性的方向”,β控制历史信息的影响程度。这种方法在复杂损失曲面中能够减少来回震荡,提高收敛效率。
在现代深度学习中,更常用的是 Adam 优化器。它结合了动量和自适应学习率的思想,不同参数会根据历史梯度自动调整更新步长,其核心形式为:
m t = β 1 m t − 1 + ( 1 − β 1 ) ∇ L v t = β 2 v t − 1 + ( 1 − β 2 ) ( ∇ L ) 2 w = w − η m t v t + ϵ m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla L v_t = \beta_2 v_{t-1} + (1 - \beta_2) (\nabla L)^2 w = w - \eta \frac{m_t}{\sqrt{v_t} + \epsilon} mt=β1mt−1+(1−β1)∇Lvt=β2vt−1+(1−β2)(∇L)2w=w−ηvt+ϵmt
这里,mt表示一阶动量,vt表示二阶动量,ϵ是一个很小的数,用于防止分母为零。直观上看,Adam 会根据梯度的大小自动调整更新幅度:如果某个参数的梯度一直很大,就适当减小步长;如果梯度很小,就适当放大更新,从而实现更稳定的训练过程。
在实际使用中,优化器的选择会直接影响模型训练效果。例如,在一个用户行为预测模型中,如果使用简单的 SGD,可能需要较多轮训练才能收敛;而使用 Adam,通常可以更快达到较好的效果。在图像识别和自然语言处理任务中,Adam 几乎成为默认选择,因为它在大多数情况下都能提供较稳定的表现。
在工程实现中,优化器的使用非常直接。以 PyTorch 为例:
import torch.optim as optim
optimizer = optim.Adam(model.parameters(), lr=0.001)
在每一轮训练中,通常流程如下:
optimizer.zero_grad()
loss.backward()
optimizer.step()
这三步分别对应:清空旧梯度、计算新梯度、更新参数。可以看到,优化器与反向传播是紧密配合的,前者负责“计算怎么改”,后者负责“真正去改”。
在实际项目中,例如你的健康管理系统,如果模型用于预测用户未来健康风险,那么优化器的作用就是不断调整模型参数,使预测结果与真实情况越来越接近。如果优化器选择不当,可能会出现训练不稳定、收敛缓慢甚至无法收敛的问题,因此在模型调优过程中,学习率和优化器类型往往是重点调节的参数。
从整体来看,优化器解决的是“如何更新参数”的问题。损失函数定义了目标,反向传播提供了梯度,而优化器决定了具体的更新路径和效率。三者共同构成了神经网络训练的核心机制,使模型能够从数据中逐步学习并不断优化自身表现。
第8章 卷积神经网络(CNN)
8.1 卷积原理
在前面的全连接网络中,每一个输入特征都会与下一层的每一个神经元相连,这种结构在处理表格数据时效果很好,但在图像这类具有空间结构的数据上,会带来两个明显问题:一是参数数量巨大,二是无法有效利用“局部特征”。卷积神经网络正是为了解决这两个问题而提出的。
卷积的核心操作可以表示为:
y ( i , j ) = ∑ m ∑ n x ( i + m , j + n ) ⋅ w ( m , n ) y(i,j) = \sum_{m}\sum_{n} x(i+m, j+n) \cdot w(m,n) y(i,j)=m∑n∑x(i+m,j+n)⋅w(m,n)
这里的 x表示输入数据(例如图像),w表示卷积核(也叫滤波器),输出 y(i,j)是在位置 (i,j)处的计算结果。这个公式的本质是:在输入上取一个局部区域,与卷积核逐元素相乘再求和,得到一个新的特征值。
理解这一过程,可以把卷积核想象成一个“特征检测器”。比如一个 3×33 的卷积核,它每次只看图像中一个 3×33 的小区域,然后滑动到下一个位置继续计算。通过不断滑动,它可以扫描整张图像,并在不同位置提取特征。不同的卷积核可以学习到不同的模式,例如边缘、纹理、方向等。
卷积神经网络之所以高效,关键在于两个设计:局部连接和权重共享。局部连接意味着每个神经元只关注输入的一小块区域,而不是整张图像,这大大减少了参数数量。权重共享意味着同一个卷积核在整张图像上重复使用,不需要为每个位置单独学习参数,这不仅进一步降低了参数规模,还让模型具备了“平移不变性”,也就是说,同一个特征出现在不同位置时,模型都能识别出来。
在实际任务中,例如人脸识别,第一层卷积可能学到的是简单的边缘特征,第二层开始组合这些边缘形成更复杂的形状,到了更深层,模型甚至可以识别出眼睛、鼻子、嘴巴等结构。也就是说,卷积网络是在逐层构建从低级特征到高级语义的表达。
在工程实现中,卷积操作通常直接调用框架接口。例如在 PyTorch 中:
import torch.nn as nn
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3)
这里的 in_channels=3 表示输入是三通道图像(RGB),out_channels=16 表示会生成16个不同的特征图,每个特征图对应一个卷积核学到的模式。
在实际应用中,卷积广泛用于视觉相关任务。例如在自动驾驶中,模型通过卷积层识别道路、车辆和行人;在医疗影像中,可以通过卷积网络检测病灶区域;在工业检测中,可以识别产品缺陷。在你的项目中,如果后续需要对用户上传的食物图片进行识别(例如估算热量或判断饮食类型),卷积神经网络就是最核心的技术基础。
从本质上看,卷积的作用是将“原始像素数据”转化为“有意义的特征表示”。它不是简单地做计算,而是在不断提取信息、压缩信息并突出关键模式。这一过程,使得后续的分类或预测变得更加容易和准确。
8.2 池化层
在卷积层提取出特征之后,如果直接把这些特征全部传递到下一层,一方面数据量仍然很大,计算成本高,另一方面模型也容易对细微变化过于敏感,从而出现过拟合问题。池化层的引入,正是为了在保留关键信息的前提下,对特征进行压缩和筛选。
最常见的池化方式是最大池化,其表达可以写为:
y = max ( x 1 , x 2 , … , x n ) y = \max(x_1, x_2, \dots, x_n) y=max(x1,x2,…,xn)
它的含义是:在一个局部区域内(例如 2×22的窗口),只保留数值最大的那个特征,其余信息被舍弃。
如果从直观角度理解,池化可以看作一种“信息浓缩”。卷积层得到的特征图中,某些位置的响应值较大,说明该区域存在重要特征,例如明显的边缘或关键结构;而响应较小的区域,往往代表信息不那么重要。最大池化的作用,就是在局部范围内只保留“最显著”的信号,从而让模型更加关注关键区域。
例如在图像识别任务中,一只猫的耳朵出现在图片的某个位置,即使稍微发生平移或缩放,池化层仍然可以捕捉到这个特征,而不会因为位置变化而完全丢失信息。这种性质被称为“平移不变性”,是卷积神经网络能够稳定识别图像的重要原因之一。
从结构上看,池化层通常跟在卷积层之后。假设一个特征图大小为 28×28,经过一个 2×22 的最大池化后,尺寸会变成 14×14。这样不仅减少了一半的宽高,也意味着后续计算量大幅下降。对于深层网络来说,这种降维操作是非常关键的,否则模型参数会迅速膨胀,训练成本难以控制。
除了最大池化,还有平均池化,其形式为:
y = 1 n ∑ i = 1 n x i y = \frac{1}{n} \sum_{i=1}^{n} x_i y=n1i=1∑nxi
平均池化会取区域内所有值的平均值,相比最大池化,它更平滑,但也可能削弱一些重要特征。在实际应用中,最大池化使用更广泛,因为它更有利于保留显著特征。
在工程实现中,池化层同样非常简单。例如在 PyTorch 中:
import torch.nn as nn
pool = nn.MaxPool2d(kernel_size=2, stride=2)
这里的 kernel_size=2 表示池化窗口为 2×22 ,stride=2 表示每次移动两个像素,从而实现尺寸减半。
在实际应用中,池化不仅用于图像任务,在某些序列模型中也会用类似思想进行降维。例如在文本分类中,可以对词向量序列做“最大池化”,提取最重要的语义特征。
结合具体业务来看,如果你的系统中涉及用户运动轨迹分析或图像数据(例如饮食识别、动作识别),池化层可以帮助模型在保留关键模式的同时降低计算成本。例如在跑步轨迹分析中,如果将轨迹转化为图像表示,池化可以突出关键变化区域,忽略微小波动,从而提高模型稳定性。
从整体角度看,卷积层负责“提取特征”,而池化层负责“筛选特征”。两者结合,使得模型既能捕捉细节,又不会被冗余信息干扰。这种结构设计,是卷积神经网络能够在大规模视觉任务中高效运行的重要原因之一。
8.3 经典模型(LeNet / AlexNet / ResNet)
在理解了卷积和池化之后,还需要进一步看到这些结构是如何在实际模型中组合起来的。深度学习的发展,很大程度上就是不断设计更有效的网络结构。从早期简单模型到现代深层网络,每一步的改进,本质上都是在解决“表达能力”和“可训练性”之间的平衡问题。
最早具有代表性的模型是 LeNet。它主要用于手写数字识别任务,其结构已经包含了卷积层、池化层和全连接层的基本组合方式。LeNet 的意义不在于复杂,而在于它首次验证了:通过卷积提取特征,再通过全连接层完成分类,是一条可行路径。在实际使用中,如果任务规模较小,例如简单图像分类或嵌入式设备上的轻量识别,类似 LeNet 的浅层结构仍然具有参考价值。
随着数据规模和计算能力的提升,AlexNet 的出现成为一个重要转折点。它在 ImageNet 大规模图像分类任务中取得突破,推动了深度学习的广泛应用。AlexNet 相比 LeNet 更深,引入了 ReLU 激活函数来加快训练速度,同时利用 GPU 进行加速计算,使得深层卷积网络第一次在大规模任务中表现出显著优势。在实际工程中,AlexNet 的思想延续至今,例如“多层卷积 + 非线性激活 + 池化 + 全连接”的结构,仍然是许多模型的基础框架。
当网络不断加深时,一个新的问题出现:层数越多,训练越困难。即使理论上更深的网络表达能力更强,但在实际训练中,梯度会逐渐变小甚至消失,导致前面的层几乎无法更新。这一问题在传统结构中非常严重,直接限制了网络深度的提升。
ResNet 的提出,正是为了解决这一问题。其核心思想是引入“残差连接”,即让网络学习输入与输出之间的差值,而不是直接学习完整映射。其基本形式可以表示为:
y = F ( x ) + x y = F(x) + x y=F(x)+x
这里的 F(x)表示网络需要学习的残差部分,而 x 是输入直接跳跃连接到输出。这样一来,如果某一层不需要复杂变换,只需让 F(x)≈0,模型就可以直接保留原始信息,而不会因为层数增加而退化。
从理解角度来看,这种结构相当于给网络增加了一条“捷径”。在没有残差连接时,信息必须一层一层传递,中间任何一层出现问题都会影响整体;而有了残差连接后,即使中间层学习效果不好,信息仍然可以直接传到后面,从而保证训练稳定性。
在实际应用中,ResNet 的意义非常大。它使得网络可以达到数十层甚至上百层,同时仍然可以正常训练。在图像识别任务中,例如人脸识别、医学影像分析、自动驾驶视觉系统,ResNet 及其变体被广泛使用。例如在医疗影像中,可以通过深层 ResNet 模型识别细微病灶;在工业检测中,可以识别复杂的缺陷模式;在你的项目中,如果未来引入图像分析模块(如饮食识别、运动姿态识别),ResNet 是非常常见且成熟的选择。
在工程实现中,主流框架通常已经封装好这些经典模型。例如在 PyTorch 中:
import torchvision.models as models
model = models.resnet18(pretrained=True)
这行代码可以直接加载一个预训练的 ResNet 模型,用于迁移学习。也就是说,在实际项目中,不需要从零开始训练整个网络,而是可以利用已有模型进行微调,从而大幅降低开发成本和数据需求。
从整体发展来看,这些经典模型代表了三个阶段。LeNet 验证了卷积网络的可行性,AlexNet 推动了深度学习在大规模任务中的应用,而 ResNet 解决了深层网络训练困难的问题。它们共同构成了现代计算机视觉模型的基础。
更重要的是,这些模型体现了一条清晰的演进逻辑:随着任务复杂度增加,网络必须变深;随着网络变深,必须解决训练稳定性问题;最终通过结构创新,使模型既强大又可训练。这种思路不仅适用于视觉领域,也影响了后续所有深度学习模型的设计。
第9章 序列模型(RNN系列)
9.1 RNN原理
在前面的神经网络和卷积网络中,模型处理的数据通常是“静态的”,例如一张图片或一条固定长度的特征向量。但在很多实际问题中,数据是具有时间顺序的,例如一句话、语音信号、用户行为序列等。这类数据的一个核心特点是:当前信息往往依赖于过去的信息。为了解决这类问题,引入了循环神经网络(RNN)。
RNN 的核心结构可以表示为:
h t = f ( W x t + U h t − 1 ) y t = g ( V h t ) h_t = f(W x_t + U h_{t-1}) y_t = g(V h_t) ht=f(Wxt+Uht−1)yt=g(Vht)
其中,xt 表示当前时刻的输入,ht表示当前隐藏状态,ht−1 表示上一时刻的状态。可以看到,RNN 的关键在于:当前状态不仅由当前输入决定,还受到历史状态的影响。
这种结构使得模型具备“记忆能力”。在处理序列时,模型不会孤立地看某一个数据点,而是会把之前的信息带入当前计算中。例如在一句话“我昨天去了北京,很开心”中,“很开心”这个情绪的理解,需要结合前面的“去了北京”这一信息。如果没有记忆机制,模型只能看到当前词,很难理解完整语义。
从结构上看,RNN 可以理解为“同一个网络在时间上的重复使用”。每一个时间步使用的是同一组参数,这就是所谓的权重共享。这种设计不仅减少了参数数量,也保证了模型在不同时间位置上的一致性。
如果把整个序列展开,可以得到一个类似“链式结构”的网络,每一步的状态都依赖前一步。这也是为什么在训练 RNN 时,会使用一种特殊的反向传播方式,称为“通过时间的反向传播”(BPTT)。本质上,仍然是通过梯度不断调整参数,只不过误差是沿着时间维度传播的。
在实际应用中,RNN 非常适合处理各种序列数据。例如在文本任务中,可以用于情感分析、文本生成、机器翻译;在语音任务中,可以用于语音识别;在时间序列预测中,可以用于预测股票走势或用户行为变化。在你的健康管理系统中,如果需要分析用户一段时间内的运动数据或体重变化趋势,也可以使用 RNN 来建模时间依赖关系,例如根据过去一周的数据预测未来状态。
在工程实现中,RNN 的使用也非常直接。例如在 PyTorch 中:
import torch.nn as nn
rnn = nn.RNN(input_size=10, hidden_size=20, batch_first=True)
这里的 input_size 表示每个时间步的特征维度,hidden_size 表示隐藏状态的维度。模型在处理一段序列时,会逐步更新隐藏状态,从而形成对整个序列的理解。
不过,RNN 在实际使用中也存在明显问题。由于信息需要通过多个时间步传递,当序列很长时,早期信息在传递过程中会逐渐减弱,甚至消失,这就是“长期依赖问题”。简单来说,模型很难记住很久之前的重要信息。例如在一段很长的文本中,句子开头的信息可能对结尾很重要,但普通 RNN 很难保持这种长距离依赖。
正因为这个问题,后续才发展出了 LSTM 和 GRU 等改进结构,用更复杂的机制来控制信息的保留与遗忘。但即便如此,RNN 的基本思想仍然非常重要:通过引入“状态”,让模型能够处理时间序列数据。
从整体来看,RNN 解决的是“如何让模型记住过去”的问题。它将神经网络从静态输入扩展到动态序列,使 AI 能够理解时间、上下文和顺序关系。这一能力是自然语言处理、语音识别以及行为预测等领域的基础。
9.2 LSTM
在 RNN 中,虽然引入了“记忆”的概念,但这种记忆在长序列中往往不稳定。随着时间步不断增加,早期的信息会在不断传递中逐渐衰减,甚至完全消失,这就是常说的长期依赖问题。LSTM(Long Short-Term Memory)正是为了解决这一问题而提出的一种改进结构。
LSTM 的核心思想是:不再简单地让信息一直传递,而是通过“选择机制”来控制信息的保留与遗忘。它引入了多个门结构,用来决定哪些信息应该被记住,哪些信息应该被丢弃。
其核心计算可以表示为:
f t = σ ( W f x t + U f h t − 1 + b f ) i t = σ ( W i x t + U i h t − 1 + b i ) C ~ t = tanh ( W c x t + U c h t − 1 + b c ) C t = f t ⊙ C t − 1 + i t ⊙ C ~ t o t = σ ( W o x t + U o h t − 1 + b o ) h t = o t ⊙ tanh ( C t ) f_t = \sigma(W_f x_t + U_f h_{t-1} + b_f) i_t = \sigma(W_i x_t + U_i h_{t-1} + b_i) \tilde{C}_t = \tanh(W_c x_t + U_c h_{t-1} + b_c) C_t = f_t \odot C_{t-1} + i_t \odot \tilde{C}_t o_t = \sigma(W_o x_t + U_o h_{t-1} + b_o) h_t = o_t \odot \tanh(C_t) ft=σ(Wfxt+Ufht−1+bf)it=σ(Wixt+Uiht−1+bi)C~t=tanh(Wcxt+Ucht−1+bc)Ct=ft⊙Ct−1+it⊙C~tot=σ(Woxt+Uoht−1+bo)ht=ot⊙tanh(Ct)
其中,Ct表示细胞状态,可以理解为“长期记忆”;ht 表示当前输出状态;σ是 Sigmoid 函数,用于控制信息通过的比例;⊙表示逐元素相乘。
这些公式看起来复杂,但可以从整体逻辑来理解。LSTM 做的事情,本质上是给网络加了一套“信息管理系统”。遗忘门 ft决定过去的信息中哪些应该保留,哪些应该丢弃;输入门 it决定当前输入中哪些信息值得写入记忆;输出门 ot 决定当前时刻应该输出多少信息。通过这三种门的配合,模型不再被动接收信息,而是主动筛选和管理信息。
如果从直观角度理解,可以把 LSTM 看成一个“带筛选功能的记忆系统”。例如在处理一句长文本时,并不是每个词都同样重要,有些词需要长期记住,有些只在局部起作用。LSTM 的门结构,就相当于在不断判断:这条信息有没有用,要不要记下来,要记多久。
在实际应用中,这种能力非常关键。例如在机器翻译中,句子开头的信息可能会影响句子结尾的翻译结果;在语音识别中,前面的语音片段可能决定后面的语义;在用户行为预测中,较早的行为模式可能对当前决策产生影响。普通 RNN 很难处理这些长距离依赖,而 LSTM 可以通过门控机制有效保留关键信息。
在工程实践中,LSTM 的使用非常普遍。例如在 PyTorch 中,可以直接调用:
import torch.nn as nn
lstm = nn.LSTM(input_size=10, hidden_size=20, batch_first=True)
在实际项目中,例如你的健康管理系统,如果需要分析用户一段时间内的运动记录、饮食习惯或体重变化趋势,可以将这些数据按时间序列输入 LSTM,让模型学习长期变化规律。例如,模型可以根据过去一周甚至一个月的数据,判断用户当前的健康趋势,而不是只依赖某一天的数据。
需要注意的是,LSTM 相比普通 RNN 结构更复杂,参数更多,计算开销也更大。在数据量较小或实时性要求较高的场景中,可能会带来一定负担。因此,在实际工程中,是否使用 LSTM,需要在“效果”和“效率”之间做权衡。
从整体来看,LSTM 解决的是“如何稳定记住长期信息”的问题。它通过引入门控机制,让神经网络具备了更精细的信息控制能力,使模型不仅能记住过去,还能决定记什么、忘什么。这一能力,使得深度学习在处理长序列数据时更加可靠,也为后续更复杂的模型结构奠定了基础。
9.3 GRU
在 LSTM 的基础上,虽然模型已经具备了较强的长期记忆能力,但结构相对复杂,参数较多,计算开销也更大。在一些对效率要求较高的场景中,这种复杂性会成为负担。GRU(Gated Recurrent Unit)就是在这样的背景下提出的一种简化结构,它在尽量保留 LSTM 效果的同时,减少了计算复杂度。
GRU 的核心结构可以表示为:
z t = σ ( W z x t + U z h t − 1 ) r t = σ ( W r x t + U r h t − 1 ) h ~ t = tanh ( W x t + U ( r t ⊙ h t − 1 ) ) h t = ( 1 − z t ) h t − 1 + z t h ~ t z_t = \sigma(W_z x_t + U_z h_{t-1}) r_t = \sigma(W_r x_t + U_r h_{t-1}) \tilde{h}_t = \tanh(W x_t + U (r_t \odot h_{t-1})) h_t = (1 - z_t) h_{t-1} + z_t \tilde{h}_t zt=σ(Wzxt+Uzht−1)rt=σ(Wrxt+Urht−1)h~t=tanh(Wxt+U(rt⊙ht−1))ht=(1−zt)ht−1+zth~t
其中,zt被称为更新门,用来控制“新信息和旧信息的融合比例”;rtr_trt 被称为重置门,用来控制“历史信息在当前计算中保留多少”。
从整体结构上看,GRU 把 LSTM 中的多个门合并成两个核心机制,省去了单独的“细胞状态”,直接用隐藏状态 ht来表示记忆。这种简化使模型更加紧凑,同时在很多任务中仍然能够保持良好效果。
如果从直观角度理解,GRU 的工作方式可以概括为一句话:在每一步中决定“要不要更新记忆,以及更新多少”。更新门 zt起到类似“权重分配”的作用,如果 zt接近 1,说明当前输入很重要,模型会更多地采用新信息;如果 zt 接近 0,说明当前信息不重要,模型更倾向于保留过去的状态。重置门 rt则控制在生成新候选状态时,是否需要参考历史信息,如果 rt 较小,说明当前更关注新的输入,而忽略过去。
这种机制让 GRU 在结构上更加简洁,同时仍然具备处理长期依赖的能力。相比 LSTM,GRU 的参数更少,训练速度更快,在数据量较小或计算资源有限的场景中更具优势。
在实际应用中,GRU 常用于需要实时响应的系统。例如在在线推荐系统中,用户行为是连续变化的,需要快速更新模型对用户兴趣的判断;在语音识别或实时预测任务中,模型必须在较短时间内完成计算;在移动端或嵌入式设备中,计算资源有限,GRU 的轻量特性更适合部署。
在工程实现中,GRU 同样可以直接调用。例如在 PyTorch 中:
import torch.nn as nn
gru = nn.GRU(input_size=10, hidden_size=20, batch_first=True)
在实际项目中,如果需要处理用户的时间序列数据,例如连续多天的运动记录、饮食行为或健康指标变化,可以将这些数据作为序列输入 GRU,让模型学习用户行为的变化趋势。相比 LSTM,GRU 在保持效果的同时,能够更快完成训练和推理,更适合需要高效率的场景。
制在生成新候选状态时,是否需要参考历史信息,如果 rt 较小,说明当前更关注新的输入,而忽略过去。
这种机制让 GRU 在结构上更加简洁,同时仍然具备处理长期依赖的能力。相比 LSTM,GRU 的参数更少,训练速度更快,在数据量较小或计算资源有限的场景中更具优势。
在实际应用中,GRU 常用于需要实时响应的系统。例如在在线推荐系统中,用户行为是连续变化的,需要快速更新模型对用户兴趣的判断;在语音识别或实时预测任务中,模型必须在较短时间内完成计算;在移动端或嵌入式设备中,计算资源有限,GRU 的轻量特性更适合部署。
在工程实现中,GRU 同样可以直接调用。例如在 PyTorch 中:
import torch.nn as nn
gru = nn.GRU(input_size=10, hidden_size=20, batch_first=True)
在实际项目中,如果需要处理用户的时间序列数据,例如连续多天的运动记录、饮食行为或健康指标变化,可以将这些数据作为序列输入 GRU,让模型学习用户行为的变化趋势。相比 LSTM,GRU 在保持效果的同时,能够更快完成训练和推理,更适合需要高效率的场景。
从整体来看,GRU 解决的是“在保证效果的前提下提高效率”的问题。它延续了 LSTM 的门控思想,但通过结构简化,使模型更加轻量。可以理解为:如果 LSTM 更强调“精细控制记忆”,那么 GRU 更强调“高效地更新记忆”。在实际工程中,两者往往可以互换使用,具体选择取决于任务复杂度、数据规模以及计算资源限制。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)