PyTorch深度学习实战:从模型构建到训练技巧

PyTorch深度学习实战:从模型构建到训练技巧

本文将通过18个实战代码示例,带你深入掌握PyTorch的核心技术,包括数据准备、模型构建、自定义层、参数初始化、损失函数设计等关键知识点。

目录

  • [1. 数据准备与可视化](#1. 数据准备与可视化)
  • [2. 并行网络架构设计](#2. 并行网络架构设计)
  • [3. 模型训练与可视化](#3. 模型训练与可视化)
  • [4. 简化网络与参数初始化](#4. 简化网络与参数初始化)
  • [5. 自定义Sequential容器](#5. 自定义Sequential容器)
  • [6. 自定义参数与固定模块](#6. 自定义参数与固定模块)
  • [7. 自定义损失函数](#7. 自定义损失函数)
  • [8. 参数访问与模块管理](#8. 参数访问与模块管理)
  • [9. 动态添加模块](#9. 动态添加模块)
  • [10. 参数手动修改与梯度陷阱](#10. 参数手动修改与梯度陷阱)
  • [11. 参数初始化方法](#11. 参数初始化方法)
  • [12. 深度自编码器实现](#12. 深度自编码器实现)
  • [13. detach与梯度分离](#13. detach与梯度分离)

1. 数据准备与可视化

1.1 多项式特征数据生成

在开始深度学习之前,我们首先准备训练数据。这里模拟一个多项式回归问题,通过构造X的多次幂作为特征,来学习一个非线性关系。

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
from torch.utils.data import DataLoader, TensorDataset

# ==================== 数据准备 ====================
# 定义训练集和测试集大小
train_num = 150
test_num = 20
high = 1
low = -1

# 生成基础特征X:在[-1, 1]区间内均匀分布
X = torch.rand(size=(train_num + test_num, 1)) * (high - low) + low

# ==================== 多项式特征构造 ====================
# 构造高维特征:[X, X², X³, X⁴, X⁵]
# 注意:切片操作会创建新的内存地址
X_2 = X[:, :] ** 2
X_3 = X[:, :] ** 3
X_4 = X[:, :] ** 4
X_5 = X[:, :] ** 5
X = torch.cat((X, X_2, X_3, X_4, X_5), dim=1)

# ==================== 数据可视化 ====================
# 绘制不同幂次特征的关系图
plt.figure(figsize=(16, 4))
plt.subplot(1, 4, 1)
plt.scatter(X[:, 0], X[:, 3])
plt.title('X vs X³')

plt.subplot(1, 4, 2)
plt.scatter(X[:, 0], X[:, 4])
plt.title('X vs X⁴')

plt.subplot(1, 4, 3)
plt.scatter(X[:, 0], X[:, 1])
plt.title('X vs X²')

plt.subplot(1, 4, 4)
plt.scatter(X[:, 0], X[:, 2])
plt.title('X vs X³')

plt.tight_layout()
plt.show()

print(f"特征矩阵形状: {X.shape}")  # torch.Size([170, 5])

1.2 目标值生成

构造线性回归模型,添加噪声使问题更具挑战性。

python 复制代码
# ==================== 生成目标值 ====================
# 定义真实的权重和偏置
W = torch.tensor([1, 2, 3, 4, 5], dtype=X.dtype)
b = torch.tensor(3, dtype=X.dtype)

# 线性变换:Y = X·W + b
Y = torch.mv(X, W) + b

# 添加高斯噪声,模拟真实数据的不确定性
epsilon = torch.randn(size=Y.shape)
Y += epsilon

# 可视化目标值
fig = plt.figure(figsize=(10, 6))
plt.plot(X[:, 0], Y, 'o', alpha=0.5)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Polynomial Regression Data with Noise')
plt.show()

1.3 数据加载器创建

使用PyTorch的DataLoader进行批量数据加载和打乱。

python 复制代码
# ==================== 创建数据集 ====================
# 划分训练集和测试集
train_dataset = TensorDataset(X[:train_num, :], Y[:train_num])
test_dataset = TensorDataset(X[train_num:, :], Y[train_num:])

# 创建数据加载器
train_dataloader = DataLoader(
    train_dataset, 
    batch_size=16,  # 批量大小
    shuffle=True    # 训练时打乱数据
)

test_dataloader = DataLoader(
    test_dataset, 
    batch_size=16, 
    shuffle=True
)

# ==================== 检查数据加载器 ====================
for X_batch, y_batch in train_dataloader:
    print(f"批次形状: X={X_batch.shape}, y={y_batch.shape}")
    break

2. 并行网络架构设计

2.1 并行网络原理

并行网络通过多个分支同时处理输入,最后将结果拼接,能够提取更丰富的特征表示。

python 复制代码
from sklearn import metrics
from tqdm import tqdm

# ==================== 并行网络定义 ====================
class Parallel_Net(nn.Module):
    """
    并行网络架构
    - 双分支结构:block1和block2同时处理输入
    - 输出拼接:将两个分支的特征拼接在一起
    """
    def __init__(self, in_channel, out_channel):
        super().__init__()
        
        # 分支1:两层全连接网络
        self.block1 = nn.Sequential(
            nn.Linear(in_channel, out_channel),
            nn.ReLU(),
            nn.BatchNorm1d(out_channel),  # 批归一化:加速收敛
            nn.Linear(out_channel, out_channel),
            nn.ReLU()
        )
        
        # 分支2:三层全连接网络
        self.block2 = nn.Sequential(
            nn.Linear(in_channel, out_channel),
            nn.ReLU(),
            nn.BatchNorm1d(out_channel),
            nn.Linear(out_channel, out_channel),
            nn.ReLU(),
            nn.Linear(out_channel, 1)  # 输出维度为1
        )
    
    def forward(self, X):
        # 并行计算两个分支
        out1 = self.block1(X)
        out2 = self.block2(X)
        
        # 拼接两个分支的输出
        return torch.cat((out1, out2), dim=1)

# ==================== 完整网络构建 ====================
# 构建完整网络:输入层 + Dropout + 并行网络 + 输出层
net = nn.Sequential(
    nn.Linear(5, 3),          # 输入层:5维特征 -> 3维隐藏层
    nn.Dropout(0.3),         # Dropout:防止过拟合
    Parallel_Net(3, 5),      # 并行网络:3维 -> 6维(5+1)
    nn.Linear(6, 1)          # 输出层:6维 -> 1维输出
)

2.2 初始性能评估

在训练前先评估模型的初始性能,作为对比基准。

python 复制代码
# ==================== 初始性能评估 ====================
# 获取一个批次的数据用于评估
for X_eval, y_eval in train_dataloader:
    print(f"评估批次形状: X={X_eval.shape}, y={y_eval.shape}")
    break

# 计算初始预测
Y_init = net(X_eval)
initial_loss = metrics.mean_squared_error(
    y_eval.detach().numpy(), 
    Y_init.detach().numpy()
)

print(f"初始MSE损失: {initial_loss:.4f}")

# ==================== 初始预测可视化 ====================
plt.figure(figsize=(12, 6))
plt.plot(range(len(X_eval)), Y_init.detach().numpy(), 'b-', label='Predicted')
plt.plot(range(len(X_eval)), y_eval.numpy(), 'r--', label='True')
plt.xlabel('Sample Index')
plt.ylabel('Value')
plt.title('Initial Model Predictions (Before Training)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

3. 模型训练与可视化

3.1 训练函数定义

封装训练过程,包括前向传播、损失计算、反向传播和参数更新。

python 复制代码
# ==================== 训练函数定义 ====================
def train_model(train_dataloader, net, lr, epoch):
    """
    训练函数
    
    参数:
        train_dataloader: 训练数据加载器
        net: 待训练的网络模型
        lr: 学习率
        epoch: 训练轮数
    
    返回:
        result_loss: 每轮训练的平均损失列表
    """
    # 定义优化器:SGD随机梯度下降
    optimizer = torch.optim.SGD(net.parameters(), lr=lr)
    
    # 定义损失函数:MSE均方误差
    criterion = nn.MSELoss(reduction="mean")
    
    # 记录每轮的平均损失
    result_loss = []
    
    # 开始训练循环
    for i in tqdm(range(epoch), desc="Training Progress"):
        # 每轮训练
        for X_batch, Y_batch in train_dataloader:
            # ==================== 前向传播 ====================
            optimizer.zero_grad()  # 清空梯度
            Y_pred = net(X_batch)  # 计算预测值
            
            # ==================== 损失计算 ====================
            loss_value = criterion(Y_pred, Y_batch.unsqueeze(1))
            
            # ==================== 反向传播 ====================
            loss_value.backward()  # 计算梯度
            optimizer.step()      # 更新参数
        
        # ==================== 评估当前轮次性能 ====================
        with torch.no_grad():  # 不计算梯度,节省内存
            Y_pred = net(X_eval)
            mean_loss = metrics.mean_squared_error(Y_pred, Y_batch)
            result_loss.append(mean_loss)
    
    # ==================== 绘制训练曲线 ====================
    plt.figure(figsize=(10, 6))
    plt.plot(range(len(result_loss)), result_loss, 'b-', linewidth=2)
    plt.xlabel('Epoch')
    plt.ylabel('MSE Loss')
    plt.title('Training Loss Curve')
    plt.grid(True, alpha=0.3)
    plt.show()
    
    return result_loss

# ==================== 开始训练 ====================
result_loss = train_model(
    train_dataloader, 
    net=net, 
    lr=0.01, 
    epoch=1000
)

3.2 训练后性能评估

对比训练前后的模型性能。

python 复制代码
# ==================== 训练后预测 ====================
# 使用训练好的模型进行预测
for X_test, y_test in train_dataloader:
    print(f"测试批次形状: X={X_test.shape}, y={y_test.shape}")
    break

Y_pred = net(X_test)

# ==================== 预测结果可视化 ====================
plt.figure(figsize=(12, 6))
plt.plot(range(len(X_test)), Y_pred.detach().numpy(), 'b-', label='Predicted')
plt.plot(range(len(X_test)), y_test.numpy(), 'r--', label='True')
plt.xlabel('Sample Index')
plt.ylabel('Value')
plt.title('Model Predictions (After Training)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

# 计算最终损失
final_loss = metrics.mean_squared_error(
    y_test.numpy(), 
    Y_pred.detach().numpy()
)
print(f"训练后MSE损失: {final_loss:.4f}")
print(f"损失降低: {((initial_loss - final_loss) / initial_loss * 100):.2f}%")

4. 简化网络与参数初始化

4.1 简化的深度网络

对比并行网络,我们构建一个更简单的深度网络。

python 复制代码
# ==================== 简化网络定义 ====================
class Net_de(nn.Module):
    """
    简化的深度网络
    - 两层全连接网络
    - 中间使用ReLU激活函数
    """
    def __init__(self, in_feature, out_feature):
        super().__init__()
        self.layer1 = nn.Linear(in_feature, out_feature)
        self.layer2 = nn.Linear(out_feature, 1)
    
    def forward(self, X):
        X = F.relu(self.layer1(X))
        return self.layer2(X)

# 实例化网络
Net_d = Net_de(5, 3)
print("简化网络结构:")
print(Net_d)

4.2 参数初始化方法

参数初始化对训练效果影响巨大,我们对比不同的初始化策略。

python 复制代码
# ==================== 参数初始化函数 ====================
def init_normal(m):
    """正态分布初始化"""
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=1, std=1)
        nn.init.zeros_(m.bias)

def init_constant(m):
    """常数初始化"""
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)
        nn.init.zeros_(m.bias)

def init_xavier(m):
    """Xavier初始化:适合sigmoid/tanh激活函数"""
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
        nn.init.zeros_(m.bias)

# ==================== 对比不同初始化方法 ====================
# 方法1:使用自定义网络
Net_d.apply(init_normal)
print("使用正态分布初始化:")
result_loss_1 = train_model(train_dataloader, net=Net_d, lr=0.01, epoch=300)

# 方法2:使用Sequential网络
net_d = nn.Sequential(
    nn.Linear(5, 3), 
    nn.ReLU(), 
    nn.Linear(3, 1)
)

# 应用Xavier初始化
net_d.apply(init_xavier)
print("\n使用Xavier初始化:")
result_loss_2 = train_model(train_dataloader, net=net_d, lr=0.01, epoch=300)

# 方法3:再次训练自定义网络
Net_d3 = Net_de(5, 3)
Net_d3.apply(init_normal)
print("\n再次训练自定义网络:")
result_loss_3 = train_model(train_dataloader, net=Net_d3, lr=0.01, epoch=300)

4.3 Sequential命名尝试

尝试为Sequential中的模块命名(注意:这种写法在某些PyTorch版本中可能不支持)。

python 复制代码
# ==================== Sequential命名尝试 ====================
# 注意:以下写法在某些PyTorch版本中可能报错
# PyTorch的Sequential支持两种方式:
# 1. 直接传入模块列表
# 2. 使用OrderedDict(推荐)

from collections import OrderedDict

# 方法1:直接传入(不支持命名)
net_simple = nn.Sequential(
    nn.Linear(5, 3),
    nn.ReLU(),
    nn.Linear(3, 1)
)

# 方法2:使用OrderedDict(支持命名)
net_named = nn.Sequential(
    OrderedDict([
        ('linear1', nn.Linear(5, 3)),
        ('relu', nn.ReLU()),
        ('linear2', nn.Linear(3, 1))
    ])
)

print("简单Sequential结构:")
print(net_simple)
print("\n命名Sequential结构:")
print(net_named)

5. 自定义Sequential容器

5.1 错误的自定义实现

这是一个常见的错误实现,会导致优化器无法获取参数。

python 复制代码
# ==================== 错误的自定义Sequential ====================
class MySequential_Wrong(nn.Module):
    """
    错误的自定义Sequential实现
    问题:使用self.modules而非self._modules
    结果:优化器无法获取参数,训练时报错
    """
    def __init__(self, *args):
        super().__init__()
        self.modules = {}  # 错误:应该使用self._modules
        
        for idx, module in enumerate(args):
            self.modules[str(idx)] = module
    
    def forward(self, X):
        for m in self.modules.values():
            X = m(X)
        return X

# 这个实现会导致优化器报错:"optimizer got an empty parameter list"

5.2 正确的自定义实现

正确的自定义Sequential容器,使用_modules来注册参数。

python 复制代码
# ==================== 正确的自定义Sequential ====================
class MySequential(nn.Module):
    """
    正确的自定义Sequential实现
    关键:使用self._modules来注册模块
    优点:参数会被自动注册,可以被优化器更新
    """
    def __init__(self, *args):
        super().__init__()
        
        # 关键:使用self._modules(nn.Module的内置属性)
        # 这样PyTorch会自动将模块的参数注册到网络中
        for idx, module in enumerate(args):
            self._modules[str(idx)] = module
    
    def forward(self, X):
        # 依次执行每个模块
        for m in self._modules.values():
            X = m(X)
        return X

# ==================== 测试自定义Sequential ====================
# 使用自定义Sequential构建网络
net_myseq = MySequential(
    nn.Linear(5, 3),
    nn.ReLU(),
    nn.Linear(3, 1)
)

print("自定义Sequential网络:")
print(net_myseq)

# 训练自定义Sequential网络
print("\n训练自定义Sequential网络:")
result_loss_custom = train_model(
    train_dataloader, 
    net=net_myseq, 
    lr=0.01, 
    epoch=300
)

# 测试预测
output = net_myseq(X_eval)
print(f"\n预测输出形状: {output.shape}")
print(f"网络参数数量: {sum(p.numel() for p in net_myseq.parameters())}")

6. 自定义参数与固定模块

6.1 固定参数模块

有时候我们需要某些参数不参与训练,这可以通过设置requires_grad=False实现。

python 复制代码
# ==================== 固定参数模块 ====================
class Fixed_Module(nn.Module):
    """
    包含固定参数的模块
    - gamma矩阵:固定参数,不参与训练
    - while循环:实现动态计算图
    """
    def __init__(self):
        super().__init__()
        
        # 固定参数:不参与梯度计算
        self.gamma = torch.randn(size=(3, 3), requires_grad=False)
        
        # 可训练参数
        self.linear1 = nn.Linear(5, 3)
        self.linear2 = nn.Linear(3, 1)
    
    def forward(self, X):
        # 第一层
        X = self.linear1(X)
        
        # 使用固定矩阵进行变换
        X = self.linear2(torch.mm(X, self.gamma))
        
        # 动态while循环:计算图会根据条件展开
        while X.sum() > 10:
            X = X / 2
        
        return X

# ==================== 测试固定参数模块 ====================
fixed_module = Fixed_Module()

print("固定参数模块:")
print(fixed_module)

print("\n参数状态:")
for name, param in fixed_module.named_parameters():
    print(f"  {name}: requires_grad={param.requires_grad}, shape={param.shape}")

# 训练固定参数模块
print("\n训练固定参数模块:")
result_loss_fixed = train_model(
    train_dataloader, 
    net=fixed_module, 
    lr=0.01, 
    epoch=300
)

7. 自定义损失函数

7.1 可学习权重的组合损失

自定义损失函数,损失函数的权重也可以作为可学习参数。

python 复制代码
# ==================== 自定义组合损失函数 ====================
class LearnableCombinedLoss(nn.Module):
    """
    可学习权重的组合损失函数
    - gamma, beta:可学习的权重参数
    - loss1, loss2:基础损失函数
    """
    def __init__(self, 
                 loss1=nn.MSELoss(reduction="mean"), 
                 loss2=nn.L1Loss(reduction="mean")):
        super().__init__()
        
        # 可学习的权重参数
        self.gamma = nn.Parameter(torch.ones(1))
        self.beta = nn.Parameter(torch.ones(1))
        
        # 基础损失函数
        self.loss1 = loss1  # MSE损失
        self.loss2 = loss2  # L1损失
    
    def forward(self, pred, target):
        # 计算两个损失
        l1 = self.loss1(pred, target)
        l2 = self.loss2(pred, target)
        
        # 组合损失
        total_loss = self.gamma * l1 + self.beta * l2
        
        return total_loss

# ==================== 使用自定义损失函数 ====================
# 创建自定义损失
combined_loss = LearnableCombinedLoss()

# 拼接模型参数和损失函数参数
all_params = list(net.parameters()) + list(combined_loss.parameters())

# 定义优化器:为不同参数设置不同学习率
optimizer = torch.optim.Adam([
    {"params": net.parameters(), "lr": 1e-3},           # 模型参数学习率
    {"params": combined_loss.parameters(), "lr": 1e-2}  # 损失权重学习率
])

print("自定义组合损失函数:")
print(combined_loss)
print("\n可训练参数:")
print(f"  gamma: {combined_loss.gamma.item():.4f}")
print(f"  beta: {combined_loss.beta.item():.4f}")

8. 参数访问与模块管理

8.1 模块访问方式

展示如何访问Sequential中的模块和参数。

python 复制代码
# ==================== 模块访问 ====================
net_d = nn.Sequential(
    nn.Linear(5, 3), 
    nn.ReLU(), 
    nn.Linear(3, 1)
)

print("网络结构:")
print(net_d)

print("\n=== 模块访问方式 ===")

# 方式1:遍历模块
print("\n方式1:遍历所有模块")
for m in net_d:
    print(f"  {m.__class__.__name__}")

# 方式2:通过索引访问
print("\n方式2:通过索引访问模块")
print(f"  第一层: {net_d[0]}")
print(f"  第二层: {net_d[1]}")
print(f"  第三层: {net_d[2]}")

# 方式3:访问模块参数
print("\n方式3:访问模块参数")
print(f"  第一层状态字典: {net_d[0].state_dict()}")

# 方式4:访问网络所有参数
print("\n方式4:访问网络所有参数")
print("  网络状态字典:")
for name, param in net_d.state_dict().items():
    print(f"    {name}: shape={param.shape}")

8.2 参数数据访问

展示如何访问参数的值和梯度。

python 复制代码
# ==================== 参数数据访问 ====================
print("\n=== 参数数据访问方式 ===")

# 方式1:使用.data属性
print("\n方式1:使用.data属性")
print(f"  权重: {net_d[0].weight.data[:3, :3]}")

# 方式2:使用.detach()方法
print("\n方式2:使用.detach()方法")
print(f"  权重: {net_d[0].weight.detach()[:3, :3]}")

# 方式3:查看命名参数
print("\n方式3:查看所有命名参数")
for name, param in net_d.named_parameters():
    print(f"  {name}: shape={param.shape}, requires_grad={param.requires_grad}")

# 方式4:查看命名模块
print("\n方式4:查看所有命名模块")
for name, module in net_d.named_modules():
    if name:  # 跳过根模块(空名称)
        print(f"  {name}: {module.__class__.__name__}")

9. 动态添加模块

9.1 动态构建网络

使用add_module方法动态添加网络模块。

python 复制代码
# ==================== 动态模块定义 ====================
def block2():
    """定义一个基础块"""
    return nn.Sequential(
        nn.Linear(5, 5),
        nn.ReLU(),
        nn.Linear(5, 5)
    )

def block1(times):
    """
    动态构建多个基础块
    使用add_module方法为每个模块命名
    """
    net = nn.Sequential()
    
    for i in range(times):
        # 使用add_module动态添加模块
        net.add_module(f"module{i}", block2())
    
    return net

# ==================== 构建动态网络 ====================
# 构建5个基础块的网络
net_dynamic = nn.Sequential(
    block1(5),      # 5个基础块
    nn.Linear(5, 1) # 输出层
)

print("动态构建的网络:")
print(net_dynamic)

print("\n网络参数:")
for name, param in net_dynamic.named_parameters():
    print(f"  {name}: shape={param.shape}")

10. 参数手动修改与梯度陷阱

10.1 手动修改参数的陷阱

展示手动修改参数可能导致的梯度计算错误。

python 复制代码
# ==================== 梯度陷阱演示 ====================
# 创建简单的线性网络
net = torch.nn.Linear(1, 1, bias=False)
net.weight.data = torch.tensor([[1.0]])  # 初始化权重为1.0

# 定义优化器
opt = torch.optim.SGD(net.parameters(), lr=0.1)

# 准备数据
x = torch.tensor([[2.0]])
y_true = torch.tensor([[3.0]])

# 前向传播
y_pred = net(x)  # θ=1.0,y_pred=2.0
loss2 = nn.MSELoss()

print("=== 参数修改前 ===")
print(f"  初始权重: {net.weight.data.item():.4f}")
print(f"  预测值: {y_pred.item():.4f}")

# 计算损失
loss = loss2(y_pred, y_true)
print(f"  初始损失: {loss.item():.4f}")

# ==================== 关键操作:手动修改参数 ====================
# 这里的修改会导致梯度计算错误!
net.weight.data.zero_()  # 将权重改为0.0

print("\n=== 手动修改参数后 ===")
print(f"  修改后权重: {net.weight.data.item():.4f}")
print(f"  注意:此时梯度是基于修改前的参数计算的!")

# 反向传播
opt.zero_grad()
loss.backward()
print(f"  计算的梯度: {net.weight.grad.item():.4f}")

# 参数更新
opt.step()
print(f"  更新后权重: {net.weight.data.item():.4f}")

print("\n=== 原理说明 ===")
print("  正确梯度应该是:-0.1 * 4 * (1.0*2-3) = 0.4")
print("  但因为手动修改了参数,实际梯度计算基于错误的值")

11. 参数初始化方法

11.1 内置初始化方法

PyTorch提供了多种参数初始化方法。

python 复制代码
# ==================== 参数初始化方法 ====================
def init_normal(m):
    """正态分布初始化"""
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=1, std=1)
        nn.init.zeros_(m.bias)

def init_constant(m):
    """常数初始化"""
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)
        nn.init.zeros_(m.bias)

def init_uniform(m):
    """均匀分布初始化"""
    if type(m) == nn.Linear:
        nn.init.uniform_(m.weight, -10, 10)
        nn.init.zeros_(m.bias)

def init_xavier(m):
    """Xavier初始化"""
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
        nn.init.zeros_(m.bias)

def init_kaiming(m):
    """Kaiming初始化:适合ReLU激活函数"""
    if type(m) == nn.Linear:
        nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
        nn.init.zeros_(m.bias)

# ==================== 自定义初始化 ====================
def init_custom(m):
    """自定义初始化:只保留绝对值大于5的值"""
    if type(m) == nn.Linear:
        nn.init.uniform_(m.weight, -10, 10)
        # 只保留绝对值>=5的值,其他设为0
        m.weight.data *= (m.weight.data.abs() >= 5).float()
        nn.init.zeros_(m.bias)

# ==================== 测试不同初始化方法 ====================
linear_net = nn.Linear(1, 1, bias=False)

# 应用自定义初始化
init_custom(linear_net)
print("自定义初始化结果:")
print(f"  权重值: {linear_net.weight.data.item():.4f}")

# 多次应用初始化并统计分布
uniform_list = []
for i in range(1000):
    linear_net.apply(init_custom)
    uniform_list.append(int(linear_net.weight.data.item()))

# 绘制分布直方图
plt.figure(figsize=(10, 6))
plt.hist(uniform_list, bins=20, edgecolor='black', alpha=0.7)
plt.xlabel('Weight Value')
plt.ylabel('Frequency')
plt.title('Custom Initialization Distribution')
plt.grid(True, alpha=0.3)
plt.show()

print(f"前5个值: {uniform_list[:5]}")

12. 深度自编码器实现

12.1 自编码器原理

自编码器是一种无监督学习方法,用于学习数据的压缩表示。

python 复制代码
# ==================== 模拟高维数据 ====================
torch.manual_seed(42)

def simulate_data(num_sample, features):
    """
    模拟高维数据
    - 每个特征有不同的均值和标准差
    """
    mean = torch.rand(features) * 10 - 5
    std = torch.randint(low=1, high=10, size=(features,))
    
    # 为每个特征生成正态分布数据
    normal_list = [
        torch.normal(mean=mean1, std=std1, size=(num_sample,))
        for mean1, std1 in zip(mean, std)
    ]
    
    return torch.stack(normal_list, dim=1)

# 生成1000个样本,20个特征
X = simulate_data(1000, (20,))
print(f"数据形状: {X.shape}")

12.2 自编码器网络构建

构建编码器和解码器网络。

python 复制代码
# ==================== 编码器定义 ====================
left_side_layer = nn.Sequential(
    nn.Linear(20, 15),
    nn.ReLU(),
    nn.Linear(15, 15),
    nn.ReLU(),
    nn.Linear(15, 10)  # 压缩到10维
)

# ==================== 解码器定义 ====================
def right_side(layer):
    """从编码器层提取权重和偏置,用于构建解码器"""
    dict_1 = {}
    weight = layer.weight.detach().T  # 转置权重
    bias = layer.bias.detach()
    dict_1['weight'] = weight
    dict_1['bias'] = bias
    return dict_1

class Rightside(nn.Module):
    """
    解码器网络
    - 自动从编码器构建对称的解码器
    - 使用编码器的转置权重
    """
    def __init__(self, left_side_net):
        super().__init__()
        
        # 提取编码器的Linear层
        module_list = []
        for i in range(len(left_side_net)):
            if isinstance(left_side_net[i], nn.Linear):
                module_list.append(left_side_net[i])
        
        # 反转顺序构建解码器
        index_list = list(range(len(module_list)))[::-1]
        right_dict = {}
        
        for i, k in enumerate(index_list):
            right_dict[i] = right_side(module_list[k])
        
        self.right_dict = right_dict
    
    def forward(self, X):
        # 逐层解码
        for layer in self.right_dict.values():
            X = torch.matmul(X, layer['weight'].T)  # 使用转置权重
        
        return X

# ==================== 构建完整自编码器 ====================
right_side_net = Rightside(left_side_layer)
net = nn.Sequential(left_side_layer, right_side_net)

print("自编码器网络:")
print(net)

print("\n参数梯度状态:")
for name, param in net.named_parameters():
    print(f"  {name}: requires_grad={param.requires_grad}")

12.3 自编码器训练

训练自编码器进行数据重建。

python 复制代码
# ==================== 训练自编码器 ====================
# 定义损失函数和优化器
criterion = nn.MSELoss(reduction='mean')
optimizer = torch.optim.SGD(net.parameters(), lr=1e-1)

result_list = []

print("开始训练自编码器...")
for i in tqdm(range(30000), desc="Autoencoder Training"):
    optimizer.zero_grad()
    
    # 前向传播
    Y = net(X)
    
    # 计算重建损失
    loss_value = criterion(X, Y)
    
    # 反向传播
    loss_value.backward()
    optimizer.step()
    
    result_list.append(loss_value.item())

# ==================== 绘制训练曲线 ====================
plt.figure(figsize=(10, 6))
plt.plot(result_list, linewidth=2)
plt.xlabel('Iteration')
plt.ylabel('Reconstruction Loss')
plt.title('Autoencoder Training Progress')
plt.yscale('log')  # 对数坐标
plt.grid(True, alpha=0.3)
plt.show()

print(f"最终重建损失: {result_list[-1]:.6f}")

12.4 保存和加载模型

python 复制代码
# ==================== 保存模型 ====================
torch.save(net.state_dict(), "autoencoder_model.pth")
print("模型已保存到: autoencoder_model.pth")

# ==================== 加载模型 ====================
# 创建新的网络实例
net_loaded = nn.Sequential(
    nn.Linear(20, 15),
    nn.ReLU(),
    nn.Linear(15, 15),
    nn.ReLU(),
    nn.Linear(15, 10),
    Rightside(left_side_layer)
)

# 加载参数
net_loaded.load_state_dict(torch.load("autoencoder_model.pth"))
print("\n模型参数已加载")

# 验证加载的模型
with torch.no_grad():
    Y_loaded = net_loaded(X)
    reconstruction_error = criterion(X, Y_loaded)
    
print(f"加载模型的重建误差: {reconstruction_error.item():.6f}")

13. detach与梯度分离

13.1 detach的作用

detach()用于从计算图中分离张量,停止梯度传播。

python 复制代码
# ==================== detach演示 ====================
torch.manual_seed(11)

# 创建需要梯度的张量
x1 = torch.rand((10, 1), requires_grad=True)
print("=== 原始张量 x1 ===")
print(f"  值(前5个): {x1[:5].squeeze().tolist()}")
print(f"  requires_grad: {x1.requires_grad}")
print(f"  梯度: {x1.grad}")

# 使用detach分离张量
x2 = x1.detach()
print("\n=== 分离后的张量 x2 ===")
print(f"  值(前5个): {x2[:5].squeeze().tolist()}")
print(f"  requires_grad: {x2.requires_grad}")

# 修改分离后的张量
x2[0, 0] = 1
print("\n=== 修改x2后 ===")
print(f"  x1值(前5个): {x1[:5].squeeze().tolist()}")
print(f"  x2值(前5个): {x2[:5].squeeze().tolist()}")
print(f"  x1 requires_grad: {x1.requires_grad}")
print(f"  x2 requires_grad: {x2.requires_grad}")

# 测试梯度传播
y1 = x1 * 2
y2 = x2 * 2

print("\n=== 梯度计算测试 ===")
print(f"  y1 requires_grad: {y1.requires_grad}")
print(f"  y2 requires_grad: {y2.requires_grad}")

# 反向传播
y1.sum().backward()
print(f"\n反向传播后 x1 的梯度: {x1.grad}")
print(f"x2 没有梯度,因为它不在计算图中")

13.2 detach的应用场景

python 复制代码
# ==================== detach应用场景 ====================
# 场景1:GAN训练中的生成器
# 生成器生成的图像需要detach后传入判别器
class SimpleGAN(nn.Module):
    def __init__(self):
        super().__init__()
        self.generator = nn.Sequential(
            nn.Linear(10, 20),
            nn.ReLU()
        )
        self.discriminator = nn.Sequential(
            nn.Linear(20, 1),
            nn.Sigmoid()
        )
    
    def train_step(self, noise, real_labels, fake_labels):
        # 生成假数据
        fake_data = self.generator(noise)
        
        # 关键:detach停止梯度,避免更新生成器
        fake_pred = self.discriminator(fake_data.detach())
        
        # 训练判别器
        real_pred = self.discriminator(real_labels)
        
        return fake_pred, real_pred

# 场景2:Actor-Critic算法
# 计算优势函数时需要detach策略网络的输出
def compute_advantages(rewards, values, gamma=0.99):
    """
    计算优势函数
    - values: 来自价值网络,需要detach
    """
    # detach确保不会反向传播到价值网络
    returns = rewards + gamma * values.detach()
    advantages = returns - values.detach()
    return advantages

# 场景3:预训练模型微调
# 冻结某些层时使用detach
def freeze_layers(model, layer_names):
    """冻结指定层"""
    for name, param in model.named_parameters():
        if any(layer_name in name for layer_name in layer_names):
            param.requires_grad = False
            print(f"冻结层: {name}")

print("detach的典型应用场景:")
print("1. GAN训练:生成器输出detach后传入判别器")
print("2. 强化学习:优势函数计算时detach策略网络输出")
print("3. 迁移学习:冻结预训练层时使用detach")

相关推荐
安全二次方security²9 小时前
CUDA C++编程指南(7.31&32&33&34)——C++语言扩展之性能分析计数器函数和断言、陷阱、断点函数
c++·人工智能·nvidia·cuda·断点·断言·性能分析计数器函数
bksheng9 小时前
【Dify】安装与部署
人工智能
狸奴算君9 小时前
告别数据泄露:三步构建企业级AI的隐私保护盾
人工智能
Christo39 小时前
TKDE-2026《Efficient Co-Clustering via Bipartite Graph Factorization》
人工智能·算法·机器学习·数据挖掘
jackylzh9 小时前
PyTorch 2.x 中 `torch.load` 的 `FutureWarning` 与 `weights_only=False` 参数分析
人工智能·pytorch·python
LCG米9 小时前
基于PyTorch的Transformer-CNN时序预测实战:从特征工程到服务化部署
pytorch·cnn·transformer
子夜江寒9 小时前
基于PyTorch的语言模型实现详解
pytorch·语言模型
叶庭云9 小时前
AI Agent KernelCAT:深耕算子开发和模型迁移的 “计算加速专家”
人工智能·运筹优化·算子·ai agent·kernelcat·模型迁移适配·生态壁垒
码农三叔9 小时前
(8-2)传感器系统与信息获取:外部环境传感
人工智能·嵌入式硬件·数码相机·机器人·人形机器人