超参优化

2024-06-11

optuna

文档:https://optuna.readthedocs.io/

博客:https://medium.com/optuna

集群性能测试

0 P2c_BTC_T720_III_v8
COMPLETE: 8320
m8:     平均 = 1.91, 中位 = 1.91 (2837)
M10:    平均 = 1.93, 中位 = 1.88 (2771)
bric:   平均 = 3.60, 中位 = 3.60 (382)
m1:     平均 = 3.68, 中位 = 3.67 (374)
m5:     平均 = 4.42, 中位 = 4.43 (775)
M4:     平均 = 4.61, 中位 = 4.68 (1181)

4 P4_BTC_T720_III_v7
m8:     平均 = 2.01, 中位 = 2.00 (2797|7)
bric:   平均 = 3.78, 中位 = 3.78 (374|2)
m10:    平均 = 3.78, 中位 = 3.74 (1493|6)
m1:     平均 = 3.86, 中位 = 3.85 (366|1)
m5:     平均 = 4.36, 中位 = 4.34 (809|5)
m4:     平均 = 4.68, 中位 = 4.65 (1206|6)

3 P4_ETH_T720_III_v6
m8:     平均 = 1.89, 中位 = 1.90 (800)
m10:    平均 = 3.45, 中位 = 3.45 (439)
bric:   平均 = 3.55, 中位 = 3.56 (107)
m1:     平均 = 3.64, 中位 = 3.64 (105)
m5:     平均 = 4.14, 中位 = 4.15 (229)
m4:     平均 = 4.42, 中位 = 4.42 (340)

2 P4_BTC_T720_II_v6
m8:     平均 = 1.93, 中位 = 1.94 (1809)
m10:    平均 = 3.53, 中位 = 3.54 (996)
bric:   平均 = 3.61, 中位 = 3.62 (243)
m1:     平均 = 3.72, 中位 = 3.72 (237)
m5:     平均 = 4.25, 中位 = 4.25 (517)
m4:     平均 = 4.53, 中位 = 4.52 (776)

1 P4_BTC_T720_III_v6
m8:     平均 = 1.98, 中位 = 1.97 (2806)
m10:    平均 = 3.71, 中位 = 3.68 (1512)
bric:   平均 = 3.71, 中位 = 3.70 (380)
m1:     平均 = 3.82, 中位 = 3.81 (368)
m5:     平均 = 4.37, 中位 = 4.35 (802)
m4:     平均 = 4.70, 中位 = 4.67 (1190)

数据库(redis:)
P3_ETH_T720_tpe_v6.1
m8:     平均 = 1.87, 中位 = 1.87
bric:   平均 = 3.61, 中位 = 3.60
m1:     平均 = 3.68, 中位 = 3.69
m10:    平均 = 3.89, 中位 = 3.89
m5:     平均 = 4.11, 中位 = 4.10
m4:     平均 = 5.40, 中位 = 5.39
耗时:0.383秒

P3_ETH_T720_II_v6.1
m8:     平均 = 1.92, 中位 = 1.92
bric:   平均 = 3.67, 中位 = 3.67
m1:     平均 = 3.73, 中位 = 3.73
m10:    平均 = 3.95, 中位 = 3.96
m5:     平均 = 4.16, 中位 = 4.17
m4:     平均 = 5.47, 中位 = 5.49
耗时:0.333秒

P3_ETH_T720_III_v6
m8:     平均 = 1.94, 中位 = 1.94
m9:     平均 = 1.99, 中位 = 1.98
m10:    平均 = 3.48, 中位 = 3.48
bric:   平均 = 3.58, 中位 = 3.59
m1:     平均 = 3.69, 中位 = 3.70
m4:     平均 = 4.53, 中位 = 4.52
耗时:0.616秒

性能优化

分布式性能优化

NSGAIIISampler 非支配排序遗传算法NSGA-III

Pareto Front 帕累托最优

1.增大 population_size

建议值:一般而言,种群大小在100到300之间可以提供一个好的开始点。但是,在分布式环境下,可以利用更多的计算单元来处理更大的种群,从而能够探索更广泛的搜索空间。可以尝试将种群大小设置为500到1000,以观察是否能获得更好的结果。

TPESampler 减少参数重复的策略:

1.使用 Constant Liar 策略

constant_liar 是 TPESampler 的一个参数,当设置为 True 时,会在分布式环境中减少参数重复的情况。当一个试验开始但尚未完成时,Constant Liar 策略会假设该试验的目标值为“常数”(“lie”)。这样做可以在参数空间中“占位”,让后续的采样趋向于选择不同的参数。该策略特别适用于每次评估代价较高的情况,可以有效减少不同工作节点选择重复参数的可能性。

