{"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":"# 🔎 Dataset Description\nEl objetivo de esta competición es detectar y traducir el lenguaje de señas americano (ASL) a texto.\n\nEsta competencia requiere que las presentaciones se realicen en forma de modelos de TensorFlow Lite. Puedes entrenar tu modelo utilizando el marco de trabajo de tu elección siempre que conviertas el punto de control del modelo al formato tflite antes de la presentación. Consulta la página de evaluación para obtener más detalles.\n\n# 📄 Files\n\n## [train/supplemental_metadata].csv\n\n* `path` - La ruta al archivo de referencia.\n* `file_id` - Un identificador único para el archivo de datos.\n* `participant_id` - Un identificador único para el contribuyente de datos.\n* `sequence_id` - Un identificador único para la secuencia de referencia. Cada archivo de datos puede contener muchas secuencias.\n* `phrase` - Las etiquetas para la secuencia de referencia. Los conjuntos de datos de entrenamiento y prueba contienen **direcciones, números de teléfono y URL generados al azar, derivados de componentes de direcciones reales/números de teléfono/URL**. Cualquier coincidencia con direcciones reales, números de teléfono o URL es puramente accidental. **El conjunto de datos complementario consiste en oraciones deletreadas con los dedos**. Ten en cuenta que algunas de las URL incluyen contenido para adultos. El objetivo de esta competencia es apoyar a la comunidad de personas sordas y con discapacidad auditiva para que se involucren con la tecnología en igualdad de condiciones con otros adultos.\n\n## character_to_prediction_index.json\n\nUn diccionario en el que se asigna a distintos simbolos un número entero.\n\n## [train/supplemental]_landmarks/\n\nLos datos de referencia. Los puntos de referencia se extrajeron de videos en bruto con el modelo de MediaPipe. **No todos los fotogramas necesariamente tenían manos visibles o manos que pudieran ser detectadas por el modelo**.\nLos archivos de referencia contienen los mismos datos que en la competición de ASL Signs (excepto la columna de ID de fila) pero con una estructura amplia. **Esto te permite aprovechar el formato Parquet para omitir por completo la carga de puntos de referencia que no estás utilizando**.\n\n* `sequence_id` - Un identificador único para la secuencia de referencia. Los archivos de referencia contienen aproximadamente 1,000 secuencias. El ID de secuencia se utiliza como índice del dataframe.\n* `frame` - El número de fotograma dentro de una secuencia de referencia.\n* `[x/y/z]_[type]_[landmark_index]` - Ahora hay 1,629 columnas de coordenadas espaciales para las coordenadas x, y y z de cada uno de los 543 puntos de referencia. El tipo de punto de referencia puede ser uno de `['face', 'left_hand', 'pose', 'right_hand']`. Aquí se pueden encontrar detalles sobre las ubicaciones de los puntos de referencia de las manos. Las coordenadas espaciales ya han sido normalizadas por MediaPipe. Ten en cuenta que el modelo de MediaPipe no está completamente entrenado para predecir la profundidad, por lo que es posible que desees ignorar los valores de z. Los puntos de referencia se han convertido a float32.\n","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport seaborn as sn\nimport tensorflow as tf\n\nfrom tqdm.notebook import tqdm\nfrom sklearn.model_selection import train_test_split, GroupShuffleSplit\nimport Levenshtein as lev\n\nimport glob\nimport sys\nimport os\nimport math\nimport gc\nimport sys\nimport sklearn\nimport time\nimport json\n\n# TQDM Progress Bar With Pandas Apply Function\ntqdm.pandas()\n\n# Supress Warnings\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\nprint(f'Tensorflow Version {tf.__version__}')\nprint(f'Python Version: {sys.version}')\nprint(f'Levenshtein Version : {lev.__version__}')","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-07-03T08:11:50.531091Z","iopub.execute_input":"2023-07-03T08:11:50.531537Z","iopub.status.idle":"2023-07-03T08:11:50.542902Z","shell.execute_reply.started":"2023-07-03T08:11:50.531504Z","shell.execute_reply":"2023-07-03T08:11:50.54166Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Character_to_prediction_index\n\nConvertimos el diccionario en un dataframe de pandas","metadata":{}},{"cell_type":"markdown","source":"# Hyperparameters\n\nDefinimos los hiperparámetros que vamos a usar posteriormente\n\n- `IS_INTERACTIVE`: Esta variable indica si el cuaderno se está ejecutando en modo interactivo, lo que significa que se está ejecutando en un entorno donde se pueden realizar cambios y experimentar de manera interactiva. En este modo, puedes ejecutar celdas de código individualmente, modificar el código y ver los resultados inmediatamente. Esto es útil cuando estás desarrollando y depurando tu código, ya que te permite realizar cambios rápidos y observar los efectos de esos cambios en tiempo real.\n\n- `VERBOSE`: Esta variable determina el nivel de verbose durante el entrenamiento. Si `IS_INTERACTIVE` es `True`, se establece en `1`, lo que significa que se mostrará menos información durante el entrenamiento. Si es `False`, se establece en `2`, lo que significa que se mostrará más información durante el entrenamiento.\n\n- `SEED`: Esta es una semilla aleatoria global utilizada para reproducir los resultados. Al fijar una semilla, se garantiza que los resultados sean consistentes en diferentes ejecuciones.\n\n- `N_TARGET_FRAMES`: Esta variable indica el número de fotogramas a los que se redimensionarán las grabaciones. Esto se hace para **estandarizar la longitud de las secuencias de entrada**.\n\n- `DEBUG`: Esta variable indica si se está ejecutando en modo de depuración. Si es `True`, se ejecutará en modo de depuración, lo que puede implicar la ejecución de un subconjunto de los datos para una ejecución más rápida y para facilitar la depuración.\n\n- `N_UNIQUE_CHARACTERS0`: Esta variable representa el número de **caracteres únicos que se deben predecir en el modelo**. No incluye el token de padding (relleno), el token de inicio de oración y el token de fin de oración.\n\n- `N_UNIQUE_CHARACTERS`: Esta variable es similar a `N_UNIQUE_CHARACTERS0`, pero incluye el token de relleno, el token de inicio de oración y el token de fin de oración. Por lo tanto, representa el número total de clases que el modelo debe predecir.\n\n- `PAD_TOKEN`, `SOS_TOKEN` y `EOS_TOKEN`: Estas variables representan los índices de los tokens de padding, inicio de oración y fin de oración en el vocabulario. Se utilizan durante el procesamiento de los datos y la generación de las secuencias de texto objetivo.\n\n- `USE_VAL`: Esta variable indica si se debe usar el 10% de los datos como conjunto de validación durante el entrenamiento. Si es `True`, se separará una parte de los datos para validar el modelo durante el entrenamiento.\n\n- `BATCH_SIZE`: Esta variable indica el tamaño del lote utilizado durante el entrenamiento. Un tamaño de lote más grande puede acelerar el entrenamiento, pero también requiere más memoria.\n\n- `N_EPOCHS`: Esta variable indica el número de épocas a entrenar. Si `IS_INTERACTIVE` es `True`, se establece en `2`, lo que significa que se realizarán solo dos épocas durante la depuración. Si es `False`, se establece en 30, lo que indica que se realizarán 30 épocas durante el entrenamiento completo.\n\n- `N_WARMUP_EPOCHS`: Esta variable indica el número de épocas de calentamiento en el programador de la tasa de aprendizaje. El calentamiento es una técnica en la que la tasa de aprendizaje se incrementa gradualmente","metadata":{}},{"cell_type":"code","source":"# If Notebook Is Run By Committing or In Interactive Mode For Development\nIS_INTERACTIVE = os.environ['KAGGLE_KERNEL_RUN_TYPE'] == 'Interactive'\n# Verbose Setting during training\nVERBOSE = 1 if IS_INTERACTIVE else 2\n# Global Random Seed\nSEED = 42\n# Number of Frames to resize recording to\nN_TARGET_FRAMES = 128\n# Global debug flag, takes subset of train\nDEBUG = False\n# Whether to use 10% of data for validation\nUSE_VAL = True\n# Batch Size\nBATCH_SIZE = 128\n# Number of Epochs to Train for\nN_EPOCHS = 2 if IS_INTERACTIVE else 30\n# Number of Warmup Epochs in Learning Rate Scheduler\nN_WARMUP_EPOCHS = 0\n# Maximum Learning Rate\nLR_MAX = 1e-3\n# Weight Decay Ratio as Ratio of Learning Rate\nWD_RATIO = 0.05\n# Length of Phrase + EOS Token\nMAX_PHRASE_LENGTH = 31 + 1\n# Whether to Train The model\nTRAIN_MODEL = True\n# Whether to Load Pretrained Weights\nLOAD_WEIGHTS = False","metadata":{"collapsed":false,"ExecuteTime":{"start_time":"2023-06-30T11:02:29.959637Z","end_time":"2023-06-30T11:02:29.972515Z"},"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-07-03T08:11:50.545046Z","iopub.execute_input":"2023-07-03T08:11:50.546381Z","iopub.status.idle":"2023-07-03T08:11:50.564907Z","shell.execute_reply.started":"2023-07-03T08:11:50.546333Z","shell.execute_reply":"2023-07-03T08:11:50.56366Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Read Character to Ordinal Encoding Mapping\nwith open('/kaggle/input/asl-fingerspelling/character_to_prediction_index.json') as json_file:\n    CHAR2ORD = json.load(json_file)\n    \n# Number of Unique Characters To Predict + Pad Token + SOS Token + EOS Token\nN_UNIQUE_CHARACTERS0 = len(CHAR2ORD)\nN_UNIQUE_CHARACTERS = len(CHAR2ORD) + 1 + 1 + 1\nPAD_TOKEN = len(CHAR2ORD) # Padding # idx 59\nSTART_TOKEN = len(CHAR2ORD) + 1 # Start Of Sentence # idx 60\nEND_TOKEN = len(CHAR2ORD) + 2 # End Of Sentence # idx 61\n\n# assign tokens to the padding, start of the sentence and end of the sentence\nCHAR2ORD['P'] = PAD_TOKEN   # padding\nCHAR2ORD['S'] = START_TOKEN # start\nCHAR2ORD['E'] = END_TOKEN   # end\n\n# convert dictionary to pandas dataframe\nCHAR2ORD_DF = pd.DataFrame(CHAR2ORD.values(),index=CHAR2ORD.keys(),columns=['Ordinal Encoding'])\nCHAR2ORD_DF.head()","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-07-03T08:11:50.619931Z","iopub.execute_input":"2023-07-03T08:11:50.620944Z","iopub.status.idle":"2023-07-03T08:11:50.643011Z","shell.execute_reply.started":"2023-07-03T08:11:50.620888Z","shell.execute_reply":"2023-07-03T08:11:50.640779Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Preprocessing\n\nPreprocesaremos los datos de tipo *parquet* de cara al posterior modelizado. Para ello, necesitaremos conocer algunas nociones acerca de los datos proporcionados. El modelo *pose landmarker* rastrea 33 ubicaciones de referencia corporal, representando la ubicación aproximada de las siguientes partes del cuerpo:\n\n![Índices de los landmarks](http://developers.google.com/static/mediapipe/images/solutions/pose_landmarks_index.png)","metadata":{}},{"cell_type":"code","source":"import pyarrow.parquet as pq\n\n# Read parquet file\ntable = pq.read_table('/kaggle/input/asl-fingerspelling/supplemental_landmarks/1176508147.parquet')\n\n# Convert to pd dataframe\ndf = table.to_pandas()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:11:50.645256Z","iopub.execute_input":"2023-07-03T08:11:50.646651Z","iopub.status.idle":"2023-07-03T08:11:56.164093Z","shell.execute_reply.started":"2023-07-03T08:11:50.646599Z","shell.execute_reply":"2023-07-03T08:11:56.163164Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# z component of the variable pose ==> range(33)\ndf.iloc[:, -54:-21].head()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:11:56.166768Z","iopub.execute_input":"2023-07-03T08:11:56.167739Z","iopub.status.idle":"2023-07-03T08:11:56.218492Z","shell.execute_reply.started":"2023-07-03T08:11:56.1677Z","shell.execute_reply":"2023-07-03T08:11:56.216746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Como vemos, los índices del *pose* se encuentran en el rango `[0,32]`. En nuestro caso, como solo buscamos describir el lenguaje de gestos, tan solo usaremos los índices correspondientes a las manos. Para describir la mano izquierda `[13, 15, 17, 19, 21]` usaremos por tanto los índices y para la mano derecha `[14, 16, 18, 20, 22]`. Usaremos por tanto un modelo simplificado.\n\nPreprocesaremos los datos de tipo *parquet* de cara al posterior modelizado. En primer lugar, veamos la estructura del formato *parquet*:","metadata":{}},{"cell_type":"code","source":"# z component of the variable right hand ==> range(21)\ndf.iloc[:, -21:].head()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:11:56.222227Z","iopub.execute_input":"2023-07-03T08:11:56.223606Z","iopub.status.idle":"2023-07-03T08:11:56.271049Z","shell.execute_reply.started":"2023-07-03T08:11:56.223533Z","shell.execute_reply":"2023-07-03T08:11:56.269659Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Una vez aclarado el formato *parquet* y la simplificación que vamos a utilizar, procedemos con el preprocesado","metadata":{}},{"cell_type":"code","source":"# read train.csv\ninpdir = \"/kaggle/input/asl-fingerspelling\"\ndf = pd.read_csv(f'{inpdir}/train.csv')\n\ndf.head()","metadata":{"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2023-07-03T08:11:56.272712Z","iopub.execute_input":"2023-07-03T08:11:56.27305Z","iopub.status.idle":"2023-07-03T08:11:56.450073Z","shell.execute_reply.started":"2023-07-03T08:11:56.273021Z","shell.execute_reply":"2023-07-03T08:11:56.448593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Definimos las siguientes acciones:\n\n- Definimos la lista `LPOSE` que contiene los índices de las posiciones correspondientes a la mano izquierda en nuestro dataset.\n- Definimos la lista `RPOSE` que contiene los índices de las posiciones correspondientes a la mano derecha en nuestro dataset.\n- Combinamos las listas `LPOSE` y `RPOSE` en la lista `POSE`, que contiene todos los índices de las posiciones de las manos.\n- Definimos la lista `RHAND_LBLS` que contiene las etiquetas correspondientes a las coordenadas X, Y y Z de la mano derecha en nuestro dataset.\n- Definimos la lista `LHAND_LBLS` que contiene las etiquetas correspondientes a las coordenadas X, Y y Z de la mano izquierda en nuestro dataset.\n- Definimos la lista `POSE_LBLS` que contiene las etiquetas correspondientes a las coordenadas X, Y y Z de las posiciones de las manos en nuestro dataset.\n- Definimos la lista `X` que combina las etiquetas de coordenadas X de la mano derecha, la mano izquierda y las posiciones de las manos.\n- Definimos la lista `Y` que combina las etiquetas de coordenadas Y de la mano derecha, la mano izquierda y las posiciones de las manos.\n- Definimos la lista `Z` que combina las etiquetas de coordenadas Z de la mano derecha, la mano izquierda y las posiciones de las manos.\n- Definimos la lista `SEL_COLS` que combina las listas `X`, `Y` y `Z`, representando todas las columnas seleccionadas para nuestro análisis.\n- Definimos la variable `FRAME_LEN` con el valor 128, que indica la longitud de cada fotograma de datos.\n- Definimos la lista `X_IDX` que contiene los índices de las columnas en `SEL_COLS` que contienen \"x_\" en su nombre.\n- Definimos la lista `Y_IDX` que contiene los índices de las columnas en `SEL_COLS` que contienen \"y_\" en su nombre.\n- Definimos la lista `Z_IDX` que contiene los índices de las columnas en `SEL_COLS` que contienen \"z_\" en su nombre.\n- Definimos la lista `RHAND_IDX` que contiene los índices de las columnas en `SEL_COLS` que contienen \"right\" en su nombre, correspondiendo a las coordenadas de la mano derecha.\n- Definimos la lista `LHAND_IDX` que contiene los índices de las columnas en `SEL_COLS` que contienen \"left\" en su nombre, correspondiendo a las coordenadas de la mano izquierda.\n- Definimos la lista `RPOSE_IDX` que contiene los índices de las columnas en `SEL_COLS` que contienen \"pose\" en su nombre y su último dígito se encuentra en `RPOSE`, correspondiendo a las posiciones de la mano derecha.\n- Definimos la lista `LPOSE_IDX` que contiene los índices de las columnas en `SEL_COLS` que contienen \"pose\" en su nombre y su último dígito se encuentra en `LPOSE`, correspondiendo a las posiciones de la mano izquierda.","metadata":{}},{"cell_type":"code","source":"LPOSE = [13, 15, 17, 19, 21] # idx of the the left hand positions\nRPOSE = [14, 16, 18, 20, 22] # idx of the the right hand positions\nPOSE = LPOSE + RPOSE # all posible indexes (both hands)\n\n# all posibly labels for the right and left hand\n## hand labels:\nRHAND_LABELS = [f'x_right_hand_{i}' for i in range(21)] + [f'y_right_hand_{i}' for i in range(21)] + [f'z_right_hand_{i}' for i in range(21)]\nLHAND_LABELS = [ f'x_left_hand_{i}' for i in range(21)] + [ f'y_left_hand_{i}' for i in range(21)] + [ f'z_left_hand_{i}' for i in range(21)]\n## hand position labels:\nPOSE_LABELS = [f'x_pose_{i}' for i in POSE] + [f'y_pose_{i}' for i in POSE] + [f'z_pose_{i}' for i in POSE]\n\n# all posibly [x,y,z] labels\nX = [f'x_right_hand_{i}' for i in range(21)] + [f'x_left_hand_{i}' for i in range(21)] + [f'x_pose_{i}' for i in POSE]\nY = [f'y_right_hand_{i}' for i in range(21)] + [f'y_left_hand_{i}' for i in range(21)] + [f'y_pose_{i}' for i in POSE]\nZ = [f'z_right_hand_{i}' for i in range(21)] + [f'z_left_hand_{i}' for i in range(21)] + [f'z_pose_{i}' for i in POSE]\n\nSEL_COLS = X + Y + Z\nFRAME_LEN = 128\n\nX_IDX = [i for i, col in enumerate(SEL_COLS)  if \"x_\" in col]\nY_IDX = [i for i, col in enumerate(SEL_COLS)  if \"y_\" in col]\nZ_IDX = [i for i, col in enumerate(SEL_COLS)  if \"z_\" in col]\n\nRHAND_IDX = [i for i, col in enumerate(SEL_COLS)  if \"right\" in col]\nLHAND_IDX = [i for i, col in enumerate(SEL_COLS)  if  \"left\" in col]\nRPOSE_IDX = [i for i, col in enumerate(SEL_COLS)  if  \"pose\" in col and int(col[-2:]) in RPOSE]\nLPOSE_IDX = [i for i, col in enumerate(SEL_COLS)  if  \"pose\" in col and int(col[-2:]) in LPOSE]","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:11:56.451383Z","iopub.execute_input":"2023-07-03T08:11:56.451726Z","iopub.status.idle":"2023-07-03T08:11:56.469172Z","shell.execute_reply.started":"2023-07-03T08:11:56.451698Z","shell.execute_reply":"2023-07-03T08:11:56.467746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ahora, buscamos redimensionar la secuencia de datos de forma que esté estandarizado. Para ello, recurrimos a las funciones `resize_pad(x)` y `pre_process(x)`.\n\n1) La función `resize_pad(x)` se encarga de redimensionar y rellenar una secuencia de datos `x` para asegurarse de que tenga una longitud específica definida por `FRAME_LEN`.\n\n- Si la longitud de la secuencia `x` es menor que `FRAME_LEN`, se agrega relleno o *padding* a la secuencia mediante la función `tf.pad()` (se agregan ceros al final de la secuencia hasta que tenga la longitud deseada.)\n\n- Por otro lado, si la longitud de la secuencia `x` es mayor que `FRAME_LEN`, se realiza un redimensionamiento de la secuencia utilizando la función `tf.image.resize()`. Esto asegura que la secuencia se ajuste a la longitud deseada sin perder información importante.\n\n2) La función `pre_process(x)` es responsable de realizar el preprocesamiento de los datos de entrada `x` antes de su uso en un modelo.\n\n- La función `tf.gather` se utiliza para realizar una operación de extracción de elementos de un tensor en función de los índices proporcionados. Así pues, se seleccionan las columnas correspondientes a las coordenadas de la mano derecha, la mano izquierda, las posiciones de la mano derecha y las posiciones de la mano izquierda utilizando las variables `RHAND_IDX`, `LHAND_IDX`, `RPOSE_IDX` y `LPOSE_IDX`.\n\n- A continuación, se buscan los valores *NaN* en los datos de la mano derecha y la mano izquierda utilizando la función `tf.reduce_any(tf.math.is_nan())`. Esto devuelve un índice booleano (True/False) que indica si existe algún valor *NaN* en cada fila de los datos de la mano derecha y la mano izquierda. La función `tf.reduce_any` se utiliza para reducir un tensor a un único valor booleano que indica si se cumple al menos una condición. En este caso, se aplica a la expresión `tf.math.is_nan(rhand)`, que verifica si hay algún valor NaN (Not a Number) en el tensor rhand. Al especificar axis=1, se realiza la reducción a lo largo del eje de las columnas, lo que significa que se obtiene un valor booleano para cada fila del tensor. El resultado será True para una fila si al menos uno de los valores en esa fila es NaN, y False en caso contrario. \n\n- Luego, se cuentan los valores *NaN* utilizando la función `tf.math.count_nonzero()`, obteniendo así la cantidad de valores *NaN* en los datos de la mano derecha y la mano izquierda.\n\n- A continuación, se compara la cantidad de valores *NaN* en las manos derecha e izquierda para determinar cuál es la mano dominante. Si hay más valores NaN en la mano derecha, se asume que la mano izquierda es la dominante y se asignan los datos correspondientes a las variables `hand` y `pose`. En caso contrario, se asignan los datos de la mano derecha.\n\n- Crear el tensor hand con nuevas dimensiones: \n    * Se utiliza la función `tf.concat` para combinar las coordenadas `hand_x`, `hand_y` y `hand_z` a lo largo del último eje (el que creamos), creando así un nuevo tensor hand con una dimensión adicional.\n    * La función `tf.newaxis` se utiliza para agregar una nueva dimensión a cada coordenada. En el código, las coordenadas `hand_x`, `hand_y` y `hand_z` representan las tres dimensiones espaciales de la mano, y se desea combinarlas en un tensor tridimensional hand con forma `(batch_size, num_points, 3)`. Sin embargo, para usar `tf.concat`, los tensores deben tener la misma forma en todas las dimensiones, excepto en la dimensión a lo largo de la cual se desea concatenar. Al agregar un nuevo eje utilizando `[..., tf.newaxis]`, se expande la dimensión de las coordenadas individuales de `(batch_size, num_points) => (batch_size, num_points, 1)`. Esto permite que las coordenadas se concatenen correctamente a lo largo del último eje utilizando `tf.concat`, creando así el tensor tridimensional hand.\n- Normalizar los valores de hand: se calcula la media y la desviación estándar del tensor hand a lo largo del eje 1 utilizando las funciones `tf.math.reduce_mean` y `tf.math.reduce_std` respectivamente. Luego, se resta la media y se divide por la desviación estándar para normalizar los valores de hand.\n- Seleccionar las coordenadas de la posición de la mano: `pose_x`, `pose_y` y `pose_z` son tensores que contienen las coordenadas X, Y y Z de la posición de la mano respectivamente. Estas coordenadas se obtienen dividiendo el tensor pose en tres partes iguales en función de la longitud de `LPOSE_IDX` (índices de la posición).\n- Crear el tensor pose con nuevas dimensiones: se utiliza la función `tf.concat` de manera similar a como se hizo con hand para combinar las coordenadas `pose_x`, `pose_y` y `pose_z` en un nuevo tensor pose con una dimensión adicional.\n- Combinar hand y pose en un único tensor `x`: se utiliza la función `tf.concat` para combinar hand y pose a lo largo del segundo eje, creando así un nuevo tensor `x`.\n- Realizar un redimensionamiento y ajuste de tamaño en `x`: se aplica una función `resize_pad` al tensor `x` para ajustar su tamaño según algún criterio específico que no se muestra en el código proporcionado. La función `resize_pad` probablemente cambia el tamaño del tensor o agrega valores de relleno según algún requisito de dimensiones o formato.\n- Reemplazar los valores *NaN* por ceros: se utiliza la función `tf.where` para reemplazar cualquier valor NaN en el tensor x por cero, creando así un tensor modificado `x`.\n- Redimensionar `x`: finalmente, el tensor `x` se redimensiona utilizando la función `tf.reshape` para tener una forma específica de (`FRAME_LEN, len(LHAND_IDX) + len(LPOSE_IDX)`).","metadata":{}},{"cell_type":"code","source":"def resize_pad(x):\n    if tf.shape(x)[0] < FRAME_LEN:\n        # if its < then we use padding to resize it\n        x = tf.pad(x, ([[0, FRAME_LEN-tf.shape(x)[0]], [0, 0], [0, 0]]))\n    else:\n        # if its > we resize it\n        x = tf.image.resize(x, (FRAME_LEN, tf.shape(x)[1]))\n    return x\n\ndef pre_process(x):\n    # right and left hand indexes\n    rhand = tf.gather(x, RHAND_IDX, axis=1) # extract right hand indexes on the tensor 'x'\n    lhand = tf.gather(x, LHAND_IDX, axis=1)\n    # right and left hand position indexes\n    rpose = tf.gather(x, RPOSE_IDX, axis=1) # extract right hand position indexes on the tensor 'x'\n    lpose = tf.gather(x, LPOSE_IDX, axis=1)\n    \n    # search if this row got a NaN\n    rnan_idx = tf.reduce_any(tf.math.is_nan(rhand), axis=1) \n    lnan_idx = tf.reduce_any(tf.math.is_nan(lhand), axis=1)\n    \n    # count number of NaNs \n    rnans = tf.math.count_nonzero(rnan_idx)\n    lnans = tf.math.count_nonzero(lnan_idx)\n    \n    # For dominant hand\n    if rnans > lnans:\n        # if the number of NaNs if greater in one right hand then the dominant is the left one\n        # we assign the hand as the dominant one\n        hand = lhand\n        # positions then are the left positions\n        pose = lpose\n        \n        hand_x = hand[:, 0*(len(LHAND_IDX)//3) : 1*(len(LHAND_IDX)//3)]\n        hand_y = hand[:, 1*(len(LHAND_IDX)//3) : 2*(len(LHAND_IDX)//3)]\n        hand_z = hand[:, 2*(len(LHAND_IDX)//3) : 3*(len(LHAND_IDX)//3)]\n        hand = tf.concat([1-hand_x, hand_y, hand_z], axis=1) # this is to make it right handed\n        \n        pose_x = pose[:, 0*(len(LPOSE_IDX)//3) : 1*(len(LPOSE_IDX)//3)]\n        pose_y = pose[:, 1*(len(LPOSE_IDX)//3) : 2*(len(LPOSE_IDX)//3)]\n        pose_z = pose[:, 2*(len(LPOSE_IDX)//3) : 3*(len(LPOSE_IDX)//3)]\n        pose = tf.concat([1-pose_x, pose_y, pose_z], axis=1) # this is to make it right handed\n        \n        \n    else:\n        # if the number of NaNs if lower in one right hand then the dominant is the right one\n        hand = rhand\n        pose = rpose\n    \n    # select the coordinates of the hand \n    hand_x = hand[:, 0*(len(LHAND_IDX)//3) : 1*(len(LHAND_IDX)//3)]\n    hand_y = hand[:, 1*(len(LHAND_IDX)//3) : 2*(len(LHAND_IDX)//3)]\n    hand_z = hand[:, 2*(len(LHAND_IDX)//3) : 3*(len(LHAND_IDX)//3)]\n    #   create the hand in the coordinates x,y,z \n    hand = tf.concat([hand_x[..., tf.newaxis], hand_y[..., tf.newaxis], hand_z[..., tf.newaxis]], axis=-1)\n    #hand = tf.concat([hand_x, hand_y, hand_z], axis=1) # ESTO DA ERROR, ERA UNA PRUEBA\n    \n    mean = tf.math.reduce_mean(hand, axis=1)[:, tf.newaxis, :]\n    std = tf.math.reduce_std(hand, axis=1)[:, tf.newaxis, :]\n    hand = (hand - mean) / std\n    # select the coordinates of the position of the hand \n    pose_x = pose[:, 0*(len(LPOSE_IDX)//3) : 1*(len(LPOSE_IDX)//3)]\n    pose_y = pose[:, 1*(len(LPOSE_IDX)//3) : 2*(len(LPOSE_IDX)//3)]\n    pose_z = pose[:, 2*(len(LPOSE_IDX)//3) : 3*(len(LPOSE_IDX)//3)]\n    #  create position of the hand in the coordinates x,y,z \n    pose = tf.concat([pose_x[..., tf.newaxis], pose_y[..., tf.newaxis], pose_z[..., tf.newaxis]], axis=-1)\n    # pose = tf.concat([pose_x, pose_y, pose_z], axis=1) # ESTO DA ERROR, ERA UNA PRUEBA\n    # join the hand with the position\n    x = tf.concat([hand, pose], axis=1)\n    x = resize_pad(x)\n    \n    x = tf.where(tf.math.is_nan(x), tf.zeros_like(x), x)\n    x = tf.reshape(x, (FRAME_LEN, len(LHAND_IDX) + len(LPOSE_IDX)))\n    return x","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:15:24.006088Z","iopub.execute_input":"2023-07-03T08:15:24.006539Z","iopub.status.idle":"2023-07-03T08:15:24.029283Z","shell.execute_reply.started":"2023-07-03T08:15:24.006508Z","shell.execute_reply":"2023-07-03T08:15:24.02819Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ahora, una vez definida la función de preprocesado, procedemos a preparar el conjunto de datos para su posterior entrenamiento:\n\n1) Se define una tabla de búsqueda `(StaticHashTable)` utilizando la clase `tf.lookup.StaticHashTable`. Esta tabla se utiliza para mapear caracteres (keys) a valores numéricos (values). Se inicializa con un initializer que especifica las keys y values utilizando un `KeyValueTensorInitializer`. Además, se especifica un `default_value` que se utiliza cuando una key no tiene un valor asociado en la tabla.Podemos ver `tf.lookup.StaticHashTable` como un diccionario escalable en *TensorFlow*. Mientras que un diccionario convencional en Python puede funcionar bien para un número pequeño de claves y valores, cuando lidias con conjuntos de datos más grandes, la tabla de búsqueda proporciona una forma más eficiente de realizar asignaciones y búsquedas.\n\n2) Se define una función `preprocess_fn` que toma como entrada `landmarks` (marcas de referencia) y `phrase` (frase). La función realiza las siguientes operaciones:\n\n- Agrega un token de inicio `(start_token)` y un token de fin `(end_token)` a la frase (ya definidos anteriormente).\n- Divide la frase en bytes utilizando `tf.strings.bytes_split`.\n- Realiza una búsqueda en la tabla utilizando `table.lookup` para convertir los bytes de la frase en valores numéricos.\n- Rellena la frase con ceros (pad_token_idx) para que tenga una longitud de 64 elementos (todos tengan la misma longitud).\n- Llama a la función pre_process para preprocesar los landmarks.\n\n3) Se define una función `decode_fn` que toma un registro de bytes como entrada y realiza el proceso de decodificación.\n\n- Se define una variable llamada `schema` que especifica la estructura de los datos almacenados en un registro. En este caso, se utiliza un diccionario donde cada clave `(COL)` representa una columna de datos y su valor asociado es un objeto `VarLenFeature` que especifica que los datos en esa columna son de tipo *float32* y tienen una longitud variable.\n- Se agrega una entrada adicional al `schema` llamada `\"phrase\"`, que se espera que sea una cadena de texto y tiene una longitud fija de tamaño 0 (es decir, un solo elemento).\n- La función `parse_single_example` se usa para decodificar un registro almacenado en el formato *TFRecord*. La función recibe dos argumentos: `record_bytes`, que representa el registro en forma de bytes, y `schema`, que define la estructura de los datos en el registro. La función `parse_single_example` utiliza`schema` para interpretar y extraer los datos del registro `record_bytes`. Después de llamar a `parse_single_example`, se obtiene un diccionario llamado `features` que contiene los datos decodificados del registro. Cada clave en features corresponde a una característica definida en el schema, y los valores asociados son los datos decodificados para esas características.\n- Los datos de las columnas restantes se extraen utilizando una comprensión de lista. Se itera sobre las claves `(COL)` en `SEL_COLS` y se utiliza `tf.sparse.to_dense` para convertir los datos dispersos en una representación densa. Estos datos se almacenan en una lista.\n- Finalmente, se transponen los datos almacenados en la lista `landmarks` para que las filas representen las instancias de datos y las columnas representen las características.\n- La función retorna los datos de `landmarks` (marcas de referencia) y la frase, que se utilizarán más adelante en el proceso de preprocesamiento y entrenamiento del modelo.","metadata":{}},{"cell_type":"code","source":"table = tf.lookup.StaticHashTable(\n    initializer=tf.lookup.KeyValueTensorInitializer(\n        keys=list(CHAR2ORD.keys()),\n        values=list(CHAR2ORD.values()),\n    ),\n    default_value=tf.constant(-1),\n    name=\"class_weight\"\n)\n\ndef preprocess_fn(landmarks, phrase):\n    # normalize prhase length\n    phrase = 'S' + phrase + 'E' # add start token and end token\n    phrase = tf.strings.bytes_split(phrase) # split into bytes\n    phrase = table.lookup(phrase) # convert into numeric using StaticHashTable\n    phrase = tf.pad(phrase, paddings=[[0, 64 - tf.shape(phrase)[0]]], mode = 'CONSTANT',\n                    constant_values = PAD_TOKEN) # normalize\n    return pre_process(landmarks), phrase\n\ndef decode_fn(record_bytes):\n    # decodify\n    schema = {COL: tf.io.VarLenFeature(dtype=tf.float32) for COL in SEL_COLS} # set data to be float32 \n    schema[\"phrase\"] = tf.io.FixedLenFeature([], dtype=tf.string) # fixed length \n    features = tf.io.parse_single_example(record_bytes, schema) # transform record_bytes into tfrecord using fixed schema\n    phrase = features[\"phrase\"] \n    landmarks = ([tf.sparse.to_dense(features[COL]) for COL in SEL_COLS]) # select the rest of the features\n    landmarks = tf.transpose(landmarks)\n    \n    return landmarks, phrase","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:14:20.895751Z","iopub.execute_input":"2023-07-03T08:14:20.896133Z","iopub.status.idle":"2023-07-03T08:14:20.912073Z","shell.execute_reply.started":"2023-07-03T08:14:20.896103Z","shell.execute_reply":"2023-07-03T08:14:20.910527Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- Se crea una lista de rutas completas a los archivos *TFRecord* correspondientes a cada valor único de `file_id`.\n- La lista de rutas se convierte en un conjunto utilizando el método `unique()`, lo que elimina cualquier duplicado que pueda existir.\n- Se define la variable `batch_size` con un valor de 32, que representa el tamaño de cada lote de datos que se utilizará durante el entrenamiento.\n- Se crean los conjuntos de datos de entrenamiento `(train_dataset)` y validación `(val_dataset)`  utilizando la clase `TFRecordDataset` de *TensorFlow*. Se pasan como argumento las rutas de los archivos *TFRecord* correspondientes a los valores a partir del índice `val_len` en la lista `tffiles`. Luego, se aplica la función de decodificación `(decode_fn)` y la función de preprocesamiento `(preprocess_fn)` . Después, los datos se mezclan aleatoriamente utilizando `shuffle`, con un tamaño de buffer de 30000 y con el parámetro `reshuffle_each_iteration establecido` en True. A continuación, los datos se agrupan en lotes utilizando batch con un tamaño igual a batch_size. Por último, se utiliza `prefetch` para cargar de forma eficiente los dabtos en el modelo, con un tamaño de buffer configurado automáticamente por TensorFlow mediante `tf.data.AUTOTUNE.`","metadata":{}},{"cell_type":"code","source":"inpdir = \"/kaggle/input/aslfr-parquets-to-tfrecords-cleaned\"\ntffiles = df['file_id'].map(lambda x: f'{inpdir}/tfds/{x}.tfrecord').unique() # number of files tfrecord\n\nBATCH_SIZE = 32\nval_len = int(0.05 * len(tffiles)) # length of the validation dataset\n\ntrain_dataset = tf.data.TFRecordDataset(tffiles[val_len:]).map(decode_fn).map(preprocess_fn).shuffle(30000, reshuffle_each_iteration=True).batch(BATCH_SIZE).prefetch(buffer_size=tf.data.AUTOTUNE)\nval_dataset = tf.data.TFRecordDataset(tffiles[:val_len]).map(decode_fn).map(preprocess_fn).batch(BATCH_SIZE).prefetch(buffer_size=tf.data.AUTOTUNE)","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:15:27.125265Z","iopub.execute_input":"2023-07-03T08:15:27.125798Z","iopub.status.idle":"2023-07-03T08:15:29.087356Z","shell.execute_reply.started":"2023-07-03T08:15:27.125751Z","shell.execute_reply":"2023-07-03T08:15:29.086222Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataset","metadata":{"execution":{"iopub.status.busy":"2023-07-03T08:21:59.32966Z","iopub.execute_input":"2023-07-03T08:21:59.330187Z","iopub.status.idle":"2023-07-03T08:21:59.338974Z","shell.execute_reply.started":"2023-07-03T08:21:59.330142Z","shell.execute_reply":"2023-07-03T08:21:59.338098Z"},"trusted":true},"execution_count":null,"outputs":[]}]}