返回文章列表

深度學習超引數最佳化服務設計

本文探討深度學習中超引數最佳化(HPO)服務的設計原則與實作方式,涵蓋與訓練程式碼解耦、支援多種 HPO 演算法、可擴充套件性與容錯性、多租戶支援以及可移植性等關鍵導向。同時,文章也介紹了 HPO 服務的通用設計架構,包含 API 介面、HPO 作業管理器和超引數建議生成器,並以程式碼範例說明如何提交 HPO

機器學習 系統設計

隨著深度學習模型日益複雜,超引數最佳化已成為提升模型效能的關鍵環節。建構一個高效的 HPO 服務,需要考量多種因素,例如與訓練程式碼的獨立性、支援不同 HPO 演算法的彈性、系統的可擴充套件性和容錯性、多租戶資源隔離以及跨平台佈署能力。一個典型的 HPO 服務包含 API 介面、HPO 作業管理器和超引數建議生成器,透過 API 請求提交 HPO 作業,並由 HPO 作業管理器協調不同 HPO 演算法與訓練試驗的執行,最終回傳最佳超引陣列合。在實際應用中,除了建構完整的 HPO 服務,也可以選擇使用輕量級的開源 HPO 函式庫,例如 Optuna、Hyperopt 和 Ray Tune,這些函式庫提供簡潔的 API 和多種 HPO 演算法,方便資料科學家快速進行模型引數調校。

設計超引數最佳化(HPO)服務

在深度學習的生產環境中,超引數最佳化(HPO)服務已成為主流的HPO方法。與使用HPO函式庫相比,HPO服務提供了更自動化和更便捷的方式來進行超引數最佳化。本章將介紹如何設計一個HPO服務,以支援對任意模型訓練的自動化和黑箱超引數最佳化。

HPO服務設計原則

在進行具體的設計提案之前,我們先來看看構建HPO服務的五項設計原則:

原則1:與訓練程式碼無關

HPO服務需要與訓練程式碼和模型訓練框架無關。除了支援任意機器學習框架,如TensorFlow、PyTorch和MPI之外,我們希望該服務能夠調整以任何程式語言編寫的訓練程式碼的超引數。

原則2:支援不同HPO演算法的可擴充套件性和一致性

從第5.2.2節對HPO演算法的討論中,我們知道超引數搜尋演算法是HPO過程的核心。超引數搜尋的效率決定了HPO的效能。一個好的HPO演算法可以在少量的試驗中找到具有大量超引數和任意搜尋空間的最佳超引數。

由於HPO演算法研究是一個活躍的研究領域,每隔幾個月就會發表新的有效演算法。我們的HPO服務需要能夠輕鬆地與這些新演算法整合,並將它們作為演算法選項提供給客戶(資料科學家)。此外,新加入的演算法在使用者經驗方面應該與現有的演算法保持一致。

原則3:可擴充套件性和容錯性

除了HPO演算法之外,HPO服務的另一個重要職責是管理用於HPO的計算資源——具有不同超引數值的模型訓練。從HPO實驗的角度來看,我們希望在實驗層級和試驗層級都實作分散式執行。更具體地說,我們不僅希望以分散式和平行的方式執行試驗,還希望能夠分散式地執行單個訓練試驗——例如,在一次試驗中執行模型的分散式訓練。從資源利用的角度來看,系統需要支援自動擴充套件,這樣可以根據目前的工作負載自動調整計算叢集的大小,從而避免資源利用不足或過度。

容錯性也是HPO試驗執行管理的重要方面。容錯性很重要,因為一些HPO演算法需要按順序執行試驗。例如,試驗2必須在試驗1之後進行,因為演算法需要過去的超引數值和結果來推斷下一次試驗開始之前的超引數。在這種情況下,當一次試驗意外失敗時——例如,由於節點重啟或網路問題——整個HPO過程就會失敗。系統應該能夠從之前的失敗中自動還原。常見的方法是記錄每個試驗的最新狀態,以便我們可以從最後記錄的檢查點還原。

原則4:多租戶

HPO過程本質上是一組模型訓練執行。與模型訓練類別似,HPO服務必須為不同的使用者或群組提供資源隔離。這將確保不同使用者的活動保持在其邊界內。

原則5:可移植性

如今,“雲中立”的概念變得非常流行。人們希望在不同的環境中執行其模型訓練作業——Amazon Web Services、Google Cloud Platform和Azure——因此我們構建的HPO服務需要與底層基礎設施解耦。在Kubernetes上執行HPO服務是一個不錯的選擇。

一般的HPO服務設計

由於HPO工作流程(圖5.3)相當標準且變化不多,因此HPO服務系統設計(圖5.9)可以應用於大多數的HPO場景。它由三個主要元件組成:API介面、HPO作業管理器和超引數(HP)建議生成器。(它們在圖5.9中標記為A、B和C。)

API介面(元件A)是使用者提交HPO作業的入口點。要啟動一個HPO實驗,使用者向介面提交一個API請求(步驟1);該請求提供模型訓練程式碼,例如Docker映像;超引數及其搜尋空間;以及一個HPO演算法。

# 示範如何提交一個HPO作業的API請求
import requests

# 設定API端點和請求資料
api_endpoint = "http://hpo-service/api/submit_job"
request_data = {
    "model_training_code": "docker_image_url",
    "hyperparameters": {
        "learning_rate": {"type": "float", "min": 0.01, "max": 0.1},
        "batch_size": {"type": "int", "min": 32, "max": 128}
    },
    "hpo_algorithm": "random_search"
}

# 提交API請求
response = requests.post(api_endpoint, json=request_data)

# 處理回應
if response.status_code == 200:
    print("HPO作業提交成功")
else:
    print("提交失敗:", response.text)

內容解密:

  1. api_endpoint: 定義了提交HPO作業的API端點URL。
  2. request_data: 包含了提交HPO作業所需的資訊,包括模型訓練程式碼(Docker映像)、要最佳化的超引數及其搜尋空間,以及所選用的HPO演算法。
  3. requests.post: 使用POST方法向指定的API端點提交請求,將request_data以JSON格式傳送。
  4. response.status_code: 檢查API請求的回應狀態碼,以確定提交是否成功。

超引數建議生成器(元件C)是不同HPO演算法的包裝器/介面卡。它為使用者提供了一個統一的介面來執行每個不同的HPO演算法,因此使用者可以在不擔心執行細節的情況下選擇演算法。

超引數最佳化服務的設計與實作

5.3 超引數最佳化服務的設計

要新增一個超引數最佳化演算法,必須在建議生成元件中註冊,使其成為使用者可選擇的演算法選項。

超引數最佳化任務管理器(元件B)是超引數最佳化服務的核心元件,負責管理客戶請求的超引數最佳化實驗。對於每個超引數最佳化請求,任務管理器啟動一個超引數最佳化試驗迴圈(步驟2)。在迴圈中,首先呼叫超引數建議生成器以取得一組建議的超引數值(步驟2.a),然後建立一個試驗以使用這些超引數值進行模型訓練(步驟2.b和2.c)。

對於每個訓練試驗,超引數最佳化任務管理器建立一個試驗物件。該物件有兩個主要職責:首先,收集試驗執行的輸出,例如訓練進度、模型指標、模型準確度和嘗試的超引數;其次,管理訓練程式,包括啟動訓練程式、分散式訓練設定和故障還原。

超引數最佳化服務的端對端執行流程

讓我們來看看端對端的使用者工作流程,如圖5.10所示。首先,使用者向API介面提交一個超引數最佳化請求(步驟1)。該請求定義了訓練程式碼、一組超引數及其搜尋空間、訓練目標和超引數最佳化演算法。然後,超引數最佳化任務管理器為該請求啟動一個超引數最佳化試驗迴圈(步驟2)。該迴圈啟動一組試驗,以確定哪組超引數值效果最佳。最終,當試驗預算用完或某個試驗達到訓練目標時,試驗迴圈終止,並傳回最佳超引數(步驟3)。

試驗迴圈詳解

在試驗迴圈中,任務管理器首先查詢超引數建議生成器,以取得建議的超引數候選值(步驟2.a)。建議生成器執行所選的超引數最佳化演算法,計算出一組超引數值並傳回給任務管理器(步驟2.b)。任務管理器然後建立一個試驗物件,該物件使用建議的超引數值啟動模型訓練程式(步驟2.c)。 試驗物件還會監控訓練程式,並持續向試驗歷史資料函式庫報告訓練指標,直到訓練完成(步驟2.d)。 當任務管理器發現當前試驗完成時,它會提取試驗歷史(過去試驗的指標和使用的超引數值),並將其傳遞給超引數建議生成器,以取得新的超引數候選值(步驟2.e)。

