神经网络基础-从感知机到多层网络
神经网络是深度学习的基石,也是所有大语言模型的底层结构。这篇文章从最简单的单个神经元出发,逐步建立起对神经网络工作原理的完整认知——不只是"怎么用",更是"为什么这样设计"。
神经网络基础:从感知机到多层网络
神经网络是深度学习的基石,也是所有大语言模型的底层结构。这篇文章从最简单的单个神经元出发,逐步建立起对神经网络工作原理的完整认知——不只是"怎么用",更是"为什么这样设计"。
1.1 一、为什么需要神经网络
从感知机到现代深度神经网络的历史演进(1958—2012至今)
1.1.1 从规则到学习
先想一个问题:怎么让计算机识别一张猫的图片?
最直觉的方法是:人工总结规则。比如"有尖耳朵"、"有胡须"、"眼睛是椭圆形的"……然后把这些规则写成代码,让计算机逐条检查。
这就是早期AI的主流思路,叫专家系统(Expert System)。1980年代非常流行,IBM、MIT都投入了大量资源。
但这个方法有一个致命的问题:规则太难写了。
猫有多少种姿势?多少种光线条件?多少种品种?被遮挡了一半怎么办?人类专家写了几十年规则,识别准确率还是远不如人眼。更糟糕的是,每换一个任务(识别狗、识别车牌),就要重新从头写一套规则。
核心矛盾:人类能轻松做到的事(看一眼就知道是猫),却极难用明确的规则描述出来。
神经网络提出了一个根本不同的思路:不要人工写规则,让机器从数据中自己学规则。
给网络看100万张猫的图片和100万张非猫的图片,告诉它哪些是猫、哪些不是。网络通过不断调整内部参数,自己摸索出"什么特征代表猫"——这个过程不需要人类告诉它"猫有尖耳朵",它会自己发现。
这个思路转变是革命性的:从"人工设计特征"到"机器自动学习特征"。
1.2 二、从生物神经元到人工神经元
1.2.1 生物启发
大脑处理信息的基本单元是神经元。一个神经元会收到来自其他神经元的信号,当这些信号的总强度超过某个阈值时,它就"激发",把信号传给下一个神经元;否则就保持沉默。
整个大脑就是数百亿个神经元通过突触连接成的巨大网络,不同的连接强度(权重)编码了我们学到的所有知识和技能。
1.2.2 投票决策的类比
在理解数学公式之前,先用一个更直觉的类比:
想象一个委员会投票决策。委员会里有N个委员(对应神经网络的N个输入),每位委员的意见有不同的权重(重要性不同)。最终决策不只是看"多少人赞成",而是看"加权后的总得分"。如果总得分超过某个阈值,就通过;否则就否决。
人工神经元做的正是这件事:
输入信号 → 加权求和 → 激活函数 → 输出
用公式表达:
输出 = 激活函数(w₁x₁ + w₂x₂ + ... + wₙxₙ + b)
其中:
x₁, x₂, ..., xₙ是输入(像素值、词编码、传感器数据等)w₁, w₂, ..., wₙ是权重(weight)——决定每个输入的重要程度b是偏置(bias)——相当于调整"激发阈值"的旋钮- 激活函数决定这个神经元如何响应输入(后面详细讲)
权重是神经网络的核心。判断一封邮件是否是垃圾邮件时,"包含'免费领取'"这个特征的权重应该很高,"发件人是老板"的权重应该很低甚至是负数。训练神经网络,本质上就是从数据中自动学习这些权重的值,让网络的判断越来越准确。
1.3 三、感知机:第一个会学习的神经元
1957年,Frank Rosenblatt提出感知机(Perceptron),这是第一个有完整学习算法的人工神经元模型。
感知机使用最简单的激活函数——阶跃函数:输入的加权和 ≥ 0 就输出1,否则输出0。
感知机的学习规则同样简单:
- 如果预测正确,权重不变
- 如果预测错误(预测0但真实是1),就增大对应特征的权重
- 如果预测错误(预测1但真实是0),就减小对应特征的权重
更新公式:新权重 = 旧权重 + 学习率 × (真实值 - 预测值) × 输入值
背后的直觉:哪个特征导致了错误,就调整那个特征的权重。
import numpy as np
class Perceptron:
def __init__(self, n_inputs, learning_rate=0.01):
self.weights = np.zeros(n_inputs) # 初始权重全为0
self.bias = 0
self.lr = learning_rate
def predict(self, x):
z = np.dot(self.weights, x) + self.bias # 加权求和
return 1 if z >= 0 else 0 # 阶跃函数
def train(self, X, y, epochs=100):
for epoch in range(epochs):
errors = 0
for xi, yi in zip(X, y):
prediction = self.predict(xi)
error = yi - prediction
self.weights += self.lr * error * xi # 调整权重
self.bias += self.lr * error
errors += int(error != 0)
if errors == 0:
print(f"第 {epoch+1} 轮收敛,最终权重: {self.weights}, 偏置: {self.bias:.3f}")
break
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([0, 0, 0, 1]) # AND:只有 [1,1] 才输出1
p = Perceptron(n_inputs=2)
p.train(X, y)
# 输出类似:第 4 轮收敛,最终权重: [0.01 0.01], 偏置: -0.010
感知机经过几轮训练就收敛了。它学到的权重两个都是正数(两个输入都重要),偏置是负数(需要两个输入同时为1才能超过阈值)——这符合AND逻辑的直觉。
1.3.1 感知机的根本局限:只能画一条直线
感知机能学会AND,是因为AND问题在二维平面上可以用一条直线分开。但XOR问题(异或:两个输入不同时才输出1)就不行了:
输入 (0,0) → 输出 0 输入 (1,1) → 输出 0
输入 (0,1) → 输出 1 输入 (1,0) → 输出 1
在平面上,输出为0的两个点在对角线上,输出为1的两个点在另一条对角线上——无论怎么画直线,都没法把它们正确分开。
这个缺陷在1969年被数学严格证明,直接导致神经网络研究沉寂了将近20年。解决方案要等到多层网络的出现。
1.4 四、多层感知机:叠加层,突破线性限制
解决线性不可分问题的思路是:在输入层和输出层之间加入"隐藏层"(Hidden Layer)。
隐藏层的作用是对输入做非线性变换——把原始空间"扭曲变形",让在原始空间里线性不可分的问题,在变换后的空间里变成线性可分的。
输入层(2个神经元)→ 隐藏层(N个神经元,做空间变换)→ 输出层(1个神经元,做线性分类)
这就是多层感知机(MLP,Multi-Layer Perceptron)。"深度学习"中的"深度"就来源于此——网络有多个隐藏层,层数越多(越"深"),能学习的特征越复杂。
关键问题:为什么多层就能解决非线性问题?
如果每层都是纯线性变换,那无论叠多少层,整体仍然是线性变换:
第二层输出 = W₂ × (W₁ × 输入 + b₁) + b₂ = W₂W₁ × 输入 + W₂b₁ + b₂ = W' × 输入 + b'
还是线性变换!只有在每层之间加入非线性的激活函数,多层网络才真正有意义。
1.4.1 前向传播:数据如何流过网络
数据从输入层进入,逐层向前计算,直到输出层给出预测结果——这个过程叫前向传播(Forward Propagation)。
每一层做的事情:
- 把上一层的输出乘以本层的权重矩阵,加上偏置
- 通过激活函数做非线性变换
- 把结果传给下一层
import torch
import torch.nn as nn
# 用PyTorch定义一个两层MLP
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(2, 4), # 第一层:2个输入 → 4个隐藏神经元
nn.ReLU(), # 激活函数(引入非线性)
nn.Linear(4, 1), # 第二层:4个隐藏神经元 → 1个输出
nn.Sigmoid() # 输出层激活:把结果压缩到(0,1),解释为概率
)
def forward(self, x):
return self.layers(x) # 前向传播:数据从左到右流过每一层
model = MLP()
x = torch.FloatTensor([[0, 1]]) # 输入
print(model(x)) # 输出一个(0,1)之间的概率值
nn.Module 对Java工程师的类比:
nn.Module相当于Java的抽象类,forward()是必须实现的抽象方法,框架在训练和推理时会自动调用它。
1.5 五、激活函数:为什么需要非线性
激活函数是神经网络能处理复杂问题的关键。没有激活函数,多层网络等价于单层网络。
不同激活函数有不同的特性,选错了会导致训练失败:
1.5.1 为什么需要激活函数?
直觉:神经网络需要学习"如果A和B同时出现才判断为猫"这种非线性规则。纯线性变换只能学到"A越强、B越强,越可能是猫"这种线性规则。激活函数让每个神经元可以有不同的"响应模式",从而让网络能学习非线性规律。
1.5.2 常用激活函数对比
Sigmoid:把任意输入压缩到(0,1)区间,输出可解释为概率。
致命问题——梯度消失:当输入非常大或非常小时,Sigmoid的导数接近于0。反向传播时,梯度经过每个Sigmoid层都会被压缩一次。层数一多,传到前面几层的梯度几乎为零,权重无法更新,网络学不动了。这是早期深层网络训练困难的主要原因。
ReLU(修正线性单元):f(x) = max(0, x)。
输入为正就原样输出,输入为负就输出0。这个简单的设计解决了梯度消失问题——正区间的梯度恒为1,不会被压缩。ReLU的出现是深度学习复兴的重要推手,目前是隐藏层的默认选择。
代价是可能出现"死亡ReLU"(Dead ReLU):某些神经元的输入一直是负数,梯度一直为0,永远无法更新——相当于死掉了。
GELU(高斯误差线性单元):ReLU的平滑版本,在Transformer系列模型(BERT、GPT)中广泛使用。
Softmax:把多个输出值转换为概率分布(所有值加起来等于1),专门用于多分类任务的输出层。
| 激活函数 | 输出范围 | 主要用途 | 核心问题 |
|---|---|---|---|
| Sigmoid | (0, 1) | 二分类输出层 | 梯度消失,深层网络不适用 |
| Tanh | (-1, 1) | RNN隐藏层 | 同样有梯度消失问题 |
| ReLU | [0, +∞) | 隐藏层(最常用) | 可能出现死亡ReLU |
| GELU | ≈ReLU | Transformer隐藏层 | 计算略复杂 |
| Softmax | (0,1),和为1 | 多分类输出层 | — |
1.6 六、反向传播:网络如何从错误中学习
1.6.1 损失函数:量化"错了多少"
**损失函数(Loss Function)**把"预测值和真实值的差距"量化成一个数字。训练的目标就是让这个数字尽可能小。
常见损失函数:
- 均方误差(MSE):
(预测值 - 真实值)²的平均,用于回归问题 - 交叉熵(Cross Entropy):衡量两个概率分布的差异,用于分类问题
1.6.2 梯度下降:沿着下坡方向走
想象你站在一座山上,目标是走到山谷(损失最小的地方)。你看不到全局地图,但可以感受到脚下的坡度——梯度告诉你当前位置哪个方向是上坡,往反方向走就是下坡。
梯度下降的核心逻辑:
- 计算当前损失对每个权重的梯度(偏导数)——"调整这个权重,损失会怎么变化"
- 沿梯度的反方向更新权重——"往让损失减小的方向调"
- 重复,直到损失收敛
1.6.3 反向传播:高效计算梯度
网络有成千上万个权重,怎么高效计算每个权重的梯度?
**反向传播(Backpropagation)**利用链式法则,从输出层开始,逐层向后计算梯度:
输出层误差 → 倒数第二层的梯度 → 倒数第三层的梯度 → ... → 第一层的梯度
每层的梯度都可以由后一层的梯度推导出来,避免了重复计算。
PyTorch的loss.backward()就是在做这件事——自动计算所有权重的梯度,存储在每个参数的.grad属性里:
# 训练的三步节奏(PyTorch标准写法)
optimizer.zero_grad() # ① 清空上一步的梯度(PyTorch默认累加,必须手动清空)
loss.backward() # ② 反向传播:计算所有权重的梯度
optimizer.step() # ③ 梯度下降:按梯度更新权重
这三行代码是所有深度学习训练代码的核心节奏,后续章节会反复见到。
1.7 七、让训练更稳定的两个技巧
1.7.1 批归一化(Batch Normalization,BN)
训练过程中,随着前面层的权重不断变化,后面层的输入分布也在不断变化——每层都要适应一个移动的目标,训练不稳定、速度慢。这个现象叫内部协变量偏移。
BN的解决方案:在每层之后,把这一批数据的输出归一化到均值为0、方差为1,然后通过可学习的参数做缩放和平移。这样每层的输入分布保持稳定,训练更快更稳。
1.7.2 Dropout
过拟合(Overfitting):模型把训练数据"背下来"了,在训练集上表现很好,但换一批新数据就差很多——就像只会背例题、不会举一反三。
Dropout的思路:训练时随机把一部分神经元(通常20%~50%)"关掉",强迫网络不能过度依赖任何单个神经元,必须学到更分散、更鲁棒的特征表示。推理时所有神经元都开着。
这个看似粗暴的方法效果出奇地好,是防止过拟合最常用的手段之一。
小结
| 概念 | 核心思想 | 解决了什么问题 |
|---|---|---|
| 神经元 | 加权求和 + 激活函数(投票决策) | 模拟生物神经元的决策逻辑 |
| 感知机 | 最简单的学习算法 | 线性分类问题 |
| 多层感知机 | 隐藏层做非线性空间变换 | 线性不可分问题(XOR等) |
| 前向传播 | 数据逐层流过网络 | 从输入得到预测结果 |
| 损失函数 | 量化预测误差 | 给"学得好不好"一个数字 |
| 梯度下降 | 沿损失下降方向更新权重 | 让网络越来越准确 |
| 反向传播 | 链式法则高效计算梯度 | 让深层网络可以实际训练 |
| 激活函数(ReLU) | 引入非线性,缓解梯度消失 | 深层网络训练困难 |
| 批归一化 | 稳定每层输入分布 | 训练不稳定、速度慢 |
| Dropout | 训练时随机关闭神经元 | 过拟合 |
这篇建立的是神经网络的完整概念框架。下一篇PyTorch入门会把这些概念落地成可运行的代码,包括完整的训练循环怎么写、自动求导怎么用。
后续应用:本文的神经网络结构是理解后续所有内容的基础:
- PyTorch入门:把这些概念用代码跑起来
- CNN:在MLP基础上加入卷积层,专门处理图像
- RNN/LSTM:在MLP基础上加入循环结构,处理序列数据
- Transformer:用注意力机制替代循环结构,成为LLM的基础