Skip to content

Latest commit

 

History

History
504 lines (383 loc) · 19 KB

File metadata and controls

504 lines (383 loc) · 19 KB

深度学习

张量

  • 张量:Tensor: shape=(3, 4, 5), dtype=float32

  • 访问

    • 切片:t[0:2](3, 4, 5) -> (2, 4, 5)(轴 0 取前两个元素,轴 0 长度变短)
    • 索引:t[0,-1],得 (3, 4, 5) -> (5) (轴 0 取第一个元素,轴 1 取最后一个元素,轴 0,1 消失,降两维)
    • 切片+索引:t[0:2,0,-1],得 (3, 4, 5) -> (2) (轴 0 取前两个元素,轴 0 长度变短,轴 1 取第一个元素,轴 2 取最后一个元素,轴 1,2 消失,降两维)
  • 运算

    • 逐元素运算符,如 + - * / ** 等,需要两个 Tensor 具有相同的 dtype 和尽量相同的 shape

      • 广播机制:对两个 shape 不同的 Tensor 进行逐元素运算时,会尝试扩张两个 Tensor 以让它们 shape 一样
        1. 从最右边的维度开始比较两个 Tensor
        2. 如果每对维度都是,相等的或其中一个为 1 或 0(没有该维度)
        3. 则允许运算,必要时广播
        4. 比如两个维度为 (3, 1, 4) + (3, 4) 的 Tensor 广播为 (3, 3, 4) + (3, 3, 4)
    • 降维运算

      • 求和:例如将 Tensor (3, 4, 5) 对轴 0, 2 进行求和

        1. 轴 0 元素为 (4, 5),求和后轴 0 消失得 (4, 5)
        2. 轴 2 元素为 (),求和后轴 2 消失得 (4)
        • 避免降维:通常可以指定参数 keepdims=True 使求和后保持轴长度为 1,即 (3, 4, 5) -> (1, 4, 1)
      • 缩并:例如将 Tensor A (3, 4, 5) 和 B (3, 1, 4) 对轴 0_a, 1_a 和 0_b, 2_b 进行缩并

        1. 对 A 的轴编号(指标) (i, j, k),对 B 的轴编号 (l, m, n)
        2. 缩并轴 i, l 具有相同长度 3,缩并轴 j, n 具有相同长度 4,允许缩并
        3. 自由轴排列得最终结果 Tensor 的 shape 为 (k, m)(5, 1)
        for k:
          for m:
            for i_l:
              for j_n:
                C[k, m] += A[i_l, j_n, k] * B[i_l, m, j_n]
        
        # 爱因斯坦求和约定:缩并运算的特殊表示法
        einsum("ijk,imj->km", A, B)
        • 缩并后信息被压缩到新的 Tensor 中,缩并轴的元素之间发生信息交互,而自由轴的元素之间相互独立不会发生信息交互

        • 全连接神经网络层的本质也是缩并运算,将输入的特征轴全部缩并则输出中的每个值都由所有输入特征参与贡献,即 X(B, n)W(n, m) + b(m) -> y(B, m)

        • 线性代数中向量和矩阵运算即是特殊的缩并运算

          • 点积:(n)(n) -> ()
          • 矩阵-向量积:(m, n)(n) -> (m)
          • 矩阵乘法:(m, k)(k, n) -> (m, n)

线性回归

  • 线性模型
    • $\hat{y} = w \cdot x + b$
    • 用来拟合潜在的目标函数
  • 损失函数
    • $L=\frac{1}{2B}\sum (\hat{y} - y)^2$
    • 用来度量预测 (predict) 与目标 (target) 之间的误差
  • 梯度下降
    • $w = w - \alpha\frac{\partial L}{\partial w} = w - \alpha\frac{\partial L}{\partial \hat{y}} \frac{\partial \hat{y}}{\partial w} = w - \alpha \cdot \frac{1}{B}\sum (\hat{y} - y) \cdot x$
    • $b = b - \alpha\frac{\partial L}{\partial b} = b - \alpha\frac{\partial L}{\partial \hat{y}} \frac{\partial \hat{y}}{\partial b} = b - \alpha \cdot \frac{1}{B}\sum (\hat{y} - y)$
    • 调整参数 $w$$b$ 使它们朝误差变小的方向前进
  • 训练
    • 独立同分布假设:训练集中每个样例之间相互独立没有关联,且训练集中的样例分布与真实环境相同
    • 样例 (example):特征 (feature)、标签 (label)
    • 数据集:
      • 训练集:每次 epoch 用来优化模型
      • 验证集:每次 epoch 用来验证模型是否过拟合
      • 测试集:仅在最后用来测试模型的真实水平

