分析 BigQuery 中的 AI Platform Prediction 日志

本文档是系列文章中的第二篇,该系列介绍了如何监控部署到 AI Platform Prediction 的机器学习 (ML) 模型,以帮助您检测数据偏差。本指南介绍如何将 AI Platform Prediction 请求-响应日志中的原始数据解析为分析数据模型。然后,本指南介绍如何使用数据洞察来分析对数据偏差和偏移的已记录请求。

本系列文章适用于想要通过监控应用数据如何随时间变化来维持生产环境中机器学习模型的性能的数据科学家和 MLOp 工程师。本教程假定您具备一些 Google Cloud、BigQuery 和 Jupyter 笔记本方面的经验。

系列文章包含以下指南:

本文讨论的任务已纳入 Jupyter 笔记本中。这些笔记本位于 GitHub 代码库中。

概览

如本系列文章的第一部分所述,Jupyter 笔记本中的代码为 Covertype 数据集训练 Keras 分类模型以根据地图学变量预测森林覆盖类型。然后,将导出的 SavedModel 部署到 AI Platform Prediction 以用于在线应用。此外,笔记本还允许请求-响应日志记录将在线预测请求(实例)和响应(预测的标签概率)的样本记录到 BigQuery 表。

整体架构如下图所示:

本教程系列中所创建流程的架构。

在此架构中,AI Platform Prediction 请求-响应日志记录会将在线请求的示例记录到 BigQuery 表中。将原始实例和预测数据存储在 BigQuery 中之后,您可以解析此数据、计算描述性统计信息并直观呈现数据偏差和数据偏移。

下表汇总了 BigQuery 表的架构。

字段名称 类型 Mode 说明
model STRING REQUIRED 模型的名称
model_version STRING REQUIRED 模型版本的名称
time TIMESTAMP REQUIRED 捕获请求的日期和时间
raw_data STRING REQUIRED 采用 AI Platform Prediction JSON 表示法的请求正文
raw_prediction STRING NULLABLE 采用 AI Platform Prediction JSON 表示法的响应正文(预测)
groundtruth STRING NULLABLE 标准答案(如果可用)

下表显示了存储在 BigQuery 表的 raw_dataraw_prediction 列中的数据的样本。

样本数据
raw_data

{
  "signature_name":"serving_default",
  "instances":[
    {
      "Elevation":[3158],
      "Aspect":[78],
      "Slope":[25],
      "Horizontal_Distance_To_Hydrology":[150],
      "Vertical_Distance_To_Hydrology":[53],
      "Horizontal_Distance_To_Roadways":[1080],
      "Hillshade_9am":[243],
      "Hillshade_Noon":[185],
      "Hillshade_3pm":[57],
      "Horizontal_Distance_To_Fire_Points":[2234],
      "Wilderness_Area":["Rawah"],
      "Soil_Type":["7745"],
    }
  ]
}
raw_prediction

{
 "predictions": [
   {
     "probabilities": [
       0.03593460097908974,
       0.9640452861785889,
       1.2815438710234162e-9,
       1.5712469103590365e-9,
       0.000018715836631599814,
       4.030006106603423e-9,
       0.0000013792159734293818
     ],
     "confidence":  0.9640452861785889,
     "predicted_label": "1"

   }
 ]
}

在本文档中,您将 raw_data 字段解析为分析模型,以便单独分析每项特征的内容并识别任何数据偏差。

目标

  • 创建数据集的元数据。
  • 生成特定于数据集的 SQL CREATE VIEW 语句。
  • 使用视图查询 BigQuery 中的日志数据。
  • 创建日志数据的直观呈现。

费用

本教程使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用量来估算费用。 Google Cloud 新用户可能有资格申请免费试用

准备工作

在开始之前,您必须完成本系列文章的第一部分

在您完成第一部分后,您就具有:

  • 使用 TensorFlow 2.3 的 Notebooks 实例
  • GitHub 代码库的克隆,其中包含您需要用于本指南的 Jupyter 笔记本。

