用 Facebook Hydra 參數配置框架來簡化程序配置


用 Facebook Hydra 參數配置框架來簡化程序配置

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的核心,子進程的創建和管理都靠它處理。

構造函數:

class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0,restore_signals=True, start_new_session=False, pass_fds=(),*, encoding=None, errors=None)

常用參數:

  • 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運行的子進程中實時獲取輸出的例子


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM