<?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 Tanuj Jain on Medium]]></title>
        <description><![CDATA[Stories by Tanuj Jain on Medium]]></description>
        <link>https://medium.com/@tanuj-jain?source=rss-15510b6546e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Eqlh1UrLt8PMIuK2C1AtTA.jpeg</url>
            <title>Stories by Tanuj Jain on Medium</title>
            <link>https://medium.com/@tanuj-jain?source=rss-15510b6546e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 11:58:36 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@tanuj-jain/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[ChatGPT: A no-jargon explanation]]></title>
            <link>https://medium.com/axel-springer-tech/chatgpt-a-no-jargon-explanation-9109e12c0aa4?source=rss-15510b6546e------2</link>
            <guid isPermaLink="false">https://medium.com/p/9109e12c0aa4</guid>
            <category><![CDATA[text]]></category>
            <category><![CDATA[nlp]]></category>
            <category><![CDATA[chatgpt]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Tanuj Jain]]></dc:creator>
            <pubDate>Tue, 07 Mar 2023 12:14:19 GMT</pubDate>
            <atom:updated>2023-03-07T12:14:19.078Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4iDupR3HvbT1O8S6" /><figcaption>Photo by <a href="https://unsplash.com/@emilianovittoriosi?utm_source=medium&amp;utm_medium=referral">Emiliano Vittoriosi</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Are you familiar with gradient descent and have practised some form of machine learning at some point in your life? If yes, congrats! You can easily understand the basic building blocks of ChatGPT.</p><p><strong>This article is an attempt at simplifying the explanation of the ChatGPT paper and keeping jargon to a minimum.</strong></p><p>The methodology behind ChatGPT is exactly the same as one of OpenAI’s previous models- instructGPT: <a href="https://arxiv.org/pdf/2203.02155.pdf">https://arxiv.org/pdf/2203.02155.pdf</a></p><p>The training comprises 3 phases:</p><ol><li>Supervised Fine Tune (<strong>SFT</strong>) a pretrained GPT3 model</li><li>Train a Reward Model (<strong>RM</strong>)</li><li>Train a Reinforcement Learning Model (<strong>RL</strong>)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/186/1*hBb1mxdPZHGurjRhvo0UZQ.png" /><figcaption>Basic phases of training</figcaption></figure><p>Let’s look at each step in detail.</p><h3>1. Supervised Fine Tune (SFT)</h3><p>The goal of this step is to <strong>fine tune a pretrained GPT3 model using human generated responses.</strong></p><p>OpenAI hired a bunch of humans to write high quality prompts and responses, threw the prompt and responses at a pretrained GPT3 model in order to finetune it and voilà, the step was completed!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*6DFJOnl4s19cKOIJ-tv1xA.png" /><figcaption>SFT: Inputs and expected outputs are provided at the time of training</figcaption></figure><h4><strong>Model</strong></h4><p>A pretrained GPT3 model is used.</p><h4><strong>Dataset</strong></h4><p>OpenAI collected prompts not just from humans, but also from their API. The responses to each of these were written exclusively by humans. The prompts were taken from a variety of task types- brainstorming, classification, Q&amp;A, summarization, chat, extraction, etc. Some example of the prompts:</p><ol><li>List five ideas for how to regain enthusiasm for my career</li><li>{java code} What language is the code above written in?</li><li>Write a short story where a bear goes to the beach, makes friends with a seal, and then returns home.</li><li>Who built the statue of liberty?</li></ol><p>The paper doesn’t mention the responses to the prompts but rest assured, some human wrote a response to each prompt. (In fact the examples above are just a mimicry of the actual prompts; authors didn’t disclose the real data).</p><h4><strong>Training regime</strong></h4><p>If you’re familiar with sequence modelling (specifically ‘<em>Next Token Prediction</em>’), feel free to skip this section. If not, read on.</p><p>Since this is a supervised learning setup, one has to have access to the input and its corresponding target at the time of training. This is a GPT model- the task is to predict the next token in the sequence given all the past tokens. Let’s take a simple example:</p><p><em>Prompt</em>- How are you doing?</p><p><em>Response</em>- I am good</p><p>First step is to concatenate the prompt and the response-</p><p><em>How are you doing? I am good</em></p><p>Assuming one word is one token (which is not the case since GPT typically uses Byte Pair Encoding, but it simplifies our explanation without loss of generality), we have the following tokens-</p><p><em>‘How’, ‘are’, ’you’, ‘doing’, ‘?’, ‘I’, ‘am’, ‘good’</em></p><p>- this gives us 8 tokens (’?’ is an individual token).</p><p>Now, at each timestep, the output of the model comprises a probability distribution over the entire vocabulary of the dataset. Eg: if there are a total of 50,000 different tokens the model is allowed to produce, these 50k tokens would make up the vocabulary of the model. Our 8 tokens are 8 entries of this vocabulary. So, at each time step, a probability distribution over 50,000 tokens is produced. These 50k values denote the probability of each of the 50k tokens to be the next predicted token in the sequence. Since here we already know the whole sequence, we know exactly what we expect the probability distribution to be at each time step- a 1.0 for the token we know exists at the timestep and 0.0 for all other tokens in the vocab. In our example, for each timestep, 49,999 entries would be 0.0, only one entry would be 1.0 — this would be the expected training distribution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/884/1*mIi4FoMFCV-7M55Y5GJqkQ.png" /><figcaption>Finetuning Process for the first 4 timesteps</figcaption></figure><p>The training process depicted in the above diagram for the first 4 time steps can be broken down as below:</p><p>Given our sequence of 8 tokens- <em>How are you doing? I am good</em></p><p>Let’s look at the first timestep-</p><p><em>At t=0</em>,</p><p>expected output distribution- P(‘<em>how</em>’) = 1.0 given a beginning-of-sentence (<em>&lt;bos&gt;</em>) token to start generation, all other tokens P(<em>token</em>)=0.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6e34cbac4e9aacc907a038833370e7a7/href">https://medium.com/media/6e34cbac4e9aacc907a038833370e7a7/href</a></iframe><p>Since the model is untrained, P(‘<em>how</em>’) would not be 1.0 (=0.85 in the diagram). Similarly, all the 49,999 probabilities would also not be 0.0. Hence, we can calculate a loss between the expected and actual output. We use this loss to update the model.</p><p><em>At t=1</em>,</p><p>expected output distribution- P(‘<em>are</em>’ | ‘<em>how</em>’) = 1.0, all other tokens P(<em>token</em> | ‘<em>how</em>’)=0.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aa70484a903e5677717e4bdc5327b2dd/href">https://medium.com/media/aa70484a903e5677717e4bdc5327b2dd/href</a></iframe><p>We already know the first token in the sequence(= ‘<em>how</em>’) and would now want to get the second token(= ‘<em>are</em>’). Again, an untrained model produces probabilities that are different from expected. So, we incur a loss that we backpropagate and update the model.</p><p>The above procedure goes on for all tokens in the sequence and through multiple sequences (thousands).</p><p>When the SFT is trained, one can trigger a generation process by adding a prompt and let the model produce one token per timestep (one token can be selected from the the probability distribution produced for that timestep by, for example, selecting the highest prob token in the distribution).</p><p>We end up with a Supervised Fine Tuned model (SFT) at the end of this procedure.</p><p>SFT model is where the story usually ends for a majority of ML practitioners at the moment. The model trained using usecase specific data is now ready to start generating. However, the model is still likely to produce responses that are not preferable to humans. As an example, let’s say we have a prompt- <em>How are you doing?</em></p><p>3 different runs of the model produce 3 different responses:</p><ol><li><em>I am good</em></li><li><em>Doing good. How about you?</em></li><li><em>Apple is a nice fruit.</em></li></ol><p>It is easy to see that 3rd response makes no sense for the given prompt, while it could be argued that 2nd response is marginally better than the first one. So, not all responses are created equal! What if one could train a model that would produce responses that are more aligned with human preference? This is precisely the goal of the next 2 phases.</p><h3>2. Train a Reward Model (RM)</h3><p>One could take the responses produced by the SFT model and ask a human to rate those responses on the basis of how close those are to the responses a human would write for that prompt. Once such a rating is available, it could be used to update the model to align the generation with human-preference.</p><p><strong>Simulating human preference is the goal of this second step</strong>- training a so-called Reward Model. Here the term ‘reward’ refers to the rating mentioned above- a higher reward means that the human is more likely to prefer that response; a lower reward indicates the opposite.</p><p>Let’s now look at the model used in this phase and the corresponding data used to train it.</p><h4><strong>Model</strong></h4><ol><li>The SFT trained in phase 1 is copied.</li><li>The last linear layer of SFT is cut-off (referred to as ‘unembedding’ layer in the paper).</li><li>A randomly initialized linear layer is slapped on top of the model that outputs a scalar value.</li></ol><p>So, we basically end up with a regressor!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xtSoafKXUqq65J-VIYf4yw.png" /><figcaption>Reward model architecture</figcaption></figure><h4><strong>Dataset</strong></h4><ol><li>Take the SFT model from phase 1.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/180/1*8hRgS6PrE2khPOloQGXg_w.png" /></figure><p>2. Throw a prompt multiple times (4–9 times) at it to generate multiple responses (4–9 responses).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0nwawhIAQQx4rABZCbUdXg.png" /></figure><p>3. Create pairs of responses from a given prompt. <em>C(4,2)</em> to <em>C(9,2)</em> pairs per prompt.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/623/1*lenMq2umskHrg0iDI8Un1w.png" /><figcaption>Pairs of responses generated by the same prompt</figcaption></figure><p>4. Ask a human to look at each pair of responses and rank individual response within each pair. Essentially, for a given pair of responses to a prompt, indicate which of the response in the pair is better.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/755/1*_cXCW6W-E3uqeKY-mH-DBw.png" /><figcaption>Human goes to each pair and generates a relative ranking for goodness of response</figcaption></figure><p>33k prompts were used in this step. Since 4–9 responses were used for each prompt, the total number of samples generated here were between [<em>33k X C(4, 2)]</em> and [<em>33k X C(9, 2)].</em></p><h4><strong>Training regime</strong></h4><ol><li>Take a response pair- this pair has already been ranked as explained before.</li><li>Concatenate the prompt with each of the responses in the pair. As a result, there would be one winning prompt-response and one losing prompt-response.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/949/1*mxx7016Jb5tH96_YFe01aA.png" /><figcaption>Ranking prompt-responses for each combination of responses</figcaption></figure><p>3. Pass the winning prompt-response to the reward model and generate a winning reward.</p><p>4. Pass the losing prompt-response to the same reward model and generate a losing reward.</p><p>5. Take the difference between the two rewards and update the model so as to maximize this difference.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JUSIqkqv-e_muxCxxd-Bjw.png" /></figure><p>The loss for training the reward model is:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/312/1*SmBS6z2TXwGQwtDRGkoN9Q.png" /></figure><p>The loss is a difference in log-odds of a high reward and low reward response. <strong>Intuitively</strong>, the reward model is learning to maximize the difference between a winning and a losing response.</p><p>Once the model is trained, one can simply concatenate the prompt with a response and get a scalar reward that indicates the degree to which a human will prefer that response for the given prompt.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m4FtL66RTJZSR2EHzG9v8A.png" /><figcaption>Reward model inference</figcaption></figure><p>So, we now have a way to simulate a human’s preference for a given prompt-response combination.</p><h3>3. Train a Reinforcement Learning Model (RL)</h3><p>Until now, we have trained 2 models:</p><ol><li>An SFT model trained in phase 1 that is capable of generating a response given a prompt- but it still generates responses that are not aligned with human preferences.</li><li>A reward model that gives out a scalar value but is incapable of actually generating any text.</li></ol><p>So, now we have to come up with<strong> a model that utilizes the above 2 models to produce another model that is capable of generating text that aligns well with human preferences- this is the goal of the 3rd phase</strong>- training a reinforcement learning model.</p><p>Aligning generation with human preference boils down to generation of responses that will yield a high reward for a given prompt. So, while training the RL model, the aim of the model is to maximize reward. Fortunately, one can formulate this aim mathematically as an objective maximization problem:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/286/1*_UuV3RQ4P8P8PH9bI-exRg.png" /></figure><p>where,</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/205/1*mQOl1SixbS_srqLwrMRHvQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/213/1*opCpTTdeqzVKc8fBX-ifCQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/114/1*caBYaGINqliQbo55IDDFSA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/346/1*1Gf6qUquu_x_rswigkfoDw.png" /></figure><p>One can take the gradient of this objective function and update the parameters of the model like a regular machine learning objective function, the only difference from the usual case being the use of gradient ascent instead of gradient descent since this is a maximization problem:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/372/1*Zt-fdc6AWLxEcflTTgEoKA.png" /></figure><p>With some simple <a href="https://spinningup.openai.com/en/latest/spinningup/rl_intro3.html">mathematical aerobics</a>, the expression for the above gradient reduces to:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/696/1*-Dg1EwxSdPOSY9atzVGAuQ.png" /></figure><p>where <strong>D</strong> refers to all the prompt-response pairs, <strong>x</strong> denotes token at different timesteps and the gradient-log term has a tractable value.</p><p>Let’s look at the model, the dataset and the training regime for this step.</p><h4><strong>Model</strong></h4><p>The SFT trained in phase 1 is copied.</p><p>That’s it! No modifications, just copy the SFT model.</p><h4><strong>Dataset</strong></h4><p>31k prompts- no responses are available; it’s just a collection of 31k prompts.</p><h4><strong>Training regime</strong></h4><ol><li>Throw a prompt at the model.</li><li>The model generates a response.</li><li>Concatenate the prompt-response and pass it to the reward model to get a reward score for the pair.</li><li>Plug the values for reward and probabilitites in the gradient equation above.</li><li>Use this gradient to update the model parameters.</li></ol><p>That’s it!</p><p><strong>BUT, as it turns out</strong>, if you use the regime above, the updates are unstable- they’re too large and generic, which makes the model prone to generating gibberish. To drive the update in a more deterministic direction, one can ensure that the updates are done such that the resulting model parameters are not too far away from the SFT trained in phase 1.</p><p>This notion of distance is applied through the use of <a href="https://www.countbayesie.com/blog/2017/5/9/kullback-leibler-divergence-explained">KL divergence</a> (in the paper at least)- which measures a proxy of distance between the SFT and the model under training (RL model). Too large a divergence indicates that the model parameters are too far away from SFT, too low indicate the opposite. KL divergence is essentially a way to compare two distributions. Remember from phase 1 that at each time step, the generative model emits a probability distribution over the whole vocabulary. These are the distributions that can be compared.</p><p>More importantly, having access to this divergence value, one can directly use it as a part of the objective function. This is accomplished as follows:</p><ol><li>For a prompt, response is generated by the RL model.</li><li>At each time step of generation, a probability distribution over the vocab is generated.</li><li>The same prompt-response is fed to the SFT model and a probability distribution over the same vocab for each timestep is obtained.</li><li>Distributions from the above 2 steps are used to calculate KL divergence between SFT and RL model.</li><li>The divergence value is subtracted from the reward generated for a prompt-response pair.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/656/1*XroeMcz-6nQL7YdboeQ1Cw.png" /></figure><p>For a low divergence value, the reward has more impact on the update than for a high divergence. A hyperparameter <strong>beta </strong>is added to control the effect of the divergence term on the objective.</p><p><strong>However, </strong>when the authors trained the model with the above cost function, they found that the model performance dropped on some datasets compared to the performance of a pretrained GPT3 on those datasets. So, if the authors hadn’t gone through all this trouble, they would still obtain a better performance on some datasets using a naive pretrained GPT3. A bummer! So, they decided to do something about it- add another term in the cost function to ensure that the RL model isn’t allowed to go very far away from the pretrained model either (this is the model that was the base for finetuning in the first phase to obtain the SFT model). The cost function now becomes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HIg9SIpTcvIwUVeVeVXm0g.png" /></figure><p>The <strong>intuition</strong> behind the term is that the RL model should also maximize the likelihood of observing the sequences seen during the pretraining of GPT3. GPT3 was pretrained using a bunch of pretraining sequences and the objective was to maximize the likelihood of observing these pretraining sequences. If the RL model also maximizes the likelihood of observing the same sequences, one could argue that it is not very different from the pretrained GPT3. A hyperparameter <strong>gamma </strong>is added to control the effect of the pretraining term on the objective.</p><h4><strong>In conclusion, </strong>the model is optimized to:</h4><ol><li><strong><em>Maximize</em></strong> reward obtained using the reward model.</li><li><em>Minimize</em> divergence from SFT model.</li><li><strong><em>Maximize</em></strong> likelihood of observing sequences seen during pretraining.</li></ol><p>After updating the RL model for some steps, one could initialize a new reward model from it, ask humans to generate another dataset following the procedure described in Phase 2 and conduct another training session for the reward model. The <strong>intuition</strong> here is that the updated RL model is now better at capturing human preference than the SFT model and hence, would make for a better initialization point for the reward model.</p><p>Is it a guarantee that the model will now generate responses that a human would prefer? <strong>No</strong>, but it’s <em>merely more likely</em> to produce human-preferred responses. We have just managed to reduce this gap, not close it.</p><p>And this concludes our explanation of the instructGPT paper!</p><p>Give yourself a cookie for making it till the end!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9109e12c0aa4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/axel-springer-tech/chatgpt-a-no-jargon-explanation-9109e12c0aa4">ChatGPT: A no-jargon explanation</a> was originally published in <a href="https://medium.com/axel-springer-tech">Axel Springer Tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Largescale similarity search or the art of buying a winter jacket]]></title>
            <link>https://tanuj-jain.medium.com/largescale-similarity-search-or-the-art-of-buying-a-winter-jacket-3a0c8de7ee9e?source=rss-15510b6546e------2</link>
            <guid isPermaLink="false">https://medium.com/p/3a0c8de7ee9e</guid>
            <category><![CDATA[shopping]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[reasearch]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[Tanuj Jain]]></dc:creator>
            <pubDate>Thu, 07 Jan 2021 21:13:17 GMT</pubDate>
            <atom:updated>2021-01-07T21:13:17.657Z</atom:updated>
            <content:encoded><![CDATA[<p>In the last few days, I was confronted with 2 tasks which had zero relation to each other, but ended up with me employing strikingly similar approaches to tackle. These exercises made me more aware of my own approach to research and how the term ‘research’ isn’t just a term reserved for scientific endeavours.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*KoetICCUCUxq7rwBcs7Pjw.jpeg" /><figcaption>Source: Google images.</figcaption></figure><p><strong>The first task</strong> aligned with my profession as a Machine Learning Engineer. <a href="https://github.com/idealo/imagededup">imagededup</a>, the open source python package I built with my team at work, could use a functionality that would allow user to find duplicates in an image collection <strong>at scale</strong>. I would refer to this task as <strong>mlTask </strong>henceforth.</p><p><strong>The second task</strong> was a more mundane one: I had managed to rip my winter jacket in several places and was in urgent need of buying a new one during chilly January Berlin winter. In non-covid times, this task would comprise a hasty visit to 1 or 2 stores of my choice, where I would try on a few jackets and be done with the whole thing while consuming minimal mental bandwidth (minimal patience for shopping). In the lockdown period however, this bliss was snatched away from me, leaving me no choice but to get the darn jacket online. (which is a massive chore for me since I can’t just feel how the product is, but need to put in time to understand what I’m buying and I hate trying on and returning articles in case they don’t work for me). I will refer to this task as <strong>jacketTask</strong> from here on.</p><h4>Phase 1: Basic requirements</h4><p>First, I needed to have a basic idea of what I’m looking for.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*Az8ciWdYEowpc_VmIsuS3A.jpeg" /><figcaption>Source: Google images</figcaption></figure><p><strong>mlTask: </strong>Try to find <strong><em>one</em></strong> algorithm that would find duplicates in an image collection comprising a million images in a short amount of time with low memory consumption while making minimal sacrifices to the deduplication accuracy.</p><p><strong>jacketTask: </strong>Get a Jack Wolfskin winter jacket( with a limit on my budget). Why Jack Wolfskin (JW)?- Because I was curious to use one. Having used H&amp;M and Jack &amp; Jones, I now wanted to go a little more premium (premium for me at least).</p><h4>Phase 2.1: Ask around</h4><p>Talk to humans you know might know whatever might be worth knowing about the thing you are interested in knowing about. You get the drift ;)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q3JLUWkxf4OaogjW3C3bWQ.jpeg" /><figcaption>Source: Also google images.</figcaption></figure><p><strong>mlTask:</strong> Several users of the package(i.e., imagededup) had pointed to interesting algos/python packages that could be used here. This told me that firstly, there are already alternatives out there and secondly, there are alternatives that the community seems to be leaning towards. Had these alternatives not come up with my own research, I would have gone back and specifically looked at them.</p><p><strong>jacketTask: </strong>A simple message in a whatsapp group comprising friends living in Germany. Got the first suggestion as a JW with a slight exposition of the specifics of the jacket. Good start! And some validation of my decision to buy a JW. Additionally, I looked at my current jacket and noticed what I liked/disliked about it.</p><h4>Phase 2.2: Preliminary googling/Optimistic googling</h4><p>As is usually the case, google is the know-it-all we all go to for all our questions concerning life, its meaning and everything beyond. But, the crucial deal at this precarious moment is this: <em>What shall the search term be? </em>I like to be optimistic at this point and try to look for the most complete solution available.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*4tq4hCa4AUmhIYlhVDodyg.jpeg" /><figcaption>Source: Google images yet again!</figcaption></figure><p><strong>mlTask: </strong>The first search term was on the lines of- ’Large scale image deduplication’. Could I be so lucky that somehow someone has tried doing exactly what I’m doing and has been kind enough to condense all that info into a great, working piece of code and a corresponding document? This is common enough a problem, but this search term returned results that turned out to be a little bland or seemed to miss the point.</p><p><strong>jacketTask: </strong>Looking at the basic requirements, it must surely be easy to find what I was after and hence, a straightforward search term- ’Jack Wolfskin winter jacket’. An immediate autosuggestion led me to append the words ‘for men’ to the search term. This opened up an army of google shopping, amazon, zalando links listing pieces from about a 100 euros to several times my budget. The shopaphobic in me immediately felt disoriented. I just didn’t know what to try next and had a strong urge to just use a thick rag for the remainder of the winter.</p><h4>Phase 3: Better search term</h4><p>The optimistic googling didn’t help much (the usual deal), so it was time to try and get specific.</p><p><strong>mlTask: </strong>A slight deliberation narrowed the issue down: it’s the similarity search component that needed to be optimised. So, a modified search term became- ‘Largescale similarity search’. Going through several reddit discussions on largescale similarity vector search and perusing the Readmes of several github repos (both pythonic and otherwise), certain algorithms seemed to be talked about more than others: Local Sensitive Hashing (LSH), Graph-based proximity methods, specialised tree structures such as vp-tree. Within these choices, Graph-based proximity methods seemed to be more promising as per the community. Another common term that came up is: approximate nearest neighbour. Along with the algorithms themselves, several open source, ready-to-use python packages were available as implementations. Some more talked-about were: Spotify’s <a href="https://github.com/spotify/annoy">annoy</a>, facebook’s <a href="https://github.com/facebookresearch/faiss">faiss</a> and <a href="https://github.com/nmslib/nmslib">nmslib</a>. Some of these algorithms/packages were already known to me (Phase 2.1 and otherwise), but I wanted this to be a clean research attempt lest I miss something more suited to my required functionality. Next, I needed to look for a way to compare these algos/packages. So, the better search term became: ‘Benchmarks annoy faiss nmslib’.</p><p><strong>jacketTask: </strong>No amount of deliberation got me any closer to improving my search term, so I had no choice but to click what seemed like a gazillion shopping links from the preliminary search term. After reading the descriptions, certain terms emerged: 3-in-1 jackets, something about polyester, about breathability (it’s a stupid jacket, not an Asthma-medicine-dispenser), about fur-hoods, about ass-coverability, outer-shell, inner-covering, etc. Feeling megastupid, I decided to google what a 3-in-1 jacket is: an outer shell with a detachable inner jacket (usually a fleece jacket), combinations of which allow one to wear it in 3 different ways. This is what I had found fascinating about JW when I was looking for my first winter jacket 8 years ago (didn’t buy a JW then #budget #studentlife). So, at least I knew that I wanted a 3-in-1 jacket now instead of other variants. This gave me a better search term: ’3-in-1 Jack Wolfskin winter jacket men’.</p><h4>Phase 4 : Drill down/ Noise reduction</h4><p>Now that I had better search terms at my disposal, I was ready to get my hands dirtier.</p><p><strong>mlTask: </strong>The better search term helped me find a veritable jackpot; a <a href="https://github.com/erikbern/ann-benchmarks">repo</a> that compared many different largescale similarity search algos/packages on several open source datasets having different properties. It seemed like a rare stroke of good luck and I’m grateful to the author for putting in the effort that helped reduce my own. Anyway, I went about examining the detailed recall graphs in the repo and ended up deciding to check 4 python packages that seemed to be doing decently well across most datasets: <a href="https://github.com/spotify/annoy">annoy</a>, <a href="https://github.com/nmslib/nmslib">nmslib</a> (Hierarchical Navigable Small World Graph variant), <a href="https://github.com/facebookresearch/faiss">faiss</a> and <a href="https://github.com/lmcinnes/pynndescent">pynndescent</a>. (nmslib additionally performed best on a 960 dimensional dataset, which was fairly close to the 1024 dimensions that CNN method of imagededup produced). I was now in a position to set up an experiment to deduplicate another dataset using all 4 packages in order to narrow it down further.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/954/1*rMEkU3uCpy4VvT7yORSJXA.png" /><figcaption>nmslib and pynndescent seem to do well on 960 dimensional GIST dataset (Source: <a href="http://ann-benchmarks.com/">http://ann-benchmarks.com/</a>)</figcaption></figure><p><strong>jacketTask: </strong>What seemed like a win in the last phase quickly resembled a case of counting chickens before hatching.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/304/1*RB-z-CzgfZJuew0t_M2BPw.jpeg" /><figcaption>Source: Google images (You expected me to pay for this image?)</figcaption></figure><p>It turns out that there are many different models of 3-in-1 jackets manufactured by JW: Arland, Iceland, Gotland. There are probably more variants, but these 3 came up regularly. Next, I tried to find some benchmarks that could compare these variations. The search terms moved in the vicinity of: ‘Gotland vs Iceland’, ‘Gotland vs Arland’, ‘Iceland vs Arland vs Gotland’. Some links turned up, but nothing promising came up that would offer a comprehensive comparison. Additionally, most blogs kept mentioning ‘The North face’ as a comparison leading me to question my JW decision. I decided not to pay heed to the noise and stayed with JW. Having been unable to find a good comparison, I started re-reading the specifics: the outer-shell material, breathability, stretchability and so-on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/692/1*AU41AFFJHBCRth875PESLw.jpeg" /><figcaption>Jacket-making is sorcery. Source: Google images (although the creator printed his name in there, well-played)</figcaption></figure><p>I had never thought of Jacket-making as anything mega-technical. I guess my primitive ideas about jackets could be summed up as- <em>“Thick covering=you stay alive in cold, thin covering=you die”</em>. Ignoring my ego, I googled up: ‘What to look for in a winter jacket’. (Bought several winter jackets over last years, for me and others, so wasn’t expecting to go back to absolute basics in this manner). Several verbose albeit wonderful explanatory blogs later(reddit-you beauty), I felt like a caveman had just discovered electricity (not fire, but electricity). Now, I knew that simply looking for a ‘winter jacket’ doesn’t have much meaning without the intended purpose: Are you going to do any type of sport while wearing it? Maybe go for more stretchability/ breathability. Are you looking for extreme hikes? Go for more expensive outer shell materials like Gore-tex (JW has Texapore as a Gore-tex implementation). Are you looking to hang about in the city? Then don’t worry too much about stretchability. Another basic difference I had looked over was the one between water/wind-<em>proofing</em> and water/wind-<em>resistance</em>. Certain materials were more resistant to water and wind than others, which is kind of important in the precipitation-prone and windy Germany climate. This gave me some ammo to critique these 3-in-1 variants based on these properties.</p><h4>Phase 5: Decision</h4><p>At this point, some kind of final decision needed to be reached using the last bits of deciding factors.</p><p><strong>mlTask: </strong>Knowing the 4 packages, I ran a quick experiment: Deduplicating a dataset of 10,000 images. While all of the methods did better than the current similarity search implemented in imagededup, 2 packages: nmslib and faiss vastly outperformed the others. Additionally, faiss doesn’t support windows, which would make it unsuitable for being an imagededup dependency. Hence, nmslib emerged as a clear winner. I’m currently in the process of running an experiment that attempts to deduplicate a dataset comprising 1 million images using nmslib (in addition to reading the <a href="https://arxiv.org/pdf/1603.09320.pdf">paper</a>). If the results are encouraging and there are no conflicting dependency issues with imagededup, nmslib would be the ultimate winner and conclude my attempt at finding something for largescale similarity search. For this task, a lot more needs to be done, but due to this exercise, the initial hassle of coming up with a promising candidate algo/implementation has been tackled to a good degree.</p><p><strong>jacketTask: </strong>Ass-coverability was an important consideration for me and several amazon reviews had mentioned that Arland failed at this task. So, Arland was out. The choice came down to Iceland vs Gotland which seemed too similar when it came to these properties. So, I just picked up Gotland in the end. This concluded my jacket buying antics.</p><p><em>Phase 3 and 4 usually go on in a loop: You find a better search term, then you drill down and find a still better search term and so on until you feel you have enough information to go onto phase 5.</em></p><h4>Phase 6: The crippling doubt</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M_9SsPCi8OayUJrcwyHzZA.jpeg" /><figcaption>Source: Also from google images. Surprise!</figcaption></figure><p>Could I have done better? Maybe spend more time researching specifics? Maybe my decision isn’t a good one after all? Did I screw up in a massive way somehow? All these worries are legit, but tasks can not stay in research phase forever. <em>Time-to-completion vs quality</em> is a real trade-off that needs to be respected. What good is buying a winter jacket in May because you wasted all winter coming up with more info? What good is forever second-guessing and never going through with code implementations in the fear of having barfed somewhere?</p><p>The fact that these 2 completely unrelated tasks ended up sharing a similar research approach interested me. It told me that in today’s world, it’s not just the scientists, the technical workers or the journalists that need to engage in research, it’s literally anybody trying to make a well informed decision. Therefore, developing a robust research approach is of paramount importance for everyone on the planet and must be treated as a high-priority addition to everyone’s to-do list.</p><p>Do share your approach to research!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3a0c8de7ee9e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Simple way to deploy machine learning models to cloud]]></title>
            <link>https://medium.com/data-science/simple-way-to-deploy-machine-learning-models-to-cloud-fd58b771fdcf?source=rss-15510b6546e------2</link>
            <guid isPermaLink="false">https://medium.com/p/fd58b771fdcf</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Tanuj Jain]]></dc:creator>
            <pubDate>Sun, 14 Apr 2019 14:47:46 GMT</pubDate>
            <atom:updated>2019-04-25T12:34:26.854Z</atom:updated>
            <content:encoded><![CDATA[<h4>Deploy your first ML model to production with a simple tech stack</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9_T5LKhIiz3-7JEB" /><figcaption>Photo by <a href="https://unsplash.com/@randyfath?utm_source=medium&amp;utm_medium=referral">Randy Fath</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>The Machine learning world currently sees Data Scientists (DS) performing one or both of the following 2 prominent roles:</p><ol><li>Where a DS receives a data dump, applies some Machine learning algo on the data and reports back the results in the form of some presentation or report.</li><li>Where the DS creates a usable piece of software for the stakeholders to consume the machine learning models.</li></ol><p>In this blog post, I’m attempting to display an example approach to the second aspect of a DS’s job i.e., creating some software that can be used by the stakeholders. Specifically, we would create a web-service that can be queried to obtain the predictions from a machine learning model. The post is mostly intended for machine learning practitioners who would like to go beyond only developing models.</p><p><strong>Tech-stack</strong>: Python, <a href="http://flask.pocoo.org/">Flask</a>, <a href="https://www.docker.com/">Docker</a>, <a href="https://aws.amazon.com/ec2/">AWS ec2</a></p><p>The workflow can be broken down into following basic steps:</p><ol><li>Training a machine learning model on a local system.</li><li>Wrapping the inference logic into a flask application.</li><li>Using docker to containerize the flask application.</li><li>Hosting the docker container on an AWS ec2 instance and consuming the web-service.</li></ol><p><strong>DISCLAIMER</strong>: The system presented here is light years away from what a commercial production system should look like. The key takeaways from this blogpost should be the development workflow, acquaintance with the tech stack and getting the first taste of building an ML production system.</p><p>Let’s start with the first step.</p><h4>Training a machine learning model on a local system</h4><p>We need <strong>some </strong>machine learning model that we can wrap in a web-service. For demo purpose, I chose a logistic regression model to do multiclass classification on iris dataset (Yep, super easy! #LazinessFTW). The model was trained on a local system using python 3.6.</p><p>Using the familiar scikit-learn, the above mentioned model can be trained quickly. For model development, refer the notebook ‘Model_training.ipynb’ in the <a href="https://github.com/tanujjain/deploy-ml-model">github repo</a> for this blog. There are only 2 important aspects of model development that I would like to highlight:</p><ol><li>The model file generated after training is stored as a pickle file which is a serialized format for storing objects. (In the repo, the file is named ‘iris_trained_model.pkl’)</li><li>The inference call (.predict()) call requires 4 features per test sample in the form of a numpy array.</li></ol><h4>Wrapping the inference logic into a flask web service</h4><p>Now that we have the trained model file, we are ready to query the model to get a class label for a test sample. The inference is as simple as calling a predict() function on the trained model with the test data. However, we would like to build the inference as a web-service. For this purpose, we would use Flask.</p><p>Flask is a powerful python microwebserver framework that allows us to build REST API based web-services quickly with minimum configuration hassle. Let’s dive into the code:</p><p>a. First, let’s define a simple function to load the trained model file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8875e87207f598599e94f8c0bba08b44/href">https://medium.com/media/8875e87207f598599e94f8c0bba08b44/href</a></iframe><p>Here, we define a global variable called ‘model’ and populate it within the load_model() function. The purpose of using a global variable will become clear shortly.</p><p>b. Next, we instantiate a Flask object called ‘app’:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/42f0f5fd893dcc6df4dce67f48b63fc0/href">https://medium.com/media/42f0f5fd893dcc6df4dce67f48b63fc0/href</a></iframe><p>c. Now, we define a home endpoint, which when hit, returns a ‘Hello World!’ message.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4a2d3623467aa69856ef7affe4939bdd/href">https://medium.com/media/4a2d3623467aa69856ef7affe4939bdd/href</a></iframe><p>Notice the use of app.route decorator.</p><p>d. Now, we define a ‘predict’ endpoint. The endpoint accepts a ‘POST’ request wherein the test data on which we wish to get a prediction is received by the endpoint. Keeping things simple, the function works only when a single test sample needs to be predicted (won’t work if multiple samples need to be predicted in a single call to the endpoint).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e35bbb8ad5f2b0ba9bc5fb0b015a9dcf/href">https://medium.com/media/e35bbb8ad5f2b0ba9bc5fb0b015a9dcf/href</a></iframe><p>Notice the direct call to the predict function through the ‘model’ variable.</p><p>e. Finally, declare the main function:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e0dcba807384a9e9dbb2593fd8b6c6ef/href">https://medium.com/media/e0dcba807384a9e9dbb2593fd8b6c6ef/href</a></iframe><p>Here, a call to the load_model() function ensures that the variable ‘model’ is populated with the trained model attributes (and hence the need for a global model variable). So, there is no need to load the model repeatedly with every call to the predict endpoint. This allows the web-service to be quick. The response is returned as a string which is the predicted class label.</p><p>The complete flask specific code is as below:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cc000cbdc0b7db2d8ce67b21149c6ac8/href">https://medium.com/media/cc000cbdc0b7db2d8ce67b21149c6ac8/href</a></iframe><p>At this point, the web-service is ready to be run locally. Let’s test this.</p><p>Execute the command python app.py from the terminal. Go to the browser and hit the url 0.0.0.0:80 to get a message Hello World! displayed. This corresponds to the home endpoint return message.</p><p><strong>NOTE</strong>: A permission error may be received at this point. In this case, change the port number to 5000 in app.run() command in app.py. (Port 80 is a privileged port, so change it to some port that isn&#39;t, eg: 5000)</p><p>Next, let’s test if we can get predictions using this web-service using the following curl post request on the terminal:</p><pre>curl -X POST \<br>   0.0.0.0:80/predict \<br>   -H &#39;Content-Type: application/json&#39; \<br>   -d &#39;[5.9,3.0,5.1,1.8]&#39;</pre><p>The curl request posts one test sample [5.9,3.0,5.1,1.8]to our web-server and returns a single class label.</p><h4>Using docker to containerize the flask service</h4><p>Up to this point, we have a web-service that runs locally. Our ultimate intention is to be able to run this piece of code on a cloud virtual machine.</p><p>In the Software Development world, there is a famous justification given by a developer whose code was found to be broken by a tester: ‘<em>But it worked on my machine!</em>’. The problem portrayed here can usually be attributed to a lack of consistent environment that runs the software across different machines. Ideally, our code itself should be independent of the underlying machine/OS that runs it. Containerization allows developers to provide such isolation.</p><p><em>How is it important here?</em></p><p>Our intention is to run our web-service on a cloud VM. The cloud VM itself may run any OS. Containerization of our web-server allows us to avoid the trouble of running into environment related issues. If the containerized code works on one machine, it will surely run on another irrespective of the characteristics of the machine. Docker is the most famous containerized technology out there at this point and we will be using the same here. For a quick tutorial on docker, check this <a href="https://docker-curriculum.com/">link</a>.</p><p>Let’s dive into the Dockerfile that comprises a set of instructions for docker daemon to build the docker image.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3f966ea89cf163a18752c8cc91a966aa/href">https://medium.com/media/3f966ea89cf163a18752c8cc91a966aa/href</a></iframe><p>We pull the base docker image from python dockerhub repo on which our specific build instructions are executed. The COPY commands are simply taking specific files from the current folder and copying them over to a folder called ‘deploy’ within the docker image we are trying to build. In addition to app.py and model file, we also need a requirements file that lists specific versions of python packages we use to run our code. The WORKDIR command changes the working directory to ‘deploy/’ within the image. We then issue a RUN command to install specific python packages using the requirements file. The EXPOSE command makes the port 80 accessible to the outside world (our flask service runs on port 80; we need this port inside the container to be accessible outside the container).</p><p>Issue the build command to end up with a docker image:</p><pre>docker build -t app-iris .</pre><p>(Don’t forget the period at the end of the command).</p><p>Use command ‘docker images’ to see a docker image with a docker repository named ‘app-iris’ created. (Another repository named python will also be seen since it is the base image on top of which we build our custom image.)</p><p>Now, the image is built and ready to be run. We can do this using the command:</p><pre>docker run -p 80:80 app-iris .</pre><p>The above commands uses -p flag to map port 80 of the local system to the port 80 of the docker container for the redirection of traffic on local HTTP port 80 to port 80 of the container. (If you are using local port 5000 instead of port 80, change the port mapping part of the command to 5000:80).</p><p>Let’s test if this works by hitting the URL: <a href="http://0.0.0.0/">http://0.0.0.0:80</a> on the browser which should display ‘Hello World!’ which is the home endpoint output message (If port 5000 is used, modify the http port to 5000 in the url). Also, use the curl request mentioned earlier to check if the predicted class label is returned.</p><h4>Hosting the docker container on an AWS ec2 instance</h4><p>We already have a containerized application that works on our local system. Now, what if someone else wishes to consume the service? What happens if we need to build an architectural ecosystem around the service that needs to be available, automated and scalable? It’s easy to see that having a web-service running locally would be a very bad idea. So, we wish to host the web-service somewhere on the internet to fulfil the requirements we listed. For this blog, we choose to host our service on an <a href="https://aws.amazon.com/ec2/">AWS ec2 instance</a>.</p><p>As a prerequisite, one needs to have an AWS account for using the ec2 instance. For new users, there are several AWS resources that are available for free for a period of 1 year (usually up to some limit). In this blog, I would be using a ‘<em>t2.micro</em>’ ec2 instance type which is free tier eligible. For users who have exhausted their AWS free-tier period, this instance costs around 1 cent(USD) per hour at the time of writing this blog; a super negligible amount to pay.</p><p>Let’s start with the process.</p><p>Log into the AWS management console and search for ec2 in the search bar to navigate to EC2 dashboard.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xZhZR2zICk9MmJjJhjkg8Q.png" /><figcaption>Search for ec2 on the aws management console</figcaption></figure><p>Look for the below pane, select ‘Key Pairs’ and create one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/332/1*nsD-JvvpSN_ZvkHYIWcsfw.png" /><figcaption>Select Key Pairs for looking at existing key pairs and creating new ones</figcaption></figure><p>This will download a ‘.pem’ file that is the key. Save this file somewhere safely. Now navigate to the location of this file on your system and issue the below command with key file name replaced by yours:</p><pre>chmod 400 key-file-name.pem</pre><p>This commands changes permissions on your key pair file to <a href="https://chmodcommand.com/chmod-400/">private</a>. The use of key pairs will be explained later.</p><p>Next, click ‘Launch Instance’ on the EC2 dashboard:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qw5-8sj1VJAVFloM9dfcCQ.png" /><figcaption>Launch ec2 instance</figcaption></figure><p>Choose the Amazon Machine Instance (AMI) from the list of options. An AMI determines the OS that the VM will be running (plus some other stuff we don’t care about at this point). For this blog, I chose ‘Amazon Linux 2 AMI’ which was the default selection.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aWcetNlmWuiYZHZl33_0rA.png" /><figcaption>Choosing AMI</figcaption></figure><p>The next screen allows you to select the instance type. This is where the hardware part of the VM can be selected. As mentioned previously, we will work with ‘t2.micro’ instance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gGCTKQ7AIKVVnKXYN2rgvA.png" /><figcaption>Selecting instance type</figcaption></figure><p>You can select ‘Review and Launch’ that takes you to ‘Step 7: Review Instance Launch’ screen. Here, you need to click the ‘Edit Security Groups’ link:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q7aRC_DWQtnJqJeMnIs1UQ.png" /><figcaption>Security Groups</figcaption></figure><p>You now have to modify the security group to allow HTTP traffic on port 80 of your instance to be accessible by the outside world. This can be done by creating a rule. At the end, you should end up with such a screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xv_PjIpjIs2sKKMYwiMmxQ.png" /><figcaption>Adding HTTP rule to security group</figcaption></figure><p>In the absence of this rule, your web-service will never be reachable. For more on security groups and configuration, refer <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html">AWS documentation</a>. Clicking on the ‘Launch’ icon will lead to a pop up seeking a confirmation on having a key-pair. Use the name of the key pair that was generated earlier and launch the VM.</p><p>You would be redirected to a Launch screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eS4BvycaNrZduXjIZ8oRLw.png" /><figcaption>Launch Status of ec2 instance</figcaption></figure><p>Use the ‘View Instance’ button to navigate to a screen that displays the ec2 instance being launched. When the instance state turns to ‘running’, then it is ready to be used.</p><p>We will now ssh into the ec2 machine from our local system terminal using the command with the field public-dns-name replaced with your ec2 instance name (of the form: <a href="/service/mailto:ec2-user@ec2-198-51-100-1.compute-1.amazonaws.com">ec2–x–x–x–x.compute-1.amazonaws.com</a>) and the path of the key pair pem file you saved earlier.</p><pre>ssh -i /path/my-key-pair.pem <a href="/service/mailto:ec2-user@ec2-198-51-100-1.compute-1.amazonaws.com">ec2-user@</a>public-dns-name</pre><p>This will get us into the prompt of our instance where we’ll first install docker. This is required for our workflow since we will build the docker image within the ec2 instance (There are better, but slightly complicated alternatives to this step). For the AMI we selected, the following bunch of commands can be used:</p><pre><strong>sudo amazon-linux-extras install docker<br>sudo yum install docker<br>sudo service docker start<br>sudo usermod -a -G docker ec2-user</strong></pre><p>For an explanation of the commands, check the <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html">documentation</a>.</p><p>Log out of the ec2 instance using the ‘exit’ command and log back in again using the ssh command. Check if docker works by issuing the ‘docker info’ command. Log out again or open another terminal window.</p><p>Now let’s copy the files we need to build the docker image within the ec2 instance. Issue the command from your local terminal (not from within ec2):</p><pre>scp -i /path/my-key-pair.pem file-to-copy <a href="/service/mailto:ec2-user@ec2-52-59-206-245.eu-central-1.compute.amazonaws.com">ec2-user@</a>public-dns-name:/home/ec2-user</pre><p>We would need to copy requirements.txt, app.py, trained model file and Dockerfile to build the docker image as was done earlier. Log back into the ec2 instance and issue ‘<em>ls</em>’ command to see if the copied files exist. Next, build and run the docker image using the exact same commands that were used in the local system (Use port 80 at all locations in the code/commands this time).</p><p>Hit the home endpoint from your browser using the public dns name to see the familiar ‘<em>Hello World!</em>’ message:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7Hs1xpTVm-rQXIUS-7zQPg.png" /><figcaption>Home endpoint works from the browser (I used my ec2 public-dns-name in the address bar)</figcaption></figure><p>Now send a curl request to your web-service from local terminal with your test sample data after replacing the public-dns-name by yours:</p><pre>curl -X POST \<br>public-dns-name:80/predict \<br>-H &#39;Content-Type: application/json&#39; \<br>-d &#39;[5.9,3.0,5.1,1.8]&#39;</pre><p>This should get you the same predicted class label as the one you got locally.</p><p><strong>And you are done!</strong> You can now share this curl request with anyone who wishes to consume your web-service with their test samples.</p><p>When you no longer need the web-service, <strong>do not forget to stop or terminate the ec2 instance</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PdWopIwPzQiTsHqR0S5HAQ.png" /><figcaption>Stop or terminate the ec2 instance to avoid getting charged</figcaption></figure><h3>Some additional thoughts</h3><p>This is a super basic workflow intended for ML practitioners itching to go beyond model development. A huge number of things need to be changed to make this system into one that is more suited to a real production system. Some suggestions (far from complete):</p><ol><li>Use a Web Server Gateway Interface (WSGI) such as gunicorn in the flask app. Bonus points for using nginx reverse proxy and async workers.</li><li>Improve security of the ec2 instance: The service is currently open to the entire world. Suggestions: Restrict access to a set of IPs.</li><li>Write test cases for the app: Software without testing = Shooting yourself in the leg and then throwing yourself in a cage full of hungry lions all the while being pelted with stones. (Moral: Do not float a production software without thoroughly testing first)</li></ol><p>A lot more could be added to the above list, but maybe that’s something for another blog post.</p><p>Github repo: <a href="https://github.com/tanujjain/deploy-ml-model">https://github.com/tanujjain/deploy-ml-model</a></p><p>It would be great to hear your feedback!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd58b771fdcf" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/simple-way-to-deploy-machine-learning-models-to-cloud-fd58b771fdcf">Simple way to deploy machine learning models to cloud</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[Parallelisation In Python — An Alternative Approach]]></title>
            <link>https://medium.com/idealo-tech-blog/parallelisation-in-python-an-alternative-approach-b2749b49a1e?source=rss-15510b6546e------2</link>
            <guid isPermaLink="false">https://medium.com/p/b2749b49a1e</guid>
            <category><![CDATA[parallel-computing]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Tanuj Jain]]></dc:creator>
            <pubDate>Wed, 04 Jul 2018 09:50:25 GMT</pubDate>
            <atom:updated>2018-07-04T11:21:46.816Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7tlGX31xgQVz9QCeoAy_8Q.png" /></figure><p>One of the biggest, ever-present banes of a data scientist’s life is the constant wait for the data processing code to finish executing. The problem with slow code affects almost every step of a typical data science pipeline: data collection, data pre-processing/parsing, feature engineering, etc. Many times, the gigantic execution times even end up making the project infeasible and more often than desired, forces a data scientist to work with only a subset of data, depriving him/her of the insights and performance improvements that could be obtained with a larger dataset.</p><p>One of the tools that could mitigate this problem and speed up data science pipelines is <strong>parallelisation</strong> through <a href="https://docs.python.org/3/library/multiprocessing.html"><strong>multiprocessing</strong></a> or <a href="https://docs.python.org/3/library/threading.html"><strong>multithreading</strong></a>.<br>In this blogpost, I am sharing my experience of solving a <a href="https://medium.com/p/b2749b49a1e/#d8af">data collection</a> problem by using an approach that utilizes multiprocessing with python. The main achievement of this exercise was the development of a general approach for parallelisation wherein a code template was created and used. Using the approach, I was able to <strong>reduce</strong> the <strong>execution time from several days to under an hour for a </strong><a href="https://medium.com/p/b2749b49a1e/#d8af"><strong>practical problem</strong></a><strong> </strong>discussed in this blog<strong>.</strong> The same code template has been tested for other use cases and has proven to be effective.</p><h4>The Vanilla approach to parallelisation</h4><p>This is the usual way parallelisation is applied in python.</p><ol><li>Get a list of all items for which the same task needs to be performed independent of other items.</li><li>Workers execute in parallel and each worker applies the task logic to a single item in this list and obtains the result for this specific item.</li><li>After a worker finishes the task on a single item, it picks up another item from the global list of items and applies the task logic to it.</li><li>The process goes on until the list of items gets exhausted.</li></ol><h4>The Splitting approach to parallelisation (New)</h4><p>This is the new parallelisation approach.</p><ol><li>Get a list of all items for which the same task needs to be performed independent of other items.</li><li>Divide the list of items into sublists.</li><li>Pass one sublist to each worker/thread so that each worker/thread iterates over its own sublist in a serial manner and performs the required task.<br>The workers themselves execute in parallel. Thus, even though each worker is performing the task in a serial manner, the workers are working simultaneously on their own sublist of tasks and hence, parallelisation is achieved as the net effect. Therefore, the approach is actually a mix of parallel and serial processing.</li></ol><p>In contrast, the <strong>Serial approach</strong> works as follows:</p><ol><li>Get a list of all items for which the same task needs to be performed.</li><li>Pick up an item in the list and execute the task for this item.</li><li>Pick up the next item in the list. The process goes on until the list gets exhausted.</li></ol><h3>Problem Description</h3><p>For one of my recent projects, I was faced with the problem of downloading a huge number of emails from a mail server (~3 million). An attempt to download the mails serially resulted in 100k emails being downloaded after 5 days of run time. The email download time clearly needed to be reduced for which parallelisation seemed like an obvious candidate.</p><p>For the purpose of code demonstration, I am downloading emails from a public <a href="https://whatismyipaddress.com/imap">imap</a> (created a fake email id on <a href="https://mail.yandex.com/">yandex</a> and filled it with 1200 emails) using <a href="https://medium.com/p/b2749b49a1e/#6e30"><strong>Multiprocessing with the splitting approach</strong></a>. Later on, the same task is going to be performed with all the variants described above as well (code not shown for these in the post). My machine is a MacBook Pro with 2.8 GHz i7 quad core processor.</p><h4>Multiprocessed splitting</h4><ol><li>Get a list of all mail ids present in the mail box after establishing connection with the imap server.</li><li>Select the number of multiprocessing workers (I generally set this to twice the number of logical cores on my machine).</li><li>Divide the list of mail-ids into sublists.</li><li>Launch multiple processes where each process corresponds to a worker and has a sublist of items it must operate upon. Each process establishes an independent connection with the imap server and calls a function that performs the download.</li><li>Each worker downloads the mails serially using the sublist of mail-ids it possesses.</li></ol><p>Let us develop the logic for each of these steps:</p><p>1. Get a list of all mail ids present in the mail box after establishing a single connection with the imap server.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3290cf664c2e94376f35a398b6fa6545/href">https://medium.com/media/3290cf664c2e94376f35a398b6fa6545/href</a></iframe><p>2. Select the number of multiprocessing workers (I generally set this to twice the number of logical cores on my machine).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a7101349a3a276cb9afaf27cbe9cabdf/href">https://medium.com/media/a7101349a3a276cb9afaf27cbe9cabdf/href</a></iframe><p>3. Divide the list of mail-ids into sublists.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fdce2156271e08e5b8c64c23a3b1ee76/href">https://medium.com/media/fdce2156271e08e5b8c64c23a3b1ee76/href</a></iframe><p>4. Launch multiple processes where each process corresponds to a worker and has a sublist of items it must operate upon. Each process establishes an independent connection with the imap server and calls a function that performs the download.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/86a09fddc5ce6f6ae287689770c2bb2b/href">https://medium.com/media/86a09fddc5ce6f6ae287689770c2bb2b/href</a></iframe><p>The get_mails function launches a pool of workers and passes a list of sublists to mp_process function such that each worker gets one sublist of mail-ids to process. Virtually, each worker is executing its own copy of the mp_process function with its own specific sublist of mail-ids that it needs to download. All the workers execute in a parallel fashion with no dependence on each other.</p><p>5. Each worker downloads the mails serially using the sublist of mail-ids it possesses.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e8868aed022c1ae574a58b48359b9dcc/href">https://medium.com/media/e8868aed022c1ae574a58b48359b9dcc/href</a></iframe><p>This function iterates over the sublist of mail-ids it receives from its calling process and begins downloading the sublist serially.</p><p>The complete code can be found in the <a href="https://medium.com/p/b2749b49a1e/#1f19"><strong>appendix</strong></a><strong>.</strong></p><p>The general <strong>template</strong> that can be reused and adapted to various tasks is:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2d1ca6f2711f83f2a4efc21957863bbc/href">https://medium.com/media/2d1ca6f2711f83f2a4efc21957863bbc/href</a></iframe><h4>EXPERIMENTS</h4><p>Several methods were compared in terms of execution time:</p><ol><li>Serial download (Serial).</li><li>Multiprocessing download using the vanilla approach (MP-V).</li><li>Multiprocessing download using the splitting approach (code shown above) (MP-Sp).</li><li>Multithreaded download using the vanilla approach (Mth-V).</li><li>Multithreaded download using the splitting approach (Mth-Sp).</li></ol><p>The number of threads/workers were also varied across different runs of multithreading/multiprocessing approaches (X-axis of the graph below).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nE9QXqrPlbY8l_GJ1eMg-Q.png" /></figure><h4>RESULTS</h4><p>The following conclusions can be drawn from the experiments:</p><ol><li>The splitting approach always outperforms the vanilla approach and the serial approach regardless of the number of workers/threads.</li><li>The vanilla approach performs worse than the serial approach if the number of threads/workers are less than 8.</li><li>There is no real difference between the performance of multithreading and multiprocessing.</li><li>The vanilla approach tends to give better gains in terms of execution time as the number of workers/threads increase compared to the splitting approach. (Further experiments were carried out so that the vanilla approach uses more workers/threads, but the splitting approach was always better even when it used fewer workers/threads.)</li></ol><p><em>Therefore, using a small number of workers/threads with the splitting approach ensures the quick completion of a task while only occupying a small amount of resources.</em></p><h4>SUMMARY</h4><p>An alternative approach of splitting item list into sublists for achieving better parallelisation in python was introduced and shown to be better than the usual approach wherein each item in the list is picked and processed by workers/threads. A practical example was used to demonstrate the performance improvement with the use of the splitting approach. Furthermore, a template to use this approach was developed which makes it possible to apply it regardless of the nature of the underlying task.</p><h4>APPENDIX</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3c0fed66a26f7f396d891bd164e4e01e/href">https://medium.com/media/3c0fed66a26f7f396d891bd164e4e01e/href</a></iframe><h4>ADDITIONAL QUIRKS</h4><ol><li>The experiments were carried out on a rather small set of data (only 1200 emails with 2KB per email). It is possible that vanilla approach could outperform the splitting approach for much larger datasets (Splitting approach seemed promising with the initial experimentation, so I ended up choosing it for my final task which involved downloading hundreds of Gigabytes of data across 3 million mails).</li><li>Multiprocessing vs. Multithreading: As can be seen from the results, these two approaches performed similarly. However, as far as python is concerned, <a href="https://docs.python.org/3/glossary.html#term-global-interpreter-lock"><em>Global Interpreter Lock (GIL)</em></a> usually ends up hampering the multithreading performance and it is usually difficult to ascertain the usability of multithreading for a given use case in python. Multiprocessing, on the other hand, suffers from no such drawbacks and hence, I tend to prefer multiprocessing over multithreading in most cases. But then again, multiprocessing eats up a lot more resources than multithreading, so there’s usually a trade-off between multiprocessing and multithreading when it comes to python. (I chose multiprocessing for my final task).</li><li>Several runs were done for each experiment. During one of the runs, a strange behaviour was observed with the vanilla approach (for both multithreading and multiprocessing) — About 95% of tasks got executed rather quickly, but the last 5% took an extremely long time to finish. This issue wasn’t reproduced.</li></ol><blockquote><em>The results are largely empirical in nature and a satisfactory explanation of why splitting approach outperforms the vanilla approach still evades me. I would be happy to hear any ideas on this.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b2749b49a1e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/idealo-tech-blog/parallelisation-in-python-an-alternative-approach-b2749b49a1e">Parallelisation In Python — An Alternative Approach</a> was originally published in <a href="https://medium.com/idealo-tech-blog">idealo Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>