def objective(trial):
    # 定義搜尋空間
    n_estimators = trial.suggest_categorical('n_estimators', [10, 50, 100, 200])
    max_depth = trial.suggest_int('max_depth', 1, 10)

    # 訓練模型
    model = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth)
    model.fit(X_train, y_train)

    # 評估模型
    score = model.score(X_test, y_test)
    return score

#### 內容解密:
此程式碼定義了一個目標函式用於在給定的超引數空間中進行搜尋。`trial.suggest_categorical``trial.suggest_int` 用於定義超引數的搜尋空間。`RandomForestClassifier` 使用建議的超引數進行訓練並在測試集上評估模型的表現最終傳回模型的準確率作為評分

由於超引數最佳化的使用場景相當標準和通用,且已經有多個開源的超引數最佳化專案可以直接使用,因此我們認為學習如何使用這些現有的專案比重新建立一個沒有額外價值的新系統更有意義。因此,在附錄C中,我們將介紹一個根據Kubernetes的強大且可移植的超引數最佳化服務——Kubeflow Katib。

5.4 開源超引數最佳化函式庫

對於小型資料科學團隊來說,超引數最佳化服務可能顯得過於複雜,尤其是當他們的模型是在自己管理的少量伺服器上訓練時。在這種情況下,使用超引數最佳化函式庫來最佳化本地機器或受控叢集(小規模,1-10台伺服器)中的模型訓練是一個更好的選擇。

在本文中,我們將介紹三個有用的開源超引數最佳化函式庫:Optuna、Hyperopt和Ray Tune。它們都以函式庫的形式執行超引數最佳化,並且易於學習和使用。由於Optuna、Hyperopt和Ray Tune都有清晰的檔案和適當的範例,因此我們將重點介紹它們的一般概述和功能介紹,以便您可以根據自己的情況選擇合適的工具。

Hyperopt簡介

Hyperopt(http://hyperopt.github.io/hyperopt/#getting-started)是一個輕量級且易於使用的Python函式庫,用於序列和平行超引數最佳化。Hyperopt實作了隨機搜尋、TPE和自適應TPE三種超引數最佳化演算法。

from hyperopt import hp, fmin, tpe, Trials

# 定義搜尋空間
space = {
    'n_estimators': hp.choice('n_estimators', [10, 50, 100, 200]),
    'max_depth': hp.quniform('max_depth', 1, 10, 1)
}

# 定義目標函式
def objective(params):
    model = RandomForestClassifier(**params)
    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)
    return -score  # 注意:Hyperopt是最小化目標函式

# 進行超引數最佳化
trials = Trials()
best = fmin(objective, space, algo=tpe.suggest, trials=trials, max_evals=50)

#### 內容解密:
此程式碼展示瞭如何使用Hyperopt進行超引數最佳化首先定義了搜尋空間包括分類別器的兩個重要超引數:`n_estimators``max_depth`。然後定義了目標函式該函式根據給定的超引數訓練模型並傳回模型的準確率的負值因為Hyperopt是最小化目標函式)。最後使用TPE演算法進行50次評估以找到最佳的超引陣列合

在討論不同超引數最佳化函式庫時,尤其是在“如何使用”章節,您將經常看到“目標函式”這個術語。什麼是目標函式?圖5.11展示了這個過程。

對於一個超引數最佳化演算法,例如貝葉斯搜尋,要產生更好的超引數建議,它需要知道之前的超引數最佳化試驗的效果如何。因此,超引數最佳化演算法要求我們定義一個函式來評估每個訓練試驗,並在後續試驗中繼續最小化或最大化該函式的傳回值(評分)。我們將這個函式稱為目標函式。

目標函式接收超引數作為輸入,並傳回一個浮點數值或評分。目標函式執行給定超引數下的模型訓練,並在訓練完成後評估輸出模型。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 深度學習超引數最佳化服務設計

package "HPO 服務架構" {
    package "API 層" {
        component [HPO 作業提交] as submit
        component [超引數空間定義] as space
        component [結果查詢 API] as query
    }

    package "HPO 管理器" {
        component [作業調度器] as scheduler
        component [演算法選擇器] as algo
        component [試驗協調器] as trial
    }

    package "執行環境" {
        component [Kubernetes 叢集] as k8s
        component [多租戶隔離] as tenant
        component [容錯恢復] as fault
    }
}

