一般在一个新的 trickexperience 开坑时,都会先暂时粗略地搬运一些其他地方的内容,或者简略描述。偶尔精进与专门研究时,会特别地丰富和细致化该内容。

# pytorch_mssim.ssim 的使用

以下面计算 ssim 的代码为例:

ssim_value = ssim(final, gt_batch, data_range=2.0, size_average=True)

data_range 表示图像像素值的动态范围(最大值与最小值的差)。如果输入图像经过归一化处理(如 transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ),则像素值范围会被映射到 [-1, 1] 。此时 data_range 应设为 2.0 (因为 1 - (-1) = 2 ),而不是 1.0

SSIM 默认假设输入范围是 [0, 1] (当 data_range=1 )或 [0, 255] (当 data_range=255 )。

size_average=True 会将 SSIM 值在所有图像和通道上取平均。 pytorch_msssim 的新版本使用 reduction 取代这个参数,重写为: reduction='mean'

有时候,应该确保输入给 ssim 做计算时的数据范围在 [-1, 1] 之间,不然,则要裁剪处理:

final_clamped = torch.clamp(final, 0.0, 1.0)
ssim_value = ssim(final_clamped, gt_batch, data_range=1.0, size_average=True)

# 损失函数下降、震荡、上升的原因

在训练包含多个损失函数的模型时,各子损失函数的变化趋势能够反映模型的学习动态和优化方向。以下是不同趋势的详细分析及应对策略:


# 一、损失函数整体持续下降

# 含义解析

  • 良性学习信号:模型正在有效优化该任务目标,权重分配合理,数据质量良好。
  • 潜在风险
    • 过拟合倾向:若验证集对应指标未同步下降,可能过拟合训练数据。
    • 任务主导性:其他损失未充分优化,模型可能偏向该任务。

# 典型案例

  • 重建损失(如 L1/L2)持续下降,但对抗损失震荡 → 模型过度拟合像素级精度,忽视生成真实性。

# 应对策略

  1. 验证泛化性:检查验证集对应指标是否同步改善
  2. 调整权重:若其他损失停滞,适当降低该损失权重(如从 1.0→0.7)
  3. 早停机制:当验证损失不再下降时停止训练

# 二、损失函数震荡波动

# 含义解析

  • 优化不稳定:学习率过高、批次过小或损失间存在冲突。
  • 数据问题:噪声数据或类别不均衡导致梯度方向不一致。
  • 对抗性博弈:典型于 GAN 的判别器与生成器损失交替上升。

# 数值特征

  • 高频震荡(如 ±5%):常由学习率过大引起
  • 低频震荡(如每 5 个 epoch 变化):多任务目标冲突

# 典型案例

  • 分类损失下降但正则化损失震荡 → L2 正则化强度过高导致参数更新不稳定

# 应对策略

  1. 降低学习率:将初始学习率减少 3-5 倍(如 2e-4→5e-5)
  2. 增大批次大小:从 32 提升至 128,稳定梯度估计
  3. 梯度裁剪:设置 max_grad_norm=1.0
  4. 冲突分析:计算损失梯度余弦相似度,对负相关损失解耦训练

# 三、损失函数持续上升

# 含义解析

  • 严重警告信号:模型在该任务上性能退化,优化方向错误。
  • 常见诱因
    • 损失权重倒置:如误将权重设为负数
    • 任务本质冲突:如超分辨率任务中,L1 损失下降但感知损失上升
    • 数值不稳定:梯度爆炸导致损失进入病态区域

# 典型案例

  • 对抗损失上升而重建损失下降 → 判别器过强导致生成器无法有效学习

# 应对策略

  1. 立即暂停训练:检查损失计算代码和权重符号
  2. 损失权重热力图:可视化各损失对总损失的贡献比例
  3. 渐进式训练:分阶段引入上升的损失项(如先用 L1 预训练,第 50epoch 加入对抗损失)
  4. 架构改进:对于根本性冲突,修改网络结构(如增加多尺度特征融合模块)

