irace package — 参数调优神器


作者:chegxy
说明:转载请注明出处,原文在博客园中,如有勘误会及时更新,喜欢还请点个赞再走吧


1. irace 是什么

相信许多做过算法的同学都知道参数调优这个过程,例如在做PID算法的时候,需要调节P、I以及D这三个参数以使得系统可以快速的达到期望值,又或者在做进化算法时,你需要调整种群规模、交叉概率、遗传概率以及其他一些参数。当然啦,这些参数都可以凭自己的经验去调整,但这样依然略显麻烦。这时,irace出现了,其目的就是通过迭代计算从若干组参数中得到一组最优参数,其算法也是使用的进化计算,感兴趣的小伙伴可以看看这篇文章。irace package 是实现该调优算法的软件包,使用的语言是R语言(学统计学的朋友应该非常熟悉了),即使你没学过R语言也没有关系,你只需要学会怎么配置调优环境即可,接下来我们会详细讨论这个问题。

2. 安装 irace

提前申明一下,以下内容均以Ubuntu系统为例,更多详细内容请阅读The irace Package: User Guide

  1. 首先你需要先安装R语言环境,运行命令 sudo apt install r-base ,中间可能会有两次提示,第一次回车,第二次输入 y 回车,安装完成后命令行运行命令 R ,出现版本说明以及提示行表示安装成功;
  2. 输入 install.packages("irace") 回车,出现以下内容:

  1. 接下来,会弹处一个对话框,让你选择镜像站点,你选择一个国内的,点击 ok 开始安装;
  2. 安装完成后,输入 library("irace") 看是否会报错,没有信息说明安装成功,输入 q() 退出 R 命令行。
  3. 为了可以在任意位置运行 irace ,我们还需要将其运行目录添加到环境变量中,运行命令 sudo vim ~/.bashrc,在文件最后添加如下内容(记得将 IRACE_HOME 修改为你自己的 irace 安装目录):

  1. 保存退出编辑,运行命令 irace --help ,正常显示帮助信息说明配置成功。

3. irace 的运行机制

下面的一张图是 irace 用户手册中的一张图,展示了 irace 的运行基本流程:

在 irace 运行的开始,用户需要为其配置测试环境,包括测试集、参数空间(也就是参数的取值范围)以及初始参数等,然后 irace 会使用这些配置进行迭代计算用户的程序,同时用户程序必须返回一个用以评估使用该参数获得效果的好坏,这些都是 targetRunner 的任务。通过几次迭代,irace 最终会得到一个相对较好的参数取值反馈给用户。

4. irace 的配置环境

之前谈到,irace 的配置环境是很重要的一环,它为用户提供了足够的自由度来设计他自己的调优过程,其主要由7个文件组成:

  • configurations.txt
  • instances-list.txt
  • scenario.txt
  • forbidden.txt
  • parameters.txt
  • target-runner
  • target-evaluator

这些文件可以在 $IRACE_HOME/templates/ 下找到,接下来我们分别阐述这些配置的作用。

4.1. parameters

参数配置可以通过修改 parameters.txt 实现,在parameters.txt中,所有参数以 <name> <label> <type> <range> [ | <condition> ] 的方式组织起来,每个参数占一行,不使用的参数可以在行首添加 #

name 表示的是参数名,是一个字符串,不需要加引号。

label 用于作为在命令行中传递这个参数的标识符,例如label是 "--ants ", 那么当取该参数值为5时,会在命令行中使用 --ants 5,需要注意的是这一项引号和字符串后的空格是需要加上的。

type 表示该参数的类型,使用单字符表示,无需加引号。range 为参数取值提供了范围,无需加引号。irace 中为参数提供了4种类型:

  • 实数(r),意味着参数可以是浮点数,其range由两个实数确定(,),参数可以是上界或下界,且最后保留的小数位应该是与你在range中小数位相同的。另外支持使用log取值(以e为底,需要将 r 改为 r,log)
  • 整数(i),参数应该是指定范围内的一个整数,是实数的一个特殊类型
  • 分类的参数(c),range 由若干个值组成,例如 (<value 1>, …, ),这些值无需加引号,但如果值中包含空格或者逗号时需要加引号
  • 顺序参数(o),与 c 相似,但其 range 是顺序的,在 irace 内部会将其看作整数处理,对应于值的索引

condition 可以看作是参数的开关,这个项是可选的,当condition成立时,参数会被传递到命令行中,否则不会传递该参数。值得注意的是,该参数是一个遵循R语言规则的条件表达式,且使用该条件后不能导致所有参数的依赖关系出现环。

以parameters.txt中的例子为例:

# 1:            2:                   3: 4:      5:
param1          "--param1 "          i  (1, 10) | mode %in% c("x1", "x2")
param2          "--param2 "          i  (1, 10) | mode %in% c("x1", "x3") && real > 2.5 && real <= 3.5
mode            "--"                 c  ("x1" ,"x2", "x3")
real            "--paramreal="       r  (1.5, 4.5)
mutation        "--mutation="        o  ("none", "very low", "low", "medium", "high", "very high", "all")
#unused         "-u "                c  (1, 2, 10, 20)

