玄同 765

大语言模型 (LLM) 开发工程师 | 中国传媒大学 · 数字媒体技术(智能交互与游戏设计)

CSDN · 个人主页 | GitHub · Follow


关于作者

  • 深耕领域:大语言模型开发 / RAG 知识库 / AI Agent 落地 / 模型微调
  • 技术栈:Python | RAG (LangChain / Dify + Milvus) | FastAPI + Docker
  • 工程能力:专注模型工程化部署、知识库构建与优化,擅长全流程解决方案

「让 AI 交互更智能,让技术落地更高效」
欢迎技术探讨与项目合作,解锁大模型与智能交互的无限可能!


从神经元到神经网络:深度学习的本质

这篇文章将带你从零开始理解神经网络。我们会从生物神经元讲起,一步步推导到人工神经网络,理解前向传播、反向传播的原理,最后用PyTorch实现你的第一个神经网络。

零、前置知识:从生物神经元到人工神经元

在深入神经网络之前,让我们先理解一下"神经元"这个概念从何而来。如果你已经了解生物神经元的基本结构,可以跳过这一节。

0.1 生物神经元是如何工作的?

人脑由约860亿个神经元组成。每个神经元都是一个微小的信息处理单元,它们通过电信号和化学信号相互通信。

一个生物神经元由三部分组成:

树突:接收来自其他神经元的信号,就像天线的输入端。

细胞体:整合所有输入信号,决定是否"激活"。当输入信号的总和超过某个阈值时,神经元会发放一个电脉冲。

轴突:将输出信号传递给其他神经元,就像输出导线。

输出信号

处理中心

输入信号

树突接收信号1

树突接收信号2

树突接收信号3

细胞体整合信号

信号总和超阈值

轴突传递脉冲

关键洞察:神经元的工作方式可以抽象为"加权求和 + 阈值判断"。这个简单的抽象,正是人工神经网络的理论基础。

0.2 人工神经元:用数学模拟生物

1943年,McCulloch和Pitts提出了第一个人工神经元模型。他们将生物神经元抽象为数学运算:

  1. 输入:来自其他神经元的信号,用 x 1 , x 2 , . . . , x n x_1, x_2, ..., x_n x1,x2,...,xn 表示
  2. 权重:每个输入的重要性,用 w 1 , w 2 , . . . , w n w_1, w_2, ..., w_n w1,w2,...,wn 表示
  3. 偏置:激活的门槛,用 b b b 表示
  4. 激活函数:决定是否激活,用 σ \sigma σ 表示

人工神经元的输出公式:

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

这个公式看起来简单,但它蕴含了深刻的智慧:通过调整权重 w w w 和偏置 b b b,神经元可以学习识别不同的模式


一、为什么需要神经网络?

在机器学习的世界里,我们已经有了线性回归、逻辑回归、决策树等算法。为什么还需要神经网络?

1.1 线性模型的局限

假设你要预测房价。线性回归假设房价与特征(面积、房间数、位置等)之间存在线性关系:

房价 = w 1 × 面积 + w 2 × 房间数 + b \text{房价} = w_1 \times \text{面积} + w_2 \times \text{房间数} + b 房价=w1×面积+w2×房间数+b

这个模型在简单情况下有效,但现实世界远比这复杂。房价与面积的关系可能是非线性的——面积太小或太大都不好,中等面积最受欢迎。这种"倒U型"关系,线性模型无法捕捉。

更复杂的情况是图像识别。一张28x28像素的手写数字图片有784个像素,每个像素都是0-255的灰度值。如果用线性模型,我们需要学习784个权重。但问题是:同一个数字在不同位置、不同大小、不同书写风格下,像素值会完全不同。线性模型无法处理这种复杂性。

1.2 神经网络的解决方案

神经网络的核心理念是:通过多层非线性变换,将原始特征转换为更抽象、更有表达力的特征

想象你要识别一张猫的图片。第一层可能学习识别边缘和纹理,第二层可能学习识别眼睛、耳朵等部件,第三层可能学习识别完整的猫。每一层都在前一层的基础上构建更高级的特征。

