第 3 部分:训练和注册机器学习模型

本教程介绍如何训练多个机器学习模型以选择最佳模型,以便预测哪些银行客户可能离开。

在本教程中,你将:

  • 训练 Random Forrest 和 LightGBM 模型。
  • 使用 Microsoft Fabric 与 MLflow 框架的本机集成来记录训练的机器学习模型、使用的超参数和评估指标。
  • 注册训练的机器学习模型。
  • 评估验证数据集上训练的机器学习模型的性能。

MLflow 是一个开源平台,用于使用跟踪、模型和模型注册表等功能管理机器学习生命周期。 MLflow 原生与 Fabric 数据科学体验集成。

先决条件

这是系列教程的第 3 部分(共 5 部分)。 若要完成本教程,请先完成:

在笔记本中继续操作

3-train-evaluate.ipynb 是本教程随附的笔记本。

若要打开本教程随附的笔记本,请按照让系统为数据科学做好准备教程中的说明操作,将该笔记本导入到工作区。

或者,如果要从此页面复制并粘贴代码,则可以创建新的笔记本

在开始运行代码之前,请务必将湖屋连接到笔记本

重要

附加第 1 部分和第 2 部分中使用的湖屋。

安装自定义库

对于此笔记本,你将使用 %pip install 安装不平衡学习(导入为 imblearn)。 不平衡学习是合成少数过采样技术 (SMOTE) 的库,在处理不平衡数据集时使用。 PySpark 内核将在 %pip install 之后重启,因此需要先安装库,然后才能运行任何其他单元格。

你将使用 imblearn 库访问 SMOTE。 现在使用内联安装功能(例如 %pip%conda)进行安装。

# Install imblearn for SMOTE using pip
%pip install imblearn

重要

每次重启笔记本时,都要运行此安装。

在笔记本中安装库时,它将仅在笔记本会话期间可用,而不适用于工作区。 如果重新启动笔记本,则需要再次安装库。

如果有经常要使用的库,并且想要将其对工作区中的所有笔记本开放,则可以使用针对此用途的 Fabric 环境。 可以创建一个环境并在其中安装库,然后“工作区管理员”可以将该环境作为默认环境附加到工作区。 有关将环境设置为工作区默认值的详细信息,请参阅管理员设置工作区的默认库

有关将现有工作区库和 Spark 属性迁移到环境的信息,请参阅将工作区库和 Spark 属性迁移到默认环境

加载数据

在训练任何机器学习模型之前,需要从湖屋加载 delta 表,以便读取在上一笔记本中创建的清理数据。

import pandas as pd
SEED = 12345
df_clean = spark.read.format("delta").load("Tables/df_clean").toPandas()

使用 MLflow 生成用于跟踪和记录模型的试验

本部分演示如何生成试验、指定机器学习模型和训练参数以及评分指标、训练机器学习模型、记录这些模型,以及保存训练的模型供以后使用。

import mlflow
# Setup experiment name
EXPERIMENT_NAME = "bank-churn-experiment"  # MLflow experiment name

自动记录扩展了 MLflow 自动记录功能,它的工作原理是在训练机器学习模型时自动捕获其输入参数和输出指标的值。 然后,该信息会记录到工作区。在那里,可以使用 MLflow API 或工作区中的相应试验对其进行访问和可视化。

所有具有各自名称的试验都将被记录下来,并且你能够跟踪其参数和性能指标。 若要详细了解自动记录,请参阅 Microsoft Fabric 中的自动记录

设置试验和自动记录规范

mlflow.set_experiment(EXPERIMENT_NAME)
mlflow.autolog(exclusive=False)

导入 scikit-learn 和 LightGBM

准备好数据后,便可以定义机器学习模型。 你将在此笔记本中应用随机林和 LightGBM 模型。 使用 scikit-learnlightgbm 通过短短几行代码实现模型。

# Import the required libraries for model training
from sklearn.model_selection import train_test_split
from lightgbm import LGBMClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score, precision_score, confusion_matrix, recall_score, roc_auc_score, classification_report

准备训练、验证和测试数据集

使用 scikit-learn 中的 train_test_split 函数将数据拆分为训练、验证和测试集。

y = df_clean["Exited"]
X = df_clean.drop("Exited",axis=1)
# Split the dataset to 60%, 20%, 20% for training, validation, and test datasets
# Train-Test Separation
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=SEED)
# Train-Validation Separation
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=SEED)

将测试数据保存到 delta 表

将测试数据保存到 delta 表,以便在下一个笔记本中使用。

