【Bug已解决】UserWarning: Implicit dimension choice for log_softmax has been deprecated 解决方案
【Bug已解决】UserWarning: Implicit dimension choice for log_softmax has been deprecated 解决方案问题描述在 PyTorch 中使用F.log_softmax或F.softmax函数时许多开发者遇到过以下警告UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dimX as an argument.这个警告在 PyTorch 1.8 版本开始出现并在后续版本中逐渐加强。虽然它目前只是一个警告不会中断程序运行但如果不处理在未来的 PyTorch 版本中可能会变成错误。问题的核心是log_softmax以及softmax函数需要明确指定在哪个维度上进行操作而旧代码中常常省略dim参数让 PyTorch 自动推断维度。这种隐式推断行为已经被废弃。错误复现场景一基本的 log_softmax 调用import torch import torch.nn.functional as F # 模拟模型输出 (batch_size4, num_classes10) logits torch.randn(4, 10) # 旧写法不指定 dim 参数 output F.log_softmax(logits) # 触发警告 print(output.shape)输出UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dimX as an argument. output F.log_softmax(logits) torch.Size([4, 10])场景二在自定义损失函数中class CustomLoss(nn.Module): def __init__(self): super().__init__() def forward(self, logits, targets): # 旧写法不指定 dim log_probs F.log_softmax(logits) # 警告 loss -log_probs.gather(1, targets.unsqueeze(1)).squeeze() return loss.mean() # 使用 model nn.Linear(784, 10) criterion CustomLoss() data torch.randn(32, 784) target torch.randint(0, 10, (32,)) logits model(data) loss criterion(logits, target) # 触发警告场景三在训练循环中def train(model, dataloader, optimizer, device): model.train() for batch_idx, (data, target) in enumerate(dataloader): data, target data.to(device), target.to(device) optimizer.zero_grad() logits model(data) # 旧写法不指定 dim log_probs F.log_softmax(logits) # 警告 loss F.nll_loss(log_probs, target) loss.backward() optimizer.step()场景四多维张量的歧义# 3D 张量 (batch, seq_len, vocab_size) logits_3d torch.randn(4, 20, 1000) # 不指定 dimPyTorch 会自动选择最后一个维度 # 但这种行为是不明确的可能不符合预期 output F.log_softmax(logits_3d) # 警告 # 在某些情况下开发者可能想在 dim1 上做 softmax # 但隐式推断会选择 dim2导致错误根因分析1. PyTorch 的隐式维度推断历史在早期 PyTorch 版本中softmax和log_softmax在不指定dim参数时会自动推断操作维度# PyTorch 早期版本的隐式推断逻辑简化版 def log_softmax(input, dimNone): if dim is None: # 自动推断选择最后一个维度 dim input.dim() - 1 # 同时发出警告 warnings.warn(Implicit dimension choice for log_softmax has been deprecated.) return _log_softmax(input, dim)2. 为什么废弃隐式推断原因说明歧义性多维张量中不指定 dim 可能导致非预期行为可读性明确指定 dim 使代码意图更清晰一致性与其他 PyTorch 函数如sum、mean保持一致性能明确 dim 可以避免运行时推断开销未来兼容为支持更多维度和功能做准备3. 隐式推断的规则# PyTorch 隐式推断的规则已废弃 # 1D 张量: dim0 x torch.randn(10) F.softmax(x) # 等价于 F.softmax(x, dim0) # 2D 张量: dim1 (最后一个维度) x torch.randn(4, 10) F.softmax(x) # 等价于 F.softmax(x, dim1) # 3D 张量: dim2 (最后一个维度) x torch.randn(4, 20, 10) F.softmax(x) # 等价于 F.softmax(x, dim2) # 但这个规则不总是符合预期 # 例如在 NLP 中可能想在 dim1 上做 softmax4. 受影响的函数列表# 以下函数都受此废弃影响 F.softmax(x) # 需要 dim 参数 F.log_softmax(x) # 需要 dim 参数 F.softmax(x, dim-1) # 正确写法 F.log_softmax(x, dim-1) # 正确写法 # 注意nn.Softmax() 在 __init__ 中需要指定 dim # nn.LogSoftmax() 同样需要解决方案方案一明确指定 dim 参数最直接的修复import torch import torch.nn.functional as F # 2D 张量 (batch_size, num_classes) logits torch.randn(4, 10) # 正确写法明确指定 dim1 log_probs F.log_softmax(logits, dim1) # 无警告 print(log_probs.shape) # torch.Size([4, 10]) # 或者使用 dim-1等价于 dim1 对于 2D 张量 log_probs F.log_softmax(logits, dim-1) # 无警告方案二使用 nn.LogSoftmax 模块import torch.nn as nn # 在模型定义中使用 nn.LogSoftmax class Classifier(nn.Module): def __init__(self, input_dim, num_classes): super().__init__() self.fc nn.Linear(input_dim, num_classes) self.log_softmax nn.LogSoftmax(dim1) # 在 __init__ 中指定 dim def forward(self, x): x self.fc(x) return self.log_softmax(x) model Classifier(784, 10) logits model(torch.randn(4, 784)) print(logits.shape) # torch.Size([4, 10])方案三使用 CrossEntropyLoss 替代# 如果使用 log_softmax nll_loss 的组合 # 可以直接用 CrossEntropyLoss 替代内部已包含 log_softmax # 旧写法触发警告 log_probs F.log_softmax(logits) # 警告 loss F.nll_loss(log_probs, target) # 新写法推荐 criterion nn.CrossEntropyLoss() loss criterion(logits, target) # 无需手动 log_softmax方案四处理多维张量# NLP 场景3D 张量 (batch, seq_len, vocab_size) logits torch.randn(4, 20, 1000) # 在词汇表维度上做 softmaxdim2 或 dim-1 log_probs F.log_softmax(logits, dim-1) # 正确 # 在序列维度上做 softmaxdim1- 注意力权重 attention_logits torch.randn(4, 20, 20) attention_weights F.softmax(attention_logits, dim1) # 明确指定 dim1 # 4D 张量 (batch, channels, height, width) feature_map torch.randn(4, 64, 32, 32) # 在通道维度上做 softmax channel_weights F.softmax(feature_map, dim1) # 明确指定 dim1方案五批量修复旧代码import re from pathlib import Path def fix_log_softmax_in_file(file_path): 自动修复文件中的 log_softmax/softmax 调用 with open(file_path, r) as f: content f.read() original_content content # 修复 F.log_softmax(x) - F.log_softmax(x, dim1) # 修复 F.softmax(x) - F.softmax(x, dim1) # 需要处理各种调用模式 patterns [ # F.log_softmax(variable) - F.log_softmax(variable, dim1) (rF\.log_softmax\(([^,)])\)(?!\s*,), rF.log_softmax(\1, dim1)), # F.softmax(variable) - F.softmax(variable, dim1) (rF\.softmax\(([^,)])\)(?!\s*,), rF.softmax(\1, dim1)), # torch.log_softmax(variable) - torch.log_softmax(variable, dim1) (rtorch\.log_softmax\(([^,)])\)(?!\s*,), rtorch.log_softmax(\1, dim1)), # torch.softmax(variable) - torch.softmax(variable, dim1) (rtorch\.softmax\(([^,)])\)(?!\s*,), rtorch.softmax(\1, dim1)), ] for pattern, replacement in patterns: content re.sub(pattern, replacement, content) if content ! original_content: with open(file_path, w) as f: f.write(content) print(f已修复: {file_path}) return True return False # 使用示例 # fix_log_softmax_in_file(train.py)完整修复代码 完整的分类训练示例 正确使用 log_softmax 和 softmax import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset import warnings # 抑制其他不相关的警告但保留 log_softmax 警告 # warnings.filterwarnings(error, categoryUserWarning, moduletorch) # 模型定义 class MLPClassifier(nn.Module): 多层感知机分类器 - 正确使用 log_softmax def __init__(self, input_dim784, hidden_dim256, num_classes10): super().__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, num_classes) self.relu nn.ReLU() self.dropout nn.Dropout(0.3) # 使用 nn.LogSoftmax 模块在初始化时指定 dim self.log_softmax nn.LogSoftmax(dim1) def forward(self, x): x self.relu(self.fc1(x)) x self.dropout(x) x self.relu(self.fc2(x)) x self.dropout(x) x self.fc3(x) # 使用模块化的 LogSoftmax return self.log_softmax(x) class MLPClassifierV2(nn.Module): 使用 F.log_softmax 的版本 def __init__(self, input_dim784, hidden_dim256, num_classes10): super().__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, num_classes) self.relu nn.ReLU() self.dropout nn.Dropout(0.3) def forward(self, x): x self.relu(self.fc1(x)) x self.dropout(x) x self.relu(self.fc2(x)) x self.dropout(x) x self.fc3(x) # 正确写法明确指定 dim1 return F.log_softmax(x, dim1) class MLPClassifierV3(nn.Module): 使用 CrossEntropyLoss 的版本推荐 def __init__(self, input_dim784, hidden_dim256, num_classes10): super().__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, num_classes) self.relu nn.ReLU() self.dropout nn.Dropout(0.3) def forward(self, x): x self.relu(self.fc1(x)) x self.dropout(x) x self.relu(self.fc2(x)) x self.dropout(x) x self.fc3(x) # 不需要 log_softmaxCrossEntropyLoss 内部会处理 return x # 自定义损失函数 class FocalLoss(nn.Module): Focal Loss - 正确使用 log_softmax def __init__(self, alpha1.0, gamma2.0, num_classes10): super().__init__() self.alpha alpha self.gamma gamma self.num_classes num_classes def forward(self, logits, targets): # 正确写法明确指定 dim1 log_probs F.log_softmax(logits, dim1) # 获取目标类别的 log probability targets_one_hot F.one_hot(targets, self.num_classes).float() pt torch.exp(log_probs) * targets_one_hot pt pt.sum(dim1) # 正确指定 dim # Focal loss 公式 focal_weight (1 - pt) ** self.gamma loss -focal_weight * (log_probs.gather(1, targets.unsqueeze(1)).squeeze(1)) return loss.mean() class LabelSmoothingLoss(nn.Module): 标签平滑损失 - 正确使用 log_softmax def __init__(self, num_classes10, smoothing0.1): super().__init__() self.num_classes num_classes self.smoothing smoothing self.confidence 1.0 - smoothing def forward(self, logits, targets): # 正确写法明确指定 dim1 log_probs F.log_softmax(logits, dim1) # 创建平滑标签 with torch.no_grad(): smooth_labels torch.zeros_like(log_probs) smooth_labels.fill_(self.smoothing / (self.num_classes - 1)) smooth_labels.scatter_(1, targets.unsqueeze(1), self.confidence) # 计算损失 loss (-smooth_labels * log_probs).sum(dim1) # 正确指定 dim return loss.mean() # 注意力机制 class SelfAttention(nn.Module): 自注意力机制 - 正确使用 softmax def __init__(self, hidden_dim, num_heads8): super().__init__() self.hidden_dim hidden_dim self.num_heads num_heads self.head_dim hidden_dim // num_heads self.query nn.Linear(hidden_dim, hidden_dim) self.key nn.Linear(hidden_dim, hidden_dim) self.value nn.Linear(hidden_dim, hidden_dim) self.dropout nn.Dropout(0.1) def forward(self, x): batch_size, seq_len, _ x.shape # 计算 Q, K, V Q self.query(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) K self.key(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) V self.value(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2) # 注意力分数 scores torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5) # 正确写法在最后一个维度上做 softmax attention_weights F.softmax(scores, dim-1) attention_weights self.dropout(attention_weights) # 加权求和 context torch.matmul(attention_weights, V) context context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_dim) return context, attention_weights # 训练函数 def train_model(model, train_loader, criterion, optimizer, device, num_epochs10): 训练模型 model.to(device) for epoch in range(num_epochs): model.train() total_loss 0.0 correct 0 total 0 for batch_idx, (data, targets) in enumerate(train_loader): data, targets data.to(device), targets.to(device) optimizer.zero_grad() # 前向传播 if isinstance(criterion, nn.CrossEntropyLoss): # CrossEntropyLoss 不需要 log_softmax logits model(data) loss criterion(logits, targets) outputs logits else: # 其他损失函数需要 log_softmax 输出 log_probs model(data) loss criterion(log_probs, targets) outputs log_probs # 反向传播 loss.backward() optimizer.step() # 统计 total_loss loss.item() _, predicted outputs.max(1) total targets.size(0) correct predicted.eq(targets).sum().item() avg_loss total_loss / len(train_loader) accuracy 100. * correct / total print(fEpoch [{epoch1}/{num_epochs}] Loss: {avg_loss:.4f}, Acc: {accuracy:.2f}%) return model def create_synthetic_data(num_samples2000, input_dim784, num_classes10): 创建合成数据 X torch.randn(num_samples, input_dim) y torch.randint(0, num_classes, (num_samples,)) # 添加可学习模式 for i in range(num_samples): X[i, y[i]] 3.0 return TensorDataset(X, y) def main(): 主函数对比不同实现方式 device torch.device(cuda if torch.cuda.is_available() else cpu) print(f设备: {device}) # 数据 dataset create_synthetic_data(2000, 784, 10) train_loader DataLoader(dataset, batch_size64, shuffleTrue) # 方式1LogSoftmax NLLLoss print(\n *60) print(方式1: nn.LogSoftmax nn.NLLLoss) print(*60) model1 MLPClassifier(784, 256, 10) criterion1 nn.NLLLoss() optimizer1 optim.Adam(model1.parameters(), lr0.001) train_model(model1, train_loader, criterion1, optimizer1, device, num_epochs5) # 方式2F.log_softmax NLLLoss print(\n *60) print(方式2: F.log_softmax(dim1) nn.NLLLoss) print(*60) model2 MLPClassifierV2(784, 256, 10) criterion2 nn.NLLLoss() optimizer2 optim.Adam(model2.parameters(), lr0.001) train_model(model2, train_loader, criterion2, optimizer2, device, num_epochs5) # 方式3CrossEntropyLoss推荐 print(\n *60) print(方式3: CrossEntropyLoss (推荐)) print(*60) model3 MLPClassifierV3(784, 256, 10) criterion3 nn.CrossEntropyLoss() optimizer3 optim.Adam(model3.parameters(), lr0.001) train_model(model3, train_loader, criterion3, optimizer3, device, num_epochs5) # 方式4Focal Loss print(\n *60) print(方式4: Focal Loss with log_softmax(dim1)) print(*60) model4 MLPClassifierV3(784, 256, 10) # 输出 logits criterion4 FocalLoss(alpha1.0, gamma2.0, num_classes10) optimizer4 optim.Adam(model4.parameters(), lr0.001) train_model(model4, train_loader, criterion4, optimizer4, device, num_epochs5) # 方式5标签平滑 print(\n *60) print(方式5: Label Smoothing Loss with log_softmax(dim1)) print(*60) model5 MLPClassifierV3(784, 256, 10) criterion5 LabelSmoothingLoss(num_classes10, smoothing0.1) optimizer5 optim.Adam(model5.parameters(), lr0.001) train_model(model5, train_loader, criterion5, optimizer5, device, num_epochs5) # 注意力机制演示 print(\n *60) print(自注意力机制: softmax(dim-1)) print(*60) attention SelfAttention(hidden_dim256, num_heads8).to(device) dummy_input torch.randn(4, 20, 256).to(device) context, weights attention(dummy_input) print(f输入形状: {dummy_input.shape}) print(f输出形状: {context.shape}) print(f注意力权重形状: {weights.shape}) # 验证 softmax 正确性 weight_sum weights.sum(dim-1) print(f注意力权重和 (应为1): {weight_sum[0, 0, :3]}) def demonstrate_dim_choices(): 演示不同 dim 参数的效果 print(\n *60) print(不同 dim 参数的效果演示) print(*60) # 2D 张量 logits_2d torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) print(f\n2D 张量: {logits_2d.shape}) # dim0: 沿列方向 softmax sm_dim0 F.softmax(logits_2d, dim0) print(fsoftmax(dim0):\n{sm_dim0}) print(f 列和: {sm_dim0.sum(dim0)}) # dim1: 沿行方向 softmax sm_dim1 F.softmax(logits_2d, dim1) print(fsoftmax(dim1):\n{sm_dim1}) print(f 行和: {sm_dim1.sum(dim1)}) # dim-1: 等价于 dim1最后一个维度 sm_dim_last F.softmax(logits_2d, dim-1) print(fsoftmax(dim-1) 等价于 dim1: {torch.allclose(sm_dim1, sm_dim_last)}) # 3D 张量 logits_3d torch.randn(2, 3, 4) print(f\n3D 张量: {logits_3d.shape}) # dim0 sm_0 F.softmax(logits_3d, dim0) print(fsoftmax(dim0) 形状: {sm_0.shape}, dim0 和: {sm_0.sum(dim0).flatten()[:4]}) # dim1 sm_1 F.softmax(logits_3d, dim1) print(fsoftmax(dim1) 形状: {sm_1.shape}, dim1 和: {sm_1.sum(dim1).flatten()[:4]}) # dim2 / dim-1 sm_2 F.softmax(logits_3d, dim2) print(fsoftmax(dim2) 形状: {sm_2.shape}, dim2 和: {sm_2.sum(dim2).flatten()[:4]}) if __name__ __main__: main() demonstrate_dim_choices()常见陷阱与注意事项1. dim 参数选择错误# 错误在分类任务中使用了 dim0 logits torch.randn(4, 10) # (batch, classes) log_probs F.log_softmax(logits, dim0) # 错误在 batch 维度上做 softmax # 正确分类任务应该在类别维度上做 softmax log_probs F.log_softmax(logits, dim1) # 正确 # 或 log_probs F.log_softmax(logits, dim-1) # 正确等价于 dim12. CrossEntropyLoss 与 log_softmax 的重复# 错误CrossEntropyLoss 内部已经包含 log_softmax log_probs F.log_softmax(logits, dim1) # 多余 loss F.cross_entropy(log_probs, target) # 对 log_softmax 再做 log_softmax # 正确直接使用 logits loss F.cross_entropy(logits, target) # 正确3. NLLLoss 需要 log_softmax 输入# NLLLoss 期望输入是 log probabilities # 错误传入 logits loss F.nll_loss(logits, target) # 错误logits 不是 log probabilities # 正确先做 log_softmax log_probs F.log_softmax(logits, dim1) loss F.nll_loss(log_probs, target) # 正确4. dim-1 的通用性# dim-1 总是指向最后一个维度 # 对于不同维度的张量dim-1 的含义不同 # 2D: dim-1 dim1 F.softmax(torch.randn(4, 10), dim-1) # 在类别维度 # 3D: dim-1 dim2 F.softmax(torch.randn(4, 20, 1000), dim-1) # 在词汇表维度 # 使用 dim-1 可以使代码更通用 # 因为无论输入维度如何都在最后一个维度上操作5. nn.Softmax 与 F.softmax 的区别# nn.Softmax 是模块在 __init__ 中指定 dim module nn.Softmax(dim1) output module(input) # F.softmax 是函数在调用时指定 dim output F.softmax(input, dim1) # 两者功能相同选择取决于使用场景 # 模块适合放在 nn.Sequential 中 # 函数适合在 forward 中灵活使用6. 抑制警告的正确方式import warnings # 不推荐全局抑制警告 # warnings.filterwarnings(ignore) # 推荐修复代码明确指定 dim F.log_softmax(logits, dim1) # 如果必须临时抑制不推荐 with warnings.catch_warnings(): warnings.simplefilter(ignore) output F.log_softmax(logits) # 临时抑制总结Implicit dimension choice for log_softmax has been deprecated警告虽然不会中断程序运行但应该及时修复以确保代码的清晰性和未来兼容性。修复方式代码示例推荐程度明确指定 dimF.log_softmax(x, dim1)★★★★★使用 nn.LogSoftmaxnn.LogSoftmax(dim1)★★★★☆使用 CrossEntropyLossnn.CrossEntropyLoss()★★★★★使用 dim-1F.log_softmax(x, dim-1)★★★★☆核心要点始终明确指定dim参数不要依赖隐式推断分类任务推荐使用CrossEntropyLoss它内部已包含log_softmax无需手动调用dim-1是通用的选择它总是指向最后一个维度适合处理不同维度的输入注意NLLLoss需要 log probabilities 输入必须先做log_softmax不要对CrossEntropyLoss的输入再做log_softmax否则会导致双重 log softmax在注意力机制中softmax通常在dim-1上操作使注意力权重在最后一个维度上归一化通过理解dim参数的作用和不同损失函数的期望输入开发者可以编写出更清晰、更健壮的 PyTorch 代码。