前馈神经网络

通用近似定理:⼀个前馈神经⽹络如果包含至少一个具有非多项式激活函数的隐藏层,则它可以模拟任何可测函数

  • 0 层: 只能表示线性可分函数或决策
  • 1 层: 可以近似任何包含从一个有限空间到另一个有限空间的连续映射的函数
  • 2 层: 可以用有理激活函数以任意精度表示任意决策边界,并且可以近似任何平滑映射到任何精度
  • ≥3 层: 额外的隐藏层可以学习复杂的描述(某种自动特征工程)

mlp

  • 输入层:输入 Tensor,第一维通常是 batch
  • 隐含层
    • 全连接层:提取特征
    • 激活函数:非线性化
      • ReLU: $g(z)=max{0,z}$
      • tanh: $g(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}$
      • sigmoid: $g(z)=\frac{1}{1+e^{-z}}$
  • 输出层:将输入数据映射到特定的输出范围
    • linear: $\hat{y}=z=Wh+b$
    • softmax: $\hat{y}_i=\frac{exp(z_i)}{\sum_jexp(z_j)}$
    • sigmoid: $\hat{y}=\frac{1}{1+exp(-z)}$

forward_propagation

  • 前向传播:记录每层的计算结果
  • 后向传播:隐藏层的梯度依赖上一层的输出和下一层的梯度,所以先计算损失函数(L)对输出层(o)的梯度,再将该梯度一层层向前转播即可计算每层的梯度,从而对每层参数应用梯度下降优化算法

后向传播参考

卷积神经网络

cnn

pooling

  • CNN
    • 填充:为防止边缘像素参与卷积运算次数过少,对边缘进行 0 填充
    • 步幅:每次移动卷积核的距离
    • 卷积核:
      • 非全连接(局部性)
      • 共享参数(平移不变性)
    • 汇聚层:
      • 减少内存使用
      • 只取最大值,降低特征模式在卷积核中的位置敏感性(平移不变性)

nin

  • NiN
    • 1x1 卷积层:学习到更复杂的跨通道的特征
    • GAP:避免使用全连接层,大大降低内存使用和过拟合现象

resnet

  • ResNet

    • Skip Connection:让网络可以学习并跳过某个块,保证较深的网络总是至少要和较浅网络一样好
  • DenseNet

    • Concatenation:前面层的输出共同参与当前层的计算

循环神经网络

rnn

  • $\mathbf{H}t = \phi(\mathbf{X}t \mathbf{W}{xh} + \mathbf{H}{t-1} \mathbf{W}_{hh} + \mathbf{b}_h)$
  • $\mathbf{O}_t = \mathbf{H}t \mathbf{W}{hq} + \mathbf{b}_q$
  • 输入 (Batch, Sequence, Features)
  • 输出:通常只取最后一个时间步的输出 (Batch, Output)

lstm

deeprnn

binrnn

seq2seq

Transformer

transformer

