天天看點

用 Facebook Hydra 參數配置架構來簡化程式配置用 Facebook Hydra 參數配置架構來簡化程式配置

用 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 個人資訊

★★★★★★關于生活和技術的思考★★★★★★

微信公衆賬号:羅西的思考

如果您想及時得到個人撰寫文章的消息推送,或者想看看個人推薦的技術資料,敬請關注。

用 Facebook Hydra 參數配置架構來簡化程式配置用 Facebook Hydra 參數配置架構來簡化程式配置

0xFF 參考

機器學習項目配置太複雜怎麼辦?Facebook 開發了 Hydra 來幫你

Python 從subprocess運作的子程序中實時擷取輸出的例子