# 四、综合优化建议

  1. 动态权重调整:采用不确定性加权法(如《Multi-Task Learning Using Uncertainty to Weigh Losses》)
    # 各损失自动加权示例
    log_var = torch.nn.Parameter(torch.zeros(3)) # 3 个损失
    loss = 0.5*(loss1/torch.exp(log_var[0]) + loss2/torch.exp(log_var[1]) + log_var.sum())
  2. 损失相关性监控:计算各损失间的 Pearson 相关系数矩阵,识别冲突组合
  3. 课程学习策略:早期侧重易优化损失(如 L1),后期加强高阶损失(如 SSIM、VGG 感知损失)
  4. 可视化工具:使用 TensorBoard 的并行坐标视图对比超参数与损失关系

# 五、调试检查清单

当出现异常损失趋势时,按以下顺序排查:

  1. 数值检查
    • 确认损失计算未出现 NaN/Inf
    • 检查梯度幅值( torch.nn.utils.clip_grad_norm_
  2. 数据流验证
    # 数据检查代码片段
    for batch in val_loader:
        print(batch['image'].min(), batch['image'].max()) # 应为 [0,1] 或 [-1,1]
        visualize(batch['image'][0]) # 肉眼验证图像质量
  3. 权重合理性:确保各损失量级匹配(如 L1≈0.1,对抗损失≈2.0 时,需调整权重平衡)
  4. 模型容量测试:在小数据集(如 100 样本)上过拟合,验证能否达到预期损失

通过系统分析损失动态,可精准定位模型优化瓶颈,实现多目标协同优化。

# transforms.Resize()

这个与 RandomCrop() 还不太一样, Resize() 是等比例的缩放原图。所以存在一定的信息损失,一般不使用这种操作。

# 不确定性损失加权法 —— 多任务损失均衡

以下是使用不确定性加权法改造后的损失函数实现:

import torch
import torch.nn as nn
import torch.nn.functional as F
from pytorch_msssim import ssim
class UncertaintyWeightedLoss(nn.Module):
    def __init__(self, trans, num_tasks=5):
        """
        trans: HVI转换器实例
        num_tasks: 需要加权的损失项数量(这里包含L1, SSIM, Res, Cons, HVI)
        """
        super().__init__()
        self.trans = trans
        # 初始化可学习的不确定性参数(log 方差)
        self.log_vars = nn.Parameter(torch.zeros(num_tasks))
        # 初始化参数(可选)
        nn.init.uniform_(self.log_vars, -3, -1)  # 初始方差在 0.05~0.37 之间
    def compute_losses(self, final, gt, output, S1, P1, S2, P2):
        """分解计算各个基础损失项"""
        # RGB 空间损失
        l1_loss = F.l1_loss(final, gt)
        ssim_loss = 1 - ssim(final, gt, data_range=1.0, size_average=True)
        
        # 残差一致性损失
        loss_res1 = F.mse_loss(S1, P1 + S2) / (S1.detach().var() + 1e-6)
        loss_res2 = F.mse_loss(S2, P2 + S1) / (S2.detach().var() + 1e-6)
        res_loss = loss_res1 + loss_res2
        
        # 下采样一致性损失
        g1, g2 = pair_downsampler(output)
        cons_loss = F.l1_loss(S1, P1 + g1) + F.l1_loss(S2, P2 + g2)
        
        # HVI 空间损失
        final_hvi = self.trans.HVIT(final)
        gt_hvi = self.trans.HVIT(gt)
        hvi_l1 = F.l1_loss(final_hvi, gt_hvi)
        hvi_edge = edge_loss(final_hvi, gt_hvi)
        
        return [l1_loss, ssim_loss, res_loss, cons_loss, hvi_l1, hvi_edge]
    def forward(self, final, gt, output, S1, P1, S2, P2):
        # 获取所有基础损失项
        losses = self.compute_losses(final, gt, output, S1, P1, S2, P2)
        
        # 应用不确定性加权
        total_loss = 0.0
        for i, loss in enumerate(losses):
            precision = torch.exp(-self.log_vars[i])
            total_loss += precision * loss + self.log_vars[i]
        
        # 返回总损失和详细损失项(用于监控)
        loss_details = {
            'total': total_loss,
            'l1': losses[0],
            'ssim': losses[1],
            'res': losses[2],
            'cons': losses[3],
            'hvi_l1': losses[4],
            'hvi_edge': losses[5],
            'log_vars': self.log_vars
        }
        return total_loss, loss_details

主要改进点说明:

  1. 模块化设计
# 初始化方式变化
trans = RGB_HVI().to(device)
criterion = UncertaintyWeightedLoss(trans).to(device)
# 前向计算变化
total_loss, loss_details = criterion(final, gt, output, S1, P1, S2, P2)
  1. 动态权重机制
  • 每个损失项自动获得权重:weight = exp (-log_var)
  • 包含正则项:log_var 防止方差无限增大
  • 初始权重范围:exp (-3)=0.05 ~ exp (-1)=0.37
  1. 训练监控增强
# 在训练循环中添加监控
writer.add_scalars('Loss/Train', {
    'total': loss_details['total'].item(),
    'l1': loss_details['l1'].item(),
    'ssim': loss_details['ssim'].item(),
    'res': loss_details['res'].item(),
    'cons': loss_details['cons'].item(),
    'hvi_l1': loss_details['hvi_l1'].item(),
    'hvi_edge': loss_details['hvi_edge'].item()
}, total_iter)
# 监控不确定性参数
for i, log_var in enumerate(loss_details['log_vars']):
    writer.add_scalar(f'Uncertainty/var_{i}', torch.exp(log_var).item(), epoch)
  1. 优化器调整
# 需要将损失参数加入优化器
optimizer = optim.Adam(
    list(model.parameters()) + list(criterion.parameters()),
    lr=args.lr, 
    betas=(0.9, 0.999)
)
  1. 梯度稳定性处理
# 在训练步骤中添加梯度裁剪
torch.nn.utils.clip_grad_norm_(
    list(model.parameters()) + list(criterion.parameters()),
    max_norm=1.0
)

该实现通过以下方式解决原代码问题:

  1. 自动平衡机制
  • 难任务(高方差项)自动获得低权重
  • 简单任务(低方差项)获得高权重
  • 通过反向传播自动调整权重
  1. 数值稳定性改进
  • 使用 log 方差代替直接学习方差,避免除零错误
  • 初始值限制在合理范围
  • 梯度裁剪防止参数爆炸
  1. 可解释性增强
  • 通过 TensorBoard 可直接观察每个任务的:
    • 损失值变化曲线
    • 自适应权重变化(exp (-log_var))
    • 方差参数演化

实际使用时需要注意:

  1. 参数初始化

    # 如果某些任务需要强调,可以调整初始值
    nn.init.constant_(self.log_vars[4], -2.0)  # 强调 HVI_L1 损失
  2. 学习率设置

    # 为损失参数设置更小的学习率
    optimizer = optim.Adam([
        {'params': model.parameters(), 'lr': 1e-4},
        {'params': criterion.parameters(), 'lr': 1e-5}
    ])
  3. 多阶段训练

    # 初始阶段固定部分损失权重
    if epoch < warmup_epochs:
        criterion.log_vars.requires_grad_(False)
    else:
        criterion.log_vars.requires_grad_(True)

这种实现方式比手动调整 lambda 参数更科学,尤其在处理类似 HVI 这类新增损失时,能自动平衡其与原有损失项的关系。

# 余弦退火学习率调度器

一代经典的学习率调度器。

模板代码参考如:

# ... 其他导入 ...
from torch.optim.lr_scheduler import CosineAnnealingLR
def main():
    # 参数解析
    parser = argparse.ArgumentParser("MAI_Denoised_Train")
    # ... 原有参数 ...
    parser.add_argument('--min_lr', type=float, default=1e-6, help='minimum learning rate for cosine annealing')
    args = parser.parse_args()
    # ... 模型初始化 ...
  
    # 初始化优化器和调度器
    optimizer = optim.Adam(model.parameters(), lr=args.lr, betas=(0.9, 0.999), weight_decay=1e-6)
    scheduler = CosineAnnealingLR(optimizer, T_max=args.epochs, eta_min=args.min_lr)
    # 训练循环
    for epoch in range(args.epochs):
        # 训练过程...
      
        # 验证过程...
      
        # 更新学习率
        scheduler.step()
      
        # 记录学习率
        current_lr = scheduler.get_last_lr()[0]
        writer.add_scalar('Learning Rate', current_lr, epoch)
        logging.info(f'Epoch [{epoch+1}/{args.epochs}] Learning Rate: {current_lr:.7f}')
    # ... 后续代码 ...

# Optuna 自动化调参

为了使用 Optuna 进行超参数调优,我们需要对代码进行以下主要修改。这里展示关键修改步骤(完整代码见最后):

import optuna
from optuna.trial import Trial
def objective(trial):
    # 定义可调参数范围
    args = argparse.Namespace(
        batch_size=trial.suggest_categorical('batch_size', [32, 64, 128]),
        lr=trial.suggest_float('lr', 1e-6, 1e-3, log=True),
        lambda_hvi1=trial.suggest_float('lambda_hvi1', 0.001, 0.1),
        lambda_hvi2=trial.suggest_float('lambda_hvi2', 0.001, 0.1),
        lambda_rgb1=trial.suggest_float('lambda_rgb1', 0.1, 2),
        lambda_rgb2=trial.suggest_float('lambda_rgb2', 0.1, 2),
        # 保留其他固定参数...
        cuda=True,
        gpu='cuda:0',
        epochs=100,  # 调试时可减少 epoch
        save=f'./EXP/{trial.number}'  # 为每个 trial 创建独立目录
    )
    # 运行训练并返回验证指标
    final_psnr = train_model(args, trial)
    return final_psnr
def train_model(args, trial=None):
    # [原 main () 函数内容在这里重构]
    # 在验证循环中报告中间结果
    if trial:
        trial.report(val_psnr, epoch)
        if trial.should_prune():
            raise optuna.exceptions.TrialPruned()
    
    return best_val_psnr  # 返回需要优化的指标
if __name__ == "__main__":
    # 创建 Optuna study
    study = optuna.create_study(
        direction='maximize',
        sampler=optuna.samplers.TPESampler(),
        pruner=optuna.pruners.MedianPruner()
    )
    
    # 运行优化
    study.optimize(objective, n_trials=50, timeout=3600*24)
    
    # 输出结果
    print("Best trial:")
    trial = study.best_trial
    print(f"  Value: {trial.value}")
    print("  Params: ")
    for key, value in trial.params.items():
        print(f"    {key}: {value}")

主要修改点说明:

  1. 参数建议:使用 trial.suggest_* 方法替代原 argparse 参数:

    trial.suggest_categorical('batch_size', [32, 64, 128])
    trial.suggest_float('lr', 1e-6, 1e-3, log=True)
  2. 目标函数:将训练过程封装到目标函数中,返回验证 PSNR 作为优化指标

  3. 中间报告:在训练过程中定期报告验证指标,支持提前终止:

    trial.report(val_psnr, epoch)
    if trial.should_prune():
        raise optuna.exceptions.TrialPruned()
  4. 独立目录:为每个 trial 创建独立的保存目录,避免文件冲突:

    args.save = f'./EXP/{trial.number}'
  5. Study 配置:创建优化 study 时指定优化方向(最大化 PSNR)和采样策略:

    study = optuna.create_study(direction='maximize')

完整整合后的代码示例:

import optuna
from optuna.trial import Trial
import argparse
import torch
def main():
    # 原始训练代码...
def objective(trial):
    # 定义超参数搜索空间
    params = {
        'batch_size': trial.suggest_categorical('batch_size', [32, 64, 128]),
        'lr': trial.suggest_float('lr', 1e-6, 1e-3, log=True),
        'lambda_hvi1': trial.suggest_float('lambda_hvi1', 0.001, 0.1),
        'lambda_hvi2': trial.suggest_float('lambda_hvi2', 0.001, 0.1),
        'lambda_rgb1': trial.suggest_float('lambda_rgb1', 0.5, 2.0),
        'lambda_rgb2': trial.suggest_float('lambda_rgb2', 0.5, 2.0),
        'lambda_res': trial.suggest_float('lambda_res', 0.5, 2.0),
        'lambda_cons': trial.suggest_float('lambda_cons', 0.5, 2.0),
    }
    
    # 固定参数
    fixed_params = {
        'cuda': True,
        'gpu': 'cuda:0',
        'epochs': 100,  # 调优时 epoch 可以适当减少
        'data_dir': '/path/to/data',
        'save': f'./EXP/trial_{trial.number}',
    }
    
    # 合并参数
    args = argparse.Namespace(**{**params, **fixed_params})
    
    # 运行训练
    best_psnr = train_with_args(args, trial)
    return best_psnr
def train_with_args(args, trial=None):
    # [原 main () 函数内容重构到这里]
    # 初始化模型、数据加载等...
    
    best_psnr = 0
    for epoch in range(args.epochs):
        # 训练循环...
        
        # 验证循环
        with torch.no_grad():
            val_psnr = evaluate(model, val_loader)
            
            # 向 Optuna 报告中间结果
            if trial:
                trial.report(val_psnr, epoch)
                
                # 提前终止
                if trial.should_prune():
                    raise optuna.exceptions.TrialPruned()
                    
        if val_psnr > best_psnr:
            best_psnr = val_psnr
            torch.save(model.state_dict(), f"{args.save}/best_model.pth")
    
    return best_psnr
if __name__ == "__main__":
    # 创建 Optuna study
    study = optuna.create_study(
        direction='maximize',
        sampler=optuna.samplers.TPESampler(),
        pruner=optuna.pruners.MedianPruner(n_startup_trials=5, n_warmup_steps=10)
    )
    
    # 运行优化
    study.optimize(objective, n_trials=50, timeout=3600*24)
    
    # 输出最佳结果
    print("Best trial:")
    trial = study.best_trial
    print(f"  PSNR: {trial.value}")
    print("  Params: ")
    for key, value in trial.params.items():
        print(f"    {key}: {value}")

关键优化技巧:

  1. 参数范围设置:根据参数性质选择适当的范围:

    trial.suggest_float('lr', 1e-6, 1e-3, log=True)  # 对数尺度更适合学习率
    trial.suggest_categorical('batch_size', [32, 64, 128])
  2. 提前终止:使用 MedianPruner 避免资源浪费:

    pruner=optuna.pruners.MedianPruner(n_startup_trials=5, n_warmup_steps=10)
  3. 并行优化:通过指定 n_jobs 并行运行:

    study.optimize(objective, n_trials=100, n_jobs=4)
  4. 持久化存储:使用数据库保存进度:

    study = optuna.create_study(
        storage='sqlite:///optuna.db',
        study_name='denoising_study',
        load_if_exists=True
    )

注意事项:

  1. 资源管理:调优时适当减少 epoch 数量(如 50-100),最终训练时再用完整 epoch

  2. 参数空间:初始搜索使用较宽范围,后期可基于初步结果缩小范围

  3. 指标选择:建议使用验证集 PSNR 作为优化目标,而非训练损失

  4. 随机种子:为保持可比性,可在每个 trial 中固定随机种子:

    torch.manual_seed(trial.suggest_int('seed', 0, 1000))
  5. GPU 内存:注意 batch_size 与 GPU 显存的匹配,建议在 suggest_categorical 中包含可行值

这种集成方式可以在不破坏原有训练逻辑的基础上,系统性地探索超参数空间。最终可以通过 study.best_trial.params 获取最佳参数组合,用于最终模型的训练。

# 混合精度训练

# 梯度累积

通过多次小批量迭代累积梯度,模拟大 Batch Size 的效果,模板代码参考可见下方:

accumulation_steps = 4  # 累积 4 个 batch 的梯度
for i, (inputs, labels) in enumerate(dataloader):
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss = loss / accumulation_steps  # 损失按累积步数缩放
    loss.backward()
    
    if (i + 1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()

# torchinfo 统计模型的显存占用

参考如下代码:

from torchinfo import summary
model = MyModel().cuda()
summary(model, input_size=(batch_size, 3, 256, 256))

# 显存分析器 memory_profiler

这个工具可以可以统计每行代码的显存变化

from pytorch_memlab import LineProfiler
@profile
def train_batch(inputs, labels):
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()
# 运行后会打印每行代码的显存变化
train_batch(inputs, labels)

# Dataloadernum_workers 设置

num_workers 通常设置为 0, CPU 线程数的 75%CPU 线程数, CPU 线程数的两倍。

CPU 的线程数计算公式为:线程数 = 逻辑核心数 = 物理核心数 * 单核线程数。

num_workers 很大程度上影响 GPU 的占用率。保持长时间的高 GPU 占用率是高效率训练深度学习的基础。

# 损失为 Nan 的分析

损失值出现 NaN(Not a Number)通常由数值不稳定引起,以下是可能原因及解决方案:

# 1. 输入数据问题

  • 检查数据中的 NaN 或异常值:确保输入数据无缺失或无效值。

    import numpy as np
    print("NaN in data:", np.isnan(data).any())
    print("数据范围:", data.min(), data.max())
  • 数据标准化 / 归一化:过大或过小的输入值可能导致梯度爆炸。

    data = (data - data.mean()) / data.std()  # 标准化

# 2. 学习率过高

  • 降低学习率:过大的学习率会导致参数更新不稳定。
    optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # 初始学习率设为 0.01 或更小

# 3. 损失函数实现问题

  • 避免对零取对数:在交叉熵损失中增加极小值 ε(如 1e-8)。

    loss = -tf.reduce_sum(y_true * tf.math.log(y_pred + 1e-8))
  • 使用框架内置函数:如 TensorFlow 的 CategoricalCrossentropy(from_logits=True) ,避免手动实现中的错误。

# 4. 梯度爆炸(前提是你的其他代码得写对)

  • 梯度裁剪:限制梯度最大范数。

    # PyTorch 示例
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    # TensorFlow 示例
    gradients = tape.gradient(loss, model.trainable_variables)
    gradients, _ = tf.clip_by_global_norm(gradients, 1.0)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

# 5. 模型结构问题

  • 激活函数与输出层匹配:分类任务最后一层需用 Softmax(或配合 from_logits=True )。
  • 权重初始化:使用 He/Xavier 初始化避免初始值过大。
    # PyTorch 示例
    torch.nn.init.kaiming_normal_(layer.weight)

# 6. 数值稳定性技巧

  • 添加 Batch Normalization:稳定层间输出分布。
    model.add(tf.keras.layers.BatchNormalization())
  • 混合精度训练:使用 FP16 时,开启梯度缩放。
    optimizer = tf.keras.mixed_precision.LossScaleOptimizer(optimizer)

# 7. 调试步骤

  • 小数据集测试:用少量样本过拟合,快速复现问题。
  • 打印中间结果:检查前向传播输出和梯度。
    # 检查输出层
    print("模型输出:", outputs)
    # 检查梯度
    for name, param in model.named_parameters():
        if param.grad is not None:
            print(f"梯度 {name}: {param.grad.norm()}")

# 8. 其他可能原因

  • 正则化过强:降低 L2 正则化系数。
  • 数据预处理错误:检查标准化时是否除以零(如方差为零的特征)。

# 总结流程

  1. 检查输入数据:确保无 NaN 且已标准化。
  2. 降低学习率:尝试 0.001 或更低。
  3. 验证损失函数:使用内置函数或添加 ε。
  4. 梯度裁剪:限制梯度大小。
  5. 检查模型结构:激活函数、初始化、添加 BatchNorm。
  6. 逐步调试:缩小数据范围,打印中间变量。

通过以上步骤逐步排查,通常可以定位并解决 NaN 损失问题。

# torchvision.utils.save_image

有个参数叫做 Normalize ,这个将数值映射到 [0,255] 的区间。相关使用说明如下:

当设置normalize=True时:
- 会自动将张量的数值范围从[min, max]线性映射到[0, 255]
- 例如:输入张量范围是[-1, 1],会被映射到0-255
- 例如:输入张量范围是[0, 1],会被映射到0-255(相当于直接乘以255

torchvision.utils.save_image 保存图像要求图像的数值范围必须是指定范围,即在 [0,1][0,255] 。如果数据范围在其他区间,则需要保证数据范围符合 torchvision.utils.save_image 的要求,可通过设置 normalizeTrue 解决这个问题。

# export PYTHONPATH="$PWD:$PYTHONPATH"

这是一个通过 PYTHONPATH 手动指定项目根目录的命令。

以深度学习项目 Retinexformer 为例,这个项目文件夹内包含了训练的代码 train.py ,以及模型架构文件等。

有时候直接在终端通过 python train.py 的绝对路径会报一些代码文件中的库引用错误,但是你反复检查了路径,觉得代码里导包的方式没问题。

这时候你就可以先通过 cd 命令进入项目文件夹中,然后再执行这个 export 命令手动指定项目根目录:

> cd Retinexformer
> export PYTHONPATH="$PWD:$PYTHONPATH"

# 确保 Python 优先加载本地项目的代码而不是 Anaconda 环境中的库

情景: TinyNeuralNetwork 库代码在项目文件夹 Retinexformer 下面, Anaconda 也有一个 TinyNeuralNetwork 库。现在我们在本地更新了 TinyNeuralNetwork 库代码,想要运行更新后的库代码中的 convert.py 代码。这个时候 Python 有可能会在执行新库代码 convert.py 的时候,调用 Anaconda 环境的旧库代码。

一种方法是指定优先级,强制优先加载项目中的本地库:

import sys
import os
# 获取当前脚本所在目录(TinyNeuralNetwork 文件夹的路径)
TINYNN_DIR = os.path.dirname(os.path.abspath(__file__))
# 获取项目根目录(假设 TinyNeuralNetwork 是 Retinexformer 的子目录)
PROJECT_ROOT = os.path.dirname(TINYNN_DIR)
# 将本地库路径插入到 sys.path 的最前面
sys.path.insert(0, TINYNN_DIR)
sys.path.insert(0, PROJECT_ROOT)
# 打印验证路径是否正确添加(可选)
print("当前 Python 路径:")
for p in sys.path:
    print(p)

另一种方式就是在终端执行 convert.py 而不是在 IDE 中运行:

> cd Retinexformer
> export PYTHONPATH="$PWD:$PYTHONPATH"
> python convert.py

#Linux 内存不足时,扩大交换空间

首先要处理一下现有的交换文件:

# 查看当前启用的交换空间
sudo swapon --show
# 如果存在 /swapfile,先关闭交换文件
sudo swapoff /swapfile
# 删除旧的交换文件
sudo rm /swapfile

然后创建一个更大内存的交换文件:

# 创建 8GB 交换文件
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile