def init_components( data_dir, module_file, training_steps=TRAIN_STEPS, eval_steps=EVAL_STEPS, serving_model_dir=None, ai_platform_training_args=None, ai_platform_serving_args=None, ): if serving_model_dir and ai_platform_serving_args: raise NotImplementedError( "Can't set ai_platform_serving_args and serving_model_dir at " "the same time. Choose one deployment option.") output = example_gen_pb2.Output(split_config=example_gen_pb2.SplitConfig( splits=[ example_gen_pb2.SplitConfig.Split(name="train", hash_buckets=99), example_gen_pb2.SplitConfig.Split(name="eval", hash_buckets=1), ])) examples = external_input(data_dir) example_gen = CsvExampleGen(input=examples, output_config=output) statistics_gen = StatisticsGen(examples=example_gen.outputs["examples"]) schema_gen = SchemaGen( statistics=statistics_gen.outputs["statistics"], infer_feature_shape=False, ) example_validator = ExampleValidator( statistics=statistics_gen.outputs["statistics"], schema=schema_gen.outputs["schema"], ) transform = Transform( examples=example_gen.outputs["examples"], schema=schema_gen.outputs["schema"], module_file=module_file, ) training_kwargs = { "module_file": module_file, "examples": transform.outputs["transformed_examples"], "schema": schema_gen.outputs["schema"], "transform_graph": transform.outputs["transform_graph"], "train_args": trainer_pb2.TrainArgs(num_steps=training_steps), "eval_args": trainer_pb2.EvalArgs(num_steps=eval_steps), } if ai_platform_training_args: from tfx.extensions.google_cloud_ai_platform.trainer import ( executor as aip_trainer_executor, ) training_kwargs.update({ "custom_executor_spec": executor_spec.ExecutorClassSpec( aip_trainer_executor.GenericExecutor), "custom_config": { aip_trainer_executor.TRAINING_ARGS_KEY: ai_platform_training_args # noqa }, }) else: training_kwargs.update({ "custom_executor_spec": executor_spec.ExecutorClassSpec(GenericExecutor) }) trainer = Trainer(**training_kwargs) model_resolver = ResolverNode( instance_name="latest_blessed_model_resolver", resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing), ) eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key="consumer_disputed")], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=["product"]), ], metrics_specs=[ tfma.MetricsSpec( metrics=[ tfma.MetricConfig(class_name="BinaryAccuracy"), tfma.MetricConfig(class_name="ExampleCount"), tfma.MetricConfig(class_name="AUC"), ], thresholds={ "AUC": tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={"value": 0.65}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={"value": 0.01}, ), ) }, ) ], ) evaluator = Evaluator( examples=example_gen.outputs["examples"], model=trainer.outputs["model"], baseline_model=model_resolver.outputs["model"], eval_config=eval_config, ) pusher_kwargs = { "model": trainer.outputs["model"], "model_blessing": evaluator.outputs["blessing"], } if ai_platform_serving_args: from tfx.extensions.google_cloud_ai_platform.pusher import ( executor as aip_pusher_executor, ) pusher_kwargs.update({ "custom_executor_spec": executor_spec.ExecutorClassSpec(aip_pusher_executor.Executor), "custom_config": { aip_pusher_executor.SERVING_ARGS_KEY: ai_platform_serving_args # noqa }, }) elif serving_model_dir: pusher_kwargs.update({ "push_destination": pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir)) }) else: raise NotImplementedError( "Provide ai_platform_serving_args or serving_model_dir.") pusher = Pusher(**pusher_kwargs) components = [ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher, ] return components
def create_pipeline( pipeline_name: Text, pipeline_root: Text, query: Text, preprocessing_fn: Text, run_fn: Text, train_args: trainer_pb2.TrainArgs, eval_args: trainer_pb2.EvalArgs, model_serve_dir: Text, metadata_connection_config: Optional[metadata_store_pb2.ConnectionConfig] = None, beam_pipeline_args: Optional[Dict[Text, Any]] = None, ai_platform_training_args: Optional[Dict[Text, Text]] = None, ai_platform_serving_args: Optional[Dict[Text, Any]] = None, enable_cache: Optional[bool] = False, system_config: Optional[Dict[Text, Any]] = None, model_config: Optional[Dict[Text, Any]] = None, ) -> pipeline.Pipeline: """Implements the pipeline with TFX.""" components = [] # %% # ExampleGen: Load the graph data from bigquery query_str = load_query_string( query, field_dict={ "GOOGLE_CLOUD_PROJECT": system_config["GOOGLE_CLOUD_PROJECT"], "DEBUG_SETTINGS": model_config["query_debug_settings"], }, ) output_config = example_gen_pb2.Output( split_config=example_gen_pb2.SplitConfig( splits=[ # Generate no splitting, as we need to load everything example_gen_pb2.SplitConfig.Split(name="train", hash_buckets=1), ], ) ) example_gen = BigQueryExampleGen(query=query_str, output_config=output_config) components.append(example_gen) # %% # StatisticsGen: # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs["examples"]) components.append(statistics_gen) # %% # SchemaGen: # Generates schema based on statistics files. schema_gen = SchemaGen( statistics=statistics_gen.outputs["statistics"], infer_feature_shape=True ) components.append(schema_gen) # %% # ExampleValidator: # Performs anomaly detection based on statistics and data schema. # example_validator = ExampleValidator( # pylint: disable=unused-variable # statistics=statistics_gen.outputs["statistics"], # schema=schema_gen.outputs["schema"], # ) # components.append(example_validator) # %% # Transform: # Performs transformations and feature engineering in training and serving. transform = Transform( examples=example_gen.outputs["examples"], schema=schema_gen.outputs["schema"], preprocessing_fn=preprocessing_fn, ) components.append(transform) # %% # Trainer # Uses user-provided Python function that implements a model using TF-Learn. trainer_args = { "run_fn": run_fn, "transformed_examples": transform.outputs["transformed_examples"], "schema": schema_gen.outputs["schema"], "transform_graph": transform.outputs["transform_graph"], "train_args": train_args, "eval_args": eval_args, "custom_config": {"model_config": model_config, "system_config": system_config}, "custom_executor_spec": executor_spec.ExecutorClassSpec( trainer_executor.GenericExecutor ), } if ai_platform_training_args is not None: trainer_args["custom_executor_spec"] = executor_spec.ExecutorClassSpec( ai_platform_trainer_executor.GenericExecutor ) trainer_args["custom_config"][ ai_platform_trainer_executor.TRAINING_ARGS_KEY ] = ai_platform_training_args # Lowercase and replace illegal characters in labels. # This is the job ID that will be shown in the platform # See https://cloud.google.com/compute/docs/naming-resources. trainer_args["custom_config"][ ai_platform_trainer_executor.JOB_ID_KEY ] = "tfx_{}_{}".format( re.sub(r"[^a-z0-9\_]", "_", pipeline_name.lower()), datetime.datetime.now().strftime("%Y%m%d%H%M%S"), )[ -63: ] logging.info("trainer arguments") logging.info(trainer_args) trainer = Trainer(**trainer_args) components.append(trainer) # %% # ResolveNode: # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name="latest_blessed_model_resolver", resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing), ) # components.append(model_resolver) # %% # Evaluator: # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key="big_tipper")], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec( metrics=[ tfma.MetricConfig( class_name="BinaryAccuracy", threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={"value": 0.1} ), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={"value": -1e-10}, ), ), ) ] ) ], ) evaluator = Evaluator( examples=example_gen.outputs["examples"], model=trainer.outputs["model"], baseline_model=model_resolver.outputs["model"], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config, ) # components.append(evaluator) # %% # Pusher: # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher_args = { "model": trainer.outputs["model"], "model_blessing": evaluator.outputs["blessing"], "push_destination": pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=model_serve_dir, ) ), } if ai_platform_serving_args is not None: pusher_args.update( { "custom_executor_spec": executor_spec.ExecutorClassSpec( ai_platform_pusher_executor.Executor ), "custom_config": { ai_platform_pusher_executor.SERVING_ARGS_KEY: ai_platform_serving_args }, } ) pusher = Pusher(**pusher_args) # pylint: disable=unused-variable # components.append(pusher) # %% return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=enable_cache, metadata_connection_config=metadata_connection_config, beam_pipeline_args=( # parse beam pipeline into a list of commands [f"--{key}={val}" for key, val in beam_pipeline_args.items()] if beam_pipeline_args is not None else None ), )
def create_pipeline( pipeline_name: Text, pipeline_root: Text, module_file: Text, ai_platform_training_args: Dict[Text, Text], ai_platform_serving_args: Dict[Text, Text], beam_pipeline_args: Optional[List[Text]] = None) -> pipeline.Pipeline: """Implements the chicago taxi pipeline with TFX and Kubeflow Pipelines. Args: pipeline_name: name of the TFX pipeline being created. pipeline_root: root directory of the pipeline. Should be a valid GCS path. module_file: uri of the module files used in Trainer and Transform components. ai_platform_training_args: Args of CAIP training job. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.jobs#Job for detailed description. ai_platform_serving_args: Args of CAIP model deployment. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.models for detailed description. beam_pipeline_args: Optional list of beam pipeline options. Please refer to https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options. When this argument is not provided, the default is to use GCP DataflowRunner with 50GB disk size as specified in this function. If an empty list is passed in, default specified by Beam will be used, which can be found at https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options Returns: A TFX pipeline object. """ # The rate at which to sample rows from the Taxi dataset using BigQuery. # The full taxi dataset is > 200M record. In the interest of resource # savings and time, we've set the default for this example to be much smaller. # Feel free to crank it up and process the full dataset! # By default it generates a 0.1% random sample. query_sample_rate = data_types.RuntimeParameter(name='query_sample_rate', ptype=float, default=0.001) # This is the upper bound of FARM_FINGERPRINT in Bigquery (ie the max value of # signed int64). max_int64 = '0x7FFFFFFFFFFFFFFF' # The query that extracts the examples from BigQuery. The Chicago Taxi dataset # used for this example is a public dataset available on Google AI Platform. # https://console.cloud.google.com/marketplace/details/city-of-chicago-public-data/chicago-taxi-trips query = """ SELECT pickup_community_area, fare, EXTRACT(MONTH FROM trip_start_timestamp) AS trip_start_month, EXTRACT(HOUR FROM trip_start_timestamp) AS trip_start_hour, EXTRACT(DAYOFWEEK FROM trip_start_timestamp) AS trip_start_day, UNIX_SECONDS(trip_start_timestamp) AS 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 FROM `bigquery-public-data.chicago_taxi_trips.taxi_trips` WHERE (ABS(FARM_FINGERPRINT(unique_key)) / {max_int64}) < {query_sample_rate}""".format( max_int64=max_int64, query_sample_rate=str(query_sample_rate)) # Beam args to run data processing on DataflowRunner. # # TODO(b/151114974): Remove `disk_size_gb` flag after default is increased. # TODO(b/151116587): Remove `shuffle_mode` flag after default is changed. # TODO(b/156874687): Remove `machine_type` after IP addresses are no longer a # scaling bottleneck. if beam_pipeline_args is None: beam_pipeline_args = [ '--runner=DataflowRunner', '--project=' + _project_id, '--temp_location=' + os.path.join(_output_bucket, 'tmp'), '--region=' + _gcp_region, # Temporary overrides of defaults. '--disk_size_gb=50', '--experiments=shuffle_mode=auto', '--machine_type=n1-standard-8', ] # Number of epochs in training. train_steps = data_types.RuntimeParameter( name='train_steps', default=10000, ptype=int, ) # Number of epochs in evaluation. eval_steps = data_types.RuntimeParameter( name='eval_steps', default=5000, ptype=int, ) # Brings data into the pipeline or otherwise joins/converts training data. example_gen = big_query_example_gen_component.BigQueryExampleGen( query=query) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform(examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Update ai_platform_training_args if distributed training was enabled. # Number of worker machines used in distributed training. worker_count = data_types.RuntimeParameter( name='worker_count', default=2, ptype=int, ) # Type of worker machines used in distributed training. worker_type = data_types.RuntimeParameter( name='worker_type', default='standard', ptype=str, ) local_training_args = copy.deepcopy(ai_platform_training_args) if FLAGS.distributed_training: local_training_args.update({ # You can specify the machine types, the number of replicas for workers # and parameter servers. # https://cloud.google.com/ml-engine/reference/rest/v1/projects.jobs#ScaleTier 'scaleTier': 'CUSTOM', 'masterType': 'large_model', 'workerType': worker_type, 'parameterServerType': 'standard', 'workerCount': worker_count, 'parameterServerCount': 1 }) # Uses user-provided Python function that implements a model using TF-Learn # to train a model on Google Cloud AI Platform. trainer = Trainer( custom_executor_spec=executor_spec.ExecutorClassSpec( ai_platform_trainer_executor.Executor), module_file=module_file, transformed_examples=transform.outputs['transformed_examples'], schema=schema_gen.outputs['schema'], transform_graph=transform.outputs['transform_graph'], train_args={'num_steps': train_steps}, eval_args={'num_steps': eval_steps}, custom_config={ ai_platform_trainer_executor.TRAINING_ARGS_KEY: local_training_args }) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['trip_start_hour']) ], metrics_specs=[ tfma.MetricsSpec( thresholds={ 'accuracy': tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.6}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to Google Cloud AI Platform if check passed. # TODO(b/162451308): Add pusher back to components list once AIP Prediction # Service supports TF>=2.3. _ = Pusher(custom_executor_spec=executor_spec.ExecutorClassSpec( ai_platform_pusher_executor.Executor), model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], custom_config={ ai_platform_pusher_executor.SERVING_ARGS_KEY: ai_platform_serving_args }) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator ], beam_pipeline_args=beam_pipeline_args, )
class ExecutorTest(tf.test.TestCase, parameterized.TestCase): @parameterized.named_parameters(('evaluation_w_eval_config', { 'eval_config': json_format.MessageToJson( tfma.EvalConfig(slicing_specs=[ tfma.SlicingSpec(feature_keys=['trip_start_hour']), tfma.SlicingSpec( feature_keys=['trip_start_day', 'trip_miles']), ]), preserving_proto_field_name=True) })) def testEvalution(self, exec_properties): source_data_dir = os.path.join( os.path.dirname(os.path.dirname(__file__)), 'testdata') output_data_dir = os.path.join( os.environ.get('TEST_UNDECLARED_OUTPUTS_DIR', self.get_temp_dir()), self._testMethodName) # Create input dict. examples = standard_artifacts.Examples() examples.uri = os.path.join(source_data_dir, 'csv_example_gen') examples.split_names = artifact_utils.encode_split_names(['train', 'eval']) model = standard_artifacts.Model() baseline_model = standard_artifacts.Model() model.uri = os.path.join(source_data_dir, 'trainer/current') baseline_model.uri = os.path.join(source_data_dir, 'trainer/previous/') schema = standard_artifacts.Schema() schema.uri = os.path.join(source_data_dir, 'schema_gen') input_dict = { constants.EXAMPLES_KEY: [examples], constants.MODEL_KEY: [model], constants.SCHEMA_KEY: [schema], } # Create output dict. eval_output = standard_artifacts.ModelEvaluation() eval_output.uri = os.path.join(output_data_dir, 'eval_output') blessing_output = standard_artifacts.ModelBlessing() blessing_output.uri = os.path.join(output_data_dir, 'blessing_output') output_dict = { constants.EVALUATION_KEY: [eval_output], constants.BLESSING_KEY: [blessing_output], } # Test multiple splits. exec_properties[constants.EXAMPLE_SPLITS_KEY] = json_utils.dumps( ['train', 'eval']) # Run executor. evaluator = executor.Executor() evaluator.Do(input_dict, output_dict, exec_properties) # Check evaluator outputs. self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'eval_config.json'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'metrics'))) self.assertTrue(tf.io.gfile.exists(os.path.join(eval_output.uri, 'plots'))) self.assertFalse( tf.io.gfile.exists(os.path.join(blessing_output.uri, 'BLESSED'))) @parameterized.named_parameters(('legacy_feature_slicing', { 'feature_slicing_spec': json_format.MessageToJson( evaluator_pb2.FeatureSlicingSpec(specs=[ evaluator_pb2.SingleSlicingSpec( column_for_slicing=['trip_start_hour']), evaluator_pb2.SingleSlicingSpec( column_for_slicing=['trip_start_day', 'trip_miles']), ]), preserving_proto_field_name=True), })) def testDoLegacySingleEvalSavedModelWFairness(self, exec_properties): source_data_dir = os.path.join( os.path.dirname(os.path.dirname(__file__)), 'testdata') output_data_dir = os.path.join( os.environ.get('TEST_UNDECLARED_OUTPUTS_DIR', self.get_temp_dir()), self._testMethodName) # Create input dict. examples = standard_artifacts.Examples() examples.uri = os.path.join(source_data_dir, 'csv_example_gen') examples.split_names = artifact_utils.encode_split_names(['train', 'eval']) model = standard_artifacts.Model() model.uri = os.path.join(source_data_dir, 'trainer/current') input_dict = { constants.EXAMPLES_KEY: [examples], constants.MODEL_KEY: [model], } # Create output dict. eval_output = standard_artifacts.ModelEvaluation() eval_output.uri = os.path.join(output_data_dir, 'eval_output') blessing_output = standard_artifacts.ModelBlessing() blessing_output.uri = os.path.join(output_data_dir, 'blessing_output') output_dict = { constants.EVALUATION_KEY: [eval_output], constants.BLESSING_KEY: [blessing_output], } try: # Need to import the following module so that the fairness indicator # post-export metric is registered. This may raise an ImportError if the # currently-installed version of TFMA does not support fairness # indicators. import tensorflow_model_analysis.addons.fairness.post_export_metrics.fairness_indicators # pylint: disable=g-import-not-at-top, unused-variable exec_properties['fairness_indicator_thresholds'] = [ 0.1, 0.3, 0.5, 0.7, 0.9 ] except ImportError: logging.warning( 'Not testing fairness indicators because a compatible TFMA version ' 'is not installed.') # List needs to be serialized before being passed into Do function. exec_properties[constants.EXAMPLE_SPLITS_KEY] = json_utils.dumps(None) # Run executor. evaluator = executor.Executor() evaluator.Do(input_dict, output_dict, exec_properties) # Check evaluator outputs. self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'eval_config.json'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'metrics'))) self.assertTrue(tf.io.gfile.exists(os.path.join(eval_output.uri, 'plots'))) self.assertFalse( tf.io.gfile.exists(os.path.join(blessing_output.uri, 'BLESSED'))) @parameterized.named_parameters( ( 'eval_config_w_validation', { 'eval_config': json_format.MessageToJson( tfma.EvalConfig( model_specs=[ tfma.ModelSpec(label_key='tips'), ], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.config.MetricConfig( class_name='ExampleCount', # Count > 0, OK. threshold=tfma.config.MetricThreshold( value_threshold=tfma .GenericValueThreshold( lower_bound={'value': 0}))), ]), ], slicing_specs=[tfma.SlicingSpec()]), preserving_proto_field_name=True) }, True, True), ( 'eval_config_w_validation_fail', { 'eval_config': json_format.MessageToJson( tfma.EvalConfig( model_specs=[ tfma.ModelSpec( name='baseline1', label_key='tips', is_baseline=True), tfma.ModelSpec( name='candidate1', label_key='tips'), ], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.config.MetricConfig( class_name='ExampleCount', # Count < -1, NOT OK. threshold=tfma.config.MetricThreshold( value_threshold=tfma .GenericValueThreshold( upper_bound={'value': -1}))), ]), ], slicing_specs=[tfma.SlicingSpec()]), preserving_proto_field_name=True) }, False, True), ( 'no_baseline_model_ignore_change_threshold_validation_pass', { 'eval_config': json_format.MessageToJson( tfma.EvalConfig( model_specs=[ tfma.ModelSpec( name='baseline', label_key='tips', is_baseline=True), tfma.ModelSpec( name='candidate', label_key='tips'), ], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.config.MetricConfig( class_name='ExampleCount', # Count > 0, OK. threshold=tfma.config.MetricThreshold( value_threshold=tfma .GenericValueThreshold( lower_bound={'value': 0}))), tfma.config.MetricConfig( class_name='Accuracy', # Should be ignored due to no baseline. threshold=tfma.config.MetricThreshold( change_threshold=tfma .GenericChangeThreshold( relative={'value': 0}, direction=tfma.MetricDirection .LOWER_IS_BETTER))), ]), ], slicing_specs=[tfma.SlicingSpec()]), preserving_proto_field_name=True) }, True, False)) def testDoValidation(self, exec_properties, blessed, has_baseline): source_data_dir = os.path.join( os.path.dirname(os.path.dirname(__file__)), 'testdata') output_data_dir = os.path.join( os.environ.get('TEST_UNDECLARED_OUTPUTS_DIR', self.get_temp_dir()), self._testMethodName) # Create input dict. examples = standard_artifacts.Examples() examples.uri = os.path.join(source_data_dir, 'csv_example_gen') examples.split_names = artifact_utils.encode_split_names(['train', 'eval']) model = standard_artifacts.Model() baseline_model = standard_artifacts.Model() model.uri = os.path.join(source_data_dir, 'trainer/current') baseline_model.uri = os.path.join(source_data_dir, 'trainer/previous/') blessing_output = standard_artifacts.ModelBlessing() blessing_output.uri = os.path.join(output_data_dir, 'blessing_output') schema = standard_artifacts.Schema() schema.uri = os.path.join(source_data_dir, 'schema_gen') input_dict = { constants.EXAMPLES_KEY: [examples], constants.MODEL_KEY: [model], constants.SCHEMA_KEY: [schema], } if has_baseline: input_dict[constants.BASELINE_MODEL_KEY] = [baseline_model] # Create output dict. eval_output = standard_artifacts.ModelEvaluation() eval_output.uri = os.path.join(output_data_dir, 'eval_output') blessing_output = standard_artifacts.ModelBlessing() blessing_output.uri = os.path.join(output_data_dir, 'blessing_output') output_dict = { constants.EVALUATION_KEY: [eval_output], constants.BLESSING_KEY: [blessing_output], } # List needs to be serialized before being passed into Do function. exec_properties[constants.EXAMPLE_SPLITS_KEY] = json_utils.dumps(None) # Run executor. evaluator = executor.Executor() evaluator.Do(input_dict, output_dict, exec_properties) # Check evaluator outputs. self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'eval_config.json'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'metrics'))) self.assertTrue(tf.io.gfile.exists(os.path.join(eval_output.uri, 'plots'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'validations'))) if blessed: self.assertTrue( tf.io.gfile.exists(os.path.join(blessing_output.uri, 'BLESSED'))) else: self.assertTrue( tf.io.gfile.exists(os.path.join(blessing_output.uri, 'NOT_BLESSED')))
def _create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, serving_model_dir: Text, metadata_path: Text, beam_pipeline_args: List[Text]) -> pipeline.Pipeline: """Implements the imdb sentiment analysis pipline with TFX.""" output = example_gen_pb2.Output(split_config=example_gen_pb2.SplitConfig( splits=[ example_gen_pb2.SplitConfig.Split(name='train', hash_buckets=9), example_gen_pb2.SplitConfig.Split(name='eval', hash_buckets=1) ])) # Brings data in to the pipline example_gen = CsvExampleGen(input_base=data_root, output_config=output) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform(examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Uses user-provided Python function that trains a model. trainer = Trainer(module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], train_args=trainer_pb2.TrainArgs(num_steps=500), eval_args=trainer_pb2.EvalArgs(num_steps=200)) # Get the latest blessed model for model validation. model_resolver = resolver.Resolver( strategy_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel( type=ModelBlessing)).with_id('latest_blessed_model_resolver') # Uses TFMA to compute evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key='label')], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='BinaryAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( # Increase this threshold when training on complete # dataset. lower_bound={'value': 0.01}), # Change threshold will be ignored if there is no # baseline model resolved from MLMD (first run). change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-2}))) ]) ]) evaluator = Evaluator(examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher(model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) components = [ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher, ] return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), enable_cache=True, beam_pipeline_args=beam_pipeline_args)
def _create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, serving_model_dir: Text, metadata_path: Text, beam_pipeline_args: List[Text]) -> pipeline.Pipeline: """Implements the chicago taxi pipeline with TFX.""" # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input_base=data_root) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen( statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Uses user-provided Python function that implements a model using TF-Learn. trainer = Trainer( module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], train_args=trainer_pb2.TrainArgs(num_steps=1000), eval_args=trainer_pb2.EvalArgs(num_steps=150)) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec( signature_name='serving_default', label_key='tips_xf', preprocessing_function_names=['tft_layer']) ], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='BinaryAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.6}), # Change threshold will be ignored if there is no # baseline model resolved from MLMD (first run). change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10}))) ]) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher, ], enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), beam_pipeline_args=beam_pipeline_args)
def _create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, serving_model_dir: Text, metadata_path: Text, worker_parallelism: int) -> pipeline.Pipeline: """Implements the chicago taxi pipeline with TFX.""" examples = external_input(data_root) # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input=examples) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen( statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Uses user-provided Python function that implements a model using TF-Learn. trainer = Trainer( module_file=module_file, transformed_examples=transform.outputs['transformed_examples'], schema=schema_gen.outputs['schema'], transform_graph=transform.outputs['transform_graph'], train_args=trainer_pb2.TrainArgs(num_steps=10000), eval_args=trainer_pb2.EvalArgs(num_steps=5000)) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['trip_start_hour']) ], metrics_specs=[ tfma.MetricsSpec( thresholds={ 'accuracy': tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.6}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher ], enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), # LINT.IfChange beam_pipeline_args=[ # -------------------------- Beam Args --------------------------. '--runner=PortableRunner', # Points to the job server started in # setup_beam_on_{flink, spark}.sh '--job_endpoint=localhost:8099', '--environment_type=LOOPBACK', '--sdk_worker_parallelism=%d' % worker_parallelism, '--experiments=use_loopback_process_worker=True', # Setting environment_cache_millis to practically infinity enables # continual reuse of Beam SDK workers, improving performance. '--environment_cache_millis=1000000', # TODO(BEAM-7199): Obviate the need for setting pre_optimize=all. # pylint: disable=g-bad-todo '--experiments=pre_optimize=all', # Note; We use 100 worker threads to mitigate the issue with # scheduling work between the Beam runner and SDK harness. Flink # and Spark can process unlimited work items concurrently while # SdkHarness can only process 1 work item per worker thread. # Having 100 threads will let 100 tasks execute concurrently # avoiding scheduling issue in most cases. In case the threads are # exhausted, beam prints the relevant message in the log. # TODO(BEAM-8151) Remove worker_threads=100 after we start using a # pylint: disable=g-bad-todo # virtually unlimited thread pool by default. '--experiments=worker_threads=100', # ---------------------- End of Beam Args -----------------------. # --------- Flink runner Args (ignored by Spark runner) ---------. '--parallelism=%d' % worker_parallelism, # TODO(FLINK-10672): Obviate setting BATCH_FORCED. # pylint: disable=g-bad-todo '--execution_mode_for_batch=BATCH_FORCED', # ------------------ End of Flink runner Args -------------------. ], # LINT.ThenChange(setup/setup_beam_on_spark.sh) # LINT.ThenChange(setup/setup_beam_on_flink.sh) )
def create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, test_data_root: Text, module_file: Text, serving_model_dir: Text, enable_cache: bool, metadata_connection_config: Optional[ metadata_store_pb2.ConnectionConfig] = None, beam_pipeline_args: Optional[List[Text]] = None): """create pipeline Args: pipeline_name (Text): pipeline name pipeline_root (Text): pipeline root path data_root (Text): input data path test_data_root (Text): test data path module_file (Text): Python module files to inject customized logic into the TFX components. serving_model_dir (Text): output directory path enable_cache (bool): Whether to use the cache or not metadata_connection_config (Optional[ metadata_store_pb2.ConnectionConfig], optional): [description]. Defaults to None. beam_pipeline_args (Optional[List[Text]], optional): [description]. Defaults to None. Returns: [type]: [description] """ # train testで分かれているtfrecordを指定 output_config = example_gen_pb2.Output( split_config=example_gen_pb2.SplitConfig(splits=[ example_gen_pb2.SplitConfig.Split(name='train', hash_buckets=8), example_gen_pb2.SplitConfig.Split(name='eval', hash_buckets=2), ])) # パイプラインにデータをロード example_gen = ImportExampleGen(input_base=data_root, output_config=output_config, instance_name="train_data") test_example_gen = ImportExampleGen(input_base=test_data_root, instance_name="test_data") # データの統計量を計算 statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # staticsGenの統計ファイルからスキーマを生成 schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # データに欠損などがないかを検査 example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) transform = Transform(examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) trainer = Trainer( module_file=module_file, custom_executor_spec=executor_spec.ExecutorClassSpec(GenericExecutor), examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], train_args=trainer_pb2.TrainArgs(num_steps=160), eval_args=trainer_pb2.EvalArgs(num_steps=4), ) model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # https://github.com/tensorflow/tfx/issues/3016 eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec(label_key='label', model_type='tf_keras', signature_name="serving_default") ], slicing_specs=[ tfma.SlicingSpec(), ], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.2}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-3}))) ]) ]) evaluator = Evaluator(examples=test_example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) pusher = Pusher(model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) components = [ example_gen, test_example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher ] return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=enable_cache, metadata_connection_config=metadata_connection_config, beam_pipeline_args=beam_pipeline_args, )
class ExecutorTest(tf.test.TestCase, absl.testing.parameterized.TestCase): # TODO(jinhuang): add test for eval_saved_model when supported. @absl.testing.parameterized.named_parameters(('eval_config', { 'eval_config': json_format.MessageToJson(tfma.EvalConfig(slicing_specs=[ tfma.SlicingSpec(feature_keys=['trip_start_hour']), tfma.SlicingSpec(feature_keys=['trip_start_day', 'trip_miles']), ]), preserving_proto_field_name=True) }), ('eval_config_w_baseline', { 'eval_config': json_format.MessageToJson(tfma.EvalConfig( model_specs=[ tfma.ModelSpec(name='baseline', is_baseline=True), tfma.ModelSpec(name='candidate'), ], slicing_specs=[ tfma.SlicingSpec(feature_keys=['trip_start_hour']), tfma.SlicingSpec( feature_keys=['trip_start_day', 'trip_miles']), ]), preserving_proto_field_name=True) }), ('legacy_feature_slicing', { 'feature_slicing_spec': json_format.MessageToJson(evaluator_pb2.FeatureSlicingSpec(specs=[ evaluator_pb2.SingleSlicingSpec( column_for_slicing=['trip_start_hour']), evaluator_pb2.SingleSlicingSpec( column_for_slicing=['trip_start_day', 'trip_miles']), ]), preserving_proto_field_name=True), })) def testDo(self, exec_properties): source_data_dir = os.path.join( os.path.dirname(os.path.dirname(__file__)), 'testdata') output_data_dir = os.path.join( os.environ.get('TEST_UNDECLARED_OUTPUTS_DIR', self.get_temp_dir()), self._testMethodName) # Create input dict. examples = standard_artifacts.Examples() examples.uri = os.path.join(source_data_dir, 'csv_example_gen') examples.split_names = artifact_utils.encode_split_names( ['train', 'eval']) model = standard_artifacts.Model() baseline_model = standard_artifacts.Model() model.uri = os.path.join(source_data_dir, 'trainer/current') baseline_model.uri = os.path.join(source_data_dir, 'trainer/previous/') input_dict = { executor.EXAMPLES_KEY: [examples], executor.MODEL_KEY: [model], executor.BASELINE_MODEL_KEY: [baseline_model], } # Create output dict. eval_output = standard_artifacts.ModelEvaluation() eval_output.uri = os.path.join(output_data_dir, 'eval_output') output_dict = { executor.EVALUATION_KEY: [eval_output], } # Run executor. evaluator = executor.Executor() evaluator.Do(input_dict, output_dict, exec_properties) # Check evaluator outputs. self.assertTrue( tf.io.gfile.exists( os.path.join(eval_output.uri, 'eval_config.json'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'metrics'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'plots'))) @absl.testing.parameterized.named_parameters(('legacy_feature_slicing', { 'feature_slicing_spec': json_format.MessageToJson(evaluator_pb2.FeatureSlicingSpec(specs=[ evaluator_pb2.SingleSlicingSpec( column_for_slicing=['trip_start_hour']), evaluator_pb2.SingleSlicingSpec( column_for_slicing=['trip_start_day', 'trip_miles']), ]), preserving_proto_field_name=True), })) def testDoLegacySingleEvalSavedModelWFairness(self, exec_properties): source_data_dir = os.path.join( os.path.dirname(os.path.dirname(__file__)), 'testdata') output_data_dir = os.path.join( os.environ.get('TEST_UNDECLARED_OUTPUTS_DIR', self.get_temp_dir()), self._testMethodName) # Create input dict. examples = standard_artifacts.Examples() examples.uri = os.path.join(source_data_dir, 'csv_example_gen') examples.split_names = artifact_utils.encode_split_names( ['train', 'eval']) model = standard_artifacts.Model() baseline_model = standard_artifacts.Model() model.uri = os.path.join(source_data_dir, 'trainer/current') baseline_model.uri = os.path.join(source_data_dir, 'trainer/previous/') input_dict = { executor.EXAMPLES_KEY: [examples], executor.MODEL_KEY: [model], } # Create output dict. eval_output = standard_artifacts.ModelEvaluation() eval_output.uri = os.path.join(output_data_dir, 'eval_output') output_dict = {executor.EVALUATION_KEY: [eval_output]} try: # Need to import the following module so that the fairness indicator # post-export metric is registered. This may raise an ImportError if the # currently-installed version of TFMA does not support fairness # indicators. import tensorflow_model_analysis.addons.fairness.post_export_metrics.fairness_indicators # pylint: disable=g-import-not-at-top, unused-variable exec_properties['fairness_indicator_thresholds'] = [ 0.1, 0.3, 0.5, 0.7, 0.9 ] except ImportError: absl.logging.warning( 'Not testing fairness indicators because a compatible TFMA version ' 'is not installed.') # Run executor. evaluator = executor.Executor() evaluator.Do(input_dict, output_dict, exec_properties) # Check evaluator outputs. self.assertTrue( tf.io.gfile.exists( os.path.join(eval_output.uri, 'eval_config.json'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'metrics'))) self.assertTrue( tf.io.gfile.exists(os.path.join(eval_output.uri, 'plots')))
def _create_pipeline(pipeline_name: str, pipeline_root: str, data_root: str, module_file: str, serving_model_dir_lite: str, metadata_path: str, labels_path: str, beam_pipeline_args: List[str]) -> pipeline.Pipeline: """Implements the CIFAR10 image classification pipeline using TFX.""" # This is needed for datasets with pre-defined splits # Change the pattern argument to train_whole/* and test_whole/* to train # on the whole CIFAR-10 dataset input_config = example_gen_pb2.Input(splits=[ example_gen_pb2.Input.Split(name='train', pattern='train/*'), example_gen_pb2.Input.Split(name='eval', pattern='test/*') ]) # Brings data into the pipeline. example_gen = ImportExampleGen( input_base=data_root, input_config=input_config) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen( statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Uses user-provided Python function that trains a model. # When traning on the whole dataset, use 18744 for train steps, 156 for eval # steps. 18744 train steps correspond to 24 epochs on the whole train set, and # 156 eval steps correspond to 1 epoch on the whole test set. The # configuration below is for training on the dataset we provided in the data # folder, which has 128 train and 128 test samples. The 160 train steps # correspond to 40 epochs on this tiny train set, and 4 eval steps correspond # to 1 epoch on this tiny test set. trainer = Trainer( module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], train_args=trainer_pb2.TrainArgs(num_steps=160), eval_args=trainer_pb2.EvalArgs(num_steps=4), custom_config={'labels_path': labels_path}) # Get the latest blessed model for model validation. model_resolver = resolver.Resolver( strategy_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel( type=ModelBlessing)).with_id('latest_blessed_model_resolver') # Uses TFMA to compute evaluation statistics over features of a model and # perform quality validation of a candidate model (compare to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key='label_xf', model_type='tf_lite')], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.55}), # Change threshold will be ignored if there is no # baseline model resolved from MLMD (first run). change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-3}))) ]) ]) # Uses TFMA to compute the evaluation statistics over features of a model. # We evaluate using the materialized examples that are output by Transform # because # 1. the decoding_png function currently performed within Transform are not # compatible with TFLite. # 2. MLKit requires deserialized (float32) tensor image inputs # Note that for deployment, the same logic that is performed within Transform # must be reproduced client-side. evaluator = Evaluator( examples=transform.outputs['transformed_examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir_lite))) components = [ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher ] return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), beam_pipeline_args=beam_pipeline_args)
def create_pipeline( pipeline_name: Text, pipeline_root: Text, data_path: Text, preprocessing_fn: Text, run_fn: Text, train_args: trainer_pb2.TrainArgs, eval_args: trainer_pb2.EvalArgs, eval_accuracy_threshold: float, serving_model_dir: Text, metadata_connection_config: Optional[ metadata_store_pb2.ConnectionConfig] = None, beam_pipeline_args: Optional[List[Text]] = None, ) -> pipeline.Pipeline: """Implements the penguin pipeline with TFX.""" components = [] # Brings data into the pipeline or otherwise joins/converts training data. # TODO(step 2): Might use another ExampleGen class for your data. example_gen = CsvExampleGen(input_base=data_path) components.append(example_gen) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) components.append(statistics_gen) # Generates schema based on statistics files. schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) components.append(schema_gen) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( # pylint: disable=unused-variable statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) components.append(example_validator) # Performs transformations and feature engineering in training and serving. transform = Transform( # pylint: disable=unused-variable examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], preprocessing_fn=preprocessing_fn) # TODO(step 3): Uncomment here to add Transform to the pipeline. # components.append(transform) # Uses user-provided Python function that implements a model using Tensorflow. trainer = Trainer( run_fn=run_fn, examples=example_gen.outputs['examples'], # Use outputs of Transform as training inputs if Transform is used. # examples=transform.outputs['transformed_examples'], # transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], train_args=train_args, eval_args=eval_args) # TODO(step 4): Uncomment here to add Trainer to the pipeline. # components.append(trainer) # Get the latest blessed model for model validation. model_resolver = resolver.Resolver( strategy_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel( type=ModelBlessing)).with_id('latest_blessed_model_resolver') # TODO(step 5): Uncomment here to add ResolverNode to the pipeline. # components.append(model_resolver) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key=features.LABEL_KEY)], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': eval_accuracy_threshold}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10}))) ]) ]) evaluator = Evaluator( # pylint: disable=unused-variable examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # TODO(step 5): Uncomment here to add Evaluator to the pipeline. # components.append(evaluator) # Pushes the model to a file destination if check passed. pusher = Pusher( # pylint: disable=unused-variable model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) # TODO(step 5): Uncomment here to add Pusher to the pipeline. # components.append(pusher) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, # Change this value to control caching of execution results. Default value # is `False`. # enable_cache=True, metadata_connection_config=metadata_connection_config, beam_pipeline_args=beam_pipeline_args, )
def create_pipeline( pipeline_name: str, pipeline_root: str, data_root: str, module_file: str, ai_platform_training_args: Dict[str, str], ai_platform_serving_args: Dict[str, str], enable_tuning: bool, user_provided_schema_path: str, beam_pipeline_args: List[str], use_aip_component: bool, serving_model_dir: Optional[str] = None) -> tfx.dsl.Pipeline: """Implements the penguin pipeline with TFX and Kubeflow Pipeline. Args: pipeline_name: name of the TFX pipeline being created. pipeline_root: root directory of the pipeline. Should be a valid GCS path. data_root: uri of the penguin data. module_file: uri of the module file used in Trainer, Transform and Tuner. ai_platform_training_args: Args of CAIP training job. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.jobs#Job for detailed description. ai_platform_serving_args: Args of CAIP model deployment. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.models for detailed description. enable_tuning: If True, the hyperparameter tuning through CloudTuner is enabled. user_provided_schema_path: Path to the schema of the input data. beam_pipeline_args: List of beam pipeline options. Please refer to https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options. use_aip_component: whether to use normal TFX components or customized AI platform components. serving_model_dir: filepath to write pipeline SavedModel to. Returns: A TFX pipeline object. """ # Number of epochs in training. train_args = tfx.dsl.experimental.RuntimeParameter( name='train-args', default='__SOME_PLACEHOLDER_TO_MAKE_TEST_FAIL_IF_NOT_REPLACED', ptype=str, ) # Number of epochs in evaluation. eval_args = tfx.dsl.experimental.RuntimeParameter( name='eval-args', default='{"num_steps": 50}', ptype=str, ) # Brings data into the pipeline or otherwise joins/converts training data. example_gen = tfx.components.CsvExampleGen( input_base=os.path.join(data_root, 'labelled')) # Computes statistics over data for visualization and example validation. statistics_gen = tfx.components.StatisticsGen( examples=example_gen.outputs['examples']) # Import user-provided schema. schema_gen = tfx.components.ImportSchemaGen( schema_file=user_provided_schema_path) # Performs anomaly detection based on statistics and data schema. example_validator = tfx.components.ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = tfx.components.Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Tunes the hyperparameters for model training based on user-provided Python # function. Note that once the hyperparameters are tuned, you can drop the # Tuner component from pipeline and feed Trainer with tuned hyperparameters. if enable_tuning: if use_aip_component: # The Tuner component launches 1 AIP Training job for flock management of # parallel tuning. For example, 2 workers (defined by num_parallel_trials) # in the flock management AIP Training job, each runs a search loop for # trials as shown below. # Tuner component -> CAIP job X -> CloudTunerA -> tuning trials # -> CloudTunerB -> tuning trials # # Distributed training for each trial depends on the Tuner # (kerastuner.BaseTuner) setup in tuner_fn. Currently CloudTuner is single # worker training per trial. DistributingCloudTuner (a subclass of # CloudTuner) launches remote distributed training job per trial. # # E.g., single worker training per trial # ... -> CloudTunerA -> single worker training # -> CloudTunerB -> single worker training # vs distributed training per trial # ... -> DistributingCloudTunerA -> CAIP job Y -> master,worker1,2,3 # -> DistributingCloudTunerB -> CAIP job Z -> master,worker1,2,3 tuner = tfx.extensions.google_cloud_ai_platform.Tuner( module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], train_args=tfx.proto.TrainArgs(num_steps=100), eval_args=tfx.proto.EvalArgs(num_steps=50), tune_args=tfx.proto.TuneArgs( # num_parallel_trials=3 means that 3 search loops are # running in parallel. num_parallel_trials=3), custom_config={ # Note that this TUNING_ARGS_KEY will be used to start the CAIP # job for parallel tuning (CAIP job X above). # # num_parallel_trials will be used to fill/overwrite the # workerCount specified by TUNING_ARGS_KEY: # num_parallel_trials = workerCount + 1 (for master) tfx.extensions.google_cloud_ai_platform.experimental.TUNING_ARGS_KEY: ai_platform_training_args, # This working directory has to be a valid GCS path and will be # used to launch remote training job per trial. tfx.extensions.google_cloud_ai_platform.experimental.REMOTE_TRIALS_WORKING_DIR_KEY: os.path.join(pipeline_root, 'trials'), }) else: tuner = tfx.components.Tuner( examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], module_file=module_file, train_args=tfx.proto.TrainArgs(num_steps=100), eval_args=tfx.proto.EvalArgs(num_steps=50), tune_args=tfx.proto.TuneArgs(num_parallel_trials=3)) if use_aip_component: # Uses user-provided Python function that trains a model. trainer = tfx.extensions.google_cloud_ai_platform.Trainer( module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], # If Tuner is in the pipeline, Trainer can take Tuner's output # best_hyperparameters artifact as input and utilize it in the user # module code. # # If there isn't Tuner in the pipeline, either use Importer to # import a previous Tuner's output to feed to Trainer, or directly use # the tuned hyperparameters in user module code and set hyperparameters # to None here. # # Example of Importer, # hparams_importer = Importer( # source_uri='path/to/best_hyperparameters.txt', # artifact_type=HyperParameters).with_id('import_hparams') # ... # hyperparameters = hparams_importer.outputs['result'], hyperparameters=(tuner.outputs['best_hyperparameters'] if enable_tuning else None), train_args=tfx.proto.TrainArgs(num_steps=100), eval_args=tfx.proto.EvalArgs(num_steps=50), custom_config={ tfx.extensions.google_cloud_ai_platform.TRAINING_ARGS_KEY: ai_platform_training_args }) else: trainer = tfx.components.Trainer( module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], hyperparameters=(tuner.outputs['best_hyperparameters'] if enable_tuning else None), train_args=train_args, eval_args=eval_args, ) # Get the latest blessed model for model validation. model_resolver = tfx.dsl.Resolver( strategy_class=tfx.dsl.experimental.LatestBlessedModelStrategy, model=tfx.dsl.Channel(type=tfx.types.standard_artifacts.Model), model_blessing=tfx.dsl.Channel( type=tfx.types.standard_artifacts.ModelBlessing)).with_id( 'latest_blessed_model_resolver') # Uses TFMA to compute evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec(signature_name='serving_default', label_key='species_xf', preprocessing_function_names=['transform_features']) ], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.3}), # Change threshold will be ignored if there is no # baseline model resolved from MLMD (first run). change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10}))) ]) ]) evaluator = tfx.components.Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) if use_aip_component: pusher = tfx.extensions.google_cloud_ai_platform.Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], custom_config={ tfx.extensions.google_cloud_ai_platform.experimental.PUSHER_SERVING_ARGS_KEY: ai_platform_serving_args }) else: pusher = tfx.components.Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=tfx.proto.PushDestination( filesystem=tfx.proto.PushDestination.Filesystem( base_directory=serving_model_dir))) components = [ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher, ] if enable_tuning: components.append(tuner) return tfx.dsl.Pipeline(pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=True, beam_pipeline_args=beam_pipeline_args)
def _create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, user_schema_path: Text, module_file: Text, serving_model_dir: Text, metadata_path: Text, beam_pipeline_args: List[Text]) -> pipeline.Pipeline: """Implements the chicago taxi pipeline with TFX.""" # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input_base=data_root) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Import user-provided schema. user_schema_importer = ImporterNode(instance_name='import_user_schema', source_uri=user_schema_path, artifact_type=Schema) # Generates schema based on statistics files. Even we use user-provided schema # in downstream components, we still want to generate the schema of the newest # data so that user can compare and optionally update the schema to use. schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=user_schema_importer.outputs['result']) # Performs transformations and feature engineering in training and serving. transform = Transform(examples=example_gen.outputs['examples'], schema=user_schema_importer.outputs['result'], module_file=module_file) # Uses user-provided Python function that implements a model using TF-Learn. trainer = Trainer( module_file=module_file, transformed_examples=transform.outputs['transformed_examples'], schema=user_schema_importer.outputs['result'], transform_graph=transform.outputs['transform_graph'], train_args=trainer_pb2.TrainArgs(num_steps=10000), eval_args=trainer_pb2.EvalArgs(num_steps=5000)) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['trip_start_hour']) ], metrics_specs=[ tfma.MetricsSpec( thresholds={ 'accuracy': tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.6}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher(model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, user_schema_importer, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher ], enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), beam_pipeline_args=beam_pipeline_args)
def create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root_uri: data_types.RuntimeParameter, train_steps: data_types.RuntimeParameter, eval_steps: data_types.RuntimeParameter, enable_tuning: bool, ai_platform_training_args: Dict[Text, Text], ai_platform_serving_args: Dict[Text, Text], beam_pipeline_args: List[Text], enable_cache: Optional[bool] = False) -> pipeline.Pipeline: """Trains and deploys the Keras Covertype Classifier with TFX pipeline on Google Cloud. Args: pipeline_name: name of the TFX pipeline being created. pipeline_root: root directory of the pipeline. Should be a valid GCS path. data_root_uri: uri of the dataset. train_steps: runtime parameter for number of model training steps for the Trainer component. eval_steps: runtime parameter for number of model evaluation steps for the Trainer component. enable_tuning: If True, the hyperparameter tuning through CloudTuner is enabled. ai_platform_training_args: Args of CAIP training job. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.jobs#Job for detailed description. ai_platform_serving_args: Args of CAIP model deployment. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.models for detailed description. beam_pipeline_args: Optional list of beam pipeline options. Please refer to https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options. When this argument is not provided, the default is to use GCP DataflowRunner with 50GB disk size as specified in this function. If an empty list is passed in, default specified by Beam will be used, which can be found at https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options enable_cache: Optional boolean Returns: A TFX pipeline object. """ # Brings data into the pipeline and splits the data into training and eval splits output = example_gen_pb2.Output( split_config=example_gen_pb2.SplitConfig(splits=[ example_gen_pb2.SplitConfig.Split(name='train', hash_buckets=4), example_gen_pb2.SplitConfig.Split(name='eval', hash_buckets=1) ])) examplegen = CsvExampleGen(input_base=data_root_uri, output_config=output) # Computes statistics over data for visualization and example validation. statisticsgen = StatisticsGen(examples=examplegen.outputs.examples) # Generates schema based on statistics files. Even though, we use user-provided schema # we still want to generate the schema of the newest data for tracking and comparison schemagen = SchemaGen(statistics=statisticsgen.outputs.statistics) # Import a user-provided schema import_schema = ImporterNode(instance_name='import_user_schema', source_uri=SCHEMA_FOLDER, artifact_type=Schema) # Performs anomaly detection based on statistics and data schema. examplevalidator = ExampleValidator( statistics=statisticsgen.outputs.statistics, schema=import_schema.outputs.result) # Performs transformations and feature engineering in training and serving. transform = Transform(examples=examplegen.outputs.examples, schema=import_schema.outputs.result, module_file=TRANSFORM_MODULE_FILE) # Tunes the hyperparameters for model training based on user-provided Python # function. Note that once the hyperparameters are tuned, you can drop the # Tuner component from pipeline and feed Trainer with tuned hyperparameters. if enable_tuning: # The Tuner component launches 1 AI Platform Training job for flock management. # For example, 3 workers (defined by num_parallel_trials) in the flock # management AI Platform Training job, each runs Tuner.Executor. tuner = Tuner( module_file=TRAIN_MODULE_FILE, examples=transform.outputs.transformed_examples, transform_graph=transform.outputs.transform_graph, train_args={'num_steps': train_steps}, eval_args={'num_steps': eval_steps}, tune_args=tuner_pb2.TuneArgs( # num_parallel_trials=2 means that 2 search loops are running in parallel. num_parallel_trials=2), custom_config={ # Configures Cloud AI Platform-specific configs. For details, see # https://cloud.google.com/ai-platform/training/docs/reference/rest/v1/projects.jobs#traininginput. ai_platform_trainer_executor.TRAINING_ARGS_KEY: ai_platform_training_args }) # Trains the model using a user provided trainer function. trainer = Trainer( custom_executor_spec=executor_spec.ExecutorClassSpec( ai_platform_trainer_executor.GenericExecutor), module_file=TRAIN_MODULE_FILE, transformed_examples=transform.outputs.transformed_examples, schema=import_schema.outputs.result, transform_graph=transform.outputs.transform_graph, hyperparameters=(tuner.outputs.best_hyperparameters if enable_tuning else None), train_args={'num_steps': train_steps}, eval_args={'num_steps': eval_steps}, custom_config={'ai_platform_training_args': ai_platform_training_args}) # Get the latest blessed model for model validation. resolver = ResolverNode(instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute a evaluation statistics over features of a model. accuracy_threshold = tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold(lower_bound={'value': 0.5}, upper_bound={'value': 0.99}), ) metrics_specs = tfma.MetricsSpec(metrics=[ tfma.MetricConfig(class_name='SparseCategoricalAccuracy', threshold=accuracy_threshold), tfma.MetricConfig(class_name='ExampleCount') ]) eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key='Cover_Type')], metrics_specs=[metrics_specs], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['Wilderness_Area']) ]) evaluator = Evaluator(examples=examplegen.outputs.examples, model=trainer.outputs.model, baseline_model=resolver.outputs.model, eval_config=eval_config) # Validate model can be loaded and queried in sand-boxed environment # mirroring production. serving_config = infra_validator_pb2.ServingSpec( tensorflow_serving=infra_validator_pb2.TensorFlowServing( tags=['latest']), kubernetes=infra_validator_pb2.KubernetesConfig(), ) validation_config = infra_validator_pb2.ValidationSpec( max_loading_time_seconds=60, num_tries=3, ) request_config = infra_validator_pb2.RequestSpec( tensorflow_serving=infra_validator_pb2.TensorFlowServingRequestSpec(), num_examples=3, ) infravalidator = InfraValidator( model=trainer.outputs.model, examples=examplegen.outputs.examples, serving_spec=serving_config, validation_spec=validation_config, request_spec=request_config, ) # Checks whether the model passed the validation steps and pushes the model # to CAIP Prediction if checks are passed. pusher = Pusher(custom_executor_spec=executor_spec.ExecutorClassSpec( ai_platform_pusher_executor.Executor), model=trainer.outputs.model, model_blessing=evaluator.outputs.blessing, infra_blessing=infravalidator.outputs.blessing, custom_config={ ai_platform_pusher_executor.SERVING_ARGS_KEY: ai_platform_serving_args }) components = [ examplegen, statisticsgen, schemagen, import_schema, examplevalidator, transform, trainer, resolver, evaluator, infravalidator, pusher ] if enable_tuning: components.append(tuner) return pipeline.Pipeline(pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=enable_cache, beam_pipeline_args=beam_pipeline_args)
def create_pipeline( pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, ai_platform_training_args: Dict[Text, Text], ai_platform_serving_args: Dict[Text, Text], enable_tuning: bool, beam_pipeline_args: Optional[List[Text]] = None) -> pipeline.Pipeline: """Implements the penguin pipeline with TFX and Kubeflow Pipeline. Args: pipeline_name: name of the TFX pipeline being created. pipeline_root: root directory of the pipeline. Should be a valid GCS path. data_root: uri of the penguin data. module_file: uri of the module files used in Trainer and Transform components. ai_platform_training_args: Args of CAIP training job. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.jobs#Job for detailed description. ai_platform_serving_args: Args of CAIP model deployment. Please refer to https://cloud.google.com/ml-engine/reference/rest/v1/projects.models for detailed description. enable_tuning: If True, the hyperparameter tuning through CloudTuner is enabled. beam_pipeline_args: Optional list of beam pipeline options. Please refer to https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options. When this argument is not provided, the default is to use GCP DataflowRunner with 50GB disk size as specified in this function. If an empty list is passed in, default specified by Beam will be used, which can be found at https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options Returns: A TFX pipeline object. """ examples = external_input(data_root) # Beam args to run data processing on DataflowRunner. # # TODO(b/151114974): Remove `disk_size_gb` flag after default is increased. # TODO(b/151116587): Remove `shuffle_mode` flag after default is changed. # TODO(b/156874687): Remove `machine_type` after IP addresses are no longer a # scaling bottleneck. if beam_pipeline_args is None: beam_pipeline_args = [ '--runner=DataflowRunner', '--project=' + _project_id, '--temp_location=' + os.path.join(_output_bucket, 'tmp'), '--region=' + _gcp_region, # Temporary overrides of defaults. '--disk_size_gb=50', '--experiments=shuffle_mode=auto', '--machine_type=e2-standard-8', ] # Number of epochs in training. train_steps = data_types.RuntimeParameter( name='train_steps', default=100, ptype=int, ) # Number of epochs in evaluation. eval_steps = data_types.RuntimeParameter( name='eval_steps', default=50, ptype=int, ) # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input=examples) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform(examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Update ai_platform_training_args if distributed training was enabled. # Number of worker machines used in distributed training. worker_count = data_types.RuntimeParameter( name='worker_count', default=2, ptype=int, ) # Type of worker machines used in distributed training. worker_type = data_types.RuntimeParameter( name='worker_type', default='standard', ptype=str, ) local_training_args = copy.deepcopy(ai_platform_training_args) if FLAGS.distributed_training: local_training_args.update({ # You can specify the machine types, the number of replicas for workers # and parameter servers. # https://cloud.google.com/ml-engine/reference/rest/v1/projects.jobs#ScaleTier 'scaleTier': 'CUSTOM', 'masterType': 'large_model', 'workerType': worker_type, 'parameterServerType': 'standard', 'workerCount': worker_count, 'parameterServerCount': 1, }) # Tunes the hyperparameters for model training based on user-provided Python # function. Note that once the hyperparameters are tuned, you can drop the # Tuner component from pipeline and feed Trainer with tuned hyperparameters. if enable_tuning: # The Tuner component launches 1 AIP Training job for flock management. # For example, 3 workers (defined by num_parallel_trials) in the flock # management AIP Training job, each runs Tuner.Executor. # Then, 3 AIP Training Jobs (defined by local_training_args) are invoked # from each worker in the flock management Job for Trial execution. tuner = Tuner( module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], train_args={'num_steps': train_steps}, eval_args={'num_steps': eval_steps}, tune_args=tuner_pb2.TuneArgs( # num_parallel_trials=3 means that 3 search loops are # running in parallel. # Each tuner may include a distributed training job which can be # specified in local_training_args above (e.g. 1 PS + 2 workers). num_parallel_trials=3), custom_config={ # Configures Cloud AI Platform-specific configs . For details, see # https://cloud.google.com/ai-platform/training/docs/reference/rest/v1/projects.jobs#traininginput. ai_platform_trainer_executor.TRAINING_ARGS_KEY: local_training_args }) # Uses user-provided Python function that trains a model. trainer = Trainer( custom_executor_spec=executor_spec.ExecutorClassSpec( ai_platform_trainer_executor.GenericExecutor), module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], # If Tuner is in the pipeline, Trainer can take Tuner's output # best_hyperparameters artifact as input and utilize it in the user module # code. # # If there isn't Tuner in the pipeline, either use ImporterNode to import # a previous Tuner's output to feed to Trainer, or directly use the tuned # hyperparameters in user module code and set hyperparameters to None # here. # # Example of ImporterNode, # hparams_importer = ImporterNode( # instance_name='import_hparams', # source_uri='path/to/best_hyperparameters.txt', # artifact_type=HyperParameters) # ... # hyperparameters = hparams_importer.outputs['result'], hyperparameters=(tuner.outputs['best_hyperparameters'] if enable_tuning else None), train_args={'num_steps': train_steps}, eval_args={'num_steps': eval_steps}, custom_config={ ai_platform_trainer_executor.TRAINING_ARGS_KEY: local_training_args }) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute an evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key='species')], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.6}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10}))) ]) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) pusher = Pusher(custom_executor_spec=executor_spec.ExecutorClassSpec( ai_platform_pusher_executor.Executor), model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], custom_config={ ai_platform_pusher_executor.SERVING_ARGS_KEY: ai_platform_serving_args }) components = [ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher, ] if enable_tuning: components.append(tuner) return pipeline.Pipeline(pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=True, beam_pipeline_args=beam_pipeline_args)
def create_test_pipeline(): """Builds an Iris example pipeline with slight changes.""" pipeline_name = "iris" iris_root = "iris_root" serving_model_dir = os.path.join(iris_root, "serving_model", pipeline_name) tfx_root = "tfx_root" data_path = os.path.join(tfx_root, "data_path") pipeline_root = os.path.join(tfx_root, "pipelines", pipeline_name) example_gen = CsvExampleGen(input=external_input(data_path)) statistics_gen = StatisticsGen(examples=example_gen.outputs["examples"]) importer = ImporterNode(instance_name="my_importer", source_uri="m/y/u/r/i", properties={ "split_names": "['train', 'eval']", }, custom_properties={ "int_custom_property": 42, "str_custom_property": "42", }, artifact_type=standard_artifacts.Examples) another_statistics_gen = StatisticsGen( examples=importer.outputs["result"], instance_name="another_statistics_gen") schema_gen = SchemaGen(statistics=statistics_gen.outputs["statistics"]) example_validator = ExampleValidator( statistics=statistics_gen.outputs["statistics"], schema=schema_gen.outputs["schema"]) trainer = Trainer( # Use RuntimeParameter as module_file to test out RuntimeParameter in # compiler. module_file=data_types.RuntimeParameter(name="module_file", default=os.path.join( iris_root, "iris_utils.py"), ptype=str), custom_executor_spec=executor_spec.ExecutorClassSpec(GenericExecutor), examples=example_gen.outputs["examples"], schema=schema_gen.outputs["schema"], train_args=trainer_pb2.TrainArgs(num_steps=2000), # Attaching `TrainerArgs` as platform config is not sensible practice, # but is only for testing purpose. eval_args=trainer_pb2.EvalArgs(num_steps=5)).with_platform_config( config=trainer_pb2.TrainArgs(num_steps=2000)) model_resolver = ResolverNode( instance_name="latest_blessed_model_resolver", resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=standard_artifacts.Model), model_blessing=Channel(type=standard_artifacts.ModelBlessing)) eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name="eval")], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec( thresholds={ "sparse_categorical_accuracy": tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={"value": 0.6}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={"value": -1e-10})) }) ]) evaluator = Evaluator(examples=example_gen.outputs["examples"], model=trainer.outputs["model"], baseline_model=model_resolver.outputs["model"], eval_config=eval_config) pusher = Pusher(model=trainer.outputs["model"], model_blessing=evaluator.outputs["blessing"], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, another_statistics_gen, importer, schema_gen, example_validator, trainer, model_resolver, evaluator, pusher, ], enable_cache=True, beam_pipeline_args=["--my_testing_beam_pipeline_args=foo"], # Attaching `TrainerArgs` as platform config is not sensible practice, # but is only for testing purpose. platform_config=trainer_pb2.TrainArgs(num_steps=2000), execution_mode=pipeline.ExecutionMode.SYNC)
def create_pipeline( pipeline_name: Text, pipeline_root: Text, data_path: Text, # TODO(step 7): (Optional) Uncomment here to use BigQuery as a data source. # query: Text, preprocessing_fn: Text, trainer_fn: Text, train_args: trainer_pb2.TrainArgs, eval_args: trainer_pb2.EvalArgs, eval_accuracy_threshold: float, serving_model_dir: Text, metadata_connection_config: Optional[ metadata_store_pb2.ConnectionConfig] = None, beam_pipeline_args: Optional[List[Text]] = None, ai_platform_training_args: Optional[Dict[Text, Text]] = None, ai_platform_serving_args: Optional[Dict[Text, Any]] = None, ) -> pipeline.Pipeline: """Implements the chicago taxi pipeline with TFX.""" components = [] # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input=external_input(data_path)) # TODO(step 7): (Optional) Uncomment here to use BigQuery as a data source. # example_gen = BigQueryExampleGen(query=query) components.append(example_gen) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # TODO(step 5): Uncomment here to add StatisticsGen to the pipeline. # components.append(statistics_gen) # Generates schema based on statistics files. infer_schema = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False) # TODO(step 5): Uncomment here to add SchemaGen to the pipeline. # components.append(infer_schema) # Performs anomaly detection based on statistics and data schema. validate_stats = ExampleValidator( # pylint: disable=unused-variable statistics=statistics_gen.outputs['statistics'], schema=infer_schema.outputs['schema']) # TODO(step 5): Uncomment here to add ExampleValidator to the pipeline. # components.append(validate_stats) # Performs transformations and feature engineering in training and serving. transform = Transform(examples=example_gen.outputs['examples'], schema=infer_schema.outputs['schema'], preprocessing_fn=preprocessing_fn) # TODO(step 6): Uncomment here to add Transform to the pipeline. # components.append(transform) # Uses user-provided Python function that implements a model using TF-Learn. trainer_args = { 'trainer_fn': trainer_fn, 'transformed_examples': transform.outputs['transformed_examples'], 'schema': infer_schema.outputs['schema'], 'transform_graph': transform.outputs['transform_graph'], 'train_args': train_args, 'eval_args': eval_args, } if ai_platform_training_args is not None: trainer_args.update({ 'custom_executor_spec': executor_spec.ExecutorClassSpec( ai_platform_trainer_executor.Executor), 'custom_config': { ai_platform_trainer_executor.TRAINING_ARGS_KEY: ai_platform_training_args } }) trainer = Trainer(**trainer_args) # TODO(step 6): Uncomment here to add Trainer to the pipeline. # components.append(trainer) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # TODO(step 6): Uncomment here to add ResolverNode to the pipeline. # components.append(model_resolver) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key='tips')], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec( thresholds={ 'binary_accuracy': tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': eval_accuracy_threshold}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ]) model_analyzer = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # TODO(step 6): Uncomment here to add Evaluator to the pipeline. # components.append(model_analyzer) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher_args = { 'model': trainer.outputs['model'], 'model_blessing': model_analyzer.outputs['blessing'], 'push_destination': pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir)), } if ai_platform_serving_args is not None: pusher_args.update({ 'custom_executor_spec': executor_spec.ExecutorClassSpec( ai_platform_pusher_executor.Executor), 'custom_config': { ai_platform_pusher_executor.SERVING_ARGS_KEY: ai_platform_serving_args }, }) pusher = Pusher(**pusher_args) # pylint: disable=unused-variable # TODO(step 6): Uncomment here to add Pusher to the pipeline. # components.append(pusher) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=True, metadata_connection_config=metadata_connection_config, beam_pipeline_args=beam_pipeline_args, )
def _create_pipeline(pipeline_root: str, csv_input_location: str, taxi_module_file: tfx.dsl.experimental.RuntimeParameter, push_destination: tfx.dsl.experimental.RuntimeParameter, enable_cache: bool): """Creates a simple Kubeflow-based Chicago Taxi TFX pipeline. Args: pipeline_root: The root of the pipeline output. csv_input_location: The location of the input data directory. taxi_module_file: The location of the module file for Transform/Trainer. enable_cache: Whether to enable cache or not. Returns: A logical TFX pipeline.Pipeline object. """ example_gen = tfx.components.CsvExampleGen(input_base=csv_input_location) statistics_gen = tfx.components.StatisticsGen( examples=example_gen.outputs['examples']) schema_gen = tfx.components.SchemaGen( statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False, ) example_validator = tfx.components.ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema'], ) transform = tfx.components.Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=taxi_module_file, ) trainer = tfx.components.Trainer( module_file=taxi_module_file, examples=transform.outputs['transformed_examples'], schema=schema_gen.outputs['schema'], transform_graph=transform.outputs['transform_graph'], train_args=tfx.proto.TrainArgs(num_steps=10), eval_args=tfx.proto.EvalArgs(num_steps=5), ) # Set the TFMA config for Model Evaluation and Validation. eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec(signature_name='serving_default', label_key='tips_xf', preprocessing_function_names=['transform_features']) ], metrics_specs=[ tfma.MetricsSpec( # The metrics added here are in addition to those saved with the # model (assuming either a keras model or EvalSavedModel is used). # Any metrics added into the saved model (for example using # model.compile(..., metrics=[...]), etc) will be computed # automatically. metrics=[tfma.MetricConfig(class_name='ExampleCount')], # To add validation thresholds for metrics saved with the model, # add them keyed by metric name to the thresholds map. thresholds={ 'binary_accuracy': tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.5}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ], slicing_specs=[ # An empty slice spec means the overall slice, i.e. the whole dataset. tfma.SlicingSpec(), # Data can be sliced along a feature column. In this case, data is # sliced along feature column trip_start_hour. tfma.SlicingSpec(feature_keys=['trip_start_hour']) ]) evaluator = tfx.components.Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], eval_config=eval_config, ) pusher = tfx.components.Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=push_destination, ) return tfx.dsl.Pipeline( pipeline_name='parameterized_tfx_oss', pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, evaluator, pusher ], enable_cache=enable_cache, )
def create_pipeline( pipeline_name: str, pipeline_root: str, data_path: str, # TODO(step 7): (Optional) Uncomment here to use BigQuery as a data source. # query: str, preprocessing_fn: str, run_fn: str, train_args: tfx.proto.TrainArgs, eval_args: tfx.proto.EvalArgs, eval_accuracy_threshold: float, serving_model_dir: str, schema_path: Optional[str] = None, metadata_connection_config: Optional[ metadata_store_pb2.ConnectionConfig] = None, beam_pipeline_args: Optional[List[str]] = None, ai_platform_training_args: Optional[Dict[str, str]] = None, ai_platform_serving_args: Optional[Dict[str, Any]] = None, ) -> tfx.dsl.Pipeline: """Implements the chicago taxi pipeline with TFX.""" components = [] # Brings data into the pipeline or otherwise joins/converts training data. example_gen = tfx.components.CsvExampleGen(input_base=data_path) # TODO(step 7): (Optional) Uncomment here to use BigQuery as a data source. # example_gen = tfx.extensions.google_cloud_big_query.BigQueryExampleGen( # query=query) components.append(example_gen) # Computes statistics over data for visualization and example validation. statistics_gen = tfx.components.StatisticsGen( examples=example_gen.outputs['examples']) # TODO(step 5): Uncomment here to add StatisticsGen to the pipeline. # components.append(statistics_gen) if schema_path is None: # Generates schema based on statistics files. schema_gen = tfx.components.SchemaGen( statistics=statistics_gen.outputs['statistics']) # TODO(step 5): Uncomment here to add SchemaGen to the pipeline. # components.append(schema_gen) else: # Import user provided schema into the pipeline. schema_gen = tfx.components.ImportSchemaGen(schema_file=schema_path) # TODO(step 5): (Optional) Uncomment here to add ImportSchemaGen to the # pipeline. # components.append(schema_gen) # Performs anomaly detection based on statistics and data schema. example_validator = tfx.components.ExampleValidator( # pylint: disable=unused-variable statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # TODO(step 5): (Optional) Uncomment here to add ExampleValidator to the # pipeline. # components.append(example_validator) # Performs transformations and feature engineering in training and serving. transform = tfx.components.Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], preprocessing_fn=preprocessing_fn) # TODO(step 6): Uncomment here to add Transform to the pipeline. # components.append(transform) # Uses user-provided Python function that implements a model. trainer_args = { 'run_fn': run_fn, 'examples': transform.outputs['transformed_examples'], 'schema': schema_gen.outputs['schema'], 'transform_graph': transform.outputs['transform_graph'], 'train_args': train_args, 'eval_args': eval_args, } if ai_platform_training_args is not None: trainer_args['custom_config'] = { tfx.extensions.google_cloud_ai_platform.TRAINING_ARGS_KEY: ai_platform_training_args, } trainer = tfx.extensions.google_cloud_ai_platform.Trainer(**trainer_args) else: trainer = tfx.components.Trainer(**trainer_args) # TODO(step 6): Uncomment here to add Trainer to the pipeline. # components.append(trainer) # Get the latest blessed model for model validation. model_resolver = tfx.dsl.Resolver( strategy_class=tfx.dsl.experimental.LatestBlessedModelStrategy, model=tfx.dsl.Channel(type=tfx.types.standard_artifacts.Model), model_blessing=tfx.dsl.Channel( type=tfx.types.standard_artifacts.ModelBlessing)).with_id( 'latest_blessed_model_resolver') # TODO(step 6): Uncomment here to add Resolver to the pipeline. # components.append(model_resolver) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec( signature_name='serving_default', label_key='tips_xf', preprocessing_function_names=['transform_features']) ], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='BinaryAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': eval_accuracy_threshold}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10}))) ]) ]) evaluator = tfx.components.Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # TODO(step 6): Uncomment here to add Evaluator to the pipeline. # components.append(evaluator) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher_args = { 'model': trainer.outputs['model'], 'model_blessing': evaluator.outputs['blessing'], } if ai_platform_serving_args is not None: pusher_args['custom_config'] = { tfx.extensions.google_cloud_ai_platform.experimental .PUSHER_SERVING_ARGS_KEY: ai_platform_serving_args } pusher = tfx.extensions.google_cloud_ai_platform.Pusher(**pusher_args) # pylint: disable=unused-variable else: pusher_args['push_destination'] = tfx.proto.PushDestination( filesystem=tfx.proto.PushDestination.Filesystem( base_directory=serving_model_dir)) pusher = tfx.components.Pusher(**pusher_args) # pylint: disable=unused-variable # TODO(step 6): Uncomment here to add Pusher to the pipeline. # components.append(pusher) return tfx.dsl.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, # Change this value to control caching of execution results. Default value # is `False`. # enable_cache=True, metadata_connection_config=metadata_connection_config, beam_pipeline_args=beam_pipeline_args, )
def _create_pipeline( pipeline_name: Text, pipeline_root: Text ) -> pipeline.Pipeline: """Implements the Iris flowers pipeline with TFX.""" examples = external_input(_data_root_param) # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input=examples) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. infer_schema = SchemaGen( statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True ) # Performs anomaly detection based on statistics and data schema. validate_stats = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=infer_schema.outputs['schema'] ) # Performs transformations and feature engineering in training and serving. transform = Transform( examples=example_gen.outputs['examples'], schema=infer_schema.outputs['schema'], module_file=_module_file_param ) # Uses user-provided Python function that implements a model using Keras. trainer = Trainer( module_file=_module_file_param, custom_executor_spec=executor_spec.ExecutorClassSpec(GenericExecutor), examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=infer_schema.outputs['schema'], train_args=trainer_pb2.TrainArgs(num_steps=100), eval_args=trainer_pb2.EvalArgs(num_steps=50) ) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing) ) # Uses TFMA to compute an evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). # Note: to compile this successfully you'll need TFMA at >= 0.21.5 eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec(name='candidate', label_key='variety'), tfma.ModelSpec( name='baseline', label_key='variety', is_baseline=True ) ], slicing_specs=[ tfma.SlicingSpec(), # Data can be sliced along a feature column. Required by TFMA visualization. tfma.SlicingSpec(feature_keys=['sepal_length'])], metrics_specs=[ tfma.MetricsSpec( metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.9} ), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10} ) ) ) ] ) ] ) # Uses TFMA to compute a evaluation statistics over features of a model. model_analyzer = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config ) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher( model=trainer.outputs['model'], model_blessing=model_analyzer.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=os.path. join(str(pipeline.ROOT_PARAMETER), 'model_serving') ) ) ) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, infer_schema, validate_stats, transform, trainer, model_resolver, model_analyzer, pusher ], enable_cache=True, )
def create_e2e_components( pipeline_root: Text, csv_input_location: Text, transform_module: Text, trainer_module: Text, ) -> List[BaseComponent]: """Creates components for a simple Chicago Taxi TFX pipeline for testing. Args: pipeline_root: The root of the pipeline output. csv_input_location: The location of the input data directory. transform_module: The location of the transform module file. trainer_module: The location of the trainer module file. Returns: A list of TFX components that constitutes an end-to-end test pipeline. """ example_gen = CsvExampleGen(input_base=csv_input_location) statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False) example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) transform = Transform(examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=transform_module) latest_model_resolver = ResolverNode( instance_name='latest_model_resolver', resolver_class=latest_artifacts_resolver.LatestArtifactsResolver, latest_model=Channel(type=Model)) trainer = Trainer( transformed_examples=transform.outputs['transformed_examples'], schema=schema_gen.outputs['schema'], base_model=latest_model_resolver.outputs['latest_model'], transform_graph=transform.outputs['transform_graph'], train_args=trainer_pb2.TrainArgs(num_steps=10), eval_args=trainer_pb2.EvalArgs(num_steps=5), module_file=trainer_module, ) # Set the TFMA config for Model Evaluation and Validation. eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], metrics_specs=[ tfma.MetricsSpec( metrics=[tfma.MetricConfig(class_name='ExampleCount')], thresholds={ 'accuracy': tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.5}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['trip_start_hour']) ]) evaluator = Evaluator(examples=example_gen.outputs['examples'], model=trainer.outputs['model'], eval_config=eval_config) infra_validator = InfraValidator( model=trainer.outputs['model'], examples=example_gen.outputs['examples'], serving_spec=infra_validator_pb2.ServingSpec( tensorflow_serving=infra_validator_pb2.TensorFlowServing( tags=['latest']), kubernetes=infra_validator_pb2.KubernetesConfig()), request_spec=infra_validator_pb2.RequestSpec( tensorflow_serving=infra_validator_pb2. TensorFlowServingRequestSpec())) pusher = Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=os.path.join(pipeline_root, 'model_serving')))) return [ example_gen, statistics_gen, schema_gen, example_validator, transform, latest_model_resolver, trainer, evaluator, infra_validator, pusher, ]
def _create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, serving_model_dir: Text, metadata_path: Text, direct_num_workers: int) -> pipeline.Pipeline: """Implements the Iris flowers pipeline with TFX.""" examples = external_input(data_root) # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input=examples) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Uses user-provided Python function that implements a model using TF-Learn. trainer = Trainer( module_file=module_file, # GenericExecutor uses `run_fn`, while default estimator based executor # uses `trainer_fn` instead. custom_executor_spec=executor_spec.ExecutorClassSpec(GenericExecutor), examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], train_args=trainer_pb2.TrainArgs(num_steps=10000), eval_args=trainer_pb2.EvalArgs(num_steps=5000)) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute an evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec( thresholds={ 'sparse_categorical_accuracy': tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.9}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher(model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, schema_gen, example_validator, trainer, model_resolver, evaluator, pusher, ], enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), # TODO(b/142684737): The multi-processing API might change. beam_pipeline_args=['--direct_num_workers=%d' % direct_num_workers], )
def _create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, accuracy_threshold: float, serving_model_dir: Text, metadata_path: Text, beam_pipeline_args: List[Text], make_warmup: bool) -> pipeline.Pipeline: """Implements the penguin pipeline with TFX.""" # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input_base=data_root) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen( statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=module_file) # Uses user-provided Python function that trains a model using TF-Learn. trainer = Trainer( module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], train_args=trainer_pb2.TrainArgs(num_steps=2000), eval_args=trainer_pb2.EvalArgs(num_steps=5)) # Get the latest blessed model for model validation. model_resolver = ResolverNode( resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel( type=ModelBlessing)).with_id('latest_blessed_model_resolver') # Uses TFMA to compute evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key='species')], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': accuracy_threshold}), # Change threshold will be ignored if there is no # baseline model resolved from MLMD (first run). change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10}))) ]) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) # Performs infra validation of a candidate model to prevent unservable model # from being pushed. This config will launch a model server of the latest # TensorFlow Serving image in a local docker engine. infra_validator = InfraValidator( model=trainer.outputs['model'], examples=example_gen.outputs['examples'], serving_spec=infra_validator_pb2.ServingSpec( tensorflow_serving=infra_validator_pb2.TensorFlowServing( tags=['latest']), local_docker=infra_validator_pb2.LocalDockerConfig()), request_spec=infra_validator_pb2.RequestSpec( tensorflow_serving=infra_validator_pb2.TensorFlowServingRequestSpec(), # If this flag is set, InfraValidator will produce a model with # warmup requests (in its outputs['blessing']). make_warmup=make_warmup)) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. if make_warmup: # If InfraValidator.request_spec.make_warmup = True, its output contains # a model so that Pusher can push 'infra_blessing' input instead of # 'model' input. pusher = Pusher( model_blessing=evaluator.outputs['blessing'], infra_blessing=infra_validator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) else: # Otherwise, 'infra_blessing' does not contain a model and is used as a # conditional checker just like 'model_blessing' does. This is the typical # use case. pusher = Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], infra_blessing=infra_validator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, infra_validator, pusher, ], enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), beam_pipeline_args=beam_pipeline_args)
def init_components( data_dir, module_file, training_steps=TRAIN_STEPS, eval_steps=EVAL_STEPS, serving_model_dir=None, vertex_training_custom_config=None, vertex_serving_args=None, ): if serving_model_dir and vertex_serving_args: raise NotImplementedError( "Can't set vertex_serving_args and serving_model_dir at " "the same time. Choose one deployment option." ) output = example_gen_pb2.Output( split_config=example_gen_pb2.SplitConfig( splits=[ example_gen_pb2.SplitConfig.Split(name="train", hash_buckets=9), example_gen_pb2.SplitConfig.Split(name="eval", hash_buckets=1), ] ) ) example_gen = tfx.components.CsvExampleGen( input_base=os.path.join(os.getcwd(), data_dir), output_config=output ) statistics_gen = tfx.components.StatisticsGen( examples=example_gen.outputs["examples"] ) schema_gen = tfx.components.SchemaGen( statistics=statistics_gen.outputs["statistics"], infer_feature_shape=False, ) example_validator = tfx.components.ExampleValidator( statistics=statistics_gen.outputs["statistics"], schema=schema_gen.outputs["schema"], ) transform = tfx.components.Transform( examples=example_gen.outputs["examples"], schema=schema_gen.outputs["schema"], module_file=module_file, ) training_kwargs = { "module_file": module_file, "examples": transform.outputs["transformed_examples"], "schema": schema_gen.outputs["schema"], "transform_graph": transform.outputs["transform_graph"], "train_args": trainer_pb2.TrainArgs(num_steps=training_steps), "eval_args": trainer_pb2.EvalArgs(num_steps=eval_steps), } if vertex_training_custom_config: training_kwargs.update({"custom_config": vertex_training_custom_config}) trainer = tfx.extensions.google_cloud_ai_platform.Trainer(**training_kwargs) else: trainer = tfx.components.Trainer(**training_kwargs) model_resolver = tfx.dsl.Resolver( strategy_class=tfx.dsl.experimental.LatestBlessedModelStrategy, model=tfx.dsl.Channel(type=tfx.types.standard_artifacts.Model), model_blessing=tfx.dsl.Channel(type=tfx.types.standard_artifacts.ModelBlessing), ) eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec( signature_name="serving_default", label_key="consumer_disputed", # preprocessing_function_names=["transform_features"], ) ], slicing_specs=[tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=["product"])], metrics_specs=[ tfma.MetricsSpec( metrics=[ tfma.MetricConfig( class_name="BinaryAccuracy", threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={"value": 0.65} ), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={"value": -1e-10}, ), ), ), tfma.MetricConfig(class_name="Precision"), tfma.MetricConfig(class_name="Recall"), tfma.MetricConfig(class_name="ExampleCount"), tfma.MetricConfig(class_name="AUC"), ], ) ], ) evaluator = tfx.components.Evaluator( examples=example_gen.outputs["examples"], model=trainer.outputs["model"], baseline_model=model_resolver.outputs["model"], eval_config=eval_config, ) if vertex_serving_args: pusher = tfx.extensions.google_cloud_ai_platform.Pusher( model=trainer.outputs["model"], model_blessing=evaluator.outputs["blessing"], custom_config=vertex_serving_args, ) elif serving_model_dir: pusher = tfx.components.Pusher( model=trainer.outputs["model"], model_blessing=evaluator.outputs["blessing"], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir ) ), ) else: raise NotImplementedError( "Provide ai_platform_serving_args or serving_model_dir." ) components = [ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher, ] return components
def create_pipeline_components( pipeline_root: Text, transform_module: Text, trainer_module: Text, bigquery_query: Text = '', csv_input_location: Text = '', ) -> List[base_node.BaseNode]: """Creates components for a simple Chicago Taxi TFX pipeline for testing. Args: pipeline_root: The root of the pipeline output. transform_module: The location of the transform module file. trainer_module: The location of the trainer module file. bigquery_query: The query to get input data from BigQuery. If not empty, BigQueryExampleGen will be used. csv_input_location: The location of the input data directory. Returns: A list of TFX components that constitutes an end-to-end test pipeline. """ if bool(bigquery_query) == bool(csv_input_location): raise ValueError( 'Exactly one example gen is expected. ', 'Please provide either bigquery_query or csv_input_location.') if bigquery_query: example_gen = big_query_example_gen_component.BigQueryExampleGen( query=bigquery_query) else: examples = dsl_utils.external_input(csv_input_location) example_gen = components.CsvExampleGen(input=examples) statistics_gen = components.StatisticsGen( examples=example_gen.outputs['examples']) schema_gen = components.SchemaGen( statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False) example_validator = components.ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) transform = components.Transform(examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file=transform_module) latest_model_resolver = resolver.Resolver( instance_name='latest_model_resolver', strategy_class=latest_artifacts_resolver.LatestArtifactsResolver, model=channel.Channel(type=standard_artifacts.Model)) trainer = components.Trainer( transformed_examples=transform.outputs['transformed_examples'], schema=schema_gen.outputs['schema'], base_model=latest_model_resolver.outputs['model'], transform_graph=transform.outputs['transform_graph'], train_args=trainer_pb2.TrainArgs(num_steps=10), eval_args=trainer_pb2.EvalArgs(num_steps=5), module_file=trainer_module, ) # Get the latest blessed model for model validation. model_resolver = resolver.Resolver( instance_name='latest_blessed_model_resolver', strategy_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=channel.Channel(type=standard_artifacts.Model), model_blessing=channel.Channel(type=standard_artifacts.ModelBlessing)) # Set the TFMA config for Model Evaluation and Validation. eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], metrics_specs=[ tfma.MetricsSpec( metrics=[tfma.MetricConfig(class_name='ExampleCount')], thresholds={ 'binary_accuracy': tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.5}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['trip_start_hour']) ]) evaluator = components.Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) pusher = components.Pusher( model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=os.path.join(pipeline_root, 'model_serving')))) return [ example_gen, statistics_gen, schema_gen, example_validator, transform, latest_model_resolver, trainer, model_resolver, evaluator, pusher ]
def _create_pipeline( pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, accuracy_threshold: float, serving_model_dir: Text, metadata_path: Text, enable_tuning: bool, examplegen_input_config: Optional[example_gen_pb2.Input], examplegen_range_config: Optional[range_config_pb2.RangeConfig], resolver_range_config: Optional[range_config_pb2.RangeConfig], beam_pipeline_args: List[Text], ) -> pipeline.Pipeline: """Implements the penguin pipeline with TFX. Args: pipeline_name: name of the TFX pipeline being created. pipeline_root: root directory of the pipeline. data_root: directory containing the penguin data. module_file: path to files used in Trainer and Transform components. accuracy_threshold: minimum accuracy to push the model. serving_model_dir: filepath to write pipeline SavedModel to. metadata_path: path to local pipeline ML Metadata store. enable_tuning: If True, the hyperparameter tuning through KerasTuner is enabled. examplegen_input_config: ExampleGen's input_config. examplegen_range_config: ExampleGen's range_config. resolver_range_config: SpansResolver's range_config. Specify this will enable SpansResolver to get a window of ExampleGen's output Spans for transform and training. beam_pipeline_args: list of beam pipeline options for LocalDAGRunner. Please refer to https://beam.apache.org/documentation/runners/direct/. Returns: A TFX pipeline object. """ # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input_base=data_root, input_config=examplegen_input_config, range_config=examplegen_range_config) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=True) # Performs anomaly detection based on statistics and data schema. example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) # Gets multiple Spans for transform and training. if resolver_range_config: examples_resolver = resolver.Resolver( instance_name='span_resolver', strategy_class=spans_resolver.SpansResolver, config={'range_config': resolver_range_config}, examples=Channel(type=Examples, producer_component_id=example_gen.id)) # Performs transformations and feature engineering in training and serving. transform = Transform( examples=(examples_resolver.outputs['examples'] if resolver_range_config else example_gen.outputs['examples']), schema=schema_gen.outputs['schema'], module_file=module_file) # Tunes the hyperparameters for model training based on user-provided Python # function. Note that once the hyperparameters are tuned, you can drop the # Tuner component from pipeline and feed Trainer with tuned hyperparameters. if enable_tuning: tuner = Tuner(module_file=module_file, examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], train_args=trainer_pb2.TrainArgs(num_steps=20), eval_args=trainer_pb2.EvalArgs(num_steps=5)) # Uses user-provided Python function that trains a model. trainer = Trainer( module_file=module_file, custom_executor_spec=executor_spec.ExecutorClassSpec(GenericExecutor), examples=transform.outputs['transformed_examples'], transform_graph=transform.outputs['transform_graph'], schema=schema_gen.outputs['schema'], # If Tuner is in the pipeline, Trainer can take Tuner's output # best_hyperparameters artifact as input and utilize it in the user module # code. # # If there isn't Tuner in the pipeline, either use ImporterNode to import # a previous Tuner's output to feed to Trainer, or directly use the tuned # hyperparameters in user module code and set hyperparameters to None # here. # # Example of ImporterNode, # hparams_importer = ImporterNode( # instance_name='import_hparams', # source_uri='path/to/best_hyperparameters.txt', # artifact_type=HyperParameters) # ... # hyperparameters = hparams_importer.outputs['result'], hyperparameters=(tuner.outputs['best_hyperparameters'] if enable_tuning else None), train_args=trainer_pb2.TrainArgs(num_steps=100), eval_args=trainer_pb2.EvalArgs(num_steps=5)) # Get the latest blessed model for model validation. model_resolver = resolver.Resolver( instance_name='latest_blessed_model_resolver', strategy_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(label_key='species')], slicing_specs=[tfma.SlicingSpec()], metrics_specs=[ tfma.MetricsSpec(metrics=[ tfma.MetricConfig( class_name='SparseCategoricalAccuracy', threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': accuracy_threshold}), # Change threshold will be ignored if there is no # baseline model resolved from MLMD (first run). change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10}))) ]) ]) evaluator = Evaluator(examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher(model=trainer.outputs['model'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) components = [ example_gen, statistics_gen, schema_gen, example_validator, transform, trainer, model_resolver, evaluator, pusher, ] if resolver_range_config: components.append(examples_resolver) if enable_tuning: components.append(tuner) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=components, enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), beam_pipeline_args=beam_pipeline_args)
def create_pipeline(pipeline_name: Text, pipeline_root: Text): """ Args: pipeline_name: pipeline_root: num_epochs: batch_size: learning_rate: hidden_units: Returns: pipeline: """ # Get train split BigQuery query. train_sql_query = bq_datasource_utils.get_training_source_query( config.GOOGLE_CLOUD_PROJECT_ID, config.GOOGLE_CLOUD_REGION, config.DATASET_DISPLAY_NAME, ml_use="UNASSIGNED", limit=int(config.TRAIN_LIMIT), ) # Configure train and eval splits for model training and evaluation. train_output_config = example_gen_pb2.Output( split_config=example_gen_pb2.SplitConfig( splits=[ example_gen_pb2.SplitConfig.Split( name="train", hash_buckets=int(config.NUM_TRAIN_SPLITS) ), example_gen_pb2.SplitConfig.Split( name="eval", hash_buckets=int(config.NUM_EVAL_SPLITS) ), ] ) ) # Generate train split examples. train_example_gen = BigQueryExampleGen( query=train_sql_query, output_config=train_output_config, ).with_id("TrainDataGen") # Get test source query. test_sql_query = bq_datasource_utils.get_training_source_query( config.GOOGLE_CLOUD_PROJECT_ID, config.GOOGLE_CLOUD_REGION, config.DATASET_DISPLAY_NAME, ml_use="TEST", limit=int(config.TEST_LIMIT), ) # Configure test split for model evaluation. test_output_config = example_gen_pb2.Output( split_config=example_gen_pb2.SplitConfig( splits=[ example_gen_pb2.SplitConfig.Split(name="test", hash_buckets=1), ] ) ) # Test example generation. test_example_gen = BigQueryExampleGen( query=test_sql_query, output_config=test_output_config, ).with_id("TestDataGen") # Schema importer. schema_importer = Importer( source_uri=SCHEMA_DIR, artifact_type=Schema, ).with_id("SchemaImporter") # schema_importer = ImportSchemaGen(schema_file=SCHEMA_FILE).with_id("SchemaImporter") # Generate dataset statistics. statistics_gen = StatisticsGen( examples=train_example_gen.outputs["examples"] ).with_id("StatisticsGen") # Generate data schema file. # schema_gen = SchemaGen( # statistics=statistics_gen.outputs["statistics"], infer_feature_shape=True # ) # Example validation. example_validator = ExampleValidator( statistics=statistics_gen.outputs["statistics"], schema=schema_importer.outputs["result"], ).with_id("ExampleValidator") # Data transformation. transform = Transform( examples=train_example_gen.outputs["examples"], schema=schema_importer.outputs["result"], module_file=TRANSFORM_MODULE_FILE, # This is a temporary workaround to run on Dataflow. force_tf_compat_v1=config.BEAM_RUNNER == "DataflowRunner", splits_config=transform_pb2.SplitsConfig( analyze=["train"], transform=["train", "eval"] ), ).with_id("Tranform") # Add dependency from example_validator to transform. transform.add_upstream_node(example_validator) # Train model on Vertex AI. trainer = VertexTrainer( module_file=TRAIN_MODULE_FILE, examples=transform.outputs["transformed_examples"], transform_graph=transform.outputs["transform_graph"], custom_config=config.VERTEX_TRAINING_CONFIG, ).with_id("ModelTrainer") # Get the latest blessed model (baseline) for model validation. baseline_model_resolver = Resolver( strategy_class=LatestBlessedModelStrategy, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing), ).with_id("BaselineModelResolver") # Prepare evaluation config. eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec( signature_name="serving_tf_example", label_key=features.TARGET_FEATURE_NAME, prediction_key="probabilities", ) ], slicing_specs=[ tfma.SlicingSpec(), ], metrics_specs=[ tfma.MetricsSpec( metrics=[ tfma.MetricConfig(class_name="ExampleCount"), tfma.MetricConfig( class_name="BinaryAccuracy", threshold=tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={"value": float(config.ACCURACY_THRESHOLD)} ), # Change threshold will be ignored if there is no # baseline model resolved from MLMD (first run). change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={"value": -1e-10}, ), ), ), ] ) ], ) # Model evaluation. evaluator = Evaluator( examples=test_example_gen.outputs["examples"], example_splits=["test"], model=trainer.outputs["model"], baseline_model=baseline_model_resolver.outputs["model"], eval_config=eval_config, schema=schema_importer.outputs["result"], ).with_id("ModelEvaluator") exported_model_location = os.path.join( config.MODEL_REGISTRY_URI, config.MODEL_DISPLAY_NAME ) push_destination = pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=exported_model_location ) ) # Push custom model to model registry. pusher = Pusher( model=trainer.outputs["model"], model_blessing=evaluator.outputs["blessing"], push_destination=push_destination, ).with_id("ModelPusher") pipeline_components = [ train_example_gen, test_example_gen, schema_importer, statistics_gen, # schema_gen, example_validator, transform, trainer, baseline_model_resolver, evaluator, pusher, ] logging.info( "Pipeline components: %s", ", ".join([component.id for component in pipeline_components]), ) beam_pipeline_args = config.BEAM_DIRECT_PIPELINE_ARGS if config.BEAM_RUNNER == "DataflowRunner": beam_pipeline_args = config.BEAM_DATAFLOW_PIPELINE_ARGS logging.info("Beam pipeline args: %s", beam_pipeline_args) return Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=pipeline_components, beam_pipeline_args=beam_pipeline_args, enable_cache=int(config.ENABLE_CACHE), )
def _create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root: Text, module_file: Text, serving_model_dir: Text, metadata_path: Text, direct_num_workers: int) -> pipeline.Pipeline: """Implements the chicago taxi pipeline with TFX.""" examples = external_input(data_root) # Brings data into the pipeline or otherwise joins/converts training data. example_gen = CsvExampleGen(input=examples) # Computes statistics over data for visualization and example validation. statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) # Generates schema based on statistics files. infer_schema = SchemaGen(statistics=statistics_gen.outputs['statistics'], infer_feature_shape=False) # Performs anomaly detection based on statistics and data schema. validate_stats = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=infer_schema.outputs['schema']) # Performs transformations and feature engineering in training and serving. transform = Transform(examples=example_gen.outputs['examples'], schema=infer_schema.outputs['schema'], module_file=module_file) # Get the latest model so that we can warm start from the model. latest_model_resolver = ResolverNode( instance_name='latest_model_resolver', resolver_class=latest_artifacts_resolver.LatestArtifactsResolver, latest_model=Channel(type=Model)) # Uses user-provided Python function that implements a model using TF-Learn. trainer = Trainer( module_file=module_file, transformed_examples=transform.outputs['transformed_examples'], schema=infer_schema.outputs['schema'], base_model=latest_model_resolver.outputs['latest_model'], transform_graph=transform.outputs['transform_graph'], train_args=trainer_pb2.TrainArgs(num_steps=10000), eval_args=trainer_pb2.EvalArgs(num_steps=5000)) # Get the latest blessed model for model validation. model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver. LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute a evaluation statistics over features of a model and # perform quality validation of a candidate model (compared to a baseline). eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['trip_start_hour']) ], metrics_specs=[ tfma.MetricsSpec( thresholds={ 'binary_accuracy': tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.6}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ]) model_analyzer = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], # Change threshold will be ignored if there is no baseline (first run). eval_config=eval_config) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. pusher = Pusher(model=trainer.outputs['model'], model_blessing=model_analyzer.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=serving_model_dir))) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ example_gen, statistics_gen, infer_schema, validate_stats, transform, latest_model_resolver, trainer, model_resolver, model_analyzer, pusher ], enable_cache=True, metadata_connection_config=metadata.sqlite_metadata_connection_config( metadata_path), # TODO(b/142684737): The multi-processing API might change. beam_pipeline_args=['--direct_num_workers=%d' % direct_num_workers])
def create_pipeline(pipeline_name: Text, pipeline_root: Text, data_root_uri: data_types.RuntimeParameter, train_steps: data_types.RuntimeParameter, eval_steps: data_types.RuntimeParameter, ai_platform_training_args: Dict[Text, Text], ai_platform_serving_args: Dict[Text, Text], beam_pipeline_args: List[Text], enable_cache: Optional[bool] = False) -> pipeline.Pipeline: """Trains and deploys the Covertype classifier.""" # Brings data into the pipeline and splits the data into training and eval splits examples = external_input(data_root_uri) output_config = example_gen_pb2.Output( split_config=example_gen_pb2.SplitConfig(splits=[ example_gen_pb2.SplitConfig.Split(name='train', hash_buckets=4), example_gen_pb2.SplitConfig.Split(name='eval', hash_buckets=1) ])) generate_examples = CsvExampleGen(input=examples) # Computes statistics over data for visualization and example validation. generate_statistics = StatisticsGen(examples=generate_examples.outputs.examples) # Import a user-provided schema import_schema = ImporterNode( instance_name='import_user_schema', source_uri=SCHEMA_FOLDER, artifact_type=Schema) # Generates schema based on statistics files.Even though, we use user-provided schema # we still want to generate the schema of the newest data for tracking and comparison infer_schema = SchemaGen(statistics=generate_statistics.outputs.statistics) # Performs anomaly detection based on statistics and data schema. validate_stats = ExampleValidator( statistics=generate_statistics.outputs.statistics, schema=import_schema.outputs.result) # Performs transformations and feature engineering in training and serving. transform = Transform( examples=generate_examples.outputs.examples, schema=import_schema.outputs.result, module_file=TRANSFORM_MODULE_FILE) # Trains the model using a user provided trainer function. train = Trainer( custom_executor_spec=executor_spec.ExecutorClassSpec( ai_platform_trainer_executor.GenericExecutor), # custom_executor_spec=executor_spec.ExecutorClassSpec(trainer_executor.GenericExecutor), module_file=TRAIN_MODULE_FILE, transformed_examples=transform.outputs.transformed_examples, schema=import_schema.outputs.result, transform_graph=transform.outputs.transform_graph, train_args={'num_steps': train_steps}, eval_args={'num_steps': eval_steps}, custom_config={'ai_platform_training_args': ai_platform_training_args}) # Get the latest blessed model for model validation. resolve = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) # Uses TFMA to compute a evaluation statistics over features of a model. accuracy_threshold = tfma.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.5}, upper_bound={'value': 0.99}), change_threshold=tfma.GenericChangeThreshold( absolute={'value': 0.0001}, direction=tfma.MetricDirection.HIGHER_IS_BETTER), ) metrics_specs = tfma.MetricsSpec( metrics = [ tfma.MetricConfig(class_name='SparseCategoricalAccuracy', threshold=accuracy_threshold), tfma.MetricConfig(class_name='ExampleCount')]) eval_config = tfma.EvalConfig( model_specs=[ tfma.ModelSpec(label_key='Cover_Type') ], metrics_specs=[metrics_specs], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['Wilderness_Area']) ] ) analyze = Evaluator( examples=generate_examples.outputs.examples, model=train.outputs.model, baseline_model=resolve.outputs.model, eval_config=eval_config ) # Validate model can be loaded and queried in sand-boxed environment # mirroring production. serving_config = infra_validator_pb2.ServingSpec( tensorflow_serving=infra_validator_pb2.TensorFlowServing( tags=['latest']), local_docker=infra_validator_pb2.LocalDockerConfig(), ) validation_config = infra_validator_pb2.ValidationSpec( max_loading_time_seconds=60, num_tries=3, ) request_config = infra_validator_pb2.RequestSpec( tensorflow_serving=infra_validator_pb2.TensorFlowServingRequestSpec(), num_examples=3, ) infra_validate = InfraValidator( model=train.outputs['model'], examples=generate_examples.outputs['examples'], serving_spec=serving_config, validation_spec=validation_config, request_spec=request_config, ) # Checks whether the model passed the validation steps and pushes the model # to a file destination if check passed. deploy = Pusher( model=train.outputs['model'], model_blessing=analyze.outputs['blessing'], infra_blessing=infra_validate.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory=os.path.join( str(pipeline.ROOT_PARAMETER), 'model_serving')))) #deploy = Pusher( # custom_executor_spec=executor_spec.ExecutorClassSpec( # ai_platform_pusher_executor.Executor), # model=train.outputs.model, # model_blessing=validate.outputs.blessing, # custom_config={'ai_platform_serving_args': ai_platform_serving_args}) return pipeline.Pipeline( pipeline_name=pipeline_name, pipeline_root=pipeline_root, components=[ generate_examples, generate_statistics, import_schema, infer_schema, validate_stats, transform, train, resolve, analyze, infra_validate, deploy ], enable_cache=enable_cache, beam_pipeline_args=beam_pipeline_args )
def testTaxiPipelineNewStyleCompatibility(self): example_gen = CsvExampleGen(input_base='/tmp/fake/path') statistics_gen = StatisticsGen(examples=example_gen.outputs['examples']) self.assertIs(statistics_gen.inputs['examples'], statistics_gen.inputs['input_data']) schema_gen = SchemaGen(statistics=statistics_gen.outputs['statistics']) self.assertIs(schema_gen.inputs['statistics'], schema_gen.inputs['stats']) self.assertIs(schema_gen.outputs['schema'], schema_gen.outputs['output']) example_validator = ExampleValidator( statistics=statistics_gen.outputs['statistics'], schema=schema_gen.outputs['schema']) self.assertIs(example_validator.inputs['statistics'], example_validator.inputs['stats']) self.assertIs(example_validator.outputs['anomalies'], example_validator.outputs['output']) transform = Transform( examples=example_gen.outputs['examples'], schema=schema_gen.outputs['schema'], module_file='/tmp/fake/module/file') self.assertIs(transform.inputs['examples'], transform.inputs['input_data']) self.assertIs(transform.outputs['transform_graph'], transform.outputs['transform_output']) trainer = Trainer( module_file='/tmp/fake/module/file', transformed_examples=transform.outputs['transformed_examples'], schema=schema_gen.outputs['schema'], transform_graph=transform.outputs['transform_graph'], train_args=trainer_pb2.TrainArgs(num_steps=10000), eval_args=trainer_pb2.EvalArgs(num_steps=5000)) self.assertIs(trainer.inputs['transform_graph'], trainer.inputs['transform_output']) self.assertIs(trainer.outputs['model'], trainer.outputs['output']) model_resolver = ResolverNode( instance_name='latest_blessed_model_resolver', resolver_class=latest_blessed_model_resolver.LatestBlessedModelResolver, model=Channel(type=Model), model_blessing=Channel(type=ModelBlessing)) eval_config = tfma.EvalConfig( model_specs=[tfma.ModelSpec(signature_name='eval')], slicing_specs=[ tfma.SlicingSpec(), tfma.SlicingSpec(feature_keys=['trip_start_hour']) ], metrics_specs=[ tfma.MetricsSpec( thresholds={ 'accuracy': tfma.config.MetricThreshold( value_threshold=tfma.GenericValueThreshold( lower_bound={'value': 0.6}), change_threshold=tfma.GenericChangeThreshold( direction=tfma.MetricDirection.HIGHER_IS_BETTER, absolute={'value': -1e-10})) }) ]) evaluator = Evaluator( examples=example_gen.outputs['examples'], model=trainer.outputs['model'], baseline_model=model_resolver.outputs['model'], eval_config=eval_config) self.assertIs(evaluator.inputs['model'], evaluator.inputs['model_exports']) self.assertIs(evaluator.outputs['evaluation'], evaluator.outputs['output']) pusher = Pusher( model=trainer.outputs['output'], model_blessing=evaluator.outputs['blessing'], push_destination=pusher_pb2.PushDestination( filesystem=pusher_pb2.PushDestination.Filesystem( base_directory='/fake/serving/dir'))) self.assertIs(pusher.inputs['model'], pusher.inputs['model_export']) self.assertIs(pusher.outputs['pushed_model'], pusher.outputs['model_push'])