You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

初始化相同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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 16:55:27