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的詳細說明,以及一些更為高級的用法,本文作為一個入門文章內容已經足夠了,因此若有興趣或需要還請閱讀更為詳細的官方文檔。

另外,若以後我發現了新的有趣用法也會繼續更新這篇文章,還請點個關注。