TensorFlow Data Validation

TensorFlow Extended 关键组件示例

注:您现在可以在 Jupyter 风格的笔记本中运行此示例而无需进行设置!只需点击“在 Google Colab 中运行”

此示例 Colab 笔记本将演示如何使用 TensorFlow Data Validation (TFDV) 调查和呈现数据集。包括查看描述性统计信息、推断架构、检查和修复异常,以及检查数据集中的漂移和偏差。了解数据集的特点非常重要,包括它在生产流水线中随时间变化的方式。查找数据中的异常,并比较训练、评估和应用数据集以确保它们保持一致也很重要。

我们将使用的数据来自芝加哥市发布的 Taxi Trips 数据集

注:本网站提供的应用所使用的数据来自原始源(www.cityofchicago.org,芝加哥市官方网站),但在使用时进行了修改。芝加哥市不对本网站提供的任何数据的内容、准确性、时效性或完整性承担任何责任。本网站提供的数据可能会随时更改。您了解并同意,使用本网站提供的数据须自担风险。

您可以在 Google BigQuery详细了解此数据集,并在 BigQuery 界面中探索完整的数据集。

要点:作为建模者和开发者,请思考如何使用这些数据以及模型预测的潜在好处和危害。此类模型可能会加剧社会偏见和差距。某个特征是与您要解决的问题相关,还是会引入偏见?有关更多信息,请阅读 ML 公平性

数据集中的各列为:

pickup_community_area fare trip_start_month
trip_start_hour trip_start_day trip_start_timestamp
pickup_latitude pickup_longitude dropoff_latitude
dropoff_longitude trip_miles pickup_census_tract
dropoff_census_tract payment_type company
trip_seconds dropoff_community_area tips

升级 Pip

为了避免在本地运行时升级系统中的 Pip,请检查以确保我们在 Colab 中运行。当然,可以单独升级本地系统。

try:
  import colab
  !pip install --upgrade pip
except:
  pass

安装 TensorFlow

注:在 Google Colab 中,由于软件包更新,第一次运行此代码单元时必须重新启动运行时 (Runtime > Restart runtime ...)。

pip install tensorflow==2.2.0

检查 Python 版本

import sys

# Confirm that we're using Python 3
assert sys.version_info.major is 3, 'Oops, not running Python 3. Use Runtime > Change runtime type'

安装 TFDV

这将拉取所有依赖项,并需要花点时间。请忽略有关不兼容的依赖项版本的警告或错误。

注:在 Google Colab 中,由于软件包更新,第一次运行此代码单元时必须重新启动运行时 (Runtime > Restart runtime ...)。

import tensorflow as tf

print('Installing TensorFlow Data Validation')
!pip install -q tensorflow_data_validation[visualization]

是否已重新启动运行时?

如果您使用的是 Google Colab,首次运行上面的代码单元时,必须重新启动运行时 (Runtime > Restart runtime ...)。这样做的原因是 Colab 加载软件包的方式。

加载文件

我们将从 Google Cloud Storage 下载数据集。

import os
import tempfile, urllib, zipfile

# Set up some globals for our file paths
BASE_DIR = tempfile.mkdtemp()
DATA_DIR = os.path.join(BASE_DIR, 'data')
OUTPUT_DIR = os.path.join(BASE_DIR, 'chicago_taxi_output')
TRAIN_DATA = os.path.join(DATA_DIR, 'train', 'data.csv')
EVAL_DATA = os.path.join(DATA_DIR, 'eval', 'data.csv')
SERVING_DATA = os.path.join(DATA_DIR, 'serving', 'data.csv')

# Download the zip file from GCP and unzip it
zip, headers = urllib.request.urlretrieve('https://storage.googleapis.com/artifacts.tfx-oss-public.appspot.com/datasets/chicago_data.zip')
zipfile.ZipFile(zip).extractall(BASE_DIR)
zipfile.ZipFile(zip).close()

print("Here's what we downloaded:")
!ls -R {os.path.join(BASE_DIR, 'data')}
Here's what we downloaded:
/tmp/tmpecs76cci/data:
eval  serving  train

/tmp/tmpecs76cci/data/eval:
data.csv

/tmp/tmpecs76cci/data/serving:
data.csv