param1 会在参数 mode 是 x1 或 x2 时使用,其取值范围为(1,10),param2 会在参数 mode 是 x1 或 x3,且2.5 < real <= 3.5 时使用,其取值范围为(1,10),其他参数都是无条件的,描述比较简单,就不一一列举了。

4.2. target algorithm runner

这个对应于targetRunner,其作为一个脚本调用用户程序,并需要打印出一个用户程序的评估值,默认应该是打印最小值,如果要打印最大值可以将其乘上-1。target-runner调用用户的程序格式为 <id.configuration> <id.instance> <seed> <instance> [bound] <configuration>,其含义如下:

  • id.configuration,一个数值串,唯一标识了一种配置
  • id.instance,一个数值串,唯一标识了一个测试项
  • seed,随机数,忽略确定性算法的种子
  • instance,用于计算的测试项文件位置
  • bound,可选项,执行时间界限,与maxBound相关
  • configuration,参数项

例如:

target-runner 1 113 734618556 /home/cxy/instances/tsp/2000-533.tsp \
-- eas --localsearch 0 -alpha 2.92 --beta 3.06 --rho 0.6 --ants 80

还需要注意的一点是,通过设置 execDir 或 –exec-dir 参数可以指定 target-runner 的运行目录。除此之外,最重要的是修改这个文件中的 EXE 对应的值,将其修改为被测程序的全路径。

4.3. target evaluator

在一般情况下,target-runner在每个测试项上会为每一组参数配置输出一个cost值。但在一些时候,我们需要在每个测试项上,计算完所有的参数配置后再进行cost评估,这时就要用到target evaluator。这个用到的地方有限,因此不做过多阐述,详细说明请参考The irace Package: User Guide

4.4. training instance

测试集可以由 trainInstancesDir 或者 trainInstancesFile 指定,一般情况下,trainInstancesFile 是一个空文件,trainInstancesDir 默认为 ./Instances,因此 irace 会将该目录下的所有文件作为测试项。你也可以编写 trainInstancesFile 文件指定要使用的测试项,每一行表示一个测试项,例如:

# Example training instances file 
100/100-1_100-2.tsp --time 1 
100/100-1_100-3.tsp --time 2 
100/100-1_100-4.tsp --time 3

有趣的是,每一行最后都会完整的作为参数传递给target-runner,且其前缀都是trainInstancesDir表示的字符串,例如:

target-runner 1 113 734718 ./Instances/100/100-1_100-2.tsp --time 1 ...

也正是由于这个特性,training instance 可以不是文件,而是一些其他的符号。

4.5. initial configurations

初始配置可以由 configurationsFile 文件描述,其中每行表示一种配置(每列对应一个参数值),第一行是所有参数的名称,且与之后每列的参数相对应。同时,参数值需要满足条件(在 range 范围内),NA 表示不使用该参数。例如:

## Initial candidate configuration for irace 
algorithm localsearch alpha beta rho  ants nnls dlb q0 rasrank elitistants 
as        0           1.0   1.0  0.95 10   NA   NA  0  NA      NA

4.6. forbidden configurations

这一部分主要用于丢弃掉一些无用的配置项,其由若干个符合R语言的表达式组成,每行一个。例如:

## Examples of valid logical operators are: 
## == != >= <= > < & | ! %in% 
(alpha == 0.0) & (beta == 0.0)

对于这个例子,只要配置项中 alpha 等于 0 且 beta 等于 0,那么该配置项将会被丢弃不做计算,这可以避免计算一些特殊的配置项导致系统崩溃。如果需要非常复杂的表达式,可以参考一下repairing configurations。

4.7. scenario

$IRACE_HOME/templates/ 文件夹下保存了一个 scenario.txt.tmpl 的文件,其内容包含了所有 irace 运行时使用的变量值,通过修改其中的任意项可以配置 irace 运行的默认值,例如,通过修改 execDir 切换 irace 的运行目录,通过修改 parameterFile 指定要使用的参数文件,等等。

5. 一个小例子带你感受一下 irace

假设我们有一组坐标对,现在给出一个函数式,要求计算该式的参数,以至于其可以很好的拟合这些坐标对。我们的这些坐标对提取的是sin函数[-1, 1]区间的点,如下所示:

-1 -0.841471
-0.8 -0.717356
-0.6 -0.564642
-0.4 -0.389418
-0.2 -0.198669
0 0
0.2 0.198669
0.4 0.389418
0.6 0.564642
0.8 0.717356
1 0.841471