适合此场景的 Jupyter 笔记本

用于解析和分析数据的任务已纳入 GitHub 代码库中的 Jupyter 笔记本中。如需执行这些任务,您需要获取笔记本,然后按顺序执行笔记本中的代码单元。

在本文档中,您将使用 Jupyter 笔记本执行以下任务:

  • 创建 BigQuery SQL 视图以解析原始请求和响应点数据点。您通过运行汇编信息(例如数据集的元数据和您提供的)的代码来生成视图。
  • 使用人工偏差模拟几天时间的应用数据流程。
  • 使用数据洞察直观呈现记录在 BigQuery 中的已解析应用数据。

配置笔记本设置

在笔记本的这一部分中,您需要准备 Python 环境来为运行适合该场景的代码。笔记本中的代码会创建基于数据集的特征规范的视图。为了生成 CREATE OR REPLACE VIEW SQL 脚本,您需要设置多个变量。

  1. 如果您还没有在 Cloud Console 中打开第一部分中的 AI Platform Notebooks 实例,请执行以下操作:

    1. 转到 Notebooks 页面。

      转到 Notebooks 页面

    2. 在 Notebooks 列表中,选择笔记本,然后点击打开 Jupyterlab。JupyterLab 环境会在浏览器中打开。

    3. 在文件浏览器中,打开 mlops-on-gcp,然后导航到 skew-detection1 目录。

  2. 打开 02-covertype-logs-parsing-analysis.ipynb 笔记本。

  3. 在笔记本中的设置下,运行安装软件包和依赖项单元以安装所需的 Python 软件包并配置环境变量。

  4. 配置 Google Cloud 环境设置下,设置以下变量:

    • PROJECT_ID:在其中记录请求-响应数据的 BigQuery 数据集的 Google Cloud 项目的 ID。
    • BQ_DATASET_NAME:用于存储请求-响应日志的 BigQuery 数据集的名称。
    • BQ_TABLE_NAME:用于存储请求-响应日志的 BigQuery 表的名称。
    • MODEL_NAME:部署到 AI Platform Prediction 的模型的名称。
    • VERSION_NAME:部署到 AI Platform Prediction 的模型的版本名称。版本采用 vN 格式;例如 v1
  5. 运行设置下的其余单元以完成环境配置:

    1. 验证您的 GCP 帐号
    2. 导入库

定义数据集的元数据

运行笔记本的第一部分定义数据集元数据,以设置变量供稍后在用于生成 SQL 脚本的代码中使用。例如,本部分中的代码会创建名为 NUMERIC_FEATURE_NAMESCATEGORICAL_FEATURES_WITH_VOCABULARY 的两个变量,如以下代码段所示:

NUMERIC_FEATURE_NAMES = ['Aspect', 'Elevation', 'Hillshade_3pm',
                         'Hillshade_9am', 'Hillshade_Noon',
                         'Horizontal_Distance_To_Fire_Points',
                         'Horizontal_Distance_To_Hydrology',
                         'Horizontal_Distance_To_Roadways','Slope',
                         'Vertical_Distance_To_Hydrology']

CATEGORICAL_FEATURES_WITH_VOCABULARY = {
    'Soil_Type': ['2702', '2703', '2704', '2705', '2706', '2717', '3501', '3502',
                  '4201', '4703', '4704', '4744', '4758', '5101', '6101', '6102',
                  '6731', '7101', '7102', '7103', '7201', '7202', '7700', '7701',
                  '7702', '7709', '7710', '7745', '7746', '7755', '7756', '7757',
                  '7790', '8703', '8707', '8708', '8771', '8772', '8776'],
    'Wilderness_Area': ['Cache', 'Commanche', 'Neota', 'Rawah']
}

然后,该代码会创建一个名为 FEATURE_NAMES 的变量来组合这些值,如以下行所示:

FEATURE_NAMES = list(CATEGORICAL_FEATURES_WITH_VOCABULARY.keys()) + NUMERIC_FEATURE_NAMES

