初始化相同PyTorch ANN后,Ray Tune调参返回不同最优超参的问题求助
相同初始参数ANN的Ray Tune调优结果不一致问题排查
我用PyTorch初始化了两个结构与初始参数完全一致的ANN,但在使用Ray Tune进行超参数调优时,两者返回的最优超参数组合却不相同,且未进行任何随机初始化操作。相关代码如下:
ANN初始化
class Featrues_model(nn.Module): def __init__(self, n_inputs, dim_hidden, n_outputs): super().__init__() self.fc1 = nn.Linear(n_inputs, dim_hidden) self.fc2 = nn.Linear(dim_hidden, n_outputs) def forward(self, X): X = self.fc1(X) X = self.fc2(X) return X features_model_v1 = Featrues_model(len(list_input_variables),5,6) features_model_v2 = Featrues_model(len(list_input_variables),5,6) features_model_v2.load_state_dict(features_model_v1.state_dict())
超参数设置
config = { "lr": tune.choice([1e-2, 1e-5]), "weight_decay": tune.choice([1e-2, 1e-5]), "batch_size": tune.choice([16,64]), "epochs": tune.choice([10,50]) }
训练与验证数据集
trainset = df_final.copy() test_abs = int(len(trainset) * 0.8) train_subset, val_subset = random_split( trainset, [test_abs, len(trainset) - test_abs] ) df_train = df_final.iloc[train_subset.indices] df_val = df_final.iloc[val_subset.indices]
训练函数设计
def setting_model(config, df_train, df_val, model): criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=config["lr"], weight_decay=config["weight_decay"]) BATCH_SIZE = config["batch_size"] for epoch in range(config["epochs"]): train_epoch_loss = 0 train_epoch_acc = 0 step = 0 for i in tqdm(range(0, df_train.shape[0], BATCH_SIZE)): batch_X = np.array( df_train[list_input_variables].iloc[i:i+BATCH_SIZE] ) batch_X = torch.Tensor([x for x in batch_X]) batch_Y = np.array( df_train[list_output_variables].iloc[i:i+BATCH_SIZE] ) batch_Y = torch.Tensor([int(y) for y in batch_Y]) batch_Y = batch_Y.type(torch.int64) optimizer.zero_grad() outputs = model.forward(batch_X) train_loss = criterion(outputs, batch_Y) train_acc = multi_acc(outputs, batch_Y) train_loss.backward() optimizer.step() train_epoch_loss += train_loss.item() train_epoch_acc += train_acc.item() step += 1 # print statistics print(f"Epochs: {epoch}") print(f"Train Loss: {train_epoch_loss/len(df_train)}") print(f"Train Acc: {train_epoch_acc/step}") print("\n") # Validation loss with torch.no_grad(): X_val = np.array( df_val[list_input_variables] ) X_val = torch.Tensor([x for x in X_val]) Y_val = np.array( df_val[list_output_variables] ) Y_val = torch.Tensor([int(y) for y in Y_val]) Y_val = Y_val.type(torch.int64) outputs = model.forward(X_val) _, predicted = torch.max(outputs.data, 1) total = Y_val.size(0) correct = (predicted == Y_val).sum().item() loss = criterion(outputs, Y_val) tune.report(loss=(loss.numpy()), accuracy=correct / total) print(f"Validation Loss: {loss.numpy()/len(df_val)}") print(f"Validation Acc: {correct / total:.3f}") print("Finished Training")
超参数调优
result_v1 = tune.run( partial(setting_model, df_train=df_train, df_val=df_val, model=features_model_v1), config=config, fail_fast="raise", ) result_v2 = tune.run( partial(setting_model, df_train=df_train, df_val=df_val, model=features_model_v2), config=config, fail_fast="raise" )
输出结果
result_v1.get_best_config() {'lr': 1e-05, 'weight_decay': 1e-05, 'epochs': 1} result_v2.get_best_config() {'lr': 0.01, 'weight_decay': 1e-05, 'epochs': 1}
问题根源分析
1. 模型参数被原地修改
两次调优复用了同一个模型实例,第一次调优结束后,模型参数已经被训练更新,第二次调优是基于训练后的参数启动,而非最初的初始化参数。即便你一开始复制了参数,训练过程会直接修改实例的参数,导致第二次调优的初始状态完全偏离预期。
2. 数据集划分的随机性未固定
random_split未设置随机种子,不同运行(包括调优内部的试验)的训练/验证划分可能存在差异,直接影响超参数的评估结果。
3. 未固定全局随机种子
PyTorch优化器(如Adam)、NumPy等模块的随机性未被固定,加上Ray Tune试验执行顺序的随机性,会导致不同调优任务的结果出现偏差。
修复方案
1. 每次试验重新初始化模型
不在调优外部传递已创建的模型实例,而是在训练函数内部每次试验都重新创建并加载初始参数:
# 修改训练函数 def setting_model(config, df_train, df_val, n_inputs, dim_hidden, n_outputs, init_state_dict): # 每次试验创建新模型,加载初始参数 model = Featrues_model(n_inputs, dim_hidden, n_outputs) model.load_state_dict(init_state_dict) # 后续训练逻辑不变 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=config["lr"], weight_decay=config["weight_decay"]) BATCH_SIZE = config["batch_size"] # ... 其余训练代码
调用调优时,传递初始状态字典:
# 预先保存初始模型的状态字典 init_model = Featrues_model(len(list_input_variables),5,6) init_state_dict = init_model.state_dict() # 两次调优共享同一初始状态 result_v1 = tune.run( partial(setting_model, df_train=df_train, df_val=df_val, n_inputs=len(list_input_variables), dim_hidden=5, n_outputs=6, init_state_dict=init_state_dict), config=config, fail_fast="raise", ) result_v2 = tune.run( partial(setting_model, df_train=df_train, df_val=df_val, n_inputs=len(list_input_variables), dim_hidden=5, n_outputs=6, init_state_dict=init_state_dict), config=config, fail_fast="raise" )
2. 固定所有随机种子
在代码开头添加全局种子固定逻辑:
import random import numpy as np import torch seed = 42 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False
同时,划分数据集时指定种子:
train_subset, val_subset = random_split( trainset, [test_abs, len(trainset) - test_abs], generator=torch.Generator().manual_seed(seed) )
3. 固定Ray Tune的搜索随机性
为Ray Tune的搜索算法设置固定种子,保证两次调优的超参数搜索顺序一致:
from ray.tune.suggest.basic_variant import BasicVariantGenerator # 创建带固定种子的搜索器 searcher = BasicVariantGenerator(random_state=seed) result_v1 = tune.run( partial(...), config=config, fail_fast="raise", search_alg=searcher ) # 第二次调优使用相同种子的搜索器 searcher_v2 = BasicVariantGenerator(random_state=seed) result_v2 = tune.run( partial(...), config=config, fail_fast="raise", search_alg=searcher_v2 )
内容的提问来源于stack exchange,提问作者Lorenzo Boletti
相关产品推荐
相关产品推荐