table_name = "df_test"
# Create PySpark DataFrame from Pandas
df_test=spark.createDataFrame(X_test)
df_test.write.mode("overwrite").format("delta").save(f"Tables/{table_name}")
print(f"Spark test DataFrame saved to delta table: {table_name}")

将 SMOTE 应用于训练数据,以合成少数类的新样本

第 2 部分的数据探索显示,在与 10,000 个客户对应的 10,000 个数据点中,只有 2,037 个客户(约 20%)离开银行。 这表示数据集高度不平衡。 分类不平衡的问题在于,少数类的示例太少,模型无法有效了解决策边界。 SMOTE 是为少数类合成新样本的最广泛使用的方法。 在此处此处了解有关 SMOTE 的详细信息。

提示

请注意,SMOTE 应仅应用于训练数据集。 必须使测试数据集保留为其原始不平衡分布,以便获取有关机器学习模型在处理原始数据时的表现的有效近似值,这代表了生产环境中的情况。

from collections import Counter
from imblearn.over_sampling import SMOTE

sm = SMOTE(random_state=SEED)
X_res, y_res = sm.fit_resample(X_train, y_train)
new_train = pd.concat([X_res, y_res], axis=1)

提示

运行此单元格时显示的 MLflow 警告消息可以安全地忽略。 如果看到 ModuleNotFoundError 消息,则表示没有运行此笔记本中的第一个单元格,这会安装 imblearn 库。 每次重启笔记本时,都需要再次安装此库。 返回并从此笔记本中的第一个单元格开始重新运行所有单元格。

模型定型

  • 使用随机林训练模型,最大深度为 4,具有 4 个特征
mlflow.sklearn.autolog(registered_model_name='rfc1_sm') # Register the trained model with autologging
rfc1_sm = RandomForestClassifier(max_depth=4, max_features=4, min_samples_split=3, random_state=1) # Pass hyperparameters
with mlflow.start_run(run_name="rfc1_sm") as run:
    rfc1_sm_run_id = run.info.run_id # Capture run_id for model prediction later
    print("run_id: {}; status: {}".format(rfc1_sm_run_id, run.info.status))
    # rfc1.fit(X_train,y_train) # Imbalanaced training data
    rfc1_sm.fit(X_res, y_res.ravel()) # Balanced training data
    rfc1_sm.score(X_val, y_val)
    y_pred = rfc1_sm.predict(X_val)
    cr_rfc1_sm = classification_report(y_val, y_pred)
    cm_rfc1_sm = confusion_matrix(y_val, y_pred)
    roc_auc_rfc1_sm = roc_auc_score(y_res, rfc1_sm.predict_proba(X_res)[:, 1])
  • 使用随机林训练模型,最大深度为 8,具有 6 个特征
mlflow.sklearn.autolog(registered_model_name='rfc2_sm') # Register the trained model with autologging
rfc2_sm = RandomForestClassifier(max_depth=8, max_features=6, min_samples_split=3, random_state=1) # Pass hyperparameters
with mlflow.start_run(run_name="rfc2_sm") as run:
    rfc2_sm_run_id = run.info.run_id # Capture run_id for model prediction later
    print("run_id: {}; status: {}".format(rfc2_sm_run_id, run.info.status))
    # rfc2.fit(X_train,y_train) # Imbalanced training data
    rfc2_sm.fit(X_res, y_res.ravel()) # Balanced training data
    rfc2_sm.score(X_val, y_val)
    y_pred = rfc2_sm.predict(X_val)
    cr_rfc2_sm = classification_report(y_val, y_pred)
    cm_rfc2_sm = confusion_matrix(y_val, y_pred)
    roc_auc_rfc2_sm = roc_auc_score(y_res, rfc2_sm.predict_proba(X_res)[:, 1])
  • 使用 LightGBM 训练模型
# lgbm_model
mlflow.lightgbm.autolog(registered_model_name='lgbm_sm') # Register the trained model with autologging
lgbm_sm_model = LGBMClassifier(learning_rate = 0.07, 
                        max_delta_step = 2, 
                        n_estimators = 100,
                        max_depth = 10, 
                        eval_metric = "logloss", 
                        objective='binary', 
                        random_state=42)

with mlflow.start_run(run_name="lgbm_sm") as run:
    lgbm1_sm_run_id = run.info.run_id # Capture run_id for model prediction later
    # lgbm_sm_model.fit(X_train,y_train) # Imbalanced training data
    lgbm_sm_model.fit(X_res, y_res.ravel()) # Balanced training data
    y_pred = lgbm_sm_model.predict(X_val)
    accuracy = accuracy_score(y_val, y_pred)
    cr_lgbm_sm = classification_report(y_val, y_pred)
    cm_lgbm_sm = confusion_matrix(y_val, y_pred)
    roc_auc_lgbm_sm = roc_auc_score(y_res, lgbm_sm_model.predict_proba(X_res)[:, 1])

