Compare commits

..
Author SHA1 Message Date
Andrew Ferlitsch b91306fd6a test: fixes for testing 2022-02-16 19:07:16 +00:00
Andrew Ferlitsch 22731cae8a test: fixes for testing 2022-02-16 19:06:00 +00:00
369 changed files with 34227 additions and 204762 deletions
@@ -1,2 +1 @@
ratemate
google-cloud-aiplatform
+25 -29
View File
@@ -1,49 +1,45 @@
from typing import List
from ratemate import RateLimit
from resource_cleanup_manager import (
DatasetResourceCleanupManager,
ModelResourceCleanupManager,
EndpointResourceCleanupManager,
ResourceCleanupManager,
ResourceCleanupManager,
DatasetResourceCleanupManager,
EndpointResourceCleanupManager,
ModelResourceCleanupManager,
)
rate_limit = RateLimit(max_count=25, per=60, greedy=False)
def run_cleanup_managers(managers: List[ResourceCleanupManager], is_dry_run: bool):
for manager in managers:
type_name = manager.type_name
for manager in managers:
type_name = manager.type_name
print(f"Fetching {type_name}'s...")
resources = manager.list()
print(f"Found {len(resources)} {type_name}'s")
for resource in resources:
try:
if not manager.is_deletable(resource):
continue
print(f"Fetching {type_name}'s...")
resources = manager.list()
print(f"Found {len(resources)} {type_name}'s")
for resource in resources:
if not manager.is_deletable(resource):
continue
if is_dry_run:
resource_name = manager.resource_name(resource)
print(f"Will delete '{type_name}': {resource_name}")
else:
rate_limit.wait() # wait before deleting
manager.delete(resource)
except Exception as exception:
print(exception)
if is_dry_run:
resource_name = manager.resource_name(resource)
print(f"Will delete '{type_name}': {resource_name}")
else:
try:
manager.delete(resource)
except Exception as exception:
print(exception)
print("")
print("")
is_dry_run = False
if is_dry_run:
print("Starting cleanup in dry run mode...")
print("Starting cleanup in dry run mode...")
# List of all cleanup managers
managers = [
DatasetResourceCleanupManager(),
EndpointResourceCleanupManager(),
ModelResourceCleanupManager(), # ModelResourceCleanupManager must follow EndpointResourceCleanupManager due to deployed models blocking model deletion.
DatasetResourceCleanupManager(),
EndpointResourceCleanupManager(),
ModelResourceCleanupManager(),
]
run_cleanup_managers(managers=managers, is_dry_run=is_dry_run)
@@ -1,9 +1,8 @@
import abc
from typing import Any, Type
from google.cloud import aiplatform
from google.cloud.aiplatform import base
from typing import Any
from proto.datetime_helpers import DatetimeWithNanoseconds
from google.cloud.aiplatform import base
# If a resource was updated within this number of seconds, do not delete.
RESOURCE_UPDATE_BUFFER_IN_SECONDS = 60 * 60 * 8
@@ -41,7 +40,7 @@ class ResourceCleanupManager(abc.ABC):
# Check that it wasn't created too recently, to prevent race conditions
if time_difference <= RESOURCE_UPDATE_BUFFER_IN_SECONDS:
print(
f"Skipping '{resource}' due to update_time being '{time_difference}', which is less than '{RESOURCE_UPDATE_BUFFER_IN_SECONDS}'."
f"Skipping '{resource}' due update_time being '{time_difference}', which is less than '{RESOURCE_UPDATE_BUFFER_IN_SECONDS}'."
)
return False
@@ -51,7 +50,7 @@ class ResourceCleanupManager(abc.ABC):
class VertexAIResourceCleanupManager(ResourceCleanupManager):
@property
@abc.abstractmethod
def vertex_ai_resource(self) -> Type[base.VertexAiResourceNounWithFutureManager]:
def vertex_ai_resource(self) -> base.VertexAiResourceNounWithFutureManager:
pass
@property
@@ -61,9 +60,7 @@ class VertexAIResourceCleanupManager(ResourceCleanupManager):
def list(self) -> Any:
return self.vertex_ai_resource.list()
def resource_name(
self, resource: Type[base.VertexAiResourceNounWithFutureManager]
) -> str:
def resource_name(self, resource: Any) -> str:
return resource.display_name
def delete(self, resource):
@@ -77,33 +74,12 @@ class VertexAIResourceCleanupManager(ResourceCleanupManager):
class DatasetResourceCleanupManager(VertexAIResourceCleanupManager):
vertex_ai_resource = aiplatform.datasets._Dataset
dataset_types = [
aiplatform.ImageDataset,
aiplatform.TabularDataset,
aiplatform.TextDataset,
aiplatform.TimeSeriesDataset,
aiplatform.VideoDataset,
]
def list(self) -> Any:
return [
dataset
for dataset_type in self.dataset_types
for dataset in dataset_type.list()
]
class EndpointResourceCleanupManager(VertexAIResourceCleanupManager):
vertex_ai_resource = aiplatform.Endpoint
def delete(self, resource):
# TODO: Remove this once https://github.com/googleapis/python-aiplatform/issues/1441 is fixed
resource._sync_gca_resource()
for deployed_model_id in [
models.id for models in resource._gca_resource.deployed_models
]:
resource._undeploy(deployed_model_id=deployed_model_id)
resource.delete(force=True)
+2 -25
View File
@@ -17,7 +17,6 @@
import argparse
import pathlib
import execute_changed_notebooks_helper
@@ -62,18 +61,6 @@ parser.add_argument(
help="The GCP region. This is used to inject a variable value into the notebook before running.",
required=True,
)
parser.add_argument(
"--variable_service_account",
type=str,
help="A service account. This is used to inject a variable value into the notebook before running. This is not the account that will run the notebook.",
required=True,
)
parser.add_argument(
"--variable_vpc_network",
type=str,
help="The full VPC network name. See https://cloud.google.com/compute/docs/networks-and-firewalls#networks. Format is projects/{project}/global/networks/{network}, where {project} is a project number, as in '12345', and {network} is network name. See <https://cloud.google.com/compute/docs/reference/rest/v1/networks/insert> for details. This is used to inject a variable value into the notebook before running.",
required=False,
)
parser.add_argument(
"--staging_bucket",
type=str,
@@ -86,13 +73,6 @@ parser.add_argument(
help="The GCP directory for storing executed notebooks.",
required=True,
)
parser.add_argument(
"--timeout",
type=int,
help="Timeout in seconds",
default=86400,
required=False,
)
parser.add_argument(
"--private_pool_id",
type=str,
@@ -120,11 +100,8 @@ execute_changed_notebooks_helper.process_and_execute_notebooks(
container_uri=args.container_uri,
staging_bucket=args.staging_bucket,
artifacts_bucket=args.artifacts_bucket,
should_parallelize=args.should_parallelize,
timeout=args.timeout,
variable_project_id=args.variable_project_id,
variable_region=args.variable_region,
variable_service_account=args.variable_service_account,
variable_vpc_network=args.variable_vpc_network,
private_pool_id=args.private_pool_id,
private_pool_id=args.private_pool_id if not "default" else None,
should_parallelize=args.should_parallelize,
)
+55 -210
View File
@@ -17,28 +17,18 @@ import concurrent
import dataclasses
import datetime
import functools
import json
import git
import operator
import os
import pathlib
import nbformat
import re
import subprocess
import utils
from typing import List, Optional
from utils import util
import execute_notebook_helper
import execute_notebook_remote
import nbformat
from google.cloud.devtools.cloudbuild_v1.types import BuildOperationMetadata
from ratemate import RateLimit
from tabulate import tabulate
from utils import NotebookProcessors, util
import operator
# A buffer so that workers finish before the orchestrating job
WORKER_TIMEOUT_BUFFER_IN_SECONDS: int = 60 * 60
PYTHON_VERSION = "3.9" # Set default python version
import execute_notebook_remote
from utils import util, NotebookProcessors
from google.cloud.devtools.cloudbuild_v1.types import BuildOperationMetadata
def format_timedelta(delta: datetime.timedelta) -> str:
@@ -70,23 +60,13 @@ class NotebookExecutionResult:
log_url: str
output_uri: str
build_id: str
logs_bucket: str
error_message: Optional[str]
@property
def output_uri_web(self) -> Optional[str]:
if self.output_uri.startswith("gs://"):
return f"https://storage.googleapis.com/{self.output_uri[5:]}"
else:
return None
def _process_notebook(
notebook_path: str,
variable_project_id: str,
variable_region: str,
variable_service_account: str,
variable_vpc_network: Optional[str],
):
# Read notebook
with open(notebook_path) as f:
@@ -98,8 +78,6 @@ def _process_notebook(
replacement_map={
"PROJECT_ID": variable_project_id,
"REGION": variable_region,
"SERVICE_ACCOUNT": variable_service_account,
"VPC_NETWORK": variable_vpc_network,
},
)
@@ -115,33 +93,6 @@ def _process_notebook(
nbformat.write(nb, new_file)
def _get_notebook_python_version(notebook_path: str) -> str:
"""
Get the python version for running the notebook if it is specified in
the notebook.
"""
python_version = PYTHON_VERSION
# Load the notebook
file = open(notebook_path)
src = file.read()
nb_json = json.loads(src)
#Iterate over the cells in the ipynb
for cell in nb_json['cells']:
if cell['cell_type'] == 'markdown':
markdown = str.join('', cell['source'])
# Look for the python version specification pattern
re_match = re.search('python version = (\d\.\d)', markdown, flags=re.IGNORECASE)
if re_match:
# get the version number
python_version = re_match.group(1)
break
return python_version
def _create_tag(filepath: str) -> str:
tag = os.path.basename(os.path.normpath(filepath))
tag = re.sub("[^0-9a-zA-Z_.-]+", "-", tag)
@@ -152,33 +103,18 @@ def _create_tag(filepath: str) -> str:
return tag
rate_limit = RateLimit(max_count=50, per=60, greedy=True)
def process_and_execute_notebook(
container_uri: str,
staging_bucket: str,
artifacts_bucket: str,
variable_project_id: str,
variable_region: str,
variable_service_account: str,
variable_vpc_network: Optional[str],
private_pool_id: Optional[str],
deadline: datetime.datetime,
notebook: str,
should_get_tail_logs: bool = False,
) -> NotebookExecutionResult:
rate_limit.wait() # wait before creating the task
print(f"Running notebook: {notebook}")
# Handle empty strings
if not variable_vpc_network:
variable_vpc_network = None
if not private_pool_id:
private_pool_id = None
# Create paths
notebook_output_uri = "/".join([artifacts_bucket, pathlib.Path(notebook).name])
@@ -192,7 +128,6 @@ def process_and_execute_notebook(
output_uri=notebook_output_uri,
log_url="",
build_id="",
logs_bucket="",
error_message=None,
)
@@ -200,43 +135,29 @@ def process_and_execute_notebook(
time_start = datetime.datetime.now()
operation = None
try:
# Get the python version for running the notebook if specified
notebook_exec_python_version = _get_notebook_python_version(notebook_path=notebook)
print(f"Running notebook with python {notebook_exec_python_version}")
# Pre-process notebook by substituting variable names
_process_notebook(
notebook_path=notebook,
variable_project_id=variable_project_id,
variable_region=variable_region,
variable_service_account=variable_service_account,
variable_vpc_network=variable_vpc_network,
)
# Upload the pre-processed code to a GCS bucket
code_archive_uri = util.archive_code_and_upload(staging_bucket=staging_bucket)
# Calculate timeout in seconds
timeout_in_seconds = max(
int((deadline - datetime.datetime.now()).total_seconds()), 1
)
operation = execute_notebook_remote.execute_notebook_remote(
code_archive_uri=code_archive_uri,
notebook_uri=notebook,
notebook_output_uri=notebook_output_uri,
container_uri=container_uri,
tag=tag,
region=variable_region,
private_pool_id=private_pool_id,
private_pool_region=variable_region,
timeout_in_seconds=timeout_in_seconds,
python_version=notebook_exec_python_version
)
operation_metadata = BuildOperationMetadata(mapping=operation.metadata)
result.build_id = operation_metadata.build.id
result.log_url = operation_metadata.build.log_url
result.logs_bucket = operation_metadata.build.logs_bucket
# Block and wait for the result
operation_result = operation.result()
@@ -294,40 +215,20 @@ def get_changed_notebooks(
# Find notebooks
notebooks = []
# Instantiate GitPython objects
repo = git.Repo(os.getcwd())
index = repo.index
if base_branch:
# Get the point at which this branch branches off from main
branching_commits = repo.merge_base("HEAD", f"origin/{base_branch}")
if len(branching_commits) > 0:
branching_commit = branching_commits[0]
print(f"Looking for notebooks that changed from branch: {branching_commit}")
notebooks = [
diff.b_path
for diff in index.diff(branching_commit, paths=test_paths)
if diff.b_path is not None
]
else:
notebooks = []
print(f"Looking for notebooks that changed from branch: {base_branch}")
notebooks = subprocess.check_output(
["git", "diff", "--name-only", f"origin/{base_branch}..."] + test_paths
)
else:
print(f"Looking for all notebooks.")
notebooks_str = subprocess.check_output(["git", "ls-files"] + test_paths)
notebooks = notebooks_str.decode("utf-8").split("\n")
notebooks = subprocess.check_output(["git", "ls-files"] + test_paths)
notebooks = notebooks.decode("utf-8").split("\n")
notebooks = [notebook for notebook in notebooks if notebook.endswith(".ipynb")]
notebooks = [notebook for notebook in notebooks if len(notebook) > 0]
notebooks = [notebook for notebook in notebooks if pathlib.Path(notebook).exists()]
if len(notebooks) > 0:
print(f"Found {len(notebooks)} notebooks:")
for notebook in notebooks:
print(f"\t{notebook}")
return notebooks
@@ -336,13 +237,10 @@ def process_and_execute_notebooks(
container_uri: str,
staging_bucket: str,
artifacts_bucket: str,
should_parallelize: bool,
timeout: int,
variable_project_id: str,
variable_region: str,
variable_service_account: str,
variable_vpc_network: Optional[str] = None,
private_pool_id: Optional[str] = None,
private_pool_id: Optional[str],
should_parallelize: bool,
):
"""
Run the notebooks that exist under the folders defined in the test_paths_file.
@@ -369,27 +267,17 @@ def process_and_execute_notebooks(
Required. The value for REGION to inject into notebooks.
should_parallelize (bool):
Required. Should run notebooks in parallel using a thread pool as opposed to in sequence.
timeout (str):
Required. Timeout string according to https://cloud.google.com/build/docs/build-config-file-schema#timeout.
"""
notebook_execution_results: List[NotebookExecutionResult] = []
# Calculate deadline
deadline = datetime.datetime.now() + datetime.timedelta(
seconds=max(timeout - WORKER_TIMEOUT_BUFFER_IN_SECONDS, 0)
)
if len(notebooks) >= 1:
notebook_execution_results: List[NotebookExecutionResult] = []
if len(notebooks) > 0:
print(f"Found {len(notebooks)} modified notebooks: {notebooks}")
if should_parallelize and len(notebooks) > 1:
print(
"Running notebooks in parallel, so no logs will be displayed. Please wait..."
)
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
print(f"Max workers: {executor._max_workers}")
with concurrent.futures.ThreadPoolExecutor(max_workers=None) as executor:
notebook_execution_results = list(
executor.map(
functools.partial(
@@ -399,10 +287,7 @@ def process_and_execute_notebooks(
artifacts_bucket,
variable_project_id,
variable_region,
variable_service_account,
variable_vpc_network,
private_pool_id,
deadline,
),
notebooks,
)
@@ -415,88 +300,48 @@ def process_and_execute_notebooks(
artifacts_bucket=artifacts_bucket,
variable_project_id=variable_project_id,
variable_region=variable_region,
variable_service_account=variable_service_account,
variable_vpc_network=variable_vpc_network,
private_pool_id=private_pool_id,
deadline=deadline,
notebook=notebook,
)
for notebook in notebooks
]
print("\n=== RESULTS ===\n")
results_sorted = sorted(
notebook_execution_results,
key=lambda result: result.is_pass,
reverse=True,
)
# Print results
print(
tabulate(
[
[
result.name,
"PASSED" if result.is_pass else "FAILED",
format_timedelta(result.duration),
result.log_url,
result.output_uri,
result.output_uri_web,
result.logs_bucket
]
for result in results_sorted
],
headers=[
"build_tag",
"status",
"duration",
"log_url",
"output_uri",
"output_uri_web",
"logs_bucket"
],
)
)
if len(notebooks) == 1:
print("="*100)
print("The notebook execution build log:\n")
print("="*100)
build_id = results_sorted[0].build_id
logs_bucket_name = (results_sorted[0].logs_bucket).removeprefix("gs://")
log_file_name = f"log-{build_id}.txt"
log_contents = util.download_blob_into_memory(
bucket_name=logs_bucket_name,
blob_name=log_file_name,
download_as_text=True
)
# Remove extra steps from the log
match = re.search("starting Step #4", log_contents, flags=re.IGNORECASE)
if match is not None:
match_index = match.span()[0]
print(log_contents[match_index:])
else:
print(log_contents)
print("\n=== END RESULTS===\n")
total_notebook_duration = functools.reduce(
operator.add,
[datetime.timedelta(seconds=0)]
+ [result.duration for result in results_sorted],
)
print(
f"Cumulative notebook duration: {format_timedelta(total_notebook_duration)}"
)
# Raise error if any notebooks failed
if not all([result.is_pass for result in results_sorted]):
raise RuntimeError("Notebook failures detected. See logs for details")
else:
print("No notebooks modified in this pull request.")
print("\n=== RESULTS ===\n")
results_sorted = sorted(
notebook_execution_results,
key=lambda result: result.is_pass,
reverse=True,
)
# Print results
print(
tabulate(
[
[
result.name,
"PASSED" if result.is_pass else "FAILED",
format_timedelta(result.duration),
result.log_url,
]
for result in results_sorted
],
headers=["build_tag", "status", "duration", "log_url"],
)
)
print("\n=== END RESULTS===\n")
total_notebook_duration = functools.reduce(
operator.add,
[datetime.timedelta(seconds=0)]
+ [result.duration for result in results_sorted],
)
print(f"Cumulative notebook duration: {format_timedelta(total_notebook_duration)}")
# Raise error if any notebooks failed
if not all([result.is_pass for result in results_sorted]):
raise RuntimeError("Notebook failures detected. See logs for details")
-1
View File
@@ -16,7 +16,6 @@
"""A CLI to download (optional) and run a single notebook locally"""
import argparse
import execute_notebook_helper
parser = argparse.ArgumentParser(description="Run a single notebook locally.")
+9 -20
View File
@@ -15,20 +15,17 @@
"""Methods to run a notebook locally"""
import errno
import os
import shutil
import sys
import os
import errno
import papermill as pm
from google.cloud.aiplatform import utils
import shutil
from utils import util
from google.cloud.aiplatform import utils
# This script is used to execute a notebook and write out the output notebook.
# This is used to force papermill to use this kernel to run the notebook instead of any defined inside the notebook itself
DEFAULT_KERNEL_NAME = "python3"
def execute_notebook(
notebook_source: str,
@@ -53,17 +50,6 @@ def execute_notebook(
execution_exception = None
print("\n=== DOWNLOAD EXECUTED NOTEBOOK ===\n")
print(f"Please debug the executed notebook by downloading the executed notebook:")
print("Option 1. Using gsutil. Run the following command in your terminal.")
print(f'\tgsutil cp "{output_file_or_uri}" .')
print("Option 2. Using this link.")
print(f"\thttps://storage.googleapis.com/{output_file_or_uri[5:]}")
print("\n======\n")
# Execute notebook
try:
# Execute notebook
@@ -72,7 +58,6 @@ def execute_notebook(
output_path=notebook_source,
progress_bar=should_log_output,
request_save_on_cell_execute=should_log_output,
kernel_name=DEFAULT_KERNEL_NAME,
log_output=should_log_output,
stdout_file=sys.stdout if should_log_output else None,
stderr_file=sys.stderr if should_log_output else None,
@@ -86,6 +71,10 @@ def execute_notebook(
util.upload_file(notebook_source, remote_file_path=output_file_or_uri)
print("\n=== EXECUTION FINISHED ===\n")
print(
f"Please debug the executed notebook by downloading: {output_file_or_uri}"
)
print("\n======\n")
else:
# Create directories if they don't exist
if not os.path.exists(os.path.dirname(output_file_or_uri)):
+19 -23
View File
@@ -16,18 +16,22 @@
"""Methods to run a notebook on Google Cloud Build"""
from re import sub
from typing import Optional
import google.auth
import yaml
from google.api_core import client_options, operation
from google.cloud.aiplatform import utils
from google.cloud.devtools import cloudbuild_v1
from google.cloud.devtools.cloudbuild_v1.types import Source, StorageSource
from google.protobuf import duration_pb2
from yaml.loader import FullLoader
import google.auth
from google.cloud.devtools import cloudbuild_v1
from google.cloud.devtools.cloudbuild_v1.types import Source, StorageSource
from typing import Optional
import yaml
from google.cloud.aiplatform import utils
from google.api_core import operation, client_options
CLOUD_BUILD_FILEPATH = ".cloud-build/notebook-execution-test-cloudbuild-single.yaml"
TIMEOUT_IN_SECONDS = 86400
SERVICE_BASE_PATH = "cloudbuild.googleapis.com"
@@ -36,38 +40,30 @@ def execute_notebook_remote(
notebook_uri: str,
notebook_output_uri: str,
container_uri: str,
region: str,
private_pool_id: Optional[str],
private_pool_region: Optional[str],
tag: Optional[str],
timeout_in_seconds: Optional[int] = None,
python_version: Optional[str] = None
) -> operation.Operation:
"""Create and execute a single notebook on Google Cloud Build"""
# Load build steps from YAML
cloudbuild_config = yaml.load(open(CLOUD_BUILD_FILEPATH), Loader=FullLoader)
substitutions = {
"_PYTHON_IMAGE": container_uri,
"_NOTEBOOK_GCS_URI": notebook_uri,
"_NOTEBOOK_OUTPUT_GCS_URI": notebook_output_uri,
"_PYTHON_VERSION" : f"python{python_version}"
}
if python_version is not None:
substitutions["_PYTHON_VERSION"] = "python" + python_version
build = cloudbuild_v1.Build()
options: Optional[client_options.ClientOptions] = None
if private_pool_id and private_pool_region:
# substitutions["_PRIVATE_POOL_NAME"] = private_pool_id
build.options = cloudbuild_config.get("options")
build.options.pool = {"name": private_pool_id}
if private_pool_id:
substitutions["_PRIVATE_POOL_NAME"] = private_pool_id
build.options = cloudbuild_config["options"]
# Switch to the regional endpoint of the pool
options = client_options.ClientOptions(
api_endpoint=f"{private_pool_region}-{SERVICE_BASE_PATH}"
api_endpoint=f"{region}-{SERVICE_BASE_PATH}"
)
# Authorize the client with Google defaults
@@ -89,8 +85,8 @@ def execute_notebook_remote(
build.steps = cloudbuild_config["steps"]
build.substitutions = substitutions
build.timeout = duration_pb2.Duration(seconds=timeout_in_seconds)
build.queue_ttl = duration_pb2.Duration(seconds=timeout_in_seconds)
build.timeout = duration_pb2.Duration(seconds=TIMEOUT_IN_SECONDS)
build.queue_ttl = duration_pb2.Duration(seconds=TIMEOUT_IN_SECONDS)
if tag:
build.tags = [tag]
@@ -4,35 +4,28 @@ steps:
entrypoint: /bin/sh
args:
- -c
- 'gcloud config list --quiet'
- 'gcloud config list'
# Check the Python version
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- ${_PYTHON_VERSION} .cloud-build/CheckPythonVersion.py -q
# Create a virtual environment
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- ${_PYTHON_VERSION} -m venv workspace/env
- 'python3 .cloud-build/CheckPythonVersion.py'
# Install Python dependencies
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- . workspace/env/bin/activate &&
python -m pip -q install -U pip &&
python -m pip -q install -U -r .cloud-build/requirements.txt
- 'python3 -m pip install -U pip && python3 -m pip install -U --user -r .cloud-build/requirements.txt'
# Install Python dependencies and run testing script
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- |
. workspace/env/bin/activate &&
python .cloud-build/execute_notebook_cli.py --notebook_source "${_NOTEBOOK_GCS_URI}" --output_file_or_uri "${_NOTEBOOK_OUTPUT_GCS_URI}"
- 'python3 -m pip install -U pip && python3 -m pip freeze && python3 .cloud-build/execute_notebook_cli.py --notebook_source "${_NOTEBOOK_GCS_URI}" --output_file_or_uri "${_NOTEBOOK_OUTPUT_GCS_URI}"'
env:
- 'IS_TESTING=1'
timeout: 86400s
options:
pool:
name: ${_PRIVATE_POOL_NAME}
@@ -4,42 +4,35 @@ steps:
entrypoint: /bin/sh
args:
- -c
- gcloud config list --quiet
- 'gcloud config list'
# Check the Python version
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- python3 .cloud-build/CheckPythonVersion.py -q
# Fetch full repo for diff purposes
- name: gcr.io/cloud-builders/git
args: [fetch, --unshallow, --quiet]
# Create a virtual environment
- 'python3 .cloud-build/CheckPythonVersion.py'
# Fetch base branch if required
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- python3 -m venv workspace/env
- 'if [ -n "${_BASE_BRANCH}" ]; then git fetch origin "${_BASE_BRANCH}":refs/remotes/origin/"${_BASE_BRANCH}"; else echo "Skipping fetch."; fi'
# Install Python dependencies
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- . workspace/env/bin/activate &&
python3 -m pip -q install -U pip &&
python3 -m pip -q install -U -r .cloud-build/requirements.txt
- 'python3 -m pip install -U pip && python3 -m pip install -U --user -r .cloud-build/requirements.txt'
# Install Python dependencies and run testing script
# TODO: Only pass in private_pool_id if it is set
- name: ${_PYTHON_IMAGE}
entrypoint: /bin/sh
args:
- -c
- |
. workspace/env/bin/activate &&
python3 .cloud-build/execute_changed_notebooks_cli.py --test_paths_file "${_TEST_PATHS_FILE}" --base_branch "${_FORCED_BASE_BRANCH}" --container_uri ${_PYTHON_IMAGE} --staging_bucket ${_GCS_STAGING_BUCKET} --artifacts_bucket ${_GCS_STAGING_BUCKET}/executed_notebooks/PR_${_PR_NUMBER}/BUILD_${BUILD_ID} --variable_project_id ${PROJECT_ID} --variable_region ${_GCP_REGION} --variable_service_account ${_GCP_SERVICE_ACCOUNT} --variable_vpc_network "${_GPC_VPC_NETWORK_NAME}" `if [ ! -z "${_PRIVATE_POOL_NAME}" ]; then echo "--private_pool_id ${_PRIVATE_POOL_NAME}"; fi`
- 'python3 -m pip install -U pip && python3 -m pip freeze && python3 .cloud-build/execute_changed_notebooks_cli.py --test_paths_file "${_TEST_PATHS_FILE}" --base_branch "${_FORCED_BASE_BRANCH}" --container_uri ${_PYTHON_IMAGE} --staging_bucket ${_GCS_STAGING_BUCKET} --artifacts_bucket ${_GCS_STAGING_BUCKET}/executed_notebooks/PR_${_PR_NUMBER}/BUILD_${BUILD_ID} --variable_project_id ${PROJECT_ID} --variable_region ${_GCP_REGION} `if [ ! -z "${_PRIVATE_POOL_NAME}" ]; then echo "--private_pool_id ${_PRIVATE_POOL_NAME}"; fi`'
env:
- 'IS_TESTING=1'
timeout: 86400s
options:
pool:
name: ${_PRIVATE_POOL_NAME}
name: ${_PRIVATE_POOL_NAME}
+1 -2
View File
@@ -9,5 +9,4 @@ tabulate
google-cloud-aiplatform
google-cloud-storage
google-cloud-build
ratemate
GitPython
gcloud
+2 -3
View File
@@ -1,6 +1,5 @@
notebooks/official/vizier/gapic-vizier-multi-objective-optimization.ipynb
notebooks/official/pipelines/lightweight_functions_component_io_kfp.ipynb
notebooks/official/matching_engine/intro-swivel.ipynb
notebooks/official/ml_metadata/sdk-metric-parameter-tracking-for-locally-trained-models.ipynb
notebooks/official/custom/custom-tabular-bq-managed-dataset.ipynb
notebooks/official/matching_engine/sdk_matching_engine_for_indexing.ipynb
.cloud-build/tests/python_version_test.ipynb
notebooks/official/pipelines/metrics_viz_run_compare_kfp.ipynb
-1
View File
@@ -1 +0,0 @@
notebooks/official/custom/custom-tabular-bq-managed-dataset.ipynb
@@ -1,61 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "57a3d44ed8a8"
},
"source": [
"### Set up your Google Cloud project\n",
"\n",
"**_NOTE_**: This notebook has been tested in the following environment:\n",
"\n",
"* Python version = 3.7\n",
"\n",
"**The following steps are required, regardless of your notebook environment.**\n",
"\n",
"1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n",
"\n",
"1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
"\n",
"1. [Enable the Vertex AI API and Compute Engine API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com,compute_component).\n",
"\n",
"1. If you are running this notebook locally, you will need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
"\n",
"1. Enter your project ID in the cell below. Then run the cell to make sure the\n",
"Cloud SDK uses the right project for all the commands in this notebook.\n",
"\n",
"**Note**: Jupyter runs lines prefixed with `!` as shell commands, and it interpolates Python variables prefixed with `$` into these commands."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c6516f90311b"
},
"outputs": [],
"source": [
"# test if the right python version is being used\n",
"import sys\n",
"\n",
"actual_python_version = f\"{sys.version_info.major}.{sys.version_info.minor}\"\n",
"print(f\"Runtime python version: {actual_python_version}\")\n",
"\n",
"assert actual_python_version == \"3.7\", \"Wrong python version!\""
]
}
],
"metadata": {
"colab": {
"name": "python_version_test.ipynb",
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
+2 -4
View File
@@ -13,10 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Dict
from nbconvert.preprocessors import Preprocessor
from typing import Dict
from . import UpdateNotebookVariables as update_notebook_variables
@@ -62,4 +60,4 @@ class UpdateVariablesPreprocessor(Preprocessor):
executable_cells.append(cell)
notebook.cells = executable_cells
return notebook, resources
return notebook, resources
+3 -26
View File
@@ -35,8 +35,8 @@ Variables in conditionals can also be replaced:
def get_updated_value(content: str, variable_name: str, variable_value: str) -> str:
return re.sub(
rf"({variable_name}.*? = .*?[\",\'])\[.+?\]([\",\'].*?)",
rf"\g<1>{variable_value}\g<2>",
rf"({variable_name}.*?=.*?[\",\'])\[.+?\]([\",\'].*?)",
rf"\1{variable_value}\2",
content,
flags=re.M,
)
@@ -78,27 +78,4 @@ def test_region():
variable_name="REGION",
variable_value="us-central1",
)
assert new_content == 'REGION = "us-central1" # @param {type:"string"}'
def test_region_equal_equals_ignore():
# Tests that == is ignored
new_content = get_updated_value(
content='REGION == "[your-region]" # @param {type:"string"}',
variable_name="REGION",
variable_value="us-central1",
)
assert new_content == 'REGION == "[your-region]" # @param {type:"string"}'
def test_service_account():
# Tests that == is ignored
new_content = get_updated_value(
content='SERVICE_ACCOUNT = "[your-service-account]" # @param {type:"string"}',
variable_name="SERVICE_ACCOUNT",
variable_value="12345-compute@developer.gserviceaccount.com",
)
assert (
new_content
== 'SERVICE_ACCOUNT = "12345-compute@developer.gserviceaccount.com" # @param {type:"string"}'
)
assert new_content == 'REGION = "us-central1" # @param {type:"string"}'
+7 -38
View File
@@ -1,13 +1,13 @@
from datetime import datetime
from typing import Optional
from google.cloud import storage
from google.cloud.aiplatform import utils
from google.auth import credentials as auth_credentials
import os
import subprocess
import tarfile
import uuid
from datetime import datetime
from typing import Optional, Union
from google.auth import credentials as auth_credentials
from google.cloud import storage
from google.cloud.aiplatform import utils
def download_file(bucket_name: str, blob_name: str, destination_file: str) -> str:
@@ -57,35 +57,4 @@ def archive_code_and_upload(staging_bucket: str):
print(f"Uploaded source code archive to {source_archived_file_gcs}")
return source_archived_file_gcs
def download_blob_into_memory(
bucket_name: str,
blob_name: str,
download_as_text: Optional[bool]=False
) -> Union[bytes, str]:
"""
Downloads a blob into memory as byte or as text if
download_as_text is set to True.
"""
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name)
# Construct a client side representation of a blob.
blob = bucket.blob(blob_name)
# Download the blob content
if download_as_text:
contents = blob.download_as_text()
else:
contents = blob.download_as_bytes()
print(
f"Downloaded storage object {blob_name} from bucket {bucket_name}."
)
return contents
return source_archived_file_gcs
+9 -19
View File
@@ -1,28 +1,18 @@
**REQUIRED:** Add a summary of your PR here, typically including why the change is needed and what was changed. Include any design alternatives for discussion purposes.
<br>
--- YOUR PR SUMMARY GOES HERE ---
<br><br><br>
**REQUIRED:** Fill out the below checklists or remove if irrelevant
1. If you are opening a PR for `Official Notebooks` under the [notebooks/official](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/notebooks/official) folder, follow this mandatory checklist:
If you are opening a PR for `Official Notebooks` under the [notebooks/official](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/notebooks/official) folder, follow this mandatory checklist:
- [ ] Use the [notebook template](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/notebook_template.ipynb) as a starting point.
- [ ] Follow the style and grammar rules outlined in the above notebook template.
- [ ] Verify the notebook runs successfully in Colab since the automated tests cannot guarantee this even when it passes.
- [ ] Passes all the required automated checks. You can locally test for formatting and linting with these [instructions](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/CONTRIBUTING.md#code-quality-checks).
- [ ] Passes all the required automated checks. You can locally test for formatting and linting with these [instructions](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/docs/contributing.md#code-quality-checks).
- [ ] You have consulted with a tech writer to see if tech writer review is necessary. If so, the notebook has been reviewed by a tech writer, and they have approved it.
- [ ] This notebook has been added to the [CODEOWNERS](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/CODEOWNERS) file under the `Official Notebooks` section, pointing to the author or the author's team.
- [ ] This notebook has been added to the [CODEOWNERS](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/docs/CODEOWNERS) file under `# Official Notebooks` section, pointing to the author or the author's team.
- [ ] The Jupyter notebook cleans up any artifacts it has created (datasets, ML models, endpoints, etc) so as not to eat up unnecessary resources.
<br>
2. If you are opening a PR for `Community Notebooks` under the [notebooks/community](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/notebooks/community) folder:
- [ ] This notebook has been added to the [CODEOWNERS](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/CODEOWNERS) file under the `Community Notebooks` section, pointing to the author or the author's team.
- [ ] Passes all the required formatting and linting checks. You can locally test with these [instructions](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/CONTRIBUTING.md#code-quality-checks).
If you are opening a PR for `Community Notebooks` under the [notebooks/community](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/notebooks/community) folder:
- [ ] This notebook has been added to the [CODEOWNERS](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/docs/CODEOWNERS) file under the `# Community Notebooks` section, pointing to the author or the author's team.
- [ ] Passes all the required formatting and linting checks. You can locally test with these [instructions](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/docs/contributing.md#code-quality-checks).
<br>
3. If you are opening a PR for `Community Content` under the [community-content](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/community-content) folder:
If you are opening a PR for `Community Content` under the [community-content](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/community-content) folder:
- [ ] Make sure your main `Content Directory Name` is descriptive, informative, and includes some of the key products and attributes of your content, so that it is differentiable from other content
- [ ] The main content directory has been added to the [CODEOWNERS](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/community-content/CODEOWNERS) file under the `Community Content` section, pointing to the author or the author's team.
- [ ] Passes all the required formatting and linting checks. You can locally test with these [instructions](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/CONTRIBUTING.md#code-quality-checks).
- [ ] The main content directory has been added to the [CODEOWNERS](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/docs/CODEOWNERS) file under the `# Community Content` section, pointing to the author or the author's team.
- [ ] Passes all the required formatting and linting checks. You can locally test with these [instructions](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/docs/contributing.md#code-quality-checks).
+2 -4
View File
@@ -7,11 +7,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
uses: actions/setup-python@v2
- name: Fetch pull request branch
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Fetch base main branch
-20
View File
@@ -1,20 +0,0 @@
# To use this image, run this command with the desired notebook args from the top-level vertex-ai-samples directory:
# 1. To lint all changed notebooks:
# docker run -v ${PWD}:/setup/app gcr.io/python-docs-samples-tests/notebook_linter:latest
# 2. To lint specific notebooks:
# docker run -v ${PWD}:/setup/app gcr.io/python-docs-samples-tests/notebook_linter:latest notebooks/1.ipynb notebooks/2.ipynb
FROM python:3.10
WORKDIR setup
COPY ./requirements.txt .
COPY ./run_linter.sh .
# Install dependencies.
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
WORKDIR app
ENTRYPOINT ["/setup/run_linter.sh"]
+3 -4
View File
@@ -2,9 +2,8 @@ git+https://github.com/tensorflow/docs
ipython
jupyter
nbconvert
black==22.6.0
pyupgrade==2.34.0
black==22.1.0
pyupgrade==2.31.0
isort==5.10.1
flake8==4.0.1
nbqa==1.4.0
nbqa==1.2.3
+6 -16
View File
@@ -47,22 +47,12 @@ done
echo "Test mode: $is_test"
# Read in user-provided notebooks
notebooks=()
for arg in "$@"; do
if [[ $arg == *.ipynb ]]; then
notebooks+=("$arg")
fi
done
# Only check notebooks in test folders modified in this pull request.
# Note: Use process substitution to persist the data in the array
if [ ${#notebooks[@]} -eq 0 ]; then
echo "Checking for changed notebooked using git"
while read -r file || [ -n "$line" ]; do
notebooks+=("$file")
done < <(git diff --name-only main... | grep '\.ipynb$')
fi
notebooks=()
while read -r file || [ -n "$line" ]; do
notebooks+=("$file")
done < <(git diff --name-only main... | grep '\.ipynb$')
problematic_notebooks=()
if [ ${#notebooks[@]} -gt 0 ]; then
@@ -78,7 +68,7 @@ if [ ${#notebooks[@]} -gt 0 ]; then
if [ "$is_test" = true ]; then
echo "Running nbfmt..."
python3 -m tensorflow_docs.tools.nbfmt --test "$notebook"
python3 -m tensorflow_docs.tools.nbfmt --remove_outputs --test "$notebook"
NBFMT_RTN=$?
# echo "Running black..."
# python3 -m nbqa black "$notebook" --check
@@ -103,7 +93,7 @@ if [ ${#notebooks[@]} -gt 0 ]; then
python3 -m nbqa isort "$notebook"
ISORT_RTN=$?
echo "Running nbfmt..."
python3 -m tensorflow_docs.tools.nbfmt "$notebook"
python3 -m tensorflow_docs.tools.nbfmt --remove_outputs "$notebook"
NBFMT_RTN=$?
echo "Running flake8..."
python3 -m nbqa flake8 "$notebook" --show-source --extend-ignore=W391,E501,F821,E402,F404,W503,E203,E722,W293,W291
+1 -1
View File
@@ -48,8 +48,8 @@ then you will need to manually address them before submitting your PR.
nbqa black "$notebook"
nbqa pyupgrade "$notebook"
nbqa isort "$notebook"
nbqa flake8 "$notebook" --extend-ignore=W391,E501,F821,E402,F404,W503,E203,E722,W293,W291
python3 -m tensorflow_docs.tools.nbfmt --remove_outputs "$notebook"
nbqa flake8 "$notebook" --extend-ignore=W391,E501,F821,E402,F404,W503,E203,E722,W293,W291
```
## Code Reviews
+1 -13
View File
@@ -6,19 +6,7 @@ Welcome to the Google Cloud [Vertex AI](https://cloud.google.com/vertex-ai/docs/
## Overview
The repository contains [notebooks](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/master/notebooks) and [community content](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/master/community-content) that demonstrate how to develop and manage ML workflows using Google Cloud Vertex AI.
## Repository structure
```bash
├── community-content - Sample code and tutorials contributed by the community
├── notebooks
│ ├── community - Notebooks contributed by the community
│ ├── official - Notebooks demonstrating use of each Vertex AI service
│ │ ├── automl
│ │ ├── custom
│ │ ├── ...
```
The repository contains [Notebooks](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/master/notebooks) and [Community Content](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/master/community-content) that demonstrate how to develop and manage ML workflows using Google Cloud Vertex AI.
## Contributing
-3
View File
@@ -1,8 +1,5 @@
* @vertex-ai-samples-contributors @GoogleCloudPlatform/cloudml-samples-owners
/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk @yinghsienwu
/pytorch_pre_built_images_deployment @googleapis/vertex-prediction-team
/pytorch_text_classification_using_vertex_sdk_and_gcloud @RajeshThallam
/pytorch_text_classification_using_vertex_sdk_and_gcloud @RajeshThallam @ultrons
/sklearn_text_classification_from_script_using_vertex_sdk @maxhardt
/pluto_on_workbench @wkharold
/cpr-examples @samthrasher
@@ -1,5 +0,0 @@
testdata/*
build.py
test.py
state_dict.pth
config.json
@@ -1,6 +0,0 @@
cpr_model_server.py
entrypoint.py
state_dict.pth
config.json
**/__pycache__
!testdata/**
@@ -1,110 +0,0 @@
# CPR Example: PyTorch Image Models (timm)
## About CPR
CPR ([custom prediction routines](https://github.com/googleapis/python-aiplatform/blob/main/google/cloud/aiplatform/prediction/README.md)) is a framework designed by Google Cloud developers to make it easier to combine machine learning models with custom preprocessing and postprocessing logic in a real-time serving application.
## Using this example
This code is a self-contained example of a custom model server project built using CPR.
As is, you can use it to serve the ViT-Small image classification model from Ross Wightman's [`timm`](https://github.com/rwightman/pytorch-image-models) library of image model implementations in PyTorch. Both CPU and GPU are supported.
You can also consider using the code here as a template for your own CPR project if you want to use a different model from `timm`, a different PyTorch model, or an entirely different framework.
### Requirements
In order to use this example, you'll need Docker and Python 3 installed on your system.
To get started, first create a virtual environment in an empty directory:
```sh
mkdir cpr-example
python3 -m venv cpr-example
cd cpr-example && source bin/activate
```
Then, clone the [vertex-ai-samples repo](https://github.com/GoogleCloudPlatform/vertex-ai-samples) in that directory:
```sh
git clone https://github.com/GoogleCloudPlatform/vertex-ai-samples.git
cd vertex-ai-samples/community-content/cpr-examples/timm_serving
```
Finally, install the Python modules required to build and run the model server:
```sh
pip install -r requirements.txt
```
### Auth
This example uses Google Cloud Storage for hosting model artifacts and Artifact Registry to store the container image.
You'll need to authorize yourself before you can interact with these.
First, log in to GCP with application default credentials:
```sh
gcloud auth application-default login
```
Next, if you haven't done so already, set up the [gcloud credential helper](https://cloud.google.com/artifact-registry/docs/docker/authentication)
for the Artifact Registry region where you intend to host the image.
```
gcloud auth configure-docker <region>-docker.pkg.dev
```
### Predictor
The `TimmPredictor` class in `timm_serving/predictor.py` implements most of the important logic for the server.
- `load(artifacts_dir)`: The predictor's `load` method is called when the server starts up in order to set up the predictor, usually by loading model weights and any artifacts needed for preprocessing and postprocessing. In this example, we initialize the saved model from the `state_dict.pth` file located inside the `artifacts_dir` folder and create the preprocessing transform from the model config.
- `preprocess`, `predict`, `postprocess`: These methods are applied in sequence to the deserialized JSON data from each request.
- `preprocess` decodes images from base64 and apply cropping, scaling and normalizing transforms.
- `predict` runs the ViT-Small model on the preprocessed images and returns class scores.
- `postprocess` finds the top five classes and packs the class names, probabilities, and indices in a serializable result.
### Building the container
To build the model server locally, run the build command:
```sh
python build.py build
```
You can edit configuration values such as the model server's base image, the name and tag assigned to the image, and the path where model weights are stored locally.
When you run the build command, model weights are downloaded and the model server container is built.
### Running local tests
`test.py` contains a suite of unit tests for the predictor as well as end-to-end tests for the model server.
To run the tests:
```sh
python test.py
```
All of the test images are public domain.
- [Cat](https://commons.wikimedia.org/wiki/File:Stray_cat_on_wall.jpg)
- [Airplane](https://commons.wikimedia.org/wiki/File:Airplanes_jets.jpg)
- The infamous [mandrill](https://commons.wikimedia.org/wiki/File:Wikipedia-sipi-image-db-mandrill-4.2.03.png)
### Deploying to Vertex AI
Before uploading or deploying the container, you'll need to modify `config.py` to set appropriate values for:
- `project_id`: Your GCP project id.
- `region`: Region where the model will be uploaded and deployed.
- `repository`: [Artifact Registry repository](https://cloud.google.com/artifact-registry/docs/repositories/create-repos) in your project where the container image will be uploaded.
- `artifacts_gcs_dir`: Folder in a [Google Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) where the model weights will be uploaded.
Once this is done, first upload the model:
```sh
python build.py upload
```
Then deploy it:
```sh
python build.py deploy
```
If you run the deploy command again, it will create a new endpoint. If you want to undeploy the model, you can do so using the Vertex AI dashboard on the Google Cloud console, or use `gcloud ai endpoints undeploy` from the command line.
After deploying successfully, you can run `python build.py probe` to send a sample request to the deployed model.
@@ -1,117 +0,0 @@
# Copyright 2022 Google LLC
# 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
# https://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.
"""Build the model server container."""
import json
import logging
import os
import pathlib
from typing import Sequence
from absl import app
from absl import logging
from config import CPRConfig
from google.cloud import aiplatform
from google.cloud.aiplatform import prediction as cpr
import smart_open
import timm
from timm_serving import predictor
import torch
def build_container(config: CPRConfig, tag: str) -> cpr.LocalModel:
"""Build the model server container.
Args:
tag: Output image tag.
Returns:
LocalModel exposing the built model server.
"""
return cpr.LocalModel.build_cpr_model(
src_dir=os.path.join(os.getcwd()),
output_image_uri=tag,
base_image=config.base_image,
predictor=predictor.TimmPredictor,
requirements_path=os.path.join(os.getcwd(), "requirements.txt"),
)
def save_model_artifact(destination: str) -> None:
"""Save a copy of the model state dict."""
model = timm.create_model(predictor.TimmPredictor.TIMM_MODEL_NAME, pretrained=True)
dest_file = os.path.join(destination, predictor.TimmPredictor.WEIGHTS_FILE)
with smart_open.open(dest_file, "wb") as f:
torch.save(model, f)
logging.info("Saved model to %s", dest_file)
logging.info("%s parameters", sum(p.numel() for p in model.parameters()))
def upload_model(config: CPRConfig) -> aiplatform.Model:
"""Tag and upload the model server."""
ar_tag = (
f"{config.region}-docker.pkg.dev/{config.project_id}"
f"/{config.repository}/{config.image}"
)
local_model = build_container(config, tag=ar_tag)
aiplatform.init(project=config.project_id, location=config.region)
local_model.push_image()
aip_model = aiplatform.Model.upload(
local_model=local_model,
display_name=predictor.TimmPredictor.TIMM_MODEL_NAME,
artifact_uri=config.artifact_gcs_dir,
)
config.model_name = aip_model.resource_name
config.save()
return aip_model
def deploy_model(config: CPRConfig) -> aiplatform.Endpoint:
"""Deploy the model server to a Vertex Prediction endpoint."""
aiplatform.init(project=config.project_id, location=config.region)
aip_model = aiplatform.Model(model_name=config.model_name)
endpoint = aip_model.deploy(machine_type=config.machine_type)
config.endpoint_name = endpoint.resource_name
config.save()
return endpoint
def probe_prediction(config: CPRConfig, request_path: str) -> None:
"""Send a sample prediction request to the Vertex Prediction endpoint."""
aiplatform.init(project=config.project_id, location=config.region)
aip_endpoint = aiplatform.Endpoint(endpoint_name=config.endpoint_name)
with open(request_path) as f:
logging.info(aip_endpoint.predict(**json.load(f)))
def main(argv: Sequence[str]):
config = CPRConfig()
if pathlib.Path(config.config_file).exists():
config.load()
actions = set(argv[1:])
if "build" in actions:
build_container(config, config.image)
save_model_artifact(config.artifact_local_dir)
if "upload" in actions:
save_model_artifact(config.artifact_gcs_dir)
upload_model(config)
if "deploy" in actions:
deploy_model(config)
if "probe" in actions:
probe_prediction(config, request_path="sample_request.json")
if __name__ == "__main__":
app.run(main)
@@ -1,76 +0,0 @@
# Copyright 2022 Google LLC
# 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
# https://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 dataclasses
import json
@dataclasses.dataclass
class CPRConfig(object):
"""Configure the build process by editing the default values here.
config_file: File path used to save values in this config. (Some
values, such as the model name, are generated at build time and
depended on by future steps, so saving it allows this script to
deploy the model without re-uploading it, for example.)
base_image: Base Docker image on top of which the model server will
be built. By default, a Debian-based Python 3 image without GPU
support will be used.
image: Name and tag assigned to the built model server image.
artifact_local_dir: Local directory where a copy of the pretrained model weights
will be saved.
region: Google Cloud Region where the model will be uploaded during the
build process.
project_id: Google Cloud project ID.
repository: Name of the Artifact Registry repository where the container
will be uploaded.
artifact_gcs_dir: Location on GCS where a copy of the pretrained model
weights will be uploaded.
model_name: Full resource path of the uploaded model. This is a write-only
field, the value is generated by Vertex AI when the model is uploaded.
endpoint_name: Full resource path of the created endpoint. This is a
write-only field, the value is generated by Vertex AI when the model is
deployed to an endpoint.
machine_type: Machine type to use when deploying the model.
"""
config_file: str = "config.json"
base_image: str = "python:3.10-bullseye"
image: str = "timm_predictor:latest"
artifact_local_dir: str = ""
region: str = "us-central1"
project_id: str = "<your project ID here>"
repository: str = "cpr-images"
artifact_gcs_dir: str = "gs://<your bucket ID here>/timm-vit224/"
model_name: str = ""
endpoint_name: str = ""
machine_type: str = "n1-standard-2"
def save(self):
with open(self.config_file, "w") as f:
json.dump(dataclasses.asdict(self), f, indent=2)
def load(self):
with open(self.config_file) as f:
self.__init__(**json.load(f))
@@ -1,8 +0,0 @@
absl-py==1.1.0
fastapi==0.75.2
uvicorn==0.18.2
timm==0.5.4
smart_open==6.0.0
google-cloud-storage>=1.26.0,<2.0.0dev
google-cloud-aiplatform[prediction]>=1.16.0
File diff suppressed because one or more lines are too long
@@ -1,255 +0,0 @@
"""Test the timm_serving predictor."""
import base64
import json
import logging
import os
import pickle
from typing import List, Dict
from absl import flags
from absl import logging
from absl.testing import absltest
from config import CPRConfig
import fastapi
from google.cloud import aiplatform
from google.cloud.aiplatform import prediction as cpr
import PIL
from timm_serving import predictor
import torch
VIT_SMALL_PARAMS = 22878952
def b64_encode_file(path: str) -> str:
"""Encode a file's contents as base64.
Args:
path: Path to the file.
Returns:
Base64-encoded contents of the file.
"""
with open(path, "rb") as f:
return str(base64.b64encode(f.read()), encoding="utf-8")
def make_instance_dict(
image_paths: List[str], base64_encodings: List[str]
) -> Dict[str, List[str]]:
"""Generate a dictionary similar to a parsed prediction server request.
Args:
image_paths: Paths to image files to include.
base64_encodings: Pre-encoded base64 strings.
Returns:
Dictionary of instances in the format accepted by the preprocessor.
"""
instances = [s for s in base64_encodings]
for path in image_paths:
instances.append(b64_encode_file(path))
return {"instances": instances}
def count_parameters(model: torch.nn.Module):
"""Count the parameters in a Pytorch model.
Args:
model: Pytorch model (nn.Module).
Returns:
Number of parameters in the model.
"""
return sum(p.numel() for p in model.parameters())
class PredictorUnitTests(absltest.TestCase):
"""Unit tests for timm_serving.predictor."""
def setUp(self):
super().setUp()
self.config = CPRConfig()
try:
self.config.load()
except FileNotFoundError:
logging.info("No saved config file found, using default values.")
self.predictor = predictor.TimmPredictor()
def test_load_from_saved_state_dict_ok(self):
self.predictor.load(self.config.artifact_local_dir)
self.assertEqual(count_parameters(self.predictor._model), VIT_SMALL_PARAMS)
def test_load_bad_path(self):
with self.assertRaises(FileNotFoundError):
self.predictor.load("testdata/")
with self.assertRaisesRegex(ValueError, "not a directory"):
self.predictor.load("blah")
def test_load_bad_data(self):
with self.assertRaises(pickle.UnpicklingError):
self.predictor.load("testdata/bad_model_1")
with self.assertRaisesRegex(RuntimeError, "Invalid magic number"):
self.predictor.load("testdata/bad_model_2")
def test_preprocess_ok(self):
self.predictor.load(self.config.artifact_local_dir)
instance_dict = make_instance_dict(
base64_encodings=[],
image_paths=[
"testdata/airplane.jpg",
"testdata/mandrill.tiff",
"testdata/mandrill.tiff",
"testdata/cat_alpha.png",
],
)
result = self.predictor.preprocess(instance_dict)
self.assertEqual(result.size(), torch.Size([4, 3, 224, 224]))
self.assertEqual(result.dtype, torch.float32)
def test_preprocess_no_instances(self):
self.predictor.load(self.config.artifact_local_dir)
with self.assertRaises(fastapi.HTTPException) as ctx:
self.predictor.preprocess({})
self.assertEqual(ctx.exception.status_code, 400)
self.assertRegex(ctx.exception.detail, 'must contain "instances"')
def test_preprocess_wrong_shape_instances(self):
self.predictor.load(self.config.artifact_local_dir)
instance_dict = {"instances": [[b64_encode_file("testdata/mandrill.tiff")]]}
with self.assertRaises(fastapi.HTTPException) as ctx:
self.predictor.preprocess(instance_dict)
self.assertEqual(ctx.exception.status_code, 400)
self.assertRegex(ctx.exception.detail, "not 'list'")
def test_preprocess_bad_base64(self):
self.predictor.load(self.config.artifact_local_dir)
instance_dict = make_instance_dict(base64_encodings=["!@#$"], image_paths=[])
with self.assertRaises(fastapi.HTTPException) as ctx:
self.predictor.preprocess(instance_dict)
self.assertEqual(ctx.exception.status_code, 400)
self.assertRegex(ctx.exception.detail, "[Bb]ase64")
def test_preprocess_not_image_data(self):
self.predictor.load(self.config.artifact_local_dir)
instance_dict = make_instance_dict(
base64_encodings=[], image_paths=["testdata/bad.jpg"]
)
with self.assertRaises(fastapi.HTTPException) as ctx:
self.predictor.preprocess(instance_dict)
self.assertEqual(ctx.exception.status_code, 400)
self.assertRegex(ctx.exception.detail, "image file")
def test_predict_ok(self):
self.predictor.load(self.config.artifact_local_dir)
inputs = torch.zeros(size=[2, 3, 224, 224], dtype=torch.float32)
if torch.cuda.device_count() > 0:
inputs = inputs.cuda()
result = self.predictor.predict(inputs)
self.assertEqual(result.size(), torch.Size([2, 1000]))
self.assertEqual(result.dtype, torch.float32)
def test_postprocess_ok(self):
class_probs = torch.zeros(size=[2, 1000])
class_probs[0, 0] = 1
class_probs[1, 123] = 1
result = self.predictor.postprocess(class_probs)
predictions = result["predictions"]
self.assertLen(predictions[0]["class_names"], 5)
self.assertLen(predictions[0]["indices"], 5)
self.assertLen(predictions[0]["probabilities"], 5)
self.assertLen(predictions[1]["class_names"], 5)
self.assertLen(predictions[1]["indices"], 5)
self.assertLen(predictions[1]["probabilities"], 5)
self.assertContainsSubsequence(predictions[0]["class_names"][0], "tench")
self.assertContainsSubsequence(
predictions[1]["class_names"][0], "spiny lobster"
)
class ServerEndToEndTests(absltest.TestCase):
"""End-to-end tests for the model server, using LocalEndpoint."""
def setUp(self):
super().setUp()
self.config = CPRConfig()
try:
self.config.load()
except FileNotFoundError:
logging.info("No saved config file found, using default values.")
self.local_model = cpr.LocalModel(
serving_container_spec=aiplatform.gapic.ModelContainerSpec(
image_uri=self.config.image
)
)
self.local_endpoint = self.local_model.deploy_to_local_endpoint(
artifact_uri=self.config.artifact_local_dir or os.getcwd()
)
self.local_endpoint.serve()
def tearDown(self):
self.local_endpoint.stop()
super().tearDown()
def test_e2e_healthcheck_ok(self):
health_check_response = self.local_endpoint.run_health_check()
self.assertEqual(health_check_response.status_code, 200)
self.assertEqual(health_check_response.content, b"{}")
def test_e2e_predict_ok(self):
predict_request = json.dumps(
make_instance_dict(
base64_encodings=[],
image_paths=[
"testdata/mandrill.tiff",
],
)
)
response = self.local_endpoint.predict(
request=predict_request, headers={"Content-Type": "application/json"}
)
logging.info(response.content)
self.assertEqual(response.status_code, 200)
predictions = response.json()["predictions"]
self.assertContainsSubsequence(predictions[0]["class_names"][0], "baboon")
def test_e2e_predict_bad_json_returns_400(self):
predict_request = "blah"
response = self.local_endpoint.predict(
request=predict_request, headers={"Content-Type": "application/json"}
)
logging.info(response.content)
self.assertEqual(response.status_code, 400)
def test_e2e_predict_no_instances_returns_400(self):
predict_request = json.dumps({})
response = self.local_endpoint.predict(
request=predict_request, headers={"Content-Type": "application/json"}
)
logging.info(response.content)
self.assertEqual(response.status_code, 400)
def test_e2e_predict_bad_base64_returns_400(self):
predict_request = json.dumps(
make_instance_dict(base64_encodings=["blah"], image_paths=[])
)
response = self.local_endpoint.predict(
request=predict_request, headers={"Content-Type": "application/json"}
)
logging.info(response.content)
self.assertEqual(response.status_code, 400)
def test_e2e_predict_bad_image_returns_400(self):
predict_request = json.dumps(
make_instance_dict(base64_encodings=[], image_paths=["testdata/bad.jpg"])
)
response = self.local_endpoint.predict(
request=predict_request, headers={"Content-Type": "application/json"}
)
logging.info(response.content)
self.assertEqual(response.status_code, 400)
if __name__ == "__main__":
absltest.main()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

@@ -1 +0,0 @@
some non-image data
@@ -1 +0,0 @@
some non-image data
@@ -1 +0,0 @@
blah
Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

File diff suppressed because it is too large Load Diff
@@ -1,178 +0,0 @@
# Copyright 2022 Google LLC
# 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
# https://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.
"""Adapts a pretrained TIMM image classification model to the CPR framework.
Documentation for the TIMM (Torch IMage Models) library is here:
https://rwightman.github.io/pytorch-image-models/
Its source can also be found here:
https://github.com/rwightman/pytorch-image-models
"""
import base64
import binascii
import io
import os
from typing import Dict, List, Union
from fastapi import HTTPException
from google.cloud.aiplatform import prediction as cpr
from pathlib import Path
import PIL
import smart_open
import timm
import torch
import torch.nn.functional as F
with open(Path(__file__).parent.absolute().joinpath("imagenet.txt")) as f:
IMAGENET_CLASSES = f.read().splitlines()
class TimmPredictor(cpr.predictor.Predictor):
"""Predictor class for image models based on TIMM."""
TIMM_MODEL_NAME = os.getenv("TIMM_MODEL_NAME", default="vit_small_patch32_224")
WEIGHTS_FILE = "state_dict.pth"
NUM_TOP_CLASSES_TO_RETURN = 5
def __init__(self):
self._cuda = torch.cuda.device_count() > 0
def load(self, artifacts_uri: str = ""):
"""Initializes the model and preprocessing transforms.
Args:
artifacts_uri: Directory where state dict is stored. Can be a
GCS URI or local path.
"""
if artifacts_uri:
artifact_path = os.path.join(artifacts_uri)
if not (os.path.isdir(artifact_path) or artifact_path.startswith("gs://")):
raise ValueError("Provided artifact_uri is not a directory.")
else:
artifact_path = os.getcwd()
artifact_path = os.path.join(artifact_path, self.WEIGHTS_FILE)
with smart_open.open(artifact_path, "rb") as f:
self._model = torch.load(f)
if self._cuda:
self._model.cuda()
config = timm.data.resolve_data_config(model=self.TIMM_MODEL_NAME, args=[])
self._transform = timm.data.create_transform(
is_training=False, use_prefetcher=False, **config
)
def preprocess(self, request_dict: Dict[str, List[str]]) -> torch.Tensor:
"""Performs preprocessing.
By default, the server expects a request body consisting of a valid JSON
object. This will be parsed by the handler before it's evaluated by the
preprocess method.
Args:
request_dict: Parsed request body. We expect that the input consists of
a list of base64-encoded image files under the "instances" key. (Any
image format that PIL.image.open can handle is okay.)
Returns:
torch.Tensor containing the preprocessed images as a batch. If GPU is
available, the result tensor will be stored on GPU.
"""
if "instances" not in request_dict:
raise HTTPException(
status_code=400,
detail='Request must contain "instances" as a top-level key.',
)
tensors = []
for (i, image) in enumerate(request_dict["instances"]):
# We use Base64 encoding to handle image data.
# This is probably the best we can do while still using JSON input.
# Overriding the input format requires building a custom Handler.
try:
image_bytes = base64.b64decode(image, validate=True)
except (binascii.Error, TypeError) as e:
raise HTTPException(
status_code=400,
detail=f"Base64 decoding of the input image at index {i} failed:"
f" {str(e)}",
)
try:
pil_image = PIL.Image.open(io.BytesIO(image_bytes)).convert("RGB")
except PIL.UnidentifiedImageError:
raise HTTPException(
status_code=400,
detail=f"The input image at index {i} could not be identified as an"
" image file.",
)
tensors.append(self._transform(pil_image))
with torch.inference_mode():
result = torch.stack(tensors)
if self._cuda:
result = result.cuda()
return result
def predict(self, instances: torch.Tensor) -> torch.Tensor:
"""Performs prediction.
Args:
instances: torch.Tensor with type torch.float32 and shape
[?, 3, 224, 224], containing the pre-processed input images.
Returns:
Vector of scores with type torch.float32 and shape [?, 1000],
representing the model's estimate of the likelihood that the
input belongs to the Imagenet class with that index.
"""
with torch.inference_mode():
class_scores = self._model(instances)
return class_scores
def postprocess(
self, class_scores: torch.Tensor
) -> Dict[str, List[Dict[str, Union[str, int, float]]]]:
"""Translate the model output into a classification result.
Args:
class_scores: torch.Tensor with type torch.float32 and shape
[?, 1000], containing the scores assigned to each class by
the model.
Returns:
Dictionary containing the list of classification results. Each
classification result contains the probabilities, class names, and
class indices of the classes with the top class scores as reported by
the model.
"""
class_probs = F.softmax(class_scores, dim=1)
top_k = class_probs.topk(self.NUM_TOP_CLASSES_TO_RETURN)
top_k_values = top_k.values.numpy().tolist()
top_k_indices = top_k.indices.numpy().tolist()
predictions = [
dict(
probabilities=values,
indices=indices,
class_names=[IMAGENET_CLASSES[int(class_num)] for class_num in indices],
)
for (values, indices) in zip(top_k_values, top_k_indices)
]
return {"predictions": predictions}
@@ -1,52 +0,0 @@
# Overview
*Pluto* is a programming environment for Julia, designed to be interactive and helpful. It provides a familiar notebook interface but it is not a Jupyter notebook. The biggest difference is that Pluto notebooks are reactive, changing a variable or function in one cell causes the cells that depend on that variable or function to be reevaluated. Pluto also provides useful interaction mechanisms that allow users to dynamically interact with the notebooks computation state.
The JuliaCon 2020 presentation: [Interactive notebooks ~ Pluto.jl]() provides a good introduction to Pluto. The source is at [fonsp/Pluto.jl]()
# Install Pluto
## Create a Vertex AI JupyterLab Instance
1. From the [GCP console](https://console.cloud.google.com) "hamburger menu"
select Vertex AI > Workbench
2. Click NEW NOTEBOOK
* Choose Python 3 if you won't be using a GPU
* Choose Python 3 (CUDA Toolkit xx.y) if you do want use a GPU
3. Give the notebook an appropriate name
4. Edit Notebook properties if you have special requirements otherwise accept the defaults and click CREATE
5. When the notebook instance is ready click OPEN JUPYTERLAB
## Configure JupyterLab
1. Open a terminal by clicking the Terminal icon.
1. Install the plutoserver
pip3 install git+https://github.com/fonsp/pluto-on-jupyterlab.git
1. In a browser go to [julialang.org/downloads](https://julialang.org/downloads/)
1. In the Current stable release right click on the `Generic Linux on x86 / 64-bit (glibc)` link
Select copy link address
1. Back in the terminal switch to root via
sudo -i
1. Download the release to /opt and install julia in /usr/local/bin
```bash
cd /opt
wget <paste the release link address>
tar xf <name of the downloaded tar file>
ln -s /opt/<julia-x.y.z>/bin/julia /usr/local/bin
^d
```
1. Add the Pluto package to Julia
```bash
julia
julia> ]add Pluto
julia> bksp
julia> using Pluto
julia> ^d
```
1. From the JupyterLab menu bar select File > Shut Down
# Start Pluto
1. Click OPEN JUPYTERLAB in the Workbench
1. In the Notebook section of the Launcher click Pluto.jl
1. The welcome to Pluto.jl screen should appear
@@ -0,0 +1,474 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "a6b56b1c7b76"
},
"outputs": [],
"source": [
"# Copyright 2021 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c414a395a19b"
},
"source": [
"# PyTorch Image Classification Multi-Node Distributed Data Parallel Training on CPU using Vertex Training with Custom Container"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b98238e32cf7"
},
"source": [
"<table align=\"left\">\n",
" <td>\n",
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/master/community-content/pytorch_image_classification_distributed_data_parallel_training_with_vertex_sdk/multi_node_ddp_gloo_vertex_training_with_custom_container.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\">\n",
" View on GitHub\n",
" </a>\n",
" </td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "03d216c7f7b1"
},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c5ac73516218"
},
"outputs": [],
"source": [
"PROJECT_ID = \"YOUR PROJECT ID\"\n",
"BUCKET_NAME = \"gs://YOUR BUCKET NAME\"\n",
"REGION = \"YOUR REGION\"\n",
"SERVICE_ACCOUNT = \"YOUR SERVICE ACCOUNT\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "0b5ae674177e"
},
"outputs": [],
"source": [
"! gsutil ls -al $BUCKET_NAME"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "19a9b3bdd553"
},
"outputs": [],
"source": [
"content_name = \"pt-img-cls-multi-node-ddp-cust-cont\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "57bf6f8b4361"
},
"source": [
"## Local Training"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "e5d8a3443da0"
},
"outputs": [],
"source": [
"! ls trainer"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "07f79309472d"
},
"outputs": [],
"source": [
"! cat trainer/requirements.txt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "e16cd8bb7483"
},
"outputs": [],
"source": [
"! pip install -r trainer/requirements.txt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "0b8a210718c4"
},
"outputs": [],
"source": [
"! cat trainer/task.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c0c6e7dfb3c6"
},
"outputs": [],
"source": [
"%run trainer/task.py --epochs 5 --no-cuda --local-mode"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "31dfdeede587"
},
"outputs": [],
"source": [
"! ls ./tmp"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "48d56ec621cc"
},
"outputs": [],
"source": [
"! rm -rf ./tmp"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8f3ea1210749"
},
"source": [
"## Vertex Training using Vertex SDK and Custom Container"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "93002a20a2a6"
},
"source": [
"### Build Custom Container"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "4130ce43fd08"
},
"outputs": [],
"source": [
"hostname = \"gcr.io\"\n",
"image_name = content_name\n",
"tag = \"latest\"\n",
"\n",
"custom_container_image_uri = f\"{hostname}/{PROJECT_ID}/{image_name}:{tag}\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "2f1fc5b05240"
},
"outputs": [],
"source": [
"! cd trainer && docker build -t $custom_container_image_uri -f Dockerfile ."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "b4f274f499ac"
},
"outputs": [],
"source": [
"! docker run --rm $custom_container_image_uri --epochs 5 --no-cuda --local-mode"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ee1a0a06d0b4"
},
"outputs": [],
"source": [
"! docker push $custom_container_image_uri"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cb763be12fc9"
},
"outputs": [],
"source": [
"! gcloud container images list --repository $hostname/$PROJECT_ID"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "10c8cc6b3334"
},
"source": [
"### Initialize Vertex SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "1a12348169fa"
},
"outputs": [],
"source": [
"! pip install -r requirements.txt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "42e981cefe41"
},
"outputs": [],
"source": [
"from google.cloud import aiplatform\n",
"\n",
"aiplatform.init(\n",
" project=PROJECT_ID,\n",
" staging_bucket=BUCKET_NAME,\n",
" location=REGION,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "73c92c9298e9"
},
"source": [
"### Create a Vertex Tensorboard Instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "bde509558cd5"
},
"outputs": [],
"source": [
"content_name = content_name + \"-cpu\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "6d7908c0083c"
},
"outputs": [],
"source": [
"tensorboard = aiplatform.Tensorboard.create(\n",
" display_name=content_name,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "a1f0a4f54037"
},
"source": [
"#### Option: Use a Previously Created Vertex Tensorboard Instance\n",
"\n",
"```\n",
"tensorboard_name = \"Your Tensorboard Resource Name or Tensorboard ID\"\n",
"tensorboard = aiplatform.Tensorboard(tensorboard_name=tensorboard_name)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "a4cac84e04ac"
},
"source": [
"### Run a Vertex SDK CustomContainerTrainingJob"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "f92e8fdd44ee"
},
"outputs": [],
"source": [
"display_name = content_name\n",
"gcs_output_uri_prefix = f\"{BUCKET_NAME}/{display_name}\"\n",
"\n",
"replica_count = 4\n",
"machine_type = \"n1-standard-4\"\n",
"\n",
"args = [\n",
" \"--backend\",\n",
" \"gloo\",\n",
" \"--no-cuda\",\n",
" \"--batch-size\",\n",
" \"128\",\n",
" \"--epochs\",\n",
" \"25\",\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ae4c57df7e07"
},
"outputs": [],
"source": [
"custom_container_training_job = aiplatform.CustomContainerTrainingJob(\n",
" display_name=display_name,\n",
" container_uri=custom_container_image_uri,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "35cf3ecdf0df"
},
"outputs": [],
"source": [
"custom_container_training_job.run(\n",
" args=args,\n",
" base_output_dir=gcs_output_uri_prefix,\n",
" replica_count=replica_count,\n",
" machine_type=machine_type,\n",
" tensorboard=tensorboard.resource_name,\n",
" service_account=SERVICE_ACCOUNT,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "49d10dded73b"
},
"outputs": [],
"source": [
"print(f\"Custom Training Job Name: {custom_container_training_job.resource_name}\")\n",
"print(f\"GCS Output URI Prefix: {gcs_output_uri_prefix}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "78398f52807b"
},
"source": [
"### Training Output Artifact"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fc74422de1d1"
},
"outputs": [],
"source": [
"! gsutil ls $gcs_output_uri_prefix"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5e99a6a05b10"
},
"source": [
"## Clean Up Artifact"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "b0c1b3f7466b"
},
"outputs": [],
"source": [
"! gsutil rm -rf $gcs_output_uri_prefix"
]
}
],
"metadata": {
"colab": {
"name": "multi_node_ddp_gloo_vertex_training_with_custom_container.ipynb",
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
@@ -0,0 +1,347 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "a6b56b1c7b76"
},
"outputs": [],
"source": [
"# Copyright 2021 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "20a5ea0081d0"
},
"source": [
"# PyTorch Image Classification Multi-Node Distributed Data Parallel Training on GPU using Vertex Training with Custom Container"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8752d4a255fb"
},
"source": [
"<table align=\"left\">\n",
" <td>\n",
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/master/community-content/pytorch_image_classification_distributed_data_parallel_training_with_vertex_sdk/multi_node_ddp_nccl_vertex_training_with_custom_container.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\">\n",
" View on GitHub\n",
" </a>\n",
" </td>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "03d216c7f7b1"
},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c5ac73516218"
},
"outputs": [],
"source": [
"PROJECT_ID = \"YOUR PROJECT ID\"\n",
"BUCKET_NAME = \"gs://YOUR BUCKET NAME\"\n",
"REGION = \"YOUR REGION\"\n",
"SERVICE_ACCOUNT = \"YOUR SERVICE ACCOUNT\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "0b5ae674177e"
},
"outputs": [],
"source": [
"! gsutil ls -al $BUCKET_NAME"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "19a9b3bdd553"
},
"outputs": [],
"source": [
"content_name = \"pt-img-cls-multi-node-ddp-cust-cont\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5307fe28b633"
},
"source": [
"## Vertex Training using Vertex SDK and Custom Container"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "46cb58c7fbf9"
},
"source": [
"### Built Custom Container"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "97e66e9f9bab"
},
"outputs": [],
"source": [
"hostname = \"gcr.io\"\n",
"image_name = content_name\n",
"tag = \"latest\"\n",
"\n",
"custom_container_image_uri = f\"{hostname}/{PROJECT_ID}/{image_name}:{tag}\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ae9b29c4773f"
},
"source": [
"### Initialize Vertex SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dc1e84d5dec2"
},
"outputs": [],
"source": [
"! pip install -r requirements.txt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "6964be27b98e"
},
"outputs": [],
"source": [
"from google.cloud import aiplatform\n",
"\n",
"aiplatform.init(\n",
" project=PROJECT_ID,\n",
" staging_bucket=BUCKET_NAME,\n",
" location=REGION,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "594a91f438f2"
},
"source": [
"### Create a Vertex Tensorboard Instance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "93134273261e"
},
"outputs": [],
"source": [
"content_name = content_name + \"-gpu\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c2bd82dbcd9b"
},
"outputs": [],
"source": [
"tensorboard = aiplatform.Tensorboard.create(\n",
" display_name=content_name,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ebc593c6472e"
},
"source": [
"#### Option: Use a Previously Created Vertex Tensorboard Instance\n",
"\n",
"```\n",
"tensorboard_name = \"Your Tensorboard Resource Name or Tensorboard ID\"\n",
"tensorboard = aiplatform.Tensorboard(tensorboard_name=tensorboard_name)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "0769e8e34c2f"
},
"source": [
"### Run a Vertex SDK CustomContainerTrainingJob"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "023f33ece826"
},
"outputs": [],
"source": [
"display_name = content_name\n",
"gcs_output_uri_prefix = f\"{BUCKET_NAME}/{display_name}\"\n",
"\n",
"replica_count = 1\n",
"machine_type = \"n1-standard-4\"\n",
"accelerator_count = 4\n",
"accelerator_type = \"NVIDIA_TESLA_K80\"\n",
"\n",
"args = [\n",
" \"--backend\",\n",
" \"nccl\",\n",
" \"--batch-size\",\n",
" \"128\",\n",
" \"--epochs\",\n",
" \"25\",\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "d4b599e726ef"
},
"outputs": [],
"source": [
"custom_container_training_job = aiplatform.CustomContainerTrainingJob(\n",
" display_name=display_name,\n",
" container_uri=custom_container_image_uri,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "81321e3bdf7f"
},
"outputs": [],
"source": [
"custom_container_training_job.run(\n",
" args=args,\n",
" base_output_dir=gcs_output_uri_prefix,\n",
" replica_count=replica_count,\n",
" machine_type=machine_type,\n",
" accelerator_count=accelerator_count,\n",
" accelerator_type=accelerator_type,\n",
" tensorboard=tensorboard.resource_name,\n",
" service_account=SERVICE_ACCOUNT,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5100712c2c4c"
},
"outputs": [],
"source": [
"print(f\"Custom Training Job Name: {custom_container_training_job.resource_name}\")\n",
"print(f\"GCS Output URI Prefix: {gcs_output_uri_prefix}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f9b77676e5a6"
},
"source": [
"### Training Output Artifact"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "0e171ce95ace"
},
"outputs": [],
"source": [
"! gsutil ls $gcs_output_uri_prefix"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "cf1b74a12b87"
},
"source": [
"## Clean Up Artifact"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "a0b15089c341"
},
"outputs": [],
"source": [
"! gsutil rm -rf $gcs_output_uri_prefix"
]
}
],
"metadata": {
"colab": {
"name": "multi_node_ddp_nccl_vertex_training_with_custom_container.ipynb",
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
@@ -1,30 +0,0 @@
# PyTorch Deployment on Google Cloud: Text Classification
**This is an Experimental release**, covered by the Pre-GA Offerings Terms of your Google Cloud Platform [Terms of Service](https://cloud.google.com/terms).
Experiments are focused on validating a prototype and are not guaranteed to be released. They are not intended for production use or covered by any SLA, support obligation, or deprecation policy and might be subject to backward-incompatible changes.
**Kindly drop us a note before you run any scale tests.**
**Do not hesitate to contact vertexai-prediction-preview-feedback@google.com if you have any questions or run into any issues.**
The projects need to be added to the allowlist in order to deploy PyTorch models using Vertex AI Prediction pre-built PyTorch images. If you are interested in the feature, please send an email to vertexai-prediction-preview-feedback@google.com to provide your project numbers OR project ids.
## Overview
In the PyTorch on Google Cloud series of blog posts, we aim to share how to deploy PyTorch models at scale on [Vertex AI](https://cloud.google.com/vertex-ai).
This tutorial on text classification shows how to deploy a PyTorch based text classification model on [Vertex AI](https://cloud.google.com/vertex-ai/docs/start/client-libraries#python) using Vertex SDK and [`gcloud ai`](https://cloud.google.com/sdk/gcloud/reference/beta/ai).
## Notebooks
| <h4>Notebook</h4> | <h4>Description</h4> |
| :-------- | :------- |
| [pytorch-text-classification-vertex-ai-deploy.ipynb](./pytorch-text-classification-vertex-ai-deploy.ipynb) | Notebook to show deploying a PyTorch model on Vertex AI |
## Folders
| <h4>Folder Name</h4> | <h4>Description</h4> |
| :-------- | :------- |
| [`predictor`](./predictor) | Folder with custom prediction handler to deploy a PyTorch model to Vertex Prediction. In the [notebook](./pytorch-text-classification-vertex-ai-deploy.ipynb), this folder is used for deploying a PyTorch model on Vertex AI using Vertex Prediction pre-built PyTorch images |
@@ -1,91 +0,0 @@
import os
import json
import logging
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from ts.torch_handler.base_handler import BaseHandler
logger = logging.getLogger(__name__)
class TransformersClassifierHandler(BaseHandler):
"""
The handler takes an input string and returns the classification text
based on the serialized transformers checkpoint.
"""
def __init__(self):
super(TransformersClassifierHandler, self).__init__()
self.initialized = False
def initialize(self, ctx):
""" Loads the model.pt file and initialized the model object.
Instantiates Tokenizer for preprocessor to use
Loads labels to name mapping file for post-processing inference response
"""
self.manifest = ctx.manifest
properties = ctx.system_properties
model_dir = properties.get("model_dir")
self.device = torch.device("cuda:" + str(properties.get("gpu_id")) if torch.cuda.is_available() else "cpu")
# Read model serialize/pt file
serialized_file = self.manifest["model"]["serializedFile"]
model_pt_path = os.path.join(model_dir, serialized_file)
if not os.path.isfile(model_pt_path):
raise RuntimeError("Missing the model.pt or pytorch_model.bin file")
# Load model
self.model = AutoModelForSequenceClassification.from_pretrained(model_dir)
self.model.to(self.device)
self.model.eval()
logger.debug('Transformer model from path {0} loaded successfully'.format(model_dir))
# Ensure to use the same tokenizer used during training
self.tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')
# Read the mapping file, index to object name
mapping_file_path = os.path.join(model_dir, "index_to_name.json")
if os.path.isfile(mapping_file_path):
with open(mapping_file_path) as f:
self.mapping = json.load(f)
else:
logger.warning('Missing the index_to_name.json file. Inference output will default.')
self.mapping = {"0": "Negative", "1": "Positive"}
self.initialized = True
def preprocess(self, data):
""" Preprocessing input request by tokenizing
Extend with your own preprocessing steps as needed
"""
text = data[0].get("data")
if text is None:
text = data[0].get("body")
sentences = text.decode('utf-8')
logger.info("Received text: '%s'", sentences)
# Tokenize the texts
tokenizer_args = ((sentences,))
inputs = self.tokenizer(*tokenizer_args,
padding='max_length',
max_length=128,
truncation=True,
return_tensors = "pt")
return inputs
def inference(self, inputs):
""" Predict the class of a text using a trained transformer model.
"""
prediction = self.model(inputs['input_ids'].to(self.device))[0].argmax().item()
if self.mapping:
prediction = self.mapping[str(prediction)]
logger.info("Model predicted: '%s'", prediction)
return [prediction]
def postprocess(self, inference_output):
return inference_output
@@ -1,5 +0,0 @@
{
"0": "Negative",
"1": "Positive"
}
@@ -2,13 +2,10 @@
FROM pytorch/torchserve:latest-cpu
# install dependencies
RUN python3 -m pip install --upgrade pip
RUN pip3 install transformers
USER model-server
# copy model artifacts, custom handler and other dependencies
COPY ./custom_handler.py /home/model-server/
COPY ./custom_text_handler.py /home/model-server/
COPY ./index_to_name.json /home/model-server/
COPY ./model/finetuned-bert-classifier/ /home/model-server/
@@ -24,7 +21,7 @@ EXPOSE 7080
EXPOSE 7081
# create model archive file packaging model artifacts and dependencies
RUN torch-model-archiver -f --model-name=finetuned-bert-classifier --version=1.0 --serialized-file=/home/model-server/pytorch_model.bin --handler=/home/model-server/custom_handler.py --extra-files "/home/model-server/config.json,/home/model-server/tokenizer.json,/home/model-server/training_args.bin,/home/model-server/tokenizer_config.json,/home/model-server/special_tokens_map.json,/home/model-server/vocab.txt,/home/model-server/index_to_name.json" --export-path=/home/model-server/model-store
RUN torch-model-archiver -f --model-name=finetuned-bert-classifier --version=1.0 --serialized-file=/home/model-server/pytorch_model.bin --handler=/home/model-server/custom_text_handler.py --extra-files "/home/model-server/config.json,/home/model-server/tokenizer.json,/home/model-server/training_args.bin,/home/model-server/tokenizer_config.json,/home/model-server/special_tokens_map.json,/home/model-server/vocab.txt,/home/model-server/index_to_name.json" --export-path=/home/model-server/model-store
# run Torchserve HTTP serve to respond to prediction requests
CMD ["torchserve", "--start", "--ts-config=/home/model-server/config.properties", "--models", "finetuned-bert-classifier=finetuned-bert-classifier.mar", "--model-store", "/home/model-server/model-store"]
CMD ["torchserve", "--start", "--ts-config=/home/model-server/config.properties", "--models", "finetuned-bert-classifier=finetuned-bert-classifier.mar", "--model-store", "/home/model-server/model-store"]
@@ -63,12 +63,12 @@
"- [Training](#Training)\n",
" - [Run Training Locally in the Notebook](#Training-locally-in-the-notebook)\n",
" - [Run Training Job on Vertex AI](#Training-on-Vertex-AI)\n",
" - [Training with pre-built container](#Run-Custom-Job-on-Vertex-AI-Training-with-a-pre-built-container)\n",
" - [Training with custom container](#Run-Custom-Job-on-Vertex-AI-Training-with-custom-container)\n",
" - [Training with pre-built container](#Run-Custom-Job-on-Vertex-Training-with-a-pre-built-container)\n",
" - [Training with custom container](#Run-Custom-Job-on-Vertex-Training-with-custom-container)\n",
"- [Tuning](#Hyperparameter-Tuning) \n",
" - [Run Hyperparameter Tuning job on Vertex AI](#Run-Hyperparameter-Tuning-Job-on-Vertex-AI)\n",
"- [Deploying](#Deploying)\n",
" - [Deploying model on Vertex AI Predictions with custom container](#Deploying-model-on-Vertex AI-Predictions-with-custom-container)\n",
" - [Deploying model on Vertex Predictions with custom container](#Deploying-model-on-Vertex-Predictions-with-custom-container)\n",
"\n",
"### Costs \n",
"\n",
@@ -202,9 +202,9 @@
"id": "e0c1dcadc2c8"
},
"source": [
"We will be using [Vertex AI SDK for Python](https://cloud.google.com/vertex-ai/docs/start/client-libraries#python) to interact with Vertex AI services. The high-level `aiplatform` library is designed to simplify common data science workflows by using wrapper classes and opinionated defaults. \n",
"We will be using [Vertex SDK for Python](https://cloud.google.com/vertex-ai/docs/start/client-libraries#python) to interact with Vertex AI services. The high-level `aiplatform` library is designed to simplify common data science workflows by using wrapper classes and opinionated defaults. \n",
"\n",
"#### Install Vertex AI SDK for Python"
"#### Install Vertex SDK for Python"
]
},
{
@@ -658,8 +658,8 @@
},
"outputs": [],
"source": [
"dataset = load_dataset(\"imdb\")\n",
"dataset"
"datasets = load_dataset(\"imdb\")\n",
"datasets"
]
},
{
@@ -668,7 +668,7 @@
"id": "RzfPtOMoIrIu"
},
"source": [
"The `dataset` object itself is [`DatasetDict`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasetdict), which contains one key for the training, validation and test set."
"The `datasets` object itself is [`DatasetDict`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasetdict), which contains one key for the training, validation and test set."
]
},
{
@@ -681,12 +681,12 @@
"source": [
"print(\n",
" \"Total # of rows in training dataset {} and size {:5.2f} MB\".format(\n",
" dataset[\"train\"].shape[0], dataset[\"train\"].size_in_bytes / (1024 * 1024)\n",
" datasets[\"train\"].shape[0], datasets[\"train\"].size_in_bytes / (1024 * 1024)\n",
" )\n",
")\n",
"print(\n",
" \"Total # of rows in test dataset {} and size {:5.2f} MB\".format(\n",
" dataset[\"test\"].shape[0], dataset[\"test\"].size_in_bytes / (1024 * 1024)\n",
" datasets[\"test\"].shape[0], datasets[\"test\"].size_in_bytes / (1024 * 1024)\n",
" )\n",
")"
]
@@ -708,7 +708,7 @@
},
"outputs": [],
"source": [
"dataset[\"train\"][0]"
"datasets[\"train\"][0]"
]
},
{
@@ -728,7 +728,7 @@
},
"outputs": [],
"source": [
"label_list = dataset[\"train\"].unique(\"label\")\n",
"label_list = datasets[\"train\"].unique(\"label\")\n",
"label_list"
]
},
@@ -779,7 +779,7 @@
},
"outputs": [],
"source": [
"show_random_elements(dataset[\"train\"])"
"show_random_elements(datasets[\"train\"])"
]
},
{
@@ -883,7 +883,7 @@
},
"outputs": [],
"source": [
"example = dataset[\"train\"][4]\n",
"example = datasets[\"train\"][4]\n",
"print(example)"
]
},
@@ -920,7 +920,7 @@
"source": [
"# Dataset loading repeated here to make this cell idempotent\n",
"# Since we are over-writing datasets variable\n",
"dataset = load_dataset(\"imdb\")\n",
"datasets = load_dataset(\"imdb\")\n",
"\n",
"# Mapping labels to ids\n",
"# NOTE: We can extract this automatically but the `Unique` method of the datasets\n",
@@ -948,7 +948,7 @@
"\n",
"\n",
"# apply preprocessing function to input examples\n",
"dataset = dataset.map(preprocess_function, batched=True, load_from_cache_file=True)"
"datasets = datasets.map(preprocess_function, batched=True, load_from_cache_file=True)"
]
},
{
@@ -1091,8 +1091,8 @@
"trainer = Trainer(\n",
" model,\n",
" args,\n",
" train_dataset=dataset[\"train\"],\n",
" eval_dataset=dataset[\"test\"],\n",
" train_dataset=datasets[\"train\"],\n",
" eval_dataset=datasets[\"test\"],\n",
" data_collator=default_data_collator,\n",
" tokenizer=tokenizer,\n",
" compute_metrics=compute_metrics,\n",
@@ -1199,7 +1199,7 @@
"source": [
"### Run predictions locally with sample examples\n",
"\n",
"Using the trained model, we can predict the sentiment label for an input text after applying the preprocessing function that was used during the training. We will run the predictions locally in the notebook and later show how you can deploy the model to an endpoint using [TorchServe](https://pytorch.org/serve/) on Vertex AI Predictions."
"Using the trained model, we can predict the sentiment label for an input text after applying the preprocessing function that was used during the training. We will run the predictions locally in the notebook and later show how you can deploy the model to an endpoint using [TorchServe](https://pytorch.org/serve/) on Vertex Predictions."
]
},
{
@@ -1382,7 +1382,7 @@
"id": "f7466d414a0e"
},
"source": [
"### Run Custom Job on Vertex AI Training with a pre-built container"
"### Run Custom Job on Vertex Training with a pre-built container"
]
},
{
@@ -1395,7 +1395,7 @@
"\n",
"In this notebook, we are using Hugging Face Datasets and fine tuning a transformer model from Hugging Face Transformers Library for sentiment analysis task using PyTorch. We will use [pre-built container for PyTorch](https://cloud.google.com/vertex-ai/docs/training/pre-built-containers#pytorch) and package the training application code by adding standard Python dependencies - `transformers`, `datasets` and `tqdm` - in the `setup.py` file. \n",
"\n",
"![Training with Prebuilt Containers on Vertex AI Training](./images/training-with-prebuilt-containers-on-vertex-training.png)"
"![Training with Prebuilt Containers on Vertex Training](./images/training-with-prebuilt-containers-on-vertex-training.png)"
]
},
{
@@ -1569,7 +1569,7 @@
"source": [
"#### **Run custom training job on Vertex AI**\n",
"\n",
"We use [Vertex AI SDK for Python](https://cloud.google.com/vertex-ai/docs/start/client-libraries#client_libraries) to create and submit training job to the Vertex AI training service."
"We use [Vertex SDK for Python](https://cloud.google.com/vertex-ai/docs/start/client-libraries#client_libraries) to create and submit training job to the Vertex training service."
]
},
{
@@ -1578,7 +1578,7 @@
"id": "5d2957ef04fd"
},
"source": [
"##### **Initialize the Vertex AI SDK for Python**"
"##### **Initialize the Vertex SDK for Python**"
]
},
{
@@ -1598,7 +1598,7 @@
"id": "6b0fed34b728"
},
"source": [
"##### **Configure and submit Custom Job to Vertex AI Training service**"
"##### **Configure and submit Custom Job to Vertex Training service**"
]
},
{
@@ -1609,7 +1609,7 @@
"source": [
"Configure a [Custom Job](https://cloud.google.com/vertex-ai/docs/training/create-custom-job) with the [pre-built container](https://cloud.google.com/vertex-ai/docs/training/pre-built-containers) image for PyTorch and training code packaged as Python source distribution. \n",
"\n",
"**NOTE:** When using Vertex AI SDK for Python for submitting a training job, it creates a [Training Pipeline](https://cloud.google.com/vertex-ai/docs/training/create-training-pipeline) which launches the Custom Job on Vertex AI Training service."
"**NOTE:** When using Vertex SDK for Python for submitting a training job, it creates a [Training Pipeline](https://cloud.google.com/vertex-ai/docs/training/create-training-pipeline) which launches the Custom Job on Vertex Training service."
]
},
{
@@ -1686,7 +1686,7 @@
"\n",
"You can monitor the custom job launched from Cloud Console following the link [here](https://console.cloud.google.com/vertex-ai/training/training-pipelines/) or use gcloud CLI command [`gcloud beta ai custom-jobs stream-logs`](https://cloud.google.com/sdk/gcloud/reference/beta/ai/custom-jobs/stream-logs)\n",
"\n",
"![Monitor custom job progress in Vertex AI Training](./images/vertex-training-monitor-custom-job.png)"
"![Monitor custom job progress in Vertex Training](./images/vertex-training-monitor-custom-job.png)"
]
},
{
@@ -1798,7 +1798,7 @@
"id": "c170d386492b"
},
"source": [
"### Run Custom Job on Vertex AI Training with custom container"
"### Run Custom Job on Vertex Training with custom container"
]
},
{
@@ -1807,7 +1807,7 @@
"id": "035227b6e581"
},
"source": [
"To create a [training job with custom container](https://cloud.google.com/vertex-ai/docs/training/create-custom-container?hl=hr), you define a `Dockerfile` to install or add the dependencies required for the training job. Then, you build and test your Docker image locally to verify, push the image to Container Registry and submit a Custom Job to Vertex AI Training service.\n",
"To create a [training job with custom container](https://cloud.google.com/vertex-ai/docs/training/create-custom-container?hl=hr), you define a `Dockerfile` to install or add the dependencies required for the training job. Then, you build and test your Docker image locally to verify, push the image to Container Registry and submit a Custom Job to Vertex Training service.\n",
"\n",
"![Training with custom containers on Vertex AI](./images/training-with-custom-containers-on-vertex-training.png)"
]
@@ -1834,7 +1834,7 @@
"%%writefile ./custom_container/Dockerfile\n",
"\n",
"# Use pytorch GPU base image\n",
"FROM us-docker.pkg.dev/vertex-ai/training/pytorch-gpu.1-10:latest\n",
"FROM gcr.io/cloud-aiplatform/training/pytorch-gpu.1-7\n",
"\n",
"# set working directory\n",
"WORKDIR /app\n",
@@ -1968,7 +1968,7 @@
"id": "a23e5e34bea9"
},
"source": [
"##### **Initialize the Vertex AI SDK for Python**"
"##### **Initialize the Vertex SDK for Python**"
]
},
{
@@ -1988,11 +1988,11 @@
"id": "abf1fa4085cb"
},
"source": [
"##### **Configure and submit Custom Job to Vertex AI Training service**\n",
"##### **Configure and submit Custom Job to Vertex Training service**\n",
"\n",
"Configure a [Custom Job](https://cloud.google.com/vertex-ai/docs/training/create-custom-job) with the [custom container](https://cloud.google.com/vertex-ai/docs/training/create-custom-container) image with training code and other dependencies\n",
"\n",
"**NOTE:** When using Vertex AI SDK for Python for submitting a training job, it creates a [Training Pipeline](https://cloud.google.com/vertex-ai/docs/training/create-training-pipeline) which launches the Custom Job to train on Vertex AI Training."
"**NOTE:** When using Vertex SDK for Python for submitting a training job, it creates a [Training Pipeline](https://cloud.google.com/vertex-ai/docs/training/create-training-pipeline) which launches the Custom Job to train on Vertex Training."
]
},
{
@@ -2044,7 +2044,7 @@
},
"outputs": [],
"source": [
"# submit the custom job to Vertex AI training service\n",
"# submit the custom job to Vertex training service\n",
"model = job.run(\n",
" replica_count=1,\n",
" machine_type=\"n1-standard-8\",\n",
@@ -2065,7 +2065,7 @@
"\n",
"You can monitor the custom job launched from Cloud Console following the link [here](https://console.cloud.google.com/vertex-ai/training/training-pipelines/) or use gcloud CLI command [`gcloud beta ai custom-jobs stream-logs`](https://cloud.google.com/sdk/gcloud/reference/beta/ai/custom-jobs/stream-logs)\n",
"\n",
"![Monitor custom job progress in Vertex AI Training](./images/vertex-training-monitor-custom-job-container.png)"
"![Monitor custom job progress in Vertex Training](./images/vertex-training-monitor-custom-job-container.png)"
]
},
{
@@ -2148,11 +2148,11 @@
"id": "ba6122f929e3"
},
"source": [
"The training application code for fine-tuning a transformer model for sentiment analysis task uses hyperparameters such as learning rate and weight decay. These hyperparameters control the behavior of the training algorithm and can have a significant effect on the performance of the resulting model. This part of the notebook show how you can automate tuning these hyperparameters with Vertex AI Training service.\n",
"The training application code for fine-tuning a transformer model for sentiment analysis task uses hyperparameters such as learning rate and weight decay. These hyperparameters control the behavior of the training algorithm and can have a significant effect on the performance of the resulting model. This part of the notebook show how you can automate tuning these hyperparameters with Vertex Training service.\n",
"\n",
"We submit a [Hyperparameter Tuning job](https://cloud.google.com/vertex-ai/docs/training/hyperparameter-tuning-overview) to Vertex AI Training service by packaging the training application code and dependencies in a Docker container and push the container to Google Container Registry, similar to running a Custom Job on Vertex AI with Custom Container.\n",
"We submit a [Hyperparameter Tuning job](https://cloud.google.com/vertex-ai/docs/training/hyperparameter-tuning-overview) to Vertex Training service by packaging the training application code and dependencies in a Docker container and push the container to Google Container Registry, similar to running a Custom Job on Vertex AI with Custom Container.\n",
"\n",
"![Hyperparameter Tuning with Custom Containers on Vertex AI Training](./images/hp-tuning-with-custom-containers-on-vertex-training.png)"
"![Hyperparameter Tuning with Custom Containers on Vertex Training](./images/hp-tuning-with-custom-containers-on-vertex-training.png)"
]
},
{
@@ -2163,7 +2163,7 @@
"source": [
"### How hyperparameter tuning works in Vertex AI?\n",
"\n",
"Following are the high level steps involved in running a Hyperparameter Tuning job on Vertex AI Training service:\n",
"Following are the high level steps involved in running a Hyperparameter Tuning job on Vertex Training service:\n",
"\n",
"- You define the hyperparameters to tune the model along with the metric (or goal) to optimize\n",
"- Vertex AI runs multiple trials of your training application with the hyperparameters and limits you specified - maximum number of trials to run and number of parallel trials. \n",
@@ -2297,7 +2297,7 @@
"source": [
"### Run Hyperparameter Tuning Job on Vertex AI\n",
"\n",
"Before submitting the hyperparameter tuning job to Vertex AI, push the custom container image with training application to Google Cloud Container Registry and then submit the job to Vertex AI. We will be using the same image used for running Custom Job on Vertex AI Training service."
"Before submitting the hyperparameter tuning job to Vertex AI, push the custom container image with training application to Google Cloud Container Registry and then submit the job to Vertex AI. We will be using the same image used for running Custom Job on Vertex Training service."
]
},
{
@@ -2326,7 +2326,7 @@
"id": "f60fab07d67c"
},
"source": [
"##### **Initialize the Vertex AI SDK for Python**"
"##### **Initialize the Vertex SDK for Python**"
]
},
{
@@ -2346,7 +2346,7 @@
"id": "6652aa63ddff"
},
"source": [
"##### **Configure and submit Hyperparameter Tuning Job to Vertex AI Training service**\n",
"##### **Configure and submit Hyperparameter Tuning Job to Vertex Training service**\n",
"\n",
"Configure a [Hyperparameter Tuning Job](https://cloud.google.com/vertex-ai/docs/training/using-hyperparameter-tuning) with the [custom container](https://cloud.google.com/vertex-ai/docs/training/create-custom-container) image with training code and other dependencies.\n",
"\n",
@@ -2374,7 +2374,7 @@
"id": "9d46db3a8b23"
},
"source": [
"Define the training arguments with `hp-tune` argument set to `y` so that training application code can report metrics to Vertex AI"
"Define the training arguments with `hp-tune` argument set to `y` so that training application code can report metrics to Vertex"
]
},
{
@@ -2548,7 +2548,7 @@
"\n",
"You can monitor the hyperparameter tuning job launched from Cloud Console following the link [here](https://console.cloud.google.com/vertex-ai/training/hyperparameter-tuning-jobs/) or use gcloud CLI command [`gcloud beta ai custom-jobs stream-logs`](https://cloud.google.com/sdk/gcloud/reference/beta/ai/custom-jobs/stream-logs)\n",
"\n",
"![Monitor hyperparameter tuning job progress in Vertex AI Training](./images/vertex-training-monitor-hptuning-job-container.png)"
"![Monitor hyperparameter tuning job progress in Vertex Training](./images/vertex-training-monitor-hptuning-job-container.png)"
]
},
{
@@ -2557,7 +2557,7 @@
"id": "ba934b434f03"
},
"source": [
"After the job is finished, you can view and format the results of the hyperparameter tuning Trials (run by Vertex AI Training service) as a Pandas dataframe"
"After the job is finished, you can view and format the results of the hyperparameter tuning Trials (run by Vertex Training service) as a Pandas dataframe"
]
},
{
@@ -2612,7 +2612,7 @@
"id": "5dbccb2b7d32"
},
"source": [
"Now from the results of Trials, you can pick the best performing Trial to deploy to Vertex AI Predictions"
"Now from the results of Trials, you can pick the best performing Trial to deploy to Vertex Predictions"
]
},
{
@@ -2701,8 +2701,8 @@
"JOB_NAME=${JOB_PREFIX}-pytorch-hptune-$(date +%Y%m%d%H%M%S)\n",
"echo \"Launching hyperparameter tuning job with display name as \"$JOB_NAME\n",
"\n",
"# BUCKET_NAME is a required parameter to run the cell.\n",
"BUCKET_NAME=$1\n",
"# BUCKET_NAME: Change to your bucket name\n",
"BUCKET_NAME=$1 # <-- CHANGE TO YOUR BUCKET NAME\n",
"\n",
"# APP_NAME: get application name\n",
"APP_NAME=$2\n",
@@ -2711,7 +2711,7 @@
"JOB_DIR=${BUCKET_NAME}/${JOB_PREFIX}/model/${JOB_NAME}\n",
"\n",
"# custom container image URI\n",
"CUSTOM_TRAIN_IMAGE_URI='gcr.io/'${PROJECT_ID}'/pytorch_gpu_train_'${APP_NAME}\n",
"CUSTOM_TRAIN_IMAGE_URI=f'gcr.io/'${PROJECT_ID}'/pytorch_gpu_train_'${APP_NAME}\n",
"\n",
"# ========================================================\n",
"# create hyperparameter tuning configuration file\n",
@@ -2772,20 +2772,20 @@
"source": [
"## Deploying\n",
"\n",
"Deploying a PyTorch model on [Vertex AI Predictions](https://cloud.google.com/vertex-ai/docs/predictions/getting-predictions) requires to use a custom container that serves online predictions. You will deploy a container running [PyTorch's TorchServe](https://pytorch.org/serve/) tool in order to serve predictions from a fine-tuned transformer model from Hugging Face Transformers for sentiment analysis task. You can then use Vertex AI Predictions to classify sentiment of input texts. \n",
"Deploying a PyTorch model on [Vertex Predictions](https://cloud.google.com/vertex-ai/docs/predictions/getting-predictions) requires to use a custom container that serves online predictions. You will deploy a container running [PyTorch's TorchServe](https://pytorch.org/serve/) tool in order to serve predictions from a fine-tuned transformer model from Hugging Face Transformers for sentiment analysis task. You can then use Vertex Predictions to classify sentiment of input texts. \n",
"\n",
"### Deploying model on Vertex AI Predictions with custom container\n",
"### Deploying model on Vertex Predictions with custom container\n",
"\n",
"To use a custom container to serve predictions from a PyTorch model, you must provide Vertex AI with a Docker container image that runs an HTTP server, such as TorchServe in this case. Please refer to [documentation](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements) that describes the container image requirements to be compatible with Vertex AI Predictions.\n",
"To use a custom container to serve predictions from a PyTorch model, you must provide Vertex AI with a Docker container image that runs an HTTP server, such as TorchServe in this case. Please refer to [documentation](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements) that describes the container image requirements to be compatible with Vertex Predictions.\n",
"\n",
"![Serving with Custom Containers on Vertex AI Predictions](./images/serve-pytorch-model-on-vertex-predictions-with-custom-containers.png)\n",
"![Serving with Custom Containers on Vertex Predictions](./images/serve-pytorch-model-on-vertex-predictions-with-custom-containers.png)\n",
"\n",
"Essentially, to deploy a PyTorch model on Vertex AI Predictions following are the steps:\n",
"Essentially, to deploy a PyTorch model on Vertex Predictions following are the steps:\n",
"\n",
"1. Package the trained model artifacts including [default](https://pytorch.org/serve/#default-handlers) or [custom](https://pytorch.org/serve/custom_service.html) handlers by creating an archive file using [Torch model archiver](https://github.com/pytorch/serve/tree/master/model-archiver)\n",
"2. Build a [custom container](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements) compatible with Vertex AI Predictions to serve the model using Torchserve\n",
"3. Upload the model with custom container image to serve predictions as a Vertex AI Model resource\n",
"4. Create a Vertex AI Endpoint and [deploy the model](https://cloud.google.com/vertex-ai/docs/predictions/deploy-model-api) resource"
"2. Build a [custom container](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements) compatible with Vertex Predictions to serve the model using Torchserve\n",
"3. Upload the model with custom container image to serve predictions as a Vertex Model resource\n",
"4. Create a Vertex Endpoint and [deploy the model](https://cloud.google.com/vertex-ai/docs/predictions/deploy-model-api) resource"
]
},
{
@@ -3048,13 +3048,10 @@
"FROM pytorch/torchserve:latest-cpu\n",
"\n",
"# install dependencies\n",
"RUN python3 -m pip install --upgrade pip\n",
"RUN pip3 install transformers\n",
"\n",
"USER model-server\n",
"\n",
"# copy model artifacts, custom handler and other dependencies\n",
"COPY ./custom_handler.py /home/model-server/\n",
"COPY ./custom_text_handler.py /home/model-server/\n",
"COPY ./index_to_name.json /home/model-server/\n",
"COPY ./model/$APP_NAME/ /home/model-server/\n",
"\n",
@@ -3074,7 +3071,7 @@
" --model-name=$APP_NAME \\\n",
" --version=1.0 \\\n",
" --serialized-file=/home/model-server/pytorch_model.bin \\\n",
" --handler=/home/model-server/custom_handler.py \\\n",
" --handler=/home/model-server/custom_text_handler.py \\\n",
" --extra-files \"/home/model-server/config.json,/home/model-server/tokenizer.json,/home/model-server/training_args.bin,/home/model-server/tokenizer_config.json,/home/model-server/special_tokens_map.json,/home/model-server/vocab.txt,/home/model-server/index_to_name.json\" \\\n",
" --export-path=/home/model-server/model-store\n",
"\n",
@@ -3133,7 +3130,7 @@
"source": [
"#### **Run the container locally** ***[Optional]***\n",
"\n",
"Before push the container image to Container Registry to use it with Vertex AI Predictions, you can run it as a container in your local environment to verify that the server works as expected"
"Before push the container image to Container Registry to use it with Vertex Predictions, you can run it as a container in your local environment to verify that the server works as expected"
]
},
{
@@ -3271,9 +3268,9 @@
"id": "69477b3a00c0"
},
"source": [
"#### **Deploying the serving container to Vertex AI Predictions**\n",
"#### **Deploying the serving container to Vertex Predictions**\n",
"\n",
"We create a model resource on Vertex AI and deploy the model to a Vertex AI Endpoints. You must deploy a model to an endpoint before using the model. The deployed model runs the custom container image to serve predictions. "
"We create a model resource on Vertex AI and deploy the model to a Vertex Endpoints. You must deploy a model to an endpoint before using the model. The deployed model runs the custom container image to serve predictions. "
]
},
{
@@ -3304,7 +3301,7 @@
"id": "a3da91e19af4"
},
"source": [
"##### **Initialize the Vertex AI SDK for Python**"
"##### **Initialize the Vertex SDK for Python**"
]
},
{
@@ -3441,7 +3438,7 @@
"id": "bc4673478269"
},
"source": [
"#### **Invoking the Endpoint with deployed Model using Vertex AI SDK to make predictions**"
"#### **Invoking the Endpoint with deployed Model using Vertex SDK to make predictions**"
]
},
{
@@ -3491,7 +3488,7 @@
"source": [
"##### **Formatting input for online prediction**\n",
"\n",
"This notebook uses [Torchserve's KServe based inference API](https://pytorch.org/serve/inference_api.html#kserve-inference-api) which is also [Vertex AI Predictions compatible format](https://cloud.google.com/vertex-ai/docs/predictions/custom-container-requirements#prediction). For online prediction requests, format the prediction input instances as JSON with base64 encoding as shown here:\n",
"For online prediction requests, the prediction input instances must be formatted as JSON with base64 encoding as shown here:\n",
"\n",
"```\n",
"[\n",
@@ -3564,9 +3561,9 @@
},
"source": [
"##### ***[Optional]*** **Make prediction requests using gcloud CLI**\n",
"You can also call the Vertex AI Endpoint to make predictions using [`gcloud beta ai endpoints predict`](https://cloud.google.com/sdk/gcloud/reference/beta/ai/endpoints/predict). \n",
"You can also call the Vertex Endpoint to make predictions using [`gcloud beta ai endpoints predict`](https://cloud.google.com/sdk/gcloud/reference/beta/ai/endpoints/predict). \n",
"\n",
"The following cell shows how to make a prediction request to Vertex AI Endpoints using `gcloud` CLI: "
"The following cell shows how to make a prediction request to Vertex Endpoints using `gcloud` CLI: "
]
},
{
@@ -3657,12 +3654,12 @@
},
"outputs": [],
"source": [
"delete_custom_job = False\n",
"delete_hp_tuning_job = False\n",
"delete_custom_job = True\n",
"delete_hp_tuning_job = True\n",
"delete_endpoint = True\n",
"delete_model = False\n",
"delete_bucket = False\n",
"delete_image = False"
"delete_model = True\n",
"delete_bucket = True\n",
"delete_image = True"
]
},
{
@@ -3690,7 +3687,7 @@
"\n",
"client_options = {\"api_endpoint\": API_ENDPOINT}\n",
"\n",
"# Initialize Vertex AI SDK\n",
"# Initialize Vertex SDK\n",
"aiplatform.init(project=PROJECT_ID, staging_bucket=BUCKET_NAME)"
]
},
@@ -188,14 +188,11 @@
},
"outputs": [],
"source": [
"! pip3 install {USER_FLAG} google-cloud-aiplatform\n",
"! pip3 install {USER_FLAG} google-cloud-pipeline-components\n",
"! pip3 install {USER_FLAG} google-cloud-aiplatform==1.0.1\n",
"! pip3 install {USER_FLAG} google-cloud-pipeline-components==0.1.3\n",
"! pip3 install {USER_FLAG} --upgrade kfp\n",
"! pip3 install {USER_FLAG} numpy\n",
"! pip3 install {USER_FLAG} --upgrade tensorflow\n",
"! pip3 install {USER_FLAG} --upgrade pillow\n",
"! pip3 install {USER_FLAG} --upgrade tf-agents\n",
"! pip3 install {USER_FLAG} --upgrade fastapi"
"! pip3 install {USER_FLAG} numpy==1.20.3\n",
"! pip3 install {USER_FLAG} --upgrade tensorflow"
]
},
{
@@ -290,7 +287,7 @@
"\n",
"# Get your Google Cloud project ID from gcloud\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" shell_output = !gcloud config list --format 'value(core.project)' 2>/dev/null\n",
" shell_output=!gcloud config list --format 'value(core.project)' 2>/dev/null\n",
" PROJECT_ID = shell_output[0]\n",
" print(\"Project ID: \", PROJECT_ID)"
]
@@ -521,7 +518,6 @@
"import os\n",
"import sys\n",
"\n",
"from google.cloud import aiplatform\n",
"from google_cloud_pipeline_components import aiplatform as gcc_aip\n",
"from kfp.v2 import compiler, dsl\n",
"from kfp.v2.google.client import AIPlatformClient"
@@ -561,30 +557,6 @@
"You may use the default values below as is."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "895ac243c125"
},
"outputs": [],
"source": [
"# Dataset parameters\n",
"RAW_DATA_PATH = \"gs://[your-bucket-name]/raw_data/u.data\" # @param {type:\"string\"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "62bfb9a820f6"
},
"outputs": [],
"source": [
"# Download the sample data into your RAW_DATA_PATH\n",
"! gsutil cp \"gs://cloud-samples-data/vertex-ai/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/u.data\" $RAW_DATA_PATH"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -593,6 +565,9 @@
},
"outputs": [],
"source": [
"# Dataset parameters\n",
"RAW_DATA_PATH = \"gs://cloud-samples-data/vertex-ai/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/u.data\" # Location of the MovieLens 100K dataset's \"u.data\" file.\n",
"\n",
"# Pipeline parameters\n",
"PIPELINE_NAME = \"movielens-pipeline\" # Pipeline display name.\n",
"ENABLE_CACHING = False # Whether to enable execution caching for the pipeline.\n",
@@ -660,7 +635,7 @@
"source": [
"#### Run unit tests on the Generator component\n",
"\n",
"Before running the command, you should update the `RAW_DATA_PATH` in [`src/generator/test_generator_component.py`](src/generator/test_generator_component.py)."
"Before running the command, fill in `RAW_DATA_PATH` in [`src/generator/test_generator_component.py`](src/generator/test_generator_component.py)."
]
},
{
@@ -738,12 +713,12 @@
"TRAINING_ARTIFACTS_DIR = (\n",
" f\"{BUCKET_NAME}/artifacts\" # Root directory for training artifacts.\n",
")\n",
"TRAINING_REPLICA_COUNT = 1 # Number of replica to run the custom training job.\n",
"TRAINING_REPLICA_COUNT = \"1\" # Number of replica to run the custom training job.\n",
"TRAINING_MACHINE_TYPE = (\n",
" \"n1-standard-4\" # Type of machine to run the custom training job.\n",
")\n",
"TRAINING_ACCELERATOR_TYPE = \"ACCELERATOR_TYPE_UNSPECIFIED\" # Type of accelerators to run the custom training job.\n",
"TRAINING_ACCELERATOR_COUNT = 0 # Number of accelerators for the custom training job."
"TRAINING_ACCELERATOR_COUNT = \"0\" # Number of accelerators for the custom training job."
]
},
{
@@ -794,12 +769,8 @@
"TRAINED_POLICY_DISPLAY_NAME = (\n",
" \"movielens-trained-policy\" # Display name of the uploaded and deployed policy.\n",
")\n",
"TRAFFIC_SPLIT = {\"0\": 100}\n",
"ENDPOINT_DISPLAY_NAME = \"movielens-endpoint\" # Display name of the prediction endpoint.\n",
"ENDPOINT_MACHINE_TYPE = \"n1-standard-4\" # Type of machine of the prediction endpoint.\n",
"ENDPOINT_REPLICA_COUNT = 1 # Number of replicas of the prediction endpoint.\n",
"ENDPOINT_ACCELERATOR_TYPE = \"ACCELERATOR_TYPE_UNSPECIFIED\" # Type of accelerators to run the custom training job.\n",
"ENDPOINT_ACCELERATOR_COUNT = 0 # Number of accelerators for the custom training job."
"ENDPOINT_MACHINE_TYPE = \"n1-standard-4\" # Type of machine of the prediction endpoint."
]
},
{
@@ -929,17 +900,16 @@
},
"outputs": [],
"source": [
"from google_cloud_pipeline_components.experimental.custom_job import utils\n",
"from kfp.components import load_component_from_url\n",
"\n",
"generate_op = load_component_from_url(\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/62a2a7611499490b4b04d731d48a7ba87c2d636f/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/generator/component.yaml\"\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/68d6cf46ee22a9b9295d62ea71996150baf8db94/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/generator/component.yaml\"\n",
")\n",
"ingest_op = load_component_from_url(\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/62a2a7611499490b4b04d731d48a7ba87c2d636f/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/ingester/component.yaml\"\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/68d6cf46ee22a9b9295d62ea71996150baf8db94/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/ingester/component.yaml\"\n",
")\n",
"train_op = load_component_from_url(\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/62a2a7611499490b4b04d731d48a7ba87c2d636f/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/trainer/component.yaml\"\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/68d6cf46ee22a9b9295d62ea71996150baf8db94/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/trainer/component.yaml\"\n",
")\n",
"\n",
"\n",
@@ -1008,7 +978,7 @@
" bigquery_location=bigquery_location,\n",
" bigquery_table_id=bigquery_table_id,\n",
" )\n",
" \n",
"\n",
" # Run the Ingester component.\n",
" ingest_task = ingest_op(\n",
" project_id=project_id,\n",
@@ -1018,16 +988,7 @@
" )\n",
"\n",
" # Run the Trainer component and submit custom job to Vertex AI.\n",
" # Convert the train_op component into a Vertex AI Custom Job pre-built component\n",
" custom_job_training_op = utils.create_custom_training_job_op_from_component(\n",
" component_spec=train_op,\n",
" replica_count=TRAINING_REPLICA_COUNT,\n",
" machine_type=TRAINING_MACHINE_TYPE,\n",
" accelerator_type=TRAINING_ACCELERATOR_TYPE,\n",
" accelerator_count=TRAINING_ACCELERATOR_COUNT,\n",
" )\n",
"\n",
" train_task = custom_job_training_op(\n",
" train_task = train_op(\n",
" training_artifacts_dir=training_artifacts_dir,\n",
" tfrecord_file=ingest_task.outputs[\"tfrecord_file\"],\n",
" num_epochs=num_epochs,\n",
@@ -1035,10 +996,28 @@
" num_actions=num_actions,\n",
" tikhonov_weight=tikhonov_weight,\n",
" agent_alpha=agent_alpha,\n",
" project=PROJECT_ID,\n",
" location=REGION,\n",
" )\n",
"\n",
" worker_pool_specs = [\n",
" {\n",
" \"containerSpec\": {\n",
" \"imageUri\": train_task.container.image,\n",
" },\n",
" \"replicaCount\": TRAINING_REPLICA_COUNT,\n",
" \"machineSpec\": {\n",
" \"machineType\": TRAINING_MACHINE_TYPE,\n",
" \"acceleratorType\": TRAINING_ACCELERATOR_TYPE,\n",
" \"acceleratorCount\": TRAINING_ACCELERATOR_COUNT,\n",
" },\n",
" },\n",
" ]\n",
" train_task.custom_job_spec = {\n",
" \"displayName\": train_task.name,\n",
" \"jobSpec\": {\n",
" \"workerPoolSpecs\": worker_pool_specs,\n",
" },\n",
" }\n",
"\n",
" # Run the Deployer components.\n",
" # Upload the trained policy as a model.\n",
" model_upload_op = gcc_aip.ModelUploadOp(\n",
@@ -1055,14 +1034,11 @@
" # Deploy the uploaded, trained policy to the created endpoint. (This operation\n",
" # has to occur after both model uploading and endpoint creation complete.)\n",
" gcc_aip.ModelDeployOp(\n",
" project=project_id,\n",
" endpoint=endpoint_create_op.outputs[\"endpoint\"],\n",
" model=model_upload_op.outputs[\"model\"],\n",
" deployed_model_display_name=TRAINED_POLICY_DISPLAY_NAME,\n",
" traffic_split=TRAFFIC_SPLIT,\n",
" dedicated_resources_machine_type=ENDPOINT_MACHINE_TYPE,\n",
" dedicated_resources_accelerator_type=ENDPOINT_ACCELERATOR_TYPE,\n",
" dedicated_resources_accelerator_count=ENDPOINT_ACCELERATOR_COUNT,\n",
" dedicated_resources_min_replica_count=ENDPOINT_REPLICA_COUNT,\n",
" machine_type=ENDPOINT_MACHINE_TYPE,\n",
" )"
]
},
@@ -1077,11 +1053,12 @@
"# Compile the authored pipeline.\n",
"compiler.Compiler().compile(pipeline_func=pipeline, package_path=PIPELINE_SPEC_PATH)\n",
"\n",
"# Createa Vertex AI client.\n",
"api_client = AIPlatformClient(project_id=PROJECT_ID, region=REGION)\n",
"\n",
"# Create a pipeline run job.\n",
"job = aiplatform.PipelineJob(\n",
" display_name=f\"{PIPELINE_NAME}-startup\",\n",
" template_path=PIPELINE_SPEC_PATH,\n",
" pipeline_root=PIPELINE_ROOT,\n",
"response = api_client.create_run_from_job_spec(\n",
" job_spec_path=PIPELINE_SPEC_PATH,\n",
" parameter_values={\n",
" # Pipeline configs\n",
" \"project_id\": PROJECT_ID,\n",
@@ -1093,9 +1070,7 @@
" \"bigquery_table_id\": BIGQUERY_TABLE_ID,\n",
" },\n",
" enable_caching=ENABLE_CACHING,\n",
")\n",
"\n",
"job.run()"
")"
]
},
{
@@ -1136,11 +1111,7 @@
"SIMULATOR_SCHEDULE = \"*/5 * * * *\" # Cloud Scheduler cron job schedule for the Simulator. Eg. \"*/5 * * * *\" means every 5 mins.\n",
"SIMULATOR_SCHEDULER_MESSAGE = (\n",
" \"simulator-message\" # Cloud Scheduler message for the Simulator.\n",
")\n",
"# TF-Agents RL configs\n",
"BATCH_SIZE = 8\n",
"RANK_K = 20\n",
"NUM_ACTIONS = 20"
")"
]
},
{
@@ -1250,7 +1221,7 @@
},
"outputs": [],
"source": [
"endpoints = ! gcloud ai endpoints list \\\n",
"endpoints = ! gcloud beta ai endpoints list \\\n",
" --region=$REGION \\\n",
" --filter=display_name=$ENDPOINT_DISPLAY_NAME\n",
"print(\"\\n\".join(endpoints), \"\\n\")\n",
@@ -1453,11 +1424,13 @@
},
"outputs": [],
"source": [
"from kfp.components import load_component_from_url\n",
"\n",
"ingest_op = load_component_from_url(\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/62a2a7611499490b4b04d731d48a7ba87c2d636f/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/ingester/component.yaml\"\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/68d6cf46ee22a9b9295d62ea71996150baf8db94/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/ingester/component.yaml\"\n",
")\n",
"train_op = load_component_from_url(\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/62a2a7611499490b4b04d731d48a7ba87c2d636f/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/trainer/component.yaml\"\n",
" \"https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/68d6cf46ee22a9b9295d62ea71996150baf8db94/community-content/tf_agents_bandits_movie_recommendation_with_kfp_and_vertex_sdk/mlops_pipeline_tf_agents_bandits_movie_recommendation/src/trainer/component.yaml\"\n",
")\n",
"\n",
"\n",
@@ -1508,16 +1481,7 @@
" )\n",
"\n",
" # Run the Trainer component and submit custom job to Vertex AI.\n",
" # Convert the train_op component into a Vertex AI Custom Job pre-built component\n",
" custom_job_training_op = utils.create_custom_training_job_op_from_component(\n",
" component_spec=train_op,\n",
" replica_count=TRAINING_REPLICA_COUNT,\n",
" machine_type=TRAINING_MACHINE_TYPE,\n",
" accelerator_type=TRAINING_ACCELERATOR_TYPE,\n",
" accelerator_count=TRAINING_ACCELERATOR_COUNT,\n",
" )\n",
"\n",
" train_task = custom_job_training_op(\n",
" train_task = train_op(\n",
" training_artifacts_dir=training_artifacts_dir,\n",
" tfrecord_file=ingest_task.outputs[\"tfrecord_file\"],\n",
" num_epochs=num_epochs,\n",
@@ -1525,10 +1489,28 @@
" num_actions=num_actions,\n",
" tikhonov_weight=tikhonov_weight,\n",
" agent_alpha=agent_alpha,\n",
" project=PROJECT_ID,\n",
" location=REGION,\n",
" )\n",
"\n",
" worker_pool_specs = [\n",
" {\n",
" \"containerSpec\": {\n",
" \"imageUri\": train_task.container.image,\n",
" },\n",
" \"replicaCount\": TRAINING_REPLICA_COUNT,\n",
" \"machineSpec\": {\n",
" \"machineType\": TRAINING_MACHINE_TYPE,\n",
" \"acceleratorType\": TRAINING_ACCELERATOR_TYPE,\n",
" \"acceleratorCount\": TRAINING_ACCELERATOR_COUNT,\n",
" },\n",
" },\n",
" ]\n",
" train_task.custom_job_spec = {\n",
" \"displayName\": train_task.name,\n",
" \"jobSpec\": {\n",
" \"workerPoolSpecs\": worker_pool_specs,\n",
" },\n",
" }\n",
"\n",
" # Run the Deployer components.\n",
" # Upload the trained policy as a model.\n",
" model_upload_op = gcc_aip.ModelUploadOp(\n",
@@ -1545,13 +1527,11 @@
" # Deploy the uploaded, trained policy to the created endpoint. (This operation\n",
" # has to occur after both model uploading and endpoint creation complete.)\n",
" gcc_aip.ModelDeployOp(\n",
" project=project_id,\n",
" endpoint=endpoint_create_op.outputs[\"endpoint\"],\n",
" model=model_upload_op.outputs[\"model\"],\n",
" deployed_model_display_name=TRAINED_POLICY_DISPLAY_NAME,\n",
" dedicated_resources_machine_type=ENDPOINT_MACHINE_TYPE,\n",
" dedicated_resources_accelerator_type=ENDPOINT_ACCELERATOR_TYPE,\n",
" dedicated_resources_accelerator_count=ENDPOINT_ACCELERATOR_COUNT,\n",
" dedicated_resources_min_replica_count=ENDPOINT_REPLICA_COUNT,\n",
" machine_type=ENDPOINT_MACHINE_TYPE,\n",
" )"
]
},
@@ -39,15 +39,14 @@ outputs:
- {name: bigquery_table_id, type: String}
implementation:
container:
image: python:3.7
image: tensorflow/tensorflow:2.5.0
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'google-cloud-bigquery==2.20.0' 'pillow' 'tensorflow==2.5.0' 'tf-agents==0.8.0'
|| PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'google-cloud-bigquery==2.20.0' 'pillow' 'tensorflow==2.5.0' 'tf-agents==0.8.0'
--user) && "$0" "$@"
'google-cloud-bigquery==2.20.0' 'tensorflow==2.5.0' 'tf-agents==0.8.0' || PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location 'google-cloud-bigquery==2.20.0'
'tensorflow==2.5.0' 'tf-agents==0.8.0' --user) && "$0" "$@"
- sh
- -ec
- |
@@ -297,8 +296,7 @@ implementation:
def _serialize_str(str_value: str) -> str:
if not isinstance(str_value, str):
raise TypeError('Value "{}" has type "{}" instead of str.'.format(
str(str_value), str(type(str_value))))
raise TypeError('Value "{}" has type "{}" instead of str.'.format(str(str_value), str(type(str_value))))
return str_value
import argparse
@@ -20,7 +20,7 @@ outputs:
- {name: tfrecord_file, type: String}
implementation:
container:
image: python:3.7
image: tensorflow/tensorflow:2.5.0
command:
- sh
- -c
@@ -187,8 +187,7 @@ implementation:
def _serialize_str(str_value: str) -> str:
if not isinstance(str_value, str):
raise TypeError('Value "{}" has type "{}" instead of str.'.format(
str(str_value), str(type(str_value))))
raise TypeError('Value "{}" has type "{}" instead of str.'.format(str(str_value), str(type(str_value))))
return str_value
import argparse
@@ -1,4 +1,3 @@
google-cloud-bigquery==2.20.0
tensorflow==2.7.2
pillow==9.0.1
tensorflow==2.5.3
tf-agents==0.8.0
@@ -1,4 +1,2 @@
google-cloud-pubsub==2.5.0
pillow==9.0.1
tf-agents==0.8.0
tensorflow==2.7.2
tensorflow==2.5.3
@@ -1,5 +0,0 @@
dataclasses==0.6
google-cloud-aiplatform==1.8.1
tensorflow==2.7.2
pillow==9.0.1
tf-agents==0.8.0
@@ -27,14 +27,14 @@ outputs:
- {name: training_artifacts_dir, type: String}
implementation:
container:
image: python:3.7
image: tensorflow/tensorflow:2.5.0
command:
- sh
- -c
- (PIP_DISABLE_PIP_VERSION_CHECK=1 python3 -m pip install --quiet --no-warn-script-location
'tensorflow==2.5.0' 'tf-agents==0.8.0' 'Pillow' || PIP_DISABLE_PIP_VERSION_CHECK=1
python3 -m pip install --quiet --no-warn-script-location 'tensorflow==2.5.0'
'tf-agents==0.8.0' 'Pillow' --user) && "$0" "$@"
'tensorflow==2.5.0' 'tf-agents==0.8.0' || PIP_DISABLE_PIP_VERSION_CHECK=1 python3
-m pip install --quiet --no-warn-script-location 'tensorflow==2.5.0' 'tf-agents==0.8.0'
--user) && "$0" "$@"
- sh
- -ec
- |
@@ -270,8 +270,7 @@ implementation:
def _serialize_str(str_value: str) -> str:
if not isinstance(str_value, str):
raise TypeError('Value "{}" has type "{}" instead of str.'.format(
str(str_value), str(type(str_value))))
raise TypeError('Value "{}" has type "{}" instead of str.'.format(str(str_value), str(type(str_value))))
return str_value
import argparse
@@ -22,13 +22,13 @@ from src.training import task
# Paths and configurations
DATA_PATH = "gs://[your-bucket-name]/artifacts/u.data" # FILL IN
DATA_PATH = "gs://[your-bucket-name]/[your-dataset-dir]/u.data" # FILL IN
ROOT_DIR = "gs://[your-bucket-name]/artifacts" # FILL IN
ARTIFACTS_DIR = "gs://[your-bucket-name]/artifacts" # FILL IN
PROFILER_DIR = "gs://[your-bucket-name]/profiler" # FILL IN
HPTUNING_RESULT_DIR = "[your-hptuning-result-dir]/" # FILL IN
HPTUNING_RESULT_PATH = os.path.join(HPTUNING_RESULT_DIR,
"result.json") # FILL IN
"[your-file-name].json") # FILL IN
RAW_BUCKET_NAME = "[your-hptuning-result-bucket-name]" # FILL IN
# Hyperparameters
@@ -1 +1 @@
tensorflow==2.7.2
tensorflow==2.5.3
@@ -8,7 +8,7 @@
},
"outputs": [],
"source": [
"# Copyright 2022 Google LLC\n",
"# Copyright 2021 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
@@ -113,8 +113,8 @@
},
"outputs": [],
"source": [
"! gcloud ai custom-jobs local-run \\\n",
" --executor-image-uri=$BASE_IMAGE_URI \\\n",
"! gcloud beta ai custom-jobs local-run \\\n",
" --base-image=$BASE_IMAGE_URI \\\n",
" --script=$SCRIPT_PATH \\\n",
" --output-image-uri=$OUTPUT_IMAGE_NAME \\\n",
" -- \\\n",
-5
View File
@@ -1,5 +0,0 @@
The [official](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/notebooks/official) folder contains notebooks organized by Google Cloud product. These are tested weekly and maintained by Google.
The [community](https://github.com/GoogleCloudPlatform/vertex-ai-samples/tree/main/notebooks/community) folder contains notebooks that may be created by Google or external contributors. They are not necessary maintained.
Contributions to the repo should use the [notebook template](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/notebook_template.ipynb) as a starting point.
+11 -27
View File
@@ -3,34 +3,18 @@
# @global-owner1 and @global-owner2 will be requested for
# review when someone opens a pull request.
/sdk/sdk_* @andrewferlitsch
/gapic @andrewferlitsch
/gapic/custom/showcase_custom_image_classification_online_explain_example_based_api.ipynb @inardini
/ml_ops @andrewferlitsch
/model_monitoring/* @andrewferlitsch
/sdk/sdk_* @aferlitsch
/gapic @aferlitsch
/ml_ops @aferlitsch
/model_monitoring/* @mco
/structured_data/rapid_prototyping_* @rafael-carvalho
/managed_notebooks/
/bigquery_ml/ @polong
/sdk/SDK_FBProphet_Forecasting_Online.ipynb @brianchunkang
/managed_notebooks/ @notebooks-team
/sdk/SDK_FBProphet_Forecasting_Online.ipynb @brianchunkang
/pipelines/google_cloud_pipeline_components_TPU_model_train_upload_deploy.ipynb @brianchunkang
/explainable_ai/SDK_Custom_Container_XAI.ipynb @brianchunkang
/matching_engine/sdk_matching_engine_for_indexing.ipynb @ivanmkc
/matching_engine/matching_engine_for_indexing.ipynb @yinghsienwu
/matching_engine/stream_update_for_matching_engine.ipynb @peterping666
/sdk/SDK_AutoML_Forecasting_Model_Training_Example.ipynb @thehardikv
/sdk/sdk_automl_forecasting_evaluating_a_model.ipynb @thehardikv
/matching_engine @yinghsienwu
/neo4j @benofben @htappen
/sdk/pytorch_lightning_custom_container_training.ipynb @brianchunkang
/tensorboard @yfang1
/feature_store @nayaknishant @morgandu
/prediction @googleapis/vertex-prediction-team
/vertex_endpoints/tf_hub_obj_detection/deploy_tfhub_object_detection_on_vertex_endpoints.ipynb @entrpn
/vertex_endpoints/nvidia-triton/nvidia-triton-custom-container-prediction.ipynb @RajeshThallam
/vertex_endpoints/optimized_tensorflow_runtime @vlasenkoalexey
/notebooks/community/ml_ops/stage2/get_started_with_visionapi_and_automl.ipynb @mansari
/notebooks/community/neo4j/graph_paysim.ipynb @benofben @laeg
/notebooks/community/ml_ops/stage1/get_started_with_visionapi_and_vertex_datasets.ipynb @mansari
/notebooks/community/pipelines/google_cloud_pipeline_components_bqml_pipeline_demand_forecasting.ipynb @inardini
/notebooks/community/ml_ops/stage2/get_started_vertex_hpt_r_kernel.ipynb @fhirschmann
/notebooks/community/ml_ops/stage2/get_started_vertex_training_r_using_r_kernel.ipynb @fhirschmann
/notebooks/community/vertex-ai-samples/notebooks/community/model_registry/vertex_ai_model_registry_bqml_custom_model_versioning.ipynb @inardini
/notebooks/community/vertex-ai-samples/notebooks/community/model_registry/vertex_ai_model_registry_automl_model_versioning.ipynb @inardini
/notebooks/community/vizier/conversions_vertex_vizier_and_open_source_vizier.ipynb @halio-g
/tensorboard @yfang1 @wattli
Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

@@ -1,55 +1,29 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"metadata": {
"id": "503077811e70"
},
"outputs": [],
"source": [
"# Copyright 2022 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "e885ac09bc73"
},
"source": [
"# Train a multi-class classification model for ads-targeting\n",
"<table align=\"left\">\n",
"\n",
" <td>\n",
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/ads_targetting/training-multi-class-classification-model-for-ads-targeting-usecase.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\" alt=\"Colab logo\"> Run in Colab\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/ads_targetting/training-multi-class-classification-model-for-ads-targeting-usecase.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\">\n",
" View on GitHub\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/ads_targetting/training-multi-class-classification-model-for-ads-targeting-usecase.ipynb\">\n",
" <img src=\"https://lh3.googleusercontent.com/UiNooY4LUgW_oTvpsNhPpQzsstV5W8F7rYgxgGBD85cWJoLmrOzhVs_ksK_vgx40SHs7jCqkTkCk=e14-rj-sc0xffffff-h130-w32\" alt=\"Vertex AI logo\">\n",
" Open in Vertex AI Workbench\n",
" </a>\n",
" </td> \n",
"</table>"
"## Table of contents\n",
"\n",
"* [Overview](#section-1)\n",
"* [Dataset](#section-2)\n",
"* [Objective](#section-3)\n",
"* [Costs](#section-4)\n",
"* [Tutorial](#section-5)\n",
"\t- [Fetch the data from BigQuery](#section-5)\n",
" - [Preprocess the data](#section-6)\n",
" - [Train a TensorFlow model](#section-7)\n",
" - [Run the model on test data](#section-8)\n",
" - [Automating the execution of the notebook using executor](#section-9)\n",
" - [Scheduled runs on executor](#section-10)\n",
" - [Parameterizing the variables](#section-11)\n",
"* [Save the model to a Cloud Storage path](#section-12)\n",
"* [Clean up](#section-13)\n"
]
},
{
@@ -59,19 +33,23 @@
},
"source": [
"## Overview\n",
"<a name=\"section-1\"></a>\n",
"\n",
"This tutorial demonstrates how to build a machine learning model for an ads-targeting use case. Ads-targeting is an advertisement technique where chosen or tailor-made ads are shown to the customers based on their past behavior and preferences. Targeted ads are meant to reach specific customers based on demographics, psychographics, behavior, and other second-order activities that are learned usually through data collected from the customers.\n",
"\n",
"*Note: If you are using [Vertex AI Workbench managed notebooks](https://cloud.google.com/vertex-ai/docs/workbench/managed/create-instance) instance use the `TensorFlow 2 (Local)` kernel. Some components of this notebook may not work in other notebook environments.*\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1bea2b6e9b25"
},
"source": [
"*Note: This notebook file was designed to run in a [Vertex AI Workbench managed notebooks](https://cloud.google.com/vertex-ai/docs/workbench/managed/create-instance) instance using the `TensorFlow 2 (Local)` kernel. Some components of this notebook may not work in other notebook environments.*\n",
"\n",
"## Dataset\n",
"<a name=\"section-2\"></a>\n",
"\n",
"This tutorial uses the `looker-private-demo.ecomm` dataset in BigQuery. The dataset consists of information about various advertisement campaigns including the demographics of users who have clicked and made some purchases after seeing the ads. For this tutorial, the top three campaigns from the USA are selected from this dataset and user information for those who have made purchases shall be used to train a model with the campaigns as the classes. The idea is to see if the advertisement and the user data can be used to identify which campaign is best-suited for the user.\n",
"\n",
"The dataset can be accessed by pinning the `looker-private-demo` project in BigQuery. Instead of going to the BigQuery user interface, this process can be performed from the JupyterLab user interface on a Vertex AI Workbench managed notebooks instance. Vertex AI Workbench managed notebooks instances support browsing through the datasets and tables from BigQuery through its BigQuery integration. \n",
"\n",
"<img src=\"images/Bigquery_UI_new.PNG\"></img>\n",
"\n",
"## Objective\n",
"<a name=\"section-3\"></a>\n",
"\n",
"This tutorial demonstrates how to collect data from BigQuery, preprocess it, and train a multi-class classification model on an E-commerce dataset. The steps performed include the following:\n",
"\n",
@@ -81,31 +59,10 @@
"- Evaluate the loss for the trained model\n",
"- Automate the notebook execution using the executor feature\n",
"- Save the model to a Cloud Storage path\n",
"- Clean up the created resources"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "34d623e6dfa3"
},
"source": [
"## Dataset\n",
"- Clean up the created resources\n",
"\n",
"This tutorial uses the `looker-private-demo.ecomm` dataset in BigQuery. The dataset consists of information about various advertisement campaigns including the demographics of users who have clicked and made some purchases after seeing the ads. For this tutorial, the top three campaigns from the USA are selected from this dataset and user information for those who have made purchases shall be used to train a model with the campaigns as the classes. The idea is to see if the advertisement and the user data can be used to identify which campaign is best-suited for the user.\n",
"\n",
"The dataset can be accessed by pinning the `looker-private-demo` project in BigQuery. If you are using Vertex AI Workbench managed notebooks instance, instead of going to the BigQuery user interface, this process can be performed from the JupyterLab user interface. Vertex AI Workbench managed notebooks instances support browsing through the datasets and tables from BigQuery through its BigQuery integration. \n",
"\n",
"<img src=\"images/Bigquery_UI_new.PNG\"></img>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ee02650bb7fd"
},
"source": [
"### Costs \n",
"<a name=\"section-4\"></a>\n",
"\n",
"This tutorial uses billable components of Google Cloud:\n",
"\n",
@@ -121,121 +78,6 @@
"to generate a cost estimate based on your projected usage."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "y320EIk-kXT7"
},
"source": [
"### Set up your local development environment\n",
"\n",
"**If you are using Colab or Vertex AI Workbench Notebooks**, your environment already meets\n",
"all the requirements to run this notebook. You can skip this step.\n",
"\n",
"**Otherwise**, make sure your environment meets this notebook's requirements.\n",
"You need the following:\n",
"\n",
"* The Google Cloud SDK\n",
"* Git\n",
"* Python 3\n",
"* virtualenv\n",
"* Jupyter notebook running in a virtual environment with Python 3\n",
"\n",
"The Google Cloud guide to [Setting up a Python development\n",
"environment](https://cloud.google.com/python/setup) and the [Jupyter\n",
"installation guide](https://jupyter.org/install) provide detailed instructions\n",
"for meeting these requirements. The following steps provide a condensed set of\n",
"instructions:\n",
"\n",
"1. [Install and initialize the Cloud SDK.](https://cloud.google.com/sdk/docs/)\n",
"\n",
"1. [Install Python 3.](https://cloud.google.com/python/setup#installing_python)\n",
"\n",
"1. [Install\n",
" virtualenv](https://cloud.google.com/python/setup#installing_and_using_virtualenv)\n",
" and create a virtual environment that uses Python 3. Activate the virtual environment.\n",
"\n",
"1. To install Jupyter, run `pip3 install jupyter` on the\n",
"command-line in a terminal shell.\n",
"\n",
"1. To launch Jupyter, run `jupyter notebook` on the command-line in a terminal shell.\n",
"\n",
"1. Open this notebook in the Jupyter Notebook Dashboard."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1DouUvNOkXT8"
},
"source": [
"### Install additional packages\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Ayt1jhFXkXT9"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"# The Vertex AI Workbench Notebook product has specific requirements\n",
"IS_WORKBENCH_NOTEBOOK = os.getenv(\"DL_ANACONDA_HOME\")\n",
"IS_USER_MANAGED_WORKBENCH_NOTEBOOK = os.path.exists(\n",
" \"/opt/deeplearning/metadata/env_version\"\n",
")\n",
"\n",
"# Vertex AI Notebook requires dependencies to be installed with '--user'\n",
"USER_FLAG = \"\"\n",
"if IS_WORKBENCH_NOTEBOOK:\n",
" USER_FLAG = \"--user\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "95826791kXT_"
},
"outputs": [],
"source": [
"! pip3 install {USER_FLAG} --upgrade pandas-gbq 'google-cloud-bigquery[bqstorage,pandas]' tensorflow sklearn protobuf==3.20.1 -q \\\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "aNeMRbpukXUA"
},
"source": [
"### Restart the kernel\n",
"\n",
"After you install the additional packages, you need to restart the notebook kernel so it can find the packages."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dJ_yvi_9kXUB"
},
"outputs": [],
"source": [
"# Automatically restart kernel after installs\n",
"import os\n",
"\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" # Automatically restart kernel after installs\n",
" import IPython\n",
"\n",
" app = IPython.Application.instance()\n",
" app.kernel.do_shutdown(True)"
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -255,67 +97,34 @@
},
"outputs": [],
"source": [
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5bf9979b96ff"
},
"outputs": [],
"source": [
"if PROJECT_ID == \"\" or PROJECT_ID is None or PROJECT_ID == \"[your-project-id]\":\n",
" # Get your GCP project id from gcloud\n",
" shell_output = ! gcloud config list --format 'value(core.project)' 2>/dev/null\n",
"PROJECT_ID = \"\"\n",
"\n",
"# Get your Google Cloud project ID from gcloud\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" shell_output=!gcloud config list --format 'value(core.project)' 2>/dev/null\n",
" PROJECT_ID = shell_output[0]\n",
" print(\"Project ID:\", PROJECT_ID)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "07-xo93jlC6l"
},
"outputs": [],
"source": [
"! gcloud config set project $PROJECT_ID"
" print(\"Project ID: \", PROJECT_ID)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "03d8d65b914d"
"id": "d0058f55f8cf"
},
"source": [
"#### Region\n",
"\n",
"You can also change the `REGION` variable, which is used for operations\n",
"throughout the rest of this notebook. Below are regions supported for Vertex AI. We recommend that you choose the region closest to you.\n",
"\n",
"- Americas: `us-central1`\n",
"- Europe: `europe-west4`\n",
"- Asia Pacific: `asia-east1`\n",
"\n",
"You may not use a multi-regional bucket for training with Vertex AI. Not all regions provide support for all Vertex AI services.\n",
"\n",
"Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
"Otherwise, set your project ID here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3281bedf6d3c"
"id": "19579640c063"
},
"outputs": [],
"source": [
"REGION = \"[your-region]\" # @param {type: \"string\"}\n",
"\n",
"if REGION == \"[your-region]\":\n",
" REGION = \"us-central1\""
"if PROJECT_ID == \"\" or PROJECT_ID is None:\n",
" PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
@@ -342,74 +151,6 @@
"TIMESTAMP = datetime.now().strftime(\"%Y%m%d%H%M%S\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "OoPGk5KOkXUG"
},
"source": [
"### Authenticate your Google Cloud account\n",
"\n",
"**If you are using Vertex AI Workbench Notebooks**, your environment is already\n",
"authenticated. Skip this step.\n",
"\n",
"**If you are using Colab**, run the cell below and follow the instructions\n",
"when prompted to authenticate your account via oAuth.\n",
"\n",
"**Otherwise**, follow these steps:\n",
"\n",
"1. In the Cloud Console, go to the [**Create service account key**\n",
" page](https://console.cloud.google.com/apis/credentials/serviceaccountkey).\n",
"\n",
"2. Click **Create service account**.\n",
"\n",
"3. In the **Service account name** field, enter a name, and\n",
" click **Create**.\n",
"\n",
"4. In the **Grant this service account access to project** section, click the **Role** drop-down list. Type \"Vertex AI\"\n",
"into the filter box, and select\n",
" **Vertex AI Administrator**. Type \"Storage Object Admin\" into the filter box, and select **Storage Object Admin**.\n",
"\n",
"5. Click *Create*. A JSON file that contains your key downloads to your\n",
"local environment.\n",
"\n",
"6. Enter the path to your service account key as the\n",
"`GOOGLE_APPLICATION_CREDENTIALS` variable in the cell below and run the cell."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Teyy6LGqkXUG"
},
"outputs": [],
"source": [
"# If you are running this notebook in Colab, run this cell and follow the\n",
"# instructions to authenticate your GCP account. This provides access to your\n",
"# Cloud Storage bucket and lets you submit training jobs and prediction\n",
"# requests.\n",
"\n",
"import os\n",
"import sys\n",
"\n",
"# If on Vertex AI Workbench, then don't execute this code\n",
"IS_COLAB = \"google.colab\" in sys.modules\n",
"if not os.path.exists(\"/opt/deeplearning/metadata/env_version\") and not os.getenv(\n",
" \"DL_ANACONDA_HOME\"\n",
"):\n",
" if \"google.colab\" in sys.modules:\n",
" from google.colab import auth as google_auth\n",
"\n",
" google_auth.authenticate_user()\n",
"\n",
" # If you are running this notebook locally, replace the string below with the\n",
" # path to your service account key and run this cell to authenticate your GCP\n",
" # account.\n",
" elif not os.getenv(\"IS_TESTING\"):\n",
" %env GOOGLE_APPLICATION_CREDENTIALS ''"
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -420,8 +161,20 @@
"\n",
"**The following steps are required, regardless of your notebook environment.**\n",
"\n",
"When you submit a training job using the Cloud SDK, you upload a Python package\n",
"containing your training code to a Cloud Storage bucket. Vertex AI runs\n",
"the code from this package. In this tutorial, Vertex AI also saves the\n",
"trained model that results from your job in the same bucket. Using this model artifact, you can then\n",
"create Vertex AI model and endpoint resources in order to serve\n",
"online predictions.\n",
"\n",
"Set the name of your Cloud Storage bucket below. It must be unique across all\n",
"Cloud Storage buckets.\n"
"Cloud Storage buckets.\n",
"\n",
"You may also change the `REGION` variable, which is used for operations\n",
"throughout the rest of this notebook. Make sure to [choose a region where Vertex AI services are\n",
"available](https://cloud.google.com/vertex-ai/docs/general/locations#available_regions). You may\n",
"not use a Multi-Regional Storage bucket for training with Vertex AI."
]
},
{
@@ -432,8 +185,8 @@
},
"outputs": [],
"source": [
"BUCKET_NAME = \"[your-bucket-name]\" # @param {type:\"string\"}\n",
"BUCKET_URI = f\"gs://{BUCKET_NAME}\""
"BUCKET_NAME = \"gs://[your-bucket-name]\" # @param {type:\"string\"}\n",
"REGION = \"[your-region]\" # @param {type:\"string\"}"
]
},
{
@@ -444,9 +197,8 @@
},
"outputs": [],
"source": [
"if BUCKET_NAME == \"\" or BUCKET_NAME is None or BUCKET_NAME == \"[your-bucket-name]\":\n",
" BUCKET_NAME = PROJECT_ID + \"aip-\" + TIMESTAMP\n",
" BUCKET_URI = f\"gs://{BUCKET_NAME}\""
"if BUCKET_NAME == \"\" or BUCKET_NAME is None or BUCKET_NAME == \"gs://[your-bucket-name]\":\n",
" BUCKET_NAME = \"gs://\" + PROJECT_ID + \"aip-\" + TIMESTAMP"
]
},
{
@@ -466,7 +218,7 @@
},
"outputs": [],
"source": [
"! gsutil mb -l $REGION $BUCKET_URI"
"! gsutil mb -l $REGION $BUCKET_NAME"
]
},
{
@@ -486,36 +238,7 @@
},
"outputs": [],
"source": [
"! gsutil ls -al $BUCKET_URI"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "bmnMD2MjkXUJ"
},
"source": [
"### Import libraries and define constants"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "oqtZRqDEkXUJ"
},
"outputs": [],
"source": [
"import warnings\n",
"\n",
"import pandas as pd\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import StandardScaler\n",
"from tensorflow.keras import Sequential\n",
"from tensorflow.keras.layers import Dense\n",
"from tensorflow.keras.utils import to_categorical\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
"! gsutil ls -al $BUCKET_NAME"
]
},
{
@@ -526,16 +249,8 @@
"source": [
"## Tutorial\n",
"\n",
"### Fetch the data from BigQuery \n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5c07be8840ae"
},
"source": [
"If you are using ***Vertex AI Workbench managed notebooks instance***, below cell which starts with \"#@bigquery\" will be a SQL Query. If you are using Vertex AI Workbench user managed notebooks instance or Colab it will be a markdown cell."
"### Fetch the data from BigQuery \n",
"<a name=\"section-5\"></a>"
]
},
{
@@ -616,7 +331,7 @@
"id": "923fdd823683"
},
"source": [
"If you are using Vertex AI Workbench managed notebooks instance, once the results from BigQuery are displayed in the above cell, click the **Query and load as DataFrame** button and execute the generated code stub to fetch the data into the current notebook as a dataframe.\n",
"Once the results from BigQuery are displayed in the above cell, click the **Query and load as DataFrame** button and execute the generated code stub to fetch the data into the current notebook as a dataframe.\n",
"\n",
"*Note: By default the data is loaded into a `df` variable, though this can be changed before executing the cell if required.*"
]
@@ -633,7 +348,7 @@
"# Comment out otherwise for speed-up.\n",
"from google.cloud.bigquery import Client\n",
"\n",
"client = Client(project=PROJECT_ID)\n",
"client = Client()\n",
"\n",
"query = \"\"\"WITH traindata AS (\n",
"SELECT b.* except(ad_event_id, user_id), c.* except(id), d.* except(keyword_id, ad_id), a.amount, a.device_type, e.name\n",
@@ -664,6 +379,44 @@
},
"source": [
"### Preprocess the data\n",
"<a name=\"section-6\"></a>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "e8503e799eec"
},
"source": [
"Import the required libraries."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5b11973ccf76"
},
"outputs": [],
"source": [
"import warnings\n",
"\n",
"import pandas as pd\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import StandardScaler\n",
"from tensorflow.keras import Sequential\n",
"from tensorflow.keras.layers import Dense\n",
"from tensorflow.keras.utils import to_categorical\n",
"\n",
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "e48d156d8bb6"
},
"source": [
"Select the necessary columns from the E-commerce data and divide them based on their type (numerical/categorical)."
]
},
@@ -688,22 +441,13 @@
"num_cols = [\"age\", \"cpc_bid_amount\", \"quality_score\", \"amount\"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9bd71de0d37e"
},
"source": [
"#### Select top three campaigns"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ace612851261"
},
"source": [
"From the current dataset, only the top three campaigns will be chosen to target the users. All the relevant information about the advertisement and the user who purchased an item after seeing the advertisement is available in the dataframe already. "
"From the current dataset, only the top three camapigns will be chosen to target the users. All the relevant information about the advertisement and the user who purchased an item after seeing the advertisement is available in the dataframe already. "
]
},
{
@@ -737,22 +481,13 @@
"df[\"name\"] = df[\"name\"].map({\"Tops & Tees\": 0, \"Active\": 1, \"Accessories\": 2})"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c2d5338b1b95"
},
"source": [
"#### One-hot encode the categorical variables"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8902f763d1ca"
},
"source": [
"After one-hot encoding, the first level-column is dropped to avoid the [dummy-variable trap](https://en.wikipedia.org/wiki/Dummy_variable_(statistics)) scenario. This process is called *dummy-encoding*."
"One-hot encode the categorical variables. After one-hot encoding, the first level-column is dropped to avoid the [dummy-variable trap](https://en.wikipedia.org/wiki/Dummy_variable_(statistics)) scenario. This process is called *dummy-encoding*."
]
},
{
@@ -786,7 +521,7 @@
"id": "3abf027eda2d"
},
"source": [
"#### Split the data into train and test."
"Split the data into train and test."
]
},
{
@@ -811,7 +546,7 @@
"id": "d1a32b9d9640"
},
"source": [
"#### Scale the data."
"Scale the data."
]
},
{
@@ -834,7 +569,16 @@
},
"source": [
"### Train a TensorFlow model\n",
"#### Convert the target column to a categorical encoded colum (one-hot encoded)."
"<a name=\"section-7\"></a>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3e7656556a48"
},
"source": [
"Convert the target column to a categorical encoded colum (one-hot encoded)."
]
},
{
@@ -855,7 +599,7 @@
"id": "3dd0014a7e1d"
},
"source": [
"#### Define hyperparameters for model training. \n",
"Define hyperparameters for model training. \n",
"\n",
"*Note: Comment or remove the parameters from the following cell if they are provided already as an input parameter through the executor feature.*"
]
@@ -880,7 +624,7 @@
"id": "406b731f576b"
},
"source": [
"#### Define the architecture and compile the model."
"Define the architecture and compile the model."
]
},
{
@@ -920,7 +664,7 @@
"id": "4ab12c34f258"
},
"source": [
"#### Fit the model."
"Fit the model."
]
},
{
@@ -940,7 +684,8 @@
"id": "51a2d0b52df3"
},
"source": [
"### Run the model on test data\n"
"### Run the model on test data\n",
"<a name=\"section-8\"></a>"
]
},
{
@@ -949,7 +694,7 @@
"id": "f08445f2cd02"
},
"source": [
"#### Evaluate the model on test data."
"Evaluate the model on test data."
]
},
{
@@ -964,24 +709,16 @@
"print(f\"Test results - Loss: {test_results}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "81ef0e081340"
},
"source": [
"**Please note that executor feature is available only in Vertex AI Workbench managed notebooks**"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9769168778e8"
},
"source": [
"### Automating the execution of the notebook using executor in Vertex AI Workbench managed notebooks instance\n",
"### Automating the execution of the notebook using executor\n",
"<a name=\"section-9\"></a>\n",
"\n",
"If you are using Vertex AI Workbench managed notebooks instance, the executor can help you run a notebook file from start to end, with your choice of the environment, machine type, input parameters, and other characteristics. After setting up an execution, the notebook is executed as a job in Vertex AI custom training. Your jobs can be monitored from the <b>Notebook Executor</b> pane in the menu on the left.\n",
"The executor can help you run a notebook file from start to end, with your choice of the environment, machine type, input parameters, and other characteristics. After setting up an execution, the notebook is executed as a job in Vertex AI custom training. Your jobs can be monitored from the <b>Notebook Executor</b> pane in the menu on the left.\n",
"\n",
"<img src=\"images/executor.png\"></img>\n",
"\n",
@@ -994,9 +731,10 @@
"id": "cf486c351581"
},
"source": [
"### Scheduled runs on executor in Vertex AI Workbench managed notebooks instance\n",
"### Scheduled runs on executor\n",
"<a name=\"section-10\"></a>\n",
"\n",
"Vertex AI Workbench managed noteboook runs can also be scheduled recurringly with the executor. To do so, select <b>Schedule-based recurring executions</b> as the run type instead of <b>One-time execution</b>. The frequency of the job and the time when it executes is provided when you create the execution.\n",
"Notebook runs can also be scheduled recurringly with the executor. To do so, select <b>Schedule-based recurring executions</b> as the run type instead of <b>One-time execution</b>. The frequency of the job and the time when it executes is provided when you create the execution.\n",
"\n",
"<img src=\"images/executor_scheduled_runs2.png\"></img>"
]
@@ -1008,8 +746,9 @@
},
"source": [
"### Parameterizing the variables\n",
"<a name=\"section-11\"></a>\n",
"\n",
"If you are using Vertex AI Workbench managed notebooks instance, executor lets you run a notebook with different sets of input parameters. If required, constants in the notebook can be treated as arguments to a function, and when you submit the execution, you can provide those constants as input parameters.\n",
"Executor lets you run a notebook with different sets of input parameters. If required, constants in the notebook can be treated as arguments to a function, and when you submit the execution, you can provide those constants as input parameters.\n",
"\n",
"<img src=\"images/executor_input_parameters.png\"></img>\n",
"\n",
@@ -1023,6 +762,7 @@
},
"source": [
"### Save the model to a Cloud Storage path\n",
"<a name=\"section-12\"></a>\n",
"\n",
"TensorFlow's `model.save()` method supports Cloud Storage paths as well as the local file paths while writing the model object to a file. It needs to be ensured that the service account being used to run this notebook has `write` permissions to the specified Cloud Storage path."
]
@@ -1035,7 +775,7 @@
},
"outputs": [],
"source": [
"GCS_PATH = BUCKET_URI + \"/path-to-save/\"\n",
"GCS_PATH = \"gs://\" + BUCKET_NAME + \"/[path-to-save]/\"\n",
"model.save(GCS_PATH)"
]
},
@@ -1046,6 +786,7 @@
},
"source": [
"## Clean up\n",
"<a name=\"section-13\"></a>\n",
"\n",
"To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n",
"project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n",
@@ -1061,11 +802,7 @@
},
"outputs": [],
"source": [
"# Delete the Cloud Storage bucket\n",
"\n",
"delete_bucket = False\n",
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
" ! gsutil -m rm -r $BUCKET_URI"
"! gsutil -m rm -r [cloud-storage-folder-path-to-delete]"
]
}
],

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -1,56 +1,34 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"metadata": {
"id": "5fcd3e4da897"
"id": "c4b363e1330b"
},
"outputs": [],
"source": [
"# Copyright 2022 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
"# Build a fraud detection model on Vertex AI"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "05c670d35496"
"id": "4c5fb7f2090f"
},
"source": [
"# Build a fraud detection model on Vertex AI\n",
"## Table of contents\n",
"\n",
"<table align=\"left\">\n",
"\n",
" <td>\n",
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/fraud_detection/fraud-detection-model.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\" alt=\"Colab logo\"> Run in Colab\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/fraud_detection/fraud-detection-model.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\">\n",
" View on GitHub\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/fraud_detection/fraud-detection-model.ipynb\">\n",
" <img src=\"https://lh3.googleusercontent.com/UiNooY4LUgW_oTvpsNhPpQzsstV5W8F7rYgxgGBD85cWJoLmrOzhVs_ksK_vgx40SHs7jCqkTkCk=e14-rj-sc0xffffff-h130-w32\" alt=\"Vertex AI logo\">\n",
" Open in Vertex AI Workbench\n",
" </a>\n",
" </td>\n",
"</table>"
"* [Overview](#section-1)\n",
"* [Dataset](#section-2)\n",
"* [Objective](#section-3)\n",
"* [Costs](#section-4)\n",
"* [Analyze the dataset](#section-5)\n",
"* [Fit a random forest model](#section-6)\n",
"* [Analyzing results](#section-7)\n",
"* [Save the model to a Cloud Storagae path](#section-8)\n",
"* [Create a model in Vertex AI](#section-9)\n",
"* [Create an Endpoint](#section-10) \n",
"* [What-If Tool ](#section-11)\n",
"* [Clean up](#section-12)"
]
},
{
@@ -60,8 +38,24 @@
},
"source": [
"## Overview\n",
"<a name=\"section-1\"></a>\n",
"\n",
"This tutorial shows you how to build, deploy, and analyze predictions from a simple [random forest](https://en.wikipedia.org/wiki/Random_forest) model using tools like scikit-learn, Vertex AI, and the [What-IF Tool (WIT)](https://cloud.google.com/ai-platform/prediction/docs/using-what-if-tool) on a synthetic fraud transaction dataset to solve a financial fraud detection problem.\n"
"This tutorial shows you how to build, deploy, and analyze predictions from a simple [random forest](https://en.wikipedia.org/wiki/Random_forest) model using tools like scikit-learn, Vertex AI, and the [What-IF Tool (WIT)](https://cloud.google.com/ai-platform/prediction/docs/using-what-if-tool) on a synthetic fraud transaction dataset to solve a financial fraud detection problem.\n",
"\n",
"*Note: This notebook file was designed to run in a [Vertex AI Workbench managed notebooks](https://cloud.google.com/vertex-ai/docs/workbench/managed/create-instance) instance using the `TensorFlow 2 (Local)` kernel. Some components of this notebook may not work in other notebook environments.*"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9625185ccee9"
},
"source": [
"## Dataset\n",
"<a name=\"section-2\"></a>\n",
"\n",
"\n",
"The dataset used in this tutorial is publicly available at Kaggle. See [Synthetic Financial Datasets For Fraud Detection](https://www.kaggle.com/ealaxi/paysim1)."
]
},
{
@@ -70,17 +64,11 @@
"id": "411d886b6144"
},
"source": [
"### Objective\n",
"## Objective\n",
"<a name=\"section-3\"></a>\n",
"\n",
"This tutorial demonstrates data analysis and model-building using a synthetic financial dataset. The model is trained on identifying fraudulent cases among the transactions. Then, the trained model is deployed on a Vertex AI Endpoint and analyzed using the What-If Tool. The steps taken in this tutorial are as follows: \n",
"\n",
"This tutorial uses the following Google Cloud ML services and resources:\n",
"\n",
"- Vertex AI Model\n",
"- Vertex AI Endpoint\n",
"\n",
"The steps performed include:\n",
"\n",
"- Installation of required libraries\n",
"- Reading the dataset from a Cloud Storage bucket\n",
"- Performing exploratory analysis on the dataset\n",
@@ -92,25 +80,14 @@
"- Un-deploying the model and cleaning up the model resources"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3037523e7523"
},
"source": [
"### Dataset\n",
"\n",
"\n",
"The dataset used in this tutorial is publicly available at Kaggle. See [Synthetic Financial Datasets For Fraud Detection](https://www.kaggle.com/ealaxi/paysim1)."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "65f7cca50e5c"
},
"source": [
"### Costs\n",
"## Costs\n",
"<a name=\"section-4\"></a>\n",
"\n",
"\n",
"This tutorial uses billable components of Google Cloud:\n",
@@ -125,62 +102,18 @@
"to generate a cost estimate based on your projected usage. "
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "cd1bc75a1cb2"
},
"source": [
"### Set up your local development environment\n",
"\n",
"**If you are using Colab or Vertex AI Workbench Notebooks**, your environment already meets\n",
"all the requirements to run this notebook. You can skip this step.\n",
"\n",
"**_NOTE_**: This notebook has been tested in the following environment:\n",
"\n",
"* Python version = 3.9\n",
"\n",
"**Otherwise**, make sure your environment meets this notebook's requirements.\n",
"You need the following:\n",
"\n",
"* The Google Cloud SDK\n",
"\n",
"The Google Cloud guide to [Setting up a Python development\n",
"environment](https://cloud.google.com/python/setup) and the [Jupyter\n",
"installation guide](https://jupyter.org/install) provide detailed instructions\n",
"for meeting these requirements. The following steps provide a condensed set of\n",
"instructions:\n",
"\n",
"1. [Install and initialize the Cloud SDK.](https://cloud.google.com/sdk/docs/)\n",
"\n",
"1. [Install Python 3.](https://cloud.google.com/python/setup#installing_python)\n",
"\n",
"1. [Install\n",
" virtualenv](https://cloud.google.com/python/setup#installing_and_using_virtualenv)\n",
" and create a virtual environment that uses Python 3. Activate the virtual environment.\n",
"\n",
"1. To install Jupyter, run `pip3 install jupyter` on the\n",
"command-line in a terminal shell.\n",
"\n",
"1. To launch Jupyter, run `jupyter notebook` on the command-line in a terminal shell.\n",
"\n",
"1. Open this notebook in the Jupyter Notebook Dashboard."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "611991f03b38"
},
"source": [
"## Installation\n",
"\n",
"Install the following packages required to execute this notebook. "
"## Installation"
]
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {
"id": "172533a994ad"
},
@@ -188,74 +121,34 @@
"source": [
"import os\n",
"\n",
"# The Vertex AI Workbench Notebook product has specific requirements\n",
"IS_WORKBENCH_NOTEBOOK = os.getenv(\"DL_ANACONDA_HOME\")\n",
"IS_USER_MANAGED_WORKBENCH_NOTEBOOK = os.path.exists(\n",
" \"/opt/deeplearning/metadata/env_version\"\n",
")\n",
"import google.auth\n",
"\n",
"# Vertex AI Notebook requires dependencies to be installed with '--user'\n",
"USER_FLAG = \"\"\n",
"if IS_WORKBENCH_NOTEBOOK:\n",
" USER_FLAG = \"--user\"\n",
"\n",
"# Install the latest version of the Vertex AI client library.\n",
"! pip3 install --upgrade google-cloud-aiplatform witwidget scikit-learn fsspec gcsfs {USER_FLAG} -q\n",
"! pip3 install protobuf==3.20.1 -q"
"# Google Cloud Notebook requires dependencies to be installed with '--user'\n",
"if \"default\" in dir(google.auth):\n",
" USER_FLAG = \"--user\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1c7b2a25df27"
"id": "a465cf9367de"
},
"source": [
"### Restart the kernel\n",
"Install the latest version of the Vertex AI client library.\n",
"\n",
"After you install the additional packages, you need to restart the notebook kernel so it can find the packages."
"Run the following command in your virtual environment to install the Vertex SDK for Python:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {
"id": "2117d92e6766"
"id": "6380f7ee5f54"
},
"outputs": [],
"source": [
"# Automatically restart kernel after installs\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" # Automatically restart kernel after installs\n",
" import IPython\n",
"\n",
" app = IPython.Application.instance()\n",
" app.kernel.do_shutdown(True)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7a5cb1df1ef7"
},
"source": [
"## Before you begin\n",
"\n",
"### Set up your Google Cloud project\n",
"\n",
"**The following steps are required, regardless of your notebook environment.**\n",
"\n",
"1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n",
"\n",
"1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
"\n",
"1. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com). \n",
"\n",
"1. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
"\n",
"1. Enter your project ID in the cell below. Then run the cell to make sure the\n",
"Cloud SDK uses the right project for all the commands in this notebook.\n",
"\n",
"**Note**: Jupyter runs lines prefixed with `!` as shell commands, and it interpolates Python variables prefixed with `$` into these commands."
"! pip install {USER_FLAG} --upgrade google-cloud-aiplatform"
]
},
{
@@ -273,71 +166,38 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dcdfccf50581"
"id": "b27f37ed1ccf"
},
"outputs": [],
"source": [
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5bf9979b96ff"
},
"outputs": [],
"source": [
"if PROJECT_ID == \"\" or PROJECT_ID is None or PROJECT_ID == \"[your-project-id]\":\n",
" # Get your GCP project id from gcloud\n",
" shell_output = ! gcloud config list --format 'value(core.project)' 2>/dev/null\n",
"PROJECT_ID = \"\"\n",
"\n",
"# Get your Google Cloud project ID from gcloud\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" shell_output = !gcloud config list --format 'value(core.project)' 2>/dev/null\n",
" PROJECT_ID = shell_output[0]\n",
" print(\"Project ID:\", PROJECT_ID)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "b11114d77c5f"
},
"outputs": [],
"source": [
"! gcloud config set project $PROJECT_ID"
" print(\"Project ID: \", PROJECT_ID)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "264543a144ad"
"id": "8827f32850b2"
},
"source": [
"#### Region\n",
"\n",
"You can also change the `REGION` variable, which is used for operations\n",
"throughout the rest of this notebook. Below are regions supported for Vertex AI. It is recommended that you choose the region closest to you.\n",
"\n",
"- Americas: `us-central1`\n",
"- Europe: `europe-west4`\n",
"- Asia Pacific: `asia-east1`\n",
"\n",
"You may not use a multi-regional bucket for training with Vertex AI. Not all regions provide support for all Vertex AI services.\n",
"\n",
"Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
"Otherwise, set your project ID here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3281bedf6d3c"
"id": "3dbdf6a5c539"
},
"outputs": [],
"source": [
"REGION = \"[your-region]\" # @param {type: \"string\"}\n",
"\n",
"if REGION == \"[your-region]\":\n",
" REGION = \"us-central1\""
"if PROJECT_ID == \"\" or PROJECT_ID is None:\n",
" PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
@@ -346,9 +206,9 @@
"id": "e663bd062c6f"
},
"source": [
"#### UUID\n",
"#### Timestamp\n",
"\n",
"If you are in a live tutorial session, you might be using a shared test account or project. To avoid name collisions between users on resources created, you create a uuid for each instance session, and append it onto the name of resources you create in this tutorial."
"If you are in a live tutorial session, you might be using a shared test account or project. To avoid name collisions between users on resources created, you create a timestamp for each instance session, and append it onto the name of resources you create in this tutorial."
]
},
{
@@ -359,84 +219,9 @@
},
"outputs": [],
"source": [
"import random\n",
"import string\n",
"from datetime import datetime\n",
"\n",
"\n",
"# Generate a uuid of length 8\n",
"def generate_uuid():\n",
" return \"\".join(random.choices(string.ascii_lowercase + string.digits, k=8))\n",
"\n",
"\n",
"UUID = generate_uuid()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "72bf8f7c9ab3"
},
"source": [
"### Authenticate your Google Cloud account\n",
"\n",
"**If you are using Vertex AI Workbench Notebooks**, your environment is already\n",
"authenticated. Skip this step.\n",
"\n",
"**If you are using Colab**, run the cell below and follow the instructions\n",
"when prompted to authenticate your account via oAuth.\n",
"\n",
"**Otherwise**, follow these steps:\n",
"\n",
"1. In the Cloud Console, go to the [**Create service account key**\n",
" page](https://console.cloud.google.com/apis/credentials/serviceaccountkey).\n",
"\n",
"2. Click **Create service account**.\n",
"\n",
"3. In the **Service account name** field, enter a name, and\n",
" click **Create**.\n",
"\n",
"4. In the **Grant this service account access to project** section, click the **Role** drop-down list. Type \"Vertex AI\"\n",
"into the filter box, and select\n",
" **Vertex AI Administrator**. Type \"Storage Object Admin\" into the filter box, and select **Storage Object Admin**.\n",
"\n",
"5. Click *Create*. A JSON file that contains your key downloads to your\n",
"local environment.\n",
"\n",
"6. Enter the path to your service account key as the\n",
"`GOOGLE_APPLICATION_CREDENTIALS` variable in the cell below and run the cell."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "da63f3587ef9"
},
"outputs": [],
"source": [
"# If you are running this notebook in Colab, run this cell and follow the\n",
"# instructions to authenticate your GCP account. This provides access to your\n",
"# Cloud Storage bucket and lets you submit training jobs and prediction\n",
"# requests.\n",
"\n",
"import os\n",
"import sys\n",
"\n",
"# If on Vertex AI Workbench, then don't execute this code\n",
"IS_COLAB = \"google.colab\" in sys.modules\n",
"if not os.path.exists(\"/opt/deeplearning/metadata/env_version\") and not os.getenv(\n",
" \"DL_ANACONDA_HOME\"\n",
"):\n",
" if \"google.colab\" in sys.modules:\n",
" from google.colab import auth as google_auth\n",
"\n",
" google_auth.authenticate_user()\n",
"\n",
" # If you are running this notebook locally, replace the string below with the\n",
" # path to your service account key and run this cell to authenticate your GCP\n",
" # account.\n",
" elif not os.getenv(\"IS_TESTING\"):\n",
" %env GOOGLE_APPLICATION_CREDENTIALS ''"
"TIMESTAMP = datetime.now().strftime(\"%Y%m%d%H%M%S\")"
]
},
{
@@ -456,32 +241,37 @@
"online predictions.\n",
"\n",
"Set the name of your Cloud Storage bucket below. It must be unique across all\n",
"Cloud Storage buckets.\n"
"Cloud Storage buckets.\n",
"\n",
"You may also change the `REGION` variable, which is used for operations\n",
"throughout the rest of this notebook. Make sure to [choose a region where Vertex AI services are\n",
"available](https://cloud.google.com/vertex-ai/docs/general/locations#available_regions). You may\n",
"not use a Multi-Regional Storage bucket for training with Vertex AI."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5e9a782f5608"
"id": "f56c52ba662c"
},
"outputs": [],
"source": [
"BUCKET_NAME = \"[your-bucket-name]\" # @param {type:\"string\"}\n",
"BUCKET_URI = f\"gs://{BUCKET_NAME}\""
"REGION = \"us-central1\" # @param {type:\"string\"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "6d0729c4ae94"
"id": "68d1f4908641"
},
"outputs": [],
"source": [
"if BUCKET_NAME == \"\" or BUCKET_NAME is None or BUCKET_NAME == \"[your-bucket-name]\":\n",
" BUCKET_NAME = PROJECT_ID + \"aip-\" + UUID\n",
" BUCKET_URI = f\"gs://{BUCKET_NAME}\""
" BUCKET_NAME = PROJECT_ID + \"-vertex-ai-\" + TIMESTAMP\n",
"BUCKET_URI = f\"gs://{BUCKET_NAME}\""
]
},
{
@@ -501,7 +291,7 @@
},
"outputs": [],
"source": [
"! gsutil mb -l $REGION -p $PROJECT_ID $BUCKET_URI"
"! gsutil mb -l $REGION $BUCKET_URI"
]
},
{
@@ -550,14 +340,13 @@
},
"outputs": [],
"source": [
"import pickle\n",
"import warnings\n",
"\n",
"import joblib\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import pandas as pd\n",
"from google.cloud import aiplatform, storage\n",
"from IPython.display import display\n",
"from google.cloud import storage\n",
"from sklearn.ensemble import RandomForestClassifier\n",
"from sklearn.metrics import (average_precision_score, classification_report,\n",
" confusion_matrix, f1_score)\n",
@@ -567,15 +356,6 @@
"warnings.filterwarnings(\"ignore\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "fdcb614c716f"
},
"source": [
"## Load dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -584,6 +364,7 @@
},
"outputs": [],
"source": [
"# Load dataset\n",
"df = pd.read_csv(\n",
" \"gs://cloud-samples-data/vertex-ai/managed_notebooks/fraud_detection/fraud_detection_data.csv\"\n",
")"
@@ -595,7 +376,8 @@
"id": "5467471277e9"
},
"source": [
"## Analyze the dataset\n"
"## Analyze the dataset\n",
"<a name=\"section-5\"></a>\n"
]
},
{
@@ -792,6 +574,7 @@
},
"source": [
"## Fit a random forest model\n",
"<a name=\"section-6\"></a>\n",
"\n",
"Fit a simple random forest classifier on the preprocessed training dataset."
]
@@ -804,11 +587,9 @@
},
"outputs": [],
"source": [
"print(\"before initiating\")\n",
"forest = RandomForestClassifier(verbose=1)\n",
"print(\"after initiating\")\n",
"forest.fit(X_train, y_train)\n",
"print(\"after fitting\")"
"%%time\n",
"forest = RandomForestClassifier()\n",
"forest.fit(X_train, y_train)"
]
},
{
@@ -818,6 +599,7 @@
},
"source": [
"## Analyzing Results\n",
"<a name=\"section-7\"></a>\n",
"\n",
"The model returns good scores and the confusion matrix confirms that this model can indeed work with imbalanced data."
]
@@ -830,9 +612,7 @@
},
"outputs": [],
"source": [
"print(\"before predicting\")\n",
"y_prob = forest.predict_proba(X_test)\n",
"print(\"after predicting y_prob\")\n",
"y_pred = forest.predict(X_test)\n",
"\n",
"print(\"AUPRC :\", (average_precision_score(y_test, y_prob[:, 1])))\n",
@@ -842,8 +622,7 @@
"print(confusion_matrix(y_test, y_pred))\n",
"\n",
"print(\"classification_report\")\n",
"print(classification_report(y_test, y_pred))\n",
"print(\"after printing classification_report\")"
"print(classification_report(y_test, y_pred))"
]
},
{
@@ -879,7 +658,8 @@
"id": "f96d2120eaf7"
},
"source": [
"## Save the model to a Cloud Storage path\n"
"## Save the model to a Cloud Storage path\n",
"<a name=\"section-8\"></a>"
]
},
{
@@ -890,20 +670,15 @@
},
"outputs": [],
"source": [
"import os\n",
"\n",
"# save the trained model to a local file \"model.pkl\"\n",
"FILE_NAME = \"model.pkl\"\n",
"with open(FILE_NAME, \"wb\") as file:\n",
" pickle.dump(forest, file)\n",
"# save the trained model to a local file \"model.joblib\"\n",
"FILE_NAME = \"model.joblib\"\n",
"joblib.dump(forest, FILE_NAME)\n",
"\n",
"# Upload the saved model file to Cloud Storage\n",
"BLOB_PATH = \"[your-blob-path]\"\n",
"if BLOB_PATH == \"[your-blob-path]\":\n",
" BLOB_PATH = \"fraud-detection-model-path\"\n",
"BLOB_NAME = os.path.join(BLOB_PATH, FILE_NAME)\n",
"\n",
"bucket = storage.Client(PROJECT_ID).bucket(BUCKET_NAME)\n",
"bucket = storage.Client().bucket(BUCKET_NAME)\n",
"blob = bucket.blob(BLOB_NAME)\n",
"blob.upload_from_filename(FILE_NAME)"
]
@@ -914,7 +689,8 @@
"id": "624a66e36aef"
},
"source": [
"## Create a model in Vertex AI\n"
"## Create a model in Vertex AI\n",
"<a name=\"section-9\"></a>"
]
},
{
@@ -926,12 +702,7 @@
"outputs": [],
"source": [
"MODEL_DISPLAY_NAME = \"[your-model-display-name]\"\n",
"if MODEL_DISPLAY_NAME == \"[your-model-display-name]\":\n",
" MODEL_DISPLAY_NAME = \"fraud-detection-model-display-name\"\n",
"ARTIFACT_GCS_PATH = f\"{BUCKET_URI}/{BLOB_PATH}\"\n",
"SERVING_CONTAINER_IMAGE_URI = (\n",
" \"us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest\"\n",
")"
"ARTIFACT_GCS_PATH = f\"{BUCKET_URI}/{BLOB_PATH}\""
]
},
{
@@ -943,13 +714,14 @@
"outputs": [],
"source": [
"# Create a Vertex AI model resource\n",
"from google.cloud import aiplatform\n",
"\n",
"aiplatform.init(project=PROJECT_ID, location=REGION)\n",
"\n",
"model = aiplatform.Model.upload(\n",
" display_name=MODEL_DISPLAY_NAME,\n",
" artifact_uri=ARTIFACT_GCS_PATH,\n",
" serving_container_image_uri=SERVING_CONTAINER_IMAGE_URI,\n",
" serving_container_image_uri=\"us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-24:latest\",\n",
")\n",
"\n",
"model.wait()\n",
@@ -964,7 +736,8 @@
"id": "208e1e07c9f6"
},
"source": [
"## Create an Endpoint\n"
"## Create an Endpoint\n",
"<a name=\"section-10\"></a>"
]
},
{
@@ -975,9 +748,7 @@
},
"outputs": [],
"source": [
"ENDPOINT_DISPLAY_NAME = \"[your-endpoint-display-name]\"\n",
"if ENDPOINT_DISPLAY_NAME == \"[your-endpoint-display-name]\":\n",
" ENDPOINT_DISPLAY_NAME = \"fraud-detection-endpoint\""
"ENDPOINT_DISPLAY_NAME = \"[your-endpoint-display-name]\""
]
},
{
@@ -1015,8 +786,6 @@
"outputs": [],
"source": [
"DEPLOYED_MODEL_NAME = \"[your-deployed-model-name]\"\n",
"if DEPLOYED_MODEL_NAME == \"[your-deployed-model-name]\":\n",
" DEPLOYED_MODEL_NAME = \"fraud-detection-deployed-model\"\n",
"MACHINE_TYPE = \"n1-standard-2\""
]
},
@@ -1028,6 +797,10 @@
},
"outputs": [],
"source": [
"# Uncomment if starting over without model and endpoint references\n",
"# model = aiplatform.Model('[your-model-resource-name]')\n",
"# endpoint = aiplatform.Endpoint('[your-endpoint-resource-name]')\n",
"\n",
"# deploy the model to the endpoint\n",
"model.deploy(\n",
" endpoint=endpoint,\n",
@@ -1041,6 +814,26 @@
"print(model.resource_name)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "602a1a615bb0"
},
"source": [
"Save the ID of the deployed model. The ID of the deployed model can also be checked by using the `endpoint.list_models()` method."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "84a1da5b5e93"
},
"outputs": [],
"source": [
"DEPLOYED_MODEL_ID = \"[your-deployed-model-id]\""
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -1048,8 +841,9 @@
},
"source": [
"## What-If Tool \n",
"<a name=\"section-11\"></a>\n",
"\n",
"The What-If Tool can be used to analyze the model predictions on a test data. See a [brief introduction to the What-If Tool](https://pair-code.github.io/what-if-tool/). In this tutorial, the What-If Tool will be configured and run on the model trained locally, and on the model deployed on Vertex AI Endpoint in the previous steps.\n",
"The What-If Tool can be used to analyze the model predictions on a test data. See a [brief introduction to the What-If Tool](https://pair-code.github.io/what-if-tool/). In this tutorial, the What-If Tool will be configured and run on the model trained locally, and on the model deployed on Vertex AI Endpoints in the previous steps.\n",
"\n",
"[WitConfigBuilder](https://github.com/PAIR-code/what-if-tool/blob/master/witwidget/notebook/visualization.py#L30) provides the `set_ai_platform_model()` method to configure the What-If Tool with a model deployed as a version on Ai Platform models. This feature currently supports Ai Platform only but not Vertex AI models. Fortunately, there is also an option to pass a custom function for generating predictions through the `set_custom_predict_fn()` method where either the locally trained model or a function that returns predictions from a Vertex AI model can be passed."
]
@@ -1097,33 +891,34 @@
},
"outputs": [],
"source": [
"if IS_COLAB:\n",
" # define target and labels\n",
" TARGET_FEATURE = \"isFraud\"\n",
" LABEL_VOCAB = [\"not-fraud\", \"fraud\"]\n",
"# define target and labels\n",
"TARGET_FEATURE = \"isFraud\"\n",
"LABEL_VOCAB = [\"not-fraud\", \"fraud\"]\n",
"\n",
" # define the function to adjust the predictions\n",
"# define the function to adjust the predictions\n",
"\n",
" def adjust_prediction(pred):\n",
" return [1 - pred, pred]\n",
"\n",
" # Combine the features and labels into one array for the What-If Tool\n",
" test_examples = np.hstack(\n",
" (test_samples_X.to_numpy(), test_samples_y.to_numpy().reshape(-1, 1))\n",
"def adjust_prediction(pred):\n",
" return [1 - pred, pred]\n",
"\n",
"\n",
"# Combine the features and labels into one array for the What-If Tool\n",
"test_examples = np.hstack(\n",
" (test_samples_X.to_numpy(), test_samples_y.to_numpy().reshape(-1, 1))\n",
")\n",
"\n",
"# Configure the WIT to run on the locally trained model\n",
"config_builder = (\n",
" WitConfigBuilder(\n",
" test_examples.tolist(), test_samples_X.columns.tolist() + [\"isFraud\"]\n",
" )\n",
" .set_custom_predict_fn(forest.predict_proba)\n",
" .set_target_feature(TARGET_FEATURE)\n",
" .set_label_vocab(LABEL_VOCAB)\n",
")\n",
"\n",
" # Configure the WIT to run on the locally trained model\n",
" config_builder = (\n",
" WitConfigBuilder(\n",
" test_examples.tolist(), test_samples_X.columns.tolist() + [\"isFraud\"]\n",
" )\n",
" .set_custom_predict_fn(forest.predict_proba)\n",
" .set_target_feature(TARGET_FEATURE)\n",
" .set_label_vocab(LABEL_VOCAB)\n",
" )\n",
"\n",
" # display the WIT widget\n",
" display(WitWidget(config_builder, height=600))"
"# display the WIT widget\n",
"WitWidget(config_builder, height=600)"
]
},
{
@@ -1143,56 +938,36 @@
},
"outputs": [],
"source": [
"if IS_COLAB:\n",
" # configure the target and class-labels\n",
" TARGET_FEATURE = \"isFraud\"\n",
" LABEL_VOCAB = [\"not-fraud\", \"fraud\"]\n",
"# configure the target and class-labels\n",
"TARGET_FEATURE = \"isFraud\"\n",
"LABEL_VOCAB = [\"not-fraud\", \"fraud\"]\n",
"\n",
" # function to return predictions from the deployed Model\n",
"# function to return predictions from the deployed Model\n",
"\n",
" def endpoint_predict_sample(instances: list):\n",
" prediction = endpoint.predict(instances=instances)\n",
" preds = [[1 - i, i] for i in prediction.predictions]\n",
" return preds\n",
"\n",
" # Combine the features and labels into one array for the What-If Tool\n",
" test_examples = np.hstack(\n",
" (test_samples_X.to_numpy(), test_samples_y.to_numpy().reshape(-1, 1))\n",
"def endpoint_predict_sample(instances: list):\n",
" prediction = endpoint.predict(instances=instances)\n",
" preds = [[1 - i, i] for i in prediction.predictions]\n",
" return preds\n",
"\n",
"\n",
"# Combine the features and labels into one array for the What-If Tool\n",
"test_examples = np.hstack(\n",
" (test_samples_X.to_numpy(), test_samples_y.to_numpy().reshape(-1, 1))\n",
")\n",
"\n",
"# Configure the WIT with the prediction function\n",
"config_builder = (\n",
" WitConfigBuilder(\n",
" test_examples.tolist(), test_samples_X.columns.tolist() + [\"isFraud\"]\n",
" )\n",
" .set_custom_predict_fn(endpoint_predict_sample)\n",
" .set_target_feature(TARGET_FEATURE)\n",
" .set_label_vocab(LABEL_VOCAB)\n",
")\n",
"\n",
" # Configure the WIT with the prediction function\n",
" config_builder = (\n",
" WitConfigBuilder(\n",
" test_examples.tolist(), test_samples_X.columns.tolist() + [\"isFraud\"]\n",
" )\n",
" .set_custom_predict_fn(endpoint_predict_sample)\n",
" .set_target_feature(TARGET_FEATURE)\n",
" .set_label_vocab(LABEL_VOCAB)\n",
" )\n",
"\n",
" # run the WIT-widget\n",
" display(WitWidget(config_builder, height=400))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c446b1263b34"
},
"source": [
"## Undeploy the model\n",
"When you are done doing predictions, you undeploy the model from the Endpoint resouce. This deprovisions all compute resources and ends billing for the deployed model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "72eb599403d4"
},
"outputs": [],
"source": [
"endpoint.undeploy_all()"
"# run the WIT-widget\n",
"WitWidget(config_builder, height=400)"
]
},
{
@@ -1202,6 +977,7 @@
},
"source": [
"## Clean up\n",
"<a name=\"section-12\"></a>\n",
"\n",
"\n",
"To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n",
@@ -1210,6 +986,18 @@
"Otherwise, you can delete the individual resources you created in this tutorial:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "46061cbb656d"
},
"outputs": [],
"source": [
"# undeploy the model\n",
"endpoint.undeploy(deployed_model_id=DEPLOYED_MODEL_ID)"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -1219,14 +1007,31 @@
"outputs": [],
"source": [
"# delete the endpoint\n",
"endpoint.delete()\n",
"\n",
"endpoint.delete()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "f5d7143e9f5c"
},
"outputs": [],
"source": [
"# delete the model\n",
"model.delete()\n",
"\n",
"delete_bucket = False\n",
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
" ! gsutil rm -r $BUCKET_URI"
"model.delete()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ea8063b9606f"
},
"outputs": [],
"source": [
"# uncomment to remove the contents of the Cloud Storage bucket\n",
"# ! gsutil -m rm -r $BUCKET_NAME"
]
}
],

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

@@ -1,131 +1,55 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "18ebbd838e32"
},
"outputs": [],
"source": [
"# Copyright 2022 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "aef73cfa8725"
},
"source": [
"# Predictive Maintenance using Vertex AI\n",
"\n",
"<table align=\"left\">\n",
" <td>\n",
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/predictive_maintainance/predictive_maintenance_usecase.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\">\n",
" View on GitHub\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/predictive_maintainance/predictive_maintenance_usecase.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\\\" alt=\"Colab logo\"> Run in Colab\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/predictive_maintainance/predictive_maintenance_usecase.ipynb\">\n",
" <img src=\"https://lh3.googleusercontent.com/UiNooY4LUgW_oTvpsNhPpQzsstV5W8F7rYgxgGBD85cWJoLmrOzhVs_ksK_vgx40SHs7jCqkTkCk=e14-rj-sc0xffffff-h130-w32\" alt=\"Vertex AI logo\">\n",
" Open in Vertex AI Workbench\n",
" </a>\n",
" </td>\n",
"</table>\n",
"<br/><br/><br/>\n",
"\n",
"\n",
"# Predictive Maintenance \n",
"## Table of contents\n",
"* [Overview](#section-1)\n",
"* [Objective](#section-2)\n",
"* [Dataset](#section-3)\n",
"* [Dataset](#section-2)\n",
"* [Objective](#section-3)\n",
"* [Costs](#section-4)\n",
"* [Data analysis](#section-5)\n",
"* [Fit a regression model](#section-6)\n",
"* [Data Analysis](#section-5)\n",
"* [Fit a Regression model](#section-6)\n",
"* [Evaluate the trained model](#section-7)\n",
"* [Save the model](#section-8)\n",
"* [Running a notebook end-to-end using the executor](#section-9)\n",
"* [Running a notebook end-to-end using **Executor**](#section-9)\n",
"* [Hosting the model on Vertex AI](#section-10)\n",
" * [Create an endpoint](#section-11)\n",
" * [Deploy the model to the created endpoint](#section-12)\n",
" * [Test calling the endpoint](#section-13)\n",
"* [Clean up](#section-14)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "e10c5167a061"
},
"source": [
" * [Create an Endpoint](#section-11)\n",
" * [Deploy the model to the created Endpoint](#section-12)\n",
" * [Test calling the endpoint](#section-13)\n",
"* [Clean up](#section-14)\n",
"\n",
"\n",
"## Overview\n",
"<a name=\"section-1\"></a>\n",
"This notebook demonstrates performing predictive maintenance on industrial data using machine learning techniques, deploying the machine learning model on Vertex-AI and automating the workflow using executor feature of Vertex-AI.\n",
"\n",
"In this notebook, you go through a predictive maintenance usecase on industrial data using machine learning techniques, deploy the machine learning model on Vertex AI, and automate the workflow using the executor feature of Vertex AI Workbench.\n",
"<b>Note</b>: This notebook is designed to run on managed notebooks instance of Vertex AI Workbench. Some components of this notebook may not work in other notebook environments.\n",
"\n",
"*Note: This notebook file is developed to run in a [Vertex AI Workbench managed notebooks](https://console.cloud.google.com/vertex-ai/workbench/list/managed) instance using the XGBoost (Local) kernel. Some components of this notebook may not work in other notebook environments.*"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "fead9e83ebd7"
},
"source": [
"### Objective\n",
"## Dataset\n",
"<a name=\"section-2\"></a>\n",
"The dataset used in this notebook is a part of the [NASA Turbofan Engine Degradation Dataset](https://ti.arc.nasa.gov/tech/dash/groups/pcoe/prognostic-data-repository/) which consists of simulated time-series data for four sets of fleet-engines under different combinations of operational conditions and fault modes. In this notebook, only one of the engine's simulated data(FD001) has been considered to analyze and train a model that can predict the engine's remaining useful life.\n",
"\n",
"The objectives of this notebook include:\n",
"## Objective\n",
"<a name=\"section-3\"></a>\n",
"In this notebook :\n",
"\n",
"- Loading the required dataset from a Cloud Storage bucket.\n",
"- Loading the required dataset from Cloud Storage bucket.\n",
"- Analyzing the fields present in the dataset.\n",
"- Selecting the required data for the predictive maintenance model.\n",
"- Training an XGBoost regression model for predicting the remaining useful life.\n",
"- Evaluating the model.\n",
"- Running the notebook end-to-end as a training job using Executor.\n",
"- Deploying the model on Vertex AI.\n",
"- Clean up."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "a71f4d96bf80"
},
"source": [
"### Dataset\n",
"<a name=\"section-3\"></a>\n",
"- Deploying the model on Vertex-AI.\n",
"- Clean up.\n",
"\n",
"The dataset used in this notebook is a part of the [NASA Turbofan Engine Degradation Simulation dataset](https://ti.arc.nasa.gov/tech/dash/groups/pcoe/prognostic-data-repository/), which consists of simulated time-series data for four sets of fleet engines under different combinations of operational conditions and fault modes. A version of this dataset which is saved to a public Cloud Storage bucket is used in this notebook. In this notebook, one of the engine's simulated data (FD001) is used to analyze and train a model that can predict the engine's remaining useful life."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "36c53c95b4b9"
},
"source": [
"### Costs\n",
"\n",
"## Costs\n",
"<a name=\"section-4\"></a>\n",
"\n",
"This tutorial uses the following billable components of Google Cloud:\n",
"\n",
"- Vertex AI\n",
@@ -141,128 +65,22 @@
{
"cell_type": "markdown",
"metadata": {
"id": "629f52f6efe1"
"id": "5b15a97278df"
},
"source": [
"### Kernel selection\n",
"Select <b>XGBoost</b> kernel while running this notebook on Vertex AI Workbench's managed instances. Otherwise, ensure that the following libraries are installed in the environment where this notebook is being run.\n",
"## Kernel selection\n",
"Select <b>XGBoost</b> kernel while running this notebook on Vertex-AIs managed instances or ensure that the following libraries are installed in the environment where this notebook is being run.\n",
"- XGBoost\n",
"- Pandas\n",
"- Seaborn\n",
"- Sklearn\n",
"\n",
"Along with the above libraries, th`e following google-cloud libraries are also used in this notebook.\n",
"Along with the above libraries, the following google-cloud libraries are also used in this notebook.\n",
"\n",
"- google.cloud.aiplatform\n",
"- google.cloud.storage"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "16bee0754628"
},
"source": [
"## Installation\n",
"- google.cloud.storage\n",
"\n",
"Install the following packages to run this notebook outside Vertex AI Workbench's managed instances."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "69520a67e54c"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"# The Vertex AI Workbench Notebook product has specific requirements\n",
"IS_WORKBENCH_NOTEBOOK = os.getenv(\"DL_ANACONDA_HOME\")\n",
"IS_USER_MANAGED_WORKBENCH_NOTEBOOK = os.path.exists(\n",
" \"/opt/deeplearning/metadata/env_version\"\n",
")\n",
"\n",
"# Vertex AI Notebook requires dependencies to be installed with '--user'\n",
"USER_FLAG = \"\"\n",
"if IS_WORKBENCH_NOTEBOOK:\n",
" USER_FLAG = \"--user\"\n",
" \n",
"! pip3 install {USER_FLAG} --upgrade google-cloud-aiplatform \\\n",
" google-cloud-storage \\\n",
" xgboost \\\n",
" seaborn \\\n",
" sklearn \\\n",
" fsspec \\\n",
" gcsfs \\\n",
" pandas -q"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "eda79cca981d"
},
"source": [
"### Restart the kernel\n",
"\n",
"Once you've installed the additional packages, you need to restart the notebook kernel so it can find the packages."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "e200999cabe5"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" # Automatically restart kernel after installs\n",
" import IPython\n",
"\n",
" app = IPython.Application.instance()\n",
" app.kernel.do_shutdown(True)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5b15a97278df"
},
"source": [
"## Before you begin \n",
"\n",
"### Set up your Google Cloud project\n",
"\n",
"**The following steps are required, regardless of your notebook environment.**\n",
"\n",
"1. [Select or create a Google Cloud project](https://console.cloud.google.com/cloud-resource-manager). When you first create an account, you get a $300 free credit towards your compute/storage costs.\n",
"\n",
"1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
"\n",
"1. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n",
"\n",
"1. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
"\n",
"1. Enter your project ID in the cell below. Then run the cell to make sure the\n",
"Cloud SDK uses the right project for all the commands in this notebook.\n",
"\n",
"**Note**: Jupyter runs lines prefixed with `!` as shell commands, and it interpolates Python variables prefixed with `$` into these commands."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5aee4379e8e5"
},
"source": [
"#### Set your project ID\n",
"\n",
"**If you don't know your project ID**, you may be able to get your project ID using `gcloud`."
"## Set your project ID"
]
},
{
@@ -273,67 +91,7 @@
},
"outputs": [],
"source": [
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5bf9979b96ff"
},
"outputs": [],
"source": [
"if PROJECT_ID == \"\" or PROJECT_ID is None or PROJECT_ID == \"[your-project-id]\":\n",
" # Get your GCP project id from gcloud\n",
" shell_output = ! gcloud config list --format 'value(core.project)' 2>/dev/null\n",
" PROJECT_ID = shell_output[0]\n",
" print(\"Project ID:\", PROJECT_ID)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "09021c90b34c"
},
"outputs": [],
"source": [
"! gcloud config set project $PROJECT_ID"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9658ecf524b1"
},
"source": [
"#### Region\n",
"\n",
"You can also change the `REGION` variable, which is used for operations\n",
"throughout the rest of this notebook. Below are regions supported for Vertex AI. It is recommended that you choose the region closest to you.\n",
"\n",
"- Americas: `us-central1`\n",
"- Europe: `europe-west4`\n",
"- Asia Pacific: `asia-east1`\n",
"\n",
"You may not use a multi-regional bucket for training with Vertex AI. Not all regions provide support for all Vertex AI services.\n",
"\n",
"Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "5c615e53149f"
},
"outputs": [],
"source": [
"REGION = \"[your-region]\" # @param {type: \"string\"}\n",
"\n",
"if REGION == \"[your-region]\":\n",
" REGION = \"us-central1\""
"PROJECT_ID = \"[your-project-id]\""
]
},
{
@@ -342,9 +100,9 @@
"id": "f66f96816fd0"
},
"source": [
"#### UUID\n",
"#### Timestamp\n",
"\n",
"If you are in a live tutorial session, you might be using a shared test account or project. To avoid name collisions between users on resources created, you create a uuid for each instance session, and append it onto the name of resources you create in this tutorial."
"If you are in a live tutorial session, you might be using a shared test account or project. To avoid name collisions between users on resources created, you create a timestamp for each instance session, and append it onto the name of resources you create in this tutorial."
]
},
{
@@ -355,84 +113,9 @@
},
"outputs": [],
"source": [
"import random\n",
"import string\n",
"from datetime import datetime\n",
"\n",
"\n",
"# Generate a uuid of a specifed length(default=8)\n",
"def generate_uuid(length: int = 8) -> str:\n",
" return \"\".join(random.choices(string.ascii_lowercase + string.digits, k=length))\n",
"\n",
"\n",
"UUID = generate_uuid()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "df899ce9999c"
},
"source": [
"### Authenticate your Google Cloud account\n",
"\n",
"**If you are using Vertex AI Workbench Notebooks**, your environment is already\n",
"authenticated.\n",
"\n",
"**If you are using Colab**, run the cell below and follow the instructions\n",
"when prompted to authenticate your account via oAuth.\n",
"\n",
"**Otherwise**, follow these steps:\n",
"\n",
"1. In the Cloud Console, go to the [**Create service account key**\n",
" page](https://console.cloud.google.com/apis/credentials/serviceaccountkey).\n",
"\n",
"2. Click **Create service account**.\n",
"\n",
"3. In the **Service account name** field, enter a name, and\n",
" click **Create**.\n",
"\n",
"4. In the **Grant this service account access to project** section, click the **Role** drop-down list. Type \"Vertex AI\"\n",
"into the filter box, and select\n",
" **Vertex AI Administrator**. Type \"Storage Object Admin\" into the filter box, and select **Storage Object Admin**.\n",
"\n",
"5. Click *Create*. A JSON file that contains your key downloads to your\n",
"local environment.\n",
"\n",
"6. Enter the path to your service account key as the\n",
"`GOOGLE_APPLICATION_CREDENTIALS` variable in the cell below and run the cell."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "201e8e760d22"
},
"outputs": [],
"source": [
"# If you are running this notebook in Colab, run this cell and follow the\n",
"# instructions to authenticate your GCP account. This provides access to your\n",
"# Cloud Storage bucket and lets you submit training jobs and prediction\n",
"# requests.\n",
"\n",
"import os\n",
"import sys\n",
"\n",
"# If on Vertex AI Workbench, then don't execute this code\n",
"IS_COLAB = \"google.colab\" in sys.modules\n",
"if not os.path.exists(\"/opt/deeplearning/metadata/env_version\") and not os.getenv(\n",
" \"DL_ANACONDA_HOME\"\n",
"):\n",
" if \"google.colab\" in sys.modules:\n",
" from google.colab import auth as google_auth\n",
"\n",
" google_auth.authenticate_user()\n",
"\n",
" # If you are running this notebook locally, replace the string below with the\n",
" # path to your service account key and run this cell to authenticate your GCP\n",
" # account.\n",
" elif not os.getenv(\"IS_TESTING\"):\n",
" %env GOOGLE_APPLICATION_CREDENTIALS ''"
"TIMESTAMP = datetime.now().strftime(\"%Y%m%d%H%M%S\")"
]
},
{
@@ -441,18 +124,11 @@
"id": "ea53caa30628"
},
"source": [
"### Create a Cloud Storage bucket\n",
"## Select or Create Cloud Storage Bucket for storing the model\n",
"\n",
"**The following steps are required, regardless of your notebook environment.**\n",
"When you create a model resource on Vertex AI using the Cloud SDK, you need to give a Cloud Storage bucket URI of the model where the model is stored. Using the model saved, you can then create Vertex AI model and endpoint resources in order to serve online predictions.\n",
"\n",
"\n",
"When you create a model in Vertex AI using the Cloud SDK, you give a Cloud Storage path where the trained model is saved. \n",
"In this tutorial, Vertex AI saves the trained model to a Cloud Storage bucket. Using this model artifact, you can then\n",
"create Vertex AI model and endpoint resources in order to serve\n",
"online predictions.\n",
"\n",
"Set the name of your Cloud Storage bucket below. It must be unique across all\n",
"Cloud Storage buckets."
"Set the name of your Cloud Storage bucket below. It must be unique across all Cloud Storage buckets.You may also change the REGION variable, which is used for operations throughout the rest of this notebook. Make sure to choose a region where Vertex AI services are available."
]
},
{
@@ -463,8 +139,9 @@
},
"outputs": [],
"source": [
"BUCKET_NAME = \"[your-bucket-name]\" # @param {type:\"string\"}\n",
"BUCKET_URI = f\"gs://{BUCKET_NAME}\""
"BUCKET_NAME = \"[your-bucket-name]\"\n",
"BUCKET_URI = f\"gs://{BUCKET_NAME}\"\n",
"REGION = \"us-central1\""
]
},
{
@@ -475,9 +152,13 @@
},
"outputs": [],
"source": [
"if BUCKET_NAME == \"\" or BUCKET_NAME is None or BUCKET_NAME == \"[your-bucket-name]\":\n",
" BUCKET_NAME = PROJECT_ID + \"aip-\" + UUID\n",
" BUCKET_URI = f\"gs://{BUCKET_NAME}\""
"# Set a default bucketname in case bucket name is not given\n",
"if BUCKET_NAME == \"\" or BUCKET_NAME is None:\n",
" from datetime import datetime\n",
"\n",
" TIMESTAMP = datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
" BUCKET_NAME = PROJECT_ID + \"aip-\" + TIMESTAMP\n",
" BUCKET_URI = \"gs://\" + BUCKET_NAME"
]
},
{
@@ -497,7 +178,7 @@
},
"outputs": [],
"source": [
"! gsutil mb -l $REGION $BUCKET_URI"
"! gsutil mb -l $REGION $BUCKET_NAME"
]
},
{
@@ -517,7 +198,7 @@
},
"outputs": [],
"source": [
"! gsutil ls -al $BUCKET_URI"
"! gsutil ls -al $BUCKET_NAME"
]
},
{
@@ -526,7 +207,7 @@
"id": "4c0f6aac282a"
},
"source": [
"### Import the required libraries"
"## Import the required libraries"
]
},
{
@@ -569,7 +250,7 @@
"outputs": [],
"source": [
"# load the data from the source\n",
"INPUT_PATH = \"gs://cloud-samples-data/ai-platform-unified/datasets/tabular/predictive_maintenance.csv\" # data source\n",
"INPUT_PATH = \"gs://vertex_ai_managed_services_demo/mfg_predictive_maintenance/train_FD001.txt\" # data source\n",
"raw_data = pd.read_csv(INPUT_PATH, sep=\" \", header=None)\n",
"# check the data\n",
"print(raw_data.shape)\n",
@@ -582,7 +263,7 @@
"id": "8cfc304d35b5"
},
"source": [
"The data itself doesn't contain any feature names and thus needs its columns to be renamed. The data source already provides some data description. Apparently, the <b>ID</b> column represents the unit-number of the fleet-engine and <b>Cycle</b> represents the time in cycles. <b>OpSet1</b>,<b>Opset2</b> & <b>Opset3</b> represent the three operational settings that are described in the original data source and have a substantial effect on engine performance. The rest of the fields show sensor readings collected from 21 different sensors."
"The data itself doesn't contain any feature names and thus needs its columns to be re-named. The data source already provides us with some data description. Apparently, the <b>ID</b> column represents the unit-number of the fleet-engine and <b>Cycle</b> represents the time in cycles. <b>OpSet1</b>,<b>Opset2</b> & <b>Opset3</b> represent the three operational settings that are described in the original data source and have a substantial effect on engine performance. The rest of the fields show sensor readings collected from 21 different sensors."
]
},
{
@@ -655,7 +336,7 @@
"id": "43c3f01352ad"
},
"source": [
"On an average, there seem to be around 225 cycles per each ID in the dataset. Next, lets check the data types of the fields and the number of null records in the data."
"On an average, there seems to be around 225 cycles per each ID in the dataset. Further, lets check the data-types of the fields and the number of null records in the data."
]
},
{
@@ -737,7 +418,7 @@
"id": "284debdf4294"
},
"source": [
"Fields **SensorMeasure7**, **SensorMeasure12**, **SensorMeasure20** & **SensorMeasure21** correlate highly with many other fields. These fields can be omitted. Further, **SensorMeasure8**, **SensorMeasure11** and **SensorMeasure4** seem highly correlated with each other and so any one of them, for example, **SensorMeasure4**, can be kept and the rest can be omitted."
"Fields **SensorMeasure7**, **SensorMeasure12**, **SensorMeasure20** & **SensorMeasure21** correlate highly with many other fields. These fields can be omitted. Further, **SensorMeasure8**, **SensorMeasure11** and **SensorMeasure4** seem highly correlated with each other and so any one of them, say **SensorMeasure4** can be kept and the rest can be omitted."
]
},
{
@@ -774,7 +455,7 @@
"id": "8197cdef2cff"
},
"source": [
"As the current objective is to predict the remaining useful life (RUL) of each unit (ID), the target variable needs to be identified. Since you're dealing with a timeseries data that represents the lifetime of a unit, remaining useful life of a unit can be calculated by subtracting the current cycle from the maximum cycle of that unit.\n",
"As the current objective is to predict the remaining useful life(RUL) of each unit(ID), the target variable needs to be identified. Since we're dealing with a timeseries data that represents the lifetime of a unit, remaining useful life of a unit can be calculated by subtracting the current cycle from the maximum cycle of that unit.\n",
"\n",
"\t\t\t\t\tRUL = Max. Cycle - Current Cycle \n",
"## RUL calculation and Feature selection"
@@ -837,7 +518,7 @@
"id": "fc3b82355cdc"
},
"source": [
"The above plot suggests that the RUL, in other words, the remaining cycles, is decreasing as the current cycle increases which is expected. Further, lets see the how the other fields relate to RUL in the current dataset."
"The above plot suggests that the RUL i.e., the remaining cycles is decreasing as the current cycle increases which is expected. Further, lets see the how the other fields relate to RUL in the current dataset."
]
},
{
@@ -876,7 +557,7 @@
"- Fields **SensorMeasure5** and **SensorMeasure16** don't show much variance with the RUL and seem constant all the time. Hence, they can be removed.\n",
"- Fields **SensorMeasure2**, **SensorMeasure3**, **SensorMeasure4**, **SensorMeasure13**, **SensorMeasure15** & **SensorMeasure17** show a similar rising trend.\n",
"- **SensorMeasure9** and **SensorMeasure14** show a similar trend.\n",
"- **SensorMeasure6** shows a flatline most of the time except in a very few places and therefore can be ignored."
"- **SensorMeasure6** shows flatline most of the time except at a very few places and therefore can be ignored."
]
},
{
@@ -902,7 +583,7 @@
"id": "cae198bd96ef"
},
"source": [
"## Split the data into train and test\n",
"## Split the data into Train and Test\n",
"\n",
"Divide the dataset with the selected features into train and test sets."
]
@@ -932,10 +613,9 @@
"id": "43a26d74c687"
},
"source": [
"## Fit a regression model\n",
"## Fit a Regression model\n",
"<a name=\"section-6\"></a>\n",
"\n",
"Initialize and train a regression model using the XGBoost library with the calculated RUL as the target feature."
"Initialize and train a regression model using XGBoost library with the calculated RUL as the target feature."
]
},
{
@@ -1089,26 +769,23 @@
"id": "4bd88d7f4bbb"
},
"source": [
"## Running a notebook end-to-end using executor\n",
"## Running a notebook end-to-end using **Executor**\n",
"<a name=\"section-9\"></a>\n",
"\n",
"**Note:** This section can only be considered when running this notebook on Managed instances from Vertex AI Workbench.\n",
"### Automating the notebook execution\n",
"All the steps followed until now can be run as a training job without using any additional code using the Vertex AI Workbench executor. The executor can help you run a notebook file from start to end, with your choice of the environment, machine type, input parameters, and other characteristics. After setting up an execution, the notebook is executed as a job in Vertex AI custom training. Your jobs can be monitored from the Executor pane in the left sidebar.\n",
"All the steps followed till now can be run as a training job without using any additional code using the Notebook executor. Notebook executor can help you run a notebook file from start to end, with your choice of the environment, machine type, input parameters, and other characteristics. After setting up an execution, the notebook is executed as a job in Vertex AI custom training. Your jobs can be monitored from the Notebook Executor pane in the menu on the left.\n",
"\n",
"<img src=\"images/executor.PNG\">\n",
"\n",
"The executor also lets you choose the environment and machine type while automating the runs similar to Vertex AI training jobs without switching to the training jobs UI. Apart from the custom container that replicates the existing kernel by default, pre-built environments like TensorFlow Enterprise, PyTorch, and others can also be selected to run the notebook. The required compute power can be specified by choosing from the list of machine types available, including GPUs.\n",
"\n",
"### Scheduled runs on executor\n",
"Executor also lets you choose the environment and machine type while automating the runs similar to Vertex AI training jobs without switching to the training jobs UI. Apart from the custom container that replicates the existing kernel by default, pre-built environments like TensorFlow Enterprise, PyTorch, and others can also be selected to run the notebook. Furthermore the required compute power can be specified by choosing from the list of machine types available, including GPUs.\n",
"\n",
"## Scheduled runs on executor\n",
"Notebook runs can also be scheduled recurringly with the executor. To do so, select Schedule-based recurring executions as the run type instead of One-time execution. The frequency of the job and the time when it executes is provided when you create the execution.\n",
"\n",
"<img src=\"https://storage.googleapis.com/gweb-cloudblog-publish/images/7_Vertex_AI_Workbench.max-1100x1100.jpg\">\n",
"\n",
"### Parameterizing the variables\n",
"\n",
"The executor lets you run a notebook with different sets of input parameters. If you’ve added parameter tags to any of your notebook cells, you can pass in your parameter values to the executor. More about how to use this feature can be found on this [blog](https://cloud.google.com/blog/products/ai-machine-learning/schedule-and-execute-notebooks-with-vertex-ai-workbench).\n",
"## Parameterizing the variables\n",
"Executor lets you run a notebook with different sets of input parameters.If you’ve added parameter tags to any of your notebook cells, you can pass in your parameter values to the executor. More about how to use this feature can be found on this [blog](https://cloud.google.com/blog/products/ai-machine-learning/schedule-and-execute-notebooks-with-vertex-ai-workbench).\n",
"\n",
"<img src=\"https://storage.googleapis.com/gweb-cloudblog-publish/images/6_Vertex_AI_Workbench.max-700x700.jpg\">\n"
]
@@ -1138,37 +815,6 @@
"ARTIFACT_GCS_PATH = f\"gs://{BUCKET_NAME}/{BLOB_PATH}\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1aa75b3d4616"
},
"source": [
"Give a display name to the Vertex AI model resource."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "02ca350dba6c"
},
"outputs": [],
"source": [
"# Set the model-dsiplay-name\n",
"MODEL_DISPLAY_NAME = \"[your-model-display-name]\" # @param {type:\"string\"}\n",
"\n",
"# Otherwise, use the default name\n",
"if (\n",
" MODEL_DISPLAY_NAME == \"[your-model-display-name]\"\n",
" or MODEL_DISPLAY_NAME is None\n",
" or MODEL_DISPLAY_NAME == \"\"\n",
"):\n",
" MODEL_DISPLAY_NAME = \"pred_maint_model_\" + UUID\n",
"\n",
"print(MODEL_DISPLAY_NAME)"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -1205,28 +851,6 @@
"Next, create an endpoint resource for deploying the model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "e1e0cd571992"
},
"outputs": [],
"source": [
"# Set the endpoint-dsiplay-name\n",
"ENDPOINT_DISPLAY_NAME = \"[your-endpoint-display-name]\" # @param {type:\"string\"}\n",
"\n",
"# Otherwise, use the default name\n",
"if (\n",
" ENDPOINT_DISPLAY_NAME == \"[your-endpoint-display-name]\"\n",
" or ENDPOINT_DISPLAY_NAME is None\n",
" or ENDPOINT_DISPLAY_NAME == \"\"\n",
"):\n",
" ENDPOINT_DISPLAY_NAME = \"pred_maint_endpoint_\" + UUID\n",
"\n",
"print(ENDPOINT_DISPLAY_NAME)"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -1235,7 +859,6 @@
},
"outputs": [],
"source": [
"# Create the Endpoint resource\n",
"endpoint = aiplatform.Endpoint.create(display_name=ENDPOINT_DISPLAY_NAME)\n",
"\n",
"print(endpoint.display_name)\n",
@@ -1252,11 +875,18 @@
"<a name=\"section-12\"></a>\n",
"\n",
"\n",
"Configure the following parameters and deploy the model to the created endpoint.\n",
"\n",
"- `endpoint`: The `Endpoint` object created using Vertex AI SDK.\n",
"- `deployed_model_display_name`: A display-name for the deployment.\n",
"- `machine_type`: Type of the machine required for the deployment environment. See [here](https://cloud.google.com/vertex-ai/docs/predictions/configure-compute) for references."
"Configure the deployment name, machine type, and other parameters for the deployment and deploy the model to the created endpoint."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ca41cac871d6"
},
"outputs": [],
"source": [
"MACHINE_TYPE = \"n1-standard-2\""
]
},
{
@@ -1270,8 +900,8 @@
"# deploy the model to the endpoint\n",
"model.deploy(\n",
" endpoint=endpoint,\n",
" deployed_model_display_name=MODEL_DISPLAY_NAME + \"_deployment\",\n",
" machine_type=\"n1-standard-2\",\n",
" deployed_model_display_name=DEPLOYED_MODEL_NAME,\n",
" machine_type=MACHINE_TYPE,\n",
")\n",
"\n",
"model.wait()\n",
@@ -1314,15 +944,7 @@
"## Clean up\n",
"<a name=\"section-14\"></a>\n",
"\n",
"To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n",
"project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n",
"\n",
"Otherwise, you can delete the individual resources you created in this tutorial:\n",
"* Vertex AI Model\n",
"* Vertex AI Endpoint\n",
"* Cloud Storage bucket\n",
"\n",
"Set `delete_bucket` to **True** to delete the Cloud Storage bucket."
"Undeploy the model from endpoint."
]
},
{
@@ -1333,25 +955,74 @@
},
"outputs": [],
"source": [
"# Undeploy all the models from the endpoint\n",
"endpoint.undeploy_all()\n",
"\n",
"# Delete the endpoint resource\n",
"endpoint.delete()\n",
"\n",
"# Delete the model resource\n",
"model.delete()\n",
"\n",
"# Delete the Cloud Storage bucket\n",
"delete_bucket = False\n",
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
" ! gsutil -m rm -r $BUCKET_URI"
"DEPLOYED_MODEL_ID = \"\"\n",
"endpoint.undeploy(deployed_model_id=DEPLOYED_MODEL_ID)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "96e427b77791"
},
"source": [
"Delete the endpoint."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ace028ac23ea"
},
"outputs": [],
"source": [
"endpoint.delete()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4b77998d0512"
},
"source": [
"Delete the model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "e034150a4c94"
},
"outputs": [],
"source": [
"model.delete()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "23cb2deb122d"
},
"source": [
"Remove the contents of the Cloud Storage bucket."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "98aaac27d85d"
},
"outputs": [],
"source": [
"! gsutil -m rm -r $BUCKET_URI"
]
}
],
"metadata": {
"colab": {
"name": "predictive_maintenance_usecase.ipynb",
"name": "Predictive_maintenance_usecase.ipynb",
"toc_visible": true
},
"kernelspec": {
@@ -0,0 +1,819 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "d1cc1c1fa076"
},
"source": [
"# Pricing Optimization \n",
"## Table of contents\n",
"* [Overview](#section-1)\n",
"* [Dataset](#section-2)\n",
"* [Objective](#section-3)\n",
"* [Costs](#section-4)\n",
"* [Create a BigQuery dataset](#section-5)\n",
"* [Load the dataset from Cloud Storage](#section-6)\n",
"* [Data Analysis](#section-7)\n",
"* [Preprocess the data for training](#section-8)\n",
"* [Train the model using BigQuery ML](#section-9)\n",
"* [Generate forecasts from the model](#section-10)\n",
"* [Interpret the results to choose the best price](#section-11)\n",
"* [Clean Up](#section-12)\n",
"\n",
"\n",
"## Overview\n",
"<a name=\"section-1\"></a>\n",
"This notebook demonstrates analysis of pricing optimization on [CDM Pricing Data](https://github.com/trifacta/trifacta-google-cloud/tree/main/design-pattern-pricing-optimization) and automating the workflow using Vertex AI Workbench's managed notebooks.\n",
"\n",
"<b>Note</b>: This notebook is designed to run on managed notebooks instance of Vertex AI Workbench. Some components of this notebook may not work in other notebook environments.\n",
"\n",
"## Dataset\n",
"<a name=\"section-2\"></a>\n",
"The dataset used in this notebook is a part of the [CDM Pricing Data](https://github.com/trifacta/trifacta-google-cloud/blob/main/design-pattern-pricing-optimization/CDM_Pricing_large_table.csv) which consists of products sales information on specified dates.\n",
"\n",
"## Objective\n",
"<a name=\"section-3\"></a>\n",
"The objective of this notebook is to build a pricing optimization model using Vertex AI on GCP. The following steps have been followed in this usecase : \n",
"\n",
"- Load the required dataset from a Cloud Storage bucket.\n",
"- Analyze the fields present in the dataset.\n",
"- Process the data to build a model.\n",
"- Build a BigQuery ML forecast model on the processed data.\n",
"- Get forecasted values from the BigQuery ML model.\n",
"- Interpret the forecasts to identify best prices.\n",
"- Clean up.\n",
"\n",
"\n",
"## Costs\n",
"<a name=\"section-4\"></a>\n",
"This tutorial uses the following billable components of Google Cloud:\n",
"\n",
"- Vertex AI\n",
"- Bigquery\n",
"- Cloud Storage\n",
"\n",
"\n",
"Learn about [Vertex AI\n",
"pricing](https://cloud.google.com/vertex-ai/pricing), [Bigquery pricing](https://cloud.google.com/bigquery/pricing) and [Cloud Storage\n",
"pricing](https://cloud.google.com/storage/pricing), and use the [Pricing\n",
"Calculator](https://cloud.google.com/products/calculator/)\n",
"to generate a cost estimate based on your projected usage.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5ed1f5e85640"
},
"source": [
"#### Set your project ID\n",
"\n",
"**If you don't know your project ID**, you may be able to get your project ID using `gcloud`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c3f30148b66d"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"PROJECT_ID = \"\"\n",
"\n",
"# Get your Google Cloud project ID from gcloud\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" shell_output = !gcloud config list --format 'value(core.project)' 2>/dev/null\n",
" PROJECT_ID = shell_output[0]\n",
" print(\"Project ID: \", PROJECT_ID)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "750bf2883c2d"
},
"source": [
"Otherwise, set your project ID here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3c6db1ca88b9"
},
"outputs": [],
"source": [
"if PROJECT_ID == \"\" or PROJECT_ID is None:\n",
" PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "2a1c270c7d34"
},
"source": [
"### Import the required libraries and define constants\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "acc6fac1fa55"
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"import seaborn as sns\n",
"from google.cloud import bigquery\n",
"from google.cloud.bigquery import Client"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "a06006dff8f9"
},
"outputs": [],
"source": [
"DATASET = \"[your-bigquery-dataset-id]\" # set the Bigquery dataset-id\n",
"TRAINING_DATA_TABLE = \"[your-bigquery-table-id-to-store-the-training-data]\" # set the Bigquery table-id to store the training data"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "016c3d47cc69"
},
"source": [
"## Create a BigQuery dataset\n",
"<a name=\"section-5\"></a>\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "12ccd8d7956e"
},
"source": [
"#@bigquery\n",
"-- create a dataset in BigQuery\n",
"\n",
"CREATE SCHEMA pricing_optimization\n",
"OPTIONS(\n",
" location=\"us\"\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c106b978a79b"
},
"source": [
"## Load the dataset from Cloud Storage\n",
"<a name=\"section-6\"></a>\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "8aeae9da9796"
},
"outputs": [],
"source": [
"DATA_LOCATION = \"gs://cloud-samples-data/ai-platform-unified/datasets/tabular/cdm_pricing_large_table.csv\"\n",
"df = pd.read_csv(DATA_LOCATION)\n",
"print(df.shape)\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7b98d5f09842"
},
"source": [
"We will build a forecast model on this data and thus determine the best price for a product. For this type of model, we may not be using many fields but just the sales and price related ones. For the current execrcise, we will just focus on the following fields :\n",
"- `Product_ID`\n",
"- `Customer_Hierarchy`\n",
"- `Fiscal_Date`\n",
"- `List_Price_Converged`\n",
"- `Invoiced_quantity_in_Pieces`\n",
"- `Net_Sales`\n",
"\n",
"\n",
"\n",
"## Data Analysis\n",
"<a name=\"section-7\"></a>\n",
"\n",
"First, we will explore the data and distributions.\n",
"\n",
"Select the required columns from the dataframe."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "af4b41c5eb1f"
},
"outputs": [],
"source": [
"id_col = \"Product_ID\"\n",
"date_col = \"Fiscal_Date\"\n",
"categ_cols = [\"Customer_Hierarchy\"]\n",
"num_cols = [\"List_Price_Converged\", \"Invoiced_quantity_in_Pieces\", \"Net_Sales\"]\n",
"\n",
"df = df[[id_col, date_col] + categ_cols + num_cols].copy()\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3d780043ee5b"
},
"source": [
"Check the column types and null values in the dataframe."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "f54c445a1288"
},
"outputs": [],
"source": [
"df.info()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "cd817b414c4d"
},
"source": [
"This data description reveals that there are no null values in the data. Also, the field `Fiscal_Date` which is a date field is loaded as an object type. \n",
"\n",
"Change the type of the date field to datetime."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "b160fac085c8"
},
"outputs": [],
"source": [
"df[\"Fiscal_Date\"] = pd.to_datetime(df[\"Fiscal_Date\"], infer_datetime_format=True)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "fb4778578064"
},
"source": [
"Plot the distributions for the categorical fields."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dd0467cd57c3"
},
"outputs": [],
"source": [
"for i in categ_cols:\n",
" df[i].value_counts(normalize=True).plot(kind=\"bar\")\n",
" plt.title(i)\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "145deed255e0"
},
"source": [
"Plot the distributions for the numerical fields."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "f934137c6d82"
},
"outputs": [],
"source": [
"for i in num_cols:\n",
" _, ax = plt.subplots(1, 2, figsize=(10, 4))\n",
" df[i].plot(kind=\"box\", ax=ax[0])\n",
" df[i].plot(kind=\"hist\", ax=ax[1])\n",
" ax[0].set_title(i + \"-Boxplot\")\n",
" ax[1].set_title(i + \"-Histogram\")\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f9b9c2e58380"
},
"source": [
"Check maximum date and minimum date in Fiscal_Date column."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "2a10aa689f9d"
},
"outputs": [],
"source": [
"print(df[\"Fiscal_Date\"].max())\n",
"print(df[\"Fiscal_Date\"].min())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4834f63e2e59"
},
"source": [
"Check the product distribution across each category."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "4664877f5304"
},
"outputs": [],
"source": [
"grp_cols = [\"Customer_Hierarchy\", \"Product_ID\"]\n",
"grp_df = df[grp_cols].groupby(by=grp_cols).count().reset_index()\n",
"grp_df.groupby(\"Customer_Hierarchy\").nunique()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "01ed02b9c8fd"
},
"source": [
"Check the percentage changes in the orders based on the percentage changes in the price."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "0b2c428cb135"
},
"outputs": [],
"source": [
"# aggregate the data\n",
"df_aggr = (\n",
" df.groupby([\"Product_ID\", \"List_Price_Converged\"])\n",
" .agg({\"Fiscal_Date\": min, \"Invoiced_quantity_in_Pieces\": sum, \"Net_Sales\": sum})\n",
" .reset_index()\n",
")\n",
"# rename the aggregated columns\n",
"df_aggr.rename(\n",
" columns={\n",
" \"Fiscal_Date\": \"First_price_date\",\n",
" \"Invoiced_quantity_in_Pieces\": \"Total_ordered_pieces\",\n",
" \"Net_Sales\": \"Total_net_sales\",\n",
" },\n",
" inplace=True,\n",
")\n",
"\n",
"# sort values chronologically\n",
"df_aggr.sort_values(by=[\"Product_ID\", \"First_price_date\"], inplace=True)\n",
"df_aggr.reset_index(drop=True, inplace=True)\n",
"\n",
"# add columns for previous values\n",
"df_aggr[\"Previous_List\"] = df_aggr.groupby([\"Product_ID\"])[\n",
" \"List_Price_Converged\"\n",
"].shift()\n",
"df_aggr[\"Previous_Total_ordered_pieces\"] = df_aggr.groupby([\"Product_ID\"])[\n",
" \"Total_ordered_pieces\"\n",
"].shift()\n",
"\n",
"# average price change across sku's\n",
"df_aggr[\"price_change_perc\"] = (\n",
" (df_aggr[\"List_Price_Converged\"] - df_aggr[\"Previous_List\"])\n",
" / df_aggr[\"Previous_List\"].fillna(0)\n",
" * 100\n",
")\n",
"df_aggr[\"order_change_perc\"] = (\n",
" (df_aggr[\"Total_ordered_pieces\"] - df_aggr[\"Previous_Total_ordered_pieces\"])\n",
" / df_aggr[\"Previous_Total_ordered_pieces\"].fillna(0)\n",
" * 100\n",
")\n",
"\n",
"# plot a scatterplot to visualize the changes\n",
"sns.scatterplot(\n",
" x=\"price_change_perc\",\n",
" y=\"order_change_perc\",\n",
" data=df_aggr,\n",
" hue=\"Product_ID\",\n",
" legend=False,\n",
")\n",
"plt.title(\"Percentage of change in price vs order\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8259e916fe25"
},
"source": [
"For most of the products, we see that the percentage change in orders are high where the percentage changes in the prices are low. This suggests that too much change in the prices can affect the number of orders. \n",
"\n",
"**Note**: There seem to be some outliers in the data as percentage changes greater than 800 are found and as evident from the box plots made earlier. In the current exercise, we will not take any manual measures to deal with outliers as we will create a BigQuery ML timeseries model that already deals with outliers.\n",
"\n",
"## Preprocess the data for training\n",
"<a name=\"section-8\"></a>\n",
"\n",
"Check which `Product_ID`s that have the maximum orders."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "f5cbc7709c6a"
},
"outputs": [],
"source": [
"df_orders = df.groupby([\"Product_ID\", \"Customer_Hierarchy\"], as_index=False)[\n",
" \"Invoiced_quantity_in_Pieces\"\n",
"].sum()\n",
"df_orders.loc[\n",
" df_orders.groupby(\"Customer_Hierarchy\")[\"Invoiced_quantity_in_Pieces\"].idxmax()\n",
"]"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "fd6d227e513e"
},
"source": [
"From the above result, we can infer the following :\n",
"\n",
"- Under **Food** category, **SKU 62** has maximum orders.\n",
"- Under **Manufacturing** category, **SKU 17** has maximum orders.\n",
"- Under **Paper** category, **SKU 107** has maximum orders.\n",
"- Under **Publishing** category, **SKU 8** has maximum orders.\n",
"- Under **Utilities** category, **SKU 140** has maximum orders.\n",
"\n",
"Given there are too many ids and only a few records for most of them, we will consider only the above `Product_ID`s for which there are maximum number of orders. \n",
"\n",
"**Note**: The `Invoiced_quantity_in_Pieces` field seem to be a *float* type rather than an *int* type as it should be. This could be probably because of the data itself might be averaged in the first place."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "2dbc0d64d157"
},
"source": [
"Check the various prices available for these `Product_ID`s."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "acc1dbd2d838"
},
"outputs": [],
"source": [
"df_type_food = df[(df[\"Product_ID\"] == \"SKU 62\") & (df[\"Customer_Hierarchy\"] == \"Food\")]\n",
"print(\"Food :\")\n",
"print(df_type_food[\"List_Price_Converged\"].value_counts())\n",
"df_type_manuf = df[\n",
" (df[\"Product_ID\"] == \"SKU 17\") & (df[\"Customer_Hierarchy\"] == \"Manufacturing\")\n",
"]\n",
"print(\"Manufacturing :\")\n",
"print(df_type_manuf[\"List_Price_Converged\"].value_counts())\n",
"df_type_paper = df[\n",
" (df[\"Product_ID\"] == \"SKU 107\") & (df[\"Customer_Hierarchy\"] == \"Paper\")\n",
"]\n",
"print(\"Paper :\")\n",
"print(df_type_paper[\"List_Price_Converged\"].value_counts())\n",
"df_type_pub = df[\n",
" (df[\"Product_ID\"] == \"SKU 8\") & (df[\"Customer_Hierarchy\"] == \"Publishing\")\n",
"]\n",
"print(\"Publishing :\")\n",
"print(df_type_pub[\"List_Price_Converged\"].value_counts())\n",
"df_type_util = df[\n",
" (df[\"Product_ID\"] == \"SKU 140\") & (df[\"Customer_Hierarchy\"] == \"Utilities\")\n",
"]\n",
"print(\"Utilities :\")\n",
"print(df_type_util[\"List_Price_Converged\"].value_counts())"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f023af578c0f"
},
"source": [
"In the publishing category, `Product_ID` `SKU 8` and `SKU 17` has less than or equal to two different prices in the entire data and so we will exclude them and consider the rest for building the forecast model. The idea here is to train a forecast model on the timeseries data for products with different prices.\n",
"\n",
"Join the data for all the `Product_ID`s into one dataframe and remove duplicate records."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "a44771cc4c20"
},
"outputs": [],
"source": [
"df_final = pd.concat([df_type_food, df_type_paper, df_type_util])\n",
"df_final = (\n",
" df_final[\n",
" [\n",
" \"Product_ID\",\n",
" \"Fiscal_Date\",\n",
" \"Customer_Hierarchy\",\n",
" \"List_Price_Converged\",\n",
" \"Invoiced_quantity_in_Pieces\",\n",
" ]\n",
" ]\n",
" .drop_duplicates()\n",
" .reset_index(drop=True)\n",
")\n",
"df_final.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "add5063df368"
},
"source": [
"Save the data to a BigQuery table."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fd82ba56571f"
},
"outputs": [],
"source": [
"bq_client = bigquery.Client(project=PROJECT_ID)\n",
"\n",
"job_config = bigquery.LoadJobConfig(\n",
" # Specify a (partial) schema. All columns are always written to the\n",
" # table. The schema is used to assist in data type definitions.\n",
" schema=[\n",
" bigquery.SchemaField(\"Product_ID\", bigquery.enums.SqlTypeNames.STRING),\n",
" bigquery.SchemaField(\"Fiscal_Date\", bigquery.enums.SqlTypeNames.DATE),\n",
" bigquery.SchemaField(\"List_Price_Converged\", bigquery.enums.SqlTypeNames.FLOAT),\n",
" bigquery.SchemaField(\n",
" \"Invoiced_quantity_in_Pieces\", bigquery.enums.SqlTypeNames.FLOAT\n",
" ),\n",
" ],\n",
" # Optionally, set the write disposition. BigQuery appends loaded rows\n",
" # to an existing table by default, but with WRITE_TRUNCATE write\n",
" # disposition it replaces the table with the loaded data.\n",
" write_disposition=\"WRITE_TRUNCATE\",\n",
")\n",
"\n",
"# save the dataframe to a table in the created dataset\n",
"job = bq_client.load_table_from_dataframe(\n",
" df_final,\n",
" \"{}.{}.{}\".format(PROJECT_ID, DATASET, TRAINING_DATA_TABLE),\n",
" job_config=job_config,\n",
") # Make an API request.\n",
"job.result() # Wait for the job to complete."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "fca77641b03b"
},
"source": [
"# Train the model using BigQuery ML\n",
"<a name=\"section-9\"></a>\n",
"\n",
"Train an [Arima-Plus](https://cloud.google.com/bigquery-ml/docs/reference/standard-sql/bigqueryml-syntax-create-time-series) model on the data using BigQuery ML."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "cded27507891"
},
"source": [
"#@bigquery\n",
"create or replace model pricing_optimization.bqml_arima\n",
"options\n",
" (model_type = 'ARIMA_PLUS',\n",
" time_series_timestamp_col = 'Fiscal_Date',\n",
" time_series_data_col = 'Invoiced_quantity_in_Pieces',\n",
" time_series_id_col = 'ID'\n",
" ) as\n",
"select\n",
" Fiscal_Date,\n",
" Concat(Product_ID,\"_\" ,Cast(List_Price_Converged as string)) as ID,\n",
" Invoiced_quantity_in_Pieces\n",
"from\n",
" pricing_optimization.TRAINING_DATA\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "332fd11ff32b"
},
"source": [
"## Generate forecasts from the model\n",
"<a name=\"section-10\"></a>\n",
"\n",
"Predict the sales for the next 30 days for each id and save to a dataframe."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ef926cdbf28e"
},
"outputs": [],
"source": [
"client = Client()\n",
"\n",
"query = '''\n",
"DECLARE HORIZON STRING DEFAULT \"30\"; #number of values to forecast\n",
"DECLARE CONFIDENCE_LEVEL STRING DEFAULT \"0.90\"; ## required confidence level\n",
"\n",
"EXECUTE IMMEDIATE format(\"\"\"\n",
" SELECT\n",
" *\n",
" FROM \n",
" ML.FORECAST(MODEL pricing_optimization.bqml_arima, \n",
" STRUCT(%s AS horizon, \n",
" %s AS confidence_level)\n",
" )\n",
" \"\"\",HORIZON,CONFIDENCE_LEVEL)'''\n",
"job = client.query(query)\n",
"dfforecast = job.to_dataframe()\n",
"dfforecast.head()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "608c7de72dae"
},
"source": [
"## Interpret the results to choose the best price\n",
"<a name=\"section-11\"></a>\n",
"\n",
"Calculate average forecast values for the forecast duration."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "e1e193680400"
},
"outputs": [],
"source": [
"dfforecast_avg = (\n",
" dfforecast[[\"ID\", \"forecast_value\"]].groupby(\"ID\", as_index=False).mean()\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5ce395d652a3"
},
"source": [
"Extract the ID and Price fields from the ID field."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "452c56fa58ed"
},
"outputs": [],
"source": [
"dfforecast_avg[\"Product_ID\"] = dfforecast_avg[\"ID\"].apply(lambda x: x.split(\"_\")[0])\n",
"dfforecast_avg[\"Price\"] = dfforecast_avg[\"ID\"].apply(lambda x: x.split(\"_\")[1])"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3cee67f4028f"
},
"source": [
"Plot the average forecasted sales vs. the price of the product."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fb351c8f383d"
},
"outputs": [],
"source": [
"for i in dfforecast_avg[\"Product_ID\"].unique():\n",
" dfforecast_avg[dfforecast_avg[\"Product_ID\"] == i].set_index(\"Price\").sort_values(\n",
" \"forecast_value\"\n",
" ).plot(kind=\"bar\")\n",
" plt.title(\"Price vs. Average Sales for \" + i)\n",
" plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "67ff3acc74a5"
},
"source": [
"Based on the plots for price vs. the average forecasted orders, it can be said that to avail the maximum orders, each of the considered `Product_ID`s can follow the below prices :\n",
"- SKU 107's price range can be from 4.44 - 4.73 units\n",
"- SKU 140's price can be 1.95 units\n",
"- SKU 62's price can be 4.23 units\n",
"\n",
"\n",
"## Clean Up\n",
"<a name=\"section-12\"></a>\n",
"\n",
"To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n",
"\n",
"Otherwise, you can delete the individual resources you created in this tutorial. The following code deletes the entire dataset."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "d78908b8134d"
},
"outputs": [],
"source": [
"# Construct a BigQuery client object.\n",
"client = bigquery.Client()\n",
"\n",
"# TODO(developer): Set model_id to the ID of the model to fetch.\n",
"dataset_id = \"{PROJECT}.{DATASET}\".format(PROJECT=PROJECT_ID, DATASET=DATASET)\n",
"\n",
"# Use the delete_contents parameter to delete a dataset and its contents.\n",
"# Use the not_found_ok parameter to not receive an error if the dataset has already been deleted.\n",
"client.delete_dataset(\n",
" dataset_id, delete_contents=True, not_found_ok=True\n",
") # Make an API request.\n",
"\n",
"print(\"Deleted dataset '{}'.\".format(dataset_id))"
]
}
],
"metadata": {
"colab": {
"name": "pricing-optimization.ipynb",
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
@@ -1,56 +1,33 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "18ebbd838e32"
},
"outputs": [],
"source": [
"# Copyright 2022 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "64f7165bd1ac"
},
"source": [
"# Telecom subscriber churn prediction on Vertex AI\n",
"# Telecom subscriber churn prediction on Vertex AI"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "1ecf72573623"
},
"source": [
"## Table of contents\n",
"\n",
"<table align=\"left\">\n",
" <td>\n",
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/subscriber_churn_prediction/telecom-subscriber-churn-prediction.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\">\n",
" View on GitHub\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/subscriber_churn_prediction/telecom-subscriber-churn-prediction.ipynb\">\n",
" <img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\\\" alt=\"Colab logo\"> Run in Colab\n",
" </a>\n",
" </td>\n",
" <td>\n",
" <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/workbench/subscriber_churn_prediction/telecom-subscriber-churn-prediction.ipynb\">\n",
" <img src=\"https://lh3.googleusercontent.com/UiNooY4LUgW_oTvpsNhPpQzsstV5W8F7rYgxgGBD85cWJoLmrOzhVs_ksK_vgx40SHs7jCqkTkCk=e14-rj-sc0xffffff-h130-w32\" alt=\"Vertex AI logo\">\n",
" Open in Vertex AI Workbench\n",
" </a>\n",
" </td>\n",
"</table>\n",
"<br/><br/><br/>"
"* [Overview](#section-1)\n",
"* [Dataset](#section-2)\n",
"* [Objective](#section-3)\n",
"* [Costs](#section-4)\n",
"* [Perform EDA](#section-5)\n",
"* [Train a logistic regression model using scikit-learn](#section-6)\n",
"* [Evaluate the trained model](#section-7)\n",
"* [Save the model to a Cloud Storage path](#section-8)\n",
"* [Create a model with Explainable AI support in Vertex AI](#section-9)\n",
"* [Get explanations from the model](#section-10)\n",
"* [Clean up](#section-11)\n"
]
},
{
@@ -60,28 +37,35 @@
},
"source": [
"## Overview\n",
"<a name=\"section-1\"></a>\n",
"\n",
"This example demonstrates building a subscriber churn prediction model on a [telecom customer churn dataset](https://www.kaggle.com/c/customer-churn-prediction-2020/overview). The generated churn model is further deployed to Vertex AI Endpoints and explanations are generated using the Explainable AI feature of Vertex AI. "
"This example demonstrates building a subscriber churn prediction model on a [telecom customer churn dataset](https://www.kaggle.com/c/customer-churn-prediction-2020/overview). The generated churn model is further deployed to Vertex AI Endpoints and explanations are generated using the Explainable AI feature of Vertex AI. \n",
"\n",
"*Note: This notebook file was designed to run in a [Vertex AI Workbench managed notebooks](https://cloud.google.com/vertex-ai/docs/workbench/managed/create-instance) instance using the `Python (Local)` kernel. Some components of this notebook may not work in other notebook environments.*"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8c7726146192"
"id": "4bae972f3229"
},
"source": [
"### Objective\n",
"## Dataset\n",
"<a name=\"section-2\"></a>\n",
"\n",
"This tutorial shows you how to do exploratory data analysis, preprocess data, train, deploy and get predictions from a churn prediction model on a tabular churn dataset. The objectives of this tutorial are as follows:\n",
"The dataset used in this tutorial is publicly available at Kaggle. See [Customer Churn Prediction 2020](https://www.kaggle.com/c/customer-churn-prediction-2020/data). "
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "75e3d160163c"
},
"source": [
"## Objective\n",
"<a name=\"section-3\"></a>\n",
"\n",
"This tutorial uses the following Google Cloud ML services and resources:\n",
"\n",
"- `Vertex AI Model` resource\n",
"- `Vertex AI Endpoint` resource\n",
"- `Vertex Explainable AI`\n",
"- Google Cloud Storage\n",
"\n",
"The steps performed include:\n",
"This tutorial shows you how to do exploratory data analysis, preprocess data, and train a churn prediction model on a tabular churn dataset. The steps include the following:\n",
"\n",
"- Load data from a Cloud Storage path\n",
"- Perform exploratory data analysis (EDA)\n",
@@ -95,24 +79,14 @@
"- Undeploy the model resource"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4bae972f3229"
},
"source": [
"### Dataset\n",
"\n",
"The dataset used in this tutorial is Telecom-Customer Churn dataset publicly available on Kaggle. See [Customer Churn Prediction 2020](https://www.kaggle.com/c/customer-churn-prediction-2020/data). This dataset is used to build and deploy a churn prediction model using Vertex AI in this notebook."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "556bf343c423"
},
"source": [
"### Costs \n",
"## Costs \n",
"<a name=\"section-4\"></a>\n",
"\n",
"\n",
"This tutorial uses billable components of Google Cloud:\n",
@@ -127,57 +101,13 @@
"to generate a cost estimate based on your projected usage."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "f0b0e0803638"
},
"source": [
"### Set up your local development environment\n",
"\n",
"**If you are using Colab or Vertex AI Workbench Notebooks**, your environment already meets\n",
"all the requirements to run this notebook. You can skip this step.\n",
"\n",
"**_NOTE_**: This notebook has been tested in the following environment:\n",
"\n",
"* Python version = 3.9\n",
"\n",
"**Otherwise**, make sure your environment meets this notebook's requirements.\n",
"You need the following:\n",
"\n",
"* The Google Cloud SDK\n",
"\n",
"The Google Cloud guide to [Setting up a Python development\n",
"environment](https://cloud.google.com/python/setup) and the [Jupyter\n",
"installation guide](https://jupyter.org/install) provide detailed instructions\n",
"for meeting these requirements. The following steps provide a condensed set of\n",
"instructions:\n",
"\n",
"1. [Install and initialize the Cloud SDK.](https://cloud.google.com/sdk/docs/)\n",
"\n",
"1. [Install Python 3.](https://cloud.google.com/python/setup#installing_python)\n",
"\n",
"1. [Install\n",
" virtualenv](https://cloud.google.com/python/setup#installing_and_using_virtualenv)\n",
" and create a virtual environment that uses Python 3. Activate the virtual environment.\n",
"\n",
"1. To install Jupyter, run `pip3 install jupyter` on the\n",
"command-line in a terminal shell.\n",
"\n",
"1. To launch Jupyter, run `jupyter notebook` on the command-line in a terminal shell.\n",
"\n",
"1. Open this notebook in the Jupyter Notebook Dashboard."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "44b8ae8e2d19"
},
"source": [
"## Installation\n",
"\n",
"Install the following packages required to execute this notebook. "
"## Installation"
]
},
{
@@ -190,55 +120,95 @@
"source": [
"import os\n",
"\n",
"# The Vertex AI Workbench Notebook product has specific requirements\n",
"IS_WORKBENCH_NOTEBOOK = os.getenv(\"DL_ANACONDA_HOME\")\n",
"IS_USER_MANAGED_WORKBENCH_NOTEBOOK = os.path.exists(\n",
" \"/opt/deeplearning/metadata/env_version\"\n",
")\n",
"# The Google Cloud Notebook product has specific requirements\n",
"IS_GOOGLE_CLOUD_NOTEBOOK = os.path.exists(\"/opt/deeplearning/metadata/env_version\")\n",
"\n",
"# Vertex AI Notebook requires dependencies to be installed with '--user'\n",
"USER_FLAG = \"\"\n",
"if IS_WORKBENCH_NOTEBOOK:\n",
" USER_FLAG = \"--user\"\n",
" \n",
"! pip3 install {USER_FLAG} --upgrade google-cloud-aiplatform \\\n",
" google-cloud-storage \\\n",
" category_encoders \\\n",
" seaborn \\\n",
" scikit-learn \\\n",
" pandas \\\n",
" fsspec \\\n",
" gcsfs -q "
"# Google Cloud Notebook requires dependencies to be installed with '--user'\n",
"if IS_GOOGLE_CLOUD_NOTEBOOK:\n",
" USER_FLAG = \"--user\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b24902cde81b"
"id": "606337930991"
},
"source": [
"### Restart the kernel\n",
"Install the latest version of the Vertex AI client library.\n",
"\n",
"After you install the additional packages, you need to restart the notebook kernel so it can find the packages."
"Run the following command in your virtual environment to install the Vertex SDK for Python:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "c61d171395d7"
"id": "9f52f949a77b"
},
"outputs": [],
"source": [
"# Automatically restart kernel after installs\n",
"import os\n",
"\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" # Automatically restart kernel after installs\n",
" import IPython\n",
"\n",
" app = IPython.Application.instance()\n",
" app.kernel.do_shutdown(True)"
"! pip install {USER_FLAG} --upgrade google-cloud-aiplatform"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "e67139e68463"
},
"source": [
"Install the Cloud Storage library:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "2ad918f94f5d"
},
"outputs": [],
"source": [
"! pip install {USER_FLAG} --upgrade google-cloud-storage"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "eb0c1e24a8f0"
},
"source": [
"Install the `category_encoders` library:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "deb95a7f2104"
},
"outputs": [],
"source": [
"! pip install --upgrade category_encoders"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "184560c1b742"
},
"source": [
"Install the `seaborn` library for the EDA step. If a Vertex AI Workbench managed notebooks instance is being used, this step is optional as the library is already available in the `Python (Local)` kernel."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "0d99cdcdc470"
},
"outputs": [],
"source": [
"! pip install --upgrade seaborn"
]
},
{
@@ -247,7 +217,7 @@
"id": "b012ef94ce80"
},
"source": [
"## Before you begin\n",
"## Before you begin \n",
"\n",
"### Set up your Google Cloud project\n",
"\n",
@@ -257,9 +227,9 @@
"\n",
"1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
"\n",
"1. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com). \n",
"1. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n",
"\n",
"1. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
"1. If you are running this notebook locally, you will need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
"\n",
"1. Enter your project ID in the cell below. Then run the cell to make sure the\n",
"Cloud SDK uses the right project for all the commands in this notebook.\n",
@@ -286,67 +256,34 @@
},
"outputs": [],
"source": [
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "6855b42885bf"
},
"outputs": [],
"source": [
"if PROJECT_ID == \"\" or PROJECT_ID is None or PROJECT_ID == \"[your-project-id]\":\n",
" # Get your GCP project id from gcloud\n",
" shell_output = ! gcloud config list --format 'value(core.project)' 2>/dev/null\n",
"PROJECT_ID = \"\"\n",
"\n",
"# Get your Google Cloud project ID from gcloud\n",
"if not os.getenv(\"IS_TESTING\"):\n",
" shell_output=!gcloud config list --format 'value(core.project)' 2>/dev/null\n",
" PROJECT_ID = shell_output[0]\n",
" print(\"Project ID:\", PROJECT_ID)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "f2e3c0f2cbfb"
},
"outputs": [],
"source": [
"! gcloud config set project $PROJECT_ID"
" print(\"Project ID: \", PROJECT_ID)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "60d535f443ac"
"id": "6855b42885bf"
},
"source": [
"#### Region\n",
"\n",
"You can also change the `REGION` variable, which is used for operations\n",
"throughout the rest of this notebook. Below are regions supported for Vertex AI. It is recommended that you choose the region closest to you.\n",
"\n",
"- Americas: `us-central1`\n",
"- Europe: `europe-west4`\n",
"- Asia Pacific: `asia-east1`\n",
"\n",
"You may not use a multi-regional bucket for training with Vertex AI. Not all regions provide support for all Vertex AI services.\n",
"\n",
"Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
"Otherwise, set your project ID here."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "3aaadaaf9b30"
"id": "59255d2246fd"
},
"outputs": [],
"source": [
"REGION = \"[your-region]\" # @param {type: \"string\"}\n",
"\n",
"if REGION == \"[your-region]\":\n",
" REGION = \"us-central1\""
"if PROJECT_ID == \"\" or PROJECT_ID is None:\n",
" PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
]
},
{
@@ -355,9 +292,9 @@
"id": "e663bd062c6f"
},
"source": [
"#### UUID\n",
"#### Timestamp\n",
"\n",
"If you are in a live tutorial session, you might be using a shared test account or project. To avoid name collisions between users on resources created, you create a uuid for each instance session, and append it onto the name of resources you create in this tutorial."
"If you are in a live tutorial session, you might be using a shared test account or project. To avoid name collisions between users on resources created, you create a timestamp for each instance session, and append it onto the name of resources you create in this tutorial."
]
},
{
@@ -368,84 +305,9 @@
},
"outputs": [],
"source": [
"import random\n",
"import string\n",
"from datetime import datetime\n",
"\n",
"\n",
"# Generate a uuid of a specifed length(default=8)\n",
"def generate_uuid(length: int = 8) -> str:\n",
" return \"\".join(random.choices(string.ascii_lowercase + string.digits, k=length))\n",
"\n",
"\n",
"UUID = generate_uuid()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3ffa6b6c7cdb"
},
"source": [
"### Authenticate your Google Cloud account\n",
"\n",
"**If you are using Vertex AI Workbench Notebooks**, your environment is already\n",
"authenticated. \n",
"\n",
"**If you are using Colab**, run the cell below and follow the instructions\n",
"when prompted to authenticate your account via oAuth.\n",
"\n",
"**Otherwise**, follow these steps:\n",
"\n",
"1. In the Cloud Console, go to the [**Create service account key**\n",
" page](https://console.cloud.google.com/apis/credentials/serviceaccountkey).\n",
"\n",
"2. Click **Create service account**.\n",
"\n",
"3. In the **Service account name** field, enter a name, and\n",
" click **Create**.\n",
"\n",
"4. In the **Grant this service account access to project** section, click the **Role** drop-down list. Type \"Vertex AI\"\n",
"into the filter box, and select\n",
" **Vertex AI Administrator**. Type \"Storage Object Admin\" into the filter box, and select **Storage Object Admin**.\n",
"\n",
"5. Click *Create*. A JSON file that contains your key downloads to your\n",
"local environment.\n",
"\n",
"6. Enter the path to your service account key as the\n",
"`GOOGLE_APPLICATION_CREDENTIALS` variable in the cell below and run the cell."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "2b72272258fc"
},
"outputs": [],
"source": [
"# If you are running this notebook in Colab, run this cell and follow the\n",
"# instructions to authenticate your GCP account. This provides access to your\n",
"# Cloud Storage bucket and lets you submit training jobs and prediction\n",
"# requests.\n",
"\n",
"import os\n",
"import sys\n",
"\n",
"# If on Vertex AI Workbench, then don't execute this code\n",
"IS_COLAB = \"google.colab\" in sys.modules\n",
"if not os.path.exists(\"/opt/deeplearning/metadata/env_version\") and not os.getenv(\n",
" \"DL_ANACONDA_HOME\"\n",
"):\n",
" if \"google.colab\" in sys.modules:\n",
" from google.colab import auth as google_auth\n",
"\n",
" google_auth.authenticate_user()\n",
"\n",
" # If you are running this notebook locally, replace the string below with the\n",
" # path to your service account key and run this cell to authenticate your GCP\n",
" # account.\n",
" elif not os.getenv(\"IS_TESTING\"):\n",
" %env GOOGLE_APPLICATION_CREDENTIALS '[your-service-account-key-path]'"
"TIMESTAMP = datetime.now().strftime(\"%Y%m%d%H%M%S\")"
]
},
{
@@ -465,7 +327,12 @@
"online predictions.\n",
"\n",
"Set the name of your Cloud Storage bucket below. It must be unique across all\n",
"Cloud Storage buckets."
"Cloud Storage buckets.\n",
"\n",
"You may also change the `REGION` variable, which is used for operations\n",
"throughout the rest of this notebook. Make sure to [choose a region where Vertex AI services are\n",
"available](https://cloud.google.com/vertex-ai/docs/general/locations#available_regions). You may\n",
"not use a Multi-Regional Storage bucket for training with Vertex AI."
]
},
{
@@ -476,8 +343,8 @@
},
"outputs": [],
"source": [
"BUCKET_NAME = \"[your-bucket-name]\" # @param {type:\"string\"}\n",
"BUCKET_URI = f\"gs://{BUCKET_NAME}\""
"BUCKET_NAME = \"gs://[your-bucket-name]\" # @param {type:\"string\"}\n",
"REGION = \"[your-region]\" # @param {type:\"string\"}"
]
},
{
@@ -488,9 +355,8 @@
},
"outputs": [],
"source": [
"if BUCKET_NAME == \"\" or BUCKET_NAME is None or BUCKET_NAME == \"[your-bucket-name]\":\n",
" BUCKET_NAME = PROJECT_ID + \"aip-\" + UUID\n",
" BUCKET_URI = f\"gs://{BUCKET_NAME}\""
"if BUCKET_NAME == \"\" or BUCKET_NAME is None or BUCKET_NAME == \"gs://[your-bucket-name]\":\n",
" BUCKET_NAME = \"gs://\" + PROJECT_ID + \"aip-\" + TIMESTAMP"
]
},
{
@@ -510,7 +376,7 @@
},
"outputs": [],
"source": [
"! gsutil mb -l $REGION -p $PROJECT_ID $BUCKET_URI"
"! gsutil mb -l $REGION $BUCKET_NAME"
]
},
{
@@ -530,7 +396,7 @@
},
"outputs": [],
"source": [
"! gsutil ls -al $BUCKET_URI"
"! gsutil ls -al $BUCKET_NAME"
]
},
{
@@ -563,15 +429,13 @@
"import pandas as pd\n",
"\n",
"%matplotlib inline\n",
"import pickle\n",
"# configure to don't display the warnings\n",
"import warnings\n",
"\n",
"import category_encoders as ce\n",
"import joblib\n",
"import seaborn as sns\n",
"from google.cloud import aiplatform, storage\n",
"from google.cloud.aiplatform_v1.types import SampledShapleyAttribution\n",
"from google.cloud.aiplatform_v1.types.explanation import ExplanationParameters\n",
"from sklearn.linear_model import LogisticRegression\n",
"from sklearn.metrics import confusion_matrix, plot_roc_curve\n",
"from sklearn.model_selection import train_test_split\n",
@@ -586,13 +450,7 @@
"id": "e37354341588"
},
"source": [
"### Load data from Cloud Storage using Pandas\n",
"\n",
"The Telecom-Customer Churn dataset from [Kaggle](https://www.kaggle.com/c/customer-churn-prediction-2020/overview) is made available on a public Cloud Storage bucket at: \n",
"\n",
"```gs://cloud-samples-data/vertex-ai/managed_notebooks/telecom_churn_prediction/train.csv```\n",
"\n",
"Use Pandas to read data directly from the URI."
"### Load data from Cloud Storage path using Pandas"
]
},
{
@@ -616,7 +474,8 @@
"id": "ca1a5b9da481"
},
"source": [
"## Perform EDA\n"
"## Perform EDA\n",
"<a name=\"section-5\"></a>"
]
},
{
@@ -1007,7 +866,8 @@
"id": "229cfba1fd32"
},
"source": [
"## Train a logistic regression model using scikit-learn\n"
"## Train a logistic regression model using scikit-learn\n",
"<a name=\"section-6\"></a>"
]
},
{
@@ -1037,7 +897,8 @@
"id": "18e23a047402"
},
"source": [
"## Evaluate the trained model\n"
"## Evaluate the trained model\n",
"<a name=\"section-7\"></a>"
]
},
{
@@ -1226,7 +1087,8 @@
"id": "a7fc7b467b6f"
},
"source": [
"## Save the model to a Cloud Storage path\n"
"## Save the model to a Cloud Storage path\n",
"<a name=\"section-8\"></a>"
]
},
{
@@ -1235,7 +1097,7 @@
"id": "ae43e214775f"
},
"source": [
"Save the trained model to a local file `model.pkl`."
"Save the trained model to a local file `model.joblib`."
]
},
{
@@ -1246,17 +1108,14 @@
},
"outputs": [],
"source": [
"FILE_NAME = \"model.pkl\"\n",
"with open(FILE_NAME, \"wb\") as file:\n",
" pickle.dump(model, file)\n",
"FILE_NAME = \"model.joblib\"\n",
"joblib.dump(model, FILE_NAME)\n",
"\n",
"# Upload the saved model file to Cloud Storage\n",
"BLOB_PATH = (\n",
" \"[your-blob-path]\" # leave blank if no folders inside the bucket are needed.\n",
")\n",
"\n",
"if BLOB_PATH == (\"[your-blob-path]\"):\n",
" BLOB_PATH = \"\"\n",
"\n",
"BLOB_NAME = BLOB_PATH + FILE_NAME\n",
"\n",
@@ -1272,10 +1131,9 @@
},
"source": [
"## Create a model with Explainable AI support in Vertex AI\n",
"<a name=\"section-9\"></a>\n",
"\n",
"Before creating a model, configure the explanations for the model. For further details, see [Configuring explanations in Vertex AI](https://cloud.google.com/vertex-ai/docs/explainable-ai/configuring-explanations#scikit-learn-and-xgboost-pre-built-containers).\n",
"\n",
"Set a display name for the model resource."
"Before creating a model, configure the explanations for the model. For further details, see [Configuring explanations in Vertex AI](https://cloud.google.com/vertex-ai/docs/explainable-ai/configuring-explanations#scikit-learn-and-xgboost-pre-built-containers)."
]
},
{
@@ -1286,13 +1144,10 @@
},
"outputs": [],
"source": [
"# Set the model display name\n",
"MODEL_DISPLAY_NAME = \"[your-model-display-name]\" # @param {type:\"string\"}\n",
"\n",
"if MODEL_DISPLAY_NAME == \"[your-model-display-name]\":\n",
" MODEL_DISPLAY_NAME = \"subscriber_churn_model\"\n",
"\n",
"MODEL_DISPLAY_NAME = \"[your-model-display-name]\"\n",
"ARTIFACT_GCS_PATH = f\"gs://{BUCKET_NAME}/{BLOB_PATH}\"\n",
"PROJECT = \"[your-project-id]\"\n",
"LOCATION = REGION\n",
"\n",
"# Feature-name(Inp_feature) and Output-name(Model_output) can be arbitrary\n",
"exp_metadata = {\"inputs\": {\"Inp_feature\": {}}, \"outputs\": {\"Model_output\": {}}}"
@@ -1308,17 +1163,15 @@
"source": [
"# Create a Vertex AI model resource with support for explanations\n",
"\n",
"\n",
"aiplatform.init(project=PROJECT_ID, location=REGION)\n",
"aiplatform.init(project=PROJECT, location=LOCATION)\n",
"explanation_parameters = {\"sampledShapleyAttribution\": {\"pathCount\": 25}}\n",
"\n",
"model = aiplatform.Model.upload(\n",
" display_name=MODEL_DISPLAY_NAME,\n",
" artifact_uri=ARTIFACT_GCS_PATH,\n",
" serving_container_image_uri=\"us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest\",\n",
" serving_container_image_uri=\"us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-24:latest\",\n",
" explanation_metadata=exp_metadata,\n",
" explanation_parameters=ExplanationParameters(\n",
" sampled_shapley_attribution=SampledShapleyAttribution(path_count=25)\n",
" ),\n",
" explanation_parameters=explanation_parameters,\n",
")\n",
"\n",
"model.wait()\n",
@@ -1339,7 +1192,7 @@
"gcloud beta ai models upload \\\n",
" --region=$REGION \\\n",
" --display-name=$MODEL_DISPLAY_NAME \\\n",
" --container-image-uri=\"us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.1-0:latest\" \\\n",
" --container-image-uri=\"us-docker.pkg.dev/vertex-ai/prediction/sklearn-cpu.0-24:latest\" \\\n",
" --artifact-uri=$ARTIFACT_GCS_PATH \\\n",
" --explanation-method=sampled-shapley \\\n",
" --explanation-path-count=25 \\\n",
@@ -1364,9 +1217,7 @@
},
"outputs": [],
"source": [
"ENDPOINT_DISPLAY_NAME = \"[your-endpoint-display-name]\" # @param {type:\"string\"}\n",
"if ENDPOINT_DISPLAY_NAME == \"[your-endpoint-display-name]\":\n",
" ENDPOINT_DISPLAY_NAME = \"subsc_churn_endpoint\""
"ENDPOINT_DISPLAY_NAME = \"[your-endpoint-display-name]\""
]
},
{
@@ -1378,13 +1229,33 @@
"outputs": [],
"source": [
"endpoint = aiplatform.Endpoint.create(\n",
" display_name=ENDPOINT_DISPLAY_NAME, project=PROJECT_ID, location=REGION\n",
" display_name=ENDPOINT_DISPLAY_NAME, project=PROJECT, location=LOCATION\n",
")\n",
"\n",
"print(endpoint.display_name)\n",
"print(endpoint.resource_name)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ae4c69ef8a8c"
},
"source": [
"Save the endpoint ID after the endpoint is created."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "6aa73d9a88d3"
},
"outputs": [],
"source": [
"ENDPOINT_ID = \"[your-endpoint-id]\""
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -1404,11 +1275,8 @@
},
"outputs": [],
"source": [
"DEPLOYED_MODEL_NAME = \"[deployment-model-name]\" # @param {type:\"string\"}\n",
"MACHINE_TYPE = \"n1-standard-4\"\n",
"\n",
"if DEPLOYED_MODEL_NAME == \"[deployment-model-name]\":\n",
" DEPLOYED_MODEL_NAME = \"subsc_churn_deployment\""
"DEPLOYED_MODEL_NAME = \"[deployment-model-name]\"\n",
"MACHINE_TYPE = \"n1-standard-4\""
]
},
{
@@ -1438,7 +1306,7 @@
"id": "359c43e630cb"
},
"source": [
"To ensure the model is deployed, the ID of the deployed model can be checked using the `endpoint.list_models()` method."
"Save the ID of the deployed model. The ID of the deployed model can also checked using the `endpoint.list_models()` method."
]
},
{
@@ -1449,7 +1317,7 @@
},
"outputs": [],
"source": [
"endpoint.list_models()"
"DEPLOYED_MODEL_ID = \"[your-deployed-model-id]\""
]
},
{
@@ -1458,7 +1326,8 @@
"id": "21a50d4e9946"
},
"source": [
"## Get explanations from the deployed model\n"
"## Get explanations from the deployed model\n",
"<a name=\"section-10\"></a>"
]
},
{
@@ -1467,7 +1336,7 @@
"id": "7b50c31e0552"
},
"source": [
"Get explanations for a test instance from the hosted model."
"Get explanations for some test instances from the hosted model."
]
},
{
@@ -1478,8 +1347,8 @@
},
"outputs": [],
"source": [
"# format a test instance as the request's payload\n",
"test_json = [X_test.iloc[0].tolist()]"
"# format the top 2 test instances as the request's payload\n",
"test_json = {\"instances\": [X_test.iloc[0].tolist(), X_test.iloc[1].tolist()]}"
]
},
{
@@ -1516,13 +1385,15 @@
" return\n",
"\n",
"\n",
"def explain_tabular_sample(project: str, location: str, endpoint, instances: list):\n",
"def explain_tabular_sample(\n",
" project: str, location: str, endpoint_id: str, instances: list\n",
"):\n",
" \"\"\"\n",
" Function to make an explanation request for the specified payload and generate feature attribution plots\n",
" \"\"\"\n",
" aiplatform.init(project=project, location=location)\n",
"\n",
" # endpoint = aiplatform.Endpoint(endpoint_id)\n",
" endpoint = aiplatform.Endpoint(endpoint_id)\n",
"\n",
" response = endpoint.explain(instances=instances)\n",
" print(\"#\" * 10 + \"Explanations\" + \"#\" * 10)\n",
@@ -1551,8 +1422,8 @@
" return response\n",
"\n",
"\n",
"# Get explanations for the test instance\n",
"prediction = explain_tabular_sample(PROJECT_ID, REGION, endpoint, test_json)"
"test_json = [X_test.iloc[0].tolist(), X_test.iloc[1].tolist()]\n",
"prediction = explain_tabular_sample(PROJECT, LOCATION, ENDPOINT_ID, test_json)"
]
},
{
@@ -1562,16 +1433,12 @@
},
"source": [
"## Clean up\n",
"<a name=\"section-11\"></a>\n",
"\n",
"To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n",
"project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial.\n",
"\n",
"Otherwise, you can delete the individual resources you created in this tutorial:\n",
"* Vertex AI Model\n",
"* Vertex AI Endpoint\n",
"* Cloud Storage bucket\n",
"\n",
"Set `delete_bucket` to *True* to delete the Cloud Storage bucket."
"Otherwise, you can delete the individual resources you created in this tutorial:"
]
},
{
@@ -1582,19 +1449,44 @@
},
"outputs": [],
"source": [
"# Undeploy model\n",
"endpoint.undeploy_all()\n",
"\n",
"# Delete the endpoint\n",
"endpoint.delete()\n",
"\n",
"# Delete the model\n",
"model.delete()\n",
"\n",
"# Delete the Cloud Storage bucket\n",
"delete_bucket = True\n",
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
" ! gsutil -m rm -r $BUCKET_URI"
"# undeploy the model\n",
"endpoint.undeploy(deployed_model_id=DEPLOYED_MODEL_ID)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "67e88dd0b2da"
},
"outputs": [],
"source": [
"# delete the endpoint\n",
"endpoint.delete()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "023779d4ac48"
},
"outputs": [],
"source": [
"# delete the model\n",
"model.delete()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dd3b12107312"
},
"outputs": [],
"source": [
"# remove the contents of the Cloud Storage bucket\n",
"! gsutil -m rm -r $BUCKET_NAME"
]
}
],

Some files were not shown because too many files have changed in this diff Show More