输出层

隐藏层2

隐藏层1

输入层

原始像素

边缘特征

部件特征

分类结果

这种"层层抽象"的方式,使得神经网络能够学习极其复杂的模式。这就是为什么神经网络在图像识别、语音识别、自然语言处理等领域取得了突破性进展。


二、神经网络的核心组件

2.1 激活函数:引入非线性

激活函数是神经网络的灵魂。没有激活函数,无论神经网络有多少层,它本质上还是一个线性模型。

为什么?因为多个线性变换的组合仍然是线性变换:

y = W 2 ( W 1 x + b 1 ) + b 2 = W 2 W 1 x + ( W 2 b 1 + b 2 ) y = W_2(W_1 x + b_1) + b_2 = W_2 W_1 x + (W_2 b_1 + b_2) y=W2(W1x+b1)+b2=W2W1x+(W2b1+b2)

这等价于一个线性变换 y = W ′ x + b ′ y = W' x + b' y=Wx+b。所以,非线性激活函数是神经网络能够学习复杂模式的关键

常用激活函数

ReLU(Rectified Linear Unit)

ReLU是目前最常用的激活函数。它的定义非常简单:

ReLU ( x ) = max ⁡ ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)

当输入为正时,输出等于输入;当输入为负时,输出为0。

ReLU的优势在于计算简单,且在正区间梯度恒为1,避免了梯度消失问题。但它有一个缺点:当输入为负时,梯度为0,神经元"死亡"不再更新。

Sigmoid

Sigmoid将输入压缩到0-1之间:

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

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+exexex

Tanh是Sigmoid的缩放版本,输出以0为中心,收敛速度通常比Sigmoid快。

Tanh函数

输出范围负1到1

输出以0为中心

Sigmoid函数

输出范围0到1

常用于二分类输出

ReLU函数

输入大于0输出输入值

输入小于等于0输出0

如何选择激活函数?

隐藏层:首选ReLU。如果出现大量神经元死亡,可以尝试Leaky ReLU或ELU。

输出层

  • 二分类:Sigmoid
  • 多分类:Softmax
  • 回归:线性激活(不使用激活函数)

2.2 前向传播:从输入到输出

前向传播是神经网络"推理"的过程:输入数据经过各层的计算,最终得到输出。

让我们用一个具体的例子来理解。假设有一个简单的三层神经网络:

  • 输入层:2个神经元
  • 隐藏层:3个神经元,使用ReLU激活
  • 输出层:1个神经元,使用Sigmoid激活

输出层

隐藏层

输入层

x1

x2

h1

h2

h3

y

前向传播的计算步骤

第1步:输入层到隐藏层
  z1 = W1 · x + b1     # 线性变换
  h = ReLU(z1)         # 激活函数

第2步:隐藏层到输出层
  z2 = W2 · h + b2     # 线性变换
  y = Sigmoid(z2)      # 激活函数

最终输出:y(预测值)

其中:

  • W 1 W_1 W1 是输入层到隐藏层的权重矩阵,形状为 (3, 2)
  • b 1 b_1 b1 是隐藏层的偏置向量,形状为 (3, 1)
  • W 2 W_2 W2 是隐藏层到输出层的权重矩阵,形状为 (1, 3)
  • b 2 b_2 b2 是输出层的偏置,形状为 (1, 1)

2.3 损失函数:衡量预测与真实的差距

损失函数衡量神经网络的预测值与真实值之间的差距。训练神经网络的目标就是最小化损失函数。

回归问题常用损失