生成 CREATE VIEW SQL 脚本

您可以运行笔记本的第二部分中的任务来生成 CREATE VIEW 语句,稍后运行该语句来解析日志。

第一个任务运行代码以根据 json_features_extractionjson_prediction_extraction 变量的数据元数据创建值。这些变量包含采用可插入到 SQL 语句的格式的特征和预测值。

此代码依赖于您之前在配置笔记本设置时和为数据集定义元数据时设置的变量。以下代码段显示了此代码。

LABEL_KEY = 'predicted_label'
SCORE_KEY = 'confidence'
SIGNATURE_NAME = 'serving_default'

def _extract_json(column, feature_name):
  return "JSON_EXTRACT({}, '$.{}')".format(column, feature_name)

def _replace_brackets(field):
  return "REPLACE(REPLACE({}, ']', ''), '[','')".format(field)

def _replace_quotes(field):
  return 'REPLACE({}, "\\"","")'.format(field)

def _cast_to_numeric(field):
  return "CAST({} AS NUMERIC)".format(field)

def _add_alias(field, feature_name):
  return "{} AS {}".format(field, feature_name)

view_name = "vw_"+BQ_TABLE_NAME+"_"+VERSION_NAME

colum_names = FEATURE_NAMES
input_features = ', \r\n  '.join(colum_names)

json_features_extraction = []
for feature_name in colum_names:
  field = _extract_json('instance', feature_name)
  field = _replace_brackets(field)
  if feature_name in NUMERIC_FEATURE_NAMES:
    field = _cast_to_numeric(field)
  else:
    field = _replace_quotes(field)
  field = _add_alias(field, feature_name)
  json_features_extraction.append(field)

json_features_extraction = ', \r\n    '.join(json_features_extraction)

json_prediction_extraction = []
for feature_name in [LABEL_KEY, SCORE_KEY]:
  field = _extract_json('prediction', feature_name)
  field = _replace_brackets(field)
  if feature_name == SCORE_KEY:
    field = _cast_to_numeric(field)
  else:
    field = _replace_quotes(field)
  field = _add_alias(field, feature_name)
  json_prediction_extraction.append(field)

json_prediction_extraction = ', \r\n    '.join(json_prediction_extraction)

第二个任务将名为 sql_script 的变量设置为包含 CREATE OR REPLACE VIEW 语句的长字符串。该语句包含多个占位符,这些占位符使用 @ 作为前缀在字符串中进行标记。例如,数据集和视图的名称的占位符:

CREATE OR REPLACE VIEW @dataset_name.@view_name

还有项目、表、模型和版本名称的占位符:

FROM
  `@project.@dataset_name.@table_name`
  WHERE
    model = '@model_name' AND
    model_version = '@version'
)

语句末尾包含占位符,它们使用您在前一个任务中运行代码创建的 json_features_extractionjson_prediction_extraction 变量:

step3 AS
(SELECT
    model,
    model_version,
    time,
    @json_features_extraction,
    @json_prediction_extraction
FROM step2
)

最后,运行下一个单元,将 SQL 语句中的占位符替换为您之前设置的值,如以下代码段所示:

sql_script = sql_script.replace("@project", PROJECT_ID)
sql_script = sql_script.replace("@dataset_name", BQ_DATASET_NAME)
sql_script = sql_script.replace("@table_name", BQ_TABLE_NAME)
sql_script = sql_script.replace("@view_name", view_name)
sql_script = sql_script.replace("@model_name", MODEL_NAME)
sql_script = sql_script.replace("@version", VERSION_NAME)
sql_script = sql_script.replace("@input_features", input_features)
sql_script = sql_script.replace("@json_features_extraction", json_features_extraction)
sql_script = sql_script.replace("@json_prediction_extraction", json_prediction_extraction)

此步骤完成生成 SQL 语句,该语句用于创建视图以解析原始请求和响应点数据点。

