一般在一个新的 trick
和 experience
开坑时,都会先暂时粗略地搬运一些其他地方的内容,或者简略描述。偶尔精进与专门研究时,会特别地丰富和细致化该内容。
# 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.0→0.7)
- 早停机制:当验证损失不再下降时停止训练
# 二、损失函数震荡波动
# 含义解析
- 优化不稳定:学习率过高、批次过小或损失间存在冲突。
- 数据问题:噪声数据或类别不均衡导致梯度方向不一致。
- 对抗性博弈:典型于 GAN 的判别器与生成器损失交替上升。
# 数值特征
- 高频震荡(如 ±5%):常由学习率过大引起
- 低频震荡(如每 5 个 epoch 变化):多任务目标冲突
# 典型案例
- 分类损失下降但正则化损失震荡 → L2 正则化强度过高导致参数更新不稳定
# 应对策略
- 降低学习率:将初始学习率减少 3-5 倍(如 2e-4→5e-5)
- 增大批次大小:从 32 提升至 128,稳定梯度估计
- 梯度裁剪:设置
max_grad_norm=1.0
- 冲突分析:计算损失梯度余弦相似度,对负相关损失解耦训练
# 三、损失函数持续上升
# 含义解析
- 严重警告信号:模型在该任务上性能退化,优化方向错误。
- 常见诱因:
- 损失权重倒置:如误将权重设为负数
- 任务本质冲突:如超分辨率任务中,L1 损失下降但感知损失上升
- 数值不稳定:梯度爆炸导致损失进入病态区域
# 典型案例
- 对抗损失上升而重建损失下降 → 判别器过强导致生成器无法有效学习
# 应对策略
- 立即暂停训练:检查损失计算代码和权重符号
- 损失权重热力图:可视化各损失对总损失的贡献比例
- 渐进式训练:分阶段引入上升的损失项(如先用 L1 预训练,第 50epoch 加入对抗损失)
- 架构改进:对于根本性冲突,修改网络结构(如增加多尺度特征融合模块)
# 四、综合优化建议
- 动态权重调整:采用不确定性加权法(如《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())- 损失相关性监控:计算各损失间的 Pearson 相关系数矩阵,识别冲突组合
- 课程学习策略:早期侧重易优化损失(如 L1),后期加强高阶损失(如 SSIM、VGG 感知损失)
- 可视化工具:使用 TensorBoard 的并行坐标视图对比超参数与损失关系
# 五、调试检查清单
当出现异常损失趋势时,按以下顺序排查:
- 数值检查:
- 确认损失计算未出现 NaN/Inf
- 检查梯度幅值(
torch.nn.utils.clip_grad_norm_
)- 数据流验证:
# 数据检查代码片段
for batch in val_loader: print(batch['image'].min(), batch['image'].max()) # 应为 [0,1] 或 [-1,1] visualize(batch['image'][0]) # 肉眼验证图像质量- 权重合理性:确保各损失量级匹配(如 L1≈0.1,对抗损失≈2.0 时,需调整权重平衡)
- 模型容量测试:在小数据集(如 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主要改进点说明:
- 模块化设计:
# 初始化方式变化
trans = RGB_HVI().to(device) criterion = UncertaintyWeightedLoss(trans).to(device)# 前向计算变化
total_loss, loss_details = criterion(final, gt, output, S1, P1, S2, P2)
- 动态权重机制:
- 每个损失项自动获得权重:weight = exp (-log_var)
- 包含正则项:log_var 防止方差无限增大
- 初始权重范围:exp (-3)=0.05 ~ exp (-1)=0.37
- 训练监控增强:
# 在训练循环中添加监控
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)
- 优化器调整:
# 需要将损失参数加入优化器
optimizer = optim.Adam( list(model.parameters()) + list(criterion.parameters()), lr=args.lr, betas=(0.9, 0.999))
- 梯度稳定性处理:
# 在训练步骤中添加梯度裁剪
torch.nn.utils.clip_grad_norm_( list(model.parameters()) + list(criterion.parameters()), max_norm=1.0)
该实现通过以下方式解决原代码问题:
- 自动平衡机制:
- 难任务(高方差项)自动获得低权重
- 简单任务(低方差项)获得高权重
- 通过反向传播自动调整权重
- 数值稳定性改进:
- 使用 log 方差代替直接学习方差,避免除零错误
- 初始值限制在合理范围
- 梯度裁剪防止参数爆炸
- 可解释性增强:
- 通过 TensorBoard 可直接观察每个任务的:
- 损失值变化曲线
- 自适应权重变化(exp (-log_var))
- 方差参数演化
实际使用时需要注意:
参数初始化:
# 如果某些任务需要强调,可以调整初始值
nn.init.constant_(self.log_vars[4], -2.0) # 强调 HVI_L1 损失学习率设置:
# 为损失参数设置更小的学习率
optimizer = optim.Adam([ {'params': model.parameters(), 'lr': 1e-4}, {'params': criterion.parameters(), 'lr': 1e-5} ])多阶段训练:
# 初始阶段固定部分损失权重
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}")主要修改点说明:
参数建议:使用 trial.suggest_* 方法替代原 argparse 参数:
trial.suggest_categorical('batch_size', [32, 64, 128]) trial.suggest_float('lr', 1e-6, 1e-3, log=True)目标函数:将训练过程封装到目标函数中,返回验证 PSNR 作为优化指标
中间报告:在训练过程中定期报告验证指标,支持提前终止:
trial.report(val_psnr, epoch) if trial.should_prune(): raise optuna.exceptions.TrialPruned()独立目录:为每个 trial 创建独立的保存目录,避免文件冲突:
args.save = f'./EXP/{trial.number}'Study 配置:创建优化 study 时指定优化方向(最大化 PSNR)和采样策略:
study = optuna.create_study(direction='maximize')完整整合后的代码示例:
import optuna
from optuna.trial import Trialimport 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}")关键优化技巧:
参数范围设置:根据参数性质选择适当的范围:
trial.suggest_float('lr', 1e-6, 1e-3, log=True) # 对数尺度更适合学习率 trial.suggest_categorical('batch_size', [32, 64, 128])提前终止:使用 MedianPruner 避免资源浪费:
pruner=optuna.pruners.MedianPruner(n_startup_trials=5, n_warmup_steps=10)并行优化:通过指定 n_jobs 并行运行:
study.optimize(objective, n_trials=100, n_jobs=4)持久化存储:使用数据库保存进度:
study = optuna.create_study( storage='sqlite:///optuna.db', study_name='denoising_study', load_if_exists=True)
注意事项:
资源管理:调优时适当减少 epoch 数量(如 50-100),最终训练时再用完整 epoch
参数空间:初始搜索使用较宽范围,后期可基于初步结果缩小范围
指标选择:建议使用验证集 PSNR 作为优化目标,而非训练损失
随机种子:为保持可比性,可在每个 trial 中固定随机种子:
torch.manual_seed(trial.suggest_int('seed', 0, 1000))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) |
# Dataloader
的 num_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 正则化系数。
- 数据预处理错误:检查标准化时是否除以零(如方差为零的特征)。
# 总结流程
- 检查输入数据:确保无 NaN 且已标准化。
- 降低学习率:尝试 0.001 或更低。
- 验证损失函数:使用内置函数或添加 ε。
- 梯度裁剪:限制梯度大小。
- 检查模型结构:激活函数、初始化、添加 BatchNorm。
- 逐步调试:缩小数据范围,打印中间变量。
通过以上步骤逐步排查,通常可以定位并解决 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
的要求,可通过设置 normalize
为 True
解决这个问题。
# 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 |