第一次看到一张密密麻麻的网络结构图,很多人会把神经网络理解成“许多逻辑回归堆起来”。这个说法能帮助你认出“加权求和”与 sigmoid,却也会很快带来误解:隐藏单元未必输出概率,现代网络的隐藏层经常使用 ReLU;一个单元只完成局部计算,真正改变表示能力的是仿射变换、非线性激活与多层组合。
这一章只讨论“网络怎样表示一个函数,以及给定参数时怎样完成前向传播”。权重如何从数据中学出来、反向传播怎样求梯度、初始化和优化为何影响训练,留到下一章。把这条边界守住,我们就能先回答几个更基础的问题:
本章统一采用“样本按行排列”的记法。若批量大小为 、输入特征数为 ,输入矩阵写成 。先把这个约定记住,后面的矩阵形状会顺很多。
线性分类器先计算一个仿射分数:
再根据分数决定类别。无论使用阈值还是 sigmoid,类别发生变化的位置都满足 。在二维平面中,这是一条直线;在更高维空间中,它是一个超平面。只要特征表示不变,模型就只能用一个平面把空间切成两边。
XOR(异或)把这个限制展示得很干净。它有两个二进制输入,仅在两个输入不同时输出 :
两个正类点位于正方形的一组对角,两个负类点位于另一组对角。你画任何一条直线,都无法把两组点完全分开。这不是“优化器没调好”,而是模型族里根本没有符合要求的边界。

隐藏层改变的是数据表示;边界变简单,是因为坐标变了。
下面的交互应让读者拖动直线,亲自验证单条线无法分开 XOR,再切换到隐藏表示观察边界怎样变化。
一种解决办法是人工构造新特征,例如加入 。另一种办法是让网络从数据中学习中间表示。对二进制输入,下面两个隐藏特征已经足够:
把它们相加得到:
当两个输入相同,结果为 ;两个输入不同,结果为 。输入空间里无法由一条直线解决的问题,到了隐藏表示 中就变得简单了。
这里真正发生的事不是网络“想出了 XOR 规则”,而是中间层把原坐标变换成了更适合输出层使用的特征。神经网络的“表示”指的正是这些中间激活值,以及它们构成的坐标系统。
给线性层后面接多少个线性层都无法解决 XOR。若中间没有非线性激活,多层仿射变换仍可合并成一次仿射变换。深度只有和非线性组合起来,才会扩大函数表示方式。
一个人工神经元接收向量 ,先计算预激活值 ,再通过激活函数 得到输出 :
权重 决定各输入方向对 的影响,偏置 平移阈值位置,激活函数则决定怎样把这个分数变成新的表示。三部分缺一不可,但它们扮演的角色不同。

神经元是一段计算结构,不自动等同于逻辑回归。
若没有偏置,二维线性边界 必须经过原点。加入 后,边界可以在空间中平移。对一个 ReLU 单元来说, 也是它从“关闭”切换到“开启”的位置。偏置因此参与决定分段发生在哪里。
有些教材把常数 拼到输入向量中,再把偏置写进扩展权重。数学上这样没有问题;代码中通常把 和 分开保存,因为形状更清楚,也方便框架分别管理参数。
当 ,且这个单元位于二分类输出层,并与合适的概率模型和损失配对时,它可以构成逻辑回归形式的分类器。但以下情况都不应直接称为逻辑回归:
“神经元”描述的是一段计算结构,“逻辑回归”描述的是带有输出解释和训练目标的统计模型。两者有重叠,但不能画等号。
若一层有 个单元,把每个单元的权重按行堆成矩阵,就得到:
其中:
激活函数通常逐元素作用:
这样,一个“层”就是同时学习 个不同的投影方向和偏移,再把结果变成 维新表示。
一个全连接前馈网络把多个函数按顺序复合。令输入为 ,第 个仿射层的计算写成:
信息从输入流向输出,没有把当前输出重新送回前面,因此叫前馈网络。位于输入与最终输出之间的层叫隐藏层,因为训练数据并没有为这些中间激活提供直接标签。
若第 层有 个单元,我们说它的宽度是 。宽层可以同时保留许多不同特征。比如一层可能对多个方向、阈值或局部模式产生响应,下一层再组合这些响应。
不同教材对“几层网络”的说法可能差一层:有人只数带参数的层,有人数隐藏层,还有人把输入层也算进去。本章约定:
讨论架构时,直接写各层宽度比只说“二层网络”更不容易误会。

宽度回答每层保留多少特征,深度回答经过多少次带参数变换。
实际计算不会一次只处理一个样本。将 个样本按行放进矩阵:
为了保持“样本按行”,本章令每层权重的形状为:
于是批量计算写成:
这里:
偏置 会沿批量维广播到每一行。激活后的形状不变:
下面的形状实验应允许修改批量大小与各层宽度,实时显示每次矩阵乘法、偏置广播和输出张量形状。
假设架构是 ,批量大小 :
如果你总在网络代码里遇到维度错误,先别盯着数值。把每个张量的“批量维、输入维、输出维”写出来,通常比反复改转置更快。
如果每层都使用恒等激活 ,两层网络为:
整理后得到:
其中:
无论中间矩阵多大,这仍是一次仿射变换。非线性激活阻止层与层被这样合并,也让不同输入区域可以经历不同的变换。

激活函数决定隐藏表示怎样响应输入,输出层仍要按任务单独设计。
sigmoid 平滑、有界,适合把二分类输出 logit 转成一个 0 到 1 的数。作为隐藏表示时,它会把很大的正负输入分别压到接近 1 和 0,数值差异在两端变得很小。这里只需记住它表达的是一种“软阈值”;饱和如何影响梯度,下一章再谈。
tanh 的范围是 ,负输入保留负号,零附近近似线性。它同样是平滑、会在两端饱和的激活。与 sigmoid 相比,隐藏表示可以围绕 0 分布。
ReLU 在 时输出 0,在 时保留线性值。单个 ReLU 像一个带门槛的斜坡;许多 ReLU 叠加后,可以拼出折线、折面和更高维的分段线性函数。
ReLU 不是处处可导,但前向表示并不因此含糊:输入落在阈值哪一侧,输出规则就确定了。至于阈值处在训练时怎样处理,框架会选择一个约定的次梯度,这属于下一章内容。
隐藏层激活决定中间表示,输出层变换要匹配目标含义。一个网络可以在隐藏层使用 ReLU,在二分类输出层使用 sigmoid;也可以在隐藏层使用 ReLU,回归输出层不加激活。不要因为隐藏层选了 ReLU,就默认输出也必须是 ReLU。
我们用一个 网络完成二分类前向计算。隐藏层使用 ReLU,输出层先给出 logit,再用 sigmoid 转成分数。输入为:
第一层参数为:
第二层参数为:

前向传播只计算当前参数对应的函数值,并不更新参数。
先算隐藏层预激活。矩阵乘法给出 ,再加偏置得到 。
这次计算里有三个容易混淆的对象:
前向传播只是在当前参数下计算函数值。上面的权重是直接给定的,所以我们还没有讨论它们怎样变好。下一章的反向传播会沿相反方向计算损失对参数的影响,但它不会改变这里的前向公式。
隐藏层不必对应人类能命名的概念。它的更稳妥解释是:每个单元对输入做一次投影和阈值变换,整层把输入映射到新的坐标。下一层再对这些坐标进行组合。
考虑三个 ReLU 单元:
输出若写成:
那么函数会在 、、 这些位置改变斜率。每个单元贡献一个“折点”,输出层把多条折线组合成新的分段线性曲线。权重和偏置决定折点的位置与斜率贡献。
在二维输入中,单个 ReLU 的切换位置 是一条直线。这条线把空间分成激活和未激活两侧。多个单元产生多条分界线,一层之后,不同区域拥有不同的激活模式。后续层继续组合这些模式,边界可以出现弯折与嵌套。

跨过阈值时激活模式改变,网络才切换到另一条仿射规则。
对一个已经固定参数的 ReLU 网络,在某个激活模式不变的区域内,每个 ReLU 要么传递线性值,要么输出 0,所以整个网络在该区域内等价于一个仿射函数。跨过某个单元的阈值,激活模式改变,仿射规则也随之改变。这就是“分段线性”的准确含义。
深度可能让函数复合更高效,但不要把理论上可达到的最大区域数当成实际网络一定拥有的区域数。区域数量取决于具体参数,全部权重为零时甚至只有一种平凡输出;能表示复杂函数与当前参数已经表示出复杂函数是两回事。
多项式特征工程会预先决定加入哪些 、 或更高阶项。神经网络则让隐藏层参数随任务调整。一个中间特征还能被多个下游单元复用,后续层在已有表示上继续组合。
这不意味着网络会自动学到“边缘”“轮子”或“用户价值”这样的可读语义。某些网络中能观察到相对清楚的模式,但单个单元也可能只编码分布式表示的一部分。判断中间表示,需要看网络、数据和分析方法,不能只靠给节点起名字。
隐藏表示算好后,输出层仍需匹配问题结构。最常见的错误不是矩阵算错,而是把“互斥类别”和“可同时成立的标签”混为一谈。

输出头的区别来自标签关系,不是类别数量本身。
对“邮件是否为垃圾邮件”,输出层可给一个 logit ,再用 sigmoid 得到:
得到概率分数后,还要根据错误成本选择阈值。阈值 0.5 是常见默认值,不是数学定律。若漏掉一次欺诈的代价远高于误报,部署阈值可能更低。
若一张手写数字图片只能属于 0 到 9 中的一个类别,输出层给 个 logits:
softmax 将它们转换成总和为 1 的分布:
提高某一类的相对分数会挤压其他类的概率,这正符合“类别互斥”的假设。预测类别通常取最大 logit 或最大 softmax 概率,两者的索引相同。
一张照片可以同时包含“人物”“自行车”和“树”。这时输出 个独立 logits,并分别应用 sigmoid:
这些值不要求总和为 1。每个标签还可以有自己的阈值。若错误地使用 softmax,模型会被迫让多个真实标签互相竞争。
预测温度、收益或残差这类可正可负的实数时,输出层常直接返回线性值。若目标有明确范围,可以使用合适变换或参数化,但要把限制和数据含义对应起来,不能因为“神经网络必须有激活”就在所有输出后接 sigmoid。
实际框架常把 logits 直接交给数值稳定的损失函数,例如把 sigmoid 与二元交叉熵合并。前向解释时我们仍可显式写出概率变换,但实现中不要为了“看得见概率”而重复应用激活。
下面的交互应让读者选择标签关系,并观察 sigmoid 与 softmax 在同一组 logits 上产生的不同结果。
从宽度表可以直接算出全连接网络的参数量。连接 个输入到 个输出时,权重有 个,偏置有 个,因此该层参数量为:
整个 层网络的参数总数为:
以 为例:
批量大小 决定一次前向传播处理多少样本,也决定中间激活需要多少内存,但它不改变 和 的形状。因此从 改到 ,参数总数仍是 2676,激活张量却会变大约 8 倍。
全连接层的主要乘法量与 同阶。输入维数很高时,第一层尤其昂贵。例如把 灰度图像展平成 784 维,再接 128 个隐藏单元,仅第一层就有:
个参数。这也是为什么处理图像时,是否利用局部结构会显著影响参数效率。这里先看清成本,卷积等专门结构留给后续课程。
通用近似定理常被缩成一句话:“一个隐藏层就能表示任何函数。”这句话省掉的条件,恰好是最容易误导人的部分。
以经典 sigmoid 版本为例,更准确的表述是:对于紧致区域上的连续目标函数,只要允许单隐藏层网络拥有足够多的隐藏单元,就存在一组参数,使网络在给定误差容忍度内一致逼近目标函数。可把目标写成:
其中 是紧致输入集合, 是允许误差。后续结果把激活函数和函数空间推广到了更广范围,但“近似”“给定区域”“足够容量”和“存在参数”仍是理解边界的关键词。
定理说明参数空间中有合适解,不说明梯度下降会在有限时间内找到它,也不说明需要多少训练样本。表示问题问“这个架构能不能装下某类函数”,优化问题问“训练过程能不能找到好参数”,统计问题问“有限样本能不能支持对新数据泛化”。三者不能互相替代。
“单隐藏层足够”可能要求数量极大的隐藏单元。更深的网络有时能通过重复组合,用更少参数表达具有层次结构的函数;但深度也会带来优化、计算和调试成本。定理没有宣布深度无用,也没有给出所有任务的最佳宽深比例。
误差小于任意给定的正数,不等于一个固定有限网络对所有输入都完全精确。定理通常关注指定紧致区域,不保证离开训练与近似区域后的外推行为。一个在 内逼近良好的网络,到了 可能表现完全不同。
容量很大的模型既能表示真实规律,也能表示噪声和偶然模式。只有训练数据、归纳偏置、正则化、优化过程和评估设计共同作用,才可能得到可靠泛化。通用近似定理不能用来解释某个具体网络为何在测试集表现好。
看到“神经网络什么都能拟合”时,至少追问四件事:在哪个输入区域、以多大误差、需要多少单元、怎样找到那组参数。少了这些条件,表示能力很容易被误写成训练与泛化保证。
神经网络接收的是数值张量。数据来自表格还是图像,会改变输入维度和结构含义,但前向传播仍然遵循同一套矩阵计算。

全连接层能处理两类向量,但不会自动知道图像的局部空间关系。
假设我们用 12 个数值特征预测用户是否续订,输入批量形状是 。架构 会把每行依次映射为 16 维、8 维表示,最后输出一个 logit。
表格特征往往量纲不同,还可能包含类别编码和缺失标记。网络不会自动知道“金额 1000”和“次数 3”不在同一尺度,也不会理解类别编号 2 比编号 1“更大”是否有意义。预处理方式决定了模型实际看到的几何空间。
中间 8 维激活可以帮助输出层组合特征,但不要擅自把第一个单元命名成“消费能力”、第二个单元命名成“忠诚度”。除非有独立分析支持,否则它们只是为当前预测目标形成的分布式表示。
一张 灰度图可展平成 784 维向量,批量形状变为 ,再送进全连接层。数学上完全可行,但展平后,模型本身不知道第 27 个像素与第 28 个像素在空间上相邻,也不知道同一个边缘平移几格仍是相似模式。
全连接网络仍可能从足够数据中学到有用表示,只是参数效率和数据效率未必理想。卷积网络通过局部连接和权重共享显式利用图像结构;这是一种归纳偏置,不是说全连接层无法计算图像。
对架构 ,批量大小 :
每一行的 10 个输出 logits 对应同一张图片的 10 个候选类别。softmax 应沿类别轴计算,也就是对每一行归一化,而不是把整个批量混在一起。
最后把公式落到代码。下面实现一个两隐藏层网络,输入和每层激活都保持二维形状(批量,特征)。函数返回 logits 和中间值,便于逐层检查。
import numpy as np
def relu(z):
return np.maximum(z, 0.0)
def sigmoid(z):
z = np.clip(z, -30.0, 30.0)
return 1.0 / (1.0 + np.exp(-z))
def softmax(logits):
shifted = logits - logits.max(axis
若任务是互斥多分类,可以这样得到每行总和为 1 的概率:
logits, cache = forward(X, params)
probs = softmax(logits)
predictions = np.argmax(logits, axis=1)
print("logits:", logits.shape)
print("probabilities:", probs.shape)
print("row sums:", probs.sum(axis=1))若是多标签任务,则对每个 logit 独立应用 sigmoid:
label_scores = sigmoid(logits)
predictions = label_scores >= 0.5这里没有训练代码。params 中的矩阵可由随机数构造,也可以由训练过程更新;只要形状一致,前向传播逻辑不变。
在 中,形状为 的 会沿批量维正确复用。广播只检查维度是否兼容,不知道你的语义。若批量大小恰好等于层宽,错误形状有时也能算出结果,所以最好主动断言:
assert Z1.shape == (X.shape[0], W1.shape[0])
assert A1.shape == Z1.shape
assert logits.shape == (X.shape[0], W3.shape[0])把上一节的 参数写进 NumPy,再检查 logit 是否接近 。这种小测试比只看“代码没有报错”更有价值,因为转置、偏置或激活位置写错时,程序仍可能返回一个形状合理的数组。
走到这里,我们已经把神经网络表示串成一条完整链路:输入先经过仿射变换,非线性激活形成新坐标,多层组合产生复杂表示,输出头把最后的表示翻译成任务需要的分数。下一章会在这条前向链上定义损失,并从输出向前计算每个参数应该怎样调整。
| 3 个隐藏单元,各有 4 个输入权重 |
| 每个隐藏单元一个偏置 |
| 每个样本得到 3 个隐藏激活 |
| 2 个输出,各连接 3 个隐藏激活 |
| 每个输出一个偏置 |
| 每个样本得到 2 个输出分数 |
| 不改变分数 |
| 实值回归输出、分类 logits |
对隐藏层逐元素应用 ReLU。三个数都不小于 0,所以 。
计算输出 logit:。
最后应用 sigmoid,得到 。若业务阈值为 0.5,才会进一步把它转换成类别 1。
| 16 |
| 528 |
| 4 | 68 |
| 合计 | 2676 |