Commit 0016b0a7 authored by sunxx1's avatar sunxx1
Browse files

Merge branch 'dtk22.04' into 'main'

Dtk22.04

See merge request dcutoolkit/deeplearing/dlexamples_new!49
parents 17bc28d5 7a382d5d
import math
import random
import time
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import tensorflow as tf
import keras_cv
from keras_cv.metrics import coco
def produce_random_data(include_confidence=False, num_images=128, classes=20):
"""Generates a fake list of bounding boxes for use in this test.
Returns:
a tensor list of size [128, 25, 5/6]. This represents 128 images, 25 bboxes
and 5/6 dimensions to represent each bbox depending on if confidence is
set.
"""
images = []
for _ in range(num_images):
num_boxes = math.floor(25 * random.uniform(0, 1))
classes_in_image = np.floor(np.random.rand(num_boxes, 1) * classes)
bboxes = np.random.rand(num_boxes, 4)
boxes = np.concatenate([bboxes, classes_in_image], axis=-1)
if include_confidence:
confidence = np.random.rand(num_boxes, 1)
boxes = np.concatenate([boxes, confidence], axis=-1)
images.append(
keras_cv.utils.bounding_box.xywh_to_corners(
tf.constant(boxes, dtype=tf.float32)
)
)
images = [
keras_cv.bounding_box.pad_batch_to_shape(x, [25, images[0].shape[1]])
for x in images
]
return tf.stack(images, axis=0)
y_true = produce_random_data()
y_pred = produce_random_data(include_confidence=True)
class_ids = list(range(20))
n_images = [128, 256, 512, 512 + 256, 1024]
update_state_runtimes = []
result_runtimes = []
end_to_end_runtimes = []
for images in n_images:
y_true = produce_random_data(num_images=images)
y_pred = produce_random_data(num_images=images, include_confidence=True)
metric = coco.COCORecall(class_ids)
# warm up
metric.update_state(y_true, y_pred)
metric.result()
start = time.time()
metric.update_state(y_true, y_pred)
update_state_done = time.time()
r = metric.result()
end = time.time()
update_state_runtimes.append(update_state_done - start)
result_runtimes.append(end - update_state_done)
end_to_end_runtimes.append(end - start)
print("end_to_end_runtimes", end_to_end_runtimes)
data = pd.DataFrame(
{
"n_images": n_images,
"update_state_runtimes": update_state_runtimes,
"result_runtimes": result_runtimes,
"end_to_end_runtimes": end_to_end_runtimes,
}
)
sns.lineplot(data=data, x="n_images", y="update_state_runtimes")
plt.xlabel("Number of Images")
plt.ylabel("update_state() runtime (seconds)")
plt.title("Runtime of update_state()")
plt.show()
sns.lineplot(data=data, x="n_images", y="result_runtimes")
plt.xlabel("Number of Images")
plt.ylabel("result() runtime (seconds)")
plt.title("Runtime of result()")
plt.show()
sns.lineplot(data=data, x="n_images", y="end_to_end_runtimes")
plt.xlabel("Number of Images")
plt.ylabel("End to end runtime (seconds)")
plt.title("Runtimes of update_state() followed by result()")
plt.show()
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
# Setup/utils
"""
import time
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
from tensorflow.keras import backend
from keras_cv.utils import bounding_box
from keras_cv.utils import fill_utils
def single_rectangle_mask(corners, mask_shape):
"""Computes masks of rectangles
Args:
corners: tensor of rectangle coordinates with shape (batch_size, 4) in
corners format (x0, y0, x1, y1).
mask_shape: a shape tuple as (width, height) indicating the output
width and height of masks.
Returns:
boolean masks with shape (batch_size, width, height) where True values
indicate positions within rectangle coordinates.
"""
# add broadcasting axes
corners = corners[..., tf.newaxis, tf.newaxis]
# split coordinates
x0 = corners[0]
y0 = corners[1]
x1 = corners[2]
y1 = corners[3]
# repeat height and width
width, height = mask_shape
x0_rep = tf.repeat(x0, height, axis=0)
y0_rep = tf.repeat(y0, width, axis=1)
x1_rep = tf.repeat(x1, height, axis=0)
y1_rep = tf.repeat(y1, width, axis=1)
# range grid
range_row = tf.range(0, height, dtype=corners.dtype)
range_col = tf.range(0, width, dtype=corners.dtype)
range_row = range_row[:, tf.newaxis]
range_col = range_col[tf.newaxis, :]
# boolean masks
mask_x0 = tf.less_equal(x0_rep, range_col)
mask_y0 = tf.less_equal(y0_rep, range_row)
mask_x1 = tf.less(range_col, x1_rep)
mask_y1 = tf.less(range_row, y1_rep)
masks = mask_x0 & mask_y0 & mask_x1 & mask_y1
return masks
def fill_single_rectangle(image, centers_x, centers_y, widths, heights, fill_values):
"""Fill rectangles with fill value into images.
Args:
images: Tensor of images to fill rectangles into.
centers_x: Tensor of positions of the rectangle centers on the x-axis.
centers_y: Tensor of positions of the rectangle centers on the y-axis.
widths: Tensor of widths of the rectangles
heights: Tensor of heights of the rectangles
fill_values: Tensor with same shape as images to get rectangle fill from.
Returns:
images with filled rectangles.
"""
images_shape = tf.shape(image)
images_height = images_shape[0]
images_width = images_shape[1]
xywh = tf.stack([centers_x, centers_y, widths, heights], axis=0)
xywh = tf.cast(xywh, tf.float32)
corners = bounding_box.convert_to_corners(xywh, format="coco")
mask_shape = (images_width, images_height)
is_rectangle = single_rectangle_mask(corners, mask_shape)
is_rectangle = tf.expand_dims(is_rectangle, -1)
images = tf.where(is_rectangle, fill_values, image)
return images
"""
# Layer Implementations
## Fully Vectorized
"""
class VectorizedRandomCutout(layers.Layer):
def __init__(
self,
height_factor,
width_factor,
fill_mode="constant",
fill_value=0.0,
seed=None,
**kwargs,
):
super().__init__(**kwargs)
self.height_lower, self.height_upper = self._parse_bounds(height_factor)
self.width_lower, self.width_upper = self._parse_bounds(width_factor)
if fill_mode not in ["gaussian_noise", "constant"]:
raise ValueError(
'`fill_mode` should be "gaussian_noise" '
f'or "constant". Got `fill_mode`={fill_mode}'
)
if not isinstance(self.height_lower, type(self.height_upper)):
raise ValueError(
"`height_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.height_lower), type(self.height_upper)
)
)
if not isinstance(self.width_lower, type(self.width_upper)):
raise ValueError(
"`width_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.width_lower), type(self.width_upper)
)
)
if self.height_upper < self.height_lower:
raise ValueError(
"`height_factor` cannot have upper bound less than "
"lower bound, got {}".format(height_factor)
)
self._height_is_float = isinstance(self.height_lower, float)
if self._height_is_float:
if not self.height_lower >= 0.0 or not self.height_upper <= 1.0:
raise ValueError(
"`height_factor` must have values between [0, 1] "
"when is float, got {}".format(height_factor)
)
if self.width_upper < self.width_lower:
raise ValueError(
"`width_factor` cannot have upper bound less than "
"lower bound, got {}".format(width_factor)
)
self._width_is_float = isinstance(self.width_lower, float)
if self._width_is_float:
if not self.width_lower >= 0.0 or not self.width_upper <= 1.0:
raise ValueError(
"`width_factor` must have values between [0, 1] "
"when is float, got {}".format(width_factor)
)
self.fill_mode = fill_mode
self.fill_value = fill_value
self.seed = seed
def _parse_bounds(self, factor):
if isinstance(factor, (tuple, list)):
return factor[0], factor[1]
else:
return type(factor)(0), factor
@tf.function(jit_compile=True)
def call(self, inputs, training=True):
if training is None:
training = backend.learning_phase()
augment = lambda: self._random_cutout(inputs)
no_augment = lambda: inputs
return tf.cond(tf.cast(training, tf.bool), augment, no_augment)
def _random_cutout(self, inputs):
"""Apply random cutout."""
center_x, center_y = self._compute_rectangle_position(inputs)
rectangle_height, rectangle_width = self._compute_rectangle_size(inputs)
rectangle_fill = self._compute_rectangle_fill(inputs)
inputs = fill_utils.fill_rectangle(
inputs,
center_x,
center_y,
rectangle_width,
rectangle_height,
rectangle_fill,
)
return inputs
def _compute_rectangle_position(self, inputs):
input_shape = tf.shape(inputs)
batch_size, image_height, image_width = (
input_shape[0],
input_shape[1],
input_shape[2],
)
center_x = tf.random.uniform(
shape=[batch_size],
minval=0,
maxval=image_width,
dtype=tf.int32,
seed=self.seed,
)
center_y = tf.random.uniform(
shape=[batch_size],
minval=0,
maxval=image_height,
dtype=tf.int32,
seed=self.seed,
)
return center_x, center_y
def _compute_rectangle_size(self, inputs):
input_shape = tf.shape(inputs)
batch_size, image_height, image_width = (
input_shape[0],
input_shape[1],
input_shape[2],
)
height = tf.random.uniform(
[batch_size],
minval=self.height_lower,
maxval=self.height_upper,
dtype=tf.float32,
)
width = tf.random.uniform(
[batch_size],
minval=self.width_lower,
maxval=self.width_upper,
dtype=tf.float32,
)
if self._height_is_float:
height = height * tf.cast(image_height, tf.float32)
if self._width_is_float:
width = width * tf.cast(image_width, tf.float32)
height = tf.cast(tf.math.ceil(height), tf.int32)
width = tf.cast(tf.math.ceil(width), tf.int32)
height = tf.minimum(height, image_height)
width = tf.minimum(width, image_width)
return height, width
def _compute_rectangle_fill(self, inputs):
input_shape = tf.shape(inputs)
if self.fill_mode == "constant":
fill_value = tf.fill(input_shape, self.fill_value)
else:
# gaussian noise
fill_value = tf.random.normal(input_shape)
return fill_value
def get_config(self):
config = {
"height_factor": self.height_factor,
"width_factor": self.width_factor,
"fill_mode": self.fill_mode,
"fill_value": self.fill_value,
"seed": self.seed,
}
base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
"""
## tf.map_fn
"""
class MapFnRandomCutout(layers.Layer):
def __init__(
self,
height_factor,
width_factor,
fill_mode="constant",
fill_value=0.0,
seed=None,
**kwargs,
):
super().__init__(**kwargs)
self.height_lower, self.height_upper = self._parse_bounds(height_factor)
self.width_lower, self.width_upper = self._parse_bounds(width_factor)
if fill_mode not in ["gaussian_noise", "constant"]:
raise ValueError(
'`fill_mode` should be "gaussian_noise" '
f'or "constant". Got `fill_mode`={fill_mode}'
)
if not isinstance(self.height_lower, type(self.height_upper)):
raise ValueError(
"`height_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.height_lower), type(self.height_upper)
)
)
if not isinstance(self.width_lower, type(self.width_upper)):
raise ValueError(
"`width_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.width_lower), type(self.width_upper)
)
)
if self.height_upper < self.height_lower:
raise ValueError(
"`height_factor` cannot have upper bound less than "
"lower bound, got {}".format(height_factor)
)
self._height_is_float = isinstance(self.height_lower, float)
if self._height_is_float:
if not self.height_lower >= 0.0 or not self.height_upper <= 1.0:
raise ValueError(
"`height_factor` must have values between [0, 1] "
"when is float, got {}".format(height_factor)
)
if self.width_upper < self.width_lower:
raise ValueError(
"`width_factor` cannot have upper bound less than "
"lower bound, got {}".format(width_factor)
)
self._width_is_float = isinstance(self.width_lower, float)
if self._width_is_float:
if not self.width_lower >= 0.0 or not self.width_upper <= 1.0:
raise ValueError(
"`width_factor` must have values between [0, 1] "
"when is float, got {}".format(width_factor)
)
self.fill_mode = fill_mode
self.fill_value = fill_value
self.seed = seed
def _parse_bounds(self, factor):
if isinstance(factor, (tuple, list)):
return factor[0], factor[1]
else:
return type(factor)(0), factor
@tf.function(jit_compile=True)
def call(self, inputs, training=True):
augment = lambda: tf.map_fn(self._random_cutout, inputs)
no_augment = lambda: inputs
return tf.cond(tf.cast(training, tf.bool), augment, no_augment)
def _random_cutout(self, input):
center_x, center_y = self._compute_rectangle_position(input)
rectangle_height, rectangle_width = self._compute_rectangle_size(input)
rectangle_fill = self._compute_rectangle_fill(input)
input = fill_single_rectangle(
input,
center_x,
center_y,
rectangle_width,
rectangle_height,
rectangle_fill,
)
return input
def _compute_rectangle_position(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
center_x = tf.random.uniform(
shape=[],
minval=0,
maxval=image_width,
dtype=tf.int32,
seed=self.seed,
)
center_y = tf.random.uniform(
shape=[],
minval=0,
maxval=image_height,
dtype=tf.int32,
seed=self.seed,
)
return center_x, center_y
def _compute_rectangle_size(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
height = tf.random.uniform(
[],
minval=self.height_lower,
maxval=self.height_upper,
dtype=tf.float32,
)
width = tf.random.uniform(
[],
minval=self.width_lower,
maxval=self.width_upper,
dtype=tf.float32,
)
if self._height_is_float:
height = height * tf.cast(image_height, tf.float32)
if self._width_is_float:
width = width * tf.cast(image_width, tf.float32)
height = tf.cast(tf.math.ceil(height), tf.int32)
width = tf.cast(tf.math.ceil(width), tf.int32)
height = tf.minimum(height, image_height)
width = tf.minimum(width, image_width)
return height, width
def _compute_rectangle_fill(self, inputs):
input_shape = tf.shape(inputs)
if self.fill_mode == "constant":
fill_value = tf.fill(input_shape, self.fill_value)
else:
# gaussian noise
fill_value = tf.random.normal(input_shape)
return fill_value
def get_config(self):
config = {
"height_factor": self.height_factor,
"width_factor": self.width_factor,
"fill_mode": self.fill_mode,
"fill_value": self.fill_value,
"seed": self.seed,
}
base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
"""
## tf.vectorized_map
"""
class VMapRandomCutout(layers.Layer):
def __init__(
self,
height_factor,
width_factor,
fill_mode="constant",
fill_value=0.0,
seed=None,
**kwargs,
):
super().__init__(**kwargs)
self.height_lower, self.height_upper = self._parse_bounds(height_factor)
self.width_lower, self.width_upper = self._parse_bounds(width_factor)
if fill_mode not in ["gaussian_noise", "constant"]:
raise ValueError(
'`fill_mode` should be "gaussian_noise" '
f'or "constant". Got `fill_mode`={fill_mode}'
)
if not isinstance(self.height_lower, type(self.height_upper)):
raise ValueError(
"`height_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.height_lower), type(self.height_upper)
)
)
if not isinstance(self.width_lower, type(self.width_upper)):
raise ValueError(
"`width_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.width_lower), type(self.width_upper)
)
)
if self.height_upper < self.height_lower:
raise ValueError(
"`height_factor` cannot have upper bound less than "
"lower bound, got {}".format(height_factor)
)
self._height_is_float = isinstance(self.height_lower, float)
if self._height_is_float:
if not self.height_lower >= 0.0 or not self.height_upper <= 1.0:
raise ValueError(
"`height_factor` must have values between [0, 1] "
"when is float, got {}".format(height_factor)
)
if self.width_upper < self.width_lower:
raise ValueError(
"`width_factor` cannot have upper bound less than "
"lower bound, got {}".format(width_factor)
)
self._width_is_float = isinstance(self.width_lower, float)
if self._width_is_float:
if not self.width_lower >= 0.0 or not self.width_upper <= 1.0:
raise ValueError(
"`width_factor` must have values between [0, 1] "
"when is float, got {}".format(width_factor)
)
self.fill_mode = fill_mode
self.fill_value = fill_value
self.seed = seed
def _parse_bounds(self, factor):
if isinstance(factor, (tuple, list)):
return factor[0], factor[1]
else:
return type(factor)(0), factor
@tf.function(jit_compile=True)
def call(self, inputs, training=True):
augment = lambda: tf.vectorized_map(self._random_cutout, inputs)
no_augment = lambda: inputs
return tf.cond(tf.cast(training, tf.bool), augment, no_augment)
def _random_cutout(self, input):
center_x, center_y = self._compute_rectangle_position(input)
rectangle_height, rectangle_width = self._compute_rectangle_size(input)
rectangle_fill = self._compute_rectangle_fill(input)
input = fill_single_rectangle(
input,
center_x,
center_y,
rectangle_width,
rectangle_height,
rectangle_fill,
)
return input
def _compute_rectangle_position(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
center_x = tf.random.uniform(
shape=[],
minval=0,
maxval=image_width,
dtype=tf.int32,
seed=self.seed,
)
center_y = tf.random.uniform(
shape=[],
minval=0,
maxval=image_height,
dtype=tf.int32,
seed=self.seed,
)
return center_x, center_y
def _compute_rectangle_size(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
height = tf.random.uniform(
[],
minval=self.height_lower,
maxval=self.height_upper,
dtype=tf.float32,
)
width = tf.random.uniform(
[],
minval=self.width_lower,
maxval=self.width_upper,
dtype=tf.float32,
)
if self._height_is_float:
height = height * tf.cast(image_height, tf.float32)
if self._width_is_float:
width = width * tf.cast(image_width, tf.float32)
height = tf.cast(tf.math.ceil(height), tf.int32)
width = tf.cast(tf.math.ceil(width), tf.int32)
height = tf.minimum(height, image_height)
width = tf.minimum(width, image_width)
return height, width
def _compute_rectangle_fill(self, inputs):
input_shape = tf.shape(inputs)
if self.fill_mode == "constant":
fill_value = tf.fill(input_shape, self.fill_value)
else:
# gaussian noise
fill_value = tf.random.normal(input_shape)
return fill_value
def get_config(self):
config = {
"height_factor": self.height_factor,
"width_factor": self.width_factor,
"fill_mode": self.fill_mode,
"fill_value": self.fill_value,
"seed": self.seed,
}
base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
"""
JIT COMPILED
# Layer Implementations
## Fully Vectorized
"""
class JITVectorizedRandomCutout(layers.Layer):
def __init__(
self,
height_factor,
width_factor,
fill_mode="constant",
fill_value=0.0,
seed=None,
**kwargs,
):
super().__init__(**kwargs)
self.height_lower, self.height_upper = self._parse_bounds(height_factor)
self.width_lower, self.width_upper = self._parse_bounds(width_factor)
if fill_mode not in ["gaussian_noise", "constant"]:
raise ValueError(
'`fill_mode` should be "gaussian_noise" '
f'or "constant". Got `fill_mode`={fill_mode}'
)
if not isinstance(self.height_lower, type(self.height_upper)):
raise ValueError(
"`height_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.height_lower), type(self.height_upper)
)
)
if not isinstance(self.width_lower, type(self.width_upper)):
raise ValueError(
"`width_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.width_lower), type(self.width_upper)
)
)
if self.height_upper < self.height_lower:
raise ValueError(
"`height_factor` cannot have upper bound less than "
"lower bound, got {}".format(height_factor)
)
self._height_is_float = isinstance(self.height_lower, float)
if self._height_is_float:
if not self.height_lower >= 0.0 or not self.height_upper <= 1.0:
raise ValueError(
"`height_factor` must have values between [0, 1] "
"when is float, got {}".format(height_factor)
)
if self.width_upper < self.width_lower:
raise ValueError(
"`width_factor` cannot have upper bound less than "
"lower bound, got {}".format(width_factor)
)
self._width_is_float = isinstance(self.width_lower, float)
if self._width_is_float:
if not self.width_lower >= 0.0 or not self.width_upper <= 1.0:
raise ValueError(
"`width_factor` must have values between [0, 1] "
"when is float, got {}".format(width_factor)
)
self.fill_mode = fill_mode
self.fill_value = fill_value
self.seed = seed
def _parse_bounds(self, factor):
if isinstance(factor, (tuple, list)):
return factor[0], factor[1]
else:
return type(factor)(0), factor
@tf.function(jit_compile=True)
def call(self, inputs, training=True):
if training is None:
training = backend.learning_phase()
augment = lambda: self._random_cutout(inputs)
no_augment = lambda: inputs
return tf.cond(tf.cast(training, tf.bool), augment, no_augment)
def _random_cutout(self, inputs):
"""Apply random cutout."""
center_x, center_y = self._compute_rectangle_position(inputs)
rectangle_height, rectangle_width = self._compute_rectangle_size(inputs)
rectangle_fill = self._compute_rectangle_fill(inputs)
inputs = fill_utils.fill_rectangle(
inputs,
center_x,
center_y,
rectangle_width,
rectangle_height,
rectangle_fill,
)
return inputs
def _compute_rectangle_position(self, inputs):
input_shape = tf.shape(inputs)
batch_size, image_height, image_width = (
input_shape[0],
input_shape[1],
input_shape[2],
)
center_x = tf.random.uniform(
shape=[batch_size],
minval=0,
maxval=image_width,
dtype=tf.int32,
seed=self.seed,
)
center_y = tf.random.uniform(
shape=[batch_size],
minval=0,
maxval=image_height,
dtype=tf.int32,
seed=self.seed,
)
return center_x, center_y
def _compute_rectangle_size(self, inputs):
input_shape = tf.shape(inputs)
batch_size, image_height, image_width = (
input_shape[0],
input_shape[1],
input_shape[2],
)
height = tf.random.uniform(
[batch_size],
minval=self.height_lower,
maxval=self.height_upper,
dtype=tf.float32,
)
width = tf.random.uniform(
[batch_size],
minval=self.width_lower,
maxval=self.width_upper,
dtype=tf.float32,
)
if self._height_is_float:
height = height * tf.cast(image_height, tf.float32)
if self._width_is_float:
width = width * tf.cast(image_width, tf.float32)
height = tf.cast(tf.math.ceil(height), tf.int32)
width = tf.cast(tf.math.ceil(width), tf.int32)
height = tf.minimum(height, image_height)
width = tf.minimum(width, image_width)
return height, width
def _compute_rectangle_fill(self, inputs):
input_shape = tf.shape(inputs)
if self.fill_mode == "constant":
fill_value = tf.fill(input_shape, self.fill_value)
else:
# gaussian noise
fill_value = tf.random.normal(input_shape)
return fill_value
def get_config(self):
config = {
"height_factor": self.height_factor,
"width_factor": self.width_factor,
"fill_mode": self.fill_mode,
"fill_value": self.fill_value,
"seed": self.seed,
}
base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
"""
## tf.map_fn
"""
class JITMapFnRandomCutout(layers.Layer):
def __init__(
self,
height_factor,
width_factor,
fill_mode="constant",
fill_value=0.0,
seed=None,
**kwargs,
):
super().__init__(**kwargs)
self.height_lower, self.height_upper = self._parse_bounds(height_factor)
self.width_lower, self.width_upper = self._parse_bounds(width_factor)
if fill_mode not in ["gaussian_noise", "constant"]:
raise ValueError(
'`fill_mode` should be "gaussian_noise" '
f'or "constant". Got `fill_mode`={fill_mode}'
)
if not isinstance(self.height_lower, type(self.height_upper)):
raise ValueError(
"`height_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.height_lower), type(self.height_upper)
)
)
if not isinstance(self.width_lower, type(self.width_upper)):
raise ValueError(
"`width_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.width_lower), type(self.width_upper)
)
)
if self.height_upper < self.height_lower:
raise ValueError(
"`height_factor` cannot have upper bound less than "
"lower bound, got {}".format(height_factor)
)
self._height_is_float = isinstance(self.height_lower, float)
if self._height_is_float:
if not self.height_lower >= 0.0 or not self.height_upper <= 1.0:
raise ValueError(
"`height_factor` must have values between [0, 1] "
"when is float, got {}".format(height_factor)
)
if self.width_upper < self.width_lower:
raise ValueError(
"`width_factor` cannot have upper bound less than "
"lower bound, got {}".format(width_factor)
)
self._width_is_float = isinstance(self.width_lower, float)
if self._width_is_float:
if not self.width_lower >= 0.0 or not self.width_upper <= 1.0:
raise ValueError(
"`width_factor` must have values between [0, 1] "
"when is float, got {}".format(width_factor)
)
self.fill_mode = fill_mode
self.fill_value = fill_value
self.seed = seed
def _parse_bounds(self, factor):
if isinstance(factor, (tuple, list)):
return factor[0], factor[1]
else:
return type(factor)(0), factor
@tf.function(jit_compile=True)
def call(self, inputs, training=True):
augment = lambda: tf.map_fn(self._random_cutout, inputs)
no_augment = lambda: inputs
return tf.cond(tf.cast(training, tf.bool), augment, no_augment)
def _random_cutout(self, input):
center_x, center_y = self._compute_rectangle_position(input)
rectangle_height, rectangle_width = self._compute_rectangle_size(input)
rectangle_fill = self._compute_rectangle_fill(input)
input = fill_single_rectangle(
input,
center_x,
center_y,
rectangle_width,
rectangle_height,
rectangle_fill,
)
return input
def _compute_rectangle_position(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
center_x = tf.random.uniform(
shape=[],
minval=0,
maxval=image_width,
dtype=tf.int32,
seed=self.seed,
)
center_y = tf.random.uniform(
shape=[],
minval=0,
maxval=image_height,
dtype=tf.int32,
seed=self.seed,
)
return center_x, center_y
def _compute_rectangle_size(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
height = tf.random.uniform(
[],
minval=self.height_lower,
maxval=self.height_upper,
dtype=tf.float32,
)
width = tf.random.uniform(
[],
minval=self.width_lower,
maxval=self.width_upper,
dtype=tf.float32,
)
if self._height_is_float:
height = height * tf.cast(image_height, tf.float32)
if self._width_is_float:
width = width * tf.cast(image_width, tf.float32)
height = tf.cast(tf.math.ceil(height), tf.int32)
width = tf.cast(tf.math.ceil(width), tf.int32)
height = tf.minimum(height, image_height)
width = tf.minimum(width, image_width)
return height, width
def _compute_rectangle_fill(self, inputs):
input_shape = tf.shape(inputs)
if self.fill_mode == "constant":
fill_value = tf.fill(input_shape, self.fill_value)
else:
# gaussian noise
fill_value = tf.random.normal(input_shape)
return fill_value
def get_config(self):
config = {
"height_factor": self.height_factor,
"width_factor": self.width_factor,
"fill_mode": self.fill_mode,
"fill_value": self.fill_value,
"seed": self.seed,
}
base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
"""
## tf.vectorized_map
"""
class JITVMapRandomCutout(layers.Layer):
def __init__(
self,
height_factor,
width_factor,
fill_mode="constant",
fill_value=0.0,
seed=None,
**kwargs,
):
super().__init__(**kwargs)
self.height_lower, self.height_upper = self._parse_bounds(height_factor)
self.width_lower, self.width_upper = self._parse_bounds(width_factor)
if fill_mode not in ["gaussian_noise", "constant"]:
raise ValueError(
'`fill_mode` should be "gaussian_noise" '
f'or "constant". Got `fill_mode`={fill_mode}'
)
if not isinstance(self.height_lower, type(self.height_upper)):
raise ValueError(
"`height_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.height_lower), type(self.height_upper)
)
)
if not isinstance(self.width_lower, type(self.width_upper)):
raise ValueError(
"`width_factor` must have lower bound and upper bound "
"with same type, got {} and {}".format(
type(self.width_lower), type(self.width_upper)
)
)
if self.height_upper < self.height_lower:
raise ValueError(
"`height_factor` cannot have upper bound less than "
"lower bound, got {}".format(height_factor)
)
self._height_is_float = isinstance(self.height_lower, float)
if self._height_is_float:
if not self.height_lower >= 0.0 or not self.height_upper <= 1.0:
raise ValueError(
"`height_factor` must have values between [0, 1] "
"when is float, got {}".format(height_factor)
)
if self.width_upper < self.width_lower:
raise ValueError(
"`width_factor` cannot have upper bound less than "
"lower bound, got {}".format(width_factor)
)
self._width_is_float = isinstance(self.width_lower, float)
if self._width_is_float:
if not self.width_lower >= 0.0 or not self.width_upper <= 1.0:
raise ValueError(
"`width_factor` must have values between [0, 1] "
"when is float, got {}".format(width_factor)
)
self.fill_mode = fill_mode
self.fill_value = fill_value
self.seed = seed
def _parse_bounds(self, factor):
if isinstance(factor, (tuple, list)):
return factor[0], factor[1]
else:
return type(factor)(0), factor
@tf.function(jit_compile=True)
def call(self, inputs, training=True):
augment = lambda: tf.vectorized_map(self._random_cutout, inputs)
no_augment = lambda: inputs
return tf.cond(tf.cast(training, tf.bool), augment, no_augment)
def _random_cutout(self, input):
center_x, center_y = self._compute_rectangle_position(input)
rectangle_height, rectangle_width = self._compute_rectangle_size(input)
rectangle_fill = self._compute_rectangle_fill(input)
input = fill_single_rectangle(
input,
center_x,
center_y,
rectangle_width,
rectangle_height,
rectangle_fill,
)
return input
def _compute_rectangle_position(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
center_x = tf.random.uniform(
shape=[],
minval=0,
maxval=image_width,
dtype=tf.int32,
seed=self.seed,
)
center_y = tf.random.uniform(
shape=[],
minval=0,
maxval=image_height,
dtype=tf.int32,
seed=self.seed,
)
return center_x, center_y
def _compute_rectangle_size(self, inputs):
input_shape = tf.shape(inputs)
image_height, image_width = (
input_shape[0],
input_shape[1],
)
height = tf.random.uniform(
[],
minval=self.height_lower,
maxval=self.height_upper,
dtype=tf.float32,
)
width = tf.random.uniform(
[],
minval=self.width_lower,
maxval=self.width_upper,
dtype=tf.float32,
)
if self._height_is_float:
height = height * tf.cast(image_height, tf.float32)
if self._width_is_float:
width = width * tf.cast(image_width, tf.float32)
height = tf.cast(tf.math.ceil(height), tf.int32)
width = tf.cast(tf.math.ceil(width), tf.int32)
height = tf.minimum(height, image_height)
width = tf.minimum(width, image_width)
return height, width
def _compute_rectangle_fill(self, inputs):
input_shape = tf.shape(inputs)
if self.fill_mode == "constant":
fill_value = tf.fill(input_shape, self.fill_value)
else:
# gaussian noise
fill_value = tf.random.normal(input_shape)
return fill_value
def get_config(self):
config = {
"height_factor": self.height_factor,
"width_factor": self.width_factor,
"fill_mode": self.fill_mode,
"fill_value": self.fill_value,
"seed": self.seed,
}
base_config = super().get_config()
return dict(list(base_config.items()) + list(config.items()))
"""
# Benchmarking
"""
(x_train, _), _ = keras.datasets.cifar10.load_data()
x_train = x_train.astype(float)
x_train.shape
images = []
num_images = [1000, 2000, 5000, 10000, 25000, 37500, 50000]
results = {}
for aug in [
VectorizedRandomCutout,
VMapRandomCutout,
MapFnRandomCutout,
JITVectorizedRandomCutout,
JITVMapRandomCutout,
JITMapFnRandomCutout,
]:
c = aug.__name__
layer = aug(0.2, 0.2)
runtimes = []
print(f"Timing {c}")
for n_images in num_images:
# warmup
layer(x_train[:n_images])
t0 = time.time()
r1 = layer(x_train[:n_images])
t1 = time.time()
runtimes.append(t1 - t0)
print(f"Runtime for {c}, n_images={n_images}: {t1-t0}")
results[c] = runtimes
plt.figure()
for key in results:
plt.plot(num_images, results[key], label=key)
plt.xlabel("Number images")
plt.ylabel("Runtime (seconds)")
plt.legend()
plt.show()
"""
# Sanity check
all of these should have comparable outputs
"""
images = []
for aug in [VectorizedRandomCutout, VMapRandomCutout, MapFnRandomCutout]:
layer = aug(0.5, 0.5)
images.append(layer(x_train[:3]))
images = [y for x in images for y in x]
plt.figure(figsize=(8, 8))
for i in range(9):
plt.subplot(3, 3, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.axis("off")
plt.show()
"""
# Extra notes
## Warnings
it would be really annoying as a user to use an official keras_cv component and get
warned that "RandomUniform" or "RandomUniformInt" inside pfor may not get the same
output.
"""
#!/usr/bin/env bash
# Copyright 2022 The KerasCV Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
# Builds a wheel of KerasCV for Pip. Requires Bazel.
# Adapted from https://github.com/tensorflow/addons/blob/master/build_deps/build_pip_pkg.sh
set -e
set -x
PLATFORM="$(uname -s | tr 'A-Z' 'a-z')"
function is_windows() {
if [[ "${PLATFORM}" =~ (cygwin|mingw32|mingw64|msys)_nt* ]]; then
true
else
false
fi
}
if is_windows; then
PIP_FILE_PREFIX="bazel-bin/build_pip_pkg.exe.runfiles/__main__/"
else
PIP_FILE_PREFIX="bazel-bin/build_pip_pkg.runfiles/__main__/"
fi
function main() {
while [[ ! -z "${1}" ]]; do
if [[ ${1} == "make" ]]; then
echo "Using Makefile to build pip package."
PIP_FILE_PREFIX=""
else
DEST=${1}
fi
shift
done
if [[ -z ${DEST} ]]; then
echo "No destination dir provided"
exit 1
fi
# Create the directory, then do dirname on a non-existent file inside it to
# give us an absolute paths with tilde characters resolved to the destination
# directory.
mkdir -p ${DEST}
if [[ ${PLATFORM} == "darwin" ]]; then
DEST=$(pwd -P)/${DEST}
else
DEST=$(readlink -f "${DEST}")
fi
echo "=== destination directory: ${DEST}"
TMPDIR=$(mktemp -d -t tmp.XXXXXXXXXX)
echo $(date) : "=== Using tmpdir: ${TMPDIR}"
echo "=== Copy KerasCV Custom op files"
cp ${PIP_FILE_PREFIX}setup.py "${TMPDIR}"
cp ${PIP_FILE_PREFIX}MANIFEST.in "${TMPDIR}"
cp ${PIP_FILE_PREFIX}README.md "${TMPDIR}"
cp ${PIP_FILE_PREFIX}LICENSE "${TMPDIR}"
rsync -avm -L --exclude='*_test.py' ${PIP_FILE_PREFIX}keras_cv "${TMPDIR}"
pushd ${TMPDIR}
echo $(date) : "=== Building wheel"
python3 setup.py bdist_wheel > /dev/null
cp dist/*.whl "${DEST}"
popd
rm -rf ${TMPDIR}
echo $(date) : "=== Output wheel file is in: ${DEST}"
}
main "$@"
# Copyright 2022 The KerasCV Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
# Usage: python configure.py
"""Configures local environment to prepare for building KerasCV from source."""
import logging
import os
import pathlib
import platform
import tensorflow as tf
from packaging.version import Version
_TFA_BAZELRC = ".bazelrc"
# Writes variables to bazelrc file
def write(line):
with open(_TFA_BAZELRC, "a") as f:
f.write(line + "\n")
def write_action_env(var_name, var):
write('build --action_env {}="{}"'.format(var_name, var))
def is_macos():
return platform.system() == "Darwin"
def is_windows():
return platform.system() == "Windows"
def is_linux():
return platform.system() == "Linux"
def is_raspi_arm():
return os.uname()[4] == "armv7l" or os.uname()[4] == "aarch64"
def is_linux_ppc64le():
return is_linux() and platform.machine() == "ppc64le"
def is_linux_x86_64():
return is_linux() and platform.machine() == "x86_64"
def is_linux_arm():
return is_linux() and platform.machine() == "arm"
def is_linux_aarch64():
return is_linux() and platform.machine() == "aarch64"
def is_linux_s390x():
return is_linux() and platform.machine() == "s390x"
def get_tf_header_dir():
import tensorflow as tf
tf_header_dir = tf.sysconfig.get_compile_flags()[0][2:]
if is_windows():
tf_header_dir = tf_header_dir.replace("\\", "/")
return tf_header_dir
def get_cpp_version():
cpp_version = "c++14"
if Version(tf.__version__) >= Version("2.10"):
cpp_version = "c++17"
return cpp_version
def get_tf_shared_lib_dir():
import tensorflow as tf
# OS Specific parsing
if is_windows():
tf_shared_lib_dir = tf.sysconfig.get_compile_flags()[0][2:-7] + "python"
return tf_shared_lib_dir.replace("\\", "/")
elif is_raspi_arm():
return tf.sysconfig.get_compile_flags()[0][2:-7] + "python"
else:
return tf.sysconfig.get_link_flags()[0][2:]
# Converts the linkflag namespec to the full shared library name
def get_shared_lib_name():
import tensorflow as tf
namespec = tf.sysconfig.get_link_flags()
if is_macos():
# MacOS
return "lib" + namespec[1][2:] + ".dylib"
elif is_windows():
# Windows
return "_pywrap_tensorflow_internal.lib"
elif is_raspi_arm():
# The below command for linux would return an empty list
return "_pywrap_tensorflow_internal.so"
else:
# Linux
return namespec[1][3:]
def create_build_configuration():
print()
print("Configuring KerasCV to be built from source...")
if os.path.isfile(_TFA_BAZELRC):
os.remove(_TFA_BAZELRC)
logging.disable(logging.WARNING)
write_action_env("TF_HEADER_DIR", get_tf_header_dir())
write_action_env("TF_SHARED_LIBRARY_DIR", get_tf_shared_lib_dir())
write_action_env("TF_SHARED_LIBRARY_NAME", get_shared_lib_name())
write_action_env("TF_CXX11_ABI_FLAG", tf.sysconfig.CXX11_ABI_FLAG)
# This should be replaced with a call to tf.sysconfig if it's added
write_action_env("TF_CPLUSPLUS_VER", get_cpp_version())
write("build --spawn_strategy=standalone")
write("build --strategy=Genrule=standalone")
write("build --experimental_repo_remote_exec")
write("build -c opt")
write(
"build --cxxopt="
+ '"-D_GLIBCXX_USE_CXX11_ABI="'
+ str(tf.sysconfig.CXX11_ABI_FLAG)
)
if is_windows():
write("build --config=windows")
write("build:windows --enable_runfiles")
write("build:windows --copt=/experimental:preprocessor")
write("build:windows --host_copt=/experimental:preprocessor")
write("build:windows --copt=/arch=AVX")
write("build:windows --cxxopt=/std:" + get_cpp_version())
write("build:windows --host_cxxopt=/std:" + get_cpp_version())
if is_macos() or is_linux():
if not is_linux_ppc64le() and not is_linux_arm() and not is_linux_aarch64():
write("build --copt=-mavx")
write("build --cxxopt=-std=" + get_cpp_version())
write("build --host_cxxopt=-std=" + get_cpp_version())
print("> Building only CPU ops")
print()
print("Build configurations successfully written to", _TFA_BAZELRC, ":\n")
print(pathlib.Path(_TFA_BAZELRC).read_text())
if __name__ == "__main__":
create_build_configuration()
package(default_visibility = ["//visibility:public"])
cc_library(
name = "tf_header_lib",
hdrs = [":tf_header_include"],
includes = ["include"],
visibility = ["//visibility:public"],
)
cc_library(
name = "libtensorflow_framework",
srcs = ["%{TF_SHARED_LIBRARY_NAME}"],
visibility = ["//visibility:public"],
)
%{TF_HEADER_GENRULE}
%{TF_SHARED_LIBRARY_GENRULE}
# Addons Build Definitions inherited from TensorFlow Core
D_GLIBCXX_USE_CXX11_ABI = "%{tf_cx11_abi}"
CPLUSPLUS_VERSION = "%{tf_cplusplus_ver}"
"""Setup TensorFlow as external dependency"""
_TF_HEADER_DIR = "TF_HEADER_DIR"
_TF_SHARED_LIBRARY_DIR = "TF_SHARED_LIBRARY_DIR"
_TF_SHARED_LIBRARY_NAME = "TF_SHARED_LIBRARY_NAME"
_TF_CXX11_ABI_FLAG = "TF_CXX11_ABI_FLAG"
_TF_CPLUSPLUS_VER = "TF_CPLUSPLUS_VER"
def _tpl(repository_ctx, tpl, substitutions = {}, out = None):
if not out:
out = tpl
repository_ctx.template(
out,
Label("//build_deps/tf_dependency:%s.tpl" % tpl),
substitutions,
)
def _fail(msg):
"""Output failure message when auto configuration fails."""
red = "\033[0;31m"
no_color = "\033[0m"
fail("%sPython Configuration Error:%s %s\n" % (red, no_color, msg))
def _is_windows(repository_ctx):
"""Returns true if the host operating system is windows."""
os_name = repository_ctx.os.name.lower()
if os_name.find("windows") != -1:
return True
return False
def _execute(
repository_ctx,
cmdline,
error_msg = None,
error_details = None,
empty_stdout_fine = False):
"""Executes an arbitrary shell command.
Helper for executes an arbitrary shell command.
Args:
repository_ctx: the repository_ctx object.
cmdline: list of strings, the command to execute.
error_msg: string, a summary of the error if the command fails.
error_details: string, details about the error or steps to fix it.
empty_stdout_fine: bool, if True, an empty stdout result is fine, otherwise
it's an error.
Returns:
The result of repository_ctx.execute(cmdline).
"""
result = repository_ctx.execute(cmdline)
if result.stderr or not (empty_stdout_fine or result.stdout):
_fail("\n".join([
error_msg.strip() if error_msg else "Repository command failed",
result.stderr.strip(),
error_details if error_details else "",
]))
return result
def _read_dir(repository_ctx, src_dir):
"""Returns a string with all files in a directory.
Finds all files inside a directory, traversing subfolders and following
symlinks. The returned string contains the full path of all files
separated by line breaks.
Args:
repository_ctx: the repository_ctx object.
src_dir: directory to find files from.
Returns:
A string of all files inside the given dir.
"""
if _is_windows(repository_ctx):
src_dir = src_dir.replace("/", "\\")
find_result = _execute(
repository_ctx,
["cmd.exe", "/c", "dir", src_dir, "/b", "/s", "/a-d"],
empty_stdout_fine = True,
)
# src_files will be used in genrule.outs where the paths must
# use forward slashes.
result = find_result.stdout.replace("\\", "/")
else:
find_result = _execute(
repository_ctx,
["find", src_dir, "-follow", "-type", "f"],
empty_stdout_fine = True,
)
result = find_result.stdout
return result
def _genrule(genrule_name, command, outs):
"""Returns a string with a genrule.
Genrule executes the given command and produces the given outputs.
Args:
genrule_name: A unique name for genrule target.
command: The command to run.
outs: A list of files generated by this rule.
Returns:
A genrule target.
"""
return (
"genrule(\n" +
' name = "' +
genrule_name + '",\n' +
" outs = [\n" +
outs +
"\n ],\n" +
' cmd = """\n' +
command +
'\n """,\n' +
")\n"
)
def _norm_path(path):
"""Returns a path with '/' and remove the trailing slash."""
path = path.replace("\\", "/")
if path[-1] == "/":
path = path[:-1]
return path
def _symlink_genrule_for_dir(
repository_ctx,
src_dir,
dest_dir,
genrule_name,
src_files = [],
dest_files = [],
tf_pip_dir_rename_pair = []):
"""Returns a genrule to symlink(or copy if on Windows) a set of files.
If src_dir is passed, files will be read from the given directory; otherwise
we assume files are in src_files and dest_files.
Args:
repository_ctx: the repository_ctx object.
src_dir: source directory.
dest_dir: directory to create symlink in.
genrule_name: genrule name.
src_files: list of source files instead of src_dir.
dest_files: list of corresonding destination files.
tf_pip_dir_rename_pair: list of the pair of tf pip parent directory to
replace. For example, in TF pip package, the source code is under
"tensorflow_core", and we might want to replace it with
"tensorflow" to match the header includes.
Returns:
genrule target that creates the symlinks.
"""
# Check that tf_pip_dir_rename_pair has the right length
tf_pip_dir_rename_pair_len = len(tf_pip_dir_rename_pair)
if tf_pip_dir_rename_pair_len != 0 and tf_pip_dir_rename_pair_len != 2:
_fail("The size of argument tf_pip_dir_rename_pair should be either 0 or 2, but %d is given." % tf_pip_dir_rename_pair_len)
if src_dir != None:
src_dir = _norm_path(src_dir)
dest_dir = _norm_path(dest_dir)
files = "\n".join(sorted(_read_dir(repository_ctx, src_dir).splitlines()))
# Create a list with the src_dir stripped to use for outputs.
if tf_pip_dir_rename_pair_len:
dest_files = files.replace(src_dir, "").replace(tf_pip_dir_rename_pair[0], tf_pip_dir_rename_pair[1]).splitlines()
else:
dest_files = files.replace(src_dir, "").splitlines()
src_files = files.splitlines()
command = []
outs = []
for i in range(len(dest_files)):
if dest_files[i] != "":
# If we have only one file to link we do not want to use the dest_dir, as
# $(@D) will include the full path to the file.
dest = "$(@D)/" + dest_dir + dest_files[i] if len(dest_files) != 1 else "$(@D)/" + dest_files[i]
# Copy the headers to create a sandboxable setup.
cmd = "cp -f"
command.append(cmd + ' "%s" "%s"' % (src_files[i], dest))
outs.append(' "' + dest_dir + dest_files[i] + '",')
genrule = _genrule(
genrule_name,
";\n".join(command),
"\n".join(outs),
)
return genrule
def _tf_pip_impl(repository_ctx):
tf_header_dir = repository_ctx.os.environ[_TF_HEADER_DIR]
tf_header_rule = _symlink_genrule_for_dir(
repository_ctx,
tf_header_dir,
"include",
"tf_header_include",
tf_pip_dir_rename_pair = ["tensorflow_core", "tensorflow"],
)
tf_shared_library_dir = repository_ctx.os.environ[_TF_SHARED_LIBRARY_DIR]
tf_shared_library_name = repository_ctx.os.environ[_TF_SHARED_LIBRARY_NAME]
tf_shared_library_path = "%s/%s" % (tf_shared_library_dir, tf_shared_library_name)
tf_cx11_abi = "-D_GLIBCXX_USE_CXX11_ABI=%s" % (repository_ctx.os.environ[_TF_CXX11_ABI_FLAG])
tf_cplusplus_ver = "-std=%s" % repository_ctx.os.environ[_TF_CPLUSPLUS_VER]
tf_shared_library_rule = _symlink_genrule_for_dir(
repository_ctx,
None,
"",
tf_shared_library_name,
[tf_shared_library_path],
[tf_shared_library_name],
)
_tpl(repository_ctx, "BUILD", {
"%{TF_HEADER_GENRULE}": tf_header_rule,
"%{TF_SHARED_LIBRARY_GENRULE}": tf_shared_library_rule,
"%{TF_SHARED_LIBRARY_NAME}": tf_shared_library_name,
})
_tpl(
repository_ctx,
"build_defs.bzl",
{
"%{tf_cx11_abi}": tf_cx11_abi,
"%{tf_cplusplus_ver}": tf_cplusplus_ver,
},
)
tf_configure = repository_rule(
environ = [
_TF_HEADER_DIR,
_TF_SHARED_LIBRARY_DIR,
_TF_SHARED_LIBRARY_NAME,
_TF_CXX11_ABI_FLAG,
_TF_CPLUSPLUS_VER,
],
implementation = _tf_pip_impl,
)
# keras-cv-image:deps has all deps of KerasCV for testing.
FROM us-west1-docker.pkg.dev/keras-team-test/keras-cv-test/keras-cv-image:deps
COPY . /kerascv
WORKDIR /kerascv
# KerasCV Accelerators Testing
This `cloudbuild/` directory contains configurations for accelerators (GPU/TPU)
testing. Briefly, for each PR, it copies the PR's code to a base docker image
which contains KerasCV dependencies to make a new docker image, and deploys the
new image to Google Kubernetes Engine cluster, then run all tests in
`keras_cv/` via Google Cloud Build.
- `cloudbuild.yaml`: The cloud build configuration that specifies steps to run
by cloud build.
- `Dockerfile`: The configuration to build the docker image for deployment.
- `requirements.txt`: Dependencies of KerasCV.
- `unit_test_jobs.jsonnet`: Jsonnet config that tells GKE cluster to run all
unit tests in `keras_cv/`.
This test is powered by [ml-testing-accelerators](https://github.com/GoogleCloudPlatform/ml-testing-accelerators).
### Adding Test Dependencies
You must be authorized to run builds in the `keras-team-test` GCP project.
If you are not, please open a GitHub issue and ping a team member.
To authorize yourself with `keras-team-test`, run:
```bash
gcloud config set project keras-team-test
```
To add a dependency for GPU tests:
- Create a PR adding the dependency to `requirements.txt`
- Have a Keras team member update the Docker image for GPU tests by running the remaining steps
- Create a `Dockerfile` with the following contents:
```
FROM tensorflow/tensorflow:2.10.0-gpu
RUN \
apt-get -y update && \
apt-get -y install openjdk-8-jdk && \
echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list && \
curl https://bazel.build/bazel-release.pub.gpg | apt-key add
RUN apt-get -y update
RUN apt-get -y install bazel
RUN apt-get -y install git
RUN git clone https://github.com/{path_to_keras_cv_fork}.git
RUN cd keras-cv && git checkout {branch_name}
RUN pip install -r keras-cv/cloudbuild/requirements.txt
```
- Run the following command from the directory with your `Dockerfile`:
```
gcloud builds submit --region=us-west1 --tag us-west1-docker.pkg.dev/keras-team-test/keras-cv-test/keras-cv-image:deps --timeout=10m
```
- Merge the PR adding the dependency
substitutions:
# GCS bucket name.
_GCS_BUCKET: 'gs://keras-cv-github-test'
# GKE cluster name.
_CLUSTER_NAME: 'keras-cv-test-cluster'
# Location of GKE cluster.
_CLUSTER_ZONE: 'us-west1-b'
# Image name.
_IMAGE_NAME: 'us-west1-docker.pkg.dev/keras-team-test/keras-cv-test/keras-cv-image'
steps:
- name: 'docker'
id: build-image
args: [
'build',
'.',
'-f', 'cloudbuild/Dockerfile',
'-t', '$_IMAGE_NAME:$BUILD_ID',
]
- name: 'docker'
id: push-image
waitFor:
- build-image
args: ['push', '$_IMAGE_NAME:$BUILD_ID']
- name: 'golang'
id: download-jsonnet
waitFor: ['-']
entrypoint: 'go'
args: [
'install',
'github.com/google/go-jsonnet/cmd/jsonnet@latest',
]
- name: 'google/cloud-sdk'
id: clone-templates
waitFor: ['-']
entrypoint: 'git'
args: [
'clone',
'https://github.com/GoogleCloudPlatform/ml-testing-accelerators.git',
]
- name: 'golang'
id: build-templates
waitFor:
- download-jsonnet
- clone-templates
entrypoint: 'jsonnet'
args: [
'cloudbuild/unit_test_jobs.jsonnet',
'--string',
'-J', 'ml-testing-accelerators',
'--ext-str', 'image=$_IMAGE_NAME',
'--ext-str', 'tag_name=$BUILD_ID',
'--ext-str', 'gcs_bucket=$_GCS_BUCKET',
'-o', 'output.yaml',
]
- name: 'google/cloud-sdk'
id: create-job
waitFor:
- push-image
- build-templates
entrypoint: bash
args:
- -c
- |
set -u
set -e
set -x
gcloud container clusters get-credentials $_CLUSTER_NAME --zone $_CLUSTER_ZONE --project keras-team-test
job_name=$(kubectl create -f output.yaml -o name)
sleep 5
pod_name=$(kubectl wait --for condition=ready --timeout=10m pod -l job-name=${job_name#job.batch/} -o name)
kubectl logs -f $pod_name --container=train
sleep 5
gcloud artifacts docker images delete $_IMAGE_NAME:$BUILD_ID
exit $(kubectl get $pod_name -o jsonpath={.status.containerStatuses[0].state.terminated.exitCode})
timeout: 1800s # 30 minutes
options:
volumes:
- name: go-modules
path: /go
absl-py
packaging
pandas
tensorflow
tensorflow-datasets
flake8
regex
isort
black
pytest
\ No newline at end of file
local base = import 'templates/base.libsonnet';
local gpus = import 'templates/gpus.libsonnet';
local image = std.extVar('image');
local tagName = std.extVar('tag_name');
local gcsBucket = std.extVar('gcs_bucket');
local unittest = base.BaseTest {
// Configure job name.
frameworkPrefix: "tf",
modelName: "keras-cv",
mode: "unit-tests",
timeout: 3600, # 1 hour, in seconds
// Set up runtime environment.
image: image,
imageTag: tagName,
accelerator: gpus.teslaT4,
outputBucket: gcsBucket,
entrypoint: [
'bash',
'-c',
|||
# Build custom ops from source
python build_deps/configure.py
bazel build keras_cv/custom_ops:all --verbose_failures
cp bazel-bin/keras_cv/custom_ops/*.so keras_cv/custom_ops/
TEST_CUSTOM_OPS=true
# Run whatever is in `command` here.
${@:0}
|||
],
command: [
'pytest',
'keras_cv',
],
};
std.manifestYamlDoc(unittest.oneshotJob, quote_keys=false)
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import demo_utils
import tensorflow as tf
from keras_cv import layers as cv_layers
def _default_anchor_generator(bounding_box_format):
strides = [50]
sizes = [100.0]
scales = [1.0]
aspect_ratios = [1.0]
return cv_layers.AnchorGenerator(
bounding_box_format=bounding_box_format,
anchor_sizes=sizes,
aspect_ratios=aspect_ratios,
scales=scales,
strides=strides,
clip_boxes=True,
)
generator = _default_anchor_generator(bounding_box_format="xywh")
def pair_with_anchor_boxes(inputs):
images = inputs["images"]
anchor_boxes = generator(images[0])
anchor_boxes = anchor_boxes[0]
anchor_boxes = tf.expand_dims(anchor_boxes, axis=0)
anchor_boxes = tf.tile(anchor_boxes, [tf.shape(images)[0], 1, 1])
inputs["bounding_boxes"] = anchor_boxes
return inputs
if __name__ == "__main__":
dataset = demo_utils.load_voc_dataset(bounding_box_format="xywh")
result = dataset.map(pair_with_anchor_boxes, num_parallel_calls=tf.data.AUTOTUNE)
demo_utils.visualize_data(result, bounding_box_format="xywh")
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility functions for preprocessing demos."""
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
from keras_cv import bounding_box
def preprocess_voc(inputs, format, image_size):
"""mapping function to create batched image and bbox coordinates"""
inputs["image"] = tf.image.resize(inputs["image"], image_size)
inputs["objects"]["bbox"] = bounding_box.convert_format(
inputs["objects"]["bbox"],
images=inputs["image"],
source="rel_yxyx",
target=format,
)
return {"images": inputs["image"], "bounding_boxes": inputs["objects"]["bbox"]}
def load_voc_dataset(
bounding_box_format,
name="voc/2007",
batch_size=9,
image_size=(224, 224),
):
dataset = tfds.load(name, split=tfds.Split.TRAIN, shuffle_files=True)
dataset = dataset.map(
lambda x: preprocess_voc(x, format=bounding_box_format, image_size=image_size),
num_parallel_calls=tf.data.AUTOTUNE,
)
dataset = dataset.padded_batch(
batch_size, padding_values={"images": None, "bounding_boxes": -1.0}
)
return dataset
def visualize_data(data, bounding_box_format):
data = next(iter(data))
images = data["images"]
bounding_boxes = data["bounding_boxes"]
output_images = visualize_bounding_boxes(
images, bounding_boxes, bounding_box_format
).numpy()
gallery_show(output_images)
def visualize_bounding_boxes(image, bounding_boxes, bounding_box_format):
color = np.array([[255.0, 0.0, 0.0]])
bounding_boxes = bounding_box.convert_format(
bounding_boxes,
source=bounding_box_format,
target="rel_yxyx",
images=image,
)
return tf.image.draw_bounding_boxes(image, bounding_boxes, color, name=None)
def gallery_show(images):
images = images.astype(int)
for i in range(9):
image = images[i]
plt.subplot(3, 3, i + 1)
plt.imshow(image.astype("uint8"))
plt.axis("off")
plt.show()
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility functions for preprocessing demos."""
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds
from keras_cv import bounding_box
def preprocess_voc(inputs, format, image_size):
"""mapping function to create batched image and bbox coordinates"""
inputs["image"] = tf.image.resize(inputs["image"], image_size)
inputs["objects"]["bbox"] = bounding_box.convert_format(
inputs["objects"]["bbox"],
images=inputs["image"],
source="rel_yxyx",
target=format,
)
inputs["objects"]["bbox"] = bounding_box.add_class_id(inputs["objects"]["bbox"])
return {"images": inputs["image"], "bounding_boxes": inputs["objects"]["bbox"]}
def load_voc_dataset(
bounding_box_format,
name="voc/2007",
batch_size=9,
image_size=(224, 224),
):
dataset = tfds.load(name, split=tfds.Split.TRAIN, shuffle_files=True)
dataset = dataset.map(
lambda x: preprocess_voc(x, format=bounding_box_format, image_size=image_size),
num_parallel_calls=tf.data.AUTOTUNE,
)
dataset = dataset.padded_batch(
batch_size, padding_values={"images": None, "bounding_boxes": -1.0}
)
return dataset
def visualize_data(data, bounding_box_format):
data = next(iter(data))
images = data["images"]
bounding_boxes = data["bounding_boxes"]
output_images = visualize_bounding_boxes(
images, bounding_boxes, bounding_box_format
).numpy()
gallery_show(output_images)
def visualize_bounding_boxes(image, bounding_boxes, bounding_box_format):
color = np.array([[255.0, 0.0, 0.0]])
bounding_boxes = bounding_boxes[..., :4]
bounding_boxes = bounding_box.convert_format(
bounding_boxes,
source=bounding_box_format,
target="rel_yxyx",
images=image,
)
return tf.image.draw_bounding_boxes(image, bounding_boxes, color, name=None)
def gallery_show(images):
images = images.astype(int)
for i in range(9):
image = images[i]
plt.subplot(3, 3, i + 1)
plt.imshow(image.astype("uint8"))
plt.axis("off")
plt.show()
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
mosaic_demo.py shows how to use the Mosaic preprocessing layer for
object detection.
"""
import demo_utils
import tensorflow as tf
from keras_cv.layers import preprocessing
IMG_SIZE = (256, 256)
BATCH_SIZE = 9
def main():
dataset = demo_utils.load_voc_dataset(bounding_box_format="rel_xyxy")
mosaic = preprocessing.Mosaic(bounding_box_format="rel_xyxy")
result = dataset.map(mosaic, num_parallel_calls=tf.data.AUTOTUNE)
demo_utils.visualize_data(result, bounding_box_format="rel_xyxy")
if __name__ == "__main__":
main()
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
random_flip_demo.py shows how to use the RandomFlip preprocessing layer for
object detection.
"""
import demo_utils
import tensorflow as tf
from keras_cv.layers import preprocessing
IMG_SIZE = (256, 256)
BATCH_SIZE = 9
def main():
dataset = demo_utils.load_voc_dataset(bounding_box_format="rel_xyxy")
random_rotation = preprocessing.RandomFlip(bounding_box_format="rel_xyxy")
result = dataset.map(random_rotation, num_parallel_calls=tf.data.AUTOTUNE)
demo_utils.visualize_data(result, bounding_box_format="rel_xyxy")
if __name__ == "__main__":
main()
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
random_rotation_demo.py shows how to use the RandomRotation preprocessing layer
for object detection.
"""
import demo_utils
import tensorflow as tf
from keras_cv.layers import preprocessing
IMG_SIZE = (256, 256)
BATCH_SIZE = 9
def main():
dataset = demo_utils.load_voc_dataset(bounding_box_format="rel_xyxy")
random_rotation = preprocessing.RandomRotation(
factor=0.5, bounding_box_format="rel_xyxy"
)
result = dataset.map(random_rotation, num_parallel_calls=tf.data.AUTOTUNE)
demo_utils.visualize_data(result, bounding_box_format="rel_xyxy")
if __name__ == "__main__":
main()
# Copyright 2022 The KerasCV Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
random_shear_demo.py shows how to use the RandomShear preprocessing layer
for object detection.
"""
import demo_utils
import tensorflow as tf
from keras_cv.layers import preprocessing
IMG_SIZE = (256, 256)
BATCH_SIZE = 9
def main():
dataset = demo_utils.load_voc_dataset(bounding_box_format="rel_xyxy")
random_shear = preprocessing.RandomShear(
x_factor=(0.1, 0.5),
y_factor=(0.1, 0.5),
bounding_box_format="rel_xyxy",
)
dataset = dataset.map(random_shear, num_parallel_calls=tf.data.AUTOTUNE)
demo_utils.visualize_data(dataset, bounding_box_format="rel_xyxy")
if __name__ == "__main__":
main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment