{
  "id": 258312,
  "title": "[Private 28th/public 14th solution]-Tri's part-custom loss",
  "content": "<p>First of all, I would like to say thank you very much to my wonderful teammates, <a href=\"https://www.kaggle.com/horsek\" target=\"_blank\">@horsek</a>, and <a href=\"https://www.kaggle.com/lftuwujie\" target=\"_blank\">@lftuwujie</a>, I learned a lot from them during this competition. Next, thank you to all competitors, the competition host, and Kaggle for competing and organizing this amazing competition. Besides, congratulation to all teams in the gold medal zone.</p>\n<p>At the beginning of this competition, I told <a href=\"https://www.kaggle.com/horsek\" target=\"_blank\">@horsek</a> that let's treat it as our chance to learn about NLP because I didn't have knowledge of it and it has been my first NLP competition.</p>\n<p>In this write-up, I would like to share not only my models but also my journey in this competition, from starting points to forming ideas under a newbie's viewpoint like me.</p>\n<h1>Starting point</h1>\n<p>I started my journey with this wonderful notebook (<a href=\"https://www.kaggle.com/rhtsingh/commonlit-readability-prize-roberta-torch-fit)\" target=\"_blank\">https://www.kaggle.com/rhtsingh/commonlit-readability-prize-roberta-torch-fit)</a>, please give him/her an upvote.</p>\n<p>However, I didn't fork it but read and re-wrote my own notebook. This helped me to debug, and forming some ideas to further customize and optimize the pipeline.</p>\n<p>After this step, my best single model is the RoBERTa-large and scored 0.468 on the public LB and 0.469 on the private LB, my inference notebook is <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-inference?scriptVersionId=65661288\" target=\"_blank\">here</a></p>\n<p>The setting of this model is,</p>\n<ul>\n<li>Epochs: 8;</li>\n<li>Batch size: 8;</li>\n<li>LR: 2e-5;</li>\n<li>Evaluate after every 2 steps;</li>\n<li>Differentiate learning rate of hidden layers;</li>\n<li>Model: Backbone --&gt; Weighted-mean/max pooling of hidden layers --&gt; Attention pooling over document length --&gt; Output</li>\n<li>Loss: RMSE</li>\n<li>Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup</li>\n</ul>\n<h1>Custom loss</h1>\n<p>In this competition (and also in all of my competitions), I studied related literature a lot. This step helped me to understand the concept of the competition and find new ideas. The first idea I got is the Quadratic-Weighted-Kappa loss, the second is the custom Bradley-Terry loss.</p>\n<h3>Quadratic-Weighted-Kappa (QWK) Loss</h3>\n<p>The idea of this loss came from this <a href=\"https://direct.mit.edu/coli/article/47/1/141/97334/Supervised-and-Unsupervised-Neural-Approaches-to\" target=\"_blank\">paper</a>. The main idea of this loss is binning the excerpt targets into categories, and train the model to classify them into these categories. In my opinion, this step is equivalent to label-smoothing, because it gathers excerpts with similar targets into a bin. The QWK loss is not a must, you can implement it with Cross-entropy loss. This is the implementation of this QWK loss,</p>\n<pre><code>class QuadraticWeightedKappaLoss(nn.Module):\n    def __init__(self, num_cat = 7, device = 'cpu'):\n        super(QuadraticWeightedKappaLoss, self).__init__()\n        self.num_cat = num_cat\n        cats = torch.arange(num_cat).to(device)\n        self.weights = (cats.view(-1,1) - cats.view(1,-1)).pow(2) / (num_cat - 1)**2\n\n    def _confusion_matrix(self, pred_cat, true_cat):\n        confusion_matrix = torch.zeros((self.num_cat, self.num_cat)).to(pred_cat.device)\n        for t, p in zip(true_cat.view(-1), pred_cat.view(-1)):\n            confusion_matrix[t.long(), p.long()] += 1\n        return confusion_matrix\n\n    def forward(self, pred_cat, true_cat):\n        # Confusion matrix\n        O = self._confusion_matrix(pred_cat, true_cat)\n\n        # Count elements in each category\n        true_hist = torch.bincount(true_cat, minlength = self.num_cat)\n        pred_hist = torch.bincount(pred_cat, minlength = self.num_cat)\n\n        # Expected values\n        E = torch.outer(true_hist, pred_hist)\n\n        # Normlization\n        O = O / torch.sum(O)\n        E = E / torch.sum(E)\n\n        # Weighted Kappa\n        numerator = torch.sum(self.weights * O)\n        denominator = torch.sum(self.weights * E)\n\n        return numerator / denominator\n</code></pre>\n<p>My best model after this step is again a RoBERTa-large with the following setting,</p>\n<ul>\n<li>Epochs: 8;</li>\n<li>Batch size: 8;</li>\n<li>LR: 2e-5;</li>\n<li>Evaluate after every 2 steps;</li>\n<li>Differentiate learning rate of hidden layers;</li>\n<li>Model: Backbone --&gt; Weighted-mean/max pooling of hidden layers --&gt; Attention pooling over document length --&gt; Output</li>\n<li>Loss: RMSE + QWK</li>\n<li>Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup</li>\n</ul>\n<p>It scored 0.466/0.466 on the public/private LB.</p>\n<h3>Bradley-Terry (BT) Loss</h3>\n<p>This is so far the most effective trick and my most contribution to my team. The idea came from the observation about the benchmark excerpt which its target and standard error are both 0. As I studied the information from the host, each excerpt in the corpus is compared to some others. Then, by using the Bradley-Terry method, their prior targets are estimated. Finally, the prior targets of all excerpts are subtracted to the target a chosen benchmark excerpt to obtain the data we have.</p>\n<p>From this observation, I designed my models accordingly. The model architecture is <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-model-architecture\" target=\"_blank\">here</a> (sorry that I couldn't insert an image to a post, so I uploaded my model architecture in another notebook).</p>\n<p>The implementation of the custom BT loss is as follow,</p>\n<pre><code>class BradleyTerryLoss(nn.Module):\n    def __init__(self):\n        super(BradleyTerryLoss, self).__init__()\n\n    def forward(self, pred_mean, true_mean):\n        batch_size = len(pred_mean)\n        true_comparison = true_mean.view(-1,1) - true_mean.view(1,-1)\n        pred_comparison = pred_mean.view(-1,1) - pred_mean.view(1,-1)\n        return torch.log(1 + torch.tril(torch.exp(-true_comparison * pred_comparison))).sum() / (batch_size * (batch_size - 1) / 2)\n</code></pre>\n<p>With this structure, the predicted target of the benchmark excerpt is always 0, which is consistent with the truth. This loss also allows models to compare the targets to each other. We applied this custom loss with many models of my teams.</p>\n<p>This custom loss gave me the best single model so far with 0.461/0.462 on the public and private LB.<br>\nMy best model after this step is again a RoBERTa-large with the following setting,</p>\n<ul>\n<li>Epochs: 20, stop after 8 epochs;</li>\n<li>Batch size: 8;</li>\n<li>LR: 2e-5;</li>\n<li>Evaluate after every 10 steps;</li>\n<li>Differentiate learning rate of hidden layers;</li>\n<li>Model: Backbone --&gt; Weighted-mean/max pooling of hidden layers --&gt; Attention pooling over document length --&gt; Output</li>\n<li>Loss: RMSE + QWK + BT</li>\n<li>Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup</li>\n</ul>\n<p>Using this setting, I train my models with different types of backbone. The following summarizes my best models according to public LB with each backbone (public LB/private LB),</p>\n<ul>\n<li>RoBERTa-large with weighted-mean/max pooling: 0.461/0.462;</li>\n<li>RoBERTa-large with max-pooling only: 0.463/0.466;</li>\n<li>GPT2-medium: 0.492/0.508;</li>\n<li>XLNet-large-cased: 0.468/0.473;</li>\n<li>ALBERT-xlarge-v2: 0.502/0.500;</li>\n<li>ELECTRA-large: 0.468/0.476;</li>\n<li>Funnel-transformer/large: 0.477/0.477;</li>\n<li>BART-large: 0.487/0.491;</li>\n<li>DeBERTa-large: 0.463/0.468;</li>\n</ul>\n<p>For each backbone, I sometimes changed the setting (evaluate less frequently, omit the QWK loss, training with another fold splitting, etc.) to avoid overfitting.</p>\n<p>Finally, I simply took the equal average of all of my 13 models, it gave me 0.453/0.456 on the public/private LB. Ensemble my average model with my teammates' models gave us 0.448/0.452 on the public/private LB.</p>\n<h1>What didn't work for me?</h1>\n<ul>\n<li>Data augmentation;</li>\n<li>Linguistic features;</li>\n<li>HAN</li>\n</ul>\n<h1>Final words</h1>\n<p>Although the result is not as good as I expected, I still enjoyed the competition a lot. From nearly 0 in NLP, after 3 months, I learned a lot from my teammates and fellow competitors. I hope to meet you in future competitions!</p>\n<h1>Notebooks</h1>\n<ul>\n<li>Training of my models: <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-training-notebook\" target=\"_blank\">https://www.kaggle.com/shinomoriaoshi/readability-training-notebook</a></li>\n<li>Inference of my stacking model: <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-stacking-inference\" target=\"_blank\">https://www.kaggle.com/shinomoriaoshi/readability-stacking-inference</a></li>\n<li>OOF notebook of a RoBERTa-large model: <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-oof\" target=\"_blank\">https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-oof</a></li>\n</ul>",
  "comments": [
    {
      "id": 1516529,
      "author_name": "Allohvk",
      "author_url": "",
      "post_date": "2021-09-18T12:18:30.617000",
      "content": "<p><a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> , I was studying this competition with a more specific focus on interesting loss engg strategies. Of course your solution tops my list. QWK is very apt and BT seems to be even more apt. My question is on the BT loss that you used…which is something of the form of log(1+e to power yhat . pred) for every pair in the batch. But ideally in the formula we should have restricted the prediction and groundtruths to +1 and -1 only for win and loss respectively. But I think you are using the BT scores directly. Is that correct or I am misunderstanding something? and somehow that too seems to work…</p>",
      "votes": 1,
      "replies": []
    },
    {
      "id": 1433007,
      "author_name": "ABCbum",
      "author_url": "",
      "post_date": "2021-08-03T13:40:37.380000",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> !! Few weeks ago I considered merging with your team but my ranking was not great and you guys just boosted up to the top so I decided not to since my approach requires a bit more submissions at the time. Could have been a great opportunity to learn from you guys!</p>",
      "votes": 1,
      "replies": [
        {
          "id": 1433465,
          "author_name": "Minh Tri Phan",
          "author_url": "",
          "post_date": "2021-08-03T14:01:45.340000",
          "content": "<p>Thank you for your kind words. Haha, so now we should be the ones who regret about that. Anyway, congratulation with your solo gold. I look forward to working with you in future competitions.</p>",
          "votes": 1,
          "replies": []
        }
      ]
    },
    {
      "id": 1428643,
      "author_name": "Allohvk",
      "author_url": "",
      "post_date": "2021-08-03T09:40:46.007000",
      "content": "<p>Nice…I think this is the first solution post that talks of binning and loss-strategy. I wanted to explore it but couldn't get much of a chance. I wrote some code based on my own logic but scrapped it somewhere mid-way(should have persisted). I strongly felt that outliers skewed MSE and entropy related losses would give much better results…Anyway, good to read about QWK loss. As you mentioned, I guess the trick is to read more related literature…</p>",
      "votes": 1,
      "replies": [
        {
          "id": 1428886,
          "author_name": "Minh Tri Phan",
          "author_url": "",
          "post_date": "2021-08-03T10:12:20.187000",
          "content": "<p>Thank you, I am currently working in an academic environment, so I guess reading literature as my first step is just my habit, haha.</p>",
          "votes": 1,
          "replies": []
        },
        {
          "id": 1429993,
          "author_name": "Allohvk",
          "author_url": "",
          "post_date": "2021-08-03T11:11:27.530000",
          "content": "<p>I need to inculcate that habit for sure :)</p>\n<p>W.r.t HAN, I was finally able to get it to a .473 score - in case you are interested in the model - <a href=\"https://www.kaggle.com/c/commonlitreadabilityprize/discussion/257674\" target=\"_blank\">https://www.kaggle.com/c/commonlitreadabilityprize/discussion/257674</a></p>",
          "votes": 1,
          "replies": []
        },
        {
          "id": 1430534,
          "author_name": "Minh Tri Phan",
          "author_url": "",
          "post_date": "2021-08-03T11:45:01.883000",
          "content": "<p>Thank you very much. I couldn't make HAN model break 0.50x. I will study your solution for sure.</p>",
          "votes": 1,
          "replies": []
        }
      ]
    },
    {
      "id": 1446336,
      "author_name": "hosuke",
      "author_url": "",
      "post_date": "2021-08-04T11:55:26.483000",
      "content": "<p><a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> Thank you so much for inviting me to this exciting competition! I had really great and meaningful days with you and <a href=\"https://www.kaggle.com/lftuwujie\" target=\"_blank\">@lftuwujie</a>.<br>\nAt first I joined this competition as a learning experience, but as our ranking improved, I became more and more serious about it. haha<br>\nLet's win the gold medal together next time!</p>",
      "votes": 2,
      "replies": [
        {
          "id": 1447526,
          "author_name": "Minh Tri Phan",
          "author_url": "",
          "post_date": "2021-08-04T15:12:54.743000",
          "content": "<p>Haha, after seeing the improvement, we commit to this competition more seriously without realizing it. Let's get a gold next time!</p>",
          "votes": 1,
          "replies": []
        }
      ]
    },
    {
      "id": 1431232,
      "author_name": "Jie Wu",
      "author_url": "",
      "post_date": "2021-08-03T12:17:11.903000",
      "content": "<p><a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> very good summary, and it's a great journey to work with both you and <a href=\"https://www.kaggle.com/horsek\" target=\"_blank\">@horsek</a>. I also learnt quite a lot from you too. Looking forward to working together again.</p>",
      "votes": 2,
      "replies": [
        {
          "id": 1432314,
          "author_name": "Minh Tri Phan",
          "author_url": "",
          "post_date": "2021-08-03T13:11:56.663000",
          "content": "<p>Thank you for your kind words, Jie. Me too, I am looking forward to working with you in the future.</p>",
          "votes": 1,
          "replies": []
        }
      ]
    }
  ],
  "comment_count": 11,
  "messages": [
    {
      "id": 1427550,
      "postDate": "2021-08-03T08:33:16.753Z",
      "content": "<p>First of all, I would like to say thank you very much to my wonderful teammates, <a href=\"https://www.kaggle.com/horsek\" target=\"_blank\">@horsek</a>, and <a href=\"https://www.kaggle.com/lftuwujie\" target=\"_blank\">@lftuwujie</a>, I learned a lot from them during this competition. Next, thank you to all competitors, the competition host, and Kaggle for competing and organizing this amazing competition. Besides, congratulation to all teams in the gold medal zone.</p>\n<p>At the beginning of this competition, I told <a href=\"https://www.kaggle.com/horsek\" target=\"_blank\">@horsek</a> that let's treat it as our chance to learn about NLP because I didn't have knowledge of it and it has been my first NLP competition.</p>\n<p>In this write-up, I would like to share not only my models but also my journey in this competition, from starting points to forming ideas under a newbie's viewpoint like me.</p>\n<h1>Starting point</h1>\n<p>I started my journey with this wonderful notebook (<a href=\"https://www.kaggle.com/rhtsingh/commonlit-readability-prize-roberta-torch-fit)\" target=\"_blank\">https://www.kaggle.com/rhtsingh/commonlit-readability-prize-roberta-torch-fit)</a>, please give him/her an upvote.</p>\n<p>However, I didn't fork it but read and re-wrote my own notebook. This helped me to debug, and forming some ideas to further customize and optimize the pipeline.</p>\n<p>After this step, my best single model is the RoBERTa-large and scored 0.468 on the public LB and 0.469 on the private LB, my inference notebook is <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-inference?scriptVersionId=65661288\" target=\"_blank\">here</a></p>\n<p>The setting of this model is,</p>\n<ul>\n<li>Epochs: 8;</li>\n<li>Batch size: 8;</li>\n<li>LR: 2e-5;</li>\n<li>Evaluate after every 2 steps;</li>\n<li>Differentiate learning rate of hidden layers;</li>\n<li>Model: Backbone --&gt; Weighted-mean/max pooling of hidden layers --&gt; Attention pooling over document length --&gt; Output</li>\n<li>Loss: RMSE</li>\n<li>Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup</li>\n</ul>\n<h1>Custom loss</h1>\n<p>In this competition (and also in all of my competitions), I studied related literature a lot. This step helped me to understand the concept of the competition and find new ideas. The first idea I got is the Quadratic-Weighted-Kappa loss, the second is the custom Bradley-Terry loss.</p>\n<h3>Quadratic-Weighted-Kappa (QWK) Loss</h3>\n<p>The idea of this loss came from this <a href=\"https://direct.mit.edu/coli/article/47/1/141/97334/Supervised-and-Unsupervised-Neural-Approaches-to\" target=\"_blank\">paper</a>. The main idea of this loss is binning the excerpt targets into categories, and train the model to classify them into these categories. In my opinion, this step is equivalent to label-smoothing, because it gathers excerpts with similar targets into a bin. The QWK loss is not a must, you can implement it with Cross-entropy loss. This is the implementation of this QWK loss,</p>\n<pre><code>class QuadraticWeightedKappaLoss(nn.Module):\n    def __init__(self, num_cat = 7, device = 'cpu'):\n        super(QuadraticWeightedKappaLoss, self).__init__()\n        self.num_cat = num_cat\n        cats = torch.arange(num_cat).to(device)\n        self.weights = (cats.view(-1,1) - cats.view(1,-1)).pow(2) / (num_cat - 1)**2\n\n    def _confusion_matrix(self, pred_cat, true_cat):\n        confusion_matrix = torch.zeros((self.num_cat, self.num_cat)).to(pred_cat.device)\n        for t, p in zip(true_cat.view(-1), pred_cat.view(-1)):\n            confusion_matrix[t.long(), p.long()] += 1\n        return confusion_matrix\n\n    def forward(self, pred_cat, true_cat):\n        # Confusion matrix\n        O = self._confusion_matrix(pred_cat, true_cat)\n\n        # Count elements in each category\n        true_hist = torch.bincount(true_cat, minlength = self.num_cat)\n        pred_hist = torch.bincount(pred_cat, minlength = self.num_cat)\n\n        # Expected values\n        E = torch.outer(true_hist, pred_hist)\n\n        # Normlization\n        O = O / torch.sum(O)\n        E = E / torch.sum(E)\n\n        # Weighted Kappa\n        numerator = torch.sum(self.weights * O)\n        denominator = torch.sum(self.weights * E)\n\n        return numerator / denominator\n</code></pre>\n<p>My best model after this step is again a RoBERTa-large with the following setting,</p>\n<ul>\n<li>Epochs: 8;</li>\n<li>Batch size: 8;</li>\n<li>LR: 2e-5;</li>\n<li>Evaluate after every 2 steps;</li>\n<li>Differentiate learning rate of hidden layers;</li>\n<li>Model: Backbone --&gt; Weighted-mean/max pooling of hidden layers --&gt; Attention pooling over document length --&gt; Output</li>\n<li>Loss: RMSE + QWK</li>\n<li>Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup</li>\n</ul>\n<p>It scored 0.466/0.466 on the public/private LB.</p>\n<h3>Bradley-Terry (BT) Loss</h3>\n<p>This is so far the most effective trick and my most contribution to my team. The idea came from the observation about the benchmark excerpt which its target and standard error are both 0. As I studied the information from the host, each excerpt in the corpus is compared to some others. Then, by using the Bradley-Terry method, their prior targets are estimated. Finally, the prior targets of all excerpts are subtracted to the target a chosen benchmark excerpt to obtain the data we have.</p>\n<p>From this observation, I designed my models accordingly. The model architecture is <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-model-architecture\" target=\"_blank\">here</a> (sorry that I couldn't insert an image to a post, so I uploaded my model architecture in another notebook).</p>\n<p>The implementation of the custom BT loss is as follow,</p>\n<pre><code>class BradleyTerryLoss(nn.Module):\n    def __init__(self):\n        super(BradleyTerryLoss, self).__init__()\n\n    def forward(self, pred_mean, true_mean):\n        batch_size = len(pred_mean)\n        true_comparison = true_mean.view(-1,1) - true_mean.view(1,-1)\n        pred_comparison = pred_mean.view(-1,1) - pred_mean.view(1,-1)\n        return torch.log(1 + torch.tril(torch.exp(-true_comparison * pred_comparison))).sum() / (batch_size * (batch_size - 1) / 2)\n</code></pre>\n<p>With this structure, the predicted target of the benchmark excerpt is always 0, which is consistent with the truth. This loss also allows models to compare the targets to each other. We applied this custom loss with many models of my teams.</p>\n<p>This custom loss gave me the best single model so far with 0.461/0.462 on the public and private LB.<br>\nMy best model after this step is again a RoBERTa-large with the following setting,</p>\n<ul>\n<li>Epochs: 20, stop after 8 epochs;</li>\n<li>Batch size: 8;</li>\n<li>LR: 2e-5;</li>\n<li>Evaluate after every 10 steps;</li>\n<li>Differentiate learning rate of hidden layers;</li>\n<li>Model: Backbone --&gt; Weighted-mean/max pooling of hidden layers --&gt; Attention pooling over document length --&gt; Output</li>\n<li>Loss: RMSE + QWK + BT</li>\n<li>Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup</li>\n</ul>\n<p>Using this setting, I train my models with different types of backbone. The following summarizes my best models according to public LB with each backbone (public LB/private LB),</p>\n<ul>\n<li>RoBERTa-large with weighted-mean/max pooling: 0.461/0.462;</li>\n<li>RoBERTa-large with max-pooling only: 0.463/0.466;</li>\n<li>GPT2-medium: 0.492/0.508;</li>\n<li>XLNet-large-cased: 0.468/0.473;</li>\n<li>ALBERT-xlarge-v2: 0.502/0.500;</li>\n<li>ELECTRA-large: 0.468/0.476;</li>\n<li>Funnel-transformer/large: 0.477/0.477;</li>\n<li>BART-large: 0.487/0.491;</li>\n<li>DeBERTa-large: 0.463/0.468;</li>\n</ul>\n<p>For each backbone, I sometimes changed the setting (evaluate less frequently, omit the QWK loss, training with another fold splitting, etc.) to avoid overfitting.</p>\n<p>Finally, I simply took the equal average of all of my 13 models, it gave me 0.453/0.456 on the public/private LB. Ensemble my average model with my teammates' models gave us 0.448/0.452 on the public/private LB.</p>\n<h1>What didn't work for me?</h1>\n<ul>\n<li>Data augmentation;</li>\n<li>Linguistic features;</li>\n<li>HAN</li>\n</ul>\n<h1>Final words</h1>\n<p>Although the result is not as good as I expected, I still enjoyed the competition a lot. From nearly 0 in NLP, after 3 months, I learned a lot from my teammates and fellow competitors. I hope to meet you in future competitions!</p>\n<h1>Notebooks</h1>\n<ul>\n<li>Training of my models: <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-training-notebook\" target=\"_blank\">https://www.kaggle.com/shinomoriaoshi/readability-training-notebook</a></li>\n<li>Inference of my stacking model: <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-stacking-inference\" target=\"_blank\">https://www.kaggle.com/shinomoriaoshi/readability-stacking-inference</a></li>\n<li>OOF notebook of a RoBERTa-large model: <a href=\"https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-oof\" target=\"_blank\">https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-oof</a></li>\n</ul>",
      "rawMarkdown": "First of all, I would like to say thank you very much to my wonderful teammates, @horsek, and @lftuwujie, I learned a lot from them during this competition. Next, thank you to all competitors, the competition host, and Kaggle for competing and organizing this amazing competition. Besides, congratulation to all teams in the gold medal zone.\n\nAt the beginning of this competition, I told @horsek that let's treat it as our chance to learn about NLP because I didn't have knowledge of it and it has been my first NLP competition.\n\nIn this write-up, I would like to share not only my models but also my journey in this competition, from starting points to forming ideas under a newbie's viewpoint like me.\n\n# Starting point\nI started my journey with this wonderful notebook (https://www.kaggle.com/rhtsingh/commonlit-readability-prize-roberta-torch-fit), please give him/her an upvote.\n\nHowever, I didn't fork it but read and re-wrote my own notebook. This helped me to debug, and forming some ideas to further customize and optimize the pipeline.\n\nAfter this step, my best single model is the RoBERTa-large and scored 0.468 on the public LB and 0.469 on the private LB, my inference notebook is [here](https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-inference?scriptVersionId=65661288)\n\nThe setting of this model is,\n* Epochs: 8;\n* Batch size: 8;\n* LR: 2e-5;\n* Evaluate after every 2 steps;\n* Differentiate learning rate of hidden layers;\n* Model: Backbone --> Weighted-mean/max pooling of hidden layers --> Attention pooling over document length --> Output\n* Loss: RMSE\n* Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup\n\n# Custom loss\nIn this competition (and also in all of my competitions), I studied related literature a lot. This step helped me to understand the concept of the competition and find new ideas. The first idea I got is the Quadratic-Weighted-Kappa loss, the second is the custom Bradley-Terry loss.\n\n### Quadratic-Weighted-Kappa (QWK) Loss\nThe idea of this loss came from this [paper](https://direct.mit.edu/coli/article/47/1/141/97334/Supervised-and-Unsupervised-Neural-Approaches-to). The main idea of this loss is binning the excerpt targets into categories, and train the model to classify them into these categories. In my opinion, this step is equivalent to label-smoothing, because it gathers excerpts with similar targets into a bin. The QWK loss is not a must, you can implement it with Cross-entropy loss. This is the implementation of this QWK loss,\n\n```\nclass QuadraticWeightedKappaLoss(nn.Module):\n    def __init__(self, num_cat = 7, device = 'cpu'):\n        super(QuadraticWeightedKappaLoss, self).__init__()\n        self.num_cat = num_cat\n        cats = torch.arange(num_cat).to(device)\n        self.weights = (cats.view(-1,1) - cats.view(1,-1)).pow(2) / (num_cat - 1)**2\n        \n    def _confusion_matrix(self, pred_cat, true_cat):\n        confusion_matrix = torch.zeros((self.num_cat, self.num_cat)).to(pred_cat.device)\n        for t, p in zip(true_cat.view(-1), pred_cat.view(-1)):\n            confusion_matrix[t.long(), p.long()] += 1\n        return confusion_matrix\n        \n    def forward(self, pred_cat, true_cat):\n        # Confusion matrix\n        O = self._confusion_matrix(pred_cat, true_cat)\n        \n        # Count elements in each category\n        true_hist = torch.bincount(true_cat, minlength = self.num_cat)\n        pred_hist = torch.bincount(pred_cat, minlength = self.num_cat)\n        \n        # Expected values\n        E = torch.outer(true_hist, pred_hist)\n        \n        # Normlization\n        O = O / torch.sum(O)\n        E = E / torch.sum(E)\n        \n        # Weighted Kappa\n        numerator = torch.sum(self.weights * O)\n        denominator = torch.sum(self.weights * E)\n        \n        return numerator / denominator\n```\n\nMy best model after this step is again a RoBERTa-large with the following setting,\n* Epochs: 8;\n* Batch size: 8;\n* LR: 2e-5;\n* Evaluate after every 2 steps;\n* Differentiate learning rate of hidden layers;\n* Model: Backbone --> Weighted-mean/max pooling of hidden layers --> Attention pooling over document length --> Output\n* Loss: RMSE + QWK\n* Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup\n\nIt scored 0.466/0.466 on the public/private LB.\n\n### Bradley-Terry (BT) Loss\nThis is so far the most effective trick and my most contribution to my team. The idea came from the observation about the benchmark excerpt which its target and standard error are both 0. As I studied the information from the host, each excerpt in the corpus is compared to some others. Then, by using the Bradley-Terry method, their prior targets are estimated. Finally, the prior targets of all excerpts are subtracted to the target a chosen benchmark excerpt to obtain the data we have.\n\nFrom this observation, I designed my models accordingly. The model architecture is [here](https://www.kaggle.com/shinomoriaoshi/readability-model-architecture) (sorry that I couldn't insert an image to a post, so I uploaded my model architecture in another notebook).\n\nThe implementation of the custom BT loss is as follow,\n```\nclass BradleyTerryLoss(nn.Module):\n    def __init__(self):\n        super(BradleyTerryLoss, self).__init__()\n        \n    def forward(self, pred_mean, true_mean):\n        batch_size = len(pred_mean)\n        true_comparison = true_mean.view(-1,1) - true_mean.view(1,-1)\n        pred_comparison = pred_mean.view(-1,1) - pred_mean.view(1,-1)\n        return torch.log(1 + torch.tril(torch.exp(-true_comparison * pred_comparison))).sum() / (batch_size * (batch_size - 1) / 2)\n```\n\nWith this structure, the predicted target of the benchmark excerpt is always 0, which is consistent with the truth. This loss also allows models to compare the targets to each other. We applied this custom loss with many models of my teams.\n\nThis custom loss gave me the best single model so far with 0.461/0.462 on the public and private LB.\nMy best model after this step is again a RoBERTa-large with the following setting,\n* Epochs: 20, stop after 8 epochs;\n* Batch size: 8;\n* LR: 2e-5;\n* Evaluate after every 10 steps;\n* Differentiate learning rate of hidden layers;\n* Model: Backbone --> Weighted-mean/max pooling of hidden layers --> Attention pooling over document length --> Output\n* Loss: RMSE + QWK + BT\n* Optimizer and scheduler: AdamW, get_linear_schedule_with_warmup\n\nUsing this setting, I train my models with different types of backbone. The following summarizes my best models according to public LB with each backbone (public LB/private LB),\n* RoBERTa-large with weighted-mean/max pooling: 0.461/0.462;\n* RoBERTa-large with max-pooling only: 0.463/0.466;\n* GPT2-medium: 0.492/0.508;\n* XLNet-large-cased: 0.468/0.473;\n* ALBERT-xlarge-v2: 0.502/0.500;\n* ELECTRA-large: 0.468/0.476;\n* Funnel-transformer/large: 0.477/0.477;\n* BART-large: 0.487/0.491;\n* DeBERTa-large: 0.463/0.468;\n\nFor each backbone, I sometimes changed the setting (evaluate less frequently, omit the QWK loss, training with another fold splitting, etc.) to avoid overfitting.\n\nFinally, I simply took the equal average of all of my 13 models, it gave me 0.453/0.456 on the public/private LB. Ensemble my average model with my teammates' models gave us 0.448/0.452 on the public/private LB.\n\n# What didn't work for me?\n* Data augmentation;\n* Linguistic features;\n* HAN\n\n# Final words\nAlthough the result is not as good as I expected, I still enjoyed the competition a lot. From nearly 0 in NLP, after 3 months, I learned a lot from my teammates and fellow competitors. I hope to meet you in future competitions!\n\n# Notebooks\n* Training of my models: https://www.kaggle.com/shinomoriaoshi/readability-training-notebook\n* Inference of my stacking model: https://www.kaggle.com/shinomoriaoshi/readability-stacking-inference\n* OOF notebook of a RoBERTa-large model: https://www.kaggle.com/shinomoriaoshi/readability-roberta-large-oof",
      "votes": 20
    },
    {
      "id": 1516529,
      "postDate": "2021-09-18T12:18:30.617Z",
      "content": "<p><a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> , I was studying this competition with a more specific focus on interesting loss engg strategies. Of course your solution tops my list. QWK is very apt and BT seems to be even more apt. My question is on the BT loss that you used…which is something of the form of log(1+e to power yhat . pred) for every pair in the batch. But ideally in the formula we should have restricted the prediction and groundtruths to +1 and -1 only for win and loss respectively. But I think you are using the BT scores directly. Is that correct or I am misunderstanding something? and somehow that too seems to work…</p>",
      "rawMarkdown": "@shinomoriaoshi , I was studying this competition with a more specific focus on interesting loss engg strategies. Of course your solution tops my list. QWK is very apt and BT seems to be even more apt. My question is on the BT loss that you used...which is something of the form of log(1+e to power yhat . pred) for every pair in the batch. But ideally in the formula we should have restricted the prediction and groundtruths to +1 and -1 only for win and loss respectively. But I think you are using the BT scores directly. Is that correct or I am misunderstanding something? and somehow that too seems to work...",
      "votes": 1
    },
    {
      "id": 1433007,
      "postDate": "2021-08-03T13:40:37.380Z",
      "content": "<p>Congrats <a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> !! Few weeks ago I considered merging with your team but my ranking was not great and you guys just boosted up to the top so I decided not to since my approach requires a bit more submissions at the time. Could have been a great opportunity to learn from you guys!</p>",
      "rawMarkdown": "Congrats @shinomoriaoshi !! Few weeks ago I considered merging with your team but my ranking was not great and you guys just boosted up to the top so I decided not to since my approach requires a bit more submissions at the time. Could have been a great opportunity to learn from you guys!",
      "votes": 1,
      "replies": [
        {
          "id": 1433465,
          "postDate": "2021-08-03T14:01:45.340Z",
          "content": "<p>Thank you for your kind words. Haha, so now we should be the ones who regret about that. Anyway, congratulation with your solo gold. I look forward to working with you in future competitions.</p>",
          "rawMarkdown": "Thank you for your kind words. Haha, so now we should be the ones who regret about that. Anyway, congratulation with your solo gold. I look forward to working with you in future competitions.",
          "votes": 1
        }
      ]
    },
    {
      "id": 1428643,
      "postDate": "2021-08-03T09:40:46.007Z",
      "content": "<p>Nice…I think this is the first solution post that talks of binning and loss-strategy. I wanted to explore it but couldn't get much of a chance. I wrote some code based on my own logic but scrapped it somewhere mid-way(should have persisted). I strongly felt that outliers skewed MSE and entropy related losses would give much better results…Anyway, good to read about QWK loss. As you mentioned, I guess the trick is to read more related literature…</p>",
      "rawMarkdown": "Nice...I think this is the first solution post that talks of binning and loss-strategy. I wanted to explore it but couldn't get much of a chance. I wrote some code based on my own logic but scrapped it somewhere mid-way(should have persisted). I strongly felt that outliers skewed MSE and entropy related losses would give much better results...Anyway, good to read about QWK loss. As you mentioned, I guess the trick is to read more related literature...",
      "votes": 1,
      "replies": [
        {
          "id": 1428886,
          "postDate": "2021-08-03T10:12:20.187Z",
          "content": "<p>Thank you, I am currently working in an academic environment, so I guess reading literature as my first step is just my habit, haha.</p>",
          "rawMarkdown": "Thank you, I am currently working in an academic environment, so I guess reading literature as my first step is just my habit, haha.",
          "votes": 1
        },
        {
          "id": 1429993,
          "postDate": "2021-08-03T11:11:27.530Z",
          "content": "<p>I need to inculcate that habit for sure :)</p>\n<p>W.r.t HAN, I was finally able to get it to a .473 score - in case you are interested in the model - <a href=\"https://www.kaggle.com/c/commonlitreadabilityprize/discussion/257674\" target=\"_blank\">https://www.kaggle.com/c/commonlitreadabilityprize/discussion/257674</a></p>",
          "rawMarkdown": "I need to inculcate that habit for sure :)\n\nW.r.t HAN, I was finally able to get it to a .473 score - in case you are interested in the model - https://www.kaggle.com/c/commonlitreadabilityprize/discussion/257674",
          "votes": 1
        },
        {
          "id": 1430534,
          "postDate": "2021-08-03T11:45:01.883Z",
          "content": "<p>Thank you very much. I couldn't make HAN model break 0.50x. I will study your solution for sure.</p>",
          "rawMarkdown": "Thank you very much. I couldn't make HAN model break 0.50x. I will study your solution for sure.",
          "votes": 1
        }
      ]
    },
    {
      "id": 1446336,
      "postDate": "2021-08-04T11:55:26.483Z",
      "content": "<p><a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> Thank you so much for inviting me to this exciting competition! I had really great and meaningful days with you and <a href=\"https://www.kaggle.com/lftuwujie\" target=\"_blank\">@lftuwujie</a>.<br>\nAt first I joined this competition as a learning experience, but as our ranking improved, I became more and more serious about it. haha<br>\nLet's win the gold medal together next time!</p>",
      "rawMarkdown": "@shinomoriaoshi Thank you so much for inviting me to this exciting competition! I had really great and meaningful days with you and @lftuwujie.\nAt first I joined this competition as a learning experience, but as our ranking improved, I became more and more serious about it. haha\nLet's win the gold medal together next time!",
      "votes": 2,
      "replies": [
        {
          "id": 1447526,
          "postDate": "2021-08-04T15:12:54.743Z",
          "content": "<p>Haha, after seeing the improvement, we commit to this competition more seriously without realizing it. Let's get a gold next time!</p>",
          "rawMarkdown": "Haha, after seeing the improvement, we commit to this competition more seriously without realizing it. Let's get a gold next time!",
          "votes": 1
        }
      ]
    },
    {
      "id": 1431232,
      "postDate": "2021-08-03T12:17:11.903Z",
      "content": "<p><a href=\"https://www.kaggle.com/shinomoriaoshi\" target=\"_blank\">@shinomoriaoshi</a> very good summary, and it's a great journey to work with both you and <a href=\"https://www.kaggle.com/horsek\" target=\"_blank\">@horsek</a>. I also learnt quite a lot from you too. Looking forward to working together again.</p>",
      "rawMarkdown": "@shinomoriaoshi very good summary, and it's a great journey to work with both you and @horsek. I also learnt quite a lot from you too. Looking forward to working together again.",
      "votes": 2,
      "replies": [
        {
          "id": 1432314,
          "postDate": "2021-08-03T13:11:56.663Z",
          "content": "<p>Thank you for your kind words, Jie. Me too, I am looking forward to working with you in the future.</p>",
          "rawMarkdown": "Thank you for your kind words, Jie. Me too, I am looking forward to working with you in the future.",
          "votes": 1
        }
      ]
    }
  ],
  "complete": true,
  "source": "live",
  "fetched_at": "2026-10-19T23:46:53Z",
  "competition_id": 25914,
  "competition_slug": "commonlitreadabilityprize",
  "team_id": 6747769,
  "solution_url": "/competitions/commonlitreadabilityprize/writeups/trust-what-you-trust-private-28th-public-14th-solu"
}