如果要查看所生成的脚本,请运行用于打印视图的单元。此单元包含以下代码:

print(sql_script)

执行 CREATE VIEW SQL 脚本

如需执行 CREATE VIEW 语句,请运行笔记本的第三部分中代码。完成后,代码会显示消息 View created or replaced。当您看到此消息后,用于解析数据的视图已准备就绪。

以下代码段显示生成的语句。

CREATE OR REPLACE VIEW prediction_logs.vw_covertype_classifier_logs_v1
AS

WITH step1 AS
(
  SELECT
    model,
    model_version,
    time,
    SPLIT(JSON_EXTRACT(raw_data, '$.instances'), '}],[{') instance_list,
    SPLIT(JSON_EXTRACT(raw_prediction, '$.predictions'), '}],[{') as prediction_list
  FROM
  `sa-data-validation.prediction_logs.covertype_classifier_logs`
  WHERE
    model = 'covertype_classifier' AND
    model_version = 'v1'
),

step2 AS
(
  SELECT
    model,
    model_version,
    time,
    REPLACE(REPLACE(instance, '[{', '{'),'}]', '}') AS instance,
    REPLACE(REPLACE(prediction, '[{', '{'),'}]', '}') AS prediction,
  FROM step1
  JOIN UNNEST(step1.instance_list) AS instance
  WITH OFFSET AS f1
  JOIN UNNEST(step1.prediction_list) AS prediction
  WITH OFFSET AS f2
  ON f1=f2
),

step3 AS
(
  SELECT
    model,
    model_version,
    time,
    REPLACE(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Soil_Type'), ']', ''), '[',''), "\"","") AS Soil_Type,
    REPLACE(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Wilderness_Area'), ']', ''), '[',''), "\"","") AS Wilderness_Area,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Aspect'), ']', ''), '[','') AS NUMERIC) AS Aspect,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Elevation'), ']', ''), '[','') AS NUMERIC) AS Elevation,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Hillshade_3pm'), ']', ''), '[','') AS NUMERIC) AS Hillshade_3pm,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Hillshade_9am'), ']', ''), '[','') AS NUMERIC) AS Hillshade_9am,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Hillshade_Noon'), ']', ''), '[','') AS NUMERIC) AS Hillshade_Noon,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Horizontal_Distance_To_Fire_Points'), ']', ''), '[','') AS NUMERIC) AS Horizontal_Distance_To_Fire_Points,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Horizontal_Distance_To_Hydrology'), ']', ''), '[','') AS NUMERIC) AS Horizontal_Distance_To_Hydrology,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Horizontal_Distance_To_Roadways'), ']', ''), '[','') AS NUMERIC) AS Horizontal_Distance_To_Roadways,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Slope'), ']', ''), '[','') AS NUMERIC) AS Slope,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(instance, '$.Vertical_Distance_To_Hydrology'), ']', ''), '[','') AS NUMERIC) AS Vertical_Distance_To_Hydrology,
    REPLACE(REPLACE(REPLACE(JSON_EXTRACT(prediction, '$.predicted_label'), ']', ''), '[',''), "\"","") AS predicted_label,
    CAST(REPLACE(REPLACE(JSON_EXTRACT(prediction, '$.confidence'), ']', ''), '[','') AS NUMERIC) AS confidence
  FROM step2
)

SELECT*
FROM step3

查询视图

创建视图后,您可以对其进行查询。如需查询视图,请运行第四部分查询视图中的代码。该代码使用笔记本中的 pandas.io.gbq.read_gbq 方法,如以下代码段所示:

query = '''
 SELECT * FROM
 `{}.{}`
 LIMIT {}
'''.format(BQ_DATASET_NAME, view_name, 3)

pd.io.gbq.read_gbq(
   query, project_id=PROJECT_ID).T

该代码会产生类似于以下内容的输出:

查询视图生成的输出。

视图查询结果会显示以下内容:

  • 每个特征都有自己的条目。
  • 引号会从分类特征中删除。
  • 预测的类标签显示在 predicted_label 条目中。
  • 预测的类标签的概率显示在 confidence 条目中。