用于跟踪模型性能的试验项目

试验运行会自动保存在可从工作区中找到的试验项目中。 它们根据用于设置试验的名称进行命名。 记录所有经过训练的机器学习模型及其运行、性能指标和模型参数。

查看试验:

  1. 在左侧面板中,选择工作区。

  2. 在右上角可进行筛选,使其仅显示试验,以便更轻松地查找所需的试验。

    屏幕截图,其中显示选择了试验筛选条件的工作区。

  3. 查找并选择试验名称,在本例中为 bank-churn-experiment。 如果在工作区中未看到试验,请刷新浏览器。

    显示银行客户流失试验的试验页面的屏幕截图。

评估验证数据集上训练的模型的性能

完成机器学习模型训练后,可以通过两种方式评估已训练模型的性能。

  • 从工作区打开保存的试验,加载机器学习模型,然后在验证数据集上评估加载的模型的性能。

    # Define run_uri to fetch the model
    # mlflow client: mlflow.model.url, list model
    load_model_rfc1_sm = mlflow.sklearn.load_model(f"runs:/{rfc1_sm_run_id}/model")
    load_model_rfc2_sm = mlflow.sklearn.load_model(f"runs:/{rfc2_sm_run_id}/model")
    load_model_lgbm1_sm = mlflow.lightgbm.load_model(f"runs:/{lgbm1_sm_run_id}/model")
    # Assess the performance of the loaded model on validation dataset
    ypred_rfc1_sm_v1 = load_model_rfc1_sm.predict(X_val) # Random Forest with max depth of 4 and 4 features
    ypred_rfc2_sm_v1 = load_model_rfc2_sm.predict(X_val) # Random Forest with max depth of 8 and 6 features
    ypred_lgbm1_sm_v1 = load_model_lgbm1_sm.predict(X_val) # LightGBM
    
  • 直接评估验证数据集上训练的机器学习模型的性能。

    ypred_rfc1_sm_v2 = rfc1_sm.predict(X_val) # Random Forest with max depth of 4 and 4 features
    ypred_rfc2_sm_v2 = rfc2_sm.predict(X_val) # Random Forest with max depth of 8 and 6 features
    ypred_lgbm1_sm_v2 = lgbm_sm_model.predict(X_val) # LightGBM
    

根据偏好,任一方法都是正常的,应提供相同的性能。 在此笔记本中,你将选择第一种方法,以便更好地演示 Microsoft Fabric 中的 MLflow 自动记录功能。

使用混淆矩阵显示真正、假正、真负和假负

接下来,你将开发一个脚本来绘制混淆矩阵,以便使用验证数据集评估分类的准确性。 还可以使用 SynapseML 工具绘制混淆矩阵,如此处可用的欺诈检测示例中所示。

import seaborn as sns
sns.set_theme(style="whitegrid", palette="tab10", rc = {'figure.figsize':(9,6)})
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from matplotlib import rc, rcParams
import numpy as np
import itertools

def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    print(cm)
    plt.figure(figsize=(4,4))
    plt.rcParams.update({'font.size': 10})
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45, color="blue")
    plt.yticks(tick_marks, classes, color="blue")

    fmt = '.2f' if normalize else 'd'
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, format(cm[i, j], fmt),
                 horizontalalignment="center",
                 color="red" if cm[i, j] > thresh else "black")

    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
  • 随机林分类器的混淆矩阵,最大深度为 4,具有 4 个特征
cfm = confusion_matrix(y_val, y_pred=ypred_rfc1_sm_v1)
plot_confusion_matrix(cfm, classes=['Non Churn','Churn'],
                      title='Random Forest with max depth of 4')
tn, fp, fn, tp = cfm.ravel()

图显示了随机林的混淆矩阵,最大深度为 4。

  • 随机林分类器的混淆矩阵,最大深度为 8,具有 6 个特征
cfm = confusion_matrix(y_val, y_pred=ypred_rfc2_sm_v1)
plot_confusion_matrix(cfm, classes=['Non Churn','Churn'],
                      title='Random Forest with max depth of 8')
tn, fp, fn, tp = cfm.ravel()

图显示了随机林的混淆矩阵,最大深度为 8。

  • LightGBM 的混淆矩阵
cfm = confusion_matrix(y_val, y_pred=ypred_lgbm1_sm_v1)
plot_confusion_matrix(cfm, classes=['Non Churn','Churn'],
                      title='LightGBM')
tn, fp, fn, tp = cfm.ravel()

图显示了 LightGBM 的混淆矩阵。

下一步