{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# GISLR [Small Version]: On the Shoulders of Giants\nSame as LB 0.63 original, except reduce starting layer size to reduce model size for ensembling.\n\nThis notebook is built on top of the great work from the community! In particular, this notebook is copied directly from Darien's excellent work [here][1], and the features are loaded from my notebook [here][2], and that notebook is directly based off of the format [here][3].\n\nThis notebook is going public to celebrate my first ever public LB first place! Fun!\n\nIn this notebook, the main updates are:\n1. Adding the time dimension! Finally, right? There's a 0.58 notebook [here][4] that STILL is just flat mean + std, so I hardly think 0.63 is nearing the limit of what we can achieve. Check out the [features notebook][2], I do two different techniques for the time dimension, resize is one, and padding into a multiple of 3, and then taking mean and std of each third of the data is the other. It took some doing to implement them within the TFLite format/restrictions, so I hope it serves as a useful resource for others!\n2. Just small hyperparameter adjustments.\n3. Oh, and drop face, drop pose, add lips. But refer to the features notebook for the precise details.\n\n# More Credits\nThanks to many Kagglers who have shared ideas. Lonnie's notebook [here][5] helped me and pretty much everyone as we were struggling to figure out this TFLite Model competition format. Andrew helped give suggestions on some of my questions on the discussion page.\n\n[1]: https://www.kaggle.com/code/dschettler8845/gislr-learn-eda-baseline\n[2]: https://www.kaggle.com/code/roberthatch/gislr-feature-data-on-the-shoulders\n[3]: https://www.kaggle.com/code/mayukh18/gislr-feature-data\n[4]: https://www.kaggle.com/code/medali1992/gislr-nn-arcface-baseline\n[5]: https://www.kaggle.com/code/lonnieqin/isolated-sign-language-recognition-with-dnn","metadata":{}},{"cell_type":"markdown","source":"<br><br>\n# MOTIVATION\n**\\#OpenToWork**\n\nAfter 9 years as a Silicon Firmware Engineer at Intel, I am now looking for my next career opportunity. Naturally I am especially interested in Machine Learning opportunities.\n\nIn this notebook, my goals are to:\n* Demonstrate my core strengths. Soft skills like problem solving, communication, and passion.\n* Build up my knowledge skills. I love machine learning and, in particular, love to optimize anything and everything. In this notebook, I explore TensorFlow and Neural Networks.\n* Continue to build on previous Kaggle successes.\n\n**LinkedIn:** https://www.linkedin.com/in/robhatch/\n\n**Kaggle:** https://www.kaggle.com/roberthatch","metadata":{}},{"cell_type":"markdown","source":"### Versions:\n10. LB 0.63. Best run (non-deterministic)\n11. First public version with header documentation added. Same code, worse score LB 0.62 (non-deterministic)\n13. Add deterministic fix for TensorFlow CUDA, as pointed out by Jonathan Chan in the comments. Also save final model in Keras format for easier ensembling.\n\nSMALL:\n1. Reduce STARTING_LAYER_SIZE to 512. The only purpose is allow for more model ensembling without hitting 40MB limit of final model.","metadata":{}},{"cell_type":"markdown","source":"# IMPORTS","metadata":{}},{"cell_type":"code","source":"print(\"\\n... PIP INSTALLS STARTING ...\\n\")\n!pip install -q --upgrade tensorflow-io\ntry:\n    import mediapipe as mp\nexcept:\n    !pip install -q mediapipe\n    import mediapipe as mp\nprint(\"\\n... PIP INSTALLS COMPLETE ...\\n\")\n\n\nprint(\"\\n... IMPORTS STARTING ...\\n\")\nprint(\"\\n\\tVERSION INFORMATION\")\n# Competition Specific Imports (You'll see why we need these later)\n# mediapipe above\n\n# Machine Learning and Data Science Imports (basics)\nimport tensorflow as tf; print(f\"\\t\\t– TENSORFLOW VERSION: {tf.__version__}\");\nimport tensorflow_io as tfio; print(f\"\\t\\t– TENSORFLOW-IO VERSION: {tfio.__version__}\");\nimport pandas as pd; pd.options.mode.chained_assignment = None; pd.set_option('display.max_columns', None);\nimport numpy as np; print(f\"\\t\\t– NUMPY VERSION: {np.__version__}\");\nimport sklearn; print(f\"\\t\\t– SKLEARN VERSION: {sklearn.__version__}\");\n\n# Built-In Imports (mostly don't worry about these)\nfrom kaggle_datasets import KaggleDatasets\nfrom collections import Counter\nfrom datetime import datetime\nfrom zipfile import ZipFile\nfrom glob import glob\nimport Levenshtein\nimport warnings\nimport requests\nimport hashlib\nimport imageio\nimport IPython\nimport sklearn\nimport urllib\nimport zipfile\nimport pickle\nimport random\nimport shutil\nimport string\nimport json\nimport math\nimport time\nimport gzip\nimport ast\nimport sys\nimport io\nimport os\nimport gc\nimport re\n\n# fix the non-deterministic nature of cuda - https://ai-researcher.com/2022/07/30/how-to-write-reproducible-experiments-with-tensorflow/\nos.environ['TF_DETERMINISTIC_OPS'] = '1'\nos.environ['TF_CUDNN_DETERMINISTIC'] = '1'\n\ndef seed_it_all(seed=7):\n    \"\"\" Attempt to be Reproducible \"\"\"\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    random.seed(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n\n\nprint(\"\\n\\n... IMPORTS COMPLETE ...\\n\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-03-07T22:51:03.186248Z","iopub.execute_input":"2023-03-07T22:51:03.18652Z","iopub.status.idle":"2023-03-07T22:51:37.534729Z","shell.execute_reply.started":"2023-03-07T22:51:03.186492Z","shell.execute_reply":"2023-03-07T22:51:37.533474Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# SETUP","metadata":{}},{"cell_type":"code","source":"SEED = 12\nseed_it_all(seed=SEED)\n\n## Hyperparameters\nBATCH_SIZE = 64\nVAL_PCT = 0.1\nLEARNING_RATE = 0.000333\nLR_PATIENCE = 2\nLR_REDUCTION_FACTOR = 0.8\nEPOCHS = 100\n\nSTARTING_LAYER_SIZE = 512\nDROPOUTS = [0.4, 0.4]\n","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:51:37.537691Z","iopub.execute_input":"2023-03-07T22:51:37.538698Z","iopub.status.idle":"2023-03-07T22:51:37.545765Z","shell.execute_reply.started":"2023-03-07T22:51:37.538657Z","shell.execute_reply":"2023-03-07T22:51:37.544163Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### HELPER FUNCTIONS","metadata":{}},{"cell_type":"code","source":"def read_json_file(file_path):\n    try:\n        # Open the file and load the JSON data into a Python object\n        with open(file_path, 'r') as file:\n            json_data = json.load(file)\n        return json_data\n    except FileNotFoundError:\n        # Raise an error if the file path does not exist\n        raise FileNotFoundError(f\"File not found: {file_path}\")\n    except ValueError:\n        # Raise an error if the file does not contain valid JSON data\n        raise ValueError(f\"Invalid JSON data in file: {file_path}\")\n\n\nROWS_PER_FRAME = 543  # number of landmarks per frame\ndef load_relevant_data_subset(pq_path):\n    data_columns = ['x', 'y', 'z']\n    data = pd.read_parquet(pq_path, columns=data_columns)\n    n_frames = int(len(data) / ROWS_PER_FRAME)\n    data = data.values.reshape(n_frames, ROWS_PER_FRAME, len(data_columns))\n    return data.astype(np.float32)","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:51:37.548946Z","iopub.execute_input":"2023-03-07T22:51:37.54944Z","iopub.status.idle":"2023-03-07T22:51:37.568923Z","shell.execute_reply.started":"2023-03-07T22:51:37.549376Z","shell.execute_reply":"2023-03-07T22:51:37.567759Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### LOAD DATA","metadata":{}},{"cell_type":"code","source":"# Define the path to the root data directory\nDATA_DIR         = \"/kaggle/input/asl-signs\"\n\nprint(\"\\n... BASIC DATA SETUP STARTING ...\\n\")\nprint(\"\\n\\n... LOAD TRAIN DATAFRAME FROM CSV FILE ...\\n\")\n\ntrain_df = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\ntrain_df[\"path\"] = DATA_DIR+\"/\"+train_df[\"path\"]\ndisplay(train_df)\n\nprint(\"\\n\\n... LOAD SIGN TO PREDICTION INDEX MAP FROM JSON FILE ...\\n\")\ns2p_map = {k.lower():v for k,v in read_json_file(os.path.join(DATA_DIR, \"sign_to_prediction_index_map.json\")).items()}\np2s_map = {v:k for k,v in read_json_file(os.path.join(DATA_DIR, \"sign_to_prediction_index_map.json\")).items()}\nencoder = lambda x: s2p_map.get(x.lower())\ndecoder = lambda x: p2s_map.get(x)\nprint(s2p_map)\n","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:51:37.570314Z","iopub.execute_input":"2023-03-07T22:51:37.570721Z","iopub.status.idle":"2023-03-07T22:51:37.801596Z","shell.execute_reply.started":"2023-03-07T22:51:37.570681Z","shell.execute_reply":"2023-03-07T22:51:37.800567Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# PREPROCESSING\n\nThe preprocessing cells need to match the same cells from the feature generation notebook","metadata":{}},{"cell_type":"markdown","source":"## Configuration","metadata":{}},{"cell_type":"code","source":"DROP_Z = False\n\nNUM_FRAMES = 15\nSEGMENTS = 3\n\nLEFT_HAND_OFFSET = 468\nPOSE_OFFSET = LEFT_HAND_OFFSET+21\nRIGHT_HAND_OFFSET = POSE_OFFSET+33\n\n## average over the entire face, and the entire 'pose'\naveraging_sets = [[0, 468], [POSE_OFFSET, 33]]\n\nlip_landmarks = [61, 185, 40, 39, 37,  0, 267, 269, 270, 409,\n                 291,146, 91,181, 84, 17, 314, 405, 321, 375, \n                 78, 191, 80, 81, 82, 13, 312, 311, 310, 415, \n                 95, 88, 178, 87, 14,317, 402, 318, 324, 308]\nleft_hand_landmarks = list(range(LEFT_HAND_OFFSET, LEFT_HAND_OFFSET+21))\nright_hand_landmarks = list(range(RIGHT_HAND_OFFSET, RIGHT_HAND_OFFSET+21))\n\npoint_landmarks = [item for sublist in [lip_landmarks, left_hand_landmarks, right_hand_landmarks] for item in sublist]\n\nLANDMARKS = len(point_landmarks) + len(averaging_sets)\nprint(LANDMARKS)\nif DROP_Z:\n    INPUT_SHAPE = (NUM_FRAMES,LANDMARKS*2)\nelse:\n    INPUT_SHAPE = (NUM_FRAMES,LANDMARKS*3)\n\nFLAT_INPUT_SHAPE = (INPUT_SHAPE[0] + 2 * (SEGMENTS + 1)) * INPUT_SHAPE[1]\n    ","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:51:37.805129Z","iopub.execute_input":"2023-03-07T22:51:37.805465Z","iopub.status.idle":"2023-03-07T22:51:37.816852Z","shell.execute_reply.started":"2023-03-07T22:51:37.805436Z","shell.execute_reply":"2023-03-07T22:51:37.815558Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Helper Functions","metadata":{}},{"cell_type":"code","source":"def tf_nan_mean(x, axis=0):\n    return tf.reduce_sum(tf.where(tf.math.is_nan(x), tf.zeros_like(x), x), axis=axis) / tf.reduce_sum(tf.where(tf.math.is_nan(x), tf.zeros_like(x), tf.ones_like(x)), axis=axis)\n\ndef tf_nan_std(x, axis=0):\n    d = x - tf_nan_mean(x, axis=axis)\n    return tf.math.sqrt(tf_nan_mean(d * d, axis=axis))\n\ndef flatten_means_and_stds(x, axis=0):\n    # Get means and stds\n    x_mean = tf_nan_mean(x, axis=0)\n    x_std  = tf_nan_std(x,  axis=0)\n\n    x_out = tf.concat([x_mean, x_std], axis=0)\n    x_out = tf.reshape(x_out, (1, INPUT_SHAPE[1]*2))\n    x_out = tf.where(tf.math.is_finite(x_out), x_out, tf.zeros_like(x_out))\n    return x_out\n","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:51:37.819088Z","iopub.execute_input":"2023-03-07T22:51:37.819612Z","iopub.status.idle":"2023-03-07T22:51:37.829557Z","shell.execute_reply.started":"2023-03-07T22:51:37.819534Z","shell.execute_reply":"2023-03-07T22:51:37.82853Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## TensorFlow Feature Preprocessing Layer","metadata":{}},{"cell_type":"code","source":"class FeatureGen(tf.keras.layers.Layer):\n    def __init__(self):\n        super(FeatureGen, self).__init__()\n    \n    def call(self, x_in):\n        if DROP_Z:\n            x_in = x_in[:, :, 0:2]\n        x_list = [tf.expand_dims(tf_nan_mean(x_in[:, av_set[0]:av_set[0]+av_set[1], :], axis=1), axis=1) for av_set in averaging_sets]\n        x_list.append(tf.gather(x_in, point_landmarks, axis=1))\n        x = tf.concat(x_list, 1)\n\n        x_padded = x\n        for i in range(SEGMENTS):\n            p0 = tf.where( ((tf.shape(x_padded)[0] % SEGMENTS) > 0) & ((i % 2) != 0) , 1, 0)\n            p1 = tf.where( ((tf.shape(x_padded)[0] % SEGMENTS) > 0) & ((i % 2) == 0) , 1, 0)\n            paddings = [[p0, p1], [0, 0], [0, 0]]\n            x_padded = tf.pad(x_padded, paddings, mode=\"SYMMETRIC\")\n        x_list = tf.split(x_padded, SEGMENTS)\n        x_list = [flatten_means_and_stds(_x, axis=0) for _x in x_list]\n\n        x_list.append(flatten_means_and_stds(x, axis=0))\n        \n        ## Resize only dimension 0. Resize can't handle nan, so replace nan with that dimension's avg value to reduce impact.\n        x = tf.image.resize(tf.where(tf.math.is_finite(x), x, tf_nan_mean(x, axis=0)), [NUM_FRAMES, LANDMARKS])\n        x = tf.reshape(x, (1, INPUT_SHAPE[0]*INPUT_SHAPE[1]))\n        x = tf.where(tf.math.is_nan(x), tf.zeros_like(x), x)\n        x_list.append(x)\n        x = tf.concat(x_list, axis=1)\n        return x\n\nprint(FeatureGen()(tf.keras.Input((543, 3), dtype=tf.float32, name=\"inputs\")))\nFeatureGen()(load_relevant_data_subset(train_df.path[0]))","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:51:37.830981Z","iopub.execute_input":"2023-03-07T22:51:37.831447Z","iopub.status.idle":"2023-03-07T22:51:41.193398Z","shell.execute_reply.started":"2023-03-07T22:51:37.831408Z","shell.execute_reply":"2023-03-07T22:51:41.191914Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# TRAIN","metadata":{}},{"cell_type":"code","source":"train_x    = np.load(\"/kaggle/input/gislr-feature-data-on-the-shoulders/feature_data.npy\").astype(np.float32)\ntrain_y    = np.load(\"/kaggle/input/gislr-feature-data-on-the-shoulders/feature_labels.npy\").astype(np.uint8)\nprint(train_x.shape, train_y.shape)\nif DROP_Z:\n    train_x = np.reshape(train_x, [train_x.shape[0], -1, 3])\n    train_x = train_x[:, :, 0:2]\n    train_x = np.reshape(train_x, [train_x.shape[0], -1])\n    print(train_x.shape, train_y.shape)\n\nN_TOTAL = train_x.shape[0]\nFLAT_FRAME_SHAPE = train_x.shape[1]\nassert(FLAT_FRAME_SHAPE == FLAT_INPUT_SHAPE)\nN_VAL   = int(N_TOTAL*VAL_PCT)\nN_TRAIN = N_TOTAL-N_VAL\n\nrandom_idxs = random.sample(range(N_TOTAL), N_TOTAL)\ntrain_idxs, val_idxs = np.array(random_idxs[:N_TRAIN]), np.array(random_idxs[N_TRAIN:])\n\nval_x, val_y = train_x[val_idxs], train_y[val_idxs]\ntrain_x, train_y = train_x[train_idxs], train_y[train_idxs]\n","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:51:41.195976Z","iopub.execute_input":"2023-03-07T22:51:41.19666Z","iopub.status.idle":"2023-03-07T22:52:16.679869Z","shell.execute_reply.started":"2023-03-07T22:51:41.196617Z","shell.execute_reply":"2023-03-07T22:52:16.678777Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def fc_block(inputs, output_channels, dropout=0.2):\n    x = tf.keras.layers.Dense(output_channels)(inputs)\n    x = tf.keras.layers.BatchNormalization()(x)\n    x = tf.keras.layers.Activation(\"gelu\")(x)\n    x = tf.keras.layers.Dropout(dropout)(x)\n    return x\n\ndef get_model(n_labels=250, init_fc=STARTING_LAYER_SIZE, flat_frame_len=FLAT_FRAME_SHAPE):\n    _inputs = tf.keras.layers.Input(shape=(flat_frame_len,))\n    x = _inputs\n    \n    # Define layers\n    for i in range(len(DROPOUTS)):\n        x = fc_block(\n            x, output_channels=init_fc//(2**i), \n            dropout=DROPOUTS[i]\n        )\n    \n    # Define output layer\n    _outputs = tf.keras.layers.Dense(n_labels, activation=\"softmax\")(x)\n    \n    # Build the model\n    model = tf.keras.models.Model(inputs=_inputs, outputs=_outputs)\n    return model\n\nmodel = get_model()\nmodel.compile(tf.keras.optimizers.Adam(LEARNING_RATE), \"sparse_categorical_crossentropy\", metrics=\"acc\")\nmodel.summary()\n\ntf.keras.utils.plot_model(model)","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:52:16.681488Z","iopub.execute_input":"2023-03-07T22:52:16.682509Z","iopub.status.idle":"2023-03-07T22:52:17.146285Z","shell.execute_reply.started":"2023-03-07T22:52:16.682469Z","shell.execute_reply":"2023-03-07T22:52:17.145005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!mkdir models\ncb_list = [\n    tf.keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True, verbose=1),\n    tf.keras.callbacks.ReduceLROnPlateau(patience=LR_PATIENCE, factor=LR_REDUCTION_FACTOR, verbose=1)\n]\nhistory = model.fit(train_x, train_y, validation_data=(val_x, val_y), epochs=EPOCHS, callbacks=cb_list, batch_size=BATCH_SIZE)\nmodel.save(\"./models/asl_model\")","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:52:17.148266Z","iopub.execute_input":"2023-03-07T22:52:17.149055Z","iopub.status.idle":"2023-03-07T22:53:49.706359Z","shell.execute_reply.started":"2023-03-07T22:52:17.149019Z","shell.execute_reply":"2023-03-07T22:53:49.705205Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.evaluate(val_x, val_y)\nfor x,y in zip(val_x[:10], val_y[:10]):\n    print(f\"PRED: {decoder(np.argmax(model.predict(tf.expand_dims(x, axis=0), verbose=0), axis=-1)[0]):<20} – GT: {decoder(y)}\")","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:53:49.709766Z","iopub.execute_input":"2023-03-07T22:53:49.710508Z","iopub.status.idle":"2023-03-07T22:53:52.87692Z","shell.execute_reply.started":"2023-03-07T22:53:49.710458Z","shell.execute_reply":"2023-03-07T22:53:52.875573Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Generate TFLite Model and submission.zip","metadata":{}},{"cell_type":"code","source":"class TFLiteModel(tf.keras.Model):\n    \"\"\"\n    TensorFlow Lite model that takes input tensors and applies:\n        – a preprocessing model\n        – the ASL model \n    \"\"\"\n\n    def __init__(self, asl_model):\n        \"\"\"\n        Initializes the TFLiteModel with the specified feature generation model and main model.\n        \"\"\"\n        super(TFLiteModel, self).__init__()\n\n        # Load the feature generation and main models\n        self.prep_inputs = FeatureGen()\n        self.asl_model   = asl_model\n    \n    @tf.function(input_signature=[tf.TensorSpec(shape=[None, 543, 3], dtype=tf.float32, name='inputs')])\n    def call(self, inputs):\n        \"\"\"\n        Applies the feature generation model and main model to the input tensors.\n\n        Args:\n            inputs: Input tensor with shape [batch_size, 543, 3].\n\n        Returns:\n            A dictionary with a single key 'outputs' and corresponding output tensor.\n        \"\"\"\n        x = self.prep_inputs(tf.cast(inputs, dtype=tf.float32))\n        outputs = self.asl_model(x)[0, :]\n\n        # Return a dictionary with the output tensor\n        return {'outputs': outputs}\n\ntflite_keras_model = TFLiteModel(model)\ndemo_output = tflite_keras_model(load_relevant_data_subset(train_df.path[0]))[\"outputs\"]\ndecoder(np.argmax(demo_output.numpy(), axis=-1))","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:53:52.878603Z","iopub.execute_input":"2023-03-07T22:53:52.878985Z","iopub.status.idle":"2023-03-07T22:53:53.460474Z","shell.execute_reply.started":"2023-03-07T22:53:52.878946Z","shell.execute_reply":"2023-03-07T22:53:53.459188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tflite_keras_model.save(\"./models/final_model\")\nkeras_model_converter = tf.lite.TFLiteConverter.from_keras_model(tflite_keras_model)\ntflite_model = keras_model_converter.convert()\nwith open('/kaggle/working/models/model.tflite', 'wb') as f:\n    f.write(tflite_model)\n!zip submission.zip /kaggle/working/models/model.tflite\n\n!pip install tflite-runtime\nimport tflite_runtime.interpreter as tflite\n\ninterpreter = tflite.Interpreter(\"/kaggle/working/models/model.tflite\")\nfound_signatures = list(interpreter.get_signature_list().keys())\nprediction_fn = interpreter.get_signature_runner(\"serving_default\")\n\noutput = prediction_fn(inputs=load_relevant_data_subset(train_df.path[0]))\nsign = np.argmax(output[\"outputs\"])\n\nprint(\"PRED : \", decoder(sign))\nprint(\"GT   : \", train_df.sign[0])","metadata":{"execution":{"iopub.status.busy":"2023-03-07T22:53:53.465037Z","iopub.execute_input":"2023-03-07T22:53:53.467463Z","iopub.status.idle":"2023-03-07T22:54:14.981414Z","shell.execute_reply.started":"2023-03-07T22:53:53.467423Z","shell.execute_reply":"2023-03-07T22:54:14.979978Z"},"trusted":true},"execution_count":null,"outputs":[]}]}