mha

  • 人类大脑

    1. 输入
    2. 过滤(前馈神经网络)

      filter out & filter in ?

    3. 关联(多头注意力)
  • 多头注意力:$Attention(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$

Step Operation Intuition
1. Project $W \cdot X$ 仅进行线性转换以保留空间特征并映射到某个子空间
1. Score $Q \cdot K^T$ 计算每对 Query 与 Key 的相似度,想象二维向量点积,若两方向相同则点积很大,若两方向正交则点积为 0
2. Weights $Softmax(Scores)$ 计算每个 Query 对每个 Key 的相关度概率
3. Context $Weights \cdot V$ 合并相关信息构造成新的 token
4. Mask (optional) $Output + Mask$ Decoder中每个 Query 仅能看到当前时间步之前的 Key
class MultiHeadAttention(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        self.num_heads = num_heads
        self.d_model = d_model

        # d_model must be divisible by num_heads
        assert d_model % num_heads == 0
        self.depth = d_model // num_heads # Dimensionality of each head

        # Linear layers for Query, Key, Value
        self.wq = tf.keras.layers.Dense(d_model)
        self.wk = tf.keras.layers.Dense(d_model)
        self.wv = tf.keras.layers.Dense(d_model)

        # Final linear layer
        self.dense = tf.keras.layers.Dense(d_model)

    def split_heads(self, x, batch_size):
        # Original Shape: (batch_size, seq_len, d_model)
        # Reshape to: (batch_size, seq_len, num_heads, depth)
        x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))
        # Transpose to: (batch_size, num_heads, seq_len, depth)
        # We put num_heads dimension before seq_len to process heads in parallel
        return tf.transpose(x, perm=[0, 2, 1, 3])

    def call(self, v, k, q, mask):
        batch_size = tf.shape(q)[0]

        # 1. Linear projections
        q = self.wq(q)  # (batch_size, seq_len, d_model)
        k = self.wk(k)
        v = self.wv(v)

        # 2. Split heads
        q = self.split_heads(q, batch_size) # (batch_size, num_heads, seq_len_q, depth)
        k = self.split_heads(k, batch_size) # (batch_size, num_heads, seq_len_k, depth)
        v = self.split_heads(v, batch_size)

        # 3. Scaled Dot-Product Attention
        # Matmul Q and K transpose -> (batch_size, num_heads, seq_len_q, seq_len_k)
        matmul_qk = tf.matmul(q, k, transpose_b=True)

        # Scale by square root of depth (to keep gradients stable)
        dk = tf.cast(tf.shape(k)[-1], tf.float32)
        scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)

        # 4. Apply Mask (Optional)
        # If mask is present, we set masked positions to -1e9 (very negative number)
        # so softmax makes them zero.
        if mask is not None:
            scaled_attention_logits += (mask * -1e9)

        # 5. Softmax to get attention weights (0 to 1)
        attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)

        # 6. Multiply by V
        output = tf.matmul(attention_weights, v) # (batch_size, num_heads, seq_len_q, depth)

        # 7. Concatenate heads back together
        output = tf.transpose(output, perm=[0, 2, 1, 3]) # (batch_size, seq_len_q, num_heads, depth)
        output = tf.reshape(output, (batch_size, -1, self.d_model)) # (batch_size, seq_len_q, d_model)

        # 8. Final dense layer
        return self.dense(output)

模型优化

过拟合

过拟合问题:对训练数据具有较低误差,但对测试数据具有较高误差

解决方案:

  • 权重衰减:过拟合通常表现为拟合函数太过复杂(扭曲),即 W 过大导致的,通过在损失函数中加一项范数来惩罚 W 过大的情况,从而改善过拟合

    • L1 and L2 Regularization: 1e-3 ~ 1e-4,越大正则化能力越强
  • Dropout:通过往层之间添加噪声可以优化模型的泛化能力

    • Dropout: 20% ~ 50%, 越大正则化能力越强,放在激活函数后
  • Early Stopping: 验证损失持续不下降则停止训练

梯度消失与爆炸

梯度消失与梯度爆炸问题:由于网络中前一层的梯度依赖后一层的梯度,连续相乘导致梯度快速消失或快速增长,使得训练不稳定

解决方案:核心思想是尽量保证输入/权重/输出的均值为 0 方差为 1 来限制梯度的变化

  • Xavier 初始化:通过限制初始权重张量的均值和方差,从而限制梯度的均值和方差
  • Batch Normalization:将小批量输出(激活函数前)映射到均值为 0 方差为 1 的范围中,让梯度更稳定更平滑,从而加速梯度下降

环境和分布偏移

Shift Type Changes Constant Note
Covariate Shift P(X) P(Y∣X) The "population" looks different.
Label Shift P(Y) P(X∣Y) The "frequency" of classes changed.
Concept Drift P(Y∣X) P(X) The "rules" of the world changed.

内部协变量偏移问题:网络内部层与层之间的输入分布,会随着权重的变化而变化,导致学习效率变低

解决方案:

  • Batch Normalization

Misc

  • Add more training data
  • Data Augmentations
  • Batch Size: 4 ~ 512,越小泛化能力越强,但训练成本也越高

Pytorch 框架

完整的训练代码示例

Tensor

import torch

torch.arange(5)
torch.arange(1, 4)
torch.arange(1, 2.5, 0.5)
torch.zeros(2, 3)
torch.ones(2, 3)
torch.full((2, 3), 3.14)
torch.rand(2, 3)
torch.randint(3, 10, (2, 3))
torch.randn(2, 3)
x = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float, device="cuda")

x.shape  # torch.Size([2, 3])
x.dtype  # torch.float32
x.device # gpu

x = x.reshape(-1)
x = x.to(torch.int)
x = x.to("cpu")

x[0]     # first row, 降维 dim=0
x[:, 0]  # first column, 降维 dim=1
x[...,-1]# last column, 降维 dim=1

Dataset

from torch.utils.data import Dataset, DataLoader
from torchvision import datasets, transforms

class CustomDataset(Dataset):
    def __init__(self, root, transform=None, target_transform=None):
        pass

    def __len__(self):
        pass

    def __getitem__(self, idx):
        pass


