From cbb5675f0ba4a117eed6a30ce84433314466b466 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:39:17 -0700 Subject: [PATCH 01/58] Fix link to tfx mobile --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 4fa2d04b08..b9e5e4b3e8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -156,7 +156,7 @@ nav: - "TFX Cloud Solutions": guide/solutions.md - "Using Keras with TFX": guide/keras - "Using Non-TensorFlow Frameworks in TFX": guide/non_tf - - "Mobile & IoT: TFX for TensorFlow Lite": tutorials/tfx_for_mobile + - "Mobile & IoT: TFX for TensorFlow Lite": tutorials/tfx/tfx_for_mobile - "TFX Pipelines": - "Understanding TFX pipelines": guide/understanding_tfx_pipelines From 300cdb167794311a385e9c870f02d7d3344ac626 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:47:40 -0700 Subject: [PATCH 02/58] Fix link to Recommenders ranking TFX --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index b9e5e4b3e8..15142fff82 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -130,7 +130,7 @@ nav: - LLM finetuning and conversion: tutorials/tfx/gpt2_finetuning_and_conversion - Custom component tutorial: tutorials/tfx/python_function_component - Recommenders with TFX: tutorials/tfx/recommenders - - Ranking with TFX: mmenders/examples/ranking_tfx + - Ranking with TFX: https://www.tensorflow.org/recommenders/examples/ranking_tfx - Airflow tutorial: tutorials/tfx/airflow_workshop - Neural Structured Learning in TFX: tutorials/tfx/neural_structured_learning - Data Validation: From c5f3563215d0409b012631b5ab1e51deb154543c Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:55:29 -0700 Subject: [PATCH 03/58] Fix github repo name --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 15142fff82..9910392ec7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: TFX -repo_name: "Tensorflow TFX" +repo_name: "TFX" repo_url: https://github.com/tensorflow/tfx theme: From a3c9d3eaaaf4175d86860a526a25a5699f9a840e Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:09:40 -0700 Subject: [PATCH 04/58] Fix internal link to "Run the pipeline in Dataflow" --- docs/tutorials/transform/data_preprocessing_with_cloud.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index 88d6ef9428..c65f108756 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -116,7 +116,7 @@ notebook name. ## Implement the Apache Beam pipeline This section and the next section -[Run the pipeline in Dataflow](#run-the-pipeline-in-dataflow){: track-type="solution" track-name="internalLink" track-metadata-position="body" } +[Run the pipeline in Dataflow](#run-the-pipeline-in-dataflow) provide an overview and context for Notebook 1. The notebook provides a practical example to describe how to use the `tf.Transform` library to preprocess data. This example uses the Natality dataset, which is used to @@ -716,7 +716,7 @@ The following artifacts are also produced, as shown in the next section: - `transformed_metadata`: a directory that contains the `schema.json` file that describes the schema of the transformed data. -## Run the pipeline in Dataflow{:#run_the_pipeline_in_dataflow} +## Run the pipeline in Dataflow After you define the `tf.Transform` pipeline, you run the pipeline using Dataflow. The following diagram, figure 4, shows the From 60a3dad7633b4c86e87648cf5cd34f3d66b935aa Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:32:31 -0700 Subject: [PATCH 05/58] Fix code listings --- .../data_preprocessing_with_cloud.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index c65f108756..92672c4431 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -139,7 +139,7 @@ table in BigQuery. The last part of the output is the following: - ```none{:.devsite-disable-click-to-copy} + ``` { .yaml .no-copy } Successfully installed ... ``` @@ -149,7 +149,7 @@ table in BigQuery. 1. Execute the second cell to run the `pip install tensorflow-transform `command. The last part of the output is the following: - ```none{:.devsite-disable-click-to-copy} + ``` { .yaml .no-copy } Successfully installed ... Note: you may need to restart the kernel to use updated packages. ``` @@ -188,7 +188,7 @@ the pipeline. The overall pipeline steps are as follows: The following example shows the Python code for the overall pipeline. The sections that follow provide explanations and code listings for each step. -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def run_transformation_pipeline(args): pipeline_options = beam.pipeline.PipelineOptions(flags=[], **args) @@ -241,7 +241,7 @@ pass a `step` value of `train` or `eval`. The BigQuery source query is constructed using the `get_source_query` function, as shown in the following example: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def read_from_bq(pipeline, step, data_size): source_query = get_source_query(step, data_size) @@ -270,7 +270,7 @@ In addition, to use the `tf.Transform` library to analyze and transform the The `raw_metadata` object is created using the `create_raw_metadata` function, as follows: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } CATEGORICAL_FEATURE_NAMES = ['is_male', 'mother_race'] NUMERIC_FEATURE_NAMES = ['mother_age', 'plurality', 'gestation_weeks'] TARGET_FEATURE_NAME = 'weight_pounds' @@ -393,7 +393,7 @@ The following code shows the implementation of the `preprocess_fn` function, using the `tf.Transform` full-pass transformation APIs (prefixed with `tft.`), and TensorFlow (prefixed with `tf.`) instance-level operations: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def preprocess_fn(input_features): output_features = {} @@ -508,7 +508,7 @@ the `raw_dataset` object as input, applies the `preprocess_fn` function, and it produces the `transformed_dataset` object and the `transform_fn` graph. The following code illustrates this processing: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def analyze_and_transform(raw_dataset, step): transformed_dataset, transform_fn = ( @@ -619,7 +619,7 @@ converted into tensors when they are fed to the model for training. The following code writes the transformed dataset to TFRecord files in the specified location: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def write_tfrecords(transformed_dataset, location, step): from tfx_bsl.coders import example_coder @@ -645,7 +645,7 @@ and passing a value of `eval` for the `step` parameter. Then, you use the following code to transform the raw evaluation dataset (`raw_dataset`) to the expected transformed format (`transformed_dataset`): -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def transform(raw_dataset, transform_fn, step): transformed_dataset = ( @@ -691,7 +691,7 @@ artifacts, which includes the `transform_fn` graph that's produced by the analyze phase on the training data. The code for storing the artifacts is shown in the following `write_transform_artefacts` function: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def write_transform_artefacts(transform_fn, location): ( @@ -740,20 +740,20 @@ bucket. The transformed training and evaluation data in TFRecord format are stored at the following location: -```none{:.devsite-disable-click-to-copy} +``` { .yaml .no-copy } gs://YOUR_BUCKET_NAME/babyweight_tft/transformed ``` The transform artifacts are produced at the following location: -```none{:.devsite-disable-click-to-copy} +``` { .yaml .no-copy } gs://YOUR_BUCKET_NAME/babyweight_tft/transform ``` The following list is the output of the pipeline, showing the produced data objects and artifacts: -```none{:.devsite-disable-click-to-copy} +``` { .yaml .no-copy } transformed data: gs://YOUR_BUCKET_NAME/babyweight_tft/transformed/eval-00000-of-00001.tfrecords gs://YOUR_BUCKET_NAME/babyweight_tft/transformed/train-00000-of-00002.tfrecords @@ -802,7 +802,7 @@ preprocessing pipeline explained earlier. The last part of the output is the following: - ```none{:.devsite-disable-click-to-copy} + ``` { .yaml .no-copy } Successfully installed ... Note: you may need to restart the kernel to use updated packages. ``` @@ -993,7 +993,7 @@ interface—that is, the input features schema that is expected during serving. This input features schema is defined in the `serving_fn` function, as shown in the following code: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } def export_serving_model(model, output_dir): tf_transform_output = tft.TFTransformOutput(TRANSFORM_ARTEFACTS_DIR) @@ -1081,7 +1081,7 @@ When you inspect the exported SavedModel object using the `saved_model_cli` tool, you see that the `inputs` elements of the signature definition `signature_def` include the raw features, as shown in the following example: -```py{:.devsite-disable-click-to-copy} +``` { .py .yaml .no-copy } signature_def['serving_default']: The given SavedModel SignatureDef contains the following input(s): inputs['gestation_weeks'] tensor_info: From 2464ae6859de621172e17bb7ff666dcf87698c2d Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:23:46 -0700 Subject: [PATCH 06/58] Fix links --- .../data_preprocessing_with_cloud.md | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index 92672c4431..ada8f36f33 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -11,16 +11,16 @@ and they create as byproducts a TensorFlow graph to apply the same transformations during prediction as when the model is served. This tutorial provides an end-to-end example using -[Dataflow](https://cloud.google.com/dataflow/docs){: .external } +[Dataflow](https://cloud.google.com/dataflow/docs) as a runner for Apache Beam. It assumes that you're familiar with -[BigQuery](https://cloud.google.com/bigquery/docs){: .external }, +[BigQuery](https://cloud.google.com/bigquery/docs), Dataflow, -[Vertex AI](https://cloud.google.com/vertex-ai/docs/start/introduction-unified-platform){: .external }, +[Vertex AI](https://cloud.google.com/vertex-ai/docs/start/introduction-unified-platform), and the TensorFlow [Keras](https://www.tensorflow.org/guide/keras/overview) API. It also assumes that you have some experience using Jupyter Notebooks, such as with -[Vertex AI Workbench](https://cloud.google.com/vertex-ai/docs/workbench/introduction){: .external }. +[Vertex AI Workbench](https://cloud.google.com/vertex-ai/docs/workbench/introduction). This tutorial also assumes that you're familiar with the concepts of preprocessing types, challenges, and options on Google Cloud, as described in @@ -47,7 +47,7 @@ This tutorial uses the following billable components of Google Cloud: To estimate the cost to run this tutorial, assuming you use every resource for an entire day, use the preconfigured -[pricing calculator](/products/calculator/#id=fad408d8-dd68-45b8-954e-5a5619a5d148){: .external }. +[pricing calculator](/products/calculator/#id=fad408d8-dd68-45b8-954e-5a5619a5d148). ## Before you begin @@ -72,11 +72,11 @@ an entire day, use the preconfigured The following Jupyter notebooks show the implementation example: -* [Notebook 1](https://github.com/GoogleCloudPlatform/training-data-analyst/blob/master/blogs/babyweight_tft/babyweight_tft_keras_01.ipynb){: .external } +* [Notebook 1](https://github.com/GoogleCloudPlatform/training-data-analyst/blob/master/blogs/babyweight_tft/babyweight_tft_keras_01.ipynb) covers data preprocessing. Details are provided in the [Implementing the Apache Beam pipeline](#implement-the-apache-beam-pipeline) section later. -* [Notebook 2](https://github.com/GoogleCloudPlatform/training-data-analyst/blob/master/blogs/babyweight_tft/babyweight_tft_keras_02.ipynb){: .external } +* [Notebook 2](https://github.com/GoogleCloudPlatform/training-data-analyst/blob/master/blogs/babyweight_tft/babyweight_tft_keras_02.ipynb) covers model training. Details are provided in the [Implementing the TensorFlow model](#implement-the-tensorflow-model) section later. @@ -176,7 +176,7 @@ the pipeline. The overall pipeline steps are as follows: 1. Read training data from BigQuery. 1. Analyze and transform training data using the `tf.Transform` library. 1. Write transformed training data to Cloud Storage in the - [TFRecord](https://www.tensorflow.org/tutorials/load_data/tfrecord){: target="external" class="external" track-type="solution" track-name="externalLink" track-metadata-position="body" } + [TFRecord](https://www.tensorflow.org/tutorials/load_data/tfrecord) format. 1. Read evaluation data from BigQuery. 1. Transform evaluation data using the `transform_fn` graph produced by step 2. @@ -232,7 +232,7 @@ def run_transformation_pipeline(args): write_text(transformed_train_dataset, transformed_data_location, step) ``` -### Read raw training data from BigQuery{: id="read_raw_training_data"} +### Read raw training data from BigQuery The first step is to read the raw training data from BigQuery using the `read_from_bq` function. This function returns a `raw_dataset` object @@ -425,7 +425,7 @@ def preprocess_fn(input_features): ``` The `tf.Transform` -[framework](https://github.com/tensorflow/transform){: .external } +[framework](https://github.com/tensorflow/transform) has several other transformations in addition to those in the preceding example, including those listed in the following table: @@ -536,7 +536,7 @@ produces two outputs: - `transform_fn`: a TensorFlow graph that contains the computed stats from the analyze phase and the transformation logic (which uses the stats) as instance-level operations. As discussed later in - [Save the graph](#save_the_graph){: track-type="solution" track-name="internalLink" track-metadata-position="body" }, + [Save the graph](#save-the-graph), the `transform_fn` graph is saved to be attached to the model `serving_fn` function. This makes it possible to apply the same transformation to the online prediction data points. @@ -552,7 +552,7 @@ The analyze phase is illustrated in the following diagram, figure 1: The `tf.Transform` -[analyzers](https://github.com/tensorflow/transform/blob/master/tensorflow_transform/beam/analyzer_impls.py){: target="github" class="external" track-type="solution" track-name="gitHubLink" track-metadata-position="body" } +[analyzers](https://github.com/tensorflow/transform/blob/master/tensorflow_transform/beam/analyzer_impls.py) include `min`, `max`, `sum`, `size`, `mean`, `var`, `covariance`, `quantiles`, `vocabulary`, and `pca`. @@ -602,7 +602,7 @@ categorical features are represented by integer values. In the columns indicates whether the column represents a categorical feature or a true numeric feature. -### Write transformed training data{: id="step_3_write_transformed_training_data"} +### Write transformed training data After the training data is preprocessed with the `preprocess_fn` function through the analyze and transform phases, you can write the data to a sink to be @@ -640,7 +640,7 @@ After you transform the training data and produce the `transform_fn` graph, you can use it to transform the evaluation data. First, you read and clean the evaluation data from BigQuery using the `read_from_bq` function described earlier in -[Read raw training data from BigQuery](#read-raw-training-data-from-bigquery){: track-type="solution" track-name="internalLink" track-metadata-position="body" }, +[Read raw training data from BigQuery](#read-raw-training-data-from-bigquery), and passing a value of `eval` for the `step` parameter. Then, you use the following code to transform the raw evaluation dataset (`raw_dataset`) to the expected transformed format (`transformed_dataset`): @@ -673,7 +673,7 @@ You then write the data to a sink (Cloud Storage or local disk, depending on the runner) in the TFRecord format for evaluating the TensorFlow model during the training process. To do this, you use the `write_tfrecords` function that's discussed in -[Write transformed training data](#step_3_write_transformed_training_data){: track-type="solution" track-name="internalLink" track-metadata-position="body" }. +[Write transformed training data](#write-transformed-training-data). The following diagram, figure 3, shows how the `transform_fn` graph that's produced in the analyze phase of the training data is used to transform the evaluation data. @@ -777,10 +777,10 @@ gs://YOUR_BUCKET_NAME/babyweight_tft/transform/transform_fn/assets/is_multiple gs://YOUR_BUCKET_NAME/babyweight_tft/transform/transform_fn/assets/mother_race ``` -## Implement the TensorFlow model{: id="implementing_the_tensorflow_model"} +## Implement the TensorFlow model This section and the next section, -[Train and use the model for predictions](#train_and_use_the_model_for_predictions){: track-type="solution" track-name="internalLink" track-metadata-position="body" }, +[Train and use the model for predictions](#train-and-use-the-model-for-predictions), provide an overview and context for Notebook 2. The notebook provides an example ML model to predict baby weights. In this example, a TensorFlow model is implemented using the Keras API. The model @@ -866,7 +866,7 @@ the previous step: 1. Create a `TFTransformOutput` object from the artifacts that are generated and saved in the previous preprocessing step, as described in the - [Save the graph](#save_the_graph){: track-type="solution" track-name="internalLink" track-metadata-position="body" } + [Save the graph](#save-the-graph) section: ```py @@ -965,7 +965,7 @@ features, and a `tf.feature_column.categorical_column_with_identity` column for categorical features. You can also create extended feature columns, as described in -[Option C: TensorFlow](/architecture/data-preprocessing-for-ml-with-tf-transform-pt1#option_c_tensorflow){: track-type="solution" track-name="internalLink" track-metadata-position="body" } +[Option C: TensorFlow](../../../guide/tft_bestpractices#option-c-tensorflow) in the first part of this series. In the example used for this series, a new feature is created, `mother_race_X_mother_age_bucketized`, by crossing the `mother_race` and `mother_age_bucketized` features using the @@ -1074,7 +1074,7 @@ for serving: You can train the model locally by executing the cells of the notebook. For examples of how to package the code and train your model at scale using Vertex AI Training, see the samples and guides in the Google Cloud -[cloudml-samples](https://github.com/GoogleCloudPlatform/cloudml-samples){: .external } +[cloudml-samples](https://github.com/GoogleCloudPlatform/cloudml-samples) GitHub repository. When you inspect the exported SavedModel object using the `saved_model_cli` @@ -1170,11 +1170,11 @@ resources used in this tutorial, delete the project that contains the resources. [Data preprocessing for ML: options and recommendations](../guide/tft_bestpractices). - For more information about how to implement, package, and run a tf.Transform pipeline on Dataflow, see the - [Predicting income with Census Dataset](https://github.com/GoogleCloudPlatform/cloudml-samples/tree/master/census/tftransformestimator){: .external } + [Predicting income with Census Dataset](https://github.com/GoogleCloudPlatform/cloudml-samples/tree/master/census/tftransformestimator) sample. - Take the Coursera specialization on ML with - [TensorFlow on Google Cloud](https://www.coursera.org/specializations/machine-learning-tensorflow-gcp){: .external }. + [TensorFlow on Google Cloud](https://www.coursera.org/specializations/machine-learning-tensorflow-gcp). - Learn about best practices for ML engineering in - [Rules of ML](https://developers.google.com/machine-learning/guides/rules-of-ml/){: .external }. + [Rules of ML](https://developers.google.com/machine-learning/guides/rules-of-ml/). - For more reference architectures, diagrams, and best practices, explore the [Cloud Architecture Center](https://cloud.google.com/architecture). From f6c04cf4a821ab3d0cfc4bcc7ec2687a155d42c0 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:29:54 -0700 Subject: [PATCH 07/58] Fix buttons --- .../tutorials/transform/data_preprocessing_with_cloud.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index ada8f36f33..d23f9ad377 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -60,13 +60,14 @@ an entire day, use the preconfigured After you finish these steps, you can delete the project, removing all resources associated with the project. - [Go to project selector](https://console.cloud.google.com/projectselector2/home/dashboard){: class="button button-primary" target="console" track-type="solution" track-name="consoleLink" track-metadata-position="body" } + [Go to project selector](https://console.cloud.google.com/projectselector2/home/dashboard){ .md-button .md-button--primary } 2. Make sure that billing is enabled for your Cloud project. Learn how to [check if billing is enabled on a project](https://cloud.google.com/billing/docs/how-to/verify-billing-enabled). 3. Enable the Dataflow, Vertex AI, and Notebooks APIs. - [Enable the APIs](https://console.cloud.google.com/flows/enableapi?apiid=dataflow,aiplatform.googleapis.com,notebooks.googleapis.com){: class="button button-primary" target="console" track-type="solution" track-name="consoleLink" track-metadata-position="body" } + + [Enable the APIs](https://console.cloud.google.com/flows/enableapi?apiid=dataflow,aiplatform.googleapis.com,notebooks.googleapis.com){ .md-button .md-button--primary } ## Jupyter notebooks for this solution @@ -88,7 +89,7 @@ notebooks to learn how the implementation example works. 1. In the Google Cloud console, go to the **Vertex AI Workbench** page. - [Go to Workbench](https://console.cloud.google.com/ai-platform/notebooks/list/instances){: class="button button-primary" target="console" track-type="solution" track-name="consoleLink" track-metadata-position="body" } + [Go to Workbench](https://console.cloud.google.com/ai-platform/notebooks/list/instances){ .md-button .md-button--primary } 1. On the **User-managed notebooks** tab, click **+New notebook**. 1. Select **TensorFlow Enterprise 2.8 (with LTS) without GPUs** for the @@ -1155,7 +1156,7 @@ resources used in this tutorial, delete the project that contains the resources. 1. In the Google Cloud console, go to the **Manage resources** page. - [Go to Manage resources](https://console.cloud.google.com/iam-admin/projects){: class="button button-primary" target="console" track-type="solution" track-name="consoleLink" track-metadata-position="body" } + [Go to Manage resources](https://console.cloud.google.com/iam-admin/projects){ .md-button .md-button--primary } 1. In the project list, select the project that you want to delete, and then click **Delete**. From 60eada3054f86972fc9b87458aca1b2c177bdf02 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:30:58 -0700 Subject: [PATCH 08/58] Clean up link --- docs/tutorials/transform/data_preprocessing_with_cloud.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index d23f9ad377..b034f8daf6 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -122,7 +122,7 @@ provide an overview and context for Notebook 1. The notebook provides a practical example to describe how to use the `tf.Transform` library to preprocess data. This example uses the Natality dataset, which is used to predict baby weights based on various inputs. The data is stored in the public -[natality](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=samples&t=natality&page=table&_ga=2.267763789.2122871960.1676620306-376763843.1676620306){: target="console" track-type="solution" track-name="consoleLink" track-metadata-position="body" } +[natality](https://console.cloud.google.com/bigquery?p=bigquery-public-data&d=samples&t=natality&page=table&_ga=2.267763789.2122871960.1676620306-376763843.1676620306) table in BigQuery. ### Run Notebook 1 From 5d08102222ba763ab8a9e50c5288b558785d15dc Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 00:01:19 -0700 Subject: [PATCH 09/58] Fix images --- .../data_preprocessing_with_cloud.md | 51 +++++++------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index b034f8daf6..77967a0533 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -546,11 +546,9 @@ produces two outputs: The analyze phase is illustrated in the following diagram, figure 1: -
- The tf.Transform analyze phase. -
Figure 1. The tf.Transform analyze phase.
-
+Figure: The `tf.Transform` analyze phase. { #tf-transform-analyze-phase } + +![The tf.Transform analyze phase.](images/data-preprocessing-for-ml-with-tf-transform-tf-transform-analyze-phase.svg) The `tf.Transform` [analyzers](https://github.com/tensorflow/transform/blob/master/tensorflow_transform/beam/analyzer_impls.py) @@ -567,11 +565,9 @@ the `transformed_train_dataset` dataset. The transform phase is illustrated in the following diagram, figure 2: -
- The tf.Transform transform phase. -
Figure 2. The tf.Transform transform phase.
-
+Figure: The `tf.Transform` transform phase. { #tf-transform-transform-phase } + +![The tf.Transform transform phase.](images/data-preprocessing-for-ml-with-tf-transform-tf-transform-transform-phase.svg) To preprocess the features, you call the required `tensorflow_transform` transformations (imported as `tft` in the code) in your implementation of the @@ -679,11 +675,9 @@ The following diagram, figure 3, shows how the `transform_fn` graph that's produced in the analyze phase of the training data is used to transform the evaluation data. -
- Transforming evaluation data using the transform_fn graph. -
Figure 3. Transforming evaluation data using the transform_fn graph.
-
+Figure: Transforming evaluation data using the `transform_fn` graph. { #transform-eval-data-using-transform-fn } + +![Transforming evaluation data using the transform_fn graph.](images/data-preprocessing-for-ml-with-tf-transform-transforming-eval-data-using-transform_fn.svg) ### Save the graph @@ -724,12 +718,9 @@ Dataflow. The following diagram, figure 4, shows the Dataflow execution graph of the `tf.Transform` pipeline described in the example. -
- Dataflow execution graph of the tf.Transform pipeline. -
Figure 4. Dataflow execution graph - of the tf.Transform pipeline.
-
+Figure: Dataflow execution graph of the `tf.Transform` pipeline. { #dataflow-execution-graph } + +![Dataflow execution graph of the tf.Transform pipeline.](images/data-preprocessing-for-ml-with-tf-transform-dataflow-execution-graph.png) After you execute the Dataflow pipeline to preprocess the training and evaluation data, you can explore the produced objects in @@ -978,12 +969,9 @@ The following diagram, figure 5, shows the transformed data and how the transformed metadata is used to define and train the TensorFlow model: -
- Training the TensorFlow model with transformed data. -
Figure 5. Training the TensorFlow model with - the transformed data.
-
+Figure: Training the TensorFlow model with the transformed data. { #training-tf-with-transformed-data } + +![Training the TensorFlow model with transformed data.](images/data-preprocessing-for-ml-with-tf-transform-training-tf-model-with-transformed-data.svg) ### Export the model for serving prediction @@ -1063,12 +1051,9 @@ following steps: The following diagram, figure 6, illustrates the final step of exporting a model for serving: -
- Exporting the model for serving with the transform_fn graph attached. -
Figure 6. Exporting the model for serving with the - transform_fn graph attached.
-
+Figure: Exporting the model for serving with the `transform_fn` graph attached. { #exporting-model-for-serving-with-transform_fn } + +![Exporting the model for serving with the transform_fn graph attached.](images/data-preprocessing-for-ml-with-tf-transform-exporting-model-for-serving-with-transform_fn.svg) ## Train and use the model for predictions From b829c342a8bd932f38e0ace8e24caff4974dc5c7 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 00:17:49 -0700 Subject: [PATCH 10/58] Fix warning --- .../data_preprocessing_with_cloud.md | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index 77967a0533..7cb99a0f68 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -1118,25 +1118,15 @@ resources used in this tutorial, delete the project that contains the resources. ### Delete the project - +!!! danger + + Deleting a project has the following effects: + + - __Everything in the project is deleted.__ If you used an existing project for + this tutorial, when you delete it, you also delete any other work you've done in the project. + - __Custom project IDs are lost.__ When you created this project, you might have created a custom project ID that you want to use in the future. To preserve the URLs that use the project ID, such as an `appspot.com`{translate="no" dir="ltr"} URL, delete selected resources inside the project instead of deleting the whole project. + + If you plan to explore multiple tutorials and quickstarts, reusing projects can help you avoid exceeding project quota limits. 1. In the Google Cloud console, go to the **Manage resources** page. From 1a26664631ff6a9a6ad9aa20d330428ab7819428 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 00:55:26 -0700 Subject: [PATCH 11/58] Fix name of admonition --- docs/tutorials/transform/data_preprocessing_with_cloud.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index 7cb99a0f68..f69bccceb0 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -1118,7 +1118,7 @@ resources used in this tutorial, delete the project that contains the resources. ### Delete the project -!!! danger +!!! danger "Caution" Deleting a project has the following effects: From 250353b8ac89e93d2d73f52d4bc1b9abdbe375f5 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 00:55:50 -0700 Subject: [PATCH 12/58] Fix admonitions --- .../tfx/cloud-ai-platform-pipelines.md | 137 ++++++++++-------- 1 file changed, 76 insertions(+), 61 deletions(-) diff --git a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md index b0f9dd33c8..9135ebab81 100644 --- a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md +++ b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md @@ -14,14 +14,16 @@ At the end of this tutorial, you will have created and run an ML Pipeline, hosted on Google Cloud. You'll be able to visualize the results of each run, and view the lineage of the created artifacts. -Key Term: A TFX pipeline is a Directed Acyclic Graph, or "DAG". We will often -refer to pipelines as DAGs. +!!! abstract "Key Term" + A TFX pipeline is a Directed Acyclic Graph, or "DAG". We will often + refer to pipelines as DAGs. You'll follow a typical ML development process, starting by examining the dataset, and ending up with a complete working pipeline. Along the way you'll explore ways to debug and update your pipeline, and measure performance. -Note: Completing this tutorial may take 45-60 minutes. +!!! Note + Completing this tutorial may take 45-60 minutes. ### Chicago Taxi Dataset @@ -35,12 +37,13 @@ You're using the [Taxi Trips dataset](https://data.cityofchicago.org/Transportation/Taxi-Trips/wrvz-psew) released by the City of Chicago. -Note: This site provides applications using data that has been modified for use -from its original source, www.cityofchicago.org, the official website of the -City of Chicago. The City of Chicago makes no claims as to the content, -accuracy, timeliness, or completeness of any of the data provided at this site. -The data provided at this site is subject to change at any time. It is -understood that the data provided at this site is being used at one’s own risk. +!!! Note + This site provides applications using data that has been modified for use + from its original source, www.cityofchicago.org, the official website of the + City of Chicago. The City of Chicago makes no claims as to the content, + accuracy, timeliness, or completeness of any of the data provided at this site. + The data provided at this site is subject to change at any time. It is + understood that the data provided at this site is being used at one’s own risk. You can [read more](https://cloud.google.com/bigquery/public-data/chicago-taxi) about the dataset in [Google BigQuery](https://cloud.google.com/bigquery/). @@ -58,11 +61,12 @@ Will the customer tip more or less than 20%? To get started, you need a Google Cloud Account. If you already have one, skip ahead to [Create New Project](#create_project). -Warning: This demo is designed to not exceed -[Google Cloud's Free Tier](https://cloud.google.com/free) limits. If you already -have a Google Account, you may have reached your Free Tier limits, or exhausted -any free Google Cloud credits given to new users. **If that is the case, -following this demo will result in charges to your Google Cloud account**. +!!! Warning + This demo is designed to not exceed + [Google Cloud's Free Tier](https://cloud.google.com/free) limits. If you already + have a Google Account, you may have reached your Free Tier limits, or exhausted + any free Google Cloud credits given to new users. **If that is the case, + following this demo will result in charges to your Google Cloud account**. 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). @@ -85,19 +89,22 @@ following this demo will result in charges to your Google Cloud account**. [Google Cloud Free Tier](https://cloud.google.com/free) limits, which includes a max of 8 cores running at the same time. -Note: You can choose at this point to become a paid user instead of relying on -the free trial. Since this tutorial stays within the Free Tier limits, you still -won't be charged if this is your only project and you stay within those limits. -For more details, see -[Google Cloud Cost Calculator](https://cloud.google.com/products/calculator/) -and [Google Cloud Platform Free Tier](https://cloud.google.com/free). +!!! Note + You can choose at this point to become a paid user instead of relying on + the free trial. Since this tutorial stays within the Free Tier limits, you still + won't be charged if this is your only project and you stay within those limits. + For more details, see + [Google Cloud Cost Calculator](https://cloud.google.com/products/calculator/) + and [Google Cloud Platform Free Tier](https://cloud.google.com/free). ### 1.b Create a new project. -Note: This tutorial assumes you want to work on this demo in a new project. You -can, if you want, work in an existing project. +!!! Note + This tutorial assumes you want to work on this demo in a new project. You + can, if you want, work in an existing project. -Note: You must have a verified credit card on file before creating the project. +!!! Note + You must have a verified credit card on file before creating the project. 1. From the [main Google Cloud dashboard](https://console.cloud.google.com/home/dashboard), @@ -109,8 +116,9 @@ drop-down.** ## 2. Set up and deploy an AI Platform Pipeline on a new Kubernetes cluster -Note: This will take up to 10 minutes, as it requires waiting at several points -for resources to be provisioned. +!!! Note + This will take up to 10 minutes, as it requires waiting at several points + for resources to be provisioned. 1. Go to the [AI Platform Pipelines Clusters](https://console.cloud.google.com/ai-platform/pipelines) @@ -130,7 +138,8 @@ for resources to be provisioned. - Note: You may have to wait several minutes before moving on, while the Kubernetes Engine APIs are being enabled for you. + !!! Note + You may have to wait several minutes before moving on, while the Kubernetes Engine APIs are being enabled for you. 1. On the **Deploy Kubeflow Pipelines** page: @@ -190,15 +199,16 @@ for resources to be provisioned. 1. Wait for the new notebook to be created, and then click **Enable Notebooks API** -Note: You may experience slow performance in your notebook if you use 1 or 2 -vCPUs instead of the default or higher. This should not seriously hinder your -completion of this tutorial. If would like to use the default settings, -[upgrade your account](https://cloud.google.com/free/docs/gcp-free-tier#to_upgrade_your_account) -to at least 12 vCPUs. This will accrue charges. See -[Google Kubernetes Engine Pricing](https://cloud.google.com/kubernetes-engine/pricing/) -for more details on pricing, including a -[pricing calculator](https://cloud.google.com/products/calculator) and -information about the [Google Cloud Free Tier](https://cloud.google.com/free). +!!! Note + You may experience slow performance in your notebook if you use 1 or 2 + vCPUs instead of the default or higher. This should not seriously hinder your + completion of this tutorial. If would like to use the default settings, + [upgrade your account](https://cloud.google.com/free/docs/gcp-free-tier#to_upgrade_your_account) + to at least 12 vCPUs. This will accrue charges. See + [Google Kubernetes Engine Pricing](https://cloud.google.com/kubernetes-engine/pricing/) + for more details on pricing, including a + [pricing calculator](https://cloud.google.com/products/calculator) and + information about the [Google Cloud Free Tier](https://cloud.google.com/free). ## 4. Launch the Getting Started Notebook @@ -379,13 +389,14 @@ Kubeflow Pipelines Dashboard. You can view your pipeline from the Kubeflow Pipelines Dashboard. -Note: If your pipeline run fails, you can see detailed logs in the KFP -Dashboard. One of the major sources of failure is permission related problems. -Make sure your KFP cluster has permissions to access Google Cloud APIs. This can -be configured -[when you create a KFP cluster in GCP](https://cloud.google.com/ai-platform/pipelines/docs/setting-up), -or see -[Troubleshooting document in GCP](https://cloud.google.com/ai-platform/pipelines/docs/troubleshooting). +!!! Note + If your pipeline run fails, you can see detailed logs in the KFP + Dashboard. One of the major sources of failure is permission related problems. + Make sure your KFP cluster has permissions to access Google Cloud APIs. This can + be configured + [when you create a KFP cluster in GCP](https://cloud.google.com/ai-platform/pipelines/docs/setting-up), + or see + [Troubleshooting document in GCP](https://cloud.google.com/ai-platform/pipelines/docs/troubleshooting). ## 8. Validate your data @@ -713,8 +724,9 @@ setting `--project` in `beam_pipeline_args` when creating a pipeline. should replace the project id and the region value in this file with the correct values for your GCP project. ->**Note: You MUST set your GCP project ID and region in the `configs.py` file -before proceeding.** +!!! Note + You MUST set your GCP project ID and region in the `configs.py` file + before proceeding. **Change directory one level up.** Click the name of the directory above the file list. The name of the directory is the name of the pipeline which is @@ -746,9 +758,10 @@ processing workloads using will set the Kubeflow orchestrator to use Dataflow as the data processing back-end for Apache Beam. ->**Note:** If the Dataflow API is not already enabled, you can enable it using -the console, or from the CLI using this command (for example, in the Cloud -Shell): +!!! Note + If the Dataflow API is not already enabled, you can enable it using + the console, or from the CLI using this command (for example, in the Cloud + Shell): ```bash # Select your project: @@ -765,15 +778,16 @@ gcloud services list --available | grep Dataflow gcloud services enable dataflow.googleapis.com ``` -> **Note:** Execution speed may be limited by default -> [Google Compute Engine (GCE)](https://cloud.google.com/compute) quota. We -> recommend setting a sufficient quota for approximately 250 Dataflow VMs: **250 -> CPUs, 250 IP Addresses, and 62500 GB of Persistent Disk**. For more details, -> please see the [GCE Quota](https://cloud.google.com/compute/quotas) and -> [Dataflow Quota](https://cloud.google.com/dataflow/quotas) documentation. If -> you are blocked by IP Address quota, using a bigger -> [`worker_type`](https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options) -> will reduce the number of needed IPs. +!!! Note + Execution speed may be limited by default + [Google Compute Engine (GCE)](https://cloud.google.com/compute) quota. We + recommend setting a sufficient quota for approximately 250 Dataflow VMs: **250 + CPUs, 250 IP Addresses, and 62500 GB of Persistent Disk**. For more details, + please see the [GCE Quota](https://cloud.google.com/compute/quotas) and + [Dataflow Quota](https://cloud.google.com/dataflow/quotas) documentation. If + you are blocked by IP Address quota, using a bigger + [`worker_type`](https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#setting-other-cloud-dataflow-pipeline-options) + will reduce the number of needed IPs. **Double-click `pipeline` to change directory, and double-click to open `configs.py`**. Uncomment the definition of `GOOGLE_CLOUD_REGION`, and @@ -825,11 +839,12 @@ the same value as `CUSTOM_TFX_IMAGE` above. `kubeflow_runner.py`**. Uncomment `ai_platform_training_args` and `ai_platform_serving_args`. -> Note: If you receive a permissions error in the Training step, you may need to -> provide Storage Object Viewer permissions to the Cloud Machine Learning Engine -> (AI Platform Prediction & Training) service account. More information is -> available in the -> [Container Registry documentation](https://cloud.google.com/container-registry/docs/access-control#grant). +!!! Note + If you receive a permissions error in the Training step, you may need to + provide Storage Object Viewer permissions to the Cloud Machine Learning Engine + (AI Platform Prediction & Training) service account. More information is + available in the + [Container Registry documentation](https://cloud.google.com/container-registry/docs/access-control#grant). #### Update the pipeline and re-run it From cdfb7805f729d7443ef7456ddc859d87848ada29 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 01:05:05 -0700 Subject: [PATCH 13/58] Fix broken images --- .../tfx/cloud-ai-platform-pipelines.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md index 9135ebab81..701ec96526 100644 --- a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md +++ b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md @@ -72,7 +72,7 @@ ahead to [Create New Project](#create_project). 1. Agree to Google Cloud terms and conditions - + ![](images/cloud-ai-platform-pipelines/welcome-popup.png){ width="65%" } 1. If you would like to start with a free trial account, click on [**Try For Free**](https://console.cloud.google.com/freetrial) (or @@ -128,15 +128,15 @@ drop-down.** 1. Click **+ New Instance** to create a new cluster. - + ![](images/cloud-ai-platform-pipelines/new-instance.png){ width="65%" } 1. On the **Kubeflow Pipelines** overview page, click **Configure**. - + ![](images/cloud-ai-platform-pipelines/configure.png){ width="65%" } 1. Click "Enable" to enable the Kubernetes Engine API - + ![](images/cloud-ai-platform-pipelines/enable_api.png){ width="65%" } !!! Note You may have to wait several minutes before moving on, while the Kubernetes Engine APIs are being enabled for you. @@ -151,7 +151,7 @@ drop-down.** APIs*. (This is required for this cluster to access the other pieces of your project. If you miss this step, fixing it later is a bit tricky.) - + ![](images/cloud-ai-platform-pipelines/check-the-box.png){ width="50%" } 1. Click **Create New Cluster**, and wait several minutes until the cluster has been created. This will take a few minutes. When it completes you @@ -181,7 +181,7 @@ drop-down.** 1. Create a **New Notebook** with TensorFlow Enterprise 2.7 (or above) installed. - + ![](images/cloud-ai-platform-pipelines/new-notebook.png){ width="65%" } New Notebook -> TensorFlow Enterprise 2.7 -> Without GPU @@ -195,7 +195,8 @@ drop-down.** 1. Under **Machine configuration** you may want to select a configuration with 1 or 2 vCPUs if you need to stay in the free tier. - + ![](images/cloud-ai-platform-pipelines/two-cpus.png){ width="65%" } + 1. Wait for the new notebook to be created, and then click **Enable Notebooks API** @@ -220,12 +221,12 @@ drop-down.** 1. On the line for the cluster you are using in this tutorial, click **Open Pipelines Dashboard**. - + ![](images/cloud-ai-platform-pipelines/open-dashboard.png) 1. On the **Getting Started** page, click **Open a Cloud AI Platform Notebook on Google Cloud**. - + ![](images/cloud-ai-platform-pipelines/open-template.png) 1. Select the Notebook instance you are using for this tutorial and **Continue**, and then **Confirm**. From 69f7d541b2bb9a9421f98325f19215b10d9b24c2 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:35:29 -0700 Subject: [PATCH 14/58] Fix api docs index link --- docs/api/v1/index.md | 17 +++++++++++++++++ docs/api/v1/root.md | 17 ----------------- mkdocs.yml | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 docs/api/v1/root.md diff --git a/docs/api/v1/index.md b/docs/api/v1/index.md index e69de29bb2..b06cb920bf 100644 --- a/docs/api/v1/index.md +++ b/docs/api/v1/index.md @@ -0,0 +1,17 @@ +# Modules + +[components][tfx.v1.components] module: TFX components module. + +[dsl][tfx.v1.dsl] module: TFX DSL module. + +[extensions][tfx.v1.extensions] module: TFX extensions module. + +[orchestration][tfx.v1.orchestration] module: TFX orchestration module. + +[proto][tfx.v1.proto] module: TFX proto module. + +[testing][tfx.v1.testing] module: Public testing modules for TFX. + +[types][tfx.v1.types] module: TFX types module. + +[utils][tfx.v1.utils] module: TFX utils module. diff --git a/docs/api/v1/root.md b/docs/api/v1/root.md deleted file mode 100644 index b06cb920bf..0000000000 --- a/docs/api/v1/root.md +++ /dev/null @@ -1,17 +0,0 @@ -# Modules - -[components][tfx.v1.components] module: TFX components module. - -[dsl][tfx.v1.dsl] module: TFX DSL module. - -[extensions][tfx.v1.extensions] module: TFX extensions module. - -[orchestration][tfx.v1.orchestration] module: TFX orchestration module. - -[proto][tfx.v1.proto] module: TFX proto module. - -[testing][tfx.v1.testing] module: Public testing modules for TFX. - -[types][tfx.v1.types] module: TFX types module. - -[utils][tfx.v1.utils] module: TFX utils module. diff --git a/mkdocs.yml b/mkdocs.yml index 9910392ec7..740db52194 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -242,7 +242,7 @@ nav: - "TensorBoard": "https://www.tensorflow.org/tensorboard" - API: - v1: - - "Overview": api/v1/root + - "Overview": api/v1/index.md - "components": api/v1/components - "dsl": api/v1/dsl - "extensions": api/v1/extensions From 02b8c56292041a9f88355b13c3cd3ee443b8c9cd Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:54:10 -0700 Subject: [PATCH 15/58] Add notes admonitions to guide index page --- docs/guide/index.md | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/docs/guide/index.md b/docs/guide/index.md index dd1001ca38..692419fef9 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -26,16 +26,18 @@ https://github.com/tensorflow/tfx) pip install tfx ``` -Note: See the -[TensorFlow Serving](https://www.tensorflow.org/tfx/guide/serving), -[TensorFlow JS](https://js.tensorflow.org/), and/or -[TensorFlow Lite](https://www.tensorflow.org/lite) documentation for installing -those optional components. - -Note: This installs [Apache Beam](beam.md) with the DirectRunner. You can also -separately install runners that perform distributed computation, such as -[Apache Flink](https://flink.apache.org/) or -[Apache Spark](https://spark.apache.org/). +!!! Note + See the + [TensorFlow Serving](https://www.tensorflow.org/tfx/guide/serving), + [TensorFlow JS](https://js.tensorflow.org/), and/or + [TensorFlow Lite](https://www.tensorflow.org/lite) documentation for installing + those optional components. + +!!! Note + This installs [Apache Beam](beam.md) with the DirectRunner. You can also + separately install runners that perform distributed computation, such as + [Apache Flink](https://flink.apache.org/) or + [Apache Spark](https://spark.apache.org/). ### Nightly Packages @@ -50,8 +52,9 @@ This will install the nightly packages for the major dependencies of TFX such as TensorFlow Model Analysis (TFMA), TensorFlow Data Validation (TFDV), TensorFlow Transform (TFT), TFX Basic Shared Libraries (TFX-BSL), ML Metadata (MLMD). -Note: These nightly packages are unstable and breakages are likely to happen. -The fix could often take a week or more depending on the complexity involved. +!!! Note + These nightly packages are unstable and breakages are likely to happen. + The fix could often take a week or more depending on the complexity involved. ## About TFX @@ -170,8 +173,9 @@ TFX libraries include: [KerasTuner](https://www.tensorflow.org/tutorials/keras/keras_tuner) is used for tuning hyperparameters for model. - Note: TFX supports TensorFlow 1.15 and, with some exceptions, 2.x. For - details, see [Designing TensorFlow Modeling Code For TFX](train.md). + !!! Note + TFX supports TensorFlow 1.15 and, with some exceptions, 2.x. For + details, see [Designing TensorFlow Modeling Code For TFX](train.md). * [**TensorFlow Model Analysis (TFMA)**](tfma.md) is a library for evaluating TensorFlow models. It is used along with TensorFlow to create an @@ -250,8 +254,9 @@ TFX interoperates with serveral managed GCP services, such as [Cloud Dataflow](https://cloud.google.com/dataflow/) for distributed data processing for several other aspects of the ML lifecycle. -Note: The current revision of this user guide primarily discusses deployment -on a bare-metal system using Apache Airflow for orchestration. +!!! Note + The current revision of this user guide primarily discusses deployment + on a bare-metal system using Apache Airflow for orchestration. ### Model vs. SavedModel @@ -336,9 +341,10 @@ The following components use the schema: In a typical TFX pipeline TensorFlow Data Validation generates a schema, which is consumed by the other components. -Note: The auto-generated schema is best-effort and only tries to infer basic -properties of the data. It is expected that developers review and modify it as -needed. +!!! Note + The auto-generated schema is best-effort and only tries to infer basic + properties of the data. It is expected that developers review and modify it as + needed. ## Developing with TFX From 98f73ef8ca0e7db5df2bd943dba746bdf9d41d2b Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:55:16 -0700 Subject: [PATCH 16/58] Fix note admonitions for "TFX Cloud Solutions" --- docs/guide/solutions.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/guide/solutions.md b/docs/guide/solutions.md index f14b6fb47f..c47181eebb 100644 --- a/docs/guide/solutions.md +++ b/docs/guide/solutions.md @@ -3,12 +3,13 @@ Looking for insights into how TFX can be applied to build a solution that meets your needs? These in-depth articles and guides may help! -Note: These articles discuss complete solutions in which TFX is a key part, but -not the only part. This is nearly always the case for real-world deployments. So -implementing these solutions yourself will require more than just TFX. The main -goal is to give you some insight into how others have implemented solutions that -may meet requirements that are similar to yours, and not to serve as a cookbook -or list of approved applications of TFX. +!!! Note + These articles discuss complete solutions in which TFX is a key part, but + not the only part. This is nearly always the case for real-world deployments. So + implementing these solutions yourself will require more than just TFX. The main + goal is to give you some insight into how others have implemented solutions that + may meet requirements that are similar to yours, and not to serve as a cookbook + or list of approved applications of TFX. ## Architecture of a machine learning system for near real-time item matching From 819a29544f1c3838f4072fd9dbfb4253a07aaba1 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:04:11 -0700 Subject: [PATCH 17/58] Fix notes admoniations for keras page in guide --- docs/guide/keras.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/guide/keras.md b/docs/guide/keras.md index dd1454db9a..8716f27e83 100644 --- a/docs/guide/keras.md +++ b/docs/guide/keras.md @@ -87,9 +87,10 @@ unchanged. ## Native Keras (i.e. Keras without `model_to_estimator`) -Note: Full support for all features in Keras is in progress, in most cases, -Keras in TFX will work as expected. It does not yet work with Sparse Features -for FeatureColumns. +!!! Note + Full support for all features in Keras is in progress, in most cases, + Keras in TFX will work as expected. It does not yet work with Sparse Features + for FeatureColumns. ### Examples and Colab @@ -125,8 +126,9 @@ ops. The serving function and eval function are changed for native Keras. Details will be discussed in the following Trainer and Evaluator sections. -Note: Transformations within the `preprocessing_fn` cannot be applied to the -label feature for training or eval. +!!! Note + Transformations within the `preprocessing_fn` cannot be applied to the + label feature for training or eval. #### Trainer @@ -280,9 +282,10 @@ logging.getLogger("tensorflow").setLevel(logging.INFO) and you should be able to see `Using MirroredStrategy with devices (...)` in the log. -Note: The environment variable `TF_FORCE_GPU_ALLOW_GROWTH=true` might be needed -for a GPU out of memory issue. For details, please refer to -[tensorflow GPU guide](https://www.tensorflow.org/guide/gpu#limiting_gpu_memory_growth). +!!! Note + The environment variable `TF_FORCE_GPU_ALLOW_GROWTH=true` might be needed + for a GPU out of memory issue. For details, please refer to + [tensorflow GPU guide](https://www.tensorflow.org/guide/gpu#limiting_gpu_memory_growth). #### Evaluator From 9f217e8e63462a53beb672b854daf05afbda2c89 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:20:12 -0700 Subject: [PATCH 18/58] Fix note admonitions for "Building TFX Pipelines" --- docs/guide/build_tfx_pipeline.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/guide/build_tfx_pipeline.md b/docs/guide/build_tfx_pipeline.md index f03a5f4648..f2dd6b863d 100644 --- a/docs/guide/build_tfx_pipeline.md +++ b/docs/guide/build_tfx_pipeline.md @@ -1,11 +1,13 @@ # Building TFX pipelines -Note: For a conceptual view of TFX Pipelines, see -[Understanding TFX Pipelines](understanding_tfx_pipelines.md). +!!! Note + For a conceptual view of TFX Pipelines, see + [Understanding TFX Pipelines](understanding_tfx_pipelines.md). -Note: Want to build your first pipeline before you dive into the details? Get -started -[building a pipeline using a template](build_local_pipeline.md#build-a-pipeline-using-a-template). +!!!Note + Want to build your first pipeline before you dive into the details? Get + started + [building a pipeline using a template](build_local_pipeline.md#build-a-pipeline-using-a-template). ## Using the `Pipeline` class @@ -61,9 +63,10 @@ statistics. In this example, the instance of `StatisticsGen` must follow ### Task-based dependencies -Note: Using task-based dependencies is typically not recommended. Defining the -execution graph with artifact dependencies lets you take advantage of the -automatic artifact lineage tracking and caching features of TFX. +!!! Note + Using task-based dependencies is typically not recommended. Defining the + execution graph with artifact dependencies lets you take advantage of the + automatic artifact lineage tracking and caching features of TFX. You can also define task-based dependencies using your component's [`add_upstream_node` and `add_downstream_node`](https://github.com/tensorflow/tfx/blob/master/tfx/components/base/base_node.py){: .external } From 6d94eb33a9f06361a8a3da7987c87751fb871cdb Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:34:56 -0700 Subject: [PATCH 19/58] Fix notes admonitions in examplegen guide page --- docs/guide/examplegen.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/guide/examplegen.md b/docs/guide/examplegen.md index aff3284de2..bbd9e43173 100644 --- a/docs/guide/examplegen.md +++ b/docs/guide/examplegen.md @@ -37,9 +37,10 @@ See the usage examples in the source code and [this discussion](examplegen.md#custom_examplegen) for more information on how to use and develop custom executors. -Note: In most case it's better to inherit from `base_example_gen_executor` -instead of `base_executor`. So following the Avro or Parquet example in the -Executor source code may be advisable. +!!! Note + In most case it's better to inherit from `base_example_gen_executor` + instead of `base_executor`. So following the Avro or Parquet example in the + Executor source code may be advisable. In addition, these data sources and formats are available as [custom component](understanding_custom_components.md) examples: @@ -92,7 +93,8 @@ data. ### Custom input/output split -Note: this feature is only available after TFX 0.14. +!!! Note + This feature is only available after TFX 0.14. To customize the train/eval split ratio which ExampleGen will output, set the `output_config` for ExampleGen component. For example: @@ -185,7 +187,8 @@ Notice how the `partition_feature_name` was set in this example. ### Span -Note: this feature is only available after TFX 0.15. +!!! Note + This feature is only available after TFX 0.15. Span can be retrieved by using '{SPAN}' spec in the [input glob pattern](https://github.com/tensorflow/tfx/blob/master/tfx/proto/example_gen.proto): @@ -244,7 +247,8 @@ Retrieving a certain span can be done with RangeConfig, which is detailed below. ### Date -Note: this feature is only availible after TFX 0.24.0. +!!! Note + This feature is only availible after TFX 0.24.0. If your data source is organized on filesystem by date, TFX supports mapping dates directly to span numbers. There are three specs to represent mapping from @@ -303,7 +307,8 @@ example_gen = CsvExampleGen(input_base='/tmp', input_config=input) ### Version -Note: this feature is only availible after TFX 0.24.0. +!!! Note + This feature is only availible after TFX 0.24.0. Version can be retrieved by using '{VERSION}' spec in the [input glob pattern](https://github.com/tensorflow/tfx/blob/master/tfx/proto/example_gen.proto): @@ -363,7 +368,8 @@ example_gen = CsvExampleGen(input_base='/tmp', input_config=input) ### Range Config -Note: this feature is only available after TFX 0.24.0. +!!! Note + This feature is only available after TFX 0.24.0. TFX supports retrieval and processing of a specific span in file-based ExampleGen using range config, an abstract config used to describe ranges for From 6de0d62769f11ddaa160ac6adf910ef96f6c6bba Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:42:30 -0700 Subject: [PATCH 20/58] Fix links in examplegen guide page --- docs/guide/examplegen.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/examplegen.md b/docs/guide/examplegen.md index bbd9e43173..a08e09ba56 100644 --- a/docs/guide/examplegen.md +++ b/docs/guide/examplegen.md @@ -636,6 +636,6 @@ evaluator = Evaluator( More details are available in the [CsvExampleGen API reference][tfx.v1.components.CsvExampleGen], -[FileBasedExampleGen API implementation][tfx.v1.components.example_gen.component], +[FileBasedExampleGen API implementation](https://github.com/tensorflow/tfx/blob/master/tfx/components/example_gen/component.py), and -[ImportExampleGen API reference][tfx.v1.components/ImportExampleGen]. +[ImportExampleGen API reference][tfx.v1.components.ImportExampleGen]. From 22fc82d1aab9f37b61eb4631e45d68ae6f292d38 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:44:41 -0700 Subject: [PATCH 21/58] Add `__init__.py` to help fix link --- tfx/components/schema_gen/import_schema_gen/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tfx/components/schema_gen/import_schema_gen/__init__.py diff --git a/tfx/components/schema_gen/import_schema_gen/__init__.py b/tfx/components/schema_gen/import_schema_gen/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From b1a04a84848398b06ecadedd847e144ab9ac83db Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:02:24 -0700 Subject: [PATCH 22/58] Fix note admonition in transform guide page --- docs/guide/transform.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/guide/transform.md b/docs/guide/transform.md index 753f82fa42..8ad130ffc9 100644 --- a/docs/guide/transform.md +++ b/docs/guide/transform.md @@ -78,8 +78,9 @@ By contrast, TensorFlow Transform is designed for transformations that require a full pass over the data to compute values that are not known in advance. For example, vocabulary generation requires a full pass over the data. -Note: These computations are implemented in [Apache Beam](https://beam.apache.org/) -under the hood. +!!! Note + These computations are implemented in [Apache Beam](https://beam.apache.org/) + under the hood. In addition to computing values using Apache Beam, TensorFlow Transform allows users to embed these values into a TensorFlow graph, which can then be loaded From e1323c9fdeb68e10c76743a54e323bc39f9d432f Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:10:43 -0700 Subject: [PATCH 23/58] Fix note admonition for trainer guide page --- docs/guide/trainer.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guide/trainer.md b/docs/guide/trainer.md index 0b94a62c09..ba80f2e4ca 100644 --- a/docs/guide/trainer.md +++ b/docs/guide/trainer.md @@ -7,7 +7,8 @@ The Trainer TFX pipeline component trains a TensorFlow model. Trainer makes extensive use of the Python [TensorFlow](https://www.tensorflow.org) API for training models. -Note: TFX supports TensorFlow 1.15 and 2.x. +!!! Note + TFX supports TensorFlow 1.15 and 2.x. ## Component From c0c34c3397397dd51b6aface7bd086c128200a8b Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:12:28 -0700 Subject: [PATCH 24/58] Fix note admonition for tuner guide page --- docs/guide/tuner.md | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/docs/guide/tuner.md b/docs/guide/tuner.md index abba1a7505..a2cb39f790 100644 --- a/docs/guide/tuner.md +++ b/docs/guide/tuner.md @@ -8,8 +8,9 @@ The Tuner component makes extensive use of the Python [KerasTuner](https://www.tensorflow.org/tutorials/keras/keras_tuner) API for tuning hyperparameters. -Note: The KerasTuner library can be used for hyperparameter tuning regardless of -the modeling API, not just for Keras models only. +!!! Note + The KerasTuner library can be used for hyperparameter tuning regardless of + the modeling API, not just for Keras models only. ## Component @@ -206,22 +207,24 @@ algorithm uses information from results of prior trials, such as Google Vizier algorithm implemented in the AI Platform Vizier does, an excessively parallel search would negatively affect the efficacy of the search. -Note: Each trial in each parallel search is conducted on a single machine in the -worker flock, i.e., each trial does not take advantage of multi-worker -distributed training. If multi-worker distribution is desired for each trial, -refer to -[`DistributingCloudTuner`](https://github.com/tensorflow/cloud/blob/b9c8752f5c53f8722dfc0b5c7e05be52e62597a8/src/python/tensorflow_cloud/tuner/tuner.py#L384-L676), -instead of `CloudTuner`. - -Note: Both `CloudTuner` and the Google Cloud AI Platform extensions Tuner -component can be used together, in which case it allows distributed parallel -tuning backed by the AI Platform Vizier's hyperparameter search algorithm. -However, in order to do so, the Cloud AI Platform Job must be given access to -the AI Platform Vizier service. See this -[guide](https://cloud.google.com/ai-platform/training/docs/custom-service-account#custom) -to set up a custom service account. After that, you should specify the custom -service account for your training job in the pipeline code. More details see -[E2E CloudTuner on GCP example](https://github.com/tensorflow/tfx/blob/master/tfx/examples/penguin/penguin_pipeline_kubeflow.py). +!!! Note + Each trial in each parallel search is conducted on a single machine in the + worker flock, i.e., each trial does not take advantage of multi-worker + distributed training. If multi-worker distribution is desired for each trial, + refer to + [`DistributingCloudTuner`](https://github.com/tensorflow/cloud/blob/b9c8752f5c53f8722dfc0b5c7e05be52e62597a8/src/python/tensorflow_cloud/tuner/tuner.py#L384-L676), + instead of `CloudTuner`. + +!!! Note + Both `CloudTuner` and the Google Cloud AI Platform extensions Tuner + component can be used together, in which case it allows distributed parallel + tuning backed by the AI Platform Vizier's hyperparameter search algorithm. + However, in order to do so, the Cloud AI Platform Job must be given access to + the AI Platform Vizier service. See this + [guide](https://cloud.google.com/ai-platform/training/docs/custom-service-account#custom) + to set up a custom service account. After that, you should specify the custom + service account for your training job in the pipeline code. More details see + [E2E CloudTuner on GCP example](https://github.com/tensorflow/tfx/blob/master/tfx/examples/penguin/penguin_pipeline_kubeflow.py). ## Links From 87cd42ee7a49fe8f5c35bec5c3d782f1b588997a Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:14:42 -0700 Subject: [PATCH 25/58] Fix note admonition for infravalidator guide page --- docs/guide/infra_validator.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/guide/infra_validator.md b/docs/guide/infra_validator.md index 1daeea2856..ef2f6edf20 100644 --- a/docs/guide/infra_validator.md +++ b/docs/guide/infra_validator.md @@ -91,11 +91,12 @@ For model server types (called serving binary) we support - [TensorFlow Serving](serving.md) -Note: InfraValidator allows specifying multiple versions of the same model -server type in order to upgrade the model server version without affecting model -compatibility. For example, user can test `tensorflow/serving` image with both -`2.1.0` and `latest` versions, to ensure the model will be compatible with the -latest `tensorflow/serving` version as well. +!!! Note + InfraValidator allows specifying multiple versions of the same model + server type in order to upgrade the model server version without affecting model + compatibility. For example, user can test `tensorflow/serving` image with both + `2.1.0` and `latest` versions, to ensure the model will be compatible with the + latest `tensorflow/serving` version as well. Following serving platforms are currently supported: From 4a7969e9e6063f6a2e540f2962670585d32e4e6a Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:22:26 -0700 Subject: [PATCH 26/58] Fix note admonitions in "Custom Python function components" guide page --- docs/guide/custom_function_component.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/guide/custom_function_component.md b/docs/guide/custom_function_component.md index 8aca8be9aa..393ad9ea27 100644 --- a/docs/guide/custom_function_component.md +++ b/docs/guide/custom_function_component.md @@ -35,9 +35,10 @@ Under the hood, this defines a custom component that is a subclass of [`BaseComponent`](https://github.com/tensorflow/tfx/blob/master/tfx/dsl/components/base/base_component.py){: .external } and its Spec and Executor classes. -Note: the feature (BaseBeamComponent based component by annotating a function -with `@component(use_beam=True)`) described below is experimental and there is -no public backwards compatibility guarantees. +!!! Note + the feature (BaseBeamComponent based component by annotating a function + with `@component(use_beam=True)`) described below is experimental and there is + no public backwards compatibility guarantees. If you want to define a subclass of [`BaseBeamComponent`](https://github.com/tensorflow/tfx/blob/master/tfx/dsl/components/base/base_beam_component.py){: .external } @@ -79,10 +80,11 @@ arguments and hyperparameters like training iteration count, dropout rate, and other configuration to your component. Parameters are stored as properties of component executions when tracked in ML Metadata. -Note: Currently, output simple data type values cannot be used as parameters -since they are not known at execution time. Similarly, input simple data type -values currently cannot take concrete values known at pipeline construction -time. We may remove this restriction in a future release of TFX. +!!! Note + Currently, output simple data type values cannot be used as parameters + since they are not known at execution time. Similarly, input simple data type + values currently cannot take concrete values known at pipeline construction + time. We may remove this restriction in a future release of TFX. ## Definition From c0aadc713a2bd260fda32b30689154905b8540d8 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:25:57 -0700 Subject: [PATCH 27/58] Fix capitalization --- docs/guide/custom_function_component.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/custom_function_component.md b/docs/guide/custom_function_component.md index 393ad9ea27..bf61bed771 100644 --- a/docs/guide/custom_function_component.md +++ b/docs/guide/custom_function_component.md @@ -36,7 +36,7 @@ Under the hood, this defines a custom component that is a subclass of and its Spec and Executor classes. !!! Note - the feature (BaseBeamComponent based component by annotating a function + The feature (BaseBeamComponent based component by annotating a function with `@component(use_beam=True)`) described below is experimental and there is no public backwards compatibility guarantees. From 3adb4c1ee4546d85f0245ea4dd7f941b5b746dc8 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 22:26:06 -0700 Subject: [PATCH 28/58] Fix admonitions in cli guide page --- docs/guide/cli.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 855f5d2bdd..462f77df0a 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -10,8 +10,9 @@ can use the CLI to: * Run a pipeline and monitor the run on various orchestrators. * List pipelines and pipeline runs. -Note: The TFX CLI doesn't currently provide compatibility guarantees. The CLI -interface might change as new versions are released. +!!! Note + The TFX CLI doesn't currently provide compatibility guarantees. The CLI + interface might change as new versions are released. ## About the TFX CLI @@ -35,8 +36,9 @@ instructions in the [pipeline commands](#tfx-pipeline), [run commands](#tfx-run), and [template commands](#tfx-template-experimental) sections to learn more about using these commands. -Warning: Currently not all commands are supported in every orchestrator. Such -commands explicitly mention the engines supported. +!!! Warning + Currently not all commands are supported in every orchestrator. Such + commands explicitly mention the engines supported. Flags let you pass arguments into CLI commands. Words in flags are separated with either a hyphen (`-`) or an underscore (`_`). For example, the pipeline From a1ed39a7ec2c488da95c22ee17e96aa5dc726aa3 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Wed, 11 Sep 2024 23:16:58 -0700 Subject: [PATCH 29/58] Enable tables and definition lists --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 740db52194..8c7e14c4b8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,6 +82,8 @@ plugins: markdown_extensions: - admonition - attr_list + - def_list + - tables - toc: permalink: true - pymdownx.highlight: From a88f513c24dc431778ab6b8be0183297545adfd2 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:15:10 -0700 Subject: [PATCH 30/58] Command Line Guide: Update definition list and fix admonitions Other minor formatting changes --- docs/guide/cli.md | 1225 +++++++++++++++++---------------------------- 1 file changed, 447 insertions(+), 778 deletions(-) diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 462f77df0a..cadcab772f 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -64,118 +64,76 @@ Creates a new pipeline in the given orchestrator. Usage: ```bash -tfx pipeline create --pipeline_path=pipeline-path [--endpoint=endpoint --engine=engine \ ---iap_client_id=iap-client-id --namespace=namespace \ ---build_image --build_base_image=build-base-image] +tfx pipeline create --pipeline_path=pipeline-path [--endpoint=endpoint --engine=engine \ +--iap_client_id=iap-client-id --namespace=namespace \ +--build_image --build_base_image=build-base-image] ``` -
-
--pipeline_path=pipeline-path
-
The path to the pipeline configuration file.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint when using Kubeflow Pipelines. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
- -
--build_image
-
-

- (Optional.) When the engine is kubeflow or vertex, TFX - creates a container image for your pipeline if specified. `Dockerfile` in - the current directory will be used, and TFX will automatically generate - one if not exists. -

-

- The built image will be pushed to the remote registry which is specified - in `KubeflowDagRunnerConfig` or `KubeflowV2DagRunnerConfig`. -

-
-
--build_base_image=build-base-image
-
-

- (Optional.) When the engine is kubeflow, TFX - creates a container image for your pipeline. The build base image - specifies the base container image to use when building the pipeline - container image. -

-
-
+\--pipeline\_path=`pipeline-path`{.variable} +: The path to the pipeline configuration file. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **local**: sets engine to local orchestrator + - **vertex**: sets engine to Vertex Pipelines + - **airflow**: (experimental) sets engine to Apache Airflow + - **beam**: (experimental) sets engine to Apache Beam + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint when using Kubeflow Pipelines. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + +\--build\_image + +: (Optional.) When the `engine`{.variable} is **kubeflow** or **vertex**, TFX creates a container image for your pipeline if specified. `Dockerfile` in the current directory will be used, and TFX will automatically generate one if not exists. + + The built image will be pushed to the remote registry which is specified in `KubeflowDagRunnerConfig` or `KubeflowV2DagRunnerConfig`. + +\--build\_base\_image=`build-base-image`{.variable} + +: (Optional.) When the `engine`{.variable} is **kubeflow**, TFX creates a container image for your pipeline. The build base image specifies the base container image to use when building the pipeline container image. + #### Examples Kubeflow: ```bash -tfx pipeline create --engine=kubeflow --pipeline_path=pipeline-path \ ---iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint \ +tfx pipeline create --engine=kubeflow --pipeline_path=pipeline-path \ +--iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint \ --build_image ``` Local: ```bash -tfx pipeline create --engine=local --pipeline_path=pipeline-path +tfx pipeline create --engine=local --pipeline_path=pipeline-path ``` Vertex: ```bash -tfx pipeline create --engine=vertex --pipeline_path=pipeline-path \ +tfx pipeline create --engine=vertex --pipeline_path=pipeline-path \ --build_image ``` @@ -183,7 +141,7 @@ To autodetect engine from user environment, simply avoid using the engine flag like the example below. For more details, check the flags section. ```bash -tfx pipeline create --pipeline_path=pipeline-path +tfx pipeline create --pipeline_path=pipeline-path ``` ### update @@ -193,106 +151,71 @@ Updates an existing pipeline in the given orchestrator. Usage: ```bash -tfx pipeline update --pipeline_path=pipeline-path [--endpoint=endpoint --engine=engine \ ---iap_client_id=iap-client-id --namespace=namespace --build_image] +tfx pipeline update --pipeline_path=pipeline-path [--endpoint=endpoint --engine=engine \ +--iap_client_id=iap-client-id --namespace=namespace --build_image] ``` -
-
--pipeline_path=pipeline-path
-
The path to the pipeline configuration file.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
-
--build_image
-
-

- (Optional.) When the engine is kubeflow or vertex, TFX - creates a container image for your pipeline if specified. `Dockerfile` in - the current directory will be used. -

-

- The built image will be pushed to the remote registry which is specified - in `KubeflowDagRunnerConfig` or `KubeflowV2DagRunnerConfig`. -

-
-
+\--pipeline\_path=`pipeline-path`{.variable} +: The path to the pipeline configuration file. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **local**: sets engine to local orchestrator + - **vertex**: sets engine to Vertex Pipelines + - **airflow**: (experimental) sets engine to Apache Airflow + - **beam**: (experimental) sets engine to Apache Beam + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + +\--build\_image + +: (Optional.) When the `engine`{.variable} is **kubeflow** or **vertex**, TFX creates a container image for your pipeline if specified. `Dockerfile` in the current directory will be used. + + The built image will be pushed to the remote registry which is specified in `KubeflowDagRunnerConfig` or `KubeflowV2DagRunnerConfig`. + #### Examples Kubeflow: ```bash -tfx pipeline update --engine=kubeflow --pipeline_path=pipeline-path \ ---iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint \ +tfx pipeline update --engine=kubeflow --pipeline_path=pipeline-path \ +--iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint \ --build_image ``` Local: ```bash -tfx pipeline update --engine=local --pipeline_path=pipeline-path +tfx pipeline update --engine=local --pipeline_path=pipeline-path ``` Vertex: ```bash -tfx pipeline update --engine=vertex --pipeline_path=pipeline-path \ +tfx pipeline update --engine=vertex --pipeline_path=pipeline-path \ --build_image ``` @@ -313,57 +236,46 @@ Recommended to use before creating or updating a pipeline. Usage: ```bash -tfx pipeline compile --pipeline_path=pipeline-path [--engine=engine] +tfx pipeline compile --pipeline_path=pipeline-path [--engine=engine] ``` -
-
--pipeline_path=pipeline-path
-
The path to the pipeline configuration file.
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
+\--pipeline\_path=`pipeline-path`{.variable} +: The path to the pipeline configuration file. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **local**: sets engine to local orchestrator + - **vertex**: sets engine to Vertex Pipelines + - **airflow**: (experimental) sets engine to Apache Airflow + - **beam**: (experimental) sets engine to Apache Beam + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + #### Examples Kubeflow: ```bash -tfx pipeline compile --engine=kubeflow --pipeline_path=pipeline-path +tfx pipeline compile --engine=kubeflow --pipeline_path=pipeline-path ``` Local: ```bash -tfx pipeline compile --engine=local --pipeline_path=pipeline-path +tfx pipeline compile --engine=local --pipeline_path=pipeline-path ``` Vertex: ```bash -tfx pipeline compile --engine=vertex --pipeline_path=pipeline-path +tfx pipeline compile --engine=vertex --pipeline_path=pipeline-path ``` ### delete @@ -373,93 +285,64 @@ Deletes a pipeline from the given orchestrator. Usage: ```bash -tfx pipeline delete --pipeline_path=pipeline-path [--endpoint=endpoint --engine=engine \ ---iap_client_id=iap-client-id --namespace=namespace] +tfx pipeline delete --pipeline_path=pipeline-path [--endpoint=endpoint --engine=engine \ +--iap_client_id=iap-client-id --namespace=namespace] ``` -
-
--pipeline_path=pipeline-path
-
The path to the pipeline configuration file.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
-
+\--pipeline\_path=`pipeline-path`{.variable} +: The path to the pipeline configuration file. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **local**: sets engine to local orchestrator + - **vertex**: sets engine to Vertex Pipelines + - **airflow**: (experimental) sets engine to Apache Airflow + - **beam**: (experimental) sets engine to Apache Beam + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + #### Examples Kubeflow: ```bash -tfx pipeline delete --engine=kubeflow --pipeline_name=pipeline-name \ ---iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint +tfx pipeline delete --engine=kubeflow --pipeline_name=pipeline-name \ +--iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint ``` Local: ```bash -tfx pipeline delete --engine=local --pipeline_name=pipeline-name +tfx pipeline delete --engine=local --pipeline_name=pipeline-name ``` Vertex: ```bash -tfx pipeline delete --engine=vertex --pipeline_name=pipeline-name +tfx pipeline delete --engine=vertex --pipeline_name=pipeline-name ``` ### list @@ -469,79 +352,49 @@ Lists all the pipelines in the given orchestrator. Usage: ```bash -tfx pipeline list [--endpoint=endpoint --engine=engine \ ---iap_client_id=iap-client-id --namespace=namespace] +tfx pipeline list [--endpoint=endpoint --engine=engine \ +--iap_client_id=iap-client-id --namespace=namespace] ``` -
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
-
+\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **local**: sets engine to local orchestrator + - **vertex**: sets engine to Vertex Pipelines + - **airflow**: (experimental) sets engine to Apache Airflow + - **beam**: (experimental) sets engine to Apache Beam + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + #### Examples Kubeflow: ```bash -tfx pipeline list --engine=kubeflow --iap_client_id=iap-client-id \ ---namespace=namespace --endpoint=endpoint +tfx pipeline list --engine=kubeflow --iap_client_id=iap-client-id \ +--namespace=namespace --endpoint=endpoint ``` Local: @@ -561,7 +414,7 @@ tfx pipeline list --engine=vertex The structure for commands in the `tfx run` command group is as follows: ```bash -tfx run command required-flags [optional-flags] +tfx run command required-flags [optional-flags] ``` Use the following sections to learn more about the commands in the `tfx run` @@ -575,446 +428,295 @@ most recent pipeline version of the pipeline in the cluster is used. Usage: ```bash -tfx run create --pipeline_name=pipeline-name [--endpoint=endpoint \ ---engine=engine --iap_client_id=iap-client-id --namespace=namespace] +tfx run create --pipeline_name=pipeline-name [--endpoint=endpoint \ +--engine=engine --iap_client_id=iap-client-id --namespace=namespace] ``` -
-
--pipeline_name=pipeline-name
-
The name of the pipeline.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
- -
--runtime_parameter=parameter-name=parameter-value
-
- (Optional.) Sets a runtime parameter value. Can be set multiple times to set - values of multiple variables. Only applicable to `airflow`, `kubeflow` and - `vertex` engine. -
- -
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace
-
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
- -
--project=GCP-project-id
-
- (Required for Vertex.) GCP project id for the vertex pipeline. -
- -
--region=GCP-region
-
- (Required for Vertex.) GCP region name like us-central1. See [Vertex documentation](https://cloud.google.com/vertex-ai/docs/general/locations) for available regions. -
- -
+\--pipeline\_name=`pipeline-name`{.variable} +: The name of the pipeline. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **local**: sets engine to local orchestrator + - **vertex**: sets engine to Vertex Pipelines + - **airflow**: (experimental) sets engine to Apache Airflow + - **beam**: (experimental) sets engine to Apache Beam + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--runtime\_parameter=`parameter-name`{.variable}=`parameter-value`{.variable} +: (Optional.) Sets a runtime parameter value. Can be set multiple times to set values of multiple variables. Only applicable to `airflow`, `kubeflow` and `vertex` engine. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + +\--project=`GCP-project-id`{.variable} +: (Required for Vertex.) GCP project id for the vertex pipeline. + +\--region=`GCP-region`{.variable} +: (Required for Vertex.) GCP region name like us-central1. See \[Vertex documentation\](https://cloud.google.com/vertex-ai/docs/general/locations) for available regions. + #### Examples Kubeflow: ```bash -tfx run create --engine=kubeflow --pipeline_name=pipeline-name --iap_client_id=iap-client-id \ ---namespace=namespace --endpoint=endpoint +tfx run create --engine=kubeflow --pipeline_name=pipeline-name --iap_client_id=iap-client-id \ +--namespace=namespace --endpoint=endpoint ``` Local: ```bash -tfx run create --engine=local --pipeline_name=pipeline-name +tfx run create --engine=local --pipeline_name=pipeline-name ``` Vertex: ```bash -tfx run create --engine=vertex --pipeline_name=pipeline-name \ - --runtime_parameter=var_name=var_value \ - --project=gcp-project-id --region=gcp-region +tfx run create --engine=vertex --pipeline_name=pipeline-name \ + --runtime_parameter=var_name=var_value \ + --project=gcp-project-id --region=gcp-region ``` ### terminate Stops a run of a given pipeline. -** Important Note: Currently supported only in Kubeflow. +!!! note "Important Note" + Currently supported only in Kubeflow. Usage: ```bash -tfx run terminate --run_id=run-id [--endpoint=endpoint --engine=engine \ ---iap_client_id=iap-client-id --namespace=namespace] +tfx run terminate --run_id=run-id [--endpoint=endpoint --engine=engine \ +--iap_client_id=iap-client-id --namespace=namespace] ``` -
-
--run_id=run-id
-
Unique identifier for a pipeline run.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
-
+\--run\_id=`run-id`{.variable} +: Unique identifier for a pipeline run. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + #### Examples Kubeflow: ```bash -tfx run delete --engine=kubeflow --run_id=run-id --iap_client_id=iap-client-id \ ---namespace=namespace --endpoint=endpoint +tfx run delete --engine=kubeflow --run_id=run-id --iap_client_id=iap-client-id \ +--namespace=namespace --endpoint=endpoint ``` ### list Lists all runs of a pipeline. -** Important Note: Currently not supported in Local and Apache Beam. +!!! note "Important Note" + Currently not supported in Local and Apache Beam. Usage: ```bash -tfx run list --pipeline_name=pipeline-name [--endpoint=endpoint \ ---engine=engine --iap_client_id=iap-client-id --namespace=namespace] +tfx run list --pipeline_name=pipeline-name [--endpoint=endpoint \ +--engine=engine --iap_client_id=iap-client-id --namespace=namespace] ``` -
-
--pipeline_name=pipeline-name
-
The name of the pipeline.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
-
+\--pipeline\_name=`pipeline-name`{.variable} +: The name of the pipeline. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **airflow**: (experimental) sets engine to Apache Airflow + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. #### Examples Kubeflow: ```bash -tfx run list --engine=kubeflow --pipeline_name=pipeline-name --iap_client_id=iap-client-id \ ---namespace=namespace --endpoint=endpoint +tfx run list --engine=kubeflow --pipeline_name=pipeline-name --iap_client_id=iap-client-id \ +--namespace=namespace --endpoint=endpoint ``` ### status Returns the current status of a run. -** Important Note: Currently not supported in Local and Apache Beam. +!!! note "Important Note" + Currently not supported in Local and Apache Beam. Usage: ```bash -tfx run status --pipeline_name=pipeline-name --run_id=run-id [--endpoint=endpoint \ ---engine=engine --iap_client_id=iap-client-id --namespace=namespace] +tfx run status --pipeline_name=pipeline-name --run_id=run-id [--endpoint=endpoint \ +--engine=engine --iap_client_id=iap-client-id --namespace=namespace] ``` -
-
--pipeline_name=pipeline-name
-
The name of the pipeline.
-
--run_id=run-id
-
Unique identifier for a pipeline run.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
-
+\--pipeline\_name=`pipeline-name`{.variable} +: The name of the pipeline. + +\--run\_id=`run-id`{.variable} +: Unique identifier for a pipeline run. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **airflow**: (experimental) sets engine to Apache Airflow + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + #### Examples Kubeflow: ```bash -tfx run status --engine=kubeflow --run_id=run-id --pipeline_name=pipeline-name \ ---iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint +tfx run status --engine=kubeflow --run_id=run-id --pipeline_name=pipeline-name \ +--iap_client_id=iap-client-id --namespace=namespace --endpoint=endpoint ``` ### delete Deletes a run of a given pipeline. -** Important Note: Currently supported only in Kubeflow +!!! note Important Note + Currently supported only in Kubeflow Usage: ```bash -tfx run delete --run_id=run-id [--engine=engine --iap_client_id=iap-client-id \ ---namespace=namespace --endpoint=endpoint] +tfx run delete --run_id=run-id [--engine=engine --iap_client_id=iap-client-id \ +--namespace=namespace --endpoint=endpoint] ``` -
-
--run_id=run-id
-
Unique identifier for a pipeline run.
-
--endpoint=endpoint
-
-

- (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
-
--engine=engine
-
-

- (Optional.) The orchestrator to be used for the pipeline. The value of - engine must match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
-
--iap_client_id=iap-client-id
-
- (Optional.) Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. - If the namespace is not specified, the value defaults to - kubeflow. -
-
+\--run\_id=`run-id`{.variable} +: Unique identifier for a pipeline run. + +\--endpoint=`endpoint`{.variable} + +: (Optional.) Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--engine=`engine`{.variable} + +: (Optional.) The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--iap\_client\_id=`iap-client-id`{.variable} +: (Optional.) Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: (Optional.) Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + #### Examples Kubeflow: ```bash -tfx run delete --engine=kubeflow --run_id=run-id --iap_client_id=iap-client-id \ ---namespace=namespace --endpoint=endpoint +tfx run delete --engine=kubeflow --run_id=run-id --iap_client_id=iap-client-id \ +--namespace=namespace --endpoint=endpoint ``` ## tfx template [Experimental] @@ -1022,7 +724,7 @@ tfx run delete --engine=kubeflow --run_id=run-id --iap_client_id=command required-flags [optional-flags] +tfx template command required-flags [optional-flags] ``` Use the following sections to learn more about the commands in the `tfx @@ -1046,100 +748,67 @@ Copy a template to the destination directory. Usage: ```bash -tfx template copy --model=model --pipeline_name=pipeline-name \ ---destination_path=destination-path +tfx template copy --model=model --pipeline_name=pipeline-name \ +--destination_path=destination-path ``` -
-
--model=model
-
The name of the model built by the pipeline template.
-
--pipeline_name=pipeline-name
-
The name of the pipeline.
-
--destination_path=destination-path
-
The path to copy the template to.
-
+\--model=`model`{.variable} +: The name of the model built by the pipeline template. + +\--pipeline\_name=`pipeline-name`{.variable} +: The name of the pipeline. + +\--destination\_path=`destination-path`{.variable} +: The path to copy the template to. + ## Understanding TFX CLI Flags ### Common flags -
-
--engine=engine
-
-

- The orchestrator to be used for the pipeline. The value of engine must - match on of the following values: -

- -

- If the engine is not set, the engine is auto-detected based on the - environment. -

-

- ** Important note: The orchestrator required by the DagRunner in the - pipeline config file must match the selected or autodetected engine. - Engine auto-detection is based on user environment. If Apache Airflow - and Kubeflow Pipelines are not installed, then the local orchestrator is - used by default. -

-
- -
--pipeline_name=pipeline-name
-
The name of the pipeline.
- -
--pipeline_path=pipeline-path
-
The path to the pipeline configuration file.
- -
--run_id=run-id
-
Unique identifier for a pipeline run.
- -
+\--engine=`engine`{.variable} + +: The orchestrator to be used for the pipeline. The value of engine must match on of the following values: + + - **kubeflow**: sets engine to Kubeflow + - **local**: sets engine to local orchestrator + - **vertex**: sets engine to Vertex Pipelines + - **airflow**: (experimental) sets engine to Apache Airflow + - **beam**: (experimental) sets engine to Apache Beam + + If the engine is not set, the engine is auto-detected based on the environment. + + !!! note "Important Note" + The orchestrator required by the DagRunner in the pipeline config file must match the selected or autodetected engine. Engine auto-detection is based on user environment. If Apache Airflow and Kubeflow Pipelines are not installed, then the local orchestrator is used by default. + +\--pipeline\_name=`pipeline-name`{.variable} +: The name of the pipeline. + +\--pipeline\_path=`pipeline-path`{.variable} +: The path to the pipeline configuration file. + +\--run\_id=`run-id`{.variable} +: Unique identifier for a pipeline run. + ### Kubeflow specific flags -
-
--endpoint=endpoint
-
-

- Endpoint of the Kubeflow Pipelines API service. The endpoint - of your Kubeflow Pipelines API service is the same as URL of the Kubeflow - Pipelines dashboard. Your endpoint value should be something like: -

- -
https://host-name/pipeline
- -

- If you do not know the endpoint for your Kubeflow Pipelines cluster, - contact you cluster administrator. -

- -

- If the --endpoint is not specified, the in-cluster service - DNS name is used as the default value. This name works only if the - CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a - Kubeflow Jupyter notebooks instance. -

-
- -
--iap_client_id=iap-client-id
-
- Client ID for IAP protected endpoint. -
- -
--namespace=namespace -
- Kubernetes namespace to connect to the Kubeflow Pipelines API. If the - namespace is not specified, the value defaults to - kubeflow. -
-
+\--endpoint=`endpoint`{.variable} + +: Endpoint of the Kubeflow Pipelines API service. The endpoint of your Kubeflow Pipelines API service is the same as URL of the Kubeflow Pipelines dashboard. Your endpoint value should be something like: + + https://host-name/pipeline + + If you do not know the endpoint for your Kubeflow Pipelines cluster, contact you cluster administrator. + + If the `--endpoint` is not specified, the in-cluster service DNS name is used as the default value. This name works only if the CLI command executes in a pod on the Kubeflow Pipelines cluster, such as a [Kubeflow Jupyter notebooks](https://www.kubeflow.org/docs/components/notebooks/jupyter-tensorflow-examples/){.external} instance. + +\--iap\_client\_id=`iap-client-id`{.variable} +: Client ID for IAP protected endpoint. + +\--namespace=`namespace`{.variable} +: Kubernetes namespace to connect to the Kubeflow Pipelines API. If the namespace is not specified, the value defaults to `kubeflow`. + ## Generated files by TFX CLI From 9f3b7f3cc654dfbf7f83bcb92e9ba3445173a7e2 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:22:49 -0700 Subject: [PATCH 31/58] Fix note admonitions for tfdv guide page --- docs/guide/tfdv.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/guide/tfdv.md b/docs/guide/tfdv.md index b496170d86..5ed4b83771 100644 --- a/docs/guide/tfdv.md +++ b/docs/guide/tfdv.md @@ -146,9 +146,10 @@ This triggers an automatic schema generation based on the following rules: * Otherwise, TensorFlow Data Validation examines the available data statistics and computes a suitable schema for the data. -_Note: The auto-generated schema is best-effort and only tries to infer basic -properties of the data. It is expected that users review and modify it as -needed._ +!!! Note + The auto-generated schema is best-effort and only tries to infer basic + properties of the data. It is expected that users review and modify it as + needed. ### Training-Serving Skew Detection @@ -164,10 +165,11 @@ the serving data to train on. ##### Example Scenario -Note: For instance, in order to compensate for an underrepresented slice of -data, if a biased sampling is used without upweighting the downsampled examples -appropriately, the distribution of feature values between training and -serving data gets artificially skewed. +!!! Note + For instance, in order to compensate for an underrepresented slice of + data, if a biased sampling is used without upweighting the downsampled examples + appropriately, the distribution of feature values between training and + serving data gets artificially skewed. See the [TensorFlow Data Validation Get Started Guide](https://www.tensorflow.org/tfx/data_validation/get_started#checking_data_skew_and_drift) for information about configuring training-serving skew detection. From c9b4f7fccc189d53712bb21ff91e884ac47bdd67 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Thu, 12 Sep 2024 00:42:18 -0700 Subject: [PATCH 32/58] TFT Best Practices: Minor formatting changes --- docs/guide/tft_bestpractices.md | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/docs/guide/tft_bestpractices.md b/docs/guide/tft_bestpractices.md index 11bd10ad52..7488759fbd 100644 --- a/docs/guide/tft_bestpractices.md +++ b/docs/guide/tft_bestpractices.md @@ -114,13 +114,6 @@ Figure: The flow of data from raw data to prepared data to engineered features t ![Flow diagram showing raw data moving to prepared data moving to engineered features.](images/data-preprocessing-for-ml-with-tf-transform-data-preprocessing-flow.svg) - - In practice, data from the same source is often at different stages of readiness. For example, a field from a table in your data warehouse might be used directly as an engineered feature. At the same time, another field in the @@ -238,7 +231,7 @@ on operation granularity: values that are computed during training are used to adjust the feature value, which is the following simple *instance-level* operation: - \[ value_{scaled} = (value_{raw} - \mu) \div \sigma \] + \[ value_{\text{scaled}} = \frac{value_{\text{raw}} - \mu}{\sigma} \] Full-pass transformations include the following: @@ -306,7 +299,7 @@ on operation granularity: before training and prediction. -## ML pipeline on Google Cloud{: id="machine_learning_pipeline_on_gcp" } +## ML pipeline on Google Cloud This section discusses the core components of a typical end-to-end pipeline to train and serve TensorFlow ML models on Google Cloud using @@ -329,13 +322,6 @@ Figure: High-level architecture for ML training and serving on Google Cloud. {#h ![Architecture diagram showing stages for processing data.](images/data-preprocessing-for-ml-with-tf-transform-ml-training-serving-architecture.svg) - - The pipeline consists of the following steps: 1. After raw data is imported, tabular data is stored in BigQuery, and other @@ -461,13 +447,6 @@ Figure: High-level architecture using stream data for prediction in Dataflow. {# ![Architecture for using stream data for prediction.](images/data-preprocessing-for-ml-with-tf-transform-streaming-data-with-dataflow-architecture.svg) - - As shown in figure 3, during processing, events called *data points* are ingested into [Pub/Sub](https://cloud.google.com/pubsub/docs){: .external }. Dataflow consumes these data points, computes features based on aggregates over @@ -627,14 +606,6 @@ Figure: Behavior of `tf.Transform` for preprocessing and transforming data. ![Diagram showing flow from raw data through tf.Transform to predictions.](images/data-preprocessing-for-ml-with-tf-transform-tf-transform-behavior-flow.svg) - - - ### Transform training and evaluation data You preprocess the raw training data using the transformation implemented in From 040235ca51547380edd9cc8c9a1d053ca9fae0b8 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:37:57 -0700 Subject: [PATCH 33/58] Fix table in TFT best practices --- .github/workflows/cd-docs.yml | 2 +- docs/guide/tft_bestpractices.md | 221 +++++--------------------------- mkdocs.yml | 6 + tfx/dependencies.py | 4 +- 4 files changed, 41 insertions(+), 192 deletions(-) diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 93536f52bb..6616cd5aea 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -39,7 +39,7 @@ jobs: mkdocs-material- - name: Install Dependencies - run: pip install mkdocs mkdocs-material mkdocstrings[python] griffe-inherited-docstrings mkdocs-autorefs mkdocs-jupyter mkdocs-caption + run: pip install mkdocs mkdocs-material mkdocstrings[python] griffe-inherited-docstrings mkdocs-autorefs mkdocs-jupyter mkdocs-caption markdown-grid-tables - name: Deploy to GitHub Pages run: mkdocs gh-deploy --force diff --git a/docs/guide/tft_bestpractices.md b/docs/guide/tft_bestpractices.md index 7488759fbd..4bf25d74c8 100644 --- a/docs/guide/tft_bestpractices.md +++ b/docs/guide/tft_bestpractices.md @@ -676,196 +676,37 @@ new data points during prediction serving. The following table summarizes the data preprocessing options that this document discussed. In the table, "N/A" stands for "not applicable." - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Data preprocessing option - - Instance-level
- (stateless transformations) -
-

- Full-pass during training and instance-level during serving - (stateful transformations) -

-
-

- Real-time (window) aggregations during training and serving (streaming - transformations) -

-
-

- BigQuery -  (SQL) -

-
-

- Batch scoring: OK—the same transformation implementation is - applied on data during training and batch scoring. -

-

- Online prediction: Not recommended—you can process training data, - but it results in training-serving skew because you process serving data - using different tools. -

-
-

- Batch scoring: Not recommended. -

-

- Online prediction: Not recommended. -

-

- Although you can use statistics computed using BigQuery - for instance-level batch/online transformations, it isn't easy because - you must maintain a stats store to be populated during training and - used during prediction. -

-
-

- Batch scoring: N/A—aggregates like these are computed based on - real-time events. -

-

- Online prediction: Not recommended—you can process training data, - but it results in training-serving skew because you process serving data - using different tools. -

-
-

- Dataflow (Apache Beam) -

-
-

- Batch scoring: OK—the same transformation implementation is - applied on data during training and batch scoring. -

-

- Online prediction: OK—if data at serving time comes from - Pub/Sub to be consumed by Dataflow. - Otherwise, results in training-serving skew. -

-
-

- Batch scoring: Not recommended. -

-

- Online predictions: Not recommended. -

-

- Although you can use statistics computed using Dataflow - for instance-level batch/online transformations, it isn't easy - because you must maintain a stats store to be populated during training - and used during prediction. -

-
-

- Batch scoring: N/A—aggregates like these are computed - based on real-time events. -

-

- Online prediction: OK—the same Apache Beam transformation is - applied on data during training (batch) and serving (stream). -

-
-

- Dataflow (Apache Beam + TFT) -

-
-

- Batch scoring: OK—the same transformation implementation is - applied to data during training and batch scoring. -

-

- Online prediction: Recommended—it avoids training-serving skew - and prepares training data up front. -

-
-

- Batch scoring: Recommended. -

-

- Online prediction: Recommended. -

-

- Both uses are recommended because transformation logic and computed - statistics during training are stored as a TensorFlow - graph that's attached to the exported model for serving. -

-
-

- Batch scoring: N/A—aggregates like these are computed - based on real-time events. -

-

- Online prediction: OK—the same Apache Beam transformation is - applied on data during training (batch) and serving (stream). -

-
-

- TensorFlow * -
- (input_fn & serving_fn) -

-
-

- Batch scoring: Not recommended. -

-

- Online prediction: Not recommended. -

-

- For training efficiency in both cases, it's better to prepare the - training data up front. -

-
-

- Batch scoring: Not Possible. -

-

- Online prediction: Not Possible. -

-
-

- Batch scoring: N/A—aggregates like these are computed - based on real-time events. -

- Online prediction: Not Possible. -

-
- -* With TensorFlow, transformations like crossing, embedding, ++----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Data preprocessing option | Instance-level | Full-pass during training and instance-level during serving | Real-time (window) aggregations during training and serving | +| | | | | +| | (stateless transformations) | (stateful transformations) | (streaming transformations) | ++==================================+=========================================================================================================================================================================+=============================================================================================================================================================================================================================+=========================================================================================================================================================================+ +| **BigQuery** | **Batch scoring: OK**—the same transformation implementation is applied on data during training and batch scoring. | **Batch scoring: Not recommended**. | **Batch scoring: N/A**—aggregates like these are computed based on real-time events. | +| | | | | +| (SQL) | **Online prediction: Not recommended**—you can process training data, but it results in training-serving skew because you process serving data using different | **Online prediction: Not recommended**. | **Online prediction: Not recommended**—you can process training data, but it results in training-serving skew because you process serving data using different | +| | tools. | | tools. | +| | | Although you can use statistics computed using BigQuery for instance-level batch/online transformations, it isn't easy because you must maintain a stats store to be populated during training and used during prediction. | | ++----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Dataflow** | **Batch scoring: OK**—the same transformation implementation is applied on data during training and batch scoring. | **Batch scoring: Not recommended**. | **Batch scoring: N/A**---aggregates like these are computed based on real-time events. | +| | | | | +| (Apache Beam) | **Online prediction: OK**—if data at serving time comes from Pub/Sub to be consumed by Dataflow. Otherwise, results in training-serving skew. | **Online predictions: Not recommended**. | **Online prediction: OK**—the same Apache Beam transformation is applied on data during training (batch) and serving (stream). | +| | | | | +| | | Although you can use statistics computed using Dataflow for instance-level batch/online transformations, it isn't easy because you must maintain a stats store to be populated during training and used during prediction. | | ++----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Dataflow** | **Batch scoring: OK**—the same transformation implementation is applied to data during training and batch scoring. | **Batch scoring: Recommended**. | **Batch scoring: N/A**---aggregates like these are computed based on real-time events. | +| | | | | +| (Apache Beam + TFT) | **Online prediction: Recommended**—it avoids training-serving skew and prepares training data up front. | **Online prediction: Recommended**. | **Online prediction: OK**—the same Apache Beam transformation is applied on data during training (batch) and serving (stream). | +| | | | | +| | | Both uses are recommended because transformation logic and computed statistics during training are stored as a TensorFlow graph that's attached to the exported model for serving. | | ++----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **TensorFlow** ^\*^ | **Batch scoring: Not recommended**. | **Batch scoring: Not Possible**. | **Batch scoring: N/A**—aggregates like these are computed based on real-time events. | +| | | | | +| (`input_fn` & `serving_fn`) | **Online prediction: Not recommended**. | **Online prediction: Not Possible**. | **Online prediction: Not Possible**. | +| | | | | +| | For training efficiency in both cases, it's better to prepare the training data up front. | | | ++----------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +^\*^ With TensorFlow, transformations like crossing, embedding, and one-hot encoding should be performed declaratively as `feature_columns` columns. diff --git a/mkdocs.yml b/mkdocs.yml index 8c7e14c4b8..034c81399f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,6 +96,12 @@ markdown_extensions: - pymdownx.superfences - pymdownx.arithmatex: generic: true + - pymdownx.critic + - pymdownx.caret + - pymdownx.keys + - pymdownx.mark + - pymdownx.tilde + - markdown_grid_tables - md_in_html - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji diff --git a/tfx/dependencies.py b/tfx/dependencies.py index 8ed768835b..7cb051c75c 100644 --- a/tfx/dependencies.py +++ b/tfx/dependencies.py @@ -33,6 +33,7 @@ branch HEAD. - For the release, we use a range of version, which is also used as a default. """ + from __future__ import annotations import os @@ -98,7 +99,7 @@ def make_required_install_packages(): # TODO(b/332616741): Scipy version 1.13 breaks the TFX OSS test. # Unpin once the issue is resolved. "scipy<1.13", - 'scikit-learn==1.5.1', + "scikit-learn==1.5.1", # TODO(b/291837844): Pinned pyyaml to 5.3.1. # Unpin once the issue with installation is resolved. "pyyaml>=6,<7", @@ -270,6 +271,7 @@ def make_extra_packages_docs() -> list[str]: "mkdocs-jupyter", "mkdocs-caption", "pymdown-extensions", + "markdown-grid-tables", ] From ac33b0abb143b7f81aa2f5e9c7a5061f140d9110 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:48:36 -0700 Subject: [PATCH 34/58] Convert html table to markdown --- .../data_preprocessing_with_cloud.md | 172 +++--------------- 1 file changed, 23 insertions(+), 149 deletions(-) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index f69bccceb0..a67576c895 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -307,84 +307,17 @@ input raw features of the training data in order to prepare it for ML. These transformations include both full-pass and instance-level operations, as shown in the following table: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Input featureTransformationStats neededTypeOutput feature
weight_poundNoneNoneNAweight_pound
mother_ageNormalizemean, varFull-passmother_age_normalized
mother_ageEqual size bucketizationquantilesFull-passmother_age_bucketized
mother_ageCompute the logNoneInstance-level - mother_age_log -
pluralityIndicate if it is single or multiple babiesNoneInstance-levelis_multiple
is_multipleConvert nominal values to numerical indexvocabFull-passis_multiple_index
gestation_weeksScale between 0 and 1min, maxFull-passgestation_weeks_scaled
mother_raceConvert nominal values to numerical indexvocabFull-passmother_race_index
is_maleConvert nominal values to numerical indexvocabFull-passis_male_index
+ | Input feature | Transformation | Stats needed | Type | Output feature + | ------------------- | --------------------------------------------- | -------------- | ---------------- | -------------------------- | + | `weight_pound` | None | None | NA | `weight_pound` | + | `mother_age` | Normalize | mean, var | Full-pass | `mother_age_normalized` | + | `mother_age` | Equal size bucketization | quantiles | Full-pass | `mother_age_bucketized` | + | `mother_age` | Compute the log | None | Instance-level | `mother_age_log` | + | `plurality` | Indicate if it is single or multiple babies | None | Instance-level | `is_multiple` | + | `is_multiple` | Convert nominal values to numerical index | vocab | Full-pass | `is_multiple_index` | + | `gestation_weeks` | Scale between 0 and 1 | min, max | Full-pass | `gestation_weeks_scaled` | + | `mother_race` | Convert nominal values to numerical index | vocab | Full-pass | `mother_race_index` | + | `is_male` | Convert nominal values to numerical index | vocab | Full-pass | `is_male_index` | These transformations are implemented in a `preprocess_fn` function, which expects a dictionary of tensors (`input_features`) and returns a dictionary of @@ -430,77 +363,18 @@ The `tf.Transform` has several other transformations in addition to those in the preceding example, including those listed in the following table: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TransformationApplies toDescription
scale_by_min_maxNumeric features - Scales a numerical column into the range [output_min, - output_max] -
scale_to_0_1Numeric features - Returns a column which is the input column scaled to have range - [0,1] -
scale_to_z_scoreNumeric featuresReturns a standardized column with mean 0 and variance 1
tfidfText features - Maps the terms in x to their term frequency * inverse document - frequency -
compute_and_apply_vocabularyCategorical features - Generates a vocabulary for a categorical feature and maps it to - an integer with this vocab -
ngramsText featuresCreates a SparseTensor of n-grams
hash_stringsCategorical featuresHashes strings into buckets
pcaNumeric featuresComputes PCA on the dataset using biased covariance
bucketizeNumeric features - Returns an equal-sized (quantiles-based) bucketized column, with - a bucket index assigned to each input -
+ | Transformation | Applies to | Description | + | -------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------- | + | `scale_by_min_max` | Numeric features | Scales a numerical column into the range \[`output_min`, `output_max`\] | + | `scale_to_0_1` | Numeric features | Returns a column which is the input column scaled to have range \[`0`,`1`\] | + | `scale_to_z_score` | Numeric features | Returns a standardized column with mean 0 and variance 1 | + | `tfidf` | Text features | Maps the terms in *x* to their term frequency \* inverse document frequency | + | `compute_and_apply_vocabulary` | Categorical features | Generates a vocabulary for a categorical feature and maps it to an integer with this vocab | + | `ngrams` | Text features | Creates a `SparseTensor` of n-grams | + | `hash_strings` | Categorical features | Hashes strings into buckets | + | `pca` | Numeric features | Computes PCA on the dataset using biased covariance | + | `bucketize` | Numeric features | Returns an equal-sized (quantiles-based) bucketized column, with a bucket index assigned to each input | + In order to apply the transformations implemented in the `preprocess_fn` function to the `raw_train_dataset` object produced in the previous step of the From 8e23f661a920a105b4b770a1dc6162b11af4eee8 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:59:08 -0700 Subject: [PATCH 35/58] Formatting fixes to `components` api docs --- tfx/components/bulk_inferrer/component.py | 15 +-- tfx/components/evaluator/component.py | 24 ++-- tfx/components/example_diff/component.py | 7 +- .../example_gen/csv_example_gen/component.py | 32 +++-- .../import_example_gen/component.py | 6 +- tfx/components/example_validator/component.py | 33 +++--- tfx/components/infra_validator/component.py | 13 +- tfx/components/pusher/component.py | 62 +++++----- tfx/components/schema_gen/component.py | 22 ++-- .../schema_gen/import_schema_gen/component.py | 6 +- tfx/components/trainer/component.py | 111 ++++++++++-------- tfx/components/transform/component.py | 57 +++++---- tfx/components/tuner/component.py | 22 ++-- tfx/v1/types/standard_artifacts.py | 2 + 14 files changed, 229 insertions(+), 183 deletions(-) diff --git a/tfx/components/bulk_inferrer/component.py b/tfx/components/bulk_inferrer/component.py index 297e1fe305..a5fe87e378 100644 --- a/tfx/components/bulk_inferrer/component.py +++ b/tfx/components/bulk_inferrer/component.py @@ -42,14 +42,15 @@ class BulkInferrer(base_beam_component.BaseBeamComponent): ``` Component `outputs` contains: - - `inference_result`: Channel of type `standard_artifacts.InferenceResult` + + - `inference_result`: Channel of type [`standard_artifacts.InferenceResult`][tfx.v1.types.standard_artifacts.InferenceResult] to store the inference results. - - `output_examples`: Channel of type `standard_artifacts.Examples` + - `output_examples`: Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples] to store the output examples. This is optional controlled by `output_example_spec`. See [the BulkInferrer - guide](https://www.tensorflow.org/tfx/guide/bulkinferrer) for more details. + guide](../../../guide/bulkinferrer) for more details. """ SPEC_CLASS = standard_component_specs.BulkInferrerSpec @@ -69,11 +70,11 @@ def __init__( """Construct an BulkInferrer component. Args: - examples: A BaseChannel of type `standard_artifacts.Examples`, usually + examples: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples], usually produced by an ExampleGen component. _required_ - model: A BaseChannel of type `standard_artifacts.Model`, usually produced - by a Trainer component. - model_blessing: A BaseChannel of type `standard_artifacts.ModelBlessing`, + model: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Model`][tfx.v1.types.standard_artifacts.Model], usually produced + by a [Trainer][tfx.v1.components.Trainer] component. + model_blessing: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.ModelBlessing`][tfx.v1.types.standard_artifacts.ModelBlessing], usually produced by a ModelValidator component. data_spec: bulk_inferrer_pb2.DataSpec instance that describes data selection. diff --git a/tfx/components/evaluator/component.py b/tfx/components/evaluator/component.py index 191ce7ac27..e8ccfbe7d1 100644 --- a/tfx/components/evaluator/component.py +++ b/tfx/components/evaluator/component.py @@ -33,13 +33,13 @@ class Evaluator(base_beam_component.BaseBeamComponent): """A TFX component to evaluate models trained by a TFX Trainer component. Component `outputs` contains: - - `evaluation`: Channel of type `standard_artifacts.ModelEvaluation` to - store - the evaluation results. - - `blessing`: Channel of type `standard_artifacts.ModelBlessing' that + + - `evaluation`: Channel of type [`standard_artifacts.ModelEvaluation`][tfx.v1.types.standard_artifacts.ModelEvaluation] to + store the evaluation results. + - `blessing`: Channel of type [`standard_artifacts.ModelBlessing`][tfx.v1.types.standard_artifacts.ModelBlessing] that contains the blessing result. - See [the Evaluator guide](https://www.tensorflow.org/tfx/guide/evaluator) for + See [the Evaluator guide](../../../guide/evaluator) for more details. """ @@ -64,18 +64,18 @@ def __init__( """Construct an Evaluator component. Args: - examples: A BaseChannel of type `standard_artifacts.Examples`, usually + examples: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples], usually produced by an ExampleGen component. _required_ - model: A BaseChannel of type `standard_artifacts.Model`, usually produced - by a Trainer component. - baseline_model: An optional channel of type 'standard_artifacts.Model' as + model: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Model`][tfx.v1.types.standard_artifacts.Model], usually produced + by a [Trainer][tfx.v1.components.Trainer] component. + baseline_model: An optional channel of type ['standard_artifacts.Model'][tfx.v1.types.standard_artifacts.Model] as the baseline model for model diff and model validation purpose. feature_slicing_spec: Deprecated, please use eval_config instead. Only support estimator. [evaluator_pb2.FeatureSlicingSpec](https://github.com/tensorflow/tfx/blob/master/tfx/proto/evaluator.proto) instance that describes how Evaluator should slice the data. fairness_indicator_thresholds: Optional list of float (or - RuntimeParameter) threshold values for use with TFMA fairness + [RuntimeParameter][tfx.v1.dsl.experimental.RuntimeParameter]) threshold values for use with TFMA fairness indicators. Experimental functionality: this interface and functionality may change at any time. TODO(b/142653905): add a link to additional documentation for TFMA fairness indicators here. @@ -90,12 +90,16 @@ def __init__( customization. This functionality is experimental and may change at any time. The module_file can implement following functions at its top level. + ``` {.py .no-copy} def custom_eval_shared_model( eval_saved_model_path, model_name, eval_config, **kwargs, ) -> tfma.EvalSharedModel: + ``` + ``` {.py .no-copy} def custom_extractors( eval_shared_model, eval_config, tensor_adapter_config, ) -> List[tfma.extractors.Extractor]: + ``` module_path: A python path to the custom module that contains the UDFs. See 'module_file' for the required signature of UDFs. This functionality is experimental and this API may change at any time. Note this can not diff --git a/tfx/components/example_diff/component.py b/tfx/components/example_diff/component.py index 4229b4556c..87ab3e01fc 100644 --- a/tfx/components/example_diff/component.py +++ b/tfx/components/example_diff/component.py @@ -29,7 +29,8 @@ class ExampleDiff(base_beam_component.BaseBeamComponent): """TFX ExampleDiff component. Computes example level diffs according to an ExampleDiffConfig. See TFDV - feature_skew_detector.py for more details. + [feature_skew_detector.py](https://github.com/tensorflow/data-validation/blob/master/tensorflow_data_validation/skew/feature_skew_detector.py) + for more details. This executor is under development and may change. """ @@ -45,10 +46,10 @@ def __init__(self, """Construct an ExampleDiff component. Args: - examples_test: A BaseChannel of `ExamplesPath` type, as generated by the + examples_test: A [BaseChannel][tfx.v1.types.BaseChannel] of `ExamplesPath` type, as generated by the [ExampleGen component](https://www.tensorflow.org/tfx/guide/examplegen). This needs to contain any splits referenced in `include_split_pairs`. - examples_base: A second BaseChannel of `ExamplesPath` type to which + examples_base: A second [BaseChannel][tfx.v1.types.BaseChannel] of `ExamplesPath` type to which `examples` should be compared. This needs to contain any splits referenced in `include_split_pairs`. config: A ExampleDiffConfig that defines configuration for the skew diff --git a/tfx/components/example_gen/csv_example_gen/component.py b/tfx/components/example_gen/csv_example_gen/component.py index eb246e5a71..cedabb6566 100644 --- a/tfx/components/example_gen/csv_example_gen/component.py +++ b/tfx/components/example_gen/csv_example_gen/component.py @@ -32,31 +32,37 @@ class CsvExampleGen(component.FileBasedExampleGen): # pylint: disable=protected The csv examplegen encodes column values to tf.Example int/float/byte feature. For the case when there's missing cells, the csv examplegen uses: - -- tf.train.Feature(`type`_list=tf.train.`type`List(value=[])), when the + + - tf.train.Feature(`type`_list=tf.train.`type`List(value=[])), when the `type` can be inferred. - -- tf.train.Feature() when it cannot infer the `type` from the column. + - tf.train.Feature() when it cannot infer the `type` from the column. Note that the type inferring will be per input split. If input isn't a single split, users need to ensure the column types align in each pre-splits. For example, given the following csv rows of a split: - header:A,B,C,D - row1: 1,,x,0.1 - row2: 2,,y,0.2 - row3: 3,,,0.3 - row4: + ``` + header:A,B,C,D + row1: 1,,x,0.1 + row2: 2,,y,0.2 + row3: 3,,,0.3 + row4: + ``` The output example will be - example1: 1(int), empty feature(no type), x(string), 0.1(float) - example2: 2(int), empty feature(no type), x(string), 0.2(float) - example3: 3(int), empty feature(no type), empty list(string), 0.3(float) + ``` + example1: 1(int), empty feature(no type), x(string), 0.1(float) + example2: 2(int), empty feature(no type), x(string), 0.2(float) + example3: 3(int), empty feature(no type), empty list(string), 0.3(float) + ``` - Note that the empty feature is `tf.train.Feature()` while empty list string - feature is `tf.train.Feature(bytes_list=tf.train.BytesList(value=[]))`. + Note that the empty feature is `tf.train.Feature()` while empty list string + feature is `tf.train.Feature(bytes_list=tf.train.BytesList(value=[]))`. Component `outputs` contains: - - `examples`: Channel of type `standard_artifacts.Examples` for output train + + - `examples`: Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples] for output train and eval examples. """ diff --git a/tfx/components/example_gen/import_example_gen/component.py b/tfx/components/example_gen/import_example_gen/component.py index a07856bc9b..5a16a0bf2e 100644 --- a/tfx/components/example_gen/import_example_gen/component.py +++ b/tfx/components/example_gen/import_example_gen/component.py @@ -32,9 +32,9 @@ class ImportExampleGen(component.FileBasedExampleGen): # pylint: disable=protec shuffle the dataset for ML best practice. Component `outputs` contains: - - `examples`: Channel of type `standard_artifacts.Examples` for output - train - and eval examples. + + - `examples`: Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples] for output + train and eval examples. """ EXECUTOR_SPEC = executor_spec.BeamExecutorSpec(executor.Executor) diff --git a/tfx/components/example_validator/component.py b/tfx/components/example_validator/component.py index 2d8e3a9837..2d23244daf 100644 --- a/tfx/components/example_validator/component.py +++ b/tfx/components/example_validator/component.py @@ -36,29 +36,32 @@ class ExampleValidator(base_component.BaseComponent): The ExampleValidator component identifies anomalies in training and serving data. The component can be configured to detect different classes of anomalies in the data. It can: - - perform validity checks by comparing data statistics against a schema that - codifies expectations of the user. - - run custom validations based on an optional SQL-based config. - Schema Based Example Validation + - perform validity checks by comparing data statistics against a schema that + codifies expectations of the user. + - run custom validations based on an optional SQL-based config. + + ## Schema Based Example Validation + The ExampleValidator component identifies any anomalies in the example data by - comparing data statistics computed by the StatisticsGen component against a + comparing data statistics computed by the [StatisticsGen][tfx.v1.components.StatisticsGen] component against a schema. The schema codifies properties which the input data is expected to satisfy, and is provided and maintained by the user. - ## Example - ``` - # Performs anomaly detection based on statistics and data schema. - validate_stats = ExampleValidator( - statistics=statistics_gen.outputs['statistics'], - schema=infer_schema.outputs['schema']) - ``` + !!! Example + ``` python + # Performs anomaly detection based on statistics and data schema. + validate_stats = ExampleValidator( + statistics=statistics_gen.outputs['statistics'], + schema=infer_schema.outputs['schema']) + ``` Component `outputs` contains: + - `anomalies`: Channel of type `standard_artifacts.ExampleAnomalies`. See [the ExampleValidator - guide](https://www.tensorflow.org/tfx/guide/exampleval) for more details. + guide](../../../guide/exampleval) for more details. """ SPEC_CLASS = standard_component_specs.ExampleValidatorSpec @@ -73,8 +76,8 @@ def __init__(self, """Construct an ExampleValidator component. Args: - statistics: A BaseChannel of type `standard_artifacts.ExampleStatistics`. - schema: A BaseChannel of type `standard_artifacts.Schema`. _required_ + statistics: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.ExampleStatistics`][tfx.v1.types.standard_artifacts.ExampleStatistics]. + schema: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Schema`]. _required_ exclude_splits: Names of splits that the example validator should not validate. Default behavior (when exclude_splits is set to None) is excluding no splits. diff --git a/tfx/components/infra_validator/component.py b/tfx/components/infra_validator/component.py index 4161567c88..ccfe7a7a91 100644 --- a/tfx/components/infra_validator/component.py +++ b/tfx/components/infra_validator/component.py @@ -36,7 +36,7 @@ class InfraValidator(base_component.BaseComponent): Full example using TensorFlowServing binary running on local docker. - ``` + ``` python infra_validator = InfraValidator( model=trainer.outputs['model'], examples=test_example_gen.outputs['examples'], @@ -59,7 +59,7 @@ class InfraValidator(base_component.BaseComponent): Minimal example when running on Kubernetes. - ``` + ``` python infra_validator = InfraValidator( model=trainer.outputs['model'], examples=test_example_gen.outputs['examples'], @@ -73,11 +73,12 @@ class InfraValidator(base_component.BaseComponent): ``` Component `outputs` contains: - - `blessing`: Channel of type `standard_artifacts.InfraBlessing` that + + - `blessing`: Channel of type [`standard_artifacts.InfraBlessing`][tfx.v1.types.standard_artifacts.InfraBlessing] that contains the validation result. See [the InfraValidator - guide](https://www.tensorflow.org/tfx/guide/infra_validator) for more + guide](../../../guide/infra_validator) for more details. """ @@ -95,12 +96,12 @@ def __init__( """Construct a InfraValidator component. Args: - model: A `BaseChannel` of `ModelExportPath` type, usually produced by + model: A [`BaseChannel`][tfx.v1.types.BaseChannel] of `ModelExportPath` type, usually produced by [Trainer](https://www.tensorflow.org/tfx/guide/trainer) component. _required_ serving_spec: A `ServingSpec` configuration about serving binary and test platform config to launch model server for validation. _required_ - examples: A `BaseChannel` of `ExamplesPath` type, usually produced by + examples: A [`BaseChannel`][tfx.v1.types.BaseChannel] of `ExamplesPath` type, usually produced by [ExampleGen](https://www.tensorflow.org/tfx/guide/examplegen) component. If not specified, InfraValidator does not issue requests for validation. diff --git a/tfx/components/pusher/component.py b/tfx/components/pusher/component.py index 28bc0460dc..f4bffa1800 100644 --- a/tfx/components/pusher/component.py +++ b/tfx/components/pusher/component.py @@ -32,37 +32,41 @@ class Pusher(base_component.BaseComponent): """A TFX component to push validated TensorFlow models to a model serving platform. The `Pusher` component can be used to push an validated SavedModel from output - of the [Trainer component](https://www.tensorflow.org/tfx/guide/trainer) to + of the [Trainer component](../../../guide/trainer) to [TensorFlow Serving](https://www.tensorflow.org/tfx/serving). The Pusher will check the validation results from the [Evaluator - component](https://www.tensorflow.org/tfx/guide/evaluator) and [InfraValidator - component](https://www.tensorflow.org/tfx/guide/infra_validator) + component](../../../guide/evaluator) and [InfraValidator + component](../../../guide/infra_validator) before deploying the model. If the model has not been blessed, then the model will not be pushed. - *Note:* The executor for this component can be overriden to enable the model - to be pushed to other serving platforms than tf.serving. The [Cloud AI - Platform custom - executor](https://github.com/tensorflow/tfx/tree/master/tfx/extensions/google_cloud_ai_platform/pusher) - provides an example how to implement this. + !!! Note + The executor for this component can be overriden to enable the model + to be pushed to other serving platforms than tf.serving. The [Cloud AI + Platform custom executor](https://github.com/tensorflow/tfx/tree/master/tfx/extensions/google_cloud_ai_platform/pusher) + provides an example how to implement this. - ## Example - ``` - # 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=proto.PushDestination( - filesystem=proto.PushDestination.Filesystem( - base_directory=serving_model_dir))) - ``` + !!! Example + ``` python + # 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=proto.PushDestination( + filesystem=proto.PushDestination.Filesystem( + base_directory=serving_model_dir, + ) + ), + ) + ``` Component `outputs` contains: - - `pushed_model`: Channel of type `standard_artifacts.PushedModel` with + + - `pushed_model`: Channel of type [`standard_artifacts.PushedModel`][tfx.v1.types.standard_artifacts.PushedModel] with result of push. - See [the Pusher guide](https://www.tensorflow.org/tfx/guide/pusher) for more + See [the Pusher guide](../../../guide/pusher) for more details. """ @@ -81,14 +85,14 @@ def __init__( """Construct a Pusher component. Args: - model: An optional BaseChannel of type `standard_artifacts.Model`, usually - produced by a Trainer component. - model_blessing: An optional BaseChannel of type - `standard_artifacts.ModelBlessing`, usually produced from an Evaluator - component. - infra_blessing: An optional BaseChannel of type - `standard_artifacts.InfraBlessing`, usually produced from an - InfraValidator component. + model: An optional [BaseChannel][tfx.v1.types.BaseChannel] of type `standard_artifacts.Model`, usually + produced by a [Trainer][tfx.v1.components.Trainer] component. + model_blessing: An optional [BaseChannel][tfx.v1.types.BaseChannel] of type + [`standard_artifacts.ModelBlessing`][tfx.v1.types.standard_artifacts.ModelBlessing], + usually produced from an [Evaluator][tfx.v1.components.Evaluator] component. + infra_blessing: An optional [BaseChannel][tfx.v1.types.BaseChannel] of type + [`standard_artifacts.InfraBlessing`][tfx.v1.types.standard_artifacts.InfraBlessing], + usually produced from an [InfraValidator][tfx.v1.components.InfraValidator] component. push_destination: A pusher_pb2.PushDestination instance, providing info for tensorflow serving to load models. Optional if executor_class doesn't require push_destination. diff --git a/tfx/components/schema_gen/component.py b/tfx/components/schema_gen/component.py index 914e2966f1..3123129a8e 100644 --- a/tfx/components/schema_gen/component.py +++ b/tfx/components/schema_gen/component.py @@ -40,17 +40,18 @@ class SchemaGen(base_component.BaseComponent): In a typical TFX pipeline, the SchemaGen component generates a schema which is consumed by the other pipeline components. - ## Example - ``` - # Generates schema based on statistics files. - infer_schema = SchemaGen(statistics=statistics_gen.outputs['statistics']) - ``` + !!! Example + ``` python + # Generates schema based on statistics files. + infer_schema = SchemaGen(statistics=statistics_gen.outputs['statistics']) + ``` Component `outputs` contains: - - `schema`: Channel of type `standard_artifacts.Schema` for schema + + - `schema`: Channel of type [`standard_artifacts.Schema`][tfx.v1.types.standard_artifacts.Schema] for schema result. - See [the SchemaGen guide](https://www.tensorflow.org/tfx/guide/schemagen) + See [the SchemaGen guide](../../../guide/schemagen) for more details. """ SPEC_CLASS = standard_component_specs.SchemaGenSpec @@ -65,10 +66,11 @@ def __init__( """Constructs a SchemaGen component. Args: - statistics: A BaseChannel of `ExampleStatistics` type (required if spec is - not passed). This should contain at least a `train` split. Other splits + statistics: A [BaseChannel][tfx.v1.types.BaseChannel] + of `ExampleStatistics` type (required if spec is not passed). + This should contain at least a `train` split. Other splits are currently ignored. _required_ - infer_feature_shape: Boolean (or RuntimeParameter) value indicating + infer_feature_shape: Boolean (or [RuntimeParameter][tfx.v1.dsl.experimental.RuntimeParameter]) value indicating whether or not to infer the shape of features. If the feature shape is not inferred, downstream Tensorflow Transform component using the schema will parse input as tf.SparseTensor. Default to True if not set. diff --git a/tfx/components/schema_gen/import_schema_gen/component.py b/tfx/components/schema_gen/import_schema_gen/component.py index 7e61dacb20..626c2793c7 100644 --- a/tfx/components/schema_gen/import_schema_gen/component.py +++ b/tfx/components/schema_gen/import_schema_gen/component.py @@ -38,12 +38,14 @@ class ImportSchemaGen(base_component.BaseComponent): ``` Component `outputs` contains: - - `schema`: Channel of type `standard_artifacts.Schema` for schema result. - See [the SchemaGen guide](https://www.tensorflow.org/tfx/guide/schemagen) + - `schema`: Channel of type `standard_artifacts.Schema` for schema result. + + See [the SchemaGen guide](../../../guide/schemagen) for more details. ImportSchemaGen works almost similar to `Importer` except following: + - `schema_file` should be the full file path instead of directory holding it. - `schema_file` is copied to the output artifact. This is different from `Importer` that loads an "Artifact" by setting its URI to the given path. diff --git a/tfx/components/trainer/component.py b/tfx/components/trainer/component.py index 7357e615b6..e3fcbedba1 100644 --- a/tfx/components/trainer/component.py +++ b/tfx/components/trainer/component.py @@ -32,35 +32,38 @@ class Trainer(base_component.BaseComponent): """A TFX component to train a TensorFlow model. The Trainer component is used to train and eval a model using given inputs and - a user-supplied run_fn function. + a user-supplied `run_fn` function. An example of `run_fn()` can be found in the [user-supplied code](https://github.com/tensorflow/tfx/blob/master/tfx/examples/penguin/penguin_utils_keras.py) of the TFX penguin pipeline example. - *Note:* This component trains locally. For cloud distributed training, please - refer to [Cloud AI Platform - Trainer](https://github.com/tensorflow/tfx/tree/master/tfx/extensions/google_cloud_ai_platform/trainer). - - ## Example - ``` - # Uses user-provided Python function that trains a model using TF. - trainer = Trainer( - module_file=module_file, - examples=transform.outputs['transformed_examples'], - schema=infer_schema.outputs['schema'], - transform_graph=transform.outputs['transform_graph'], - train_args=proto.TrainArgs(splits=['train'], num_steps=10000), - eval_args=proto.EvalArgs(splits=['eval'], num_steps=5000)) - ``` + !!! Note + This component trains locally. For cloud distributed training, please + refer to [Cloud AI Platform + Trainer](https://github.com/tensorflow/tfx/tree/master/tfx/extensions/google_cloud_ai_platform/trainer). + + !!! Example + ``` + # Uses user-provided Python function that trains a model using TF. + trainer = Trainer( + module_file=module_file, + examples=transform.outputs["transformed_examples"], + schema=infer_schema.outputs["schema"], + transform_graph=transform.outputs["transform_graph"], + train_args=proto.TrainArgs(splits=["train"], num_steps=10000), + eval_args=proto.EvalArgs(splits=["eval"], num_steps=5000), + ) + ``` Component `outputs` contains: - - `model`: Channel of type `standard_artifacts.Model` for trained model. - - `model_run`: Channel of type `standard_artifacts.ModelRun`, as the working + + - `model`: Channel of type [`standard_artifacts.Model`][tfx.v1.types.standard_artifacts.Model] for trained model. + - `model_run`: Channel of type [`standard_artifacts.ModelRun`][tfx.v1.types.standard_artifacts.ModelRun], as the working dir of models, can be used to output non-model related output (e.g., TensorBoard logs). - Please see [the Trainer guide](https://www.tensorflow.org/tfx/guide/trainer) + Please see [the Trainer guide](../../../guide/trainer) for more details. """ @@ -89,54 +92,62 @@ def __init__( """Construct a Trainer component. Args: - examples: A BaseChannel of type `standard_artifacts.Examples`, serving as - the source of examples used in training (required). May be raw or + examples: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples], + serving as the source of examples used in training (required). May be raw or transformed. transformed_examples: Deprecated (no compatibility guarantee). Please set 'examples' instead. - transform_graph: An optional BaseChannel of type - `standard_artifacts.TransformGraph`, serving as the input transform - graph if present. - schema: An optional BaseChannel of type `standard_artifacts.Schema`, + transform_graph: An optional [BaseChannel][tfx.v1.types.BaseChannel] of type + [`standard_artifacts.TransformGraph`][tfx.v1.types.standard_artifacts.TransformGraph], + serving as the input transform graph if present. + schema: An optional [BaseChannel][tfx.v1.types.BaseChannel] of type + [`standard_artifacts.Schema`][tfx.v1.types.standard_artifacts.Schema], serving as the schema of training and eval data. Schema is optional when - 1) transform_graph is provided which contains schema. 2) user module - bypasses the usage of schema, e.g., hardcoded. - base_model: A BaseChannel of type `Model`, containing model that will be + + 1. transform_graph is provided which contains schema. + 2. user module bypasses the usage of schema, e.g., hardcoded. + base_model: A [BaseChannel][tfx.v1.types.BaseChannel] of type `Model`, containing model that will be used for training. This can be used for warmstart, transfer learning or model ensembling. - hyperparameters: A BaseChannel of type - `standard_artifacts.HyperParameters`, serving as the hyperparameters for - training module. Tuner's output best hyperparameters can be feed into - this. + hyperparameters: A [BaseChannel] of type + [`standard_artifacts.HyperParameters`][tfx.v1.types.standard_artifacts.HyperParameters], + serving as the hyperparameters for training module. Tuner's output best + hyperparameters can be feed into this. module_file: A path to python module file containing UDF model definition. - The module_file must implement a function named `run_fn` at its top + The `module_file` must implement a function named `run_fn` at its top level with function signature: - `def run_fn(trainer.fn_args_utils.FnArgs)`, - and the trained model must be saved to FnArgs.serving_model_dir when + ```python + def run_fn(trainer.fn_args_utils.FnArgs) + ``` + and the trained model must be saved to `FnArgs.serving_model_dir` when this function is executed. - For Estimator based Executor, The module_file must implement a function + For Estimator based Executor, The `module_file` must implement a function named `trainer_fn` at its top level. The function must have the following signature. - def trainer_fn(trainer.fn_args_utils.FnArgs, - tensorflow_metadata.proto.v0.schema_pb2) -> Dict: + ``` python + def trainer_fn(trainer.fn_args_utils.FnArgs, + tensorflow_metadata.proto.v0.schema_pb2) -> Dict: ... - where the returned Dict has the following key-values. - 'estimator': an instance of tf.estimator.Estimator - 'train_spec': an instance of tf.estimator.TrainSpec - 'eval_spec': an instance of tf.estimator.EvalSpec - 'eval_input_receiver_fn': an instance of tfma EvalInputReceiver. - Exactly one of 'module_file' or 'run_fn' must be supplied if Trainer - uses GenericExecutor (default). Use of a RuntimeParameter for this + ``` + where the returned Dict has the following key-values. + + - `estimator`: an instance of `tf.estimator.Estimator` + - `train_spec`: an instance of `tf.estimator.TrainSpec` + - `eval_spec`: an instance of `tf.estimator.EvalSpec` + - `eval_input_receiver_fn`: an instance of tfma `EvalInputReceiver`. + + Exactly one of `module_file` or `run_fn` must be supplied if Trainer + uses GenericExecutor (default). Use of a [RuntimeParameter][] for this argument is experimental. run_fn: A python path to UDF model definition function for generic trainer. See 'module_file' for details. Exactly one of 'module_file' or 'run_fn' must be supplied if Trainer uses GenericExecutor (default). Use - of a RuntimeParameter for this argument is experimental. + of a [RuntimeParameter][] for this argument is experimental. trainer_fn: A python path to UDF model definition function for estimator based trainer. See 'module_file' for the required signature of the UDF. Exactly one of 'module_file' or 'trainer_fn' must be supplied if Trainer - uses Estimator based Executor. Use of a RuntimeParameter for this + uses Estimator based Executor. Use of a [RuntimeParameter][tfx.v1.dsl.experimental.RuntimeParameter] for this argument is experimental. train_args: A proto.TrainArgs instance, containing args used for training Currently only splits and num_steps are available. Default behavior @@ -151,11 +162,11 @@ def trainer_fn(trainer.fn_args_utils.FnArgs, Raises: ValueError: - - When both or neither of 'module_file' and user function + - When both or neither of `module_file` and user function (e.g., trainer_fn and run_fn) is supplied. - - When both or neither of 'examples' and 'transformed_examples' + - When both or neither of `examples` and `transformed_examples` is supplied. - - When 'transformed_examples' is supplied but 'transform_graph' + - When `transformed_examples` is supplied but `transform_graph` is not supplied. """ if [bool(module_file), bool(run_fn), bool(trainer_fn)].count(True) != 1: diff --git a/tfx/components/transform/component.py b/tfx/components/transform/component.py index 7ee88c6df0..1430917e1e 100644 --- a/tfx/components/transform/component.py +++ b/tfx/components/transform/component.py @@ -60,26 +60,28 @@ class Transform(base_beam_component.BaseBeamComponent): code](https://github.com/tensorflow/tfx/blob/master/tfx/examples/bert/mrpc/bert_mrpc_utils.py) of the TFX BERT MRPC pipeline example. - ## Example - ``` - # 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) - ``` + !!! Example + ``` python + # 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, + ) + ``` Component `outputs` contains: - - `transform_graph`: Channel of type `standard_artifacts.TransformGraph`, + + - `transform_graph`: Channel of type [`standard_artifacts.TransformGraph`][tfx.v1.types.standard_artifacts.TransformGraph], which includes an exported Tensorflow graph suitable for both training and serving. - - `transformed_examples`: Channel of type `standard_artifacts.Examples` for + - `transformed_examples`: Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples] for materialized transformed examples, which includes transform splits as specified in splits_config. This is optional controlled by `materialize`. Please see [the Transform - guide](https://www.tensorflow.org/tfx/guide/transform) for more details. + guide](../../../guide/transform) for more details. """ SPEC_CLASS = standard_component_specs.TransformSpec @@ -103,20 +105,20 @@ def __init__( """Construct a Transform component. Args: - examples: A BaseChannel of type `standard_artifacts.Examples` (required). + examples: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples] _required_. This should contain custom splits specified in splits_config. If custom split is not provided, this should contain two splits 'train' and 'eval'. - schema: A BaseChannel of type `standard_artifacts.Schema`. This should + schema: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Schema`][tfx.v1.types.standard_artifacts.Schema]. This should contain a single schema artifact. module_file: The file path to a python module file, from which the 'preprocessing_fn' function will be loaded. Exactly one of 'module_file' or 'preprocessing_fn' must be supplied. The function needs to have the following signature: - ``` + ``` {.python .no-copy} def preprocessing_fn(inputs: Dict[Text, Any]) -> Dict[Text, Any]: - ... + ... ``` where the values of input and returned Dict are either tf.Tensor or tf.SparseTensor. @@ -124,26 +126,29 @@ def preprocessing_fn(inputs: Dict[Text, Any]) -> Dict[Text, Any]: If additional inputs are needed for preprocessing_fn, they can be passed in custom_config: - ``` - def preprocessing_fn(inputs: Dict[Text, Any], custom_config: - Dict[Text, Any]) -> Dict[Text, Any]: - ... + ``` {.python .no-copy} + def preprocessing_fn( + inputs: Dict[Text, Any], + custom_config: Dict[Text, Any], + ) -> Dict[Text, Any]: + ... ``` To update the stats options used to compute the pre-transform or post-transform statistics, optionally define the 'stats-options_updater_fn' within the same module. If implemented, this function needs to have the following signature: + ``` {.python .no-copy} + def stats_options_updater_fn( + stats_type: tfx.components.transform.stats_options_util.StatsType, + stats_options: tfdv.StatsOptions, + ) -> tfdv.StatsOptions: + ... ``` - def stats_options_updater_fn(stats_type: tfx.components.transform - .stats_options_util.StatsType, stats_options: tfdv.StatsOptions) - -> tfdv.StatsOptions: - ... - ``` - Use of a RuntimeParameter for this argument is experimental. + Use of a [RuntimeParameter][tfx.v1.dsl.experimental.RuntimeParameter] for this argument is experimental. preprocessing_fn: The path to python function that implements a 'preprocessing_fn'. See 'module_file' for expected signature of the function. Exactly one of 'module_file' or 'preprocessing_fn' must be - supplied. Use of a RuntimeParameter for this argument is experimental. + supplied. Use of a [RuntimeParameter][tfx.v1.dsl.experimental.RuntimeParameter] for this argument is experimental. splits_config: A transform_pb2.SplitsConfig instance, providing splits that should be analyzed and splits that should be transformed. Note analyze and transform splits can have overlap. Default behavior (when diff --git a/tfx/components/tuner/component.py b/tfx/components/tuner/component.py index 9b28062574..2639aaa91e 100644 --- a/tfx/components/tuner/component.py +++ b/tfx/components/tuner/component.py @@ -48,10 +48,11 @@ class Tuner(base_component.BaseComponent): """A TFX component for model hyperparameter tuning. Component `outputs` contains: + - `best_hyperparameters`: Channel of type - `standard_artifacts.HyperParameters` for result of + [`standard_artifacts.HyperParameters`][tfx.v1.types.standard_artifacts.HyperParameters] for result of the best hparams. - - `tuner_results`: Channel of type `standard_artifacts.TunerResults` for + - `tuner_results`: Channel of type [`standard_artifacts.TunerResults`][tfx.v1.types.standard_artifacts.TunerResults] for results of all trials. Experimental: subject to change and no backwards compatibility guarantees. @@ -76,22 +77,25 @@ def __init__(self, """Construct a Tuner component. Args: - examples: A BaseChannel of type `standard_artifacts.Examples`, serving as + examples: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples], serving as the source of examples that are used in tuning (required). - schema: An optional BaseChannel of type `standard_artifacts.Schema`, + schema: An optional [BaseChannel][tfx.v1.types.BaseChannel] of type [`standard_artifacts.Schema`][tfx.v1.types.standard_artifacts.Schema], serving as the schema of training and eval data. This is used when raw examples are provided. - transform_graph: An optional BaseChannel of type - `standard_artifacts.TransformGraph`, serving as the input transform + transform_graph: An optional [BaseChannel][tfx.v1.types.BaseChannel] of type + [`standard_artifacts.TransformGraph`][tfx.v1.types.standard_artifacts.TransformGraph], serving as the input transform graph if present. This is used when transformed examples are provided. - base_model: A BaseChannel of type `Model`, containing model that will be + base_model: A [BaseChannel][tfx.v1.types.BaseChannel] of type [`Model`][tfx.v1.types.standard_artifacts.Model], containing model that will be used for training. This can be used for warmstart, transfer learning or model ensembling. module_file: A path to python module file containing UDF tuner definition. The module_file must implement a function named `tuner_fn` at its top level. The function must have the following signature. - def tuner_fn(fn_args: FnArgs) -> TunerFnResult: Exactly one of - 'module_file' or 'tuner_fn' must be supplied. + ``` {.python .no-copy} + def tuner_fn(fn_args: FnArgs) -> TunerFnResult: + ... + ``` + Exactly one of 'module_file' or 'tuner_fn' must be supplied. tuner_fn: A python path to UDF model definition function. See 'module_file' for the required signature of the UDF. Exactly one of 'module_file' or 'tuner_fn' must be supplied. diff --git a/tfx/v1/types/standard_artifacts.py b/tfx/v1/types/standard_artifacts.py index 155ce36ac6..db6b4154b0 100644 --- a/tfx/v1/types/standard_artifacts.py +++ b/tfx/v1/types/standard_artifacts.py @@ -27,6 +27,7 @@ Schema, TransformCache, TransformGraph, + TunerResults, HyperParameters, ) @@ -61,4 +62,5 @@ "String", "TransformCache", "TransformGraph", + "TunerResults", ] From 12b79fc15d200ad4bbca4e9b43f984eca81ad4a9 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:10:40 -0700 Subject: [PATCH 36/58] Fix notes admonitions Also fix minor code highlighting errors --- docs/tutorials/tfx/airflow_workshop.md | 35 +++++++++++-------- docs/tutorials/tfx/stub_template.md | 7 ++-- .../data_preprocessing_with_cloud.md | 13 +++---- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/docs/tutorials/tfx/airflow_workshop.md b/docs/tutorials/tfx/airflow_workshop.md index 61b8d7abdf..99ccb310aa 100644 --- a/docs/tutorials/tfx/airflow_workshop.md +++ b/docs/tutorials/tfx/airflow_workshop.md @@ -80,13 +80,14 @@ You'll be using the [Taxi Trips dataset](https://data.cityofchicago.org/Transportation/Taxi-Trips/wrvz-psew) released by the City of Chicago. -Note: This tutorial builds an application using data that has been modified for -use from its original source, www.cityofchicago.org, the official website of the -City of Chicago. The City of Chicago makes no claims as to the content, -accuracy, timeliness, or completeness of any of the data provided at in this -tutorial. The data provided at this site is subject to change at any time. It is -understood that the data provided in this tutorial is being used at one’s own -risk. +!!! Note + This tutorial builds an application using data that has been modified for + use from its original source, www.cityofchicago.org, the official website of the + City of Chicago. The City of Chicago makes no claims as to the content, + accuracy, timeliness, or completeness of any of the data provided at in this + tutorial. The data provided at this site is subject to change at any time. It is + understood that the data provided in this tutorial is being used at one’s own + risk. ### Model Goal - Binary classification Will the customer tip more or less than 20%? @@ -107,11 +108,13 @@ the duration of the lab. * Access to a standard internet browser (Chrome browser recommended). * Time to complete the lab. -**Note:** If you already have your own personal Google Cloud account or project, -do not use it for this lab. +!!! Note + If you already have your own personal Google Cloud account or project, + do not use it for this lab. -**Note:** If you are using a Chrome OS device, open an Incognito window to run -this lab. +!!! Note + If you are using a Chrome OS device, open an Incognito window to run + this lab. **How to start your lab and sign in to the Google Cloud Console** 1. Click the **Start Lab** button. If you need to pay for the lab, a pop-up opens for you to @@ -146,8 +149,9 @@ account, do not use it for this lab (avoids incurring charges). After a few moments, the Cloud Console opens in this tab. -**Note:** You can view the menu with a list of Google Cloud Products and -Services by clicking the **Navigation menu** at the top-left. +!!! Note + You can view the menu with a list of Google Cloud Products and + Services by clicking the **Navigation menu** at the top-left. ![qwiksetup4.png](images/airflow_workshop/qwiksetup4.png) @@ -242,8 +246,9 @@ followed by **Open Jupyterlab**. Next you'll clone the `tfx` repository in your JupyterLab instance. 1. In JupyterLab, click the **Terminal** icon to open a new terminal. -Note: If prompted, click Cancel for -Build Recommended. +!!! Note + If prompted, click `Cancel` for + Build Recommended. 1. To clone the `tfx` Github repository, type in the following command, and press **Enter**. diff --git a/docs/tutorials/tfx/stub_template.md b/docs/tutorials/tfx/stub_template.md index 04dd58b9ec..42d2bba9b7 100644 --- a/docs/tutorials/tfx/stub_template.md +++ b/docs/tutorials/tfx/stub_template.md @@ -92,9 +92,10 @@ following two files in the copied source files. test_component_ids=test_component_ids) ``` - NOTE: This stub component launcher cannot be defined within - `kubeflow_dag_runner.py` because launcher class is imported by the module - path. + !!! Note + This stub component launcher cannot be defined within + `kubeflow_dag_runner.py` because launcher class is imported by the module + path. 1. Set component ids to be list of component ids that are to be tested (in other words, other components' executors are replaced with BaseStubExecutor) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index a67576c895..a8ea0db108 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -55,10 +55,11 @@ an entire day, use the preconfigured 1. In the Google Cloud console, on the project selector page, select or [create a Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects). - Note: If you don't plan to keep the resources that you create in this - procedure, create a project instead of selecting an existing project. - After you finish these steps, you can delete the project, removing all - resources associated with the project. + !!! Note + If you don't plan to keep the resources that you create in this + procedure, create a project instead of selecting an existing project. + After you finish these steps, you can delete the project, removing all + resources associated with the project. [Go to project selector](https://console.cloud.google.com/projectselector2/home/dashboard){ .md-button .md-button--primary } @@ -140,7 +141,7 @@ table in BigQuery. The last part of the output is the following: - ``` { .yaml .no-copy } + ``` {.no-copy } Successfully installed ... ``` @@ -150,7 +151,7 @@ table in BigQuery. 1. Execute the second cell to run the `pip install tensorflow-transform `command. The last part of the output is the following: - ``` { .yaml .no-copy } + ``` { .no-copy } Successfully installed ... Note: you may need to restart the kernel to use updated packages. ``` From 51035c3ee63a50999fadfe444175ca93a9307131 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:23:58 -0700 Subject: [PATCH 37/58] Fix internal links from tutorials to guide pages --- docs/tutorials/mlmd/mlmd_tutorial.ipynb | 2 +- .../tutorials/model_analysis/tfma_basic.ipynb | 2 +- docs/tutorials/serving/rest_simple.ipynb | 4 +-- docs/tutorials/tfx/airflow_workshop.md | 10 +++---- .../tfx/cloud-ai-platform-pipelines.md | 28 ++++++++--------- docs/tutorials/tfx/components.ipynb | 4 +-- docs/tutorials/tfx/components_keras.ipynb | 4 +-- .../tfx/gcp/vertex_pipelines_simple.ipynb | 2 +- docs/tutorials/tfx/penguin_simple.ipynb | 12 ++++---- docs/tutorials/tfx/penguin_template.ipynb | 30 +++++++++---------- docs/tutorials/tfx/penguin_tfdv.ipynb | 14 ++++----- docs/tutorials/tfx/penguin_tfma.ipynb | 12 ++++---- docs/tutorials/tfx/penguin_tft.ipynb | 6 ++-- .../tfx/python_function_component.ipynb | 4 +-- docs/tutorials/tfx/recommenders.ipynb | 14 ++++----- docs/tutorials/tfx/stub_template.md | 2 +- docs/tutorials/tfx/template.ipynb | 10 +++---- docs/tutorials/tfx/template_local.ipynb | 6 ++-- docs/tutorials/tfx/tfx_for_mobile.md | 4 +-- 19 files changed, 85 insertions(+), 85 deletions(-) diff --git a/docs/tutorials/mlmd/mlmd_tutorial.ipynb b/docs/tutorials/mlmd/mlmd_tutorial.ipynb index 5f869c6363..debf5b3ba0 100644 --- a/docs/tutorials/mlmd/mlmd_tutorial.ipynb +++ b/docs/tutorials/mlmd/mlmd_tutorial.ipynb @@ -919,7 +919,7 @@ "To learn more about how to use MLMD, check out these additional resources:\n", "\n", "* [MLMD API documentation](https://www.tensorflow.org/tfx/ml_metadata/api_docs/python/mlmd)\n", - "* [MLMD guide](https://www.tensorflow.org/tfx/guide/mlmd)" + "* [MLMD guide](../../../guide/mlmd)" ] } ], diff --git a/docs/tutorials/model_analysis/tfma_basic.ipynb b/docs/tutorials/model_analysis/tfma_basic.ipynb index e3251c0222..367ee9a6da 100644 --- a/docs/tutorials/model_analysis/tfma_basic.ipynb +++ b/docs/tutorials/model_analysis/tfma_basic.ipynb @@ -67,7 +67,7 @@ "id": "mPt5BHTwy_0F" }, "source": [ - "[TensorFlow Model Analysis (TFMA)](https://www.tensorflow.org/tfx/guide/tfma) is a library for performing model evaluation across different slices of data. TFMA performs its computations in a distributed manner over large amounts of data using [Apache Beam](https://beam.apache.org/documentation/programming-guide/).\n", + "[TensorFlow Model Analysis (TFMA)](../../../guide/tfma) is a library for performing model evaluation across different slices of data. TFMA performs its computations in a distributed manner over large amounts of data using [Apache Beam](https://beam.apache.org/documentation/programming-guide/).\n", "\n", "This example colab notebook illustrates how TFMA can be used to investigate and visualize the performance of a model with respect to characteristics of the dataset. We'll use a model that we trained previously, and now you get to play with the results! The model we trained was for the [Chicago Taxi Example](https://github.com/tensorflow/tfx/tree/master/tfx/examples/chicago_taxi_pipeline), which uses the [Taxi Trips dataset](https://data.cityofchicago.org/Transportation/Taxi-Trips/wrvz-psew) released by the City of Chicago. Explore the full dataset in the [BigQuery UI](https://bigquery.cloud.google.com/dataset/bigquery-public-data:chicago_taxi_trips).\n", "\n", diff --git a/docs/tutorials/serving/rest_simple.ipynb b/docs/tutorials/serving/rest_simple.ipynb index aa13c8d202..1756f1a2c5 100644 --- a/docs/tutorials/serving/rest_simple.ipynb +++ b/docs/tutorials/serving/rest_simple.ipynb @@ -67,7 +67,7 @@ "id": "FbVhjPpzn6BM" }, "source": [ - "This guide trains a neural network model to classify [images of clothing, like sneakers and shirts](https://github.com/zalandoresearch/fashion-mnist), saves the trained model, and then serves it with [TensorFlow Serving](https://www.tensorflow.org/tfx/guide/serving). The focus is on TensorFlow Serving, rather than the modeling and training in TensorFlow, so for a complete example which focuses on the modeling and training see the [Basic Classification example](https://github.com/tensorflow/docs/blob/master/site/en/r1/tutorials/keras/basic_classification.ipynb).\n", + "This guide trains a neural network model to classify [images of clothing, like sneakers and shirts](https://github.com/zalandoresearch/fashion-mnist), saves the trained model, and then serves it with [TensorFlow Serving](../../../guide/serving). The focus is on TensorFlow Serving, rather than the modeling and training in TensorFlow, so for a complete example which focuses on the modeling and training see the [Basic Classification example](https://github.com/tensorflow/docs/blob/master/site/en/r1/tutorials/keras/basic_classification.ipynb).\n", "\n", "This guide uses [tf.keras](https://github.com/tensorflow/docs/blob/master/site/en/r1/guide/keras.ipynb), a high-level API to build and train models in TensorFlow." ] @@ -217,7 +217,7 @@ "source": [ "## Save your model\n", "\n", - "To load our trained model into TensorFlow Serving we first need to save it in [SavedModel](https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/saved_model) format. This will create a protobuf file in a well-defined directory hierarchy, and will include a version number. [TensorFlow Serving](https://www.tensorflow.org/tfx/guide/serving) allows us to select which version of a model, or \"servable\" we want to use when we make inference requests. Each version will be exported to a different sub-directory under the given path." + "To load our trained model into TensorFlow Serving we first need to save it in [SavedModel](https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/saved_model) format. This will create a protobuf file in a well-defined directory hierarchy, and will include a version number. [TensorFlow Serving](../../../guide/serving) allows us to select which version of a model, or \"servable\" we want to use when we make inference requests. Each version will be exported to a different sub-directory under the given path." ] }, { diff --git a/docs/tutorials/tfx/airflow_workshop.md b/docs/tutorials/tfx/airflow_workshop.md index 99ccb310aa..12f2cbbacd 100644 --- a/docs/tutorials/tfx/airflow_workshop.md +++ b/docs/tutorials/tfx/airflow_workshop.md @@ -24,7 +24,7 @@ You’ll learn how to create an ML pipeline using TFX important * Google uses TFX pipelines for production ML -Please see the [TFX User Guide](https://www.tensorflow.org/tfx/guide) to learn +Please see the [TFX User Guide](../../../guide) to learn more. You'll follow a typical ML development process: @@ -42,7 +42,7 @@ TFX orchestrators are responsible for scheduling components of the TFX pipeline based on the dependencies defined by the pipeline. TFX is designed to be portable to multiple environments and orchestration frameworks. One of the default orchestrators supported by TFX is -[Apache Airflow](https://www.tensorflow.org/tfx/guide/airflow). This lab +[Apache Airflow](../../../guide/airflow). This lab illustrates the use of Apache Airflow for TFX pipeline orchestration. Apache Airflow is a platform to programmatically author, schedule and monitor workflows. TFX uses Airflow to author workflows as directed acyclic graphs @@ -56,16 +56,16 @@ In this example, we are going to run a TFX pipeline on an instance by manually setting up Airflow. The other default orchestrators supported by TFX are Apache Beam and Kubeflow. -[Apache Beam](https://www.tensorflow.org/tfx/guide/beam_orchestrator) can run on +[Apache Beam](../../../guide/beam_orchestrator) can run on multiple data processing backends (Beam Ruunners). Cloud Dataflow is one such beam runner which can be used for running TFX pipelines. Apache Beam can be used for both streaming and batch processing pipelines. \ -[Kubeflow](https://www.tensorflow.org/tfx/guide/kubeflow) is an open source ML +[Kubeflow](../../../guide/kubeflow) is an open source ML platform dedicated to making deployments of machine learning (ML) workflows on Kubernetes simple, portable and scalable. Kubeflow can be used as an orchestrator for TFFX pipelines when they need to be deployed on Kubernetes clusters. In addition, you can also use your own -[custom orchestrator](https://www.tensorflow.org/tfx/guide/custom_orchestrator) +[custom orchestrator](../../../guide/custom_orchestrator) to run a TFX pipeline. Read more about Airflow [here](https://airflow.apache.org/). diff --git a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md index 701ec96526..3bd7b37167 100644 --- a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md +++ b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md @@ -413,13 +413,13 @@ data. ![Data Components](images/airflow_workshop/examplegen1.png) ![Data Components](images/airflow_workshop/examplegen2.png) -* [ExampleGen](https://www.tensorflow.org/tfx/guide/examplegen) ingests and +* [ExampleGen](../../../guide/examplegen) ingests and splits the input dataset. -* [StatisticsGen](https://www.tensorflow.org/tfx/guide/statsgen) calculates +* [StatisticsGen](../../../guide/statsgen) calculates statistics for the dataset. -* [SchemaGen](https://www.tensorflow.org/tfx/guide/schemagen) SchemaGen +* [SchemaGen](../../../guide/schemagen) SchemaGen examines the statistics and creates a data schema. -* [ExampleValidator](https://www.tensorflow.org/tfx/guide/exampleval) looks +* [ExampleValidator](../../../guide/exampleval) looks for anomalies and missing values in the dataset. ### In Jupyter lab file editor: @@ -481,13 +481,13 @@ serving. ![Transform](images/airflow_workshop/transform.png) -* [Transform](https://www.tensorflow.org/tfx/guide/transform) performs feature +* [Transform](../../../guide/transform) performs feature engineering on the dataset. ### In Jupyter lab file editor: In `pipeline`/`pipeline.py`, find and uncomment the line which appends -[Transform](https://www.tensorflow.org/tfx/guide/transform) to the pipeline. +[Transform](../../../guide/transform) to the pipeline. ```python # components.append(transform) @@ -529,7 +529,7 @@ Train a TensorFlow model with your nice, clean, transformed data. ### Components -* [Trainer](https://www.tensorflow.org/tfx/guide/trainer) trains a TensorFlow +* [Trainer](../../../guide/trainer) trains a TensorFlow model. ### In Jupyter lab file editor: @@ -580,7 +580,7 @@ Understanding more than just the top level metrics. ### Components -* [Evaluator](https://www.tensorflow.org/tfx/guide/evaluator) performs deep +* [Evaluator](../../../guide/evaluator) performs deep analysis of the training results. ### In Jupyter lab file editor: @@ -625,7 +625,7 @@ Deployment targets receive new models from well-known locations ### Components -* [Pusher](https://www.tensorflow.org/tfx/guide/pusher) deploys the model to a +* [Pusher](../../../guide/pusher) deploys the model to a serving infrastructure. ### In Jupyter lab file editor: @@ -650,7 +650,7 @@ You have now trained and validated your model, and your model is now ready for production. You can now deploy your model to any of the TensorFlow deployment targets, including: -* [TensorFlow Serving](https://www.tensorflow.org/tfx/guide/serving), for +* [TensorFlow Serving](../../../guide/serving), for serving your model on a server or server farm and processing REST and/or gRPC inference requests. * [TensorFlow Lite](https://www.tensorflow.org/lite), for including your model @@ -752,7 +752,7 @@ pipeline as before and create a new execution run as we did in step 5 and 6. ### Try Dataflow Several -[TFX Components use Apache Beam](https://www.tensorflow.org/tfx/guide/beam) to +[TFX Components use Apache Beam](../../../guide/beam) to implement data-parallel pipelines, and it means that you can distribute data processing workloads using [Google Cloud Dataflow](https://cloud.google.com/dataflow/). In this step, we @@ -881,13 +881,13 @@ You need to modify the pipeline definition to accommodate your data. 1. Modify `BIG_QUERY_QUERY` in configs.py to your query statement. 1. Add features in `models`/`features.py`. 1. Modify `models`/`preprocessing.py` to - [transform input data for training](https://www.tensorflow.org/tfx/guide/transform). + [transform input data for training](../../../guide/transform). 1. Modify `models`/`keras`/`model.py` and `models`/`keras`/`constants.py` to - [describe your ML model](https://www.tensorflow.org/tfx/guide/trainer). + [describe your ML model](../../../guide/trainer). ### Learn more about Trainer -See [Trainer component guide](https://www.tensorflow.org/tfx/guide/trainer) for +See [Trainer component guide](../../../guide/trainer) for more details on Training pipelines. ## Cleaning up diff --git a/docs/tutorials/tfx/components.ipynb b/docs/tutorials/tfx/components.ipynb index 74b9435523..f32fceb8cf 100644 --- a/docs/tutorials/tfx/components.ipynb +++ b/docs/tutorials/tfx/components.ipynb @@ -385,7 +385,7 @@ "\n", "`ExampleGen` takes as input the path to your data source. In our case, this is the `_data_root` path that contains the downloaded CSV.\n", "\n", - "Note: In this notebook, we can instantiate components one-by-one and run them with `InteractiveContext.run()`. By contrast, in a production setting, we would specify all the components upfront in a `Pipeline` to pass to the orchestrator (see the [Building a TFX Pipeline Guide](https://www.tensorflow.org/tfx/guide/build_tfx_pipeline))." + "Note: In this notebook, we can instantiate components one-by-one and run them with `InteractiveContext.run()`. By contrast, in a production setting, we would specify all the components upfront in a `Pipeline` to pass to the orchestrator (see the [Building a TFX Pipeline Guide](../../../guide/build_tfx_pipeline))." ] }, { @@ -564,7 +564,7 @@ "source": [ "Each feature in your dataset shows up as a row in the schema table, alongside its properties. The schema also captures all the values that a categorical feature takes on, denoted as its domain.\n", "\n", - "To learn more about schemas, see [the SchemaGen documentation](https://www.tensorflow.org/tfx/guide/schemagen)." + "To learn more about schemas, see [the SchemaGen documentation](../../../guide/schemagen)." ] }, { diff --git a/docs/tutorials/tfx/components_keras.ipynb b/docs/tutorials/tfx/components_keras.ipynb index 2b0e5edfb6..adf7461994 100644 --- a/docs/tutorials/tfx/components_keras.ipynb +++ b/docs/tutorials/tfx/components_keras.ipynb @@ -371,7 +371,7 @@ "\n", "`ExampleGen` takes as input the path to your data source. In our case, this is the `_data_root` path that contains the downloaded CSV.\n", "\n", - "Note: In this notebook, we can instantiate components one-by-one and run them with `InteractiveContext.run()`. By contrast, in a production setting, we would specify all the components upfront in a `Pipeline` to pass to the orchestrator (see the [Building a TFX Pipeline Guide](https://www.tensorflow.org/tfx/guide/build_tfx_pipeline)).\n", + "Note: In this notebook, we can instantiate components one-by-one and run them with `InteractiveContext.run()`. By contrast, in a production setting, we would specify all the components upfront in a `Pipeline` to pass to the orchestrator (see the [Building a TFX Pipeline Guide](../../../guide/build_tfx_pipeline)).\n", "\n", "#### Enabling the Cache\n", "When using the `InteractiveContext` in a notebook to develop a pipeline you can control when individual components will cache their outputs. Set `enable_cache` to `True` when you want to reuse the previous output artifacts that the component generated. Set `enable_cache` to `False` when you want to recompute the output artifacts for a component, if you are making changes to the code for example." @@ -556,7 +556,7 @@ "source": [ "Each feature in your dataset shows up as a row in the schema table, alongside its properties. The schema also captures all the values that a categorical feature takes on, denoted as its domain.\n", "\n", - "To learn more about schemas, see [the SchemaGen documentation](https://www.tensorflow.org/tfx/guide/schemagen)." + "To learn more about schemas, see [the SchemaGen documentation](../../../guide/schemagen)." ] }, { diff --git a/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb b/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb index 465637753a..3a4d4824af 100644 --- a/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb +++ b/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb @@ -400,7 +400,7 @@ "\n", "The only difference is that we don't need to set `metadata_connection_config`\n", "which is used to locate\n", - "[ML Metadata](https://www.tensorflow.org/tfx/guide/mlmd) database. Because\n", + "[ML Metadata](../../../guide/mlmd) database. Because\n", "Vertex Pipelines uses a managed metadata service, users don't need to care\n", "of it, and we don't need to specify the parameter.\n", "\n", diff --git a/docs/tutorials/tfx/penguin_simple.ipynb b/docs/tutorials/tfx/penguin_simple.ipynb index 52e4a54df6..6a2e708290 100644 --- a/docs/tutorials/tfx/penguin_simple.ipynb +++ b/docs/tutorials/tfx/penguin_simple.ipynb @@ -89,7 +89,7 @@ "importing data, training a model and exporting the trained model.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX." ] }, @@ -312,14 +312,14 @@ "consists of following three components.\n", "- CsvExampleGen: Reads in data files and convert them to TFX internal format\n", "for further processing. There are multiple\n", - "[ExampleGen](https://www.tensorflow.org/tfx/guide/examplegen)s for various\n", + "[ExampleGen](../../../guide/examplegen)s for various\n", "formats. In this tutorial, we will use CsvExampleGen which takes CSV file input.\n", "- Trainer: Trains an ML model.\n", - "[Trainer component](https://www.tensorflow.org/tfx/guide/trainer) requires a\n", + "[Trainer component](../../../guide/trainer) requires a\n", "model definition code from users. You can use TensorFlow APIs to specify how to\n", "train a model and save it in a _saved_model_ format.\n", "- Pusher: Copies the trained model outside of the TFX pipeline.\n", - "[Pusher component](https://www.tensorflow.org/tfx/guide/pusher) can be thought\n", + "[Pusher component](../../../guide/pusher) can be thought\n", "of as a deployment process of the trained ML model.\n", "\n", "Before actually define the pipeline, we need to write a model code for the\n", @@ -338,7 +338,7 @@ "API. This model training code will be saved to a separate file.\n", "\n", "In this tutorial we will use\n", - "[Generic Trainer](https://www.tensorflow.org/tfx/guide/trainer#generic_trainer)\n", + "[Generic Trainer](../../../guide/trainer#generic_trainer)\n", "of TFX which support Keras-based models. You need to write a Python file\n", "containing `run_fn` function, which is the entrypoint for the `Trainer`\n", "component." @@ -640,7 +640,7 @@ "You can find more resources on https://www.tensorflow.org/tfx/tutorials.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX.\n" ] } diff --git a/docs/tutorials/tfx/penguin_template.ipynb b/docs/tutorials/tfx/penguin_template.ipynb index 9ce1babc6b..326f0c0802 100644 --- a/docs/tutorials/tfx/penguin_template.ipynb +++ b/docs/tutorials/tfx/penguin_template.ipynb @@ -312,7 +312,7 @@ "By default, the template only includes standard TFX components. If you need\n", "some customized actions, you can create custom components for your pipeline.\n", "Please see\n", - "[TFX custom component guide](https://www.tensorflow.org/tfx/guide/understanding_custom_components)\n", + "[TFX custom component guide](../../../guide/understanding_custom_components)\n", "for the detail." ] }, @@ -414,7 +414,7 @@ "### Choose an ExampleGen\n", "\n", "Your data can be stored anywhere your pipeline can access, on either a local or distributed filesystem, or a query-able system. TFX provides various\n", - "[`ExampleGen` components](https://www.tensorflow.org/tfx/guide/examplegen)\n", + "[`ExampleGen` components](../../../guide/examplegen)\n", "to bring your data into a TFX pipeline. You can choose one from following\n", "example generating components.\n", "\n", @@ -436,7 +436,7 @@ "You can also create your own ExampleGen, for example, tfx includes\n", "[a custom ExecampleGen which uses Presto](https://github.com/tensorflow/tfx/tree/master/tfx/examples/custom_components/presto_example_gen)\n", "as a data source. See\n", - "[the guide](https://www.tensorflow.org/tfx/guide/examplegen#custom_examplegen)\n", + "[the guide](../../../guide/examplegen#custom_examplegen)\n", "for more information on how to use and develop custom executors.\n", "\n", "Once you decide which ExampleGen to use, you will need to modify the pipeline\n", @@ -475,7 +475,7 @@ "\n", "1. Replace existing CsvExampleGen to your ExampleGen class in\n", "`pipeline/pipeline.py`. Each ExampleGen class has different signature.\n", - "Please see [ExampleGen component guide](https://www.tensorflow.org/tfx/guide/examplegen) for more detail. Don't forget to import required modules with\n", + "Please see [ExampleGen component guide](../../../guide/examplegen) for more detail. Don't forget to import required modules with\n", "`import` statements in `pipeline/pipeline.py`." ] }, @@ -529,7 +529,7 @@ }, "source": [ "TFX pipeline produces two kinds of output, artifacts and a\n", - "[metadata DB(MLMD)](https://www.tensorflow.org/tfx/guide/mlmd) which contains\n", + "[metadata DB(MLMD)](../../../guide/mlmd) which contains\n", "metadata of artifacts and pipeline executions. The location to the output is\n", "defined in `local_runner.py`. By default, artifacts are stored under\n", "`tfx_pipeline_output` directory and metadata is stored as an sqlite database\n", @@ -736,7 +736,7 @@ "source": [ "By default, TFX ExampleGen divides examples into two splits, *train* and\n", "*eval*, but you can\n", - "[adjust your split configuration](https://www.tensorflow.org/tfx/guide/examplegen#span_version_and_split)." + "[adjust your split configuration](../../../guide/examplegen#span_version_and_split)." ] }, { @@ -799,7 +799,7 @@ "source": [ "This schema is automatically inferred from the output of StatisticsGen.\n", "We will use this generated schema in this tutorial, but you also can\n", - "[modify and customize the schema](https://www.tensorflow.org/tfx/guide/statsgen#creating_a_curated_schema)." + "[modify and customize the schema](../../../guide/statsgen#creating_a_curated_schema)." ] }, { @@ -858,7 +858,7 @@ "\n", "In this step, you will define various feature engineering job which will be\n", "used by `Transform` component in the pipeline. See\n", - "[Transform component guide](https://www.tensorflow.org/tfx/guide/transform)\n", + "[Transform component guide](../../../guide/transform)\n", "for more information.\n", "\n", "This is only necessary if you training code requires additional feature(s)\n", @@ -1001,7 +1001,7 @@ "## Step 4. Train your model with Trainer component.\n", "\n", "We will build a ML model using `Trainer` component. See\n", - "[Trainer component guide](https://www.tensorflow.org/tfx/guide/trainer)\n", + "[Trainer component guide](../../../guide/trainer)\n", "for more information. You need to provide your model code to the Trainer\n", "component.\n", "\n", @@ -1011,7 +1011,7 @@ "`Trainer` component. It means that `run_fn()` function in `models/model.py`\n", "will be called when `Trainer` component runs. You can see the code to construct\n", "a simple DNN model using `keras` API in given code. See\n", - "[TensorFlow 2.x in TFX](https://www.tensorflow.org/tfx/guide/keras)\n", + "[TensorFlow 2.x in TFX](../../../guide/keras)\n", "guide for more information about using keras API in TFX.\n", "\n", "In this `run_fn`, you should build a model and save it to a directory pointed\n", @@ -1109,9 +1109,9 @@ "id": "5DID2nzH-IR7" }, "source": [ - "[`Evaluator`](https://www.tensorflow.org/tfx/guide/evaluator) component\n", + "[`Evaluator`](../../../guide/evaluator) component\n", "continuously evaluate every built model from `Trainer`, and\n", - "[`Pusher`](https://www.tensorflow.org/tfx/guide/pusher) copies the model to\n", + "[`Pusher`](../../../guide/pusher) copies the model to\n", "a predefined location in the file system or even to\n", "[Google Cloud AI Platform Models](https://console.cloud.google.com/ai-platform/models).\n", "\n", @@ -1127,7 +1127,7 @@ "because we are solving a multi category classification problem. You also need\n", "to specify `tfma.SliceSpec` to analyze your model for specific slices. For more\n", "detail, see\n", - "[Evaluator component guide](https://www.tensorflow.org/tfx/guide/evaluator).\n", + "[Evaluator component guide](../../../guide/evaluator).\n", "1. Uncomment `# components.append(evaluator)` to add the component to the\n", "pipeline.\n", "\n", @@ -1222,13 +1222,13 @@ "### Adds Pusher component to the pipeline.\n", "\n", "If the model looks promising, we need to publish the model.\n", - "[Pusher component](https://www.tensorflow.org/tfx/guide/pusher)\n", + "[Pusher component](../../../guide/pusher)\n", "can publish the model to a location in the filesystem or to GCP AI Platform\n", "Models using\n", "[a custom executor](https://github.com/tensorflow/tfx/blob/master/tfx/extensions/google_cloud_ai_platform/pusher/executor.py).\n", "\n", "`Evaluator` component continuously evaluate every built model from `Trainer`,\n", - "and [`Pusher`](https://www.tensorflow.org/tfx/guide/pusher) copies the model to\n", + "and [`Pusher`](../../../guide/pusher) copies the model to\n", "a predefined location in the file system or even to\n", "[Google Cloud AI Platform Models](https://console.cloud.google.com/ai-platform/models).\n", "\n", diff --git a/docs/tutorials/tfx/penguin_tfdv.ipynb b/docs/tutorials/tfx/penguin_tfdv.ipynb index 09fb11a0af..224d22d42b 100644 --- a/docs/tutorials/tfx/penguin_tfdv.ipynb +++ b/docs/tutorials/tfx/penguin_tfdv.ipynb @@ -93,10 +93,10 @@ "The three new components, StatisticsGen, SchemaGen and ExampleValidator, are\n", "TFX components for data analysis and validation, and they are implemented\n", "using the\n", - "[TensorFlow Data Validation](https://www.tensorflow.org/tfx/guide/tfdv) library.\n", + "[TensorFlow Data Validation](../../../guide/tfdv) library.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX." ] }, @@ -331,9 +331,9 @@ "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple),\n", "we will use `StatisticsGen` and `SchemaGen`:\n", "\n", - "- [StatisticsGen](https://www.tensorflow.org/tfx/guide/statsgen) calculates\n", + "- [StatisticsGen](../../../guide/statsgen) calculates\n", "statistics for the dataset.\n", - "- [SchemaGen](https://www.tensorflow.org/tfx/guide/schemagen) examines the\n", + "- [SchemaGen](../../../guide/schemagen) examines the\n", "statistics and creates an initial data schema.\n", "\n", "See the guides for each component or\n", @@ -448,7 +448,7 @@ "source": [ "As explained in the previous tutorial, a TFX pipeline produces two kinds of\n", "outputs, artifacts and a\n", - "[metadata DB(MLMD)](https://www.tensorflow.org/tfx/guide/mlmd) which contains\n", + "[metadata DB(MLMD)](../../../guide/mlmd) which contains\n", "metadata of artifacts and pipeline executions. We defined the location of \n", "these outputs in the above cells. By default, artifacts are stored under\n", "the `pipelines` directory and metadata is stored as a sqlite database\n", @@ -705,7 +705,7 @@ "training code.\n", "\n", "We will also add an\n", - "[ExampleValidator](https://www.tensorflow.org/tfx/guide/exampleval)\n", + "[ExampleValidator](../../../guide/exampleval)\n", "component which will look for anomalies and missing values in the incoming\n", "dataset with respect to the schema.\n" ] @@ -1063,7 +1063,7 @@ "You can find more resources on https://www.tensorflow.org/tfx/tutorials.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX.\n", "\n" ] diff --git a/docs/tutorials/tfx/penguin_tfma.ipynb b/docs/tutorials/tfx/penguin_tfma.ipynb index 706ac1e546..ca2e3f3465 100644 --- a/docs/tutorials/tfx/penguin_tfma.ipynb +++ b/docs/tutorials/tfx/penguin_tfma.ipynb @@ -97,10 +97,10 @@ "tutorial. The Evaluator component performs deep analysis for your models and\n", "compare the new model against a baseline to determine they are \"good enough\".\n", "It is implemented using the\n", - "[TensorFlow Model Analysis](https://www.tensorflow.org/tfx/guide/tfma) library.\n", + "[TensorFlow Model Analysis](../../../guide/tfma) library.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX." ] }, @@ -282,7 +282,7 @@ "source": [ "## Create a pipeline\n", "\n", - "We will add an [`Evaluator`](https://www.tensorflow.org/tfx/guide/evaluator)\n", + "We will add an [`Evaluator`](../../../guide/evaluator)\n", "component to the pipeline we created in the\n", "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", "\n", @@ -464,7 +464,7 @@ "[`Resolver`](https://www.tensorflow.org/tfx/api_docs/python/tfx/v1/dsl/Resolver).\n", "To check a new model is getting better than previous model, we need to compare\n", "it against a previous published model, called baseline.\n", - "[ML Metadata(MLMD)](https://www.tensorflow.org/tfx/guide/mlmd) tracks all\n", + "[ML Metadata(MLMD)](../../../guide/mlmd) tracks all\n", "previous artifacts of the pipeline and `Resolver` can find what was the latest\n", "*blessed* model -- a model passed Evaluator successfully -- from MLMD using a\n", "strategy class called `LatestBlessedModelStrategy`.\n" @@ -591,7 +591,7 @@ "model from the previous run and it will be used as a baseline model for the\n", "comparison.\n", "\n", - "See [Evaluator component guide](https://www.tensorflow.org/tfx/guide/evaluator#using_the_evaluator_component) for more information." + "See [Evaluator component guide](../../../guide/evaluator#using_the_evaluator_component) for more information." ] }, { @@ -808,7 +808,7 @@ "You can find more resources on https://www.tensorflow.org/tfx/tutorials.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX.\n" ] } diff --git a/docs/tutorials/tfx/penguin_tft.ipynb b/docs/tutorials/tfx/penguin_tft.ipynb index 7bfb8213b9..f638a049d0 100644 --- a/docs/tutorials/tfx/penguin_tft.ipynb +++ b/docs/tutorials/tfx/penguin_tft.ipynb @@ -84,7 +84,7 @@ "[tf.transform](https://www.tensorflow.org/tfx/transform/get_started) library.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX." ] }, @@ -880,11 +880,11 @@ "## Next steps\n", "\n", "If you want to learn more about Transform component, see\n", - "[Transform Component guide](https://www.tensorflow.org/tfx/guide/transform).\n", + "[Transform Component guide](../../../guide/transform).\n", "You can find more resources on https://www.tensorflow.org/tfx/tutorials.\n", "\n", "Please see\n", - "[Understanding TFX Pipelines](https://www.tensorflow.org/tfx/guide/understanding_tfx_pipelines)\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", "to learn more about various concepts in TFX.\n" ] } diff --git a/docs/tutorials/tfx/python_function_component.ipynb b/docs/tutorials/tfx/python_function_component.ipynb index ab6df9f0c5..463125d0ab 100644 --- a/docs/tutorials/tfx/python_function_component.ipynb +++ b/docs/tutorials/tfx/python_function_component.ipynb @@ -101,7 +101,7 @@ "components within the TFX InteractiveContext and in a locally-orchestrated TFX\n", "pipeline.\n", "\n", - "For more context and information, see the [Custom Python function components](https://www.tensorflow.org/tfx/guide/custom_function_component)\n", + "For more context and information, see the [Custom Python function components](../../../guide/custom_function_component)\n", "page on the TFX documentation site." ] }, @@ -238,7 +238,7 @@ "the Python function component development process.\n", "\n", "See [Python function based component\n", - "guide](https://www.tensorflow.org/tfx/guide/custom_function_component)\n", + "guide](../../../guide/custom_function_component)\n", "for more documentation." ] }, diff --git a/docs/tutorials/tfx/recommenders.ipynb b/docs/tutorials/tfx/recommenders.ipynb index 78bc375039..2acb59b449 100644 --- a/docs/tutorials/tfx/recommenders.ipynb +++ b/docs/tutorials/tfx/recommenders.ipynb @@ -209,7 +209,7 @@ "source": [ "## Create a TFDS ExampleGen\n", "\n", - "We create a [custom ExampleGen component](https://www.tensorflow.org/tfx/guide/examplegen#custom_examplegen) which we use to load a TensorFlow Datasets (TFDS) dataset. This uses a custom executor in a FileBasedExampleGen." + "We create a [custom ExampleGen component](../../../guide/examplegen#custom_examplegen) which we use to load a TensorFlow Datasets (TFDS) dataset. This uses a custom executor in a FileBasedExampleGen." ] }, { @@ -396,7 +396,7 @@ "source": [ "## Generate statistics for movies and ratings\n", "\n", - "For a TFX pipeline we need to generate statistics for the dataset. We do that by using a [StatisticsGen component](https://www.tensorflow.org/tfx/guide/statsgen). These will be used by the [SchemaGen component](https://www.tensorflow.org/tfx/guide/schemagen) below when we generate a schema for our dataset. This is good practice anyway, because it's important to examine and analyze your data on an ongoing basis. Since we have two datasets we will create two StatisticsGen components." + "For a TFX pipeline we need to generate statistics for the dataset. We do that by using a [StatisticsGen component](../../../guide/statsgen). These will be used by the [SchemaGen component](../../../guide/schemagen) below when we generate a schema for our dataset. This is good practice anyway, because it's important to examine and analyze your data on an ongoing basis. Since we have two datasets we will create two StatisticsGen components." ] }, { @@ -455,7 +455,7 @@ "source": [ "## Create schemas for movies and ratings\n", "\n", - "For a TFX pipeline we need to generate a data schema from our dataset. We do that by using a [SchemaGen component](https://www.tensorflow.org/tfx/guide/schemagen). This will be used by the [Transform component](https://www.tensorflow.org/tfx/guide/transform) below to do our feature engineering in a way that is highly scalable to large datasets, and avoids training/serving skew. Since we have two datasets we will create two SchemaGen components." + "For a TFX pipeline we need to generate a data schema from our dataset. We do that by using a [SchemaGen component](../../../guide/schemagen). This will be used by the [Transform component](../../../guide/transform) below to do our feature engineering in a way that is highly scalable to large datasets, and avoids training/serving skew. Since we have two datasets we will create two SchemaGen components." ] }, { @@ -516,7 +516,7 @@ "source": [ "## Feature Engineering using Transform\n", "\n", - "For a structured and repeatable design of a TFX pipeline we will need a scalable approach to feature engineering. This allows us to handle the large datasets which are usually part of many recommender systems, and it also avoids training/serving skew. We will do that using the [Transform component](https://www.tensorflow.org/tfx/guide/transform).\n", + "For a structured and repeatable design of a TFX pipeline we will need a scalable approach to feature engineering. This allows us to handle the large datasets which are usually part of many recommender systems, and it also avoids training/serving skew. We will do that using the [Transform component](../../../guide/transform).\n", "\n", "The Transform component uses a module file to supply user code for the feature engineering what we want to do, so our first step is to create that module file. Since we have two datasets, we will create two of these module files and two Transform components.\n", "\n", @@ -684,7 +684,7 @@ "source": [ "## Implementing a model in TFX\n", "\n", - "In the [basic_retrieval](https://www.tensorflow.org/recommenders/examples/basic_retrieval) tutorial the model was created inline in the Python runtime. In a TFX pipeline, the model, metric, and loss are defined and trained in the module file for a [pipeline component called Trainer](https://www.tensorflow.org/tfx/guide/trainer). This makes the model, metric, and loss part of a repeatable process which can be automated and monitored.\n", + "In the [basic_retrieval](https://www.tensorflow.org/recommenders/examples/basic_retrieval) tutorial the model was created inline in the Python runtime. In a TFX pipeline, the model, metric, and loss are defined and trained in the module file for a [pipeline component called Trainer](../../../guide/trainer). This makes the model, metric, and loss part of a repeatable process which can be automated and monitored.\n", "\n", "### TensorFlow Recommenders model architecture\n", "\n", @@ -989,7 +989,7 @@ "source": [ "## Training the model\n", "\n", - "After defining the model, we can run the [Trainer component](https://www.tensorflow.org/tfx/guide/trainer) to do the model training." + "After defining the model, we can run the [Trainer component](../../../guide/trainer) to do the model training." ] }, { @@ -1027,7 +1027,7 @@ "source": [ "## Exporting the model\n", "\n", - "After training the model, we can use the [Pusher component](https://www.tensorflow.org/tfx/guide/pusher) to export the model." + "After training the model, we can use the [Pusher component](../../../guide/pusher) to export the model." ] }, { diff --git a/docs/tutorials/tfx/stub_template.md b/docs/tutorials/tfx/stub_template.md index 42d2bba9b7..d99fa455dd 100644 --- a/docs/tutorials/tfx/stub_template.md +++ b/docs/tutorials/tfx/stub_template.md @@ -26,7 +26,7 @@ over the artifacts from the recorded outputs. Since this tutorial assumes that you have completed `template.ipynb` up to step 6, a successful pipeline run must have been saved in the -[MLMD](https://www.tensorflow.org/tfx/guide/mlmd). The execution information in +[MLMD](../../../guide/mlmd). The execution information in MLMD can be accessed using gRPC server. Open a Terminal and run the following commands: diff --git a/docs/tutorials/tfx/template.ipynb b/docs/tutorials/tfx/template.ipynb index fd5454b57e..64f2daacd5 100644 --- a/docs/tutorials/tfx/template.ipynb +++ b/docs/tutorials/tfx/template.ipynb @@ -365,7 +365,7 @@ "source": [ "## Step 4. Run your first TFX pipeline\n", "\n", - "Components in the TFX pipeline will generate outputs for each run as [ML Metadata Artifacts](https://www.tensorflow.org/tfx/guide/mlmd), and they need to be stored somewhere. You can use any storage which the KFP cluster can access, and for this example we will use Google Cloud Storage (GCS). A default GCS bucket should have been created automatically. Its name will be `\u003cyour-project-id\u003e-kubeflowpipelines-default`.\n" + "Components in the TFX pipeline will generate outputs for each run as [ML Metadata Artifacts](../../../guide/mlmd), and they need to be stored somewhere. You can use any storage which the KFP cluster can access, and for this example we will use Google Cloud Storage (GCS). A default GCS bucket should have been created automatically. Its name will be `\u003cyour-project-id\u003e-kubeflowpipelines-default`.\n" ] }, { @@ -592,7 +592,7 @@ "source": [ "## Step 8. (*Optional*) Try Dataflow with KFP\n", "\n", - "Several [TFX Components uses Apache Beam](https://www.tensorflow.org/tfx/guide/beam) to implement data-parallel pipelines, and it means that you can distribute data processing workloads using [Google Cloud Dataflow](https://cloud.google.com/dataflow/). In this step, we will set the Kubeflow orchestrator to use dataflow as the data processing back-end for Apache Beam.\n", + "Several [TFX Components uses Apache Beam](../../../guide/beam) to implement data-parallel pipelines, and it means that you can distribute data processing workloads using [Google Cloud Dataflow](https://cloud.google.com/dataflow/). In this step, we will set the Kubeflow orchestrator to use dataflow as the data processing back-end for Apache Beam.\n", "\n", "\u003e**Double-click `pipeline` to change directory, and double-click to open `configs.py`**. Uncomment the definition of `GOOGLE_CLOUD_REGION`, and `DATAFLOW_BEAM_PIPELINE_ARGS`.\n", "\n", @@ -682,11 +682,11 @@ "\n", "1. If your data is stored in files, modify the `DATA_PATH` in `kubeflow_runner.py` or `local_runner.py` and set it to the location of your files. If your data is stored in BigQuery, modify `BIG_QUERY_QUERY` in `pipeline/configs.py` to correctly query for your data.\n", "1. Add features in `models/features.py`.\n", - "1. Modify `models/preprocessing.py` to [transform input data for training](https://www.tensorflow.org/tfx/guide/transform).\n", - "1. Modify `models/keras/model.py` and `models/keras/constants.py` to [describe your ML model](https://www.tensorflow.org/tfx/guide/trainer).\n", + "1. Modify `models/preprocessing.py` to [transform input data for training](../../../guide/transform).\n", + "1. Modify `models/keras/model.py` and `models/keras/constants.py` to [describe your ML model](../../../guide/trainer).\n", " - You can use an estimator based model, too. Change `RUN_FN` constant to `models.estimator.model.run_fn` in `pipeline/configs.py`.\n", "\n", - "Please see [Trainer component guide](https://www.tensorflow.org/tfx/guide/trainer) for more introduction." + "Please see [Trainer component guide](../../../guide/trainer) for more introduction." ] }, { diff --git a/docs/tutorials/tfx/template_local.ipynb b/docs/tutorials/tfx/template_local.ipynb index 4cad4d5988..01c030212c 100644 --- a/docs/tutorials/tfx/template_local.ipynb +++ b/docs/tutorials/tfx/template_local.ipynb @@ -595,11 +595,11 @@ "\n", "1. If your data is stored in files, modify the `DATA_PATH` in `kubeflow_runner.py` or `local_runner.py` and set it to the location of your files. If your data is stored in BigQuery, modify `BIG_QUERY_QUERY` in `pipeline/configs.py` to correctly query for your data.\n", "1. Add features in `models/features.py`.\n", - "1. Modify `models/preprocessing.py` to [transform input data for training](https://www.tensorflow.org/tfx/guide/transform).\n", - "1. Modify `models/keras/model.py` and `models/keras/constants.py` to [describe your ML model](https://www.tensorflow.org/tfx/guide/trainer).\n", + "1. Modify `models/preprocessing.py` to [transform input data for training](../../../guide/transform).\n", + "1. Modify `models/keras/model.py` and `models/keras/constants.py` to [describe your ML model](../../../guide/trainer).\n", " - You can use an estimator based model, too. Change `RUN_FN` constant to `models.estimator.model.run_fn` in `pipeline/configs.py`.\n", "\n", - "Please see [Trainer component guide](https://www.tensorflow.org/tfx/guide/trainer) for more introduction." + "Please see [Trainer component guide](../../../guide/trainer) for more introduction." ] } ], diff --git a/docs/tutorials/tfx/tfx_for_mobile.md b/docs/tutorials/tfx/tfx_for_mobile.md index 95fe2899a8..e5823837fc 100644 --- a/docs/tutorials/tfx/tfx_for_mobile.md +++ b/docs/tutorials/tfx/tfx_for_mobile.md @@ -21,7 +21,7 @@ then please see this [tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/com ## Steps Only two steps are required to create and evaluate a TFLite model in TFX. The first step is invoking the TFLite rewriter within the context of the -[TFX Trainer](https://www.tensorflow.org/tfx/guide/trainer) to convert the +[TFX Trainer](../../../guide/trainer) to convert the trained TensorFlow model into a TFLite one. The second step is configuring the Evaluator to evaluate TFLite models. We now discuss each in turn. @@ -79,7 +79,7 @@ components will be expecting to find the model. ### Evaluating the TFLite model. -The [TFX Evaluator](https://www.tensorflow.org/tfx/guide/evaluator) provides the +The [TFX Evaluator](../../../guide/evaluator) provides the ability to analyze trained models to understand their quality across a wide range of metrics. In addition to analyzing SavedModels, the TFX Evaluator is now able to analyze TFLite models as well. From 78ae4568c84be3be3463df1c36c2586ed0f9b989 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:27:53 -0700 Subject: [PATCH 38/58] Fix broken link --- tfx/components/trainer/component.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tfx/components/trainer/component.py b/tfx/components/trainer/component.py index e3fcbedba1..93dd4052cc 100644 --- a/tfx/components/trainer/component.py +++ b/tfx/components/trainer/component.py @@ -138,12 +138,12 @@ def trainer_fn(trainer.fn_args_utils.FnArgs, - `eval_input_receiver_fn`: an instance of tfma `EvalInputReceiver`. Exactly one of `module_file` or `run_fn` must be supplied if Trainer - uses GenericExecutor (default). Use of a [RuntimeParameter][] for this + uses GenericExecutor (default). Use of a [RuntimeParameter][tfx.v1.dsl.experimental.RuntimeParameter] for this argument is experimental. run_fn: A python path to UDF model definition function for generic trainer. See 'module_file' for details. Exactly one of 'module_file' or 'run_fn' must be supplied if Trainer uses GenericExecutor (default). Use - of a [RuntimeParameter][] for this argument is experimental. + of a [RuntimeParameter][tfx.v1.dsl.experimental.RuntimeParameter] for this argument is experimental. trainer_fn: A python path to UDF model definition function for estimator based trainer. See 'module_file' for the required signature of the UDF. Exactly one of 'module_file' or 'trainer_fn' must be supplied if Trainer From 4ac0a6c33492e48b1d88be66aa68697eb5b94bb6 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Fri, 13 Sep 2024 22:23:58 -0700 Subject: [PATCH 39/58] Fix formatting and links in `tfx.v1.dsl` API docs --- .../experimental/container_component.py | 45 +++--- tfx/dsl/component/experimental/decorators.py | 148 +++++++++--------- tfx/dsl/components/common/importer.py | 12 +- tfx/dsl/components/common/resolver.py | 36 +++-- .../experimental/conditionals/conditional.py | 18 ++- .../strategies/latest_artifact_strategy.py | 18 +-- .../latest_blessed_model_strategy.py | 22 +-- .../strategies/span_range_strategy.py | 17 +- tfx/dsl/placeholder/artifact_placeholder.py | 32 ++-- tfx/dsl/placeholder/runtime_placeholders.py | 26 +-- tfx/orchestration/pipeline.py | 2 +- tfx/types/artifact.py | 9 +- tfx/types/channel.py | 10 +- 13 files changed, 203 insertions(+), 192 deletions(-) diff --git a/tfx/dsl/component/experimental/container_component.py b/tfx/dsl/component/experimental/container_component.py index 7e771976bf..923f55800d 100644 --- a/tfx/dsl/component/experimental/container_component.py +++ b/tfx/dsl/component/experimental/container_component.py @@ -48,29 +48,28 @@ def create_container_component( Returns: Component that can be instantiated and user inside pipeline. - Example: - - ``` - component = create_container_component( - name='TrainModel', - inputs={ - 'training_data': Dataset, - }, - outputs={ - 'model': Model, - }, - parameters={ - 'num_training_steps': int, - }, - image='gcr.io/my-project/my-trainer', - command=[ - 'python3', 'my_trainer', - '--training_data_uri', InputUriPlaceholder('training_data'), - '--model_uri', OutputUriPlaceholder('model'), - '--num_training-steps', InputValuePlaceholder('num_training_steps'), - ] - ) - ``` + !!! Example + ``` python + component = create_container_component( + name="TrainModel", + inputs={ + "training_data": Dataset, + }, + outputs={ + "model": Model, + }, + parameters={ + "num_training_steps": int, + }, + image="gcr.io/my-project/my-trainer", + command=[ + "python3", "my_trainer", + "--training_data_uri", InputUriPlaceholder("training_data"), + "--model_uri", OutputUriPlaceholder("model"), + "--num_training-steps", InputValuePlaceholder("num_training_steps"), + ], + ) + ``` """ if not name: raise ValueError('Component name cannot be empty.') diff --git a/tfx/dsl/component/experimental/decorators.py b/tfx/dsl/component/experimental/decorators.py index d9719f4075..d83bd3cc18 100644 --- a/tfx/dsl/component/experimental/decorators.py +++ b/tfx/dsl/component/experimental/decorators.py @@ -320,7 +320,7 @@ def component( BaseFunctionalComponentFactory, Callable[[types.FunctionType], BaseFunctionalComponentFactory], ]: - """Decorator: creates a component from a typehint-annotated Python function. + '''Decorator: creates a component from a typehint-annotated Python function. This decorator creates a component based on typehint annotations specified for the arguments and return value for a Python function. The decorator can be @@ -368,65 +368,67 @@ def component( This is example usage of component definition using this decorator: - from tfx import v1 as tfx - - InputArtifact = tfx.dsl.components.InputArtifact - OutputArtifact = tfx.dsl.components.OutputArtifact - Parameter = tfx.dsl.components.Parameter - Examples = tfx.types.standard_artifacts.Examples - Model = tfx.types.standard_artifacts.Model - - class MyOutput(TypedDict): - loss: float - accuracy: float - - @component(component_annotation=tfx.dsl.standard_annotations.Train) - def MyTrainerComponent( - training_data: InputArtifact[Examples], - model: OutputArtifact[Model], - dropout_hyperparameter: float, - num_iterations: Parameter[int] = 10 - ) -> MyOutput: - '''My simple trainer component.''' - - records = read_examples(training_data.uri) - model_obj = train_model(records, num_iterations, dropout_hyperparameter) - model_obj.write_to(model.uri) - - return { - 'loss': model_obj.loss, - 'accuracy': model_obj.accuracy - } - - # Example usage in a pipeline graph definition: - # ... - trainer = MyTrainerComponent( - training_data=example_gen.outputs['examples'], - dropout_hyperparameter=other_component.outputs['dropout'], - num_iterations=1000) - pusher = Pusher(model=trainer.outputs['model']) - # ... + ``` python + from tfx import v1 as tfx + + InputArtifact = tfx.dsl.components.InputArtifact + OutputArtifact = tfx.dsl.components.OutputArtifact + Parameter = tfx.dsl.components.Parameter + Examples = tfx.types.standard_artifacts.Examples + Model = tfx.types.standard_artifacts.Model + + + class MyOutput(TypedDict): + loss: float + accuracy: float + + + @component(component_annotation=tfx.dsl.standard_annotations.Train) + def MyTrainerComponent( + training_data: InputArtifact[Examples], + model: OutputArtifact[Model], + dropout_hyperparameter: float, + num_iterations: Parameter[int] = 10, + ) -> MyOutput: + """My simple trainer component.""" + + records = read_examples(training_data.uri) + model_obj = train_model(records, num_iterations, dropout_hyperparameter) + model_obj.write_to(model.uri) + + return {"loss": model_obj.loss, "accuracy": model_obj.accuracy} + + + # Example usage in a pipeline graph definition: + # ... + trainer = MyTrainerComponent( + training_data=example_gen.outputs["examples"], + dropout_hyperparameter=other_component.outputs["dropout"], + num_iterations=1000, + ) + pusher = Pusher(model=trainer.outputs["model"]) + # ... + ``` When the parameter `component_annotation` is not supplied, the default value is None. This is another example usage with `component_annotation` = None: - @component - def MyTrainerComponent( - training_data: InputArtifact[standard_artifacts.Examples], - model: OutputArtifact[standard_artifacts.Model], - dropout_hyperparameter: float, - num_iterations: Parameter[int] = 10 - ) -> Output: - '''My simple trainer component.''' + ``` python + @component + def MyTrainerComponent( + training_data: InputArtifact[standard_artifacts.Examples], + model: OutputArtifact[standard_artifacts.Model], + dropout_hyperparameter: float, + num_iterations: Parameter[int] = 10, + ) -> Output: + """My simple trainer component.""" - records = read_examples(training_data.uri) - model_obj = train_model(records, num_iterations, dropout_hyperparameter) - model_obj.write_to(model.uri) + records = read_examples(training_data.uri) + model_obj = train_model(records, num_iterations, dropout_hyperparameter) + model_obj.write_to(model.uri) - return { - 'loss': model_obj.loss, - 'accuracy': model_obj.accuracy - } + return {"loss": model_obj.loss, "accuracy": model_obj.accuracy} + ``` When the parameter `use_beam` is True, one of the parameters of the decorated function type-annotated by BeamComponentParameter[beam.Pipeline] and the @@ -434,17 +436,19 @@ def MyTrainerComponent( with the tfx pipeline's beam_pipeline_args that's shared with other beam-based components: - @component(use_beam=True) - def DataProcessingComponent( - input_examples: InputArtifact[standard_artifacts.Examples], - output_examples: OutputArtifact[standard_artifacts.Examples], - beam_pipeline: BeamComponentParameter[beam.Pipeline] = None, - ) -> None: - '''My simple trainer component.''' - - records = read_examples(training_data.uri) - with beam_pipeline as p: + ``` python + @component(use_beam=True) + def DataProcessingComponent( + input_examples: InputArtifact[standard_artifacts.Examples], + output_examples: OutputArtifact[standard_artifacts.Examples], + beam_pipeline: BeamComponentParameter[beam.Pipeline] = None, + ) -> None: + """My simple trainer component.""" + + records = read_examples(training_data.uri) + with beam_pipeline as p: ... + ``` Experimental: no backwards compatibility guarantees. @@ -459,19 +463,15 @@ def DataProcessingComponent( Returns: An object that: - 1. you can call like the initializer of a subclass of - `base_component.BaseComponent` (or `base_component.BaseBeamComponent`). - 2. has a test_call() member function for unit testing the inner - implementation of the component. - Today, the returned object is literally a subclass of BaseComponent, so it - can be used as a `Type` e.g. in isinstance() checks. But you must not rely - on this, as we reserve the right to reserve a different kind of object in - future, which _only_ satisfies the two criteria (1.) and (2.) above - without being a `Type` itself. + + 1. you can call like the initializer of a subclass of [`base_component.BaseComponent`][tfx.v1.types.BaseChannel] (or [`base_component.BaseBeamComponent`][tfx.v1.types.BaseBeamComponent]). + 2. has a test_call() member function for unit testing the inner implementation of the component. + + Today, the returned object is literally a subclass of [BaseComponent][tfx.v1.types.BaseChannel], so it can be used as a `Type` e.g. in isinstance() checks. But you must not rely on this, as we reserve the right to reserve a different kind of object in the future, which _only_ satisfies the two criteria (1.) and (2.) above without being a `Type` itself. Raises: EnvironmentError: if the current Python interpreter is not Python 3. - """ + ''' if func is None: # Python decorators with arguments in parentheses result in two function # calls. The first function call supplies the kwargs and the second supplies diff --git a/tfx/dsl/components/common/importer.py b/tfx/dsl/components/common/importer.py index 08ab49d6e5..5d8a100c3c 100644 --- a/tfx/dsl/components/common/importer.py +++ b/tfx/dsl/components/common/importer.py @@ -274,14 +274,16 @@ class Importer(base_node.BaseNode): Here is an example to use the Importer: - ``` + ``` python importer = Importer( - source_uri='uri/to/schema', + source_uri="uri/to/schema", artifact_type=standard_artifacts.Schema, - reimport=False).with_id('import_schema') + reimport=False, + ).with_id("import_schema") schema_gen = SchemaGen( - fixed_schema=importer.outputs['result'], - examples=...) + fixed_schema=importer.outputs["result"], + examples=..., + ) ``` """ diff --git a/tfx/dsl/components/common/resolver.py b/tfx/dsl/components/common/resolver.py index 60f7791bd7..df91a2a89f 100644 --- a/tfx/dsl/components/common/resolver.py +++ b/tfx/dsl/components/common/resolver.py @@ -46,9 +46,9 @@ class ResolverStrategy(abc.ABC): to express the input resolution logic. Currently TFX supports the following builtin ResolverStrategy: - - [LatestArtifactStrategy](/tfx/api_docs/python/tfx/v1/dsl/experimental/LatestArtifactStrategy) - - [LatestBlessedModelStrategy](/tfx/api_docs/python/tfx/v1/dsl/experimental/LatestBlessedModelStrategy) - - [SpanRangeStrategy](/tfx/api_docs/python/tfx/v1/dsl/experimental/SpanRangeStrategy) + - [LatestArtifactStrategy][tfx.v1.dsl.experimental.LatestArtifactStrategy] + - [LatestBlessedModelStrategy][tfx.v1.dsl.experimental.LatestBlessedModelStrategy] + - [SpanRangeStrategy][tfx.v1.dsl.experimental.SpanRangeStrategy] A resolver strategy defines a type behavior used for input selection. A resolver strategy subclass must override the `resolve_artifacts()` function @@ -81,7 +81,7 @@ def resolve_artifacts( Returns: If all entries has enough data after the resolving, returns the resolved - input_dict. Otherise, return None. + input_dict. Otherise, return None. """ @@ -193,27 +193,31 @@ class Resolver(base_node.BaseNode): To use Resolver, pass the followings to the Resolver constructor: * Name of the Resolver instance - * A subclass of ResolverStrategy - * Configs that will be used to construct an instance of ResolverStrategy + * A subclass of [ResolverStrategy][tfx.v1.dsl.experimental.ResolverStrategy] + * Configs that will be used to construct an instance of [ResolverStrategy][tfx.v1.dsl.experimental.ResolverStrategy] * Channels to resolve with their tag, in the form of kwargs Here is an example: - ``` + ``` {.python .no-copy} example_gen = ImportExampleGen(...) examples_resolver = Resolver( - strategy_class=tfx.dsl.experimental.SpanRangeStrategy, - config={'range_config': range_config}, - examples=Channel(type=Examples, producer_component_id=example_gen.id) - ).with_id('Resolver.span_resolver') + strategy_class=tfx.dsl.experimental.SpanRangeStrategy, + config={"range_config": range_config}, + examples=Channel( + type=Examples, + producer_component_id=example_gen.id, + ), + ).with_id("Resolver.span_resolver") trainer = Trainer( - examples=examples_resolver.outputs['examples'], - ...) + examples=examples_resolver.outputs["examples"], + ..., + ) ``` - You can find experimental `ResolverStrategy` classes under - `tfx.v1.dsl.experimental` module, including `LatestArtifactStrategy`, - `LatestBlessedModelStrategy`, `SpanRangeStrategy`, etc. + You can find experimental [`ResolverStrategy`][tfx.v1.dsl.experimental.ResolverStrategy] classes under + [`tfx.v1.dsl.experimental`][tfx.v1.dsl.experimental] module, including [`LatestArtifactStrategy`][tfx.v1.dsl.experimental.LatestArtifactStrategy], + `LatestBlessedModelStrategy`, [`SpanRangeStrategy`][tfx.v1.dsl.experimental.SpanRangeStrategy], etc. """ def __init__(self, diff --git a/tfx/dsl/experimental/conditionals/conditional.py b/tfx/dsl/experimental/conditionals/conditional.py index e2a7aa6ede..1a05f4464a 100644 --- a/tfx/dsl/experimental/conditionals/conditional.py +++ b/tfx/dsl/experimental/conditionals/conditional.py @@ -55,16 +55,18 @@ class Cond(dsl_context_manager.DslContextManager[None]): Usage: - evaluator = Evaluator( - examples=example_gen.outputs['examples'], - model=trainer.outputs['model'], - eval_config=EvalConfig(...)) + ``` python + evaluator = Evaluator( + examples=example_gen.outputs["examples"], + model=trainer.outputs["model"], + eval_config=EvalConfig(...), + ) - with Cond(evaluator.outputs['blessing'].future() - .custom_property('blessed') == 1): + with Cond(evaluator.outputs["blessing"].future().custom_property("blessed") == 1): pusher = Pusher( - model=trainer.outputs['model'], - push_destination=PushDestination(...)) + model=trainer.outputs["model"], push_destination=PushDestination(...) + ) + ``` """ def __init__(self, predicate: placeholder.Predicate): diff --git a/tfx/dsl/input_resolution/strategies/latest_artifact_strategy.py b/tfx/dsl/input_resolution/strategies/latest_artifact_strategy.py index e836e88719..54bea2ce5e 100644 --- a/tfx/dsl/input_resolution/strategies/latest_artifact_strategy.py +++ b/tfx/dsl/input_resolution/strategies/latest_artifact_strategy.py @@ -25,16 +25,16 @@ class LatestArtifactStrategy(resolver.ResolverStrategy): """Strategy that resolves the latest n(=1) artifacts per each channel. - Note that this ResolverStrategy is experimental and is subject to change in - terms of both interface and implementation. + Note that this [ResolverStrategy][tfx.v1.dsl.experimental.ResolverStrategy] is experimental and is subject to change in terms of both interface and implementation. Don't construct LatestArtifactStrategy directly, example usage: - ``` - model_resolver = Resolver( - strategy_class=LatestArtifactStrategy, - model=Channel(type=Model), - ).with_id('latest_model_resolver') - model_resolver.outputs['model'] + ``` python + model_resolver.outputs['model'] + model_resolver = Resolver( + strategy_class=LatestArtifactStrategy, + model=Channel(type=Model), + ).with_id("latest_model_resolver") + model_resolver.outputs["model"] ``` """ @@ -63,7 +63,7 @@ def resolve_artifacts( Returns: If `min_count` for every input is met, returns a - Dict[str, List[Artifact]]. Otherwise, return None. + Dict[str, List[Artifact]]. Otherwise, return None. """ resolved_dict = self._resolve(input_dict) all_min_count_met = all( diff --git a/tfx/dsl/input_resolution/strategies/latest_blessed_model_strategy.py b/tfx/dsl/input_resolution/strategies/latest_blessed_model_strategy.py index 109d879f6b..2fee07ac73 100644 --- a/tfx/dsl/input_resolution/strategies/latest_blessed_model_strategy.py +++ b/tfx/dsl/input_resolution/strategies/latest_blessed_model_strategy.py @@ -35,17 +35,17 @@ class LatestBlessedModelStrategy(resolver.ResolverStrategy): """LatestBlessedModelStrategy resolves the latest blessed Model artifact. - Note that this ResolverStrategy is experimental and is subject to change in - terms of both interface and implementation. + Note that this [ResolverStrategy][tfx.v1.dsl.experimental.ResolverStrategy] is experimental and is subject to change in terms of both interface and implementation. Don't construct LatestBlessedModelStrategy directly, example usage: - ``` - model_resolver = Resolver( - strategy_class=LatestBlessedModelStrategy, - model=Channel(type=Model), - model_blessing=Channel(type=ModelBlessing), - ).with_id('latest_blessed_model_resolver') - model_resolver.outputs['model'] + ``` python + model_resolver.outputs['model'] + model_resolver = Resolver( + strategy_class=LatestBlessedModelStrategy, + model=Channel(type=Model), + model_blessing=Channel(type=ModelBlessing), + ).with_id("latest_blessed_model_resolver") + model_resolver.outputs["model"] ``` """ @@ -85,8 +85,8 @@ def resolve_artifacts( input_dict: The input_dict to resolve from. Returns: - The latest blessed Model and its corresponding ModelBlessing, respectively - in the same input channel they were contained to. + The latest blessed Model and its corresponding [ModelBlessing][tfx.v1.types.standard_artifacts.ModelBlessing], respectively + in the same input channel they were contained to. Raises: RuntimeError: if input_dict contains unsupported artifact types. diff --git a/tfx/dsl/input_resolution/strategies/span_range_strategy.py b/tfx/dsl/input_resolution/strategies/span_range_strategy.py index 6e74a7d531..aa607776d0 100644 --- a/tfx/dsl/input_resolution/strategies/span_range_strategy.py +++ b/tfx/dsl/input_resolution/strategies/span_range_strategy.py @@ -40,17 +40,16 @@ def _get_span_custom_property(artifact: types.Artifact) -> int: class SpanRangeStrategy(resolver.ResolverStrategy): """SpanRangeStrategy resolves artifacts based on "span" property. - Note that this ResolverStrategy is experimental and is subject to change in - terms of both interface and implementation. + Note that this [ResolverStrategy][tfx.v1.dsl.experimental.ResolverStrategy] is experimental and is subject to change in terms of both interface and implementation. Don't construct SpanRangeStrategy directly, example usage: - ``` - examples_resolver = Resolver( - strategy_class=SpanRangeStrategy, - config={'range_config': range_config}, - examples=Channel(type=Examples, producer_component_id=example_gen.id), - ).with_id('span_resolver') - examples_resolver.outputs['examples'] + ``` python + examples_resolver = Resolver( + strategy_class=SpanRangeStrategy, + config={"range_config": range_config}, + examples=Channel(type=Examples, producer_component_id=example_gen.id), + ).with_id("span_resolver") + examples_resolver.outputs["examples"] ``` """ diff --git a/tfx/dsl/placeholder/artifact_placeholder.py b/tfx/dsl/placeholder/artifact_placeholder.py index 2acb4000fe..9ab75d205e 100644 --- a/tfx/dsl/placeholder/artifact_placeholder.py +++ b/tfx/dsl/placeholder/artifact_placeholder.py @@ -31,21 +31,22 @@ def input(key: str) -> ArtifactPlaceholder: # pylint: disable=redefined-builtin Returns: A Placeholder that supports + 1. Rendering the whole MLMD artifact proto as text_format. - Example: input('model') - 2. Accessing a specific index using [index], if multiple artifacts are + Example: `#!python input('model')` + 2. Accessing a specific index using `#!python [index]`, if multiple artifacts are associated with the given key. If not specified, default to the first artifact. - Example: input('model')[0] + Example: `#!python input('model')[0]` 3. Getting the URI of an artifact through .uri property. - Example: input('model').uri or input('model')[0].uri + Example: `#!python input('model').uri or input('model')[0].uri` 4. Getting the URI of a specific split of an artifact using - .split_uri(split_name) method. - Example: input('examples')[0].split_uri('train') + `#!python .split_uri(split_name)` method. + Example: `#!python input('examples')[0].split_uri('train')` 5. Getting the value of a primitive artifact through .value property. - Example: input('primitive').value + Example: `#!python input('primitive').value` 6. Concatenating with other placeholders or strings. - Example: input('model').uri + '/model/' + exec_property('version') + Example: `#!python input('model').uri + '/model/' + exec_property('version')` """ return ArtifactPlaceholder(key, is_input=True) @@ -60,21 +61,22 @@ def output(key: str) -> ArtifactPlaceholder: Returns: A Placeholder that supports + 1. Rendering the whole artifact as text_format. - Example: output('model') + Example: `#!python output('model')` 2. Accessing a specific index using [index], if multiple artifacts are associated with the given key. If not specified, default to the first artifact. - Example: output('model')[0] + Example: `#!python output('model')[0]` 3. Getting the URI of an artifact through .uri property. - Example: output('model').uri or output('model')[0].uri + Example: `#!python output('model').uri or output('model')[0].uri` 4. Getting the URI of a specific split of an artifact using - .split_uri(split_name) method. - Example: output('examples')[0].split_uri('train') + `#!python .split_uri(split_name)` method. + Example: `#!python output('examples')[0].split_uri('train')` 5. Getting the value of a primitive artifact through .value property. - Example: output('primitive').value + Example: `#!python output('primitive').value` 6. Concatenating with other placeholders or strings. - Example: output('model').uri + '/model/' + exec_property('version') + Example: `#!python output('model').uri + '/model/' + exec_property('version')` """ return ArtifactPlaceholder(key, is_input=False) diff --git a/tfx/dsl/placeholder/runtime_placeholders.py b/tfx/dsl/placeholder/runtime_placeholders.py index b2b364a7d6..d235ae6c32 100644 --- a/tfx/dsl/placeholder/runtime_placeholders.py +++ b/tfx/dsl/placeholder/runtime_placeholders.py @@ -32,15 +32,16 @@ def exec_property(key: str) -> ExecPropertyPlaceholder: Returns: A Placeholder that supports + 1. Rendering the value of an execution property at a given key. - Example: exec_property('version') + Example: `#!python exec_property('version')` 2. Rendering the whole proto or a proto field of an execution property, if the value is a proto type. The (possibly nested) proto field in a placeholder can be accessed as if accessing a proto field in Python. - Example: exec_property('model_config').num_layers + Example: `#!python exec_property('model_config').num_layers` 3. Concatenating with other placeholders or strings. - Example: output('model').uri + '/model/' + exec_property('version') + Example: `#!python output('model').uri + '/model/' + exec_property('version')` """ return ExecPropertyPlaceholder(key) @@ -56,10 +57,10 @@ def runtime_info(key: RuntimeInfoKeys) -> RuntimeInfoPlaceholder: """Returns a Placeholder that contains runtime information for component. Currently the runtime info includes following keys: - 1. executor_spec: The executor spec proto. - 2. platform_config: A proto that contains platform-specific information for + 1. `executor_spec`: The executor spec proto. + 2. `platform_config`: A proto that contains platform-specific information for the current pipeline node. - 3. pipeline_platform_config: A proto that contains platform-specific + 3. `pipeline_platform_config`: A proto that contains platform-specific information for the pipeline as a whole. @@ -68,8 +69,8 @@ def runtime_info(key: RuntimeInfoKeys) -> RuntimeInfoPlaceholder: Returns: A Placeholder that will render to the information associated with the key. - If the placeholder is proto-valued. Accessing a proto field can be - represented as if accessing a proto field in Python. + If the placeholder is proto-valued. Accessing a proto field can be + represented as if accessing a proto field in Python. Raises: ValueError: If received unsupported key. @@ -82,11 +83,11 @@ def execution_invocation() -> ExecInvocationPlaceholder: Returns: A Placeholder that will render to the ExecutionInvocation proto. - Accessing a proto field is the same as if accessing a proto field in Python. + Accessing a proto field is the same as if accessing a proto field in Python. - Prefer to use input(key)/output(key)/exec_property(key) functions instead of - input_dict/output_dict/execution_properties field from ExecutionInvocation - proto. + Prefer to use input(key)/output(key)/exec_property(key) functions instead of + input_dict/output_dict/execution_properties field from ExecutionInvocation + proto. """ return ExecInvocationPlaceholder() @@ -99,6 +100,7 @@ def environment_variable(key: str) -> EnvironmentVariablePlaceholder: Returns: A Placeholder that supports + 1. Rendering the value of an environment variable for a given key. Example: environment_variable('FOO') 2. Concatenating with other placeholders or strings. diff --git a/tfx/orchestration/pipeline.py b/tfx/orchestration/pipeline.py index b2622eda97..dd8e4984a1 100644 --- a/tfx/orchestration/pipeline.py +++ b/tfx/orchestration/pipeline.py @@ -233,7 +233,7 @@ class Pipeline(base_node.BaseNode): Pipeline object represents the DAG of TFX components, which can be run using one of the pipeline orchestration systems that TFX supports. For details, please refer to the - [guide](https://github.com/tensorflow/tfx/blob/master/docs/guide/build_tfx_pipeline.md). + [guide](../../../guide/build_tfx_pipeline). Attributes: components: A deterministic list of logical components of this pipeline, diff --git a/tfx/types/artifact.py b/tfx/types/artifact.py index 7d283b07c7..8f8fcc3131 100644 --- a/tfx/types/artifact.py +++ b/tfx/types/artifact.py @@ -113,8 +113,8 @@ class Artifact(json_utils.Jsonable): """TFX artifact used for orchestration. This is used for type-checking and inter-component communication. Currently, - it wraps a tuple of (ml_metadata.proto.Artifact, - ml_metadata.proto.ArtifactType) with additional property accessors for + it wraps a tuple of (`#!python ml_metadata.proto.Artifact`, + `#!python ml_metadata.proto.ArtifactType`) with additional property accessors for internal state. A user may create a subclass of Artifact and override the TYPE_NAME property @@ -124,8 +124,9 @@ class Artifact(json_utils.Jsonable): A user may specify artifact type-specific properties for an Artifact subclass by overriding the PROPERTIES dictionary, as detailed below. - Note: the behavior of this class is experimental, without backwards - compatibility guarantees, and may change in upcoming releases. + !!! Note + The behavior of this class is experimental, without backwards + compatibility guarantees, and may change in upcoming releases. """ # String artifact type name used to identify the type in ML Metadata diff --git a/tfx/types/channel.py b/tfx/types/channel.py index c972d221d0..333e4dc89f 100644 --- a/tfx/types/channel.py +++ b/tfx/types/channel.py @@ -90,8 +90,8 @@ class TriggerByProperty: class BaseChannel(abc.ABC, Generic[_AT]): """An abstraction for component (BaseNode) artifact inputs. - `BaseChannel` is often interchangeably used with the term 'channel' (not - capital `Channel` which points to the legacy class name). + [`BaseChannel`][tfx.v1.types.BaseChannel] is often interchangeably used with the term 'channel' (not + capital [`Channel`][tfx.v1.dsl.Channel] which points to the legacy class name). Component takes artifact inputs distinguished by each "input key". For example: @@ -104,7 +104,7 @@ class BaseChannel(abc.ABC, Generic[_AT]): channel Here "examples" is the input key of the `Examples` artifact type. - `example_gen.outputs['examples']` is a channel. Typically a single channel + `#!python example_gen.outputs['examples']` is a channel. Typically a single channel refers to a *list of `Artifact` of a homogeneous type*. Since channel is a declarative abstraction it is not strictly bound to the actual artifact, but is more of an *input selector*. @@ -217,12 +217,12 @@ def __hash__(self): class Channel(json_utils.Jsonable, BaseChannel): """Legacy channel interface. - `Channel` used to represent the `BaseChannel` concept in the early TFX code, + [`Channel`][tfx.v1.dsl.Channel] used to represent the [`BaseChannel`][tfx.v1.types.BaseChannel] concept in the early TFX code, but due to having too much features in the same class, we refactored it to multiple classes: - BaseChannel for the general input abstraction - - OutputChannel for `component.outputs['key']`. + - OutputChannel for `#!python component.outputs['key']`. - MLMDQueryChannel for simple filter-based input resolution. Please do not use this class directly, but instead use the alternatives. This From d511e3ee48ce65b380876f69258224ccb3987a94 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Fri, 13 Sep 2024 23:54:18 -0700 Subject: [PATCH 40/58] Fix formatting and links for `extensions` submodule --- .../bulk_inferrer/component.py | 13 +++--- .../pusher/component.py | 10 ++--- .../trainer/component.py | 43 +++++++++++-------- .../example_gen/component.py | 3 +- .../pusher/component.py | 11 ++--- 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/tfx/extensions/google_cloud_ai_platform/bulk_inferrer/component.py b/tfx/extensions/google_cloud_ai_platform/bulk_inferrer/component.py index 4333fdcf7e..029f2c1b6e 100644 --- a/tfx/extensions/google_cloud_ai_platform/bulk_inferrer/component.py +++ b/tfx/extensions/google_cloud_ai_platform/bulk_inferrer/component.py @@ -69,9 +69,10 @@ class CloudAIBulkInferrerComponent(base_component.BaseComponent): TODO(b/155325467): Creates a end-to-end test for this component. Component `outputs` contains: - - `inference_result`: Channel of type `standard_artifacts.InferenceResult` + + - `inference_result`: Channel of type [`standard_artifacts.InferenceResult`][tfx.v1.types.standard_artifacts.InferenceResult] to store the inference results. - - `output_examples`: Channel of type `standard_artifacts.Examples` + - `output_examples`: Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples] to store the output examples. """ @@ -91,11 +92,11 @@ def __init__( """Construct an BulkInferrer component. Args: - examples: A Channel of type `standard_artifacts.Examples`, usually + examples: A Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples], usually produced by an ExampleGen component. _required_ - model: A Channel of type `standard_artifacts.Model`, usually produced by + model: A Channel of type [`standard_artifacts.Model`][tfx.v1.types.standard_artifacts.Model], usually produced by a Trainer component. - model_blessing: A Channel of type `standard_artifacts.ModelBlessing`, + model_blessing: A Channel of type [`standard_artifacts.ModelBlessing`][tfx.v1.types.standard_artifacts.ModelBlessing], usually produced by a ModelValidator component. data_spec: bulk_inferrer_pb2.DataSpec instance that describes data selection. @@ -105,7 +106,7 @@ def __init__( passed to Google Cloud AI Platform. custom_config.ai_platform_serving_args need to contain the serving job parameters. For the full set of parameters, refer to - https://cloud.google.com/ml-engine/reference/rest/v1/projects.models + [https://cloud.google.com/ml-engine/reference/rest/v1/projects.models](https://cloud.google.com/ml-engine/reference/rest/v1/projects.models) Raises: ValueError: Must not specify inference_result or output_examples depends diff --git a/tfx/extensions/google_cloud_ai_platform/pusher/component.py b/tfx/extensions/google_cloud_ai_platform/pusher/component.py index a1ebf95bf9..be4afcdfa9 100644 --- a/tfx/extensions/google_cloud_ai_platform/pusher/component.py +++ b/tfx/extensions/google_cloud_ai_platform/pusher/component.py @@ -34,15 +34,15 @@ def __init__(self, """Construct a Pusher component. Args: - model: An optional Channel of type `standard_artifacts.Model`, usually - produced by a Trainer component, representing the model used for + model: An optional Channel of type [`standard_artifacts.Model`][tfx.v1.types.standard_artifacts.Model], usually + produced by a [Trainer][tfx.v1.components.Trainer] component, representing the model used for training. model_blessing: An optional Channel of type - `standard_artifacts.ModelBlessing`, usually produced from an Evaluator + [`standard_artifacts.ModelBlessing`][tfx.v1.types.standard_artifacts.ModelBlessing], usually produced from an [Evaluator][tfx.v1.components.Evaluator] component, containing the blessing model. infra_blessing: An optional Channel of type - `standard_artifacts.InfraBlessing`, usually produced from an - InfraValidator component, containing the validation result. + [`standard_artifacts.InfraBlessing`][tfx.v1.types.standard_artifacts.InfraBlessing], usually produced from an + [InfraValidator][tfx.v1.components.InfraValidator] component, containing the validation result. custom_config: A dict which contains the deployment job parameters to be passed to Cloud platforms. """ diff --git a/tfx/extensions/google_cloud_ai_platform/trainer/component.py b/tfx/extensions/google_cloud_ai_platform/trainer/component.py index b6a8b93ecb..49eab5512e 100644 --- a/tfx/extensions/google_cloud_ai_platform/trainer/component.py +++ b/tfx/extensions/google_cloud_ai_platform/trainer/component.py @@ -47,37 +47,46 @@ def __init__(self, """Construct a Trainer component. Args: - examples: A Channel of type `standard_artifacts.Examples`, serving as the + examples: A Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples], serving as the source of examples used in training (required). May be raw or transformed. transformed_examples: Deprecated field. Please set `examples` instead. transform_graph: An optional Channel of type - `standard_artifacts.TransformGraph`, serving as the input transform + [`standard_artifacts.TransformGraph`][tfx.v1.types.standard_artifacts.TransformGraph], serving as the input transform graph if present. - schema: An optional Channel of type `standard_artifacts.Schema`, serving + schema: An optional Channel of type [`standard_artifacts.Schema`][tfx.v1.types.standard_artifacts.Schema], serving as the schema of training and eval data. Schema is optional when 1) transform_graph is provided which contains schema. 2) user module bypasses the usage of schema, e.g., hardcoded. - base_model: A Channel of type `Model`, containing model that will be used + base_model: A Channel of type [`Model`][tfx.v1.types.standard_artifacts.Model], containing model that will be used for training. This can be used for warmstart, transfer learning or model ensembling. - hyperparameters: A Channel of type `standard_artifacts.HyperParameters`, + hyperparameters: A Channel of type [`standard_artifacts.HyperParameters`][tfx.v1.types.standard_artifacts.HyperParameters], serving as the hyperparameters for training module. Tuner's output best hyperparameters can be feed into this. module_file: A path to python module file containing UDF model definition. The module_file must implement a function named `run_fn` at its top - level with function signature: `def - run_fn(trainer.fn_args_utils.FnArgs)`, and the trained model must be - saved to FnArgs.serving_model_dir when this function is executed. For - Estimator based Executor, The module_file must implement a function - named `trainer_fn` at its top level. The function must have the - following signature. def trainer_fn(trainer.fn_args_utils.FnArgs, - tensorflow_metadata.proto.v0.schema_pb2) -> Dict: ... - where the returned Dict has the following key-values. - 'estimator': an instance of tf.estimator.Estimator - 'train_spec': an instance of tf.estimator.TrainSpec - 'eval_spec': an instance of tf.estimator.EvalSpec - 'eval_input_receiver_fn': an instance of tfma EvalInputReceiver. + level with function signature: + ```python + def run_fn(trainer.fn_args_utils.FnArgs): ... + ``` + and the trained model must be + saved to FnArgs.serving_model_dir when this function is executed. For + Estimator based Executor, The module_file must implement a function + named `trainer_fn` at its top level. The function must have the + following signature. + ```python + def trainer_fn( + trainer.fn_args_utils.FnArgs, + tensorflow_metadata.proto.v0.schema_pb2 + ) -> Dict: ... + ``` + where the returned Dict has the following key-values. + + - `estimator`: an instance of tf.estimator.Estimator + - `train_spec`: an instance of tf.estimator.TrainSpec + - `eval_spec`: an instance of tf.estimator.EvalSpec + - `eval_input_receiver_fn`: an instance of tfma EvalInputReceiver. run_fn: A python path to UDF model definition function for generic trainer. See 'module_file' for details. Exactly one of 'module_file' or 'run_fn' must be supplied if Trainer uses GenericExecutor (default). diff --git a/tfx/extensions/google_cloud_big_query/example_gen/component.py b/tfx/extensions/google_cloud_big_query/example_gen/component.py index db9dd63228..a8567e6374 100644 --- a/tfx/extensions/google_cloud_big_query/example_gen/component.py +++ b/tfx/extensions/google_cloud_big_query/example_gen/component.py @@ -32,7 +32,8 @@ class BigQueryExampleGen(component.QueryBasedExampleGen): and eval examples for downstream components. Component `outputs` contains: - - `examples`: Channel of type `standard_artifacts.Examples` for output train + + - `examples`: Channel of type [`standard_artifacts.Examples`][tfx.v1.types.standard_artifacts.Examples] for output train and eval examples. """ diff --git a/tfx/extensions/google_cloud_big_query/pusher/component.py b/tfx/extensions/google_cloud_big_query/pusher/component.py index 3bd2551dd1..0728d20cd5 100644 --- a/tfx/extensions/google_cloud_big_query/pusher/component.py +++ b/tfx/extensions/google_cloud_big_query/pusher/component.py @@ -25,6 +25,7 @@ class Pusher(pusher_component.Pusher): """Cloud Big Query Pusher component. Component `outputs` contains: + - `pushed_model`: Channel of type `standard_artifacts.PushedModel` with result of push. """ @@ -39,14 +40,14 @@ def __init__(self, """Construct a Pusher component. Args: - model: An optional Channel of type `standard_artifacts.Model`, usually - produced by a Trainer component. + model: An optional Channel of type [`standard_artifacts.Model`][tfx.v1.types.standard_artifacts.Model], usually + produced by a [Trainer][tfx.v1.components.Trainer] component. model_blessing: An optional Channel of type - `standard_artifacts.ModelBlessing`, usually produced from an Evaluator + [`standard_artifacts.ModelBlessing`][tfx.v1.types.standard_artifacts.ModelBlessing], usually produced from an Evaluator component. infra_blessing: An optional Channel of type - `standard_artifacts.InfraBlessing`, usually produced from an - InfraValidator component. + [`standard_artifacts.InfraBlessing`][tfx.v1.types.standard_artifacts.InfraBlessing], usually produced from an + [InfraValidator][tfx.v1.components.InfraValidator] component. custom_config: A dict which contains the deployment job parameters to be passed to Cloud platforms. """ From 8f3bc05a04161982b5b50a3ffd9a5c70176b0daa Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Fri, 13 Sep 2024 23:54:43 -0700 Subject: [PATCH 41/58] Fix links and formatting for orchestration submodule --- tfx/orchestration/kubeflow/decorators.py | 66 +++++++++++++----------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/tfx/orchestration/kubeflow/decorators.py b/tfx/orchestration/kubeflow/decorators.py index 03eb99ff7f..65866872cf 100644 --- a/tfx/orchestration/kubeflow/decorators.py +++ b/tfx/orchestration/kubeflow/decorators.py @@ -31,36 +31,40 @@ def exit_handler(func: types.FunctionType) -> Callable[..., Any]: pipeline, parameter should be defined as Parameter[str], passing in FinalStatusStr type when initializing the component. - This is example usage of component definition using this decorator: - ``` - from tfx import v1 as tfx - - @tfx.orchestration.experimental.exit_handler - def MyExitHandlerComponent(final_status: tfx.dsl.components.Parameter[str]): - # parse the final status - pipeline_task_status = pipeline_pb2.PipelineTaskFinalStatus() - proto_utils.json_to_proto(final_status, pipeline_task_status) - print(pipeline_task_status) - ``` - - Example usage in a Vertex AI graph definition: - ``` - exit_handler = exit_handler_component( - final_status=tfx.dsl.experimental.FinalStatusStr()) - - dsl_pipeline = tfx.dsl.Pipeline(...) - - runner = tfx.orchestration.experimental.KubeflowV2DagRunner(...) - runner.set_exit_handler([exit_handler]) - runner.run(pipeline=dsl_pipeline) - ``` + !!! example + This is example usage of component definition using this decorator: + ``` python + from tfx import v1 as tfx + + + @tfx.orchestration.experimental.exit_handler + def MyExitHandlerComponent(final_status: tfx.dsl.components.Parameter[str]): + # parse the final status + pipeline_task_status = pipeline_pb2.PipelineTaskFinalStatus() + proto_utils.json_to_proto(final_status, pipeline_task_status) + print(pipeline_task_status) + ``` + + !!! example + Example usage in a Vertex AI graph definition: + ```python + exit_handler = exit_handler_component( + final_status=tfx.dsl.experimental.FinalStatusStr() + ) + + dsl_pipeline = tfx.dsl.Pipeline(...) + + runner = tfx.orchestration.experimental.KubeflowV2DagRunner(...) + runner.set_exit_handler([exit_handler]) + runner.run(pipeline=dsl_pipeline) + ``` Experimental: no backwards compatibility guarantees. Args: func: Typehint-annotated component executor function. Returns: - `base_component.BaseComponent` subclass for the given component executor + [`base_component.BaseComponent`][tfx.v1.types.BaseComponent] subclass for the given component executor function. """ return component(func) @@ -70,13 +74,15 @@ class FinalStatusStr(str): """FinalStatusStr: is the type for parameter receiving PipelineTaskFinalStatus. Vertex AI backend passes in jsonlized string of - kfp.pipeline_spec.pipeline_spec_pb2.PipelineTaskFinalStatus. + `#!python kfp.pipeline_spec.pipeline_spec_pb2.PipelineTaskFinalStatus`. - This is example usage of FinalStatusStr definition: - ``` - exit_handler = exit_handler_component( - final_status=tfx.dsl.experimental.FinalStatusStr()) - ``` + !!! example + This is example usage of FinalStatusStr definition: + ``` python + exit_handler = exit_handler_component( + final_status=tfx.dsl.experimental.FinalStatusStr() + ) + ``` """ pass From 87ba7e8de920b28f5b38b27a5101645366db7752 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sat, 14 Sep 2024 00:30:00 -0700 Subject: [PATCH 42/58] Fix links and formatting for types submodule --- tfx/types/channel.py | 33 +++++++++++++++++++-------------- tfx/types/standard_artifacts.py | 10 +++++----- tfx/types/value_artifact.py | 29 ++++++++++++++--------------- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/tfx/types/channel.py b/tfx/types/channel.py index 333e4dc89f..a00c4c3bbc 100644 --- a/tfx/types/channel.py +++ b/tfx/types/channel.py @@ -96,21 +96,23 @@ class BaseChannel(abc.ABC, Generic[_AT]): Component takes artifact inputs distinguished by each "input key". For example: - trainer = Trainer( - examples=example_gen.outputs['examples']) - ^^^^^^^^ - input key - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - channel + ``` python + trainer = Trainer( + examples=example_gen.outputs['examples'], + ) # ^^^^^^^^ + # input key + # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + # channel + ``` Here "examples" is the input key of the `Examples` artifact type. - `#!python example_gen.outputs['examples']` is a channel. Typically a single channel - refers to a *list of `Artifact` of a homogeneous type*. Since channel is a + `#!python example_gen.outputs["examples"]` is a channel. Typically a single channel + refers to a *list of [`Artifact`][tfx.v1.dsl.Artifact] of a homogeneous type*. Since channel is a declarative abstraction it is not strictly bound to the actual artifact, but is more of an *input selector*. The most commonly used channel type is an `OutputChannel` (in the form of - `component.outputs["key"]`, which selects the artifact produced by the + `#!python component.outputs["key"]`, which selects the artifact produced by the component in the same pipeline run (in synchronous execution mode; more information on OutputChannel docstring), and is typically a single artifact. @@ -732,7 +734,7 @@ def __init__( """Initialization of ExternalPipelineChannel. Args: - artifact_type: Subclass of Artifact for this channel. + artifact_type: Subclass of [Artifact][tfx.v1.dsl.Artifact] for this channel. owner: Owner of the pipeline. pipeline_name: Name of the pipeline the artifacts belong to. producer_component_id: Id of the component produces the artifacts. @@ -780,11 +782,14 @@ class ChannelWrappedPlaceholder(artifact_placeholder.ArtifactPlaceholder): yet reference its name/key wrt. the downstream component in which it is used. So a ChannelWrappedPlaceholder simply remembers the original Channel instance that was used. The Placeholder expression tree built from this wrapper is then - passed to the component that uses it, and encode_placeholder_with_channels() + passed to the component that uses it, and `encode_placeholder_with_channels()` is used to inject the key only later, when encoding the Placeholder. For instance, this allows making Predicates using syntax like: - channel.future().value > 5 + + ``` python + channel.future().value > 5 + ``` """ def __init__( @@ -803,8 +808,8 @@ def set_key(self, key: Optional[str]): setter technically violates this guarantee, but we control the effects of it by _only_ calling the setter right before an `encode()` operation on this placeholder or a larger placeholder that contains it, and then calling - set_key(None) right after. encode_placeholder_with_channels() demonstrates - how to do this correctly and should be the preferred way to call set_key(). + `#!python set_key(None)` right after. `#!python encode_placeholder_with_channels()` demonstrates + how to do this correctly and should be the preferred way to call `#!python set_key()`. Args: key: The new key for the channel. diff --git a/tfx/types/standard_artifacts.py b/tfx/types/standard_artifacts.py index b67a5978b3..8e5c1aa57b 100644 --- a/tfx/types/standard_artifacts.py +++ b/tfx/types/standard_artifacts.py @@ -84,13 +84,13 @@ class Examples(_TfxArtifact): The file and payload format must be specified as optional custom properties if not using default formats. Please see - https://www.tensorflow.org/tfx/guide/examplegen#span_version_and_split to + [https://www.tensorflow.org/tfx/guide/examplegen#span_version_and_split](https://www.tensorflow.org/tfx/guide/examplegen#span_version_and_split) to understand about span, version and splits. * Properties: - `span`: Integer to distinguish group of Examples. - `version`: Integer to represent updated data. - - `splits`: A list of split names. For example, ["train", "test"]. + - `splits`: A list of split names. For example, `#!python ["train", "test"]`. * File structure: - `{uri}/` @@ -101,10 +101,10 @@ class Examples(_TfxArtifact): * Commonly used custom properties of the Examples artifact: - `file_format`: a string that represents the file format. See - tfx/components/util/tfxio_utils.py:make_tfxio for + [tfx/components/util/tfxio_utils.py](https://github.com/tensorflow/tfx/blob/v1.15.1/tfx/components/util/tfxio_utils.py):make_tfxio for available values. - `payload_format`: int (enum) value of the data payload format. - See tfx/proto/example_gen.proto:PayloadFormat for available formats. + See [tfx/proto/example_gen.proto](https://github.com/tensorflow/tfx/blob/v1.15.1/tfx/proto/example_gen.proto):PayloadFormat for available formats. """ TYPE_NAME = "Examples" TYPE_ANNOTATION = Dataset @@ -299,7 +299,7 @@ class Schema(_TfxArtifact): Schema artifact is used to store the schema of the data. The schema is a proto that describes the data, including the type of each feature, the range of values for each feature, and other - properties. The schema is usually generated by the SchemaGen component, which + properties. The schema is usually generated by the [SchemaGen][tfx.v1.components.SchemaGen] component, which uses the statistics of the data to infer the schema. The schema can be used by other components in the pipeline to validate the data and to generate models. diff --git a/tfx/types/value_artifact.py b/tfx/types/value_artifact.py index 3716e74014..6215695296 100644 --- a/tfx/types/value_artifact.py +++ b/tfx/types/value_artifact.py @@ -106,20 +106,19 @@ def encode(self, value) -> Any: def annotate_as(cls, type_annotation: Optional[Type[SystemArtifact]] = None): """Annotate the value artifact type with a system artifact class. - Example usage: + !!! example "Example usage" - ```python - from tfx import v1 as tfx - OutputArtifact = tfx.dsl.components.OutputArtifact - String = tfx.types.standard_artifacts.String - Model = tfx.dsl.standard_annotations.Model + ```python + from tfx import v1 as tfx - @tfx.dsl.components.component - def MyTrainer( - model: OutputArtifact[String.annotate_as(Model)] - ): - ... - ``` + OutputArtifact = tfx.dsl.components.OutputArtifact + String = tfx.types.standard_artifacts.String + Model = tfx.dsl.standard_annotations.Model + + + @tfx.dsl.components.component + def MyTrainer(model: OutputArtifact[String.annotate_as(Model)]): ... + ``` Args: type_annotation: the standard annotations used to annotate the value @@ -127,9 +126,9 @@ def MyTrainer( `tfx.v1.dsl.standard_annotations`. Returns: - A subclass of the method caller class (e.g., standard_artifacts.String, - standard_artifacts.Float) with TYPE_ANNOTATION attribute set to be - `type_annotation`; returns the original class if`type_annotation` is None. + A subclass of the method caller class (e.g., [`standard_artifacts.String`][tfx.v1.types.standard_artifacts.String], + [`standard_artifacts.Float`][tfx.v1.types.standard_artifacts.Float]) with TYPE_ANNOTATION attribute set to be + `type_annotation`; returns the original class if`type_annotation` is None. """ if not type_annotation: return cls From 385715d702bd50f14c50786bf5a1210680d0bda5 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sat, 14 Sep 2024 01:09:03 -0700 Subject: [PATCH 43/58] Fix guide links in components submodule --- tfx/components/evaluator/constants.py | 2 +- tfx/components/evaluator/executor.py | 2 +- tfx/components/example_diff/component.py | 2 +- tfx/components/infra_validator/component.py | 4 ++-- tfx/components/model_validator/component.py | 4 ++-- tfx/components/pusher/executor.py | 4 ++-- tfx/components/statistics_gen/component.py | 4 ++-- tfx/components/tuner/component.py | 2 +- tfx/types/standard_artifacts.py | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tfx/components/evaluator/constants.py b/tfx/components/evaluator/constants.py index 5aec8b2c71..c57106527a 100644 --- a/tfx/components/evaluator/constants.py +++ b/tfx/components/evaluator/constants.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Constants for [Evaluator](https://www.tensorflow.org/tfx/guide/evaluator).""" +"""Constants for [Evaluator](../../../guide/evaluator).""" # Keys for artifact (custom) properties. ARTIFACT_PROPERTY_BLESSED_KEY = 'blessed' diff --git a/tfx/components/evaluator/executor.py b/tfx/components/evaluator/executor.py index 2fad481272..f01f2e12e3 100644 --- a/tfx/components/evaluator/executor.py +++ b/tfx/components/evaluator/executor.py @@ -40,7 +40,7 @@ class Executor(base_beam_executor.BaseBeamExecutor): - """Executor for [Evaluator](https://www.tensorflow.org/tfx/guide/evaluator).""" + """Executor for [Evaluator](../../../guide/evaluator).""" def _get_slice_spec_from_feature_slicing_spec( self, spec: evaluator_pb2.FeatureSlicingSpec diff --git a/tfx/components/example_diff/component.py b/tfx/components/example_diff/component.py index 87ab3e01fc..001b3197f2 100644 --- a/tfx/components/example_diff/component.py +++ b/tfx/components/example_diff/component.py @@ -47,7 +47,7 @@ def __init__(self, Args: examples_test: A [BaseChannel][tfx.v1.types.BaseChannel] of `ExamplesPath` type, as generated by the - [ExampleGen component](https://www.tensorflow.org/tfx/guide/examplegen). + [ExampleGen component](../../../guide/examplegen). This needs to contain any splits referenced in `include_split_pairs`. examples_base: A second [BaseChannel][tfx.v1.types.BaseChannel] of `ExamplesPath` type to which `examples` should be compared. This needs to contain any splits diff --git a/tfx/components/infra_validator/component.py b/tfx/components/infra_validator/component.py index ccfe7a7a91..ef053100bd 100644 --- a/tfx/components/infra_validator/component.py +++ b/tfx/components/infra_validator/component.py @@ -97,12 +97,12 @@ def __init__( Args: model: A [`BaseChannel`][tfx.v1.types.BaseChannel] of `ModelExportPath` type, usually produced by - [Trainer](https://www.tensorflow.org/tfx/guide/trainer) component. + [Trainer](../../../guide/trainer) component. _required_ serving_spec: A `ServingSpec` configuration about serving binary and test platform config to launch model server for validation. _required_ examples: A [`BaseChannel`][tfx.v1.types.BaseChannel] of `ExamplesPath` type, usually produced by - [ExampleGen](https://www.tensorflow.org/tfx/guide/examplegen) component. + [ExampleGen](../../../guide/examplegen) component. If not specified, InfraValidator does not issue requests for validation. request_spec: Optional `RequestSpec` configuration about making requests diff --git a/tfx/components/model_validator/component.py b/tfx/components/model_validator/component.py index f82e74422f..ea7ffe170d 100644 --- a/tfx/components/model_validator/component.py +++ b/tfx/components/model_validator/component.py @@ -74,11 +74,11 @@ def __init__(self, Args: examples: A BaseChannel of type `standard_artifacts.Examples`, usually produced by an - [ExampleGen](https://www.tensorflow.org/tfx/guide/examplegen) component. + [ExampleGen](../../../guide/examplegen) component. _required_ model: A BaseChannel of type `standard_artifacts.Model`, usually produced by - a [Trainer](https://www.tensorflow.org/tfx/guide/trainer) component. + a [Trainer](../../../guide/trainer) component. _required_ blessing: Output channel of type `standard_artifacts.ModelBlessing` that contains the validation result. diff --git a/tfx/components/pusher/executor.py b/tfx/components/pusher/executor.py index 2d37ad8d38..2ff068699c 100644 --- a/tfx/components/pusher/executor.py +++ b/tfx/components/pusher/executor.py @@ -56,8 +56,8 @@ class Executor(base_executor.BaseExecutor): https://github.com/tensorflow/tfx/blob/master/tfx/examples/chicago_taxi_pipeline/taxi_pipeline_simple.py#L104. For more details on tf.serving itself, please refer to - https://tensorflow.org/tfx/guide/pusher. For a tutuorial on TF Serving, - please refer to https://www.tensorflow.org/tfx/guide/serving. + [the pusher guide](../../../guide/pusher). For a tutuorial on TF Serving, + please refer to [the serving guide](../../../guide/serving). """ def CheckBlessing(self, input_dict: Dict[str, List[types.Artifact]]) -> bool: diff --git a/tfx/components/statistics_gen/component.py b/tfx/components/statistics_gen/component.py index addccc4c59..5fbeaae479 100644 --- a/tfx/components/statistics_gen/component.py +++ b/tfx/components/statistics_gen/component.py @@ -44,7 +44,7 @@ class StatisticsGen(base_beam_component.BaseBeamComponent): statistics of each split provided in the input examples. Please see [the StatisticsGen - guide](https://www.tensorflow.org/tfx/guide/statsgen) for more details. + guide](../../../guide/statsgen) for more details. """ SPEC_CLASS = standard_component_specs.StatisticsGenSpec @@ -59,7 +59,7 @@ def __init__(self, Args: examples: A BaseChannel of `ExamplesPath` type, likely generated by the - [ExampleGen component](https://www.tensorflow.org/tfx/guide/examplegen). + [ExampleGen component](../../../guide/examplegen). This needs to contain two splits labeled `train` and `eval`. _required_ schema: A `Schema` channel to use for automatically configuring the value diff --git a/tfx/components/tuner/component.py b/tfx/components/tuner/component.py index 2639aaa91e..4db47c1cb2 100644 --- a/tfx/components/tuner/component.py +++ b/tfx/components/tuner/component.py @@ -56,7 +56,7 @@ class Tuner(base_component.BaseComponent): results of all trials. Experimental: subject to change and no backwards compatibility guarantees. - See [the Tuner guide](https://www.tensorflow.org/tfx/guide/tuner) + See [the Tuner guide](../../../guide/tuner) for more details. """ diff --git a/tfx/types/standard_artifacts.py b/tfx/types/standard_artifacts.py index 8e5c1aa57b..981309badf 100644 --- a/tfx/types/standard_artifacts.py +++ b/tfx/types/standard_artifacts.py @@ -84,7 +84,7 @@ class Examples(_TfxArtifact): The file and payload format must be specified as optional custom properties if not using default formats. Please see - [https://www.tensorflow.org/tfx/guide/examplegen#span_version_and_split](https://www.tensorflow.org/tfx/guide/examplegen#span_version_and_split) to + [the `ExampleGen` guide](../../../guide/examplegen#span-version-and-split) to understand about span, version and splits. * Properties: From eab043534079f740efe2f9fb110c41edb3c45be1 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sat, 14 Sep 2024 01:23:40 -0700 Subject: [PATCH 44/58] Fix broken internal links --- docs/guide/build_local_pipeline.md | 2 +- docs/guide/examplegen.md | 6 +++--- docs/guide/keras.md | 2 +- docs/guide/tfdv.md | 2 +- docs/guide/understanding_tfx_pipelines.md | 2 +- docs/tutorials/tfx/airflow_workshop.md | 3 ++- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/guide/build_local_pipeline.md b/docs/guide/build_local_pipeline.md index c5a4e3a998..594b88fbb4 100644 --- a/docs/guide/build_local_pipeline.md +++ b/docs/guide/build_local_pipeline.md @@ -157,7 +157,7 @@ template. implement a pipeline for tabular data using the TFX standard components. If you are moving an existing ML workflow into a pipeline, you may need to revise your code to make full use of - [TFX standard components](index.md#tfx_standard_components). You may also need + [TFX standard components](index.md#tfx-standard-components). You may also need to create [custom components](understanding_custom_components.md) that implement features which are unique to your workflow or that are not yet supported by TFX standard components. diff --git a/docs/guide/examplegen.md b/docs/guide/examplegen.md index a08e09ba56..10c9b39ceb 100644 --- a/docs/guide/examplegen.md +++ b/docs/guide/examplegen.md @@ -34,7 +34,7 @@ components for these data sources and formats: * [Parquet](https://github.com/tensorflow/tfx/blob/master/tfx/components/example_gen/custom_executors/parquet_executor.py) See the usage examples in the source code and -[this discussion](examplegen.md#custom_examplegen) for more information on +[this discussion](examplegen.md#custom-examplegen) for more information on how to use and develop custom executors. !!! Note @@ -51,10 +51,10 @@ In addition, these data sources and formats are available as Apache Beam supports ingesting data from a [broad range of data sources and formats](https://beam.apache.org/documentation/io/built-in/), -([see below](#additional_data_formats)). These capabilities +([see below](#additional-data-formats)). These capabilities can be used to create custom ExampleGen components for TFX, which is demonstrated by some existing ExampleGen components -([see below](#additional_data_formats)). +([see below](#additional-data-formats)). ## How to use an ExampleGen Component diff --git a/docs/guide/keras.md b/docs/guide/keras.md index 8716f27e83..f0870b8200 100644 --- a/docs/guide/keras.md +++ b/docs/guide/keras.md @@ -135,7 +135,7 @@ will be discussed in the following Trainer and Evaluator sections. To configure native Keras, the `GenericExecutor` needs to be set for Trainer component to replace the default Estimator based executor. For details, please check -[here](trainer.md#configuring-the-trainer-component-to-use-the-genericexecutor). +[here](trainer.md#configuring-the-trainer-component). ##### Keras Module file with Transform diff --git a/docs/guide/tfdv.md b/docs/guide/tfdv.md index 5ed4b83771..ea8ca06905 100644 --- a/docs/guide/tfdv.md +++ b/docs/guide/tfdv.md @@ -24,7 +24,7 @@ TFX tools can both help find data bugs, and help with feature engineering. ## TensorFlow Data Validation * [Overview](#overview) -* [Schema Based Example Validation](#schema_based-example-validation) +* [Schema Based Example Validation](#schema-based-example-validation) * [Training-Serving Skew Detection](#skewdetect) * [Drift Detection](#drift-detection) diff --git a/docs/guide/understanding_tfx_pipelines.md b/docs/guide/understanding_tfx_pipelines.md index f0edac2546..21a043063c 100644 --- a/docs/guide/understanding_tfx_pipelines.md +++ b/docs/guide/understanding_tfx_pipelines.md @@ -35,7 +35,7 @@ which components such as the `StatisticsGen` standard component use as inputs. Artifacts must be strongly typed with an **artifact type** registered in the [ML Metadata](mlmd.md) store. Learn more about the -[concepts used in ML Metadata](mlmd.md#concepts). +[concepts used in ML Metadata](mlmd.md). Artifact types have a name and define a schema of its properties. Artifact type names must be unique in your ML Metadata store. TFX provides several diff --git a/docs/tutorials/tfx/airflow_workshop.md b/docs/tutorials/tfx/airflow_workshop.md index 12f2cbbacd..9dc033d5e3 100644 --- a/docs/tutorials/tfx/airflow_workshop.md +++ b/docs/tutorials/tfx/airflow_workshop.md @@ -59,7 +59,8 @@ The other default orchestrators supported by TFX are Apache Beam and Kubeflow. [Apache Beam](../../../guide/beam_orchestrator) can run on multiple data processing backends (Beam Ruunners). Cloud Dataflow is one such beam runner which can be used for running TFX pipelines. Apache Beam can be used -for both streaming and batch processing pipelines. \ +for both streaming and batch processing pipelines. + [Kubeflow](../../../guide/kubeflow) is an open source ML platform dedicated to making deployments of machine learning (ML) workflows on Kubernetes simple, portable and scalable. Kubeflow can be used as an From 12d3f3e8d830deec4bab956b5b37ede6bea5d106 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sat, 14 Sep 2024 18:28:26 -0700 Subject: [PATCH 45/58] Fix broken links --- docs/guide/build_local_pipeline.md | 2 +- docs/guide/examplegen.md | 2 +- docs/guide/index.md | 4 +-- docs/guide/infra_validator.md | 2 +- docs/guide/tfdv.md | 2 +- docs/guide/transform.md | 2 +- docs/tutorials/index.md | 26 +++++++++---------- .../data_preprocessing_with_cloud.md | 4 +-- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/guide/build_local_pipeline.md b/docs/guide/build_local_pipeline.md index 594b88fbb4..27475528f2 100644 --- a/docs/guide/build_local_pipeline.md +++ b/docs/guide/build_local_pipeline.md @@ -198,7 +198,7 @@ without using a template. features such as data augmentation. * Learn more about - [standard TFX components](index.md#tfx_standard_components). + [standard TFX components](index.md#tfx-standard-components). * Learn more about [custom components](understanding_custom_components.md). 1. Create a script file to define your pipeline using the following example. diff --git a/docs/guide/examplegen.md b/docs/guide/examplegen.md index 10c9b39ceb..af7be7e662 100644 --- a/docs/guide/examplegen.md +++ b/docs/guide/examplegen.md @@ -137,7 +137,7 @@ the train and eval output split is generated with a 2:1 ratio. Please refer to [proto/example_gen.proto](https://github.com/tensorflow/tfx/blob/master/tfx/proto/example_gen.proto) for ExampleGen's input and output split configuration. And refer to -[downstream components guide](#examplegen_downstream_components) for utilizing +[downstream components guide](#examplegen-downstream-components) for utilizing the custom splits downstream. #### Splitting Method diff --git a/docs/guide/index.md b/docs/guide/index.md index 692419fef9..65d86b3f30 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -244,7 +244,7 @@ monitoring, and maintaining an ML pipeline easier. TFX is designed to be portable to multiple environments and orchestration frameworks, including [Apache Airflow](airflow.md), -[Apache Beam](beam_orchestrator.md) and [Kubeflow](kubeflow.md) . It is also +[Apache Beam](beam.md) and [Kubeflow](kubeflow.md) . It is also portable to different computing platforms, including on-premise, and cloud platforms such as the [Google Cloud Platform (GCP)](https://cloud.google.com/). In particular, @@ -600,4 +600,4 @@ TFX provides a unified CLI which helps the perform full range of pipeline actions such as create, update, run, list, and delete pipelines on various orchestrators including Apache Airflow, Apache Beam, and Kubeflow. For details, please follow -[these instructions](https://github.com/tensorflow/tfx/blob/master/docs/guide/cli.md). +[these instructions](cli.md). diff --git a/docs/guide/infra_validator.md b/docs/guide/infra_validator.md index ef2f6edf20..791e9b611c 100644 --- a/docs/guide/infra_validator.md +++ b/docs/guide/infra_validator.md @@ -210,7 +210,7 @@ Current InfraValidator is not complete yet, and has some limitations. for deployments to [TensorFlow Lite](https://www.tensorflow.org/lite) and [TensorFlow.js](https://www.tensorflow.org/js), or other inference frameworks. - There's a limited support on `LOAD_AND_QUERY` mode for the - [Predict](/versions/r1.15/api_docs/python/tf/saved_model/predict_signature_def) + [Predict](https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/saved_model/predict_signature_def) method signature (which is the only exportable method in TensorFlow 2). InfraValidator requires the Predict signature to consume a serialized [`tf.Example`](https://www.tensorflow.org/tutorials/load_data/tfrecord#tfexample) as the only input. diff --git a/docs/guide/tfdv.md b/docs/guide/tfdv.md index ea8ca06905..1628f3de14 100644 --- a/docs/guide/tfdv.md +++ b/docs/guide/tfdv.md @@ -42,7 +42,7 @@ be configured to detect different classes of anomalies in the data. It can We document each of these functionalities independently: -* [Schema Based Example Validation](#schema_based-example-validation) +* [Schema Based Example Validation](#schema-based-example-validation) * [Training-Serving Skew Detection](#skewdetect) * [Drift Detection](#drift-detection) diff --git a/docs/guide/transform.md b/docs/guide/transform.md index 8ad130ffc9..db01b4e371 100644 --- a/docs/guide/transform.md +++ b/docs/guide/transform.md @@ -126,7 +126,7 @@ disk. As a TFX user, you only have to define a single function called the In `preprocessing_fn` you define a series of functions that manipulate the input dict of tensors to produce the output dict of tensors. You can find helper functions like scale_to_0_1 and compute_and_apply_vocabulary the -[TensorFlow Transform API](/tfx/transform/api_docs/python/tft) or use +[TensorFlow Transform API](https://www.tensorflow.org/tfx/transform/api_docs/python/tft) or use regular TensorFlow functions as shown below. ```python diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index d4163ca297..ed761c87fc 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -21,7 +21,7 @@ you'll learn the two main styles of developing a TFX pipeline: Probably the simplest pipeline you can build, to help you get started. Click the _Run in Google Colab_ button. - [:octicons-arrow-right-24: Starter Pipeline](tutorials/tfx/penguin_simple.md) + [:octicons-arrow-right-24: Starter Pipeline](tfx/penguin_simple.md) - __2. Adding Data Validation__ @@ -29,7 +29,7 @@ you'll learn the two main styles of developing a TFX pipeline: Building on the simple pipeline to add data validation components. - [:octicons-arrow-right-24: Data Validation](tutorials/tfx/penguin_tfdv) + [:octicons-arrow-right-24: Data Validation](tfx/penguin_tfdv) - __3. Adding Feature Engineering__ @@ -37,7 +37,7 @@ you'll learn the two main styles of developing a TFX pipeline: Building on the data validation pipeline to add a feature engineering component. - [:octicons-arrow-right-24: Feature Engineering](tutorials/tfx/penguin_tft) + [:octicons-arrow-right-24: Feature Engineering](tfx/penguin_tft) - __4. Adding Model Analysis__ @@ -45,7 +45,7 @@ you'll learn the two main styles of developing a TFX pipeline: Building on the simple pipeline to add a model analysis component. - [:octicons-arrow-right-24: Model Analysis](tutorials/tfx/penguin_tfma) + [:octicons-arrow-right-24: Model Analysis](tfx/penguin_tfma) @@ -64,7 +64,7 @@ in your TFX pipeline. Running pipelines on a managed pipeline service, Vertex Pipelines. - [:octicons-arrow-right-24: Vertex Pipelines](tutorials/tfx/gcp/vertex_pipelines_simple) + [:octicons-arrow-right-24: Vertex Pipelines](tfx/gcp/vertex_pipelines_simple) - __Read data from BigQuery__ @@ -72,7 +72,7 @@ in your TFX pipeline. Using BigQuery as a data source of ML pipelines. - [:octicons-arrow-right-24: BigQuery](tutorials/tfx/gcp/vertex_pipelines_bq) + [:octicons-arrow-right-24: BigQuery](tfx/gcp/vertex_pipelines_bq) - __Vertex AI Training and Serving__ @@ -80,7 +80,7 @@ in your TFX pipeline. Using cloud resources for ML training and serving with Vertex AI. - [:octicons-arrow-right-24: Vertex Training and Serving](tutorials/tfx/gcp/vertex_pipelines_vertex_training) + [:octicons-arrow-right-24: Vertex Training and Serving](tfx/gcp/vertex_pipelines_vertex_training) - __TFX on Cloud AI Platform Pipelines__ @@ -88,7 +88,7 @@ in your TFX pipeline. An introduction to using TFX and Cloud AI Platform Pipelines. - [:octicons-arrow-right-24: Cloud Pipelines](tutorials/tfx/cloud-ai-platform-pipelines) + [:octicons-arrow-right-24: Cloud Pipelines](tfx/cloud-ai-platform-pipelines) @@ -107,7 +107,7 @@ guides. And don't forget to read the [TFX User Guide](guide/index.md). context_, a very useful development tool. Click the _Run in Google Colab_ button. - [:octicons-arrow-right-24: Keras](tutorials/tfx/components_keras) + [:octicons-arrow-right-24: Keras](tfx/components_keras) - __Custom Component Tutorial__ @@ -115,7 +115,7 @@ guides. And don't forget to read the [TFX User Guide](guide/index.md). A tutorial showing how to develop your own custom TFX components. - [:octicons-arrow-right-24: Custom Component](tutorials/tfx/python_function_component) + [:octicons-arrow-right-24: Custom Component](tfx/python_function_component) - __Data Validation__ @@ -126,7 +126,7 @@ guides. And don't forget to read the [TFX User Guide](guide/index.md). generating descriptive statistics, inferring a schema, and finding anomalies. - [:octicons-arrow-right-24: Data Validation](tutorials/data_validation/tfdv_basic) + [:octicons-arrow-right-24: Data Validation](data_validation/tfdv_basic) - __Model Analysis__ @@ -137,7 +137,7 @@ guides. And don't forget to read the [TFX User Guide](guide/index.md). dataset and evaluate the performance of a model along several axes of accuracy. - [:octicons-arrow-right-24: Model Analysis](tutorials/model_analysis/tfma_basic) + [:octicons-arrow-right-24: Model Analysis](model_analysis/tfma_basic) - __Serve a Model__ @@ -146,7 +146,7 @@ guides. And don't forget to read the [TFX User Guide](guide/index.md). This tutorial demonstrates how TensorFlow Serving can be used to serve a model using a simple REST API. - [:octicons-arrow-right-24: Model Analysis](tutorials/serving/rest_simple) + [:octicons-arrow-right-24: Model Analysis](serving/rest_simple) diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index a8ea0db108..b49ef825ef 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -47,7 +47,7 @@ This tutorial uses the following billable components of Google Cloud: To estimate the cost to run this tutorial, assuming you use every resource for an entire day, use the preconfigured -[pricing calculator](/products/calculator/#id=fad408d8-dd68-45b8-954e-5a5619a5d148). +[pricing calculator](https://www.tensorflow.org/products/calculator#id=fad408d8-dd68-45b8-954e-5a5619a5d148). ## Before you begin @@ -466,7 +466,7 @@ following columns: - `weight_pounds` (type: `FLOAT`) As explained in -[Preprocessing operations](data-preprocessing-for-ml-with-tf-transform-pt1#preprocessing_operations) +[Preprocessing operations](data-preprocessing-for-ml-with-tf-transform-pt1#preprocessing-operations) in the first part of this series, the feature transformation converts categorical features to a numeric representation. After the transformation, the categorical features are represented by integer values. In the From ece000cda06601ddab97b963adb9fe20b2e51c4f Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:35:20 -0700 Subject: [PATCH 46/58] Fix button formatting in penguin_simple --- docs/stylesheets/extra.css | 27 + docs/tutorials/tfx/penguin_simple.ipynb | 1329 ++++++++++++----------- 2 files changed, 710 insertions(+), 646 deletions(-) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index e734efefd6..21c97aa98c 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -13,3 +13,30 @@ width: 100%; aspect-ratio: 16 / 9; } + +.buttons-wrapper { + flex-wrap: wrap; + gap: 1em; + display: flex; + /* flex-grow: 1; */ + /* justify-content: center; */ + /* align-content: center; */ +} + +.buttons-wrapper > a { + justify-content: center; + align-content: center; + flex-wrap: nowrap; + /* gap: 1em; */ + align-items: center; + text-align: center; + flex: 1 1 30%; + display: flex; +} + +.md-button > .buttons-content { + align-items: center; + justify-content: center; + display: flex; + gap: 1em; +} diff --git a/docs/tutorials/tfx/penguin_simple.ipynb b/docs/tutorials/tfx/penguin_simple.ipynb index 6a2e708290..7783bb3fce 100644 --- a/docs/tutorials/tfx/penguin_simple.ipynb +++ b/docs/tutorials/tfx/penguin_simple.ipynb @@ -1,648 +1,685 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "name": "penguin_simple.ipynb", - "provenance": [], - "collapsed_sections": [ - "DjUA6S30k52h" - ], - "toc_visible": true - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - } - }, - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "DjUA6S30k52h" - }, - "source": [ - "##### Copyright 2021 The TensorFlow Authors." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "SpNWyqewk8fE" - }, - "source": [ - "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "6x1ypzczQCwy" - }, - "source": [ - "# Simple TFX Pipeline Tutorial using Penguin dataset\n", - "\n", - "***A Short tutorial to run a simple TFX pipeline.***" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "HU9YYythm0dx" - }, - "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/penguin_simple.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/penguin_simple.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/penguin_simple.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "_VuwrlnvQJ5k" - }, - "source": [ - "In this notebook-based tutorial, we will create and run a TFX pipeline\n", - "for a simple classification model.\n", - "The pipeline will consist of three essential TFX components: ExampleGen,\n", - "Trainer and Pusher. The pipeline includes the most minimal ML workflow like\n", - "importing data, training a model and exporting the trained model.\n", - "\n", - "Please see\n", - "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", - "to learn more about various concepts in TFX." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Fmgi8ZvQkScg" - }, - "source": [ - "## Set Up\n", - "We first need to install the TFX Python package and download\n", - "the dataset which we will use for our model.\n", - "\n", - "### Upgrade Pip\n", - "\n", - "To avoid upgrading Pip in a system when running locally,\n", - "check to make sure that we are running in Colab.\n", - "Local systems can of course be upgraded separately." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "as4OTe2ukSqm" - }, - "source": [ - "try:\n", - " import colab\n", - " !pip install --upgrade pip\n", - "except:\n", - " pass" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "MZOYTt1RW4TK" - }, - "source": [ - "### Install TFX\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "iyQtljP-qPHY" - }, - "source": [ - "!pip install -U tfx" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "EwT0nov5QO1M" - }, - "source": [ - "### Did you restart the runtime?\n", - "\n", - "If you are using Google Colab, the first time that you run\n", - "the cell above, you must restart the runtime by clicking\n", - "above \"RESTART RUNTIME\" button or using \"Runtime \u003e Restart\n", - "runtime ...\" menu. This is because of the way that Colab\n", - "loads packages." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "BDnPgN8UJtzN" - }, - "source": [ - "Check the TensorFlow and TFX versions." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "6jh7vKSRqPHb" - }, - "source": [ - "import tensorflow as tf\n", - "print('TensorFlow version: {}'.format(tf.__version__))\n", - "from tfx import v1 as tfx\n", - "print('TFX version: {}'.format(tfx.__version__))" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aDtLdSkvqPHe" - }, - "source": [ - "### Set up variables\n", - "\n", - "There are some variables used to define a pipeline. You can customize these\n", - "variables as you want. By default all output from the pipeline will be\n", - "generated under the current directory." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "EcUseqJaE2XN" - }, - "source": [ - "import os\n", - "\n", - "PIPELINE_NAME = \"penguin-simple\"\n", - "\n", - "# Output directory to store artifacts generated from the pipeline.\n", - "PIPELINE_ROOT = os.path.join('pipelines', PIPELINE_NAME)\n", - "# Path to a SQLite DB file to use as an MLMD storage.\n", - "METADATA_PATH = os.path.join('metadata', PIPELINE_NAME, 'metadata.db')\n", - "# Output directory where created models from the pipeline will be exported.\n", - "SERVING_MODEL_DIR = os.path.join('serving_model', PIPELINE_NAME)\n", - "\n", - "from absl import logging\n", - "logging.set_verbosity(logging.INFO) # Set default logging level." - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "8F2SRwRLSYGa" - }, - "source": [ - "### Prepare example data\n", - "We will download the example dataset for use in our TFX pipeline. The dataset we\n", - "are using is\n", - "[Palmer Penguins dataset](https://allisonhorst.github.io/palmerpenguins/articles/intro.html)\n", - "which is also used in other\n", - "[TFX examples](https://github.com/tensorflow/tfx/tree/master/tfx/examples/penguin).\n", - "\n", - "There are four numeric features in this dataset:\n", - "\n", - "- culmen_length_mm\n", - "- culmen_depth_mm\n", - "- flipper_length_mm\n", - "- body_mass_g\n", - "\n", - "All features were already normalized to have range [0,1]. We will build a\n", - "classification model which predicts the `species` of penguins." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "11J7XiCq6AFP" - }, - "source": [ - "Because TFX ExampleGen reads inputs from a directory, we need to create a\n", - "directory and copy dataset to it." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "4fxMs6u86acP" - }, - "source": [ - "import urllib.request\n", - "import tempfile\n", - "\n", - "DATA_ROOT = tempfile.mkdtemp(prefix='tfx-data') # Create a temporary directory.\n", - "_data_url = 'https://raw.githubusercontent.com/tensorflow/tfx/master/tfx/examples/penguin/data/labelled/penguins_processed.csv'\n", - "_data_filepath = os.path.join(DATA_ROOT, \"data.csv\")\n", - "urllib.request.urlretrieve(_data_url, _data_filepath)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ASpoNmxKSQjI" - }, - "source": [ - "Take a quick look at the CSV file." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "-eSz28UDSnlG" - }, - "source": [ - "!head {_data_filepath}" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "OTtQNq1DdVvG" - }, - "source": [ - "You should be able to see five values. `species` is one of 0, 1 or 2, and all\n", - "other features should have values between 0 and 1." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "nH6gizcpSwWV" - }, - "source": [ - "## Create a pipeline\n", - "\n", - "TFX pipelines are defined using Python APIs. We will define a pipeline which\n", - "consists of following three components.\n", - "- CsvExampleGen: Reads in data files and convert them to TFX internal format\n", - "for further processing. There are multiple\n", - "[ExampleGen](../../../guide/examplegen)s for various\n", - "formats. In this tutorial, we will use CsvExampleGen which takes CSV file input.\n", - "- Trainer: Trains an ML model.\n", - "[Trainer component](../../../guide/trainer) requires a\n", - "model definition code from users. You can use TensorFlow APIs to specify how to\n", - "train a model and save it in a _saved_model_ format.\n", - "- Pusher: Copies the trained model outside of the TFX pipeline.\n", - "[Pusher component](../../../guide/pusher) can be thought\n", - "of as a deployment process of the trained ML model.\n", - "\n", - "Before actually define the pipeline, we need to write a model code for the\n", - "Trainer component first." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "lOjDv93eS5xV" - }, - "source": [ - "### Write model training code\n", - "\n", - "We will create a simple DNN model for classification using TensorFlow Keras\n", - "API. This model training code will be saved to a separate file.\n", - "\n", - "In this tutorial we will use\n", - "[Generic Trainer](../../../guide/trainer#generic_trainer)\n", - "of TFX which support Keras-based models. You need to write a Python file\n", - "containing `run_fn` function, which is the entrypoint for the `Trainer`\n", - "component." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "aES7Hv5QTDK3" - }, - "source": [ - "_trainer_module_file = 'penguin_trainer.py'" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "metadata": { - "id": "Gnc67uQNTDfW" - }, - "source": [ - "%%writefile {_trainer_module_file}\n", - "\n", - "from typing import List\n", - "from absl import logging\n", - "import tensorflow as tf\n", - "from tensorflow import keras\n", - "from tensorflow_transform.tf_metadata import schema_utils\n", - "\n", - "from tfx import v1 as tfx\n", - "from tfx_bsl.public import tfxio\n", - "from tensorflow_metadata.proto.v0 import schema_pb2\n", - "\n", - "_FEATURE_KEYS = [\n", - " 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g'\n", - "]\n", - "_LABEL_KEY = 'species'\n", - "\n", - "_TRAIN_BATCH_SIZE = 20\n", - "_EVAL_BATCH_SIZE = 10\n", - "\n", - "# Since we're not generating or creating a schema, we will instead create\n", - "# a feature spec. Since there are a fairly small number of features this is\n", - "# manageable for this dataset.\n", - "_FEATURE_SPEC = {\n", - " **{\n", - " feature: tf.io.FixedLenFeature(shape=[1], dtype=tf.float32)\n", - " for feature in _FEATURE_KEYS\n", - " },\n", - " _LABEL_KEY: tf.io.FixedLenFeature(shape=[1], dtype=tf.int64)\n", - "}\n", - "\n", - "\n", - "def _input_fn(file_pattern: List[str],\n", - " data_accessor: tfx.components.DataAccessor,\n", - " schema: schema_pb2.Schema,\n", - " batch_size: int = 200) -\u003e tf.data.Dataset:\n", - " \"\"\"Generates features and label for training.\n", - "\n", - " Args:\n", - " file_pattern: List of paths or patterns of input tfrecord files.\n", - " data_accessor: DataAccessor for converting input to RecordBatch.\n", - " schema: schema of the input data.\n", - " batch_size: representing the number of consecutive elements of returned\n", - " dataset to combine in a single batch\n", - "\n", - " Returns:\n", - " A dataset that contains (features, indices) tuple where features is a\n", - " dictionary of Tensors, and indices is a single Tensor of label indices.\n", - " \"\"\"\n", - " return data_accessor.tf_dataset_factory(\n", - " file_pattern,\n", - " tfxio.TensorFlowDatasetOptions(\n", - " batch_size=batch_size, label_key=_LABEL_KEY),\n", - " schema=schema).repeat()\n", - "\n", - "\n", - "def _build_keras_model() -\u003e tf.keras.Model:\n", - " \"\"\"Creates a DNN Keras model for classifying penguin data.\n", - "\n", - " Returns:\n", - " A Keras Model.\n", - " \"\"\"\n", - " # The model below is built with Functional API, please refer to\n", - " # https://www.tensorflow.org/guide/keras/overview for all API options.\n", - " inputs = [keras.layers.Input(shape=(1,), name=f) for f in _FEATURE_KEYS]\n", - " d = keras.layers.concatenate(inputs)\n", - " for _ in range(2):\n", - " d = keras.layers.Dense(8, activation='relu')(d)\n", - " outputs = keras.layers.Dense(3)(d)\n", - "\n", - " model = keras.Model(inputs=inputs, outputs=outputs)\n", - " model.compile(\n", - " optimizer=keras.optimizers.Adam(1e-2),\n", - " loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n", - " metrics=[keras.metrics.SparseCategoricalAccuracy()])\n", - "\n", - " model.summary(print_fn=logging.info)\n", - " return model\n", - "\n", - "\n", - "# TFX Trainer will call this function.\n", - "def run_fn(fn_args: tfx.components.FnArgs):\n", - " \"\"\"Train the model based on given args.\n", - "\n", - " Args:\n", - " fn_args: Holds args used to train the model as name/value pairs.\n", - " \"\"\"\n", - "\n", - " # This schema is usually either an output of SchemaGen or a manually-curated\n", - " # version provided by pipeline author. A schema can also derived from TFT\n", - " # graph if a Transform component is used. In the case when either is missing,\n", - " # `schema_from_feature_spec` could be used to generate schema from very simple\n", - " # feature_spec, but the schema returned would be very primitive.\n", - " schema = schema_utils.schema_from_feature_spec(_FEATURE_SPEC)\n", - "\n", - " train_dataset = _input_fn(\n", - " fn_args.train_files,\n", - " fn_args.data_accessor,\n", - " schema,\n", - " batch_size=_TRAIN_BATCH_SIZE)\n", - " eval_dataset = _input_fn(\n", - " fn_args.eval_files,\n", - " fn_args.data_accessor,\n", - " schema,\n", - " batch_size=_EVAL_BATCH_SIZE)\n", - "\n", - " model = _build_keras_model()\n", - " model.fit(\n", - " train_dataset,\n", - " steps_per_epoch=fn_args.train_steps,\n", - " validation_data=eval_dataset,\n", - " validation_steps=fn_args.eval_steps)\n", - "\n", - " # The result of the training should be saved in `fn_args.serving_model_dir`\n", - " # directory.\n", - " model.save(fn_args.serving_model_dir, save_format='tf')" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "blaw0rs-emEf" - }, - "source": [ - "Now you have completed all preparation steps to build a TFX pipeline." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "w3OkNz3gTLwM" - }, - "source": [ - "### Write a pipeline definition\n", - "\n", - "We define a function to create a TFX pipeline. A `Pipeline` object\n", - "represents a TFX pipeline which can be run using one of the pipeline\n", - "orchestration systems that TFX supports.\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "M49yYVNBTPd4" - }, - "source": [ - "def _create_pipeline(pipeline_name: str, pipeline_root: str, data_root: str,\n", - " module_file: str, serving_model_dir: str,\n", - " metadata_path: str) -\u003e tfx.dsl.Pipeline:\n", - " \"\"\"Creates a three component penguin pipeline with TFX.\"\"\"\n", - " # Brings data into the pipeline.\n", - " example_gen = tfx.components.CsvExampleGen(input_base=data_root)\n", - "\n", - " # Uses user-provided Python function that trains a model.\n", - " trainer = tfx.components.Trainer(\n", - " module_file=module_file,\n", - " examples=example_gen.outputs['examples'],\n", - " train_args=tfx.proto.TrainArgs(num_steps=100),\n", - " eval_args=tfx.proto.EvalArgs(num_steps=5))\n", - "\n", - " # Pushes the model to a filesystem destination.\n", - " pusher = tfx.components.Pusher(\n", - " model=trainer.outputs['model'],\n", - " push_destination=tfx.proto.PushDestination(\n", - " filesystem=tfx.proto.PushDestination.Filesystem(\n", - " base_directory=serving_model_dir)))\n", - "\n", - " # Following three components will be included in the pipeline.\n", - " components = [\n", - " example_gen,\n", - " trainer,\n", - " pusher,\n", - " ]\n", - "\n", - " return tfx.dsl.Pipeline(\n", - " pipeline_name=pipeline_name,\n", - " pipeline_root=pipeline_root,\n", - " metadata_connection_config=tfx.orchestration.metadata\n", - " .sqlite_metadata_connection_config(metadata_path),\n", - " components=components)" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mJbq07THU2GV" - }, - "source": [ - "## Run the pipeline\n", - "\n", - "TFX supports multiple orchestrators to run pipelines.\n", - "In this tutorial we will use `LocalDagRunner` which is included in the TFX\n", - "Python package and runs pipelines on local environment.\n", - "We often call TFX pipelines \"DAGs\" which stands for directed acyclic graph.\n", - "\n", - "`LocalDagRunner` provides fast iterations for development and debugging.\n", - "TFX also supports other orchestrators including Kubeflow Pipelines and Apache\n", - "Airflow which are suitable for production use cases.\n", - "\n", - "See\n", - "[TFX on Cloud AI Platform Pipelines](https://www.tensorflow.org/tfx/tutorials/tfx/cloud-ai-platform-pipelines)\n", - "or\n", - "[TFX Airflow Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/airflow_workshop)\n", - "to learn more about other orchestration systems." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "7mp0AkmrPdUb" - }, - "source": [ - "Now we create a `LocalDagRunner` and pass a `Pipeline` object created from the\n", - "function we already defined.\n", - "\n", - "The pipeline runs directly and you can see logs for the progress of the pipeline including ML model training." - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "fAtfOZTYWJu-" - }, - "source": [ - "tfx.orchestration.LocalDagRunner().run(\n", - " _create_pipeline(\n", - " pipeline_name=PIPELINE_NAME,\n", - " pipeline_root=PIPELINE_ROOT,\n", - " data_root=DATA_ROOT,\n", - " module_file=_trainer_module_file,\n", - " serving_model_dir=SERVING_MODEL_DIR,\n", - " metadata_path=METADATA_PATH))" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ppERq0Mj6xvW" - }, - "source": [ - "You should see \"INFO:absl:Component Pusher is finished.\" at the end of the\n", - "logs if the pipeline finished successfully. Because `Pusher` component is the\n", - "last component of the pipeline.\n", - "\n", - "The pusher component pushes the trained model to the `SERVING_MODEL_DIR` which\n", - "is the `serving_model/penguin-simple` directory if you did not change the\n", - "variables in the previous steps. You can see the result from the file browser\n", - "in the left-side panel in Colab, or using the following command:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "NTHROkqX6yHx" - }, - "source": [ - "# List files in created model directory.\n", - "!find {SERVING_MODEL_DIR}" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "08R8qvweThRf" - }, - "source": [ - "## Next steps\n", - "\n", - "You can find more resources on https://www.tensorflow.org/tfx/tutorials.\n", - "\n", - "Please see\n", - "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", - "to learn more about various concepts in TFX.\n" - ] - } - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "DjUA6S30k52h" + }, + "source": [ + "##### Copyright 2021 The TensorFlow Authors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "SpNWyqewk8fE" + }, + "outputs": [], + "source": [ + "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6x1ypzczQCwy" + }, + "source": [ + "# Simple TFX Pipeline Tutorial using Penguin dataset\n", + "\n", + "***A Short tutorial to run a simple TFX pipeline.***" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HU9YYythm0dx" + }, + "source": [ + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_VuwrlnvQJ5k" + }, + "source": [ + "In this notebook-based tutorial, we will create and run a TFX pipeline\n", + "for a simple classification model.\n", + "The pipeline will consist of three essential TFX components: ExampleGen,\n", + "Trainer and Pusher. The pipeline includes the most minimal ML workflow like\n", + "importing data, training a model and exporting the trained model.\n", + "\n", + "Please see\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", + "to learn more about various concepts in TFX." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Fmgi8ZvQkScg" + }, + "source": [ + "## Set Up\n", + "We first need to install the TFX Python package and download\n", + "the dataset which we will use for our model.\n", + "\n", + "### Upgrade Pip\n", + "\n", + "To avoid upgrading Pip in a system when running locally,\n", + "check to make sure that we are running in Colab.\n", + "Local systems can of course be upgraded separately." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "as4OTe2ukSqm" + }, + "outputs": [], + "source": [ + "try:\n", + " import colab\n", + " !pip install --upgrade pip\n", + "except:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MZOYTt1RW4TK" + }, + "source": [ + "### Install TFX\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "iyQtljP-qPHY" + }, + "outputs": [], + "source": [ + "!pip install -U tfx" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EwT0nov5QO1M" + }, + "source": [ + "### Did you restart the runtime?\n", + "\n", + "If you are using Google Colab, the first time that you run\n", + "the cell above, you must restart the runtime by clicking\n", + "above \"RESTART RUNTIME\" button or using \"Runtime > Restart\n", + "runtime ...\" menu. This is because of the way that Colab\n", + "loads packages." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BDnPgN8UJtzN" + }, + "source": [ + "Check the TensorFlow and TFX versions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6jh7vKSRqPHb" + }, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "print('TensorFlow version: {}'.format(tf.__version__))\n", + "from tfx import v1 as tfx\n", + "print('TFX version: {}'.format(tfx.__version__))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aDtLdSkvqPHe" + }, + "source": [ + "### Set up variables\n", + "\n", + "There are some variables used to define a pipeline. You can customize these\n", + "variables as you want. By default all output from the pipeline will be\n", + "generated under the current directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "EcUseqJaE2XN" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "PIPELINE_NAME = \"penguin-simple\"\n", + "\n", + "# Output directory to store artifacts generated from the pipeline.\n", + "PIPELINE_ROOT = os.path.join('pipelines', PIPELINE_NAME)\n", + "# Path to a SQLite DB file to use as an MLMD storage.\n", + "METADATA_PATH = os.path.join('metadata', PIPELINE_NAME, 'metadata.db')\n", + "# Output directory where created models from the pipeline will be exported.\n", + "SERVING_MODEL_DIR = os.path.join('serving_model', PIPELINE_NAME)\n", + "\n", + "from absl import logging\n", + "logging.set_verbosity(logging.INFO) # Set default logging level." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8F2SRwRLSYGa" + }, + "source": [ + "### Prepare example data\n", + "We will download the example dataset for use in our TFX pipeline. The dataset we\n", + "are using is\n", + "[Palmer Penguins dataset](https://allisonhorst.github.io/palmerpenguins/articles/intro.html)\n", + "which is also used in other\n", + "[TFX examples](https://github.com/tensorflow/tfx/tree/master/tfx/examples/penguin).\n", + "\n", + "There are four numeric features in this dataset:\n", + "\n", + "- culmen_length_mm\n", + "- culmen_depth_mm\n", + "- flipper_length_mm\n", + "- body_mass_g\n", + "\n", + "All features were already normalized to have range [0,1]. We will build a\n", + "classification model which predicts the `species` of penguins." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "11J7XiCq6AFP" + }, + "source": [ + "Because TFX ExampleGen reads inputs from a directory, we need to create a\n", + "directory and copy dataset to it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "4fxMs6u86acP" + }, + "outputs": [], + "source": [ + "import urllib.request\n", + "import tempfile\n", + "\n", + "DATA_ROOT = tempfile.mkdtemp(prefix='tfx-data') # Create a temporary directory.\n", + "_data_url = 'https://raw.githubusercontent.com/tensorflow/tfx/master/tfx/examples/penguin/data/labelled/penguins_processed.csv'\n", + "_data_filepath = os.path.join(DATA_ROOT, \"data.csv\")\n", + "urllib.request.urlretrieve(_data_url, _data_filepath)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ASpoNmxKSQjI" + }, + "source": [ + "Take a quick look at the CSV file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-eSz28UDSnlG" + }, + "outputs": [], + "source": [ + "!head {_data_filepath}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OTtQNq1DdVvG" + }, + "source": [ + "You should be able to see five values. `species` is one of 0, 1 or 2, and all\n", + "other features should have values between 0 and 1." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nH6gizcpSwWV" + }, + "source": [ + "## Create a pipeline\n", + "\n", + "TFX pipelines are defined using Python APIs. We will define a pipeline which\n", + "consists of following three components.\n", + "- CsvExampleGen: Reads in data files and convert them to TFX internal format\n", + "for further processing. There are multiple\n", + "[ExampleGen](../../../guide/examplegen)s for various\n", + "formats. In this tutorial, we will use CsvExampleGen which takes CSV file input.\n", + "- Trainer: Trains an ML model.\n", + "[Trainer component](../../../guide/trainer) requires a\n", + "model definition code from users. You can use TensorFlow APIs to specify how to\n", + "train a model and save it in a _saved_model_ format.\n", + "- Pusher: Copies the trained model outside of the TFX pipeline.\n", + "[Pusher component](../../../guide/pusher) can be thought\n", + "of as a deployment process of the trained ML model.\n", + "\n", + "Before actually define the pipeline, we need to write a model code for the\n", + "Trainer component first." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lOjDv93eS5xV" + }, + "source": [ + "### Write model training code\n", + "\n", + "We will create a simple DNN model for classification using TensorFlow Keras\n", + "API. This model training code will be saved to a separate file.\n", + "\n", + "In this tutorial we will use\n", + "[Generic Trainer](../../../guide/trainer#generic_trainer)\n", + "of TFX which support Keras-based models. You need to write a Python file\n", + "containing `run_fn` function, which is the entrypoint for the `Trainer`\n", + "component." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "aES7Hv5QTDK3" + }, + "outputs": [], + "source": [ + "_trainer_module_file = 'penguin_trainer.py'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Gnc67uQNTDfW" + }, + "outputs": [], + "source": [ + "%%writefile {_trainer_module_file}\n", + "\n", + "from typing import List\n", + "from absl import logging\n", + "import tensorflow as tf\n", + "from tensorflow import keras\n", + "from tensorflow_transform.tf_metadata import schema_utils\n", + "\n", + "from tfx import v1 as tfx\n", + "from tfx_bsl.public import tfxio\n", + "from tensorflow_metadata.proto.v0 import schema_pb2\n", + "\n", + "_FEATURE_KEYS = [\n", + " 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g'\n", + "]\n", + "_LABEL_KEY = 'species'\n", + "\n", + "_TRAIN_BATCH_SIZE = 20\n", + "_EVAL_BATCH_SIZE = 10\n", + "\n", + "# Since we're not generating or creating a schema, we will instead create\n", + "# a feature spec. Since there are a fairly small number of features this is\n", + "# manageable for this dataset.\n", + "_FEATURE_SPEC = {\n", + " **{\n", + " feature: tf.io.FixedLenFeature(shape=[1], dtype=tf.float32)\n", + " for feature in _FEATURE_KEYS\n", + " },\n", + " _LABEL_KEY: tf.io.FixedLenFeature(shape=[1], dtype=tf.int64)\n", + "}\n", + "\n", + "\n", + "def _input_fn(file_pattern: List[str],\n", + " data_accessor: tfx.components.DataAccessor,\n", + " schema: schema_pb2.Schema,\n", + " batch_size: int = 200) -> tf.data.Dataset:\n", + " \"\"\"Generates features and label for training.\n", + "\n", + " Args:\n", + " file_pattern: List of paths or patterns of input tfrecord files.\n", + " data_accessor: DataAccessor for converting input to RecordBatch.\n", + " schema: schema of the input data.\n", + " batch_size: representing the number of consecutive elements of returned\n", + " dataset to combine in a single batch\n", + "\n", + " Returns:\n", + " A dataset that contains (features, indices) tuple where features is a\n", + " dictionary of Tensors, and indices is a single Tensor of label indices.\n", + " \"\"\"\n", + " return data_accessor.tf_dataset_factory(\n", + " file_pattern,\n", + " tfxio.TensorFlowDatasetOptions(\n", + " batch_size=batch_size, label_key=_LABEL_KEY),\n", + " schema=schema).repeat()\n", + "\n", + "\n", + "def _build_keras_model() -> tf.keras.Model:\n", + " \"\"\"Creates a DNN Keras model for classifying penguin data.\n", + "\n", + " Returns:\n", + " A Keras Model.\n", + " \"\"\"\n", + " # The model below is built with Functional API, please refer to\n", + " # https://www.tensorflow.org/guide/keras/overview for all API options.\n", + " inputs = [keras.layers.Input(shape=(1,), name=f) for f in _FEATURE_KEYS]\n", + " d = keras.layers.concatenate(inputs)\n", + " for _ in range(2):\n", + " d = keras.layers.Dense(8, activation='relu')(d)\n", + " outputs = keras.layers.Dense(3)(d)\n", + "\n", + " model = keras.Model(inputs=inputs, outputs=outputs)\n", + " model.compile(\n", + " optimizer=keras.optimizers.Adam(1e-2),\n", + " loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n", + " metrics=[keras.metrics.SparseCategoricalAccuracy()])\n", + "\n", + " model.summary(print_fn=logging.info)\n", + " return model\n", + "\n", + "\n", + "# TFX Trainer will call this function.\n", + "def run_fn(fn_args: tfx.components.FnArgs):\n", + " \"\"\"Train the model based on given args.\n", + "\n", + " Args:\n", + " fn_args: Holds args used to train the model as name/value pairs.\n", + " \"\"\"\n", + "\n", + " # This schema is usually either an output of SchemaGen or a manually-curated\n", + " # version provided by pipeline author. A schema can also derived from TFT\n", + " # graph if a Transform component is used. In the case when either is missing,\n", + " # `schema_from_feature_spec` could be used to generate schema from very simple\n", + " # feature_spec, but the schema returned would be very primitive.\n", + " schema = schema_utils.schema_from_feature_spec(_FEATURE_SPEC)\n", + "\n", + " train_dataset = _input_fn(\n", + " fn_args.train_files,\n", + " fn_args.data_accessor,\n", + " schema,\n", + " batch_size=_TRAIN_BATCH_SIZE)\n", + " eval_dataset = _input_fn(\n", + " fn_args.eval_files,\n", + " fn_args.data_accessor,\n", + " schema,\n", + " batch_size=_EVAL_BATCH_SIZE)\n", + "\n", + " model = _build_keras_model()\n", + " model.fit(\n", + " train_dataset,\n", + " steps_per_epoch=fn_args.train_steps,\n", + " validation_data=eval_dataset,\n", + " validation_steps=fn_args.eval_steps)\n", + "\n", + " # The result of the training should be saved in `fn_args.serving_model_dir`\n", + " # directory.\n", + " model.save(fn_args.serving_model_dir, save_format='tf')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "blaw0rs-emEf" + }, + "source": [ + "Now you have completed all preparation steps to build a TFX pipeline." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "w3OkNz3gTLwM" + }, + "source": [ + "### Write a pipeline definition\n", + "\n", + "We define a function to create a TFX pipeline. A `Pipeline` object\n", + "represents a TFX pipeline which can be run using one of the pipeline\n", + "orchestration systems that TFX supports.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "M49yYVNBTPd4" + }, + "outputs": [], + "source": [ + "def _create_pipeline(pipeline_name: str, pipeline_root: str, data_root: str,\n", + " module_file: str, serving_model_dir: str,\n", + " metadata_path: str) -> tfx.dsl.Pipeline:\n", + " \"\"\"Creates a three component penguin pipeline with TFX.\"\"\"\n", + " # Brings data into the pipeline.\n", + " example_gen = tfx.components.CsvExampleGen(input_base=data_root)\n", + "\n", + " # Uses user-provided Python function that trains a model.\n", + " trainer = tfx.components.Trainer(\n", + " module_file=module_file,\n", + " examples=example_gen.outputs['examples'],\n", + " train_args=tfx.proto.TrainArgs(num_steps=100),\n", + " eval_args=tfx.proto.EvalArgs(num_steps=5))\n", + "\n", + " # Pushes the model to a filesystem destination.\n", + " pusher = tfx.components.Pusher(\n", + " model=trainer.outputs['model'],\n", + " push_destination=tfx.proto.PushDestination(\n", + " filesystem=tfx.proto.PushDestination.Filesystem(\n", + " base_directory=serving_model_dir)))\n", + "\n", + " # Following three components will be included in the pipeline.\n", + " components = [\n", + " example_gen,\n", + " trainer,\n", + " pusher,\n", + " ]\n", + "\n", + " return tfx.dsl.Pipeline(\n", + " pipeline_name=pipeline_name,\n", + " pipeline_root=pipeline_root,\n", + " metadata_connection_config=tfx.orchestration.metadata\n", + " .sqlite_metadata_connection_config(metadata_path),\n", + " components=components)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mJbq07THU2GV" + }, + "source": [ + "## Run the pipeline\n", + "\n", + "TFX supports multiple orchestrators to run pipelines.\n", + "In this tutorial we will use `LocalDagRunner` which is included in the TFX\n", + "Python package and runs pipelines on local environment.\n", + "We often call TFX pipelines \"DAGs\" which stands for directed acyclic graph.\n", + "\n", + "`LocalDagRunner` provides fast iterations for development and debugging.\n", + "TFX also supports other orchestrators including Kubeflow Pipelines and Apache\n", + "Airflow which are suitable for production use cases.\n", + "\n", + "See\n", + "[TFX on Cloud AI Platform Pipelines](https://www.tensorflow.org/tfx/tutorials/tfx/cloud-ai-platform-pipelines)\n", + "or\n", + "[TFX Airflow Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/airflow_workshop)\n", + "to learn more about other orchestration systems." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7mp0AkmrPdUb" + }, + "source": [ + "Now we create a `LocalDagRunner` and pass a `Pipeline` object created from the\n", + "function we already defined.\n", + "\n", + "The pipeline runs directly and you can see logs for the progress of the pipeline including ML model training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "fAtfOZTYWJu-" + }, + "outputs": [], + "source": [ + "tfx.orchestration.LocalDagRunner().run(\n", + " _create_pipeline(\n", + " pipeline_name=PIPELINE_NAME,\n", + " pipeline_root=PIPELINE_ROOT,\n", + " data_root=DATA_ROOT,\n", + " module_file=_trainer_module_file,\n", + " serving_model_dir=SERVING_MODEL_DIR,\n", + " metadata_path=METADATA_PATH))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ppERq0Mj6xvW" + }, + "source": [ + "You should see \"INFO:absl:Component Pusher is finished.\" at the end of the\n", + "logs if the pipeline finished successfully. Because `Pusher` component is the\n", + "last component of the pipeline.\n", + "\n", + "The pusher component pushes the trained model to the `SERVING_MODEL_DIR` which\n", + "is the `serving_model/penguin-simple` directory if you did not change the\n", + "variables in the previous steps. You can see the result from the file browser\n", + "in the left-side panel in Colab, or using the following command:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "NTHROkqX6yHx" + }, + "outputs": [], + "source": [ + "# List files in created model directory.\n", + "!find {SERVING_MODEL_DIR}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "08R8qvweThRf" + }, + "source": [ + "## Next steps\n", + "\n", + "You can find more resources on https://www.tensorflow.org/tfx/tutorials.\n", + "\n", + "Please see\n", + "[Understanding TFX Pipelines](../../../guide/understanding_tfx_pipelines)\n", + "to learn more about various concepts in TFX.\n" + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [ + "DjUA6S30k52h" + ], + "name": "penguin_simple.ipynb", + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } From f618708fe14535b5482fff74dc101f940cfadd1b Mon Sep 17 00:00:00 2001 From: pdmurray Date: Tue, 17 Sep 2024 17:20:41 -0700 Subject: [PATCH 47/58] Update "open in colab" buttons in every tutorial --- .../data_validation/tfdv_basic.ipynb | 48 ++++++++++++---- docs/tutorials/mlmd/mlmd_tutorial.ipynb | 50 ++++++++++++----- .../tutorials/model_analysis/tfma_basic.ipynb | 49 +++++++++++----- docs/tutorials/serving/rest_simple.ipynb | 50 ++++++++++++----- docs/tutorials/tfx/components.ipynb | 49 +++++++++++----- docs/tutorials/tfx/components_keras.ipynb | 49 +++++++++++----- .../tfx/gcp/vertex_pipelines_bq.ipynb | 47 ++++++++++++---- .../tfx/gcp/vertex_pipelines_simple.ipynb | 47 ++++++++++++---- .../vertex_pipelines_vertex_training.ipynb | 47 ++++++++++++---- .../tfx/gpt2_finetuning_and_conversion.ipynb | 54 ++++++++++++------ .../tfx/neural_structured_learning.ipynb | 56 ++++++++++++------- docs/tutorials/tfx/penguin_template.ipynb | 49 +++++++++++----- docs/tutorials/tfx/penguin_tfdv.ipynb | 48 ++++++++++++---- docs/tutorials/tfx/penguin_tfma.ipynb | 48 ++++++++++++---- docs/tutorials/tfx/penguin_tft.ipynb | 48 ++++++++++++---- .../tfx/python_function_component.ipynb | 50 ++++++++++++----- docs/tutorials/tfx/recommenders.ipynb | 50 ++++++++++++----- docs/tutorials/tfx/template.ipynb | 49 +++++++++++----- docs/tutorials/tfx/template_local.ipynb | 46 +++++++++++---- docs/tutorials/transform/census.ipynb | 47 ++++++++++++---- docs/tutorials/transform/simple.ipynb | 49 +++++++++++----- 21 files changed, 756 insertions(+), 274 deletions(-) diff --git a/docs/tutorials/data_validation/tfdv_basic.ipynb b/docs/tutorials/data_validation/tfdv_basic.ipynb index f8e44389a0..6b412fc3c8 100644 --- a/docs/tutorials/data_validation/tfdv_basic.ipynb +++ b/docs/tutorials/data_validation/tfdv_basic.ipynb @@ -46,18 +46,42 @@ "id": "rLsMb4vqY244" }, "source": [ - "Note: You can run this example right now in a Jupyter-style notebook, no setup required! Just click \"Run in Google Colab\"\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/data_validation/tfdv_basic\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.sandbox.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/data_validation/tfdv_basic.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/blob/master/docs/tutorials/data_validation/tfdv_basic.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/data_validation/tfdv_basic.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/mlmd/mlmd_tutorial.ipynb b/docs/tutorials/mlmd/mlmd_tutorial.ipynb index debf5b3ba0..92afb2eb75 100644 --- a/docs/tutorials/mlmd/mlmd_tutorial.ipynb +++ b/docs/tutorials/mlmd/mlmd_tutorial.ipynb @@ -50,20 +50,42 @@ "id": "MfBg1C5NB3X0" }, "source": [ - "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - " \u003ctd\u003e\n", - " \u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/mlmd/mlmd_tutorial\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\n", - " \u003c/td\u003e\n", - " \u003ctd\u003e\n", - " \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/mlmd/mlmd_tutorial.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n", - " \u003c/td\u003e\n", - " \u003ctd\u003e\n", - " \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/blob/master/docs/tutorials/mlmd/mlmd_tutorial.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/mlmd/mlmd_tutorial.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - " \u003c/td\u003e\n", - "\u003c/table\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/model_analysis/tfma_basic.ipynb b/docs/tutorials/model_analysis/tfma_basic.ipynb index 367ee9a6da..d22d3b0604 100644 --- a/docs/tutorials/model_analysis/tfma_basic.ipynb +++ b/docs/tutorials/model_analysis/tfma_basic.ipynb @@ -37,19 +37,42 @@ "id": "rLsMb4vqY244" }, "source": [ - "Note: You can run this example right now in a Jupyter-style notebook, no setup required! Just click \"Run in Google Colab\"\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/model_analysis/tfma_basic\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.sandbox.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/model_analysis/tfma_basic.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/blob/master/docs/tutorials/model_analysis/tfma_basic.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/model_analysis/tfma_basic.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/serving/rest_simple.ipynb b/docs/tutorials/serving/rest_simple.ipynb index 1756f1a2c5..a3c25bbf9e 100644 --- a/docs/tutorials/serving/rest_simple.ipynb +++ b/docs/tutorials/serving/rest_simple.ipynb @@ -46,20 +46,42 @@ "id": "E6FwTNtl3S4v" }, "source": [ - "**Warning: This notebook is designed to be run in a Google Colab only**. It installs packages on the system and requires root access. If you want to run it in a local Jupyter notebook, please proceed with caution.\n", - "\n", - "Note: You can run this example right now in a Jupyter-style notebook, no setup required! Just click \"Run in Google Colab\"\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctr\u003e\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/serving/rest_simple\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/serving/rest_simple.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/blob/master/docs/tutorials/serving/rest_simple.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/serving/rest_simple.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/tr\u003e\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/components.ipynb b/docs/tutorials/tfx/components.ipynb index f32fceb8cf..6a4d5de23d 100644 --- a/docs/tutorials/tfx/components.ipynb +++ b/docs/tutorials/tfx/components.ipynb @@ -48,19 +48,42 @@ "id": "LidV2qsXm4XC" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/components\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/components.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/components.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/components.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/components_keras.ipynb b/docs/tutorials/tfx/components_keras.ipynb index adf7461994..c87885db12 100644 --- a/docs/tutorials/tfx/components_keras.ipynb +++ b/docs/tutorials/tfx/components_keras.ipynb @@ -48,19 +48,42 @@ "id": "LidV2qsXm4XC" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/components_keras\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/components_keras.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/components_keras.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/components_keras.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb b/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb index c864e1ee40..4ec012ff0c 100644 --- a/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb +++ b/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb @@ -45,17 +45,42 @@ "id": "_445qeKq8e3-" }, "source": [ - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_bq\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?q=download_url%3Dhttps%253A%252F%252Fraw.githubusercontent.com%252Ftensorflow%252Ftfx%252Fmaster%252Fdocs%252Ftutorials%252Ftfx%252Fgcp%252Fvertex_pipelines_bq.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eRun in Google Cloud Vertex AI Workbench\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e\n" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb b/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb index 3a4d4824af..d7e299e8c6 100644 --- a/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb +++ b/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb @@ -45,17 +45,42 @@ "id": "_445qeKq8e3-" }, "source": [ - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?q=download_url%3Dhttps%253A%252F%252Fraw.githubusercontent.com%252Ftensorflow%252Ftfx%252Fmaster%252Fdocs%252Ftutorials%252Ftfx%252Fgcp%252Fvertex_pipelines_simple.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eRun in Google Cloud Vertex AI Workbench\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e\n" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb b/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb index ee7c821ea0..6c6975d936 100644 --- a/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb +++ b/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb @@ -45,17 +45,42 @@ "id": "_445qeKq8e3-" }, "source": [ - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_vertex_training\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?q=download_url%3Dhttps%253A%252F%252Fraw.githubusercontent.com%252Ftensorflow%252Ftfx%252Fmaster%252Fdocs%252Ftutorials%252Ftfx%252Fgcp%252Fvertex_pipelines_vertex_training.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eRun in Google Cloud Vertex AI Workbench\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e\n" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb b/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb index 35f8af7b4e..84d9b30dc8 100644 --- a/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb +++ b/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb @@ -64,24 +64,42 @@ "id": "uf3QpfdiIl7O" }, "source": [ - "# TFX Pipeline for Fine-Tuning a Large Language Model (LLM)\n", - "\n", - "\n", - "This codelab demonstrates how to leverage the power of Keras 3, KerasNLP and TFX pipelines to fine-tune a pre-trained GPT-2 model on the IMDb movie reviews dataset. The dataset that is used in this demo is [IMDB Reviews dataset](https://www.tensorflow.org/datasets/catalog/imdb_reviews).\n", - "\n", - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/gpt2_finetuning_and_conversion\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e\n", - "\n" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/neural_structured_learning.ipynb b/docs/tutorials/tfx/neural_structured_learning.ipynb index 1ba25acf08..6011f258c3 100644 --- a/docs/tutorials/tfx/neural_structured_learning.ipynb +++ b/docs/tutorials/tfx/neural_structured_learning.ipynb @@ -50,26 +50,42 @@ "id": "vyAF26z9IDoq" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - " \u003ctd\u003e\n", - " \u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/neural_structured_learning\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\n", - " \u003c/td\u003e\n", - " \u003ctd\u003e\n", - " \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/neural_structured_learning.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n", - " \u003c/td\u003e\n", - " \u003ctd\u003e\n", - " \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/neural_structured_learning.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView on GitHub\u003c/a\u003e\n", - " \u003c/td\u003e\n", - " \u003ctd\u003e\n", - " \u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/neural_structured_learning.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\n", - " \u003c/td\u003e\n", - " \u003ctd\u003e\n", - " \u003ca href=\"https://tfhub.dev/google/tf2-preview/gnews-swivel-20dim/1\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/hub_logo_32px.png\" /\u003eSee TF Hub model\u003c/a\u003e\n", - " \u003c/td\u003e\n", - "\u003c/table\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/penguin_template.ipynb b/docs/tutorials/tfx/penguin_template.ipynb index 326f0c0802..dc48bc6906 100644 --- a/docs/tutorials/tfx/penguin_template.ipynb +++ b/docs/tutorials/tfx/penguin_template.ipynb @@ -48,19 +48,42 @@ "id": "ZQmvgl9nsqPW" }, "source": [ - "Note: We recommend running this tutorial on Google Cloud [Vertex AI Workbench](https://cloud.google.com/vertex-ai-workbench). [Go to Vertex AI Workbench](https://console.cloud.google.com/vertex-ai/workbench).\n", - "\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/penguin_template\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/penguin_template.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/penguin_template.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/penguin_template.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/penguin_tfdv.ipynb b/docs/tutorials/tfx/penguin_tfdv.ipynb index 224d22d42b..5c72437570 100644 --- a/docs/tutorials/tfx/penguin_tfdv.ipynb +++ b/docs/tutorials/tfx/penguin_tfdv.ipynb @@ -45,18 +45,42 @@ "id": "HU9YYythm0dx" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/penguin_tfdv\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/penguin_tfdv.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/penguin_tfdv.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/penguin_tfdv.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/penguin_tfma.ipynb b/docs/tutorials/tfx/penguin_tfma.ipynb index ca2e3f3465..4476713ef9 100644 --- a/docs/tutorials/tfx/penguin_tfma.ipynb +++ b/docs/tutorials/tfx/penguin_tfma.ipynb @@ -62,18 +62,42 @@ "id": "HU9YYythm0dx" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/penguin_tfma\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/penguin_tfma.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/penguin_tfma.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/penguin_tfma.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/penguin_tft.ipynb b/docs/tutorials/tfx/penguin_tft.ipynb index f638a049d0..da6f38f507 100644 --- a/docs/tutorials/tfx/penguin_tft.ipynb +++ b/docs/tutorials/tfx/penguin_tft.ipynb @@ -47,18 +47,42 @@ "id": "HU9YYythm0dx" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/penguin_tft\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/penguin_tft.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/penguin_tft.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/penguin_tft.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/python_function_component.ipynb b/docs/tutorials/tfx/python_function_component.ipynb index 463125d0ab..46625b11b3 100644 --- a/docs/tutorials/tfx/python_function_component.ipynb +++ b/docs/tutorials/tfx/python_function_component.ipynb @@ -75,20 +75,42 @@ "id": "WdRDkO2wQHUw" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup\n", - "required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/python_function_component\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/python_function_component.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/python_function_component.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/python_function_component.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/recommenders.ipynb b/docs/tutorials/tfx/recommenders.ipynb index 2acb59b449..b77ae2f672 100644 --- a/docs/tutorials/tfx/recommenders.ipynb +++ b/docs/tutorials/tfx/recommenders.ipynb @@ -46,20 +46,42 @@ "id": "Z17OmgavQfp4" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup\n", - "required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/recommenders\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/recommenders.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/recommenders.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/recommenders.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/tfx/template.ipynb b/docs/tutorials/tfx/template.ipynb index 64f2daacd5..eba3e8f42c 100644 --- a/docs/tutorials/tfx/template.ipynb +++ b/docs/tutorials/tfx/template.ipynb @@ -45,19 +45,42 @@ "id": "wD2KOXlZuAOj" }, "source": [ - "Note: We recommend running this tutorial on Google Cloud Vertex AI Workbench. [Launch this notebook on Vertex AI Workbench](https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?q=download_url%3Dhttps%253A%252F%252Fraw.githubusercontent.com%252Ftensorflow%252Ftfx%252Fmaster%252Fdocs%252Ftutorials%252Ftfx%252Ftemplate.ipynb).\n", - "\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/template\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/template.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/template.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/template.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "metadata": { diff --git a/docs/tutorials/tfx/template_local.ipynb b/docs/tutorials/tfx/template_local.ipynb index 01c030212c..9ee604c9ec 100644 --- a/docs/tutorials/tfx/template_local.ipynb +++ b/docs/tutorials/tfx/template_local.ipynb @@ -45,16 +45,42 @@ "id": "XdSXv1DrxdLL" }, "source": [ - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/tfx/template_local\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"/\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/tfx/template_local.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/tree/master/docs/tutorials/tfx/template_local.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/tfx/template_local.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "metadata": { diff --git a/docs/tutorials/transform/census.ipynb b/docs/tutorials/transform/census.ipynb index 5e2ac99985..f90dcc944f 100644 --- a/docs/tutorials/transform/census.ipynb +++ b/docs/tutorials/transform/census.ipynb @@ -6,17 +6,42 @@ "id": "uAttKaKmT435" }, "source": [ - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/transform/census\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.sandbox.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/transform/census.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/blob/master/docs/tutorials/transform/census.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/transform/census.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", diff --git a/docs/tutorials/transform/simple.ipynb b/docs/tutorials/transform/simple.ipynb index 70e9f6963d..e49ca7f86b 100644 --- a/docs/tutorials/transform/simple.ipynb +++ b/docs/tutorials/transform/simple.ipynb @@ -47,19 +47,42 @@ "id": "S5ST8dI25wbA" }, "source": [ - "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", - "\n", - "\u003cdiv class=\"devsite-table-wrapper\"\u003e\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tfx/tutorials/transform/simple\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tfx/blob/master/docs/tutorials/transform/simple.ipynb\"\u003e\n", - "\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\"\u003eRun in Google Colab\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tfx/blob/master/docs/tutorials/transform/simple.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\"\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\n", - "\u003ctd\u003e\u003ca target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/tfx/docs/tutorials/transform/simple.ipynb\"\u003e\n", - "\u003cimg width=32px src=\"https://www.tensorflow.org/images/download_logo_32px.png\"\u003eDownload notebook\u003c/a\u003e\u003c/td\u003e\n", - "\u003c/table\u003e\u003c/div\u003e" - ] + "Note: We recommend running this tutorial in a Colab notebook, with no setup required! Just click \"Run in Google Colab\".\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + " View on TensorFlow.org\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Run in Google Colab\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " View source on GitHub\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + " Download notebook\n", + "
\n", + "
\n", + "
" + ] }, { "cell_type": "markdown", From b5a701559c9b5b8cbdca720c5b43e862eb4096d9 Mon Sep 17 00:00:00 2001 From: pdmurray Date: Tue, 17 Sep 2024 17:28:50 -0700 Subject: [PATCH 48/58] Remove TF logo --- mkdocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 034c81399f..ee70643be3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,7 +30,6 @@ theme: toggle: icon: material/brightness-4 name: Switch to system preference - logo: assets/tf_full_color_primary_icon.svg favicon: assets/tf_full_color_primary_icon.svg features: From 351fcd46158a7494519f2e9ff2d42edb01b45698 Mon Sep 17 00:00:00 2001 From: pdmurray Date: Tue, 17 Sep 2024 17:31:27 -0700 Subject: [PATCH 49/58] Add "edit on github" button --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index ee70643be3..f111d6f7cd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,6 +35,7 @@ theme: features: - content.code.copy - content.code.select + - content.action.edit plugins: - search - autorefs From cec3ffac067eeb7c8b7fc89c334974a7e31875fa Mon Sep 17 00:00:00 2001 From: pdmurray Date: Tue, 17 Sep 2024 17:35:00 -0700 Subject: [PATCH 50/58] Fix fairness indicators link --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index f111d6f7cd..0de5c9f376 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -149,7 +149,7 @@ nav: - Data preprocessing for ML with Google Cloud: tutorials/transform/data_preprocessing_with_cloud - Model Analysis: - Get started with TFMA: tutorials/model_analysis/tfma_basic - - Fairness Indicators tutorial: onsible_ai/fairness_indicators/tutorials/Fairness_Indicators_Example_Colab + - Fairness Indicators tutorial: https://www.tensorflow.org/responsible_ai/fairness_indicators/tutorials/Fairness_Indicators_Example_Colab - Deploy a trained model: - 'Servers: TFX for TensorFlow Serving': tutorials/serving/rest_simple - 'Mobile & IoT: TFX for TensorFlow Lite': tutorials/tfx/tfx_for_mobile From 53a34f226191ca79fe723550e04f78a01800351a Mon Sep 17 00:00:00 2001 From: pdmurray Date: Tue, 17 Sep 2024 18:15:03 -0700 Subject: [PATCH 51/58] Update guide and tutorials with github-hosted docs links --- docs/guide/fairness_indicators.md | 2 +- docs/guide/index.md | 4 ++-- docs/guide/tft_bestpractices.md | 2 +- docs/tutorials/mlmd/mlmd_tutorial.ipynb | 4 ++-- docs/tutorials/tfx/cloud-ai-platform-pipelines.md | 4 ++-- docs/tutorials/tfx/components.ipynb | 4 ++-- docs/tutorials/tfx/components_keras.ipynb | 4 ++-- docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb | 10 +++++----- .../tfx/gcp/vertex_pipelines_simple.ipynb | 10 +++++----- .../tfx/gcp/vertex_pipelines_vertex_training.ipynb | 14 +++++++------- .../tfx/gpt2_finetuning_and_conversion.ipynb | 2 +- docs/tutorials/tfx/penguin_simple.ipynb | 4 ++-- docs/tutorials/tfx/penguin_template.ipynb | 6 +++--- docs/tutorials/tfx/penguin_tfdv.ipynb | 10 +++++----- docs/tutorials/tfx/penguin_tfma.ipynb | 8 ++++---- docs/tutorials/tfx/penguin_tft.ipynb | 6 +++--- docs/tutorials/tfx/python_function_component.ipynb | 2 +- docs/tutorials/tfx/template.ipynb | 2 +- docs/tutorials/tfx/template_local.ipynb | 4 ++-- docs/tutorials/tfx/tfx_for_mobile.md | 2 +- 20 files changed, 52 insertions(+), 52 deletions(-) diff --git a/docs/guide/fairness_indicators.md b/docs/guide/fairness_indicators.md index 88192873ae..b316a66467 100644 --- a/docs/guide/fairness_indicators.md +++ b/docs/guide/fairness_indicators.md @@ -308,7 +308,7 @@ contains several examples: * [Fairness_Indicators_Example_Colab.ipynb](https://github.com/tensorflow/fairness-indicators/blob/master/g3doc/tutorials/Fairness_Indicators_Example_Colab.ipynb) gives an overview of Fairness Indicators in - [TensorFlow Model Analysis](https://www.tensorflow.org/tfx/guide/tfma) and + [TensorFlow Model Analysis](./tfma) and how to use it with a real dataset. This notebook also goes over [TensorFlow Data Validation](https://www.tensorflow.org/tfx/data_validation/get_started) and [What-If Tool](https://pair-code.github.io/what-if-tool/), two tools for diff --git a/docs/guide/index.md b/docs/guide/index.md index 65d86b3f30..9f41cc3f57 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -28,7 +28,7 @@ pip install tfx !!! Note See the - [TensorFlow Serving](https://www.tensorflow.org/tfx/guide/serving), + [TensorFlow Serving](./serving), [TensorFlow JS](https://js.tensorflow.org/), and/or [TensorFlow Lite](https://www.tensorflow.org/lite) documentation for installing those optional components. @@ -351,7 +351,7 @@ is consumed by the other components. TFX provides a powerful platform for every phase of a machine learning project, from research, experimentation, and development on your local machine, through deployment. In order to avoid code duplication and eliminate the potential for -[training/serving skew](https://www.tensorflow.org/tfx/guide/tfdv#training-serving_skew_detection) +[training/serving skew](./tfdv#training-serving_skew_detection) it is strongly recommended to implement your TFX pipeline for both model training and deployment of trained models, and use [Transform](transform.md) components which leverage the [TensorFlow Transform](tft.md) library for both diff --git a/docs/guide/tft_bestpractices.md b/docs/guide/tft_bestpractices.md index 4bf25d74c8..8288f8d072 100644 --- a/docs/guide/tft_bestpractices.md +++ b/docs/guide/tft_bestpractices.md @@ -720,5 +720,5 @@ columns. - Learn about best practices for ML engineering in [Rules of ML](https://developers.google.com/machine-learning/guides/rules-of-ml/){: .external }. + For more reference architectures, diagrams, and best practices, explore the - TFX + TFX Cloud Solutions. diff --git a/docs/tutorials/mlmd/mlmd_tutorial.ipynb b/docs/tutorials/mlmd/mlmd_tutorial.ipynb index 92afb2eb75..73027a6cb8 100644 --- a/docs/tutorials/mlmd/mlmd_tutorial.ipynb +++ b/docs/tutorials/mlmd/mlmd_tutorial.ipynb @@ -118,7 +118,7 @@ "source": [ "## TFX Pipelines in Colab\n", "\n", - "Colab is a lightweight development environment which differs significantly from a production environment. In production, you may have various pipeline components like data ingestion, transformation, model training, run histories, etc. across multiple, distributed systems. For this tutorial, you should be aware that siginificant differences exist in Orchestration and Metadata storage - it is all handled locally within Colab. Learn more about TFX in Colab [here](https://www.tensorflow.org/tfx/tutorials/tfx/components_keras#background).\n", + "Colab is a lightweight development environment which differs significantly from a production environment. In production, you may have various pipeline components like data ingestion, transformation, model training, run histories, etc. across multiple, distributed systems. For this tutorial, you should be aware that siginificant differences exist in Orchestration and Metadata storage - it is all handled locally within Colab. Learn more about TFX in Colab [here](/tutorials/tfx/components_keras#background).\n", "\n" ] }, @@ -302,7 +302,7 @@ "\n", "A TFX pipeline consists of several components that perform different aspects of the ML workflow. In this notebook, you create and run the `ExampleGen`, `StatisticsGen`, `SchemaGen`, and `Trainer` components and use the `Evaluator` and `Pusher` component to evaluate and push the trained model. \n", "\n", - "Refer to the [components tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/components_keras) for more information on TFX pipeline components." + "Refer to the [components tutorial](/tutorials/tfx/components_keras) for more information on TFX pipeline components." ] }, { diff --git a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md index 3bd7b37167..eaa60c7f77 100644 --- a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md +++ b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md @@ -457,7 +457,7 @@ your pipeline. The example presented here is really only meant to get you started. For a more advanced example see the -[TensorFlow Data Validation Colab](https://www.tensorflow.org/tfx/tutorials/data_validation/chicago_taxi). +[TensorFlow Data Validation Colab](/tutorials/data_validation/chicago_taxi). For more information on using TFDV to explore and validate a dataset, [see the examples on tensorflow.org](https://www.tensorflow.org/tfx/data_validation). @@ -515,7 +515,7 @@ your pipeline. The example presented here is really only meant to get you started. For a more advanced example see the -[TensorFlow Transform Colab](https://www.tensorflow.org/tfx/tutorials/transform/census). +[TensorFlow Transform Colab](/tutorials/transform/census). ## 10. Training diff --git a/docs/tutorials/tfx/components.ipynb b/docs/tutorials/tfx/components.ipynb index 6a4d5de23d..49959bc8a8 100644 --- a/docs/tutorials/tfx/components.ipynb +++ b/docs/tutorials/tfx/components.ipynb @@ -656,7 +656,7 @@ "\n", "`Transform` will take as input the data from `ExampleGen`, the schema from `SchemaGen`, as well as a module that contains user-defined Transform code.\n", "\n", - "Let's see an example of user-defined Transform code below (for an introduction to the TensorFlow Transform APIs, [see the tutorial](https://www.tensorflow.org/tfx/tutorials/transform/simple)). First, we define a few constants for feature engineering:\n", + "Let's see an example of user-defined Transform code below (for an introduction to the TensorFlow Transform APIs, [see the tutorial](/tutorials/transform/simple)). First, we define a few constants for feature engineering:\n", "\n", "Note: The `%%writefile` cell magic will save the contents of the cell as a `.py` file on disk. This allows the `Transform` component to load your code as a module.\n", "\n" @@ -1430,7 +1430,7 @@ "source": [ "This visualization shows the same metrics, but computed at every feature value of `trip_start_hour` instead of on the entire evaluation set.\n", "\n", - "TensorFlow Model Analysis supports many other visualizations, such as Fairness Indicators and plotting a time series of model performance. To learn more, see [the tutorial](https://www.tensorflow.org/tfx/tutorials/model_analysis/tfma_basic)." + "TensorFlow Model Analysis supports many other visualizations, such as Fairness Indicators and plotting a time series of model performance. To learn more, see [the tutorial](/tutorials/model_analysis/tfma_basic)." ] }, { diff --git a/docs/tutorials/tfx/components_keras.ipynb b/docs/tutorials/tfx/components_keras.ipynb index c87885db12..37d3843ae1 100644 --- a/docs/tutorials/tfx/components_keras.ipynb +++ b/docs/tutorials/tfx/components_keras.ipynb @@ -648,7 +648,7 @@ "\n", "`Transform` will take as input the data from `ExampleGen`, the schema from `SchemaGen`, as well as a module that contains user-defined Transform code.\n", "\n", - "Let's see an example of user-defined Transform code below (for an introduction to the TensorFlow Transform APIs, [see the tutorial](https://www.tensorflow.org/tfx/tutorials/transform/simple)). First, we define a few constants for feature engineering:\n", + "Let's see an example of user-defined Transform code below (for an introduction to the TensorFlow Transform APIs, [see the tutorial](/tutorials/transform/simple)). First, we define a few constants for feature engineering:\n", "\n", "Note: The `%%writefile` cell magic will save the contents of the cell as a `.py` file on disk. This allows the `Transform` component to load your code as a module.\n", "\n" @@ -1455,7 +1455,7 @@ "source": [ "This visualization shows the same metrics, but computed at every feature value of `trip_start_hour` instead of on the entire evaluation set.\n", "\n", - "TensorFlow Model Analysis supports many other visualizations, such as Fairness Indicators and plotting a time series of model performance. To learn more, see [the tutorial](https://www.tensorflow.org/tfx/tutorials/model_analysis/tfma_basic)." + "TensorFlow Model Analysis supports many other visualizations, such as Fairness Indicators and plotting a time series of model performance. To learn more, see [the tutorial](/tutorials/model_analysis/tfma_basic)." ] }, { diff --git a/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb b/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb index 4ec012ff0c..bc35bdb777 100644 --- a/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb +++ b/docs/tutorials/tfx/gcp/vertex_pipelines_bq.ipynb @@ -94,7 +94,7 @@ "Google Cloud Vertex Pipelines.\n", "\n", "This notebook is based on the TFX pipeline we built in\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple).\n", + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple).\n", "If you have not read that tutorial yet, you should read it before proceeding\n", "with this notebook.\n", "\n", @@ -123,7 +123,7 @@ "\n", "## Set up\n", "If you have completed\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple),\n", + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple),\n", "you will have a working GCP project and a GCS bucket and that is all we need\n", "for this tutorial. Please read the preliminary tutorial first if you missed it." ] @@ -397,7 +397,7 @@ "## Create a pipeline\n", "\n", "TFX pipelines are defined using Python APIs as we did in\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple).\n", + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple).\n", "We previously used `CsvExampleGen` which reads data from a CSV file. In this\n", "tutorial, we will use\n", "[`BigQueryExampleGen`](https://www.tensorflow.org/tfx/api_docs/python/tfx/v1/extensions/google_cloud_big_query/BigQueryExampleGen)\n", @@ -473,7 +473,7 @@ "### Write model code.\n", "\n", "We will use the same model code as in the\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple)." + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple)." ] }, { @@ -712,7 +712,7 @@ "## Run the pipeline on Vertex Pipelines.\n", "\n", "We will use Vertex Pipelines to run the pipeline as we did in\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple).\n" + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple).\n" ] }, { diff --git a/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb b/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb index d7e299e8c6..3c63483712 100644 --- a/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb +++ b/docs/tutorials/tfx/gcp/vertex_pipelines_simple.ipynb @@ -91,7 +91,7 @@ "This notebook-based tutorial will create a simple TFX pipeline and run it using\n", "Google Cloud Vertex Pipelines. This notebook is based on the TFX pipeline\n", "we built in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "If you are not familiar with TFX and you have not read that tutorial yet, you\n", "should read it before proceeding with this notebook.\n", "\n", @@ -361,7 +361,7 @@ "We will use the same\n", "[Palmer Penguins dataset](https://allisonhorst.github.io/palmerpenguins/articles/intro.html)\n", "as\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "\n", "There are four numeric features in this dataset which were already normalized\n", "to have range [0,1]. We will build a classification model which predicts the\n", @@ -421,7 +421,7 @@ "TFX pipelines are defined using Python APIs. We will define a pipeline which\n", "consists of three components, CsvExampleGen, Trainer and Pusher. The pipeline\n", "and model definition is almost the same as\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "\n", "The only difference is that we don't need to set `metadata_connection_config`\n", "which is used to locate\n", @@ -442,7 +442,7 @@ "### Write model code.\n", "\n", "We will use the same model code as in the\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple)." + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple)." ] }, { @@ -675,7 +675,7 @@ "## Run the pipeline on Vertex Pipelines.\n", "\n", "We used `LocalDagRunner` which runs on local environment in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "TFX provides multiple orchestrators to run your pipeline. In this tutorial we\n", "will use the Vertex Pipelines together with the Kubeflow V2 dag runner." ] diff --git a/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb b/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb index 6c6975d936..9773b9f317 100644 --- a/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb +++ b/docs/tutorials/tfx/gcp/vertex_pipelines_vertex_training.ipynb @@ -92,7 +92,7 @@ "ML model using Vertex AI Training service and publishes it to Vertex AI for serving.\n", "\n", "This notebook is based on the TFX pipeline we built in\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple).\n", + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple).\n", "If you have not read that tutorial yet, you should read it before proceeding\n", "with this notebook.\n", "\n", @@ -123,7 +123,7 @@ "\n", "## Set up\n", "If you have completed\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple),\n", + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple),\n", "you will have a working GCP project and a GCS bucket and that is all we need\n", "for this tutorial. Please read the preliminary tutorial first if you missed it." ] @@ -358,7 +358,7 @@ "We will use the same\n", "[Palmer Penguins dataset](https://allisonhorst.github.io/palmerpenguins/articles/intro.html)\n", "as\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "\n", "There are four numeric features in this dataset which were already normalized\n", "to have range [0,1]. We will build a classification model which predicts the\n", @@ -416,7 +416,7 @@ "## Create a pipeline\n", "\n", "Our pipeline will be very similar to the pipeline we created in\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple).\n", + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple).\n", "The pipeline will consists of three components, CsvExampleGen, Trainer and\n", "Pusher. But we will use a special Trainer and Pusher component. The Trainer component will move\n", "training workloads to Vertex AI, and the Pusher component will publish the\n", @@ -446,7 +446,7 @@ "### Write model code.\n", "\n", "The model itself is almost similar to the model in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "\n", "We will add `_get_distribution_strategy()` function which creates a\n", "[TensorFlow distribution strategy](https://www.tensorflow.org/guide/distributed_training)\n", @@ -641,7 +641,7 @@ "\n", "We will define a function to create a TFX pipeline. It has the same three\n", "Components as in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple),\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple),\n", "but we use a `Trainer` and `Pusher` component in the GCP extension module.\n", "\n", "`tfx.extensions.google_cloud_ai_platform.Trainer` behaves like a regular\n", @@ -770,7 +770,7 @@ "## Run the pipeline on Vertex Pipelines.\n", "\n", "We will use Vertex Pipelines to run the pipeline as we did in\n", - "[Simple TFX Pipeline for Vertex Pipelines Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/gcp/vertex_pipelines_simple)." + "[Simple TFX Pipeline for Vertex Pipelines Tutorial](/tutorials/tfx/gcp/vertex_pipelines_simple)." ] }, { diff --git a/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb b/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb index 84d9b30dc8..688268512f 100644 --- a/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb +++ b/docs/tutorials/tfx/gpt2_finetuning_and_conversion.ipynb @@ -1401,7 +1401,7 @@ "source": [ "TFX supports multiple orchestrators to run pipelines. In this tutorial we will use LocalDagRunner which is included in the TFX Python package and runs pipelines on local environment. We often call TFX pipelines \"DAGs\" which stands for directed acyclic graph.\n", "\n", - "LocalDagRunner provides fast iterations for development and debugging. TFX also supports other orchestrators including Kubeflow Pipelines and Apache Airflow which are suitable for production use cases. See [TFX on Cloud AI Platform Pipelines](https://www.tensorflow.org/tfx/tutorials/tfx/cloud-ai-platform-pipelines) or [TFX Airflow](https://www.tensorflow.org/tfx/tutorials/tfx/airflow_workshop) Tutorial to learn more about other orchestration systems.\n", + "LocalDagRunner provides fast iterations for development and debugging. TFX also supports other orchestrators including Kubeflow Pipelines and Apache Airflow which are suitable for production use cases. See [TFX on Cloud AI Platform Pipelines](/tutorials/tfx/cloud-ai-platform-pipelines) or [TFX Airflow](/tutorials/tfx/airflow_workshop) Tutorial to learn more about other orchestration systems.\n", "\n", "Now we create a LocalDagRunner and pass a Pipeline object created from the function we already defined. The pipeline runs directly and you can see logs for the progress of the pipeline including ML model training." ] diff --git a/docs/tutorials/tfx/penguin_simple.ipynb b/docs/tutorials/tfx/penguin_simple.ipynb index 7783bb3fce..a9339e295d 100644 --- a/docs/tutorials/tfx/penguin_simple.ipynb +++ b/docs/tutorials/tfx/penguin_simple.ipynb @@ -573,9 +573,9 @@ "Airflow which are suitable for production use cases.\n", "\n", "See\n", - "[TFX on Cloud AI Platform Pipelines](https://www.tensorflow.org/tfx/tutorials/tfx/cloud-ai-platform-pipelines)\n", + "[TFX on Cloud AI Platform Pipelines](/tutorials/tfx/cloud-ai-platform-pipelines)\n", "or\n", - "[TFX Airflow Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/airflow_workshop)\n", + "[TFX Airflow Tutorial](/tutorials/tfx/airflow_workshop)\n", "to learn more about other orchestration systems." ] }, diff --git a/docs/tutorials/tfx/penguin_template.ipynb b/docs/tutorials/tfx/penguin_template.ipynb index dc48bc6906..4d343e35cc 100644 --- a/docs/tutorials/tfx/penguin_template.ipynb +++ b/docs/tutorials/tfx/penguin_template.ipynb @@ -724,7 +724,7 @@ "\n", "In this tutorial, we will use visualzation helper methods in TFX which use TFDV\n", "internally to show the visualization. Please see\n", - "[TFX components tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/components_keras)\n", + "[TFX components tutorial](/tutorials/tfx/components_keras)\n", "to learn more about each component." ] }, @@ -1353,7 +1353,7 @@ "source": [ "You also need a Kubeflow Pipelines cluster to run the pipeline. Please\n", "follow Step 1 and 2 in\n", - "[TFX on Cloud AI Platform Pipelines tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/cloud-ai-platform-pipelines).\n", + "[TFX on Cloud AI Platform Pipelines tutorial](/tutorials/tfx/cloud-ai-platform-pipelines).\n", "\n", "When your cluster is ready, open the pipeline dashboard by clicking\n", "*Open Pipelines Dashboard* in the\n", @@ -1517,7 +1517,7 @@ "source": [ "If you are interested in running your pipeline on Kubeflow Pipelines,\n", "find more instructions in\n", - "[TFX on Cloud AI Platform Pipelines tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/cloud-ai-platform-pipelines)." + "[TFX on Cloud AI Platform Pipelines tutorial](/tutorials/tfx/cloud-ai-platform-pipelines)." ] }, { diff --git a/docs/tutorials/tfx/penguin_tfdv.ipynb b/docs/tutorials/tfx/penguin_tfdv.ipynb index 5c72437570..4a707b26d6 100644 --- a/docs/tutorials/tfx/penguin_tfdv.ipynb +++ b/docs/tutorials/tfx/penguin_tfdv.ipynb @@ -91,7 +91,7 @@ "In this notebook-based tutorial, we will create and run TFX pipelines\n", "to validate input data and create an ML model. This notebook is based on the\n", "TFX pipeline we built in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "If you have not read that tutorial yet, you should read it before proceeding\n", "with this notebook.\n", "\n", @@ -352,7 +352,7 @@ "be used for training and example validation in later tasks.\n", "\n", "In addition to `CsvExampleGen` which is used in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple),\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple),\n", "we will use `StatisticsGen` and `SchemaGen`:\n", "\n", "- [StatisticsGen](../../../guide/statsgen) calculates\n", @@ -361,7 +361,7 @@ "statistics and creates an initial data schema.\n", "\n", "See the guides for each component or\n", - "[TFX components tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/components_keras)\n", + "[TFX components tutorial](/tutorials/tfx/components_keras)\n", "to learn more on these components." ] }, @@ -724,7 +724,7 @@ "## Validate input examples and train an ML model\n", "\n", "We will go back to the pipeline that we created in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple),\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple),\n", "to train an ML model and use the generated schema for writing the model\n", "training code.\n", "\n", @@ -743,7 +743,7 @@ "### Write model training code\n", "\n", "We need to write the model code as we did in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "\n", "The model itself is the same as in the previous tutorial, but this time we will\n", "use the schema generated from the previous pipeline instead of specifying\n", diff --git a/docs/tutorials/tfx/penguin_tfma.ipynb b/docs/tutorials/tfx/penguin_tfma.ipynb index 4476713ef9..2ee9524917 100644 --- a/docs/tutorials/tfx/penguin_tfma.ipynb +++ b/docs/tutorials/tfx/penguin_tfma.ipynb @@ -108,7 +108,7 @@ "In this notebook-based tutorial, we will create and run a TFX pipeline\n", "which creates a simple classification model and analyzes its performance\n", "across multiple runs. This notebook is based on the TFX pipeline we built in\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "If you have not read that tutorial yet, you should read it before proceeding\n", "with this notebook.\n", "\n", @@ -308,7 +308,7 @@ "\n", "We will add an [`Evaluator`](../../../guide/evaluator)\n", "component to the pipeline we created in the\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple).\n", + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple).\n", "\n", "An Evaluator component requires input data from an `ExampleGen` component and\n", "a model from a `Trainer` component and a\n", @@ -332,7 +332,7 @@ "### Write model training code\n", "\n", "We will use the same model code as in the\n", - "[Simple TFX Pipeline Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_simple)." + "[Simple TFX Pipeline Tutorial](/tutorials/tfx/penguin_simple)." ] }, { @@ -827,7 +827,7 @@ "## Next steps\n", "\n", "Learn more on model analysis at\n", - "[TensorFlow Model Analysis library tutorial](https://www.tensorflow.org/tfx/tutorials/model_analysis/tfma_basic).\n", + "[TensorFlow Model Analysis library tutorial](/tutorials/model_analysis/tfma_basic).\n", "\n", "You can find more resources on https://www.tensorflow.org/tfx/tutorials.\n", "\n", diff --git a/docs/tutorials/tfx/penguin_tft.ipynb b/docs/tutorials/tfx/penguin_tft.ipynb index da6f38f507..0e979f4f49 100644 --- a/docs/tutorials/tfx/penguin_tft.ipynb +++ b/docs/tutorials/tfx/penguin_tft.ipynb @@ -93,7 +93,7 @@ "In this notebook-based tutorial, we will create and run a TFX pipeline\n", "to ingest raw input data and preprocess it appropriately for ML training.\n", "This notebook is based on the TFX pipeline we built in\n", - "[Data validation using TFX Pipeline and TensorFlow Data Validation Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_tfdv).\n", + "[Data validation using TFX Pipeline and TensorFlow Data Validation Tutorial](/tutorials/tfx/penguin_tfdv).\n", "If you have not read that one yet, you should read it before proceeding with\n", "this notebook.\n", "\n", @@ -346,7 +346,7 @@ "### Prepare a schema file\n", "\n", "As described in\n", - "[Data validation using TFX Pipeline and TensorFlow Data Validation Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_tfdv),\n", + "[Data validation using TFX Pipeline and TensorFlow Data Validation Tutorial](/tutorials/tfx/penguin_tfdv),\n", "we need a schema file for the dataset. Because the dataset is different from the previous tutorial we need to generate it again. In this tutorial, we will skip those steps and just use a prepared schema file.\n" ] }, @@ -390,7 +390,7 @@ "\n", "TFX pipelines are defined using Python APIs. We will add `Transform`\n", "component to the pipeline we created in the\n", - "[Data Validation tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/penguin_tfdv).\n", + "[Data Validation tutorial](/tutorials/tfx/penguin_tfdv).\n", "\n", "A Transform component requires input data from an `ExampleGen` component and\n", "a schema from a `SchemaGen` component, and produces a \"transform graph\". The\n", diff --git a/docs/tutorials/tfx/python_function_component.ipynb b/docs/tutorials/tfx/python_function_component.ipynb index 46625b11b3..639abbeec3 100644 --- a/docs/tutorials/tfx/python_function_component.ipynb +++ b/docs/tutorials/tfx/python_function_component.ipynb @@ -362,7 +362,7 @@ "InteractiveContext.\n", "\n", "For more information on what you can do with the TFX notebook\n", - "InteractiveContext, see the in-notebook [TFX Keras Component Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/components_keras)." + "InteractiveContext, see the in-notebook [TFX Keras Component Tutorial](/tutorials/tfx/components_keras)." ] }, { diff --git a/docs/tutorials/tfx/template.ipynb b/docs/tutorials/tfx/template.ipynb index eba3e8f42c..bf9592cbd4 100644 --- a/docs/tutorials/tfx/template.ipynb +++ b/docs/tutorials/tfx/template.ipynb @@ -339,7 +339,7 @@ "source": [ "## Step 3. Browse your copied source files\n", "\n", - "The TFX template provides basic scaffold files to build a pipeline, including Python source code, sample data, and Jupyter Notebooks to analyse the output of the pipeline. The `taxi` template uses the same *Chicago Taxi* dataset and ML model as the [Airflow Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/airflow_workshop).\n", + "The TFX template provides basic scaffold files to build a pipeline, including Python source code, sample data, and Jupyter Notebooks to analyse the output of the pipeline. The `taxi` template uses the same *Chicago Taxi* dataset and ML model as the [Airflow Tutorial](/tutorials/tfx/airflow_workshop).\n", "\n", "Here is brief introduction to each of the Python files.\n", "- `pipeline` - This directory contains the definition of the pipeline\n", diff --git a/docs/tutorials/tfx/template_local.ipynb b/docs/tutorials/tfx/template_local.ipynb index 9ee604c9ec..1263259c0e 100644 --- a/docs/tutorials/tfx/template_local.ipynb +++ b/docs/tutorials/tfx/template_local.ipynb @@ -109,7 +109,7 @@ "released by the City of Chicago. We strongly encourage you to try to build\n", "your own pipeline using your dataset by utilizing this pipeline as a baseline.\n", "\n", - "We will build a pipeline which runs on local environment. If you are interested in using Kubeflow orchestrator on Google Cloud, please see [TFX on Cloud AI Platform Pipelines tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/cloud-ai-platform-pipelines).\n", + "We will build a pipeline which runs on local environment. If you are interested in using Kubeflow orchestrator on Google Cloud, please see [TFX on Cloud AI Platform Pipelines tutorial](/tutorials/tfx/cloud-ai-platform-pipelines).\n", "\n", "## Prerequisites\n", "\n", @@ -318,7 +318,7 @@ "id": "QdiHik_w42xN" }, "source": [ - "The TFX template provides basic scaffold files to build a pipeline, including Python source code, sample data, and Jupyter Notebooks to analyse the output of the pipeline. The `taxi` template uses the same *Chicago Taxi* dataset and ML model as the [Airflow Tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/airflow_workshop).\n", + "The TFX template provides basic scaffold files to build a pipeline, including Python source code, sample data, and Jupyter Notebooks to analyse the output of the pipeline. The `taxi` template uses the same *Chicago Taxi* dataset and ML model as the [Airflow Tutorial](/tutorials/tfx/airflow_workshop).\n", "\n", "In Google Colab, you can browse files by clicking a folder icon on the left. Files should be copied under the project directoy, whose name is `my_pipeline` in this case. You can click directory names to see the content of the directory, and double-click file names to open them.\n", "\n", diff --git a/docs/tutorials/tfx/tfx_for_mobile.md b/docs/tutorials/tfx/tfx_for_mobile.md index e5823837fc..ec12a0575c 100644 --- a/docs/tutorials/tfx/tfx_for_mobile.md +++ b/docs/tutorials/tfx/tfx_for_mobile.md @@ -16,7 +16,7 @@ standard Keras-based [SavedModel](https://www.tensorflow.org/guide/saved_model) as well as the TFLite one, allowing users to compare the quality of the two. We assume you are familiar with TFX, our components, and our pipelines. If not, -then please see this [tutorial](https://www.tensorflow.org/tfx/tutorials/tfx/components). +then please see this [tutorial](/tutorials/tfx/components). ## Steps Only two steps are required to create and evaluate a TFLite model in TFX. The From d7c3bd17483e32a0d18946b48a050467edb262e6 Mon Sep 17 00:00:00 2001 From: pdmurray Date: Tue, 17 Sep 2024 18:41:11 -0700 Subject: [PATCH 52/58] Show all types for api.v1.utils --- docs/api/v1/utils.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/v1/utils.md b/docs/api/v1/utils.md index 349a42c01b..0b061e9d9b 100644 --- a/docs/api/v1/utils.md +++ b/docs/api/v1/utils.md @@ -1,3 +1,5 @@ # Utils ::: tfx.v1.utils + options: + show_if_no_docstring: true From 3f296ff12e8a2be8f2d1750d7c90b855aad8255e Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Tue, 17 Sep 2024 19:12:02 -0700 Subject: [PATCH 53/58] Show all members of `extensions`, `orchestration`, `testing`, `components`, and `dsl` in docs --- docs/api/v1/extensions.md | 2 ++ docs/api/v1/orchestration.md | 3 +++ docs/api/v1/testing.md | 2 ++ tfx/components/trainer/fn_args_utils.py | 2 +- tfx/components/tuner/component.py | 2 +- tfx/v1/dsl/standard_annotations.py | 27 +++++++++---------- tfx/v1/orchestration/experimental/__init__.py | 26 ++++++------------ tfx/v1/orchestration/metadata.py | 10 +++---- 8 files changed, 35 insertions(+), 39 deletions(-) diff --git a/docs/api/v1/extensions.md b/docs/api/v1/extensions.md index 2679aae75d..87b68d6713 100644 --- a/docs/api/v1/extensions.md +++ b/docs/api/v1/extensions.md @@ -1,3 +1,5 @@ # Extension ::: tfx.v1.extensions + options: + show_if_no_docstring: true diff --git a/docs/api/v1/orchestration.md b/docs/api/v1/orchestration.md index 26250ca1d9..7b336419c8 100644 --- a/docs/api/v1/orchestration.md +++ b/docs/api/v1/orchestration.md @@ -1,3 +1,6 @@ # Orchestration ::: tfx.v1.orchestration + options: + show_if_no_docstring: true + diff --git a/docs/api/v1/testing.md b/docs/api/v1/testing.md index 1369879c3a..f81aedc1ae 100644 --- a/docs/api/v1/testing.md +++ b/docs/api/v1/testing.md @@ -1,3 +1,5 @@ # Testing ::: tfx.v1.testing + options: + show_if_no_docstring: true diff --git a/tfx/components/trainer/fn_args_utils.py b/tfx/components/trainer/fn_args_utils.py index 613f84702e..30ad5fc8cd 100644 --- a/tfx/components/trainer/fn_args_utils.py +++ b/tfx/components/trainer/fn_args_utils.py @@ -48,7 +48,7 @@ Optional[schema_pb2.Schema], ], Iterator[pa.RecordBatch]]), ('data_view_decode_fn', Optional[Callable[[tf.Tensor], Dict[str, Any]]])]) -DataAccessor.__doc__ = """ +""" For accessing the data on disk. Contains factories that can create tf.data.Datasets or other means to access diff --git a/tfx/components/tuner/component.py b/tfx/components/tuner/component.py index 4db47c1cb2..87fe5ef3cf 100644 --- a/tfx/components/tuner/component.py +++ b/tfx/components/tuner/component.py @@ -33,7 +33,7 @@ # args depend on the tuner's implementation. TunerFnResult = NamedTuple('TunerFnResult', [('tuner', base_tuner.BaseTuner), ('fit_kwargs', Dict[str, Any])]) -TunerFnResult.__doc__ = """ +""" Return type of tuner_fn. tuner_fn returns a TunerFnResult that contains: diff --git a/tfx/v1/dsl/standard_annotations.py b/tfx/v1/dsl/standard_annotations.py index beb6c4de7f..36ace9ae18 100644 --- a/tfx/v1/dsl/standard_annotations.py +++ b/tfx/v1/dsl/standard_annotations.py @@ -13,21 +13,20 @@ # limitations under the License. """Public API for base type annotations.""" -from tfx.types import system_artifacts as _system_artifacts -from tfx.types import system_executions as _system_executions - # List of MLMD base artifact type annotations. -Dataset = _system_artifacts.Dataset -Model = _system_artifacts.Model -Statistics = _system_artifacts.Statistics -Metrics = _system_artifacts.Metrics +from tfx.types.system_artifacts import Dataset, Model, Statistics, Metrics # List of MLMD base execution type annotations. -Train = _system_executions.Train -Transform = _system_executions.Transform -Process = _system_executions.Process -Evaluate = _system_executions.Evaluate -Deploy = _system_executions.Deploy +from tfx.types.system_executions import Train, Transform, Process, Evaluate, Deploy -del _system_artifacts -del _system_executions +__all__ = [ + "Dataset", + "Deploy", + "Evaluate", + "Metrics", + "Model", + "Process", + "Statistics", + "Train", + "Transform", +] diff --git a/tfx/v1/orchestration/experimental/__init__.py b/tfx/v1/orchestration/experimental/__init__.py index 4f222b8371..df82230e4e 100644 --- a/tfx/v1/orchestration/experimental/__init__.py +++ b/tfx/v1/orchestration/experimental/__init__.py @@ -14,8 +14,10 @@ """TFX orchestration.experimental module.""" try: - from tfx.orchestration.kubeflow import ( - kubeflow_dag_runner, + from tfx.orchestration.kubeflow.kubeflow_dag_runner import ( + KubeflowDagRunner, + KubeflowDagRunnerConfig, + get_default_kubeflow_metadata_config, ) from tfx.orchestration.kubeflow.decorators import ( exit_handler, @@ -23,28 +25,16 @@ from tfx.orchestration.kubeflow.decorators import ( FinalStatusStr, ) - from tfx.utils import telemetry_utils + from tfx.utils.telemetry_utils import LABEL_KFP_SDK_ENV - KubeflowDagRunner = kubeflow_dag_runner.KubeflowDagRunner - KubeflowDagRunnerConfig = kubeflow_dag_runner.KubeflowDagRunnerConfig - get_default_kubeflow_metadata_config = ( - kubeflow_dag_runner.get_default_kubeflow_metadata_config - ) - LABEL_KFP_SDK_ENV = telemetry_utils.LABEL_KFP_SDK_ENV - - del telemetry_utils - del kubeflow_dag_runner except ImportError: # Import will fail without kfp package. pass try: - from tfx.orchestration.kubeflow.v2 import ( - kubeflow_v2_dag_runner, + from tfx.orchestration.kubeflow.v2.kubeflow_v2_dag_runner import ( + KubeflowV2DagRunner, + KubeflowV2DagRunnerConfig, ) - - KubeflowV2DagRunner = kubeflow_v2_dag_runner.KubeflowV2DagRunner - KubeflowV2DagRunnerConfig = kubeflow_v2_dag_runner.KubeflowV2DagRunnerConfig - del kubeflow_v2_dag_runner except ImportError: # Import will fail without kfp package. pass diff --git a/tfx/v1/orchestration/metadata.py b/tfx/v1/orchestration/metadata.py index 2eaaa2f6d8..ccf7f4fab3 100644 --- a/tfx/v1/orchestration/metadata.py +++ b/tfx/v1/orchestration/metadata.py @@ -13,11 +13,11 @@ # limitations under the License. """Public API for metadata.""" -from tfx.orchestration import metadata - -ConnectionConfigType = metadata.ConnectionConfigType -mysql_metadata_connection_config = metadata.mysql_metadata_connection_config -sqlite_metadata_connection_config = metadata.sqlite_metadata_connection_config +from tfx.orchestration.metadata import ( + ConnectionConfigType, + mysql_metadata_connection_config, + sqlite_metadata_connection_config, +) __all__ = [ "mysql_metadata_connection_config", From 172c3fe7e15431e2997fcf39c35e007e48f8ee40 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sun, 22 Sep 2024 15:12:47 -0700 Subject: [PATCH 54/58] Remove comment --- docs/api/v1/orchestration.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/api/v1/orchestration.md b/docs/api/v1/orchestration.md index 7b336419c8..6a13999208 100644 --- a/docs/api/v1/orchestration.md +++ b/docs/api/v1/orchestration.md @@ -3,4 +3,3 @@ ::: tfx.v1.orchestration options: show_if_no_docstring: true - From 69f7a5343d9f6e772fd4c9a7f5a9525215ebd6f6 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sun, 22 Sep 2024 18:05:27 -0700 Subject: [PATCH 55/58] Fix links --- docs/guide/build_tfx_pipeline.md | 2 +- docs/guide/fairness_indicators.md | 2 +- docs/guide/index.md | 2 +- docs/guide/tft_bestpractices.md | 4 ++-- docs/tutorials/index.md | 4 ++-- docs/tutorials/transform/data_preprocessing_with_cloud.md | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/guide/build_tfx_pipeline.md b/docs/guide/build_tfx_pipeline.md index f2dd6b863d..c9294d7e4d 100644 --- a/docs/guide/build_tfx_pipeline.md +++ b/docs/guide/build_tfx_pipeline.md @@ -78,7 +78,7 @@ that the current component must be executed before the specified component. The easiest way to get a pipeline set up quickly, and to see how all the pieces fit together, is to use a template. Using templates is covered in [Building a -TFX Pipeline Locally](build_local_pipeline). +TFX Pipeline Locally](../build_local_pipeline). ## Caching diff --git a/docs/guide/fairness_indicators.md b/docs/guide/fairness_indicators.md index b316a66467..7f891d1408 100644 --- a/docs/guide/fairness_indicators.md +++ b/docs/guide/fairness_indicators.md @@ -308,7 +308,7 @@ contains several examples: * [Fairness_Indicators_Example_Colab.ipynb](https://github.com/tensorflow/fairness-indicators/blob/master/g3doc/tutorials/Fairness_Indicators_Example_Colab.ipynb) gives an overview of Fairness Indicators in - [TensorFlow Model Analysis](./tfma) and + [TensorFlow Model Analysis](../tfma) and how to use it with a real dataset. This notebook also goes over [TensorFlow Data Validation](https://www.tensorflow.org/tfx/data_validation/get_started) and [What-If Tool](https://pair-code.github.io/what-if-tool/), two tools for diff --git a/docs/guide/index.md b/docs/guide/index.md index 9f41cc3f57..cf70a88ecf 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -351,7 +351,7 @@ is consumed by the other components. TFX provides a powerful platform for every phase of a machine learning project, from research, experimentation, and development on your local machine, through deployment. In order to avoid code duplication and eliminate the potential for -[training/serving skew](./tfdv#training-serving_skew_detection) +[training/serving skew](./tfdv#training-serving-skew-detection) it is strongly recommended to implement your TFX pipeline for both model training and deployment of trained models, and use [Transform](transform.md) components which leverage the [TensorFlow Transform](tft.md) library for both diff --git a/docs/guide/tft_bestpractices.md b/docs/guide/tft_bestpractices.md index 8288f8d072..44ab9bbc0c 100644 --- a/docs/guide/tft_bestpractices.md +++ b/docs/guide/tft_bestpractices.md @@ -155,7 +155,7 @@ For structured data, data preprocessing operations include the following: lower-dimension, more powerful data representations using techniques such as [PCA](https://en.wikipedia.org/wiki/Principal_component_analysis){: .external }, - [embedding](https://developers.google.com/machine-learning/glossary/#embeddings){: .external } + [embedding](https://developers.google.com/machine-learning/crash-course/embeddings){: .external } extraction, and [hashing](https://medium.com/value-stream-design/introducing-one-of-the-best-hacks-in-machine-learning-the-hashing-trick-bf6a9c8af18f){: .external }. - **Feature selection:** selecting a subset of the input features for @@ -720,5 +720,5 @@ columns. - Learn about best practices for ML engineering in [Rules of ML](https://developers.google.com/machine-learning/guides/rules-of-ml/){: .external }. + For more reference architectures, diagrams, and best practices, explore the - TFX + TFX Cloud Solutions. diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index ed761c87fc..6085d56ace 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -21,7 +21,7 @@ you'll learn the two main styles of developing a TFX pipeline: Probably the simplest pipeline you can build, to help you get started. Click the _Run in Google Colab_ button. - [:octicons-arrow-right-24: Starter Pipeline](tfx/penguin_simple.md) + [:octicons-arrow-right-24: Starter Pipeline](tfx/penguin_simple) - __2. Adding Data Validation__ @@ -95,7 +95,7 @@ in your TFX pipeline. ## Next Steps Once you have a basic understanding of TFX, check these additional tutorials and -guides. And don't forget to read the [TFX User Guide](guide/index.md). +guides. And don't forget to read the [TFX User Guide](../../guide).
diff --git a/docs/tutorials/transform/data_preprocessing_with_cloud.md b/docs/tutorials/transform/data_preprocessing_with_cloud.md index b49ef825ef..8b4db2a29b 100644 --- a/docs/tutorials/transform/data_preprocessing_with_cloud.md +++ b/docs/tutorials/transform/data_preprocessing_with_cloud.md @@ -466,7 +466,7 @@ following columns: - `weight_pounds` (type: `FLOAT`) As explained in -[Preprocessing operations](data-preprocessing-for-ml-with-tf-transform-pt1#preprocessing-operations) +[Preprocessing operations](../data-preprocessing-for-ml-with-tf-transform-pt1#preprocessing-operations) in the first part of this series, the feature transformation converts categorical features to a numeric representation. After the transformation, the categorical features are represented by integer values. In the @@ -1018,7 +1018,7 @@ resources used in this tutorial, delete the project that contains the resources. - To learn about the concepts, challenges, and options of data preprocessing for machine learning on Google Cloud, see the first article in this series, - [Data preprocessing for ML: options and recommendations](../guide/tft_bestpractices). + [Data preprocessing for ML: options and recommendations](../../../guide/tft_bestpractices). - For more information about how to implement, package, and run a tf.Transform pipeline on Dataflow, see the [Predicting income with Census Dataset](https://github.com/GoogleCloudPlatform/cloudml-samples/tree/master/census/tftransformestimator) From fb8f301832e73146cdb856ddaa0fe9921db8d3b5 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:02:56 -0700 Subject: [PATCH 56/58] Fix image paths --- docs/tutorials/tfx/cloud-ai-platform-pipelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md index eaa60c7f77..d5787c6255 100644 --- a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md +++ b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md @@ -410,8 +410,8 @@ data. ### Components -![Data Components](images/airflow_workshop/examplegen1.png) -![Data Components](images/airflow_workshop/examplegen2.png) +![Data Components](../../../tfx/examples/airflow_workshop/taxi/notebooks/img/examplegen1.png) +![Data Components](../../../tfx/examples/airflow_workshop/taxi/notebooks/img/examplegen2.png) * [ExampleGen](../../../guide/examplegen) ingests and splits the input dataset. @@ -479,7 +479,7 @@ serving. ### Components -![Transform](images/airflow_workshop/transform.png) +![Transform](../../../tfx/examples/airflow_workshop/taxi/notebooks/img/transform.png) * [Transform](../../../guide/transform) performs feature engineering on the dataset. From 9358eb566fdd67700cc7ea83951a442347cec893 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:51:36 -0700 Subject: [PATCH 57/58] Copy image files from code source directory into docs directory --- .../tfx/cloud-ai-platform-pipelines.md | 6 +++--- .../cloud-ai-platform-pipelines/examplegen1.png | Bin 0 -> 57859 bytes .../cloud-ai-platform-pipelines/examplegen2.png | Bin 0 -> 49866 bytes .../cloud-ai-platform-pipelines/transform.png | Bin 0 -> 20710 bytes 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png create mode 100644 docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen2.png create mode 100644 docs/tutorials/tfx/images/cloud-ai-platform-pipelines/transform.png diff --git a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md index d5787c6255..7edd78f6ab 100644 --- a/docs/tutorials/tfx/cloud-ai-platform-pipelines.md +++ b/docs/tutorials/tfx/cloud-ai-platform-pipelines.md @@ -410,8 +410,8 @@ data. ### Components -![Data Components](../../../tfx/examples/airflow_workshop/taxi/notebooks/img/examplegen1.png) -![Data Components](../../../tfx/examples/airflow_workshop/taxi/notebooks/img/examplegen2.png) +![Data Components](images/cloud-ai-platform-pipelines/examplegen1.png) +![Data Components](images/cloud-ai-platform-pipelines/examplegen2.png) * [ExampleGen](../../../guide/examplegen) ingests and splits the input dataset. @@ -479,7 +479,7 @@ serving. ### Components -![Transform](../../../tfx/examples/airflow_workshop/taxi/notebooks/img/transform.png) +![Transform](images/cloud-ai-platform-pipelines/transform.png) * [Transform](../../../guide/transform) performs feature engineering on the dataset. diff --git a/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png new file mode 100644 index 0000000000000000000000000000000000000000..b1840ff92c6c62bd8de08aece323b8e77713976d GIT binary patch literal 57859 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�b^(Yqz6flL0Obda?_4&B1Kcj$YMR>O8=;(GT48ULV zD=4@(R062>T_DrjB*ZCpG*A|l&tC%iIL*v$1c(8Fs5WCjo=i{Bq@?#YksX;^i`~z1 zjPA*ET<>wLR$@vNbT3h2avg!vX#$Fbg%r23=b)WVDA*YPJ$*|Qpv(t}KF2x^*ffR* z#bt9bZu5YmDp%S8`QwHr#%OTz}Ma=Qi`?Qzujs|)08;1*_Cq%;S_ z)Kel|WVw;-&u5<^@=5Rd(@%2wEEcAwzwWnzId>hAMWL`Az?4QZtHM*vOs_wT)XH1b ze1yF^P?DPh_A=1ui^2lf=>XA5VD|%GT-VtlL-~j^$G0U3dAhxLaq~)mAFyI~l+|JaG&(3eDBaG; z5aA8%_y9-DlarGd26(i&Xvs}F>QWRM;Cop^4LBa-Aj^GXcI^K9`r8{F4wMNz74hWr zmj-Ucl$EW0)T#dM%}rq*Hrsc=(@f__LdsBJi=;)MXicPr%(TFXt9v}o`5f6{40O&F z!}+J0Qy2q*of8qRB8FK~&1z49TQK#24bRM3+j7f+(;mPrciXoge_Xi!Ja9E6`(z)r z^z?M#6vpyp%dQm7FLF|nxfCN{RSr6WF7r7P-?}*- zg?{mG=Nz0h0aSW_eKmPz+ZEmQH9oIY+~-*4FH7|c(GW@T17<|vJgnL5x%20TU-c~F zW@vc+#PpnH53qXKd8l@SkDF6gsqxlhGiOcf0Gk6$iN-RLh7PyTK>(|P6Z79L;& zQf0By^F7xW$p{Gor>|_5J1GU$eOkCK+lUz$=#$R00oxwdp^_74?Sr}{_v)!sEv-+M zA(mrU!Kt8-xDj$k`_68*1uIvam?|8ZderFl zwotz(s~MXnFfcGWfzAcs>O9c{Y^|*{x=?8QC*ZniXwVWSWz!31w(gPy8q2_7#$4{F z66Wr@PCC2t`!kqk-70>`VE?ca0GH&gY2Q@FPd}_;L z{eTOnt%EfC^G)kncU~@?v3)LZxiG`Eo3=$t8+UCuF;z13oyh9Pijd06v48cd6Os}@ zp(%pEaw8#l7tpmk#rBjhbDGL2_)&mi!7_{0T`kJGt8%!7z|DlD@3VzH*Qu;9l2B4) zVz{#A)Rq;m>VOs`T&~(M=fugcjzDDx+8%H hfyxLV*x>lLewR||zwWTfz(wl}44$rjF6*2UngAQt;sO8w literal 0 HcmV?d00001 diff --git a/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen2.png b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen2.png new file mode 100644 index 0000000000000000000000000000000000000000..c9d7072b25b7ad23870dd308a4d7381922d0109a GIT binary patch literal 49866 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 z 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

N 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 diff --git a/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/transform.png b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/transform.png new file mode 120000 index 0000000000..9391389e98 --- /dev/null +++ b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/transform.png @@ -0,0 +1 @@ +../../../../../tfx/examples/airflow_workshop/taxi/notebooks/img/transform.png \ No newline at end of file

N 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 literal 0 HcmV?d00001 From 8bf8b695340097e12920e2bd310f0fed4103ffc7 Mon Sep 17 00:00:00 2001 From: smokestacklightnin <125844868+smokestacklightnin@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:41:11 -0700 Subject: [PATCH 58/58] Change copied images to softlinks The command used was `ln -rs` --- .../examplegen1.png | Bin 57859 -> 79 bytes .../examplegen2.png | Bin 49866 -> 79 bytes .../cloud-ai-platform-pipelines/transform.png | Bin 20710 -> 77 bytes 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 120000 docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png mode change 100644 => 120000 docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen2.png mode change 100644 => 120000 docs/tutorials/tfx/images/cloud-ai-platform-pipelines/transform.png diff --git a/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png deleted file mode 100644 index b1840ff92c6c62bd8de08aece323b8e77713976d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57859 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�b^(Yqz6flL0Obda?_4&B1Kcj$YMR>O8=;(GT48ULV zD=4@(R062>T_DrjB*ZCpG*A|l&tC%iIL*v$1c(8Fs5WCjo=i{Bq@?#YksX;^i`~z1 zjPA*ET<>wLR$@vNbT3h2avg!vX#$Fbg%r23=b)WVDA*YPJ$*|Qpv(t}KF2x^*ffR* z#bt9bZu5YmDp%S8`QwHr#%OTz}Ma=Qi`?Qzujs|)08;1*_Cq%;S_ z)Kel|WVw;-&u5<^@=5Rd(@%2wEEcAwzwWnzId>hAMWL`Az?4QZtHM*vOs_wT)XH1b ze1yF^P?DPh_A=1ui^2lf=>XA5VD|%GT-VtlL-~j^$G0U3dAhxLaq~)mAFyI~l+|JaG&(3eDBaG; z5aA8%_y9-DlarGd26(i&Xvs}F>QWRM;Cop^4LBa-Aj^GXcI^K9`r8{F4wMNz74hWr zmj-Ucl$EW0)T#dM%}rq*Hrsc=(@f__LdsBJi=;)MXicPr%(TFXt9v}o`5f6{40O&F z!}+J0Qy2q*of8qRB8FK~&1z49TQK#24bRM3+j7f+(;mPrciXoge_Xi!Ja9E6`(z)r z^z?M#6vpyp%dQm7FLF|nxfCN{RSr6WF7r7P-?}*- zg?{mG=Nz0h0aSW_eKmPz+ZEmQH9oIY+~-*4FH7|c(GW@T17<|vJgnL5x%20TU-c~F zW@vc+#PpnH53qXKd8l@SkDF6gsqxlhGiOcf0Gk6$iN-RLh7PyTK>(|P6Z79L;& zQf0By^F7xW$p{Gor>|_5J1GU$eOkCK+lUz$=#$R00oxwdp^_74?Sr}{_v)!sEv-+M zA(mrU!Kt8-xDj$k`_68*1uIvam?|8ZderFl zwotz(s~MXnFfcGWfzAcs>O9c{Y^|*{x=?8QC*ZniXwVWSWz!31w(gPy8q2_7#$4{F z66Wr@PCC2t`!kqk-70>`VE?ca0GH&gY2Q@FPd}_;L z{eTOnt%EfC^G)kncU~@?v3)LZxiG`Eo3=$t8+UCuF;z13oyh9Pijd06v48cd6Os}@ zp(%pEaw8#l7tpmk#rBjhbDGL2_)&mi!7_{0T`kJGt8%!7z|DlD@3VzH*Qu;9l2B4) zVz{#A)Rq;m>VOs`T&~(M=fugcjzDDx+8%H hfyxLV*x>lLewR||zwWTfz(wl}44$rjF6*2UngAQt;sO8w diff --git a/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png new file mode 120000 index 0000000000..1a26a5688c --- /dev/null +++ b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen1.png @@ -0,0 +1 @@ +../../../../../tfx/examples/airflow_workshop/taxi/notebooks/img/examplegen1.png \ No newline at end of file diff --git a/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen2.png b/docs/tutorials/tfx/images/cloud-ai-platform-pipelines/examplegen2.png deleted file mode 100644 index c9d7072b25b7ad23870dd308a4d7381922d0109a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49866 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 z 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