使用 BigQuery 控制台

除了使用 pandas API 查询视图之外,还可以在 BigQuery 控制台中查询视图。

  1. 打开 BigQuery 控制台。

    转到 BigQuery 控制台

  2. 查询编辑器窗格中,输入如下查询:

    Select*
    FROM PROJECT_ID.prediction_logs.vw_covertype_classifier_logs_v1
    Limit 10
    

    PROJECT_ID 替换为您之前设置的 Cloud 项目的 ID。

    输出内容类似如下:

    包含 SQL 语句和输出的查询编辑器窗口。

(可选)模拟应用数据

如果您要处理自己的模型和数据,请跳过此部分并转到下一部分,其说明如何使用示例数据填充请求-响应日志表。

您可以使用示例数据生成有偏差的数据点,然后模拟对部署到 AI Platform Prediction 的模型版本的预测请求。模型会为请求实例生成预测。实例和预测均存储在 BigQuery 中。

可为预测请求生成示例(正常和有偏差)数据点,然后使用生成的数据点调用部署到 AI Platform Prediction 的 covertype 分类模型。您克隆的代码库包含一个笔记本(其中包含此任务的代码),或者您可以加载包含有偏差的数据的日志 CSV 文件。

如需从笔记本生成示例数据,请执行以下操作:

  1. 在笔记本中,转到文件浏览器,打开 mlops-on-gcp,然后导航到 skew-detection/workload_simulator 目录。
  2. 打开 covertype-data-generation.ipynb 笔记本。
  3. 设置下,为您的项目 ID、存储分区名称和区域设置值。
  4. 按顺序运行笔记本中的所有单元。

您可以更改要生成的数据的大小以及数据的偏差方式。数据中引入的默认偏差如下所示:

  • 数值特征偏差。对于 Elevation 特征,在 10% 的数据中,代码会将衡量单位从米转换为千米。
  • 数值特征分布偏差。对于 Aspect 特征,代码将值降低 25%。
  • 分类特征偏差。对于 Wilderness_Area 特征,代码会将数据的随机 1% 的值转换为名为 Others 的新类别。
  • 分类特征分布偏差。对于 Wilderness_Area 特征,代码会增加 NeotaCache 值的频率。代码通过将随机选择的 25% 的数据点从其原始值转换为值 NeotaCache 来完成此操作。

或者,您也可以将 workload_simulator/bq_prediction_logs.csv 数据文件加载到 BigQuery 请求-响应日志表中。CSV 文件包含请求-响应日志示例,其中包含 2000 个正常数据点和 1000 个偏差数据点。如需了解详情,请参阅将本地数据源中的数据加载到 BigQuery

直观呈现记录的应用数据

您可以使用直观呈现工具连接到 BigQuery 视图,并直观呈现已记录的应用数据。在后面显示的示例中,这些直观呈现是使用数据洞察创建的。

以下屏幕截图显示了示例信息中心,创建该信息中心是为了直观呈现来自本指南的预测请求-响应日志。

直观呈现请求-响应日志。

信息中心会显示以下信息:

  • 从 6 月 1 日到 6 月 6 日的每一天,预测服务接收的实例数相同 (500)。
  • 对于类分布情况,最后两天(6 月 5 日和 6 月 6 日)的预测类标签 3 的频率有所提高。
  • 对于 Wilderness Area 值分布,NeotaCache 值在最后两天有所增加。
  • 对于 Elevation 特征的描述性统计信息,最后两天的最小值会明显低于前面 4 天的值。标准差值与前面 4 天的值明显不同。

此外,如以下屏幕截图所示,最后 2 天 Aspect 特征的值分布在 300 到 350 之间的值频率显著降低。

切面分布线图。

清除数据

如果您打算继续学习本系列中的其他教程,请保留已创建的资源。否则,请删除包含这些资源的项目,或者保留项目并删除个别资源。

删除项目

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