均方误差(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=1n(yiy^i)2

分类问题常用损失

交叉熵损失(Cross-Entropy Loss):

L = − ∑ i = 1 n y i log ⁡ ( y ^ i ) L = -\sum_{i=1}^{n} y_i \log(\hat{y}_i) L=i=1nyilog(y^i)

对于二分类问题:

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

交叉熵损失的优势在于:当预测值与真实值差距很大时,梯度也很大,模型能够快速学习。


三、反向传播:神经网络如何学习

反向传播是神经网络训练的核心算法。它通过计算损失函数对每个参数的梯度,告诉我们应该如何调整参数才能减小损失。

3.1 反向传播的核心思想

反向传播基于一个简单的数学原理:链式法则

假设有一个复合函数 y = f ( g ( x ) ) y = f(g(x)) y=f(g(x)),那么:

d y d x = d y d g ⋅ d g d x \frac{dy}{dx} = \frac{dy}{dg} \cdot \frac{dg}{dx} dxdy=dgdydxdg

在神经网络中,输出是输入经过多层变换的结果:

y = f L ( f L − 1 ( . . . f 1 ( x ) . . . ) ) y = f_L(f_{L-1}(...f_1(x)...)) y=fL(fL1(...f1(x)...))

要计算损失 L L L 对第一层参数 W 1 W_1 W1 的梯度,我们需要沿着计算图反向传播:

∂ L ∂ W 1 = ∂ L ∂ y ⋅ ∂ y ∂ f L − 1 ⋅ . . . ⋅ ∂ f 1 ∂ W 1 \frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial f_{L-1}} \cdot ... \cdot \frac{\partial f_1}{\partial W_1} W1L=yLfL1y...W1f1

3.2 反向传播一步步图解

让我们用一个简单的两层网络来理解反向传播:

网络结构: x → h → y x \rightarrow h \rightarrow y xhy

前向传播:

  1. z 1 = W 1 x + b 1 z_1 = W_1 x + b_1 z1=W1x+b1
  2. h = ReLU ( z 1 ) h = \text{ReLU}(z_1) h=ReLU(z1)
  3. z 2 = W 2 h + b 2 z_2 = W_2 h + b_2 z2=W2h+b2
  4. y ^ = σ ( z 2 ) \hat{y} = \sigma(z_2) y^=σ(z2)
  5. L = − [ y log ⁡ ( y ^ ) + ( 1 − y ) log ⁡ ( 1 − y ^ ) ] L = -[y \log(\hat{y}) + (1-y)\log(1-\hat{y})] L=[ylog(y^)+(1y)log(1y^)]

反向传播

dL

dy_hat

dz2

dh

dz1

dx

前向传播

x

z1

h

z2

y_hat

L

反向传播步骤

第1步:计算损失对输出的梯度
  dL_dy_hat = -y/y_hat + (1-y)/(1-y_hat)

第2步:计算输出层激活前的梯度
  dL_dz2 = dL_dy_hat * sigmoid_derivative(z2)
         = dL_dy_hat * y_hat * (1 - y_hat)

第3步:计算输出层权重和偏置的梯度
  dL_dW2 = dL_dz2 · h.T
  dL_db2 = dL_dz2

第4步:计算隐藏层的梯度
  dL_dh = W2.T · dL_dz2

第5步:计算隐藏层激活前的梯度
  dL_dz1 = dL_dh * relu_derivative(z1)
         = dL_dh * (z1 > 0)

第6步:计算隐藏层权重和偏置的梯度
  dL_dW1 = dL_dz1 · x.T
  dL_db1 = dL_dz1

3.3 梯度下降:更新参数

计算出梯度后,我们使用梯度下降更新参数:

W = W − α ∂ L ∂ W W = W - \alpha \frac{\partial L}{\partial W} W=WαWL

b = b − α ∂ L ∂ b b = b - \alpha \frac{\partial L}{\partial b} b=bαbL

其中 α \alpha α 是学习率,控制每次更新的步长。学习率太大可能导致震荡,太小则收敛太慢。


四、用PyTorch实现第一个神经网络

现在让我们把理论付诸实践,用PyTorch实现一个完整的神经网络。

4.1 PyTorch核心概念

张量(Tensor)

张量是PyTorch的核心数据结构,类似于NumPy的数组,但支持GPU加速和自动求导。

import torch

