第13章:神经网络基础 - 感知机到多层网络

说起来,我们在前面12章里,学了传统的机器学习算法------线性回归、逻辑回归、决策树、随机森林、支持向量机、K-Means聚类,甚至还做了两个完整的实战项目。这些算法都是上世纪50年代到90年代发展起来的,虽然经典,但在处理复杂任务时,确实有点力不从心了。

现在,我们要进入深度学习的世界了。

深度学习不是什么玄学,它本质上就是神经网络,而且是多层神经网络。但为什么要搞多层?一层不够吗?这得从神经网络的鼻祖------感知机说起。

从生物神经元到人工神经元

1943年,心理学家Warren McCulloch和数学家Walter Pitts提出了第一个神经元模型,后来被称为M-P神经元模型。这个模型很简单:神经元接收多个输入,每个输入都有一个权重,神经元对所有加权输入求和,然后通过一个激活函数决定是否"兴奋"。

这个想法其实挺自然的,因为它模仿了人类大脑的工作方式。人脑里有大约860亿个神经元,每个神经元通过突触连接到成千上万个其他神经元。当外界刺激(光、声、触觉等)作用于我们的感官时,神经元会接收这些信号,如果信号强度超过某个阈值,神经元就会"兴奋",并向下游神经元发送电信号。

1957年,康奈尔大学的心理学家Frank Rosenblatt发明了感知机(Perceptron),这是第一个可以学习的人工神经网络模型。感知机就是一个简单的二分类器:输入几个特征,对每个特征加权求和,然后根据结果的正负来分类。

说真的,这个模型简单到让人怀疑它能不能解决问题。但事实是,在某些任务上,它确实能工作,而且工作得还不错。

感知机:最简单的神经网络

感知机的工作原理可以用一个数学公式来表达:

y = f ( ∑ i = 1 n w i x i + b ) y = f(\sum_{i=1}^{n} w_i x_i + b) y=f(i=1∑nwixi+b)

其中:

  • x i x_i xi是输入特征
  • w i w_i wi是对应的权重
  • b b b是偏置
  • f f f是激活函数

如果用代码来实现,一个感知机就是这样:

python 复制代码
import numpy as np

class Perceptron:
    def __init__(self, n_features):
        # 初始化权重和偏置
        self.weights = np.random.randn(n_features)
        self.bias = np.random.randn()
    
    def forward(self, X):
        # 前向传播
        z = np.dot(X, self.weights) + self.bias
        return self._step_function(z)
    
    def _step_function(self, z):
        # 阶跃激活函数
        return np.where(z >= 0, 1, 0)
    
    def predict(self, X):
        return self.forward(X)

这个感知机可以做简单的二分类任务。比如,我们可以用它来模拟"AND"逻辑:只有当两个输入都是1时,输出才为1。

python 复制代码
# 模拟AND逻辑
perceptron = Perceptron(n_features=2)

# 训练数据(手动设置权重和偏置)
perceptron.weights = np.array([1, 1])
perceptron.bias = -1.5

# 测试
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
predictions = perceptron.predict(X)

print("AND逻辑测试:")
for x, y_pred in zip(X, predictions):
    print(f"输入: {x}, 输出: {y_pred}")

这看起来很简单,对吧?但问题是,感知机有一个致命的缺陷:它只能解决线性可分问题。

感知机的致命缺陷:线性可分性

什么意思呢?如果我们在二维平面上画点,能用一条直线把两类点分开,这就是线性可分问题。感知机可以学习这条直线的位置,然后根据点在直线的哪一边来分类。

但如果不能用一条直线分开呢?比如"XOR"逻辑:当两个输入相同时输出0,不同时输出1。这是一个经典的非线性可分问题。

python 复制代码
# 测试XOR逻辑
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])

perceptron = Perceptron(n_features=2)
predictions = perceptron.predict(X)

print("XOR逻辑测试:")
print(f"真实标签: {y}")
print(f"预测结果: {predictions}")

