<jupyter_start><jupyter_text>Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.SPDX-License-Identifier: Apache-2.0 ![image.png](attachment:image.png)  Link Prediction - IntroductionIn this Notebook we are going to examine the process of using Amazon Neptune ML feature to perform link prediction in a property graph.  **Note:** Link prediction models are computationally complex, so this notebook takes ~2-3 hours hour to completeNeptune ML is the ability within Amazon Neptune to create and manage machine learning models using [Amazon SageMaker](https://aws.amazon.com/sagemaker/) and [Deep Graph Library](https://www.dgl.ai/) as use these models within a query to predict elements in the graph. For this notebook we are going to focus on how to predict edges between a pair of vertices in a graph using a type of task known as **link prediction**.(https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning.htmlmachine-learning-overview)To accomplish this in Neptune ML we use a four step process:1. **Load Data** - Data is loaded into a Neptune cluster using any of the normal methods such as the via the Gremlin drivers or using the Neptune Bulk Loader.2. **Export Data** - A service call is made specifying the machine learning model type and model configuration parameters.  The data and model configuration parameters are then exported from a Neptune cluster to an S3 bucket.3. **Model Training** - A set of service calls are made to pre-process the exported data, train the machine learning model, and then generate an Amazon SageMaker endpoint that exposes the model.4. **Run Queries** - The final step is to use this inference endpoint within our Gremlin queries to infer data using the machine learning model.![image.png](attachment:image.png) For this notebook we'll use the [MovieLens 100k dataset](https://grouplens.org/datasets/movielens/100k/) provided by [GroupLens Research](https://grouplens.org/datasets/movielens/). This dataset consists of movies, users, and ratings of those movies by users. ![image.png](attachment:image.png)Each of these vertices have a set of edge associated with them but for this notebook we are interested in the `rated`  edge between a `user` vertex and a `movie` vertex.  To demonstrate link prediction we'll walk through constructing a Neptune ML model from scratch.  We'll then demonstrate how to use that model to infer `rated` edges within Gremlin traversals.   Checking that we are ready to run Neptune ML This notebook assumes that you have already setup and enabled a cluster for Neptune ML. If you have not then please follow the directions located here: [Neptune ML Overview](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning.htmlmachine-learning-overview)Run the code below to check that your cluster is configured to run Neptune ML.<jupyter_code>import neptune_ml_utils as neptune_ml
neptune_ml.check_ml_enabled()<jupyter_output><empty_output><jupyter_text>If the check above did not say that this cluster is ready to run Neptune ML jobs then please check the configuration of your cluster. Load the dataThe first step in building a Neptune ML model is to load data into the Neptune cluster. Loading data for Neptune ML follows the standard process of ingesting data into Amazon Neptune, for this example we'll be using the Bulk Loader. For this notebook we have written a script that automates the process of downloading the data from the MovieLens websites and formatting it to load into Neptune. To begin specify an S3 bucket URI (must be located in the same region as the cluster) and then run the cell below, which takes ~1 minute:<jupyter_code>s3_bucket_uri="s3://<INSERT S3 BUCKET>"
response = neptune_ml.prepare_movielens_data(s3_bucket_uri)<jupyter_output><empty_output><jupyter_text>This process only takes a few minutes and once it has completed you can load the data using the `%load` command in the cell below.<jupyter_code>%load -s {response} -f csv -p OVERSUBSCRIBE<jupyter_output><empty_output><jupyter_text>Check to make sure the data is loadedOnce the cell has completes execution, the data has now been loaded into the cluster. Assuming the cluster was previously empty we can verify the data loaded correctly by running the traversals below to see the count of vertices by label:<jupyter_code>%%gremlin
g.V().groupCount().by(label).unfold()<jupyter_output><empty_output><jupyter_text>If our vertices loaded correctly then the output is:* 1682 movies* 943 users* 19 genres* 100000 ratingTo check that our edges loaded correctly we check the edge counts:<jupyter_code>%%gremlin
g.E().groupCount().by(label).unfold()<jupyter_output><empty_output><jupyter_text>If our edges loaded correctly then the output is:* 100000 rated* 2893 included_in* 100000 wrote* 100000 about Preparing for ExportWith our data validated let's remove some `rated` edges so that we can build a model that predicts these missing connections.  In a normal scenario, the data you would like to predict is most likely missing from the data being loaded so removing these values prior to building our machine learning model simulates that situation.Specifically, let's remove the `rated` edgesfor `user_1`, to provide us with a few candidate vertices to run our link prediction tasks.  Let's start by taking a look at what `rated` edges currently exist.<jupyter_code>%%gremlin

g.V('user_1').outE('rated')<jupyter_output><empty_output><jupyter_text>Now let's remove these edges to simulate them missing from our data.<jupyter_code>%%gremlin
g.V('user_1').outE('rated').drop()<jupyter_output><empty_output><jupyter_text>Checking our data again we see that the edges have now been removed.<jupyter_code>%%gremlin
g.V('user_1').outE('rated')<jupyter_output><empty_output><jupyter_text>Export the data and model configuration**Note:**  If you created your cluster using the CloudFormation script provided in the documentation then the Neptune Export has been installed and configured for you.  If you did not then you need to ensure it is configured as described here: [Neptune Export Service](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-data-export-service.htmlmachine-learning-data-export-service-run-export).  With the data loaded we are ready to export the data and configuration which will be used to train the ML model.  The export process is triggered by a call to the [Neptune Export service endpoint](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-data-export-service.html).  This call contains a configuration object which specifies the type of machine learning model to build, in this example node classification, as well as any feature configurations required.  **Note** The used in this notebook specifies only a minimal set of configuration options meaning that our model's predictions are not as accurate as they could be.  The parameters included in this configuration are one of a couple of sets of options available to the end user to tune the model and optimize the accuracy of the resulting predictions.The configuration options provided to the export service are broken into two main sections, selecting the target and configuring features.  Selecting Targets for Link PredictionIn the first section, selecting the target, we specify what type of machine learning task will be run. To run a link prediction mdoel do not specify any `targets` in the `additionalParams` value.  Unlike node classification or node regression, link prediction can be used to predict any edge type that exists in the graph between any two vertices.  Becasue of this, there is no need to define a target set of values. Configuring Features to Train the ModelThe second section of the configuration, configuring features, is where we specify details about the types of data stored in our graph and how the machine learning model should interpret that data.  In machine learning, each property is known as a feature and these features are used by the model to make predictions.  In machine learning, each property is known as a feature and these features are used by the model to make predictions.  When data is exported from Neptune all properties of all vertices are included.  Each property is treated as a separate feature for the ML model.  Neptune ML does its best to infer the correct type of feature for a property, in many cases, the accuracy of the model can be improved by specifying information about the property used for a feature.  By default Neptune ML puts features into one of two categories:* If the feature represents a numerical property (float, double, int) then it is treated as a `numerical` feature type. In this feature type data is represented as a continuous set of numbers.  In our example, the `age` of a `user` would best be represented as a numerical feature as the age of a user is best represented as a continuous set of values.* All other property types are represented as `category` features.  In this feature type, each unique value of data is represented as a unique value in the set of classifications used by the model.  In our MovieLens example the `occupation` of a `user` would represent a good example of a `category` feature as we want to group users that all have the same job.If all of the properties fit into these two feature types then no configuration changes are needed at the time of export.  However, in many scenarios these defaults are not always the best choice.  In these cases, additional configuration options should be specified to better define how the property should be represented as a feature. One common feature that needs additional configuration is numerical data, and specifically properties of numerical data that represent chunks or groups of items instead of a continuous stream.Let's say that instead of wanting `age` to be represented as a set of continuous values we want to represent it as a set of discrete buckets of values (e.g. 18-25, 26-24, 35-44, etc.).  In this scenario we want to specify some additional attributes of that feature to bucket this attribute into certain known sets.  We can achieve this by specifying this feature as a `numerical_bucket`.  This feature type takes a range of expected values, as well as a number of buckets, and groups data into buckets during the training process.Another common feature that needs additional attributes are text features such as names, titles, or descriptions.  While Neptune ML will treat these as categorical features by default the reality of these features is that they will likely be unique for each vertex.  For example, since  the `title` property of a `movie` vertex does not fit into a category grouping our model would be better served by representing this type of feature as a `word2vec` feature.  A `word2vec` feature uses techniques from natural language processing to create a vector of data that represents a string of text.  In our export example below we have specified that the `title` property of our `movie` should be exported and trained as a `word2vec` feature and that our `age` field should range from 0-100 and that data should be bucketed into 10 distinct groups. **Important:** The example below is an example of a minimal amount of the features of the model configuration parameters and will not create the most accurate model possible.  Additional options are available for tuning this configuration to produce an optimal model are described here: [Neptune Export Process Parameters](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-data-export-parameters.html)Running the cell below we set the export configuration and run the export process.  Neptune export is capable of automatically creating a clone of the cluster by setting `cloneCluster=True` which takes about 20 minutes to complete and will incur additional costs while the cloned cluster is running.  Exporting from the existing cluster takes about 5 minutes but requires that the `neptune_query_timeout` parameter in the [parameter group](https://docs.aws.amazon.com/neptune/latest/userguide/parameters.html) is set to a large enough value (>72000) to prevent timeout errors.<jupyter_code>export_params={ 
"command": "export-pg", 
"params": { "endpoint": neptune_ml.get_host(),
            "profile": "neptune_ml",
            "cloneCluster": False
            }, 
"outputS3Path": f'{s3_bucket_uri}/neptune-export',
"additionalParams": {
        "neptune_ml": {
         "features": [
            {
                "node": "movie",
                "property": "title",
                "type": "word2vec"
            },
            {
                "node": "user",
                "property": "age",
                "type": "bucket_numerical",
                "range" : [1, 100],
                "num_buckets": 10
            }
         ]
        }
      },
"jobSize": "medium"}
%%neptune_ml export start --export-url {neptune_ml.get_export_service_host()} --export-iam --wait --store-to export_results
${export_params}<jupyter_output><empty_output><jupyter_text>ML Data Processing, Model Training, and Endpoint CreationOnce the export job is completed we are now ready to train our machine learning model and create the inference endpoint. Training our Neptune ML model requires three steps.  The cells below configure a minimal set of parameters required to run a model training.  The first step (data processing) processes the exported graph dataset using standard feature preprocessing techniques to prepare it for use by DGL. This step performs functions such as feature normalization for numeric data and encoding text features using word2vec. At the conclusion of this step the dataset is formatted for model training. This step is implemented using a SageMaker Processing Job and data artifacts are stored in a pre-specified S3 location once the job is complete.Additional options and configuration parameters for the data processing job can be found using the links below:* [Data Processing](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-on-graphs-processing.html)* [dataprocessing command](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-api-dataprocessing.html)**Note** Each time you run this you must also provide a unique identifier for the training run under the `training_job_name`.  If you attempt to use a name that has already been used then it will result in an error.<jupyter_code># The training_job_name can be set to a unique value below, otherwise one will be auto generated
training_job_name=neptune_ml.get_training_job_name('link-prediction')

processing_params = f"""
--config-file-name training-job-configuration.json
--job-id {training_job_name} 
--s3-input-uri {export_results['outputS3Uri']} 
--s3-processed-uri {str(s3_bucket_uri)}/preloading """
%neptune_ml dataprocessing start --wait --store-to processing_results {processing_params}<jupyter_output><empty_output><jupyter_text>The second step (model training) trains the ML model that will be used for predictions. The model training is done in two stages. The first stage uses a SageMaker Processing job to generate a model training strategy. A model training strategy is a configuration set that specifies what type of model and model hyperparameter ranges will be used for the model training. Once the first stage is complete, the SageMaker Processing job launches a SageMaker Hyperparameter tuning job. The SageMaker Hyperparameter tuning job runs a pre-specified number of model training job trials on the processed data, and stores the model artifacts generated by the training in the output S3 location. Once all the training jobs are complete, the Hyperparameter tuning job also notes the training job that produced the best performing model.Additional options and configuration parameters for the data processing job can be found using the links below:* [Model Training](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-on-graphs-model-training.html)* [modeltraining command](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-api-modeltraining.html)**Note** Link prediction is a computationally complex model to generate so training this model will take 2-3 hours<jupyter_code>training_params=f"""
--job-id {training_job_name} 
--data-processing-id {training_job_name} 
--instance-type ml.p3.2xlarge
--s3-output-uri {str(s3_bucket_uri)}/training """
%neptune_ml training start --wait --store-to training_results {training_params}<jupyter_output><empty_output><jupyter_text>The final step is to create the inference endpoint which is an Amazon SageMaker endpoint instance that is launched with the model artifacts produced by the best training job. This endpoint will be used by our graph queries to  return the model predictions for the inputs in the request. The endpoint once created stays active until it is manually deleted. Each model is tied to a single endpoint.Additional options and configuration parameters for the data processing job can be found using the links below:* [Inference Endpoint](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-on-graphs-inference-endpoint.html)* [endpoint command](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-api-endpoints.html)**Note** The endpoint creation process takes ~5-10 minutes<jupyter_code>endpoint_params=f"""
--job-id {training_job_name} 
--model-job-id {training_job_name}"""
%neptune_ml endpoint create --wait --store-to endpoint_results {endpoint_params}<jupyter_output><empty_output><jupyter_text>Once this has completed we get the endpoint name for our newly created inference endpoint.  The cell below will print out the endpoint name which will be used in the configuration of Neptune ML in your Gremlin query.<jupyter_code>print(endpoint_results['endpoint']['name'])<jupyter_output><empty_output><jupyter_text>Querying using GremlinNow that we have built our inference endpoint let's see how we can use this to query our graph to find out what `rated` edges will connect `user_1` to movies.  Link prediction is the ability to infer edges between two vertices in our graph. Unlike node classification and node regression, link prediction can infer any of the edge labels that existed in our graph when the model was created.  In our model this means we could infer the probability that a `wrote`, `about`, `rated`, or `included_in` edge exists between any two vertices.  However for this example we are going to focus on inferring the `rated` edges between the `user` and `movie` vertices. To demonstrate this let's predict the `rated` edges exist for `user_1`.A standard Gremlin query to find this information would look like the cell below:<jupyter_code>%%gremlin
g.V('user_1').out('rated').hasLabel('movie').valueMap()<jupyter_output><empty_output><jupyter_text>As we can see, their are currently not any `rated` edges for `user_1` so let's see how we can go about predicting them.First, as with node classification and regression we need to specify the endpoint we are going to use. (`with("Neptuneml.endpoint",""`).Next, when we ask for the link within our query we use the `out()` step to predict the target vertex or the `in()` step to predict the source vertex.  For each of these steps we need to specify the type of model being used with a with() step (with("Neptuneml.prediction")).One additional requirement for link prediction is that we need to use the `hasLabel('labelname')` step after the `out()` or `in()` step which specifies the type of vertex to predict.Putting these items together we get the query below, which will find the movies that` user_1` is likely to rate.<jupyter_code>%%gremlin
g.with("Neptune#ml.endpoint","<INSERT ENDPOINT NAME>").
    V('user_1').out('rated').with("Neptune#ml.prediction").hasLabel('movie').valueMap()<jupyter_output><empty_output><jupyter_text>Great, we can now see that we are getting edges returned from our prediction that do not exist within the graph data.  In the example above we predicted the target vertex but we can also use the same mechanism to predict the source vertex.  In the example below we find the three `user` vertices most likely to create a `rated` edge for the movie *Toy Story*.<jupyter_code>%%gremlin
g.with("Neptune#ml.endpoint","<INSERT ENDPOINT NAME>").
    with("Neptune#ml.limit",3).
      V().has('title', 'Toy Story (1995)').
        in('rated').with("Neptune#ml.prediction").hasLabel('user').valueMap()<jupyter_output><empty_output><jupyter_text>As with the other example models we have shown here these link prediction values can be combined with other Gremlin traversal steps to create more complex traversals, such as returning the path from `user_1` across the inferred `rated` edge to the `movie` vertex to retrieve the `genre` vertices that it is included in.<jupyter_code>%%gremlin
g.with("Neptune#ml.endpoint","<INSERT ENDPOINT NAME>").
      V('user_1').out('rated').with("Neptune#ml.prediction").hasLabel('movie').out('included_in').path()<jupyter_output><empty_output><jupyter_text>From the examples we have shown here you can begin to see how the ability to infer unknown connections within a graph starts to enable many interesting and unique use cases within Amazon Neptune.    Cleaning Up Now that you have completed this walkthrough you have created a Sagemaker endpoint which is currently running and will incur the standard charges.  If you are done trying out Neptune ML and would like to avoid thes recurring costs, run the cell below to delete the inference endpoint.<jupyter_code>neptune_ml.delete_endpoint(training_job_name)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.06507973191587706, 'fraction_numerical': 0.0059625606655881675, 'mean_word_length': 4.945589447650454, 'pattern_counts': {'":': 17, '<': 77, '<?xml version=': 0, '>': 78, 'https://': 16, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '42654', 'license': 'ISC', 'n_tokens_mistral': 5580, 'n_tokens_neox': 5364, 'n_words': 2800, 'path': 'src/graph_notebook/notebooks/04-Machine-Learning/Neptune-ML-03-Introduction-to-Link-Prediction-Gremlin.ipynb', 'repo_name': 'rogargon/graph-notebook', 'size': '21635'}