# 创建张量
x = torch.tensor([1.0, 2.0, 3.0])
print(f"张量: {x}")
print(f"形状: {x.shape}")
print(f"数据类型: {x.dtype}")

# 张量运算
y = x * 2 + 1
print(f"运算结果: {y}")

# GPU加速
if torch.cuda.is_available():
    x_gpu = x.cuda()
    y_gpu = x_gpu * 2 + 1
    print(f"GPU运算结果: {y_gpu.cpu()}")

自动求导(Autograd)

PyTorch的Autograd模块能够自动计算梯度,这是反向传播的基础。

# 创建需要梯度的张量
x = torch.tensor([2.0], requires_grad=True)

# 定义计算过程
y = x ** 2
z = y + 3

# 反向传播计算梯度
z.backward()

# 查看梯度
print(f"dz/dx = {x.grad}")

4.2 定义神经网络模型

PyTorch提供了nn.Module类来定义神经网络。我们需要实现两个方法:

  • __init__:定义网络结构
  • forward:定义前向传播
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np


class SimpleNeuralNetwork(nn.Module):
    """
    简单的全连接神经网络
    
    用于二分类任务,包含一个隐藏层。
    
    网络结构:
        输入层 -> 隐藏层(ReLU) -> 输出层(Sigmoid)
    
    Attributes:
        hidden: 隐藏层线性变换
        output: 输出层线性变换
        relu: ReLU激活函数
        sigmoid: Sigmoid激活函数
    
    使用示例:
        model = SimpleNeuralNetwork(input_size=10, hidden_size=20)
        output = model(torch.randn(32, 10))
        print(output.shape)
    """
    
    def __init__(self, input_size: int, hidden_size: int):
        """
        初始化神经网络
        
        Args:
            input_size: 输入特征维度
            hidden_size: 隐藏层神经元数量
        """
        super().__init__()
        
        # 定义隐藏层:input_size -> hidden_size
        self.hidden = nn.Linear(input_size, hidden_size)
        
        # 定义输出层:hidden_size -> 1
        self.output = nn.Linear(hidden_size, 1)
        
        # 定义激活函数
        self.relu = nn.ReLU()
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        前向传播
        
        执行流程:
        1. 输入经过隐藏层线性变换
        2. 应用ReLU激活函数
        3. 经过输出层线性变换
        4. 应用Sigmoid激活函数得到概率
        
        Args:
            x: 输入张量,形状为 (batch_size, input_size)
            
        Returns:
            输出张量,形状为 (batch_size, 1),值为0-1之间的概率
        """
        # 隐藏层:线性变换 + ReLU激活
        hidden_out = self.hidden(x)
        hidden_activated = self.relu(hidden_out)
        
        # 输出层:线性变换 + Sigmoid激活
        output_out = self.output(hidden_activated)
        output_activated = self.sigmoid(output_out)
        
        return output_activated


class DeepNeuralNetwork(nn.Module):
    """
    深层神经网络
    
    支持任意数量的隐藏层,使用BatchNorm和Dropout提高训练稳定性。
    
    Attributes:
        layers: 神经网络层列表
        batch_norms: BatchNorm层列表
        dropouts: Dropout层列表
    
    使用示例:
        model = DeepNeuralNetwork(
            input_size=784,
            hidden_sizes=[256, 128, 64],
            output_size=10,
            dropout_rate=0.3
        )
        output = model(torch.randn(32, 784))
        print(output.shape)
    """
    
    def __init__(
        self,
        input_size: int,
        hidden_sizes: list,
        output_size: int,
        dropout_rate: float = 0.3
    ):
        """
        初始化深层神经网络
        
        Args:
            input_size: 输入特征维度
            hidden_sizes: 各隐藏层神经元数量列表
            output_size: 输出维度
            dropout_rate: Dropout概率,用于防止过拟合
        """
        super().__init__()
        
        self.layers = nn.ModuleList()
        self.batch_norms = nn.ModuleList()
        self.dropouts = nn.ModuleList()
        
        # 构建隐藏层
        prev_size = input_size
        for hidden_size in hidden_sizes:
            # 线性层
            self.layers.append(nn.Linear(prev_size, hidden_size))
            # BatchNorm层:加速训练,提高稳定性
            self.batch_norms.append(nn.BatchNorm1d(hidden_size))
            # Dropout层:随机丢弃神经元,防止过拟合
            self.dropouts.append(nn.Dropout(dropout_rate))
            prev_size = hidden_size
        
        # 输出层
        self.output_layer = nn.Linear(prev_size, output_size)
        
        # 激活函数
        self.relu = nn.ReLU()
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        前向传播
        
        执行流程:
        1. 依次通过各隐藏层
        2. 每个隐藏层:线性变换 -> BatchNorm -> ReLU -> Dropout
        3. 输出层:线性变换
        
        Args:
            x: 输入张量,形状为 (batch_size, input_size)
            
        Returns:
            输出张量,形状为 (batch_size, output_size)
        """
        # 通过各隐藏层
        for layer, bn, dropout in zip(self.layers, self.batch_norms, self.dropouts):
            x = layer(x)
            x = bn(x)
            x = self.relu(x)
            x = dropout(x)
        
        # 输出层(不加激活函数,后续用CrossEntropyLoss)
        x = self.output_layer(x)
        
        return x