你会发现,无论怎么调整权重和偏置,感知机都无法正确分类XOR问题。这是一个巨大的打击,因为在1969年,AI领域的泰斗Marvin Minsky和Seymour Papert在《Perceptrons》一书中,从数学上证明了单层感知机的局限性。

这本书的出版直接导致了AI的第一个寒冬。研究者们意识到,简单的感知机解决不了复杂问题,但当时的技术又无法构建更复杂的网络,所以很多人放弃了AI研究。

这一停,就是将近20年。

多层感知机:突破线性限制

20世纪80年代,AI研究者们找到了突破感知机局限性的方法:多层感知机(Multi-Layer Perceptron,MLP)。核心思想很简单,但很巧妙:既然一层感知机解决不了问题,那就用多层。

多层感知机包含三种类型的层:

  • 输入层:接收原始数据
  • 隐藏层:进行特征提取和变换
  • 输出层:输出最终结果

每个神经元都与上一层的所有神经元连接,这种结构叫做全连接层。

但这里有个问题:如果所有的激活函数都是线性的,那么多层网络就等价于单层网络。因为线性变换的组合仍然是线性变换。比如,两层线性网络: y = W 2 ( W 1 x + b 1 ) + b 2 y = W_2(W_1 x + b_1) + b_2 y=W2(W1x+b1)+b2,可以简化为 y = ( W 2 W 1 ) x + ( W 2 b 1 + b 2 ) y = (W_2 W_1)x + (W_2 b_1 + b_2) y=(W2W1)x+(W2b1+b2),这还是个线性变换。

所以,我们需要非线性激活函数。

非线性激活函数:网络的灵魂

非线性激活函数是神经网络能够学习复杂模式的关键。没有它们,多层网络就失去了意义。

常用的非线性激活函数包括:

Sigmoid函数

Sigmoid函数将输入压缩到(0, 1)区间,历史上曾被广泛使用:

σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1

python 复制代码
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

Sigmoid函数的输出可以理解为概率,所以在早期的神经网络中被广泛使用。但它有一个致命缺陷:梯度消失问题。当输入非常大或非常小时,Sigmoid的导数趋近于0,导致梯度无法有效传播。

Tanh函数

Tanh函数将输入压缩到(-1, 1)区间:

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

python 复制代码
def tanh(x):
    return np.tanh(x)

Tanh函数的输出是以0为中心的,这在某些情况下比Sigmoid更好。但它同样存在梯度消失问题。

ReLU函数

ReLU(Rectified Linear Unit)是目前最流行的激活函数,它非常简单:

R e L U ( x ) = max ⁡ ( 0 , x ) ReLU(x) = \max(0, x) ReLU(x)=max(0,x)

python 复制代码
def relu(x):
    return np.maximum(0, x)

ReLU函数解决了梯度消失问题,因为当输入为正时,梯度恒为1。这使得深度网络能够被有效训练。这也是为什么深度学习在2010年后迅速发展的关键原因之一。

前向传播:数据如何在网络中流动

在神经网络中,数据从输入层流向输出层,这个过程叫做前向传播。我们用一个简单的例子来说明:

假设有一个三层网络:输入层有2个神经元,隐藏层有3个神经元,输出层有1个神经元。

python 复制代码
class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        # 初始化权重和偏置
        self.W1 = np.random.randn(input_size, hidden_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size)
        self.b2 = np.zeros((1, output_size))
    
    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))
    
    def forward(self, X):
        # 输入层到隐藏层
        self.z1 = np.dot(X, self.W1) + self.b1
        self.a1 = self.sigmoid(self.z1)
        
        # 隐藏层到输出层
        self.z2 = np.dot(self.a1, self.W2) + self.b2
        self.a2 = self.sigmoid(self.z2)
        
        return self.a2

前向传播的过程就是:

  1. 输入X乘以权重W1,加上偏置b1,得到z1
  2. 对z1应用激活函数,得到a1
  3. a1乘以权重W2,加上偏置b2,得到z2
  4. 对z2应用激活函数,得到输出a2

这个过程可以重复任意次,形成任意深度的网络。

损失函数:如何衡量网络的表现

网络输出之后,我们需要评估它的表现。对于二分类问题,我们通常使用交叉熵损失函数:

L = − y log ⁡ ( y ^ ) − ( 1 − y ) log ⁡ ( 1 − y ^ ) L = -y \log(\hat{y}) - (1 - y) \log(1 - \hat{y}) L=−ylog(y^)−(1−y)log(1−y^)

其中y是真实标签(0或1), y ^ \hat{y} y^是网络输出的概率。

python 复制代码
def binary_cross_entropy(y_true, y_pred):
    epsilon = 1e-15  # 防止log(0)
    y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
    return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

损失函数的值越小,说明网络的预测越准确。训练网络的目标就是最小化这个损失函数。

反向传播:网络如何学习

现在我们有了前向传播和损失函数,但问题来了:如何调整网络的权重和偏置,使得损失最小?

这就需要反向传播算法。反向传播是神经网络训练的核心,它基于链式法则,从输出层向输入层反向计算梯度。

假设我们的网络只有一层,损失函数 L L L关于权重 w w w的梯度是:

∂ L ∂ w = ∂ L ∂ y ^ ⋅ ∂ y ^ ∂ z ⋅ ∂ z ∂ w \frac{\partial L}{\partial w} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z} \cdot \frac{\partial z}{\partial w} ∂w∂L=∂y^∂L⋅∂z∂y^⋅∂w∂z

根据链式法则,我们把它分解成三个部分:

  1. 损失关于输出的导数
  2. 输出关于激活值的导数
  3. 激活值关于权重的导数

这个过程可以递归地应用到每一层,从输出层一直反向传播到输入层。

梯度下降:更新网络参数

有了梯度之后,我们就可以用梯度下降法来更新权重:

w n e w = w o l d − η ⋅ ∂ L ∂ w w_{new} = w_{old} - \eta \cdot \frac{\partial L}{\partial w} wnew=wold−η⋅∂w∂L

其中 η \eta η是学习率,控制每次更新的步长。

python 复制代码
def update_weights(self, X, y, learning_rate):
    # 前向传播
    y_pred = self.forward(X)
    
    # 计算损失
    loss = binary_cross_entropy(y, y_pred)
    
    # 反向传播
    # 输出层梯度
    m = X.shape[0]
    dz2 = y_pred - y
    dW2 = (1 / m) * np.dot(self.a1.T, dz2)
    db2 = (1 / m) * np.sum(dz2, axis=0, keepdims=True)
    
    # 隐藏层梯度
    da1 = np.dot(dz2, self.W2.T)
    dz1 = da1 * self.a1 * (1 - self.a1)  # Sigmoid的导数
    dW1 = (1 / m) * np.dot(X.T, dz1)
    db1 = (1 / m) * np.sum(dz1, axis=0, keepdims=True)
    
    # 更新权重
    self.W1 -= learning_rate * dW1
    self.b1 -= learning_rate * db1
    self.W2 -= learning_rate * dW2
    self.b2 -= learning_rate * db2
    
    return loss

这个过程不断重复,直到损失收敛或者达到预设的迭代次数。

训练一个完整的神经网络

现在我们把所有部分组合起来,训练一个简单的神经网络来分类XOR问题:

python 复制代码
# 训练数据
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])

# 创建网络
nn = NeuralNetwork(input_size=2, hidden_size=4, output_size=1)

# 训练
losses = []
learning_rate = 0.1
epochs = 10000

for epoch in range(epochs):
    loss = nn.update_weights(X, y, learning_rate)
    losses.append(loss)
    
    if (epoch + 1) % 1000 == 0:
        print(f"Epoch {epoch + 1}/{epochs}, Loss: {loss:.6f}")

# 预测
predictions = nn.forward(X)
print(f"\n预测结果:")
print(f"真实: {y.flatten()}")
print(f"预测: {predictions.flatten().round()}")

