用 Facebook Hydra 參數配置架構來簡化程式配置
文章目錄
- 用 Facebook Hydra 參數配置架構來簡化程式配置
-
- 0x00 摘要
- 0x01 問題描述
- 0x02 概述
- 0x03 使用
-
- 3.1 安裝
- 3.2 示例
-
- 3.2.1 示例代碼
- 3.2.2 簡化參數處理
- 3.2.3 輸出目錄
- 3.2.4 配置所在
- 0x04 Multirun 處理組合情況
- 0x05 處理複雜情況
-
- 5.1 Python subprocess
- 5.2 具體例子
- 5.3 流程示例
- 5.4 期待
- 0x06 總結
- 0xEE 個人資訊
- 0xFF 參考
0x00 摘要
Facebook Hydra 允許開發人員通過編寫和覆寫配置來簡化 Python 應用程式(尤其是機器學習方面)的開發。開發人員可以借助Hydra,通過更改配置檔案來更改産品的行為方式,而不是通過更改代碼來适應新的用例。
本文通過幾個示例為大家展示如何使用。
0x01 問題描述
在機器學習的開發中,經常會遇到各種調整參數,各種比較性能的情況。是以開發者經常會迷惑:
- 我現在這兩個模型都使用的是什麼參數來着?
- 我需要添加幾個參數,又要修改代碼,應該如何防止搞亂代碼?
- 可以使用配置檔案,但是如果希望新添加一個參數,則各個配置檔案之間很難同步,我如何處理配置檔案?
- 我今天跑了十幾個模型,一不小心把他們的輸出給沖掉了,我該怎麼辦?
- 十幾個模型的log也容易被誤删除,如何防止彼此沖突?
- 我在哪裡?我在做什麼?
這些問題,Facebook的開發人員早已遭遇過,深受其害的他們于是開發出來了 Hydra 來解決這些問題。
0x02 概述
Hydra提供了一種靈活的方法來開發和維護代碼及配置,進而加快了機器學習研究等領域中複雜應用程式的開發。 它允許開發人員從指令行或配置檔案“組合”應用程式的配置。這解決了在修改配置時可能出現的問題,例如:
- 維護配置的稍微不同的副本或添加邏輯以覆寫配置值。
- 可以在運作應用程式之前就組成和覆寫配置。
- 動态指令行頁籤完成功能可幫助開發人員發現複雜配置并減少錯誤。
- 可以在本地或遠端啟動應用程式,使使用者可以利用更多的本地資源。
Hydra承諾的其他好處包括:
- 使為新用例和需求的項目添加功能變得更加容易,而無需重寫大量代碼。
- 減少了複雜應用程式中常見的一些樣闆代碼,例如處理配置檔案,配置日志記錄和定義指令行标志。
下面我們通過幾個簡單例子給大家示範下如何使用。
0x03 使用
3.1 安裝
項目位址位于:https://github.com/facebookresearch/hydra
安裝方式如下:
pip install --upgrade hydra-core -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com
3.2 示例
3.2.1 示例代碼
示例代碼如下
import hydra
@hydra.main()
def app(cfg):
print(cfg.pretty())
print("The user is : " + cfg.user)
if __name__ == "__main__":
app()
運作如下:
python3 test_hydra.py +user=ua +pwd=pa
輸出如下:
Use OmegaConf.to_yaml(cfg)
category=UserWarning,
user: ua
pwd: pa
The user is : ua
3.2.2 簡化參數處理
常見的一個機器學習程式中,是用如下代碼來處理輸入和各種參數。
parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
parser.add_argument('--batch-size', type=int, default=64, metavar='N',
help='input batch size for training (default: 64)')
parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
help='input batch size for testing (default: 1000)')
parser.add_argument('--epochs', type=int, default=10, metavar='N',
help='number of epochs to train (default: 10)')
parser.add_argument('--lr', type=float, default=0.01, metavar='LR',
help='learning rate (default: 0.01)')
通過示例代碼我們可以看出來,在 hydra 之中,我們直接使用 cfg.user 就可以。
而且還可以通過配置檔案來直接處理參數,比如:
@hydra.main(config_path="conf", config_name="config")
def my_app(cfg: DictConfig) -> None:
print(OmegaConf.to_yaml(cfg))
3.2.3 輸出目錄
人們在做研究時經常遇到的一個問題是如何儲存輸出。典型的解決方案是傳入一個指定輸出目錄的指令行标志,但這很快會變得乏味。當你希望同時運作多項任務,并且必須為每個任務傳遞不同的輸出目錄時,這尤其令人惱火。
Hydra 通過為每次運作生成輸出目錄,并在運作代碼之前更改目前工作目錄來解決此問題。這樣可以很好地将來自同一 sweep 的任務分組在一起,同時保持每個任務與其他任務的輸出分離。
我們可以簡單的來看看目錄的變化,可以看到,在目前目錄下生成了一個 outputs 目錄。
其内部組織是按照時間來進行,把每次運作的輸出,log 和 配置都歸類在一起。
├── outputs
│ └── 2021-03-21
│ ├── 11-52-35
│ │ ├── .hydra
│ │ │ ├── config.yaml
│ │ │ ├── hydra.yaml
│ │ │ └── overrides.yaml
│ │ └── test_hydra.log
│ └── 11-57-55
│ ├── .hydra
│ │ ├── config.yaml
│ │ ├── hydra.yaml
│ │ └── overrides.yaml
│ └── test_hydra.log
├── test_hydra.py
3.2.4 配置所在
我們分别打開兩個.hydra目錄下的config.yaml檔案看看。
可以看到,每次運作時候,對應的參數配置都儲存在其中。這樣極大的友善了使用者的比對和分析。
$ cat outputs/2021-03-21/11-52-35/.hydra/config.yaml
user: ua
pwd: pa
$ cat outputs/2021-03-21/11-57-55/.hydra/config.yaml
user: ub
pwd: pb
0x04 Multirun 處理組合情況
Multirun 是 Hydra 的一種功能,它可以多次運作你的函數,每次都組成一個不同的配置對象。這是一個自然的擴充,可以輕松地組合複雜的配置,并且非常友善地進行參數掃描,而無需編寫冗長的腳本。
例如,對于兩種參數,我們可以掃描所有 4 個組合,一個指令就是會完成所有組合的執行:
python test_hydra.py --multirun user=ua,ub pwd=pa,pb
得到輸出如下:
[2021-03-27 11:57:54,435][HYDRA] Launching 4 jobs locally
[2021-03-27 11:57:54,435][HYDRA] #0 : +user=ua +pwd=pa
user: ua
pwd: pa
[2021-03-27 11:57:54,723][HYDRA] #1 : +user=ua +pwd=pb
user: ua
pwd: pb
[2021-03-27 11:57:54,992][HYDRA] #2 : +user=ub +pwd=pa
user: ub
pwd: pa
[2021-03-27 11:57:55,248][HYDRA] #3 : +user=ub +pwd=pb
user: ub
pwd: pb
可以看到生成如下目錄樹,每個參數組合對應了一個目錄。
├── multirun
│ └── 2021-03-27
│ └── 11-57-53
│ ├── 0
│ │ ├── .hydra
│ │ │ ├── config.yaml
│ │ │ ├── hydra.yaml
│ │ │ └── overrides.yaml
│ │ └── test_hydra.log
│ ├── 1
│ │ ├── .hydra
│ │ │ ├── config.yaml
│ │ │ ├── hydra.yaml
│ │ │ └── overrides.yaml
│ │ └── test_hydra.log
│ ├── 2
│ │ ├── .hydra
│ │ │ ├── config.yaml
│ │ │ ├── hydra.yaml
│ │ │ └── overrides.yaml
│ │ └── test_hydra.log
│ ├── 3
│ │ ├── .hydra
│ │ │ ├── config.yaml
│ │ │ ├── hydra.yaml
│ │ │ └── overrides.yaml
│ │ └── test_hydra.log
│ └── multirun.yaml
0x05 處理複雜情況
對于一般的機器學習運作和普通python程式,hydra是非常好用的,因為可以使用 裝飾器 來直接作用于 python 函數。
但是如果遇到了複雜情況,比如spark-submit,我們該如何處理?因為 spark-submit 是沒辦法用 hydra 來裝飾。
比如:
spark-submit cut_words.py
這樣就hydra就沒辦法截取 spark 的輸入,輸出。
遇到這個情況,我是使用 python 檔案内部 調用 linux指令行,然後在spark-submit之前就處理其參數,在 spark 運作時候 轉發程式輸出的辦法來解決(如果哪位同學有更好的辦法,可以告訴我,謝謝)。
5.1 Python subprocess
Python subprocess 允許你去建立一個新的程序讓其執行另外的程式,并與它進行通信,擷取标準的輸入、标準輸出、标準錯誤以及傳回碼等。
subprocess子產品中定義了一個Popen類,通過它可以來建立程序,并與其進行複雜的互動。Popen 是 subprocess的核心,子程序的建立和管理都靠它處理。
構造函數:
常用參數:
- args:shell指令,可以是字元串或者序列類型(如:list,元組)
-
bufsize:緩沖區大小。當建立标準流的管道對象時使用,預設-1。
0:不使用緩沖區
1:表示行緩沖,僅當universal_newlines=True時可用,也就是文本模式
正數:表示緩沖區大小
負數:表示使用系統預設的緩沖區大小。
- stdin, stdout, stderr:分别表示程式的标準輸入、輸出、錯誤句柄
- preexec_fn:隻在 Unix 平台下有效,用于指定一個可執行對象(callable object),它将在子程序運作之前被調用
- shell:如果該參數為 True,将通過作業系統的 shell 執行指定的指令。
- cwd:用于設定子程序的目前目錄。
- env:用于指定子程序的環境變量。如果 env = None,子程序的環境變量将從父程序中繼承。
5.2 具體例子
下面例子很簡陋,不能直接運作,隻是給大家示範下大緻思路,還請根據具體情況做相關調整。
- 我們通過subprocess.Popen啟動了spark;
- hydra 的輸入 可以轉換為 spark 和 python 的輸入;
- 然後讀取子程序的stdout;
- 逐次使用log.info來列印轉發的stdout,這樣spark的輸出就被轉發到了hydra的輸出之中;
這樣,spark的輸出就可以被hydra捕獲,進而整合到hydra log體系之中。
import shlex
import subprocess
import hydra
import logging
log = logging.getLogger(__name__)
@hydra.main()
def app(cfg):
# 可以在這裡事先處理參數,被hydra處理之後,也成為 spark 和 python 的輸入,進行處理
shell_cmd = 'spark-submit cut_words.py' + cfg.xxxxxx # 假如cut_words有參數
cmd = shlex.split(shell_cmd)
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while p.poll() is None:
line = p.stdout.readline()
line = line.strip()
if line:
log.info('Subprogram output: [{}]'.format(line))
if p.returncode == 0:
log.info('Subprogram success')
else:
log.info('Subprogram failed')
if __name__ == '__main__':
app()
5.3 流程示例
以下就是我采取辦法的流程示例。
- Input 由 hydra 處理之後,由 python 父程序 轉發給 spark 和 我們的python 商業邏輯;
- 具體spark 的輸出,由 python 父程序轉發給 Hydra logging;
具體如下圖:
Input Input
Hydra +----------+ +------------------------v
| ^ |
| | |
| | |
+-------------------------+ v
| | | | +------+-------------+
| v +----------> | | Spark |
| | | |
| Parent Python Process | | Business Python |
| | | |
| +<-----------^ | | |
| | | | | |
+-------------------------+ +------+-------------+
| | |
| | |
| | |
Hydra <---------<+ +------------------------+
Logging Output
5.4 期待
現在 Hydra 統一儲存 配置 到獨立的配置檔案之中。如果可以把某些輸出也按照統一格式儲存在配置檔案中就更好了。這樣我們就可以把這些配置檔案統一處理,比較,圖形化。直接把配置和輸出結合起來,更加直覺。
0x06 總結
這裡隻是簡單給出了三個例子,Hydra還有衆多的用法等待大家去探究。相信大家的很多痛點都可以用它來解決,趕緊試試吧。
0xEE 個人資訊
★★★★★★關于生活和技術的思考★★★★★★
微信公衆賬号:羅西的思考
如果您想及時得到個人撰寫文章的消息推送,或者想看看個人推薦的技術資料,敬請關注。
0xFF 參考
機器學習項目配置太複雜怎麼辦?Facebook 開發了 Hydra 來幫你
Python 從subprocess運作的子程序中實時擷取輸出的例子