Project: Beam (App Definition and Development / Streaming & Messaging)
File: side-input-updates.md

title: "Auto Update ML models using WatchFilePattern" <!-- 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. --> The pipeline in this example uses a `PTransform` to run inference on images using TensorFlow models. It uses a `PCollection` that emits `ModelMetadata` to update the model. Using side inputs, you can update your model (which is passed in a `ModelHandler` configuration object) in real-time, even while the Beam pipeline is still running. This can be done either by leveraging one of Beam's provided patterns, such as the `WatchFilePattern`, or by configuring a custom side input `PCollection` that defines the logic for the model update. For more information about side inputs, see the section in the Apache Beam Programming Guide. This example uses as a side input. `WatchFilePattern` is used to watch for the file updates matching the `file_pattern` based on timestamps. It emits the latest , which is used in the RunInference `PTransform` to automatically update the ML model without stopping the Beam pipeline. To read the image names, use a Pub/Sub topic as the source. The Pub/Sub topic emits a `UTF-8` encoded model path that is used to read and preprocess images to run the inference. For the purpose of this example, use TensorFlow models saved in format. The Pub/Sub topic emits an image path. We need to read and preprocess the image to use it for RunInference. The `read_image` function is used to read the image for inference. ```python import io from PIL import Image from apache_beam.io.filesystems import FileSystems import numpy import tensorflow as tf def readimage(imagefile_name): with FileSystems().open(imagefilename, 'r') as file: data = Image.open(io.BytesIO(file.read())).convert('RGB') img = data.resize((224, 224)) img = numpy.array(img) / 255.0 imgtensor = tf.cast(tf.convertto_tensor(img[...]), dtype=tf.float32) return img_tensor ``` Now, let's jump into the pipeline code. Pipeline steps: Get the image names from the Pub/Sub

Read and pre-process the images using the `read_image` function. Pass the images to the RunInference `PTransform`. RunInference takes `modelhandler` and `modelmetadata_pcoll` as input parameters. For the , we use . ```python from apachebeam.ml.inference.tensorflowinference import TFModelHandlerTensor tfmodelhandler = TFModelHandlerTensor(modeluri='gs://<your-bucket>/<modelpath.h5>') ``` The `modelmetadatapcoll` is a `PCollection` to the RunInference `PTransform`. This side input is used to update the models in the `model_handler` without needing to stop the beam pipeline. We will use `WatchFilePattern` as side input to watch a glob pattern matching `.h5` files. `modelmetadatapcoll` expects a `PCollection` of ModelMetadata compatible with . Because the pipeline uses `WatchFilePattern` as side input, it will take care of windowing and wrapping the output into `ModelMetadata`. After the pipeline starts processing data and when you see some outputs emitted from the RunInference `PTransform`, upload a `.h5` `TensorFlow` model that matches the `filepattern` to the Google Cloud Storage bucket. RunInference will update the `modeluri` of `TFModelHandlerTensor` using `WatchFilePattern` as a side input. Note: Side input update frequency is non-deterministic and can have longer intervals between updates. ```python import apache_beam as beam from apache_beam.ml.inference.utils import WatchFilePattern from apache_beam.ml.inference.base import RunInference with beam.Pipeline() as pipeline: file_pattern = 'gs://<your-bucket>/*.h5' pubsubtopic = '<topicemittingimagenames>' sideinputpcoll = ( pipeline | "FilePatternUpdates" >> WatchFilePattern(filepattern=filepattern)) images_pcoll = ( pipeline | "ReadFromPubSub" >> beam.io.ReadFromPubSub(topic=pubsub_topic) | "DecodeBytes" >> beam.Map(lambda x: x.decode('utf-8')) | "PreProcessImage" >> beam.Map(read_image) ) inference_pcoll = ( images_pcoll | "RunInference" >> RunInference( modelhandler=tfmodel_handler, modelmetadatapcoll=sideinputpcoll)) ``` When the inference is complete, RunInference outputs a `PredictionResult` object that contains `example`, `inference`, and `modelid` fields. The `modelid` is used to identify which model is used for running the inference. ```python from apache_beam.ml.inference.base import PredictionResult class PostProcessor(beam.DoFn): """ Process the PredictionResult to get the predicted label and model id used for inference. """ def process(self, element: PredictionResult) -> typing.Iterable[str]: predicted_class = numpy.argmax(element.inference[0], axis=-1) labelspath = tf.keras.utils.getfile( 'ImageNetLabels.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt' ) imagenetlabels = numpy.array(open(labelspath).read().splitlines()) predictedclassname = imagenetlabels[predictedclass] return predictedclassname.title(), element.model_id postprocessorpcoll = (inference_pcoll | "PostProcessor" >> PostProcessor()) ``` ```python result = pipeline.run().waituntilfinish() ``` Note: The `model_name` of the `ModelMetaData` object will be attached as prefix to the calculated by the RunInference `PTransform`. You can use this example as a pattern when using side inputs with the RunInference `PTransform` to auto-update the models without stopping the pipeline. You can see a similar example for PyTorch on .