用于拟合的函数式为 \(y = ax+bx^3+cx^5\) ,因此代求参数为a,b,c。接下来我们一步步配置 irace 环境去运行它获得最佳参数。

  1. 首先,我们新建一个 irace 的运行目录,执行 mkdir -p ~/Documents/tuning-fit

  2. 切换到新建的目录然后将配置模板文件copy过来,执行命令 cp $IRACE_HOME/templates/*.tmpl ./

  3. 删除没有用到的文件:forbidden.txt.tmpl,target-evaluator.tmpl,将其他文件的tmpl后缀删掉

  4. 修改 scenario.txt,删除下面变量的注释,并修改其值:

parameterFile = "./parameters.txt"
execDir = "./"
trainInstancesDir = "./Instances"
trainInstancesFile = "instances-list.txt"
configurationsFile = "configurations.txt"
targetRunner = "./target-runner"
maxExperiments = 5000
digits = 10
  1. 设置初始值,修改configurations.txt:
a b     c
1 0.000 0.000
  1. 设置测试实例,修改instances-list.txt,并新建Instances文件夹,运行 mkdir Instances,切换到该目录下新建文件instance1,并为其添加测试内容:

instances-list.txt:

## This is an example of specifying instances with a file.

# Each line is an instance relative to trainInstancesDir
# (see scenario.txt.tmpl) and an optional sequence of instance-specific
# parameters that will be passed to target-runnerx when invoked on that
# instance.

# Empty lines and comments are ignored.

instance1

./Instances/instance1:

-1 -0.841471
-0.8 -0.717356
-0.6 -0.564642
-0.4 -0.389418
-0.2 -0.198669
0 0
0.2 0.198669
0.4 0.389418
0.6 0.564642
0.8 0.717356
1 0.841471
  1. 设置参数,修改parameters.txt:
# 1:            2:                   3: 4:      5:
a               "-a "                 r  (0.5, 1.5)
b               "-b "                 r  (-0.500, 0.500)
c               "-c "                 r  (-0.500, 0.500)
  1. 设置target-runner,修改target-runner:
EXE=~/Documents/tuning-fit/bin/fit.py
  1. 编写我们的测试程序,新建目录 mkdir -p ~/Documents/tuning-fit/bin,切换到该目录下,编写python文件fit.py:
#! /usr/bin/python3
import sys, getopt

instance = ""
a = 0.0
b = 0.0
c = 0.0

def main(argv):
    global instance, a, b, c
    try:
        opts, args = getopt.getopt(argv, "i:a:b:c:s:",["instance=","a=","b=","c=","seed="])
    except getopt.GetoptError:
        print("fit.py -i <instance> -a <param a> -b <param b> -c <param c>")
        sys.exit(2)
    for opt, arg in opts:
        if opt in ("-i", "--instance"):
            instance = arg
        elif opt in ("-a", "--a"):
            a = float(arg)
        elif opt in ("-b", "--b"):
            b = float(arg)
        elif opt in ("-c", "--c"):
            c = float(arg)

def get_test(filepath):
    instanceFile = open(filepath, mode = 'r')
    if(instanceFile):
        res = []
        line = instanceFile.readline()
        while line:
            pack = line.split()
            pack = list(map(float, pack))
            res.append(pack)
            line = instanceFile.readline()
        instanceFile.close()
        return res
    else:
        print("Filepath is not existing.")
        return None

def fun(x):
    global a,b,c
    return (a * x + b * x ** 3 + c * x ** 5)

def evaluate(test):
    var = 0.0
    vals = []
    for x , y in test:
        vals.append(fun(x))
    average = sum(vals) / len(test)
    i = 0
    while(i < len(vals)):
        var += (vals[i] - test[i][1]) ** 2
        i += 1
    return (var / (len(vals) - 1))


if __name__ == "__main__":
    main(sys.argv[1:])
    test = get_test(instance)
    if(len(test) == 0):
        print("empty test")
        sys.exit(0)
    else:
        res = evaluate(test)
        print(res)
  1. 保存后退出,为其增加执行权限,执行 chmod u+x fit.py,切换到tuning-fit目录下,运行irace,执行 irace,最后可以看到如下所示内容:
# Best configurations as commandlines (first number is the configuration ID; same order as above):
971  -a 0.9887557725 -b -0.1196516576 -c -0.0287763796
947  -a 0.9887960867 -b -0.1196100851 -c -0.0290062498
918  -a 0.9891314065 -b -0.1196766357 -c -0.0302310215

我们用第一组参数绘图并与sin函数做了一个对比,如下图所示,可以看出,其在[-1,1]区间内基本拟合。

如果你还想尝试更多,更复杂的实例,你可以在irace的安转目录的examples文件夹下寻找,或者用户手册也提供了一个关于蚁群算法计算旅行商问题的调优实例。

6. 其他的一些说明

The irace Package: User Guide中有更多的关于irace的详细说明,以及一些更为高级的用法,本文作为一个入门文章内容已经足够了,因此若有兴趣或需要还请阅读更为详细的官方文档。

另外,若以后我发现了新的有趣用法也会继续更新这篇文章,还请点个关注。