diff --git a/review/pr-767/.buildinfo b/review/pr-767/.buildinfo new file mode 100644 index 0000000000..6f628636d6 --- /dev/null +++ b/review/pr-767/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: f709464ee2a16d14c2802e9832ac6eee +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/review/pr-767/.doctrees/CONTRIBUTING.doctree b/review/pr-767/.doctrees/CONTRIBUTING.doctree new file mode 100644 index 0000000000..96c386a92a Binary files /dev/null and b/review/pr-767/.doctrees/CONTRIBUTING.doctree differ diff --git a/review/pr-767/.doctrees/README.doctree b/review/pr-767/.doctrees/README.doctree new file mode 100644 index 0000000000..39e9445cd7 Binary files /dev/null and b/review/pr-767/.doctrees/README.doctree differ diff --git a/review/pr-767/.doctrees/api/merlin_standard_lib.doctree b/review/pr-767/.doctrees/api/merlin_standard_lib.doctree new file mode 100644 index 0000000000..ec78772a61 Binary files /dev/null and b/review/pr-767/.doctrees/api/merlin_standard_lib.doctree differ diff --git a/review/pr-767/.doctrees/api/merlin_standard_lib.proto.doctree b/review/pr-767/.doctrees/api/merlin_standard_lib.proto.doctree new file mode 100644 index 0000000000..17b89c56a7 Binary files /dev/null and b/review/pr-767/.doctrees/api/merlin_standard_lib.proto.doctree differ diff --git a/review/pr-767/.doctrees/api/merlin_standard_lib.schema.doctree b/review/pr-767/.doctrees/api/merlin_standard_lib.schema.doctree new file mode 100644 index 0000000000..ebe354e6c2 Binary files /dev/null and b/review/pr-767/.doctrees/api/merlin_standard_lib.schema.doctree differ diff --git a/review/pr-767/.doctrees/api/merlin_standard_lib.utils.doctree b/review/pr-767/.doctrees/api/merlin_standard_lib.utils.doctree new file mode 100644 index 0000000000..676618497e Binary files /dev/null and b/review/pr-767/.doctrees/api/merlin_standard_lib.utils.doctree differ diff --git a/review/pr-767/.doctrees/api/modules.doctree b/review/pr-767/.doctrees/api/modules.doctree new file mode 100644 index 0000000000..2abe6fe5d2 Binary files /dev/null and b/review/pr-767/.doctrees/api/modules.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.config.doctree b/review/pr-767/.doctrees/api/transformers4rec.config.doctree new file mode 100644 index 0000000000..9a57d9ab0a Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.config.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.doctree b/review/pr-767/.doctrees/api/transformers4rec.doctree new file mode 100644 index 0000000000..9c9305dcca Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.torch.block.doctree b/review/pr-767/.doctrees/api/transformers4rec.torch.block.doctree new file mode 100644 index 0000000000..bf24f23cf0 Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.torch.block.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.torch.doctree b/review/pr-767/.doctrees/api/transformers4rec.torch.doctree new file mode 100644 index 0000000000..f93297bb59 Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.torch.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.torch.features.doctree b/review/pr-767/.doctrees/api/transformers4rec.torch.features.doctree new file mode 100644 index 0000000000..87306d61e6 Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.torch.features.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.torch.model.doctree b/review/pr-767/.doctrees/api/transformers4rec.torch.model.doctree new file mode 100644 index 0000000000..45f6e0d6e7 Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.torch.model.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.torch.tabular.doctree b/review/pr-767/.doctrees/api/transformers4rec.torch.tabular.doctree new file mode 100644 index 0000000000..54e64bf829 Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.torch.tabular.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.torch.utils.doctree b/review/pr-767/.doctrees/api/transformers4rec.torch.utils.doctree new file mode 100644 index 0000000000..63ed23837e Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.torch.utils.doctree differ diff --git a/review/pr-767/.doctrees/api/transformers4rec.utils.doctree b/review/pr-767/.doctrees/api/transformers4rec.utils.doctree new file mode 100644 index 0000000000..ed499d7475 Binary files /dev/null and b/review/pr-767/.doctrees/api/transformers4rec.utils.doctree differ diff --git a/review/pr-767/.doctrees/environment.pickle b/review/pr-767/.doctrees/environment.pickle new file mode 100644 index 0000000000..8c7ca418ec Binary files /dev/null and b/review/pr-767/.doctrees/environment.pickle differ diff --git a/review/pr-767/.doctrees/examples/end-to-end-session-based/01-ETL-with-NVTabular.doctree b/review/pr-767/.doctrees/examples/end-to-end-session-based/01-ETL-with-NVTabular.doctree new file mode 100644 index 0000000000..7fa456b34b Binary files /dev/null and b/review/pr-767/.doctrees/examples/end-to-end-session-based/01-ETL-with-NVTabular.doctree differ diff --git a/review/pr-767/.doctrees/examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT.doctree b/review/pr-767/.doctrees/examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT.doctree new file mode 100644 index 0000000000..6a943e56db Binary files /dev/null and b/review/pr-767/.doctrees/examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT.doctree differ diff --git a/review/pr-767/.doctrees/examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT.doctree b/review/pr-767/.doctrees/examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT.doctree new file mode 100644 index 0000000000..0cca46748c Binary files /dev/null and b/review/pr-767/.doctrees/examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT.doctree differ diff --git a/review/pr-767/.doctrees/examples/end-to-end-session-based/index.doctree b/review/pr-767/.doctrees/examples/end-to-end-session-based/index.doctree new file mode 100644 index 0000000000..a039a037e3 Binary files /dev/null and b/review/pr-767/.doctrees/examples/end-to-end-session-based/index.doctree differ diff --git a/review/pr-767/.doctrees/examples/getting-started-session-based/01-ETL-with-NVTabular.doctree b/review/pr-767/.doctrees/examples/getting-started-session-based/01-ETL-with-NVTabular.doctree new file mode 100644 index 0000000000..736c2f8456 Binary files /dev/null and b/review/pr-767/.doctrees/examples/getting-started-session-based/01-ETL-with-NVTabular.doctree differ diff --git a/review/pr-767/.doctrees/examples/getting-started-session-based/02-session-based-XLNet-with-PyT.doctree b/review/pr-767/.doctrees/examples/getting-started-session-based/02-session-based-XLNet-with-PyT.doctree new file mode 100644 index 0000000000..793172c81c Binary files /dev/null and b/review/pr-767/.doctrees/examples/getting-started-session-based/02-session-based-XLNet-with-PyT.doctree differ diff --git a/review/pr-767/.doctrees/examples/getting-started-session-based/03-serving-session-based-model-torch-backend.doctree b/review/pr-767/.doctrees/examples/getting-started-session-based/03-serving-session-based-model-torch-backend.doctree new file mode 100644 index 0000000000..f831f340ee Binary files /dev/null and b/review/pr-767/.doctrees/examples/getting-started-session-based/03-serving-session-based-model-torch-backend.doctree differ diff --git a/review/pr-767/.doctrees/examples/getting-started-session-based/index.doctree b/review/pr-767/.doctrees/examples/getting-started-session-based/index.doctree new file mode 100644 index 0000000000..58db28d4d7 Binary files /dev/null and b/review/pr-767/.doctrees/examples/getting-started-session-based/index.doctree differ diff --git a/review/pr-767/.doctrees/examples/index.doctree b/review/pr-767/.doctrees/examples/index.doctree new file mode 100644 index 0000000000..869f7965bc Binary files /dev/null and b/review/pr-767/.doctrees/examples/index.doctree differ diff --git a/review/pr-767/.doctrees/examples/t4rec_paper_experiments/index.doctree b/review/pr-767/.doctrees/examples/t4rec_paper_experiments/index.doctree new file mode 100644 index 0000000000..3338885472 Binary files /dev/null and b/review/pr-767/.doctrees/examples/t4rec_paper_experiments/index.doctree differ diff --git a/review/pr-767/.doctrees/examples/tutorial/01-preprocess.doctree b/review/pr-767/.doctrees/examples/tutorial/01-preprocess.doctree new file mode 100644 index 0000000000..f5ae992121 Binary files /dev/null and b/review/pr-767/.doctrees/examples/tutorial/01-preprocess.doctree differ diff --git a/review/pr-767/.doctrees/examples/tutorial/02-ETL-with-NVTabular.doctree b/review/pr-767/.doctrees/examples/tutorial/02-ETL-with-NVTabular.doctree new file mode 100644 index 0000000000..082ee20f5b Binary files /dev/null and b/review/pr-767/.doctrees/examples/tutorial/02-ETL-with-NVTabular.doctree differ diff --git a/review/pr-767/.doctrees/examples/tutorial/03-Session-based-recsys.doctree b/review/pr-767/.doctrees/examples/tutorial/03-Session-based-recsys.doctree new file mode 100644 index 0000000000..a8df15ff3b Binary files /dev/null and b/review/pr-767/.doctrees/examples/tutorial/03-Session-based-recsys.doctree differ diff --git a/review/pr-767/.doctrees/examples/tutorial/index.doctree b/review/pr-767/.doctrees/examples/tutorial/index.doctree new file mode 100644 index 0000000000..313d401e13 Binary files /dev/null and b/review/pr-767/.doctrees/examples/tutorial/index.doctree differ diff --git a/review/pr-767/.doctrees/glue_cache.json b/review/pr-767/.doctrees/glue_cache.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/review/pr-767/.doctrees/glue_cache.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/review/pr-767/.doctrees/index.doctree b/review/pr-767/.doctrees/index.doctree new file mode 100644 index 0000000000..04ba5c31c6 Binary files /dev/null and b/review/pr-767/.doctrees/index.doctree differ diff --git a/review/pr-767/.doctrees/model_definition.doctree b/review/pr-767/.doctrees/model_definition.doctree new file mode 100644 index 0000000000..943beacfd8 Binary files /dev/null and b/review/pr-767/.doctrees/model_definition.doctree differ diff --git a/review/pr-767/.doctrees/multi_gpu_train.doctree b/review/pr-767/.doctrees/multi_gpu_train.doctree new file mode 100644 index 0000000000..4ac3906d03 Binary files /dev/null and b/review/pr-767/.doctrees/multi_gpu_train.doctree differ diff --git a/review/pr-767/.doctrees/pipeline.doctree b/review/pr-767/.doctrees/pipeline.doctree new file mode 100644 index 0000000000..d3fbf4ae53 Binary files /dev/null and b/review/pr-767/.doctrees/pipeline.doctree differ diff --git a/review/pr-767/.doctrees/resources.doctree b/review/pr-767/.doctrees/resources.doctree new file mode 100644 index 0000000000..585ab83d57 Binary files /dev/null and b/review/pr-767/.doctrees/resources.doctree differ diff --git a/review/pr-767/.doctrees/training_eval.doctree b/review/pr-767/.doctrees/training_eval.doctree new file mode 100644 index 0000000000..4fc53ce047 Binary files /dev/null and b/review/pr-767/.doctrees/training_eval.doctree differ diff --git a/review/pr-767/.doctrees/why_transformers4rec.doctree b/review/pr-767/.doctrees/why_transformers4rec.doctree new file mode 100644 index 0000000000..a04c4f5e1a Binary files /dev/null and b/review/pr-767/.doctrees/why_transformers4rec.doctree differ diff --git a/review/pr-767/.nojekyll b/review/pr-767/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/review/pr-767/CONTRIBUTING.html b/review/pr-767/CONTRIBUTING.html new file mode 100644 index 0000000000..879ccb6840 --- /dev/null +++ b/review/pr-767/CONTRIBUTING.html @@ -0,0 +1,224 @@ + + + + + + Contributing to Transformers4Rec — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Contributing to Transformers4Rec

+

If you are interested in contributing to Transformers4Rec your contributions will fall +into three categories:

+
    +
  1. You want to report a bug, feature request, or documentation issue

    +
      +
    • File an issue +describing what you encountered or what you want to see changed.

    • +
    • The NVIDIA-Merlin team will evaluate the issues and triage them, scheduling +them for a release. If you believe the issue needs priority attention +comment on the issue to notify the team.

    • +
    +
  2. +
  3. You want to propose a new Feature and implement it

    +
      +
    • Post about your intended feature, and we shall discuss the design and +implementation.

    • +
    • Once we agree that the plan looks good, go ahead and implement it, using +the code contributions guide below.

    • +
    +
  4. +
  5. You want to implement a feature or bug-fix for an outstanding issue

    +
      +
    • Follow the code contributions guide below.

    • +
    • If you need more context on a particular issue, please ask and we shall +provide.

    • +
    +
  6. +
+
+

Code contributions

+
+

Your first issue

+
    +
  1. Read the project’s README.md +to learn how to setup the development environment

  2. +
  3. Find an issue to work on. The best way is to look for the good first issue +or help wanted labels

  4. +
  5. Comment on the issue saying you are going to work on it

  6. +
  7. Code! Make sure to update unit tests!

  8. +
  9. When done, create your pull request

  10. +
  11. Verify that CI passes all status checks. Fix if needed

  12. +
  13. Wait for other developers to review your code and update code as needed

  14. +
  15. Once reviewed and approved, a developer will merge your pull request

  16. +
+

Remember, if you are unsure about anything, don’t hesitate to comment on issues +and ask for clarifications!

+
+
+

Seasoned developers

+

Once you have gotten your feet wet and are more comfortable with the code, you +can look at the prioritized issues of our next release in our project boards.

+
+

Pro Tip: Always look at the release board with the highest number for +issues to work on. This is where Transformers4Rec developers also focus their efforts.

+
+

Look at the unassigned issues, and find an issue you are comfortable with +contributing to. Start with Step 3 from above, commenting on the issue to let +others know you are working on it. If you have any questions related to the +implementation of the issue, ask them in the issue instead of the PR.

+
+
+
+

Label your PRs

+

This repository uses the release-drafter action to draft and create our change log.

+

Please add one of the following labels to your PR to specify the type of contribution +and help categorize the PR in our change log:

+
    +
  • breaking – The PR creates a breaking change to the API.

  • +
  • bug – The PR fixes a problem with the code.

  • +
  • feature or enhancement – The PR introduces a backward-compatible feature.

  • +
  • documentation or examples – The PR is an addition or update to documentation.

  • +
  • build, dependencies, chore, or ci – The PR is related to maintaining the +repository or the project.

  • +
+

By default, an unlabeled PR is listed at the top of the change log and is not +grouped under a heading like Features that groups similar PRs. +Labeling the PRs so we can categorize them is preferred.

+

If, for some reason, you do not believe your PR should be included in the change +log, you can add the skip-changelog label. +This label excludes the PR from the change log.

+

For more information, see .github/release-drafter.yml in the repository +or go to https://github.com/release-drafter/release-drafter.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/README.html b/review/pr-767/README.html new file mode 100644 index 0000000000..94efe9c3d2 --- /dev/null +++ b/review/pr-767/README.html @@ -0,0 +1,310 @@ + + + + + + Transformers4Rec — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Transformers4Rec
  • +
  • +
  • +
+
+
+
+
+ +
+

Transformers4Rec

+

PyPI +LICENSE +Documentation

+

Transformers4Rec is a flexible and efficient library for sequential and session-based recommendation and can work with PyTorch.

+

The library works as a bridge between natural language processing (NLP) and recommender systems (RecSys) by integrating with one of the most popular NLP frameworks, Hugging Face Transformers (HF). +Transformers4Rec makes state-of-the-art transformer architectures available for RecSys researchers and industry practitioners.

+

The following figure shows the use of the library in a recommender system. +Input data is typically a sequence of interactions such as items that are browsed in a web session or items put in a cart. +The library helps you process and model the interactions so that you can output better recommendations for the next item.

+

Sequential and Session-based recommendation with Transformers4Rec

+
+
Sequential and Session-based recommendation with Transformers4Rec
+
+

Traditional recommendation algorithms usually ignore the temporal dynamics and the sequence of interactions when trying to model user behavior. +Generally, the next user interaction is related to the sequence of the user’s previous choices. +In some cases, it might be a repeated purchase or song play. +User interests can also suffer from interest drift because preferences can change over time. +Those challenges are addressed by the sequential recommendation task.

+

A special use case of sequential-recommendation is the session-based recommendation task where you only have access to the short sequence of interactions within the current session. +This is very common in online services like e-commerce, news, and media portals where the user might choose to browse anonymously due to GDPR compliance that restricts collecting cookies or because the user is new to the site. +This task is also relevant for scenarios where the users’ interests change a lot over time depending on the user context or intent. +In this case, leveraging the interactions for the current session is more promising than old interactions to provide relevant recommendations.

+

To deal with sequential and session-based recommendation, many sequence learning algorithms previously applied in machine learning and NLP research have been explored for RecSys based on k-Nearest Neighbors, Frequent Pattern Mining, Hidden Markov Models, Recurrent Neural Networks, and more recently neural architectures using the Self-Attention Mechanism and transformer architectures. +Unlike Transformers4Rec, these frameworks only accept sequences of item IDs as input and do not provide a modularized, scalable implementation for production usage.

+
+

Benefits of Transformers4Rec

+

Transformers4Rec offers the following benefits:

+
    +
  • Flexibility: Transformers4Rec provides modularized building blocks that are configurable and compatible with standard PyTorch modules. +This building-block design enables you to create custom architectures with multiple towers, multiple heads/tasks, and losses.

  • +
  • Access to HF Transformers: More than 64 different Transformer architectures can be used to evaluate your sequential and session-based recommendation task as a result of the Hugging Face Transformers integration.

  • +
  • Support for multiple input features: HF Transformers only support sequences of token IDs as input because it was originally designed for NLP. +Transformers4Rec enables you to use other types of sequential tabular data as input with HF transformers due to the rich features that are available in RecSys datasets. +Transformers4Rec uses a schema to configure the input features and automatically creates the necessary layers, such as embedding tables, projection layers, and output layers based on the target without requiring code changes to include new features. +You can normalize and combine interaction and sequence-level input features in configurable ways.

  • +
  • Seamless preprocessing and feature engineering: As part of the Merlin ecosystem, Transformers4Rec is integrated with NVTabular and Triton Inference Server. +These components enable you to build a fully GPU-accelerated pipeline for sequential and session-based recommendation. +NVTabular has common preprocessing operations for session-based recommendation and exports a dataset schema. +The schema is compatible with Transformers4Rec so that input features can be configured automatically. +You can export your trained models to serve with Triton Inference Server in a single pipeline that includes online feature preprocessing and model inference. +For more information, refer to End-to-end pipeline with NVIDIA Merlin.

  • +
+

GPU-accelerated Sequential and Session-based recommendation

+
+
GPU-accelerated pipeline for Sequential and Session-based recommendation using NVIDIA Merlin components
+
+
+
+

Transformers4Rec Achievements

+

Transformers4Rec recently won two session-based recommendation competitions: WSDM WebTour Workshop Challenge 2021 (organized by Booking.com) and SIGIR eCommerce Workshop Data Challenge 2021 (organized by Coveo). +The library provides higher accuracy for session-based recommendation than baseline algorithms and we performed extensive empirical analysis about the accuracy. +These observations are published in our ACM RecSys’21 paper.

+
+
+

Sample Code: Defining and Training the Model

+

Training a model with Transformers4Rec typically requires performing the following high-level steps:

+
    +
  1. Provide the schema and construct an input-module.

    +

    If you encounter session-based recommendation issues, you typically want to use the +TabularSequenceFeatures +class because it merges context features with sequential features.

    +
  2. +
  3. Provide the prediction-tasks.

    +

    The tasks that are provided right out of the box are available from our API documentation.

    +
  4. +
  5. Construct a transformer-body and convert this into a model.

  6. +
+

The following code sample shows how to define and train an XLNet model with PyTorch for next-item prediction task:

+
from transformers4rec import torch as tr
+from transformers4rec.torch.ranking_metric import NDCGAt, RecallAt
+
+# Create a schema or read one from disk: tr.Schema().from_json(SCHEMA_PATH).
+schema: tr.Schema = tr.data.tabular_sequence_testing_data.schema
+
+max_sequence_length, d_model = 20, 64
+
+# Define the input module to process the tabular input features.
+input_module = tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=max_sequence_length,
+    continuous_projection=d_model,
+    aggregation="concat",
+    masking="causal",
+)
+
+# Define a transformer-config like the XLNet architecture.
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=4, n_layer=2, total_seq_length=max_sequence_length
+)
+
+# Define the model block including: inputs, masking, projection and transformer block.
+body = tr.SequentialBlock(
+    input_module,
+    tr.MLPBlock([d_model]),
+    tr.TransformerBlock(transformer_config, masking=input_module.masking)
+)
+
+# Define the evaluation top-N metrics and the cut-offs
+metrics = [NDCGAt(top_ks=[20, 40], labels_onehot=True),
+           RecallAt(top_ks=[20, 40], labels_onehot=True)]
+
+# Define a head with NextItemPredictionTask.
+head = tr.Head(
+    body,
+    tr.NextItemPredictionTask(weight_tying=True, metrics=metrics),
+    inputs=input_module,
+)
+
+# Get the end-to-end Model class.
+model = tr.Model(head)
+
+
+
+

You can modify the preceding code to perform binary classification. +The masking in the input module can be set to None instead of causal. +When you define the head, you can replace the NextItemPredictionTask +with an instance of BinaryClassificationTask. +See the sample code in the API documentation for the class.

+
+
+
+

Installation

+

You can install Transformers4Rec with Pip, Conda, or run a Docker container.

+
+

Installing Transformers4Rec Using Pip

+

You can install Transformers4Rec with the functionality to use the GPU-accelerated Merlin dataloader. +Installation with the dataloader is highly recommended for better performance. +Those components can be installed as optional arguments for the pip install command.

+

To install Transformers4Rec using Pip, run the following command:

+
pip install transformers4rec[nvtabular]
+
+
+

-> Be aware that installing Transformers4Rec with pip does not automatically install RAPIDS cuDF. +-> cuDF is required for GPU-accelerated versions of NVTabular transforms and the Merlin Dataloader.

+

Instructions for installing cuDF with pip are available here: https://docs.rapids.ai/install#pip-install

+
pip install cudf-cu11 dask-cudf-cu11 --extra-index-url=https://pypi.nvidia.com
+
+
+
+
+

Installing Transformers4Rec Using Conda

+

To install Transformers4Rec using Conda, run the following command with conda or mamba to create a new environment.

+
mamba create -n transformers4rec-23.04 -c nvidia -c rapidsai -c pytorch -c conda-forge \
+    transformers4rec=23.04 `# NVIDIA Merlin` \
+    nvtabular=23.04 `# NVIDIA Merlin - Used in example notebooks` \
+    python=3.10 `# Compatible Python environment` \
+    cudf=23.02 `# RAPIDS cuDF - GPU accelerated DataFrame` \
+    cudatoolkit=11.8 pytorch-cuda=11.8 `# NVIDIA CUDA version`
+
+
+
+
+

Installing Transformers4Rec Using Docker

+

Transformers4Rec is pre-installed in the merlin-pytorch container that is available from the NVIDIA GPU Cloud (NGC) catalog.

+

Refer to the Merlin Containers documentation page for information about the Merlin container names, URLs to container images in the catalog, and key Merlin components.

+
+
+
+

Notebook Examples and Tutorials

+

The End-to-end pipeline with NVIDIA Merlin page +shows how to use Transformers4Rec and other Merlin libraries like NVTabular to build a complete recommender system.

+

We have several example notebooks to help you build a recommender system or integrate Transformers4Rec into your system:

+
    +
  • A getting started example that includes training a session-based model with an XLNET transformer architecture.

  • +
  • An end-to-end example that trains a model and takes the next step to serve inference with Triton Inference Server.

  • +
  • Another end-to-end example that trains and evaluates a session-based model on RNN and also serves inference with Triton Inference Server.

  • +
  • A notebook and scripts that reproduce the experiments presented in a paper for RecSys 2021.

  • +
+
+
+

Feedback and Support

+

If you’d like to make direct contributions to Transformers4Rec, refer to Contributing to Transformers4Rec. We’re particularly interested in contributions or feature requests for our feature engineering and preprocessing operations. To further advance our Merlin roadmap, we encourage you to share all the details regarding your recommender system pipeline by going to https://developer.nvidia.com/merlin-devzone-survey.

+

If you’re interested in learning more about how Transformers4Rec works, refer to our +Transformers4Rec documentation. We also have API documentation that outlines the specifics of the available modules and classes within Transformers4Rec.

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_images/DP_DDP_perf.png b/review/pr-767/_images/DP_DDP_perf.png new file mode 100644 index 0000000000..4576b383fa Binary files /dev/null and b/review/pr-767/_images/DP_DDP_perf.png differ diff --git a/review/pr-767/_images/encoder.png b/review/pr-767/_images/encoder.png new file mode 100644 index 0000000000..066766944b Binary files /dev/null and b/review/pr-767/_images/encoder.png differ diff --git a/review/pr-767/_images/gru_based.png b/review/pr-767/_images/gru_based.png new file mode 100644 index 0000000000..4ddaf122a7 Binary files /dev/null and b/review/pr-767/_images/gru_based.png differ diff --git a/review/pr-767/_images/masking.png b/review/pr-767/_images/masking.png new file mode 100644 index 0000000000..7b0b654ac0 Binary files /dev/null and b/review/pr-767/_images/masking.png differ diff --git a/review/pr-767/_images/nlp_x_recsys.png b/review/pr-767/_images/nlp_x_recsys.png new file mode 100644 index 0000000000..c80e18080b Binary files /dev/null and b/review/pr-767/_images/nlp_x_recsys.png differ diff --git a/review/pr-767/_images/pipeline.png b/review/pr-767/_images/pipeline.png new file mode 100644 index 0000000000..5e87165980 Binary files /dev/null and b/review/pr-767/_images/pipeline.png differ diff --git a/review/pr-767/_images/preproc_data_example.png b/review/pr-767/_images/preproc_data_example.png new file mode 100644 index 0000000000..6d68b1c9cc Binary files /dev/null and b/review/pr-767/_images/preproc_data_example.png differ diff --git a/review/pr-767/_images/sequential_rec.png b/review/pr-767/_images/sequential_rec.png new file mode 100644 index 0000000000..56cd9f6d85 Binary files /dev/null and b/review/pr-767/_images/sequential_rec.png differ diff --git a/review/pr-767/_images/tf4rec_meta.png b/review/pr-767/_images/tf4rec_meta.png new file mode 100644 index 0000000000..57dae2542c Binary files /dev/null and b/review/pr-767/_images/tf4rec_meta.png differ diff --git a/review/pr-767/_images/tf4rec_meta2.png b/review/pr-767/_images/tf4rec_meta2.png new file mode 100644 index 0000000000..a54ba5ea43 Binary files /dev/null and b/review/pr-767/_images/tf4rec_meta2.png differ diff --git a/review/pr-767/_images/transformer_vs_rnn.png b/review/pr-767/_images/transformer_vs_rnn.png new file mode 100644 index 0000000000..90ebce236b Binary files /dev/null and b/review/pr-767/_images/transformer_vs_rnn.png differ diff --git a/review/pr-767/_images/transformers4rec_metaarchitecture.png b/review/pr-767/_images/transformers4rec_metaarchitecture.png new file mode 100644 index 0000000000..7e485d8bde Binary files /dev/null and b/review/pr-767/_images/transformers4rec_metaarchitecture.png differ diff --git a/review/pr-767/_modules/index.html b/review/pr-767/_modules/index.html new file mode 100644 index 0000000000..53583a416b --- /dev/null +++ b/review/pr-767/_modules/index.html @@ -0,0 +1,156 @@ + + + + + + Overview: module code — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/merlin_standard_lib/proto/schema_bp.html b/review/pr-767/_modules/merlin_standard_lib/proto/schema_bp.html new file mode 100644 index 0000000000..7aa31184da --- /dev/null +++ b/review/pr-767/_modules/merlin_standard_lib/proto/schema_bp.html @@ -0,0 +1,937 @@ + + + + + + merlin_standard_lib.proto.schema_bp — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • merlin_standard_lib.proto.schema_bp
  • +
  • +
  • +
+
+
+
+
+ +

Source code for merlin_standard_lib.proto.schema_bp

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# sources: tensorflow_metadata/proto/v0/path.proto, tensorflow_metadata/proto/v0/schema.proto
+# plugin: python-betterproto
+import json
+from dataclasses import dataclass
+from types import SimpleNamespace
+from typing import Dict, List, Sequence
+
+import betterproto
+
+
+
[docs]class LifecycleStage(betterproto.Enum): + """ + LifecycleStage. Only UNKNOWN_STAGE, BETA, and PRODUCTION features are + actually validated. PLANNED, ALPHA, DISABLED, and DEBUG are treated as + DEPRECATED. + """ + + UNKNOWN_STAGE = 0 + PLANNED = 1 + ALPHA = 2 + BETA = 3 + PRODUCTION = 4 + DEPRECATED = 5 + DEBUG_ONLY = 6 + DISABLED = 7
+ + +
[docs]class FeatureType(betterproto.Enum): + """ + Describes the physical representation of a feature. It may be different + than the logical representation, which is represented as a Domain. + """ + + TYPE_UNKNOWN = 0 + BYTES = 1 + INT = 2 + FLOAT = 3 + STRUCT = 4
+ + +
[docs]class TimeDomainIntegerTimeFormat(betterproto.Enum): + FORMAT_UNKNOWN = 0 + UNIX_DAYS = 5 + UNIX_SECONDS = 1 + UNIX_MILLISECONDS = 2 + UNIX_MICROSECONDS = 3 + UNIX_NANOSECONDS = 4
+ + +
[docs]class TimeOfDayDomainIntegerTimeOfDayFormat(betterproto.Enum): + FORMAT_UNKNOWN = 0 + PACKED_64_NANOS = 1
+ + +
[docs]class TensorRepresentationRowPartitionDType(betterproto.Enum): + UNSPECIFIED = 0 + INT64 = 1 + INT32 = 2
+ + +
[docs]@dataclass +class Path(betterproto.Message): + """ + A path is a more general substitute for the name of a field or feature that + can be used for flat examples as well as structured data. For example, if + we had data in a protocol buffer: message Person { int age = 1; + optional string gender = 2; repeated Person parent = 3; } Thus, here the + path {step:["parent", "age"]} in statistics would refer to the age of a + parent, and {step:["parent", "parent", "age"]} would refer to the age of a + grandparent. This allows us to distinguish between the statistics of + parents' ages and grandparents' ages. In general, repeated messages are to + be preferred to linked lists of arbitrary length. For SequenceExample, if + we have a feature list "foo", this is represented by {step:["##SEQUENCE##", + "foo"]}. + """ + + # Any string is a valid step. However, whenever possible have a step be + # [A-Za-z0-9_]+. + step: List[str] = betterproto.string_field(1)
+ + +@dataclass +class _Schema(betterproto.Message): + """Message to represent schema information. NextID: 14""" + + # Features described in this schema. + feature: Sequence["Feature"] = betterproto.message_field(1) + # Sparse features described in this schema. + sparse_feature: List["SparseFeature"] = betterproto.message_field(6) + # Weighted features described in this schema. + weighted_feature: List["WeightedFeature"] = betterproto.message_field(12) + # declared as top-level features in <feature>. String domains referenced in + # the features. + string_domain: List["StringDomain"] = betterproto.message_field(4) + # top level float domains that can be reused by features + float_domain: List["FloatDomain"] = betterproto.message_field(9) + # top level int domains that can be reused by features + int_domain: List["IntDomain"] = betterproto.message_field(10) + # Default environments for each feature. An environment represents both a + # type of location (e.g. a server or phone) and a time (e.g. right before + # model X is run). In the standard scenario, 99% of the features should be in + # the default environments TRAINING, SERVING, and the LABEL (or labels) AND + # WEIGHT is only available at TRAINING (not at serving). Other possible + # variations: 1. There may be TRAINING_MOBILE, SERVING_MOBILE, + # TRAINING_SERVICE, and SERVING_SERVICE. 2. If one is ensembling three + # models, where the predictions of the first three models are available + # for the ensemble model, there may be TRAINING, SERVING_INITIAL, + # SERVING_ENSEMBLE. See FeatureProto::not_in_environment and + # FeatureProto::in_environment. + default_environment: List[str] = betterproto.string_field(5) + # Additional information about the schema as a whole. Features may also be + # annotated individually. + annotation: "Annotation" = betterproto.message_field(8) + # Dataset-level constraints. This is currently used for specifying + # information about changes in num_examples. + dataset_constraints: "DatasetConstraints" = betterproto.message_field(11) + # TensorRepresentation groups. The keys are the names of the groups. Key "" + # (empty string) denotes the "default" group, which is what should be used + # when a group name is not provided. See the documentation at + # TensorRepresentationGroup for more info. Under development. DO NOT USE. + tensor_representation_group: Dict[str, "TensorRepresentationGroup"] = betterproto.map_field( + 13, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + ) + + +
[docs]@dataclass +class ValueCountList(betterproto.Message): + value_count: List["ValueCount"] = betterproto.message_field(1)
+ + +
[docs]@dataclass +class Feature(betterproto.Message): + """ + Describes schema-level information about a specific feature. NextID: 33 + """ + + # The name of the feature. + name: str = betterproto.string_field(1) + # This field is no longer supported. Instead, use: lifecycle_stage: + # DEPRECATED TODO(b/111450258): remove this. + deprecated: bool = betterproto.bool_field(2) + # Constraints on the presence of this feature in the examples. + presence: "FeaturePresence" = betterproto.message_field(14, group="presence_constraints") + # Only used in the context of a "group" context, e.g., inside a sequence. + group_presence: "FeaturePresenceWithinGroup" = betterproto.message_field( + 17, group="presence_constraints" + ) + # The feature has a fixed shape corresponding to a multi-dimensional tensor. + shape: "FixedShape" = betterproto.message_field(23, group="shape_type") + # The feature doesn't have a well defined shape. All we know are limits on + # the minimum and maximum number of values. + value_count: "ValueCount" = betterproto.message_field(5, group="shape_type") + # Captures the same information as value_count but for features with nested + # values. A ValueCount is provided for each nest level. + value_counts: "ValueCountList" = betterproto.message_field(32, group="shape_type") + # Physical type of the feature's values. Note that you can have: type: BYTES + # int_domain: { min: 0 max: 3 } This would be a field that is + # syntactically BYTES (i.e. strings), but semantically an int, i.e. it would + # be "0", "1", "2", or "3". + type: "FeatureType" = betterproto.enum_field(6) + # Reference to a domain defined at the schema level. + domain: str = betterproto.string_field(7, group="domain_info") + # Inline definitions of domains. + int_domain: "IntDomain" = betterproto.message_field(9, group="domain_info") + float_domain: "FloatDomain" = betterproto.message_field(10, group="domain_info") + string_domain: "StringDomain" = betterproto.message_field(11, group="domain_info") + bool_domain: "BoolDomain" = betterproto.message_field(13, group="domain_info") + struct_domain: "StructDomain" = betterproto.message_field(29, group="domain_info") + # Supported semantic domains. + natural_language_domain: "NaturalLanguageDomain" = betterproto.message_field( + 24, group="domain_info" + ) + image_domain: "ImageDomain" = betterproto.message_field(25, group="domain_info") + mid_domain: "MIDDomain" = betterproto.message_field(26, group="domain_info") + url_domain: "URLDomain" = betterproto.message_field(27, group="domain_info") + time_domain: "TimeDomain" = betterproto.message_field(28, group="domain_info") + time_of_day_domain: "TimeOfDayDomain" = betterproto.message_field(30, group="domain_info") + # Constraints on the distribution of the feature values. Only supported for + # StringDomains. + distribution_constraints: "DistributionConstraints" = betterproto.message_field(15) + # Additional information about the feature for documentation purpose. + annotation: "Annotation" = betterproto.message_field(16) + # Tests comparing the distribution to the associated serving data. + skew_comparator: "FeatureComparator" = betterproto.message_field(18) + # Tests comparing the distribution between two consecutive spans (e.g. days). + drift_comparator: "FeatureComparator" = betterproto.message_field(21) + # List of environments this feature is present in. Should be disjoint from + # not_in_environment. This feature is in environment "foo" if: ("foo" is in + # in_environment or default_environment) AND "foo" is not in + # not_in_environment. See Schema::default_environment. + in_environment: List[str] = betterproto.string_field(20) + # List of environments this feature is not present in. Should be disjoint + # from of in_environment. See Schema::default_environment and in_environment. + not_in_environment: List[str] = betterproto.string_field(19) + # The lifecycle stage of a feature. It can also apply to its descendants. + # i.e., if a struct is DEPRECATED, its children are implicitly deprecated. + lifecycle_stage: "LifecycleStage" = betterproto.enum_field(22) + # Constraints on the number of unique values for a given feature. This is + # supported for string and categorical features only. + unique_constraints: "UniqueConstraints" = betterproto.message_field(31)
+ + +
[docs]@dataclass +class Annotation(betterproto.Message): + """Additional information about the schema or about a feature.""" + + # Tags can be used to mark features. For example, tag on user_age feature can + # be `user_feature`, tag on user_country feature can be `location_feature`, + # `user_feature`. + tag: List[str] = betterproto.string_field(1) + # Free-text comments. This can be used as a description of the feature, + # developer notes etc. + comment: List[str] = betterproto.string_field(2) + # Application-specific metadata may be attached here. + extra_metadata: List[betterproto.Any] = betterproto.message_field(3) + + @property + def metadata(self): + if self.comment: + metadata = SimpleNamespace(**json.loads(self.comment[-1])) + return metadata + + return None
+ + +
[docs]@dataclass +class NumericValueComparator(betterproto.Message): + """ + Checks that the ratio of the current value to the previous value is not + below the min_fraction_threshold or above the max_fraction_threshold. That + is, previous value * min_fraction_threshold <= current value <= previous + value * max_fraction_threshold. To specify that the value cannot change, + set both min_fraction_threshold and max_fraction_threshold to 1.0. + """ + + min_fraction_threshold: float = betterproto.double_field(1) + max_fraction_threshold: float = betterproto.double_field(2)
+ + +
[docs]@dataclass +class DatasetConstraints(betterproto.Message): + """Constraints on the entire dataset.""" + + # Tests differences in number of examples between the current data and the + # previous span. + num_examples_drift_comparator: "NumericValueComparator" = betterproto.message_field(1) + # Tests comparisons in number of examples between the current data and the + # previous version of that data. + num_examples_version_comparator: "NumericValueComparator" = betterproto.message_field(2) + # Minimum number of examples in the dataset. + min_examples_count: int = betterproto.int64_field(3) + # Maximum number of examples in the dataset. + max_examples_count: int = betterproto.int64_field(4)
+ + +
[docs]@dataclass +class FixedShape(betterproto.Message): + """ + Specifies a fixed shape for the feature's values. The immediate implication + is that each feature has a fixed number of values. Moreover, these values + can be parsed in a multi-dimensional tensor using the specified axis sizes. + The FixedShape defines a lexicographical ordering of the data. For + instance, if there is a FixedShape { dim {size:3} dim {size:2} } then + tensor[0][0]=field[0] then tensor[0][1]=field[1] then tensor[1][0]=field[2] + then tensor[1][1]=field[3] then tensor[2][0]=field[4] then + tensor[2][1]=field[5] The FixedShape message is identical with the + TensorFlow TensorShape proto message. + """ + + # The dimensions that define the shape. The total number of values in each + # example is the product of sizes of each dimension. + dim: List["FixedShapeDim"] = betterproto.message_field(2)
+ + +
[docs]@dataclass +class FixedShapeDim(betterproto.Message): + """An axis in a multi-dimensional feature representation.""" + + size: int = betterproto.int64_field(1) + # Optional name of the tensor dimension. + name: str = betterproto.string_field(2)
+ + +
[docs]@dataclass +class ValueCount(betterproto.Message): + """ + Limits on maximum and minimum number of values in a single example (when + the feature is present). Use this when the minimum value count can be + different than the maximum value count. Otherwise prefer FixedShape. + """ + + min: int = betterproto.int64_field(1) + max: int = betterproto.int64_field(2)
+ + +
[docs]@dataclass +class WeightedFeature(betterproto.Message): + """ + Represents a weighted feature that is encoded as a combination of raw base + features. The `weight_feature` should be a float feature with identical + shape as the `feature`. This is useful for representing weights associated + with categorical tokens (e.g. a TFIDF weight associated with each token). + TODO(b/142122960): Handle WeightedCategorical end to end in TFX + (validation, TFX Unit Testing, etc) + """ + + # Name for the weighted feature. This should not clash with other features in + # the same schema. + name: str = betterproto.string_field(1) + # Path of a base feature to be weighted. Required. + feature: "Path" = betterproto.message_field(2) + # Path of weight feature to associate with the base feature. Must be same + # shape as feature. Required. + weight_feature: "Path" = betterproto.message_field(3) + # The lifecycle_stage determines where a feature is expected to be used, and + # therefore how important issues with it are. + lifecycle_stage: "LifecycleStage" = betterproto.enum_field(4)
+ + +
[docs]@dataclass +class SparseFeature(betterproto.Message): + """ + A sparse feature represents a sparse tensor that is encoded with a + combination of raw features, namely index features and a value feature. + Each index feature defines a list of indices in a different dimension. + """ + + # Name for the sparse feature. This should not clash with other features in + # the same schema. + name: str = betterproto.string_field(1) + # This field is no longer supported. Instead, use: lifecycle_stage: + # DEPRECATED TODO(b/111450258): remove this. + deprecated: bool = betterproto.bool_field(2) + # The lifecycle_stage determines where a feature is expected to be used, and + # therefore how important issues with it are. + lifecycle_stage: "LifecycleStage" = betterproto.enum_field(7) + # Constraints on the presence of this feature in examples. Deprecated, this + # is inferred by the referred features. + presence: "FeaturePresence" = betterproto.message_field(4) + # Shape of the sparse tensor that this SparseFeature represents. Currently + # not supported. TODO(b/109669962): Consider deriving this from the referred + # features. + dense_shape: "FixedShape" = betterproto.message_field(5) + # Features that represent indexes. Should be integers >= 0. + index_feature: List["SparseFeatureIndexFeature"] = betterproto.message_field(6) + # If true then the index values are already sorted lexicographically. + is_sorted: bool = betterproto.bool_field(8) + value_feature: "SparseFeatureValueFeature" = betterproto.message_field(9) + # Type of value feature. Deprecated, this is inferred by the referred + # features. + type: "FeatureType" = betterproto.enum_field(10)
+ + +
[docs]@dataclass +class SparseFeatureIndexFeature(betterproto.Message): + # Name of the index-feature. This should be a reference to an existing + # feature in the schema. + name: str = betterproto.string_field(1)
+ + +
[docs]@dataclass +class SparseFeatureValueFeature(betterproto.Message): + # Name of the value-feature. This should be a reference to an existing + # feature in the schema. + name: str = betterproto.string_field(1)
+ + +
[docs]@dataclass +class DistributionConstraints(betterproto.Message): + """ + Models constraints on the distribution of a feature's values. + TODO(martinz): replace min_domain_mass with max_off_domain (but slowly). + """ + + # The minimum fraction (in [0,1]) of values across all examples that should + # come from the feature's domain, e.g.: 1.0 => All values must come from + # the domain. .9 => At least 90% of the values must come from the domain. + min_domain_mass: float = betterproto.double_field(1)
+ + +
[docs]@dataclass +class FeatureCoverageConstraints(betterproto.Message): + """Encodes vocabulary coverage constraints.""" + + # Fraction of feature values that map to a vocab entry (i.e. are not oov). + min_coverage: float = betterproto.float_field(1) + # Average length of tokens. Used for cases such as wordpiece that fallback to + # character-level tokenization. + min_avg_token_length: float = betterproto.float_field(2) + # String tokens to exclude when calculating min_coverage and + # min_avg_token_length. Useful for tokens such as [PAD]. + excluded_string_tokens: List[str] = betterproto.string_field(3) + # Integer tokens to exclude when calculating min_coverage and + # min_avg_token_length. + excluded_int_tokens: List[int] = betterproto.int64_field(4) + # String tokens to treat as oov tokens (e.g. [UNK]). These tokens are also + # excluded when calculating avg token length. + oov_string_tokens: List[str] = betterproto.string_field(5)
+ + +
[docs]@dataclass +class SequenceValueConstraints(betterproto.Message): + """Encodes constraints on specific values in sequences.""" + + int_value: int = betterproto.int64_field(1, group="value") + string_value: str = betterproto.string_field(2, group="value") + # Min / max number of times the value can occur in a sequence. + min_per_sequence: int = betterproto.int64_field(3) + max_per_sequence: int = betterproto.int64_field(4) + # Min / max fraction of sequences that must contain the value. + min_fraction_of_sequences: float = betterproto.float_field(5) + max_fraction_of_sequences: float = betterproto.float_field(6)
+ + +
[docs]@dataclass +class SequenceLengthConstraints(betterproto.Message): + """Encodes constraints on sequence lengths.""" + + # Token values (int and string) that are excluded when calculating sequence + # length. + excluded_int_value: List[int] = betterproto.int64_field(1) + excluded_string_value: List[str] = betterproto.string_field(2) + # Min / max sequence length. + min_sequence_length: int = betterproto.int64_field(3) + max_sequence_length: int = betterproto.int64_field(4)
+ + +
[docs]@dataclass +class IntDomain(betterproto.Message): + """ + Encodes information for domains of integer values. Note that FeatureType + could be either INT or BYTES. + """ + + # Id of the domain. Required if the domain is defined at the schema level. If + # so, then the name must be unique within the schema. + name: str = betterproto.string_field(1) + # Min and max values for the domain. + min: int = betterproto.int64_field(3) + max: int = betterproto.int64_field(4) + # If true then the domain encodes categorical values (i.e., ids) rather than + # ordinal values. + is_categorical: bool = betterproto.bool_field(5)
+ + +
[docs]@dataclass +class FloatDomain(betterproto.Message): + """ + Encodes information for domains of float values. Note that FeatureType + could be either INT or BYTES. + """ + + # Id of the domain. Required if the domain is defined at the schema level. If + # so, then the name must be unique within the schema. + name: str = betterproto.string_field(1) + # Min and max values of the domain. + min: float = betterproto.float_field(3) + max: float = betterproto.float_field(4) + # If true, feature should not contain NaNs. + disallow_nan: bool = betterproto.bool_field(5) + # If true, feature should not contain Inf or -Inf. + disallow_inf: bool = betterproto.bool_field(6) + # If True, this indicates that the feature is semantically an embedding. This + # can be useful for distinguishing fixed dimensional numeric features that + # should be fed to a model unmodified. + is_embedding: bool = betterproto.bool_field(7)
+ + +
[docs]@dataclass +class StructDomain(betterproto.Message): + """ + Domain for a recursive struct. NOTE: If a feature with a StructDomain is + deprecated, then all the child features (features and sparse_features of + the StructDomain) are also considered to be deprecated. Similarly child + features can only be in environments of the parent feature. + """ + + feature: List["Feature"] = betterproto.message_field(1) + sparse_feature: List["SparseFeature"] = betterproto.message_field(2)
+ + +
[docs]@dataclass +class StringDomain(betterproto.Message): + """Encodes information for domains of string values.""" + + # Id of the domain. Required if the domain is defined at the schema level. If + # so, then the name must be unique within the schema. + name: str = betterproto.string_field(1) + # The values appearing in the domain. + value: List[str] = betterproto.string_field(2)
+ + +
[docs]@dataclass +class BoolDomain(betterproto.Message): + """ + Encodes information about the domain of a boolean attribute that encodes + its TRUE/FALSE values as strings, or 0=false, 1=true. Note that FeatureType + could be either INT or BYTES. + """ + + # Id of the domain. Required if the domain is defined at the schema level. If + # so, then the name must be unique within the schema. + name: str = betterproto.string_field(1) + # Strings values for TRUE/FALSE. + true_value: str = betterproto.string_field(2) + false_value: str = betterproto.string_field(3)
+ + +
[docs]@dataclass +class NaturalLanguageDomain(betterproto.Message): + """Natural language text.""" + + # Name of the vocabulary associated with the NaturalLanguageDomain. When + # computing and validating stats using TFDV, tfdv.StatsOptions.vocab_paths + # should map this name to a vocabulary file. + vocabulary: str = betterproto.string_field(1) + coverage: "FeatureCoverageConstraints" = betterproto.message_field(2) + token_constraints: List["SequenceValueConstraints"] = betterproto.message_field(3) + sequence_length_constraints: "SequenceLengthConstraints" = betterproto.message_field(5) + # Specifies the location constraints as a function of the tokens specified in + # token_constraints. String tokens will be specified by S_TOKEN_, (e.g. + # S_(PAD)_) and integer tokens will be specified as I_#_ (e.g. I_123_). A_T_ + # will match any token that has not been specified in token_constraints. + # Parenthesis, +, and * are supported. _ will be escapable with a \ for + # tokens containing it (e.g. FOO\_BAR). For example, a two-sequence BERT + # model may look as follows: S_(CLS)_ A_T_+ S_(SEP)_ A_T_+ S_(SEP)_ S_(PAD)_* + # Note: Support for this field is not yet implemented. Please do not use. + # TODO(b/188095987): Remove warning once field is implemented. + location_constraint_regex: str = betterproto.string_field(4)
+ + +
[docs]@dataclass +class ImageDomain(betterproto.Message): + """Image data.""" + + # If set, at least this fraction of values should be TensorFlow supported + # images. + minimum_supported_image_fraction: float = betterproto.float_field(1) + # If set, image should have less than this value of undecoded byte size. + max_image_byte_size: int = betterproto.int64_field(2)
+ + +
[docs]@dataclass +class MIDDomain(betterproto.Message): + """Knowledge graph ID, see: https://www.wikidata.org/wiki/Property:P646""" + + pass
+ + +
[docs]@dataclass +class URLDomain(betterproto.Message): + """A URL, see: https://en.wikipedia.org/wiki/URL""" + + pass
+ + +
[docs]@dataclass +class TimeDomain(betterproto.Message): + """Time or date representation.""" + + # Expected format that contains a combination of regular characters and + # special format specifiers. Format specifiers are a subset of the strptime + # standard. + string_format: str = betterproto.string_field(1, group="format") + # Expected format of integer times. + integer_format: "TimeDomainIntegerTimeFormat" = betterproto.enum_field(2, group="format")
+ + +
[docs]@dataclass +class TimeOfDayDomain(betterproto.Message): + """Time of day, without a particular date.""" + + # Expected format that contains a combination of regular characters and + # special format specifiers. Format specifiers are a subset of the strptime + # standard. + string_format: str = betterproto.string_field(1, group="format") + # Expected format of integer times. + integer_format: "TimeOfDayDomainIntegerTimeOfDayFormat" = betterproto.enum_field( + 2, group="format" + )
+ + +
[docs]@dataclass +class FeaturePresence(betterproto.Message): + """Describes constraints on the presence of the feature in the data.""" + + # Minimum fraction of examples that have this feature. + min_fraction: float = betterproto.double_field(1) + # Minimum number of examples that have this feature. + min_count: int = betterproto.int64_field(2)
+ + +
[docs]@dataclass +class FeaturePresenceWithinGroup(betterproto.Message): + """ + Records constraints on the presence of a feature inside a "group" context + (e.g., .presence inside a group of features that define a sequence). + """ + + required: bool = betterproto.bool_field(1)
+ + +
[docs]@dataclass +class InfinityNorm(betterproto.Message): + r""" + Checks that the L-infinity norm is below a certain threshold between the + two discrete distributions. Since this is applied to a + FeatureNameStatistics, it only considers the top K. + + .. math:: + + l_{\infty}(p,q) = max_{i} | p_{i} - q_{i} | + """ + + # The InfinityNorm is in the interval [0.0, 1.0] so sensible bounds should be + # in the interval [0.0, 1.0). + threshold: float = betterproto.double_field(1)
+ + +
[docs]@dataclass +class JensenShannonDivergence(betterproto.Message): + """ + Checks that the approximate Jensen-Shannon Divergence is below a certain + threshold between the two distributions. + """ + + # The JensenShannonDivergence will be in the interval [0.0, 1.0] so sensible + # bounds should be in the interval [0.0, 1.0). + threshold: float = betterproto.double_field(1)
+ + +
[docs]@dataclass +class FeatureComparator(betterproto.Message): + infinity_norm: "InfinityNorm" = betterproto.message_field(1) + jensen_shannon_divergence: "JensenShannonDivergence" = betterproto.message_field(2)
+ + +
[docs]@dataclass +class UniqueConstraints(betterproto.Message): + """ + Checks that the number of unique values is greater than or equal to the + min, and less than or equal to the max. + """ + + min: int = betterproto.int64_field(1) + max: int = betterproto.int64_field(2)
+ + +
[docs]@dataclass +class TensorRepresentation(betterproto.Message): + """ + A TensorRepresentation captures the intent for converting columns in a + dataset to TensorFlow Tensors (or more generally, tf.CompositeTensors). + Note that one tf.CompositeTensor may consist of data from multiple columns, + for example, a N-dimensional tf.SparseTensor may need N + 1 columns to + provide the sparse indices and values. Note that the "column name" that a + TensorRepresentation needs is a string, not a Path -- it means that the + column name identifies a top-level Feature in the schema (i.e. you cannot + specify a Feature nested in a STRUCT Feature). + """ + + dense_tensor: "TensorRepresentationDenseTensor" = betterproto.message_field(1, group="kind") + varlen_sparse_tensor: "TensorRepresentationVarLenSparseTensor" = betterproto.message_field( + 2, group="kind" + ) + sparse_tensor: "TensorRepresentationSparseTensor" = betterproto.message_field(3, group="kind") + ragged_tensor: "TensorRepresentationRaggedTensor" = betterproto.message_field(4, group="kind")
+ + +
[docs]@dataclass +class TensorRepresentationDefaultValue(betterproto.Message): + float_value: float = betterproto.double_field(1, group="kind") + # Note that the data column might be of a shorter integral type. It's the + # user's responsitiblity to make sure the default value fits that type. + int_value: int = betterproto.int64_field(2, group="kind") + bytes_value: bytes = betterproto.bytes_field(3, group="kind") + # uint_value should only be used if the default value can't fit in a int64 + # (`int_value`). + uint_value: int = betterproto.uint64_field(4, group="kind")
+ + +
[docs]@dataclass +class TensorRepresentationDenseTensor(betterproto.Message): + """A tf.Tensor""" + + # Identifies the column in the dataset that provides the values of this + # Tensor. + column_name: str = betterproto.string_field(1) + # The shape of each row of the data (i.e. does not include the batch + # dimension) + shape: "FixedShape" = betterproto.message_field(2) + # If this column is missing values in a row, the default_value will be used + # to fill that row. + default_value: "TensorRepresentationDefaultValue" = betterproto.message_field(3)
+ + +
[docs]@dataclass +class TensorRepresentationVarLenSparseTensor(betterproto.Message): + """A ragged tf.SparseTensor that models nested lists.""" + + # Identifies the column in the dataset that should be converted to the + # VarLenSparseTensor. + column_name: str = betterproto.string_field(1)
+ + +
[docs]@dataclass +class TensorRepresentationSparseTensor(betterproto.Message): + """ + A tf.SparseTensor whose indices and values come from separate data columns. + This will replace Schema.sparse_feature eventually. The index columns must + be of INT type, and all the columns must co-occur and have the same valency + at the same row. + """ + + # The dense shape of the resulting SparseTensor (does not include the batch + # dimension). + dense_shape: "FixedShape" = betterproto.message_field(1) + # The columns constitute the coordinates of the values. indices_column[i][j] + # contains the coordinate of the i-th dimension of the j-th value. + index_column_names: List[str] = betterproto.string_field(2) + # The column that contains the values. + value_column_name: str = betterproto.string_field(3)
+ + +
[docs]@dataclass +class TensorRepresentationRaggedTensor(betterproto.Message): + """ + A tf.RaggedTensor that models nested lists. Currently there is no way for + the user to specify the shape of the leaf value (the innermost value tensor + of the RaggedTensor). The leaf value will always be a 1-D tensor. + """ + + # Identifies the leaf feature that provides values of the RaggedTensor. + # struct type sub fields. The first step of the path refers to a top-level + # feature in the data. The remaining steps refer to STRUCT features under the + # top-level feature, recursively. If the feature has N outer ragged lists, + # they will become the first N dimensions of the resulting RaggedTensor and + # the contents will become the flat_values. + feature_path: "Path" = betterproto.message_field(1) + # The result RaggedTensor would be of shape: [B, D_0, D_1, ..., D_N, P_0, + # P_1, ..., P_M, U_0, U_1, ..., U_P] Where the dimensions belong to different + # categories: * B: Batch size dimension * D_n: Dimensions specified by the + # nested structure specified by the value path until the leaf node. n>=1. * + # P_m: Dimensions specified by the partitions that do not define any fixed + # diomension size. m>=0. * U_0: Dimensions specified by the latest partitions + # of type uniform_row_length that can define the fixed inner shape of the + # tensor. If iterationg the partitions from the end to the beginning, these + # dimensions are defined by all the continuous uniform_row_length partitions + # present. p>=0. + partition: List["TensorRepresentationRaggedTensorPartition"] = betterproto.message_field(3) + # The data type of the ragged tensor's row partitions. This will default to + # INT64 if it is not specified. + row_partition_dtype: "TensorRepresentationRowPartitionDType" = betterproto.enum_field(2)
+ + +
[docs]@dataclass +class TensorRepresentationRaggedTensorPartition(betterproto.Message): + """Further partition of the feature values at the leaf level.""" + + # If the final element(s) of partition are uniform_row_lengths [U0, U1, ...] + # , then the result RaggedTensor will have their flat values (a dense + # tensor) being of shape [U0, U1, ...]. Otherwise, a uniform_row_length + # simply means a ragged dimension with row_lengths + # [uniform_row_length]*nrows. + uniform_row_length: int = betterproto.int64_field(1, group="kind") + # Identifies a leaf feature who share the same parent of value_feature_path + # that contains the partition row lengths. + row_length: str = betterproto.string_field(2, group="kind")
+ + +
[docs]@dataclass +class TensorRepresentationGroup(betterproto.Message): + """ + A TensorRepresentationGroup is a collection of TensorRepresentations with + names. These names may serve as identifiers when converting the dataset to + a collection of Tensors or tf.CompositeTensors. For example, given the + following group: { key: "dense_tensor" tensor_representation { + dense_tensor { column_name: "univalent_feature" shape { + dim { size: 1 } } default_value { + float_value: 0 } } } } { key: "varlen_sparse_tensor" + tensor_representation { varlen_sparse_tensor { column_name: + "multivalent_feature" } } } Then the schema is expected to have + feature "univalent_feature" and "multivalent_feature", and when a batch of + data is converted to Tensors using this TensorRepresentationGroup, the + result may be the following dict: { "dense_tensor": tf.Tensor(...), + "varlen_sparse_tensor": tf.SparseTensor(...), } + """ + + tensor_representation: Dict[str, "TensorRepresentation"] = betterproto.map_field( + 1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE + )
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/merlin_standard_lib/schema/schema.html b/review/pr-767/_modules/merlin_standard_lib/schema/schema.html new file mode 100644 index 0000000000..2e03e62541 --- /dev/null +++ b/review/pr-767/_modules/merlin_standard_lib/schema/schema.html @@ -0,0 +1,679 @@ + + + + + + merlin_standard_lib.schema.schema — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • merlin_standard_lib.schema.schema
  • +
  • +
  • +
+
+
+
+
+ +

Source code for merlin_standard_lib.schema.schema

+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import collections
+import json
+import os
+from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, TypeVar, Union
+
+from google.protobuf import json_format, text_format
+from google.protobuf.message import Message as ProtoMessage
+from merlin.models.utils import schema_utils as mm_schema_utils
+from merlin.schema import Schema as CoreSchema
+from merlin.schema import Tags, TagSet, TagsType
+from merlin.schema.io import proto_utils
+
+try:
+    from functools import cached_property  # type: ignore
+except ImportError:
+    # polyfill cached_property for python <= 3.7 (using lru_cache which was introduced in python3.2)
+    from functools import lru_cache
+
+    cached_property = lambda func: property(lru_cache()(func))  # type: ignore  # noqa
+
+import betterproto  # noqa
+from betterproto import Message as BetterProtoMessage
+
+from ..proto.schema_bp import *  # noqa
+from ..proto.schema_bp import (
+    Annotation,
+    Feature,
+    FeatureType,
+    FixedShape,
+    FixedShapeDim,
+    FloatDomain,
+    IntDomain,
+    ValueCount,
+    ValueCountList,
+    _Schema,
+)
+
+ProtoMessageType = TypeVar("ProtoMessageType", bound=BetterProtoMessage)
+
+
+def _parse_shape_and_value_count(shape, value_count) -> Dict[str, Any]:
+    output: Dict[str, Union[ValueCount, ValueCountList, FixedShape]] = {}
+    if shape:
+        output["shape"] = FixedShape([FixedShapeDim(d) for d in shape])
+
+    if value_count:
+        if isinstance(value_count, ValueCount):
+            output["value_count"] = value_count
+        elif isinstance(value_count, ValueCountList):
+            output["value_counts"] = value_count
+        else:
+            raise ValueError("Unknown value_count type.")
+
+    return output
+
+
+
[docs]class ColumnSchema(Feature): +
[docs] @classmethod + def create_categorical( + cls, + name: str, + num_items: int, + shape: Optional[Union[Tuple[int, ...], List[int]]] = None, + value_count: Optional[Union[ValueCount, ValueCountList]] = None, + min_index: int = 0, + tags: Optional[TagsType] = None, + **kwargs, + ) -> "ColumnSchema": + _tags: List[str] = [t.value for t in TagSet(tags or [])] + + extra = _parse_shape_and_value_count(shape, value_count) + int_domain = IntDomain(name=name, min=min_index, max=num_items, is_categorical=True) + _tags = list(set(_tags + [Tags.CATEGORICAL.value])) + extra["type"] = FeatureType.INT + + return cls(name=name, int_domain=int_domain, **extra, **kwargs).with_tags(_tags)
+ +
[docs] @classmethod + def create_continuous( + cls, + name: str, + is_float: bool = True, + min_value: Optional[Union[int, float]] = None, + max_value: Optional[Union[int, float]] = None, + disallow_nan: bool = False, + disallow_inf: bool = False, + is_embedding: bool = False, + shape: Optional[Union[Tuple[int, ...], List[int]]] = None, + value_count: Optional[Union[ValueCount, ValueCountList]] = None, + tags: Optional[TagsType] = None, + **kwargs, + ) -> "ColumnSchema": + _tags: List[str] = [t.value for t in TagSet(tags or [])] + + extra = _parse_shape_and_value_count(shape, value_count) + if min_value is not None and max_value is not None: + if is_float: + extra["float_domain"] = FloatDomain( + name=name, + min=float(min_value), + max=float(max_value), + disallow_nan=disallow_nan, + disallow_inf=disallow_inf, + is_embedding=is_embedding, + ) + else: + extra["int_domain"] = IntDomain( + name=name, min=int(min_value), max=int(max_value), is_categorical=False + ) + extra["type"] = FeatureType.FLOAT if is_float else FeatureType.INT + _tags = list(set(_tags + [Tags.CONTINUOUS.value])) + + return cls(name=name, **extra, **kwargs).with_tags(_tags)
+ +
[docs] def copy(self, **kwargs) -> "ColumnSchema": + return proto_utils.copy_better_proto_message(self, **kwargs)
+ +
[docs] def with_name(self, name: str): + return self.copy(name=name)
+ +
[docs] def with_tags(self, tags: TagsType) -> "ColumnSchema": + tags = [str(t) for t in tags] + output = self.copy() + if self.annotation: + output.annotation.tag = list(set(list(self.annotation.tag) + tags)) + else: + output.annotation = Annotation(tag=tags) + + return output
+ +
[docs] def with_tags_based_on_properties( + self, using_value_count=True, using_domain=True + ) -> "ColumnSchema": + extra_tags = [] + + if using_value_count and proto_utils.has_field(self, "value_count"): + extra_tags.append(str(Tags.LIST)) + + if using_domain and proto_utils.has_field(self, "int_domain"): + if self.int_domain.is_categorical: + extra_tags.append(str(Tags.CATEGORICAL)) + else: + extra_tags.append(str(Tags.CONTINUOUS)) + + if using_domain and proto_utils.has_field(self, "float_domain"): + extra_tags.append(str(Tags.CONTINUOUS)) + + return self.with_tags(extra_tags) if extra_tags else self.copy()
+ +
[docs] def with_properties(self, properties: Dict[str, Union[str, int, float]]) -> "ColumnSchema": + output = self.copy() + if output.annotation: + if len(output.annotation.extra_metadata) > 0: + output.annotation.extra_metadata[0].update(properties) + else: + output.annotation.extra_metadata = [properties] + else: + output.annotation = Annotation(extra_metadata=[properties]) + + return output
+ +
[docs] def to_proto_text(self) -> str: + from tensorflow_metadata.proto.v0 import schema_pb2 + + return proto_utils.better_proto_to_proto_text(self, schema_pb2.Feature())
+ + @property + def tags(self): + return self.annotation.tag + + @property + def properties(self) -> Dict[str, Union[str, float, int]]: + if self.annotation.extra_metadata: + properties: Dict[str, Union[str, float, int]] = self.annotation.extra_metadata[0] + + return properties + + return {} + + def _set_tags(self, tags: List[str]): + if self.annotation: + self.annotation.tag = list(set(list(self.annotation.tag) + tags)) + else: + self.annotation = Annotation(tag=tags) + + def __str__(self) -> str: + return self.name + + def __eq__(self, other) -> bool: + if not isinstance(other, ColumnSchema): + return NotImplemented + + return self.to_dict() == other.to_dict()
+ + +ColumnSchemaOrStr = Union[ColumnSchema, str] + +FilterT = TypeVar("FilterT") + + +
[docs]class Schema(_Schema): + """A collection of column schemas for a dataset.""" + + feature: List["ColumnSchema"] = betterproto.message_field(1) + +
[docs] @classmethod + def create( + cls, + column_schemas: Optional[ + Union[List[ColumnSchemaOrStr], Dict[str, ColumnSchemaOrStr]] + ] = None, + **kwargs, + ): + column_schemas = column_schemas or [] + + if isinstance(column_schemas, dict): + column_schemas = list(column_schemas.values()) + + features: List[ColumnSchema] = [] + if isinstance(column_schemas, list): + for column_schema in column_schemas: + if isinstance(column_schema, str): + features.append(ColumnSchema(column_schema)) + else: + features.append(column_schema) + else: + raise TypeError("The `column_schemas` parameter must be a list or dict.") + + return cls(feature=features, **kwargs)
+ +
[docs] def with_tags_based_on_properties(self, using_value_count=True, using_domain=True) -> "Schema": + column_schemas = [] + for column in self.column_schemas: + column_schemas.append( + column.with_tags_based_on_properties( + using_value_count=using_value_count, using_domain=using_domain + ) + ) + + return Schema(column_schemas)
+ +
[docs] def apply(self, selector) -> "Schema": + if selector and selector.names: + return self.select_by_name(selector.names) + else: + return self
+ +
[docs] def apply_inverse(self, selector) -> "Schema": + if selector: + output_schema: Schema = self - self.select_by_name(selector.names) + + return output_schema + else: + return self
+ +
[docs] def filter_columns_from_dict(self, input_dict): + filtered_dict = {} + for key, val in input_dict.items(): + if key in self.column_names: + filtered_dict[key] = val + + return filtered_dict
+ +
[docs] def select_by_type(self, to_select) -> "Schema": + if not isinstance(to_select, (list, tuple)) and not callable(to_select): + to_select = [to_select] + + def collection_filter_fn(type): + return type in to_select + + output: Schema = self._filter_column_schemas( + to_select, collection_filter_fn, lambda x: x.type + ) + + return output
+ +
[docs] def remove_by_type(self, to_remove) -> "Schema": + if not isinstance(to_remove, (list, tuple)) and not callable(to_remove): + to_remove = [to_remove] + + def collection_filter_fn(type): + return type in to_remove + + output: Schema = self._filter_column_schemas( + to_remove, collection_filter_fn, lambda x: x.type, negate=True + ) + + return output
+ +
[docs] def select_by_tag(self, to_select) -> "Schema": + if not isinstance(to_select, (list, tuple)) and not callable(to_select): + to_select = [to_select] + + if callable(to_select): + return self._filter_column_schemas(to_select, lambda x: False, lambda x: x.tags) + else: + # Schema.tags always returns a List[str] with the tag values, so if the user wants to + # filter using the Tags Enum, we need to convert those to their string value + if not isinstance(to_select, (list, tuple)): + to_select = [to_select] + + to_select = TagSet(to_select) + + def collection_filter_fn(column_names: List[str]): + return all(x in column_names for x in to_select) + + return self._filter_column_schemas( + list(to_select), collection_filter_fn, lambda x: TagSet(x.tags) + )
+ +
[docs] def remove_by_tag(self, to_remove) -> "Schema": + if not isinstance(to_remove, (list, tuple)) and not callable(to_remove): + to_remove = [to_remove] + + to_remove = TagSet(to_remove) + + def collection_filter_fn(column_tags): + return all(x in column_tags for x in to_remove) + + return self._filter_column_schemas( + list(to_remove), collection_filter_fn, lambda x: TagSet(x.tags), negate=True + )
+ +
[docs] def select_by_name(self, to_select) -> "Schema": + if not isinstance(to_select, (list, tuple)) and not callable(to_select): + to_select = [to_select] + + def collection_filter_fn(column_name): + return column_name in to_select + + output: Schema = self._filter_column_schemas( + to_select, collection_filter_fn, lambda x: x.name + ) + + return output
+ +
[docs] def remove_by_name(self, to_remove) -> "Schema": + if not isinstance(to_remove, (list, tuple)) and not callable(to_remove): + to_remove = [to_remove] + + def collection_filter_fn(column_name): + return column_name in to_remove + + return self._filter_column_schemas( + to_remove, collection_filter_fn, lambda x: x.name, negate=True + )
+ +
[docs] def map_column_schemas(self, map_fn: Callable[[ColumnSchema], ColumnSchema]) -> "Schema": + output_schemas = [] + for column_schema in self.column_schemas: + output_schemas.append(map_fn(column_schema)) + + return Schema(output_schemas)
+ +
[docs] def filter_column_schemas( + self, filter_fn: Callable[[ColumnSchema], bool], negate=False + ) -> "Schema": + selected_schemas = [] + for column_schema in self.column_schemas: + if self._check_column_schema(column_schema, filter_fn, negate=negate): + selected_schemas.append(column_schema) + + return Schema(selected_schemas)
+ + @property + def column_names(self) -> List[str]: + return [f.name for f in self.feature] + + @property + def column_schemas(self) -> Sequence[ColumnSchema]: + return self.feature + + @cached_property + def item_id_column_name(self): + item_id_col = self.select_by_tag(Tags.ITEM_ID) + if len(item_id_col.column_names) == 0: + raise ValueError("There is no column tagged as item id.") + + return item_id_col.column_names[0] + +
[docs] def from_json(self, value: Union[str, bytes]) -> "Schema": + if os.path.isfile(value): + with open(value, "rb") as f: + value = f.read() + + return super().from_json(value)
+ +
[docs] def to_proto_text(self) -> str: + from tensorflow_metadata.proto.v0 import schema_pb2 + + return proto_utils.better_proto_to_proto_text(self, schema_pb2.Schema())
+ +
[docs] def from_proto_text(self, path_or_proto_text: str) -> "Schema": + from tensorflow_metadata.proto.v0 import schema_pb2 + + return _proto_text_to_better_proto(self, path_or_proto_text, schema_pb2.Schema())
+ +
[docs] def copy(self, **kwargs) -> "Schema": + return proto_utils.copy_better_proto_message(self, **kwargs)
+ +
[docs] def add(self, other, allow_overlap=True) -> "Schema": + if isinstance(other, str): + other = Schema.create([other]) + elif isinstance(other, collections.abc.Sequence): # type: ignore + other = Schema(other) + + if not allow_overlap: + # check if there are any columns with the same name in both column groups + overlap = set(self.column_names).intersection(other.column_names) + + if overlap: + raise ValueError(f"duplicate column names found: {overlap}") + new_columns = self.column_schemas + other.column_schemas + else: + self_column_dict = {col.name: col for col in self.column_schemas} + other_column_dict = {col.name: col for col in other.column_schemas} + + new_columns = [col for col in self.column_schemas] + for key, val in other_column_dict.items(): + maybe_duplicate = self_column_dict.get(key, None) + if maybe_duplicate: + merged_col = maybe_duplicate.with_tags(val.tags) + new_columns[new_columns.index(maybe_duplicate)] = merged_col + else: + new_columns.append(val) + + return Schema(new_columns)
+ + def _filter_column_schemas( + self, + to_filter: Union[list, tuple, Callable[[FilterT], bool]], + collection_filter_fn: Callable[[FilterT], bool], + column_select_fn: Callable[[ColumnSchema], FilterT], + negate=False, + ) -> "Schema": + if isinstance(to_filter, (list, tuple)): + check_fn = collection_filter_fn + elif callable(to_filter): + check_fn = to_filter + else: + raise ValueError(f"Expected either a collection or function, got: {to_filter}.") + + selected_schemas = [] + for column_schema in self.column_schemas: + if self._check_column_schema(column_select_fn(column_schema), check_fn, negate=negate): + selected_schemas.append(column_schema) + + return Schema(selected_schemas) + + def _check_column_schema( + self, inputs: FilterT, filter_fn: Callable[[FilterT], bool], negate=False + ) -> bool: + check = filter_fn(inputs) + if check and not negate: + return True + elif not check and negate: + return True + + return False + + def __iter__(self): + return iter(self.column_schemas) + + def __len__(self): + return len(self.column_schemas) + + def __repr__(self): + return str( + [ + col_schema.to_dict(casing=betterproto.Casing.SNAKE) + for col_schema in self.column_schemas + ] + ) + + def __eq__(self, other): + if not isinstance(other, Schema) or len(self.column_schemas) != len(other.column_schemas): + return False + + return sorted(self.column_schemas, key=lambda x: x.name) == sorted( + other.column_schemas, key=lambda x: x.name + ) + + def __add__(self, other): + return self.add(other, allow_overlap=True) + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + if other is None: + return self + + if not isinstance(other, Schema): + raise TypeError(f"unsupported operand type(s) for -: 'Schema' and {type(other)}") + + result = Schema(self.column_schemas) + + for key in other.column_schemas: + if key in self.column_schemas: + result.column_schemas.pop(key, None) + + return result
+ + +def _proto_text_to_better_proto( + better_proto_message: ProtoMessageType, path_proto_text: str, message: ProtoMessage +) -> ProtoMessageType: + proto_text = path_proto_text + if os.path.isfile(proto_text): + with open(path_proto_text, "r") as f: + proto_text = f.read() + + proto = text_format.Parse(proto_text, message) + + # This is a hack because as of now we can't parse the Any representation. + # TODO: Fix this. + d = json_format.MessageToDict(proto) + for f in d["feature"]: + if "extraMetadata" in f["annotation"]: # type: ignore + extra_metadata = f["annotation"].pop("extraMetadata") # type: ignore + f["annotation"]["comment"] = [json.dumps(extra_metadata[0]["value"])] # type: ignore + json_str = json_format.MessageToJson(json_format.ParseDict(d, message)) + + return better_proto_message.__class__().from_json(json_str) + + +
[docs]def categorical_cardinalities(schema) -> Dict[str, int]: + if isinstance(schema, CoreSchema): + return mm_schema_utils.categorical_cardinalities(schema) + + outputs = {} + for col in schema: + if col.int_domain and col.int_domain.is_categorical: + outputs[col.name] = col.int_domain.max + 1 + + return outputs
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/merlin_standard_lib/utils/embedding_utils.html b/review/pr-767/_modules/merlin_standard_lib/utils/embedding_utils.html new file mode 100644 index 0000000000..e15fd3c788 --- /dev/null +++ b/review/pr-767/_modules/merlin_standard_lib/utils/embedding_utils.html @@ -0,0 +1,164 @@ + + + + + + merlin_standard_lib.utils.embedding_utils — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • merlin_standard_lib.utils.embedding_utils
  • +
  • +
  • +
+
+
+
+
+ +

Source code for merlin_standard_lib.utils.embedding_utils

+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+import math
+
+from merlin_standard_lib import Schema, categorical_cardinalities
+
+
+
[docs]def get_embedding_sizes_from_schema(schema: Schema, multiplier: float = 2.0): + cardinalities = categorical_cardinalities(schema) + + return { + key: get_embedding_size_from_cardinality(val, multiplier) + for key, val in cardinalities.items() + }
+ + +
[docs]def get_embedding_size_from_cardinality(cardinality: int, multiplier: float = 2.0): + # A rule-of-thumb from Google. + embedding_size = int(math.ceil(math.pow(cardinality, 0.25) * multiplier)) + + return embedding_size
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/config/schema.html b/review/pr-767/_modules/transformers4rec/config/schema.html new file mode 100644 index 0000000000..a42622c503 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/config/schema.html @@ -0,0 +1,201 @@ + + + + + + transformers4rec.config.schema — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.config.schema
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.config.schema

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from typing import Optional
+
+from merlin_standard_lib import Schema
+
+
+
[docs]class SchemaMixin: + REQUIRES_SCHEMA = False + +
[docs] def set_schema(self, schema=None): + self.check_schema(schema=schema) + + if schema and not getattr(self, "schema", None): + self._schema = schema + + return self
+ + @property + def schema(self) -> Optional[Schema]: + return getattr(self, "_schema", None) + + @schema.setter + def schema(self, value): + if value: + self.set_schema(value) + else: + self._schema = value + +
[docs] def check_schema(self, schema=None): + if self.REQUIRES_SCHEMA and not getattr(self, "schema", None) and not schema: + raise ValueError(f"{self.__class__.__name__} requires a schema.")
+ + def __call__(self, *args, **kwargs): + self.check_schema() + + return super().__call__(*args, **kwargs) + + def _maybe_set_schema(self, input, schema): + if input and getattr(input, "set_schema"): + input.set_schema(schema) + +
[docs] def get_item_ids_from_inputs(self, inputs): + return inputs[self.schema.item_id_column_name]
+ +
[docs] def get_padding_mask_from_item_id(self, inputs, pad_token=0): + item_id_inputs = self.get_item_ids_from_inputs(inputs) + if len(item_id_inputs.shape) != 2: + raise ValueError( + "To extract the padding mask from item id tensor " + "it is expected to have 2 dims, but it has {} dims.".format(item_id_inputs.shape) + ) + return self.get_item_ids_from_inputs(inputs) != pad_token
+ + +
[docs]def requires_schema(module): + module.REQUIRES_SCHEMA = True + + return module
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/config/trainer.html b/review/pr-767/_modules/transformers4rec/config/trainer.html new file mode 100644 index 0000000000..db24f0c45c --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/config/trainer.html @@ -0,0 +1,272 @@ + + + + + + transformers4rec.config.trainer — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.config.trainer
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.config.trainer

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from dataclasses import dataclass, field
+from typing import Optional
+
+from transformers import TFTrainingArguments, TrainingArguments
+
+
+
[docs]@dataclass +class T4RecTrainingArguments(TrainingArguments): + """ + Class that inherits HF TrainingArguments and add on top of it arguments needed for + session-based and sequential-based recommendation + + Parameters + ---------- + shuffle_buffer_size: + validate_every: Optional[int], int + Run validation set every this epoch. + -1 means no validation is used + by default -1 + eval_on_test_set: + eval_steps_on_train_set: + predict_top_k: Option[int], int + Truncate recommendation list to the highest top-K predicted items, + (do not affect evaluation metrics computation), + This parameter is specific to NextItemPredictionTask and only affects + model.predict() and model.evaluate(), which both call `Trainer.evaluation_loop`. + By default 100. + log_predictions : Optional[bool], bool + log predictions, labels and metadata features each --compute_metrics_each_n_steps + (for test set). + by default False + log_attention_weights : Optional[bool], bool + Logs the inputs and attention weights + each --eval_steps (only test set)" + by default False + learning_rate_num_cosine_cycles_by_epoch : Optional[int], int + Number of cycles for by epoch when --lr_scheduler_type = cosine_with_warmup. + The number of waves in the cosine schedule + (e.g. 0.5 is to just decrease from the max value to 0, following a half-cosine). + by default 1.25 + experiments_group: Optional[str], str + Name of the Experiments Group, for organizing job runs logged on W&B + by default "default" + """ + + max_sequence_length: Optional[int] = field( + default=None, + metadata={"help": "maximum length of sequence"}, + ) + + shuffle_buffer_size: int = field( + default=0, + metadata={ + "help": "Number of samples to keep in the buffer for shuffling." + "shuffle_buffer_size=0 means no shuffling" + }, + ) + + data_loader_engine: str = field( + default="merlin", + metadata={ + "help": "Parquet data loader engine. " + "'merlin': GPU-accelerated parquet data loader from Merlin, 'pyarrow': read whole parquet into memory." + }, + ) + + eval_on_test_set: bool = field( + default=False, + metadata={"help": "Evaluate on test set (by default, evaluates on the validation set)."}, + ) + + eval_steps_on_train_set: int = field( + default=20, + metadata={"help": "Number of steps to evaluate on train set (which is usually large)"}, + ) + + predict_top_k: int = field( + default=100, + metadata={ + "help": "Truncate recommendation list to the highest top-K predicted items (do not affect evaluation metrics computation), " + "this parameter is specific to NextItemPredictionTask. Default is 100." + }, + ) + + learning_rate_num_cosine_cycles_by_epoch: float = field( + default=1.25, + metadata={ + "help": "Number of cycles for by epoch when --learning_rate_schedule = cosine_with_warmup." + "The number of waves in the cosine schedule " + "(e.g. 0.5 is to just decrease from the max value to 0, following a half-cosine)." + }, + ) + + log_predictions: bool = field( + default=False, + metadata={ + "help": "Logs predictions, labels and metadata features each --compute_metrics_each_n_steps (for test set)." + }, + ) + + compute_metrics_each_n_steps: int = field( + default=1, + metadata={"help": "Log metrics each n steps (for train, validation and test sets)"}, + ) + + experiments_group: str = field( + default="default", + metadata={"help": "Name of the Experiments Group, for organizing job runs logged on W&B"}, + ) + + @property + def place_model_on_device(self): + """ + Override the method to allow running training on cpu + """ + if self.device.type == "cuda": + return True + return False
+ + +
[docs]class T4RecTrainingArgumentsTF(T4RecTrainingArguments, TFTrainingArguments): + """ + Prepare Training arguments for TFTrainer, + Inherit arguments from T4RecTrainingArguments and TFTrainingArguments + """ + + pass
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/config/transformer.html b/review/pr-767/_modules/transformers4rec/config/transformer.html new file mode 100644 index 0000000000..1476f17d95 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/config/transformer.html @@ -0,0 +1,770 @@ + + + + + + transformers4rec.config.transformer — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.config.transformer
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.config.transformer

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import transformers
+from merlin.models.utils.doc_utils import docstring_parameter
+from merlin.models.utils.registry import Registry
+
+transformer_registry: Registry = Registry("transformers")
+
+
+TRANSFORMER_CONFIG_PARAMETER_DOCSTRING = """        
+        d_model: int
+            The  hidden dimension of the transformer layer.
+        n_head: int
+            The number of attention heads in each transformer layer.
+        n_layer: int
+            The number of transformer layers to stack.
+        total_seq_length: int
+            The maximum sequence length.
+        hidden_act: str, optional
+            The activation function in the hidden layers.
+            By default 'gelu'
+        initializer_range: float, optional
+            The standard deviation of the `truncated_normal_initializer`
+            for initializing all transformer's weights parameters.
+            By default 0.01
+        layer_norm_eps: float, optional
+            The epsilon used by the layer normalization layers.
+            By default 0.03
+        dropout: float, optional
+            The dropout probability. By default 0.3
+        pad_token: int, optional
+            The padding token ID. By default 0
+        log_attention_weights: bool, optional
+            Whether to log attention weights. By default False
+"""
+
+
+
[docs]class T4RecConfig: + """A class responsible for setting the configuration of the transformers class + from Hugging Face and returning the corresponding T4Rec model. + """ + +
[docs] def to_huggingface_torch_model(self): + """ + Instantiate a Hugging Face transformer model based on + the configuration parameters of the class. + + Returns + ------- + transformers.PreTrainedModel + The Hugging Face transformer model. + """ + model_cls = transformers.MODEL_MAPPING[self.transformers_config_cls] + + return model_cls(self)
+ +
[docs] def to_torch_model( + self, + input_features, + *prediction_task, + task_blocks=None, + task_weights=None, + loss_reduction="mean", + **kwargs + ): + """Links the Hugging Face transformer model to the given input block and prediction tasks, + and returns a T4Rec model. + + Parameters + ---------- + input_features: torch4rec.TabularSequenceFeatures + The sequential block that represents the input features and + defines the masking strategy for training and evaluation. + prediction_task: torch4rec.PredictionTask + One or multiple prediction tasks. + task_blocks: list, optional + List of task-specific blocks that we apply on top of the HF transformer's output. + task_weights: list, optional + List of the weights to use for combining the tasks losses. + loss_reduction: str, optional + The reduction to apply to the prediction losses, possible values are: + 'none': no reduction will be applied, + 'mean': the weighted mean of the output is taken, + 'sum': the output will be summed. + By default: 'mean'. + + Returns + ------- + torch4rec.Model + The T4Rec torch model. + + Raises + ------ + ValueError + If input block or prediction task is of the wrong type. + """ + from .. import torch as torch4rec + + if not isinstance(input_features, torch4rec.TabularSequenceFeatures): + raise ValueError("`input_features` must an instance of SequentialTabularFeatures") + if not all(isinstance(t, torch4rec.PredictionTask) for t in prediction_task): + raise ValueError( + "`task` is of the wrong type, please provide one or multiple " + "instance(s) of PredictionTask" + ) + + body = torch4rec.SequentialBlock( + input_features, torch4rec.TransformerBlock(self, masking=input_features.masking) + ) + + return torch4rec.Head( + body, + *prediction_task, + task_blocks=task_blocks, + task_weights=task_weights, + loss_reduction=loss_reduction, + ).to_model(**kwargs)
+ + @property + def transformers_config_cls(self): + return self.__class__.__bases__[1] + +
[docs] @classmethod + def build(cls, *args, **kwargs): + raise NotImplementedError
+ + +
[docs]@transformer_registry.register("reformer") +class ReformerConfig(T4RecConfig, transformers.ReformerConfig): + """Subclass of T4RecConfig and transformers.ReformerConfig from Hugging Face. + It handles configuration for Reformer layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + axial_pos_shape_first_dim=4, + **kwargs + ): + """ + Creates an instance of ReformerConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + axial_pos_shape_first_dim: int, optional + The first dimension of the axial position encodings. + During training, the product of the position dims has to be equal to the sequence length. + + Returns + ------- + ReformerConfig + An instance of ReformerConfig. + """ + # To account for target positions at inference mode, we extend the maximum sequence length. + total_seq_length = total_seq_length + 2 + return cls( + hidden_size=d_model, + attention_head_size=d_model, + attn_layers=["local", "lsh"] * (n_layer // 2) if n_layer > 2 else ["local"], + num_hidden_layers=n_layer, + feed_forward_size=d_model * 4, + num_attention_heads=n_head, + hidden_act=hidden_act, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + hidden_dropout_prob=dropout, + lsh_attention_probs_dropout_prob=dropout, + pad_token_id=pad_token, + output_attentions=log_attention_weights, + max_position_embeddings=total_seq_length, + axial_pos_embds_dim=[ + d_model // 2, + d_model // 2, + ], + axial_pos_shape=[ + axial_pos_shape_first_dim, + total_seq_length // axial_pos_shape_first_dim, + ], + vocab_size=1, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("gtp2") +@docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) +class GPT2Config(T4RecConfig, transformers.GPT2Config): + """Subclass of T4RecConfig and transformers.GPT2Config from Hugging Face. + It handles configuration for GPT2 layers in the context of T4Rec models. + """ + +
[docs] @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + **kwargs + ): + """ + Creates an instance of GPT2Config with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + + Returns + ------- + GPT2Config + An instance of GPT2Config. + """ + return cls( + n_embd=d_model, + n_inner=d_model * 4, + n_layer=n_layer, + n_head=n_head, + activation_function=hidden_act, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + resid_pdrop=dropout, + embd_pdrop=dropout, + attn_pdrop=dropout, + n_positions=total_seq_length, + n_ctx=total_seq_length, + output_attentions=log_attention_weights, + vocab_size=1, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("longformer") +class LongformerConfig(T4RecConfig, transformers.LongformerConfig): + """Subclass of T4RecConfig and transformers.LongformerConfig from Hugging Face. + It handles configuration for LongformerConfig layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + **kwargs + ): + """ + Creates an instance of LongformerConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + + Returns + ------- + LongformerConfig + An instance of LongformerConfig. + """ + # To account for target positions at inference mode, we extend the maximum sequence length. + total_seq_length = total_seq_length + 2 + return cls( + hidden_size=d_model, + num_hidden_layers=n_layer, + num_attention_heads=n_head, + hidden_act=hidden_act, + attention_window=total_seq_length, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + dropout=dropout, + pad_token_id=pad_token, + output_attentions=log_attention_weights, + vocab_size=1, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("electra") +class ElectraConfig(T4RecConfig, transformers.ElectraConfig): + """Subclass of T4RecConfig and transformers.ElectraConfig from Hugging Face. + It handles configuration for ElectraConfig layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + **kwargs + ): + """ + Creates an instance of ElectraConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + + Returns + ------- + ElectraConfig + An instance of ElectraConfig. + """ + # To account for target positions at inference mode, we extend the maximum sequence length. + total_seq_length = total_seq_length + 2 + return cls( + hidden_size=d_model, + embedding_size=d_model, + num_hidden_layers=n_layer, + num_attention_heads=n_head, + intermediate_size=d_model * 4, + hidden_act=hidden_act, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + hidden_dropout_prob=dropout, + max_position_embeddings=total_seq_length, + pad_token_id=pad_token, + output_attentions=log_attention_weights, + vocab_size=1, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("albert") +class AlbertConfig(T4RecConfig, transformers.AlbertConfig): + """Subclass of T4RecConfig and transformers.AlbertConfig from Hugging Face. + It handles configuration for AlbertConfig layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + **kwargs + ): + """ + Creates an instance of AlbertConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + + Returns + ------- + AlbertConfig + An instance of AlbertConfig. + """ + # To account for target positions at inference mode, we extend the maximum sequence length. + total_seq_length = total_seq_length + 2 + return cls( + hidden_size=d_model, + num_attention_heads=n_head, + num_hidden_layers=n_layer, + hidden_act=hidden_act, + intermediate_size=d_model * 4, + hidden_dropout_prob=dropout, + attention_probs_dropout_prob=dropout, + max_position_embeddings=total_seq_length, + embedding_size=d_model, # should be same as dimension of the input to ALBERT + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + output_attentions=log_attention_weights, + vocab_size=1, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("xlnet") +@docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) +class XLNetConfig(T4RecConfig, transformers.XLNetConfig): + """Subclass of T4RecConfig and transformers.XLNetConfig from Hugging Face. + It handles configuration for XLNetConfig layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length=None, + attn_type="bi", + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + mem_len=1, + **kwargs + ): + """ + Creates an instance of XLNetConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + mem_len: int, + The number of tokens to be cached. Pre-computed key/value pairs + from a previous forward pass are stored and won't be re-computed. + This parameter is especially useful for long sequence modeling where + different batches may truncate the entire sequence. + Tasks like user-aware recommendation could benefit from this feature. + By default, this parameter is set to 1, which means no caching is used. + + Returns + ------- + XLNetConfig + An instance of XLNetConfig. + """ + return cls( + d_model=d_model, + d_inner=d_model * 4, + n_layer=n_layer, + n_head=n_head, + attn_type=attn_type, + ff_activation=hidden_act, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + dropout=dropout, + pad_token_id=pad_token, + output_attentions=log_attention_weights, + vocab_size=1, + mem_len=mem_len, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("bert") +class BertConfig(T4RecConfig, transformers.BertConfig): + """Subclass of T4RecConfig and transformers.BertConfig from Hugging Face. + It handles configuration for BertConfig layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + **kwargs + ): + """ + Creates an instance of BertConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + + Returns + ------- + BertConfig + An instance of BertConfig. + """ + # To account for target positions at inference mode, we extend the maximum sequence length. + total_seq_length = total_seq_length + 2 + return cls( + hidden_size=d_model, + num_hidden_layers=n_layer, + num_attention_heads=n_head, + hidden_act=hidden_act, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + dropout=dropout, + pad_token_id=pad_token, + output_attentions=log_attention_weights, + max_position_embeddings=total_seq_length, + vocab_size=1, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("roberta") +class RobertaConfig(T4RecConfig, transformers.RobertaConfig): + """Subclass of T4RecConfig and transformers.RobertaConfig from Hugging Face. + It handles configuration for RobertaConfig layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + **kwargs + ): + """ + Creates an instance of RobertaConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + + Returns + ------- + RobertaConfig + An instance of RobertaConfig. + """ + # To account for target positions at inference mode, we extend the maximum sequence length. + total_seq_length = total_seq_length + 2 + return cls( + hidden_size=d_model, + num_hidden_layers=n_layer, + num_attention_heads=n_head, + hidden_act=hidden_act, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + dropout=dropout, + pad_token_id=pad_token, + output_attentions=log_attention_weights, + max_position_embeddings=total_seq_length, + vocab_size=1, + **kwargs, + )
+ + +
[docs]@transformer_registry.register("transfo-xl") +class TransfoXLConfig(T4RecConfig, transformers.TransfoXLConfig): + """Subclass of T4RecConfig and transformers. TransfoXLConfig from Hugging Face. + It handles configuration for TransfoXLConfig layers in the context of T4Rec models. + """ + +
[docs] @docstring_parameter(transformer_cfg_parameters=TRANSFORMER_CONFIG_PARAMETER_DOCSTRING) + @classmethod + def build( + cls, + d_model, + n_head, + n_layer, + total_seq_length, + hidden_act="gelu", + initializer_range=0.01, + layer_norm_eps=0.03, + dropout=0.3, + pad_token=0, + log_attention_weights=False, + **kwargs + ): + """ + Creates an instance of TransfoXLConfig with the given parameters. + + Parameters + ---------- + {transformer_cfg_parameters} + + Returns + ------- + TransfoXLConfig + An instance of TransfoXLConfig. + """ + return cls( + d_model=d_model, + d_embed=d_model, + n_layer=n_layer, + n_head=n_head, + d_inner=d_model * 4, + hidden_act=hidden_act, + untie_r=True, + attn_type=0, + initializer_range=initializer_range, + layer_norm_eps=layer_norm_eps, + dropout=dropout, + pad_token_id=pad_token, + output_attentions=log_attention_weights, + vocab_size=1, # As the input_embeds will be fed in the forward function, limits the memory reserved by the internal input embedding table, which will not be used + mem_len=1, # We do not use mems, because we feed the full sequence to the Transformer models and not sliding segments (which is useful for the long sequences in NLP. As setting mem_len to 0 leads to NaN in loss, we set it to one, to minimize the computing overhead) + div_val=1, # Disables adaptative input (embeddings), because the embeddings are managed by TabularFeatures + **kwargs, + )
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/block/base.html b/review/pr-767/_modules/transformers4rec/torch/block/base.html new file mode 100644 index 0000000000..3bf96409f0 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/block/base.html @@ -0,0 +1,567 @@ + + + + + + transformers4rec.torch.block.base — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.block.base
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.block.base

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import abc
+import inspect
+import logging
+from collections import OrderedDict
+from typing import List, Optional, Union
+
+import torch
+from merlin.models.utils.misc_utils import filter_kwargs
+from torch.nn import Module
+
+from ..utils import torch_utils
+
+LOG = logging.getLogger("transformers4rec")
+
+
+
[docs]class BlockBase(torch_utils.OutputSizeMixin, torch.nn.Module, metaclass=abc.ABCMeta): + """A subclass of PyTorch's torch.nn.Module, providing additional functionality + for dealing with automatic setting of input/output dimensions of neural networks layers. + Specifically, It implements the 'OutputSizeMixin' for managing output sizes. + """ + +
[docs] def to_model(self, prediction_task_or_head, inputs=None, **kwargs): + """Converts the BlockBase instance into a T4Rec model by attaching it to + attaching a 'Head' or 'PredictionTask'. + + Parameters + ---------- + prediction_task_or_head : Union[PredictionTask, Head] + A PredictionTask or Head instance to attach to this block. + inputs :InputBlock, optional + The input block representing input features. + By default None + + Raises + ------ + ValueError + If prediction_task_or_head is neither a Head nor a PredictionTask. + """ + from ..model.base import Head, Model, PredictionTask + + if isinstance(prediction_task_or_head, PredictionTask): + head = prediction_task_or_head.to_head(self, inputs=inputs, **kwargs) + elif isinstance(prediction_task_or_head, Head): + head = prediction_task_or_head + else: + raise ValueError( + "`prediction_task_or_head` needs to be a `Head` or `PredictionTask` " + f"found: {type(prediction_task_or_head)}" + ) + + return Model(head, **kwargs)
+ +
[docs] def as_tabular(self, name=None): + """Converts the output of the block into a dictionary, keyed by the + provided name + + Parameters + ---------- + name : str, optional + The output name, if not provided, uses the name of the block class. + by default None + """ + from ..tabular.base import AsTabular + + if not name: + name = self.name + + return SequentialBlock(self, AsTabular(name))
+ + +
[docs]class Block(BlockBase): + """Wraps a PyTorch module, allowing it to be used as a block in a T4Rec model. + It carries the module and its expected output size. + + Parameters + ---------- + module: torch.nn.Module + The PyTorch module to be wrapped in this block. + output_size: Union[List[int], torch.Size] + The expected output size of the module. + + """ + + def __init__(self, module: torch.nn.Module, output_size: Union[List[int], torch.Size]): + super().__init__() + self.module = module + self._output_size = output_size + +
[docs] def forward(self, inputs, **kwargs): + return self.module(inputs, **kwargs)
+ +
[docs] def forward_output_size(self, input_size): + """ + Calculates the output size of the tensor(s) returned by the forward pass, + given the input size. + + Parameters + ---------- + input_size: Union[List[int], torch.Size] + The size of the input tensor(s) to the module. + + Returns + ------- + Union[List[int], torch.Size] + The size of the output from the module. + """ + if self._output_size[0] is None: + batch_size = torch_utils.calculate_batch_size_from_input_size(input_size) + + return [batch_size] + self._output_size[1:] + + return self._output_size
+ + +
[docs]class SequentialBlock(BlockBase, torch.nn.Sequential): + """Extends the module torch.nn.Sequential. It's used for creating + a sequence of layers or blocks in a T4Rec model. The modules + will be applied to inputs in the order they are passed in the constructor. + + Parameters + ---------- + *args: + The list of PyTorch modules. + output_size : Union[List[int], torch.Size], optional + The expected output size from the last layer in the sequential block + By default None + + """ + + def __init__(self, *args, output_size: Union[List[int], torch.Size] = None): + from transformers4rec.torch import TabularSequenceFeatures, TransformerBlock + + if isinstance(args[0], TabularSequenceFeatures) and any( + isinstance(arg, TransformerBlock) for arg in args + ): + masking = args[0].masking + for arg in args: + if isinstance(arg, TransformerBlock): + if arg.masking != masking: + LOG.warning( + "Masking is set in the input module but not in the " + "TransformerBlock, provide this through the masking argument" + ) + + super().__init__() + self._static_output_size = output_size + self.input_size = None + + if len(args) == 1 and isinstance(args[0], OrderedDict): + last = None + for idx, key, module in enumerate(args[0].items()): # type: ignore + self.add_module_and_maybe_build(key, module, last, idx) # type: ignore + last = module # type: ignore + else: + if len(args) == 1 and isinstance(args[0], list): + args = args[0] # type: ignore + last = None + for idx, module in enumerate(args): + last = self.add_module_and_maybe_build(str(idx), module, last, idx) + + @property + def inputs(self): + from transformers4rec.torch import TabularFeatures, TabularSequenceFeatures + + first = list(self)[0] + if isinstance(first, (TabularSequenceFeatures, TabularFeatures)): + return first + +
[docs] def add_module(self, name: str, module: Optional[Module]) -> None: + """ + Adds a PyTorch module to the sequential block. If a list of strings is provided, + a `FilterFeatures` block gets added to the sequential block. + + Parameters + ---------- + name : str + The name of the child module. The child module can be accessed + from this module using the given name. + module : Optional[Union[List[str], Module]] + The child module to be added to the module. + """ + from ..tabular.base import FilterFeatures + + if isinstance(module, list): + module = FilterFeatures(module) + super().add_module(name, module)
+ +
[docs] def add_module_and_maybe_build(self, name: str, module, parent, idx) -> torch.nn.Module: + """Checks if a module needs to be built and adds it to the sequential block. + + Parameters + ---------- + name : str + The name of the child module. + module : torch.nn.Module + The child module to be added to the sequential block. + parent : torch.nn.Module + The parent module. + idx : int + The index of the current module in the sequential block. + """ + # Check if module needs to be built + if getattr(parent, "output_size", None) and getattr(module, "build", None): + module = module.build(parent.output_size()) + + if idx == 0: + self.input_size = getattr(module, "input_size", None) + + self.add_module(name, module) + + return module
+ + def __rrshift__(self, other): + return right_shift_block(self, other) + + def __rshift__(self, other): + # pylint: disable=arguments-out-of-order + return right_shift_block(other, self) + +
[docs] def forward(self, input, training=False, testing=False, **kwargs): + """Applies the module's layers sequentially to the input block. + + Parameters + ---------- + input : tensor + The input to the block. + training : bool, optional + Whether the block is in training mode. The default is False. + testing : bool, optional + Whether the block is in testing mode. The default is False. + + """ + for i, module in enumerate(self): + if i == len(self) - 1: + filtered_kwargs = filter_kwargs(kwargs, module, cascade_kwargs_if_possible=True) + input = module(input, **filtered_kwargs) + + elif "training" in inspect.signature(module.forward).parameters: + if "testing" in inspect.signature(module.forward).parameters: + input = module(input, training=training, testing=testing) + else: + input = module(input, training=training) + else: + input = module(input) + + return input
+ +
[docs] def build(self, input_size, schema=None, **kwargs): + """Builds the layers of the sequential block given the input size. + + Parameters + ---------- + input_size : Union[List[int], torch.Size] + The size of the input tensor(s). + schema : Schema, optional + The schema of the inputs features, by default None + + Returns + ------- + SequentialBlock + The built sequential block. + """ + output_size = input_size + for module in self: + if not hasattr(module, "build"): + break + module.build(output_size, schema=schema) + output_size = module.output_size() + + return super(SequentialBlock, self).build(input_size, schema=None, **kwargs)
+ +
[docs] def as_tabular(self, name=None): + """Converts the output of the block into a dictionary, keyed by the + provided name + + Parameters + ---------- + name : str, optional + The output name, if not provided, uses the name of the block class. + by default None + """ + from transformers4rec.torch import AsTabular + + if not name: + name = self.name + + return SequentialBlock(self, AsTabular(name))
+ + def __add__(self, other): + from ..tabular.base import merge_tabular + + return merge_tabular(self, other) + +
[docs] def forward_output_size(self, input_size): + """ + Calculates the output size of the tensor(s) returned by the forward pass, + given the input size. + + Parameters + ---------- + input_size: Union[List[int], torch.Size] + The size of the input tensor(s) to the module. + + Returns + ------- + Union[List[int], torch.Size] + The size of the output from the module. + """ + if self._static_output_size: + return self._static_output_size + + x = input_size + for module in list(self): + if getattr(module, "output_size", None): + x = module.output_size(x) + else: + # TODO log warning here + return None + + return x
+ +
[docs] @staticmethod + def get_children_by_class_name(parent, *class_name): + children = [] + + def add_if_class_name_matches(to_check): + if to_check.__class__.__name__ in class_name: + children.append(to_check) + + for child in parent: + if getattr(child, "merge_values", None): + for to_merge in child.merge_values: + add_if_class_name_matches(to_merge) + + add_if_class_name_matches(child) + + return children
+ + +
[docs]def build_blocks(*modules): + """Builds a SequentialBlock from a list of PyTorch modules. + + Parameters + ---------- + *modules : List[torch.nn.Module] + List containing PyTorch modules. + + Returns + ------- + A SequentialBlock instance created from the provided modules. + """ + return list(SequentialBlock(*modules))
+ + +
[docs]class BuildableBlock(abc.ABC): + """ + Abstract base class for buildable blocks. + Subclasses of BuildableBlock must implement the `build` method + """ + +
[docs] @abc.abstractmethod + def build(self, input_size) -> BlockBase: + raise NotImplementedError
+ + def __rrshift__(self, other): + return right_shift_block(self, other) + +
[docs] def to_module(self, shape_or_module): + shape = shape_or_module + if isinstance(shape_or_module, torch.nn.Module): + shape = getattr(shape_or_module, "output_size", None) + if shape: + shape = shape() + + return self.build(shape)
+ + +
[docs]def right_shift_block(self, other): + from ..tabular.base import FilterFeatures + + if isinstance(other, list): + left_side = [FilterFeatures(other)] + else: + left_side = list(other) if isinstance(other, SequentialBlock) else [other] + right_side = list(self) if isinstance(self, SequentialBlock) else [self] + + # Maybe build right-side + if hasattr(left_side[-1], "output_size") and left_side[-1].output_size(): + _right_side = [] + x = left_side[-1].output_size() + for module in right_side: + if getattr(module, "build", None): + if "parents" in inspect.signature(module.build).parameters: + build = module.build(x, left_side) + else: + build = module.build(x) + if build: + module = build + x = module.output_size() if hasattr(module, "output_size") else None + _right_side.append(module) + right_side = _right_side + + sequential = left_side + right_side + + need_moving_to_gpu = False + if isinstance(self, torch.nn.Module): + need_moving_to_gpu = need_moving_to_gpu or torch_utils.check_gpu(self) + if isinstance(other, torch.nn.Module): + need_moving_to_gpu = need_moving_to_gpu or torch_utils.check_gpu(other) + + out = SequentialBlock(*sequential) + if getattr(left_side[-1], "input_size", None) and left_side[-1].input_size: + out.input_size = left_side[-1].input_size + + if need_moving_to_gpu: + out.to("cuda") + + return out
+ + +BlockType = Union[BlockBase, BuildableBlock] +BlockOrModule = Union[BlockBase, BuildableBlock, torch.nn.Module] +
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/block/mlp.html b/review/pr-767/_modules/transformers4rec/torch/block/mlp.html new file mode 100644 index 0000000000..74ccee11ea --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/block/mlp.html @@ -0,0 +1,279 @@ + + + + + + transformers4rec.torch.block.mlp — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.block.mlp
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.block.mlp

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from typing import List, Optional, Union
+
+import torch
+
+from .base import BuildableBlock, SequentialBlock
+
+
+
[docs]class MLPBlock(BuildableBlock): + """Defines Multi-Layer Perceptron (MLP) Block by stacking + multiple DenseBlock instances. + + Parameters + ---------- + dimensions : int or list of int + The dimensions of the layers in the MLP. + If an integer is provided, a single layer MLP is created. + If a list is provided, it must contain the size of each layer in order. + activation : optional + The activation function to apply after each layer. + By default `torch.nn.ReLU`. + use_bias : bool, optional + Whether to add a bias term to the dense layers. + by default True + dropout : float, optional + The dropout rate to apply after each layer, by default None + normalization : str, optional + The normalization to apply after each layer, by default None + filter_features : List[str], optional + List of features to select from the input., by default None + """ + + def __init__( + self, + dimensions, + activation=torch.nn.ReLU, + use_bias: bool = True, + dropout: float = None, + normalization: str = None, + filter_features=None, + ) -> None: + super().__init__() + + if isinstance(dimensions, int): + dimensions = [dimensions] + + self.normalization = normalization + self.dropout = dropout + self.filter_features = filter_features + self.use_bias = use_bias + self.activation = activation + self.dimensions = dimensions + +
[docs] def build(self, input_shape) -> SequentialBlock: + layer_input_sizes = list(input_shape[-1:]) + list(self.dimensions[:-1]) + layer_output_sizes = self.dimensions + sequential = [ + DenseBlock( + input_shape, + input_size, + output_size, + activation=self.activation, + use_bias=self.use_bias, + dropout=self.dropout, + normalization=self.normalization, + ) + for input_size, output_size in zip(layer_input_sizes, layer_output_sizes) + ] + + output = SequentialBlock(*sequential) + output.input_size = input_shape + + return output
+ + +
[docs]class DenseBlock(SequentialBlock): + """ + A buildable dense Block to represent a fully connected layer. + + Parameters + ---------- + input_shape : Union[List[int], torch.Size] + The shape of the input tensor. + + in_features : int + Number of input features. + + out_features : int + Number of output features. + + activation : torch.nn.Module, optional + The activation function to apply after the linear layer. + By default `torch.nn.ReLU`. + + use_bias : bool, optional + Whether to use bias in the layer. + By default True. + + dropout : float, optional + The dropout rate to apply after the dense layer, if any. + By default is None. + + normalization : str, optional + The type of normalization to apply after the dense layer. + Only 'batch_norm' is supported. + By default is None. + """ + + def __init__( + self, + input_shape: Union[List[int], torch.Size], + in_features: int, + out_features: int, + activation=torch.nn.ReLU, + use_bias: bool = True, + dropout: Optional[float] = None, + normalization=None, + ): + args: List[torch.nn.Module] = [torch.nn.Linear(in_features, out_features, bias=use_bias)] + if activation: + args.append(activation(inplace=True)) + if normalization: + if normalization == "batch_norm": + args.append(torch.nn.BatchNorm1d(out_features)) + if dropout: + args.append(torch.nn.Dropout(dropout)) + + super().__init__(*args) + self._input_shape = input_shape + self._output_size = out_features + + def _get_name(self): + return "DenseBlock" + +
[docs] def forward_output_size(self, input_size): + return torch.Size(list(input_size[:-1]) + [self._output_size])
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/block/transformer.html b/review/pr-767/_modules/transformers4rec/torch/block/transformer.html new file mode 100644 index 0000000000..0d6abe9aab --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/block/transformer.html @@ -0,0 +1,337 @@ + + + + + + transformers4rec.torch.block.transformer — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.block.transformer
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.block.transformer

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import inspect
+from typing import Any, Dict, Optional, Type, Union
+
+import torch
+import transformers
+from transformers import GPT2Model, PretrainedConfig, PreTrainedModel
+
+from ...config.transformer import T4RecConfig, transformer_registry
+from ..masking import MaskSequence
+from ..utils.torch_utils import MappingTransformerMasking
+from .base import BlockBase
+
+TransformerBody = Union[PreTrainedModel, PretrainedConfig]
+
+
+
[docs]class TransformerPrepare(torch.nn.Module): + """ + Base class to prepare additional inputs to the forward call of + the HF transformer layer. + + Parameters + ---------- + transformer : TransformerBody + The Transformer module. + masking : Optional[MaskSequence] + Masking block used to for masking input sequences. + By default None. + """ + + def __init__(self, transformer: TransformerBody, masking: Optional[MaskSequence] = None): + super().__init__() + self.transformer = transformer + self.masking = masking + +
[docs] def forward(self, inputs_embeds) -> Dict[str, Any]: + raise NotImplementedError()
+ + +
[docs]class GPT2Prepare(TransformerPrepare): + """TransformerPrepare module for GPT-2. + + This class extends the inputs for GPT-2 with a + triangular causal mask to the inputs. + """ + +
[docs] def forward(self, inputs_embeds) -> Dict[str, Any]: + seq_len = inputs_embeds.shape[1] + # head_mask has shape n_layer x batch x n_heads x N x N + head_mask = ( + torch.tril( + torch.ones((seq_len, seq_len), dtype=torch.uint8, device=inputs_embeds.device) + ) + .view(1, 1, 1, seq_len, seq_len) + .repeat(self.transformer.config.num_hidden_layers, 1, 1, 1, 1) + ) + + return {"inputs_embeds": inputs_embeds, "head_mask": head_mask}
+ + +
[docs]class TransformerBlock(BlockBase): + """ + Class to support HF Transformers for session-based and sequential-based recommendation models. + + Parameters + ---------- + transformer: TransformerBody + The T4RecConfig or a pre-trained HF object related to specific transformer architecture. + masking: + Needed when masking is applied on the inputs. + """ + + TRANSFORMER_TO_PREPARE: Dict[Type[PreTrainedModel], Type[TransformerPrepare]] = { + GPT2Model: GPT2Prepare + } + + def __init__( + self, + transformer: TransformerBody, + masking: Optional[MaskSequence] = None, + prepare_module: Optional[Type[TransformerPrepare]] = None, + output_fn=lambda model_outputs: model_outputs[0], + ): + super().__init__() + + self.transformer: PreTrainedModel + if isinstance(transformer, T4RecConfig): + self.transformer = transformer.to_huggingface_torch_model() + elif isinstance(transformer, PretrainedConfig): + model_cls = transformers.MODEL_MAPPING[transformer.__class__] + self.transformer = model_cls(transformer) + else: + self.transformer = transformer + + if masking: + # check for the four default masking + if (masking.__class__ in MappingTransformerMasking.DEFAULT_MASKING) and ( + masking.__class__ + not in getattr( + MappingTransformerMasking, + self.transformer.config_class.__name__, # type: ignore + [masking.__class__], + ) + ): + raise ValueError( + f"{masking.__class__.__name__} is not supported by: " # type: ignore + f"the {self.transformer.config_class.__name__} architecture" # type: ignore + ) + + required = list(masking.transformer_required_arguments().keys()) + check = all( + param in inspect.signature(self.transformer.forward).parameters + for param in required + ) + if not check: + raise ValueError( + f"{masking.__class__.__name__} requires the parameters: " + f"{', '.join(required)} " + f"in the {type(self.transformer)} signature" + ) + + self.masking = masking + self.prepare_module: Optional[TransformerPrepare] = None + if not prepare_module and type(self.transformer) in self.TRANSFORMER_TO_PREPARE: + prepare_module = self.TRANSFORMER_TO_PREPARE[type(self.transformer)] + if prepare_module: + self.prepare_module = prepare_module(self.transformer, self.masking) + self.output_fn = output_fn + +
[docs] @classmethod + def from_registry( + cls, + transformer: str, + d_model: int, + n_head: int, + n_layer: int, + total_seq_length: int, + masking: Optional[MaskSequence] = None, + ): + """ + Load the HF transformer architecture based on its name + + Parameters + ---------- + transformer: str + Name of the Transformer to use. Possible values are : + ["reformer", "gtp2", "longformer", "electra", "albert", "xlnet"] + d_model: int + size of hidden states for Transformers + n_head: + Number of attention heads for Transformers + n_layer: int + Number of layers for RNNs and Transformers" + total_seq_length: int + The maximum sequence length + """ + _transformer = transformer_registry.parse(transformer).build( + d_model=d_model, + n_head=n_head, + n_layer=n_layer, + total_seq_length=total_seq_length, + ) + + return cls(_transformer, masking)
+ +
[docs] def forward(self, inputs_embeds, **kwargs): + """ + Transformer Models + """ + transformer_kwargs = {"inputs_embeds": inputs_embeds} + if self.prepare_module: + transformer_kwargs = self.prepare_module(inputs_embeds) + if self.masking: + masking_kwargs = self.masking.transformer_arguments + if masking_kwargs: + transformer_kwargs.update(masking_kwargs) + + filtered_transformer_kwargs = {} + for param in inspect.signature(self.transformer.forward).parameters: + if param in transformer_kwargs: + filtered_transformer_kwargs[param] = transformer_kwargs[param] + model_outputs = self.transformer(**filtered_transformer_kwargs) + outputs = self.output_fn(model_outputs) + + # TODO: store the attention outputs for meta-data logging + return outputs
+ + def _get_name(self): + return "TansformerBlock" + +
[docs] def forward_output_size(self, input_size): + assert len(input_size) == 3 + return torch.Size([input_size[0], input_size[1], self.transformer.config.hidden_size])
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/features/base.html b/review/pr-767/_modules/transformers4rec/torch/features/base.html new file mode 100644 index 0000000000..935ce7bdcd --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/features/base.html @@ -0,0 +1,152 @@ + + + + + + transformers4rec.torch.features.base — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.features.base
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.features.base

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from abc import ABC
+
+from ..tabular.base import TabularBlock
+
+
+
[docs]class InputBlock(TabularBlock, ABC): + pass
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/features/continuous.html b/review/pr-767/_modules/transformers4rec/torch/features/continuous.html new file mode 100644 index 0000000000..a01a65495f --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/features/continuous.html @@ -0,0 +1,197 @@ + + + + + + transformers4rec.torch.features.continuous — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.features.continuous
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.features.continuous

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from typing import List, Optional
+
+import torch
+from merlin.models.utils.doc_utils import docstring_parameter
+
+from merlin_standard_lib import Schema
+
+from ..tabular.base import (
+    TABULAR_MODULE_PARAMS_DOCSTRING,
+    FilterFeatures,
+    TabularAggregationType,
+    TabularTransformationType,
+)
+from .base import InputBlock
+
+
+
[docs]@docstring_parameter(tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING) +class ContinuousFeatures(InputBlock): + """Input block for continuous features. + + Parameters + ---------- + features: List[str] + List of continuous features to include in this module. + {tabular_module_parameters} + """ + + def __init__( + self, + features: List[str], + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + schema: Optional[Schema] = None, + **kwargs + ): + super().__init__(aggregation=aggregation, pre=pre, post=post, schema=schema) + self.filter_features = FilterFeatures(features) + +
[docs] @classmethod + def from_features(cls, features, **kwargs): + return cls(features, **kwargs)
+ +
[docs] def forward(self, inputs, **kwargs): + cont_features = self.filter_features(inputs) + cont_features = {k: v.unsqueeze(-1) for k, v in cont_features.items()} + return cont_features
+ +
[docs] def forward_output_size(self, input_sizes): + cont_features_sizes = self.filter_features.forward_output_size(input_sizes) + cont_features_sizes = {k: torch.Size(list(v) + [1]) for k, v in cont_features_sizes.items()} + return cont_features_sizes
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/features/embedding.html b/review/pr-767/_modules/transformers4rec/torch/features/embedding.html new file mode 100644 index 0000000000..806f97de6d --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/features/embedding.html @@ -0,0 +1,866 @@ + + + + + + transformers4rec.torch.features.embedding — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.features.embedding
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.features.embedding

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from functools import partial
+from typing import Any, Callable, Dict, List, Optional, Text, Union
+
+import torch
+from merlin.models.utils.doc_utils import docstring_parameter
+from merlin.schema import Tags, TagsType
+
+from merlin_standard_lib import Schema, categorical_cardinalities
+from merlin_standard_lib.utils.embedding_utils import get_embedding_sizes_from_schema
+
+from ..block.base import SequentialBlock
+from ..tabular.base import (
+    TABULAR_MODULE_PARAMS_DOCSTRING,
+    FilterFeatures,
+    TabularAggregationType,
+    TabularTransformation,
+    TabularTransformationType,
+)
+from ..utils.torch_utils import calculate_batch_size_from_input_size, get_output_sizes_from_schema
+from .base import InputBlock
+
+EMBEDDING_FEATURES_PARAMS_DOCSTRING = """
+    feature_config: Dict[str, FeatureConfig]
+        This specifies what TableConfig to use for each feature. For shared embeddings, the same
+        TableConfig can be used for multiple features.
+    item_id: str, optional
+        The name of the feature that's used for the item_id.
+"""
+
+
+
[docs]@docstring_parameter( + tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING, + embedding_features_parameters=EMBEDDING_FEATURES_PARAMS_DOCSTRING, +) +class EmbeddingFeatures(InputBlock): + """Input block for embedding-lookups for categorical features. + + For multi-hot features, the embeddings will be aggregated into a single tensor using the mean. + + Parameters + ---------- + {embedding_features_parameters} + {tabular_module_parameters} + """ + + def __init__( + self, + feature_config: Dict[str, "FeatureConfig"], + item_id: Optional[str] = None, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + schema: Optional[Schema] = None, + ): + super().__init__(pre=pre, post=post, aggregation=aggregation, schema=schema) + self.item_id = item_id + self.feature_config = feature_config + self.filter_features = FilterFeatures(list(feature_config.keys())) + + embedding_tables = {} + features_dim = {} + tables: Dict[str, TableConfig] = {} + for name, feature in self.feature_config.items(): + table: TableConfig = feature.table + features_dim[name] = table.dim + if name not in tables: + tables[name] = table + + for name, table in tables.items(): + embedding_tables[name] = self.table_to_embedding_module(table) + + self.embedding_tables = torch.nn.ModuleDict(embedding_tables) + + @property + def item_embedding_table(self): + assert self.item_id is not None + + return self.embedding_tables[self.item_id] + +
[docs] def table_to_embedding_module(self, table: "TableConfig") -> torch.nn.Module: + embedding_table = EmbeddingBagWrapper(table.vocabulary_size, table.dim, mode=table.combiner) + + if table.initializer is not None: + table.initializer(embedding_table.weight) + return embedding_table
+ +
[docs] @classmethod + def from_schema( # type: ignore + cls, + schema: Schema, + embedding_dims: Optional[Dict[str, int]] = None, + embedding_dim_default: int = 64, + infer_embedding_sizes: bool = False, + infer_embedding_sizes_multiplier: float = 2.0, + embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] = None, + combiner: str = "mean", + tags: Optional[TagsType] = None, + item_id: Optional[str] = None, + automatic_build: bool = True, + max_sequence_length: Optional[int] = None, + aggregation=None, + pre=None, + post=None, + **kwargs, + ) -> Optional["EmbeddingFeatures"]: + """Instantitates ``EmbeddingFeatures`` from a ``DatasetSchema``. + + Parameters + ---------- + schema : DatasetSchema + Dataset schema + embedding_dims : Optional[Dict[str, int]], optional + The dimension of the embedding table for each feature (key), + by default None by default None + default_embedding_dim : Optional[int], optional + Default dimension of the embedding table, when the feature is not found + in ``default_soft_embedding_dim``, by default 64 + infer_embedding_sizes : bool, optional + Automatically defines the embedding dimension from the + feature cardinality in the schema, + by default False + infer_embedding_sizes_multiplier: Optional[int], by default 2.0 + multiplier used by the heuristic to infer the embedding dimension from + its cardinality. Generally reasonable values range between 2.0 and 10.0 + embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] + Dict where keys are feature names and values are callable to initialize embedding tables + combiner : Optional[str], optional + Feature aggregation option, by default "mean" + tags : Optional[Union[DefaultTags, list, str]], optional + Tags to filter columns, by default None + item_id : Optional[str], optional + Name of the item id column (feature), by default None + automatic_build : bool, optional + Automatically infers input size from features, by default True + max_sequence_length : Optional[int], optional + Maximum sequence length for list features,, by default None + + Returns + ------- + Optional[EmbeddingFeatures] + Returns the ``EmbeddingFeatures`` for the dataset schema + """ + # TODO: propagate item-id from ITEM_ID tag + + if tags: + schema = schema.select_by_tag(tags) + + _item_id = schema.select_by_tag(Tags.ITEM_ID) + if not item_id and len(_item_id) > 0: + if len(_item_id) > 1: + raise ValueError( + "Multiple columns with tag ITEM_ID found. " + "Please specify the item_id column name." + ) + item_id = list(_item_id)[0].name + + embedding_dims = embedding_dims or {} + + if infer_embedding_sizes: + embedding_dims_infered = get_embedding_sizes_from_schema( + schema, infer_embedding_sizes_multiplier + ) + + embedding_dims = { + **embedding_dims, + **{k: v for k, v in embedding_dims_infered.items() if k not in embedding_dims}, + } + + embeddings_initializers = embeddings_initializers or {} + + emb_config = {} + cardinalities = categorical_cardinalities(schema) + for key, cardinality in cardinalities.items(): + embedding_size = embedding_dims.get(key, embedding_dim_default) + embedding_initializer = embeddings_initializers.get(key, None) + emb_config[key] = (cardinality, embedding_size, embedding_initializer) + + feature_config: Dict[str, FeatureConfig] = {} + for name, (vocab_size, dim, emb_initilizer) in emb_config.items(): + feature_config[name] = FeatureConfig( + TableConfig( + vocabulary_size=vocab_size, + dim=dim, + name=name, + combiner=combiner, + initializer=emb_initilizer, + ) + ) + + if not feature_config: + return None + + output = cls(feature_config, item_id=item_id, pre=pre, post=post, aggregation=aggregation) + + if automatic_build and schema: + output.build( + get_output_sizes_from_schema( + schema, + kwargs.get("batch_size", -1), + max_sequence_length=max_sequence_length, + ), + schema=schema, + ) + + return output
+ +
[docs] def item_ids(self, inputs) -> torch.Tensor: + return inputs[self.item_id]
+ +
[docs] def forward(self, inputs, **kwargs): + embedded_outputs = {} + filtered_inputs = self.filter_features(inputs) + for name, val in filtered_inputs.items(): + if isinstance(val, tuple): + values, offsets = val + values = torch.squeeze(values, -1) + # for the case where only one value in values + if len(values.shape) == 0: + values = values.unsqueeze(0) + embedded_outputs[name] = self.embedding_tables[name](values, offsets[:, 0]) + else: + # if len(val.shape) <= 1: + # val = val.unsqueeze(0) + embedded_outputs[name] = self.embedding_tables[name](val) + + # Store raw item ids for masking and/or negative sampling + # This makes this module stateful. + if self.item_id: + self.item_seq = self.item_ids(inputs) + + embedded_outputs = super().forward(embedded_outputs) + + return embedded_outputs
+ +
[docs] def forward_output_size(self, input_sizes): + sizes = {} + batch_size = calculate_batch_size_from_input_size(input_sizes) + for name, feature in self.feature_config.items(): + sizes[name] = torch.Size([batch_size, feature.table.dim]) + + return sizes
+ + +
[docs]class EmbeddingBagWrapper(torch.nn.EmbeddingBag): + """ + Wrapper class for the PyTorch EmbeddingBag module. + + This class extends the torch.nn.EmbeddingBag class and overrides + the forward method to handle 1D tensor inputs + by reshaping them to 2D as required by the EmbeddingBag. + """ + +
[docs] def forward(self, input, **kwargs): + # EmbeddingBag requires 2D tensors (or offsets) + if len(input.shape) == 1: + input = input.unsqueeze(-1) + return super().forward(input, **kwargs)
+ + +
[docs]@docstring_parameter( + tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING, + embedding_features_parameters=EMBEDDING_FEATURES_PARAMS_DOCSTRING, +) +class SoftEmbeddingFeatures(EmbeddingFeatures): + """ + Encapsulate continuous features encoded using the Soft-one hot encoding + embedding technique (SoftEmbedding), from https://arxiv.org/pdf/1708.00065.pdf + In a nutshell, it keeps an embedding table for each continuous feature, + which is represented as a weighted average of embeddings. + + Parameters + ---------- + feature_config: Dict[str, FeatureConfig] + This specifies what TableConfig to use for each feature. For shared embeddings, the same + TableConfig can be used for multiple features. + layer_norm: boolean + When layer_norm is true, TabularLayerNorm will be used in post. + {tabular_module_parameters} + """ + + def __init__( + self, + feature_config: Dict[str, "FeatureConfig"], + layer_norm: bool = True, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + **kwarg, + ): + if layer_norm: + from transformers4rec.torch import TabularLayerNorm + + post = TabularLayerNorm.from_feature_config(feature_config) + + super().__init__(feature_config, pre=pre, post=post, aggregation=aggregation) + +
[docs] @classmethod + def from_schema( # type: ignore + cls, + schema: Schema, + soft_embedding_cardinalities: Optional[Dict[str, int]] = None, + soft_embedding_cardinality_default: int = 10, + soft_embedding_dims: Optional[Dict[str, int]] = None, + soft_embedding_dim_default: int = 8, + embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] = None, + layer_norm: bool = True, + combiner: str = "mean", + tags: Optional[TagsType] = None, + automatic_build: bool = True, + max_sequence_length: Optional[int] = None, + **kwargs, + ) -> Optional["SoftEmbeddingFeatures"]: + """ + Instantitates ``SoftEmbeddingFeatures`` from a ``DatasetSchema``. + + Parameters + ---------- + schema : DatasetSchema + Dataset schema + soft_embedding_cardinalities : Optional[Dict[str, int]], optional + The cardinality of the embedding table for each feature (key), + by default None + soft_embedding_cardinality_default : Optional[int], optional + Default cardinality of the embedding table, when the feature + is not found in ``soft_embedding_cardinalities``, by default 10 + soft_embedding_dims : Optional[Dict[str, int]], optional + The dimension of the embedding table for each feature (key), by default None + soft_embedding_dim_default : Optional[int], optional + Default dimension of the embedding table, when the feature + is not found in ``soft_embedding_dim_default``, by default 8 + embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] + Dict where keys are feature names and values are callable to initialize embedding tables + combiner : Optional[str], optional + Feature aggregation option, by default "mean" + tags : Optional[Union[DefaultTags, list, str]], optional + Tags to filter columns, by default None + automatic_build : bool, optional + Automatically infers input size from features, by default True + max_sequence_length : Optional[int], optional + Maximum sequence length for list features, by default None + + Returns + ------- + Optional[SoftEmbeddingFeatures] + Returns a ``SoftEmbeddingFeatures`` instance from the dataset schema + """ + # TODO: propagate item-id from ITEM_ID tag + + if tags: + schema = schema.select_by_tag(tags) + + soft_embedding_cardinalities = soft_embedding_cardinalities or {} + soft_embedding_dims = soft_embedding_dims or {} + embeddings_initializers = embeddings_initializers or {} + + sizes = {} + cardinalities = categorical_cardinalities(schema) + for col_name in schema.column_names: + # If this is NOT a categorical feature + if col_name not in cardinalities: + embedding_size = soft_embedding_dims.get(col_name, soft_embedding_dim_default) + cardinality = soft_embedding_cardinalities.get( + col_name, soft_embedding_cardinality_default + ) + emb_initializer = embeddings_initializers.get(col_name, None) + sizes[col_name] = (cardinality, embedding_size, emb_initializer) + + feature_config: Dict[str, FeatureConfig] = {} + for name, (vocab_size, dim, emb_initializer) in sizes.items(): + feature_config[name] = FeatureConfig( + TableConfig( + vocabulary_size=vocab_size, + dim=dim, + name=name, + combiner=combiner, + initializer=emb_initializer, + ) + ) + + if not feature_config: + return None + + output = cls(feature_config, layer_norm=layer_norm, **kwargs) + + if automatic_build and schema: + output.build( + get_output_sizes_from_schema( + schema, + kwargs.get("batch_size", -1), + max_sequence_length=max_sequence_length, + ) + ) + + return output
+ +
[docs] def table_to_embedding_module(self, table: "TableConfig") -> "SoftEmbedding": + return SoftEmbedding(table.vocabulary_size, table.dim, table.initializer)
+ + +
[docs]class TableConfig: + """ + Class to configure the embeddings lookup table for a categorical feature. + + Attributes + ---------- + vocabulary_size : int + The size of the vocabulary, + i.e., the cardinality of the categorical feature. + dim : int + The dimensionality of the embedding vectors. + initializer : Optional[Callable[[torch.Tensor], None]] + The initializer function for the embedding weights. + If None, the weights are initialized using a normal + distribution with mean 0.0 and standard deviation 0.05. + combiner : Optional[str] + The combiner operation used to aggregate bag of embeddings. + Possible options are "mean", "sum", and "sqrtn". + By default "mean". + name : Optional[str] + The name of the lookup table. + By default None. + """ + + def __init__( + self, + vocabulary_size: int, + dim: int, + initializer: Optional[Callable[[torch.Tensor], None]] = None, + combiner: Text = "mean", + name: Optional[Text] = None, + ): + if not isinstance(vocabulary_size, int) or vocabulary_size < 1: + raise ValueError("Invalid vocabulary_size {}.".format(vocabulary_size)) + + if not isinstance(dim, int) or dim < 1: + raise ValueError("Invalid dim {}.".format(dim)) + + if combiner not in ("mean", "sum", "sqrtn"): + raise ValueError("Invalid combiner {}".format(combiner)) + + if (initializer is not None) and (not callable(initializer)): + raise ValueError("initializer must be callable if specified.") + + self.initializer: Callable[[torch.Tensor], None] + if initializer is None: + self.initializer = partial(torch.nn.init.normal_, mean=0.0, std=0.05) # type: ignore + else: + self.initializer = initializer + + self.vocabulary_size = vocabulary_size + self.dim = dim + self.combiner = combiner + self.name = name + + def __repr__(self): + return ( + "TableConfig(vocabulary_size={vocabulary_size!r}, dim={dim!r}, " + "combiner={combiner!r}, name={name!r})".format( + vocabulary_size=self.vocabulary_size, + dim=self.dim, + combiner=self.combiner, + name=self.name, + ) + )
+ + +
[docs]class FeatureConfig: + """ + Class to set the embeddings table of a categorical feature + with a maximum sequence length. + + Attributes + ---------- + table : TableConfig + Configuration for the lookup table, + which is used for embedding lookup and aggregation. + max_sequence_length : int, optional + Maximum sequence length for sequence features. + By default 0. + name : str, optional + The feature name. + By default None + """ + + def __init__( + self, table: TableConfig, max_sequence_length: int = 0, name: Optional[Text] = None + ): + self.table = table + self.max_sequence_length = max_sequence_length + self.name = name + + def __repr__(self): + return ( + "FeatureConfig(table={table!r}, " + "max_sequence_length={max_sequence_length!r}, name={name!r})".format( + table=self.table, max_sequence_length=self.max_sequence_length, name=self.name + ) + )
+ + +
[docs]class SoftEmbedding(torch.nn.Module): + """ + Soft-one hot encoding embedding technique, from https://arxiv.org/pdf/1708.00065.pdf + In a nutshell, it represents a continuous feature as a weighted average of embeddings + """ + + def __init__(self, num_embeddings, embeddings_dim, emb_initializer=None): + """ + + Parameters + ---------- + num_embeddings: Number of embeddings to use (cardinality of the embedding table). + embeddings_dim: The dimension of the vector space for projecting the scalar value. + embeddings_init_std: The standard deviation factor for normal initialization of the + embedding matrix weights. + emb_initializer: Dict where keys are feature names and values are callable to initialize + embedding tables + """ + + assert ( + num_embeddings > 0 + ), "The number of embeddings for soft embeddings needs to be greater than 0" + assert ( + embeddings_dim > 0 + ), "The embeddings dim for soft embeddings needs to be greater than 0" + + super(SoftEmbedding, self).__init__() + self.embedding_table = torch.nn.Embedding(num_embeddings, embeddings_dim) + if emb_initializer: + emb_initializer(self.embedding_table.weight) + + self.projection_layer = torch.nn.Linear(1, num_embeddings, bias=True) + self.softmax = torch.nn.Softmax(dim=-1) + +
[docs] def forward(self, input_numeric): + input_numeric = input_numeric.unsqueeze(-1) + weights = self.softmax(self.projection_layer(input_numeric)) + soft_one_hot_embeddings = (weights.unsqueeze(-1) * self.embedding_table.weight).sum(-2) + + return soft_one_hot_embeddings
+ + +
[docs]class PretrainedEmbeddingsInitializer(torch.nn.Module): + """ + Initializer of embedding tables with pre-trained weights + + Parameters + ---------- + weight_matrix : Union[torch.Tensor, List[List[float]]] + A 2D torch or numpy tensor or lists of lists with the pre-trained + weights for embeddings. The expect dims are + (embedding_cardinality, embedding_dim). The embedding_cardinality + can be inferred from the column schema, for example, + `schema.select_by_name("item_id").feature[0].int_domain.max + 1`. + The first position of the embedding table is reserved for padded + items (id=0). + trainable : bool + Whether the embedding table should be trainable or not + """ + + def __init__( + self, + weight_matrix: Union[torch.Tensor, List[List[float]]], + trainable: bool = False, + **kwargs, + ): + super().__init__(**kwargs) + # The weight matrix is kept in CPU, but when forward() is called + # to initialize the embedding table weight will be copied to + # the embedding table device (e.g. cuda) + self.weight_matrix = torch.tensor(weight_matrix, device="cpu") + self.trainable = trainable + +
[docs] def forward(self, x): + with torch.no_grad(): + x.copy_(self.weight_matrix) + x.requires_grad = self.trainable
+ + +
[docs]@docstring_parameter( + tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING, +) +class PretrainedEmbeddingFeatures(InputBlock): + """Input block for pre-trained embeddings features. + + For 3-D features, if sequence_combiner is set, the features are aggregated + using the second dimension (sequence length) + + Parameters + ---------- + features: List[str] + A list of the pre-trained embeddings feature names. + You typically will pass schema.select_by_tag(Tags.EMBEDDING).column_names, + as that is the tag added to pre-trained embedding features when using the + merlin.dataloader.ops.embeddings.EmbeddingOperator + pretrained_output_dims: Optional[Union[int, Dict[str, int]]] + If provided, it projects features to specified dim(s). + If an int, all features are projected to that dim. + If a dict, only features provided in the dict will be mapped to the specified dim, + sequence_combiner: Optional[Union[str, torch.nn.Module]], optional + A string ("mean", "sum", "max", "min") or torch.nn.Module specifying + how to combine the second dimension of the pre-trained embeddings if it is 3D. + Default is None (no sequence combiner used) + normalizer: Optional[Union[str, TabularTransformationType]] + A tabular layer (e.g.tr.TabularLayerNorm()) or string ("layer-norm") to be applied + to pre-trained embeddings after projected and sequence combined + Default is None (no normalization) + schema (Optional[Schema]): the schema of the input data. + {tabular_module_parameters} + """ + + def __init__( + self, + features: List[str], + pretrained_output_dims: Optional[Union[int, Dict[str, int]]] = None, + sequence_combiner: Optional[Union[str, torch.nn.Module]] = None, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + normalizer: Optional[TabularTransformationType] = None, + schema: Optional[Schema] = None, + ): + if isinstance(normalizer, str): + normalizer = TabularTransformation.parse(normalizer) + if not post: + post = normalizer + elif normalizer: + post = SequentialBlock(normalizer, post) # type: ignore + + super().__init__(pre=pre, post=post, aggregation=aggregation, schema=schema) + self.features = features + self.filter_features = FilterFeatures(features) + self.pretrained_output_dims = pretrained_output_dims + self.sequence_combiner = self.parse_combiner(sequence_combiner) + +
[docs] def build(self, input_size, **kwargs): + if input_size is not None: + if self.pretrained_output_dims: + self.projection = torch.nn.ModuleDict() + if isinstance(self.pretrained_output_dims, int): + for key in self.features: + self.projection[key] = torch.nn.Linear( + input_size[key][-1], self.pretrained_output_dims + ) + + elif isinstance(self.pretrained_output_dims, dict): + for key in self.features: + self.projection[key] = torch.nn.Linear( + input_size[key][-1], self.pretrained_output_dims[key] + ) + + return super().build(input_size, **kwargs)
+ +
[docs] @classmethod + def from_schema( + cls, + schema: Schema, + tags: Optional[TagsType] = None, + pretrained_output_dims=None, + sequence_combiner=None, + normalizer: Optional[Union[str, TabularTransformationType]] = None, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + **kwargs, + ): # type: ignore + if tags: + schema = schema.select_by_tag(tags) + + features = schema.column_names + return cls( + features=features, + pretrained_output_dims=pretrained_output_dims, + sequence_combiner=sequence_combiner, + pre=pre, + post=post, + aggregation=aggregation, + normalizer=normalizer, + )
+ +
[docs] def forward(self, inputs): + output = self.filter_features(inputs) + if self.pretrained_output_dims: + output = {key: self.projection[key](val) for key, val in output.items()} + if self.sequence_combiner: + for key, val in output.items(): + if val.dim() > 2: + output[key] = self.sequence_combiner(val, axis=1) + + return output
+ +
[docs] def forward_output_size(self, input_sizes): + sizes = self.filter_features.forward_output_size(input_sizes) + if self.pretrained_output_dims: + if isinstance(self.pretrained_output_dims, dict): + sizes.update( + { + key: torch.Size(list(sizes[key][:-1]) + [self.pretrained_output_dims[key]]) + for key in self.features + } + ) + else: + sizes.update( + { + key: torch.Size(list(sizes[key][:-1]) + [self.pretrained_output_dims]) + for key in self.features + } + ) + return sizes
+ +
[docs] def parse_combiner(self, combiner): + if isinstance(combiner, str): + if combiner == "sum": + combiner = torch.sum + elif combiner == "max": + combiner = torch.max + elif combiner == "min": + combiner = torch.min + elif combiner == "mean": + combiner = torch.mean + return combiner
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/features/sequence.html b/review/pr-767/_modules/transformers4rec/torch/features/sequence.html new file mode 100644 index 0000000000..b1fb4d5b92 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/features/sequence.html @@ -0,0 +1,428 @@ + + + + + + transformers4rec.torch.features.sequence — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.features.sequence
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.features.sequence

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from typing import Dict, List, Optional, Tuple, Union
+
+import torch
+from merlin.models.utils.doc_utils import docstring_parameter
+from merlin.schema import Tags, TagsType
+
+from merlin_standard_lib import Schema
+
+from ..block.base import BlockOrModule, BuildableBlock, SequentialBlock
+from ..block.mlp import MLPBlock
+from ..masking import MaskSequence, masking_registry
+from ..tabular.base import (
+    TABULAR_MODULE_PARAMS_DOCSTRING,
+    AsTabular,
+    TabularAggregationType,
+    TabularModule,
+    TabularTransformationType,
+)
+from . import embedding
+from .tabular import TABULAR_FEATURES_PARAMS_DOCSTRING, TabularFeatures
+
+
+
[docs]@docstring_parameter( + tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING, + embedding_features_parameters=embedding.EMBEDDING_FEATURES_PARAMS_DOCSTRING, +) +class SequenceEmbeddingFeatures(embedding.EmbeddingFeatures): + """Input block for embedding-lookups for categorical features. This module produces 3-D tensors, + this is useful for sequential models like transformers. + + Parameters + ---------- + {embedding_features_parameters} + padding_idx: int + The symbol to use for padding. + {tabular_module_parameters} + """ + + def __init__( + self, + feature_config: Dict[str, embedding.FeatureConfig], + item_id: Optional[str] = None, + padding_idx: int = 0, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + schema: Optional[Schema] = None, + ): + self.padding_idx = padding_idx + super(SequenceEmbeddingFeatures, self).__init__( + feature_config=feature_config, + item_id=item_id, + pre=pre, + post=post, + aggregation=aggregation, + schema=schema, + ) + +
[docs] def table_to_embedding_module(self, table: embedding.TableConfig) -> torch.nn.Embedding: + embedding_table = torch.nn.Embedding( + table.vocabulary_size, table.dim, padding_idx=self.padding_idx + ) + if table.initializer is not None: + table.initializer(embedding_table.weight) + return embedding_table
+ +
[docs] def forward_output_size(self, input_sizes): + sizes = {} + + for fname, fconfig in self.feature_config.items(): + fshape = input_sizes[fname] + sizes[fname] = torch.Size(list(fshape) + [fconfig.table.dim]) + + return sizes
+ + +
[docs]@docstring_parameter( + tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING, + tabular_features_parameters=TABULAR_FEATURES_PARAMS_DOCSTRING, +) +class TabularSequenceFeatures(TabularFeatures): + """Input module that combines different types of features to a sequence: continuous, + categorical & text. + + Parameters + ---------- + {tabular_features_parameters} + projection_module: BlockOrModule, optional + Module that's used to project the output of this module, typically done by an MLPBlock. + masking: MaskSequence, optional + Masking to apply to the inputs. + {tabular_module_parameters} + + """ + + EMBEDDING_MODULE_CLASS = SequenceEmbeddingFeatures + + def __init__( + self, + continuous_module: Optional[TabularModule] = None, + categorical_module: Optional[TabularModule] = None, + pretrained_embedding_module: Optional[TabularModule] = None, + projection_module: Optional[BlockOrModule] = None, + masking: Optional[MaskSequence] = None, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + schema: Optional[Schema] = None, + **kwargs + ): + super().__init__( + continuous_module, + categorical_module, + pretrained_embedding_module, + pre=pre, + post=post, + aggregation=aggregation, + schema=schema, + **kwargs + ) + self.projection_module = projection_module + self.set_masking(masking) + +
[docs] @classmethod + def from_schema( # type: ignore + cls, + schema: Schema, + continuous_tags: Optional[Union[TagsType, Tuple[Tags]]] = (Tags.CONTINUOUS,), + categorical_tags: Optional[Union[TagsType, Tuple[Tags]]] = (Tags.CATEGORICAL,), + pretrained_embeddings_tags: Optional[Union[TagsType, Tuple[Tags]]] = (Tags.EMBEDDING,), + aggregation: Optional[str] = None, + automatic_build: bool = True, + max_sequence_length: Optional[int] = None, + continuous_projection: Optional[Union[List[int], int]] = None, + continuous_soft_embeddings: bool = False, + projection: Optional[Union[torch.nn.Module, BuildableBlock]] = None, + d_output: Optional[int] = None, + masking: Optional[Union[str, MaskSequence]] = None, + **kwargs + ) -> "TabularSequenceFeatures": + """Instantiates ``TabularFeatures`` from a ``DatasetSchema`` + + Parameters + ---------- + schema : DatasetSchema + Dataset schema + continuous_tags : Optional[Union[TagsType, Tuple[Tags]]], optional + Tags to filter the continuous features, by default Tags.CONTINUOUS + categorical_tags : Optional[Union[TagsType, Tuple[Tags]]], optional + Tags to filter the categorical features, by default Tags.CATEGORICAL + aggregation : Optional[str], optional + Feature aggregation option, by default None + automatic_build : bool, optional + Automatically infers input size from features, by default True + max_sequence_length : Optional[int], optional + Maximum sequence length for list features by default None + continuous_projection : Optional[Union[List[int], int]], optional + If set, concatenate all numerical features and project them by a number of MLP layers. + The argument accepts a list with the dimensions of the MLP layers, by default None + continuous_soft_embeddings : bool + Indicates if the soft one-hot encoding technique must be used to represent + continuous features, by default False + projection: Optional[Union[torch.nn.Module, BuildableBlock]], optional + If set, project the aggregated embeddings vectors into hidden dimension vector space, + by default None + d_output: Optional[int], optional + If set, init a MLPBlock as projection module to project embeddings vectors, + by default None + masking: Optional[Union[str, MaskSequence]], optional + If set, Apply masking to the input embeddings and compute masked labels, It requires + a categorical_module including an item_id column, by default None + + Returns + ------- + TabularFeatures + Returns ``TabularFeatures`` from a dataset schema + """ + output: TabularSequenceFeatures = super().from_schema( # type: ignore + schema=schema, + continuous_tags=continuous_tags, + categorical_tags=categorical_tags, + pretrained_embeddings_tags=pretrained_embeddings_tags, + aggregation=aggregation, + automatic_build=automatic_build, + max_sequence_length=max_sequence_length, + continuous_projection=continuous_projection, + continuous_soft_embeddings=continuous_soft_embeddings, + **kwargs + ) + if d_output and projection: + raise ValueError("You cannot specify both d_output and projection at the same time") + if (projection or masking or d_output) and not aggregation: + # TODO: print warning here for clarity + output.aggregation = "concat" # type: ignore + hidden_size = output.output_size() + + if d_output and not projection: + projection = MLPBlock([d_output]) + if projection and hasattr(projection, "build"): + projection = projection.build(hidden_size) # type: ignore + if projection: + output.projection_module = projection # type: ignore + hidden_size = projection.output_size() # type: ignore + + if isinstance(masking, str): + masking = masking_registry.parse(masking)( + hidden_size=output.output_size()[-1], **kwargs + ) + if masking and not getattr(output, "item_id", None): + raise ValueError("For masking a categorical_module is required including an item_id.") + output.set_masking(masking) # type: ignore + + return output
+ + @property + def masking(self): + return self._masking + +
[docs] def set_masking(self, value): + self._masking = value
+ + @property + def item_id(self) -> Optional[str]: + if "categorical_module" in self.to_merge: + return getattr(self.to_merge["categorical_module"], "item_id", None) + + return None + + @property + def item_embedding_table(self) -> Optional[torch.nn.Module]: + if "categorical_module" in self.to_merge: + return getattr(self.to_merge["categorical_module"], "item_embedding_table", None) + + return None + +
[docs] def forward(self, inputs, training=False, testing=False, **kwargs): + outputs = super(TabularSequenceFeatures, self).forward(inputs) + + if self.masking or self.projection_module: + outputs = self.aggregation(outputs) + + if self.projection_module: + outputs = self.projection_module(outputs) + + if self.masking: + outputs = self.masking( + outputs, + item_ids=self.to_merge["categorical_module"].item_seq, + training=training, + testing=testing, + ) + + return outputs
+ +
[docs] def project_continuous_features(self, dimensions): + if isinstance(dimensions, int): + dimensions = [dimensions] + + continuous = self.to_merge["continuous_module"] + continuous.aggregation = "concat" + + continuous = SequentialBlock( + continuous, MLPBlock(dimensions), AsTabular("continuous_projection") + ) + + self.to_merge["continuous_module"] = continuous + + return self
+ +
[docs] def forward_output_size(self, input_size): + output_sizes = {} + for in_layer in self.merge_values: + output_sizes.update(in_layer.forward_output_size(input_size)) + + output_sizes = self._check_post_output_size(output_sizes) + + if self.projection_module: + output_sizes = self.projection_module.output_size() + + return output_sizes
+ + +TabularFeaturesType = Union[TabularSequenceFeatures, TabularFeatures] +
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/features/tabular.html b/review/pr-767/_modules/transformers4rec/torch/features/tabular.html new file mode 100644 index 0000000000..c869ce924c --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/features/tabular.html @@ -0,0 +1,368 @@ + + + + + + transformers4rec.torch.features.tabular — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.features.tabular
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.features.tabular

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from typing import List, Optional, Tuple, Type, Union
+
+from merlin.models.utils.doc_utils import docstring_parameter
+from merlin.schema import Tags, TagsType
+
+from merlin_standard_lib import Schema
+
+from ..block.base import SequentialBlock
+from ..block.mlp import MLPBlock
+from ..tabular.base import (
+    TABULAR_MODULE_PARAMS_DOCSTRING,
+    AsTabular,
+    MergeTabular,
+    TabularAggregationType,
+    TabularModule,
+    TabularTransformationType,
+)
+from ..utils.torch_utils import get_output_sizes_from_schema
+from .continuous import ContinuousFeatures
+from .embedding import EmbeddingFeatures, PretrainedEmbeddingFeatures, SoftEmbeddingFeatures
+
+TABULAR_FEATURES_PARAMS_DOCSTRING = """
+    continuous_module: TabularModule, optional
+        Module used to process continuous features.
+    categorical_module: TabularModule, optional
+        Module used to process categorical features.
+    text_embedding_module: TabularModule, optional
+        Module used to process text features.
+"""
+
+
+
[docs]@docstring_parameter( + tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING, + tabular_features_parameters=TABULAR_FEATURES_PARAMS_DOCSTRING, +) +class TabularFeatures(MergeTabular): + """Input module that combines different types of features: continuous, categorical & text. + + Parameters + ---------- + {tabular_features_parameters} + {tabular_module_parameters} + """ + + CONTINUOUS_MODULE_CLASS: Type[TabularModule] = ContinuousFeatures + EMBEDDING_MODULE_CLASS: Type[TabularModule] = EmbeddingFeatures + SOFT_EMBEDDING_MODULE_CLASS: Type[TabularModule] = SoftEmbeddingFeatures + PRETRAINED_EMBEDDING_MODULE_CLASS: Type[TabularModule] = PretrainedEmbeddingFeatures + + def __init__( + self, + continuous_module: Optional[TabularModule] = None, + categorical_module: Optional[TabularModule] = None, + pretrained_embedding_module: Optional[TabularModule] = None, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + schema: Optional[Schema] = None, + **kwargs, + ): + to_merge = {} + if continuous_module: + to_merge["continuous_module"] = continuous_module + if categorical_module: + to_merge["categorical_module"] = categorical_module + if pretrained_embedding_module: + to_merge["pretrained_embedding_module"] = pretrained_embedding_module + + assert to_merge != {}, "Please provide at least one input layer" + super(TabularFeatures, self).__init__( + to_merge, pre=pre, post=post, aggregation=aggregation, schema=schema, **kwargs + ) + +
[docs] def project_continuous_features( + self, mlp_layers_dims: Union[List[int], int] + ) -> "TabularFeatures": + """Combine all concatenated continuous features with stacked MLP layers + + Parameters + ---------- + mlp_layers_dims : Union[List[int], int] + The MLP layer dimensions + + Returns + ------- + TabularFeatures + Returns the same ``TabularFeatures`` object with the continuous features projected + """ + if isinstance(mlp_layers_dims, int): + mlp_layers_dims = [mlp_layers_dims] + + continuous = self.to_merge["continuous_module"] + continuous.aggregation = "concat" + + continuous = SequentialBlock( + continuous, MLPBlock(mlp_layers_dims), AsTabular("continuous_projection") + ) + + self.to_merge["continuous_module"] = continuous # type: ignore + + return self
+ +
[docs] @classmethod + def from_schema( # type: ignore + cls, + schema: Schema, + continuous_tags: Optional[Union[TagsType, Tuple[Tags]]] = (Tags.CONTINUOUS,), + categorical_tags: Optional[Union[TagsType, Tuple[Tags]]] = (Tags.CATEGORICAL,), + pretrained_embeddings_tags: Optional[Union[TagsType, Tuple[Tags]]] = (Tags.EMBEDDING,), + aggregation: Optional[str] = None, + automatic_build: bool = True, + max_sequence_length: Optional[int] = None, + continuous_projection: Optional[Union[List[int], int]] = None, + continuous_soft_embeddings: bool = False, + **kwargs, + ) -> "TabularFeatures": + """Instantiates ``TabularFeatures`` from a ``DatasetSchema`` + + Parameters + ---------- + schema : DatasetSchema + Dataset schema + continuous_tags : Optional[Union[TagsType, Tuple[Tags]]], optional + Tags to filter the continuous features, by default Tags.CONTINUOUS + categorical_tags : Optional[Union[TagsType, Tuple[Tags]]], optional + Tags to filter the categorical features, by default Tags.CATEGORICAL + aggregation : Optional[str], optional + Feature aggregation option, by default None + automatic_build : bool, optional + Automatically infers input size from features, by default True + max_sequence_length : Optional[int], optional + Maximum sequence length for list features by default None + continuous_projection : Optional[Union[List[int], int]], optional + If set, concatenate all numerical features and project them by a number of MLP layers. + The argument accepts a list with the dimensions of the MLP layers, by default None + continuous_soft_embeddings : bool + Indicates if the soft one-hot encoding technique must be used to + represent continuous features, by default False + + Returns + ------- + TabularFeatures + Returns ``TabularFeatures`` from a dataset schema + """ + maybe_continuous_module, maybe_categorical_module, maybe_pretrained_module = ( + None, + None, + None, + ) + + if continuous_tags: + if continuous_soft_embeddings: + maybe_continuous_module = cls.SOFT_EMBEDDING_MODULE_CLASS.from_schema( + schema, + tags=continuous_tags, + **kwargs, + ) + else: + maybe_continuous_module = cls.CONTINUOUS_MODULE_CLASS.from_schema( + schema, tags=continuous_tags, **kwargs + ) + if categorical_tags: + maybe_categorical_module = cls.EMBEDDING_MODULE_CLASS.from_schema( + schema, tags=categorical_tags, **kwargs + ) + if pretrained_embeddings_tags: + maybe_pretrained_module = cls.PRETRAINED_EMBEDDING_MODULE_CLASS.from_schema( + schema, tags=pretrained_embeddings_tags, **kwargs + ) + + output = cls( + continuous_module=maybe_continuous_module, + categorical_module=maybe_categorical_module, + pretrained_embedding_module=maybe_pretrained_module, + aggregation=aggregation, + ) + + if automatic_build and schema: + output.build( + get_output_sizes_from_schema( + schema, + kwargs.get("batch_size", -1), + max_sequence_length=max_sequence_length, + ), + schema=schema, + ) + + if continuous_projection: + if not automatic_build: + raise ValueError( + "Continuous feature projection can only be done with automatic_build" + ) + output = output.project_continuous_features(continuous_projection) + + return output
+ +
[docs] def forward_output_size(self, input_size): + output_sizes = {} + for in_layer in self.merge_values: + output_sizes.update(in_layer.forward_output_size(input_size)) + + return output_sizes
+ + @property + def continuous_module(self) -> Optional[TabularModule]: + if "continuous_module" in self.to_merge: + return self.to_merge["continuous_module"] + + return None + + @property + def categorical_module(self) -> Optional[TabularModule]: + if "categorical_module" in self.to_merge: + return self.to_merge["categorical_module"] + + return None + + @property + def pretrained_module(self) -> Optional[TabularModule]: + if "pretrained_embedding_module" in self.to_merge: + return self.to_merge["pretrained_embedding_module"] + + return None
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/losses.html b/review/pr-767/_modules/transformers4rec/torch/losses.html new file mode 100644 index 0000000000..cfc7a2f591 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/losses.html @@ -0,0 +1,149 @@ + + + + + + transformers4rec.torch.losses — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.losses
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.losses

+import torch
+
+
+
[docs]def LabelSmoothCrossEntropyLoss(smoothing: float = 0.0, reduction: str = "mean", **kwargs): + """Coss-entropy loss with label smoothing. + This is going to be deprecated. You should use torch.nn.CrossEntropyLoss() + directly that in recent PyTorch versions already supports label_smoothing arg + + Parameters + ---------- + smoothing: float + The label smoothing factor. Specify a value between 0 and 1. + reduction: str + Specifies the reduction to apply to the output. + Specify one of `none`, `sum`, or `mean`. + + Adapted from https://github.com/NingAnMe/Label-Smoothing-for-CrossEntropyLoss-PyTorch + """ + + return torch.nn.CrossEntropyLoss(label_smoothing=smoothing, reduction=reduction, **kwargs)
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/masking.html b/review/pr-767/_modules/transformers4rec/torch/masking.html new file mode 100644 index 0000000000..bd7b49e766 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/masking.html @@ -0,0 +1,999 @@ + + + + + + transformers4rec.torch.masking — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.masking
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.masking

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from dataclasses import dataclass
+from typing import Any, Dict, Optional, Tuple
+
+import torch
+from merlin.models.utils.doc_utils import docstring_parameter
+from merlin.models.utils.registry import Registry
+from torch import nn
+
+from .utils.torch_utils import OutputSizeMixin
+
+masking_registry = Registry("torch.masking")
+
+
+
[docs]@dataclass +class MaskingInfo: + schema: torch.Tensor + targets: torch.Tensor
+ + +MASK_SEQUENCE_PARAMETERS_DOCSTRING = """ + hidden_size: int + The hidden dimension of input tensors, needed to initialize trainable vector of masked + positions. + padding_idx: int, default = 0 + Index of padding item used for getting batch of sequences with the same length + eval_on_last_item_seq_only: bool, default = True + Predict only last item during evaluation +""" + +TRAINING_TESTING_FLAGS_DOCSTRING = """ + training: bool + Flag to indicate whether we are in `Training` mode or not. + During training, the labels can be any items within the sequence + based on the selected masking task. + testing: bool + Flag to indicate whether we are in `Evaluation` (=True) + or `Inference` (=False) mode. + During evaluation, we are predicting all next items or last item only + in the sequence based on the param `eval_on_last_item_seq_only`. + During inference, we don't mask the input sequence and use all available + information to predict the next item. +""" + + +
[docs]@docstring_parameter(mask_sequence_parameters=MASK_SEQUENCE_PARAMETERS_DOCSTRING) +class MaskSequence(OutputSizeMixin, torch.nn.Module): + """Base class to prepare masked items inputs/labels for language modeling tasks. + + Transformer architectures can be trained in different ways. Depending of the training method, + there is a specific masking schema. The masking schema sets the items to be predicted (labels) + and mask (hide) their positions in the sequence so that they are not used by the Transformer + layers for prediction. + + We currently provide 4 different masking schemes out of the box: + - Causal LM (clm) + - Masked LM (mlm) + - Permutation LM (plm) + - Replacement Token Detection (rtd) + + This class can be extended to add different a masking scheme. + + Parameters + ---------- + hidden_size: + The hidden dimension of input tensors, needed to initialize trainable vector of + masked positions. + pad_token: int, default = 0 + Index of the padding token used for getting batch of sequences with the same length + """ + + # TODO: Link to masking-class in the doc-string. + + def __init__( + self, + hidden_size: int, + padding_idx: int = 0, + eval_on_last_item_seq_only: bool = True, + **kwargs + ): + super(MaskSequence, self).__init__() + self.padding_idx = padding_idx + self.hidden_size = hidden_size + self.eval_on_last_item_seq_only = eval_on_last_item_seq_only + self.mask_schema: Optional[torch.Tensor] = None + self.masked_targets: Optional[torch.Tensor] = None + + # Create a trainable embedding to replace masked interactions + self.masked_item_embedding = nn.Parameter(torch.Tensor(self.hidden_size)) + torch.nn.init.normal_( + self.masked_item_embedding, + mean=0, + std=0.001, + ) + + @docstring_parameter(flags_parameters_docstrings=TRAINING_TESTING_FLAGS_DOCSTRING) + def _compute_masked_targets( + self, item_ids: torch.Tensor, training: bool = False, testing: bool = False + ) -> MaskingInfo: + """ + Method to prepare masked labels based on the sequence of item ids. + It returns The true labels of masked positions and the related boolean mask. + + Parameters + ---------- + item_ids: torch.Tensor + The sequence of input item ids used for deriving labels of + next item prediction task. + {flags_parameters_docstrings} + """ + raise NotImplementedError + +
[docs] @docstring_parameter(flags_parameters_docstrings=TRAINING_TESTING_FLAGS_DOCSTRING) + def compute_masked_targets( + self, item_ids: torch.Tensor, training: bool = False, testing: bool = False + ) -> MaskingInfo: + """ + Method to prepare masked labels based on the sequence of item ids. + It returns The true labels of masked positions and the related boolean mask. + And the attributes of the class `mask_schema` and `masked_targets` + are updated to be re-used in other modules. + + Parameters + ---------- + item_ids: torch.Tensor + The sequence of input item ids used for deriving labels of + next item prediction task. + {flags_parameters_docstrings} + + Returns + ------- + Tuple[MaskingSchema, MaskedTargets] + """ + assert item_ids.ndim == 2, "`item_ids` must have 2 dimensions." + masking_info = self._compute_masked_targets(item_ids, training=training, testing=testing) + self.mask_schema, self.masked_targets = masking_info.schema, masking_info.targets + + return masking_info
+ +
[docs] def apply_mask_to_inputs( + self, + inputs: torch.Tensor, + schema: torch.Tensor, + training: bool = False, + testing: bool = False, + ) -> torch.Tensor: + """ + Control the masked positions in the inputs by replacing the true interaction + by a learnable masked embedding. + + Parameters + ---------- + inputs: torch.Tensor + The 3-D tensor of interaction embeddings resulting from the ops: + TabularFeatures + aggregation + projection(optional) + schema: MaskingSchema + The boolean mask indicating masked positions. + """ + if not training and not testing: + return inputs + inputs = torch.where( + schema.unsqueeze(-1).bool(), + self.masked_item_embedding.to(inputs.dtype), + inputs, + ) + return inputs
+ +
[docs] def predict_all(self, item_ids: torch.Tensor) -> MaskingInfo: + """ + Prepare labels for all next item predictions instead of + last-item predictions in a user's sequence. + + Parameters + ---------- + item_ids: torch.Tensor + The sequence of input item ids used for deriving labels of + next item prediction task. + + Returns + ------- + Tuple[MaskingSchema, MaskedTargets] + """ + # TODO : Add option to predict N-last items + # shift sequence of item-ids + labels = item_ids[:, 1:] + # As after shifting the sequence length will be subtracted by one, adding a masked item in + # the sequence to return to the initial sequence. + # This is important for ReformerModel(), for example + labels = torch.cat( # type: ignore + [ + labels, + torch.zeros((labels.shape[0], 1), dtype=labels.dtype).to(item_ids.device), + ], + axis=-1, + ) + # apply mask on input where target is on padding index + mask_labels = labels != self.padding_idx + + return MaskingInfo(mask_labels, labels)
+ +
[docs] def forward( + self, + inputs: torch.Tensor, + item_ids: torch.Tensor, + training: bool = False, + testing: bool = False, + ) -> torch.Tensor: + _ = self.compute_masked_targets(item_ids=item_ids, training=training, testing=testing) + if self.mask_schema is None: + raise ValueError("`mask_schema must be set.`") + schema: torch.Tensor = self.mask_schema + return self.apply_mask_to_inputs(inputs, schema, training=training, testing=testing)
+ +
[docs] def forward_output_size(self, input_size): + return input_size
+ +
[docs] def transformer_required_arguments(self) -> Dict[str, Any]: + return {}
+ +
[docs] def transformer_optional_arguments(self) -> Dict[str, Any]: + return {}
+ + @property + def transformer_arguments(self) -> Dict[str, Any]: + """ + Prepare additional arguments to pass to the Transformer forward methods. + """ + return {**self.transformer_required_arguments(), **self.transformer_optional_arguments()}
+ + +
[docs]@masking_registry.register_with_multiple_names("clm", "causal") +@docstring_parameter(mask_sequence_parameters=MASK_SEQUENCE_PARAMETERS_DOCSTRING) +class CausalLanguageModeling(MaskSequence): + """ + In Causal Language Modeling (clm) you predict the next item based on past positions of the + sequence. Future positions are masked. + + Parameters + ---------- + {mask_sequence_parameters} + train_on_last_item_seq_only: predict only last item during training + """ + + def __init__( + self, + hidden_size: int, + padding_idx: int = 0, + eval_on_last_item_seq_only: bool = True, + train_on_last_item_seq_only: bool = False, + **kwargs + ): + super(CausalLanguageModeling, self).__init__( + hidden_size=hidden_size, + padding_idx=padding_idx, + eval_on_last_item_seq_only=eval_on_last_item_seq_only, + kwargs=kwargs, + ) + self.train_on_last_item_seq_only = train_on_last_item_seq_only + + def _compute_masked_targets( + self, item_ids: torch.Tensor, training: bool = False, testing: bool = False + ) -> MaskingInfo: + if not training and not testing: + mask_labels = item_ids != self.padding_idx + return MaskingInfo(mask_labels, item_ids) + + masking_info = self.predict_all(item_ids) + mask_labels, labels = masking_info.schema, masking_info.targets + + if (self.eval_on_last_item_seq_only and not training) or ( + self.train_on_last_item_seq_only and training + ): + rows_ids = torch.arange( + labels.size(0), dtype=torch.long, device=item_ids.device # type: ignore + ) + last_item_sessions = mask_labels.sum(dim=1) - 1 + label_seq_trg_eval = torch.zeros( + labels.shape, dtype=labels.dtype, device=item_ids.device + ) + label_seq_trg_eval[rows_ids, last_item_sessions] = labels[rows_ids, last_item_sessions] + # Updating labels and mask + labels = label_seq_trg_eval + # We only mask padded positions + mask_labels = item_ids != self.padding_idx + + return MaskingInfo(mask_labels, labels) + +
[docs] def apply_mask_to_inputs( + self, + inputs: torch.Tensor, + mask_schema: torch.Tensor, + training: bool = False, + testing: bool = False, + ) -> torch.Tensor: + if not training and not testing: + # Replacing the inputs corresponding to padded items with a trainable embedding + # To mimic training and evaluation masking strategy + inputs = torch.where( + mask_schema.unsqueeze(-1).bool(), + inputs, + self.masked_item_embedding.to(inputs.dtype), + ) + return inputs + # shift sequence of interaction embeddings + pos_emb_inp = inputs[:, :-1] + # Adding a masked item in the sequence to return to the initial sequence. + pos_emb_inp = torch.cat( # type: ignore + [ + pos_emb_inp, + torch.zeros( + (pos_emb_inp.shape[0], 1, pos_emb_inp.shape[2]), + dtype=pos_emb_inp.dtype, + ).to(inputs.device), + ], + axis=1, + ) + # Replacing the inputs corresponding to padded items with a trainable embedding + pos_emb_inp = torch.where( + mask_schema.unsqueeze(-1).bool(), + pos_emb_inp, + self.masked_item_embedding.to(pos_emb_inp.dtype), + ) + return pos_emb_inp
+ + +
[docs]@masking_registry.register_with_multiple_names("mlm", "masked") +@docstring_parameter(mask_sequence_parameters=MASK_SEQUENCE_PARAMETERS_DOCSTRING) +class MaskedLanguageModeling(MaskSequence): + """ + In Masked Language Modeling (mlm) you randomly select some positions of the sequence to be + predicted, which are masked. + During training, the Transformer layer is allowed to use positions on the right (future info). + During inference, all past items are visible for the Transformer layer, which tries to predict + the next item. + + Parameters + ---------- + {mask_sequence_parameters} + mlm_probability: Optional[float], default = 0.15 + Probability of an item to be selected (masked) as a label of the given sequence. + p.s. We enforce that at least one item is masked for each sequence, so that the network can + learn something with it. + """ + + def __init__( + self, + hidden_size: int, + padding_idx: int = 0, + eval_on_last_item_seq_only: bool = True, + mlm_probability: float = 0.15, + **kwargs + ): + super(MaskedLanguageModeling, self).__init__( + hidden_size=hidden_size, + padding_idx=padding_idx, + eval_on_last_item_seq_only=eval_on_last_item_seq_only, + kwargs=kwargs, + ) + self.mlm_probability = mlm_probability + + @docstring_parameter(flags_parameters_docstrings=TRAINING_TESTING_FLAGS_DOCSTRING) + def _compute_masked_targets( + self, item_ids: torch.Tensor, training: bool = False, testing: bool = False + ) -> MaskingInfo: + """ + Prepare sequence with mask schema for masked language modeling prediction + the function is based on HuggingFace's transformers/data/data_collator.py + + Parameters + ---------- + item_ids: torch.Tensor + Sequence of input itemid (target) column + {flags_parameters_docstrings} + + Returns + ------- + labels: torch.Tensor + Sequence of masked item ids. + mask_labels: torch.Tensor + Masking schema for masked targets positions. + {flags_parameters_docstrings} + + `Note:` During inference, the inputs are extended with one additional + [MASK] item embeddings. This position is then used to retrieve + the final hidden representation from the transformer block. + This is needed to take into account the actual target position + when applying the transformer layer. + """ + non_padded_mask = item_ids != self.padding_idx + rows_ids = torch.arange(item_ids.size(0), dtype=torch.long, device=item_ids.device) + + if not training and not testing: + # At inference we extend the input with a [MASK] element at the first padded position + # to take into account the positional encoding of the target + labels = torch.full( + (item_ids.shape[0], item_ids.shape[1] + 1), + self.padding_idx, + dtype=item_ids.dtype, + device=item_ids.device, + ) + last_item_sessions = non_padded_mask.sum(dim=1) + labels[rows_ids, last_item_sessions] = item_ids[rows_ids, last_item_sessions - 1] + mask_labels = labels != self.padding_idx + return MaskingInfo(mask_labels, labels) + + labels = torch.full( + item_ids.shape, self.padding_idx, dtype=item_ids.dtype, device=item_ids.device + ) + # During training, masks labels to be predicted according to a probability, ensuring that + # each session has at least one label to predict + if training: + # Selects a percentage of items to be masked (selected as labels) + probability_matrix = torch.full( + item_ids.shape, self.mlm_probability, device=item_ids.device + ) + mask_labels = torch.bernoulli(probability_matrix).bool() & non_padded_mask + labels = torch.where( + mask_labels, + item_ids, + torch.full_like(item_ids, self.padding_idx), + ) + + # Set at least one item in the sequence to mask, so that the network + # can learn something with this session + one_random_index_by_session = torch.multinomial( + non_padded_mask.float(), num_samples=1 + ).squeeze() + labels[rows_ids, one_random_index_by_session] = item_ids[ + rows_ids, one_random_index_by_session + ] + mask_labels = labels != self.padding_idx + + # If a sequence has only masked labels, unmasks one of the labels + sequences_with_only_labels = mask_labels.sum(dim=1) == non_padded_mask.sum(dim=1) + sampled_labels_to_unmask = torch.multinomial( + mask_labels.float(), num_samples=1 + ).squeeze() + + labels_to_unmask = torch.masked_select( + sampled_labels_to_unmask, sequences_with_only_labels + ) + rows_to_unmask = torch.masked_select(rows_ids, sequences_with_only_labels) + + labels[rows_to_unmask, labels_to_unmask] = self.padding_idx + mask_labels = labels != self.padding_idx + + else: + if self.eval_on_last_item_seq_only: + last_item_sessions = non_padded_mask.sum(dim=1) - 1 + labels[rows_ids, last_item_sessions] = item_ids[rows_ids, last_item_sessions] + mask_labels = labels != self.padding_idx + else: + masking_info = self.predict_all(item_ids) + mask_labels, labels = masking_info.schema, masking_info.targets + + return MaskingInfo(mask_labels, labels) + +
[docs] @docstring_parameter(flags_parameters_docstrings=TRAINING_TESTING_FLAGS_DOCSTRING) + def apply_mask_to_inputs( + self, inputs: torch.Tensor, mask_schema: torch.Tensor, training=False, testing=False + ) -> torch.Tensor: + """ + Control the masked positions in the inputs by replacing the true interaction + by a learnable masked embedding. + + Parameters + ---------- + inputs: torch.Tensor + The 3-D tensor of interaction embeddings resulting from the ops: + TabularFeatures + aggregation + projection(optional) + schema: MaskingSchema + The boolean mask indicating masked positions. + {flags_parameters_docstrings} + """ + if not testing and not training: + # We extend the inputs with a [MASK] embeddings to take into account + # the positional encode of the target + inputs = torch.cat([inputs, inputs[:, -1, :].unsqueeze(1)], dim=1) + inputs = torch.where( + mask_schema.unsqueeze(-1).bool(), + self.masked_item_embedding.to(inputs.dtype), + inputs, + ) + return inputs
+ + +
[docs]@masking_registry.register_with_multiple_names("plm", "permutation") +@docstring_parameter(mask_sequence_parameters=MASK_SEQUENCE_PARAMETERS_DOCSTRING) +class PermutationLanguageModeling(MaskSequence): + """ + In Permutation Language Modeling (plm) you use a permutation factorization at the level of the + self-attention layer to define the accessible bidirectional context. + + Parameters + ---------- + {mask_sequence_parameters} + max_span_length: int + maximum length of a span of masked items + plm_probability: float + The ratio of surrounding items to unmask to define the context of the span-based + prediction segment of items + permute_all: bool + Compute partial span-based prediction (=False) or not. + """ + + def __init__( + self, + hidden_size: int, + padding_idx: int = 0, + eval_on_last_item_seq_only: bool = True, + plm_probability: float = 1 / 6, + max_span_length: int = 5, + permute_all: bool = False, + **kwargs + ): + super(PermutationLanguageModeling, self).__init__( + hidden_size=hidden_size, + padding_idx=padding_idx, + eval_on_last_item_seq_only=eval_on_last_item_seq_only, + kwargs=kwargs, + ) + + self.plm_probability = plm_probability + self.max_span_length = max_span_length + self.permute_all = permute_all + + # additional masked scheme needed for XLNet-PLM task : + self.target_mapping: Optional[torch.Tensor] = None + self.perm_mask: Optional[torch.Tensor] = None + + def _compute_masked_targets(self, item_ids: torch.Tensor, **kwargs): + pass + + def _compute_masked_targets_extended( + self, + item_ids: torch.Tensor, + training=False, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Prepare the attention masks needed for permutation language modeling + The function is based on HuggingFace's transformers/data/data_collator.py + + Parameters + ---------- + item_ids: torch.Tensor + Sequence of input itemid (target) column. + + Returns + ------- + labels: torch.Tensor + Sequence of masked item ids. + mask_labels: torch.Tensor + Masking schema for masked targets positions. + perm_mask: torch.Tensor of shape (bs, seq_len, seq_len) + The random factorization order attention mask for each target + target_mapping: torch.Tensor of shape (bs, seq_len, seq_len) : + Binary mask to specify the items to predict. + """ + + labels = torch.full( + item_ids.shape, self.padding_idx, dtype=item_ids.dtype, device=item_ids.device + ) + non_padded_mask = item_ids != self.padding_idx + + rows_ids = torch.arange(item_ids.size(0), dtype=torch.long, device=item_ids.device) + mask_labels = torch.full(labels.shape, 0, dtype=torch.bool, device=item_ids.device) + # During training: + # Masks a span of consecutive items to be predicted according to plm_probability, + # While ensuring that each session has at least one item to predict + if training: + target_mapping = torch.zeros( + (labels.size(0), labels.size(1), labels.size(1)), + dtype=torch.float32, + device=item_ids.device, + ) + perm_mask = torch.zeros( + (labels.size(0), labels.size(1), labels.size(1)), + dtype=torch.float32, + device=item_ids.device, + ) + if self.permute_all: + # Permute all non padded items + mask_labels = non_padded_mask + else: + # For each session select a span of consecutive item ids to be masked + for i in range(labels.size(0)): + # Start from the beginning of the sequence by setting `cur_len = 0` + # (number of tokens processed so far). + cur_len = 0 + max_len = non_padded_mask.sum(1)[i] # mask only non-padded items + while cur_len < max_len: + # Sample a `span_length` from the interval `[1, max_span_length]` + # (length of span of tokens to be masked) + span_length = torch.randint(1, self.max_span_length + 1, (1,)).item() + # Reserve a context + # to surround span to be masked + context_length = int(span_length / self.plm_probability) + # Sample a starting point `start_index` + # from the interval `[cur_len, cur_len + context_length - span_length]` + start_index = ( + cur_len + + torch.randint( + context_length - span_length + 1, (1,) # type: ignore + ).item() + ) + if start_index < max_len: + # Mask the span of non-padded items + # `start_index:start_index + span_length` + mask_labels[ + i, start_index : start_index + span_length # type: ignore + ] = 1 + # Set `cur_len = cur_len + context_length` + cur_len += context_length + # if no item was masked: + if mask_labels[i].sum() == 0: + # Set at least one item in the sequence to mask, so that the network can + # learn something with this session + one_random_index_by_session = torch.multinomial( + non_padded_mask[i].float(), num_samples=1 + ).squeeze() + mask_labels[i, one_random_index_by_session] = item_ids[ + i, one_random_index_by_session + ] + # Since we're replacing non-masked tokens with padding_idxs in the labels tensor + # instead of skipping them altogether, + # the i-th predict corresponds to the i-th token. + # N.B: the loss function will be computed only on non paded items + target_mapping[i] = torch.eye(labels.size(1)) + + labels = torch.where(mask_labels, item_ids, torch.full_like(item_ids, self.padding_idx)) + + # If a sequence has only masked labels, unmasks one of the labels + sequences_with_only_labels = mask_labels.sum(dim=1) == non_padded_mask.sum(dim=1) + sampled_labels_to_unmask = torch.multinomial( + mask_labels.float(), num_samples=1 + ).squeeze() + + labels_to_unmask = torch.masked_select( + sampled_labels_to_unmask, sequences_with_only_labels + ) + rows_to_unmask = torch.masked_select(rows_ids, sequences_with_only_labels) + + labels[rows_to_unmask, labels_to_unmask] = self.padding_idx + mask_labels = labels != self.padding_idx + + for i in range(labels.size(0)): + # Generate permutation indices i.e. + # sample a random factorisation order for the sequence. + # This will determine which tokens a given token can attend to + # (encoded in `perm_mask`). + # Create a linear factorisation order + perm_index = torch.arange(labels.size(1), dtype=torch.long, device=item_ids.device) + # randomly permute indices of each session + perm_index = perm_index[torch.randperm(labels.size(1))] + # Set the permutation indices of non-masked (non-functional) tokens to the + # smallest index (-1) so that: + # (1) They can be seen by all other positions + # (2) They cannot see masked positions, so there won't be information leak + perm_index.masked_fill_(~mask_labels[i], -1) + # The logic for whether the i-th token can attend on the j-th token + # based on the factorisation order: + # 0 (can attend): + # If perm_index[i] > perm_index[j] or j is neither masked nor a padded item + # 1 (cannot attend): + # If perm_index[i] <= perm_index[j] and j is either masked or a padded item + perm_mask[i] = ( + perm_index.reshape((labels.size(1), 1)) + <= perm_index.reshape((1, labels.size(1))) + ) & mask_labels[i] + # During evaluation always mask the last item of the session + else: + if self.eval_on_last_item_seq_only: + last_item_sessions = non_padded_mask.sum(dim=1) - 1 + labels[rows_ids, last_item_sessions] = item_ids[rows_ids, last_item_sessions] + mask_labels = labels != self.padding_idx + perm_mask = torch.zeros( + (labels.size(0), labels.size(1), labels.size(1)), + dtype=torch.float32, + device=item_ids.device, + ) + # Previous tokens don't see last non-padded token + perm_mask[rows_ids, :, last_item_sessions] = 1 + # add causal mask to avoid attending to future when evaluating + causal_mask = torch.ones([labels.size(1), labels.size(1)], device=item_ids.device) + mask_up = torch.triu(causal_mask, diagonal=1) + temp_perm = ( + mask_up.expand((labels.size(0), labels.size(1), labels.size(1))) + perm_mask + ) + perm_mask = (temp_perm > 0).long() + # the i-th predict corresponds to the i-th token. + target_mapping = torch.diag( + torch.ones(labels.size(1), dtype=torch.float32, device=item_ids.device) + ).expand((labels.size(0), labels.size(1), labels.size(1))) + + else: + # predict all next items + masking_info = self.predict_all(item_ids) + mask_labels, labels = masking_info.schema, masking_info.targets + # targets: the i-th predict corresponds to the i-th item in the sequence. + target_mapping = torch.nn.functional.one_hot( + torch.arange(0, labels.size(1), dtype=torch.long), num_classes=labels.size(1) + ) + target_mapping = target_mapping.expand( + (labels.size(0), labels.size(1), labels.size(1)) + ) + # perm_mask: causal mask + # Perm mask: + perm_mask = torch.zeros( + (labels.size(0), labels.size(1), labels.size(1)), + dtype=torch.float32, + device=item_ids.device, + ) + # add causal mask to avoid attending to future when evaluating + causal_mask = torch.ones([labels.size(1), labels.size(1)], device=item_ids.device) + mask_up = torch.triu(causal_mask, diagonal=1) + temp_perm = ( + mask_up.expand((labels.size(0), labels.size(1), labels.size(1))) + perm_mask + ) + perm_mask = (temp_perm > 0).long() + + return mask_labels, labels, target_mapping, perm_mask + +
[docs] def compute_masked_targets( + self, item_ids: torch.Tensor, training=False, **kwargs + ) -> MaskingInfo: + ( + self.mask_schema, + self.masked_targets, + self.target_mapping, + self.perm_mask, + ) = self._compute_masked_targets_extended(item_ids, training=training) + + return MaskingInfo(self.mask_schema, self.masked_targets)
+ +
[docs] def transformer_required_arguments(self) -> Dict[str, Any]: + return dict(target_mapping=self.target_mapping, perm_mask=self.perm_mask)
+ + +
[docs]@masking_registry.register_with_multiple_names("rtd", "replacement") +@docstring_parameter(mask_sequence_parameters=MASK_SEQUENCE_PARAMETERS_DOCSTRING) +class ReplacementLanguageModeling(MaskedLanguageModeling): + """ + Replacement Language Modeling (rtd) you use MLM to randomly select some items, but replace + them by random tokens. + Then, a discriminator model (that can share the weights with the generator or not), is asked + to classify whether the item at each position belongs or not to the original sequence. + The generator-discriminator architecture was jointly trained using Masked LM and RTD tasks. + + Parameters + ---------- + {mask_sequence_parameters} + sample_from_batch: bool + Whether to sample replacement item ids from the same batch or not + """ + + def __init__( + self, + hidden_size: int, + padding_idx: int = 0, + eval_on_last_item_seq_only: bool = True, + sample_from_batch: bool = False, + **kwargs + ): + super(ReplacementLanguageModeling, self).__init__( + hidden_size=hidden_size, + padding_idx=padding_idx, + eval_on_last_item_seq_only=eval_on_last_item_seq_only, + kwargs=kwargs, + ) + + self.sample_from_batch = sample_from_batch + +
[docs] def get_fake_tokens(self, itemid_seq, target_flat, logits): + """ + Second task of RTD is binary classification to train the discriminator. + The task consists of generating fake data by replacing [MASK] positions with random items, + ELECTRA discriminator learns to detect fake replacements. + + Parameters + ---------- + itemid_seq: torch.Tensor of shape (bs, max_seq_len) + input sequence of item ids + target_flat: torch.Tensor of shape (bs*max_seq_len) + flattened masked label sequences + logits: torch.Tensor of shape (#pos_item, vocab_size or #pos_item), + mlm probabilities of positive items computed by the generator model. + The logits are over the whole corpus if sample_from_batch = False, + over the positive items (masked) of the current batch otherwise + + Returns + ------- + corrupted_inputs: torch.Tensor of shape (bs, max_seq_len) + input sequence of item ids with fake replacement + discriminator_labels: torch.Tensor of shape (bs, max_seq_len) + binary labels to distinguish between original and replaced items + batch_updates: torch.Tensor of shape (#pos_item) + the indices of replacement item within the current batch if sample_from_batch is enabled + """ + # TODO: Generate fake interactions embeddings using metadatainfo in addition to item ids. + + # Replace only items that were masked during MLM + non_pad_mask = target_flat != self.padding_idx + pos_labels = torch.masked_select(target_flat, non_pad_mask) + # Sample random item ids + if self.sample_from_batch: + # get batch indices for replacement items + batch_updates = self.sample_from_softmax(logits).flatten() + # get item ids based on batch indices + updates = pos_labels[batch_updates] + else: + # get replacement item ids directly from logits over the whole corpus + updates = self.sample_from_softmax(logits).flatten() + batch_updates = [] + + # Replace masked labels by replacement item ids + # detach() is needed to not propagate the discriminator loss through generator + corrupted_labels = ( + target_flat.clone().detach().scatter(-1, non_pad_mask.nonzero().flatten(), updates) + ) + # Build discriminator label : distinguish original token from replaced one + discriminator_labels = (corrupted_labels != target_flat).view(-1, itemid_seq.size(1)) + # Build corrupted inputs : replacing [MASK] by sampled item + corrupted_inputs = ( + itemid_seq.clone() + .detach() + .reshape(-1) + .scatter(-1, non_pad_mask.nonzero().flatten(), updates) + ) + + return ( + corrupted_inputs.view(-1, itemid_seq.size(1)), + discriminator_labels, + batch_updates, + )
+ +
[docs] def sample_from_softmax(self, logits: torch.Tensor) -> torch.Tensor: + """ + Sampling method for replacement token modeling (ELECTRA) + + Parameters + ---------- + logits: torch.Tensor(pos_item, vocab_size) + scores of probability of masked positions returned by the generator model + + Returns + ------- + samples: torch.Tensor(#pos_item) + ids of replacements items. + """ + # add noise to logits to prevent from the case where the generator learn to exactly + # retrieve the true item that was masked + uniform_noise = torch.rand(logits.shape, dtype=logits.dtype, device=logits.device) + gumbel_noise = -torch.log(-torch.log(uniform_noise + 1e-9) + 1e-9) + s = logits + gumbel_noise + + return torch.argmax(torch.nn.functional.softmax(s, dim=-1), -1)
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/model/base.html b/review/pr-767/_modules/transformers4rec/torch/model/base.html new file mode 100644 index 0000000000..29b0aff68f --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/model/base.html @@ -0,0 +1,1014 @@ + + + + + + transformers4rec.torch.model.base — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.model.base
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.model.base

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import copy
+import inspect
+import os
+import pathlib
+from collections import defaultdict
+from types import SimpleNamespace
+from typing import Callable, Dict, Iterable, List, Optional, Type, Union, cast
+
+import numpy as np
+import torch
+import torchmetrics as tm
+from merlin.models.utils.registry import camelcase_to_snakecase
+from merlin.schema import ColumnSchema
+from merlin.schema import Schema as Core_Schema
+from merlin.schema import Tags
+from tqdm import tqdm
+from transformers.modeling_utils import SequenceSummary
+
+from merlin_standard_lib import Schema
+
+from ..block.base import BlockBase, BlockOrModule, BlockType
+from ..features.base import InputBlock
+from ..features.sequence import TabularFeaturesType
+from ..typing import TabularData
+from ..utils.padding import pad_inputs
+from ..utils.torch_utils import LossMixin, MetricsMixin
+
+
+def name_fn(name, inp):
+    return "/".join([name, inp]) if name else None
+
+
+
[docs]class PredictionTask(torch.nn.Module, LossMixin, MetricsMixin): + """Individual prediction-task of a model. + Parameters + ---------- + loss: torch.nn.Module + The loss to use during training of this task. + metrics: torch.nn.Module + The metrics to calculate during training & evaluation. + target_name: str, optional + Name of the target, this is needed when there are multiple targets. + task_name: str, optional + Name of the prediction task, if not provided a name will be automatically constructed based + on the target-name & class-name. + forward_to_prediction_fn: Callable[[torch.Tensor], torch.Tensor] + Function to apply before the prediction + task_block: BlockType + Module to transform input tensor before computing predictions. + pre: BlockType + Module to compute the predictions probabilities. + summary_type: str + This is used to summarize a sequence into a single tensor. Accepted values are: + - `"last"` -- Take the last token hidden state (like XLNet) + - `"first"` -- Take the first token hidden state (like Bert) + - `"mean"` -- Take the mean of all tokens hidden states + - `"cls_index"` -- Supply a Tensor of classification token position (GPT/GPT-2) + - `"attn"` -- Not implemented now, use multi-head attention + """ + + def __init__( + self, + loss: torch.nn.Module, + metrics: Iterable[tm.Metric] = None, + target_name: Optional[str] = None, + task_name: Optional[str] = None, + forward_to_prediction_fn: Callable[[torch.Tensor], torch.Tensor] = lambda x: x, + task_block: Optional[BlockType] = None, + pre: Optional[BlockType] = None, + summary_type: str = "last", + ): + super().__init__() + self.summary_type = summary_type + self.sequence_summary = SequenceSummary( + SimpleNamespace(summary_type=self.summary_type) # type: ignore + ) # noqa + self.target_name = target_name + self.forward_to_prediction_fn = forward_to_prediction_fn + self.set_metrics(metrics) + self.loss = loss + self.pre = pre + self.task_block = task_block + self._task_name = task_name + +
[docs] def build( + self, + body: BlockType, + input_size, + inputs: Optional[InputBlock] = None, + device=None, + task_block: Optional[BlockType] = None, + pre=None, + ): + """ + The method will be called when block is converted to a model, + i.e when linked to prediction head. + Parameters + ---------- + block: + the model block to link with head + device: + set the device for the metrics and layers of the task + """ + + if task_block: + # TODO: What to do when `self.task_block is not None`? + self.task_block = task_block + if pre: + # TODO: What to do when `self.pre is not None`? + self.pre = pre + + # Build task block + pre_input_size = input_size + if self.task_block: + if isinstance(self.task_block, torch.nn.Module): + self.task_block = copy.deepcopy(self.task_block) + else: + self.task_block = self.task_block.build(input_size) + pre_input_size = self.task_block.output_size() # type: ignore + + if self.pre: + if isinstance(self.pre, torch.nn.Module): + self.pre = copy.deepcopy(self.pre) + else: + self.pre = self.pre.build(pre_input_size) + + if device: + self.to(device) + for metric in self.metrics: + metric.to(device) + self.built = True
+ +
[docs] def forward( + self, + inputs: torch.Tensor, + targets: torch.Tensor = None, + training: bool = False, + testing: bool = False, + ): + x = inputs + + if len(x.size()) == 3 and self.summary_type: + x = self.sequence_summary(x) + + if self.task_block: + x = self.task_block(x) # type: ignore + + if self.pre: + x = self.pre(x) # type: ignore + + if training or testing: + # add support of computing the loss inside the forward + # and return a dictionary as standard output + if self.summary_type is None: + if targets.dim() != 2: + raise ValueError( + "If `summary_type==None`, targets are expected to be a 2D tensor, " + f"but got a tensor with shape {targets.shape}" + ) + + loss = self.loss(x, target=targets) + return {"loss": loss, "labels": targets, "predictions": x} + + return x
+ + @property + def task_name(self): + if self._task_name: + return self._task_name + + base_name = camelcase_to_snakecase(self.__class__.__name__) + + return name_fn(self.target_name, base_name) if self.target_name else base_name + +
[docs] def child_name(self, name): + return name_fn(self.task_name, name)
+ +
[docs] def set_metrics(self, metrics): + self.metrics = torch.nn.ModuleList(metrics)
+ +
[docs] def calculate_metrics( # type: ignore + self, + predictions: torch.Tensor, + targets: torch.Tensor, + ) -> Dict[str, torch.Tensor]: + outputs = {} + + predictions = self.forward_to_prediction_fn(cast(torch.Tensor, predictions)) + + from .prediction_task import BinaryClassificationTask + + for metric in self.metrics: + if isinstance(metric, tuple(type(x) for x in BinaryClassificationTask.DEFAULT_METRICS)): + targets = cast(torch.Tensor, targets).int() + outputs[self.metric_name(metric)] = metric(predictions, targets) + + return outputs
+ +
[docs] def compute_metrics(self, **kwargs): + return {self.metric_name(metric): metric.compute() for metric in self.metrics}
+ +
[docs] def metric_name(self, metric: tm.Metric) -> str: + return self.child_name(camelcase_to_snakecase(metric.__class__.__name__))
+ +
[docs] def reset_metrics(self): + for metric in self.metrics: + metric.reset()
+ +
[docs] def to_head(self, body, inputs=None, **kwargs) -> "Head": + return Head(body, self, inputs=inputs, **kwargs)
+ +
[docs] def to_model(self, body, inputs=None, **kwargs) -> "Model": + return Model(Head(body, self, inputs=inputs, **kwargs), **kwargs)
+ + + + + +
[docs]class Model(torch.nn.Module, LossMixin, MetricsMixin): + def __init__( + self, + *head: Head, + head_weights: Optional[List[float]] = None, + head_reduction: str = "mean", + optimizer: Type[torch.optim.Optimizer] = torch.optim.Adam, + name: str = None, + max_sequence_length: Optional[int] = None, + top_k: Optional[int] = None, + ): + """Model class that can aggregate one or multiple heads. + Parameters + ---------- + head: Head + One or more heads of the model. + head_weights: List[float], optional + Weight-value to use for each head. + head_reduction: str, optional + How to reduce the losses into a single tensor when multiple heads are used. + optimizer: Type[torch.optim.Optimizer] + Optimizer-class to use during fitting + name: str, optional + Name of the model. + max_sequence_length: int, optional + The maximum sequence length supported by the model. + Used to truncate sequence inputs longer than this value. + top_k: int, optional + The number of items to return at the inference step once the model is deployed. + Default is None, which will return all items. + """ + if head_weights: + if not isinstance(head_weights, list): + raise ValueError("`head_weights` must be a list") + if not len(head_weights) == len(head): + raise ValueError( + "`head_weights` needs to have the same length " "as the number of heads" + ) + + super().__init__() + + self.name = name + self.heads = torch.nn.ModuleList(head) + self.head_weights = head_weights or [1.0] * len(head) + self.head_reduction = head_reduction + self.optimizer = optimizer + self.max_sequence_length = max_sequence_length + self.top_k = top_k + +
[docs] def forward(self, inputs: TabularData, targets=None, training=False, testing=False, **kwargs): + # Convert inputs to float32 which is the default type, expected by PyTorch + for name, val in inputs.items(): + if torch.is_floating_point(val): + inputs[name] = val.to(torch.float32) + + # pad ragged inputs + inputs = pad_inputs(inputs, self.max_sequence_length) + + if isinstance(targets, dict) and len(targets) == 0: + # `pyarrow`` dataloader is returning {} instead of None + # TODO remove this code when `PyarraowDataLoader` is dropped + targets = None + + # TODO: Optimize this + if training or testing: + losses = [] + labels = {} + predictions = {} + for i, head in enumerate(self.heads): + head_output = head( + inputs, + call_body=True, + targets=targets, + training=training, + testing=testing, + **kwargs, + ) + labels.update(head_output["labels"]) + predictions.update(head_output["predictions"]) + losses.append(head_output["loss"] * self.head_weights[i]) + loss_tensor = torch.stack(losses) + loss = getattr(loss_tensor, self.head_reduction)() + if len(labels) == 1: + labels = list(labels.values())[0] + predictions = list(predictions.values())[0] + return {"loss": loss, "labels": labels, "predictions": predictions} + else: + outputs = {} + for head in self.heads: + outputs.update( + head( + inputs, + call_body=True, + targets=targets, + training=training, + testing=testing, + top_k=self.top_k, + **kwargs, + ) + ) + if len(outputs) == 1: + return list(outputs.values())[0] + + return outputs
+ +
[docs] def calculate_metrics( # type: ignore + self, + predictions: Union[torch.Tensor, TabularData], + targets: Union[torch.Tensor, TabularData], + ) -> Dict[str, Union[Dict[str, torch.Tensor], torch.Tensor]]: + """Calculate metrics of the task(s) set in the Head instance. + Parameters + ---------- + predictions: Union[torch.Tensor, TabularData] + The predictions tensors returned by the model. + They can be either a torch.Tensor if a single task is used or + a dictionary of torch.Tensor if multiple heads/tasks are used. In the + second case, the dictionary is indexed by the tasks names. + targets: + The tensor or dictionary of targets returned by the model. + They are used for computing the metrics of one or multiple tasks. + """ + outputs = {} + for head in self.heads: + outputs.update( + head.calculate_metrics( + predictions, + targets, + ) + ) + + return outputs
+ +
[docs] def compute_metrics(self, mode=None) -> Dict[str, Union[float, torch.Tensor]]: + metrics = {} + for head in self.heads: + metrics.update(head.compute_metrics(mode=mode)) + + return metrics
+ +
[docs] def reset_metrics(self): + for head in self.heads: + head.reset_metrics()
+ +
[docs] def to_lightning(self): + import pytorch_lightning as pl + + parent_self = self + + class BlockWithHeadLightning(pl.LightningModule): + def __init__(self): + super(BlockWithHeadLightning, self).__init__() + self.parent = parent_self + + def forward(self, inputs, targets=None, training=False, testing=False, *args, **kwargs): + return self.parent( + inputs, targets=targets, training=training, testing=testing, *args, **kwargs + ) + + def training_step(self, batch, batch_idx, targets=None, training=True, testing=False): + loss = self.parent(*batch, targets=targets, training=training, testing=testing)[ + "loss" + ] + self.log("train_loss", loss) + + return loss + + def configure_optimizers(self): + optimizer = self.parent.optimizer(self.parent.parameters(), lr=1e-3) + + return optimizer + + return BlockWithHeadLightning()
+ +
[docs] def fit( + self, + dataloader, + optimizer=torch.optim.Adam, + eval_dataloader=None, + num_epochs=1, + amp=False, + train=True, + verbose=True, + compute_metric=True, + ): + if isinstance(dataloader, torch.utils.data.DataLoader): + dataset = dataloader.dataset + else: + dataset = dataloader + + if inspect.isclass(optimizer): + optimizer = optimizer(self.parameters()) + + self.train(mode=train) + epoch_losses = [] + with torch.set_grad_enabled(mode=train): + for epoch in range(num_epochs): + losses = [] + batch_iterator = enumerate(iter(dataset)) + if verbose: + batch_iterator = tqdm(batch_iterator) + for batch_idx, (x, y) in batch_iterator: + if amp: + with torch.cuda.amp.autocast(): + output = self(x, targets=y, training=True) + else: + output = self(x, targets=y, training=True) + losses.append(float(output["loss"])) + if compute_metric: + self.calculate_metrics( + output["predictions"], + targets=output["labels"], + ) + if train: + optimizer.zero_grad() + output["loss"].backward() + optimizer.step() + if verbose: + print(self.compute_metrics(mode="train")) + if eval_dataloader: + print(self.evaluate(eval_dataloader, verbose=False)) + epoch_losses.append(np.mean(losses)) + + return np.array(epoch_losses)
+ +
[docs] def evaluate( + self, dataloader, targets=None, training=False, testing=True, verbose=True, mode="eval" + ): + if isinstance(dataloader, torch.utils.data.DataLoader): + dataset = dataloader.dataset + else: + dataset = dataloader + + batch_iterator = enumerate(iter(dataset)) + if verbose: + batch_iterator = tqdm(batch_iterator) + self.reset_metrics() + for batch_idx, (x, y) in batch_iterator: + output = self(x, targets=y, training=training, testing=testing) + self.calculate_metrics( + output["predictions"], + targets=output["labels"], + ) + + return self.compute_metrics(mode=mode)
+ + def _get_name(self): + if self.name: + return self.name + + return super(Model, self)._get_name() + + @property + def input_schema(self): + # return the input schema given by the model + # loop over the heads to get input schemas + schemas = [] + for head in self.heads: + schemas.append(head.body.inputs.schema) + if all(isinstance(s, Core_Schema) for s in schemas): + return sum(schemas, Core_Schema()) + + model_schema = sum(schemas, Schema()) + + # TODO: rework T4R to use Merlin Schemas. + # In the meantime, we convert model_schema to merlin core schema + core_schema = Core_Schema() + for column in model_schema: + name = column.name + + dtype = {0: np.float32, 2: np.int64, 3: np.float32}[column.type] + tags = column.tags + dims = None + if column.value_count.max > 0: + dims = (None, (column.value_count.min, column.value_count.max)) + int_domain = {"min": column.int_domain.min, "max": column.int_domain.max} + properties = { + "int_domain": int_domain, + } + + col_schema = ColumnSchema( + name, dtype=dtype, tags=tags, properties=properties, dims=dims + ) + core_schema[name] = col_schema + return core_schema + + @property + def output_schema(self): + from merlin.schema import Tags + + from .prediction_task import BinaryClassificationTask, RegressionTask + + # if the model has one head with one task, the output is a tensor + # if multiple heads and/or multiple prediction task, the output is a dictionary + output_cols = [] + for head in self.heads: + dims = None + for name, task in head.prediction_task_dict.items(): + target_dim = task.target_dim + int_domain = {"min": target_dim, "max": target_dim} + if ( + isinstance(task, (BinaryClassificationTask, RegressionTask)) + and not task.summary_type + ): + dims = (None, (1, None)) + elif ( + isinstance(task, (BinaryClassificationTask, RegressionTask)) + and task.summary_type + ): + dims = (None,) + else: + dims = (None, task.target_dim) + properties = { + "int_domain": int_domain, + } + # in case one sets top_k at the inference step we return two outputs + if self.top_k: + # be sure categ item-id dtype in model.input schema and output schema matches + col_name = self.input_schema.select_by_tag(Tags.ITEM_ID).column_names[0] + col_dtype = ( + self.input_schema.select_by_tag(Tags.ITEM_ID) + .column_schemas[col_name] + .dtype.name + ) + col_schema_scores = ColumnSchema( + "item_id_scores", dtype=np.float32, properties=properties, dims=dims + ) + col_schema_ids = ColumnSchema( + "item_ids", dtype=np.dtype(col_dtype), properties=properties, dims=dims + ) + output_cols.append(col_schema_scores) + output_cols.append(col_schema_ids) + else: + col_schema = ColumnSchema( + name, dtype=np.float32, properties=properties, dims=dims + ) + output_cols.append(col_schema) + + return Core_Schema(output_cols) + + @property + def prediction_tasks(self): + return [task for head in self.heads for task in list(head.prediction_task_dict.values())] + +
[docs] def save(self, path: Union[str, os.PathLike], model_name="t4rec_model_class"): + """Saves the model to f"{export_path}/{model_name}.pkl" using `cloudpickle` + Parameters + ---------- + path : Union[str, os.PathLike] + Path to the directory where the T4Rec model should be saved. + model_name : str, optional + the name given to the pickle file storing the T4Rec model, + by default 't4rec_model_class' + """ + try: + import cloudpickle + except ImportError: + raise ValueError("cloudpickle is required to save model class") + + export_path = pathlib.Path(path) + export_path.mkdir(exist_ok=True) + + model_name = model_name + ".pkl" + export_path = export_path / model_name + with open(export_path, "wb") as out: + cloudpickle.dump(self, out)
+ +
[docs] @classmethod + def load(cls, path: Union[str, os.PathLike], model_name="t4rec_model_class") -> "Model": + """Loads a T4Rec model that was saved with `model.save()`. + Parameters + ---------- + path : Union[str, os.PathLike] + Path to the directory where the T4Rec model is saved. + model_name : str, optional + the name given to the pickle file storing the T4Rec model, + by default 't4rec_model_class'. + """ + try: + import cloudpickle + except ImportError: + raise ValueError("cloudpickle is required to load T4Rec model") + + export_path = pathlib.Path(path) + model_name = model_name + ".pkl" + export_path = export_path / model_name + return cloudpickle.load(open(export_path, "rb"))
+ + +def _output_metrics(metrics): + # If there is only a single head with metrics, returns just those metrics + if len(metrics) == 1 and isinstance(metrics[list(metrics.keys())[0]], dict): + return metrics[list(metrics.keys())[0]] + + return metrics +
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/model/prediction_task.html b/review/pr-767/_modules/transformers4rec/torch/model/prediction_task.html new file mode 100644 index 0000000000..f6f852312c --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/model/prediction_task.html @@ -0,0 +1,990 @@ + + + + + + transformers4rec.torch.model.prediction_task — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.model.prediction_task
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.model.prediction_task

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+import logging
+from math import sqrt
+from typing import Dict, Iterable, Optional, Sequence, Tuple
+
+import torch
+import torchmetrics as tm
+
+from ..block.base import Block, BuildableBlock, SequentialBlock
+from ..block.mlp import MLPBlock
+from ..masking import MaskedLanguageModeling
+from ..ranking_metric import AvgPrecisionAt, NDCGAt, RecallAt
+from ..utils.torch_utils import LambdaModule
+from .base import BlockType, PredictionTask
+
+LOG = logging.getLogger("transformers4rec")
+
+
+
[docs]class BinaryClassificationPrepareBlock(BuildableBlock): + """Prepares the output layer of the binary classification prediction task. + The output layer is a SequentialBlock of a torch linear + layer followed by a sigmoid activation and a squeeze operation. + """ + +
[docs] def build(self, input_size) -> SequentialBlock: + """Builds the output layer of binary classification based on the input_size. + + Parameters + ---------- + input_size: Tuple[int] + The size of the input tensor, specifically the last dimension is + used for setting the input dimension of the linear layer. + + Returns + ------- + SequentialBlock + A SequentialBlock consisting of a linear layer (with input dimension equal to the last + dimension of input_size), a sigmoid activation, and a squeeze operation. + """ + return SequentialBlock( + torch.nn.Linear(input_size[-1], 1, bias=False), + torch.nn.Sigmoid(), + LambdaModule(lambda x: torch.squeeze(x, -1)), + output_size=[ + None, + ], + )
+ + +
[docs]class BinaryClassificationTask(PredictionTask): + """Returns a ``PredictionTask`` for binary classification. + + Example usage:: + + # Define the input module to process the tabular input features. + input_module = tr.TabularSequenceFeatures.from_schema( + schema, + max_sequence_length=max_sequence_length, + continuous_projection=d_model, + aggregation="concat", + masking=None, + ) + + # Define XLNetConfig class and set default parameters for HF XLNet config. + transformer_config = tr.XLNetConfig.build( + d_model=d_model, n_head=4, n_layer=2, total_seq_length=max_sequence_length + ) + + # Define the model block including: inputs, masking, projection and transformer block. + body = tr.SequentialBlock( + input_module, + tr.MLPBlock([64]), + tr.TransformerBlock( + transformer_config, + masking=input_module.masking + ) + ) + + # Define a head with BinaryClassificationTask. + head = tr.Head( + body, + tr.BinaryClassificationTask( + "click", + summary_type="mean", + metrics=[ + tm.Precision(task='binary'), + tm.Recall(task='binary'), + tm.Accuracy(task='binary'), + tm.F1Score(task='binary') + ] + ), + inputs=input_module, + ) + + # Get the end-to-end Model class. + model = tr.Model(head) + + Parameters + ---------- + + target_name: Optional[str] = None + Specifies the variable name that represents the positive and negative values. + + task_name: Optional[str] = None + Specifies the name of the prediction task. If this parameter is not specified, + a name is automatically constructed based on ``target_name`` and the Python + class name of the model. + + task_block: Optional[BlockType] = None + Specifies a module to transform the input tensor before computing predictions. + + loss: torch.nn.Module + Specifies the loss function for the task. + The default class is ``torch.nn.BCELoss``. + + metrics: Tuple[torch.nn.Module, ...] + Specifies the metrics to calculate during training and evaluation. + The default metrics are ``Precision``, ``Recall``, and ``Accuracy``. + + summary_type: str + Summarizes a sequence into a single tensor. Accepted values are: + + - ``last`` -- Take the last token hidden state (like XLNet) + - ``first`` -- Take the first token hidden state (like Bert) + - ``mean`` -- Take the mean of all tokens hidden states + - ``cls_index`` -- Supply a Tensor of classification token position (GPT/GPT-2) + - ``attn`` -- Not implemented now, use multi-head attention + """ + + DEFAULT_LOSS = torch.nn.BCELoss() + DEFAULT_METRICS = ( + tm.Precision(num_classes=2, task="binary"), + tm.Recall(num_classes=2, task="binary"), + tm.Accuracy(task="binary"), + # TODO: Fix this: tm.AUC() + ) + + def __init__( + self, + target_name: Optional[str] = None, + task_name: Optional[str] = None, + task_block: Optional[BlockType] = None, + loss=DEFAULT_LOSS, + metrics=DEFAULT_METRICS, + summary_type="first", + ): + self.target_dim = 1 + super().__init__( + loss=loss, + metrics=metrics, + target_name=target_name, + task_name=task_name, + summary_type=summary_type, + task_block=task_block, + pre=BinaryClassificationPrepareBlock(), + forward_to_prediction_fn=lambda x: torch.round(x).int(), + )
+ + +
[docs]class RegressionPrepareBlock(BuildableBlock): + """Prepares the output layer of the regression prediction task. + The output layer is a SequentialBlock of a torch linear + layer followed by a squeeze operation. + """ + +
[docs] def build(self, input_size) -> SequentialBlock: + """Builds the output layer of regression based on the input_size. + + Parameters + ---------- + input_size: Tuple[int] + The size of the input tensor, specifically the last dimension is + used for setting the input dimension of the linear layer. + + Returns + ------- + SequentialBlock + A SequentialBlock consisting of a linear layer (with input dimension equal to + the last dimension of input_size), and a squeeze operation. + """ + return SequentialBlock( + torch.nn.Linear(input_size[-1], 1), + LambdaModule(lambda x: torch.squeeze(x, -1)), + output_size=[ + None, + ], + )
+ + +
[docs]class RegressionTask(PredictionTask): + """Returns a ``PredictionTask`` for regression. + + Example usage:: + + # Define the input module to process the tabular input features. + input_module = tr.TabularSequenceFeatures.from_schema( + schema, + max_sequence_length=max_sequence_length, + continuous_projection=d_model, + aggregation="concat", + masking=None, + ) + + # Define XLNetConfig class and set default parameters for HF XLNet config. + transformer_config = tr.XLNetConfig.build( + d_model=d_model, n_head=4, n_layer=2, total_seq_length=max_sequence_length + ) + + # Define the model block including: inputs, projection and transformer block. + body = tr.SequentialBlock( + input_module, + tr.MLPBlock([64]), + tr.TransformerBlock( + transformer_config, + ) + ) + + # Define a head with BinaryClassificationTask. + head = tr.Head( + body, + tr.RegressionTask( + "watch_time", + summary_type="mean", + metrics=[tm.regression.MeanSquaredError()] + ), + inputs=input_module, + ) + + # Get the end-to-end Model class. + model = tr.Model(head) + + Parameters + ---------- + + target_name: Optional[str] + Specifies the variable name that represents the continuous value to predict. + By default None + + task_name: Optional[str] + Specifies the name of the prediction task. If this parameter is not specified, + a name is automatically constructed based on ``target_name`` and the Python + class name of the model. + By default None + + task_block: Optional[BlockType] = None + Specifies a module to transform the input tensor before computing predictions. + + loss: torch.nn.Module + Specifies the loss function for the task. + The default class is ``torch.nn.MSELoss``. + + metrics: Tuple[torch.nn.Module, ...] + Specifies the metrics to calculate during training and evaluation. + The default metric is MeanSquaredError. + + summary_type: str + Summarizes a sequence into a single tensor. Accepted values are: + + - ``last`` -- Take the last token hidden state (like XLNet) + - ``first`` -- Take the first token hidden state (like Bert) + - ``mean`` -- Take the mean of all tokens hidden states + - ``cls_index`` -- Supply a Tensor of classification token position (GPT/GPT-2) + - ``attn`` -- Not implemented now, use multi-head attention + """ + + DEFAULT_LOSS = torch.nn.MSELoss() + DEFAULT_METRICS = (tm.regression.MeanSquaredError(),) + + def __init__( + self, + target_name: Optional[str] = None, + task_name: Optional[str] = None, + task_block: Optional[BlockType] = None, + loss=DEFAULT_LOSS, + metrics=DEFAULT_METRICS, + summary_type="first", + ): + self.target_dim = 1 + super().__init__( + loss=loss, + metrics=metrics, + target_name=target_name, + task_name=task_name, + summary_type=summary_type, + task_block=task_block, + pre=RegressionPrepareBlock(), + )
+ + +
[docs]class NextItemPredictionTask(PredictionTask): + """This block performs item prediction task for session and sequential-based models. + It requires a body containing a masking schema to use for training and target generation. + For the supported masking schemes, please refers to: + https://nvidia-merlin.github.io/Transformers4Rec/stable/model_definition.html#sequence-masking + + Parameters + ---------- + loss: torch.nn.Module + Loss function to use. Defaults to NLLLos. + metrics: Iterable[torchmetrics.Metric] + List of ranking metrics to use for evaluation. + task_block: + Module to transform input tensor before computing predictions. + task_name: str, optional + Name of the prediction task, if not provided a name will be automatically constructed based + on the target-name & class-name. + weight_tying: bool + The item id embedding table weights are shared with the prediction network layer. + softmax_temperature: float + Softmax temperature, used to reduce model overconfidence, so that softmax(logits / T). + Value 1.0 reduces to regular softmax. + padding_idx: int + pad token id. + target_dim: int + vocabulary size of item ids + sampled_softmax: Optional[bool] + Enables sampled softmax. By default False + max_n_samples: Optional[int] + Number of samples for sampled softmax. By default 100 + """ + + DEFAULT_METRICS = ( + # default metrics suppose labels are int encoded + NDCGAt(top_ks=[10, 20], labels_onehot=True), + AvgPrecisionAt(top_ks=[10, 20], labels_onehot=True), + RecallAt(top_ks=[10, 20], labels_onehot=True), + ) + + def __init__( + self, + loss: torch.nn.Module = torch.nn.CrossEntropyLoss(), + metrics: Iterable[tm.Metric] = DEFAULT_METRICS, + task_block: Optional[BlockType] = None, + task_name: str = "next-item", + weight_tying: bool = False, + softmax_temperature: float = 1, + padding_idx: int = 0, + target_dim: int = None, + sampled_softmax: Optional[bool] = False, + max_n_samples: Optional[int] = 100, + ): + super().__init__(loss=loss, metrics=metrics, task_block=task_block, task_name=task_name) + self.softmax_temperature = softmax_temperature + self.weight_tying = weight_tying + self.padding_idx = padding_idx + self.target_dim = target_dim + self.sampled_softmax = sampled_softmax + self.max_n_samples = max_n_samples + + self.item_embedding_table = None + self.masking = None + +
[docs] def build(self, body, input_size, device=None, inputs=None, task_block=None, pre=None): + """Build method, this is called by the `Head`.""" + if not len(input_size) == 3 or isinstance(input_size, dict): + raise ValueError( + "NextItemPredictionTask needs a 3-dim vector as input, found:" f"{input_size}" + ) + + # Retrieve the embedding module to get the name of itemid col and its related table + if not inputs: + inputs = body.inputs + if not getattr(inputs, "item_id", None): + raise ValueError( + "For Item Prediction task a categorical_module " + "including an item_id column is required." + ) + self.embeddings = inputs.categorical_module + if not self.target_dim: + self.target_dim = self.embeddings.item_embedding_table.num_embeddings + if self.weight_tying: + self.item_embedding_table = self.embeddings.item_embedding_table + item_dim = self.item_embedding_table.weight.shape[1] + if input_size[-1] != item_dim and not task_block: + LOG.warning( + f"Projecting inputs of NextItemPredictionTask to'{item_dim}' " + f"As weight tying requires the input dimension '{input_size[-1]}' " + f"to be equal to the item-id embedding dimension '{item_dim}'" + ) + # project input tensors to same dimension as item-id embeddings + task_block = MLPBlock([item_dim], activation=None) + + # Retrieve the masking from the input block + self.masking = inputs.masking + if not self.masking: + raise ValueError( + "The input block should contain a masking schema for training and evaluation" + ) + self.padding_idx = self.masking.padding_idx + pre = NextItemPredictionPrepareBlock( + target_dim=self.target_dim, + weight_tying=self.weight_tying, + item_embedding_table=self.item_embedding_table, + softmax_temperature=self.softmax_temperature, + sampled_softmax=self.sampled_softmax, + max_n_samples=self.max_n_samples, + min_id=self.padding_idx + 1, + ) + super().build( + body, input_size, device=device, inputs=inputs, task_block=task_block, pre=pre + )
+ +
[docs] def forward( + self, + inputs: torch.Tensor, + targets=None, + training=False, + testing=False, + top_k=None, + **kwargs, + ): + if isinstance(inputs, (tuple, list)): + inputs = inputs[0] + x = inputs.float() + + if self.task_block: + x = self.task_block(x) # type: ignore + + # Retrieve labels from masking + if training or testing: + labels = self.masking.masked_targets # type: ignore + trg_flat = labels.flatten() + non_pad_mask = trg_flat != self.padding_idx + labels_all = torch.masked_select(trg_flat, non_pad_mask).long() + # remove padded items, keep only masked positions + x = self.remove_pad_3d(x, non_pad_mask) + y = labels_all + x, y = self.pre(x, targets=y, training=training, testing=testing) # type: ignore + + loss = self.loss(x, y) + return { + "loss": loss, + "labels": y, + "predictions": x, + } + else: + # Get the hidden position to use for predicting the next item + labels = self.embeddings.item_seq + non_pad_mask = labels != self.padding_idx + rows_ids = torch.arange(labels.size(0), dtype=torch.long, device=labels.device) + if isinstance(self.masking, MaskedLanguageModeling): + last_item_sessions = non_pad_mask.sum(dim=1) + else: + last_item_sessions = non_pad_mask.sum(dim=1) - 1 + x = x[rows_ids, last_item_sessions] + + # Compute predictions probs + x, _ = self.pre(x) # type: ignore + + if top_k is None: + return x + else: + preds_sorted_item_scores, preds_sorted_item_ids = torch.topk(x, k=top_k, dim=-1) + return preds_sorted_item_scores, preds_sorted_item_ids
+ +
[docs] def remove_pad_3d(self, inp_tensor, non_pad_mask): + # inp_tensor: (n_batch x seqlen x emb_dim) + inp_tensor = inp_tensor.flatten(end_dim=1) + inp_tensor_fl = torch.masked_select( + inp_tensor, non_pad_mask.unsqueeze(1).expand_as(inp_tensor) + ) + out_tensor = inp_tensor_fl.view(-1, inp_tensor.size(1)) + return out_tensor
+ +
[docs] def calculate_metrics(self, predictions, targets) -> Dict[str, torch.Tensor]: # type: ignore + if isinstance(targets, dict) and self.target_name: + targets = targets[self.target_name] + + outputs = {} + predictions = self.forward_to_prediction_fn(predictions) + + for metric in self.metrics: + result = metric(predictions, targets) + outputs[self.metric_name(metric)] = result + + return outputs
+ +
[docs] def compute_metrics(self): + metrics = { + self.metric_name(metric): metric.compute() + for metric in self.metrics + if getattr(metric, "top_ks", None) + } + # Explode metrics for each cut-off + # TODO make result generic: + # To accept a mix of ranking metrics and others not requiring top_ks ? + topks = {self.metric_name(metric): metric.top_ks for metric in self.metrics} + results = {} + for name, metric in metrics.items(): + # Fix for when using a single cut-off, as torch metrics convert results to scalar + # when a single element vector is returned + if len(metric.size()) == 0: + metric = metric.unsqueeze(0) + for measure, k in zip(metric, topks[name]): + results[f"{name}_{k}"] = measure + return results
+ + +
[docs]class NextItemPredictionPrepareBlock(BuildableBlock): + """Prepares the output layer of the next item prediction task. + The output layer is a an instance of `_NextItemPredictionTask` class. + + Parameters + ---------- + target_dim: int + The output dimension for next-item predictions. + weight_tying: bool, optional + If true, ties the weights of the prediction layer and the item embedding layer. + By default False. + item_embedding_table: torch.nn.Module, optional + The module containing the item embedding table. + By default None. + softmax_temperature: float, optional + The temperature to be applied to the softmax function. Defaults to 0. + sampled_softmax: bool, optional + If true, sampled softmax is used for approximating the full softmax function. + By default False. + max_n_samples: int, optional + The maximum number of samples when using sampled softmax. + By default 100. + min_id: int, optional + The minimum value of the range for the log-uniform sampling. + By default 0. + """ + + def __init__( + self, + target_dim: int, + weight_tying: bool = False, + item_embedding_table: Optional[torch.nn.Module] = None, + softmax_temperature: float = 0, + sampled_softmax: Optional[bool] = False, + max_n_samples: Optional[int] = 100, + min_id: Optional[int] = 0, + ): + super().__init__() + self.target_dim = target_dim + self.weight_tying = weight_tying + self.item_embedding_table = item_embedding_table + self.softmax_temperature = softmax_temperature + self.sampled_softmax = sampled_softmax + self.max_n_samples = max_n_samples + self.min_id = min_id + +
[docs] def build(self, input_size) -> Block: + """Builds the output layer of next-item prediction based on the input_size. + + Parameters + ---------- + input_size : Tuple[int] + The size of the input tensor, specifically the last dimension is + used for setting the input dimension of the output layer. + Returns + ------- + Block[_NextItemPredictionTask] + an instance of _NextItemPredictionTask + """ + return Block( + _NextItemPredictionTask( + input_size, + self.target_dim, + self.weight_tying, + self.item_embedding_table, + self.softmax_temperature, + self.sampled_softmax, + self.max_n_samples, + self.min_id, + ), + [-1, self.target_dim], + )
+ + +class _NextItemPredictionTask(torch.nn.Module): + """Predict the interacted item-id probabilities. + + - During inference, the task consists of predicting the next item. + - During training, the class supports the following Language modeling tasks: + Causal LM, Masked LM, Permutation LM and Replacement Token Detection + + Parameters: + ----------- + input_size: int + Input size of this module. + target_dim: int + Dimension of the target. + weight_tying: bool + The item id embedding table weights are shared with the prediction network layer. + item_embedding_table: torch.nn.Module + Module that's used to store the embedding table for the item. + softmax_temperature: float + Softmax temperature, used to reduce model overconfidence, so that softmax(logits / T). + Value 1.0 reduces to regular softmax. + sampled_softmax: Optional[bool] + Enables sampled softmax. By default False + max_n_samples: Optional[int] + Number of samples for sampled softmax. By default 100 + min_id : Optional[int] + The minimum value of the range for the log-uniform sampling. By default 0. + """ + + def __init__( + self, + input_size: Sequence, + target_dim: int, + weight_tying: bool = False, + item_embedding_table: Optional[torch.nn.Module] = None, + softmax_temperature: float = 0, + sampled_softmax: Optional[bool] = False, + max_n_samples: Optional[int] = 100, + min_id: Optional[int] = 0, + ): + super().__init__() + self.input_size = input_size + self.target_dim = target_dim + self.weight_tying = weight_tying + self.item_embedding_table = item_embedding_table + self.softmax_temperature = softmax_temperature + self.sampled_softmax = sampled_softmax + + if not self.weight_tying: + self.output_layer = torch.nn.Parameter(torch.empty(self.target_dim, input_size[-1])) + torch.nn.init.kaiming_uniform_(self.output_layer, a=sqrt(5)) + + if self.sampled_softmax: + self.sampler = LogUniformSampler( + max_n_samples=max_n_samples, + max_id=target_dim, + min_id=min_id, + unique_sampling=True, + ) + + def forward( + self, + inputs: torch.Tensor, + targets: Optional[torch.Tensor] = None, + training=False, + testing=False, + **kwargs, + ) -> Tuple[torch.Tensor, torch.Tensor]: + if self.weight_tying: + output_weights = self.item_embedding_table.weight + else: + output_weights = self.output_layer + + if self.sampled_softmax and training: + logits, targets = self.sampled(inputs, targets, output_weights) + else: + logits = inputs @ output_weights.t() # type: ignore + + if self.softmax_temperature: + # Softmax temperature to reduce model overconfidence + # and better calibrate probs and accuracy + logits = torch.div(logits, self.softmax_temperature) + + return logits, targets + + def sampled(self, inputs, targets, output_weights): + """Returns logits using sampled softmax""" + neg_samples, targets_probs, samples_probs = self.sampler.sample(targets) + + positive_weights = output_weights[targets] + negative_weights = output_weights[neg_samples] + + positive_scores = (inputs * positive_weights).sum(dim=-1, keepdim=True) + negative_scores = inputs @ negative_weights.t() + + # logQ correction, to not overpenalize popular items for being sampled + # more often as negatives + epsilon = 1e-16 + positive_scores -= torch.unsqueeze(torch.log(targets_probs + epsilon), dim=-1) + negative_scores -= torch.unsqueeze(torch.log(samples_probs + epsilon), dim=0) + + # Remove accidental matches + accidental_hits = torch.unsqueeze(targets, -1) == torch.unsqueeze(neg_samples, 0) + negative_scores[accidental_hits] = torch.finfo(torch.float16).min / 100.0 + + logits = torch.cat([positive_scores, negative_scores], axis=1) + new_targets = torch.zeros(logits.shape[0], dtype=torch.int64, device=targets.device) + + return logits, new_targets + + def _get_name(self) -> str: + return "NextItemPredictionTask" + + +
[docs]class LogUniformSampler(torch.nn.Module): + def __init__( + self, + max_n_samples: int, + max_id: int, + min_id: Optional[int] = 0, + unique_sampling: bool = True, + n_samples_multiplier_before_unique: int = 2, + ): + """LogUniformSampler samples negative samples based on a log-uniform distribution. + `P(class) = (log(class + 2) - log(class + 1)) / log(max_id + 1)` + + This implementation is based on to: + https://github.com/kimiyoung/transformer-xl/blob/master/pytorch/utils/log_uniform_sampler.py + TensorFlow Reference: + https://github.com/tensorflow/tensorflow/blob/r1.10/tensorflow/python/ops/candidate_sampling_ops.py + + LogUniformSampler assumes item ids are sorted decreasingly by their frequency. + + if `unique_sampling==True`, then only unique sampled items will be returned. + The actual # samples will vary from run to run if `unique_sampling==True`, + as sampling without replacement (`torch.multinomial(..., replacement=False)`) is slow, + so we use `torch.multinomial(..., replacement=True).unique()` which doesn't guarantee + the same number of unique sampled items. You can try to increase + n_samples_multiplier_before_unique to increase the chances to have more + unique samples in that case. + + Parameters + ---------- + max_n_samples : int + The maximum desired number of negative samples. The number of samples might be + smaller than that if `unique_sampling==True`, as explained above. + max_id : int + The maximum value of the range for the log-uniform distribution. + min_id : Optional[int] + The minimum value of the range for the log-uniform sampling. By default 0. + unique_sampling : bool + Whether to return unique samples. By default True + n_samples_multiplier_before_unique : int + If unique_sampling=True, it is not guaranteed that the number of returned + samples will be equal to max_n_samples, as explained above. + You can increase n_samples_multiplier_before_unique to maximize + chances that a larger number of unique samples is returned. + """ + super().__init__() + + if max_id <= 0: + raise ValueError("max_id must be a positive integer.") + if max_n_samples <= 0: + raise ValueError("n_sample must be a positive integer.") + + self.max_id = max_id + self.unique_sampling = unique_sampling + self.max_n_samples = max_n_samples + self.n_sample = max_n_samples + if self.unique_sampling: + self.n_sample = int(self.n_sample * n_samples_multiplier_before_unique) + + with torch.no_grad(): + dist = self.get_log_uniform_distr(max_id, min_id) + self.register_buffer("dist", dist) + unique_sampling_dist = self.get_unique_sampling_distr(dist, self.n_sample) + self.register_buffer("unique_sampling_dist", unique_sampling_dist) + +
[docs] def get_log_uniform_distr(self, max_id: int, min_id: int = 0) -> torch.Tensor: + """Approximates the items frequency distribution with log-uniform probability distribution + with P(class) = (log(class + 2) - log(class + 1)) / log(max_id + 1). + It assumes item ids are sorted decreasingly by their frequency. + + Parameters + ---------- + max_id : int + Maximum discrete value for sampling (e.g. cardinality of the item id) + + Returns + ------- + torch.Tensor + Returns the log uniform probability distribution + """ + log_indices = torch.arange(1.0, max_id - min_id + 2.0, 1.0).log_() + probs = (log_indices[1:] - log_indices[:-1]) / log_indices[-1] + if min_id > 0: + probs = torch.cat( + [torch.zeros([min_id], dtype=probs.dtype), probs], axis=0 + ) # type: ignore + return probs
+ +
[docs] def get_unique_sampling_distr(self, dist, n_sample): + """Returns the probability that each item is sampled at least once + given the specified number of trials. This is meant to be used when + self.unique_sampling == True. + That probability can be approximated by by 1 - (1 - p)^n + and we use a numerically stable version: -expm1(num_tries * log1p(-p)) + """ + return (-(-dist.double().log1p_() * n_sample).expm1_()).float()
+ +
[docs] def sample(self, labels: torch.Tensor): + """Sample negative samples and calculate their probabilities. + + If `unique_sampling==True`, then only unique sampled items will be returned. + The actual # samples will vary from run to run if `unique_sampling==True`, + as sampling without replacement (`torch.multinomial(..., replacement=False)`) is slow, + so we use `torch.multinomial(..., replacement=True).unique()` + which doesn't guarantee the same number of unique sampled items. + You can try to increase n_samples_multiplier_before_unique + to increase the chances to have more unique samples in that case. + + Parameters + ---------- + labels : torch.Tensor, dtype=torch.long, shape=(batch_size,) + The input labels for which negative samples should be generated. + + Returns + ------- + neg_samples : torch.Tensor, dtype=torch.long, shape=(n_samples,) + The unique negative samples drawn from the log-uniform distribution. + true_probs : torch.Tensor, dtype=torch.float32, shape=(batch_size,) + The probabilities of the input labels according + to the log-uniform distribution (depends on self.unique_sampling choice). + samp_log_probs : torch.Tensor, dtype=torch.float32, shape=(n_samples,) + The probabilities of the sampled negatives according + to the log-uniform distribution (depends on self.unique_sampling choice). + """ + + if not torch.is_tensor(labels): + raise TypeError("Labels must be a torch.Tensor.") + if labels.dtype != torch.long: + raise ValueError("Labels must be a tensor of dtype long.") + if labels.dim() > 2 or (labels.dim() == 2 and min(labels.shape) > 1): + raise ValueError( + "Labels must be a 1-dimensional tensor or a 2-dimensional tensor" + "with one of the dimensions equal to 1." + ) + if labels.size(0) == 0: + raise ValueError("Labels must not be an empty tensor.") + if (labels < 0).any() or (labels > self.max_id).any(): + raise ValueError("All label values must be within the range [0, max_id].") + + n_tries = self.n_sample + + with torch.no_grad(): + neg_samples = torch.multinomial( + self.dist, n_tries, replacement=True # type: ignore + ).unique()[: self.max_n_samples] + + device = labels.device + neg_samples = neg_samples.to(device) + + if self.unique_sampling: + dist = self.unique_sampling_dist + else: + dist = self.dist + + true_probs = dist[labels] # type: ignore + samples_probs = dist[neg_samples] # type: ignore + + return neg_samples, true_probs, samples_probs
+ +
[docs] def forward(self, labels): + return self.sample(labels)
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/ranking_metric.html b/review/pr-767/_modules/transformers4rec/torch/ranking_metric.html new file mode 100644 index 0000000000..321f420fa0 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/ranking_metric.html @@ -0,0 +1,448 @@ + + + + + + transformers4rec.torch.ranking_metric — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.ranking_metric
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.ranking_metric

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Adapted from source code: https://github.com/karlhigley/ranking-metrics-torch
+from abc import abstractmethod
+
+import torch
+import torchmetrics as tm
+from merlin.models.utils.registry import Registry
+from torchmetrics.utilities.data import dim_zero_cat
+
+from .utils import torch_utils
+
+ranking_metrics_registry = Registry.class_registry("torch.ranking_metrics")
+
+
+
[docs]class RankingMetric(tm.Metric): + """ + Metric wrapper for computing ranking metrics@K for session-based task. + + Parameters + ---------- + top_ks : list, default [2, 5]) + list of cutoffs + labels_onehot : bool + Enable transform the labels to one-hot representation + """ + + def __init__(self, top_ks=None, labels_onehot=False): + super(RankingMetric, self).__init__() + self.top_ks = top_ks or [2, 5] + if not isinstance(self.top_ks, (list, tuple)): + self.top_ks = [self.top_ks] + + self.labels_onehot = labels_onehot + # Store the mean of the batch metrics (for each cut-off at topk) + self.add_state("metric_mean", default=[], dist_reduce_fx="cat") + +
[docs] def update(self, preds: torch.Tensor, target: torch.Tensor, **kwargs): # type: ignore + # Computing the metrics at different cut-offs + if self.labels_onehot: + target = torch_utils.tranform_label_to_onehot(target, preds.size(-1)) + metric = self._metric( + self.top_ks, preds.view(-1, preds.size(-1)), target.view(-1, target.size(-1)) + ) + self.metric_mean.append(metric) # type: ignore
+ +
[docs] def compute(self): + # Computing the mean of the batch metrics (for each cut-off at topk) + return dim_zero_cat(self.metric_mean).mean(0)
+ + @abstractmethod + def _metric(self, ks: torch.Tensor, preds: torch.Tensor, target: torch.Tensor) -> torch.Tensor: + """ + Compute a ranking metric over a predictions and one-hot targets. + This method should be overridden by subclasses. + """
+ + +
[docs]@ranking_metrics_registry.register_with_multiple_names("precision_at", "precision") +class PrecisionAt(RankingMetric): + def __init__(self, top_ks=None, labels_onehot=False): + super(PrecisionAt, self).__init__(top_ks=top_ks, labels_onehot=labels_onehot) + + def _metric(self, ks: torch.Tensor, scores: torch.Tensor, labels: torch.Tensor) -> torch.Tensor: + """Compute precision@K for each of the provided cutoffs + + Parameters + ---------- + ks : torch.Tensor or list + list of cutoffs + scores : torch.Tensor + predicted item scores + labels : torch.Tensor + true item labels + + Returns + ------- + torch.Tensor: + list of precisions at cutoffs + """ + + ks, scores, labels = torch_utils.check_inputs(ks, scores, labels) + _, _, topk_labels = torch_utils.extract_topk(ks, scores, labels) + precisions = torch_utils.create_output_placeholder(scores, ks) + + for index, k in enumerate(ks): + precisions[:, index] = torch.sum(topk_labels[:, : int(k)], dim=1) / float(k) + + return precisions
+ + +
[docs]@ranking_metrics_registry.register_with_multiple_names("recall_at", "recall") +class RecallAt(RankingMetric): + def __init__(self, top_ks=None, labels_onehot=False): + super(RecallAt, self).__init__(top_ks=top_ks, labels_onehot=labels_onehot) + + def _metric(self, ks: torch.Tensor, scores: torch.Tensor, labels: torch.Tensor) -> torch.Tensor: + """Compute recall@K for each of the provided cutoffs + + Parameters + ---------- + ks : torch.Tensor or list + list of cutoffs + scores : torch.Tensor + predicted item scores + labels : torch.Tensor + true item labels + + Returns + ------- + torch.Tensor: list of recalls at cutoffs + """ + + ks, scores, labels = torch_utils.check_inputs(ks, scores, labels) + _, _, topk_labels = torch_utils.extract_topk(ks, scores, labels) + recalls = torch_utils.create_output_placeholder(scores, ks) + + # Compute recalls at K + num_relevant = torch.sum(labels, dim=-1) + rel_indices = (num_relevant != 0).nonzero().squeeze() + rel_count = num_relevant[rel_indices] + + if rel_indices.shape[0] > 0: + for index, k in enumerate(ks): + rel_labels = topk_labels[rel_indices, : int(k)] + + recalls[rel_indices, index] = torch.div( + torch.sum(rel_labels, dim=-1), rel_count + ).to( + dtype=torch.float32 + ) # Ensuring type is double, because it can be float if --fp16 + + return recalls
+ + +
[docs]@ranking_metrics_registry.register_with_multiple_names("avg_precision_at", "avg_precision", "map") +class AvgPrecisionAt(RankingMetric): + def __init__(self, top_ks=None, labels_onehot=False): + super(AvgPrecisionAt, self).__init__(top_ks=top_ks, labels_onehot=labels_onehot) + self.precision_at = PrecisionAt(top_ks)._metric + + def _metric(self, ks: torch.Tensor, scores: torch.Tensor, labels: torch.Tensor) -> torch.Tensor: + """Compute average precision at K for provided cutoffs + + Parameters + ---------- + ks : torch.Tensor or list + list of cutoffs + scores : torch.Tensor + 2-dim tensor of predicted item scores + labels : torch.Tensor + 2-dim tensor of true item labels + + Returns + ------- + torch.Tensor: + list of average precisions at cutoffs + """ + ks, scores, labels = torch_utils.check_inputs(ks, scores, labels) + topk_scores, _, topk_labels = torch_utils.extract_topk(ks, scores, labels) + avg_precisions = torch_utils.create_output_placeholder(scores, ks) + + # Compute average precisions at K + num_relevant = torch.sum(labels, dim=1) + max_k = max(ks) + + precisions = self.precision_at(list(range(1, max_k + 1)), topk_scores, topk_labels) + rel_precisions = precisions * topk_labels + + for index, k in enumerate(ks): + total_prec = rel_precisions[:, : int(k)].sum(dim=1) + avg_precisions[:, index] = total_prec / num_relevant.clamp(min=1, max=k).to( + dtype=torch.float32, device=scores.device + ) # Ensuring type is double, because it can be float if --fp16 + + return avg_precisions
+ + +
[docs]@ranking_metrics_registry.register_with_multiple_names("dcg_at", "dcg") +class DCGAt(RankingMetric): + def __init__(self, top_ks=None, labels_onehot=False): + super(DCGAt, self).__init__(top_ks=top_ks, labels_onehot=labels_onehot) + + def _metric( + self, ks: torch.Tensor, scores: torch.Tensor, labels: torch.Tensor, log_base: int = 2 + ) -> torch.Tensor: + """Compute discounted cumulative gain at K for provided cutoffs (ignoring ties) + + Parameters + ---------- + ks : torch.Tensor or list + list of cutoffs + scores : torch.Tensor + predicted item scores + labels : torch.Tensor + true item labels + + Returns + ------- + torch.Tensor : + list of discounted cumulative gains at cutoffs + """ + ks, scores, labels = torch_utils.check_inputs(ks, scores, labels) + topk_scores, topk_indices, topk_labels = torch_utils.extract_topk(ks, scores, labels) + dcgs = torch_utils.create_output_placeholder(scores, ks) + + # Compute discounts + discount_positions = torch.arange(max(ks)).to(device=scores.device, dtype=torch.float32) + + discount_log_base = torch.log( + torch.Tensor([log_base]).to(device=scores.device, dtype=torch.float32) + ).item() + + discounts = 1 / (torch.log(discount_positions + 2) / discount_log_base) + + # Compute DCGs at K + for index, k in enumerate(ks): + dcgs[:, index] = torch.sum( + (topk_labels[:, :k] * discounts[:k].repeat(topk_labels.shape[0], 1)), dim=1 + ).to( + dtype=torch.float32, device=scores.device + ) # Ensuring type is double, because it can be float if --fp16 + + return dcgs
+ + +
[docs]@ranking_metrics_registry.register_with_multiple_names("ndcg_at", "ndcg") +class NDCGAt(RankingMetric): + def __init__(self, top_ks=None, labels_onehot=False): + super(NDCGAt, self).__init__(top_ks=top_ks, labels_onehot=labels_onehot) + self.dcg_at = DCGAt(top_ks)._metric + + def _metric( + self, ks: torch.Tensor, scores: torch.Tensor, labels: torch.Tensor, log_base: int = 2 + ) -> torch.Tensor: + """Compute normalized discounted cumulative gain at K for provided cutoffs (ignoring ties) + + Parameters + ---------- + ks : torch.Tensor or list + list of cutoffs + scores : torch.Tensor + predicted item scores + labels : torch.Tensor + true item labels + + Returns + ------- + torch.Tensor : + list of discounted cumulative gains at cutoffs + """ + ks, scores, labels = torch_utils.check_inputs(ks, scores, labels) + topk_scores, topk_indices, topk_labels = torch_utils.extract_topk(ks, scores, labels) + # ndcgs = _create_output_placeholder(scores, ks) #TODO track if this line is needed + + # Compute discounted cumulative gains + gains = self.dcg_at(ks, topk_scores, topk_labels) + normalizing_gains = self.dcg_at(ks, topk_labels, topk_labels) + + # Prevent divisions by zero + relevant_pos = (normalizing_gains != 0).nonzero(as_tuple=True) + irrelevant_pos = (normalizing_gains == 0).nonzero(as_tuple=True) + + gains[irrelevant_pos] = 0 + gains[relevant_pos] /= normalizing_gains[relevant_pos] + + return gains
+ + +
[docs]@ranking_metrics_registry.register_with_multiple_names("mrr_at", "mrr") +class MeanReciprocalRankAt(RankingMetric): + def __init__(self, top_ks=None, labels_onehot=False): + super(MeanReciprocalRankAt, self).__init__(top_ks=top_ks, labels_onehot=labels_onehot) + + def _metric( + self, ks: torch.Tensor, scores: torch.Tensor, labels: torch.Tensor, log_base: int = 2 + ) -> torch.Tensor: + """Compute mean recipricol rank at K for provided cutoffs (ignoring ties) + + Parameters + ---------- + ks : torch.Tensor or list + list of cutoffs + scores : torch.Tensor + predicted item scores + labels : torch.Tensor + true item labels + + Returns + ------- + torch.Tensor : + list of mean recipricol rank at cutoffs + """ + ks, scores, labels = torch_utils.check_inputs(ks, scores, labels) + topk_scores, topk_indices, topk_labels = torch_utils.extract_topk(ks, scores, labels) + + results = torch.zeros(scores.shape[0], len(ks)).to( + device=scores.device, dtype=torch.float32 + ) + for index, k in enumerate(ks): + values, _ = (topk_labels[:, :k] / (torch.arange(k) + 1).to(device=scores.device)).max( + dim=1 + ) + results[:, index] = values + return results
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/tabular/aggregation.html b/review/pr-767/_modules/transformers4rec/torch/tabular/aggregation.html new file mode 100644 index 0000000000..ea53cf5e16 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/tabular/aggregation.html @@ -0,0 +1,322 @@ + + + + + + transformers4rec.torch.tabular.aggregation — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.tabular.aggregation
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.tabular.aggregation

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import torch
+
+from merlin_standard_lib import Schema
+
+from ...config.schema import requires_schema
+from ..typing import TabularData
+from ..utils.torch_utils import calculate_batch_size_from_input_size
+from .base import TabularAggregation, tabular_aggregation_registry
+
+
+
[docs]@tabular_aggregation_registry.register("concat") +class ConcatFeatures(TabularAggregation): + """Aggregation by stacking all values in TabularData, all non-sequential values will be + converted to a sequence. + + The output of this concatenation will have 3 dimensions. + """ + +
[docs] def forward( + self, + inputs: TabularData, + ) -> torch.Tensor: + self._expand_non_sequential_features(inputs) + self._check_concat_shapes(inputs) + + tensors = [] + for name in sorted(inputs.keys()): + val = inputs[name] + tensors.append(val) + + return torch.cat(tensors, dim=-1)
+ +
[docs] def forward_output_size(self, input_size): + agg_dim = sum([i[-1] for i in input_size.values()]) + output_size = self._get_agg_output_size(input_size, agg_dim) + return output_size
+ + +
[docs]@tabular_aggregation_registry.register("stack") +class StackFeatures(TabularAggregation): + """Aggregation by stacking all values in input dictionary in the given dimension. + + Parameters + ---------- + axis: int, default=-1 + Axis to use for the stacking operation. + """ + + def __init__(self, axis: int = -1): + super().__init__() + self.axis = axis + +
[docs] def forward(self, inputs: TabularData) -> torch.Tensor: + self._expand_non_sequential_features(inputs) + self._check_concat_shapes(inputs) + + tensors = [] + for name in sorted(inputs.keys()): + tensors.append(inputs[name]) + + return torch.stack(tensors, dim=self.axis)
+ +
[docs] def forward_output_size(self, input_size): + batch_size = calculate_batch_size_from_input_size(input_size) + seq_features_shapes, sequence_length = self._get_seq_features_shapes(input_size) + + if len(seq_features_shapes) > 0: + output_size = [ + batch_size, + sequence_length, + ] + else: + output_size = [batch_size] + + num_features = len(input_size) + if self.axis == -1: + output_size.append(num_features) + else: + output_size.insert(self.axis, num_features) + + return tuple(output_size)
+ + +
[docs]class ElementwiseFeatureAggregation(TabularAggregation): + """Base class for aggregation methods that aggregates + features element-wise. + It implements two check methods to ensure inputs have the correct shape. + """ + + def _check_input_shapes_equal(self, inputs: TabularData): + """Checks if the shapes of all inputs are equal. + + Parameters + ---------- + inputs : TabularData + Dictionary of tensors. + + """ + all_input_shapes_equal = len(set([x.shape for x in inputs.values()])) == 1 + if not all_input_shapes_equal: + raise ValueError( + "The shapes of all input features are not equal, which is required for" + " element-wise aggregation: {}".format({k: v.shape for k, v in inputs.items()}) + ) + + def _check_inputs_last_dim_equal(self, inputs_sizes): + """ + Checks if the last dimensions of all inputs are equal. + + Parameters + ---------- + inputs_sizes : dict[str, Union[List[int], torch.Size]] + A dictionary containing the sizes of the inputs. + """ + all_input_last_dim_equal = len(set([x[-1] for x in inputs_sizes.values()])) == 1 + if not all_input_last_dim_equal: + raise ValueError( + f"The last dim of all input features is not equal, which is" + f" required for element-wise aggregation: {inputs_sizes}" + )
+ + +
[docs]@tabular_aggregation_registry.register("element-wise-sum") +class ElementwiseSum(ElementwiseFeatureAggregation): + """Aggregation by first stacking all values in TabularData in the first dimension, and then + summing the result.""" + + def __init__(self): + super().__init__() + self.stack = StackFeatures(axis=0) + +
[docs] def forward(self, inputs: TabularData) -> torch.Tensor: + self._expand_non_sequential_features(inputs) + self._check_input_shapes_equal(inputs) + return self.stack(inputs).sum(dim=0)
+ +
[docs] def forward_output_size(self, input_size): + self._check_inputs_last_dim_equal(input_size) + agg_dim = list(input_size.values())[0][-1] + output_size = self._get_agg_output_size(input_size, agg_dim) + return output_size
+ + +
[docs]@tabular_aggregation_registry.register("element-wise-sum-item-multi") +@requires_schema +class ElementwiseSumItemMulti(ElementwiseFeatureAggregation): + """Aggregation by applying the `ElementwiseSum` aggregation to all features except the item-id, + and then multiplying this with the item-ids. + + Parameters + ---------- + schema: DatasetSchema + """ + + def __init__(self, schema: Schema = None): + super().__init__() + self.stack = StackFeatures(axis=0) + self.schema = schema + self.item_id_col_name = None + +
[docs] def forward(self, inputs: TabularData) -> torch.Tensor: + item_id_inputs = self.get_item_ids_from_inputs(inputs) + + self._expand_non_sequential_features(inputs) + self._check_input_shapes_equal(inputs) + + schema: Schema = self.schema # type: ignore + other_inputs = {k: v for k, v in inputs.items() if k != schema.item_id_column_name} + other_inputs_sum = self.stack(other_inputs).sum(dim=0) + result = item_id_inputs.multiply(other_inputs_sum) + return result
+ +
[docs] def forward_output_size(self, input_size): + self._check_inputs_last_dim_equal(input_size) + agg_dim = list(input_size.values())[0][-1] + output_size = self._get_agg_output_size(input_size, agg_dim) + return output_size
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/tabular/base.html b/review/pr-767/_modules/transformers4rec/torch/tabular/base.html new file mode 100644 index 0000000000..21a38cde28 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/tabular/base.html @@ -0,0 +1,779 @@ + + + + + + transformers4rec.torch.tabular.base — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.tabular.base
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.tabular.base

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from abc import ABC
+from copy import deepcopy
+from functools import reduce
+from typing import Dict, List, Optional, Tuple, Union
+
+import torch
+from merlin.models.utils.doc_utils import docstring_parameter
+from merlin.models.utils.registry import Registry
+
+from merlin_standard_lib import Schema
+
+from ..block.base import BlockBase, SequentialBlock, right_shift_block
+from ..typing import TabularData, TensorOrTabularData
+from ..utils.torch_utils import OutputSizeMixin, calculate_batch_size_from_input_size
+
+tabular_transformation_registry: Registry = Registry.class_registry("torch.tabular_transformations")
+tabular_aggregation_registry: Registry = Registry.class_registry("torch.tabular_aggregations")
+
+
+
[docs]class TabularTransformation(OutputSizeMixin, torch.nn.Module, ABC): + """Transformation that takes in `TabularData` and outputs `TabularData`.""" + +
[docs] def forward(self, inputs: TabularData, **kwargs) -> TabularData: + raise NotImplementedError()
+ +
[docs] @classmethod + def parse(cls, class_or_str): + return tabular_transformation_registry.parse(class_or_str)
+ + +
[docs]class TabularAggregation(OutputSizeMixin, torch.nn.Module, ABC): + """Aggregation of `TabularData` that outputs a single `Tensor`""" + +
[docs] def forward(self, inputs: TabularData) -> torch.Tensor: + raise NotImplementedError()
+ + def _expand_non_sequential_features(self, inputs: TabularData) -> TabularData: + inputs_sizes = {k: v.shape for k, v in inputs.items()} + seq_features_shapes, sequence_length = self._get_seq_features_shapes(inputs_sizes) + + if len(seq_features_shapes) > 0: + non_seq_features = set(inputs.keys()).difference(set(seq_features_shapes.keys())) + for fname in non_seq_features: + # Including the 2nd dim and repeating for the sequence length + inputs[fname] = inputs[fname].unsqueeze(dim=1).repeat(1, sequence_length, 1) + + return inputs + + def _get_seq_features_shapes(self, inputs_sizes: Dict[str, torch.Size]): + seq_features_shapes = dict() + for fname, fshape in inputs_sizes.items(): + # Saves the shapes of sequential features + if len(fshape) >= 3: + seq_features_shapes[fname] = tuple(fshape[:2]) + + self._check_first_two_dims(seq_features_shapes) + if len(seq_features_shapes) > 0: + sequence_length = list(seq_features_shapes.values())[0][1] + else: + sequence_length = 0 + + return seq_features_shapes, sequence_length + + def _check_first_two_dims(self, seq_features_shapes: Dict[str, Tuple[int, ...]]): + if ( + not torch.jit.is_tracing() + and len(seq_features_shapes) > 0 + and len(set(seq_features_shapes.values())) > 1 + ): + raise ValueError( + "All sequential features must share the same shape in the first two dims " + "(batch_size, seq_length): {}".format(seq_features_shapes) + ) + + def _check_concat_shapes(self, inputs: TabularData): + if torch.jit.is_tracing(): + return + input_sizes = {k: v.shape for k, v in inputs.items()} + if len(set(list([v[:-1] for v in input_sizes.values()]))) > 1: + raise Exception( + "All features dimensions except the last one must match: {}".format(input_sizes) + ) + + def _get_agg_output_size(self, input_size, agg_dim): + batch_size = calculate_batch_size_from_input_size(input_size) + seq_features_shapes, sequence_length = self._get_seq_features_shapes(input_size) + + if len(seq_features_shapes) > 0: + return ( + batch_size, + sequence_length, + agg_dim, + ) + + else: + return (batch_size, agg_dim) + +
[docs] @classmethod + def parse(cls, class_or_str): + return tabular_aggregation_registry.parse(class_or_str)
+ + +TabularTransformationType = Union[str, TabularTransformation] +TabularTransformationsType = Union[TabularTransformationType, List[TabularTransformationType]] +TabularAggregationType = Union[str, TabularAggregation] + + +
[docs]class SequentialTabularTransformations(SequentialBlock): + """A sequential container, modules will be added to it in the order they are passed in. + + Parameters + ---------- + transformation: TabularTransformationType + transformations that are passed in here will be called in order. + """ + + def __init__(self, *transformation: TabularTransformationsType): + if len(transformation) == 1 and isinstance(transformation[0], list): + transformation = transformation[0] # type: ignore + if not isinstance(transformation, (list, tuple)): + transformation = [transformation] # type: ignore + super().__init__(*[TabularTransformation.parse(t) for t in transformation]) + +
[docs] def append(self, transformation): + self.transformations.append(TabularTransformation.parse(transformation))
+ + +TABULAR_MODULE_PARAMS_DOCSTRING = """ + pre: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional + Transformations to apply on the inputs when the module is called (so **before** `forward`). + post: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional + Transformations to apply on the inputs after the module is called (so **after** `forward`). + aggregation: Union[str, TabularAggregation], optional + Aggregation to apply after processing the `forward`-method to output a single Tensor. +""" + + +
[docs]@docstring_parameter(tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING) +class TabularModule(torch.nn.Module): + """PyTorch Module that's specialized for tabular-data by integrating many often used operations. + + Parameters + ---------- + {tabular_module_parameters} + """ + + def __init__( + self, + pre: Optional[TabularTransformationsType] = None, + post: Optional[TabularTransformationsType] = None, + aggregation: Optional[TabularAggregationType] = None, + **kwargs, + ): + super().__init__() + self.input_size = None + self.pre = pre # type: ignore + self.post = post # type: ignore + self.aggregation = aggregation # type: ignore + +
[docs] @classmethod + def from_schema(cls, schema: Schema, tags=None, **kwargs) -> Optional["TabularModule"]: + """Instantiate a TabularModule instance from a DatasetSchema. + + Parameters + ---------- + schema + tags + kwargs + + Returns + ------- + Optional[TabularModule] + """ + schema_copy = deepcopy(schema) + if tags: + schema_copy = schema_copy.select_by_tag(tags) + + if not schema_copy.column_schemas: + return None + + return cls.from_features(schema_copy.column_names, schema=schema_copy, **kwargs)
+ +
[docs] @classmethod + @docstring_parameter(tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING, extra_padding=4) + def from_features( + cls, + features: List[str], + pre: Optional[TabularTransformationsType] = None, + post: Optional[TabularTransformationsType] = None, + aggregation: Optional[TabularAggregationType] = None, + ) -> "TabularModule": + """Initializes a TabularModule instance where the contents of features will be filtered + out + + Parameters + ---------- + features: List[str] + A list of feature-names that will be used as the first pre-processing op to filter out + all other features not in this list. + {tabular_module_parameters} + + Returns + ------- + TabularModule + """ + pre = [FilterFeatures(features), pre] if pre else FilterFeatures(features) # type: ignore + + return cls(pre=pre, post=post, aggregation=aggregation)
+ + @property + def pre(self) -> Optional[SequentialTabularTransformations]: + """ + + Returns + ------- + SequentialTabularTransformations, optional + """ + return self._pre + + @pre.setter + def pre(self, value: Optional[TabularTransformationsType]): + if value: + self._pre: Optional[ + SequentialTabularTransformations + ] = SequentialTabularTransformations(value) + else: + self._pre = None + + @property + def post(self) -> Optional[SequentialTabularTransformations]: + """ + + Returns + ------- + SequentialTabularTransformations, optional + """ + return self._post + + @post.setter + def post(self, value: Optional[TabularTransformationsType]): + if value: + self._post: Optional[ + SequentialTabularTransformations + ] = SequentialTabularTransformations(value) + else: + self._post = None + + @property + def aggregation(self) -> Optional[TabularAggregation]: + """ + + Returns + ------- + TabularAggregation, optional + """ + return self._aggregation + + @aggregation.setter + def aggregation(self, value: Optional[Union[str, TabularAggregation]]): + """ + + Parameters + ---------- + value + """ + if value: + self._aggregation: Optional[TabularAggregation] = TabularAggregation.parse(value) + else: + self._aggregation = None + +
[docs] def pre_forward( + self, inputs: TabularData, transformations: Optional[TabularTransformationsType] = None + ) -> TabularData: + """Method that's typically called before the forward method for pre-processing. + + Parameters + ---------- + inputs: TabularData + input-data, typically the output of the forward method. + transformations: TabularAggregationType, optional + + Returns + ------- + TabularData + """ + return self._maybe_apply_transformations( + inputs, transformations=transformations or self.pre + )
+ +
[docs] def forward(self, x: TabularData, *args, **kwargs) -> TabularData: + return x
+ +
[docs] def post_forward( + self, + inputs: TabularData, + transformations: Optional[TabularTransformationsType] = None, + merge_with: Union["TabularModule", List["TabularModule"]] = None, + aggregation: Optional[TabularAggregationType] = None, + ) -> TensorOrTabularData: + """Method that's typically called after the forward method for post-processing. + + Parameters + ---------- + inputs: TabularData + input-data, typically the output of the forward method. + transformations: TabularTransformationType, optional + Transformations to apply on the input data. + merge_with: Union[TabularModule, List[TabularModule]], optional + Other TabularModule's to call and merge the outputs with. + aggregation: TabularAggregationType, optional + Aggregation to aggregate the output to a single Tensor. + + Returns + ------- + TensorOrTabularData (Tensor when aggregation is set, else TabularData) + """ + _aggregation: Optional[TabularAggregation] + if aggregation: + _aggregation = TabularAggregation.parse(aggregation) + else: + _aggregation = getattr(self, "aggregation", None) + + outputs = inputs + if merge_with: + if not isinstance(merge_with, list): + merge_with = [merge_with] + for layer_or_tensor in merge_with: + to_add = layer_or_tensor(inputs) if callable(layer_or_tensor) else layer_or_tensor + outputs.update(to_add) + + outputs = self._maybe_apply_transformations( + outputs, transformations=transformations or self.post + ) + + if _aggregation: + schema = getattr(self, "schema", None) + _aggregation.set_schema(schema) + return _aggregation(outputs) + + return outputs
+ + def __call__( + self, + inputs: TabularData, + *args, + pre: Optional[TabularTransformationsType] = None, + post: Optional[TabularTransformationsType] = None, + merge_with: Union["TabularModule", List["TabularModule"]] = None, + aggregation: Optional[TabularAggregationType] = None, + **kwargs, + ) -> TensorOrTabularData: + """We overwrite the call method in order to be able to do pre- and post-processing. + + Parameters + ---------- + inputs: TabularData + Input TabularData. + pre: TabularTransformationType, optional + Transformations to apply before calling the forward method. If pre is None, this method + will check if `self.pre` is set. + post: TabularTransformationType, optional + Transformations to apply after calling the forward method. If post is None, this method + will check if `self.post` is set. + merge_with: Union[TabularModule, List[TabularModule]] + Other TabularModule's to call and merge the outputs with. + aggregation: TabularAggregationType, optional + Aggregation to aggregate the output to a single Tensor. + + Returns + ------- + TensorOrTabularData (Tensor when aggregation is set, else TabularData) + """ + inputs = self.pre_forward(inputs, transformations=pre) + + # This will call the `forward` method implemented by the super class. + outputs = super().__call__(inputs, *args, **kwargs) # noqa + + if isinstance(outputs, dict): + outputs = self.post_forward( + outputs, transformations=post, merge_with=merge_with, aggregation=aggregation + ) + + return outputs + + def _maybe_apply_transformations( + self, + inputs: TabularData, + transformations: Optional[ + Union[TabularTransformationsType, SequentialTabularTransformations] + ] = None, + ) -> TabularData: + """Apply transformations to the inputs if these are defined. + + Parameters + ---------- + inputs + transformations + + Returns + ------- + + """ + if transformations: + _transformations = TabularTransformation.parse(transformations) + return _transformations(inputs) + + return inputs + + def __rrshift__(self, other): + return right_shift_block(self, other)
+ + +
[docs]class FilterFeatures(TabularTransformation): + """Module that filters out certain features from `TabularData`." + + Parameters + ---------- + to_include: List[str] + List of features to include in the result of calling the module + pop: bool + Boolean indicating whether to pop the features to exclude from the inputs dictionary. + """ + + def __init__(self, to_include: List[str], pop: bool = False): + super().__init__() + self.to_include = to_include + self.pop = pop + +
[docs] def forward(self, inputs: TabularData, **kwargs) -> TabularData: + """ + + Parameters + ---------- + inputs: TabularData + Input dictionary containing features to filter. + + Returns Filtered TabularData that only contains the feature-names in `self.to_include`. + ------- + + """ + assert isinstance(inputs, dict), "Inputs needs to be a dict" + + outputs = {k: v for k, v in inputs.items() if k in self.to_include} + if self.pop: + for key in outputs.keys(): + inputs.pop(key) + + return outputs
+ +
[docs] def forward_output_size(self, input_shape): + """ + + Parameters + ---------- + input_shape + + Returns + ------- + + """ + return {k: v for k, v in input_shape.items() if k in self.to_include}
+ + +
[docs]@docstring_parameter(tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING) +class TabularBlock(BlockBase, TabularModule, ABC): + """TabularBlock extends TabularModule to turn it into a block with output size info. + + Parameters + ---------- + {tabular_module_parameters} + """ + + def __init__( + self, + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + schema: Optional[Schema] = None, + **kwargs, + ): + super().__init__(pre=pre, post=post, aggregation=aggregation, **kwargs) + self.schema = schema + +
[docs] def to_module(self, shape_or_module, device=None): + shape = shape_or_module + if isinstance(shape_or_module, torch.nn.Module): + shape = getattr(shape_or_module, "output_size", None) + if shape: + shape = shape() + + return self.build(shape, device=device)
+ +
[docs] def output_size(self, input_size=None): + if self.pre: + input_size = self.pre.output_size(input_size) + + output_size = self._check_post_output_size(super().output_size(input_size)) + + return output_size
+ +
[docs] def build(self, input_size, schema=None, **kwargs): + output = super().build(input_size, schema=schema, **kwargs) + output_size = input_size + if self.pre: + self.pre.build(input_size, schema=schema, **kwargs) + output_size = self.pre.output_size(input_size) + + output_size = self.forward_output_size(output_size) + + if self.post: + self.post.build(output_size, schema=schema, **kwargs) + output_size = self.post.output_size(output_size) + + if self.aggregation: + self.aggregation.build(output_size, schema=schema, **kwargs) + + return output
+ + def _check_post_output_size(self, input_size): + output_size = input_size + + if isinstance(input_size, dict): + if self.post: + output_size = self.post.output_size(output_size) + if self.aggregation: + schema = getattr(self, "schema", None) + # self.aggregation.build(output_size, schema=schema) + self.aggregation.set_schema(schema) + output_size = self.aggregation.forward_output_size(output_size) + + return output_size + + def __rrshift__(self, other): + return right_shift_block(self, other)
+ + +
[docs]@docstring_parameter(tabular_module_parameters=TABULAR_MODULE_PARAMS_DOCSTRING) +class MergeTabular(TabularBlock): + """Merge multiple TabularModule's into a single output of TabularData. + + Parameters + ---------- + modules_to_merge: Union[TabularModule, Dict[str, TabularModule]] + TabularModules to merge into, this can also be one or multiple dictionaries keyed by the + name the module should have. + {tabular_module_parameters} + """ + + def __init__( + self, + *modules_to_merge: Union[TabularModule, Dict[str, TabularModule]], + pre: Optional[TabularTransformationType] = None, + post: Optional[TabularTransformationType] = None, + aggregation: Optional[TabularAggregationType] = None, + schema: Optional[Schema] = None, + **kwargs, + ): + super().__init__(pre=pre, post=post, aggregation=aggregation, schema=schema, **kwargs) + self.to_merge: Union[torch.nn.ModuleDict, torch.nn.ModuleList] + if all(isinstance(x, dict) for x in modules_to_merge): + to_merge: Dict[str, TabularModule] + to_merge = reduce(lambda a, b: dict(a, **b), modules_to_merge) # type: ignore + self.to_merge = torch.nn.ModuleDict(to_merge) + elif all(isinstance(x, torch.nn.Module) for x in modules_to_merge): + self.to_merge = torch.nn.ModuleList(modules_to_merge) # type: ignore + else: + raise ValueError( + "Please provide one or multiple TabularBlock's to merge or " + f"dictionaries of TabularBlocks. got: {modules_to_merge}" + ) + + # Merge schemas if necessary. + if not schema and all(getattr(m, "schema", False) for m in self.merge_values): + self.schema = reduce(lambda a, b: a + b, [m.schema for m in self.merge_values]) + + @property + def merge_values(self): + if isinstance(self.to_merge, torch.nn.ModuleDict): + return list(self.to_merge.values()) + + return self.to_merge + +
[docs] def forward(self, inputs: TabularData, training=True, **kwargs) -> TabularData: # type: ignore + assert isinstance(inputs, dict), "Inputs needs to be a dict" + + outputs = {} + for layer in self.merge_values: + outputs.update(layer(inputs)) + + return outputs
+ +
[docs] def forward_output_size(self, input_size): + output_shapes = {} + + for layer in self.merge_values: + output_shapes.update(layer.forward_output_size(input_size)) + + return super(MergeTabular, self).forward_output_size(output_shapes)
+ +
[docs] def build(self, input_size, **kwargs): + super().build(input_size, **kwargs) + + for layer in self.merge_values: + layer.build(input_size, **kwargs) + + return self
+ + +
[docs]class AsTabular(TabularBlock): + """Converts a Tensor to TabularData by converting it to a dictionary. + + Parameters + ---------- + output_name: str + Name that should be used as the key in the output dictionary. + """ + + def __init__(self, output_name: str): + super().__init__() + self.output_name = output_name + +
[docs] def forward(self, inputs: torch.Tensor, **kwargs) -> TabularData: # type: ignore + return {self.output_name: inputs}
+ +
[docs] def forward_output_size(self, input_size): + return {self.output_name: input_size}
+ + +def merge_tabular(self, other): + return MergeTabular(self, other) + + +TabularModule.__add__ = merge_tabular # type: ignore +TabularModule.merge = merge_tabular # type: ignore +
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/tabular/transformations.html b/review/pr-767/_modules/transformers4rec/torch/tabular/transformations.html new file mode 100644 index 0000000000..c63aec70bd --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/tabular/transformations.html @@ -0,0 +1,289 @@ + + + + + + transformers4rec.torch.tabular.transformations — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.tabular.transformations
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.tabular.transformations

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+from typing import Dict, Optional
+
+import torch
+
+from ..features.embedding import FeatureConfig, TableConfig
+from ..typing import TabularData, TensorOrTabularData
+from .base import TabularTransformation, tabular_transformation_registry
+
+LOG = logging.getLogger("transformers4rec")
+
+
+
[docs]@tabular_transformation_registry.register_with_multiple_names("stochastic-swap-noise", "ssn") +class StochasticSwapNoise(TabularTransformation): + """ + Applies Stochastic replacement of sequence features. + It can be applied as a `pre` transform like `TransformerBlock(pre="stochastic-swap-noise")` + """ + + def __init__(self, schema=None, pad_token=0, replacement_prob=0.1): + super().__init__() + self.schema = schema + self.pad_token = pad_token + self.replacement_prob = replacement_prob + +
[docs] def forward( # type: ignore + self, inputs: TensorOrTabularData, input_mask: Optional[torch.Tensor] = None, **kwargs + ) -> TensorOrTabularData: + if self.schema: + input_mask = input_mask or self.get_padding_mask_from_item_id(inputs, self.pad_token) + if isinstance(inputs, dict): + return {key: self.augment(val, input_mask) for key, val in inputs.items()} + + return self.augment(inputs, input_mask)
+ +
[docs] def forward_output_size(self, input_size): + return input_size
+ +
[docs] def augment( + self, input_tensor: torch.Tensor, mask: Optional[torch.Tensor] = None + ) -> torch.Tensor: + # Applies this transformation only during training + if not self.training: + return input_tensor + + with torch.no_grad(): + if mask is not None: + if input_tensor.ndim == mask.ndim - 1: + mask = mask[:, 0] + + sse_prob_replacement_matrix = torch.full( + input_tensor.shape, + self.replacement_prob, + device=input_tensor.device, + ) + sse_replacement_mask = torch.bernoulli(sse_prob_replacement_matrix).bool() + if mask is not None: + sse_replacement_mask = sse_replacement_mask & mask + n_values_to_replace = sse_replacement_mask.sum() + + if mask is not None: + masked = torch.masked_select(input_tensor, mask) + else: + masked = torch.clone(input_tensor) + + input_permutation = torch.randperm(masked.shape[0]) + sampled_values_to_replace = masked[input_permutation][ + :n_values_to_replace # type: ignore + ] + output_tensor = input_tensor.clone() + + if input_tensor[sse_replacement_mask].size() != sampled_values_to_replace: + sampled_values_to_replace = torch.squeeze(sampled_values_to_replace) + + output_tensor[sse_replacement_mask] = sampled_values_to_replace + + return output_tensor
+ + +
[docs]@tabular_transformation_registry.register_with_multiple_names("layer-norm") +class TabularLayerNorm(TabularTransformation): + """ + Applies Layer norm to each input feature individually, before the aggregation + """ + + def __init__(self, features_dim: Optional[Dict[str, int]] = None): + super().__init__() + self.feature_layer_norm = torch.nn.ModuleDict() + self._set_features_layer_norm(features_dim) + + def _set_features_layer_norm(self, features_dim): + feature_layer_norm = {} + if features_dim: + for fname, dim in features_dim.items(): + if dim == 1: + LOG.warning( + f"Layer norm can only be applied on features with more than 1 dim, " + f"but feature {fname} has dim {dim}" + ) + continue + feature_layer_norm[fname] = torch.nn.LayerNorm(normalized_shape=dim) + self.feature_layer_norm.update(feature_layer_norm) + +
[docs] @classmethod + def from_feature_config(cls, feature_config: Dict[str, FeatureConfig]): + features_dim = {} + for name, feature in feature_config.items(): + table: TableConfig = feature.table + features_dim[name] = table.dim + return cls(features_dim)
+ +
[docs] def forward(self, inputs: TabularData, **kwargs) -> TabularData: + return { + key: (self.feature_layer_norm[key](val) if key in self.feature_layer_norm else val) + for key, val in inputs.items() + }
+ +
[docs] def forward_output_size(self, input_size): + return input_size
+ +
[docs] def build(self, input_size, **kwargs): + if input_size is not None: + features_dim = {k: v[-1] for k, v in input_size.items()} + self._set_features_layer_norm(features_dim) + + return super().build(input_size, **kwargs)
+ + +
[docs]@tabular_transformation_registry.register_with_multiple_names("dropout") +class TabularDropout(TabularTransformation): + """ + Applies dropout transformation. + """ + + def __init__(self, dropout_rate=0.0): + super().__init__() + self.dropout = torch.nn.Dropout(dropout_rate) + +
[docs] def forward(self, inputs: TensorOrTabularData, **kwargs) -> TensorOrTabularData: # type: ignore + outputs = {key: self.dropout(val) for key, val in inputs.items()} # type: ignore + return outputs
+ +
[docs] def forward_output_size(self, input_size): + return input_size
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/trainer.html b/review/pr-767/_modules/transformers4rec/torch/trainer.html new file mode 100644 index 0000000000..2b17069f07 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/trainer.html @@ -0,0 +1,1033 @@ + + + + + + transformers4rec.torch.trainer — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.trainer
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.trainer

+# type: ignore
+
+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import collections
+import inspect
+import random
+import re
+from collections.abc import Sized
+from copy import deepcopy
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+
+import numpy as np
+import torch
+from torch.cuda.amp import autocast
+from torch.optim import Optimizer
+from torch.utils.data.dataloader import DataLoader
+from torch.utils.data.dataset import Dataset
+from transformers import Trainer as BaseTrainer
+from transformers.optimization import TYPE_TO_SCHEDULER_FUNCTION
+from transformers.trainer_callback import TrainerCallback
+from transformers.trainer_pt_utils import find_batch_size
+from transformers.trainer_utils import PREFIX_CHECKPOINT_DIR, EvalLoopOutput, SchedulerType
+from transformers.utils import logging
+
+from merlin_standard_lib import Schema
+
+from ..config.trainer import T4RecTrainingArguments
+from .model.base import Model
+from .model.prediction_task import NextItemPredictionTask
+from .utils.data_utils import T4RecDataLoader
+from .utils.torch_utils import nested_concat, nested_detach, nested_numpify, nested_truncate
+
+logger = logging.get_logger(__name__)
+
+
+
[docs]class Trainer(BaseTrainer): + """ + An :class:`~transformers.Trainer` specialized for sequential recommendation + including (session-based and sequtial recommendation) + + Parameters + ---------- + model: Model + The Model defined using Transformers4Rec api. + args: T4RecTrainingArguments + The training arguments needed to setup training and evaluation + experiments. + schema: Optional[Dataset.schema], optional + The schema object including features to use and their properties. + by default None + train_dataset_or_path: Optional[Union[str, Dataset]], optional + Path of parquet files or DataSet to use for training. + by default None + eval_dataset_or_path: Optional[str, Dataset], optional + Path of parquet files or DataSet to use for evaluation. + by default None + train_dataloader: Optional[DataLoader], optional + The data generator to use for training. + by default None + eval_dataloader: Optional[DataLoader], optional + The data generator to use for evaluation. + by default None + compute_metrics: Optional[bool], optional + Whether to compute metrics defined by Model class or not. + by default None + incremental_logging: bool + Whether to enable incremental logging or not. If True, it ensures that + global steps are incremented over many `trainer.train()` calls, so that + train and eval metrics steps do not overlap and can be seen properly + in reports like W&B and Tensorboard + """ + + def __init__( + self, + model: Model, + args: T4RecTrainingArguments, + schema: Schema = None, + train_dataset_or_path=None, + eval_dataset_or_path=None, + test_dataset_or_path=None, + train_dataloader: Optional[DataLoader] = None, + eval_dataloader: Optional[DataLoader] = None, + test_dataloader: Optional[DataLoader] = None, + callbacks: Optional[List[TrainerCallback]] = [], + compute_metrics=None, + incremental_logging: bool = False, + **kwargs, + ): + mock_dataset = DatasetMock() + + self.incremental_logging = incremental_logging + if self.incremental_logging: + self.past_global_steps = 0 + incremental_logging_callback = IncrementalLoggingCallback(self) + callbacks.append(incremental_logging_callback) + + super(Trainer, self).__init__( + model=model, + args=args, + train_dataset=mock_dataset, + eval_dataset=mock_dataset, + callbacks=callbacks, + **kwargs, + ) + + self.compute_metrics = compute_metrics + self.train_dataset_or_path = train_dataset_or_path + self.eval_dataset_or_path = eval_dataset_or_path + self.test_dataset_or_path = test_dataset_or_path + self.train_dataloader = train_dataloader + self.eval_dataloader = eval_dataloader + self.test_dataloader = test_dataloader + self.schema = schema + self.incremental_logging = incremental_logging + + # Set global_rank and global_size if DDP is used + if self.args.local_rank != -1: + self.device = self.local_rank = self.args.local_rank + self.global_size = self.args.world_size + else: + self.device = self.local_rank = None + self.global_size = None + +
[docs] def get_train_dataloader(self): + """ + Set the train dataloader to use by Trainer. + It supports user defined data-loader set as an attribute in the constructor. + When the attribute is None, The data-loader is defined using train_dataset + and the `data_loader_engine` specified in Training Arguments. + """ + if self.train_dataloader is not None: + return self.train_dataloader + + assert self.schema is not None, "schema is required to generate Train Dataloader" + + return T4RecDataLoader.parse(self.args.data_loader_engine).from_schema( + self.schema, + self.train_dataset_or_path, + self.args.per_device_train_batch_size, + max_sequence_length=self.args.max_sequence_length, + drop_last=self.args.dataloader_drop_last, + shuffle=True, + shuffle_buffer_size=self.args.shuffle_buffer_size, + global_rank=self.local_rank, + global_size=self.global_size, + device=self.device, + )
+ +
[docs] def get_eval_dataloader(self, eval_dataset=None): + """ + Set the eval dataloader to use by Trainer. + It supports user defined data-loader set as an attribute in the constructor. + When the attribute is None, The data-loader is defined using eval_dataset + and the `data_loader_engine` specified in Training Arguments. + """ + if self.eval_dataloader is not None: + return self.eval_dataloader + + if eval_dataset is None and self.eval_dataset is None: + raise ValueError("Trainer: evaluation requires an eval_dataset.") + eval_dataset = eval_dataset if eval_dataset is not None else self.eval_dataset + assert self.schema is not None, "schema is required to generate Eval Dataloader" + + return T4RecDataLoader.parse(self.args.data_loader_engine).from_schema( + self.schema, + self.eval_dataset_or_path, + self.args.per_device_eval_batch_size, + max_sequence_length=self.args.max_sequence_length, + drop_last=self.args.dataloader_drop_last, + shuffle=False, + shuffle_buffer_size=self.args.shuffle_buffer_size, + global_rank=self.local_rank, + global_size=self.global_size, + device=self.device, + )
+ +
[docs] def get_test_dataloader(self, test_dataset=None): + """ + Set the test dataloader to use by Trainer. + It supports user defined data-loader set as an attribute in the constructor. + When the attribute is None, The data-loader is defined using test_dataset + and the `data_loader_engine` specified in Training Arguments. + """ + if self.test_dataloader is not None: + return self.test_dataloader + + if test_dataset is None and self.test_dataset_or_path is None: + raise ValueError("Trainer: test requires an test_dataset.") + test_dataset = test_dataset if test_dataset is not None else self.test_dataset_or_path + assert self.schema is not None, "schema is required to generate Test Dataloader" + return T4RecDataLoader.parse(self.args.data_loader_engine).from_schema( + self.schema, + test_dataset, + self.args.per_device_eval_batch_size, + max_sequence_length=self.args.max_sequence_length, + drop_last=self.args.dataloader_drop_last, + shuffle=False, + shuffle_buffer_size=self.args.shuffle_buffer_size, + global_rank=self.local_rank, + global_size=self.global_size, + device=self.device, + )
+ +
[docs] def num_examples(self, dataloader: DataLoader): + """ + Overriding :obj:`Trainer.num_examples()` method because + the data loaders for this project do not return the dataset size, + but the number of steps. So we estimate the dataset size here + by multiplying the number of steps * batch size + """ + """ + if dataloader == self.get_train_dataloader(): + batch_size = self.args.per_device_train_batch_size + else: + batch_size = self.args.per_device_eval_batch_size + """ + return len(dataloader) * dataloader._batch_size
+ +
[docs] def reset_lr_scheduler(self) -> None: + """ + Resets the LR scheduler of the previous :obj:`Trainer.train()` call, + so that a new LR scheduler one is created by the next :obj:`Trainer.train()` call. + This is important for LR schedules like `get_linear_schedule_with_warmup()` + which decays LR to 0 in the end of the train + """ + self.lr_scheduler = None
+ +
[docs] def create_scheduler(self, num_training_steps: int, optimizer: torch.optim.Optimizer = None): + # flexibility in scheduler with num_cycles as hyperparams + if self.lr_scheduler is None: + self.lr_scheduler = self.get_scheduler( + self.args.lr_scheduler_type, + optimizer=self.optimizer if optimizer is None else optimizer, + num_warmup_steps=self.args.warmup_steps, + num_training_steps=num_training_steps, + num_cycles=self.args.learning_rate_num_cosine_cycles_by_epoch + * self.args.num_train_epochs, + )
+ + # Override the method get_scheduler to accept num_cycle params ? + # The advantage is to use the unified HF API with many scheduler + # we can also send a PR to HF ? +
[docs] @staticmethod + def get_scheduler( + name: Union[str, SchedulerType], + optimizer: Optimizer, + num_warmup_steps: Optional[int] = None, + num_training_steps: Optional[int] = None, + num_cycles: Optional[int] = 0.5, + ): + """ + Unified API to get any scheduler from its name. + + Parameters + ---------- + name: (:obj:`str` or `:obj:`SchedulerType`) + The name of the scheduler to use. + optimizer: (:obj:`torch.optim.Optimizer`) + The optimizer that will be used during training. + num_warmup_steps: (:obj:`int`, `optional`) + The number of warm-up steps to perform. This is not required by all schedulers + (hence the argument being optional), + the function will raise an error if it's unset and the scheduler type requires it. + num_training_steps: (:obj:`int`, `optional`) + The number of training steps to do. This is not required by all schedulers + (hence the argument being optional), + the function will raise an error if it's unset and the scheduler type requires it. + num_cycles: (:obj:`int`, `optional`) + The number of waves in the cosine schedule / + hard restarts to use for cosine scheduler + """ + name = SchedulerType(name) + schedule_func = TYPE_TO_SCHEDULER_FUNCTION[name] + if name == SchedulerType.CONSTANT: + return schedule_func(optimizer) + + # All other schedulers require `num_warmup_steps` + if num_warmup_steps is None: + raise ValueError(f"{name} requires `num_warmup_steps`, please provide that argument.") + + if name == SchedulerType.CONSTANT_WITH_WARMUP: + return schedule_func(optimizer, num_warmup_steps=num_warmup_steps) + + # All other schedulers require `num_training_steps` + if num_training_steps is None: + raise ValueError(f"{name} requires `num_training_steps`, please provide that argument.") + + if "num_cycles" in inspect.signature(schedule_func).parameters: + return schedule_func( + optimizer, + num_warmup_steps=num_warmup_steps, + num_training_steps=num_training_steps, + num_cycles=num_cycles, + ) + + return schedule_func( + optimizer, num_warmup_steps=num_warmup_steps, num_training_steps=num_training_steps + )
+ +
[docs] def compute_loss(self, model, inputs, return_outputs=False): + """ + Overriding :obj:`Trainer.compute_loss()` + To allow for passing the targets to the model's forward method + How the loss is computed by Trainer. By default, all Transformers4Rec models return + a dictionary of three elements {'loss', 'predictions', and 'labels} + """ + inputs, targets = inputs + outputs = model(inputs, targets=targets, training=True) + # Save past state if it exists + # TODO: this needs to be fixed and made cleaner later. + if self.args.past_index >= 0: + self._past = outputs[self.args.past_index] + + if "loss" not in outputs: + raise ValueError( + "The model did not return a loss from the inputs, only the following keys: " + f"{','.join(outputs.keys())}. " + "For reference, the inputs it received are {','.join(inputs.keys())}." + ) + + loss = outputs["loss"] + + return (loss, outputs) if return_outputs else loss
+ +
[docs] def prediction_step( + self, + model: torch.nn.Module, + inputs: Dict[str, torch.Tensor], + prediction_loss_only: bool, + ignore_keys: Optional[List[str]] = None, + training: bool = False, + testing: bool = True, + ) -> Tuple[ + Optional[float], + Optional[torch.Tensor], + Optional[torch.Tensor], + Optional[Dict[str, Any]], + ]: + """ + Overriding :obj:`Trainer.prediction_step()` + to provide more flexibility to unpack results from the model, + like returning labels that are not exactly one input feature + model + """ + inputs = self._prepare_inputs(inputs) + inputs, targets = inputs + with torch.no_grad(): + if self._use_cuda_amp: + with autocast(): + outputs = model(inputs, targets=targets, training=training, testing=testing) + else: + outputs = model(inputs, targets=targets, training=training, testing=testing) + + if testing: + loss = outputs["loss"].mean().detach() + labels = nested_detach(outputs["labels"]) + predictions = nested_detach(outputs["predictions"]) + else: + loss, labels = None, None + predictions = nested_detach(outputs) + + if prediction_loss_only: + return (loss, None, None, None) + + # TODO: define metadata dict in the model for logging + # other_outputs = { + # k: v.detach() if isinstance(v, torch.Tensor) else v + # for k, v in outputs.items() + # if k not in ignore_keys + ["loss", "predictions", "labels"] + # } + other_outputs = None + + return (loss, predictions, labels, other_outputs)
+ + @property + def _use_cuda_amp(self): + """ + Check for CUDA AMP that is compatible with versions of the + transformers package before and after version 4.20 (which + renamed the property `use_amp` to `use_cuda_amp`) + """ + try: + return self.use_cuda_amp + except AttributeError: + return self.use_amp + +
[docs] def evaluation_loop( + self, + dataloader: DataLoader, + description: str, + prediction_loss_only: Optional[bool] = None, + ignore_keys: Optional[List[str]] = None, + metric_key_prefix: Optional[str] = "eval", + ) -> EvalLoopOutput: + """ + Overriding :obj:`Trainer.prediction_loop()` + (shared by :obj:`Trainer.evaluate()` and :obj:`Trainer.predict()`) + to provide more flexibility to work with streaming metrics + (computed at each eval batch) and + to log with the outputs of the model + (e.g. prediction scores, prediction metadata, attention weights) + + Parameters + ---------- + dataloader: DataLoader + DataLoader object to use to iterate over evaluation data + description: str + Parameter to describe the evaluation experiment. + e.g: `Prediction`, `test` + prediction_loss_only: Optional[bool] + Whether or not to return the loss only. + by default None + ignore_keys: Optional[List[str]] + Columns not accepted by the ``model.forward()`` method + are automatically removed. + by default None + metric_key_prefix: Optional[str] + Prefix to use when logging evaluation metrics. + by default `eval` + """ + prediction_loss_only = ( + prediction_loss_only + if prediction_loss_only is not None + else self.args.prediction_loss_only + ) + + if description == "Prediction": + testing = False + else: + testing = True + + # set the model + model = self.model + # reset metrics for the dataset (Train, Valid or Test) + if self.compute_metrics: + model.reset_metrics() + + if not isinstance(dataloader.dataset, collections.abc.Sized): + raise ValueError("dataset must implement __len__") + + batch_size = dataloader._batch_size + + logger.info("***** Running %s *****", description) + logger.info(" Batch size = %d", batch_size) + + preds_host: Union[torch.Tensor, List[torch.Tensor], Dict[str, torch.Tensor]] = None + labels_host: Union[torch.Tensor, List[torch.Tensor]] = None + + if metric_key_prefix == "train" and self.args.eval_steps_on_train_set: + num_examples = self.args.eval_steps_on_train_set * batch_size + else: + num_examples = self.num_examples(dataloader) + + logger.info(" Num sessions (examples) = %d", num_examples) + + model.eval() + + self.callback_handler.eval_dataloader = dataloader + + # Initialize containers + # losses/preds/labels on GPU/TPU (accumulated for eval_accumulation_steps) + losses_host = None + preds_host = None + labels_host = None + # losses/preds/labels on CPU (final containers) + all_losses = None + all_preds = None + all_labels = None + # Will be useful when we have an iterable dataset so don't know its length. + observed_num_examples = 0 + + # Iterate over dataloader + for step, inputs in enumerate(dataloader): + # Update the observed num examples + observed_batch_size = find_batch_size(inputs) + if observed_batch_size is not None: + observed_num_examples += observed_batch_size + + # Limits the number of evaluation steps on train set (which is usually larger) + if ( + metric_key_prefix == "train" + and self.args.eval_steps_on_train_set > 0 + and step + 1 > self.args.eval_steps_on_train_set + ): + break + + loss, preds, labels, outputs = self.prediction_step( + model, + inputs, + prediction_loss_only, + ignore_keys=ignore_keys, + testing=testing, + ) + + # Updates metrics + # TODO: compute metrics each N eval_steps to speedup evaluation + metrics_results_detailed = None + if self.compute_metrics is not None and testing: + if step % self.args.compute_metrics_each_n_steps == 0: + metrics_results_detailed = model.calculate_metrics(preds, labels) + + # Update containers on host + if loss is not None: + losses = self._nested_gather(loss.repeat(batch_size)) + losses_host = ( + losses if losses_host is None else torch.cat((losses_host, losses), dim=0) + ) + if labels is not None: + labels = self._pad_across_processes(labels) + labels = self._nested_gather(labels) + labels_host = ( + labels + if labels_host is None + else nested_concat(labels_host, labels, padding_index=0) + ) + + if ( + preds is not None + and any(isinstance(x, NextItemPredictionTask) for x in model.prediction_tasks) + and (self.args.predict_top_k or self.model.top_k) + ): + # get outputs of next-item scores + if isinstance(preds, dict): + pred_next_item = preds["next-item"] + else: + pred_next_item = preds + + preds_sorted_item_scores = None + preds_sorted_item_ids = None + + if self.model.top_k is not None and isinstance(pred_next_item, (list, tuple)): + preds_sorted_item_scores, preds_sorted_item_ids = pred_next_item + + if self.args.predict_top_k: + if self.args.predict_top_k > self.model.top_k: + raise ValueError( + "The args.predict_top_k should not be larger than model.top_k. " + "The model.top_k is available to support inference (e.g. when " + "serving with Triton Inference Server) to return only the top-k " + "predicted items ids and their scores." + "When doing offline predictions with `trainer.predict(), " + "if you set model.top_k, the model will also limit the number of " + "predictions output from trainer.predict(). " + "In that case, you want either to reduce args.predict_top_k or " + "increase model.top_k, so that args.predict_top_k is " + "not larger than model.top_k." + ) + preds_sorted_item_scores = preds_sorted_item_scores[ + :, : self.args.predict_top_k + ] + preds_sorted_item_ids = preds_sorted_item_ids[:, : self.args.predict_top_k] + elif self.args.predict_top_k: + preds_sorted_item_scores, preds_sorted_item_ids = torch.topk( + pred_next_item, k=self.args.predict_top_k, dim=-1 + ) + + if preds_sorted_item_scores is not None: + self._maybe_log_predictions( + labels, + preds_sorted_item_ids, + preds_sorted_item_scores, + # outputs["pred_metadata"], + metrics_results_detailed, + metric_key_prefix, + ) + # The output predictions will be a tuple with the ranked top-n item ids, + # and item recommendation scores + if isinstance(preds, dict): + preds["next-item"] = ( + preds_sorted_item_ids, + preds_sorted_item_scores, + ) + else: + preds = ( + preds_sorted_item_ids, + preds_sorted_item_scores, + ) + + preds_host = ( + preds + if preds_host is None + else nested_concat( + preds_host, + preds, + ) + ) + + self.control = self.callback_handler.on_prediction_step( + self.args, self.state, self.control + ) + + # Gather all tensors and put them back on the CPU + # if we have done enough accumulation steps. + if ( + self.args.eval_accumulation_steps is not None + and (step + 1) % self.args.eval_accumulation_steps == 0 + ): + if losses_host is not None: + losses = nested_numpify(losses_host) + all_losses = ( + losses + if all_losses is None + else np.concatenate((all_losses, losses), axis=0) + ) + if labels_host is not None: + labels = nested_numpify(labels_host) + all_labels = ( + labels + if all_labels is None + else nested_concat(all_labels, labels, padding_index=0) + ) + if preds_host is not None: + preds = nested_numpify(preds_host) + all_preds = ( + preds + if all_preds is None + else nested_concat( + all_preds, + preds, + ) + ) + + # Set back to None to begin a new accumulation + losses_host, preds_host, labels_host = None, None, None + + if self.args.past_index and hasattr(self, "_past"): + # Clean the state at the end of the evaluation loop + delattr(self, "_past") + + # Gather all remaining tensors and put them back on the CPU + if losses_host is not None: + losses = nested_numpify(losses_host) + all_losses = ( + losses if all_losses is None else np.concatenate((all_losses, losses), axis=0) + ) + if labels_host is not None: + labels = nested_numpify(labels_host) + all_labels = ( + labels if all_labels is None else nested_concat(all_labels, labels, padding_index=0) + ) + if preds_host is not None: + preds_host = nested_numpify(preds_host) + all_preds = ( + preds_host + if all_preds is None + else nested_concat( + all_preds, + preds_host, + ) + ) + # Get Number of samples : + # the data loaders for this project do not return the dataset size, + num_samples = observed_num_examples + + # Number of losses has been rounded to a multiple of batch_size + # and in a distributed training, the number of + # samplers has been rounded to a multiple of batch_size, so we truncate. + if all_losses is not None: + all_losses = all_losses[:num_samples] + if all_preds is not None: + all_preds = nested_truncate(all_preds, num_samples) + if all_labels is not None: + all_labels = nested_truncate(all_labels, num_samples) + + # Get metrics : + metrics = {} + # Computing the metrics results as the average of all steps + if self.compute_metrics and testing: + streaming_metrics_results = model.compute_metrics(mode=metric_key_prefix) + streaming_metrics_results_flattened = process_metrics( + streaming_metrics_results, prefix=metric_key_prefix + "_/" + ) + + metrics = {**metrics, **streaming_metrics_results_flattened} + + if testing: + metrics[f"{metric_key_prefix}_/loss"] = all_losses.mean().item() + + return EvalLoopOutput( + predictions=all_preds, + label_ids=all_labels, + metrics=metrics, + num_samples=num_examples, + )
+ + def _save_model_and_checkpoint(self, save_model_class=False): + """ + Save the serialized model + trainer and random states. + + Parameters + ---------- + save_model_class: Optional[bool] + Whether to save the Model class or not. + by default False + """ + import os + + logger.info("Saving model...") + output_dir = os.path.join( + self.args.output_dir, f"{PREFIX_CHECKPOINT_DIR}-{self.state.global_step}" + ) + + # save model parameters + self._save_checkpoint(self.model, trial=None, metrics=None) + # save the serialized model + if save_model_class: + # TODO : fix serialization of DatasetSchema object + self.model.save(output_dir) + +
[docs] def load_model_trainer_states_from_checkpoint(self, checkpoint_path, model=None): + """ + This method loads the checkpoints states of the model, trainer and random states. + If model is None the serialized model class is loaded from checkpoint. + It does not loads the optimizer and LR scheduler states (for that call trainer.train() + with resume_from_checkpoint argument for a complete load) + + Parameters + ---------- + checkpoint_path: str + Path to the checkpoint directory. + model: Optional[Model] + Model class used by Trainer. by default None + """ + import os + + if model is None: + try: + import cloudpickle + except ImportError: + raise ImportError("cloudpickle is required to load model class") + logger.info("Loading model class") + model = cloudpickle.load( + open(os.path.join(checkpoint_path, "t4rec_model_class.pkl"), "rb") + ) + + self.model = model + logger.info("Loading weights of previously trained model") + # Restoring model weights + self.model.load_state_dict( + # torch.load(os.path.join(training_args.output_dir, "pytorch_model.bin")) + torch.load(os.path.join(checkpoint_path, "pytorch_model.bin")) + ) + # Restoring random state + rng_file = os.path.join(checkpoint_path, "rng_state.pth") + checkpoint_rng_state = torch.load(rng_file) + random.setstate(checkpoint_rng_state["python"]) + np.random.set_state(checkpoint_rng_state["numpy"]) + torch.random.set_rng_state(checkpoint_rng_state["cpu"]) + torch.cuda.random.set_rng_state_all(checkpoint_rng_state["cuda"]) + # Restoring AMP scaler + if self._use_cuda_amp: + self.scaler.load_state_dict(torch.load(os.path.join(checkpoint_path, "scaler.pt")))
+ + @property + def log_predictions_callback(self) -> Callable: + return self.__log_predictions_callback + + @log_predictions_callback.setter + def log_predictions_callback(self, var: Callable): + self.__log_predictions_callback = var + + def _maybe_log_predictions( + self, + labels: torch.Tensor, + pred_item_ids: torch.Tensor, + pred_item_scores: torch.Tensor, + metrics: Dict[str, np.ndarray], + metric_key_prefix: str, + ): + """ + If --log_predictions is enabled, calls a callback function to + log predicted item ids, scores, metadata and metrics. + Parameters + ---------- + labels: torch.Tensor + True labels. + pred_item_ids: torch.Tensor + The predicted items ids. if top_k is set: + we return to top-k items for each + next-item prediction. + pred_item_scores: torch.Tensor + The prediction scores, if top_k is set: + we return to top-k predictions for each + next-item prediction. + metrics: Dict[str, np.ndarray] + Dictionary of metrics computed by Model. + metric_key_prefix: str + Prefix to use when logging evaluation metrics. + by default `eval` + """ + # TODO Add pred_metadata: Dict[str, torch.Tensor], + + if self.args.log_predictions and self.log_predictions_callback is not None: + # Converting torch Tensors to NumPy and callback predictions logging function + # preds_metadata = {k: v.cpu().numpy() for k, v in pred_metadata.items()} + + self.log_predictions_callback( + labels=labels.cpu().numpy(), + pred_item_ids=pred_item_ids.cpu().numpy(), + pred_item_scores=pred_item_scores.cpu() + .numpy() + .astype(np.float32), # Because it is float16 when --fp16 + # preds_metadata=preds_metadata, + metrics=metrics, + dataset_type=metric_key_prefix, + ) + + def _increment_past_global_steps(self, current_global_step: int): + self.past_global_steps += current_global_step + + def _get_general_global_step(self) -> int: + general_global_step = self.past_global_steps + if self.model.training: + general_global_step += self.state.global_step + + return general_global_step + +
[docs] def log(self, logs: Dict[str, float]) -> None: + # Ensuring that eval metrics are prefixed as "eval_" so that the HF integration loggers + # do not prefix metrics names with 'train/' (as 'train/' is always added when not eval) + logs = {re.sub("^eval/", "eval_", k).replace("train/", ""): v for k, v in logs.items()} + + if not self.incremental_logging: + super().log(logs) + else: + # If Incremental logging is enabled, ensures that global steps are always + # incremented after train() calls + # so that metrics are logger with no overlap on W&B and Tensorboard + if self.state.epoch is not None: + logs["epoch"] = round(self.state.epoch, 2) + + # As state.global_step is also used for the learning rate schedules, + # we create a copy only for logging + state_copy = deepcopy(self.state) + state_copy.global_step = self._get_general_global_step() + + output = {**logs, **{"step": state_copy.global_step}} + self.state.log_history.append(output) + self.control = self.callback_handler.on_log(self.args, state_copy, self.control, logs)
+ + +
[docs]def process_metrics(metrics, prefix="", to_cpu=True): + metrics_proc = {} + for root_key, root_value in metrics.items(): + if isinstance(root_value, dict): + flattened_metrics = process_metrics(root_value, prefix=prefix, to_cpu=to_cpu) + metrics_proc = {**metrics_proc, **flattened_metrics} + else: + value = root_value.cpu().numpy().item() if to_cpu else root_value + metrics_proc[f"{prefix}{root_key}"] = value + return metrics_proc
+ + +
[docs]class IncrementalLoggingCallback(TrainerCallback): + """ + An :class:`~transformers.TrainerCallback` that changes the state of the Trainer + on specific hooks for the purpose of the incremental logging + Parameters + ---------- + trainer: Trainer + """ + + def __init__(self, trainer: Trainer): + self.trainer = trainer + +
[docs] def on_train_begin(self, args, state, control, model=None, **kwargs): + pass
+ +
[docs] def on_train_end(self, args, state, control, model=None, **kwargs): + # Increments the global steps for logging with the global steps of the last train() + self.trainer._increment_past_global_steps(state.global_step)
+ +
[docs] def on_epoch_end(self, args, state, control, model=None, **kwargs): + # Evaluates on eval set + # self.trainer.evaluate() + pass
+ + +
[docs]class DatasetMock(Dataset, Sized): + """ + Mock to inform HF Trainer that the dataset is sized, + and can be obtained via the generated/provided data loader + """ + + def __init__(self, nsteps=1): + self.nsteps = nsteps + + def __len__(self): + return self.nsteps
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/utils/data_utils.html b/review/pr-767/_modules/transformers4rec/torch/utils/data_utils.html new file mode 100644 index 0000000000..8c31629ea0 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/utils/data_utils.html @@ -0,0 +1,674 @@ + + + + + + transformers4rec.torch.utils.data_utils — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.utils.data_utils
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.utils.data_utils

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import logging
+import warnings
+from abc import ABC
+
+import numpy as np
+import torch
+from merlin.dataloader.torch import Loader
+from merlin.models.utils.misc_utils import validate_dataset
+from merlin.models.utils.registry import Registry
+from merlin.schema import Tags
+from merlin.schema.io.tensorflow_metadata import TensorflowMetadata
+from torch.utils.data import DataLoader as PyTorchDataLoader
+from torch.utils.data import Dataset, IterableDataset
+
+from merlin_standard_lib import Schema
+from transformers4rec.torch.utils.padding import pad_batch
+
+from ...utils import dependencies
+
+logger = logging.getLogger(__name__)
+
+dataloader_registry: Registry = Registry("torch.dataloader_loader")
+
+
+
[docs]class T4RecDataLoader(ABC): + """ + Base Helper class to build dataloader from the schema with properties + required by T4Rec Trainer class. + """ + +
[docs] @classmethod + def from_schema( + self, schema: Schema, paths_or_dataset, batch_size, max_sequence_length, **kwargs + ): + # Build the data-loader from the schema + raise NotImplementedError
+ +
[docs] def set_dataset(self, paths_or_dataset): + # set the dataset from paths + # or from provided dataset + raise NotImplementedError
+ +
[docs] @classmethod + def parse(cls, class_or_str): + return dataloader_registry.parse(class_or_str)
+ + +if dependencies.is_pyarrow_available(): + import pyarrow.parquet as pq + +
[docs] @dataloader_registry.register_with_multiple_names("pyarrow_builder", "pyarrow") + class PyarrowDataLoader(T4RecDataLoader, PyTorchDataLoader): + def __init__( + self, + paths_or_dataset, + batch_size, + max_sequence_length, + cols_to_read=None, + target_names=None, + shuffle=False, + shuffle_buffer_size=0, + num_workers=1, + pin_memory=True, + drop_last=False, + **kwargs, + ): + T4RecDataLoader.__init__(self) + warnings.warn( + "The `pyarrow` data loader is deprecated and should be replaced " + "by `merlin_dataloader`", + DeprecationWarning, + ) + self.paths_or_dataset = paths_or_dataset + self.batch_size = batch_size + self.shuffle = shuffle + self.shuffle_buffer_size = shuffle_buffer_size + self.num_workers = num_workers + self.pin_memory = pin_memory + self.max_sequence_length = max_sequence_length + self.drop_last = drop_last + + self.set_dataset(cols_to_read=cols_to_read, target_names=target_names) + + PyTorchDataLoader.__init__( + self, + self.dataset, + batch_size=self.batch_size, + drop_last=self.drop_last, + num_workers=self.num_workers, + pin_memory=self.pin_memory, + ) + # set _batch_size attribute needed by HF trainer + self._batch_size = self.batch_size + +
[docs] def set_dataset(self, cols_to_read, target_names): + """ + set the Parquet dataset + + Parameters + ---------- + cols_to_read: str + The list of features names to load + """ + + if isinstance(self.paths_or_dataset, ParquetDataset): + dataset = self.paths_or_dataset + dataset = ParquetDataset( + self.paths_or_dataset, + cols_to_read, + seq_features_len_pad_trim=self.max_sequence_length, + target_names=target_names, + ) + if self.shuffle and self.shuffle_buffer_size > 0: + dataset = ShuffleDataset(dataset, buffer_size=self.shuffle_buffer_size) + + self.dataset = dataset
+ +
[docs] @classmethod + def from_schema( + cls, + schema, + paths_or_dataset, + batch_size, + max_sequence_length, + continuous_features=None, + categorical_features=None, + targets=None, + shuffle=False, + shuffle_buffer_size=0, + num_workers=1, + pin_memory=True, + **kwargs, + ): + """ + Instantiates ``PyarrowDataLoader`` from a ``DatasetSchema``. + + Parameters + ---------- + schema: DatasetSchema + Dataset schema + paths_or_dataset: Union[str, Dataset] + Path to paquet data of Dataset object. + batch_size: int + batch size of Dataloader. + max_sequence_length: int + The maximum length of list features. + """ + + categorical_features = ( + categorical_features or schema.select_by_tag(Tags.CATEGORICAL).column_names + ) + continuous_features = ( + continuous_features or schema.select_by_tag(Tags.CONTINUOUS).column_names + ) + targets = targets or schema.select_by_tag(Tags.TARGET).column_names + + cols_to_read = categorical_features + continuous_features + targets + + return cls( + paths_or_dataset, + batch_size, + max_sequence_length, + cols_to_read=cols_to_read, + target_names=targets, + shuffle=shuffle, + shuffle_buffer_size=shuffle_buffer_size, + num_workers=num_workers, + pin_memory=pin_memory, + **kwargs, + )
+ + +
[docs]class DLDataLoader(PyTorchDataLoader): + """ + This class is an extension of the torch dataloader. + It is required to support the FastAI framework. + + Setting the batch size directly to DLDataLoader makes it 3x slower. + So we set as an alternative attribute and use it within + T4Rec Trainer during evaluation + # TODO : run experiments with new merlin-dataloader + """ + + def __init__(self, *args, **kwargs) -> None: + if "batch_size" in kwargs: + self._batch_size = kwargs.pop("batch_size") + super().__init__(*args, **kwargs) + + @property + def device(self): + return torch.device("cuda" if torch.cuda.is_available() else "cpu") + + def __len__(self): + return len(self.dataset)
+ + +
[docs]@dataloader_registry.register_with_multiple_names( + "merlin_dataloader", "merlin", "nvtabular_dataloader", "nvtabular" +) +class MerlinDataLoader(T4RecDataLoader, DLDataLoader): + """ + This class extends the [Merlin data loader] + (https://github.com/NVIDIA-Merlin/dataloader/blob/stable/merlin/dataloader/torch.py). + The data input requires a merlin.io.Dataset or a path to the data files. + It also sets the dataset's schema with the necessary properties to prepare the input + list features as dense tensors (i.e. padded to the specified `max_sequence_length`). + The dense representation is required by the Transformers4Rec input modules. + + Parameters + ---------- + paths_or_dataset: Union[str, merlin.io.Dataset] + The dataset to load. + batch_size: int + The size of each batch to supply to the model. + max_sequence_length: int + The maximum sequence length to use for padding list columns. + By default, `0` is used as the padding index. + cats : List[str], optional + The list of categorical columns in the dataset. + By default None. + conts: List[str], optional + The list of continuous columns in the dataset. + By default None. + labels : List[str], optional + The list of label columns in the dataset. + By default None. + lists : List[str], optional + The list of sequential columns in the dataset. + By default None. + shuffle : bool, optional + Enable/disable shuffling of dataset. + By default False. + parts_per_chunk : int, optional + The number of partitions from the iterator, an Merlin Dataset, + to concatenate into a "chunk". By default 1. + device : int, optional + The device id of the selected GPU + By default None. + drop_last: bool, optional + Whether or not to drop the last batch in an epoch. This is useful when you need to + guarantee that each batch contains exactly `batch_size` rows - since the last batch + will usually contain fewer rows. + seed_fn: callable + Function used to initialize random state + parts_per_chunk: int + Number of dataset partitions with size dictated by `buffer_size` + to load and concatenate asynchronously. More partitions leads to + better epoch-level randomness but can negatively impact throughput + global_size: int, optional + When doing distributed training, this indicates the number of total processes that are + training the model. + global_rank: int, optional + When doing distributed training, this indicates the local rank for the current process. + schema: Schema, optional + The `Schema` with the input features. + reader_kwargs: + Extra arguments to pass to the merlin.io.Dataset object, when the path to data files + is provided in `paths_or_dataset` argument. + row_groups_per_part: bool, optional + If true, preserve the group partitions when loading the dataset from parquet files. + collate_fn: Callable, optional + A processing function to collect and prepare the list samples + (tuple of (input, target) Tensor(s)) returned by the Merlin DataLoader. + transforms: List[merlin.dag.BaseOperator] + A list of operators that the Merlin dataloader applies on top of the loaded + batch, which is a tuple of input and target tensors. + """ + + def __init__( + self, + paths_or_dataset, + batch_size, + max_sequence_length=None, + conts=None, + cats=None, + labels=None, + lists=None, + collate_fn=lambda x: x[0], + engine=None, + buffer_size=0.1, + reader_kwargs=None, + shuffle=False, + seed_fn=None, + parts_per_chunk=1, + device=None, + global_size=None, + global_rank=None, + drop_last=False, + schema=None, + row_groups_per_part=True, + transforms=None, + **kwargs, + ): + T4RecDataLoader.__init__(self) + + self.paths_or_dataset = paths_or_dataset + self.batch_size = batch_size + self.shuffle = shuffle + self.max_sequence_length = max_sequence_length + self.drop_last = drop_last + + reader_kwargs = reader_kwargs or {} + reader_kwargs["row_groups_per_part"] = row_groups_per_part + self.set_dataset(buffer_size, engine, reader_kwargs, schema=schema) + + if (global_rank is not None) and (self.dataset.npartitions < global_size): + logger.warning( + "UserWarning: User is advised to repartition the parquet file before training " + "so npartitions>=global_size. Cudf or pandas can be used for repartitioning " + "eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas " + "or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf " + "so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible " + "by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used)." + ) + self.dataset = self.dataset.repartition(npartitions=global_size) + + if (global_rank is not None) and (self.dataset.npartitions % global_size != 0): + logger.warning( + f"UserWarning: User is advised to set the number of partitions" + f" ({self.dataset.npartitions}) divisible by the number of available" + f" GPUs ({global_size}). This will divide the work equally among GPUs" + " for DDP training and ensure optimal performance." + ) + + self.dataset.schema = self._augment_schema( + self.dataset.schema, + cats=cats, + conts=conts, + labels=labels, + lists=lists, + ) + + loader = Loader( + self.dataset, + self.batch_size, + shuffle, + seed_fn=seed_fn, + parts_per_chunk=parts_per_chunk, + device=device, + global_size=global_size, + global_rank=global_rank, + drop_last=drop_last, + transforms=transforms, + ) + if max_sequence_length: + # Apply padding + output_schema = loader.output_schema + sparse_feats = [col.name for col in output_schema if Tags.LIST in col.tags] + sparse_max = {name: max_sequence_length for name in sparse_feats} + loader = loader.map(self._get_pad_fn(sparse_max)) + + DLDataLoader.__init__( + self, + loader, + collate_fn=collate_fn, + batch_size=self.batch_size, + drop_last=self.drop_last, + ) + self.schema = schema + self.max_sequence_length = max_sequence_length + + @staticmethod + def _get_pad_fn(padding_lengths): + def pad_fn(x, y): + new_x = pad_batch(x, padding_lengths) + if y is not None and isinstance(y, dict): + new_y = pad_batch(y, padding_lengths) + else: + new_y = y + return new_x, new_y + + return pad_fn + + @staticmethod + def _augment_schema( + schema, + cats=None, + conts=None, + labels=None, + lists=None, + ): + cats = cats or [] + conts = conts or [] + labels = labels or [] + + schema = schema.select_by_name(conts + cats + labels + lists) + + labels = [labels] if isinstance(labels, str) else labels + for label in labels: + schema[label] = schema[label].with_tags(Tags.TARGET) + for label in cats: + schema[label] = schema[label].with_tags(Tags.CATEGORICAL) + for label in conts: + schema[label] = schema[label].with_tags(Tags.CONTINUOUS) + for col in lists: + schema[col] = schema[col].with_tags(Tags.LIST) + + return schema + +
[docs] def set_dataset(self, buffer_size, engine, reader_kwargs, schema=None): + dataset = validate_dataset( + self.paths_or_dataset, + self.batch_size, + buffer_size, + engine, + reader_kwargs, + ) + if schema: + # set the dataset schema based on the provided one to keep all original information + if isinstance(schema, Schema): + # convert merlin-standardlib schema to merlin-core schema + schema = to_core_schema(schema) + dataset.schema = schema + self.dataset = dataset
+ +
[docs] @classmethod + def from_schema( + cls, + schema: Schema, + paths_or_dataset, + batch_size, + max_sequence_length=None, + continuous_features=None, + categorical_features=None, + list_features=None, + targets=None, + collate_fn=lambda x: x[0], + shuffle=True, + buffer_size=0.06, + parts_per_chunk=1, + transforms=None, + **kwargs, + ): + """ + Instantitates `MerlinDataLoader` from a ``DatasetSchema``. + Parameters + ---------- + schema: DatasetSchema + Dataset schema + paths_or_dataset: Union[str, Dataset] + Path to paquet data of Dataset object. + batch_size: int + batch size of Dataloader. + """ + categorical_features = ( + categorical_features or schema.select_by_tag(Tags.CATEGORICAL).column_names + ) + continuous_features = ( + continuous_features or schema.select_by_tag(Tags.CONTINUOUS).column_names + ) + targets = targets or schema.select_by_tag(Tags.TARGET).column_names + list_features = list_features or schema.select_by_tag(Tags.LIST).column_names + schema = schema.select_by_name( + categorical_features + continuous_features + targets + list_features + ) + loader = cls( + paths_or_dataset, + batch_size=batch_size, + labels=targets, + max_sequence_length=max_sequence_length, + cats=categorical_features, + conts=continuous_features, + lists=list_features, + collate_fn=collate_fn, + engine="parquet", + shuffle=shuffle, + buffer_size=buffer_size, # how many batches to load at once + parts_per_chunk=parts_per_chunk, + schema=schema, + transforms=transforms, + **kwargs, + ) + + return loader
+ + @property + def output_schema(self): + return self.dataset.output_schema
+ + +
[docs]class ParquetDataset(Dataset): + def __init__(self, parquet_file, cols_to_read, target_names, seq_features_len_pad_trim): + self.cols_to_read = cols_to_read + self.target_names = target_names + self.data = pq.ParquetDataset(parquet_file).read(columns=self.cols_to_read).to_pandas() + self.seq_features_len_pad_trim = seq_features_len_pad_trim + + def __len__(self): + return len(self.data) + + def __getitem__(self, index): + df = self.data.loc[index] + input_features = list(set(self.cols_to_read).difference(self.target_names)) + inputs = {col: self.pad_seq_column_if_needed(df[col]) for col in input_features} + targets = {col: self.pad_seq_column_if_needed(df[col]) for col in self.target_names} + return inputs, targets + +
[docs] def pad_seq_column_if_needed(self, values): + if type(values) is np.ndarray: + values = values[: self.seq_features_len_pad_trim] + if len(values) < self.seq_features_len_pad_trim: + placeholder = np.zeros(self.seq_features_len_pad_trim, dtype=values.dtype) + placeholder[: len(values)] = values + values = placeholder + if isinstance(values[0], np.floating) and values.dtype is not np.float32: + values = values.astype(np.float32) + if isinstance(values[0], np.integer) and values.dtype is not np.int64: + values = values.astype(np.int64) + return values
+ + +
[docs]class ShuffleDataset(IterableDataset): + def __init__(self, dataset, buffer_size): + super().__init__() + self.dataset = dataset + self.buffer_size = buffer_size + + def __iter__(self): + logger.info("[SHUFFLE] INITIALIZING BUFFER_SIZE: {}".format(self.buffer_size)) + + raise StopIteration() + # TODO define The shuffle method for pyarrow dataloader + + def __len__(self): + return len(self.dataset)
+ + +
[docs]def to_core_schema(t4rec_schema): + return TensorflowMetadata.from_json(t4rec_schema.to_json()).to_merlin_schema()
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/utils/examples_utils.html b/review/pr-767/_modules/transformers4rec/torch/utils/examples_utils.html new file mode 100644 index 0000000000..57bee92b7f --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/utils/examples_utils.html @@ -0,0 +1,232 @@ + + + + + + transformers4rec.torch.utils.examples_utils — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.utils.examples_utils
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.utils.examples_utils

+import gc
+import glob
+import os
+
+import numpy as np
+import torch
+
+
+
[docs]def list_files(startpath): + """ + Util function to print the nested structure of a directory + """ + for root, dirs, files in os.walk(startpath): + level = root.replace(startpath, "").count(os.sep) + indent = " " * 4 * (level) + print("{}{}/".format(indent, os.path.basename(root))) + subindent = " " * 4 * (level + 1) + for f in files: + print("{}{}".format(subindent, f))
+ + +
[docs]def visualize_response(batch, response, top_k, session_col="session_id"): + """ + Util function to extract top-k encoded item-ids from logits + + Parameters + ---------- + batch : cudf.DataFrame + the batch of raw data sent to triton server. + response: tritonclient.grpc.InferResult + the response returned by grpc client. + top_k: int + the `top_k` top items to retrieve from predictions. + """ + sessions = batch[session_col].drop_duplicates().values + predictions = response.as_numpy("output") + top_preds = np.argpartition(predictions, -top_k, axis=1)[:, -top_k:] + for session, next_items in zip(sessions, top_preds): + print( + "- Top-%s predictions for session `%s`: %s\n" + % (top_k, session, " || ".join([str(e) for e in next_items])) + )
+ + +
[docs]def fit_and_evaluate(trainer, start_time_index, end_time_index, input_dir): + """ + Util function for time-window based fine-tuning using the T4rec Trainer class. + Iteratively train using data of a given index and evaluate on the validation data + of the following index. + + Parameters + ---------- + start_time_index: int + The start index for training, it should match the partitions of the data directory + end_time_index: int + The end index for training, it should match the partitions of the data directory + input_dir: str + The input directory where the parquet files were saved based on partition column + + Returns + ------- + indexed_by_time_metrics: dict + The dictionary of ranking metrics: each item is the list of scores over time indices. + """ + indexed_by_time_metrics = {} + for time_index in range(start_time_index, end_time_index + 1): + # 1. Set data + time_index_train = time_index + time_index_eval = time_index + 1 + train_paths = glob.glob(os.path.join(input_dir, f"{time_index_train}/train.parquet")) + eval_paths = glob.glob(os.path.join(input_dir, f"{time_index_eval}/valid.parquet")) + + # 2. Train on train data of time_index + print("\n***** Launch training for day %s: *****" % time_index) + trainer.train_dataset_or_path = train_paths + trainer.reset_lr_scheduler() + trainer.train() + + # 3. Evaluate on valid data of time_index+1 + trainer.eval_dataset_or_path = eval_paths + eval_metrics = trainer.evaluate(metric_key_prefix="eval") + print("\n***** Evaluation results for day %s:*****\n" % time_index_eval) + for key in sorted(eval_metrics.keys()): + if "at_" in key: + print(" %s = %s" % (key.replace("_at_", "@"), str(eval_metrics[key]))) + if "indexed_by_time_" + key.replace("_at_", "@") in indexed_by_time_metrics: + indexed_by_time_metrics["indexed_by_time_" + key.replace("_at_", "@")] += [ + eval_metrics[key] + ] + else: + indexed_by_time_metrics["indexed_by_time_" + key.replace("_at_", "@")] = [ + eval_metrics[key] + ] + + # free GPU for next day training + wipe_memory() + + return indexed_by_time_metrics
+ + +
[docs]def wipe_memory(): + gc.collect() + torch.cuda.empty_cache()
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/utils/schema_utils.html b/review/pr-767/_modules/transformers4rec/torch/utils/schema_utils.html new file mode 100644 index 0000000000..d25a1b6e73 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/utils/schema_utils.html @@ -0,0 +1,303 @@ + + + + + + transformers4rec.torch.utils.schema_utils — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.utils.schema_utils
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.utils.schema_utils

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import random
+from typing import Any, Dict, Optional
+
+import numpy as np
+import torch
+from merlin.schema.io.proto_utils import has_field
+
+from merlin_standard_lib import Schema
+
+from ..typing import TabularData
+
+
+
[docs]def random_data_from_schema( + schema: Schema, + num_rows: int, + max_session_length: Optional[int] = None, + min_session_length: int = 5, + device=None, + ragged=False, + seed=0, +) -> TabularData: + """Generates random tabular data based on a given schema. + The generated data can be used for testing + data preprocessing or model training pipelines. + + Parameters + ---------- + schema : Schema + The schema to be used for generating the random tabular data. + num_rows : int + The number of rows. + max_session_length : Optional[int] + The maximum session length. + If None, the session length will not be limited. + By default None + min_session_length : int + The minimum session length. + By default 5 + device : torch.device + The device on which the synthetic data should be created. + If None, the synthetic data will be created on the CPU. + By default None + ragged : bool + If True, the sequence features will be represented with __values and __offsets. + By default False + + Returns + ------- + TabularData + A dictionary where each key is a feature name and each value is the generated + tensor. + + """ + data: Dict[str, Any] = {} + + random.seed(seed) + np.random.seed(seed) + if seed: + torch.manual_seed(seed) + + for i in range(num_rows): + session_length = None + if max_session_length: + session_length = random.randint(min_session_length, max_session_length) + + for feature in schema.feature: + is_list_feature = has_field(feature, "value_count") + is_int_feature = has_field(feature, "int_domain") + is_embedding = feature.shape.dim[0].size > 1 if has_field(feature, "shape") else False + + shape = [d.size for d in feature.shape.dim] if has_field(feature, "shape") else (1,) + + if is_int_feature: + max_num = feature.int_domain.max + if is_list_feature: + list_length = session_length or feature.value_count.max + row = torch.randint(1, max_num, (list_length,), device=device) + + else: + row = torch.randint(1, max_num, tuple(shape), device=device) + else: + if is_list_feature: + list_length = session_length or feature.value_count.max + row = torch.rand((list_length,), device=device) + else: + row = torch.rand(tuple(shape), device=device) + + if is_list_feature: + row = (row, [len(row)]) # type: ignore + + if feature.name in data: + if is_list_feature: + data[feature.name] = ( + torch.cat((data[feature.name][0], row[0])), + data[feature.name][1] + row[1], + ) + elif is_embedding: + f = data[feature.name] + if isinstance(f, list): + f.append(row) + else: + data[feature.name] = [f, row] + if i == num_rows - 1: + data[feature.name] = torch.stack(data[feature.name], dim=0) + else: + data[feature.name] = torch.cat((data[feature.name], row)) + else: + data[feature.name] = row + + outputs: TabularData = {} + for key, val in data.items(): + if isinstance(val, tuple): + offsets = [0] + for row_length in val[1]: + offsets.append(offsets[-1] + row_length) + + if ragged: + outputs[f"{key}__values"] = val[0] + outputs[f"{key}__offsets"] = torch.tensor(offsets, device=device) + else: + vals = (val[0], torch.tensor(offsets[:-1], device=device).unsqueeze(dim=1)) + values, offsets, diff_offsets, num_rows = _pull_values_offsets(vals, device=device) + indices = _get_indices(offsets, diff_offsets, device=device) + seq_limit = max_session_length or val[1][0] + outputs[key] = _get_sparse_tensor(values, indices, num_rows, seq_limit) + else: + outputs[key] = data[key] + + return outputs
+ + +def _pull_values_offsets(values_offset, device=None): + # pull_values_offsets, return values offsets diff_offsets + if isinstance(values_offset, tuple): + values = values_offset[0].flatten() + offsets = values_offset[1].flatten() + else: + values = values_offset.flatten() + offsets = torch.arange(values.size()[0], device=device) + num_rows = len(offsets) + offsets = torch.cat([offsets, torch.tensor([len(values)], device=device)]) + diff_offsets = offsets[1:] - offsets[:-1] + return values, offsets, diff_offsets, num_rows + + +def _get_indices(offsets, diff_offsets, device=None): + row_ids = torch.arange(len(offsets) - 1, device=device) + row_ids_repeated = torch.repeat_interleave(row_ids, diff_offsets) + row_offset_repeated = torch.repeat_interleave(offsets[:-1], diff_offsets) + col_ids = torch.arange(len(row_offset_repeated), device=device) - row_offset_repeated + indices = torch.cat([row_ids_repeated.unsqueeze(-1), col_ids.unsqueeze(-1)], axis=1) + return indices + + +def _get_sparse_tensor(values, indices, num_rows, seq_limit): + sparse_tensor = torch.sparse_coo_tensor(indices.T, values, torch.Size([num_rows, seq_limit])) + + return sparse_tensor.to_dense() +
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/torch/utils/torch_utils.html b/review/pr-767/_modules/transformers4rec/torch/utils/torch_utils.html new file mode 100644 index 0000000000..c9b4df8eb3 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/torch/utils/torch_utils.html @@ -0,0 +1,602 @@ + + + + + + transformers4rec.torch.utils.torch_utils — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.torch.utils.torch_utils
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.torch.utils.torch_utils

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import abc
+from collections.abc import Mapping
+from dataclasses import dataclass
+from typing import Dict, Optional, Union
+
+import numpy as np
+import torch
+from merlin.schema import Schema as CoreSchema
+from merlin.schema.io.proto_utils import has_field
+from merlin.schema.tags import Tags
+
+from merlin_standard_lib import Schema
+from merlin_standard_lib.schema.schema import ColumnSchema
+
+from ...config.schema import SchemaMixin
+from ..typing import TabularData
+
+
+
[docs]class OutputSizeMixin(SchemaMixin, abc.ABC): +
[docs] def build(self, input_size, schema=None, **kwargs): + self.check_schema(schema=schema) + + self.input_size = input_size + if schema and not getattr(self, "schema", None): + self.schema = schema + + return self
+ +
[docs] def output_size(self, input_size=None): + input_size = input_size or getattr(self, "input_size", None) + if not input_size: + # TODO: log warning here + return None + + return self.forward_output_size(input_size)
+ +
[docs] def forward_output_size(self, input_size): + raise NotImplementedError()
+ + def __rrshift__(self, other): + from ..block.base import right_shift_block + + return right_shift_block(self, other)
+ + +
[docs]class LossMixin: + """Mixin to use for a `torch.Module` that can calculate a loss.""" + +
[docs] def compute_loss( + self, + inputs: Union[torch.Tensor, TabularData], + targets: Union[torch.Tensor, TabularData], + compute_metrics: bool = True, + **kwargs, + ) -> torch.Tensor: + """Compute the loss on a batch of data. + + Parameters + ---------- + inputs: Union[torch.Tensor, TabularData] + TODO + targets: Union[torch.Tensor, TabularData] + TODO + compute_metrics: bool, default=True + Boolean indicating whether or not to update the state of the metrics + (if they are defined). + """ + raise NotImplementedError()
+ + +
[docs]class MetricsMixin: + """Mixin to use for a `torch.Module` that can calculate metrics.""" + +
[docs] def calculate_metrics( + self, + inputs: Union[torch.Tensor, TabularData], + targets: Union[torch.Tensor, TabularData], + ) -> Dict[str, torch.Tensor]: + """Calculate metrics on a batch of data, each metric is stateful and this updates the state. + + The state of each metric can be retrieved by calling the `compute_metrics` method. + + Parameters + ---------- + inputs: Union[torch.Tensor, TabularData] + Tensor or dictionary of predictions returned by the T4Rec model + targets: Union[torch.Tensor, TabularData] + Tensor or dictionary of true labels returned by the T4Rec model + + + """ + raise NotImplementedError()
+ +
[docs] def compute_metrics(self, mode: str = None) -> Dict[str, Union[float, torch.Tensor]]: + """Returns the current state of each metric. + + The state is typically updated each batch by calling the `calculate_metrics` method. + + Parameters + ---------- + mode: str, default="val" + + Returns + ------- + Dict[str, Union[float, torch.Tensor]] + """ + raise NotImplementedError()
+ +
[docs] def reset_metrics(self): + """Reset all metrics.""" + raise NotImplementedError()
+ + +
[docs]def requires_schema(module): + module.REQUIRES_SCHEMA = True + + return module
+ + +
[docs]def check_gpu(module): + try: + return next(module.parameters()).is_cuda + except StopIteration: + return False
+ + +def _has_field(col_schema, field_name): + if isinstance(col_schema, ColumnSchema): + return has_field(col_schema, field_name) + + return getattr(col_schema, field_name, None) + + +def _get_size_from_shape(col_schema, batch_size) -> torch.Size: + shape = [batch_size] + + if isinstance(col_schema, ColumnSchema): + if has_field(col_schema, "shape"): + shape += [d.size for d in col_schema.shape.dim] + elif col_schema.shape.dims is not None: + if len(col_schema.shape.dims) == 1 and col_schema.shape.dims[0].max is None: + return torch.Size(shape) + raise NotImplementedError("TODO: support shape.dims") + + return torch.Size(shape) + + +
[docs]def get_output_sizes_from_schema(schema: Schema, batch_size=-1, max_sequence_length=None): + sizes = {} + + features = schema if isinstance(schema, CoreSchema) else schema.feature + + for feature in features: + name = feature.name + # Pretrained embeddings (2-D or 3-D) + if Tags.EMBEDDING in feature.tags: + if len(feature.shape.dims) > 2: + sizes[name] = torch.Size( + [ + batch_size, + max_sequence_length if max_sequence_length else feature.value_count.max, + feature.shape[-1].min, + ] + ) + else: + sizes[name] = torch.Size([batch_size, feature.shape[-1].min]) + + # Sequential or multi-hot feature + elif _has_field(feature, "value_count"): + sizes[name] = torch.Size( + [ + batch_size, + max_sequence_length if max_sequence_length else feature.value_count.max, + ] + ) + + else: + sizes[name] = _get_size_from_shape(feature, batch_size) + + return sizes
+ + +
[docs]def calculate_batch_size_from_input_size(input_size): + if isinstance(input_size, dict): + input_size = [i for i in input_size.values() if isinstance(i, torch.Size)][0] + + return input_size[0]
+ + +
[docs]def check_inputs(ks, scores, labels): + if not (isinstance(ks, (list, tuple)) and len(ks) >= 1): + raise ValueError("ks should be a list or tuple with at least one element") + + if len(scores.shape) != 2: + raise ValueError("scores must be a 2-dimensional tensor") + + if len(labels.shape) != 2: + raise ValueError("labels must be a 2-dimensional tensor") + + if scores.shape != labels.shape: + raise ValueError("scores and labels must be the same shape") + + return ( + ks, + scores, + labels, + )
+ + +
[docs]def extract_topk(ks, scores, labels): + max_k = int(max(ks)) + topk_scores, topk_indices = torch.topk(scores, max_k) + topk_labels = torch.gather(labels, 1, topk_indices) + return topk_scores, topk_indices, topk_labels
+ + +
[docs]def create_output_placeholder(scores, ks): + return torch.zeros(scores.shape[0], len(ks)).to(device=scores.device, dtype=torch.float32)
+ + +
[docs]def tranform_label_to_onehot(labels, vocab_size): + return one_hot_1d(labels.reshape(-1).to(torch.int64), vocab_size, dtype=torch.float32).detach()
+ + +
[docs]def nested_detach(tensors): + """Detach `tensors` (even if it's a nested list/tuple/dict of tensors). + #TODO this method was copied from the latest version of HF transformers library to support + dict outputs. So we should remove it when T4Rec is updated to use the latest version + """ + if isinstance(tensors, (list, tuple)): + return type(tensors)(nested_detach(t) for t in tensors) + elif isinstance(tensors, Mapping): + return type(tensors)({k: nested_detach(t) for k, t in tensors.items()}) + return tensors.detach()
+ + +
[docs]def nested_concat(tensors, new_tensors, padding_index=-100): + """ + Concat the `new_tensors` to `tensors` on the first dim and pad them on the second if needed. + Works for tensors or nested list/tuples/dict of tensors. + #TODO this method was copied from the latest version of HF transformers library to support + dict outputs. So we should remove it when T4Rec is updated to use the latest version + """ + assert type(tensors) == type( + new_tensors + ), f"Expected `tensors` and `new_tensors` to have the same type but found {type(tensors)}" + f" and {type(new_tensors)}." + if isinstance(tensors, (list, tuple)): + return type(tensors)( + nested_concat(t, n, padding_index=padding_index) for t, n in zip(tensors, new_tensors) + ) + elif isinstance(tensors, torch.Tensor): + return torch_pad_and_concatenate(tensors, new_tensors, padding_index=padding_index) + elif isinstance(tensors, Mapping): + return type(tensors)( + { + k: nested_concat(t, new_tensors[k], padding_index=padding_index) + for k, t in tensors.items() + } + ) + elif isinstance(tensors, np.ndarray): + return numpy_pad_and_concatenate(tensors, new_tensors, padding_index=padding_index) + else: + raise TypeError(f"Unsupported type for concatenation: got {type(tensors)}")
+ + +
[docs]def torch_pad_and_concatenate(tensor1, tensor2, padding_index=-100): + """Concatenates `tensor1` and `tensor2` on first axis, applying padding on the second as needed + + #TODO this method was copied from the latest version of HF transformers library to support + dict outputs. So we should remove it when T4Rec is updated to use the latest version + """ + tensor1 = atleast_1d(tensor1) + tensor2 = atleast_1d(tensor2) + + if len(tensor1.shape) == 1 or tensor1.shape[1] == tensor2.shape[1]: + return torch.cat((tensor1, tensor2), dim=0) + + # Let's figure out the new shape + new_shape = ( + tensor1.shape[0] + tensor2.shape[0], + max(tensor1.shape[1], tensor2.shape[1]), + ) + tensor1.shape[2:] + + # Now let's fill the result tensor + result = tensor1.new_full(new_shape, padding_index) + result[: tensor1.shape[0], : tensor1.shape[1]] = tensor1 + result[tensor1.shape[0] :, : tensor2.shape[1]] = tensor2 + return result
+ + +
[docs]def atleast_1d(tensor_or_array: Union[torch.Tensor, np.ndarray]): + if isinstance(tensor_or_array, torch.Tensor): + if hasattr(torch, "atleast_1d"): + tensor_or_array = torch.atleast_1d(tensor_or_array) + elif tensor_or_array.ndim < 1: + tensor_or_array = tensor_or_array[None] + else: + tensor_or_array = np.atleast_1d(tensor_or_array) + return tensor_or_array
+ + +
[docs]def nested_numpify(tensors): + """Numpify `tensors` (even if it's a nested list/tuple/dict of tensors). + #TODO this method was copied from the latest version of HF transformers library to support + dict outputs. So we should remove it when T4Rec is updated to use the latest version + """ + if isinstance(tensors, (list, tuple)): + return type(tensors)(nested_numpify(t) for t in tensors) + if isinstance(tensors, Mapping): + return type(tensors)({k: nested_numpify(t) for k, t in tensors.items()}) + + t = tensors.cpu() + if t.dtype == torch.bfloat16: + """ + # As of Numpy 1.21.4, NumPy does not support bfloat16 (see + # https://github.com/numpy/numpy/blob/a47ecdea856986cd60eabbd53265c2ca5916ad5d/doc/source/user/basics.types.rst). + # Until Numpy adds bfloat16, we must convert float32. + """ + t = t.to(torch.float32) + return t.numpy()
+ + +
[docs]def nested_truncate(tensors, limit): + """Truncate `tensors` at `limit` (even if it's a nested list/tuple/dict of tensors). + #TODO this method was copied from the latest version of HF transformers library to support + dict outputs. So we should remove it when T4Rec is updated to use the latest version + """ + if isinstance(tensors, (list, tuple)): + return type(tensors)(nested_truncate(t, limit) for t in tensors) + if isinstance(tensors, Mapping): + return type(tensors)({k: nested_truncate(t, limit) for k, t in tensors.items()}) + + return tensors[:limit]
+ + +
[docs]def numpy_pad_and_concatenate(array1, array2, padding_index=-100): + """ + Concatenates `array1` and `array2` on first axis, applying padding on the second if necessary. + #TODO this method was copied from the latest version of HF transformers library to support + dict outputs. So we should remove it when T4Rec is updated to use the latest version + """ + array1 = atleast_1d(array1) + array2 = atleast_1d(array2) + + if len(array1.shape) == 1 or array1.shape[1] == array2.shape[1]: + return np.concatenate((array1, array2), axis=0) + + # Let's figure out the new shape + new_shape = ( + array1.shape[0] + array2.shape[0], + max(array1.shape[1], array2.shape[1]), + ) + array1.shape[2:] + + # Now let's fill the result tensor + result = np.full_like(array1, padding_index, shape=new_shape) + result[: array1.shape[0], : array1.shape[1]] = array1 + result[array1.shape[0] :, : array2.shape[1]] = array2 + return result
+ + +
[docs]def one_hot_1d( + labels: torch.Tensor, + num_classes: int, + device: Optional[torch.device] = None, + dtype: Optional[torch.dtype] = torch.float32, +) -> torch.Tensor: + r"""Coverts a 1d label tensor to one-hot representation + + Args: + labels (torch.Tensor) : tensor with labels of shape :math:`(N, H, W)`, + where N is batch size. Each value is an integer + representing correct classification. + num_classes (int): number of classes in labels. + device (Optional[torch.device]): the desired device of returned tensor. + Default: if None, uses the current device for the default tensor type + (see torch.set_default_tensor_type()). device will be the CPU for CPU + tensor types and the current CUDA device for CUDA tensor types. + dtype (Optional[torch.dtype]): the desired data type of returned + tensor. Default: torch.float32 + + Returns: + torch.Tensor: the labels in one hot tensor. + + Examples:: + >>> labels = torch.LongTensor([0, 1, 2, 0]) + >>> one_hot_1d(labels, num_classes=3) + tensor([[1., 0., 0.], + [0., 1., 0.], + [0., 0., 1.], + [1., 0., 0.], + ]) + """ + if not torch.is_tensor(labels): + raise TypeError("Input labels type is not a torch.Tensor. Got {}".format(type(labels))) + if not len(labels.shape) == 1: + raise ValueError("Expected tensor should have 1 dim. Got: {}".format(labels.shape)) + if not labels.dtype == torch.int64: + raise ValueError( + "labels must be of the same dtype torch.int64. Got: {}".format(labels.dtype) + ) + if num_classes < 1: + raise ValueError( + "The number of classes must be bigger than one." " Got: {}".format(num_classes) + ) + if device is None: + device = labels.device + labels_size = labels.shape[0] + one_hot = torch.zeros(labels_size, num_classes, device=device, dtype=dtype) + return one_hot.scatter_(1, labels.unsqueeze(-1), 1.0)
+ + +
[docs]class LambdaModule(torch.nn.Module): + def __init__(self, lambda_fn): + super().__init__() + import types + + assert isinstance(lambda_fn, types.LambdaType) + self.lambda_fn = lambda_fn + +
[docs] def forward(self, x): + return self.lambda_fn(x)
+ + +
[docs]@dataclass +class MappingTransformerMasking: + from transformers4rec.torch.masking import ( + CausalLanguageModeling, + MaskedLanguageModeling, + PermutationLanguageModeling, + ReplacementLanguageModeling, + ) + + DEFAULT_MASKING = [ + CausalLanguageModeling, + MaskedLanguageModeling, + ReplacementLanguageModeling, + PermutationLanguageModeling, + ] + + BertConfig = [MaskedLanguageModeling, ReplacementLanguageModeling] + ConvBertConfig = [MaskedLanguageModeling, ReplacementLanguageModeling] + DebertaConfig = [MaskedLanguageModeling, ReplacementLanguageModeling] + DistilBertConfig = [MaskedLanguageModeling, ReplacementLanguageModeling] + GPT2Config = [CausalLanguageModeling] + LongformerConfig = [CausalLanguageModeling, MaskedLanguageModeling, ReplacementLanguageModeling] + MegatronBertConfig = [MaskedLanguageModeling, ReplacementLanguageModeling] + MPNetConfig = [MaskedLanguageModeling, ReplacementLanguageModeling] + RobertaConfig = [MaskedLanguageModeling, ReplacementLanguageModeling] + RoFormerConfig = [CausalLanguageModeling, MaskedLanguageModeling, ReplacementLanguageModeling] + TransfoXLConfig = [CausalLanguageModeling] + XLNetConfig = [ + CausalLanguageModeling, + MaskedLanguageModeling, + ReplacementLanguageModeling, + PermutationLanguageModeling, + ]
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_modules/transformers4rec/utils/dependencies.html b/review/pr-767/_modules/transformers4rec/utils/dependencies.html new file mode 100644 index 0000000000..07281c2865 --- /dev/null +++ b/review/pr-767/_modules/transformers4rec/utils/dependencies.html @@ -0,0 +1,168 @@ + + + + + + transformers4rec.utils.dependencies — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Module code »
  • +
  • transformers4rec.utils.dependencies
  • +
  • +
  • +
+
+
+
+
+ +

Source code for transformers4rec.utils.dependencies

+#
+# Copyright (c) 2021, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
[docs]def is_gpu_dataloader_available() -> bool: + try: + import cudf + import cupy + except ImportError: + cudf = None + cupy = None + return cudf is not None and cupy is not None
+ + +
[docs]def is_pyarrow_available() -> bool: + try: + import pyarrow + except ImportError: + pyarrow = None + return pyarrow is not None
+ + +
[docs]def is_merlin_dataloader_available() -> bool: + try: + import merlin.dataloader + except ImportError: + merlin.dataloader = None + return merlin.dataloader is not None
+
+ +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/_static/basic.css b/review/pr-767/_static/basic.css new file mode 100644 index 0000000000..b3bdc00406 --- /dev/null +++ b/review/pr-767/_static/basic.css @@ -0,0 +1,861 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/review/pr-767/_static/css/badge_only.css b/review/pr-767/_static/css/badge_only.css new file mode 100644 index 0000000000..e380325bc6 --- /dev/null +++ b/review/pr-767/_static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/review/pr-767/_static/css/custom.css b/review/pr-767/_static/css/custom.css new file mode 100644 index 0000000000..319ddff89a --- /dev/null +++ b/review/pr-767/_static/css/custom.css @@ -0,0 +1,34 @@ +.wy-nav-content { + margin: 0; + background: #fcfcfc; + padding-top: 40px; +} + +.wy-side-nav-search { + display: block; + width: 300px; + padding: .809em; + padding-top: 0.809em; + margin-bottom: .809em; + z-index: 200; + background-color: #2980b9; + text-align: center; + color: #fcfcfc; + padding-top: 40px; +} + +div.banner { + position: fixed; + top: 10px; + left: 20px; + margin: 0; + z-index: 1000; + width: 1050px; + text-align: center; +} + +p.banner { + border-radius: 4px; + color: #004831; + background: #76b900; +} \ No newline at end of file diff --git a/review/pr-767/_static/css/fonts/Roboto-Slab-Bold.woff b/review/pr-767/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000000..6cb6000018 Binary files /dev/null and b/review/pr-767/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/review/pr-767/_static/css/fonts/Roboto-Slab-Bold.woff2 b/review/pr-767/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000000..7059e23142 Binary files /dev/null and b/review/pr-767/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/review/pr-767/_static/css/fonts/Roboto-Slab-Regular.woff b/review/pr-767/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000000..f815f63f99 Binary files /dev/null and b/review/pr-767/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/review/pr-767/_static/css/fonts/Roboto-Slab-Regular.woff2 b/review/pr-767/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000000..f2c76e5bda Binary files /dev/null and b/review/pr-767/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/review/pr-767/_static/css/fonts/fontawesome-webfont.eot b/review/pr-767/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000..e9f60ca953 Binary files /dev/null and b/review/pr-767/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/review/pr-767/_static/css/fonts/fontawesome-webfont.svg b/review/pr-767/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000000..855c845e53 --- /dev/null +++ b/review/pr-767/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/review/pr-767/_static/css/fonts/fontawesome-webfont.ttf b/review/pr-767/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000..35acda2fa1 Binary files /dev/null and b/review/pr-767/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/review/pr-767/_static/css/fonts/fontawesome-webfont.woff b/review/pr-767/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000..400014a4b0 Binary files /dev/null and b/review/pr-767/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/review/pr-767/_static/css/fonts/fontawesome-webfont.woff2 b/review/pr-767/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000000..4d13fc6040 Binary files /dev/null and b/review/pr-767/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/review/pr-767/_static/css/fonts/lato-bold-italic.woff b/review/pr-767/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000000..88ad05b9ff Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-bold-italic.woff differ diff --git a/review/pr-767/_static/css/fonts/lato-bold-italic.woff2 b/review/pr-767/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000000..c4e3d804b5 Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/review/pr-767/_static/css/fonts/lato-bold.woff b/review/pr-767/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000000..c6dff51f06 Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-bold.woff differ diff --git a/review/pr-767/_static/css/fonts/lato-bold.woff2 b/review/pr-767/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000000..bb195043cf Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-bold.woff2 differ diff --git a/review/pr-767/_static/css/fonts/lato-normal-italic.woff b/review/pr-767/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000000..76114bc033 Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-normal-italic.woff differ diff --git a/review/pr-767/_static/css/fonts/lato-normal-italic.woff2 b/review/pr-767/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000000..3404f37e2e Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/review/pr-767/_static/css/fonts/lato-normal.woff b/review/pr-767/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000000..ae1307ff5f Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-normal.woff differ diff --git a/review/pr-767/_static/css/fonts/lato-normal.woff2 b/review/pr-767/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000000..3bf9843328 Binary files /dev/null and b/review/pr-767/_static/css/fonts/lato-normal.woff2 differ diff --git a/review/pr-767/_static/css/theme.css b/review/pr-767/_static/css/theme.css new file mode 100644 index 0000000000..0d9ae7e1a4 --- /dev/null +++ b/review/pr-767/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,.wy-nav-top a,.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.rst-content .wy-breadcrumbs li tt,.wy-breadcrumbs li .rst-content tt,.wy-breadcrumbs li code{padding:5px;border:none;background:none}.rst-content .wy-breadcrumbs li tt.literal,.wy-breadcrumbs li .rst-content tt.literal,.wy-breadcrumbs li code.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.field-list>dt:after,html.writer-html5 .rst-content dl.footnote>dt:after{content:":"}html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.footnote>dt>span.brackets{margin-right:.5rem}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{font-style:italic}html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.footnote>dd p,html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{font-size:inherit;line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.field-list)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dl:not(.field-list)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/review/pr-767/_static/doctools.js b/review/pr-767/_static/doctools.js new file mode 100644 index 0000000000..61ac9d266f --- /dev/null +++ b/review/pr-767/_static/doctools.js @@ -0,0 +1,321 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/review/pr-767/_static/documentation_options.js b/review/pr-767/_static/documentation_options.js new file mode 100644 index 0000000000..b81a3a72f8 --- /dev/null +++ b/review/pr-767/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: false, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/review/pr-767/_static/file.png b/review/pr-767/_static/file.png new file mode 100644 index 0000000000..a858a410e4 Binary files /dev/null and b/review/pr-767/_static/file.png differ diff --git a/review/pr-767/_static/jquery-3.5.1.js b/review/pr-767/_static/jquery-3.5.1.js new file mode 100644 index 0000000000..50937333b9 --- /dev/null +++ b/review/pr-767/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

merlin_standard_lib package

+ +
+

Submodules

+
+
+

merlin_standard_lib.registry module

+
+
+

Module contents

+
+
+class merlin_standard_lib.ColumnSchema(name: str = <betterproto._PLACEHOLDER object>, deprecated: bool = <betterproto._PLACEHOLDER object>, presence: merlin_standard_lib.proto.schema_bp.FeaturePresence = <betterproto._PLACEHOLDER object>, group_presence: merlin_standard_lib.proto.schema_bp.FeaturePresenceWithinGroup = <betterproto._PLACEHOLDER object>, shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>, value_count: merlin_standard_lib.proto.schema_bp.ValueCount = <betterproto._PLACEHOLDER object>, value_counts: merlin_standard_lib.proto.schema_bp.ValueCountList = <betterproto._PLACEHOLDER object>, type: merlin_standard_lib.proto.schema_bp.FeatureType = <betterproto._PLACEHOLDER object>, domain: str = <betterproto._PLACEHOLDER object>, int_domain: merlin_standard_lib.proto.schema_bp.IntDomain = <betterproto._PLACEHOLDER object>, float_domain: merlin_standard_lib.proto.schema_bp.FloatDomain = <betterproto._PLACEHOLDER object>, string_domain: merlin_standard_lib.proto.schema_bp.StringDomain = <betterproto._PLACEHOLDER object>, bool_domain: merlin_standard_lib.proto.schema_bp.BoolDomain = <betterproto._PLACEHOLDER object>, struct_domain: merlin_standard_lib.proto.schema_bp.StructDomain = <betterproto._PLACEHOLDER object>, natural_language_domain: merlin_standard_lib.proto.schema_bp.NaturalLanguageDomain = <betterproto._PLACEHOLDER object>, image_domain: merlin_standard_lib.proto.schema_bp.ImageDomain = <betterproto._PLACEHOLDER object>, mid_domain: merlin_standard_lib.proto.schema_bp.MIDDomain = <betterproto._PLACEHOLDER object>, url_domain: merlin_standard_lib.proto.schema_bp.URLDomain = <betterproto._PLACEHOLDER object>, time_domain: merlin_standard_lib.proto.schema_bp.TimeDomain = <betterproto._PLACEHOLDER object>, time_of_day_domain: merlin_standard_lib.proto.schema_bp.TimeOfDayDomain = <betterproto._PLACEHOLDER object>, distribution_constraints: merlin_standard_lib.proto.schema_bp.DistributionConstraints = <betterproto._PLACEHOLDER object>, annotation: merlin_standard_lib.proto.schema_bp.Annotation = <betterproto._PLACEHOLDER object>, skew_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>, drift_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>, in_environment: List[str] = <betterproto._PLACEHOLDER object>, not_in_environment: List[str] = <betterproto._PLACEHOLDER object>, lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>, unique_constraints: merlin_standard_lib.proto.schema_bp.UniqueConstraints = <betterproto._PLACEHOLDER object>)[source]
+

Bases: merlin_standard_lib.proto.schema_bp.Feature

+
+
+classmethod create_categorical(name: str, num_items: int, shape: Optional[Union[List[int], Tuple[int, ]]] = None, value_count: Optional[Union[merlin_standard_lib.proto.schema_bp.ValueCount, merlin_standard_lib.proto.schema_bp.ValueCountList]] = None, min_index: int = 0, tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, **kwargs)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+classmethod create_continuous(name: str, is_float: bool = True, min_value: Optional[Union[int, float]] = None, max_value: Optional[Union[int, float]] = None, disallow_nan: bool = False, disallow_inf: bool = False, is_embedding: bool = False, shape: Optional[Union[List[int], Tuple[int, ]]] = None, value_count: Optional[Union[merlin_standard_lib.proto.schema_bp.ValueCount, merlin_standard_lib.proto.schema_bp.ValueCountList]] = None, tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, **kwargs)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+copy(**kwargs)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+with_name(name: str)[source]
+
+ +
+
+with_tags(tags: Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]])merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+with_tags_based_on_properties(using_value_count=True, using_domain=True)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+with_properties(properties: Dict[str, Union[str, int, float]])merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+to_proto_text()str[source]
+
+ +
+
+property tags
+
+ +
+
+property properties
+
+ +
+ +
+
+class merlin_standard_lib.Schema(feature: Sequence[merlin_standard_lib.proto.schema_bp.Feature] = <betterproto._PLACEHOLDER object>, sparse_feature: List[merlin_standard_lib.proto.schema_bp.SparseFeature] = <betterproto._PLACEHOLDER object>, weighted_feature: List[merlin_standard_lib.proto.schema_bp.WeightedFeature] = <betterproto._PLACEHOLDER object>, string_domain: List[merlin_standard_lib.proto.schema_bp.StringDomain] = <betterproto._PLACEHOLDER object>, float_domain: List[merlin_standard_lib.proto.schema_bp.FloatDomain] = <betterproto._PLACEHOLDER object>, int_domain: List[merlin_standard_lib.proto.schema_bp.IntDomain] = <betterproto._PLACEHOLDER object>, default_environment: List[str] = <betterproto._PLACEHOLDER object>, annotation: merlin_standard_lib.proto.schema_bp.Annotation = <betterproto._PLACEHOLDER object>, dataset_constraints: merlin_standard_lib.proto.schema_bp.DatasetConstraints = <betterproto._PLACEHOLDER object>, tensor_representation_group: Dict[str, merlin_standard_lib.proto.schema_bp.TensorRepresentationGroup] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: merlin_standard_lib.proto.schema_bp._Schema

+

A collection of column schemas for a dataset.

+
+
+feature: List[merlin_standard_lib.schema.schema.ColumnSchema] = Field(name=None,type=None,default=<betterproto._PLACEHOLDER object>,default_factory=<dataclasses._MISSING_TYPE object>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({'betterproto': FieldMetadata(number=1, proto_type='message', map_types=None, group=None, wraps=None)}),_field_type=None)
+
+ +
+
+classmethod create(column_schemas: Optional[Union[List[Union[merlin_standard_lib.schema.schema.ColumnSchema, str]], Dict[str, Union[merlin_standard_lib.schema.schema.ColumnSchema, str]]]] = None, **kwargs)[source]
+
+ +
+
+with_tags_based_on_properties(using_value_count=True, using_domain=True)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+apply(selector)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+apply_inverse(selector)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+filter_columns_from_dict(input_dict)[source]
+
+ +
+
+select_by_type(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_type(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+select_by_tag(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_tag(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+select_by_name(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_name(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+map_column_schemas(map_fn: Callable[[merlin_standard_lib.schema.schema.ColumnSchema], merlin_standard_lib.schema.schema.ColumnSchema])merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+filter_column_schemas(filter_fn: Callable[[merlin_standard_lib.schema.schema.ColumnSchema], bool], negate=False)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+property column_names
+
+ +
+
+property column_schemas
+
+ +
+
+property item_id_column_name
+
+ +
+
+from_json(value: Union[str, bytes])merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+to_proto_text()str[source]
+
+ +
+
+from_proto_text(path_or_proto_text: str)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+copy(**kwargs)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+add(other, allow_overlap=True)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+ +
+
+merlin_standard_lib.categorical_cardinalities(schema)Dict[str, int][source]
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/merlin_standard_lib.proto.html b/review/pr-767/api/merlin_standard_lib.proto.html new file mode 100644 index 0000000000..d6fd6f2110 --- /dev/null +++ b/review/pr-767/api/merlin_standard_lib.proto.html @@ -0,0 +1,1344 @@ + + + + + + merlin_standard_lib.proto package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

merlin_standard_lib.proto package

+
+

Submodules

+
+
+

merlin_standard_lib.proto.schema_bp module

+
+
+class merlin_standard_lib.proto.schema_bp.LifecycleStage(value)[source]
+

Bases: betterproto.Enum

+

LifecycleStage. Only UNKNOWN_STAGE, BETA, and PRODUCTION features are +actually validated. PLANNED, ALPHA, DISABLED, and DEBUG are treated as +DEPRECATED.

+
+
+UNKNOWN_STAGE = 0
+
+ +
+
+PLANNED = 1
+
+ +
+
+ALPHA = 2
+
+ +
+
+BETA = 3
+
+ +
+
+PRODUCTION = 4
+
+ +
+
+DEPRECATED = 5
+
+ +
+
+DEBUG_ONLY = 6
+
+ +
+
+DISABLED = 7
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FeatureType(value)[source]
+

Bases: betterproto.Enum

+

Describes the physical representation of a feature. It may be different +than the logical representation, which is represented as a Domain.

+
+
+TYPE_UNKNOWN = 0
+
+ +
+
+BYTES = 1
+
+ +
+
+INT = 2
+
+ +
+
+FLOAT = 3
+
+ +
+
+STRUCT = 4
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TimeDomainIntegerTimeFormat(value)[source]
+

Bases: betterproto.Enum

+

An enumeration.

+
+
+FORMAT_UNKNOWN = 0
+
+ +
+
+UNIX_DAYS = 5
+
+ +
+
+UNIX_SECONDS = 1
+
+ +
+
+UNIX_MILLISECONDS = 2
+
+ +
+
+UNIX_MICROSECONDS = 3
+
+ +
+
+UNIX_NANOSECONDS = 4
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TimeOfDayDomainIntegerTimeOfDayFormat(value)[source]
+

Bases: betterproto.Enum

+

An enumeration.

+
+
+FORMAT_UNKNOWN = 0
+
+ +
+
+PACKED_64_NANOS = 1
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationRowPartitionDType(value)[source]
+

Bases: betterproto.Enum

+

An enumeration.

+
+
+UNSPECIFIED = 0
+
+ +
+
+INT64 = 1
+
+ +
+
+INT32 = 2
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.Path(step: List[str] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A path is a more general substitute for the name of a field or feature that +can be used for flat examples as well as structured data. For example, if +we had data in a protocol buffer: message Person { int age = 1; +optional string gender = 2; repeated Person parent = 3; } Thus, here the +path {step:[“parent”, “age”]} in statistics would refer to the age of a +parent, and {step:[“parent”, “parent”, “age”]} would refer to the age of a +grandparent. This allows us to distinguish between the statistics of +parents’ ages and grandparents’ ages. In general, repeated messages are to +be preferred to linked lists of arbitrary length. For SequenceExample, if +we have a feature list “foo”, this is represented by {step:[“##SEQUENCE##”, +“foo”]}.

+
+
+step: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.ValueCountList(value_count: List[ForwardRef('ValueCount')] = <betterproto._PLACEHOLDER object at 0x7f338f170520>)[source]
+

Bases: betterproto.Message

+
+
+value_count: List[merlin_standard_lib.proto.schema_bp.ValueCount] = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.Feature(name: str = <betterproto._PLACEHOLDER object>, deprecated: bool = <betterproto._PLACEHOLDER object>, presence: merlin_standard_lib.proto.schema_bp.FeaturePresence = <betterproto._PLACEHOLDER object>, group_presence: merlin_standard_lib.proto.schema_bp.FeaturePresenceWithinGroup = <betterproto._PLACEHOLDER object>, shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>, value_count: merlin_standard_lib.proto.schema_bp.ValueCount = <betterproto._PLACEHOLDER object>, value_counts: merlin_standard_lib.proto.schema_bp.ValueCountList = <betterproto._PLACEHOLDER object>, type: merlin_standard_lib.proto.schema_bp.FeatureType = <betterproto._PLACEHOLDER object>, domain: str = <betterproto._PLACEHOLDER object>, int_domain: merlin_standard_lib.proto.schema_bp.IntDomain = <betterproto._PLACEHOLDER object>, float_domain: merlin_standard_lib.proto.schema_bp.FloatDomain = <betterproto._PLACEHOLDER object>, string_domain: merlin_standard_lib.proto.schema_bp.StringDomain = <betterproto._PLACEHOLDER object>, bool_domain: merlin_standard_lib.proto.schema_bp.BoolDomain = <betterproto._PLACEHOLDER object>, struct_domain: merlin_standard_lib.proto.schema_bp.StructDomain = <betterproto._PLACEHOLDER object>, natural_language_domain: merlin_standard_lib.proto.schema_bp.NaturalLanguageDomain = <betterproto._PLACEHOLDER object>, image_domain: merlin_standard_lib.proto.schema_bp.ImageDomain = <betterproto._PLACEHOLDER object>, mid_domain: merlin_standard_lib.proto.schema_bp.MIDDomain = <betterproto._PLACEHOLDER object>, url_domain: merlin_standard_lib.proto.schema_bp.URLDomain = <betterproto._PLACEHOLDER object>, time_domain: merlin_standard_lib.proto.schema_bp.TimeDomain = <betterproto._PLACEHOLDER object>, time_of_day_domain: merlin_standard_lib.proto.schema_bp.TimeOfDayDomain = <betterproto._PLACEHOLDER object>, distribution_constraints: merlin_standard_lib.proto.schema_bp.DistributionConstraints = <betterproto._PLACEHOLDER object>, annotation: merlin_standard_lib.proto.schema_bp.Annotation = <betterproto._PLACEHOLDER object>, skew_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>, drift_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>, in_environment: List[str] = <betterproto._PLACEHOLDER object>, not_in_environment: List[str] = <betterproto._PLACEHOLDER object>, lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>, unique_constraints: merlin_standard_lib.proto.schema_bp.UniqueConstraints = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Describes schema-level information about a specific feature. NextID: 33

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+deprecated: bool = <betterproto._PLACEHOLDER object>
+
+ +
+
+presence: merlin_standard_lib.proto.schema_bp.FeaturePresence = <betterproto._PLACEHOLDER object>
+
+ +
+
+group_presence: merlin_standard_lib.proto.schema_bp.FeaturePresenceWithinGroup = <betterproto._PLACEHOLDER object>
+
+ +
+
+shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>
+
+ +
+
+value_count: merlin_standard_lib.proto.schema_bp.ValueCount = <betterproto._PLACEHOLDER object>
+
+ +
+
+value_counts: merlin_standard_lib.proto.schema_bp.ValueCountList = <betterproto._PLACEHOLDER object>
+
+ +
+
+type: merlin_standard_lib.proto.schema_bp.FeatureType = <betterproto._PLACEHOLDER object>
+
+ +
+
+domain: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+int_domain: merlin_standard_lib.proto.schema_bp.IntDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+float_domain: merlin_standard_lib.proto.schema_bp.FloatDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+string_domain: merlin_standard_lib.proto.schema_bp.StringDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+bool_domain: merlin_standard_lib.proto.schema_bp.BoolDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+struct_domain: merlin_standard_lib.proto.schema_bp.StructDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+natural_language_domain: merlin_standard_lib.proto.schema_bp.NaturalLanguageDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+image_domain: merlin_standard_lib.proto.schema_bp.ImageDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+mid_domain: merlin_standard_lib.proto.schema_bp.MIDDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+url_domain: merlin_standard_lib.proto.schema_bp.URLDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+time_domain: merlin_standard_lib.proto.schema_bp.TimeDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+time_of_day_domain: merlin_standard_lib.proto.schema_bp.TimeOfDayDomain = <betterproto._PLACEHOLDER object>
+
+ +
+
+distribution_constraints: merlin_standard_lib.proto.schema_bp.DistributionConstraints = <betterproto._PLACEHOLDER object>
+
+ +
+
+annotation: merlin_standard_lib.proto.schema_bp.Annotation = <betterproto._PLACEHOLDER object>
+
+ +
+
+skew_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>
+
+ +
+
+drift_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>
+
+ +
+
+in_environment: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+
+not_in_environment: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+
+lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>
+
+ +
+
+unique_constraints: merlin_standard_lib.proto.schema_bp.UniqueConstraints = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.Annotation(tag: List[str] = <betterproto._PLACEHOLDER object>, comment: List[str] = <betterproto._PLACEHOLDER object>, extra_metadata: List[Any] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Additional information about the schema or about a feature.

+
+
+tag: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+
+comment: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+
+extra_metadata: List[Any] = <betterproto._PLACEHOLDER object>
+
+ +
+
+property metadata
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.NumericValueComparator(min_fraction_threshold: float = <betterproto._PLACEHOLDER object>, max_fraction_threshold: float = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Checks that the ratio of the current value to the previous value is not +below the min_fraction_threshold or above the max_fraction_threshold. That +is, previous value * min_fraction_threshold <= current value <= previous +value * max_fraction_threshold. To specify that the value cannot change, +set both min_fraction_threshold and max_fraction_threshold to 1.0.

+
+
+min_fraction_threshold: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+max_fraction_threshold: float = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.DatasetConstraints(num_examples_drift_comparator: merlin_standard_lib.proto.schema_bp.NumericValueComparator = <betterproto._PLACEHOLDER object>, num_examples_version_comparator: merlin_standard_lib.proto.schema_bp.NumericValueComparator = <betterproto._PLACEHOLDER object>, min_examples_count: int = <betterproto._PLACEHOLDER object>, max_examples_count: int = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Constraints on the entire dataset.

+
+
+num_examples_drift_comparator: merlin_standard_lib.proto.schema_bp.NumericValueComparator = <betterproto._PLACEHOLDER object>
+
+ +
+
+num_examples_version_comparator: merlin_standard_lib.proto.schema_bp.NumericValueComparator = <betterproto._PLACEHOLDER object>
+
+ +
+
+min_examples_count: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+max_examples_count: int = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FixedShape(dim: List[merlin_standard_lib.proto.schema_bp.FixedShapeDim] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Specifies a fixed shape for the feature’s values. The immediate implication +is that each feature has a fixed number of values. Moreover, these values +can be parsed in a multi-dimensional tensor using the specified axis sizes. +The FixedShape defines a lexicographical ordering of the data. For +instance, if there is a FixedShape { dim {size:3} dim {size:2} } then +tensor[0][0]=field[0] then tensor[0][1]=field[1] then tensor[1][0]=field[2] +then tensor[1][1]=field[3] then tensor[2][0]=field[4] then +tensor[2][1]=field[5] The FixedShape message is identical with the +TensorFlow TensorShape proto message.

+
+
+dim: List[merlin_standard_lib.proto.schema_bp.FixedShapeDim] = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FixedShapeDim(size: int = <betterproto._PLACEHOLDER object>, name: str = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

An axis in a multi-dimensional feature representation.

+
+
+size: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.ValueCount(min: int = <betterproto._PLACEHOLDER object>, max: int = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Limits on maximum and minimum number of values in a single example (when +the feature is present). Use this when the minimum value count can be +different than the maximum value count. Otherwise prefer FixedShape.

+
+
+min: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+max: int = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.WeightedFeature(name: str = <betterproto._PLACEHOLDER object>, feature: merlin_standard_lib.proto.schema_bp.Path = <betterproto._PLACEHOLDER object>, weight_feature: merlin_standard_lib.proto.schema_bp.Path = <betterproto._PLACEHOLDER object>, lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Represents a weighted feature that is encoded as a combination of raw base +features. The weight_feature should be a float feature with identical +shape as the feature. This is useful for representing weights associated +with categorical tokens (e.g. a TFIDF weight associated with each token). +TODO(b/142122960): Handle WeightedCategorical end to end in TFX +(validation, TFX Unit Testing, etc)

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+feature: merlin_standard_lib.proto.schema_bp.Path = <betterproto._PLACEHOLDER object>
+
+ +
+
+weight_feature: merlin_standard_lib.proto.schema_bp.Path = <betterproto._PLACEHOLDER object>
+
+ +
+
+lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.SparseFeature(name: str = <betterproto._PLACEHOLDER object>, deprecated: bool = <betterproto._PLACEHOLDER object>, lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>, presence: merlin_standard_lib.proto.schema_bp.FeaturePresence = <betterproto._PLACEHOLDER object>, dense_shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>, index_feature: List[merlin_standard_lib.proto.schema_bp.SparseFeatureIndexFeature] = <betterproto._PLACEHOLDER object>, is_sorted: bool = <betterproto._PLACEHOLDER object>, value_feature: merlin_standard_lib.proto.schema_bp.SparseFeatureValueFeature = <betterproto._PLACEHOLDER object>, type: merlin_standard_lib.proto.schema_bp.FeatureType = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A sparse feature represents a sparse tensor that is encoded with a +combination of raw features, namely index features and a value feature. +Each index feature defines a list of indices in a different dimension.

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+deprecated: bool = <betterproto._PLACEHOLDER object>
+
+ +
+
+lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>
+
+ +
+
+presence: merlin_standard_lib.proto.schema_bp.FeaturePresence = <betterproto._PLACEHOLDER object>
+
+ +
+
+dense_shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>
+
+ +
+
+index_feature: List[merlin_standard_lib.proto.schema_bp.SparseFeatureIndexFeature] = <betterproto._PLACEHOLDER object>
+
+ +
+
+is_sorted: bool = <betterproto._PLACEHOLDER object>
+
+ +
+
+value_feature: merlin_standard_lib.proto.schema_bp.SparseFeatureValueFeature = <betterproto._PLACEHOLDER object>
+
+ +
+
+type: merlin_standard_lib.proto.schema_bp.FeatureType = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.SparseFeatureIndexFeature(name: str = <betterproto._PLACEHOLDER object at 0x7f338f170520>)[source]
+

Bases: betterproto.Message

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.SparseFeatureValueFeature(name: str = <betterproto._PLACEHOLDER object at 0x7f338f170520>)[source]
+

Bases: betterproto.Message

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.DistributionConstraints(min_domain_mass: float = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Models constraints on the distribution of a feature’s values. +TODO(martinz): replace min_domain_mass with max_off_domain (but slowly).

+
+
+min_domain_mass: float = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FeatureCoverageConstraints(min_coverage: float = <betterproto._PLACEHOLDER object>, min_avg_token_length: float = <betterproto._PLACEHOLDER object>, excluded_string_tokens: List[str] = <betterproto._PLACEHOLDER object>, excluded_int_tokens: List[int] = <betterproto._PLACEHOLDER object>, oov_string_tokens: List[str] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Encodes vocabulary coverage constraints.

+
+
+min_coverage: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+min_avg_token_length: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+excluded_string_tokens: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+
+excluded_int_tokens: List[int] = <betterproto._PLACEHOLDER object>
+
+ +
+
+oov_string_tokens: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.SequenceValueConstraints(int_value: int = <betterproto._PLACEHOLDER object>, string_value: str = <betterproto._PLACEHOLDER object>, min_per_sequence: int = <betterproto._PLACEHOLDER object>, max_per_sequence: int = <betterproto._PLACEHOLDER object>, min_fraction_of_sequences: float = <betterproto._PLACEHOLDER object>, max_fraction_of_sequences: float = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Encodes constraints on specific values in sequences.

+
+
+int_value: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+string_value: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+min_per_sequence: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+max_per_sequence: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+min_fraction_of_sequences: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+max_fraction_of_sequences: float = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.SequenceLengthConstraints(excluded_int_value: List[int] = <betterproto._PLACEHOLDER object>, excluded_string_value: List[str] = <betterproto._PLACEHOLDER object>, min_sequence_length: int = <betterproto._PLACEHOLDER object>, max_sequence_length: int = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Encodes constraints on sequence lengths.

+
+
+excluded_int_value: List[int] = <betterproto._PLACEHOLDER object>
+
+ +
+
+excluded_string_value: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+
+min_sequence_length: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+max_sequence_length: int = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.IntDomain(name: str = <betterproto._PLACEHOLDER object>, min: int = <betterproto._PLACEHOLDER object>, max: int = <betterproto._PLACEHOLDER object>, is_categorical: bool = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Encodes information for domains of integer values. Note that FeatureType +could be either INT or BYTES.

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+min: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+max: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+is_categorical: bool = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FloatDomain(name: str = <betterproto._PLACEHOLDER object>, min: float = <betterproto._PLACEHOLDER object>, max: float = <betterproto._PLACEHOLDER object>, disallow_nan: bool = <betterproto._PLACEHOLDER object>, disallow_inf: bool = <betterproto._PLACEHOLDER object>, is_embedding: bool = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Encodes information for domains of float values. Note that FeatureType +could be either INT or BYTES.

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+min: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+max: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+disallow_nan: bool = <betterproto._PLACEHOLDER object>
+
+ +
+
+disallow_inf: bool = <betterproto._PLACEHOLDER object>
+
+ +
+
+is_embedding: bool = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.StructDomain(feature: List[merlin_standard_lib.proto.schema_bp.Feature] = <betterproto._PLACEHOLDER object>, sparse_feature: List[merlin_standard_lib.proto.schema_bp.SparseFeature] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Domain for a recursive struct. NOTE: If a feature with a StructDomain is +deprecated, then all the child features (features and sparse_features of +the StructDomain) are also considered to be deprecated. Similarly child +features can only be in environments of the parent feature.

+
+
+feature: List[merlin_standard_lib.proto.schema_bp.Feature] = <betterproto._PLACEHOLDER object>
+
+ +
+
+sparse_feature: List[merlin_standard_lib.proto.schema_bp.SparseFeature] = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.StringDomain(name: str = <betterproto._PLACEHOLDER object>, value: List[str] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Encodes information for domains of string values.

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+value: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.BoolDomain(name: str = <betterproto._PLACEHOLDER object>, true_value: str = <betterproto._PLACEHOLDER object>, false_value: str = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Encodes information about the domain of a boolean attribute that encodes +its TRUE/FALSE values as strings, or 0=false, 1=true. Note that FeatureType +could be either INT or BYTES.

+
+
+name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+true_value: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+false_value: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.NaturalLanguageDomain(vocabulary: str = <betterproto._PLACEHOLDER object>, coverage: merlin_standard_lib.proto.schema_bp.FeatureCoverageConstraints = <betterproto._PLACEHOLDER object>, token_constraints: List[merlin_standard_lib.proto.schema_bp.SequenceValueConstraints] = <betterproto._PLACEHOLDER object>, sequence_length_constraints: merlin_standard_lib.proto.schema_bp.SequenceLengthConstraints = <betterproto._PLACEHOLDER object>, location_constraint_regex: str = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Natural language text.

+
+
+vocabulary: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+coverage: merlin_standard_lib.proto.schema_bp.FeatureCoverageConstraints = <betterproto._PLACEHOLDER object>
+
+ +
+
+token_constraints: List[merlin_standard_lib.proto.schema_bp.SequenceValueConstraints] = <betterproto._PLACEHOLDER object>
+
+ +
+
+sequence_length_constraints: merlin_standard_lib.proto.schema_bp.SequenceLengthConstraints = <betterproto._PLACEHOLDER object>
+
+ +
+
+location_constraint_regex: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.ImageDomain(minimum_supported_image_fraction: float = <betterproto._PLACEHOLDER object>, max_image_byte_size: int = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Image data.

+
+
+minimum_supported_image_fraction: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+max_image_byte_size: int = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.MIDDomain[source]
+

Bases: betterproto.Message

+

Knowledge graph ID, see: https://www.wikidata.org/wiki/Property:P646

+
+ +
+
+class merlin_standard_lib.proto.schema_bp.URLDomain[source]
+

Bases: betterproto.Message

+

A URL, see: https://en.wikipedia.org/wiki/URL

+
+ +
+
+class merlin_standard_lib.proto.schema_bp.TimeDomain(string_format: str = <betterproto._PLACEHOLDER object>, integer_format: merlin_standard_lib.proto.schema_bp.TimeDomainIntegerTimeFormat = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Time or date representation.

+
+
+string_format: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+integer_format: merlin_standard_lib.proto.schema_bp.TimeDomainIntegerTimeFormat = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TimeOfDayDomain(string_format: str = <betterproto._PLACEHOLDER object>, integer_format: merlin_standard_lib.proto.schema_bp.TimeOfDayDomainIntegerTimeOfDayFormat = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Time of day, without a particular date.

+
+
+string_format: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+integer_format: merlin_standard_lib.proto.schema_bp.TimeOfDayDomainIntegerTimeOfDayFormat = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FeaturePresence(min_fraction: float = <betterproto._PLACEHOLDER object>, min_count: int = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Describes constraints on the presence of the feature in the data.

+
+
+min_fraction: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+min_count: int = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FeaturePresenceWithinGroup(required: bool = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Records constraints on the presence of a feature inside a “group” context +(e.g., .presence inside a group of features that define a sequence).

+
+
+required: bool = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.InfinityNorm(threshold: float = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Checks that the L-infinity norm is below a certain threshold between the +two discrete distributions. Since this is applied to a +FeatureNameStatistics, it only considers the top K.

+
+\[l_{\infty}(p,q) = max_{i} | p_{i} - q_{i} |\]
+
+
+threshold: float = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.JensenShannonDivergence(threshold: float = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Checks that the approximate Jensen-Shannon Divergence is below a certain +threshold between the two distributions.

+
+
+threshold: float = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.FeatureComparator(infinity_norm: 'InfinityNorm' = <betterproto._PLACEHOLDER object at 0x7f338f170520>, jensen_shannon_divergence: 'JensenShannonDivergence' = <betterproto._PLACEHOLDER object at 0x7f338f170520>)[source]
+

Bases: betterproto.Message

+
+
+infinity_norm: merlin_standard_lib.proto.schema_bp.InfinityNorm = <betterproto._PLACEHOLDER object>
+
+ +
+
+jensen_shannon_divergence: merlin_standard_lib.proto.schema_bp.JensenShannonDivergence = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.UniqueConstraints(min: int = <betterproto._PLACEHOLDER object>, max: int = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Checks that the number of unique values is greater than or equal to the +min, and less than or equal to the max.

+
+
+min: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+max: int = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentation(dense_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationDenseTensor = <betterproto._PLACEHOLDER object>, varlen_sparse_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationVarLenSparseTensor = <betterproto._PLACEHOLDER object>, sparse_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationSparseTensor = <betterproto._PLACEHOLDER object>, ragged_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensor = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A TensorRepresentation captures the intent for converting columns in a +dataset to TensorFlow Tensors (or more generally, tf.CompositeTensors). +Note that one tf.CompositeTensor may consist of data from multiple columns, +for example, a N-dimensional tf.SparseTensor may need N + 1 columns to +provide the sparse indices and values. Note that the “column name” that a +TensorRepresentation needs is a string, not a Path – it means that the +column name identifies a top-level Feature in the schema (i.e. you cannot +specify a Feature nested in a STRUCT Feature).

+
+
+dense_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationDenseTensor = <betterproto._PLACEHOLDER object>
+
+ +
+
+varlen_sparse_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationVarLenSparseTensor = <betterproto._PLACEHOLDER object>
+
+ +
+
+sparse_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationSparseTensor = <betterproto._PLACEHOLDER object>
+
+ +
+
+ragged_tensor: merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensor = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationDefaultValue(float_value: float = <betterproto._PLACEHOLDER object at 0x7f338f170520>, int_value: int = <betterproto._PLACEHOLDER object at 0x7f338f170520>, bytes_value: bytes = <betterproto._PLACEHOLDER object at 0x7f338f170520>, uint_value: int = <betterproto._PLACEHOLDER object at 0x7f338f170520>)[source]
+

Bases: betterproto.Message

+
+
+float_value: float = <betterproto._PLACEHOLDER object>
+
+ +
+
+int_value: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+bytes_value: bytes = <betterproto._PLACEHOLDER object>
+
+ +
+
+uint_value: int = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationDenseTensor(column_name: str = <betterproto._PLACEHOLDER object>, shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>, default_value: merlin_standard_lib.proto.schema_bp.TensorRepresentationDefaultValue = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A tf.Tensor

+
+
+column_name: str = <betterproto._PLACEHOLDER object>
+
+ +
+
+shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>
+
+ +
+
+default_value: merlin_standard_lib.proto.schema_bp.TensorRepresentationDefaultValue = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationVarLenSparseTensor(column_name: str = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A ragged tf.SparseTensor that models nested lists.

+
+
+column_name: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationSparseTensor(dense_shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>, index_column_names: List[str] = <betterproto._PLACEHOLDER object>, value_column_name: str = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A tf.SparseTensor whose indices and values come from separate data columns. +This will replace Schema.sparse_feature eventually. The index columns must +be of INT type, and all the columns must co-occur and have the same valency +at the same row.

+
+
+dense_shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>
+
+ +
+
+index_column_names: List[str] = <betterproto._PLACEHOLDER object>
+
+ +
+
+value_column_name: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensor(feature_path: merlin_standard_lib.proto.schema_bp.Path = <betterproto._PLACEHOLDER object>, partition: List[merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensorPartition] = <betterproto._PLACEHOLDER object>, row_partition_dtype: merlin_standard_lib.proto.schema_bp.TensorRepresentationRowPartitionDType = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A tf.RaggedTensor that models nested lists. Currently there is no way for +the user to specify the shape of the leaf value (the innermost value tensor +of the RaggedTensor). The leaf value will always be a 1-D tensor.

+
+
+feature_path: merlin_standard_lib.proto.schema_bp.Path = <betterproto._PLACEHOLDER object>
+
+ +
+
+partition: List[merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensorPartition] = <betterproto._PLACEHOLDER object>
+
+ +
+
+row_partition_dtype: merlin_standard_lib.proto.schema_bp.TensorRepresentationRowPartitionDType = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensorPartition(uniform_row_length: int = <betterproto._PLACEHOLDER object>, row_length: str = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

Further partition of the feature values at the leaf level.

+
+
+uniform_row_length: int = <betterproto._PLACEHOLDER object>
+
+ +
+
+row_length: str = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+class merlin_standard_lib.proto.schema_bp.TensorRepresentationGroup(tensor_representation: Dict[str, merlin_standard_lib.proto.schema_bp.TensorRepresentation] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: betterproto.Message

+

A TensorRepresentationGroup is a collection of TensorRepresentations with +names. These names may serve as identifiers when converting the dataset to +a collection of Tensors or tf.CompositeTensors. For example, given the +following group: { key: “dense_tensor” tensor_representation { +dense_tensor { column_name: “univalent_feature” shape { +dim { size: 1 } } default_value { +float_value: 0 } } } } { key: “varlen_sparse_tensor” +tensor_representation { varlen_sparse_tensor { column_name: +“multivalent_feature” } } } Then the schema is expected to have +feature “univalent_feature” and “multivalent_feature”, and when a batch of +data is converted to Tensors using this TensorRepresentationGroup, the +result may be the following dict: { “dense_tensor”: tf.Tensor(…), +“varlen_sparse_tensor”: tf.SparseTensor(…), }

+
+
+tensor_representation: Dict[str, merlin_standard_lib.proto.schema_bp.TensorRepresentation] = <betterproto._PLACEHOLDER object>
+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/merlin_standard_lib.schema.html b/review/pr-767/api/merlin_standard_lib.schema.html new file mode 100644 index 0000000000..4c237c5bd1 --- /dev/null +++ b/review/pr-767/api/merlin_standard_lib.schema.html @@ -0,0 +1,342 @@ + + + + + + merlin_standard_lib.schema package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

merlin_standard_lib.schema package

+
+

Submodules

+
+
+

merlin_standard_lib.schema.schema module

+
+
+class merlin_standard_lib.schema.schema.ColumnSchema(name: str = <betterproto._PLACEHOLDER object>, deprecated: bool = <betterproto._PLACEHOLDER object>, presence: merlin_standard_lib.proto.schema_bp.FeaturePresence = <betterproto._PLACEHOLDER object>, group_presence: merlin_standard_lib.proto.schema_bp.FeaturePresenceWithinGroup = <betterproto._PLACEHOLDER object>, shape: merlin_standard_lib.proto.schema_bp.FixedShape = <betterproto._PLACEHOLDER object>, value_count: merlin_standard_lib.proto.schema_bp.ValueCount = <betterproto._PLACEHOLDER object>, value_counts: merlin_standard_lib.proto.schema_bp.ValueCountList = <betterproto._PLACEHOLDER object>, type: merlin_standard_lib.proto.schema_bp.FeatureType = <betterproto._PLACEHOLDER object>, domain: str = <betterproto._PLACEHOLDER object>, int_domain: merlin_standard_lib.proto.schema_bp.IntDomain = <betterproto._PLACEHOLDER object>, float_domain: merlin_standard_lib.proto.schema_bp.FloatDomain = <betterproto._PLACEHOLDER object>, string_domain: merlin_standard_lib.proto.schema_bp.StringDomain = <betterproto._PLACEHOLDER object>, bool_domain: merlin_standard_lib.proto.schema_bp.BoolDomain = <betterproto._PLACEHOLDER object>, struct_domain: merlin_standard_lib.proto.schema_bp.StructDomain = <betterproto._PLACEHOLDER object>, natural_language_domain: merlin_standard_lib.proto.schema_bp.NaturalLanguageDomain = <betterproto._PLACEHOLDER object>, image_domain: merlin_standard_lib.proto.schema_bp.ImageDomain = <betterproto._PLACEHOLDER object>, mid_domain: merlin_standard_lib.proto.schema_bp.MIDDomain = <betterproto._PLACEHOLDER object>, url_domain: merlin_standard_lib.proto.schema_bp.URLDomain = <betterproto._PLACEHOLDER object>, time_domain: merlin_standard_lib.proto.schema_bp.TimeDomain = <betterproto._PLACEHOLDER object>, time_of_day_domain: merlin_standard_lib.proto.schema_bp.TimeOfDayDomain = <betterproto._PLACEHOLDER object>, distribution_constraints: merlin_standard_lib.proto.schema_bp.DistributionConstraints = <betterproto._PLACEHOLDER object>, annotation: merlin_standard_lib.proto.schema_bp.Annotation = <betterproto._PLACEHOLDER object>, skew_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>, drift_comparator: merlin_standard_lib.proto.schema_bp.FeatureComparator = <betterproto._PLACEHOLDER object>, in_environment: List[str] = <betterproto._PLACEHOLDER object>, not_in_environment: List[str] = <betterproto._PLACEHOLDER object>, lifecycle_stage: merlin_standard_lib.proto.schema_bp.LifecycleStage = <betterproto._PLACEHOLDER object>, unique_constraints: merlin_standard_lib.proto.schema_bp.UniqueConstraints = <betterproto._PLACEHOLDER object>)[source]
+

Bases: merlin_standard_lib.proto.schema_bp.Feature

+
+
+classmethod create_categorical(name: str, num_items: int, shape: Optional[Union[List[int], Tuple[int, ]]] = None, value_count: Optional[Union[merlin_standard_lib.proto.schema_bp.ValueCount, merlin_standard_lib.proto.schema_bp.ValueCountList]] = None, min_index: int = 0, tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, **kwargs)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+classmethod create_continuous(name: str, is_float: bool = True, min_value: Optional[Union[int, float]] = None, max_value: Optional[Union[int, float]] = None, disallow_nan: bool = False, disallow_inf: bool = False, is_embedding: bool = False, shape: Optional[Union[List[int], Tuple[int, ]]] = None, value_count: Optional[Union[merlin_standard_lib.proto.schema_bp.ValueCount, merlin_standard_lib.proto.schema_bp.ValueCountList]] = None, tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, **kwargs)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+copy(**kwargs)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+with_name(name: str)[source]
+
+ +
+
+with_tags(tags: Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]])merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+with_tags_based_on_properties(using_value_count=True, using_domain=True)merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+with_properties(properties: Dict[str, Union[str, int, float]])merlin_standard_lib.schema.schema.ColumnSchema[source]
+
+ +
+
+to_proto_text()str[source]
+
+ +
+
+property tags
+
+ +
+
+property properties
+
+ +
+ +
+
+class merlin_standard_lib.schema.schema.Schema(feature: Sequence[merlin_standard_lib.proto.schema_bp.Feature] = <betterproto._PLACEHOLDER object>, sparse_feature: List[merlin_standard_lib.proto.schema_bp.SparseFeature] = <betterproto._PLACEHOLDER object>, weighted_feature: List[merlin_standard_lib.proto.schema_bp.WeightedFeature] = <betterproto._PLACEHOLDER object>, string_domain: List[merlin_standard_lib.proto.schema_bp.StringDomain] = <betterproto._PLACEHOLDER object>, float_domain: List[merlin_standard_lib.proto.schema_bp.FloatDomain] = <betterproto._PLACEHOLDER object>, int_domain: List[merlin_standard_lib.proto.schema_bp.IntDomain] = <betterproto._PLACEHOLDER object>, default_environment: List[str] = <betterproto._PLACEHOLDER object>, annotation: merlin_standard_lib.proto.schema_bp.Annotation = <betterproto._PLACEHOLDER object>, dataset_constraints: merlin_standard_lib.proto.schema_bp.DatasetConstraints = <betterproto._PLACEHOLDER object>, tensor_representation_group: Dict[str, merlin_standard_lib.proto.schema_bp.TensorRepresentationGroup] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: merlin_standard_lib.proto.schema_bp._Schema

+

A collection of column schemas for a dataset.

+
+
+feature: List[merlin_standard_lib.schema.schema.ColumnSchema] = Field(name=None,type=None,default=<betterproto._PLACEHOLDER object>,default_factory=<dataclasses._MISSING_TYPE object>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({'betterproto': FieldMetadata(number=1, proto_type='message', map_types=None, group=None, wraps=None)}),_field_type=None)
+
+ +
+
+classmethod create(column_schemas: Optional[Union[List[Union[merlin_standard_lib.schema.schema.ColumnSchema, str]], Dict[str, Union[merlin_standard_lib.schema.schema.ColumnSchema, str]]]] = None, **kwargs)[source]
+
+ +
+
+with_tags_based_on_properties(using_value_count=True, using_domain=True)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+apply(selector)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+apply_inverse(selector)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+filter_columns_from_dict(input_dict)[source]
+
+ +
+
+select_by_type(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_type(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+select_by_tag(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_tag(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+select_by_name(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_name(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+map_column_schemas(map_fn: Callable[[merlin_standard_lib.schema.schema.ColumnSchema], merlin_standard_lib.schema.schema.ColumnSchema])merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+filter_column_schemas(filter_fn: Callable[[merlin_standard_lib.schema.schema.ColumnSchema], bool], negate=False)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+property column_names
+
+ +
+
+property column_schemas
+
+ +
+
+property item_id_column_name
+
+ +
+
+from_json(value: Union[str, bytes])merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+to_proto_text()str[source]
+
+ +
+
+from_proto_text(path_or_proto_text: str)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+copy(**kwargs)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+add(other, allow_overlap=True)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+ +
+
+merlin_standard_lib.schema.schema.categorical_cardinalities(schema)Dict[str, int][source]
+
+ +
+
+

merlin_standard_lib.schema.tag module

+
+
+merlin_standard_lib.schema.tag.Tag
+

alias of merlin.schema.tags.Tags

+
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/merlin_standard_lib.utils.html b/review/pr-767/api/merlin_standard_lib.utils.html new file mode 100644 index 0000000000..4cf7df22c1 --- /dev/null +++ b/review/pr-767/api/merlin_standard_lib.utils.html @@ -0,0 +1,174 @@ + + + + + + merlin_standard_lib.utils package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

merlin_standard_lib.utils package

+
+

Submodules

+
+
+

merlin_standard_lib.utils.doc_utils module

+
+
+

merlin_standard_lib.utils.embedding_utils module

+
+
+merlin_standard_lib.utils.embedding_utils.get_embedding_sizes_from_schema(schema: merlin_standard_lib.schema.schema.Schema, multiplier: float = 2.0)[source]
+
+ +
+
+merlin_standard_lib.utils.embedding_utils.get_embedding_size_from_cardinality(cardinality: int, multiplier: float = 2.0)[source]
+
+ +
+
+

merlin_standard_lib.utils.misc_utils module

+
+
+

merlin_standard_lib.utils.proto_utils module

+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/modules.html b/review/pr-767/api/modules.html new file mode 100644 index 0000000000..f87413097e --- /dev/null +++ b/review/pr-767/api/modules.html @@ -0,0 +1,207 @@ + + + + + + API Documentation — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.config.html b/review/pr-767/api/transformers4rec.config.html new file mode 100644 index 0000000000..8bae8a83d8 --- /dev/null +++ b/review/pr-767/api/transformers4rec.config.html @@ -0,0 +1,613 @@ + + + + + + transformers4rec.config package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.config package

+
+

Submodules

+
+
+

transformers4rec.config.schema module

+
+
+class transformers4rec.config.schema.SchemaMixin[source]
+

Bases: object

+
+
+REQUIRES_SCHEMA = False
+
+ +
+
+set_schema(schema=None)[source]
+
+ +
+
+property schema
+
+ +
+
+check_schema(schema=None)[source]
+
+ +
+
+get_item_ids_from_inputs(inputs)[source]
+
+ +
+
+get_padding_mask_from_item_id(inputs, pad_token=0)[source]
+
+ +
+ +
+
+transformers4rec.config.schema.requires_schema(module)[source]
+
+ +
+
+

transformers4rec.config.trainer module

+
+
+class transformers4rec.config.trainer.T4RecTrainingArguments(output_dir: str, overwrite_output_dir: bool = False, do_train: bool = False, do_eval: bool = False, do_predict: bool = False, evaluation_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'no', prediction_loss_only: bool = False, per_device_train_batch_size: int = 8, per_device_eval_batch_size: int = 8, per_gpu_train_batch_size: Optional[int] = None, per_gpu_eval_batch_size: Optional[int] = None, gradient_accumulation_steps: int = 1, eval_accumulation_steps: Optional[int] = None, eval_delay: Optional[float] = 0, learning_rate: float = 5e-05, weight_decay: float = 0.0, adam_beta1: float = 0.9, adam_beta2: float = 0.999, adam_epsilon: float = 1e-08, max_grad_norm: float = 1.0, num_train_epochs: float = 3.0, max_steps: int = - 1, lr_scheduler_type: Union[transformers.trainer_utils.SchedulerType, str] = 'linear', warmup_ratio: float = 0.0, warmup_steps: int = 0, log_level: Optional[str] = 'passive', log_level_replica: Optional[str] = 'warning', log_on_each_node: bool = True, logging_dir: Optional[str] = None, logging_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'steps', logging_first_step: bool = False, logging_steps: float = 500, logging_nan_inf_filter: bool = True, save_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'steps', save_steps: float = 500, save_total_limit: Optional[int] = None, save_safetensors: Optional[bool] = False, save_on_each_node: bool = False, no_cuda: bool = False, use_mps_device: bool = False, seed: int = 42, data_seed: Optional[int] = None, jit_mode_eval: bool = False, use_ipex: bool = False, bf16: bool = False, fp16: bool = False, fp16_opt_level: str = 'O1', half_precision_backend: str = 'auto', bf16_full_eval: bool = False, fp16_full_eval: bool = False, tf32: Optional[bool] = None, local_rank: int = - 1, ddp_backend: Optional[str] = None, tpu_num_cores: Optional[int] = None, tpu_metrics_debug: bool = False, debug: str = '', dataloader_drop_last: bool = False, eval_steps: Optional[float] = None, dataloader_num_workers: int = 0, past_index: int = - 1, run_name: Optional[str] = None, disable_tqdm: Optional[bool] = None, remove_unused_columns: Optional[bool] = True, label_names: Optional[List[str]] = None, load_best_model_at_end: Optional[bool] = False, metric_for_best_model: Optional[str] = None, greater_is_better: Optional[bool] = None, ignore_data_skip: bool = False, sharded_ddp: str = '', fsdp: str = '', fsdp_min_num_params: int = 0, fsdp_config: Optional[str] = None, fsdp_transformer_layer_cls_to_wrap: Optional[str] = None, deepspeed: Optional[str] = None, label_smoothing_factor: float = 0.0, optim: Union[transformers.training_args.OptimizerNames, str] = 'adamw_hf', optim_args: Optional[str] = None, adafactor: bool = False, group_by_length: bool = False, length_column_name: Optional[str] = 'length', report_to: Optional[List[str]] = None, ddp_find_unused_parameters: Optional[bool] = None, ddp_bucket_cap_mb: Optional[int] = None, dataloader_pin_memory: bool = True, skip_memory_metrics: bool = True, use_legacy_prediction_loop: bool = False, push_to_hub: bool = False, resume_from_checkpoint: Optional[str] = None, hub_model_id: Optional[str] = None, hub_strategy: Union[transformers.trainer_utils.HubStrategy, str] = 'every_save', hub_token: Optional[str] = None, hub_private_repo: bool = False, gradient_checkpointing: bool = False, include_inputs_for_metrics: bool = False, fp16_backend: str = 'auto', push_to_hub_model_id: Optional[str] = None, push_to_hub_organization: Optional[str] = None, push_to_hub_token: Optional[str] = None, mp_parameters: str = '', auto_find_batch_size: bool = False, full_determinism: bool = False, torchdynamo: Optional[str] = None, ray_scope: Optional[str] = 'last', ddp_timeout: Optional[int] = 1800, torch_compile: bool = False, torch_compile_backend: Optional[str] = None, torch_compile_mode: Optional[str] = None, xpu_backend: Optional[str] = None, max_sequence_length: Optional[int] = None, shuffle_buffer_size: int = 0, data_loader_engine: str = 'merlin', eval_on_test_set: bool = False, eval_steps_on_train_set: int = 20, predict_top_k: int = 100, learning_rate_num_cosine_cycles_by_epoch: float = 1.25, log_predictions: bool = False, compute_metrics_each_n_steps: int = 1, experiments_group: str = 'default')[source]
+

Bases: transformers.training_args.TrainingArguments

+

Class that inherits HF TrainingArguments and add on top of it arguments needed for +session-based and sequential-based recommendation

+
+
Parameters
+
    +
  • shuffle_buffer_size (int) –

  • +
  • validate_every (Optional[int], int) – Run validation set every this epoch. +-1 means no validation is used +by default -1

  • +
  • eval_on_test_set (bool) –

  • +
  • eval_steps_on_train_set (int) –

  • +
  • predict_top_k (Option[int], int) – Truncate recommendation list to the highest top-K predicted items, +(do not affect evaluation metrics computation), +This parameter is specific to NextItemPredictionTask and only affects +model.predict() and model.evaluate(), which both call Trainer.evaluation_loop. +By default 100.

  • +
  • log_predictions (Optional[bool], bool) – log predictions, labels and metadata features each –compute_metrics_each_n_steps +(for test set). +by default False

  • +
  • log_attention_weights (Optional[bool], bool) – Logs the inputs and attention weights +each –eval_steps (only test set)” +by default False

  • +
  • learning_rate_num_cosine_cycles_by_epoch (Optional[int], int) – Number of cycles for by epoch when –lr_scheduler_type = cosine_with_warmup. +The number of waves in the cosine schedule +(e.g. 0.5 is to just decrease from the max value to 0, following a half-cosine). +by default 1.25

  • +
  • experiments_group (Optional[str], str) – Name of the Experiments Group, for organizing job runs logged on W&B +by default “default”

  • +
+
+
+
+
+max_sequence_length: Optional[int] = None
+
+ +
+
+shuffle_buffer_size: int = 0
+
+ +
+
+data_loader_engine: str = 'merlin'
+
+ +
+
+eval_on_test_set: bool = False
+
+ +
+
+eval_steps_on_train_set: int = 20
+
+ +
+
+predict_top_k: int = 100
+
+ +
+
+learning_rate_num_cosine_cycles_by_epoch: float = 1.25
+
+ +
+
+log_predictions: bool = False
+
+ +
+
+compute_metrics_each_n_steps: int = 1
+
+ +
+
+experiments_group: str = 'default'
+
+ +
+
+property place_model_on_device
+

Override the method to allow running training on cpu

+
+ +
+ +
+
+class transformers4rec.config.trainer.T4RecTrainingArgumentsTF(output_dir: str, overwrite_output_dir: bool = False, do_train: bool = False, do_eval: bool = False, do_predict: bool = False, evaluation_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'no', prediction_loss_only: bool = False, per_device_train_batch_size: int = 8, per_device_eval_batch_size: int = 8, per_gpu_train_batch_size: Optional[int] = None, per_gpu_eval_batch_size: Optional[int] = None, gradient_accumulation_steps: int = 1, eval_accumulation_steps: Optional[int] = None, eval_delay: Optional[float] = 0, learning_rate: float = 5e-05, weight_decay: float = 0.0, adam_beta1: float = 0.9, adam_beta2: float = 0.999, adam_epsilon: float = 1e-08, max_grad_norm: float = 1.0, num_train_epochs: float = 3.0, max_steps: int = - 1, lr_scheduler_type: Union[transformers.trainer_utils.SchedulerType, str] = 'linear', warmup_ratio: float = 0.0, warmup_steps: int = 0, log_level: Optional[str] = 'passive', log_level_replica: Optional[str] = 'warning', log_on_each_node: bool = True, logging_dir: Optional[str] = None, logging_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'steps', logging_first_step: bool = False, logging_steps: float = 500, logging_nan_inf_filter: bool = True, save_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'steps', save_steps: float = 500, save_total_limit: Optional[int] = None, save_safetensors: Optional[bool] = False, save_on_each_node: bool = False, no_cuda: bool = False, use_mps_device: bool = False, seed: int = 42, data_seed: Optional[int] = None, jit_mode_eval: bool = False, use_ipex: bool = False, bf16: bool = False, fp16: bool = False, fp16_opt_level: str = 'O1', half_precision_backend: str = 'auto', bf16_full_eval: bool = False, fp16_full_eval: bool = False, tf32: Optional[bool] = None, local_rank: int = - 1, ddp_backend: Optional[str] = None, tpu_num_cores: Optional[int] = None, tpu_metrics_debug: bool = False, debug: str = '', dataloader_drop_last: bool = False, eval_steps: Optional[float] = None, dataloader_num_workers: int = 0, past_index: int = - 1, run_name: Optional[str] = None, disable_tqdm: Optional[bool] = None, remove_unused_columns: Optional[bool] = True, label_names: Optional[List[str]] = None, load_best_model_at_end: Optional[bool] = False, metric_for_best_model: Optional[str] = None, greater_is_better: Optional[bool] = None, ignore_data_skip: bool = False, sharded_ddp: str = '', fsdp: str = '', fsdp_min_num_params: int = 0, fsdp_config: Optional[str] = None, fsdp_transformer_layer_cls_to_wrap: Optional[str] = None, deepspeed: Optional[str] = None, label_smoothing_factor: float = 0.0, optim: Union[transformers.training_args.OptimizerNames, str] = 'adamw_hf', optim_args: Optional[str] = None, adafactor: bool = False, group_by_length: bool = False, length_column_name: Optional[str] = 'length', report_to: Optional[List[str]] = None, ddp_find_unused_parameters: Optional[bool] = None, ddp_bucket_cap_mb: Optional[int] = None, dataloader_pin_memory: bool = True, skip_memory_metrics: bool = True, use_legacy_prediction_loop: bool = False, push_to_hub: bool = False, resume_from_checkpoint: Optional[str] = None, hub_model_id: Optional[str] = None, hub_strategy: Union[transformers.trainer_utils.HubStrategy, str] = 'every_save', hub_token: Optional[str] = None, hub_private_repo: bool = False, gradient_checkpointing: bool = False, include_inputs_for_metrics: bool = False, fp16_backend: str = 'auto', push_to_hub_model_id: Optional[str] = None, push_to_hub_organization: Optional[str] = None, push_to_hub_token: Optional[str] = None, mp_parameters: str = '', auto_find_batch_size: bool = False, full_determinism: bool = False, torchdynamo: Optional[str] = None, ray_scope: Optional[str] = 'last', ddp_timeout: Optional[int] = 1800, torch_compile: bool = False, torch_compile_backend: Optional[str] = None, torch_compile_mode: Optional[str] = None, xpu_backend: Optional[str] = None, max_sequence_length: Optional[int] = None, shuffle_buffer_size: int = 0, data_loader_engine: str = 'merlin', eval_on_test_set: bool = False, eval_steps_on_train_set: int = 20, predict_top_k: int = 100, learning_rate_num_cosine_cycles_by_epoch: float = 1.25, log_predictions: bool = False, compute_metrics_each_n_steps: int = 1, experiments_group: str = 'default')[source]
+

Bases: transformers4rec.config.trainer.T4RecTrainingArguments, transformers.training_args_tf.TFTrainingArguments

+

Prepare Training arguments for TFTrainer, +Inherit arguments from T4RecTrainingArguments and TFTrainingArguments

+
+
+output_dir: str
+
+ +
+ +
+
+

transformers4rec.config.transformer module

+
+
+class transformers4rec.config.transformer.T4RecConfig[source]
+

Bases: object

+

A class responsible for setting the configuration of the transformers class +from Hugging Face and returning the corresponding T4Rec model.

+
+
+to_huggingface_torch_model()[source]
+

Instantiate a Hugging Face transformer model based on +the configuration parameters of the class.

+
+
Returns
+

The Hugging Face transformer model.

+
+
Return type
+

transformers.PreTrainedModel

+
+
+
+ +
+
+to_torch_model(input_features, *prediction_task, task_blocks=None, task_weights=None, loss_reduction='mean', **kwargs)[source]
+

Links the Hugging Face transformer model to the given input block and prediction tasks, +and returns a T4Rec model.

+
+
Parameters
+
    +
  • input_features (torch4rec.TabularSequenceFeatures) – The sequential block that represents the input features and +defines the masking strategy for training and evaluation.

  • +
  • prediction_task (torch4rec.PredictionTask) – One or multiple prediction tasks.

  • +
  • task_blocks (list, optional) – List of task-specific blocks that we apply on top of the HF transformer’s output.

  • +
  • task_weights (list, optional) – List of the weights to use for combining the tasks losses.

  • +
  • loss_reduction (str, optional) –

    +
    The reduction to apply to the prediction losses, possible values are:

    ’none’: no reduction will be applied, +‘mean’: the weighted mean of the output is taken, +‘sum’: the output will be summed.

    +
    +
    +

    By default: ‘mean’.

    +

  • +
+
+
Returns
+

The T4Rec torch model.

+
+
Return type
+

torch4rec.Model

+
+
Raises
+

ValueError – If input block or prediction task is of the wrong type.

+
+
+
+ +
+
+property transformers_config_cls
+
+ +
+
+classmethod build(*args, **kwargs)[source]
+
+ +
+ +
+
+class transformers4rec.config.transformer.ReformerConfig(attention_head_size=64, attn_layers=['local', 'lsh', 'local', 'lsh', 'local', 'lsh'], axial_norm_std=1.0, axial_pos_embds=True, axial_pos_shape=[64, 64], axial_pos_embds_dim=[64, 192], chunk_size_lm_head=0, eos_token_id=2, feed_forward_size=512, hash_seed=None, hidden_act='relu', hidden_dropout_prob=0.05, hidden_size=256, initializer_range=0.02, is_decoder=False, layer_norm_eps=1e-12, local_num_chunks_before=1, local_num_chunks_after=0, local_attention_probs_dropout_prob=0.05, local_attn_chunk_length=64, lsh_attn_chunk_length=64, lsh_attention_probs_dropout_prob=0.0, lsh_num_chunks_before=1, lsh_num_chunks_after=0, max_position_embeddings=4096, num_attention_heads=12, num_buckets=None, num_hashes=1, pad_token_id=0, vocab_size=320, tie_word_embeddings=False, use_cache=True, classifier_dropout=None, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.reformer.configuration_reformer.ReformerConfig

+

Subclass of T4RecConfig and transformers.ReformerConfig from Hugging Face. +It handles configuration for Reformer layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, axial_pos_shape_first_dim=4, **kwargs)[source]
+

Creates an instance of ReformerConfig with the given parameters.

+
+
Parameters
+
    +
  • {transformer_cfg_parameters}

  • +
  • axial_pos_shape_first_dim (int, optional) – The first dimension of the axial position encodings. +During training, the product of the position dims has to be equal to the sequence length.

  • +
+
+
Returns
+

An instance of ReformerConfig.

+
+
Return type
+

ReformerConfig

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.GPT2Config(vocab_size=50257, n_positions=1024, n_embd=768, n_layer=12, n_head=12, n_inner=None, activation_function='gelu_new', resid_pdrop=0.1, embd_pdrop=0.1, attn_pdrop=0.1, layer_norm_epsilon=1e-05, initializer_range=0.02, summary_type='cls_index', summary_use_proj=True, summary_activation=None, summary_proj_to_labels=True, summary_first_dropout=0.1, scale_attn_weights=True, use_cache=True, bos_token_id=50256, eos_token_id=50256, scale_attn_by_inverse_layer_idx=False, reorder_and_upcast_attn=False, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.gpt2.configuration_gpt2.GPT2Config

+

Subclass of T4RecConfig and transformers.GPT2Config from Hugging Face. +It handles configuration for GPT2 layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of GPT2Config with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of GPT2Config.

+
+
Return type
+

GPT2Config

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.LongformerConfig(attention_window: Union[List[int], int] = 512, sep_token_id: int = 2, pad_token_id: int = 1, bos_token_id: int = 0, eos_token_id: int = 2, vocab_size: int = 30522, hidden_size: int = 768, num_hidden_layers: int = 12, num_attention_heads: int = 12, intermediate_size: int = 3072, hidden_act: str = 'gelu', hidden_dropout_prob: float = 0.1, attention_probs_dropout_prob: float = 0.1, max_position_embeddings: int = 512, type_vocab_size: int = 2, initializer_range: float = 0.02, layer_norm_eps: float = 1e-12, onnx_export: bool = False, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.longformer.configuration_longformer.LongformerConfig

+

Subclass of T4RecConfig and transformers.LongformerConfig from Hugging Face. +It handles configuration for LongformerConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of LongformerConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of LongformerConfig.

+
+
Return type
+

LongformerConfig

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.ElectraConfig(vocab_size=30522, embedding_size=128, hidden_size=256, num_hidden_layers=12, num_attention_heads=4, intermediate_size=1024, hidden_act='gelu', hidden_dropout_prob=0.1, attention_probs_dropout_prob=0.1, max_position_embeddings=512, type_vocab_size=2, initializer_range=0.02, layer_norm_eps=1e-12, summary_type='first', summary_use_proj=True, summary_activation='gelu', summary_last_dropout=0.1, pad_token_id=0, position_embedding_type='absolute', use_cache=True, classifier_dropout=None, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.electra.configuration_electra.ElectraConfig

+

Subclass of T4RecConfig and transformers.ElectraConfig from Hugging Face. +It handles configuration for ElectraConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of ElectraConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of ElectraConfig.

+
+
Return type
+

ElectraConfig

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.AlbertConfig(vocab_size=30000, embedding_size=128, hidden_size=4096, num_hidden_layers=12, num_hidden_groups=1, num_attention_heads=64, intermediate_size=16384, inner_group_num=1, hidden_act='gelu_new', hidden_dropout_prob=0, attention_probs_dropout_prob=0, max_position_embeddings=512, type_vocab_size=2, initializer_range=0.02, layer_norm_eps=1e-12, classifier_dropout_prob=0.1, position_embedding_type='absolute', pad_token_id=0, bos_token_id=2, eos_token_id=3, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.albert.configuration_albert.AlbertConfig

+

Subclass of T4RecConfig and transformers.AlbertConfig from Hugging Face. +It handles configuration for AlbertConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of AlbertConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of AlbertConfig.

+
+
Return type
+

AlbertConfig

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.XLNetConfig(vocab_size=32000, d_model=1024, n_layer=24, n_head=16, d_inner=4096, ff_activation='gelu', untie_r=True, attn_type='bi', initializer_range=0.02, layer_norm_eps=1e-12, dropout=0.1, mem_len=512, reuse_len=None, use_mems_eval=True, use_mems_train=False, bi_data=False, clamp_len=- 1, same_length=False, summary_type='last', summary_use_proj=True, summary_activation='tanh', summary_last_dropout=0.1, start_n_top=5, end_n_top=5, pad_token_id=5, bos_token_id=1, eos_token_id=2, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.xlnet.configuration_xlnet.XLNetConfig

+

Subclass of T4RecConfig and transformers.XLNetConfig from Hugging Face. +It handles configuration for XLNetConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length=None, attn_type='bi', hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, mem_len=1, **kwargs)[source]
+

Creates an instance of XLNetConfig with the given parameters.

+
+
Parameters
+
    +
  • {transformer_cfg_parameters}

  • +
  • mem_len (int,) – The number of tokens to be cached. Pre-computed key/value pairs +from a previous forward pass are stored and won’t be re-computed. +This parameter is especially useful for long sequence modeling where +different batches may truncate the entire sequence. +Tasks like user-aware recommendation could benefit from this feature. +By default, this parameter is set to 1, which means no caching is used.

  • +
+
+
Returns
+

An instance of XLNetConfig.

+
+
Return type
+

XLNetConfig

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.BertConfig(vocab_size=30522, hidden_size=768, num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072, hidden_act='gelu', hidden_dropout_prob=0.1, attention_probs_dropout_prob=0.1, max_position_embeddings=512, type_vocab_size=2, initializer_range=0.02, layer_norm_eps=1e-12, pad_token_id=0, position_embedding_type='absolute', use_cache=True, classifier_dropout=None, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.bert.configuration_bert.BertConfig

+

Subclass of T4RecConfig and transformers.BertConfig from Hugging Face. +It handles configuration for BertConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of BertConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of BertConfig.

+
+
Return type
+

BertConfig

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.RobertaConfig(vocab_size=50265, hidden_size=768, num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072, hidden_act='gelu', hidden_dropout_prob=0.1, attention_probs_dropout_prob=0.1, max_position_embeddings=512, type_vocab_size=2, initializer_range=0.02, layer_norm_eps=1e-12, pad_token_id=1, bos_token_id=0, eos_token_id=2, position_embedding_type='absolute', use_cache=True, classifier_dropout=None, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.roberta.configuration_roberta.RobertaConfig

+

Subclass of T4RecConfig and transformers.RobertaConfig from Hugging Face. +It handles configuration for RobertaConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of RobertaConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of RobertaConfig.

+
+
Return type
+

RobertaConfig

+
+
+
+ +
+ +
+
+class transformers4rec.config.transformer.TransfoXLConfig(vocab_size=267735, cutoffs=[20000, 40000, 200000], d_model=1024, d_embed=1024, n_head=16, d_head=64, d_inner=4096, div_val=4, pre_lnorm=False, n_layer=18, mem_len=1600, clamp_len=1000, same_length=True, proj_share_all_but_first=True, attn_type=0, sample_softmax=- 1, adaptive=True, dropout=0.1, dropatt=0.0, untie_r=True, init='normal', init_range=0.01, proj_init_std=0.01, init_std=0.02, layer_norm_epsilon=1e-05, eos_token_id=0, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.transfo_xl.configuration_transfo_xl.TransfoXLConfig

+

Subclass of T4RecConfig and transformers. TransfoXLConfig from Hugging Face. +It handles configuration for TransfoXLConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of TransfoXLConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of TransfoXLConfig.

+
+
Return type
+

TransfoXLConfig

+
+
+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.html b/review/pr-767/api/transformers4rec.html new file mode 100644 index 0000000000..51e1c19198 --- /dev/null +++ b/review/pr-767/api/transformers4rec.html @@ -0,0 +1,231 @@ + + + + + + transformers4rec package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec package

+
+

Subpackages

+
+ +
+
+
+

Submodules

+
+
+

transformers4rec.types module

+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.torch.block.html b/review/pr-767/api/transformers4rec.torch.block.html new file mode 100644 index 0000000000..99d210edc3 --- /dev/null +++ b/review/pr-767/api/transformers4rec.torch.block.html @@ -0,0 +1,584 @@ + + + + + + transformers4rec.torch.block package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.torch.block package

+
+

Submodules

+
+
+

transformers4rec.torch.block.base module

+
+
+class transformers4rec.torch.block.base.BlockBase(*args, **kwargs)[source]
+

Bases: transformers4rec.torch.utils.torch_utils.OutputSizeMixin, torch.nn.modules.module.Module

+

A subclass of PyTorch’s torch.nn.Module, providing additional functionality +for dealing with automatic setting of input/output dimensions of neural networks layers. +Specifically, It implements the ‘OutputSizeMixin’ for managing output sizes.

+
+
+to_model(prediction_task_or_head, inputs=None, **kwargs)[source]
+

Converts the BlockBase instance into a T4Rec model by attaching it to +attaching a ‘Head’ or ‘PredictionTask’.

+
+
Parameters
+
    +
  • prediction_task_or_head (Union[PredictionTask, Head]) – A PredictionTask or Head instance to attach to this block.

  • +
  • inputs (InputBlock, optional) – The input block representing input features. +By default None

  • +
+
+
Raises
+

ValueError – If prediction_task_or_head is neither a Head nor a PredictionTask.

+
+
+
+ +
+
+as_tabular(name=None)[source]
+

Converts the output of the block into a dictionary, keyed by the +provided name

+
+
Parameters
+

name (str, optional) – The output name, if not provided, uses the name of the block class. +by default None

+
+
+
+ +
+ +
+
+class transformers4rec.torch.block.base.Block(module: torch.nn.modules.module.Module, output_size: Union[List[int], torch.Size])[source]
+

Bases: transformers4rec.torch.block.base.BlockBase

+

Wraps a PyTorch module, allowing it to be used as a block in a T4Rec model. +It carries the module and its expected output size.

+
+
Parameters
+
    +
  • module (torch.nn.Module) – The PyTorch module to be wrapped in this block.

  • +
  • output_size (Union[List[int], torch.Size]) – The expected output size of the module.

  • +
+
+
+
+
+forward(inputs, **kwargs)[source]
+
+ +
+
+forward_output_size(input_size)[source]
+

Calculates the output size of the tensor(s) returned by the forward pass, +given the input size.

+
+
Parameters
+

input_size (Union[List[int], torch.Size]) – The size of the input tensor(s) to the module.

+
+
Returns
+

The size of the output from the module.

+
+
Return type
+

Union[List[int], torch.Size]

+
+
+
+ +
+ +
+
+class transformers4rec.torch.block.base.SequentialBlock(*args, output_size: Optional[Union[List[int], torch.Size]] = None)[source]
+

Bases: transformers4rec.torch.block.base.BlockBase, torch.nn.modules.container.Sequential

+

Extends the module torch.nn.Sequential. It’s used for creating +a sequence of layers or blocks in a T4Rec model. The modules +will be applied to inputs in the order they are passed in the constructor.

+
+
Parameters
+
    +
  • *args – The list of PyTorch modules.

  • +
  • output_size (Union[List[int], torch.Size], optional) – The expected output size from the last layer in the sequential block +By default None

  • +
+
+
+
+
+property inputs
+
+ +
+
+add_module(name: str, module: Optional[torch.nn.modules.module.Module])None[source]
+

Adds a PyTorch module to the sequential block. If a list of strings is provided, +a FilterFeatures block gets added to the sequential block.

+
+
Parameters
+
    +
  • name (str) – The name of the child module. The child module can be accessed +from this module using the given name.

  • +
  • module (Optional[Union[List[str], Module]]) – The child module to be added to the module.

  • +
+
+
+
+ +
+
+add_module_and_maybe_build(name: str, module, parent, idx)torch.nn.modules.module.Module[source]
+

Checks if a module needs to be built and adds it to the sequential block.

+
+
Parameters
+
    +
  • name (str) – The name of the child module.

  • +
  • module (torch.nn.Module) – The child module to be added to the sequential block.

  • +
  • parent (torch.nn.Module) – The parent module.

  • +
  • idx (int) – The index of the current module in the sequential block.

  • +
+
+
+
+ +
+
+forward(input, training=False, testing=False, **kwargs)[source]
+

Applies the module’s layers sequentially to the input block.

+
+
Parameters
+
    +
  • input (tensor) – The input to the block.

  • +
  • training (bool, optional) – Whether the block is in training mode. The default is False.

  • +
  • testing (bool, optional) – Whether the block is in testing mode. The default is False.

  • +
+
+
+
+ +
+
+build(input_size, schema=None, **kwargs)[source]
+

Builds the layers of the sequential block given the input size.

+
+
Parameters
+
    +
  • input_size (Union[List[int], torch.Size]) – The size of the input tensor(s).

  • +
  • schema (Schema, optional) – The schema of the inputs features, by default None

  • +
+
+
Returns
+

The built sequential block.

+
+
Return type
+

SequentialBlock

+
+
+
+ +
+
+as_tabular(name=None)[source]
+

Converts the output of the block into a dictionary, keyed by the +provided name

+
+
Parameters
+

name (str, optional) – The output name, if not provided, uses the name of the block class. +by default None

+
+
+
+ +
+
+forward_output_size(input_size)[source]
+

Calculates the output size of the tensor(s) returned by the forward pass, +given the input size.

+
+
Parameters
+

input_size (Union[List[int], torch.Size]) – The size of the input tensor(s) to the module.

+
+
Returns
+

The size of the output from the module.

+
+
Return type
+

Union[List[int], torch.Size]

+
+
+
+ +
+
+static get_children_by_class_name(parent, *class_name)[source]
+
+ +
+ +
+
+transformers4rec.torch.block.base.build_blocks(*modules)[source]
+

Builds a SequentialBlock from a list of PyTorch modules.

+
+
Parameters
+

*modules (List[torch.nn.Module]) – List containing PyTorch modules.

+
+
Returns
+

+
+
Return type
+

A SequentialBlock instance created from the provided modules.

+
+
+
+ +
+
+class transformers4rec.torch.block.base.BuildableBlock[source]
+

Bases: abc.ABC

+

Abstract base class for buildable blocks. +Subclasses of BuildableBlock must implement the build method

+
+
+abstract build(input_size)transformers4rec.torch.block.base.BlockBase[source]
+
+ +
+
+to_module(shape_or_module)[source]
+
+ +
+ +
+
+transformers4rec.torch.block.base.right_shift_block(self, other)[source]
+
+ +
+
+

transformers4rec.torch.block.mlp module

+
+
+class transformers4rec.torch.block.mlp.MLPBlock(dimensions, activation=<class 'torch.nn.modules.activation.ReLU'>, use_bias: bool = True, dropout: Optional[float] = None, normalization: Optional[str] = None, filter_features=None)[source]
+

Bases: transformers4rec.torch.block.base.BuildableBlock

+

Defines Multi-Layer Perceptron (MLP) Block by stacking +multiple DenseBlock instances.

+
+
Parameters
+
    +
  • dimensions (int or list of int) – The dimensions of the layers in the MLP. +If an integer is provided, a single layer MLP is created. +If a list is provided, it must contain the size of each layer in order.

  • +
  • activation (optional) – The activation function to apply after each layer. +By default torch.nn.ReLU.

  • +
  • use_bias (bool, optional) – Whether to add a bias term to the dense layers. +by default True

  • +
  • dropout (float, optional) – The dropout rate to apply after each layer, by default None

  • +
  • normalization (str, optional) – The normalization to apply after each layer, by default None

  • +
  • filter_features (List[str], optional) – List of features to select from the input., by default None

  • +
+
+
+
+
+build(input_shape)transformers4rec.torch.block.base.SequentialBlock[source]
+
+ +
+ +
+
+class transformers4rec.torch.block.mlp.DenseBlock(input_shape: Union[List[int], torch.Size], in_features: int, out_features: int, activation=<class 'torch.nn.modules.activation.ReLU'>, use_bias: bool = True, dropout: Optional[float] = None, normalization=None)[source]
+

Bases: transformers4rec.torch.block.base.SequentialBlock

+

A buildable dense Block to represent a fully connected layer.

+
+
Parameters
+
    +
  • input_shape (Union[List[int], torch.Size]) – The shape of the input tensor.

  • +
  • in_features (int) – Number of input features.

  • +
  • out_features (int) – Number of output features.

  • +
  • activation (torch.nn.Module, optional) – The activation function to apply after the linear layer. +By default torch.nn.ReLU.

  • +
  • use_bias (bool, optional) – Whether to use bias in the layer. +By default True.

  • +
  • dropout (float, optional) – The dropout rate to apply after the dense layer, if any. +By default is None.

  • +
  • normalization (str, optional) – The type of normalization to apply after the dense layer. +Only ‘batch_norm’ is supported. +By default is None.

  • +
+
+
+
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+

transformers4rec.torch.block.transformer module

+
+
+class transformers4rec.torch.block.transformer.TransformerPrepare(transformer: Union[transformers.modeling_utils.PreTrainedModel, transformers.configuration_utils.PretrainedConfig], masking: Optional[transformers4rec.torch.masking.MaskSequence] = None)[source]
+

Bases: torch.nn.modules.module.Module

+

Base class to prepare additional inputs to the forward call of +the HF transformer layer.

+
+
Parameters
+
    +
  • transformer (TransformerBody) – The Transformer module.

  • +
  • masking (Optional[MaskSequence]) – Masking block used to for masking input sequences. +By default None.

  • +
+
+
+
+
+forward(inputs_embeds)Dict[str, Any][source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.block.transformer.GPT2Prepare(transformer: Union[transformers.modeling_utils.PreTrainedModel, transformers.configuration_utils.PretrainedConfig], masking: Optional[transformers4rec.torch.masking.MaskSequence] = None)[source]
+

Bases: transformers4rec.torch.block.transformer.TransformerPrepare

+

TransformerPrepare module for GPT-2.

+

This class extends the inputs for GPT-2 with a +triangular causal mask to the inputs.

+
+
+forward(inputs_embeds)Dict[str, Any][source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.block.transformer.TransformerBlock(transformer: Union[transformers.modeling_utils.PreTrainedModel, transformers.configuration_utils.PretrainedConfig], masking: Optional[transformers4rec.torch.masking.MaskSequence] = None, prepare_module: Optional[Type[transformers4rec.torch.block.transformer.TransformerPrepare]] = None, output_fn=<function TransformerBlock.<lambda>>)[source]
+

Bases: transformers4rec.torch.block.base.BlockBase

+

Class to support HF Transformers for session-based and sequential-based recommendation models.

+
+
Parameters
+
    +
  • transformer (TransformerBody) – The T4RecConfig or a pre-trained HF object related to specific transformer architecture.

  • +
  • masking – Needed when masking is applied on the inputs.

  • +
+
+
+
+
+TRANSFORMER_TO_PREPARE: Dict[Type[transformers.modeling_utils.PreTrainedModel], Type[transformers4rec.torch.block.transformer.TransformerPrepare]] = {<class 'transformers.models.gpt2.modeling_gpt2.GPT2Model'>: <class 'transformers4rec.torch.block.transformer.GPT2Prepare'>}
+
+ +
+
+transformer: PreTrainedModel
+
+ +
+
+prepare_module: Optional[TransformerPrepare]
+
+ +
+
+classmethod from_registry(transformer: str, d_model: int, n_head: int, n_layer: int, total_seq_length: int, masking: Optional[transformers4rec.torch.masking.MaskSequence] = None)[source]
+

Load the HF transformer architecture based on its name

+
+
Parameters
+
    +
  • transformer (str) – Name of the Transformer to use. Possible values are : +[“reformer”, “gtp2”, “longformer”, “electra”, “albert”, “xlnet”]

  • +
  • d_model (int) – size of hidden states for Transformers

  • +
  • n_head – Number of attention heads for Transformers

  • +
  • n_layer (int) – Number of layers for RNNs and Transformers”

  • +
  • total_seq_length (int) – The maximum sequence length

  • +
+
+
+
+ +
+
+training: bool
+
+ +
+
+forward(inputs_embeds, **kwargs)[source]
+

Transformer Models

+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.torch.features.html b/review/pr-767/api/transformers4rec.torch.features.html new file mode 100644 index 0000000000..43988f2b7b --- /dev/null +++ b/review/pr-767/api/transformers4rec.torch.features.html @@ -0,0 +1,882 @@ + + + + + + transformers4rec.torch.features package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.torch.features package

+
+

Submodules

+
+
+

transformers4rec.torch.features.base module

+
+
+class transformers4rec.torch.features.base.InputBlock(pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularBlock, abc.ABC

+
+ +
+
+

transformers4rec.torch.features.continuous module

+
+
+class transformers4rec.torch.features.continuous.ContinuousFeatures(features: List[str], pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.features.base.InputBlock

+

Input block for continuous features.

+
+
Parameters
+
+
+
+
+
+classmethod from_features(features, **kwargs)[source]
+
+ +
+
+forward(inputs, **kwargs)[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+ +
+
+

transformers4rec.torch.features.embedding module

+
+
+class transformers4rec.torch.features.embedding.EmbeddingFeatures(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig], item_id: Optional[str] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.features.base.InputBlock

+

Input block for embedding-lookups for categorical features.

+

For multi-hot features, the embeddings will be aggregated into a single tensor using the mean.

+
+
Parameters
+
    +
  • feature_config (Dict[str, FeatureConfig]) – This specifies what TableConfig to use for each feature. For shared embeddings, the same +TableConfig can be used for multiple features.

  • +
  • item_id (str, optional) – The name of the feature that’s used for the item_id.

  • +
+
+
+
+
pre: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs when the module is called (so before forward).

+
+
post: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs after the module is called (so after forward).

+
+
aggregation: Union[str, TabularAggregation], optional

Aggregation to apply after processing the forward-method to output a single Tensor.

+
+
+
+
+property item_embedding_table
+
+ +
+
+table_to_embedding_module(table: transformers4rec.torch.features.embedding.TableConfig)torch.nn.modules.module.Module[source]
+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, embedding_dims: Optional[Dict[str, int]] = None, embedding_dim_default: int = 64, infer_embedding_sizes: bool = False, infer_embedding_sizes_multiplier: float = 2.0, embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] = None, combiner: str = 'mean', tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, item_id: Optional[str] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, aggregation=None, pre=None, post=None, **kwargs)Optional[transformers4rec.torch.features.embedding.EmbeddingFeatures][source]
+

Instantitates EmbeddingFeatures from a DatasetSchema.

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • embedding_dims (Optional[Dict[str, int]], optional) – The dimension of the embedding table for each feature (key), +by default None by default None

  • +
  • default_embedding_dim (Optional[int], optional) – Default dimension of the embedding table, when the feature is not found +in default_soft_embedding_dim, by default 64

  • +
  • infer_embedding_sizes (bool, optional) – Automatically defines the embedding dimension from the +feature cardinality in the schema, +by default False

  • +
  • infer_embedding_sizes_multiplier (Optional[int], by default 2.0) – multiplier used by the heuristic to infer the embedding dimension from +its cardinality. Generally reasonable values range between 2.0 and 10.0

  • +
  • embeddings_initializers (Optional[Dict[str, Callable[[Any], None]]]) – Dict where keys are feature names and values are callable to initialize embedding tables

  • +
  • combiner (Optional[str], optional) – Feature aggregation option, by default “mean”

  • +
  • tags (Optional[Union[DefaultTags, list, str]], optional) – Tags to filter columns, by default None

  • +
  • item_id (Optional[str], optional) – Name of the item id column (feature), by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features,, by default None

  • +
+
+
Returns
+

Returns the EmbeddingFeatures for the dataset schema

+
+
Return type
+

Optional[EmbeddingFeatures]

+
+
+
+ +
+
+item_ids(inputs)torch.Tensor[source]
+
+ +
+
+forward(inputs, **kwargs)[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+ +
+
+class transformers4rec.torch.features.embedding.EmbeddingBagWrapper(num_embeddings: int, embedding_dim: int, max_norm: Optional[float] = None, norm_type: float = 2.0, scale_grad_by_freq: bool = False, mode: str = 'mean', sparse: bool = False, _weight: Optional[torch.Tensor] = None, include_last_offset: bool = False, padding_idx: Optional[int] = None, device=None, dtype=None)[source]
+

Bases: torch.nn.modules.sparse.EmbeddingBag

+

Wrapper class for the PyTorch EmbeddingBag module.

+

This class extends the torch.nn.EmbeddingBag class and overrides +the forward method to handle 1D tensor inputs +by reshaping them to 2D as required by the EmbeddingBag.

+
+
+forward(input, **kwargs)[source]
+
+ +
+
+num_embeddings: int
+
+ +
+
+embedding_dim: int
+
+ +
+
+max_norm: Optional[float]
+
+ +
+
+norm_type: float
+
+ +
+
+scale_grad_by_freq: bool
+
+ +
+
+weight: torch.Tensor
+
+ +
+
+mode: str
+
+ +
+
+sparse: bool
+
+ +
+
+include_last_offset: bool
+
+ +
+
+padding_idx: Optional[int]
+
+ +
+ +
+
+class transformers4rec.torch.features.embedding.SoftEmbeddingFeatures(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig], layer_norm: bool = True, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, **kwarg)[source]
+

Bases: transformers4rec.torch.features.embedding.EmbeddingFeatures

+

Encapsulate continuous features encoded using the Soft-one hot encoding +embedding technique (SoftEmbedding), from https://arxiv.org/pdf/1708.00065.pdf +In a nutshell, it keeps an embedding table for each continuous feature, +which is represented as a weighted average of embeddings.

+
+
Parameters
+
    +
  • feature_config (Dict[str, FeatureConfig]) – This specifies what TableConfig to use for each feature. For shared embeddings, the same +TableConfig can be used for multiple features.

  • +
  • layer_norm (boolean) – When layer_norm is true, TabularLayerNorm will be used in post.

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, soft_embedding_cardinalities: Optional[Dict[str, int]] = None, soft_embedding_cardinality_default: int = 10, soft_embedding_dims: Optional[Dict[str, int]] = None, soft_embedding_dim_default: int = 8, embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] = None, layer_norm: bool = True, combiner: str = 'mean', tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, **kwargs)Optional[transformers4rec.torch.features.embedding.SoftEmbeddingFeatures][source]
+

Instantitates SoftEmbeddingFeatures from a DatasetSchema.

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • soft_embedding_cardinalities (Optional[Dict[str, int]], optional) – The cardinality of the embedding table for each feature (key), +by default None

  • +
  • soft_embedding_cardinality_default (Optional[int], optional) – Default cardinality of the embedding table, when the feature +is not found in soft_embedding_cardinalities, by default 10

  • +
  • soft_embedding_dims (Optional[Dict[str, int]], optional) – The dimension of the embedding table for each feature (key), by default None

  • +
  • soft_embedding_dim_default (Optional[int], optional) – Default dimension of the embedding table, when the feature +is not found in soft_embedding_dim_default, by default 8

  • +
  • embeddings_initializers (Optional[Dict[str, Callable[[Any], None]]]) – Dict where keys are feature names and values are callable to initialize embedding tables

  • +
  • combiner (Optional[str], optional) – Feature aggregation option, by default “mean”

  • +
  • tags (Optional[Union[DefaultTags, list, str]], optional) – Tags to filter columns, by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features, by default None

  • +
+
+
Returns
+

Returns a SoftEmbeddingFeatures instance from the dataset schema

+
+
Return type
+

Optional[SoftEmbeddingFeatures]

+
+
+
+ +
+
+table_to_embedding_module(table: transformers4rec.torch.features.embedding.TableConfig)transformers4rec.torch.features.embedding.SoftEmbedding[source]
+
+ +
+ +
+
+class transformers4rec.torch.features.embedding.TableConfig(vocabulary_size: int, dim: int, initializer: Optional[Callable[[torch.Tensor], None]] = None, combiner: str = 'mean', name: Optional[str] = None)[source]
+

Bases: object

+

Class to configure the embeddings lookup table for a categorical feature.

+
+
+vocabulary_size
+

The size of the vocabulary, +i.e., the cardinality of the categorical feature.

+
+
Type
+

int

+
+
+
+ +
+
+dim
+

The dimensionality of the embedding vectors.

+
+
Type
+

int

+
+
+
+ +
+
+initializer
+

The initializer function for the embedding weights. +If None, the weights are initialized using a normal +distribution with mean 0.0 and standard deviation 0.05.

+
+
Type
+

Optional[Callable[[torch.Tensor], None]]

+
+
+
+ +
+
+combiner
+

The combiner operation used to aggregate bag of embeddings. +Possible options are “mean”, “sum”, and “sqrtn”. +By default “mean”.

+
+
Type
+

Optional[str]

+
+
+
+ +
+
+name
+

The name of the lookup table. +By default None.

+
+
Type
+

Optional[str]

+
+
+
+ +
+ +
+
+class transformers4rec.torch.features.embedding.FeatureConfig(table: transformers4rec.torch.features.embedding.TableConfig, max_sequence_length: int = 0, name: Optional[str] = None)[source]
+

Bases: object

+

Class to set the embeddings table of a categorical feature +with a maximum sequence length.

+
+
+table
+

Configuration for the lookup table, +which is used for embedding lookup and aggregation.

+
+
Type
+

TableConfig

+
+
+
+ +
+
+max_sequence_length
+

Maximum sequence length for sequence features. +By default 0.

+
+
Type
+

int, optional

+
+
+
+ +
+
+name
+

The feature name. +By default None

+
+
Type
+

str, optional

+
+
+
+ +
+ +
+
+class transformers4rec.torch.features.embedding.SoftEmbedding(num_embeddings, embeddings_dim, emb_initializer=None)[source]
+

Bases: torch.nn.modules.module.Module

+

Soft-one hot encoding embedding technique, from https://arxiv.org/pdf/1708.00065.pdf +In a nutshell, it represents a continuous feature as a weighted average of embeddings

+
+
+forward(input_numeric)[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.features.embedding.PretrainedEmbeddingsInitializer(weight_matrix: Union[torch.Tensor, List[List[float]]], trainable: bool = False, **kwargs)[source]
+

Bases: torch.nn.modules.module.Module

+

Initializer of embedding tables with pre-trained weights

+
+
Parameters
+
    +
  • weight_matrix (Union[torch.Tensor, List[List[float]]]) – A 2D torch or numpy tensor or lists of lists with the pre-trained +weights for embeddings. The expect dims are +(embedding_cardinality, embedding_dim). The embedding_cardinality +can be inferred from the column schema, for example, +schema.select_by_name(“item_id”).feature[0].int_domain.max + 1. +The first position of the embedding table is reserved for padded +items (id=0).

  • +
  • trainable (bool) – Whether the embedding table should be trainable or not

  • +
+
+
+
+
+forward(x)[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.features.embedding.PretrainedEmbeddingFeatures(features: List[str], pretrained_output_dims: Optional[Union[int, Dict[str, int]]] = None, sequence_combiner: Optional[Union[str, torch.nn.modules.module.Module]] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, normalizer: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.features.base.InputBlock

+

Input block for pre-trained embeddings features.

+

For 3-D features, if sequence_combiner is set, the features are aggregated +using the second dimension (sequence length)

+
+
Parameters
+
    +
  • features (List[str]) – A list of the pre-trained embeddings feature names. +You typically will pass schema.select_by_tag(Tags.EMBEDDING).column_names, +as that is the tag added to pre-trained embedding features when using the +merlin.dataloader.ops.embeddings.EmbeddingOperator

  • +
  • pretrained_output_dims (Optional[Union[int, Dict[str, int]]]) – If provided, it projects features to specified dim(s). +If an int, all features are projected to that dim. +If a dict, only features provided in the dict will be mapped to the specified dim,

  • +
  • sequence_combiner (Optional[Union[str, torch.nn.Module]], optional) – A string (“mean”, “sum”, “max”, “min”) or torch.nn.Module specifying +how to combine the second dimension of the pre-trained embeddings if it is 3D. +Default is None (no sequence combiner used)

  • +
  • normalizer (Optional[Union[str, TabularTransformationType]]) – A tabular layer (e.g.tr.TabularLayerNorm()) or string (“layer-norm”) to be applied +to pre-trained embeddings after projected and sequence combined +Default is None (no normalization)

  • +
  • (Optional[Schema]) (schema) –

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+build(input_size, **kwargs)[source]
+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, pretrained_output_dims=None, sequence_combiner=None, normalizer: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, **kwargs)[source]
+
+ +
+
+forward(inputs)[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+
+parse_combiner(combiner)[source]
+
+ +
+ +
+
+

transformers4rec.torch.features.sequence module

+
+
+class transformers4rec.torch.features.sequence.SequenceEmbeddingFeatures(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig], item_id: Optional[str] = None, padding_idx: int = 0, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.features.embedding.EmbeddingFeatures

+

Input block for embedding-lookups for categorical features. This module produces 3-D tensors, +this is useful for sequential models like transformers.

+
+
Parameters
+
    +
  • feature_config (Dict[str, FeatureConfig]) – This specifies what TableConfig to use for each feature. For shared embeddings, the same +TableConfig can be used for multiple features.

  • +
  • item_id (str, optional) – The name of the feature that’s used for the item_id.

  • +
  • padding_idx (int) – The symbol to use for padding.

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+table_to_embedding_module(table: transformers4rec.torch.features.embedding.TableConfig)torch.nn.modules.sparse.Embedding[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+ +
+
+class transformers4rec.torch.features.sequence.TabularSequenceFeatures(continuous_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, categorical_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, pretrained_embedding_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, projection_module: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock, torch.nn.modules.module.Module]] = None, masking: Optional[transformers4rec.torch.masking.MaskSequence] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.features.tabular.TabularFeatures

+

Input module that combines different types of features to a sequence: continuous, +categorical & text.

+
+
Parameters
+
    +
  • continuous_module (TabularModule, optional) – Module used to process continuous features.

  • +
  • categorical_module (TabularModule, optional) – Module used to process categorical features.

  • +
  • text_embedding_module (TabularModule, optional) – Module used to process text features.

  • +
  • projection_module (BlockOrModule, optional) – Module that’s used to project the output of this module, typically done by an MLPBlock.

  • +
  • masking (MaskSequence, optional) – Masking to apply to the inputs.

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.sequence.SequenceEmbeddingFeatures

+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, continuous_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CONTINUOUS: 'continuous'>,), categorical_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CATEGORICAL: 'categorical'>,), pretrained_embeddings_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.EMBEDDING: 'embedding'>,), aggregation: Optional[str] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, continuous_projection: Optional[Union[int, List[int]]] = None, continuous_soft_embeddings: bool = False, projection: Optional[Union[torch.nn.modules.module.Module, transformers4rec.torch.block.base.BuildableBlock]] = None, d_output: Optional[int] = None, masking: Optional[Union[str, transformers4rec.torch.masking.MaskSequence]] = None, **kwargs)transformers4rec.torch.features.sequence.TabularSequenceFeatures[source]
+

Instantiates TabularFeatures from a DatasetSchema

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • continuous_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the continuous features, by default Tags.CONTINUOUS

  • +
  • categorical_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the categorical features, by default Tags.CATEGORICAL

  • +
  • aggregation (Optional[str], optional) – Feature aggregation option, by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features by default None

  • +
  • continuous_projection (Optional[Union[List[int], int]], optional) – If set, concatenate all numerical features and project them by a number of MLP layers. +The argument accepts a list with the dimensions of the MLP layers, by default None

  • +
  • continuous_soft_embeddings (bool) – Indicates if the soft one-hot encoding technique must be used to represent +continuous features, by default False

  • +
  • projection (Optional[Union[torch.nn.Module, BuildableBlock]], optional) – If set, project the aggregated embeddings vectors into hidden dimension vector space, +by default None

  • +
  • d_output (Optional[int], optional) – If set, init a MLPBlock as projection module to project embeddings vectors, +by default None

  • +
  • masking (Optional[Union[str, MaskSequence]], optional) – If set, Apply masking to the input embeddings and compute masked labels, It requires +a categorical_module including an item_id column, by default None

  • +
+
+
Returns
+

Returns TabularFeatures from a dataset schema

+
+
Return type
+

TabularFeatures

+
+
+
+ +
+
+property masking
+
+ +
+
+set_masking(value)[source]
+
+ +
+
+property item_id
+
+ +
+
+property item_embedding_table
+
+ +
+
+forward(inputs, training=False, testing=False, **kwargs)[source]
+
+ +
+
+project_continuous_features(dimensions)[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+

transformers4rec.torch.features.tabular module

+
+
+class transformers4rec.torch.features.tabular.TabularFeatures(continuous_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, categorical_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, pretrained_embedding_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.tabular.base.MergeTabular

+

Input module that combines different types of features: continuous, categorical & text.

+
+
Parameters
+
    +
  • continuous_module (TabularModule, optional) – Module used to process continuous features.

  • +
  • categorical_module (TabularModule, optional) – Module used to process categorical features.

  • +
  • text_embedding_module (TabularModule, optional) – Module used to process text features.

  • +
+
+
+
+
pre: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs when the module is called (so before forward).

+
+
post: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs after the module is called (so after forward).

+
+
aggregation: Union[str, TabularAggregation], optional

Aggregation to apply after processing the forward-method to output a single Tensor.

+
+
+
+
+CONTINUOUS_MODULE_CLASS
+

alias of transformers4rec.torch.features.continuous.ContinuousFeatures

+
+ +
+
+EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.embedding.EmbeddingFeatures

+
+ +
+
+SOFT_EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.embedding.SoftEmbeddingFeatures

+
+ +
+
+PRETRAINED_EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.embedding.PretrainedEmbeddingFeatures

+
+ +
+
+project_continuous_features(mlp_layers_dims: Union[List[int], int])transformers4rec.torch.features.tabular.TabularFeatures[source]
+

Combine all concatenated continuous features with stacked MLP layers

+
+
Parameters
+

mlp_layers_dims (Union[List[int], int]) – The MLP layer dimensions

+
+
Returns
+

Returns the same TabularFeatures object with the continuous features projected

+
+
Return type
+

TabularFeatures

+
+
+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, continuous_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CONTINUOUS: 'continuous'>,), categorical_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CATEGORICAL: 'categorical'>,), pretrained_embeddings_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.EMBEDDING: 'embedding'>,), aggregation: Optional[str] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, continuous_projection: Optional[Union[int, List[int]]] = None, continuous_soft_embeddings: bool = False, **kwargs)transformers4rec.torch.features.tabular.TabularFeatures[source]
+

Instantiates TabularFeatures from a DatasetSchema

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • continuous_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the continuous features, by default Tags.CONTINUOUS

  • +
  • categorical_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the categorical features, by default Tags.CATEGORICAL

  • +
  • aggregation (Optional[str], optional) – Feature aggregation option, by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features by default None

  • +
  • continuous_projection (Optional[Union[List[int], int]], optional) – If set, concatenate all numerical features and project them by a number of MLP layers. +The argument accepts a list with the dimensions of the MLP layers, by default None

  • +
  • continuous_soft_embeddings (bool) – Indicates if the soft one-hot encoding technique must be used to +represent continuous features, by default False

  • +
+
+
Returns
+

Returns TabularFeatures from a dataset schema

+
+
Return type
+

TabularFeatures

+
+
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+property continuous_module
+
+ +
+
+property categorical_module
+
+ +
+
+property pretrained_module
+
+ +
+ +
+
+

transformers4rec.torch.features.text module

+
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.torch.html b/review/pr-767/api/transformers4rec.torch.html new file mode 100644 index 0000000000..e048097535 --- /dev/null +++ b/review/pr-767/api/transformers4rec.torch.html @@ -0,0 +1,3466 @@ + + + + + + transformers4rec.torch package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.torch package

+ +
+

Submodules

+
+
+

transformers4rec.torch.masking module

+
+
+class transformers4rec.torch.masking.MaskingInfo(schema: torch.Tensor, targets: torch.Tensor)[source]
+

Bases: object

+
+
+schema: torch.Tensor
+
+ +
+
+targets: torch.Tensor
+
+ +
+ +
+
+class transformers4rec.torch.masking.MaskSequence(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, **kwargs)[source]
+

Bases: transformers4rec.torch.utils.torch_utils.OutputSizeMixin, torch.nn.modules.module.Module

+

Base class to prepare masked items inputs/labels for language modeling tasks.

+

Transformer architectures can be trained in different ways. Depending of the training method, +there is a specific masking schema. The masking schema sets the items to be predicted (labels) +and mask (hide) their positions in the sequence so that they are not used by the Transformer +layers for prediction.

+
+
We currently provide 4 different masking schemes out of the box:
    +
  • Causal LM (clm)

  • +
  • Masked LM (mlm)

  • +
  • Permutation LM (plm)

  • +
  • Replacement Token Detection (rtd)

  • +
+
+
+

This class can be extended to add different a masking scheme.

+
+
Parameters
+
    +
  • hidden_size – The hidden dimension of input tensors, needed to initialize trainable vector of +masked positions.

  • +
  • pad_token (int, default = 0) – Index of the padding token used for getting batch of sequences with the same length

  • +
+
+
+
+
+compute_masked_targets(item_ids: torch.Tensor, training: bool = False, testing: bool = False)transformers4rec.torch.masking.MaskingInfo[source]
+
+

Method to prepare masked labels based on the sequence of item ids. +It returns The true labels of masked positions and the related boolean mask. +And the attributes of the class mask_schema and masked_targets +are updated to be re-used in other modules.

+
+
item_ids: torch.Tensor

The sequence of input item ids used for deriving labels of +next item prediction task.

+
+
+
+
+
training: bool

Flag to indicate whether we are in Training mode or not. +During training, the labels can be any items within the sequence +based on the selected masking task.

+
+
testing: bool

Flag to indicate whether we are in Evaluation (=True) +or Inference (=False) mode. +During evaluation, we are predicting all next items or last item only +in the sequence based on the param eval_on_last_item_seq_only. +During inference, we don’t mask the input sequence and use all available +information to predict the next item.

+

Tuple[MaskingSchema, MaskedTargets]

+
+
+
+ +
+
+apply_mask_to_inputs(inputs: torch.Tensor, schema: torch.Tensor, training: bool = False, testing: bool = False)torch.Tensor[source]
+

Control the masked positions in the inputs by replacing the true interaction +by a learnable masked embedding.

+
+
Parameters
+
    +
  • inputs (torch.Tensor) – The 3-D tensor of interaction embeddings resulting from the ops: +TabularFeatures + aggregation + projection(optional)

  • +
  • schema (MaskingSchema) – The boolean mask indicating masked positions.

  • +
+
+
+
+ +
+
+predict_all(item_ids: torch.Tensor)transformers4rec.torch.masking.MaskingInfo[source]
+

Prepare labels for all next item predictions instead of +last-item predictions in a user’s sequence.

+
+
Parameters
+

item_ids (torch.Tensor) – The sequence of input item ids used for deriving labels of +next item prediction task.

+
+
Returns
+

+
+
Return type
+

Tuple[MaskingSchema, MaskedTargets]

+
+
+
+ +
+
+forward(inputs: torch.Tensor, item_ids: torch.Tensor, training: bool = False, testing: bool = False)torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+transformer_required_arguments()Dict[str, Any][source]
+
+ +
+
+transformer_optional_arguments()Dict[str, Any][source]
+
+ +
+
+property transformer_arguments
+

Prepare additional arguments to pass to the Transformer forward methods.

+
+ +
+ +
+
+class transformers4rec.torch.masking.CausalLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, train_on_last_item_seq_only: bool = False, **kwargs)[source]
+

Bases: transformers4rec.torch.masking.MaskSequence

+

In Causal Language Modeling (clm) you predict the next item based on past positions of the +sequence. Future positions are masked.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • train_on_last_item_seq_only (predict only last item during training) –

  • +
+
+
+
+
+apply_mask_to_inputs(inputs: torch.Tensor, mask_schema: torch.Tensor, training: bool = False, testing: bool = False)torch.Tensor[source]
+
+ +
+ +
+
+class transformers4rec.torch.masking.MaskedLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, mlm_probability: float = 0.15, **kwargs)[source]
+

Bases: transformers4rec.torch.masking.MaskSequence

+

In Masked Language Modeling (mlm) you randomly select some positions of the sequence to be +predicted, which are masked. +During training, the Transformer layer is allowed to use positions on the right (future info). +During inference, all past items are visible for the Transformer layer, which tries to predict +the next item.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • mlm_probability (Optional[float], default = 0.15) – Probability of an item to be selected (masked) as a label of the given sequence. +p.s. We enforce that at least one item is masked for each sequence, so that the network can +learn something with it.

  • +
+
+
+
+
+apply_mask_to_inputs(inputs: torch.Tensor, mask_schema: torch.Tensor, training=False, testing=False)torch.Tensor[source]
+
+

Control the masked positions in the inputs by replacing the true interaction +by a learnable masked embedding.

+
+
inputs: torch.Tensor

The 3-D tensor of interaction embeddings resulting from the ops: +TabularFeatures + aggregation + projection(optional)

+
+
schema: MaskingSchema

The boolean mask indicating masked positions.

+
+
+
+
+
training: bool

Flag to indicate whether we are in Training mode or not. +During training, the labels can be any items within the sequence +based on the selected masking task.

+
+
testing: bool

Flag to indicate whether we are in Evaluation (=True) +or Inference (=False) mode. +During evaluation, we are predicting all next items or last item only +in the sequence based on the param eval_on_last_item_seq_only. +During inference, we don’t mask the input sequence and use all available +information to predict the next item.

+
+
+
+ +
+ +
+
+class transformers4rec.torch.masking.PermutationLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, plm_probability: float = 0.16666666666666666, max_span_length: int = 5, permute_all: bool = False, **kwargs)[source]
+

Bases: transformers4rec.torch.masking.MaskSequence

+

In Permutation Language Modeling (plm) you use a permutation factorization at the level of the +self-attention layer to define the accessible bidirectional context.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • max_span_length (int) – maximum length of a span of masked items

  • +
  • plm_probability (float) – The ratio of surrounding items to unmask to define the context of the span-based +prediction segment of items

  • +
  • permute_all (bool) – Compute partial span-based prediction (=False) or not.

  • +
+
+
+
+
+compute_masked_targets(item_ids: torch.Tensor, training=False, **kwargs)transformers4rec.torch.masking.MaskingInfo[source]
+
+ +
+
+transformer_required_arguments()Dict[str, Any][source]
+
+ +
+ +
+
+class transformers4rec.torch.masking.ReplacementLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, sample_from_batch: bool = False, **kwargs)[source]
+

Bases: transformers4rec.torch.masking.MaskedLanguageModeling

+

Replacement Language Modeling (rtd) you use MLM to randomly select some items, but replace +them by random tokens. +Then, a discriminator model (that can share the weights with the generator or not), is asked +to classify whether the item at each position belongs or not to the original sequence. +The generator-discriminator architecture was jointly trained using Masked LM and RTD tasks.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • sample_from_batch (bool) – Whether to sample replacement item ids from the same batch or not

  • +
+
+
+
+
+get_fake_tokens(itemid_seq, target_flat, logits)[source]
+

Second task of RTD is binary classification to train the discriminator. +The task consists of generating fake data by replacing [MASK] positions with random items, +ELECTRA discriminator learns to detect fake replacements.

+
+
Parameters
+
    +
  • itemid_seq (torch.Tensor of shape (bs, max_seq_len)) – input sequence of item ids

  • +
  • target_flat (torch.Tensor of shape (bs*max_seq_len)) – flattened masked label sequences

  • +
  • logits (torch.Tensor of shape (#pos_item, vocab_size or #pos_item),) – mlm probabilities of positive items computed by the generator model. +The logits are over the whole corpus if sample_from_batch = False, +over the positive items (masked) of the current batch otherwise

  • +
+
+
Returns
+

    +
  • corrupted_inputs (torch.Tensor of shape (bs, max_seq_len)) – input sequence of item ids with fake replacement

  • +
  • discriminator_labels (torch.Tensor of shape (bs, max_seq_len)) – binary labels to distinguish between original and replaced items

  • +
  • batch_updates (torch.Tensor of shape (#pos_item)) – the indices of replacement item within the current batch if sample_from_batch is enabled

  • +
+

+
+
+
+ +
+
+sample_from_softmax(logits: torch.Tensor)torch.Tensor[source]
+

Sampling method for replacement token modeling (ELECTRA)

+
+
Parameters
+

logits (torch.Tensor(pos_item, vocab_size)) – scores of probability of masked positions returned by the generator model

+
+
Returns
+

samples – ids of replacements items.

+
+
Return type
+

torch.Tensor(#pos_item)

+
+
+
+ +
+ +
+
+

transformers4rec.torch.ranking_metric module

+
+
+class transformers4rec.torch.ranking_metric.RankingMetric(top_ks=None, labels_onehot=False)[source]
+

Bases: torchmetrics.metric.Metric

+

Metric wrapper for computing ranking metrics@K for session-based task.

+
+
Parameters
+
    +
  • top_ks (list, default [2, 5])) – list of cutoffs

  • +
  • labels_onehot (bool) – Enable transform the labels to one-hot representation

  • +
+
+
+
+
+update(preds: torch.Tensor, target: torch.Tensor, **kwargs)[source]
+
+ +
+
+compute()[source]
+
+ +
+ +
+
+class transformers4rec.torch.ranking_metric.PrecisionAt(top_ks=None, labels_onehot=False)[source]
+

Bases: transformers4rec.torch.ranking_metric.RankingMetric

+
+ +
+
+class transformers4rec.torch.ranking_metric.RecallAt(top_ks=None, labels_onehot=False)[source]
+

Bases: transformers4rec.torch.ranking_metric.RankingMetric

+
+ +
+
+class transformers4rec.torch.ranking_metric.AvgPrecisionAt(top_ks=None, labels_onehot=False)[source]
+

Bases: transformers4rec.torch.ranking_metric.RankingMetric

+
+ +
+
+class transformers4rec.torch.ranking_metric.DCGAt(top_ks=None, labels_onehot=False)[source]
+

Bases: transformers4rec.torch.ranking_metric.RankingMetric

+
+ +
+
+class transformers4rec.torch.ranking_metric.NDCGAt(top_ks=None, labels_onehot=False)[source]
+

Bases: transformers4rec.torch.ranking_metric.RankingMetric

+
+ +
+
+class transformers4rec.torch.ranking_metric.MeanReciprocalRankAt(top_ks=None, labels_onehot=False)[source]
+

Bases: transformers4rec.torch.ranking_metric.RankingMetric

+
+ +
+
+

transformers4rec.torch.trainer module

+
+
+class transformers4rec.torch.trainer.Trainer(model: transformers4rec.torch.model.base.Model, args: transformers4rec.config.trainer.T4RecTrainingArguments, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, train_dataset_or_path=None, eval_dataset_or_path=None, test_dataset_or_path=None, train_dataloader: Optional[torch.utils.data.dataloader.DataLoader] = None, eval_dataloader: Optional[torch.utils.data.dataloader.DataLoader] = None, test_dataloader: Optional[torch.utils.data.dataloader.DataLoader] = None, callbacks: Optional[List[transformers.trainer_callback.TrainerCallback]] = [], compute_metrics=None, incremental_logging: bool = False, **kwargs)[source]
+

Bases: transformers.trainer.Trainer

+

An Trainer specialized for sequential recommendation +including (session-based and sequtial recommendation)

+
+
Parameters
+
    +
  • model (Model) – The Model defined using Transformers4Rec api.

  • +
  • args (T4RecTrainingArguments) – The training arguments needed to setup training and evaluation +experiments.

  • +
  • schema (Optional[Dataset.schema], optional) – The schema object including features to use and their properties. +by default None

  • +
  • train_dataset_or_path (Optional[Union[str, Dataset]], optional) – Path of parquet files or DataSet to use for training. +by default None

  • +
  • eval_dataset_or_path (Optional[str, Dataset], optional) – Path of parquet files or DataSet to use for evaluation. +by default None

  • +
  • train_dataloader (Optional[DataLoader], optional) – The data generator to use for training. +by default None

  • +
  • eval_dataloader (Optional[DataLoader], optional) – The data generator to use for evaluation. +by default None

  • +
  • compute_metrics (Optional[bool], optional) – Whether to compute metrics defined by Model class or not. +by default None

  • +
  • incremental_logging (bool) – Whether to enable incremental logging or not. If True, it ensures that +global steps are incremented over many trainer.train() calls, so that +train and eval metrics steps do not overlap and can be seen properly +in reports like W&B and Tensorboard

  • +
+
+
+
+
+get_train_dataloader()[source]
+

Set the train dataloader to use by Trainer. +It supports user defined data-loader set as an attribute in the constructor. +When the attribute is None, The data-loader is defined using train_dataset +and the data_loader_engine specified in Training Arguments.

+
+ +
+
+get_eval_dataloader(eval_dataset=None)[source]
+

Set the eval dataloader to use by Trainer. +It supports user defined data-loader set as an attribute in the constructor. +When the attribute is None, The data-loader is defined using eval_dataset +and the data_loader_engine specified in Training Arguments.

+
+ +
+
+get_test_dataloader(test_dataset=None)[source]
+

Set the test dataloader to use by Trainer. +It supports user defined data-loader set as an attribute in the constructor. +When the attribute is None, The data-loader is defined using test_dataset +and the data_loader_engine specified in Training Arguments.

+
+ +
+
+num_examples(dataloader: torch.utils.data.dataloader.DataLoader)[source]
+

Overriding Trainer.num_examples() method because +the data loaders for this project do not return the dataset size, +but the number of steps. So we estimate the dataset size here +by multiplying the number of steps * batch size

+
+ +
+
+reset_lr_scheduler()None[source]
+

Resets the LR scheduler of the previous Trainer.train() call, +so that a new LR scheduler one is created by the next Trainer.train() call. +This is important for LR schedules like get_linear_schedule_with_warmup() +which decays LR to 0 in the end of the train

+
+ +
+
+create_scheduler(num_training_steps: int, optimizer: Optional[torch.optim.optimizer.Optimizer] = None)[source]
+
+ +
+
+static get_scheduler(name: Union[str, transformers.trainer_utils.SchedulerType], optimizer: torch.optim.optimizer.Optimizer, num_warmup_steps: Optional[int] = None, num_training_steps: Optional[int] = None, num_cycles: Optional[int] = 0.5)[source]
+

Unified API to get any scheduler from its name.

+
+
Parameters
+
    +
  • name ((str or :obj:`SchedulerType)) – The name of the scheduler to use.

  • +
  • optimizer ((torch.optim.Optimizer)) – The optimizer that will be used during training.

  • +
  • num_warmup_steps ((int, optional)) – The number of warm-up steps to perform. This is not required by all schedulers +(hence the argument being optional), +the function will raise an error if it’s unset and the scheduler type requires it.

  • +
  • num_training_steps ((int, optional)) – The number of training steps to do. This is not required by all schedulers +(hence the argument being optional), +the function will raise an error if it’s unset and the scheduler type requires it.

  • +
  • num_cycles ((int, optional)) – The number of waves in the cosine schedule / +hard restarts to use for cosine scheduler

  • +
+
+
+
+ +
+
+compute_loss(model, inputs, return_outputs=False)[source]
+

Overriding Trainer.compute_loss() +To allow for passing the targets to the model’s forward method +How the loss is computed by Trainer. By default, all Transformers4Rec models return +a dictionary of three elements {‘loss’, ‘predictions’, and ‘labels}

+
+ +
+
+prediction_step(model: torch.nn.modules.module.Module, inputs: Dict[str, torch.Tensor], prediction_loss_only: bool, ignore_keys: Optional[List[str]] = None, training: bool = False, testing: bool = True)Tuple[Optional[float], Optional[torch.Tensor], Optional[torch.Tensor], Optional[Dict[str, Any]]][source]
+

Overriding Trainer.prediction_step() +to provide more flexibility to unpack results from the model, +like returning labels that are not exactly one input feature +model

+
+ +
+
+evaluation_loop(dataloader: torch.utils.data.dataloader.DataLoader, description: str, prediction_loss_only: Optional[bool] = None, ignore_keys: Optional[List[str]] = None, metric_key_prefix: Optional[str] = 'eval')transformers.trainer_utils.EvalLoopOutput[source]
+

Overriding Trainer.prediction_loop() +(shared by Trainer.evaluate() and Trainer.predict()) +to provide more flexibility to work with streaming metrics +(computed at each eval batch) and +to log with the outputs of the model +(e.g. prediction scores, prediction metadata, attention weights)

+
+
Parameters
+
    +
  • dataloader (DataLoader) – DataLoader object to use to iterate over evaluation data

  • +
  • description (str) – Parameter to describe the evaluation experiment. +e.g: Prediction, test

  • +
  • prediction_loss_only (Optional[bool]) – Whether or not to return the loss only. +by default None

  • +
  • ignore_keys (Optional[List[str]]) – Columns not accepted by the model.forward() method +are automatically removed. +by default None

  • +
  • metric_key_prefix (Optional[str]) – Prefix to use when logging evaluation metrics. +by default eval

  • +
+
+
+
+ +
+
+load_model_trainer_states_from_checkpoint(checkpoint_path, model=None)[source]
+

This method loads the checkpoints states of the model, trainer and random states. +If model is None the serialized model class is loaded from checkpoint. +It does not loads the optimizer and LR scheduler states (for that call trainer.train() +with resume_from_checkpoint argument for a complete load)

+
+
Parameters
+
    +
  • checkpoint_path (str) – Path to the checkpoint directory.

  • +
  • model (Optional[Model]) – Model class used by Trainer. by default None

  • +
+
+
+
+ +
+
+property log_predictions_callback
+
+ +
+
+log(logs: Dict[str, float])None[source]
+
+ +
+ +
+
+transformers4rec.torch.trainer.process_metrics(metrics, prefix='', to_cpu=True)[source]
+
+ +
+
+class transformers4rec.torch.trainer.IncrementalLoggingCallback(trainer: transformers4rec.torch.trainer.Trainer)[source]
+

Bases: transformers.trainer_callback.TrainerCallback

+

An TrainerCallback that changes the state of the Trainer +on specific hooks for the purpose of the incremental logging +:param trainer: +:type trainer: Trainer

+
+
+on_train_begin(args, state, control, model=None, **kwargs)[source]
+
+ +
+
+on_train_end(args, state, control, model=None, **kwargs)[source]
+
+ +
+
+on_epoch_end(args, state, control, model=None, **kwargs)[source]
+
+ +
+ +
+
+class transformers4rec.torch.trainer.DatasetMock(nsteps=1)[source]
+

Bases: Generic[torch.utils.data.dataset.T_co]

+

Mock to inform HF Trainer that the dataset is sized, +and can be obtained via the generated/provided data loader

+
+ +
+
+

transformers4rec.torch.typing module

+
+
+

Module contents

+
+
+class transformers4rec.torch.Schema(feature: Sequence[merlin_standard_lib.proto.schema_bp.Feature] = <betterproto._PLACEHOLDER object>, sparse_feature: List[merlin_standard_lib.proto.schema_bp.SparseFeature] = <betterproto._PLACEHOLDER object>, weighted_feature: List[merlin_standard_lib.proto.schema_bp.WeightedFeature] = <betterproto._PLACEHOLDER object>, string_domain: List[merlin_standard_lib.proto.schema_bp.StringDomain] = <betterproto._PLACEHOLDER object>, float_domain: List[merlin_standard_lib.proto.schema_bp.FloatDomain] = <betterproto._PLACEHOLDER object>, int_domain: List[merlin_standard_lib.proto.schema_bp.IntDomain] = <betterproto._PLACEHOLDER object>, default_environment: List[str] = <betterproto._PLACEHOLDER object>, annotation: merlin_standard_lib.proto.schema_bp.Annotation = <betterproto._PLACEHOLDER object>, dataset_constraints: merlin_standard_lib.proto.schema_bp.DatasetConstraints = <betterproto._PLACEHOLDER object>, tensor_representation_group: Dict[str, merlin_standard_lib.proto.schema_bp.TensorRepresentationGroup] = <betterproto._PLACEHOLDER object>)[source]
+

Bases: merlin_standard_lib.proto.schema_bp._Schema

+

A collection of column schemas for a dataset.

+
+
+feature: List[merlin_standard_lib.schema.schema.ColumnSchema] = Field(name=None,type=None,default=<betterproto._PLACEHOLDER object>,default_factory=<dataclasses._MISSING_TYPE object>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({'betterproto': FieldMetadata(number=1, proto_type='message', map_types=None, group=None, wraps=None)}),_field_type=None)
+
+ +
+
+classmethod create(column_schemas: Optional[Union[List[Union[merlin_standard_lib.schema.schema.ColumnSchema, str]], Dict[str, Union[merlin_standard_lib.schema.schema.ColumnSchema, str]]]] = None, **kwargs)[source]
+
+ +
+
+with_tags_based_on_properties(using_value_count=True, using_domain=True)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+apply(selector)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+apply_inverse(selector)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+filter_columns_from_dict(input_dict)[source]
+
+ +
+
+select_by_type(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_type(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+select_by_tag(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_tag(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+select_by_name(to_select)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+remove_by_name(to_remove)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+map_column_schemas(map_fn: Callable[[merlin_standard_lib.schema.schema.ColumnSchema], merlin_standard_lib.schema.schema.ColumnSchema])merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+filter_column_schemas(filter_fn: Callable[[merlin_standard_lib.schema.schema.ColumnSchema], bool], negate=False)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+property column_names
+
+ +
+
+property column_schemas
+
+ +
+
+property item_id_column_name
+
+ +
+
+from_json(value: Union[str, bytes])merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+to_proto_text()str[source]
+
+ +
+
+from_proto_text(path_or_proto_text: str)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+copy(**kwargs)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+
+add(other, allow_overlap=True)merlin_standard_lib.schema.schema.Schema[source]
+
+ +
+ +
+
+transformers4rec.torch.requires_schema(module)[source]
+
+ +
+
+class transformers4rec.torch.T4RecConfig[source]
+

Bases: object

+

A class responsible for setting the configuration of the transformers class +from Hugging Face and returning the corresponding T4Rec model.

+
+
+to_huggingface_torch_model()[source]
+

Instantiate a Hugging Face transformer model based on +the configuration parameters of the class.

+
+
Returns
+

The Hugging Face transformer model.

+
+
Return type
+

transformers.PreTrainedModel

+
+
+
+ +
+
+to_torch_model(input_features, *prediction_task, task_blocks=None, task_weights=None, loss_reduction='mean', **kwargs)[source]
+

Links the Hugging Face transformer model to the given input block and prediction tasks, +and returns a T4Rec model.

+
+
Parameters
+
    +
  • input_features (torch4rec.TabularSequenceFeatures) – The sequential block that represents the input features and +defines the masking strategy for training and evaluation.

  • +
  • prediction_task (torch4rec.PredictionTask) – One or multiple prediction tasks.

  • +
  • task_blocks (list, optional) – List of task-specific blocks that we apply on top of the HF transformer’s output.

  • +
  • task_weights (list, optional) – List of the weights to use for combining the tasks losses.

  • +
  • loss_reduction (str, optional) –

    +
    The reduction to apply to the prediction losses, possible values are:

    ’none’: no reduction will be applied, +‘mean’: the weighted mean of the output is taken, +‘sum’: the output will be summed.

    +
    +
    +

    By default: ‘mean’.

    +

  • +
+
+
Returns
+

The T4Rec torch model.

+
+
Return type
+

torch4rec.Model

+
+
Raises
+

ValueError – If input block or prediction task is of the wrong type.

+
+
+
+ +
+
+property transformers_config_cls
+
+ +
+
+classmethod build(*args, **kwargs)[source]
+
+ +
+ +
+
+class transformers4rec.torch.GPT2Config(vocab_size=50257, n_positions=1024, n_embd=768, n_layer=12, n_head=12, n_inner=None, activation_function='gelu_new', resid_pdrop=0.1, embd_pdrop=0.1, attn_pdrop=0.1, layer_norm_epsilon=1e-05, initializer_range=0.02, summary_type='cls_index', summary_use_proj=True, summary_activation=None, summary_proj_to_labels=True, summary_first_dropout=0.1, scale_attn_weights=True, use_cache=True, bos_token_id=50256, eos_token_id=50256, scale_attn_by_inverse_layer_idx=False, reorder_and_upcast_attn=False, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.gpt2.configuration_gpt2.GPT2Config

+

Subclass of T4RecConfig and transformers.GPT2Config from Hugging Face. +It handles configuration for GPT2 layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of GPT2Config with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of GPT2Config.

+
+
Return type
+

GPT2Config

+
+
+
+ +
+ +
+
+class transformers4rec.torch.XLNetConfig(vocab_size=32000, d_model=1024, n_layer=24, n_head=16, d_inner=4096, ff_activation='gelu', untie_r=True, attn_type='bi', initializer_range=0.02, layer_norm_eps=1e-12, dropout=0.1, mem_len=512, reuse_len=None, use_mems_eval=True, use_mems_train=False, bi_data=False, clamp_len=- 1, same_length=False, summary_type='last', summary_use_proj=True, summary_activation='tanh', summary_last_dropout=0.1, start_n_top=5, end_n_top=5, pad_token_id=5, bos_token_id=1, eos_token_id=2, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.xlnet.configuration_xlnet.XLNetConfig

+

Subclass of T4RecConfig and transformers.XLNetConfig from Hugging Face. +It handles configuration for XLNetConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length=None, attn_type='bi', hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, mem_len=1, **kwargs)[source]
+

Creates an instance of XLNetConfig with the given parameters.

+
+
Parameters
+
    +
  • {transformer_cfg_parameters}

  • +
  • mem_len (int,) – The number of tokens to be cached. Pre-computed key/value pairs +from a previous forward pass are stored and won’t be re-computed. +This parameter is especially useful for long sequence modeling where +different batches may truncate the entire sequence. +Tasks like user-aware recommendation could benefit from this feature. +By default, this parameter is set to 1, which means no caching is used.

  • +
+
+
Returns
+

An instance of XLNetConfig.

+
+
Return type
+

XLNetConfig

+
+
+
+ +
+ +
+
+class transformers4rec.torch.TransfoXLConfig(vocab_size=267735, cutoffs=[20000, 40000, 200000], d_model=1024, d_embed=1024, n_head=16, d_head=64, d_inner=4096, div_val=4, pre_lnorm=False, n_layer=18, mem_len=1600, clamp_len=1000, same_length=True, proj_share_all_but_first=True, attn_type=0, sample_softmax=- 1, adaptive=True, dropout=0.1, dropatt=0.0, untie_r=True, init='normal', init_range=0.01, proj_init_std=0.01, init_std=0.02, layer_norm_epsilon=1e-05, eos_token_id=0, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.transfo_xl.configuration_transfo_xl.TransfoXLConfig

+

Subclass of T4RecConfig and transformers. TransfoXLConfig from Hugging Face. +It handles configuration for TransfoXLConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of TransfoXLConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of TransfoXLConfig.

+
+
Return type
+

TransfoXLConfig

+
+
+
+ +
+ +
+
+class transformers4rec.torch.LongformerConfig(attention_window: Union[List[int], int] = 512, sep_token_id: int = 2, pad_token_id: int = 1, bos_token_id: int = 0, eos_token_id: int = 2, vocab_size: int = 30522, hidden_size: int = 768, num_hidden_layers: int = 12, num_attention_heads: int = 12, intermediate_size: int = 3072, hidden_act: str = 'gelu', hidden_dropout_prob: float = 0.1, attention_probs_dropout_prob: float = 0.1, max_position_embeddings: int = 512, type_vocab_size: int = 2, initializer_range: float = 0.02, layer_norm_eps: float = 1e-12, onnx_export: bool = False, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.longformer.configuration_longformer.LongformerConfig

+

Subclass of T4RecConfig and transformers.LongformerConfig from Hugging Face. +It handles configuration for LongformerConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of LongformerConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of LongformerConfig.

+
+
Return type
+

LongformerConfig

+
+
+
+ +
+ +
+
+class transformers4rec.torch.AlbertConfig(vocab_size=30000, embedding_size=128, hidden_size=4096, num_hidden_layers=12, num_hidden_groups=1, num_attention_heads=64, intermediate_size=16384, inner_group_num=1, hidden_act='gelu_new', hidden_dropout_prob=0, attention_probs_dropout_prob=0, max_position_embeddings=512, type_vocab_size=2, initializer_range=0.02, layer_norm_eps=1e-12, classifier_dropout_prob=0.1, position_embedding_type='absolute', pad_token_id=0, bos_token_id=2, eos_token_id=3, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.albert.configuration_albert.AlbertConfig

+

Subclass of T4RecConfig and transformers.AlbertConfig from Hugging Face. +It handles configuration for AlbertConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of AlbertConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of AlbertConfig.

+
+
Return type
+

AlbertConfig

+
+
+
+ +
+ +
+
+class transformers4rec.torch.ReformerConfig(attention_head_size=64, attn_layers=['local', 'lsh', 'local', 'lsh', 'local', 'lsh'], axial_norm_std=1.0, axial_pos_embds=True, axial_pos_shape=[64, 64], axial_pos_embds_dim=[64, 192], chunk_size_lm_head=0, eos_token_id=2, feed_forward_size=512, hash_seed=None, hidden_act='relu', hidden_dropout_prob=0.05, hidden_size=256, initializer_range=0.02, is_decoder=False, layer_norm_eps=1e-12, local_num_chunks_before=1, local_num_chunks_after=0, local_attention_probs_dropout_prob=0.05, local_attn_chunk_length=64, lsh_attn_chunk_length=64, lsh_attention_probs_dropout_prob=0.0, lsh_num_chunks_before=1, lsh_num_chunks_after=0, max_position_embeddings=4096, num_attention_heads=12, num_buckets=None, num_hashes=1, pad_token_id=0, vocab_size=320, tie_word_embeddings=False, use_cache=True, classifier_dropout=None, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.reformer.configuration_reformer.ReformerConfig

+

Subclass of T4RecConfig and transformers.ReformerConfig from Hugging Face. +It handles configuration for Reformer layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, axial_pos_shape_first_dim=4, **kwargs)[source]
+

Creates an instance of ReformerConfig with the given parameters.

+
+
Parameters
+
    +
  • {transformer_cfg_parameters}

  • +
  • axial_pos_shape_first_dim (int, optional) – The first dimension of the axial position encodings. +During training, the product of the position dims has to be equal to the sequence length.

  • +
+
+
Returns
+

An instance of ReformerConfig.

+
+
Return type
+

ReformerConfig

+
+
+
+ +
+ +
+
+class transformers4rec.torch.ElectraConfig(vocab_size=30522, embedding_size=128, hidden_size=256, num_hidden_layers=12, num_attention_heads=4, intermediate_size=1024, hidden_act='gelu', hidden_dropout_prob=0.1, attention_probs_dropout_prob=0.1, max_position_embeddings=512, type_vocab_size=2, initializer_range=0.02, layer_norm_eps=1e-12, summary_type='first', summary_use_proj=True, summary_activation='gelu', summary_last_dropout=0.1, pad_token_id=0, position_embedding_type='absolute', use_cache=True, classifier_dropout=None, **kwargs)[source]
+

Bases: transformers4rec.config.transformer.T4RecConfig, transformers.models.electra.configuration_electra.ElectraConfig

+

Subclass of T4RecConfig and transformers.ElectraConfig from Hugging Face. +It handles configuration for ElectraConfig layers in the context of T4Rec models.

+
+
+classmethod build(d_model, n_head, n_layer, total_seq_length, hidden_act='gelu', initializer_range=0.01, layer_norm_eps=0.03, dropout=0.3, pad_token=0, log_attention_weights=False, **kwargs)[source]
+

Creates an instance of ElectraConfig with the given parameters.

+
+
Parameters
+

{transformer_cfg_parameters}

+
+
Returns
+

An instance of ElectraConfig.

+
+
Return type
+

ElectraConfig

+
+
+
+ +
+ +
+
+class transformers4rec.torch.T4RecTrainingArguments(output_dir: str, overwrite_output_dir: bool = False, do_train: bool = False, do_eval: bool = False, do_predict: bool = False, evaluation_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'no', prediction_loss_only: bool = False, per_device_train_batch_size: int = 8, per_device_eval_batch_size: int = 8, per_gpu_train_batch_size: Optional[int] = None, per_gpu_eval_batch_size: Optional[int] = None, gradient_accumulation_steps: int = 1, eval_accumulation_steps: Optional[int] = None, eval_delay: Optional[float] = 0, learning_rate: float = 5e-05, weight_decay: float = 0.0, adam_beta1: float = 0.9, adam_beta2: float = 0.999, adam_epsilon: float = 1e-08, max_grad_norm: float = 1.0, num_train_epochs: float = 3.0, max_steps: int = - 1, lr_scheduler_type: Union[transformers.trainer_utils.SchedulerType, str] = 'linear', warmup_ratio: float = 0.0, warmup_steps: int = 0, log_level: Optional[str] = 'passive', log_level_replica: Optional[str] = 'warning', log_on_each_node: bool = True, logging_dir: Optional[str] = None, logging_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'steps', logging_first_step: bool = False, logging_steps: float = 500, logging_nan_inf_filter: bool = True, save_strategy: Union[transformers.trainer_utils.IntervalStrategy, str] = 'steps', save_steps: float = 500, save_total_limit: Optional[int] = None, save_safetensors: Optional[bool] = False, save_on_each_node: bool = False, no_cuda: bool = False, use_mps_device: bool = False, seed: int = 42, data_seed: Optional[int] = None, jit_mode_eval: bool = False, use_ipex: bool = False, bf16: bool = False, fp16: bool = False, fp16_opt_level: str = 'O1', half_precision_backend: str = 'auto', bf16_full_eval: bool = False, fp16_full_eval: bool = False, tf32: Optional[bool] = None, local_rank: int = - 1, ddp_backend: Optional[str] = None, tpu_num_cores: Optional[int] = None, tpu_metrics_debug: bool = False, debug: str = '', dataloader_drop_last: bool = False, eval_steps: Optional[float] = None, dataloader_num_workers: int = 0, past_index: int = - 1, run_name: Optional[str] = None, disable_tqdm: Optional[bool] = None, remove_unused_columns: Optional[bool] = True, label_names: Optional[List[str]] = None, load_best_model_at_end: Optional[bool] = False, metric_for_best_model: Optional[str] = None, greater_is_better: Optional[bool] = None, ignore_data_skip: bool = False, sharded_ddp: str = '', fsdp: str = '', fsdp_min_num_params: int = 0, fsdp_config: Optional[str] = None, fsdp_transformer_layer_cls_to_wrap: Optional[str] = None, deepspeed: Optional[str] = None, label_smoothing_factor: float = 0.0, optim: Union[transformers.training_args.OptimizerNames, str] = 'adamw_hf', optim_args: Optional[str] = None, adafactor: bool = False, group_by_length: bool = False, length_column_name: Optional[str] = 'length', report_to: Optional[List[str]] = None, ddp_find_unused_parameters: Optional[bool] = None, ddp_bucket_cap_mb: Optional[int] = None, dataloader_pin_memory: bool = True, skip_memory_metrics: bool = True, use_legacy_prediction_loop: bool = False, push_to_hub: bool = False, resume_from_checkpoint: Optional[str] = None, hub_model_id: Optional[str] = None, hub_strategy: Union[transformers.trainer_utils.HubStrategy, str] = 'every_save', hub_token: Optional[str] = None, hub_private_repo: bool = False, gradient_checkpointing: bool = False, include_inputs_for_metrics: bool = False, fp16_backend: str = 'auto', push_to_hub_model_id: Optional[str] = None, push_to_hub_organization: Optional[str] = None, push_to_hub_token: Optional[str] = None, mp_parameters: str = '', auto_find_batch_size: bool = False, full_determinism: bool = False, torchdynamo: Optional[str] = None, ray_scope: Optional[str] = 'last', ddp_timeout: Optional[int] = 1800, torch_compile: bool = False, torch_compile_backend: Optional[str] = None, torch_compile_mode: Optional[str] = None, xpu_backend: Optional[str] = None, max_sequence_length: Optional[int] = None, shuffle_buffer_size: int = 0, data_loader_engine: str = 'merlin', eval_on_test_set: bool = False, eval_steps_on_train_set: int = 20, predict_top_k: int = 100, learning_rate_num_cosine_cycles_by_epoch: float = 1.25, log_predictions: bool = False, compute_metrics_each_n_steps: int = 1, experiments_group: str = 'default')[source]
+

Bases: transformers.training_args.TrainingArguments

+

Class that inherits HF TrainingArguments and add on top of it arguments needed for +session-based and sequential-based recommendation

+
+
Parameters
+
    +
  • shuffle_buffer_size (int) –

  • +
  • validate_every (Optional[int], int) – Run validation set every this epoch. +-1 means no validation is used +by default -1

  • +
  • eval_on_test_set (bool) –

  • +
  • eval_steps_on_train_set (int) –

  • +
  • predict_top_k (Option[int], int) – Truncate recommendation list to the highest top-K predicted items, +(do not affect evaluation metrics computation), +This parameter is specific to NextItemPredictionTask and only affects +model.predict() and model.evaluate(), which both call Trainer.evaluation_loop. +By default 100.

  • +
  • log_predictions (Optional[bool], bool) – log predictions, labels and metadata features each –compute_metrics_each_n_steps +(for test set). +by default False

  • +
  • log_attention_weights (Optional[bool], bool) – Logs the inputs and attention weights +each –eval_steps (only test set)” +by default False

  • +
  • learning_rate_num_cosine_cycles_by_epoch (Optional[int], int) – Number of cycles for by epoch when –lr_scheduler_type = cosine_with_warmup. +The number of waves in the cosine schedule +(e.g. 0.5 is to just decrease from the max value to 0, following a half-cosine). +by default 1.25

  • +
  • experiments_group (Optional[str], str) – Name of the Experiments Group, for organizing job runs logged on W&B +by default “default”

  • +
+
+
+
+
+max_sequence_length: Optional[int] = None
+
+ +
+
+shuffle_buffer_size: int = 0
+
+ +
+
+data_loader_engine: str = 'merlin'
+
+ +
+
+eval_on_test_set: bool = False
+
+ +
+
+eval_steps_on_train_set: int = 20
+
+ +
+
+predict_top_k: int = 100
+
+ +
+
+learning_rate_num_cosine_cycles_by_epoch: float = 1.25
+
+ +
+
+log_predictions: bool = False
+
+ +
+
+compute_metrics_each_n_steps: int = 1
+
+ +
+
+experiments_group: str = 'default'
+
+ +
+
+property place_model_on_device
+

Override the method to allow running training on cpu

+
+ +
+
+output_dir: str
+
+ +
+ +
+
+class transformers4rec.torch.SequentialBlock(*args, output_size: Optional[Union[List[int], torch.Size]] = None)[source]
+

Bases: transformers4rec.torch.block.base.BlockBase, torch.nn.modules.container.Sequential

+

Extends the module torch.nn.Sequential. It’s used for creating +a sequence of layers or blocks in a T4Rec model. The modules +will be applied to inputs in the order they are passed in the constructor.

+
+
Parameters
+
    +
  • *args – The list of PyTorch modules.

  • +
  • output_size (Union[List[int], torch.Size], optional) – The expected output size from the last layer in the sequential block +By default None

  • +
+
+
+
+
+property inputs
+
+ +
+
+add_module(name: str, module: Optional[torch.nn.modules.module.Module])None[source]
+

Adds a PyTorch module to the sequential block. If a list of strings is provided, +a FilterFeatures block gets added to the sequential block.

+
+
Parameters
+
    +
  • name (str) – The name of the child module. The child module can be accessed +from this module using the given name.

  • +
  • module (Optional[Union[List[str], Module]]) – The child module to be added to the module.

  • +
+
+
+
+ +
+
+add_module_and_maybe_build(name: str, module, parent, idx)torch.nn.modules.module.Module[source]
+

Checks if a module needs to be built and adds it to the sequential block.

+
+
Parameters
+
    +
  • name (str) – The name of the child module.

  • +
  • module (torch.nn.Module) – The child module to be added to the sequential block.

  • +
  • parent (torch.nn.Module) – The parent module.

  • +
  • idx (int) – The index of the current module in the sequential block.

  • +
+
+
+
+ +
+
+forward(input, training=False, testing=False, **kwargs)[source]
+

Applies the module’s layers sequentially to the input block.

+
+
Parameters
+
    +
  • input (tensor) – The input to the block.

  • +
  • training (bool, optional) – Whether the block is in training mode. The default is False.

  • +
  • testing (bool, optional) – Whether the block is in testing mode. The default is False.

  • +
+
+
+
+ +
+
+build(input_size, schema=None, **kwargs)[source]
+

Builds the layers of the sequential block given the input size.

+
+
Parameters
+
    +
  • input_size (Union[List[int], torch.Size]) – The size of the input tensor(s).

  • +
  • schema (Schema, optional) – The schema of the inputs features, by default None

  • +
+
+
Returns
+

The built sequential block.

+
+
Return type
+

SequentialBlock

+
+
+
+ +
+
+as_tabular(name=None)[source]
+

Converts the output of the block into a dictionary, keyed by the +provided name

+
+
Parameters
+

name (str, optional) – The output name, if not provided, uses the name of the block class. +by default None

+
+
+
+ +
+
+forward_output_size(input_size)[source]
+

Calculates the output size of the tensor(s) returned by the forward pass, +given the input size.

+
+
Parameters
+

input_size (Union[List[int], torch.Size]) – The size of the input tensor(s) to the module.

+
+
Returns
+

The size of the output from the module.

+
+
Return type
+

Union[List[int], torch.Size]

+
+
+
+ +
+
+static get_children_by_class_name(parent, *class_name)[source]
+
+ +
+ +
+
+transformers4rec.torch.right_shift_block(self, other)[source]
+
+ +
+
+transformers4rec.torch.build_blocks(*modules)[source]
+

Builds a SequentialBlock from a list of PyTorch modules.

+
+
Parameters
+

*modules (List[torch.nn.Module]) – List containing PyTorch modules.

+
+
Returns
+

+
+
Return type
+

A SequentialBlock instance created from the provided modules.

+
+
+
+ +
+
+class transformers4rec.torch.BlockBase(*args, **kwargs)[source]
+

Bases: transformers4rec.torch.utils.torch_utils.OutputSizeMixin, torch.nn.modules.module.Module

+

A subclass of PyTorch’s torch.nn.Module, providing additional functionality +for dealing with automatic setting of input/output dimensions of neural networks layers. +Specifically, It implements the ‘OutputSizeMixin’ for managing output sizes.

+
+
+to_model(prediction_task_or_head, inputs=None, **kwargs)[source]
+

Converts the BlockBase instance into a T4Rec model by attaching it to +attaching a ‘Head’ or ‘PredictionTask’.

+
+
Parameters
+
    +
  • prediction_task_or_head (Union[PredictionTask, Head]) – A PredictionTask or Head instance to attach to this block.

  • +
  • inputs (InputBlock, optional) – The input block representing input features. +By default None

  • +
+
+
Raises
+

ValueError – If prediction_task_or_head is neither a Head nor a PredictionTask.

+
+
+
+ +
+
+as_tabular(name=None)[source]
+

Converts the output of the block into a dictionary, keyed by the +provided name

+
+
Parameters
+

name (str, optional) – The output name, if not provided, uses the name of the block class. +by default None

+
+
+
+ +
+ +
+
+class transformers4rec.torch.TabularBlock(pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.tabular.base.TabularModule, abc.ABC

+

TabularBlock extends TabularModule to turn it into a block with output size info.

+
+
Parameters
+
+
+
+
+
+to_module(shape_or_module, device=None)[source]
+
+ +
+
+output_size(input_size=None)[source]
+
+ +
+
+build(input_size, schema=None, **kwargs)[source]
+
+ +
+ +
+
+class transformers4rec.torch.Block(module: torch.nn.modules.module.Module, output_size: Union[List[int], torch.Size])[source]
+

Bases: transformers4rec.torch.block.base.BlockBase

+

Wraps a PyTorch module, allowing it to be used as a block in a T4Rec model. +It carries the module and its expected output size.

+
+
Parameters
+
    +
  • module (torch.nn.Module) – The PyTorch module to be wrapped in this block.

  • +
  • output_size (Union[List[int], torch.Size]) – The expected output size of the module.

  • +
+
+
+
+
+forward(inputs, **kwargs)[source]
+
+ +
+
+forward_output_size(input_size)[source]
+

Calculates the output size of the tensor(s) returned by the forward pass, +given the input size.

+
+
Parameters
+

input_size (Union[List[int], torch.Size]) – The size of the input tensor(s) to the module.

+
+
Returns
+

The size of the output from the module.

+
+
Return type
+

Union[List[int], torch.Size]

+
+
+
+ +
+ +
+
+class transformers4rec.torch.MLPBlock(dimensions, activation=<class 'torch.nn.modules.activation.ReLU'>, use_bias: bool = True, dropout: Optional[float] = None, normalization: Optional[str] = None, filter_features=None)[source]
+

Bases: transformers4rec.torch.block.base.BuildableBlock

+

Defines Multi-Layer Perceptron (MLP) Block by stacking +multiple DenseBlock instances.

+
+
Parameters
+
    +
  • dimensions (int or list of int) – The dimensions of the layers in the MLP. +If an integer is provided, a single layer MLP is created. +If a list is provided, it must contain the size of each layer in order.

  • +
  • activation (optional) – The activation function to apply after each layer. +By default torch.nn.ReLU.

  • +
  • use_bias (bool, optional) – Whether to add a bias term to the dense layers. +by default True

  • +
  • dropout (float, optional) – The dropout rate to apply after each layer, by default None

  • +
  • normalization (str, optional) – The normalization to apply after each layer, by default None

  • +
  • filter_features (List[str], optional) – List of features to select from the input., by default None

  • +
+
+
+
+
+build(input_shape)transformers4rec.torch.block.base.SequentialBlock[source]
+
+ +
+ +
+
+class transformers4rec.torch.TabularTransformation(*args, **kwargs)[source]
+

Bases: transformers4rec.torch.utils.torch_utils.OutputSizeMixin, torch.nn.modules.module.Module, abc.ABC

+

Transformation that takes in TabularData and outputs TabularData.

+
+
+forward(inputs: Dict[str, torch.Tensor], **kwargs)Dict[str, torch.Tensor][source]
+
+ +
+
+classmethod parse(class_or_str)[source]
+
+ +
+ +
+
+class transformers4rec.torch.SequentialTabularTransformations(*transformation: Union[str, transformers4rec.torch.tabular.base.TabularTransformation, List[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]]])[source]
+

Bases: transformers4rec.torch.block.base.SequentialBlock

+

A sequential container, modules will be added to it in the order they are passed in.

+
+
Parameters
+

transformation (TabularTransformationType) – transformations that are passed in here will be called in order.

+
+
+
+
+append(transformation)[source]
+
+ +
+ +
+
+class transformers4rec.torch.TabularAggregation(*args, **kwargs)[source]
+

Bases: transformers4rec.torch.utils.torch_utils.OutputSizeMixin, torch.nn.modules.module.Module, abc.ABC

+

Aggregation of TabularData that outputs a single Tensor

+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+classmethod parse(class_or_str)[source]
+
+ +
+ +
+
+class transformers4rec.torch.StochasticSwapNoise(schema=None, pad_token=0, replacement_prob=0.1)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularTransformation

+

Applies Stochastic replacement of sequence features. +It can be applied as a pre transform like TransformerBlock(pre=”stochastic-swap-noise”)

+
+
+forward(inputs: Union[torch.Tensor, Dict[str, torch.Tensor]], input_mask: Optional[torch.Tensor] = None, **kwargs)Union[torch.Tensor, Dict[str, torch.Tensor]][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+augment(input_tensor: torch.Tensor, mask: Optional[torch.Tensor] = None)torch.Tensor[source]
+
+ +
+ +
+
+class transformers4rec.torch.TabularLayerNorm(features_dim: Optional[Dict[str, int]] = None)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularTransformation

+

Applies Layer norm to each input feature individually, before the aggregation

+
+
+classmethod from_feature_config(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig])[source]
+
+ +
+
+forward(inputs: Dict[str, torch.Tensor], **kwargs)Dict[str, torch.Tensor][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+build(input_size, **kwargs)[source]
+
+ +
+ +
+
+class transformers4rec.torch.TabularDropout(dropout_rate=0.0)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularTransformation

+

Applies dropout transformation.

+
+
+forward(inputs: Union[torch.Tensor, Dict[str, torch.Tensor]], **kwargs)Union[torch.Tensor, Dict[str, torch.Tensor]][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.TransformerBlock(transformer: Union[transformers.modeling_utils.PreTrainedModel, transformers.configuration_utils.PretrainedConfig], masking: Optional[transformers4rec.torch.masking.MaskSequence] = None, prepare_module: Optional[Type[transformers4rec.torch.block.transformer.TransformerPrepare]] = None, output_fn=<function TransformerBlock.<lambda>>)[source]
+

Bases: transformers4rec.torch.block.base.BlockBase

+

Class to support HF Transformers for session-based and sequential-based recommendation models.

+
+
Parameters
+
    +
  • transformer (TransformerBody) – The T4RecConfig or a pre-trained HF object related to specific transformer architecture.

  • +
  • masking – Needed when masking is applied on the inputs.

  • +
+
+
+
+
+TRANSFORMER_TO_PREPARE: Dict[Type[transformers.modeling_utils.PreTrainedModel], Type[transformers4rec.torch.block.transformer.TransformerPrepare]] = {<class 'transformers.models.gpt2.modeling_gpt2.GPT2Model'>: <class 'transformers4rec.torch.block.transformer.GPT2Prepare'>}
+
+ +
+
+classmethod from_registry(transformer: str, d_model: int, n_head: int, n_layer: int, total_seq_length: int, masking: Optional[transformers4rec.torch.masking.MaskSequence] = None)[source]
+

Load the HF transformer architecture based on its name

+
+
Parameters
+
    +
  • transformer (str) – Name of the Transformer to use. Possible values are : +[“reformer”, “gtp2”, “longformer”, “electra”, “albert”, “xlnet”]

  • +
  • d_model (int) – size of hidden states for Transformers

  • +
  • n_head – Number of attention heads for Transformers

  • +
  • n_layer (int) – Number of layers for RNNs and Transformers”

  • +
  • total_seq_length (int) – The maximum sequence length

  • +
+
+
+
+ +
+
+forward(inputs_embeds, **kwargs)[source]
+

Transformer Models

+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.ContinuousFeatures(features: List[str], pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.features.base.InputBlock

+

Input block for continuous features.

+
+
Parameters
+
+
+
+
+
+classmethod from_features(features, **kwargs)[source]
+
+ +
+
+forward(inputs, **kwargs)[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+ +
+
+class transformers4rec.torch.EmbeddingFeatures(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig], item_id: Optional[str] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.features.base.InputBlock

+

Input block for embedding-lookups for categorical features.

+

For multi-hot features, the embeddings will be aggregated into a single tensor using the mean.

+
+
Parameters
+
    +
  • feature_config (Dict[str, FeatureConfig]) – This specifies what TableConfig to use for each feature. For shared embeddings, the same +TableConfig can be used for multiple features.

  • +
  • item_id (str, optional) – The name of the feature that’s used for the item_id.

  • +
+
+
+
+
pre: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs when the module is called (so before forward).

+
+
post: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs after the module is called (so after forward).

+
+
aggregation: Union[str, TabularAggregation], optional

Aggregation to apply after processing the forward-method to output a single Tensor.

+
+
+
+
+property item_embedding_table
+
+ +
+
+table_to_embedding_module(table: transformers4rec.torch.features.embedding.TableConfig)torch.nn.modules.module.Module[source]
+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, embedding_dims: Optional[Dict[str, int]] = None, embedding_dim_default: int = 64, infer_embedding_sizes: bool = False, infer_embedding_sizes_multiplier: float = 2.0, embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] = None, combiner: str = 'mean', tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, item_id: Optional[str] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, aggregation=None, pre=None, post=None, **kwargs)Optional[transformers4rec.torch.features.embedding.EmbeddingFeatures][source]
+

Instantitates EmbeddingFeatures from a DatasetSchema.

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • embedding_dims (Optional[Dict[str, int]], optional) – The dimension of the embedding table for each feature (key), +by default None by default None

  • +
  • default_embedding_dim (Optional[int], optional) – Default dimension of the embedding table, when the feature is not found +in default_soft_embedding_dim, by default 64

  • +
  • infer_embedding_sizes (bool, optional) – Automatically defines the embedding dimension from the +feature cardinality in the schema, +by default False

  • +
  • infer_embedding_sizes_multiplier (Optional[int], by default 2.0) – multiplier used by the heuristic to infer the embedding dimension from +its cardinality. Generally reasonable values range between 2.0 and 10.0

  • +
  • embeddings_initializers (Optional[Dict[str, Callable[[Any], None]]]) – Dict where keys are feature names and values are callable to initialize embedding tables

  • +
  • combiner (Optional[str], optional) – Feature aggregation option, by default “mean”

  • +
  • tags (Optional[Union[DefaultTags, list, str]], optional) – Tags to filter columns, by default None

  • +
  • item_id (Optional[str], optional) – Name of the item id column (feature), by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features,, by default None

  • +
+
+
Returns
+

Returns the EmbeddingFeatures for the dataset schema

+
+
Return type
+

Optional[EmbeddingFeatures]

+
+
+
+ +
+
+item_ids(inputs)torch.Tensor[source]
+
+ +
+
+forward(inputs, **kwargs)[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+ +
+
+class transformers4rec.torch.SoftEmbeddingFeatures(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig], layer_norm: bool = True, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, **kwarg)[source]
+

Bases: transformers4rec.torch.features.embedding.EmbeddingFeatures

+

Encapsulate continuous features encoded using the Soft-one hot encoding +embedding technique (SoftEmbedding), from https://arxiv.org/pdf/1708.00065.pdf +In a nutshell, it keeps an embedding table for each continuous feature, +which is represented as a weighted average of embeddings.

+
+
Parameters
+
    +
  • feature_config (Dict[str, FeatureConfig]) – This specifies what TableConfig to use for each feature. For shared embeddings, the same +TableConfig can be used for multiple features.

  • +
  • layer_norm (boolean) – When layer_norm is true, TabularLayerNorm will be used in post.

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, soft_embedding_cardinalities: Optional[Dict[str, int]] = None, soft_embedding_cardinality_default: int = 10, soft_embedding_dims: Optional[Dict[str, int]] = None, soft_embedding_dim_default: int = 8, embeddings_initializers: Optional[Dict[str, Callable[[Any], None]]] = None, layer_norm: bool = True, combiner: str = 'mean', tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, **kwargs)Optional[transformers4rec.torch.features.embedding.SoftEmbeddingFeatures][source]
+

Instantitates SoftEmbeddingFeatures from a DatasetSchema.

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • soft_embedding_cardinalities (Optional[Dict[str, int]], optional) – The cardinality of the embedding table for each feature (key), +by default None

  • +
  • soft_embedding_cardinality_default (Optional[int], optional) – Default cardinality of the embedding table, when the feature +is not found in soft_embedding_cardinalities, by default 10

  • +
  • soft_embedding_dims (Optional[Dict[str, int]], optional) – The dimension of the embedding table for each feature (key), by default None

  • +
  • soft_embedding_dim_default (Optional[int], optional) – Default dimension of the embedding table, when the feature +is not found in soft_embedding_dim_default, by default 8

  • +
  • embeddings_initializers (Optional[Dict[str, Callable[[Any], None]]]) – Dict where keys are feature names and values are callable to initialize embedding tables

  • +
  • combiner (Optional[str], optional) – Feature aggregation option, by default “mean”

  • +
  • tags (Optional[Union[DefaultTags, list, str]], optional) – Tags to filter columns, by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features, by default None

  • +
+
+
Returns
+

Returns a SoftEmbeddingFeatures instance from the dataset schema

+
+
Return type
+

Optional[SoftEmbeddingFeatures]

+
+
+
+ +
+
+table_to_embedding_module(table: transformers4rec.torch.features.embedding.TableConfig)transformers4rec.torch.features.embedding.SoftEmbedding[source]
+
+ +
+ +
+
+class transformers4rec.torch.PretrainedEmbeddingsInitializer(weight_matrix: Union[torch.Tensor, List[List[float]]], trainable: bool = False, **kwargs)[source]
+

Bases: torch.nn.modules.module.Module

+

Initializer of embedding tables with pre-trained weights

+
+
Parameters
+
    +
  • weight_matrix (Union[torch.Tensor, List[List[float]]]) – A 2D torch or numpy tensor or lists of lists with the pre-trained +weights for embeddings. The expect dims are +(embedding_cardinality, embedding_dim). The embedding_cardinality +can be inferred from the column schema, for example, +schema.select_by_name(“item_id”).feature[0].int_domain.max + 1. +The first position of the embedding table is reserved for padded +items (id=0).

  • +
  • trainable (bool) – Whether the embedding table should be trainable or not

  • +
+
+
+
+
+forward(x)[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.PretrainedEmbeddingFeatures(features: List[str], pretrained_output_dims: Optional[Union[int, Dict[str, int]]] = None, sequence_combiner: Optional[Union[str, torch.nn.modules.module.Module]] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, normalizer: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.features.base.InputBlock

+

Input block for pre-trained embeddings features.

+

For 3-D features, if sequence_combiner is set, the features are aggregated +using the second dimension (sequence length)

+
+
Parameters
+
    +
  • features (List[str]) – A list of the pre-trained embeddings feature names. +You typically will pass schema.select_by_tag(Tags.EMBEDDING).column_names, +as that is the tag added to pre-trained embedding features when using the +merlin.dataloader.ops.embeddings.EmbeddingOperator

  • +
  • pretrained_output_dims (Optional[Union[int, Dict[str, int]]]) – If provided, it projects features to specified dim(s). +If an int, all features are projected to that dim. +If a dict, only features provided in the dict will be mapped to the specified dim,

  • +
  • sequence_combiner (Optional[Union[str, torch.nn.Module]], optional) – A string (“mean”, “sum”, “max”, “min”) or torch.nn.Module specifying +how to combine the second dimension of the pre-trained embeddings if it is 3D. +Default is None (no sequence combiner used)

  • +
  • normalizer (Optional[Union[str, TabularTransformationType]]) – A tabular layer (e.g.tr.TabularLayerNorm()) or string (“layer-norm”) to be applied +to pre-trained embeddings after projected and sequence combined +Default is None (no normalization)

  • +
  • (Optional[Schema]) (schema) –

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+build(input_size, **kwargs)[source]
+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]]]] = None, pretrained_output_dims=None, sequence_combiner=None, normalizer: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, **kwargs)[source]
+
+ +
+
+forward(inputs)[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+
+parse_combiner(combiner)[source]
+
+ +
+ +
+
+class transformers4rec.torch.TabularSequenceFeatures(continuous_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, categorical_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, pretrained_embedding_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, projection_module: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock, torch.nn.modules.module.Module]] = None, masking: Optional[transformers4rec.torch.masking.MaskSequence] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.features.tabular.TabularFeatures

+

Input module that combines different types of features to a sequence: continuous, +categorical & text.

+
+
Parameters
+
    +
  • continuous_module (TabularModule, optional) – Module used to process continuous features.

  • +
  • categorical_module (TabularModule, optional) – Module used to process categorical features.

  • +
  • text_embedding_module (TabularModule, optional) – Module used to process text features.

  • +
  • projection_module (BlockOrModule, optional) – Module that’s used to project the output of this module, typically done by an MLPBlock.

  • +
  • masking (MaskSequence, optional) – Masking to apply to the inputs.

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.sequence.SequenceEmbeddingFeatures

+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, continuous_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CONTINUOUS: 'continuous'>,), categorical_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CATEGORICAL: 'categorical'>,), pretrained_embeddings_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.EMBEDDING: 'embedding'>,), aggregation: Optional[str] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, continuous_projection: Optional[Union[int, List[int]]] = None, continuous_soft_embeddings: bool = False, projection: Optional[Union[torch.nn.modules.module.Module, transformers4rec.torch.block.base.BuildableBlock]] = None, d_output: Optional[int] = None, masking: Optional[Union[str, transformers4rec.torch.masking.MaskSequence]] = None, **kwargs)transformers4rec.torch.features.sequence.TabularSequenceFeatures[source]
+

Instantiates TabularFeatures from a DatasetSchema

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • continuous_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the continuous features, by default Tags.CONTINUOUS

  • +
  • categorical_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the categorical features, by default Tags.CATEGORICAL

  • +
  • aggregation (Optional[str], optional) – Feature aggregation option, by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features by default None

  • +
  • continuous_projection (Optional[Union[List[int], int]], optional) – If set, concatenate all numerical features and project them by a number of MLP layers. +The argument accepts a list with the dimensions of the MLP layers, by default None

  • +
  • continuous_soft_embeddings (bool) – Indicates if the soft one-hot encoding technique must be used to represent +continuous features, by default False

  • +
  • projection (Optional[Union[torch.nn.Module, BuildableBlock]], optional) – If set, project the aggregated embeddings vectors into hidden dimension vector space, +by default None

  • +
  • d_output (Optional[int], optional) – If set, init a MLPBlock as projection module to project embeddings vectors, +by default None

  • +
  • masking (Optional[Union[str, MaskSequence]], optional) – If set, Apply masking to the input embeddings and compute masked labels, It requires +a categorical_module including an item_id column, by default None

  • +
+
+
Returns
+

Returns TabularFeatures from a dataset schema

+
+
Return type
+

TabularFeatures

+
+
+
+ +
+
+property masking
+
+ +
+
+set_masking(value)[source]
+
+ +
+
+property item_id
+
+ +
+
+property item_embedding_table
+
+ +
+
+forward(inputs, training=False, testing=False, **kwargs)[source]
+
+ +
+
+project_continuous_features(dimensions)[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.SequenceEmbeddingFeatures(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig], item_id: Optional[str] = None, padding_idx: int = 0, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.features.embedding.EmbeddingFeatures

+

Input block for embedding-lookups for categorical features. This module produces 3-D tensors, +this is useful for sequential models like transformers.

+
+
Parameters
+
    +
  • feature_config (Dict[str, FeatureConfig]) – This specifies what TableConfig to use for each feature. For shared embeddings, the same +TableConfig can be used for multiple features.

  • +
  • item_id (str, optional) – The name of the feature that’s used for the item_id.

  • +
  • padding_idx (int) – The symbol to use for padding.

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
+
+
+table_to_embedding_module(table: transformers4rec.torch.features.embedding.TableConfig)torch.nn.modules.sparse.Embedding[source]
+
+ +
+
+forward_output_size(input_sizes)[source]
+
+ +
+ +
+
+class transformers4rec.torch.FeatureConfig(table: transformers4rec.torch.features.embedding.TableConfig, max_sequence_length: int = 0, name: Optional[str] = None)[source]
+

Bases: object

+

Class to set the embeddings table of a categorical feature +with a maximum sequence length.

+
+
+table
+

Configuration for the lookup table, +which is used for embedding lookup and aggregation.

+
+
Type
+

TableConfig

+
+
+
+ +
+
+max_sequence_length
+

Maximum sequence length for sequence features. +By default 0.

+
+
Type
+

int, optional

+
+
+
+ +
+
+name
+

The feature name. +By default None

+
+
Type
+

str, optional

+
+
+
+ +
+ +
+
+class transformers4rec.torch.TableConfig(vocabulary_size: int, dim: int, initializer: Optional[Callable[[torch.Tensor], None]] = None, combiner: str = 'mean', name: Optional[str] = None)[source]
+

Bases: object

+

Class to configure the embeddings lookup table for a categorical feature.

+
+
+vocabulary_size
+

The size of the vocabulary, +i.e., the cardinality of the categorical feature.

+
+
Type
+

int

+
+
+
+ +
+
+dim
+

The dimensionality of the embedding vectors.

+
+
Type
+

int

+
+
+
+ +
+
+initializer
+

The initializer function for the embedding weights. +If None, the weights are initialized using a normal +distribution with mean 0.0 and standard deviation 0.05.

+
+
Type
+

Optional[Callable[[torch.Tensor], None]]

+
+
+
+ +
+
+combiner
+

The combiner operation used to aggregate bag of embeddings. +Possible options are “mean”, “sum”, and “sqrtn”. +By default “mean”.

+
+
Type
+

Optional[str]

+
+
+
+ +
+
+name
+

The name of the lookup table. +By default None.

+
+
Type
+

Optional[str]

+
+
+
+ +
+ +
+
+class transformers4rec.torch.TabularFeatures(continuous_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, categorical_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, pretrained_embedding_module: Optional[transformers4rec.torch.tabular.base.TabularModule] = None, pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.tabular.base.MergeTabular

+

Input module that combines different types of features: continuous, categorical & text.

+
+
Parameters
+
    +
  • continuous_module (TabularModule, optional) – Module used to process continuous features.

  • +
  • categorical_module (TabularModule, optional) – Module used to process categorical features.

  • +
  • text_embedding_module (TabularModule, optional) – Module used to process text features.

  • +
+
+
+
+
pre: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs when the module is called (so before forward).

+
+
post: Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional

Transformations to apply on the inputs after the module is called (so after forward).

+
+
aggregation: Union[str, TabularAggregation], optional

Aggregation to apply after processing the forward-method to output a single Tensor.

+
+
+
+
+CONTINUOUS_MODULE_CLASS
+

alias of transformers4rec.torch.features.continuous.ContinuousFeatures

+
+ +
+
+EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.embedding.EmbeddingFeatures

+
+ +
+
+SOFT_EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.embedding.SoftEmbeddingFeatures

+
+ +
+
+PRETRAINED_EMBEDDING_MODULE_CLASS
+

alias of transformers4rec.torch.features.embedding.PretrainedEmbeddingFeatures

+
+ +
+
+project_continuous_features(mlp_layers_dims: Union[List[int], int])transformers4rec.torch.features.tabular.TabularFeatures[source]
+

Combine all concatenated continuous features with stacked MLP layers

+
+
Parameters
+

mlp_layers_dims (Union[List[int], int]) – The MLP layer dimensions

+
+
Returns
+

Returns the same TabularFeatures object with the continuous features projected

+
+
Return type
+

TabularFeatures

+
+
+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, continuous_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CONTINUOUS: 'continuous'>,), categorical_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.CATEGORICAL: 'categorical'>,), pretrained_embeddings_tags: Optional[Union[merlin.schema.tags.TagSet, List[str], List[merlin.schema.tags.Tags], List[Union[str, merlin.schema.tags.Tags]], Tuple[merlin.schema.tags.Tags]]] = (<Tags.EMBEDDING: 'embedding'>,), aggregation: Optional[str] = None, automatic_build: bool = True, max_sequence_length: Optional[int] = None, continuous_projection: Optional[Union[int, List[int]]] = None, continuous_soft_embeddings: bool = False, **kwargs)transformers4rec.torch.features.tabular.TabularFeatures[source]
+

Instantiates TabularFeatures from a DatasetSchema

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • continuous_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the continuous features, by default Tags.CONTINUOUS

  • +
  • categorical_tags (Optional[Union[TagsType, Tuple[Tags]]], optional) – Tags to filter the categorical features, by default Tags.CATEGORICAL

  • +
  • aggregation (Optional[str], optional) – Feature aggregation option, by default None

  • +
  • automatic_build (bool, optional) – Automatically infers input size from features, by default True

  • +
  • max_sequence_length (Optional[int], optional) – Maximum sequence length for list features by default None

  • +
  • continuous_projection (Optional[Union[List[int], int]], optional) – If set, concatenate all numerical features and project them by a number of MLP layers. +The argument accepts a list with the dimensions of the MLP layers, by default None

  • +
  • continuous_soft_embeddings (bool) – Indicates if the soft one-hot encoding technique must be used to +represent continuous features, by default False

  • +
+
+
Returns
+

Returns TabularFeatures from a dataset schema

+
+
Return type
+

TabularFeatures

+
+
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+property continuous_module
+
+ +
+
+property categorical_module
+
+ +
+
+property pretrained_module
+
+ +
+ +
+
+class transformers4rec.torch.Head(body: transformers4rec.torch.block.base.BlockBase, prediction_tasks: Union[List[transformers4rec.torch.model.base.PredictionTask], transformers4rec.torch.model.base.PredictionTask], task_blocks: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock, Dict[str, Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]]]] = None, task_weights: Optional[List[float]] = None, loss_reduction: str = 'mean', inputs: Optional[Union[transformers4rec.torch.features.sequence.TabularSequenceFeatures, transformers4rec.torch.features.tabular.TabularFeatures]] = None)[source]
+

Bases: torch.nn.modules.module.Module, transformers4rec.torch.utils.torch_utils.LossMixin, transformers4rec.torch.utils.torch_utils.MetricsMixin

+

Head of a Model, a head has a single body but could have multiple prediction-tasks. +:param body: TODO +:type body: Block +:param prediction_tasks: TODO +:type prediction_tasks: Union[List[PredictionTask], PredictionTask], optional +:param task_blocks: TODO +:param task_weights: TODO +:type task_weights: List[float], optional +:param loss_reduction: TODO +:type loss_reduction: str, default=”mean” +:param inputs: TODO +:type inputs: TabularFeaturesType, optional

+
+
+build(inputs=None, device=None, task_blocks=None)[source]
+

Build each prediction task that’s part of the head. +:param body: +:param inputs: +:param device: +:param task_blocks:

+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, body: transformers4rec.torch.block.base.BlockBase, task_blocks: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock, Dict[str, Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]]]] = None, task_weight_dict: Optional[Dict[str, float]] = None, loss_reduction: str = 'mean', inputs: Optional[Union[transformers4rec.torch.features.sequence.TabularSequenceFeatures, transformers4rec.torch.features.tabular.TabularFeatures]] = None)transformers4rec.torch.model.base.Head[source]
+

Instantiate a Head from a Schema through tagged targets. +:param schema: Schema to use for inferring all targets based on the tags. +:type schema: DatasetSchema +:param body: +:param task_blocks: +:param task_weight_dict: +:param loss_reduction: +:param inputs:

+
+
Returns
+

+
+
Return type
+

Head

+
+
+
+ +
+
+pop_labels(inputs: Dict[str, torch.Tensor])Dict[str, torch.Tensor][source]
+

Pop the labels from the different prediction_tasks from the inputs. +:param inputs: Input dictionary containing all targets. +:type inputs: TabularData

+
+
Returns
+

+
+
Return type
+

TabularData

+
+
+
+ +
+
+forward(body_outputs: Union[torch.Tensor, Dict[str, torch.Tensor]], training: bool = False, testing: bool = False, targets: Optional[Union[torch.Tensor, Dict[str, torch.Tensor]]] = None, call_body: bool = False, top_k: Optional[int] = None, **kwargs)Union[torch.Tensor, Dict[str, torch.Tensor]][source]
+
+ +
+
+calculate_metrics(predictions: Union[torch.Tensor, Dict[str, torch.Tensor]], targets: Union[torch.Tensor, Dict[str, torch.Tensor]])Dict[str, Union[Dict[str, torch.Tensor], torch.Tensor]][source]
+

Calculate metrics of the task(s) set in the Head instance. +:param predictions: The predictions tensors to use for calculate metrics.

+
+

They can be either a torch.Tensor if a single task is used or +a dictionary of torch.Tensor if multiple tasks are used. In the +second case, the dictionary is indexed by the tasks names.

+
+
+
Parameters
+

targets – The tensor or dictionary of targets to use for computing the metrics of +one or multiple tasks.

+
+
+
+ +
+
+compute_metrics(mode: Optional[str] = None)Dict[str, Union[float, torch.Tensor]][source]
+
+ +
+
+reset_metrics()[source]
+
+ +
+
+property task_blocks
+
+ +
+
+to_model(**kwargs)transformers4rec.torch.model.base.Model[source]
+

Convert the head to a Model. +:returns: +:rtype: Model

+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.Model(*head: transformers4rec.torch.model.base.Head, head_weights: Optional[List[float]] = None, head_reduction: str = 'mean', optimizer: Type[torch.optim.optimizer.Optimizer] = <class 'torch.optim.adam.Adam'>, name: Optional[str] = None, max_sequence_length: Optional[int] = None, top_k: Optional[int] = None)[source]
+

Bases: torch.nn.modules.module.Module, transformers4rec.torch.utils.torch_utils.LossMixin, transformers4rec.torch.utils.torch_utils.MetricsMixin

+
+
+forward(inputs: Dict[str, torch.Tensor], targets=None, training=False, testing=False, **kwargs)[source]
+
+ +
+
+calculate_metrics(predictions: Union[torch.Tensor, Dict[str, torch.Tensor]], targets: Union[torch.Tensor, Dict[str, torch.Tensor]])Dict[str, Union[Dict[str, torch.Tensor], torch.Tensor]][source]
+

Calculate metrics of the task(s) set in the Head instance. +:param predictions: The predictions tensors returned by the model.

+
+

They can be either a torch.Tensor if a single task is used or +a dictionary of torch.Tensor if multiple heads/tasks are used. In the +second case, the dictionary is indexed by the tasks names.

+
+
+
Parameters
+

targets – The tensor or dictionary of targets returned by the model. +They are used for computing the metrics of one or multiple tasks.

+
+
+
+ +
+
+compute_metrics(mode=None)Dict[str, Union[float, torch.Tensor]][source]
+
+ +
+
+reset_metrics()[source]
+
+ +
+
+to_lightning()[source]
+
+ +
+
+fit(dataloader, optimizer=<class 'torch.optim.adam.Adam'>, eval_dataloader=None, num_epochs=1, amp=False, train=True, verbose=True, compute_metric=True)[source]
+
+ +
+
+evaluate(dataloader, targets=None, training=False, testing=True, verbose=True, mode='eval')[source]
+
+ +
+
+property input_schema
+
+ +
+
+property output_schema
+
+ +
+
+property prediction_tasks
+
+ +
+
+save(path: Union[str, os.PathLike], model_name='t4rec_model_class')[source]
+

Saves the model to f”{export_path}/{model_name}.pkl” using cloudpickle +:param path: Path to the directory where the T4Rec model should be saved. +:type path: Union[str, os.PathLike] +:param model_name:

+
+
+
the name given to the pickle file storing the T4Rec model,

by default ‘t4rec_model_class’

+
+
+
+
+
+
+ +
+
+classmethod load(path: Union[str, os.PathLike], model_name='t4rec_model_class')transformers4rec.torch.model.base.Model[source]
+

Loads a T4Rec model that was saved with model.save(). +:param path: Path to the directory where the T4Rec model is saved. +:type path: Union[str, os.PathLike] +:param model_name:

+
+
+
the name given to the pickle file storing the T4Rec model,

by default ‘t4rec_model_class’.

+
+
+
+
+
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.PredictionTask(loss: torch.nn.modules.module.Module, metrics: Optional[Iterable[torchmetrics.metric.Metric]] = None, target_name: Optional[str] = None, task_name: Optional[str] = None, forward_to_prediction_fn: Callable[[torch.Tensor], torch.Tensor] = <function PredictionTask.<lambda>>, task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, pre: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, summary_type: str = 'last')[source]
+

Bases: torch.nn.modules.module.Module, transformers4rec.torch.utils.torch_utils.LossMixin, transformers4rec.torch.utils.torch_utils.MetricsMixin

+

Individual prediction-task of a model. +:param loss: The loss to use during training of this task. +:type loss: torch.nn.Module +:param metrics: The metrics to calculate during training & evaluation. +:type metrics: torch.nn.Module +:param target_name: Name of the target, this is needed when there are multiple targets. +:type target_name: str, optional +:param task_name: Name of the prediction task, if not provided a name will be automatically constructed based

+
+

on the target-name & class-name.

+
+
+
Parameters
+
    +
  • forward_to_prediction_fn (Callable[[torch.Tensor], torch.Tensor]) – Function to apply before the prediction

  • +
  • task_block (BlockType) – Module to transform input tensor before computing predictions.

  • +
  • pre (BlockType) – Module to compute the predictions probabilities.

  • +
  • summary_type (str) –

    +
    This is used to summarize a sequence into a single tensor. Accepted values are:
      +
    • ”last” – Take the last token hidden state (like XLNet)

    • +
    • ”first” – Take the first token hidden state (like Bert)

    • +
    • ”mean” – Take the mean of all tokens hidden states

    • +
    • ”cls_index” – Supply a Tensor of classification token position (GPT/GPT-2)

    • +
    • ”attn” – Not implemented now, use multi-head attention

    • +
    +
    +
    +

  • +
+
+
+
+
+build(body: Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock], input_size, inputs: Optional[transformers4rec.torch.features.base.InputBlock] = None, device=None, task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, pre=None)[source]
+

The method will be called when block is converted to a model, +i.e when linked to prediction head. +:param block: the model block to link with head +:param device: set the device for the metrics and layers of the task

+
+ +
+
+forward(inputs: torch.Tensor, targets: Optional[torch.Tensor] = None, training: bool = False, testing: bool = False)[source]
+
+ +
+
+property task_name
+
+ +
+
+child_name(name)[source]
+
+ +
+
+set_metrics(metrics)[source]
+
+ +
+
+calculate_metrics(predictions: torch.Tensor, targets: torch.Tensor)Dict[str, torch.Tensor][source]
+
+ +
+
+compute_metrics(**kwargs)[source]
+
+ +
+
+metric_name(metric: torchmetrics.metric.Metric)str[source]
+
+ +
+
+reset_metrics()[source]
+
+ +
+
+to_head(body, inputs=None, **kwargs)transformers4rec.torch.model.base.Head[source]
+
+ +
+
+to_model(body, inputs=None, **kwargs)transformers4rec.torch.model.base.Model[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.AsTabular(output_name: str)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularBlock

+

Converts a Tensor to TabularData by converting it to a dictionary.

+
+
Parameters
+

output_name (str) – Name that should be used as the key in the output dictionary.

+
+
+
+
+forward(inputs: torch.Tensor, **kwargs)Dict[str, torch.Tensor][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.ConcatFeatures(*args, **kwargs)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularAggregation

+

Aggregation by stacking all values in TabularData, all non-sequential values will be +converted to a sequence.

+

The output of this concatenation will have 3 dimensions.

+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.FilterFeatures(to_include: List[str], pop: bool = False)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularTransformation

+

Module that filters out certain features from TabularData.”

+
+
Parameters
+
    +
  • to_include (List[str]) – List of features to include in the result of calling the module

  • +
  • pop (bool) – Boolean indicating whether to pop the features to exclude from the inputs dictionary.

  • +
+
+
+
+
+forward(inputs: Dict[str, torch.Tensor], **kwargs)Dict[str, torch.Tensor][source]
+
+
Parameters
+
    +
  • inputs (TabularData) – Input dictionary containing features to filter.

  • +
  • Filtered TabularData that only contains the feature-names in self.to_include. (Returns) –

  • +
  • -------

  • +
+
+
+
+ +
+
+forward_output_size(input_shape)[source]
+
+
Parameters
+

input_shape

+
+
+
+ +
+ +
+
+class transformers4rec.torch.ElementwiseSum[source]
+

Bases: transformers4rec.torch.tabular.aggregation.ElementwiseFeatureAggregation

+

Aggregation by first stacking all values in TabularData in the first dimension, and then +summing the result.

+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.ElementwiseSumItemMulti(schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.tabular.aggregation.ElementwiseFeatureAggregation

+

Aggregation by applying the ElementwiseSum aggregation to all features except the item-id, +and then multiplying this with the item-ids.

+
+
Parameters
+

schema (DatasetSchema) –

+
+
+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+REQUIRES_SCHEMA = True
+
+ +
+ +
+
+class transformers4rec.torch.MergeTabular(*modules_to_merge: Union[transformers4rec.torch.tabular.base.TabularModule, Dict[str, transformers4rec.torch.tabular.base.TabularModule]], pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, **kwargs)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularBlock

+

Merge multiple TabularModule’s into a single output of TabularData.

+
+
Parameters
+
+
+
+
+
+property merge_values
+
+ +
+
+forward(inputs: Dict[str, torch.Tensor], training=True, **kwargs)Dict[str, torch.Tensor][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+build(input_size, **kwargs)[source]
+
+ +
+ +
+
+class transformers4rec.torch.StackFeatures(axis: int = - 1)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularAggregation

+

Aggregation by stacking all values in input dictionary in the given dimension.

+
+
Parameters
+

axis (int, default=-1) – Axis to use for the stacking operation.

+
+
+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.BinaryClassificationTask(target_name: Optional[str] = None, task_name: Optional[str] = None, task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, loss=BCELoss(), metrics=(BinaryPrecision(), BinaryRecall(), BinaryAccuracy()), summary_type='first')[source]
+

Bases: transformers4rec.torch.model.base.PredictionTask

+

Returns a PredictionTask for binary classification.

+

Example usage:

+
# Define the input module to process the tabular input features.
+input_module = tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=max_sequence_length,
+    continuous_projection=d_model,
+    aggregation="concat",
+    masking=None,
+)
+
+# Define XLNetConfig class and set default parameters for HF XLNet config.
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=4, n_layer=2, total_seq_length=max_sequence_length
+)
+
+# Define the model block including: inputs, masking, projection and transformer block.
+body = tr.SequentialBlock(
+    input_module,
+    tr.MLPBlock([64]),
+    tr.TransformerBlock(
+        transformer_config,
+        masking=input_module.masking
+    )
+)
+
+# Define a head with BinaryClassificationTask.
+head = tr.Head(
+    body,
+    tr.BinaryClassificationTask(
+        "click",
+        summary_type="mean",
+        metrics=[
+            tm.Precision(task='binary'),
+            tm.Recall(task='binary'),
+            tm.Accuracy(task='binary'),
+            tm.F1Score(task='binary')
+        ]
+    ),
+    inputs=input_module,
+)
+
+# Get the end-to-end Model class.
+model = tr.Model(head)
+
+
+
+
Parameters
+
    +
  • target_name (Optional[str] = None) – Specifies the variable name that represents the positive and negative values.

  • +
  • task_name (Optional[str] = None) – Specifies the name of the prediction task. If this parameter is not specified, +a name is automatically constructed based on target_name and the Python +class name of the model.

  • +
  • task_block (Optional[BlockType] = None) – Specifies a module to transform the input tensor before computing predictions.

  • +
  • loss (torch.nn.Module) – Specifies the loss function for the task. +The default class is torch.nn.BCELoss.

  • +
  • metrics (Tuple[torch.nn.Module, ..]) – Specifies the metrics to calculate during training and evaluation. +The default metrics are Precision, Recall, and Accuracy.

  • +
  • summary_type (str) –

    Summarizes a sequence into a single tensor. Accepted values are:

    +
    +
      +
    • last – Take the last token hidden state (like XLNet)

    • +
    • first – Take the first token hidden state (like Bert)

    • +
    • mean – Take the mean of all tokens hidden states

    • +
    • cls_index – Supply a Tensor of classification token position (GPT/GPT-2)

    • +
    • attn – Not implemented now, use multi-head attention

    • +
    +
    +

  • +
+
+
+
+
+DEFAULT_LOSS = BCELoss()
+
+ +
+
+DEFAULT_METRICS = (BinaryPrecision(), BinaryRecall(), BinaryAccuracy())
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.RegressionTask(target_name: Optional[str] = None, task_name: Optional[str] = None, task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, loss=MSELoss(), metrics=(MeanSquaredError()), summary_type='first')[source]
+

Bases: transformers4rec.torch.model.base.PredictionTask

+

Returns a PredictionTask for regression.

+

Example usage:

+
# Define the input module to process the tabular input features.
+input_module = tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=max_sequence_length,
+    continuous_projection=d_model,
+    aggregation="concat",
+    masking=None,
+)
+
+# Define XLNetConfig class and set default parameters for HF XLNet config.
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=4, n_layer=2, total_seq_length=max_sequence_length
+)
+
+# Define the model block including: inputs, projection and transformer block.
+body = tr.SequentialBlock(
+    input_module,
+    tr.MLPBlock([64]),
+    tr.TransformerBlock(
+        transformer_config,
+    )
+)
+
+# Define a head with BinaryClassificationTask.
+head = tr.Head(
+    body,
+    tr.RegressionTask(
+        "watch_time",
+        summary_type="mean",
+        metrics=[tm.regression.MeanSquaredError()]
+    ),
+    inputs=input_module,
+)
+
+# Get the end-to-end Model class.
+model = tr.Model(head)
+
+
+
+
Parameters
+
    +
  • target_name (Optional[str]) – Specifies the variable name that represents the continuous value to predict. +By default None

  • +
  • task_name (Optional[str]) – Specifies the name of the prediction task. If this parameter is not specified, +a name is automatically constructed based on target_name and the Python +class name of the model. +By default None

  • +
  • task_block (Optional[BlockType] = None) – Specifies a module to transform the input tensor before computing predictions.

  • +
  • loss (torch.nn.Module) – Specifies the loss function for the task. +The default class is torch.nn.MSELoss.

  • +
  • metrics (Tuple[torch.nn.Module, ..]) – Specifies the metrics to calculate during training and evaluation. +The default metric is MeanSquaredError.

  • +
  • summary_type (str) –

    Summarizes a sequence into a single tensor. Accepted values are:

    +
    +
      +
    • last – Take the last token hidden state (like XLNet)

    • +
    • first – Take the first token hidden state (like Bert)

    • +
    • mean – Take the mean of all tokens hidden states

    • +
    • cls_index – Supply a Tensor of classification token position (GPT/GPT-2)

    • +
    • attn – Not implemented now, use multi-head attention

    • +
    +
    +

  • +
+
+
+
+
+DEFAULT_LOSS = MSELoss()
+
+ +
+
+DEFAULT_METRICS = (MeanSquaredError(),)
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.NextItemPredictionTask(loss: torch.nn.modules.module.Module = CrossEntropyLoss(), metrics: Iterable[torchmetrics.metric.Metric] = (NDCGAt(), AvgPrecisionAt(), RecallAt()), task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, task_name: str = 'next-item', weight_tying: bool = False, softmax_temperature: float = 1, padding_idx: int = 0, target_dim: Optional[int] = None, sampled_softmax: Optional[bool] = False, max_n_samples: Optional[int] = 100)[source]
+

Bases: transformers4rec.torch.model.base.PredictionTask

+

This block performs item prediction task for session and sequential-based models. +It requires a body containing a masking schema to use for training and target generation. +For the supported masking schemes, please refers to: +https://nvidia-merlin.github.io/Transformers4Rec/stable/model_definition.html#sequence-masking

+
+
Parameters
+
    +
  • loss (torch.nn.Module) – Loss function to use. Defaults to NLLLos.

  • +
  • metrics (Iterable[torchmetrics.Metric]) – List of ranking metrics to use for evaluation.

  • +
  • task_block – Module to transform input tensor before computing predictions.

  • +
  • task_name (str, optional) – Name of the prediction task, if not provided a name will be automatically constructed based +on the target-name & class-name.

  • +
  • weight_tying (bool) – The item id embedding table weights are shared with the prediction network layer.

  • +
  • softmax_temperature (float) – Softmax temperature, used to reduce model overconfidence, so that softmax(logits / T). +Value 1.0 reduces to regular softmax.

  • +
  • padding_idx (int) – pad token id.

  • +
  • target_dim (int) – vocabulary size of item ids

  • +
  • sampled_softmax (Optional[bool]) – Enables sampled softmax. By default False

  • +
  • max_n_samples (Optional[int]) – Number of samples for sampled softmax. By default 100

  • +
+
+
+
+
+DEFAULT_METRICS = (NDCGAt(), AvgPrecisionAt(), RecallAt())
+
+ +
+
+build(body, input_size, device=None, inputs=None, task_block=None, pre=None)[source]
+

Build method, this is called by the Head.

+
+ +
+
+forward(inputs: torch.Tensor, targets=None, training=False, testing=False, top_k=None, **kwargs)[source]
+
+ +
+
+remove_pad_3d(inp_tensor, non_pad_mask)[source]
+
+ +
+
+calculate_metrics(predictions, targets)Dict[str, torch.Tensor][source]
+
+ +
+
+compute_metrics()[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.TabularModule(pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation, List[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]]]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation, List[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]]]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None, **kwargs)[source]
+

Bases: torch.nn.modules.module.Module

+

PyTorch Module that’s specialized for tabular-data by integrating many often used operations.

+
+
Parameters
+
+
+
+
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, tags=None, **kwargs)Optional[transformers4rec.torch.tabular.base.TabularModule][source]
+

Instantiate a TabularModule instance from a DatasetSchema.

+
+
Parameters
+
    +
  • schema

  • +
  • tags

  • +
  • kwargs

  • +
+
+
Returns
+

+
+
Return type
+

Optional[TabularModule]

+
+
+
+ +
+
+classmethod from_features(features: List[str], pre: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation, List[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]]]] = None, post: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation, List[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]]]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None)transformers4rec.torch.tabular.base.TabularModule[source]
+
+
Initializes a TabularModule instance where the contents of features will be filtered

out

+
+
+
+
Parameters
+
    +
  • features (List[str]) – A list of feature-names that will be used as the first pre-processing op to filter out +all other features not in this list.

  • +
  • pre (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs when the module is called (so before forward).

  • +
  • post (Union[str, TabularTransformation, List[str], List[TabularTransformation]], optional) – Transformations to apply on the inputs after the module is called (so after forward).

  • +
  • aggregation (Union[str, TabularAggregation], optional) – Aggregation to apply after processing the forward-method to output a single Tensor.

  • +
+
+
Returns
+

+
+
Return type
+

TabularModule

+
+
+
+ +
+
+property pre
+

returns: +:rtype: SequentialTabularTransformations, optional

+
+ +
+
+property post
+

returns: +:rtype: SequentialTabularTransformations, optional

+
+ +
+
+property aggregation
+

returns: +:rtype: TabularAggregation, optional

+
+ +
+
+pre_forward(inputs: Dict[str, torch.Tensor], transformations: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation, List[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]]]] = None)Dict[str, torch.Tensor][source]
+

Method that’s typically called before the forward method for pre-processing.

+
+
Parameters
+
    +
  • inputs (TabularData) – input-data, typically the output of the forward method.

  • +
  • transformations (TabularAggregationType, optional) –

  • +
+
+
Returns
+

+
+
Return type
+

TabularData

+
+
+
+ +
+
+forward(x: Dict[str, torch.Tensor], *args, **kwargs)Dict[str, torch.Tensor][source]
+
+ +
+
+post_forward(inputs: Dict[str, torch.Tensor], transformations: Optional[Union[str, transformers4rec.torch.tabular.base.TabularTransformation, List[Union[str, transformers4rec.torch.tabular.base.TabularTransformation]]]] = None, merge_with: Optional[Union[transformers4rec.torch.tabular.base.TabularModule, List[transformers4rec.torch.tabular.base.TabularModule]]] = None, aggregation: Optional[Union[str, transformers4rec.torch.tabular.base.TabularAggregation]] = None)Union[torch.Tensor, Dict[str, torch.Tensor]][source]
+

Method that’s typically called after the forward method for post-processing.

+
+
Parameters
+
    +
  • inputs (TabularData) – input-data, typically the output of the forward method.

  • +
  • transformations (TabularTransformationType, optional) – Transformations to apply on the input data.

  • +
  • merge_with (Union[TabularModule, List[TabularModule]], optional) – Other TabularModule’s to call and merge the outputs with.

  • +
  • aggregation (TabularAggregationType, optional) – Aggregation to aggregate the output to a single Tensor.

  • +
+
+
Returns
+

+
+
Return type
+

TensorOrTabularData (Tensor when aggregation is set, else TabularData)

+
+
+
+ +
+
+merge(other)
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.SoftEmbedding(num_embeddings, embeddings_dim, emb_initializer=None)[source]
+

Bases: torch.nn.modules.module.Module

+

Soft-one hot encoding embedding technique, from https://arxiv.org/pdf/1708.00065.pdf +In a nutshell, it represents a continuous feature as a weighted average of embeddings

+
+
+forward(input_numeric)[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.Trainer(model: transformers4rec.torch.model.base.Model, args: transformers4rec.config.trainer.T4RecTrainingArguments, schema: Optional[merlin_standard_lib.schema.schema.Schema] = None, train_dataset_or_path=None, eval_dataset_or_path=None, test_dataset_or_path=None, train_dataloader: Optional[torch.utils.data.dataloader.DataLoader] = None, eval_dataloader: Optional[torch.utils.data.dataloader.DataLoader] = None, test_dataloader: Optional[torch.utils.data.dataloader.DataLoader] = None, callbacks: Optional[List[transformers.trainer_callback.TrainerCallback]] = [], compute_metrics=None, incremental_logging: bool = False, **kwargs)[source]
+

Bases: transformers.trainer.Trainer

+

An Trainer specialized for sequential recommendation +including (session-based and sequtial recommendation)

+
+
Parameters
+
    +
  • model (Model) – The Model defined using Transformers4Rec api.

  • +
  • args (T4RecTrainingArguments) – The training arguments needed to setup training and evaluation +experiments.

  • +
  • schema (Optional[Dataset.schema], optional) – The schema object including features to use and their properties. +by default None

  • +
  • train_dataset_or_path (Optional[Union[str, Dataset]], optional) – Path of parquet files or DataSet to use for training. +by default None

  • +
  • eval_dataset_or_path (Optional[str, Dataset], optional) – Path of parquet files or DataSet to use for evaluation. +by default None

  • +
  • train_dataloader (Optional[DataLoader], optional) – The data generator to use for training. +by default None

  • +
  • eval_dataloader (Optional[DataLoader], optional) – The data generator to use for evaluation. +by default None

  • +
  • compute_metrics (Optional[bool], optional) – Whether to compute metrics defined by Model class or not. +by default None

  • +
  • incremental_logging (bool) – Whether to enable incremental logging or not. If True, it ensures that +global steps are incremented over many trainer.train() calls, so that +train and eval metrics steps do not overlap and can be seen properly +in reports like W&B and Tensorboard

  • +
+
+
+
+
+get_train_dataloader()[source]
+

Set the train dataloader to use by Trainer. +It supports user defined data-loader set as an attribute in the constructor. +When the attribute is None, The data-loader is defined using train_dataset +and the data_loader_engine specified in Training Arguments.

+
+ +
+
+get_eval_dataloader(eval_dataset=None)[source]
+

Set the eval dataloader to use by Trainer. +It supports user defined data-loader set as an attribute in the constructor. +When the attribute is None, The data-loader is defined using eval_dataset +and the data_loader_engine specified in Training Arguments.

+
+ +
+
+get_test_dataloader(test_dataset=None)[source]
+

Set the test dataloader to use by Trainer. +It supports user defined data-loader set as an attribute in the constructor. +When the attribute is None, The data-loader is defined using test_dataset +and the data_loader_engine specified in Training Arguments.

+
+ +
+
+num_examples(dataloader: torch.utils.data.dataloader.DataLoader)[source]
+

Overriding Trainer.num_examples() method because +the data loaders for this project do not return the dataset size, +but the number of steps. So we estimate the dataset size here +by multiplying the number of steps * batch size

+
+ +
+
+reset_lr_scheduler()None[source]
+

Resets the LR scheduler of the previous Trainer.train() call, +so that a new LR scheduler one is created by the next Trainer.train() call. +This is important for LR schedules like get_linear_schedule_with_warmup() +which decays LR to 0 in the end of the train

+
+ +
+
+create_scheduler(num_training_steps: int, optimizer: Optional[torch.optim.optimizer.Optimizer] = None)[source]
+
+ +
+
+static get_scheduler(name: Union[str, transformers.trainer_utils.SchedulerType], optimizer: torch.optim.optimizer.Optimizer, num_warmup_steps: Optional[int] = None, num_training_steps: Optional[int] = None, num_cycles: Optional[int] = 0.5)[source]
+

Unified API to get any scheduler from its name.

+
+
Parameters
+
    +
  • name ((str or :obj:`SchedulerType)) – The name of the scheduler to use.

  • +
  • optimizer ((torch.optim.Optimizer)) – The optimizer that will be used during training.

  • +
  • num_warmup_steps ((int, optional)) – The number of warm-up steps to perform. This is not required by all schedulers +(hence the argument being optional), +the function will raise an error if it’s unset and the scheduler type requires it.

  • +
  • num_training_steps ((int, optional)) – The number of training steps to do. This is not required by all schedulers +(hence the argument being optional), +the function will raise an error if it’s unset and the scheduler type requires it.

  • +
  • num_cycles ((int, optional)) – The number of waves in the cosine schedule / +hard restarts to use for cosine scheduler

  • +
+
+
+
+ +
+
+compute_loss(model, inputs, return_outputs=False)[source]
+

Overriding Trainer.compute_loss() +To allow for passing the targets to the model’s forward method +How the loss is computed by Trainer. By default, all Transformers4Rec models return +a dictionary of three elements {‘loss’, ‘predictions’, and ‘labels}

+
+ +
+
+prediction_step(model: torch.nn.modules.module.Module, inputs: Dict[str, torch.Tensor], prediction_loss_only: bool, ignore_keys: Optional[List[str]] = None, training: bool = False, testing: bool = True)Tuple[Optional[float], Optional[torch.Tensor], Optional[torch.Tensor], Optional[Dict[str, Any]]][source]
+

Overriding Trainer.prediction_step() +to provide more flexibility to unpack results from the model, +like returning labels that are not exactly one input feature +model

+
+ +
+
+evaluation_loop(dataloader: torch.utils.data.dataloader.DataLoader, description: str, prediction_loss_only: Optional[bool] = None, ignore_keys: Optional[List[str]] = None, metric_key_prefix: Optional[str] = 'eval')transformers.trainer_utils.EvalLoopOutput[source]
+

Overriding Trainer.prediction_loop() +(shared by Trainer.evaluate() and Trainer.predict()) +to provide more flexibility to work with streaming metrics +(computed at each eval batch) and +to log with the outputs of the model +(e.g. prediction scores, prediction metadata, attention weights)

+
+
Parameters
+
    +
  • dataloader (DataLoader) – DataLoader object to use to iterate over evaluation data

  • +
  • description (str) – Parameter to describe the evaluation experiment. +e.g: Prediction, test

  • +
  • prediction_loss_only (Optional[bool]) – Whether or not to return the loss only. +by default None

  • +
  • ignore_keys (Optional[List[str]]) – Columns not accepted by the model.forward() method +are automatically removed. +by default None

  • +
  • metric_key_prefix (Optional[str]) – Prefix to use when logging evaluation metrics. +by default eval

  • +
+
+
+
+ +
+
+load_model_trainer_states_from_checkpoint(checkpoint_path, model=None)[source]
+

This method loads the checkpoints states of the model, trainer and random states. +If model is None the serialized model class is loaded from checkpoint. +It does not loads the optimizer and LR scheduler states (for that call trainer.train() +with resume_from_checkpoint argument for a complete load)

+
+
Parameters
+
    +
  • checkpoint_path (str) – Path to the checkpoint directory.

  • +
  • model (Optional[Model]) – Model class used by Trainer. by default None

  • +
+
+
+
+ +
+
+property log_predictions_callback
+
+ +
+
+log(logs: Dict[str, float])None[source]
+
+ +
+ +
+
+transformers4rec.torch.LabelSmoothCrossEntropyLoss(smoothing: float = 0.0, reduction: str = 'mean', **kwargs)[source]
+

Coss-entropy loss with label smoothing. +This is going to be deprecated. You should use torch.nn.CrossEntropyLoss() +directly that in recent PyTorch versions already supports label_smoothing arg

+
+
Parameters
+
    +
  • smoothing (float) – The label smoothing factor. Specify a value between 0 and 1.

  • +
  • reduction (str) – Specifies the reduction to apply to the output. +Specify one of none, sum, or mean.

  • +
  • from https (Adapted) –

  • +
+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.torch.model.html b/review/pr-767/api/transformers4rec.torch.model.html new file mode 100644 index 0000000000..ff2e3d5add --- /dev/null +++ b/review/pr-767/api/transformers4rec.torch.model.html @@ -0,0 +1,588 @@ + + + + + + transformers4rec.torch.model package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.torch.model package

+
+

Submodules

+
+
+

transformers4rec.torch.model.head module

+
+
+

transformers4rec.torch.model.model module

+
+
+

transformers4rec.torch.model.prediction_task module

+
+
+class transformers4rec.torch.model.prediction_task.BinaryClassificationPrepareBlock[source]
+

Bases: transformers4rec.torch.block.base.BuildableBlock

+

Prepares the output layer of the binary classification prediction task. +The output layer is a SequentialBlock of a torch linear +layer followed by a sigmoid activation and a squeeze operation.

+
+
+build(input_size)transformers4rec.torch.block.base.SequentialBlock[source]
+

Builds the output layer of binary classification based on the input_size.

+
+
Parameters
+

input_size (Tuple[int]) – The size of the input tensor, specifically the last dimension is +used for setting the input dimension of the linear layer.

+
+
Returns
+

A SequentialBlock consisting of a linear layer (with input dimension equal to the last +dimension of input_size), a sigmoid activation, and a squeeze operation.

+
+
Return type
+

SequentialBlock

+
+
+
+ +
+ +
+
+class transformers4rec.torch.model.prediction_task.BinaryClassificationTask(target_name: Optional[str] = None, task_name: Optional[str] = None, task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, loss=BCELoss(), metrics=(BinaryPrecision(), BinaryRecall(), BinaryAccuracy()), summary_type='first')[source]
+

Bases: transformers4rec.torch.model.base.PredictionTask

+

Returns a PredictionTask for binary classification.

+

Example usage:

+
# Define the input module to process the tabular input features.
+input_module = tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=max_sequence_length,
+    continuous_projection=d_model,
+    aggregation="concat",
+    masking=None,
+)
+
+# Define XLNetConfig class and set default parameters for HF XLNet config.
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=4, n_layer=2, total_seq_length=max_sequence_length
+)
+
+# Define the model block including: inputs, masking, projection and transformer block.
+body = tr.SequentialBlock(
+    input_module,
+    tr.MLPBlock([64]),
+    tr.TransformerBlock(
+        transformer_config,
+        masking=input_module.masking
+    )
+)
+
+# Define a head with BinaryClassificationTask.
+head = tr.Head(
+    body,
+    tr.BinaryClassificationTask(
+        "click",
+        summary_type="mean",
+        metrics=[
+            tm.Precision(task='binary'),
+            tm.Recall(task='binary'),
+            tm.Accuracy(task='binary'),
+            tm.F1Score(task='binary')
+        ]
+    ),
+    inputs=input_module,
+)
+
+# Get the end-to-end Model class.
+model = tr.Model(head)
+
+
+
+
Parameters
+
    +
  • target_name (Optional[str] = None) – Specifies the variable name that represents the positive and negative values.

  • +
  • task_name (Optional[str] = None) – Specifies the name of the prediction task. If this parameter is not specified, +a name is automatically constructed based on target_name and the Python +class name of the model.

  • +
  • task_block (Optional[BlockType] = None) – Specifies a module to transform the input tensor before computing predictions.

  • +
  • loss (torch.nn.Module) – Specifies the loss function for the task. +The default class is torch.nn.BCELoss.

  • +
  • metrics (Tuple[torch.nn.Module, ..]) – Specifies the metrics to calculate during training and evaluation. +The default metrics are Precision, Recall, and Accuracy.

  • +
  • summary_type (str) –

    Summarizes a sequence into a single tensor. Accepted values are:

    +
    +
      +
    • last – Take the last token hidden state (like XLNet)

    • +
    • first – Take the first token hidden state (like Bert)

    • +
    • mean – Take the mean of all tokens hidden states

    • +
    • cls_index – Supply a Tensor of classification token position (GPT/GPT-2)

    • +
    • attn – Not implemented now, use multi-head attention

    • +
    +
    +

  • +
+
+
+
+
+DEFAULT_LOSS = BCELoss()
+
+ +
+
+DEFAULT_METRICS = (BinaryPrecision(), BinaryRecall(), BinaryAccuracy())
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.model.prediction_task.RegressionPrepareBlock[source]
+

Bases: transformers4rec.torch.block.base.BuildableBlock

+

Prepares the output layer of the regression prediction task. +The output layer is a SequentialBlock of a torch linear +layer followed by a squeeze operation.

+
+
+build(input_size)transformers4rec.torch.block.base.SequentialBlock[source]
+

Builds the output layer of regression based on the input_size.

+
+
Parameters
+

input_size (Tuple[int]) – The size of the input tensor, specifically the last dimension is +used for setting the input dimension of the linear layer.

+
+
Returns
+

A SequentialBlock consisting of a linear layer (with input dimension equal to +the last dimension of input_size), and a squeeze operation.

+
+
Return type
+

SequentialBlock

+
+
+
+ +
+ +
+
+class transformers4rec.torch.model.prediction_task.RegressionTask(target_name: Optional[str] = None, task_name: Optional[str] = None, task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, loss=MSELoss(), metrics=(MeanSquaredError()), summary_type='first')[source]
+

Bases: transformers4rec.torch.model.base.PredictionTask

+

Returns a PredictionTask for regression.

+

Example usage:

+
# Define the input module to process the tabular input features.
+input_module = tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=max_sequence_length,
+    continuous_projection=d_model,
+    aggregation="concat",
+    masking=None,
+)
+
+# Define XLNetConfig class and set default parameters for HF XLNet config.
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=4, n_layer=2, total_seq_length=max_sequence_length
+)
+
+# Define the model block including: inputs, projection and transformer block.
+body = tr.SequentialBlock(
+    input_module,
+    tr.MLPBlock([64]),
+    tr.TransformerBlock(
+        transformer_config,
+    )
+)
+
+# Define a head with BinaryClassificationTask.
+head = tr.Head(
+    body,
+    tr.RegressionTask(
+        "watch_time",
+        summary_type="mean",
+        metrics=[tm.regression.MeanSquaredError()]
+    ),
+    inputs=input_module,
+)
+
+# Get the end-to-end Model class.
+model = tr.Model(head)
+
+
+
+
Parameters
+
    +
  • target_name (Optional[str]) – Specifies the variable name that represents the continuous value to predict. +By default None

  • +
  • task_name (Optional[str]) – Specifies the name of the prediction task. If this parameter is not specified, +a name is automatically constructed based on target_name and the Python +class name of the model. +By default None

  • +
  • task_block (Optional[BlockType] = None) – Specifies a module to transform the input tensor before computing predictions.

  • +
  • loss (torch.nn.Module) – Specifies the loss function for the task. +The default class is torch.nn.MSELoss.

  • +
  • metrics (Tuple[torch.nn.Module, ..]) – Specifies the metrics to calculate during training and evaluation. +The default metric is MeanSquaredError.

  • +
  • summary_type (str) –

    Summarizes a sequence into a single tensor. Accepted values are:

    +
    +
      +
    • last – Take the last token hidden state (like XLNet)

    • +
    • first – Take the first token hidden state (like Bert)

    • +
    • mean – Take the mean of all tokens hidden states

    • +
    • cls_index – Supply a Tensor of classification token position (GPT/GPT-2)

    • +
    • attn – Not implemented now, use multi-head attention

    • +
    +
    +

  • +
+
+
+
+
+DEFAULT_LOSS = MSELoss()
+
+ +
+
+DEFAULT_METRICS = (MeanSquaredError(),)
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.model.prediction_task.NextItemPredictionTask(loss: torch.nn.modules.module.Module = CrossEntropyLoss(), metrics: Iterable[torchmetrics.metric.Metric] = (NDCGAt(), AvgPrecisionAt(), RecallAt()), task_block: Optional[Union[transformers4rec.torch.block.base.BlockBase, transformers4rec.torch.block.base.BuildableBlock]] = None, task_name: str = 'next-item', weight_tying: bool = False, softmax_temperature: float = 1, padding_idx: int = 0, target_dim: Optional[int] = None, sampled_softmax: Optional[bool] = False, max_n_samples: Optional[int] = 100)[source]
+

Bases: transformers4rec.torch.model.base.PredictionTask

+

This block performs item prediction task for session and sequential-based models. +It requires a body containing a masking schema to use for training and target generation. +For the supported masking schemes, please refers to: +https://nvidia-merlin.github.io/Transformers4Rec/stable/model_definition.html#sequence-masking

+
+
Parameters
+
    +
  • loss (torch.nn.Module) – Loss function to use. Defaults to NLLLos.

  • +
  • metrics (Iterable[torchmetrics.Metric]) – List of ranking metrics to use for evaluation.

  • +
  • task_block – Module to transform input tensor before computing predictions.

  • +
  • task_name (str, optional) – Name of the prediction task, if not provided a name will be automatically constructed based +on the target-name & class-name.

  • +
  • weight_tying (bool) – The item id embedding table weights are shared with the prediction network layer.

  • +
  • softmax_temperature (float) – Softmax temperature, used to reduce model overconfidence, so that softmax(logits / T). +Value 1.0 reduces to regular softmax.

  • +
  • padding_idx (int) – pad token id.

  • +
  • target_dim (int) – vocabulary size of item ids

  • +
  • sampled_softmax (Optional[bool]) – Enables sampled softmax. By default False

  • +
  • max_n_samples (Optional[int]) – Number of samples for sampled softmax. By default 100

  • +
+
+
+
+
+DEFAULT_METRICS = (NDCGAt(), AvgPrecisionAt(), RecallAt())
+
+ +
+
+build(body, input_size, device=None, inputs=None, task_block=None, pre=None)[source]
+

Build method, this is called by the Head.

+
+ +
+
+forward(inputs: torch.Tensor, targets=None, training=False, testing=False, top_k=None, **kwargs)[source]
+
+ +
+
+remove_pad_3d(inp_tensor, non_pad_mask)[source]
+
+ +
+
+calculate_metrics(predictions, targets)Dict[str, torch.Tensor][source]
+
+ +
+
+compute_metrics()[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.model.prediction_task.NextItemPredictionPrepareBlock(target_dim: int, weight_tying: bool = False, item_embedding_table: Optional[torch.nn.modules.module.Module] = None, softmax_temperature: float = 0, sampled_softmax: Optional[bool] = False, max_n_samples: Optional[int] = 100, min_id: Optional[int] = 0)[source]
+

Bases: transformers4rec.torch.block.base.BuildableBlock

+

Prepares the output layer of the next item prediction task. +The output layer is a an instance of _NextItemPredictionTask class.

+
+
Parameters
+
    +
  • target_dim (int) – The output dimension for next-item predictions.

  • +
  • weight_tying (bool, optional) – If true, ties the weights of the prediction layer and the item embedding layer. +By default False.

  • +
  • item_embedding_table (torch.nn.Module, optional) – The module containing the item embedding table. +By default None.

  • +
  • softmax_temperature (float, optional) – The temperature to be applied to the softmax function. Defaults to 0.

  • +
  • sampled_softmax (bool, optional) – If true, sampled softmax is used for approximating the full softmax function. +By default False.

  • +
  • max_n_samples (int, optional) – The maximum number of samples when using sampled softmax. +By default 100.

  • +
  • min_id (int, optional) – The minimum value of the range for the log-uniform sampling. +By default 0.

  • +
+
+
+
+
+build(input_size)transformers4rec.torch.block.base.Block[source]
+

Builds the output layer of next-item prediction based on the input_size.

+
+
Parameters
+

input_size (Tuple[int]) – The size of the input tensor, specifically the last dimension is +used for setting the input dimension of the output layer.

+
+
Returns
+

an instance of _NextItemPredictionTask

+
+
Return type
+

Block[_NextItemPredictionTask]

+
+
+
+ +
+ +
+
+class transformers4rec.torch.model.prediction_task.LogUniformSampler(max_n_samples: int, max_id: int, min_id: Optional[int] = 0, unique_sampling: bool = True, n_samples_multiplier_before_unique: int = 2)[source]
+

Bases: torch.nn.modules.module.Module

+
+
+get_log_uniform_distr(max_id: int, min_id: int = 0)torch.Tensor[source]
+

Approximates the items frequency distribution with log-uniform probability distribution +with P(class) = (log(class + 2) - log(class + 1)) / log(max_id + 1). +It assumes item ids are sorted decreasingly by their frequency.

+
+
Parameters
+

max_id (int) – Maximum discrete value for sampling (e.g. cardinality of the item id)

+
+
Returns
+

Returns the log uniform probability distribution

+
+
Return type
+

torch.Tensor

+
+
+
+ +
+
+get_unique_sampling_distr(dist, n_sample)[source]
+

Returns the probability that each item is sampled at least once +given the specified number of trials. This is meant to be used when +self.unique_sampling == True. +That probability can be approximated by by 1 - (1 - p)^n +and we use a numerically stable version: -expm1(num_tries * log1p(-p))

+
+ +
+
+sample(labels: torch.Tensor)[source]
+

Sample negative samples and calculate their probabilities.

+

If unique_sampling==True, then only unique sampled items will be returned. +The actual # samples will vary from run to run if unique_sampling==True, +as sampling without replacement (torch.multinomial(…, replacement=False)) is slow, +so we use torch.multinomial(…, replacement=True).unique() +which doesn’t guarantee the same number of unique sampled items. +You can try to increase n_samples_multiplier_before_unique +to increase the chances to have more unique samples in that case.

+
+
Parameters
+

labels (torch.Tensor, dtype=torch.long, shape=(batch_size,)) – The input labels for which negative samples should be generated.

+
+
Returns
+

    +
  • neg_samples (torch.Tensor, dtype=torch.long, shape=(n_samples,)) – The unique negative samples drawn from the log-uniform distribution.

  • +
  • true_probs (torch.Tensor, dtype=torch.float32, shape=(batch_size,)) – The probabilities of the input labels according +to the log-uniform distribution (depends on self.unique_sampling choice).

  • +
  • samp_log_probs (torch.Tensor, dtype=torch.float32, shape=(n_samples,)) – The probabilities of the sampled negatives according +to the log-uniform distribution (depends on self.unique_sampling choice).

  • +
+

+
+
+
+ +
+
+forward(labels)[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.torch.tabular.html b/review/pr-767/api/transformers4rec.torch.tabular.html new file mode 100644 index 0000000000..3a32f5a27b --- /dev/null +++ b/review/pr-767/api/transformers4rec.torch.tabular.html @@ -0,0 +1,337 @@ + + + + + + transformers4rec.torch.tabular package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.torch.tabular package

+
+

Submodules

+
+
+

transformers4rec.torch.tabular.aggregation module

+
+
+class transformers4rec.torch.tabular.aggregation.ConcatFeatures(*args, **kwargs)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularAggregation

+

Aggregation by stacking all values in TabularData, all non-sequential values will be +converted to a sequence.

+

The output of this concatenation will have 3 dimensions.

+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.tabular.aggregation.StackFeatures(axis: int = - 1)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularAggregation

+

Aggregation by stacking all values in input dictionary in the given dimension.

+
+
Parameters
+

axis (int, default=-1) – Axis to use for the stacking operation.

+
+
+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.tabular.aggregation.ElementwiseFeatureAggregation(*args, **kwargs)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularAggregation

+

Base class for aggregation methods that aggregates +features element-wise. +It implements two check methods to ensure inputs have the correct shape.

+
+ +
+
+class transformers4rec.torch.tabular.aggregation.ElementwiseSum[source]
+

Bases: transformers4rec.torch.tabular.aggregation.ElementwiseFeatureAggregation

+

Aggregation by first stacking all values in TabularData in the first dimension, and then +summing the result.

+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.tabular.aggregation.ElementwiseSumItemMulti(schema: Optional[merlin_standard_lib.schema.schema.Schema] = None)[source]
+

Bases: transformers4rec.torch.tabular.aggregation.ElementwiseFeatureAggregation

+

Aggregation by applying the ElementwiseSum aggregation to all features except the item-id, +and then multiplying this with the item-ids.

+
+
Parameters
+

schema (DatasetSchema) –

+
+
+
+
+forward(inputs: Dict[str, torch.Tensor])torch.Tensor[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+REQUIRES_SCHEMA = True
+
+ +
+
+training: bool
+
+ +
+ +
+
+

transformers4rec.torch.tabular.tabular module

+
+
+

transformers4rec.torch.tabular.transformations module

+
+
+class transformers4rec.torch.tabular.transformations.StochasticSwapNoise(schema=None, pad_token=0, replacement_prob=0.1)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularTransformation

+

Applies Stochastic replacement of sequence features. +It can be applied as a pre transform like TransformerBlock(pre=”stochastic-swap-noise”)

+
+
+forward(inputs: Union[torch.Tensor, Dict[str, torch.Tensor]], input_mask: Optional[torch.Tensor] = None, **kwargs)Union[torch.Tensor, Dict[str, torch.Tensor]][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+augment(input_tensor: torch.Tensor, mask: Optional[torch.Tensor] = None)torch.Tensor[source]
+
+ +
+ +
+
+class transformers4rec.torch.tabular.transformations.TabularLayerNorm(features_dim: Optional[Dict[str, int]] = None)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularTransformation

+

Applies Layer norm to each input feature individually, before the aggregation

+
+
+classmethod from_feature_config(feature_config: Dict[str, transformers4rec.torch.features.embedding.FeatureConfig])[source]
+
+ +
+
+forward(inputs: Dict[str, torch.Tensor], **kwargs)Dict[str, torch.Tensor][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+
+build(input_size, **kwargs)[source]
+
+ +
+ +
+
+class transformers4rec.torch.tabular.transformations.TabularDropout(dropout_rate=0.0)[source]
+

Bases: transformers4rec.torch.tabular.base.TabularTransformation

+

Applies dropout transformation.

+
+
+forward(inputs: Union[torch.Tensor, Dict[str, torch.Tensor]], **kwargs)Union[torch.Tensor, Dict[str, torch.Tensor]][source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.torch.utils.html b/review/pr-767/api/transformers4rec.torch.utils.html new file mode 100644 index 0000000000..6268c27d0d --- /dev/null +++ b/review/pr-767/api/transformers4rec.torch.utils.html @@ -0,0 +1,1053 @@ + + + + + + transformers4rec.torch.utils package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.torch.utils package

+
+

Submodules

+
+
+

transformers4rec.torch.utils.data_utils module

+
+
+class transformers4rec.torch.utils.data_utils.T4RecDataLoader[source]
+

Bases: abc.ABC

+

Base Helper class to build dataloader from the schema with properties +required by T4Rec Trainer class.

+
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, paths_or_dataset, batch_size, max_sequence_length, **kwargs)[source]
+
+ +
+
+set_dataset(paths_or_dataset)[source]
+
+ +
+
+classmethod parse(class_or_str)[source]
+
+ +
+ +
+
+class transformers4rec.torch.utils.data_utils.PyarrowDataLoader(paths_or_dataset, batch_size, max_sequence_length, cols_to_read=None, target_names=None, shuffle=False, shuffle_buffer_size=0, num_workers=1, pin_memory=True, drop_last=False, **kwargs)[source]
+

Bases: Generic[torch.utils.data.dataloader.T_co]

+
+
+batch_size: Optional[int]
+
+ +
+
+num_workers: int
+
+ +
+
+pin_memory: bool
+
+ +
+
+drop_last: bool
+
+ +
+
+set_dataset(cols_to_read, target_names)[source]
+

set the Parquet dataset

+
+
Parameters
+

cols_to_read (str) – The list of features names to load

+
+
+
+ +
+
+classmethod from_schema(schema, paths_or_dataset, batch_size, max_sequence_length, continuous_features=None, categorical_features=None, targets=None, shuffle=False, shuffle_buffer_size=0, num_workers=1, pin_memory=True, **kwargs)[source]
+

Instantiates PyarrowDataLoader from a DatasetSchema.

+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • paths_or_dataset (Union[str, Dataset]) – Path to paquet data of Dataset object.

  • +
  • batch_size (int) – batch size of Dataloader.

  • +
  • max_sequence_length (int) – The maximum length of list features.

  • +
+
+
+
+ +
+
+dataset: torch.utils.data.dataset.Dataset[T_co]
+
+ +
+
+timeout: float
+
+ +
+
+sampler: Union[torch.utils.data.sampler.Sampler, Iterable]
+
+ +
+
+pin_memory_device: str
+
+ +
+
+prefetch_factor: Optional[int]
+
+ +
+ +
+
+class transformers4rec.torch.utils.data_utils.DLDataLoader(*args, **kwargs)[source]
+

Bases: Generic[torch.utils.data.dataloader.T_co]

+

This class is an extension of the torch dataloader. +It is required to support the FastAI framework.

+

Setting the batch size directly to DLDataLoader makes it 3x slower. +So we set as an alternative attribute and use it within +T4Rec Trainer during evaluation +# TODO : run experiments with new merlin-dataloader

+
+
+property device
+
+ +
+
+dataset: torch.utils.data.dataset.Dataset[T_co]
+
+ +
+
+batch_size: Optional[int]
+
+ +
+
+num_workers: int
+
+ +
+
+pin_memory: bool
+
+ +
+
+drop_last: bool
+
+ +
+
+timeout: float
+
+ +
+
+sampler: Union[torch.utils.data.sampler.Sampler, Iterable]
+
+ +
+
+pin_memory_device: str
+
+ +
+
+prefetch_factor: Optional[int]
+
+ +
+ +
+
+class transformers4rec.torch.utils.data_utils.MerlinDataLoader(paths_or_dataset, batch_size, max_sequence_length=None, conts=None, cats=None, labels=None, lists=None, collate_fn=<function MerlinDataLoader.<lambda>>, engine=None, buffer_size=0.1, reader_kwargs=None, shuffle=False, seed_fn=None, parts_per_chunk=1, device=None, global_size=None, global_rank=None, drop_last=False, schema=None, row_groups_per_part=True, transforms=None, **kwargs)[source]
+

Bases: Generic[torch.utils.data.dataloader.T_co]

+

This class extends the [Merlin data loader] +(https://github.com/NVIDIA-Merlin/dataloader/blob/stable/merlin/dataloader/torch.py). +The data input requires a merlin.io.Dataset or a path to the data files. +It also sets the dataset’s schema with the necessary properties to prepare the input +list features as dense tensors (i.e. padded to the specified max_sequence_length). +The dense representation is required by the Transformers4Rec input modules.

+
+
Parameters
+
    +
  • paths_or_dataset (Union[str, merlin.io.Dataset]) – The dataset to load.

  • +
  • batch_size (int) – The size of each batch to supply to the model.

  • +
  • max_sequence_length (int) – The maximum sequence length to use for padding list columns. +By default, 0 is used as the padding index.

  • +
  • cats (List[str], optional) – The list of categorical columns in the dataset. +By default None.

  • +
  • conts (List[str], optional) – The list of continuous columns in the dataset. +By default None.

  • +
  • labels (List[str], optional) – The list of label columns in the dataset. +By default None.

  • +
  • lists (List[str], optional) – The list of sequential columns in the dataset. +By default None.

  • +
  • shuffle (bool, optional) – Enable/disable shuffling of dataset. +By default False.

  • +
  • parts_per_chunk (int) – The number of partitions from the iterator, an Merlin Dataset, +to concatenate into a “chunk”. By default 1.

  • +
  • device (int, optional) – The device id of the selected GPU +By default None.

  • +
  • drop_last (bool, optional) – Whether or not to drop the last batch in an epoch. This is useful when you need to +guarantee that each batch contains exactly batch_size rows - since the last batch +will usually contain fewer rows.

  • +
  • seed_fn (callable) – Function used to initialize random state

  • +
  • parts_per_chunk – Number of dataset partitions with size dictated by buffer_size +to load and concatenate asynchronously. More partitions leads to +better epoch-level randomness but can negatively impact throughput

  • +
  • global_size (int, optional) – When doing distributed training, this indicates the number of total processes that are +training the model.

  • +
  • global_rank (int, optional) – When doing distributed training, this indicates the local rank for the current process.

  • +
  • schema (Schema, optional) – The Schema with the input features.

  • +
  • reader_kwargs – Extra arguments to pass to the merlin.io.Dataset object, when the path to data files +is provided in paths_or_dataset argument.

  • +
  • row_groups_per_part (bool, optional) – If true, preserve the group partitions when loading the dataset from parquet files.

  • +
  • collate_fn (Callable, optional) – A processing function to collect and prepare the list samples +(tuple of (input, target) Tensor(s)) returned by the Merlin DataLoader.

  • +
  • transforms (List[merlin.dag.BaseOperator]) – A list of operators that the Merlin dataloader applies on top of the loaded +batch, which is a tuple of input and target tensors.

  • +
+
+
+
+
+batch_size: Optional[int]
+
+ +
+
+drop_last: bool
+
+ +
+
+dataset: torch.utils.data.dataset.Dataset[T_co]
+
+ +
+
+set_dataset(buffer_size, engine, reader_kwargs, schema=None)[source]
+
+ +
+
+classmethod from_schema(schema: merlin_standard_lib.schema.schema.Schema, paths_or_dataset, batch_size, max_sequence_length=None, continuous_features=None, categorical_features=None, list_features=None, targets=None, collate_fn=<function MerlinDataLoader.<lambda>>, shuffle=True, buffer_size=0.06, parts_per_chunk=1, transforms=None, **kwargs)[source]
+
+

Instantitates MerlinDataLoader from a DatasetSchema.

+
+
+
Parameters
+
    +
  • schema (DatasetSchema) – Dataset schema

  • +
  • paths_or_dataset (Union[str, Dataset]) – Path to paquet data of Dataset object.

  • +
  • batch_size (int) – batch size of Dataloader.

  • +
+
+
+
+ +
+
+property output_schema
+
+ +
+
+num_workers: int
+
+ +
+
+pin_memory: bool
+
+ +
+
+timeout: float
+
+ +
+
+sampler: Union[torch.utils.data.sampler.Sampler, Iterable]
+
+ +
+
+pin_memory_device: str
+
+ +
+
+prefetch_factor: Optional[int]
+
+ +
+ +
+
+class transformers4rec.torch.utils.data_utils.ParquetDataset(parquet_file, cols_to_read, target_names, seq_features_len_pad_trim)[source]
+

Bases: Generic[torch.utils.data.dataset.T_co]

+
+
+pad_seq_column_if_needed(values)[source]
+
+ +
+ +
+
+class transformers4rec.torch.utils.data_utils.ShuffleDataset(dataset, buffer_size)[source]
+

Bases: torch.utils.data.dataset.Dataset[torch.utils.data.dataset.T_co]

+
+ +
+
+transformers4rec.torch.utils.data_utils.to_core_schema(t4rec_schema)[source]
+
+ +
+
+

transformers4rec.torch.utils.examples_utils module

+
+
+transformers4rec.torch.utils.examples_utils.list_files(startpath)[source]
+

Util function to print the nested structure of a directory

+
+ +
+
+transformers4rec.torch.utils.examples_utils.visualize_response(batch, response, top_k, session_col='session_id')[source]
+

Util function to extract top-k encoded item-ids from logits

+
+
Parameters
+
    +
  • batch (cudf.DataFrame) – the batch of raw data sent to triton server.

  • +
  • response (tritonclient.grpc.InferResult) – the response returned by grpc client.

  • +
  • top_k (int) – the top_k top items to retrieve from predictions.

  • +
+
+
+
+ +
+
+transformers4rec.torch.utils.examples_utils.fit_and_evaluate(trainer, start_time_index, end_time_index, input_dir)[source]
+

Util function for time-window based fine-tuning using the T4rec Trainer class. +Iteratively train using data of a given index and evaluate on the validation data +of the following index.

+
+
Parameters
+
    +
  • start_time_index (int) – The start index for training, it should match the partitions of the data directory

  • +
  • end_time_index (int) – The end index for training, it should match the partitions of the data directory

  • +
  • input_dir (str) – The input directory where the parquet files were saved based on partition column

  • +
+
+
Returns
+

indexed_by_time_metrics – The dictionary of ranking metrics: each item is the list of scores over time indices.

+
+
Return type
+

dict

+
+
+
+ +
+
+transformers4rec.torch.utils.examples_utils.wipe_memory()[source]
+
+ +
+
+

transformers4rec.torch.utils.schema_utils module

+
+
+transformers4rec.torch.utils.schema_utils.random_data_from_schema(schema: merlin_standard_lib.schema.schema.Schema, num_rows: int, max_session_length: Optional[int] = None, min_session_length: int = 5, device=None, ragged=False, seed=0)Dict[str, torch.Tensor][source]
+

Generates random tabular data based on a given schema. +The generated data can be used for testing +data preprocessing or model training pipelines.

+
+
Parameters
+
    +
  • schema (Schema) – The schema to be used for generating the random tabular data.

  • +
  • num_rows (int) – The number of rows.

  • +
  • max_session_length (Optional[int]) – The maximum session length. +If None, the session length will not be limited. +By default None

  • +
  • min_session_length (int) – The minimum session length. +By default 5

  • +
  • device (torch.device) – The device on which the synthetic data should be created. +If None, the synthetic data will be created on the CPU. +By default None

  • +
  • ragged (bool) – If True, the sequence features will be represented with __values and __offsets. +By default False

  • +
+
+
Returns
+

A dictionary where each key is a feature name and each value is the generated +tensor.

+
+
Return type
+

TabularData

+
+
+
+ +
+
+

transformers4rec.torch.utils.torch_utils module

+
+
+class transformers4rec.torch.utils.torch_utils.OutputSizeMixin[source]
+

Bases: transformers4rec.config.schema.SchemaMixin, abc.ABC

+
+
+build(input_size, schema=None, **kwargs)[source]
+
+ +
+
+output_size(input_size=None)[source]
+
+ +
+
+forward_output_size(input_size)[source]
+
+ +
+ +
+
+class transformers4rec.torch.utils.torch_utils.LossMixin[source]
+

Bases: object

+

Mixin to use for a torch.Module that can calculate a loss.

+
+
+compute_loss(inputs: Union[torch.Tensor, Dict[str, torch.Tensor]], targets: Union[torch.Tensor, Dict[str, torch.Tensor]], compute_metrics: bool = True, **kwargs)torch.Tensor[source]
+

Compute the loss on a batch of data.

+
+
Parameters
+
    +
  • inputs (Union[torch.Tensor, TabularData]) – TODO

  • +
  • targets (Union[torch.Tensor, TabularData]) – TODO

  • +
  • compute_metrics (bool, default=True) – Boolean indicating whether or not to update the state of the metrics +(if they are defined).

  • +
+
+
+
+ +
+ +
+
+class transformers4rec.torch.utils.torch_utils.MetricsMixin[source]
+

Bases: object

+

Mixin to use for a torch.Module that can calculate metrics.

+
+
+calculate_metrics(inputs: Union[torch.Tensor, Dict[str, torch.Tensor]], targets: Union[torch.Tensor, Dict[str, torch.Tensor]])Dict[str, torch.Tensor][source]
+

Calculate metrics on a batch of data, each metric is stateful and this updates the state.

+

The state of each metric can be retrieved by calling the compute_metrics method.

+
+
Parameters
+
    +
  • inputs (Union[torch.Tensor, TabularData]) – Tensor or dictionary of predictions returned by the T4Rec model

  • +
  • targets (Union[torch.Tensor, TabularData]) – Tensor or dictionary of true labels returned by the T4Rec model

  • +
+
+
+
+ +
+
+compute_metrics(mode: Optional[str] = None)Dict[str, Union[float, torch.Tensor]][source]
+

Returns the current state of each metric.

+

The state is typically updated each batch by calling the calculate_metrics method.

+
+
Parameters
+

mode (str, default="val") –

+
+
Returns
+

+
+
Return type
+

Dict[str, Union[float, torch.Tensor]]

+
+
+
+ +
+
+reset_metrics()[source]
+

Reset all metrics.

+
+ +
+ +
+
+transformers4rec.torch.utils.torch_utils.requires_schema(module)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.check_gpu(module)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.get_output_sizes_from_schema(schema: merlin_standard_lib.schema.schema.Schema, batch_size=- 1, max_sequence_length=None)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.calculate_batch_size_from_input_size(input_size)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.check_inputs(ks, scores, labels)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.extract_topk(ks, scores, labels)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.create_output_placeholder(scores, ks)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.tranform_label_to_onehot(labels, vocab_size)[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.nested_detach(tensors)[source]
+

Detach tensors (even if it’s a nested list/tuple/dict of tensors). +#TODO this method was copied from the latest version of HF transformers library to support +dict outputs. So we should remove it when T4Rec is updated to use the latest version

+
+ +
+
+transformers4rec.torch.utils.torch_utils.nested_concat(tensors, new_tensors, padding_index=- 100)[source]
+

Concat the new_tensors to tensors on the first dim and pad them on the second if needed. +Works for tensors or nested list/tuples/dict of tensors. +#TODO this method was copied from the latest version of HF transformers library to support +dict outputs. So we should remove it when T4Rec is updated to use the latest version

+
+ +
+
+transformers4rec.torch.utils.torch_utils.torch_pad_and_concatenate(tensor1, tensor2, padding_index=- 100)[source]
+

Concatenates tensor1 and tensor2 on first axis, applying padding on the second as needed

+

#TODO this method was copied from the latest version of HF transformers library to support +dict outputs. So we should remove it when T4Rec is updated to use the latest version

+
+ +
+
+transformers4rec.torch.utils.torch_utils.atleast_1d(tensor_or_array: Union[torch.Tensor, numpy.ndarray])[source]
+
+ +
+
+transformers4rec.torch.utils.torch_utils.nested_numpify(tensors)[source]
+

Numpify tensors (even if it’s a nested list/tuple/dict of tensors). +#TODO this method was copied from the latest version of HF transformers library to support +dict outputs. So we should remove it when T4Rec is updated to use the latest version

+
+ +
+
+transformers4rec.torch.utils.torch_utils.nested_truncate(tensors, limit)[source]
+

Truncate tensors at limit (even if it’s a nested list/tuple/dict of tensors). +#TODO this method was copied from the latest version of HF transformers library to support +dict outputs. So we should remove it when T4Rec is updated to use the latest version

+
+ +
+
+transformers4rec.torch.utils.torch_utils.numpy_pad_and_concatenate(array1, array2, padding_index=- 100)[source]
+

Concatenates array1 and array2 on first axis, applying padding on the second if necessary. +#TODO this method was copied from the latest version of HF transformers library to support +dict outputs. So we should remove it when T4Rec is updated to use the latest version

+
+ +
+
+transformers4rec.torch.utils.torch_utils.one_hot_1d(labels: torch.Tensor, num_classes: int, device: Optional[torch.device] = None, dtype: Optional[torch.dtype] = torch.float32)torch.Tensor[source]
+

Coverts a 1d label tensor to one-hot representation

+
+
Parameters
+
    +
  • labels (torch.Tensor) – tensor with labels of shape \((N, H, W)\), +where N is batch size. Each value is an integer +representing correct classification.

  • +
  • num_classes (int) – number of classes in labels.

  • +
  • device (Optional[torch.device]) – the desired device of returned tensor. +Default: if None, uses the current device for the default tensor type +(see torch.set_default_tensor_type()). device will be the CPU for CPU +tensor types and the current CUDA device for CUDA tensor types.

  • +
  • dtype (Optional[torch.dtype]) – the desired data type of returned +tensor. Default: torch.float32

  • +
+
+
Returns
+

the labels in one hot tensor.

+
+
Return type
+

torch.Tensor

+
+
+
+
Examples::
>>> labels = torch.LongTensor([0, 1, 2, 0])
+>>> one_hot_1d(labels, num_classes=3)
+tensor([[1., 0., 0.],
+        [0., 1., 0.],
+        [0., 0., 1.],
+        [1., 0., 0.],
+       ])
+
+
+
+
+
+ +
+
+class transformers4rec.torch.utils.torch_utils.LambdaModule(lambda_fn)[source]
+

Bases: torch.nn.modules.module.Module

+
+
+forward(x)[source]
+
+ +
+
+training: bool
+
+ +
+ +
+
+class transformers4rec.torch.utils.torch_utils.MappingTransformerMasking[source]
+

Bases: object

+
+
+class CausalLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, train_on_last_item_seq_only: bool = False, **kwargs)
+

Bases: transformers4rec.torch.masking.MaskSequence

+

In Causal Language Modeling (clm) you predict the next item based on past positions of the +sequence. Future positions are masked.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • train_on_last_item_seq_only (predict only last item during training) –

  • +
+
+
+
+
+apply_mask_to_inputs(inputs: torch.Tensor, mask_schema: torch.Tensor, training: bool = False, testing: bool = False)torch.Tensor
+
+ +
+ +
+
+class MaskedLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, mlm_probability: float = 0.15, **kwargs)
+

Bases: transformers4rec.torch.masking.MaskSequence

+

In Masked Language Modeling (mlm) you randomly select some positions of the sequence to be +predicted, which are masked. +During training, the Transformer layer is allowed to use positions on the right (future info). +During inference, all past items are visible for the Transformer layer, which tries to predict +the next item.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • mlm_probability (Optional[float], default = 0.15) – Probability of an item to be selected (masked) as a label of the given sequence. +p.s. We enforce that at least one item is masked for each sequence, so that the network can +learn something with it.

  • +
+
+
+
+
+apply_mask_to_inputs(inputs: torch.Tensor, mask_schema: torch.Tensor, training=False, testing=False)torch.Tensor
+
+

Control the masked positions in the inputs by replacing the true interaction +by a learnable masked embedding.

+
+
inputs: torch.Tensor

The 3-D tensor of interaction embeddings resulting from the ops: +TabularFeatures + aggregation + projection(optional)

+
+
schema: MaskingSchema

The boolean mask indicating masked positions.

+
+
+
+
+
training: bool

Flag to indicate whether we are in Training mode or not. +During training, the labels can be any items within the sequence +based on the selected masking task.

+
+
testing: bool

Flag to indicate whether we are in Evaluation (=True) +or Inference (=False) mode. +During evaluation, we are predicting all next items or last item only +in the sequence based on the param eval_on_last_item_seq_only. +During inference, we don’t mask the input sequence and use all available +information to predict the next item.

+
+
+
+ +
+ +
+
+class PermutationLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, plm_probability: float = 0.16666666666666666, max_span_length: int = 5, permute_all: bool = False, **kwargs)
+

Bases: transformers4rec.torch.masking.MaskSequence

+

In Permutation Language Modeling (plm) you use a permutation factorization at the level of the +self-attention layer to define the accessible bidirectional context.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • max_span_length (int) – maximum length of a span of masked items

  • +
  • plm_probability (float) – The ratio of surrounding items to unmask to define the context of the span-based +prediction segment of items

  • +
  • permute_all (bool) – Compute partial span-based prediction (=False) or not.

  • +
+
+
+
+
+compute_masked_targets(item_ids: torch.Tensor, training=False, **kwargs)transformers4rec.torch.masking.MaskingInfo
+
+ +
+
+transformer_required_arguments()Dict[str, Any]
+
+ +
+ +
+
+class ReplacementLanguageModeling(hidden_size: int, padding_idx: int = 0, eval_on_last_item_seq_only: bool = True, sample_from_batch: bool = False, **kwargs)
+

Bases: transformers4rec.torch.masking.MaskedLanguageModeling

+

Replacement Language Modeling (rtd) you use MLM to randomly select some items, but replace +them by random tokens. +Then, a discriminator model (that can share the weights with the generator or not), is asked +to classify whether the item at each position belongs or not to the original sequence. +The generator-discriminator architecture was jointly trained using Masked LM and RTD tasks.

+
+
Parameters
+
    +
  • hidden_size (int) – The hidden dimension of input tensors, needed to initialize trainable vector of masked +positions.

  • +
  • padding_idx (int, default = 0) – Index of padding item used for getting batch of sequences with the same length

  • +
  • eval_on_last_item_seq_only (bool, default = True) – Predict only last item during evaluation

  • +
  • sample_from_batch (bool) – Whether to sample replacement item ids from the same batch or not

  • +
+
+
+
+
+get_fake_tokens(itemid_seq, target_flat, logits)
+

Second task of RTD is binary classification to train the discriminator. +The task consists of generating fake data by replacing [MASK] positions with random items, +ELECTRA discriminator learns to detect fake replacements.

+
+
Parameters
+
    +
  • itemid_seq (torch.Tensor of shape (bs, max_seq_len)) – input sequence of item ids

  • +
  • target_flat (torch.Tensor of shape (bs*max_seq_len)) – flattened masked label sequences

  • +
  • logits (torch.Tensor of shape (#pos_item, vocab_size or #pos_item),) – mlm probabilities of positive items computed by the generator model. +The logits are over the whole corpus if sample_from_batch = False, +over the positive items (masked) of the current batch otherwise

  • +
+
+
Returns
+

    +
  • corrupted_inputs (torch.Tensor of shape (bs, max_seq_len)) – input sequence of item ids with fake replacement

  • +
  • discriminator_labels (torch.Tensor of shape (bs, max_seq_len)) – binary labels to distinguish between original and replaced items

  • +
  • batch_updates (torch.Tensor of shape (#pos_item)) – the indices of replacement item within the current batch if sample_from_batch is enabled

  • +
+

+
+
+
+ +
+
+sample_from_softmax(logits: torch.Tensor)torch.Tensor
+

Sampling method for replacement token modeling (ELECTRA)

+
+
Parameters
+

logits (torch.Tensor(pos_item, vocab_size)) – scores of probability of masked positions returned by the generator model

+
+
Returns
+

samples – ids of replacements items.

+
+
Return type
+

torch.Tensor(#pos_item)

+
+
+
+ +
+ +
+
+DEFAULT_MASKING = [<class 'transformers4rec.torch.masking.CausalLanguageModeling'>, <class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>, <class 'transformers4rec.torch.masking.PermutationLanguageModeling'>]
+
+ +
+
+BertConfig = [<class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+ConvBertConfig = [<class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+DebertaConfig = [<class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+DistilBertConfig = [<class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+GPT2Config = [<class 'transformers4rec.torch.masking.CausalLanguageModeling'>]
+
+ +
+
+LongformerConfig = [<class 'transformers4rec.torch.masking.CausalLanguageModeling'>, <class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+MegatronBertConfig = [<class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+MPNetConfig = [<class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+RobertaConfig = [<class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+RoFormerConfig = [<class 'transformers4rec.torch.masking.CausalLanguageModeling'>, <class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>]
+
+ +
+
+TransfoXLConfig = [<class 'transformers4rec.torch.masking.CausalLanguageModeling'>]
+
+ +
+
+XLNetConfig = [<class 'transformers4rec.torch.masking.CausalLanguageModeling'>, <class 'transformers4rec.torch.masking.MaskedLanguageModeling'>, <class 'transformers4rec.torch.masking.ReplacementLanguageModeling'>, <class 'transformers4rec.torch.masking.PermutationLanguageModeling'>]
+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/api/transformers4rec.utils.html b/review/pr-767/api/transformers4rec.utils.html new file mode 100644 index 0000000000..210252d9c3 --- /dev/null +++ b/review/pr-767/api/transformers4rec.utils.html @@ -0,0 +1,170 @@ + + + + + + transformers4rec.utils package — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

transformers4rec.utils package

+
+

Submodules

+
+
+

transformers4rec.utils.dependencies module

+
+
+transformers4rec.utils.dependencies.is_gpu_dataloader_available()bool[source]
+
+ +
+
+transformers4rec.utils.dependencies.is_pyarrow_available()bool[source]
+
+ +
+
+transformers4rec.utils.dependencies.is_merlin_dataloader_available()bool[source]
+
+ +
+
+

Module contents

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/end-to-end-session-based/01-ETL-with-NVTabular.html b/review/pr-767/examples/end-to-end-session-based/01-ETL-with-NVTabular.html new file mode 100644 index 0000000000..8d57d92034 --- /dev/null +++ b/review/pr-767/examples/end-to-end-session-based/01-ETL-with-NVTabular.html @@ -0,0 +1,839 @@ + + + + + + ETL with NVTabular — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_end-to-end-session-based-01-etl-with-nvtabular/nvidia_logo.png +
+

ETL with NVTabular

+

This notebook is created using the latest stable merlin-pytorch container.

+

Launch the docker container

+
docker run -it --gpus device=0 -p 8000:8000 -p 8001:8001 -p 8002:8002 -p 8888:8888 -v <path_to_data>:/workspace/data/  nvcr.io/nvidia/merlin/merlin-pytorch:23.XX
+
+
+

This script will mount your local data folder that includes your data files to /workspace/data directory in the merlin-pytorch docker container.

+
+

Overview

+

This notebook demonstrates how to use NVTabular to perform the feature engineering that is needed to model the YOOCHOOSE dataset which contains a collection of sessions from a retailer. Each session encapsulates the click events that the user performed in that session.

+

The dataset is available on Kaggle. You need to download it and copy to the DATA_FOLDER path. Note that we are only using the yoochoose-clicks.dat file.

+

Alternatively, you can generate a synthetic dataset with the same columns and dtypes as the YOOCHOOSE dataset and a default date range of 5 days. If the environment variable USE_SYNTHETIC is set to True, the code below will execute the function generate_synthetic_data and the rest of the notebook will run on a synthetic dataset.

+

First, let’s start by importing several libraries:

+
+
+
import os
+import glob
+import numpy as np
+import pandas as pd
+import gc
+import calendar
+import datetime
+
+import cudf
+import cupy
+import nvtabular as nvt
+from merlin.dag import ColumnSelector
+from merlin.schema import Schema, Tags
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+
+
+
+
+

Avoid Numba low occupancy warnings:

+
+
+
from numba import config
+config.CUDA_LOW_OCCUPANCY_WARNINGS = 0
+
+
+
+
+
+

Define Data Input and Output Paths

+
+
+
DATA_FOLDER = os.environ.get("DATA_FOLDER", "/workspace/data")
+FILENAME_PATTERN = 'yoochoose-clicks.dat'
+DATA_PATH = os.path.join(DATA_FOLDER, FILENAME_PATTERN)
+
+OUTPUT_FOLDER = "./yoochoose_transformed"
+OVERWRITE = False
+
+USE_SYNTHETIC = os.environ.get("USE_SYNTHETIC", False)
+
+
+
+
+
+
+
+

Load and clean raw data

+

Execute the cell below if you would like to work with synthetic data. Otherwise you can skip and continue with the next cell.

+
+
+
def generate_synthetic_data(
+    start_date: datetime.date, end_date: datetime.date, rows_per_day: int = 10000
+) -> pd.DataFrame:
+    assert end_date > start_date, "end_date must be later than start_date"
+
+    number_of_days = (end_date - start_date).days
+    total_number_of_rows = number_of_days * rows_per_day
+
+    # Generate a long-tail distribution of item interactions. This simulates that some items are
+    # more popular than others.
+    long_tailed_item_distribution = np.clip(
+        np.random.lognormal(3.0, 1.0, total_number_of_rows).astype(np.int64), 1, 50000
+    )
+
+    # generate random item interaction features
+    df = pd.DataFrame(
+        {
+            "session_id": np.random.randint(70000, 80000, total_number_of_rows),
+            "item_id": long_tailed_item_distribution,
+        },
+    )
+
+    # generate category mapping for each item-id
+    df["category"] = pd.cut(df["item_id"], bins=334, labels=np.arange(1, 335)).astype(
+        np.int64
+    )
+
+    max_session_length = 60 * 60  # 1 hour
+
+    def add_timestamp_to_session(session: pd.DataFrame):
+        random_start_date_and_time = calendar.timegm(
+            (
+                start_date
+                # Add day offset from start_date
+                + datetime.timedelta(days=np.random.randint(0, number_of_days))
+                # Add time offset within the random day
+                + datetime.timedelta(seconds=np.random.randint(0, 86_400))
+            ).timetuple()
+        )
+        session["timestamp"] = random_start_date_and_time + np.clip(
+            np.random.lognormal(3.0, 1.0, len(session)).astype(np.int64),
+            0,
+            max_session_length,
+        )
+        return session
+
+    df = df.groupby("session_id").apply(add_timestamp_to_session).reset_index()
+
+    return df
+
+
+
+
+
+
+
if USE_SYNTHETIC:
+    START_DATE = os.environ.get("START_DATE", "2014/4/1")
+    END_DATE = os.environ.get("END_DATE", "2014/4/5")
+    interactions_df = generate_synthetic_data(datetime.datetime.strptime(START_DATE, '%Y/%m/%d'),
+                                              datetime.datetime.strptime(END_DATE, '%Y/%m/%d'))
+    interactions_df = cudf.from_pandas(interactions_df)
+else:
+    interactions_df = cudf.read_csv(DATA_PATH, sep=',', 
+                                    names=['session_id','timestamp', 'item_id', 'category'], 
+                                    dtype=['int', 'datetime64[s]', 'int', 'int'])
+
+
+
+
+
+

Remove repeated interactions within the same session

+
+
+
print("Count with in-session repeated interactions: {}".format(len(interactions_df)))
+
+# Sorts the dataframe by session and timestamp, to remove consecutive repetitions
+interactions_df.timestamp = interactions_df.timestamp.astype(int)
+interactions_df = interactions_df.sort_values(['session_id', 'timestamp'])
+past_ids = interactions_df['item_id'].shift(1).fillna()
+session_past_ids = interactions_df['session_id'].shift(1).fillna()
+
+# Keeping only no consecutive repeated in session interactions
+interactions_df = interactions_df[~((interactions_df['session_id'] == session_past_ids) & (interactions_df['item_id'] == past_ids))]
+
+print("Count after removed in-session repeated interactions: {}".format(len(interactions_df)))
+
+
+
+
+
Count with in-session repeated interactions: 33003944
+Count after removed in-session repeated interactions: 28971543
+
+
+
+
+
+
+

Create new feature with the timestamp when the item was first seen

+
+
+
items_first_ts_df = interactions_df.groupby('item_id').agg({'timestamp': 'min'}).reset_index().rename(columns={'timestamp': 'itemid_ts_first'})
+interactions_merged_df = interactions_df.merge(items_first_ts_df, on=['item_id'], how='left')
+print(interactions_merged_df.head())
+
+
+
+
+
   session_id   timestamp    item_id  category  itemid_ts_first
+0        7401  1396439960  214826816         0       1396321828
+1        7402  1396780751  214613743         0       1396329089
+2        7402  1396780780  214827011         0       1396735848
+3        7402  1396780912  214821388         0       1396330458
+4        7402  1396780991  214827011         0       1396735848
+
+
+
+
+

Let’s save the interactions_merged_df to disk to be able to use in the inference step.

+
+
+
if os.path.isdir(DATA_FOLDER) == False:
+    os.mkdir(DATA_FOLDER)
+interactions_merged_df.to_parquet(os.path.join(DATA_FOLDER, 'interactions_merged_df.parquet'))
+
+
+
+
+
+
+
# print the total number of unique items in the dataset
+print(interactions_merged_df.item_id.nunique())
+
+
+
+
+
52739
+
+
+
+
+
+
+
# free gpu memory
+del interactions_df, session_past_ids, items_first_ts_df
+gc.collect()
+
+
+
+
+
517
+
+
+
+
+
+
+
+

Define a preprocessing workflow with NVTabular

+

NVTabular is a feature engineering and preprocessing library for tabular data designed to quickly and easily manipulate terabyte scale datasets used to train deep learning based recommender systems. It provides a high level abstraction to simplify code and accelerates computation on the GPU using the RAPIDS cuDF library.

+

NVTabular supports different feature engineering transformations required by deep learning (DL) models such as Categorical encoding and numerical feature normalization. It also supports feature engineering and generating sequential features.

+

More information about the supported features can be found here.

+
+

Feature engineering: Create and Transform items features

+

In this cell, we are defining three transformations ops:

+
    +
    1. +
    2. Encoding categorical variables using Categorify() op. Categorify op maps nulls to 1, OOVs to 2, automatically. We reserve 0 for padding the sequence features. The encoding of each category starts from 3.

    3. +
    +
  • +
    1. +
    2. Deriving temporal features from timestamp and computing their cyclical representation using a custom lambda function.

    3. +
    +
  • +
    1. +
    2. Computing the item recency in days using a custom op. Note that item recency is defined as the difference between the first occurrence of the item in dataset and the actual date of item interaction.

    3. +
    +
  • +
+

For more ETL workflow examples, visit NVTabular example notebooks.

+
+
+
# Encodes categorical features as contiguous integers
+cat_feats = ColumnSelector(['category', 'item_id']) >> nvt.ops.Categorify()
+
+# create time features
+session_ts = ColumnSelector(['timestamp'])
+session_time = (
+    session_ts >> 
+    nvt.ops.LambdaOp(lambda col: cudf.to_datetime(col, unit='s')) >> 
+    nvt.ops.Rename(name = 'event_time_dt')
+)
+sessiontime_weekday = (
+    session_time >> 
+    nvt.ops.LambdaOp(lambda col: col.dt.weekday) >> 
+    nvt.ops.Rename(name ='et_dayofweek')
+)
+
+# Derive cyclical features: Define a custom lambda function 
+def get_cycled_feature_value_sin(col, max_value):
+    value_scaled = (col + 0.000001) / max_value
+    value_sin = np.sin(2*np.pi*value_scaled)
+    return value_sin
+
+weekday_sin = sessiontime_weekday >> (lambda col: get_cycled_feature_value_sin(col+1, 7)) >> nvt.ops.Rename(name = 'et_dayofweek_sin')
+
+# Compute Item recency: Define a custom Op 
+class ItemRecency(nvt.ops.Operator):
+    def transform(self, columns, gdf):
+        for column in columns.names:
+            col = gdf[column]
+            item_first_timestamp = gdf['itemid_ts_first']
+            delta_days = (col - item_first_timestamp) / (60*60*24)
+            gdf[column + "_age_days"] = delta_days * (delta_days >=0)
+        return gdf
+
+    def compute_selector(
+        self,
+        input_schema: Schema,
+        selector: ColumnSelector,
+        parents_selector: ColumnSelector,
+        dependencies_selector: ColumnSelector,
+    ) -> ColumnSelector:
+        self._validate_matching_cols(input_schema, parents_selector, "computing input selector")
+        return parents_selector
+
+    def column_mapping(self, col_selector):
+        column_mapping = {}
+        for col_name in col_selector.names:
+            column_mapping[col_name + "_age_days"] = [col_name]
+        return column_mapping
+
+    @property
+    def dependencies(self):
+        return ["itemid_ts_first"]
+
+    @property
+    def output_dtype(self):
+        return np.float64
+    
+recency_features = session_ts >> ItemRecency() 
+# Apply standardization to this continuous feature
+recency_features_norm = recency_features >> nvt.ops.LogOp() >> nvt.ops.Normalize(out_dtype=np.float32) >> nvt.ops.Rename(name='product_recency_days_log_norm')
+
+time_features = (
+    session_time +
+    sessiontime_weekday +
+    weekday_sin + 
+    recency_features_norm
+)
+
+features = ColumnSelector(['session_id', 'timestamp']) + cat_feats + time_features 
+
+
+
+
+
+
+

Define the preprocessing of sequential features

+

Once the item features are generated, the objective of this cell is to group interactions at the session level, sorting the interactions by time. We additionally truncate all sessions to first 20 interactions and filter out sessions with less than 2 interactions.

+
+
+
# Define Groupby Operator
+groupby_features = features >> nvt.ops.Groupby(
+    groupby_cols=["session_id"], 
+    sort_cols=["timestamp"],
+    aggs={
+        'item_id': ["list", "count"],
+        'category': ["list"],  
+        'timestamp': ["first"],
+        'event_time_dt': ["first"],
+        'et_dayofweek_sin': ["list"],
+        'product_recency_days_log_norm': ["list"]
+        },
+    name_sep="-")
+
+# Truncate sequence features to first interacted 20 items 
+SESSIONS_MAX_LENGTH = 20 
+
+item_feat = groupby_features['item_id-list'] >> nvt.ops.TagAsItemID()
+cont_feats = groupby_features['et_dayofweek_sin-list', 'product_recency_days_log_norm-list'] >> nvt.ops.AddMetadata(tags=[Tags.CONTINUOUS])
+
+
+groupby_features_list =  item_feat + cont_feats + groupby_features['category-list']
+groupby_features_truncated = groupby_features_list >> nvt.ops.ListSlice(-SESSIONS_MAX_LENGTH)
+
+# Calculate session day index based on 'event_time_dt-first' column
+day_index = ((groupby_features['event_time_dt-first'])  >> 
+             nvt.ops.LambdaOp(lambda col: (col - col.min()).dt.days +1) >> 
+             nvt.ops.Rename(f = lambda col: "day_index") >>
+             nvt.ops.AddMetadata(tags=[Tags.CATEGORICAL])
+            )
+
+# tag session_id column for serving with legacy api
+sess_id = groupby_features['session_id'] >> nvt.ops.AddMetadata(tags=[Tags.CATEGORICAL])
+
+# Select features for training 
+selected_features = sess_id + groupby_features['item_id-count'] + groupby_features_truncated + day_index
+
+# Filter out sessions with less than 2 interactions 
+MINIMUM_SESSION_LENGTH = 2
+filtered_sessions = selected_features >> nvt.ops.Filter(f=lambda df: df["item_id-count"] >= MINIMUM_SESSION_LENGTH) 
+
+
+
+
+
+
+

Execute NVTabular workflow

+

Once we have defined the general workflow (filtered_sessions), we provide our cudf dataset to nvt.Dataset class which is optimized to split data into chunks that can fit in device memory and to handle the calculation of complex global statistics. Then, we execute the pipeline that fits and transforms data to get the desired output features.

+
+
+
dataset = nvt.Dataset(interactions_merged_df)
+workflow = nvt.Workflow(filtered_sessions)
+# Learn features statistics necessary of the preprocessing workflow
+# The following will generate schema.pbtxt file in the provided folder and export the parquet files.
+workflow.fit_transform(dataset).to_parquet(os.path.join(DATA_FOLDER, "processed_nvt"))
+
+
+
+
+

Let’s print the head of our preprocessed dataset. You can notice that now each example (row) is a session and the sequential features with respect to user interactions were converted to lists with matching length.

+
+
+
workflow.output_schema
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametagsdtypeis_listis_raggedproperties.num_bucketsproperties.freq_thresholdproperties.max_sizeproperties.cat_pathproperties.domain.minproperties.domain.maxproperties.domain.nameproperties.embedding_sizes.cardinalityproperties.embedding_sizes.dimensionproperties.value_count.minproperties.value_count.max
0session_id(Tags.CATEGORICAL)DType(name='int64', element_type=<ElementType....FalseFalseNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
1item_id-count(Tags.CATEGORICAL)DType(name='int32', element_type=<ElementType....FalseFalseNaN0.00.0.//categories/unique.item_id.parquet0.052741.0item_id52742.0512.0NaNNaN
2item_id-list(Tags.CATEGORICAL, Tags.ID, Tags.LIST, Tags.ITEM)DType(name='int64', element_type=<ElementType....TrueTrueNaN0.00.0.//categories/unique.item_id.parquet0.052741.0item_id52742.0512.00.020.0
3et_dayofweek_sin-list(Tags.CONTINUOUS, Tags.LIST)DType(name='float64', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaN0.020.0
4product_recency_days_log_norm-list(Tags.CONTINUOUS, Tags.LIST)DType(name='float32', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaN0.020.0
5category-list(Tags.CATEGORICAL, Tags.LIST)DType(name='int64', element_type=<ElementType....TrueTrueNaN0.00.0.//categories/unique.category.parquet0.0336.0category337.042.00.020.0
6day_index(Tags.CATEGORICAL)DType(name='int64', element_type=<ElementType....FalseFalseNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
+
+
+
+

Save the preprocessing workflow

+
+
+
workflow.save(os.path.join(DATA_FOLDER, "workflow_etl"))
+
+
+
+
+
+
+
+

Export pre-processed data by day

+

In this example we are going to split the preprocessed parquet files by days, to allow for temporal training and evaluation. There will be a folder for each day and three parquet files within each day: train.parquet, validation.parquet and test.parquet.

+

P.s. It is worthwhile to note that the dataset has a single categorical feature (category), which, however, is inconsistent over time in the dataset. All interactions before day 84 (2014-06-23) have the same value for that feature, whereas many other categories are introduced afterwards. Thus for this example, we save only the last five days.

+
+
+
# read in the processed train dataset
+sessions_gdf = cudf.read_parquet(os.path.join(DATA_FOLDER, "processed_nvt/part_0.parquet"))
+if USE_SYNTHETIC:
+    THRESHOLD_DAY_INDEX = int(os.environ.get("THRESHOLD_DAY_INDEX", '1'))
+    sessions_gdf = sessions_gdf[sessions_gdf.day_index>=THRESHOLD_DAY_INDEX]
+else:
+    sessions_gdf = sessions_gdf[sessions_gdf.day_index>=178]
+
+
+
+
+
+
+
print(sessions_gdf.head(3))
+
+
+
+
+
         session_id  item_id-count  \
+6606147    11255549             12   
+6606148    11255552              2   
+6606149    11255553              2   
+
+                                              item_id-list  \
+6606147  [605, 879, 743, 91, 4778, 1584, 3447, 8084, 34...   
+6606148                                       [185, 12289]   
+6606149                                       [7300, 1954]   
+
+                                     et_dayofweek_sin-list  \
+6606147  [-0.43388454782514785, -0.43388454782514785, -...   
+6606148       [-0.43388454782514785, -0.43388454782514785]   
+6606149         [-0.7818309228245777, -0.7818309228245777]   
+
+                        product_recency_days_log_norm-list  \
+6606147  [1.5241561, 1.523876, 1.523935, 1.5241641, 1.5...   
+6606148                              [-0.533007, 1.521495]   
+6606149                             [1.5338274, 1.5355083]   
+
+                                category-list  day_index  
+6606147  [4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 4, 4]        178  
+6606148                                [1, 3]        178  
+6606149                                [8, 8]        180  
+
+
+
+
+
+
+
from transformers4rec.utils.data_utils import save_time_based_splits
+save_time_based_splits(data=nvt.Dataset(sessions_gdf),
+                       output_dir=os.path.join(DATA_FOLDER, "preproc_sessions_by_day"),
+                       partition_col='day_index',
+                       timestamp_col='session_id', 
+                      )
+
+
+
+
+
Creating time-based splits: 100%|██████████| 5/5 [00:02<00:00,  2.24it/s]
+
+
+
+
+
+
+
# free gpu memory
+del  sessions_gdf
+gc.collect()
+
+
+
+
+
748
+
+
+
+
+

That’s it! We created our sequential features, now we can go to the next notebook to train a PyTorch session-based model.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT.html b/review/pr-767/examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT.html new file mode 100644 index 0000000000..485b4fe75a --- /dev/null +++ b/review/pr-767/examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT.html @@ -0,0 +1,1028 @@ + + + + + + End-to-end session-based recommendations with PyTorch — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_end-to-end-session-based-02-end-to-end-session-based-with-yoochoose-pyt/nvidia_logo.png +
+

End-to-end session-based recommendations with PyTorch

+

In recent years, several deep learning-based algorithms have been proposed for recommendation systems while its adoption in industry deployments have been steeply growing. In particular, NLP inspired approaches have been successfully adapted for sequential and session-based recommendation problems, which are important for many domains like e-commerce, news and streaming media. Session-Based Recommender Systems (SBRS) have been proposed to model the sequence of interactions within the current user session, where a session is a short sequence of user interactions typically bounded by user inactivity. They have recently gained popularity due to their ability to capture short-term or contextual user preferences towards items.

+

The field of NLP has evolved significantly within the last decade, particularly due to the increased usage of deep learning. As a result, state of the art NLP approaches have inspired RecSys practitioners and researchers to adapt those architectures, especially for sequential and session-based recommendation problems. Here, we leverage one of the state-of-the-art Transformer-based architecture, XLNet with Masked Language Modeling (MLM) training technique (see our tutorial for details) for training a session-based model.

+

In this end-to-end-session-based recommnender model example, we use Transformers4Rec library, which leverages the popular HuggingFace’s Transformers NLP library and make it possible to experiment with cutting-edge implementation of such architectures for sequential and session-based recommendation problems. For detailed explanations of the building blocks of Transformers4Rec meta-architecture visit getting-started-session-based and tutorial example notebooks.

+
+

1. Model definition using Transformers4Rec

+

In the previous notebook, we have created sequential features and saved our processed data frames as parquet files. Now we use these processed parquet files to train a session-based recommendation model with the XLNet architecture.

+
+

1.1 Get the schema

+

The library uses a schema format to configure the input features and automatically creates the necessary layers. This protobuf text file contains the description of each input feature by defining: the name, the type, the number of elements of a list column, the cardinality of a categorical feature and the min and max values of each feature. In addition, the annotation field contains the tags such as specifying the continuous and categorical features, the target column or the item_id feature, among others.

+

We create the schema object by reading the processed train parquet file generated by NVTabular pipeline in the previous, 01-ETL-with-NVTabular, notebook.

+
+
+
import os
+os.environ["CUDA_VISIBLE_DEVICES"]="0"
+INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data")
+OUTPUT_DIR = os.environ.get("OUTPUT_DIR", f"{INPUT_DATA_DIR}/preproc_sessions_by_day")
+
+
+
+
+
+
+
from merlin.schema import Schema
+from merlin.io import Dataset
+
+train = Dataset(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+schema = train.schema
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+
+
+
+
+

We can select the subset of features we want to use for training the model by their tags or their names.

+
+
+
schema = schema.select_by_name(
+   ['item_id-list', 'category-list', 'product_recency_days_log_norm-list', 'et_dayofweek_sin-list']
+)
+
+
+
+
+

We can print out the schema.

+
+
+
schema
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametagsdtypeis_listis_raggedproperties.num_bucketsproperties.freq_thresholdproperties.max_sizeproperties.cat_pathproperties.embedding_sizes.cardinalityproperties.embedding_sizes.dimensionproperties.domain.minproperties.domain.maxproperties.domain.nameproperties.value_count.minproperties.value_count.max
0item_id-list(Tags.CATEGORICAL, Tags.LIST, Tags.ID, Tags.ITEM)DType(name='int64', element_type=<ElementType....TrueTrueNaN0.00.0.//categories/unique.item_id.parquet52742.0512.00.052741.0item_id020
1category-list(Tags.CATEGORICAL, Tags.LIST)DType(name='int64', element_type=<ElementType....TrueTrueNaN0.00.0.//categories/unique.category.parquet337.042.00.0336.0category020
2product_recency_days_log_norm-list(Tags.LIST, Tags.CONTINUOUS)DType(name='float32', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaN020
3et_dayofweek_sin-list(Tags.LIST, Tags.CONTINUOUS)DType(name='float64', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaN020
+
+
+
+
+

1.2 Define the end-to-end Session-based Transformer-based recommendation model

+

For defining a session-based recommendation model, the end-to-end model definition requires four steps:

+
    +
  1. Instantiate TabularSequenceFeatures input-module from schema to prepare the embedding tables of categorical variables and project continuous features, if specified. In addition, the module provides different aggregation methods (e.g. ‘concat’, ‘elementwise-sum’) to merge input features and generate the sequence of interactions embeddings. The module also supports language modeling tasks to prepare masked labels for training and evaluation (e.g: ‘mlm’ for masked language modeling)

  2. +
  3. Next, we need to define one or multiple prediction tasks. For this demo, we are going to use NextItemPredictionTask with Masked Language modeling: during training, randomly selected items are masked and predicted using the unmasked sequence items. For inference, it is meant to always predict the next item to be interacted with.

  4. +
  5. Then we construct a transformer_config based on the architectures provided by Hugging Face Transformers framework.

  6. +
  7. Finally we link the transformer-body to the inputs and the prediction tasks to get the final pytorch Model class.

  8. +
+

For more details about the features supported by each sub-module, please check out the library documentation page.

+
+
+
from transformers4rec import torch as tr
+
+max_sequence_length, d_model = 20, 320
+# Define input module to process tabular input-features and to prepare masked inputs
+input_module = tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=max_sequence_length,
+    continuous_projection=64,
+    aggregation="concat",
+    d_output=d_model,
+    masking="mlm",
+)
+
+# Define Next item prediction-task 
+prediction_task = tr.NextItemPredictionTask(weight_tying=True)
+
+# Define the config of the XLNet Transformer architecture
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=8, n_layer=2, total_seq_length=max_sequence_length
+)
+
+# Get the end-to-end model 
+model = transformer_config.to_torch_model(input_module, prediction_task)
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+Projecting inputs of NextItemPredictionTask to'64' As weight tying requires the input dimension '320' to be equal to the item-id embedding dimension '64'
+
+
+
+
+

You can print out the model structure by uncommenting the line below.

+
+
+
#model
+
+
+
+
+
+
+

1.3. Daily Fine-Tuning: Training over a time window¶

+

Now that the model is defined, we are going to launch training. For that, Transfromers4rec extends HF Transformers Trainer class to adapt the evaluation loop for session-based recommendation task and the calculation of ranking metrics. The original train() method is not modified meaning that we leverage the efficient training implementation from that library, which manages, for example, half-precision (FP16) training.

+
+

Set the training arguments

+

An additional argument data_loader_engine is defined to automatically load the features needed for training using the schema. The default value is merlin for optimized GPU-based data-loading. Optionally a PyarrowDataLoader (pyarrow) can also be used as a basic option, but it is slower and works only for small datasets, as the full data is loaded to CPU memory.

+
+
+
BATCH_SIZE_TRAIN = int(os.environ.get("BATCH_SIZE_TRAIN", "512"))
+BATCH_SIZE_VALID = int(os.environ.get("BATCH_SIZE_VALID", "256"))
+training_args = tr.trainer.T4RecTrainingArguments(
+            output_dir="./tmp",
+            max_sequence_length=20,
+            data_loader_engine='merlin',
+            num_train_epochs=10, 
+            dataloader_drop_last=False,
+            per_device_train_batch_size = BATCH_SIZE_TRAIN,
+            per_device_eval_batch_size = BATCH_SIZE_VALID,
+            learning_rate=0.0005,
+            fp16=True,
+            report_to = [],
+            logging_steps=200
+        )
+
+
+
+
+
+
+

Instantiate the trainer

+
+
+
recsys_trainer = tr.Trainer(
+    model=model,
+    args=training_args,
+    schema=schema,
+    compute_metrics=True)
+
+
+
+
+
Using amp fp16 backend
+
+
+
+
+
+
+

Launch daily training and evaluation

+

In this demo, we will use the fit_and_evaluate method that allows us to conduct a time-based finetuning by iteratively training and evaluating using a sliding time window: At each iteration, we use the training data of a specific time index \(t\) to train the model; then we evaluate on the validation data of the next index \(t + 1\). Particularly, we set start time to 178 and end time to 180.

+

If you have generated a synthetic dataset in the previous notebook, remember to change the values 178 and 180 accordingly.

+
+
+
from transformers4rec.torch.utils.examples_utils import fit_and_evaluate
+start_time_idx = int(os.environ.get("START_TIME_INDEX", "178"))
+end_time_idx = int(os.environ.get("END_TIME_INDEX", "180"))
+OT_results = fit_and_evaluate(recsys_trainer, start_time_index=start_time_idx, end_time_index=end_time_idx, input_dir=OUTPUT_DIR)
+
+
+
+
+
***** Launch training for day 178: *****
+
+
+
***** Running training *****
+  Num examples = 28672
+  Num Epochs = 10
+  Instantaneous batch size per device = 512
+  Total train batch size (w. parallel, distributed & accumulation) = 512
+  Gradient Accumulation steps = 1
+  Total optimization steps = 560
+
+
+
+
+ + + [560/560 00:25, Epoch 10/10] +
+ + + + + + + + + + + + + + + + + +
StepTraining Loss
2007.585600
4006.608700

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
+
+ + + [11/11 00:33] +
+
***** Running training *****
+  Num examples = 20480
+  Num Epochs = 10
+  Instantaneous batch size per device = 512
+  Total train batch size (w. parallel, distributed & accumulation) = 512
+  Gradient Accumulation steps = 1
+  Total optimization steps = 400
+
+
+
***** Evaluation results for day 179:*****
+
+ eval_/next-item/avg_precision@10 = 0.07277625054121017
+ eval_/next-item/avg_precision@20 = 0.077287457883358
+ eval_/next-item/ndcg@10 = 0.1008271649479866
+ eval_/next-item/ndcg@20 = 0.11763089150190353
+ eval_/next-item/recall@10 = 0.18959537148475647
+ eval_/next-item/recall@20 = 0.25549131631851196
+
+***** Launch training for day 179: *****
+
+
+
+
+ + + [400/400 00:17, Epoch 10/10] +
+ + + + + + + + + + + + + + + + + +
StepTraining Loss
2006.838400
4006.304600

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+***** Running training *****
+  Num examples = 16896
+  Num Epochs = 10
+  Instantaneous batch size per device = 512
+  Total train batch size (w. parallel, distributed & accumulation) = 512
+  Gradient Accumulation steps = 1
+  Total optimization steps = 330
+
+
+
***** Evaluation results for day 180:*****
+
+ eval_/next-item/avg_precision@10 = 0.059328265488147736
+ eval_/next-item/avg_precision@20 = 0.06352042406797409
+ eval_/next-item/ndcg@10 = 0.08318208903074265
+ eval_/next-item/ndcg@20 = 0.09845318645238876
+ eval_/next-item/recall@10 = 0.16083915531635284
+ eval_/next-item/recall@20 = 0.2209790199995041
+
+***** Launch training for day 180: *****
+
+
+
+
+ + + [330/330 00:14, Epoch 10/10] +
+ + + + + + + + + + + + + +
StepTraining Loss
2006.708000

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
***** Evaluation results for day 181:*****
+
+ eval_/next-item/avg_precision@10 = 0.12736327946186066
+ eval_/next-item/avg_precision@20 = 0.13500627875328064
+ eval_/next-item/ndcg@10 = 0.16738776862621307
+ eval_/next-item/ndcg@20 = 0.19680777192115784
+ eval_/next-item/recall@10 = 0.29406309127807617
+ eval_/next-item/recall@20 = 0.41187384724617004
+
+
+
+
+
+
+

Visualize the average of metrics over time

+

OT_results is a list of scores (accuracy metrics) for evaluation based on given start and end time_index. Since in this example we do evaluation on days 179, 180 and 181, we get three metrics in the list one for each day.

+
+
+
OT_results
+
+
+
+
+
{'indexed_by_time_eval_/next-item/avg_precision@10': [0.07277625054121017,
+  0.059328265488147736,
+  0.12736327946186066],
+ 'indexed_by_time_eval_/next-item/avg_precision@20': [0.077287457883358,
+  0.06352042406797409,
+  0.13500627875328064],
+ 'indexed_by_time_eval_/next-item/ndcg@10': [0.1008271649479866,
+  0.08318208903074265,
+  0.16738776862621307],
+ 'indexed_by_time_eval_/next-item/ndcg@20': [0.11763089150190353,
+  0.09845318645238876,
+  0.19680777192115784],
+ 'indexed_by_time_eval_/next-item/recall@10': [0.18959537148475647,
+  0.16083915531635284,
+  0.29406309127807617],
+ 'indexed_by_time_eval_/next-item/recall@20': [0.25549131631851196,
+  0.2209790199995041,
+  0.41187384724617004]}
+
+
+
+
+
+
+
import numpy as np
+# take the average of metric values over time
+avg_results = {k: np.mean(v) for k,v in OT_results.items()}
+for key in sorted(avg_results.keys()): 
+    print(" %s = %s" % (key, str(avg_results[key]))) 
+
+
+
+
+
 indexed_by_time_eval_/next-item/avg_precision@10 = 0.08648926516373952
+ indexed_by_time_eval_/next-item/avg_precision@20 = 0.09193805356820424
+ indexed_by_time_eval_/next-item/ndcg@10 = 0.1171323408683141
+ indexed_by_time_eval_/next-item/ndcg@20 = 0.13763061662515005
+ indexed_by_time_eval_/next-item/recall@10 = 0.21483253935972849
+ indexed_by_time_eval_/next-item/recall@20 = 0.2961147278547287
+
+
+
+
+
+
+

Trace the model

+

We serve the model with the PyTorch backend that is used to execute TorchScript models. All models created in PyTorch using the python API must be traced/scripted to produce a TorchScript model. For tracing the model, we use torch.jit.trace api that takes the model as a Python function or torch.nn.Module, and an example input that will be passed to the function while tracing.

+
+
+
import os
+import torch
+import cudf
+from merlin.io import Dataset
+from nvtabular import Workflow
+
+from merlin.systems.dag import Ensemble
+from merlin.systems.dag.ops.pytorch import PredictPyTorch
+from merlin.systems.dag.ops.workflow import TransformWorkflow
+from merlin.table import TensorTable, TorchColumn
+from merlin.table.conversions import convert_col
+
+
+
+
+

Create a dict of tensors to feed it as example inputs in the torch.jit.trace().

+
+
+
df = cudf.read_parquet(os.path.join(INPUT_DATA_DIR, f"preproc_sessions_by_day/{start_time_idx}/train.parquet"), columns=model.input_schema.column_names)
+table = TensorTable.from_df(df.iloc[:100])
+for column in table.columns:
+    table[column] = convert_col(table[column], TorchColumn)
+model_input_dict = table.to_dict()
+
+
+
+
+

Let’s now add a top_k parameter to model so that we can return the top k item ids with the highest scores when we serve the model on Triton Inference Server.

+
+
+
topk = 20
+model.top_k = topk
+
+
+
+
+

Let us now trace the model.

+
+
+
model.eval()
+traced_model = torch.jit.trace(model, model_input_dict, strict=True)
+
+
+
+
+

Let’s check out the item_id-list column in the model_input_dict dictionary.

+

The column is represented as values and offsets dictating which values belong to which example.

+
+
+
model_input_dict['item_id-list__values']
+
+
+
+
+
tensor([  605,   879,   743,    91,  4778,  1584,  3447,  8084,  3447,  4019,
+          743,  4778,   185, 12289,  2066,   431,     7,    31,     7,   158,
+         1988,  2591, 10856,  8218,  4211,  8712,  4243,    82,   113,  4243,
+         5733,  6811,    34,     7,    74,   665,  2313,  7125,  9114,   446,
+         1158,   775,   686,   431,  1946,   476,   598,   290,   167,    30,
+          343,   290,    34,   424,   167,   481,  2773,   289,   963,  4002,
+         2051,  3275,   500,  1220,   396,  1637, 11840, 10715, 11108,   290,
+          167,   651,  1086,   303,    89,   651,   215,   305,   178,   318,
+          424,     7,  3819,   932,  2187,  1086,   207,   688,  3832,   688,
+          203,    21,    44,    21,  2035, 10458,    21,    22,  1127,  2816,
+         4211, 34265,   831,   775,   621,  2051,  1988,  1080,  4714,  1337,
+          662,   290,   431,   864,  4830,  5787, 19157, 17271, 23366,  4210,
+         3652,  1038,  4771,   225,   278,  1021,   651,   167,  1355,   207,
+         1890,  2474,  1698,   998,   481,   775,  3842,  4317,  3842,  1231,
+         3842,  1698,  3810,   476,   982,   805,   314,   614,  1220,  1335,
+         1942,  2889,  2627,  1335,  2690,   805,   476,   982,   314,   805,
+         1220,   207,   652,   430,   606,   102,   414,  1966,   628,   815,
+          628,   815,  4714,  3676,  2790,  3770,  1284,  9541,  1252,   314,
+          686,  2498,   396,   846,  3463,  2714,  5078,   389,   341,   298,
+          389,  9642,    47,    62,   823,    62,   603,  2271,   720,  3275,
+         2557,     3,    62,    25,    97,    62,   424,    62,   476,  3461,
+         2694,  3461,  3045,  2557,  3989,   993,  1604,   123,  2705,  2788,
+         3136,   551,   517,    45,  1552,  2703,   207,  1763,    32,   475,
+          482,   199,   475,  2705,  2394,  1026, 20034,    73,  1335,   225,
+         3461,  4775,  2051,  6486, 15954,   423,  1489,  2347,  4471,  2549,
+          572,  1771,  1325,   454,   838,   124,   639,  4760,  3553,  6826,
+         2741,  5348,  5391,  1170,  4101,  1231,   805,  3589,  2450,   186,
+           17,   644,   275,   687, 18093, 10458,   610,  2970,  3481,  2970,
+           38,   610,  2970,   610,    23,  8313,   258,    38,    23,  5362,
+         7187,  7381,  6053,  7257, 13405,   558,   161,  1665,  4376,  3485,
+          686,   652,   446,   430,   446,   775,   652,  4285,  1739,  3856,
+          226,   211,  7246,  6732,   772,  1988,   158,   805,   443,   805,
+         2092,  1170,  2092,  1170,  3485,  4376,  3485,   446,   430,   431,
+          424,  1698,  1394,  1799,  2754,   207,  1154, 21589,  2190,  3705,
+         4464,  5817,  7558,   508,  1798,   815,   628,  2017,   856,  1890,
+          225,   598,    38,   598, 16534, 10256,     3,  2652,  4029,  2557,
+         2789,  6380,  1831,  1071,    31,   313,   446,  1086,  1570,  2223,
+         2665,  1951,  2099,  1673,   225,  4337,   652,   998,  1158,   831,
+          801,   598,  1086, 12431,   416, 12431,   652,   801,  1757,  1379,
+         1414,   634,  2035,  7933,  6035,  6361,  4663,   577,  4663,   577,
+         4663], device='cuda:0')
+
+
+
+
+

And here are the offsets

+
+
+
model_input_dict['item_id-list__offsets']
+
+
+
+
+
tensor([  0,  12,  14,  16,  19,  21,  23,  26,  32,  35,  45,  47,  49,  51,
+         56,  59,  61,  66,  69,  72,  76,  78,  80,  83,  87,  91,  94,  96,
+         98, 100, 102, 104, 107, 111, 113, 122, 124, 128, 130, 133, 136, 141,
+        143, 161, 164, 167, 172, 176, 180, 182, 184, 191, 193, 196, 199, 201,
+        209, 214, 234, 237, 243, 245, 265, 267, 274, 276, 281, 289, 295, 298,
+        300, 305, 307, 312, 314, 317, 320, 324, 327, 331, 335, 337, 339, 343,
+        345, 348, 351, 354, 356, 360, 362, 364, 367, 374, 376, 381, 383, 386,
+        388, 396, 401], device='cuda:0', dtype=torch.int32)
+
+
+
+
+

Let’s create a folder that we can store the exported models and the config files.

+
+
+
import shutil
+ens_model_path = os.environ.get("ens_model_path", f"{INPUT_DATA_DIR}/models")
+# Make sure we have a clean stats space for Dask
+if os.path.isdir(ens_model_path):
+    shutil.rmtree(ens_model_path)
+os.mkdir(ens_model_path)
+
+
+
+
+
+
+
workflow = Workflow.load(os.path.join(INPUT_DATA_DIR, "workflow_etl"))
+
+
+
+
+
+
+
torch_op = workflow.input_schema.column_names >> TransformWorkflow(workflow) >> PredictPyTorch(
+    traced_model, model.input_schema, model.output_schema
+)
+
+
+
+
+

The last step is to create the ensemble artifacts that Triton Inference Server can consume. To make these artifacts, we import the Ensemble class. The class is responsible for interpreting the graph and exporting the correct files for the server.

+

When we create an Ensemble object we supply the graph and a schema representing the starting input of the graph. The inputs to the ensemble graph are the inputs to the first operator of out graph. After we created the Ensemble we export the graph, supplying an export path for the ensemble.export function. This returns an ensemble config which represents the entire inference pipeline and a list of node-specific configs.

+
+
+
ensemble = Ensemble(torch_op, workflow.input_schema)
+ens_config, node_configs = ensemble.export(ens_model_path)
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/systems/dag/node.py:100: UserWarning: Operator 'TransformWorkflow' is producing the output column 'session_id', which is not being used by any downstream operator in the ensemble graph.
+  warnings.warn(
+/usr/local/lib/python3.8/dist-packages/merlin/systems/dag/node.py:100: UserWarning: Operator 'TransformWorkflow' is producing the output column 'item_id-count', which is not being used by any downstream operator in the ensemble graph.
+  warnings.warn(
+/usr/local/lib/python3.8/dist-packages/merlin/systems/dag/node.py:100: UserWarning: Operator 'TransformWorkflow' is producing the output column 'day_index', which is not being used by any downstream operator in the ensemble graph.
+  warnings.warn(
+
+
+
+
+
+
+
+
+

2. Serving Ensemble Model to the Triton Inference Server

+

NVIDIA Triton Inference Server (TIS) simplifies the deployment of AI models at scale in production. TIS provides a cloud and edge inferencing solution optimized for both CPUs and GPUs. It supports a number of different machine learning frameworks such as TensorFlow and PyTorch.

+

The last step of a machine learning (ML)/deep learning (DL) pipeline is to deploy the ETL workflow and saved model to production. In the production setting, we want to transform the input data as done during training (ETL). We need to apply the same mean/std for continuous features and use the same categorical mapping to convert the categories to continuous integer before we use the DL model for a prediction. Therefore, we deploy the NVTabular workflow with the PyTorch model as an ensemble model to Triton Inference. The ensemble model guarantees that the same transformation is applied to the raw inputs.

+

In this section, you will learn how to

+
    +
  • to deploy saved NVTabular and PyTorch models to Triton Inference Server

  • +
  • send requests for predictions and get responses.

  • +
+
+

2.1 Starting Triton Server

+

It is time to deploy all the models as an ensemble model to Triton Inference Serve TIS. After we export the ensemble, we are ready to start the TIS. You can start triton server by using the following command on your terminal:

+

tritonserver --model-repository=<ensemble_export_path>

+

For the --model-repository argument, specify the same path as the export_path that you specified previously in the ensemble.export method. This command will launch the server and load all the models to the server. Once all the models are loaded successfully, you should see READY status printed out in the terminal for each loaded model.

+
+
+

2.2. Connect to the Triton Inference Server and check if the server is alive

+
+
+
import tritonhttpclient
+try:
+    triton_client = tritonhttpclient.InferenceServerClient(url="localhost:8000", verbose=True)
+    print("client created.")
+except Exception as e:
+    print("channel creation failed: " + str(e))
+triton_client.is_server_live()
+
+
+
+
+
client created.
+GET /v2/health/live, headers None
+<HTTPSocketPoolResponse status=200 headers={'content-length': '0', 'content-type': 'text/plain'}>
+
+
+
/usr/local/lib/python3.8/dist-packages/tritonhttpclient/__init__.py:31: DeprecationWarning: The package `tritonhttpclient` is deprecated and will be removed in a future version. Please use instead `tritonclient.http`
+  warnings.warn(
+
+
+
True
+
+
+
+
+
+
+

2.3. Load raw data for inference

+

We select the last 50 interactions and filter out sessions with less than 2 interactions.

+
+
+
import pandas as pd
+interactions_merged_df = pd.read_parquet(os.path.join(INPUT_DATA_DIR, "interactions_merged_df.parquet"))
+interactions_merged_df = interactions_merged_df.sort_values('timestamp')
+batch = interactions_merged_df[-50:]
+sessions_to_use = batch.session_id.value_counts()
+filtered_batch = batch[batch.session_id.isin(sessions_to_use[sessions_to_use.values>1].index.values)]
+
+
+
+
+
+
+

2.5. Send the request to triton server

+
+
+
triton_client.get_model_repository_index()
+
+
+
+
+
POST /v2/repository/index, headers None
+
+<HTTPSocketPoolResponse status=200 headers={'content-type': 'application/json', 'content-length': '188'}>
+bytearray(b'[{"name":"0_transformworkflowtriton","version":"1","state":"READY"},{"name":"1_predictpytorchtriton","version":"1","state":"READY"},{"name":"executor_model","version":"1","state":"READY"}]')
+
+
+
[{'name': '0_transformworkflowtriton', 'version': '1', 'state': 'READY'},
+ {'name': '1_predictpytorchtriton', 'version': '1', 'state': 'READY'},
+ {'name': 'executor_model', 'version': '1', 'state': 'READY'}]
+
+
+
+
+

If all models are loaded successfully, you should be seeing READY status next to each model.

+
+
+
from merlin.systems.triton.utils import send_triton_request
+response = send_triton_request(workflow.input_schema, filtered_batch, model.output_schema.column_names)
+print(response)
+
+
+
+
+
---------------------------------------------------------------------------
+ValueError                                Traceback (most recent call last)
+Cell In[26], line 2
+      1 from merlin.systems.triton.utils import send_triton_request
+----> 2 response = send_triton_request(workflow.input_schema, filtered_batch, model.output_schema.column_names)
+      3 print(response)
+
+File /usr/local/lib/python3.8/dist-packages/merlin/systems/triton/utils.py:226, in send_triton_request(schema, inputs, outputs_list, client, endpoint, request_id, triton_model)
+    224     triton_inputs = triton.convert_table_to_triton_input(schema, inputs, grpcclient.InferInput)
+    225 else:
+--> 226     triton_inputs = triton.convert_df_to_triton_input(schema, inputs, grpcclient.InferInput)
+    228 outputs = [grpcclient.InferRequestedOutput(col) for col in outputs_list]
+    230 response = client.infer(triton_model, triton_inputs, request_id=request_id, outputs=outputs)
+
+File /usr/local/lib/python3.8/dist-packages/merlin/systems/triton/__init__.py:88, in convert_df_to_triton_input(schema, batch, input_class, dtype)
+     68 def convert_df_to_triton_input(schema, batch, input_class=grpcclient.InferInput, dtype="int32"):
+     69     """
+     70     Convert a dataframe to a set of Triton inputs
+     71 
+   (...)
+     86         A list of Triton inputs of the requested input class
+     87     """
+---> 88     df_dict = _convert_df_to_dict(schema, batch, dtype)
+     89     inputs = [
+     90         _convert_array_to_triton_input(col_name, col_values, input_class)
+     91         for col_name, col_values in df_dict.items()
+     92     ]
+     93     return inputs
+
+File /usr/local/lib/python3.8/dist-packages/merlin/systems/triton/__init__.py:183, in _convert_df_to_dict(schema, batch, dtype)
+    181     else:
+    182         values = col.values if isinstance(col, pd.Series) else col.values_host
+--> 183         values = values.reshape(*shape).astype(col_schema.dtype.to_numpy)
+    184         df_dict[col_name] = values
+    185 return df_dict
+
+File /usr/local/lib/python3.8/dist-packages/pandas/core/arrays/masked.py:471, in BaseMaskedArray.astype(self, dtype, copy)
+    469 # to_numpy will also raise, but we get somewhat nicer exception messages here
+    470 if is_integer_dtype(dtype) and self._hasna:
+--> 471     raise ValueError("cannot convert NA to integer")
+    472 if is_bool_dtype(dtype) and self._hasna:
+    473     # careful: astype_nansafe converts np.nan to True
+    474     raise ValueError("cannot convert float NaN to bool")
+
+ValueError: cannot convert NA to integer
+
+
+
+
+

The response contains scores (logits) for each of the returned items and the returned it ids.

+
+
+
response.keys()
+
+
+
+
+
dict_keys(['item_id_scores', 'item_ids'])
+
+
+
+
+

Just as we requested by setting the top_k parameter, only 20 predictions are returned.

+
+
+
response['item_ids'].shape
+
+
+
+
+
(15, 20)
+
+
+
+
+

This is the end of the tutorial. You successfully

+
    +
  • performed feature engineering with NVTabular

  • +
  • trained transformer architecture based session-based recommendation models with Transformers4Rec

  • +
  • deployed a trained model to Triton Inference Server, sent request and got responses from the server.

  • +
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT.html b/review/pr-767/examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT.html new file mode 100644 index 0000000000..adcde16862 --- /dev/null +++ b/review/pr-767/examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT.html @@ -0,0 +1,582 @@ + + + + + + Multi-GPU training for session-based recommendations with PyTorch — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Multi-GPU training for session-based recommendations with PyTorch
  • +
  • +
  • +
+
+
+
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_end-to-end-session-based-02-end-to-end-session-based-with-yoochoose-pyt/nvidia_logo.png +
+

Multi-GPU training for session-based recommendations with PyTorch

+

This notebook was prepared by using the latest merlin-pytorch:22.XX container.

+

In the previous two notebooks, we have first created sequential features and saved our processed data frames as parquet files. Then we used these processed parquet files in training a session-based recommendation model with XLNet architecture on a single GPU. We will now expand this exercise to perform a multi-GPU training with the same dataset using PyTorch.

+

There are multiple ways to scale a training pipeline to multiple GPUs:

+
    +
  • Model Parallel: If the model is too large to fit on a single GPU, the parameters are distributed over multiple GPUs. This is usually the case for the RecSys domain since the embedding tables can be exceptionally large and memory intensive.

  • +
  • Data Parallel: Every GPU has a copy of all model parameters and runs the forward/backward pass for its batch. Data parallel is useful when you want to speed-up the training/evaluation of data leveraging multiple GPUs in parallel (as typically data won’t fit into GPU memory, that is why models are trained on batches).

  • +
+

In this example, we demonstrate how to scale a training pipeline to multi-GPU, single node. The goal is to maximize throughput and reduce training time. In that way, models can be trained more frequently and researches can run more experiments in a shorter time duration.

+

This is equivalent to training with a larger batch-size. As we are using more GPUs, we have more computational resources and can achieve higher throughput. In data parallel training, it is often required that all model parameters fit into a single GPU. Every worker (each GPU) has a copy of the model parameters and runs the forward pass on their local batch. The workers synchronize the gradients with each other, which can introduce an overhead.

+

Learning objectives

+
    +
  • Scaling training pipeline to multiple GPUs

  • +
+

Prerequisites

+
    +
  • Run the 01-ETL-with-NVTabular.ipynb notebook first to generate the dataset and directories that are also needed by this notebook.

  • +
+
+

1. Creating a multi-gpu training python script

+

In this example, we will be using PyTorch, which expects all code including importing libraries, loading data, building the model and training it, to be in a single python script (.py file). We will then spawn torch.distributed.launch, which will distribute the training and evaluation tasks to 2 GPUs with the default DistributedDataParallel configuration.

+

The following cell exports all related code to a pyt_trainer.py file to be created in the same working directory as this notebook. The code is structured as follows:

+
    +
  • importing required libraries

  • +
  • specifying and processing command line arguments

  • +
  • specifying the schema file to load and filtering the features that will be used in training

  • +
  • defining the input module

  • +
  • specifying the prediction task

  • +
  • defining the XLNet Transformer architecture configuration

  • +
  • defining the model object and its training arguments

  • +
  • creating a trainer object and running the training loop (over multiple days) with the trainer object

  • +
+

All of these steps were covered in the previous two notebooks; feel free to visit the two notebooks to refresh the concepts for each step of the script.

+

Please note these important points that are relevant to multi-gpu training:

+
    +
  • specifying multiple GPUs: PyTorch distributed launch environment will recognize that we have two GPUs, since the --nproc_per_node arg of torch.distributed.launch takes care of assigning one GPU per process and performs the training loop on multiple GPUs (2 in this case) using different batches of the data in a data-parallel fashion.

  • +
  • data repartitioning: when training on multiple GPUs, data must be re-partitioned into >1 partitions where the number of partitions must be at least equal to the number of GPUs. The torch utility library in Transformers4Rec does this automatically and outputs a UserWarning message. If you would like to avoid this warning message, you may choose to manually re-partition your data files before you launch the training loop or function. See this document for further information on how to do manual re-partitioning.

  • +
  • training and evaluation batch sizes: in the default DistributedDataParallel mode we will be running, keeping the batch size unchanged means each worker will receive the same-size batch despite the fact that you are now using multiple GPUs. If you would like to keep the total batch size constant, you may want to divide the training and evaluation batch sizes by the number of GPUs you are running on, which is expected to reduce time it takes train and evaluate on each batch.

  • +
+

Finally, if you have worked with the synthetic data set in notebook 01, remember to change the values 178 and 181 in the next cell accordingly:

+
+
+
import os
+TRAINER_FILE = os.path.join(os.environ.get("INPUT_DATA_DIR", "/workspace/data"), "pyt_trainer.py")
+
+
+
+
+
+
+
%%writefile {TRAINER_FILE}
+
+import argparse
+import os
+import glob
+import torch 
+
+import cupy
+
+from transformers4rec import torch as tr
+from transformers4rec.torch.ranking_metric import NDCGAt, AvgPrecisionAt, RecallAt
+from transformers4rec.torch.utils.examples_utils import wipe_memory
+from merlin.schema import Schema
+from merlin.io import Dataset
+
+cupy.cuda.Device(int(os.environ["LOCAL_RANK"])).use()
+
+# define arguments that can be passed to this python script
+parser = argparse.ArgumentParser(description='Hyperparameters for model training')
+parser.add_argument('--path', type=str, help='Directory with training and validation data')
+parser.add_argument('--learning-rate', type=float, default=0.0005, help='Learning rate for training')
+parser.add_argument('--per-device-train-batch-size', type=int, default=64, help='Per device batch size for training')
+parser.add_argument('--per-device-eval-batch-size', type=int, default=32, help='Per device batch size for evaluation')
+sh_args = parser.parse_args()
+
+# create the schema object by reading the processed train set generated in the previous 01-ETL-with-NVTabular notebook
+
+INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data")
+train = Dataset(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+schema = train.schema
+
+# select the subset of features we want to use for training the model by their tags or their names.
+schema = schema.select_by_name(
+   ['item_id-list', 'category-list', 'product_recency_days_log_norm-list', 'et_dayofweek_sin-list']
+)
+
+max_sequence_length, d_model = 20, 320
+# Define input module to process tabular input-features and to prepare masked inputs
+input_module = tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=max_sequence_length,
+    continuous_projection=64,
+    aggregation="concat",
+    d_output=d_model,
+    masking="mlm",
+)
+
+# Define Next item prediction-task 
+prediction_task = tr.NextItemPredictionTask(weight_tying=True)
+
+# Define the config of the XLNet Transformer architecture
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=8, n_layer=2, total_seq_length=max_sequence_length
+)
+
+# Get the end-to-end model 
+model = transformer_config.to_torch_model(input_module, prediction_task)
+
+# Set training arguments 
+training_args = tr.trainer.T4RecTrainingArguments(
+            output_dir="./tmp",
+            max_sequence_length=20,
+            data_loader_engine='merlin',
+            num_train_epochs=10, 
+            dataloader_drop_last=True,
+            per_device_train_batch_size = sh_args.per_device_train_batch_size,
+            per_device_eval_batch_size = sh_args.per_device_eval_batch_size,
+            learning_rate=sh_args.learning_rate,
+            report_to = [],
+            logging_steps=200,
+        )
+
+# Instantiate the trainer
+recsys_trainer = tr.Trainer(
+    model=model,
+    args=training_args,
+    schema=schema,
+    compute_metrics=True)
+
+# Set input and output directories
+INPUT_DIR=sh_args.path
+OUTPUT_DIR=sh_args.path
+
+import time
+start = time.time()
+
+# main loop for training
+start_time_window_index = int(os.environ.get("START_TIME_INDEX", "178"))
+final_time_window_index = int(os.environ.get("END_TIME_INDEX", "181"))
+
+# Iterating over days from 178 to 181
+for time_index in range(start_time_window_index, final_time_window_index):
+    # Set data 
+    time_index_train = time_index
+    time_index_eval = time_index + 1
+    train_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_train}/train.parquet"))
+    eval_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_eval}/valid.parquet"))
+
+    # Train on day related to time_index 
+    print('*'*20)
+    print("Launch training for day %s are:" %time_index)
+    print('*'*20 + '\n')
+    recsys_trainer.train_dataset_or_path = train_paths
+    recsys_trainer.reset_lr_scheduler()
+    recsys_trainer.train()
+    recsys_trainer.state.global_step +=1
+    print('finished')
+
+    # Evaluate on the following day
+    recsys_trainer.eval_dataset_or_path = eval_paths
+    eval_metrics = recsys_trainer.evaluate(metric_key_prefix='eval')
+    print('*'*20)
+    print("Eval results for day %s are:\t" %time_index_eval)
+    print('\n' + '*'*20 + '\n')
+    for key in sorted(eval_metrics.keys()):
+        print(" %s = %s" % (key, str(eval_metrics[key]))) 
+    wipe_memory()
+
+# export evaluation metrics to a file
+import json
+fname = os.path.join(INPUT_DATA_DIR, "eval_metrics.txt")
+f = open(fname, "w")
+f.write(json.dumps(eval_metrics))
+f.close()
+
+end = time.time()
+print('Total training time:', end-start)
+
+
+
+
+
Overwriting /workspace/data/pyt_trainer.py
+
+
+
+
+
+
+

2. Executing the multi-gpu training

+

You are now ready to execute the python script you created. Run the following shell command which will execute the script and perform data loading, model building and training using 2 GPUs.

+

Note that there are four arguments you can pass to your python script, and only the first one is required:

+
    +
  • path: this argument specifies the directory in which to find the multi-day train and validation files to work on

  • +
  • learning rate: you can experiment with different learning rates and see the effect of this hyperparameter when multiple GPUs are used in training (versus 1 GPU). Typically, increasing the learning rate (up to a certain level) as the number of GPUs is increased helps with the accuracy metrics.

  • +
  • per device batch size for training: when using multiple GPUs in DistributedDataParallel mode, you may choose to reduce the batch size in order to keep the total batch size constant. This should help reduce training times.

  • +
  • per device batch size for evaluation: see above

  • +
+
+
+
# If only 1 GPU are available, starts a single process to use that GPU
+from torch.cuda import device_count
+num_gpus = device_count()
+NUM_PROCESSES = min(num_gpus, 2)
+
+
+
+
+
+
+
import os
+OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "/workspace/data/preproc_sessions_by_day")
+LR = float(os.environ.get("LEARNING_RATE", "0.0005"))
+BATCH_SIZE_TRAIN = int(os.environ.get("BATCH_SIZE_TRAIN", "256"))
+BATCH_SIZE_VALID = int(os.environ.get("BATCH_SIZE_VALID", "128"))
+!python -m torch.distributed.run --nproc_per_node {NUM_PROCESSES} {TRAINER_FILE} --path {OUTPUT_DIR} --learning-rate {LR} --per-device-train-batch-size {BATCH_SIZE_TRAIN} --per-device-eval-batch-size {BATCH_SIZE_VALID}
+
+
+
+
+
WARNING:torch.distributed.run:
+*****************************************
+Setting OMP_NUM_THREADS environment variable for each process to be 1 in default, to avoid your system being overloaded, please further tune the variable for optimal performance in your application as needed. 
+*****************************************
+/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+Projecting inputs of NextItemPredictionTask to'64' As weight tying requires the input dimension '320' to be equal to the item-id embedding dimension '64'
+Projecting inputs of NextItemPredictionTask to'64' As weight tying requires the input dimension '320' to be equal to the item-id embedding dimension '64'
+********************
+Launch training for day 178 are:
+********************
+
+********************
+Launch training for day 178 are:
+********************
+
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+***** Running training *****
+  Num examples = 14080
+  Num Epochs = 10
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 512
+  Gradient Accumulation steps = 1
+  Total optimization steps = 550
+{'loss': 7.608, 'learning_rate': 0.0003181818181818182, 'epoch': 3.64}          
+{'loss': 6.6604, 'learning_rate': 0.00013636363636363637, 'epoch': 7.27}        
+ 91%|█████████████████████████████████████▎   | 500/550 [00:27<00:02, 20.71it/s]Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+100%|████████████████████████████████████████▊| 548/550 [00:29<00:00, 24.82it/s]
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+finished
+{'train_runtime': 29.4835, 'train_samples_per_second': 0.339, 'train_steps_per_second': 18.655, 'train_loss': 6.9447163529829545, 'epoch': 10.0}
+100%|█████████████████████████████████████████| 550/550 [00:29<00:00, 18.66it/s]
+finished
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+100%|███████████████████████████████████████████| 10/10 [00:00<00:00, 39.28it/s]********************
+Eval results for day 179 are:	
+
+********************
+
+ eval_/loss = 7.320416450500488
+ eval_/next-item/avg_precision_at_10 = 0.07864832133054733
+ eval_/next-item/avg_precision_at_20 = 0.08251794427633286
+ eval_/next-item/ndcg_at_10 = 0.10605569183826447
+ eval_/next-item/ndcg_at_20 = 0.12029790133237839
+ eval_/next-item/recall_at_10 = 0.19570313394069672
+ eval_/next-item/recall_at_20 = 0.2523437440395355
+ eval_runtime = 0.3992
+ eval_samples_per_second = 3206.723
+ eval_steps_per_second = 12.526
+100%|███████████████████████████████████████████| 10/10 [00:00<00:00, 38.88it/s]
+********************
+Eval results for day 179 are:	
+
+********************
+
+ eval_/loss = 7.320416450500488
+ eval_/next-item/avg_precision_at_10 = 0.07864832133054733
+ eval_/next-item/avg_precision_at_20 = 0.08251794427633286
+ eval_/next-item/ndcg_at_10 = 0.10605569183826447
+ eval_/next-item/ndcg_at_20 = 0.12029790133237839
+ eval_/next-item/recall_at_10 = 0.19570313394069672
+ eval_/next-item/recall_at_20 = 0.2523437440395355
+ eval_runtime = 0.3989
+ eval_samples_per_second = 3209.046
+ eval_steps_per_second = 12.535
+********************
+Launch training for day 179 are:
+********************
+
+********************
+Launch training for day 179 are:
+********************
+
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+***** Running training *****
+  Num examples = 9984
+  Num Epochs = 10
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 512
+  Gradient Accumulation steps = 1
+  Total optimization steps = 390
+{'loss': 6.9038, 'learning_rate': 0.0002435897435897436, 'epoch': 5.13}         
+100%|█████████████████████████████████████████| 390/390 [00:16<00:00, 25.05it/s]
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+finished
+{'train_runtime': 16.8289, 'train_samples_per_second': 0.594, 'train_steps_per_second': 23.174, 'train_loss': 6.640231244991988, 'epoch': 10.0}
+100%|█████████████████████████████████████████| 390/390 [00:16<00:00, 23.18it/s]
+finished
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+ 62%|████████████████████████████▏                | 5/8 [00:00<00:00, 46.46it/s]********************
+Eval results for day 180 are:	
+
+********************
+
+ eval_/loss = 7.8266496658325195
+ eval_/next-item/avg_precision_at_10 = 0.0614391528069973
+ eval_/next-item/avg_precision_at_20 = 0.0654601976275444
+ eval_/next-item/ndcg_at_10 = 0.08422157913446426
+ eval_/next-item/ndcg_at_20 = 0.09865029156208038
+ eval_/next-item/recall_at_10 = 0.1591796875
+ eval_/next-item/recall_at_20 = 0.2158203125
+ eval_runtime = 0.3334
+ eval_samples_per_second = 3071.067
+ eval_steps_per_second = 11.996
+100%|█████████████████████████████████████████████| 8/8 [00:00<00:00, 41.08it/s]
+********************
+Eval results for day 180 are:	
+
+********************
+
+ eval_/loss = 7.8266496658325195
+ eval_/next-item/avg_precision_at_10 = 0.0614391528069973
+ eval_/next-item/avg_precision_at_20 = 0.0654601976275444
+ eval_/next-item/ndcg_at_10 = 0.08422157913446426
+ eval_/next-item/ndcg_at_20 = 0.09865029156208038
+ eval_/next-item/recall_at_10 = 0.1591796875
+ eval_/next-item/recall_at_20 = 0.2158203125
+ eval_runtime = 0.3325
+ eval_samples_per_second = 3080.064
+ eval_steps_per_second = 12.031
+********************
+Launch training for day 180 are:
+********************
+
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+********************
+Launch training for day 180 are:
+********************
+
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+***** Running training *****
+  Num examples = 8192
+  Num Epochs = 10
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 512
+  Gradient Accumulation steps = 1
+  Total optimization steps = 320
+{'loss': 6.6858, 'learning_rate': 0.0001875, 'epoch': 6.25}                     
+100%|████████████████████████████████████████▊| 319/320 [00:14<00:00, 24.21it/s]
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+finished
+{'train_runtime': 14.4363, 'train_samples_per_second': 0.693, 'train_steps_per_second': 22.166, 'train_loss': 6.4950298309326175, 'epoch': 10.0}
+100%|█████████████████████████████████████████| 320/320 [00:14<00:00, 22.17it/s]
+finished
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+UserWarning: User is advised to repartition the parquet file before training so npartitions>=global_size. Cudf or pandas can be used for repartitioning eg. pdf.to_parquet('file.parquet',row_group_size=N_ROWS/NPARTITIONS) for pandas or gdf.to_parquet('file.parquet',row_group_size_rows=N_ROWS/NPARTITIONS) for cudf so that npartitions=nr_rows/row_group_size. Also ensure npartitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).
+100%|█████████████████████████████████████████████| 4/4 [00:00<00:00, 36.98it/s]********************
+Eval results for day 181 are:	
+
+********************
+
+ eval_/loss = 5.795247554779053
+ eval_/next-item/avg_precision_at_10 = 0.12972043454647064
+ eval_/next-item/avg_precision_at_20 = 0.1380162239074707
+ eval_/next-item/ndcg_at_10 = 0.17062446475028992
+ eval_/next-item/ndcg_at_20 = 0.200949028134346
+ eval_/next-item/recall_at_10 = 0.3046875
+ eval_/next-item/recall_at_20 = 0.4248046875
+ eval_runtime = 0.2612
+ eval_samples_per_second = 1960.129
+ eval_steps_per_second = 7.657
+100%|█████████████████████████████████████████████| 4/4 [00:00<00:00, 34.63it/s]
+********************
+Eval results for day 181 are:	
+
+********************
+
+ eval_/loss = 5.795247554779053
+ eval_/next-item/avg_precision_at_10 = 0.12972043454647064
+ eval_/next-item/avg_precision_at_20 = 0.1380162239074707
+ eval_/next-item/ndcg_at_10 = 0.17062446475028992
+ eval_/next-item/ndcg_at_20 = 0.200949028134346
+ eval_/next-item/recall_at_10 = 0.3046875
+ eval_/next-item/recall_at_20 = 0.4248046875
+ eval_runtime = 0.259
+ eval_samples_per_second = 1976.599
+ eval_steps_per_second = 7.721
+Total training time: 66.7806625366211
+Total training time: 66.76544284820557
+
+
+
+
+

Congratulations!!! You successfully trained your model using 2 GPUs with a distributed data parallel approach. If you choose, you may now go back and experiment with some of the hyperparameters (eg. learning rate, batch sizes, number of GPUs) to collect information on various accuracy metrics as well as total training time, to see what fits best into your workflow.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/end-to-end-session-based/index.html b/review/pr-767/examples/end-to-end-session-based/index.html new file mode 100644 index 0000000000..9a70d88285 --- /dev/null +++ b/review/pr-767/examples/end-to-end-session-based/index.html @@ -0,0 +1,162 @@ + + + + + + End-to-end session-based recommendation — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

End-to-end session-based recommendation

+

These end-to-end example notebooks focus on the following:

+
    +
  • Preprocessing the Yoochoose e-commerce dataset.

  • +
  • Generating session features with on GPU.

  • +
  • Using the NVTabular dataloader with PyTorch.

  • +
  • Training a session-based recommendation model with a Transformer architecture (XLNET).

  • +
  • Exporting the preprocessing workflow and trained model to Triton Inference Server (TIS).

  • +
  • Sending request to TIS and generating next-item predictions for each session.

  • +
+

Refer to the following notebooks:

+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/getting-started-session-based/01-ETL-with-NVTabular.html b/review/pr-767/examples/getting-started-session-based/01-ETL-with-NVTabular.html new file mode 100644 index 0000000000..24fd48cfa3 --- /dev/null +++ b/review/pr-767/examples/getting-started-session-based/01-ETL-with-NVTabular.html @@ -0,0 +1,727 @@ + + + + + + ETL with NVTabular — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ======================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_getting-started-session-based-01-etl-with-nvtabular/nvidia_logo.png +
+

ETL with NVTabular

+

In this notebook we are going to generate synthetic data and then create sequential features with NVTabular. Such data will be used in the next notebook to train a session-based recommendation model.

+

NVTabular is a feature engineering and preprocessing library for tabular data designed to quickly and easily manipulate terabyte scale datasets used to train deep learning based recommender systems. It provides a high level abstraction to simplify code and accelerates computation on the GPU using the RAPIDS cuDF library.

+
+

Import required libraries

+
+
+
import os
+os.environ["CUDA_VISIBLE_DEVICES"]="0"
+import glob
+
+import cudf
+import numpy as np
+import pandas as pd
+
+import nvtabular as nvt
+from nvtabular.ops import *
+from merlin.schema.tags import Tags
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+/usr/local/lib/python3.8/dist-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+
+
+
+
+
+
+

Define Input/Output Path

+
+
+
INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data/")
+
+
+
+
+
+
+

Create a Synthetic Input Data

+
+
+
NUM_ROWS = os.environ.get("NUM_ROWS", 100000)
+
+
+
+
+
+
+
long_tailed_item_distribution = np.clip(np.random.lognormal(3., 1., int(NUM_ROWS)).astype(np.int32), 1, 50000)
+# generate random item interaction features 
+df = pd.DataFrame(np.random.randint(70000, 90000, int(NUM_ROWS)), columns=['session_id'])
+df['item_id'] = long_tailed_item_distribution
+
+# generate category mapping for each item-id
+df['category'] = pd.cut(df['item_id'], bins=334, labels=np.arange(1, 335)).astype(np.int32)
+df['age_days'] = np.random.uniform(0, 1, int(NUM_ROWS)).astype(np.float32)
+df['weekday_sin']= np.random.uniform(0, 1, int(NUM_ROWS)).astype(np.float32)
+
+# generate day mapping for each session 
+map_day = dict(zip(df.session_id.unique(), np.random.randint(1, 10, size=(df.session_id.nunique()))))
+df['day'] =  df.session_id.map(map_day)
+
+
+
+
+

Visualize couple of rows of the synthetic dataset:

+
+
+
df.head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
session_iditem_idcategoryage_daysweekday_sinday
0883482870.4160520.1165081
186615620.9987830.5390346
2851611440.9756560.2463313
37588961160.3291820.0337159
4753962980.2191270.9932507
+
+
+
+
+

Feature Engineering with NVTabular

+

Deep Learning models require dense input features. Categorical features are sparse, and need to be represented by dense embeddings in the model. To allow for that, categorical features first need to be encoded as contiguous integers (0, ..., |C|), where |C| is the feature cardinality (number of unique values), so that their embeddings can be efficiently stored in embedding layers. We will use NVTabular to preprocess the categorical features, so that all categorical columns are encoded as contiguous integers. Note that the Categorify op encodes nulls to 1, OOVs to 2 automatically. We preserve 0 for padding. The encoding of other categories starts from 3. In our synthetic dataset we do not have any nulls. On the other hand 0 is used for padding the sequences in input block.

+

Here our goal is to create sequential features. To do so, we are grouping the features together at the session level in the following cell. In this synthetically generated example dataset, we do not have a timestamp column, but if we had one (that’s the case for most real-world datasets), we would be sorting the interactions by the timestamp column as in this example notebook. Note that we also trim each feature sequence in a session to a certain length. Here, we use the NVTabular library so that we can easily preprocess and create features on GPU with a few lines.

+
+
+
SESSIONS_MAX_LENGTH =20
+
+# Categorify categorical features
+categ_feats = ['item_id', 'category'] >> nvt.ops.Categorify()
+
+# Define Groupby Workflow
+groupby_feats = categ_feats + ['session_id', 'day', 'age_days', 'weekday_sin']
+
+# Group interaction features by session
+groupby_features = groupby_feats >> nvt.ops.Groupby(
+    groupby_cols=["session_id"], 
+    aggs={
+        "item_id": ["list", "count"],
+        "category": ["list"],     
+        "day": ["first"],
+        "age_days": ["list"],
+        'weekday_sin': ["list"],
+        },
+    name_sep="-")
+
+# Select and truncate the sequential features
+sequence_features_truncated = (
+    groupby_features['category-list']
+    >> nvt.ops.ListSlice(-SESSIONS_MAX_LENGTH) 
+)
+
+sequence_features_truncated_item = (
+    groupby_features['item_id-list']
+    >> nvt.ops.ListSlice(-SESSIONS_MAX_LENGTH) 
+    >> TagAsItemID()
+)  
+sequence_features_truncated_cont = (
+    groupby_features['age_days-list', 'weekday_sin-list'] 
+    >> nvt.ops.ListSlice(-SESSIONS_MAX_LENGTH) 
+    >> nvt.ops.AddMetadata(tags=[Tags.CONTINUOUS])
+)
+
+# Filter out sessions with length 1 (not valid for next-item prediction training and evaluation)
+MINIMUM_SESSION_LENGTH = 2
+selected_features = (
+    groupby_features['item_id-count', 'day-first', 'session_id'] + 
+    sequence_features_truncated_item +
+    sequence_features_truncated + 
+    sequence_features_truncated_cont
+)
+    
+filtered_sessions = selected_features >> nvt.ops.Filter(f=lambda df: df["item_id-count"] >= MINIMUM_SESSION_LENGTH)
+
+seq_feats_list = filtered_sessions['item_id-list', 'category-list', 'age_days-list', 'weekday_sin-list'] >>  nvt.ops.ValueCount()
+
+workflow = nvt.Workflow(filtered_sessions['session_id', 'day-first'] + seq_feats_list)
+
+dataset = nvt.Dataset(df)
+
+# Generate statistics for the features and export parquet files
+# this step will generate the schema file
+workflow.fit_transform(dataset).to_parquet(os.path.join(INPUT_DATA_DIR, "processed_nvt"))
+
+
+
+
+

It is possible to save the preprocessing workflow. That is useful to apply the same preprocessing to other data (with the same schema) and also to deploy the session-based recommendation pipeline to Triton Inference Server.

+
+
+
workflow.output_schema
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametagsdtypeis_listis_raggedproperties.num_bucketsproperties.freq_thresholdproperties.max_sizeproperties.cat_pathproperties.domain.minproperties.domain.maxproperties.domain.nameproperties.embedding_sizes.cardinalityproperties.embedding_sizes.dimensionproperties.value_count.minproperties.value_count.max
0session_id()DType(name='int64', element_type=<ElementType....FalseFalseNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
1day-first()DType(name='int64', element_type=<ElementType....FalseFalseNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
2item_id-list(Tags.CATEGORICAL, Tags.ID, Tags.LIST, Tags.ITEM)DType(name='int64', element_type=<ElementType....TrueTrueNaN0.00.0.//categories/unique.item_id.parquet0.0494.0item_id495.052.02.016.0
3category-list(Tags.CATEGORICAL, Tags.LIST)DType(name='int64', element_type=<ElementType....TrueTrueNaN0.00.0.//categories/unique.category.parquet0.0171.0category172.029.02.016.0
4age_days-list(Tags.CONTINUOUS, Tags.LIST)DType(name='float32', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaN2.016.0
5weekday_sin-list(Tags.CONTINUOUS, Tags.LIST)DType(name='float32', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaN2.016.0
+
+
+

Save NVTabular workflow.

+
+
+
workflow.save(os.path.join(INPUT_DATA_DIR, "workflow_etl"))
+
+
+
+
+
+
+

Export pre-processed data by day

+

In this example we are going to split the preprocessed parquet files by days, to allow for temporal training and evaluation. There will be a folder for each day and three parquet files within each day folder: train.parquet, validation.parquet and test.parquet.

+
+
+
OUTPUT_DIR = os.environ.get("OUTPUT_DIR",os.path.join(INPUT_DATA_DIR, "sessions_by_day"))
+
+
+
+
+
+
+
# Read in the processed parquet file
+sessions_gdf = cudf.read_parquet(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+
+
+
+
+
+
+
print(sessions_gdf.head(3))
+
+
+
+
+
   session_id  day-first              item_id-list            category-list  \
+0       70000          1          [306, 5, 40, 17]          [104, 3, 12, 6]   
+1       70001          1       [43, 20, 69, 8, 57]       [13, 6, 21, 3, 16]   
+2       70002          1  [137, 35, 37, 85, 65, 5]  [37, 10, 11, 22, 18, 3]   
+
+                                       age_days-list  \
+0   [0.044022594, 0.34956282, 0.7326993, 0.09403495]   
+1  [0.8072543, 0.28916782, 0.04966254, 0.08417622...   
+2  [0.04696693, 0.94499177, 0.2922437, 0.83047426...   
+
+                                    weekday_sin-list  
+0    [0.7417527, 0.60325843, 0.07417604, 0.28911334]  
+1  [0.7995051, 0.86722755, 0.84298295, 0.15793765...  
+2  [0.72519076, 0.92308444, 0.40120387, 0.3821016...  
+
+
+
+
+
+
+
from transformers4rec.utils.data_utils import save_time_based_splits
+save_time_based_splits(data=nvt.Dataset(sessions_gdf),
+                       output_dir= OUTPUT_DIR,
+                       partition_col='day-first',
+                       timestamp_col='session_id', 
+                      )
+
+
+
+
+
Creating time-based splits: 100%|██████████| 9/9 [00:02<00:00,  4.12it/s]
+
+
+
+
+
+
+

Check out the preprocessed outputs

+
+
+
TRAIN_PATHS = os.path.join(OUTPUT_DIR, "1", "train.parquet")
+
+
+
+
+
+
+
df = pd.read_parquet(TRAIN_PATHS)
+df.head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
session_iditem_id-listcategory-listage_days-listweekday_sin-list
070000[306, 5, 40, 17][104, 3, 12, 6][0.044022594, 0.34956282, 0.7326993, 0.09403495][0.7417527, 0.60325843, 0.07417604, 0.28911334]
170001[43, 20, 69, 8, 57][13, 6, 21, 3, 16][0.8072543, 0.28916782, 0.04966254, 0.08417622...[0.7995051, 0.86722755, 0.84298295, 0.15793765...
270002[137, 35, 37, 85, 65, 5][37, 10, 11, 22, 18, 3][0.04696693, 0.94499177, 0.2922437, 0.83047426...[0.72519076, 0.92308444, 0.40120387, 0.3821016...
470007[28, 9, 153, 74, 53, 15, 173][9, 4, 39, 20, 15, 5, 46][0.4730765, 0.69885534, 0.034774363, 0.7225920...[0.33613566, 0.660022, 0.72897774, 0.66087157,...
570021[59, 32, 11, 21, 23, 23, 9, 15][17, 10, 7, 7, 8, 8, 4, 5][0.07898139, 0.27463168, 0.1885847, 0.5203435,...[0.39734098, 0.74895114, 0.43540764, 0.8372503...
+
+
+
+
+
import gc
+del df
+gc.collect()
+
+
+
+
+
512
+
+
+
+
+

You have just created session-level features to train a session-based recommendation model using NVTabular. Now you can move to the the next notebook,02-session-based-XLNet-with-PyT.ipynb to train a session-based recommendation model using XLNet, one of the state-of-the-art NLP model. Please shut down this kernel to free the GPU memory before you start the next one.

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/getting-started-session-based/02-session-based-XLNet-with-PyT.html b/review/pr-767/examples/getting-started-session-based/02-session-based-XLNet-with-PyT.html new file mode 100644 index 0000000000..0473ae6df7 --- /dev/null +++ b/review/pr-767/examples/getting-started-session-based/02-session-based-XLNet-with-PyT.html @@ -0,0 +1,1010 @@ + + + + + + Session-based Recommendation with XLNET — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_getting-started-session-based-02-session-based-xlnet-with-pyt/nvidia_logo.png +
+

Session-based Recommendation with XLNET

+

This notebook is created using the latest stable merlin-pytorch container.

+

In this notebook we introduce the Transformers4Rec library for sequential and session-based recommendation. This notebook uses the PyTorch API. Transformers4Rec integrates with the popular HuggingFace’s Transformers and makes it possible to experiment with a cutting-edge implementation of the latest NLP Transformer architectures.

+

We demonstrate how to build a session-based recommendation model with the XLNET Transformer architecture. The XLNet architecture was designed to leverage the best of both auto-regressive language modeling and auto-encoding with its Permutation Language Modeling training method. In this example we will use XLNET with masked language modeling (MLM) training method, which showed very promising results in the experiments conducted in our ACM RecSys’21 paper.

+

In the previous notebook we went through our ETL pipeline with the NVTabular library, and created sequential features to be used in training a session-based recommendation model. In this notebook we will learn:

+
    +
  • Accelerating data loading of parquet files with multiple features on PyTorch using NVTabular library

  • +
  • Training and evaluating a Transformer-based (XLNET-MLM) session-based recommendation model with multiple features

  • +
+
+

Build a DL model with Transformers4Rec library

+

Transformers4Rec supports multiple input features and provides configurable building blocks that can be easily combined for custom architectures:

+
    +
  • TabularSequenceFeatures class that reads from schema and creates an input block. This input module combines different types of features (continuous, categorical & text) to a sequence.

  • +
  • MaskSequence to define masking schema and prepare the masked inputs and labels for the selected LM task.

  • +
  • TransformerBlock class that supports HuggingFace Transformers for session-based and sequential-based recommendation models.

  • +
  • SequentialBlock creates the body by mimicking torch.nn.sequential class. It is designed to define our model as a sequence of layers.

  • +
  • Head where we define the prediction task of the model.

  • +
  • NextItemPredictionTask is the class to support next item prediction task.

  • +
  • Trainer extends the Trainer class from HF transformers and manages the model training and evaluation.

  • +
+

You can check the full documentation of Transformers4Rec if needed.

+

Figure 1 illustrates Transformers4Rec meta-architecture and how each module/block interacts with each other.

+

tf4rec_meta

+
+

Import required libraries

+
+
+
import os
+
+os.environ["CUDA_VISIBLE_DEVICES"]="0"
+
+import glob
+import torch 
+
+from transformers4rec import torch as tr
+from transformers4rec.torch.ranking_metric import NDCGAt, AvgPrecisionAt, RecallAt
+from transformers4rec.torch.utils.examples_utils import wipe_memory
+
+
+
+
+

Transformers4Rec library relies on a schema object to automatically build all necessary layers to represent, normalize and aggregate input features. As you can see below, schema.pb is a protobuf file that contains metadata including statistics about features such as cardinality, min and max values and also tags features based on their characteristics and dtypes (e.g., categorical, continuous, list, integer).

+
+
+

Set the schema object

+

We create the schema object by reading the processed train parquet file generated by NVTabular pipeline in the previous, 01-ETL-with-NVTabular, notebook.

+
+
+
INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data")
+OUTPUT_DIR = os.environ.get("OUTPUT_DIR", f"{INPUT_DATA_DIR}/sessions_by_day")
+
+
+
+
+
+
+
from merlin.schema import Schema
+from merlin.io import Dataset
+
+train = Dataset(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+schema = train.schema
+
+
+
+
+
/workspace/merlin/core/merlin/schema/tags.py:149: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
+  warnings.warn(
+
+
+
+
+
+
+
# You can select a subset of features for training
+schema = schema.select_by_name(['item_id-list', 
+                                'category-list', 
+                                'weekday_sin-list',
+                                'age_days-list'])
+
+
+
+
+

Let’s print out the schema.

+
+
+
schema
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametagsdtypeis_listis_raggedproperties.embedding_sizes.dimensionproperties.embedding_sizes.cardinalityproperties.num_bucketsproperties.start_indexproperties.max_sizeproperties.cat_pathproperties.freq_thresholdproperties.domain.minproperties.domain.maxproperties.domain.nameproperties.value_count.minproperties.value_count.max
0item_id-list(Tags.CATEGORICAL, Tags.ITEM_ID, Tags.ITEM, Ta...DType(name='int64', element_type=<ElementType....TrueTrue52.0493.0NaN0.00.0.//categories/unique.item_id.parquet0.00.0492.0item_id216
1category-list(Tags.LIST, Tags.CATEGORICAL)DType(name='int64', element_type=<ElementType....TrueTrue29.0173.0NaN0.00.0.//categories/unique.category.parquet0.00.0172.0category216
2weekday_sin-list(Tags.LIST, Tags.CONTINUOUS)DType(name='float32', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN216
3age_days-list(Tags.LIST, Tags.CONTINUOUS)DType(name='float32', element_type=<ElementTyp...TrueTrueNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN216
+
+
+
+
+

Define the sequential input module

+

Below we define our input block using the TabularSequenceFeatures class. The from_schema() method processes the schema and creates the necessary layers to represent features and aggregate them. It keeps only features tagged as categorical and continuous and supports data aggregation methods like concat and elementwise-sum. It also supports data augmentation techniques like stochastic swap noise. It outputs an interaction representation after combining all features and also the input mask according to the training task (more on this later).

+

The max_sequence_length argument defines the maximum sequence length of our sequential input, and if continuous_projection argument is set, all numerical features are concatenated and projected by an MLP block so that continuous features are represented by a vector of size defined by user, which is 64 in this example.

+
+
+
inputs = tr.TabularSequenceFeatures.from_schema(
+        schema,
+        max_sequence_length=20,
+        continuous_projection=64,
+        masking="mlm",
+        d_output=100,
+)
+
+
+
+
+

The output of the TabularSequenceFeatures module is the sequence of interactions embedding vectors defined in the following steps:

+
    +
    1. +
    2. Create sequence inputs: If the schema contains non sequential features, expand each feature to a sequence by repeating the value as many times as the max_sequence_length value.

    3. +
    +
  • +
    1. +
    2. Get a representation vector of categorical features: Project each sequential categorical feature using the related embedding table. The resulting tensor is of shape (bs, max_sequence_length, embed_dim).

    3. +
    +
  • +
    1. +
    2. Project scalar values if continuous_projection is set : Apply an MLP layer with hidden size equal to continuous_projection vector size value. The resulting tensor is of shape (batch_size, max_sequence_length, continuous_projection).

    3. +
    +
  • +
    1. +
    2. Aggregate the list of features vectors to represent each interaction in the sequence with one vector: For example, concat will concat all vectors based on the last dimension -1 and the resulting tensor will be of shape (batch_size, max_sequence_length, D) where D is the sum over all embedding dimensions and the value of continuous_projection.

    3. +
    +
  • +
    1. +
    2. If masking schema is set (needed only for the NextItemPredictionTask training), the masked labels are derived from the sequence of raw item-ids and the sequence of interactions embeddings are processed to mask information about the masked positions.

    3. +
    +
  • +
+
+
+

Define the Transformer block

+

In the next cell, the whole model is build with a few lines of code. +Here is a brief explanation of the main classes:

+
    +
  • XLNetConfig - We have injected in the HF transformers config classes like XLNetConfigthe build() method that provides default configuration to Transformer architectures for session-based recommendation. Here we use it to instantiate and configure an XLNET architecture.

  • +
  • TransformerBlock class integrates with HF Transformers, which are made available as a sequence processing module for session-based and sequential-based recommendation models.

  • +
  • NextItemPredictionTask supports the next-item prediction task. We also support other predictions tasks, like classification and regression for the whole sequence.

  • +
+
+
+
# Define XLNetConfig class and set default parameters for HF XLNet config  
+transformer_config = tr.XLNetConfig.build(
+    d_model=64, n_head=4, n_layer=2, total_seq_length=20
+)
+# Define the model block including: inputs, masking, projection and transformer block.
+body = tr.SequentialBlock(
+    inputs, tr.MLPBlock([64]), tr.TransformerBlock(transformer_config, masking=inputs.masking)
+)
+
+# Define the evaluation top-N metrics and the cut-offs
+metrics = [NDCGAt(top_ks=[20, 40], labels_onehot=True),  
+           RecallAt(top_ks=[20, 40], labels_onehot=True)]
+
+# Define a head related to next item prediction task 
+head = tr.Head(
+    body,
+    tr.NextItemPredictionTask(weight_tying=True, 
+                              metrics=metrics),
+    inputs=inputs,
+)
+
+# Get the end-to-end Model class 
+model = tr.Model(head)
+
+
+
+
+

Note that we can easily define an RNN-based model inside the SequentialBlock instead of a Transformer-based model. You can explore this tutorial for a GRU-based model example.

+
+
+

Train the model

+

We use the Merlin Dataloader’s PyTorch Dataloader for optimized loading of multiple features from input parquet files. You can learn more about this data loader here.

+
+
+

Set Training arguments

+
+
+
per_device_train_batch_size = int(os.environ.get(
+    "per_device_train_batch_size", 
+    '128'
+))
+
+per_device_eval_batch_size = int(os.environ.get(
+    "per_device_eval_batch_size", 
+    '32'
+))
+
+
+
+
+
+
+
from transformers4rec.config.trainer import T4RecTrainingArguments
+from transformers4rec.torch import Trainer
+# Set hyperparameters for training 
+train_args = T4RecTrainingArguments(data_loader_engine='merlin', 
+                                    dataloader_drop_last = True,
+                                    gradient_accumulation_steps = 1,
+                                    per_device_train_batch_size = per_device_train_batch_size, 
+                                    per_device_eval_batch_size = per_device_eval_batch_size,
+                                    output_dir = "./tmp", 
+                                    learning_rate=0.0005,
+                                    lr_scheduler_type='cosine', 
+                                    learning_rate_num_cosine_cycles_by_epoch=1.5,
+                                    num_train_epochs=5,
+                                    max_sequence_length=20, 
+                                    report_to = [],
+                                    logging_steps=50,
+                                    no_cuda=False)
+
+
+
+
+
PyTorch: setting up devices
+
+
+
+
+

Note that we add an argument data_loader_engine='merlin' to automatically load the features needed for training using the schema. The default value is merlin for optimized GPU-based data-loading. Optionally a PyarrowDataLoader (pyarrow) can also be used as a basic option, but it is slower and works only for small datasets, as the full data is loaded to CPU memory.

+
+
+
+

Daily Fine-Tuning: Training over a time window

+

Here we do daily fine-tuning meaning that we use the first day to train and second day to evaluate, then we use the second day data to train the model by resuming from the first step, and evaluate on the third day, so on and so forth.

+

We have extended the HuggingFace transformers Trainer class (PyTorch only) to support evaluation of RecSys metrics. In this example, the evaluation of the session-based recommendation model is performed using traditional Top-N ranking metrics such as Normalized Discounted Cumulative Gain (NDCG@20) and Hit Rate (HR@20). NDCG accounts for rank of the relevant item in the recommendation list and is a more fine-grained metric than HR, which only verifies whether the relevant item is among the top-n items. HR@n is equivalent to Recall@n when there is only one relevant item in the recommendation list.

+
+
+
# Instantiate the T4Rec Trainer, which manages training and evaluation for the PyTorch API
+trainer = Trainer(
+    model=model,
+    args=train_args,
+    schema=schema,
+    compute_metrics=True,
+)
+
+
+
+
+

Define the output folder of the processed parquet files:

+
+
+
start_window_index = int(os.environ.get(
+    "start_window_index", 
+    '1'
+))
+
+final_window_index = int(os.environ.get(
+    "final_window_index", 
+    '8'
+))
+
+
+
+
+
+
+
start_time_window_index = start_window_index
+final_time_window_index = final_window_index
+#Iterating over days of one week
+for time_index in range(start_time_window_index, final_time_window_index):
+    # Set data 
+    time_index_train = time_index
+    time_index_eval = time_index + 1
+    train_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_train}/train.parquet"))
+    eval_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_eval}/valid.parquet"))
+    print(train_paths)
+    
+    # Train on day related to time_index 
+    print('*'*20)
+    print("Launch training for day %s are:" %time_index)
+    print('*'*20 + '\n')
+    trainer.train_dataset_or_path = train_paths
+    trainer.reset_lr_scheduler()
+    trainer.train()
+    trainer.state.global_step +=1
+    print('finished')
+    
+    # Evaluate on the following day
+    trainer.eval_dataset_or_path = eval_paths
+    train_metrics = trainer.evaluate(metric_key_prefix='eval')
+    print('*'*20)
+    print("Eval results for day %s are:\t" %time_index_eval)
+    print('\n' + '*'*20 + '\n')
+    for key in sorted(train_metrics.keys()):
+        print(" %s = %s" % (key, str(train_metrics[key]))) 
+    wipe_memory()
+
+
+
+
+
/venv/lib/python3.8/site-packages/transformers/optimization.py:306: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning
+  warnings.warn(
+***** Running training *****
+  Num examples = 1664
+  Num Epochs = 5
+  Instantaneous batch size per device = 128
+  Total train batch size (w. parallel, distributed & accumulation) = 128
+  Gradient Accumulation steps = 1
+  Total optimization steps = 65
+
+
+
['/workspace/data/sessions_by_day/1/train.parquet']
+********************
+Launch training for day 1 are:
+********************
+
+
+
+
+ + + [65/65 00:01, Epoch 5/5] +
+ + + + + + + + + + + + + +
StepTraining Loss
505.878700

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
finished
+
+
+
+
+ + + [6/6 00:14] +
+
********************
+Eval results for day 2 are:	
+
+********************
+
+ eval_/loss = 5.100889682769775
+ eval_/next-item/ndcg_at_20 = 0.18669554591178894
+ eval_/next-item/ndcg_at_40 = 0.2293076366186142
+ eval_/next-item/recall_at_20 = 0.5052083730697632
+ eval_/next-item/recall_at_40 = 0.7135416865348816
+ eval_runtime = 0.1324
+ eval_samples_per_second = 1449.793
+ eval_steps_per_second = 45.306
+['/workspace/data/sessions_by_day/2/train.parquet']
+********************
+Launch training for day 2 are:
+********************
+
+
+
***** Running training *****
+  Num examples = 1536
+  Num Epochs = 5
+  Instantaneous batch size per device = 128
+  Total train batch size (w. parallel, distributed & accumulation) = 128
+  Gradient Accumulation steps = 1
+  Total optimization steps = 60
+
+
+
+
+ + + [60/60 00:01, Epoch 5/5] +
+ + + + + + + + + + + + + +
StepTraining Loss
504.931100

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
finished
+********************
+Eval results for day 3 are:	
+
+********************
+
+ eval_/loss = 4.713772296905518
+ eval_/next-item/ndcg_at_20 = 0.1680772304534912
+ eval_/next-item/ndcg_at_40 = 0.21330341696739197
+ eval_/next-item/recall_at_20 = 0.46875
+ eval_/next-item/recall_at_40 = 0.6875
+ eval_runtime = 0.1286
+ eval_samples_per_second = 1492.732
+ eval_steps_per_second = 46.648
+['/workspace/data/sessions_by_day/3/train.parquet']
+********************
+Launch training for day 3 are:
+********************
+
+
+
***** Running training *****
+  Num examples = 1536
+  Num Epochs = 5
+  Instantaneous batch size per device = 128
+  Total train batch size (w. parallel, distributed & accumulation) = 128
+  Gradient Accumulation steps = 1
+  Total optimization steps = 60
+
+
+
+
+ + + [60/60 00:01, Epoch 5/5] +
+ + + + + + + + + + + + + +
StepTraining Loss
504.586300

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
finished
+********************
+Eval results for day 4 are:	
+
+********************
+
+ eval_/loss = 4.556211948394775
+ eval_/next-item/ndcg_at_20 = 0.18394140899181366
+ eval_/next-item/ndcg_at_40 = 0.2390844076871872
+ eval_/next-item/recall_at_20 = 0.484375
+ eval_/next-item/recall_at_40 = 0.7552083730697632
+ eval_runtime = 0.1311
+ eval_samples_per_second = 1464.947
+ eval_steps_per_second = 45.78
+
+
+
***** Running training *****
+  Num examples = 1664
+  Num Epochs = 5
+  Instantaneous batch size per device = 128
+  Total train batch size (w. parallel, distributed & accumulation) = 128
+  Gradient Accumulation steps = 1
+  Total optimization steps = 65
+
+
+
['/workspace/data/sessions_by_day/4/train.parquet']
+********************
+Launch training for day 4 are:
+********************
+
+
+
+
+ + + [65/65 00:01, Epoch 5/5] +
+ + + + + + + + + + + + + +
StepTraining Loss
504.518600

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
finished
+********************
+Eval results for day 5 are:	
+
+********************
+
+ eval_/loss = 4.407809734344482
+ eval_/next-item/ndcg_at_20 = 0.20426493883132935
+ eval_/next-item/ndcg_at_40 = 0.24358350038528442
+ eval_/next-item/recall_at_20 = 0.5885416865348816
+ eval_/next-item/recall_at_40 = 0.78125
+ eval_runtime = 0.1327
+ eval_samples_per_second = 1447.378
+ eval_steps_per_second = 45.231
+
+
+
***** Running training *****
+  Num examples = 1664
+  Num Epochs = 5
+  Instantaneous batch size per device = 128
+  Total train batch size (w. parallel, distributed & accumulation) = 128
+  Gradient Accumulation steps = 1
+  Total optimization steps = 65
+
+
+
['/workspace/data/sessions_by_day/5/train.parquet']
+********************
+Launch training for day 5 are:
+********************
+
+
+
+
+ + + [65/65 00:01, Epoch 5/5] +
+ + + + + + + + + + + + + +
StepTraining Loss
504.488800

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
finished
+********************
+Eval results for day 6 are:	
+
+********************
+
+ eval_/loss = 4.5086541175842285
+ eval_/next-item/ndcg_at_20 = 0.19850534200668335
+ eval_/next-item/ndcg_at_40 = 0.2486129105091095
+ eval_/next-item/recall_at_20 = 0.5052083730697632
+ eval_/next-item/recall_at_40 = 0.75
+ eval_runtime = 0.1258
+ eval_samples_per_second = 1526.789
+ eval_steps_per_second = 47.712
+
+
+
***** Running training *****
+  Num examples = 1664
+  Num Epochs = 5
+  Instantaneous batch size per device = 128
+  Total train batch size (w. parallel, distributed & accumulation) = 128
+  Gradient Accumulation steps = 1
+  Total optimization steps = 65
+
+
+
['/workspace/data/sessions_by_day/6/train.parquet']
+********************
+Launch training for day 6 are:
+********************
+
+
+
+
+ + + [65/65 00:01, Epoch 5/5] +
+ + + + + + + + + + + + + +
StepTraining Loss
504.496300

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
finished
+********************
+Eval results for day 7 are:	
+
+********************
+
+ eval_/loss = 4.442317962646484
+ eval_/next-item/ndcg_at_20 = 0.1833297461271286
+ eval_/next-item/ndcg_at_40 = 0.23786288499832153
+ eval_/next-item/recall_at_20 = 0.5052083730697632
+ eval_/next-item/recall_at_40 = 0.7760416865348816
+ eval_runtime = 0.143
+ eval_samples_per_second = 1342.609
+ eval_steps_per_second = 41.957
+['/workspace/data/sessions_by_day/7/train.parquet']
+********************
+Launch training for day 7 are:
+********************
+
+
+
***** Running training *****
+  Num examples = 1792
+  Num Epochs = 5
+  Instantaneous batch size per device = 128
+  Total train batch size (w. parallel, distributed & accumulation) = 128
+  Gradient Accumulation steps = 1
+  Total optimization steps = 70
+
+
+
+
+ + + [70/70 00:02, Epoch 5/5] +
+ + + + + + + + + + + + + +
StepTraining Loss
504.489300

Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
finished
+********************
+Eval results for day 8 are:	
+
+********************
+
+ eval_/loss = 4.538415431976318
+ eval_/next-item/ndcg_at_20 = 0.17900079488754272
+ eval_/next-item/ndcg_at_40 = 0.22817844152450562
+ eval_/next-item/recall_at_20 = 0.5052083730697632
+ eval_/next-item/recall_at_40 = 0.7447916865348816
+ eval_runtime = 0.1381
+ eval_samples_per_second = 1390.084
+ eval_steps_per_second = 43.44
+
+
+
+
+
+

Re-compute evaluation metrics of the validation data

+
+
+
eval_data_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_eval}/valid.parquet"))
+
+
+
+
+
+
+
# set new data from day 7
+eval_metrics = trainer.evaluate(eval_dataset=eval_data_paths, metric_key_prefix='eval')
+for key in sorted(eval_metrics.keys()):
+    print("  %s = %s" % (key, str(eval_metrics[key])))
+
+
+
+
+
  eval_/loss = 4.538415431976318
+  eval_/next-item/ndcg_at_20 = 0.17900079488754272
+  eval_/next-item/ndcg_at_40 = 0.22817844152450562
+  eval_/next-item/recall_at_20 = 0.5052083730697632
+  eval_/next-item/recall_at_40 = 0.7447916865348816
+  eval_runtime = 0.1281
+  eval_samples_per_second = 1498.788
+  eval_steps_per_second = 46.837
+
+
+
+
+
+
+

Save the model

+

Let’s save the model to be able to load it back at inference step. Using model.save(), we save the model as a pkl file in the given path.

+
+
+
model_path= os.environ.get("OUTPUT_DIR", f"{INPUT_DATA_DIR}/saved_model")
+model.save(model_path)
+
+
+
+
+

That’s it! You have just trained your session-based recommendation model using Transformers4Rec. Now you can move on to the next notebook 03-serving-session-based-model-torch-backend. Please shut down this kernel to free the GPU memory before you start the next one.

+

Tip: We can easily log and visualize model training and evaluation on Weights & Biases (W&B), TensorBoard, or NVIDIA DLLogger. By default, the HuggingFace transformers Trainer (which we extend) uses Weights & Biases (W&B) to log training and evaluation metrics, which provides nice visualization results and comparison between different runs.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/getting-started-session-based/03-serving-session-based-model-torch-backend.html b/review/pr-767/examples/getting-started-session-based/03-serving-session-based-model-torch-backend.html new file mode 100644 index 0000000000..a947966689 --- /dev/null +++ b/review/pr-767/examples/getting-started-session-based/03-serving-session-based-model-torch-backend.html @@ -0,0 +1,828 @@ + + + + + + Serving a Session-based Recommendation model with Torch Backend — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Serving a Session-based Recommendation model with Torch Backend
  • +
  • +
  • +
+
+
+
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com//notebooks/dlsw-notebooks/remtting-started-session-based-03-serving-session-based-model-torch-backend/nvidia_logo.png +
+

Serving a Session-based Recommendation model with Torch Backend

+

This notebook is created using the latest stable merlin-pytorch container.

+

At this point, when you reach out to this notebook, we expect that you have already executed the 01-ETL-with-NVTabular.ipynb and 02-session-based-XLNet-with-PyT.ipynb notebooks, and saved the NVT workflow and the trained session-based model.

+

In this notebook, you are going to learn how you can serve a trained Transformer-based PyTorch model on NVIDIA Triton Inference Server (TIS) with Torch backend using Merlin systems library. One common way to do inference with a trained model is to use TorchScript, an intermediate representation of a PyTorch model that can be run in Python as well as in a high performance environment like C++. TorchScript is actually the recommended model format for scaled inference and deployment. TIS PyTorch (LibTorch) backend is designed to run TorchScript models using the PyTorch C++ API.

+

Triton Inference Server (TIS) simplifies the deployment of AI models at scale in production. TIS provides a cloud and edge inferencing solution optimized for both CPUs and GPUs. It supports a number of different machine learning frameworks such as TensorFlow and PyTorch.

+
+

Import required libraries

+
+
+
import os
+os.environ["CUDA_VISIBLE_DEVICES"]="0"
+
+import cudf
+import glob
+import numpy as np
+import pandas as pd
+import torch 
+
+from transformers4rec import torch as tr
+from merlin.io import Dataset
+
+from merlin.core.dispatch import make_df 
+from merlin.systems.dag import Ensemble  
+from merlin.systems.dag.ops.pytorch import PredictPyTorch 
+from merlin.systems.dag.ops.workflow import TransformWorkflow 
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+
+
+
+
+

We define the paths

+
+
+
INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data")
+OUTPUT_DIR = os.environ.get("OUTPUT_DIR", f"{INPUT_DATA_DIR}/sessions_by_day")
+model_path= os.environ.get("model_path", f"{INPUT_DATA_DIR}/saved_model")
+
+
+
+
+
+
+

Set the schema object

+

We create the schema object by reading the processed train parquet file.

+
+
+
from merlin.schema import Schema
+from merlin.io import Dataset
+
+train = Dataset(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+schema = train.schema
+
+
+
+
+

We need to load the saved model to be able to serve it on TIS.

+
+
+
import cloudpickle
+loaded_model = cloudpickle.load(
+                open(os.path.join(model_path, "t4rec_model_class.pkl"), "rb")
+            )
+
+
+
+
+

Switch the model to eval mode. We call model.eval() before tracing to set dropout and batch normalization layers to evaluation mode before running inference. Failing to do this might yield inconsistent inference results.

+
+
+
model = loaded_model.cuda()
+model.eval()
+
+
+
+
+
Model(
+  (heads): ModuleList(
+    (0): Head(
+      (body): SequentialBlock(
+        (0): TabularSequenceFeatures(
+          (to_merge): ModuleDict(
+            (continuous_module): SequentialBlock(
+              (0): ContinuousFeatures(
+                (filter_features): FilterFeatures()
+                (_aggregation): ConcatFeatures()
+              )
+              (1): SequentialBlock(
+                (0): DenseBlock(
+                  (0): Linear(in_features=2, out_features=64, bias=True)
+                  (1): ReLU(inplace=True)
+                )
+              )
+              (2): AsTabular()
+            )
+            (categorical_module): SequenceEmbeddingFeatures(
+              (filter_features): FilterFeatures()
+              (embedding_tables): ModuleDict(
+                (item_id-list): Embedding(495, 64, padding_idx=0)
+                (category-list): Embedding(172, 64, padding_idx=0)
+              )
+            )
+          )
+          (_aggregation): ConcatFeatures()
+          (projection_module): SequentialBlock(
+            (0): DenseBlock(
+              (0): Linear(in_features=192, out_features=100, bias=True)
+              (1): ReLU(inplace=True)
+            )
+          )
+          (_masking): MaskedLanguageModeling()
+        )
+        (1): SequentialBlock(
+          (0): DenseBlock(
+            (0): Linear(in_features=100, out_features=64, bias=True)
+            (1): ReLU(inplace=True)
+          )
+        )
+        (2): TansformerBlock(
+          (transformer): XLNetModel(
+            (word_embedding): Embedding(1, 64)
+            (layer): ModuleList(
+              (0): XLNetLayer(
+                (rel_attn): XLNetRelativeAttention(
+                  (layer_norm): LayerNorm((64,), eps=0.03, elementwise_affine=True)
+                  (dropout): Dropout(p=0.3, inplace=False)
+                )
+                (ff): XLNetFeedForward(
+                  (layer_norm): LayerNorm((64,), eps=0.03, elementwise_affine=True)
+                  (layer_1): Linear(in_features=64, out_features=256, bias=True)
+                  (layer_2): Linear(in_features=256, out_features=64, bias=True)
+                  (dropout): Dropout(p=0.3, inplace=False)
+                )
+                (dropout): Dropout(p=0.3, inplace=False)
+              )
+              (1): XLNetLayer(
+                (rel_attn): XLNetRelativeAttention(
+                  (layer_norm): LayerNorm((64,), eps=0.03, elementwise_affine=True)
+                  (dropout): Dropout(p=0.3, inplace=False)
+                )
+                (ff): XLNetFeedForward(
+                  (layer_norm): LayerNorm((64,), eps=0.03, elementwise_affine=True)
+                  (layer_1): Linear(in_features=64, out_features=256, bias=True)
+                  (layer_2): Linear(in_features=256, out_features=64, bias=True)
+                  (dropout): Dropout(p=0.3, inplace=False)
+                )
+                (dropout): Dropout(p=0.3, inplace=False)
+              )
+            )
+            (dropout): Dropout(p=0.3, inplace=False)
+          )
+          (masking): MaskedLanguageModeling()
+        )
+      )
+      (prediction_task_dict): ModuleDict(
+        (next-item): NextItemPredictionTask(
+          (sequence_summary): SequenceSummary(
+            (summary): Identity()
+            (activation): Identity()
+            (first_dropout): Identity()
+            (last_dropout): Identity()
+          )
+          (metrics): ModuleList(
+            (0): NDCGAt()
+            (1): RecallAt()
+          )
+          (loss): CrossEntropyLoss()
+          (embeddings): SequenceEmbeddingFeatures(
+            (filter_features): FilterFeatures()
+            (embedding_tables): ModuleDict(
+              (item_id-list): Embedding(495, 64, padding_idx=0)
+              (category-list): Embedding(172, 64, padding_idx=0)
+            )
+          )
+          (item_embedding_table): Embedding(495, 64, padding_idx=0)
+          (masking): MaskedLanguageModeling()
+          (pre): Block(
+            (module): NextItemPredictionTask(
+              (item_embedding_table): Embedding(495, 64, padding_idx=0)
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
+
+
+
+
+
+

Trace the model

+

We serve the model with the PyTorch backend that is used to execute TorchScript models. All models created in PyTorch using the python API must be traced/scripted to produce a TorchScript model. For tracing the model, we use torch.jit.trace api that takes the model as a Python function or torch.nn.Module, and an example input that will be passed to the function while tracing.

+
+
+
train_paths = os.path.join(OUTPUT_DIR, f"{1}/train.parquet")
+dataset = Dataset(train_paths)
+
+
+
+
+

Create a dict of tensors to feed it as example inputs in the torch.jit.trace().

+
+
+
import pandas as pd
+from merlin.table import TensorTable, TorchColumn
+from merlin.table.conversions import convert_col
+
+df = cudf.read_parquet(train_paths, columns=model.input_schema.column_names)
+table = TensorTable.from_df(df.loc[:100])
+for column in table.columns:
+    table[column] = convert_col(table[column], TorchColumn)
+model_input_dict = table.to_dict()
+
+
+
+
+
+
+
model_input_dict['item_id-list__values']
+
+
+
+
+
tensor([306,   5,  40,  17,  43,  20,  69,   8,  57, 137,  35,  37,  85,  65,
+          5,  28,   9, 153,  74,  53,  15, 173,  59,  32,  11,  21,  23,  23,
+          9,  15,  12,  69,  37,  16,   6,  22,  39,  20,  22,  95,  40,   7,
+         25,  32,  17,   8,  26,  32,  33,  18,  12,  10,  41,  14,  28,  56,
+         30,  21,  16,  42,  13,  83,  65,  46, 105,  38,  11,   3,   3,  14,
+          9,  36, 116,  15,  15,  23,   8,  16,  68, 151,  60,  18,  48,  19,
+         16,   4,  37, 246, 169,  21,  16, 116,  27,   4,  19,  76,   6,  31,
+        153,  38,  35,  11,  38,   3,  73,  38,  74,   6,   7,  12,  18,  10,
+         54,  11,  29,   5,  24,  11,  20,   3,  17,  42,  26,  24,  30,  26,
+         62,  89,  12,  38,  18,   3,  10,  18,  15, 131,  19,   6,  51,  60,
+         10,   3,  14,  22,  21,  39,  44, 221,  88,  14,  16,  80,   5,  16,
+         21,  81,  27,   8,  20,  49,  32,  83,  49,  19,   3,  17,   8,  10,
+         29,  62,  94,  38,  15,  11,  12,  16,  10,  31,   7,  53,   3,  42,
+         38,  25,   5,  62,  20,  73,  48,   6,  12,  19,  15,  38,  30,   9,
+         82,  31,  49,  64,  22,  38,  10,  56,  11,  13,   3,  14,  39,  18,
+         47,  65,  18,  15,   9,  74,   3,  50,  37,  22,  66,  47,  23,  17,
+          8,  21,  35,   7,  12,  16,  21,  26,  31,  13,  20,   9, 193,  49,
+          9,  62,  51,  45,  90,  14,  47,   9,  73,  16,   3,  62,  24,  82,
+          7,  14,  37,  29,  26,  42,   6,  90,   3,  10,  33,   7,   7,  10,
+         31,  10,  12,  21,  55,  25,  21,   3,  20,  24,  25,   4,   3,  52,
+          5,   5,  10,  12,  37, 162,  31,   5, 119,   5,  24,  65,   4,  10,
+         46,  86,   5,  58,  15,  48,  66,  14,  23,  12,  13,   6,  48,   8,
+         22,  95,   5,  42,  86, 108,  26,   7,  80,  54,  63,  12, 147, 177,
+         17,  18,  24,  15,  40,   5,  40,   7,   6,  63,   4,  18, 123,  33,
+         36,  25,  40,  18,  16,  10,  18,  26,  21,  59,  44,  12,  28,  30,
+        134,   7,  21,   8,   7,  32,  41,  60,  52,  25,  36,   6,  45,  39,
+         16,  20,  95,   8,  56,  53,  48,  17,  14,   3,  46,  35,  17,  12,
+         30,   8,   5,  54,  75,  96,   4,  43,   8,  61,   4,   8,  34,  30,
+         34,  49,  29,  92,   6,  28,  26,  22,  46,  20,  11,  14,  13,  75,
+         22,  21,  17, 166,   4,  87,   5,  11,  37,  26,  23],
+       device='cuda:0')
+
+
+
+
+
+
+
traced_model = torch.jit.trace(model, model_input_dict, strict=True)
+
+
+
+
+

Generate model input and output schemas to feed in the PredictPyTorch operator below.

+
+
+
input_schema = model.input_schema
+output_schema = model.output_schema
+
+
+
+
+
+
+
input_schema
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametagsdtypeis_listis_raggedproperties.value_count.minproperties.value_count.maxproperties.num_bucketsproperties.freq_thresholdproperties.max_sizeproperties.cat_pathproperties.embedding_sizes.cardinalityproperties.embedding_sizes.dimensionproperties.domain.minproperties.domain.maxproperties.domain.name
0weekday_sin-list(Tags.LIST, Tags.CONTINUOUS)DType(name='float32', element_type=<ElementTyp...TrueTrue216NaNNaNNaNNaNNaNNaNNaNNaNNaN
1age_days-list(Tags.LIST, Tags.CONTINUOUS)DType(name='float32', element_type=<ElementTyp...TrueTrue216NaNNaNNaNNaNNaNNaNNaNNaNNaN
2item_id-list(Tags.CATEGORICAL, Tags.LIST, Tags.ITEM, Tags.ID)DType(name='int64', element_type=<ElementType....TrueTrue216NaN0.00.0.//categories/unique.item_id.parquet495.052.00.0494.0item_id
3category-list(Tags.CATEGORICAL, Tags.LIST)DType(name='int64', element_type=<ElementType....TrueTrue216NaN0.00.0.//categories/unique.category.parquet172.029.00.0171.0category
+
+
+

Let’s create a folder that we can store the exported models and the config files.

+
+
+
import shutil
+ens_model_path = os.environ.get("ens_model_path", f"{INPUT_DATA_DIR}/models")
+# Make sure we have a clean stats space for Dask
+if os.path.isdir(ens_model_path):
+    shutil.rmtree(ens_model_path)
+os.mkdir(ens_model_path)
+
+
+
+
+

We want to serve NVT model and our trained session-based model together as an ensemble to the Triton Inference Server. That way we can send raw requests to Triton and return back item scores per session. For that we need to load our save workflow first.

+
+
+
from nvtabular.workflow import Workflow
+workflow = Workflow.load(os.path.join(INPUT_DATA_DIR, "workflow_etl"))
+print(workflow.input_schema.column_names)
+
+
+
+
+
['item_id', 'category', 'day', 'age_days', 'weekday_sin', 'session_id']
+
+
+
+
+

For transforming the raw input features during inference, we use TransformWorkflow operator that ensures the workflow is correctly saved and packaged with the required config so the server will know how to load it. We use PredictPyTorch operator that takes a pytorch model and packages it correctly for tritonserver to run on the PyTorch backend.

+
+
+
torch_op = workflow.input_schema.column_names >> TransformWorkflow(workflow) >> PredictPyTorch(
+    traced_model, input_schema, output_schema
+)
+
+ensemble = Ensemble(torch_op, workflow.input_schema)
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/systems/dag/node.py:100: UserWarning: Operator 'TransformWorkflow' is producing the output column 'session_id', which is not being used by any downstream operator in the ensemble graph.
+  warnings.warn(
+/usr/local/lib/python3.8/dist-packages/merlin/systems/dag/node.py:100: UserWarning: Operator 'TransformWorkflow' is producing the output column 'day-first', which is not being used by any downstream operator in the ensemble graph.
+  warnings.warn(
+
+
+
+
+

The last step is to create the ensemble artifacts that Triton Inference Server can consume. To make these artifacts, we import the Ensemble class. The class is responsible for interpreting the graph and exporting the correct files for the server.

+

When we create an Ensemble object we supply the graph and a schema representing the starting input of the graph. The inputs to the ensemble graph are the inputs to the first operator of out graph. After we created the Ensemble we export the graph, supplying an export path for the ensemble.export function. This returns an ensemble config which represents the entire inference pipeline and a list of node-specific configs.

+
+
+
ens_config, node_configs = ensemble.export(ens_model_path)
+
+
+
+
+
+
+
ensemble.input_schema
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametagsdtypeis_listis_ragged
0item_id()DType(name='int32', element_type=<ElementType....FalseFalse
1category()DType(name='int32', element_type=<ElementType....FalseFalse
2day()DType(name='int64', element_type=<ElementType....FalseFalse
3age_days()DType(name='float32', element_type=<ElementTyp...FalseFalse
4weekday_sin()DType(name='float32', element_type=<ElementTyp...FalseFalse
5session_id()DType(name='int64', element_type=<ElementType....FalseFalse
+
+
+
+
+

Starting Triton Server

+

It is time to deploy all the models as an ensemble model to Triton Inference Serve TIS. After we export the ensemble, we are ready to start the TIS. You can start triton server by using the following command on your terminal:

+

tritonserver --model-repository=<ensemble_export_path>

+

For the --model-repository argument, specify the same path as the export_path that you specified previously in the ensemble.export method. This command will launch the server and load all the models to the server. Once all the models are loaded successfully, you should see READY status printed out in the terminal for each loaded model.

+
+
+
import tritonclient.http as client
+
+# Create a triton client
+try:
+    triton_client = client.InferenceServerClient(url="localhost:8000", verbose=True)
+    print("client created.")
+except Exception as e:
+    print("channel creation failed: " + str(e))
+
+
+
+
+
client created.
+
+
+
+
+

After we create the client and verified it is connected to the server instance, we can communicate with the server and ensure all the models are loaded correctly.

+
+
+
# ensure triton is in a good state
+triton_client.is_server_live()
+triton_client.get_model_repository_index()
+
+
+
+
+
GET /v2/health/live, headers None
+<HTTPSocketPoolResponse status=200 headers={'content-length': '0', 'content-type': 'text/plain'}>
+POST /v2/repository/index, headers None
+
+<HTTPSocketPoolResponse status=200 headers={'content-type': 'application/json', 'content-length': '188'}>
+bytearray(b'[{"name":"0_transformworkflowtriton","version":"1","state":"READY"},{"name":"1_predictpytorchtriton","version":"1","state":"READY"},{"name":"executor_model","version":"1","state":"READY"}]')
+
+
+
[{'name': '0_transformworkflowtriton', 'version': '1', 'state': 'READY'},
+ {'name': '1_predictpytorchtriton', 'version': '1', 'state': 'READY'},
+ {'name': 'executor_model', 'version': '1', 'state': 'READY'}]
+
+
+
+
+
+

Send request to Triton and get the response

+

The last step of a machine learning (ML)/deep learning (DL) pipeline is to deploy the model to production, and get responses for a given query or a set of queries. +In this section, we generate a dataframe that we can serve as a request to TIS. We do serve the raw dataframe and in the production setting, we want to transform the input data as done during training (ETL). We need to apply the same mean/std for continuous features and use the same categorical mapping to convert the categories to continuous integer before we use the deployed DL model for a prediction.

+

Let’s generate a dataframe with raw input values. We can send this dataframe to Triton as a request.

+
+
+
NUM_ROWS =1000
+long_tailed_item_distribution = np.clip(np.random.lognormal(3., 1., int(NUM_ROWS)).astype(np.int32), 1, 50000)
+# generate random item interaction features 
+df = pd.DataFrame(np.random.randint(70000, 90000, int(NUM_ROWS)), columns=['session_id'])
+df['item_id'] = long_tailed_item_distribution
+
+# generate category mapping for each item-id
+df['category'] = pd.cut(df['item_id'], bins=334, labels=np.arange(1, 335)).astype(np.int32)
+df['age_days'] = np.random.uniform(0, 1, int(NUM_ROWS)).astype(np.float32)
+df['weekday_sin']= np.random.uniform(0, 1, int(NUM_ROWS)).astype(np.float32)
+
+# generate day mapping for each session 
+map_day = dict(zip(df.session_id.unique(), np.random.randint(1, 10, size=(df.session_id.nunique()))))
+df['day'] =  df.session_id.map(map_day)
+
+print(df.head(2))
+
+
+
+
+
   session_id  item_id  category  age_days  weekday_sin  day
+0       79856        3         2  0.327276     0.080060    2
+1       74117        6         4  0.012172     0.147716    1
+
+
+
+
+

Once our models are successfully loaded to the TIS, we can now easily send a request to TIS and get a response for our query with send_triton_request utility function.

+
+
+
from merlin.systems.triton.utils import send_triton_request
+response = send_triton_request(workflow.input_schema, df, output_schema.column_names, endpoint="localhost:8001")
+response
+
+
+
+
+
{'next-item': array([[-3.9399953, -2.632081 , -4.2211075, ..., -3.6699016, -3.673493 ,
+         -3.1244578],
+        [-3.940445 , -2.6335964, -4.2203593, ..., -3.671566 , -3.6745713,
+         -3.1240335],
+        [-3.9393594, -2.6300201, -4.222065 , ..., -3.6674871, -3.672068 ,
+         -3.1251097],
+        ...,
+        [-3.9396427, -2.6304667, -4.2218847, ..., -3.6677885, -3.6724825,
+         -3.1250875],
+        [-3.939829 , -2.6316376, -4.221267 , ..., -3.6693997, -3.6732295,
+         -3.1245873],
+        [-3.9399223, -2.631995 , -4.2210817, ..., -3.669589 , -3.6734715,
+         -3.1244512]], dtype=float32)}
+
+
+
+
+
+
+
response['next-item'].shape
+
+
+
+
+
(28, 495)
+
+
+
+
+

We return a response for each request in the df. Each row in the response['next-item'] array corresponds to the logit values per item in the catalog, and one logit score corresponding to the null, OOV and padded items. The first score of each array in each row corresponds to the score for the padded item, OOV or null item. Note that we dont have OOV or null items in our syntheticall generated datasets.

+

This is the end of this suit of examples. You successfully performed feature engineering with NVTabular trained transformer architecture based session-based recommendation models with Transformers4Rec deployed the saved workflow and the trained model to Triton Inference Server with Torch backend, sent request and got responses from the server. If you would like to learn how to serve a TF4Rec model with Python backend please visit this example.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/getting-started-session-based/index.html b/review/pr-767/examples/getting-started-session-based/index.html new file mode 100644 index 0000000000..245442fee9 --- /dev/null +++ b/review/pr-767/examples/getting-started-session-based/index.html @@ -0,0 +1,160 @@ + + + + + + Getting Started: Session-based Recommendation with Synthetic Data — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Getting Started: Session-based Recommendation with Synthetic Data

+

This example notebook focuses on the following basic concepts of Transformers4Rec:

+
    +
  • Generating synthetic data of user interactions.

  • +
  • Preprocessing sequential data with NVTabular on GPU.

  • +
  • Using the NVTabular dataloader with PyTorch.

  • +
  • Training a session-based recommendation model with a Transformer architecture (XLNET).

  • +
+

Refer to the following notebooks:

+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/index.html b/review/pr-767/examples/index.html new file mode 100644 index 0000000000..74cc8ff52a --- /dev/null +++ b/review/pr-767/examples/index.html @@ -0,0 +1,235 @@ + + + + + + Transformers4Rec Example Notebooks — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Transformers4Rec Example Notebooks
  • +
  • +
  • +
+
+
+
+
+ +
+

Transformers4Rec Example Notebooks

+

We have collection of example Jupyter notebooks using different datasets to demonstrate how to use Transformers4Rec with the PyTorch API. Each example notebook provides incremental information about Transformers4Rec features and modules.

+
+

Inventory

+
+

Getting started - Session-based recommendation with Synthetic Data

+

This example notebook is focusing primarily on the basic concepts of Transformers4Rec, which includes:

+
    +
  • Generating synthetic data of user interactions

  • +
  • Preprocessing sequential data with NVTabular on GPU

  • +
  • Using the NVTabular dataloader with Pytorch

  • +
  • Training a session-based recommendation model with a Transformer architecture (XLNET)

  • +
+
+
+

End-to-end session-based recommendation

+

This end-to-end example notebook is focuses on:

+
    +
  • Preprocessing the Yoochoose e-commerce dataset

  • +
  • Generating session features with on GPU

  • +
  • Using the NVTabular dataloader with PyTorch

  • +
  • Training a session-based recommendation model with a Transformer architecture (XLNET)

  • +
  • Exporting the preprocessing workflow and trained model to Triton Inference Server (TIS)

  • +
  • Sending request to TIS and generating next-item predictions for each session

  • +
+
+
+

Tutorial - End-to-End Session-Based Recommendation on GPU

+

This tutorial was presented at the ACM RecSys 2021. It covers the following topics:

+
    +
  • The main concepts for session-based recommendation

  • +
  • Implementation of preprocessing and feature engineering techniques for session-based recommendation model with NVTabular on GPU

  • +
  • How to build, train and evaluate a session-based recommendation models based on RNN and Transformer architectures with Transformers4Rec library

  • +
  • How to deploy a session-based recommendation pipeline (preprocessing workflow and trained model) to the Triton Inference Server

  • +
+
+
+

Transformers4Rec paper experiments reproducibility

+

This example contains scripts to reproduce the experiments reported in the Transformers4Rec paper at RecSys 2021. The experiments focused in the session-based recommendation using incremental training and evaluation over time on two e-commerce and two news datasets.

+
+
+
+

Running the Example Notebooks

+

You can run the examples with Docker containers. +Docker containers are available from the NVIDIA GPU Cloud. +Access the catalog of containers at http://ngc.nvidia.com/catalog/containers.

+

The merlin-pytorch container is suited to running the Transformers4Rec notebooks. +The container is available from the NGC catalog at the following URL:

+

https://catalog.ngc.nvidia.com/orgs/nvidia/teams/merlin/containers/merlin-pytorch

+

This container includes the Merlin Core, Merlin Models, Merlin Systems, NVTabular and PyTorch libraries.

+

To run the example notebooks using the container, perform the following steps:

+
    +
  1. If you haven’t already created a Docker volume to share models and datasets +between containers, create the volume by running the following command:

    +
    docker volume create merlin-examples
    +
    +
    +
  2. +
  3. Pull and start the container by running the following command:

    +
    docker run --gpus all --rm -it \
    +  -p 8888:8888 -p 8797:8787 -p 8796:8786 --ipc=host \
    +  -v merlin-examples:/workspace/data \
    +  <docker container> /bin/bash
    +
    +
    +

    The container opens a shell when the run command execution is completed. +Your shell prompt should look similar to the following example:

    +
    root@2efa5b50b909:
    +
    +
    +
  4. +
  5. Install JupyterLab with pip by running the following command:

    +
    pip install jupyterlab
    +
    +
    +

    For more information, see the JupyterLab Installation Guide.

    +
  6. +
  7. Start the JupyterLab server by running the following command:

    +
    jupyter-lab --allow-root --ip='0.0.0.0'
    +
    +
    +

    View the messages in your terminal to identify the URL for JupyterLab. +The messages in your terminal show similar lines to the following example:

    +
    Or copy and paste one of these URLs:
    +http://2efa5b50b909:8888/lab?token=9b537d1fda9e4e9cadc673ba2a472e247deee69a6229ff8d
    +or http://127.0.0.1:8888/lab?token=9b537d1fda9e4e9cadc673ba2a472e247deee69a6229ff8d
    +
    +
    +
  8. +
  9. Open a browser and use the 127.0.0.1 URL provided in the messages by JupyterLab.

  10. +
  11. After you log in to JupyterLab, navigate to the /transformers4rec/examples directory to try out the example notebooks.

  12. +
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/t4rec_paper_experiments/index.html b/review/pr-767/examples/t4rec_paper_experiments/index.html new file mode 100644 index 0000000000..7b3a26a7a9 --- /dev/null +++ b/review/pr-767/examples/t4rec_paper_experiments/index.html @@ -0,0 +1,221 @@ + + + + + + Transformers4Rec paper - Experiments reproducibility — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Transformers4Rec paper - Experiments reproducibility

+
+

Context

+

The Transformers4Rec library was introduced in a paper at RecSys’21, which reports experiments on session-based recommendation for two e-commerce and two news datasets.

+

The original experiments for that paper were performed in a former pre-release version of the Transformers4Rec library tagged as recsys2021, which can be used for the full reproducibility of paper experiments, as detailed here in the paper online appendices.

+
+
+

Paper experiments reproducibility with the released Transformers4Rec API

+

In this example, we demonstrate how to reproduce the most of the paper experiments (only Transformers, not the baselines algorithms) with the PyTorch API of the released Transformers4Rec library.

+

For researchers and practitioners aiming to perform experiments similar to the ones presented in our paper (e.g. incremental training and evaluation of session-based recommendation with Transformers), we strongly encourage the usage of our released PyTorch API (like in this example), because it is more modularized and documented than the original scripts, and is supported by the NVIDIA Merlin team.

+

A few warnings:

+
    +
  • It is natural to find some differences in evaluation metrics results, as the library was completely refactored after the paper experiments and even the same random seeds won’t initialize the model weights identically when layers are build in different order.

  • +
  • WIP: We are still working to add a few missing components in our PyTorch API necessary to reproduce some experiments: (1) Including ALBERT and ELECTRA and (2) including the Replacement Token Detection (RTD) training task. You can track the progress of those issues in #262 and #263.

  • +
+
+

Datasets

+

We have used four datasets for the paper experiments with session-based recommendation:

+
    +
  • REES46 ecommerce

  • +
  • YOOCHOOSE ecommerce

  • +
  • G1 news

  • +
  • ADRESSA news

  • +
+

We provide links to download the original datasets and preprocessing scripts here, i.e., for creating features and grouping interactions features by sessions.

+

But for your convenience we also provide the pre-processed version of the datasets for download, so that you can directly jump into running experiments with Transformers4Rec.

+
+
+

Requirements and Setup

+

To run the experiments you need:

+
    +
  • Install Transformers4Rec for PyTorch API and NVTabular (more instructions here): pip install transformers4rec[pytorch,nvtabular]

  • +
  • Install the example additional requirements (available in this folder): pip install -r requirements.txt

  • +
+
+
+

Weights & Biases logging setup

+

By default, Huggingface uses Weights & Biases (W&B) to log training and evaluation metrics. +It allows a nice management of experiments, including config logging, and provides plots with the evolution of losses and metrics over time. +To see the experiment metrics reported in W&B you can follow the following steps. Otherwise you need to disable wandb sync to the online service by setting this environment variable: WANDB_MODE="dryrun".

+
    +
  1. Create account if you don’t have and obtain API key: https://www.wandb.com

  2. +
  3. Run the following command and follow the instructions to get an API key.

  4. +
+
wandb login
+
+
+

After you get the API key, you can set it as an environment variable with

+
export WANDB_API_KEY=<YOUR API KEY HERE>
+
+
+
+
+

Training and evaluation commands

+

In our paper we have performed hyperparameter tuning for each experiment group (dataset and algorithm pair), whose search space and best hyperparameters can be found in the paper Online Appendix C. The command lines to run each experiment group with the best hyperparameters using the original scripts can be found here.

+

This example script was implemented using the released PyTorch API of the Transformers4Rec library, keeping compatibility with the command line arguments used for the original scripts.

+

To reproduce the paper experiments with this example, you just need to perform two replacements in the original scripts command lines:

+
    +
  1. Replace the Python package name and script hf4rec.recsys_main by t4r_paper_repro.transf_exp_main

  2. +
  3. Replace the argument --feature_config [.yaml file path] by --features_schema_path [schema file path], as previously we used an YAML file to configure dataset features and now we use a features schema protobuf text file for the same purpose.

  4. +
  5. For experiments using multiple features (RQ3), include the --use_side_information_features argument

  6. +
+

Below is the updated command to reproduce the experiment TRANSFORMERS WITH MULTIPLE FEATURES - XLNet (MLM) for the REES46 ECOMMERCE DATASET.

+
DATA_PATH=~/transformers4rec_paper_preproc_datasets/ecom_rees46/
+FEATURE_SCHEMA_PATH=datasets_configs/ecom_rees46/rees46_schema.pbtxt
+CUDA_VISIBLE_DEVICES=0 python3 -m t4r_paper_repro.transf_exp_main --output_dir ./tmp/ --overwrite_output_dir --do_train --do_eval --validate_every 10 --logging_steps 20 --save_steps 0 --data_path $DATA_PATH --features_schema_path $FEATURE_SCHEMA_PATH --fp16 --data_loader_engine merlin --start_time_window_index 1 --final_time_window_index 30 --time_window_folder_pad_digits 4 --model_type xlnet --loss_type cross_entropy --per_device_eval_batch_size 512 --similarity_type concat_mlp --tf_out_activation tanh --inp_merge mlp --learning_rate_warmup_steps 0 --learning_rate_schedule linear_with_warmup --hidden_act gelu --num_train_epochs 10 --dataloader_drop_last --compute_metrics_each_n_steps 1 --session_seq_length_max 20 --eval_on_last_item_seq_only --mf_constrained_embeddings --layer_norm_featurewise --attn_type bi --mlm --input_features_aggregation concat --per_device_train_batch_size 256 --learning_rate 0.00020171456712823088 --dropout 0.0 --input_dropout 0.0 --weight_decay 2.747484129693843e-05 --d_model 448 --item_embedding_dim 448 --n_layer 2 --n_head 8 --label_smoothing 0.5 --stochastic_shared_embeddings_replacement_prob 0.0 --item_id_embeddings_init_std 0.09 --other_embeddings_init_std 0.015 --mlm_probability 0.1 --embedding_dim_from_cardinality_multiplier 3.0 --eval_on_test_set --seed 100 --use_side_information_features
+
+
+
+

Remarks

+

For the experiments with multiple input features and element-wise aggregation of features (--input_features_aggregation elementwise_sum_multiply_item_embedding), it is necessary that all features have the same dimension. In the original implementation of the paper experiments we used a linear layer to project the continuous features to the the same dimension of the categorical embeddings. But that option is not available in the API, as the soft-one hot embedding technique is more effective to represent continuous features. So reproducing exactly the experiment results for the element-wise aggregation will not be possible with the new API, but instead we recommend enabling Soft-One Hot Embeddings to represent continuous features, by setting the arguments --numeric_features_project_to_embedding_dim to be equal to the --item_embedding_dim value and also --numeric_features_soft_one_hot_encoding_num_embeddings to the number of desired embeddings (generally a value between 10-20 is a good default choice).

+
+
+
+

Code organization

+

The main script of this example is the transf_exp_main.py script that is available from the t4r_paper_repro directory of the GitHub repository. +This script parses the command line arguments and use the Transformers4Rec PyTorch API to build a model for session-based recommendation according to the arguments and perform incremental training and evaluation over time.

+

The available command-line arguments are configured in the transf_exp_args.py script and logic for logging and saving results is available in the exp_outputs.py script.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/tutorial/01-preprocess.html b/review/pr-767/examples/tutorial/01-preprocess.html new file mode 100644 index 0000000000..efb181c8c4 --- /dev/null +++ b/review/pr-767/examples/tutorial/01-preprocess.html @@ -0,0 +1,1017 @@ + + + + + + Preliminary Preprocessing — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_tutorial-01-preprocess/nvidia_logo.png +
+

Preliminary Preprocessing

+

Read and Process E-Commerce data

+

In this notebook, we are going to use a subset of a publicly available eCommerce dataset. The full dataset contains 7 months data (from October 2019 to April 2020) from a large multi-category online store. Each row in the file represents an event. All events are related to products and users. Each event is like many-to-many relation between products and users. +Data collected by Open CDP project and the source of the dataset is REES46 Marketing Platform.

+

We use only 2019-Oct.csv file for training our models, so you can visit this site and download the csv file: https://www.kaggle.com/mkechinov/ecommerce-behavior-data-from-multi-category-store.

+
+

Import the required libraries

+
+
+
import os
+import numpy as np 
+import gc
+import shutil
+import glob
+
+import cudf
+import nvtabular as nvt
+
+
+
+
+
+
+

Read Data via cuDF from CSV

+

At this point we expect that you have already downloaded the 2019-Oct.csv dataset and stored it in the INPUT_DATA_DIR as defined below. It is worth mentioning that the raw dataset is ~ 6 GB, therefore a single GPU with 16 GB or less memory might run out of memory.

+
+
+
# define some information about where to get our data
+INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data/")
+
+
+
+
+
+
+
%%time
+raw_df = cudf.read_csv(os.path.join(INPUT_DATA_DIR, '2019-Oct.csv')) 
+raw_df.head()
+
+
+
+
+
CPU times: user 3.2 s, sys: 1.5 s, total: 4.69 s
+Wall time: 5.32 s
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
event_timeevent_typeproduct_idcategory_idcategory_codebrandpriceuser_iduser_session
02019-10-01 00:00:00 UTCview446000622103807459595387724<NA>shiseido35.7954131214072d76fde-8bb3-4e00-8c23-a032dfed738c
12019-10-01 00:00:00 UTCview39008212053013552326770905appliances.environment.water_heateraqua33.205547487179333dfbd-b87a-4708-9857-6336556b0fcc
22019-10-01 00:00:01 UTCview172005062053013559792632471furniture.living_room.sofa<NA>543.10519107250566511c2-e2e3-422b-b695-cf8e6e792ca8
32019-10-01 00:00:01 UTCview13070672053013558920217191computers.notebooklenovo251.745500508547c90fc70-0e80-4590-96f3-13c02c18c713
42019-10-01 00:00:04 UTCview10042372053013555631882655electronics.smartphoneapple1081.98535871217c6bd7419-2748-4c56-95b4-8cec9ff8b80d
+
+
+
+
+
raw_df.shape
+
+
+
+
+
(42448764, 9)
+
+
+
+
+
+
+

Convert timestamp from datetime

+
+
+
raw_df['event_time_dt'] = raw_df['event_time'].astype('datetime64[s]')
+raw_df['event_time_ts']= raw_df['event_time_dt'].astype('int')
+raw_df.head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
event_timeevent_typeproduct_idcategory_idcategory_codebrandpriceuser_iduser_sessionevent_time_dtevent_time_ts
02019-10-01 00:00:00 UTCview446000622103807459595387724<NA>shiseido35.7954131214072d76fde-8bb3-4e00-8c23-a032dfed738c2019-10-01 00:00:001569888000
12019-10-01 00:00:00 UTCview39008212053013552326770905appliances.environment.water_heateraqua33.205547487179333dfbd-b87a-4708-9857-6336556b0fcc2019-10-01 00:00:001569888000
22019-10-01 00:00:01 UTCview172005062053013559792632471furniture.living_room.sofa<NA>543.10519107250566511c2-e2e3-422b-b695-cf8e6e792ca82019-10-01 00:00:011569888001
32019-10-01 00:00:01 UTCview13070672053013558920217191computers.notebooklenovo251.745500508547c90fc70-0e80-4590-96f3-13c02c18c7132019-10-01 00:00:011569888001
42019-10-01 00:00:04 UTCview10042372053013555631882655electronics.smartphoneapple1081.98535871217c6bd7419-2748-4c56-95b4-8cec9ff8b80d2019-10-01 00:00:041569888004
+
+
+
+
+
# check out the columns with nulls
+raw_df.isnull().any()
+
+
+
+
+
event_time       False
+event_type       False
+product_id       False
+category_id      False
+category_code     True
+brand             True
+price            False
+user_id          False
+user_session      True
+event_time_dt    False
+event_time_ts    False
+dtype: bool
+
+
+
+
+
+
+
# Remove rows where `user_session` is null.
+raw_df = raw_df[raw_df['user_session'].isnull()==False]
+len(raw_df)
+
+
+
+
+
42448762
+
+
+
+
+

We no longer need event_time column.

+
+
+
raw_df = raw_df.drop(['event_time'],  axis=1)
+
+
+
+
+
+
+

Categorify user_session column

+

Although user_session is not used as an input feature for the model, it is useful to convert those raw long string to int values to avoid potential failures when grouping interactions by user_session in the next notebook.

+
+
+
cols = list(raw_df.columns)
+cols.remove('user_session')
+cols
+
+
+
+
+
['event_type',
+ 'product_id',
+ 'category_id',
+ 'category_code',
+ 'brand',
+ 'price',
+ 'user_id',
+ 'event_time_dt',
+ 'event_time_ts']
+
+
+
+
+
+
+
# load data 
+df_event = nvt.Dataset(raw_df) 
+
+# categorify user_session 
+cat_feats = ['user_session'] >> nvt.ops.Categorify()
+
+workflow = nvt.Workflow(cols + cat_feats)
+workflow.fit(df_event)
+df = workflow.transform(df_event).to_ddf().compute()
+
+
+
+
+
+
+
df.head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
user_sessionevent_typeproduct_idcategory_idcategory_codebrandpriceuser_idevent_time_dtevent_time_ts
05126085view446000622103807459595387724<NA>shiseido35.795413121402019-10-01 00:00:001569888000
17854470view39008212053013552326770905appliances.environment.water_heateraqua33.205547487172019-10-01 00:00:001569888000
2730655view172005062053013559792632471furniture.living_room.sofa<NA>543.105191072502019-10-01 00:00:011569888001
31637332view13070672053013558920217191computers.notebooklenovo251.745500508542019-10-01 00:00:011569888001
44202155view10042372053013555631882655electronics.smartphoneapple1081.985358712172019-10-01 00:00:041569888004
+
+
+
+
+
raw_df = None
+del(raw_df)
+
+
+
+
+
+
+
gc.collect()
+
+
+
+
+
145
+
+
+
+
+
+
+

Removing consecutive repeated (user, item) interactions

+

We keep repeated interactions on the same items, removing only consecutive interactions, because it might be due to browser tab refreshes or different interaction types (e.g. click, add-to-card, purchase)

+
+
+
%%time
+df = df.sort_values(['user_session', 'event_time_ts']).reset_index(drop=True)
+
+print("Count with in-session repeated interactions: {}".format(len(df)))
+# Sorts the dataframe by session and timestamp, to remove consecutive repetitions
+df['product_id_past'] = df['product_id'].shift(1).fillna(0)
+df['session_id_past'] = df['user_session'].shift(1).fillna(0)
+#Keeping only no consecutive repeated in session interactions
+df = df[~((df['user_session'] == df['session_id_past']) & \
+             (df['product_id'] == df['product_id_past']))]
+print("Count after removed in-session repeated interactions: {}".format(len(df)))
+del(df['product_id_past'])
+del(df['session_id_past'])
+
+gc.collect()
+
+
+
+
+
Count with in-session repeated interactions: 42448762
+Count after removed in-session repeated interactions: 30733301
+CPU times: user 789 ms, sys: 120 ms, total: 909 ms
+Wall time: 1.16 s
+
+
+
0
+
+
+
+
+
+
+

Include the item first time seen feature (for recency calculation)

+

We create prod_first_event_time_ts column which indicates the timestamp that an item was seen first time.

+
+
+
item_first_interaction_df = df.groupby('product_id').agg({'event_time_ts': 'min'}) \
+            .reset_index().rename(columns={'event_time_ts': 'prod_first_event_time_ts'})
+item_first_interaction_df.head()
+gc.collect()
+
+
+
+
+
0
+
+
+
+
+
+
+
df = df.merge(item_first_interaction_df, on=['product_id'], how='left').reset_index(drop=True)
+
+
+
+
+
+
+
df.head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
user_sessionevent_typeproduct_idcategory_idcategory_codebrandpriceuser_idevent_time_dtevent_time_tsprod_first_event_time_ts
094view262025602053013563693335403<NA><NA>388.495128927062019-10-15 17:21:5915711601191569925682
194view262039942053013563693335403<NA><NA>157.795128927062019-10-15 17:22:1715711601371569941460
294view262040362053013563693335403<NA>sokolov471.705128927062019-10-15 17:22:2915711601491569897265
394view262039942053013563693335403<NA><NA>157.795128927062019-10-15 17:22:5815711601781569941460
494view262037272053013563693335403<NA>lucente317.385128927062019-10-15 17:23:1915711601991569901056
+
+
+
+
+
del(item_first_interaction_df)
+item_first_interaction_df=None
+gc.collect()
+
+
+
+
+
0
+
+
+
+
+

In this tutorial, we only use one week of data from Oct 2019 dataset.

+
+
+
# check the min date
+df['event_time_dt'].min()
+
+
+
+
+
numpy.datetime64('2019-10-01T00:00:00')
+
+
+
+
+
+
+
# Filters only the first week of the data.
+df = df[df['event_time_dt'] < np.datetime64('2019-10-08')].reset_index(drop=True)
+
+
+
+
+

We verify that we only have the first week of Oct-2019 dataset.

+
+
+
df['event_time_dt'].max()
+
+
+
+
+
numpy.datetime64('2019-10-07T23:59:59')
+
+
+
+
+

We drop event_time_dt column as it will not be used anymore.

+
+
+
df = df.drop(['event_time_dt'],  axis=1)
+
+
+
+
+
+
+
df.head()
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
user_sessionevent_typeproduct_idcategory_idcategory_codebrandpriceuser_idevent_time_tsprod_first_event_time_ts
043view53007972053013563173241677<NA>panasonic39.9051390357215704606111569948287
143view53007982053013563173241677<NA>panasonic32.1851390357215704606161569934097
243view53002842053013563173241677<NA>rowenta30.8651390357215704606211569927253
343view53003822053013563173241677<NA>remington28.2251390357215704606361570026747
443view53003662053013563173241677<NA>polaris26.4651390357215704606501570097085
+
+
+

Save the data as a single parquet file to be used in the ETL notebook.

+
+
+
# save df as parquet files on disk
+df.to_parquet(os.path.join(INPUT_DATA_DIR, 'Oct-2019.parquet'))
+
+
+
+
+
    +
  • Shut down the kernel

  • +
+
+
+
import IPython
+
+app = IPython.Application.instance()
+app.kernel.do_shutdown(True)
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/tutorial/02-ETL-with-NVTabular.html b/review/pr-767/examples/tutorial/02-ETL-with-NVTabular.html new file mode 100644 index 0000000000..4424b2335b --- /dev/null +++ b/review/pr-767/examples/tutorial/02-ETL-with-NVTabular.html @@ -0,0 +1,765 @@ + + + + + + ETL with NVTabular — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_tutorial-02-etl-with-nvtabular/nvidia_logo.png +
+

ETL with NVTabular

+

You can execute these tutorial notebooks using the latest stable merlin-pytorch container.

+

Launch the docker container

+
docker run -it --gpus device=0 -p 8000:8000 -p 8001:8001 -p 8002:8002 -p 8888:8888 -v <path_to_data>:/workspace/data/  nvcr.io/nvidia/merlin/merlin-pytorch:23.XX
+
+
+

This script will mount your local data folder that includes your data files to /workspace/data directory in the merlin-pytorch docker container.

+
+

1. Introduction

+

In this notebook, we will create a preprocessing and feature engineering pipeline with Rapids cuDF and Merlin NVTabular libraries to prepare our dataset for session-based recommendation model training.

+

NVTabular is a feature engineering and preprocessing library for tabular data that is designed to easily manipulate terabyte scale datasets and train deep learning (DL) based recommender systems. It provides high-level abstraction to simplify code and accelerates computation on the GPU using the RAPIDS Dask-cuDF library, and is designed to be interoperable with both PyTorch and TensorFlow using dataloaders that have been developed as extensions of native framework code.

+

Our main goal is to create sequential features. In order to do that, we are going to perform the following:

+
    +
  • Categorify categorical features with Categorify() op

  • +
  • Create temporal features with a user-defined custom op and Lambda op

  • +
  • Transform continuous features using Log and Normalize ops

  • +
  • Group all these features together at the session level sorting the interactions by time with Groupby

  • +
  • Finally export the preprocessed datasets to parquet files by hive-partitioning.

  • +
+
+

1.1. Dataset

+

In our hands-on exercise notebooks we are going to use a subset of the publicly available eCommerce dataset. The eCommerce behavior data contains 7 months of data (from October 2019 to April 2020) from a large multi-category online store. Each row in the file represents an event. All events are related to products and users. Each event is like many-to-many relation between products and users.

+

Data collected by Open CDP project and the source of the dataset is REES46 Marketing Platform.

+
+
+
+

2. Import Libraries

+
+
+
import os
+
+import numpy as np 
+import cupy as cp
+import glob
+
+import cudf
+import nvtabular as nvt
+
+from nvtabular.ops import Operator
+from merlin.dag import ColumnSelector
+from merlin.schema import Schema, Tags
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+
+
+
+
+
+
+
# avoid numba warnings
+from numba import config
+config.CUDA_LOW_OCCUPANCY_WARNINGS = 0
+
+
+
+
+
+
+

3. Set up Input and Output Data Paths

+
+
+
# define data path about where to get our data
+INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data/")
+
+
+
+
+
+
+

4. Read the Input Parquet file

+

Even though the original dataset contains 7 months data files, we are going to use the first seven days of the Oct-2019.csv ecommerce dataset. We already performed certain preprocessing steps on the first month (Oct-2019) of the raw dataset in the 01-preprocess notebook:

+
    +
  • we created event_time_ts column from event_time column which shows the time when event happened at (in UTC).

  • +
  • we created prod_first_event_time_ts column which indicates the timestamp that an item was seen first time.

  • +
  • we removed the rows where the user_session is Null. As a result, 2 rows were removed.

  • +
  • we categorified the user_session column, so that it now has only integer values.

  • +
  • we removed consequetively repeated (user, item) interactions. For example, an original session with [1, 2, 4, 1, 2, 2, 3, 3, 3] product interactions has become [1, 2, 4, 1, 2, 3] after removing the repeated interactions on the same item within the same session.

  • +
+

Below, we start by reading in Oct-2019.parquetwith cuDF. In order to create and save Oct-2019.parquet file, please run 01-preprocess.ipynb notebook first.

+
+
+
%%time
+df = cudf.read_parquet(os.path.join(INPUT_DATA_DIR, 'Oct-2019.parquet'))  
+print(df.head(5))
+
+
+
+
+
   user_session event_type  product_id          category_id category_code  \
+0            43       view     5300797  2053013563173241677          <NA>   
+1            43       view     5300798  2053013563173241677          <NA>   
+2            43       view     5300284  2053013563173241677          <NA>   
+3            43       view     5300382  2053013563173241677          <NA>   
+4            43       view     5300366  2053013563173241677          <NA>   
+
+       brand  price    user_id  event_time_ts  prod_first_event_time_ts  
+0  panasonic  39.90  513903572     1570460611                1569948287  
+1  panasonic  32.18  513903572     1570460616                1569934097  
+2    rowenta  30.86  513903572     1570460621                1569927253  
+3  remington  28.22  513903572     1570460636                1570026747  
+4    polaris  26.46  513903572     1570460650                1570097085  
+CPU times: user 1.9 s, sys: 596 ms, total: 2.5 s
+Wall time: 2.52 s
+
+
+
+
+
+
+
df.shape
+
+
+
+
+
(6390928, 10)
+
+
+
+
+

Let’s check if there is any column with nulls.

+
+
+
df.isnull().any()
+
+
+
+
+
user_session                False
+event_type                  False
+product_id                  False
+category_id                 False
+category_code                True
+brand                        True
+price                       False
+user_id                     False
+event_time_ts               False
+prod_first_event_time_ts    False
+dtype: bool
+
+
+
+
+

We see that 'category_code' and 'brand' columns have null values, and in the following cell we are going to fill these nulls with via categorify op, and then all categorical columns will be encoded to continuous integers. Categorify op maps nulls to 1, OOVs to 2, automatically. We reserve 0 for padding the sequence features. The encoding of each category starts from 3.

+
+
+

5. Initialize NVTabular Workflow

+
+

5.1. Categorical Features Encoding

+
+
+
# categorify features 
+item_id = ['product_id'] >> nvt.ops.TagAsItemID()
+cat_feats = item_id + ['category_code', 'brand', 'user_id', 'category_id', 'event_type'] >> nvt.ops.Categorify()
+
+
+
+
+
+
+

5.2. Extract Temporal Features

+
+
+
# create time features
+session_ts = ['event_time_ts']
+
+session_time = (
+    session_ts >> 
+    nvt.ops.LambdaOp(lambda col: cudf.to_datetime(col, unit='s')) >> 
+    nvt.ops.Rename(name = 'event_time_dt')
+)
+
+sessiontime_weekday = (
+    session_time >> 
+    nvt.ops.LambdaOp(lambda col: col.dt.weekday) >> 
+    nvt.ops.Rename(name ='et_dayofweek')
+)
+
+
+
+
+

Now let’s create cycling features from the sessiontime_weekday column. We would like to use the temporal features (hour, day of week, month, etc.) that have inherently cyclical characteristic. We represent the day of week as a cycling feature (sine and cosine), so that it can be represented in a continuous space. That way, the difference between the representation of two different days is the same, in other words, with cyclical features we can convey closeness between data. You can read more about it here.

+
+
+
def get_cycled_feature_value_sin(col, max_value):
+    value_scaled = (col + 0.000001) / max_value
+    value_sin = np.sin(2*np.pi*value_scaled)
+    return value_sin
+
+def get_cycled_feature_value_cos(col, max_value):
+    value_scaled = (col + 0.000001) / max_value
+    value_cos = np.cos(2*np.pi*value_scaled)
+    return value_cos
+
+
+
+
+
+
+
weekday_sin = (sessiontime_weekday >> 
+               (lambda col: get_cycled_feature_value_sin(col+1, 7)) >> 
+               nvt.ops.Rename(name = 'et_dayofweek_sin') >>
+               nvt.ops.AddMetadata(tags=[Tags.CONTINUOUS])
+              )
+    
+weekday_cos= (sessiontime_weekday >> 
+              (lambda col: get_cycled_feature_value_cos(col+1, 7)) >> 
+              nvt.ops.Rename(name = 'et_dayofweek_cos') >>
+              nvt.ops.AddMetadata(tags=[Tags.CONTINUOUS])
+             )
+
+
+
+
+
+
+

5.2.1 Add Product Recency feature

+
    +
  • Let’s define a custom op to calculate product recency in days

  • +
+
+
+
# Compute Item recency: Define a custom Op 
+class ItemRecency(nvt.ops.Operator):
+    def transform(self, columns, gdf):
+        for column in columns.names:
+            col = gdf[column]
+            item_first_timestamp = gdf['prod_first_event_time_ts']
+            delta_days = (col - item_first_timestamp) / (60*60*24)
+            gdf[column + "_age_days"] = delta_days * (delta_days >=0)
+        return gdf
+
+    def compute_selector(
+        self,
+        input_schema: Schema,
+        selector: ColumnSelector,
+        parents_selector: ColumnSelector,
+        dependencies_selector: ColumnSelector,
+    ) -> ColumnSelector:
+        self._validate_matching_cols(input_schema, parents_selector, "computing input selector")
+        return parents_selector
+
+    def column_mapping(self, col_selector):
+        column_mapping = {}
+        for col_name in col_selector.names:
+            column_mapping[col_name + "_age_days"] = [col_name]
+        return column_mapping
+
+    @property
+    def dependencies(self):
+        return ["prod_first_event_time_ts"]
+
+    @property
+    def output_dtype(self):
+        return np.float64
+
+
+
+
+
+
+
recency_features = ['event_time_ts'] >> ItemRecency() 
+recency_features_norm = (recency_features >> 
+                         nvt.ops.LogOp() >> 
+                         nvt.ops.Normalize(out_dtype=np.float32) >> 
+                         nvt.ops.Rename(name='product_recency_days_log_norm')
+                        )
+
+
+
+
+
+
+
time_features = (
+    session_time +
+    sessiontime_weekday +
+    weekday_sin +
+    weekday_cos +
+    recency_features_norm
+)
+
+
+
+
+
+
+

5.3. Normalize Continuous Features¶

+
+
+
# Smoothing price long-tailed distribution and applying standardization
+price_log = ['price'] >> nvt.ops.LogOp() >> nvt.ops.Normalize(out_dtype=np.float32) >> nvt.ops.Rename(name='price_log_norm')
+
+
+
+
+
+
+
# Relative price to the average price for the category_id
+def relative_price_to_avg_categ(col, gdf):
+    epsilon = 1e-5
+    col = ((gdf['price'] - col) / (col + epsilon)) * (col > 0).astype(int)
+    return col
+    
+avg_category_id_pr = ['category_id'] >> nvt.ops.JoinGroupby(cont_cols =['price'], stats=["mean"]) >> nvt.ops.Rename(name='avg_category_id_price')
+relative_price_to_avg_category = (
+    avg_category_id_pr >> 
+    nvt.ops.LambdaOp(relative_price_to_avg_categ, dependency=['price']) >> 
+    nvt.ops.Rename(name="relative_price_to_avg_categ_id") >>
+    nvt.ops.AddMetadata(tags=[Tags.CONTINUOUS])
+)
+
+
+
+
+
+
+

5.4. Grouping interactions into sessions

+
+

Aggregate by session id and creates the sequential features

+
+
+
groupby_feats = ['event_time_ts', 'user_session'] + cat_feats + time_features + price_log + relative_price_to_avg_category
+
+
+
+
+
+
+
# Define Groupby Workflow
+groupby_features = groupby_feats >> nvt.ops.Groupby(
+    groupby_cols=["user_session"], 
+    sort_cols=["event_time_ts"],
+    aggs={
+        'user_id': ['first'],
+        'product_id': ["list", "count"],
+        'category_code': ["list"],  
+        'brand': ["list"], 
+        'category_id': ["list"], 
+        'event_time_ts': ["first"],
+        'event_time_dt': ["first"],
+        'et_dayofweek_sin': ["list"],
+        'et_dayofweek_cos': ["list"],
+        'price_log_norm': ["list"],
+        'relative_price_to_avg_categ_id': ["list"],
+        'product_recency_days_log_norm': ["list"]
+        },
+    name_sep="-")
+
+
+
+
+
    +
  • Select columns which are list

  • +
+
+
+
groupby_features_list = groupby_features['product_id-list',
+        'category_code-list',  
+        'brand-list', 
+        'category_id-list', 
+        'et_dayofweek_sin-list',
+        'et_dayofweek_cos-list',
+        'price_log_norm-list',
+        'relative_price_to_avg_categ_id-list',
+        'product_recency_days_log_norm-list']
+
+
+
+
+
+
+
SESSIONS_MAX_LENGTH = 20 
+MINIMUM_SESSION_LENGTH = 2
+
+
+
+
+

We truncate the sequence features in length according to sessions_max_length param, which is set as 20 in our example.

+
+
+
groupby_features_trim = groupby_features_list >> nvt.ops.ListSlice(-SESSIONS_MAX_LENGTH, pad=True)
+
+
+
+
+
    +
  • Create a day_index column in order to partition sessions by day when saving the parquet files.

  • +
+
+
+
# calculate session day index based on 'timestamp-first' column
+day_index = ((groupby_features['event_time_dt-first'])  >> 
+             nvt.ops.LambdaOp(lambda col: (col - col.min()).dt.days +1) >> 
+             nvt.ops.Rename(f = lambda col: "day_index") >>
+             nvt.ops.AddMetadata(tags=[Tags.CATEGORICAL])
+            )
+
+
+
+
+
    +
  • Select certain columns to be used in model training

  • +
+
+
+
sess_id = groupby_features['user_session'] >> nvt.ops.AddMetadata(tags=[Tags.CATEGORICAL])
+
+selected_features = sess_id + groupby_features['product_id-count'] + groupby_features_trim + day_index
+
+
+
+
+
    +
  • Filter out the session that have less than 2 interactions.

  • +
+
+
+
filtered_sessions = selected_features >> nvt.ops.Filter(f=lambda df: df["product_id-count"] >= MINIMUM_SESSION_LENGTH)
+
+
+
+
+
    +
  • Initialize the NVTabular dataset object and workflow graph.

  • +
+

NVTabular’s preprocessing and feature engineering workflows are directed graphs of operators. When we initialize a Workflow with our pipeline, workflow organizes the input and output columns.

+
+
+
workflow = nvt.Workflow(filtered_sessions)
+dataset = nvt.Dataset(df)
+# Learn features statistics necessary of the preprocessing workflow
+# The following will generate schema.pbtxt file in the provided folder and export the parquet files.
+workflow.fit_transform(dataset).to_parquet(os.path.join(INPUT_DATA_DIR, "processed_nvt"))
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:149: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
+  warnings.warn(
+/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:149: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
+  warnings.warn(
+
+
+
+
+
+
+
workflow.output_schema
+
+
+
+
+

Above, we created an NVTabular Dataset object using our input dataset. Then, we calculate statistics for this workflow on the input dataset, i.e. on our training set, using the workflow.fit() method so that our Workflow can use these stats to transform any given input.

+
+
+
+
+

6. Exporting data

+

We export dataset to parquet partitioned by the session day_index column.

+
+
+
# define partition column
+PARTITION_COL = 'day_index'
+
+# define output_folder to store the partitioned parquet files
+OUTPUT_FOLDER = os.environ.get("OUTPUT_FOLDER", INPUT_DATA_DIR + "sessions_by_day")
+!mkdir -p $OUTPUT_FOLDER
+
+
+
+
+

In this section we are going to create a folder structure as shown below. As we explained above, this is just to structure parquet files so that it would be easier to do incremental training and evaluation.

+
/sessions_by_day/
+|-- 1
+|   |-- train.parquet
+|   |-- valid.parquet
+|   |-- test.parquet
+
+|-- 2
+|   |-- train.parquet
+|   |-- valid.parquet
+|   |-- test.parquet
+
+
+

gpu_preprocessing function converts the process df to a Dataset object and write out hive-partitioned data to disk.

+
+
+
# read in the processed train dataset
+sessions_gdf = cudf.read_parquet(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+
+
+
+
+

Let’s print the head of our preprocessed dataset. You can notice that now each example (row) is a session and the sequential features with respect to user interactions were converted to lists with matching length.

+
+
+
print(sessions_gdf.head(2))
+
+
+
+
+
   user_session  product_id-count  \
+0             1               779   
+1             6               316   
+
+                                     product_id-list  \
+0  [7171, 13290, 19757, 20401, 19757, 20401, 2122...   
+1  [4093, 3427, 20536, 27530, 11262, 18532, 33985...   
+
+                                  category_code-list  \
+0  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...   
+1  [17, 17, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2...   
+
+                                          brand-list  \
+0  [34, 231, 239, 34, 239, 34, 120, 120, 249, 231...   
+1  [50, 1, 36, 1, 1, 1, 1, 3, 2, 2, 2, 2, 2, 2, 1...   
+
+                                    category_id-list  \
+0  [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, ...   
+1  [36, 36, 163, 163, 163, 163, 163, 2, 2, 2, 2, ...   
+
+                               et_dayofweek_sin-list  \
+0  [0.7818321, 0.7818321, 0.7818321, 0.7818321, 0...   
+1  [0.43388295, 0.43388295, 0.43388295, 0.4338829...   
+
+                               et_dayofweek_cos-list  \
+0  [0.6234891, 0.6234891, 0.6234891, 0.6234891, 0...   
+1  [-0.90096927, -0.90096927, -0.90096927, -0.900...   
+
+                                 price_log_norm-list  \
+0  [-0.543168, -0.5673179, -0.578279, -0.62369585...   
+1  [0.23708352, 0.24015874, -0.80294055, -0.66287...   
+
+                 relative_price_to_avg_categ_id-list  \
+0  [0.12176345592788261, 0.08783709744899952, 0.0...   
+1  [-0.23209574328358615, -0.22911214248041414, -...   
+
+                  product_recency_days_log_norm-list  day_index  
+0  [1.1603423, 1.1818986, 1.0499662, 1.0900966, 1...          1  
+1  [-0.7081338, -0.69109577, -0.73886937, -0.7074...          2  
+
+
+
+
+
+
+
from transformers4rec.utils.data_utils import save_time_based_splits
+save_time_based_splits(data=nvt.Dataset(sessions_gdf),
+                       output_dir= OUTPUT_FOLDER,
+                       partition_col=PARTITION_COL,
+                       timestamp_col='user_session', 
+                      )
+
+
+
+
+
Creating time-based splits: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 7/7 [00:02<00:00,  2.75it/s]
+
+
+
+
+
+
+
# check out the OUTPUT_FOLDER
+!ls $OUTPUT_FOLDER
+
+
+
+
+
1  2  3  4  5  6  7
+
+
+
+
+

Save NVTabular workflow to load at the inference step.

+

You can uncomment the cell below and execute it to see the output schema generated from nvtabular workflow.

+
+
+
workflow_path = os.path.join(INPUT_DATA_DIR, 'workflow_etl')
+workflow.save(workflow_path)
+
+
+
+
+
+
+

7. Wrap Up

+

That’s it! We finished our first task. We reprocessed our dataset and created new features to train a session-based recommendation model. Please shut down the kernel before moving on to the next notebook.

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/tutorial/03-Session-based-recsys.html b/review/pr-767/examples/tutorial/03-Session-based-recsys.html new file mode 100644 index 0000000000..8abe0c91f4 --- /dev/null +++ b/review/pr-767/examples/tutorial/03-Session-based-recsys.html @@ -0,0 +1,1656 @@ + + + + + + Session-based recommendation with Transformers4Rec — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+
+
# Copyright 2022 NVIDIA Corporation. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+# Each user is responsible for checking the content of datasets and the
+# applicable licenses and determining if suitable for the intended use.
+
+
+
+
+https://developer.download.nvidia.com/notebooks/dlsw-notebooks/merlin_transformers4rec_tutorial-03-session-based-recsys/nvidia_logo.png +
+

Session-based recommendation with Transformers4Rec

+
+

1. Introduction

+

In the previous notebook we went through our ETL pipeline with NVTabular library, and created sequential features to be used for training a session-based recommendation model. In this notebook we will learn:

+
    +
  • Accelerating data loading of parquet files multiple features on PyTorch using NVTabular library

  • +
  • Training and evaluating an RNN-based (GRU) session-based recommendation model

  • +
  • Training and evaluating a Transformer architecture (XLNET) for session-based recommendation model

  • +
  • Integrate side information (additional features) into transformer architectures in order to improve recommendation accuracy

  • +
+
+
+

2. Session-based Recommendation

+

Session-based recommendation, a sub-area of sequential recommendation, has been an important task in online services like e-commerce and news portals, where most users either browse anonymously or may have very distinct interests for different sessions. Session-Based Recommender Systems (SBRS) have +been proposed to model the sequence of interactions within the current user session, where a session is a short sequence of user interactions typically bounded by user inactivity. They have recently gained popularity due to their ability to capture short-term and contextual user preferences towards items.

+

Many methods have been proposed to leverage the sequence of interactions that occur during a session, including session-based k-NN algorithms like V-SkNN [1] and neural approaches like GRU4Rec [2]. In addition, state of the art NLP approaches have inspired RecSys practitioners and researchers to leverage the self-attention mechanism and the Transformer-based architectures for sequential [3] and session-based recommendation [4].

+
+
+

3. Transformers4Rec Library

+

In this tutorial, we introduce the Transformers4Rec open-source library for sequential and session-based recommendation task.

+

With Transformers4Rec we import from the HF Transformers NLP library the transformer architectures and their configuration classes.

+

In addition, Transformers4Rec provides additional blocks necessary for recommendation, e.g., input features normalization and aggregation, and heads for recommendation and sequence classification/prediction. We also extend their Trainer class to allow for the evaluation with RecSys metrics.

+

Here are some of the most important modules:

+
    +
  • TabularSequenceFeatures is the input block for sequential features. Based on a Schema and options set by the user, it dynamically creates all the necessary layers (e.g. embedding layers) to encode, normalize, and aggregate categorical and continuous features. It also allows to set the masking training approach (e.g. Causal LM, Masked LM).

  • +
  • TransformerBlock class is the bridge that adapts HuggingFace Transformers for session-based and sequential-based recommendation models.

  • +
  • SequentialBlock allows the definition of a model body as as sequence of layer (similarly to torch.nn.sequential). It is designed to define our model as a sequence of layers and automatically setting the input shape of a layer from the output shape of the previous one.

  • +
  • Head class defines the head of a model.

  • +
  • NextItemPredictionTask is the class to support next item prediction task, combining a model body with a head.

  • +
  • Trainer extends the Trainer class from HF transformers and manages the model training and evaluation.

  • +
+

You can check the full documentation of Transformers4Rec if needed.

+

In Figure 1, we present a reference architecture that we are going to build with Transformers4Rec PyTorch API in this notebook. We are going to start using only product-id as input feature, but as you can notice in the figure, we can add additional categorical and numerical features later to improve recommendation accuracy, as shown in Section 3.2.4.

+

+

Figure 1. Transformers4Rec meta-architecture.

+

3.1 Training an RNN-based Session-based Recommendation Model

+

In this section, we use a type of Recurrent Neural Networks (RNN) - the Gated Recurrent Unit (GRU)[5] - to do next-item prediction using a sequence of events (e.g., click, view, or purchase) per user in a given session. There is obviously some sequential patterns that we want to capture to provide more relevant recommendations. In our case, the input of the GRU layer is a representation of the user interaction, the internal GRU hidden state encodes a representation of the session based on past interactions and the outputs are the next-item predictions. Basically, for each item in a given session, we generate the output as the predicted preference of the items, i.e. the likelihood of being the next.

+

Figure 2 illustrates the logic of predicting next item in a given session. First, the product ids are embedded and fed as a sequence to a GRU layer, which outputs a representation than can be used to predict the next item. For the sake of simplicity, we treat the recommendation as a multi-class classification problem and use cross-entropy loss. In our first example, we use a GRU block instead of Transformer block (shown in the Figure 1).

+

+

Figure 2. Next item prediction with RNN.

+

3.1.1 Import Libraries and Modules

+
+
+
import os
+import glob
+
+import torch 
+import transformers4rec.torch as tr
+
+from transformers4rec.torch.ranking_metric import NDCGAt, RecallAt
+from transformers4rec.torch.utils.examples_utils import wipe_memory
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+
+
+
+
+
+
Instantiates Schema object by reading the save trained parquet file.
+
+
+
from merlin.schema import Schema
+from merlin.io import Dataset
+
+INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data")
+
+train = Dataset(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+schema = train.schema
+schema = schema.select_by_name(['product_id-list'])
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:149: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
+  warnings.warn(
+
+
+
+
+

Transformers4Rec library relies on Schema object in TabularSequenceFeatures that takes the input features as input and create all the necessary layers to process and aggregate them. As you can see below, the schema.pb is a protobuf text file contains features metadata, including statistics about features such as cardinality, min and max values and also tags based on their characteristics and dtypes (e.g., categorical, continuous, list, item_id). We can tag our target column and even add the prediction task such as binary, regression or multiclass as tags for the target column in the schema.pb file. The Schema provides a standard representation for metadata that is useful when training machine learning or deep learning models.

+

The metadata information loaded from Schema and their tags are used to automatically set the parameters of Transformers4rec models. Certain Transformers4rec modules have a from_schema() method to instantiate their parameters and layers from protobuf text file respectively.

+

Although in this tutorial we are defining the Schema manually, the next NVTabular release is going to generate the schema with appropriate types and tags automatically from the preprocessing workflow, allowing the user to set additional feaure tags if needed.

+
+
+
Defining the input block: TabularSequenceFeatures
+

We define our input block using TabularSequenceFeatures class. The from_schema() method directly parses the schema and accepts sequential and non-sequential features. Based on the Schema and some user-defined options, the categorical features are represented by embeddings and numerical features can be represented as continuous scalars or by a technique named Soft One-Hot embeddings (more info in our paper’s online appendix).

+

The embedding features can optionally be normalized (layer_norm=True). Data augmentation methods like “Stochastic Swap Noise” (pre="stochastic-swap-noise") and aggregation opptions (like concat and elementwise-sum) are also available. The continuous features can also be combined and projected by MLP layers by setting continuous_projection=[dim]. Finally, the max_sequence_length argument defines the maximum sequence length of our sequential input.

+

Another important argument is the masking method, which sets the training approach. See Section 3.2.2 for details on this.

+
+
+
sequence_length = 20
+inputs = tr.TabularSequenceFeatures.from_schema(
+        schema,
+        max_sequence_length= sequence_length,
+        masking = 'causal',
+    )
+
+
+
+
+
+
+
Connecting the blocks with SequentialBlock
+

The SequentialBlock creates a pipeline by connecting the building blocks in a serial way, so that the input shape of one block is inferred from the output of the previous block. In this example, the TabularSequenceFeatures object is followed by an MLP projection layer, which feeds data to a GRU block.

+
+
+
d_model = 128
+body = tr.SequentialBlock(
+        inputs,
+        tr.MLPBlock([d_model]),
+        tr.Block(torch.nn.GRU(input_size=d_model, hidden_size=d_model, num_layers=1), [None, 20, d_model])
+)
+
+
+
+
+
+
+
Item Prediction head and tying embeddings
+

In our experiments published in our ACM RecSys’21 paper [8], we used the next item prediction head, which projects the output of the RNN/Transformer block to the items space, followed by a softmax layer to produce the relevance scores over all items. For the output layer we provide the Tying Embeddings technique (weight_tying). It was proposed originally by the NLP community to tie the weights of the input (item id) embedding matrix with the output projection layer, showed to be a very effective technique in extensive experimentation for competitions and empirical analysis (for more details see our paper and its online appendix). In practice, such technique helps the network to learn faster item embeddings even for rare items, reduces the number of parameters for large item cardinalities and enables Approximate Nearest Neighbours (ANN) search on inference, as the predictions can be obtained by a dot product between the model output and the item embeddings.

+

Next, we link the transformer-body to the inputs and the prediction tasks to get the final PyTorch Model class.

+
+
+
head = tr.Head(
+    body,
+    tr.NextItemPredictionTask(weight_tying=True, 
+                              metrics=[NDCGAt(top_ks=[10, 20], labels_onehot=True),  
+                                       RecallAt(top_ks=[10, 20], labels_onehot=True)]),
+)
+model = tr.Model(head)
+
+
+
+
+
Projecting inputs of NextItemPredictionTask to'64' As weight tying requires the input dimension '128' to be equal to the item-id embedding dimension '64'
+
+
+
+
+
+
+
Define a Dataloader function from schema
+

We use optimized NVTabular PyTorch Dataloader which has the following benefits:

+
    +
  • removing bottlenecks from dataloading by processing large chunks of data at a time instead iterating by row

  • +
  • processing datasets that don’t fit within the GPU or CPU memory by streaming from the disk

  • +
  • reading data directly into the GPU memory and removing CPU-GPU communication

  • +
  • preparing batch asynchronously into the GPU to avoid CPU-GPU communication

  • +
  • supporting commonly used formats such as parquet

  • +
  • having native support to sparse sequential features

  • +
+
+
+
from transformers4rec.torch.utils.data_utils import MerlinDataLoader
+x_cat_names, x_cont_names = ['product_id-list_seq'], []
+
+# dictionary representing max sequence length for column
+sparse_features_max = {
+    fname: sequence_length
+    for fname in x_cat_names + x_cont_names
+}
+
+def get_dataloader(data_path, batch_size=128):
+        loader = MerlinDataLoader.from_schema(
+            schema,
+            data_path,
+            batch_size,
+            max_sequence_length=sequence_length,
+            shuffle=False,
+        )
+        return loader
+
+
+
+
+
+
+
Daily Fine-Tuning: Training over a time window
+

Now that the model is defined, we are going to launch training. For that, Transfromers4rec extends the HF Transformers Trainer class to adapt the evaluation loop for session-based recommendation task and the calculation of ranking metrics. +The original HF Trainer.train() method is not overloaded, meaning that we leverage the efficient training implementation from HF transformers library, which manages for example half-precision (FP16) training.

+
+
+
Set training arguments
+
+
+
from transformers4rec.config.trainer import T4RecTrainingArguments
+from transformers4rec.torch import Trainer
+
+#Set arguments for training 
+train_args = T4RecTrainingArguments(local_rank = -1, 
+                                    dataloader_drop_last = False,
+                                    report_to = [],   #set empty list to avoid logging metrics to Weights&Biases
+                                    gradient_accumulation_steps = 1,
+                                    per_device_train_batch_size = 256, 
+                                    per_device_eval_batch_size = 32,
+                                    output_dir = "./tmp", 
+                                    max_sequence_length=sequence_length,
+                                    learning_rate=0.00071,
+                                    num_train_epochs=3,
+                                    logging_steps=200,
+                                   )
+
+
+
+
+
+
+
Instantiate the Trainer
+
+
+
# Instantiate the T4Rec Trainer, which manages training and evaluation
+trainer = Trainer(
+    model=model,
+    args=train_args,
+    schema=schema,
+    compute_metrics=True,
+)
+
+
+
+
+

Define the output folder of the processed parquet files

+
+
+
OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "/workspace/data/sessions_by_day")
+
+
+
+
+
+
+
Model finetuning and incremental evaluation
+

Training models incrementally, e.g. fine-tuning pre-trained models with new data over time is a common practice in industry to scale to the large streaming data been generated every data. Furthermore, it is common to evaluate recommendation models on data that came after the one used to train the models, for a more realistic evaluation.

+

Here, we use a loop that to conduct a time-based finetuning, by iteratively training and evaluating using a sliding time window as follows: At each iteration, we use training data of a specific time index t to train the model then we evaluate on the validation data of next index t + 1. We set the start time to 1 and end time to 4.

+
+
+
%%time
+start_time_window_index = 1
+final_time_window_index = 4
+for time_index in range(start_time_window_index, final_time_window_index):
+    # Set data 
+    time_index_train = time_index
+    time_index_eval = time_index + 1
+    train_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_train}/train.parquet"))
+    eval_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_eval}/valid.parquet"))
+    
+    # Initialize dataloaders
+    trainer.train_dataloader = get_dataloader(train_paths, train_args.per_device_train_batch_size)
+    trainer.eval_dataloader = get_dataloader(eval_paths, train_args.per_device_eval_batch_size)
+    
+    # Train on day related to time_index 
+    print('*'*20)
+    print("Launch training for day %s are:" %time_index)
+    print('*'*20 + '\n')
+    trainer.reset_lr_scheduler()
+    trainer.train()
+    trainer.state.global_step +=1
+    
+    # Evaluate on the following day
+    train_metrics = trainer.evaluate(metric_key_prefix='eval')
+    print('*'*20)
+    print("Eval results for day %s are:\t" %time_index_eval)
+    print('\n' + '*'*20 + '\n')
+    for key in sorted(train_metrics.keys()):
+        print(" %s = %s" % (key, str(train_metrics[key]))) 
+    wipe_memory()
+
+
+
+
+
***** Running training *****
+  Num examples = 112128
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1314
+
+
+
********************
+Launch training for day 1 are:
+********************
+
+
+
+
+ + + [1314/1314 00:52, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2009.759100
4008.757200
6008.920300
8008.442900
10008.751000
12008.367600

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
+
+ + + [415/415 03:57] +
+
********************
+Eval results for day 2 are:	
+
+********************
+
+ eval_/loss = 8.848333358764648
+ eval_/next-item/ndcg_at_10 = 0.040753647685050964
+ eval_/next-item/ndcg_at_20 = 0.049305129796266556
+ eval_/next-item/recall_at_10 = 0.07925495505332947
+ eval_/next-item/recall_at_20 = 0.11349068582057953
+ eval_runtime = 43.1016
+ eval_samples_per_second = 308.109
+ eval_steps_per_second = 9.628
+
+
+
***** Running training *****
+  Num examples = 106240
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1245
+
+
+
********************
+Launch training for day 2 are:
+********************
+
+
+
+
+ + + [1245/1245 00:51, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2008.940700
4008.468400
6008.670800
8008.197200
10008.498300
12008.065300

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
********************
+Eval results for day 3 are:	
+
+********************
+
+ eval_/loss = 8.614578247070312
+ eval_/next-item/ndcg_at_10 = 0.05164230614900589
+ eval_/next-item/ndcg_at_20 = 0.0637783482670784
+ eval_/next-item/recall_at_10 = 0.09867884963750839
+ eval_/next-item/recall_at_20 = 0.1468765288591385
+ eval_runtime = 35.8932
+ eval_samples_per_second = 342.349
+ eval_steps_per_second = 10.698
+
+
+
***** Running training *****
+  Num examples = 97792
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1146
+
+
+
********************
+Launch training for day 3 are:
+********************
+
+
+
+
+ + + [1146/1146 00:47, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2008.620800
4008.233000
6008.173500
8007.982100
10007.933700

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
********************
+Eval results for day 4 are:	
+
+********************
+
+ eval_/loss = 8.204485893249512
+ eval_/next-item/ndcg_at_10 = 0.06745054572820663
+ eval_/next-item/ndcg_at_20 = 0.08173713833093643
+ eval_/next-item/recall_at_10 = 0.12599845230579376
+ eval_/next-item/recall_at_20 = 0.18262046575546265
+ eval_runtime = 60.7599
+ eval_samples_per_second = 255.958
+ eval_steps_per_second = 7.999
+CPU times: user 2min 52s, sys: 1min 5s, total: 3min 57s
+Wall time: 4min 55s
+
+
+
+
+

Let’s write out model evaluation accuracy results to a text file to compare model at the end

+
+
+
with open("results.txt", 'w') as f: 
+    f.write('GRU accuracy results:')
+    f.write('\n')
+    for key, value in  model.compute_metrics().items(): 
+        f.write('%s:%s\n' % (key, value.item()))
+
+
+
+
+
+
+
+

Metrics

+

We have extended the HuggingFace transformers Trainer class (PyTorch only) to support evaluation of RecSys metrics. The following information +retrieval metrics are used to compute the Top-20 accuracy of recommendation lists containing all items:

+
    +
  • Normalized Discounted Cumulative Gain (NDCG@20): NDCG accounts for rank of the relevant item in the recommendation list and is a more fine-grained metric than HR, which only verifies whether the relevant item is among the top-k items.

  • +
  • Hit Rate (HR@20): Also known as Recall@n when there is only one relevant item in the recommendation list. HR just verifies whether the relevant item is among the top-n items.

  • +
+
+
Restart the kernel to free our GPU memory
+
+
+
import IPython
+app = IPython.Application.instance()
+app.kernel.do_shutdown(True)
+
+
+
+
+
{'status': 'ok', 'restart': True}
+
+
+
+
+

At this stage if the kernel does not restart automatically, we expect you to manually restart the kernel to free GPU memory so that you can move on to the next session-based model training with a SOTA deep learning Transformer-based model, XLNet.

+
+
+
+
+

3.2. Training a Transformer-based Session-based Recommendation Model

+
+

3.2.1 What’s Transformers?

+

The Transformer is a competitive alternative to the models using Recurrent Neural Networks (RNNs) for a range of sequence modeling tasks. The Transformer architecture [6] was introduced as a novel architecture in NLP domain that aims to solve sequence-to-sequence tasks relying entirely on self-attention mechanism to compute representations of its input and output. Hence, the Transformer overperforms RNNs with their three mechanisms:

+
    +
  • Non-sequential: Transformers network is parallelized where as RNN computations are inherently sequential. That resulted in significant speed-up in the training time.

  • +
  • Self-attention mechanisms: Transformers rely entirely on self-attention mechanisms that directly model relationships between all item-ids in a sequence.

  • +
  • Positional encodings: A representation of the location or “position” of items in a sequence which is used to give the order context to the model architecture.

  • +
+

+

Figure 3. Transformer vs vanilla RNN.

Figure 4 illustrates the differences of Transformer (self-attention based) and a vanilla RNN architecture. As we see, RNN cannot be parallelized because it uses sequential processing over time (notice the sequential path from previous cells to the current one). On the other hand, the Transformer is a more powerful architecture because the self-attention mechanism is capable of representing dependencies within the sequence of tokens, favors parallel processing and handle longer sequences.

+

As illustrated in the Attention is All You Need paper, the original transformer model is made up of an encoder and decoder where each is a stack we can call a transformer block. In Transformers4Rec architectures we use the encoder block of transformer architecture.

+

+

Figure 4. Encoder block of the Transformer Architecture.

+
+

3.2.2. XLNet

+

Here, we use XLNet [10] as the Transformer block in our architecture. It was originally proposed to be trained with the Permutation Language Modeling (PLM) technique, that combines the advantages of autoregressive (Causal LM) and autoencoding (Masked LM). Although, we found out in our paper [8] that the Masked Language Model (MLM) approach worked better than PLM for the small sequences in session-based recommendation, thus we use MLM for this example. MLM was introduced in BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding paper [8].

+

Figure 5 illustrates the causal language modeling (LM) and masked LM. In this example, we use in causal LM for RNN masked LM for XLNet. Causal LM is the task of predicting the token following a sequence of tokens, where the model only attends to the left context, i.e. models the probability of a token given the previous tokens in a sentence [7]. On the other hand, the MLM randomly masks some of the tokens from the input sequence, and the objective is to predict the original vocabulary id of the masked word based only on its bi-directional context. When we train with MLM, the Transformer layer is also allowed to use positions on the right (future information) during training. During inference, all past items are visible for the Transformer layer, which tries to predict the next item. It performs a type of data augmentation, by masking different positions of the sequences in each training epoch.

+

+

Figure 5. Causal and Masked Language Model masking methods.

+
+

3.2.3 Train XLNET for Next Item Prediction

+

Now we are going to define an architecture for next-item prediction using the XLNET architecture.

+
+
+
import os
+import glob
+
+import torch 
+import transformers4rec.torch as tr
+from transformers4rec.torch.ranking_metric import NDCGAt, RecallAt
+from transformers4rec.torch.utils.examples_utils import wipe_memory
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+
+
+
+
+

As we did above, we start with defining our schema object and selecting only the product_id feature for training.

+
+
+
from merlin.schema import Schema
+from merlin.io import Dataset
+
+INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data")
+
+train = Dataset(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+schema = train.schema
+schema = schema.select_by_name(['product_id-list'])
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:149: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
+  warnings.warn(
+
+
+
+
+
+
Define Input block
+

Here we instantiate TabularSequenceFeatures from the feature schema and set masking="mlm" to use MLM as training method.

+
+
+
#Input 
+sequence_length, d_model = 20, 192
+# Define input module to process tabular input-features and to prepare masked inputs
+inputs= tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=sequence_length,
+    d_output=d_model,
+    masking="mlm",
+)
+
+
+
+
+

We have inherited the original XLNetConfig class of HF transformers with some default arguments in the build() method. Here we use it to instantiate an XLNET model according to the arguments (d_model, n_head, etc.), defining the model architecture.

+

The TransformerBlock class supports HF Transformers for session-based and sequential-based recommendation models. NextItemPredictionTask is the class to support next item prediction task, encapsulating the corresponding heads and loss.

+
+
+
# Define XLNetConfig class and set default parameters for HF XLNet config  
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=4, n_layer=2, total_seq_length=sequence_length
+)
+# Define the model block including: inputs, masking, projection and transformer block.
+body = tr.SequentialBlock(
+    inputs, tr.MLPBlock([192]), tr.TransformerBlock(transformer_config, masking=inputs.masking)
+)
+
+# Define the head for to next item prediction task 
+head = tr.Head(
+    body,
+    tr.NextItemPredictionTask(weight_tying=True,
+                              metrics=[NDCGAt(top_ks=[10, 20], labels_onehot=True),  
+                                       RecallAt(top_ks=[10, 20], labels_onehot=True)]),
+)
+
+# Get the end-to-end Model class 
+model = tr.Model(head)
+
+
+
+
+
Projecting inputs of NextItemPredictionTask to'64' As weight tying requires the input dimension '192' to be equal to the item-id embedding dimension '64'
+
+
+
+
+

Set training arguments

+

Among the training arguments you can set the data_loader_engine to automatically instantiate the dataloader based on the schema, rather than instantiating the data loader manually like we did for the RNN example. The default value is "merlin" for optimized GPU-based data-loading. Optionally the PyarrowDataLoader ("pyarrow") can also be used as a basic option, but it is slower and works only for small datasets, as the full data is loaded into CPU memory.

+
+
+
from transformers4rec.config.trainer import T4RecTrainingArguments
+from transformers4rec.torch import Trainer
+
+#Set arguments for training 
+training_args = T4RecTrainingArguments(
+            output_dir="./tmp",
+            max_sequence_length=20,
+            data_loader_engine='merlin',
+            num_train_epochs=3, 
+            dataloader_drop_last=False,
+            per_device_train_batch_size = 256,
+            per_device_eval_batch_size = 32,
+            gradient_accumulation_steps = 1,
+            learning_rate=0.000666,
+            report_to = [],
+            logging_steps=200,
+        )
+
+
+
+
+

Instantiate the trainer

+
+
+
# Instantiate the T4Rec Trainer, which manages training and evaluation
+trainer = Trainer(
+    model=model,
+    args=training_args,
+    schema=schema,
+    compute_metrics=True,
+)
+
+
+
+
+

Define the output folder of the processed parquet files

+
+
+
OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "/workspace/data/sessions_by_day")
+
+
+
+
+

Now, we do time-based fine-tuning the model by iteratively training and evaluating using a sliding time window, like we did for the RNN example.

+
+
+
%%time
+start_time_window_index = 1
+final_time_window_index = 4
+for time_index in range(start_time_window_index, final_time_window_index):
+    # Set data 
+    time_index_train = time_index
+    time_index_eval = time_index + 1
+    train_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_train}/train.parquet"))
+    eval_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_eval}/valid.parquet"))
+    # Train on day related to time_index 
+    print('*'*20)
+    print("Launch training for day %s are:" %time_index)
+    print('*'*20 + '\n')
+    trainer.train_dataset_or_path = train_paths
+    trainer.reset_lr_scheduler()
+    trainer.train()
+    trainer.state.global_step +=1
+    # Evaluate on the following day
+    trainer.eval_dataset_or_path = eval_paths
+    train_metrics = trainer.evaluate(metric_key_prefix='eval')
+    print('*'*20)
+    print("Eval results for day %s are:\t" %time_index_eval)
+    print('\n' + '*'*20 + '\n')
+    for key in sorted(train_metrics.keys()):
+        print(" %s = %s" % (key, str(train_metrics[key]))) 
+    wipe_memory()
+
+
+
+
+
********************
+Launch training for day 1 are:
+********************
+
+
+
***** Running training *****
+  Num examples = 112128
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1314
+
+
+
+
+ + + [1314/1314 00:52, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2009.978000
4009.067800
6008.780000
8008.656200
10008.566800
12008.511600

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
+
+ + + [415/415 04:01] +
+
***** Running training *****
+  Num examples = 106240
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1245
+
+
+
********************
+Eval results for day 2 are:	
+
+********************
+
+ eval_/loss = 8.715866088867188
+ eval_/next-item/ndcg_at_10 = 0.050047338008880615
+ eval_/next-item/ndcg_at_20 = 0.06090249866247177
+ eval_/next-item/recall_at_10 = 0.09584496170282364
+ eval_/next-item/recall_at_20 = 0.13875272870063782
+ eval_runtime = 45.3102
+ eval_samples_per_second = 293.091
+ eval_steps_per_second = 9.159
+********************
+Launch training for day 2 are:
+********************
+
+
+
+
+ + + [1245/1245 00:48, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2008.646500
4008.534100
6008.395500
8008.349400
10008.259200
12008.222200

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
********************
+Eval results for day 3 are:	
+
+********************
+
+ eval_/loss = 8.42210865020752
+ eval_/next-item/ndcg_at_10 = 0.06027791276574135
+ eval_/next-item/ndcg_at_20 = 0.07407757639884949
+ eval_/next-item/recall_at_10 = 0.1122981607913971
+ eval_/next-item/recall_at_20 = 0.16726472973823547
+ eval_runtime = 39.0537
+ eval_samples_per_second = 314.643
+ eval_steps_per_second = 9.833
+********************
+Launch training for day 3 are:
+********************
+
+
+
***** Running training *****
+  Num examples = 97792
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1146
+
+
+
+
+ + + [1146/1146 00:46, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2008.325100
4008.238700
6008.108700
8008.037000
10007.972000

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
********************
+Eval results for day 4 are:	
+
+********************
+
+ eval_/loss = 8.08250617980957
+ eval_/next-item/ndcg_at_10 = 0.0742299035191536
+ eval_/next-item/ndcg_at_20 = 0.09017771482467651
+ eval_/next-item/recall_at_10 = 0.14068539440631866
+ eval_/next-item/recall_at_20 = 0.20387786626815796
+ eval_runtime = 63.4715
+ eval_samples_per_second = 245.023
+ eval_steps_per_second = 7.657
+CPU times: user 11min 29s, sys: 51.9 s, total: 12min 21s
+Wall time: 4min 59s
+
+
+
+
+

Add eval accuracy metric results to the existing resuls.txt file.

+
+
+
with open("results.txt", 'a') as f:
+    f.write('\n')
+    f.write('XLNet-MLM accuracy results:')
+    f.write('\n')
+    for key, value in  model.compute_metrics().items(): 
+        f.write('%s:%s\n' % (key, value.item()))
+
+
+
+
+
+
+
+

Restart the kernel to free our GPU memory

+
+
+
import IPython
+app = IPython.Application.instance()
+app.kernel.do_shutdown(True)
+
+
+
+
+
{'status': 'ok', 'restart': True}
+
+
+
+
+

At this stage if the kernel does not restart automatically, we expect you to manually restart the kernel to free GPU memory so that you can move on to the next session-based model training with XLNet using side information.

+
+
+

3.2.4 Train XLNET with Side Information for Next Item Prediction

+

It is a common practice in RecSys to leverage additional tabular features of item (product) metadata and user context, providing the model more +information for meaningful predictions. With that motivation, in this section, we will use additional features to train our XLNET architecture. We already checked our schema.pb, saw that it includes features and their tags. Now it is time to use these additional features that we created in the 02_ETL-with-NVTabular.ipynb notebook.

+
+
+
import os
+import glob
+import nvtabular as nvt
+
+import torch 
+import transformers4rec.torch as tr
+from transformers4rec.torch.ranking_metric import NDCGAt, RecallAt
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from .autonotebook import tqdm as notebook_tqdm
+/usr/local/lib/python3.8/dist-packages/merlin/dtypes/mappings/tf.py:52: UserWarning: Tensorflow dtype mappings did not load successfully due to an error: No module named 'tensorflow'
+  warn(f"Tensorflow dtype mappings did not load successfully due to an error: {exc.msg}")
+
+
+
+
+
+
+
from merlin.schema import Schema
+from merlin.io import Dataset
+
+# Define categorical and continuous columns to fed to training model
+x_cat_names = ['product_id-list', 'category_id-list', 'brand-list']
+x_cont_names = ['product_recency_days_log_norm-list', 'et_dayofweek_sin-list', 'et_dayofweek_cos-list', 
+                'price_log_norm-list', 'relative_price_to_avg_categ_id-list']
+
+
+INPUT_DATA_DIR = os.environ.get("INPUT_DATA_DIR", "/workspace/data")
+
+train = Dataset(os.path.join(INPUT_DATA_DIR, "processed_nvt/part_0.parquet"))
+schema = train.schema
+schema = schema.select_by_name(x_cat_names + x_cont_names)
+
+
+
+
+
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:149: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
+  warnings.warn(
+
+
+
+
+

Here we set aggregation="concat", so that all categorical and continuous features are concatenated to form an interaction representation.

+
+
+
# Define input block
+sequence_length, d_model = 20, 192
+# Define input module to process tabular input-features and to prepare masked inputs
+inputs= tr.TabularSequenceFeatures.from_schema(
+    schema,
+    max_sequence_length=sequence_length,
+    aggregation="concat",
+    d_output=d_model,
+    masking="mlm",
+)
+
+# Define XLNetConfig class and set default parameters for HF XLNet config  
+transformer_config = tr.XLNetConfig.build(
+    d_model=d_model, n_head=4, n_layer=2, total_seq_length=sequence_length
+)
+# Define the model block including: inputs, masking, projection and transformer block.
+body = tr.SequentialBlock(
+    inputs, tr.MLPBlock([192]), tr.TransformerBlock(transformer_config, masking=inputs.masking)
+)
+
+# Define the head related to next item prediction task 
+head = tr.Head(
+    body,
+    tr.NextItemPredictionTask(weight_tying=True, 
+                                     metrics=[NDCGAt(top_ks=[10, 20], labels_onehot=True),  
+                                              RecallAt(top_ks=[10, 20], labels_onehot=True)]),
+)
+
+# Get the end-to-end Model class 
+model = tr.Model(head)
+
+
+
+
+
Projecting inputs of NextItemPredictionTask to'64' As weight tying requires the input dimension '192' to be equal to the item-id embedding dimension '64'
+
+
+
+
+
+
Training and Evaluation
+
+
+
from transformers4rec.config.trainer import T4RecTrainingArguments
+from transformers4rec.torch import Trainer
+from transformers4rec.torch.utils.examples_utils import wipe_memory
+
+#Set arguments for training 
+training_args = T4RecTrainingArguments(
+            output_dir="./tmp",
+            max_sequence_length=20,
+            data_loader_engine='merlin',
+            num_train_epochs=3, 
+            dataloader_drop_last=False,
+            per_device_train_batch_size = 256,
+            per_device_eval_batch_size = 32,
+            gradient_accumulation_steps = 1,
+            learning_rate=0.000666,
+            report_to = [],
+            logging_steps=200,
+)
+
+
+
+
+
+
+
# Instantiate the T4Rec Trainer, which manages training and evaluation
+trainer = Trainer(
+    model=model,
+    args=training_args,
+    schema=schema,
+    compute_metrics=True,
+)
+
+
+
+
+

Define the output folder of the processed parquet files

+
+
+
OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "/workspace/data/sessions_by_day")
+
+
+
+
+
+
+
%%time
+start_time_window_index = 1
+final_time_window_index = 4
+for time_index in range(start_time_window_index, final_time_window_index):
+    # Set data 
+    time_index_train = time_index
+    time_index_eval = time_index + 1
+    train_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_train}/train.parquet"))
+    eval_paths = glob.glob(os.path.join(OUTPUT_DIR, f"{time_index_eval}/valid.parquet"))
+    # Train on day related to time_index 
+    print('*'*20)
+    print("Launch training for day %s are:" %time_index)
+    print('*'*20 + '\n')
+    trainer.train_dataset_or_path = train_paths
+    trainer.reset_lr_scheduler()
+    trainer.train()
+    trainer.state.global_step +=1
+    # Evaluate on the following day
+    trainer.eval_dataset_or_path = eval_paths
+    train_metrics = trainer.evaluate(metric_key_prefix='eval')
+    print('*'*20)
+    print("Eval results for day %s are:\t" %time_index_eval)
+    print('\n' + '*'*20 + '\n')
+    for key in sorted(train_metrics.keys()):
+        print(" %s = %s" % (key, str(train_metrics[key]))) 
+    wipe_memory()
+
+
+
+
+
********************
+Launch training for day 1 are:
+********************
+
+
+
***** Running training *****
+  Num examples = 112128
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1314
+
+
+
+
+ + + [1314/1314 02:07, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2009.823300
4008.907700
6008.615000
8008.508400
10008.392100
12008.309200

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
+
+ + + [415/415 06:27] +
+
***** Running training *****
+  Num examples = 106240
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1245
+
+
+
********************
+Eval results for day 2 are:	
+
+********************
+
+ eval_/loss = 8.507274627685547
+ eval_/next-item/ndcg_at_10 = 0.059581220149993896
+ eval_/next-item/ndcg_at_20 = 0.07178892195224762
+ eval_/next-item/recall_at_10 = 0.10866450518369675
+ eval_/next-item/recall_at_20 = 0.15722796320915222
+ eval_runtime = 48.7391
+ eval_samples_per_second = 272.471
+ eval_steps_per_second = 8.515
+********************
+Launch training for day 2 are:
+********************
+
+
+
+
+ + + [1245/1245 02:01, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2008.442700
4008.282900
6008.154300
8008.022300
10007.951400
12007.864900

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
********************
+Eval results for day 3 are:	
+
+********************
+
+ eval_/loss = 8.165782928466797
+ eval_/next-item/ndcg_at_10 = 0.07608938217163086
+ eval_/next-item/ndcg_at_20 = 0.09094516187906265
+ eval_/next-item/recall_at_10 = 0.14394062757492065
+ eval_/next-item/recall_at_20 = 0.2028217315673828
+ eval_runtime = 41.5671
+ eval_samples_per_second = 295.618
+ eval_steps_per_second = 9.238
+********************
+Launch training for day 3 are:
+********************
+
+
+
***** Running training *****
+  Num examples = 97792
+  Num Epochs = 3
+  Instantaneous batch size per device = 256
+  Total train batch size (w. parallel, distributed & accumulation) = 256
+  Gradient Accumulation steps = 1
+  Total optimization steps = 1146
+
+
+
+
+ + + [1146/1146 01:51, Epoch 3/3] +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StepTraining Loss
2008.026600
4007.860000
6007.709400
8007.622100
10007.549700

Saving model checkpoint to ./tmp/checkpoint-500
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+Saving model checkpoint to ./tmp/checkpoint-1000
+Trainer.model is not a `PreTrainedModel`, only saving its state dict.
+
+
+Training completed. Do not forget to share your model on huggingface.co/models =)
+
+
+
********************
+Eval results for day 4 are:	
+
+********************
+
+ eval_/loss = 7.7548112869262695
+ eval_/next-item/ndcg_at_10 = 0.09143278002738953
+ eval_/next-item/ndcg_at_20 = 0.10998427122831345
+ eval_/next-item/recall_at_10 = 0.17630764842033386
+ eval_/next-item/recall_at_20 = 0.249935582280159
+ eval_runtime = 66.6057
+ eval_samples_per_second = 233.494
+ eval_steps_per_second = 7.297
+CPU times: user 16min 50s, sys: 1min 6s, total: 17min 57s
+Wall time: 8min 42s
+
+
+
+
+

Add XLNet-MLM with side information accuracy results to the results.txt

+
+
+
with open("results.txt", 'a') as f:
+    f.write('\n')
+    f.write('XLNet-MLM with side information accuracy results:')
+    f.write('\n')
+    for key, value in  model.compute_metrics().items(): 
+        f.write('%s:%s\n' % (key, value.item()))
+
+
+
+
+
+
+
!cat results.txt
+
+
+
+
+
GRU accuracy results:
+next-item/ndcg_at_10:0.06745054572820663
+next-item/ndcg_at_20:0.08173713833093643
+next-item/recall_at_10:0.12599845230579376
+next-item/recall_at_20:0.18262046575546265
+
+XLNet-MLM accuracy results:
+next-item/ndcg_at_10:0.0742299035191536
+next-item/ndcg_at_20:0.09017771482467651
+next-item/recall_at_10:0.14068539440631866
+next-item/recall_at_20:0.20387786626815796
+
+XLNet-MLM with side information accuracy results:
+next-item/ndcg_at_10:0.08309739083051682
+next-item/ndcg_at_20:0.10099391639232635
+next-item/recall_at_10:0.1542772501707077
+next-item/recall_at_20:0.22519969940185547
+
+
+
+
+

In the end, using side information provided higher accuracy. Why is that? Have an idea?

+
+
+
+
+
+

Wrap Up

+

Congratulations on finishing this notebook. In this tutorial, we have presented Transformers4Rec, an open source library designed to enable RecSys researchers and practitioners to quickly and easily explore the latest developments of the NLP for sequential and session-based recommendation tasks.

+
+
+

References

+

[1] Malte Ludewig and Dietmar Jannach. 2018. Evaluation of session-based recommendation algorithms. User Modeling and User-Adapted Interaction 28, 4-5 (2018), 331–390.
+[2] Balázs Hidasi and Alexandros Karatzoglou. 2018. Recurrent neural networks with top-k gains for session-based recommendations. In Proceedings of the 27th ACMinternational conference on information and knowledge management. 843–852.
+[3] Fei Sun, Jun Liu, Jian Wu, Changhua Pei, Xiao Lin, Wenwu Ou, and Peng Jiang. 2019. BERT4Rec: Sequential recommendation with bidirectional encoder representations from transformer. In Proceedings of the 28th ACM international conference on information and knowledge management. 1441–1450. +[4] Shiming Sun, Yuanhe Tang, Zemei Dai, and Fu Zhou. 2019. Self-attention network for session-based recommendation with streaming data input. IEEE Access 7 (2019), 110499–110509.
+[5] Kyunghyun Cho, Bart Van Merriënboer, Caglar Gulcehre, Dzmitry Bahdanau, Fethi Bougares, Holger Schwenk, and Yoshua Bengio. 2014. Learning phrase representations using RNN encoder-decoder for statistical machine translation. arXiv preprint arXiv:1406.1078 (2014).
+[6] Vaswani, A., et al. (2017). Attention is all you need. In Advances in neural information processing systems (pp. 5998-6008).
+[7] Lample, Guillaume, and Alexis Conneau. “Cross-lingual language model pretraining.” arXiv preprint arXiv:1901.07291
+[8] Gabriel De Souza P. Moreira, et al. (2021). Transformers4Rec: Bridging the Gap between NLP and Sequential / Session-Based Recommendation. RecSys’21.
+[9] Understanding XLNet, BorealisAI. Online available: https://www.borealisai.com/en/blog/understanding-xlnet/
+[10] Yang, Zhilin, et al. “Xlnet: Generalized autoregressive pretraining for language understanding.” Advances in neural information processing systems 32 (2019).

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/examples/tutorial/index.html b/review/pr-767/examples/tutorial/index.html new file mode 100644 index 0000000000..05385d7633 --- /dev/null +++ b/review/pr-767/examples/tutorial/index.html @@ -0,0 +1,185 @@ + + + + + + Tutorial: End-to-end Session-based Recommendation — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Tutorial: End-to-end Session-based Recommendation

+

Session-based recommendation, a sub-area of sequential recommendation, has been an important task in online services like e-commerce and news portals. Session-based recommenders provide relevant and personalized recommendations even when prior user history is not available or their tastes change over time. They recently gained popularity due to their ability to capture short-term or contextual user preferences towards items.

+
+

Learning Objectives

+

The example notebooks cover the following concepts and tasks:

+
    +
  • Preprocessing with cuDF and NVTabular.

  • +
  • Feature engineering with NVTabular.

  • +
  • Introduction to Transformers4Rec.

    +
      +
    • Introduction to session-based recommendation.

    • +
    • Accelerated dataloaders for PyTorch.

    • +
    • Training and evaluating an RNN-based session based recommendation model for next item prediction task.

    • +
    • Training and evaluating Transformer architecture based session-based recommendation model next item prediction task.

    • +
    • Using side information (additional features) to improve the accuracy of a model.

    • +
    +
  • +
  • Deploying to inference with Triton Inference Server.

  • +
+
+
+

Getting Started

+

In this tutorial, we use a subset of the publicly-available eCommerce dataset. +The e-commerce behavior data contains 7 months of data (from October 2019 to April 2020) from a large multi-category online store. +Each row in the file represents an event. +All events are related to products and users. +Each event is a many-to-many relation between products and users. +The data were collected by the Open CDP project and the source of the dataset is REES46 Marketing Platform.

+

We use only the 2019-Oct.csv file for training our models, so you can visit this site and download the csv file: https://www.kaggle.com/mkechinov/ecommerce-behavior-data-from-multi-category-store.

+

Refer to the following notebooks:

+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/genindex.html b/review/pr-767/genindex.html new file mode 100644 index 0000000000..7a7133a3f6 --- /dev/null +++ b/review/pr-767/genindex.html @@ -0,0 +1,2892 @@ + + + + + + Index — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Index
  • +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + | X + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + +
+ +

I

+ + + +
+ +

J

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

X

+ + +
+ + + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/index.html b/review/pr-767/index.html new file mode 100644 index 0000000000..d7885d1636 --- /dev/null +++ b/review/pr-767/index.html @@ -0,0 +1,160 @@ + + + + + + Merlin Transformers4Rec — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Merlin Transformers4Rec
  • +
  • +
  • +
+
+
+
+
+ +
+

Merlin Transformers4Rec

+

NVIDIA Merlin Transformers4Rec is a flexible and efficient library for sequential and session-based recommendation. +Transformers4Rec works with PyTorch.

+

To learn more, start with the Introduction.

+ +
+
+

Indices and tables

+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/model_definition.html b/review/pr-767/model_definition.html new file mode 100644 index 0000000000..b805b6f32a --- /dev/null +++ b/review/pr-767/model_definition.html @@ -0,0 +1,290 @@ + + + + + + Model Architectures — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Model Architectures
  • +
  • +
  • +
+
+
+
+
+ +
+

Model Architectures

+ +
+

Modular Building-Block Design

+

Transformers4Rec provides modularized building blocks that you can combine with standard PyTorch modules. +This provides a great flexibility in the model definition so that you can use these blocks to build custom architectures with multiple towers, multiple heads, and losses (multi-task). +For more information about the available options for each building block, refer to our API Documentation.

+

The following figure shows a reference architecture for next-item prediction with Transformers. +The model can be used for both sequential and session-based recommendation. +This architecture can be divided into four conceptual layers:

+
    +
  • Feature aggregation (Input Block)

  • +
  • Sequence masking

  • +
  • Sequence processing (Transformer/RNN Block)

  • +
  • Prediction head (Output Block)

  • +
+

Transformers4Rec meta-architecture

+
+
Transformers4Rec meta-architecture
+
+
+
+

Feature Aggregation (Input Block)

+

To read the sequences of input features like user IDs, user metadata, item IDs, and item metadata into a Transformer block, the sequences must be aggregated into a single vector representation per element in the sequence that we call the interaction embedding.

+

The following list identifies the aggregation methods:

+
    +
  • Concat: Concatenation of the features.

  • +
  • Element-wise sum: Features are summed in which all features must have the same dimension. +For example, categorical embeddings must have the same dimension and continuous features are projected to that dimension.

  • +
  • Element-wise sum with item multiplication: Similar to the Element-wise sum aggregation in which all features are summed except for the item ID embedding because it is multiplied by the other features’ sum. +The aggregation formula is available in our Transformers4Rec: Bridging the Gap between NLP and Sequential / Session-Based Recommendation paper.

  • +
+

Categorical features are represented by embeddings. +Numerical features can be represented as a scalar and projected by a fully-connected (FC) layer to multiple dimensions or represented as a weighted average of embeddings by using the soft one-hot encoding technique. +For more information, refer to the online appendix to the preceding paper. +Categorical input features are optionally normalized (with layer normalization) before aggregation. +Continuous features should be normalized during feature engineering of the dataset.

+

TabularSequenceFeatures is the core class of this module. +This class processes and aggregates all features and outputs a sequence of interaction embeddings to be fed into transformer blocks. +You can create an instance of TabularSequenceFeatures automatically from a dataset schema that is generated from NVTabular by using the from_schema() method. +This method creates the layers that are required to represent the categorical and continuous features in the dataset. +In addition, you can specify the aggregation option of this method to aggregate the sequential features and to prepare masked labels according to the specified sequence masking approach.

+

The following code block shows one way the TabularSequenceFeatures.from_schema() method can create the interaction embeddings that are ready for use with Transformer blocks:

+
from transformers4rec.torch import TabularSequenceFeatures
+tabular_inputs = TabularSequenceFeatures.from_schema(
+        schema,
+        embedding_dim_default=128,
+        max_sequence_length=20,
+        d_output=100,
+        aggregation="concat",
+        masking="clm"
+    )
+
+
+
+

The embedding_dim_default argument sets a fixed dimension for all categorical input features. +For more information, see the TabularSequenceFeatures class documentation.

+
+
+
+

Sequence Masking

+

You can train Transformer architectures in different ways. +Depending on the training method, there is a specific masking schema. +The masking schema sets the items to be predicted–labels–and masks some positions of the sequence that cannot be used by the Transformer layers for prediction.

+

Transformers4Rec supports the following training approaches that are inspired by NLP:

+
    +
  • Causal Language Modeling (masking="clm"): Predicts the next item based on past positions of the sequence. +Future positions are masked.

  • +
  • Masked Language Modeling (masking="mlm"): Randomly selects some positions of the sequence to predict, which are masked. +The Transformer layer is allowed to use positions on the right–future information–during training. +During inference, all past items are visible for the Transformer layer as it tries to predict the next item.

  • +
  • Permutation Language Modeling (masking="plm"): Uses a permutation factorization at the level of the self-attention layer to define the accessible bi-directional context.

  • +
+

NOTE: Not all transformer architectures support all of these training approaches. +Transformers4Rec raises an exception if you attempt to use an invalid combination and provides suggestions for using the appropriate masking techniques for that architecture.

+
+
+

Sequence Processing (Transformer/RNN Block)

+

The Transformer block processes the input sequences of interaction embeddings created by the input block using Transformer architectures like XLNet, GPT-2, and so on–or RNN architectures like LSTM or GRU. +The created block is a standard Torch block and is compatible with and substitutable by other Torch blocks that support the input of a sequence.

+

In the following example, a SequentialBlock module is used to build the model body. +The model contains a TabularSequenceFeatures object (tabular_inputs defined in the previous code snippet), followed by an MLP projection layer to 64 dim (to match the Transformer d_model), and then is followed by an XLNet transformer block with two layers (four heads each).

+
from transformers4rec.config import transformer
+from transformers4rec.torch import MLPBlock, SequentialBlock, TransformerBlock
+
+# Configures the XLNet Transformer architecture.
+transformer_config = transformer.XLNetConfig.build(
+    d_model=64, n_head=4, n_layer=2, total_seq_length=20
+)
+
+# Defines the model body including: inputs, masking, projection and transformer block.
+model_body = SequentialBlock(
+    tabular_inputs,
+    torch4rec.MLPBlock([64]),
+    torch4rec.TransformerBlock(transformer_config, masking=tabular_inputs.masking)
+)
+
+
+
+
+

Prediction Head (Output Block)

+

Following the input and transformer blocks, the model outputs its predictions. +Transformers4Rec supports the following prediction heads, which can have multiple losses and can be combined for multi-task learning and multiple metrics:

+
    +
  • Next Item Prediction: Predicts next items for a given sequence of interactions. +During training, the prediction can be the next item or randomly selected items depending on the masking scheme. +For inference, the intended purpose is to always predict the next interacted item. +Cross-entropy and pairwise losses are supported.

  • +
  • Binary Classification: Predicts a binary feature using the whole sequence. +In the context of recommendation, you can use classification to predict the user’s next action such as whether the user will abandon a product in their cart or proceed with the purchase.

  • +
  • Regression: Predicts a continuous feature using the whole sequence, such as the elapsed time until the user returns to a service.

  • +
+

In the following example, a head is instantiated with the predefined model_body for the NextItemPredictionTask. +This head enables the weight_tying option. +Decoupling the model bodies and heads provides a flexible architecture that enables you to define a model with features like multiple towers and multiple heads. +Lastly, the Model class combines the heads and wraps the whole model.

+
from transformers4rec.torch import Head, Model
+from transformers4rec.torch.model.head import NextItemPredictionTask
+
+# Defines the head related to next item prediction task
+head = Head(
+    model_body,
+    NextItemPredictionTask(weight_tying=True),
+    inputs=inputs,
+)
+
+# Get the end-to-end Model class
+model = Model(head)
+
+
+
+

Tying Embeddings

+

For the NextItemPredictionTask class, we recommend tying embeddings. +The tying embeddings concept was initially proposed by the NLP community to tie the weights of the input (item ID) embedding matrix with the output projection layer. +Not only do tied embeddings reduce the memory requirements significantly, but our own experimentation during recent competitions and empirical analysis detailed in our Transformers4Rec: Bridging the Gap between NLP and Sequential / Session-Based Recommendation paper and online appendix demonstrate that this method is very effective. +Tying embeddings is enabled by default, but can be disabled by setting weight_tying to False.

+
+
+
+

Regularization

+

Transformers4Rec supports a number of regularization techniques such as dropout, weight decay, softmax temperature scaling, stochastic shared embeddings, and label smoothing. +In our extensive experimentation, we hypertuned all regularization techniques for different datasets and found out that label smoothing was particularly useful at improving both training and validation accuracy and better at calibrating the predictions.

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/multi_gpu_train.html b/review/pr-767/multi_gpu_train.html new file mode 100644 index 0000000000..5798609d5d --- /dev/null +++ b/review/pr-767/multi_gpu_train.html @@ -0,0 +1,184 @@ + + + + + + Multi-GPU data-parallel training using the Trainer class — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Multi-GPU data-parallel training using the Trainer class
  • +
  • +
  • +
+
+
+
+
+ +
+

Multi-GPU data-parallel training using the Trainer class

+

To train models faster, users can use Data-Parallel training when using transformers4rec.Trainer for training. Data-parallel multi-GPU training distributes train data between GPUs to speedup training and support larger batch sizes at each step.

+

The Trainer class supports both DataParallel and DistributedDataParallel built-in features of PyTorch. Here we explain how each of these training modes can be used in Transformers4Rec.

+
+

DataParallel

+

When the DataParallel mode is used, the following happens for each training step:

+
    +
  • GPU-0 reads a batch then evenly distributes it among available GPUs

  • +
  • The latest model will be copied to all GPUs

  • +
  • A Python thread is created for each GPU to run forward() step and the partial loss will be sent to GPU-0 to compute the global loss

  • +
  • Computed global loss is broadcasted to all GPU threads to run backward()

  • +
  • Gradients from each GPU are sent to GPU-0 and their average is computed

  • +
+

As we see, parallelism in DataParallel mode is implemented through Python threads which will be blocked by GIL (Global Interepreter Lock) so DataParallel is not the preferred method. Users are advised to use DistributedDataParallel instead as it uses multi-processing and is better maintained. Also, some model types such as transfoxl can not be used with DataParallel. To learn more about DataParallel refer to PyTorch documentation.

+

To use the DataParallel mode training, user just needs to make sure CUDA_VISIBLE_DEVICES is set. For example when 2 GPUs are available:

+
    +
  • Add os.environ["CUDA_VISIBLE_DEVICES"]="0,1" to the script

  • +
+

or

+
    +
  • Run export CUDA_VISIBLE_DEVICES=0,1 in the terminal

  • +
+

Do not try wrapping the model with torch.nn.DataParallel yourself because that will break automatic wrapping done by Trainer. +Note: When using DataParallel the dataloader generates one batch on each train step then the batch will be divided between GPUs so the per_device_train_batch_size argument represents the total batch size in this mode, not size of the batch each GPU receives.

+
+
+

DistributedDataParallel

+

This is the suggested and more efficient method. When a model is trained using the DistributedDataParallel mode:

+
    +
  • A separate process will be assigned to each GPU in the beginning and GPU-0 will replicate the model on each GPU

  • +
  • On each step each GPU receives a different mini-batch produced by the dataloader

  • +
  • On the backward pass the gradient from GPUs will be averaged for accumulation

  • +
+

To learn more about DistributedDataParallel see the PyTorch Documentation.

+

To train using the DistributedDataParallel mode user should use PyTorch distributed launcher to run the script:

+

python -m torch.distributed.launch --nproc_per_node N_GPU your_script.py --your_arguments

+

To have one process per GPU, replace N_GPU with the number of GPUs you want to use. Optionally, you can also set CUDA_VISIBLE_DEVICES accordingly.

+

Note: When using DistributedDataParallel, our data loader splits data between the GPUs based on dataset partitions. For that reason, the number of partitions of the dataset must be equal to or an integer multiple of the number of processes. If the parquet file has a small number of row groups (partitions), try repartitioning and saving it again using cudf or pandas before training. The dataloader checks dataloader.dataset.npartitions and will repartition if needed but we advise users to repartition the dataset and save it for better efficiency. Use pandas or cudf for repartitioning. Example of repartitioning a parquet file with cudf:

+

gdf.to_parquet("filename.parquet", row_group_size_rows=10000)

+

Choose row_group_size_rows such that nr_rows/row_group_size_rows>=n_proc because n_rows=npartition*row_group_size_rows.

+

With pandas, use the argument as row_group_size=10000 instead.

+

Please make sure that the resulting number of partitions is divisible by number of GPUs to be used (eg. 2 or 4 partitions, if 2 GPUs will be used).

+
+
+

Performance Comparison

+

We trained and evaluated a number of models using single GPU, DataParallel and DistributedDataParallel training modes and the results are shown in the table below. To reproduce, use the models included in ci/test_integration.sh.

+

Performance comparison of diffrerent training modes

+

These experiments used a machine with 2 Tesla V100-SXM2-32GB-LS GPUs.

+

Note: Using Data-Parallel training can impact metrics such at NDCG and recall. Therefore, Users are advised to tune the learning rate for better accuracy.

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/objects.inv b/review/pr-767/objects.inv new file mode 100644 index 0000000000..1961c034e2 Binary files /dev/null and b/review/pr-767/objects.inv differ diff --git a/review/pr-767/pipeline.html b/review/pr-767/pipeline.html new file mode 100644 index 0000000000..1096943c43 --- /dev/null +++ b/review/pr-767/pipeline.html @@ -0,0 +1,251 @@ + + + + + + End-to-End Pipeline with Hugging Face Transformers and NVIDIA Merlin — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • End-to-End Pipeline with Hugging Face Transformers and NVIDIA Merlin
  • +
  • +
  • +
+
+
+
+
+ +
+

End-to-End Pipeline with Hugging Face Transformers and NVIDIA Merlin

+ +
+

Overview of the Pipeline

+

Transformers4Rec has a first-class integration with Hugging Face (HF) Transformers, NVTabular, and Triton Inference Server, making it easy to build end-to-end GPU accelerated pipelines for sequential and session-based recommendation.

+

Pipeline for Sequential and Session-based recommendation using NVIDIA Merlin components

+
+
Pipeline for Sequential and Session-based recommendation using NVIDIA Merlin components
+
+
+
+

Integration with Hugging Face Transformers

+

Transformers4Rec integrates with Hugging Face Transformers, allowing RecSys researchers and practitioners to easily experiment with the latest state-of-the-art NLP Transformer architectures for sequential and session-based recommendation tasks and deploy those models into production.

+

HF Transformers has become very popular among NLP researchers and practitioners (more than 900 contributors), providing standardized implementations of the state-of-the-art transformer architectures (more than 68 and counting) produced by the research community, often within days or weeks of their publication.

+

Models are composed of three building blocks:

+
    +
  • Tokenizer that converts raw text to sparse index encodings

  • +
  • Transformer architecture

  • +
  • Head for NLP tasks such as text classification, generation, sentiment analysis, translation, and summarization

  • +
+

Example of preprocessed parquet file

+
+
Example of preprocessed parquet file
+
+

Only the Transformer architecture building block and their configuration classes are leveraged from HF Transformers. +Transformers4Rec provides additional building blocks that are necessary for recommendation such as input features like normalization and aggregation as well as heads for recommendation and sequence classification and prediction. +These building blocks’ Trainer class are extended to enable evaluation with RecSys metrics.

+
+
+

Integration with NVTabular

+

NVTabular is a feature engineering and preprocessing library for tabular data that is designed to easily manipulate datasets at terabyte scale and train deep learning (DL) based recommender systems.

+

Some popular techniques have been implemented within NVTabular to deal with categorical and numerical features, such as Categorify, Normalize, Bucketize, TargetEncoding, and DifferenceLag, and allow for custom transformations (LambdaOp) to be defined using cuDF data frame operations.

+

Typically, the input RecSys datasets contain one example per user interaction. +For sequential recommendation, the training example is a sequence of user interactions. +For session-based recommendation, the training example is a sequence of session interactions. +In practice, each interaction-level feature needs to be converted to a sequence that is grouped by user or session and their sequence length must match since each position of the sequence corresponds to one interaction.

+

The following figure provides a visualization of the preprocessed tabular data:

+

Example of a preprocessed parquet file

+
+
Example of a Preprocessed Parquet File
+
+
+

NVTabular can easily prepare such data with the Groupby operation. +This operation supports grouping by a categorical column such as user ID and session ID, sorting by another column such as timestamp and aggregating other columns as sequences (list), or by taking the first or last element of the sequence as shown in the following code block.

+
groupby_features = [
+    'user_id', 'session_id', 'product_id', 'category_id', 'timestamp'
+] >> ops.Groupby(
+    groupby_cols=['session_id'],
+    sort_cols=['timestamp'],
+    aggs={
+        'product_id': 'list',
+        'category_id': 'list',
+        'timestamp': ['first', 'last'],
+    },
+)
+
+
+
+

Outputs

+

NVTabular can save preprocessed data in the Parquet file format. +You can partition the data by a categorical column, such as day and company, as shown in the following example:

+
nvt_output_path ='./output'
+partition_col = ['day']
+nvt.Dataset(dataset).to_parquet(nvt_output_path, partition_on=[partition_col])
+
+
+

NVTabular also creates a schema file, schema.pbtxt, in the protobuf text format with the Parquet files. +The schema file contains statistics that are obtained during the preprocessing such as the cardinality of categorical features and the maximum sequence length for sequential features. +NVTabular also supports the association of tags for features. +You can use the tags to indicate the item ID, item and user features, and categorical or continuous features. +This example of a schema.pbtxt file properly formats the schema in protobuf text.

+

NOTE: If you don’t use NVTabular to preprocess your data, you can also instantiate a Schema in code manually as shown in this schema example Python program.

+

After you call workflow.fit(), you can save the workflow so that you can apply the same preprocessing workflow to new input data as a batch or online by using the Triton Inference Server integration.

+

The following code block shows how to save an NVTabular workflow:

+
# Instantiates an NVTabular dataset
+dataset = nvt.Dataset([os.path.join(INPUT_PATH, "*.parquet")], part_size="100MB")
+
+# Perform a single pass over the dataset to collect columns statistics
+workflow.fit(dataset)
+
+# Applies the transform ops to the dataset
+new_dataset = workflow.transform(dataset)
+
+# Saves the preprocessed dataset in parquet files
+new_dataset.to_parquet("/path")
+
+# Saves the "fitted" preprocessing workflow
+workflow.save(os.path.join(OUTPUT_PATH, "workflow"))
+
+
+
+
+
+

Integration with Triton Inference Server

+

NVIDIA Triton Inference Server (TIS) simplifies the deployment of AI models at scale to production. +TIS is a cloud and edge inferencing solution that is optimized to deploy machine learning models for GPUs and CPUs. +It supports a number of different deep learning frameworks such as TensorFlow and PyTorch.

+

An end-to-end ML/DL pipeline consists of preprocessing and feature engineering (ETL), model training, and model deployment for inference. +Model deployment to production is the critical step of this pipeline because it enables model inference for practical business decisions. +In the production setting, we want to apply the input data to the same data transformation operations that were completed during training (ETL). +Essentially, the preprocessing operations, such as standardizing continuous features and encoding categorical features, should be compatible with the statistics of the original data before feeding data to the deep learning model. +NVTabular supports the same scenario when you save the data processing workflow along with a trained PyTorch or Tensorflow model in a single ensemble pipeline to be served on TIS.

+

The TIS integration enables the deployment of deep learning recommender models at scale with GPU acceleration. +Transformers4Rec supports exporting a model trained with the PyTorch API to Triton Inference Server using the Python backend.

+

To learn about how to deploy a large and complex recommender workflow to production with only a few lines of code, refer to our end-to-end-session-based recommendation notebook and session-based recommendation on GPU with Transformers4Rec notebook.

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/py-modindex.html b/review/pr-767/py-modindex.html new file mode 100644 index 0000000000..26502523d5 --- /dev/null +++ b/review/pr-767/py-modindex.html @@ -0,0 +1,358 @@ + + + + + + Python Module Index — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Python Module Index
  • +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ m | + t +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ m
+ merlin_standard_lib +
    + merlin_standard_lib.proto +
    + merlin_standard_lib.proto.schema_bp +
    + merlin_standard_lib.schema +
    + merlin_standard_lib.schema.schema +
    + merlin_standard_lib.schema.tag +
    + merlin_standard_lib.utils +
    + merlin_standard_lib.utils.embedding_utils +
 
+ t
+ transformers4rec +
    + transformers4rec.config +
    + transformers4rec.config.schema +
    + transformers4rec.config.trainer +
    + transformers4rec.config.transformer +
    + transformers4rec.torch +
    + transformers4rec.torch.block +
    + transformers4rec.torch.block.base +
    + transformers4rec.torch.block.mlp +
    + transformers4rec.torch.block.transformer +
    + transformers4rec.torch.features +
    + transformers4rec.torch.features.base +
    + transformers4rec.torch.features.continuous +
    + transformers4rec.torch.features.embedding +
    + transformers4rec.torch.features.sequence +
    + transformers4rec.torch.features.tabular +
    + transformers4rec.torch.features.text +
    + transformers4rec.torch.masking +
    + transformers4rec.torch.model +
    + transformers4rec.torch.model.prediction_task +
    + transformers4rec.torch.ranking_metric +
    + transformers4rec.torch.tabular +
    + transformers4rec.torch.tabular.aggregation +
    + transformers4rec.torch.tabular.transformations +
    + transformers4rec.torch.trainer +
    + transformers4rec.torch.typing +
    + transformers4rec.torch.utils +
    + transformers4rec.torch.utils.data_utils +
    + transformers4rec.torch.utils.examples_utils +
    + transformers4rec.torch.utils.schema_utils +
    + transformers4rec.torch.utils.torch_utils +
    + transformers4rec.types +
    + transformers4rec.utils +
    + transformers4rec.utils.dependencies +
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/resources.html b/review/pr-767/resources.html new file mode 100644 index 0000000000..5315d701e0 --- /dev/null +++ b/review/pr-767/resources.html @@ -0,0 +1,304 @@ + + + + + + Additional Resources — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Additional Resources
  • +
  • +
  • +
+
+
+
+
+ +
+

Additional Resources

+ +
+

Transformers4Rec and Session-Based Recommendation

+ +
+
+

Competitions

+
    +
  • SIGIR eCommerce Workshop Data Challenge 2021 (organized by Coveo) - The NVIDIA Merlin team won this competition by using Transformer architectures to predict the next interacted products for user sessions in an e-commerce. For more information about our solution, refer to our blog post and paper.

  • +
  • WSDM WebTour Challenge 2021 (organized by Booking. com) - The NVIDIA Merlin team won this competition by leveraging a Transformers4Rec model in the final ensemble. For more information about our solution, refer to our blog post and paper.

  • +
+
+
+

NVIDIA Merlin

+

Transformers4Rec is part of the NVIDIA Merlin ecosystem for recommender systems, which includes the following components:

+
    +
  • NVTabular - NVTabular is a feature engineering and preprocessing library for tabular data that is designed to easily manipulate datasets at terabyte scale and train deep learning (DL) based recommender systems.

  • +
  • Triton Inference Server - Provides a cloud and edge inferencing solution that is optimized for both CPUs and GPUs. Transformers4Rec models can be exported and served with Triton.

  • +
  • HugeCTR - A GPU-accelerated recommender framework designed to distribute training across multiple GPUs and nodes and estimate Click-Through Rates (CTRs).

  • +
+
+
+

Supported Hugging Face Architectures and Pre-Training Approaches

+

Transformers4Rec supports the following masking tasks:

+
    +
  • Causal Language Modeling (CLM)

  • +
  • Masked Language Modeling (MLM)

  • +
  • Permutation Language Modeling (PLM)

  • +
  • Replacement Token Detection (RTD)

  • +
+

In Transformers4Rec, we decouple the pre-training approaches from transformers architectures and provide a TransformerBlock module that links the config class of the transformer architecture to the masking task. Transformers4Rec also defines a transformer_registry, which includes pre-defined T4RecConfig constructors that automatically set the arguments of the related Hugging Face Transformers’ configuration classes.

+

The table below represents the architectures that are currently supported in Transformers4Rec and links them to the possible masking tasks. It also lists the pre-registered T4RecConfig classes in the Registered column.

+

Tip: Consider registering HF Transformers config classes into Transformers4Rec as this can be a great first contribution.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Model

CLM

MLM

PLM

RTD

Registered

AlBERT

BERT

ConvBERT

DeBERTa

DistilBERT

GPT-2

Longformer

MegatronBert

MPNet

RoBERTa

RoFormer

Transformer-XL

XLNet

+

Note: The following HF architectures will eventually be supported: Reformer, Funnel Transformer, and ELECTRA.

+
+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/search.html b/review/pr-767/search.html new file mode 100644 index 0000000000..1bc48e00c4 --- /dev/null +++ b/review/pr-767/search.html @@ -0,0 +1,148 @@ + + + + + + Search — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Search
  • +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/review/pr-767/searchindex.js b/review/pr-767/searchindex.js new file mode 100644 index 0000000000..6d278dd7ac --- /dev/null +++ b/review/pr-767/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["CONTRIBUTING","README","api/merlin_standard_lib","api/merlin_standard_lib.proto","api/merlin_standard_lib.schema","api/merlin_standard_lib.utils","api/modules","api/transformers4rec","api/transformers4rec.config","api/transformers4rec.torch","api/transformers4rec.torch.block","api/transformers4rec.torch.features","api/transformers4rec.torch.model","api/transformers4rec.torch.tabular","api/transformers4rec.torch.utils","api/transformers4rec.utils","examples/end-to-end-session-based/01-ETL-with-NVTabular","examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT","examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT","examples/end-to-end-session-based/index","examples/getting-started-session-based/01-ETL-with-NVTabular","examples/getting-started-session-based/02-session-based-XLNet-with-PyT","examples/getting-started-session-based/03-serving-session-based-model-torch-backend","examples/getting-started-session-based/index","examples/index","examples/t4rec_paper_experiments/index","examples/tutorial/01-preprocess","examples/tutorial/02-ETL-with-NVTabular","examples/tutorial/03-Session-based-recsys","examples/tutorial/index","index","model_definition","multi_gpu_train","pipeline","resources","training_eval","why_transformers4rec"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["CONTRIBUTING.md","README.md","api/merlin_standard_lib.rst","api/merlin_standard_lib.proto.rst","api/merlin_standard_lib.schema.rst","api/merlin_standard_lib.utils.rst","api/modules.rst","api/transformers4rec.rst","api/transformers4rec.config.rst","api/transformers4rec.torch.rst","api/transformers4rec.torch.block.rst","api/transformers4rec.torch.features.rst","api/transformers4rec.torch.model.rst","api/transformers4rec.torch.tabular.rst","api/transformers4rec.torch.utils.rst","api/transformers4rec.utils.rst","examples/end-to-end-session-based/01-ETL-with-NVTabular.ipynb","examples/end-to-end-session-based/02-End-to-end-session-based-with-Yoochoose-PyT.ipynb","examples/end-to-end-session-based/03-Session-based-Yoochoose-multigpu-training-PyT.ipynb","examples/end-to-end-session-based/index.md","examples/getting-started-session-based/01-ETL-with-NVTabular.ipynb","examples/getting-started-session-based/02-session-based-XLNet-with-PyT.ipynb","examples/getting-started-session-based/03-serving-session-based-model-torch-backend.ipynb","examples/getting-started-session-based/index.md","examples/index.md","examples/t4rec_paper_experiments/index.md","examples/tutorial/01-preprocess.ipynb","examples/tutorial/02-ETL-with-NVTabular.ipynb","examples/tutorial/03-Session-based-recsys.ipynb","examples/tutorial/index.md","index.rst","model_definition.md","multi_gpu_train.md","pipeline.md","resources.md","training_eval.md","why_transformers4rec.md"],objects:{"":{merlin_standard_lib:[2,0,0,"-"],transformers4rec:[7,0,0,"-"]},"merlin_standard_lib.ColumnSchema":{copy:[2,2,1,""],create_categorical:[2,2,1,""],create_continuous:[2,2,1,""],properties:[2,2,1,""],tags:[2,2,1,""],to_proto_text:[2,2,1,""],with_name:[2,2,1,""],with_properties:[2,2,1,""],with_tags:[2,2,1,""],with_tags_based_on_properties:[2,2,1,""]},"merlin_standard_lib.Schema":{add:[2,2,1,""],apply:[2,2,1,""],apply_inverse:[2,2,1,""],column_names:[2,2,1,""],column_schemas:[2,2,1,""],copy:[2,2,1,""],create:[2,2,1,""],feature:[2,3,1,""],filter_column_schemas:[2,2,1,""],filter_columns_from_dict:[2,2,1,""],from_json:[2,2,1,""],from_proto_text:[2,2,1,""],item_id_column_name:[2,2,1,""],map_column_schemas:[2,2,1,""],remove_by_name:[2,2,1,""],remove_by_tag:[2,2,1,""],remove_by_type:[2,2,1,""],select_by_name:[2,2,1,""],select_by_tag:[2,2,1,""],select_by_type:[2,2,1,""],to_proto_text:[2,2,1,""],with_tags_based_on_properties:[2,2,1,""]},"merlin_standard_lib.proto":{schema_bp:[3,0,0,"-"]},"merlin_standard_lib.proto.schema_bp":{Annotation:[3,1,1,""],BoolDomain:[3,1,1,""],DatasetConstraints:[3,1,1,""],DistributionConstraints:[3,1,1,""],Feature:[3,1,1,""],FeatureComparator:[3,1,1,""],FeatureCoverageConstraints:[3,1,1,""],FeaturePresence:[3,1,1,""],FeaturePresenceWithinGroup:[3,1,1,""],FeatureType:[3,1,1,""],FixedShape:[3,1,1,""],FixedShapeDim:[3,1,1,""],FloatDomain:[3,1,1,""],ImageDomain:[3,1,1,""],InfinityNorm:[3,1,1,""],IntDomain:[3,1,1,""],JensenShannonDivergence:[3,1,1,""],LifecycleStage:[3,1,1,""],MIDDomain:[3,1,1,""],NaturalLanguageDomain:[3,1,1,""],NumericValueComparator:[3,1,1,""],Path:[3,1,1,""],SequenceLengthConstraints:[3,1,1,""],SequenceValueConstraints:[3,1,1,""],SparseFeature:[3,1,1,""],SparseFeatureIndexFeature:[3,1,1,""],SparseFeatureValueFeature:[3,1,1,""],StringDomain:[3,1,1,""],StructDomain:[3,1,1,""],TensorRepresentation:[3,1,1,""],TensorRepresentationDefaultValue:[3,1,1,""],TensorRepresentationDenseTensor:[3,1,1,""],TensorRepresentationGroup:[3,1,1,""],TensorRepresentationRaggedTensor:[3,1,1,""],TensorRepresentationRaggedTensorPartition:[3,1,1,""],TensorRepresentationRowPartitionDType:[3,1,1,""],TensorRepresentationSparseTensor:[3,1,1,""],TensorRepresentationVarLenSparseTensor:[3,1,1,""],TimeDomain:[3,1,1,""],TimeDomainIntegerTimeFormat:[3,1,1,""],TimeOfDayDomain:[3,1,1,""],TimeOfDayDomainIntegerTimeOfDayFormat:[3,1,1,""],URLDomain:[3,1,1,""],UniqueConstraints:[3,1,1,""],ValueCount:[3,1,1,""],ValueCountList:[3,1,1,""],WeightedFeature:[3,1,1,""]},"merlin_standard_lib.proto.schema_bp.Annotation":{comment:[3,3,1,""],extra_metadata:[3,3,1,""],metadata:[3,2,1,""],tag:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.BoolDomain":{false_value:[3,3,1,""],name:[3,3,1,""],true_value:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.DatasetConstraints":{max_examples_count:[3,3,1,""],min_examples_count:[3,3,1,""],num_examples_drift_comparator:[3,3,1,""],num_examples_version_comparator:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.DistributionConstraints":{min_domain_mass:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.Feature":{annotation:[3,3,1,""],bool_domain:[3,3,1,""],deprecated:[3,3,1,""],distribution_constraints:[3,3,1,""],domain:[3,3,1,""],drift_comparator:[3,3,1,""],float_domain:[3,3,1,""],group_presence:[3,3,1,""],image_domain:[3,3,1,""],in_environment:[3,3,1,""],int_domain:[3,3,1,""],lifecycle_stage:[3,3,1,""],mid_domain:[3,3,1,""],name:[3,3,1,""],natural_language_domain:[3,3,1,""],not_in_environment:[3,3,1,""],presence:[3,3,1,""],shape:[3,3,1,""],skew_comparator:[3,3,1,""],string_domain:[3,3,1,""],struct_domain:[3,3,1,""],time_domain:[3,3,1,""],time_of_day_domain:[3,3,1,""],type:[3,3,1,""],unique_constraints:[3,3,1,""],url_domain:[3,3,1,""],value_count:[3,3,1,""],value_counts:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FeatureComparator":{infinity_norm:[3,3,1,""],jensen_shannon_divergence:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FeatureCoverageConstraints":{excluded_int_tokens:[3,3,1,""],excluded_string_tokens:[3,3,1,""],min_avg_token_length:[3,3,1,""],min_coverage:[3,3,1,""],oov_string_tokens:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FeaturePresence":{min_count:[3,3,1,""],min_fraction:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FeaturePresenceWithinGroup":{required:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FeatureType":{BYTES:[3,3,1,""],FLOAT:[3,3,1,""],INT:[3,3,1,""],STRUCT:[3,3,1,""],TYPE_UNKNOWN:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FixedShape":{dim:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FixedShapeDim":{name:[3,3,1,""],size:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.FloatDomain":{disallow_inf:[3,3,1,""],disallow_nan:[3,3,1,""],is_embedding:[3,3,1,""],max:[3,3,1,""],min:[3,3,1,""],name:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.ImageDomain":{max_image_byte_size:[3,3,1,""],minimum_supported_image_fraction:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.InfinityNorm":{threshold:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.IntDomain":{is_categorical:[3,3,1,""],max:[3,3,1,""],min:[3,3,1,""],name:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.JensenShannonDivergence":{threshold:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.LifecycleStage":{ALPHA:[3,3,1,""],BETA:[3,3,1,""],DEBUG_ONLY:[3,3,1,""],DEPRECATED:[3,3,1,""],DISABLED:[3,3,1,""],PLANNED:[3,3,1,""],PRODUCTION:[3,3,1,""],UNKNOWN_STAGE:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.NaturalLanguageDomain":{coverage:[3,3,1,""],location_constraint_regex:[3,3,1,""],sequence_length_constraints:[3,3,1,""],token_constraints:[3,3,1,""],vocabulary:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.NumericValueComparator":{max_fraction_threshold:[3,3,1,""],min_fraction_threshold:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.Path":{step:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.SequenceLengthConstraints":{excluded_int_value:[3,3,1,""],excluded_string_value:[3,3,1,""],max_sequence_length:[3,3,1,""],min_sequence_length:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.SequenceValueConstraints":{int_value:[3,3,1,""],max_fraction_of_sequences:[3,3,1,""],max_per_sequence:[3,3,1,""],min_fraction_of_sequences:[3,3,1,""],min_per_sequence:[3,3,1,""],string_value:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.SparseFeature":{dense_shape:[3,3,1,""],deprecated:[3,3,1,""],index_feature:[3,3,1,""],is_sorted:[3,3,1,""],lifecycle_stage:[3,3,1,""],name:[3,3,1,""],presence:[3,3,1,""],type:[3,3,1,""],value_feature:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.SparseFeatureIndexFeature":{name:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.SparseFeatureValueFeature":{name:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.StringDomain":{name:[3,3,1,""],value:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.StructDomain":{feature:[3,3,1,""],sparse_feature:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentation":{dense_tensor:[3,3,1,""],ragged_tensor:[3,3,1,""],sparse_tensor:[3,3,1,""],varlen_sparse_tensor:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationDefaultValue":{bytes_value:[3,3,1,""],float_value:[3,3,1,""],int_value:[3,3,1,""],uint_value:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationDenseTensor":{column_name:[3,3,1,""],default_value:[3,3,1,""],shape:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationGroup":{tensor_representation:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensor":{feature_path:[3,3,1,""],partition:[3,3,1,""],row_partition_dtype:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationRaggedTensorPartition":{row_length:[3,3,1,""],uniform_row_length:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationRowPartitionDType":{INT32:[3,3,1,""],INT64:[3,3,1,""],UNSPECIFIED:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationSparseTensor":{dense_shape:[3,3,1,""],index_column_names:[3,3,1,""],value_column_name:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TensorRepresentationVarLenSparseTensor":{column_name:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TimeDomain":{integer_format:[3,3,1,""],string_format:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TimeDomainIntegerTimeFormat":{FORMAT_UNKNOWN:[3,3,1,""],UNIX_DAYS:[3,3,1,""],UNIX_MICROSECONDS:[3,3,1,""],UNIX_MILLISECONDS:[3,3,1,""],UNIX_NANOSECONDS:[3,3,1,""],UNIX_SECONDS:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TimeOfDayDomain":{integer_format:[3,3,1,""],string_format:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.TimeOfDayDomainIntegerTimeOfDayFormat":{FORMAT_UNKNOWN:[3,3,1,""],PACKED_64_NANOS:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.UniqueConstraints":{max:[3,3,1,""],min:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.ValueCount":{max:[3,3,1,""],min:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.ValueCountList":{value_count:[3,3,1,""]},"merlin_standard_lib.proto.schema_bp.WeightedFeature":{feature:[3,3,1,""],lifecycle_stage:[3,3,1,""],name:[3,3,1,""],weight_feature:[3,3,1,""]},"merlin_standard_lib.schema":{schema:[4,0,0,"-"],tag:[4,0,0,"-"]},"merlin_standard_lib.schema.schema":{ColumnSchema:[4,1,1,""],Schema:[4,1,1,""],categorical_cardinalities:[4,4,1,""]},"merlin_standard_lib.schema.schema.ColumnSchema":{copy:[4,2,1,""],create_categorical:[4,2,1,""],create_continuous:[4,2,1,""],properties:[4,2,1,""],tags:[4,2,1,""],to_proto_text:[4,2,1,""],with_name:[4,2,1,""],with_properties:[4,2,1,""],with_tags:[4,2,1,""],with_tags_based_on_properties:[4,2,1,""]},"merlin_standard_lib.schema.schema.Schema":{add:[4,2,1,""],apply:[4,2,1,""],apply_inverse:[4,2,1,""],column_names:[4,2,1,""],column_schemas:[4,2,1,""],copy:[4,2,1,""],create:[4,2,1,""],feature:[4,3,1,""],filter_column_schemas:[4,2,1,""],filter_columns_from_dict:[4,2,1,""],from_json:[4,2,1,""],from_proto_text:[4,2,1,""],item_id_column_name:[4,2,1,""],map_column_schemas:[4,2,1,""],remove_by_name:[4,2,1,""],remove_by_tag:[4,2,1,""],remove_by_type:[4,2,1,""],select_by_name:[4,2,1,""],select_by_tag:[4,2,1,""],select_by_type:[4,2,1,""],to_proto_text:[4,2,1,""],with_tags_based_on_properties:[4,2,1,""]},"merlin_standard_lib.schema.tag":{Tag:[4,3,1,""]},"merlin_standard_lib.utils":{embedding_utils:[5,0,0,"-"]},"merlin_standard_lib.utils.embedding_utils":{get_embedding_size_from_cardinality:[5,4,1,""],get_embedding_sizes_from_schema:[5,4,1,""]},"transformers4rec.config":{schema:[8,0,0,"-"],trainer:[8,0,0,"-"],transformer:[8,0,0,"-"]},"transformers4rec.config.schema":{SchemaMixin:[8,1,1,""],requires_schema:[8,4,1,""]},"transformers4rec.config.schema.SchemaMixin":{REQUIRES_SCHEMA:[8,3,1,""],check_schema:[8,2,1,""],get_item_ids_from_inputs:[8,2,1,""],get_padding_mask_from_item_id:[8,2,1,""],schema:[8,2,1,""],set_schema:[8,2,1,""]},"transformers4rec.config.trainer":{T4RecTrainingArguments:[8,1,1,""],T4RecTrainingArgumentsTF:[8,1,1,""]},"transformers4rec.config.trainer.T4RecTrainingArguments":{compute_metrics_each_n_steps:[8,3,1,""],data_loader_engine:[8,3,1,""],eval_on_test_set:[8,3,1,""],eval_steps_on_train_set:[8,3,1,""],experiments_group:[8,3,1,""],learning_rate_num_cosine_cycles_by_epoch:[8,3,1,""],log_predictions:[8,3,1,""],max_sequence_length:[8,3,1,""],place_model_on_device:[8,2,1,""],predict_top_k:[8,3,1,""],shuffle_buffer_size:[8,3,1,""]},"transformers4rec.config.trainer.T4RecTrainingArgumentsTF":{output_dir:[8,3,1,""]},"transformers4rec.config.transformer":{AlbertConfig:[8,1,1,""],BertConfig:[8,1,1,""],ElectraConfig:[8,1,1,""],GPT2Config:[8,1,1,""],LongformerConfig:[8,1,1,""],ReformerConfig:[8,1,1,""],RobertaConfig:[8,1,1,""],T4RecConfig:[8,1,1,""],TransfoXLConfig:[8,1,1,""],XLNetConfig:[8,1,1,""]},"transformers4rec.config.transformer.AlbertConfig":{build:[8,2,1,""]},"transformers4rec.config.transformer.BertConfig":{build:[8,2,1,""]},"transformers4rec.config.transformer.ElectraConfig":{build:[8,2,1,""]},"transformers4rec.config.transformer.GPT2Config":{build:[8,2,1,""]},"transformers4rec.config.transformer.LongformerConfig":{build:[8,2,1,""]},"transformers4rec.config.transformer.ReformerConfig":{build:[8,2,1,""]},"transformers4rec.config.transformer.RobertaConfig":{build:[8,2,1,""]},"transformers4rec.config.transformer.T4RecConfig":{build:[8,2,1,""],to_huggingface_torch_model:[8,2,1,""],to_torch_model:[8,2,1,""],transformers_config_cls:[8,2,1,""]},"transformers4rec.config.transformer.TransfoXLConfig":{build:[8,2,1,""]},"transformers4rec.config.transformer.XLNetConfig":{build:[8,2,1,""]},"transformers4rec.torch":{AlbertConfig:[9,1,1,""],AsTabular:[9,1,1,""],BinaryClassificationTask:[9,1,1,""],Block:[9,1,1,""],BlockBase:[9,1,1,""],ConcatFeatures:[9,1,1,""],ContinuousFeatures:[9,1,1,""],ElectraConfig:[9,1,1,""],ElementwiseSum:[9,1,1,""],ElementwiseSumItemMulti:[9,1,1,""],EmbeddingFeatures:[9,1,1,""],FeatureConfig:[9,1,1,""],FilterFeatures:[9,1,1,""],GPT2Config:[9,1,1,""],Head:[9,1,1,""],LabelSmoothCrossEntropyLoss:[9,4,1,""],LongformerConfig:[9,1,1,""],MLPBlock:[9,1,1,""],MergeTabular:[9,1,1,""],Model:[9,1,1,""],NextItemPredictionTask:[9,1,1,""],PredictionTask:[9,1,1,""],PretrainedEmbeddingFeatures:[9,1,1,""],PretrainedEmbeddingsInitializer:[9,1,1,""],ReformerConfig:[9,1,1,""],RegressionTask:[9,1,1,""],Schema:[9,1,1,""],SequenceEmbeddingFeatures:[9,1,1,""],SequentialBlock:[9,1,1,""],SequentialTabularTransformations:[9,1,1,""],SoftEmbedding:[9,1,1,""],SoftEmbeddingFeatures:[9,1,1,""],StackFeatures:[9,1,1,""],StochasticSwapNoise:[9,1,1,""],T4RecConfig:[9,1,1,""],T4RecTrainingArguments:[9,1,1,""],TableConfig:[9,1,1,""],TabularAggregation:[9,1,1,""],TabularBlock:[9,1,1,""],TabularDropout:[9,1,1,""],TabularFeatures:[9,1,1,""],TabularLayerNorm:[9,1,1,""],TabularModule:[9,1,1,""],TabularSequenceFeatures:[9,1,1,""],TabularTransformation:[9,1,1,""],Trainer:[9,1,1,""],TransfoXLConfig:[9,1,1,""],TransformerBlock:[9,1,1,""],XLNetConfig:[9,1,1,""],block:[10,0,0,"-"],build_blocks:[9,4,1,""],features:[11,0,0,"-"],masking:[9,0,0,"-"],model:[12,0,0,"-"],ranking_metric:[9,0,0,"-"],requires_schema:[9,4,1,""],right_shift_block:[9,4,1,""],tabular:[13,0,0,"-"],trainer:[9,0,0,"-"],typing:[9,0,0,"-"],utils:[14,0,0,"-"]},"transformers4rec.torch.AlbertConfig":{build:[9,2,1,""]},"transformers4rec.torch.AsTabular":{forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.BinaryClassificationTask":{DEFAULT_LOSS:[9,3,1,""],DEFAULT_METRICS:[9,3,1,""],training:[9,3,1,""]},"transformers4rec.torch.Block":{forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.BlockBase":{as_tabular:[9,2,1,""],to_model:[9,2,1,""]},"transformers4rec.torch.ConcatFeatures":{forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.ContinuousFeatures":{forward:[9,2,1,""],forward_output_size:[9,2,1,""],from_features:[9,2,1,""]},"transformers4rec.torch.ElectraConfig":{build:[9,2,1,""]},"transformers4rec.torch.ElementwiseSum":{forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.ElementwiseSumItemMulti":{REQUIRES_SCHEMA:[9,3,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.EmbeddingFeatures":{forward:[9,2,1,""],forward_output_size:[9,2,1,""],from_schema:[9,2,1,""],item_embedding_table:[9,2,1,""],item_ids:[9,2,1,""],table_to_embedding_module:[9,2,1,""]},"transformers4rec.torch.FeatureConfig":{max_sequence_length:[9,3,1,""],name:[9,3,1,""],table:[9,3,1,""]},"transformers4rec.torch.FilterFeatures":{forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.GPT2Config":{build:[9,2,1,""]},"transformers4rec.torch.Head":{build:[9,2,1,""],calculate_metrics:[9,2,1,""],compute_metrics:[9,2,1,""],forward:[9,2,1,""],from_schema:[9,2,1,""],pop_labels:[9,2,1,""],reset_metrics:[9,2,1,""],task_blocks:[9,2,1,""],to_model:[9,2,1,""],training:[9,3,1,""]},"transformers4rec.torch.LongformerConfig":{build:[9,2,1,""]},"transformers4rec.torch.MLPBlock":{build:[9,2,1,""]},"transformers4rec.torch.MergeTabular":{build:[9,2,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""],merge_values:[9,2,1,""]},"transformers4rec.torch.Model":{calculate_metrics:[9,2,1,""],compute_metrics:[9,2,1,""],evaluate:[9,2,1,""],fit:[9,2,1,""],forward:[9,2,1,""],input_schema:[9,2,1,""],load:[9,2,1,""],output_schema:[9,2,1,""],prediction_tasks:[9,2,1,""],reset_metrics:[9,2,1,""],save:[9,2,1,""],to_lightning:[9,2,1,""],training:[9,3,1,""]},"transformers4rec.torch.NextItemPredictionTask":{DEFAULT_METRICS:[9,3,1,""],build:[9,2,1,""],calculate_metrics:[9,2,1,""],compute_metrics:[9,2,1,""],forward:[9,2,1,""],remove_pad_3d:[9,2,1,""],training:[9,3,1,""]},"transformers4rec.torch.PredictionTask":{build:[9,2,1,""],calculate_metrics:[9,2,1,""],child_name:[9,2,1,""],compute_metrics:[9,2,1,""],forward:[9,2,1,""],metric_name:[9,2,1,""],reset_metrics:[9,2,1,""],set_metrics:[9,2,1,""],task_name:[9,2,1,""],to_head:[9,2,1,""],to_model:[9,2,1,""],training:[9,3,1,""]},"transformers4rec.torch.PretrainedEmbeddingFeatures":{build:[9,2,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""],from_schema:[9,2,1,""],parse_combiner:[9,2,1,""]},"transformers4rec.torch.PretrainedEmbeddingsInitializer":{forward:[9,2,1,""],training:[9,3,1,""]},"transformers4rec.torch.ReformerConfig":{build:[9,2,1,""]},"transformers4rec.torch.RegressionTask":{DEFAULT_LOSS:[9,3,1,""],DEFAULT_METRICS:[9,3,1,""],training:[9,3,1,""]},"transformers4rec.torch.Schema":{add:[9,2,1,""],apply:[9,2,1,""],apply_inverse:[9,2,1,""],column_names:[9,2,1,""],column_schemas:[9,2,1,""],copy:[9,2,1,""],create:[9,2,1,""],feature:[9,3,1,""],filter_column_schemas:[9,2,1,""],filter_columns_from_dict:[9,2,1,""],from_json:[9,2,1,""],from_proto_text:[9,2,1,""],item_id_column_name:[9,2,1,""],map_column_schemas:[9,2,1,""],remove_by_name:[9,2,1,""],remove_by_tag:[9,2,1,""],remove_by_type:[9,2,1,""],select_by_name:[9,2,1,""],select_by_tag:[9,2,1,""],select_by_type:[9,2,1,""],to_proto_text:[9,2,1,""],with_tags_based_on_properties:[9,2,1,""]},"transformers4rec.torch.SequenceEmbeddingFeatures":{forward_output_size:[9,2,1,""],table_to_embedding_module:[9,2,1,""]},"transformers4rec.torch.SequentialBlock":{add_module:[9,2,1,""],add_module_and_maybe_build:[9,2,1,""],as_tabular:[9,2,1,""],build:[9,2,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""],get_children_by_class_name:[9,2,1,""],inputs:[9,2,1,""]},"transformers4rec.torch.SequentialTabularTransformations":{append:[9,2,1,""]},"transformers4rec.torch.SoftEmbedding":{forward:[9,2,1,""],training:[9,3,1,""]},"transformers4rec.torch.SoftEmbeddingFeatures":{from_schema:[9,2,1,""],table_to_embedding_module:[9,2,1,""]},"transformers4rec.torch.StackFeatures":{forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.StochasticSwapNoise":{augment:[9,2,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.T4RecConfig":{build:[9,2,1,""],to_huggingface_torch_model:[9,2,1,""],to_torch_model:[9,2,1,""],transformers_config_cls:[9,2,1,""]},"transformers4rec.torch.T4RecTrainingArguments":{compute_metrics_each_n_steps:[9,3,1,""],data_loader_engine:[9,3,1,""],eval_on_test_set:[9,3,1,""],eval_steps_on_train_set:[9,3,1,""],experiments_group:[9,3,1,""],learning_rate_num_cosine_cycles_by_epoch:[9,3,1,""],log_predictions:[9,3,1,""],max_sequence_length:[9,3,1,""],output_dir:[9,3,1,""],place_model_on_device:[9,2,1,""],predict_top_k:[9,3,1,""],shuffle_buffer_size:[9,3,1,""]},"transformers4rec.torch.TableConfig":{combiner:[9,3,1,""],dim:[9,3,1,""],initializer:[9,3,1,""],name:[9,3,1,""],vocabulary_size:[9,3,1,""]},"transformers4rec.torch.TabularAggregation":{forward:[9,2,1,""],parse:[9,2,1,""]},"transformers4rec.torch.TabularBlock":{build:[9,2,1,""],output_size:[9,2,1,""],to_module:[9,2,1,""]},"transformers4rec.torch.TabularDropout":{forward:[9,2,1,""],forward_output_size:[9,2,1,""]},"transformers4rec.torch.TabularFeatures":{CONTINUOUS_MODULE_CLASS:[9,3,1,""],EMBEDDING_MODULE_CLASS:[9,3,1,""],PRETRAINED_EMBEDDING_MODULE_CLASS:[9,3,1,""],SOFT_EMBEDDING_MODULE_CLASS:[9,3,1,""],categorical_module:[9,2,1,""],continuous_module:[9,2,1,""],forward_output_size:[9,2,1,""],from_schema:[9,2,1,""],pretrained_module:[9,2,1,""],project_continuous_features:[9,2,1,""]},"transformers4rec.torch.TabularLayerNorm":{build:[9,2,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""],from_feature_config:[9,2,1,""]},"transformers4rec.torch.TabularModule":{aggregation:[9,2,1,""],forward:[9,2,1,""],from_features:[9,2,1,""],from_schema:[9,2,1,""],merge:[9,2,1,""],post:[9,2,1,""],post_forward:[9,2,1,""],pre:[9,2,1,""],pre_forward:[9,2,1,""],training:[9,3,1,""]},"transformers4rec.torch.TabularSequenceFeatures":{EMBEDDING_MODULE_CLASS:[9,3,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""],from_schema:[9,2,1,""],item_embedding_table:[9,2,1,""],item_id:[9,2,1,""],masking:[9,2,1,""],project_continuous_features:[9,2,1,""],set_masking:[9,2,1,""]},"transformers4rec.torch.TabularTransformation":{forward:[9,2,1,""],parse:[9,2,1,""]},"transformers4rec.torch.Trainer":{compute_loss:[9,2,1,""],create_scheduler:[9,2,1,""],evaluation_loop:[9,2,1,""],get_eval_dataloader:[9,2,1,""],get_scheduler:[9,2,1,""],get_test_dataloader:[9,2,1,""],get_train_dataloader:[9,2,1,""],load_model_trainer_states_from_checkpoint:[9,2,1,""],log:[9,2,1,""],log_predictions_callback:[9,2,1,""],num_examples:[9,2,1,""],prediction_step:[9,2,1,""],reset_lr_scheduler:[9,2,1,""]},"transformers4rec.torch.TransfoXLConfig":{build:[9,2,1,""]},"transformers4rec.torch.TransformerBlock":{TRANSFORMER_TO_PREPARE:[9,3,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""],from_registry:[9,2,1,""]},"transformers4rec.torch.XLNetConfig":{build:[9,2,1,""]},"transformers4rec.torch.block":{base:[10,0,0,"-"],mlp:[10,0,0,"-"],transformer:[10,0,0,"-"]},"transformers4rec.torch.block.base":{Block:[10,1,1,""],BlockBase:[10,1,1,""],BuildableBlock:[10,1,1,""],SequentialBlock:[10,1,1,""],build_blocks:[10,4,1,""],right_shift_block:[10,4,1,""]},"transformers4rec.torch.block.base.Block":{forward:[10,2,1,""],forward_output_size:[10,2,1,""]},"transformers4rec.torch.block.base.BlockBase":{as_tabular:[10,2,1,""],to_model:[10,2,1,""]},"transformers4rec.torch.block.base.BuildableBlock":{build:[10,2,1,""],to_module:[10,2,1,""]},"transformers4rec.torch.block.base.SequentialBlock":{add_module:[10,2,1,""],add_module_and_maybe_build:[10,2,1,""],as_tabular:[10,2,1,""],build:[10,2,1,""],forward:[10,2,1,""],forward_output_size:[10,2,1,""],get_children_by_class_name:[10,2,1,""],inputs:[10,2,1,""]},"transformers4rec.torch.block.mlp":{DenseBlock:[10,1,1,""],MLPBlock:[10,1,1,""]},"transformers4rec.torch.block.mlp.DenseBlock":{forward_output_size:[10,2,1,""]},"transformers4rec.torch.block.mlp.MLPBlock":{build:[10,2,1,""]},"transformers4rec.torch.block.transformer":{GPT2Prepare:[10,1,1,""],TransformerBlock:[10,1,1,""],TransformerPrepare:[10,1,1,""]},"transformers4rec.torch.block.transformer.GPT2Prepare":{forward:[10,2,1,""],training:[10,3,1,""]},"transformers4rec.torch.block.transformer.TransformerBlock":{TRANSFORMER_TO_PREPARE:[10,3,1,""],forward:[10,2,1,""],forward_output_size:[10,2,1,""],from_registry:[10,2,1,""],prepare_module:[10,3,1,""],training:[10,3,1,""],transformer:[10,3,1,""]},"transformers4rec.torch.block.transformer.TransformerPrepare":{forward:[10,2,1,""],training:[10,3,1,""]},"transformers4rec.torch.features":{base:[11,0,0,"-"],continuous:[11,0,0,"-"],embedding:[11,0,0,"-"],sequence:[11,0,0,"-"],tabular:[11,0,0,"-"],text:[11,0,0,"-"]},"transformers4rec.torch.features.base":{InputBlock:[11,1,1,""]},"transformers4rec.torch.features.continuous":{ContinuousFeatures:[11,1,1,""]},"transformers4rec.torch.features.continuous.ContinuousFeatures":{forward:[11,2,1,""],forward_output_size:[11,2,1,""],from_features:[11,2,1,""]},"transformers4rec.torch.features.embedding":{EmbeddingBagWrapper:[11,1,1,""],EmbeddingFeatures:[11,1,1,""],FeatureConfig:[11,1,1,""],PretrainedEmbeddingFeatures:[11,1,1,""],PretrainedEmbeddingsInitializer:[11,1,1,""],SoftEmbedding:[11,1,1,""],SoftEmbeddingFeatures:[11,1,1,""],TableConfig:[11,1,1,""]},"transformers4rec.torch.features.embedding.EmbeddingBagWrapper":{embedding_dim:[11,3,1,""],forward:[11,2,1,""],include_last_offset:[11,3,1,""],max_norm:[11,3,1,""],mode:[11,3,1,""],norm_type:[11,3,1,""],num_embeddings:[11,3,1,""],padding_idx:[11,3,1,""],scale_grad_by_freq:[11,3,1,""],sparse:[11,3,1,""],weight:[11,3,1,""]},"transformers4rec.torch.features.embedding.EmbeddingFeatures":{forward:[11,2,1,""],forward_output_size:[11,2,1,""],from_schema:[11,2,1,""],item_embedding_table:[11,2,1,""],item_ids:[11,2,1,""],table_to_embedding_module:[11,2,1,""]},"transformers4rec.torch.features.embedding.FeatureConfig":{max_sequence_length:[11,3,1,""],name:[11,3,1,""],table:[11,3,1,""]},"transformers4rec.torch.features.embedding.PretrainedEmbeddingFeatures":{build:[11,2,1,""],forward:[11,2,1,""],forward_output_size:[11,2,1,""],from_schema:[11,2,1,""],parse_combiner:[11,2,1,""]},"transformers4rec.torch.features.embedding.PretrainedEmbeddingsInitializer":{forward:[11,2,1,""],training:[11,3,1,""]},"transformers4rec.torch.features.embedding.SoftEmbedding":{forward:[11,2,1,""],training:[11,3,1,""]},"transformers4rec.torch.features.embedding.SoftEmbeddingFeatures":{from_schema:[11,2,1,""],table_to_embedding_module:[11,2,1,""]},"transformers4rec.torch.features.embedding.TableConfig":{combiner:[11,3,1,""],dim:[11,3,1,""],initializer:[11,3,1,""],name:[11,3,1,""],vocabulary_size:[11,3,1,""]},"transformers4rec.torch.features.sequence":{SequenceEmbeddingFeatures:[11,1,1,""],TabularSequenceFeatures:[11,1,1,""]},"transformers4rec.torch.features.sequence.SequenceEmbeddingFeatures":{forward_output_size:[11,2,1,""],table_to_embedding_module:[11,2,1,""]},"transformers4rec.torch.features.sequence.TabularSequenceFeatures":{EMBEDDING_MODULE_CLASS:[11,3,1,""],forward:[11,2,1,""],forward_output_size:[11,2,1,""],from_schema:[11,2,1,""],item_embedding_table:[11,2,1,""],item_id:[11,2,1,""],masking:[11,2,1,""],project_continuous_features:[11,2,1,""],set_masking:[11,2,1,""]},"transformers4rec.torch.features.tabular":{TabularFeatures:[11,1,1,""]},"transformers4rec.torch.features.tabular.TabularFeatures":{CONTINUOUS_MODULE_CLASS:[11,3,1,""],EMBEDDING_MODULE_CLASS:[11,3,1,""],PRETRAINED_EMBEDDING_MODULE_CLASS:[11,3,1,""],SOFT_EMBEDDING_MODULE_CLASS:[11,3,1,""],categorical_module:[11,2,1,""],continuous_module:[11,2,1,""],forward_output_size:[11,2,1,""],from_schema:[11,2,1,""],pretrained_module:[11,2,1,""],project_continuous_features:[11,2,1,""]},"transformers4rec.torch.masking":{CausalLanguageModeling:[9,1,1,""],MaskSequence:[9,1,1,""],MaskedLanguageModeling:[9,1,1,""],MaskingInfo:[9,1,1,""],PermutationLanguageModeling:[9,1,1,""],ReplacementLanguageModeling:[9,1,1,""]},"transformers4rec.torch.masking.CausalLanguageModeling":{apply_mask_to_inputs:[9,2,1,""]},"transformers4rec.torch.masking.MaskSequence":{apply_mask_to_inputs:[9,2,1,""],compute_masked_targets:[9,2,1,""],forward:[9,2,1,""],forward_output_size:[9,2,1,""],predict_all:[9,2,1,""],transformer_arguments:[9,2,1,""],transformer_optional_arguments:[9,2,1,""],transformer_required_arguments:[9,2,1,""]},"transformers4rec.torch.masking.MaskedLanguageModeling":{apply_mask_to_inputs:[9,2,1,""]},"transformers4rec.torch.masking.MaskingInfo":{schema:[9,3,1,""],targets:[9,3,1,""]},"transformers4rec.torch.masking.PermutationLanguageModeling":{compute_masked_targets:[9,2,1,""],transformer_required_arguments:[9,2,1,""]},"transformers4rec.torch.masking.ReplacementLanguageModeling":{get_fake_tokens:[9,2,1,""],sample_from_softmax:[9,2,1,""]},"transformers4rec.torch.model":{prediction_task:[12,0,0,"-"]},"transformers4rec.torch.model.prediction_task":{BinaryClassificationPrepareBlock:[12,1,1,""],BinaryClassificationTask:[12,1,1,""],LogUniformSampler:[12,1,1,""],NextItemPredictionPrepareBlock:[12,1,1,""],NextItemPredictionTask:[12,1,1,""],RegressionPrepareBlock:[12,1,1,""],RegressionTask:[12,1,1,""]},"transformers4rec.torch.model.prediction_task.BinaryClassificationPrepareBlock":{build:[12,2,1,""]},"transformers4rec.torch.model.prediction_task.BinaryClassificationTask":{DEFAULT_LOSS:[12,3,1,""],DEFAULT_METRICS:[12,3,1,""],training:[12,3,1,""]},"transformers4rec.torch.model.prediction_task.LogUniformSampler":{forward:[12,2,1,""],get_log_uniform_distr:[12,2,1,""],get_unique_sampling_distr:[12,2,1,""],sample:[12,2,1,""],training:[12,3,1,""]},"transformers4rec.torch.model.prediction_task.NextItemPredictionPrepareBlock":{build:[12,2,1,""]},"transformers4rec.torch.model.prediction_task.NextItemPredictionTask":{DEFAULT_METRICS:[12,3,1,""],build:[12,2,1,""],calculate_metrics:[12,2,1,""],compute_metrics:[12,2,1,""],forward:[12,2,1,""],remove_pad_3d:[12,2,1,""],training:[12,3,1,""]},"transformers4rec.torch.model.prediction_task.RegressionPrepareBlock":{build:[12,2,1,""]},"transformers4rec.torch.model.prediction_task.RegressionTask":{DEFAULT_LOSS:[12,3,1,""],DEFAULT_METRICS:[12,3,1,""],training:[12,3,1,""]},"transformers4rec.torch.ranking_metric":{AvgPrecisionAt:[9,1,1,""],DCGAt:[9,1,1,""],MeanReciprocalRankAt:[9,1,1,""],NDCGAt:[9,1,1,""],PrecisionAt:[9,1,1,""],RankingMetric:[9,1,1,""],RecallAt:[9,1,1,""]},"transformers4rec.torch.ranking_metric.RankingMetric":{compute:[9,2,1,""],update:[9,2,1,""]},"transformers4rec.torch.tabular":{aggregation:[13,0,0,"-"],transformations:[13,0,0,"-"]},"transformers4rec.torch.tabular.aggregation":{ConcatFeatures:[13,1,1,""],ElementwiseFeatureAggregation:[13,1,1,""],ElementwiseSum:[13,1,1,""],ElementwiseSumItemMulti:[13,1,1,""],StackFeatures:[13,1,1,""]},"transformers4rec.torch.tabular.aggregation.ConcatFeatures":{forward:[13,2,1,""],forward_output_size:[13,2,1,""]},"transformers4rec.torch.tabular.aggregation.ElementwiseSum":{forward:[13,2,1,""],forward_output_size:[13,2,1,""]},"transformers4rec.torch.tabular.aggregation.ElementwiseSumItemMulti":{REQUIRES_SCHEMA:[13,3,1,""],forward:[13,2,1,""],forward_output_size:[13,2,1,""],training:[13,3,1,""]},"transformers4rec.torch.tabular.aggregation.StackFeatures":{forward:[13,2,1,""],forward_output_size:[13,2,1,""]},"transformers4rec.torch.tabular.transformations":{StochasticSwapNoise:[13,1,1,""],TabularDropout:[13,1,1,""],TabularLayerNorm:[13,1,1,""]},"transformers4rec.torch.tabular.transformations.StochasticSwapNoise":{augment:[13,2,1,""],forward:[13,2,1,""],forward_output_size:[13,2,1,""]},"transformers4rec.torch.tabular.transformations.TabularDropout":{forward:[13,2,1,""],forward_output_size:[13,2,1,""]},"transformers4rec.torch.tabular.transformations.TabularLayerNorm":{build:[13,2,1,""],forward:[13,2,1,""],forward_output_size:[13,2,1,""],from_feature_config:[13,2,1,""]},"transformers4rec.torch.trainer":{DatasetMock:[9,1,1,""],IncrementalLoggingCallback:[9,1,1,""],Trainer:[9,1,1,""],process_metrics:[9,4,1,""]},"transformers4rec.torch.trainer.IncrementalLoggingCallback":{on_epoch_end:[9,2,1,""],on_train_begin:[9,2,1,""],on_train_end:[9,2,1,""]},"transformers4rec.torch.trainer.Trainer":{compute_loss:[9,2,1,""],create_scheduler:[9,2,1,""],evaluation_loop:[9,2,1,""],get_eval_dataloader:[9,2,1,""],get_scheduler:[9,2,1,""],get_test_dataloader:[9,2,1,""],get_train_dataloader:[9,2,1,""],load_model_trainer_states_from_checkpoint:[9,2,1,""],log:[9,2,1,""],log_predictions_callback:[9,2,1,""],num_examples:[9,2,1,""],prediction_step:[9,2,1,""],reset_lr_scheduler:[9,2,1,""]},"transformers4rec.torch.utils":{data_utils:[14,0,0,"-"],examples_utils:[14,0,0,"-"],schema_utils:[14,0,0,"-"],torch_utils:[14,0,0,"-"]},"transformers4rec.torch.utils.data_utils":{DLDataLoader:[14,1,1,""],MerlinDataLoader:[14,1,1,""],ParquetDataset:[14,1,1,""],PyarrowDataLoader:[14,1,1,""],ShuffleDataset:[14,1,1,""],T4RecDataLoader:[14,1,1,""],to_core_schema:[14,4,1,""]},"transformers4rec.torch.utils.data_utils.DLDataLoader":{batch_size:[14,3,1,""],dataset:[14,3,1,""],device:[14,2,1,""],drop_last:[14,3,1,""],num_workers:[14,3,1,""],pin_memory:[14,3,1,""],pin_memory_device:[14,3,1,""],prefetch_factor:[14,3,1,""],sampler:[14,3,1,""],timeout:[14,3,1,""]},"transformers4rec.torch.utils.data_utils.MerlinDataLoader":{batch_size:[14,3,1,""],dataset:[14,3,1,""],drop_last:[14,3,1,""],from_schema:[14,2,1,""],num_workers:[14,3,1,""],output_schema:[14,2,1,""],pin_memory:[14,3,1,""],pin_memory_device:[14,3,1,""],prefetch_factor:[14,3,1,""],sampler:[14,3,1,""],set_dataset:[14,2,1,""],timeout:[14,3,1,""]},"transformers4rec.torch.utils.data_utils.ParquetDataset":{pad_seq_column_if_needed:[14,2,1,""]},"transformers4rec.torch.utils.data_utils.PyarrowDataLoader":{batch_size:[14,3,1,""],dataset:[14,3,1,""],drop_last:[14,3,1,""],from_schema:[14,2,1,""],num_workers:[14,3,1,""],pin_memory:[14,3,1,""],pin_memory_device:[14,3,1,""],prefetch_factor:[14,3,1,""],sampler:[14,3,1,""],set_dataset:[14,2,1,""],timeout:[14,3,1,""]},"transformers4rec.torch.utils.data_utils.T4RecDataLoader":{from_schema:[14,2,1,""],parse:[14,2,1,""],set_dataset:[14,2,1,""]},"transformers4rec.torch.utils.examples_utils":{fit_and_evaluate:[14,4,1,""],list_files:[14,4,1,""],visualize_response:[14,4,1,""],wipe_memory:[14,4,1,""]},"transformers4rec.torch.utils.schema_utils":{random_data_from_schema:[14,4,1,""]},"transformers4rec.torch.utils.torch_utils":{LambdaModule:[14,1,1,""],LossMixin:[14,1,1,""],MappingTransformerMasking:[14,1,1,""],MetricsMixin:[14,1,1,""],OutputSizeMixin:[14,1,1,""],atleast_1d:[14,4,1,""],calculate_batch_size_from_input_size:[14,4,1,""],check_gpu:[14,4,1,""],check_inputs:[14,4,1,""],create_output_placeholder:[14,4,1,""],extract_topk:[14,4,1,""],get_output_sizes_from_schema:[14,4,1,""],nested_concat:[14,4,1,""],nested_detach:[14,4,1,""],nested_numpify:[14,4,1,""],nested_truncate:[14,4,1,""],numpy_pad_and_concatenate:[14,4,1,""],one_hot_1d:[14,4,1,""],requires_schema:[14,4,1,""],torch_pad_and_concatenate:[14,4,1,""],tranform_label_to_onehot:[14,4,1,""]},"transformers4rec.torch.utils.torch_utils.LambdaModule":{forward:[14,2,1,""],training:[14,3,1,""]},"transformers4rec.torch.utils.torch_utils.LossMixin":{compute_loss:[14,2,1,""]},"transformers4rec.torch.utils.torch_utils.MappingTransformerMasking":{BertConfig:[14,3,1,""],CausalLanguageModeling:[14,1,1,""],ConvBertConfig:[14,3,1,""],DEFAULT_MASKING:[14,3,1,""],DebertaConfig:[14,3,1,""],DistilBertConfig:[14,3,1,""],GPT2Config:[14,3,1,""],LongformerConfig:[14,3,1,""],MPNetConfig:[14,3,1,""],MaskedLanguageModeling:[14,1,1,""],MegatronBertConfig:[14,3,1,""],PermutationLanguageModeling:[14,1,1,""],ReplacementLanguageModeling:[14,1,1,""],RoFormerConfig:[14,3,1,""],RobertaConfig:[14,3,1,""],TransfoXLConfig:[14,3,1,""],XLNetConfig:[14,3,1,""]},"transformers4rec.torch.utils.torch_utils.MappingTransformerMasking.CausalLanguageModeling":{apply_mask_to_inputs:[14,2,1,""]},"transformers4rec.torch.utils.torch_utils.MappingTransformerMasking.MaskedLanguageModeling":{apply_mask_to_inputs:[14,2,1,""]},"transformers4rec.torch.utils.torch_utils.MappingTransformerMasking.PermutationLanguageModeling":{compute_masked_targets:[14,2,1,""],transformer_required_arguments:[14,2,1,""]},"transformers4rec.torch.utils.torch_utils.MappingTransformerMasking.ReplacementLanguageModeling":{get_fake_tokens:[14,2,1,""],sample_from_softmax:[14,2,1,""]},"transformers4rec.torch.utils.torch_utils.MetricsMixin":{calculate_metrics:[14,2,1,""],compute_metrics:[14,2,1,""],reset_metrics:[14,2,1,""]},"transformers4rec.torch.utils.torch_utils.OutputSizeMixin":{build:[14,2,1,""],forward_output_size:[14,2,1,""],output_size:[14,2,1,""]},"transformers4rec.utils":{dependencies:[15,0,0,"-"]},"transformers4rec.utils.dependencies":{is_gpu_dataloader_available:[15,4,1,""],is_merlin_dataloader_available:[15,4,1,""],is_pyarrow_available:[15,4,1,""]},merlin_standard_lib:{ColumnSchema:[2,1,1,""],Schema:[2,1,1,""],categorical_cardinalities:[2,4,1,""],proto:[3,0,0,"-"],schema:[4,0,0,"-"],utils:[5,0,0,"-"]},transformers4rec:{config:[8,0,0,"-"],torch:[9,0,0,"-"],types:[7,0,0,"-"],utils:[15,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:function"},terms:{"000001":[16,27],"00013636363636363637":18,"0001875":18,"00020171456712823088":25,"0002435897435897436":18,"0003181818181818182":18,"0005":[17,18,21],"00065":[9,11],"000666":28,"00071":28,"012172":22,"015":25,"01t00":26,"022300":28,"023":28,"026600":28,"02_etl":28,"031":18,"033715":20,"034774363":20,"037000":28,"040753647685050964":28,"044022594":20,"046":18,"04696693":20,"049305129796266556":28,"04966254":20,"0499662":27,"050047338008880615":28,"05164230614900589":28,"0537":28,"059328265488147736":17,"059581220149993896":28,"05it":18,"06027791276574135":28,"06090249866247177":28,"0614391528069973":18,"06352042406797409":17,"0637783482670784":28,"064":18,"065300":28,"0654601976275444":18,"067":18,"06745054572820663":28,"067800":28,"07178892195224762":28,"07277625054121017":17,"07291":28,"07407757639884949":28,"07417604":20,"0742299035191536":28,"07608938217163086":28,"077287457883358":17,"07864832133054733":18,"07898139":20,"07925495505332947":28,"07t23":26,"080060":22,"08173713833093643":28,"08250617980957":28,"08251794427633286":18,"08309739083051682":28,"08318208903074265":17,"084":21,"08417622":20,"08422157913446426":18,"08648926516373952":17,"08783709744899952":27,"08it":18,"0900966":27,"09017771482467651":28,"09094516187906265":28,"091":28,"09143278002738953":28,"09193805356820424":17,"09403495":20,"09584496170282364":28,"09845318645238876":17,"09865029156208038":18,"09867884963750839":28,"0_transformworkflowtriton":[17,22],"0e80":26,"0x7f338f170520":3,"100":[8,9,12,14,16,17,18,20,21,22,25,27,31],"1000":[8,9,22,28],"10000":[16,32],"100000":20,"1004237":26,"1008271649479866":17,"100889682769775":21,"10099391639232635":28,"100mb":33,"1016":28,"102":17,"1021":17,"1024":[8,9],"10256":17,"1026":17,"1038":17,"104":[17,20],"10458":17,"105":22,"10605569183826447":18,"106240":28,"107":17,"1071":17,"10715":17,"1078":28,"108":22,"1080":17,"1081":26,"10856":17,"1086":17,"10866450518369675":28,"108700":28,"109":28,"10998427122831345":28,"110499":28,"110509":28,"111":17,"11108":17,"112128":28,"1122981607913971":28,"11255549":16,"11255552":16,"11255553":16,"11262":27,"1127":17,"113":17,"11349068582057953":28,"1146":28,"1154":17,"1158":17,"116":22,"116508":20,"1170":17,"1171323408683141":17,"11763089150190353":17,"11840":17,"119":22,"11min":28,"120":[26,27],"1200":28,"12029790133237839":18,"12176345592788261":27,"122":17,"1220":17,"12289":[16,17],"123":[17,22],"1231":17,"124":17,"1240335":22,"12431":17,"1244512":22,"1244578":22,"1245":28,"1245873":22,"1250875":22,"1251097":22,"1252":17,"1258":21,"12599845230579376":28,"127":24,"12736327946186066":17,"128":[8,9,17,18,21,28,31],"1281":21,"1284":17,"1286":21,"129":18,"12972043454647064":18,"12it":20,"12min":28,"130":17,"1307067":26,"131":22,"1311":21,"1314":28,"1324":21,"1325":17,"1327":21,"13290":27,"133":17,"1335":17,"1337":17,"134":22,"13405":17,"1342":21,"13500627875328064":17,"1355":17,"136":17,"137":[20,22],"13763061662515005":17,"1379":17,"1380162239074707":18,"1381":21,"13875272870063782":28,"1390":21,"1394":17,"1396321828":16,"1396329089":16,"1396330458":16,"1396439960":16,"1396735848":16,"1396780751":16,"1396780780":16,"1396780912":16,"1396780991":16,"13c02c18c713":26,"1406":28,"14068539440631866":28,"14080":18,"141":17,"1414":17,"142122960":3,"143":[17,21],"14394062757492065":28,"1441":28,"1447":21,"1449":21,"145":26,"1450":28,"1464":21,"1468765288591385":28,"147":22,"147716":22,"1489":17,"149":[21,27,28],"1492":21,"1498":21,"151":22,"1526":21,"153":[20,22],"1536":21,"1542772501707077":28,"154300":28,"1552":17,"1569888000":26,"1569888001":26,"1569888004":26,"1569897265":26,"1569901056":26,"1569925682":26,"1569927253":[26,27],"1569934097":[26,27],"1569941460":26,"1569948287":[26,27],"157":26,"1570":17,"1570026747":[26,27],"1570097085":[26,27],"1570460611":[26,27],"1570460616":[26,27],"1570460621":[26,27],"1570460636":[26,27],"1570460650":[26,27],"1571160119":26,"1571160137":26,"1571160149":26,"1571160178":26,"1571160199":26,"15722796320915222":28,"15793765":20,"158":17,"1584":[16,17],"159":28,"1591796875":18,"15954":17,"1600":[8,9],"1603423":27,"1604":17,"16083915531635284":17,"161":17,"162":22,"163":27,"1637":17,"1637332":26,"16384":[8,9],"164":17,"16534":17,"165782928466797":28,"166":[18,22],"1664":21,"1665":17,"16666666666666666":[9,14],"167":17,"16726472973823547":28,"1673":17,"16738776862621307":17,"1680772304534912":21,"16896":17,"169":22,"1698":17,"16min":28,"17062446475028992":18,"1708":[9,11],"171":[20,22],"172":[17,20,21,22],"17200506":26,"17271":17,"173":[20,21,22],"173500":28,"1739":17,"174":18,"1757":17,"176":17,"1763":17,"17630764842033386":28,"177":22,"1771":17,"178":[16,17,18],"179":[17,18],"17900079488754272":21,"1792":21,"1798":17,"1799":17,"17it":18,"17min":28,"180":[16,17,18],"1800":[8,9],"18093":17,"181":[17,18],"1818986":27,"182":17,"18262046575546265":28,"183":17,"1831":17,"1833297461271286":21,"18394140899181366":21,"184":17,"185":[16,17],"18532":27,"186":17,"18669554591178894":21,"188":[17,22],"1885847":20,"1890":17,"18959537148475647":17,"18it":18,"1901":28,"191":17,"19157":17,"192":[8,9,22,28],"193":[17,22],"1942":17,"1946":17,"1951":17,"1954":16,"19570313394069672":18,"196":17,"1960":18,"1966":17,"19680777192115784":17,"197200":28,"19757":27,"1976":18,"19850534200668335":21,"1988":17,"199":17,"1_predictpytorchtriton":[17,22],"1min":28,"200":[17,18,22,28],"20000":[8,9],"200000":[8,9],"20034":17,"200949028134346":18,"201":17,"2014":[16,28],"2017":[17,28],"2018":28,"2019":[26,27,28,29],"2020":[26,27,29],"2021":[1,24,28,34,36],"2022":[16,17,18,20,21,22,26,27,28],"2028217315673828":28,"203":17,"2035":17,"20387786626815796":28,"20401":27,"20426493883132935":21,"204485893249512":28,"20480":17,"2051":17,"2053013552326770905":26,"2053013555631882655":26,"2053013558920217191":26,"2053013559792632471":26,"2053013563173241677":[26,27],"2053013563693335403":26,"20536":27,"2066":17,"207":17,"209":17,"2092":17,"2099":17,"2103807459595387724":26,"211":17,"2122":27,"21330341696739197":21,"214":17,"214613743":16,"214821388":16,"214826816":16,"214827011":16,"21483253935972849":17,"215":17,"2158203125":18,"21589":17,"2187":17,"2190":17,"219127":20,"21it":18,"21s":28,"2203593":22,"2209790199995041":17,"221":22,"2210817":22,"2211075":22,"221267":22,"2218847":22,"222065":22,"222200":28,"2223":17,"224":17,"225":17,"22519969940185547":28,"226":17,"2271":17,"228":17,"22817844152450562":21,"22911214248041414":27,"2293076366186142":21,"230":17,"231":[21,27],"2313":17,"23209574328358615":27,"233":28,"233000":28,"23366":17,"234":17,"2347":17,"237":17,"23708352":27,"23786288499832153":21,"238":28,"238700":28,"239":27,"2390844076871872":21,"2394":17,"24015874":27,"243":17,"24358350038528442":21,"245":[17,28],"2450":17,"246":22,"246331":20,"2474":17,"2486129105091095":21,"249":27,"2498":17,"249935582280159":28,"24it":16,"251":26,"2523437440395355":18,"2549":17,"255":28,"25549131631851196":17,"2557":17,"256":[8,9,17,18,22,25,28,35],"258":17,"259":18,"2591":17,"259200":28,"2612":18,"262":25,"26202560":26,"26203727":26,"26203994":26,"26204036":26,"2627":17,"263":25,"265":17,"2652":17,"2665":17,"267":17,"267735":[8,9],"2690":17,"2694":17,"2703":17,"2705":17,"2714":17,"272":28,"274":17,"2741":17,"27463168":20,"2748":26,"275":17,"27530":27,"2754":17,"276":17,"2773":17,"278":17,"2788":17,"2789":17,"2790":17,"27th":28,"281":17,"2816":17,"282900":28,"28672":17,"2889":17,"289":17,"28911334":20,"28916782":20,"28971543":16,"28it":18,"28th":28,"290":17,"2922437":20,"293":28,"29406309127807617":17,"295":[17,28],"2961147278547287":17,"297":28,"2970":17,"298":17,"29s":28,"2efa5b50b909":24,"2min":28,"300":17,"30000":[8,9],"303":17,"3045":17,"304600":17,"3046875":18,"305":17,"30522":[8,9],"306":[20,21,22],"307":17,"3071":18,"3072":[8,9],"30733301":26,"308":28,"3080":18,"309200":28,"3102":28,"312":17,"313":17,"3136":17,"314":[17,28],"316":27,"317":[17,26],"318":17,"319":18,"320":[8,9,17,18],"32000":[8,9],"320416450500488":18,"3206":18,"3209":18,"324":17,"325100":28,"327":17,"327276":22,"3275":17,"329182":20,"32gb":32,"330":17,"33003944":16,"331":[17,28],"3325":18,"3334":18,"334":[16,20,22],"335":[16,17,20,22],"336":[16,17],"33613566":20,"337":[16,17],"339":[17,18],"33985":27,"341":17,"342":28,"34265":17,"3427":27,"343":17,"3447":[16,17],"345":17,"3461":17,"3463":17,"348":17,"3481":17,"3485":17,"349":28,"349400":28,"34956282":20,"351":17,"354":17,"3553":17,"356":17,"3589":17,"360":17,"362":17,"364":17,"3652":17,"367":17,"3676":17,"367600":28,"3705":17,"374":17,"376":17,"3770":17,"378":21,"381":17,"3810":17,"3819":17,"3821016":20,"383":17,"3832":17,"3842":17,"3856":17,"386":17,"388":[17,26],"389":17,"390":[18,28],"3900821":26,"392100":28,"395500":28,"396":17,"39734098":20,"3989":[17,18],"3992":18,"3min":28,"400":[17,28],"40000":[8,9],"4002":17,"401":17,"40120387":20,"4019":17,"4029":17,"407809734344482":21,"4093":27,"4096":[8,9],"4101":17,"41187384724617004":17,"414":17,"415":28,"416":17,"416052":20,"4202155":26,"4210":17,"4211":17,"42210865020752":28,"422b":26,"423":17,"424":17,"4243":17,"42448762":26,"42448764":26,"4248046875":18,"4285":17,"42s":28,"430":17,"431":17,"4317":17,"4337":17,"4338829":27,"43388295":27,"43388454782514785":16,"43540764":20,"4363":18,"4376":17,"442317962646484":21,"442700":28,"442900":28,"443":17,"446":17,"44600062":26,"4464":17,"4471":17,"448":25,"454":17,"4590":26,"4663":17,"468400":28,"46875":21,"469":17,"46it":18,"470":17,"4708":26,"471":[17,26,28],"4714":17,"4715":28,"472":17,"473":17,"4730765":20,"474":17,"475":17,"476":17,"4760":17,"4771":17,"4775":17,"4778":[16,17],"481":17,"482":17,"4830":17,"4835":18,"484375":21,"488800":21,"489300":21,"492":21,"493":21,"494":[20,22,28],"495":[20,22],"4950298309326175":18,"496300":21,"498300":28,"4c56":26,"4e00":26,"4min":28,"500":[8,9,17,18,28],"50000":[16,20,22],"50256":[8,9],"50257":[8,9],"50265":8,"5052083730697632":21,"507274627685547":28,"5078":17,"508":17,"508400":28,"5086541175842285":21,"50s":28,"511600":28,"512":[8,9,16,17,18,20,25,35],"5126085":26,"512892706":26,"513903572":[26,27],"515":28,"517":[16,17],"518600":21,"519107250":26,"5203435":20,"521495":16,"523876":16,"523935":16,"5241561":16,"5241641":16,"526":18,"52739":16,"52741":[16,17],"52742":[16,17],"52s":28,"5300284":[26,27],"5300366":[26,27],"5300382":[26,27],"5300797":[26,27],"5300798":[26,27],"533007":16,"5338274":16,"534100":28,"5348":17,"535":18,"5355083":16,"535871217":26,"5362":17,"538415431976318":21,"539034":20,"5391":17,"541312140":26,"543":26,"543168":27,"548":18,"549700":28,"550":18,"550050854":26,"551":17,"554748717":26,"556211948394775":21,"558":17,"55s":28,"560":17,"566511c2":26,"566800":28,"5671":28,"5673179":27,"572":17,"5733":17,"577":17,"578279":27,"5787":17,"57s":28,"5817":17,"585600":17,"586300":21,"5885416865348816":21,"594":18,"596":27,"598":17,"599":18,"5998":28,"59s":28,"600":28,"6008":28,"603":17,"60325843":20,"6035":17,"605":[16,17],"6053":17,"6057":28,"606":17,"608":18,"608700":17,"609":21,"610":17,"614":17,"614578247070312":28,"615000":28,"618":28,"620800":28,"621":17,"622100":28,"6234891":27,"62369585":27,"628":[17,28],"6300201":22,"6304667":22,"6316376":22,"631995":22,"632081":22,"6335964":22,"6336556b0fcc":26,"634":17,"6361":17,"6380":17,"639":17,"6390928":27,"63it":18,"640231244991988":18,"643":28,"644":17,"646500":28,"648":21,"6486":17,"651":17,"652":17,"655":18,"656200":28,"657":[18,28],"660022":20,"6604":18,"6606147":16,"6606148":16,"6606149":16,"66087157":20,"662":17,"66287":27,"665":17,"6674871":22,"6677885":22,"6693997":22,"669589":22,"6699016":22,"66it":18,"670800":28,"671566":22,"672068":22,"6724825":22,"6732":17,"6732295":22,"6734715":22,"673493":22,"6745713":22,"6811":17,"6826":17,"6858":18,"686":17,"687":17,"6875":21,"688":17,"69109577":27,"693":18,"698":28,"69885534":20,"70000":[16,20,22],"70001":20,"70002":20,"70007":20,"70021":20,"7074":27,"708000":17,"7081338":27,"709400":28,"712":21,"7125":17,"7135416865348816":21,"713772296905518":21,"715866088867188":28,"7171":27,"7187":17,"71it":18,"720":17,"721":18,"7225920":20,"723":18,"7246":17,"72519076":20,"7257":17,"72897774":20,"72d76fde":26,"7300":16,"730655":26,"732":21,"7326993":20,"7381":17,"73886937":27,"7391":28,"7401":16,"7402":16,"74117":22,"7417527":20,"743":[16,17],"7447916865348816":21,"747484129693843e":25,"748":16,"74895114":20,"751000":28,"75396":20,"7548112869262695":28,"7552083730697632":21,"7558":17,"757200":28,"75889":20,"759100":28,"7599":28,"75it":27,"76544284820557":18,"768":[8,9],"772":17,"775":17,"7760416865348816":21,"779":27,"780000":28,"7806625366211":18,"78125":21,"7818309228245777":16,"7818321":27,"7854470":26,"788":21,"789":[21,26],"793":21,"7933":17,"795247554779053":18,"79856":22,"7995051":20,"7c90fc70":26,"800":28,"8000":[16,17,22,27],"80000":16,"8001":[16,22,27],"8002":[16,27],"801":17,"80294055":27,"805":17,"8072543":20,"8084":[16,17],"815":17,"8192":18,"8218":17,"823":17,"823300":28,"8266496658325195":18,"8289":18,"82it":18,"83047426":20,"831":17,"8313":17,"833":28,"837":21,"8372503":20,"838":17,"838400":17,"84298295":20,"843":28,"846":17,"848333358764648":28,"85161":20,"852":28,"856":17,"860000":28,"864":17,"864900":28,"86615":20,"86722755":20,"86_400":16,"8712":17,"8786":24,"8787":24,"878700":21,"879":[16,17],"8796":24,"8797":24,"88348":20,"8888":[16,24,27],"88it":18,"8932":28,"8bb3":26,"8c23":26,"8cec9ff8b80d":26,"8min":28,"900":[27,33,36],"90000":[20,22],"90096927":27,"9038":18,"907700":28,"909":26,"9114":17,"920300":28,"92308444":20,"931100":21,"932":17,"9333dfbd":26,"933700":28,"9393594":22,"9396427":22,"939829":22,"9399223":22,"9399953":22,"940445":22,"940700":28,"9447163529829545":18,"94499177":20,"947":21,"951400":28,"9541":17,"957":21,"958":28,"95b4":26,"963":17,"9642":17,"96f3":26,"972000":28,"975656":20,"97792":28,"978000":28,"982":17,"982100":28,"9857":26,"98it":18,"993":17,"993250":20,"996":18,"998":17,"9984":18,"998783":20,"999":[8,9,28],"9b537d1fda9e4e9cadc673ba2a472e247deee69a6229ff8d":24,"abstract":[10,16,20,27],"bal\u00e1z":28,"boolean":[3,9,11,14],"break":[0,32],"byte":[2,3,4,9],"case":[1,9,12,18,20,28],"class":[1,2,3,4,8,9,10,11,12,13,14,16,17,18,21,22,27,28,31,33,34,35,36],"default":[0,2,4,8,9,10,11,12,13,14,16,17,18,21,25,28,31,35],"enum":3,"export":[1,17,18,19,22,24,25,32,33,34],"final":[17,18,27,28,34],"float":[2,3,4,5,8,9,10,11,12,14,17,18],"function":[1,9,10,11,12,14,16,17,18,22,27],"import":[1,9,16,17,18,29,31,35],"int":[2,3,4,5,8,9,10,11,12,13,14,16,17,18,20,21,22,26,27],"long":[8,9,12,16,26,27,36],"merri\u00ebnbo":28,"new":[0,1,9,14,17,21,24,25,27,28,29,33],"null":[16,20,22,26,27],"public":[33,36],"return":[8,9,10,11,12,14,16,17,22,27,28,31],"short":[1,17,28,29],"static":[9,10],"switch":22,"true":[1,2,3,4,8,9,10,11,12,13,14,16,17,18,20,21,22,26,27,28,31,35],"try":[1,12,17,22,24,32],"while":[17,22,35],And:[9,17],But:25,For:[0,1,3,9,11,12,16,17,21,22,24,25,27,28,31,32,33,34,35,36],IDs:[1,31],Not:[9,12,31],One:[8,9,22,25,28],Such:20,TIS:[17,19,22,24,33],That:[3,12,16,20,21,22,27,28],The:[0,1,3,8,9,10,11,12,13,14,16,17,18,20,21,22,24,25,26,27,28,29,31,32,33,34,35],Then:[3,9,14,16,17,18,27],There:[16,18,20,28],These:[1,3,19,32,33,36],Tying:28,Use:[3,21,32],Used:1,Uses:31,Using:[17,19,21,23,24,29,32,34],WITH:25,With:[28,32],__init__:17,__offset:14,__valu:14,_age_dai:[16,27],_aggreg:22,_convert_array_to_triton_input:17,_convert_df_to_dict:17,_field_typ:[2,4,9],_hasna:17,_mask:22,_missing_typ:[2,4,9],_nextitempredictiontask:12,_placehold:[2,3,4,9],_schema:[2,4,9],_validate_matching_col:[16,27],_weight:11,a032dfed738c:26,abandon:31,abc:[9,10,11,14],abil:[17,28,29],abl:[16,21,22],about:[0,1,3,16,17,21,24,26,27,28,30,31,32,33,34,36],abov:[0,3,18,27,28],absolut:[8,9],acceler:[1,16,20,21,27,28,29,33,34,35],accept:[1,9,11,12,28],access:[1,9,10,14,24,28,31],accord:[12,21,25,27,28,31],accordingli:[17,18,32],account:[21,25,28],accumul:[17,18,21,28,32],accuraci:[1,9,12,17,18,28,29,31,32,36],achiev:18,acm:[1,21,24,28,34,36],acmintern:28,across:34,action:[0,31],activ:[9,10,12,22],activation_funct:[8,9],actual:[3,12,16,22],adafactor:[8,9],adam:9,adam_beta1:[8,9],adam_beta2:[8,9],adam_epsilon:[8,9],adamw:21,adamw_hf:[8,9],adapt:[8,9,17,28],add:[0,2,4,8,9,10,16,17,21,25,26,28,32],add_argu:18,add_modul:[9,10],add_module_and_maybe_build:[9,10],add_timestamp_to_sess:16,added:[9,10,11],addit:[0,3,9,10,17,25,28,29,31,33,36],addition:16,addmetadata:[16,20,27],address:1,adopt:[0,17,36],adressa:25,advanc:[1,28,36],advantag:28,advis:[18,32],affect:[8,9],after:[9,10,11,16,17,21,22,24,25,26,27,28,33],afterward:16,again:32,age:3,age_dai:[20,21,22],ages:3,agg:[16,20,26,27,33],aggreg:[1,7,9,11,12,14,17,18,21,25,28,33,36],agre:[0,16,17,18,20,21,22,26,27,28],ahead:0,aim:[25,28],albert:[8,9,10,25,34],albertconfig:[8,9],alexandro:28,alexi:28,algorithm:[1,17,25,28,34],alia:[4,9,11],all:[0,1,3,9,11,12,13,14,16,17,18,20,21,22,24,25,26,27,28,29,31,32,35],allow:[3,8,9,10,14,16,17,20,24,25,28,31,33,36],allow_overlap:[2,4,9],along:[33,34],alpha:3,alreadi:[9,22,24,26,27,28],also:[0,1,3,9,14,16,17,18,20,21,25,28,32,33,34,35,36],altern:[14,16,28],although:[26,28],alwai:[0,3,17,31],among:[17,21,28,32,33,35,36],amp:[9,17],analog:36,analysi:[1,28,31,33,34,36],ani:[0,3,9,10,11,14,16,17,18,20,21,22,26,27,28],ann:28,annot:[2,3,4,9,17],anonym:[1,28],anoth:[1,28,33],anymor:26,anyth:0,apach:[16,17,18,20,21,22,26,27,28],api:[0,1,9,16,17,21,22,24,28,31,33],app:[26,28],append:9,appendic:[25,34],appendix:[25,28,31],appl:26,appli:[1,2,3,4,8,9,10,11,12,13,14,16,17,20,21,22,27,33,36],applianc:26,applic:[16,17,18,20,21,22,26,27,28],apply_invers:[2,4,9],apply_mask_to_input:[9,14],approach:[17,18,28,31,35,36],appropri:[28,31],approv:0,approxim:[3,12,28],april:[26,27,29],aqua:26,arang:[16,20,22],arbitrari:3,architectur:[1,9,10,14,17,18,19,21,22,23,24,28,29,33,36],area:[28,29],arg:[8,9,10,13,14,17,18,21,28,35],argpars:18,argument:[1,8,9,11,14,18,22,25,31,32,34,35],argumentpars:18,arrai:[17,22],array1:14,array2:14,art:[1,17,20,28,33,36],artifact:[17,22],arxiv:[9,11,28],as_tabular:[9,10],ask:[0,9,14],assert:16,assign:[18,32,35],associ:[3,33],assum:[12,35],astabular:[9,22],astyp:[16,17,20,22,26,27],astype_nansaf:17,asynchron:[14,28],atleast_1d:14,atom:[21,27,28],attach:[9,10],attempt:31,attend:28,attent:[0,1,8,9,10,12,14,28,31,36],attention_head_s:[8,9],attention_probs_dropout_prob:[8,9],attention_window:[8,9],attn:[9,12],attn_lay:[8,9],attn_pdrop:[8,9],attn_typ:[8,9,25],attribut:[3,9,14],augment:[9,13,21,28],auto:[8,9,17,20,21,22,27,28],auto_find_batch_s:[8,9],autoencod:28,automat:[1,9,10,11,12,16,17,18,20,21,27,28,31,32,34,35],automatic_build:[9,11],autonotebook:[17,20,22,27,28],autoregress:28,avail:[1,9,14,16,18,21,24,25,26,27,28,29,30,31,32,35],averag:[9,11,27,31,32,35],avg_category_id_pr:27,avg_category_id_pric:27,avg_precis:17,avg_precision_at_10:18,avg_precision_at_20:18,avg_result:17,avgprecisionat:[9,12,18,21],avoid:[16,18,26,27,28],awar:[1,8,9],axi:[3,9,13,14,26],axial:[8,9],axial_norm_std:[8,9],axial_pos_embd:[8,9],axial_pos_embds_dim:[8,9],axial_pos_shap:[8,9],axial_pos_shape_first_dim:[8,9],b695:26,b87a:26,back:[18,21,22],backend:[17,21,33],backward:[0,18,32],bag:[9,11],bahdanau:28,bart:28,base:[1,2,3,4,7,8,9,12,13,14,16,20,25,27,30,31,32,33,36],baselin:[1,25],basemaskedarrai:17,baseoper:14,bash:24,basi:[16,17,18,20,21,22,26,27,28],basic:[17,21,23,24,28,35],batch:[3,8,9,14,17,18,21,22,28,32,33,35],batch_norm:10,batch_siz:[12,14,21,28,35],batch_size_train:[17,18],batch_size_valid:[17,18],batch_upd:[9,14],bceloss:[9,12],becaus:[1,9,25,26,28,31,32,33,36],becom:[27,33,36],been:[1,17,21,27,28,29,33,36],befor:[9,11,12,13,16,17,18,20,21,22,27,31,32,33],begin:32,behavior:[1,26,27,29],being:[9,17,18,22,28],believ:0,belong:[9,14,17],below:[0,3,16,17,21,22,25,26,27,28,32,34],benefit:[8,9,28],bengio:28,bert4rec:[28,36],bert:[8,9,12,28,34],bertconfig:[8,14],best:[0,18,21,25],beta:3,better:[1,14,28,31,32],betterproto:[2,3,4,9],between:[1,3,9,11,14,16,21,24,25,26,27,28,29,31,32,34],bf16:[8,9],bf16_full_ev:[8,9],bi_data:[8,9],bia:[9,10,22],bias:[21,28],bidirect:[9,14,28],bin:[16,20,22,24],binari:[1,9,12,14,28,31],binaryaccuraci:[9,12],binaryclassificationprepareblock:12,binaryclassificationtask:[1,9,12],binaryprecis:[9,12],binaryrecal:[9,12],blob:[0,14],block:[1,7,8,9,11,12,17,20,22,32,33,35,36],blockbas:[9,10,11,12],blockormodul:[9,11],blocktyp:[9,12],blog:[28,34],board:0,bodi:[1,9,12,17,21,22,28,31],body_output:9,book:[1,34],bool:[2,3,4,8,9,10,11,12,13,14,15,17,26,27],bool_domain:[2,3,4],booldomain:[2,3,4],borealisai:28,bos_token_id:[8,9],both:[3,8,9,17,21,22,27,31,32,34,35,36],bottleneck:28,bougar:28,bound:[17,28],box:[1,9],brand:[26,27,28],bridg:[1,28,31,34,36],brief:21,briefli:34,bring:30,broadcast:32,brows:[1,28],browser:[24,26],bst:36,bucket:33,buffer:3,buffer_s:14,bug:0,build:[0,1,8,9,10,11,12,13,14,17,18,24,25,28,33,34,36],build_block:[9,10],buildabl:10,buildableblock:[9,10,11,12],built:[9,10,32],busi:33,bytearrai:[17,22],bytes_valu:3,c6bd7419:26,cach:[8,9],caglar:28,calcul:[9,10,12,14,16,17,27,28],calculate_batch_size_from_input_s:14,calculate_metr:[9,12,14],calendar:16,calibr:31,call:[8,9,10,11,12,14,17,22,28,31,33],call_bodi:9,callabl:[2,4,9,11,14],callback:9,came:28,can:[0,1,3,9,10,11,12,13,14,16,17,18,20,21,22,24,25,26,27,28,29,31,32,33,34,35,36],cannot:[3,17,28,31],capabl:28,captur:[3,17,28,29],card:26,cardin:[5,9,11,12,16,17,20,21,22,28,33],care:[17,18],carri:[9,10],cart:[1,31],cat:[14,28],cat_feat:[16,26,27],cat_path:[16,17,20,21,22],catalog:[1,22,24],categ_feat:20,categor:[0,3,9,11,14,16,17,20,21,22,25,28,31,33],categori:[0,16,17,18,20,21,22,26,27,29],categorical_cardin:[2,4],categorical_featur:14,categorical_modul:[9,11,22],categorical_tag:[9,11],categorifi:[16,20,27,33],category_cod:[26,27],category_id:[26,27,28,33],causal:[1,9,10,14,28,31,34],causallanguagemodel:[9,14],cdp:[26,27,29],cell:[16,17,18,20,21,27,28],certain:[3,9,18,20,27,28],cf8e6e792ca8:26,challeng:[1,34],chanc:12,chang:[0,1,3,9,17,18,29],changelog:0,changhua:28,channel:[17,22],characterist:[21,27,28],check:[0,3,9,10,13,16,18,21,22,26,27,28,32],check_gpu:14,check_input:14,check_schema:8,checkpoint:[9,17,18,28],checkpoint_path:9,child:[3,9,10],child_nam:9,cho:28,choic:[1,12,25],choos:[1,18,32],chore:0,chunk:[14,16,28],chunk_size_lm_head:[8,9],clamp_len:[8,9],clarif:0,class_nam:[9,10],class_or_str:[9,14],classif:[1,9,12,14,21,28,31,33,36],classifi:[9,14],classifier_dropout:[8,9],classifier_dropout_prob:[8,9],classmethod:[2,4,8,9,10,11,13,14],clean:[17,22],click:[9,12,16,26,28,34],client:[14,17,22],clip:[16,20,22],clm:[9,14,31,34],close:[18,27],cloud:[1,17,22,24,33,34],cloudpickl:[9,22],cls_index:[8,9,12],cnn:36,code:[16,18,20,21,27,31,33,35],col:[16,17,26,27],col_nam:[16,17,27],col_schema:17,col_selector:[16,27],col_valu:17,collate_fn:14,collect:[1,2,3,4,9,14,16,18,20,24,26,27,29,33],cols_to_read:14,column:[2,3,4,9,11,14,16,17,20,22,27,28,33,34],column_map:[16,27],column_nam:[2,3,4,9,11,17,22],column_schema:[2,4,9],columnschema:[2,4,9],columnselector:[16,27],com:[0,1,14,17,18,24,25,26,28,29,30,34],combin:[1,3,8,9,11,21,28,31],come:3,comfort:0,command:[1,17,18,22,24],comment:[0,3],commerc:[1,17,19,24,25,26,28,29,34],common:[1,22,28],commonli:[28,35],commun:[22,28,31,33,36],compani:33,compar:[2,4,9,28,34],comparison:21,compat:[0,1,25,31,33],competit:[1,28,31],complet:[1,9,17,18,21,24,25,28,33],complex:[16,33],complianc:[1,16,17,18,20,21,22,26,27,28],compon:[1,25,33,34],compos:[33,36],compositetensor:3,compound:[21,27,28],comprehens:34,comput:[8,9,11,12,14,16,18,20,26,27,28,32,35],compute_loss:[9,14],compute_masked_target:[9,14],compute_metr:[9,12,14,17,18,21,28],compute_metrics_each_n_step:[8,9,25],compute_selector:[16,27],concat:[1,9,12,14,17,18,21,25,28,31],concat_mlp:25,concaten:[9,11,13,14,21,28,31],concatfeatur:[9,13,22],concept:[18,23,24,29,31],conceptu:31,condit:[16,17,18,20,21,22,26,27,28],conduct:[17,21,28],confer:28,config:[1,6,7,9,12,14,16,17,18,21,22,25,27,28,31,34,35],configur:[1,8,9,11,17,18,21,25,28,31,33,34,36],configuration_albert:[8,9],configuration_bert:8,configuration_electra:[8,9],configuration_gpt2:[8,9],configuration_longform:[8,9],configuration_reform:[8,9],configuration_roberta:8,configuration_transfo_xl:[8,9],configuration_util:[9,10],configuration_xlnet:[8,9],congratul:[18,28],conneau:28,connect:[10,22,31],consecut:16,consequet:27,consid:[3,34],consist:[3,9,12,14,33],constant:18,constraint:3,construct:[1,9,12,17],constructor:[9,10,34],consum:[17,22],cont:14,cont_col:27,cont_feat:16,contain:[1,9,10,12,14,16,17,18,21,22,24,26,27,28,29,31,33,35],content:[6,16,17,18,20,21,22,26,27,28],context:[0,1,3,8,9,14,28,31],contextu:[17,28,29],contigu:[16,20],continu:[7,9,12,14,16,17,20,21,22,25,28,31,33],continuous_featur:14,continuous_modul:[9,11,22],continuous_module_class:[9,11],continuous_project:[1,9,11,12,17,18,21,28],continuous_soft_embed:[9,11],continuous_tag:[9,11],continuousfeatur:[9,11,22],contribut:[1,34],contributor:[33,36],control:[9,14],convbert:34,convbertconfig:14,convei:27,conveni:25,convers:[17,22],convert:[1,3,9,10,13,16,17,22,27,33,36],convert_col:[17,22],convert_df_to_triton_input:17,convert_table_to_triton_input:17,convolut:36,cooki:1,copi:[2,4,9,14,16,17,18,20,21,22,24,26,27,28,32],copyright:[16,17,18,20,21,22,26,27,28],core:[17,21,22,24,31,34],corpor:[16,17,18,20,21,22,26,27,28],corpu:[9,14],correct:[13,14,17,22],correctli:22,correspond:[8,9,22,28,33],corrupted_input:[9,14],cos:27,cosin:[8,9,21,27],cosine_with_warmup:[8,9],coss:9,could:[3,8,9],count:[3,16,17,20,26,27,33,36],coupl:20,coveo:[1,34],cover:[18,24,29],coverag:3,covert:14,cpu:[8,9,14,17,21,22,26,27,28,33,34,35],creat:[0,1,2,4,8,9,10,14,17,21,22,24,25,26,28,31,32,33,35],create_categor:[2,4],create_continu:[2,4],create_output_placehold:14,create_schedul:9,creation:[17,22],critic:33,cross:[28,31],cross_entropi:25,crossentropyloss:[9,12,22],csv:[27,29],ctr:34,cu11:1,cuda:[1,14,17,18,22],cuda_low_occupancy_warn:[16,27],cuda_visible_devic:[17,20,21,22,25,32],cudatoolkit:1,cudf:[1,14,16,17,18,20,22,27,29,32,33],cumul:[21,28,35],cupi:[16,18,27],current:[1,3,9,10,14,17,28,34],custom:[1,16,21,27,31,33],cut:[1,16,17,20,21,22,35],cutoff:[8,9],cycl:[8,9,27],cyclic:[16,27],d_emb:[8,9],d_head:[8,9],d_inner:[8,9],d_model:[1,8,9,10,12,17,18,21,25,28,31],d_output:[9,11,17,18,21,28,31],dag:[14,16,17,22,27],dai:[3,17,18,21,22,27,28,33,35,36],daili:35,dask:[1,17,22,27],dat:16,data:[1,3,9,14,18,22,28,29,33,34,36],data_fold:16,data_loader_engin:[8,9,17,18,21,25,28,35],data_path:[16,25,28],data_se:[8,9],data_util:[7,9,16,20,27,28,35],dataclass:[2,4,9],datafram:[1,14,16,17,20,22,26],dataload:[1,9,11,14,17,18,19,21,23,24,27,29,32,35],dataloader_drop_last:[8,9,17,18,21,25,28],dataloader_num_work:[8,9],dataloader_pin_memori:[8,9],dataparallel:35,dataset:[1,2,3,4,9,11,14,16,17,18,19,20,21,22,24,26,28,29,31,32,33,34,35],dataset_constraint:[2,4,9],datasetconstraint:[2,3,4,9],datasetmock:9,datasets_config:25,datasetschema:[9,11,13,14],date:[3,16,26],datetim:16,datetime64:[16,26],day_index:[16,17,27],dcgat:9,ddp_backend:[8,9],ddp_bucket_cap_mb:[8,9],ddp_find_unused_paramet:[8,9],ddp_timeout:[8,9],deal:[1,9,10,33],deberta:34,debertaconfig:14,debug:[3,8,9],debug_onli:3,decad:[17,36],decai:[9,31],decis:33,decod:28,decoupl:[31,34],decreas:[8,9],decreasingli:12,deep:[16,17,20,22,27,28,33,34],deepspe:[8,9],def:[16,17,27,28],default_embedding_dim:[9,11],default_environ:[2,4,9],default_factori:[2,4,9],default_loss:[9,12],default_mask:14,default_metr:[9,12],default_soft_embedding_dim:[9,11],default_valu:3,defaulttag:[9,11],defin:[3,8,9,10,11,12,14,18,22,26,27,31,33,34],definit:[28,31],del:[16,20,26],delta_dai:[16,27],demo:[17,34],demonstr:[16,18,21,24,25,31,35],dens:[9,10,14,20],dense_shap:3,dense_tensor:3,denseblock:[9,10,22],depend:[0,1,6,7,9,12,16,27,28,31],dependencies_selector:[16,27],deploi:[17,20,22,24,29,33,36],deploy:[17,22,33],deprec:[2,3,4,9,17,21,27,28],deprecationwarn:17,deriv:[9,16,21],describ:[0,3,9],descript:[9,17,18],design:[0,1,16,20,21,22,27,28,33,34,36],desir:[14,16,25],despit:18,detach:14,detail:[1,17,25,28,31],detect:[9,14,25,34],determin:[16,17,18,20,21,22,26,27,28],develop:[1,27,28,30,34],deviat:[9,11],devic:[9,11,12,14,16,17,18,21,22,27,28],device_count:18,devzon:1,df_dict:17,df_event:26,dict:[2,3,4,9,10,11,12,13,14,17,18,20,22,28],dict_kei:17,dictat:[14,17],dictionari:[9,10,13,14,17,28],did:[16,17,18,20,22,27,28],dietmar:28,differ:[1,3,8,9,11,16,17,18,21,22,24,25,26,27,28,31,32,33],differencelag:33,dim:[3,8,9,11,14,28,31],dimens:[3,8,9,10,11,12,13,14,16,17,18,20,21,22,25,28,31],dimension:[3,9,11],direct:[1,27,28,31],directli:[9,14,25,28,35],directori:[9,14,16,18,24,25,27],disabl:[3,14,21,25,31],disable_tqdm:[8,9],disallow_inf:[2,3,4],disallow_nan:[2,3,4],discount:[21,28,35],discret:[3,12],discrimin:[9,14],discriminator_label:[9,14],discuss:[0,34],disk:[1,16,26,27,28],dispatch:22,dist:[12,16,17,18,20,22,27,28],distilbert:34,distilbertconfig:14,distinct:28,distinguish:[3,9,14],distribut:[3,9,11,12,14,16,17,18,20,21,22,26,27,28,32,34],distributeddataparallel:[18,35],distribution_constraint:[2,3,4],distributionconstraint:[2,3,4],div_val:[8,9],diverg:3,divid:[18,31,32],divis:[18,32],dldataload:14,dllogger:21,do_ev:[8,9,25],do_predict:[8,9],do_shutdown:[26,28],do_train:[8,9,25],doc:1,doc_util:[2,6],docker:[16,24,27],document:[0,1,17,18,21,25,28,30,31,32],doe:[1,9,18,28],doesn:12,doing:14,domain:[2,3,4,16,17,18,20,21,22,28],domin:36,don:[0,9,14,25,28,33],done:[0,9,11,17,22,32],dont:22,dot:28,down:[20,21,26,27],download:[16,25,26,29],downstream:[17,22],draft:0,drafter:0,drawn:12,drift:1,drift_compar:[2,3,4],drop:[14,26],drop_last:[14,35],dropatt:[8,9],dropout:[8,9,10,13,22,25,31],dropout_r:[9,13],dryrun:25,dtype:[11,12,14,16,17,18,20,21,22,26,27,28],due:[1,16,17,18,20,22,26,27,28,29],dump:18,durat:18,dure:[8,9,12,14,17,22,28,31,33,35],dynam:[1,28],dzmitri:28,e2e3:26,each:[3,8,9,10,11,12,13,14,16,17,18,19,20,21,22,24,25,26,27,28,29,31,32,33,35],easi:33,easier:27,easili:[16,20,21,22,27,28,33,34,35,36],ecom_rees46:25,ecommerc:[1,25,26,27,29,34],ecosystem:[1,34],edg:[17,21,22,33,34],effect:[18,25,28,31,36],effici:[1,17,20,28,30,32,35,36],effort:0,either:[3,9,16,17,18,20,21,22,26,27,28],elaps:31,electra:[8,9,10,14,25,34],electraconfig:[8,9],electron:26,element:[9,13,17,25,31,33],element_typ:[16,17,20,21,22],elementtyp:[16,17,20,21,22],elementwis:[17,21,28],elementwise_affin:22,elementwise_sum_multiply_item_embed:25,elementwisefeatureaggreg:[9,13],elementwisesum:[9,13],elementwisesumitemmulti:[9,13],els:[9,16,17],emb_initi:[9,11],embd_pdrop:[8,9],embed:[1,7,9,12,13,14,17,18,20,21,22,25],embed_dim:21,embedding_cardin:[9,11],embedding_dim:[9,11],embedding_dim_default:[9,11,31],embedding_dim_from_cardinality_multipli:25,embedding_module_class:[9,11],embedding_s:[8,9,16,17,20,21,22],embedding_t:22,embedding_util:[2,6],embeddingbag:11,embeddingbagwrapp:11,embeddingfeatur:[9,11],embeddingoper:[9,11],embeddings_dim:[9,11],embeddings_initi:[9,11],empir:[1,28,31,34],empti:28,enabl:[1,9,12,14,25,28,31,33,35],encapsul:[9,11,16,28],encod:[3,8,9,11,14,16,20,21,28,31,33,36],encount:[0,1],encourag:[1,25],end:[1,3,9,12,14,18,21,22,28,31,34],end_dat:16,end_n_top:[8,9],end_time_idx:17,end_time_index:[14,17,18],endpoint:[17,22],enforc:[9,14],engin:[1,14,17,22,24,27,29,31,33,34],enhanc:0,ens_config:[17,22],ens_model_path:[17,22],ensembl:[22,33,34],ensemble_export_path:[17,22],ensur:[9,13,18,22],entir:[3,8,9,17,22,28],entropi:[9,28,31],enumer:3,environ:[0,1,3,16,17,18,20,21,22,25,26,27,28,32],eos_token_id:[8,9],epoch:[8,9,14,17,18,21,28],eps:22,epsilon:27,equal:[3,8,9,12,17,18,21,25,28,32],equival:[18,21],error:[9,16,17,18,20,22,27,28],especi:[8,9,17,36],essenti:[33,35],establish:36,estim:[9,34],et_dayofweek:[16,27],et_dayofweek_co:[27,28],et_dayofweek_sin:[16,17,18,27,28],etc:[3,27,28],etl:[17,18,19,21,22,23,26,28,29,33],eval:[9,17,18,21,22,28,35],eval_:[17,18,21,28],eval_accumulation_step:[8,9],eval_data_path:21,eval_dataload:[9,28,35],eval_dataset:[9,21,35],eval_dataset_or_path:[9,18,21,28,35],eval_delai:[8,9],eval_load:35,eval_metr:[18,21],eval_on_last_item_seq_onli:[9,14,25],eval_on_test_set:[8,9,25],eval_path:[18,21,28,35],eval_runtim:[18,21,28],eval_samples_per_second:[18,21,28],eval_step:[8,9],eval_steps_on_train_set:[8,9],eval_steps_per_second:[18,21,28],evalloopoutput:9,evalu:[0,1,8,9,12,14,16,18,20,22,24,27,29,32,33,36],evaluation_loop:[8,9],evaluation_measures_:35,evaluation_strategi:[8,9],even:[14,25,27,28,29],evenli:32,event:[16,26,27,28,29],event_tim:[26,27],event_time_dt:[16,26,27],event_time_t:[26,27],event_typ:[26,27],eventu:[3,34],everi:[8,9,18,28],every_sav:[8,9],evolut:[25,36],evolv:17,exactli:[9,14,25],exampl:[0,3,9,11,12,14,16,17,18,19,20,21,22,23,25,27,28,29,31,32,33,35],examples_util:[7,9,17,18,21,28],exc:[16,17,18,20,22,27,28],except:[9,13,16,17,18,20,21,22,26,27,28,31],exception:18,exclud:[0,9],excluded_int_token:3,excluded_int_valu:3,excluded_string_token:3,excluded_string_valu:3,execut:[17,22,24,27],executor_model:[17,22],exercis:[18,27],exist:28,exp_output:25,expand:[18,21],expect:[3,9,10,11,18,22,26,28],experi:[1,8,9,14,17,18,21,28,32,33,34,36],experiment:[28,31],experiments_group:[8,9],explain:[27,32],explan:[17,21],explor:[1,21,28],expm1:12,export_path:[9,17,22],express:[16,17,18,20,21,22,26,27,28],extend:[9,10,11,14,17,21,28,33,35,36],extens:[1,14,27,28,31],extra:[1,14],extra_metadata:3,extract:14,extract_topk:14,f1score:[9,12],face:[1,8,9,17,36],fact:18,factor:[9,14,31],fail:[17,22],failur:26,fake:[9,14],fall:0,fals:[2,3,4,8,9,10,11,12,14,16,17,20,21,22,26,27,28,31],false_valu:3,fashion:18,fastai:14,faster:[28,32],favor:28,featur:[0,1,2,3,4,7,8,9,10,12,13,14,17,18,19,21,22,24,25,28,29,32,33,34,35,36],feature_config:[9,11,13,25],feature_path:3,feature_schema_path:25,featurecompar:[2,3,4],featureconfig:[9,11,13],featurecoverageconstraint:3,featurenamestatist:3,featurepres:[2,3,4],featurepresencewithingroup:[2,3,4],features_dim:[9,13],features_schema_path:25,featuretyp:[2,3,4],feaur:28,fed:[28,31],feed:[17,22,28,33],feed_forward_s:[8,9],feel:18,feet:0,fei:28,fethi:28,few:[20,21,25,33],fewer:14,ff_activ:[8,9],field:[2,3,4,9,17],fieldmetadata:[2,4,9],figur:[1,21,28,31,33],file:[0,9,14,16,17,18,20,21,22,25,26,29,32,33,35],filenam:32,filename_pattern:16,fill:27,fillna:[16,26],filter:[9,11,16,17,18,20,26,27],filter_column_schema:[2,4,9],filter_columns_from_dict:[2,4,9],filter_featur:[9,10,22],filter_fn:[2,4,9],filtered_batch:17,filtered_sess:[16,20,27],filterfeatur:[9,10,22],final_time_window_index:[18,21,25,28],final_window_index:21,find:[0,18,25],fine:[14,35],finetun:17,finish:[18,21,27,28],first:[8,9,11,12,13,14,17,18,20,21,22,27,28,33,34],first_dropout:22,fit:[9,16,18,26,27,28,33],fit_and_evalu:[14,17],fit_transform:[16,20,27],five:16,fix:[0,3,31],fixedshap:[2,3,4],fixedshapedim:3,flag:[9,14],flat:3,flatten:[9,14],flexibl:[1,9,30,31],float32:[12,14,16,17,20,21,22,27],float64:[16,17,27],float_domain:[2,3,4,9],float_valu:3,floatdomain:[2,3,4,9],fname:[18,28],focu:[0,19],focus:[23,24],folder:[16,17,20,21,22,25,27,28,35],follow:[0,1,3,8,9,12,14,16,17,18,19,20,21,22,23,24,25,27,28,29,31,32,33,34,35],foo:3,forg:1,forget:[17,18,21,28],form:28,format:[16,17,22,26,28,33,35],format_unknown:3,former:25,formula:31,forth:21,forward:[8,9,10,11,12,13,14,18,32],forward_output_s:[9,10,11,13,14],forward_to_prediction_fn:9,forwardref:3,found:[9,11,16,17,20,22,25,27,28,31],four:[17,18,25,31],fp16:[8,9,17,25,28,35],fp16_backend:[8,9],fp16_full_ev:[8,9],fp16_opt_level:[8,9],frame:[17,18,33],framework:[1,14,17,22,27,33,34],free:[16,18,20,21],freq_threshold:[16,17,20,21,22],frequenc:12,frequent:[1,18],from:[0,1,3,8,9,10,11,12,14,16,17,18,20,21,22,24,25,27,29,31,32,33,34,35,36],from_df:[17,22],from_featur:[9,11],from_feature_config:[9,13],from_json:[1,2,4,9],from_panda:16,from_proto_text:[2,4,9],from_registri:[9,10],from_schema:[1,9,11,12,14,17,18,21,28,31,35],fsdp:[8,9],fsdp_config:[8,9],fsdp_min_num_param:[8,9],fsdp_transformer_layer_cls_to_wrap:[8,9],full:[12,17,21,25,26,28,35],full_determin:[8,9],fulli:[1,10,31],funnel:34,furnitur:26,further:[1,3,18],furthermor:28,futur:[9,14,17,21,27,28,31],futurewarn:21,gabriel:28,gain:[17,21,28,29,35,36],gap:[28,31,34,36],gate:28,gdf:[16,18,27,32],gdpr:1,gelu:[8,9,25],gelu_new:[8,9],gender:3,gener:[1,3,9,11,12,14,16,17,18,19,20,21,22,23,24,25,27,28,31,32,33,36],generate_synthetic_data:16,get:[1,9,10,12,14,16,18,20,21,25,26,27,28,31],get_children_by_class_nam:[9,10],get_cycled_feature_value_co:27,get_cycled_feature_value_sin:[16,27],get_dataload:28,get_embedding_size_from_cardin:5,get_embedding_sizes_from_schema:5,get_eval_dataload:9,get_fake_token:[9,14],get_item_ids_from_input:8,get_linear_schedule_with_warmup:9,get_log_uniform_distr:12,get_model_repository_index:[17,22],get_output_sizes_from_schema:14,get_padding_mask_from_item_id:8,get_schedul:9,get_test_dataload:9,get_train_dataload:9,get_unique_sampling_distr:12,gil:32,github:[0,9,12,14,17,18,25,30],give:28,given:[3,8,9,10,12,13,14,17,21,22,27,28,31],glob:[16,18,20,21,22,26,27,28,35],global:[9,16,32],global_rank:14,global_s:[14,18],global_step:[18,21,28],goal:[18,20,27,36],going:[0,1,9,16,17,20,22,26,27,28],good:[0,22,25],got:[17,22],gotten:0,govern:[16,17,18,20,21,22,26,27,28],gpt2:[8,9,10],gpt2config:[8,9,14],gpt2model:[9,10],gpt2prepar:[9,10],gpt:[9,10,12,31,34],gpu:[1,14,16,17,19,20,21,22,23,26,27,33,34,35],gpu_preprocess:27,gradient:[17,18,21,28,32],gradient_accumulation_step:[8,9,21,28],gradient_checkpoint:[8,9],grain:[21,28],grandpar:3,graph:[3,17,22,27],great:[31,34],greater:3,greater_is_bett:[8,9],group:[0,2,3,4,8,9,14,16,20,25,26,32,33],group_by_length:[8,9],group_pres:[2,3,4],groupbi:[16,20,26,27,33],groupby_col:[16,20,27,33],groupby_feat:[20,27],groupby_featur:[16,20,27,33],groupby_features_list:[16,27],groupby_features_trim:27,groupby_features_trunc:16,grow:17,grpc:14,grpcclient:17,gru4rec:[28,36],gru:[21,28,31,36],gtp2:[9,10],guarante:[12,14,17],guid:[0,24],guillaum:28,gulcehr:28,had:[3,20],half:[8,9,17,28,35],half_precision_backend:[8,9],hand:[20,27,28],handl:[3,8,9,11,16,28],happen:[27,32],hard:9,has:[1,3,8,9,16,17,18,27,28,29,32,33,36],hash:[2,4,9],hash_se:[8,9],have:[0,1,3,9,12,13,16,17,18,20,21,22,24,25,26,27,28,31,32,33,36],haven:24,head:[0,1,7,9,10,16,20,21,22,26,27,33,35,36],head_reduct:9,head_weight:9,header:[17,22],health:[17,22],help:[0,1,18,28],helper:14,henc:[9,28],here:[1,3,9,16,17,20,21,25,27,28,32],hesit:0,heurist:[9,11],hf4rec:25,hidasi:28,hidden:[1,9,10,11,12,14,21,28],hidden_act:[8,9,25],hidden_dropout_prob:[8,9],hidden_s:[8,9,14,28],hide:9,high:[1,16,20,22,27],higher:[1,18,28,36],highest:[0,8,9,17],highli:1,histori:29,hit:[21,28],hive:27,holger:28,hook:9,host:24,hot:[9,11,14,25,28,31],hour:[16,27,35],how:[0,1,9,11,16,17,18,21,22,24,25,26,32,33,34,35],howev:16,html:[9,12,17,18,20,22,27,28],http:[0,1,3,9,11,12,14,16,17,18,20,21,22,24,25,26,27,28,29,30,35],httpsocketpoolrespons:[17,22],hub_model_id:[8,9],hub_private_repo:[8,9],hub_strategi:[8,9],hub_token:[8,9],hubstrategi:[8,9],hug:[1,8,9,17,36],hugectr:34,huggingfac:[17,18,21,25,28],hyperparamet:[18,21,25],hypertun:31,idea:28,ident:[3,22,25],identifi:[3,24,31],ids:[9,12,13,14,17,21,28],idx:[9,10],ieee:28,ignor:1,ignore_data_skip:[8,9],ignore_kei:9,illustr:[21,28,36],iloc:17,imag:[1,3],image_domain:[2,3,4],imagedomain:[2,3,4],immedi:3,impact:[14,32],implement:[0,1,9,10,12,13,17,21,24,25,28,32,33,35,36],impli:[16,17,18,20,21,22,26,27,28],implic:3,improv:[28,29,31],in_environ:[2,3,4],in_featur:[10,22],inact:[17,28],includ:[0,1,9,11,12,16,18,21,24,25,27,28,31,32,34],include_inputs_for_metr:[8,9],include_last_offset:11,inconsist:[16,22],increas:[12,17,18],increment:[9,24,25,27,35],incremental_log:9,incrementalloggingcallback:9,index:[1,3,9,10,14,16,17,22,27,28,30,33,36],index_column_nam:3,index_featur:3,indexed_by_time_eval_:17,indexed_by_time_metr:14,indic:[3,9,11,14,26,27,33],individu:[9,13],industri:[1,17,28,36],infer:[1,9,11,14,16,19,20,21,22,24,27,28,29,31,34],infer_embedding_s:[9,11],infer_embedding_sizes_multipli:[9,11],inferenc:[17,22,33,34],inferenceservercli:[17,22],inferinput:17,inferrequestedoutput:17,inferresult:14,infin:3,infinity_norm:3,infinitynorm:3,influenc:36,info:[9,14,28],inform:[0,1,3,9,14,16,18,21,24,26,29,30,31,34,35,36],information_retriev:35,infti:3,inher:[27,28],inherit:[8,9,28],init:[2,4,8,9,11],init_rang:[8,9],init_std:[8,9],initi:[9,11,14,25,28,31],initializer_rang:[8,9],inject:21,inner_group_num:[8,9],innermost:3,inp_merg:25,inp_tensor:[9,12],inplac:22,input:[1,8,9,10,11,12,13,14,17,18,22,25,26,33,36],input_class:17,input_data_dir:[17,18,20,21,22,26,27,28],input_dict:[2,4,9],input_dir:[14,17,18],input_dropout:25,input_featur:[8,9],input_features_aggreg:25,input_mask:[9,13],input_modul:[1,9,12,17,18],input_numer:[9,11],input_path:33,input_s:[9,10,11,12,13,14,28],input_schema:[9,16,17,22,27],input_shap:[9,10],input_tensor:[9,13],inputblock:[9,10,11],inputs_emb:[9,10],insid:[3,21],inspir:[17,28,31],instal:[24,25],instanc:[1,3,8,9,10,11,12,22,26,28,31,35],instantan:[17,18,21,28],instanti:[8,9,11,14,18,21,31,33,35],instantit:[9,11,14],instead:[0,1,9,17,21,25,28,32],instruct:[1,25],int32:[3,16,17,20,22],int64:[3,16,17,20,21,22],int_domain:[2,3,4,9,11],int_valu:3,intdomain:[2,3,4,9],integ:[3,9,10,14,16,17,20,21,22,27,32],integer_format:3,integr:[1,9,21,28],intend:[0,16,17,18,20,21,22,26,27,28,31],intens:18,intent:[1,3],interact:[1,9,14,17,20,21,22,23,24,25,28,31,33,34,36],interactions_df:16,interactions_merged_df:[16,17],interepret:32,interest:[0,1,28],intermedi:22,intermediate_s:[8,9],intern:28,interoper:27,interpret:[17,22],intervalstrategi:[8,9],introduc:[0,16,18,21,25,28,34],introduct:[29,30],invalid:31,ipc:24,iprogress:[17,20,22,27,28],ipynb:[18,20,22,27,28],ipython:[26,28],ipywidget:[17,20,22,27,28],is_bool_dtyp:17,is_categor:3,is_decod:[8,9],is_embed:[2,3,4],is_float:[2,4],is_gpu_dataloader_avail:15,is_integer_dtyp:17,is_list:[16,17,20,21,22],is_merlin_dataloader_avail:15,is_pyarrow_avail:15,is_rag:[16,17,20,21,22],is_server_l:[17,22],is_sort:3,isdir:[16,17,22],isin:17,isinst:17,isnul:[26,27],issu:[1,25],item:[1,8,9,11,12,13,14,17,18,19,20,21,22,24,27,29,31,33,35],item_embedding_dim:25,item_embedding_t:[9,11,12,22],item_feat:16,item_first_interaction_df:26,item_first_timestamp:[16,27],item_id:[9,11,14,16,17,18,20,21,22,27,28],item_id_column_nam:[2,4,9],item_id_embeddings_init_std:25,item_id_scor:17,itemid_seq:[9,14],itemid_ts_first:16,itemrec:[16,27],items_first_ts_df:16,iter:[9,12,14,17,18,21,28,35],its:[3,9,10,11,17,18,21,28,31,34,35],jannach:28,jensen:3,jensen_shannon_diverg:3,jensenshannondiverg:3,jian:28,jiang:28,jit:[17,22],jit_mode_ev:[8,9],job:[8,9],join:[16,17,18,20,21,22,26,27,28,33],joingroupbi:27,jointli:[9,14],json:[17,18,22],jump:25,jun:28,jupyt:[17,20,22,24,27,28],jupyterlab:24,just:[8,9,17,20,21,25,27,28,32],kaggl:[16,26,29],karatzogl:28,keep:[9,11,16,18,21,25,26],kei:[1,3,8,9,10,11,14,17,18,21,25,28,36],kernel:[20,21,26,27],kind:[16,17,18,20,21,22,26,27,28],know:[0,22],knowledg:[3,28],known:28,kwarg:[2,4,8,9,10,11,12,13,14],kyunghyun:28,lab:24,label:[8,9,11,12,14,16,17,20,21,22,31,35],label_nam:[8,9],label_smooth:[9,25],label_smoothing_factor:[8,9],labels_onehot:[1,9,21,28],labelsmoothcrossentropyloss:9,lambda:[9,10,14,16,20,27],lambda_fn:14,lambdamodul:14,lambdaop:[16,27,33],lampl:28,languag:[1,3,9,14,16,17,18,20,21,22,26,27,28,31,34,36],larg:[18,26,27,28,29,33],larger:[18,32],last:[8,9,10,12,14,16,17,21,22,33],last_dropout:22,lastli:31,later:[16,21,28],latest:[14,16,18,21,22,27,28,32,33,36],launch:[16,18,21,22,27,28,32],launcher:32,law:[16,17,18,20,21,22,26,27,28],layer:[1,8,9,10,11,12,13,14,17,20,21,22,25,28,31],layer_1:22,layer_2:22,layer_norm:[9,11,22,28],layer_norm_ep:[8,9],layer_norm_epsilon:[8,9],layer_norm_featurewis:25,layernorm:22,lead:14,leaf:3,learn:[0,1,9,14,16,17,18,20,21,22,27,28,30,31,32,33,34,36],learnabl:[9,14],learning_r:[8,9,17,18,21,25,28],learning_rate_num_cosine_cycles_by_epoch:[8,9,21],learning_rate_schedul:25,learning_rate_warmup_step:25,least:[9,12,14,18],left:[16,26,28],legaci:16,len:[16,26],length:[3,8,9,10,11,14,16,17,20,21,22,27,28,33],length_column_nam:[8,9],lenovo:26,less:[3,16,17,26,27],let:[0,16,17,21,22,27,28],level:[1,3,9,14,16,18,20,27,31,33],leverag:[1,17,18,21,28,33,34,35,36],lexicograph:3,lib:[16,17,18,20,21,22,27,28],librari:[1,14,16,17,18,24,25,30,33,34,35,36],libtorch:22,licens:[16,17,18,20,21,22,26,27,28],lifecycle_stag:[2,3,4],lifecyclestag:[2,3,4],like:[0,1,8,9,11,12,13,16,17,18,21,22,25,26,27,28,29,31,33],likelihood:28,limit:[3,14,16,17,18,20,21,22,26,27,28],lin:28,line:[17,18,20,21,24,25,33],linear:[8,9,10,12,22,25],linear_with_warmup:25,lingual:28,link:[3,8,9,17,25,28,34],list:[0,2,3,4,8,9,10,11,12,14,16,17,18,20,21,22,26,27,28,31,33,34,35],list__offset:17,list__valu:[17,22],list_featur:14,list_fil:14,list_seq:28,listslic:[16,20,27],liu:28,live:[17,22],living_room:26,load:[9,10,14,18,20,21,22,26,27,28],load_best_model_at_end:[8,9],load_model_trainer_states_from_checkpoint:9,loaded_model:22,loader:[9,14,21,28,32],loc:22,local:[8,9,14,16,17,18,20,22,27,28],local_attention_probs_dropout_prob:[8,9],local_attn_chunk_length:[8,9],local_num_chunks_aft:[8,9],local_num_chunks_befor:[8,9],local_rank:[8,9,18,28],localhost:[17,22],locat:28,location_constraint_regex:3,lock:32,log1p:12,log:[0,8,9,12,21,24,27,28],log_attention_weight:[8,9],log_level:[8,9],log_level_replica:[8,9],log_on_each_nod:[8,9],log_predict:[8,9],log_predictions_callback:9,logging_dir:[8,9],logging_first_step:[8,9],logging_nan_inf_filt:[8,9],logging_step:[8,9,17,18,21,25,28],logging_strategi:[8,9],logic:[3,25,28],login:25,logit:[9,12,14,17,22],lognorm:[16,20,22],logop:[16,27],loguniformsampl:12,long_tailed_item_distribut:[16,20,22],longer:[26,28],longform:[8,9,10,34],longformerconfig:[8,9,14],longtensor:14,look:[0,24],lookup:[9,11],loop:[17,18,28,35],loss:[1,8,9,12,14,17,18,21,22,25,28,31,32],loss_reduct:[8,9],loss_typ:25,lossmixin:[9,14],lot:1,low:16,lr_scheduler_typ:[8,9,21],lsh:[8,9],lsh_attention_probs_dropout_prob:[8,9],lsh_attn_chunk_length:[8,9],lsh_num_chunks_aft:[8,9],lsh_num_chunks_befor:[8,9],lstm:31,lucent:26,ludewig:28,machin:[1,17,22,28,32,33,35,36],made:[21,28],mai:[3,8,9,16,17,18,20,21,22,26,27,28],main:[18,21,24,25,27],maintain:[0,32],make:[0,1,14,17,21,22,32,33],make_df:22,malt:28,mamba:1,manag:[9,10,17,21,25,28,35],mani:[1,9,16,17,21,26,27,28,29,36],manipul:[16,20,27,33,34],manual:[18,28,33],map:[9,11,16,17,18,20,22,27,28,35],map_column_schema:[2,4,9],map_dai:[20,22],map_fn:[2,4,9],map_typ:[2,4,9],mappingproxi:[2,4,9],mappingtransformermask:14,market:[26,27,29],markov:1,martinz:3,mask:[1,6,7,8,10,11,12,13,14,17,18,21,22,28,34],mask_schema:[9,14],masked_target:9,maskedlanguagemodel:[9,14,22],maskedtarget:9,maskinginfo:[9,14],maskingschema:[9,14],masksequ:[9,10,11,14,21],master:0,match:[14,16,27,31,33],matrix:[28,31],max:[3,8,9,11,16,17,20,21,22,26,28],max_:3,max_examples_count:3,max_fraction_of_sequ:3,max_fraction_threshold:3,max_grad_norm:[8,9],max_id:12,max_image_byte_s:3,max_n_sampl:[9,12],max_norm:11,max_off_domain:3,max_per_sequ:3,max_position_embed:[8,9],max_seq_len:[9,14],max_sequence_length:[1,3,8,9,11,12,14,17,18,21,28,31],max_session_length:[14,16],max_siz:[16,17,20,21,22],max_span_length:[9,14],max_step:[8,9],max_valu:[2,4,16,27],maxim:18,maximum:[3,9,10,11,12,14,21,28,33],mean:[3,8,9,11,12,17,18,21,22,27,28,35],meaning:28,meanreciprocalrankat:9,meansquarederror:[9,12],meant:[12,17],mechan:[1,28],media:[1,17],megatronbert:34,megatronbertconfig:14,mem_len:[8,9],memori:[16,17,18,20,21,26,31,35],mention:26,merg:[0,1,9,16,17,26],merge_valu:9,merge_with:9,mergetabular:[9,11],merlin:[0,1,2,4,8,9,11,12,14,16,17,18,20,21,22,24,25,27,28,35],merlin_standard_lib:[6,9,11,13,14],merlindataload:[14,28,35],messag:[2,3,4,9,17,18,24],meta:[17,21,28,31],metadata:[2,3,4,8,9,21,28,31],method:[8,9,10,11,12,13,14,17,21,22,27,28,31,32,35],metric:[1,8,9,12,14,18,22,25,31,32,33,35,36],metric_for_best_model:[8,9],metric_key_prefix:[9,18,21,28,35],metric_nam:9,metricsmixin:[9,14],mf_constrained_embed:25,mid_domain:[2,3,4],middomain:[2,3,4],might:[1,22,26],mimick:21,min:[3,9,11,16,17,18,20,21,22,26,27,28],min_avg_token_length:3,min_count:3,min_coverag:3,min_domain_mass:3,min_examples_count:3,min_fract:3,min_fraction_of_sequ:3,min_fraction_threshold:3,min_id:12,min_index:[2,4],min_per_sequ:3,min_sequence_length:3,min_session_length:14,min_valu:[2,4],mine:1,mini:32,minimum:[3,12,14],minimum_session_length:[16,20,27],minimum_supported_image_fract:3,misc_util:[2,6],miss:25,mixin:14,mkdir:[16,17,22,27],mkechinov:[26,29],mlm:[9,14,17,18,21,25,28,31,34],mlm_probabl:[9,14,25],mlp:[7,9,11,21,25,28,31],mlp_layers_dim:[9,11],mlpblock:[1,9,10,11,12,21,28,31],mock:9,mode:[9,10,11,14,18,22,32],model:[3,7,8,9,10,11,14,16,18,19,20,23,24,25,26,27,29,32,33,34,35,36],model_bodi:31,model_definit:[9,12],model_input_dict:[17,22],model_nam:9,model_path:[21,22],model_typ:25,modeling_gpt2:[9,10],modeling_util:[9,10],modifi:[1,17],modul:[1,6,16,17,18,20,22,24,27,30,31,34],modular:[1,25],moduledict:22,modulelist:22,modules_to_merg:9,month:[26,27,29],more:[0,1,3,9,12,14,16,17,18,21,24,25,27,28,30,31,32,33,34,35,36],moreira:28,moreov:3,most:[1,17,20,25,28],motiv:28,mount:[16,27],move:[20,21,27,28],mp_paramet:[8,9],mpnet:34,mpnetconfig:14,mseloss:[9,12],msg:[16,17,18,20,22,27,28],multi:[3,9,10,11,12,26,27,28,29,31,35],multi_gpu_train:18,multiclass:28,multinomi:12,multipl:[1,3,8,9,10,11,17,18,21,25,28,31,32,34,35],multipli:[5,9,11,13,31],multivalent_featur:3,must:[3,9,10,11,16,17,18,22,31,32,33],n_embd:[8,9],n_gpu:32,n_head:[1,8,9,10,12,17,18,21,25,28,31],n_inner:[8,9],n_layer:[1,8,9,10,12,17,18,21,25,28,31],n_posit:[8,9],n_proc:32,n_row:[18,32],n_sampl:12,n_samples_multiplier_before_uniqu:12,name:[1,2,3,4,8,9,10,11,12,14,16,17,18,20,21,22,25,27,28],name_sep:[16,20,27],nan:[16,17,20,21,22],nativ:[27,28],natur:[1,3,25],natural_language_domain:[2,3,4],naturallanguagedomain:[2,3,4],navig:24,ndarrai:14,ndcg:[17,21,28,32,35],ndcg_at_10:[18,28],ndcg_at_20:[18,21,28],ndcg_at_40:21,ndcgat:[1,9,12,18,21,22,28],nearest:[1,28],necessari:[1,14,16,17,21,25,27,28,33,36],need:[0,3,8,9,10,14,16,17,18,20,21,22,25,26,28,32,33],neg:[9,12,14],neg_sampl:12,negat:[2,4,9],neighbor:1,neighbour:28,neither:[9,10],nest:[3,14],nested_concat:14,nested_detach:14,nested_numpifi:14,nested_trunc:14,network:[1,9,10,12,14,28,34,36],neural:[1,9,10,28,34,36],new_dataset:33,new_tensor:14,next:[0,1,9,12,14,16,17,18,19,20,21,22,24,26,27,29,31,34,35],nextid:3,nextitempredictionprepareblock:12,nextitempredictiontask:[1,8,9,12,17,18,21,22,28,31],ngc:[1,24],nice:[21,25],nicer:17,nlllo:[9,12],nlp:[1,17,20,21,28,31,33,34],no_cuda:[8,9,21],no_deprecation_warn:21,node:[17,18,22,34],node_config:[17,22],nois:[9,13,21,28],non:[9,13,21,28],non_pad_mask:[9,12],none:[1,2,4,8,9,10,11,12,13,14,17,22,26,28],nor:[9,10],norm:[3,9,11,13],norm_typ:11,normal:[1,8,9,10,11,16,21,22,28,31,33,35,36],not_in_environ:[2,3,4],note:[3,16,18,20,21,22,31,32,33,34],notebook:[16,17,18,19,20,21,22,23,26,27,28,29,33,35],notebook_tqdm:[17,20,22,27,28],notic:[16,27,28,36],notifi:0,novel:28,now:[9,12,16,17,18,20,21,22,25,27,28],npartit:[18,32],nproc_per_nod:[18,32],nr_row:[18,32],nstep:9,num:[17,18,21,28],num_attention_head:[8,9],num_bucket:[8,9,16,17,20,21,22],num_class:14,num_cycl:9,num_embed:[9,11],num_epoch:9,num_exampl:9,num_examples_drift_compar:3,num_examples_version_compar:3,num_gpu:18,num_hash:[8,9],num_hidden_group:[8,9],num_hidden_lay:[8,9],num_item:[2,4],num_lay:28,num_process:18,num_row:[14,20,22],num_train_epoch:[8,9,17,18,21,25,28,35],num_training_step:9,num_tri:12,num_warmup_step:9,num_work:14,numba:[16,27],number:[0,2,3,4,8,9,10,11,12,14,16,17,18,20,22,25,28,31,32,33],number_of_dai:16,numer:[9,11,12,16,21,28,31,33],numeric_features_project_to_embedding_dim:25,numeric_features_soft_one_hot_encoding_num_embed:25,numericvaluecompar:3,numpi:[9,11,14,16,17,20,22,26,27],numpifi:14,numpy_pad_and_concaten:14,nuniqu:[16,20,22],nutshel:[9,11],nvcr:[16,27],nvidia:[0,1,9,12,14,16,17,18,20,21,22,24,25,26,27,28,30],nvt:[16,20,22,26,27,28,33],nvt_output_path:33,nvtabular:[1,17,18,19,21,22,23,24,25,26,28,29,31,34,35],obj:9,object:[2,3,4,8,9,10,11,14,16,17,18,27,31],observ:1,obtain:[9,16,17,18,20,21,22,25,26,27,28,33,34],obvious:28,occup:16,occur:[3,28],occurr:16,oct:[26,27,29],octob:[26,27,29],off:[1,21,35],offer:1,offset:[16,17],often:[9,18,33,36],old:1,omp_num_thread:18,on_epoch_end:9,on_train_begin:9,on_train_end:9,onc:[0,12,16,17,22],one:[0,1,3,9,11,14,17,18,20,21,22,24,25,26,28,31,32,33],one_hot_1d:14,ones:25,onli:[1,3,8,9,10,11,12,14,16,17,18,21,25,26,27,28,29,31,33,35],onlin:[1,25,26,27,28,29,31,33,34],onnx_export:[8,9],oov:[16,20,22,27],oov_string_token:3,open:[18,22,24,26,27,28,29,36],oper:[1,9,11,12,13,14,16,17,22,27,33],opption:28,ops:[9,11,14,16,17,20,22,26,27,33],optim:[8,9,16,17,18,21,22,28,33,34],optim_arg:[8,9],optimizernam:[8,9],option:[1,2,3,4,8,9,10,11,12,13,14,17,21,25,28,31,32,35],order:[3,9,10,18,25,27,28],org:[3,9,11,16,17,18,20,21,22,24,26,27,28,35],organ:[1,8,9,27,34],origin:[1,9,14,17,25,27,28,33],ot_result:17,other:[0,1,2,4,9,10,16,17,18,20,21,27,28,31,33,36],other_embeddings_init_std:25,otherwis:[3,9,14,16,25],our:[0,1,16,17,18,20,21,22,25,26,27,29,30,31,32,33,34,35,36],out:[1,9,16,17,21,22,24,26,27,28,31],out_dtyp:[16,27],out_featur:[10,22],outlin:1,output:[1,8,9,10,11,12,13,14,17,18,21,22,28],output_dir:[8,9,16,17,18,20,21,22,25,27,28,35],output_dtyp:[16,27],output_fn:[9,10],output_fold:[16,27],output_nam:9,output_path:33,output_s:[9,10,14],output_schema:[9,14,16,17,20,22,27],outputs_list:17,outputsizemixin:[9,10,14],outstand:0,over:[1,9,14,16,18,24,25,29,33,35,36],overarch:30,overconfid:[9,12],overhead:18,overlap:9,overload:[18,28],overperform:28,overrid:[8,9,11],overwrit:[16,18],overwrite_output_dir:[8,9,25],own:31,p646:3,packag:[6,16,17,18,20,21,22,25,27,28],packed_64_nano:3,pad:[9,11,12,14,16,20,22,27],pad_seq_column_if_need:14,pad_token:[8,9,13],pad_token_id:[8,9],padding_idx:[9,11,12,14,22],padding_index:14,page:[1,17,30],pair:[8,9,25],pairwis:31,panason:[26,27],panda:[16,17,18,20,22,32],paper:[1,21,28,31,34,36],paquet:14,parallel:[17,18,21,28,36],param:[9,14,27],paramet:[8,9,10,11,12,13,14,17,18,21,28],parent:[3,9,10],parents_selector:[16,27],parquet:[9,14,16,17,18,20,21,22,26,32,33,35],parquet_fil:14,parquetdataset:14,pars:[3,9,14,25,28],parse_arg:18,parse_combin:[9,11],parser:18,part:[1,9,34],part_0:[16,17,18,20,21,22,27,28],part_siz:33,partial:[9,14,32],particular:[0,3,17],particularli:[1,17,31],partit:[3,14,18,27,32,33],partition_col:[16,20,27,33],partition_on:33,parts_per_chunk:14,pass:[0,8,9,10,11,14,17,18,22,32,33],passiv:[8,9],past:[9,14,24,28,31,36],past_id:16,past_index:[8,9],path:[3,9,14,17,18,21,22,25,26,28,33,35],path_or_proto_text:[2,4,9],path_to_data:[16,27],pathlik:9,paths_or_dataset:[14,35],pattern:[1,28],pbtxt:[16,25,27,33],pdf:[9,11,18],pei:28,peng:28,per:[17,18,21,22,28,31,32,33],per_device_eval_batch_s:[8,9,17,18,21,25,28,35],per_device_train_batch_s:[8,9,17,18,21,25,28,32,35],per_gpu_eval_batch_s:[8,9],per_gpu_train_batch_s:[8,9],percentag:35,perceptron:[9,10],perform:[1,9,12,16,17,18,21,22,24,25,27,28,33],permiss:[16,17,18,20,21,22,26,27,28],permut:[9,14,21,28,31,34],permutationlanguagemodel:[9,14],permute_al:[9,14],person:[3,29],phenomena:36,phrase:28,physic:3,pickl:9,pin_memori:14,pin_memory_devic:14,pip:[24,25],pipelin:[1,14,16,17,18,20,21,22,24,27,28],pkl:[9,21,22],place_model_on_devic:[8,9],plai:1,plain:[17,22],plan:[0,3],platform:[26,27,29],pleas:[0,9,12,17,18,20,21,22,27,28,32],plm:[9,14,28,31,34],plm_probabl:[9,14],plot:25,point:[18,22,26],polari:[26,27],pop:9,pop_label:9,popular:[1,16,17,21,28,29,33,36],portal:[1,28,29],portion:0,pos_item:[9,14],posit:[8,9,11,12,14,21,28,31,33],position_embedding_typ:[8,9],possibl:[8,9,10,11,17,20,21,25,34],post:[0,9,11,17,22,34],post_forward:9,potenti:26,power:28,practic:[28,33],practition:[1,17,25,28,33,36],pre:[1,8,9,10,11,12,13,22,25,28,35],pre_forward:9,pre_lnorm:[8,9],preced:[1,31],precis:[9,12,17,28,35],precisionat:9,pred:9,predefin:31,predict:[1,8,9,12,14,17,18,19,20,21,22,24,29,33,34,35,36],predict_al:9,predict_top_k:[8,9],prediction_loop:9,prediction_loss_onli:[8,9],prediction_step:9,prediction_task:[7,8,9,17,18],prediction_task_dict:22,prediction_task_or_head:[9,10],predictiontask:[8,9,10,12],predictpytorch:[17,22],prefer:[0,1,3,17,28,29,32],prefetch_factor:14,prefix:9,preliminari:29,prepar:[8,9,10,12,14,17,18,21,27,28,31,33],prepare_modul:[9,10],preprint:28,preproc_sessions_by_dai:[16,17,18],preprocess:[1,14,19,23,24,25,27,28,29,33,34,35],prerequisit:18,presenc:[2,3,4],present:[1,3,24,25,28,34,35],preserv:[14,20],pretrain:28,pretrained_embedding_modul:[9,11],pretrained_embedding_module_class:[9,11],pretrained_embeddings_tag:[9,11],pretrained_modul:[9,11],pretrained_output_dim:[9,11],pretrainedconfig:[9,10],pretrainedembeddingfeatur:[9,11],pretrainedembeddingsiniti:[9,11],pretrainedmodel:[8,9,10,17,18,28],previou:[1,3,8,9,17,18,21,28,31,35],previous:[1,17,22,25],price:[26,27],price_log:27,price_log_norm:[27,28],primarili:24,print:[14,16,17,18,20,21,22,26,27,28,35],prior:29,priorit:0,prioriti:0,pro:0,probabl:[9,12,14,28],problem:[0,17,28],proce:31,proceed:28,process:[1,9,11,12,14,17,18,21,22,25,26,27,28,32,33,35,36],process_metr:9,processed_nvt:[16,17,18,20,21,22,27,28],prod_first_event_time_t:[26,27],produc:[9,11,17,22,28,32,33,36],product:[1,3,8,9,17,22,26,28,29,31,33,34,36],product_id:[26,27,28,33],product_id_past:26,product_recency_days_log_norm:[16,17,18,27,28],program:33,progress:25,proj_init_std:[8,9],proj_share_all_but_first:[8,9],project:[0,1,9,11,12,14,17,18,21,25,26,27,28,29,30,31],project_continuous_featur:[9,11],projection_modul:[9,11,22],promis:[1,21],prompt:24,properli:[9,33],properti:[2,3,4,8,9,10,11,14,16,17,20,21,22,27],propos:[0,17,28,31,36],proto:[2,4,6,9],proto_typ:[2,4,9],proto_util:[2,6],protobuf:[17,21,25,28,33],protocol:3,provid:[0,1,3,9,10,11,12,14,16,17,20,21,22,24,25,27,28,29,31,33,34,36],publicli:[26,27,29],publish:[1,28],pull:[0,24],purchas:[1,26,28,31],purpos:[9,25,31],push_to_hub:[8,9],push_to_hub_model_id:[8,9],push_to_hub_organ:[8,9],push_to_hub_token:[8,9],put:1,pyarrow:[17,21,28],pyarrowdataload:[14,17,21,28,35],pypi:1,pyt:[20,22],pyt_train:18,python3:[16,17,18,20,21,22,25,27,28],python:[1,9,12,17,22,25,32,33],pytorch:[0,1,9,10,11,16,19,21,22,23,24,25,27,28,29,30,31,32,33],queri:22,queryabl:35,question:0,quickli:[16,20,28],rag:[3,14],ragged_tensor:3,raggedtensor:3,rais:[8,9,10,17,31],randint:[16,20,22],random:[9,14,16,20,22,25],random_data_from_schema:14,random_start_date_and_tim:16,randomli:[9,14,17,28,31],rang:[9,11,12,16,18,21,28,35,36],rank:[9,12,14,17,21,28,35],ranking_metr:[1,6,7,18,21,28],rankingmetr:9,rapid:[1,16,20,27],rapidsai:1,rare:28,rate:[9,10,18,21,28,32,34],rather:28,ratio:[3,9,14],raw:[3,14,21,22,26,27,33,36],raw_df:26,ray_scop:[8,9],reach:22,read:[0,1,16,17,18,20,21,22,31,32],read_csv:[16,26],read_parquet:[16,17,20,22,27],reader_kwarg:14,readi:[17,18,22,31],readm:[0,34],readthedoc:[17,20,22,27,28],real:20,realist:28,reason:[0,9,11,32],recal:[9,12,17,21,28,32,35],recall_at_10:[18,28],recall_at_20:[18,21,28],recall_at_40:21,recallat:[1,9,12,18,21,22,28],receiv:[18,32],recenc:16,recency_featur:[16,27],recency_features_norm:[16,27],recent:[1,9,17,28,29,31],recogn:18,recommend:[1,8,9,10,16,20,25,27,30,31,33,35,36],recommnend:17,record:[3,34],recsi:[1,17,18,21,24,25,28,33,34,35],recsys2021:25,recsys_main:25,recsys_train:[17,18,35],recurr:[1,28,36],recurs:3,reduc:[9,12,18,28,31],reduct:[8,9],rees46:[25,26,27,29],rees46_schema:25,refactor:25,refer:[1,3,9,12,19,23,29,31,32,33,34,35],reform:[8,9,10,34],reformerconfig:[8,9],refresh:[18,26],regard:1,regist:34,registri:6,regress:[9,12,21,28,31],regressionprepareblock:12,regressiontask:[9,12],regular:[9,12],rel:27,rel_attn:22,relat:[0,1,9,10,18,21,26,27,28,29,31,34],relationship:[28,34],relative_price_to_avg_categ:27,relative_price_to_avg_categ_id:[27,28],relative_price_to_avg_categori:27,releas:[0,28],relev:[1,18,21,28,29,35],reli:[21,28],relu:[8,9,10,22],rememb:[0,17,18],remington:[26,27],remov:[9,14,17,21,27,28],remove_by_nam:[2,4,9],remove_by_tag:[2,4,9],remove_by_typ:[2,4,9],remove_pad_3d:[9,12],remove_unused_column:[8,9],renam:[16,26,27],reorder_and_upcast_attn:[8,9],repartit:[18,32],repeat:[1,3,21,27],repetit:[16,26],replac:[1,3,9,12,13,14,25,32,34],replacement_prob:[9,13],replacementlanguagemodel:[9,14],replic:32,report:[0,9,24,25],report_to:[8,9,17,18,21,28],repositori:[0,17,22,25,30],repr:[2,4,9],repres:[3,8,9,10,11,12,14,17,20,21,22,25,26,27,28,29,31,32,34],represent:[3,9,14,16,21,22,27,28,31],reprocess:27,reproduc:[1,32,34],request:[0,1,19,24],request_id:17,requir:[1,3,9,11,12,14,16,17,18,27,28,31],requires_schema:[8,9,13,14],research:[1,17,18,25,28,33,36],reserv:[9,11,16,17,18,20,21,22,26,27,28],reset:[9,14],reset_index:[16,26],reset_lr_schedul:[9,18,21,28,35],reset_metr:[9,14],reshap:[11,17],resid_pdrop:[8,9],resourc:18,respect:[16,27,28],respons:[8,9,14,16,17,18,20,21,26,27,28],rest:16,restart:9,restrict:1,resul:28,result:[1,3,9,13,14,17,18,21,22,25,27,28,32],resum:21,resume_from_checkpoint:[8,9],retail:16,retain:35,retriev:[14,28,35],return_output:9,reuse_len:[8,9],review:0,rich:1,right:[1,9,14,16,17,18,20,21,22,26,27,28,31],right_shift_block:[9,10],rmtree:[17,22],rnn:[1,9,10,21,24,29,36],roadmap:1,roberta:[8,34],robertaconfig:[8,14],roform:34,roformerconfig:14,root:24,row:[3,14,16,20,22,26,27,28,29,32],row_group_s:[18,32],row_group_size_row:[18,32],row_groups_per_part:14,row_length:3,row_partition_dtyp:3,rowenta:[26,27],rows_per_dai:16,rq3:25,rtd:[9,14,25,34],rtype:9,run:[1,8,9,12,14,16,17,18,21,22,25,26,27,28,32],run_nam:[8,9],sai:0,sake:28,same:[3,9,11,12,14,17,18,20,22,25,26,27,31,33],same_length:[8,9],samp_log_prob:12,sampl:[9,12,14],sample_from_batch:[9,14],sample_from_softmax:[9,14],sample_softmax:[8,9],sampled_softmax:[9,12],sampler:14,sasrec:36,save:[9,14,17,18,20,22,25,26,27,32,33],save_on_each_nod:[8,9],save_safetensor:[8,9],save_step:[8,9,25],save_strategi:[8,9],save_time_based_split:[16,20,27],save_total_limit:[8,9],saved_model:[21,22],saw:28,sbr:[17,28],scalabl:1,scalar:[21,28,31],scale:[16,17,18,20,22,27,28,31,33,34,36],scale_attn_by_inverse_layer_idx:[8,9],scale_attn_weight:[8,9],scale_grad_by_freq:11,scenario:[1,33],schedul:[0,8,9],schedulertyp:[8,9],schema:[1,2,3,5,6,7,9,10,11,12,13,14,16,18,20,25,27,31,33,35],schema_bp:[2,4,6,9],schema_path:1,schema_util:[7,9],schemamixin:[8,14],scheme:[9,12,31],schwenk:28,score:[9,14,17,22,28],script:[1,16,17,22,24,25,27,32],seamless:1,search:[25,28,30],second:[9,11,14,16,21],section:[17,22,27,28],see:[0,1,3,14,16,17,18,20,21,22,24,25,26,27,28,30,31,32,36],seed:[8,9,14,25],seed_fn:14,seen:[9,27],segment:[9,14],select:[9,10,14,16,17,18,20,21,27,28,31],select_by_nam:[2,4,9,11,17,18,21,28],select_by_tag:[2,4,9,11],select_by_typ:[2,4,9],selected_featur:[16,20,27],selector:[2,4,9,16,27],self:[1,9,10,12,14,16,17,27,28,31],semin:36,send:[19,24],send_triton_request:[17,22],sent:[14,17,22,32],sentenc:28,sentiment:[33,36],sep:16,sep_token_id:[8,9],separ:[3,32,35],seq_feats_list:20,seq_features_len_pad_trim:14,sequenc:[1,2,3,4,7,8,9,10,12,13,14,16,17,20,21,27,28,33,36],sequence_combin:[9,11],sequence_features_trunc:20,sequence_features_truncated_cont:20,sequence_features_truncated_item:20,sequence_length:28,sequence_length_constraint:3,sequence_summari:22,sequenceembeddingfeatur:[9,11,22],sequenceexampl:3,sequencelengthconstraint:3,sequencesummari:22,sequencevalueconstraint:3,sequenti:[1,8,9,10,11,12,13,14,17,18,20,23,24,28,29,30,31,33,34,36],sequentialblock:[1,9,10,12,21,22,31],sequentialtabulartransform:9,sequti:9,seri:17,serial:[9,28],serv:[1,3,16,21,33,34],server:[1,14,19,20,24,29,34],servic:[1,25,28,29,31],sess_id:[16,27],session:[1,8,9,10,12,14,20,25,26,30,31,33,35,36],session_col:14,session_id:[14,16,17,20,22,33],session_id_past:26,session_past_id:16,session_seq_length_max:25,session_t:[16,27],session_tim:[16,27],sessions_by_dai:[20,21,22,27,28],sessions_gdf:[16,20,27],sessions_max_length:[16,20,27],sessions_to_us:17,sessiontime_weekdai:[16,27],set:[1,3,8,9,10,11,12,14,16,18,25,31,32,33,34,35],set_dataset:14,set_default_tensor_typ:14,set_mask:[9,11],set_metr:9,set_schema:8,setup:[0,9],seven:27,sever:[1,16,17],sh_arg:18,shall:0,shannon:3,shape:[2,3,4,9,10,12,13,14,17,21,22,26,27,28],shape_or_modul:[9,10],sharded_ddp:[8,9],share:[1,9,11,12,14,17,18,21,24,28,31],shell:[18,24],shift:[16,26],shime:28,shiseido:26,shorter:18,should:[0,3,9,11,12,14,17,18,22,24,31,32,33],show:[1,21,24,27,28,31,33,35],shown:[27,28,32,33],shuffl:[14,28,35],shuffle_buffer_s:[8,9,14],shuffledataset:14,shut:[20,21,26,27],shutil:[17,22,26],side:29,sigir:[1,34],sigmoid:12,signific:28,significantli:[17,31],similar:[0,24,25,31],similarity_typ:25,similarli:[3,28],simplic:28,simplifi:[16,17,20,22,27,33],simul:16,sin:[16,27],sinc:[3,14,17,18,33,35],sine:27,singl:[1,3,9,10,11,12,16,18,26,31,32,33,35],site:[1,21,26,29],size:[3,9,10,11,12,14,17,18,20,21,22,28,32,36],skew_compar:[2,3,4],skip:[0,16],skip_memory_metr:[8,9],sknn:28,slide:[17,28],slow:12,slower:[14,17,21,28,35],slowli:3,small:[17,21,28,32,35],smartphon:26,smooth:[9,27,31],snippet:31,sofa:26,soft:[9,11,25,28,31],soft_embedding_cardin:[9,11],soft_embedding_cardinality_default:[9,11],soft_embedding_dim:[9,11],soft_embedding_dim_default:[9,11],soft_embedding_module_class:[9,11],softembed:[9,11],softembeddingfeatur:[9,11],softmax:[9,12,28,31],softmax_temperatur:[9,12],softwar:[16,17,18,20,21,22,26,27,28],sokolov:26,solut:[17,22,33,34],solv:28,some:[0,1,9,14,16,18,25,26,28,31,32,33,35],someth:[9,14],somewhat:17,song:1,sort:[12,16,17,18,20,21,26,27,28,33],sort_col:[16,27,33],sort_valu:[16,17,26],sota:28,sourc:[2,3,4,5,8,9,10,11,12,13,14,15,26,27,28,29],souza:28,space:[9,11,17,22,25,27,28],span:[9,14],spars:[3,9,11,20,28,33,36],sparse_featur:[2,3,4,9],sparse_features_max:28,sparse_tensor:3,sparsefeatur:[2,3,4,9],sparsefeatureindexfeatur:3,sparsefeaturevaluefeatur:3,sparsetensor:3,spawn:18,special:[1,9],specif:[1,3,8,9,10,12,16,17,18,20,21,22,26,27,28,31],specifi:[0,3,9,11,12,14,17,18,22,31,35],speed:[18,28],speedup:32,split:[16,20,27,32,35],sqrtn:[9,11],squeez:12,stabl:[9,12,14,16,17,18,20,21,22,27,28],stack:[9,10,11,13,28],stackfeatur:[9,13],stage:28,standard:[1,9,11,16,27,28,31,33,36],start:[0,1,14,16,18,20,21,27,28,30],start_dat:16,start_index:21,start_n_top:[8,9],start_time_idx:17,start_time_index:[14,17,18],start_time_window_index:[18,21,25,28],start_window_index:21,startpath:14,stat:[17,22,27],state:[1,9,10,12,14,17,18,20,21,22,28,33,36],statist:[3,16,20,21,27,28,33],statu:[0,17,22,28],std:[17,22],steepli:17,step:[0,1,3,8,9,16,17,18,20,21,22,24,25,27,28,32,33,35],still:25,stochast:[9,13,21,28,31],stochastic_shared_embeddings_replacement_prob:25,stochasticswapnois:[9,13],store:[8,9,17,20,22,26,27,29,35],str:[2,3,4,8,9,10,11,12,13,14,17,18,21,22,28],strategi:[8,9],stream:[9,17,28],strict:[17,22],string:[3,9,10,11,26],string_domain:[2,3,4,9],string_format:3,string_valu:3,stringdomain:[2,3,4,9],strongli:25,strptime:16,struct:3,struct_domain:[2,3,4],structdomain:[2,3,4],structur:[3,14,17,18,27,35],sub:[17,28,29],subclass:[8,9,10],submodul:6,subpackag:6,subset:[17,18,21,26,27,29],substitut:[3,31],successfulli:[16,17,18,20,22,27,28],suffer:1,suggest:[31,32],suit:[22,24],suitabl:[16,17,18,20,21,22,26,27,28],sum:[8,9,11,13,17,21,28,31],summar:[9,12,33,36],summari:22,summary_activ:[8,9],summary_first_dropout:[8,9],summary_last_dropout:[8,9],summary_proj_to_label:[8,9],summary_typ:[8,9,12],summary_use_proj:[8,9],sun:28,suppli:[9,12,14,17,22],support:[9,10,12,14,16,17,21,22,25,28,31,32,33,35],sure:[0,17,22,32],surround:[9,14],survei:1,swap:[9,13,21,28],sxm2:32,symbol:[9,11],sync:25,synchron:18,synthet:[14,16,17,18],synthetical:22,sys:[26,27,28],system:[1,16,17,18,20,22,24,27,28,29,33,34,36],t4r_paper_repro:25,t4rec:[8,9,10,14,21,28],t4rec_model_class:[9,22],t4rec_schema:14,t4recconfig:[8,9,10,34],t4recdataload:14,t4rectrainingargu:[8,9,17,18,21,28,35],t4rectrainingargumentstf:8,t_co:[9,14],tab:26,tabl:[1,9,11,12,17,18,21,22,32,34],table_to_embedding_modul:[9,11],tableconfig:[9,11],tabular:[1,7,9,12,14,16,17,18,20,27,28,33,34],tabular_input:31,tabular_sequence_testing_data:1,tabularaggreg:[9,11,13],tabularaggregationtyp:9,tabularblock:[9,11],tabulardata:[9,13,14],tabulardropout:[9,13],tabularfeatur:[9,11,14],tabularfeaturestyp:9,tabularlayernorm:[9,11,13],tabularmodul:[9,11],tabularsequencefeatur:[1,8,9,11,12,17,18,21,22,31],tabulartransform:[9,11,13],tabulartransformationtyp:[9,11],tag:[2,3,6,9,11,16,17,18,20,21,22,25,27,28,33],tagasitemid:[16,20,27],tagset:[2,4,9,11],tagstyp:[9,11],tail:[16,27],take:[1,9,12,17,18,22,28,33],taken:[8,9],tang:28,tanh:[8,9,25],tansformerblock:22,target:[1,9,12,14,17,28],target_dim:[9,12],target_flat:[9,14],target_nam:[9,12,14],targetencod:33,task:[1,8,9,12,14,17,18,21,25,27,28,29,31,33,34,36],task_block:[8,9,12],task_nam:[9,12],task_weight:[8,9],task_weight_dict:9,tast:29,team:[0,24,25,34],technic:34,techniqu:[9,11,17,21,24,25,28,31,33,36],temperatur:[9,12,31],tempor:[1,16,20],tensor1:14,tensor2:14,tensor:[3,9,10,11,12,13,14,17,21,22,35],tensor_or_arrai:14,tensor_represent:3,tensor_representation_group:[2,4,9],tensorboard:[9,21],tensorflow:[3,16,17,18,20,22,27,28,33],tensorortabulardata:9,tensorrepresent:3,tensorrepresentationdefaultvalu:3,tensorrepresentationdensetensor:3,tensorrepresentationgroup:[2,3,4,9],tensorrepresentationraggedtensor:3,tensorrepresentationraggedtensorpartit:3,tensorrepresentationrowpartitiondtyp:3,tensorrepresentationsparsetensor:3,tensorrepresentationvarlensparsetensor:3,tensorshap:3,tensort:[17,22],terabyt:[16,20,27,33,34],term:[9,10,17,28,29],termin:[17,22,24,32],tesla:32,test:[0,3,8,9,10,11,12,14,16,20,27],test_dataload:9,test_dataset:9,test_dataset_or_path:9,test_integr:32,text:[3,7,9,17,21,22,25,28,33,36],text_embedding_modul:[9,11],tf32:[8,9],tf4rec:22,tf_out_activ:25,tfidf:3,tftrainer:8,tftrainingargu:8,tfx:3,than:[1,3,16,17,21,25,27,28,33,36],thei:[9,10,14,17,28,29],them:[0,9,11,14,21,28,34],therefor:[17,26,32],thi:[0,1,3,8,9,10,11,12,13,14,16,17,18,20,21,22,23,24,25,26,27,28,29,31,32,33,34,35,36],third:21,those:[1,17,25,26,33,36],though:27,thread:[32,35],three:[0,9,16,17,20,28,33,36],threshold:3,threshold_day_index:16,through:[9,21,28,32,34],throughput:[14,18],thu:[3,16,28],tie:[28,31],tie_word_embed:[8,9],tied:31,ties:12,time:[1,3,14,16,18,20,22,24,25,27,29,31,35],time_domain:[2,3,4],time_featur:[16,27],time_index:[17,18,21,28,35],time_index_ev:[18,21,28],time_index_train:[18,21,28],time_of_day_domain:[2,3,4],time_window_folder_pad_digit:25,timedelta:16,timedomain:[2,3,4],timedomainintegertimeformat:3,timegm:16,timelin:36,timeofdaydomain:[2,3,4],timeofdaydomainintegertimeofdayformat:3,timeout:14,timestamp:[17,20,27,33],timestamp_col:[16,20,27],timetupl:16,tip:[0,21,34],tmp:[17,18,21,25,28,35],to_core_schema:14,to_cpu:9,to_datetim:[16,27],to_ddf:26,to_dict:[17,22],to_head:9,to_huggingface_torch_model:[8,9],to_includ:9,to_lightn:9,to_merg:22,to_model:[9,10],to_modul:[9,10],to_numpi:17,to_parquet:[16,18,20,26,27,32,33],to_proto_text:[2,4,9],to_remov:[2,4,9],to_select:[2,4,9],to_torch_model:[8,9,17,18],todo:[3,9,14],togeth:[20,22,27,30],token:[1,3,8,9,12,14,24,25,28,33,34,36],token_constraint:3,too:18,top:[0,1,3,8,9,14,17,21,28,35],top_k:[1,9,12,14,17,21,28],topic:24,topk:17,torch4rec:[8,9,31],torch:[1,6,7,8,17,18,21,28,31,32,35],torch_compil:[8,9],torch_compile_backend:[8,9],torch_compile_mod:[8,9],torch_op:[17,22],torch_pad_and_concaten:14,torch_util:[7,9,10],torchcolumn:[17,22],torchdynamo:[8,9],torchmetr:[9,12],torchscript:[17,22],total:[14,16,17,18,21,26,27,28,32],total_number_of_row:16,total_seq_length:[1,8,9,10,12,17,18,21,28,31],toward:[17,28,29],tower:[1,31],tpu_metrics_debug:[8,9],tpu_num_cor:[8,9],tqdm:[17,20,22,27,28],tqdmwarn:[17,20,22,27,28],traceback:17,traced_model:[17,22],track:25,tradit:[1,21],train:[8,9,10,11,12,13,14,16,19,20,22,23,24,26,27,29,31,33,36],train_arg:[21,28],train_batch_s:35,train_dataload:[9,28,35],train_dataset:[9,35],train_dataset_or_path:[9,18,21,28,35],train_dataset_path:35,train_load:35,train_loss:18,train_metr:[21,28,35],train_on_last_item_seq_onli:[9,14],train_path:[18,20,21,22,28,35],train_runtim:18,train_samples_per_second:18,train_steps_per_second:18,trainabl:[9,11,14],trainer:[6,7,14,18,21,33,35,36],trainer_callback:9,trainer_fil:18,trainer_util:[8,9],trainercallback:9,training_arg:[8,9,17,18,28,35],training_args_tf:8,trainingargu:[8,9,35],tranform_label_to_onehot:14,transf_exp_arg:25,transf_exp_main:25,transfo_xl:[8,9],transform:[1,6,7,9,11,12,14,18,19,22,23,24,25,26,27,29,34,35],transformer_argu:9,transformer_cfg_paramet:[8,9],transformer_config:[1,9,12,17,18,21,28,31],transformer_optional_argu:9,transformer_registri:34,transformer_required_argu:[9,14],transformer_to_prepar:[9,10],transformerblock:[1,9,10,12,13,21,28,31,34],transformerbodi:[9,10],transformerprepar:[9,10],transformers4rec:[6,16,18,20,22,23,27,29,31,32,33,35],transformers4rec_paper_preproc_dataset:25,transformers_config_cl:[8,9],transformworkflow:[17,22],transfoxl:32,transfoxlconfig:[8,9,14],transfromers4rec:[17,28],translat:[28,33,36],treat:[3,28],tree:[17,18],tri:[9,14,28,31],triag:0,trial:12,triangular:10,trim:20,triton:[1,14,19,20,24,29,34],triton_cli:[17,22],triton_input:17,triton_model:17,tritoncli:[14,17,22],tritonhttpcli:17,tritonserv:[17,22],true_prob:12,true_valu:3,truncat:[8,9,14,16,20,27],tune:[14,18,25,32,35],tupl:[2,4,9,11,12,14],turn:9,tutori:[17,21,26,27,28],two:[1,3,13,18,24,25,27,31,35],txt:[18,25,28],tying:[17,18,31],type:[0,1,2,3,4,6,8,10,11,12,14,17,18,21,22,26,28,32],type_unknown:3,type_vocab_s:[8,9],typic:[1,9,11,14,17,18,28,33],uint_valu:3,unassign:0,unchang:18,uncom:[17,27],under:[0,16,17,18,20,21,22,26,27,28],understand:28,unifi:9,uniform:[12,20,22],uniform_row_length:3,union:[2,4,8,9,10,11,12,13,14],uniqu:[3,12,16,17,20,21,22],unique_constraint:[2,3,4],unique_sampl:12,uniqueconstraint:[2,3,4],unit:[0,3,16,27,28],univalent_featur:3,unix_dai:3,unix_microsecond:3,unix_millisecond:3,unix_nanosecond:3,unix_second:3,unknown_stag:3,unlabel:0,unless:[16,17,18,20,21,22,26,27,28],unlik:1,unmask:[9,14,17],unpack:9,unset:9,unspecifi:3,unsur:0,untie_r:[8,9],until:31,updat:[0,9,14,17,20,22,25,27,28],url:[1,3,17,22,24],url_domain:[2,3,4],urldomain:[2,3,4],usag:[1,9,12,17,25],use:[1,8,9,10,11,12,13,14,16,17,18,20,21,22,24,25,26,27,28,29,31,32,33,35],use_bia:[9,10],use_cach:[8,9],use_ipex:[8,9],use_legacy_prediction_loop:[8,9],use_mems_ev:[8,9],use_mems_train:[8,9],use_mps_devic:[8,9],use_side_information_featur:25,use_synthet:16,used:[1,3,8,9,10,11,12,14,16,17,18,20,21,22,25,26,27,28,31,32,35],useful:[3,8,9,11,14,18,20,26,28,31],user:[1,3,8,9,16,17,18,20,21,22,23,24,27,28,29,31,32,33,34,36],user_id:[26,27,33],user_instal:[17,20,22,27,28],user_sess:27,userwarn:[16,17,18,20,21,22,27,28],uses:[0,1,9,10,14,17,21,25,28,32,35],using:[0,1,3,9,10,11,12,14,16,18,20,21,22,24,25,27,28,31,33,34,35],using_domain:[2,4,9],using_value_count:[2,4,9],usr:[16,17,18,20,22,27,28],usual:[1,14,18],utc:[26,27],util:[2,6,7,9,10,16,17,18,20,21,22,27,28,35],v100:32,val:14,valenc:3,valid:[3,8,9,14,16,17,18,20,27,28,31],validate_everi:[8,9,25],valu:[2,3,4,8,9,10,11,12,13,14,16,17,18,20,21,22,25,26,27,28],value_co:27,value_column_nam:3,value_count:[2,3,4,16,17,20,21,22],value_featur:3,value_sc:[16,27],value_sin:[16,27],valuecount:[2,3,4,20],valuecountlist:[2,3,4],valueerror:[8,9,10,17],values_host:17,van:28,vanilla:28,vari:12,variabl:[9,12,16,17,18,25],variou:18,varlen_sparse_tensor:3,vaswani:28,vector:[9,11,14,21,31],venv:21,verbos:[9,17,22],veri:[1,21,28,31,33,36],verifi:[0,21,22,26,28],version:[1,9,12,14,16,17,18,20,21,22,25,26,27,28],versu:18,via:[9,27],view:[24,26,27,28],visibl:[9,14,28,31],visit:[16,17,18,22,26,29],visual:[20,21,33],visualize_respons:14,vocab_s:[8,9,14],vocabulari:[3,9,11,12,28],vocabulary_s:[9,11],volum:24,wai:[0,1,3,9,18,22,27,28,31],wait:0,walkthrough:34,wall:[26,27,28],wandb:25,wandb_api_kei:25,wandb_mod:25,want:[0,1,17,18,22,28,32,33],warm:9,warmup_ratio:[8,9],warmup_step:[8,9],warn:[8,9,16,17,18,20,21,22,25,27,28],warranti:[16,17,18,20,21,22,26,27,28],watch_tim:[9,12],water_heat:26,wave:[8,9],web:1,websit:30,webtour:[1,34],week:[21,26,27,33,35,36],weekdai:[16,27],weekday_co:27,weekday_sin:[16,20,21,22,27],weight:[3,8,9,11,12,14,17,18,21,28,31],weight_decai:[8,9,25],weight_featur:3,weight_matrix:[9,11],weight_ti:[1,9,12,17,18,21,28,31],weighted_featur:[2,4,9],weightedcategor:3,weightedfeatur:[2,3,4,9],well:[3,18,22,33],went:[21,28],wenwu:28,were:[14,16,18,25,27,29,33],wet:0,what:[0,9,11,18],when:[0,1,3,8,9,10,11,12,14,17,18,21,22,24,25,26,27,28,29,32,33,35],where:[0,1,8,9,11,14,17,18,20,21,26,27,28,34,36],wherea:16,whether:[9,10,11,14,21,28,31],which:[3,8,9,11,12,14,16,17,18,21,22,24,25,26,27,28,31,32,34,35],whole:[9,14,21,31],whose:[3,25],why:[18,28],wider:36,wiki:[3,35],wikidata:3,wikipedia:[3,35],win:34,window:[14,35],wip:25,wipe_memori:[14,18,21,28,35],wise:[13,25,31],with_nam:[2,4],with_properti:[2,4],with_tag:[2,4],with_tags_based_on_properti:[2,4,9],within:[1,9,14,17,20,27,28,33,36],without:[1,3,12,16,17,18,20,21,22,26,27,28],won:[1,8,9,18,25,34],word2vec:36,word:[27,28],word_embed:22,work:[0,1,9,14,16,17,18,21,25,28,30,35],worker:18,workflow:[17,18,19,20,22,24,26,28,33],workflow_etl:[16,17,20,22,27],workflow_path:27,workshop:[1,34],workspac:[16,17,18,20,21,22,24,26,27,28],world:20,worth:26,worthwhil:16,would:[3,16,18,20,22,27],wrap:[2,4,9,10,31,32,35],wrapper:[9,11],write:[16,17,18,20,21,22,26,27,28],writefil:18,wrong:[8,9],wsdm:[1,34],www:[3,16,17,18,20,21,22,25,26,27,28,29],x_cat_nam:28,x_cont_nam:28,xiao:28,xlnet:[1,8,9,10,12,17,18,19,20,22,23,24,25,31,34],xlnetconfig:[1,8,9,12,14,17,18,21,28,31],xlnetfeedforward:22,xlnetlay:22,xlnetmodel:22,xlnetrelativeattent:22,xpu_backend:[8,9],yaml:25,yang:28,year:17,yield:22,yml:0,yoochoos:[16,19,24,25],yoochoose_transform:16,yoshua:28,you:[0,1,3,9,11,12,14,16,17,18,20,21,22,24,25,26,27,28,29,31,32,33,35],your:[1,16,17,18,21,22,24,25,27,28,33,34,35],your_argu:32,your_script:32,yourself:32,yuanh:28,zemei:28,zhilin:28,zhou:28,zip:[20,22]},titles:["Contributing to Transformers4Rec","Transformers4Rec","merlin_standard_lib package","merlin_standard_lib.proto package","merlin_standard_lib.schema package","merlin_standard_lib.utils package","API Documentation","transformers4rec package","transformers4rec.config package","transformers4rec.torch package","transformers4rec.torch.block package","transformers4rec.torch.features package","transformers4rec.torch.model package","transformers4rec.torch.tabular package","transformers4rec.torch.utils package","transformers4rec.utils package","ETL with NVTabular","End-to-end session-based recommendations with PyTorch","Multi-GPU training for session-based recommendations with PyTorch","End-to-end session-based recommendation","ETL with NVTabular","Session-based Recommendation with XLNET","Serving a Session-based Recommendation model with Torch Backend","Getting Started: Session-based Recommendation with Synthetic Data","Transformers4Rec Example Notebooks","Transformers4Rec paper - Experiments reproducibility","Preliminary Preprocessing","ETL with NVTabular","Session-based recommendation with Transformers4Rec","Tutorial: End-to-end Session-based Recommendation","Merlin Transformers4Rec","Model Architectures","Multi-GPU data-parallel training using the Trainer class","End-to-End Pipeline with Hugging Face Transformers and NVIDIA Merlin","Additional Resources","Training and Evaluation","Why Transformers4Rec?"],titleterms:{"class":32,"export":[16,20,27],"function":28,"import":[20,21,22,26,27,28],"new":16,PRs:0,The:36,Tying:31,Using:1,achiev:1,add:27,addit:34,aggreg:[13,27,31],aliv:17,api:[6,25],approach:34,architectur:[31,34],argument:[17,21,28],attribut:0,averag:17,backend:22,base:[10,11,17,18,19,21,22,23,24,28,29,34],benefit:1,between:36,bias:25,block:[10,21,28,31],build:[21,31],calcul:26,categor:27,categorifi:26,check:[17,20],clean:16,code:[0,1,25],column:26,command:25,comparison:32,competit:34,comput:21,conda:1,config:8,connect:[17,28],consecut:26,content:[2,3,4,5,7,8,9,10,11,12,13,14,15],context:25,continu:[11,27],contribut:0,convert:26,creat:[16,18,20,27],csv:26,cudf:26,dai:[16,20],daili:[17,21,28],data:[16,17,20,21,23,24,26,27,32,35],data_util:14,dataload:28,dataparallel:32,dataset:[25,27],datetim:26,defin:[1,16,17,20,21,28],definit:17,depend:15,design:31,develop:0,distributeddataparallel:32,doc_util:5,docker:1,document:6,embed:[11,28,31],embedding_util:5,encod:27,end:[17,19,24,29,33],engin:[16,20],ensembl:17,etl:[16,20,27],evalu:[17,21,25,28,35],exampl:[1,24],examples_util:14,execut:[16,18],experi:[24,25],extract:27,face:[33,34],featur:[11,16,20,26,27,31],feedback:1,file:[27,28],fine:[17,21,28],finetun:28,first:[0,16,26],free:28,from:[26,28],get:[17,22,23,24,29],gpu:[18,24,28,32],group:27,head:[12,28,31],hug:[33,34],huggingfac:36,includ:26,increment:28,indic:30,infer:[17,33],inform:28,initi:27,input:[16,20,21,27,28,31],instal:1,instanti:[17,28],integr:[33,36],interact:[16,26,27],introduct:[27,28],inventori:24,issu:0,item:[16,26,28],kernel:28,label:0,launch:17,learn:29,librari:[20,21,22,26,27,28],load:[16,17,35],log:25,mask:[9,31],memori:28,merlin:[30,33,34],merlin_standard_lib:[2,3,4,5],metric:[17,21,28],misc_util:5,mlp:10,model:[1,12,17,21,22,28,31],modul:[2,3,4,5,7,8,9,10,11,12,13,14,15,21,28],modular:31,multi:[18,32],next:28,nlp:36,normal:27,notebook:[1,24],nvidia:[33,34],nvtabular:[16,20,27,33],object:[21,22,28,29],organ:25,other:34,our:28,out:20,output:[16,20,27,31,33],over:[17,21,28],overview:[16,33],packag:[2,3,4,5,7,8,9,10,11,12,13,14,15],paper:[24,25],parallel:32,parquet:[27,28],path:[16,20,27],perform:32,pip:1,pipelin:33,pre:[16,20,34],predict:[28,31],prediction_task:12,preliminari:26,preprocess:[16,20,26],process:[16,20,31],product:27,proto:3,proto_util:5,python:18,pytorch:[17,18,35],ranking_metr:9,raw:[16,17],read:[26,27,28],recenc:[26,27],recommend:[17,18,19,21,22,23,24,28,29,34],recsi:36,refer:[17,18,28],registri:2,regular:31,relat:30,relationship:36,releas:25,remark:25,remov:[16,26],repeat:[16,26],reproduc:[24,25],request:[17,22],requir:[20,21,22,25,26],resourc:[30,34],respons:22,restart:28,rnn:[28,31],run:24,same:16,sampl:1,save:[16,21,28],schema:[4,8,17,21,22,28],schema_bp:3,schema_util:14,script:18,season:0,seen:[16,26],send:[17,22],sequenc:[11,31],sequenti:[16,21,27],sequentialblock:28,serv:[17,22],server:[17,22,33],session:[16,17,18,19,21,22,23,24,27,28,29,34],set:[17,21,22,27,28],setup:25,side:28,start:[17,22,23,24,29],submodul:[2,3,4,5,7,8,9,10,11,12,13,14,15],subpackag:[2,7,9],support:[1,34],synthet:[20,23,24],tabl:30,tabular:[11,13],tabularsequencefeatur:28,tag:4,tempor:27,text:11,time:[17,21,26,28],timestamp:[16,26],torch:[9,10,11,12,13,14,22],torch_util:14,trace:[17,22],train:[1,17,18,21,25,28,32,34,35],trainer:[8,9,17,28,32],transform:[8,10,13,16,17,21,28,31,33,36],transformers4rec:[0,1,7,8,9,10,11,12,13,14,15,17,21,24,25,28,30,34,36],triton:[17,22,33],tune:[17,21,28],tutori:[1,24,29],tying:28,type:[7,9],user:26,user_sess:26,using:[17,32],util:[5,14,15],valid:21,via:26,visual:17,weight:25,what:28,when:16,why:36,window:[17,21,28],within:16,workflow:[16,27],wrap:[27,28],xlnet:[21,28],your:0}}) \ No newline at end of file diff --git a/review/pr-767/training_eval.html b/review/pr-767/training_eval.html new file mode 100644 index 0000000000..ce404d2e16 --- /dev/null +++ b/review/pr-767/training_eval.html @@ -0,0 +1,260 @@ + + + + + + Training and Evaluation — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Training and Evaluation
  • +
  • +
  • +
+
+
+
+
+ +
+

Training and Evaluation

+ +
+

Data Loading

+

By default, Transformers4Rec leverages the Merlin dataloader for GPU-accelerated loading of preprocessed data that is stored as Parquet files. +Parquet enables this preprocessed data to be easily structured and queryable. +The data in these parquet files are directly loaded to the GPU memory as feature tensors. +CPUs are also supported when GPUs are not available.

+

The following example uses the Merlin Dataloader that is wrapped by the MerlinDataLoader class. +The class automatically sets some options from the dataset schema. +Optionally, you can use the PyarrowDataLoader as a basic option, but it is slower and works only for small datasets since the full data is loaded to CPU memory.

+
train_loader = transformers4rec.torch.utils.data_utils.MerlinDataLoader.from_schema(
+        schema,
+        paths_or_dataset=train_path,
+        batch_size=TrainingArguments.train_batch_size,
+        drop_last=True,
+        shuffle=True,
+    )
+
+
+
+
+

PyTorch

+

To leverage our Transformers4Rec example notebooks that demonstrate how to use Transformers4Rec with PyTorch, refer to Transformers4Rec Example Notebooks.

+
+

Training

+

For PyTorch, the HF transformers Trainer class is extended while retaining its train() method. +Essentially, this means the efficient training implementation from that library is leveraged and manages half-precision (FP16) and multi-GPU training.

+

PyTorch supports two approaches for multi-GPU training: DataParallel and DistributedDataParallel. +DataParallel uses a single process and multiple threads on a single machine. +DistributedDataParallel is more efficient for assigning separate processes for each GPU. +Transformers4Rec supports the DataParallel approach when using the Merlin dataloader.

+

The following code block shows how to create an instance of the Trainer class:

+
from transformers4rec.config.trainer import T4RecTrainingArguments
+from transformers4rec.torch import Trainer
+
+training_args = T4RecTrainingArguments(
+            output_dir="./tmp",
+            num_train_epochs=3,
+            fp16=True,
+        )
+
+recsys_trainer = Trainer(
+    model=model,
+    args=training_args,
+    train_dataloader=train_loader,
+    eval_dataloader=eval_loader,
+)
+
+recsys_trainer.train()
+
+
+

You can automatically instantiate the dataloader when you create the Trainer instance by specifying the following:

+
    +
  • The path or dataset of the training and evaluation data in the train_dataset_path and eval_dataset_or_path arguments.

  • +
  • Specify the schema for the dataset in the schema argument.

  • +
+
training_args = T4RecTrainingArguments(
+            ...,
+            data_loader_engine="nvtabular",
+            per_device_train_batch_size=256,
+            per_device_eval_batch_size=512,
+        )
+
+# Instantiates the train and eval dataloader
+Trainer(
+    model=model,
+    schema=schema,
+    args=training_args,
+    train_dataset_or_path=train_path,
+    eval_dataset_or_path=eval_path,
+)
+
+
+
+
+

Evaluation

+

For the item prediction head, top-N metrics and ranking metrics commonly used in [information retrieval](https://en.wikipedia.org/wiki/Evaluation_measures_(information_retrieval) and RecSys are supported for evaluation:

+
+
Top-N metrics
    +
  • Precision@n - Computes the percentage of the top-N recommended items, which are relevant (labels).

  • +
  • Recall@n - Computes the percentage of relevant items (labels) that are present among the top-N recommended items.

  • +
+
+
Ranking metrics
    +
  • NDCG@n - Normalized Discounted Cumulative Gain at cut-off N of the recommendation list.

  • +
  • MAP@n - Mean Average Precision at cut-off N of the recommendation list.

  • +
+
+
+

During training, the metrics are computed for each N step for both training and evaluation sets. +During evaluation, the metrics are computed for all evaluation batches and averaged.

+

You can implement incremental evaluation by splitting your data into time windows such as week, day, or hour. +A loop, which trains a model or fine-tunes a pre-trained model, can be used with sessions of time window T. +This loop evaluates on sessions of time window T+1.

+

The following example contains a loop that iterates over the days, trains the model or fine-tunes the model pre-trained in the previous day, evaluates with data of the next day, and assumes daily data is split in folders:

+
# Iterates over parquet files with daily data
+for time_index in range(1, 7):
+    train_paths = glob.glob(f"./data/day-{time_index}/data.parquet")
+    eval_paths = glob.glob(f"./data/day-{time_index+1}/data.parquet")
+
+    print('Training with day {}'.format(time_index))
+    trainer.train_dataset = train_paths
+    trainer.reset_lr_scheduler()
+    trainer.train()
+
+    print('Evaluating with day {}'.format(time_index+1))
+    trainer.eval_dataset = eval_paths
+    train_metrics = trainer.evaluate(metric_key_prefix='eval')
+    print(train_metrics)
+    trainer.wipe_memory()
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/review/pr-767/why_transformers4rec.html b/review/pr-767/why_transformers4rec.html new file mode 100644 index 0000000000..4c00b6d023 --- /dev/null +++ b/review/pr-767/why_transformers4rec.html @@ -0,0 +1,173 @@ + + + + + + Why Transformers4Rec? — Transformers4Rec documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • »
  • +
  • Why Transformers4Rec?
  • +
  • +
  • +
+
+
+
+
+ +
+

Why Transformers4Rec?

+ +
+

The relationship between NLP and RecSys

+

Over the past decade, proposed approaches based on NLP research, such as Word2Vec, GRU, and Attention for RecSys, have gained popularity with RecSys researchers and industry practitioners. +This phenomena is especially noticeable for sequential and session-based recommendation where the sequential processing of user interactions is analogous to the language modeling (LM) task. +Many key RecSys architectures have been adopted based on NLP research such as GRU4Rec. +GRU4Rec is the seminal recurrent neural network (RNN) based architecture for session-based recommendation.

+

Transformer architectures have become the dominant technique over convolutional neural networks (CNNs) and recurrent neural networks (RNNs) for language modeling tasks. +Because of their efficient parallel training, these architectures can scale training data and model sizes. +Transformer architectures are also effective at modeling long-range sequences.

+

Transformers have also been applied to sequential recommendation in architectures such as SASRec, BERT4Rec, and BST. +These architectures can provide higher accuracy than CNN and RNN-based architectures. +For more information, see our ACM RecSys 2021 paper. +For more information about the evolution of Transformer architectures and bridging the gap between NLP and sequential and session-based recommendation, see our Transformers4Rec: Bridging the Gap between NLP and Sequential / Session-Based Recommendation paper.

+

A timeline illustrating the influence of NLP research in Recommender Systems

+
+
A timeline illustrating the influence of NLP research in Recommender Systems, from the Transformers4Rec paper
+
+
+
+

Integration with HuggingFace Transformers

+

Transformers4Rec integrates with the HuggingFace (HF) Transformers library, allowing RecSys researchers and practitioners to easily experiment with the latest and state-of-the-art NLP Transformer architectures for sequential and session-based recommendation tasks and deploy those models into production.

+

The HF Transformers was “established with the goal of opening up advancements in NLP to the wider machine learning community”. It has become very popular among NLP researchers and practitioners (more than 900 contributors), providing standardized implementations of the state-of-the-art Transformer architectures (more than 68 and counting) produced by the research community, often within days or weeks of their publication.

+

HF Transformers is designed for both research and production. Models are composed of three building blocks:

+
    +
  • A tokenizer that converts raw text to sparse index encodings.

  • +
  • A Transformer architecture.

  • +
  • A head for NLP tasks such as text classification, generation, sentiment analysis, translation, summarization, among others.

  • +
+

Transformers4Rec leverages the Transformer architectures building block and configuration classes from Hugging Face. +Transformers4Rec provides additional blocks that are necessary for recommendation, such as input features normalization and aggregation, and heads for recommendation and sequence classification and prediction. +The library also extends the Trainer class to allow for the evaluation with RecSys metrics.

+
+
+ + +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file