{ "cells": [ { "cell_type": "markdown", "id": "84c6ff9c", "metadata": {}, "source": [ "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n", "SPDX-License-Identifier: Apache-2.0\n", "\n", "# People Analytics using Neptune ML - Predicting Attrition and Career Progression\n", "\n", "Hiring and retaining good personnel is a key characteristic to making an organization sucessful. One way that organizations approach this problem is through the use of people analytics. People analytics allow business leaders to make data-driven decisions about personnel-related issues such as recruiting, evaluation, hiring, promotion, and retention, etc.\n", "\n", "Human resource and personnel data often contains a vast amount of information that shows the connections and relationships between individuals, companies, jobs, etc. such as:\n", "\n", "* Who works for whom\n", "* How has this person's career progressed\n", "* What opportunities has this person had\n", "\n", "This type of information can be efficently and effectively represented in the form of a graph. Storing and thinking about this data as a series of connections enable us to gain some unique insights. \n", "\n", "In this notebook we will answer two specific questions that come up frequently when looking at HR data in terms of hiring and retaining personnel:\n", "\n", "* Who is most likely to leave?\n", "* What job should someone look at next?\n", "\n", "To demonstrate how you might accomplish this we are going to use a well-known dataset originally from IBM that contains HR attrition data. This dataset is available from [Kaggle](https://www.kaggle.com/pavansubhasht/ibm-hr-analytics-attrition-dataset) under the [Open Data Commons License]( https://opendatacommons.org/licenses/dbcl/1-0/).\n", "\n", "To answer these questions, we will be using [Neptune ML](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning.html) which is a feature of Amazon Neptune that enables users to automate the creation, management, and usage of Graph Neural Network (GNN) machine learning models within Amazon Neptune. Neptune ML is built using [Amazon SageMaker](https://aws.amazon.com/sagemaker/) and [Deep Graph Library](https://www.dgl.ai/) and provides a simple and easy-to-use mechanism to build/train/maintain these models and then use the predictive capabilities of these models within a Gremlin query to predict elements or property values in the graph.\n", "\n", "## Setup\n", "\n", "Before we begin, we need to setup a few things. Most importantly, you will need to provide the path to an Amazon S3 bucket that we can use to store our data in." ] }, { "cell_type": "code", "execution_count": null, "id": "35b40b85", "metadata": {}, "outputs": [], "source": [ "!cp ../../neptune_ml_utils.py ." ] }, { "cell_type": "code", "execution_count": null, "id": "37e9b0af", "metadata": {}, "outputs": [], "source": [ "import neptune_ml_utils as neptune_ml\n", "neptune_ml.check_ml_enabled()\n", "\n", "s3_bucket_uri=\"s3://\"\n", "# remove trailing slashes\n", "s3_bucket_uri = s3_bucket_uri[:-1] if s3_bucket_uri.endswith('/') else s3_bucket_uri" ] }, { "cell_type": "markdown", "id": "ecf827ae", "metadata": {}, "source": [ "## Import and explore data\n", "\n", "Let's start by loading up the data using the associated CSV file and see what our data looks like. *Note* - This notebook assumes the cluster was empty before this command being run." ] }, { "cell_type": "code", "execution_count": null, "id": "2d8dc75c", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "df = pd.read_csv('WA_Fn-UseC_-HR-Employee-Attrition.csv')\n", "pd.set_option('display.max_columns', None)\n", "print(f'Our data file contains {df.shape[0]} columns and {df.shape[1]} rows.')\n", "print('Here is what a few of them look like:')\n", "df.head()" ] }, { "cell_type": "markdown", "id": "8c24a472", "metadata": {}, "source": [ "As you can see, the data contains 1470 rows and 35 columns. Let's see how much overlap is in our data:" ] }, { "cell_type": "code", "execution_count": null, "id": "8bb09372", "metadata": {}, "outputs": [], "source": [ "print(f'Number of unique employees: {df[\"EmployeeNumber\"].nunique()}')\n", "print(f'Number of unique departments: {df[\"Department\"].nunique()}')\n", "print(f'Number of unique fields: {df[\"EducationField\"].nunique()}')" ] }, { "attachments": { "image.png": { "image/png": "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" } }, "cell_type": "markdown", "id": "5e463146", "metadata": {}, "source": [ "Now that we have a high-level view of the data in our file, let's import it into Neptune.\n", "\n", "## Import into Neptune\n", "\n", "To accomplish our data import into Neptune we are going to use the [Neptune Bulk Loader](https://docs.aws.amazon.com/neptune/latest/userguide/bulk-load.html) with a CSV file format. \n", "\n", "Vertices loaded from a CSV are required to be in this format:\n", " \n", " ~id, name:String, age:Int, lang:String, ~label\n", " v1, \"brad\", 29, , person\n", " \n", "Edges loaded from a CSV are required to be in this format:\n", "\n", " ~id, ~from, ~to, ~label, weight:Double\n", " e1, v1, v2, created, 0.4\n", " \n", "Let's convert the employee data from our file into a compatible set of vertices and edges. \n", "\n", "* vertices\n", " * Employee\n", " * Age (int)\n", " * Attrition (categorical)\n", " * BusinessTravel (categorical)\n", " * DailyRate (int)\n", " * DistanceFromHome (int, but likely represents a category)\n", " * Education (int, but likely represents a category)\n", " * EmployeeCount (int)\n", " * EmployeeNumber (int)\n", " * EnvironmentSatisfaction (int, but likely represents a category)\n", " * Gender (category)\n", " * HourlyRate (int)\n", " * JobInvolvement (int, but likely represents a category)\n", " * JobLevel (int, but likely represents a category)\n", " * JobSatisfaction (int, but likely represents a category)\n", " * MaritalStatus (category)\n", " * MonthlyIncome (int)\n", " * MonthlyRate (int)\n", " * NumCompaniesWorked (int)\n", " * Over18 (category)\n", " * OverTime (category)\n", " * PercentSalaryHike (int)\n", " * PerformanceRating (int, but likely represents a category)\n", " * RelationshipSatisfaction (int, but likely represents a category)\n", " * StandardHours (int)\n", " * StockOptionLevel (int, but likely represents a category)\n", " * TotalWorkingYears (int)\n", " * TrainingTimesLastYear (int)\n", " * WorkLifeBalance (int, but likely represents a category)\n", " * YearsAtCompany (int)\n", " * YearsInCurrentRole (int)\n", " * YearsSinceLastPromotion (int)\n", " * YearsWithCurrManager (int)\n", " * Department\n", " * Department (string)\n", " * EducationField\n", " * EducationField (string)\n", " * JobRole\n", " * JobRole (string)\n", "* edges\n", " * Employee-works_in->Department\n", " * Employee-has_education_level->EducationField\n", " * Employee-works_as->JobRole\n", " * JobRole-requires->EducationField\n", " * JobRole-part_of->Department\n", " \n", "In our graph, our data model will look like this:\n", "\n", "![image.png](attachment:image.png)\n", " \n", "Using this model, we will try to predict the following:\n", "\n", "* Attrition (node classification)\n", "* Possible job roles (link prediction)\n", "\n", "Before we can do that, we first need to create the CSV files and then upload them to S3, which is accomplished by running the two cells below." ] }, { "cell_type": "code", "execution_count": null, "id": "ce38e391", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import boto3\n", "import os\n", "import uuid\n", "from urllib.parse import urlparse\n", "\n", "# Create the output folder\n", "output_folder = 'data-upload'\n", "if os.path.exists(output_folder) == False:\n", " os.mkdir(output_folder)\n", "\n", "# Create the Employee dataframe\n", "empVdf = pd.DataFrame(columns=['~id', \n", " 'Age:Int',\n", " 'Attrition:String',\n", " 'BusinessTravel:String',\n", " 'DailyRate:Int',\n", " 'DistanceFromHome:String',\n", " 'Education:String',\n", " 'EmployeeCount:Int',\n", " 'EmployeeNumber:Int',\n", " 'EnvironmentSatisfaction:String',\n", " 'Gender:String',\n", " 'HourlyRate:Int',\n", " 'JobInvolvement:String',\n", " 'JobLevel:String',\n", " 'JobSatisfaction:String',\n", " 'MaritalStatus:String',\n", " 'MonthlyIncome:Int',\n", " 'MonthlyRate:Int',\n", " 'NumCompaniesWorked:Int',\n", " 'Over18:String',\n", " 'OverTime:String',\n", " 'PercentSalaryHike:Int',\n", " 'PerformanceRating:String',\n", " 'RelationshipSatisfaction:String',\n", " 'StandardHours:Int',\n", " 'StockOptionLevel:String',\n", " 'TotalWorkingYears:Int',\n", " 'TrainingTimesLastYear:Int',\n", " 'WorkLifeBalance:String',\n", " 'YearsAtCompany:Int',\n", " 'YearsInCurrentRole:Int',\n", " 'YearsSinceLastPromotion:Int',\n", " 'YearsWithCurrManager:Int',\n", " '~label'])\n", "empVdf['Age:Int'] = df['Age']\n", "empVdf['Attrition:String'] = df['Attrition']\n", "empVdf['BusinessTravel:String'] = df['BusinessTravel']\n", "empVdf['DailyRate:Int'] = df['DailyRate']\n", "empVdf['DistanceFromHome:String'] = df['DistanceFromHome']\n", "empVdf['Education:String'] = df['Education']\n", "empVdf['EmployeeCount:Int'] = df['EmployeeCount']\n", "empVdf['EmployeeNumber:Int'] = df['EmployeeNumber']\n", "empVdf['EnvironmentSatisfaction:String'] = df['EnvironmentSatisfaction']\n", "empVdf['Gender:String'] = df['Gender']\n", "empVdf['HourlyRate:Int'] = df['HourlyRate']\n", "empVdf['JobInvolvement:String'] = df['JobInvolvement']\n", "empVdf['JobLevel:String'] = df['JobLevel']\n", "empVdf['JobSatisfaction:String'] = df['JobSatisfaction']\n", "empVdf['MaritalStatus:String'] = df['MaritalStatus']\n", "empVdf['MonthlyIncome:Int'] = df['MonthlyIncome']\n", "empVdf['MonthlyRate:Int'] = df['MonthlyRate']\n", "empVdf['NumCompaniesWorked:Int'] = df['NumCompaniesWorked']\n", "empVdf['Over18:String'] = df['Over18']\n", "empVdf['OverTime:String'] = df['OverTime']\n", "empVdf['PercentSalaryHike:Int'] = df['PercentSalaryHike']\n", "empVdf['PerformanceRating:String'] = df['PerformanceRating']\n", "empVdf['RelationshipSatisfaction:String'] = df['RelationshipSatisfaction']\n", "empVdf['StandardHours:Int'] = df['StandardHours']\n", "empVdf['StockOptionLevel:String'] = df['StockOptionLevel']\n", "empVdf['TotalWorkingYears:Int'] = df['TotalWorkingYears']\n", "empVdf['TrainingTimesLastYear:Int'] = df['TrainingTimesLastYear']\n", "empVdf['WorkLifeBalance:String'] = df['WorkLifeBalance']\n", "empVdf['YearsAtCompany:Int'] = df['YearsAtCompany']\n", "empVdf['YearsInCurrentRole:Int'] = df['YearsInCurrentRole']\n", "empVdf['YearsSinceLastPromotion:Int'] = df['YearsSinceLastPromotion']\n", "empVdf['YearsWithCurrManager:Int'] = df['YearsWithCurrManager']\n", "\n", "empVdf['~label'] = 'Employee'\n", "empVdf['~id'] = np.arange(len(empVdf))\n", "empVdf['~id'] = empVdf['~id'].apply(lambda x: f\"emp{x}\")\n", "empVdf.to_csv(os.path.join(output_folder, 'vertex_emp.csv'), index=False)\n", "emp_map = pd.Series(empVdf['~id'].values,index=empVdf['EmployeeNumber:Int']).to_dict()\n", "\n", "# Create the Department dataframe\n", "deptVdf = pd.DataFrame(columns=['~id', \n", " 'Name:String', \n", " '~label'])\n", "deptVdf['Name:String'] = df['Department']\n", "\n", "deptVdf['~label'] = 'Department'\n", "deptVdf = deptVdf.drop_duplicates(subset=['Name:String'])\n", "deptVdf['~id'] = np.arange(len(deptVdf))\n", "deptVdf['~id'] = deptVdf['~id'].apply(lambda x: f\"dept{x}\")\n", "deptVdf.to_csv(os.path.join(output_folder, 'vertex_dept.csv'), index=False)\n", "dept_map = pd.Series(deptVdf['~id'].values,index=deptVdf['Name:String']).to_dict()\n", "\n", "# Create the JobRole dataframe\n", "roleVdf = pd.DataFrame(columns=['~id', \n", " 'Name:String', \n", " '~label'])\n", "roleVdf['Name:String'] = df['JobRole']\n", "\n", "roleVdf['~label'] = 'JobRole'\n", "roleVdf = roleVdf.drop_duplicates(subset=['Name:String'])\n", "roleVdf['~id'] = np.arange(len(roleVdf))\n", "roleVdf['~id'] = roleVdf['~id'].apply(lambda x: f\"role{x}\")\n", "roleVdf.to_csv(os.path.join(output_folder, 'vertex_role.csv'), index=False)\n", "role_map = pd.Series(roleVdf['~id'].values,index=roleVdf['Name:String']).to_dict()\n", "\n", "# Create the EducationField dataframe\n", "fieldVdf = pd.DataFrame(columns=['~id', \n", " 'Name:String', \n", " '~label'])\n", "fieldVdf['Name:String'] = df['EducationField']\n", "\n", "fieldVdf['~label'] = 'EducationField'\n", "fieldVdf = fieldVdf.drop_duplicates(subset=['Name:String'])\n", "fieldVdf['~id'] = np.arange(len(fieldVdf))\n", "fieldVdf['~id'] = fieldVdf['~id'].apply(lambda x: f\"field{x}\")\n", "fieldVdf.to_csv(os.path.join(output_folder, 'vertex_field.csv'), index=False)\n", "field_map = pd.Series(fieldVdf['~id'].values,index=fieldVdf['Name:String']).to_dict()\n", "\n", "# Construct our edges\n", "edge_emp_dept = pd.DataFrame(columns=['~id', '~from', '~to', '~label'])\n", "edge_emp_role = pd.DataFrame(columns=['~id', '~from', '~to', '~label'])\n", "edge_emp_field = pd.DataFrame(columns=['~id', '~from', '~to', '~label'])\n", "edge_role_dept = pd.DataFrame(columns=['~id', '~from', '~to', '~label'])\n", "edge_role_field = pd.DataFrame(columns=['~id', '~from', '~to', '~label'])\n", "edge_cnt = 1\n", "role_dept_list = []\n", "role_field_list = []\n", "\n", "edge_emp_dept_rows_list = [edge_emp_dept]\n", "edge_emp_role_rows_list = [edge_emp_role]\n", "edge_emp_field_rows_list = [edge_emp_field]\n", "edge_role_dept_rows_list = [edge_role_dept]\n", "edge_role_field_rows_list = [edge_role_field]\n", "\n", "for index, row in df.iterrows():\n", " emp = row['EmployeeNumber']\n", " emp_id = emp_map[emp]\n", " role = row['JobRole']\n", " role_id = role_map[role]\n", " field = row['EducationField']\n", " field_id = field_map[field]\n", " dept = row['Department']\n", " dept_id = dept_map[dept]\n", "\n", " edge_emp_dept_row_df = pd.DataFrame.from_dict({'~id': uuid.uuid4(),\n", " '~from': emp_id,\n", " '~to': dept_id,\n", " '~label': 'works_in'},\n", " orient='index').T\n", " edge_emp_dept_rows_list.append(edge_emp_dept_row_df)\n", " edge_emp_role_row_df = pd.DataFrame.from_dict({'~id': uuid.uuid4(),\n", " '~from': emp_id,\n", " '~to': role_id,\n", " '~label': 'works_as'},\n", " orient='index').T\n", " edge_emp_role_rows_list.append(edge_emp_role_row_df)\n", " edge_emp_field_row_df = pd.DataFrame.from_dict({'~id': uuid.uuid4(),\n", " '~from': emp_id,\n", " '~to': field_id,\n", " '~label': 'has_education_level'},\n", " orient='index').T\n", " edge_emp_field_rows_list.append(edge_emp_field_row_df)\n", " \n", " role_dept = f\"{role_id}-{dept_id}\"\n", " role_field = f\"{role_id}-{field_id}\"\n", " if role_dept not in role_dept_list:\n", " edge_role_dept_row_df = pd.DataFrame.from_dict({'~id': uuid.uuid4(),\n", " '~from': role_id,\n", " '~to': dept_id,\n", " '~label': 'part_of'},\n", " orient='index').T\n", " edge_role_dept_rows_list.append(edge_role_dept_row_df)\n", " #edge_role_dept = pd.concat([edge_role_dept, edge_role_dept_row_df], ignore_index=True)\n", " role_dept_list.append(role_dept)\n", " if role_field not in role_field_list:\n", " edge_role_field_row_df = pd.DataFrame.from_dict({'~id': uuid.uuid4(), '~from': role_id,\n", " '~to': field_id,\n", " '~label': 'requires'},\n", " orient='index').T\n", " edge_role_field_rows_list.append(edge_role_field_row_df)\n", " role_field_list.append(role_field)\n", " edge_cnt = edge_cnt + 1\n", "\n", "edge_emp_dept = pd.concat(edge_emp_dept_rows_list, ignore_index=True)\n", "edge_emp_role = pd.concat(edge_emp_role_rows_list, ignore_index=True)\n", "edge_emp_field = pd.concat(edge_emp_field_rows_list, ignore_index=True)\n", "edge_role_dept = pd.concat(edge_role_dept_rows_list, ignore_index=True)\n", "edge_role_field = pd.concat(edge_role_field_rows_list, ignore_index=True)\n", "\n", "edge_df = pd.concat([edge_emp_dept, edge_emp_role, edge_emp_field, edge_role_dept, edge_role_field])\n", "edge_df.to_csv(os.path.join(output_folder, 'edge.csv'), index=False)\n", "\n", "# Upload the CSV files to the S3 bucket\n", "s3 = boto3.client('s3')\n", "path = urlparse(s3_bucket_uri, allow_fragments=False)\n", "bucket = path.netloc\n", "file_path = path.path.lstrip('/').rstrip('/')\n", "for f in ['vertex_dept.csv', 'vertex_emp.csv', 'vertex_field.csv', 'vertex_role.csv', 'edge.csv']:\n", " s3.upload_file(os.path.join(output_folder, f), bucket, os.path.join(file_path, f))" ] }, { "cell_type": "code", "execution_count": null, "id": "727cf589", "metadata": {}, "outputs": [], "source": [ "%load -s {s3_bucket_uri} -f csv -p OVERSUBSCRIBE --run" ] }, { "cell_type": "markdown", "id": "6c8fc4e4", "metadata": {}, "source": [ "## Inspecting our HR Data" ] }, { "cell_type": "markdown", "id": "1f79e58f", "metadata": {}, "source": [ "First, let's see how many unique vertices and edges we have." ] }, { "cell_type": "code", "execution_count": null, "id": "772469ce", "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "g.V().groupCount().by(label).unfold()" ] }, { "cell_type": "code", "execution_count": null, "id": "54685984", "metadata": { "scrolled": true }, "outputs": [], "source": [ "%%gremlin\n", "g.E().groupCount().by(label).unfold()" ] }, { "cell_type": "markdown", "id": "b946e19e", "metadata": {}, "source": [ "Next, let's visualize the entire employee list and the job role. " ] }, { "cell_type": "code", "execution_count": null, "id": "dd025190", "metadata": {}, "outputs": [], "source": [ "role_emp_node_labels = '{\"JobRole\":\"Name\",\"Department\":\"Name\",\"EducationField\":\"Name\"}'" ] }, { "cell_type": "code", "execution_count": null, "id": "c02fca33", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "g.V().hasLabel('Employee').outE('works_as').inV().path().by(elementMap())" ] }, { "cell_type": "markdown", "id": "0523ab36", "metadata": {}, "source": [ "To make the graph a little more intuitive, let's ask a more specific question: \n", "\n", "**Which job roles tend to have the most attrition?**\n", "\n", "We could easily change this to look by department or by education." ] }, { "cell_type": "code", "execution_count": null, "id": "513f1c9f", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "g.V().hasLabel('Employee').has('Attrition', 'Yes').outE('works_as').inV().path().by(elementMap())" ] }, { "cell_type": "markdown", "id": "2ac8888d", "metadata": {}, "source": [ "Now let's look at the relationship between departments and job roles." ] }, { "cell_type": "code", "execution_count": null, "id": "39e89991", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels -l 20\n", "g.V().hasLabel('JobRole').outE('part_of').inV().path().by(elementMap())" ] }, { "cell_type": "markdown", "id": "51662ad5", "metadata": {}, "source": [ "Our last visualization shows the relationship between job roles and education." ] }, { "cell_type": "code", "execution_count": null, "id": "45085c06", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels -l 20\n", "g.V().hasLabel('JobRole').outE('requires').inV().path().by(elementMap())" ] }, { "cell_type": "markdown", "id": "24976663", "metadata": {}, "source": [ "## Using Neptune ML\n", "\n", "As a reminder, we will try to predict the following:\n", "\n", "* Attrition - since this is a categorical (as opposed to numerical) property of the `Employee` vertex, we will use a [Node Classification](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning.html#machine-learning-capabilities) task.\n", "* Career Progression - Predicting career progression in our model is a matter of predicting a relationship between an `Employee` and a `JobRole` so we will use a [Link Prediction](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning.html#machine-learning-capabilities) task." ] }, { "cell_type": "markdown", "id": "6310f7ed", "metadata": {}, "source": [ "### Predicting attrition using Node Classification\n", "\n", "Node classification is a task supported by Neptune ML that supports predicting the categorical (i.e. string) value from a set of options included in the training data. In our example, we will use the `Attrition` field as the 'Yes/No' categorical field on which we perform our prediction tasks.\n", "\n", "Before we can go about building our model, we'll start by dropping the field from the first 50 employees, in order to have some values to predict on." ] }, { "cell_type": "code", "execution_count": null, "id": "c5f78857", "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "\n", "g.V().hasLabel('Employee').limit(50).properties('Attrition').drop()" ] }, { "cell_type": "markdown", "id": "b8295567", "metadata": {}, "source": [ "Now that we have prepared our dataset, the first step in the process of building our Attrition Prediction model is to export the data and configuration. \n", "\n", "**Note:** The details of how this is configured and the options available are covered in the [Neptune ML documentation](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-data-export.html#machine-learning-params) as well as the [Neptune-ML-01-Introduction-to-Node-Classification-Gremlin notebook](https://github.com/aws/graph-notebook/blob/main/src/graph_notebook/notebooks/04-Machine-Learning/Neptune-ML-01-Introduction-to-Node-Classification-Gremlin.ipynb), so we will not cover the detail here.\n", "\n", "For our `Attrition` prediction model, we have specified our targets as the `Attrition` property of the `Employee` vertices as shown here:\n", "```\n", "\"targets\": [\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"Attrition\"\n", " }\n", "```\n", "\n", "In addition, we have specified several details about features within our data set in order to best represent the data in the model." ] }, { "cell_type": "code", "execution_count": null, "id": "daaa5659", "metadata": {}, "outputs": [], "source": [ "s3_bucket_uri = f's3://{bucket}/empml'\n", "export_params={ \n", "\"command\": \"export-pg\", \n", "\"params\": { \"endpoint\": neptune_ml.get_host(),\n", " \"profile\": \"neptune_ml\",\n", " \"useIamAuth\": neptune_ml.get_iam(),\n", " \"cloneCluster\": False\n", " }, \n", "\"outputS3Path\": f'{s3_bucket_uri}/neptune-export',\n", "\"additionalParams\": {\n", " \"neptune_ml\": {\n", " \"targets\": [\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"Attrition\",\n", " \"type\": \"classification\"\n", " }\n", " ],\n", " \"features\": [\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"Age\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [17, 65],\n", " \"num_buckets\": 10\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"BusinessTravel\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"DailyRate\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [100, 2000],\n", " \"num_buckets\": 10\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"NumCompaniesWorked\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 15],\n", " \"num_buckets\": 16\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"Education\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"JobInvolvement\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"JobLevel\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"JobSatisfaction\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"PercentSalaryHike\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [5, 40],\n", " \"num_buckets\": 10\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"PerformanceRating\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"WorkLifeBalance\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsAtCompany\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 45],\n", " \"num_buckets\": 10\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsInCurrentRole\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 20],\n", " \"num_buckets\": 10\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsSinceLastPromotion\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 20],\n", " \"num_buckets\": 20\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsWithCurrManager\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 20],\n", " \"num_buckets\": 20\n", " }\n", " ]\n", " }\n", " },\n", "\"jobSize\": \"large\"}" ] }, { "cell_type": "markdown", "id": "029b787f", "metadata": {}, "source": [ "With our configuration ready, we can now export our data and configuration, which will take several minutes, using the command below." ] }, { "cell_type": "code", "execution_count": null, "id": "d609ee8d", "metadata": {}, "outputs": [], "source": [ "%%neptune_ml export start --export-url {neptune_ml.get_export_service_host()} --export-iam --wait --store-to export_results\n", "${export_params}" ] }, { "cell_type": "markdown", "id": "097e4da1", "metadata": {}, "source": [ "The next step in building our Attrition Prediction model is to run the [data processing](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-on-graphs-processing.html) job, which takes our exported data and transforms it into the form used by the [Deep Graph Library](https://www.dgl.ai/). Run the next two cells to create the training configuration and then to run the training job, which takes 5-10 minutes." ] }, { "cell_type": "code", "execution_count": null, "id": "8afa9b4b", "metadata": {}, "outputs": [], "source": [ "# The training_job_name can be set to a unique value below, otherwise one will be auto-generated\n", "training_job_name=neptune_ml.get_training_job_name('node-classification')\n", "\n", "processing_params = f\"\"\"\n", "--config-file-name training-data-configuration.json\n", "--job-id {training_job_name} \n", "--s3-input-uri {export_results['outputS3Uri']} \n", "--s3-processed-uri {str(s3_bucket_uri)}/preloading \"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "e8bbd720", "metadata": {}, "outputs": [], "source": [ "%neptune_ml dataprocessing start --wait --store-to processing_results {processing_params}" ] }, { "cell_type": "markdown", "id": "62e140e8", "metadata": {}, "source": [ "Once the data processing complete, the next step is to run the [model training](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-on-graphs-model-training.html) which will create the DGL model for our Attrition Prediction task. Run the next two cells to create the training configuration and then to run the training job, which takes 10-15 minutes." ] }, { "cell_type": "code", "execution_count": null, "id": "213363a6", "metadata": {}, "outputs": [], "source": [ "training_params=f\"\"\"\n", "--job-id {training_job_name} \n", "--data-processing-id {training_job_name} \n", "--instance-type ml.m5.2xlarge\n", "--s3-output-uri {str(s3_bucket_uri)}/training \"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "02586fb0", "metadata": {}, "outputs": [], "source": [ "%neptune_ml training start --wait --store-to training_results {training_params}" ] }, { "cell_type": "markdown", "id": "9138a991", "metadata": {}, "source": [ "Now that we have trained our model, the final step is to create a Sagemaker-hosted [inference endpoint](https://docs.aws.amazon.com/neptune/latest/userguide/machine-learning-on-graphs-inference-endpoint.html) that will allow us to query our prediction model. This step should take 5-10 minutes." ] }, { "cell_type": "code", "execution_count": null, "id": "f05c4544", "metadata": {}, "outputs": [], "source": [ "endpoint_params=f\"\"\"\n", "--id {training_job_name}\n", "--model-training-job-id {training_job_name}\"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "7a4779b8", "metadata": {}, "outputs": [], "source": [ "%neptune_ml endpoint create --wait --store-to endpoint_results {endpoint_params}" ] }, { "cell_type": "code", "execution_count": null, "id": "cb0ceb26", "metadata": {}, "outputs": [], "source": [ "endpoint = endpoint_results['endpoint']['name']" ] }, { "cell_type": "markdown", "id": "91bd6b83", "metadata": {}, "source": [ "### Testing our Attrition prediction task\n", "\n", "Now that we have built our model and have it hosted, let's test it out and see how we can use this to predict potential employee attrition.\n", "\n", "#### Which employees are likely to leave?" ] }, { "cell_type": "code", "execution_count": null, "id": "6107d4ba", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "g.with(\"Neptune#ml.endpoint\", \"${endpoint}\").\n", " V().\n", " hasLabel('Employee').\n", " hasNot('Attrition').\n", " project('EmployeeNumber', 'Attrition').\n", " by('EmployeeNumber').\n", " by(\n", " properties(\"Attrition\").with(\"Neptune#ml.classification\").value()).\n", " group().by('Attrition').by('EmployeeNumber').\n", " unfold()" ] }, { "cell_type": "markdown", "id": "05c83b70", "metadata": {}, "source": [ "#### Of the employees without a value, how many are going to leave versus stay?" ] }, { "cell_type": "code", "execution_count": null, "id": "385cf507", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "g.with(\"Neptune#ml.endpoint\", \"${endpoint}\").\n", " V().\n", " hasLabel('Employee').\n", " hasNot('Attrition').\n", " project('EmployeeNumber', 'Attrition').\n", " by('EmployeeNumber').\n", " by(\n", " properties(\"Attrition\").with(\"Neptune#ml.classification\").value()).\n", " groupCount().by('Attrition').\n", " unfold()" ] }, { "cell_type": "markdown", "id": "fa524674", "metadata": {}, "source": [ "#### What JobRoles have the most employees predicted to leave?" ] }, { "cell_type": "code", "execution_count": null, "id": "d2b122f9", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "g.with(\"Neptune#ml.endpoint\", \"${endpoint}\").\n", " V().\n", " hasLabel('Employee').\n", " hasNot('Attrition').as('e').\n", " properties(\"Attrition\").with(\"Neptune#ml.classification\").\n", " value().\n", " is('Yes').\n", " select('e').\n", " out('works_as').\n", " values('Name').\n", " groupCount().\n", " unfold().\n", " order().by(values, desc)" ] }, { "cell_type": "markdown", "id": "085381c0", "metadata": {}, "source": [ "## Predicting career progression with link prediction\n", "\n", "Link prediction is a task supported by Neptune ML that supports predicting edges (or relationships) between two vertices in the graph. In our example, we will be predicting `works_as` relationships between an `Employee` and a `JobRole`.\n", "\n", "Before we can go about building our model, we'll start by dropping the `works_as` edge from 50 employees, in order to have some values to predict on." ] }, { "cell_type": "code", "execution_count": null, "id": "aa3c748d", "metadata": {}, "outputs": [], "source": [ "%%gremlin\n", "g.V().hasLabel('Employee').limit(50).outE('works_as').drop()" ] }, { "cell_type": "markdown", "id": "8c4da2be", "metadata": {}, "source": [ "Now that we have prepared our dataset, we are going to follow the same steps we used in our Attrition Prediction model is to export the data and configuration, prepare the data, train the model, and create the inference endpoint. We are not going to cover each of these steps in detail so run the next six cells below in order which will take about 30-45 minutes total." ] }, { "cell_type": "code", "execution_count": null, "id": "4a1f73b5", "metadata": {}, "outputs": [], "source": [ "s3_bucket_uri = f's3://{bucket}/empmllink'\n", "export_params_link ={ \n", "\"command\": \"export-pg\", \n", "\"params\": { \"endpoint\": neptune_ml.get_host(),\n", " \"profile\": \"neptune_ml\",\n", " \"useIamAuth\": neptune_ml.get_iam(),\n", " \"cloneCluster\": False\n", " }, \n", "\"outputS3Path\": f'{s3_bucket_uri}/neptune-export-link',\n", "\"additionalParams\": {\n", " \"neptune_ml\": {\n", " \"features\": [\n", " {\n", " \"node\": \"JobRole\",\n", " \"property\": \"Name\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"Age\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [17, 65],\n", " \"num_buckets\": 10\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"BusinessTravel\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"DailyRate\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [100, 2000],\n", " \"num_buckets\": 10\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"NumCompaniesWorked\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 15],\n", " \"num_buckets\": 3\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"Education\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"JobInvolvement\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"JobLevel\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"JobSatisfaction\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"PercentSalaryHike\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [5, 40],\n", " \"num_buckets\": 3\n", " },\n", " {\n", " \"node\": \"employee\",\n", " \"property\": \"PerformanceRating\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"WorkLifeBalance\",\n", " \"type\": \"category\"\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsAtCompany\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 45],\n", " \"num_buckets\": 3\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsInCurrentRole\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 20],\n", " \"num_buckets\": 3\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsSinceLastPromotion\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 20],\n", " \"num_buckets\": 3\n", " },\n", " {\n", " \"node\": \"Employee\",\n", " \"property\": \"YearsWithCurrManager\",\n", " \"type\": \"bucket_numerical\",\n", " \"range\" : [0, 20],\n", " \"num_buckets\": 3\n", " }\n", " ]\n", " }\n", " },\n", "\"jobSize\": \"large\"}" ] }, { "cell_type": "code", "execution_count": null, "id": "87cbbc58", "metadata": {}, "outputs": [], "source": [ "%%neptune_ml export start --export-url {neptune_ml.get_export_service_host()} --export-iam --wait --store-to export_results\n", "${export_params_link}" ] }, { "cell_type": "code", "execution_count": null, "id": "f857ef12", "metadata": {}, "outputs": [], "source": [ "# The training_job_name can be set to a unique value below, otherwise one will be auto generated\n", "training_job_name_link=neptune_ml.get_training_job_name('link-prediction')\n", "\n", "processing_params_link = f\"\"\"\n", "--config-file-name training-data-configuration.json\n", "--job-id {training_job_name_link} \n", "--s3-input-uri {export_results['outputS3Uri']} \n", "--s3-processed-uri {str(s3_bucket_uri)}/preloading_link \"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "7f79cc94", "metadata": {}, "outputs": [], "source": [ "%neptune_ml dataprocessing start --wait --store-to processing_results {processing_params_link}" ] }, { "cell_type": "code", "execution_count": null, "id": "ebc5319b", "metadata": {}, "outputs": [], "source": [ "training_params_link=f\"\"\"\n", "--job-id {training_job_name_link} \n", "--data-processing-id {training_job_name_link} \n", "--instance-type ml.m5.2xlarge\n", "--s3-output-uri {str(s3_bucket_uri)}/training_link \"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "70d47eaf", "metadata": {}, "outputs": [], "source": [ "%neptune_ml training start --wait --store-to training_results {training_params_link}" ] }, { "cell_type": "code", "execution_count": null, "id": "55c587e8", "metadata": {}, "outputs": [], "source": [ "endpoint_params_link=f\"\"\"\n", "--job-id {training_job_name_link} \n", "--model-job-id {training_job_name_link}\"\"\"" ] }, { "cell_type": "code", "execution_count": null, "id": "c35f339e", "metadata": {}, "outputs": [], "source": [ "%neptune_ml endpoint create --wait --store-to endpoint_results {endpoint_params_link}" ] }, { "cell_type": "code", "execution_count": null, "id": "4213424b", "metadata": {}, "outputs": [], "source": [ "lp_endpoint= endpoint_results['endpoint']['name']" ] }, { "cell_type": "markdown", "id": "915e6616", "metadata": {}, "source": [ "### Testing our Career Progression prediction task\n", "\n", "Now that we have built our model and have it hosted, let's test it out and see how we can use this to predict potential jobs for an employee.\n", "\n", "#### What is the most likely job for an employee?" ] }, { "cell_type": "code", "execution_count": null, "id": "bababf71", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "\n", "g.with(\"Neptune#ml.endpoint\",\"${lp_endpoint}\").\n", " V().hasLabel('Employee').where(outE('works_as').count().is(0)).\n", " out('works_as').\n", " with(\"Neptune#ml.prediction\").hasLabel('JobRole').path().by(elementMap())\n", " " ] }, { "cell_type": "markdown", "id": "b4091806", "metadata": {}, "source": [ "#### Which job role has the most predicted employees?" ] }, { "cell_type": "code", "execution_count": null, "id": "8bc09bd7", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "\n", "g.with(\"Neptune#ml.endpoint\",\"${lp_endpoint}\").\n", " V().hasLabel('Employee').out('works_as').\n", " with(\"Neptune#ml.prediction\").hasLabel('JobRole').groupCount().by('Name').unfold()" ] }, { "cell_type": "code", "execution_count": null, "id": "f1f220c2", "metadata": {}, "outputs": [], "source": [ "%%gremlin -d $role_emp_node_labels\n", "\n", "g.with(\"Neptune#ml.endpoint\",\"${lp_endpoint}\").with(\"Neptune#ml.limit\",5).\n", " V().hasLabel('Employee').where(outE('works_as').count().is(0)).\n", " out('works_as').\n", " with(\"Neptune#ml.prediction\").hasLabel('JobRole').path().by(elementMap())" ] }, { "cell_type": "markdown", "id": "4fb58911", "metadata": {}, "source": [ "# Cleaning up \n", "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 using Neptune ML for this exercise, and would like to avoid future costs, run the cell below to delete the inference endpoint." ] }, { "cell_type": "code", "execution_count": null, "id": "81cc82ec", "metadata": {}, "outputs": [], "source": [ "neptune_ml.delete_endpoint(training_job_name)\n", "neptune_ml.delete_endpoint(training_job_name_link)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.13" } }, "nbformat": 4, "nbformat_minor": 5 }