深度学习入门(一)——从神经元到损失函数,一步步理解前向传播(上)
本系列是为那些想“真正理解”深度学习的人写的。
不谈框架黑箱,不追求堆砌术语。
我们从最小的神经元开始,一步步走向真正可理解的深度学习。
一、前言:从“写规则”到“让机器自己学”
在传统编程的世界中,程序员的工作是——写规则。
比如,我们要写一个程序判断一封邮件是不是垃圾邮件。最直接的做法是:
if "中奖" in 邮件 or "转账" in 邮件:标记为垃圾邮件
但是,现实世界不是这样简单。
骗子的文案越来越像正常邮件,真正的客户通知又可能包含“转账”这类词汇。
于是我们不断加规则,反复调试,结果是:
系统越来越复杂、越来越脆弱。
这种“写规则”的方式有一个致命弱点:
程序只会做我们明确告诉它的事,却永远学不会自己判断。
于是,机器学习出现了。
它反过来问:“能不能不写规则,而是让机器自己从数据中总结规律?”
我们不再告诉它“中奖=垃圾”,而是给它上千封标注好的邮件,让它自己去学“哪些特征意味着垃圾”。
深度学习,正是这种思想的极致体现。
二、神经元:从数学角度复刻“思考”
1. 生物神经元的抽象
生物神经元接收来自其它神经元的电信号,当输入信号累积超过某个阈值时,它就“激活”,并把信号传递给下一个神经元。
人工神经元简化了这种行为,用数学表达:
输入: x₁, x₂, x₃, ...
权重: w₁, w₂, w₃, ...
偏置: b
输出: y = f(w₁x₁ + w₂x₂ + w₃x₃ + b)
这其实就是一个加权求和,再通过函数 f 进行变换的过程。
这里的 f 就是我们常说的“激活函数”。
2. 权重与偏置的意义
权重 w:表示输入的重要程度。
如果一个输入特征对结果影响大,它对应的权重会被学得更大。偏置 b:表示整体输出的“基础水平”。
它帮助模型在所有输入为 0 时,也能有一个非零输出。
例如,在判断天气是否炎热的简单模型中:
y = f(0.7*温度 + 0.1*湿度 - 0.3*风速 + 0.2)
温度的权重最高,说明它最关键。
而偏置 0.2,代表基础上就有一点“热”的倾向。
3. 激活函数:为什么需要“非线性”
假设没有激活函数:
y = w₁x₁ + w₂x₂ + b
多个神经元堆叠在一起,也只是一次线性变换的组合。
线性叠加仍然是线性,这意味着无论堆多深,模型本质上都只能画出一条直线的决策边界。
而真实世界是非线性的。
图像分类、语音识别、自然语言理解,背后都是高度非线性的映射关系。
要想让神经网络学到这种复杂关系,必须引入非线性函数。
常见的激活函数:
名称 | 公式 | 优点 | 缺点 |
---|---|---|---|
Sigmoid | 1 / (1 + e^(-x)) | 平滑、可微 | 梯度消失 |
Tanh | (e^x - e^(-x)) / (e^x + e^(-x)) | 对称、收敛快 | 仍有梯度问题 |
ReLU | max(0, x) | 简单高效 | 神经元“死亡”问题 |
Leaky ReLU | x if x>0 else 0.01x | 改进ReLU死区 | 参数敏感 |
GELU | x * Φ(x) | 更平滑、现代网络常用 | 计算复杂 |
ReLU 的成功在于简单:只保留正数。
它让网络的非线性更明显,也加快了训练。
三、神经网络的结构与计算流
一个简单的神经网络可以这样看:
输入层 → 隐藏层 → 输出层
输入层接收数据,隐藏层进行特征变换,输出层给出预测结果。
在数学上,我们把前向传播描述为:
for each layer L:z[L] = W[L] * a[L-1] + b[L]a[L] = f(z[L])
这里:
a[0]
是输入;W[L]
是第 L 层权重;b[L]
是偏置;f
是激活函数;a[L]
是这一层的输出。
1. 举个例子
假设我们有一个简单的神经网络:
输入层:2个神经元
隐藏层:2个神经元
输出层:1个神经元
伪代码如下:
输入: X = [x1, x2]
权重:W1 = [[0.2, 0.4],[0.3, -0.5]]b1 = [0.1, -0.2]W2 = [0.7, 0.6]b2 = 0.05隐藏层:z1 = W1 * X + b1a1 = ReLU(z1)输出层:z2 = W2 * a1 + b2a2 = Sigmoid(z2)
如果 X = [1.0, 0.5],则:
z1 = [0.2*1 + 0.4*0.5 + 0.1, 0.3*1 - 0.5*0.5 - 0.2]= [0.5, -0.15]
a1 = ReLU(z1) = [0.5, 0]
z2 = 0.7*0.5 + 0.6*0 + 0.05 = 0.4
a2 = Sigmoid(0.4) ≈ 0.598
输出 0.598 就是模型预测的结果,比如“这张图片属于猫的概率”。
2. 计算的本质
每一层的计算都是:
加权求和 → 加偏置 → 激活变换
这种结构很简单,却非常强大。
因为每一层都能提取更“抽象”的特征。
举例:
第一层可能学到“边缘”;
第二层学到“形状”;
第三层学到“物体类别”。
深度,就是特征抽象的层次。
四、损失函数:让模型“知道自己错了”
有了输出,还需要知道输出对不对。
这就是损失函数(Loss Function)的作用。
损失函数衡量预测值与真实值的差距。
模型训练的目标,就是最小化这个损失。
1. 回归任务:均方误差
L = (1/n) * Σ(y_pred - y_true)²
它表示预测偏差的平方平均。
预测得越接近真实值,损失越小。
但在分类问题中,MSE 并不好用,因为概率输出和离散标签之间的差异无法正确反映“分类好坏”。
2. 分类任务:交叉熵
交叉熵本质上衡量两个分布的差异:
一个是模型预测的概率分布 p(y_pred)
,一个是实际标签的分布 p(y_true)
。
L = -Σ y_true * log(y_pred)
举个例子:
真实标签:[1, 0, 0]
预测输出:[0.7, 0.2, 0.1]
L = - (1*log(0.7) + 0 + 0) = -log(0.7)
如果模型预测得更差,如 [0.4, 0.3, 0.3],损失就会变成 -log(0.4),更大。
这促使模型调整权重,使正确类别的概率更高。
3. Softmax 的作用
多分类时,输出层一般使用 Softmax:
Softmax(x_i) = exp(x_i) / Σ exp(x_j)
它把任意实数向量映射成一个概率分布。
但在实现时,exp(x_i)
可能会溢出。
因此,稳定的实现是:
Softmax(x_i) = exp(x_i - max(x)) / Σ exp(x_j - max(x))
这样不会影响结果,却避免了数值问题。
五、前向传播的完整伪代码实现
# 初始化参数
for layer in range(1, L+1):W[layer] = small_random()b[layer] = zeros()# 前向传播函数
function forward(X):a[0] = Xfor L in 1...L_max:z[L] = W[L] * a[L-1] + b[L]a[L] = activation(z[L])return a[L_max]# 损失函数
function cross_entropy(y_pred, y_true):return -sum(y_true * log(y_pred))
可以看到,这个流程没有魔法,只有一连串简单的矩阵乘法与非线性变换。
六、几个常见的坑
没有激活函数
网络退化成线性映射,等价于一个线性回归。
激活函数选错
用 Sigmoid 在深层网络中会导致梯度几乎为 0,训练停滞。
损失函数选错
用 MSE 处理分类问题会让梯度方向混乱,收敛极慢。
忽略数值稳定
没减去 max(x) 的 Softmax,容易溢出成 NaN。
参数初始化不当
全部初始化为 0,会让所有神经元输出相同,永远学不会东西。
七、直觉理解:神经网络是在逼近函数
很多人初学时问:“神经网络到底在干什么?”
最本质的答案是——它在逼近一个未知函数 f(x)
。
我们假设有一条从输入到输出的真函数,但我们不知道它长什么样。
神经网络通过调整权重参数 W, b
,不断逼近这个函数。
最终,网络会学到一种近似关系:
输入 → (一系列变换)→ 输出
也就是说:
神经网络不是在“记住”数据,而是在“学习数据的模式”。
八、回到初衷:学习的意义
当我们说“神经网络在学习”,其实它并不是真的有意识。
它只是在不断最小化损失函数,使预测越来越接近真值。
但这个过程,本质上就是“试错—调整—再试”的循环。
这正是人类学习的缩影。
也正因如此,深度学习成为了当代最有生命力的算法体系之一。
九、小结与过渡
这一篇我们完成了“深度学习的第一步”:
理解了神经元的数学模型;
掌握了前向传播的逻辑;
知道了激活函数和损失函数的作用;
用伪代码看清了整个计算流。
但我们还没有学会“如何让模型调整自己”。
换句话说,现在的神经网络,只会算,不会改。
下一步,我们将正式进入深度学习的核心机制——反向传播(Backpropagation)。