mirror of
https://github.com/GoogleCloudPlatform/vertex-ai-samples.git
synced 2026-09-26 22:51:56 +00:00
Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88eb0f35e8 | ||
|
|
8730fd6fec | ||
|
|
d7caba028c | ||
|
|
9ce9cec0a8 | ||
|
|
42bc870ee3 | ||
|
|
85f4e2b294 | ||
|
|
734836b928 | ||
|
|
0b13475152 | ||
|
|
4320bf500c | ||
|
|
07ec84687e | ||
|
|
06926f8318 | ||
|
|
067fab6aba | ||
|
|
b239467901 | ||
|
|
9cb60dc7f8 | ||
|
|
8ad0e435e8 | ||
|
|
e229ba997b | ||
|
|
8f1c79684f | ||
|
|
1ecb182603 | ||
|
|
68452d30ce | ||
|
|
2c12bcb257 | ||
|
|
688f748c1e | ||
|
|
57061d7a7b | ||
|
|
c26c240570 | ||
|
|
ce1f9080ee | ||
|
|
5909a3dbb1 | ||
|
|
5ec6512c3e | ||
|
|
c9ff35db22 | ||
|
|
2dd8729326 | ||
|
|
b9e07d9400 | ||
|
|
9d3c84dbd5 | ||
|
|
d2b07abdea | ||
|
|
fff45ff60a | ||
|
|
f115e52637 | ||
|
|
3c7c3f8b3a | ||
|
|
188525acc9 | ||
|
|
90da7214c7 | ||
|
|
ac4bf93914 | ||
|
|
80eefe2043 | ||
|
|
d573c9e7f5 | ||
|
|
62f49b91ec | ||
|
|
0901306cf5 | ||
|
|
2dd47e8c70 | ||
|
|
3e89a23166 | ||
|
|
d48692bd4b | ||
|
|
9fa9fb078e | ||
|
|
e170a5cb5a | ||
|
|
a6794907e4 | ||
|
|
fed657b8fb | ||
|
|
c2ca773c27 | ||
|
|
228cad82c2 | ||
|
|
d83ef25cc6 | ||
|
|
a6439ecb5e | ||
|
|
2cbebe604c | ||
|
|
432ce2aeb1 | ||
|
|
654907ad4d | ||
|
|
8d0ad548b2 | ||
|
|
0bb5343dca | ||
|
|
97a18feba0 | ||
|
|
7200238f4f | ||
|
|
d9058c2e4e | ||
|
|
1b6e663af0 | ||
|
|
5887f400c8 | ||
|
|
5e509423a6 | ||
|
|
bb61d92f80 | ||
|
|
34431b6511 | ||
|
|
ec3ec5a2c1 | ||
|
|
d9f5a40088 | ||
|
|
713a54815b | ||
|
|
06c87bc24d | ||
|
|
75c37416d8 | ||
|
|
ad99d0d0c0 | ||
|
|
c7b3e67989 |
@@ -12,4 +12,10 @@
|
||||
/prediction_featurestore_integration @googleapis/vertex-prediction-team
|
||||
/vertex_vision_model_garden/model_oss/util @weigary
|
||||
/vertex_vision_model_garden/model_oss/diffusers @weigary
|
||||
/vertex_vision_model_garden/model_oss/keras @dstnluong-google
|
||||
/vertex_vision_model_garden/model_oss/transformers @dstnluong-google
|
||||
/vertex_vision_model_garden/model_oss/pic2word @jismailyan-google
|
||||
/vertex_vision_model_garden/model_oss/open_clip @lydhr
|
||||
/vertex_vision_model_garden/model_oss/movinet @KCFindstr
|
||||
/vertex_vision_model_garden/model_oss/data_converter @KCFindstr
|
||||
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
"""Library with functions to use for data conversion."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union
|
||||
import uuid
|
||||
|
||||
from absl import logging
|
||||
import apache_beam as beam
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import PIL
|
||||
from PIL import Image
|
||||
import tensorflow as tf
|
||||
import yaml
|
||||
|
||||
from util import constants
|
||||
from util import fileutils
|
||||
from apache_beam.options import pipeline_options
|
||||
|
||||
REFORMATTED_CSV_SUFFIX = '-reformatted.csv'
|
||||
|
||||
LABEL_MAP_NAME = 'label_map.yaml'
|
||||
|
||||
_SPLIT_RATIO_ERROR_THRESHOLD = 1e-5
|
||||
# Internal constant. Only for distinguishing rows without ML use.
|
||||
ML_USE_UNASSIGNED = 'unassigned'
|
||||
ALL_ML_USES = (
|
||||
constants.ML_USE_TRAINING,
|
||||
constants.ML_USE_VALIDATION,
|
||||
constants.ML_USE_TEST,
|
||||
ML_USE_UNASSIGNED,
|
||||
)
|
||||
COLUMN_NAME_ML_USE = 'ml_use'
|
||||
COLUMN_NAME_GCS_FILE_PATH = 'gcs_file_path'
|
||||
COLUMN_NAME_LABEL = 'label'
|
||||
COLUMN_NAME_START_SEC = 'start_sec'
|
||||
COLUMN_NAME_END_SEC = 'end_sec'
|
||||
# Output filenames
|
||||
TRAIN_TFRECORD_NAME = 'train.tfrecord'
|
||||
VALIDATION_TFRECORD_NAME = 'val.tfrecord'
|
||||
TEST_TFRECORD_NAME = 'test.tfrecord'
|
||||
# Jsonl keys
|
||||
JSON_GCS_URI_KEY = 'imageGcsUri'
|
||||
JSON_RESOURCE_LABEL_KEY = 'dataItemResourceLabels'
|
||||
JSON_ML_USE_KEY = 'aiplatform.googleapis.com/ml_use'
|
||||
# I/O parameters
|
||||
READ_CHUNK_SIZE = 1024 * 1024 * 1024 # 1GB
|
||||
|
||||
|
||||
class WriteToTFRecord(beam.DoFn):
|
||||
"""DoFn to write TF examples to sharded TF record files."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
output_prefix: str,
|
||||
num_shards: int,
|
||||
convert_fn: Callable[[Dict[str, Any]], tf.train.Example],
|
||||
):
|
||||
self.output_prefix = output_prefix
|
||||
self.num_shards = num_shards
|
||||
self.writer: list[tf.io.TFRecordWriter] = []
|
||||
self.sharded_files: list[str] = []
|
||||
self.convert_fn = convert_fn
|
||||
self.success_counter = beam.metrics.Metrics.counter(
|
||||
self.__class__.__name__, 'Success'
|
||||
)
|
||||
self.failure_counter = beam.metrics.Metrics.counter(
|
||||
self.__class__.__name__, 'Failure'
|
||||
)
|
||||
|
||||
def start_bundle(self):
|
||||
logging.info('Start writing TF Record to %s.', self.output_prefix)
|
||||
unique_str = uuid.uuid4().hex
|
||||
for i in range(self.num_shards):
|
||||
uri = f'{self.output_prefix}-{i}-{unique_str}'
|
||||
self.sharded_files.append(uri)
|
||||
self.writer.append(tf.io.TFRecordWriter(uri))
|
||||
|
||||
def process(self, data: Dict[str, Any]) -> Iterable[Tuple[int, str]]:
|
||||
try:
|
||||
example = self.convert_fn(data)
|
||||
data = example.SerializeToString()
|
||||
idx = hash(data) % self.num_shards
|
||||
self.writer[idx].write(data)
|
||||
self.success_counter.inc()
|
||||
yield (idx, self.sharded_files[idx])
|
||||
# pylint: disable-next=broad-exception-caught
|
||||
except Exception as err:
|
||||
logging.error('Failed to process %s', data)
|
||||
logging.exception(err)
|
||||
self.failure_counter.inc()
|
||||
|
||||
def finish_bundle(self):
|
||||
logging.info('Finish writing TF Record to %s.', self.output_prefix)
|
||||
for writer in self.writer:
|
||||
writer.close()
|
||||
self.writer = []
|
||||
|
||||
|
||||
def convert_to_feature(
|
||||
value: Union[List[Union[int, float, bytes]], int, float, bytes],
|
||||
value_type: Optional[str] = None,
|
||||
) -> tf.train.Feature:
|
||||
"""Converts the given python object to a tf.train.Feature.
|
||||
|
||||
This is copied from tensorflow_models/official/vision/data/tfrecord_lib.py.
|
||||
|
||||
Args:
|
||||
value: int, float, bytes or a list of them.
|
||||
value_type: optional, if specified, forces the feature to be of the given
|
||||
type. Otherwise, type is inferred automatically. Can be one of ['bytes',
|
||||
'int64', 'float', 'bytes_list', 'int64_list', 'float_list']
|
||||
|
||||
Returns:
|
||||
feature: A tf.train.Feature object.
|
||||
"""
|
||||
|
||||
if value_type is None:
|
||||
element = value[0] if isinstance(value, list) else value
|
||||
|
||||
if isinstance(element, bytes):
|
||||
value_type = 'bytes'
|
||||
|
||||
elif isinstance(element, (int, np.integer)):
|
||||
value_type = 'int64'
|
||||
|
||||
elif isinstance(element, (float, np.floating)):
|
||||
value_type = 'float'
|
||||
|
||||
else:
|
||||
raise ValueError(
|
||||
'Cannot convert type {} to feature'.format(type(element))
|
||||
)
|
||||
|
||||
if isinstance(value, list):
|
||||
value_type = value_type + '_list'
|
||||
|
||||
if value_type == 'int64':
|
||||
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
|
||||
|
||||
elif value_type == 'int64_list':
|
||||
value = np.asarray(value).astype(np.int64).reshape(-1)
|
||||
return tf.train.Feature(int64_list=tf.train.Int64List(value=value))
|
||||
|
||||
elif value_type == 'float':
|
||||
return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))
|
||||
|
||||
elif value_type == 'float_list':
|
||||
value = np.asarray(value).astype(np.float32).reshape(-1)
|
||||
return tf.train.Feature(float_list=tf.train.FloatList(value=value))
|
||||
|
||||
elif value_type == 'bytes':
|
||||
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
|
||||
|
||||
elif value_type == 'bytes_list':
|
||||
return tf.train.Feature(bytes_list=tf.train.BytesList(value=value))
|
||||
|
||||
else:
|
||||
raise ValueError('Unknown value_type parameter - {}'.format(value_type))
|
||||
|
||||
|
||||
def convert_to_string_feature(
|
||||
value: str, encoding: str = 'utf-8'
|
||||
) -> tf.train.Feature:
|
||||
"""Returns a bytes_list from an encoded string."""
|
||||
return convert_to_feature(value.encode(encoding))
|
||||
|
||||
|
||||
def convert_to_list_string_feature(
|
||||
lst: list[str], encoding: str = 'utf-8'
|
||||
) -> tf.train.Feature:
|
||||
"""Returns a bytes_list from a list of encoded strings."""
|
||||
return convert_to_feature([value.encode(encoding) for value in lst])
|
||||
|
||||
|
||||
def create_ml_use_array_with_split(
|
||||
total_size: int,
|
||||
split_ratio: Sequence[float],
|
||||
) -> list[str]:
|
||||
"""Create randomized list of 'training', 'validation', 'test'.
|
||||
|
||||
The list of will be of length total_size with ratios according to train_size,
|
||||
validation_size, and test_size.
|
||||
|
||||
Args:
|
||||
total_size: Length of sequence to return
|
||||
split_ratio: Proportions to split into 'training', 'validation', and 'test'
|
||||
|
||||
Returns:
|
||||
List containing 'training', 'validation', and 'test'
|
||||
"""
|
||||
train_size, validation_size, _ = split_ratio
|
||||
num_train = round(train_size * total_size)
|
||||
num_validation = round(validation_size * total_size)
|
||||
num_test = total_size - num_train - num_validation
|
||||
ml_use_row = (
|
||||
[constants.ML_USE_TRAINING] * num_train
|
||||
+ [constants.ML_USE_VALIDATION] * num_validation
|
||||
+ [constants.ML_USE_TEST] * num_test
|
||||
)
|
||||
random.shuffle(ml_use_row)
|
||||
return ml_use_row
|
||||
|
||||
|
||||
def format_ml_use_column(df: pd.DataFrame):
|
||||
df[COLUMN_NAME_ML_USE].replace(
|
||||
# We need to support non-standard ML uses other than documented ones,
|
||||
# since they are used by some existing datasets.
|
||||
[r'(?i)^train(ing)?$', r'(?i)^test$', r'(?i)^validat(ion|e)$'],
|
||||
[
|
||||
constants.ML_USE_TRAINING,
|
||||
constants.ML_USE_TEST,
|
||||
constants.ML_USE_VALIDATION,
|
||||
],
|
||||
inplace=True,
|
||||
regex=True,
|
||||
)
|
||||
|
||||
|
||||
def insert_missing_ml_use(df: pd.DataFrame) -> None:
|
||||
"""For every row that does not have ml_use as the first column, insert a column containing 'unassigned' to the front.
|
||||
|
||||
Args:
|
||||
df: The DataFrame to process. The first column should be 'ml_use'.
|
||||
"""
|
||||
df[COLUMN_NAME_ML_USE].fillna(ML_USE_UNASSIGNED, inplace=True)
|
||||
rows_to_fill = ~df[COLUMN_NAME_ML_USE].isin(ALL_ML_USES)
|
||||
df.loc[rows_to_fill] = df[rows_to_fill].shift(
|
||||
axis=1, fill_value=ML_USE_UNASSIGNED
|
||||
)
|
||||
|
||||
|
||||
def replace_unassigned_ml_use(
|
||||
ml_uses: List[str],
|
||||
split_ratio: Sequence[float],
|
||||
):
|
||||
"""Replace `unassigned` in ml_uses with `training`, `validation`, and `test` with ratios according to split_ratio.
|
||||
|
||||
Args:
|
||||
ml_uses: List of ml_use string values.
|
||||
split_ratio: Proportions to split into `training`, `validation`, and `test`.
|
||||
"""
|
||||
unassigned_indices = [
|
||||
i for i, ml_use in enumerate(ml_uses) if ml_use == ML_USE_UNASSIGNED
|
||||
]
|
||||
ml_use_arr = create_ml_use_array_with_split(
|
||||
len(unassigned_indices), split_ratio
|
||||
)
|
||||
for unassigned_index, ml_use in zip(unassigned_indices, ml_use_arr):
|
||||
ml_uses[unassigned_index] = ml_use
|
||||
|
||||
|
||||
def merge_seq_into_dicts(
|
||||
key: str, values: Sequence[Any], dicts: Sequence[Dict[Any, Any]]
|
||||
):
|
||||
"""Merges a list of values into a list of dicts, inserted with the given key.
|
||||
|
||||
Args:
|
||||
key: Key to insert or overwrite in the dictionary.
|
||||
values: A list of values to insert.
|
||||
dicts: A list of dictionaries. Each value will be inserted into the
|
||||
corresponding dictionary. The original value will be overwritten if the
|
||||
key already existed.
|
||||
|
||||
Raises:
|
||||
ValueError: The values and dicts have different lengths.
|
||||
"""
|
||||
if len(values) != len(dicts):
|
||||
raise ValueError(
|
||||
f'Length of values and dicts must match, got {len(values)} and'
|
||||
f' {len(dicts)}'
|
||||
)
|
||||
for val, d in zip(values, dicts):
|
||||
d[key] = val
|
||||
|
||||
|
||||
def drop_invalid_rows(df: pd.DataFrame) -> int:
|
||||
"""Drops DataFrame rows missing the gcs_file_path column or the label column.
|
||||
|
||||
Args:
|
||||
df: The DataFrame to process in place.
|
||||
|
||||
Returns:
|
||||
The number of rows dropped.
|
||||
"""
|
||||
original_rows = df.shape[0]
|
||||
df.dropna(subset=[COLUMN_NAME_GCS_FILE_PATH, COLUMN_NAME_LABEL], inplace=True)
|
||||
dropped_num = original_rows - df.shape[0]
|
||||
if dropped_num > 0:
|
||||
df.reset_index(drop=True, inplace=True)
|
||||
return dropped_num
|
||||
|
||||
|
||||
def check_split_ratio(split_ratio: Sequence[float]):
|
||||
"""Checks if the give split ratio is valid.
|
||||
|
||||
Args:
|
||||
split_ratio: Proportions to split into 'training', 'validation', and 'test'
|
||||
|
||||
Raises:
|
||||
ValueError: Must have valid entries, correct length, and sum to 1.
|
||||
"""
|
||||
if len(split_ratio) != 3:
|
||||
raise ValueError('split_ratio must contain exactly 3 values.')
|
||||
if abs(sum(split_ratio) - 1) > _SPLIT_RATIO_ERROR_THRESHOLD:
|
||||
raise ValueError('split_ratio must sum to 1.')
|
||||
if not all([0 <= val <= 1 for val in split_ratio]):
|
||||
raise ValueError('Entries of split_ratio must be in the range [0, 1].')
|
||||
|
||||
|
||||
def check_num_shard(num_shard: Sequence[int]):
|
||||
"""Checks if the number of shards is valid.
|
||||
|
||||
Args:
|
||||
num_shard: The number of shards for each tfrecord.
|
||||
|
||||
Raises:
|
||||
ValueError: Must have valid entries and correct length.
|
||||
"""
|
||||
if len(num_shard) != 3:
|
||||
raise ValueError('num_shard must contain exactly 3 values.')
|
||||
if not all([val >= 1 for val in num_shard]):
|
||||
raise ValueError('Shards must be at least 1.')
|
||||
|
||||
|
||||
def create_label_map_yaml(meta_data_path: str, output_dir: str) -> None:
|
||||
"""Generate label_map.yaml from meta_data.yaml.
|
||||
|
||||
Args:
|
||||
meta_data_path: Path to a meta_data.yaml file.
|
||||
output_dir: Directory to output label_map.yaml.
|
||||
"""
|
||||
tf.io.gfile.copy(
|
||||
meta_data_path, os.path.join(output_dir, LABEL_MAP_NAME), overwrite=True
|
||||
)
|
||||
|
||||
|
||||
def reformat_bbox(
|
||||
bbox: Sequence[int], img_width: int, img_height: int
|
||||
) -> Tuple[float, float, float, float]:
|
||||
"""Converts XYWH unnormalized bounding box with to a normalized XYXY bounding box.
|
||||
|
||||
Args:
|
||||
bbox: Relative bounding box with unnormalized coordinates as [x, y, width,
|
||||
height].
|
||||
img_width: Image's pixel width.
|
||||
img_height: Image's pixel height.
|
||||
|
||||
Returns:
|
||||
Absolute bounding box with normalized coordinates as
|
||||
[xmin, ymin, xmax, ymax].
|
||||
"""
|
||||
x, y, width, height = bbox
|
||||
xmin = x / img_width
|
||||
ymin = y / img_height
|
||||
xmax = (x + width) / img_width
|
||||
ymax = (y + height) / img_height
|
||||
return xmin, ymin, xmax, ymax
|
||||
|
||||
|
||||
def encode_image(
|
||||
filepath: str,
|
||||
output_shape: Optional[Sequence[int]] = None,
|
||||
image_format: str = 'png',
|
||||
) -> Tuple[bytes, Sequence[int]]:
|
||||
"""Encodes an image at the given path.
|
||||
|
||||
Args:
|
||||
filepath: Path to the image.
|
||||
output_shape: The output shape of the image, (height, width).
|
||||
image_format: The format of the output image.
|
||||
|
||||
Returns:
|
||||
The encoded image data in bytes and the shape of the image, (height, width).
|
||||
|
||||
Raises:
|
||||
IOError: The image file is corrupt.
|
||||
"""
|
||||
filepath = fileutils.force_gcs_fuse_path(filepath)
|
||||
with open(filepath, 'rb') as f:
|
||||
# If an output_shape is specified, resize the image and set data to the new
|
||||
# bytes.
|
||||
try:
|
||||
img = Image.open(f)
|
||||
except PIL.UnidentifiedImageError as e:
|
||||
raise IOError(f'Failed to open {filepath}') from e
|
||||
|
||||
try:
|
||||
if output_shape is not None:
|
||||
rgb_img = img.resize((output_shape[1], output_shape[0])).convert('RGB')
|
||||
else:
|
||||
rgb_img = img.convert('RGB')
|
||||
rgb_img = np.array(rgb_img)
|
||||
|
||||
_, data = cv2.imencode(f'.{image_format}', rgb_img)
|
||||
data = data.tobytes()
|
||||
return data, rgb_img.shape
|
||||
except cv2.error as e:
|
||||
raise IOError(f'Failed to encode {filepath}') from e
|
||||
finally:
|
||||
img.close()
|
||||
|
||||
|
||||
def encode_video(
|
||||
filepath: str,
|
||||
start_sec: float,
|
||||
end_sec: float,
|
||||
output_fps: int = 5,
|
||||
output_shape: Optional[Sequence[int]] = None,
|
||||
image_format: str = 'jpg',
|
||||
) -> Sequence[bytes]:
|
||||
"""Encodes a video clip at the given path with start and end timestamps.
|
||||
|
||||
Args:
|
||||
filepath: Path to the video.
|
||||
start_sec: Start timestamp of the video clip in seconds.
|
||||
end_sec: End timestamp of the video clip in seconds.
|
||||
output_fps: The output frame rate per second.
|
||||
output_shape: The output shape of each frame, (height, width).
|
||||
image_format: The format of the encoded frames.
|
||||
|
||||
Returns:
|
||||
A list of the encoded frames data in bytes.
|
||||
|
||||
Raises:
|
||||
IOError if the video file is corrupt.
|
||||
"""
|
||||
filepath = fileutils.force_gcs_fuse_path(filepath)
|
||||
video = None
|
||||
|
||||
try:
|
||||
video = cv2.VideoCapture(filepath)
|
||||
frames = []
|
||||
frame_interval = 1 / output_fps
|
||||
total_frames = video.get(cv2.CAP_PROP_FRAME_COUNT)
|
||||
original_fps = video.get(cv2.CAP_PROP_FPS)
|
||||
if not original_fps:
|
||||
# 0 or None indicates the video is invalid
|
||||
raise IOError(f'Failed to load {filepath}')
|
||||
video_length = total_frames / original_fps
|
||||
start_sec = max(start_sec, 0)
|
||||
end_sec = min(end_sec, video_length)
|
||||
for t in np.arange(start_sec, end_sec, frame_interval):
|
||||
frame_idx = min(total_frames - 1, round(t * original_fps))
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
|
||||
ret, frame = video.read()
|
||||
if not ret:
|
||||
raise IOError(f'Failed to load {filepath} at frame {frame_idx}')
|
||||
if output_shape is not None:
|
||||
frame = cv2.resize(frame, (output_shape[1], output_shape[0]))
|
||||
_, data = cv2.imencode(f'.{image_format}', frame)
|
||||
frames.append(data.tobytes())
|
||||
except cv2.error as e:
|
||||
raise IOError(f'Failed to load {filepath}') from e
|
||||
finally:
|
||||
if video:
|
||||
video.release()
|
||||
return frames
|
||||
|
||||
|
||||
def create_label_map(
|
||||
labels: Sequence[str],
|
||||
) -> Tuple[Sequence[int], Dict[int, str]]:
|
||||
"""Creates a label map from a sequence of label strings.
|
||||
|
||||
Args:
|
||||
labels: The sequence of labels to create label map from. Must not contain
|
||||
invalid values, which means data without labels should be filtered first.
|
||||
|
||||
Returns:
|
||||
The integer labels and the mapping from integers to the original strings.
|
||||
"""
|
||||
inverse_label_map: Dict[str, int] = dict()
|
||||
num_labels = 0
|
||||
for label in labels:
|
||||
if label not in inverse_label_map:
|
||||
num_labels += 1
|
||||
inverse_label_map[label] = num_labels
|
||||
int_labels = [inverse_label_map[label] for label in labels]
|
||||
label_map = {value: key for key, value in inverse_label_map.items()}
|
||||
return int_labels, label_map
|
||||
|
||||
|
||||
def write_label_map(output_file: str, label_map: Dict[int, str]) -> None:
|
||||
"""Writes a label map to the output file, which can be a GCS uri."""
|
||||
with tf.io.gfile.GFile(output_file, 'w') as f:
|
||||
yaml.dump({'label_map': label_map}, f)
|
||||
|
||||
|
||||
def detectron_json_to_image_rows(input_json: str) -> list[Dict[str, Any]]:
|
||||
"""Converts a Detectron JSON file to a list of image rows.
|
||||
|
||||
Args:
|
||||
input_json: A path to a Detectron JSON or JSONL file.
|
||||
|
||||
Returns:
|
||||
A list of dictionaries, where each dictionary contains Detectron format
|
||||
entry.
|
||||
|
||||
Raises:
|
||||
ValueError: If the input JSON is invalid.
|
||||
"""
|
||||
|
||||
image_rows = []
|
||||
with tf.io.gfile.GFile(input_json, 'r') as f:
|
||||
for line in f:
|
||||
json_data = json.loads(line)
|
||||
if isinstance(json_data, dict):
|
||||
image_rows.append(json_data)
|
||||
elif isinstance(json_data, list):
|
||||
image_rows.extend(json_data)
|
||||
else:
|
||||
raise ValueError(
|
||||
'The input JSON is invalid. Dict or list is expected, but got '
|
||||
f'{type(json_data)}.'
|
||||
)
|
||||
return image_rows
|
||||
|
||||
|
||||
def coco_json_to_image_rows(
|
||||
input_json: str,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Converts a COCO JSON file to a list of image rows.
|
||||
|
||||
Args:
|
||||
input_json: A path to a COCO JSON or JSONL file.
|
||||
|
||||
Returns:
|
||||
A list of dictionaries, where each dictionary contains COCO format entry.
|
||||
|
||||
Raises:
|
||||
ValueError: If the input JSON is invalid.
|
||||
"""
|
||||
|
||||
with tf.io.gfile.GFile(input_json, 'r') as f:
|
||||
coco_json = json.load(f)
|
||||
if 'annotations' not in coco_json:
|
||||
raise ValueError('"annotations" is not in the dataset.')
|
||||
if 'images' not in coco_json:
|
||||
raise ValueError('"images" is not in the dataset.')
|
||||
|
||||
images = coco_json['images']
|
||||
return images
|
||||
|
||||
|
||||
def partition_by_ml_use(element: Dict[str, Any], num_partitions: int) -> int:
|
||||
"""Beam partition function to split data by ml_use."""
|
||||
del num_partitions
|
||||
try:
|
||||
partition = ALL_ML_USES.index(element[COLUMN_NAME_ML_USE])
|
||||
except Exception as e:
|
||||
raise ValueError(f'Invalid ML use: {element[COLUMN_NAME_ML_USE]}') from e
|
||||
return partition
|
||||
|
||||
|
||||
def run_beam_pipeline(pipeline: Any) -> None:
|
||||
"""Runs a beam pipeline. Works in both internal and docker environment."""
|
||||
options = pipeline_options.PipelineOptions([
|
||||
'--runner=FlinkRunner',
|
||||
'--faster_copy',
|
||||
'--max_parallelism', '8',
|
||||
])
|
||||
p = beam.Pipeline(options=options)
|
||||
pipeline(p)
|
||||
result = p.run()
|
||||
result.wait_until_finish()
|
||||
for counter in result.metrics().query()['counters']:
|
||||
logging.info('%s counter: %s.', counter.key.metric.name, counter)
|
||||
logging.info('Completing beam pipeline.')
|
||||
|
||||
|
||||
def beam_convert_tfexamples(
|
||||
root: beam.Pipeline,
|
||||
data_list: Sequence[Dict[str, Any]],
|
||||
convert_fn: Callable[[Dict[str, Any]], tf.train.Example],
|
||||
output_dir: str,
|
||||
num_shards: Sequence[int],
|
||||
) -> None:
|
||||
"""Constructs beam pipelines to convert train, val, test TF Examples."""
|
||||
names = [TRAIN_TFRECORD_NAME, VALIDATION_TFRECORD_NAME, TEST_TFRECORD_NAME]
|
||||
split_data = (
|
||||
root
|
||||
| 'Create PCollection' >> beam.Create(data_list)
|
||||
| 'Data split' >> beam.Partition(partition_by_ml_use, 3)
|
||||
)
|
||||
for i in range(3):
|
||||
ml_use: str = ALL_ML_USES[i]
|
||||
num_shard = num_shards[i]
|
||||
output_prefix = os.path.join(output_dir, names[i])
|
||||
_ = (
|
||||
split_data[i]
|
||||
| f'Convert {ml_use} TF Examples'
|
||||
>> beam.ParDo(WriteToTFRecord(output_prefix, num_shard, convert_fn))
|
||||
| f'Group {ml_use} TF Record files' >> beam.GroupBy(lambda x: x[0])
|
||||
| f'Merge {ml_use} TF Record files'
|
||||
>> beam.Map(merge_tfrecords_func(output_prefix, num_shard))
|
||||
)
|
||||
|
||||
|
||||
def merge_tfrecords_func(output_prefix: str, num_shard: int) -> ...:
|
||||
"""Returns a function to merge sharded worker output into expected shards."""
|
||||
output_prefix = fileutils.force_gcs_fuse_path(output_prefix)
|
||||
|
||||
def merge_tfrecords(worker_output: Tuple[int, Sequence[Tuple[int, str]]]):
|
||||
idx = worker_output[0]
|
||||
files: Sequence[str] = np.unique([x[1] for x in worker_output[1]])
|
||||
output_file = f'{output_prefix}-{idx:05d}-of-{num_shard:05d}'
|
||||
with open(output_file, 'wb') as f:
|
||||
for file in files:
|
||||
logging.info('Merging %s.', file)
|
||||
file = fileutils.force_gcs_fuse_path(file)
|
||||
with open(file, 'rb') as fin:
|
||||
while True:
|
||||
data = fin.read(READ_CHUNK_SIZE)
|
||||
if not data:
|
||||
break
|
||||
f.write(data)
|
||||
os.remove(file)
|
||||
|
||||
return merge_tfrecords
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
r"""Converts COCO labels as yamls for model garden playground (IOD).
|
||||
"""
|
||||
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
import tensorflow as tf
|
||||
import yaml
|
||||
|
||||
from object_detection.utils import label_map_util
|
||||
|
||||
_CONVERT_LABEL_TYPE_COCO_80 = 'coco_80'
|
||||
_CONVERT_LABEL_TYPE_COCO_91 = 'coco_91'
|
||||
|
||||
_CONVERT_LABEL_TYPE = flags.DEFINE_enum(
|
||||
'convert_label_type',
|
||||
None,
|
||||
[
|
||||
_CONVERT_LABEL_TYPE_COCO_80,
|
||||
_CONVERT_LABEL_TYPE_COCO_91,
|
||||
],
|
||||
'Different types of label type conversion.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
_TEMPORARY_PATH = flags.DEFINE_string(
|
||||
'temporary_path',
|
||||
None,
|
||||
'The tempory path.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
_OUTPUT_YAML_FILEPATH = flags.DEFINE_string(
|
||||
'output_yaml_filepath',
|
||||
None,
|
||||
'The output yaml filepath.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
|
||||
def convert_coco_label_map_91(
|
||||
output_yaml_filepath: str,
|
||||
) -> None:
|
||||
"""Converts coco label map 91."""
|
||||
input_proto_filepath = 'https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/mscoco_label_map.pbtxt'
|
||||
local_input_proto_filepath = os.path.join(
|
||||
_TEMPORARY_PATH.value, 'mscoco_label_map.pbtxt'
|
||||
)
|
||||
with open(local_input_proto_filepath, 'w') as writer:
|
||||
contents = (
|
||||
urllib.request.urlopen(input_proto_filepath).read().decode('utf-8')
|
||||
)
|
||||
writer.write(contents)
|
||||
|
||||
label_map = label_map_util.load_labelmap(local_input_proto_filepath)
|
||||
label_map_dict = label_map_util.get_label_map_dict(
|
||||
label_map, use_display_name=True
|
||||
)
|
||||
swapped_label_map_dict = {v: k for k, v in label_map_dict.items()}
|
||||
print(swapped_label_map_dict)
|
||||
|
||||
# Saves new label maps as yamls.
|
||||
with tf.io.gfile.GFile(output_yaml_filepath, 'w') as writer:
|
||||
writer.write(yaml.dump(swapped_label_map_dict))
|
||||
|
||||
|
||||
def convert_coco_label_map_80(
|
||||
output_yaml_filepath: str,
|
||||
) -> None:
|
||||
"""Converts coco label map 80."""
|
||||
# Loads label maps from texts.
|
||||
input_text_filepath = 'https://gist.githubusercontent.com/AruniRC/7b3dadd004da04c80198557db5da4bda/raw/2f10965ace1e36c4a9dca76ead19b744f5eb7e88/ms_coco_classnames.txt'
|
||||
local_input_text_filepath = os.path.join(
|
||||
_TEMPORARY_PATH.value, 'ms_coco_classnames.txt'
|
||||
)
|
||||
with open(local_input_text_filepath, 'w') as writer:
|
||||
contents = (
|
||||
urllib.request.urlopen(input_text_filepath).read().decode('utf-8')
|
||||
)
|
||||
writer.write(contents)
|
||||
with open(local_input_text_filepath, 'r') as file:
|
||||
content = file.read()
|
||||
label_map = yaml.safe_load(content)
|
||||
|
||||
# Removes background in label maps.
|
||||
new_label_map = {}
|
||||
for k, v in label_map.items():
|
||||
if k == 0:
|
||||
continue
|
||||
new_label_map[k - 1] = v
|
||||
print(new_label_map)
|
||||
# Saves new label maps as yamls.
|
||||
with tf.io.gfile.GFile(output_yaml_filepath, 'w') as writer:
|
||||
writer.write(yaml.dump(new_label_map))
|
||||
|
||||
|
||||
def main(_) -> None:
|
||||
if _CONVERT_LABEL_TYPE.value == _CONVERT_LABEL_TYPE_COCO_80:
|
||||
convert_coco_label_map_80(_OUTPUT_YAML_FILEPATH.value)
|
||||
elif _CONVERT_LABEL_TYPE.value == _CONVERT_LABEL_TYPE_COCO_91:
|
||||
convert_coco_label_map_91(
|
||||
_OUTPUT_YAML_FILEPATH.value,
|
||||
)
|
||||
else:
|
||||
print('Not supported convert label type: ', _CONVERT_LABEL_TYPE.value)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
r"""Converts ImageNet label texts as yamls for model garden playground.
|
||||
|
||||
# ImageNet1K will have label maps with background.
|
||||
"""
|
||||
|
||||
import urllib.request
|
||||
from absl import app
|
||||
from absl import flags
|
||||
import tensorflow as tf
|
||||
import yaml
|
||||
|
||||
|
||||
_INPUT_TEXT_FILEPATH = flags.DEFINE_string(
|
||||
'input_text_filepath',
|
||||
None,
|
||||
'The input text filepath.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
_ADD_BACKGROUND_LABEL = flags.DEFINE_boolean(
|
||||
'add_background_label',
|
||||
None,
|
||||
'Whether or not add background labels.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
_ADD_IDS = flags.DEFINE_boolean(
|
||||
'add_ids',
|
||||
None,
|
||||
'Whether or not add ids.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
_OUTPUT_YAML_FILEPATH = flags.DEFINE_string(
|
||||
'output_yaml_filepath',
|
||||
None,
|
||||
'The output yaml filepath.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
|
||||
def convert_imagenet_label_map_from_text_to_yaml(
|
||||
input_text_filepath: str,
|
||||
add_background_label: bool,
|
||||
add_ids: bool,
|
||||
output_yaml_filepath: str,
|
||||
) -> None:
|
||||
"""Converts imagenet label map from text to yamls."""
|
||||
label_map = {}
|
||||
|
||||
# Shifts all keys by 1, and add 0 as 'background'.
|
||||
if add_background_label:
|
||||
label_map = yaml.safe_load(
|
||||
urllib.request.urlopen(input_text_filepath).read()
|
||||
)
|
||||
new_label_map = {}
|
||||
for key, value in label_map.items():
|
||||
new_label_map[key + 1] = value
|
||||
new_label_map[0] = 'background'
|
||||
label_map = new_label_map
|
||||
|
||||
# Adds maps from id to each line.
|
||||
if add_ids:
|
||||
lines = urllib.request.urlopen(input_text_filepath).readlines()
|
||||
current_id = 0
|
||||
for line in lines:
|
||||
label_map[current_id] = line.decode('ascii').strip()
|
||||
print(label_map[current_id])
|
||||
current_id += 1
|
||||
|
||||
# Saves new label maps as yamls.
|
||||
with tf.io.gfile.GFile(output_yaml_filepath, 'w') as writer:
|
||||
writer.write(yaml.dump(label_map))
|
||||
|
||||
|
||||
def main(_) -> None:
|
||||
convert_imagenet_label_map_from_text_to_yaml(
|
||||
_INPUT_TEXT_FILEPATH.value,
|
||||
_ADD_BACKGROUND_LABEL.value,
|
||||
_ADD_IDS.value,
|
||||
_OUTPUT_YAML_FILEPATH.value,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
"""Converts ICN CSV/JSONL files to TFRecord with apache beam."""
|
||||
|
||||
import json
|
||||
from os import path
|
||||
from typing import Any, Dict, Sequence, Union, cast
|
||||
|
||||
from absl import logging
|
||||
import apache_beam as beam
|
||||
import pandas as pd
|
||||
import tensorflow as tf
|
||||
|
||||
from data_converter import common_lib
|
||||
|
||||
|
||||
_COLUMN_NAMES = [
|
||||
common_lib.COLUMN_NAME_ML_USE,
|
||||
common_lib.COLUMN_NAME_GCS_FILE_PATH,
|
||||
common_lib.COLUMN_NAME_LABEL,
|
||||
]
|
||||
_JSON_GCS_URI_KEY = 'imageGcsUri'
|
||||
_JSON_CLASS_ANNOTATION_KEY = 'classificationAnnotation'
|
||||
_JSON_RESOURCE_LABEL_KEY = 'dataItemResourceLabels'
|
||||
_JSON_CLASS_NAME_KEY = 'displayName'
|
||||
_JSON_ML_USE_KEY = 'aiplatform.googleapis.com/ml_use'
|
||||
|
||||
|
||||
def build_tf_example(element: Dict[str, Union[str, int]]) -> tf.train.Example:
|
||||
"""Builds a TF Example from an image uri and label.
|
||||
|
||||
Args:
|
||||
element: A dict with the keys gcs_file_path and label.
|
||||
|
||||
Returns:
|
||||
The created TF Example.
|
||||
"""
|
||||
image_uri = cast(str, element[common_lib.COLUMN_NAME_GCS_FILE_PATH])
|
||||
label = cast(int, element[common_lib.COLUMN_NAME_LABEL])
|
||||
image_bytes, shape = common_lib.encode_image(image_uri, image_format='jpeg')
|
||||
features = tf.train.Features(
|
||||
feature={
|
||||
'image/encoded': common_lib.convert_to_feature(image_bytes),
|
||||
'image/format': common_lib.convert_to_string_feature('jpeg'),
|
||||
'image/height': common_lib.convert_to_feature(shape[0]),
|
||||
'image/width': common_lib.convert_to_feature(shape[1]),
|
||||
'image/class/label': common_lib.convert_to_feature(label),
|
||||
},
|
||||
)
|
||||
return tf.train.Example(features=features)
|
||||
|
||||
|
||||
def _run_convert_pipeline(
|
||||
output_dir: str, df: pd.DataFrame, num_shards: Sequence[int]
|
||||
) -> None:
|
||||
"""Starts a Beam pipeline to write DataFrame as TF Records.
|
||||
|
||||
Args:
|
||||
output_dir: TF Records output directory.
|
||||
df: DataFrame to convert from.
|
||||
num_shards: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
images_list = df.to_dict('records')
|
||||
|
||||
def pipeline(root: beam.Pipeline):
|
||||
common_lib.beam_convert_tfexamples(
|
||||
root,
|
||||
images_list,
|
||||
build_tf_example,
|
||||
output_dir,
|
||||
num_shards,
|
||||
)
|
||||
|
||||
common_lib.run_beam_pipeline(pipeline)
|
||||
|
||||
|
||||
def _convert_df_to_tfrecord(
|
||||
df: pd.DataFrame,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float],
|
||||
num_shard: Sequence[int],
|
||||
) -> None:
|
||||
"""Converts a DataFrame into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
Args:
|
||||
df: DataFrame to convert.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
# Replaces ml_use with common_lib string constants for consistency.
|
||||
common_lib.format_ml_use_column(df)
|
||||
common_lib.insert_missing_ml_use(df)
|
||||
|
||||
# Ignores invalid rows.
|
||||
dropped_row_num = common_lib.drop_invalid_rows(df)
|
||||
if dropped_row_num > 0:
|
||||
logging.warning('Ignored %d invalid rows.', dropped_row_num)
|
||||
|
||||
common_lib.replace_unassigned_ml_use(
|
||||
df[common_lib.COLUMN_NAME_ML_USE], split_ratio
|
||||
)
|
||||
|
||||
# Converts labels to integers as required by training.
|
||||
new_labels, label_map = common_lib.create_label_map(
|
||||
df[common_lib.COLUMN_NAME_LABEL]
|
||||
)
|
||||
df[common_lib.COLUMN_NAME_LABEL] = new_labels
|
||||
label_map_path = path.join(output_dir, common_lib.LABEL_MAP_NAME)
|
||||
logging.info('Writing label map to %s.', label_map_path)
|
||||
common_lib.write_label_map(label_map_path, label_map)
|
||||
|
||||
_run_convert_pipeline(output_dir, df, num_shard)
|
||||
|
||||
|
||||
def convert_csv_to_tfrecord(
|
||||
input_csv: str,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float] = (0.8, 0.1, 0.1),
|
||||
num_shard: Sequence[int] = (10, 10, 10),
|
||||
) -> None:
|
||||
"""Parses input_csv file into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
The csv format is shown in
|
||||
https://cloud.google.com/vertex-ai/docs/image-data/classification/prepare-data#csv.
|
||||
|
||||
If an ml_use column is not provided, one will be created.
|
||||
|
||||
label_map.yaml containing the label map will be placed in output_dir.
|
||||
|
||||
Args:
|
||||
input_csv: Name of the csv file.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
with tf.io.gfile.GFile(input_csv, 'r') as f:
|
||||
df: pd.DataFrame = pd.read_csv(
|
||||
f, header=None, names=_COLUMN_NAMES, on_bad_lines='warn'
|
||||
)
|
||||
|
||||
_convert_df_to_tfrecord(df, output_dir, split_ratio, num_shard)
|
||||
|
||||
|
||||
def convert_jsonl_to_tfrecord(
|
||||
input_jsonl: str,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float] = (0.8, 0.1, 0.1),
|
||||
num_shard: Sequence[int] = (10, 10, 10),
|
||||
) -> None:
|
||||
"""Parses input_jsonl file into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
The JSONL format is shown in
|
||||
https://cloud.google.com/vertex-ai/docs/image-data/classification/prepare-data#json-lines.
|
||||
|
||||
If an ml_use column is not provided, one will be created.
|
||||
|
||||
label_map.yaml containing the label map will be placed in output_dir.
|
||||
|
||||
Args:
|
||||
input_jsonl: Name of the JSONL file.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
df_rows = []
|
||||
with tf.io.gfile.GFile(input_jsonl, 'r') as f:
|
||||
lines = f.read().rstrip().splitlines()
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
try:
|
||||
item: Dict[str, Any] = json.loads(line)
|
||||
|
||||
gcs_uri = item.get(_JSON_GCS_URI_KEY)
|
||||
label = item.get(_JSON_CLASS_ANNOTATION_KEY, {}).get(_JSON_CLASS_NAME_KEY)
|
||||
if not gcs_uri or not label:
|
||||
logging.warning('Invalid JSON at line %d, skipped.', i)
|
||||
continue
|
||||
|
||||
ml_use = item.get(_JSON_RESOURCE_LABEL_KEY, {}).get(
|
||||
_JSON_ML_USE_KEY, common_lib.ML_USE_UNASSIGNED
|
||||
)
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
logging.warning('Invalid JSON at line %d, skipped.', i)
|
||||
continue
|
||||
|
||||
df_rows.append([ml_use, gcs_uri, label])
|
||||
|
||||
df = pd.DataFrame(
|
||||
data=df_rows,
|
||||
columns=[
|
||||
common_lib.COLUMN_NAME_ML_USE,
|
||||
common_lib.COLUMN_NAME_GCS_FILE_PATH,
|
||||
common_lib.COLUMN_NAME_LABEL,
|
||||
],
|
||||
)
|
||||
|
||||
_convert_df_to_tfrecord(df, output_dir, split_ratio, num_shard)
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
"""Converts IOD dataset files to TFRecord with apache beam."""
|
||||
|
||||
import collections
|
||||
import json
|
||||
from os import path
|
||||
from typing import Any, Dict, Sequence
|
||||
|
||||
from absl import logging
|
||||
import apache_beam as beam
|
||||
import pandas as pd
|
||||
import tensorflow as tf
|
||||
|
||||
from data_converter import common_lib
|
||||
from util import constants
|
||||
|
||||
COLUMN_NAME_LABEL_INT = 'label_int'
|
||||
_COLUMN_NAME_XMIN = 'X_MIN'
|
||||
_COLUMN_NAME_YMIN = 'Y_MIN'
|
||||
_COLUMN_NAME_XMAX = 'X_MAX'
|
||||
_COLUMN_NAME_YMAX = 'Y_MAX'
|
||||
COLUMN_NAMES = [
|
||||
common_lib.COLUMN_NAME_ML_USE,
|
||||
common_lib.COLUMN_NAME_GCS_FILE_PATH,
|
||||
common_lib.COLUMN_NAME_LABEL,
|
||||
_COLUMN_NAME_XMIN,
|
||||
_COLUMN_NAME_YMIN,
|
||||
'XMAX_NOT_USED',
|
||||
'YMIN_NOT_USED',
|
||||
_COLUMN_NAME_XMAX,
|
||||
_COLUMN_NAME_YMAX,
|
||||
'XMIN_NOT_USED',
|
||||
'YMAX_NOT_USED',
|
||||
]
|
||||
_BOUNDING_BOX_COLUMNS = [
|
||||
_COLUMN_NAME_XMIN,
|
||||
_COLUMN_NAME_YMIN,
|
||||
_COLUMN_NAME_XMAX,
|
||||
_COLUMN_NAME_YMAX,
|
||||
]
|
||||
_JSON_BBOX_ANNOTATIONS_KEY = 'boundingBoxAnnotations'
|
||||
_JSON_DISPLAY_NAME_KEY = 'displayName'
|
||||
_JSON_X_MIN_KEY = 'xMin'
|
||||
_JSON_X_MAX_KEY = 'xMax'
|
||||
_JSON_Y_MIN_KEY = 'yMin'
|
||||
_JSON_Y_MAX_KEY = 'yMax'
|
||||
|
||||
|
||||
def build_tf_example(image_row: Dict[str, Any]) -> tf.train.Example:
|
||||
"""Builds a TF Example from an image row.
|
||||
|
||||
Args:
|
||||
image_row: A dictionary containing information about the image, such as its
|
||||
GCS uri, labels, and bounding box coordinates.
|
||||
|
||||
Returns:
|
||||
A tf.train.Example containing the encoded image and optionally a
|
||||
bounding box and label.
|
||||
"""
|
||||
image_uri = image_row[common_lib.COLUMN_NAME_GCS_FILE_PATH]
|
||||
image_bytes, shape = common_lib.encode_image(image_uri, image_format='jpeg')
|
||||
feature = {
|
||||
'image/encoded': common_lib.convert_to_feature(image_bytes),
|
||||
'image/format': common_lib.convert_to_string_feature('jpeg'),
|
||||
'image/height': common_lib.convert_to_feature(shape[0]),
|
||||
'image/width': common_lib.convert_to_feature(shape[1]),
|
||||
'image/source_id': common_lib.convert_to_string_feature(image_uri),
|
||||
'image/object/bbox/xmin': common_lib.convert_to_feature(
|
||||
image_row[_COLUMN_NAME_XMIN]
|
||||
),
|
||||
'image/object/bbox/ymin': common_lib.convert_to_feature(
|
||||
image_row[_COLUMN_NAME_YMIN]
|
||||
),
|
||||
'image/object/bbox/xmax': common_lib.convert_to_feature(
|
||||
image_row[_COLUMN_NAME_XMAX]
|
||||
),
|
||||
'image/object/bbox/ymax': common_lib.convert_to_feature(
|
||||
image_row[_COLUMN_NAME_YMAX]
|
||||
),
|
||||
'image/object/class/text': common_lib.convert_to_list_string_feature(
|
||||
image_row[common_lib.COLUMN_NAME_LABEL]
|
||||
),
|
||||
'image/object/class/label': common_lib.convert_to_feature(
|
||||
image_row[COLUMN_NAME_LABEL_INT]
|
||||
),
|
||||
}
|
||||
return tf.train.Example(features=tf.train.Features(feature=feature))
|
||||
|
||||
|
||||
def _run_convert_pipeline(
|
||||
output_dir: str,
|
||||
image_rows: Sequence[Dict[str, Any]],
|
||||
num_shards: Sequence[int],
|
||||
) -> None:
|
||||
"""Starts a Beam pipeline to write DataFrame as TF Records.
|
||||
|
||||
Args:
|
||||
output_dir: TF Records output directory.
|
||||
image_rows: Contains all necessary information to create a TF Example.
|
||||
num_shards: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
|
||||
def pipeline(root: beam.Pipeline):
|
||||
common_lib.beam_convert_tfexamples(
|
||||
root,
|
||||
image_rows,
|
||||
build_tf_example,
|
||||
output_dir,
|
||||
num_shards,
|
||||
)
|
||||
|
||||
common_lib.run_beam_pipeline(pipeline)
|
||||
|
||||
|
||||
def _convert_df_to_tfrecord(
|
||||
df: pd.DataFrame,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float],
|
||||
num_shard: Sequence[int],
|
||||
) -> None:
|
||||
"""Converts a DataFrame into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
Args:
|
||||
df: DataFrame to convert.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
# Replaces ml_use with common_lib string constants for consistency.
|
||||
common_lib.format_ml_use_column(df)
|
||||
common_lib.insert_missing_ml_use(df)
|
||||
|
||||
# Specify bounding box columns to be numeric.
|
||||
df[_BOUNDING_BOX_COLUMNS] = df[_BOUNDING_BOX_COLUMNS].apply(pd.to_numeric)
|
||||
|
||||
# Ignores invalid rows.
|
||||
dropped_row_num = common_lib.drop_invalid_rows(df)
|
||||
dropped_row_num += drop_rows_without_bbox(df)
|
||||
if dropped_row_num > 0:
|
||||
logging.warning('Ignored %d invalid rows.', dropped_row_num)
|
||||
|
||||
# Converts labels to integers as required by training.
|
||||
int_labels, label_map = common_lib.create_label_map(
|
||||
df[common_lib.COLUMN_NAME_LABEL]
|
||||
)
|
||||
df[COLUMN_NAME_LABEL_INT] = int_labels
|
||||
label_map_path = path.join(output_dir, common_lib.LABEL_MAP_NAME)
|
||||
logging.info('Writing label map to %s.', label_map_path)
|
||||
common_lib.write_label_map(label_map_path, label_map)
|
||||
|
||||
image_rows = _condense_bounding_boxes(df.to_dict(orient='records'))
|
||||
ml_uses = [row[common_lib.COLUMN_NAME_ML_USE] for row in image_rows]
|
||||
common_lib.replace_unassigned_ml_use(ml_uses, split_ratio)
|
||||
common_lib.merge_seq_into_dicts(
|
||||
common_lib.COLUMN_NAME_ML_USE, ml_uses, image_rows
|
||||
)
|
||||
|
||||
_run_convert_pipeline(output_dir, image_rows, num_shard)
|
||||
|
||||
|
||||
def _condense_bounding_boxes(
|
||||
image_rows: Sequence[Dict[str, Any]]
|
||||
) -> Sequence[Dict[str, Any]]:
|
||||
"""Gather all the bounding boxes in an image and put them in the same dictionary.
|
||||
|
||||
Args:
|
||||
image_rows: List of dictionaries, each containing information about the
|
||||
image, such as its GCS uri, labels, and bounding box coordinates.
|
||||
|
||||
Returns:
|
||||
List of dictionaries such that each contains all the bounding boxes for a
|
||||
given gcs_file_path.
|
||||
|
||||
Raises:
|
||||
RuntimeError: This is raised when the input data contains images that have
|
||||
annotations in different ml_use classes.
|
||||
"""
|
||||
output = {}
|
||||
for image_row in image_rows:
|
||||
ml_use = image_row[common_lib.COLUMN_NAME_ML_USE]
|
||||
gcs_file_path = image_row[common_lib.COLUMN_NAME_GCS_FILE_PATH]
|
||||
label = image_row[common_lib.COLUMN_NAME_LABEL]
|
||||
xmin = image_row[_COLUMN_NAME_XMIN]
|
||||
ymin = image_row[_COLUMN_NAME_YMIN]
|
||||
xmax = image_row[_COLUMN_NAME_XMAX]
|
||||
ymax = image_row[_COLUMN_NAME_YMAX]
|
||||
label_int = image_row[COLUMN_NAME_LABEL_INT]
|
||||
if gcs_file_path in output:
|
||||
d = output[gcs_file_path]
|
||||
if ml_use != common_lib.ML_USE_UNASSIGNED:
|
||||
if d[common_lib.COLUMN_NAME_ML_USE] == common_lib.ML_USE_UNASSIGNED:
|
||||
d[common_lib.COLUMN_NAME_ML_USE] = ml_use
|
||||
elif ml_use != d[common_lib.COLUMN_NAME_ML_USE]:
|
||||
raise RuntimeError(
|
||||
f'Image {gcs_file_path} can only be placed in one of'
|
||||
f' training/validation/test. It is currently in {ml_use} and'
|
||||
f' {d[common_lib.COLUMN_NAME_ML_USE]}.'
|
||||
)
|
||||
d[common_lib.COLUMN_NAME_LABEL].append(label)
|
||||
d[_COLUMN_NAME_XMIN].append(xmin)
|
||||
d[_COLUMN_NAME_YMIN].append(ymin)
|
||||
d[_COLUMN_NAME_XMAX].append(xmax)
|
||||
d[_COLUMN_NAME_YMAX].append(ymax)
|
||||
d[COLUMN_NAME_LABEL_INT].append(label_int)
|
||||
else:
|
||||
output[gcs_file_path] = {
|
||||
common_lib.COLUMN_NAME_ML_USE: ml_use,
|
||||
common_lib.COLUMN_NAME_GCS_FILE_PATH: gcs_file_path,
|
||||
common_lib.COLUMN_NAME_LABEL: [label],
|
||||
_COLUMN_NAME_XMIN: [xmin],
|
||||
_COLUMN_NAME_YMIN: [ymin],
|
||||
_COLUMN_NAME_XMAX: [xmax],
|
||||
_COLUMN_NAME_YMAX: [ymax],
|
||||
COLUMN_NAME_LABEL_INT: [label_int],
|
||||
}
|
||||
return list(output.values())
|
||||
|
||||
|
||||
def convert_csv_to_tfrecord(
|
||||
input_csv: str,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float] = (0.8, 0.1, 0.1),
|
||||
num_shard: Sequence[int] = (10, 10, 10),
|
||||
) -> None:
|
||||
"""Parses input_csv file into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
The csv format is shown in
|
||||
https://cloud.google.com/vertex-ai/docs/image-data/object-detection/prepare-data#csv.
|
||||
|
||||
If an ml_use column is not provided, one will be created.
|
||||
|
||||
label_map.yaml containing the label map will be placed in output_dir.
|
||||
|
||||
Args:
|
||||
input_csv: Name of the csv file.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the train, validation, and test splits for
|
||||
unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
with tf.io.gfile.GFile(input_csv, 'r') as f:
|
||||
df: pd.DataFrame = pd.read_csv(
|
||||
f, header=None, names=COLUMN_NAMES, on_bad_lines='warn'
|
||||
)
|
||||
|
||||
_convert_df_to_tfrecord(df, output_dir, split_ratio, num_shard)
|
||||
|
||||
|
||||
def drop_rows_without_bbox(df: pd.DataFrame) -> int:
|
||||
"""Drops DataFrame rows without bounding_boxes.
|
||||
|
||||
Args:
|
||||
df: The DataFrame to process in place.
|
||||
|
||||
Returns:
|
||||
The number of rows dropped.
|
||||
"""
|
||||
invalid_rows = df.index[~(df[_BOUNDING_BOX_COLUMNS].notnull().all(axis=1))]
|
||||
dropped_num = len(invalid_rows)
|
||||
if dropped_num > 0:
|
||||
invalid_df = df.loc[invalid_rows].to_dict(orient='records')
|
||||
for entry in invalid_df:
|
||||
logging.warning('Skipping entry due to missing bounding box: %s.', entry)
|
||||
df.drop(invalid_rows, inplace=True)
|
||||
df.reset_index(drop=True, inplace=True)
|
||||
return dropped_num
|
||||
|
||||
|
||||
def convert_coco_json_categories_to_label_map(
|
||||
categories: Sequence[Dict[str, Any]]
|
||||
) -> Dict[int, str]:
|
||||
return {category['id']: category['name'] for category in categories}
|
||||
|
||||
|
||||
def convert_coco_json_to_tfrecord(
|
||||
input_coco_json: str,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float] = (0.8, 0.1, 0.1),
|
||||
num_shard: Sequence[int] = (10, 10, 10),
|
||||
) -> None:
|
||||
"""Parses input_csv file into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
The COCO json format is shown here: https://cocodataset.org/#format-data.
|
||||
|
||||
label_map.yaml containing the label map will be placed in output_dir.
|
||||
|
||||
Args:
|
||||
input_coco_json: Name of coco json file.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the train, validation, and test splits for
|
||||
dataset.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
with tf.io.gfile.GFile(input_coco_json, 'r') as f:
|
||||
coco_json = json.load(f)
|
||||
# Writes label map from coco json categories.
|
||||
label_map = convert_coco_json_categories_to_label_map(
|
||||
coco_json[constants.COCO_JSON_CATEGORIES]
|
||||
)
|
||||
label_map_path = path.join(output_dir, common_lib.LABEL_MAP_NAME)
|
||||
logging.info('Writes label map to %s.', label_map_path)
|
||||
common_lib.write_label_map(label_map_path, label_map)
|
||||
|
||||
img_to_anns = collections.defaultdict(list)
|
||||
imgs = {}
|
||||
if constants.COCO_JSON_ANNOTATIONS in coco_json:
|
||||
for ann in coco_json[constants.COCO_JSON_ANNOTATIONS]:
|
||||
img_to_anns[ann[constants.COCO_JSON_ANNOTATION_IMAGE_ID]].append(ann)
|
||||
|
||||
if constants.COCO_JSON_IMAGES in coco_json:
|
||||
for img in coco_json[constants.COCO_JSON_IMAGES]:
|
||||
imgs[img[constants.COCO_JSON_IMAGE_ID]] = img
|
||||
|
||||
df_rows = []
|
||||
|
||||
for image_id, annotations in img_to_anns.items():
|
||||
img = imgs[image_id]
|
||||
for ann in annotations:
|
||||
xmin, ymin, xmax, ymax = common_lib.reformat_bbox(
|
||||
ann[constants.COCO_ANNOTATION_BBOX],
|
||||
img[constants.COCO_JSON_IMAGE_WIDTH],
|
||||
img[constants.COCO_JSON_IMAGE_HEIGHT],
|
||||
)
|
||||
df_rows.append([
|
||||
common_lib.ML_USE_UNASSIGNED,
|
||||
img[constants.COCO_JSON_IMAGE_COCO_URL],
|
||||
label_map[ann[constants.COCO_JSON_ANNOTATION_CATEGORY_ID]],
|
||||
xmin,
|
||||
ymin,
|
||||
xmax,
|
||||
ymin,
|
||||
xmax,
|
||||
ymax,
|
||||
xmin,
|
||||
ymax,
|
||||
ann[constants.COCO_JSON_ANNOTATION_CATEGORY_ID],
|
||||
])
|
||||
df = pd.DataFrame(
|
||||
data=df_rows,
|
||||
columns=COLUMN_NAMES + [COLUMN_NAME_LABEL_INT],
|
||||
)
|
||||
|
||||
# Replaces ml_use with common_lib string constants for consistency.
|
||||
common_lib.format_ml_use_column(df)
|
||||
common_lib.insert_missing_ml_use(df)
|
||||
|
||||
# Species bounding box columns to be numeric.
|
||||
df[_BOUNDING_BOX_COLUMNS] = df[_BOUNDING_BOX_COLUMNS].apply(pd.to_numeric)
|
||||
|
||||
# Ignores invalid rows.
|
||||
dropped_row_num = common_lib.drop_invalid_rows(df)
|
||||
dropped_row_num += drop_rows_without_bbox(df)
|
||||
if dropped_row_num > 0:
|
||||
logging.warning('Ignored %d invalid rows.', dropped_row_num)
|
||||
|
||||
image_rows = _condense_bounding_boxes(df.to_dict(orient='records'))
|
||||
ml_uses = [row[common_lib.COLUMN_NAME_ML_USE] for row in image_rows]
|
||||
common_lib.replace_unassigned_ml_use(ml_uses, split_ratio)
|
||||
common_lib.merge_seq_into_dicts(
|
||||
common_lib.COLUMN_NAME_ML_USE, ml_uses, image_rows
|
||||
)
|
||||
|
||||
_run_convert_pipeline(output_dir, image_rows, num_shard)
|
||||
|
||||
|
||||
def convert_jsonl_to_tfrecord(
|
||||
input_jsonl: str,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float] = (0.8, 0.1, 0.1),
|
||||
num_shard: Sequence[int] = (10, 10, 10),
|
||||
) -> None:
|
||||
"""Parses input_jsonl file into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
The JSONL format is shown in
|
||||
https://cloud.google.com/vertex-ai/docs/image-data/object-detection/prepare-data#json-lines.
|
||||
|
||||
If an ml_use column is not provided, one will be created.
|
||||
|
||||
label_map.yaml containing the label map will be placed in output_dir.
|
||||
|
||||
Args:
|
||||
input_jsonl: Name of the JSONL file.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
df_rows = []
|
||||
with tf.io.gfile.GFile(input_jsonl, 'r') as f:
|
||||
lines = f.read().rstrip().splitlines()
|
||||
|
||||
for i, line in enumerate(lines, start=1):
|
||||
try:
|
||||
item: Dict[str, Any] = json.loads(line)
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
logging.warning('Invalid JSON at line %d skipped.', i)
|
||||
continue
|
||||
|
||||
gcs_uri = item.get(common_lib.JSON_GCS_URI_KEY)
|
||||
if not gcs_uri:
|
||||
logging.warning(
|
||||
'Invalid JSON at line %d skipped. Missing gcs_uri_key.', i
|
||||
)
|
||||
continue
|
||||
ml_use = item.get(common_lib.JSON_RESOURCE_LABEL_KEY, {}).get(
|
||||
common_lib.JSON_ML_USE_KEY, common_lib.ML_USE_UNASSIGNED
|
||||
)
|
||||
|
||||
for bbox in item.get(_JSON_BBOX_ANNOTATIONS_KEY, []):
|
||||
label = bbox.get(_JSON_DISPLAY_NAME_KEY)
|
||||
xmin = bbox.get(_JSON_X_MIN_KEY)
|
||||
ymin = bbox.get(_JSON_Y_MIN_KEY)
|
||||
xmax = bbox.get(_JSON_X_MAX_KEY)
|
||||
ymax = bbox.get(_JSON_Y_MAX_KEY)
|
||||
|
||||
df_rows.append([ml_use, gcs_uri, label, xmin, ymin, xmax, ymax])
|
||||
|
||||
df = pd.DataFrame(
|
||||
data=df_rows,
|
||||
columns=[
|
||||
common_lib.COLUMN_NAME_ML_USE,
|
||||
common_lib.COLUMN_NAME_GCS_FILE_PATH,
|
||||
common_lib.COLUMN_NAME_LABEL,
|
||||
_COLUMN_NAME_XMIN,
|
||||
_COLUMN_NAME_YMIN,
|
||||
_COLUMN_NAME_XMAX,
|
||||
_COLUMN_NAME_YMAX,
|
||||
],
|
||||
)
|
||||
_convert_df_to_tfrecord(df, output_dir, split_ratio, num_shard)
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
"""Python script to convert different file formats for ISG to tfrecords."""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
|
||||
|
||||
from absl import logging
|
||||
import apache_beam as beam
|
||||
from apache_beam.io import tfrecordio
|
||||
import cv2
|
||||
import numpy as np
|
||||
from pycocotools import coco
|
||||
import tensorflow as tf
|
||||
import yaml
|
||||
|
||||
from data_converter import common_lib
|
||||
from util import constants
|
||||
from util import fileutils
|
||||
|
||||
_IMAGE_FORMAT = 'PNG'
|
||||
|
||||
|
||||
def build_tf_example(
|
||||
image_info: dict[str, Union[str, int]],
|
||||
segmentation_image: List[List[int]],
|
||||
output_shape: Optional[Tuple[int, int]] = None,
|
||||
) -> tf.train.Example:
|
||||
"""Encodes an image and its segmentation mask into a tf.train.Example.
|
||||
|
||||
Args:
|
||||
image_info: A dictionary containing information about the image, such as its
|
||||
file name, height, and width.
|
||||
segmentation_image: 2D image in list of lists having category ids.
|
||||
output_shape: The desired output shape of the image. If None, the original
|
||||
image shape will be used.
|
||||
|
||||
Returns:
|
||||
A tf.train.Example containing the encoded image and segmentation mask.
|
||||
|
||||
Raises:
|
||||
IOError: If image cannot be found in the path.
|
||||
"""
|
||||
file_name = image_info[constants.COCO_JSON_FILE_NAME]
|
||||
height = int(image_info[constants.COCO_JSON_IMAGE_HEIGHT])
|
||||
width = int(image_info[constants.COCO_JSON_IMAGE_WIDTH])
|
||||
|
||||
segmentation_image = np.expand_dims(
|
||||
np.asarray(segmentation_image, dtype=np.int32), axis=-1
|
||||
)
|
||||
_, encoded_seg = cv2.imencode(f'.{_IMAGE_FORMAT.lower()}', segmentation_image)
|
||||
encoded_seg = encoded_seg.tobytes()
|
||||
|
||||
encoded_img, _ = common_lib.encode_image(
|
||||
image_info[constants.COCO_JSON_IMAGE_COCO_URL],
|
||||
output_shape=output_shape,
|
||||
image_format=_IMAGE_FORMAT.lower(),
|
||||
)
|
||||
|
||||
key = hashlib.sha256(encoded_img).hexdigest()
|
||||
|
||||
return tf.train.Example(
|
||||
features=tf.train.Features(
|
||||
feature={
|
||||
'image/height': common_lib.convert_to_feature(height),
|
||||
'image/width': common_lib.convert_to_feature(width),
|
||||
'image/filename': common_lib.convert_to_string_feature(file_name),
|
||||
'image/sha256': common_lib.convert_to_string_feature(key),
|
||||
'image/encoded': common_lib.convert_to_feature(encoded_img),
|
||||
'image/format': common_lib.convert_to_string_feature(
|
||||
_IMAGE_FORMAT
|
||||
),
|
||||
'image/segmentation/class/encoded': common_lib.convert_to_feature(
|
||||
encoded_seg
|
||||
),
|
||||
'image/segmentation/class/format': (
|
||||
common_lib.convert_to_string_feature(_IMAGE_FORMAT)
|
||||
),
|
||||
'image/segmentation/class/height': common_lib.convert_to_feature(
|
||||
height
|
||||
),
|
||||
'image/segmentation/class/width': common_lib.convert_to_feature(
|
||||
width
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AcquireTFExampleDoFn(beam.DoFn):
|
||||
"""Beam DoFn to build TF Examples from a single row of image_info data."""
|
||||
|
||||
# These tags will be used to tag the outputs of this DoFn.
|
||||
output_tag_train = constants.ML_USE_TRAINING
|
||||
output_tag_validation = constants.ML_USE_VALIDATION
|
||||
output_tag_test = constants.ML_USE_TEST
|
||||
|
||||
valid_ml_use_set = set(
|
||||
[output_tag_train, output_tag_validation, output_tag_test]
|
||||
)
|
||||
|
||||
def __init__(self, output_shape: Optional[Tuple[int, int]] = None):
|
||||
self.acquired_examples_counter = beam.metrics.Metrics.counter(
|
||||
self.__class__.__name__, 'Success'
|
||||
)
|
||||
self.failure_counter = beam.metrics.Metrics.counter(
|
||||
self.__class__.__name__, 'Failure'
|
||||
)
|
||||
self.output_shape = output_shape
|
||||
|
||||
def process(
|
||||
self,
|
||||
row: Tuple[str, Dict[str, Union[str, int]], List[List[int]]],
|
||||
) -> Iterator[tf.train.Example]:
|
||||
ml_use, image_info, annotation_info = row
|
||||
if ml_use not in self.valid_ml_use_set:
|
||||
logging.warning('ml_use invalid: %s', ml_use)
|
||||
self.failure_counter.inc()
|
||||
return
|
||||
|
||||
try:
|
||||
tf_example = build_tf_example(
|
||||
image_info, annotation_info, self.output_shape
|
||||
)
|
||||
except IOError as e:
|
||||
logging.warning('Failed to build TF Example: %s', e)
|
||||
self.failure_counter.inc()
|
||||
else:
|
||||
self.acquired_examples_counter.inc()
|
||||
yield beam.pvalue.TaggedOutput(ml_use, tf_example)
|
||||
|
||||
|
||||
def _define_data_conversion_pipeline(
|
||||
root: beam.Pipeline,
|
||||
ml_use_rows: List[str],
|
||||
image_rows: List[Dict[str, Union[str, int]]],
|
||||
segmentation_rows: List[List[List[int]]],
|
||||
output_dir: str,
|
||||
output_shape: Optional[Tuple[int, int]],
|
||||
num_shard_list: List[int],
|
||||
):
|
||||
"""Define a data conversion pipeline.
|
||||
|
||||
Args:
|
||||
root: A Beam pipeline.
|
||||
ml_use_rows: List containing the ml_use.
|
||||
image_rows: List of dictionaries containing information about the image,
|
||||
such as its file name, height, and width.
|
||||
segmentation_rows: List of 2D images of integers representing segmentation
|
||||
masks.
|
||||
output_dir: Directory where the output TFRecords will be written.
|
||||
output_shape: Desired output shape of the image. If None, the original image
|
||||
shape will be used.
|
||||
num_shard_list: Number of shards to write to each output TFRecord.
|
||||
|
||||
Returns:
|
||||
A Beam pipeline.
|
||||
"""
|
||||
train, validation, test = (
|
||||
root
|
||||
| 'Load ml use and image rows to beam'
|
||||
>> beam.Create(zip(ml_use_rows, image_rows, segmentation_rows))
|
||||
| 'Build TF Examples'
|
||||
>> beam.ParDo(AcquireTFExampleDoFn(output_shape)).with_outputs(
|
||||
AcquireTFExampleDoFn.output_tag_train,
|
||||
AcquireTFExampleDoFn.output_tag_validation,
|
||||
AcquireTFExampleDoFn.output_tag_test,
|
||||
)
|
||||
)
|
||||
|
||||
# Save each split to TFRecord.
|
||||
_ = train | 'Save train split to TFRecord' >> tfrecordio.WriteToTFRecord(
|
||||
os.path.join(output_dir, common_lib.TRAIN_TFRECORD_NAME),
|
||||
coder=beam.coders.ProtoCoder(tf.train.Example),
|
||||
num_shards=num_shard_list[0],
|
||||
)
|
||||
_ = (
|
||||
validation
|
||||
| 'Save validation split to TFRecord'
|
||||
>> tfrecordio.WriteToTFRecord(
|
||||
os.path.join(output_dir, common_lib.VALIDATION_TFRECORD_NAME),
|
||||
coder=beam.coders.ProtoCoder(tf.train.Example),
|
||||
num_shards=num_shard_list[1],
|
||||
)
|
||||
)
|
||||
_ = test | 'Save test split to TFRecord' >> tfrecordio.WriteToTFRecord(
|
||||
os.path.join(output_dir, common_lib.TEST_TFRECORD_NAME),
|
||||
coder=beam.coders.ProtoCoder(tf.train.Example),
|
||||
num_shards=num_shard_list[2],
|
||||
)
|
||||
|
||||
|
||||
def _image_info_to_segmentation_image(
|
||||
img: Dict[str, Any],
|
||||
coco_dataset: coco.COCO,
|
||||
label_id_by_category_id: Dict[int, int],
|
||||
) -> List[List[int]]:
|
||||
"""Convert image information to a segmentation image.
|
||||
|
||||
Args:
|
||||
img: The image information.
|
||||
coco_dataset: The COCO dataset.
|
||||
label_id_by_category_id: The mapping from label id used for training to
|
||||
category_id defined in dataset.
|
||||
|
||||
Returns:
|
||||
The segmentation image.
|
||||
|
||||
Raises:
|
||||
ValueError: If the mask size does not match the image or if a pixel has
|
||||
multiple labels.
|
||||
"""
|
||||
seg_img = np.zeros(
|
||||
shape=(
|
||||
img[constants.COCO_JSON_IMAGE_HEIGHT],
|
||||
img[constants.COCO_JSON_IMAGE_WIDTH],
|
||||
),
|
||||
dtype=np.int32,
|
||||
)
|
||||
for ann in coco_dataset.imgToAnns[img[constants.COCO_JSON_IMAGE_ID]]:
|
||||
new_category_id = ann[constants.COCO_JSON_ANNOTATION_CATEGORY_ID]
|
||||
binary_mask = coco_dataset.annToMask(ann)
|
||||
if seg_img.shape != binary_mask.shape:
|
||||
raise ValueError(
|
||||
'Binary mask does not have the same shape as image. image_id:'
|
||||
f' {img["id"]}'
|
||||
)
|
||||
boolean_mask = binary_mask == 1
|
||||
if (seg_img[boolean_mask] != 0).any():
|
||||
raise ValueError(
|
||||
'Error: Some pixels have more than one label in image_id:'
|
||||
f' {img["id"]}.'
|
||||
)
|
||||
seg_img[boolean_mask] = label_id_by_category_id[new_category_id]
|
||||
|
||||
return seg_img.tolist()
|
||||
|
||||
|
||||
def get_input_rows(
|
||||
coco_dataset: coco.COCO,
|
||||
split_ratio: List[float],
|
||||
label_id_by_category_id: Dict[int, int],
|
||||
) -> Tuple[List[str], List[Dict[str, Union[str, int]]], List[List[List[int]]]]:
|
||||
"""Get input rows for training and validation.
|
||||
|
||||
Args:
|
||||
coco_dataset: The COCO dataset.
|
||||
split_ratio: The split ratio for training and validation.
|
||||
label_id_by_category_id: The mapping from label id used for training to
|
||||
category_id defined in dataset.
|
||||
|
||||
Returns:
|
||||
- A list of ml_use strings.
|
||||
- A list of image informations.
|
||||
- A list of segmentation images for the corresponding images.
|
||||
"""
|
||||
image_rows = coco_dataset.dataset[constants.COCO_JSON_IMAGES]
|
||||
|
||||
segmentation_rows = [
|
||||
_image_info_to_segmentation_image(
|
||||
img, coco_dataset, label_id_by_category_id
|
||||
)
|
||||
for img in image_rows
|
||||
]
|
||||
|
||||
ml_use_rows = common_lib.create_ml_use_array_with_split(
|
||||
len(image_rows), split_ratio
|
||||
)
|
||||
return ml_use_rows, image_rows, segmentation_rows
|
||||
|
||||
|
||||
def beam_build_tfrecord_from_coco_json(
|
||||
input_json: str,
|
||||
output_dir: str,
|
||||
split_ratio: List[float],
|
||||
num_shard_list: List[int],
|
||||
output_shape: Optional[Tuple[int, int]] = None,
|
||||
) -> None:
|
||||
"""Builds TFRecord files from COCO dataset.
|
||||
|
||||
The output file names are `_TRAIN_TFRECORD_NAME`, `_VALIDATION_TFRECORD_NAME`,
|
||||
and `_TEST_TFRECORD_NAME`.
|
||||
|
||||
Args:
|
||||
input_json: Path to a COCO JSON or JSONL file.
|
||||
output_dir: Directory to output the TFRecord files.
|
||||
split_ratio: List of how to split entries to train, validation, and test
|
||||
TFRecords.
|
||||
num_shard_list: List of the number of shards for each TFRecord file.
|
||||
output_shape: The desired output shape of the image. If None, the original
|
||||
image shape will be used.
|
||||
"""
|
||||
# `coco` cannot access gcs uri. Use gcsfuse, it is faster.
|
||||
input_json = fileutils.force_gcs_fuse_path(input_json)
|
||||
coco_dataset = coco.COCO(input_json)
|
||||
|
||||
label_map = {}
|
||||
label_id_by_category_id = {}
|
||||
for idx, category in enumerate(
|
||||
coco_dataset.dataset[constants.COCO_JSON_CATEGORIES], start=1
|
||||
):
|
||||
label_map[idx] = category[constants.COCO_JSON_CATEGORY_NAME]
|
||||
label_id_by_category_id[category[constants.COCO_JSON_CATEGORY_ID]] = idx
|
||||
label_map_path = os.path.join(output_dir, common_lib.LABEL_MAP_NAME)
|
||||
logging.info('Writing label map to %s.', label_map_path)
|
||||
common_lib.write_label_map(label_map_path, label_map)
|
||||
|
||||
with tf.io.gfile.GFile(
|
||||
os.path.join(output_dir, 'label_id_by_category_id.yaml'), 'w'
|
||||
) as f:
|
||||
yaml.dump(label_id_by_category_id, f)
|
||||
|
||||
ml_use_rows, image_rows, segmentation_rows = get_input_rows(
|
||||
coco_dataset, split_ratio, label_id_by_category_id
|
||||
)
|
||||
|
||||
def pipeline(root):
|
||||
_define_data_conversion_pipeline(
|
||||
root,
|
||||
ml_use_rows,
|
||||
image_rows,
|
||||
segmentation_rows,
|
||||
output_dir,
|
||||
output_shape,
|
||||
num_shard_list,
|
||||
)
|
||||
|
||||
logging.info('Beginning beam pipeline to acquire tfrecords.')
|
||||
common_lib.run_beam_pipeline(pipeline)
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
r"""Python script to convert user input data to training docker format.
|
||||
|
||||
|
||||
Note: the training format is designed to be tfrecord as in the design doc.
|
||||
If there are training efficiency issues for pytorch algorithms, we will also
|
||||
support pytorch formats as well.
|
||||
"""
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
from absl import logging
|
||||
from data_converter import common_lib
|
||||
from data_converter import data_converter_icn_lib
|
||||
from data_converter import data_converter_iod_lib
|
||||
from data_converter import data_converter_isg_lib
|
||||
from data_converter import data_converter_vcn_lib
|
||||
from util import constants
|
||||
|
||||
|
||||
_INPUT_FILE_PATH = flags.DEFINE_string(
|
||||
'input_file_path',
|
||||
None,
|
||||
'Input file path.',
|
||||
required=True,
|
||||
)
|
||||
_INPUT_FILE_TYPE = flags.DEFINE_enum(
|
||||
'input_file_type',
|
||||
None,
|
||||
[
|
||||
constants.INPUT_FILE_TYPE_CSV,
|
||||
constants.INPUT_FILE_TYPE_JSONL,
|
||||
constants.INPUT_FILE_TYPE_COCO_JSON,
|
||||
],
|
||||
'Input file type.',
|
||||
required=True,
|
||||
)
|
||||
_OBJECTIVE = flags.DEFINE_enum(
|
||||
'objective',
|
||||
None,
|
||||
[
|
||||
constants.OBJECTIVE_IMAGE_CLASSIFICATION,
|
||||
constants.OBJECTIVE_IMAGE_OBJECT_DETECTION,
|
||||
constants.OBJECTIVE_IMAGE_SEGMENTATION,
|
||||
constants.OBJECTIVE_VIDEO_CLASSIFICATION,
|
||||
],
|
||||
'The objective of this training job.',
|
||||
required=True,
|
||||
)
|
||||
_OUTPUT_DIR = flags.DEFINE_string(
|
||||
'output_dir',
|
||||
None,
|
||||
'The output directory for converted data and label map files.',
|
||||
required=True,
|
||||
)
|
||||
_SPLIT_RATIO = flags.DEFINE_list(
|
||||
'split_ratio',
|
||||
'0.8,0.1,0.1',
|
||||
'Proportion of data to split into train/validation/test.',
|
||||
)
|
||||
_NUM_SHARD = flags.DEFINE_list(
|
||||
'num_shard', '10,10,10', 'The number of shards for train/validation/test.'
|
||||
)
|
||||
_OUTPUT_FPS = flags.DEFINE_integer(
|
||||
'output_fps', 5, 'For videos only. The output frames rate per second.'
|
||||
)
|
||||
|
||||
|
||||
def main(_) -> None:
|
||||
logging.info(
|
||||
(
|
||||
'Start data converter on: %s (type: %s) with split: %s for %s'
|
||||
' (shard=%s), and output to %s.'
|
||||
),
|
||||
_INPUT_FILE_PATH.value,
|
||||
_INPUT_FILE_TYPE.value,
|
||||
_SPLIT_RATIO.value,
|
||||
_OBJECTIVE.value,
|
||||
_NUM_SHARD.value,
|
||||
_OUTPUT_DIR.value,
|
||||
)
|
||||
split_ratio = list(map(float, _SPLIT_RATIO.value))
|
||||
num_shard = list(map(int, _NUM_SHARD.value))
|
||||
common_lib.check_split_ratio(split_ratio)
|
||||
common_lib.check_num_shard(num_shard)
|
||||
if (
|
||||
_OBJECTIVE.value == constants.OBJECTIVE_IMAGE_OBJECT_DETECTION
|
||||
and _INPUT_FILE_TYPE.value == constants.INPUT_FILE_TYPE_CSV
|
||||
):
|
||||
data_converter_iod_lib.convert_csv_to_tfrecord(
|
||||
_INPUT_FILE_PATH.value,
|
||||
_OUTPUT_DIR.value,
|
||||
split_ratio,
|
||||
num_shard,
|
||||
)
|
||||
elif (
|
||||
_OBJECTIVE.value == constants.OBJECTIVE_IMAGE_OBJECT_DETECTION
|
||||
and _INPUT_FILE_TYPE.value == constants.INPUT_FILE_TYPE_JSONL
|
||||
):
|
||||
data_converter_iod_lib.convert_jsonl_to_tfrecord(
|
||||
_INPUT_FILE_PATH.value, _OUTPUT_DIR.value, split_ratio, num_shard
|
||||
)
|
||||
elif (
|
||||
_OBJECTIVE.value == constants.OBJECTIVE_IMAGE_OBJECT_DETECTION
|
||||
and _INPUT_FILE_TYPE.value == constants.INPUT_FILE_TYPE_COCO_JSON
|
||||
):
|
||||
data_converter_iod_lib.convert_coco_json_to_tfrecord(
|
||||
_INPUT_FILE_PATH.value,
|
||||
_OUTPUT_DIR.value,
|
||||
split_ratio,
|
||||
num_shard,
|
||||
)
|
||||
elif _OBJECTIVE.value == constants.OBJECTIVE_IMAGE_SEGMENTATION:
|
||||
data_converter_isg_lib.beam_build_tfrecord_from_coco_json(
|
||||
_INPUT_FILE_PATH.value,
|
||||
_OUTPUT_DIR.value,
|
||||
split_ratio,
|
||||
num_shard,
|
||||
)
|
||||
elif (
|
||||
_OBJECTIVE.value == constants.OBJECTIVE_IMAGE_CLASSIFICATION
|
||||
and _INPUT_FILE_TYPE.value == constants.INPUT_FILE_TYPE_CSV
|
||||
):
|
||||
data_converter_icn_lib.convert_csv_to_tfrecord(
|
||||
_INPUT_FILE_PATH.value,
|
||||
_OUTPUT_DIR.value,
|
||||
split_ratio,
|
||||
num_shard,
|
||||
)
|
||||
elif (
|
||||
_OBJECTIVE.value == constants.OBJECTIVE_IMAGE_CLASSIFICATION
|
||||
and _INPUT_FILE_TYPE.value == constants.INPUT_FILE_TYPE_JSONL
|
||||
):
|
||||
data_converter_icn_lib.convert_jsonl_to_tfrecord(
|
||||
_INPUT_FILE_PATH.value, _OUTPUT_DIR.value, split_ratio, num_shard
|
||||
)
|
||||
elif (
|
||||
_OBJECTIVE.value == constants.OBJECTIVE_VIDEO_CLASSIFICATION
|
||||
and _INPUT_FILE_TYPE.value == constants.INPUT_FILE_TYPE_CSV
|
||||
):
|
||||
data_converter_vcn_lib.convert_csv_to_tfrecord(
|
||||
_INPUT_FILE_PATH.value,
|
||||
_OUTPUT_DIR.value,
|
||||
_OUTPUT_FPS.value,
|
||||
split_ratio,
|
||||
num_shard,
|
||||
)
|
||||
elif (
|
||||
_OBJECTIVE.value == constants.OBJECTIVE_VIDEO_CLASSIFICATION
|
||||
and _INPUT_FILE_TYPE.value == constants.INPUT_FILE_TYPE_JSONL
|
||||
):
|
||||
data_converter_vcn_lib.convert_jsonl_to_tfrecord(
|
||||
_INPUT_FILE_PATH.value,
|
||||
_OUTPUT_DIR.value,
|
||||
_OUTPUT_FPS.value,
|
||||
split_ratio,
|
||||
num_shard,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'File format {_INPUT_FILE_TYPE.value} is not supported for'
|
||||
f' {_OBJECTIVE.value}.'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
"""Converts VCN CSV/JSONL files to TFRecord with apache beam."""
|
||||
|
||||
import json
|
||||
from os import path
|
||||
from typing import Any, Dict, Iterator, Sequence, Union, cast
|
||||
|
||||
from absl import logging
|
||||
import apache_beam as beam
|
||||
from apache_beam.io import tfrecordio
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import tensorflow as tf
|
||||
|
||||
from data_converter import common_lib
|
||||
from util import constants
|
||||
|
||||
|
||||
_COLUMN_NAMES = [
|
||||
common_lib.COLUMN_NAME_ML_USE,
|
||||
common_lib.COLUMN_NAME_GCS_FILE_PATH,
|
||||
common_lib.COLUMN_NAME_LABEL,
|
||||
common_lib.COLUMN_NAME_START_SEC,
|
||||
common_lib.COLUMN_NAME_END_SEC,
|
||||
]
|
||||
_JSON_GCS_URI_KEY = 'videoGcsUri'
|
||||
_JSON_CLASS_ANNOTATION_KEY = 'timeSegmentAnnotations'
|
||||
_JSON_CLASS_NAME_KEY = 'displayName'
|
||||
_JSON_START_TIME_KEY = 'startTime'
|
||||
_JSON_END_TIME_KEY = 'endTime'
|
||||
_JSON_RESOURCE_LABEL_KEY = 'dataItemResourceLabels'
|
||||
_JSON_ML_USE_KEY = 'aiplatform.googleapis.com/ml_use'
|
||||
|
||||
|
||||
def build_tf_example(
|
||||
video_uri: str,
|
||||
label: int,
|
||||
start_sec: float,
|
||||
end_sec: float,
|
||||
output_fps: int,
|
||||
) -> tf.train.SequenceExample:
|
||||
"""Builds a TF Example from a video clip.
|
||||
|
||||
Args:
|
||||
video_uri: GCS URI to the video file.
|
||||
label: Class label as an integer.
|
||||
start_sec: Start timestamp of the video clip in seconds.
|
||||
end_sec: End timestamp of the video clip in seconds.
|
||||
output_fps: The output frame rate per second.
|
||||
|
||||
Returns:
|
||||
The created TF Example.
|
||||
"""
|
||||
frame_bytes = common_lib.encode_video(
|
||||
video_uri, start_sec, end_sec, output_fps, image_format='jpg'
|
||||
)
|
||||
seq_example = tf.train.SequenceExample()
|
||||
seq_example.context.feature['clip/label/index'].int64_list.value[:] = [label]
|
||||
for frame in frame_bytes:
|
||||
seq_example.feature_lists.feature_list.get_or_create(
|
||||
'image/encoded'
|
||||
).feature.add().bytes_list.value[:] = [frame]
|
||||
|
||||
return seq_example
|
||||
|
||||
|
||||
class AcquireTFExampleDoFn(beam.DoFn):
|
||||
"""Beam DoFn to build TF Examples from a DataFrame row dict for VCN."""
|
||||
|
||||
def __init__(self, output_fps: int):
|
||||
self._success_counter = beam.metrics.Metrics.counter(
|
||||
self.__class__.__name__, 'Success'
|
||||
)
|
||||
self._failure_counter = beam.metrics.Metrics.counter(
|
||||
self.__class__.__name__, 'Failure'
|
||||
)
|
||||
self._output_fps = output_fps
|
||||
|
||||
def process(
|
||||
self, element: Dict[str, Union[float, int, str]]
|
||||
) -> Iterator[tf.train.SequenceExample]:
|
||||
ml_use: str = cast(str, element[common_lib.COLUMN_NAME_ML_USE])
|
||||
video_uri: str = cast(str, element[common_lib.COLUMN_NAME_GCS_FILE_PATH])
|
||||
|
||||
try:
|
||||
label: int = int(element[common_lib.COLUMN_NAME_LABEL])
|
||||
start_sec: float = float(element[common_lib.COLUMN_NAME_START_SEC])
|
||||
end_sec: float = float(element[common_lib.COLUMN_NAME_END_SEC])
|
||||
|
||||
tf_example = build_tf_example(
|
||||
video_uri,
|
||||
label,
|
||||
start_sec,
|
||||
end_sec,
|
||||
self._output_fps,
|
||||
)
|
||||
self._success_counter.inc()
|
||||
yield beam.pvalue.TaggedOutput(ml_use, tf_example)
|
||||
except (ValueError, IOError) as err:
|
||||
logging.error('Failed to process %s', video_uri)
|
||||
logging.exception(err)
|
||||
self._failure_counter.inc()
|
||||
|
||||
|
||||
def _run_convert_pipeline(
|
||||
output_dir: str,
|
||||
df: pd.DataFrame,
|
||||
num_shards: Sequence[int],
|
||||
output_fps: int,
|
||||
) -> None:
|
||||
"""Starts a Beam pipeline to write DataFrame as TF Records.
|
||||
|
||||
Args:
|
||||
output_dir: TF Records output directory.
|
||||
df: DataFrame to convert from.
|
||||
num_shards: Number of shards for train/validation/test TFRecord files.
|
||||
output_fps: The output frame rate per second.
|
||||
"""
|
||||
clip_list = df.to_dict('records')
|
||||
|
||||
def pipeline(root):
|
||||
train, val, test = (
|
||||
root
|
||||
| 'Create PCollection' >> beam.Create(clip_list)
|
||||
| 'Convert to TF Example'
|
||||
>> beam.ParDo(AcquireTFExampleDoFn(output_fps)).with_outputs(
|
||||
constants.ML_USE_TRAINING,
|
||||
constants.ML_USE_VALIDATION,
|
||||
constants.ML_USE_TEST,
|
||||
)
|
||||
)
|
||||
_ = train | 'Save train TF Record' >> tfrecordio.WriteToTFRecord(
|
||||
path.join(output_dir, common_lib.TRAIN_TFRECORD_NAME),
|
||||
coder=beam.coders.ProtoCoder(tf.train.Example),
|
||||
num_shards=num_shards[0],
|
||||
)
|
||||
_ = val | 'Save val TF Record' >> tfrecordio.WriteToTFRecord(
|
||||
path.join(output_dir, common_lib.VALIDATION_TFRECORD_NAME),
|
||||
coder=beam.coders.ProtoCoder(tf.train.Example),
|
||||
num_shards=num_shards[1],
|
||||
)
|
||||
_ = test | 'Save test TF Record' >> tfrecordio.WriteToTFRecord(
|
||||
path.join(output_dir, common_lib.TEST_TFRECORD_NAME),
|
||||
coder=beam.coders.ProtoCoder(tf.train.Example),
|
||||
num_shards=num_shards[2],
|
||||
)
|
||||
|
||||
common_lib.run_beam_pipeline(pipeline)
|
||||
|
||||
|
||||
def _convert_df_to_tfrecord(
|
||||
df: pd.DataFrame,
|
||||
output_dir: str,
|
||||
split_ratio: Sequence[float],
|
||||
num_shard: Sequence[int],
|
||||
output_fps: int,
|
||||
) -> None:
|
||||
"""Converts a DataFrame into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
Args:
|
||||
df: DataFrame to convert.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
output_fps: The output frame rate per second.
|
||||
"""
|
||||
# Replaces ml_use with common_lib string constants for consistency.
|
||||
common_lib.format_ml_use_column(df)
|
||||
common_lib.insert_missing_ml_use(df)
|
||||
|
||||
# Ignores invalid rows.
|
||||
dropped_row_num = common_lib.drop_invalid_rows(df)
|
||||
if dropped_row_num > 0:
|
||||
logging.warning('Ignored %d invalid rows.', dropped_row_num)
|
||||
|
||||
common_lib.replace_unassigned_ml_use(
|
||||
df[common_lib.COLUMN_NAME_ML_USE], split_ratio
|
||||
)
|
||||
|
||||
# Converts labels to integers as required by training.
|
||||
new_labels, label_map = common_lib.create_label_map(
|
||||
df[common_lib.COLUMN_NAME_LABEL]
|
||||
)
|
||||
df[common_lib.COLUMN_NAME_LABEL] = new_labels
|
||||
label_map_path = path.join(output_dir, common_lib.LABEL_MAP_NAME)
|
||||
logging.info('Writing label map to %s.', label_map_path)
|
||||
common_lib.write_label_map(label_map_path, label_map)
|
||||
|
||||
# Missing start / end times are treated as 0, inf, respectively.
|
||||
df[common_lib.COLUMN_NAME_START_SEC].fillna(0, inplace=True)
|
||||
df[common_lib.COLUMN_NAME_END_SEC].fillna(np.inf, inplace=True)
|
||||
|
||||
_run_convert_pipeline(output_dir, df, num_shard, output_fps)
|
||||
|
||||
|
||||
def convert_csv_to_tfrecord(
|
||||
input_csv: str,
|
||||
output_dir: str,
|
||||
output_fps: int,
|
||||
split_ratio: Sequence[float] = (0.8, 0.1, 0.1),
|
||||
num_shard: Sequence[int] = (10, 10, 10),
|
||||
) -> None:
|
||||
"""Parses input_csv file into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
The csv format is shown in
|
||||
https://cloud.google.com/vertex-ai/docs/video-data/classification/prepare-data#csv
|
||||
|
||||
If an ml_use column is not provided, one will be created.
|
||||
|
||||
label_map.yaml containing the label map will be placed in output_dir.
|
||||
|
||||
Args:
|
||||
input_csv: Name of the csv file.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
output_fps: The output frame rate per second.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
with tf.io.gfile.GFile(input_csv, 'r') as f:
|
||||
df: pd.DataFrame = pd.read_csv(
|
||||
f, header=None, names=_COLUMN_NAMES, on_bad_lines='warn'
|
||||
)
|
||||
|
||||
_convert_df_to_tfrecord(df, output_dir, split_ratio, num_shard, output_fps)
|
||||
|
||||
|
||||
def convert_jsonl_to_tfrecord(
|
||||
input_jsonl: str,
|
||||
output_dir: str,
|
||||
output_fps: int,
|
||||
split_ratio: Sequence[float] = (0.8, 0.1, 0.1),
|
||||
num_shard: Sequence[int] = (10, 10, 10),
|
||||
) -> None:
|
||||
"""Parses input_jsonl file into three separate tfrecords for training, validation, and testing into output_dir.
|
||||
|
||||
The JSONL format is shown in
|
||||
https://cloud.google.com/vertex-ai/docs/video-data/classification/prepare-data#jsonl.
|
||||
|
||||
If an ml_use column is not provided, one will be created.
|
||||
|
||||
label_map.yaml containing the label map will be placed in output_dir.
|
||||
|
||||
Args:
|
||||
input_jsonl: Name of the JSONL file.
|
||||
output_dir: The directory to save TFRecords and label_map.yaml.
|
||||
output_fps: The output frame rate per second.
|
||||
split_ratio: List specifying the training, validation, and testing splits
|
||||
for unassigned TFRecords.
|
||||
num_shard: Number of shards for train/validation/test TFRecord files.
|
||||
"""
|
||||
df_rows = []
|
||||
with tf.io.gfile.GFile(input_jsonl, 'r') as f:
|
||||
lines = f.read().rstrip().splitlines()
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
try:
|
||||
item: Dict[str, Any] = json.loads(line)
|
||||
|
||||
gcs_uri = item.get(_JSON_GCS_URI_KEY)
|
||||
if not gcs_uri:
|
||||
logging.warning('Invalid JSON at line %d, skipped.', i)
|
||||
continue
|
||||
|
||||
annotations = item.get(_JSON_CLASS_ANNOTATION_KEY, [])
|
||||
ml_use = item.get(_JSON_RESOURCE_LABEL_KEY, {}).get(
|
||||
_JSON_ML_USE_KEY, common_lib.ML_USE_UNASSIGNED
|
||||
)
|
||||
|
||||
for j, annotation in enumerate(annotations):
|
||||
label = annotation.get(_JSON_CLASS_NAME_KEY)
|
||||
if not label:
|
||||
logging.warning('Invalid annotation #%d at line %d, skipped.', j, i)
|
||||
continue
|
||||
# The example in external documentation uses strings like "1.0s", so we
|
||||
# need to remove the "s" suffix.
|
||||
start_time = annotation.get(_JSON_START_TIME_KEY, '0').removesuffix('s')
|
||||
end_time = annotation.get(_JSON_END_TIME_KEY, 'inf').removesuffix('s')
|
||||
df_rows.append([ml_use, gcs_uri, label, start_time, end_time])
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
logging.warning('Invalid JSON at line %d, skipped.', i)
|
||||
continue
|
||||
|
||||
df = pd.DataFrame(
|
||||
data=df_rows,
|
||||
columns=_COLUMN_NAMES,
|
||||
)
|
||||
|
||||
_convert_df_to_tfrecord(df, output_dir, split_ratio, num_shard, output_fps)
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
FROM python:3.9
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install basic libs.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
cmake \
|
||||
curl \
|
||||
wget \
|
||||
sudo \
|
||||
gnupg \
|
||||
python3-opencv \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
build-essential \
|
||||
git \
|
||||
vim \
|
||||
screen \
|
||||
libportaudio2 \
|
||||
libusb-1.0-0-dev \
|
||||
openjdk-17-jre
|
||||
|
||||
# Add gcsfuse distribution URL as a package source and import its public key.
|
||||
RUN echo "deb https://packages.cloud.google.com/apt gcsfuse-`lsb_release -c -s` main" | sudo tee /etc/apt/sources.list.d/gcsfuse.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
|
||||
|
||||
# Install gcsfuse.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gcsfuse
|
||||
|
||||
# Install google cloud SDK.
|
||||
RUN wget -q https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-359.0.0-linux-x86_64.tar.gz
|
||||
RUN tar xzf google-cloud-sdk-359.0.0-linux-x86_64.tar.gz
|
||||
RUN ./google-cloud-sdk/install.sh -q
|
||||
# Make sure gsutil will use the default service account.
|
||||
RUN echo '[GoogleCompute]\nservice_account = default' > /etc/boto.cfg
|
||||
|
||||
|
||||
# Install required libs.
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install pyyaml==5.4.1
|
||||
RUN pip install pycocotools==2.0.6
|
||||
RUN pip install opencv-python-headless==4.7.0.72
|
||||
RUN pip install numpy==1.24.2
|
||||
RUN pip install pandas==1.5.3
|
||||
RUN pip install Pillow==9.4.0
|
||||
RUN pip install apache-beam[gcp]==2.45.0
|
||||
RUN pip install object-detection==0.0.3
|
||||
RUN pip install google-cloud-storage==1.42.3
|
||||
RUN pip install gcsfs==2021.10.1
|
||||
RUN pip install pylint==2.17.2
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
FROM gcr.io/automl-migration-test/automl-vision-data-converter-base:latest
|
||||
|
||||
# Copy license.
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
COPY model_oss/data_converter /automl_vision/data_converter
|
||||
COPY model_oss/util /automl_vision/util
|
||||
|
||||
WORKDIR /automl_vision
|
||||
ENV PYTHONPATH "${PYTHONPATH}:/automl_vision"
|
||||
|
||||
# Run pylint to validate code.
|
||||
COPY .pylintrc /automl_vision/.pylintrc
|
||||
RUN find . -type f -name "*.py" | xargs pylint --rcfile=./.pylintrc --errors-only
|
||||
|
||||
ENTRYPOINT ["python3","data_converter/data_converter_main.py"]
|
||||
|
||||
CMD ["--input_file_path=YOUR_INPUT_FILE",\
|
||||
"--input_file_type=csv",\
|
||||
"--objective=iod",\
|
||||
"--output_dir=YOUR_OUTPUT_DIR",\
|
||||
"--num_shard=10,10,10",\
|
||||
"--split_ratio=0.8,0.1,0.1"]
|
||||
@@ -4,9 +4,10 @@
|
||||
# pylint: disable=logging-fstring-interpolation
|
||||
|
||||
import base64
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, List, Tuple
|
||||
from typing import Any, List, Sequence, Tuple
|
||||
|
||||
from diffusers import ControlNetModel
|
||||
from diffusers import DiffusionPipeline
|
||||
@@ -20,6 +21,7 @@ from diffusers import StableDiffusionPipeline
|
||||
from diffusers import StableDiffusionUpscalePipeline
|
||||
from diffusers import TextToVideoZeroPipeline
|
||||
from diffusers import UniPCMultistepScheduler
|
||||
import imageio
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
@@ -43,6 +45,13 @@ TEXT_TO_VIDEO_ZERO_SHOT = "text-to-video-zero-shot"
|
||||
TEXT_TO_VIDEO = "text-to-video"
|
||||
|
||||
|
||||
def frames_to_video_bytes(frames: Sequence[np.ndarray], fps: int) -> bytes:
|
||||
images = [Image.fromarray(array) for array in frames]
|
||||
io_obj = io.BytesIO()
|
||||
imageio.mimsave(io_obj, images, format=".mp4", fps=fps)
|
||||
return io_obj.getvalue()
|
||||
|
||||
|
||||
class DiffusersHandler(BaseHandler):
|
||||
"""Custom handler for TIMM models."""
|
||||
|
||||
@@ -214,7 +223,7 @@ class DiffusersHandler(BaseHandler):
|
||||
numpy_arrays = self.pipeline(prompt=prompt).images
|
||||
numpy_arrays = [(i * 255).astype("uint8") for i in numpy_arrays]
|
||||
videos.append(
|
||||
video_format_converter.frames_to_video_bytes(numpy_arrays, fps=4)
|
||||
frames_to_video_bytes(numpy_arrays, fps=4)
|
||||
)
|
||||
return videos
|
||||
elif self.task == TEXT_TO_VIDEO:
|
||||
@@ -224,7 +233,7 @@ class DiffusersHandler(BaseHandler):
|
||||
# Therefore we need to split the output into different videos.
|
||||
predicted_images = np.array_split(predicted_images, len(prompts), axis=2)
|
||||
videos = [
|
||||
video_format_converter.frames_to_video_bytes(images, fps=8)
|
||||
frames_to_video_bytes(images, fps=8)
|
||||
for images in predicted_images
|
||||
]
|
||||
return videos
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
# Dockerfile for basic serving dockers with Keras.
|
||||
#
|
||||
# To build:
|
||||
# docker build -f model_oss/keras/dockerfile/serve.Dockerfile . -t ${YOUR_IMAGE_TAG}
|
||||
#
|
||||
# To push to gcr:
|
||||
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
|
||||
FROM tensorflow/tensorflow:2.12.0-gpu
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# This is added to fix docker build error related to Nvidia key update.
|
||||
RUN rm -f /etc/apt/sources.list.d/cuda.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
|
||||
# Install basic libs.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
cmake \
|
||||
curl \
|
||||
wget \
|
||||
sudo \
|
||||
gnupg \
|
||||
libsm6 \
|
||||
libxext6 \
|
||||
libxrender-dev \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
build-essential \
|
||||
git \
|
||||
vim \
|
||||
screen \
|
||||
libtcmalloc-minimal4
|
||||
|
||||
|
||||
# Install google cloud SDK.
|
||||
RUN wget -q https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-359.0.0-linux-x86_64.tar.gz
|
||||
RUN tar xzf google-cloud-sdk-359.0.0-linux-x86_64.tar.gz
|
||||
RUN ./google-cloud-sdk/install.sh -q
|
||||
# Make sure gsutil will use the default service account.
|
||||
RUN echo '[GoogleCompute]\nservice_account = default' > /etc/boto.cfg
|
||||
|
||||
|
||||
# Install required libs.
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install cloud-tpu-client==0.10
|
||||
RUN pip install pyyaml==5.4.1
|
||||
RUN pip install fsspec==2021.10.1
|
||||
RUN pip install gcsfs==2021.10.1
|
||||
RUN pip install tensorflow-text==2.11.0
|
||||
RUN pip install pyglove==0.1.0
|
||||
RUN pip install cloudml-hypertune==0.1.0.dev6
|
||||
RUN pip install pylint==2.17.2
|
||||
RUN pip install keras-cv==0.4.0
|
||||
RUN pip install tensorflow-datasets==4.8.3
|
||||
RUN pip install protobuf==3.20.3
|
||||
RUN pip install Pillow==9.5.0
|
||||
RUN pip install flask==2.3.2
|
||||
RUN pip install waitress==2.1.2
|
||||
|
||||
# Installs Reduction Server NCCL plugin.
|
||||
RUN echo "deb https://packages.cloud.google.com/apt google-fast-socket main" | tee /etc/apt/sources.list.d/google-fast-socket.list \
|
||||
&& curl -s -L https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - \
|
||||
&& apt update && apt install -y google-reduction-server
|
||||
|
||||
# Downloading gcloud package
|
||||
RUN curl https://dl.google.com/dl/cloudsdk/release/google-cloud-sdk.tar.gz > /tmp/google-cloud-sdk.tar.gz
|
||||
|
||||
# Installing the package
|
||||
RUN mkdir -p /usr/local/gcloud \
|
||||
&& tar -C /usr/local/gcloud -xvf /tmp/google-cloud-sdk.tar.gz \
|
||||
&& /usr/local/gcloud/google-cloud-sdk/install.sh
|
||||
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
# Adding the package path to local
|
||||
ENV PATH $PATH:/usr/local/gcloud/google-cloud-sdk/bin
|
||||
|
||||
|
||||
ENV PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp
|
||||
|
||||
# Lower the memory fragmentation, and speed up the training.
|
||||
# https://github.com/tensorflow/tensorflow/issues/44176#issuecomment-783768033
|
||||
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4
|
||||
|
||||
# Enable userspace DNS cache
|
||||
ENV GCS_RESOLVE_REFRESH_SECS=60
|
||||
ENV GCS_REQUEST_CONNECTION_TIMEOUT_SECS=300
|
||||
ENV GCS_METADATA_REQUEST_TIMEOUT_SECS=300
|
||||
ENV GCS_READ_REQUEST_TIMEOUT_SECS=300
|
||||
ENV GCS_WRITE_REQUEST_TIMEOUT_SECS=600
|
||||
# Each opened GCS file takes GCS_READ_CACHE_BLOCK_SIZE_MB of RAM, reduce the
|
||||
# value from the default 64MB to 8MB to decrease memory footprint.
|
||||
ENV GCS_READ_CACHE_BLOCK_SIZE_MB=8
|
||||
|
||||
EXPOSE 8501
|
||||
|
||||
WORKDIR /usr/local/lib/python3.8/dist-packages/official/vision
|
||||
|
||||
COPY model_oss/keras /automl_vision/keras
|
||||
COPY model_oss/util /automl_vision/util
|
||||
|
||||
WORKDIR /automl_vision
|
||||
|
||||
ENV PYTHONPATH "${PYTHONPATH}:/automl_vision/util"
|
||||
ENV MODEL_PATH ""
|
||||
ENV IMAGE_WIDTH "512"
|
||||
ENV IMAGE_HEIGHT "512"
|
||||
|
||||
COPY model_oss/keras/serve.py ./app.py
|
||||
|
||||
# Run pylint to validate code.
|
||||
COPY .pylintrc /automl_vision/.pylintrc
|
||||
RUN find . -type f -name "*.py" | xargs pylint --rcfile=./.pylintrc --errors-only
|
||||
|
||||
ENTRYPOINT ["flask","run"]
|
||||
CMD ["--host=0.0.0.0", "--port=8501"]
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
# Dockerfile for basic training dockers with Keras.
|
||||
#
|
||||
# To build:
|
||||
# docker build -f model_oss/keras/dockerfile/train.Dockerfile . -t ${YOUR_IMAGE_TAG}
|
||||
#
|
||||
# To push to gcr:
|
||||
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
|
||||
FROM tensorflow/tensorflow:2.12.0-gpu
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# This is added to fix docker build error related to Nvidia key update.
|
||||
RUN rm -f /etc/apt/sources.list.d/cuda.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
|
||||
# Install basic libs.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
cmake \
|
||||
curl \
|
||||
wget \
|
||||
sudo \
|
||||
gnupg \
|
||||
libsm6 \
|
||||
libxext6 \
|
||||
libxrender-dev \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
build-essential \
|
||||
git \
|
||||
vim \
|
||||
screen \
|
||||
libtcmalloc-minimal4
|
||||
|
||||
|
||||
# Install google cloud SDK.
|
||||
RUN wget -q https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-359.0.0-linux-x86_64.tar.gz
|
||||
RUN tar xzf google-cloud-sdk-359.0.0-linux-x86_64.tar.gz
|
||||
RUN ./google-cloud-sdk/install.sh -q
|
||||
# Make sure gsutil will use the default service account.
|
||||
RUN echo '[GoogleCompute]\nservice_account = default' > /etc/boto.cfg
|
||||
|
||||
|
||||
# Install required libs.
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install cloud-tpu-client==0.10
|
||||
RUN pip install pyyaml==5.4.1
|
||||
RUN pip install fsspec==2021.10.1
|
||||
RUN pip install gcsfs==2021.10.1
|
||||
RUN pip install tensorflow-text==2.11.0
|
||||
RUN pip install pyglove==0.1.0
|
||||
RUN pip install cloudml-hypertune==0.1.0.dev6
|
||||
RUN pip install pylint==2.17.2
|
||||
RUN pip install keras-cv==0.4.0
|
||||
RUN pip install tensorflow-datasets==4.8.3
|
||||
RUN pip install tensorflow-estimator==2.12.0
|
||||
RUN pip install tensorflow-gcs-config==2.12.0
|
||||
RUN pip install tensorflow-hub==0.13.0
|
||||
RUN pip install tensorflow-io-gcs-filesystem==0.32.0
|
||||
RUN pip install tensorflow-metadata==1.13.1
|
||||
RUN pip install tensorflow-probability==0.19.0
|
||||
RUN pip install tensorboard==2.12.2
|
||||
RUN pip install tensorboard-data-server==0.7.0
|
||||
RUN pip install tensorboard-plugin-wit==1.8.1
|
||||
RUN pip install protobuf==3.20.3
|
||||
RUN pip install pandas==1.5.3
|
||||
RUN pip install pandas-datareader==0.10.0
|
||||
RUN pip install pandas-gbq==0.17.9
|
||||
RUN pip install pycocotools==2.0.6
|
||||
|
||||
# Installs Reduction Server NCCL plugin.
|
||||
RUN echo "deb https://packages.cloud.google.com/apt google-fast-socket main" | tee /etc/apt/sources.list.d/google-fast-socket.list \
|
||||
&& curl -s -L https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - \
|
||||
&& apt update && apt install -y google-reduction-server
|
||||
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
ENV PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp
|
||||
|
||||
# Lower the memory fragmentation, and speed up the training.
|
||||
# https://github.com/tensorflow/tensorflow/issues/44176#issuecomment-783768033
|
||||
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4
|
||||
|
||||
# Enable userspace DNS cache
|
||||
ENV GCS_RESOLVE_REFRESH_SECS=60
|
||||
ENV GCS_REQUEST_CONNECTION_TIMEOUT_SECS=300
|
||||
ENV GCS_METADATA_REQUEST_TIMEOUT_SECS=300
|
||||
ENV GCS_READ_REQUEST_TIMEOUT_SECS=300
|
||||
ENV GCS_WRITE_REQUEST_TIMEOUT_SECS=600
|
||||
# Each opened GCS file takes GCS_READ_CACHE_BLOCK_SIZE_MB of RAM, reduce the
|
||||
# value from the default 64MB to 8MB to decrease memory footprint.
|
||||
ENV GCS_READ_CACHE_BLOCK_SIZE_MB=8
|
||||
|
||||
WORKDIR /usr/local/lib/python3.8/dist-packages/official/vision
|
||||
|
||||
COPY model_oss/keras /automl_vision/keras
|
||||
COPY model_oss/util /automl_vision/util
|
||||
|
||||
WORKDIR /automl_vision
|
||||
|
||||
# Keras stable diffusion training codes set width and height as RESOLUTION.
|
||||
ENV RESOLUTION "512"
|
||||
|
||||
ENV PYTHONPATH "${PYTHONPATH}:/automl_vision/util"
|
||||
|
||||
# Run pylint to validate code.
|
||||
COPY .pylintrc /automl_vision/.pylintrc
|
||||
RUN find . -type f -name "*.py" | xargs pylint --rcfile=./.pylintrc --errors-only
|
||||
|
||||
ENTRYPOINT ["python3","keras/train.py"]
|
||||
@@ -0,0 +1,184 @@
|
||||
r"""Servers Keras Stable Diffusion models.
|
||||
|
||||
python serve.py --model_path=<model path in gcs>
|
||||
|
||||
curl -d \
|
||||
'{"prompt":"Hello Kitty"}' \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST http://localhost:8501/predict
|
||||
"""
|
||||
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from typing import List, Tuple
|
||||
|
||||
from absl import app
|
||||
# The docker builds could not find flask and waitress.
|
||||
# pylint: disable=import-error
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask import Response
|
||||
import keras_cv
|
||||
from PIL import Image
|
||||
from waitress import serve
|
||||
|
||||
from util import constants
|
||||
from util import fileutils
|
||||
|
||||
|
||||
flask_app = Flask(__name__)
|
||||
|
||||
stable_diffusion_model = None
|
||||
|
||||
|
||||
model_path = os.environ.get('MODEL_PATH', '')
|
||||
if model_path.startswith(constants.GCS_URI_PREFIX):
|
||||
print('Downloading models from gcs to local.')
|
||||
os.makedirs(constants.LOCAL_MODEL_DIR, exist_ok=True)
|
||||
fileutils.download_gcs_dir_to_local(
|
||||
os.path.dirname(model_path), constants.LOCAL_MODEL_DIR
|
||||
)
|
||||
model_path = os.path.join(
|
||||
constants.LOCAL_MODEL_DIR, os.path.basename(model_path)
|
||||
)
|
||||
|
||||
image_width = int(os.environ.get('IMAGE_WIDTH', 512))
|
||||
image_height = int(os.environ.get('IMAGE_HEIGHT', 512))
|
||||
|
||||
print('image_width=', image_width, 'image_height=', image_height)
|
||||
print('Create Keras stable diffusion models.')
|
||||
stable_diffusion_model = keras_cv.models.StableDiffusion(
|
||||
img_width=image_width,
|
||||
img_height=image_height,
|
||||
jit_compile=True,
|
||||
)
|
||||
|
||||
if model_path:
|
||||
# We just reload the weights of the fine-tuned diffusion model.
|
||||
print('Initialize finetuned models from: ', model_path)
|
||||
stable_diffusion_model.diffusion_model.load_weights(model_path)
|
||||
|
||||
|
||||
def error(message: str) -> str:
|
||||
"""Returns a JSON representing an error response."""
|
||||
return json.dumps({
|
||||
'success': False,
|
||||
'error': message,
|
||||
})
|
||||
|
||||
|
||||
def check_key_in_json(content: str, keys: List[str]) -> str:
|
||||
for key in keys:
|
||||
if key not in content:
|
||||
return error('No {} in request {}.'.format(key, content))
|
||||
return None
|
||||
|
||||
|
||||
def validate_json_key(json_key_string: str) -> Tuple[str, bool]:
|
||||
try:
|
||||
json_key = json.loads(json_key_string)
|
||||
except (ValueError, TypeError):
|
||||
return (error('Invalid key found in request'), False)
|
||||
return (json_key, True)
|
||||
|
||||
|
||||
# The health check route is required for docker deployment in google cloud.
|
||||
@flask_app.route('/ping')
|
||||
def ping() -> Response:
|
||||
"""Health checks."""
|
||||
return Response(status=200)
|
||||
|
||||
|
||||
# The return should be `Response` for docker deployment in google cloud.
|
||||
@flask_app.route('/predict', methods=['GET', 'POST'])
|
||||
def predict_model() -> Response:
|
||||
"""Predictions."""
|
||||
if request.method == 'POST':
|
||||
contents = request.get_json(force=True)
|
||||
|
||||
print('The input contents are:', contents)
|
||||
batch_size = 1
|
||||
num_steps = 25
|
||||
seed = 1234
|
||||
if 'parameters' in contents:
|
||||
parameters = contents['parameters']
|
||||
if 'batch_size' in parameters:
|
||||
batch_size = int(parameters['batch_size'])
|
||||
if 'num_steps' in parameters:
|
||||
num_steps = int(parameters['num_steps'])
|
||||
if 'seed' in parameters:
|
||||
seed = int(parameters['seed'])
|
||||
print('batch_size=', batch_size, 'num_steps=', num_steps, 'seed=', seed)
|
||||
if batch_size < 1:
|
||||
return Response(
|
||||
response=error('The batch size must be a positive integar.'),
|
||||
status=200,
|
||||
mimetype='text/plain',
|
||||
)
|
||||
if num_steps < 1:
|
||||
return Response(
|
||||
response=error('The num steps must be a positive integar.'),
|
||||
status=200,
|
||||
mimetype='text/plain',
|
||||
)
|
||||
predictions = []
|
||||
for content in contents['instances']:
|
||||
print('Processing:', content)
|
||||
prompt = content['prompt']
|
||||
generated_image_array = stable_diffusion_model.text_to_image(
|
||||
prompt=prompt,
|
||||
batch_size=batch_size,
|
||||
num_steps=num_steps,
|
||||
seed=seed,
|
||||
)
|
||||
|
||||
generated_image_bytes_array = []
|
||||
for i in range(batch_size):
|
||||
generated_image = Image.fromarray(generated_image_array[i])
|
||||
# Converts the image to a base64-encoded string.
|
||||
buffered_image = io.BytesIO()
|
||||
generated_image.save(buffered_image, format='JPEG')
|
||||
generated_image_bytes = base64.b64encode(
|
||||
buffered_image.getvalue()
|
||||
).decode('utf-8')
|
||||
generated_image_bytes_array.append(generated_image_bytes)
|
||||
prediction = {
|
||||
'prompt': prompt,
|
||||
'predicted_image': generated_image_bytes_array,
|
||||
}
|
||||
predictions.append(prediction)
|
||||
|
||||
return Response(
|
||||
response=json.dumps({
|
||||
'success': True,
|
||||
'predictions': predictions,
|
||||
}),
|
||||
status=200,
|
||||
mimetype='text/plain',
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
response=json.dumps({
|
||||
'success': True,
|
||||
'isalive': stable_diffusion_model is not None,
|
||||
}),
|
||||
status=200,
|
||||
mimetype='text/plain',
|
||||
)
|
||||
|
||||
|
||||
def serve_main(unused_argv):
|
||||
"""The main function to serve Keras models."""
|
||||
del unused_argv
|
||||
# This is used when running locally only. When deploying to Google App
|
||||
# Engine, a webserver process such as Gunicorn will serve the app.
|
||||
# # Debug deployment.
|
||||
# flask_app.run(host='0.0.0.0', port=8501, debug=True)
|
||||
# Prod deployment.
|
||||
serve(flask_app, host='0.0.0.0', port=8501)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(serve_main)
|
||||
@@ -0,0 +1,363 @@
|
||||
"""Train Keras Stable Diffusion.
|
||||
|
||||
Most the codes below are from
|
||||
https://keras.io/examples/generative/finetune_stable_diffusion/.
|
||||
"""
|
||||
import os
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
from absl import logging
|
||||
import keras_cv
|
||||
# pylint: disable=g-importing-member
|
||||
from keras_cv.models.stable_diffusion.clip_tokenizer import SimpleTokenizer
|
||||
from keras_cv.models.stable_diffusion.diffusion_model import DiffusionModel
|
||||
from keras_cv.models.stable_diffusion.image_encoder import ImageEncoder
|
||||
from keras_cv.models.stable_diffusion.noise_scheduler import NoiseScheduler
|
||||
from keras_cv.models.stable_diffusion.text_encoder import TextEncoder
|
||||
import numpy as np
|
||||
# The docker builds could not find pandas.
|
||||
# pylint: disable=import-error
|
||||
import pandas as pd
|
||||
import tensorflow as tf
|
||||
from tensorflow import keras
|
||||
import tensorflow.experimental.numpy as tnp
|
||||
|
||||
from util import constants
|
||||
from util import fileutils
|
||||
|
||||
_INPUT_CSV_PATH = flags.DEFINE_string(
|
||||
'input_csv_path',
|
||||
None,
|
||||
'The input csv path.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
_USE_MP = flags.DEFINE_bool(
|
||||
'use_mp',
|
||||
True,
|
||||
'Enable mixed-precision training if the underlying GPU has tensor cores.',
|
||||
)
|
||||
|
||||
_EPOCHS = flags.DEFINE_integer('epochs', 1, 'The number of epochs.')
|
||||
|
||||
_OUTPUT_MODEL_DIR = flags.DEFINE_string(
|
||||
'output_model_dir',
|
||||
None,
|
||||
'The output model dir.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
# These hyperparameters defaults come from this tutorial by Hugging Face:
|
||||
# https://huggingface.co/docs/diffusers/training/text2image
|
||||
_LEARNING_RATE = flags.DEFINE_float(
|
||||
'learning_rate', 1e-5, 'The learning rate parameter for AdamW optimizer.'
|
||||
)
|
||||
|
||||
_BETA_1 = flags.DEFINE_float(
|
||||
'beta_1', 0.9, 'The beta_1 parameter for AdamW optimizer.'
|
||||
)
|
||||
|
||||
_BETA_2 = flags.DEFINE_float(
|
||||
'beta_2', 0.999, 'The beta_2 parameter for AdamW optimizer.'
|
||||
)
|
||||
|
||||
_WEIGHT_DECAY = flags.DEFINE_float(
|
||||
'weight_decay', 1e-2, 'The weight decay parameter for AdamW optimizer.'
|
||||
)
|
||||
|
||||
_EPSILON = flags.DEFINE_float(
|
||||
'epsilon', 1e-08, 'The epsilon parameter for AdamW optimizer.'
|
||||
)
|
||||
|
||||
RESOLUTION = int(os.environ.get('RESOLUTION', 512))
|
||||
|
||||
# The padding token and maximum prompt length are specific to the text encoder.
|
||||
# If you're using a different text encoder be sure to change them accordingly.
|
||||
PADDING_TOKEN = 49407
|
||||
MAX_PROMPT_LENGTH = 77
|
||||
|
||||
AUTO = tf.data.AUTOTUNE
|
||||
POS_IDS = tf.convert_to_tensor([list(range(MAX_PROMPT_LENGTH))], dtype=tf.int32)
|
||||
|
||||
|
||||
augmenter = keras.Sequential(
|
||||
layers=[
|
||||
keras_cv.layers.CenterCrop(RESOLUTION, RESOLUTION),
|
||||
keras_cv.layers.RandomFlip(),
|
||||
tf.keras.layers.Rescaling(scale=1.0 / 127.5, offset=-1),
|
||||
]
|
||||
)
|
||||
text_encoder = TextEncoder(MAX_PROMPT_LENGTH)
|
||||
|
||||
|
||||
def process_image(image_path, tokenized_text):
|
||||
image = tf.io.read_file(image_path)
|
||||
image = tf.io.decode_png(image, 3)
|
||||
image = tf.image.resize(image, (RESOLUTION, RESOLUTION))
|
||||
return image, tokenized_text
|
||||
|
||||
|
||||
def apply_augmentation(image_batch, token_batch):
|
||||
return augmenter(image_batch), token_batch
|
||||
|
||||
|
||||
def run_text_encoder(image_batch, token_batch):
|
||||
return (
|
||||
image_batch,
|
||||
token_batch,
|
||||
text_encoder([token_batch, POS_IDS], training=False),
|
||||
)
|
||||
|
||||
|
||||
def prepare_dict(image_batch, token_batch, encoded_text_batch):
|
||||
return {
|
||||
'images': image_batch,
|
||||
'tokens': token_batch,
|
||||
'encoded_text': encoded_text_batch,
|
||||
}
|
||||
|
||||
|
||||
def prepare_dataset(image_paths, tokenized_texts, batch_size=1):
|
||||
dataset = tf.data.Dataset.from_tensor_slices((image_paths, tokenized_texts))
|
||||
dataset = dataset.shuffle(batch_size * 10)
|
||||
dataset = dataset.map(process_image, num_parallel_calls=AUTO).batch(
|
||||
batch_size
|
||||
)
|
||||
dataset = dataset.map(apply_augmentation, num_parallel_calls=AUTO)
|
||||
dataset = dataset.map(run_text_encoder, num_parallel_calls=AUTO)
|
||||
dataset = dataset.map(prepare_dict, num_parallel_calls=AUTO)
|
||||
return dataset.prefetch(AUTO)
|
||||
|
||||
|
||||
def prepare_training_dataset(dataset_csv):
|
||||
"""Prepares training datasets."""
|
||||
if dataset_csv.startswith(constants.GCS_URI_PREFIX):
|
||||
if not os.path.exists(constants.LOCAL_DATA_DIR):
|
||||
os.makedirs(constants.LOCAL_DATA_DIR)
|
||||
logging.info(
|
||||
'Start to download data from %s to %s.',
|
||||
os.path.dirname(dataset_csv),
|
||||
constants.LOCAL_DATA_DIR,
|
||||
)
|
||||
fileutils.download_gcs_dir_to_local(
|
||||
os.path.dirname(dataset_csv), constants.LOCAL_DATA_DIR
|
||||
)
|
||||
data_frame = pd.read_csv(
|
||||
os.path.join(constants.LOCAL_DATA_DIR, os.path.basename(dataset_csv))
|
||||
)
|
||||
data_frame['image_path'] = data_frame['image_path'].apply(
|
||||
lambda x: os.path.join(constants.LOCAL_DATA_DIR, x)
|
||||
)
|
||||
else:
|
||||
# Keeps the following codes for experiments with
|
||||
# https://keras.io/examples/generative/finetune_stable_diffusion/.
|
||||
data_path = tf.keras.utils.get_file(origin=dataset_csv, untar=True)
|
||||
data_frame = pd.read_csv(os.path.join(data_path, 'data.csv'))
|
||||
data_frame['image_path'] = data_frame['image_path'].apply(
|
||||
lambda x: os.path.join(data_path, x)
|
||||
)
|
||||
data_frame.head()
|
||||
|
||||
# Load the tokenizer.
|
||||
tokenizer = SimpleTokenizer()
|
||||
|
||||
# Method to tokenize and pad the tokens.
|
||||
def process_text(caption):
|
||||
tokens = tokenizer.encode(caption)
|
||||
tokens = tokens + [PADDING_TOKEN] * (MAX_PROMPT_LENGTH - len(tokens))
|
||||
return np.array(tokens)
|
||||
|
||||
# Collate the tokenized captions into an array.
|
||||
tokenized_texts = np.empty((len(data_frame), MAX_PROMPT_LENGTH))
|
||||
|
||||
all_captions = list(data_frame['caption'].values)
|
||||
for i, caption in enumerate(all_captions):
|
||||
tokenized_texts[i] = process_text(caption)
|
||||
|
||||
# Prepare the dataset.
|
||||
training_dataset = prepare_dataset(
|
||||
np.array(data_frame['image_path']), tokenized_texts, batch_size=4
|
||||
)
|
||||
|
||||
return training_dataset
|
||||
|
||||
|
||||
class Trainer(tf.keras.Model):
|
||||
"""The trainer for Keras Stable Diffusion."""
|
||||
|
||||
# Reference:
|
||||
# https://github.com/huggingface/diffusers/blob/main/examples/text_to_image/train_text_to_image.py
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
diffusion_model,
|
||||
vae,
|
||||
noise_scheduler,
|
||||
use_mixed_precision=False,
|
||||
max_grad_norm=1.0,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.diffusion_model = diffusion_model
|
||||
self.vae = vae
|
||||
self.noise_scheduler = noise_scheduler
|
||||
self.max_grad_norm = max_grad_norm
|
||||
|
||||
self.use_mixed_precision = use_mixed_precision
|
||||
self.vae.trainable = False
|
||||
|
||||
def train_step(self, inputs):
|
||||
images = inputs['images']
|
||||
encoded_text = inputs['encoded_text']
|
||||
batch_size = tf.shape(images)[0]
|
||||
|
||||
with tf.GradientTape() as tape:
|
||||
# Project image into the latent space and sample from it.
|
||||
latents = self.sample_from_encoder_outputs(
|
||||
self.vae(images, training=False)
|
||||
)
|
||||
# Know more about the magic number here:
|
||||
# https://keras.io/examples/generative/fine_tune_via_textual_inversion/
|
||||
latents = latents * 0.18215
|
||||
|
||||
# Sample noise that we'll add to the latents.
|
||||
noise = tf.random.normal(tf.shape(latents))
|
||||
|
||||
# Sample a random timestep for each image.
|
||||
timesteps = tnp.random.randint(
|
||||
0, self.noise_scheduler.train_timesteps, (batch_size,)
|
||||
)
|
||||
|
||||
# Add noise to the latents according to the noise magnitude at each
|
||||
# timestep (this is the forward diffusion process).
|
||||
noisy_latents = self.noise_scheduler.add_noise(
|
||||
tf.cast(latents, noise.dtype), noise, timesteps
|
||||
)
|
||||
|
||||
# Get the target for loss depending on the prediction type
|
||||
# just the sampled noise for now.
|
||||
target = noise # noise_schedule.predict_epsilon == True
|
||||
|
||||
# Predict the noise residual and compute loss.
|
||||
# pylint: disable=unnecessary-lambda
|
||||
timestep_embedding = tf.map_fn(
|
||||
lambda t: self.get_timestep_embedding(t), timesteps, dtype=tf.float32
|
||||
)
|
||||
timestep_embedding = tf.squeeze(timestep_embedding, 1)
|
||||
model_pred = self.diffusion_model(
|
||||
[noisy_latents, timestep_embedding, encoded_text], training=True
|
||||
)
|
||||
loss = self.compiled_loss(target, model_pred)
|
||||
if self.use_mixed_precision:
|
||||
loss = self.optimizer.get_scaled_loss(loss)
|
||||
|
||||
# Update parameters of the diffusion model.
|
||||
trainable_vars = self.diffusion_model.trainable_variables
|
||||
gradients = tape.gradient(loss, trainable_vars)
|
||||
if self.use_mixed_precision:
|
||||
gradients = self.optimizer.get_unscaled_gradients(gradients)
|
||||
gradients = [tf.clip_by_norm(g, self.max_grad_norm) for g in gradients]
|
||||
self.optimizer.apply_gradients(zip(gradients, trainable_vars))
|
||||
|
||||
return {m.name: m.result() for m in self.metrics}
|
||||
|
||||
def get_timestep_embedding(self, timestep, dim=320, max_period=10000):
|
||||
half = dim // 2
|
||||
log_max_preiod = tf.math.log(tf.cast(max_period, tf.float32))
|
||||
# The docker builds could not support unary `-`.
|
||||
# pylint: disable=invalid-unary-operand-type
|
||||
freqs = tf.math.exp(
|
||||
-log_max_preiod * tf.range(0, half, dtype=tf.float32) / half
|
||||
)
|
||||
args = tf.convert_to_tensor([timestep], dtype=tf.float32) * freqs
|
||||
embedding = tf.concat([tf.math.cos(args), tf.math.sin(args)], 0)
|
||||
embedding = tf.reshape(embedding, [1, -1])
|
||||
return embedding
|
||||
|
||||
def sample_from_encoder_outputs(self, outputs):
|
||||
mean, logvar = tf.split(outputs, 2, axis=-1)
|
||||
logvar = tf.clip_by_value(logvar, -30.0, 20.0)
|
||||
std = tf.exp(0.5 * logvar)
|
||||
sample = tf.random.normal(tf.shape(mean), dtype=mean.dtype)
|
||||
return mean + std * sample
|
||||
|
||||
def save_weights(
|
||||
self, filepath, overwrite=True, save_format=None, options=None
|
||||
):
|
||||
# Overriding this method will allow us to use the `ModelCheckpoint`
|
||||
# callback directly with this trainer class. In this case, it will
|
||||
# only checkpoint the `diffusion_model` since that's what we're training
|
||||
# during fine-tuning.
|
||||
self.diffusion_model.save_weights(
|
||||
filepath=filepath,
|
||||
overwrite=overwrite,
|
||||
save_format=save_format,
|
||||
options=options,
|
||||
)
|
||||
|
||||
|
||||
def main(_) -> None:
|
||||
# _INPUT_CSV_PATH and _OUTPUT_MODEL_DIR should have the format as
|
||||
# gs://<bucket_name>/<object_name>.
|
||||
if _INPUT_CSV_PATH.value:
|
||||
if not _INPUT_CSV_PATH.value.startswith(constants.GCS_URI_PREFIX):
|
||||
raise ValueError('The input csv path should be a gcs path like gs://<>')
|
||||
if _OUTPUT_MODEL_DIR.value:
|
||||
if not _OUTPUT_MODEL_DIR.value.startswith(constants.GCS_URI_PREFIX):
|
||||
raise ValueError('The output model dir should be a gcs path like gs://<>')
|
||||
|
||||
if _USE_MP.value:
|
||||
keras.mixed_precision.set_global_policy('mixed_float16')
|
||||
|
||||
image_encoder = ImageEncoder(RESOLUTION, RESOLUTION)
|
||||
diffusion_ft_trainer = Trainer(
|
||||
diffusion_model=DiffusionModel(RESOLUTION, RESOLUTION, MAX_PROMPT_LENGTH),
|
||||
# Remove the top layer from the encoder, which cuts off the variance and
|
||||
# only returns the mean.
|
||||
vae=tf.keras.Model(
|
||||
image_encoder.input,
|
||||
image_encoder.layers[-2].output,
|
||||
),
|
||||
noise_scheduler=NoiseScheduler(),
|
||||
use_mixed_precision=_USE_MP.value,
|
||||
)
|
||||
|
||||
optimizer = tf.keras.optimizers.experimental.AdamW(
|
||||
learning_rate=_LEARNING_RATE.value,
|
||||
weight_decay=_WEIGHT_DECAY.value,
|
||||
beta_1=_BETA_1.value,
|
||||
beta_2=_BETA_2.value,
|
||||
epsilon=_EPSILON.value,
|
||||
)
|
||||
diffusion_ft_trainer.compile(optimizer=optimizer, loss='mse')
|
||||
|
||||
training_dataset = prepare_training_dataset(_INPUT_CSV_PATH.value)
|
||||
|
||||
# Note: gcsfuse does not work for Keras. We saves the trained models locally
|
||||
# first, and then copy to gcs storages.
|
||||
if not os.path.exists(constants.LOCAL_MODEL_DIR):
|
||||
os.makedirs(constants.LOCAL_MODEL_DIR)
|
||||
# The default saved model is in HDF5.
|
||||
ckpt_path = os.path.join(constants.LOCAL_MODEL_DIR, 'saved_model.h5')
|
||||
ckpt_callback = tf.keras.callbacks.ModelCheckpoint(
|
||||
ckpt_path,
|
||||
save_weights_only=True,
|
||||
monitor='loss',
|
||||
mode='min',
|
||||
)
|
||||
diffusion_ft_trainer.fit(
|
||||
training_dataset, epochs=_EPOCHS.value, callbacks=[ckpt_callback]
|
||||
)
|
||||
|
||||
# Copies the files in constants.LOCAL_MODEL_DIR to output_model_dir.
|
||||
fileutils.upload_local_dir_to_gcs(
|
||||
constants.LOCAL_MODEL_DIR, _OUTPUT_MODEL_DIR.value
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
FROM tensorflow/build:2.12-python3.9
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# This is added to fix docker build error related to Nvidia key update.
|
||||
RUN rm -f /etc/apt/sources.list.d/cuda.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
|
||||
# Install basic libs.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
cmake \
|
||||
curl \
|
||||
wget \
|
||||
sudo \
|
||||
gnupg \
|
||||
libsm6 \
|
||||
libxext6 \
|
||||
libxrender-dev \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
build-essential \
|
||||
git \
|
||||
vim \
|
||||
libtcmalloc-minimal4
|
||||
|
||||
|
||||
# Install google cloud CLI.
|
||||
RUN wget -q https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-430.0.0-linux-x86.tar.gz
|
||||
RUN tar xzf google-cloud-cli-430.0.0-linux-x86.tar.gz
|
||||
RUN ./google-cloud-sdk/install.sh -q
|
||||
# Make sure gsutil will use the default service account.
|
||||
RUN echo '[GoogleCompute]\nservice_account = default' > /etc/boto.cfg
|
||||
|
||||
|
||||
# Install required libs.
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install cloud-tpu-client==0.10
|
||||
RUN pip install pyyaml==6.0
|
||||
RUN pip install fsspec==2023.4.0
|
||||
RUN pip install gcsfs==2023.4.0
|
||||
RUN pip install tf-models-official==2.12.0
|
||||
RUN pip install cloudml-hypertune==0.1.0.dev6
|
||||
RUN pip install pylint==2.17.3
|
||||
|
||||
# Installs Reduction Server NCCL plugin.
|
||||
RUN echo "deb https://packages.cloud.google.com/apt google-fast-socket main" | tee /etc/apt/sources.list.d/google-fast-socket.list \
|
||||
&& curl -s -L https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - \
|
||||
&& apt update && apt install -y google-reduction-server
|
||||
|
||||
ENV PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp
|
||||
|
||||
# Lower the memory fragmentation, and speed up the training.
|
||||
# https://github.com/tensorflow/tensorflow/issues/44176#issuecomment-783768033
|
||||
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4
|
||||
|
||||
# Enable userspace DNS cache
|
||||
ENV GCS_RESOLVE_REFRESH_SECS=60
|
||||
ENV GCS_REQUEST_CONNECTION_TIMEOUT_SECS=300
|
||||
ENV GCS_METADATA_REQUEST_TIMEOUT_SECS=300
|
||||
ENV GCS_READ_REQUEST_TIMEOUT_SECS=300
|
||||
ENV GCS_WRITE_REQUEST_TIMEOUT_SECS=600
|
||||
# Each opened GCS file takes GCS_READ_CACHE_BLOCK_SIZE_MB of RAM, reduce the
|
||||
# value from the default 64MB to 8MB to decrease memory footprint.
|
||||
ENV GCS_READ_CACHE_BLOCK_SIZE_MB=8
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
FROM gcr.io/automl-migration-test/movinet-base:latest
|
||||
|
||||
# Copy license.
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
RUN wget https://raw.githubusercontent.com/tensorflow/models/954dd73bffd43174bd3ca26a4a34abebe4147570/official/projects/movinet/tools/export_saved_model.py \
|
||||
-O /usr/local/lib/python3.9/dist-packages/official/projects/movinet/tools/export_saved_model.py
|
||||
|
||||
WORKDIR /automl_vision
|
||||
|
||||
ENV PYTHONPATH "${PYTHONPATH}:/automl_vision/util"
|
||||
|
||||
ENTRYPOINT ["python3", "-m", "official.projects.movinet.tools.export_saved_model"]
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
FROM gcr.io/automl-migration-test/movinet-base:latest
|
||||
|
||||
# Copy license.
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
RUN pip install flask==2.3.2
|
||||
RUN pip install waitress==2.1.2
|
||||
|
||||
RUN mkdir -p /automl_vision/movinet/serving
|
||||
COPY model_oss/movinet/serving /automl_vision/movinet/serving
|
||||
COPY model_oss/util /automl_vision/util
|
||||
|
||||
WORKDIR /automl_vision
|
||||
|
||||
ENV PYTHONPATH "${PYTHONPATH}:/automl_vision/util"
|
||||
|
||||
ENTRYPOINT ["flask", "--app", "movinet.serving.serving_main", "run"]
|
||||
CMD ["--host=0.0.0.0", "--port=8501"]
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
FROM gcr.io/automl-migration-test/movinet-base:latest
|
||||
|
||||
# Copy license.
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
RUN mkdir -p /automl_vision/movinet
|
||||
COPY model_oss/movinet/*.py /automl_vision/movinet/
|
||||
COPY model_oss/util /automl_vision/util
|
||||
|
||||
WORKDIR /automl_vision
|
||||
|
||||
ENV PYTHONPATH "${PYTHONPATH}:/automl_vision/util"
|
||||
|
||||
# Run pylint to validate code.
|
||||
COPY .pylintrc /automl_vision/.pylintrc
|
||||
RUN find . -type f -name "*.py" | xargs pylint --rcfile=./.pylintrc --errors-only
|
||||
|
||||
ENTRYPOINT ["python3","movinet/train.py"]
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
"""Main executable for MoViNet online / batch predictions."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
import json
|
||||
import os
|
||||
|
||||
from absl import app
|
||||
from absl import logging
|
||||
import flask
|
||||
import tensorflow as tf
|
||||
import waitress
|
||||
|
||||
from movinet.serving import video_serving_lib
|
||||
from util import constants
|
||||
|
||||
|
||||
flask_app = flask.Flask(__name__)
|
||||
logging.set_verbosity(logging.INFO)
|
||||
|
||||
movinet_model = None
|
||||
|
||||
_BATCH_SIZE = int(os.environ.get('BATCH_SIZE', '1'))
|
||||
_NUM_FRAMES = int(os.environ.get('NUM_FRAMES', '32'))
|
||||
_FPS = float(os.environ.get('FPS', '5'))
|
||||
_OVERLAP_FRAMES = int(os.environ.get('OVERLAP_FRAMES', '24'))
|
||||
_OBJECTIVE = os.environ.get(
|
||||
'OBJECTIVE', constants.OBJECTIVE_VIDEO_CLASSIFICATION
|
||||
).lower()
|
||||
|
||||
# VAR parameters.
|
||||
_CONFIDENCE_THRESHOLD = float(os.environ.get('CONFIDENCE_THRESHOLD', '0.5'))
|
||||
_MIN_GAP_TIME = float(os.environ.get('MIN_GAP_TIME', '1.5'))
|
||||
|
||||
|
||||
def load_movinet_model() -> None:
|
||||
model_path = os.environ.get('MODEL_PATH')
|
||||
|
||||
if not model_path:
|
||||
raise app.UsageError('Missing MODEL_PATH environment variable.')
|
||||
|
||||
# We just reload the weights of the fine-tuned diffusion model.
|
||||
logging.info('Initialize finetuned models from: %s', model_path)
|
||||
global movinet_model
|
||||
movinet_model = tf.saved_model.load(model_path)
|
||||
|
||||
|
||||
load_movinet_model()
|
||||
|
||||
|
||||
def error(message: str) -> str:
|
||||
"""Returns a JSON representing an error response."""
|
||||
return json.dumps({
|
||||
'success': False,
|
||||
'error': message,
|
||||
})
|
||||
|
||||
|
||||
# The health check route is required for docker deployment in google cloud.
|
||||
@flask_app.route('/ping')
|
||||
def ping() -> flask.Response:
|
||||
"""Health checks."""
|
||||
return flask.Response(status=200)
|
||||
|
||||
|
||||
# The return should be `Response` for docker deployment in google cloud.
|
||||
@flask_app.route('/predict', methods=['GET', 'POST'])
|
||||
def predict_model() -> flask.Response:
|
||||
"""Predictions."""
|
||||
if flask.request.method == 'POST':
|
||||
contents = flask.request.get_json(force=True)
|
||||
|
||||
logging.info('The input contents are: %s', contents)
|
||||
instances = contents.get('instances', [])
|
||||
|
||||
try:
|
||||
predictions = []
|
||||
for instance in instances:
|
||||
executor = video_serving_lib.parse_request(instance)
|
||||
prediction = executor.get_prediction(
|
||||
movinet_model,
|
||||
_BATCH_SIZE,
|
||||
_FPS,
|
||||
_NUM_FRAMES,
|
||||
_OVERLAP_FRAMES,
|
||||
_OBJECTIVE,
|
||||
)
|
||||
if _OBJECTIVE == constants.OBJECTIVE_VIDEO_CLASSIFICATION:
|
||||
prediction = video_serving_lib.postprocess_vcn(prediction)
|
||||
elif _OBJECTIVE == constants.OBJECTIVE_VIDEO_ACTION_RECOGNITION:
|
||||
prediction = video_serving_lib.postprocess_var(
|
||||
executor.windows, prediction, _CONFIDENCE_THRESHOLD, _MIN_GAP_TIME
|
||||
)
|
||||
predictions.append(prediction)
|
||||
except ValueError as e:
|
||||
return flask.Response(
|
||||
error(str(e)), status=500, mimetype='application/json'
|
||||
)
|
||||
|
||||
return flask.Response(
|
||||
response=json.dumps({
|
||||
'success': True,
|
||||
'predictions': predictions,
|
||||
}),
|
||||
status=200,
|
||||
mimetype='application/json',
|
||||
)
|
||||
else:
|
||||
return flask.Response(
|
||||
response=json.dumps({
|
||||
'success': True,
|
||||
'isalive': movinet_model is not None,
|
||||
}),
|
||||
status=200,
|
||||
mimetype='application/json',
|
||||
)
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> None:
|
||||
if len(argv) > 1:
|
||||
raise app.UsageError('Too many command-line arguments.')
|
||||
# This is used when running locally only. When deploying to Google App
|
||||
# Engine, a webserver process such as Gunicorn will serve the app.
|
||||
# # Debug deployment.
|
||||
# flask_app.run(host='0.0.0.0', port=8501, debug=True)
|
||||
# Prod deployment.
|
||||
if _OBJECTIVE not in [
|
||||
constants.OBJECTIVE_VIDEO_CLASSIFICATION,
|
||||
constants.OBJECTIVE_VIDEO_ACTION_RECOGNITION,
|
||||
]:
|
||||
raise app.UsageError('Objective must be vcn or var.')
|
||||
logging.info(
|
||||
'Env: batch_size: %s, num_frames: %s, fps: %s, overlap_frames: %s',
|
||||
_BATCH_SIZE,
|
||||
_NUM_FRAMES,
|
||||
_FPS,
|
||||
_OVERLAP_FRAMES,
|
||||
)
|
||||
waitress.serve(flask_app, host='0.0.0.0', port=8501)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
+462
@@ -0,0 +1,462 @@
|
||||
"""Lib for handling video prediction requests.
|
||||
|
||||
The VCN inference algorithm is as follows:
|
||||
1. Find all video frames within the given clip according to the sampling FPS.
|
||||
2. Create possibly overlapping sliding windows according to the num_frames and
|
||||
overlap_frames parameters. The last window might have a larger overlap if it
|
||||
doesn't exactly fit.
|
||||
3. Run model inference on each sliding window and compute softmax to obtain
|
||||
probabilities.
|
||||
4. Average the probabilities over all sliding windows.
|
||||
|
||||
The VAR inference algorithm is very similar to VCN, with a few differences:
|
||||
1. The last sliding window is discarded if it does not exactly fit.
|
||||
2. Instead of averaging, the postprocessing consists of temporal nonmaximal
|
||||
suppression and removing background and low-confidence labels.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
from typing import Any, Dict, Optional, Sequence, Union, cast
|
||||
|
||||
from absl import logging
|
||||
import cv2
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
|
||||
from util import constants
|
||||
from util import fileutils
|
||||
|
||||
|
||||
_JSON_LABEL_KEY = 'label'
|
||||
_JSON_GCS_URI_KEY = 'content'
|
||||
_JSON_CONFIDENCE_KEY = 'confidence'
|
||||
_JSON_START_TIME_KEY = 'timeSegmentStart'
|
||||
_JSON_END_TIME_KEY = 'timeSegmentEnd'
|
||||
_BACKGROUND_LABEL = 0
|
||||
_JSON_REQUIRED_KEYS = [
|
||||
_JSON_GCS_URI_KEY,
|
||||
_JSON_START_TIME_KEY,
|
||||
_JSON_END_TIME_KEY,
|
||||
]
|
||||
_IMAGE_WIDTH = int(os.environ.get('IMAGE_WIDTH', '172'))
|
||||
_IMAGE_HEIGHT = int(os.environ.get('IMAGE_HEIGHT', '172'))
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class DetectionOutput:
|
||||
timestamp: float
|
||||
label: int
|
||||
confidence: float
|
||||
|
||||
def to_json_obj(self) -> Dict[str, Union[int, float]]:
|
||||
"""Encodes self as a dict for JSON serialization."""
|
||||
return {
|
||||
_JSON_LABEL_KEY: self.label,
|
||||
_JSON_START_TIME_KEY: self.timestamp,
|
||||
_JSON_END_TIME_KEY: self.timestamp,
|
||||
_JSON_CONFIDENCE_KEY: self.confidence,
|
||||
}
|
||||
|
||||
|
||||
def create_detection_output(
|
||||
timestamp: float, predictions: np.ndarray
|
||||
) -> DetectionOutput:
|
||||
label = np.argmax(predictions).item()
|
||||
confidence: float = predictions[label].item()
|
||||
return DetectionOutput(timestamp, label, confidence)
|
||||
|
||||
|
||||
class SlidingWindow:
|
||||
"""Represents a sliding window with start / end timestamps."""
|
||||
|
||||
def __init__(self, fps: float, frames: Sequence[int]):
|
||||
if not frames:
|
||||
raise ValueError('Sliding window cannot be empty.')
|
||||
self.frames = frames
|
||||
self.start_time = frames[0] / fps
|
||||
self.end_time = frames[-1] / fps
|
||||
self.frame_data: list[Optional[np.ndarray]] = []
|
||||
self.clear_frame_data()
|
||||
|
||||
def load_cache_from(self, other: SlidingWindow) -> int:
|
||||
"""Loads cache from another sliding window if possible."""
|
||||
cache_count = 0
|
||||
for i, frame in enumerate(self.frames):
|
||||
try:
|
||||
other_idx = other.frames.index(frame)
|
||||
self.frame_data[i] = other.frame_data[other_idx]
|
||||
cache_count += 1
|
||||
except ValueError:
|
||||
# Cache miss.
|
||||
pass
|
||||
return cache_count
|
||||
|
||||
def load_frames(self, video: Any) -> Sequence[np.ndarray]:
|
||||
"""Loads frames of this sliding window from a video."""
|
||||
for i, frame in enumerate(self.frames):
|
||||
if self.frame_data[i] is None:
|
||||
video.set(cv2.CAP_PROP_POS_FRAMES, frame)
|
||||
ret, frame = video.read()
|
||||
if not ret:
|
||||
raise IOError(f'Failed to read video at frame {frame}.')
|
||||
self.frame_data[i] = cv2.resize(frame, (_IMAGE_WIDTH, _IMAGE_HEIGHT))
|
||||
return cast(Sequence[np.ndarray], self.frame_data)
|
||||
|
||||
def clear_frame_data(self) -> None:
|
||||
"""Clears frame data of this sliding window to reduce memory usage."""
|
||||
self.frame_data: list[Optional[np.ndarray]] = [None] * len(self)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.frames)
|
||||
|
||||
@property
|
||||
def middle_timestamp(self) -> float:
|
||||
return (self.start_time + self.end_time) / 2
|
||||
|
||||
|
||||
def _get_sliding_windows(
|
||||
frames: Sequence[int],
|
||||
original_fps: float,
|
||||
window_size: int,
|
||||
overlap: int,
|
||||
flush_last_window: bool,
|
||||
) -> Sequence[SlidingWindow]:
|
||||
"""Computes a list of sliding windows from frames.
|
||||
|
||||
Args:
|
||||
frames: A list of frame indices.
|
||||
original_fps: Frames per second of the original video.
|
||||
window_size: Number of frames in a single window.
|
||||
overlap: Number of overlapping frames in adjacent windows.
|
||||
flush_last_window: Where to flush the last window if there are not enough
|
||||
frames left.
|
||||
|
||||
Returns:
|
||||
A list of sliding windows, each has a list of frame indices. The last two
|
||||
windows might have a larger overlap if the last window does not exactly fit
|
||||
and flush_last_window is set to True.
|
||||
|
||||
Raises:
|
||||
ValueError: Arguments are invalid.
|
||||
"""
|
||||
if window_size <= overlap:
|
||||
raise ValueError(f'Window size {window_size} <= overlap {overlap}')
|
||||
total_frames = len(frames)
|
||||
windows: list[SlidingWindow] = []
|
||||
for i in range(0, total_frames, window_size - overlap):
|
||||
if i == 0 or i + window_size <= total_frames:
|
||||
windows.append(SlidingWindow(original_fps, frames[i : i + window_size]))
|
||||
elif i + overlap < total_frames and flush_last_window:
|
||||
# Some frames in this window are not covered by the previous window.
|
||||
windows.append(
|
||||
SlidingWindow(
|
||||
original_fps, frames[total_frames - window_size : total_frames]
|
||||
)
|
||||
)
|
||||
return windows
|
||||
|
||||
|
||||
def _sample_frame_indices(
|
||||
start_time: float,
|
||||
end_time: float,
|
||||
original_fps: float,
|
||||
sample_fps: float,
|
||||
max_frames: int,
|
||||
padding_left: int = 0,
|
||||
padding_right: int = 0,
|
||||
) -> Sequence[int]:
|
||||
"""Samples frames from start_time to end_time by sample_fps.
|
||||
|
||||
Args:
|
||||
start_time: Start timestamp in seconds.
|
||||
end_time: End timestamp in seconds.
|
||||
original_fps: Frames per second of the original video.
|
||||
sample_fps: Number of frames to sample per second.
|
||||
max_frames: Total number of frames in the video.
|
||||
padding_left: Padding to add to the start in frames. Padded frames will be
|
||||
duplicates of the first frame.
|
||||
padding_right: Padding to add to the end in frames. Padded frames will be
|
||||
duplicates of the last frame.
|
||||
|
||||
Returns:
|
||||
A list of sampled frame indices.
|
||||
"""
|
||||
ret = [
|
||||
min(max_frames - 1, round(t * original_fps))
|
||||
for t in np.arange(start_time, end_time, 1 / sample_fps)
|
||||
]
|
||||
if ret:
|
||||
ret = [ret[0]] * padding_left + ret + [ret[-1]] * padding_right
|
||||
return ret
|
||||
|
||||
|
||||
class VideoPredictionExecutor:
|
||||
"""Represents a Video prediction request with a video clip."""
|
||||
|
||||
def __init__(self, gcs_uri: str, start_time: float, end_time: float):
|
||||
self._gcs_uri = gcs_uri
|
||||
self._start_time = start_time
|
||||
self._end_time = end_time
|
||||
self.windows: Sequence[SlidingWindow] = []
|
||||
self._last_window: SlidingWindow = None
|
||||
|
||||
def _read_frames_from_window(
|
||||
self, video: Any, new_window: SlidingWindow
|
||||
) -> Sequence[np.ndarray]:
|
||||
"""Reads video frames from the new window.
|
||||
|
||||
Args:
|
||||
video: Video loaded with cv2.
|
||||
new_window: A list of sorted frame indices in the new window.
|
||||
|
||||
Returns:
|
||||
Frame data from the video as a list of numpy arrays.
|
||||
|
||||
Raises:
|
||||
IOError: Failed to read video.
|
||||
"""
|
||||
# Caches frames as much as possible.
|
||||
if self._last_window is not None:
|
||||
cache_count = new_window.load_cache_from(self._last_window)
|
||||
logging.info('Cached %d frames.', cache_count)
|
||||
self._last_window.clear_frame_data()
|
||||
self._last_window = new_window
|
||||
return new_window.load_frames(video)
|
||||
|
||||
def _predict(
|
||||
self, model: Any, video: Any, batched_windows: Sequence[SlidingWindow]
|
||||
) -> np.ndarray:
|
||||
"""Run model inference on specific frames of a video.
|
||||
|
||||
Args:
|
||||
model: MoViNet model.
|
||||
video: Video loaded with cv2.
|
||||
batched_windows: A batch of sliding windows to predict. Each element is an
|
||||
integer frame index. Must have equal number of frames in each window.
|
||||
|
||||
Returns:
|
||||
Prediction results.
|
||||
|
||||
Raises:
|
||||
ValueError: Batched windows are not sorted, or do not have equal number of
|
||||
frames in each window.
|
||||
IOError: Failed to read video.
|
||||
"""
|
||||
if any(
|
||||
(
|
||||
len(window) != len(batched_windows[0])
|
||||
for window in batched_windows[1:]
|
||||
)
|
||||
):
|
||||
raise ValueError(
|
||||
'Batched windows do not have equal number of frames in each window.'
|
||||
)
|
||||
batch = []
|
||||
logging.info('Loading video frames...')
|
||||
for window in batched_windows:
|
||||
logging.info('Predict frames: %s', window.frames)
|
||||
frames = self._read_frames_from_window(video, window)
|
||||
batch.append(frames)
|
||||
input_tensor = tf.convert_to_tensor(batch, dtype=tf.float32) / 255.0
|
||||
logging.info('Predict: Input tensor shape %s', input_tensor.shape)
|
||||
predictions = model({'image': input_tensor})
|
||||
logging.info('Running softmax on predictions...')
|
||||
predictions = tf.nn.softmax(predictions, axis=1)
|
||||
return predictions.numpy()
|
||||
|
||||
def get_prediction(
|
||||
self,
|
||||
model: Any,
|
||||
batch_size: int,
|
||||
fps: float,
|
||||
num_frames: int,
|
||||
overlap_frames: int,
|
||||
objective: str,
|
||||
) -> Sequence[np.ndarray]:
|
||||
"""Predicts the video clip with the model.
|
||||
|
||||
Args:
|
||||
model: The loaded MoViNet model.
|
||||
batch_size: Batch size for prediction.
|
||||
fps: Video sampling FPS.
|
||||
num_frames: Number of frames in a single predictions. If the model is
|
||||
exported with a fixed input shape, this must match its num_frames
|
||||
dimension.
|
||||
overlap_frames: Number of overlapping frames of consecutive sliding
|
||||
windows.
|
||||
objective: A string `vcn` or `var`.
|
||||
|
||||
Returns:
|
||||
A list of floats as the prediction response.
|
||||
|
||||
Raises:
|
||||
IOError: The video fails to load.
|
||||
ValueError: Some arguments are invalid.
|
||||
"""
|
||||
if objective not in [
|
||||
constants.OBJECTIVE_VIDEO_CLASSIFICATION,
|
||||
constants.OBJECTIVE_VIDEO_ACTION_RECOGNITION,
|
||||
]:
|
||||
raise ValueError(f'{objective} objective is not supported.')
|
||||
|
||||
# cv2 expects a local path so we need to download the video from GCS.
|
||||
local_file_path = fileutils.generate_tmp_path(
|
||||
os.path.splitext(self._gcs_uri)[1]
|
||||
)
|
||||
logging.info('Downloading %s to %s...', self._gcs_uri, local_file_path)
|
||||
fileutils.download_gcs_file_to_local(self._gcs_uri, local_file_path)
|
||||
logging.info('Download %s complete.', self._gcs_uri)
|
||||
|
||||
# Loads video.
|
||||
video = cv2.VideoCapture(local_file_path)
|
||||
total_frames = video.get(cv2.CAP_PROP_FRAME_COUNT)
|
||||
original_fps = video.get(cv2.CAP_PROP_FPS)
|
||||
if not original_fps:
|
||||
# 0 or None indicates the video is invalid.
|
||||
raise IOError(f'Failed to load {self._gcs_uri}.')
|
||||
video_length = total_frames / original_fps
|
||||
self._start_time = max(0, self._start_time)
|
||||
self._end_time = min(video_length, self._end_time)
|
||||
padding = (
|
||||
(num_frames // 2)
|
||||
if objective == constants.OBJECTIVE_VIDEO_ACTION_RECOGNITION
|
||||
else 0
|
||||
)
|
||||
|
||||
# Computes sliding windows.
|
||||
frame_indices = _sample_frame_indices(
|
||||
self._start_time,
|
||||
self._end_time,
|
||||
original_fps,
|
||||
fps,
|
||||
total_frames,
|
||||
padding,
|
||||
padding,
|
||||
)
|
||||
logging.info('Frame indices: %s', frame_indices)
|
||||
self.windows = _get_sliding_windows(
|
||||
frame_indices,
|
||||
original_fps,
|
||||
num_frames,
|
||||
overlap_frames,
|
||||
objective != 'var',
|
||||
)
|
||||
if not self.windows:
|
||||
raise ValueError(
|
||||
f'No sliding windows found from {self._start_time} to'
|
||||
f' {self._end_time}.'
|
||||
)
|
||||
self._last_window = None
|
||||
|
||||
# Runs inference.
|
||||
predictions = []
|
||||
for i in range(0, len(self.windows), batch_size):
|
||||
predictions.extend(
|
||||
self._predict(model, video, self.windows[i : i + batch_size])
|
||||
)
|
||||
return predictions
|
||||
|
||||
|
||||
def parse_request(req_json: Any) -> VideoPredictionExecutor:
|
||||
"""Parses VideoPredictionExecutor from request JSON object.
|
||||
|
||||
Args:
|
||||
req_json: Request JSON object.
|
||||
|
||||
Returns:
|
||||
Parsed VideoPredictionExecutor.
|
||||
|
||||
Raises:
|
||||
ValueError: Request JSON object is invalid.
|
||||
"""
|
||||
for key in _JSON_REQUIRED_KEYS:
|
||||
if key not in req_json:
|
||||
raise ValueError(f'{key} not found in {req_json}.')
|
||||
gcs_uri = req_json[_JSON_GCS_URI_KEY]
|
||||
start_time = float(req_json[_JSON_START_TIME_KEY].removesuffix('s'))
|
||||
end_time = float(req_json[_JSON_END_TIME_KEY].removesuffix('s'))
|
||||
return VideoPredictionExecutor(gcs_uri, start_time, end_time)
|
||||
|
||||
|
||||
def postprocess_vcn(predictions: Sequence[np.ndarray]) -> Sequence[float]:
|
||||
"""Aggregates VCN predictions of sliding windows."""
|
||||
return np.mean(predictions, axis=0).tolist()
|
||||
|
||||
|
||||
def temporal_nonmaximal_suppression(
|
||||
detections: Sequence[DetectionOutput], min_gap_time: float
|
||||
) -> Sequence[DetectionOutput]:
|
||||
"""Nonmaximal suppression for key frame detection.
|
||||
|
||||
For consecutive packets of the same label within a pre-defined duration, we
|
||||
only keep the one with the highest confidence score. Such duration can be
|
||||
determined by performing data analysis on users' dataset.
|
||||
|
||||
Args:
|
||||
detections: A list of DetectionOutputs.
|
||||
min_gap_time: Minimum time between consecutive key frames of the same label
|
||||
in seconds.
|
||||
|
||||
Returns:
|
||||
DetectionOutput after nonmaximal suppression sorted in ascending timestamps.
|
||||
"""
|
||||
max_label = max([detection.label for detection in detections])
|
||||
prev_detections: list[Optional[DetectionOutput]] = [None] * (max_label + 1)
|
||||
ret: list[DetectionOutput] = []
|
||||
by_time = lambda x: x.timestamp
|
||||
for detection in sorted(detections, key=by_time):
|
||||
prev_detection = prev_detections[detection.label]
|
||||
prev_detections[detection.label] = detection
|
||||
if not prev_detection:
|
||||
continue
|
||||
if detection.timestamp - prev_detection.timestamp > min_gap_time:
|
||||
ret.append(prev_detection)
|
||||
continue
|
||||
detection.confidence = max(detection.confidence, prev_detection.confidence)
|
||||
ret.extend((d for d in prev_detections if d is not None))
|
||||
return sorted(ret, key=by_time)
|
||||
|
||||
|
||||
def postprocess_var(
|
||||
windows: Sequence[SlidingWindow],
|
||||
predictions: Sequence[np.ndarray],
|
||||
confidence_threshold: float,
|
||||
min_gap_time: float,
|
||||
) -> Sequence[Dict[str, Any]]:
|
||||
"""Generates a list of detected keyframes from sliding window predictions.
|
||||
|
||||
Args:
|
||||
windows: Sliding windows.
|
||||
predictions: A list of predictions of sliding windows.
|
||||
confidence_threshold: Only probabilities greater than this threshold will
|
||||
contribute to the final result.
|
||||
min_gap_time: Minimum time between consecutive key frames of the same label
|
||||
in seconds. Used in temporal nonmaximal suppression.
|
||||
|
||||
Returns:
|
||||
A sequence of dictionaries, each item has the following keys:
|
||||
- label: Integer label of the detection result.
|
||||
- timeSegmentStart: Start timestamp in seconds.
|
||||
- timeSegmentEnd: End timestamp in seconds. Always equals timeSegmentStart.
|
||||
"""
|
||||
if len(windows) != len(predictions):
|
||||
raise ValueError('Mismatched # of windows with # of predictions.')
|
||||
|
||||
# Creates detection results from windows, filtering out the background label.
|
||||
detections = [
|
||||
create_detection_output(window.middle_timestamp, predictions[i])
|
||||
for i, window in enumerate(windows)
|
||||
]
|
||||
|
||||
# Temporal nonmaximal suppression.
|
||||
detections = temporal_nonmaximal_suppression(detections, min_gap_time)
|
||||
|
||||
# Filters out ones with low confidence and the background label.
|
||||
return [
|
||||
x.to_json_obj()
|
||||
for x in detections
|
||||
if x.label != _BACKGROUND_LABEL and x.confidence > confidence_threshold
|
||||
]
|
||||
@@ -0,0 +1,210 @@
|
||||
"""Main executable for MoViNet docker."""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Sequence, Any
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
from absl import logging
|
||||
import gin
|
||||
import hypertune
|
||||
import tensorflow as tf
|
||||
|
||||
from util import constants
|
||||
from util import hypertune_utils
|
||||
from official.common import distribute_utils
|
||||
from official.common import flags as tfm_flags
|
||||
from official.core import task_factory
|
||||
from official.core import train_lib
|
||||
from official.core import train_utils
|
||||
from official.modeling import performance
|
||||
# Import movinet libraries to register the backbone and model into tf.vision
|
||||
# model garden factory.
|
||||
# pylint: disable=unused-import
|
||||
from official.projects.movinet.modeling import movinet
|
||||
from official.projects.movinet.modeling import movinet_model
|
||||
from official.vision import registry_imports
|
||||
# pylint: enable=unused-import
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
_FILE_TYPE_TFRECORD = 'tfrecord'
|
||||
|
||||
_LEARNING_RATE = flags.DEFINE_float(
|
||||
'learning_rate', None, 'The learning rate of this training job.'
|
||||
)
|
||||
|
||||
_NUM_CLASSES = flags.DEFINE_integer(
|
||||
'num_classes', None, 'The number of classes.'
|
||||
)
|
||||
|
||||
_INIT_CHECKPOINT = flags.DEFINE_string(
|
||||
'init_checkpoint', None, 'The initial checkpoint of this training job.'
|
||||
)
|
||||
|
||||
_INPUT_TRAIN_DATA_PATH = flags.DEFINE_string(
|
||||
'input_train_data_path', None, 'Input train data path.'
|
||||
)
|
||||
|
||||
_INPUT_VALIDATION_DATA_PATH = flags.DEFINE_string(
|
||||
'input_validation_data_path', None, 'Input validation data path.'
|
||||
)
|
||||
|
||||
_GLOBAL_BATCH_SIZE = flags.DEFINE_integer(
|
||||
'global_batch_size', None, 'Global batch size.'
|
||||
)
|
||||
|
||||
_PREFETCH_BUFFER_SIZE = flags.DEFINE_integer(
|
||||
'prefetch_buffer_size', None, 'Prefetch buffer size.'
|
||||
)
|
||||
|
||||
_SHUFFLE_BUFFER_SIZE = flags.DEFINE_integer(
|
||||
'shuffle_buffer_size', None, 'Shuffle buffer size.'
|
||||
)
|
||||
|
||||
_TRAIN_STEPS = flags.DEFINE_integer('train_steps', None, 'Train steps.')
|
||||
_LOG_LEVEL = flags.DEFINE_enum(
|
||||
'log_level',
|
||||
'INFO',
|
||||
['FATAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'],
|
||||
'Log level.',
|
||||
)
|
||||
|
||||
|
||||
def parse_params() -> Any:
|
||||
"""Parses parameters."""
|
||||
gin.parse_config_files_and_bindings(FLAGS.gin_file, FLAGS.gin_params)
|
||||
params = train_utils.parse_configuration(FLAGS, lock_return=False)
|
||||
if _INIT_CHECKPOINT.value:
|
||||
params.task.init_checkpoint = _INIT_CHECKPOINT.value
|
||||
params.task.init_checkpoint_modules = 'backbone'
|
||||
if _NUM_CLASSES.value:
|
||||
params.task.model.num_classes = _NUM_CLASSES.value
|
||||
params.task.train_data.num_classes = _NUM_CLASSES.value
|
||||
params.task.validation_data.num_classes = _NUM_CLASSES.value
|
||||
# If users set input train/validation data path, we assume the data are
|
||||
# converted from data converter as tfrecord. Users can use tfds by writing
|
||||
# their own config directly, and no need to override this parameter.
|
||||
if _INPUT_TRAIN_DATA_PATH.value:
|
||||
params.task.train_data.input_path = _INPUT_TRAIN_DATA_PATH.value
|
||||
params.task.train_data.file_type = _FILE_TYPE_TFRECORD
|
||||
params.task.train_data.tfds_name = ''
|
||||
if _INPUT_VALIDATION_DATA_PATH.value:
|
||||
params.task.validation_data.input_path = _INPUT_VALIDATION_DATA_PATH.value
|
||||
params.task.validation_data.file_type = _FILE_TYPE_TFRECORD
|
||||
params.task.validation_data.tfds_name = ''
|
||||
if _GLOBAL_BATCH_SIZE.value:
|
||||
params.task.train_data.global_batch_size = _GLOBAL_BATCH_SIZE.value
|
||||
params.task.validation_data.global_batch_size = _GLOBAL_BATCH_SIZE.value
|
||||
if _PREFETCH_BUFFER_SIZE.value:
|
||||
params.task.train_data.prefetch_buffer_size = _PREFETCH_BUFFER_SIZE.value
|
||||
params.task.validation_data.prefetch_buffer_size = (
|
||||
_PREFETCH_BUFFER_SIZE.value
|
||||
)
|
||||
if _SHUFFLE_BUFFER_SIZE.value:
|
||||
params.task.train_data.shuffle_buffer_size = _SHUFFLE_BUFFER_SIZE.value
|
||||
if _TRAIN_STEPS.value:
|
||||
params.trainer.train_steps = _TRAIN_STEPS.value
|
||||
if _LEARNING_RATE.value:
|
||||
logging.info('Updating learning_rate: %s', _LEARNING_RATE.value)
|
||||
# Use `get` method of train_utils.hyperparams.OneOfConfig to get learning
|
||||
# rate config.
|
||||
learning_rate = params.trainer.optimizer_config.learning_rate.get()
|
||||
if hasattr(learning_rate, 'initial_learning_rate'):
|
||||
learning_rate.initial_learning_rate = _LEARNING_RATE.value
|
||||
else:
|
||||
logging.warning('Cannot set learning rate for %s', learning_rate)
|
||||
# Set default params for best checkpoints.
|
||||
params.trainer.best_checkpoint_export_subdir = constants.BEST_CKPT_DIRNAME
|
||||
params.trainer.best_checkpoint_metric_comp = constants.BEST_CKPT_METRIC_COMP
|
||||
params.trainer.best_checkpoint_eval_metric = (
|
||||
constants.VIDEO_CLASSIFICATION_BEST_EVAL_METRIC
|
||||
)
|
||||
return params
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> None:
|
||||
logging.set_verbosity(_LOG_LEVEL.value)
|
||||
if len(argv) > 1:
|
||||
raise app.UsageError('Too many command-line arguments.')
|
||||
params = parse_params()
|
||||
logging.info('The actual training parameters are:\n%s', params.as_dict())
|
||||
model_dir: str = os.path.join(
|
||||
FLAGS.model_dir,
|
||||
constants.TRIAL_PREFIX + hypertune_utils.get_trial_id_from_environment(),
|
||||
)
|
||||
logging.info('model_dir: %s', model_dir)
|
||||
|
||||
if 'train' in FLAGS.mode:
|
||||
# Pure eval modes do not output yaml files. Otherwise continuous eval job
|
||||
# may race against the train job for writing the same file.
|
||||
train_utils.serialize_config(params, model_dir)
|
||||
|
||||
# Sets mixed_precision policy. Using 'mixed_float16' or 'mixed_bfloat16'
|
||||
# can have significant impact on model speeds by utilizing float16 in case of
|
||||
# GPUs, and bfloat16 in the case of TPUs. loss_scale takes effect only when
|
||||
# dtype is float16
|
||||
if params.runtime.mixed_precision_dtype:
|
||||
performance.set_mixed_precision_policy(params.runtime.mixed_precision_dtype)
|
||||
distribution_strategy = distribute_utils.get_distribution_strategy(
|
||||
distribution_strategy=params.runtime.distribution_strategy,
|
||||
all_reduce_alg=params.runtime.all_reduce_alg,
|
||||
num_gpus=params.runtime.num_gpus,
|
||||
tpu_address=params.runtime.tpu,
|
||||
)
|
||||
|
||||
# Create task and run experiment.
|
||||
with distribution_strategy.scope():
|
||||
task = task_factory.get_task(params.task, logging_dir=model_dir)
|
||||
|
||||
train_lib.run_experiment(
|
||||
distribution_strategy=distribution_strategy,
|
||||
task=task,
|
||||
mode=FLAGS.mode,
|
||||
params=params,
|
||||
model_dir=model_dir,
|
||||
)
|
||||
|
||||
train_utils.save_gin_config(FLAGS.mode, model_dir)
|
||||
|
||||
eval_metric_name = constants.VIDEO_CLASSIFICATION_BEST_EVAL_METRIC
|
||||
|
||||
eval_filepath = os.path.join(
|
||||
model_dir, constants.BEST_CKPT_DIRNAME, constants.BEST_CKPT_EVAL_FILENAME
|
||||
)
|
||||
logging.info('Load eval metrics from: %s.', eval_filepath)
|
||||
|
||||
with tf.io.gfile.GFile(eval_filepath, 'rb') as f:
|
||||
eval_metric_results = json.load(f)
|
||||
logging.info('eval metrics are: %s.', eval_metric_results)
|
||||
if (
|
||||
eval_metric_name in eval_metric_results
|
||||
and constants.BEST_CKPT_STEP_NAME in eval_metric_results
|
||||
):
|
||||
hp_metric = eval_metric_results[eval_metric_name]
|
||||
hp_step = int(eval_metric_results[constants.BEST_CKPT_STEP_NAME])
|
||||
hpt = hypertune.HyperTune()
|
||||
hpt.report_hyperparameter_tuning_metric(
|
||||
hyperparameter_metric_tag=constants.HP_METRIC_TAG,
|
||||
metric_value=hp_metric,
|
||||
global_step=hp_step,
|
||||
)
|
||||
logging.info(
|
||||
'Send HP metric: %f and steps %d to hyperparameter tuning.',
|
||||
hp_metric,
|
||||
hp_step,
|
||||
)
|
||||
else:
|
||||
logging.info(
|
||||
'Either %s or %s is not included in the evaluation results: %s.',
|
||||
eval_metric_name,
|
||||
constants.BEST_CKPT_STEP_NAME,
|
||||
eval_metric_results,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
tfm_flags.define_flags()
|
||||
app.run(main)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# Dockerfile for basic serving dockers for OpenCLIP.
|
||||
#
|
||||
# To build:
|
||||
# docker build -f model_oss/open_clip/dockerfile/serve.Dockerfile . -t ${YOUR_IMAGE_TAG}
|
||||
#
|
||||
# To push to gcr:
|
||||
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
|
||||
# Switch to this base image for gpu serve.
|
||||
FROM pytorch/torchserve:0.7.1-gpu
|
||||
|
||||
USER root
|
||||
|
||||
# Install tools.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
wget \
|
||||
vim
|
||||
|
||||
# Copy license.
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
ENV infer_port=7080
|
||||
ENV mng_port=7081
|
||||
ENV model_name="transformers_serving"
|
||||
ENV PATH="/home/model-server/:${PATH}"
|
||||
|
||||
# Install libraries.
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN pip install torch==1.13.1
|
||||
RUN pip install open_clip_torch==2.20.0
|
||||
RUN pip install pillow==9.5.0
|
||||
RUN pip install google-cloud-storage==2.7.0
|
||||
|
||||
# Copy model artifacts.
|
||||
COPY model_oss/open_clip/handler.py /home/model-server/handler.py
|
||||
COPY model_oss/util/ /home/model-server/util/
|
||||
ENV PYTHONPATH /home/model-server/
|
||||
|
||||
# Create torchserve configuration file.
|
||||
RUN echo \
|
||||
"default_response_timeout=1800\n" \
|
||||
"service_envelope=json\n" \
|
||||
"inference_address=http://0.0.0.0:${infer_port}\n" \
|
||||
"management_address=http://0.0.0.0:${mng_port}" >> /home/model-server/config.properties
|
||||
|
||||
# Expose ports.
|
||||
EXPOSE ${infer_port}
|
||||
EXPOSE ${mng_port}
|
||||
|
||||
# Archive model artifacts and dependencies.
|
||||
# Do not set --model-file and --serialized-file because model and checkpoint will be dynamically loaded in handler.py.
|
||||
RUN torch-model-archiver \
|
||||
--model-name=${model_name} \
|
||||
--version=1.0 \
|
||||
--handler=/home/model-server/handler.py \
|
||||
--runtime=python3 \
|
||||
--export-path=/home/model-server/model-store \
|
||||
--archive-format=default \
|
||||
--force
|
||||
|
||||
# Run Torchserve HTTP serve to respond to prediction requests.
|
||||
CMD ["torchserve", "--start", \
|
||||
"--ts-config", "/home/model-server/config.properties", \
|
||||
"--models", "${model_name}=${model_name}.mar", \
|
||||
"--model-store", "/home/model-server/model-store"]
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# Dockerfile for training dockers with OpenCLIP.
|
||||
#
|
||||
# To build:
|
||||
# docker build -f model_oss/open_clilp/dockerfile/train.Dockerfile . -t ${YOUR_IMAGE_TAG}
|
||||
#
|
||||
# To push to gcr:
|
||||
# docker tag ${YOUR_IMAGE_TAG} gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
# docker push gcr.io/${YOUR_PROJECT}/${YOUR_IMAGE_TAG}
|
||||
|
||||
FROM pytorch/pytorch:2.0.0-cuda11.7-cudnn8-devel
|
||||
|
||||
# Install tools.
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y --no-install-recommends apt-utils
|
||||
RUN apt-get install -y --no-install-recommends curl
|
||||
RUN apt-get install -y --no-install-recommends wget
|
||||
RUN apt-get install -y --no-install-recommends git
|
||||
RUN apt-get install -y --no-install-recommends jq
|
||||
RUN apt-get install -y --no-install-recommends gnupg
|
||||
RUN apt-get install -y --no-install-recommends build-essential
|
||||
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
wget \
|
||||
vim
|
||||
|
||||
# Copy license.
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
# Prepare artifacts.
|
||||
WORKDIR /workspace
|
||||
RUN git clone --branch main https://github.com/mlfoundations/open_clip.git
|
||||
WORKDIR ./open_clip
|
||||
RUN git reset --hard 67e5e5ec8741281eb9b30f640c26f91c666308b7
|
||||
|
||||
# Install libraries.
|
||||
RUN pip install webdataset==0.2.5
|
||||
RUN pip install regex==2023.6.3
|
||||
RUN pip install ftfy==6.1.1
|
||||
RUN pip install pandas==2.0.3
|
||||
RUN pip install braceexpand==0.1.7
|
||||
RUN pip install huggingface_hub==0.16.4
|
||||
RUN pip install transformers==4.31.0
|
||||
RUN pip install timm==0.9.2
|
||||
RUN pip install fsspec==2023.6.0
|
||||
RUN pip install sentencepiece==0.1.99
|
||||
RUN pip install protobuf==3.20.3
|
||||
RUN pip install tensorboard==2.12.2
|
||||
|
||||
# Switch work folder for training.
|
||||
WORKDIR ./src
|
||||
@@ -0,0 +1,142 @@
|
||||
"""Custom handler for OpenCLIP model."""
|
||||
|
||||
# pylint:disable=g-importing-member
|
||||
import enum
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import open_clip
|
||||
import torch
|
||||
from ts.torch_handler.base_handler import BaseHandler
|
||||
|
||||
from google3.cloud.ml.applications.vision.model_garden.model_oss.util import constants
|
||||
from google3.cloud.ml.applications.vision.model_garden.model_oss.util import fileutils
|
||||
from google3.cloud.ml.applications.vision.model_garden.model_oss.util import image_format_converter
|
||||
|
||||
|
||||
@enum.unique
|
||||
class Precision(enum.Enum):
|
||||
AMP = "amp"
|
||||
AMP_BF16 = "amp_bf16"
|
||||
AMP_BFLOAT16 = "amp_bfloat16"
|
||||
BF16 = "bf16"
|
||||
FP16 = "fp16"
|
||||
PURE_BF16 = "pure_bf16"
|
||||
PURE_FP16 = "pure_fp16"
|
||||
FP32 = "fp32"
|
||||
|
||||
|
||||
# Supported checkpoint&model pairs:
|
||||
# https://github.com/mlfoundations/open_clip#pretrained-model-interface
|
||||
_DEFAULT_CHECKPOINT = "openai"
|
||||
_DEFAULT_MODEL = "RN50"
|
||||
_DEFAULT_PRECISION = Precision.AMP
|
||||
_ZERO_CLASSIFICATION = "zero-shot-image-classification"
|
||||
_FEATURE_EMBEDDING = "feature-embedding"
|
||||
_VALID_TASKS = frozenset([_ZERO_CLASSIFICATION, _FEATURE_EMBEDDING])
|
||||
|
||||
_IMAGE_KEY = "image"
|
||||
_TEXT_KEY = "text"
|
||||
_IMAGE_FEATURES_KEY = "image_features"
|
||||
_TEXT_FEATURES_KEY = "text_features"
|
||||
|
||||
|
||||
class OpenclipHandler(BaseHandler):
|
||||
"""Custom handler for OpenCLIP."""
|
||||
|
||||
def initialize(self, context: Any):
|
||||
"""Custom initialize."""
|
||||
|
||||
properties = context.system_properties
|
||||
self.map_location = (
|
||||
"cuda"
|
||||
if torch.cuda.is_available() and properties.get("gpu_id") is not None
|
||||
else "cpu"
|
||||
)
|
||||
self.device = torch.device(
|
||||
self.map_location + ":" + str(properties.get("gpu_id"))
|
||||
if torch.cuda.is_available() and properties.get("gpu_id") is not None
|
||||
else self.map_location
|
||||
)
|
||||
self.manifest = context.manifest
|
||||
|
||||
model_name = os.environ.get("MODEL", _DEFAULT_MODEL)
|
||||
precision = os.environ.get("PRECISION", _DEFAULT_PRECISION)
|
||||
checkpoint = os.environ.get("CHECKPOINT", _DEFAULT_CHECKPOINT)
|
||||
self.task = os.environ.get("TASK", _FEATURE_EMBEDDING)
|
||||
if self.task not in _VALID_TASKS:
|
||||
raise ValueError(f"Invalid task: {self.task}.")
|
||||
logging.info(
|
||||
"Handler initializing task:%s, model:%s, precision:%s, checkpoint:%s",
|
||||
self.task,
|
||||
model_name,
|
||||
precision,
|
||||
checkpoint,
|
||||
)
|
||||
|
||||
if checkpoint != _DEFAULT_CHECKPOINT:
|
||||
local_fname = os.path.join(constants.LOCAL_MODEL_DIR, "model.pt")
|
||||
fileutils.download_gcs_file_to_local(checkpoint, local_fname)
|
||||
checkpoint = local_fname
|
||||
|
||||
self.model, _, self.preprocessor = open_clip.create_model_and_transforms(
|
||||
model_name, pretrained=checkpoint, precision=precision
|
||||
)
|
||||
self.tokenizer = open_clip.get_tokenizer(model_name)
|
||||
|
||||
self.initialized = True
|
||||
|
||||
def preprocess(self, data: Any) -> List[Dict[str, Any]]:
|
||||
"""Preprocess input data."""
|
||||
logging.info("preprocessing: %d instances received.", len(data))
|
||||
processed_list = []
|
||||
for item in data:
|
||||
sample = {}
|
||||
if _IMAGE_KEY in item:
|
||||
sample[_IMAGE_KEY] = self.preprocessor(
|
||||
image_format_converter.base64_to_image(item[_IMAGE_KEY])
|
||||
).unsqueeze(0)
|
||||
if _TEXT_KEY in item:
|
||||
sample[_TEXT_KEY] = self.tokenizer(item[_TEXT_KEY])
|
||||
processed_list.append(sample)
|
||||
return processed_list
|
||||
|
||||
def inference(
|
||||
self, data: List[Dict[str, Any]], *args, **kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
feature_list = []
|
||||
with torch.no_grad(), torch.cuda.amp.autocast():
|
||||
for item in data:
|
||||
sample = {}
|
||||
if _IMAGE_KEY in item:
|
||||
sample[_IMAGE_FEATURES_KEY] = self.model.encode_image(
|
||||
item[_IMAGE_KEY]
|
||||
)
|
||||
if _TEXT_KEY in item:
|
||||
sample[_TEXT_FEATURES_KEY] = self.model.encode_text(item[_TEXT_KEY])
|
||||
feature_list.append(sample)
|
||||
return feature_list
|
||||
|
||||
def postprocess(self, features: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Postprocess the image/text featreus for downstream task."""
|
||||
preds = []
|
||||
if self.task == _FEATURE_EMBEDDING:
|
||||
for item in features:
|
||||
preds.append({k: v.tolist() for k, v in item.items()})
|
||||
elif self.task == _ZERO_CLASSIFICATION:
|
||||
for item in features:
|
||||
image_features = item.get(_IMAGE_FEATURES_KEY, None)
|
||||
text_features = item.get(_TEXT_FEATURES_KEY, None)
|
||||
if image_features is None or text_features is None:
|
||||
raise ValueError(
|
||||
"Missing input for {} task. {} received.".format(
|
||||
_ZERO_CLASSIFICATION, item.keys()
|
||||
)
|
||||
)
|
||||
image_features /= image_features.norm(dim=-1, keepdim=True)
|
||||
text_features /= text_features.norm(dim=-1, keepdim=True)
|
||||
text_probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)
|
||||
preds.append(text_probs.tolist())
|
||||
|
||||
return preds
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
FROM pytorch/torchserve:0.7.1-gpu
|
||||
|
||||
USER root
|
||||
|
||||
ENV infer_port=7080
|
||||
ENV mng_port=7081
|
||||
ENV model_name="pic2word"
|
||||
ENV PATH="/home/model-server/:${PATH}"
|
||||
|
||||
# Copy license.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
wget
|
||||
|
||||
RUN wget https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/LICENSE
|
||||
|
||||
# Install dependencies.
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN pip install google-cloud-storage==2.7.0
|
||||
RUN pip install open_clip_torch==2.20.0
|
||||
RUN pip install numpy==1.22.0
|
||||
RUN pip install scikit-image==0.21.0
|
||||
RUN pip install scikit-learn==1.0.2
|
||||
RUN pip install torch==2.0.0
|
||||
RUN pip install torchvision==0.15.2
|
||||
RUN pip install tensorboard==2.13.0
|
||||
RUN pip install ase==3.21.1
|
||||
RUN pip install braceexpand==0.1.7
|
||||
RUN pip install cached-property==1.5.2
|
||||
RUN pip install configparser==5.0.2
|
||||
RUN pip install cycler==0.10.0
|
||||
RUN pip install decorator==4.4.2
|
||||
RUN pip install docker-pycreds==0.4.0
|
||||
RUN pip install gitdb==4.0.7
|
||||
RUN pip install gitpython==3.1.30
|
||||
RUN pip install googledrivedownloader==0.4
|
||||
RUN pip install h5py==3.1.0
|
||||
RUN pip install isodate==0.6.0
|
||||
RUN pip install jinja2==3.0.1
|
||||
RUN pip install kiwisolver==1.3.1
|
||||
RUN pip install littleutils==0.2.2
|
||||
RUN pip install llvmlite==0.36.0
|
||||
RUN pip install markupsafe==2.0.1
|
||||
RUN pip install matplotlib==3.3.4
|
||||
RUN pip install networkx==2.5.1
|
||||
RUN pip install numba==0.53.1
|
||||
RUN pip install ogb==1.3.1
|
||||
RUN pip install outdated==0.2.1
|
||||
RUN pip install pathtools==0.1.2
|
||||
RUN pip install promise==2.3
|
||||
RUN pip install psutil==5.8.0
|
||||
RUN pip install pyarrow==4.0.0
|
||||
RUN pip install pyparsing==2.4.7
|
||||
RUN pip install python-louvain==0.15
|
||||
RUN pip install pyyaml==5.4.1
|
||||
RUN pip install rdflib==5.0.0
|
||||
RUN pip install sentry-sdk==1.14.0
|
||||
RUN pip install shortuuid==1.0.1
|
||||
RUN pip install sklearn==0.0
|
||||
RUN pip install smmap==4.0.0
|
||||
RUN pip install subprocess32==3.5.4
|
||||
RUN pip install torch-geometric==1.7.0
|
||||
RUN pip install wandb==0.10.30
|
||||
RUN pip install wilds==1.1.0
|
||||
RUN pip install ftfy==6.1.1
|
||||
RUN pip install regex==2023.6.3
|
||||
RUN pip install webdataset==0.2.48
|
||||
RUN pip install requests==2.31.0
|
||||
RUN pip install hydra-core==1.3.2
|
||||
RUN pip install omegaconf==2.3.0
|
||||
RUN pip install fairseq==0.10.0
|
||||
RUN pip install bitarray==2.7.6
|
||||
|
||||
# Get 'composed_image_retrieval' repository from github.
|
||||
RUN git clone https://github.com/google-research/composed_image_retrieval
|
||||
# Set workdir to composed_image_retrieval.
|
||||
WORKDIR ./composed_image_retrieval
|
||||
# Using git reset command to pin it down to a specific version.
|
||||
RUN git reset --hard 8c053297c2fae9cd17ddcded48445a4f47208dbd
|
||||
|
||||
# Fix issue introduced by installing composed_image_retrieval
|
||||
# https://github.com/huggingface/transformers/issues/8638#issuecomment-790772391
|
||||
RUN pip uninstall dataclasses -y
|
||||
|
||||
# Copy model artifacts.
|
||||
COPY model_oss/pic2word/handler.py /home/model-server/handler.py
|
||||
|
||||
# Create torchserve configuration file.
|
||||
RUN echo \
|
||||
"default_response_timeout=1800\n" \
|
||||
"service_envelope=json\n" \
|
||||
"inference_address=http://0.0.0.0:${infer_port}\n" \
|
||||
"management_address=http://0.0.0.0:${mng_port}" >> /home/model-server/config.properties
|
||||
|
||||
# Expose ports.
|
||||
EXPOSE ${infer_port}
|
||||
EXPOSE ${mng_port}
|
||||
|
||||
# Archive model artifacts and dependencies.
|
||||
# Do not set --model-file and --serialized-file because model and checkpoint
|
||||
# will be dynamically loaded in handler.py.
|
||||
RUN torch-model-archiver \
|
||||
--model-name=${model_name} \
|
||||
--version=1.0 \
|
||||
--handler=/home/model-server/handler.py \
|
||||
--runtime=python3 \
|
||||
--export-path=/home/model-server/model-store \
|
||||
--archive-format=default \
|
||||
--force
|
||||
|
||||
# Run Torchserve HTTP serve to respond to prediction requests.
|
||||
CMD ["torchserve", "--start", \
|
||||
"--ts-config", "/home/model-server/config.properties", \
|
||||
"--models", "${model_name}=${model_name}.mar", \
|
||||
"--model-store", "/home/model-server/model-store"]
|
||||
@@ -0,0 +1,167 @@
|
||||
"""Custom handler for Pic2Word."""
|
||||
|
||||
from argparse import Namespace # pylint: disable=g-importing-member
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from absl import logging
|
||||
from data import CustomFolder
|
||||
from eval_utils import visualize_results
|
||||
from model.clip import load
|
||||
from model.model import convert_weights
|
||||
from model.model import IM2TEXT
|
||||
from params import get_project_root
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
from ts.torch_handler.base_handler import BaseHandler
|
||||
|
||||
from util import fileutils
|
||||
|
||||
# The COCO dataset is stored in a publicly accessible bucket.
|
||||
_COCO_STORAGE_DIR = "gs://pic2word-bucket/data/coco/"
|
||||
_COCO_LOCAL_DIR = "/home/model-server/composed_image_retrieval/data/coco/"
|
||||
_COCO_VAL2017_PATH = "coco/val2017"
|
||||
_COCO_DATASET_NAME = "coco"
|
||||
_MODEL_NAME = "ViT-L/14"
|
||||
_LOCAL_QUERY_PATH = "./query/"
|
||||
_IMAGE_OUTPUT_LOCAL_DIR = "demo_out/images"
|
||||
_OUTPUT_LOCAL_DIR = "/demo_out/"
|
||||
_DATA_DIR = "data"
|
||||
_CHECKPOINT_DIR = "checkpoint/pic2word_model.pt"
|
||||
_REQUEST_PROMPTS = "prompts"
|
||||
_REQUEST_OUTPUT_STORAGE_DIR = "output_storage_dir"
|
||||
_REQUEST_IMAGE_PATH = "image_path"
|
||||
_REQUEST_IMAGE_FILE_NAME = "image_file_name"
|
||||
_RESPONSE_MSG = "Successfully retrieved images."
|
||||
|
||||
|
||||
class ModelHandler(BaseHandler):
|
||||
"""A custom model handler implementation."""
|
||||
|
||||
def __init__(self):
|
||||
self.initialized = False
|
||||
self.gpu = 0
|
||||
self.model = None
|
||||
self.dataloader = None
|
||||
self.prompt = None
|
||||
self.output_storage_dir = None
|
||||
|
||||
def initialize(self, context: Any):
|
||||
"""Initialize."""
|
||||
logging.info("Initializing pic2word.")
|
||||
|
||||
# Download COCO dataset. The model looks for this folder specifically
|
||||
# during image retrieval to generate a response for each request.
|
||||
# This is a publicly accessible bucket.
|
||||
fileutils.download_gcs_dir_to_local(
|
||||
_COCO_STORAGE_DIR,
|
||||
_COCO_LOCAL_DIR,
|
||||
)
|
||||
|
||||
# Load the model.
|
||||
|
||||
self.initialized = True
|
||||
|
||||
torch.cuda.set_device(self.gpu)
|
||||
model, _, preprocess_val = load(_MODEL_NAME, jit=False)
|
||||
|
||||
img2text = IM2TEXT(
|
||||
embed_dim=model.embed_dim,
|
||||
output_dim=model.token_embedding.weight.shape[1],
|
||||
)
|
||||
|
||||
model.cuda(self.gpu)
|
||||
img2text.cuda(self.gpu)
|
||||
|
||||
convert_weights(model)
|
||||
convert_weights(img2text)
|
||||
|
||||
self.model = model
|
||||
self.img2text = img2text
|
||||
|
||||
# Load the dataset
|
||||
logging.info("Loading dataset.")
|
||||
|
||||
root_project = os.path.join(get_project_root(), _DATA_DIR)
|
||||
dataset = CustomFolder(
|
||||
os.path.join(root_project, _COCO_VAL2017_PATH), transform=preprocess_val
|
||||
)
|
||||
|
||||
# Initialize the dataloader. This is used to create the pickle file from
|
||||
# the dataset.
|
||||
dataloader = DataLoader(
|
||||
dataset,
|
||||
batch_size=64,
|
||||
shuffle=False,
|
||||
num_workers=1,
|
||||
pin_memory=True,
|
||||
drop_last=False,
|
||||
)
|
||||
|
||||
self.dataloader = dataloader
|
||||
|
||||
logging.info("Finished initializing Pic2Word server.")
|
||||
|
||||
def preprocess(self, data: Any) -> str:
|
||||
"""Preprocess input data."""
|
||||
logging.info("Preprocessing Pic2Word inference request.")
|
||||
query = data[0]
|
||||
|
||||
self.output_storage_dir = query[_REQUEST_OUTPUT_STORAGE_DIR]
|
||||
prompts = query[_REQUEST_PROMPTS]
|
||||
prompts = prompts.split(",")
|
||||
self.prompt = prompts
|
||||
|
||||
image_path = query[_REQUEST_IMAGE_PATH]
|
||||
# The query image is only supported via GCS bucket upload.
|
||||
fileutils.download_gcs_dir_to_local(image_path, _LOCAL_QUERY_PATH)
|
||||
image_file_name = query[_REQUEST_IMAGE_FILE_NAME]
|
||||
|
||||
query_file = f"./query/{image_file_name}"
|
||||
|
||||
logging.info("Setting model args.")
|
||||
|
||||
args = {
|
||||
"openai-pretrained": True,
|
||||
"resume": _CHECKPOINT_DIR,
|
||||
"retrieval_data": _COCO_DATASET_NAME,
|
||||
"query_file": query_file,
|
||||
"demo_out": _OUTPUT_LOCAL_DIR,
|
||||
"prompts": prompts,
|
||||
"distributed": False,
|
||||
"dp": False,
|
||||
"gpu": 0,
|
||||
"model": _MODEL_NAME,
|
||||
"world_size": 1,
|
||||
}
|
||||
model_input = Namespace(**args)
|
||||
|
||||
logging.info("Finished preprocessing Pic2Word inference request.")
|
||||
return model_input
|
||||
|
||||
def inference(self, model_input: Any):
|
||||
"""Runs inference."""
|
||||
logging.info("Running model-inference.")
|
||||
visualize_results(
|
||||
model=self.model,
|
||||
img2text=self.img2text,
|
||||
args=model_input,
|
||||
prompt=self.prompt,
|
||||
dataloader=self.dataloader,
|
||||
)
|
||||
|
||||
def postprocess(self):
|
||||
"""Upload the output images to the bucket."""
|
||||
logging.info("Running request postprocess.")
|
||||
fileutils.upload_local_dir_to_gcs(
|
||||
_IMAGE_OUTPUT_LOCAL_DIR, self.output_storage_dir
|
||||
)
|
||||
|
||||
def handle(self, data: Any, context: Any) -> str: # pylint: disable=unused-argument
|
||||
"""Runs preprocess, inference, and post-processing."""
|
||||
logging.info("Received Pic2Word inference request")
|
||||
model_input = self.preprocess(data)
|
||||
self.inference(model_input)
|
||||
self.postprocess()
|
||||
logging.info("Done handling input.")
|
||||
return _RESPONSE_MSG
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Common utility lib for prediction on images."""
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import tensorflow as tf
|
||||
import yaml
|
||||
|
||||
from util import image_format_converter
|
||||
|
||||
|
||||
def get_prediction_instances(image: Image.Image) -> List[Dict[str, Any]]:
|
||||
"""Gets prediction instances.
|
||||
|
||||
Args:
|
||||
image: Image instance.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: List of prediction instances.
|
||||
"""
|
||||
instances = [{
|
||||
"encoded_image": {"b64": image_format_converter.image_to_base64(image)},
|
||||
}]
|
||||
return instances
|
||||
|
||||
|
||||
def get_label_map(label_map_yaml_filepath: str) -> Dict[str, Any]:
|
||||
"""Gets the label map from a YAML file.
|
||||
|
||||
Args:
|
||||
label_map_yaml_filepath: Filepath to the label map YAML file.
|
||||
|
||||
Returns:
|
||||
dict: Label map.
|
||||
"""
|
||||
with tf.io.gfile.GFile(label_map_yaml_filepath, "rb") as input_file:
|
||||
label_map = yaml.safe_load(input_file.read())
|
||||
return label_map
|
||||
|
||||
|
||||
def get_object_detection_endpoint_predictions(
|
||||
detection_endpoint: ...,
|
||||
input_image: np.ndarray,
|
||||
detection_thresh: float = 0.2,
|
||||
) -> np.ndarray:
|
||||
"""Gets endpoint predictions.
|
||||
|
||||
Args:
|
||||
detection_endpoint: image object detection endpoint.
|
||||
input_image: Input image.
|
||||
detection_thresh: Detection threshold.
|
||||
|
||||
Returns:
|
||||
Object detection predictions from endpoints.
|
||||
"""
|
||||
height, width, _ = input_image.shape
|
||||
predictions = detection_endpoint.predict(
|
||||
get_prediction_instances(Image.fromarray(input_image))
|
||||
).predictions
|
||||
detection_scores = np.array(predictions[0]["detection_scores"])
|
||||
detection_classes = np.array(predictions[0]["detection_classes"])
|
||||
detection_boxes = np.array(
|
||||
[
|
||||
[b[1] * width, b[0] * height, b[3] * width, b[2] * height]
|
||||
for b in predictions[0]["detection_boxes"]
|
||||
]
|
||||
)
|
||||
thresh_indices = [
|
||||
x for x, val in enumerate(detection_scores) if val > detection_thresh
|
||||
]
|
||||
preds_merge_conf = np.column_stack((
|
||||
detection_boxes[thresh_indices],
|
||||
detection_scores[thresh_indices],
|
||||
))
|
||||
preds_merge_cls = np.column_stack(
|
||||
(preds_merge_conf, detection_classes[thresh_indices])
|
||||
)
|
||||
return preds_merge_cls
|
||||
@@ -4,6 +4,8 @@
|
||||
OBJECTIVE_IMAGE_CLASSIFICATION = 'icn'
|
||||
OBJECTIVE_IMAGE_OBJECT_DETECTION = 'iod'
|
||||
OBJECTIVE_IMAGE_SEGMENTATION = 'isg'
|
||||
OBJECTIVE_VIDEO_CLASSIFICATION = 'vcn'
|
||||
OBJECTIVE_VIDEO_ACTION_RECOGNITION = 'var'
|
||||
|
||||
# Input file types.
|
||||
INPUT_FILE_TYPE_CSV = 'csv'
|
||||
@@ -61,4 +63,11 @@ GCSFUSE_URI_PREFIX = '/gcs/'
|
||||
|
||||
LOCAL_EVALUATION_RESULT_DIR = '/tmp/evaluation_result_dir'
|
||||
LOCAL_MODEL_DIR = '/tmp/model_dir'
|
||||
LOCAL_BASE_MODEL_DIR = '/tmp/base_model_dir'
|
||||
LOCAL_DATA_DIR = '/tmp/data'
|
||||
|
||||
# PEFT finetuning constants.
|
||||
TEXT_TO_IMAGE_LORA = 'text-to-image-lora'
|
||||
SEQUENCE_CLASSIFICATION_LORA = 'sequence-classification-lora'
|
||||
CAUSAL_LANGUAGE_MODELING_LORA = 'causal-language-modeling-lora'
|
||||
INSTRUCT_LORA = 'instruct-lora'
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
import glob
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
from typing import Tuple
|
||||
import uuid
|
||||
|
||||
from absl import logging
|
||||
from google.cloud import storage
|
||||
@@ -9,6 +13,44 @@ from google.cloud import storage
|
||||
from util import constants
|
||||
|
||||
|
||||
def generate_tmp_path(extension: str = '') -> str:
|
||||
"""Generates a temporary file path with UUID.
|
||||
|
||||
Args:
|
||||
extension: File extension, e.g. '.jpg', '.avi'. If not given, no extension
|
||||
will be appended to the filename.
|
||||
|
||||
Returns:
|
||||
Generated file path.
|
||||
"""
|
||||
return os.path.join(constants.LOCAL_DATA_DIR, uuid.uuid1().hex) + extension
|
||||
|
||||
|
||||
def force_gcs_fuse_path(gcs_uri: str) -> str:
|
||||
"""Converts gs:// uris to their /gcs/ equivalents. No-op for other uris."""
|
||||
if is_gcs_path(gcs_uri):
|
||||
return (
|
||||
constants.GCSFUSE_URI_PREFIX + gcs_uri[len(constants.GCS_URI_PREFIX) :]
|
||||
)
|
||||
else:
|
||||
return gcs_uri
|
||||
|
||||
|
||||
def download_gcs_file_to_local_dir(gcs_uri: str, local_dir: str):
|
||||
"""Download a gcs file to a local dir.
|
||||
|
||||
Args:
|
||||
gcs_uri: A string of file path on GCS.
|
||||
local_dir: A string of local directory.
|
||||
"""
|
||||
if not is_gcs_path(gcs_uri):
|
||||
raise ValueError(
|
||||
f'{gcs_uri} is not a GCS path starting with {constants.GCS_URI_PREFIX}.'
|
||||
)
|
||||
filename = os.path.basename(gcs_uri)
|
||||
download_gcs_file_to_local(gcs_uri, os.path.join(local_dir, filename))
|
||||
|
||||
|
||||
def download_gcs_file_to_local(gcs_uri: str, local_path: str):
|
||||
"""Download a gcs file to a local path.
|
||||
|
||||
@@ -16,7 +58,7 @@ def download_gcs_file_to_local(gcs_uri: str, local_path: str):
|
||||
gcs_uri: A string of file path on GCS.
|
||||
local_path: A string of local file path.
|
||||
"""
|
||||
if not gcs_uri.startswith(constants.GCS_URI_PREFIX):
|
||||
if not is_gcs_path(gcs_uri):
|
||||
raise ValueError(
|
||||
f'{gcs_uri} is not a GCS path starting with {constants.GCS_URI_PREFIX}.'
|
||||
)
|
||||
@@ -38,6 +80,8 @@ def download_gcs_dir_to_local(gcs_dir: str, local_dir: str):
|
||||
gcs_dir: A string of directory path on GCS.
|
||||
local_dir: A string of local directory path.
|
||||
"""
|
||||
if not is_gcs_path(gcs_dir):
|
||||
raise ValueError(f'{gcs_dir} is not a GCS path starting with gs://.')
|
||||
bucket_name = gcs_dir.split('/')[2]
|
||||
prefix = gcs_dir[len(constants.GCS_URI_PREFIX + bucket_name) :].strip('/')
|
||||
client = storage.Client()
|
||||
@@ -77,3 +121,126 @@ def upload_local_dir_to_gcs(local_dir: str, gcs_dir: str):
|
||||
)
|
||||
blob = bucket.blob(os.path.join(blob_dir, os.path.basename(local_file)))
|
||||
blob.upload_from_filename(local_file)
|
||||
|
||||
|
||||
def upload_file_to_gcs_path(
|
||||
source_path: str,
|
||||
destination_uri: str,
|
||||
):
|
||||
"""Uploads local files to GCS uri.
|
||||
|
||||
After upload the destination_uri will contain the same data as the
|
||||
source_path.
|
||||
|
||||
Args:
|
||||
source_path: Required. Path of the local data to copy to GCS.
|
||||
destination_uri: Required. GCS URI where the data should be uploaded.
|
||||
|
||||
Raises:
|
||||
RuntimeError: When source_path does not exist.
|
||||
GoogleCloudError: When the upload process fails.
|
||||
"""
|
||||
source_path_obj = pathlib.Path(source_path)
|
||||
if not source_path_obj.exists():
|
||||
raise RuntimeError(f'Source path does not exist: {source_path}')
|
||||
|
||||
storage_client = storage.Client()
|
||||
source_file_path = source_path
|
||||
destination_file_uri = destination_uri
|
||||
logging.info('Uploading "%s" to "%s"', source_file_path, destination_file_uri)
|
||||
destination_blob = storage.Blob.from_string(
|
||||
destination_file_uri, client=storage_client
|
||||
)
|
||||
destination_blob.upload_from_filename(filename=source_file_path)
|
||||
|
||||
|
||||
def is_gcs_path(input_path: str) -> bool:
|
||||
"""Checks if the input path is a Google Cloud Storage (GCS) path.
|
||||
|
||||
Args:
|
||||
input_path: The input path to be checked.
|
||||
|
||||
Returns:
|
||||
True if the input path is a GCS path, False otherwise.
|
||||
"""
|
||||
return input_path.startswith(constants.GCS_URI_PREFIX)
|
||||
|
||||
|
||||
def release_text_assets(
|
||||
output_bucket: str, local_text_file_name: str, remote_text_file_name: str
|
||||
) -> None:
|
||||
"""Releases text assets.
|
||||
|
||||
Args:
|
||||
output_bucket: gcs output bucket.
|
||||
local_text_file_name: Local text file name.
|
||||
remote_text_file_name: Remote text file name.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
remote_file_path = '{}/{}'.format(output_bucket, remote_text_file_name)
|
||||
logging.info('Uploading "%s" to "%s"', local_text_file_name, remote_file_path)
|
||||
upload_file_to_gcs_path(local_text_file_name, remote_file_path)
|
||||
os.remove(local_text_file_name)
|
||||
|
||||
|
||||
def upload_video_from_local_to_gcs(
|
||||
output_bucket: str,
|
||||
local_video_file_name: str,
|
||||
remote_video_file_name: str,
|
||||
temp_local_video_file_name: str,
|
||||
) -> None:
|
||||
"""Uploads video from local to gcs buckent and releases video assets.
|
||||
|
||||
Args:
|
||||
output_bucket: GCS bucket address.
|
||||
local_video_file_name: Local video file name.
|
||||
remote_video_file_name: Remote video file name.
|
||||
temp_local_video_file_name: Temporary local video file name.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
upload_file_to_gcs_path(
|
||||
temp_local_video_file_name,
|
||||
'{}/{}'.format(output_bucket, remote_video_file_name),
|
||||
)
|
||||
shutil.rmtree(local_video_file_name, ignore_errors=True)
|
||||
shutil.rmtree(temp_local_video_file_name, ignore_errors=True)
|
||||
|
||||
|
||||
def download_video_from_gcs_to_local(video_file_path: str) -> Tuple[str, str]:
|
||||
"""Downloads video from gcs to local folders.
|
||||
|
||||
Args:
|
||||
video_file_path: Path to the video file.
|
||||
|
||||
Returns:
|
||||
Local and remote video file paths.
|
||||
"""
|
||||
_, local_video_file_name = os.path.split(video_file_path)
|
||||
file_extension = os.path.splitext(video_file_path)[1]
|
||||
remote_video_file_name = local_video_file_name.replace(
|
||||
file_extension, '_overlay.mp4'
|
||||
)
|
||||
local_file_path = generate_tmp_path(os.path.splitext(video_file_path)[1])
|
||||
logging.info('Downloading %s to %s...', video_file_path, local_file_path)
|
||||
download_gcs_file_to_local(video_file_path, local_file_path)
|
||||
return local_file_path, remote_video_file_name
|
||||
|
||||
|
||||
def get_output_video_file(video_output_file_path: str) -> str:
|
||||
"""Gets the output video file name for writing video.
|
||||
|
||||
Args:
|
||||
video_output_file_path: Path to the video output file.
|
||||
|
||||
Returns:
|
||||
str: Local video output file path.
|
||||
"""
|
||||
file_extension = os.path.splitext(video_output_file_path)[1]
|
||||
out_local_video_file_name = video_output_file_path.replace(
|
||||
file_extension, '_overlay' + file_extension
|
||||
)
|
||||
return out_local_video_file_name
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
"""Utility functions for Vertex Hyperparameter Tuning Jobs."""
|
||||
|
||||
import os
|
||||
|
||||
from absl import logging
|
||||
|
||||
|
||||
_ENVIRONMENT_VARIABLE_FOR_TRIAL_ID = 'CLOUD_ML_TRIAL_ID'
|
||||
|
||||
|
||||
def get_trial_id_from_environment() -> str:
|
||||
"""Gets the trial id from environment variable.
|
||||
|
||||
Returns:
|
||||
The trial id from environement or '0' if not found.
|
||||
"""
|
||||
if _ENVIRONMENT_VARIABLE_FOR_TRIAL_ID not in os.environ:
|
||||
logging.warning(
|
||||
'Environment variable %s not found, return 0 as default trial id.',
|
||||
_ENVIRONMENT_VARIABLE_FOR_TRIAL_ID,
|
||||
)
|
||||
return os.environ.get(_ENVIRONMENT_VARIABLE_FOR_TRIAL_ID, '0')
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
"""Video format converter util lib."""
|
||||
|
||||
import io
|
||||
from typing import Sequence
|
||||
import imageio
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def frames_to_video_bytes(frames: Sequence[np.ndarray], fps: int) -> bytes:
|
||||
images = [Image.fromarray(array) for array in frames]
|
||||
io_obj = io.BytesIO()
|
||||
imageio.mimsave(io_obj, images, format=".mp4", fps=fps)
|
||||
return io_obj.getvalue()
|
||||
@@ -44,7 +44,9 @@
|
||||
/notebooks/community/feature_store/get_started_vertex_feature_store.ipynb @junkourata
|
||||
/notebooks/community/model_garden/model_garden_huggingface_local_inference.ipynb @dstnluong-google
|
||||
/notebooks/community/model_garden/model_garden_mediapipe_image_classification.ipynb @schmidt-sebastian
|
||||
/notebooks/community/model_garden/model_garden_mediapipe_gesture_recognition.ipynb @schmidt-sebastian
|
||||
/notebooks/community/model_garden/model_garden_mediapipe_object_detection.ipynb @schmidt-sebastian
|
||||
/notebooks/community/model_garden/model_garden_mediapipe_text_classification.ipynb @schmidt-sebastian
|
||||
/notebooks/community/model_garden/model_garden_proprietary_image_classification.ipynb @weigary
|
||||
/notebooks/community/model_garden/model_garden_proprietary_image_object_detection.ipynb @weigary
|
||||
/notebooks/community/model_garden/model_garden_tfvision_image_classification.ipynb @genquan9
|
||||
@@ -67,10 +69,17 @@
|
||||
/notebooks/community/model_garden/model_garden_pytorch_dolly_v2.ipynb @lavraicse
|
||||
/notebooks/community/model_garden/model_garden_pytorch_bart_large_cnn.ipynb @lavraicse
|
||||
/notebooks/community/model_garden/model_garden_jax_vision_transformer.ipynb @lavraicse
|
||||
/notebooks/community/model_garden/model_garden_jax_fvlm.ipynb @lavraicse
|
||||
/notebooks/community/model_garden/model_garden_pytorch_text_to_video_zero_shot.ipynb @bingatgoogle
|
||||
/notebooks/community/model_garden/model_garden_pytorch_text_to_video.ipynb @KCFindstr
|
||||
/notebooks/community/generative_ai/text_embedding_api_semantic_search_with_scann.ipynb @henrytansetiawan
|
||||
/notebooks/community/bigquery_ml_inference/bq_ml_with_vision_translation_nlp.ipynb @deaconsmith
|
||||
/notebooks/community/model_garden/model_garden_keras_stable_diffusion.ipynb @genquan9
|
||||
/notebooks/community/model_garden/model_garden_pytorch_sam.ipynb @huguensjean
|
||||
/notebooks/community/model_garden/model_garden_pytorch_pic2word.ipynb @jismailyan
|
||||
/notebooks/community/model_garden/model_garden_pytorch_peft.ipynb @genquan9
|
||||
/notebooks/community/model_garden/model_garden_pytorch_openllama_peft.ipynb @genquan9
|
||||
/notebooks/community/model_garden/model_garden_pytorch_falcon_instruct_peft.ipynb @genquan9
|
||||
/notebooks/community/model_garden/model_garden_movinet_clip_classification.ipynb @KCFindstr
|
||||
/notebooks/community/model_garden/model_garden_pytorch_open_clip.ipynb @lydhr
|
||||
/notebooks/community/model_garden/model_garden_pytorch_llama2_peft.ipynb @genquan9
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,924 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "ur8xi4C7S06n"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "99c1c3fc2ca5"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden - JAX F-VLM\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_jax_f_vlm.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",
|
||||
"\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_jax_f_vlm.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> <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_jax_f_vlm.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>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "24743cf4a1e1"
|
||||
},
|
||||
"source": [
|
||||
"**_NOTE_**: This notebook has been tested in the following environment:\n",
|
||||
"\n",
|
||||
"* Python version = 3.9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "tvgnzT1CKxrO"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates serving a [JAX F-VLM model](https://github.com/google-research/google-research/tree/master/fvlm) for [open-vocabulary object detection](https://arxiv.org/abs/2209.15639) task and deploying them on Vertex AI for online prediction."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "d975e698c9a4"
|
||||
},
|
||||
"source": [
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"In this tutorial, you learn how to:\n",
|
||||
"\n",
|
||||
"- Upload the model to [Model Registry](https://cloud.google.com/vertex-ai/docs/model-registry/introduction).\n",
|
||||
"- Deploy the model on [Endpoint](https://cloud.google.com/vertex-ai/docs/predictions/using-private-endpoints).\n",
|
||||
"- Run online predictions for image classification.\n",
|
||||
"\n",
|
||||
"This tutorial uses the following Google Cloud ML services and resources:\n",
|
||||
"\n",
|
||||
"- Vertex AI Model Registry\n",
|
||||
"- Vertex AI Online Prediction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "08d289fa873f"
|
||||
},
|
||||
"source": [
|
||||
"### Dataset\n",
|
||||
"\n",
|
||||
"This notebook uses the following prediction image as an example:\n",
|
||||
"\n",
|
||||
"Image: https://pixabay.com/nl/photos/het-fruit-eten-citroen-limoen-3134631/\n",
|
||||
"\n",
|
||||
"Creative Commons License: https://pixabay.com/nl/service/terms/\n",
|
||||
"\n",
|
||||
"You can use your own custom prediction image as well as by modifying the `DEMO_IMAGE_PATH` variable in this notebook below."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "aed92deeb4a0"
|
||||
},
|
||||
"source": [
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "i7EUnXsZhAGF"
|
||||
},
|
||||
"source": [
|
||||
"## Installation\n",
|
||||
"\n",
|
||||
"Install the following packages required to execute this notebook."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "2b4ef9b72d43"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Install the packages.\n",
|
||||
"! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
"# Get F-VLM repository by using svn to avoid downloading entire google-research repository.\n",
|
||||
"! apt install subversion\n",
|
||||
"! rm -rf ./fvlm\n",
|
||||
"! svn export -r 59152 https://github.com/google-research/google-research/trunk/fvlm\n",
|
||||
"# Note: The following libraries are pinned down versions of:\n",
|
||||
"# https://github.com/google-research/google-research/blob/master/fvlm/requirements.txt\n",
|
||||
"! pip3 install tensorflow==2.12.0\n",
|
||||
"! pip3 install numpy==1.23.5\n",
|
||||
"! pip3 install jax==0.4.14\n",
|
||||
"! pip3 install jaxlib==0.4.14+cuda11.cudnn86\n",
|
||||
"! pip3 install flax==0.7.1\n",
|
||||
"! pip3 install torch==2.0.1+cu118\n",
|
||||
"! pip3 install torchvision==0.15.2+cu118\n",
|
||||
"! pip3 install opencv-python==4.7.0.72\n",
|
||||
"! pip3 install tqdm==4.65.0\n",
|
||||
"! pip3 install git+https://github.com/openai/CLIP.git@a1d071733d7111c9c014f024669f959182114e33\n",
|
||||
"! pip3 install Pillow==9.5.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "_9q83As4G2Yn"
|
||||
},
|
||||
"source": [
|
||||
"Download the F-VLM checkpoints into the `fvlm/checkpoints` folder."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "eXVI6s57FPyg"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%cd fvlm/checkpoints\n",
|
||||
"! ./download.sh\n",
|
||||
"%cd ../../"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "58707a750154"
|
||||
},
|
||||
"source": [
|
||||
"### Colab only"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "f200f10a1da3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Automatically restart kernel after installs so that your environment can access the new packages.\n",
|
||||
"import IPython\n",
|
||||
"\n",
|
||||
"app = IPython.Application.instance()\n",
|
||||
"app.kernel.do_shutdown(True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
},
|
||||
"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 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 need to install the [Cloud SDK](https://cloud.google.com/sdk).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**If you don't know your project ID**, try the following:\n",
|
||||
"* Run `gcloud config list`.\n",
|
||||
"* Run `gcloud projects list`.\n",
|
||||
"* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"#### Region\n",
|
||||
"\n",
|
||||
"You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "twgKk-LsLmX3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "sBCra4QMA2wR"
|
||||
},
|
||||
"source": [
|
||||
"### Authenticate your Google Cloud account\n",
|
||||
"\n",
|
||||
"Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "74ccc9e52986"
|
||||
},
|
||||
"source": [
|
||||
"**1. Vertex AI Workbench**\n",
|
||||
"* Do nothing as you are already authenticated."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "de775a3773ba"
|
||||
},
|
||||
"source": [
|
||||
"**2. Local JupyterLab instance, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "254614fa0c46"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ! gcloud auth login"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ef21552ccea8"
|
||||
},
|
||||
"source": [
|
||||
"**3. Colab, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "603adbbf0532"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# from google.colab import auth\n",
|
||||
"# auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "f6b2ccc891ed"
|
||||
},
|
||||
"source": [
|
||||
"**4. Service account or other**\n",
|
||||
"* See how to grant Cloud Storage permissions to your service account at https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\n",
|
||||
"\n",
|
||||
"Create a storage bucket to store intermediate artifacts such as datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "MzGDU7TWdts_"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-EcIXiGsCePi"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NIq7R4HZCfIc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "960505627ddf"
|
||||
},
|
||||
"source": [
|
||||
"### Import libraries"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "PyQmSRbKA8r-"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import base64\n",
|
||||
"import functools\n",
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"from io import BytesIO\n",
|
||||
"\n",
|
||||
"import jax\n",
|
||||
"import numpy as np\n",
|
||||
"import tensorflow as tf\n",
|
||||
"import tqdm\n",
|
||||
"from PIL import Image\n",
|
||||
"\n",
|
||||
"sys.path.append(\"./fvlm\")\n",
|
||||
"import inputs\n",
|
||||
"import jax_clip\n",
|
||||
"import utils\n",
|
||||
"from google.cloud import aiplatform\n",
|
||||
"from google.protobuf import json_format\n",
|
||||
"from google.protobuf.struct_pb2 import Value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "init_aip:mbsdk,all"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI SDK for Python\n",
|
||||
"\n",
|
||||
"Initialize the Vertex AI SDK for Python for your project."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "vS1hQiGuLmX4"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"staging_bucket = os.path.join(BUCKET_URI, \"jax_fvlm_staging\")\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=staging_bucket)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2cc825514deb"
|
||||
},
|
||||
"source": [
|
||||
"### Define constants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "b42bd4fa2b2d"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built prediction docker image.\n",
|
||||
"OPTIMIZED_TF_RUNTIME_IMAGE_URI = (\n",
|
||||
" \"us-docker.pkg.dev/vertex-ai-restricted/prediction/tf_opt-cpu.nightly:latest\"\n",
|
||||
")\n",
|
||||
"# The local path to the F-VLM folder.\n",
|
||||
"F_VLM_FOLDER = \"./fvlm\"\n",
|
||||
"# The F-VLM model to use. Choose between 'resnet_50', 'resnet_50x4', or 'resnet_50x16'.\n",
|
||||
"MODEL = \"resnet_50\"\n",
|
||||
"# The list of object categories to detect. For example: \"person, car, oven\".\n",
|
||||
"CATEGORIES = [\n",
|
||||
" \"kiwi\",\n",
|
||||
" \"orange\",\n",
|
||||
" \"lemon\",\n",
|
||||
" \"blackberry\",\n",
|
||||
" \"pine cone\",\n",
|
||||
" \"red orange\",\n",
|
||||
" \"table\",\n",
|
||||
" \"spoon\",\n",
|
||||
" \"pine needles\",\n",
|
||||
" \"seed\",\n",
|
||||
"]\n",
|
||||
"# An upper bound on the number of classes.\n",
|
||||
"MAX_NUM_CLS = 91\n",
|
||||
"# The max number of boxes to draw on the output image.\n",
|
||||
"MAX_BOXES_TO_DRAW = 25\n",
|
||||
"# The minimum score required to draw a detected object.\n",
|
||||
"MIN_SCORE_THRESH = 0.2 # @param {type:\"slider\", min:0, max:0.9, step:0.05}\n",
|
||||
"# The local path to the output image.\n",
|
||||
"OUTPUT_IMAGE_PATH = \"./output.jpg\"\n",
|
||||
"# The original F-VLM SavedModel folder which takes image and text embeddings as inputs.\n",
|
||||
"SAVED_MODEL_DIR = f'{F_VLM_FOLDER}/checkpoints/{MODEL.replace(\"resnet_\",\"r\")}'\n",
|
||||
"# The converted SavedModel folder which takes jpeg bytes and text-embeddings bytes as inputs.\n",
|
||||
"CONVERTED_SAVED_MODEL_DIR = \"./converted_saved_model\"\n",
|
||||
"# The Cloud Storage location for the converted SavedModel.\n",
|
||||
"GCS_CONVERTED_SAVED_MODEL_DIR = f\"{BUCKET_URI}/fvlm_saved_model\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "0c250872074f"
|
||||
},
|
||||
"source": [
|
||||
"### Define common functions\n",
|
||||
"\n",
|
||||
"This section defines functions for:\n",
|
||||
"\n",
|
||||
"- Loading and converting input image into the required prediction format.\n",
|
||||
"- Visualization of detection outputs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "XcYUGwr-AJGY"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def convert_numpy_array_to_byte_string_via_tf_tensor(np_array):\n",
|
||||
" \"\"\"Serializes a numpy array to tensor bytes.\"\"\"\n",
|
||||
" tensor_array = tf.convert_to_tensor(np_array)\n",
|
||||
" tensor_byte_string = tf.io.serialize_tensor(tensor_array)\n",
|
||||
" return tensor_byte_string.numpy()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generate_text_embeddings(categories):\n",
|
||||
" \"\"\"Generates text embeddings in numpy format from object categories.\"\"\"\n",
|
||||
" clip_text_fn = jax_clip.get_clip_text_fn(MODEL)\n",
|
||||
" class_clip_features = []\n",
|
||||
" print(\"Computing custom category text embeddings.\")\n",
|
||||
" for cls_name in tqdm.tqdm(categories, total=len(categories)):\n",
|
||||
" cls_feat = clip_text_fn(cls_name)\n",
|
||||
" class_clip_features.append(cls_feat)\n",
|
||||
" text_embeddings = np.concatenate(class_clip_features, axis=0)\n",
|
||||
" embed_path = (\n",
|
||||
" f'{F_VLM_FOLDER}/data/{MODEL.replace(\"resnet_\", \"r\")}_bg_empty_embed.npy'\n",
|
||||
" )\n",
|
||||
" background_embedding, empty_embeddings = np.load(embed_path)\n",
|
||||
" background_embedding = background_embedding[np.newaxis, Ellipsis]\n",
|
||||
" empty_embeddings = empty_embeddings[np.newaxis, Ellipsis]\n",
|
||||
" tile_empty_embeddings = np.tile(\n",
|
||||
" empty_embeddings, (MAX_NUM_CLS - len(categories) - 1, 1)\n",
|
||||
" )\n",
|
||||
" # Concatenate 'background' and 'empty' embeddings.\n",
|
||||
" text_embeddings = np.concatenate(\n",
|
||||
" (background_embedding, text_embeddings, tile_empty_embeddings), axis=0\n",
|
||||
" )\n",
|
||||
" return text_embeddings\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_jpeg_bytes(local_image_path, new_width=-1):\n",
|
||||
" \"\"\"Returns jpeg bytes given an image path and resizes if required.\"\"\"\n",
|
||||
" image = Image.open(local_image_path)\n",
|
||||
" if new_width <= 0:\n",
|
||||
" new_image = image\n",
|
||||
" else:\n",
|
||||
" width, height = image.size\n",
|
||||
" print(\"original input image size: \", width, \" , \", height)\n",
|
||||
" new_height = int(height * new_width / width)\n",
|
||||
" print(\"new input image size: \", new_width, \" , \", new_height)\n",
|
||||
" new_image = image.resize((new_width, new_height))\n",
|
||||
" buffered = BytesIO()\n",
|
||||
" new_image.save(buffered, format=\"JPEG\")\n",
|
||||
" return buffered.getvalue()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generate_prediction_output_image(\n",
|
||||
" input_image_path, prediction_output, output_image_path\n",
|
||||
"):\n",
|
||||
" \"\"\"Generates prediction output image with detected objects and bounding boxes.\"\"\"\n",
|
||||
" # Generate tensors from prediction outputs.\n",
|
||||
" prediction_output_tensor = {}\n",
|
||||
" for key, val in prediction_output.items():\n",
|
||||
" prediction_output_tensor[key] = tf.expand_dims(\n",
|
||||
" tf.convert_to_tensor(val), axis=0\n",
|
||||
" )\n",
|
||||
" prediction_output_tensor[\"num_detections\"] = tf.cast(\n",
|
||||
" prediction_output_tensor[\"num_detections\"], tf.int32\n",
|
||||
" )\n",
|
||||
" # Generate image embeddings for the input image.\n",
|
||||
" with open(input_image_path, \"rb\") as f:\n",
|
||||
" np_image = np.array(Image.open(f))\n",
|
||||
" parser_fn = inputs.get_maskrcnn_parser()\n",
|
||||
" data = parser_fn({\"image\": np_image, \"source_id\": np.array([0])})\n",
|
||||
" np_data = jax.tree_map(lambda x: x.numpy()[np.newaxis, Ellipsis], data)\n",
|
||||
" image_embeddings = np_data.pop(\"images\")\n",
|
||||
" labels = np_data.pop(\"labels\")\n",
|
||||
" # Generate visualization.\n",
|
||||
" print(\"Preparing visualization.\")\n",
|
||||
" categories = CATEGORIES\n",
|
||||
" id_mapping = {(i + 1): c for i, c in enumerate(categories)}\n",
|
||||
" id_mapping[0] = \"background\"\n",
|
||||
" for k in range(len(categories) + 2, MAX_NUM_CLS):\n",
|
||||
" id_mapping[k] = \"empty\"\n",
|
||||
" category_index = inputs.get_category_index(id_mapping)\n",
|
||||
" maskrcnn_visualizer_fn = functools.partial(\n",
|
||||
" utils.visualize_boxes_and_labels_on_image_array,\n",
|
||||
" category_index=category_index,\n",
|
||||
" use_normalized_coordinates=False,\n",
|
||||
" max_boxes_to_draw=MAX_BOXES_TO_DRAW,\n",
|
||||
" min_score_thresh=MIN_SCORE_THRESH,\n",
|
||||
" skip_labels=False,\n",
|
||||
" )\n",
|
||||
" vis_image = utils.visualize_instance_segmentations(\n",
|
||||
" prediction_output_tensor,\n",
|
||||
" image_embeddings,\n",
|
||||
" labels[\"image_info\"],\n",
|
||||
" maskrcnn_visualizer_fn,\n",
|
||||
" )\n",
|
||||
" pil_vis_image = Image.fromarray(vis_image, mode=\"RGB\")\n",
|
||||
" pil_vis_image.save(output_image_path)\n",
|
||||
" print(\"Completed saving the output image at: \", output_image_path)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ayNrua2txk0B"
|
||||
},
|
||||
"source": [
|
||||
"# Convert F-VLM SavedModel to support smaller input size\n",
|
||||
"\n",
|
||||
"The F-VLM SavedModel takes image embeddings and text embeddings as input. But you can not send these inputs directly for Vertex AI Online Prediction because there is a limit of 1.5 MB on the prediction request size. So you will first convert the SavedModel format to take jpeg bytes and text-embeddings bytes as an input instead. This modified input format will meet the 1.5 MB limit requirement."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "ef-svu2Ix1OA"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def preprocess_jpeg_byte_string(tensor_byte_string):\n",
|
||||
" \"\"\"Converts jpeg bytes to image embeddings as an input for the original F-VLM SavedModel.\"\"\"\n",
|
||||
" decoded_image_tensor = tf.io.decode_jpeg(tensor_byte_string, channels=3)\n",
|
||||
" parser_fn = inputs.get_maskrcnn_parser()\n",
|
||||
" parser_output = parser_fn({\"image\": decoded_image_tensor})\n",
|
||||
" image_embeddings_tensor = parser_output[\"images\"]\n",
|
||||
" return image_embeddings_tensor\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def preprocess_text_embeddings_byte_string(tensor_byte_string):\n",
|
||||
" \"\"\"Converts text-embeddings bytes to text-embeddings as an input for the original F-VLM SavedModel.\"\"\"\n",
|
||||
" return tf.io.parse_tensor(tensor_byte_string, tf.float32)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_serve_fn(model):\n",
|
||||
" \"\"\"Creates a serving function for the modified SavedModel which takes jpeg bytes and text-embeddings bytes as an input.\"\"\"\n",
|
||||
"\n",
|
||||
" @tf.function(\n",
|
||||
" input_signature=[\n",
|
||||
" tf.TensorSpec([None], tf.string),\n",
|
||||
" tf.TensorSpec([None], tf.string),\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" def serve_fn(image_jpeg_bytes_inputs, text_embeddings_bytes_inputs):\n",
|
||||
" image_embeddings_tensor = tf.map_fn(\n",
|
||||
" preprocess_jpeg_byte_string, image_jpeg_bytes_inputs, dtype=tf.bfloat16\n",
|
||||
" )\n",
|
||||
" text_embeddings_tensor = tf.map_fn(\n",
|
||||
" preprocess_text_embeddings_byte_string,\n",
|
||||
" text_embeddings_bytes_inputs,\n",
|
||||
" dtype=tf.float32,\n",
|
||||
" )\n",
|
||||
" return model({\"image\": image_embeddings_tensor, \"text\": text_embeddings_tensor})\n",
|
||||
"\n",
|
||||
" return serve_fn\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"! rm -rf {CONVERTED_SAVED_MODEL_DIR}\n",
|
||||
"model = tf.saved_model.load(SAVED_MODEL_DIR)\n",
|
||||
"signatures = {\n",
|
||||
" \"serving_default\": get_serve_fn(model=model).get_concrete_function(\n",
|
||||
" tf.TensorSpec(shape=[None], dtype=tf.string), tf.TensorSpec([None], tf.string)\n",
|
||||
" )\n",
|
||||
"}\n",
|
||||
"tf.saved_model.save(model, CONVERTED_SAVED_MODEL_DIR, signatures=signatures)\n",
|
||||
"print(\"Saved the converted SavedModel to directory: \", CONVERTED_SAVED_MODEL_DIR)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "1cJQEETi1jsg"
|
||||
},
|
||||
"source": [
|
||||
"Copy the local converted TF SavedModel to Cloud Storage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "6hlTWKxh11dF"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil -m rm -R -f {GCS_CONVERTED_SAVED_MODEL_DIR}\n",
|
||||
"! gsutil -m cp -R {CONVERTED_SAVED_MODEL_DIR} {GCS_CONVERTED_SAVED_MODEL_DIR}\n",
|
||||
"! gsutil ls {GCS_CONVERTED_SAVED_MODEL_DIR}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "iILhhP3TfO8B"
|
||||
},
|
||||
"source": [
|
||||
"## Run online prediction\n",
|
||||
"Run online prediction with the converted TF SavedModel."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ExIyCnKf3a94"
|
||||
},
|
||||
"source": [
|
||||
"Upload TF SavedModel and deploy it to an endpoint for prediction. This step can take up to 15 minutes to finish."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "t0xYDT0BxP0W"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"jax_fvlm_model = aiplatform.Model.upload(\n",
|
||||
" display_name=\"jax_fvlm\",\n",
|
||||
" artifact_uri=GCS_CONVERTED_SAVED_MODEL_DIR,\n",
|
||||
" serving_container_image_uri=OPTIMIZED_TF_RUNTIME_IMAGE_URI,\n",
|
||||
" serving_container_args=[],\n",
|
||||
" location=REGION,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"jax_fvlm_endpoint = jax_fvlm_model.deploy(\n",
|
||||
" deployed_model_display_name=\"jax_vlm_deployed\",\n",
|
||||
" traffic_split={\"0\": 100},\n",
|
||||
" machine_type=\"n1-highmem-16\",\n",
|
||||
" min_replica_count=1,\n",
|
||||
" max_replica_count=1,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "w99wNhz_3ruV"
|
||||
},
|
||||
"source": [
|
||||
"Prepare input prediction image.\n",
|
||||
"\n",
|
||||
"Note: You can modify the input image as required."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "0Itg0k1s30t3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Local path to the prediction image.\n",
|
||||
"DEMO_IMAGE_PATH = \"./prediction_image.jpg\"\n",
|
||||
"# Download the prediction image.\n",
|
||||
"! wget -O {DEMO_IMAGE_PATH} https://cdn.pixabay.com/photo/2018/02/06/12/37/fruit-3134631_1280.jpg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "B1Q7AbmJ4QxZ"
|
||||
},
|
||||
"source": [
|
||||
"Prepare jpeg bytes and text-embeddings bytes inputs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "qxj4Xv_DhHXj"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"image_jpeg_bytes_inputs = get_jpeg_bytes(\n",
|
||||
" local_image_path=DEMO_IMAGE_PATH, new_width=1024\n",
|
||||
")\n",
|
||||
"text_embeddings = generate_text_embeddings(categories=CATEGORIES)\n",
|
||||
"text_embeddings_bytes_inputs = convert_numpy_array_to_byte_string_via_tf_tensor(\n",
|
||||
" text_embeddings\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "Ys88lEkK4XDp"
|
||||
},
|
||||
"source": [
|
||||
"Use base-64 encoding followed by UTF-8 decoding to package the bytes inputs and then send them to the endpoint for prediction. The Vertex AI Prediction service will automatically convert these input strings back to bytes based on the `b64` keyword.\n",
|
||||
"\n",
|
||||
"**Note: The first prediction can take up to 2 minutes due to one time JIT compilation of the model. This may cause a timeout error below. If you get a timeout error, then wait for 2 minutes and run the prediction again. You will not get the timeout error after that.**\n",
|
||||
"The subsequent predictions take 4 seconds to finish."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Mj4sqTAG4sU5"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"instances_list = [\n",
|
||||
" {\n",
|
||||
" \"image_jpeg_bytes_inputs\": {\n",
|
||||
" \"b64\": base64.b64encode(image_jpeg_bytes_inputs).decode(\"utf-8\")\n",
|
||||
" },\n",
|
||||
" \"text_embeddings_bytes_inputs\": {\n",
|
||||
" \"b64\": base64.b64encode(text_embeddings_bytes_inputs).decode(\"utf-8\")\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"]\n",
|
||||
"instances = [json_format.ParseDict(s, Value()) for s in instances_list]\n",
|
||||
"prediction_output = jax_fvlm_endpoint.predict(instances=instances).predictions[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "S3qC-MrN6SEs"
|
||||
},
|
||||
"source": [
|
||||
"Generate output image with predicted bounding boxes, labels, and probabilities. The output image will be saved to `OUTPUT_IMAGE_PATH`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "wnPY2MFN6fL6"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"generate_prediction_output_image(\n",
|
||||
" input_image_path=DEMO_IMAGE_PATH,\n",
|
||||
" prediction_output=prediction_output,\n",
|
||||
" output_image_path=OUTPUT_IMAGE_PATH,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "TpV-iwP9qw9c"
|
||||
},
|
||||
"source": [
|
||||
"## Cleaning up\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:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "sx_vKniMq9ZX"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete endpoint resource.\n",
|
||||
"jax_fvlm_endpoint.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete model resource.\n",
|
||||
"jax_fvlm_model.delete()\n",
|
||||
"\n",
|
||||
"# Delete Cloud Storage objects that were created.\n",
|
||||
"delete_bucket = False\n",
|
||||
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! gsutil -m rm -r $BUCKET_URI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_jax_fvlm.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_jax_vision_transformer.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",
|
||||
@@ -72,7 +72,9 @@
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates finetuning a [JAX ViT-B16 model](https://github.com/google-research/vision_transformer#available-vit-models) for image classification task on GPU and deploying them on Vertex AI for online prediction."
|
||||
"This notebook demonstrates finetuning a [JAX ViT-B16 model](https://github.com/google-research/vision_transformer#available-vit-models) for image classification task on GPU and deploying them on Vertex AI for online prediction.\n",
|
||||
"\n",
|
||||
"Learn more about [Generative AI Support in Vertex AI](https://cloud.google.com/blog/products/ai-machine-learning/vertex-ai-model-garden-and-generative-ai-studio)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -83,18 +85,21 @@
|
||||
"source": [
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"In this tutorial, you learn how to:\n",
|
||||
"In this tutorial, you learn how fine-tune, deploy and predict with a Vertex AI pretrained JAX Vision Transformer based model.\n",
|
||||
"\n",
|
||||
"This tutorial uses the following Google Cloud ML services and resources:\n",
|
||||
"\n",
|
||||
"- Vertex AI Model Garden\n",
|
||||
"- Vertex AI Training\n",
|
||||
"- Vertex AI Model Registry\n",
|
||||
"- Vertex AI Online Prediction\n",
|
||||
"\n",
|
||||
"The steps performed are:\n",
|
||||
"\n",
|
||||
"- Finetune a JAX Vision Transformer based model.\n",
|
||||
"- Upload the model to [Model Registry](https://cloud.google.com/vertex-ai/docs/model-registry/introduction).\n",
|
||||
"- Deploy the model on [Endpoint](https://cloud.google.com/vertex-ai/docs/predictions/using-private-endpoints).\n",
|
||||
"- Run online predictions for image classification.\n",
|
||||
"\n",
|
||||
"This tutorial uses the following Google Cloud ML services and resources:\n",
|
||||
"\n",
|
||||
"- Vertex AI Training\n",
|
||||
"- Vertex AI Model Registry\n",
|
||||
"- Vertex AI Online Prediction"
|
||||
"- Run online predictions for image classification.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -165,10 +170,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Automatically restart kernel after installs so that your environment can access the new packages.\n",
|
||||
"import IPython\n",
|
||||
"# import IPython\n",
|
||||
"\n",
|
||||
"app = IPython.Application.instance()\n",
|
||||
"app.kernel.do_shutdown(True)"
|
||||
"# app = IPython.Application.instance()\n",
|
||||
"# app.kernel.do_shutdown(True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -214,7 +219,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"your-project-id\" # @param {type:\"string\"}\n",
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
@@ -511,7 +516,7 @@
|
||||
"### Prepare dataset\n",
|
||||
"\n",
|
||||
"If you are not using [TensorFlow Datasets](https://www.tensorflow.org/datasets/catalog/overview#all_datasets), then you need to prepare your dataset and store it on Cloud Storage. The following example shows\n",
|
||||
"how to do this for the [tf_flowers dataset](https://www.tensorflow.org/datasets/catalog/tf_flowers). If using TensorFlow Datasets, you can just pass\n",
|
||||
"how to do this for the [tf_flowers dataset](https://www.tensorflow.org/datasets/catalog/tf_flowers). If using TensorFlow Datasets, you pass\n",
|
||||
"the dataset name such as `tf_flowers` to the `--config.dataset` flag and bypass this section."
|
||||
]
|
||||
},
|
||||
@@ -853,7 +858,7 @@
|
||||
"jax_vit_model.delete()\n",
|
||||
"\n",
|
||||
"# Delete Cloud Storage objects that were created.\n",
|
||||
"delete_bucket = False\n",
|
||||
"delete_bucket = True\n",
|
||||
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! gsutil -m rm -r $BUCKET_URI"
|
||||
]
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_keras_stable_diffusion.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",
|
||||
@@ -282,18 +282,21 @@
|
||||
"from io import BytesIO\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from google.cloud import storage\n",
|
||||
"from PIL import Image\n",
|
||||
"\n",
|
||||
"GCS_URI_PREFIX = \"gs://\"\n",
|
||||
"\n",
|
||||
"# Training constants.\n",
|
||||
"TRAINING_JOB_PREFIX = \"train\"\n",
|
||||
"TRAIN_CONTAINER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/keras-train:latest\"\n",
|
||||
"TRAIN_CONTAINER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/keras-train:latest\"\n",
|
||||
"TRAIN_MACHINE_TYPE = \"a2-highgpu-1g\"\n",
|
||||
"TRAIN_ACCELERATOR_TYPE = \"NVIDIA_TESLA_A100\"\n",
|
||||
"TRAIN_NUM_GPU = 1\n",
|
||||
"RESOLUTION = 512\n",
|
||||
"\n",
|
||||
"# Prediction constants.\n",
|
||||
"PREDICTION_CONTAINER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/keras-serve:latest\"\n",
|
||||
"PREDICTION_CONTAINER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/keras-serve:latest\"\n",
|
||||
"PREDICTION_ACCELERATOR_TYPE = \"NVIDIA_TESLA_V100\"\n",
|
||||
"PREDICTION_MACHINE_TYPE = \"n1-standard-8\"\n",
|
||||
"DEPLOY_JOB_PREFIX = \"deploy\"\n",
|
||||
@@ -317,6 +320,21 @@
|
||||
" return gcs_path\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def download_gcs_file_to_local(gcs_uri: str, local_path: str):\n",
|
||||
" \"\"\"Download a gcs file to a local path.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" gcs_uri: A string of file path on GCS.\n",
|
||||
" local_path: A string of local file path.\n",
|
||||
" \"\"\"\n",
|
||||
" if not gcs_uri.startswith(GCS_URI_PREFIX):\n",
|
||||
" raise ValueError(f\"{gcs_uri} is not a GCS path starting with {GCS_URI_PREFIX}.\")\n",
|
||||
" client = storage.Client()\n",
|
||||
" os.makedirs(os.path.dirname(local_path), exist_ok=True)\n",
|
||||
" with open(local_path, \"wb\") as f:\n",
|
||||
" client.download_blob_to_file(gcs_uri, f)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def deploy_model(model_path, service_account):\n",
|
||||
"\n",
|
||||
" deploy_model_name = get_job_name_with_datetime(DEPLOY_JOB_PREFIX)\n",
|
||||
@@ -420,7 +438,11 @@
|
||||
"from keras_cv.models import StableDiffusion\n",
|
||||
"\n",
|
||||
"model = StableDiffusion(img_height=RESOLUTION, img_width=RESOLUTION, jit_compile=True)\n",
|
||||
"if model_path:\n",
|
||||
"if model_path.startswith(GCS_URI_PREFIX):\n",
|
||||
" local_model_path = \"/tmp/saved_model.h5\"\n",
|
||||
" download_gcs_file_to_local(model_path, local_model_path)\n",
|
||||
" model.diffusion_model.load_weights(local_model_path)\n",
|
||||
"elif model_path:\n",
|
||||
" model.diffusion_model.load_weights(model_path)"
|
||||
]
|
||||
},
|
||||
@@ -568,7 +590,7 @@
|
||||
},
|
||||
"source": [
|
||||
"## Finetune models\n",
|
||||
"This section shows how to finetune Keras Stable diffusion models with trainig dockers.\n",
|
||||
"This section shows how to finetune Keras Stable diffusion models with training dockers.\n",
|
||||
"\n",
|
||||
"If you would like to use finetuned models, please go to the section `Run inferences`."
|
||||
]
|
||||
|
||||
@@ -0,0 +1,641 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "ur8xi4C7S06n"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "TirJ-SGQseby"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden MediaPipe with gesture recognition\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_mediapipe_gesture_recognition.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",
|
||||
"\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_mediapipe_gesture_recognition.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> <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_mediapipe_gesture_recognition.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>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dwGLvtIeECLK"
|
||||
},
|
||||
"source": [
|
||||
"**_NOTE_**: This notebook has been tested in the following environment:\n",
|
||||
"\n",
|
||||
"* Python version = 3.9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "tvgnzT1CKxrO"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates how to use [MediaPipe Model Maker](https://developers.google.com/mediapipe/solutions/model_maker) to train an on-device gesture recognition model in Vertex AI Model Garden.\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"* Train new models\n",
|
||||
" * Convert input data to training formats\n",
|
||||
" * Create [custom jobs](https://cloud.google.com/vertex-ai/docs/training/create-custom-job) to train new models\n",
|
||||
" * Export models\n",
|
||||
"\n",
|
||||
"* Cleanup resources\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI\n",
|
||||
"pricing](https://cloud.google.com/vertex-ai/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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KEukV6uRk_S3"
|
||||
},
|
||||
"source": [
|
||||
"## Before you begin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "z__i0w0lCAsW"
|
||||
},
|
||||
"source": [
|
||||
"### Colab only\n",
|
||||
"Run the following commands to install dependencies and to authenticate with Google Cloud if running on Colab."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Jvqs-ehKlaYh"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip3 install --upgrade pip\n",
|
||||
"\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" ! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
"\n",
|
||||
" # Automatically restart kernel after installs\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)\n",
|
||||
"\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**If you don't know your project ID**, see the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"#### Region\n",
|
||||
"\n",
|
||||
"You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "tTy1gX11kCJY"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"asia\", or \"europe\".'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\n",
|
||||
"\n",
|
||||
"Create a storage bucket to store intermediate artifacts such as datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "MzGDU7TWdts_"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-EcIXiGsCePi"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NIq7R4HZCfIc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "960505627ddf"
|
||||
},
|
||||
"source": [
|
||||
"### Import libraries"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "PyQmSRbKA8r-"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"import tensorflow\n",
|
||||
"from google.cloud import aiplatform"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "init_aip:mbsdk,all"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI SDK for Python\n",
|
||||
"\n",
|
||||
"Initialize the Vertex AI SDK for Python for your project."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "9wExiMUxFk91"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"now = datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n",
|
||||
"\n",
|
||||
"STAGING_BUCKET = os.path.join(BUCKET_URI, \"temp/%s\" % now)\n",
|
||||
"\n",
|
||||
"EVALUATION_RESULT_OUTPUT_DIRECTORY = os.path.join(STAGING_BUCKET, \"evaluation\")\n",
|
||||
"EVALUATION_RESULT_OUTPUT_FILE = os.path.join(\n",
|
||||
" EVALUATION_RESULT_OUTPUT_DIRECTORY, \"evaluation.json\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"EXPORTED_MODEL_OUTPUT_DIRECTORY = os.path.join(STAGING_BUCKET, \"model\")\n",
|
||||
"EXPORTED_MODEL_OUTPUT_FILE = os.path.join(\n",
|
||||
" EXPORTED_MODEL_OUTPUT_DIRECTORY, \"gesture_recognizer.task\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=STAGING_BUCKET)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "n6IFz75WGCam"
|
||||
},
|
||||
"source": [
|
||||
"### Define training machine specs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "riG_qUokg0XZ"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"TRAINING_JOB_DISPLAY_NAME = \"mediapipe_gesture_recognizer_%s\" % now\n",
|
||||
"TRAINING_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/mediapipe-train\"\n",
|
||||
"TRAINING_MACHINE_TYPE = \"n1-highmem-16\"\n",
|
||||
"TRAINING_ACCELARATOR_TYPE = \"NVIDIA_TESLA_V100\"\n",
|
||||
"TRAINING_ACCELERATOR_COUNT = 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-rsdAcBV-vlf"
|
||||
},
|
||||
"source": [
|
||||
"## Train your customized models"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Prepare input data for training\n",
|
||||
"\n",
|
||||
"Finetuning a model for gesture recognition requires a dataset with a directory structure following the pattern `<dataset_path>/<label_name>/<img_name>.*` (e.g. `my_custom_dataset/thumbs_up/img12.jpg`). In addition, one of the label names must be none. The none label represents any gesture that isn't classified as one of the other gestures.\n",
|
||||
"\n",
|
||||
"This example uses a rock paper scissors dataset sample which is available on Cloud Storage.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "IndQ_m6ddUEM"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"training_data_path = (\n",
|
||||
" \"gs://mediapipe-tasks/gesture_recognizer/rps_data_sample\" # @param {type:\"string\"}\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ci4IV6vdXRMD"
|
||||
},
|
||||
"source": [
|
||||
"When Model Maker loads the dataset, it runs the pre-packaged hand detection model from MediaPipe Hands to detect the hand landmarks from the images. Any images without detected hands are ommitted from the dataset. The resulting dataset will contain the extracted hand landmark positions from each image, rather than images themselves.\n",
|
||||
"\n",
|
||||
"You can configure a few options that determine how the dataset is loaded:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "aNHLSyFtXP7I"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# A boolean controlling whether to shuffle the dataset. Defaults to true.\n",
|
||||
"shuffle = True # @param {type:\"boolean\"}\n",
|
||||
"# A float between 0 and 1 controlling the confidence threshold for hand detection\n",
|
||||
"min_detection_confidence = 0.6 # @param {type:\"number\"}\n",
|
||||
"# Configures how to split the dataset between training, validation and test data. Must sum to up 1.\n",
|
||||
"split_ratio = \"0.8,0.1,0.1\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "aaff6f5be7f6"
|
||||
},
|
||||
"source": [
|
||||
"### Set fine-tuning options\n",
|
||||
"\n",
|
||||
"You can customize the model using the by specifying ModelOptions and HParams. The ModelOptions contain parameters related to the model itself, while the HParams contains parameters related to training and saving the model.\n",
|
||||
"\n",
|
||||
"The ModelOptions contain these customizable parameter that affects accuracy:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "bDxsEaoGcibW"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The fraction of the input units to drop. Used in dropout layer.\n",
|
||||
"dropout_rate: float = 0.05 # @param {type:\"number\"}\n",
|
||||
"# A list of hidden layer widths for the gesture model. Each element\n",
|
||||
"# in the list will create a new hidden layer with the specified width.\n",
|
||||
"# The hidden layers are separated with BatchNorm, Dropout, and ReLU.\n",
|
||||
"layer_widths: str = \"\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "fk0TTZbDdJPX"
|
||||
},
|
||||
"source": [
|
||||
"HParams has the following list of customizable parameters which affect model accuracy:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "um_XKbmpTaHx"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The learning rate to use for gradient descent training.\n",
|
||||
"learning_rate: float = 0.001 # @param {type:\"number\"}\n",
|
||||
"# Batch size for training.\n",
|
||||
"batch_size: int = 2 # @param {type:\"number\"}\n",
|
||||
"# Number of training iterations over the dataset.\n",
|
||||
"epochs: int = 10 # @param {type:\"slider\", min:0, max:100, step:1}\n",
|
||||
"# An optional integer that indicates the number of training steps per\n",
|
||||
"# epoch. If set to 0, the training pipeline calculates the default\n",
|
||||
"# steps per epoch as the training dataset size divided by batch size.\n",
|
||||
"steps_per_epoch: int = 0 # @param {type:\"number\"}\n",
|
||||
"# Whether to shuffle the dataset before training\n",
|
||||
"shuffle: bool = False # @param {type:\"boolean\"}\n",
|
||||
"# Learning rate decay to use for gradient descent training.\n",
|
||||
"lr_decay: float = 0.99 # @param {type:\"number\"}\n",
|
||||
"# Gamma parameter for focal loss. Defaults to 2\n",
|
||||
"gamma: float = 2 # @param {type:\"number\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "HwcCjwlBTQIz"
|
||||
},
|
||||
"source": [
|
||||
"### Run fine-tuning\n",
|
||||
"With your training dataset and fine-tuning options prepared, you are ready to start the fine-tuning process. This process is resource intensive and can take a few minutes to complete. On Vertex AI with GPU processing, the example fine-tuning below takes between 1-2 minutes to train on approximately 500 images.\n",
|
||||
"\n",
|
||||
"To begin the fine-tuning process, use the following code:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "aec22792ee84"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model_export_path = EXPORTED_MODEL_OUTPUT_DIRECTORY\n",
|
||||
"evaluation_result_path = EVALUATION_RESULT_OUTPUT_DIRECTORY\n",
|
||||
"\n",
|
||||
"model_options = {\"dropout_rate\": dropout_rate}\n",
|
||||
"if layer_widths:\n",
|
||||
" model_options[\"layer_widths\"] = layer_widths\n",
|
||||
"\n",
|
||||
"hparams = {\n",
|
||||
" \"learning_rate\": learning_rate,\n",
|
||||
" \"batch_size\": batch_size,\n",
|
||||
" \"epochs\": epochs,\n",
|
||||
" \"shuffle\": shuffle,\n",
|
||||
" \"lr_decay\": lr_decay,\n",
|
||||
" \"gamma\": gamma,\n",
|
||||
"}\n",
|
||||
"if steps_per_epoch:\n",
|
||||
" hparams[\"steps_per_epoch\"] = steps_per_epoch\n",
|
||||
"\n",
|
||||
"worker_pool_specs = [\n",
|
||||
" {\n",
|
||||
" \"machine_spec\": {\n",
|
||||
" \"machine_type\": TRAINING_MACHINE_TYPE,\n",
|
||||
" \"accelerator_type\": TRAINING_ACCELARATOR_TYPE,\n",
|
||||
" \"accelerator_count\": TRAINING_ACCELERATOR_COUNT,\n",
|
||||
" },\n",
|
||||
" \"replica_count\": 1,\n",
|
||||
" \"container_spec\": {\n",
|
||||
" \"image_uri\": TRAINING_CONTAINER,\n",
|
||||
" \"command\": [],\n",
|
||||
" \"args\": [\n",
|
||||
" \"--task_name=gesture_recognizer\",\n",
|
||||
" \"--training_data_path=%s\" % training_data_path,\n",
|
||||
" \"--model_export_path=%s\" % model_export_path,\n",
|
||||
" \"--evaluation_result_path=%s\" % evaluation_result_path,\n",
|
||||
" \"--split_ratio=%s\" % split_ratio,\n",
|
||||
" \"--model_options=%s\" % json.dumps(model_options),\n",
|
||||
" \"--hparams=%s\" % json.dumps(hparams),\n",
|
||||
" ],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"training_job = aiplatform.CustomJob(\n",
|
||||
" display_name=TRAINING_JOB_DISPLAY_NAME,\n",
|
||||
" project=PROJECT_ID,\n",
|
||||
" worker_pool_specs=worker_pool_specs,\n",
|
||||
" staging_bucket=STAGING_BUCKET,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"training_job.run()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "rXMF2tnV_WS0"
|
||||
},
|
||||
"source": [
|
||||
"## Evaluate and export model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "mV-Djz-frBni"
|
||||
},
|
||||
"source": [
|
||||
"### Evaluate performance\n",
|
||||
"\n",
|
||||
"After fine-tuning the model, we evaluate the training result on a test dataset, which is typically a portion of your original dataset not used during training. Accuracy levels between 0.8 and 0.9 are generally considered very good, but your use case requirements may differ. You should also consider how fast the model can produce an inference. Higher accuracy frequently comes at the cost of longer inference times.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "09Rz1AYspK19"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def get_evaluation_result(evaluation_result_path):\n",
|
||||
" try:\n",
|
||||
" with tensorflow.io.gfile.GFile(evaluation_result_path, \"r\") as input_file:\n",
|
||||
" evalutation_result = json.loads(input_file.read())\n",
|
||||
" return evalutation_result[\"accuracy\"], evalutation_result[\"loss\"]\n",
|
||||
" except:\n",
|
||||
" print(\n",
|
||||
" \"Evaluation result not found. Your test dataset is likely \"\n",
|
||||
" + \"empty. You can adjust the size of your test dataset or adjust \"\n",
|
||||
" + \"how you split your dataset.\"\n",
|
||||
" )\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"evaluation_result = get_evaluation_result(EVALUATION_RESULT_OUTPUT_FILE)\n",
|
||||
"\n",
|
||||
"if evaluation_result is not None:\n",
|
||||
" print(\"Accuracy:\", evaluation_result[0])\n",
|
||||
" print(\"Loss:\", evaluation_result[1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "g0BGaofgsMsy"
|
||||
},
|
||||
"source": [
|
||||
"### Export model\n",
|
||||
"After finetuning and evaluating the model, you can save the Tensorflow Lite model, try it out in the [Gesture Recognizer](https://mediapipe-studio.webapps.google.com/demo/gesture_recognizer) demo in MediaPipe Studio or integrate it with your on-device application by following the [Gesture recognizer task guide](https://developers.google.com/mediapipe/solutions/vision/gesture_recognizer). The exported model contains the generates required model metadata, as well as a classification label file."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NYuQowyZEtxK"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def copy_model(model_source, model_dest):\n",
|
||||
" ! gsutil cp {model_source} {model_dest}\n",
|
||||
"\n",
|
||||
"copy_model(EXPORTED_MODEL_OUTPUT_FILE, \"gesture_recognizer.task\")\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" from google.colab import files\n",
|
||||
"\n",
|
||||
" files.download(\"gesture_recognizer.task\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "kkH2nrpdp4sp"
|
||||
},
|
||||
"source": [
|
||||
"## Clean up"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Ax6vQVZhp9pR"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete training data and jobs.\n",
|
||||
"if training_job.list(filter=f'display_name=\"{TRAINING_JOB_DISPLAY_NAME}\"'):\n",
|
||||
" training_job.delete()\n",
|
||||
"\n",
|
||||
"!gsutil rm -r {STAGING_BUCKET}"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_mediapipe_gesture_recognition.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
+122
-21
@@ -29,7 +29,7 @@
|
||||
"id": "TirJ-SGQseby"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden MediaPipe With Image Classification\n",
|
||||
"# Vertex AI Model Garden MediaPipe with image classification\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
@@ -44,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_mediapipe_image_classification.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",
|
||||
@@ -145,24 +145,132 @@
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"### Set up your Google Cloud project\n",
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**The following steps are required, regardless of your notebook environment.**\n",
|
||||
"**If you don't know your project ID**, see the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\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",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"#### Region\n",
|
||||
"\n",
|
||||
"1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
|
||||
"You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "tTy1gX11kCJY"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"asia\", or \"europe\".'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\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",
|
||||
"1. If you are running this notebook locally, you will need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
|
||||
"Create a storage bucket to store intermediate artifacts such as datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "MzGDU7TWdts_"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-EcIXiGsCePi"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NIq7R4HZCfIc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "960505627ddf"
|
||||
},
|
||||
"source": [
|
||||
"### Import libraries"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "PyQmSRbKA8r-"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"from datetime import datetime\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",
|
||||
"import tensorflow\n",
|
||||
"from google.cloud import aiplatform"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "init_aip:mbsdk,all"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI SDK for Python\n",
|
||||
"\n",
|
||||
"**Note**: Jupyter runs lines prefixed with `!` as shell commands, and it interpolates Python variables prefixed with `$` into these commands."
|
||||
"Initialize the Vertex AI SDK for Python for your project."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -173,13 +281,6 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"import tensorflow\n",
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"now = datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n",
|
||||
"\n",
|
||||
"# The project and bucket are for experiments below.\n",
|
||||
@@ -280,14 +381,14 @@
|
||||
"source": [
|
||||
"### Set fine-tuning options\n",
|
||||
"\n",
|
||||
"There are a few required settings to run fine-tuning aside from your training dataset: output directory for the model and the model architecture. Use HParams object export_dir parameter to specify a model output directory. Use the SupportedModels class to specify the model architecture. The image classifier solution supports the following model architectures:\n",
|
||||
"You can pick between different model architectures to further customize your training:\n",
|
||||
"\n",
|
||||
"* MobileNet-V2\n",
|
||||
"* EfficientNet-Lite0\n",
|
||||
"* EfficientNet-Lite2\n",
|
||||
"* EfficientNet-Lite4\n",
|
||||
"\n",
|
||||
"To set the required parameters, use the following code:"
|
||||
"To set the model architecture and other training parameters, adjust the following values:"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"id": "TirJ-SGQseby"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden MediaPipe With Object Detection\n",
|
||||
"# Vertex AI Model Garden MediaPipe with object detection\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
@@ -44,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_mediapipe_object_detection.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",
|
||||
@@ -112,7 +112,7 @@
|
||||
"id": "z__i0w0lCAsW"
|
||||
},
|
||||
"source": [
|
||||
"### Colab Only\n",
|
||||
"### Colab only\n",
|
||||
"Run the following commands to install dependencies and to authenticate with Google Cloud if running on Colab."
|
||||
]
|
||||
},
|
||||
@@ -145,24 +145,132 @@
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"### Set up your Google Cloud project\n",
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**The following steps are required, regardless of your notebook environment.**\n",
|
||||
"**If you don't know your project ID**, see the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\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",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"#### Region\n",
|
||||
"\n",
|
||||
"1. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
|
||||
"You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "tTy1gX11kCJY"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"asia\", or \"europe\".'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\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",
|
||||
"1. If you are running this notebook locally, you will need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
|
||||
"Create a storage bucket to store intermediate artifacts such as datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "MzGDU7TWdts_"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-EcIXiGsCePi"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NIq7R4HZCfIc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "960505627ddf"
|
||||
},
|
||||
"source": [
|
||||
"### Import libraries"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "PyQmSRbKA8r-"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"from datetime import datetime\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",
|
||||
"import tensorflow\n",
|
||||
"from google.cloud import aiplatform"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "init_aip:mbsdk,all"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI SDK for Python\n",
|
||||
"\n",
|
||||
"**Note**: Jupyter runs lines prefixed with `!` as shell commands, and it interpolates Python variables prefixed with `$` into these commands."
|
||||
"Initialize the Vertex AI SDK for Python for your project."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -173,32 +281,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"import tensorflow\n",
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"now = datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n",
|
||||
"\n",
|
||||
"# The project and bucket are for experiments below.\n",
|
||||
"PROJECT_ID = \"\" # @param {type:\"string\"}\n",
|
||||
"# The form for BUCKET_URI is gs://<bucket-name>.\\n\",\n",
|
||||
"BUCKET_URI = \"gs://\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# You can choose a region from https://cloud.google.com/about/locations.\n",
|
||||
"# Only regions prefixed by \"us\", \"asia\", or \"europe\" are supported.\n",
|
||||
"REGION = \"us-central1\" # @param {type:\"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"asia\", or \"europe\".'\n",
|
||||
"\n",
|
||||
"! gcloud config set project $PROJECT_ID\n",
|
||||
"\n",
|
||||
"STAGING_BUCKET = os.path.join(BUCKET_URI, \"temp/%s\" % now)\n",
|
||||
"\n",
|
||||
"EVALUATION_RESULT_OUTPUT_DIRECTORY = os.path.join(STAGING_BUCKET, \"evaluation\")\n",
|
||||
@@ -359,12 +443,12 @@
|
||||
"source": [
|
||||
"### Set fine-tuning options\n",
|
||||
"\n",
|
||||
"There are a few required settings to run fine-tuning aside from your training dataset: output directory for the model, and the model architecture. Use HParams to specify the export_dir parameter for the output directory. Use the SupportedModels class to specify the model architecture. The object detector solution supports the following model architectures:\n",
|
||||
"You can pick between different model architectures to further customize your training:\n",
|
||||
"\n",
|
||||
"* MobileNet-V2\n",
|
||||
"* MobileNet-MultiHW-AVG\n",
|
||||
"\n",
|
||||
"To set the parameters, use the following code:"
|
||||
"To set the model architecture and other training parameters, adjust the following values:"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,616 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "ur8xi4C7S06n"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "TirJ-SGQseby"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden MediaPipe with text classification\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_mediapipe_text_classification.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",
|
||||
"\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_mediapipe_text_classification.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/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_mediapipe_image_classification.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>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dwGLvtIeECLK"
|
||||
},
|
||||
"source": [
|
||||
"**_NOTE_**: This notebook has been tested in the following environment:\n",
|
||||
"\n",
|
||||
"* Python version = 3.9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "tvgnzT1CKxrO"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates how to use [MediaPipe Model Maker](https://developers.google.com/mediapipe/solutions/model_maker) to train an on-device text classification model in Vertex AI Model Garden.\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"* Train new models\n",
|
||||
" * Convert input data to training formats\n",
|
||||
" * Create [custom jobs](https://cloud.google.com/vertex-ai/docs/training/create-custom-job) to train new models\n",
|
||||
" * Export models\n",
|
||||
"\n",
|
||||
"* Cleanup resources\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI\n",
|
||||
"pricing](https://cloud.google.com/vertex-ai/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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KEukV6uRk_S3"
|
||||
},
|
||||
"source": [
|
||||
"## Before you begin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "z__i0w0lCAsW"
|
||||
},
|
||||
"source": [
|
||||
"### Colab only\n",
|
||||
"Run the following commands to install dependencies and to authenticate with Google Cloud if running on Colab."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Jvqs-ehKlaYh"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip3 install --upgrade pip\n",
|
||||
"\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" ! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
"\n",
|
||||
" # Automatically restart kernel after installs\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)\n",
|
||||
"\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**If you don't know your project ID**, see the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"#### Region\n",
|
||||
"\n",
|
||||
"You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "tTy1gX11kCJY"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"asia\", or \"europe\".'"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\n",
|
||||
"\n",
|
||||
"Create a storage bucket to store intermediate artifacts such as datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "MzGDU7TWdts_"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-EcIXiGsCePi"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NIq7R4HZCfIc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "960505627ddf"
|
||||
},
|
||||
"source": [
|
||||
"### Import libraries"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "PyQmSRbKA8r-"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"from google.cloud import aiplatform"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "init_aip:mbsdk,all"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI SDK for Python\n",
|
||||
"\n",
|
||||
"Initialize the Vertex AI SDK for Python for your project."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "9wExiMUxFk91"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"now = datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n",
|
||||
"\n",
|
||||
"STAGING_BUCKET = os.path.join(BUCKET_URI, \"temp/%s\" % now)\n",
|
||||
"\n",
|
||||
"EVALUATION_RESULT_OUTPUT_DIRECTORY = os.path.join(STAGING_BUCKET, \"evaluation\")\n",
|
||||
"EVALUATION_RESULT_OUTPUT_FILE = os.path.join(\n",
|
||||
" EVALUATION_RESULT_OUTPUT_DIRECTORY, \"evaluation.json\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"EXPORTED_MODEL_OUTPUT_DIRECTORY = os.path.join(STAGING_BUCKET, \"model\")\n",
|
||||
"EXPORTED_MODEL_OUTPUT_FILE = os.path.join(\n",
|
||||
" EXPORTED_MODEL_OUTPUT_DIRECTORY, \"model.tflite\"\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=STAGING_BUCKET)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "n6IFz75WGCam"
|
||||
},
|
||||
"source": [
|
||||
"### Define training machine specs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "riG_qUokg0XZ"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"TRAINING_JOB_DISPLAY_NAME = \"mediapipe_text_classifier_%s\" % now\n",
|
||||
"TRAINING_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/mediapipe-train\"\n",
|
||||
"TRAINING_MACHINE_TYPE = \"n1-highmem-16\"\n",
|
||||
"TRAINING_ACCELARATOR_TYPE = \"NVIDIA_TESLA_V100\"\n",
|
||||
"TRAINING_ACCELERATOR_COUNT = 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-rsdAcBV-vlf"
|
||||
},
|
||||
"source": [
|
||||
"## Train your customized models"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Get the Dataset\n",
|
||||
"\n",
|
||||
"The following code block uses the [SST-2](https://nlp.stanford.edu/sentiment/index.html) (Stanford Sentiment Treebank) dataset which contains 67,349 movie reviews for training and 872 movie reviews for testing. The dataset has two classes: positive and negative movie reviews. Positive reviews are labeled with 1 and negative reviews with 0.\n",
|
||||
"\n",
|
||||
"The SST-2 dataset is stored as a TSV file. The only difference between the TSV and CSV formats is that TSV uses a tab `\\t` character as its delimiter and CSV uses a comma `,`.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "IndQ_m6ddUEM"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"training_data_path = (\n",
|
||||
" \"gs://mediapipe-tasks/text_classifier/SST-2/train.tsv\" # @param {type:\"string\"}\n",
|
||||
")\n",
|
||||
"validation_data_path = (\n",
|
||||
" \"gs://mediapipe-tasks/text_classifier/SST-2/dev.tsv\" # @param {type:\"string\"}\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# The delimiter used in the dataset.\n",
|
||||
"delimiter = \"\\t\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Character used to quote fields that contain special characters\n",
|
||||
"# like the `delimiter`.\n",
|
||||
"quotechar = \"\\t\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Sequence of keys for the CSV columns (represented as a comma\n",
|
||||
"# separated list). If empty, the first row of the CSV file is used\n",
|
||||
"# as the keys\n",
|
||||
"fieldnames = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Column name for the input text.\n",
|
||||
"text_column = \"sentence\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Column name for the labels.\n",
|
||||
"label_column = \"label\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "aaff6f5be7f6"
|
||||
},
|
||||
"source": [
|
||||
"### Set fine-tuning options\n",
|
||||
"\n",
|
||||
"You can pick between different model architectures to further customize your training:\n",
|
||||
"\n",
|
||||
"* Average Word Embedding Model\n",
|
||||
"* BERT-classifier\n",
|
||||
"\n",
|
||||
"To set the model architecture and other training parameters, adjust the following values:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "um_XKbmpTaHx"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model_architecture = (\n",
|
||||
" \"average_word_embedding\" # @param [\"average_word_embedding\", \"mobilebert\"]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# The learning rate to use for gradient descent-based\n",
|
||||
"# optimizers. Defaults to 3e-5 for the BERT-based classifier\n",
|
||||
"# and 0 for the average word-embedding classifier because\n",
|
||||
"# it does not need such an optimizer.\n",
|
||||
"learning_rate: float = 0.0 # @param {type:\"number\"}\n",
|
||||
"\n",
|
||||
"# Batch size for training. Defaults to 32 for the average\n",
|
||||
"# word-embedding classifier and 48 for the BERT-based\n",
|
||||
"# classifier.\n",
|
||||
"batch_size: int = 48 # @param {type:\"number\"}\n",
|
||||
"\n",
|
||||
"# Number of training iterations over the dataset. Defaults\n",
|
||||
"# to 10 for the average word-embedding classifier and 3\n",
|
||||
"# for the BERT-based classifier.\n",
|
||||
"epochs: int = 10 # @param {type:\"slider\", min:0, max:100, step:1}\n",
|
||||
"\n",
|
||||
"# An integer that indicates the number of training steps per\n",
|
||||
"# epoch. If set to 0, the training pipeline calculates the\n",
|
||||
"# default steps per epoch as the training dataset size\n",
|
||||
"# divided by batch size.\n",
|
||||
"steps_per_epoch: int = 0 # @param {type:\"number\"}\n",
|
||||
"\n",
|
||||
"# Controls whether the dataset is shuffled before training.\n",
|
||||
"shuffle: bool = False # @param {type:\"boolean\"}\n",
|
||||
"\n",
|
||||
"# Length of the sequence to feed into the model.\n",
|
||||
"seq_len: int = 256 # @param {type:\"number\"}\n",
|
||||
"\n",
|
||||
"# Whether to convert all uppercase characters to lowercase\n",
|
||||
"# during preprocessing.\n",
|
||||
"do_lower_case: bool = True # @param {type:\"boolean\"}\n",
|
||||
"\n",
|
||||
"# The rate for dropout.\n",
|
||||
"dropout_rate: float = 0.2 # @param {type:\"number\"}\n",
|
||||
"\n",
|
||||
"# Dimension of the word embedding. Only used for the Average Word\n",
|
||||
"# Embedding Model.\n",
|
||||
"wordvec_dim: int = 16 # @param {type:\"number\"}\n",
|
||||
"\n",
|
||||
"# Number of words to generate the vocabulary from data.\n",
|
||||
"# Only used for the Average Word Embedding Model.\n",
|
||||
"vocab_size: int = 10000 # @param {type:\"number\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "HwcCjwlBTQIz"
|
||||
},
|
||||
"source": [
|
||||
"### Run fine-tuning\n",
|
||||
"With your training dataset and fine-tuning options prepared, you are ready to start the fine-tuning process. This process is resource intensive and can take a few minutes to a few hours depending on the model archtiecture and your available compute resources. On Vertex AI with GPU processing, the example fine-tuning below takes between 2-3 minutes to train an Average Word Embedding Model on the SST-2 dataset.\n",
|
||||
"\n",
|
||||
"To begin the fine-tuning process, use the following code:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "aec22792ee84"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model_export_path = EXPORTED_MODEL_OUTPUT_DIRECTORY\n",
|
||||
"evaluation_result_path = EVALUATION_RESULT_OUTPUT_DIRECTORY\n",
|
||||
"\n",
|
||||
"preprocessing_params = {\n",
|
||||
" \"text_column\": text_column,\n",
|
||||
" \"label_column\": label_column,\n",
|
||||
" \"delimiter\": delimiter,\n",
|
||||
" \"quotechar\": quotechar,\n",
|
||||
"}\n",
|
||||
"if fieldnames:\n",
|
||||
" preprocessing_params[\"fieldnames\"] = [\n",
|
||||
" fieldname.strip() for fieldname in fieldnames.split(\",\")\n",
|
||||
" ]\n",
|
||||
"\n",
|
||||
"hparams = {\n",
|
||||
" \"learning_rate\": learning_rate,\n",
|
||||
" \"batch_size\": batch_size,\n",
|
||||
" \"epochs\": epochs,\n",
|
||||
" \"shuffle\": shuffle,\n",
|
||||
"}\n",
|
||||
"if steps_per_epoch:\n",
|
||||
" hparams[\"steps_per_epoch\"] = steps_per_epoch\n",
|
||||
"\n",
|
||||
"model_options = {\n",
|
||||
" \"dropout_rate\": dropout_rate,\n",
|
||||
" \"wordvec_dim\": wordvec_dim,\n",
|
||||
" \"do_lower_case\": do_lower_case,\n",
|
||||
" \"vocab_size\": vocab_size,\n",
|
||||
" \"dropout_rate\": dropout_rate,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"worker_pool_specs = [\n",
|
||||
" {\n",
|
||||
" \"machine_spec\": {\n",
|
||||
" \"machine_type\": TRAINING_MACHINE_TYPE,\n",
|
||||
" \"accelerator_type\": TRAINING_ACCELARATOR_TYPE,\n",
|
||||
" \"accelerator_count\": TRAINING_ACCELERATOR_COUNT,\n",
|
||||
" },\n",
|
||||
" \"replica_count\": 1,\n",
|
||||
" \"container_spec\": {\n",
|
||||
" \"image_uri\": TRAINING_CONTAINER,\n",
|
||||
" \"command\": [],\n",
|
||||
" \"args\": [\n",
|
||||
" \"--task_name=text_classifier\",\n",
|
||||
" \"--training_data_path=%s\" % training_data_path,\n",
|
||||
" \"--validation_data_path=%s\" % validation_data_path,\n",
|
||||
" \"--evaluation_result_path=%s\" % evaluation_result_path,\n",
|
||||
" \"--model_export_path=%s\" % model_export_path,\n",
|
||||
" \"--model_architecture=%s\" % model_architecture,\n",
|
||||
" \"--preprocessing_params=%s\" % json.dumps(preprocessing_params),\n",
|
||||
" \"--hparams=%s\" % json.dumps(hparams),\n",
|
||||
" \"--model_options=%s\" % json.dumps(model_options),\n",
|
||||
" ],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"training_job = aiplatform.CustomJob(\n",
|
||||
" display_name=TRAINING_JOB_DISPLAY_NAME,\n",
|
||||
" project=PROJECT_ID,\n",
|
||||
" worker_pool_specs=worker_pool_specs,\n",
|
||||
" staging_bucket=STAGING_BUCKET,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"training_job.run()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "rXMF2tnV_WS0"
|
||||
},
|
||||
"source": [
|
||||
"## Export model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "g0BGaofgsMsy"
|
||||
},
|
||||
"source": [
|
||||
"After finetuning, you can save the Tensorflow Lite model, try it out in the [Text Classification](https://mediapipe-studio.webapps.google.com/demo/text_classifier) demo in MediaPipe Studio or integrate it with your on-device application by following the [Text classification task guide](https://developers.google.com/mediapipe/solutions/text/text_classifier). The exported model contains the generates required model metadata, as well as a classification label file."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NYuQowyZEtxK"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def copy_model(model_source, model_dest):\n",
|
||||
" ! gsutil cp {model_source} {model_dest}\n",
|
||||
"\n",
|
||||
"copy_model(EXPORTED_MODEL_OUTPUT_FILE, \"text_classification_model.tflite\")\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" from google.colab import files\n",
|
||||
"\n",
|
||||
" files.download(\"text_classification_model.tflite\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "kkH2nrpdp4sp"
|
||||
},
|
||||
"source": [
|
||||
"## Clean up"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Ax6vQVZhp9pR"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete training data and jobs.\n",
|
||||
"if training_job.list(filter=f'display_name=\"{TRAINING_JOB_DISPLAY_NAME}\"'):\n",
|
||||
" training_job.delete()\n",
|
||||
"\n",
|
||||
"!gsutil rm -r {STAGING_BUCKET}"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_mediapipe_text_classification.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "ur8xi4C7S06n"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "TirJ-SGQseby"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden MoViNet video clip classification\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_movinet_clip_classification.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",
|
||||
"\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_movinet_clip_classification.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> <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_movinet_clip_classification.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>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dwGLvtIeECLK"
|
||||
},
|
||||
"source": [
|
||||
"**_NOTE_**: This notebook has been tested in the following environment:\n",
|
||||
"\n",
|
||||
"* Python version = 3.9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "tvgnzT1CKxrO"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates how to use [MoViNet](https://github.com/tensorflow/models/tree/master/official/projects/movinet) in Vertex AI Model Garden.\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"* Train new models\n",
|
||||
" * Convert input data to training formats\n",
|
||||
" * Create [hyperparameter tuning jobs](https://cloud.google.com/vertex-ai/docs/training/hyperparameter-tuning-overview) to train new models\n",
|
||||
" * Find and export best models\n",
|
||||
"\n",
|
||||
"* Test trained models\n",
|
||||
" * Upload models to the [Vertex AI Model Registry](https://cloud.google.com/vertex-ai/docs/model-registry/introduction)\n",
|
||||
" * Run batch predictions\n",
|
||||
"\n",
|
||||
"* Clean up resources\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI\n",
|
||||
"pricing](https://cloud.google.com/vertex-ai/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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KEukV6uRk_S3"
|
||||
},
|
||||
"source": [
|
||||
"## Before you begin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "z__i0w0lCAsW"
|
||||
},
|
||||
"source": [
|
||||
"### Colab Only\n",
|
||||
"Run the following commands for Colab or skip this section if you use Workbench."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Jvqs-ehKlaYh"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" ! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
"\n",
|
||||
" # Automatically restart kernel after installs\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)\n",
|
||||
"\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
},
|
||||
"source": [
|
||||
"### 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 and Compute Engine API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com,compute_component).\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": "9wExiMUxFk91"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"# The GCP project ID for experiments.\n",
|
||||
"PROJECT_ID = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Bucket URI with gs:// prefix.\n",
|
||||
"BUCKET_URI = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# You can choose a region from https://cloud.google.com/about/locations.\n",
|
||||
"# Only regions prefixed by \"us\", \"asia\", or \"europe\" are supported.\n",
|
||||
"REGION = \"us-central1\" # @param {type:\"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"asia\", or \"europe\".'\n",
|
||||
"\n",
|
||||
"! gcloud config set project $PROJECT_ID\n",
|
||||
"\n",
|
||||
"STAGING_BUCKET = os.path.join(BUCKET_URI, \"temporal\")\n",
|
||||
"CHECKPOINT_BUCKET = os.path.join(BUCKET_URI, \"ckpt\")\n",
|
||||
"\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=STAGING_BUCKET)\n",
|
||||
"\n",
|
||||
"# Download config files.\n",
|
||||
"CONFIG_DIR = os.path.join(BUCKET_URI, \"config\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "n6IFz75WGCam"
|
||||
},
|
||||
"source": [
|
||||
"### Define constants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "riG_qUokg0XZ"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"OBJECTIVE = \"vcn\"\n",
|
||||
"\n",
|
||||
"# Data converter constants.\n",
|
||||
"DATA_CONVERTER_JOB_PREFIX = \"data_converter\"\n",
|
||||
"DATA_CONVERTER_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/data-converter\"\n",
|
||||
"DATA_CONVERTER_MACHINE_TYPE = \"n1-highmem-8\"\n",
|
||||
"\n",
|
||||
"# Training constants.\n",
|
||||
"TRAINING_JOB_PREFIX = \"train\"\n",
|
||||
"TRAIN_CONTAINER_URI = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/movinet-train\"\n",
|
||||
"TRAIN_MACHINE_TYPE = \"n1-highmem-16\"\n",
|
||||
"TRAIN_ACCELERATOR_TYPE = \"NVIDIA_TESLA_V100\"\n",
|
||||
"TRAIN_NUM_GPU = 2\n",
|
||||
"\n",
|
||||
"# Evaluation constants.\n",
|
||||
"EVALUATION_METRIC = \"accuracy\"\n",
|
||||
"\n",
|
||||
"# Export constants.\n",
|
||||
"EXPORT_JOB_PREFIX = \"export\"\n",
|
||||
"EXPORT_CONTAINER_URI = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/movinet-model-export\"\n",
|
||||
"EXPORT_MACHINE_TYPE = \"n1-highmem-8\"\n",
|
||||
"\n",
|
||||
"# Prediction constants.\n",
|
||||
"# You can adjust accelerator types and machine types to get faster predictions.\n",
|
||||
"UPLOAD_JOB_PREFIX = \"upload\"\n",
|
||||
"PREDICTION_CONTAINER_URI = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/movinet-serve\"\n",
|
||||
"PREDICTION_PORT = 8501\n",
|
||||
"PREDICTION_ACCELERATOR_COUNT = 1\n",
|
||||
"PREDICTION_ACCELERATOR_TYPE = \"NVIDIA_TESLA_T4\"\n",
|
||||
"PREDICTION_MACHINE_TYPE = \"n1-standard-4\"\n",
|
||||
"PREDICTION_JOB_PREFIX = \"predict\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ZZFPe_GezXg8"
|
||||
},
|
||||
"source": [
|
||||
"### Define common helper functions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "XcYUGwr-AJGY"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"import numpy as np\n",
|
||||
"import tensorflow as tf\n",
|
||||
"import yaml\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_job_name_with_datetime(prefix: str):\n",
|
||||
" \"\"\"Returns a timestamped job name with the given prefix.\"\"\"\n",
|
||||
" return prefix + datetime.now().strftime(\"_%Y%m%d_%H%M%S\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def print_response_instance(json_str: str, label_map: dict[int, str]):\n",
|
||||
" \"\"\"Prints summary of a prediction JSON result from the model response.\"\"\"\n",
|
||||
" json_obj = json.loads(json_str)\n",
|
||||
" if \"prediction\" not in json_obj:\n",
|
||||
" print(\"Error:\", json_str)\n",
|
||||
" return\n",
|
||||
" instance = json_obj[\"instance\"]\n",
|
||||
" prediction = json_obj[\"prediction\"]\n",
|
||||
" gcs_uri = instance[\"content\"]\n",
|
||||
" time_start = instance.get(\"timeSegmentStart\", \"0.0s\")\n",
|
||||
" time_end = instance.get(\"timeSegmentEnd\", \"Infinity\")\n",
|
||||
" max_idx = np.argmax(prediction)\n",
|
||||
" print(f\"{gcs_uri} {time_start}-{time_end}:\", label_map[max_idx])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_label_map(label_map_yaml_filepath: str) -> tuple[dict[int, str], int]:\n",
|
||||
" \"\"\"Reads label map from a YAML file and returns the label map with the number of classes.\"\"\"\n",
|
||||
" with tf.io.gfile.GFile(label_map_yaml_filepath, \"rb\") as input_file:\n",
|
||||
" label_map = yaml.safe_load(input_file.read())[\"label_map\"]\n",
|
||||
" num_classes = max(label_map.keys()) + 1\n",
|
||||
" return label_map, num_classes\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_best_trial(model_dir, max_trial_count, evaluation_metric):\n",
|
||||
" \"\"\"Finds the best trial directory and eval results from a hyperparameter tuning job.\"\"\"\n",
|
||||
" best_trial_dir = \"\"\n",
|
||||
" best_trial_evaluation_results = {}\n",
|
||||
" best_performance = -1\n",
|
||||
"\n",
|
||||
" for i in range(max_trial_count):\n",
|
||||
" current_trial = i + 1\n",
|
||||
" current_trial_dir = os.path.join(model_dir, \"trial_\" + str(current_trial))\n",
|
||||
" current_trial_best_ckpt_dir = os.path.join(current_trial_dir, \"best_ckpt\")\n",
|
||||
" current_trial_best_ckpt_evaluation_filepath = os.path.join(\n",
|
||||
" current_trial_best_ckpt_dir, \"info.json\"\n",
|
||||
" )\n",
|
||||
" with tf.io.gfile.GFile(current_trial_best_ckpt_evaluation_filepath, \"rb\") as f:\n",
|
||||
" eval_metric_results = json.load(f)\n",
|
||||
" current_performance = eval_metric_results[evaluation_metric]\n",
|
||||
" if current_performance > best_performance:\n",
|
||||
" best_performance = current_performance\n",
|
||||
" best_trial_dir = current_trial_dir\n",
|
||||
" best_trial_evaluation_results = eval_metric_results\n",
|
||||
" return best_trial_dir, best_trial_evaluation_results\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def find_checkpoint_in_dir(checkpoint_dir: str):\n",
|
||||
" \"\"\"Finds a checkpoint path relative to the directory.\"\"\"\n",
|
||||
" for root, dirs, files in tf.io.gfile.walk(checkpoint_dir):\n",
|
||||
" for file in files:\n",
|
||||
" if file.endswith(\".index\"):\n",
|
||||
" return os.path.join(root, os.path.splitext(file)[0])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def upload_checkpoint_to_gcs(checkpoint_url: str):\n",
|
||||
" \"\"\"Uploads a compressed .tar.gz checkpoint at the given URL to Cloud Storage.\"\"\"\n",
|
||||
" filename = os.path.basename(checkpoint_url)\n",
|
||||
" checkpoint_name = filename.replace(\".tar.gz\", \"\")\n",
|
||||
" print(\"Download checkpoint from\", checkpoint_url, \"and store to\", CHECKPOINT_BUCKET)\n",
|
||||
" ! wget $checkpoint_url -O $filename\n",
|
||||
" ! mkdir -p $checkpoint_name\n",
|
||||
" ! tar -xvzf $filename -C $checkpoint_name\n",
|
||||
"\n",
|
||||
" checkpoint_path = find_checkpoint_in_dir(checkpoint_name)\n",
|
||||
" checkpoint_path = os.path.relpath(checkpoint_path, checkpoint_name)\n",
|
||||
"\n",
|
||||
" ! gsutil cp -r $checkpoint_name $CHECKPOINT_BUCKET/\n",
|
||||
" checkpoint_uri = os.path.join(CHECKPOINT_BUCKET, checkpoint_name, checkpoint_path)\n",
|
||||
" print(\"Checkpoint uploaded to\", checkpoint_uri)\n",
|
||||
" return checkpoint_uri\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def upload_config_to_gcs(url: str):\n",
|
||||
" \"\"\"Uploads a config file at the given URL to Cloud Storage.\"\"\"\n",
|
||||
" filename = os.path.basename(url)\n",
|
||||
" destination = os.path.join(CONFIG_DIR, filename)\n",
|
||||
" print(\"Copy\", url, \"to\", destination)\n",
|
||||
" ! wget \"$url\" -O \"$filename\"\n",
|
||||
" ! gsutil cp \"$filename\" \"$destination\"\n",
|
||||
" return destination"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "RB_xY9ipr7ZU"
|
||||
},
|
||||
"source": [
|
||||
"## Train new models\n",
|
||||
"This section shows how to train new models.\n",
|
||||
"1. Convert input data to training formats\n",
|
||||
"2. Create hyperparameter tuning jobs to train new models\n",
|
||||
"3. Find and export best models\n",
|
||||
"\n",
|
||||
"If you already trained models, please go to the section `Test Trained models`.\n",
|
||||
"\n",
|
||||
"Please select a model:\n",
|
||||
"* `model_id`: MoViNet model variant ID, one of `a0`, `a1`, `a2`, `a3`, `a4`, `a5`. The model with a larger number requires more resources to train, and is expected to have a higher accuracy and latency. Here, we use `a0` for demonstration purpose.\n",
|
||||
"* `model_mode`: MoViNet model type, either `base` or `stream`. The base model has a slightly higher accuracy, while the streaming model is optimized for streaming and faster CPU inference. See [official MoViNet docs](https://github.com/tensorflow/models/tree/master/official/projects/movinet) for more information.\n",
|
||||
"\n",
|
||||
"**Note**: The prediction container only supports base model (non-streaming) for now. If you train a streaming model, you need to download the model and refer to the [MoViNet official guide](https://github.com/tensorflow/models/blob/master/official/projects/movinet/movinet_streaming_model_training_and_inference.ipynb) for running predictions locally."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "3Ry1mw6AHLTy"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model_id = \"a0\" # @param [\"a0\", \"a1\", \"a2\", \"a3\", \"a4\", \"a5\"]\n",
|
||||
"model_mode = \"base\" # @param [\"base\", \"stream\"]\n",
|
||||
"is_stream = model_mode == \"stream\"\n",
|
||||
"model_name = f\"movinet_{model_id}_{model_mode}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Prepare input data for training\n",
|
||||
"\n",
|
||||
"Prepare data in the format as described [here](https://cloud.google.com/vertex-ai/docs/video-data/classification/prepare-data), and then convert them to the training formats by running the cell below:\n",
|
||||
"\n",
|
||||
"* `input_file_path`: The input file path to the prepared data.\n",
|
||||
"* `input_file_type`: The input file type, such as `csv` or `jsonl`.\n",
|
||||
"* `split_ratio`: Three comma separated floats indicating the proportion of data to split into train/validation/test. They must add up to 1.\n",
|
||||
"* `num_shard`: Three comma separated integers indicating the shards for train/validation/test.\n",
|
||||
"* `output_dir`: The output directory, which will contain converted train/test/validation data.\n",
|
||||
"* `output_fps`: The sampling rate of the video; Frames per second."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "IndQ_m6ddUEM"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This job will convert input data as training format, with given split ratios\n",
|
||||
"# and number of shards on train/test/validation.\n",
|
||||
"\n",
|
||||
"data_converter_job_name = get_job_name_with_datetime(\n",
|
||||
" DATA_CONVERTER_JOB_PREFIX + \"_\" + OBJECTIVE\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"input_file_path = \"\" # @param {type:\"string\"}\n",
|
||||
"input_file_type = \"csv\" # @param [\"csv\", \"jsonl\"]\n",
|
||||
"output_fps = 5 # @param {type:\"integer\"}\n",
|
||||
"split_ratio = \"0.8,0.1,0.1\"\n",
|
||||
"num_shard = \"10,10,10\"\n",
|
||||
"data_converter_output_dir = os.path.join(BUCKET_URI, data_converter_job_name)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"worker_pool_specs = [\n",
|
||||
" {\n",
|
||||
" \"machine_spec\": {\n",
|
||||
" \"machine_type\": DATA_CONVERTER_MACHINE_TYPE,\n",
|
||||
" },\n",
|
||||
" \"replica_count\": 1,\n",
|
||||
" \"container_spec\": {\n",
|
||||
" \"image_uri\": DATA_CONVERTER_CONTAINER,\n",
|
||||
" \"command\": [],\n",
|
||||
" \"args\": [\n",
|
||||
" \"--input_file_path=%s\" % input_file_path,\n",
|
||||
" \"--input_file_type=%s\" % input_file_type,\n",
|
||||
" \"--objective=%s\" % OBJECTIVE,\n",
|
||||
" \"--num_shard=%s\" % num_shard,\n",
|
||||
" \"--split_ratio=%s\" % split_ratio,\n",
|
||||
" \"--output_dir=%s\" % data_converter_output_dir,\n",
|
||||
" \"--output_fps=%d\" % output_fps,\n",
|
||||
" ],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"data_converter_custom_job = aiplatform.CustomJob(\n",
|
||||
" display_name=data_converter_job_name,\n",
|
||||
" project=PROJECT_ID,\n",
|
||||
" worker_pool_specs=worker_pool_specs,\n",
|
||||
" staging_bucket=STAGING_BUCKET,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"data_converter_custom_job.run()\n",
|
||||
"\n",
|
||||
"input_train_data_path = os.path.join(data_converter_output_dir, \"train.tfrecord*\")\n",
|
||||
"input_validation_data_path = os.path.join(data_converter_output_dir, \"val.tfrecord*\")\n",
|
||||
"label_map_path = os.path.join(data_converter_output_dir, \"label_map.yaml\")\n",
|
||||
"print(\"input_train_data_path for training: \", input_train_data_path)\n",
|
||||
"print(\"input_validation_data_path for training: \", input_validation_data_path)\n",
|
||||
"print(\"label_map_path for prediction: \", label_map_path)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "aaff6f5be7f6"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Vertex AI custom job with hyperparameter tuning\n",
|
||||
"\n",
|
||||
"You use the Vertex AI SDK to create and run the [hyperparameter tuning job](https://cloud.google.com/vertex-ai/docs/training/hyperparameter-tuning-overview) with Vertex AI Model Garden training docker images.\n",
|
||||
"\n",
|
||||
"#### Define the following specifications\n",
|
||||
"\n",
|
||||
"* `worker_pool_specs`: A list of dictionaries specifying the machine type and docker image. This example defines a single node cluster with one `n1-standard-4` machine with 2 `NVIDIA_TESLA_V100` GPUs.\n",
|
||||
"\n",
|
||||
" **Note**: We recommend using 8 GPUs for MoViNet-A2 and larger. Since loading video data requires a lot of GPU memory, it is recommended to experiment with a small batch size first.\n",
|
||||
"* `parameter_spec`: Dictionary specifying the parameters to optimize. The dictionary key is the string assigned to the command line argument for each hyperparameter in your training application code, and the dictionary value is the parameter specification. The parameter specification includes the type, min/max values, and scale for the hyperparameter.\n",
|
||||
"* `metric_spec`: Dictionary specifying the metric to optimize. The dictionary key is the `hyperparameter_metric_tag` that you set in your training application code, and the value is the optimization goal."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "um_XKbmpTaHx"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from google.cloud.aiplatform import hyperparameter_tuning as hpt\n",
|
||||
"\n",
|
||||
"# Input train and validation datasets can be found from the section above\n",
|
||||
"# `Prepare input data for training`.\n",
|
||||
"# Or, set prepared datasets paths if already exist.\n",
|
||||
"# input_train_data_path = \"\"\n",
|
||||
"# input_validation_data_path = \"\"\n",
|
||||
"# label_map_path = \"\"\n",
|
||||
"\n",
|
||||
"train_job_name = get_job_name_with_datetime(f\"{TRAINING_JOB_PREFIX}_{model_name}\")\n",
|
||||
"model_dir = os.path.join(BUCKET_URI, train_job_name)\n",
|
||||
"label_map, num_classes = get_label_map(label_map_path)\n",
|
||||
"\n",
|
||||
"# Uploads pretained checkpoint to GCS bucket.\n",
|
||||
"init_checkpoint = f\"https://storage.googleapis.com/tf_model_garden/vision/movinet/{model_name}_with_backbone.tar.gz\"\n",
|
||||
"init_checkpoint = upload_checkpoint_to_gcs(init_checkpoint)\n",
|
||||
"\n",
|
||||
"# Uploads config file according to model_id and streaming options.\n",
|
||||
"config_file = f\"{model_id}_stream\" if is_stream else model_id\n",
|
||||
"config_file = f\"https://raw.githubusercontent.com/tensorflow/models/master/official/projects/movinet/configs/yaml/movinet_{config_file}_gpu.yaml\"\n",
|
||||
"config_file = upload_config_to_gcs(config_file)\n",
|
||||
"\n",
|
||||
"# The parameters here are mainly for demonstration purpose. Please update them\n",
|
||||
"# for better performance.\n",
|
||||
"trainer_args = {\n",
|
||||
" \"experiment\": \"movinet_kinetics600\",\n",
|
||||
" \"config_file\": config_file,\n",
|
||||
" \"input_train_data_path\": input_train_data_path,\n",
|
||||
" \"input_validation_data_path\": input_validation_data_path,\n",
|
||||
" \"init_checkpoint\": init_checkpoint,\n",
|
||||
" \"model_dir\": model_dir,\n",
|
||||
" \"num_classes\": num_classes,\n",
|
||||
" \"global_batch_size\": 4,\n",
|
||||
" \"prefetch_buffer_size\": 8,\n",
|
||||
" \"shuffle_buffer_size\": 32,\n",
|
||||
" \"train_steps\": 2000,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"worker_pool_specs = [\n",
|
||||
" {\n",
|
||||
" \"machine_spec\": {\n",
|
||||
" \"machine_type\": TRAIN_MACHINE_TYPE,\n",
|
||||
" \"accelerator_type\": TRAIN_ACCELERATOR_TYPE,\n",
|
||||
" # Each training job uses TRAIN_NUM_GPU GPUs.\n",
|
||||
" \"accelerator_count\": TRAIN_NUM_GPU,\n",
|
||||
" },\n",
|
||||
" \"replica_count\": 1,\n",
|
||||
" \"container_spec\": {\n",
|
||||
" \"image_uri\": TRAIN_CONTAINER_URI,\n",
|
||||
" \"args\": [\n",
|
||||
" \"--mode=train_and_eval\",\n",
|
||||
" \"--params_override=runtime.num_gpus=%d\" % TRAIN_NUM_GPU,\n",
|
||||
" ]\n",
|
||||
" + [\"--{}={}\".format(k, v) for k, v in trainer_args.items()],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"metric_spec = {\"model_performance\": \"maximize\"}\n",
|
||||
"\n",
|
||||
"# These learning rates might not be optimal for your selected model type; To\n",
|
||||
"# tune learning rates, try hpt.DoubleParameterSpec with more trials.\n",
|
||||
"LEARNING_RATES = [1e-3, 3e-3]\n",
|
||||
"MAX_TRIAL_COUNT = len(LEARNING_RATES)\n",
|
||||
"parameter_spec = {\n",
|
||||
" \"learning_rate\": hpt.DiscreteParameterSpec(values=LEARNING_RATES, scale=\"linear\"),\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"print(worker_pool_specs, metric_spec, parameter_spec)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "HwcCjwlBTQIz"
|
||||
},
|
||||
"source": [
|
||||
"#### Run the hyperparameter tuning job\n",
|
||||
"* `max_trial_count`: Sets an upper bound on the number of trials the service will run. The recommended practice is to start with a smaller number of trials and get a sense of how impactful your chosen hyperparameters are before scaling up.\n",
|
||||
"\n",
|
||||
"* `parallel_trial_count`: If you use parallel trials, the service provisions multiple training processing clusters. The worker pool spec that you specify when creating the job is used for each individual training cluster. Increasing the number of parallel trials reduces the amount of time the hyperparameter tuning job takes to run; however, it can reduce the effectiveness of the job overall. This is because the default tuning strategy uses results of previous trials to inform the assignment of values in subsequent trials.\n",
|
||||
"\n",
|
||||
"* `search_algorithm`: The available search algorithms are grid, random, or default (None). The default option applies Bayesian optimization to search the space of possible hyperparameter values and is the recommended algorithm.\n",
|
||||
"\n",
|
||||
"Click on the generated link in the output to see your run in the Cloud Console."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "aec22792ee84"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"train_custom_job = aiplatform.CustomJob(\n",
|
||||
" display_name=train_job_name,\n",
|
||||
" project=PROJECT_ID,\n",
|
||||
" worker_pool_specs=worker_pool_specs,\n",
|
||||
" staging_bucket=STAGING_BUCKET,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"train_hpt_job = aiplatform.HyperparameterTuningJob(\n",
|
||||
" display_name=train_job_name,\n",
|
||||
" custom_job=train_custom_job,\n",
|
||||
" metric_spec=metric_spec,\n",
|
||||
" parameter_spec=parameter_spec,\n",
|
||||
" max_trial_count=MAX_TRIAL_COUNT,\n",
|
||||
" parallel_trial_count=MAX_TRIAL_COUNT,\n",
|
||||
" project=PROJECT_ID,\n",
|
||||
" search_algorithm=None,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"train_hpt_job.run()\n",
|
||||
"\n",
|
||||
"print(\"model_dir is:\", model_dir)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "vugUfJEC2HrK"
|
||||
},
|
||||
"source": [
|
||||
"### Export model in Tensorflow SavedModel format"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "09Rz1AYspK19"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This job will export models from TF checkpoints to TF saved model format.\n",
|
||||
"# model_dir is from the section above.\n",
|
||||
"best_trial_dir, best_trial_evaluation_results = get_best_trial(\n",
|
||||
" model_dir, MAX_TRIAL_COUNT, EVALUATION_METRIC\n",
|
||||
")\n",
|
||||
"best_checkpoint_path = find_checkpoint_in_dir(f\"{best_trial_dir}/best_ckpt/\")\n",
|
||||
"print(\"best_trial_dir: \", best_trial_dir)\n",
|
||||
"print(\"best_trial_evaluation_results: \", best_trial_evaluation_results)\n",
|
||||
"print(\"best_checkpoint: \", best_checkpoint_path)\n",
|
||||
"\n",
|
||||
"container_args = {\n",
|
||||
" \"export_path\": f\"{model_dir}/best_model\",\n",
|
||||
" \"model_id\": model_id,\n",
|
||||
" \"num_classes\": num_classes,\n",
|
||||
" \"causal\": is_stream,\n",
|
||||
" \"checkpoint_path\": best_checkpoint_path,\n",
|
||||
" \"assert_checkpoint_objects_matched\": False,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"if is_stream:\n",
|
||||
" container_args.update(\n",
|
||||
" {\n",
|
||||
" \"conv_type\": \"2plus1d\",\n",
|
||||
" \"se_type\": \"2plus3d\",\n",
|
||||
" \"activation\": \"hard_swish\",\n",
|
||||
" \"gating_activation\": \"hard_sigmoid\",\n",
|
||||
" \"use_positional_encoding\": model_id in {\"a3\", \"a4\", \"a5\"},\n",
|
||||
" }\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"worker_pool_specs = [\n",
|
||||
" {\n",
|
||||
" \"machine_spec\": {\n",
|
||||
" \"machine_type\": EXPORT_MACHINE_TYPE,\n",
|
||||
" },\n",
|
||||
" \"replica_count\": 1,\n",
|
||||
" \"container_spec\": {\n",
|
||||
" \"image_uri\": EXPORT_CONTAINER_URI,\n",
|
||||
" \"args\": [\"--{}={}\".format(k, v) for k, v in container_args.items()],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"model_export_job_name = get_job_name_with_datetime(EXPORT_JOB_PREFIX + \"_\" + OBJECTIVE)\n",
|
||||
"model_export_custom_job = aiplatform.CustomJob(\n",
|
||||
" display_name=model_export_job_name,\n",
|
||||
" project=PROJECT_ID,\n",
|
||||
" worker_pool_specs=worker_pool_specs,\n",
|
||||
" staging_bucket=STAGING_BUCKET,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model_export_custom_job.run()\n",
|
||||
"\n",
|
||||
"print(\"best model is saved to: \", container_args[\"export_path\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "g0BGaofgsMsy"
|
||||
},
|
||||
"source": [
|
||||
"## Test trained models\n",
|
||||
"This section shows the way to test with trained models.\n",
|
||||
"1. Upload and deploy models to the [Vertex AI Model Registry](https://cloud.google.com/vertex-ai/docs/model-registry/introduction)\n",
|
||||
"2. Run batch predictions\n",
|
||||
"\n",
|
||||
"**Note:** The prediction container only works with the base model. If you trained a streaming model, download the model from the exported path and refer to the [MoViNet official guide](https://github.com/tensorflow/models/blob/master/official/projects/movinet/movinet_streaming_model_training_and_inference.ipynb) for running predictions locally."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "gdlca3BOypXU"
|
||||
},
|
||||
"source": [
|
||||
"### Upload model to Vertex AI Model Registry"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NYuQowyZEtxK"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"upload_job_name = get_job_name_with_datetime(f\"{UPLOAD_JOB_PREFIX}_{model_name}\")\n",
|
||||
"\n",
|
||||
"serving_env = {\n",
|
||||
" \"MODEL_PATH\": container_args[\"export_path\"],\n",
|
||||
" \"BATCH_SIZE\": 1, # Select a larger batch size to accelerate GPU prediction.\n",
|
||||
" \"NUM_FRAMES\": 32,\n",
|
||||
" \"FPS\": output_fps,\n",
|
||||
" \"OVERLAP_FRAMES\": 24,\n",
|
||||
" \"OBJECTIVE\": OBJECTIVE,\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"model = aiplatform.Model.upload(\n",
|
||||
" display_name=model_name,\n",
|
||||
" serving_container_image_uri=PREDICTION_CONTAINER_URI,\n",
|
||||
" serving_container_ports=[PREDICTION_PORT],\n",
|
||||
" serving_container_predict_route=\"/predict\",\n",
|
||||
" serving_container_health_route=\"/ping\",\n",
|
||||
" serving_container_environment_variables=serving_env,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model.wait()\n",
|
||||
"\n",
|
||||
"print(\"The uploaded model name is: \", model_name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "9SZsKGeS3x6S"
|
||||
},
|
||||
"source": [
|
||||
"### Run batch predictions\n",
|
||||
"\n",
|
||||
"We will now run batch predictions with the trained MoViNet clip classification model with [Vertex AI Batch Prediction](https://cloud.google.com/vertex-ai/docs/predictions/get-batch-predictions).\n",
|
||||
"\n",
|
||||
"Please prepare an input JSONL file where each line follows [this format](https://cloud.google.com/vertex-ai/docs/video-data/classification/get-predictions?hl=en#input_data_requirements) and store it in a Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "vbIW9me1F2RY"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Path to the prediction input JSONL file.\n",
|
||||
"test_jsonl_path = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"predict_job_name = get_job_name_with_datetime(f\"{PREDICTION_JOB_PREFIX}_{model_name}\")\n",
|
||||
"predict_destination_prefix = os.path.join(STAGING_BUCKET, predict_job_name)\n",
|
||||
"\n",
|
||||
"batch_prediction_job = model.batch_predict(\n",
|
||||
" job_display_name=predict_job_name,\n",
|
||||
" gcs_source=test_jsonl_path,\n",
|
||||
" gcs_destination_prefix=predict_destination_prefix,\n",
|
||||
" machine_type=PREDICTION_MACHINE_TYPE,\n",
|
||||
" accelerator_count=PREDICTION_ACCELERATOR_COUNT,\n",
|
||||
" accelerator_type=PREDICTION_ACCELERATOR_TYPE,\n",
|
||||
" max_replica_count=1,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"batch_prediction_job.wait()\n",
|
||||
"\n",
|
||||
"print(batch_prediction_job.display_name)\n",
|
||||
"print(batch_prediction_job.resource_name)\n",
|
||||
"print(batch_prediction_job.state)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ik-XPjfx9OCE"
|
||||
},
|
||||
"source": [
|
||||
"You can then read the prediction response JSONL files in the output directory:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "tdkW9e5B9OU1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# The label map file was generated from the section above (`Prepare input data for training`).\n",
|
||||
"for file in tf.io.gfile.glob(os.path.join(predict_destination_prefix, \"*/*\")):\n",
|
||||
" with tf.io.gfile.GFile(file, \"r\") as f:\n",
|
||||
" for line in f:\n",
|
||||
" print_response_instance(line, label_map)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "kkH2nrpdp4sp"
|
||||
},
|
||||
"source": [
|
||||
"## Clean up"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Ax6vQVZhp9pR"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete the trained model.\n",
|
||||
"model.delete()\n",
|
||||
"# Delete custom and hpt jobs.\n",
|
||||
"if data_converter_custom_job.list(filter=f'display_name=\"{data_converter_job_name}\"'):\n",
|
||||
" data_converter_custom_job.delete()\n",
|
||||
"if train_hpt_job.list(filter=f'display_name=\"{train_job_name}\"'):\n",
|
||||
" train_hpt_job.delete()\n",
|
||||
"if model_export_custom_job.list(filter=f'display_name=\"{model_export_job_name}\"'):\n",
|
||||
" model_export_custom_job.delete()\n",
|
||||
"if batch_prediction_job.list(filter=f'display_name=\"{predict_job_name}\"'):\n",
|
||||
" batch_prediction_job.delete()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_movinet_clip_classification.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -321,10 +321,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"IS_COLAB = False\n",
|
||||
"# from google.colab import auth\n",
|
||||
"# auth.authenticate_user()\n",
|
||||
"# IS_COLAB=True"
|
||||
"# auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -409,6 +407,9 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"IS_COLAB = \"google.colab\" in sys.modules\n",
|
||||
"if (\n",
|
||||
" SERVICE_ACCOUNT == \"\"\n",
|
||||
" or SERVICE_ACCOUNT is None\n",
|
||||
@@ -471,6 +472,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"import google.cloud.aiplatform as aiplatform\n",
|
||||
"from kfp.registry import RegistryClient"
|
||||
@@ -498,6 +500,33 @@
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2d242773d707"
|
||||
},
|
||||
"source": [
|
||||
"### Enable Artifact Registry API\n",
|
||||
"You must enable the Artifact Registry API service for your project.\n",
|
||||
"\n",
|
||||
"<a href=\"https://cloud.google.com/artifact-registry/docs/enable-service\">Learn more about Enabling service</a>."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "162b5e8883c2"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gcloud services enable artifactregistry.googleapis.com\n",
|
||||
"\n",
|
||||
"if os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! sudo apt-get update --yes && sudo apt-get --only-upgrade --yes install google-cloud-sdk-cloud-run-proxy google-cloud-sdk-harbourbridge google-cloud-sdk-cbt google-cloud-sdk-gke-gcloud-auth-plugin google-cloud-sdk-kpt google-cloud-sdk-local-extract google-cloud-sdk-minikube google-cloud-sdk-app-engine-java google-cloud-sdk-app-engine-go google-cloud-sdk-app-engine-python google-cloud-sdk-spanner-emulator google-cloud-sdk-bigtable-emulator google-cloud-sdk-nomos google-cloud-sdk-package-go-module google-cloud-sdk-firestore-emulator kubectl google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-python-extras google-cloud-sdk-cloud-build-local google-cloud-sdk-kubectl-oidc google-cloud-sdk-anthos-auth google-cloud-sdk-app-engine-grpc google-cloud-sdk-pubsub-emulator google-cloud-sdk-datalab google-cloud-sdk-skaffold google-cloud-sdk google-cloud-sdk-terraform-tools google-cloud-sdk-config-connector\n",
|
||||
" ! gcloud components update --quiet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
@@ -506,9 +535,7 @@
|
||||
"source": [
|
||||
"## Create repo in Artifact Registry\n",
|
||||
"\n",
|
||||
"First, you create your own (user-defined) repository in the `Artifact Registry`. You use this repository to upload and retrieve your pipeline templates.\n",
|
||||
"\n",
|
||||
"The name of your repo is `quickstart-kfp-repo`"
|
||||
"First, you create your own (user-defined) repository in the `Artifact Registry`. You use this repository to upload and retrieve your pipeline templates."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -519,7 +546,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REPO_NAME = \"quickstart-kfp-repo\"\n",
|
||||
"REPO_NAME = \"my-docker-repo-unique\"\n",
|
||||
"\n",
|
||||
"! gcloud artifacts repositories create {REPO_NAME} --location={REGION} --repository-format=KFP"
|
||||
]
|
||||
@@ -833,7 +860,9 @@
|
||||
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! gsutil rm -r $BUCKET_URI\n",
|
||||
"\n",
|
||||
"! rm -rf custom custom.tar.gz"
|
||||
"! rm -rf custom custom.tar.gz\n",
|
||||
"\n",
|
||||
"! gcloud artifacts repositories delete $REPO_NAME --project {PROJECT_ID} --location {REGION} --quiet"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -319,10 +319,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"IS_COLAB = False\n",
|
||||
"# from google.colab import auth\n",
|
||||
"# auth.authenticate_user()\n",
|
||||
"# IS_COLAB=True"
|
||||
"# auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -407,6 +405,9 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"IS_COLAB = \"google.colab\" in sys.modules\n",
|
||||
"if (\n",
|
||||
" SERVICE_ACCOUNT == \"\"\n",
|
||||
" or SERVICE_ACCOUNT is None\n",
|
||||
@@ -469,6 +470,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"import google.cloud.aiplatform as aiplatform\n",
|
||||
"from kfp.registry import RegistryClient"
|
||||
@@ -496,6 +498,33 @@
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2d242773d707"
|
||||
},
|
||||
"source": [
|
||||
"### Enable Artifact Registry API\n",
|
||||
"You must enable the Artifact Registry API service for your project.\n",
|
||||
"\n",
|
||||
"<a href=\"https://cloud.google.com/artifact-registry/docs/enable-service\">Learn more about Enabling service</a>."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "162b5e8883c2"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gcloud services enable artifactregistry.googleapis.com\n",
|
||||
"\n",
|
||||
"if os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! sudo apt-get update --yes && sudo apt-get --only-upgrade --yes install google-cloud-sdk-cloud-run-proxy google-cloud-sdk-harbourbridge google-cloud-sdk-cbt google-cloud-sdk-gke-gcloud-auth-plugin google-cloud-sdk-kpt google-cloud-sdk-local-extract google-cloud-sdk-minikube google-cloud-sdk-app-engine-java google-cloud-sdk-app-engine-go google-cloud-sdk-app-engine-python google-cloud-sdk-spanner-emulator google-cloud-sdk-bigtable-emulator google-cloud-sdk-nomos google-cloud-sdk-package-go-module google-cloud-sdk-firestore-emulator kubectl google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-python-extras google-cloud-sdk-cloud-build-local google-cloud-sdk-kubectl-oidc google-cloud-sdk-anthos-auth google-cloud-sdk-app-engine-grpc google-cloud-sdk-pubsub-emulator google-cloud-sdk-datalab google-cloud-sdk-skaffold google-cloud-sdk google-cloud-sdk-terraform-tools google-cloud-sdk-config-connector\n",
|
||||
" ! gcloud components update --quiet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
@@ -504,9 +533,7 @@
|
||||
"source": [
|
||||
"## Create repo in Artifact Registry\n",
|
||||
"\n",
|
||||
"First, you create your own (user-defined) repository in the `Artifact Registry`. You use this repository to upload and retreive your pipeline templates.\n",
|
||||
"\n",
|
||||
"The name of your repo is `quickstart-kfp-repo`"
|
||||
"First, you create your own (user-defined) repository in the `Artifact Registry`. You use this repository to upload and retreive your pipeline templates."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -517,7 +544,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REPO_NAME = \"quickstart-kfp-repo\"\n",
|
||||
"REPO_NAME = \"my-docker-repo-unique\"\n",
|
||||
"\n",
|
||||
"! gcloud artifacts repositories create {REPO_NAME} --location={REGION} --repository-format=KFP"
|
||||
]
|
||||
@@ -822,7 +849,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"delete_bucket = True\n",
|
||||
"delete_bucket = False\n",
|
||||
"\n",
|
||||
"endpoint.undeploy_all()\n",
|
||||
"endpoint.delete()\n",
|
||||
@@ -831,7 +858,9 @@
|
||||
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! gsutil rm -r $BUCKET_URI\n",
|
||||
"\n",
|
||||
"! rm -rf custom custom.tar.gz"
|
||||
"! rm -rf custom custom.tar.gz\n",
|
||||
"\n",
|
||||
"! gcloud artifacts repositories delete $REPO_NAME --project {PROJECT_ID} --location {REGION} --quiet"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
+7
-23
@@ -24,7 +24,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "TirJ-SGQseby"
|
||||
@@ -55,7 +54,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dwGLvtIeECLK"
|
||||
@@ -67,7 +65,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "tvgnzT1CKxrO"
|
||||
@@ -107,7 +104,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KEukV6uRk_S3"
|
||||
@@ -138,7 +134,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
@@ -196,7 +191,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "n6IFz75WGCam"
|
||||
@@ -229,16 +223,10 @@
|
||||
"# Evaluation constants.\n",
|
||||
"EVALUATION_METRIC = \"accuracy\"\n",
|
||||
"\n",
|
||||
"# Prediction constants.\n",
|
||||
"# The example in this notebook uses optimized tensorflow runtime dockers.\n",
|
||||
"# You can adjust accelerator types and machine types to get faster predictions.\n",
|
||||
"PREDICTION_ACCELERATOR_TYPE = \"NVIDIA_TESLA_T4\"\n",
|
||||
"PREDICTION_MACHINE_TYPE = \"n1-standard-4\"\n",
|
||||
"DEPLOY_JOB_PREFIX = \"deploy\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ZZFPe_GezXg8"
|
||||
@@ -301,7 +289,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "Q149N3V6Uynm"
|
||||
@@ -330,7 +317,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "8yfBZ1_8VZvq"
|
||||
@@ -363,7 +349,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "RB_xY9ipr7ZU"
|
||||
@@ -389,7 +374,7 @@
|
||||
"- `model_type`: The type of model for deployment.\n",
|
||||
" - `EFFICIENTNET`: A model that is available in Vertex Model Garden image classification training with customizable hyperparameters. Best tailored to be used within Google Cloud, and cannot be exported externally.\n",
|
||||
" - `MAXVIT`: A model that is available in Vertex Model Garden image classification training with customizable hyperparameters. Best tailored to be used within Google Cloud, and cannot be exported externally.\n",
|
||||
" - `COCA`: A model that is available in Vertex Model Garden image classification training with customizable hyperparameters. Best tailored to be used within Google Cloud, and cannot be exported externally.\n",
|
||||
" - `VIT`: A model that is available in Vertex Model Garden image classification training with customizable hyperparameters. Best tailored to be used within Google Cloud, and cannot be exported externally.\n",
|
||||
"- `checkpoint_name`: Optional. The field is reserved for Model Garden model training, based on the provided pre-trained model checkpoint.\n",
|
||||
"- `trainer_config`: Optional. The field is usually used together with the Model Garden model training when passing the customized configs for the trainer.\n",
|
||||
"\n",
|
||||
@@ -404,6 +389,9 @@
|
||||
" 'accelerator_count': '1',\n",
|
||||
" }\n",
|
||||
"```\n",
|
||||
" The global_batch_size should be divisible by accelerator_count.\n",
|
||||
" Supported values for optimizer_type are 'sgd', 'adam', 'adamw', 'lamb', 'rmsprop', 'lars', 'adagrad', and 'slide'.\n",
|
||||
" Supported values for accelerator_count are '1', '2', '4', and '8'.\n",
|
||||
"- `metric_spec`: Dictionary representing metrics to optimize. The dictionary key is the `metric_id`, which is reported by your training job, with possible values being ('loss', 'accuracy') and the dictionary value is the optimization goal of the metric ('minimize' or 'maximize').\n",
|
||||
"For example: `metric_spec = {'loss': 'minimize', 'accuracy': 'maximize'}`\n",
|
||||
"- `parameter_spec`: Dictionary representing parameters to optimize. The dictionary key is the `metric_id`, which is passed into your training job as a command line key word argument, and the dictionary value is the parameter\n",
|
||||
@@ -457,7 +445,7 @@
|
||||
"METRIC_SPEC_VALUE = \"maximize\"\n",
|
||||
"SEARCH_ALGORITHM = \"random\"\n",
|
||||
"MEASUREMENT_SELECTION = \"best\"\n",
|
||||
"MODEL_TYPE = \"COCA\" # @param {type:\"string\"} one of the values [\"COCA\", \"MAXVIT\", \"EFFICIENTNET\"]\n",
|
||||
"MODEL_TYPE = \"MAXVIT\" # @param {type:\"string\"} one of the values [\"MAXVIT\", \"EFFICIENTNET\", \"VIT\"]\n",
|
||||
"\n",
|
||||
"job = aiplatform.AutoMLImageTrainingJob(\n",
|
||||
" display_name=get_job_name_with_datetime(TRAINING_JOB_PREFIX),\n",
|
||||
@@ -478,7 +466,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "HwcCjwlBTQIz"
|
||||
@@ -523,7 +510,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "g0BGaofgsMsy"
|
||||
@@ -544,16 +530,15 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# @title Deploy model from Model Registry\n",
|
||||
"# Model does not support dedicated deployment resources.\n",
|
||||
"# An n1-standard-4 machine with 1 P100 GPU will be used.\n",
|
||||
"\n",
|
||||
"deploy_model_name = get_job_name_with_datetime(DEPLOY_JOB_PREFIX + \"_\" + OBJECTIVE)\n",
|
||||
"print(\"The deployed job name is: \", deploy_model_name)\n",
|
||||
"\n",
|
||||
"endpoint = model.deploy(\n",
|
||||
" deployed_model_display_name=deploy_model_name,\n",
|
||||
" machine_type=PREDICTION_MACHINE_TYPE,\n",
|
||||
" traffic_split={\"0\": 100},\n",
|
||||
" accelerator_type=PREDICTION_ACCELERATOR_TYPE,\n",
|
||||
" accelerator_count=1,\n",
|
||||
" min_replica_count=1,\n",
|
||||
" max_replica_count=1,\n",
|
||||
")\n",
|
||||
@@ -589,7 +574,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "kkH2nrpdp4sp"
|
||||
|
||||
+9
-24
@@ -24,7 +24,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "TirJ-SGQseby"
|
||||
@@ -55,7 +54,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dwGLvtIeECLK"
|
||||
@@ -67,7 +65,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "tvgnzT1CKxrO"
|
||||
@@ -107,7 +104,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KEukV6uRk_S3"
|
||||
@@ -138,7 +134,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
@@ -181,11 +176,11 @@
|
||||
"# You can choose a region from https://cloud.google.com/about/locations.\n",
|
||||
"# Only regions prefixed by \"us\", \"europe\", or \"asia\" are supported.\n",
|
||||
"REGION = \"us-central1\" # @param {type:\"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split('-')[0]\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\"\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"europe\", or \"asia\".'\n",
|
||||
"\n",
|
||||
"! gcloud config set project $PROJECT_ID\n",
|
||||
@@ -196,7 +191,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "n6IFz75WGCam"
|
||||
@@ -226,15 +220,10 @@
|
||||
"# Evaluation constants.\n",
|
||||
"EVALUATION_METRIC = \"AP50\"\n",
|
||||
"\n",
|
||||
"# Prediction constants.\n",
|
||||
"# You can adjust accelerator types and machine types to get faster predictions.\n",
|
||||
"PREDICTION_ACCELERATOR_TYPE = \"NVIDIA_TESLA_T4\"\n",
|
||||
"PREDICTION_MACHINE_TYPE = \"n1-standard-4\"\n",
|
||||
"DEPLOY_JOB_PREFIX = \"deploy\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ZZFPe_GezXg8"
|
||||
@@ -357,7 +346,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "nZLVI9TtUuif"
|
||||
@@ -386,7 +374,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "SZEdBfNZUxQn"
|
||||
@@ -419,7 +406,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "RB_xY9ipr7ZU"
|
||||
@@ -455,10 +441,13 @@
|
||||
" 'optimizer_type': 'sgd',\n",
|
||||
" 'optimizer_momentum': '0.9',\n",
|
||||
" 'train_steps': '10000',\n",
|
||||
" 'accelerator_count': '1',\n",
|
||||
" 'accelerator_count': '2',\n",
|
||||
" 'anchor_size': '8',\n",
|
||||
" }\n",
|
||||
"```\n",
|
||||
" The global_batch_size should be divisible by accelerator_count.\n",
|
||||
" Supported values for optimizer_type are 'sgd', 'adam', 'adamw', 'lamb', 'rmsprop', 'lars', 'adagrad', and 'slide'.\n",
|
||||
" Currently, only '2' is supported for accelerator_count.\n",
|
||||
"- `metric_spec`: Dictionary representing metrics to optimize. The dictionary key is the metric_id, which is reported by your training job, with possible values being ('loss', 'AP50') and the dictionary value is the optimization goal of the metric('minimize' or 'maximize').\n",
|
||||
"For example: `metric_spec = {'loss': 'minimize', 'AP50': 'maximize'}`\n",
|
||||
"- `parameter_spec`:Dictionary representing parameters to optimize. The dictionary key is the `metric_id`, which is passed into your training job as a command line key word argument, and the dictionary value is the parameter\n",
|
||||
@@ -506,7 +495,7 @@
|
||||
" \"global_batch_size\": \"8\",\n",
|
||||
" \"learning_rate\": \"0.001\",\n",
|
||||
" \"train_steps\": \"10000\",\n",
|
||||
" \"accelerator_count\": \"1\",\n",
|
||||
" \"accelerator_count\": \"2\",\n",
|
||||
"}\n",
|
||||
"METRIC_SPEC_KEY = \"AP50\"\n",
|
||||
"METRIC_SPEC_VALUE = \"maximize\"\n",
|
||||
@@ -550,7 +539,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "HwcCjwlBTQIz"
|
||||
@@ -595,7 +583,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "g0BGaofgsMsy"
|
||||
@@ -616,16 +603,15 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# @title Deploy model from Model Registry\n",
|
||||
"# Model does not support dedicated deployment resources.\n",
|
||||
"# An n1-standard-4 machine with 1 P100 GPU will be used.\n",
|
||||
"\n",
|
||||
"deploy_model_name = get_job_name_with_datetime(DEPLOY_JOB_PREFIX + \"_\" + OBJECTIVE)\n",
|
||||
"print(\"The deployed job name is: \", deploy_model_name)\n",
|
||||
"\n",
|
||||
"endpoint = model.deploy(\n",
|
||||
" deployed_model_display_name=deploy_model_name,\n",
|
||||
" machine_type=PREDICTION_MACHINE_TYPE,\n",
|
||||
" traffic_split={\"0\": 100},\n",
|
||||
" accelerator_type=PREDICTION_ACCELERATOR_TYPE,\n",
|
||||
" accelerator_count=1,\n",
|
||||
" min_replica_count=1,\n",
|
||||
" max_replica_count=1,\n",
|
||||
")\n",
|
||||
@@ -661,7 +647,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "frcGP5HFX1XN"
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
"source": [
|
||||
"# The pre-built serving docker image.\n",
|
||||
"# The model artifacts are embedded within the container, except for model weights which will be downloaded during deployment.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
"source": [
|
||||
"# The pre-built serving docker image.\n",
|
||||
"# The model artifacts are embedded within the container, except for model weights which will be downloaded during deployment.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -259,10 +259,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training docker image. It contains training scripts and models.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"\n",
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_pytorch_detectron2.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",
|
||||
|
||||
@@ -0,0 +1,705 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "7d9bbf86da5e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "99c1c3fc2ca5"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden - Falcon Instruct (PEFT)\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_pytorch_falcon_instruct_peft.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/community/model_garden/model_garden_pytorch_falcon_instruct_peft.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/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_pytorch_falcon_instruct_peft.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> (A Python-3 CPU notebook is recommended)\n",
|
||||
" </td>\n",
|
||||
"</table>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3de7470326a2"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates deploying prebuilt Falcon Instruct models, and also finetuning and deploying Falcon Instruct models with performance efficient finetuning libraries ([PEFT](https://github.com/huggingface/peft)) in Vertex AI.\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"- Run inferences locally on prebuilt Falcon Instruct models\n",
|
||||
"- Deploy prebuilt Falcon Instruct models\n",
|
||||
"- Finetune and deploy Falcon Instruct models with PEFT, supporting\n",
|
||||
"\n",
|
||||
"| Models | LoRA |\n",
|
||||
"| :- | :- |\n",
|
||||
"| [tiiuae/falcon-7b-instruct](https://huggingface.co/tiiuae/falcon-7b-instruct) | Y |\n",
|
||||
"| [tiiuae/falcon-40b-instruct](https://huggingface.co/tiiuae/falcon-40b-instruct) | Y |\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "264c07757582"
|
||||
},
|
||||
"source": [
|
||||
"## Before you begin\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": "ioensNKM8ned"
|
||||
},
|
||||
"source": [
|
||||
"### Colab only\n",
|
||||
"Run the following commands for Colab and skip this section if you are using Workbench."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "2707b02ef5df"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" ! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()\n",
|
||||
" # Install gdown for downloading example training images.\n",
|
||||
" ! pip3 install gdown\n",
|
||||
"\n",
|
||||
" # Restart the notebook kernel after installs.\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)\n",
|
||||
"! pip3 install transformers==4.31.0\n",
|
||||
"! pip3 install einops==0.6.1\n",
|
||||
"! pip3 install accelerate==0.21.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "bb7adab99e41"
|
||||
},
|
||||
"source": [
|
||||
"### Setup Google Cloud project\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. [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs.\n",
|
||||
"\n",
|
||||
"1. [Create a service account](https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console) with `Vertex AI User` and `Storage Object Admin` roles for deploying fine tuned model to Vertex AI endpoint."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "6c460088b873"
|
||||
},
|
||||
"source": [
|
||||
"Fill following variables for experiments environment:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "855d6b96f291"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cloud project id.\n",
|
||||
"PROJECT_ID = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The region you want to launch jobs in.\n",
|
||||
"REGION = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The Cloud Storage bucket for storing experiments output.\n",
|
||||
"BUCKET_URI = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"! gcloud config set project $PROJECT_ID\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"STAGING_BUCKET = os.path.join(BUCKET_URI, \"temporal\")\n",
|
||||
"EXPERIMENT_BUCKET = os.path.join(BUCKET_URI, \"peft\")\n",
|
||||
"DATA_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"data\")\n",
|
||||
"MODEL_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"model\")\n",
|
||||
"\n",
|
||||
"# The service account looks like:\n",
|
||||
"# '@.iam.gserviceaccount.com'\n",
|
||||
"# Please go to https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console\n",
|
||||
"# and create service account with `Vertex AI User` and `Storage Object Admin` roles.\n",
|
||||
"# The service account for deploying fine tuned model.\n",
|
||||
"SERVICE_ACCOUNT = \"\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e828eb320337"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI API"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "12cd25839741"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=STAGING_BUCKET)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2cc825514deb"
|
||||
},
|
||||
"source": [
|
||||
"### Define constants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "b42bd4fa2b2d"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training and serving docker images.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-peft-train\"\n",
|
||||
"PREDICTION_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-peft-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "0c250872074f"
|
||||
},
|
||||
"source": [
|
||||
"### Define common functions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "354da31189dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_job_name_with_datetime(prefix: str):\n",
|
||||
" \"\"\"Gets the job name with date time when triggering training or deployment\n",
|
||||
" jobs in Vertex AI.\n",
|
||||
" \"\"\"\n",
|
||||
" return prefix + datetime.now().strftime(\"_%Y%m%d_%H%M%S\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def deploy_model(\n",
|
||||
" model_name,\n",
|
||||
" base_model_id,\n",
|
||||
" finetuned_lora_model_path,\n",
|
||||
" service_account,\n",
|
||||
" task,\n",
|
||||
" machine_type=\"n1-standard-8\",\n",
|
||||
" accelerator_type=\"NVIDIA_TESLA_V100\",\n",
|
||||
"):\n",
|
||||
" \"\"\"Deploys trained models into Vertex AI.\"\"\"\n",
|
||||
" endpoint = aiplatform.Endpoint.create(display_name=f\"{model_name}-endpoint\")\n",
|
||||
" serving_env = {\n",
|
||||
" \"BASE_MODEL_ID\": base_model_id,\n",
|
||||
" \"TASK\": task,\n",
|
||||
" }\n",
|
||||
" if finetuned_lora_model_path:\n",
|
||||
" serving_env[\"FINETUNED_LORA_MODEL_PATH\"] = finetuned_lora_model_path\n",
|
||||
" model = aiplatform.Model.upload(\n",
|
||||
" display_name=model_name,\n",
|
||||
" serving_container_image_uri=PREDICTION_DOCKER_URI,\n",
|
||||
" serving_container_ports=[7080],\n",
|
||||
" serving_container_predict_route=\"/predictions/peft_serving\",\n",
|
||||
" serving_container_health_route=\"/ping\",\n",
|
||||
" serving_container_environment_variables=serving_env,\n",
|
||||
" )\n",
|
||||
" model.deploy(\n",
|
||||
" endpoint=endpoint,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=1,\n",
|
||||
" deploy_request_timeout=1800,\n",
|
||||
" service_account=service_account,\n",
|
||||
" )\n",
|
||||
" return model, endpoint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "06e73cb3f412"
|
||||
},
|
||||
"source": [
|
||||
"## Run inferences locally with prebuilt Falcon Instruct models\n",
|
||||
"\n",
|
||||
"You will need at least 16GB of memory to swiftly run inference with Falcon-7B-Instruct."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "3ea64305957f"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import transformers\n",
|
||||
"from transformers import AutoTokenizer\n",
|
||||
"\n",
|
||||
"model = \"tiiuae/falcon-7b-instruct\"\n",
|
||||
"\n",
|
||||
"tokenizer = AutoTokenizer.from_pretrained(model)\n",
|
||||
"pipeline = transformers.pipeline(\n",
|
||||
" \"text-generation\",\n",
|
||||
" model=model,\n",
|
||||
" tokenizer=tokenizer,\n",
|
||||
" torch_dtype=torch.bfloat16,\n",
|
||||
" trust_remote_code=True,\n",
|
||||
" device_map=\"auto\",\n",
|
||||
")\n",
|
||||
"sequences = pipeline(\n",
|
||||
" \"Girafatron is obsessed with giraffes, the most glorious animal on the face of this Earth. Girafatron believes all other animals are irrelevant when compared to the glorious majesty of the giraffe.\\nDaniel: Hello, Girafatron!\\nGirafatron:\",\n",
|
||||
" max_length=200,\n",
|
||||
" do_sample=True,\n",
|
||||
" top_k=10,\n",
|
||||
" num_return_sequences=1,\n",
|
||||
" eos_token_id=tokenizer.eos_token_id,\n",
|
||||
")\n",
|
||||
"for seq in sequences:\n",
|
||||
" print(f\"Result: {seq['generated_text']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "8neJc8CnDDpu"
|
||||
},
|
||||
"source": [
|
||||
"## Deploy prebuilt Falcon Instruct models\n",
|
||||
"\n",
|
||||
"This section deploys prebuilt Falcon Instruct models on the Endpoint. The model deployment step will take ~15 minutes to complete.\n",
|
||||
"\n",
|
||||
"The peak GPU memory usages for [tiiuae/falcon-7b-instruct](https://huggingface.co/tiiuae/falcon-7b-instruct), and [tiiuae/falcon-40b-instruct](https://huggingface.co/tiiuae/falcon-40b-instruct) are ~15.5G and ~38.2G separately with the default settings. We use V100 in deployments as an example. Please use A100 (40G) or A100 (80G) to get better inferences."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2MjaORIIFDVu"
|
||||
},
|
||||
"source": [
|
||||
"Set the prebuilt model id."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "E8OiHHNNE_wj"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"prebuilt_model_id = \"tiiuae/falcon-7b-instruct\" # @param [\"tiiuae/falcon-7b-instruct\", \"tiiuae/falcon-40b-instruct\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dHFW7yvjaVFV"
|
||||
},
|
||||
"source": [
|
||||
"We use the PEFT serving images to deploy prebuilt Falcon Instruct models, by setting finetuning LoRA model paths as empty."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Uak1pyEeExYM"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Sets V100 to deploy tiiuae/falcon-7b-instruct as an example.\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"\n",
|
||||
"# Sets A100 (40G) to deploy tiiuae/falcon-7b-instruct or tiiuae/falcon-40b-instruct.\n",
|
||||
"# machine_type = \"a2-highgpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"\n",
|
||||
"# Sets A100 (80G) to deploy falcon-40b-instruct models for faster inferences.\n",
|
||||
"# machine_type = \"a2-ultragpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"\n",
|
||||
"model_without_peft, endpoint_without_peft = deploy_model(\n",
|
||||
" model_name=get_job_name_with_datetime(prefix=\"falcon-instruct-serve\"),\n",
|
||||
" base_model_id=prebuilt_model_id,\n",
|
||||
" finetuned_lora_model_path=\"\", # This will avoid override finetuning models.\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" task=\"instruct-lora\",\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
")\n",
|
||||
"print(\"endpoint_name:\", endpoint_without_peft.name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "sGKIjgmDFRW2"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: The prebuilt model weights will be downloaded on the fly from the orginal location after the deployment succeeds. Thus additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint.\n",
|
||||
"\n",
|
||||
"Once deployment succeeds, you can send requests to the endpoint with text prompts.\n",
|
||||
"\n",
|
||||
"Example:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"Human: What is a car?\n",
|
||||
"Assistant: A car, or a motor car, is a road-connected human-transportation system used to move people or goods from one place to another. The term also encompasses a wide range of vehicles, including motorboats, trains, and aircrafts. Cars typically have four wheels, a cabin for passengers, and an engine or motor. They have been around since the early 19th century and are now one of the most popular forms of transportation, used for daily commuting, shopping, and other purposes.\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "rDHsCOqvFYBi"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# # Loads an existing endpoint as below.\n",
|
||||
"# endpoint_name = endpoint_without_peft.name\n",
|
||||
"# aip_endpoint_name = (\n",
|
||||
"# f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_name}\"\n",
|
||||
"# )\n",
|
||||
"# endpoint_without_peft = aiplatform.Endpoint(aip_endpoint_name)\n",
|
||||
"instances = [\n",
|
||||
" {\"prompt\": \"What is a car?\"},\n",
|
||||
"]\n",
|
||||
"response = endpoint_without_peft.predict(instances=instances)\n",
|
||||
"\n",
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" print(prediction[\"generated_text\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e70e3519ff8b"
|
||||
},
|
||||
"source": [
|
||||
"## Finetune and deploy Falcon Instruct models with PEFT\n",
|
||||
"\n",
|
||||
"This section demonstrates how to finetune and dpeloy Falcon Instruct models with PEFT LoRA."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "5qCrm_kJH5cz"
|
||||
},
|
||||
"source": [
|
||||
"Set the base model id."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "N3UBLiYrM3sU"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"base_model_id = \"tiiuae/falcon-7b-instruct\" # @param [\"tiiuae/falcon-7b-instruct\", \"tiiuae/falcon-40b-instruct\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "iWGwJHqI7LMs"
|
||||
},
|
||||
"source": [
|
||||
"### Finetune"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KKEYoRfiHDVv"
|
||||
},
|
||||
"source": [
|
||||
"Use the Vertex AI SDK to create and run the custom training jobs with Vertex AI Model Garden training images.\n",
|
||||
"\n",
|
||||
"This example uses the dataset [timdettmers/openassistant-guanaco](https://huggingface.co/datasets/timdettmers/openassistant-guanaco).\n",
|
||||
"\n",
|
||||
"The peak GPU memory usages are ~11G and ~34G for finetuning LoRA models for [tiiuae/falcon-7b-instruct](https://huggingface.co/tiiuae/falcon-7b-instruct), and [tiiuae/falcon-40b-instruct](https://huggingface.co/tiiuae/falcon-40b-instruct) separately with default training parameters and the example dataset. Falcon-7b-instruct can be finetuned on 1 P100/V100, and falcon-40b-instruct can be finetuned on 1 A100 (40G)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "65467b361315"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dataset_name = \"timdettmers/openassistant-guanaco\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Worker pool spec.\n",
|
||||
"# Uses V100 to finetune falcon-70b-instruct.\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"# Uses A100 to finetune falcon-40b-instruct.\n",
|
||||
"# machine_type = \"a2-highgpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"replica_count = 1\n",
|
||||
"accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Setup training job.\n",
|
||||
"job_name = get_job_name_with_datetime(\"falcon-instruct-lora-train\")\n",
|
||||
"train_job = aiplatform.CustomContainerTrainingJob(\n",
|
||||
" display_name=job_name,\n",
|
||||
" container_uri=TRAIN_DOCKER_URI,\n",
|
||||
")\n",
|
||||
"output_dir = os.path.join(MODEL_BUCKET, job_name)\n",
|
||||
"output_dir_gcsfuse = output_dir.replace(\"gs://\", \"/gcs/\")\n",
|
||||
"\n",
|
||||
"# Pass training arguments and launch job.\n",
|
||||
"max_steps = 10\n",
|
||||
"train_job.run(\n",
|
||||
" args=[\n",
|
||||
" \"--task=instruct-lora\",\n",
|
||||
" f\"--pretrained_model_id={base_model_id}\",\n",
|
||||
" f\"--dataset_name={dataset_name}\",\n",
|
||||
" f\"--output_dir={output_dir_gcsfuse}\",\n",
|
||||
" \"--lora_rank=64\",\n",
|
||||
" \"--lora_alpha=16\",\n",
|
||||
" \"--lora_dropout=0.1\",\n",
|
||||
" \"--warmup_ratio=0.03\",\n",
|
||||
" f\"--max_steps={max_steps}\",\n",
|
||||
" \"--max_seq_length=512\",\n",
|
||||
" \"--learning_rate=2e-4\",\n",
|
||||
" ],\n",
|
||||
" replica_count=replica_count,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=accelerator_count,\n",
|
||||
" boot_disk_size_gb=500,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(\"Trained models were saved in: \", output_dir)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "jqmCtkGnhDmp"
|
||||
},
|
||||
"source": [
|
||||
"### Deploy\n",
|
||||
"This section uploads the model to Model Registry and deploys it on the Endpoint.\n",
|
||||
"\n",
|
||||
"The model deployment step will take ~15 minutes to complete.\n",
|
||||
"\n",
|
||||
"The peak GPU memory usages for [tiiuae/falcon-7b-instruct](https://huggingface.co/tiiuae/falcon-7b-instruct), and [tiiuae/falcon-40b-instruct](https://huggingface.co/tiiuae/falcon-40b-instruct) with LoRA weights are ~15.5G and ~38.2G separately with the default settings. We use V100 in deployments as an example. Please use A100 (40G) or A100 (80G) to get better inferences."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "bf55e38815dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Sets V100 to deploy tiiuae/falcon-7b-instruct as an example.\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"\n",
|
||||
"# Sets A100 (40G) to deploy tiiuae/falcon-7b-instruct or tiiuae/falcon-40b-instruct.\n",
|
||||
"# machine_type = \"a2-highgpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"\n",
|
||||
"# Sets A100 (80G) to deploy falcon-40b-instruct models for faster inferences.\n",
|
||||
"# machine_type = \"a2-ultragpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"\n",
|
||||
"model_with_peft, endpoint_with_peft = deploy_model(\n",
|
||||
" model_name=get_job_name_with_datetime(prefix=\"falcon-instruct-peft-serve\"),\n",
|
||||
" base_model_id=base_model_id,\n",
|
||||
" finetuned_lora_model_path=os.path.join(output_dir, \"checkpoint-\" + str(max_steps)),\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" task=\"instruct-lora\",\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
")\n",
|
||||
"print(\"endpoint_name:\", endpoint_with_peft.name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "80b3fd2ace09"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: After the deployment succeeds, the base model weights will be downloaded one the fly from the original location and LoRA model weights will be downloaded from the GCS bucket used in training above. Thus additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint.\n",
|
||||
"\n",
|
||||
"Once deployment succeeds, you can send requests to the endpoint with text prompts.\n",
|
||||
"\n",
|
||||
"Example:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"Human: What is a car?\n",
|
||||
"Assistant: A car, or a motor car, is a road-connected human-transportation system used to move people or goods from one place to another. The term also encompasses a wide range of vehicles, including motorboats, trains, and aircrafts. Cars typically have four wheels, a cabin for passengers, and an engine or motor. They have been around since the early 19th century and are now one of the most popular forms of transportation, used for daily commuting, shopping, and other purposes.\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "4ab04da3ec9a"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# # Loads an existing endpoint as below.\n",
|
||||
"# endpoint_name = endpoint_with_peft.name\n",
|
||||
"# aip_endpoint_name = (\n",
|
||||
"# f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_name}\"\n",
|
||||
"# )\n",
|
||||
"# endpoint_with_peft = aiplatform.Endpoint(aip_endpoint_name)\n",
|
||||
"instances = [\n",
|
||||
" {\"prompt\": \"What is a car?\"},\n",
|
||||
"]\n",
|
||||
"response = endpoint_with_peft.predict(instances=instances)\n",
|
||||
"\n",
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" print(prediction[\"generated_text\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "af21a3cff1e0"
|
||||
},
|
||||
"source": [
|
||||
"## Clean up resources"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "911406c1561e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete custom train jobs.\n",
|
||||
"train_job.delete()\n",
|
||||
"\n",
|
||||
"# Undeploy model and delete endpoint.\n",
|
||||
"endpoint_without_peft.delete(force=True)\n",
|
||||
"endpoint_with_peft.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete models.\n",
|
||||
"model_without_peft.delete()\n",
|
||||
"model_with_peft.delete()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_pytorch_falcon_instruct_peft.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,761 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "7d9bbf86da5e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "99c1c3fc2ca5"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden - LLaMA2 (PEFT)\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_pytorch_llama2_peft.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/community/model_garden/model_garden_pytorch_llama2_peft.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/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_pytorch_llama2_peft.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> (A Python-3 CPU notebook is recommended)\n",
|
||||
" </td>\n",
|
||||
"</table>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3de7470326a2"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates deploying prebuilt LLaMA2 models, and also finetuning and deploying LLaMA2 models with performance efficient finetuning libraries ([PEFT](https://github.com/huggingface/peft)) in Vertex AI. This notebook also uses [Text moderation APIs](https://cloud.google.com/natural-language/docs/moderating-text) to analyze predictions against a list of safety attributes\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"- Deploy prebuilt LLaMA2 models\n",
|
||||
"- Finetune and deploy LLaMA2 models with PEFT\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "264c07757582"
|
||||
},
|
||||
"source": [
|
||||
"## Before you begin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ioensNKM8ned"
|
||||
},
|
||||
"source": [
|
||||
"### Colab only\n",
|
||||
"Run the following commands for Colab and skip this section if you are using Workbench."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "2707b02ef5df"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" ! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
" ! pip3 install ipython pandas[output_formatting] google-cloud-language==2.10.0\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()\n",
|
||||
" # Install gdown for downloading example training images.\n",
|
||||
" ! pip3 install gdown\n",
|
||||
"\n",
|
||||
" # Restart the notebook kernel after installs.\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "bb7adab99e41"
|
||||
},
|
||||
"source": [
|
||||
"### Setup Google Cloud project\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. [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs.\n",
|
||||
"\n",
|
||||
"1. [Create a service account](https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console) with `Vertex AI User` and `Storage Object Admin` roles for deploying fine tuned model to Vertex AI endpoint."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "6c460088b873"
|
||||
},
|
||||
"source": [
|
||||
"Fill following variables for experiments environment:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "855d6b96f291"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cloud project id.\n",
|
||||
"PROJECT_ID = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The region you want to launch jobs in.\n",
|
||||
"REGION = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The Cloud Storage bucket for storing experiments output.\n",
|
||||
"BUCKET_URI = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"! gcloud config set project $PROJECT_ID\n",
|
||||
"! gcloud services enable language.googleapis.com\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"STAGING_BUCKET = os.path.join(BUCKET_URI, \"temporal\")\n",
|
||||
"EXPERIMENT_BUCKET = os.path.join(BUCKET_URI, \"peft\")\n",
|
||||
"DATA_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"data\")\n",
|
||||
"BASE_MODEL_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"base_model\")\n",
|
||||
"MODEL_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"model\")\n",
|
||||
"PREDICTION_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"prediction\")\n",
|
||||
"\n",
|
||||
"# The service account looks like:\n",
|
||||
"# '@.iam.gserviceaccount.com'\n",
|
||||
"# Please go to https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console\n",
|
||||
"# and create service account with `Vertex AI User` and `Storage Object Admin` roles.\n",
|
||||
"# The service account for deploying fine tuned model.\n",
|
||||
"SERVICE_ACCOUNT = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"from google.colab import auth\n",
|
||||
"\n",
|
||||
"auth.authenticate_user(project_id=PROJECT_ID)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e828eb320337"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI API"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "12cd25839741"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=STAGING_BUCKET)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2cc825514deb"
|
||||
},
|
||||
"source": [
|
||||
"### Define constants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "b42bd4fa2b2d"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training and serving docker images.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-peft-train\"\n",
|
||||
"PREDICTION_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-peft-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "0c250872074f"
|
||||
},
|
||||
"source": [
|
||||
"### Define common functions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "354da31189dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"from google.cloud import aiplatform, language\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_job_name_with_datetime(prefix: str):\n",
|
||||
" \"\"\"Gets the job name with date time when triggering training or deployment\n",
|
||||
" jobs in Vertex AI.\n",
|
||||
" \"\"\"\n",
|
||||
" return prefix + datetime.now().strftime(\"_%Y%m%d_%H%M%S\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def deploy_model(\n",
|
||||
" model_name,\n",
|
||||
" base_model_id,\n",
|
||||
" finetuned_lora_model_path,\n",
|
||||
" service_account,\n",
|
||||
" task,\n",
|
||||
" machine_type=\"n1-standard-8\",\n",
|
||||
" accelerator_type=\"NVIDIA_TESLA_V100\",\n",
|
||||
" accelerator_count=1,\n",
|
||||
"):\n",
|
||||
" \"\"\"Deploys trained models into Vertex AI.\"\"\"\n",
|
||||
" endpoint = aiplatform.Endpoint.create(display_name=f\"{model_name}-endpoint\")\n",
|
||||
" serving_env = {\n",
|
||||
" \"BASE_MODEL_ID\": base_model_id,\n",
|
||||
" \"TASK\": task,\n",
|
||||
" }\n",
|
||||
" if finetuned_lora_model_path:\n",
|
||||
" serving_env[\"FINETUNED_LORA_MODEL_PATH\"] = finetuned_lora_model_path\n",
|
||||
" model = aiplatform.Model.upload(\n",
|
||||
" display_name=model_name,\n",
|
||||
" serving_container_image_uri=PREDICTION_DOCKER_URI,\n",
|
||||
" serving_container_ports=[7080],\n",
|
||||
" serving_container_predict_route=\"/predictions/peft_serving\",\n",
|
||||
" serving_container_health_route=\"/ping\",\n",
|
||||
" serving_container_environment_variables=serving_env,\n",
|
||||
" )\n",
|
||||
" model.deploy(\n",
|
||||
" endpoint=endpoint,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=accelerator_count,\n",
|
||||
" deploy_request_timeout=1800,\n",
|
||||
" service_account=service_account,\n",
|
||||
" )\n",
|
||||
" return model, endpoint\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def moderate_text(text: str) -> language.ModerateTextResponse:\n",
|
||||
" \"\"\"Calls Vertex AI APIs to analyze text moderations.\"\"\"\n",
|
||||
" client = language.LanguageServiceClient()\n",
|
||||
" document = language.Document(\n",
|
||||
" content=text,\n",
|
||||
" type_=language.Document.Type.PLAIN_TEXT,\n",
|
||||
" )\n",
|
||||
" return client.moderate_text(document=document)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def show_text_moderation(text: str, response: language.ModerateTextResponse):\n",
|
||||
" \"\"\"Shows text moderation results.\"\"\"\n",
|
||||
" import pandas as pd\n",
|
||||
"\n",
|
||||
" def confidence(category: language.ClassificationCategory) -> float:\n",
|
||||
" return category.confidence\n",
|
||||
"\n",
|
||||
" columns = [\"category\", \"confidence\"]\n",
|
||||
" categories = sorted(response.moderation_categories, key=confidence, reverse=True)\n",
|
||||
" data = ((category.name, category.confidence) for category in categories)\n",
|
||||
" df = pd.DataFrame(columns=columns, data=data)\n",
|
||||
"\n",
|
||||
" print(f\"Text analyzed:\\n{text}\")\n",
|
||||
" print(df.to_markdown(index=False, tablefmt=\"presto\", floatfmt=\".0%\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ivs2RK093c8X"
|
||||
},
|
||||
"source": [
|
||||
"## Access LLaMA2 pretrained and finetuned models\n",
|
||||
"The original models from Meta are converted into the Hugging Face format for finetuning and serving in Vertex AI.\n",
|
||||
"After clicking the agreement of LLaMA2 in Vertex AI Model Garden, a Cloud Storage bucket will be shared to access LLaMA2 pretrained and finetuned models."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Jwn4PcTf4EMt"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"VERTEX_AI_MODEL_GARDEN_LLAMA2 = \"\" # This will be shared once click the agreement of LLaMA2 in Vertex AI Model Garden.\n",
|
||||
"! gsutil cp -R $VERTEX_AI_MODEL_GARDEN_LLAMA2 $BASE_MODEL_BUCKET"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2MjaORIIFDVu"
|
||||
},
|
||||
"source": [
|
||||
"Set the base model id."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "E8OiHHNNE_wj"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"base_model_name = \"llama2-7b-chat-hf\" # @param [\"llama2-7b-hf\", \"llama2-7b-chat-hf\", \"llama2-13b-hf\", \"llama2-13b-chat-hf\", \"llama2-70b-hf\", \"llama2-70b-chat-hf\"]\n",
|
||||
"base_model_id = os.path.join(BASE_MODEL_BUCKET, base_model_name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "8neJc8CnDDpu"
|
||||
},
|
||||
"source": [
|
||||
"## Deploy prebuilt LLaMA2 models\n",
|
||||
"\n",
|
||||
"This section deploys prebuilt LLaMA2 models on Vertex AI."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dHFW7yvjaVFV"
|
||||
},
|
||||
"source": [
|
||||
"You use the PEFT serving images to deploy prebuilt LLaMA2 models, by setting finetuning LoRA model paths as empty. The model deployment step will take ~15 minutes to complete."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Uak1pyEeExYM"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Sets V100 to deploy LLaMA2 7B.\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Sets A100 (40G) to deploy LLaMA2 13B.\n",
|
||||
"# machine_type = \"a2-highgpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"# accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Sets 4 A100 (40G) to deploy LLaMA2 70B models.\n",
|
||||
"# machine_type = \"a2-highgpu-4g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"# accelerator_count = 4\n",
|
||||
"\n",
|
||||
"# The supported precision loading types are \"4bit\", \"8bit\", \"float16\" and \"float32\".\n",
|
||||
"precision_loading_type = \"float16\"\n",
|
||||
"\n",
|
||||
"model_without_peft, endpoint_without_peft = deploy_model(\n",
|
||||
" model_name=get_job_name_with_datetime(prefix=\"llama2-serve\"),\n",
|
||||
" base_model_id=base_model_id,\n",
|
||||
" finetuned_lora_model_path=\"\", # This will avoid override finetuning models.\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" task=\"causal-language-modeling-lora\",\n",
|
||||
" precision_loading_type=precision_loading_type,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=1,\n",
|
||||
")\n",
|
||||
"print(\"endpoint_name:\", endpoint_without_peft.name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "sGKIjgmDFRW2"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: The prebuilt model weights will be downloaded on the fly from $BASE_MODEL_BUCKET after the deployment succeeds. Thus additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint.\n",
|
||||
"\n",
|
||||
"Once deployment succeeds, you can send requests to the endpoint with text prompts.\n",
|
||||
"\n",
|
||||
"Example:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"Human: What is a car?\n",
|
||||
"Assistant: A car, or a motor car, is a road-connected human-transportation system used to move people or goods from one place to another. The term also encompasses a wide range of vehicles, including motorboats, trains, and aircrafts. Cars typically have four wheels, a cabin for passengers, and an engine or motor. They have been around since the early 19th century and are now one of the most popular forms of transportation, used for daily commuting, shopping, and other purposes.\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "rDHsCOqvFYBi"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Loads an existing endpoint as below.\n",
|
||||
"# endpoint_name = endpoint_without_peft.name\n",
|
||||
"# aip_endpoint_name = (\n",
|
||||
"# f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_name}\"\n",
|
||||
"# )\n",
|
||||
"# endpoint_without_peft = aiplatform.Endpoint(aip_endpoint_name)\n",
|
||||
"instances = [\n",
|
||||
" {\"prompt\": \"Write a poem about Valencia.\"},\n",
|
||||
"]\n",
|
||||
"response = endpoint_without_peft.predict(instances=instances)\n",
|
||||
"\n",
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" print(prediction[\"generated_text\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "aUMsewPDj_pS"
|
||||
},
|
||||
"source": [
|
||||
"Text moderation analyzes a document against a list of safety attributes, which include \"harmful categories\" and topics that may be considered sensitive."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "EE_GCSVVkBWj"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" generated_text = prediction[\"generated_text\"]\n",
|
||||
" # Send a request to the API.\n",
|
||||
" response = moderate_text(generated_text)\n",
|
||||
"\n",
|
||||
" # Show the results.\n",
|
||||
" show_text_moderation(generated_text, response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e70e3519ff8b"
|
||||
},
|
||||
"source": [
|
||||
"## Finetune and deploy LLaMA2 models with PEFT\n",
|
||||
"\n",
|
||||
"This section demonstrates how to finetune and deploy LLaMA2 models with PEFT LoRA."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "iWGwJHqI7LMs"
|
||||
},
|
||||
"source": [
|
||||
"### Finetune"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KKEYoRfiHDVv"
|
||||
},
|
||||
"source": [
|
||||
"Use the Vertex AI SDK to create and run the custom training jobs with Vertex AI Model Garden training images.\n",
|
||||
"\n",
|
||||
"This example uses the dataset [Abirate/english_quotes](https://huggingface.co/datasets/Abirate/english_quotes)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "65467b361315"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dataset_name = \"Abirate/english_quotes\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Worker pool spec.\n",
|
||||
"# Finetunes LLaMA2 7B with 1 V100 (16G).\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Finetunes and LLaMA2 13B with 1 A100 (40G).\n",
|
||||
"# machine_type = \"a2-highgpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"# accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Finetunes and LLaMA2 70B with 4 A100 (40G).\n",
|
||||
"# machine_type = \"a2-highgpu-4g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"# accelerator_count = 4\n",
|
||||
"\n",
|
||||
"replica_count = 1\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Setup training job.\n",
|
||||
"job_name = get_job_name_with_datetime(\"llama2-lora-train\")\n",
|
||||
"train_job = aiplatform.CustomContainerTrainingJob(\n",
|
||||
" display_name=job_name,\n",
|
||||
" container_uri=TRAIN_DOCKER_URI,\n",
|
||||
")\n",
|
||||
"output_dir = os.path.join(MODEL_BUCKET, job_name)\n",
|
||||
"output_dir_gcsfuse = output_dir.replace(\"gs://\", \"/gcs/\")\n",
|
||||
"\n",
|
||||
"# Pass training arguments and launch job.\n",
|
||||
"train_job.run(\n",
|
||||
" args=[\n",
|
||||
" \"--task=causal-language-modeling-lora\",\n",
|
||||
" f\"--pretrained_model_id={base_model_id}\",\n",
|
||||
" f\"--dataset_name={dataset_name}\",\n",
|
||||
" f\"--output_dir={output_dir_gcsfuse}\",\n",
|
||||
" \"--lora_rank=16\",\n",
|
||||
" \"--lora_alpha=32\",\n",
|
||||
" \"--lora_dropout=0.05\",\n",
|
||||
" \"--warmup_steps=10\",\n",
|
||||
" \"--max_steps=10\",\n",
|
||||
" \"--learning_rate=2e-4\",\n",
|
||||
" ],\n",
|
||||
" replica_count=replica_count,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=accelerator_count,\n",
|
||||
" boot_disk_size_gb=500,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(\"Trained models were saved in: \", output_dir)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "jqmCtkGnhDmp"
|
||||
},
|
||||
"source": [
|
||||
"### Deploy\n",
|
||||
"This section uploads the model to Model Registry and deploys it on the Endpoint.\n",
|
||||
"\n",
|
||||
"The model deployment step will take ~15 minutes to complete."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "bf55e38815dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Sets V100 to deploy LLaMA2 7B.\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Sets A100 (40G) to deploy LLaMA2 13B.\n",
|
||||
"# machine_type = \"a2-highgpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"# accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Sets 4 A100 (40G) to deploy LLaMA2 70B models.\n",
|
||||
"# machine_type = \"a2-highgpu-4g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"# accelerator_count = 4\n",
|
||||
"\n",
|
||||
"# The supported precision loading types are \"4bit\", \"8bit\", \"float16\" and \"float32\".\n",
|
||||
"precision_loading_type = \"float16\"\n",
|
||||
"\n",
|
||||
"model_with_peft, endpoint_with_peft = deploy_model(\n",
|
||||
" model_name=get_job_name_with_datetime(prefix=\"llama2-serve\"),\n",
|
||||
" base_model_id=base_model_id,\n",
|
||||
" finetuned_lora_model_path=output_dir,\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" task=\"causal-language-modeling-lora\",\n",
|
||||
" precision_loading_type=precision_loading_type,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=1,\n",
|
||||
")\n",
|
||||
"print(\"endpoint_name:\", endpoint_with_peft.name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "80b3fd2ace09"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: After the deployment succeeds, the base model weights will be downloaded on the fly from $BASE_MODEL_BUCKET and LoRA model weights will be downloaded from the GCS bucket used in training above. Thus additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint.\n",
|
||||
"\n",
|
||||
"Once deployment succeeds, you can send requests to the endpoint with text prompts.\n",
|
||||
"\n",
|
||||
"Example:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"Human: What is a car?\n",
|
||||
"Assistant: A car, or a motor car, is a road-connected human-transportation system used to move people or goods from one place to another. The term also encompasses a wide range of vehicles, including motorboats, trains, and aircrafts. Cars typically have four wheels, a cabin for passengers, and an engine or motor. They have been around since the early 19th century and are now one of the most popular forms of transportation, used for daily commuting, shopping, and other purposes.\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "4ab04da3ec9a"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Loads an existing endpoint as below.\n",
|
||||
"# endpoint_name = endpoint_with_peft.name\n",
|
||||
"# aip_endpoint_name = (\n",
|
||||
"# f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_name}\"\n",
|
||||
"# )\n",
|
||||
"# endpoint_with_peft = aiplatform.Endpoint(aip_endpoint_name)\n",
|
||||
"instances = [\n",
|
||||
" {\"prompt\": \"What is a car?\"},\n",
|
||||
"]\n",
|
||||
"response = endpoint_with_peft.predict(instances=instances)\n",
|
||||
"\n",
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" print(prediction[\"generated_text\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "yfPDd91qlSlI"
|
||||
},
|
||||
"source": [
|
||||
"Text moderation analyzes a document against a list of safety attributes, which include \"harmful categories\" and topics that may be considered sensitive."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "lEUyncyklTEE"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" generated_text = prediction[\"generated_text\"]\n",
|
||||
" # Send a request to the API.\n",
|
||||
" response = moderate_text(generated_text)\n",
|
||||
"\n",
|
||||
" # Show the results.\n",
|
||||
" show_text_moderation(generated_text, response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "af21a3cff1e0"
|
||||
},
|
||||
"source": [
|
||||
"## Clean up resources"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "911406c1561e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete custom train jobs.\n",
|
||||
"train_job.delete()\n",
|
||||
"\n",
|
||||
"# Undeploy model and delete endpoint.\n",
|
||||
"endpoint_without_peft.delete(force=True)\n",
|
||||
"endpoint_with_peft.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete models.\n",
|
||||
"model_without_peft.delete()\n",
|
||||
"model_with_peft.delete()\n",
|
||||
"\n",
|
||||
"# Delete Cloud Storage objects that were created\n",
|
||||
"delete_bucket = False\n",
|
||||
"if delete_bucket:\n",
|
||||
" ! gsutil -m rm -r $EXPERIMENT_BUCKET"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_pytorch_llama2_peft.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "7d9bbf86da5e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "99c1c3fc2ca5"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden - OpenCLIP\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_pytorch_open_clip.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/community/model_garden/model_garden_pytorch_open_clip.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/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_pytorch_open_clip.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>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3de7470326a2"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates finetuning [OpenCLIP](https://github.com/mlfoundations/open_clip) with [CC3M](https://ai.google.com/research/ConceptualCaptions/download) dataset and deploying it on Vertex AI for online prediction.\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"- Finetune the OpenCLIP model with [Vertex AI custom training](https://cloud.google.com/vertex-ai/docs/training/overview).\n",
|
||||
"- Upload the model to [Vertex AI Model Registry](https://cloud.google.com/vertex-ai/docs/model-registry/introduction).\n",
|
||||
"- Deploy the model to a [Vertex AI Endpoint resource](https://cloud.google.com/vertex-ai/docs/predictions/using-private-endpoints).\n",
|
||||
"- Run online predictions for zero-shot image classification.\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "264c07757582"
|
||||
},
|
||||
"source": [
|
||||
"## Setup environment\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": "d73ffa0c0b83"
|
||||
},
|
||||
"source": [
|
||||
"### Colab only"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "2707b02ef5df"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if \"google.colab\" in str(get_ipython()):\n",
|
||||
" ! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()\n",
|
||||
"\n",
|
||||
" # Restart the notebook kernel after installs.\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "bb7adab99e41"
|
||||
},
|
||||
"source": [
|
||||
"### Setup Google Cloud project\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. [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs.\n",
|
||||
"\n",
|
||||
"1. [Create a service account](https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console) with `Vertex AI User`, `Storage Object Admin`, and `GCS Storage Bucket Owner roles` roles for deploying fine tuned model to Vertex AI endpoint."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "6c460088b873"
|
||||
},
|
||||
"source": [
|
||||
"Fill the following variables for experiments environment:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "a12c23679315"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cloud project id.\n",
|
||||
"PROJECT_ID = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The region you want to launch jobs in.\n",
|
||||
"REGION = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The GCS bucket for storing experiments output. Fill it without the 'gs://' prefix.\n",
|
||||
"GCS_BUCKET = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The service account for deploying fine tuned model.\n",
|
||||
"# The service account looks like:\n",
|
||||
"# '<account_name>@<project>.iam.gserviceaccount.com'\n",
|
||||
"# Follow step 5 above to create this account.\n",
|
||||
"SERVICE_ACCOUNT = \"\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "12c249e14a5d"
|
||||
},
|
||||
"source": [
|
||||
"### Download data to Google Cloud Storage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "fb0259bfd059"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Install the library for downloading training data.\n",
|
||||
"!pip install img2dataset==1.41.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "1d6b99305e7e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"!gcloud storage cp gs://gcc-data/Validation/GCC-1.1.0-Validation.tsv ./data.tsv # Download list of URLs.\n",
|
||||
"!sed -i '1s/^/caption\\turl\\n/' data.tsv # Add column name."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "f36f82f74dde"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Download images from URLs. It takes around 2min.\n",
|
||||
"output_folder = \"data\"\n",
|
||||
"!img2dataset --url_list data.tsv --input_format \"tsv\"\\\n",
|
||||
" --output_folder {output_folder}\\\n",
|
||||
" --url_col \"url\" --caption_col \"caption\" --output_format webdataset\\\n",
|
||||
" --processes_count {os.cpu_count()} --thread_count {os.cpu_count()*4}\\\n",
|
||||
" --image_size 256"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "514ee0208d47"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"GCS_data_dir = f\"{GCS_BUCKET}/CC3M-val-wds\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "b90bbd80476a"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Upload data to GCS.\n",
|
||||
"!gcloud storage cp -r data gs://{GCS_data_dir}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "7ffc8c1aed83"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Get total number of samples, which is required by OpenCLIP training.\n",
|
||||
"import json\n",
|
||||
"\n",
|
||||
"n_samples = 0\n",
|
||||
"for filename in [f for f in os.listdir(output_folder) if f.endswith(\"_stats.json\")]:\n",
|
||||
" with open(os.path.join(output_folder, filename), \"r\") as f:\n",
|
||||
" n_samples += json.load(f)[\"successes\"]\n",
|
||||
"print(n_samples)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e828eb320337"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI SDK for Python\n",
|
||||
"\n",
|
||||
"Initialize the Vertex AI SDK for Python for your project and corresponding bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "12cd25839741"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=GCS_BUCKET)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2cc825514deb"
|
||||
},
|
||||
"source": [
|
||||
"### Define constants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "b42bd4fa2b2d"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training docker image. It contains training scripts and models.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-open-clip-train\"\n",
|
||||
"\n",
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-open-clip-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "0c250872074f"
|
||||
},
|
||||
"source": [
|
||||
"### Define common functions for job operations and test data preparations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "354da31189dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import base64\n",
|
||||
"from datetime import datetime\n",
|
||||
"from io import BytesIO\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import requests\n",
|
||||
"from PIL import Image\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def create_job_name(prefix):\n",
|
||||
" \"\"\"Create a job name string with a prefix.\"\"\"\n",
|
||||
" user = os.environ.get(\"USER\")\n",
|
||||
" now = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n",
|
||||
" job_name = f\"{prefix}-{user}-{now}\"\n",
|
||||
" return job_name\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def deploy_model(checkpoint, model, precision, task):\n",
|
||||
" \"\"\"Deploy a model to Vertex AI endpoint.\"\"\"\n",
|
||||
" model_name = \"openclip\"\n",
|
||||
" endpoint = aiplatform.Endpoint.create(display_name=f\"{model_name}-{task}-endpoint\")\n",
|
||||
" serving_env = {\n",
|
||||
" \"CHECKPOINT\": checkpoint,\n",
|
||||
" \"MODEL\": model,\n",
|
||||
" \"PRECISION\": precision,\n",
|
||||
" \"TASK\": task,\n",
|
||||
" }\n",
|
||||
" model = aiplatform.Model.upload(\n",
|
||||
" display_name=model_name,\n",
|
||||
" serving_container_image_uri=SERVE_DOCKER_URI,\n",
|
||||
" serving_container_ports=[7080],\n",
|
||||
" serving_container_predict_route=\"/predictions/transformers_serving\",\n",
|
||||
" serving_container_health_route=\"/ping\",\n",
|
||||
" serving_container_environment_variables=serving_env,\n",
|
||||
" )\n",
|
||||
" model.deploy(\n",
|
||||
" endpoint=endpoint,\n",
|
||||
" machine_type=\"n1-standard-4\",\n",
|
||||
" accelerator_type=\"NVIDIA_TESLA_V100\",\n",
|
||||
" accelerator_count=1,\n",
|
||||
" deploy_request_timeout=1800,\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" )\n",
|
||||
" return model, endpoint\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def download_image(url):\n",
|
||||
" response = requests.get(url)\n",
|
||||
" return Image.open(BytesIO(response.content)).convert(\"RGB\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def image_to_base64(image):\n",
|
||||
" buffer = BytesIO()\n",
|
||||
" image.save(buffer, format=\"PNG\")\n",
|
||||
" image_str = base64.b64encode(buffer.getvalue()).decode(\"utf-8\")\n",
|
||||
" return image_str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def plot_images(images, rows=1, cols=None):\n",
|
||||
" fig, axes = plt.subplots(rows, cols if cols is not None else len(images))\n",
|
||||
" for ax, img in zip(axes, images):\n",
|
||||
" ax.imshow(img)\n",
|
||||
" ax.axis(\"off\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e70e3519ff8b"
|
||||
},
|
||||
"source": [
|
||||
"## Fine tune the model\n",
|
||||
"\n",
|
||||
"This section fine tunes the OpenCLIP model with [CC3M](https://ai.google.com/research/ConceptualCaptions/download) dataset validation split for the image-text pre-training. It loads the pretrained checkpoint by OpenAI. You use a small model of ***RN50*** here; check [this list](https://github.com/mlfoundations/open_clip#pretrained-model-interface) for other options supported by OpenAI checkpoints, such as ***RN50, ViT-B-32, etc***.\n",
|
||||
"\n",
|
||||
"One `n1-standard-4` machine with 1 `NVIDIA_TESLA_V100` is required to run the fine-tuning job. The fine-tuning job takes about 3min to complete training for 2 epochs.\n",
|
||||
"\n",
|
||||
"The fine-tuned model will be saved after the job finishs and it can then be loaded for inference."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "55dabb1b02e3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"machine_type = \"n1-standard-4\"\n",
|
||||
"gpu_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"num_gpus = 1\n",
|
||||
"\n",
|
||||
"job_name = create_job_name(\"openclip\")\n",
|
||||
"\n",
|
||||
"model_name = \"RN50\"\n",
|
||||
"precision = \"amp\"\n",
|
||||
"\n",
|
||||
"job = aiplatform.CustomContainerTrainingJob(\n",
|
||||
" display_name=job_name, container_uri=TRAIN_DOCKER_URI, command=[\"torchrun\"]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"job.run(\n",
|
||||
" args=[\n",
|
||||
" f\"--nproc_per_node={num_gpus}\",\n",
|
||||
" \"-m\",\n",
|
||||
" \"training.main\",\n",
|
||||
" f\"--name={job_name}\",\n",
|
||||
" f\"--logs=/gcs/{GCS_BUCKET}\",\n",
|
||||
" f\"--train-data=/gcs/{GCS_data_dir}/{{00000..00001}}.tar\",\n",
|
||||
" f\"--train-num-samples={n_samples}\",\n",
|
||||
" \"--dataset-type=webdataset\",\n",
|
||||
" \"--batch-size=32\",\n",
|
||||
" \"--precision=amp\",\n",
|
||||
" \"--workers=8\",\n",
|
||||
" \"--dataset-resampled\",\n",
|
||||
" \"--save-frequency=2\",\n",
|
||||
" \"--epochs=2\",\n",
|
||||
" f\"--model={model_name}\",\n",
|
||||
" \"--pretrained=openai\",\n",
|
||||
" \"--save-most-recent\",\n",
|
||||
" ],\n",
|
||||
" boot_disk_size_gb=600,\n",
|
||||
" replica_count=1,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=gpu_type,\n",
|
||||
" accelerator_count=num_gpus,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "bf7f82732e61"
|
||||
},
|
||||
"source": [
|
||||
"## Upload and Deploy models\n",
|
||||
"\n",
|
||||
"This section uploads the fine-tuned model to Model Registry and deploys it on the Endpoint.\n",
|
||||
"\n",
|
||||
"One `n1-standard-4` machine with 1 `NVIDIA_TESLA_V100` is required to deploy OpenCLIP model.\n",
|
||||
"\n",
|
||||
"The model deployment step will take ~20 minutes to complete."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "6a2cf6e84b10"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Prepare image samples\n",
|
||||
"img_diagram = download_image(\n",
|
||||
" \"https://raw.githubusercontent.com/mlfoundations/open_clip/main/docs/CLIP.png\"\n",
|
||||
")\n",
|
||||
"img_cat = download_image(\n",
|
||||
" \"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Calicocats2222.jpg/220px-Calicocats2222.jpg\"\n",
|
||||
")\n",
|
||||
"plot_images([img_diagram, img_cat])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "5d63ffdd5f09"
|
||||
},
|
||||
"source": [
|
||||
"#### Zero-shot image classification"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "bf55e38815dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model, endpoint = deploy_model(\n",
|
||||
" checkpoint=f\"gs://{GCS_BUCKET}/{job_name}/checkpoints/epoch_latest.pt\",\n",
|
||||
" model=model_name,\n",
|
||||
" precision=precision,\n",
|
||||
" task=\"zero-shot-image-classification\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "80b3fd2ace09"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: The model weights are downloaded after the deployment succeeds. An additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "002f438ecfea"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"instances = [\n",
|
||||
" {\n",
|
||||
" \"text\": [\"a diagram\", \"a dog\", \"a cat\"],\n",
|
||||
" \"image\": image_to_base64(img_diagram),\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"text\": [\"a diagram\", \"a dog\", \"two cats\", \"calico cat\"],\n",
|
||||
" \"image\": image_to_base64(img_cat),\n",
|
||||
" },\n",
|
||||
"]\n",
|
||||
"response = endpoint.predict(instances=instances).predictions\n",
|
||||
"response"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "af21a3cff1e0"
|
||||
},
|
||||
"source": [
|
||||
"Clean up resources:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "911406c1561e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Undeploy model and delete endpoint.\n",
|
||||
"endpoint.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete models.\n",
|
||||
"model.delete()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "4fb87c6aeecf"
|
||||
},
|
||||
"source": [
|
||||
"#### Image/text feature embedding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "bf55e38815dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model, endpoint = deploy_model(\n",
|
||||
" checkpoint=f\"gs://{GCS_BUCKET}/{job_name}/checkpoints/epoch_latest.pt\",\n",
|
||||
" model=model_name,\n",
|
||||
" precision=precision,\n",
|
||||
" task=\"feature-embedding\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "80b3fd2ace09"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: The model weights are downloaded after the deployment succeeds. An additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "56d5c001575d"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"instances = [\n",
|
||||
" {\n",
|
||||
" \"text\": [\"a diagram\", \"a dog\", \"a cat\"],\n",
|
||||
" \"image\": image_to_base64(img_diagram),\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"image\": image_to_base64(img_cat),\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"text\": [\"a diagram\", \"a dog\", \"two cats\", \"calico cat\"],\n",
|
||||
" },\n",
|
||||
" {\n",
|
||||
" \"text\": \"a single value\",\n",
|
||||
" },\n",
|
||||
"]\n",
|
||||
"response = endpoint.predict(instances=instances).predictions\n",
|
||||
"for pred in response:\n",
|
||||
" for k, v in pred.items():\n",
|
||||
" print(k, np.array(v).shape)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "af21a3cff1e0"
|
||||
},
|
||||
"source": [
|
||||
"Clean up resources:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "911406c1561e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Undeploy model and delete endpoint.\n",
|
||||
"endpoint.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete models.\n",
|
||||
"model.delete()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_pytorch_open_clip.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "7d9bbf86da5e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "99c1c3fc2ca5"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden - OpenLLaMA (PEFT)\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_pytorch_openllama_peft.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/community/model_garden/model_garden_pytorch_openllama_peft.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/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_pytorch_openllama_peft.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> (A Python-3 CPU notebook is recommended)\n",
|
||||
" </td>\n",
|
||||
"</table>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3de7470326a2"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This notebook demonstrates deploying prebuilt OpenLLaMA, and also finetuning and deploying OpenLLaMA with performance efficient finetuning libraries ([PEFT](https://github.com/huggingface/peft)) in Vertex AI.\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"- Run local inference with prebuilt OpenLLaMA\n",
|
||||
"- Deploy prebuilt OpenLLaMA\n",
|
||||
"- Finetune and deploy OpenLLaMA with PEFT, supporting\n",
|
||||
"\n",
|
||||
"| Models | LoRA |\n",
|
||||
"| :- | :- |\n",
|
||||
"| [openlm-research/open_llama_3b](https://huggingface.co/openlm-research/open_llama_3b) | Y |\n",
|
||||
"| [openlm-research/open_llama_7b](https://huggingface.co/openlm-research/open_llama_7b) | Y |\n",
|
||||
"| [openlm-research/open_llama_13b](https://huggingface.co/openlm-research/open_llama_13b) | Y |\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "264c07757582"
|
||||
},
|
||||
"source": [
|
||||
"## Before you begin\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": "ioensNKM8ned"
|
||||
},
|
||||
"source": [
|
||||
"### Colab only\n",
|
||||
"Run the following commands for Colab and skip this section if you are using Workbench."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "2707b02ef5df"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"if \"google.colab\" in sys.modules:\n",
|
||||
" ! pip3 install --upgrade google-cloud-aiplatform\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()\n",
|
||||
" # Install gdown for downloading example training images.\n",
|
||||
" ! pip3 install gdown\n",
|
||||
"\n",
|
||||
" # Restart the notebook kernel after installs.\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)\n",
|
||||
"! pip3 install transformers==4.31.0\n",
|
||||
"! pip3 install sentencepiece==0.1.99\n",
|
||||
"! pip3 install accelerate==0.21.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "bb7adab99e41"
|
||||
},
|
||||
"source": [
|
||||
"### Setup Google Cloud project\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. [Create a Cloud Storage bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs.\n",
|
||||
"\n",
|
||||
"1. [Create a service account](https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console) with `Vertex AI User` and `Storage Object Admin` roles for deploying fine tuned model to Vertex AI endpoint."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "6c460088b873"
|
||||
},
|
||||
"source": [
|
||||
"Fill following variables for experiments environment:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "855d6b96f291"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cloud project id.\n",
|
||||
"PROJECT_ID = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The region you want to launch jobs in.\n",
|
||||
"REGION = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The Cloud Storage bucket for storing experiments output.\n",
|
||||
"BUCKET_URI = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"! gcloud config set project $PROJECT_ID\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"STAGING_BUCKET = os.path.join(BUCKET_URI, \"temporal\")\n",
|
||||
"EXPERIMENT_BUCKET = os.path.join(BUCKET_URI, \"peft\")\n",
|
||||
"DATA_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"data\")\n",
|
||||
"MODEL_BUCKET = os.path.join(EXPERIMENT_BUCKET, \"model\")\n",
|
||||
"\n",
|
||||
"# The service account looks like:\n",
|
||||
"# '@.iam.gserviceaccount.com'\n",
|
||||
"# Please go to https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console\n",
|
||||
"# and create service account with `Vertex AI User` and `Storage Object Admin` roles.\n",
|
||||
"# The service account for deploying fine tuned model.\n",
|
||||
"SERVICE_ACCOUNT = \"\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e828eb320337"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI API"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "12cd25839741"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=STAGING_BUCKET)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2cc825514deb"
|
||||
},
|
||||
"source": [
|
||||
"### Define constants"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "b42bd4fa2b2d"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training and serving docker images.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-peft-train\"\n",
|
||||
"PREDICTION_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-peft-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "0c250872074f"
|
||||
},
|
||||
"source": [
|
||||
"### Define common functions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "354da31189dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"from datetime import datetime\n",
|
||||
"\n",
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_job_name_with_datetime(prefix: str):\n",
|
||||
" \"\"\"Gets the job name with date time when triggering training or deployment\n",
|
||||
" jobs in Vertex AI.\n",
|
||||
" \"\"\"\n",
|
||||
" return prefix + datetime.now().strftime(\"_%Y%m%d_%H%M%S\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def deploy_model(\n",
|
||||
" model_name,\n",
|
||||
" base_model_id,\n",
|
||||
" finetuned_lora_model_path,\n",
|
||||
" service_account,\n",
|
||||
" task,\n",
|
||||
" machine_type=\"n1-standard-8\",\n",
|
||||
" accelerator_type=\"NVIDIA_TESLA_V100\",\n",
|
||||
"):\n",
|
||||
" \"\"\"Deploys trained models into Vertex AI.\"\"\"\n",
|
||||
" endpoint = aiplatform.Endpoint.create(display_name=f\"{model_name}-endpoint\")\n",
|
||||
" serving_env = {\n",
|
||||
" \"BASE_MODEL_ID\": base_model_id,\n",
|
||||
" \"TASK\": task,\n",
|
||||
" }\n",
|
||||
" if finetuned_lora_model_path:\n",
|
||||
" serving_env[\"FINETUNED_LORA_MODEL_PATH\"] = finetuned_lora_model_path\n",
|
||||
" model = aiplatform.Model.upload(\n",
|
||||
" display_name=model_name,\n",
|
||||
" serving_container_image_uri=PREDICTION_DOCKER_URI,\n",
|
||||
" serving_container_ports=[7080],\n",
|
||||
" serving_container_predict_route=\"/predictions/peft_serving\",\n",
|
||||
" serving_container_health_route=\"/ping\",\n",
|
||||
" serving_container_environment_variables=serving_env,\n",
|
||||
" )\n",
|
||||
" model.deploy(\n",
|
||||
" endpoint=endpoint,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=1,\n",
|
||||
" deploy_request_timeout=1800,\n",
|
||||
" service_account=service_account,\n",
|
||||
" )\n",
|
||||
" return model, endpoint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "65eaa62632d1"
|
||||
},
|
||||
"source": [
|
||||
"## Run inferences locally with prebuilt OpenLLaMA"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "339601a9500b"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"from transformers import LlamaForCausalLM, LlamaTokenizer\n",
|
||||
"\n",
|
||||
"model_path = \"openlm-research/open_llama_3b\"\n",
|
||||
"\n",
|
||||
"tokenizer = LlamaTokenizer.from_pretrained(model_path)\n",
|
||||
"\n",
|
||||
"model = LlamaForCausalLM.from_pretrained(\n",
|
||||
" model_path,\n",
|
||||
" torch_dtype=torch.float16,\n",
|
||||
" device_map=\"auto\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"prompt = \"Q: What is the largest animal?\\nA:\"\n",
|
||||
"input_ids = tokenizer(prompt, return_tensors=\"pt\").input_ids\n",
|
||||
"input_ids = input_ids.to(\"cuda\")\n",
|
||||
"generation_output = model.generate(input_ids=input_ids, max_new_tokens=32)\n",
|
||||
"print(tokenizer.decode(generation_output[0]))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "8neJc8CnDDpu"
|
||||
},
|
||||
"source": [
|
||||
"## Deploy Prebuilt OpenLLaMA\n",
|
||||
"\n",
|
||||
"This section deploys prebuilt OpenLLaMA models on the Endpoint. The model deployment step will take ~15 minutes to complete.\n",
|
||||
"\n",
|
||||
"The peak GPU memory usages for [openlm-research/open_llama_3b](https://huggingface.co/openlm-research/open_llama_3b), [openlm-research/open_llama_7b](https://huggingface.co/openlm-research/open_llama_7b), and [openlm-research/open_llama_13b](https://huggingface.co/openlm-research/open_llama_13b) are ~5.3G, ~8.7G and ~15.2G separately with the default settings."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2MjaORIIFDVu"
|
||||
},
|
||||
"source": [
|
||||
"Set the prebuilt model id."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "E8OiHHNNE_wj"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"prebuilt_model_id = \"openlm-research/open_llama_3b\" # @param [\"openlm-research/open_llama_3b\", \"openlm-research/open_llama_7b\", \"openlm-research/open_llama_13b\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dHFW7yvjaVFV"
|
||||
},
|
||||
"source": [
|
||||
"We use the PEFT serving images to deploy prebuilt OpenLLaMA models, by setting finetuning LoRA model paths as empty."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "Uak1pyEeExYM"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model_without_peft, endpoint_without_peft = deploy_model(\n",
|
||||
" model_name=get_job_name_with_datetime(prefix=\"openllama-serve\"),\n",
|
||||
" base_model_id=prebuilt_model_id,\n",
|
||||
" finetuned_lora_model_path=\"\", # This will avoid override finetuning models.\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" task=\"causal-language-modeling-lora\",\n",
|
||||
")\n",
|
||||
"print(\"endpoint_name:\", endpoint_without_peft.name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "sGKIjgmDFRW2"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: The prebuilt model weights will be downloaded on the fly from the orginal location after the deployment succeeds. Thus additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint.\n",
|
||||
"\n",
|
||||
"Once deployment succeeds, you can send requests to the endpoint with text prompts."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "rDHsCOqvFYBi"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# # Loads an existing endpoint as below.\n",
|
||||
"# endpoint_name = endpoint_without_peft.name\n",
|
||||
"# aip_endpoint_name = (\n",
|
||||
"# f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_name}\"\n",
|
||||
"# )\n",
|
||||
"# endpoint_without_peft = aiplatform.Endpoint(aip_endpoint_name)\n",
|
||||
"instances = [\n",
|
||||
" {\"prompt\": \"Hi, Google.\"},\n",
|
||||
"]\n",
|
||||
"response = endpoint_without_peft.predict(instances=instances)\n",
|
||||
"\n",
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" print(prediction[\"generated_text\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e70e3519ff8b"
|
||||
},
|
||||
"source": [
|
||||
"## Finetune and deploy OpenLLaMA with PEFT\n",
|
||||
"\n",
|
||||
"This section demonstrates how to finetune and dpeloy OpenLLaMA with PEFT LoRA."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "5qCrm_kJH5cz"
|
||||
},
|
||||
"source": [
|
||||
"Set the base model id."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "N3UBLiYrM3sU"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"base_model_id = \"openlm-research/open_llama_3b\" # @param [\"openlm-research/open_llama_3b\", \"openlm-research/open_llama_7b\", \"openlm-research/open_llama_13b\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "iWGwJHqI7LMs"
|
||||
},
|
||||
"source": [
|
||||
"### Finetune"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KKEYoRfiHDVv"
|
||||
},
|
||||
"source": [
|
||||
"Use the Vertex AI SDK to create and run the custom training jobs with Vertex AI Model Garden training images.\n",
|
||||
"\n",
|
||||
"This example uses the dataset [Abirate/english_quotes](https://huggingface.co/datasets/Abirate/english_quotes).\n",
|
||||
"\n",
|
||||
"In order to make the finetuning efficiently, we enabled quantization (8bits) when loading pretrained models for finetuning LoRA models. The peak GPU memory usages are ~7G, ~10G and ~16G for finetuning LoRA models for [openlm-research/open_llama_3b](https://huggingface.co/openlm-research/open_llama_3b), [openlm-research/open_llama_7b](https://huggingface.co/openlm-research/open_llama_7b), and [openlm-research/open_llama_13b](https://huggingface.co/openlm-research/open_llama_13b) separately with default training parameters and the example dataset. open_llama_3b and open_llama_7b can be finetuned on 1 V100, and open_llama_13b can be finetuned on 1 A100 (40G)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "65467b361315"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dataset_name = \"Abirate/english_quotes\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Worker pool spec.\n",
|
||||
"# Finetunes open_llama_3b and open_llama_7b with 1 V100.\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"# Finetunes and open_llama_13b with 1 A100 (40G).\n",
|
||||
"# machine_type = \"a2-highgpu-1g\"\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"replica_count = 1\n",
|
||||
"accelerator_count = 1\n",
|
||||
"\n",
|
||||
"# Setup training job.\n",
|
||||
"job_name = get_job_name_with_datetime(\"openllama-lora-train\")\n",
|
||||
"train_job = aiplatform.CustomContainerTrainingJob(\n",
|
||||
" display_name=job_name,\n",
|
||||
" container_uri=TRAIN_DOCKER_URI,\n",
|
||||
")\n",
|
||||
"output_dir = os.path.join(MODEL_BUCKET, job_name)\n",
|
||||
"output_dir_gcsfuse = output_dir.replace(\"gs://\", \"/gcs/\")\n",
|
||||
"\n",
|
||||
"# Pass training arguments and launch job.\n",
|
||||
"train_job.run(\n",
|
||||
" args=[\n",
|
||||
" \"--task=causal-language-modeling-lora\",\n",
|
||||
" f\"--pretrained_model_id={base_model_id}\",\n",
|
||||
" f\"--dataset_name={dataset_name}\",\n",
|
||||
" f\"--output_dir={output_dir_gcsfuse}\",\n",
|
||||
" \"--lora_rank=16\",\n",
|
||||
" \"--lora_alpha=32\",\n",
|
||||
" \"--lora_dropout=0.05\",\n",
|
||||
" \"--warmup_steps=10\",\n",
|
||||
" \"--max_steps=10\",\n",
|
||||
" \"--learning_rate=2e-4\",\n",
|
||||
" ],\n",
|
||||
" replica_count=replica_count,\n",
|
||||
" machine_type=machine_type,\n",
|
||||
" accelerator_type=accelerator_type,\n",
|
||||
" accelerator_count=accelerator_count,\n",
|
||||
" boot_disk_size_gb=500,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(\"Trained models were saved in: \", output_dir)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "jqmCtkGnhDmp"
|
||||
},
|
||||
"source": [
|
||||
"### Deploy\n",
|
||||
"This section uploads the model to Model Registry and deploys it on the Endpoint.\n",
|
||||
"\n",
|
||||
"The model deployment step will take ~15 minutes to complete.\n",
|
||||
"\n",
|
||||
"The peak GPU memory usages for [openlm-research/open_llama_3b](https://huggingface.co/openlm-research/open_llama_3b), [openlm-research/open_llama_7b](https://huggingface.co/openlm-research/open_llama_7b), and [openlm-research/open_llama_13b](https://huggingface.co/openlm-research/open_llama_13b) with LoRA weights are ~5.3G, ~8.7G and ~15.2G separately with the default settings."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "bf55e38815dc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model_with_peft, endpoint_with_peft = deploy_model(\n",
|
||||
" model_name=get_job_name_with_datetime(prefix=\"openllama-peft-serve\"),\n",
|
||||
" base_model_id=base_model_id,\n",
|
||||
" finetuned_lora_model_path=output_dir,\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" task=\"causal-language-modeling-lora\",\n",
|
||||
")\n",
|
||||
"print(\"endpoint_name:\", endpoint_with_peft.name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "80b3fd2ace09"
|
||||
},
|
||||
"source": [
|
||||
"NOTE: After the deployment succeeds, the base model weights will be downloaded one the fly from the original location and LoRA model weights will be downloaded from the GCS bucket used in training above. Thus additional 5 minutes of waiting time is needed **after** the above model deployment step succeeds and before you run the next step below. Otherwise you might see a `ServiceUnavailable: 503 502:Bad Gateway` error when you send requests to the endpoint.\n",
|
||||
"\n",
|
||||
"Once deployment succeeds, you can send requests to the endpoint with text prompts."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "4ab04da3ec9a"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# # Loads an existing endpoint as below.\n",
|
||||
"# endpoint_name = endpoint_with_peft.name\n",
|
||||
"# aip_endpoint_name = (\n",
|
||||
"# f\"projects/{PROJECT_ID}/locations/{REGION}/endpoints/{endpoint_name}\"\n",
|
||||
"# )\n",
|
||||
"# endpoint_with_peft = aiplatform.Endpoint(aip_endpoint_name)\n",
|
||||
"instances = [\n",
|
||||
" {\"prompt\": \"Hi, Google.\"},\n",
|
||||
"]\n",
|
||||
"response = endpoint_with_peft.predict(instances=instances)\n",
|
||||
"\n",
|
||||
"for prediction in response.predictions[0]:\n",
|
||||
" print(prediction[\"generated_text\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "af21a3cff1e0"
|
||||
},
|
||||
"source": [
|
||||
"## Clean up resources"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "911406c1561e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete custom train jobs.\n",
|
||||
"train_job.delete()\n",
|
||||
"\n",
|
||||
"# Undeploy model and delete endpoint.\n",
|
||||
"endpoint_without_peft.delete(force=True)\n",
|
||||
"endpoint_with_peft.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete models.\n",
|
||||
"model_without_peft.delete()\n",
|
||||
"model_with_peft.delete()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_pytorch_openllama_peft.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -649,14 +649,13 @@
|
||||
"# # If deploy finetuned falcon-40b-instruct models, please set\n",
|
||||
"# machine_type = \"a2-highgpu-1g\",\n",
|
||||
"# accelerator_type = \"NVIDIA_TESLA_A100\"\n",
|
||||
"machine_type = \"n1-standard-8\",\n",
|
||||
"machine_type = \"n1-standard-8\"\n",
|
||||
"accelerator_type = \"NVIDIA_TESLA_V100\"\n",
|
||||
"\n",
|
||||
"accelerator_type =\n",
|
||||
"model, endpoint = deploy_model(\n",
|
||||
" model_name=get_job_name_with_datetime(prefix=\"falcon-peft-serve\"),\n",
|
||||
" base_model_id=base_model_id,\n",
|
||||
" finetuned_lora_model_path=os.path.join(output_dir, \"checkpoint-\"+str(max_steps)),\n",
|
||||
" finetuned_lora_model_path=os.path.join(output_dir, \"checkpoint-\" + str(max_steps)),\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" task=\"instruct-lora\",\n",
|
||||
" machine_type=machine_type,\n",
|
||||
|
||||
@@ -0,0 +1,633 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad41232f-4ac9-4607-8ee6-9b1d98d0f5c3",
|
||||
"metadata": {
|
||||
"id": "7d9bbf86da5e"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "30e2cb63",
|
||||
"metadata": {
|
||||
"id": "99c1c3fc2ca5"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden - Pic2Word\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_pic2word.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",
|
||||
"\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_jpic2word.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> <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_pic2word.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>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "5d5af88c-a3c8-46fa-b90a-1b6737a91534",
|
||||
"metadata": {
|
||||
"id": "7e3e5205fbfd"
|
||||
},
|
||||
"source": [
|
||||
"## Overview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "87b71198-5c24-4e8c-aab1-44e080aa7fe7",
|
||||
"metadata": {
|
||||
"id": "523084ce4894"
|
||||
},
|
||||
"source": [
|
||||
"This notebook demonstrates how to use the [Pic2Word](https://github.com/google-research/composed_image_retrieval) model in Vertex AI Model Garden. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "0ba32ec8-e06f-474f-b753-20aaf74ebd12",
|
||||
"metadata": {
|
||||
"id": "f9cbeb1704e1"
|
||||
},
|
||||
"source": [
|
||||
"## Objective"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "95694bc4-0626-4371-8b2a-6b89967742db",
|
||||
"metadata": {
|
||||
"id": "da71cd41e69e"
|
||||
},
|
||||
"source": [
|
||||
"Following the notebook you will conduct experiments using the pre-built docker image on Vertex AI.\n",
|
||||
"\n",
|
||||
"- Deploy pretrained Pic2Word models in Google Cloud Vertex AI\n",
|
||||
"\n",
|
||||
"This tutorial uses the following Google Cloud ML services and resources:\n",
|
||||
"\n",
|
||||
"- Vertex AI Model Registry\n",
|
||||
"- Vertex AI Online Prediction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "b1893e0c-859b-4d42-b1d8-d133855d8767",
|
||||
"metadata": {
|
||||
"id": "8264c04e0f34"
|
||||
},
|
||||
"source": [
|
||||
"## Dataset"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "a099439c-3122-429b-8215-caaf9aee69c5",
|
||||
"metadata": {
|
||||
"id": "52e4f9df87b2"
|
||||
},
|
||||
"source": [
|
||||
"We use the [COCO](https://cocodataset.org/#home) validation set (5,000 images) for evaluation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "56d2a9f8-2d99-407e-a378-57f99754185b",
|
||||
"metadata": {
|
||||
"id": "169ef74e43a6"
|
||||
},
|
||||
"source": [
|
||||
"## Costs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "bb62de0d-71ac-45ee-82ac-5f7465f2f0eb",
|
||||
"metadata": {
|
||||
"id": "61a64432ac87"
|
||||
},
|
||||
"source": [
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"- Vertex AI\n",
|
||||
"- Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing?_ga=2.46650789.-341051769.1686949237) and [Cloud Storage pricing](https://cloud.google.com/storage/pricing?_ga=2.46650789.-341051769.1686949237), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/?_ga=2.247379078.-341051769.1686949237) to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "ea27403e",
|
||||
"metadata": {
|
||||
"id": "586c9147ba8a"
|
||||
},
|
||||
"source": [
|
||||
"# Installation\n",
|
||||
"\n",
|
||||
"Install the following packages required to execute this notebook."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f9c685d9",
|
||||
"metadata": {
|
||||
"id": "68f1241e68a7"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if \"google.colab\" in str(get_ipython()):\n",
|
||||
" # Configs for colab notebooks.\n",
|
||||
" ! pip3 install --upgrade --quiet google-cloud-aiplatform\n",
|
||||
"\n",
|
||||
" # Automatically restart kernel after installs\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)\n",
|
||||
"\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "e75fe500-5dc1-4ab3-8829-3eeada5be7f7",
|
||||
"metadata": {
|
||||
"id": "d0c7bca32b78"
|
||||
},
|
||||
"source": [
|
||||
"## Setup environment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "e8b0ef65",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
},
|
||||
"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 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 need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
|
||||
"\n",
|
||||
"1. [Create a service account](https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console) with **Vertex AI User** and **Storage Object Admin** roles for deploying fine tuned model to Vertex AI endpoint.\n",
|
||||
"\n",
|
||||
"1. Enter your project ID in the cell below. Then run the cell to make sure the Cloud SDK uses the right project for all the commands in this notebook.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"**Note**: Jupyter runs lines prefixed with ! as shell commands, and it interpolates Python variables prefixed with $ into these commands."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "09490824",
|
||||
"metadata": {
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**If you don't know your project ID**, try the following:\n",
|
||||
"* Run `gcloud config list`.\n",
|
||||
"* Run `gcloud projects list`.\n",
|
||||
"* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6ddc1d11",
|
||||
"metadata": {
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"your-project-id\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "9ed7d668",
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"#### Region\n",
|
||||
"\n",
|
||||
"You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "90d0108f",
|
||||
"metadata": {
|
||||
"id": "twgKk-LsLmX3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "ca43938d",
|
||||
"metadata": {
|
||||
"id": "409f1effab6c"
|
||||
},
|
||||
"source": [
|
||||
"### Buckets\n",
|
||||
"\n",
|
||||
"You can create a storage bucket to store model input and output images."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "889d10ac",
|
||||
"metadata": {
|
||||
"id": "bcdadd216c34"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The form for BUCKET_URI is gs://.\n",
|
||||
"BUCKET_URI = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"INPUT_BUCKET = os.path.join(BUCKET_URI, \"/input\")\n",
|
||||
"OUTPUT_BUCKET = os.path.join(BUCKET_URI, \"/output\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "15c0e671",
|
||||
"metadata": {
|
||||
"id": "sBCra4QMA2wR"
|
||||
},
|
||||
"source": [
|
||||
"### Authenticate your Google Cloud account\n",
|
||||
"\n",
|
||||
"Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "623a03cc",
|
||||
"metadata": {
|
||||
"id": "74ccc9e52986"
|
||||
},
|
||||
"source": [
|
||||
"**1. Vertex AI Workbench**\n",
|
||||
"* Do nothing as you are already authenticated."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "fbee4160",
|
||||
"metadata": {
|
||||
"id": "de775a3773ba"
|
||||
},
|
||||
"source": [
|
||||
"**2. Local JupyterLab instance, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3eabaf6b",
|
||||
"metadata": {
|
||||
"id": "254614fa0c46"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ! gcloud auth login"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "90a615fe",
|
||||
"metadata": {
|
||||
"id": "ef21552ccea8"
|
||||
},
|
||||
"source": [
|
||||
"**3. Colab, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3d4e50e1",
|
||||
"metadata": {
|
||||
"id": "603adbbf0532"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# from google.colab import auth\n",
|
||||
"# auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "b0b40233",
|
||||
"metadata": {
|
||||
"id": "f6b2ccc891ed"
|
||||
},
|
||||
"source": [
|
||||
"**4. Service account or other**\n",
|
||||
"* See how to grant Cloud Storage permissions to your service account at https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "a8019fcc-26ce-4536-b394-aa1fb18794ee",
|
||||
"metadata": {
|
||||
"id": "4226467373ef"
|
||||
},
|
||||
"source": [
|
||||
"If you are running this notebook locally, you will need to install the [Cloud SDK](https://cloud.google.com/sdk) and [gsutil](https://cloud.google.com/storage/docs/gsutil_install)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "315598c1-3ff4-4bc8-9b82-7e2fe55d4678",
|
||||
"metadata": {
|
||||
"id": "8ba36d3a171d"
|
||||
},
|
||||
"source": [
|
||||
"### Setup variables"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6d26ddb2-04ad-4973-8965-9b25eb24993f",
|
||||
"metadata": {
|
||||
"id": "25ca675e59e7"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Prediction constants.\n",
|
||||
"PREDICTION_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pic2word_serve:latest\"\n",
|
||||
"PREDICTION_ACCELERATOR_TYPE = \"NVIDIA_TESLA_T4\"\n",
|
||||
"PREDICTION_MACHINE_TYPE = \"n1-standard-8\"\n",
|
||||
"\n",
|
||||
"# The serving port.\n",
|
||||
"SERVE_PORT = 7080\n",
|
||||
"\n",
|
||||
"# The service account looks like:\n",
|
||||
"# '@.iam.gserviceaccount.com'\n",
|
||||
"# Please go to https://cloud.google.com/iam/docs/service-accounts-create#iam-service-accounts-create-console\n",
|
||||
"# and create service account with `Vertex AI User` and `Storage Object Admin` roles.\n",
|
||||
"SERVICE_ACCOUNT = \"\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "0daec6e5-7642-458f-bd93-60a15431b0a1",
|
||||
"metadata": {
|
||||
"id": "71dd15118703"
|
||||
},
|
||||
"source": [
|
||||
"## Deploy model for online prediction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "b286ce6b-cb00-441b-8249-93497de17974",
|
||||
"metadata": {
|
||||
"id": "5c891f6352ad"
|
||||
},
|
||||
"source": [
|
||||
"This section uploads the model to Vertex Model Registry and deploys it on an Endpoint resource. This will take around 15 minutes to finish."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "01f686b2-9227-496b-9c4d-a63becb59fb8",
|
||||
"metadata": {
|
||||
"id": "e6abf4ee450a"
|
||||
},
|
||||
"source": [
|
||||
"### Upload and deploy model to Vertex AI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8885d5be-5007-4778-9562-976102fefe73",
|
||||
"metadata": {
|
||||
"id": "645d8d1df8d4"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"# Init common setup.\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=BUCKET_URI)\n",
|
||||
"\n",
|
||||
"# Upload model.\n",
|
||||
"serving_env = {}\n",
|
||||
"model = aiplatform.Model.upload(\n",
|
||||
" display_name=\"pic2word-model\",\n",
|
||||
" serving_container_image_uri=PREDICTION_DOCKER_URI,\n",
|
||||
" serving_container_ports=[SERVE_PORT],\n",
|
||||
" serving_container_predict_route=\"/predictions/pic2word\",\n",
|
||||
" serving_container_health_route=\"/ping\",\n",
|
||||
" serving_container_environment_variables=serving_env,\n",
|
||||
")\n",
|
||||
"# Or reuse a pre-uploaded model.\n",
|
||||
"# model = aiplatform.Model('projects/123456789/locations/us-central1/models/123456789@1')\n",
|
||||
"\n",
|
||||
"# Create an endpoint.\n",
|
||||
"endpoint = aiplatform.Endpoint.create(display_name=\"pytorch-pic2word-endpoint\")\n",
|
||||
"# Or reuse a pre-created endpoint.\n",
|
||||
"# endpoint = aiplatform.Endpoint('projects/123456789/locations/us-central1/endpoints/123456789')\n",
|
||||
"\n",
|
||||
"# Deploy model to endpoint.\n",
|
||||
"model.deploy(\n",
|
||||
" endpoint=endpoint,\n",
|
||||
" machine_type=PREDICTION_MACHINE_TYPE,\n",
|
||||
" accelerator_type=PREDICTION_ACCELERATOR_TYPE,\n",
|
||||
" accelerator_count=1,\n",
|
||||
" traffic_percentage=100,\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "f0bd36bb-f988-479a-be40-18c54980ca1f",
|
||||
"metadata": {
|
||||
"id": "2bb47a2855a2"
|
||||
},
|
||||
"source": [
|
||||
"You can manage your uploaded models in the [Model Registry](https://pantheon.corp.google.com/vertex-ai/models) and your endpoints in the [Endpoints](https://pantheon.corp.google.com/vertex-ai/endpoints)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "95edbcdd-8246-444a-aef2-a9cadbc03000",
|
||||
"metadata": {
|
||||
"id": "f14ff5e7a476"
|
||||
},
|
||||
"source": [
|
||||
"## Send a prediction request to the endpoint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "3a83d6bb",
|
||||
"metadata": {
|
||||
"id": "c41c7d51d654"
|
||||
},
|
||||
"source": [
|
||||
"Set the query text and upload your desired query image to the specified INPUT_BUCKET. Use an asterisk in the query text to specify which token gets replaced by the image token. "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f7c6fc99-42cd-4544-86df-b14f49189bfd",
|
||||
"metadata": {
|
||||
"id": "58d4f8e84e02"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"payload = json.dumps(\n",
|
||||
" {\n",
|
||||
" \"query\": \"a bunch of *\",\n",
|
||||
" \"image_path\": INPUT_BUCKET,\n",
|
||||
" \"image_file_name\": \"cat.png\",\n",
|
||||
" \"output_storage_dir\": OUTPUT_BUCKET,\n",
|
||||
" }\n",
|
||||
")\n",
|
||||
"response = endpoint.predict(payload).predictions\n",
|
||||
"print(response)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "8e71ee23-967c-4871-802f-ccb39958386e",
|
||||
"metadata": {
|
||||
"id": "608b936179df"
|
||||
},
|
||||
"source": [
|
||||
"## Cleaning Up\n",
|
||||
"\n",
|
||||
"To clean up all Google Cloud resources used in this project, you can delete the Google Cloud project you used for the tutorial.\n",
|
||||
"\n",
|
||||
"Otherwise, you can delete the individual resources you created in this tutorial:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d9be2a18-db11-4108-9622-647b33be2594",
|
||||
"metadata": {
|
||||
"id": "7d17b385141d"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete endpoint resource.\n",
|
||||
"endpoint.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete model resource.\n",
|
||||
"model.delete()\n",
|
||||
"\n",
|
||||
"# Delete Cloud Storage objects that were created.\n",
|
||||
"delete_bucket = False\n",
|
||||
"if delete_bucket or os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! gsutil -m rm -r $BUCKET_URI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_pytorch_pic2word.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -259,10 +259,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training docker image. It contains training scripts and models.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"\n",
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -249,10 +249,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training docker image. It contains training scripts and models.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"\n",
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -252,10 +252,10 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built training docker image. It contains training scripts and models.\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"TRAIN_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-train:latest\"\n",
|
||||
"\n",
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -243,7 +243,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -216,7 +216,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-diffusers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -262,6 +262,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import urllib\n",
|
||||
"\n",
|
||||
"import timm\n",
|
||||
"import torch\n",
|
||||
"from PIL import Image\n",
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_pytorch_vit_gpt2_image_captioning.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",
|
||||
@@ -204,7 +204,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The pre-built serving docker image. It contains serving scripts and models.\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/pytorch-transformers-serve\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_tfvision_image_classification.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",
|
||||
@@ -243,7 +243,7 @@
|
||||
"\n",
|
||||
"# Data converter constants.\n",
|
||||
"DATA_CONVERTER_JOB_PREFIX = \"data_converter\"\n",
|
||||
"DATA_CONVERTER_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/data-converter\"\n",
|
||||
"DATA_CONVERTER_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/data-converter\"\n",
|
||||
"DATA_CONVERTER_MACHINE_TYPE = \"n1-highmem-8\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
|
||||
+21
-21
@@ -24,7 +24,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "TirJ-SGQseby"
|
||||
@@ -45,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_tfvision_image_object_detection.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",
|
||||
@@ -55,7 +54,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dwGLvtIeECLK"
|
||||
@@ -67,7 +65,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "tvgnzT1CKxrO"
|
||||
@@ -106,7 +103,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KEukV6uRk_S3"
|
||||
@@ -116,7 +112,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "z__i0w0lCAsW"
|
||||
@@ -149,7 +144,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "BF1j6f9HApxa"
|
||||
@@ -217,11 +211,13 @@
|
||||
"! gsutil cp coco_spinenet143_gpu_multiworker_mirrored.yaml $CONFIG_DIR/\n",
|
||||
"\n",
|
||||
"! wget https://raw.githubusercontent.com/tensorflow/models/master/official/projects/yolo/configs/experiments/yolov4/detection/scaled_yolov4_1280_gpu.yaml\n",
|
||||
"! gsutil cp scaled_yolov4_1280_gpu.yaml $CONFIG_DIR/"
|
||||
"! gsutil cp scaled_yolov4_1280_gpu.yaml $CONFIG_DIR/\n",
|
||||
"\n",
|
||||
"! wget https://raw.githubusercontent.com/tensorflow/models/master/official/projects/yolo/configs/experiments/yolov7/detection/yolov7_gpu.yaml\n",
|
||||
"! gsutil cp yolov7_gpu.yaml $CONFIG_DIR/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "n6IFz75WGCam"
|
||||
@@ -242,7 +238,7 @@
|
||||
"\n",
|
||||
"# Data converter constants.\n",
|
||||
"DATA_CONVERTER_JOB_PREFIX = \"data_converter\"\n",
|
||||
"DATA_CONVERTER_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/data-converter\"\n",
|
||||
"DATA_CONVERTER_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/data-converter\"\n",
|
||||
"DATA_CONVERTER_MACHINE_TYPE = \"n1-highmem-8\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -262,6 +258,7 @@
|
||||
" CONFIG_DIR, \"coco_spinenet143_gpu_multiworker_mirrored.yaml\"\n",
|
||||
")\n",
|
||||
"TRAIN_YOLOV4_CONFIG = os.path.join(CONFIG_DIR, \"scaled_yolov4_1280_gpu.yaml\")\n",
|
||||
"TRAIN_YOLOV7_CONFIG = os.path.join(CONFIG_DIR, \"yolov7_gpu.yaml\")\n",
|
||||
"\n",
|
||||
"# Evaluation constants.\n",
|
||||
"EVALUATION_METRIC = \"AP50\"\n",
|
||||
@@ -285,7 +282,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ZZFPe_GezXg8"
|
||||
@@ -510,7 +506,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "RB_xY9ipr7ZU"
|
||||
@@ -526,7 +521,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
@@ -603,7 +597,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "SA8DVTn7j69v"
|
||||
@@ -615,7 +608,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "aaff6f5be7f6"
|
||||
@@ -649,7 +641,7 @@
|
||||
"\n",
|
||||
"# Refer to https://github.com/tensorflow/models/blob/master/official/vision/MODEL_GARDEN.md\n",
|
||||
"# for more model details.\n",
|
||||
"experiment = \"retinanet_spinenet96\" # @param ['retinanet_spinenet49', \"retinanet_spinenet96\", 'retinanet_spinenet143', 'scaled_yolo_v4']\n",
|
||||
"experiment = \"retinanet_spinenet96\" # @param ['retinanet_spinenet49', \"retinanet_spinenet96\", 'retinanet_spinenet143', 'scaled_yolo_v4', 'yolov7']\n",
|
||||
"\n",
|
||||
"train_job_name = get_job_name_with_datetime(TRAINING_JOB_PREFIX + \"_\" + OBJECTIVE)\n",
|
||||
"model_dir = os.path.join(BUCKET_URI, train_job_name)\n",
|
||||
@@ -706,6 +698,15 @@
|
||||
" \"input_size\": \"1280,1280\",\n",
|
||||
" },\n",
|
||||
" ),\n",
|
||||
" # yolov7 experiment args.\n",
|
||||
" \"yolov7\": dict(\n",
|
||||
" common_args,\n",
|
||||
" **{\n",
|
||||
" \"experiment\": \"coco_yolov7\",\n",
|
||||
" \"config_file\": TRAIN_YOLOV7_CONFIG,\n",
|
||||
" \"input_size\": \"640,640\",\n",
|
||||
" },\n",
|
||||
" ),\n",
|
||||
"}\n",
|
||||
"experiment_container_args = experiment_container_args_dict[experiment]\n",
|
||||
"\n",
|
||||
@@ -715,6 +716,8 @@
|
||||
" experiment_container_args[\"init_checkpoint\"] = upload_checkpoint_to_gcs(\n",
|
||||
" init_checkpoint\n",
|
||||
" )\n",
|
||||
"if \"yolov7\" in experiment:\n",
|
||||
" TRAIN_CONTAINER_URI = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/tfvision-oss-v2\"\n",
|
||||
"\n",
|
||||
"params_override = \"runtime.num_gpus=%s\" % TRAIN_NUM_GPU\n",
|
||||
"eval_params_override = \"runtime.num_gpus=1,runtime.distribution_strategy=mirrored\"\n",
|
||||
@@ -768,7 +771,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "HwcCjwlBTQIz"
|
||||
@@ -817,7 +819,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "mV-Djz-frBni"
|
||||
@@ -878,7 +879,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "g0BGaofgsMsy"
|
||||
@@ -904,11 +904,12 @@
|
||||
"\n",
|
||||
"upload_job_name = get_job_name_with_datetime(UPLOAD_JOB_PREFIX + \"_\" + OBJECTIVE)\n",
|
||||
"\n",
|
||||
"if 'yolo' in experiment:\n",
|
||||
"if \"yolo\" in experiment:\n",
|
||||
" SERVING_CONTAINER_ARGS = [\"--allow_precompilation\"]\n",
|
||||
"else:\n",
|
||||
" SERVING_CONTAINER_ARGS = [\"--allow_precompilation\", \"--allow_compression\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"model = aiplatform.Model.upload(\n",
|
||||
" display_name=upload_job_name,\n",
|
||||
" artifact_uri=trained_model_dir,\n",
|
||||
@@ -982,7 +983,6 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "kkH2nrpdp4sp"
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
" View on GitHub\n",
|
||||
" </a>\n",
|
||||
" </td>\n",
|
||||
" <td> <td>\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_tfvision_image_segmentation.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",
|
||||
@@ -227,7 +227,7 @@
|
||||
"\n",
|
||||
"# Data converter constants.\n",
|
||||
"DATA_CONVERTER_JOB_PREFIX = \"data_converter\"\n",
|
||||
"DATA_CONVERTER_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/data-converter\"\n",
|
||||
"DATA_CONVERTER_CONTAINER = f\"{REGION_PREFIX}-docker.pkg.dev/vertex-ai/vertex-vision-model-garden-dockers/data-converter\"\n",
|
||||
"DATA_CONVERTER_MACHINE_TYPE = \"n1-highmem-8\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
|
||||
@@ -0,0 +1,738 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "6ad30fe2-1fc1-47e3-8a9f-624170b5aae6"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2023 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": "411c319c-49c9-412b-aff1-ce7832412bd7"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Model Garden: Video Object Tracking with Bytetrack\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_video_object_tracking_serve.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/community/model_garden/model_garden_video_object_tracking_serve.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/notebooks/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/community/model_garden/model_garden_video_object_tracking_serve.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",
|
||||
" (a Python-3 CPU notebook is recommended)\n",
|
||||
" </td>\n",
|
||||
"</table>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "962e636b5cee"
|
||||
},
|
||||
"source": [
|
||||
"**_NOTE_**: This notebook has been tested in the following environment:\n",
|
||||
"\n",
|
||||
"* Python version = 3.9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "05131ed9-08a2-4825-b35c-46986f14789b"
|
||||
},
|
||||
"source": [
|
||||
"## Overview\n",
|
||||
"\n",
|
||||
"This Jupyter notebook provides a step-by-step walkthrough of deploying a video object tracking model on Vertex AI Endpoint resource with the open-source [ByteTrack](https://github.com/ifzhang/ByteTrack) object tracking algorithm.\n",
|
||||
"\n",
|
||||
"### Objective\n",
|
||||
"* Set up a Vertex AI Endpoint resource with:\n",
|
||||
" * TensorFlow Vision [notebook](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_tfvision_image_object_detection.ipynb) or\n",
|
||||
" * Google Proprietary [notebook](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_proprietary_image_object_detection.ipynb)\n",
|
||||
"<br></br>\n",
|
||||
"* Test integrated tracking model\n",
|
||||
" * Upload models to model registry\n",
|
||||
" * Deploy uploaded models\n",
|
||||
" * Run batch predictions\n",
|
||||
" * Verify and visualize tracking results\n",
|
||||
"<br></br>\n",
|
||||
"* Cleanup resources\n",
|
||||
"\n",
|
||||
"### Costs\n",
|
||||
"\n",
|
||||
"This tutorial uses billable components of Google Cloud:\n",
|
||||
"\n",
|
||||
"* Vertex AI\n",
|
||||
"* Cloud Storage\n",
|
||||
"\n",
|
||||
"Learn about [Vertex AI\n",
|
||||
"pricing](https://cloud.google.com/vertex-ai/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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "EUgTNX8aOLDx"
|
||||
},
|
||||
"source": [
|
||||
"## Installation\n",
|
||||
"\n",
|
||||
"Install the following packages required to execute this notebook."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "kDPiiagrONAe"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip install --upgrade pip\n",
|
||||
"! pip install fastapi==0.96.0\n",
|
||||
"! pip install google-cloud-aiplatform==1.25.0\n",
|
||||
"! pip install google-cloud-storage==2.9.0\n",
|
||||
"! pip install tensorflow==2.11.0\n",
|
||||
"! pip install uvicorn==0.22.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ea399cf543a4"
|
||||
},
|
||||
"source": [
|
||||
"### Colab Only\n",
|
||||
"Run the following commands for Colab and skip this section if you are using Workbench."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "72f4e86b394c"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if \"google.colab\" in str(get_ipython()):\n",
|
||||
" ! pip install --upgrade google-cloud-aiplatform\n",
|
||||
"\n",
|
||||
" # Automatically restart kernel after installs\n",
|
||||
" import IPython\n",
|
||||
"\n",
|
||||
" app = IPython.Application.instance()\n",
|
||||
" app.kernel.do_shutdown(True)\n",
|
||||
"\n",
|
||||
" from google.colab import auth as google_auth\n",
|
||||
"\n",
|
||||
" google_auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "4fc1fc14-2d77-4bf7-8f6d-c1afc10c848a"
|
||||
},
|
||||
"source": [
|
||||
"If you are running this notebook locally, you will need to install the [Cloud SDK](https://cloud.google.com/sdk) and [gsutil](https://cloud.google.com/storage/docs/gsutil_install)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "f6c1bc20-3495-448a-b242-01930ba8153c"
|
||||
},
|
||||
"source": [
|
||||
"## Before you begin\n",
|
||||
"\n",
|
||||
"### Set up your Google Cloud project\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). Learn about [Vertex AI pricing](https://cloud.google.com/vertex-ai/pricing) and [Cloud Storage pricing](https://cloud.google.com/storage/pricing), and use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage.\n",
|
||||
"\n",
|
||||
"1. [Enable Artifact Registry](https://cloud.google.com/artifact-registry/docs/enable-service) and [create a repository](https://cloud.google.com/artifact-registry/docs/repositories/create-repos) for storing docker images.\n",
|
||||
"\n",
|
||||
"1. [Create a GCS bucket](https://cloud.google.com/storage/docs/creating-buckets) for storing experiment outputs.\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. [Create a service account](https://cloud.google.com/iam/docs/service-accounts-create?&_ga=2.233472348.-356102079.1688744268#iam-service-accounts-create-console) with Vertex AI User and Storage Object Admin roles for deploying fine tuned model to Vertex AI endpoint. [See how to grant Cloud Storage permissions to your service account](https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "dc52f3503d91"
|
||||
},
|
||||
"source": [
|
||||
"### Set your project ID\n",
|
||||
"\n",
|
||||
"**If you don't know your project ID**, try the following:\n",
|
||||
"* Run `gcloud config list`.\n",
|
||||
"* Run `gcloud projects list`.\n",
|
||||
"* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "e3ce64be5527"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3ff96cc60ab6"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\n",
|
||||
"\n",
|
||||
"**The following steps are required, regardless of your notebook environment.**\n",
|
||||
"\n",
|
||||
"To update your model artifacts without re-building the container, you must upload your model\n",
|
||||
"artifacts and any custom code to Cloud Storage.\n",
|
||||
"\n",
|
||||
"Set the name of your Cloud Storage bucket below. It must be unique across all\n",
|
||||
"Cloud Storage buckets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "34fad6505e5c"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_NAME = \"[your-bucket-name]\" # @param {type:\"string\"}\n",
|
||||
"BUCKET_URI = f\"gs://{BUCKET_NAME}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "a7c755f51afe"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "80497c1171f7"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l $REGION -p $PROJECT_ID $BUCKET_URI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e02b9811-b730-4573-83fa-9d47f2ce0436"
|
||||
},
|
||||
"source": [
|
||||
"### Setup remaining variables"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "7c2ce2fa-5a9b-40f6-b99d-6c1325775b36"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Cloud project setup.\n",
|
||||
"\n",
|
||||
"# The folder in the GCS bucket with input videos.\n",
|
||||
"# Fill it without the 'gs://' prefix.\n",
|
||||
"INPUT_GCS_FOLDER = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The video filename for the videos to be process.\n",
|
||||
"# Fill it without the 'gs://' prefix.\n",
|
||||
"VIDEO_FILE_NAME = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The video filename extension like .mp4. Please include period.\n",
|
||||
"# Fill it without the 'gs://' prefix.\n",
|
||||
"VIDEO_FILE_EXTENSION = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The folder in the GCS bucket where to store output videos and text\n",
|
||||
"# annotations. Fill it without the 'gs://' prefix.\n",
|
||||
"OUTPUT_GCS_FOLDER = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The Vertex IOD endpoint for object detection.\n",
|
||||
"# It is like projects/<project_number>/locations/<location>/endpoints/<endpoint_id>\"\n",
|
||||
"DETECTION_ENDPOINT = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# The label map for the Vertex IOD endpoint.\n",
|
||||
"# It is the path to a .yaml in GCS.\n",
|
||||
"# It is like gs://{BUCKET_NAME}/{FOLDER_NAME}/label_map.yaml\n",
|
||||
"ENDPOINT_LABEL_MAP = \"\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# You can choose a region from https://cloud.google.com/about/locations.\n",
|
||||
"# Only regions prefixed by \"us\", \"asia\", or \"europe\" are supported.\n",
|
||||
"REGION = \"us-central1\" # @param {type:\"string\"}\n",
|
||||
"REGION_PREFIX = REGION.split(\"-\")[0]\n",
|
||||
"assert REGION_PREFIX in (\n",
|
||||
" \"us\",\n",
|
||||
" \"europe\",\n",
|
||||
" \"asia\",\n",
|
||||
"), f'{REGION} is not supported. It must be prefixed by \"us\", \"asia\", or \"europe\".'\n",
|
||||
"\n",
|
||||
"# The pre-built docker images\n",
|
||||
"SERVE_DOCKER_URI = \"us-docker.pkg.dev/vertex-ai-restricted/vertex-vision-model-garden-dockers/vot-serve:latest\"\n",
|
||||
"\n",
|
||||
"# Prediction constants\n",
|
||||
"PREDICTION_ACCELERATOR_TYPE = \"NVIDIA_TESLA_T4\"\n",
|
||||
"PREDICTION_MACHINE_TYPE = \"n1-standard-4\"\n",
|
||||
"\n",
|
||||
"# The serving port.\n",
|
||||
"SERVE_PORT = 7080\n",
|
||||
"\n",
|
||||
"# The serving route.\n",
|
||||
"SERVE_ROUTE = \"/predictions/vot_serving\"\n",
|
||||
"\n",
|
||||
"# The service account you created in step-6 above.\n",
|
||||
"# It is like \"<account_name>@<project>.iam.gserviceaccount.com\"\n",
|
||||
"SERVICE_ACCOUNT = \"\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "82058975-23b5-4b97-9e14-dd9a29c578ed"
|
||||
},
|
||||
"source": [
|
||||
"### Initialize Vertex AI SDK\n",
|
||||
"\n",
|
||||
"Initialize the Vertex AI SDK for Python for your project."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "f0eb6283926b"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from google.cloud import aiplatform\n",
|
||||
"\n",
|
||||
"# Init common setup.\n",
|
||||
"aiplatform.init(project=PROJECT_ID, location=REGION, staging_bucket=GCS_BUCKET)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2b2fa2cff870"
|
||||
},
|
||||
"source": [
|
||||
"### Define utility functions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "a78f988d-e5e2-4a57-ba0f-569e970514c0"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def get_job_name_with_datetime(prefix: str):\n",
|
||||
" \"\"\"\n",
|
||||
" Generate a job name string with the current date and time appended.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" prefix: The prefix string to use for the job name.\n",
|
||||
"\n",
|
||||
" Returns:\n",
|
||||
" str: The job name string in the format \"{prefix}_{YYYYMMDD_HHMMSS}\".\n",
|
||||
" \"\"\"\n",
|
||||
" return prefix + datetime.now().strftime(\"_%Y%m%d_%H%M%S\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def deploy_model(\n",
|
||||
" detection_endpoint=None,\n",
|
||||
" label_map=None,\n",
|
||||
" output_bucket=None,\n",
|
||||
" save_video_results=False,\n",
|
||||
"):\n",
|
||||
" \"\"\"\n",
|
||||
" Deploy a model to a real-time prediction endpoint.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" detection_endpoint: The endpoint URL for object detection.\n",
|
||||
" label_map: Mapping of class IDs to class names.\n",
|
||||
" output_bucket: GCS bucket to save results.\n",
|
||||
" save_video_results: Whether to save video results.\n",
|
||||
"\n",
|
||||
" Returns:\n",
|
||||
" The created endpoint and deployed model objects.\n",
|
||||
" \"\"\"\n",
|
||||
" task = \"tracking\"\n",
|
||||
" endpoint = aiplatform.Endpoint.create(display_name=f\"{task}-endpoint\")\n",
|
||||
" serving_env = {\n",
|
||||
" \"DETECTION_ENDPOINT\": detection_endpoint,\n",
|
||||
" \"LABEL_MAP\": label_map,\n",
|
||||
" \"OUTPUT_BUCKET\": output_bucket,\n",
|
||||
" \"SAVE_VIDEO_RESULTS\": save_video_results,\n",
|
||||
" }\n",
|
||||
" model = aiplatform.Model.upload(\n",
|
||||
" display_name=task,\n",
|
||||
" serving_container_image_uri=SERVE_DOCKER_URI,\n",
|
||||
" serving_container_ports=[SERVE_PORT],\n",
|
||||
" serving_container_predict_route=SERVE_ROUTE,\n",
|
||||
" serving_container_health_route=\"/ping\",\n",
|
||||
" serving_container_environment_variables=serving_env,\n",
|
||||
" )\n",
|
||||
" model.deploy(\n",
|
||||
" endpoint=endpoint,\n",
|
||||
" machine_type=PREDICTION_MACHINE_TYPE,\n",
|
||||
" accelerator_type=PREDICTION_ACCELERATOR_TYPE,\n",
|
||||
" accelerator_count=1,\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
" )\n",
|
||||
" return endpoint, model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "242fc8cf-5a0e-483b-8c9d-77a474cedc4b"
|
||||
},
|
||||
"source": [
|
||||
"### Video Object Tracking with Vertex AI Endpoint and Model\n",
|
||||
"This section shows how to depoly the chained IOD and tracking model to Vertex AI to obtain predictions saved in a text file.\n",
|
||||
"\n",
|
||||
"* If you have not done so already, please set up a Vertex AI Endpoint resource with:\n",
|
||||
" * TensorFlow Vision [notebook](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_tfvision_image_object_detection.ipynb) or\n",
|
||||
" * Google Proprietary [notebook](https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/community/model_garden/model_garden_proprietary_image_object_detection.ipynb)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "IyW_3gDz7Rk4"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# The Vertex IOD endpoint for object detection.\n",
|
||||
"# It is like projects/<project_number>/locations/<location>/endpoints/<endpoint_id>\"\n",
|
||||
"DETECTION_ENDPOINT = \"\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "oPlXzhjx4fkj"
|
||||
},
|
||||
"source": [
|
||||
"This is the local URL for making requests to the tracking model serving container running on localhost. It points to the /predictions route on port SERVE_PORT that will handle model inference requests."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "q_dp2-wY4guH"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"LOCAL_SERVE_URL = f\"http://localhost:{SERVE_PORT}/{SERVE_ROUTE}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "seiABeZU5s-S"
|
||||
},
|
||||
"source": [
|
||||
"Run the serving container in a separate shell."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "No5ALIXA5tVl"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!nvidia-docker run -t --rm \\\n",
|
||||
"-p {SERVE_PORT}:{SERVE_PORT} \\\n",
|
||||
"-e DETECTION_ENDPOINT=f\"{DETECTION_ENDPOINT}\" \\\n",
|
||||
"-e LABEL_MAP=f\"{ENDPOINT_LABEL_MAP}\" \\\n",
|
||||
"-e OUTPUT_BUCKET=f\"gs://{GCS_BUCKET}/{OUTPUT_GCS_FOLDER}\" \\\n",
|
||||
"-e SAVE_VIDEO_RESULTS=1 \\\n",
|
||||
"-e CUDA_VISIBLE_DEVICES=0 \\\n",
|
||||
"{SERVE_DOCKER_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "a8a53d74-aa76-4429-adbe-0460ea29bae2"
|
||||
},
|
||||
"source": [
|
||||
"### Test endpoint locally and perform online prediction\n",
|
||||
"This section shows how to make prediction requests to the endpoint to obtain track IDs and bounding box coordinates for detected and tracked objects saved to a text file and/or annotated video output."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "d6e51c57-b5e2-4ae7-888a-5391cceee5fb"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"\n",
|
||||
"payload = json.dumps(\n",
|
||||
" {\n",
|
||||
" \"instances\": [\n",
|
||||
" {\n",
|
||||
" \"video_uri\": f\"gs://{GCS_BUCKET}/{INPUT_GCS_FOLDER}/{VIDEO_FILE_NAME}1{VIDEO_FILE_EXTENSION}\"\n",
|
||||
" },\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
")\n",
|
||||
"r = requests.post(\n",
|
||||
" LOCAL_SERVE_URL,\n",
|
||||
" data=payload,\n",
|
||||
" headers={\"content-type\": \"application/json\", \"Accept-Charset\": \"UTF-8\"},\n",
|
||||
")\n",
|
||||
"preds = r.json()\n",
|
||||
"\n",
|
||||
"print(preds)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "ec86394f174d"
|
||||
},
|
||||
"source": [
|
||||
"# Batch Prediction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "c4ce0a1f-96dc-4371-8dc6-803143a98e17"
|
||||
},
|
||||
"source": [
|
||||
"### Setup input file for batch prediction and upload to gs bucket\n",
|
||||
"Provide batch prediction input in jsonl format."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "d79c03b3-7bce-4ddd-b362-f6663c026e9a"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"INPUT_FILE = \"instances.jsonl\"\n",
|
||||
"VIDEO_PATH_1 = (\n",
|
||||
" f\"gs://{GCS_BUCKET}/{INPUT_GCS_FOLDER}/{VIDEO_FILE_NAME}1{VIDEO_FILE_EXTENSION}\"\n",
|
||||
")\n",
|
||||
"VIDEO_PATH_2 = (\n",
|
||||
" f\"gs://{GCS_BUCKET}/{INPUT_GCS_FOLDER}/{VIDEO_FILE_NAME}2{VIDEO_FILE_EXTENSION}\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "bc92efe1-e178-402e-a7dc-0397ee7ae402"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%writefile $INPUT_FILE\n",
|
||||
"{\"data\": { \"video_uri\": VIDEO_PATH_1}}\n",
|
||||
"{\"data\": { \"video_uri\": VIDEO_PATH_2}}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "8473cca5-6442-41d6-ac5e-881b155bdb56"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!gsutil cp \"instances.jsonl\" f\"gs://{GCS_BUCKET}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "5749b8eb-76c7-4a83-8755-0ce2c759b684"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"gcs_input_uri = f\"gs://{GCS_BUCKET}/instances.jsonl\"\n",
|
||||
"dest_uri = f\"gs://{GCS_BUCKET}/{OUTPUT_GCS_FOLDER}\"\n",
|
||||
"print(gcs_input_uri)\n",
|
||||
"! gsutil cat $gcs_input_uri"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "7e3064a7-ff6d-463d-9688-129c5c6cf4d0"
|
||||
},
|
||||
"source": [
|
||||
"### Create batch prediction job id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "02609f01-5b61-42d7-a6e9-00ef6e3993f4"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"JOB_PREFIX = \"<job name prefix>\" # @param {type:\"string\"}\n",
|
||||
"job_name = get_job_name_with_datetime(JOB_PREFIX)\n",
|
||||
"print(job_name)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "948d95fc-16af-4921-8aaa-1c46cfd30eba"
|
||||
},
|
||||
"source": [
|
||||
"### Perform batch prediction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "0c63153c-22a8-42b9-b251-1d9fb4611001"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"batch_predict_job = model.batch_predict(\n",
|
||||
" job_display_name=job_name,\n",
|
||||
" gcs_source=gcs_input_uri,\n",
|
||||
" gcs_destination_prefix=dest_uri,\n",
|
||||
" sync=False,\n",
|
||||
" machine_type=PREDICTION_MACHINE_TYPE,\n",
|
||||
" service_account=SERVICE_ACCOUNT,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(batch_predict_job)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "2ca21ed1-b4c0-4a89-a04a-798f11bc85c3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"batch_predict_job.wait()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2b148a5e-cf83-4c05-9e86-3ba5cf4b224a"
|
||||
},
|
||||
"source": [
|
||||
"## Cleaning up\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:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "5ec87451-3f4b-46f5-9352-c903a90df852"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Undeploy model and delete endpoint\n",
|
||||
"endpoint.delete(force=True)\n",
|
||||
"\n",
|
||||
"# Delete the model resource\n",
|
||||
"model.delete()\n",
|
||||
"\n",
|
||||
"delete_bucket = False\n",
|
||||
"\n",
|
||||
"if delete_bucket:\n",
|
||||
" ! gsutil rm -r $BUCKET_URI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "model_garden_video_object_tracking_serve.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Copyright 2022 Google LLC\n",
|
||||
"# Copyright 2023 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",
|
||||
|
||||
@@ -88,8 +88,8 @@
|
||||
"- Make a batch prediction with the BigQuery ML model.\n",
|
||||
"- Create a Vertex AI `Dataset` resource.\n",
|
||||
"- Train the Vertex AI Forecasting model.\n",
|
||||
"- View the Model evaluation.\n",
|
||||
"- Make a batch prediction with the Model.\n"
|
||||
"- View the Vertex AI Model Evaluation results.\n",
|
||||
"- Make a batch prediction with the Vertex AI Forecasting model.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -786,7 +786,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"name": "automl_image_classification_online_online_prediction.ipynb",
|
||||
"name": "automl_image_classification_online_prediction.ipynb",
|
||||
"toc_visible": true
|
||||
},
|
||||
"kernelspec": {
|
||||
@@ -620,10 +620,15 @@
|
||||
")\n",
|
||||
"# Must be the same region as batch_predict_bq_input_uri\n",
|
||||
"client = bigquery.Client(project=PROJECT_ID)\n",
|
||||
"bq_dataset = bigquery.Dataset(batch_predict_bq_output_dataset_path)\n",
|
||||
"bq_dataset_id = bigquery.Dataset(batch_predict_bq_output_dataset_path)\n",
|
||||
"dataset_region = \"US\" # @param {type : \"string\"}\n",
|
||||
"bq_dataset.location = dataset_region\n",
|
||||
"bq_dataset = client.create_dataset(bq_dataset)\n",
|
||||
"bq_dataset_id.location = dataset_region\n",
|
||||
"# delete any existing dataset\n",
|
||||
"try:\n",
|
||||
" client.delete_dataset(bq_dataset_id, delete_contents=True)\n",
|
||||
"except Exception as e:\n",
|
||||
" print(e)\n",
|
||||
"bq_dataset = client.create_dataset(bq_dataset_id)\n",
|
||||
"print(\n",
|
||||
" \"Created bigquery dataset {} in {}\".format(\n",
|
||||
" batch_predict_bq_output_dataset_path, dataset_region\n",
|
||||
@@ -857,6 +862,12 @@
|
||||
"# Delete batch prediction job\n",
|
||||
"batch_prediction_job.delete()\n",
|
||||
"\n",
|
||||
"# Delete the dataset\n",
|
||||
"try:\n",
|
||||
" client.delete_dataset(bq_dataset_id)\n",
|
||||
"except Exception as e:\n",
|
||||
" print(e)\n",
|
||||
"\n",
|
||||
"# Set this to true only if you'd like to delete your bucket\n",
|
||||
"delete_bucket = False\n",
|
||||
"\n",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -166,7 +166,7 @@
|
||||
"! pip3 install -U \"uvicorn[standard]>=0.12.0,<0.14.0\" fastapi~=0.63 -q\n",
|
||||
"\n",
|
||||
"# Vertex SDK for Python\n",
|
||||
"! pip3 install -U google-cloud-aiplatform -q"
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,18 +33,18 @@
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/custom/get_started_vertex_training.ipynb\">\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/custom/get_started_vertex_training_xgboost.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/custom/get_started_vertex_training.ipynb\">\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/custom/get_started_vertex_training_xgboost.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://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/official/custom/get_started_vertex_training.ipynb\">\n",
|
||||
" <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/official/custom/get_started_vertex_training_xgboost.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",
|
||||
|
||||
@@ -458,14 +458,12 @@
|
||||
"\n",
|
||||
"Set the variables `DEPLOY_GPU/DEPLOY_NGPU` to use a container image supporting a GPU and the number of GPUs allocated to the virtual machine (VM) instance. For example, to use a GPU container image with 4 Nvidia Telsa K80 GPUs allocated to each VM, you would specify:\n",
|
||||
"\n",
|
||||
" (aip.AcceleratorType.NVIDIA_TESLA_K80, 4)\n",
|
||||
" (aip.gapic.AcceleratorType.NVIDIA_TESLA_K80, 4)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Otherwise specify `(None, None)` to use a container image to run on a CPU.\n",
|
||||
"\n",
|
||||
"Learn more about [hardware accelerator support for your region](https://cloud.google.com/vertex-ai/docs/general/locations#accelerators).\n",
|
||||
"\n",
|
||||
"*Note*: TF releases before 2.3 for GPU support will fail to load the custom model in this tutorial. It is a known issue and fixed in TF 2.3. This is caused by static graph ops that are generated in the serving function. If you encounter this issue on your own custom models, use a container image for TF 2.3 with GPU support."
|
||||
"Learn more about [hardware accelerator support for your region](https://cloud.google.com/vertex-ai/docs/general/locations#accelerators)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -507,21 +505,12 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if os.getenv(\"IS_TESTING_TF\"):\n",
|
||||
" TF = os.getenv(\"IS_TESTING_TF\")\n",
|
||||
"else:\n",
|
||||
" TF = \"2.5\".replace(\".\", \"-\")\n",
|
||||
"TF = \"2.5\".replace(\".\", \"-\")\n",
|
||||
"\n",
|
||||
"if TF[0] == \"2\":\n",
|
||||
" if DEPLOY_GPU:\n",
|
||||
" DEPLOY_VERSION = \"tf2-gpu.{}\".format(TF)\n",
|
||||
" else:\n",
|
||||
" DEPLOY_VERSION = \"tf2-cpu.{}\".format(TF)\n",
|
||||
"if DEPLOY_GPU:\n",
|
||||
" DEPLOY_VERSION = \"tf2-gpu.{}\".format(TF)\n",
|
||||
"else:\n",
|
||||
" if DEPLOY_GPU:\n",
|
||||
" DEPLOY_VERSION = \"tf-gpu.{}\".format(TF)\n",
|
||||
" else:\n",
|
||||
" DEPLOY_VERSION = \"tf-cpu.{}\".format(TF)\n",
|
||||
" DEPLOY_VERSION = \"tf2-cpu.{}\".format(TF)\n",
|
||||
"\n",
|
||||
"DEPLOY_IMAGE = \"{}-docker.pkg.dev/vertex-ai/prediction/{}:latest\".format(\n",
|
||||
" REGION.split(\"-\")[0], DEPLOY_VERSION\n",
|
||||
@@ -558,10 +547,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if os.getenv(\"IS_TESTING_DEPLOY_MACHINE\"):\n",
|
||||
" MACHINE_TYPE = os.getenv(\"IS_TESTING_DEPLOY_MACHINE\")\n",
|
||||
"else:\n",
|
||||
" MACHINE_TYPE = \"n1-standard\"\n",
|
||||
"MACHINE_TYPE = \"n1-standard\"\n",
|
||||
"\n",
|
||||
"VCPU = \"4\"\n",
|
||||
"DEPLOY_COMPUTE = MACHINE_TYPE + \"-\" + VCPU\n",
|
||||
@@ -965,7 +951,7 @@
|
||||
"\n",
|
||||
"Use `GetDeploymentResourcePool` API to check out the deploynent resource pool that you created. \n",
|
||||
"\n",
|
||||
"Learn more about [Get Deployment Resource Pool](https://source.corp.google.com/piper///depot/google3/google/cloud/aiplatform/master/deployment_resource_pool_service.proto;l=75?q=deployment_resource_pool&sq=package:piper%20file:%2F%2Fdepot%2Fgoogle3%20-file:google3%2Fexperimental)."
|
||||
"Learn more about [Get Deployment Resource Pool](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.deploymentResourcePools/get)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -992,7 +978,7 @@
|
||||
"\n",
|
||||
"Use `ListDeploymentResourcePools` API to list all the deployment resource pools. \n",
|
||||
"\n",
|
||||
"Learn more about [Listing Deployment Resource Pools](https://source.corp.google.com/piper///depot/google3/google/cloud/aiplatform/master/deployment_resource_pool_service.proto;l=101?q=deployment_resource_pool&sq=package:piper%20file:%2F%2Fdepot%2Fgoogle3%20-file:google3%2Fexperimental)."
|
||||
"Learn more about [Listing Deployment Resource Pools](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.deploymentResourcePools/list)."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"<table align=\"left\">\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/datasets/get_started_bq_datasets.ipynb\">\n",
|
||||
" <img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\" alt=\"GitHub logo\">\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",
|
||||
|
||||
@@ -714,7 +714,12 @@
|
||||
"):\n",
|
||||
"\n",
|
||||
" custom_trainer(\n",
|
||||
" train_uri, label_uri, max_depth, learning_rate, boost_rounds, model_uri\n",
|
||||
" train_uri=train_uri,\n",
|
||||
" label_uri=label_uri,\n",
|
||||
" max_depth=max_depth,\n",
|
||||
" learning_rate=learning_rate,\n",
|
||||
" boost_rounds=boost_rounds,\n",
|
||||
" model_uri=model_uri,\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1136
File diff suppressed because it is too large
Load Diff
+1537
File diff suppressed because it is too large
Load Diff
@@ -34,18 +34,18 @@
|
||||
"<table align=\"left\">\n",
|
||||
"\n",
|
||||
" <td>\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/migration/UJ14 Vertex SDK AutoML Video Classification.ipynb\">\n",
|
||||
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/migration/sdk-automl-video-classification-batch-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://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/migration/UJ14 Vertex SDK AutoML Video Classification.ipynb\">\n",
|
||||
" <a href=\"https://github.com/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/migration/sdk-automl-video-classification-batch-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://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/official/migration/UJ14 Vertex SDK AutoML Video Classification.ipynb\" target='_blank'>\n",
|
||||
"<a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/vertex-ai-samples/main/notebooks/official/migration/sdk-automl-video-classification-batch-prediction.ipynb\" target='_blank'>\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",
|
||||
|
||||
+384
-565
File diff suppressed because it is too large
Load Diff
+110
-14
@@ -29,7 +29,7 @@
|
||||
"id": "JAPoU8Sm5E6e"
|
||||
},
|
||||
"source": [
|
||||
"# Vertex AI Pipelines: Evaluating BatchPrediction results from a Custom Tabular classification model\n",
|
||||
"# Vertex AI Pipelines: Evaluating BatchPrediction results from a custom tabular classification model\n",
|
||||
"\n",
|
||||
"<table align=\"left\">\n",
|
||||
"\n",
|
||||
@@ -151,15 +151,16 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Install the latest versions of the following packages\n",
|
||||
"! pip3 install --upgrade google-cloud-aiplatform \\\n",
|
||||
" google-cloud-pipeline-components==1.0.26 \\\n",
|
||||
" matplotlib \\\n",
|
||||
" pyarrow -q\n",
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform \\\n",
|
||||
" google-cloud-pipeline-components==1.0.26 \\\n",
|
||||
" matplotlib \\\n",
|
||||
" pyarrow \n",
|
||||
"# Install the specified versions of the following packages\n",
|
||||
"! pip3 install scikit-learn==1.0 \\\n",
|
||||
" pandas \\\n",
|
||||
" joblib==1.2.0 \\\n",
|
||||
" numpy==1.23.3 -q"
|
||||
"! pip3 install --quiet scikit-learn==1.0 \\\n",
|
||||
" pandas \\\n",
|
||||
" joblib==1.2.0 \\\n",
|
||||
" numpy==1.23.3 \\\n",
|
||||
" db-dtypes"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -401,12 +402,25 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if SERVICE_ACCOUNT == \"[your-service-account]\":\n",
|
||||
" shell_output = ! gcloud projects list --filter=\"PROJECT_ID:'{PROJECT_ID}'\" --format='value(PROJECT_NUMBER)'\n",
|
||||
" PROJECT_NUMBER = shell_output[0]\n",
|
||||
" SERVICE_ACCOUNT = f\"{PROJECT_NUMBER}-compute@developer.gserviceaccount.com\"\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"print(\"Service Account:\", SERVICE_ACCOUNT)"
|
||||
"IS_COLAB = \"google.colab\" in sys.modules\n",
|
||||
"if (\n",
|
||||
" SERVICE_ACCOUNT == \"\"\n",
|
||||
" or SERVICE_ACCOUNT is None\n",
|
||||
" or SERVICE_ACCOUNT == \"[your-service-account]\"\n",
|
||||
"):\n",
|
||||
" # Get your service account from gcloud\n",
|
||||
" if not IS_COLAB:\n",
|
||||
" shell_output = !gcloud auth list 2>/dev/null\n",
|
||||
" SERVICE_ACCOUNT = shell_output[2].replace(\"*\", \"\").strip()\n",
|
||||
"\n",
|
||||
" else: # IS_COLAB:\n",
|
||||
" shell_output = ! gcloud projects describe $PROJECT_ID\n",
|
||||
" project_number = shell_output[-1].split(\":\")[1].strip().replace(\"'\", \"\")\n",
|
||||
" SERVICE_ACCOUNT = f\"{project_number}-compute@developer.gserviceaccount.com\"\n",
|
||||
"\n",
|
||||
" print(\"Service Account:\", SERVICE_ACCOUNT)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1035,6 +1049,34 @@
|
||||
"RUN pip install -r requirements.txt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "OrpUIkAIs_uQ"
|
||||
},
|
||||
"source": [
|
||||
"#### Create a private Docker repository\n",
|
||||
"\n",
|
||||
"Your first step is to create your own Docker repository in Google Artifact Registry."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "0amu4063tDnG"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"! gcloud services enable artifactregistry.googleapis.com\n",
|
||||
"\n",
|
||||
"if os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! sudo apt-get update --yes && sudo apt-get --only-upgrade --yes install google-cloud-sdk-cloud-run-proxy google-cloud-sdk-harbourbridge google-cloud-sdk-cbt google-cloud-sdk-gke-gcloud-auth-plugin google-cloud-sdk-kpt google-cloud-sdk-local-extract google-cloud-sdk-minikube google-cloud-sdk-app-engine-java google-cloud-sdk-app-engine-go google-cloud-sdk-app-engine-python google-cloud-sdk-spanner-emulator google-cloud-sdk-bigtable-emulator google-cloud-sdk-nomos google-cloud-sdk-package-go-module google-cloud-sdk-firestore-emulator kubectl google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-python-extras google-cloud-sdk-cloud-build-local google-cloud-sdk-kubectl-oidc google-cloud-sdk-anthos-auth google-cloud-sdk-app-engine-grpc google-cloud-sdk-pubsub-emulator google-cloud-sdk-datalab google-cloud-sdk-skaffold google-cloud-sdk google-cloud-sdk-terraform-tools google-cloud-sdk-config-connector\n",
|
||||
" ! gcloud components update --quiet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
@@ -1159,6 +1201,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# define the evaluation pipeline\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@kfp.dsl.pipeline(name=\"custom-tabular-classification-evaluation-pipeline\")\n",
|
||||
"def evaluation_custom_tabular_feature_attribution_pipeline(\n",
|
||||
" project: str,\n",
|
||||
@@ -1246,6 +1290,58 @@
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "c7b87e9196f3"
|
||||
},
|
||||
"source": [
|
||||
"### Optional: Workaround to import a BigQuery table for predictions_bigquery_source"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "fc2df1b02f1a"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\"\"\"\n",
|
||||
"# Set constants for BigQuery Table\n",
|
||||
"BIGQUERY_PROJECT_ID = \"your-project-id\"\n",
|
||||
"BIGQUERY_DATASET_ID = \"your-dataset-id\"\n",
|
||||
"BIGQUERY_PREDICTION_RESULTS_TABLE_ID = \"your-table-id\"\n",
|
||||
"\n",
|
||||
"# Import the BigQuery table using the importer to obtain a BQTable artifact\n",
|
||||
"bq_table_uri = f\"bq://{BIGQUERY_PROJECT_ID}.{BIGQUERY_DATASET_ID}.{BIGQUERY_PREDICTION_RESULTS_TABLE_ID}\"\n",
|
||||
"bq_table = kfp.v2.dsl.importer(\n",
|
||||
" artifact_uri=bq_table_uri,\n",
|
||||
" artifact_class=artifact_types.BQTable,\n",
|
||||
" metadata={\n",
|
||||
" \"projectId\": BIGQUERY_PROJECT_ID,\n",
|
||||
" \"datasetId\": BIGQUERY_DATASET_ID,\n",
|
||||
" \"tableId\": BIGQUERY_PREDICTION_RESULTS_TABLE_ID,\n",
|
||||
" },\n",
|
||||
").output\n",
|
||||
"\n",
|
||||
"# Run the evaluation based on prediction type\n",
|
||||
"eval_task = ModelEvaluationClassificationOp(\n",
|
||||
" project=project,\n",
|
||||
" location=location,\n",
|
||||
" root_dir=root_dir,\n",
|
||||
" class_labels=evaluation_class_names,\n",
|
||||
" prediction_label_column=evaluation_prediction_label_column,\n",
|
||||
" prediction_score_column=evaluation_prediction_score_column,\n",
|
||||
" target_field_name=target_field_name,\n",
|
||||
" ground_truth_format=batch_predict_instances_format,\n",
|
||||
" ground_truth_bigquery_source=bq_table_uri,\n",
|
||||
" predictions_format=batch_predict_predictions_format,\n",
|
||||
" predictions_bigquery_source=bq_table,\n",
|
||||
")\n",
|
||||
"\"\"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
|
||||
@@ -146,48 +146,6 @@
|
||||
"to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "c233f28c4388"
|
||||
},
|
||||
"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.\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": {
|
||||
@@ -207,164 +165,38 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform \\\n",
|
||||
" google-cloud-bigquery \\\n",
|
||||
" pandas \\\n",
|
||||
" pyarrow \\\n",
|
||||
" 'kfp<2' \\\n",
|
||||
" 'google-cloud-pipeline-components<2' \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 --upgrade google-cloud-aiplatform \\\n",
|
||||
" google-cloud-bigquery \\\n",
|
||||
" pandas \\\n",
|
||||
" pyarrow \\\n",
|
||||
" 'kfp<2' \\\n",
|
||||
" 'google-cloud-pipeline-components<2' {USER_FLAG} -q \n",
|
||||
"! pip3 install db-dtypes {USER_FLAG} -q"
|
||||
"! pip3 install --quiet db-dtypes "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "d3a26cb9b19d"
|
||||
"id": "58707a750154"
|
||||
},
|
||||
"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."
|
||||
"### Colab only: Uncomment the following cell to restart the kernel."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "c1464805870e"
|
||||
"id": "f200f10a1da3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Automatically restart kernel after installs\n",
|
||||
"import os\n",
|
||||
"# Automatically restart kernel after installs so that your environment can access the new packages\n",
|
||||
"# import IPython\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": "acb809176716"
|
||||
},
|
||||
"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, Compute Engine, Artifact Registry, BigQuery and Cloud Build APIs](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com,compute.googleapis.com,artifactregistry.googleapis.com,bigquery.googleapis.com,cloudbuild.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`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "dcdfccf50581"
|
||||
},
|
||||
"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\""
|
||||
"# app = IPython.Application.instance()\n",
|
||||
"# app.kernel.do_shutdown(True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -380,7 +212,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 1,
|
||||
"metadata": {
|
||||
"id": "4e166d927e36"
|
||||
},
|
||||
@@ -401,116 +233,166 @@
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "bf8cda09fdf2"
|
||||
"id": "BF1j6f9HApxa"
|
||||
},
|
||||
"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",
|
||||
"2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
|
||||
"\n",
|
||||
"3. [Enable the Vertex AI API]\n",
|
||||
"\n",
|
||||
"4. If you are running this notebook locally, you need to install the [Cloud SDK](https://cloud.google.com/sdk)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**If you don't know your project ID**, try the following:\n",
|
||||
"* Run `gcloud config list`.\n",
|
||||
"* Run `gcloud projects list`.\n",
|
||||
"* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Updated property [core/project].\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"#### Region\n",
|
||||
"\n",
|
||||
"You can also change the `REGION` variable used by Vertex AI. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"id": "region"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "sBCra4QMA2wR"
|
||||
},
|
||||
"source": [
|
||||
"### Authenticate your Google Cloud account\n",
|
||||
"\n",
|
||||
"**If you are using Vertex AI Workbench Notebooks**, your environment is already\n",
|
||||
"authenticated. \n",
|
||||
"Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below.\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",
|
||||
"**1. Vertex AI Workbench**\n",
|
||||
"* Do nothing as you are already authenticated.\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."
|
||||
"**2. Local JupyterLab instance, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 4,
|
||||
"metadata": {
|
||||
"id": "aa4c61531a1d"
|
||||
"id": "254614fa0c46"
|
||||
},
|
||||
"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]'"
|
||||
"# ! gcloud auth login"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "40923b15369c"
|
||||
"id": "ef21552ccea8"
|
||||
},
|
||||
"source": [
|
||||
"**3. Colab, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"id": "603adbbf0532"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# from google.colab import auth\n",
|
||||
"# auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "f6b2ccc891ed"
|
||||
},
|
||||
"source": [
|
||||
"**4. Service account or other**\n",
|
||||
"* See how to grant Cloud Storage permissions to your service account at https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\n",
|
||||
"\n",
|
||||
"**The following steps are required, regardless of your notebook environment.**\n",
|
||||
"\n",
|
||||
"When you submit a Pipeline or a Batch prediction job using the Vertex AI SDK, Vertex AI uses a Cloud Storage bucket as a staging location. Instead of providing while running the job, the staging location can also be provided to Vertex AI while initializing. In this tutorial, Vertex AI is initialized with a staging bucket that you create in the next steps.\n",
|
||||
"\n",
|
||||
"Set the name of your Cloud Storage bucket below. It must be unique across all\n",
|
||||
"Cloud Storage buckets."
|
||||
"Create a storage bucket to store intermediate artifacts such as datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "f78cf4290843"
|
||||
"id": "MzGDU7TWdts_"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_NAME = \"[your-bucket-name]\" # @param {type:\"string\"}\n",
|
||||
"BUCKET_URI = f\"gs://{BUCKET_NAME}\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "219a24ea078b"
|
||||
},
|
||||
"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_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "a8a62bec0259"
|
||||
"id": "-EcIXiGsCePi"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
@@ -520,33 +402,13 @@
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "91c46850b49b"
|
||||
"id": "NIq7R4HZCfIc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l $REGION -p $PROJECT_ID $BUCKET_URI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "4e69d430073b"
|
||||
},
|
||||
"source": [
|
||||
"Finally, validate access to your Cloud Storage bucket by examining its contents:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "835eaacd691f"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil ls -al $BUCKET_URI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
@@ -577,6 +439,9 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"IS_COLAB = \"google.colab\" in sys.modules\n",
|
||||
"if (\n",
|
||||
" SERVICE_ACCOUNT == \"\"\n",
|
||||
" or SERVICE_ACCOUNT is None\n",
|
||||
@@ -638,6 +503,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"from google.cloud import aiplatform, bigquery\n",
|
||||
"from kfp.dsl import pipeline\n",
|
||||
"from kfp.v2 import compiler"
|
||||
@@ -1099,6 +966,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Define the pipeline\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@pipeline(name=\"custom-model-bq-batch-prediction-pipeline\")\n",
|
||||
"def custom_model_bq_batch_prediction_pipeline(\n",
|
||||
" project: str,\n",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -568,7 +568,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@component(\n",
|
||||
" packages_to_install=[\"scikit-learn\"],\n",
|
||||
" packages_to_install=[\"scikit-learn==1.2.2\"],\n",
|
||||
" base_image=\"python:3.9\",\n",
|
||||
" output_component_file=\"wine_classification_component.yaml\",\n",
|
||||
")\n",
|
||||
@@ -613,7 +613,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@component(packages_to_install=[\"scikit-learn\"], base_image=\"python:3.9\")\n",
|
||||
"@component(packages_to_install=[\"scikit-learn==1.2.2\"], base_image=\"python:3.9\")\n",
|
||||
"def iris_sgdclassifier(\n",
|
||||
" test_samples_fraction: float,\n",
|
||||
" metricsc: Output[ClassificationMetrics],\n",
|
||||
@@ -660,7 +660,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@component(\n",
|
||||
" packages_to_install=[\"scikit-learn\"],\n",
|
||||
" packages_to_install=[\"scikit-learn==1.2.2\"],\n",
|
||||
" base_image=\"python:3.9\",\n",
|
||||
")\n",
|
||||
"def iris_logregression(\n",
|
||||
@@ -762,7 +762,7 @@
|
||||
"\n",
|
||||
"compiler.Compiler().compile(\n",
|
||||
" pipeline_func=pipeline,\n",
|
||||
" package_path=\"tabular classification_pipeline.json\".replace(\" \", \"_\"),\n",
|
||||
" package_path=\"tabular_classification_pipeline.json\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
@@ -789,8 +789,8 @@
|
||||
"\n",
|
||||
"job = aip.PipelineJob(\n",
|
||||
" display_name=DISPLAY_NAME,\n",
|
||||
" template_path=\"tabular classification_pipeline.json\".replace(\" \", \"_\"),\n",
|
||||
" job_id=f\"tabular classification-v2{UUID}-1\".replace(\" \", \"\"),\n",
|
||||
" template_path=\"tabular_classification_pipeline.json\",\n",
|
||||
" job_id=f\"tabular-classification-v2{UUID}-1\",\n",
|
||||
" pipeline_root=PIPELINE_ROOT,\n",
|
||||
" parameter_values={\"seed\": 7, \"splits\": 10},\n",
|
||||
")\n",
|
||||
@@ -847,8 +847,8 @@
|
||||
"source": [
|
||||
"job = aip.PipelineJob(\n",
|
||||
" display_name=\"iris_\" + UUID,\n",
|
||||
" template_path=\"tabular classification_pipeline.json\".replace(\" \", \"_\"),\n",
|
||||
" job_id=f\"tabular classification-pipeline-v2{UUID}-2\".replace(\" \", \"\"),\n",
|
||||
" template_path=\"tabular_classification_pipeline.json\",\n",
|
||||
" job_id=f\"tabular-classification-pipeline-v2{UUID}-2\",\n",
|
||||
" pipeline_root=PIPELINE_ROOT,\n",
|
||||
" parameter_values={\"seed\": 5, \"splits\": 7},\n",
|
||||
")\n",
|
||||
@@ -976,6 +976,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"delete_bucket = False\n",
|
||||
"\n",
|
||||
"job.delete()\n",
|
||||
|
||||
@@ -154,10 +154,11 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Install the packages\n",
|
||||
"! pip3 install --upgrade google-cloud-aiplatform \\\n",
|
||||
" google-cloud-storage \\\n",
|
||||
" google-cloud-bigquery \\\n",
|
||||
" pyarrow -q"
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform \\\n",
|
||||
" google-cloud-storage \\\n",
|
||||
" google-cloud-bigquery \\\n",
|
||||
" pyarrow \\\n",
|
||||
" db-dtypes\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -352,7 +353,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = \"gs://your-bucket-name-unique\" # @param {type:\"string\"}"
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -372,7 +373,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l $REGION -p $PROJECT_ID $BUCKET_URI"
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1016,7 +1017,7 @@
|
||||
" display_name=JOB_NAME,\n",
|
||||
" script_path=\"task.py\",\n",
|
||||
" container_uri=TRAIN_IMAGE,\n",
|
||||
" requirements=[\"google-cloud-bigquery>=2.20.0\", \"db-dtypes\"],\n",
|
||||
" requirements=[\"google-cloud-bigquery>=2.20.0\", \"db-dtypes\", \"protobuf==3.20.3\"],\n",
|
||||
" model_serving_container_image_uri=DEPLOY_IMAGE,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
@@ -1439,12 +1440,7 @@
|
||||
"\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 notebook:\n",
|
||||
"\n",
|
||||
"- Training Job\n",
|
||||
"- Model\n",
|
||||
"- Cloud Storage Bucket\n",
|
||||
"- BigQuery Dataset"
|
||||
"Otherwise, you can delete the individual resources you created in this notebook."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1461,6 +1457,9 @@
|
||||
"# Delete the training job\n",
|
||||
"job.delete()\n",
|
||||
"\n",
|
||||
"# Delete the dataset\n",
|
||||
"dataset.delete()\n",
|
||||
"\n",
|
||||
"# Delete the model\n",
|
||||
"model.delete()\n",
|
||||
"\n",
|
||||
|
||||
@@ -149,7 +149,9 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip3 install --quiet --upgrade tensorflow\n",
|
||||
"! pip3 install --quiet --upgrade google-cloud-aiplatform tensorboard-plugin-profile\n",
|
||||
"! pip3 install --quiet --upgrade google-cloud-aiplatform \\\n",
|
||||
" tensorboard-plugin-profile \\\n",
|
||||
" google-cloud-pipeline-components\n",
|
||||
"! gcloud components update --quiet"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -53,6 +53,17 @@
|
||||
"<br/><br/><br/>\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "962e636b5cee"
|
||||
},
|
||||
"source": [
|
||||
"**_NOTE_**: This notebook has been tested in the following environment:\n",
|
||||
"\n",
|
||||
"* Python version = 3.9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
@@ -74,7 +85,7 @@
|
||||
"source": [
|
||||
"### Objective\n",
|
||||
"\n",
|
||||
"In this tutorial, you learn how to create two classification models using Vertex AI TabNet Tabular Workflows. Each workflow is a managed instance of [Vertex AI Pipelines](https://cloud.google.com/vertex-ai/docs/pipelines/introduction).\n",
|
||||
"In this tutorial, you learn how to create classification models on tabular data using two of the Vertex AI TabNet Tabular Workflows. Each workflow is a managed instance of [Vertex AI Pipelines](https://cloud.google.com/vertex-ai/docs/pipelines/introduction).\n",
|
||||
"\n",
|
||||
"This tutorial uses the following Google Cloud ML services and resources:\n",
|
||||
"\n",
|
||||
@@ -98,8 +109,10 @@
|
||||
"source": [
|
||||
"### Dataset\n",
|
||||
"\n",
|
||||
"The dataset you will be using is [Bank Marketing](https://archive.ics.uci.edu/ml/datasets/bank+marketing).\n",
|
||||
"The data is for direct marketing campaigns (phone calls) of a Portuguese banking institution. The binary classification goal is to predict if a client subscribe a term deposit. For this notebook, you randomly selected 90% of the rows in the original dataset and saved them in a train.csv file hosted on Cloud Storage. To download the file, click [here](https://storage.googleapis.com/cloud-samples-data-us-central1/vertex-ai/tabular-workflows/datasets/bank-marketing/train.csv)."
|
||||
"The dataset you use in this notebook is the [Bank Marketing](https://archive.ics.uci.edu/ml/datasets/bank+marketing) dataset.\n",
|
||||
"It consists of data related to direct marketing campaigns (phone calls) of a Portuguese banking institution. The objective of the binary classification task in this notebook is to predict if a client subscribes to a term deposit or not. \n",
|
||||
"\n",
|
||||
"For this notebook, a subset of randomly selected rows that makes 90% of the original dataset was saved to `train.csv` file and hosted on Cloud Storage. To download the file, click [here](https://storage.googleapis.com/cloud-samples-data-us-central1/vertex-ai/tabular-workflows/datasets/bank-marketing/train.csv)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -141,7 +154,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform google-cloud-pipeline-components"
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform \\\n",
|
||||
" google-cloud-pipeline-components"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -239,37 +253,6 @@
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "timestamp"
|
||||
},
|
||||
"source": [
|
||||
"#### UUID\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."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "84Vdv7R-QEH6"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"import string\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": {
|
||||
@@ -353,7 +336,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = \"gs://your-bucket-name-unique\" # @param {type:\"string\"}"
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -373,7 +356,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l $REGION -p $PROJECT_ID $BUCKET_URI"
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -382,7 +365,7 @@
|
||||
"id": "zebLBGXOky2A"
|
||||
},
|
||||
"source": [
|
||||
"## Notes about service account and permission\n",
|
||||
"### Notes about service account and permission\n",
|
||||
"\n",
|
||||
"**By default no configuration is required**, if you run into any permission related issue, please make sure the service accounts have the required roles listed in the [Service accounts for Tabular Workflow for TabNet, and Tabular Workflow for Wide & Deep, and Prophet documentation](https://cloud.google.com/vertex-ai/docs/tabular-data/tabular-workflows/service-accounts#fte-workflow)."
|
||||
]
|
||||
@@ -467,7 +450,7 @@
|
||||
"id": "fbbc3479a1da"
|
||||
},
|
||||
"source": [
|
||||
"## Import libraries and define constants"
|
||||
"### Import libraries"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -480,11 +463,10 @@
|
||||
"source": [
|
||||
"# Import required modules\n",
|
||||
"import os\n",
|
||||
"import uuid\n",
|
||||
"from typing import Any, Dict, List\n",
|
||||
"\n",
|
||||
"from google.cloud import aiplatform, storage\n",
|
||||
"from google_cloud_pipeline_components.experimental.automl.tabular import \\\n",
|
||||
"from google_cloud_pipeline_components.preview.automl.tabular import \\\n",
|
||||
" utils as automl_tabular_utils"
|
||||
]
|
||||
},
|
||||
@@ -494,7 +476,7 @@
|
||||
"id": "c0423f260423"
|
||||
},
|
||||
"source": [
|
||||
"## Initialize Vertex AI SDK for Python\n",
|
||||
"### Initialize Vertex AI SDK for Python\n",
|
||||
"\n",
|
||||
"Initialize the Vertex AI SDK for Python for your project."
|
||||
]
|
||||
@@ -516,17 +498,17 @@
|
||||
"id": "3LWH3PRF5o2v"
|
||||
},
|
||||
"source": [
|
||||
"### Define helper functions\n",
|
||||
"## Define helper functions\n",
|
||||
"Define the following helper functions:\n",
|
||||
"\n",
|
||||
"- `get_model_artifacts_path`: Get the model artifacts path from task details.\n",
|
||||
"- `get_model_uri`: Get the model uri from the task details..\n",
|
||||
"- `get_bucket_name_and_path`: Get the bucket name and path.\n",
|
||||
"- `download_from_gcs`: Download the content from the bucket.\n",
|
||||
"- `write_to_gcs`: Upload content into the bucket.\n",
|
||||
"- `get_task_detail`: Get the task details by using task name.\n",
|
||||
"- `get_model_name`: Get the model name from pipeline job ID.\n",
|
||||
"- `get_evaluation_metrics`: Get the evaluation metrics from pipeline task details.\n"
|
||||
"- `get_model_artifacts_path`: Gets the model artifacts path from task details.\n",
|
||||
"- `get_model_uri`: Gets the model uri from the task details.\n",
|
||||
"- `get_bucket_name_and_path`: Gets the bucket name and path.\n",
|
||||
"- `download_from_gcs`: Downloads the content from the bucket.\n",
|
||||
"- `write_to_gcs`: Uploads content into the bucket.\n",
|
||||
"- `get_task_detail`: Gets the task details by using task name.\n",
|
||||
"- `get_model_name`: Gets the model name from pipeline job ID.\n",
|
||||
"- `get_evaluation_metrics`: Gets the evaluation metrics from pipeline task details.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -538,6 +520,8 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Get the model artifacts path from task details.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_model_artifacts_path(task_details: List[Dict[str, Any]], task_name: str) -> str:\n",
|
||||
" task = get_task_detail(task_details, task_name)\n",
|
||||
" return task.outputs[\"unmanaged_container_model\"].artifacts[0].uri\n",
|
||||
@@ -604,27 +588,27 @@
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "gvNFMRmBegZq"
|
||||
},
|
||||
"source": [
|
||||
"## Define the training specification"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "7a7332a3f8e2"
|
||||
},
|
||||
"source": [
|
||||
"## Define training specifications\n",
|
||||
"\n",
|
||||
"Before creating the training job, you create the below steps in this section:\n",
|
||||
"\n",
|
||||
"1. Configure the source dataset.\n",
|
||||
"2. Configure the feature transformation process.\n",
|
||||
"3. Configure the feature selection process.\n",
|
||||
"4. Set up the parameters needed for running the training process.\n",
|
||||
"\n",
|
||||
"### Configure the dataset\n",
|
||||
"\n",
|
||||
"You define either of the following parameters:\n",
|
||||
"\n",
|
||||
"- `data_source_csv_filenames`: The CSV data source.\n",
|
||||
"- `data_source_bigquery_table_path`: The BigQuery data source.\n",
|
||||
"- `data_source_csv_filenames`: The CSV data source. You specify the Cloud Storage path to the `train.csv` file described in the dataset section.\n",
|
||||
"- `data_source_bigquery_table_path`: The BigQuery data source. As you use the Cloud Storage source, this is kept as none.\n",
|
||||
"\n",
|
||||
"***Notes***: Please note that the dataset's location has to be the same as the same as the service location (i.e., `REGION`) set for launching the training pipeline.\n"
|
||||
]
|
||||
@@ -653,18 +637,18 @@
|
||||
"\n",
|
||||
"Transformations can be specified using Feature Transform Engine (FTE) specific configurations. FTE supports both TensorFlow-based row-level and BigQuery-based dataset-level transformations.\n",
|
||||
"\n",
|
||||
"* TensorFlow-based row-level transformations:\n",
|
||||
"* **TensorFlow-based row-level transformations**:\n",
|
||||
" * Full automatic transformations: FTE automatically configures a set of built-in transformations for each input column based on its data statistics. This can be set via `tf_auto_transform_features` in the training pipeline.\n",
|
||||
" * Fully specified transformations: All transformations on input columns are explicitly specified with FTE's built-in transformations. Chaining of multiple transformations on a single column is also supported. These transformations can be saved to JSON configuration file and specified via `tf_transformations_path` argument of the training pipeline.\n",
|
||||
" * Custom transformations: Custom, bring-your-own transform function, where you can define and import your own transform function and use it with other FTE's built-in transformations. You can specify custom transformations as an array of JSON object and pass through the `tf_custom_transformation_definitions` argument of the training pipeline.\n",
|
||||
"\n",
|
||||
"* BigQuery-based dataset-level transformations:\n",
|
||||
"* **BigQuery-based dataset-level transformations**:\n",
|
||||
" * Fully specified transformations: All transformations on input columns are explicitly specified with FTE's built-in transformations. These transformations can be specified as an array of JSON objects via `dataset_level_transformations` argument of the training pipeline.\n",
|
||||
" * Custom transformations: Custom, bring-your-own transform function, where you can define and import your own transform function and use it with other FTE's built-in transformations. You can specify custom transformations as an array of JSON object and pass through the `dataset_level_custom_transformation_definitions` argument of the training pipeline.\n",
|
||||
"\n",
|
||||
"Below, you configure full automatic transformations by specifying a list of input features to pass to the `tf_auto_transform_features` argument of the training pipeline.\n",
|
||||
"\n",
|
||||
"For a complete list of supported feature transformation configurations and examples, please go [here](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.31/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.FeatureTransformEngineOp)."
|
||||
"Learn more about [feature transformation configurations](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.31/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.FeatureTransformEngineOp)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -707,9 +691,9 @@
|
||||
"\n",
|
||||
"To enable it, you need to set `run_feature_selection` to True.\n",
|
||||
"\n",
|
||||
"To configure the algorihtm to use, and number of features to be selected, you need to configure both `feature_selection_algorithm` and `max_selected_features` parameter.\n",
|
||||
"To configure the algorihtm to use, and number of features to be selected, you need to configure both `feature_selection_algorithm` and `max_selected_features` parameters.\n",
|
||||
"\n",
|
||||
"For a complete list of supported feature selection algorithms and configurations, please go [here](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.31/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.FeatureTransformEngineOp)."
|
||||
"Learn more about [feature selection algorithms and configurations](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.31/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.FeatureTransformEngineOp)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -735,7 +719,7 @@
|
||||
"source": [
|
||||
"### Setup training configuration\n",
|
||||
"\n",
|
||||
"You define the following:\n",
|
||||
"Now, you define the following parameters for training:\n",
|
||||
"\n",
|
||||
"- `target_column`: The target column name.\n",
|
||||
"- `prediction_type`: The type of prediction the model is to produce.\n",
|
||||
@@ -769,9 +753,6 @@
|
||||
"\n",
|
||||
"timestamp_split_key = None # timestamp column name when using timestamp split\n",
|
||||
"stratified_split_key = None # target column name when using stratified split\n",
|
||||
"training_fraction = 0.8\n",
|
||||
"validation_fraction = 0.1\n",
|
||||
"test_fraction = 0.1\n",
|
||||
"\n",
|
||||
"predefined_split_key = None\n",
|
||||
"if predefined_split_key:\n",
|
||||
@@ -788,26 +769,26 @@
|
||||
"id": "zyWGg2s09xOk"
|
||||
},
|
||||
"source": [
|
||||
"## VPC related config\n",
|
||||
"## Setup VPC configuration for Dataflow\n",
|
||||
"\n",
|
||||
"You define the following:\n",
|
||||
"In this section, you define the following parameters:\n",
|
||||
"\n",
|
||||
"- `dataflow_subnetwork`: Dataflow's fully qualified subnetwork name, when empty the default subnetwork will be used. Example:\n",
|
||||
"https://cloud.google.com/dataflow/docs/guides/specifying-networks#example_network_and_subnetwork_specifications\n",
|
||||
"- `dataflow_subnetwork`: Dataflow's fully qualified subnetwork name, when empty the default subnetwork is used. See an [example](\n",
|
||||
"https://cloud.google.com/dataflow/docs/guides/specifying-networks#example_network_and_subnetwork_specifications).\n",
|
||||
"- `dataflow_use_public_ips`: Specifies whether Dataflow workers use public IP\n",
|
||||
" addresses.\n",
|
||||
"\n",
|
||||
"If you need to use a custom Dataflow subnetwork, you can set it through the `dataflow_subnetwork` parameter. The requirements are:\n",
|
||||
"1. `dataflow_subnetwork` must be fully qualified subnetwork name.\n",
|
||||
"1. `dataflow_subnetwork` must be a fully qualified subnetwork name.\n",
|
||||
" [[reference](https://cloud.google.com/dataflow/docs/guides/specifying-networks#example_network_and_subnetwork_specifications)]\n",
|
||||
"1. The following service accounts must have [Compute Network User role](https://cloud.google.com/compute/docs/access/iam#compute.networkUser) assigned on the specified dataflow subnetwork [[reference](https://cloud.google.com/dataflow/docs/guides/specifying-networks#shared)]:\n",
|
||||
" 1. Compute Engine default service account: PROJECT_NUMBER-compute@developer.gserviceaccount.com\n",
|
||||
" 1. Dataflow service account: service-PROJECT_NUMBER@dataflow-service-producer-prod.iam.gserviceaccount.com\n",
|
||||
"\n",
|
||||
"If your project has VPC-SC enabled, please make sure:\n",
|
||||
"If your project has VPC-SC enabled, please make sure of the following:\n",
|
||||
"\n",
|
||||
"1. The dataflow subnetwork used in VPC-SC is configured properly for Dataflow.\n",
|
||||
" [[reference](https://cloud.google.com/dataflow/docs/guides/routes-firewall)]\n",
|
||||
" See [reference](https://cloud.google.com/dataflow/docs/guides/routes-firewall).\n",
|
||||
"1. `dataflow_use_public_ips` is set to False.\n"
|
||||
]
|
||||
},
|
||||
@@ -831,17 +812,19 @@
|
||||
"source": [
|
||||
"## Customize TabNet CustomJob configuration and create pipeline\n",
|
||||
"\n",
|
||||
"This is best choice if you know exactly which hyperparameter values to use for model training. It uses fewer training resources than a HyperparameterTuningJob.\n",
|
||||
"Creating a TabNet CustomJob is the best choice if you know exactly which hyperparameter values to use for model training. It uses fewer training resources than a HyperparameterTuningJob.\n",
|
||||
"\n",
|
||||
"In the example below, you configure the following:\n",
|
||||
"In the example below, you configure the following key parameters:\n",
|
||||
"\n",
|
||||
"- `root_dir`: The root GCS directory for the pipeline components.\n",
|
||||
"- `worker_pool_specs_override`: The dictionary for overriding training and evaluation worker pool specs. The dictionary should be of [this format]( https://github.com/googleapis/googleapis/blob/4e836c7c257e3e20b1de14d470993a2b1f4736a8/google/cloud/aiplatform/v1beta1/custom_job.proto#L172). TabNet supports both CPU and GPU training.\n",
|
||||
"- `worker_pool_specs_override`: The dictionary for overriding training and evaluation worker pool specs. The dictionary should follow a [particular format]( https://github.com/googleapis/googleapis/blob/4e836c7c257e3e20b1de14d470993a2b1f4736a8/google/cloud/aiplatform/v1beta1/custom_job.proto#L172). TabNet supports training using both CPUs and GPUs.\n",
|
||||
"- `learning_rate`: The learning rate used by the linear optimizer.\n",
|
||||
"- `max_steps`: Number of steps to run the trainer for.\n",
|
||||
"- `max_train_secs`: Amount of time in seconds to run the trainer for.\n",
|
||||
"\n",
|
||||
"A complete list of pipeline inputs and model hyperparameters is available [here](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.23/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.utils.get_tabnet_trainer_pipeline_and_parameters)."
|
||||
"Learn more about [pipeline inputs and model hyperparameters](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.23/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.utils.get_tabnet_trainer_pipeline_and_parameters).\n",
|
||||
"\n",
|
||||
"Learn more about the parameters needed for [creating a pipeline job](https://cloud.google.com/vertex-ai/docs/pipelines/run-pipeline#create_a_pipeline_run)."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -852,20 +835,22 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# set a unique display name for your pipeline\n",
|
||||
"pipeline_job_id = \"tabnet-unique\" # @param {type: \"string\"}\n",
|
||||
"# set the root dir\n",
|
||||
"pipeline_job_root_dir = os.path.join(BUCKET_URI, \"tabnet_custom_job\")\n",
|
||||
"\n",
|
||||
"# max_steps and/or max_train_secs must be set. If both are\n",
|
||||
"# specified, training stop after either condition is met.\n",
|
||||
"# By default, max_train_secs is set to -1.\n",
|
||||
"\n",
|
||||
"max_steps = 1000\n",
|
||||
"max_train_secs = -1\n",
|
||||
"\n",
|
||||
"learning_rate = 0.01\n",
|
||||
"\n",
|
||||
"# set the worker pool specs\n",
|
||||
"worker_pool_specs_override = [\n",
|
||||
" {\"machine_spec\": {\"machine_type\": \"c2-standard-16\"}} # Override for TF chief node\n",
|
||||
"]\n",
|
||||
"# set the learning rate\n",
|
||||
"learning_rate = 0.01\n",
|
||||
"# max_steps and/or max_train_secs must be set. If both are\n",
|
||||
"# specified, training stop after either condition is met.\n",
|
||||
"# By default, max_train_secs is set to -1.\n",
|
||||
"max_steps = 20\n",
|
||||
"\n",
|
||||
"max_train_secs = -1\n",
|
||||
"\n",
|
||||
"# To test GPU training, the worker_pool_specs_override can be specified like this.\n",
|
||||
"# worker_pool_specs_override = [\n",
|
||||
@@ -877,6 +862,7 @@
|
||||
"# }\n",
|
||||
"# ]\n",
|
||||
"\n",
|
||||
"# define the pipeline\n",
|
||||
"# If your system does not use Python, you can save the JSON file (`template_path`),\n",
|
||||
"# and use another programming language to submit the pipeline.\n",
|
||||
"(\n",
|
||||
@@ -906,10 +892,8 @@
|
||||
" run_evaluation=run_evaluation,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pipeline_job_id = f\"tabnet-{uuid.uuid4()}\"\n",
|
||||
"# More info on parameters PipelineJob accepts:\n",
|
||||
"# https://cloud.google.com/vertex-ai/docs/pipelines/run-pipeline#create_a_pipeline_run\n",
|
||||
"pipeline_job = aiplatform.PipelineJob(\n",
|
||||
"# create the pipeline job\n",
|
||||
"training_pipeline_job = aiplatform.PipelineJob(\n",
|
||||
" display_name=pipeline_job_id,\n",
|
||||
" template_path=template_path,\n",
|
||||
" job_id=pipeline_job_id,\n",
|
||||
@@ -918,7 +902,8 @@
|
||||
" enable_caching=False,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pipeline_job.run(service_account=SERVICE_ACCOUNT)"
|
||||
"# run the pipeline\n",
|
||||
"training_pipeline_job.run(service_account=SERVICE_ACCOUNT)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -928,7 +913,8 @@
|
||||
},
|
||||
"source": [
|
||||
"### Go to the Vertex Model UI\n",
|
||||
"From the link below, you can deploy the model and test online prediction or run batch prediction."
|
||||
"\n",
|
||||
"Through the link generated from the below cell, you can deploy the model and run online prediction or batch prediction."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -958,14 +944,20 @@
|
||||
"source": [
|
||||
"## Customize TabNet HyperparameterTuningJob configuration and create pipeline\n",
|
||||
"\n",
|
||||
"To get the best set of hyperparameters for your dataset, it is recommended to run a HyperparameterTuningJob.\n",
|
||||
"To get the best set of hyperparameters on your dataset, it is recommended to run a HyperparameterTuningJob.\n",
|
||||
"\n",
|
||||
"Hyperparameters that can be tuned are set in the optional `study_spec_parameters_override` parameter. you provide a helper function called `get_tabnet_study_spec_parameters_override` to get these hyperparameters. You provide `dataset_size_bucket` (one of 'small' (< 1M rows), 'medium' (1M - 100M rows), or 'large' (> 100M rows)), `training_budget_bucket` (one of 'small' (< \\\\$600), 'medium' (\\\\$600 - \\\\$2400), or 'large' (> \\\\$2400)), and `prediction_type` and Vertex AI returns a list of hyperparameters and ranges. `study_spec_parameters_override` can be empty or one or more of these hyperparameters can be specified. For hyperparameters not specified in `study_spec_parameters_override`, you set ranges in the pipeline. For a full list of hyperparameters available for tuning, see [here](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.23/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.utils.get_tabnet_trainer_pipeline_and_parameters).\n",
|
||||
"Hyperparameters that can be tuned are set with the optional `study_spec_parameters_override` parameter. You provide a helper function named `get_tabnet_study_spec_parameters_override` to get these hyperparameters. To this helper function, you provide:\n",
|
||||
"\n",
|
||||
"In addition to hyperparameters, HyperparameterTuningJob takes the following values in the example below:\n",
|
||||
"- `dataset_size_bucket`: one of 'small' (< 1M rows), 'medium' (1M - 100M rows), or 'large' (> 100M rows)).\n",
|
||||
"- `training_budget_bucket`: one of 'small' (< \\\\$600), 'medium' (\\\\$600 - \\\\$2400), or 'large' (> \\\\$2400)).\n",
|
||||
"- `prediction_type`: The type of prediction the model is to produce. “classification” or “regression”.\n",
|
||||
"\n",
|
||||
"Then, you get the list of hyperparameters and ranges. `study_spec_parameters_override` can be empty or one or more of the above hyperparameters can be specified. For hyperparameters not specified, you can set their ranges in the pipeline. Learn more about the [hyperparameters available for tuning](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.23/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.utils.get_tabnet_trainer_pipeline_and_parameters).\n",
|
||||
"\n",
|
||||
"In addition to hyperparameters, HyperparameterTuningJob takes the following values:\n",
|
||||
"\n",
|
||||
"- `root_dir`: The root GCS directory for the pipeline components.\n",
|
||||
"- `worker_pool_specs_override`: The dictionary for overriding training and evaluation worker pool specs. The dictionary should be of [this format]( https://github.com/googleapis/googleapis/blob/4e836c7c257e3e20b1de14d470993a2b1f4736a8/google/cloud/aiplatform/v1beta1/custom_job.proto#L172). TabNet supports both CPU and GPU training.\n",
|
||||
"- `worker_pool_specs_override`: The dictionary for overriding training and evaluation worker pool specs. The dictionary should follow a [particular format]( https://github.com/googleapis/googleapis/blob/4e836c7c257e3e20b1de14d470993a2b1f4736a8/google/cloud/aiplatform/v1beta1/custom_job.proto#L172). TabNet supports training using both CPUs and GPUs.\n",
|
||||
"- `study_spec_metric_id`: Metric to optimize, possible values: ['loss', 'average_loss', 'rmse', 'mae', 'mql', 'accuracy', 'auc', 'precision', 'recall'].\n",
|
||||
"- `study_spec_metric_goal`: Optimization goal of the metric, possible values: \"MAXIMIZE\", \"MINIMIZE\".\n",
|
||||
"- `max_trial_count`: The desired total number of trials.\n",
|
||||
@@ -974,9 +966,9 @@
|
||||
"- `study_spec_algorithm`: The search algorithm specified for the study. One of\n",
|
||||
"'ALGORITHM_UNSPECIFIED', 'GRID_SEARCH', or 'RANDOM_SEARCH'.\n",
|
||||
"\n",
|
||||
"For a full list of HyperparameterTuningJob parameters, see [here](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.23/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.utils.get_tabnet_hyperparameter_tuning_job_pipeline_and_parameters).\n",
|
||||
"Learno more about the [HyperparameterTuningJob parameters](https://google-cloud-pipeline-components.readthedocs.io/en/google-cloud-pipeline-components-1.0.23/google_cloud_pipeline_components.experimental.automl.tabular.html#google_cloud_pipeline_components.experimental.automl.tabular.utils.get_tabnet_hyperparameter_tuning_job_pipeline_and_parameters).\n",
|
||||
"\n",
|
||||
"Multiple trials can be configured. The pipeline returns the best trial based on the metric configured in `study_spec_metrics`. In the example below, you return the trial with the lowest loss value."
|
||||
"Multiple trials can be configured. The pipeline returns the best trial based on the metric specified in `study_spec_metrics`. In the example below, you return the trial with the lowest loss value."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -987,11 +979,18 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# set a unique display name for pipeline\n",
|
||||
"pipeline_job_id = \"tabnet-hpt-unique\" # @param {type: \"string\"}\n",
|
||||
"# set the root dir\n",
|
||||
"pipeline_job_root_dir = os.path.join(BUCKET_URI, \"tabnet_hyperparameter_tuning_job\")\n",
|
||||
"\n",
|
||||
"# set the worker pool specs\n",
|
||||
"worker_pool_specs_override = [\n",
|
||||
" {\"machine_spec\": {\"machine_type\": \"c2-standard-16\"}} # Override for TF chief node\n",
|
||||
"]\n",
|
||||
"# set the metric\n",
|
||||
"study_spec_metric_id = \"loss\"\n",
|
||||
"# set the objective for metric\n",
|
||||
"study_spec_metric_goal = \"MINIMIZE\"\n",
|
||||
"\n",
|
||||
"# To test GPU training, the worker_pool_specs_override can be specified like this.\n",
|
||||
"# worker_pool_specs_override = [\n",
|
||||
@@ -1004,9 +1003,8 @@
|
||||
"# }\n",
|
||||
"# ]\n",
|
||||
"\n",
|
||||
"study_spec_metric_id = \"loss\"\n",
|
||||
"study_spec_metric_goal = \"MINIMIZE\"\n",
|
||||
"\n",
|
||||
"# define the component to get the hyperparameters\n",
|
||||
"# max_steps and/or max_train_secs must be set. If both are\n",
|
||||
"# specified, training stop after either condition is met.\n",
|
||||
"# By default, max_train_secs is set to -1 and max_steps is set to\n",
|
||||
@@ -1019,6 +1017,7 @@
|
||||
" )\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# define the hyperparameter tuning pipeline\n",
|
||||
"# If your system does not use Python, you can save the JSON file (`template_path`),\n",
|
||||
"# and use another programming language to submit the pipeline.\n",
|
||||
"(\n",
|
||||
@@ -1051,10 +1050,8 @@
|
||||
" run_evaluation=True,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pipeline_job_id = f\"tabnet-hpt-{uuid.uuid4()}\"\n",
|
||||
"# More info on parameters PipelineJob accepts:\n",
|
||||
"# https://cloud.google.com/vertex-ai/docs/pipelines/run-pipeline#create_a_pipeline_run\n",
|
||||
"pipeline_job = aiplatform.PipelineJob(\n",
|
||||
"# create the pipeline job\n",
|
||||
"tuning_pipeline_job = aiplatform.PipelineJob(\n",
|
||||
" display_name=pipeline_job_id,\n",
|
||||
" template_path=template_path,\n",
|
||||
" job_id=pipeline_job_id,\n",
|
||||
@@ -1063,7 +1060,8 @@
|
||||
" enable_caching=False,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"pipeline_job.run(service_account=SERVICE_ACCOUNT)"
|
||||
"# run the pipeline job\n",
|
||||
"tuning_pipeline_job.run(service_account=SERVICE_ACCOUNT)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1073,7 +1071,8 @@
|
||||
},
|
||||
"source": [
|
||||
"### Go to the Vertex Model UI\n",
|
||||
"From the link below, you can deploy the model and test online prediction or run batch prediction."
|
||||
"\n",
|
||||
"Through the link generated from the below cell, you can deploy the model and run online prediction or batch prediction."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1111,9 +1110,11 @@
|
||||
"\n",
|
||||
"Otherwise, you can delete the individual resources you created in this tutorial:\n",
|
||||
"\n",
|
||||
"- Cloud Storage Bucket\n",
|
||||
"- Pipeline from CustomJob pipeline\n",
|
||||
"- Pipeline from HyperparameterTuningJob pipeline\n",
|
||||
"- Model from CustomJob pipeline\n",
|
||||
"- Model from HyperparameterTuningJob pipeline"
|
||||
"- Model from HyperparameterTuningJob pipeline\n",
|
||||
"- Cloud Storage Bucket (set `delete_bucket` to True to delete the bucket)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1124,6 +1125,12 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Delete the training pipeline job\n",
|
||||
"training_pipeline_job.delete()\n",
|
||||
"\n",
|
||||
"# Delete the tuning pipeline job\n",
|
||||
"tuning_pipeline_job.delete()\n",
|
||||
"\n",
|
||||
"# Delete model resources\n",
|
||||
"custom_job_model = aiplatform.Model(CUSTOM_JOB_MODEL)\n",
|
||||
"hpt_job_model = aiplatform.Model(HPT_JOB_MODEL)\n",
|
||||
|
||||
@@ -141,7 +141,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform google-cloud-pipeline-components"
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform \\\n",
|
||||
" \"google-cloud-pipeline-components<2.0\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+105
-249
@@ -147,39 +147,6 @@
|
||||
"Use the [Pricing Calculator](https://cloud.google.com/products/calculator/) to generate a cost estimate based on your projected usage."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "rAPBk_OCNN_h"
|
||||
},
|
||||
"source": [
|
||||
"### Set up your local development environment\n",
|
||||
"\n",
|
||||
"**If you are using Colab or Vertex AI Workbench**, your environment already meets all the requirements to run this notebook. You can skip this step.\n",
|
||||
"\n",
|
||||
"Otherwise, make sure your environment meets this notebook's requirements. You need the following:\n",
|
||||
"\n",
|
||||
"- The Cloud Storage SDK\n",
|
||||
"- Git\n",
|
||||
"- Python 3\n",
|
||||
"- virtualenv\n",
|
||||
"- Jupyter notebook running in a virtual environment with Python 3\n",
|
||||
"\n",
|
||||
"The Cloud Storage guide to [Setting up a Python development environment](https://cloud.google.com/python/setup) and the [Jupyter installation guide](https://jupyter.org/install) provide detailed instructions for meeting these requirements. The following steps provide a condensed set of instructions:\n",
|
||||
"\n",
|
||||
"1. [Install and initialize the SDK](https://cloud.google.com/sdk/docs/).\n",
|
||||
"\n",
|
||||
"2. [Install Python 3](https://cloud.google.com/python/setup#installing_python).\n",
|
||||
"\n",
|
||||
"3. [Install virtualenv](https://cloud.google.com/python/setup#installing_and_using_virtualenv) and create a virtual environment that uses Python 3. Activate the virtual environment.\n",
|
||||
"\n",
|
||||
"4. To install Jupyter, run `pip3 install jupyter` on the command-line in a terminal shell.\n",
|
||||
"\n",
|
||||
"5. To launch Jupyter, run `jupyter notebook` on the command-line in a terminal shell.\n",
|
||||
"\n",
|
||||
"6. Open this notebook in the Jupyter Notebook Dashboard."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
@@ -199,136 +166,102 @@
|
||||
},
|
||||
"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",
|
||||
"\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 google-cloud-aiplatform {USER_FLAG} -q\n"
|
||||
"! pip3 install --upgrade --quiet google-cloud-aiplatform "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "b24902cde81b"
|
||||
"id": "58707a750154"
|
||||
},
|
||||
"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."
|
||||
"### Colab only: Uncomment the following cell to restart the kernel."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "c61d171395d7"
|
||||
"id": "f200f10a1da3"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"# Automatically restart kernel after installs so that your environment can access the new packages\n",
|
||||
"# import IPython\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)"
|
||||
"# app = IPython.Application.instance()\n",
|
||||
"# app.kernel.do_shutdown(True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "1Dunp1YrhPYo"
|
||||
"id": "BF1j6f9HApxa"
|
||||
},
|
||||
"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",
|
||||
"2. [Make sure that billing is enabled for your project.](https://cloud.google.com/billing/docs/how-to/modify-project)\n",
|
||||
"2. [Make sure that billing is enabled for your project](https://cloud.google.com/billing/docs/how-to/modify-project).\n",
|
||||
"\n",
|
||||
"3. [Enable the Vertex AI, Cloud Storage, Cloud Build, and Artifact Registry APIs.](https://console.cloud.google.com/flows/enableapi?apiid=ml.googleapis.com,storage-component.googleapis.com,cloudbuild.googleapis.com,artifactregistry.googleapis.com)\n",
|
||||
"3. [Enable the Vertex AI API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com).\n",
|
||||
"\n",
|
||||
"4. If you are running this notebook locally, you will need to install the [Cloud SDK](https://cloud.google.com/sdk).\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"5. 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 `$`."
|
||||
"4. If you are running this notebook locally, install the [Cloud SDK](https://cloud.google.com/sdk)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "lMqIOZfqhXyD"
|
||||
"id": "WReHDGG5g0XY"
|
||||
},
|
||||
"source": [
|
||||
"### Set your project ID"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "siN0RvWyZYv9"
|
||||
},
|
||||
"source": [
|
||||
"**If you don't know your project ID**, you may be able to get your project ID using `gcloud`."
|
||||
"#### Set your project ID\n",
|
||||
"\n",
|
||||
"**If you don't know your project ID**, try the following:\n",
|
||||
"* Run `gcloud config list`.\n",
|
||||
"* Run `gcloud projects list`.\n",
|
||||
"* See the support page: [Locate the project ID](https://support.google.com/googleapi/answer/7014113)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "cde8e0876d62"
|
||||
"id": "oM1iC_MfAts1"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "lU-1yD4jZc-6"
|
||||
},
|
||||
"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)"
|
||||
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"# Set the project id\n",
|
||||
"! gcloud config set project {PROJECT_ID}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "XNVqyIvYaPO-"
|
||||
"id": "region"
|
||||
},
|
||||
"source": [
|
||||
"Otherwise, set your project ID here."
|
||||
"#### Set the region\n",
|
||||
"\n",
|
||||
"**Optional**: Update the 'REGION' variable to specify the region that you want to use. Learn more about [Vertex AI regions](https://cloud.google.com/vertex-ai/docs/general/locations)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "s0F0U7WZhfAZ"
|
||||
"id": "nsN5NJKSu-GU"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gcloud config set project $PROJECT_ID"
|
||||
"REGION = \"us-central1\" # @param {type: \"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -365,182 +298,103 @@
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "Ee3vBgvdhgTb"
|
||||
},
|
||||
"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": "flcwBahRhi8b"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"REGION = \"[your-region]\" # @param {type: \"string\"}\n",
|
||||
"\n",
|
||||
"if REGION == \"[your-region]\":\n",
|
||||
" REGION = \"us-central1\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "KuNRbXkIijp6"
|
||||
"id": "sBCra4QMA2wR"
|
||||
},
|
||||
"source": [
|
||||
"### Authenticate your Google Cloud account\n",
|
||||
"\n",
|
||||
"**If you are using Vertex AI Workbench Notebooks**, your environment is already\n",
|
||||
"authenticated."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "f40aa139740f"
|
||||
},
|
||||
"source": [
|
||||
"**If you are using Colab**, run the cell below and follow the instructions\n",
|
||||
"when prompted to authenticate your account via oAuth.\n",
|
||||
"Depending on your Jupyter environment, you may have to manually authenticate. Follow the relevant instructions below.\n",
|
||||
"\n",
|
||||
"**Otherwise**, follow these steps:\n",
|
||||
"**1. Vertex AI Workbench**\n",
|
||||
"* Do nothing as you are already authenticated.\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": "markdown",
|
||||
"metadata": {
|
||||
"id": "P9vQxUzfirCV"
|
||||
},
|
||||
"source": [
|
||||
"# The Google Cloud Notebook product has specific requirements\n",
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"IS_GOOGLE_CLOUD_NOTEBOOK = os.path.exists(\"/opt/deeplearning/metadata/env_version\")\n",
|
||||
"\n",
|
||||
"IS_COLAB = \"google.colab\" in sys.modules\n",
|
||||
"# If on Google Cloud Notebooks, then don't execute this code\n",
|
||||
"if not IS_GOOGLE_CLOUD_NOTEBOOK:\n",
|
||||
" if IS_COLAB:\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": {
|
||||
"id": "OG3dstAuVtgz"
|
||||
},
|
||||
"source": [
|
||||
"### Create Cloud Storage bucket"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "TKVkz5uzV07p"
|
||||
},
|
||||
"source": [
|
||||
"A Cloud Storage buckets will be used store your training code output (including TensorBoard logs). The bucket must be regional that is, not multi-region or dual-region, and the following resources must be in same region:\n",
|
||||
"\n",
|
||||
"* the Cloud Storage bucket\n",
|
||||
"* the Vertex AI training job\n",
|
||||
"* the Vertex AI TensorBoard instance"
|
||||
"**2. Local JupyterLab instance, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "_BzfwD8EV7Fw"
|
||||
"id": "254614fa0c46"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = \"gs://[your-bucket-name]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"if BUCKET_URI == \"\" or BUCKET_URI is None or BUCKET_URI == \"gs://[your-bucket-name]\":\n",
|
||||
" BUCKET_URI = \"gs://\" + PROJECT_ID + \"aip-\" + UUID"
|
||||
"# ! gcloud auth login"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "iVe3Y5UGWDoC"
|
||||
"id": "ef21552ccea8"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket. The created bucket will be deleted in the cleaning up section in the end. "
|
||||
"**3. Colab, uncomment and run:**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "M0krsPd9WJYl"
|
||||
"id": "603adbbf0532"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# from google.colab import auth\n",
|
||||
"# auth.authenticate_user()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "f6b2ccc891ed"
|
||||
},
|
||||
"source": [
|
||||
"**4. Service account or other**\n",
|
||||
"* See how to grant Cloud Storage permissions to your service account at https://cloud.google.com/storage/docs/gsutil/commands/iam#ch-examples."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "zgPO1eR3CYjk"
|
||||
},
|
||||
"source": [
|
||||
"### Create a Cloud Storage bucket\n",
|
||||
"\n",
|
||||
"Create a storage bucket to store intermediate artifacts such as datasets."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "MzGDU7TWdts_"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"BUCKET_URI = f\"gs://your-bucket-name-{PROJECT_ID}-unique\" # @param {type:\"string\"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "-EcIXiGsCePi"
|
||||
},
|
||||
"source": [
|
||||
"**Only if your bucket doesn't already exist**: Run the following cell to create your Cloud Storage bucket."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "NIq7R4HZCfIc"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil mb -l {REGION} -p {PROJECT_ID} {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "oXo9TNzQWQBS"
|
||||
},
|
||||
"source": [
|
||||
"Finally, validate access to your Cloud Storage bucket by examining its contents:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "BD-n41FzWY0G"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! gsutil ls -al {BUCKET_URI}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
@@ -558,6 +412,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"import google.cloud.aiplatform as aiplatform"
|
||||
]
|
||||
},
|
||||
@@ -603,7 +459,11 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!gcloud services enable artifactregistry.googleapis.com"
|
||||
"! gcloud services enable artifactregistry.googleapis.com\n",
|
||||
"\n",
|
||||
"if os.getenv(\"IS_TESTING\"):\n",
|
||||
" ! sudo apt-get update --yes && sudo apt-get --only-upgrade --yes install google-cloud-sdk-cloud-run-proxy google-cloud-sdk-harbourbridge google-cloud-sdk-cbt google-cloud-sdk-gke-gcloud-auth-plugin google-cloud-sdk-kpt google-cloud-sdk-local-extract google-cloud-sdk-minikube google-cloud-sdk-app-engine-java google-cloud-sdk-app-engine-go google-cloud-sdk-app-engine-python google-cloud-sdk-spanner-emulator google-cloud-sdk-bigtable-emulator google-cloud-sdk-nomos google-cloud-sdk-package-go-module google-cloud-sdk-firestore-emulator kubectl google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-python-extras google-cloud-sdk-cloud-build-local google-cloud-sdk-kubectl-oidc google-cloud-sdk-anthos-auth google-cloud-sdk-app-engine-grpc google-cloud-sdk-pubsub-emulator google-cloud-sdk-datalab google-cloud-sdk-skaffold google-cloud-sdk google-cloud-sdk-terraform-tools google-cloud-sdk-config-connector\n",
|
||||
" ! gcloud components update --quiet\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -626,14 +486,7 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"DOCKER_REPOSITORY = \"[your-docker-repository-name]\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"if (\n",
|
||||
" DOCKER_REPOSITORY == \"\"\n",
|
||||
" or DOCKER_REPOSITORY is None\n",
|
||||
" or DOCKER_REPOSITORY == \"[your-docker-repository-name]\"\n",
|
||||
"):\n",
|
||||
" DOCKER_REPOSITORY = \"tb-docker-repo-\" + PROJECT_ID + \"-\" + UUID\n",
|
||||
"DOCKER_REPOSITORY = \"my-docker-repo-unique\" # @param {type:\"string\"}\n",
|
||||
"\n",
|
||||
"print(\"Docker repository to create:\", DOCKER_REPOSITORY)"
|
||||
]
|
||||
@@ -812,6 +665,7 @@
|
||||
"\n",
|
||||
"# Specifies base image and tag\n",
|
||||
"FROM us-docker.pkg.dev/vertex-ai/training/tf-cpu.2-8:latest\n",
|
||||
"RUN pip install tensorflow-datasets\n",
|
||||
"WORKDIR /root\n",
|
||||
"\n",
|
||||
"\n",
|
||||
@@ -1016,6 +870,8 @@
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# Delete docker repository.\n",
|
||||
"! gcloud artifacts repositories delete $DOCKER_REPOSITORY --project {PROJECT_ID} --location {REGION} --quiet\n",
|
||||
"\n",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user