<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Alex Molas on Medium]]></title>
        <description><![CDATA[Stories by Alex Molas on Medium]]></description>
        <link>https://medium.com/@alexmolasmartin?source=rss-8fa4cb38d347------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Vp2Y-QrrWN5g7dL-qv5Oog.jpeg</url>
            <title>Stories by Alex Molas on Medium</title>
            <link>https://medium.com/@alexmolasmartin?source=rss-8fa4cb38d347------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 23 May 2026 06:46:41 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@alexmolasmartin/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How to initialize your bias.]]></title>
            <link>https://medium.com/@alexmolasmartin/how-to-initialize-your-bias-ccaf67697c17?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/ccaf67697c17</guid>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[neural-networks]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Thu, 23 Feb 2023 23:00:42 GMT</pubDate>
            <atom:updated>2023-06-09T14:55:12.184Z</atom:updated>
            <content:encoded><![CDATA[<h3>tldr</h3><p>Initializing correctly the bias of the last layer of your network can speed up the training process. In this post, I show first how to derive analytically the best values for the biases, and then I run an experiment to show the impact of using the correct bias.</p><p>In particular, the best biases are</p><ul><li>Classification problem with <em>M</em> classes with frequencies <em>F_i</em>, such that <em>F_1 + F_2 + … + F_M = 1</em>, using softmax activation and categorical cross entropy loss</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/994/1*onDA1S_FQmVq2Q6-xpiUqg.png" /></figure><ul><li>Regression problem using L² penalization and linear activation</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/963/1*mnDXCpiXSYh-6RuV1ngo4g.png" /></figure><ul><li>Regression problem using L¹ penalization and linear activation</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*71U5n7o5VNmSgOqcTvVB5A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3OkQAzkDiufNUYCe" /><figcaption>Photo by <a href="https://unsplash.com/@alinnnaaaa?utm_source=medium&amp;utm_medium=referral">Alina Grubnyak</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Motivation</h3><p>These last weeks at work I’ve tuned a neural network that is used to predict arrival times. Basically, the network receives a representation of Stuart’s platform state (where are the drivers, where are the packages, etc.) and outputs the estimated time of arrival of some drivers. We decided to use a deep learning approach to avoid doing boring and unmaintainable feature engineering, but the problem then was to choose the model architecture. If we were solving an image classification problem it would have been trivial to design the architecture, in fact, we wouldn’t need to design anything, just take ResNet50 and fine-tune it. However, our problem is not standard in the deep learning world, so we couldn’t rely on pre-trained models or copy the architecture of previously successful models. We ended up defining an architecture based on convolutions, self-attention, and some dense layers here and there. The results were pretty good -it beat the previous model by +30%- and the model was deployed and everyone was happy.</p><p>However, not everything is always that easy, and at some point, we noticed that our model was overfitting. This wasn’t surprising since the model architecture and training process was never tuned. We just took our initial idea, run some experiments, changed some hyper-params by hand and called it a day. But now that the model is deployed and the stakeholders are happy we are working on tuning the model and making it more competitive. To do so I started with the great post by the great Karpathy <a href="http://karpathy.github.io/2019/04/25/recipe/">here</a>. It’s not the first time I read it, but this time one of the points called specially my attention.</p><blockquote><strong><em>verify loss @ init</em></strong><em>. Verify that your loss starts at the correct loss value. E.g. if you initialize your final layer correctly you should measure </em><em>-log(1/n_classes) on a softmax at initialization. The same default values can be derived for L2 regression, Huber losses, etc.</em></blockquote><p>What does Karpathy mean by verifying that your loss starts at the correct value? How can we achieve the -log(1/n_classes) loss on a softmax? Which are the respective initializations for L2 regressions, Hubber, etc? In this post, I&#39;ll show how to initialize the network to fulfil these requirements and their implications.</p><h3>Problem statement</h3><p>We want to solve the problem of</p><blockquote><em>Which is the best initialization scheme for our network layers?</em></blockquote><p>This is a broad question and has been addressed in a lot of works, such as Glorot and He (add references). In these works, the authors initialize the weights of the layers by sampling from a distribution with some optimized parameters. For instance, Glorot proposes to sample from <em>N(0, 2/(n_i+n_o)</em> and He proposes to sample from <em>N(0, 2/n_i)</em>. The common thing between these approaches is that the mean of the distribution is 0. However, these works focus on the initialization of the weights of all the matrices of our network, while Karpathy talks only about the initialization of the last layer. Then, instead of solving the general question about how to initialize all the layers of the network, I will address the simplified problem of</p><blockquote><em>Which is the best initialization scheme for the last layer of our network?</em></blockquote><h3>Solution</h3><p>In this section, I will answer the above question for several deep learning architectures.</p><h4>Classification</h4><p>Let’s start with a classification problem. We can define a neural network of depth <em>N</em> as a set of weight matrices <em>{W_1, W_2, …, W_N}</em>, a set of biases <em>{b_1, b_2, …, b_N}</em>, a set of non-linear activations <em>{f_1, f_2, …, f_{N-1}}</em> and a final activation layer <em>f_N = softmax</em>. Then the output of the network is defined by the recurrence</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IGwjzjEMOSgh9Y6IT2tqNQ.png" /></figure><p>where <em>x_0 </em>is the input of our network. Now, we are interested in the input to the last layer, ie the softmax layer. As we saw before, the usual initialization uses a normal distribution with mean zero, therefore, if our input of the network is standardized (ie: it has mean zero) we can expect the input to the last layer to have an average <em>W_N * x_N = 0</em>. Therefore, the output of the last layer has the form</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Uy1_AL5-pnsooySzVP_orw.png" /></figure><p>Cool, we have our first result, let’s see now how can we use this to optimize the initial values of <em>b_N</em>. The standard approach to classification problems is to use the cross-entropy loss</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*byvhJcZeThZ2joh2Qhf8gQ.png" /></figure><p>where <em>M</em> is the number of classes of our problem. Therefore, if we want to minimize the expected loss at initialization our best guess is to set <em>b_N</em> such that the network output follows the same distribution of our data. This is if our training dataset has <em>M</em> classes that appear with frequency <em>F_i</em> such that <em>F_1 + F_2 + … + F_M = 1</em> we would like <em>ypred_i = F_i</em>, ie the prediction for the <em>i</em> class has the same probability as in the training dataset. With such an output the expected loss is then</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L9CaqMJ83SNgVJkzxGwnew.png" /></figure><p>where we have used that at initialization <em>y_i</em> and <em>ypred_i</em> are independent, so we can write <em>E[y_i log ypred_i] = E[y_i]log(E[ypred_i])</em>. Notice that if the problem is balanced then we have <em>F_i = 1/M</em> and the expected loss at initialization is <em>L=-log 1/M</em> as Karpathy says.</p><p>Nice, now we know which value to expect for the loss for a correctly initialized last layer, but now we need to know how to set <em>b_N </em>such that the output has the same distribution as the training dataset. To do so we can use the definition of softmax</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*glooYJLM36sorbC06fAjBA.png" /></figure><p>Now, using that the last layer is <em>softmax(b_N)</em> we can write our constraint as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*crnoyxzqffQSDO-1iPf8BA.png" /></figure><p>which has the solution</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9C2MzOY95zUfxS2C916rWg.png" /></figure><p>Therefore, setting <em>k=1</em>, the optimal initialization bias for our last layer has the form</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HDcoqCj5SRMfSoRNLF-46g.png" /></figure><h4>Regression</h4><p>In the last section, I’ve shown how to derive the optimal biases at initialization for a classification problem. In this section, I’ll show how to do the same for a regression problem. The main differences between these approaches are (1) the loss we are using, (2) the last layer activation, and (3) the dimension of the output. In regression, the output is usually 1-dim, ie: we’re just predicting one value, and the last layer activation is just the identity. The more frequent these problems are</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K737_C9DnhFA9iVnLEZLNQ.png" /></figure><p>and</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rf_ivi7amfwibaIUMT30zA.png" /></figure><p>Using the same rationale as before, we want to minimize these losses at initialization. It’s known that without any further information, the value that minimizes MSE is mean(y) and the value that minimizes MAE is median(y). Therefore, since the output of the last layer is just o = b_N (we’re using the identity activation) we have that the values that minimize loss at initialization are</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3cFLlZYpc8j9KEy8Wb9jMA.png" /></figure><p>and</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y3Tl48ydfSIhklmgYB9ajQ.png" /></figure><p>The expected loss at initialization for the MSE is then the variance since</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kHYqO39Skz8ZaiYcAef_vw.png" /></figure><p>and for the MAE</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vxZUpm7e0Zjek1TvD-N22w.png" /></figure><p>which I don’t know if it has a specific name.</p><p>In the original post, Karpathy says that you can also find the optimal values for the Hubber loss, however, unlike with MAE and MSE there’s no closed form for the value that minimizes the Hubber loss ( <a href="https://stats.stackexchange.com/a/298336/350686">explaination here</a>). However, we could obtain the value that minimizes the Hubber loss for our dataset numerically and then use it as the bias of our layer.</p><h3>Results</h3><p>In the previous sections, I explained how to determine the best initial bias through mathematical analysis. However, in the real world, things are not always precise, and data can show that our assumptions were incorrect. In this section, I will conduct some experiments to see the impact of initializing biases correctly.</p><p>To conduct these experiments, I will use the CIFAR-10 dataset. I have made the problem unbalanced by sampling each class. Then, I created two CNN networks: one with the optimal bias strategy defined above and another with the standard initialization. You can find the code used to generate the models and datasets in this <a href="https://github.com/alexmolas/alexmolas.github.io/blob/master/docs/optimal-biases/Optimal%20biases.ipynb">notebook</a>.</p><p>The results are summarized in the following plot. We can see that the network with the optimized initial bias learns faster than the one with the normal network. This effect disappears if we train the network for a sufficient number of epochs. However, training large models is often costly. Therefore, if we can save time and money by setting the correct bias, it is worthwhile.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/718/1*NlMC60qwQsPPKmMd91Uxow.png" /></figure><p><em>Originally published at </em><a href="https://www.alexmolas.com/blog/bias-initialization/"><em>https://www.alexmolas.com</em></a><em> on February 23, 2023.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ccaf67697c17" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Analyzing Chess960 Data]]></title>
            <link>https://medium.com/data-science/analyzing-chess960-data-da5c8cdb01de?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/da5c8cdb01de</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[chess]]></category>
            <category><![CDATA[ab-testing]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Fri, 13 Jan 2023 17:38:23 GMT</pubDate>
            <atom:updated>2023-01-13T17:38:23.365Z</atom:updated>
            <cc:license>https://creativecommons.org/publicdomain/mark/1.0/</cc:license>
            <content:encoded><![CDATA[<h4>Using more than 14M Chess960 games to find if there’s a variation that’s better than the others</h4><p>In this post, I analyze all the available Chess960 games played in Lichess. With this information, and using Bayesian A/B testing, I show that there are no starting positions that favor any of the players more than other positions.</p><p>The original post was published <a href="https://www.amolas.dev/blog/chess-960-initial-position/">here</a>. All the images and plots, unless stated otherwise, are by the author.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8abyPLS5xW9Zdtbu" /><figcaption>Photo by <a href="https://unsplash.com/@hpzworkz?utm_source=medium&amp;utm_medium=referral">Hassan Pasha</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>The World Fischer Random Chess Championship recently took place in Reykjavik, with <a href="https://twitter.com/GMHikaru">GMHikaru</a> emerging victorious. Fischer Random Chess, also known as Chess960, is a unique variation of the classic game that randomizes the starting position of the pieces. The intention behind this change is to level the playing field by eliminating the advantage of memorized openings and forcing players to rely on their skill and creativity.</p><p>As I followed the event, one question came to mind: are there certain initial Chess960 variations that give one player an unfair advantage? As it stands, the standard chess initial position gives white a slight edge, with white usually winning around 55% of game points (<a href="https://en.wikipedia.org/wiki/First-move_advantage_in_chess">ref</a>)) and Stockfish giving white a score of +0.3 (<a href="https://lichess.org/analysis">ref</a>)). However, this edge is relatively small, which is likely one of the reasons why this position has remained the standard.</p><p>There is some work already done about this topic. Ryan Wiley wrote this <a href="https://lichess.org/@/rdubwiley/blog/using-lichesss-public-data-to-find-the-best-chess-960-position/GCpB9WLH">blog post</a> where he analyzes some data from lichess and reach the conclusion that some variations are better than others. In the post, he says that some positions have a higher winning probability for white pieces, but he doesn’t show how significant is this claim. This made me think that maybe his findings need to be revisited. He also trains a ML model on the data in order to determine the winner of a game using as inputs the variation and the ELOs of the players. The resulting model has an accuracy of 65%.</p><p>On the other hand, there’s also this <a href="https://github.com/welyab/chess960-win-by-position-setup">repo</a> with the statistics for 4.5 millions games (~4500 games per variation). In this repo the biggest difference for white and black are listed, but again no statistical significance is given.</p><p>Finally, there’s also some research about this topic focused in computer analysis. In this <a href="https://docs.google.com/spreadsheets/u/1/d/1JVT6_ROOlCTtMmazzBe0lhcGv54rB6JCq67QOhaRp6U/edit#gid=0">spreadsheet</a> there’s the Stockfish evaluation at depth ~40 for all the starting positions. Interestingly there’s no position where Stockfish gives black player an advantage. There’s also this <a href="http://computerchess.org.uk/ccrl/404FRC/opening_report_by_white_score.html">database</a> with Chess960 games between different computer engines. However, I’m currently only interested in analyzing human games, so I’ll not put a lot of attention to this type of games. Maybe in a future post.</p><p>Since none of the previous work has addressed the problem of assigning statistical confidence to the winning chances to each variation of Chess960 I decided to give it a try.</p><h3>tl;dr</h3><p>In this post I analyze all the available Chess960 games played in Lichess. With this information I show that</p><ol><li>using bayesian AB testing I show that there are no starting positions that favor any of the players more than other positions</li><li>also, the past winning rate of a variation doesn’t predict the future winning rate of the same variation</li><li>and stockfish evaluations don’t predict actual winning rates for each variation</li><li>finally, knowing the variation being played doesn’t help to predict the winner</li></ol><h3>Data</h3><p>Lichess—the greatest chess platform out —maintains a <a href="https://database.lichess.org/">database</a> with all the games that have been played in their platform. To do the analysis, I downloaded ALL the available Chess960 data (up until 31–12–2022). For all the games played I extracted the variation, the players ELO and the final result. The data is available on <a href="https://www.kaggle.com/datasets/alexmolas/chess-960-lichess">Kaggle</a>. The scripts and notebooks to donwload and process the data are available on this <a href="https://github.com/AlexMolas/chess-960">repo</a>.</p><p>The data I used is released under the <a href="https://tldrlegal.com/license/creative-commons-cc0-1.0-universal">Creative Commons CC0 license</a>, which means you can use them for research, commercial purpose, publication, anything you like. You can download, modify and redistribute them, without asking for permission.</p><h3>Mathematical framework</h3><h4>Bayesian A/B testing</h4><p>According to the prior work mentioned above some variations are better than others. But how can we be sure that these differences are statistically significant? To answer this question we can use the famous A/B testing strategy. This is, we start with the hypothesis that variation <em>A</em> has bigger winning chances than variation <em>B</em>. The null hypothesis is then that and <em>A</em> and <em>B </em>have the same winning rate. To discard the null hypothesis we need to show that the observed data is so extreme under the assumption of the null hypothesis that it doesn’t make sense to still believe in it. To do that we’ll use bayesian A/B testing <a href="https://www.amolas.dev/blog/chess-960-initial-position/#fn:1">1</a>.</p><p>In the bayesian framework, we assign to each variation a probability distribution for the winning rate. This is, instead of saying that variation <em>A </em>has a winning rate of X% we say that the winning rate for <em>A</em> has some probability distribution. The natural choice when modelling this kind of problem is to choose the beta distribution (<a href="https://www.countbayesie.com/blog/2015/4/25/bayesian-ab-testing">ref</a>).</p><p>The beta distribution is defined as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4y0Ucp-Vy2GhM_xhm_WSUQ.png" /></figure><p>where <em>B(a, b) = Γ(a)Γ(b)/Γ(a+b), Γ(x)</em> is the gamma function, and for positive integers is <em>Γ(n) = (n-1)!. </em>For a given variation, the parameter α can be interpreted as the number of white wins plus one, and β as the number of white losses plus one.</p><p>Now, for two variations <em>A</em> and <em>B</em> we want to know how probable is that the winning rate of <em>A</em> is bigger than the winning rate of <em>B</em>. Numerically, we can do this by sampling <em>N</em> values from <em>A</em> and <em>B</em>, namely <em>w_A </em>and <em>w_B </em>and compute the fraction of times that <em>w_A &gt; w_b</em>. However, we can compute this analytically, starting with</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JewZpqYynDk6mjQGSZcfpA.png" /></figure><p>Notice that the beta function can give huge numbers, so to avoid overflow we can transform it using log. Fortunately, many statistical packages have implementations for the log beta function. With this transformation, the addends are transformed to</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LxDVrDFiUJ5PLkmj-rpTmQ.png" /></figure><p>This is implemented in python, using the scipy.special.betaln implementation of log B(a, b) , as</p><pre>import numpy as np<br>from scipy.special import betaln as logbeta<br><br>def prob_b_beats_a(n_wins_a: int, <br>                   n_losses_a: int, <br>                   n_wins_b: int, <br>                   n_losses_b: int) -&gt; float:<br><br>  alpha_a = n_wins_a + 1<br>  beta_a = n_losses_a + 1<br><br>  alpha_b = n_wins_b + 1<br>  beta_b = n_losses_b + 1<br>  probability = 0.0<br>  for i in range(alpha_b):<br>    total += np.exp(<br>      logbeta(alpha_a + i, beta_b + beta_a)<br>      - np.log(beta_b + i)<br>      - logbeta(1 + i, beta_b)<br>      - logbeta(alpha_a, beta_a)<br>    )<br>  return probability</pre><p>With this method, we can compute how probable is for a variation to be better than another, and with that, we can define a threshold <em>α</em> such that we say that variation <em>B</em> is significantly better than variation <em>A</em> if <em>Pr(p_A&gt;p_B)&lt;1-α</em>.</p><p>Below you can see the plot of some beta distributions. In the first plot, the parameters are <em>α_A</em>= 100, <em>β_A</em>=80, <em>α_B</em>=110 and <em>β_B=</em>70.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/894/1*AMVNjdaJsjf5MogzJS4_TQ.png" /><figcaption>Beta distributions with parameters <em>α_A</em>= 100, <em>β_A</em>=80, <em>α_B</em>=110 and <em>β_B=</em>70</figcaption></figure><p>In this second plot, the parameters <em>α_A</em>= 10, <em>β_A</em>=8, <em>α_B</em>=11 and <em>β_B=</em>7.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/870/1*MLZn-9gM_A2e22Iky5H6jg.png" /><figcaption>Beta distributions with parameters <em>α_A</em>= 10, <em>β_A</em>=8, <em>α_B</em>=11 and <em>β_B=</em>7</figcaption></figure><p>Notice that, even in both cases the winning rates are the same, but the distributions look different. This is because in the first case, we’re more sure about the actual rate, and this is because we’ve observed more points than in the second case.</p><h4>Family-wise error rate</h4><p>Usually, in A/B testing one just compares two variations, eg: conversions in a website with white background vs blue background. However, in this experiment, we’re not just comparing two variations, but we’re comparing all the possible pairs of variations -remember that we want to find if there is at least one variation that is better than another variation- therefore, the number of comparisons we are doing is 960*959/2 ~ 5e5. This means that using the typical value of <em>α=0.05</em> is an error because we need to take into consideration that we’re doing a lot of comparisons. For instance, assuming that the winning probabilities distributions are the same for all the initial positions and using the standard one would have a probability</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-E4Fmk0jKQcE-jwvEh2kFg.png" /></figure><p>of at least observing one false positive! This means that even if there’s no statistically significant difference between any pair of variations we’ll still observe at least one false positive. If we want to keep the same <em>α</em> but increase the number of comparisons from 2 to we need then to define an effective <em>α</em> like</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H_QQpf1kpKG6rgyeybhOLA.png" /></figure><p>and solving</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ubw-cWpsRfRVu9Q_crbufw.png" /></figure><p>Plugging our values we finally get <em>α_eff =1e-7</em>.</p><h4>Train/Test split</h4><p>In the previous sections, we developed the theory to determine if a variation is better than another variation according to the observed data. This is, after having seen some data we build a hypothesis of the form variation B is better than variation A . However, we can’t test the truth of this hypothesis using the same data we used to generate the hypothesis. We need to test the hypothesis against a set of data that we haven’t used yet.</p><p>To make this possible we will split the full dataset into two disjoint train and test datasets. The train dataset will be used together with the bayesian A/B testing framework to generate hypotheses of the form <em>B&gt;A</em>. And then, using the test dataset we’ll check if the hypotheses hold.</p><p>Notice that this approach makes sense only if the distribution of winning rates doesn’t change over time. This seems a reasonable assumption since, AFAIK, there haven’t been big theoretical advances that have changed the winning probability of certain variations during the last few years. In fact, minimizing the theory and preparation impact on game results is one of the goals of Chess960.</p><h4>Data preparation</h4><p>In the previous sections we have implicitly assumed that a game can be either won or lost, however, it can also be drawn. I’ve assigned 1 point for a victory, 1/2 point for a draw, and 0 points for a loss, which is the usual approach in chess games.</p><h3>Results</h3><p>In this section, we will apply all the techniques explained above to the lichess dataset. In the dataset, we have more than 13M games, which is ~14K games per variation. However, the dataset contains games for a huge variety of players and time controls (from ELO 900 to 2000, and from blitz to classic games). Therefore, doing the comparisons using all the games would mean ignoring confounder variables. To avoid this problem I’ve only used games for players with an ELO in the range (1800, 2100) and with a blitz time control. I’m aware that these filters do not resemble the reality of top-level contests such as the World Fischer Random Chess Championship, but in lichess data, there are not a lot of classical Chess960 games for high-rated players (&gt;2600), so I will just use the group with more games. After applying these filters we end up with a dataset with ~2.4M games, which is ~2.5K games per variation.</p><p>The train/test split has been done using a temporal split. All the games prior to 2022-06-01 are part of the training dataset, and all the games after that date are part of the testing dataset, which accounts for ~80% of the data for training and ~20% for testing.</p><h4>Generating hypotheses</h4><p>The first step is to generate a set of hypotheses using A/B testing. The number of variation pairs to compare is pretty big (1e5) and testing all of them would take a lot, so we’ll just compare the 20 variations with the highest winning rates against the 30 variations with the lowest winning rates. This means we’ll have 900 pairs of variations to compare. Here we see the pair of variations with the bigger difference in the train dataset</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sE5D-D7jpoNHqFJ0Hcau2Q.png" /></figure><p>Notice that the <em>α</em> for these variations is bigger than <em>α</em>_<em>eff</em>, which means that the difference is not significant. Since these are the variations with the higher difference we know that there’s not any variation pair with a statistically significant difference.</p><p>Anyway, even if the difference is not significant, with this table one can hypothesize that variation rnnqbkrb is worse than variation bbqrnkrn. If we check these variation values in the test dataset we get</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uZ7aqSkxld4jlwMUltzy_A.png" /></figure><p>Notice that the “bad” variation still has a winning rate lower than the “good” variation, however, it has increased from 0.473 to 0.52, which is quite a lot. This brings a new question: do past variation performance guarantee future performance?</p><h4>Past vs Future performances</h4><p>In the last section, we have seen how to generate and test hypotheses, but we have also noticed that the performance of some variations changes over time. In this section, we’ll analyze this question more in detail. To do so, I have computed the winning rate in the train and test datasets and plotted one against the other.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/848/1*ZqG_jVmfnKFKeEVjWZtbSQ.png" /><figcaption>Train vs Test winning rates</figcaption></figure><p>As we can see, there’s no relation between past and future winning rates!</p><h4>Evaluation vs Rates</h4><p>We’ve seen that past performances do not guarantee future performances, but do Stockfish evaluations predict future performances? In the following plot, I show the evaluation of Stockfish for each variation and the corresponding winning rate in the dataset.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/816/1*HP_PpFv7nWZhnoT94O_LKA.png" /><figcaption>Stockfish Evaluation vs Winning rate for each variation</figcaption></figure><h4>Machine learning model</h4><p>Until now we’ve seen that there are no better variations in the Chess960 game and that previous performance is no guarantee of future performance. In this section, we’ll see if we can predict which side is going to win a match based on the variation and the ELO of the players. To do so I’ll train an ML model.</p><p>The features of the model are the ELO of the white and black player, the variation being played, and the time control being used. Since the cardinality of the variation feature is huge I’ll use CatBoost, which has been specifically designed to deal with categorical features. Also, as a baseline, I’ll use a model that predicts that white wins if White ELO &gt; Black ELO, draws if White ELO == Black ELO, and losses if White ELO &lt; Black ELO. With this experiment, I want to see which is the impact of the variation in the expected winning rate.</p><p>In the next tables, I show the classification reports for both models.</p><ul><li>CatBoost model</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*C4dZz3NbziuJz2GRmqa1Og.png" /></figure><ul><li>Baseline model</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*wZL7PHfjsRTeMbOiXzuz2A.png" /></figure><p>From these tables, we can see that the CatBoost and the baseline model have almost the same results, which means that knowing the variation being played doesn’t help to predict the result of the game. Notice that the results are compatible with the ones obtained <a href="https://lichess.org/@/rdubwiley/blog/using-lichesss-public-data-to-find-the-best-chess-960-position/GCpB9WLH">here</a> (accuracy ~65%), but in the linked blog it’s assumed that the knowing the variation helps to predict the winner, and we have seen that this is not true.</p><h3>Conclusions &amp; Comments</h3><p>In this post, I’ve shown that</p><ul><li>using the standard threshold to determine significant results is not valid when having more than one comparison, and it needs to be adjusted.</li><li>there are no statistically significant differences in the winning rates, ie: we can’t say that a variation is preferable for white than another.</li><li>past rates don’t imply future rates.</li><li>stockfish evaluations don’t predict winning rates.</li><li>knowing which variation is being played doesn’t help to predict the result of a match.</li></ul><p>However, I’m aware that the data I’ve used is not representative of the problem I wanted to study in the first place. This is because the data accessible at Lichess is skewed towards non-professional players, and even though I’ve used data from players with a decent ELO (from 1800 to 2100) they are pretty far from the players participating in the Chess960 World Cup (&gt;2600). The problem is that the number of players with an ELO &gt;2600 is very low (209 according to <a href="https://www.chess.com/players?page=10">chess.com</a>), and not all of them play regularly Chess960 in Lichess, so the number of games with such characteristics is almost zero.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da5c8cdb01de" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/analyzing-chess960-data-da5c8cdb01de">Analyzing Chess960 Data</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Can Random Forests Overfit?]]></title>
            <link>https://medium.com/@alexmolasmartin/can-random-forests-overfit-a743755251b4?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/a743755251b4</guid>
            <category><![CDATA[random-forest]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[mathematics]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Fri, 30 Dec 2022 09:15:37 GMT</pubDate>
            <atom:updated>2022-12-30T10:59:44.290Z</atom:updated>
            <content:encoded><![CDATA[<h3>Introduction</h3><p>If you’re like me — a DS/MLE with practical experience — you may be surprised by the title of this post. At first, I assumed the answer was a clear “yes.” However, when someone with more experience asked me this question, I realized that the answer might not be as obvious as I thought. And after some googling I found these quotes by Leo Breiman, the creator of the Random Forest algorithm</p><blockquote><em>Random forests does not overfit. You can run as many trees as you want. </em>(<a href="https://www.stat.berkeley.edu/~breiman/RandomForests/cc_home.htm#remarks">website</a>)</blockquote><p>and</p><blockquote><em>Random forests do not overfit as more trees are added, but produce a limiting value of the generalization error. </em>(<a href="https://www.stat.berkeley.edu/~breiman/randomforest2001.pdf">paper</a>)</blockquote><p>Can random forests really not overfit? It seems counterintuitive, but in this post I will explain with math and experiments why it is possible under certain conditions.</p><p>To write this post I’ve used this <a href="https://arxiv.org/pdf/1407.7502.pdf">PhD dissertation</a> by <a href="https://glouppe.github.io/">Gilles Loupe</a> (one of the creators of sklearn), this <a href="https://mljar.com/blog/random-forest-overfitting/">blog post</a> that studies the same question, and the original <a href="https://www.stat.berkeley.edu/~breiman/randomforest2001.pdf">paper</a> by Leo Breiman.</p><h3>Maths</h3><h4>Model definition</h4><p>Let’s start by defining mathematically what we mean by a random forest. Assume that we already know how to create single classification trees. Let’s call the dataset <em>D</em>, <em>T</em> a decision tree , and <em>θ</em> the hyperparameters of the tree. The model generated by these pieces is then <em>ψ_{D, θ} = T(D, θ),</em> . For the sake of simplicity, let’s assume that all hyperparameters are fixed and that the only free hyperparameter is the random seed, eg: the values max_depth , min_sample_split , min_sample_leaf , etc. are fixed.</p><p>Random forests are a combination of trees such that each tree depends on the values of a random dataset sampled independently and with the same distribution for all trees in the forest. Therefore, for a set of <em>M</em> randomized trees <em>{ψ_{D, θi} | i = 1,…,M}</em> that learn from the same dataset <em>D </em>we define a random forest model as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*n5goviRl75np-H1kwqwNbw.png" /><figcaption>Random Forest model</figcaption></figure><h4>Bias-Variance decomposition</h4><p>Now that we know how to build a random forest let’s study its bias and variance, which are the key ingredients of overfiting. For a pair <em>x</em> and <em>y</em>, where <em>x</em> is the vector of features, and <em>y</em> is the target value, let’s define for a single tree <em>ψ_{D, θi}</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Kjh_LMgOZdJ2UGBIEi7wMQ.png" /></figure><p>and</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ItHukcxwCd8ndMHZvS3-kQ.png" /></figure><p>Also, we know that the expected generalization error of a model decomposes as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VtTPMi6lovQtlptE54j8SA.png" /></figure><p>We’re interested in the generalization error that our random forest has depending on its hyperparameters. Let’s start with the bias, which is defined as the difference between the actual value and the model’s expected value</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ofNVTSOBSYI9cY6XdGw0Xw.png" /></figure><p>because random variables <em>θi</em> are independent and follow the same distribution. Therefore</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kx5vt3xz2FbUVK20Q1hvUA.png" /></figure><p>which means that the bias of the random forest is the same as the bias of a single tree, so combining decision trees has no effect on the bias.</p><p>Let’s look now at what happens with the variance. After some maths (page 66 of <a href="https://arxiv.org/pdf/1407.7502.pdf">this paper</a>) it can be shown that the variance of a random forest of <em>M </em>trees is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YNgzwqvR1647CKAyQKOI3g.png" /></figure><p>where <em>ρ(x)</em> is the Pearson correlation between the predictions of two decision trees.</p><p>Therefore, the generalization error is given by</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ebsF10mjzJLv5WspCNm6iQ.png" /><figcaption>Generalization error of a Random Forest model</figcaption></figure><h4>Overfitting</h4><p>Now that we know how to compute the generalization error of our model we can study how it behaves when changing the hyperparameters. Since each tree is built using only a bootstrap of the full dataset we have <em>ρ(x)&lt;1</em>, ie: different trees in the random forest give different predictions for the same input. Therefore, if <em>K&gt;L</em> we have</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f2CICSuhWsIIHwzzh0JZ8Q.png" /></figure><p>Also, when <em>M </em>→<em>∞</em> we have</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yki0Sy58EVAFkFG6C3P1lw.png" /></figure><p>As a result, the expected generalization error of the random forest is smaller than the expected error of a single decision tree. This is, as we add more trees to the random forest the generalization error reduces.</p><p>Therefore, adding more trees to the model doesn’t make the model more prone to overfitting, in fact it reduces the generalization error! But it doesn’t mean that a random forest can’t overfit. For example, imagine the simplest case of a random forest with only one tree. If we allow the tree to have an infinite depth it can learn almost perfectly the training data (up to collisions), but then it’ll fail to generalize to other data.</p><h3>Experiment</h3><p>In the above section, we’ve seen mathematically that adding more trees to the model shouldn’t make the model overfit. In this section, we are going to take a more practical approach and show it directly with some experiments.</p><p>To study the effect of the number of trees on overfitting we’ll generate a synthetic dataset using the sklearn.datasets.make_regression method.</p><pre>from sklearn.datasets import make_regression<br>from sklearn.model_selection import train_test_split<br><br>X, y = make_regression(n_samples=10_000, <br>                       n_features=50, <br>                       n_informative=30, <br>                       noise=10)<br>X_train, X_test, y_train, y_test = train_test_split(X, y, <br>                                                    test_size=0.3,<br>                                                    random_state=42)</pre><p>Now we can train multiple random forests, each one with a different number of trees.</p><pre>from sklearn.ensemble import RandomForestRegressor<br>from sklearn.metrics import mean_squared_error<br><br>rf = RandomForestRegressor()<br>n_estimators = []<br>train_mse = []<br>test_mse = []<br>for n in range(1, 50, 1):<br>  rf.n_estimators = n<br>  rf.fit(X_train, y_train)<br>  y_train_predicted = rf.predict(X_train)<br>  y_test_predicted = rf.predict(X_test)<br>  mse_train = mean_squared_error(y_train, y_train_predicted)<br>  mse_test = mean_squared_error(y_test, y_test_predicted)<br>  n_estimators.append(n)<br>  train_mse.append(mse_train)<br>  test_mse.append(mse_test)</pre><p>And we can finally plot the MSE for both train and test datasets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*Vaq2VV1EzOqhhvxFPrsnpA.png" /></figure><p>As you can see, there’s a gap between the train and test MSE which means that there’s some overfitting. However, as we add more trees the gap between the train and test MSE doesn’t increase, which means that adding more trees doesn’t make the model more prone to overfitting. In fact, as we add more trees to the model the gap between the curves reduces. In the following plot we see how Gap = Test MSE - Train MSE reduces as n_estimators increases</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/898/1*sxGi4WsgKSXIVjriVKUMaA.png" /></figure><p>Notice also that the gap between the train and test curves — aka overfitting — can be changed by tuning other hyperparameters, such as max_depth . In the next plot I show how the gap changes depending on max_depth and n_estimators.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/882/1*BKTHYYPQbnugkji0eP8Pvg.png" /></figure><h3>Conclusions</h3><ul><li>Yes, random forests can overfit since a single tree can overfit.</li><li>The bias of random forests is the same as the bias of a single tree, however, the variance decreases as we add more trees to the model, and this is where the power of random forests comes from.</li><li>Overfitting in a random forest model can be tuned using other hyperparameters such as max_depth , but increasing n_estimators doesn’t increase the gap between train and test performance.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a743755251b4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vectorizing difficult operations: a trick for filtering lists of lists.]]></title>
            <link>https://medium.com/@alexmolasmartin/vectorizing-difficult-operations-a-trick-for-filtering-lists-of-lists-a3b86fcd7aa3?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/a3b86fcd7aa3</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Fri, 15 Jul 2022 09:15:19 GMT</pubDate>
            <atom:updated>2022-07-15T09:15:19.613Z</atom:updated>
            <content:encoded><![CDATA[<p>We all know that vectorizing our operations is the best practice, but what to do when there’s no trivial way of doing it?</p><p>In this post, I show how to use a boolean trick to vectorize complex operations involving lists.</p><p>The original story was posted <a href="https://www.amolas.dev/posts/boolean-algebra-sets-and-filters/">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ts2TGojtJOjgDEh2" /><figcaption>In this post, we’ll see how to accelerate a specific type of operation involving lists. Photo by <a href="https://unsplash.com/@claybanks?utm_source=medium&amp;utm_medium=referral">Clay Banks</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>One of the most covered topics about pandas optimization is how to apply functions over columns. One option is to use apply but is not a good idea (<a href="https://python.plainenglish.io/pandas-how-you-can-speed-up-50x-using-vectorized-operations-d6e829317f30/">maybe</a> <a href="https://stackoverflow.com/questions/27575854/vectorizing-a-function-in-pandas">this</a> <a href="https://medium.com/analytics-vidhya/understanding-vectorization-in-numpy-and-pandas-188b6ebc5398">is</a> <a href="https://towardsdatascience.com/efficient-pandas-apply-vs-vectorized-operations-91ca17669e84">one</a> <a href="https://medium.com/productive-data-science/why-you-should-forget-for-loop-for-data-science-code-and-embrace-vectorization-696632622d5f">of</a> <a href="https://morioh.com/p/7ba40acefa19">the</a> <a href="https://medium.com/analytics-vidhya/understanding-vectorization-in-numpy-and-pandas-188b6ebc5398">topics</a> <a href="https://datascience.blog.wzb.eu/2018/02/02/vectorization-and-parallelization-in-python-with-numpy-and-pandas/">with</a> <a href="https://www.architecture-performance.fr/ap_blog/applying-a-row-wise-function-to-a-pandas-dataframe/">more</a> <a href="https://ogeek.cn/qa/?qa=383643/">posts ever</a>). It’s known that the optimal solution is to use vectorization, however, some functions that can’t be vectorized easily. What to do in these cases?</p><p>In this post, I’ll present a trick to vectorize operations that involve checking the intersection between lists and other list operations. In particular, I will show how using boolean algebra enables vectorization and can speed up our computations.</p><blockquote><em>All the code and data used in this post is available in this </em><a href="https://github.com/AlexMolas/fast-filters"><em>repo</em></a><em>.</em></blockquote><h3>Introduction</h3><p>Imagine you are working on an ice cream start-up, which sells ice-creams of different flavors. There are ice creams of only one flavor, ice creams of two flavors, ice creams of three flavors, and so on. Every time that an ice cream is purchased your company stores information about the order and the rating -from 1 to 10- that the client gave to the ice cream.</p><p>The resulting table looks like this</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/833/1*mSUCFOsByxrqzqkPQ-MEiQ.png" /></figure><p>Now, your company wants you to answer some questions</p><ul><li>When the flavor X is included in the ice cream, which is the average rating?</li><li>When the flavors X and Y are included in the ice cream, which is the average rating?</li></ul><p>For example, using the table above the answers would be</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/860/1*slnIXyjVtH0KK2uKM4AeYA.png" /></figure><p>and</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/859/1*lLIOpTcMYNrSKRR0h7O7lQ.png" /></figure><p>In general, you want to be able to answer the question</p><ul><li>When flavors {X_1, X_2, ..., X_N} are included in the ice cream, which is the average rating?</li></ul><p>The question now is how to run this analysis efficiently. More specifically, how to filter a set of lists based on another list fast. Surprisingly, the answer to this question comes from boolean algebra and binary code. Stay with me and I’ll show you how to vectorize these queries!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lj4Q7F4WZau5x7XY" /><figcaption>Which is the best ice cream flavor? Photo by <a href="https://unsplash.com/@lamaroscu?utm_source=medium&amp;utm_medium=referral">Lama Roscu</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Problem Statement</h3><p>In this section, I’ll generalize the above problem using some mathematical notation.</p><p>Given a vocabulary v, two lists of lists, L=(l_1, ..., l_n) and M = (m_1, ..., m_n)where l_i∈ v and m_i∈ v and a predicate of the form P(l_1, m_i)the question is to find an efficient way to obtain the indices {i | P(l_i, m_i)</p><p>For example</p><ol><li>Given the lists L=((1, 2), (2, 3, 4), (3)) and M=((1), (1, 2), (1, 2, 3))</li><li>Given the predicate P(l_i, m_i) = m_i ∈ l_iwhich checks if a list is contained in another list.</li><li>Then, the resulting index is 1, since (1)∈ (1, 2).</li></ol><h3>Solutions</h3><p>Even though there are multiple options to work with datasets in python (<a href="https://www.pola.rs/">polars</a>, <a href="https://dask.org/">dask</a>, <a href="https://vaex.io/">vaex</a>, etc.) let’s assume that we’re using <a href="https://pandas.pydata.org/">pandas</a>. Let’s also assume that the dataframe looks like</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/833/1*YnRtYGbURFcCSYMaAOSdKw.png" /></figure><p>and we want to find all the indices where the list l_i is contained in m_i.</p><p>We’ll explore two different solutions to the problem of filtering a dataframe using lists. The first one it’s going to be a brute force one (using apply), and the other one it’s going to apply some boolean algebra tricks to vectorize the process.</p><h3>Brute force solution</h3><p>The most immediate solution is just to iterate over all the rows of the dataframe and check if the intersection is the empty set or not.</p><pre><strong>def</strong> <strong>brute_force_not_null_intersection</strong>(df: pd.DataFrame, c1: str, c2: str):<br>    <strong>def</strong> <strong>f</strong>(r):<br>        e1 <strong>=</strong> r[c1]<br>        e2 <strong>=</strong> r[c2]<br>        <strong>return</strong> len(set(e1) <strong>&amp;</strong> set(e2)) <strong>!=</strong> 0<br>    <strong>return</strong> df.apply(f, axis<strong>=</strong>1)</pre><p>However, this is not efficient, since we know that vectorization is key, and using apply is not a good idea. So the natural next step would be to vectorize this function. But how can we do it? It doesn’t seem trivial how can we write a vectorizable method for checking if a list is in another list.</p><h3>Binary representation</h3><p>Here’s where the interesting things start. In this section, we’ll study how we can represent our lists as binary numbers and vectorize the operations.</p><p>The first step is to translate every list into an integer. To do that we can use the following algorithm:</p><ol><li>Given sets L and M, construct the vocabulary v.</li><li>Express every element in L and Mas a binary number, where in position jthere’s a 1if v[j]∈ l_iand 0otherwise.</li></ol><p>For example, the binary representation for l=(b,d) if v=(a,b,c,d) is 1010.</p><p>In python, we can do this transformation with the following snippet</p><pre><strong>def</strong> <strong>list_to_int</strong>(vocabulary: Sequence[str], l: Sequence[str]):<br>    word_to_exponent <strong>=</strong> {k: i <strong>for</strong> i, k <strong>in</strong> enumerate(vocabulary)}<br>    <strong>return</strong> sum(2 <strong>**</strong> word_to_exponent[k] <strong>for</strong> k <strong>in</strong> l)</pre><p>With this approach, we can transform our lists into integers, and we know that doing vectorized operations in pandas with integer columns is easy-peasy, amazing!</p><h3>Binary operations</h3><p>Cool, we know how to represent sets as binary numbers, but how can we use that to check if a list l contains list m? If we dust off the boolean algebra university books we’ll see it’s as easy as doing l == (l &amp; m). Let’s work through an example</p><ul><li>Given v = (a, b, c, d), l = (a, b, c) and m = (a, b).</li><li>The binary representation is l -&gt; 0111 and m -&gt; 0011</li><li>Now we want to check m == (l &amp; m).</li><li>l &amp; m = 0111 &amp; 0011 = 0011</li><li>m == (l &amp; m) is 0011 == 0011, which holds. Therefore, we can say that l contains m.</li></ul><p>Here’s the python implementation of this predicate</p><pre><strong>def</strong> <strong>vectorized_not_null_intersection</strong>(df: pd.DataFrame, c1: str, c2: str) <strong>-&gt;</strong> pd.Series:<br>    <strong>return</strong> (df[c1] <strong>&amp;</strong> df[c2]) <strong>!=</strong> 0</pre><p>The great thing about this trick is that allows us to take advantage of vectorization. In the following section, we’ll compare the brute force and the binary trick approaches.</p><h3>Results</h3><p>To compare both approaches we’re going to use a synthetic dataset with N=10^6 examples, a vocabulary size of |v|=15, and a sequence maximum length of 5. To generate your own examples you can use this <a href="https://github.com/AlexMolas/fast-filters/blob/main/scripts/generate_examples.py">script</a>.</p><p>The results for the brute force algorithm are</p><pre><strong>%%</strong>time<br><br>index_brute <strong>=</strong> brute_force_not_null_intersection(df, &#39;elements_1&#39;, &#39;elements_2&#39;)<br><br><strong>&gt;&gt;</strong> CPU times: user 10.9 s, sys: 125 ms, total: 11 s<br><strong>&gt;&gt;</strong> Wall time: 11.1 s</pre><p>and using the binary trick</p><pre><strong>%%</strong>time</pre><pre>converter <strong>=</strong> Converter(vocabulary<strong>=</strong>vocabulary)<br>df[&#39;elements_1_bin&#39;] <strong>=</strong> df[&#39;elements_1&#39;].map(converter.convert)<br>df[&#39;elements_2_bin&#39;] <strong>=</strong> df[&#39;elements_2&#39;].map(converter.convert)<br>index_vec <strong>=</strong> vectorized_not_null_intersection(df, &#39;elements_1_bin&#39;, &#39;elements_2_bin&#39;)</pre><pre><strong>&gt;&gt;</strong> CPU times: user 3.54 s, sys: 52.4 ms, total: 3.6 s<br><strong>&gt;&gt;</strong> Wall time: 3.63 s</pre><p>This is a speed-up of x3, which is amazing. However, most of the time was spent in the map operation when constructing the binary representations of the lists, but once these representations are created, the not_null_intersection is much faster!</p><pre><strong>%%</strong>time</pre><pre>index_vec <strong>=</strong> vectorized_not_null_intersection(df, &#39;elements_1_bin&#39;, &#39;elements_2_bin&#39;)</pre><pre><strong>&gt;&gt;</strong> CPU times: user 3.61 ms, sys: 1.6 ms, total: 5.21 ms<br><strong>&gt;&gt;</strong> Wall time: 4.05 ms</pre><p>A speed-up of <strong>x2500</strong>, OMG! This means that, after an initial overhead of translating lists to integers, we can use vectorized operations to solve our problem and obtain an incredible reduction in computation time.</p><h3>Conclusions</h3><p>In this post, we have seen how to transform lists into binary numbers and then use boolean algebra to vectorize difficult operations. With this approach, we’ve seen a huge boost (<strong>x2500</strong>) in the performance.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3b86fcd7aa3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why do we minimize the mean squared error?]]></title>
            <link>https://medium.com/data-science/why-do-we-minimize-the-mean-squared-error-3b97391f54c?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/3b97391f54c</guid>
            <category><![CDATA[statistics]]></category>
            <category><![CDATA[math]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[maximum-likelihood]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Fri, 27 May 2022 15:55:13 GMT</pubDate>
            <atom:updated>2022-05-27T15:55:13.331Z</atom:updated>
            <content:encoded><![CDATA[<h4>Have you ever wondered why we minimize the squared error? In this post, I’ll show the mathematical reasons behind the most famous loss function.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9t8eqhmwhEeQ3bmh" /><figcaption>Photo by <a href="https://unsplash.com/@phsecan?utm_source=medium&amp;utm_medium=referral">Peter Secan</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a>. Minimizing the squared error is like trying to reach the bottom of a valley.</figcaption></figure><p>One of the first topics that one encounters when learning machine learning is linear regression. It’s usually presented as one of the simplest algorithms for regression. In my case, when I studied linear regression — back in the days during my physics degree — I was told that linear regression tries to find the line that minimizes the sum of squared distances to the data. Mathematically this is <em>y_p=ax+b</em>, where <em>x</em> is the independent variable that will be used to predict <em>y_t</em>, <em>y_p</em> is the corresponding prediction, and <em>a</em> and <em>b</em> are the slope and intercept. For the sake of simplicity let’s assume that <em>x∈R</em> and <em>y∈R</em>. The quantity that we want to minimize — aka the loss function — is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/413/1*YFYeu6Ej46t_RTh2r_cVfQ.png" /><figcaption>MSE Loss Function</figcaption></figure><p>The intuition behind this loss is that we want to penalize more big errors than small errors, and that’s why we’re squaring the error term. With this particular choice of the loss functions, it’s better to have 10 errors of 1 unit than 1 error of 10 units — in the first case, we would increase the loss by 10 squared units, while in the second case we would increase it by 100 squared units.</p><p>However, while this is intuitive, it also seems quite arbitrary. Why use the square function and not the exponential function or any other function with similar properties? The answer is that the choice of this loss function is not that arbitrary, and it can be derived from more fundamental principles. Let me introduce you to <em>maximum likelihood estimation</em>!</p><h3>Maximum Likelihood Estimation</h3><p>In this section, I’ll present maximum likelihood estimation, one of my favorite techniques in machine learning, and I’ll show how can we use this technique for statistical learning.</p><h3>Basics</h3><p>First of all some theory. Consider a dataset <strong><em>X</em></strong><em>={x1,…,xn}</em> of <em>n</em> data points drawn independently from the distribution <em>p_real(x)</em>. We have also the distribution <em>p_model(θ,x)</em>, which is indexed by the parameter <em>θ</em>. This means that for each <em>θ</em> we have a different distribution. For example, one could have <em>p_model(θ,x)=θ*exp(−θ*x)</em>, aka the exponential distribution.</p><p>The problem we want to solve is to find <em>θ*</em> that maximizes the probability of X being generated by <em>p_model(θ*,x)</em>. This is, for all the possible <em>p_model </em>distributions, which is the one that most likely could have generated <em>X</em>. This can be formalized as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/391/1*Ix2rCSTS_FnYyKxq3ZQjhA.png" /></figure><p>and since the observations from <strong><em>X</em></strong> are extracted independently we can rewrite the equation as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/363/1*xLrn7LiBF_MY2wat7ow7ig.png" /></figure><p>While this equation is completely fine from a mathematical point of view, there are some numerical problems with it. In particular, we’re multiplying probability densities, and densities can be very small sometimes, so the total product can have underflow problems — ie: we can’t represent the value with the precision of our CPU. The good news is that this problem can be overcome with a simple trick: just apply <em>log</em> to the product and convert the product to a sum.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/426/1*bRTCO2jB5xU9orLvYSaFEA.png" /></figure><p>Since logarithm is a monotonic increasing function, this trick doesn’t change the <em>argmax</em>.</p><h3>Example</h3><p>Let’s work out an example to see how one can use this technique in a real problem. Imagine you have two blobs of data that follow a gaussian distribution — or at least that is what you suspect — and you want to find the most probable center of these blobs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/426/0*k2RlKi6ciFx2XRtF.jpeg" /><figcaption>Points were generated by two Gaussian distributions with centers at (3, 3) and (-3, -3) respectively.</figcaption></figure><p>Our hypothesis is that these distributions follow a Gaussian distribution with unit covariance, ie: <strong><em>Σ</em></strong><em>=[[1,0],[0,1]].</em> Therefore, we want to maximize</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/576/1*-gjWh-eQdHbX_TtHBdkpCQ.png" /></figure><p>where <strong><em>θ</em></strong><em>=(</em><strong><em>θ_</em></strong><em>1,</em><strong><em>θ_</em></strong><em>2)</em>, and <strong><em>θ_</em></strong><em>1</em> and <strong><em>θ_</em></strong><em>2</em> are the centers of the distributions.</p><p>Using the following snippet one can get the most plausible centers according to MLE</p><pre><strong>from</strong> scipy.optimize <strong>import</strong> minimize<br><strong>import</strong> numpy <strong>as</strong> np<br><strong>class</strong> <strong>ExampleMLE</strong>:<br>    <strong>def</strong> <strong>__init__</strong>(self, x1, x2):<br>        self.x1 <strong>=</strong> x1<br>        self.x2 <strong>=</strong> x2<br>    <strong>def</strong> <strong>loss</strong>(self, x):<br>        mu1 <strong>=</strong> (x[0], x[1])<br>        mu2 <strong>=</strong> (x[2], x[3])<br>        log_likelihood <strong>=</strong> (<strong>-</strong> 1<strong>/</strong>2 <strong>*</strong> np.sum((self.x1 <strong>-</strong> mu1)<strong>**</strong>2) <br>                          <strong>-</strong> 1<strong>/</strong>2 <strong>*</strong> np.sum((self.x2 <strong>-</strong> mu2)<strong>**</strong>2))<br>        <strong>return</strong> <strong>-</strong> log_likelihood <em># adding - to make function minimizable<br># x1 and x2 are arrays with the coordinates of point for blob 1 and 2 respectively.<br></em>p <strong>=</strong> ExampleMLE(x1<strong>=</strong>x1, x2<strong>=</strong>x2) <br>res <strong>=</strong> minimize(p.loss, x0<strong>=</strong>(0, 0, 0, 0))<br><strong>print</strong>(res.x)</pre><p>In the following figure, we can see the most plausible generating distributions according to MLE. In this specific case, the real centers are in (3, 3) and (-3, -3), and the optimal found values are (3.003, 3.004) and (-3.074, -2.999) respectively, so the method seems to work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/426/0*1pNmzSPV7E_3GDKo.jpeg" /><figcaption>Original points and the contour plot of the optimal Gaussians found by MLE.</figcaption></figure><h3>Using MLE for predictions</h3><p>In the last section, we have seen how to use MLE to estimate the parameters of a distribution. However, the same principle can be extended to predict <em>y</em> given <em>x</em>, using the conditional probability <em>P(</em><strong><em>Y</em></strong><em>|</em><strong><em>X</em></strong><em>;θ)</em>. In this case, we want to estimate the parameters of our model that better predict <em>y</em> given <em>x</em>, this is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QXlLg85LpWa22FUX6j8TFg.png" /></figure><p>and using the same tricks as before</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aBQHDwXBIQFGaWMV-6rn2A.png" /></figure><p>Now, the process that generates the real data can be written as <em>y=f(x)+ϵ</em>, where <em>f(x)</em> is the function we want to estimate, and <em>ϵ</em> is the intrinsic noise of the process. Here we are assuming that <em>x</em> has enough information to predict <em>y</em>, and no amount of extra information could help us in predicting noise <em>ϵ</em>. One common assumption is that this noise is normally distributed, ie: <em>ϵ∼N(0,σ²)</em>. The intuition behind this choice is that even knowing all the variables that describe the system, you will always have some noise, and this noise usually follows a Gaussian distribution. For example, if you take the distribution of heights of all the women, of the same age, and in the same town, you are going to find a normal distribution.</p><p>Therefore, a good choice for the conditional probability for our case is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fP5w9zuQaqYet3W8DLMx9g.png" /></figure><p>where <em>f^(x)</em> is our model and is indexed by <em>θ</em>. This means that our model <em>f</em> will predict the mean of the Gaussian.</p><p>Plugging now the conditional probability in the equation we want to maximize we get</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LcNQK87ArJxm_yi1KdxWGg.png" /></figure><p>where <em>y_ip</em> is the output of our regression model for input <em>x_i</em>. Notice that both <em>n</em> and <em>σ</em> are constant values, so we can drop them from the equation. Therefore the function that we want to solve is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U429cwnMs-JynG8NGaaJ5w.png" /></figure><p>which is the same as minimizing the squared error loss!</p><p>But why? Our intuition behind the loss function was that it penalizes big over small errors, but what does this have to do with conditional probabilities and normal distributions? The point is that extreme values are very unlikely in a normal distribution, so they will contribute negatively to the likelihood. For example, for <em>p(x)=N(x;0,1)</em>, <em>log⁡ p(1)≈−1.42</em>, while <em>log ⁡p(10)≈−50.92</em>. Therefore, when maximizing the likelihood we’ll prefer values of <em>θ</em> that avoid extreme values of <em>(y_t</em>−<em>y_p)²</em>. So the answer to the question <em>Why should we minimize MSE?</em> is <em>Because we’re assuming the noise is normally distributed.</em></p><h3>Conclusions</h3><p>We just saw that minimizing the squared error is not an arbitrary choice but it has a theoretical foundation. We also saw that it comes from assuming that the noise is distributed normally. The same procedure we studied in this post can be used to derive multiple results, for example, the unbiased estimator of the variance, Viterbi Algorithm, logistic regression, machine learning classification, and a lot more.</p><p>This story was originally published here: <a href="https://www.amolas.dev/posts/mean-squared-error/">amolas.dev/posts/mean-squared-error/</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3b97391f54c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/why-do-we-minimize-the-mean-squared-error-3b97391f54c">Why do we minimize the mean squared error?</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Continuous Blackjack (i): Introduction and First Results]]></title>
            <link>https://medium.com/data-science/continuous-blackjack-i-introduction-and-first-results-be70c5da426f?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/be70c5da426f</guid>
            <category><![CDATA[math]]></category>
            <category><![CDATA[blackjack]]></category>
            <category><![CDATA[statistics]]></category>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[probability]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Mon, 16 May 2022 16:38:56 GMT</pubDate>
            <atom:updated>2022-05-17T19:10:30.486Z</atom:updated>
            <content:encoded><![CDATA[<h4>Analytical and numerical perspectives of the problem of Continuous Blackjack</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4rL8FhqN8zz-kZxm" /><figcaption>Photo by <a href="https://unsplash.com/@introspectivedsgn?utm_source=medium&amp;utm_medium=referral">Erik Mclean</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Introduction</h3><p>This is the first of several posts where we will analyze the problem of playing continuous blackjack. In particular, we want to extend the work of <a href="http://myblog.henrycharlesworth.com/continuous-blackjack">Henry Charlesworth</a> and <a href="https://arxiv.org/pdf/2011.10315.pdf">Mu Zhao</a> to other distributions. In this first post, we will reproduce the existing results and propose a way to extend them to other distributions. Then, we will analyze the case of the uniform distribution and show some interesting plots.</p><h3>Problem Statement</h3><p>Consider the continuous version of the blackjack game, which has the following rules:</p><ol><li>Each player <em>i</em> starts with an empty stack <em>S_i=[]</em>. The value of this stack is defined as <em>V_i=∑|S_i|S_ji.</em></li><li>A dealer draws random values from a distribution <em>P(x)</em>.</li><li>The players play the game in turns. The turn of every player starts by receiving a number from the dealer and adding to its stack <em>S_i</em>. Then, the player has two options:</li><li>Decide to stop playing and finish the game with its current stack. Then the player’s turns end and it is the turn of the next player.</li><li>Get another number, add it to its stack, and go back to step 3. The player can do that all the times they want while <em>V&lt;1</em>. If <em>V&gt;1</em> the player automatically loses and the turn of the next player starts.</li><li>The player with the highest value V wins the game.</li></ol><p>The question is, what’s the <strong>best strategy</strong> to win this game?</p><h3>Preliminaries</h3><p>To start let us define <em>F(t;a,b)</em> to be the probability of landing in the interval <em>R=(a,b)</em>, given that we are actually at t and we are going to keep playing if t&lt;a. This probability can be split into two parts, (1) the probability that we land on <em>R</em> in the next turn, and (2) the probability that we don’t land on R on the next turn but in one of the following ones.</p><p>In mathematical terms this is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sGHzJSDdP9pWMWb8hLk1Lg.png" /></figure><h3>Uniform distribution</h3><p>In the particular case of <em>P=U[0,1]</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7JM5E8GYHSv8XNENOLKtNw.png" /></figure><p>And differentiating with respect to t on both sides we get</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aDyGboWnhYOi7JhtWQ1HDA.png" /></figure><p>The constant K can be obtained using <em>F(a;a,b)=b−a</em>, so <em>K=(b−a)e^a.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5T2rHDRXvk4b3jKVQK2TZw.png" /></figure><p>Notice how the equation doesn’t depend on the particular value of a, b, and t but on the distances between them. Thus, one can define the value <em>W=b−a </em>as the width of the range, and <em>D=t−a</em> as the distance from the current location to the lower bound. Then, the equation can be written as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fUa59aBdEGJ2YmlBH37e1Q.png" /></figure><h3>Strategies</h3><p>In this section, we will analyze how to use the results derived in the last section to find the optimal strategy in different scenarios.</p><h3>1 vs 1</h3><p>Let’s start with the simplest case: you’re playing against only one other person. The first player gets a point if the second player busts. If the first player’s score is s, then the second player has a probability of <em>1−F(0;s,1)</em> to bust, this means that our probability to win if we stay at s is <em>1−F(0;s,1)</em>. Of course, if we could choose our s we would choose <em>s=1</em>, but since this is a random process we can’t choose s. The only thing we can choose is at which <em>α</em> we stop drawing numbers. This <em>α</em> is defined by the following point: where the probability of winning given that we stick at <em>α</em> is the same as the probability that we win given that we draw one more number.</p><h3>Specific case: uniform distribution</h3><p>For this section let’s assume that <em>P=U[0,1]</em> and that all the players start at <em>x=0</em>. In this case, the condition described by the above reasoning is written as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WGyePN4UVl3ulTHgaQWuSg.png" /></figure><p>Notice that the left side is increasing, while the right side is decreasing.</p><h3>Simulation results</h3><p>Before moving to theoretical results, let’s try to find the optimal threshold via simulations. To do so, we will try each possible threshold and simulate 50000 games. Then we will check the probability of the first player winning as a function of the chosen threshold. The results are plotted in the following plot:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zNKkB0NlCr2fu-EWpcA9Ww.png" /><figcaption>Probability of the first player winning vs stopping threshold</figcaption></figure><p>As you can see, the maximum winning probability is achieved around <em>t∗=0.6</em>, more specifically is <em>t∗=(0.565±0.035)</em>. To compute the optimal threshold and its deviation we have run a Monte-Carlo simulation. This is, we have generated 1000 samples of the threshold-vs-probability curve using the expected winning probability and its 99% confidence interval. Then, we have computed the optimal threshold for each of these samples, and from this set of optimal thresholds, we have computed the average and standard deviation.</p><p>Knowing the range where the optimal value lies, let’s repeat the above simulations but only with thresholds between 0.5 and 0.65. Using this smaller range we get the optimal threshold <em>t∗=(0.568±0.021)</em>. Finally, the expected winning probability at the optimal threshold is <em>p_win(t∗)=(0.4278±0.0011). </em>Let’s see now if the analytical results are compatible with the simulations.</p><h3>Analytical results</h3><p>Using the results from section Uniform Distribution and <em>P=U[0,1]</em>, the equation for the optimal <em>α</em> is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9N4C0s4KdgjW9PDfVoNu0Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fI_C3-lzH6L4spvlRs1-eg.png" /><figcaption>Plot of the curves 1−(1−α)e^α and (1−α)−e^α(α−2)−e.</figcaption></figure><p>This is a non-linear equation without a closed form, but it can be solved approximately using numerical methods, and the solution is <em>α∗≈0.5706</em>. This means that when the accumulated value of our stack gets bigger than 0.5706 we should end our turn. This result is compatible with the range we found in our simulations.</p><h3>Conclusions</h3><p>In this first post, we have derived the generic equation for the probability of falling in a range <em>(a,b)</em> given that we’re currently at <em>t</em>. We have also derived the condition that the optimal thresholds have to fulfill. Finally, we have studied the particular case of the uniform distribution — both numerically and analytically — and studied its properties.</p><p>In the following posts, we will study how all these results change when the distribution <em>P</em> changes. We will also study the case where we play against <em>N</em> players instead of only one.</p><p>This story was originally published <a href="https://www.amolas.dev/posts/continuous-blackjack-i/">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be70c5da426f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/continuous-blackjack-i-introduction-and-first-results-be70c5da426f">Continuous Blackjack (i): Introduction and First Results</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[About data science and software engineering]]></title>
            <link>https://medium.com/@alexmolasmartin/about-data-science-and-software-engineering-6ce0ba67a181?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/6ce0ba67a181</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Thu, 12 May 2022 13:57:40 GMT</pubDate>
            <atom:updated>2022-05-12T14:01:32.160Z</atom:updated>
            <content:encoded><![CDATA[<h4>Why applying good software practices is important to become a better data scientist.</h4><p>I still remember when I started working as an intern data scientist and I had to open my first pull request. I was very happy with my work, so I just committed all my changes together, opened a pull request, assigned it to my supervisor, and started waiting for the compliments to arrive — <em>spoiler: the compliments never arrived</em>.</p><p>Just to put things in context let me explain that I’m a physicist and I learned to code by myself. Also, up to that moment, I always worked alone, so I never had to share my code with no one besides me.</p><p>Going back to my first PR, I remember my supervisor coming directly to my desktop and having the following conversation</p><blockquote><em>WTF is this? — said my supervisor while pointing at a method with more than 200 lines of code.</em></blockquote><blockquote><em>– I’m training a machine learning model. — I, with a smirk on my face, answered proudly.</em></blockquote><blockquote><em>– Yeah, but it looks that you’re also reading some datasets here and there. — asked him without smiling at all.</em></blockquote><blockquote><em>– Yeah, of course, I’m reading the data, splitting it in train and test, preprocessing it, and then training a model — at this point, the smirk of pride in my face already had disappeared.</em></blockquote><blockquote><em>– These are not good practices.</em></blockquote><blockquote><em>– These are not what?</em></blockquote><blockquote><em>– OMG, it shows that you are a physicist…</em></blockquote><p>After that, he patiently spent the following months explaining to me some basic software engineering best practices, how to use git properly, and how to work in collaborative software projects. In the beginning, I didn’t understand why all those things were important, after all, I was a data scientist doing some cool machine learning models and not a software developer, so all these things didn’t apply to me. But, after some years of experience as a data scientist, now I have a different opinion, and now I’m convinced that these things matter a lot, even if you are a data scientist.</p><h3>Why best practices matter</h3><p>It’s widely known why best practices matter in pure software engineering projects, however, it’s not that clear why these practices matter when doing data science. Here I will try to explain why best software practices are crucial when developing a data science project.</p><h4>For speed</h4><p>When starting a new data science project, we are all excited to quickly reach final results. However, this mentality makes you slower in the long term. One of my co-workers told me some years ago: “data scientists with physics background do great work during the first steps of a project. They have great ideas and usually obtain good results in a few days. However, after this first burst of creativity and results, they get stuck, because the code they have written is now unmanageable, and any tiny change requires a lot of work.”. If you want to avoid that, following best practices and writing good code is going to help you to experiment better and faster.</p><p>Let me put an example to illustrate my point: imagine that you have a model that predicts the number of sales of your company. However, someone asks you to split the number of predicted sales into national and international sales. If your code was not well designed, you are going to spend a lot of hours changing a huge amount of code to make it work under these new requirements. However, if before developing your project you had spent some time designing the architecture of the project, now you would be able to try all the different variations of your experiment very fast and without fears of bugs.</p><p>On the other hand, I’ve met a lot of data scientists that believe that data engineers are going to do all this work for them, and from my experience, this never works like that. Data engineers are not data scientists’ minions, and they have their priorities and motivations, and usually, these motivations are not to clean your dirty code and make it scalable.</p><p>Also, you are going to share your projects with your colleagues, who will need to read and understand your code. Data science and machine learning are difficult on their own, so let’s try to not make them more difficult by writing bad software and making things obscure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/641/1*mkObxqc1coSmAWyD9bPA_Q.png" /><figcaption>From my experience, if you want to get great results from your experiments you need to invest time in software best practices.</figcaption></figure><h4>For science</h4><p>As its name indicates, data scientists follow the scientific method, and by definition, the scientific method tests the hypotheses with experiments. Therefore, as a data scientist, you should pay special attention to how you test your hypotheses. This implies several things, such as using the correct mathematical framework, using the correct data, and having a well-designed setup. Here I want to focus on the latter.</p><p>Imagine a biologist experimenting in a cluttered lab, without properly labeling the different samples they are using, or without following the tool’s instructions. Imagine an astrophysicist trying to discover new exoplanets without following a strategy, just by pointing the telescope into random locations. Imagine the physicists at CERN fixing the LHC using duct tape. It’s clear that in all these cases, the outcome of the experiments is going to be probably useless. And while serendipity is a huge driver of science, it’s always followed by a meticulous scientific process. However, I’ve seen a lot of data experts making claims based on experiments with really badly designed scientific setups.</p><p>Curiously, all data scientists are aware that selecting the correct statistical tests or selecting the correct model is of capital importance for their produced work, but when it comes to their project architecture they don’t care at all. It is as if doctors were going to perform the most advanced and difficult operation in history, and after years of preparation and study they carry out the operation in a room with an old bed and with unsharpened scalpels.</p><p>There’s, again, this spread idea that data scientists should focus only on writing notebooks and scripts to prove hypotheses as fast as possible, and if the results they found are interesting then someone else is going to refactor and clean all their code. My point here is that’s almost impossible to prove hypotheses without the proper experimental setup, so if you want to be a good data scientist you should take care of your experimental setup.</p><p>Let me finish with one of my favorites quotes about machine learning and software development</p><blockquote><strong>Do machine learning like the great engineer you are, not like the great machine learning expert you aren’t</strong><em>.<br>From “</em><a href="https://developers.google.com/machine-learning/guides/rules-of-ml"><em>Machine Learning Rules</em></a><em>” by Google</em></blockquote><p>This story was originally posted <a href="https://www.amolas.dev/posts/feynman-restaurant-problem/">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6ce0ba67a181" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Feynman’s Restaurant Problem]]></title>
            <link>https://medium.com/data-science/feynmans-restaurant-problem-57121af0bb92?source=rss-8fa4cb38d347------2</link>
            <guid isPermaLink="false">https://medium.com/p/57121af0bb92</guid>
            <category><![CDATA[math]]></category>
            <category><![CDATA[statistics]]></category>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[feynman]]></category>
            <dc:creator><![CDATA[Alex Molas]]></dc:creator>
            <pubDate>Mon, 09 May 2022 09:56:35 GMT</pubDate>
            <atom:updated>2023-10-03T11:53:12.190Z</atom:updated>
            <content:encoded><![CDATA[<h4>Introduction and solution to Feynman’s Restaurant Problem from a RecSys perspective</h4><p><em>This story was originally posted </em><a href="https://www.alexmolas.com/2022/05/09/feynman-restaurant-problem.html"><em>here</em></a><em>.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*I8rtxMBYMyNfMsLm" /><figcaption>Photo by <a href="https://unsplash.com/@shawnanggg?utm_source=medium&amp;utm_medium=referral">shawnanggg</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>You’re on holiday, and you’re going to spend the following days on a remote island in the Pacific. There are several restaurants and you would like to enjoy the most the local cuisine. The problem you face is that a priori you don’t know which restaurants are you going to enjoy and which not, and you can’t find the restaurants on Yelp, so you can’t use others&#39; opinions to decide which restaurants to visit. Also, the number of restaurants is bigger than the number of days you’re going to stay on the island, so you can’t try all the restaurants and find the best one. However, since you love math you decide to find the best strategy to optimize your experience during your holidays. This is known as the <strong>Feynman’s Restaurant Problem</strong>.</p><p>This same problem can be interpreted from the perspective of the restaurant, where cookers want to recommend dishes to clients but they don’t know which dishes are the clients going to enjoy or not. So this problem falls under the category of <strong>recommender systems.</strong> More generically, this is the problem of recommending <em>M</em> items (with repetition) from a set of <em>N</em> items with an unknown rating.</p><p>The content of this post is heavily inspired by <a href="https://www.feynmanlectures.caltech.edu/info/solutions/restaurant_problem_sol_1.html">this solution</a>. I have tried to explain some details which were obscure to me, and I also added some plots and code to give more intuition about the problem. <a href="https://www.feynmanlectures.caltech.edu/info/other/Feynmans_Restaurant_Problem_Revealed.html">Here</a> you can read more about the history of the problem.</p><h3>Mathematical formulation</h3><p>Let’s try to formalize the problem using maths. First of all, let’s define <em>D</em> as the number of days you’re going to spend in the city and <em>N</em> as the number of restaurants. Let’s assume that you can rank all the restaurants with respect to your taste, and let’s call <em>r_i</em> the ranking of restaurant <em>i</em>. Let’s assume also that you can go to the same restaurant every day without getting tired of it, which means that if you know the best restaurant in the city you are going to go there always.</p><p>Notice, that since <em>D&lt;N</em> you can’t try all the restaurants in the city, so you’ll never know if you have visited the best restaurant.</p><blockquote><em>Notice that you will never know the actual rating ri. You only know if a restaurant is the best up to that moment or not. You can rank the restaurant that you have tried up to a given moment, but this “partial” ranking maybe it’s not the same as the “absolute” ranking. For example, if you have only tried 4 out of 10 restaurants you could have the rank </em><em>[3, 2, 1, 4, ?, ?, ?, ?, ?, ?], but the real rank could be </em><em>[5, 4, 3, 6, 1, 2, 7, 8, 9].</em></blockquote><p>The function you want to optimize is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/258/1*oMF2dTlR_VXTtD6t9I6gVg.png" /></figure><p>where <em>r_i</em> is the restaurant rating you visited on the day <em>i</em>.</p><h3>Solution</h3><h4>Analytical</h4><p>Every day you stay in the city you have two options: (1) try a new restaurant, or (2) go to the best restaurant you visited until that moment. We can think about this problem as an <em>exploration-exploitation</em> problem, this is, you explore the city for the first <em>M</em> days, and after that, you always go to the best place up to that moment for the following <em>D−M</em> nights.</p><p>Therefore, we can split the function to optimize as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/566/1*cntslVnps_Si7zMbKzoILQ.png" /></figure><p>where <em>b_M,N</em> is the ranking of the best restaurant you have tried during the first <em>M</em> days.</p><p>The only free parameter in our equation is <em>M</em>, so you want to find the value of <em>M</em> where the expected profit is maximized. This is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/804/1*uPpa21Xl5Bfx4wt8snP_VA.png" /></figure><p>For the first term, applying linearity and knowing that <em>&lt;r_i&gt; = (N+1)/2 </em>we get</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/406/1*3scAA2E-qTQbAecLe8bVgg.png" /></figure><p>Now we need to compute <em>⟨b_M,N⟩</em>, which is the expected maximum value obtained after M draws from the range <em>(1,N)</em>.</p><p>On one hand, we know that if you only try a restaurant the expected ranking is <em>⟨b_1,N⟩ = (N+1) / 2. </em>On the other hand, if you try all the restaurants, the expected maximum ranking is of course <em>⟨b_N,N⟩ = N.</em></p><p>We can also compute <em>⟨b_2,N⟩</em>. In this case, there only exists 1 pair of restaurants where 2 is the maximum, ie you choose the restaurants <em>(1,2). </em>There only exist 2 pairs of restaurants where 3 is maximum, ie <em>(1,3)</em> and <em>(2,3)</em>. There only exist 3 pairs of restaurants where 4 is maximum, ie <em>(1,4)</em>, <em>(2,4)</em>, and <em>(3,4)</em>. And so on. All together there are <em>N(N−1)/2</em> possible pairs. Therefore</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/906/1*vBHVJOCkwjaR0B8yQ7w_kw.png" /></figure><p>Now consider <em>⟨b_N−1,N⟩</em>. This is, you try all the restaurants in the city except one. In this case, you’ll visit the best restaurant in <em>N−1</em> cases, and in only one case you’ll skip the best restaurant. Therefore, the expected value is</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/1*yZ8XS0lKePl4EeQlX0Ew0Q.png" /></figure><p>From all these results, one can see the pattern and guess that</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/402/1*8sM8aaK3unNAzrC6I9N7Xg.png" /></figure><p>Putting it all together we finally have</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/684/1*cLaaweUtR2cKaaPpaPkWbg.png" /></figure><p>which has a maximum at</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/356/1*ihH72s0rY2vdEyaynrZF6Q.png" /></figure><blockquote><em>Notice that the result doesn’t depend on N. This means that you don’t care about how many different restaurants are in the city, which sounds -at least for me- a little bit counterintuitive.</em></blockquote><blockquote><em>Notice also that if you want to try all the restaurants in the city without decreasing the expected profit you’ll need to stay in the city </em>D≥(N+1)^2/2−1 <em>days. So if the city has 10 restaurants you’ll need to stay in the city for at least 60 days: exploring the city for the first 10 days, and during the following 50 days going to the best restaurant. </em><strong>Please, don’t use these results to plan your next vacations.</strong></blockquote><h4>Numerical</h4><p>In the last section, we derived an analytical solution to our problem. Let’s now run some simulations to derive more intuition about this problem. In particular, it seems surprising that the solution doesn’t depend on <em>N</em>. So let’s see if the simulations support this claim.</p><p>With the following snippet, one can simulate the expected profit <em>⟨F⟩</em> for a set of parameters</p><pre><strong>import</strong> numpy <strong>as</strong> np<br><br><strong>def</strong> <strong>expected_profit</strong>(n_days: int, n_restaurants: int, n_experiments<strong>=</strong>10000):<br>    &quot;&quot;&quot;<br>    Computes the average profit at each <br>    possible m \in range(1, n_days) over n_experiments.<br><br>    :param n_days: number of times one can visit the restaurant.<br>    :param n_restaurants: number of restaurants in the city.<br>    :param n_experiments: number of experiments to perform.<br>    &quot;&quot;&quot;<br><br>    results <strong>=</strong> {}<br><br>    <strong>for</strong> m <strong>in</strong> range(1, n_days <strong>+</strong> 1):<br>        results[m] <strong>=</strong> []<br>        <strong>for</strong> i <strong>in</strong> range(n_experiments):<br>            ranks <strong>=</strong> x <strong>=</strong> list(range(n_restaurants))<br>            np.random.shuffle(ranks)<br>            profit <strong>=</strong> sum(ranks[:m]) <strong>+</strong> (n_days <strong>-</strong> m) <strong>*</strong> max(ranks[:m])<br>            results[m].append(profit)<br>    results <strong>=</strong> {k: np.mean(v) <strong>for</strong> k, v <strong>in</strong> results.items()}<br>    <strong>return</strong> results</pre><p>Using this snippet we have generated the following plot, using <em>N=(100, 110, 120)</em> and <em>D=10</em>. Notice how the maximum of the three curves coincides, which gives support to the counterintuitive analytical results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/471/1*C4t3yuxBo5UtoxJHgxoTTQ.jpeg" /><figcaption>Expected profit vs the number of exploration days. <a href="https://www.amolas.dev/posts/feynman-restaurant-problem/">Original work</a>.</figcaption></figure><h3>Conclusions</h3><p>In this post, we have explored Feynman’s Restaurant Problem. First, we have derived an analytical solution for the optimal exploration strategy, and then we have checked the analytical results with some simulations. Although these results make sense from a mathematical point of view, no one in their right mind would follow the optimal strategy. This is probably caused by the unrealistic assumptions we made, ie: you can’t go to the same restaurant every day of your life without getting tired of it. One possible solution is to change the rating of a restaurant <em>r_i</em> to be dependent on the number of times you’ve visited it, ie <em>r_i(n)</em>. However, this is outside the scope of this post and we’re not going to do it, but maybe it can serve as inspiration for another post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=57121af0bb92" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/feynmans-restaurant-problem-57121af0bb92">Feynman’s Restaurant Problem</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>