submit --> space : 作業配置
space --> query : 搜尋範圍
query --> scheduler : API 請求
scheduler --> algo : 任務分配
algo --> trial : 演算法執行
trial --> k8s : 建議生成
k8s --> tenant : 容器調度
tenant --> fault : 資源隔離

note right of algo
  支援演算法:
  - Optuna
  - Hyperopt
  - Ray Tune
end note

note right of k8s
  設計原則:
  - 框架無關
  - 可擴展性
  - 多租戶支援
end note

@enduml

此圖示展示了目標函式如何接收超引數作為輸入並傳回評分,以及如何透過反饋機制與超引數最佳化演算法互動,以不斷最佳化模型表現。

超引數最佳化服務:開源函式庫的選擇與應用

超引數最佳化(Hyperparameter Optimization, HPO)是提升機器學習模型效能的關鍵步驟。選擇適當的超引數能夠顯著改善模型的準確性和泛化能力。本篇文章將介紹兩款流行的開源HPO函式庫:Hyperopt和Optuna,並探討其使用方法、優缺點及適用場景。

Hyperopt:簡易的HPO解決方案

Hyperopt是一款輕量級的Python函式庫,旨在簡化超引數搜尋過程。它支援根據貝葉斯最佳化(Bayesian Optimization)和隨機搜尋(Random Search)的多種演算法。

使用Hyperopt進行HPO

使用Hyperopt進行HPO的基本步驟如下:

  1. 定義目標函式:將實際的訓練程式碼包裝在目標函式中,並從args變數中讀取超引數值。
  2. 定義超引數搜尋空間:指定需要最佳化的超引數及其可能的取值範圍。
  3. 啟動HPO程式:選擇合適的HPO演算法(如TPE),並設定最大嘗試次數。
# Step 1: 定義目標函式
def objective(args):
    model = train(args)
    return evaluate(model)

# Step 2: 定義超引數搜尋空間
space = hp.choice('classifier_type', [
    {'type': 'naive_bayes'},
    {'type': 'svm', 'C': hp.lognormal('svm_C', 0, 1), 'kernel': hp.choice('svm_kernel', [{'ktype': 'linear'}, {'ktype': 'RBF', 'width': hp.lognormal('svm_rbf_width', 0, 1)}])},
    {'type': 'dtree', 'criterion': hp.choice('dtree_criterion', ['gini', 'entropy']), 'max_depth': hp.choice('dtree_max_depth', [None, hp.qlognormal('dtree_max_depth_int', 3, 1, 1)])}
])

# Step 3: 啟動HPO程式
best = fmin(objective, space, algo=tpe.suggest, max_evals=100)

Hyperopt的平行化處理

Hyperopt支援在多台機器上平行執行HPO任務。透過在不同機器上執行Hyperopt工作節點,並讓它們與中央資料函式庫進行協調,可以實作大規模的平行處理。

Optuna:下一代HPO函式庫

Optuna是另一款流行的開源HPO函式庫,它提供了比Hyperopt更先進的功能和更好的檔案支援。Optuna支援大規模搜尋空間和早期剪枝(Early Pruning),並能夠在多執行緒或多程式上進行平行化處理。

使用Optuna進行HPO

Optuna的使用方法與Hyperopt相似,但需要在目標函式中定義更多的HPO邏輯。以下是一個簡單的例子:

# Step 1: 定義目標函式
def objective(trial):
    regressor_name = trial.suggest_categorical('classifier', ['SVR', 'RandomForest'])
    if regressor_name == 'SVR':
        svr_c = trial.suggest_float('svr_c', 1e-10, 1e10, log=True)
        regressor_obj = sklearn.svm.SVR(C=svr_c)
    else:
        rf_max_depth = trial.suggest_int('rf_max_depth', 2, 32)
        regressor_obj = sklearn.ensemble.RandomForestRegressor(max_depth=rf_max_depth)
    # ...
    return error

# Step 2: 設定HPO程式
study = optuna.create_study()

# Step 3: 啟動HPO程式
study.optimize(objective, n_trials=100)

Optuna的優勢

Optuna相較於Hyperopt具有更好的視覺化功能和檔案支援。它的視覺化工具可以幫助使用者瞭解超引數之間的互動作用,並找出最有效的超引數。