{
  "id": 122160,
  "title": "5th place Solution: Pseudo-Labeling + Dig-MNIST training",
  "content": "<p>Thanks a lot to Kaggle and @higgstachyon for hosting this competition, it's such a great experience for me to learn and acquire knowledge from other competitors, congratulations to all the winners!</p>\n\n<h2>The overall approach:</h2>\n\n<p><strong>CNN (Convolutional Neural Network)</strong></p>\n\n<h2>What worked for me:</h2>\n\n<p><strong>1. 5-fold cross-validation, average the results (or majority vote)</strong></p>\n\n<p><strong>2. Image augmentation:</strong>\n        translation, rotation, shearing and scaling</p>\n\n<p><strong>3. CNN with Squeeze-and-Excitation Block (SE block)</strong>\n       some simple explanation from my <a href=\"https://www.kaggle.com/ccchang801023/se-net-my-top1-baseline-model-with-pytorch\">kernel</a></p>\n\n<p><strong>4. General training tricks</strong>\n      Reduce learning rate (ReduceLROnPlateau), early stopping</p>\n\n<p><strong>5. Pseudo-labeling on test data (most effective)</strong></p>\n\n<p><strong>6. Trick when training Dig-MNIST dataset</strong>\n        As discussed in <a href=\"https://www.kaggle.com/c/Kannada-MNIST/discussion/111462\">post1 </a>and <a href=\"https://www.kaggle.com/c/Kannada-MNIST/discussion/112933\">post2</a>,  you may notice that your model will get worse if you add Dig-MNIST dataset directly to your training dataset. There are some issues in Dig-MNIST dataset such as border cut-off effect, non-native user's style... which may cause poor performance to your original model.</p>\n\n<p>My baseline model(trained only with 60k training data) got ~99.8% accuracy on training set but got only ~90% accuracy on Dig-MNIST dataset. It seems difficult to generalize well on that 10% rest of images, but it also means that your model is still able to predict most of the images correctly. So I trained with Dig-MNIST by following steps:\n(1)Train a baseline model(with 60k training data) \n(2)Use model to perform pseudo labeling on test images (now you have 65k training data)\n(3)Use model to predict Dig-MNIST dataset, add the correctly classified data to the training set (says 9200 images, now you have 74200 training data)\n(4)Train a new model on new training dataset, replace baseline model with new model\n(5)Repeat steps (2) to (4), and see if there's improvement on Dig-MNIST prediction</p>\n\n<p>the flowchart is illustrated as below:\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F1025042%2F2986414371496927cefbe70a07a7d4b7%2Fflowchart.png?generation=1576649337493329&amp;alt=media\" alt=\"\"></p>\n\n<p>Pseudo-labeling and Dig-MNIST dataset training obviously improved my LB score\nfrom (private/public) : 0.9890/0.9894  to 0.99160/0.99120.</p>\n\n<p><strong>Some implementation details:</strong>\n- Optimizer: Adam\n- Epoch of each fold: maximum 200\n- Initial learning rate: 1e-3\n- Data augmentation (in pytorch): \n     torchvision.transform.RandomAffine(degree=15, translate=(0.25,0.25),scale=(0.7,1.1),shear=8)\n- Model training time:  One set of training for 5-fold cross validation takes about 4 hours on my PC (RTX 2080+i9-9900KF)</p>\n\n<h2>What didn't work for me</h2>\n\n<p><strong>1. More folds cross-validation (up to 20 folds)</strong></p>\n\n<p><strong>2. Further image augmentations:</strong>\n     colorjitter (brightness, contrast... ) will make model worse, also, you might want to \n     avoid vertically flip since it will cause wrong classification for similar digits such as 6 and 9.</p>\n\n<p><strong>3. Critical subclasses classification(0 and 1,  6 and 9)</strong></p>\n\n<p><strong>4. Classify after \"Native\" classifier</strong>\n     In order to deal with \"newbie effect\" mentioned in this <a href=\"https://www.kaggle.com/c/Kannada-MNIST/discussion/116064\">post</a>, before classifying the digits, I also tried to predict whether the numbers are written by native user or not. Assumes that training set are done by native user, label 1; Dig-MNIST set are done by non-native user, label 0, then train two different models separably for these two kinds of image, but this method doesn't improve in my case.</p>\n\n<p><strong>5. Several optimizers and learning rate reduce methods:</strong>\n     RMSProp, Adamax, AdamW, SGD, SGDR(SGD+CosineAnnealingWarmRestarts)...</p>\n\n<p><strong>6. Several architecture of neural network</strong>\n     Resnet18, Resnet34, Resnet50, SE_Resnet(SE block +residual network) ...</p>\n\n<p><strong>7. Transfer learning</strong></p>\n\n<p><strong>8. Snapshot ensembling</strong></p>\n\n<h2>What I want to try but got no time:</h2>\n\n<p><strong>1. Border cut-off effect augmentation</strong></p>\n\n<p><strong>2. Different ensemble methods</strong></p>\n\n<p><strong>3. Tune hyperparameters in more systematically, or in more smarter ways</strong></p>\n\n<h2>Final Submission</h2>\n\n<p>My final models were trained over ~75K images (train set 60k + test set 5K + Dig-MNIST data 10k) and got 0.9924 on the private LB. In order to avoid overfitting on the Dig-MNIST dataset, I also submitted another models which were trained without Dig-MNIST dataset and got 0.9916 on the private LB.</p>\n\n<p>Thanks for reading!</p>",
  "comments": [
    {
      "id": 704302,
      "author_name": "Pham Bao",
      "author_url": "",
      "post_date": "2019-12-27T09:49:19.110000",
      "content": "<p>That's a great approach!. May I ask you 2 questions? First, How many epochs that you train the second model and third model and more...? Second, Every model you retrain again with the new data then you perform pseudo labeling on test images, could you tell me how is the label of test images changing? I think when training model, the model will memorize the label. So that could be a problem when performing pseudo label.</p>",
      "votes": 1,
      "replies": [
        {
          "id": 712483,
          "author_name": "ccchang",
          "author_url": "",
          "post_date": "2020-01-07T09:36:51.673000",
          "content": "<p>Thank for your reply!\n1. I set maximum 200 epochs for each models, but with early stopping, most of them ended in &lt; 70 epochs.\n2. After 5 stages of repeatedly training, there's only few changes on pseudo label compared to the first stage(about 20 labels are different), but the number of Dig-MNIST data (number N in my figure) increase about 200, much more than previous one. So I think how good can your model deal with DIG-MNIST dataset is more important than test set in this case.</p>",
          "votes": 2,
          "replies": []
        }
      ]
    },
    {
      "id": 698011,
      "author_name": "MichaelP",
      "author_url": "",
      "post_date": "2019-12-18T17:02:21.303000",
      "content": "<p>Good work to get some benefit from the dig-mnist data for training. I used a function to clean the borders of the dig-mnist data, not as augmentation just as a preprocessing step. The code just set the outside n number of lines in the reshaped 28x28 image to 0. I found n=2 worked quite well. I didn't use the data for training. The preprocessing step led to an improved holdout validation score. Something like this might have helped your approach a little bit. Not that you needed much help 🙂 </p>\n\n<p>Congratulations.</p>",
      "votes": 1,
      "replies": [
        {
          "id": 698267,
          "author_name": "ccchang",
          "author_url": "",
          "post_date": "2019-12-19T01:56:58.980000",
          "content": "<p>Thank you! border cleaning is also an option, I didn’t do that cause I think translate and zoom in augmentation sometimes do that for me, interesting to hear that it does help improve the accuracy!\nyour works and shared information in this competition are very useful to me,  congrats to you too!</p>",
          "votes": 1,
          "replies": []
        },
        {
          "id": 701471,
          "author_name": "MichaelP",
          "author_url": "",
          "post_date": "2019-12-23T14:19:54.643000",
          "content": "<p>Thanks</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 712857,
      "author_name": "Jainmiah Shaik",
      "author_url": "",
      "post_date": "2020-01-07T16:36:17.610000",
      "content": "<p>Very clear explanation. Can I have the entire code to learn?</p>",
      "votes": 0,
      "replies": []
    },
    {
      "id": 700619,
      "author_name": "corochann",
      "author_url": "",
      "post_date": "2019-12-22T10:09:22.443000",
      "content": "<p>Congrats and thanks for sharing.</p>",
      "votes": 0,
      "replies": [
        {
          "id": 701457,
          "author_name": "ccchang",
          "author_url": "",
          "post_date": "2019-12-23T14:07:35.353000",
          "content": "<p>Thanks you!</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 697757,
      "author_name": "",
      "author_url": "",
      "post_date": "2019-12-18T11:05:21.963000",
      "content": "",
      "votes": 0,
      "replies": [
        {
          "id": 697805,
          "author_name": "ccchang",
          "author_url": "",
          "post_date": "2019-12-18T12:47:35.887000",
          "content": "<p>Thank you for the comment!</p>",
          "votes": 0,
          "replies": []
        }
      ]
    }
  ],
  "comment_count": 10,
  "messages": [
    {
      "id": 697607,
      "postDate": "2019-12-18T06:38:12.797Z",
      "content": "<p>Thanks a lot to Kaggle and @higgstachyon for hosting this competition, it's such a great experience for me to learn and acquire knowledge from other competitors, congratulations to all the winners!</p>\n\n<h2>The overall approach:</h2>\n\n<p><strong>CNN (Convolutional Neural Network)</strong></p>\n\n<h2>What worked for me:</h2>\n\n<p><strong>1. 5-fold cross-validation, average the results (or majority vote)</strong></p>\n\n<p><strong>2. Image augmentation:</strong>\n        translation, rotation, shearing and scaling</p>\n\n<p><strong>3. CNN with Squeeze-and-Excitation Block (SE block)</strong>\n       some simple explanation from my <a href=\"https://www.kaggle.com/ccchang801023/se-net-my-top1-baseline-model-with-pytorch\">kernel</a></p>\n\n<p><strong>4. General training tricks</strong>\n      Reduce learning rate (ReduceLROnPlateau), early stopping</p>\n\n<p><strong>5. Pseudo-labeling on test data (most effective)</strong></p>\n\n<p><strong>6. Trick when training Dig-MNIST dataset</strong>\n        As discussed in <a href=\"https://www.kaggle.com/c/Kannada-MNIST/discussion/111462\">post1 </a>and <a href=\"https://www.kaggle.com/c/Kannada-MNIST/discussion/112933\">post2</a>,  you may notice that your model will get worse if you add Dig-MNIST dataset directly to your training dataset. There are some issues in Dig-MNIST dataset such as border cut-off effect, non-native user's style... which may cause poor performance to your original model.</p>\n\n<p>My baseline model(trained only with 60k training data) got ~99.8% accuracy on training set but got only ~90% accuracy on Dig-MNIST dataset. It seems difficult to generalize well on that 10% rest of images, but it also means that your model is still able to predict most of the images correctly. So I trained with Dig-MNIST by following steps:\n(1)Train a baseline model(with 60k training data) \n(2)Use model to perform pseudo labeling on test images (now you have 65k training data)\n(3)Use model to predict Dig-MNIST dataset, add the correctly classified data to the training set (says 9200 images, now you have 74200 training data)\n(4)Train a new model on new training dataset, replace baseline model with new model\n(5)Repeat steps (2) to (4), and see if there's improvement on Dig-MNIST prediction</p>\n\n<p>the flowchart is illustrated as below:\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F1025042%2F2986414371496927cefbe70a07a7d4b7%2Fflowchart.png?generation=1576649337493329&amp;alt=media\" alt=\"\"></p>\n\n<p>Pseudo-labeling and Dig-MNIST dataset training obviously improved my LB score\nfrom (private/public) : 0.9890/0.9894  to 0.99160/0.99120.</p>\n\n<p><strong>Some implementation details:</strong>\n- Optimizer: Adam\n- Epoch of each fold: maximum 200\n- Initial learning rate: 1e-3\n- Data augmentation (in pytorch): \n     torchvision.transform.RandomAffine(degree=15, translate=(0.25,0.25),scale=(0.7,1.1),shear=8)\n- Model training time:  One set of training for 5-fold cross validation takes about 4 hours on my PC (RTX 2080+i9-9900KF)</p>\n\n<h2>What didn't work for me</h2>\n\n<p><strong>1. More folds cross-validation (up to 20 folds)</strong></p>\n\n<p><strong>2. Further image augmentations:</strong>\n     colorjitter (brightness, contrast... ) will make model worse, also, you might want to \n     avoid vertically flip since it will cause wrong classification for similar digits such as 6 and 9.</p>\n\n<p><strong>3. Critical subclasses classification(0 and 1,  6 and 9)</strong></p>\n\n<p><strong>4. Classify after \"Native\" classifier</strong>\n     In order to deal with \"newbie effect\" mentioned in this <a href=\"https://www.kaggle.com/c/Kannada-MNIST/discussion/116064\">post</a>, before classifying the digits, I also tried to predict whether the numbers are written by native user or not. Assumes that training set are done by native user, label 1; Dig-MNIST set are done by non-native user, label 0, then train two different models separably for these two kinds of image, but this method doesn't improve in my case.</p>\n\n<p><strong>5. Several optimizers and learning rate reduce methods:</strong>\n     RMSProp, Adamax, AdamW, SGD, SGDR(SGD+CosineAnnealingWarmRestarts)...</p>\n\n<p><strong>6. Several architecture of neural network</strong>\n     Resnet18, Resnet34, Resnet50, SE_Resnet(SE block +residual network) ...</p>\n\n<p><strong>7. Transfer learning</strong></p>\n\n<p><strong>8. Snapshot ensembling</strong></p>\n\n<h2>What I want to try but got no time:</h2>\n\n<p><strong>1. Border cut-off effect augmentation</strong></p>\n\n<p><strong>2. Different ensemble methods</strong></p>\n\n<p><strong>3. Tune hyperparameters in more systematically, or in more smarter ways</strong></p>\n\n<h2>Final Submission</h2>\n\n<p>My final models were trained over ~75K images (train set 60k + test set 5K + Dig-MNIST data 10k) and got 0.9924 on the private LB. In order to avoid overfitting on the Dig-MNIST dataset, I also submitted another models which were trained without Dig-MNIST dataset and got 0.9916 on the private LB.</p>\n\n<p>Thanks for reading!</p>",
      "rawMarkdown": "Thanks a lot to Kaggle and @higgstachyon for hosting this competition, it's such a great experience for me to learn and acquire knowledge from other competitors, congratulations to all the winners!\n\n## The overall approach:\n**CNN (Convolutional Neural Network)**\n\n## What worked for me:\n**1. 5-fold cross-validation, average the results (or majority vote)**\n\n**2. Image augmentation:**\n        translation, rotation, shearing and scaling\n\n**3. CNN with Squeeze-and-Excitation Block (SE block)**\n       some simple explanation from my [kernel](https://www.kaggle.com/ccchang801023/se-net-my-top1-baseline-model-with-pytorch)\n\n**4. General training tricks**\n      Reduce learning rate (ReduceLROnPlateau), early stopping\n\n**5. Pseudo-labeling on test data (most effective)**\n\n**6. Trick when training Dig-MNIST dataset**\n        As discussed in [post1 ](https://www.kaggle.com/c/Kannada-MNIST/discussion/111462)and [post2](https://www.kaggle.com/c/Kannada-MNIST/discussion/112933),  you may notice that your model will get worse if you add Dig-MNIST dataset directly to your training dataset. There are some issues in Dig-MNIST dataset such as border cut-off effect, non-native user's style... which may cause poor performance to your original model.\n       \nMy baseline model(trained only with 60k training data) got ~99.8% accuracy on training set but got only ~90% accuracy on Dig-MNIST dataset. It seems difficult to generalize well on that 10% rest of images, but it also means that your model is still able to predict most of the images correctly. So I trained with Dig-MNIST by following steps:\n(1)Train a baseline model(with 60k training data) \n(2)Use model to perform pseudo labeling on test images (now you have 65k training data)\n(3)Use model to predict Dig-MNIST dataset, add the correctly classified data to the training set (says 9200 images, now you have 74200 training data)\n(4)Train a new model on new training dataset, replace baseline model with new model\n(5)Repeat steps (2) to (4), and see if there's improvement on Dig-MNIST prediction\n\nthe flowchart is illustrated as below:\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F1025042%2F2986414371496927cefbe70a07a7d4b7%2Fflowchart.png?generation=1576649337493329&amp;alt=media)\n\n\nPseudo-labeling and Dig-MNIST dataset training obviously improved my LB score\nfrom (private/public) : 0.9890/0.9894  to 0.99160/0.99120.\n\n**Some implementation details:**\n- Optimizer: Adam\n- Epoch of each fold: maximum 200\n- Initial learning rate: 1e-3\n- Data augmentation (in pytorch): \n     torchvision.transform.RandomAffine(degree=15, translate=(0.25,0.25),scale=(0.7,1.1),shear=8)\n- Model training time:  One set of training for 5-fold cross validation takes about 4 hours on my PC (RTX 2080+i9-9900KF)\n\n##What didn't work for me\n**1. More folds cross-validation (up to 20 folds)**\n\n**2. Further image augmentations:**\n     colorjitter (brightness, contrast... ) will make model worse, also, you might want to \n     avoid vertically flip since it will cause wrong classification for similar digits such as 6 and 9.\n\n**3. Critical subclasses classification(0 and 1,  6 and 9)**\n\n**4. Classify after \"Native\" classifier**\n     In order to deal with \"newbie effect\" mentioned in this [post](https://www.kaggle.com/c/Kannada-MNIST/discussion/116064), before classifying the digits, I also tried to predict whether the numbers are written by native user or not. Assumes that training set are done by native user, label 1; Dig-MNIST set are done by non-native user, label 0, then train two different models separably for these two kinds of image, but this method doesn't improve in my case.\n\n**5. Several optimizers and learning rate reduce methods:**\n     RMSProp, Adamax, AdamW, SGD, SGDR(SGD+CosineAnnealingWarmRestarts)...\n\n**6. Several architecture of neural network**\n     Resnet18, Resnet34, Resnet50, SE_Resnet(SE block +residual network) ...\n\n**7. Transfer learning**\n\n**8. Snapshot ensembling**\n\n##What I want to try but got no time:\n**1. Border cut-off effect augmentation**\n\n**2. Different ensemble methods**\n\n**3. Tune hyperparameters in more systematically, or in more smarter ways**\n\n##Final Submission\nMy final models were trained over ~75K images (train set 60k + test set 5K + Dig-MNIST data 10k) and got 0.9924 on the private LB. In order to avoid overfitting on the Dig-MNIST dataset, I also submitted another models which were trained without Dig-MNIST dataset and got 0.9916 on the private LB.\n\nThanks for reading!",
      "votes": 16
    },
    {
      "id": 704302,
      "postDate": "2019-12-27T09:49:19.110Z",
      "content": "<p>That's a great approach!. May I ask you 2 questions? First, How many epochs that you train the second model and third model and more...? Second, Every model you retrain again with the new data then you perform pseudo labeling on test images, could you tell me how is the label of test images changing? I think when training model, the model will memorize the label. So that could be a problem when performing pseudo label.</p>",
      "rawMarkdown": "That's a great approach!. May I ask you 2 questions? First, How many epochs that you train the second model and third model and more...? Second, Every model you retrain again with the new data then you perform pseudo labeling on test images, could you tell me how is the label of test images changing? I think when training model, the model will memorize the label. So that could be a problem when performing pseudo label.",
      "votes": 1,
      "replies": [
        {
          "id": 712483,
          "postDate": "2020-01-07T09:36:51.673Z",
          "content": "<p>Thank for your reply!\n1. I set maximum 200 epochs for each models, but with early stopping, most of them ended in &lt; 70 epochs.\n2. After 5 stages of repeatedly training, there's only few changes on pseudo label compared to the first stage(about 20 labels are different), but the number of Dig-MNIST data (number N in my figure) increase about 200, much more than previous one. So I think how good can your model deal with DIG-MNIST dataset is more important than test set in this case.</p>",
          "rawMarkdown": "Thank for your reply!\n1. I set maximum 200 epochs for each models, but with early stopping, most of them ended in &lt; 70 epochs.\n2. After 5 stages of repeatedly training, there's only few changes on pseudo label compared to the first stage(about 20 labels are different), but the number of Dig-MNIST data (number N in my figure) increase about 200, much more than previous one. So I think how good can your model deal with DIG-MNIST dataset is more important than test set in this case.",
          "votes": 2
        }
      ]
    },
    {
      "id": 698011,
      "postDate": "2019-12-18T17:02:21.303Z",
      "content": "<p>Good work to get some benefit from the dig-mnist data for training. I used a function to clean the borders of the dig-mnist data, not as augmentation just as a preprocessing step. The code just set the outside n number of lines in the reshaped 28x28 image to 0. I found n=2 worked quite well. I didn't use the data for training. The preprocessing step led to an improved holdout validation score. Something like this might have helped your approach a little bit. Not that you needed much help 🙂 </p>\n\n<p>Congratulations.</p>",
      "rawMarkdown": "Good work to get some benefit from the dig-mnist data for training. I used a function to clean the borders of the dig-mnist data, not as augmentation just as a preprocessing step. The code just set the outside n number of lines in the reshaped 28x28 image to 0. I found n=2 worked quite well. I didn't use the data for training. The preprocessing step led to an improved holdout validation score. Something like this might have helped your approach a little bit. Not that you needed much help 🙂 \n\nCongratulations.",
      "votes": 1,
      "replies": [
        {
          "id": 698267,
          "postDate": "2019-12-19T01:56:58.980Z",
          "content": "<p>Thank you! border cleaning is also an option, I didn’t do that cause I think translate and zoom in augmentation sometimes do that for me, interesting to hear that it does help improve the accuracy!\nyour works and shared information in this competition are very useful to me,  congrats to you too!</p>",
          "rawMarkdown": "Thank you! border cleaning is also an option, I didn’t do that cause I think translate and zoom in augmentation sometimes do that for me, interesting to hear that it does help improve the accuracy!\nyour works and shared information in this competition are very useful to me,  congrats to you too!",
          "votes": 1
        },
        {
          "id": 701471,
          "postDate": "2019-12-23T14:19:54.643Z",
          "content": "<p>Thanks</p>",
          "rawMarkdown": "Thanks"
        }
      ]
    },
    {
      "id": 712857,
      "postDate": "2020-01-07T16:36:17.610Z",
      "content": "<p>Very clear explanation. Can I have the entire code to learn?</p>",
      "rawMarkdown": "Very clear explanation. Can I have the entire code to learn?"
    },
    {
      "id": 700619,
      "postDate": "2019-12-22T10:09:22.443Z",
      "content": "<p>Congrats and thanks for sharing.</p>",
      "rawMarkdown": "Congrats and thanks for sharing.",
      "replies": [
        {
          "id": 701457,
          "postDate": "2019-12-23T14:07:35.353Z",
          "content": "<p>Thanks you!</p>",
          "rawMarkdown": "Thanks you!"
        }
      ]
    },
    {
      "id": 697757,
      "postDate": "2019-12-18T11:05:21.963Z",
      "rawMarkdown": "",
      "isDeleted": true,
      "replies": [
        {
          "id": 697805,
          "postDate": "2019-12-18T12:47:35.887Z",
          "content": "<p>Thank you for the comment!</p>",
          "rawMarkdown": "Thank you for the comment!"
        }
      ]
    }
  ],
  "complete": true,
  "source": "live",
  "fetched_at": "2026-10-19T11:35:35Z",
  "competition_id": 16017,
  "competition_slug": "Kannada-MNIST",
  "team_id": 3891052,
  "solution_url": "/competitions/Kannada-MNIST/writeups/ccchang-5th-place-solution-pseudo-labeling-dig-mni"
}