PyTorch中用eval执行优化器字符串为何导致函数报错?
我编写了如下PyTorch Lightning类:
import torch.optim as optim class GraphLevelGNN(pl.LightningModule): def __init__(self,**model_kwargs): super().__init__() self.save_hyperparameters() self.model = GraphGNNModel(**model_kwargs) self.loss_module = nn.BCEWithLogitsLoss() self.optimizer = eval('optim.SGD(self.parameters(),lr=0.1)') def forward(self, data, mode="train"): x, edge_index, batch_idx = data.x, data.edge_index, data.batch x = self.model(x, edge_index, batch_idx) x = x.squeeze(dim=-1) if self.hparams.c_out == 1: preds = (x > 0).float() data.y = data.y.float() else: preds = x.argmax(dim=-1) loss = self.loss_module(x, data.y) acc = (preds == data.y).sum().float() / preds.shape[0] return loss, acc,preds def configure_optimizers(self): optimizer = self.optimizer return optimizer def training_step(self, batch, batch_idx): loss, acc, _ = self.forward(batch, mode="train") self.log('train_loss', loss,on_epoch=True,logger=True,batch_size=64) self.log('train_acc', acc,on_epoch=True,logger=True,batch_size=64) def validation_step(self, batch, batch_idx): loss, acc,_ = self.forward(batch, mode="val") self.log('val_acc', acc,on_epoch=True,logger=True,batch_size=64) self.log('val_loss', loss,on_epoch=True,logger=True,batch_size=64) def test_step(self, batch, batch_idx): loss,acc, preds = self.forward(batch, mode="test") self.log('test_acc', acc,on_epoch=True,logger=True,batch_size=64)
我希望将优化器放入Ray Tune对象中以避免硬编码,但发现:在__init__中直接写self.optimizer = optim.SGD(self.parameters(),lr=0.1)时脚本正常运行,换成self.optimizer = eval('optim.SGD(self.parameters(),lr=0.1)')则报错:
File "script.py", line 560, in __init__ self.optimizer = eval('optim.SGD(self.parameters(),lr=0.1)') File "<string>", line 1, in <module> NameError: name 'optim' is not defined
将字符串中的optim改为torch.optim后仍出现相同错误,想了解为何eval无法将字符串转换为对应代码执行?
原因与解决方法
1. eval找不到optim的核心原因
eval()执行字符串代码时,会使用当前局部作用域查找变量。在__init__方法里,虽然全局导入了import torch.optim as optim,但eval的局部作用域默认不包含全局的optim别名,所以会抛出NameError。哪怕换成torch.optim也不行,因为torch同样不在eval的局部作用域里。
2. 替代eval的安全动态优化器方案
直接用eval处理代码字符串不仅容易出作用域问题,还存在安全风险。如果要配合Ray Tune实现动态优化器配置,推荐以下两种更可靠的方式:
方式一:字典映射优化器类
提前把可用的优化器类存入字典,通过参数指定选择:
import torch.optim as optim class GraphLevelGNN(pl.LightningModule): def __init__(self, optimizer_name="SGD", lr=0.1, **model_kwargs): super().__init__() self.save_hyperparameters() self.model = GraphGNNModel(**model_kwargs) self.loss_module = nn.BCEWithLogitsLoss() # 优化器字典映射 optimizer_dict = { "SGD": optim.SGD, "Adam": optim.Adam, "AdamW": optim.AdamW } self.optimizer_cls = optimizer_dict[optimizer_name] self.lr = lr def configure_optimizers(self): # 在configure_optimizers里实例化优化器(Lightning推荐时机) return self.optimizer_cls(self.parameters(), lr=self.lr)
这种方式完全避免了eval,配合Ray Tune时,只需在调参搜索空间中指定optimizer_name和lr的可选值即可。
方式二:直接传递优化器类与参数
如果需要更灵活的配置,直接把优化器类和参数作为model_kwargs传入:
class GraphLevelGNN(pl.LightningModule): def __init__(self, optimizer_cls=optim.SGD, optimizer_params={"lr":0.1}, **model_kwargs): super().__init__() self.save_hyperparameters() self.model = GraphGNNModel(**model_kwargs) self.loss_module = nn.BCEWithLogitsLoss() self.optimizer_cls = optimizer_cls self.optimizer_params = optimizer_params def configure_optimizers(self): return self.optimizer_cls(self.parameters(), **self.optimizer_params)
配合Ray Tune时,在调参配置里指定optimizer_cls和optimizer_params的搜索范围即可。
3. Lightning优化器的最佳实践
代码里在__init__中实例化优化器是不推荐的。PyTorch Lightning建议在configure_optimizers方法内实例化优化器,原因如下:
- 模型参数可能在
__init__之后才完成初始化(比如使用延迟初始化场景) - 多GPU训练时,Lightning会自动处理优化器的分布式适配逻辑
内容的提问来源于stack exchange,提问作者Slowat_Kela