你会发现,这个网络能够正确分类XOR问题!这是单层感知机做不到的。

从简单到复杂:网络的设计哲学

神经网络的强大之处在于它的灵活性。你可以随意调整网络的结构:增加层数、改变每层的神经元数量、选择不同的激活函数、设计不同的连接方式。

但这也带来了一个问题:如何设计一个"好"的网络?

这个问题没有标准答案,但有一些经验法则:

  1. 从简单开始:先尝试简单的网络,如果效果不好,再增加复杂度
  2. 经验法则:隐藏层的神经元数量通常在输入层和输出层之间
  3. 深度优先:增加层数通常比增加每层的神经元数量更有效
  4. 任务相关:不同的任务需要不同的网络结构

神经网络的"黑盒"问题

神经网络的一个主要批评是它的不可解释性。你知道它输入什么、输出什么,但很难知道中间发生了什么。每个神经元的权重都代表什么?隐藏层提取了什么特征?

这确实是个问题,但在实际应用中,我们并不总是需要知道网络内部的工作原理。只要它在测试集上表现良好,我们就可以信任它。

而且,研究者们也在发展各种可视化技术,试图理解网络学到了什么。

从理论到实践:为什么深度学习现在火了?

神经网络的概念并不新,多层感知机在1980年代就已经存在。但为什么深度学习直到2010年后才开始爆发?

我认为主要有三个原因:

  1. 计算能力的提升:GPU的出现使得并行计算成为可能,大大加速了神经网络的训练
  2. 大数据的可用性:互联网时代产生了海量数据,为训练深度网络提供了可能
  3. 算法的改进:ReLU激活函数、批量归一化、残差连接等技术的发明,使得训练深度网络变得更加容易

这三个因素共同推动了深度学习的发展。

本章小结

这一章我们学习了神经网络的基础知识,从最简单的感知机到多层感知机,从线性分类到非线性分类。核心概念包括:

  1. 感知机:最简单的神经网络,但只能解决线性可分问题
  2. 多层感知机:通过多层结构和非线性激活函数,突破线性限制
  3. 激活函数:网络的灵魂,引入非线性是关键
  4. 前向传播:数据从输入层流向输出层
  5. 损失函数:衡量网络表现的指标
  6. 反向传播:计算梯度、更新权重的核心算法
  7. 梯度下降:优化网络参数的方法

神经网络之所以强大,是因为它的通用性。理论上,只要网络足够大、训练足够久,它可以逼近任何连续函数。这就是所谓的"万能逼近定理"。

下一章,我们将深入学习各种激活函数的特性,以及为什么选择合适的激活函数对网络性能如此重要。

相关推荐
秦ぅ时1 小时前
【OpenAI】flux-kontext-pro深度解析:功能、原理与应用指南获取OpenAI API KEY的两种方式,开发者必看全方面教程!
人工智能·自动化
Allen_LVyingbo1 小时前
医疗AI新范式:当数理模型开始“计算”生命,传统大模型面临重构(中)
开发语言·人工智能·python·自然语言处理·重构·知识图谱
JQLvopkk2 小时前
C# 实践AI 编码:Visual Studio + VSCode 组合方案
人工智能·c#·visual studio
&星痕&2 小时前
人工智能:深度学习:1.pytorch概述(1)
人工智能·深度学习
新缸中之脑2 小时前
基于PageIndex的文档问答
人工智能
普通网友2 小时前
解决rfid系统安全的逻辑方法
人工智能
七夜zippoe2 小时前
时间序列分析实战:从平稳性检验到Prophet与LSTM预测
人工智能·python·机器学习·arima·时间序列·prophet
OpenLoong 开源社区2 小时前
合作官宣 | 技术协同新标杆!openKylin 适配具身智能人形机器人计划正式启动
人工智能·机器人·开源
说私域2 小时前
开源AI智能名片链动2+1模式S2B2C商城小程序驱动下的电商裂变增长路径研究
人工智能·小程序·开源·流量运营·私域运营