# Dataset 迭代元素为一个样例数据 (X, y)
train_data = datasets.FashionMNIST(
    root="data",                    # 数据目录
    train=True,                     # 训练集 or 测试集
    download=True,                  # 数据不存在则联网下载
    transform=transforms.ToTensor() # 将图片转换为张量并缩放至[0,1]
)

# DataLoader 迭代元素为一个 mini-batch 的所有样例数据 (X, y)
train_loader = DataLoader(
    train_data,               # 绑定 Dataset
    batch_size,               # mini-batch 大小
    shuffle=True,             # 每次循环是否打乱顺序
)

Model

from torch import nn

class MyNeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.net = nn.Sequential(
            nn.Linear(28 * 28, 512),
            nn.BatchNorm1d(512)
            nn.ReLU(),
            nn.Dropout(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        return self.net(x)

model = MyNeuralNetwork()
  • Hidden Neurons

    • input * 2 / 3 + output
    • (input + output) / 2
    • sqrt(input * output)
  • Structed Pruning

    • 提高运算速度
    • 提高泛化能力
  • Convolution Layer

    • $n_o=\frac{(n_i-f+2p)}{s}+1$
    • valid: 不填充
    • same: 填充使得卷积后输出大小与输入一致
    • full: 填充使得输出大小为 n+f-1
  • Activation Function

    • ReLU: first try
    • PReLU: avoid the dying ReLU problem
    • ReLU6: limit high positive value to avoid computational issue during the training
    • HardSwish: give it a try

Optimizer

torch.optim.SGD(model.parameters(), lr=1e-3, momentum=0.9,weight_decay=1e-4)

torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2, amsgrad=True)

Loss Function

# Regression
nn.SmoothL1Loss(beta=1.0) # 绝对元素误差小于 beta 则使用 MSELoss,其他情况使用 L1Loss

# Multi-Class Classification
nn.CrossEntropyLoss() # 多远分类利用 Softmax(logits) 获取概率

# Binary Classification
nn.BCEWithLogitsLoss() # 二元分类利用 Sigmoid(logits) 获取概率

Training Loop

# 模型有两种模式:
# model.train() 训练时使用,Dropout 与 BatchNorm 等会生效
# model.eval() 测试或推理时使用,Dropout 与 BatchNorm 等不生效

# Pytorch 全局模式有三种:
# Grad Mode: 默认模式,此模式下设置了`requires_grad=True`的张量为记录操作图用于计算梯度
# No-Grad Mode: 此模式下不再记录操作图,可加速测试或推理
# Inference Mode: 同 No-Grad Mode 但更极端,且此模式下创建的张量无法用于其他模式

model.train()
for i in range(epochs):
    for x, y in dataloader:
        # 将张量拷贝到 GPU
        x = x.to("cuda", non_blocking=True)
        y = y.to("cuda", non_blocking=True)

        optimizer.zero_grad(set_to_none=True) # 将累加的梯度归零
        y_hat = model(x) # 前向传播
        loss = criterion(y_hat, y) # 计算损失标准
        loss.backward() # 计算梯度并累加到模型参数张量的.grad字段
        optimizer.step() # 梯度下降优化权重参数

model.eval()
with torch.no_grad():
    for x, y in dataloader:
        x = x.to("cuda", non_blocking=True)
        y = y.to("cuda", non_blocking=True)
        y_hat = model(x)

Performance Tuning

  • 数据归一化 transforms.ToTensor() nn.BatchNormd1(...)
  • 异步加载数据 DataLoader(..., num_workers=1, ...)
  • 固定内存 DataLoader(..., pin_memory=True, ...)
  • 使用 GPU 加速 model.to("cuda") tensor.to("cuda")
  • 减少不必要梯度计算 torch.no_grad() torch.inference_mode()
  • 加速优化函数 torch.optim.AdamW(..., fuse=True, ...)
  • JIT 编译 torch.compile(model)
  • channels_last memory format & Automatic Mixed Precision (Require Tensor Core)

Overfitting Solution

  • Add more training data
  • Data Augmentations
  • Batch Size: 4 ~ 512,越小泛化能力越强,但训练成本也越高
  • Early Stopping: 验证损失持续不下降则停止训练
  • Batch/Layer Normalisation: 放在激活函数前
  • Dropout: 20% ~ 50%, 越大正则化能力越强。放在激活函数后
  • L1 and L2 Regularization: 1e-3 ~ 1e-4,越大正则化能力越强

Hyperparams Optimization

pip install optuna
  • Batch Size
  • Learning Rate
  • Weight Decay
  • Model Architecture