custom-tabular-bq-managed-dataset.ipynb: Remove preprocessing (#1278)

- [x] Removed pre-processing (normalization)

This simplifies the code but reduces accuracy.
This commit is contained in:
Ivan Cheung
2022-11-23 16:16:42 +00:00
committed by GitHub
parent b45e17efc0
commit 1ff7fc70ed
@@ -346,26 +346,6 @@
"! gsutil mb -l $REGION -p $PROJECT_ID $BUCKET_URI"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "import_aip"
},
"source": [
"### Import libraries"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cNEiwLd0lugu"
},
"outputs": [],
"source": [
"from google.cloud import aiplatform, bigquery"
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -385,6 +365,8 @@
},
"outputs": [],
"source": [
"from google.cloud import aiplatform\n",
"\n",
"# Initialize the Vertex AI SDK\n",
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)"
]
@@ -397,7 +379,9 @@
"source": [
"### Initialize BigQuery Client\n",
"\n",
"Initialize the BigQuery Python client for your project."
"Initialize the BigQuery Python client for your project.\n",
"\n",
"To use BigQuery, make sure your account has the \"BigQuery User\" role."
]
},
{
@@ -408,132 +392,129 @@
},
"outputs": [],
"source": [
"from google.cloud import bigquery\n",
"\n",
"# Set up BigQuery client\n",
"bqclient = bigquery.Client(project=PROJECT_ID)"
"bq_client = bigquery.Client(project=PROJECT_ID)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "container:training,prediction"
"id": "0a2c41bc91a6"
},
"source": [
"### Set pre-built containers\n",
"## Preprocess data and split data\n",
"First you should download and preprocess your data for training and testing.\n",
"\n",
"Vertex AI provides pre-built containers to run training and prediction.\n",
"\n",
"For the latest list, see [Pre-built containers for training](https://cloud.google.com/vertex-ai/docs/training/pre-built-containers) and [Pre-built containers for prediction](https://cloud.google.com/vertex-ai/docs/predictions/pre-built-containers)"
"- Convert categorical features to numeric\n",
"- Remove unused columns\n",
"- Remove unusable rows\n",
"- Split train and test data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "1u1mr18jlugv"
"id": "b5dfe6890137"
},
"outputs": [],
"source": [
"TRAIN_VERSION = \"tf-cpu.2-8\"\n",
"DEPLOY_VERSION = \"tf2-cpu.2-8\"\n",
"\n",
"TRAIN_IMAGE = \"us-docker.pkg.dev/vertex-ai/training/{}:latest\".format(TRAIN_VERSION)\n",
"DEPLOY_IMAGE = \"us-docker.pkg.dev/vertex-ai/prediction/{}:latest\".format(DEPLOY_VERSION)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "59f24e7d2269"
},
"source": [
"## Prepare the data\n",
"\n",
"To improve the convergence of the custom deep learning model, normalize the data. To prepare for this, calculate the mean and standard deviation for each numeric column.\n",
"\n",
"Pass these summary statistics to the training script to normalize the data before training. Later, during prediction, use these summary statistics again to normalize the testing data."
"# Define the BigQuery source dataset\n",
"BQ_SOURCE = \"bigquery-public-data.ml_datasets.penguins\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "8e52b7832cd3"
"id": "e3a2449cfcf1"
},
"outputs": [],
"source": [
"import json\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# Calculate mean and std across all rows\n",
"LABEL_COLUMN = \"species\"\n",
"\n",
"# Define the BigQuery source dataset\n",
"BQ_SOURCE = \"bq://bigquery-public-data.ml_datasets.penguins\"\n",
"BQ_SOURCE = \"bigquery-public-data.ml_datasets.penguins\"\n",
"\n",
"# Define NA values\n",
"NA_VALUES = [\"NA\", \".\"]\n",
"\n",
"# Download a table\n",
"table = bq_client.get_table(BQ_SOURCE)\n",
"df = bq_client.list_rows(table).to_dataframe()\n",
"\n",
"# Drop unusable rows\n",
"df = df.replace(to_replace=NA_VALUES, value=np.NaN).dropna()\n",
"\n",
"def download_table(bq_table_uri: str):\n",
" # Remove bq:// prefix if present\n",
" prefix = \"bq://\"\n",
" if bq_table_uri.startswith(prefix):\n",
" bq_table_uri = bq_table_uri[len(prefix) :]\n",
"df_numeric = df.select_dtypes(include=\"number\").astype(\"float32\")\n",
"df_numeric = (df_numeric - df_numeric.mean()) / df_numeric.std()\n",
"df[df_numeric.columns] = df_numeric\n",
"\n",
" table = bigquery.TableReference.from_string(bq_table_uri)\n",
" rows = bqclient.list_rows(\n",
" table,\n",
" )\n",
" return rows.to_dataframe()\n",
"# Convert categorical columns to numeric\n",
"df[\"island\"], _ = pd.factorize(df[\"island\"])\n",
"df[\"species\"], _ = pd.factorize(df[\"species\"])\n",
"df[\"sex\"], _ = pd.factorize(df[\"sex\"])\n",
"\n",
"\n",
"# Remove NA values\n",
"def clean_dataframe(df):\n",
" return df.replace(to_replace=NA_VALUES, value=np.NaN).dropna()\n",
"\n",
"\n",
"def calculate_mean_and_std(df):\n",
" # Calculate mean and std for each applicable column\n",
" mean_and_std = {}\n",
" dtypes = list(zip(df.dtypes.index, map(str, df.dtypes)))\n",
" # Normalize numeric columns.\n",
" for column, dtype in dtypes:\n",
" if dtype == \"float32\" or dtype == \"float64\":\n",
" mean_and_std[column] = {\n",
" \"mean\": df[column].mean(),\n",
" \"std\": df[column].std(),\n",
" }\n",
"\n",
" return mean_and_std\n",
"\n",
"\n",
"dataframe = download_table(BQ_SOURCE)\n",
"dataframe = clean_dataframe(dataframe)\n",
"mean_and_std = calculate_mean_and_std(dataframe)\n",
"\n",
"print(\"The mean and stds for each column are: \" + str(mean_and_std))\n",
"\n",
"# Write to a file\n",
"MEAN_AND_STD_JSON_FILE = \"mean_and_std.json\"\n",
"\n",
"with open(MEAN_AND_STD_JSON_FILE, \"w\") as outfile:\n",
" json.dump(mean_and_std, outfile)\n",
"\n",
"# Save to the staging bucket\n",
"! gsutil cp {MEAN_AND_STD_JSON_FILE} {BUCKET_URI}"
"# Split into a training and holdout dataset\n",
"df_train = df.sample(frac=0.8, random_state=100)\n",
"df_holdout = df[~df.index.isin(df_train.index)]"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "5c7732822757"
"id": "4e6a4fd28bab"
},
"source": [
"### Write the training dataset to BigQuery\n",
"Use the BigQuery SDK to create a dataset and write your training dataframe to it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "8b41bbd2380a"
},
"outputs": [],
"source": [
"# Write training dataset to BigQuery\n",
"\n",
"# Create BigQuery dataset\n",
"dataset_id = \"dataset_id_unique\"\n",
"bq_dataset = bigquery.Dataset(f\"{PROJECT_ID}.{dataset_id}\")\n",
"bq_dataset = bq_client.create_dataset(bq_dataset, exists_ok=True)\n",
"\n",
"# Reference: https://cloud.google.com/bigquery/docs/samples/bigquery-load-table-dataframe\n",
"table_id = \"table_id_unique\"\n",
"job = bq_client.load_table_from_dataframe(\n",
" dataframe=df_train,\n",
" destination=f\"{PROJECT_ID}.{dataset_id}.{table_id}\",\n",
")\n",
"\n",
"job.result()\n",
"\n",
"BQ_TRAIN_URI = str(job.destination)\n",
"\n",
"BQ_TRAIN_URI"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "a39df4692a70"
},
"source": [
"## Create a Vertex AI Tabular Dataset from BigQuery dataset\n",
"\n",
"Your first step in training the model is to create a Vertex AI tabular dataset resource."
"Create a Vertex AI tabular dataset resource from your BigQuery training data.\n",
"\n",
"See more info here: https://cloud.google.com/vertex-ai/docs/training/using-managed-datasets"
]
},
{
@@ -545,7 +526,7 @@
"outputs": [],
"source": [
"dataset = aiplatform.TabularDataset.create(\n",
" display_name=\"sample-penguins\", bq_source=BQ_SOURCE\n",
" display_name=\"sample-penguins\", bq_source=f\"bq://{BQ_TRAIN_URI}\"\n",
")"
]
},
@@ -574,13 +555,9 @@
"\n",
"Prepare the command-line arguments to pass to your training script.\n",
"- `args`: The command line arguments to pass to the corresponding Python module. In this example, they are:\n",
" - `\"--epochs=\" + EPOCHS`: The number of epochs for training.\n",
" - `\"--batch_size=\" + BATCH_SIZE`: The number of batch size for training.\n",
" - `\"--distribute=\" + TRAIN_STRATEGY` : The training distribution strategy to use for single or distributed training.\n",
" - `\"single\"`: single device.\n",
" - `\"mirror\"`: all GPU devices on a single compute instance.\n",
" - `\"multi\"`: all GPU devices on all compute instances.\n",
" - `\"--mean_and_std_json_file=\" + FILE_PATH`: The file on Cloud Storage with pre-calculated means and standard deviations."
" - `label_column`: The label column in your data to predict.\n",
" - `epochs`: The number of epochs for training.\n",
" - `batch_size`: The number of batch size for training."
]
},
{
@@ -593,16 +570,13 @@
"source": [
"JOB_NAME = \"custom_job_unique\"\n",
"\n",
"TRAIN_STRATEGY = \"single\"\n",
"\n",
"EPOCHS = 20\n",
"BATCH_SIZE = 10\n",
"\n",
"CMDARGS = [\n",
" \"--label_column=\" + LABEL_COLUMN,\n",
" \"--epochs=\" + str(EPOCHS),\n",
" \"--batch_size=\" + str(BATCH_SIZE),\n",
" \"--distribute=\" + TRAIN_STRATEGY,\n",
" \"--mean_and_std_json_file=\" + f\"{BUCKET_URI}/{MEAN_AND_STD_JSON_FILE}\",\n",
"]"
]
},
@@ -623,7 +597,11 @@
"- Sets a training distribution strategy according to the argument `args.distribute`.\n",
"- Trains the model (`fit()`) with epochs and batch size according to the arguments `args.epochs` and `args.batch_size`\n",
"- Gets the directory where to save the model artifacts from the environment variable `AIP_MODEL_DIR`. This variable is [set by the training service](https://cloud.google.com/vertex-ai/docs/training/code-requirements#environment-variables).\n",
"- Saves the trained model to the model directory."
"- Saves the trained model to the model directory.\n",
"\n",
"> **_NOTE:_** To improve model performance, it's recommended to normalize your inputs to the model before training. See the TensorFlow tutorial at https://www.tensorflow.org/tutorials/structured_data/preprocessing_layers#numerical_columns for details.\n",
"\n",
"> **_NOTE:_** The following training code requires you to grant the training account the \"BigQuery Read Session User\" role. See \"https://cloud.google.com/vertex-ai/docs/general/access-control#service-agents for details on how to find this account."
]
},
{
@@ -637,7 +615,6 @@
"%%writefile task.py\n",
"\n",
"import argparse\n",
"import tensorflow as tf\n",
"import numpy as np\n",
"import os\n",
"\n",
@@ -654,229 +631,67 @@
"\n",
"# Read args\n",
"parser = argparse.ArgumentParser()\n",
"parser.add_argument('--label_column', dest='label_column',\n",
" required=True, type=str,\n",
" help='Label column.')\n",
"parser.add_argument('--epochs', dest='epochs',\n",
" default=10, type=int,\n",
" help='Number of epochs.')\n",
"parser.add_argument('--batch_size', dest='batch_size',\n",
" default=10, type=int,\n",
" help='Batch size.')\n",
"parser.add_argument('--distribute', dest='distribute', type=str, default='single',\n",
" help='Distributed training strategy.')\n",
"parser.add_argument('--mean_and_std_json_file', dest='mean_and_std_json_file', type=str,\n",
" help='GCS URI to the JSON file with pre-calculated column means and standard deviations.')\n",
"args = parser.parse_args()\n",
"\n",
"def download_blob(bucket_name, source_blob_name, destination_file_name):\n",
" \"\"\"Downloads a blob from the bucket.\"\"\"\n",
" # bucket_name = \"your-bucket-name\"\n",
" # source_blob_name = \"storage-object-name\"\n",
" # destination_file_name = \"local/path/to/file\"\n",
"\n",
" storage_client = storage.Client()\n",
"\n",
" bucket = storage_client.bucket(bucket_name)\n",
"\n",
" # Construct a client side representation of a blob.\n",
" # Note `Bucket.blob` differs from `Bucket.get_blob` as it doesn't retrieve\n",
" # any content from Cloud Storage. As we don't need additional data,\n",
" # using `Bucket.blob` is preferred here.\n",
" blob = bucket.blob(source_blob_name)\n",
" blob.download_to_filename(destination_file_name)\n",
"\n",
" print(\n",
" \"Blob {} downloaded to {}.\".format(\n",
" source_blob_name, destination_file_name\n",
" )\n",
" )\n",
"\n",
"def extract_bucket_and_prefix_from_gcs_path(gcs_path: str):\n",
" \"\"\"Given a complete GCS path, return the bucket name and prefix as a tuple.\n",
"\n",
" Example Usage:\n",
"\n",
" bucket, prefix = extract_bucket_and_prefix_from_gcs_path(\n",
" \"gs://example-bucket/path/to/folder\"\n",
" )\n",
"\n",
" # bucket = \"example-bucket\"\n",
" # prefix = \"path/to/folder\"\n",
"\n",
" Args:\n",
" gcs_path (str):\n",
" Required. A full path to a Cloud Storage folder or resource.\n",
" Can optionally include \"gs://\" prefix or end in a trailing slash \"/\".\n",
"\n",
" Returns:\n",
" Tuple[str, Optional[str]]\n",
" A (bucket, prefix) pair from provided GCS path. If a prefix is not\n",
" present, None is returned in its place.\n",
" \"\"\"\n",
" if gcs_path.startswith(\"gs://\"):\n",
" gcs_path = gcs_path[5:]\n",
" if gcs_path.endswith(\"/\"):\n",
" gcs_path = gcs_path[:-1]\n",
"\n",
" gcs_parts = gcs_path.split(\"/\", 1)\n",
" gcs_bucket = gcs_parts[0]\n",
" gcs_blob_prefix = None if len(gcs_parts) == 1 else gcs_parts[1]\n",
"\n",
" return (gcs_bucket, gcs_blob_prefix)\n",
"\n",
"# Download means and std\n",
"def download_mean_and_std(mean_and_std_json_file):\n",
" \"\"\"Download mean and std for each column\"\"\"\n",
" import json\n",
" \n",
" bucket, file_path = extract_bucket_and_prefix_from_gcs_path(mean_and_std_json_file)\n",
" download_blob(bucket_name=bucket, source_blob_name=file_path, destination_file_name=file_path)\n",
" \n",
" with open(file_path, 'r') as file:\n",
" return json.loads(file.read())\n",
" \n",
"mean_and_std = download_mean_and_std(args.mean_and_std_json_file)\n",
"\n",
"# Single Machine, single compute device\n",
"if args.distribute == 'single':\n",
" if tf.test.is_gpu_available():\n",
" strategy = tf.distribute.OneDeviceStrategy(device=\"/gpu:0\")\n",
" else:\n",
" strategy = tf.distribute.OneDeviceStrategy(device=\"/cpu:0\")\n",
"# Single Machine, multiple compute device\n",
"elif args.distribute == 'mirror':\n",
" strategy = tf.distribute.MirroredStrategy()\n",
"# Multiple Machine, multiple compute device\n",
"elif args.distribute == 'multi':\n",
" strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()\n",
"\n",
"# Set up training variables\n",
"LABEL_COLUMN = \"species\"\n",
"UNUSED_COLUMNS = []\n",
"NA_VALUES = [\"NA\", \".\"]\n",
"LABEL_COLUMN = args.label_column\n",
"\n",
"# Possible categorical values\n",
"SPECIES = ['Adelie Penguin (Pygoscelis adeliae)',\n",
" 'Chinstrap penguin (Pygoscelis antarctica)',\n",
" 'Gentoo penguin (Pygoscelis papua)']\n",
"ISLANDS = ['Dream', 'Biscoe', 'Torgersen']\n",
"SEXES = ['FEMALE', 'MALE']\n",
"# See https://cloud.google.com/vertex-ai/docs/workbench/managed/executor#explicit-project-selection for issues regarding permissions.\n",
"PROJECT_NUMBER = os.environ[\"CLOUD_ML_PROJECT_ID\"]\n",
"bq_client = bigquery.Client(project=PROJECT_NUMBER)\n",
"\n",
"# Set up BigQuery clients\n",
"bqclient = bigquery.Client()\n",
"\n",
"# Download a table\n",
"def download_table(bq_table_uri: str):\n",
" # Remove bq:// prefix if present\n",
" prefix = \"bq://\"\n",
" if bq_table_uri.startswith(prefix):\n",
" bq_table_uri = bq_table_uri[len(prefix):]\n",
"\n",
" table = bigquery.TableReference.from_string(bq_table_uri)\n",
" rows = bqclient.list_rows(\n",
" table,\n",
" )\n",
" return rows.to_dataframe(create_bqstorage_client=False)\n",
"\n",
" bq_table_uri = bq_table_uri[len(prefix) :]\n",
" \n",
" # Download the BigQuery table as a dataframe\n",
" # This requires the \"BigQuery Read Session User\" role on the custom training service account.\n",
" table = bq_client.get_table(bq_table_uri)\n",
" return bq_client.list_rows(table).to_dataframe()\n",
"\n",
"# Download dataset splits\n",
"df_train = download_table(training_data_uri)\n",
"df_validation = download_table(validation_data_uri)\n",
"df_test = download_table(test_data_uri)\n",
"\n",
"# Remove NA values\n",
"def clean_dataframe(df):\n",
" return df.replace(to_replace=NA_VALUES, value=np.NaN).dropna()\n",
"\n",
"\n",
"df_train = clean_dataframe(df_train)\n",
"df_validation = clean_dataframe(df_validation)\n",
"\n",
"_CATEGORICAL_TYPES = {\n",
" \"island\": pd.api.types.CategoricalDtype(categories=ISLANDS),\n",
" \"species\": pd.api.types.CategoricalDtype(categories=SPECIES),\n",
" \"sex\": pd.api.types.CategoricalDtype(categories=SEXES),\n",
"}\n",
"\n",
"\n",
"def standardize(df, mean_and_std):\n",
" \"\"\"Scales numerical columns using their means and standard deviation to get\n",
" z-scores: the mean of each numerical column becomes 0, and the standard\n",
" deviation becomes 1. This can help the model converge during training.\n",
"\n",
" Args:\n",
" df: Pandas df\n",
"\n",
" Returns:\n",
" Input df with the numerical columns scaled to z-scores\n",
" \"\"\"\n",
" dtypes = list(zip(df.dtypes.index, map(str, df.dtypes)))\n",
" # Normalize numeric columns.\n",
" for column, dtype in dtypes:\n",
" if dtype == \"float32\":\n",
" df[column] -= mean_and_std[column][\"mean\"]\n",
" df[column] /= mean_and_std[column][\"std\"]\n",
" return df\n",
"\n",
"def preprocess(df):\n",
" \"\"\"Converts categorical features to numeric. Removes unused columns.\n",
"\n",
" Args:\n",
" df: Pandas df with raw data\n",
"\n",
" Returns:\n",
" df with preprocessed data\n",
" \"\"\"\n",
" df = df.drop(columns=UNUSED_COLUMNS)\n",
"\n",
" # Drop rows with NaN's\n",
" df = df.dropna()\n",
"\n",
" # Convert integer valued (numeric) columns to floating point\n",
" numeric_columns = df.select_dtypes([\"int32\", \"float32\", \"float64\"]).columns\n",
" df[numeric_columns] = df[numeric_columns].astype(\"float32\")\n",
"\n",
" # Convert categorical columns to numeric\n",
" cat_columns = df.select_dtypes([\"object\"]).columns\n",
"\n",
" df[cat_columns] = df[cat_columns].apply(\n",
" lambda x: x.astype(_CATEGORICAL_TYPES[x.name])\n",
" )\n",
" df[cat_columns] = df[cat_columns].apply(lambda x: x.cat.codes)\n",
" return df\n",
"\n",
"\n",
"def convert_dataframe_to_dataset(\n",
" df_train,\n",
" df_validation,\n",
" mean_and_std\n",
" df_train: pd.DataFrame,\n",
" df_validation: pd.DataFrame,\n",
"):\n",
" df_train = preprocess(df_train)\n",
" df_validation = preprocess(df_validation)\n",
"\n",
" df_train_x, df_train_y = df_train, df_train.pop(LABEL_COLUMN)\n",
" df_validation_x, df_validation_y = df_validation, df_validation.pop(LABEL_COLUMN)\n",
"\n",
" # Join train_x and eval_x to normalize on overall means and standard\n",
" # deviations. Then separate them again.\n",
" all_x = pd.concat([df_train_x, df_validation_x], keys=[\"train\", \"eval\"])\n",
" all_x = standardize(all_x, mean_and_std)\n",
" df_train_x, df_validation_x = all_x.xs(\"train\"), all_x.xs(\"eval\")\n",
"\n",
" y_train = np.asarray(df_train_y).astype(\"float32\")\n",
" y_validation = np.asarray(df_validation_y).astype(\"float32\")\n",
"\n",
" # Convert to numpy representation\n",
" x_train = np.asarray(df_train_x)\n",
" x_train = np.asarray(df_train_x) \n",
" x_test = np.asarray(df_validation_x)\n",
"\n",
" # Convert to one-hot representation\n",
" y_train = tf.keras.utils.to_categorical(y_train, num_classes=len(SPECIES))\n",
" y_validation = tf.keras.utils.to_categorical(y_validation, num_classes=len(SPECIES))\n",
" num_species = len(df_train_y.unique())\n",
" y_train = tf.keras.utils.to_categorical(y_train, num_classes=num_species)\n",
" y_validation = tf.keras.utils.to_categorical(y_validation, num_classes=num_species)\n",
"\n",
" dataset_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
" dataset_validation = tf.data.Dataset.from_tensor_slices((x_test, y_validation))\n",
" return (dataset_train, dataset_validation)\n",
"\n",
"# Create datasets\n",
"dataset_train, dataset_validation = convert_dataframe_to_dataset(df_train, df_validation, mean_and_std)\n",
"dataset_train, dataset_validation = convert_dataframe_to_dataset(df_train, df_validation)\n",
"\n",
"# Shuffle train set\n",
"dataset_train = dataset_train.shuffle(len(df_train))\n",
@@ -893,7 +708,7 @@
" input_dim=num_features,\n",
" ),\n",
" Dense(75, activation=tf.nn.relu),\n",
" Dense(50, activation=tf.nn.relu),\n",
" Dense(50, activation=tf.nn.relu), \n",
" Dense(25, activation=tf.nn.relu),\n",
" Dense(3, activation=tf.nn.softmax),\n",
" ]\n",
@@ -908,23 +723,16 @@
" return model\n",
"\n",
"# Create the model\n",
"with strategy.scope():\n",
" model = create_model(num_features=dataset_train._flat_shapes[0].dims[0].value)\n",
"model = create_model(num_features=dataset_train._flat_shapes[0].dims[0].value)\n",
"\n",
"# Set up datasets\n",
"NUM_WORKERS = strategy.num_replicas_in_sync\n",
"# Here the batch size scales up by number of workers since\n",
"# `tf.data.Dataset.batch` expects the global batch size.\n",
"GLOBAL_BATCH_SIZE = args.batch_size * NUM_WORKERS\n",
"dataset_train = dataset_train.batch(GLOBAL_BATCH_SIZE)\n",
"dataset_validation = dataset_validation.batch(GLOBAL_BATCH_SIZE)\n",
"dataset_train = dataset_train.batch(args.batch_size)\n",
"dataset_validation = dataset_validation.batch(args.batch_size)\n",
"\n",
"# Train the model\n",
"model.fit(dataset_train, epochs=args.epochs, validation_data=dataset_validation)\n",
"\n",
"tf.saved_model.save(model, os.getenv(\"AIP_MODEL_DIR\"))\n",
"\n",
"df_test.head()"
"tf.saved_model.save(model, os.getenv(\"AIP_MODEL_DIR\"))"
]
},
{
@@ -968,9 +776,9 @@
"job = aiplatform.CustomTrainingJob(\n",
" display_name=JOB_NAME,\n",
" script_path=\"task.py\",\n",
" container_uri=TRAIN_IMAGE,\n",
" container_uri=\"us-docker.pkg.dev/vertex-ai/training/tf-cpu.2-8:latest\",\n",
" requirements=[\"google-cloud-bigquery>=2.20.0\", \"db-dtypes\"],\n",
" model_serving_container_image_uri=DEPLOY_IMAGE,\n",
" model_serving_container_image_uri=\"us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-8:latest\",\n",
")\n",
"\n",
"MODEL_DISPLAY_NAME = \"penguins_model_unique\"\n",
@@ -1059,108 +867,23 @@
"source": [
"### Prepare test data\n",
"\n",
"Prepare test data by normalizing it and converting categorical values to numeric values.\n",
"You must normalize these values in the same way that your normalized training data.\n",
"\n",
"In this example, perform testing with the same dataset that you used for training. In practice, you generally want to use a separate test dataset to verify your results."
"Prepare test data by convert it to a Python list"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "e3a2449cfcf1"
"id": "67aeea91384a"
},
"outputs": [],
"source": [
"import pandas as pd\n",
"from google.cloud import bigquery\n",
"df_holdout_y = df_holdout.pop(LABEL_COLUMN)\n",
"df_holdout_x = df_holdout\n",
"\n",
"UNUSED_COLUMNS = []\n",
"LABEL_COLUMN = \"species\"\n",
"\n",
"# Possible categorical values\n",
"SPECIES = [\n",
" \"Adelie Penguin (Pygoscelis adeliae)\",\n",
" \"Chinstrap penguin (Pygoscelis antarctica)\",\n",
" \"Gentoo penguin (Pygoscelis papua)\",\n",
"]\n",
"ISLANDS = [\"Dream\", \"Biscoe\", \"Torgersen\"]\n",
"SEXES = [\"FEMALE\", \"MALE\"]\n",
"\n",
"_CATEGORICAL_TYPES = {\n",
" \"island\": pd.api.types.CategoricalDtype(categories=ISLANDS),\n",
" \"species\": pd.api.types.CategoricalDtype(categories=SPECIES),\n",
" \"sex\": pd.api.types.CategoricalDtype(categories=SEXES),\n",
"}\n",
"\n",
"\n",
"def standardize(df, mean_and_std):\n",
" \"\"\"Scales numerical columns using their means and standard deviation to get\n",
" z-scores: the mean of each numerical column becomes 0, and the standard\n",
" deviation becomes 1. This can help the model converge during training.\n",
"\n",
" Args:\n",
" df: Pandas df\n",
"\n",
" Returns:\n",
" Input df with the numerical columns scaled to z-scores\n",
" \"\"\"\n",
" dtypes = list(zip(df.dtypes.index, map(str, df.dtypes)))\n",
" # Normalize numeric columns.\n",
" for column, dtype in dtypes:\n",
" if dtype == \"float32\":\n",
" df[column] -= mean_and_std[column][\"mean\"]\n",
" df[column] /= mean_and_std[column][\"std\"]\n",
" return df\n",
"\n",
"\n",
"def preprocess(df, mean_and_std):\n",
" \"\"\"Converts categorical features to numeric. Removes unused columns.\n",
"\n",
" Args:\n",
" df: Pandas df with raw data\n",
"\n",
" Returns:\n",
" df with preprocessed data\n",
" \"\"\"\n",
" df = df.drop(columns=UNUSED_COLUMNS)\n",
"\n",
" # Drop rows with NaN's\n",
" df = df.dropna()\n",
"\n",
" # Convert integer valued (numeric) columns to floating point\n",
" numeric_columns = df.select_dtypes([\"int32\", \"float32\", \"float64\"]).columns\n",
" df[numeric_columns] = df[numeric_columns].astype(\"float32\")\n",
"\n",
" # Convert categorical columns to numeric\n",
" cat_columns = df.select_dtypes([\"object\"]).columns\n",
"\n",
" df[cat_columns] = df[cat_columns].apply(\n",
" lambda x: x.astype(_CATEGORICAL_TYPES[x.name])\n",
" )\n",
" df[cat_columns] = df[cat_columns].apply(lambda x: x.cat.codes)\n",
" return df\n",
"\n",
"\n",
"def convert_dataframe_to_list(df, mean_and_std):\n",
" df = preprocess(df, mean_and_std)\n",
"\n",
" df_x, df_y = df, df.pop(LABEL_COLUMN)\n",
"\n",
" # Normalize on overall means and standard deviations.\n",
" df = standardize(df, mean_and_std)\n",
"\n",
" y = np.asarray(df_y).astype(\"float32\")\n",
"\n",
" # Convert to numpy representation\n",
" x = np.asarray(df_x)\n",
"\n",
" # Convert to one-hot representation\n",
" return x.tolist(), y.tolist()\n",
"\n",
"\n",
"x_test, y_test = convert_dataframe_to_list(dataframe, mean_and_std)"
"# Convert to list representation\n",
"holdout_x = np.array(df_holdout_x).tolist()\n",
"holdout_y = np.array(df_holdout_y).astype(\"float32\").tolist()"
]
},
{
@@ -1193,10 +916,10 @@
},
"outputs": [],
"source": [
"predictions = endpoint.predict(instances=x_test)\n",
"predictions = endpoint.predict(instances=holdout_x)\n",
"y_predicted = np.argmax(predictions.predictions, axis=1)\n",
"\n",
"correct = sum(y_predicted == np.array(y_test))\n",
"correct = sum(y_predicted == np.array(holdout_y))\n",
"accuracy = len(y_predicted)\n",
"print(\n",
" f\"Correct predictions = {correct}, Total predictions = {accuracy}, Accuracy = {correct/accuracy}\"\n",