{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Create an Active Learning Workflow using Amazon SageMaker Ground Truth" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "### Using this Notebook\n", "\n", "Please set the kernel to *Python 3* and image to *TensorFlow 2.10.0 Python 3.9 CPU Optimized* when running this notebook. Select instance type as `ml.t3.medium`\n", "\n", "\n", "**This notebook is intended to be used along with the lab instructions provided [Create an Active Learning Workflow using Amazon SageMaker Ground Truth](https://studio.us-east-1.prod.workshops.aws/preview/60859d90-30c0-42ae-af49-64805eafc292/builds/7121d11b-9cd2-4d64-9355-aca7afb55232/en-US/module-07-advanced-topics/active-learning-workflow)** . \n", "\n", "This notebook has been adapted from the AWS blog post with additional modifications for use in this lab [Bring your own model for SageMaker labeling workflows with Active Learning](https://aws.amazon.com/blogs/machine-learning/bring-your-own-model-for-amazon-sagemaker-labeling-workflows-with-active-learning/)**\n", "\n", "In this notebook, we will also leverage [AWS SAM](https://aws.amazon.com/serverless/sam/) to manage and deploy AWS CloudFormation templates that will be used for Active Labeling workflow. The AWS CloudFormation template will help create AWS Lambda functions, AWS Step functions, and IAM roles that will be used for active learning workflow.\n", "\n", "While following along with this blog post, we recommend that you leave most of the cells unmodified. However, the notebook will indicate where you can modify variables to create the resources needed for a custom labeling job.\n", "\n", "If you plan to customize the Ground Truth labeling job request configuration below, you will also need the resources required to create a labeling job. For more information, see [Use Amazon SageMaker Ground Truth for Data Labeling](https://docs.aws.amazon.com/sagemaker/latest/dg/sms.html). \n", "\n", "Run the code cells in this notebook to configure a Labeling Job request in JSON format. This request JSON can be used in an active learning workflow and will determine how your labeling job task appears to human workers. \n", "\n", "To customize this notebook, you will need to modify the the cells below and configure the Ground Truth labeling job request (`human_task_config`) to meet your requirements. To learn how to create a Ground Truth labeling job using the Amazon SageMaker API, see [CreateLabelingJob](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateLabelingJob.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Prerequisites\n", "\n", "Please complete the initial prerequisites mentioned in the lab instructions [Create an Active Learning Workflow using Amazon SageMaker Ground Truth](https://studio.us-east-1.prod.workshops.aws/preview/60859d90-30c0-42ae-af49-64805eafc292/builds/7121d11b-9cd2-4d64-9355-aca7afb55232/en-US/module-07-advanced-topics) before proceeding with the cells in this notebook . After completing the prerequisites, you should have: \n", "- ARN of your private workforce who will perform human annotation\n", "- Labeling portal sign-in URL for your private workforce to log in" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Setup AWS SAM " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install --upgrade setuptools aws-sam-cli" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Restart your kernel for this notebook to use updated packages " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%html\n", "\n", "
Restart your kernel for this notebook to use update package.
\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "#### Setup SageMaker role, region and S3 bucket" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "import os, sys, json, sagemaker, pandas as pd, boto3, numpy as np\n", "from sagemaker import get_execution_role\n", "from sagemaker.tensorflow import TensorFlow\n", "\n", "sess = sagemaker.Session()\n", "\n", "role = get_execution_role()\n", "region = sess.boto_session.region_name\n", "account = sess.boto_session.client(\"sts\").get_caller_identity()[\"Account\"]\n", "smclient = boto3.Session().client('sagemaker')\n", "bucket = sess.default_bucket()\n", "stack_name= \"active-learning-stack\"\n", "key = \"sagemaker-byoal\"\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Build and deploy AWS CloudFormation template using AWS SAM" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Update template.yaml . The template.yaml is leverage by AWS SAM to build out the backend infrastructure consisting of State machine, helper utilities and AWS Lambda functions\n", "\n", "Update configurations variables for Active Learning process :\n", "\n", "- `byom` : (Bring your own model) . This parameter determines whether the Lambda function used for configuring the training parameters returns the Amazon Blazing Text algorithm or your own custom model image . This is set to to `false` because we are using Amazon BlazingText algorithm\n", "- `byomimage` : Your own custom model image . Setting this parameter to NotApplicable because we are using Amazon Blazing Text algorithm\n", "- `sagemaker_program` : This parameter determines the training script to be used for training your model . In this case we are using the native BlazingText training script that comes as part of the BlazingText docker image, hence setting this parameter to NotApplicable\n", "- `sagemaker_submit_directory` : The directory contains the training source code or other dependencies aside from the entry point file . Because we are not using custom ML models or pretrained models, setting this parameter to NotApplicable\n", "- `pretrain_model` : This parameter determines if the active learning process is using pretrained models or not . This is set to to `false` because we are using Amazon BlazingText algorithm .\n", "- `pretrain_algo_train_repo` : This parameter refers to the training image URI of our pretrained BERT algorithm . Because we are not using a pretrained BERT model, setting this parameter to NotApplicable\n", "- `base_model_uri` : The URI of the pretrained BERT models which will be fine tuned with propriatary data. Because we are not using a pretrained BERT model, setting this parameter to NotApplicable\n", "- `pretrain_algo_inference_repo` : This parameter refers to the inference image URI of our pretrained BERT algorithm . Because we are not using pretrained BERT model, setting this parameter to NotApplicable\n", "\n", "\n", "Update Batch Strategy to `MultiRecord` in the `CreateTransformJob` step of the ActiveLearning-active-learning-stack state machine.\n", "This is because the the Amazon SageMaker BlazingText algorithm used in this notebook supports `MultiRecord`\n", "- `BatchStrategy `: `MultiRecord`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "%%bash\n", "\n", "cd src\n", "\n", "# Update byom value to true \n", "sed -i \"0,/^\\([[:space:]]*byom: *\\).*/s//\\1\"false\"/\" template.yaml\n", "\n", "#Update byomimage value to your own custom ML image\n", "sed -i \"0,/^\\([[:space:]]*byomimage: *\\).*/s//\\1\"NotApplicable\"/\" template.yaml\n", "\n", "#Update Training script for PreTrained model to Not Applicable\n", "sed -i \"0,/^\\([[:space:]]*sagemaker_program: *\\).*/s//\\1\"NotApplicable\"/\" template.yaml\n", "\n", "#Update Pretrained Model flag to False\n", "sed -i \"0,/^\\([[:space:]]*pretrain_model: *\\).*/s//\\1\"false\"/\" template.yaml\n", "\n", "#Update Pretrained algorithm training repository to NotApplicable\n", "sed -i \"0,/^\\([[:space:]]*pretrain_algo_train_repo: *\\).*/s//\\1\"NotApplicable\"/\" template.yaml\n", "\n", "#Update SageMaker Submit Directory to NotApplicable\n", "sed -i \"0,/^\\([[:space:]]*sagemaker_submit_directory: *\\).*/s//\\1\"NotApplicable\"/\" template.yaml\n", "\n", "#Update base pretrained model URI to NotApplicable\n", "sed -i \"0,/^\\([[:space:]]*base_model_uri: *\\).*/s//\\1\"NotApplicable\"/\" template.yaml\n", "\n", "#Updated Pretrained algorithm inference reposiistory to NotApplicable\n", "sed -i \"0,/^\\([[:space:]]*pretrain_algo_inference_repo: *\\).*/s//\\1\"NotApplicable\"/\" template.yaml\n", "\n", "#Update BatchStrategy to MultiRecord\n", "sed -i 's/SingleRecord/MultiRecord/g' template.yaml\n", "\n", "cat template.yaml" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash -s \"$region\" \"$stack_name\" \"$bucket\"\n", "\n", "# Change directory to src folder\n", "cd src\n", "\n", "#Delete SAM build folder to clean up any exisiting SAM templates\n", "rm -rf .aws-sam \n", "\n", "#Build AWS SAM artifacts to deploy AWS Cloudformation template\n", "sam build\n", "\n", "#Deploy AWS SAM template\n", "sam deploy --region ${1} --stack-name ${2} --s3-bucket ${3} --capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Verify aws-sam-cli" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "\n", "sam --version" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "#### Prepare labeling input manifest file" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "We will create an input manifest file for our active learning workflow using the newsCorpora.csv file from the [UCI News Dataset](https://archive.ics.uci.edu/ml/datasets/News+Aggregator). This dataset contains a list of about 420,000 articles that fall into one of four categories: Business (b), Science & Technology (t), Entertainment (e) and Health & Medicine (m). We will randomly choose 10,000 articles from that file to create our dataset.\n", "\n", "For the active learning loop to start, 20% of the data must be labeled. To quickly test the active learning component, we will include 20% (`labeled_count`) of the original labels provided in the dataset in our input manifest. We use this partially-labeled dataset as the input to the active learning loop." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "! wget -nc https://archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip --no-check-certificate && unzip -o NewsAggregatorDataset.zip" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "column_names = [\"TITLE\", \"URL\", \"PUBLISHER\", \"CATEGORY\", \"STORY\", \"HOSTNAME\", \"TIMESTAMP\"]\n", "manifest_file = \"partially-labeled.manifest\"\n", "news_data_all = pd.read_csv(\"newsCorpora.csv\", names=column_names, header=None, delimiter=\"\\t\")\n", "news_data = news_data_all.sample(n=10000, random_state=42)\n", "news_data = news_data[[\"TITLE\", \"CATEGORY\"]]" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "We will clean our data set using *pandas*." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "news_data[\"TITLE\"].replace('\"', \"\", inplace=True, regex=True)\n", "news_data[\"TITLE\"].replace(\"[^\\w\\s]\", \"\", inplace=True, regex=True)\n", "news_data[\"TITLE\"] = news_data[\"TITLE\"].str.split(\"\\n\").str[0]\n", "news_data[\"CATEGORY\"] = news_data[\"CATEGORY\"].astype(\"category\").cat.codes" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "fixed = news_data[\"TITLE\"].str.lower().replace('\"', \"\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "news_data.to_csv(\"news_subset.csv\", index=False)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "The following cell will create our partially-labeled input manifest file, and push it to our S3 bucket. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "import json\n", "\n", "total = len(news_data)\n", "labeled_count = int(total / 5) # 20% of the dataset is labeled.\n", "label_map = {\n", " \"b\": \"Business\",\n", " \"e\": \"Entertainment\",\n", " \"m\": \"Health & Medicine\",\n", " \"t\": \"Science and Technology\",\n", "}\n", "labeled_series = pd.Series(\n", " data=news_data.iloc[:labeled_count].TITLE.values,\n", " index=news_data.iloc[:labeled_count].CATEGORY.values,\n", ")\n", "annotation_metadata = b\"\"\"{ \"category-metadata\" : { \"confidence\": 1.0, \"human-annotated\": \"yes\", \"type\": \"groundtruth/text-classification\"} }\"\"\"\n", "annotation_metadata_dict = json.loads(annotation_metadata)\n", "with open(manifest_file, \"w\") as outfile:\n", " for items in labeled_series.items():\n", " labeled_record = dict()\n", " labeled_record[\"source\"] = items[1]\n", " labeled_record[\"category\"] = int(items[0])\n", " labeled_record.update(annotation_metadata_dict)\n", " outfile.write(json.dumps(labeled_record) + \"\\n\")\n", "\n", "unlabeled_series = pd.Series(\n", " data=news_data.iloc[labeled_count:].TITLE.values,\n", " index=news_data.iloc[labeled_count:].CATEGORY.values,\n", ")\n", "with open(manifest_file, \"a\") as outfile:\n", " for items in unlabeled_series.items():\n", " outfile.write('{\"source\":\"' + items[1] + '\"}\\n')\n", "\n", "boto3.resource(\"s3\").Bucket(bucket).upload_file(manifest_file, key + \"/\" + manifest_file)\n", "manifest_file_uri = \"s3://{}/{}\".format(bucket, key + \"/\" + manifest_file)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "# Use s3 client to upload relevant json strings to s3.\n", "s3_client = boto3.client(\"s3\")" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "This cell will specify the labels that workers will use to categorize the articles. To customize your labeling job, add your own labels here. To learn more, see [LabelCategoryConfigS3Uri](https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_CreateLabelingJob.html#sagemaker-CreateLabelingJob-request-LabelCategoryConfigS3Uri)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "label_file_name = \"class_labels.json\"\n", "label_file = \"\"\"{\n", " \"document-version\": \"2018-11-28\",\n", " \"labels\": [\n", " {\n", " \"label\": \"Business\"\n", " },\n", " {\n", " \"label\": \"Entertainment\"\n", " },\n", " {\n", " \"label\": \"Health & Medicine\"\n", " },\n", " {\n", " \"label\": \"Science and Technology\"\n", " }\n", " ]\n", "}\"\"\"\n", "\n", "s3_client.put_object(Body=label_file, Bucket=bucket, Key=key + \"/\" + label_file_name)\n", "label_file_uri = \"s3://{}/{}\".format(bucket, key + \"/\" + label_file_name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Prepare custom worker task template " ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false } }, "source": [ "The following cell will specify our custom worker task template. This template will configure the UI that workers will see when they open our text classification labeling job tasks. To learn how to customize this cell, see [Creating your custom labeling task template](https://docs.aws.amazon.com/sagemaker/latest/dg/sms-custom-templates-step2.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "tags": [] }, "outputs": [], "source": [ "template_file_name = \"instructions.template\"\n", "template_file = r\"\"\"\n", "\n", "Example Business title:
US open: Stocks fall after Fed official hints at accelerated tapering.
\n",
"
Example Entertainment title:
CBS negotiates three more seasons for The Big Bang Theory
\n",
"
Example Health & Medicine title:
Blood Test Could Predict Alzheimer's. Good News?
\n",
"
Example Science and Technology (t) title:
Elephants tell human friend from foe by voice.
\n",
"