{
  "id": 406434,
  "title": "10th place solution",
  "url": "/competitions/asl-signs/writeups/nazarko99-10th-place-solution",
  "author_name": "",
  "post_date": "2023-05-02T10:23:49.590Z",
  "votes": 18,
  "comment_count": 3,
  "views": 0,
  "content": "<blockquote>\n  <p>First, I would like to thank the Armed Forces of Ukraine, Security Service of Ukraine, Defence Intelligence of Ukraine, State Emergency Service of Ukraine for providing safety and security to participate in this great competition, complete this work, and help science, technology, and business not stop and move forward.</p>\n</blockquote>\n<p>TLDR: Transformer models with embedding based on Linear layers for pose,lips,eye, and STGCN + Linear layer for target hand</p>\n<p><strong>Preprocessing</strong><br>\nFind target hand using nan in time dimension, flip if need, select poses where target hand is not nan, resize to 16 in time dimension, and finally normalize with</p>\n<pre><code>xyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(,))) / np.nanstd(xyz[:,FACE,:],axis=(,))\n</code></pre>\n<p><strong>Modeling</strong><br>\n(All validation scores are based on <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">@hengck23</a> split on participant id)<br>\nAfter a stuck at 0.75 lb (~0.7 validation), I understand that I need to change strategy.I go back and start modelling only with target hand. After finding best normalization strategy using face I get ~0.69 val score with only hand.<br>\nThen I tried to add pose,lips,eyes I get ~0.71 val score. So for me was clear that most of the signal in dominant hand, so i tried to get different representation and ensemble it. Firstly I try to plot  target hand and then run 2.5d CNN or TransformerCNN based models, and get with this 0.6 val score only with hand. Then I tried to run  STGCN(Spatio-Temporal Graph Convolutional Networks) for target hand and get ~0.66 score. Because CNN gets too much time to process I decide to drop it. So finally after ensembling pose,lip,eye,hand,STGCN(hand) -&gt; attention block i get ~0.72 score</p>\n<pre><code>x = torch.cat([\n            self.pose_emb(x[:,:,:,:].view(B,L,-), x_mask)[],\n            self.lip_emb(x[:,:,:+,:].view(B,L,-), x_mask)[],\n            self.reye_emb(x[:, :, +       :++, :].view(B, L, -), x_mask)[],\n            self.leye_emb(x[:, :, ++    :+++, :].view(B, L, -), x_mask)[],\n            self.hand_emb(x[:, :, +++ :++++, :].view(B, L, -), x_mask)[],\n            self.graph_emb(x[:, :,+++:++++, :].permute(, , , ).unsqueeze(-)),\n            self.pos_embed[:L, :].unsqueeze().repeat(B,,)],axis=-)\n</code></pre>\n<p>Example how i ensemble it.</p>\n<p>Then I started to add different augmentations (Here i have to spend definitely more time to increase my score even more), Most of the boost i get from :</p>\n<ul>\n<li>Mixup</li>\n</ul>\n<pre><code> np.random.random() &lt; self.args.mixup:\n                    indices = torch.randperm(batch[].size(), device=batch[].device, dtype=torch.long)\n                    beta = np.random.beta(, )\n                    batch[] = beta * batch[] + ( - beta) * batch[][indices]\n                    batch = self.run_logits(batch)\n                    batch[] = beta * self.args.loss(logits=batch[], labels=batch[]) + (\n                                 - beta) * self.args.loss(logits=batch[],\n                                                           labels=batch[][indices])\n</code></pre>\n<ul>\n<li>Copy paste</li>\n</ul>\n<pre><code> np.random.random() &lt; self.args.cutout:\n                indices_to_shuffle = torch.randperm(batch[].size(), device=batch[].device, dtype=torch.long)\n                cutout_len = self.args.cutout_len\n                 (cutout_len) == :\n                    cutout_len = random.uniform(*cutout_len)\n                sz = (batch[].size() * cutout_len)\n                ind = np.random.randint(, batch[].size() - sz)\n                indices_to_cut = torch.(ind, ind+sz, device=batch[].device, dtype=torch.long)\n                beta =  - cutout_len\n                batch[][:, indices_to_cut] = batch[][indices_to_shuffle][:,indices_to_cut]\n                batch[][:, indices_to_cut] = batch[][indices_to_shuffle][:,indices_to_cut]\n\n                batch = self.run_logits(batch)\n                batch[] = beta * self.args.loss(logits=batch[], labels=batch[]) + (\n                         - beta) * self.args.loss(logits=batch[],\n                                                   labels=batch[][indices_to_shuffle])\n</code></pre>\n<p>After that i get final score ~0.735-0.74 val score and 0.77 lb single model, 0.79 lb ensemble</p>\n<p><strong>What didn't work :</strong></p>\n<ul>\n<li>CNN on dominant hand, didn't get a lot of boost from it.</li>\n<li>Different losses based on embedding for words.</li>\n<li>Funny idea: Train model with arcface label : (pariticipant_id+sign nearly 5230 labels for full train) , get improvement in val score ~0.02 for val split based on label, because in lb we have samples from train too, i tried to add it to ensemble, but dont get any significant boost so decided to drop it.</li>\n</ul>\n<p>One more time want to say thanks for every one who support Ukraine in such a hard times.<br>\nSlava Ukraini.</p>",
  "messages": [
    {
      "id": "2242483",
      "postDate": "05/02/2023 10:13:31",
      "content": "<blockquote>\n  <p>First, I would like to thank the Armed Forces of Ukraine, Security Service of Ukraine, Defence Intelligence of Ukraine, State Emergency Service of Ukraine for providing safety and security to participate in this great competition, complete this work, and help science, technology, and business not stop and move forward.</p>\n</blockquote>\n<p>TLDR: Transformer models with embedding based on Linear layers for pose,lips,eye, and STGCN + Linear layer for target hand</p>\n<p><strong>Preprocessing</strong><br>\nFind target hand using nan in time dimension, flip if need, select poses where target hand is not nan, resize to 16 in time dimension, and finally normalize with</p>\n<pre><code>xyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(,))) / np.nanstd(xyz[:,FACE,:],axis=(,))\n</code></pre>\n<p><strong>Modeling</strong><br>\n(All validation scores are based on <a href=\"https://www.kaggle.com/hengck23\" target=\"_blank\">@hengck23</a> split on participant id)<br>\nAfter a stuck at 0.75 lb (~0.7 validation), I understand that I need to change strategy.I go back and start modelling only with target hand. After finding best normalization strategy using face I get ~0.69 val score with only hand.<br>\nThen I tried to add pose,lips,eyes I get ~0.71 val score. So for me was clear that most of the signal in dominant hand, so i tried to get different representation and ensemble it. Firstly I try to plot  target hand and then run 2.5d CNN or TransformerCNN based models, and get with this 0.6 val score only with hand. Then I tried to run  STGCN(Spatio-Temporal Graph Convolutional Networks) for target hand and get ~0.66 score. Because CNN gets too much time to process I decide to drop it. So finally after ensembling pose,lip,eye,hand,STGCN(hand) -&gt; attention block i get ~0.72 score</p>\n<pre><code>x = torch.cat([\n            self.pose_emb(x[:,:,:,:].view(B,L,-), x_mask)[],\n            self.lip_emb(x[:,:,:+,:].view(B,L,-), x_mask)[],\n            self.reye_emb(x[:, :, +       :++, :].view(B, L, -), x_mask)[],\n            self.leye_emb(x[:, :, ++    :+++, :].view(B, L, -), x_mask)[],\n            self.hand_emb(x[:, :, +++ :++++, :].view(B, L, -), x_mask)[],\n            self.graph_emb(x[:, :,+++:++++, :].permute(, , , ).unsqueeze(-)),\n            self.pos_embed[:L, :].unsqueeze().repeat(B,,)],axis=-)\n</code></pre>\n<p>Example how i ensemble it.</p>\n<p>Then I started to add different augmentations (Here i have to spend definitely more time to increase my score even more), Most of the boost i get from :</p>\n<ul>\n<li>Mixup</li>\n</ul>\n<pre><code> np.random.random() &lt; self.args.mixup:\n                    indices = torch.randperm(batch[].size(), device=batch[].device, dtype=torch.long)\n                    beta = np.random.beta(, )\n                    batch[] = beta * batch[] + ( - beta) * batch[][indices]\n                    batch = self.run_logits(batch)\n                    batch[] = beta * self.args.loss(logits=batch[], labels=batch[]) + (\n                                 - beta) * self.args.loss(logits=batch[],\n                                                           labels=batch[][indices])\n</code></pre>\n<ul>\n<li>Copy paste</li>\n</ul>\n<pre><code> np.random.random() &lt; self.args.cutout:\n                indices_to_shuffle = torch.randperm(batch[].size(), device=batch[].device, dtype=torch.long)\n                cutout_len = self.args.cutout_len\n                 (cutout_len) == :\n                    cutout_len = random.uniform(*cutout_len)\n                sz = (batch[].size() * cutout_len)\n                ind = np.random.randint(, batch[].size() - sz)\n                indices_to_cut = torch.(ind, ind+sz, device=batch[].device, dtype=torch.long)\n                beta =  - cutout_len\n                batch[][:, indices_to_cut] = batch[][indices_to_shuffle][:,indices_to_cut]\n                batch[][:, indices_to_cut] = batch[][indices_to_shuffle][:,indices_to_cut]\n\n                batch = self.run_logits(batch)\n                batch[] = beta * self.args.loss(logits=batch[], labels=batch[]) + (\n                         - beta) * self.args.loss(logits=batch[],\n                                                   labels=batch[][indices_to_shuffle])\n</code></pre>\n<p>After that i get final score ~0.735-0.74 val score and 0.77 lb single model, 0.79 lb ensemble</p>\n<p><strong>What didn't work :</strong></p>\n<ul>\n<li>CNN on dominant hand, didn't get a lot of boost from it.</li>\n<li>Different losses based on embedding for words.</li>\n<li>Funny idea: Train model with arcface label : (pariticipant_id+sign nearly 5230 labels for full train) , get improvement in val score ~0.02 for val split based on label, because in lb we have samples from train too, i tried to add it to ensemble, but dont get any significant boost so decided to drop it.</li>\n</ul>\n<p>One more time want to say thanks for every one who support Ukraine in such a hard times.<br>\nSlava Ukraini.</p>",
      "rawMarkdown": ">First, I would like to thank the Armed Forces of Ukraine, Security Service of Ukraine, Defence Intelligence of Ukraine, State Emergency Service of Ukraine for providing safety and security to participate in this great competition, complete this work, and help science, technology, and business not stop and move forward.\n\nTLDR: Transformer models with embedding based on Linear layers for pose,lips,eye, and STGCN + Linear layer for target hand\n\n**Preprocessing**\nFind target hand using nan in time dimension, flip if need, select poses where target hand is not nan, resize to 16 in time dimension, and finally normalize with\n ```python\nxyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(0,1))) / np.nanstd(xyz[:,FACE,:],axis=(0,1))\n```\n**Modeling**\n(All validation scores are based on @hengck23 split on participant id)\nAfter a stuck at 0.75 lb (~0.7 validation), I understand that I need to change strategy.I go back and start modelling only with target hand. After finding best normalization strategy using face I get ~0.69 val score with only hand.\nThen I tried to add pose,lips,eyes I get ~0.71 val score. So for me was clear that most of the signal in dominant hand, so i tried to get different representation and ensemble it. Firstly I try to plot  target hand and then run 2.5d CNN or TransformerCNN based models, and get with this 0.6 val score only with hand. Then I tried to run  STGCN(Spatio-Temporal Graph Convolutional Networks) for target hand and get ~0.66 score. Because CNN gets too much time to process I decide to drop it. So finally after ensembling pose,lip,eye,hand,STGCN(hand) -> attention block i get ~0.72 score\n```python\nx = torch.cat([\n            self.pose_emb(x[:,:,:8,:].view(B,L,-1), x_mask)[0],\n            self.lip_emb(x[:,:,8:20+8,:].view(B,L,-1), x_mask)[0],\n            self.reye_emb(x[:, :, 20+8       :20+8+16, :].view(B, L, -1), x_mask)[0],\n            self.leye_emb(x[:, :, 20+8+16    :20+8+16+16, :].view(B, L, -1), x_mask)[0],\n            self.hand_emb(x[:, :, 20+8+16+16 :20+8+16+16+21, :].view(B, L, -1), x_mask)[0],\n            self.graph_emb(x[:, :,20+8+16+16:20+8+16+16+21, :].permute(0, 3, 1, 2).unsqueeze(-1)),\n            self.pos_embed[:L, :].unsqueeze(0).repeat(B,1,1)],axis=-1)\n```\nExample how i ensemble it.\n\nThen I started to add different augmentations (Here i have to spend definitely more time to increase my score even more), Most of the boost i get from :\n* Mixup\n```python\nif np.random.random() < self.args.mixup:\n                    indices = torch.randperm(batch[\"xyz\"].size(0), device=batch[\"xyz\"].device, dtype=torch.long)\n                    beta = np.random.beta(0.2, 0.2)\n                    batch[\"xyz\"] = beta * batch[\"xyz\"] + (1 - beta) * batch[\"xyz\"][indices]\n                    batch = self.run_logits(batch)\n                    batch[\"loss\"] = beta * self.args.loss(logits=batch[\"logits\"], labels=batch[\"labels\"]) + (\n                                1 - beta) * self.args.loss(logits=batch[\"logits\"],\n                                                           labels=batch[\"labels\"][indices])\n```\n* Copy paste\n```python\nif np.random.random() < self.args.cutout:\n                indices_to_shuffle = torch.randperm(batch[\"xyz\"].size(0), device=batch[\"xyz\"].device, dtype=torch.long)\n                cutout_len = self.args.cutout_len\n                if type(cutout_len) == list:\n                    cutout_len = random.uniform(*cutout_len)\n                sz = int(batch[\"xyz\"].size(1) * cutout_len)\n                ind = np.random.randint(0, batch[\"xyz\"].size(1) - sz)\n                indices_to_cut = torch.range(ind, ind+sz, device=batch[\"xyz\"].device, dtype=torch.long)\n                beta = 1 - cutout_len\n                batch[\"xyz\"][:, indices_to_cut] = batch[\"xyz\"][indices_to_shuffle][:,indices_to_cut]\n                batch[\"mask\"][:, indices_to_cut] = batch[\"mask\"][indices_to_shuffle][:,indices_to_cut]\n\n                batch = self.run_logits(batch)\n                batch[\"loss\"] = beta * self.args.loss(logits=batch[\"logits\"], labels=batch[\"labels\"]) + (\n                        1 - beta) * self.args.loss(logits=batch[\"logits\"],\n                                                   labels=batch[\"labels\"][indices_to_shuffle])\n```\n\nAfter that i get final score ~0.735-0.74 val score and 0.77 lb single model, 0.79 lb ensemble\n\n**What didn't work :**\n * CNN on dominant hand, didn't get a lot of boost from it.\n * Different losses based on embedding for words.\n * Funny idea: Train model with arcface label : (pariticipant_id+sign nearly 5230 labels for full train) , get improvement in val score ~0.02 for val split based on label, because in lb we have samples from train too, i tried to add it to ensemble, but dont get any significant boost so decided to drop it.\n\nOne more time want to say thanks for every one who support Ukraine in such a hard times.\nSlava Ukraini.",
      "votes": null
    },
    {
      "id": "2242516",
      "postDate": "05/02/2023 10:33:25",
      "content": "<p>Very nice solution !! For the below I understand you are getting time and feature-wise non-nan mean. Would you mind to share how you coded that for tflite; was it rewritten in tensorflow? Time wise non-nan mean kept erroring for me when I submitted. </p>\n<p><code>xyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(0,1))) / np.nanstd(xyz[:,FACE,:],axis=(0,1))</code></p>",
      "rawMarkdown": "Very nice solution !! For the below I understand you are getting time and feature-wise non-nan mean. Would you mind to share how you coded that for tflite; was it rewritten in tensorflow? Time wise non-nan mean kept erroring for me when I submitted. \n    \n`xyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(0,1))) / np.nanstd(xyz[:,FACE,:],axis=(0,1))`",
      "votes": null
    },
    {
      "id": "2242519",
      "postDate": "05/02/2023 10:35:39",
      "content": "<pre><code>X = (xyz[:,:,]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,]))\nY = (xyz[:,:,]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,]))\nxyz = tf.stack([X,Y],axis=-)\n</code></pre>",
      "rawMarkdown": "```python\nX = (xyz[:,:,0]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,0], FACE_NAN[:,:,0])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,0], FACE_NAN[:,:,0]))\nY = (xyz[:,:,1]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,1], FACE_NAN[:,:,1])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,1], FACE_NAN[:,:,1]))\nxyz = tf.stack([X,Y],axis=-1)\n```",
      "votes": null
    },
    {
      "id": "2242539",
      "postDate": "05/02/2023 10:48:35",
      "content": "<p>Thank you! </p>",
      "rawMarkdown": "Thank you!",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 2242516,
      "author_name": "darraghdog",
      "author_url": "",
      "post_date": "05/02/2023 10:33:25",
      "content": "<p>Very nice solution !! For the below I understand you are getting time and feature-wise non-nan mean. Would you mind to share how you coded that for tflite; was it rewritten in tensorflow? Time wise non-nan mean kept erroring for me when I submitted. </p>\n<p><code>xyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(0,1))) / np.nanstd(xyz[:,FACE,:],axis=(0,1))</code></p>",
      "votes": null,
      "replies": [
        {
          "id": 2242519,
          "author_name": "nazarko99",
          "author_url": "",
          "post_date": "05/02/2023 10:35:39",
          "content": "<pre><code>X = (xyz[:,:,]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,]))\nY = (xyz[:,:,]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,], FACE_NAN[:,:,]))\nxyz = tf.stack([X,Y],axis=-)\n</code></pre>",
          "votes": null,
          "replies": [
            {
              "id": 2242539,
              "author_name": "darraghdog",
              "author_url": "",
              "post_date": "05/02/2023 10:48:35",
              "content": "<p>Thank you! </p>",
              "votes": null,
              "replies": []
            }
          ]
        }
      ]
    }
  ],
  "raw_markdown_by_id": {
    "2242483": ">First, I would like to thank the Armed Forces of Ukraine, Security Service of Ukraine, Defence Intelligence of Ukraine, State Emergency Service of Ukraine for providing safety and security to participate in this great competition, complete this work, and help science, technology, and business not stop and move forward.\n\nTLDR: Transformer models with embedding based on Linear layers for pose,lips,eye, and STGCN + Linear layer for target hand\n\n**Preprocessing**\nFind target hand using nan in time dimension, flip if need, select poses where target hand is not nan, resize to 16 in time dimension, and finally normalize with\n ```python\nxyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(0,1))) / np.nanstd(xyz[:,FACE,:],axis=(0,1))\n```\n**Modeling**\n(All validation scores are based on @hengck23 split on participant id)\nAfter a stuck at 0.75 lb (~0.7 validation), I understand that I need to change strategy.I go back and start modelling only with target hand. After finding best normalization strategy using face I get ~0.69 val score with only hand.\nThen I tried to add pose,lips,eyes I get ~0.71 val score. So for me was clear that most of the signal in dominant hand, so i tried to get different representation and ensemble it. Firstly I try to plot  target hand and then run 2.5d CNN or TransformerCNN based models, and get with this 0.6 val score only with hand. Then I tried to run  STGCN(Spatio-Temporal Graph Convolutional Networks) for target hand and get ~0.66 score. Because CNN gets too much time to process I decide to drop it. So finally after ensembling pose,lip,eye,hand,STGCN(hand) -> attention block i get ~0.72 score\n```python\nx = torch.cat([\n            self.pose_emb(x[:,:,:8,:].view(B,L,-1), x_mask)[0],\n            self.lip_emb(x[:,:,8:20+8,:].view(B,L,-1), x_mask)[0],\n            self.reye_emb(x[:, :, 20+8       :20+8+16, :].view(B, L, -1), x_mask)[0],\n            self.leye_emb(x[:, :, 20+8+16    :20+8+16+16, :].view(B, L, -1), x_mask)[0],\n            self.hand_emb(x[:, :, 20+8+16+16 :20+8+16+16+21, :].view(B, L, -1), x_mask)[0],\n            self.graph_emb(x[:, :,20+8+16+16:20+8+16+16+21, :].permute(0, 3, 1, 2).unsqueeze(-1)),\n            self.pos_embed[:L, :].unsqueeze(0).repeat(B,1,1)],axis=-1)\n```\nExample how i ensemble it.\n\nThen I started to add different augmentations (Here i have to spend definitely more time to increase my score even more), Most of the boost i get from :\n* Mixup\n```python\nif np.random.random() < self.args.mixup:\n                    indices = torch.randperm(batch[\"xyz\"].size(0), device=batch[\"xyz\"].device, dtype=torch.long)\n                    beta = np.random.beta(0.2, 0.2)\n                    batch[\"xyz\"] = beta * batch[\"xyz\"] + (1 - beta) * batch[\"xyz\"][indices]\n                    batch = self.run_logits(batch)\n                    batch[\"loss\"] = beta * self.args.loss(logits=batch[\"logits\"], labels=batch[\"labels\"]) + (\n                                1 - beta) * self.args.loss(logits=batch[\"logits\"],\n                                                           labels=batch[\"labels\"][indices])\n```\n* Copy paste\n```python\nif np.random.random() < self.args.cutout:\n                indices_to_shuffle = torch.randperm(batch[\"xyz\"].size(0), device=batch[\"xyz\"].device, dtype=torch.long)\n                cutout_len = self.args.cutout_len\n                if type(cutout_len) == list:\n                    cutout_len = random.uniform(*cutout_len)\n                sz = int(batch[\"xyz\"].size(1) * cutout_len)\n                ind = np.random.randint(0, batch[\"xyz\"].size(1) - sz)\n                indices_to_cut = torch.range(ind, ind+sz, device=batch[\"xyz\"].device, dtype=torch.long)\n                beta = 1 - cutout_len\n                batch[\"xyz\"][:, indices_to_cut] = batch[\"xyz\"][indices_to_shuffle][:,indices_to_cut]\n                batch[\"mask\"][:, indices_to_cut] = batch[\"mask\"][indices_to_shuffle][:,indices_to_cut]\n\n                batch = self.run_logits(batch)\n                batch[\"loss\"] = beta * self.args.loss(logits=batch[\"logits\"], labels=batch[\"labels\"]) + (\n                        1 - beta) * self.args.loss(logits=batch[\"logits\"],\n                                                   labels=batch[\"labels\"][indices_to_shuffle])\n```\n\nAfter that i get final score ~0.735-0.74 val score and 0.77 lb single model, 0.79 lb ensemble\n\n**What didn't work :**\n * CNN on dominant hand, didn't get a lot of boost from it.\n * Different losses based on embedding for words.\n * Funny idea: Train model with arcface label : (pariticipant_id+sign nearly 5230 labels for full train) , get improvement in val score ~0.02 for val split based on label, because in lb we have samples from train too, i tried to add it to ensemble, but dont get any significant boost so decided to drop it.\n\nOne more time want to say thanks for every one who support Ukraine in such a hard times.\nSlava Ukraini.",
    "2242516": "Very nice solution !! For the below I understand you are getting time and feature-wise non-nan mean. Would you mind to share how you coded that for tflite; was it rewritten in tensorflow? Time wise non-nan mean kept erroring for me when I submitted. \n    \n`xyz = (xyz - np.nanmean(xyz[:,FACE,:],axis(0,1))) / np.nanstd(xyz[:,FACE,:],axis=(0,1))`",
    "2242519": "```python\nX = (xyz[:,:,0]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,0], FACE_NAN[:,:,0])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,0], FACE_NAN[:,:,0]))\nY = (xyz[:,:,1]-tf.math.reduce_mean(tf.boolean_mask(FACE[:,:,1], FACE_NAN[:,:,1])))/tf.math.reduce_std(tf.boolean_mask(FACE[:,:,1], FACE_NAN[:,:,1]))\nxyz = tf.stack([X,Y],axis=-1)\n```",
    "2242539": "Thank you!"
  },
  "source": "meta"
}