2.增大 n_startup_trials

n_startup_trials 参数控制在开始使用TPE算法之前,Optuna 会进行多少次随机采样。通过增大这个数值,可以在初始阶段增加参数的多样性,间接减少后续过程中参数选择的重复性。

3.增大 n_ei_candidates

从理论上讲,增加 n_ei_candidates 的值会使得算法在决定每一步的参数值时有更多的候选点考虑,这可能会增加选择不同参数的可能性,从而在一定程度上减少在分布式环境下的参数重复。

寻参函数改进

optuna在分布式超参优化场景下,采样器对重复的trial参数会用竞争策略,这将导致集群环境下重复评估,训练高负载和全域参数空间较小情况下浪费大量算力。

改造下寻参函数,将重复入参COMPLETE状态直接取值跳过训练过程,RUNNING状态直接返回空(optuna会将空置为Fail状态):

# 求最优超参数
def f(trial):
    # 定义要找的超参数,并设置上下限
    params = {
        'minima_size': trial.suggest_int('minima_size', args.minima_size_a, args.minima_size_b,step=minima_size_STEP),
    }

    print()
    t_list = trial.study.get_trials(states=[TrialState.COMPLETE,TrialState.RUNNING])
    n = trial._cached_frozen_trial
    for t in t_list:
        if n.number == t.number:
            continue
        if n.params == t.params:
            if 'HOST' in t.user_attrs and 'host' in t.user_attrs['HOST']:
                host_s = t.user_attrs['HOST']['host']
            else:
                host_s = ""
            HOST_dic = {"host": socket.gethostname(),"msg":None,"number": t.number, "HOST_s": host_s}
            if t.state == TrialState.RUNNING:
                HOST_dic["msg"] = "RUNNING"
                print("超参重复将跳过",t.params,HOST_dic)
                trial.set_user_attr("HOST", HOST_dic)
                return
                # raise optuna.TrialPruned # 这里不再剪枝
            else:
                HOST_dic["msg"] = "COMPLETE"
                print("超参重复将跳过",t.params,HOST_dic)
                trial.set_user_attr("HOST", HOST_dic)
                return t.value
                # raise optuna.TrialPruned # 这里不再剪枝
    trial.set_user_attr("HOST", {"host": socket.gethostname()})

    # 评估超参数
    return _c.params_test(args,params,MC)

网格搜索使用步进函数

网格搜索需要定义搜索空间,遇到连续枚举时,用步进函数替代它:

def generate_float_list(start, end, step):
    """生成一个从 start 到 end 的列表,步长为 step。

    Args:
        start: 列表的起始值。
        end: 列表的结束值。
        step: 列表的步长。

    Returns:
        一个从 start 到 end 的列表,步长为 step。
    """

    list = []
    current = start
    while current <= end:
        list.append(current)
        current += step
    return list

search_space = {"minima_size":generate_float_list(args.minima_size_a, args.minima_size_b,step=minima_size_STEP)}

print("优化器:",study_name)

study = optuna.create_study(
    sampler=GridSampler(search_space),
    study_name=study_name,
    direction='maximize',
    load_if_exists=True,
    storage=args.sqluri
    )

优化退出

优化过程中直接退出会导致任务一直处于Running状态,可以通过接收信号后抛出OptunaError异常来让主优化器修改状态,另外可以通过捕获SIGINT来让主优化器在当前训练结束后再停止,

使用信号处理:捕获如SIGINTSIGTERM等信号,并定义一个处理函数来优雅地终止Optuna的优化过程。如:

import signal
import optuna

study = optuna.create_study()

def signal_handler(sig, frame):
	print(f"\n\n收到停止信号: {sig} !!!\n\n")
	args.optuna_stop = True   # 全局停止 用于循环体中退出
	study.stop()              # 优化器停止
	if sig != signal.SIGINT:  # 不是ctrl+c
		raise optuna.exceptions.OptunaError("\n\n立即中断当前训练 !!!\n\n")
# 绑定信号处理器
signal.signal(signal.SIGTERM, signal_handler)   # kill
signal.signal(signal.SIGINT, signal_handler)    # ctrl+c

study.optimize(objective, n_trials=100)

Docker Swarm集群

搭建过程

创建主manager

dk swarm init --advertise-addr=172.16.x.x

查看token

docker swarm join-token worker
docker swarm join-token manager

查看集群节点状态

docker node ls

集群解散

docker swarm leave --force   # 在manager的话加--force
docker node rm [hostname]    # 在manager上操作

启动编排

docker stack deploy -c docker-compose.yml ml-stack