如果您曾经花费数小时解决混乱的脚本或者在修复棘手的错误时感觉自己像在追踪幽灵,而同时您的模型训练却花费了很长时间,那么请举起手来。我们都有过这样的经历,对吧?但是现在,想象一个不同的情景:整洁的代码。高效的工作流程。高效的模型训练。听起来太美好了吧?但事实并非如此。事实上,我们将要深入研究的就是这些。我们将学习如何创建一个整洁、易于维护和完全可复现的机器学习模型训练流程。
在本指南中,我将为您提供一个逐步构建模型训练流程的过程,并分享解决模型训练中常见挑战的实用解决方案和注意事项,例如:
-
1
构建一个适应于各种环境(包括SLURM等研究和大学环境)的多功能流程。 -
2
创建一个实验的集中信息源,促进协作和组织。 -
3
在需要时无缝集成超参数优化(HPO)。

但是,在深入研究逐步模型训练流程之前,了解机器学习流程的基础知识、架构、动机、挑战以及您需要使用的一些工具是非常重要的。因此,让我们先快速概述一下这些内容。
为什么我们需要模型训练流程?
构建机器学习模型训练流程有几个原因(相信我!):
- 效率:流程自动化重复任务,减少手动干预,节省时间。
- 一致性:通过定义固定的工作流程,流程确保预处理和模型训练步骤在整个项目中保持一致,使得从开发环境过渡到生产环境变得容易。
- 模块化:流程可以轻松添加、删除或修改组件,而不会中断整个工作流程。
- 实验:通过结构化的流程,更容易跟踪实验并比较不同的模型或算法。这使得训练迭代快速且可靠。
- 可扩展性:流程可以设计成适应大型数据集并随着项目的发展而扩展。
机器学习模型训练流程架构
机器学习模型训练流程通常由多个相互连接的组件或阶段组成。这些阶段形成一个有向无环图(DAG),表示执行顺序。一个典型的流程可能包括:
- 数据摄取:该过程从不同来源(如数据库、文件或API)获取原始数据。此步骤对于确保流程可以访问相关和最新信息非常重要。
- 数据预处理:原始数据通常包含噪声、缺失值或不一致性。预处理阶段涉及数据的清理、转换和编码,使其适用于机器学习算法。常见的预处理任务包括处理缺失数据、归一化和分类编码。
- 特征工程:在这个阶段,从现有数据中创建新的特征以提高模型性能。可以使用降维、特征选择或特征提取等技术来识别和创建对机器学习算法最有信息量的特征。在这个流程的这一步骤中,业务知识非常有用。
- 模型训练:预处理后的数据被输入选择的机器学习算法以训练模型。训练过程涉及调整模型参数以最小化预定义的损失函数,该函数衡量模型预测值与实际值之间的差异。
- 模型验证:为了评估模型的性能,使用验证数据集(模型从未见过的数据的一部分)。可以使用准确率、精确度、召回率或F1分数等指标来评估模型在分类问题中对新(未见过的数据)的泛化能力。
- 超参数调优:超参数是机器学习算法中在训练过程中不会学习的参数,而是在训练开始之前设置的。调优超参数涉及搜索最佳数值集合,以最小化验证误差并帮助实现最佳模型性能。
模型训练管道工具
有多种选项可以实现训练管道,每个选项都有其自己的特点、优势和用例。选择训练管道选项时,要考虑项目的规模、复杂性和需求,以及您对工具和技术的熟悉程度。
在这里,我们将探讨一些常见的管道选项,包括内置库、自定义管道和端到端平台。
- 内置库:许多机器学习库都带有创建管道的内置支持。例如,Scikit-learn是一种流行的Python库,提供Pipeline类来简化预处理和模型训练。这个选项适用于较小的项目或者您已经熟悉特定库的情况。
- 自定义管道:在某些情况下,您可能需要构建一个根据项目独特需求定制的管道。这可能涉及编写自己的Python脚本或利用通用库,如Kedro或MetaFlow。自定义管道提供了灵活性,以适应特定的数据源、预处理步骤或部署场景。
- 端到端平台:对于大规模或复杂的项目,端到端机器学习平台可以带来优势。这些平台提供了全面的解决方案,用于构建、部署和管理ML管道,通常包括数据版本控制、实验跟踪和模型监控等功能。一些流行的端到端平台包括:
- TensorFlow Extended (TFX): 由Google开发的端到端平台,TFX提供了一套用于使用TensorFlow构建生产就绪ML管道的组件。
- Kubeflow Pipelines: Kubeflow是一个设计用于在Kubernetes上运行的开源平台,提供可扩展和可复现的ML工作流。Kubeflow Pipelines提供了一个平台,可以轻松构建、部署和管理复杂的ML管道。
- MLflow: 由Databricks开发的开源平台,简化了机器学习生命周期。它提供了管理实验、可重现性和ML模型部署的工具。
- Apache Airflow: 虽然不专门为机器学习设计,但Apache Airflow是一款流行的工作流管理平台,可用于创建和管理ML管道。Airflow提供了一个可扩展的解决方案,用于编排工作流,允许您使用Python脚本定义任务、依赖关系和计划。
虽然有各种各样的选项可以创建管道,但大多数选项都不提供内置的方式来监控您的管道/模型并记录您的实验。为了解决这个问题,您可以考虑将灵活的实验跟踪工具连接到现有的模型训练设置中。这种方法可以提供增强的可见性和调试能力,而只需进行最少的额外工作。
我们将在接下来的部分中构建与此完全相同的东西。
构建模型训练管道面临的挑战
尽管有很多优点,但构建ML模型训练管道也存在一些挑战:
- 复杂性:设计管道需要理解组件之间的依赖关系和管理复杂的工作流。
- 工具选择:由于可选项众多,选择合适的工具和库可能会让人感到不知所措。
- 集成:将不同的工具和技术结合起来可能需要定制解决方案或适配器,这可能需要耗费大量时间来开发。
- 调试:由于组件之间的相互关联,识别和修复管道中的问题可能会很困难。
如何构建ML模型训练管道?
在本节中,我们将逐步介绍如何构建ML模型训练管道的教程。我们将使用Python和流行的Scikit-learn库。然后,我们将使用Optuna来优化模型的超参数,最后,我们将使用neptune.ai来记录您的实验。
对于教程的每个步骤,我将解释正在做什么,并为您分解代码,以便更容易理解。这段代码将遵循机器学习最佳实践,这意味着它将被优化并完全可重现。此外,在本例中,我使用的是静态数据集,因此不会执行任何数据摄取和特征工程等操作。
让我们开始吧!
1. 安装并导入所需的库。
- 这一步安装项目所需的必要库,如NumPy、pandas、scikit-learn、Optuna和Neptune。然后将这些库导入到脚本中,使其函数和类可用于教程中的使用。
使用pip安装所需的Python包。
pip install --quiet numpy==1.22.4 optuna==3.1.0 pandas==1.4.4 scikit-learn==1.2.2 neptune-client==0.16.16导入进行数据操作、预处理、模型训练、评估、超参数优化和日志记录所需的库。
import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score import optuna from functools import partial import neptune.new as neptune2. 初始化Neptune运行并连接到您的项目。
- 在这里,我们在Neptune中初始化一个新的运行,将其连接到一个Neptune项目。这允许我们记录实验数据并跟踪您的进展。
您需要用您的API令牌和项目名称替换占位符值。
run = neptune.init_run(api_token='your_api_token', project='username/project_name')3. 加载数据集。
- 在这一步中,我们将Titanic数据集从CSV文件加载到pandas DataFrame中。该数据集包含有关Titanic上的乘客的信息,包括他们的生存状态。
data = pd.read_csv("train.csv")4. 进行一些基本的预处理,比如删除不必要的列。
- 在这里,我们删除与机器学习模型无关的列,如PassengerId、Name、Ticket和Cabin。这简化了数据集并减少了过拟合的风险。
data = data.drop(["PassengerId", "Name", "Ticket", "Cabin"], axis=1)5. 将数据分为特征和标签。
- 我们将数据集分为输入特征(X)和目标标签(y)。输入特征是模型用来进行预测的独立变量,而目标标签是“Survived”列,表示乘客是否在Titanic灾难中幸存。
X = data.drop("Survived", axis=1) y = data["Survived"]6. 将数据分为训练集和测试集。
- 您可以使用scikit-learn的train_test_split函数将数据分为训练集和测试集。这样可以确保您有单独的数据用于训练模型和评估其性能。stratify参数用于在训练集和测试集中保持类别的比例。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)7. 定义预处理步骤。
- 我们创建一个ColumnTransformer,分别对数值和分类特征进行预处理。
- 数值特征使用一个管道进行处理,该管道使用均值进行缺失值填充,并使用标准化进行数据缩放。
- 分类特征使用一个管道进行处理,该管道使用最频繁的类别进行缺失值填充,并使用独热编码进行编码。
numerical_features = ["Age", "Fare"] categorical_features = ["Pclass", "Sex", "Embarked"] num_pipeline = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='mean')), ('scaler', StandardScaler()) ]) cat_pipeline = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='most_frequent')), ('encoder', OneHotEncoder()) ]) preprocessor = ColumnTransformer( transformers=[ ('num', num_pipeline, numerical_features), ('cat', cat_pipeline, categorical_features) ], remainder='passthrough' )8. 创建ML模型。
- 在这一步中,我们使用scikit-learn创建了一个RandomForestClassifier模型。这是一种集成学习方法,它构建多个决策树并结合它们的预测结果以提高准确性和减少过拟合。
model = RandomForestClassifier(random_state=42)9. 构建流水线。
- 我们创建了一个Pipeline对象,其中包括步骤7中定义的预处理步骤和步骤8中创建的模型。
- 流水线自动化了数据预处理和模型训练的整个过程,使工作流更高效、更易于维护。
pipeline = Pipeline(steps=[ ('preprocessor', preprocessor), ('classifier', model) ])10. 使用StratifiedKFold进行交叉验证。
- 我们使用StratifiedKFold方法进行交叉验证,该方法将训练数据集分成K个折叠,保持每个折叠中类别的比例。
- 模型会进行K次训练,使用K-1个折叠进行训练,一个折叠用于验证。这样可以更稳健地估计模型的性能。
- 我们将每个得分以及平均得分保存在我们的Neptune运行中。
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) cv_scores = cross_val_score(pipeline, X_train, y_train, cv=cv, scoring='accuracy') run["cross_val_accuracy_scores"] = cv_scores run["mean_cross_val_accuracy_scores"] = np.mean(cv_scores)11. 在整个训练集上训练流水线。
- 我们通过这个流水线训练模型,使用整个训练数据集。
pipeline.fit(X_train, y_train)这是我们创建的快照。
在示例中创建的模型训练流水线的工作流程图 | 来源:作者 12. 使用多个指标评估流水线。
- 我们使用各种性能指标(如准确率、精确率、召回率和F1得分)在测试集上评估流水线。这些指标提供了对模型性能的全面视图,并可以帮助识别改进的方向。
- 我们将每个得分保存在我们的Neptune运行中。
y_pred = pipeline.predict(X_test) accuracy = accuracy_score(y_test, y_pred) precision = precision_score(y_test, y_pred) recall = recall_score(y_test, y_pred) f1 = f1_score(y_test, y_pred) run["accuracy"] = accuracy run["precision"] = precision run["recall"] = recall run["f1"] = f113. 使用Optuna定义超参数搜索空间。
- 我们创建一个目标函数,该函数接收一个trial对象,并根据Optuna采样的超参数进行模型的训练和评估。
- 目标函数是优化过程的核心。它接收包含Optuna采样的超参数值的trial对象,并使用这些超参数训练流水线。然后将交叉验证准确性得分作为目标值返回,以进行优化。
def objective(X_train, y_train, pipeline, cv, trial: optuna.Trial):
params = {
'classifier__n_estimators': trial.suggest_int('classifier__n_estimators', 10, 200),
'classifier__max_depth': trial.suggest_int('classifier__max_depth', 10, 50),
'classifier__min_samples_split': trial.suggest_int('classifier__min_samples_split', 2, 10),
'classifier__min_samples_leaf': trial.suggest_int('classifier__min_samples_leaf', 1, 5),
'classifier__max_features': trial.suggest_categorical('classifier__max_features', ['auto', 'sqrt'])
}
pipeline.set_params(**params)
scores = cross_val_score(pipeline, X_train, y_train, cv=cv, scoring='accuracy', n_jobs=-1)
mean_score = np.mean(scores)
return mean_score
如果你觉得上面的代码令人不知所措,这里是一个快速的解释:
- 使用trial.suggest_*方法定义超参数。这些方法告诉Optuna每个超参数的搜索空间。例如,trial.suggest_int(‘classifier__n_estimators’, 10, 200)指定了n_estimators参数的整数搜索空间,范围从10到200。
- 使用pipeline.set_params(**params)方法设置流水线的超参数。这个方法接受包含抽样超参数的字典params,并将它们设置为流水线的超参数。
- 使用cross_val_score函数计算交叉验证准确率得分。这个函数使用指定的cv对象和评分指标(在这个例子中是’accuracy’)对流水线进行训练和评估。
- 使用np.mean(scores)计算交叉验证得分的平均值,并将这个值作为目标值返回,由Optuna最大化。
14. 使用Optuna进行超参数调优。
- 我们创建一个指定方向(最大化)和采样器(TPE采样器)的研究。
- 然后,我们调用study.optimize方法,传入目标函数、试验次数和其他所需选项。
- Optuna将运行多个试验,每个试验使用不同的超参数值,以找到最佳组合,最大化目标函数(平均交叉验证准确率得分)。
study = optuna.create_study(direction="maximize", sampler=optuna.samplers.TPESampler(seed=42)) study.optimize(partial(objective, X_train, y_train, pipeline, cv), n_trials=50, timeout=None, gc_after_trial=True)15. 设置最佳参数并训练流水线。
- 在Optuna找到最佳超参数后,我们将这些参数设置到流水线中,并使用整个训练数据集重新训练它。这样可以确保使用优化的超参数对模型进行训练。
pipeline.set_params(**study.best_trial.params) pipeline.fit(X_train, y_train)16. 使用多个指标评估最佳模型。
- 我们使用与之前相同的性能指标(准确率、精确率、召回率和F1分数)评估优化后模型在测试集上的性能。这样可以将优化后模型的性能与初始模型进行比较。
- 我们将调整后模型的每个分数保存到 Neptune 运行中。
y_pred = pipeline.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred)
recall = recall_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
run["accuracy_tuned"] = accuracy
run["precision_tuned"] = precision
run["recall_tuned"] = recall
run["f1_tuned"] = f1
- 如果你运行这段代码并只关注这些指标的性能,可能会认为调整后的模型比之前的模型更差。然而,如果你看一下平均交叉验证得分,这是一个更可靠的评估模型的方法,你会发现调整后的模型在整个数据集上表现良好,更可靠。
17. 在 Neptune 上记录超参数、最佳试验参数和最佳得分。
- 你在 Neptune 上记录最佳试验参数和相应的最佳得分,以便跟踪实验的进展和结果。
run['parameters'] = study.best_trial.params run['best_trial'] = study.best_trial.number run['best_score'] = study.best_value18. 记录分类报告和混淆矩阵。
- 你记录模型的分类报告和混淆矩阵,提供了模型在每个类别上性能的详细视图。这可以帮助你确定模型可能表现不佳的领域,并指导进一步改进。
from sklearn.metrics import classification_report, confusion_matrix y_pred = pipeline.predict(X_test) # 记录分类报告 report = classification_report(y_test, y_pred, output_dict=True) for label, metrics in report.items(): if isinstance(metrics, dict): for metric, value in metrics.items(): run[f'classification_report/{label}/{metric}'] = value else: run[f'classification_report/{label}'] = metrics # 记录混淆矩阵 conf_mat = confusion_matrix(y_test, y_pred) conf_mat_plot = px.imshow(conf_mat, labels=dict(x="Predict", y="Target"), x=[x+1 for x in range(len(conf_mat[0]))], y=[x+1 for x in range(len(conf_mat[0]))]) run['confusion_matrix'].upload(neptune.types.File.as_html(conf_mat_plot))19. 将管道保存为pickle文件。
- 将管道保存为pickle文件并上传到Neptune。这样可以轻松共享、重用和部署训练好的模型。
import joblib joblib.dump(pipeline, 'optimized_pipeline.pkl') run['optimized_pipeline'].upload(neptune.types.File.as_pickle('optimized_pipeline.pkl'))20. 停止Neptune运行。
- 最后,停止Neptune运行,表示实验已完成。这样可以确保所有数据被保存,并释放所有资源。
run.stop()下面是使用Neptune可以构建的仪表板。如您所见,它包含有关我们的模型(超参数)、分类报告指标和混淆矩阵的信息。
Neptune中的常规仪表板,其中包含示例实验的数据记录 | 在此项目中进行实时操作 为了展示使用Neptune这样的工具对训练实验进行跟踪和比较的能力,我们通过将Optuna目标函数中的评分参数更改为“recall”来创建了另一个运行。下面是两个运行的比较。
Neptune中的比较运行功能 | 在此项目中进行实时操作 这样的比较使您可以将所有内容集中在一处,并根据每个管道迭代的性能做出明智的决策。
如果您一直都在阅读到这里,那么您可能已经实现了带有所有必要附件的训练管道。
这个特定的示例展示了如何将实验跟踪工具与训练管道集成在一起,为您的项目提供个性化视图,提高生产力。
如果您有兴趣复制这种方法,可以探索Kedro和Neptune的组合解决方案,它们在创建和跟踪管道方面配合得很好。在这里,您可以找到有关如何使用Kedro和Neptune的示例和文档。
总结一下,这是我们创建和优化管道以及跟踪由其生成的指标所采取的所有步骤的简单流程图。无论您要解决的问题是什么,在任何这样的练习中,主要步骤都是相同的。
创建和优化模型训练管道以及跟踪由其生成的指标的步骤 | 来源:作者 以分布式方式训练您的机器学习模型
到目前为止,我们已经讨论了如何创建一个用于训练模型的管道,但是如果您正在处理大型数据集或复杂模型,那么您可能需要考虑分布式训练。
通过将训练过程分布到多个设备上,可以显著加快训练速度并提高效率。在本节中,我们将简要介绍分布式训练的概念以及如何将其纳入您的管道中。
- 选择分布式训练框架:有几个可用的分布式训练框架,例如TensorFlow的tf.distribute、PyTorch的torch.distributed或Horovod。选择与您的机器学习库兼容并最适合您需求的框架。
- 设置本地集群:要在本地集群上训练模型,您需要适当配置计算资源。这包括设置设备网络(如GPU或CPU)并确保它们能够高效通信。
- 调整训练代码:修改现有的训练代码以利用所选择的分布式训练框架。这可能涉及到初始化模型的方式、处理数据加载或执行梯度更新的方式的更改。
- 监控和管理分布式训练过程:跟踪分布式训练过程的性能和资源使用情况。这可以帮助您识别瓶颈、确保资源高效利用,并在训练过程中保持稳定。
尽管这个主题超出了本文的范围,但在构建ML模型训练流程时,了解分布式训练的复杂性和考虑因素是很重要的,以防将来想要朝这个方向发展。为了有效地将分布式训练纳入到你的ML模型训练流程中,以下是一些有用的资源:
- 对于TensorFlow用户:使用TensorFlow进行分布式训练
- 对于PyTorch用户:分布式数据并行入门
- 对于Horovod用户:Horovod的官方文档
- 总览性指南:Neptune的分布式训练:数据科学家指南
- 如果你计划在特定的云平台上使用分布式训练,请确保查阅平台文档中提供的相关教程。
这些资源将帮助你通过利用分布式训练的强大能力来增强你的ML模型训练流程。
在构建模型训练流程时应考虑的最佳实践
一个良好设计的训练流程可以确保机器学习过程中的可复现性和可维护性。在本节中,我们将探讨为不同项目创建有效、高效和易于适应的流程的几个最佳实践。
- 在任何处理之前拆分你的数据:在进行任何预处理或特征工程之前,将数据分为训练集和测试集是至关重要的。这样可以确保你的模型评估是无偏的,并且你不会无意地将测试集中的信息泄露到训练集中,这可能导致过于乐观的性能估计。
- 将数据预处理、特征工程和模型训练步骤分开:将流程分解为这些不同的步骤使代码更容易理解、维护和修改。这种模块化使你可以轻松地更改或扩展流程的任何部分,而不会影响其他部分。
- 使用交叉验证来估计模型性能:交叉验证可以帮助你更好地估计模型在未见过数据上的性能。通过将训练数据分成多个折叠,并在不同折叠的组合上迭代地训练和评估模型,你可以得到更准确可靠的模型真实性能估计。
- 在训练集-测试集拆分和交叉验证过程中使用分层抽样:分层抽样可以确保每个拆分或折叠具有类似的目标变量分布,这有助于维持更具代表性的训练和评估数据样本。对于不平衡数据集,分层抽样尤为重要,因为它有助于避免创建只有极少数少数类示例的拆分。
- 为了可复现性,使用一致的随机种子:通过在代码中设置一致的随机种子,确保在每次运行代码时使用的随机数生成是相同的。这使你的结果具有可复现性,更容易调试,并允许其他研究人员复现你的实验并验证你的发现。
- 使用搜索方法优化超参数:调整超参数是改进模型性能的关键步骤。网格搜索、随机搜索和贝叶斯优化是常用的方法,用于探索超参数搜索空间并找到最佳的超参数组合。Optuna是一个强大的库,可以用于超参数优化。
- 使用版本控制系统和记录实验:像Git这样的版本控制系统可以帮助你跟踪代码的变化,使与他人合作更容易,并在需要时恢复到以前的版本。Neptune等实验跟踪工具可以帮助你记录和可视化实验结果,跟踪模型性能的演变,并比较不同的模型和超参数设置。
- 记录你的流程和结果:良好的文档使你的工作对他人更易理解,并帮助你更好地理解自己的工作。在你的代码中编写清晰简洁的注释,解释每个步骤和函数的目的。使用Jupyter Notebook、Markdown或甚至在代码中使用注释的工具来记录你的流程、方法和结果。
- 自动化重复性任务:使用脚本和自动化工具来简化数据预处理、特征工程和超参数调整等重复性任务。这不仅节省时间,还减少了流程中错误和不一致性的风险。
- 测试你的流程:编写单元测试,以确保你的流程按预期工作,并在整个流程中传播错误之前捕获它们。这可以帮助你及早发现问题,并保持高质量的代码库。
- 在训练过程中定期审查和优化您的流程:随着数据的演变或问题领域的变化,审查流程以确保其性能和有效性非常重要。这种主动的方法可以使您的流程保持最新和适应性,从而在面对变化的数据和问题领域时保持其效率。
结论
在本教程中,我们介绍了使用Scikit-learn和其他有用工具(如Optuna和Neptune)构建机器学习训练流程的基本组件。我们演示了如何预处理数据、创建模型、进行交叉验证、优化超参数以及在Titanic数据集上评估模型性能。通过将结果记录到Neptune中,您可以轻松跟踪和比较您的实验,进一步改进您的模型。
通过遵循这些指南和最佳实践,您可以为您的机器学习项目创建高效、易于维护和适应性强的流程。无论您是使用Titanic数据集还是其他任何数据集,这些原则都将帮助您简化流程,并确保在工作的不同迭代之间实现可重复性。