/tmp/tmpecs76cci/data/train:
data.csv

检查版本

import tensorflow_data_validation as tfdv
print('TFDV version: {}'.format(tfdv.version.__version__))
TFDV version: 1.2.0

计算并可视化统计信息

首先,我们将使用 tfdv.generate_statistics_from_csv 计算训练数据的统计信息。(请忽略简短警告)

TFDV 可以计算描述性统计信息,有助于快速了解数据中存在的特征及其值分布的形状。

在内部,TFDV 使用 Apache Beam 的数据并行处理框架来扩展对大型数据集的统计信息计算。对于希望与 TFDV 进行更深入集成的应用(例如,在数据生成流水线的末端附加统计信息生成),该 API 还公开了 Beam PTransform 用于统计信息生成。

train_stats = tfdv.generate_statistics_from_csv(data_location=TRAIN_DATA)
WARNING:apache_beam.runners.interactive.interactive_environment:Dependencies required for Interactive Beam PCollection visualization are not available, please use: `pip install apache-beam[interactive]` to install necessary dependencies to enable all data visualization features.
WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.
WARNING:apache_beam.io.tfrecordio:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_data_validation/utils/stats_util.py:247: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_data_validation/utils/stats_util.py:247: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

现在,我们来使用 tfdv.visualize_statistics,它使用 Facets 为我们的训练数据创建简洁的可视化效果:

  • 请注意,数字特征和分类特征会分别可视化,并且显示的图表会展示每个特征的分布。
  • 请注意,缺少值或值为零的特征的百分比将显示为红色(作为视觉指示,表明这些特征中的样本可能存在问题)。百分比是该特征缺少值或值为零的样本的百分比。
  • 请注意,没有具有 pickup_census_tract 值的样本。这是一个降维的机会!
  • 尝试点击图表上方的“expand”以更改显示
  • 尝试将鼠标悬停在图表中的条形图上以显示桶范围和计数
  • 尝试在对数尺度和线性尺度之间切换,并注意对数尺度如何显示有关 payment_type 分类特征的更多详细信息
  • 尝试从“Chart to show”菜单中选择“quantiles”,然后将鼠标悬停在标记上以显示分位数百分比
tfdv.visualize_statistics(train_stats)

推断架构

现在,我们使用 tfdv.infer_schema 为我们的数据创建架构。架构定义了与 ML 相关的数据约束。示例约束包括每个特征的数据类型(是数字特征还是分类特征),或其在数据中的出现频率。对于分类特征,架构还定义了域(可接受值的列表)。由于编写架构可能是一项繁琐的任务,特别是对于具有许多特征的数据集,TFDV 提供了一种根据描述性统计信息生成架构初始版本的方式。

获得正确的架构非常重要,因为我们的其他生产流水线将依赖于 TFDV 生成架构的正确性。架构还提供数据的文档,这在不同开发者处理同一数据时非常有用。我们使用 tfdv.display_schema 来显示推断的架构,以便对其进行检查。

schema = tfdv.infer_schema(statistics=train_stats)
tfdv.display_schema(schema=schema)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_data_validation/utils/display_util.py:180: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.
  pd.set_option('display.max_colwidth', -1)

检查评估数据有无错误

到目前为止,我们只查看了训练数据。评估数据与训练数据保持一致非常重要,包括使用相同的架构。同样重要的是,评估数据应包含与训练数据的数值特征值范围大致相同的样本,从而使评估期间我们对损失表面的覆盖范围与训练期间大致相同。对于分类特征也是如此。否则,我们可能会因为没有评估部分损失表面,而在评估期间遇到未发现的训练问题。

  • 请注意,现在每个特征都包括训练数据集和评估数据集的统计信息。
  • 请注意,图表现在同时叠加了训练数据集和评估数据集,便于进行比较。
  • 请注意,现在图表包括一个百分比视图,可以将其与对数尺度或默认的线性尺度结合使用。
  • 请注意,训练数据集和评估数据集的 trip_miles 平均值和中位数不同。这会引起问题吗?
  • 哇哦,训练数据集和评估数据集的 tips 也迥然不同。这会引起问题吗?
  • 点击“Numeric Features”图表上的“expand”,选择对数尺度。查看 trip_seconds 特征,并注意最大值之间的差异。评估会漏掉部分损失表面吗?