def train_model(
    model: nn.Module,
    train_loader: DataLoader,
    val_loader: DataLoader,
    epochs: int = 100,
    learning_rate: float = 0.001,
    device: str = 'cpu'
) -> dict:
    """
    训练神经网络模型
    
    训练流程:
    1. 定义损失函数和优化器
    2. 遍历epoch进行训练
    3. 每个epoch后验证
    4. 记录训练和验证损失
    
    Args:
        model: 要训练的神经网络模型
        train_loader: 训练数据加载器
        val_loader: 验证数据加载器
        epochs: 训练轮数
        learning_rate: 学习率
        device: 计算设备
    
    Returns:
        包含训练历史的字典:
        - train_losses: 各epoch训练损失
        - val_losses: 各epoch验证损失
        - val_accuracies: 各epoch验证准确率
    
    使用示例:
        history = train_model(model, train_loader, val_loader, epochs=50)
        plt.plot(history['train_losses'], label='Train Loss')
        plt.plot(history['val_losses'], label='Val Loss')
        plt.legend()
        plt.show()
    """
    # 将模型移到指定设备
    model = model.to(device)
    
    # 定义损失函数
    # 对于二分类,使用BCELoss(二元交叉熵)
    criterion = nn.BCELoss()
    
    # 定义优化器
    # Adam是常用的自适应学习率优化器
    optimizer = optim.Adam(model.parameters(), lr=learning_rate)
    
    # 记录训练历史
    history = {
        'train_losses': [],
        'val_losses': [],
        'val_accuracies': []
    }
    
    # 训练循环
    for epoch in range(epochs):
        # 训练阶段
        model.train()
        train_loss = 0.0
        
        for batch_x, batch_y in train_loader:
            # 将数据移到指定设备
            batch_x = batch_x.to(device)
            batch_y = batch_y.to(device)
            
            # 清零梯度
            # PyTorch默认会累积梯度,需要手动清零
            optimizer.zero_grad()
            
            # 前向传播
            outputs = model(batch_x)
            
            # 计算损失
            # 需要将batch_y调整为与outputs相同的形状
            loss = criterion(outputs, batch_y.unsqueeze(1))
            
            # 反向传播
            # 计算所有参数的梯度
            loss.backward()
            
            # 参数更新
            # 根据梯度调整参数
            optimizer.step()
            
            # 累加训练损失
            train_loss += loss.item()
        
        # 计算平均训练损失
        avg_train_loss = train_loss / len(train_loader)
        history['train_losses'].append(avg_train_loss)
        
        # 验证阶段
        model.eval()
        val_loss = 0.0
        correct = 0
        total = 0
        
        # 不需要计算梯度,节省内存
        with torch.no_grad():
            for batch_x, batch_y in val_loader:
                batch_x = batch_x.to(device)
                batch_y = batch_y.to(device)
                
                # 前向传播
                outputs = model(batch_x)
                
                # 计算损失
                loss = criterion(outputs, batch_y.unsqueeze(1))
                val_loss += loss.item()
                
                # 计算准确率
                # 将概率转换为预测类别(大于0.5为1,否则为0)
                predicted = (outputs > 0.5).float()
                total += batch_y.size(0)
                correct += (predicted.squeeze() == batch_y).sum().item()
        
        # 计算平均验证损失和准确率
        avg_val_loss = val_loss / len(val_loader)
        val_accuracy = correct / total
        
        history['val_losses'].append(avg_val_loss)
        history['val_accuracies'].append(val_accuracy)
        
        # 每10个epoch打印一次
        if (epoch + 1) % 10 == 0:
            print(f"Epoch [{epoch+1}/{epochs}]")
            print(f"  Train Loss: {avg_train_loss:.4f}")
            print(f"  Val Loss: {avg_val_loss:.4f}")
            print(f"  Val Accuracy: {val_accuracy:.4f}")
    
    return history


