在大規模機器學習場景中,單一節點的運算能力往往無法滿足模型訓練需求。分散式訓練架構應運而生,其中 Parameter Server 策略是一種廣泛應用的方法。
Parameter Server 架構原理
Parameter Server 架構將節點分為兩種角色:
- Parameter Server 節點:負責儲存模型引數,每個引數只儲存在一台 Parameter Server 上
- Worker 節點:負責執行計算任務,這些任務在多個 worker 之間複製執行
這種架構的核心優勢在於引數集中管理而計算分散執行,有效平衡了通訊開銷與計算效率。在訓練過程中,worker 節點從 parameter server 取得最新引數,進行計算後將梯度更新回 parameter server,從而實作模型的協同最佳化。
Kubeflow 中的 TF工作:分散式 TensorFlow 訓練實作
Kubeflow 提供了 TF工作 介面來支援多 worker 的分散式訓練。從概念上講,TF工作 是一個邏輯資源組,包含所有與訓練作業相關的資源,如 pods 和 services。
TF工作 資源結構
在 Kubeflow 中,每個複製的 worker 或 parameter server 都會被排程到獨立的單容器 pod 上。為了讓各個複製例項能夠相互同步,每個例項需要透過 Kubernetes 內部服務暴露自己的端點。將這些資源邏輯地組織在 TF工作 這個父資源下,可以實作資源的協同排程和垃圾回收。
分散式 MNIST 訓練例項
讓我們來看一個分散式 TensorFlow 作業的 YAML 設定範例:
apiVersion: "kubeflow.org/v1"
kind: "TF工作"
metadata:
name: "mnist"
namespace: kubeflow
spec:
cleanPodPolicy: None
tfReplicaSpecs:
Worker:
replicas: 2
restartPolicy: Never
template:
spec:
containers:
- name: tensorflow
image: gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0
command:
- "python"
- "/var/tf_mnist/mnist_with_summaries.py"
- "--log_dir=/train/logs"
- "--learning_rate=0.01"
- "--batch_size=150"
volumeMounts:
- mountPath: "/train"
name: "training"
volumes:
- name: "training"
persistentVolumeClaim:
claimName: "tfevent-volume"
- 這個 YAML 檔案定義了一個名為 “mnist” 的 TF工作,執行在 kubeflow 名稱空間中
tfReplicaSpecs
欄位指定了複製元件的型別和數量,這裡設定了 2 個 Worker 節點- 每個 Worker 使用相同的 TensorFlow 映象,執行 MNIST 訓練程式
- 透過掛載 PVC(Persistent Volume Claim)實作訓練日誌和模型的持久化儲存
restartPolicy: Never
表示 pod 結束後不會重啟,適用於有明確結束的訓練任務
TF工作 中的角色型別
在典型的 TensorFlow 訓練叢集中,存在幾種不同型別的節點角色:
- Chief:負責協調計算任務、發出事件和檢查點模型
- Parameter Servers:為模型引數提供分散式資料儲存
- Worker:執行實際的計算和訓練工作。如果沒有明確定義 Chief 節點(如上例),其中一個 Worker 會充當 Chief 節點
- Evaluator:用於在模型訓練過程中計算評估指標
TF工作 複製元件設定選項
複製規格中包含描述其所需狀態的多個屬性:
replicas:應該為此複製型別啟動多少個複製例項
template:描述為每個複製例項建立的 pod 的 PodTemplateSpec
restartPolicy:決定 pods 結束時是否重啟,可選值包括:
- Always:pod 將始終重啟,適用於 parameter servers
- OnFailure:pod 因失敗結束時重啟,非零結束碼表示失敗,適用於 chief 和 workers
- ExitCode:重啟行為取決於 TensorFlow 容器的結束碼:
- 0:表示程式成功完成,不會重啟
- 1-127:表示永久性錯誤,容器不會重啟
- 128-255:表示可重試錯誤,容器將重啟
- Never:終止的 pods 永遠不會重啟,很少使用
佈署與監控 TF工作
編寫好 TF工作 規範後,可以將其佈署到 Kubeflow 叢集:
kubectl apply -f dist-mnist.yaml
監控作業狀態的方法與單容器作業類別似:
kubectl describe tfjob mnist
輸出結果會顯示作業的完成時間、條件和複製狀態等訊息。TF工作 在所有 worker 完成時成功完成,如果任何 worker 失敗,則 TF工作 的狀態也會失敗。
GPU 加速訓練
GPU 的優勢與特性
GPU(圖形處理器)由許多較小與專用的核心組成。最初設計用於渲染圖形,GPU 越來越多地用於大規模平行計算任務,如機器學習。與 CPU 不同,GPU 非常適合將大型工作負載分配到其眾多核心上並同時執行它們。
在處理矩陣運算和深度學習時,我發現 GPU 通常能提供 10-100 倍的效能提升,特別是在處理卷積神經網路時,這種差異更為明顯。
在 Kubeflow 中啟用 GPU 訓練
要使用 GPU 進行訓練,Kubeflow 叢集需要預先設定以啟用 GPU。在啟用叢集上的 GPU 後,可以透過修改訓練規範中的命令列引數,在特定複製型別上啟用 GPU:
Worker:
replicas: 4
restartPolicy: Never
template:
spec:
containers:
- name: tensorflow
image: kubeflow/tf-dist-mnist-test:1.0
args:
- python
- /var/tf_dist_mnist/dist_mnist.py
- --num_gpus=1
- 這個設定指定了 4 個 worker 節點
- 每個節點透過
--num_gpus=1
引數請求使用一個 GPU - 容器使用專門為分散式 MNIST 訓練設計的映象
- 重啟策略設為 Never,表示任務完成或失敗後不會自動重啟
在實際佈署中,還需要透過 Kubernetes 資源規範明確請求 GPU 資源:
resources:
limits:
nvidia.com/gpu: 1
其他框架的分散式訓練支援
Kubeflow 設計為多框架機器學習平台,這意味著分散式訓練的模式可以輕鬆擴充套件到其他框架。目前,已經有多個運算元提供對 PyTorch 和 Caffe2 等其他框架的一級支援。
PyTorch 分散式訓練範例
以下是 PyTorch 訓練作業規範的範例:
apiVersion: "kubeflow.org/v1"
kind: "PyTorch工作"
metadata:
name: "pytorch-dist"
spec:
pytorchReplicaSpecs:
Master:
replicas: 1
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: gcr.io/kubeflow-ci/pytorch-dist-sendrecv-test:1.0
Worker:
replicas: 3
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: gcr.io/kubeflow-ci/pytorch-dist-sendrecv-test:1.0
- 這個設定義了一個 PyTorch工作,包含 1 個 Master 節點和 3 個 Worker 節點
- PyTorch 的分散式架構與 TensorFlow 類別似,但角色型別有所不同
- 所有節點使用相同的映象,但在 PyTorch 框架中扮演不同角色
- 重啟策略設為 OnFailure,表示任務失敗時會重啟,但成功完成則不會
PyTorch 的分散式訓練格式與 TF工作s 非常相似,主要區別在於複製型別。在 PyTorch 中,通常使用 Master 和 Worker 角色,而 TensorFlow 則使用 Chief、Parameter Server 和 Worker 等角色。
使用 Scikit-Learn 訓練模型
到目前為止,我們已經看到了如何使用 Kubeflow 中的內建運算元來訓練機器學習模型。然而,對於許多沒有 Kubeflow 運算元的框架和函式庫,你仍然可以在 Jupyter 筆記本或自定義 Docker 映象中使用你喜歡的框架。
Scikit-learn 簡介
Scikit-learn 是一個建立在 NumPy 之上的開放原始碼 Python 機器學習函式庫,用於高效能線性代數和陣列操作。該專案最初由 David Cournapeau 作為 Google Summer of Code 專案啟動,名為 scikits.learn。其名稱源於它是一個"SciKit"(SciPy 工具包),一個獨立開發和分發的 SciPy 第三方擴充套件。
Scikit-learn 是 GitHub 上最受歡迎的機器學習函式庫之一,也是維護得最好的函式庫之一。它支援最先進的演算法,如 KNN、XGBoost、Random Forest 和 SVM,被廣泛用於 Kaggle 競賽和知名科技公司。
在 Kubeflow 中,Scikit-learn 的訓練模型作為通用 Python 程式碼得到支援,沒有用於分散式訓練的特定運算元。這意味著你可以在 Jupyter 筆記本中使用 Scikit-learn,或者建立包含 Scikit-learn 程式碼的自定義容器並將其作為 Kubernetes 工作 執行。
在 Kubeflow 中使用 Scikit-learn
雖然 Scikit-learn 本身不支援像 TensorFlow 或 PyTorch 那樣的原生分散式訓練,但我們可以透過以下方式在 Kubeflow 中有效使用它:
- 使用 Jupyter 筆記本:直接在 Kubeflow 的 Jupyter 筆記本中編寫和執行 Scikit-learn 程式碼
- 建立自定義容器:將 Scikit-learn 程式碼封裝到容器中,並作為 Kubernetes 工作 執行
- 結合 Dask 或 Spark:使用 Dask 或 Spark 實作 Scikit-learn 的引數搜尋平行化
以下是在 Kubernetes 工作 中使用 Scikit-learn 的範例設定:
apiVersion: batch/v1
kind: 工作
metadata:
name: sklearn-iris-classifier
namespace: kubeflow
spec:
template:
spec:
containers:
- name: sklearn
image: my-sklearn-image:latest
command:
- "python"
- "/app/train_model.py"
- "--data_path=/data/iris.csv"
- "--model_path=/models/iris_classifier.pkl"
volumeMounts:
- mountPath: "/data"
name: "data-volume"
- mountPath: "/models"
name: "model-volume"
volumes:
- name: "data-volume"
persistentVolumeClaim:
claimName: "data-pvc"
- name: "model-volume"
persistentVolumeClaim:
claimName: "model-pvc"
restartPolicy: Never
backoffLimit: 4
- 這個設定建立了一個執行 Scikit-learn 模型訓練的 Kubernetes 工作
- 透過掛載卷實作資料和模型的持久化
- 容器執行一個 Python 指令碼,該指令碼使用 Scikit-learn 訓練模型
restartPolicy: Never
和backoffLimit: 4
確保失敗時的行為可控
分散式訓練策略比較與選擇
在實施機器學習工作流程時,選擇合適的分散式訓練策略至關重要。以下是不同策略的比較:
Parameter Server vs. AllReduce
Parameter Server 架構和 AllReduce 演算法(如 Horovod 使用的)是兩種主要的分散式訓練方法:
Parameter Server:
- 優點:靈活性高,可以動態增加/刪除節點
- 缺點:可能成為通訊瓶頸,特別是在大規模叢集中
- 適用場景:異構計算環境,需要彈性擴充的場景
AllReduce:
- 優點:通訊效率高,特別是在高速網路環境中
- 缺點:對節點故障較敏感,需要重啟整個訓練
- 適用場景:同構高效能計算叢集,模型相對較小
框架選擇考量因素
在選擇框架時,需要考慮以下因素:
- 模型複雜度:深度學習模型通常更適合 TensorFlow/PyTorch,而傳統機器學習演算法可能更適合 Scikit-learn
- 分散式需求:需要大規模分散式訓練的專案應選擇 TensorFlow 或 PyTorch
- 開發速度:Scikit-learn 通常允許更快的原型設計和實驗
- 佈署考慮:考慮模型如何投入生產,以及與現有基礎設施的相容性
在我的實踐中,對於需要快速迭代的專案,我通常先使用 Scikit-learn 進行原型設計,然後在需要更複雜模型或更大規模訓練時轉向 TensorFlow 或 PyTorch。
結論與最佳實踐
Kubeflow 為在 Kubernetes 上執行分散式機器學習訓練提供了強大而靈活的框架。透過 TF工作 和 PyTorch工作 等自定義資源,我們可以輕鬆佈署和管理複雜的分散式訓練作業。
在實際應用中,選擇合適的分散式策略和框架取決於多種因素,包括模型複雜度、資料規模、可用資源和效能需求。Parameter Server 架構適用於許多分散式訓練場景,而 GPU 加速可以顯著提高訓練速度。
對於沒有專用運算元的框架(如 Scikit-learn),我們仍然可以透過 Jupyter 筆記本或自定義容器在 Kubeflow 中使用它們。這種靈活性使 Kubeflow 成為一個通用的機器學習平台,能夠支援各種機器學習工作流程。
無論選擇哪種方法,確保正確設定資源需求、永續性儲存和重啟策略都是成功執行分散式訓練作業的關鍵。透過這些最佳實踐,可以充分利用 Kubernetes 的強大功能來擴充套件和管理機器學習工作負載。
Kubeflow中的Scikit-learn模型訓練:收入預測例項
機器學習專案從資料處理到模型佈署涉及多個複雜環節,而Kubeflow作為一個強大的機器學習平台,為這些流程提供了完整而統一的解決方案。在這篇文章中,玄貓將帶大家探索如何在Kubeflow環境中使用Scikit-learn訓練隨機森林模型,以預測根據1994年美國人口普查資料的收入水平。
為什麼選擇隨機森林演算法?
隨機森林是一種整合學習方法,可用於分類別、迴歸和其他任務。它透過在訓練時構建多個決策樹,然後輸出個別樹的分類別眾數(分類別問題)或平均預測(迴歸問題)來工作。在實際應用中,玄貓發現隨機森林具有以下優勢:
- 對於混合型特徵(數值型和類別型)有很好的適應性
- 不需要大量的特徵預處理工作
- 對過擬合有較強的抵抗力
- 能夠處理高維資料與不需要特徵選擇
這些特性使得隨機森林成為處理人口普查資料這類別混合特徵資料集的理想選擇。
在Kubeflow中建立新的Notebook工作階段
要開始在Kubeflow中進行模型訓練,首先需要建立一個新的notebook環境。步驟如下:
- 導航到Kubeflow儀錶板中的「Notebook Servers」面板
- 點選「New Server」按鈕
- 選擇
tensorFlow-1.15.2-notebook-cpu:1.0
映像檔 - 完成設定並啟動伺服器
當notebook伺服器啟動後,可以透過點選右上角的「Upload」按鈕上載準備好的notebook檔案。
在Kubeflow中工作時,如果想要加速Scikit-learn模型訓練,可以切換到GPU型別。這是一個簡單但有效的方式來利用GPU資源加速模型訓練過程。
資料準備與特徵工程
資料集介紹
這個案例使用的是1994年美國人口普查資料集的一個子集,包含了多個類別變數和連續特徵:
- 年齡、教育程度、婚姻狀況
- 職業、薪資、關係狀態
- 種族、性別、原籍國
- 資本收益和損失
特徵轉換與處理
在機器學習工作流程中,特徵準備是非常關鍵的一步。對於這個資料集,需要處理兩種型別的特徵:
- 序數特徵:需要標準化處理
- 類別特徵:需要進行獨熱編碼(one-hot encoding)
玄貓在實踐中發現,使用Scikit-learn的Pipeline功能可以大簡化特徵處理流程,並確保模型接收到一致的資料。以下是特徵準備的核心程式碼:
# 定義序數特徵和類別特徵
ordinal_features = [x for x in range(len(feature_names))
if x not in list(category_map.keys())]
categorical_features = list(category_map.keys())
# 為序數特徵建立轉換管道
ordinal_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())])
# 為類別特徵建立轉換管道
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('onehot', OneHotEncoder(handle_unknown='ignore'))])
這段程式碼首先區分了序數特徵和類別特徵,然後為每種型別建立了專用的處理管道。序數特徵的處理包括兩個步驟:首先使用SimpleImputer
處理缺失值(使用中位數填充),然後使用StandardScaler
進行標準化。類別特徵同樣先處理缺失值,然後使用OneHotEncoder
進行獨熱編碼轉換。
在實際資料處理中,缺失值是一個常見問題。許多真實世界的資料集包含缺失值,這些值通常以資料特定的佔位符(如空白和NaN)編碼。這類別資料集通常與Scikit-learn估計器不相容,因為估計器假設所有值都是數值型的。SimpleImputer
是一個處理缺失值的有效工具,它允許我們透過用預定義的值替換NaN值來處理預測模型資料集中的缺失資料。
接下來,使用ColumnTransformer
將這些轉換器組合起來:
# 使用列轉換器組合特徵
preprocessor = ColumnTransformer(transformers=[
('num', ordinal_transformer, ordinal_features),
('cat', categorical_transformer, categorical_features)])
# 訓練前處理器
preprocessor.fit(X_train)
ColumnTransformer
允許我們對資料框的不同列應用不同的轉換。在這裡,我們將之前定義的序數特徵轉換器應用於序數特徵,將類別特徵轉換器應用於類別特徵。這種方法的優勢在於可以在一個統一的介面中處理不同型別的特徵,使程式碼更簡潔、更易維護。
獨熱編碼是處理類別特徵的常用方法,它將類別變數轉換為一組二進位制特徵。例如,如果我們有一個「職業」特徵,包含「工程師」、「教師」和「醫生」三個類別,獨熱編碼會建立三個新列,每個類別一列,並用0和1表示該樣本是否屬於該類別。
使用Scikit-learn訓練隨機森林模型
完成特徵準備後,就可以進行模型訓練了。這裡使用Scikit-learn提供的RandomForestClassifier
:
# 設定隨機種子以確保結果可重現
np.random.seed(0)
# 建立並訓練隨機森林分類別器
clf = RandomForestClassifier(n_estimators=50)
clf.fit(preprocessor.transform(X_train), Y_train)
這段程式碼首先設定了隨機種子,確保結果可重現。然後建立了一個具有50棵決策樹的隨機森林分類別器。n_estimators
引數指定了森林中樹的數量,這是隨機森林中最重要的引數之一。一般來說,樹越多,模型表現越好,但訓練時間也越長。
在訓練過程中,我們首先使用之前定義的前處理器轉換訓練資料,然後將轉換後的特徵和目標變數傳給分類別器的fit
方法。這個過程會構建50棵決策樹,每棵樹都使用訓練資料的一個隨機子集進行訓練。
選擇機器學習框架時,演算法的特性和特定實作是主要考慮因素。即使是不同框架中的相同演算法實作,也可能提供略有不同的特性,這些差異可能對特定資料集至關重要。在選擇Scikit-learn的隨機森林實作時,玄貓考慮了它的易用性、豐富的引數選項以及與整個Scikit-learn生態系統的良好整合。
評估模型效能
訓練完成後,需要評估模型效能。這裡使用準確率作為評估指標:
# 定義預測函式
predict_fn = lambda x: clf.predict(preprocessor.transform(x))
# 計算訓練和測試準確率
print('Train accuracy: ', accuracy_score(Y_train, predict_fn(X_train)))
print('Test accuracy: ', accuracy_score(Y_test, predict_fn(X_test)))
這段程式碼首先定義了一個預測函式,該函式接受原始特徵,使用前處理器進行轉換,然後使用訓練好的分類別器進行預測。然後計算了訓練集和測試集上的準確率。
執行這段程式碼後,得到以下結果:
Train accuracy: 0.9655333333333334
Test accuracy: 0.855859375
從結果可以看出,模型在訓練集上的準確率約為96.55%,在測試集上的準確率約為85.59%。這表明模型學習得相當好,但存在一些過擬合,因為訓練準確率明顯高於測試準確率。
在實際應用中,除了準確率外,還應考慮其他評估指標,如精確率、召回率、F1分數等,特別是當資料集不平衡時。但對於初步評估,準確率提供了一個簡單直觀的效能度量。
模型解釋:理解模型決策
模型解釋性在機器學習中越來越重要,主要有兩個原因:
- 如果模型解釋性對模型服務很重要,那麼在模型建立過程中,我們需要驗證所建立的模型是否可解釋。
- 許多模型解釋方法需要在模型建立過程中進行額外的計算。
在這個例子中,玄貓使用了Seldon的Alibi專案中的anchors方法來解釋模型決策。Anchors是一種模型無關(黑盒)與人類可解釋的解釋方法,適用於應用於影像、文字和表格資料的分類別模型。
# 定義表格錨點
explainer = AnchorTabular(
predict_fn, feature_names, categorical_names=category_map, seed=1)
explainer.fit(X_train, disc_perc=[25, 50, 75])
這段程式碼建立了一個AnchorTabular
直譯器。它接受之前定義的預測函式、特徵名稱和類別特徵對映。disc_perc
引數指定了連續特徵離散化的百分位數,這裡使用了25%、50%和75%作為切分點。
連續特徵被離散化為分位數(例如十分位數),使它們更易於解釋。在候選錨點中,保持特徵不變(相同類別或離散化特徵的相同分箱),同時從訓練集中取樣其他特徵。
建立表格錨點後,可以取得測試集中第一個觀察值預測的錨點:
# 取得第一個測試樣本的預測
idx = 0
class_names = adult.target_names
print('Prediction: ', class_names[explainer.predictor(X_test[idx].reshape(1, -1))[0]])
錨點是一個充分條件 - 當錨點成立時,預測應該與此例項的預測相同。這種解釋方法幫助我們理解模型為什麼會做出特定預測,提高了模型的透明度和可信度。
深入理解模型解釋的重要性
在實際應用中,模型解釋不僅是技術需求,也是商業和道德需求。當我們使用機器學習模型做出影響人們生活的決策(如收入預測、貸款審批等)時,理解這些決策背後的原因變得至關重要。
Anchors方法的優勢在於它提供了直觀與區域性精確的解釋。例如,對於收入預測模型,一個錨點解釋可能是:“如果一個人的教育程度是大學歷,職業是專業人士,每週工作時間大於40小時,那麼模型預測其年收入大於50K美元的可能性為95%。”
這種解釋對於理解模型行為、識別潛在偏見、滿足監管要求以及建立使用者信任都非常有價值。
結合Kubeflow與Scikit-learn的優勢
Kubeflow提供了一個完整的平台來管理機器學習工作流程,而Scikit-learn則提供了豐富的機器學習演算法和工具。將兩者結合使用,可以獲得多方面的優勢:
- 簡化工作流程管理:Kubeflow提供了一個統一的介面來管理模型訓練、評估和佈署。
- 擴充套件性:可以輕鬆擴充套件到更大的資料集和更複雜的模型。
- 可重現性:Kubeflow的元件化設計使得實驗更易於重現和分享。
- 靈活性:可以使用Scikit-learn的豐富功能進行各種機器學習任務。
在收入預測這個案例中,我們利用了Scikit-learn的Pipeline和ColumnTransformer進行特徵工程,使用RandomForestClassifier進行模型訓練,並使用Alibi的Anchors方法進行模型解釋。整個過程在Kubeflow環境中進行,展示瞭如何在一個統一的平台上完成從資料準備到模型解釋的完整機器學習工作流程。
在實際應用中,這種方法可以擴充套件到各種機器學習任務,如客戶流失預測、欺詐檢測、推薦系統等。透過理解和應用這些技術,資料科學團隊可以更高效地開發和佈署機器學習解決方案,為組織創造更大的價值。
機器學習模型的訓練只是整個機器學習生命週期的一部分。在實際應用中,還需要考慮模型佈署、監控和更新等環節。Kubeflow提供了一個完整的平台來管理這些環節,使得機器學習解決方案更易於開發、佈署和維護。