# Compute stats for evaluation data
eval_stats = tfdv.generate_statistics_from_csv(data_location=EVAL_DATA)

# Compare evaluation data with training data
tfdv.visualize_statistics(lhs_statistics=eval_stats, rhs_statistics=train_stats,
                          lhs_name='EVAL_DATASET', rhs_name='TRAIN_DATASET')
WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.

检查评估异常

我们的评估数据集是否与训练数据集中的架构匹配?这对于分类特征尤其重要,因为我们要确定可接受值的范围。

要点:如果我们尝试使用不在训练数据集中的具有分类特征值的数据进行评估,会发生什么呢?如果使用超出训练数据集范围的数字特征又会如何?

# Check eval data for errors by validating the eval data stats using the previously inferred schema.
anomalies = tfdv.validate_statistics(statistics=eval_stats, schema=schema)
tfdv.display_anomalies(anomalies)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_data_validation/utils/display_util.py:217: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.
  pd.set_option('display.max_colwidth', -1)

修复架构中的评估异常

糟糕!看起来我们的评估数据中有一些 company 的新值,而训练数据中却没有。payment_type 也有一个新值。这些应被视为异常,但是决定如何处理它们取决于我们对数据的领域知识。如果异常确实表示数据错误,则应修复基础数据。否则,我们只需更新架构以在评估数据集中包含这些值。

要点:如果不修复这些问题,我们的评估结果会受到怎样的影响?

除非更改评估数据集,否则我们无法修复所有问题,但可以修复架构中我们愿意接受的问题。这包括放宽我们对特定特征异常的判断标准,以及更新我们的架构以包括分类特征的缺失值。TFDV 使我们能够发现需要修复的内容。

现在我们来进行修复,然后再检查一次。

# Relax the minimum fraction of values that must come from the domain for feature company.
company = tfdv.get_feature(schema, 'company')
company.distribution_constraints.min_domain_mass = 0.9

# Add new value to the domain of feature payment_type.
payment_type_domain = tfdv.get_domain(schema, 'payment_type')
payment_type_domain.value.append('Prcard')

# Validate eval stats after updating the schema 
updated_anomalies = tfdv.validate_statistics(eval_stats, schema)
tfdv.display_anomalies(updated_anomalies)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_data_validation/utils/display_util.py:217: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.
  pd.set_option('display.max_colwidth', -1)

看!经过验证,训练数据和评估数据现在是一致的!感谢 TFDV ;)

架构环境

在此示例中,我们还拆分出了一个“应用”数据集,因此我们也应该对它进行检查。默认情况下,流水线中的所有数据集都应使用相同的架构,但通常会有例外。例如,在监督学习中,我们需要在数据集中包括标签,但当我们应用模型进行推断时,则不包括标签。在某些情况下,有必要引入轻微的架构变化。

可以使用环境来表示此类需求。特别是,可以使用 default_environmentin_environmentnot_in_environment 将架构中的特征与一组环境相关联。

例如,tips 特征被作为训练标签包含在此数据集中,但却没有包含在应用数据中。如果未指定环境,它将显示为异常。

serving_stats = tfdv.generate_statistics_from_csv(SERVING_DATA)
serving_anomalies = tfdv.validate_statistics(serving_stats, schema)

tfdv.display_anomalies(serving_anomalies)
WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.

我们将在下文处理 tips 特征。在 trip_seconds 中还有一个 INT 值,架构在此处需要一个 FLOAT。通过让我们意识到这种差异,TFDV 有助于发现用于训练和应用的数据生成方式的不一致。在模型性能受到影响(有时是灾难性的)之前,很容易忽视此类问题。问题可能重要也可能不重要,但无论如何都应做进一步调查。

在本例中,我们可以安全地将 INT 值转换为 FLOAT,以便让 TFDV 使用我们的架构来推断类型。现在开始吧。

options = tfdv.StatsOptions(schema=schema, infer_type_from_schema=True)
serving_stats = tfdv.generate_statistics_from_csv(SERVING_DATA, stats_options=options)
serving_anomalies = tfdv.validate_statistics(serving_stats, schema)

tfdv.display_anomalies(serving_anomalies)
WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_data_validation/utils/display_util.py:217: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.
  pd.set_option('display.max_colwidth', -1)

现在,只有 tips 特征(这是标签)显示为异常(“Column dropped”)。当然,我们不希望在应用数据中包含标签,因此我们告诉 TFDV 忽略标签。

# All features are by default in both TRAINING and SERVING environments.
schema.default_environment.append('TRAINING')
schema.default_environment.append('SERVING')

# Specify that 'tips' feature is not in SERVING environment.
tfdv.get_feature(schema, 'tips').not_in_environment.append('SERVING')

serving_anomalies_with_env = tfdv.validate_statistics(
    serving_stats, schema, environment='SERVING')

tfdv.display_anomalies(serving_anomalies_with_env)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_data_validation/utils/display_util.py:217: FutureWarning: Passing a negative integer is deprecated in version 1.0 and will not be supported in future version. Instead, use None to not limit the column width.
  pd.set_option('display.max_colwidth', -1)

检查漂移和偏差

除了检查数据集是否符合在架构中设置的期望之外,TFDV 还提供了检测漂移和偏差的功能。TFDV 执行此检查的方式为:根据架构中指定的漂移/偏差比较器来比较不同数据集的统计信息。

漂移

支持对分类特征以及在数据的连续跨度之间(即跨度 N 和跨度 N+1 之间)进行漂移检测(例如训练数据的不同天数之间)。我们用切比雪夫距离来表示漂移,您可以设置阈值距离,以便在漂移高于可接受范围时收到警告。设置正确的距离通常是一个迭代过程,需要领域知识和实验。

偏差

TFDV 可以检测数据中三种不同类型的偏差:架构偏差、特征偏差和分布偏差。

架构偏差

当训练数据和应用数据不符合同一个架构时,会发生架构偏差。训练数据和应用数据都应遵循同一个架构。两者之间的任何预期偏差(例如,仅训练数据中存在但应用数据中不存在的标签特征)都应通过架构中的环境字段指定。

特征偏差

当模型训练所采用的特征值与它在应用时看到的特征值不同时,会发生特征偏差。例如,这可能在以下情况下发生:

  • 提供某些特征值的数据源在训练时和应用时之间被修改。
  • 用于在训练和应用之间生成特征的逻辑不同。例如,如果仅在两个代码路径之一中应用某些转换。

分布偏差

当训练数据集的分布与应用数据集的分布明显不同时,会发生分布偏差。分布偏差的主要原因之一是使用不同的代码或不同的数据源来生成训练数据集。另一个原因是错误的采样机制选择了应用数据中无代表性的子样本来进行训练。

# Add skew comparator for 'payment_type' feature.
payment_type = tfdv.get_feature(schema, 'payment_type')
payment_type.skew_comparator.infinity_norm.threshold = 0.01

# Add drift comparator for 'company' feature.
company=tfdv.get_feature(schema, 'company')
company.drift_comparator.infinity_norm.threshold = 0.001

skew_anomalies = tfdv.validate_statistics(train_stats, schema,
                                          previous_statistics=eval_stats,
                                          serving_statistics=serving_stats)

tfdv.display_anomalies(skew_anomalies)

在此示例中,我们确实看到了一些漂移,但它远低于我们设置的阈值。

冻结架构

现在,已经对架构进行了检查和整理,我们将其存储在文件中以反映其“冻结”状态。

from tensorflow.python.lib.io import file_io
from google.protobuf import text_format

file_io.recursive_create_dir(OUTPUT_DIR)
schema_file = os.path.join(OUTPUT_DIR, 'schema.pbtxt')
tfdv.write_schema_text(schema, schema_file)

!cat {schema_file}
feature {
  name: "payment_type"
  type: BYTES
  domain: "payment_type"
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  skew_comparator {
    infinity_norm {
      threshold: 0.01
    }
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "company"
  value_count {
    min: 1
    max: 1
  }
  type: BYTES
  domain: "company"
  presence {
    min_count: 1
  }
  distribution_constraints {
    min_domain_mass: 0.9
  }
  drift_comparator {
    infinity_norm {
      threshold: 0.001
    }
  }
}
feature {
  name: "pickup_community_area"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "fare"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_month"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_hour"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_day"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "trip_start_timestamp"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "pickup_latitude"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "pickup_longitude"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "dropoff_latitude"
  value_count {
    min: 1
    max: 1
  }
  type: FLOAT
  presence {
    min_count: 1
  }
}
feature {
  name: "dropoff_longitude"
  value_count {
    min: 1
    max: 1
  }
  type: FLOAT
  presence {
    min_count: 1
  }
}
feature {
  name: "trip_miles"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "pickup_census_tract"
  type: BYTES
  presence {
    min_count: 0
  }
}
feature {
  name: "dropoff_census_tract"
  value_count {
    min: 1
    max: 1
  }
  type: INT
  presence {
    min_count: 1
  }
}
feature {
  name: "trip_seconds"
  type: INT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  shape {
    dim {
      size: 1
    }
  }
}
feature {
  name: "dropoff_community_area"
  value_count {
    min: 1
    max: 1
  }
  type: INT
  presence {
    min_count: 1
  }
}
feature {
  name: "tips"
  type: FLOAT
  presence {
    min_fraction: 1.0
    min_count: 1
  }
  not_in_environment: "SERVING"
  shape {
    dim {
      size: 1
    }
  }
}
string_domain {
  name: "payment_type"
  value: "Cash"
  value: "Credit Card"
  value: "Dispute"
  value: "No Charge"
  value: "Pcard"
  value: "Unknown"
  value: "Prcard"
}
string_domain {
  name: "company"
  value: "0118 - 42111 Godfrey S.Awir"
  value: "0694 - 59280 Chinesco Trans Inc"
  value: "1085 - 72312 N and W Cab Co"
  value: "2733 - 74600 Benny Jona"
  value: "2809 - 95474 C & D Cab Co Inc."
  value: "3011 - 66308 JBL Cab Inc."
  value: "3152 - 97284 Crystal Abernathy"
  value: "3201 - C&D Cab Co Inc"
  value: "3201 - CID Cab Co Inc"
  value: "3253 - 91138 Gaither Cab Co."
  value: "3385 - 23210 Eman Cab"
  value: "3623 - 72222 Arrington Enterprises"
  value: "3897 - Ilie Malec"
  value: "4053 - Adwar H. Nikola"
  value: "4197 - 41842 Royal Star"
  value: "4615 - 83503 Tyrone Henderson"
  value: "4615 - Tyrone Henderson"
  value: "4623 - Jay Kim"
  value: "5006 - 39261 Salifu Bawa"
  value: "5006 - Salifu Bawa"
  value: "5074 - 54002 Ahzmi Inc"
  value: "5074 - Ahzmi Inc"
  value: "5129 - 87128"
  value: "5129 - 98755 Mengisti Taxi"
  value: "5129 - Mengisti Taxi"
  value: "5724 - KYVI Cab Inc"
  value: "585 - Valley Cab Co"
  value: "5864 - 73614 Thomas Owusu"
  value: "5864 - Thomas Owusu"
  value: "5874 - 73628 Sergey Cab Corp."
  value: "5997 - 65283 AW Services Inc."
  value: "5997 - AW Services Inc."
  value: "6488 - 83287 Zuha Taxi"
  value: "6743 - Luhak Corp"
  value: "Blue Ribbon Taxi Association Inc."
  value: "C & D Cab Co Inc"
  value: "Chicago Elite Cab Corp."
  value: "Chicago Elite Cab Corp. (Chicago Carriag"
  value: "Chicago Medallion Leasing INC"
  value: "Chicago Medallion Management"
  value: "Choice Taxi Association"
  value: "Dispatch Taxi Affiliation"
  value: "KOAM Taxi Association"
  value: "Northwest Management LLC"
  value: "Taxi Affiliation Services"
  value: "Top Cab Affiliation"
}
default_environment: "TRAINING"
default_environment: "SERVING"

何时使用 TFDV

很容易认为 TFDV 只适用于训练流水线的开始(如我们此处所做),但实际上它有很多用途。下面是一些其他用途:

  • 验证用于推断的新数据,以确保不会突然开始接收不良特征
  • 验证用于推断的新数据,以确保我们的模型已在决策表面的该部分进行了训练
  • 在转换数据并完成特征工程(很可能使用 TensorFlow Transform)后验证数据,以确保我们没有做错什么