def create_sample_data(n_samples: int = 1000, n_features: int = 10):
    """
    创建示例数据用于演示
    
    生成一个简单的二分类数据集。
    
    Args:
        n_samples: 样本数量
        n_features: 特征数量
    
    Returns:
        train_loader: 训练数据加载器
        val_loader: 验证数据加载器
    """
    # 生成随机特征
    X = np.random.randn(n_samples, n_features).astype(np.float32)
    
    # 生成标签:简单的线性决策边界
    # 真实权重
    true_weights = np.random.randn(n_features).astype(np.float32)
    # 真实偏置
    true_bias = 0.5
    
    # 计算线性组合
    linear_combination = np.dot(X, true_weights) + true_bias
    # 转换为概率
    probs = 1 / (1 + np.exp(-linear_combination))
    # 生成标签
    y = (probs > 0.5).astype(np.float32)
    
    # 划分训练集和验证集
    split_idx = int(0.8 * n_samples)
    X_train, X_val = X[:split_idx], X[split_idx:]
    y_train, y_val = y[:split_idx], y[split_idx:]
    
    # 转换为PyTorch张量
    X_train_tensor = torch.from_numpy(X_train)
    y_train_tensor = torch.from_numpy(y_train)
    X_val_tensor = torch.from_numpy(X_val)
    y_val_tensor = torch.from_numpy(y_val)
    
    # 创建数据集和数据加载器
    train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
    val_dataset = TensorDataset(X_val_tensor, y_val_tensor)
    
    # batch_size控制每次训练的样本数量
    train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
    
    return train_loader, val_loader


if __name__ == "__main__":
    # 设置随机种子,保证结果可复现
    torch.manual_seed(42)
    np.random.seed(42)
    
    # 创建示例数据
    print("创建示例数据...")
    train_loader, val_loader = create_sample_data(n_samples=1000, n_features=10)
    
    # 创建模型
    print("\n创建神经网络模型...")
    model = SimpleNeuralNetwork(input_size=10, hidden_size=20)
    print(model)
    
    # 检查是否有GPU
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    print(f"\n使用设备: {device}")
    
    # 训练模型
    print("\n开始训练...")
    history = train_model(
        model=model,
        train_loader=train_loader,
        val_loader=val_loader,
        epochs=100,
        learning_rate=0.01,
        device=device
    )
    
    # 打印最终结果
    print("\n" + "=" * 50)
    print("训练完成!")
    print(f"最终验证准确率: {history['val_accuracies'][-1]:.4f}")
    print("=" * 50)

4.3 代码关键点解析

nn.Linear:全连接层,执行线性变换 y = W x + b y = Wx + b y=Wx+b

nn.Module:所有神经网络模块的基类,自动管理参数

optimizer.zero_grad():清零梯度,PyTorch默认累积梯度

loss.backward():反向传播,计算所有参数的梯度

optimizer.step():根据梯度更新参数

model.train() / model.eval():切换训练/评估模式,影响Dropout和BatchNorm

torch.no_grad():禁用梯度计算,节省内存,用于验证和推理


五、神经网络的训练技巧

5.1 权重初始化

好的初始化可以加速收敛,避免梯度消失或爆炸。

Xavier初始化:适用于Sigmoid、Tanh激活函数

W ∼ N ( 0 , 2 n i n + n o u t ) W \sim \mathcal{N}\left(0, \sqrt{\frac{2}{n_{in} + n_{out}}}\right) WN(0,nin+nout2 )

He初始化:适用于ReLU激活函数

W ∼ N ( 0 , 2 n i n ) W \sim \mathcal{N}\left(0, \sqrt{\frac{2}{n_{in}}}\right) WN(0,nin2 )

PyTorch默认使用He初始化:

# 手动初始化
def init_weights(m):
    if isinstance(m, nn.Linear):
        nn.init.kaiming_normal_(m.weight)
        nn.init.zeros_(m.bias)

model.apply(init_weights)

5.2 正则化

Dropout:训练时随机丢弃一部分神经元,防止过拟合

dropout = nn.Dropout(p=0.5)  # 丢弃概率为50%

权重衰减(L2正则化):在损失函数中添加权重的平方项

optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)

5.3 学习率调度

学习率调度器可以在训练过程中动态调整学习率。

# StepLR:每step_size个epoch,学习率乘以gamma
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

for epoch in range(epochs):
    train(...)
    scheduler.step()  # 更新学习率

5.4 Batch Normalization

BatchNorm对每个mini-batch的数据进行标准化,加速训练并提高稳定性。

bn = nn.BatchNorm1d(num_features=64)

六、常见问题与解决方案

6.1 梯度消失/爆炸

症状:深层网络训练困难,梯度变得极小或极大

解决方案

  • 使用ReLU替代Sigmoid/Tanh
  • 使用BatchNorm
  • 使用残差连接(ResNet)
  • 使用梯度裁剪
# 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

6.2 过拟合

症状:训练集表现好,验证集表现差

解决方案

  • 增加训练数据
  • 使用Dropout
  • 使用权重衰减
  • 早停(Early Stopping)
  • 数据增强

6.3 欠拟合

症状:训练集和验证集都表现差

解决方案

  • 增加模型复杂度
  • 增加训练时间
  • 减小正则化强度
  • 使用更好的特征

七、总结

神经网络的核心思想可以概括为三点:

第一:通过多层非线性变换,神经网络能够学习极其复杂的模式。激活函数是引入非线性的关键,没有它,再深的网络也只是线性模型。

第二:反向传播算法通过链式法则高效计算梯度,使得训练深层网络成为可能。理解反向传播对于调试神经网络至关重要。

第三:PyTorch通过张量、自动求导和神经网络模块,大大简化了神经网络的实现。掌握这些工具,你就能快速实现各种网络架构。

神经网络的魅力在于它的通用性——同样的架构可以处理图像、文本、语音等各种类型的数据。在接下来的文章中,我们将探索专门用于图像处理的卷积神经网络(CNN)和专门用于序列数据的循环神经网络(RNN)。


参考资料

  • PyTorch官方教程:https://pytorch.org/tutorials/
  • 深度学习入门:斋藤康毅
  • 神经网络与深度学习:Michael Nielsen
  • 3Blue1Brown神经网络视频:https://www.youtube.com/watch?v=aircAruvnKk

下篇预告

在下一篇文章中,我们将深入卷积神经网络(CNN)的世界。你将了解到:

  • 卷积操作是如何提取图像特征的
  • 池化层为什么能增强模型的鲁棒性
  • 经典CNN架构(LeNet、AlexNet、VGG、ResNet)的设计思想
  • 如何用PyTorch实现图像分类模型

敬请期待:《卷积神经网络:让机器看见世界》

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